Files
lucky_shop/readme.md
ZF sun 7909cf2b22 test:导出脚本增加零漏拷/零多拷硬保证
质疑"某个目录有没有被打进 zip"时, 原先只能人工逐目录核对。现在把这个检查做进
脚本, 每次导出都跑:

copyDir 记录每个被复制文件在 zip 中应有的条目名, verifyZipFile 把它与实际 zip
条目做**逐个双向比对** —— 少一个报"漏拷"并列出文件名, 多一个报"多拷"。这样
漏拷会当场失败, 而不是等在别的电脑上解压缩时才发现。

自检输出新增一行, 例如:
  自检: 零漏拷 ✅(1381/1381) / CRC 逐条 ✅ / 偏移一致 ✅ / ...

同时把 copyDir 的条目名拼接由"从 nameOfParent 反推"改为显式传 relBase, 去掉
原先那段绕的 split/map 逻辑。

新增 3 条测试(漏拷拒绝 / 多拷拒绝 / 清单吻合时放行且 README.txt 豁免), 本
测试文件 19/19 通过, npm test 全量 17 个测试文件通过。
2026-10-17 16:13:09 +08:00

391 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小程序及快应用前端源码
## 1. 📋 项目说明
该项目基于 **uni-app** 开发构建,请使用 [HBuilderX](https://www.dcloud.io/hbuilderx.html) 进行代码开发及构建发布。
## 2. 🛠️ 开发说明
- 源码基于 **Vue2** 版本
- SCSS 采用 **dart-sass** 进行编译输出
## 3. 🔧 开发调试说明
### 3.1 注意事项
- 应用访问等关键参数的配置来源于 `./common/js/config.js`
### 3.2 本地调试
`.local.config.js.example` 是本地调试配置示例。
拷贝 `.local.config.js.example` 为 `.local.config.js` 文件,默认开发调试模式会自动加载该文件
---
## 4. 📦 发布说明
### 4.1 小程序发布
#### 4.1.1 基本操作步骤(通用版/定制化版)
##### ##### ##### ##### 【前置准备】
1. 在项目根目录打开终端安装依赖:
```bash
npm install
```
*已有依赖可跳过此步骤*
##### ##### ##### ##### 【发布构建】
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
3. 构建完成后,**自动执行**补丁脚本(拷贝 site.js / config 文件 + 生成 ZIP 包),无需手动操作
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-weixin 压缩包
💡 **示例**:`mp-weixin-2026-05-11-3bff91e.zip`
*压缩包文件名末尾为 Git 提交 ID*
*同时该目录下会生成未压缩的 `mp-weixin` 目录*
##### ##### ##### ##### 【验证与重命名】
1. 打开未压缩的 `mp-weixin` 目录,找到 `site.js` 文件,将文件内的 `uniacid` 值改为当前客户编号(如:2812)并保存
2. 打开微信开发者工具,导入「mp-weixin」目录,点击「编译」,在开发者工具控制台验证有无报错,以及能否返回对应客户的业务数据
3. 确保无误后将生成的 mp-weixin 压缩包重命名
- **定制化版**格式:`定制化-客户编号-域名-mp-weixin-当前日期-Git提交ID.zip`
- **通用版**无需重命名
💡 **示例**:`custom-2812-xcx.aigc-quickapp.com-mp-weixin-2026-05-11-3bff91e.zip`
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
##### ##### ##### ##### 【交付与最终发布】🔍
1. 将重命名后的文件发送给技术人员
*📌通用版直接将生成的 mp-weixin 压缩包发送给技术人员*
2. **技术人员操作流程**:
- 解压压缩包
- 确认 `site.js` 中的 `uniacid` 为客户编号
- 用微信开发者工具导入解压后的代码目录
- 编译验证无误后,上传发布
### 4.2 快应用发布
#### 4.2.1 基本操作(通用版/定制化版)
##### 4.2.1.1 【发布构建】
1. 使用 HBuilderX 打开项目
2. 打开项目根目录的 `manifest.json` 文件,切换至可视化配置界面:
1. 依次点击「Web 配置」→「运行的基础配置」
2. 根据情况查看是否修改路径中/hwappx/????/的客户编号
📌 **通用版**保留原有 `/hwappx/common/`
📌 **测试版**保留原有 `/hwappx/test/`
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
3. 选择菜单栏「发行」 → 「自定义发行」 → 选择对应的站点脚本
💡 例如:网址是 `5g.aigc-quickapp.com` 选 `H5-5g.aigc-quickapp.com`
4. 修改以下配置:
1. **网站标题**:快应用
2. **网站域名**:当前客户域名
3. 确认后点击「发行」等待构建完成
⚠️ **注意**:构建完成后自动输出到 `unpackage/dist/build/h5/`,格式为 `{域名}-{base路径}.zip`
💡 **示例**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
##### 4.2.1.2 【交付与最终发布】🔍
1. 将生成的 `.zip` 文件发送给运维人员
2. **运维人员操作流程**:
1. 解压压缩包
2. 打开快应用开发者工具,导入解压后的代码目录
3. 验证代码无报错后,执行上传发布操作
##### 4.2.1.3 【测试流程】
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
如果是test就把hwappx/common/改为hwappx/test/----------改站点
---
### 4.3 鸿蒙元服务发布(mp-harmony)
#### 4.3.1 【发布构建】
1. 使用 HBuilderX 打开项目
2. 选择菜单栏「发行」 → 「HarmonyOS」→ 「发行」,等待构建完成
3. 构建完成后,**自动执行** `harmony.patch.js` 补丁脚本(配置拷贝 + ZIP 打包)
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-harmony 压缩包
#### 4.3.2 【DevEco Studio 编译与产物】
在 DevEco Studio 中导入 `mp-harmony` 工程后,点击「Build」→「Build App(s)/Project(s)」,实际执行的任务是 `assembleApp`:
```
# 由 DevEco Studio 自动执行,无需手动敲
hvigorw --mode project -p product=default assembleApp
```
这个任务是**两步串联**的 —— **先逐个 module 编译出包,再把这些包打成 APP**:
| 产物 | 路径 | 说明 |
|------|------|------|
| 分包(中间产物) | `{module}/build/default/outputs/default/{module}-default-signed.{hap\|hsp}` | 每个 module 一个,编译+签名的最小单位 |
| APP(最终产物) | `build/outputs/default/mp-harmony-default-signed.app` | 把全部分包收拢成的分发单元 |
📌 **元服务上架 AppGallery Connect 上传的是 `.app`**,不是 `.hap`。`pack.info` 中 `"bundleType": "atomicService"` 即元服务包标记。
📌 **本工程共 1 个 HAP + 5 个 HSP**(不是 6 个 HAP)—— `module.json5` 里 `type: "entry"` 编成 **HAP**,`type: "shared"` 编成 **HSP**(共享包,动态下发)。详见 `build/outputs/default/pack.info`:
| 包名 | 扩展名 | moduleType | 说明 |
|------|--------|------------|------|
| `entry-default` | `.hap` | `entry` | 主包,EntryAbility + 服务卡片 + uniapp 主 bundle |
| `components_diy-default` | `.hsp` | `shared` | ascf 动态化分包 |
| `pages_goods-default` | `.hsp` | `shared` | ascf 动态化分包 |
| `pages_order-default` | `.hsp` | `shared` | ascf 动态化分包 |
| `pages_promotion-default` | `.hsp` | `shared` | ascf 动态化分包 |
| `pages_tool-default` | `.hsp` | `shared` | ascf 动态化分包 |
⚠️ **主包体积**:元服务官方要求**单个包 ≤ 2MB**(按压缩后算,含其直接依赖的 HSP),同一设备类型下全部分包总和 ≤ 10MB。实测本工程主包 `entry-default.hap` 约 **2.37 MiB**,已超单包 2MB 限制;总包约 **6.39 MiB**,在 10MB 以内。
> 若只想单模块调试,可在 DevEco 中用「Build Hap(s)/Module(s)」(即 `assembleHap`),只产出分包、不打 APP。
#### 4.3.3 【工程包导出(换机编译)】
需要在一台**没有本仓库 / 没有 HBuilderX** 的电脑上编译鸿蒙工程时,用脚本把 `stub-app-projects/mp-harmony` 里编译所需的文件导成一个 zip:
```bash
npm run pack:harmony
```
输出到 `unpackage/dist/harmony-export/mp-harmony-project-v<日期>-<提交ID>.zip`,解压后即得 `mp-harmony/` 目录。
| 参数 | 用途 |
|------|------|
| (无参数) | 默认行为:含签名证书,不含 `node_modules` |
| `--no-certs` | 不含 `cert-profile/`(见下方安全提示) |
| `--with-node-modules` | 额外含 `hvigor/node_modules`(约 320 MB),供**离线**目标机 |
| `--out <目录>` | 自定义输出目录 |
**脚本全程只读源目录**,产物先复制到暂存目录再压缩,不修改 `stub-app-projects` 下任何文件。
**导出内容**
| 类别 | 说明 |
|------|------|
| 工程源码 | `AppScope/`、`entry/src/`、`ascf/ascf_build/*/src/`、各类 `*.json5` / `hvigorfile.ts` |
| `ascf/ascf_src/` | uniapp 编译产物(页面/组件代码),**带上它目标机就不需要本仓库和 HBuilderX** |
| `cert-profile/` | 签名证书(`--no-certs` 可排除) |
**已排除**(可由目标机 DevEco 重新生成)
`node_modules/`(约 320 MB)、`oh_modules/`、`**/build/`、`.preview/`(约 22 MB)、`.idea/`、`.hvigor/`、`local.properties`、`.cxx/`、`.test/`
> ⚠️ **证书安全**:默认导出**包含签名私钥** `cert-profile/qq/qq.p12`,且 p12 密码在 `build-profile.json5` 中本就是明文。请仅在可信渠道传递,用完及时销毁或归档。对外发给不可信对象时务必加 `--no-certs`。
>
> 📌 缺证书 hvigor 无法签名,产不出可上传的 `.app`。用 `--no-certs` 时需自行把 `cert-profile/` 放到目标工程对应位置。
**目标机操作**
1. 安装 DevEco Studio 5.0.x(本工程 `compatibleSdkVersion` 5.0.0(12))
2. 解压 zip,得到 `mp-harmony/`
3. DevEco Studio「打开」`mp-harmony` 目录本身
4. 首次打开自动执行依赖同步(`ohpm install` 拉取 `hvigor/node_modules`),**需联网**;离线机请用 `--with-node-modules` 导出
5. 「Build」→「Build App(s)/Project(s)」,产物 `build/outputs/default/mp-harmony-default-signed.app`
> ⚠️ **`ascf/ascf_src/` 是导出时刻的快照**。若前端代码有更新,必须回源机器重新执行导出,否则编译出的包内容是旧的。
📌 zip 由脚本内纯 Node 实现(`zlib` + 手写 ZIP 头)生成,条目名统一正斜杠并置 UTF-8 标志位,可跨 Windows/macOS/Linux 使用。
**产出后自检(每次导出都会跑)**
脚本写完 zip 后会把文件**重新解析一遍**,任何一项不符直接判失败、不输出「完成」:
| 校验项 | 内容 |
|---|---|
| **零漏拷 / 零多拷** | 把本次实际复制的文件清单与 zip 条目**逐个双向比对**,有任何文件没打进去、或出现清单外的文件,当场失败 |
| EOCD / 中央目录 | 签名、条目数、中央目录偏移与长度边界一致 |
| `localOffset` | 每条都必须真的指向本地文件头,且文件名与中央目录一致 |
| CRC | **逐条**解压后重算 CRC32 比对 |
| 条目名 | 必须正斜杠、必须置 UTF-8 标志位、必须可逆解码为合法 UTF-8 |
| 内容断言 | 构建必需文件齐全;`node_modules`/`build`/`.preview` 等排除项不得混入 |
```bash
npm test # 跑全量测试, 含 pack-harmony-project.test.js 的 19 条用例
```
正常输出示例:
```
条目: 1382 个文件 + 327 个目录 (自检通过)
自检: 零漏拷 ✅(1381/1381) / CRC 逐条 ✅ / 偏移一致 ✅ / 正斜杠 ✅ / UTF-8 ✅ / 骨架齐全 ✅
```
> 📌 **不必再人工核对"某个目录有没有打进去"**——上面的「零漏拷」会逐文件双向比对并打印数量。怀疑漏拷时看这一行即可;若真的漏了,脚本会直接失败并列出文件名。
>
> 📌 `EntryCard/` 不在 `build-profile.json5` 的模块列表中,**不是编译模块**,其内容不参与构建;但导出脚本按「全量复制 − 排除清单」处理,同样会把它带进 zip。
> 📌 测试分**正向**(写出来能还原)与**反向**(故意篡改 zip,断言自检能抓到)两类。反向用例锁定了开发期真实踩过的三个 bug:中央目录偏移漏算文件数据长度、`localOffset` 记成数据区起点、`.test` 子串匹配误伤 `entry/src/ohosTest/ets/test/` 源码。**反向用例如果失效,自检就形同虚设**,改动脚本时务必保持它们通过。
#### 4.3.4 【常见错误】
**❌ `hvigor ERROR: 00303218 Configuration Error`**
```
Error Message: The reason and usedScene attributes are mandatory for user_grant permissions.
at entry\src\main\module.json5
```
**原因**:`user_grant` 类型权限必须**同时**声明 `reason` 和 `usedScene` 两个字段,只写 `usedScene` 会导致 `:entry:default@PreBuild` 失败。
**解决**:给对应权限补上 `reason` 字段,并在 `entry/src/main/resources/base/element/string.json` 中新增对应的理由文案。
```json5
// entry/src/main/module.json5
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:permission_microphone_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
```
```json
// entry/src/main/resources/base/element/string.json
{
"name": "permission_microphone_reason",
"value": "用于语音搜索与语音输入功能,录制语音以识别用户需求"
}
```
📌 **权限类型速查** —— 只有 `user_grant` 才需要 `reason`:
| 权限 | 类型 | 需要 reason |
|------|------|-------------|
| `ohos.permission.INTERNET` | system_grant | 否 |
| `ohos.permission.GET_NETWORK_INFO` | system_grant | 否 |
| `ohos.permission.VIBRATE` | system_grant | 否 |
| `ohos.permission.MICROPHONE` | **user_grant** | **是** |
📌 `reason` 文案必须是**用户可读的中文说明**,不能为空或写 `xxx description` 之类的占位文本,否则上架审核会被驳回。
📌 **改完 `reason` 还不够** —— `user_grant` 权限编译通过后,仍需在运行时弹窗申请,否则录音等功能不可用。本项目已在 [EntryAbility.ets](stub-app-projects/mp-harmony/entry/src/main/ets/entryability/EntryAbility.ets) 的 `onCreate` 中接入:
```ts
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 必须先调父类: ascf runtime 的初始化在 AscfUIAbility.onCreate 里, 漏掉会导致元服务白屏
super.onCreate(want, launchParam);
this.requestMicrophonePermission();
}
```
> ⚠️ **坑**:`AscfUIAbility` 父类**已定义** `onCreate`,重写时必须调 `super.onCreate(want, launchParam)`,否则 ascf runtime 初始化被跳过,元服务白屏。
> ⚠️ **坑**:`Permissions` 在 `@ohos.permissions` 里是**字符串联合类型**([permissions.d.ts](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-ability-kit/js-apis-permissions.md)),不是枚举,**只能用在类型位置**,当值引用会报:
> ```
> 'Permissions' only refers to a type, but is being used as a value here.
> ```
> 所以正确写法是标注 + 字面量,且权限名必须与 `module.json5` 中声明的一致:
> ```ts
> import { abilityAccessCtrl, AbilityConstant, Permissions, Want } from '@kit.AbilityKit';
>
> const PERMISSION_MICROPHONE: Permissions = 'ohos.permission.MICROPHONE';
> // ❌ Permissions.MICROPHONE —— 编译不过
> // ⚠️ abilityAccessCtrl.SwitchType.MICROPHONE(=1) 是"开关类型",不是权限名,别搞混
> ```
📌 为什么在 `onCreate` 申请而不是"用户点录音时才申请":
- 权限状态在 JS 侧(`uni.getRecorderManager`)读不到,项目里也没有现成的 JS ↔ ArkTS 通道,做不了按需触发
- `requestPermissionsFromUser` 对已授权的应用直接 resolve 且**不弹窗**,所以实际只有首次启动打扰用户一次
📌 用户拒绝后的降级:不影响浏览/下单/卡片等其他功能,仅语音输入不可用;[ai-chat-voice-panel.vue](pages_tool/ai-chat/components/ai-chat-voice-panel.vue) 会 catch 到 recorder 错误并 `showToast` 提示用户。
#### 4.3.5 【待完善】⚠️
- **release 签名仍指向 debug 证书**:`build-profile.json5` 中 `release` 签名的 `profile` 仍是 `./cert-profile/qq/qqDebug.p7b`,与 `default`(debug)共用同一份证书。本地调试无碍,但提交 AppGallery 前必须换成**发布证书 + release 类型 provisioning profile**,否则 AGC 校验会直接拒绝。
#### 4.3.6 【交付与最终发布】🔍
1. 将生成的 `.zip` 文件发送给运维人员
2. **运维人员操作流程**:
- 解压压缩包
- 打开鸿蒙开发者工具,导入解压后的代码目录
- 验证代码无报错后,执行上传发布操作
#### 4.3.7 【本地设置】
![](readme_files/1.jpg)
---
## 5. 📝 构建与版本信息
### 5.1 自动注入机制
生产构建时,`vue.config.js` 中的 `VersionInjectPlugin` 会自动执行以下操作:
| 操作 | 适用平台 | 说明 |
|------|---------|------|
| `__app_version` 全局变量注入 | 所有平台 | 构建产物的 `vendor.js`(小程序)或 `index.html`(H5)中自动内联版本数据 |
| 构建版本信息查看 | 所有平台 | 首页右下角连击 5 次弹出模态框显示 `commitId` 和 `buildTime` |
| 极限压缩优化 | 小程序(mp-weixin / mp-harmony) | 生产构建自动应用多层次压缩配置,显著减小产物包体积 |
版本数据包含:
- **commitId**:当前构建的 Git 短提交 ID
- **buildTime**:构建时间
> **开发模式**:HMR 热更新时由 webpack emit 钩子自动处理,不影响开发体验。
### 5.2 自动 ZIP 打包
**H5 自定义发行**构建完成后自动执行:
| 步骤 | 说明 |
|------|------|
| 重命名输出目录 | `{域名}-{base路径用.连接}`(如 `xcx.aigc-quickapp.com-hwappx.common`) |
| 创建 ZIP 文件 | 文件名格式:`{目录名}-{Git提交ID}-{上海时区时间戳}.zip` |
| 清理 | ZIP 创建完成后自动删除源目录 |
💡 **示例 ZIP**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
**微信小程序**生产构建时自动执行:
- 拷贝 `site.js` / `project.config.json` / `project.private.config.json` 文件到构建目录
- 创建 ZIP 包(文件名包含 Git 提交 ID)
**鸿蒙小程序**生产构建时自动执行:
- 拷贝必要配置文件到构建目录
- 创建 ZIP 包(文件名包含 Git 提交 ID)
### 5.3 环境配置
域名配置统一通过 `package.json` 中各脚本的 `env.UNI_APP_DOMAIN` 传入:
```json
"h5-xcx.aigc-quickapp.com": {
"title": "...",
"env": {
"UNI_PLATFORM": "h5",
"UNI_APP_DOMAIN": "https://xcx.aigc-quickapp.com/"
},
"define": { "PRODUCTION": true }
}
```
`config.js` 和 `vue.config.js` 均通过 `process.env.UNI_APP_DOMAIN` 读取,**新增/删除脚本无需改动代码**。
---
## 6. 🔄 版本历史
### 6.1 v1.5.1
- `VersionInjectPlugin`:webpack emit 钩子自动注入 `__app_version` 到所有平台产物
- 首页右下角连击 5 次显示构建版本信息(commitId + buildTime)
- `mp-weixin.patch.js` 精简为仅文件拷贝和 ZIP 打包
- 新增 `harmony.patch.js`:鸿蒙小程序生产构建补丁脚本(配置拷贝 + ZIP 打包)
- `open-directory.js`:构建完成后自动打开输出目录
- 极限压缩优化:集成 `zion-uniapp-mp-load-package` 插件 + TerserWebpackPlugin 多层次压缩配置
- 修复 `openWxWorkServiceChat` 方法中 fail 回调语法错误
- ZIP 文件名末尾使用 Git 提交 ID 替换时间戳
- 构建脚本幂等性修复,支持多次运行不产生重复注入
### 6.2 v1.3
- 修复微信小程序构建脚本,支持复制 `project.config.json` 和 `project.private.config.json` 文件
- 增强构建脚本功能,添加命令行参数支持
- 在 `package.json` 中添加相关 npm 脚本命令
- 优化脚本执行逻辑,提高可靠性和灵活性