routes.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * API 路由主入口(客户独立实例)
  3. * - Amazon:保留本地 SP-API 模块
  4. * - Sorftime / TikHub:浏览器直打公司中转;定时任务经 src/relay RelayClient
  5. * - 已移除:直连 sorftime / tikhub / eccang 模块
  6. */
  7. import express from 'npm:express';
  8. const router = express.Router();
  9. console.log('[API Routes] Initializing Jianhen customer instance...');
  10. router.use((req, res, next) => {
  11. console.log(`[API Router] Hit: ${req.method} ${req.url} (Original: ${req.originalUrl})`);
  12. next();
  13. });
  14. router.get('/ping', (req, res) => {
  15. res.json({ message: 'pong', instance: 'jianhen-voc' });
  16. });
  17. function isInternalRequest(req) {
  18. const address = String(req.socket?.remoteAddress || '');
  19. return !req.headers['x-external-request']
  20. && (address === '127.0.0.1' || address === '::1' || address.endsWith(':127.0.0.1'));
  21. }
  22. async function requireApiUser(req, res, next) {
  23. if (isInternalRequest(req)) return next();
  24. const token = String(req.headers['x-parse-session-token'] || '').trim();
  25. if (!token) return res.status(401).json({ success: false, message: '需要登录' });
  26. try {
  27. const Parse = globalThis.Parse;
  28. const query = new Parse.Query('_Session');
  29. query.equalTo('sessionToken', token);
  30. query.include('user');
  31. const session = await query.first({ useMasterKey: true });
  32. const user = session?.get('user');
  33. if (!user || user.get('status') === 'disabled') {
  34. return res.status(401).json({ success: false, message: '会话无效' });
  35. }
  36. req.currentUser = user;
  37. next();
  38. } catch {
  39. res.status(401).json({ success: false, message: '会话无效' });
  40. }
  41. }
  42. function requireAdmin(req, res, next) {
  43. if (isInternalRequest(req) || req.currentUser?.get?.('isAdmin') === true) return next();
  44. res.status(403).json({ success: false, message: '需要管理员权限' });
  45. }
  46. function requireUserExceptHealth(req, res, next) {
  47. if (req.path === '/health') return next();
  48. return requireApiUser(req, res, next);
  49. }
  50. function requireAdminExceptToken(req, res, next) {
  51. if (req.path === '/token') return next();
  52. return requireAdmin(req, res, next);
  53. }
  54. /**
  55. * 亚马逊相关接口
  56. * 必须携带店铺的 id
  57. */
  58. import { createSpApiRouter } from '../modules/fmode-amazon-sp-api/src/mod.ts';
  59. router.use('/amazon', requireApiUser, createSpApiRouter());
  60. /**
  61. * 公司中转密钥/余额:仅管理员
  62. * Sorftime / TikHub 浏览器请求不再经过本机 /forward。
  63. */
  64. import relayCredentialRouter from './relay/credential-routes.ts';
  65. router.use('/relay', requireApiUser, requireAdminExceptToken, relayCredentialRouter);
  66. console.log('[API Routes] ✅ Company relay credential routes mounted');
  67. // 定时任务管理路由(内部已改为 RelayClient 直打公司中转)
  68. import scheduleRouter from './module/schedule/routes-schedule.ts';
  69. try {
  70. router.use('/schedule', requireApiUser, requireAdmin, scheduleRouter);
  71. } catch (e) {
  72. console.warn('[API Routes] scheduleRouter 加载失败', e);
  73. }
  74. router.get('/', (req, res) => {
  75. res.json({
  76. message: 'Jianhen VOC Instance API Routes Loaded',
  77. version: '1.0.1-customer-relay',
  78. timestamp: new Date().toISOString(),
  79. availableRoutes: [
  80. '/api/amazon',
  81. '/api/relay/token',
  82. '/api/relay/status',
  83. '/api/relay/credential',
  84. '/api/schedule',
  85. ],
  86. removedModules: [
  87. 'fmode-sorftime-api (direct upstream)',
  88. 'fmode-tikhub-api (direct upstream)',
  89. 'fmode-tikhub-api-customize',
  90. 'fmode-eccang-api',
  91. ],
  92. relay: {
  93. companyBase: 'https://server.fmode.cn',
  94. sorftimeUpstream: '/api/voc-ecom/forward',
  95. tikhubUpstream: '/api/voc-social/*',
  96. },
  97. });
  98. });
  99. export default router;