简介这份微信小程序开发完整代码面向希望系统学习小程序页面搭建、样式组织与业务逻辑实现的初中级开发者也可作为课程设计、毕业项目或功能原型参考。压缩包共106个文件约253KB以wxml、wxss、js和json四类小程序核心文件为主分别对应页面结构、样式、交互逻辑与全局配置另含png、gif图片资源及xmind、md辅助说明便于快速理解项目组成与文件职责。内容覆盖影视详情与播放、城市选择、文章列表与搜索、个人中心等常见业务场景并包含接口地址配置、数据请求封装和多页面跳转示例可据此梳理目录结构、页面组织方式、复用写法与调试思路。已有775人学习下载适合对照练习、搭建自己的小程序骨架也能用于查漏补缺和二次开发前的结构参考。1. 一份能直接跑的完整代码比十篇教程都省事「微信小程序开发完整代码」这类资源的价值从来不在代码有多少行而在于它替你跳过了从零搭骨架的那两天。一个真能跑起来的工程里藏着目录怎么分层、请求怎么封装、分包怎么切、授权怎么兜底这些没人愿意写进教程的东西——教程教你写一个页面完整代码教你把二十个页面组织成能上线的样子。它更适合三类人课程设计要交作业、需要一套结构合规底座的接内部工具需求、三天内要出可演示版本的以及写了半年页面但没见过完整工程长什么样的。下面按先跑起来、再拆结构、最后上线自检的顺序走一遍每一步都能对着手里的包复现。2. 首次运行让压缩包在开发者工具里出首页拿到包之后别急着改业务代码。完整代码类资源最常见的翻车点不是逻辑错而是配置对不上appid 是别人的、基础库版本太高、npm 依赖没构建、分包路径写死在配置里。这一章的目标只有一个——让首页在你的机器上渲染出来并且知道它为什么能渲染出来。2.1 六个文件决定生死逻辑代码排在后面一个结构正常的小程序工程根目录会有一堆以点开头的隐藏配置文件其中真正影响能不能跑的是下面六个。我一般会按这个顺序逐个过一遍而不是直接点编译。文件作用常见的坑project.config.json工程配置appid、编译开关、包体策略都在这appid 是原作者的直接编译会报无权限project.private.config.json个人本地配置优先级高于上面那个被一起打包进去覆盖了你的设置app.json全局配置页面路由、分包、窗口、超时pages 里第一条决定首页顺序错了首屏就错app.js全局逻辑常放登录、埋点、全局状态启动时同步做重活冷启动直接卡住sitemap.json微信索引配置默认全允许缺文件会告警不影响运行但看着烦package.json依赖声明组件库靠它装有声明但没执行构建require 一路飘红先把project.config.json里的appid换成你自己在后台拿到的没有的话用工具的测试号也能跑通大部分页面只是真机预览和支付、订阅消息这类能力用不了。接着看libVersion字段如果不是你后台可选的基础库版本直接删掉这一行让工具自己选比硬填一个不存在的版本安全。{ appid: 填你自己的小程序 appid, projectname: demo-miniprogram, compileType: miniprogram, setting: { es6: true, enhance: true, postcss: true, minified: true, urlCheck: false, packNpmManually: false }, packOptions: { ignore: [], include: [] } }这段配置里四个开关值得单独说es6打开才会把 ES6 语法转成低版本基础库能跑的形态enhance是增强编译能顺带做不少语法降级和压缩urlCheck在开发阶段建议关掉否则你本地连测试域名会被工具直接拦住等接真后端时再打开minified只在预览和上传时起作用开发时开着会拖慢编译。packNpmManually保持 false交给工具自动处理依赖路径除非你做多包依赖需要手动指路。2.2 导入之后先打开三个开关再谈编译导入工程的正确姿势是开发者工具里选导入项目目录选到含project.config.json的那一层不要选到它的父目录也不要用打开文件夹的方式新建。导入完成后不要立刻点编译先到右上角详情 → 本地设置里确认三件事。第一件是 ES6 转 ES5 必须勾上。很多完整代码包用了可选链、扩展运算符、async/await不转译的话在低版本基础库上会直接语法报错表现是白屏且控制台只有一行看不懂的报错。第二件是使用 npm 模块必须勾上。只要工程里出现了require(some-lib)或页面 json 里引用了usingComponents: {van-button: vant/weapp/button/index}这种写法就说明它依赖了第三方组件库没勾这个开关会在编译阶段就找不到模块。第三件是上传时压缩代码开发时可以不勾但上传前一定要勾包体能不能压进 2MB 往往就差这一步。# 在工程根目录执行先把依赖装回来 npm install # 检查是否装了组件库有输出说明确实依赖 npm grep -r vant/weapp\|lin-ui\|tdesign --include*.json . | head -20这两条命令是我拆包的习惯动作先装依赖再用 grep 确认依赖到底被哪些页面引用。装完之后必须回到工具里点一次工具 → 构建 npm它会生成miniprogram_npm目录这个目录才是运行时真正读的地方。只执行npm install不构建是最常见的依赖装了但还是报错的原因。提示miniprogram_npm是构建产物不要提交到版本库也不要手动往里改文件下次构建会被覆盖。2.3 分包与按需注入首屏体积是第一优先级完整代码包里如果页面数量超过十几个作者大概率做了分包。你要做的不是重写而是先确认分包结构合不合理再决定要不要开按需注入。小程序对包体有硬性限制主包和单个分包各自不能超过 2MB所有包加起来不能超过 20MB。很多人改着改着突然编译不过报的就是主包超限原因往往是往主包里塞了图片、字体或者一整个组件库。{ pages: [ pages/index/index, pages/login/login ], subPackages: [ { root: packageUser, pages: [pages/profile/profile, pages/setting/setting] }, { root: packageOrder, pages: [pages/list/list, pages/detail/detail] } ], preloadRule: { pages/index/index: { network: wifi, packages: [packageOrder] } }, lazyCodeLoading: requiredComponents, window: { navigationBarTitleText: 示例, navigationBarBackgroundColor: #ffffff }, networkTimeout: { request: 15000 } }subPackages里每一项的root是目录名pages是相对于这个目录的路径写错一个字符就是页面不存在。preloadRule是分包预下载键是触发页面路径network可选all、wifi、4G之类我一般设成wifi避免用户在弱网下打开首页时被预下载拖慢。lazyCodeLoading设成requiredComponents是按需注入只有真正用到的页面和组件才会被加载对首屏时间改善很明显代价是某些动态注入的组件可能在某些路径下取不到需要真机回归一遍。networkTimeout里的request建议显式写出来。默认值偏长用户在网络差的时候会盯着转圈十几秒改成 15 秒后配合重试逻辑体验好很多。2.4 用一份环境配置区分开发与生产完整代码包里最容易被忽略的是环境切换。常见的写法是作者把baseUrl硬编码在请求文件里你拿过来一跑接口全 404。我一般会把环境抽成一个文件用编译条件或者手动改一个变量来控制。// utils/env.js const ENV dev // dev | prod打包前改这一行 const CONFIG { dev: { baseUrl: https://test.example.com/api, debug: true }, prod: { baseUrl: https://api.example.com/api, debug: false } } module.exports CONFIG[ENV]这段代码本身没什么技术含量但它解决的是改一处忘一处的问题。页面里所有请求都从env.js取地址切环境只改一个字符串。更规范的做法是用wx.getAccountInfoSync().miniProgram.envVersion自动判断当前是开发版、体验版还是正式版让环境跟着编译产物走不用人肉改代价是本地开发时也要走开发版编译团队里需要约定清楚。到这一步首页应该能渲染出来了。如果还是白屏先看控制台有没有红色报错再看调试器 → Network里首页请求是不是全挂了最后确认app.json的pages第一项对应的四个文件是不是真的存在。这三步能覆盖八成首次运行问题。3. 页面与组件WXML 数据流、生命周期与 setData 的边界跑起来之后才开始看结构。完整代码包最容易出问题的地方不是某个页面写得丑而是数据和视图之间的绑定关系绕了太多层页面里套组件、组件里还在改页面数据、改完不生效又去翻文档。这一章把页面层和组件层的约定讲清楚后面改业务才不会一改就崩。3.1 页面四件套与生命周期的真实执行顺序一个页面由四个同名文件组成.wxml管结构、.wxss管样式、.js管逻辑、.json管页面级配置。缺.json能跑缺.js直接报错。拆包时我习惯先看.json因为它决定了这个页面引用了哪些自定义组件、要不要用navigationStyle: custom做自定义导航栏。生命周期这块血泪经验是onLoad只执行一次适合接收路由参数onShow每次页面显示都会执行适合刷新列表onReady执行时视图层才真正渲染完成适合测量节点尺寸。很多人把请求写在onLoad里从详情页返回列表页时数据不刷新然后怀疑是缓存问题其实是生命周期用错了。Page({ data: { list: [], loading: true, page: 1 }, onLoad(options) { // options 里是路由参数做一次类型兜底 this.setData({ id: Number(options.id) || 0 }) this.fetchList() }, onShow() { // 从详情页返回时刷新第一页避免看到旧数据 if (this.data.page 1) { this.setData({ page: 1, list: [] }) this.fetchList() } }, onReachBottom() { // 触底加载下一页注意防抖避免用户快速上滑触发多次 if (this.data.loading) return this.setData({ page: this.data.page 1 }) this.fetchList() }, async fetchList() { this.setData({ loading: true }) try { const res await request({ url: /list, data: { page: this.data.page } }) this.setData({ list: this.data.list.concat(res.data), loading: false }) } catch (e) { this.setData({ loading: false }) } } })这段是列表页最常见的骨架。onReachBottom里的loading判断就是防重复请求的锁没有它用户在触底瞬间连滑两次就会发出两个同样的请求后端看到的是重复数据前端看到的是列表里出现两份相同的条目。onShow里判断page 1是为了避免首次进入时重复请求——首次onLoad已经拉过一次onShow紧接着也会执行。注意async/await在小程序里可用但依赖增强编译且catch之后一定要把loading复位否则列表会永远卡在加载态。3.2 自定义组件、behaviors 与样式隔离完整代码包里如果超过五个页面长得像作者通常会抽组件。组件的核心是三个约定properties接收外部数据、triggerEvent往外抛事件、observers监听数据变化做副作用。这三个东西用好了组件才是组件用不好就变成了一个藏了全局状态的定时炸弹。// components/goods-card/index.js Component({ options: { styleIsolation: apply-shared, // 允许页面样式影响组件但组件自身样式不出去 multipleSlots: true }, properties: { goods: { type: Object, value: {} }, showPrice: { type: Boolean, value: true } }, observers: { // 监听对象内部的字段做格式化避免在 wxml 里写复杂表达式 goods.price(val) { this.setData({ priceText: (val / 100).toFixed(2) }) } }, methods: { onTap() { this.triggerEvent(select, { id: this.data.goods.id }, { bubbles: false }) } } })styleIsolation有三个取值默认是isolated页面和组件样式互不干扰apply-shared让页面样式能影响组件shared是双向。这里选apply-shared是为了让页面里的全局字体和颜色沉下去又不让组件样式污染页面。如果发现组件在页面里位置全乱了先查这个配置。triggerEvent的第三个参数控制事件是否冒泡默认不冒泡。在长列表里我一般显式写bubbles: false避免一次点击触发外层容器的同名事件排查起来非常费劲。observers里做价格格式化是为了避免在 WXML 里写{{(goods.price / 100).toFixed(2)}}这种表达式。WXML 支持简单运算但复杂表达式既难读又没法复用而且每次渲染都要重新计算长列表里开销不小。3.3 setData 的边界哪些写法会让列表卡成幻灯片setData是小程序里唯一能改视图数据的方法也是最容易写错的地方。三个原则每次只传变化的字段、单次数据量别太大、高频触发要合并。// 反面写法整个数组重新塞一次 this.setData({ list: newList }) // 推荐写法只改受影响的下标 this.setData({ [list[${index}].checked]: true })第二种写法用的是数据路径只更新一条记录的一个字段视图层的 diff 范围小得多。在几百条的列表里两种写法的帧率差距肉眼可见。但如果一个操作要改几十条记录路径写法反而不划算因为每次setData都有一次通信开销。这时候应该先把所有变更在 JS 里合并好最后一次性setData一个大对象。判断标准很简单改动条数超过十条就走批量合并。另一个高频坑是把大对象塞进data。有些完整代码包会把整个接口返回对象直接setData到页面里包括根本用不到的字段。这些数据都要经过一次跨线程传输到了视图层再被丢弃。我一般会在请求层就把不需要的字段剔掉或者只把渲染真正需要的字段拼成新对象。3.4 用 rpx 与安全区做一次真机适配尺寸单位这件事工具里的模拟器永远是对的真机永远有偏差。rpx是按 750 设计稿等比缩放的写width: 750rpx就是满屏宽。问题出在横屏、平板和折叠屏上等比缩放会让字号大到离谱。我的做法是主体布局用rpx字体和图标用px边界值用百分比兜底。底部安全区是另一个必须处理的点。带底部按钮的页面在全面屏上会被系统手势条挡住解决方案是给容器加环境变量内边距。.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); background: #fff; box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06); }两行padding-bottom是历史包袱constant()是早期写法env()是现在标准写法两个都写能覆盖绝大多数机型后写的会覆盖前面的。这一步省掉上线后一定会有用户反馈按钮点不到。4. 请求层封装从 wx.request 到能扛住弱网的服务层页面写得再漂亮接口层散了架一样白搭。完整代码包里最有价值的往往就是utils/request.js这个文件——它决定了 token 怎么带、错误怎么提示、重复请求怎么去、超时怎么兜。这一章按封装 → 登录态 → 缓存 → 后端选型的顺序把这一层拆开。4.1 把 wx.request 包成带拦截器的服务层原生wx.request是回调式写法业务代码里写满success和fail改一个公共参数要翻遍全工程。封装的目标是把回调变成 Promise把公共逻辑token、错误码、loading收进一处。// utils/request.js const env require(./env) const pending new Map() // 请求去重key 为 methodurl参数 function getKey(options) { return ${options.method || GET}_${options.url}_${JSON.stringify(options.data || {})} } function request(options) { const key getKey(options) if (pending.has(key)) return pending.get(key) // 相同请求直接复用 const token wx.getStorageSync(token) const task new Promise((resolve, reject) { wx.request({ url: env.baseUrl options.url, method: options.method || GET, data: options.data || {}, timeout: options.timeout || 15000, header: { content-type: application/json, ...(token ? { Authorization: Bearer ${token} } : {}), ...(options.header || {}) }, success(res) { const { statusCode, data } res if (statusCode 200 data.code 0) { resolve(data.data) } else if (statusCode 401 || data.code 401) { // token 失效清掉本地态后重新登录一次再重试 wx.removeStorageSync(token) reject({ code: 401, msg: 登录已过期 }) } else { if (!options.silent) { wx.showToast({ title: data.msg || 请求失败, icon: none }) } reject(data) } }, fail(err) { if (!options.silent) { wx.showToast({ title: 网络异常请稍后重试, icon: none }) } reject(err) }, complete() { pending.delete(key) } }) }) pending.set(key, task) return task } module.exports request这段封装里几个参数值得展开。timeout单独给到 15 秒比全局配置更细某些上传类接口可以传更大的值。silent是静默开关轮询和埋点类请求不需要弹 toast否则用户会看到一堆提示。pending用 Map 做去重键由方法、地址和参数序列化拼成同一个请求在未返回前再次触发会直接拿到同一个 Promise这是解决列表页重复请求最省事的办法。Authorization头只在有 token 时拼上避免后端收到Bearer undefined这种脏数据。401 的处理我选择先清 token 再抛错交给上层决定是跳登录页还是静默重登而不是在这一层直接跳转——请求层不该知道路由。4.2 登录态code 换 token 与静默续期小程序的登录流程是固定的前端调wx.login拿临时 code后端用 code 加 appid、secret 去换 openid 和 session_key再签一个自己的 token 返给前端。注意 secret 只能放在后端前端任何地方出现它都等于把整个小程序交出去了。// utils/auth.js let loginTask null function login() { // 并发调用时复用同一个登录任务避免连点触发多次 wx.login if (loginTask) return loginTask loginTask new Promise((resolve, reject) { wx.login({ success(res) { if (!res.code) return reject(new Error(login failed)) wx.request({ url: ${require(./env).baseUrl}/auth/login, method: POST, data: { code: res.code }, success(r) { if (r.data r.data.code 0) { wx.setStorageSync(token, r.data.data.token) wx.setStorageSync(tokenExpire, Date.now() r.data.data.expiresIn * 1000) resolve(r.data.data.token) } else { reject(r.data) } }, fail: reject }) }, fail: reject }) }).then( (token) { loginTask null; return token }, (err) { loginTask null; throw err } ) return loginTask } function ensureLogin() { const token wx.getStorageSync(token) const expire wx.getStorageSync(tokenExpire) || 0 // 提前 60 秒续期避免临界点请求失败 if (token Date.now() expire - 60000) return Promise.resolve(token) return login() } module.exports { login, ensureLogin }loginTask这个变量是整段代码的关键。小程序启动时经常有多个页面同时发请求如果每个 401 都触发一次wx.login用户会看到登录接口被连续调用好几次。用模块级变量缓存进行中的任务能把并发合并成一次。续期判断里留了 60 秒余量是因为从判断到请求真正到达服务端还有网络耗时卡在过期瞬间的请求最容易失败。这里用的是.then(onFulfilled, onRejected)而不是.finally因为部分低版本基础库对finally支持不完整用两参数写法兼容性更稳。4.3 缓存与弱网兜底小程序的wx.setStorageSync单 key 上限 1MB总上限 10MB够存一些列表快照和配置项。我的习惯是首页接口的返回结果缓存一份带时间戳进入页面时先渲染缓存再请求用户感知到的就是秒开。判断缓存是否可用要看业务不能只看时间。商品价格、库存这类数据 5 分钟就过期了配置类数据可以缓存一天。缓存策略写在请求层外面别塞进request里否则哪个接口走缓存哪个不走会变成一团乱麻。弱网兜底还有一个细节请求失败时不要立刻弹 toast很多用户是在电梯里点开的弹窗只会让人烦躁。我的做法是失败后静默重试一次仍然失败再提示并且提示文案带上下拉重试的引导。4.4 云开发还是自建后端按团队规模选完整代码包里如果有cloudfunctions目录说明作者用的是云开发。云开发的好处是不用管服务器、不用配域名白名单、数据库和存储开箱即用适合个人项目和小团队内部工具。缺点是复杂查询能力弱、跨环境迁移麻烦、成本随调用量线性增长。自建后端则是反过来灵活、可控、能复用现有账号体系但要处理域名备案、HTTPS 证书、跨域配置、并发扩容。如果你手里的包是云开发版本而你要接公司已有后端工作量主要在把wx.cloud.callFunction换成request数据结构基本能平移。判断标准我一般这么划日均活跃几百以内、只有一两个人在维护云开发足够要和已有系统打通、或者有审批流这类复杂业务老老实实自建。别为了省事把复杂业务塞进云函数后期改一个字段要重新部署函数调试体验非常差。5. 避坑与排查五类上线前一定会遇到的真机问题模拟器跑通只是及格线。真正让人熬夜的是那种工具里好好的真机一打开就白屏的问题。这一章按编译期和真机期两类分开列每条都按现象、原因、解决写清楚遇到时对着查比漫无目的搜索快得多。5.1 编译期就拦下来的四类问题现象一编译报Cannot find module xxx。原因通常有两种要么依赖装了但没点构建 npm要么miniprogram_npm目录被手动删过。解决方式是先执行npm install再回工具执行一次构建最后检查页面 json 里usingComponents的路径是不是指向构建产物而不是源码包。现象二主包体积超限上传被拒。现象是上传时提示包体超过 2MB。原因九成是图片和字体被打进了主包或者整个组件库被引进了主包页面。解决方式是把大图挪到 CDN 或者分包里组件按需引入单个组件而不是整包引入然后重新编译看体积分析。# 粗略定位大文件按体积倒序排列 find . -type f \( -name *.png -o -name *.jpg -o -name *.ttf \) \ -not -path ./node_modules/* -exec du -k {} | sort -rn | head -20这条命令能快速找出藏在页面目录里的大图。找到之后别急着删先确认是不是背景图是的话改成网络图或者用 CSS 渐变替代。现象三页面路径不存在编译直接失败。原因是app.json的pages或subPackages里写了路径但对应的四个文件缺了某个。注意subPackages里的路径是相对root的不能带root前缀这一点和pages写法不一样抄配置时特别容易错。现象四app.json里用了不支持的字段名。比如把subPackages写成subpackages工具不会报错分包却不生效所有页面还是打进主包。检查方式是看编译后的包结构或者直接搜官方配置项大小写。这类问题最阴因为它不报错只是静默失效。5.2 只在真机上复现的三类问题现象五开发者工具正常真机白屏且没有明显报错。原因通常是请求域名没配白名单。开发阶段关了urlCheck真机可不认这个开关请求直接被拦。解决方式是到后台把接口域名加入 request 合法域名必须是 HTTPS 且已备案。临时验证可以先在真机上打开调试模式用 vConsole 看 Network如果请求显示被拦截那就基本确定了。现象六列表滑动卡顿工具里的性能面板一片绿。原因是setData数据量过大或者触发过频。真机的 CPU 和内存比电脑差一个量级工具里流畅不代表真机流畅。解决方式是按前面说的路径更新法改把长列表的滚动事件做节流并且给scroll-view加enable-flex之类的优化属性前先确认基础库支持。现象七时间显示成 Invalid Date。这个坑我踩过不止一次。原因是 iOS 不认2024-01-01 10:00:00这种带空格的格式new Date()直接返回 Invalid。解决方式是统一把中划线换成斜杠再解析。function parseTime(str) { if (!str) return null // iOS 只认 2024/01/01 10:00:00 这种形式 const normalized String(str).replace(/-/g, /) const d new Date(normalized) return isNaN(d.getTime()) ? null : d }这段函数看着简单但它省掉的是上线后一堆时间显示异常的反馈。更彻底的做法是后端直接返回时间戳前端只做格式化从根上绕开各端解析差异。5.3 排查工具箱三件东西就能定位八成问题第一个是真机调试。工具右上角预览扫码后在手机上下拉打开调试模式会多出一个 vConsole 悬浮球里面能看到 Console、Network、Storage和浏览器开发者工具基本一致。真机问题第一步永远是开它而不是猜。第二个是体验评分。工具菜单里能跑一次自动化检测会给出性能、体验、最佳实践三个维度的分数和具体问题项比如图片没设宽高、点击区域过小、缺少aria标注。分数不高的项挨个改比凭感觉优化靠谱。第三个是基础的日志分层。我会在请求层和关键页面入口打日志用统一前缀上线前用一个开关关掉。真机出问题时用户截图给你的日志如果能看出是哪个接口挂了排查时间能从半天缩到十分钟。6. 上线前的最后一次自检把能自动化的事情交给脚本真机跑通、坑也踩完了剩下的是发布前的收尾。这一步我从不靠记忆而是对着清单走因为漏掉任何一项的代价都是审核驳回或者线上事故。检查项具体动作不做的后果包体看主包是否接近 2MB分包是否合理上传被拒或首屏加载慢域名request、uploadFile、downloadFile 三类合法域名都配齐真机接口全挂隐私后台配置隐私保护指引声明收集的信息类型相关接口调用失败授权头像昵称改用按钮和输入框获取不再调旧接口功能不可用兼容基础库最低版本设置合理低版本有降级方案老机型白屏埋点关键路径都有上报错误有兜底日志线上问题查不到根因隐私这一项特别容易漏。只要你的小程序用到了手机号、位置、相册、剪贴板中的任意一个就必须在后台把用户隐私保护指引填完整否则接口调用时会被拦截。我一般会在app.json里显式声明用到的隐私接口配合页面上的一次性授权弹窗让用户在第一次触发时就明白为什么要这个权限而不是等他点到一半突然弹一个框。// app.js 里做一次统一的隐私授权兜底 App({ onLaunch() { if (wx.onNeedPrivacyAuthorization) { wx.onNeedPrivacyAuthorization((resolve) { wx.showModal({ title: 隐私提示, content: 需要使用你的相关信息以提供完整功能, success: (r) resolve({ event: r.confirm ? agree : disagree }) }) }) } } })这段是兜底逻辑真正规范的写法是在需要授权的那一步用按钮触发让用户明确知道授权是为了哪个功能。统一在启动时弹一次的做法体验差而且用户拒绝后很难再引导回来。发布节奏上我的习惯是先上传体验版找两三个真实用户在真实网络环境下跑一遍主流程——登录、下单、支付、返回列表。体验版和正式版的差异主要在网络环境和域名白名单用体验版能提前发现九成的配置问题。灰度发布能开就开先放一小部分流量盯一天错误率再全量。从那以后我每次上传前都强制走一遍这份清单尤其是域名和隐私两项宁可多花二十分钟也不想在审核被驳回后重新排一次队。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?