fix(V0.2): OTA 发布说明改为 commit message, sha 单独展示; 版本附构建时间
Docker Build and Push / build-image (push) Successful in 1m2s

- CI: notes 取 git log -1 的 commit message (标题+正文, 截断 400 字),
  commit sha 经 --commit 写入 manifest (不再把裸 sha 塞进发布说明)
- build-ota: 新增 --commit 参数; latest.json 增加 commit 字段
- 服务端 /ota/check 与 /manifest-web 透传 commit 与 sequence
- 我的页: 当前版本/新版本后附 seq 时间戳 (MM-dd HH:mm),
  发布说明下方新增「提交」行显示短 sha
This commit is contained in:
jif2.zhang
2026-10-25 11:56:04 +08:00
parent 74e1e7dd13
commit e3ac00a641
5 changed files with 781 additions and 757 deletions
+5 -2
View File
@@ -85,13 +85,16 @@ jobs:
- name: Build & sign OTA package - name: Build & sign OTA package
# 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增) # 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增)
# 发布说明 = 触发构建的 commit message (标题+正文, 截断 400 字),
# commit sha 通过 --commit 单独写入 manifest, 前端分行展示
# 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像 # 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像
env: env:
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
OTA_NOTES: ${{ github.sha }}
run: | run: |
set -euo pipefail set -euo pipefail
node scripts/build-ota.mjs --out ota-package --notes ${OTA_NOTES} --min-binary ${OTA_MIN_BINARY} MSG="$(git log -1 --pretty=format:%B | sed -e '/^[[:space:]]*$/d' | head -c 400)"
echo "notes: ${MSG}"
node scripts/build-ota.mjs --out ota-package --notes "${MSG}" --commit "${GITHUB_SHA}" --min-binary ${OTA_MIN_BINARY}
echo "===== latest.json =====" echo "===== latest.json ====="
cat ota-package/latest.json cat ota-package/latest.json
echo "===== bundle sha256 =====" echo "===== bundle sha256 ====="
+4 -1
View File
@@ -29,6 +29,8 @@ const OUT_DIR = path.resolve(getArg('--out', 'ota-package'))
const SEQUENCE = Number(getArg('--sequence', String(Date.now()))) // 默认用时间戳做单调计数 const SEQUENCE = Number(getArg('--sequence', String(Date.now()))) // 默认用时间戳做单调计数
const MIN_BINARY = getArg('--min-binary', '') const MIN_BINARY = getArg('--min-binary', '')
const NOTES = getArg('--notes', '') const NOTES = getArg('--notes', '')
// 构建对应的 git commit sha (发布说明=commit message, sha 单独一行展示)
const COMMIT = getArg('--commit', '')
const NO_SIGN = args.includes('--no-sign') const NO_SIGN = args.includes('--no-sign')
// 私钥优先来源: MINISIGN_PRIVATE_KEY 环境变量 (CI 注入) → 文件 (--key 路径) // 私钥优先来源: MINISIGN_PRIVATE_KEY 环境变量 (CI 注入) → 文件 (--key 路径)
const KEY_PATH = path.resolve(getArg('--key', path.join(OUT_DIR, 'hotswap.key'))) const KEY_PATH = path.resolve(getArg('--key', path.join(OUT_DIR, 'hotswap.key')))
@@ -117,7 +119,8 @@ const manifest = {
bundle_size: bundleStat.size, bundle_size: bundleStat.size,
bundle_sha256: bundleSha256, bundle_sha256: bundleSha256,
signature, signature,
notes: NOTES, notes: String(NOTES).slice(0, 400),
commit: COMMIT || undefined,
pub_date: new Date().toISOString(), pub_date: new Date().toISOString(),
mandatory: false, mandatory: false,
// 内部用: 前端 web 模式比对 build_id // 内部用: 前端 web 模式比对 build_id
+3
View File
@@ -63,6 +63,7 @@ router.get('/ota/check/:current_sequence', (req, res) => {
url: (base ? base : '') + '/api/ota/bundle/' + encodeURIComponent(latest.bundle), url: (base ? base : '') + '/api/ota/bundle/' + encodeURIComponent(latest.bundle),
signature: latest.signature, signature: latest.signature,
notes: latest.notes || '', notes: latest.notes || '',
commit: latest.commit || '',
pub_date: latest.pub_date || new Date().toISOString(), pub_date: latest.pub_date || new Date().toISOString(),
mandatory: !!latest.mandatory, mandatory: !!latest.mandatory,
bundle_size: latest.bundle_size || 0, bundle_size: latest.bundle_size || 0,
@@ -91,6 +92,8 @@ router.get('/ota/manifest-web', (_req, res) => {
res.json({ res.json({
available: true, available: true,
version: latest.version, version: latest.version,
sequence: latest.sequence,
commit: latest.commit || '',
build_id: latest.build_id || latest.version, build_id: latest.build_id || latest.version,
note: latest.notes || '', note: latest.notes || '',
size: latest.bundle_size || 0, size: latest.bundle_size || 0,
+3
View File
@@ -115,6 +115,7 @@ async function checkByVersion() {
version: manifest.version, version: manifest.version,
sequence: manifest.sequence, sequence: manifest.sequence,
notes: manifest.notes || '', notes: manifest.notes || '',
commit: manifest.commit || '',
mandatory: !!manifest.mandatory, mandatory: !!manifest.mandatory,
bundle_size: manifest.bundle_size || 0, bundle_size: manifest.bundle_size || 0,
}, },
@@ -166,7 +167,9 @@ async function checkOtaUpdateWeb(devMode) {
reason: 'ok', reason: 'ok',
info: { info: {
version: data.version, version: data.version,
sequence: data.sequence,
notes: data.note || '', notes: data.note || '',
commit: data.commit || '',
// web 端没有强制/mandatory 等字段, 仅展示 // web 端没有强制/mandatory 等字段, 仅展示
mandatory: false, mandatory: false,
bundle_size: data.size || 0, bundle_size: data.size || 0,
+14 -2
View File
@@ -14,6 +14,15 @@ import {
setOtaDevMode, setOtaDevMode,
} from '../api/ota.js' } from '../api/ota.js'
// seq/build 时间戳 → 'MM-dd HH:mm' (sequence 为毫秒时间戳, build_id 形如 seq-<ms>)
export function fmtSeqTime(v) {
const ms = typeof v === 'string' && v.startsWith('seq-') ? Number(v.slice(4)) : Number(v)
if (!Number.isFinite(ms) || ms <= 0) return ''
const p = (n) => String(n).padStart(2, '0')
const d = new Date(ms)
return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
}
// "我" 页: 顶部 NavBar + 用户头像卡片 + 信息列表 + OTA 升级区 // "我" 页: 顶部 NavBar + 用户头像卡片 + 信息列表 + OTA 升级区
// OTA 区: // OTA 区:
// - 显示当前 APP_VERSION (前端包, 对应内置 dist) // - 显示当前 APP_VERSION (前端包, 对应内置 dist)
@@ -137,7 +146,7 @@ export default function MePage() {
{/* OTA 升级区 */} {/* OTA 升级区 */}
<List className="me-list" mode="card" header="前端升级 (OTA)"> <List className="me-list" mode="card" header="前端升级 (OTA)">
<List.Item extra={'v' + cur.version + ' · ' + cur.build}>当前版本</List.Item> <List.Item extra={'v' + cur.version + (fmtSeqTime(cur.build) ? ' · ' + fmtSeqTime(cur.build) : '')}>当前版本</List.Item>
<List.Item extra={otaVersion?.active ? 'v' + (otaVersion.version || '?') : '内置'}> <List.Item extra={otaVersion?.active ? 'v' + (otaVersion.version || '?') : '内置'}>
运行资源 运行资源
</List.Item> </List.Item>
@@ -197,7 +206,7 @@ export default function MePage() {
</div> </div>
</List.Item> </List.Item>
{otaState === 'available' && otaInfo && ( {otaState === 'available' && otaInfo && (
<List.Item extra={'v' + otaInfo.version}> <List.Item extra={'v' + otaInfo.version + (fmtSeqTime(otaInfo.sequence) ? ' · ' + fmtSeqTime(otaInfo.sequence) : '')}>
新版本 新版本
{otaInfo.bundle_size ? ` (${(otaInfo.bundle_size / 1024).toFixed(0)} KB)` : ''} {otaInfo.bundle_size ? ` (${(otaInfo.bundle_size / 1024).toFixed(0)} KB)` : ''}
</List.Item> </List.Item>
@@ -217,6 +226,9 @@ export default function MePage() {
{otaInfo?.notes && ( {otaInfo?.notes && (
<List.Item description={otaInfo.notes}>发布说明</List.Item> <List.Item description={otaInfo.notes}>发布说明</List.Item>
)} )}
{otaInfo?.commit && (
<List.Item extra={String(otaInfo.commit).slice(0, 10)}>提交</List.Item>
)}
{error && ( {error && (
<List.Item> <List.Item>
<span className="me-ota-error">{error}</span> <span className="me-ota-error">{error}</span>