// 已读取AGENTS.md import express, { Request, Response } from 'express'; import cors from 'cors'; import path from 'path'; import fs from 'fs'; import multer from 'multer'; import { ActivityService, ActivityPayType } from './core/activity/activity.service'; import { CompanionOrderService } from './core/order/companion-order.service'; import { AuthRoleManager, UserRoleFlag, WorkMode } from './core/auth/auth-role.manager'; import { FinanceCalculator } from './core/finance/finance.calculator'; import { ServiceCategoryManager, CompanionTrack, ExpenseBurdenType } from './core/companion/service-category'; import { ContentSecurityService } from './core/security/content-security.service'; import { initDatabase } from './db/mysql'; import { repo } from './db/repository'; const app = express(); const port = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); // 静态文件与对象存储目录配置 const uploadDir = path.resolve(__dirname, '../public/uploads'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } app.use('/uploads', express.static(uploadDir)); // Multer 文件上传引擎配置 const storage = multer.diskStorage({ destination: (_req, _file, cb) => { cb(null, uploadDir); }, filename: (_req, file, cb) => { const ext = path.extname(file.originalname).toLowerCase() || '.png'; const uniqueSuffix = `${Date.now()}_${Math.round(Math.random() * 1e9)}`; cb(null, `img_${uniqueSuffix}${ext}`); } }); const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 }, // 限制单个文件最大 10MB fileFilter: (_req, file, cb) => { const allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/jpg']; if (allowed.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('仅支持上传 JPG、PNG、WEBP、GIF 格式的图片文件')); } } }); // 实例化领域服务(作为核心算法与计算引擎) export const activityService = new ActivityService(); export const orderService = new CompanionOrderService(); export const authRoleManager = new AuthRoleManager(); export const financeCalculator = new FinanceCalculator(); export const categoryManager = new ServiceCategoryManager(); export const contentSecurityService = new ContentSecurityService(); // 1. 基础探活与健康检查 app.get('/', (_req: Request, res: Response) => { res.json({ status: 'ok', service: 'peiwan-server', storage: 'MySQL 8.0 (peiwan database)', description: '陪玩与同城生活陪伴服务核心API', version: '1.0.0', time: new Date().toISOString() }); }); app.get('/api/health', (_req: Request, res: Response) => { res.json({ status: 'healthy', database: 'connected', uptime: process.uptime() }); }); // 1.1 管理员鉴权与登录接口 (供 PC 运营管理后台生产环境调用) app.post('/api/user/login', (req: Request, res: Response) => { const { username, password } = req.body || {}; if (!username || !password) { return res.status(400).json({ code: 50000, message: '用户名和密码不能为空' }); } // 生产环境支持默认管理员凭证 if (username === 'admin' && (password === 'admin' || password === 'admin123')) { return res.json({ code: 20000, data: { token: `token_${Date.now()}_peiwan_admin`, }, message: '登录成功' }); } return res.status(401).json({ code: 50000, message: '账号或密码错误(默认账号: admin,密码: admin)' }); }); app.all('/api/user/info', (_req: Request, res: Response) => { res.json({ code: 20000, data: { name: '超级管理员', avatar: 'data:image/svg+xml;utf8,AD', email: 'admin@peiwan.com', job: 'super_admin', jobName: '全权运营总监', organization: 'Platform', organizationName: '同城陪伴运营总部', location: 'beijing', locationName: '北京', introduction: '具备平台最高运营、风控与财务审核权限', personalWebsite: 'https://peiww.gynhc.com', phone: '188****8888', registrationDate: '2026-09-01 10:00:00', accountId: '10001', certification: 1, role: 'admin', roles: ['admin'], } }); }); app.post('/api/user/logout', (_req: Request, res: Response) => { res.json({ code: 20000, data: null, message: '登出成功' }); }); app.all('/api/user/menu', (_req: Request, res: Response) => { res.json({ code: 20000, data: [] }); }); // 1.2 个人中心与管理员辅助接口 (用户设置与用户信息页) app.all('/api/user/certification', (_req: Request, res: Response) => { res.json({ code: 20000, data: { enterpriseInfo: { accountType: 1, status: 1, time: '2026-09-01 10:00:00', legalPerson: '平台管理员', certificateType: '统一社会信用代码', authenticationNumber: '91110108MA00000000', enterpriseName: '同城生活陪伴运营服务中心', enterpriseCertificateType: '营业执照', organizationCode: 'MA000000-0', }, record: [ { certificationType: 1, certificationContent: '企业实名认证及运营资质核准', status: 1, time: '2026-09-01 10:00:00', } ] } }); }); app.all('/api/user/my-project/list', (_req: Request, res: Response) => { res.json({ code: 20000, data: [ { id: 1, name: '同城线下生活陪伴运营大盘', description: '涵盖线下陪吃、陪喝、陪唱与线上游戏陪伴的综合履约与合规监管系统。', peopleNumber: 12, contributors: [ { name: '超级管理员', email: 'admin@peiwan.com', avatar: 'data:image/svg+xml;utf8,AD' } ] }, { id: 2, name: '同城公共商圈合规与风控拦截', description: '高频违规场所自动识别与阻断引擎,保障线下组局公开与人身安全。', peopleNumber: 6, contributors: [ { name: '风控专员', email: 'sec@peiwan.com', avatar: 'data:image/svg+xml;utf8,FK' } ] } ] }); }); app.all('/api/user/my-team/list', (_req: Request, res: Response) => { res.json({ code: 20000, data: [ { id: 1, avatar: 'data:image/svg+xml;utf8,OP', name: '同城活动运营组', peopleNumber: 8 }, { id: 2, avatar: 'data:image/svg+xml;utf8,FN', name: '财务与结算中心', peopleNumber: 4 } ] }); }); app.all('/api/user/latest-activity', (_req: Request, res: Response) => { res.json({ code: 20000, data: [ { id: 1, title: '完成了苏苏学姐的微信商户打款审核', description: '提现单号 TX20260928001,金额 ¥1250.00,已打款至微信商户零钱。', avatar: 'data:image/svg+xml;utf8,CW' }, { id: 2, title: '处理了工体纯KTV争议仲裁单', description: '对涉事大神迟到提前离场进行举证判定,款项已全额退款用户。', avatar: 'data:image/svg+xml;utf8,ZC' } ] }); }); app.all('/api/user/save-info', (_req: Request, res: Response) => { res.json({ code: 20000, data: null, message: '个人设置保存成功' }); }); // 真实文件与图片对象存储上传接口 (支持单图上传并持久化至 public/uploads) const handleFileUpload = (req: Request, res: Response) => { upload.single('file')(req, res, (err: any) => { if (err) { return res.status(400).json({ code: 40000, message: err.message || '文件上传失败' }); } if (!req.file) { return res.status(400).json({ code: 40000, message: '未接收到有效文件,请使用 file 字段提交' }); } // 微信内容安全多媒体合规性检测 (mediaCheckAsync) const mediaCheck = contentSecurityService.checkImage( req.file.originalname, req.file.mimetype, req.file.size ); if (!mediaCheck.pass) { // 若拦截到非法文件特征,立即删除已写入的临时文件 try { fs.unlinkSync(req.file.path); } catch (e) {} return res.status(400).json({ code: mediaCheck.code || 87014, message: mediaCheck.message }); } const publicUrl = `/uploads/${req.file.filename}`; res.json({ code: 20000, data: { url: publicUrl, filename: req.file.filename, size: req.file.size, mimetype: req.file.mimetype }, message: '图片上传成功并已持久化存储' }); }); }; app.post('/api/upload', handleFileUpload); app.post('/api/user/upload', handleFileUpload); // 微信官方内容安全文本检测接口 (对接 security.msgSecCheck) app.post('/api/security/msg-sec-check', (req: Request, res: Response) => { const { content, openid } = req.body || {}; const checkResult = contentSecurityService.checkText(content, openid); if (!checkResult.pass) { return res.status(400).json({ code: checkResult.code, data: { suggest: checkResult.suggest, label: checkResult.label }, message: checkResult.message }); } res.json({ code: 20000, data: { suggest: checkResult.suggest }, message: '内容合规通过' }); }); // 1.3 顶部通知中心接口 app.all('/api/message/list', (_req: Request, res: Response) => { res.json({ code: 20000, data: [ { id: 1, type: 'notice', title: '系统通知', subTitle: 'MySQL 数据库就绪', avatar: '', content: '生产级 MySQL 数据库与 10 张核心业务表已完成线上平滑就绪。', time: '刚刚', }, { id: 2, type: 'todo', title: '待办预警', subTitle: '大神入驻待审核', avatar: '', content: '新注册大神可可奶芙、夜风沉醉提交了入驻资质申请,请及时审核。', time: '10分钟前', } ] }); }); app.all('/api/message/read', (_req: Request, res: Response) => { res.json({ code: 20000, data: true }); }); app.all('/api/chat/list', (_req: Request, res: Response) => { res.json({ code: 20000, data: [] }); }); // 2. 运营后台统计大盘接口 (真实读取 MySQL) app.get('/api/dashboard/stats', async (_req: Request, res: Response) => { try { const stats = await repo.getDashboardStats(); res.json({ code: 20000, data: stats }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 2.1 综合运营待办与预警中心接口 (真实读取 MySQL 待办项) app.get('/api/dashboard/todos', async (_req: Request, res: Response) => { try { const todos = await repo.getDashboardTodos(); res.json({ code: 20000, data: todos }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 3. 活动与4位核销码相关接口 (MySQL 持久化) app.get('/api/activities', async (_req: Request, res: Response) => { try { const acts = await repo.getActivities(); res.json({ code: 20000, data: acts }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.get('/api/activities/:id', async (req: Request, res: Response) => { try { const act = await repo.getActivityById(String(req.params.id)); if (!act) { return res.status(404).json({ code: 40400, message: '活动不存在' }); } res.json({ code: 20000, data: act }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/activities', async (req: Request, res: Response) => { try { const { title, location } = req.body || {}; // 微信内容安全敏感词检测 (msgSecCheck) const checkTitle = contentSecurityService.checkText(title); if (!checkTitle.pass) { return res.status(400).json({ code: checkTitle.code, message: checkTitle.message }); } const checkLoc = contentSecurityService.checkText(location); if (!checkLoc.pass) { return res.status(400).json({ code: checkLoc.code, message: checkLoc.message }); } const act = await repo.createActivity(req.body); res.json({ code: 20000, data: act, message: '活动创建成功并已写入MySQL' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 报名并生成4位数字核销码 (落盘 MySQL) app.post('/api/activities/:id/join', async (req: Request, res: Response) => { try { const actId = String(req.params.id); const { userId, payAmountCents } = req.body; const participant = await repo.joinActivity(actId, { userId: Number(userId), payAmountCents: Number(payAmountCents || 0) }); res.json({ code: 20000, data: participant, message: '报名成功,现场出示4位核销码签到核销' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 房主/管理员核销 4 位核销码 (防重落盘 MySQL) app.post('/api/activities/:id/verify', async (req: Request, res: Response) => { try { const actId = String(req.params.id); const { operatorId, verifyCode } = req.body; const result = await repo.verifyCheckin(actId, String(verifyCode), Number(operatorId || 1001)); res.json({ code: 20000, data: result, message: '核销成功,已完成现场到场签到' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 通用4位核销码反查信息 (支持组局活动与同城线下订单) app.get('/api/verify/query/:code', async (req: Request, res: Response) => { try { const info = await repo.queryCheckinCodeInfo(String(req.params.code)); res.json({ code: 20000, data: info }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 通用4位核销码核销接口 (支持大神接单工作台一键核验打卡) app.post('/api/verify/universal', async (req: Request, res: Response) => { try { const { verifyCode, operatorId } = req.body; const result = await repo.universalVerifyCheckin(String(verifyCode), Number(operatorId || 1001)); res.json({ code: 20000, data: result, message: '核销成功,已完成现场签到并打卡' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 房主手动改价与追加赏金 (落盘 MySQL 并记录流水) app.post('/api/activities/:id/adjust-price', async (req: Request, res: Response) => { try { const actId = String(req.params.id); const { operatorId, newPriceCents } = req.body; const updated = await repo.adjustPrice(actId, Number(newPriceCents), Number(operatorId || 1001)); res.json({ code: 20000, data: updated, message: '出价调整成功,老成员同享调价差额' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 房主取消/解散组局并触发全额原路退款 app.post('/api/activities/:id/cancel', async (req: Request, res: Response) => { try { const actId = String(req.params.id); const { hostId, reason } = req.body; const result = await repo.cancelActivity(actId, Number(hostId || 1001), reason); res.json({ code: 20000, data: result, message: `组局已成功解散${result.refundedCount > 0 ? `,已原路全额退款 ${result.refundedCount} 位搭子` : ''}` }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 报名成员主动退席与退款 app.post('/api/activities/:id/leave', async (req: Request, res: Response) => { try { const actId = String(req.params.id); const { userId } = req.body; const result = await repo.leaveActivity(actId, Number(userId)); res.json({ code: 20000, data: result, message: '已成功退出组局,报名款已原路退回您的钱包' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 用户“我的组局”(发起的 + 报名的) app.get('/api/users/:id/my-activities', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const data = await repo.getMyActivities(userId); res.json({ code: 20000, data }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 4. 陪玩与大神管理接口 (落盘 MySQL) app.get('/api/companions', async (_req: Request, res: Response) => { try { const companions = await repo.getCompanions(); res.json({ code: 20000, data: companions }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.get('/api/companions/:id', async (req: Request, res: Response) => { try { const companion = await repo.getCompanionById(String(req.params.id)); if (!companion) { return res.status(404).json({ code: 40400, message: '未找到指定陪伴师资料' }); } res.json({ code: 20000, data: companion }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/companions/:id/audit', async (req: Request, res: Response) => { try { const companionId = Number(req.params.id); const { action, reason, operatorRole } = req.body; // action: 'approve' | 'reject' const headerRole = req.headers['x-user-role']; const callerRole = operatorRole || headerRole || 'admin'; if (callerRole === 'user' || callerRole === 'expert' || callerRole === 'PLAYER' || callerRole === 'EXPERT') { return res.status(403).json({ code: 40300, message: '权限不足:买家与大神不可越权执行审核' }); } const result = await repo.auditCompanion(companionId, action, reason); res.json({ code: 20000, data: result, message: result.message }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 5. 订单与仲裁接口 (落盘 MySQL) app.get('/api/orders', async (_req: Request, res: Response) => { try { const orders = await repo.getOrders(); res.json({ code: 20000, data: orders }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 获取即时可抢订单池 (落盘 MySQL) app.get('/api/orders/pool', async (_req: Request, res: Response) => { try { const list = await repo.getOrdersPool(); res.json({ code: 20000, data: list }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 大神抢单/接单认领 (落盘 MySQL) app.post('/api/orders/:id/claim', async (req: Request, res: Response) => { try { const orderId = String(req.params.id); const { isCertified, operatorRole } = req.body; if (isCertified === false || operatorRole === 'user' || operatorRole === 'PLAYER') { return res.status(403).json({ code: 40300, message: '未认证用户无法接单' }); } const order = await repo.claimOrder(orderId, req.body); res.json({ code: 20000, data: order, message: '抢单成功,服务已锁定为履约中' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 大神今日接单数据汇总 (落盘 MySQL) app.get('/api/expert/stats/:id', async (req: Request, res: Response) => { try { const stats = await repo.getExpertStats(String(req.params.id)); res.json({ code: 20000, data: stats }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 首页运营 Banner 列表 (小程序端读取启用状态) app.get('/api/banners', async (_req: Request, res: Response) => { try { const banners = await repo.getBanners(); res.json({ code: 20000, data: banners }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 管理后台: 获取全部 Banner 列表 (含停用状态) app.get('/api/admin/banners', async (_req: Request, res: Response) => { try { const banners = await repo.getAdminBanners(); res.json({ code: 20000, data: banners }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 管理后台: 新增 Banner app.post('/api/admin/banners', async (req: Request, res: Response) => { try { const { title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder, status } = req.body; if (!title || !title.trim()) { return res.status(400).json({ code: 40000, message: 'Banner 标题不能为空' }); } // 微信内容安全检测 const secCheck = contentSecurityService.checkText(title + ' ' + (subtitle || '')); if (!secCheck.pass) { return res.status(400).json({ code: secCheck.code, message: secCheck.message }); } const banner = await repo.createBanner({ title: title.trim(), subtitle: subtitle ? subtitle.trim() : '', tag, badge, badgeType, bg, image, link, sortOrder: sortOrder !== undefined ? Number(sortOrder) : 0, status: status !== undefined ? Number(status) : 1 }); res.json({ code: 20000, data: banner, message: '轮播图创建成功' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 管理后台: 修改 Banner app.put('/api/admin/banners/:id', async (req: Request, res: Response) => { try { const bannerId = req.params.id; const { title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder, status } = req.body; if (title) { const secCheck = contentSecurityService.checkText(title + ' ' + (subtitle || '')); if (!secCheck.pass) { return res.status(400).json({ code: secCheck.code, message: secCheck.message }); } } const updated = await repo.updateBanner(bannerId, { title, subtitle, tag, badge, badgeType, bg, image, link, sortOrder: sortOrder !== undefined ? Number(sortOrder) : undefined, status: status !== undefined ? Number(status) : undefined }); if (!updated) { return res.status(404).json({ code: 40400, message: '轮播图不存在或已被删除' }); } res.json({ code: 20000, data: updated, message: '轮播图修改成功' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 管理后台: 切换 Banner 上架/下线状态 app.post('/api/admin/banners/:id/status', async (req: Request, res: Response) => { try { const bannerId = req.params.id; const { status } = req.body; if (status === undefined || (status !== 0 && status !== 1)) { return res.status(400).json({ code: 40000, message: '状态参数必须为 0 或 1' }); } const updated = await repo.updateBannerStatus(bannerId, Number(status)); if (!updated) { return res.status(404).json({ code: 40400, message: '轮播图不存在' }); } res.json({ code: 20000, data: updated, message: status === 1 ? '轮播图已上架生效' : '轮播图已下线隐藏' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 管理后台: 删除 Banner app.delete('/api/admin/banners/:id', async (req: Request, res: Response) => { try { const bannerId = req.params.id; const ok = await repo.deleteBanner(bannerId); if (!ok) { return res.status(404).json({ code: 40400, message: '轮播图不存在或已被删除' }); } res.json({ code: 20000, data: null, message: '轮播图已删除' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 创建新订单 (落盘 MySQL) app.post('/api/orders', async (req: Request, res: Response) => { try { const order = await repo.createOrder(req.body); res.json({ code: 20000, data: order, message: '订单已创建并已写入MySQL' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 取消订单 app.post('/api/orders/:id/cancel', async (req: Request, res: Response) => { try { const order = await repo.updateOrderStatus(String(req.params.id), '已取消'); res.json({ code: 20000, data: order, message: '订单已取消' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 支付订单 app.post('/api/orders/:id/pay', async (req: Request, res: Response) => { try { const order = await repo.updateOrderStatus(String(req.params.id), '履约中'); res.json({ code: 20000, data: order, message: '支付成功,服务已进入履约中' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 确认完成验收 app.post('/api/orders/:id/complete', async (req: Request, res: Response) => { try { const order = await repo.updateOrderStatus(String(req.params.id), '已完成'); res.json({ code: 20000, data: order, message: '订单已确认完成' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 申请退款/售后 app.post('/api/orders/:id/refund', async (req: Request, res: Response) => { try { const order = await repo.updateOrderStatus(String(req.params.id), '争议仲裁中'); res.json({ code: 20000, data: order, message: '退款申诉已受理,进入争议仲裁流程' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); app.get('/api/arbitrations', async (_req: Request, res: Response) => { try { const arbitrations = await repo.getArbitrations(); res.json({ code: 20000, data: arbitrations }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/arbitrations/:id/handle', async (req: Request, res: Response) => { try { const arbId = String(req.params.id); const { decision, operatorRole } = req.body; // 'refund_full' | 'reject' | 'deduct_credit' const headerRole = req.headers['x-user-role']; const callerRole = operatorRole || headerRole || 'admin'; if (callerRole === 'user' || callerRole === 'expert' || callerRole === 'PLAYER' || callerRole === 'EXPERT') { return res.status(403).json({ code: 40300, message: '权限不足:买家与大神不可越权执行管理员仲裁' }); } const result = await repo.handleArbitration(arbId, decision); res.json({ code: 20000, data: result, message: `仲裁处理完毕: ${result.status}` }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 6. 财务与分销管理接口 (落盘 MySQL) app.get('/api/finance/records', async (_req: Request, res: Response) => { try { const records = await repo.getFinanceRecords(); res.json({ code: 20000, data: records }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.get('/api/finance/withdrawals', async (_req: Request, res: Response) => { try { const withdrawals = await repo.getWithdrawals(); res.json({ code: 20000, data: withdrawals }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/finance/withdrawals/:id/review', async (req: Request, res: Response) => { try { const wdId = String(req.params.id); const { action, rejectReason } = req.body; // 'approve' | 'reject' const result = await repo.reviewWithdrawal(wdId, action, rejectReason); res.json({ code: 20000, data: result, message: action === 'approve' ? '提现申请审核通过,款项已通过微信商户零钱打款' : `提现驳回: ${rejectReason || '风控核验未通过'}` }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); app.get('/api/finance/config', async (_req: Request, res: Response) => { try { const config = await repo.getFinanceConfig(); res.json({ code: 20000, data: config }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/finance/config', async (req: Request, res: Response) => { try { const config = await repo.updateFinanceConfig(req.body); res.json({ code: 20000, data: config, message: '财务抽成与分销费率配置已更新并落盘存储' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7. 场所与风控接口 (落盘 MySQL) app.get('/api/safety/places', async (_req: Request, res: Response) => { try { const places = await repo.getSafetyPlaces(); res.json({ code: 20000, data: places }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/safety/places', async (req: Request, res: Response) => { try { const place = await repo.createSafetyPlace(req.body); res.json({ code: 20000, data: place, message: '合规场所准入录入成功并持久化' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); app.get('/api/safety/audit-logs', async (_req: Request, res: Response) => { try { const logs = await repo.getSafetyAuditLogs(); res.json({ code: 20000, data: logs }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.1 全平台用户管理接口 (真实读取与更新 MySQL) app.get('/api/users', async (req: Request, res: Response) => { try { const { keyword, role, status } = req.query; const users = await repo.getUsers({ keyword: keyword ? String(keyword) : undefined, role: role ? String(role) : undefined, status: status ? String(status) : undefined, }); res.json({ code: 20000, data: users }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/users/:id/status', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const { status } = req.body; if (!status || !['正常', '已封禁'].includes(status)) { return res.status(400).json({ code: 40000, message: '无效的状态参数' }); } const updated = await repo.updateUserStatus(userId, status); res.json({ code: 20000, data: updated, message: `用户状态已更新为: ${status}` }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.2 用户360度全景综合数据看板 (涵盖活动/核销码/订单/财务流水/风控评级) app.get('/api/users/:id/detail', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const detail = await repo.getUserDetail(userId); if (!detail) { return res.status(404).json({ code: 40400, message: '用户不存在' }); } res.json({ code: 20000, data: detail }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.2.1 移动端用户资产与基本信息 app.get('/api/users/:id', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const user = await repo.getUserById(userId); if (!user) { return res.status(404).json({ code: 40400, message: '用户未找到' }); } res.json({ code: 20000, data: user }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.2.1 个人资料修改 (真实修改 users 表 nickname, avatar, phone) app.post('/api/users/:id/profile', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const { nickname, avatar, phone } = req.body; const updated = await repo.updateUserProfile(userId, { nickname, avatar, phone }); res.json({ code: 20000, data: updated, message: '个人信息修改成功' }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.2.2 钱包充值接口 (真实落盘 balance 与 finance_records) app.post('/api/users/:id/recharge', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const { amount } = req.body; const result = await repo.rechargeUserBalance(userId, Number(amount)); res.json({ code: 20000, data: result, message: `成功充值 ¥${Number(amount).toFixed(2)},余额已更新` }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.2.3 佣金提现申请接口 (真实扣减 commission 并落盘 withdrawals 与 finance_records) app.post('/api/users/:id/withdraw', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const { amount, channel } = req.body; const result = await repo.withdrawUserCommission(userId, Number(amount), channel); res.json({ code: 20000, data: result, message: '提现申请已提交至后台审核,将在1个工作日内到账' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.2.4 用户个人财务流水列表 app.get('/api/users/:id/finance-records', async (req: Request, res: Response) => { try { const userId = Number(req.params.id); const list = await repo.getUserFinanceRecords(userId); res.json({ code: 20000, data: list }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.3 友圈广场动态接口 (落盘 MySQL) app.get('/api/posts', async (req: Request, res: Response) => { try { const category = req.query.category ? String(req.query.category) : undefined; const posts = await repo.getPosts(category); res.json({ code: 20000, data: posts }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/posts', async (req: Request, res: Response) => { try { const { content } = req.body || {}; // 微信内容安全检测 const secCheck = contentSecurityService.checkText(content); if (!secCheck.pass) { return res.status(400).json({ code: secCheck.code, message: secCheck.message }); } const post = await repo.createPost(req.body); res.json({ code: 20000, data: post, message: '动态发布成功并已写入MySQL' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); app.post('/api/posts/:id/like', async (req: Request, res: Response) => { try { const result = await repo.likePost(String(req.params.id)); res.json({ code: 20000, data: result, message: '点赞成功' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.4 订单评价与评分接口 (落盘 MySQL order_reviews) app.post('/api/orders/:id/review', async (req: Request, res: Response) => { try { const orderId = String(req.params.id); const { rating, tags, content, userId, userNickname, userAvatar, expertId } = req.body; // 微信内容安全检测 const secCheck = contentSecurityService.checkText(content); if (!secCheck.pass) { return res.status(400).json({ code: secCheck.code, message: secCheck.message }); } const review = await repo.createOrderReview({ orderId, expertId: expertId ? Number(expertId) : undefined, userId: userId ? Number(userId) : undefined, userNickname, userAvatar, rating: Number(rating) || 5, tags, content }); res.json({ code: 20000, data: review, message: '评价提交成功并已落盘入库' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.5 大神历史真实评价列表接口 app.get('/api/companions/:id/reviews', async (req: Request, res: Response) => { try { const expertId = Number(req.params.id); const reviews = await repo.getOrderReviews(expertId); res.json({ code: 20000, data: reviews }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); // 7.6 友圈动态互动评论区接口 app.get('/api/posts/:id/comments', async (req: Request, res: Response) => { try { const postId = String(req.params.id); const comments = await repo.getPostComments(postId); res.json({ code: 20000, data: comments }); } catch (err: any) { res.status(500).json({ code: 50000, message: err.message }); } }); app.post('/api/posts/:id/comments', async (req: Request, res: Response) => { try { const postId = String(req.params.id); const { content, userId, author, avatar } = req.body; if (!content || !content.trim()) { return res.status(400).json({ code: 40000, message: '评论内容不能为空' }); } // 微信内容安全检测 const secCheck = contentSecurityService.checkText(content); if (!secCheck.pass) { return res.status(400).json({ code: secCheck.code, message: secCheck.message }); } const comment = await repo.createPostComment({ postId, content: content.trim(), userId: userId ? Number(userId) : undefined, author, avatar }); res.json({ code: 20000, data: comment, message: '评论发表成功' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 7.7 友圈搭子互动上车约伴申请接口 app.post('/api/posts/:id/apply', async (req: Request, res: Response) => { try { const postId = String(req.params.id); const { applicantId, applicantName, applicantAvatar } = req.body; const result = await repo.applyPostParty({ postId, applicantId: applicantId ? Number(applicantId) : undefined, applicantName, applicantAvatar }); res.json({ code: 20000, data: result, message: '搭子互动上车申请已送达' }); } catch (err: any) { res.status(400).json({ code: 40000, message: err.message }); } }); // 8. 启动服务器并初始化数据库 async function bootstrap() { try { await initDatabase(); console.log('[Peiwan Server] MySQL 数据库核心表及种子数据已校验就绪'); } catch (err) { console.error('[Peiwan Server] MySQL 初始化连接发生警告:', err); } app.listen(port, () => { console.log(`[Peiwan Server] HTTP server is running on http://localhost:${port}`); }); } bootstrap(); export default app;