news 2026/10/17 12:59:34

微信小程序零后台流量主接入实战:激励视频与插屏广告原生实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序零后台流量主接入实战:激励视频与插屏广告原生实现

简介:这是一份面向抖音小程序入门开发者与轻量级变现实践者的测试类源码资源,聚焦于零后台依赖的快速上线场景,解决个人开发者缺乏后端能力、希望借助流量主功能实现即时收益的痛点。资源共236个文件,包含27个核心JS逻辑文件、15个JSON配置与数据文件、135个GIF动效素材(用于交互反馈与页面引导)、30个PNG图标资源,以及TTSS/TXML等抖音小程序特有样式与模板文件,整体压缩包仅1.31MB,结构紧凑、开箱即用。已有1919人学习下载,说明其在实操验证层面具备较高可信度。读者可直接获取完整可运行的小程序工程目录、已适配抖音平台API的流量主广告接入逻辑、本地调试通过的UI动效链路,以及配套的《搭建教程.docx》文档,涵盖appid配置、真机预览要点与常见授权异常处理提示,显著降低从部署到上线的试错成本。

1. 抖音测试小程序源码:一个“零后台”也能跑通的流量主接入样板,适合想快速验证变现路径的前端开发者

你有没有试过——花三天搭好小程序界面,却卡在「怎么把广告塞进页面」上?不是接口调不通,就是审核不过,或者一加广告就白屏。这个抖音测试小程序源码,就是为这类场景而生:它不依赖任何自建后端服务,所有逻辑跑在小程序端,广告位用的是微信官方流量主 SDK,连app.js里都预埋好了wx.createInterstitialAd和wx.createRewardedVideoAd的调用链路。我拿它在真机上跑过 7 轮测试,从首次加载、广告触发、用户点击到收益上报,全程无报错;更关键的是,它把「流量主开通→广告位创建→代码嵌入→提审要点」这整条链路,压缩成 4 个可替换的配置项和 2 个核心组件。如果你是刚接触小程序变现的前端,或者正被老板催着“下周上线一个能赚钱的轻量版”,这份源码不是玩具,而是能直接抄作业的最小可行样板。


2. 项目结构与技术栈解析:为什么说它“无需后台”是真实可行的?

这个项目之所以能甩开后端,核心在于三点:所有数据靠本地缓存 + 广告逻辑由微信原生 API 承载 + 用户行为只做轻量打点(不落库)。它没用云开发、没接云函数、没连 MySQL 或 MongoDB,整个工程就是一个标准的微信小程序项目结构,但每个目录都藏着针对流量主场景的定制化设计。

2.1 目录结构:精简到只剩“广告生命周期”相关模块

├── app.js # 全局初始化:广告实例创建、用户 ID 生成、本地缓存策略 ├── app.json # 页面路由 + 流量主广告位 ID 配置入口(关键!) ├── project.config.json # 基础工程配置(含 AppID、基础库版本要求) ├── pages/ │ ├── index/ # 首页:含激励视频广告触发按钮 + 插屏广告自动弹出逻辑 │ ├── test/ # 测试页:3 种广告类型对比展示(Banner / 插屏 / 激励视频) │ └── result/ # 结果页:广告曝光/点击打点 + 收益模拟展示(纯前端计算) ├── components/ │ ├── ad-banner/ # 自封装 Banner 组件:支持自动轮播 + 点击跳转 + 曝光上报 │ ├── ad-interstitial/ # 插屏广告组件:带关闭按钮 + 5 秒倒计时 + 失败降级逻辑 │ └── ad-rewarded/ # 激励视频组件:含播放中状态管理 + 完播回调 + 奖励发放模拟 ├── utils/ │ ├── ad-helper.js # 广告工具类:统一错误处理、重试机制、广告加载耗时统计 │ └── analytics.js # 行为分析工具:本地存储曝光/点击事件,支持导出 JSON 日志 └── project.config.json

提示:app.json中的"adUnitId"字段不是占位符,而是真实可填的广告位 ID —— 这是整个项目“零后台”的起点。你只要在微信公众平台开通流量主、创建对应类型的广告位,把 ID 粘贴进去,保存后重新编译,广告就能加载出来。

2.2 技术选型:为什么只用原生小程序 API,不用 Taro 或 UniApp?

项目用的是微信小程序原生框架(基础库 ≥ 2.25.0),没引入任何跨端框架。原因很实际:

  • 流量主 SDK 对基础库版本敏感:Taro 3.x 在某些低版本基础库下会拦截wx.createXXXAd调用,导致广告实例为空;
  • 插屏广告必须在页面onShow后 5 秒内调用show(),而跨端框架的生命周期钩子时机不可控;
  • 激励视频完播回调必须同步执行奖励逻辑,原生onClose回调能保证 JS 线程不被中断,Taro 的异步桥接层曾出现过回调丢失。

所以这不是“技术保守”,而是踩过坑后的选择:当你需要和微信原生广告能力深度耦合时,原生就是最稳的底座。

2.3 核心配置项:4 个字段决定广告能否加载成功

所有广告能力都依赖app.json中的window配置块和pages/index/index.json中的usingComponents声明,但真正起作用的是以下 4 个字段:

配置位置字段名类型必填说明
app.jsonadUnitId.bannerstring否Banner 广告位 ID,留空则 Banner 组件不渲染
app.jsonadUnitId.interstitialstring否插屏广告位 ID,留空则插屏逻辑跳过
app.jsonadUnitId.rewardedstring是激励视频广告位 ID,必须填写,否则首页按钮禁用
project.config.jsonappidstring是小程序 AppID,必须与流量主开通账号一致,否则广告加载直接报错invalid appid

注意:adUnitId不是全局变量,也不是写在 JS 里的常量,而是硬编码在app.json的window下——这是微信官方推荐的配置方式,避免被混淆或误修改。我一般会把它抽成config/ad-config.js,但这个项目为了“开箱即用”,直接放app.json,省去构建步骤。


3. 核心模块拆解:从首页按钮点击到激励视频完播的完整链路

我们以最常见的“用户点击‘看视频得奖励’按钮 → 播放激励视频 → 完播后解锁功能”为例,走一遍真实代码链路。这不是伪代码,而是源码中pages/index/index.js里删减注释后的主干逻辑。

3.1 首页按钮绑定与广告预加载

// pages/index/index.js Page({ data: { rewardBtnDisabled: false, rewardStatus: 'ready' // ready / loading / playing / rewarded }, onLoad() { // 1. 页面加载时,预加载激励视频广告(不显示,只准备) this.loadRewardAd() }, loadRewardAd() { const adUnitId = getApp().globalData.adConfig.rewarded if (!adUnitId) return this.rewardAd = wx.createRewardedVideoAd({ adUnitId }) // 2. 监听加载成功:广告资源就绪,可随时 show() this.rewardAd.onLoad(() => { console.log('激励视频广告加载成功') this.setData({ rewardStatus: 'ready' }) }) // 3. 监听错误:常见于广告位未生效、AppID 不匹配、网络异常 this.rewardAd.onError(err => { console.error('激励视频广告加载失败', err) this.setData({ rewardStatus: 'error', rewardBtnDisabled: true }) // 错误时自动 fallback 到提示弹窗,不崩溃 wx.showToast({ title: '广告暂不可用', icon: 'none' }) }) // 4. 开始加载(异步) this.rewardAd.load().catch(err => { // load() 可能 reject,需捕获 console.warn('广告 load() 被 reject', err) }) }, onRewardBtnClick() { if (this.data.rewardStatus !== 'ready') return this.setData({ rewardStatus: 'playing', rewardBtnDisabled: true }) // 5. 真正调用 show(),此时广告才弹出 this.rewardAd.show().catch(err => { // show() 失败:可能是用户中途切后台、广告已过期等 console.error('广告 show() 失败', err) this.setData({ rewardStatus: 'ready', rewardBtnDisabled: false }) wx.showToast({ title: '请稍后再试', icon: 'none' }) }) } })

这段代码的关键不在语法,而在时机控制:

  • load()必须在onLoad或onShow中尽早调用,不能等到用户点击才开始加载(否则大概率超时);
  • show()必须在onLoad回调之后调用,否则会报ad is not loaded;
  • onClose回调里必须判断isEnded字段,这是微信唯一可信的“用户是否看完”依据,不能靠onError或onLoad推断。

3.2 激励视频完播回调:如何安全发放奖励?

// pages/index/index.js(续) onLoad() { // ... 上面的 loadRewardAd() // 6. 绑定 onClose 回调(必须在 load() 之后、show() 之前绑定!) this.rewardAd.onClose(res => { if (res && res.isEnded) { // ✅ 用户完整观看,发放奖励 this.giveReward() } else { // ❌ 用户跳过或未看完,不发奖励 wx.showToast({ title: '未完成观看,无法领取', icon: 'none' }) } // 7. 无论是否完播,都要重置状态并重新加载广告(广告实例只能用一次) this.setData({ rewardStatus: 'ready', rewardBtnDisabled: false }) this.loadRewardAd() // 重新 load,为下次点击准备 }) }, giveReward() { // 模拟发放奖励:更新本地缓存 + 修改 UI 状态 const now = Date.now() const rewardCount = (wx.getStorageSync('rewardCount') || 0) + 1 wx.setStorageSync('rewardCount', rewardCount) wx.setStorageSync('lastRewardTime', now) // 更新页面数据 this.setData({ rewardCount, rewardStatus: 'rewarded', rewardBtnDisabled: true }) // 播放成功音效(可选) const audioCtx = wx.createInnerAudioContext() audioCtx.src = '/assets/sound/reward.mp3' audioCtx.play() // 3 秒后自动恢复按钮 setTimeout(() => { this.setData({ rewardStatus: 'ready', rewardBtnDisabled: false }) }, 3000) } })

这里有个血泪经验:onClose回调必须在load()之后、show()之前绑定。我第一次改这个项目时,把onClose写在onLoad回调里,结果发现偶尔onClose不触发——后来查文档才知道,onClose的监听器必须在广告实例创建后立即注册,否则可能错过事件。现在我把onClose绑定提前到onLoad里,show()只负责弹出,逻辑彻底解耦。

3.3 插屏广告自动触发:如何平衡用户体验与广告曝光?

插屏广告不能乱弹,否则用户秒退。这个项目采用“页面可见 + 延迟 + 条件触发”三重保险:

// pages/index/index.js(插屏逻辑) onShow() { // 仅在首页 onShow 时检查是否满足插屏条件 const lastInterstitialTime = wx.getStorageSync('lastInterstitialTime') || 0 const now = Date.now() // 规则:距离上次弹出超过 5 分钟,且当前页面是首页 if (now - lastInterstitialTime > 5 * 60 * 1000) { this.showInterstitialAd() wx.setStorageSync('lastInterstitialTime', now) } }, showInterstitialAd() { const adUnitId = getApp().globalData.adConfig.interstitial if (!adUnitId) return const interstitialAd = wx.createInterstitialAd({ adUnitId }) interstitialAd.load() .then(() => interstitialAd.show()) .catch(err => { console.warn('插屏广告加载/显示失败', err) // 失败时不重试,避免阻塞页面 }) // 设置 5 秒超时,防止广告一直卡住 setTimeout(() => { if (interstitialAd && interstitialAd.destroy) { interstitialAd.destroy() } }, 5000) }

这个逻辑看似简单,但解决了三个高频问题:

  • 防刷:用本地时间戳限制弹出频率,比服务端下发更轻量;
  • 防卡死:setTimeout主动销毁未响应的广告实例,避免用户卡在白屏;
  • 防误触:只在onShow触发,不监听onLoad(避免冷启动时弹出打断用户)。

4. 运行与调试全流程:从本地预览到真机提审的 7 步实操

别急着改代码,先确保你能跑起来。这个项目对环境要求极低,但有几个关键节点必须手动确认,否则你会在第 5 步卡住整整一天。

4.1 环境准备:只需 3 个东西

  1. 微信开发者工具(Stable 1.07.2309140 及以上):旧版本不支持createRewardedVideoAd的新参数;
  2. 已认证的小程序 AppID:个人主体无法开通流量主,必须企业/个体工商户;
  3. 微信公众平台已开通流量主:进入「流量主」后台 → 「广告位管理」→ 创建 Banner、插屏、激励视频三类广告位,获取adUnitId。

提示:Banner 和插屏广告位创建后即时生效,但激励视频广告位需24 小时审核。别等最后一步才发现激励视频 ID 还在“审核中”。

4.2 本地运行:5 分钟完成首次编译

  1. 解压源码包,用开发者工具打开根目录;
  2. 修改project.config.json中的"appid"为你自己的小程序 AppID;
  3. 打开app.json,找到window下的adUnitId对象,填入你在流量主后台创建的三个广告位 ID;
  4. 点击「编译」按钮(或 Ctrl+B),等待构建完成;
  5. 工具右上角切换「预览」→「真机调试」,扫码安装到手机。

此时你应该看到:首页有「看视频得奖励」按钮,点击后弹出激励视频;测试页能切换三种广告类型;结果页显示本地累计奖励次数。如果按钮灰掉,大概率是adUnitId.rewarded没填,或 AppID 不匹配。

4.3 真机调试:绕过“广告加载失败”的 3 个隐藏陷阱

真机上广告加载失败,90% 不是代码问题,而是环境配置问题。以下是我在 12 台不同机型上验证过的排查清单:

现象原因解决
点击按钮无反应,控制台无日志微信客户端版本过低(< 8.0.30)升级微信,或在app.json中设置"requiredBackgroundModes": ["audio"]强制启用后台音频(部分安卓机需要)
createRewardedVideoAd返回undefinedproject.config.json中"appid"为空或格式错误(如多了空格)删除appid字段后重新粘贴,用 JSON 格式校验工具检查合法性
广告弹出后黑屏/卡住,5 秒后自动关闭手机开启了「省电模式」或「应用后台限制」关闭省电模式,将微信加入电池白名单;安卓机还需开启「允许后台活动」

注意:iOS 真机调试时,务必在「微信开发者工具 → 详情 → 项目设置」中勾选「ES6 转 ES5」和「上传代码时样式自动补全」,否则components/ad-rewarded中的 CSS 变量会失效,导致视频容器尺寸异常。

4.4 提审前必做:4 项合规检查清单

微信对流量主小程序审核极严,以下 4 项不达标,100% 被拒:

  1. 广告位 ID 必须与提审小程序 AppID 完全一致:不能用测试号的 ID 提审正式号;
  2. 激励视频必须有明确的用户操作触发(如按钮点击),禁止自动播放;
  3. 广告内容不得诱导点击:按钮文案不能写“点击领红包”,必须是“看视频解锁功能”;
  4. 必须提供清晰的用户协议与隐私政策:在首页或设置页添加跳转链接,内容需包含“我们如何使用广告数据”条款。

我一般会把第 4 条做成一个独立页面/pages/policy/index,用wx.navigateTo跳转,文案直接抄微信《小程序广告规范》第 3.2 条原文,不自己发挥。


5. 常见问题与避坑指南:那些让我凌晨三点还在改onClose的夜晚

这个项目看着简单,但流量主接入是典型的“表面平滑、底层多坑”。以下是我在 5 个项目中踩出的 5 条血泪记录,每一条都附带复现方式和修复方案。

5.1 现象:激励视频onClose回调不触发,用户看完也没奖励

原因:onClose监听器注册时机错误。很多开发者习惯在onLoad回调里绑定onClose,但onLoad是广告加载完成回调,而onClose必须在广告实例创建后立即注册(即wx.createRewardedVideoAd()之后)。如果onLoad延迟触发(如网络慢),onClose就永远收不到事件。
解决:把onClose绑定提到onLoad外层,在Page({})的onLoad函数开头就注册,不要等load()Promise resolve。

5.2 现象:插屏广告在 iOS 上白屏,安卓正常

原因:iOS 微信对插屏广告的show()调用有严格时机限制——必须在用户手势(如tap)后 5 秒内调用,且不能在onLoad中直接调用。这个项目默认在onShow中触发,而onShow在 iOS 启动时可能没有用户手势上下文。
解决:改用「用户首次点击任意区域后触发」逻辑。在index.wxml根节点加bindtap="onPageTap",在onPageTap里记录firstTapTime = Date.now(),再在onShow中判断Date.now() - firstTapTime < 10000才调用showInterstitialAd()。

5.3 现象:Banner 广告在页面滚动时频繁重绘,CPU 占用飙升

原因:components/ad-banner组件默认监听scroll事件来动态调整位置,但未做节流。在低端安卓机上,每秒触发上百次scroll,导致setData频繁调用。
解决:在ad-banner.js的attached生命周期中,用wx.createSelectorQuery()获取 Banner 容器位置,改为IntersectionObserver监听可视区域变化,只有进入/离开视口时才更新状态。

5.4 现象:本地测试一切正常,上传体验版后广告全部加载失败

原因:体验版使用的是体验版 AppID,而流量主广告位只绑定在正式 AppID 下。开发者工具中「上传」时默认用正式 AppID,但「体验版」开关开启后,实际运行环境是体验版 AppID。
解决:关闭「体验版」开关,或在流量主后台为体验版 AppID 单独创建广告位(需同主体)。更稳妥的做法是:提审前,用正式 AppID 生成体验版二维码,让测试人员扫码安装,而非在开发者工具中点「体验版」。

5.5 现象:用户点击 Banner 广告后跳转到空白页,URL 显示https://mp.weixin.qq.com/

原因:Banner 广告的跳转目标由微信后台控制,前端无法指定。当广告主未配置落地页,或落地页审核未通过时,微信默认跳转到公众号主页。
解决:登录流量主后台 →「广告位管理」→ 找到对应 Banner 广告位 → 点击「编辑」→ 在「推广链接」中填写合法 HTTPS 页面(如你的小程序某个页面路径/pages/test/index),并确保该页面已发布、可访问。


6. 二次开发实战:如何把“测试小程序”变成你自己的变现产品

别只把它当测试包。我拿它改造过 3 个真实项目:一个单词打卡小程序、一个健身计划工具、一个儿童识字 App。核心思路就一条:把广告作为功能解锁的“货币”,而不是强插的干扰项。下面是我最常用、也最被用户接受的 3 种改造方式。

6.1 场景化广告植入:用广告替代“付费解锁”,提升转化率

原始项目里,“看视频得奖励”是抽象的数字。但在真实产品中,奖励必须对应具体功能。比如在单词打卡小程序里,我把激励视频和「今日额外学习 5 个单词」绑定:

// pages/learn/index.js onRewardBtnClick() { // ... 原有广告逻辑 this.rewardAd.onClose(res => { if (res.isEnded) { // ✅ 完播后,不是加积分,而是直接推送 5 个新单词 this.addExtraWords(5) wx.showToast({ title: '已解锁 5 个新词!' }) } }) }, addExtraWords(count) { const newWords = this.generateRandomWords(count) const currentList = wx.getStorageSync('wordList') || [] const updatedList = [...currentList, ...newWords] wx.setStorageSync('wordList', updatedList) this.setData({ wordList: updatedList }) }

这样改的好处是:用户感知不到“广告”,只觉得“功能变多了”。数据上看,这种场景化设计让激励视频完播率从 42% 提升到 79%,因为用户有明确目标(学新词),不是为广告而看。

6.2 广告位动态配置:一套代码,多套广告策略

不同用户群体对广告容忍度不同。我给儿童识字 App 做了 A/B 测试:对 12 岁以下用户,首页只显示 Banner;对 12 岁以上,增加插屏;对连续使用 7 天的用户,激励视频按钮文案从“看视频得奖励”变成“看视频解锁 VIP 功能”。

实现方式很简单,在app.js的onLaunch中加一段用户分群逻辑:

// app.js onLaunch() { const userInfo = wx.getStorageSync('userInfo') || {} const daysUsed = this.calcDaysUsed() let adStrategy = 'basic' // basic / standard / premium if (daysUsed >= 7) { adStrategy = 'premium' } else if (userInfo.age && userInfo.age < 12) { adStrategy = 'basic' } else { adStrategy = 'standard' } // 把策略存到 globalData,供各页面读取 this.globalData.adStrategy = adStrategy this.globalData.adConfig = this.getAdConfigByStrategy(adStrategy) },

然后在pages/index/index.js中,根据getApp().globalData.adStrategy动态决定渲染哪些广告组件。这样一套源码,就能支撑多个运营策略,不用维护多套分支。

6.3 数据闭环:用本地日志反哺广告位优化

原始项目只做前端打点,但这些数据很有价值。我在utils/analytics.js里加了一段导出逻辑:

// utils/analytics.js export function exportAdLog() { const logs = wx.getStorageSync('adLogs') || [] const csvContent = [ 'timestamp,adType,event,adUnitId,duration', ...logs.map(log => `${log.timestamp},${log.adType},${log.event},${log.adUnitId || ''},${log.duration || ''}` ) ].join('\n') // 生成临时文件并下载 const fs = wx.getFileSystemManager() const filePath = `${wx.env.USER_DATA_PATH}/ad-log-${Date.now()}.csv` fs.writeFile({ filePath, data: csvContent, encoding: 'utf8', success: () => { wx.openDocument({ filePath, success: () => console.log('日志导出成功') }) } }) }

每周五下午,我会让运营同事导出 CSV,用 Excel 看「Banner 曝光率」「激励视频完播率」「插屏关闭率」。如果某广告位完播率低于 60%,就去流量主后台换素材;如果 Banner 点击率高于 8%,就把它挪到首页顶部。数据驱动,比拍脑袋调优靠谱得多。

从那以后我每次接手新小程序项目,第一件事就是把这套广告日志埋点加进去,哪怕暂时不用导出,也先存着——因为你知道,总有一天,老板会问:“上个月激励视频到底带来了多少活跃?”
希望帮到你。

本文还有配套的精品资源,点击获取

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

ChatGPT账号被临时限制?四级风控触发模型全解析

1. 风控机制的底层逻辑&#xff1a;为什么账号会被临时限制用ChatGPT用得多了&#xff0c;总会遇到那么几次糟心事——前一分钟还在正常对话&#xff0c;下一分钟突然弹出"Account temporarily limited"&#xff0c;登录不进去或者只能登录不能对话。更让人抓狂的是&…

作者头像 李华
网站建设 2026/10/17 12:59:08

等保测评主机层面17个检查项解析:从身份鉴别到日志留存的避坑指南

直接告诉你结论&#xff1a;这篇文讲的是帮一家单位做等保测评时&#xff0c;我在主机层面梳理出来的17个检查项&#xff0c;以及每个检查项背后那些容易被忽视的坑。至于标题里的“龙虾”&#xff0c;纯粹是我同事在输入法里想打“的流程”&#xff0c;结果敲出了“的龙虾”&a…

作者头像 李华
网站建设 2026/10/17 12:58:35

AI演示看着惊艳?三步法帮你识破酷炫背后的虚火

最近朋友圈、群聊里总被同一种内容刷屏&#xff1a;某个AI视频生成工具放出一条演示片&#xff0c;画面精致到可以以假乱真&#xff0c;评论区整齐划一地喊“颠覆”“碾压”“王炸”&#xff1b;隔两周再刷到另一个AI编程助手&#xff0c;号称分分钟写出一套前端页面&#xff0…

作者头像 李华
网站建设 2026/10/17 12:55:52

双节锂电池主动均衡方案:MP2642GR-Z与PIC18F47K42协同设计

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

作者头像 李华
网站建设 2026/10/17 12:55:24

OpenPlayground 自定义模型接入与参数调优实战指南

OpenPlayground 这个名字&#xff0c;做 AI 应用的同学应该不陌生。如果你还在 Postman 里一个个复制 token、拼 JSON 请求体、对照文档反复调参数&#xff0c;那你真该试试这个工具。我最初接触它&#xff0c;是因为要同时对比几家大模型供应商的效果&#xff0c;又要管理本地…

作者头像 李华
网站建设 2026/10/17 12:55:23

make命令参数与特殊目标实战指南:工程级构建控制详解

1. 这不是教科书&#xff0c;是我在项目里天天用的 make 命令速查手册你有没有过这种经历&#xff1a;改完一行 C 代码&#xff0c;敲make等了 8 秒&#xff0c;结果发现它又把整个内核模块全重编了一遍&#xff1f;或者在 CI 流水线上&#xff0c;make test明明该只跑单元测试…

作者头像 李华