feat(taobao): add native mini program with shared backend

Preserve H5 flows while adding Taobao identity, native capture, reports and isolated local development. Fix canceled province selection and expired-session logout. Record passing local regressions, two pre-existing H5 assertion failures and pending platform validation.
This commit is contained in:
xsl
2026-10-19 20:14:31 +08:00
parent d07f8cc1bb
commit 9eec4369da
61 changed files with 4827 additions and 17 deletions
+48
View File
@@ -0,0 +1,48 @@
'use strict';
// Local-only fixture backend and gateway simulator. Never contacts Face++/Taobao.
const http = require('node:http'), fs = require('node:fs'), path = require('node:path'), vm = require('node:vm');
const root = path.resolve(__dirname, '../..');
const dataRoot = path.join(root, 'temp/taobao-local-data');
Object.assign(process.env, { NODE_ENV: 'test', PORT: '18181', HOST: '127.0.0.1', PUBLIC_BASE_URL: '',
JUZI_DATA_ROOT: dataRoot, WECHAT_APP_ID: '', WECHAT_APP_SECRET: '',
SKIN_API_KEY: 'local-fixture', SKIN_API_SECRET: 'local-fixture',
TAOBAO_APP_KEY: 'local-fixture', TAOBAO_APP_SECRET: 'local-fixture-not-a-production-secret' });
const context = { window: {} };
vm.runInNewContext(fs.readFileSync(path.join(root, 'h5/assets/mock/report-fixture.js'), 'utf8'), context);
const fixture = JSON.parse(JSON.stringify(context.window.MOCK_FIXTURE));
global.fetch = async () => ({ ok: true, status: 200, json: async () => ({ request_id: 'local-fixture',
result: structuredClone(fixture.result), face_rectangle: fixture.face_rectangle,
left_side_result: fixture.side.left, right_side_result: fixture.side.right }) });
require(path.join(root, 'h5/server'));
const auth = require(path.join(root, 'h5/lib/taobao-auth'));
http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost');
if (url.pathname === '/dev/health') {
res.setHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ ok: true, fixture: true, data_root: dataRoot }));
}
const chunks = []; let length = 0;
for await (const chunk of req) {
length += chunk.length;
if (length > 128 * 1024) { res.writeHead(413); return res.end(); }
chunks.push(chunk);
}
const raw = Buffer.concat(chunks).toString('utf8');
if (url.pathname === '/api/taobao/session') {
url.search = new URLSearchParams({ app_key: process.env.TAOBAO_APP_KEY,
open_id: 'local-development-user', sign_method: 'md5' }).toString();
const params = Object.fromEntries(url.searchParams);
url.searchParams.set('sign', auth.signature(params, raw, process.env.TAOBAO_APP_SECRET));
}
const headers = { ...req.headers, host: '127.0.0.1:18181', 'content-length': Buffer.byteLength(raw) };
delete headers.origin;
const upstream = http.request({ hostname: '127.0.0.1', port: 18181, path: url.pathname + url.search,
method: req.method, headers }, upstreamRes => {
res.writeHead(upstreamRes.statusCode, upstreamRes.headers); upstreamRes.pipe(res);
});
upstream.on('error', () => { if (!res.headersSent) res.writeHead(502); res.end(); });
upstream.end(raw);
}).listen(18180, '127.0.0.1', () => {
console.log('[local] Gateway simulator + H5: http://127.0.0.1:18180');
console.log('[local] Fixture data only; account/report files: ' + dataRoot);
});
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto');
const root = path.resolve(__dirname, '..'), check = process.argv.includes('--check');
const files = ['skin-score.js', 'report-ui.js', 'report-copy.js'];
const manifest = {};
for (const name of files) {
const source = fs.readFileSync(path.join(root, '../h5/js', name));
const dest = path.join(root, 'vendor', name);
// Git may convert line endings on Windows; manifest hashes stay portable.
manifest[name] = crypto.createHash('sha256').update(source.toString('utf8').replace(/\r\n/g, '\n')).digest('hex');
if (check) {
if (!fs.existsSync(dest) || !source.equals(fs.readFileSync(dest))) throw Error('Shared logic drift: run npm run sync:shared (' + name + ')');
} else { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, source); }
}
const dest = path.join(root, 'vendor/manifest.json'), expected = JSON.stringify(manifest, null, 2) + '\n';
if (check) { if (fs.readFileSync(dest, 'utf8') !== expected) throw Error('Shared manifest drift'); }
else fs.writeFileSync(dest, expected);
console.log(check ? 'PASS: scoring, report UI and copy match H5 byte for byte' : 'Synced shared logic from H5 (source files unchanged)');