feat: 完成现有小程序接口的全部迁移至新后端
This commit is contained in:
@@ -105,3 +105,7 @@ pnpm check
|
||||
本地备份预演:62 个文件,共 3,568,778 字节;其中题型预览 51、头像 7、字体 1、素材 1、首页配置 1、demo 1。该数量为本地预演结果,不代表已上传。
|
||||
|
||||
实现依据:[七牛官方 Node.js SDK](https://developer.qiniu.com/kodo/sdk/nodejs)、[七牛上传策略](https://developer.qiniu.com/kodo/manual/put-policy)。
|
||||
|
||||
## 回填服务端资产
|
||||
|
||||
上传报告确认成功后,根目录执行 `pnpm assets:map --report /绝对路径/报告.json` 预演,再添加 `--apply` 写入本机 doodle_dev。该独立回填步骤会重新核对七牛对象与备份摘要,事务更新已有题型预览资产,保留资源 ID 并记录审计;不处理头像归属、首页 JSON 嵌套链接或其他资源绑定。配置必须与 server 的域名、前缀及空间一致,详见根 README。
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { test } from 'node:test';
|
||||
import { buildAssetMappings } from '../../../apps/server/scripts/asset-mapping.mjs';
|
||||
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
||||
function fixture() {
|
||||
const sourceRef = 'cloud://legacy.env/assets/previews/math/a.jpg';
|
||||
const asset = {
|
||||
id: 'keep-original-id',
|
||||
sourceRef,
|
||||
sourceRefHash: hash(sourceRef),
|
||||
purpose: 'preview',
|
||||
visibility: 'public',
|
||||
ownerUserId: null,
|
||||
status: 'pending',
|
||||
};
|
||||
const file = {
|
||||
relativePath: 'assets/previews/math/a.jpg',
|
||||
key: 'doodle-assets/assets/previews/math/a.jpg',
|
||||
bucket: 'test-public',
|
||||
visibility: 'public',
|
||||
mimeType: 'image/jpeg',
|
||||
size: 10,
|
||||
sha256: hash('local'),
|
||||
etag: 'etag',
|
||||
status: 'uploaded',
|
||||
verifiedBy: 'qiniu-etag-and-size',
|
||||
};
|
||||
return {
|
||||
asset,
|
||||
file,
|
||||
report: { schemaVersion: 1, mode: 'apply', ok: true, files: [file] },
|
||||
config: { publicBucket: 'test-public', prefix: 'doodle-assets/' },
|
||||
};
|
||||
}
|
||||
|
||||
test('资产回填:保留 ID、完整 key 与原始来源;重复映射幂等', () => {
|
||||
const { asset, file, report, config } = fixture();
|
||||
const [mapped] = buildAssetMappings(report, [asset], config);
|
||||
assert.equal(mapped.asset.id, asset.id);
|
||||
assert.equal(mapped.data.objectKey, file.key);
|
||||
assert.equal(
|
||||
mapped.data.objectRefHash,
|
||||
hash(JSON.stringify(['qiniu', file.bucket, file.key])),
|
||||
);
|
||||
assert.equal(mapped.data.status, 'ready');
|
||||
assert.equal(mapped.unchanged, false);
|
||||
assert.equal(
|
||||
buildAssetMappings(report, [{ ...asset, ...mapped.data }], config)[0]
|
||||
.unchanged,
|
||||
true,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
buildAssetMappings(
|
||||
report,
|
||||
[{ ...asset, ...mapped.data, objectKey: 'edited-key' }],
|
||||
config,
|
||||
),
|
||||
/READY_ASSET_CONFLICT/,
|
||||
);
|
||||
});
|
||||
|
||||
test('资产回填:预演报告、未核对对象、缺失映射和来源不一致整批拒绝', () => {
|
||||
const { asset, file, report, config } = fixture();
|
||||
assert.throws(
|
||||
() =>
|
||||
buildAssetMappings({ ...report, mode: 'dry-run' }, [asset], config),
|
||||
/INVALID_SYNC_REPORT/,
|
||||
);
|
||||
for (const changed of [
|
||||
{ status: 'planned' },
|
||||
{ bucket: 'another-bucket' },
|
||||
{ key: 'wrong-prefix/a.jpg' },
|
||||
{ verifiedBy: null },
|
||||
{ sha256: 'bad' },
|
||||
])
|
||||
assert.throws(
|
||||
() =>
|
||||
buildAssetMappings(
|
||||
{ ...report, files: [{ ...file, ...changed }] },
|
||||
[asset],
|
||||
config,
|
||||
),
|
||||
/INVALID_REPORT_FILE/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
buildAssetMappings(
|
||||
report,
|
||||
[{ ...asset, sourceRefHash: 'bad' }],
|
||||
config,
|
||||
),
|
||||
/INVALID_SOURCE_REFERENCE/,
|
||||
);
|
||||
assert.throws(
|
||||
() => buildAssetMappings({ ...report, files: [] }, [asset], config),
|
||||
/ASSET_MAPPING_MISSING_OR_INELIGIBLE/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user