news 2026/9/18 4:05:17

拆解微信小游戏包体,反推Cocos工程结构——以切水果跑酷为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
拆解微信小游戏包体,反推Cocos工程结构——以切水果跑酷为例

先说点实际的:不少Cocos开发者碰到一个好玩的微信小游戏,脑子里第一反应不是“哇这游戏好玩”,而是“这游戏工程是怎么组织的?资源怎么加载这么顺?”如果你也是这种体质,这篇文章就是写给你的。

我以一个切水果跑酷类微信小游戏为案例,完整走一遍“从包里反推出Cocos工程结构”的逆向学习思路。不是说让你去扒别人商业代码搞事情,而是把反推过程当成一种学习手段:通过分析构建产物、资源映射、代码模块,反推对方当初的目录设计、模块划分、资源管理策略,然后用到自己项目里。这套方法论比你看十遍官方文档都管用。

整个过程我会拆成六个部分:先搞清楚微信小游戏包的本质是什么,再到取包、解包、识别版本、还原资源树,最后落到“切水果跑酷”这类游戏的具体工程结构解剖,以及如何把这些经验内化成自己的开发能力。

1. 先想清楚:你逆向的是“小游戏包体”,不是一台保险柜

1.1 微信小游戏包的本质:浏览器里的JS应用

很多人一听到“逆向”两个字,脑海里全是IDAA、反汇编、二进制补丁的硬核画面。微信小游戏根本没这么玄乎。小游戏跑在微信提供的运行时里,本质上就是一个 JavaScript 应用加上一堆资源文件:图片、音频、图集、配置JSON、预制体数据。Cocos Creator 构建之后,把引擎代码、游戏逻辑、场景数据打包进几个 JS 文件,资源文件按规则压缩进 assets 目录,整体合成一个 .wxapkg 包。

所以“逆向微信小游戏”的第一步,不是反汇编,而是拆包看文件。你把 .wxapkg 解压,得到的是一个相对规整的文件树,里面 JavaScript 代码占大头,资源文件直接躺在目录里,很多连加密都没做。这是学习工程结构最好的起点:你能同时看到代码、资源、配置,三者互相印证。

1.2 为什么选择“切水果跑酷”做案例

切水果跑酷类游戏是一个特别适合用来做逆向学习的样本。为什么?

这类游戏的功能模块非常典型,拆开之后,几乎可以把 Cocos 游戏开发的核心模块覆盖一个遍:

  • 水果切割系统:涉及碰撞检测、刀光轨迹、切片渲染、粒子特效;
  • 跑酷生成系统:涉及无尽地形生成、障碍物随机、动态加载与复用;
  • 结算与连击系统:涉及状态管理、UI更新、数据存储;
  • 音频与表现系统:涉及音效管理、动画触发、屏幕震动;
  • 资源组织:涉及图集、预制体、合图、分包策略。

如果你能把这样一款游戏的构建产物读懂,你对 Cocos Creator 在微信小游戏平台上的“工程化习惯”就会有很直观的理解:哪些代码放场景、哪些放组件、哪些用配置表驱动、资源怎么走 db://assets 路径。这些全是平时自己闭门造车很难意识到的问题。

还有一点很重要——这个品类代码量适中,不会大到让你无从下手,模块边界又足够清晰。你反推出来的工程结构,几乎可以直接映射到一个正经中大型 Cocos 项目的组织方式上。拿它练手,性价比非常高。

2. 拿到包体的完整链路:从微信到本地

2.1 包体在设备上的常见存放路径

先解决一个最实际的问题:怎么拿到一个微信小游戏的实际包体文件。

在小游戏运行过程中,微信会把下载到的小游戏包缓存在本地。Android 设备上,小游戏包一般存放在微信的私有目录下,路径结构类似:

/data/data/com.tencent.mm/MicroMsg/<账号hash>/appbrand/pkg/

这个目录下能看到一堆 .wxapkg 文件,文件名通常是一长串 hash。不过这个路径涉及 Android 私有数据目录,一般需要 root 或 debug 权限才能直接访问,而且微信的目录结构随版本变化非常大,不能把它当成一个稳定入口。

真正稳妥的途径是这几个:

  • 你自己开发的小游戏,直接在微信开发者工具里构建并预览,构建产物就在项目目录的build/wechatgame下,这是最简单也最推荐的学习方式;
  • 你认识这个游戏的开发者,拿到了体验版包或者构建产物,可以做朋友间的技术交流;
  • 有些游戏会直接提供开发演示包,这种拿来分析完全没问题。

我建议你搞清楚一个事实:你现在分析的这个包,到底是从哪条渠道拿到的,合法边界在哪。这不是废话,这决定了你后面所有分析动作的性质。

2.2 解包 .wxapkg 与还原文件树

拿到 .wxapkg 之后,需要把它解开。.wxapkg 本质是一个自定义格式的压缩包,文件头部有索引区,记录每个文件的相对路径和偏移量。社区里已经有非常成熟的解包工具,有的基于 Node.js,有的是 Python 脚本,也有一些桌面图形工具。不管用哪个,操作逻辑都一样:

  • 读取文件头,校验文件是否带加密标记;
  • 解析索引区,拿到文件列表;
  • 按偏移量抽取文件内容到本地目录。

执行完后,你会看到一个大概这样的文件树:

. ├── game.js # 入口 JS,Cocos 整个游戏的启动点 ├── game.json # 小游戏配置,包含分包、渲染模式等 ├── assets/ # 游戏资源目录 │ ├── main/ # 主包资源 │ ├── textures/ # 纹理、图集 │ ├── audio/ # 音频 │ └── ... ├── cocos-js/ # Cocos 引擎和项目代码编译产物 ├── src/ # 部分版本会出现,包含 setup 等逻辑 ├── subpackages/ # 分包目录 └── project.config.json # 小游戏项目配置

这个树形结构有可能因为 Cocos 版本不同而略有差异。Cocos Creator 2.x 构建的产物可能更偏向于把代码集中在 game.js 和若干模块文件里,而 3.x 的构建产物则倾向把代码放在 cocos-js 目录下按 chunk 拆分。但核心规律不变:一定有一个入口 JS 文件,一定有一个 assets 资源目录,一定有一个记录配置和资源映射的文件。

2.3 识别Cocos构建模式的“指纹”

解包之后,别急着看代码。先花十分钟确认这三件事:引擎版本、构建模式、资源加密程度。

识别引擎版本的常见方式有:

  • 看构建产物里自动生成的配置字段,里面往往带有引擎版本信息;
  • 看引擎代码的特征 API。Cocos Creator 2.x 下cc.Classcc.Node这类全局对象特征非常明显,而 3.x 的核心代码改为模块化封装,你会看到类似cc._decorator.ccclass这样更偏模块内部的调用方式;
  • 看构建产物的文件布局逻辑。2.x 时代构建之后的资源路径常见为assets/xxx.png的扁平化对应关系,3.x 则大量出现assets/main/textures/...这种带分组的目录。

构建模式主要区分普通构建分包构建。如果游戏只有一级包,所有东西都堆在主包 assets 下;如果用了分包加载,你会看到subpackages/目录下又有一套独立的 js 和资源树,甚至每个分包还有自己的配置文件。这个信息很关键,后面讲工程结构时你会用到。

资源加密程度也要看一眼。有的小游戏做了资源加密,assets 下的文件头被改过,或者直接包了一层二进制壳。有的没做,图片、音频、plist 原样躺在里面,这种是分析起来最舒服的。做逆向学习其实无所谓,资源解不出来就看代码路径,代码总能看。

识别完这三样,你对这个游戏的“构建基因”就有底了。接下来就可以开始一层层还原工程结构。

3. 从 main.js 开始,一层层还原工程目录

3.1 JavaScript 混淆程度:最影响体验的一步

把 JS 文件用本地工具做一下格式化(beautify),你会立刻感受到不同游戏在代码保护上的态度差别。

一部分小游戏完全没做混淆,格式化后代码行号清楚、变量名可读,甚至连注释都有残留——这种情况下,你基本是在阅读对方源码的“编译后形态”。另一部分游戏做了变量名混淆,比如把gameManager变成a1b2c3,但函数名、字符串、类名这些往往还留在里面,尤其 Cocos Creator 的组件类名是通过装饰器注册的,字符串形式的类名很难被抹掉。

还有极少数用了更强的混淆,把控制流都拍扁了。这类项目就不太适合作为逆向学习案例,建议直接换一个目标,没必要跟混淆死磕。

以切水果跑酷这一类大多数休闲游戏来说,代码保护通常都比较弱。原因也很简单:这类游戏核心不是靠代码逻辑建立壁垒,而是靠美术资源、关卡设计、运营体系,团队普遍选择把工程成本花在迭代上而不是防破解上。

3.2 从 “db://assets/” 路径反推原始目录

格式化之后,搜索关键词db://assets/,Cocos Creator 项目里加载资源时用的都是这种虚拟路径格式。在构建产物里,这些路径通常会被保留在加载调用中。

你搜出来的每一条db://assets/...路径,基本就是原始工程资源目录的一个位点。比如你搜到:

this.loadPrefab("db://assets/prefabs/FruitSlice.prefab");

那你至少能确认三件事:

  • 原始工程里有一个assets/prefabs/目录;
  • 里面有一个名为FruitSlice.prefab的预制体;
  • 代码里有一段逻辑需要动态加载水果切片预制体。

把所有搜索结果汇总,按路径前缀归类,你这个游戏的预制体目录、场景目录、音频目录七七八八就能列出来了。这个方法特别适合处理不知道从哪下手的情况——你不用猜对方怎么组织的,路径字符串会直接告诉你答案。

3.3 用 settings.json 反推资源映射表

除了代码里暴露的路径,资源台账才是还原工程结构的重头戏。

Cocos Creator 构建之后,会生成一个配置文件(不同版本叫法略有差异,常见的有 settings.json、application.js 里的部分字段等),里面记录了所有资源的 UUID 到压缩后文件的映射关系。这份映射表是你还原资产目录最关键的依据。

在你“拿到包体,解包,格式化代码”之后,工具里打开这个文件,你能看到类似这样的信息结构:

{ "uuids": [ "uuid字符串": "压缩资源路径" ], "scene": { "启动场景uuid": "assets/scene/MainScene.scene" } }

对照这份映射表,你可以把资产目录还原到非常细的程度:

  • 哪些资源属于启动场景;
  • 哪些是预制体、图集、音频;
  • 主包和分包分别挂载了哪些资源;
  • 整个项目的目录分组习惯。

这一步做完,你手里就有了一张“项目资源地图”,后面的一切分析都在这张地图上展开。

3.4 从 ccclass 装饰器识别代码模块

代码层面,Cocos Creator 开发时用的 TypeScript 写的组件类,在编译成 JavaScript 后虽然变量名可能被改变,但组件注册时用的类名字符串一般会原样保留。

Cocos Creator 3.x 中,一个典型的组件类编译后会长这样:

__decorate([ cc._decorator.ccclass("FruitSliceController"), cc._decorator.executeInEditor(true) ], FruitSliceController.prototype);

ccclass("FruitSliceController")括号里的字符串就是原始工程里的类名。你直接在代码里搜ccclass(,能把所有组件类名列出来,再结合每个类里调用的节点操作、加载的路径、监听的属性,就能还原出这个类在原始工程里大概承担什么职责。

如果你能找到properties的定义,那就更好了——序列化属性往往对应预制体上暴露的配置项,比如“水果切割力度”“连击窗口时长”“生成间隔”,这些参数名字能直接反映策划配置的维度和思路。

到这里,你已经完成了从“黑盒包体”到“半透明工程结构”的关键一步:资源路径、资源映射、代码类名三张网叠加,原始目录结构慢慢浮出水面。

4. 切水果跑酷类游戏的工程结构解剖

4.1 你应该会看到的目录模块

拿切水果跑酷这类游戏来说,结合前面的方法和这一类游戏的通用设计模式,我在实际拆解中汇总出的目录结构大概是这样的:

assets/ ├── scenes/ # 场景目录 │ ├── StartScene.scene # 启动/首页场景 │ └── GameScene.scene # 核心游戏场景 ├── prefabs/ │ ├── runner/ # 跑酷主角相关预制体 │ ├── fruits/ # 水果预制体 │ ├── obstacles/ # 障碍物预制体 │ ├── effects/ # 特效预制体(切割喷溅、得分飘字) │ └── ui/ # UI 预制体(结算面板、计分板) ├── textures/ │ ├── ui/ # UI 图集 │ ├── fruits/ # 水果贴图 │ └── bg/ # 背景贴图 ├── audio/ │ ├── bgm/ # 背景音乐 │ └── sfx/ # 音效 ├── config/ # 配置表目录 │ ├── level.json # 关卡配置 │ ├── fruit.json # 水果类型和切割参数 │ └── combo.json # 连击奖励配置 └── scripts/ # 如果能看到源码映射,通常反映出的结构是 ├── core/ │ ├── GameManager.ts │ ├── AudioManager.ts │ └── ResourceManager.ts ├── gameplay/ │ ├── RunnerController.ts │ ├── FruitSpawner.ts │ ├── TrackGenerator.ts │ └── ObstacleSpawner.ts ├── cutting/ │ ├── BladeTrail.ts │ └── FruitSliceManager.ts └── ui/ ├── ScoreDisplay.ts ├── ComboManager.ts └── UIManager.ts

注意,不同游戏的名字肯定有差异,但模块维度的划分逻辑基本是共通的。因为微信小游戏对包体大小敏感,开发者必须主动把不同功能的资源分门别类放好,才方便做分包、做远程资源、做按需加载。这个目录本身就是运营策略和技术选型的双重映射。

把核心逻辑分层的习惯,几乎是优秀休闲游戏的标配。你看到的 GameManager 是全局状态中枢,FruitSpawner 只负责生成和回收水果,BladeTrail 只管挥刀轨迹渲染,这些模块之间低耦合、单一职责的划分在构建产物里会体现得极其清晰。

4.2 从构建产物反推场景流程

对照代码里的场景加载调用和资源映射表里的场景列表,你能还原出这款游戏的完整启动时序。

切水果跑酷类游戏的场景流程,我拆过的里面普遍是这个套路:

  1. 启动场景:加载引擎配置、首屏展示、预下载关键资源;
  2. 大厅场景:展示入口按钮、设置、商店或排行榜,用户点击开始;
  3. 游戏主场景:游戏主循环所在,水果生成、跑酷逻辑、切割判定、计分全部在这里;
  4. 结算场景/弹窗:展示对局结果、奖励结算、重新开始。

在代码里你会看到类似director.loadScene("GameScene")的调用。微信小游戏环境下,场景切换本身有耗时,所以做得好的游戏会在启动场景里预加载游戏场景的资源,让切场景时几乎无感知。这是一个非常值得你做性能优化时借鉴的点。

另外一个值得留意的点是场景和组件的关系。有些团队偏好把几乎全部代码挂到一个场景里的根节点上,用“单场景多组件”的模式管理,减少场景切换开销;另一些团队偏好多场景跳转,逻辑更清晰。从构建产物里,观察loadScene的调用频率和场景资源的大小,就能反推对方的场景划分策略。

4.3 几个值得偷师的实现细节

拆这类游戏,有几个实现细节对你自己做产品很有借鉴价值。

对象池的使用是第一个重点。切水果跑酷里,水果生成频率极高,切割后又会产生果肉、粒子、飘字等大量临时节点,如果没有对象池管理,GC(垃圾回收)会直接拖垮帧率。在构建产物里你会经常看到类似PoolManagerFruitPoolgetFromPool()recycle()这类命名。对象池在Cocos里的实现方式通常是用NodePool,如果你在代码里搜到cc.NodePool的实例化调用,那基本可以确定对方把对象复用做到了常规武器级别。

第二个值得关注的是配置表驱动逻辑。好的休闲游戏不会把数值写死在代码里,而是用一个 JSON 配置来管理。你在反推代码时会发现很多this.levelConfig.fruitSpeedthis.fruitData.scores这种取值方式,数据来源大概率是一个挂在resources/config下的 JSON 文件。这个习惯非常值得学习:把数值放到配置表,策划调参不需要开发介入,代码里只有逻辑没有魔法数。

第三个是音效管理的单例化。几乎所有切水果类游戏都会把音频播放封装成一个全局管理器。反推代码里搜AudioManageraudioMgr,能看到它封装了背景音乐切换、音效播放、音量控制等接口。小游戏平台对音频通道有并发限制,做不好管理就容易出现吞音。看到这里,你基本就能理解为什么自己的项目有时音效不响——大概率是缺少统一封装的音频调度。

5. 把逆向结果消化成自己的开发能力

5.1 对比“别人怎么搭”和“自己怎么搭”

拆解完一个项目之后,最有价值的动作是把它的工程结构和自己的项目做一次对照。

回到自己的 Cocos Creator 工程,打开你的目录,问自己几个问题:

  • 我的资源是按场景散放的,还是按类型分目录的?
  • 我的预制体是直接在编辑器里摆好,还是通过动态加载 + 对象池创建?
  • 我的配置到底是写在代码里的魔法数,还是有独立的 JSON 配置表?
  • 我的分包策略和对方的差异在哪里?对方为什么把某个功能放进分包而不是主包?

这些问题没有标准答案。有些小游戏为了追求超小首包,会把大厅和资源全部后置,只留启动场景在主包;有些游戏则因为核心玩法密集,更适合把所有玩法资源压缩进主包。对照后你会意识到,工程结构没有最优,只有最匹配业务目标。

把对方的做法当作一种备选方案记进自己的工具箱,下次做架构决策时多一个参考维度,这才是逆向学习的核心收益。

5.2 从包体大小反推打包策略

微信小游戏对主包大小有严格限制,超了就没法过审上线。所以包体内资源的分布方式,直接反映了开发团队的分包和瘦身策略。

解包之后,你给主包和子包各列一个体积占比,能发现很多有意思的决策:

  • 如果启动场景里只有少量 UI 图集和一段首屏音效,说明团队把首屏体积控制做得很极致,通过分包或者远程资源把重资源延后加载;
  • 如果主包里有完整的水果贴图和粒子图集,但 BGM 全放在远程,说明团队认为音效对首包大小影响大,优先用远程化解决;
  • 如果多个场景共用一个大图集,说明团队用dragonBones或者自带图集工具做了合图,减少了 IO 请求次数。

这些策略从单个资源文件看不出来,但一旦你列出全包的资源分布清单,整个打包思路就浮出来了。学完这些,你再回自己项目里看构建面板,思路完全不一样。

5.3 一套可复用的逆向分析工作流

拆的项目多了之后,我总结了一套固定的流程,效率很高,分享给你:

  1. 收集包体:整理小游戏包,记录平台、版本、来源渠道;
  2. 解包还原:解出 .wxapkg,记录文件树结构;
  3. 格式化代码:JS 全部 beautify,方便搜索;
  4. 提取资源映射:找到 settings 类配置,导出 assets 映射表;
  5. 搜索关键路径:全局搜db://assets/,把资源加载路径归档;
  6. 提取组件清单:搜ccclass(,整理所有类名和文件归属;
  7. 画模块关系图:用文本工具把“场景-组件-资源”的调用关系画成一张思维导图;
  8. 沉淀笔记:把观察到的工程习惯、实现技巧、资源策略写成自己的逆向笔记。

这套流程跑完,一个游戏的工程结构基本被你吃透了。整个过程不要想着“抄”,而是想着“如果我来实现这个功能,我的做法和对方差在哪”。差距就是成长点。

第3步之后,我建议把资源映射表单独导出一个文件。很多人分析到一半会乱,因为你搜索代码是看逻辑,搜索资源映射又是另一套线索,两者在脑内没有合流。把资源映射表和组件清单放在同一个工作目录下,随时对照看,效率高很多。

6. 逆向学习的安全边界:学思路,别碰底线

6.1 哪些事能做、哪些事别碰

逆向学习虽好,但说到头,技术必须用在合法合规的框架里。

在你开始分析前,先区分清楚这几个场景:

  • 合理:分析自己开发的小游戏,优化构建产物和包体策略;
  • 合理:分析合作方明确授权、或公开提供演示包的案例;
  • 合理:基于公开技术资料学习微信小游戏和 Cocos 的运行机制;
  • 越界:直接解包竞品线上游戏,完整复制其美术资源和核心代码用于自己的商业项目;
  • 越界:篡改、破解他人小游戏的付费、验证、广告逻辑;
  • 越界:把解包产物二次分发、传播,损害原作者的合法权益。

前面所有分析代码结构、学习模块划分的方法,都必须在这一节划定的合理范围内进行。别人辛辛苦苦做的美术资源、关卡设计、数值配置,这些是受知识产权保护的内容,学习思路可以,直接拿走不行。

6.2 如何保护你自己的小游戏不被这样拆

学了逆向,反过来说说防守。

即使小游戏平台存在天然的“代码可见”属性,你仍然可以做一些基础的防护工作,提高自己产品的门槛:

  • 代码混淆:Cocos Creator 构建时开启 JavaScript 混淆选项,让变量名、函数名变成不可读状态;如果项目对体积不敏感,还能上更强的商业混淆引擎;
  • 资源加密:对敏感资源做自定义加密。Cocos 有资源加密方案,或者自己在上传前对二进制做一层 XOR 或 AES 变换,加载时再解密到内存;
  • 关键逻辑后置到服务器:游戏最重要的数值校验、排行榜、任务结算逻辑尽量放在服务端,客户端只做表现。这样即使客户端被逆向,对方也拿不到核心规则;
  • 分包 + 远程资源:把资源从首包移到远程 CDN,增加分析者获取完整内容的难度。

说到底,纯前端的游戏逻辑只能做到“防君子不防小人”。真正有价值的玩法创意、内容更新、运营能力,永远在你的服务器端和持续迭代里。对逆向学习保持开放心态,但对自己的核心资产要有底线意识。

最后分享一个我自己的习惯:每次完成一个项目的逆向分析,我都会把心得整理成一份“工程结构 + 资源策略 + 代码亮点”的三段式笔记,放进自己的知识库。翻回去看的时候,经常会发现某次记下的一个模块划分思路,在做新项目时变成了架构决策的重要参考。技术这东西,多看别人的实践,再回到自己的代码里反复验证,才是成长最快的方式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 4:04:53

GPU UMD学习指南:命令提交、同步与状态追踪实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 4:03:27

Oracle数据泵expdp/impdp实战:迁移、参数与报错排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 4:02:47

Kylin V10离线安装ffmpeg:本地yum源与源码编译全攻略

前几天有同事跑过来找我&#xff0c;说手里有一台Kylin V10的服务器放在内网环境里&#xff0c;机房是彻底断网的&#xff0c;他们想在机器上转一段监控视频&#xff0c;结果发现ffmpeg根本没装&#xff0c;yum install也直接失败。这种情况如果没处理过&#xff0c;第一次遇到…

作者头像 李华
网站建设 2026/9/18 4:02:12

XenDesktop技术白皮书精读:FMA架构与FlexCast交付模型实战指南

简介&#xff1a;PDF文档围绕Citrix XenDesktop 7.1展开&#xff0c;定位为企业级桌面虚拟化技术白皮书&#xff0c;面向需要规划远程办公、移动接入场景的IT管理员、解决方案架构师与虚拟化运维人员。内容以HTML5 Access的零接触客户端访问为主线&#xff0c;详细解析了其工作…

作者头像 李华
网站建设 2026/9/18 4:02:07

从偶发卡顿到根因:机器人RTOS优先级反转与调度抖动排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华