Files
juzi/h5/lib/taobao-routes.js
T
xsl 774376fa16 feat: 完善淘宝小程序拍照、报告与登录流程
接入本地实时识别和自动三连拍,调整报告裁切、商品跳转、分享标题及居中页眉;完善游客会话、后台渠道隔离、部署脚本与回归测试。
2026-11-04 15:07:51 +08:00

87 lines
5.9 KiB
JavaScript

'use strict';
const TaobaoAuth = require('./taobao-auth');
module.exports = function ({ accounts, wrinkleJobs, readBody, sendJson, handleAnalyze }) {
const taobaoSessions = require('./taobao-session')({ session: accounts.taobaoSession });
return async function (req, res, pathname) {
res.setHeader('Cache-Control', 'no-store');
// The cloud gateway requires its own envelope. H5 responses stay unchanged.
const reply = (status, data) => sendJson(res, 200, { success: true, data: { ...data, http_status: status } });
if (req.method !== 'POST') return reply(405, { ok: false, message: '请求方法不支持' });
if (!accounts.sameOrigin(req)) return reply(403, { ok: false, message: '请求来源不匹配' });
try {
const raw = (await readBody(req, pathname === '/api/taobao/photo' ? 70 * 1024 : 4096)).toString('utf8');
const body = JSON.parse(raw);
if (!body || typeof body !== 'object' || Array.isArray(body)) return reply(400, { ok: false, message: '请求数据异常' });
if (pathname === '/api/taobao/challenge') {
const challenge = taobaoSessions.challenge(req.socket.remoteAddress);
return reply(challenge ? 200 : 429, challenge ? { ok: true, challenge } : { ok: false, message: '操作频繁,请稍后重试' });
}
if (pathname === '/api/taobao/session') {
if (body.consent !== true) return reply(400, { ok: false, message: '请先阅读并同意隐私协议' });
const identity = TaobaoAuth.verify(req.url, raw);
if (!identity || !taobaoSessions.consume(body.challenge)) return reply(401, { ok: false, message: '淘宝身份校验失败,请重新授权登录' });
return reply(200, { ok: true, ...accounts.loginTaobao(identity.providerKey) });
}
if (pathname === '/api/taobao/guest-session') {
if (body.consent !== true) return reply(400, { ok: false, message: '请先阅读并同意隐私协议' });
if (!TaobaoAuth.verifyGateway(req.url, raw) || !taobaoSessions.consume(body.challenge)) {
return reply(401, { ok: false, message: '游客会话校验失败,请重新开始' });
}
const guest = accounts.loginTaobaoGuest();
return reply(guest ? 200 : 429, guest ? { ok: true, ...guest } : { ok: false, message: '操作频繁,请稍后再试' });
}
const owner = accounts.taobaoSession(req);
if (!owner) return reply(401, { ok: false, message: '检测会话已失效,请重新开始' });
if (pathname === '/api/taobao/me') {
// A cached Bearer must not restore the previous Taobao account after
// the host app switches accounts. Guests require a signed app gateway
// but intentionally have no dependency on userInfo/open_id authorization.
const identity = owner.kind === 'taobao_guest' ? null : TaobaoAuth.verify(req.url, raw);
const matches = owner.kind === 'taobao_guest' ? !!TaobaoAuth.verifyGateway(req.url, raw)
: !!identity && identity.providerKey === owner.provider_key;
return reply(matches ? 200 : 401, matches ? { ok: true, user: { id: owner.id, kind: owner.kind } }
: { ok: false, message: '淘宝账号状态已变化,请重新进入' });
}
if (pathname === '/api/taobao/photo') {
const { status, ...data } = taobaoSessions.upload(req, body);
return reply(status, { ok: status === 200, ...data });
}
if (pathname === '/api/taobao/skin-analyze') {
// Start once and poll: cloud gateway requests have a maximum 10s timeout.
const attempt = taobaoSessions.beginAnalysis(req, body, photos => new Promise(resolve => {
const response = { setHeader() {}, writeHead() {}, end(text, cb) { resolve(JSON.parse(text)); if (cb) cb(); } };
handleAnalyze(req, response, true, { ...body, ...photos }, owner).catch(() => resolve({ ok: false, message: '检测服务暂不可用,请重试' }));
}));
return reply(attempt.status, { ok: attempt.status === 202,
...(attempt.job ? { job: attempt.job } : { message: attempt.message }) });
}
if (pathname === '/api/taobao/analysis') {
const result = taobaoSessions.result(req, body.job);
return reply(result ? 200 : 404, result ? { ok: true, ...result } : { ok: false, message: owner.kind === 'taobao_guest' ? '本次检测任务已失效,请重新开始' : '检测任务已失效,请查看历史报告或重新检测' });
}
if (pathname === '/api/taobao/reports') {
if (owner.kind === 'taobao_guest') return reply(body.id ? 404 : 200, body.id ? { ok: false, message: '游客模式不保存历史报告' } : { ok: true, total: 0, items: [] });
if (!body.id) return reply(200, { ok: true, total: accounts.reports(owner.id).length, items: accounts.reports(owner.id) });
const report = accounts.ownedReport(owner.id, body.id);
return reply(report ? 200 : 404, report ? { ok: true, report } : { ok: false, message: '报告不存在' });
}
if (pathname === '/api/taobao/logout') {
taobaoSessions.revoke(req);
accounts.taobaoLogout(req);
return reply(200, { ok: true });
}
if (pathname === '/api/taobao/wrinkle-views') {
const job = taobaoSessions.start(req, async () => {
const views = await wrinkleJobs.run(owner, body.token);
return views ? { ok: true, wrinkle_views: views } : { ok: false, message: '标注任务已失效' };
});
return reply(job ? 202 : 429, job ? { ok: true, job } : { ok: false, message: '服务繁忙,请稍后再试' });
}
return reply(404, { ok: false, message: '接口不存在' });
} catch (e) {
const status = e.message === 'TOO_LARGE' ? 413 : e instanceof SyntaxError ? 400 : 500;
return reply(status, { ok: false, message: status === 413 ? '请求数据过大' : status === 400 ? '请求数据异常' : '服务暂不可用,请稍后再试' });
}
};
};