diff --git a/apps/backend/src/routes/worker.ts b/apps/backend/src/routes/worker.ts index 01645f4a..e49c0ac8 100644 --- a/apps/backend/src/routes/worker.ts +++ b/apps/backend/src/routes/worker.ts @@ -1,676 +1,26 @@ import { Router } from 'express' -import { createRateLimitMiddleware, getBodyFieldRateLimitKey } from '../middleware/rate-limit.js' -import { uploadSingleFile } from './file-upload.js' -import { uploadFileAsset } from '../services/file-storage/file-storage-service.js' -import { - changeWorkerPassword, - createWorkerRechargeRequest, - createWorkerWithdrawalAccount, - saveWorkerAlipayWithdrawalQrCode, - createWorkerWithdrawRequest, - getWorkerProfile, - getWorkerSessionSummary, - getWorkerHallLeaderboard, - getWorkerMyOrderOverview, - grabWorkerHallOrder, - joinWorkerSharingOrder, - listWorkerProfileFinanceRequests, - listWorkerProfileWalletLedgers, - listWorkerHallOrders, - listWorkerMyOrders, - markWorkerOrderRead, - getWorkerMyOrderEvents, - loginWorker, - loginWorkerBySmsCode, - getWorkerSessions, - deleteWorkerSession, - manageWorkerSessions, - deleteWorkerSessionWithCredentials, - logoutWorkerSession, - registerWorker, - sendWorkerSmsCode, - sendWorkerLoginSmsCode, - sendWorkerPasswordResetSmsCode, - resetWorkerPassword, - saveWorkerOrderAcceptanceDraft, - saveWorkerOrderNote, - remindWorkerOrderAcceptance, - supplementWorkerAcceptedOrderEvidence, - submitWorkerOrderAcceptance, - getWorkerCancelRequestSummary, - getWorkerAfterSalesCaseSummary, - getWorkerAnnouncement, - getWorkerOrderFeedbackSummary, - submitWorkerCancelRequest, - submitWorkerAfterSalesCaseResponse, - submitWorkerOrderFeedback, -} from '../services/worker-platform/index.js' -import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js' -import { - extractOptionalWorkerBearerToken, - extractWorkerBearerToken, - getRequiredWorkerSession, - requireActiveWorker, - requireWorkerSession, -} from './worker/session.js' -import { openWorkerRealtimeStream } from '../services/realtime/realtime-event-service.js' +import workerAuthPublicRoutes from './worker/auth-public.js' +import workerAuthSessionRoutes from './worker/auth-session.js' +import workerProfileRoutes from './worker/profile.js' +import workerHallRoutes from './worker/hall.js' +import workerOrdersRoutes from './worker/orders.js' +import { requireWorkerSession } from './worker/session.js' +import { buildNotFoundPayload } from '../utils/http.js' const router = Router() -router.post( - '/auth/sms-code', - createRateLimitMiddleware({ - scope: 'worker:sms-code', - windowMs: 60_000, - max: 5, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler((req) => sendWorkerSmsCode(req.body || {}), { - successMessage: '验证码已发送', - errorMessage: '验证码发送失败', - scope: '[worker/auth/sms-code]', - }), -) - -router.post( - '/auth/register', - createRateLimitMiddleware({ - scope: 'worker:register', - windowMs: 60_000, - max: 5, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler((req) => registerWorker(req.body || {}), { - successMessage: '注册成功,请使用手机号登录', - errorMessage: '接单账号注册失败', - scope: '[worker/auth/register]', - }), -) - -router.post( - '/auth/password-reset/sms-code', - createRateLimitMiddleware({ - scope: 'worker:password-reset-sms-code', - windowMs: 60_000, - max: 5, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler((req) => sendWorkerPasswordResetSmsCode(req.body || {}), { - successMessage: '验证码已发送', - errorMessage: '验证码发送失败', - scope: '[worker/auth/password-reset/sms-code]', - }), -) - -router.post( - '/auth/password-reset', - createRateLimitMiddleware({ - scope: 'worker:password-reset', - windowMs: 60_000, - max: 10, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler((req) => resetWorkerPassword(req.body || {}), { - successMessage: '密码已重置,请重新登录', - errorMessage: '密码重置失败', - scope: '[worker/auth/password-reset]', - }), -) - -router.post( - '/auth/login', - createRateLimitMiddleware({ - scope: 'worker:login', - windowMs: 60_000, - max: 10, - key: getBodyFieldRateLimitKey('username'), - }), - createRouteHandler( - (req) => - loginWorker(req.body?.username, req.body?.password, { - deviceId: req.body?.deviceId, - deviceType: req.body?.deviceType, - deviceName: req.body?.deviceName, - userAgent: req.headers['user-agent'], - ipAddress: req.ip, - }), - { - successMessage: '登录成功', - errorMessage: '接单端登录失败', - scope: '[worker/auth/login]', - }, - ), -) - -router.post( - '/auth/login/sms-code', - createRateLimitMiddleware({ - scope: 'worker:login-sms-code', - windowMs: 60_000, - max: 5, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler((req) => sendWorkerLoginSmsCode(req.body || {}), { - successMessage: '验证码已发送', - errorMessage: '验证码发送失败', - scope: '[worker/auth/login/sms-code]', - }), -) - -router.post( - '/auth/login/sms', - createRateLimitMiddleware({ - scope: 'worker:login-sms', - windowMs: 60_000, - max: 10, - key: getBodyFieldRateLimitKey('phone'), - }), - createRouteHandler( - (req) => - loginWorkerBySmsCode(req.body?.phone, req.body?.code, { - deviceId: req.body?.deviceId, - deviceType: req.body?.deviceType, - deviceName: req.body?.deviceName, - userAgent: req.headers['user-agent'], - ipAddress: req.ip, - }), - { - successMessage: '登录成功', - errorMessage: '接单端验证码登录失败', - scope: '[worker/auth/login/sms]', - }, - ), -) - -router.post( - '/auth/devices/manage/list', - createRateLimitMiddleware({ - scope: 'worker:devices-manage', - windowMs: 60_000, - max: 10, - key: getBodyFieldRateLimitKey('username'), - }), - createRouteHandler((req) => manageWorkerSessions(req.body?.username, req.body?.password), { - successMessage: 'ok', - errorMessage: '读取登录设备失败', - scope: '[worker/auth/devices/manage/list]', - }), -) - -router.post( - '/auth/devices/manage/delete', - createRateLimitMiddleware({ - scope: 'worker:devices-manage-delete', - windowMs: 60_000, - max: 10, - key: getBodyFieldRateLimitKey('username'), - }), - createRouteHandler( - (req) => - deleteWorkerSessionWithCredentials( - req.body?.username, - req.body?.password, - req.body?.sessionId, - ), - { - successMessage: '登录设备已删除', - errorMessage: '删除登录设备失败', - scope: '[worker/auth/devices/manage/delete]', - }, - ), -) - -router.get( - '/auth/session', - createRouteHandler((req) => getWorkerSessionSummary(extractWorkerBearerToken(req)), { - successMessage: 'ok', - errorMessage: '读取接单登录态失败', - scope: '[worker/auth/session]', - }), -) - -router.post( - '/auth/logout', - createRouteHandler((req) => logoutWorkerSession(extractOptionalWorkerBearerToken(req)), { - successMessage: '已退出登录', - errorMessage: '退出失败', - scope: '[worker/auth/logout]', - }), -) +router.use(workerAuthPublicRoutes) router.use(requireWorkerSession) -router.get( - '/auth/devices', - createRouteHandler((req) => getWorkerSessions(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取登录设备失败', - scope: '[worker/auth/devices]', - }), -) +router.use(workerAuthSessionRoutes) -router.delete( - '/auth/devices/:sessionId', - createRouteHandler( - (req) => deleteWorkerSession(getRequiredWorkerSession(req), req.params.sessionId), - { - successMessage: '登录设备已删除', - errorMessage: '删除登录设备失败', - scope: '[worker/auth/devices/delete]', - }, - ), -) +router.use(workerProfileRoutes) -router.get('/realtime', (req, res) => { - openWorkerRealtimeStream( - res, - getRequiredWorkerSession(req).workerId, - Number(req.get('Last-Event-ID') || req.query.lastEventId || 0) || 0, - ) -}) +router.use(workerHallRoutes) -router.get( - '/profile', - createRouteHandler((req) => getWorkerProfile(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取打手信息失败', - scope: '[worker/profile]', - }), -) - -router.get( - '/profile/wallet-ledgers', - createRouteHandler( - (req) => listWorkerProfileWalletLedgers(req.query, getRequiredWorkerSession(req)), - { - successMessage: 'ok', - errorMessage: '读取钱包流水失败', - scope: '[worker/profile/wallet-ledgers]', - }, - ), -) - -router.get( - '/profile/finance-requests', - createRouteHandler( - (req) => listWorkerProfileFinanceRequests(req.query, getRequiredWorkerSession(req)), - { - successMessage: 'ok', - errorMessage: '读取资金申请记录失败', - scope: '[worker/profile/finance-requests]', - }, - ), -) - -router.post( - '/profile/recharge-requests', - requireActiveWorker, - createRouteHandler( - (req) => createWorkerRechargeRequest(req.body || {}, getRequiredWorkerSession(req)), - { - successMessage: '充值申请已提交', - errorMessage: '提交充值申请失败', - scope: '[worker/profile/recharge-requests]', - }, - ), -) - -router.post( - '/profile/withdrawal-account', - requireActiveWorker, - createRouteHandler( - (req) => createWorkerWithdrawalAccount(req.body || {}, getRequiredWorkerSession(req)), - { - successMessage: '提现信息已添加', - errorMessage: '添加提现信息失败', - scope: '[worker/profile/withdrawal-account]', - }, - ), -) - -router.put( - '/profile/withdrawal-account/alipay-qr-code', - requireActiveWorker, - createRouteHandler( - (req) => saveWorkerAlipayWithdrawalQrCode(req.body || {}, getRequiredWorkerSession(req)), - { - successMessage: '支付宝收款二维码已保存', - errorMessage: '保存支付宝收款二维码失败', - scope: '[worker/profile/withdrawal-account/alipay-qr-code]', - }, - ), -) - -router.post( - '/profile/withdraw-requests', - requireActiveWorker, - createRouteHandler( - (req) => createWorkerWithdrawRequest(req.body || {}, getRequiredWorkerSession(req)), - { - successMessage: '提现申请已提交', - errorMessage: '提交提现申请失败', - scope: '[worker/profile/withdraw-requests]', - }, - ), -) - -router.post( - '/profile/change-password', - createRouteHandler((req) => changeWorkerPassword(req.body || {}, getRequiredWorkerSession(req)), { - successMessage: '密码已修改', - errorMessage: '修改密码失败', - scope: '[worker/profile/change-password]', - }), -) - -router.post( - '/files/upload', - requireActiveWorker, - uploadSingleFile, - createRouteHandler( - (req) => { - const session = getRequiredWorkerSession(req) - return uploadFileAsset({ - file: req.file, - scene: req.body?.scene || 'worker-acceptance', - uploaderType: 'worker', - uploaderId: String(session.workerId), - }) - }, - { - successMessage: '文件已上传', - errorMessage: '文件上传失败', - scope: '[worker/files/upload]', - }, - ), -) - -router.get( - '/hall/orders', - requireActiveWorker, - createRouteHandler((req) => listWorkerHallOrders(req.query, getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取抢单大厅失败', - scope: '[worker/hall/orders]', - }), -) - -router.get( - '/hall/leaderboard', - requireActiveWorker, - createRouteHandler( - (req) => getWorkerHallLeaderboard(getRequiredWorkerSession(req), req.query?.period), - { - successMessage: 'ok', - errorMessage: '读取接单排行榜失败', - scope: '[worker/hall/leaderboard]', - }, - ), -) - -router.post( - '/hall/orders/:workOrderId/grab', - requireActiveWorker, - createRouteHandler( - (req) => - grabWorkerHallOrder(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), - { - successMessage: '抢单成功', - errorMessage: '抢单失败', - scope: '[worker/hall/orders/:workOrderId/grab]', - }, - ), -) - -router.post( - '/hall/orders/:workOrderId/join-sharing', - requireActiveWorker, - createRouteHandler( - (req) => - joinWorkerSharingOrder( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '拼单成功', - errorMessage: '拼单失败', - scope: '[worker/hall/orders/:workOrderId/join-sharing]', - }, - ), -) - -router.get( - '/orders', - requireActiveWorker, - createRouteHandler((req) => listWorkerMyOrders(req.query, getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取我的订单失败', - scope: '[worker/orders]', - }), -) - -router.get( - '/orders/overview', - requireActiveWorker, - createRouteHandler((req) => getWorkerMyOrderOverview(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取我的订单概览失败', - scope: '[worker/orders/overview]', - }), -) - -router.get( - '/orders/cancel-requests', - requireActiveWorker, - createRouteHandler((req) => getWorkerCancelRequestSummary(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取取消申请失败', - scope: '[worker/orders/cancel-requests]', - }), -) - -router.get( - '/announcement', - requireActiveWorker, - createRouteHandler((req) => getWorkerAnnouncement(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取公告失败', - scope: '[worker/announcement]', - }), -) - -router.get( - '/orders/feedbacks', - requireActiveWorker, - createRouteHandler((req) => getWorkerOrderFeedbackSummary(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取问题反馈失败', - scope: '[worker/orders/feedbacks]', - }), -) - -router.get( - '/orders/after-sales-cases', - requireActiveWorker, - createRouteHandler((req) => getWorkerAfterSalesCaseSummary(getRequiredWorkerSession(req)), { - successMessage: 'ok', - errorMessage: '读取售后问题单失败', - scope: '[worker/orders/after-sales-cases]', - }), -) - -router.post( - '/orders/after-sales-cases/:caseId/response', - requireActiveWorker, - createRouteHandler( - (req) => - submitWorkerAfterSalesCaseResponse( - String(req.params.caseId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '售后说明已提交,等待客服处理', - errorMessage: '提交售后说明失败', - scope: '[worker/orders/after-sales-cases/:caseId/response]', - }, - ), -) - -router.post( - '/orders/:workOrderId/feedbacks', - requireActiveWorker, - createRouteHandler( - (req) => - submitWorkerOrderFeedback( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '问题反馈已提交,等待客服处理', - errorMessage: '提交问题反馈失败', - scope: '[worker/orders/:workOrderId/feedbacks]', - }, - ), -) - -router.post( - '/orders/:workOrderId/cancel-requests', - requireActiveWorker, - createRouteHandler( - (req) => - submitWorkerCancelRequest( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '取消申请已提交,等待客服审核', - errorMessage: '提交取消申请失败', - scope: '[worker/orders/:workOrderId/cancel-requests]', - }, - ), -) - -router.get( - '/orders/:workOrderId/events', - requireActiveWorker, - createRouteHandler( - (req) => - getWorkerMyOrderEvents(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), - { - successMessage: 'ok', - errorMessage: '读取订单流转记录失败', - scope: '[worker/orders/:workOrderId/events]', - }, - ), -) - -router.post( - '/orders/:workOrderId/read', - requireActiveWorker, - createRouteHandler( - (req) => - markWorkerOrderRead(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), - { - successMessage: '订单已标记为已读', - errorMessage: '标记订单已读失败', - scope: '[worker/orders/:workOrderId/read]', - }, - ), -) - -router.post( - '/orders/:workOrderId/note', - requireActiveWorker, - createRouteHandler( - (req) => - saveWorkerOrderNote( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '备注已保存', - errorMessage: '保存备注失败', - scope: '[worker/orders/:workOrderId/note]', - }, - ), -) - -router.post( - '/orders/:workOrderId/submit-acceptance', - requireActiveWorker, - createRouteHandler( - (req) => - submitWorkerOrderAcceptance( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '验收资料已提交', - errorMessage: '提交验收资料失败', - scope: '[worker/orders/:workOrderId/submit-acceptance]', - }, - ), -) - -router.post( - '/orders/:workOrderId/remind-acceptance', - requireActiveWorker, - createRouteHandler( - (req) => - remindWorkerOrderAcceptance( - String(req.params.workOrderId || ''), - getRequiredWorkerSession(req), - ), - { - successMessage: '已催促管理员验收', - errorMessage: '催验收失败', - scope: '[worker/orders/:workOrderId/remind-acceptance]', - }, - ), -) - -router.post( - '/orders/:workOrderId/acceptance-draft', - requireActiveWorker, - createRouteHandler( - (req) => - saveWorkerOrderAcceptanceDraft( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '验收图片已保存', - errorMessage: '保存验收图片失败', - scope: '[worker/orders/:workOrderId/acceptance-draft]', - }, - ), -) - -router.post( - '/orders/:workOrderId/acceptance-evidence', - requireActiveWorker, - createRouteHandler( - (req) => - supplementWorkerAcceptedOrderEvidence( - String(req.params.workOrderId || ''), - req.body || {}, - getRequiredWorkerSession(req), - ), - { - successMessage: '验收图片已补充', - errorMessage: '补充验收图片失败', - scope: '[worker/orders/:workOrderId/acceptance-evidence]', - }, - ), -) +router.use(workerOrdersRoutes) router.use((req, res) => { res.status(404).json(buildNotFoundPayload(req)) diff --git a/apps/backend/src/routes/worker/auth-public.ts b/apps/backend/src/routes/worker/auth-public.ts new file mode 100644 index 00000000..53144d80 --- /dev/null +++ b/apps/backend/src/routes/worker/auth-public.ts @@ -0,0 +1,206 @@ +import { Router } from 'express' + +import { createRateLimitMiddleware, getBodyFieldRateLimitKey } from '../../middleware/rate-limit.js' +import { + deleteWorkerSessionWithCredentials, + getWorkerSessionSummary, + loginWorker, + loginWorkerBySmsCode, + logoutWorkerSession, + manageWorkerSessions, + registerWorker, + resetWorkerPassword, + sendWorkerLoginSmsCode, + sendWorkerPasswordResetSmsCode, + sendWorkerSmsCode, +} from '../../services/worker-platform/index.js' +import { + extractOptionalWorkerBearerToken, + extractWorkerBearerToken, +} from './session.js' +import { createRouteHandler } from '../../utils/http.js' + +const router = Router() + +router.post( + '/auth/sms-code', + createRateLimitMiddleware({ + scope: 'worker:sms-code', + windowMs: 60_000, + max: 5, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler((req) => sendWorkerSmsCode(req.body || {}), { + successMessage: '验证码已发送', + errorMessage: '验证码发送失败', + scope: '[worker/auth/sms-code]', + }), +) + +router.post( + '/auth/register', + createRateLimitMiddleware({ + scope: 'worker:register', + windowMs: 60_000, + max: 5, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler((req) => registerWorker(req.body || {}), { + successMessage: '注册成功,请使用手机号登录', + errorMessage: '接单账号注册失败', + scope: '[worker/auth/register]', + }), +) + +router.post( + '/auth/password-reset/sms-code', + createRateLimitMiddleware({ + scope: 'worker:password-reset-sms-code', + windowMs: 60_000, + max: 5, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler((req) => sendWorkerPasswordResetSmsCode(req.body || {}), { + successMessage: '验证码已发送', + errorMessage: '验证码发送失败', + scope: '[worker/auth/password-reset/sms-code]', + }), +) + +router.post( + '/auth/password-reset', + createRateLimitMiddleware({ + scope: 'worker:password-reset', + windowMs: 60_000, + max: 10, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler((req) => resetWorkerPassword(req.body || {}), { + successMessage: '密码已重置,请重新登录', + errorMessage: '密码重置失败', + scope: '[worker/auth/password-reset]', + }), +) + +router.post( + '/auth/login', + createRateLimitMiddleware({ + scope: 'worker:login', + windowMs: 60_000, + max: 10, + key: getBodyFieldRateLimitKey('username'), + }), + createRouteHandler( + (req) => + loginWorker(req.body?.username, req.body?.password, { + deviceId: req.body?.deviceId, + deviceType: req.body?.deviceType, + deviceName: req.body?.deviceName, + userAgent: req.headers['user-agent'], + ipAddress: req.ip, + }), + { + successMessage: '登录成功', + errorMessage: '接单端登录失败', + scope: '[worker/auth/login]', + }, + ), +) + +router.post( + '/auth/login/sms-code', + createRateLimitMiddleware({ + scope: 'worker:login-sms-code', + windowMs: 60_000, + max: 5, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler((req) => sendWorkerLoginSmsCode(req.body || {}), { + successMessage: '验证码已发送', + errorMessage: '验证码发送失败', + scope: '[worker/auth/login/sms-code]', + }), +) + +router.post( + '/auth/login/sms', + createRateLimitMiddleware({ + scope: 'worker:login-sms', + windowMs: 60_000, + max: 10, + key: getBodyFieldRateLimitKey('phone'), + }), + createRouteHandler( + (req) => + loginWorkerBySmsCode(req.body?.phone, req.body?.code, { + deviceId: req.body?.deviceId, + deviceType: req.body?.deviceType, + deviceName: req.body?.deviceName, + userAgent: req.headers['user-agent'], + ipAddress: req.ip, + }), + { + successMessage: '登录成功', + errorMessage: '接单端验证码登录失败', + scope: '[worker/auth/login/sms]', + }, + ), +) + +router.post( + '/auth/devices/manage/list', + createRateLimitMiddleware({ + scope: 'worker:devices-manage', + windowMs: 60_000, + max: 10, + key: getBodyFieldRateLimitKey('username'), + }), + createRouteHandler((req) => manageWorkerSessions(req.body?.username, req.body?.password), { + successMessage: 'ok', + errorMessage: '读取登录设备失败', + scope: '[worker/auth/devices/manage/list]', + }), +) + +router.post( + '/auth/devices/manage/delete', + createRateLimitMiddleware({ + scope: 'worker:devices-manage-delete', + windowMs: 60_000, + max: 10, + key: getBodyFieldRateLimitKey('username'), + }), + createRouteHandler( + (req) => + deleteWorkerSessionWithCredentials( + req.body?.username, + req.body?.password, + req.body?.sessionId, + ), + { + successMessage: '登录设备已删除', + errorMessage: '删除登录设备失败', + scope: '[worker/auth/devices/manage/delete]', + }, + ), +) + +router.get( + '/auth/session', + createRouteHandler((req) => getWorkerSessionSummary(extractWorkerBearerToken(req)), { + successMessage: 'ok', + errorMessage: '读取接单登录态失败', + scope: '[worker/auth/session]', + }), +) + +router.post( + '/auth/logout', + createRouteHandler((req) => logoutWorkerSession(extractOptionalWorkerBearerToken(req)), { + successMessage: '已退出登录', + errorMessage: '退出失败', + scope: '[worker/auth/logout]', + }), +) + +export default router diff --git a/apps/backend/src/routes/worker/auth-session.ts b/apps/backend/src/routes/worker/auth-session.ts new file mode 100644 index 00000000..4c37bec7 --- /dev/null +++ b/apps/backend/src/routes/worker/auth-session.ts @@ -0,0 +1,39 @@ +import { Router } from 'express' + +import { deleteWorkerSession, getWorkerSessions } from '../../services/worker-platform/index.js' +import { getRequiredWorkerSession } from './session.js' +import { createRouteHandler } from '../../utils/http.js' +import { openWorkerRealtimeStream } from '../../services/realtime/realtime-event-service.js' + +const router = Router() + +router.get( + '/auth/devices', + createRouteHandler((req) => getWorkerSessions(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取登录设备失败', + scope: '[worker/auth/devices]', + }), +) + +router.delete( + '/auth/devices/:sessionId', + createRouteHandler( + (req) => deleteWorkerSession(getRequiredWorkerSession(req), req.params.sessionId), + { + successMessage: '登录设备已删除', + errorMessage: '删除登录设备失败', + scope: '[worker/auth/devices/delete]', + }, + ), +) + +router.get('/realtime', (req, res) => { + openWorkerRealtimeStream( + res, + getRequiredWorkerSession(req).workerId, + Number(req.get('Last-Event-ID') || req.query.lastEventId || 0) || 0, + ) +}) + +export default router diff --git a/apps/backend/src/routes/worker/hall.ts b/apps/backend/src/routes/worker/hall.ts new file mode 100644 index 00000000..c8250695 --- /dev/null +++ b/apps/backend/src/routes/worker/hall.ts @@ -0,0 +1,72 @@ +import { Router } from 'express' + +import { + getWorkerHallLeaderboard, + grabWorkerHallOrder, + joinWorkerSharingOrder, + listWorkerHallOrders, +} from '../../services/worker-platform/index.js' +import { + getRequiredWorkerSession, + requireActiveWorker, +} from './session.js' +import { createRouteHandler } from '../../utils/http.js' + +const router = Router() + +router.get( + '/hall/orders', + requireActiveWorker, + createRouteHandler((req) => listWorkerHallOrders(req.query, getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取抢单大厅失败', + scope: '[worker/hall/orders]', + }), +) + +router.get( + '/hall/leaderboard', + requireActiveWorker, + createRouteHandler( + (req) => getWorkerHallLeaderboard(getRequiredWorkerSession(req), req.query?.period), + { + successMessage: 'ok', + errorMessage: '读取接单排行榜失败', + scope: '[worker/hall/leaderboard]', + }, + ), +) + +router.post( + '/hall/orders/:workOrderId/grab', + requireActiveWorker, + createRouteHandler( + (req) => + grabWorkerHallOrder(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), + { + successMessage: '抢单成功', + errorMessage: '抢单失败', + scope: '[worker/hall/orders/:workOrderId/grab]', + }, + ), +) + +router.post( + '/hall/orders/:workOrderId/join-sharing', + requireActiveWorker, + createRouteHandler( + (req) => + joinWorkerSharingOrder( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '拼单成功', + errorMessage: '拼单失败', + scope: '[worker/hall/orders/:workOrderId/join-sharing]', + }, + ), +) + +export default router diff --git a/apps/backend/src/routes/worker/orders.ts b/apps/backend/src/routes/worker/orders.ts new file mode 100644 index 00000000..bf198b7b --- /dev/null +++ b/apps/backend/src/routes/worker/orders.ts @@ -0,0 +1,260 @@ +import { Router } from 'express' + +import { + getWorkerAfterSalesCaseSummary, + getWorkerAnnouncement, + getWorkerCancelRequestSummary, + getWorkerMyOrderEvents, + getWorkerMyOrderOverview, + getWorkerOrderFeedbackSummary, + listWorkerMyOrders, + markWorkerOrderRead, + remindWorkerOrderAcceptance, + saveWorkerOrderAcceptanceDraft, + saveWorkerOrderNote, + submitWorkerAfterSalesCaseResponse, + submitWorkerCancelRequest, + submitWorkerOrderAcceptance, + submitWorkerOrderFeedback, + supplementWorkerAcceptedOrderEvidence, +} from '../../services/worker-platform/index.js' +import { + getRequiredWorkerSession, + requireActiveWorker, +} from './session.js' +import { createRouteHandler } from '../../utils/http.js' + +const router = Router() + +router.get( + '/orders', + requireActiveWorker, + createRouteHandler((req) => listWorkerMyOrders(req.query, getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取我的订单失败', + scope: '[worker/orders]', + }), +) + +router.get( + '/orders/overview', + requireActiveWorker, + createRouteHandler((req) => getWorkerMyOrderOverview(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取我的订单概览失败', + scope: '[worker/orders/overview]', + }), +) + +router.get( + '/orders/cancel-requests', + requireActiveWorker, + createRouteHandler((req) => getWorkerCancelRequestSummary(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取取消申请失败', + scope: '[worker/orders/cancel-requests]', + }), +) + +router.get( + '/announcement', + requireActiveWorker, + createRouteHandler((req) => getWorkerAnnouncement(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取公告失败', + scope: '[worker/announcement]', + }), +) + +router.get( + '/orders/feedbacks', + requireActiveWorker, + createRouteHandler((req) => getWorkerOrderFeedbackSummary(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取问题反馈失败', + scope: '[worker/orders/feedbacks]', + }), +) + +router.get( + '/orders/after-sales-cases', + requireActiveWorker, + createRouteHandler((req) => getWorkerAfterSalesCaseSummary(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取售后问题单失败', + scope: '[worker/orders/after-sales-cases]', + }), +) + +router.post( + '/orders/after-sales-cases/:caseId/response', + requireActiveWorker, + createRouteHandler( + (req) => + submitWorkerAfterSalesCaseResponse( + String(req.params.caseId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '售后说明已提交,等待客服处理', + errorMessage: '提交售后说明失败', + scope: '[worker/orders/after-sales-cases/:caseId/response]', + }, + ), +) + +router.post( + '/orders/:workOrderId/feedbacks', + requireActiveWorker, + createRouteHandler( + (req) => + submitWorkerOrderFeedback( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '问题反馈已提交,等待客服处理', + errorMessage: '提交问题反馈失败', + scope: '[worker/orders/:workOrderId/feedbacks]', + }, + ), +) + +router.post( + '/orders/:workOrderId/cancel-requests', + requireActiveWorker, + createRouteHandler( + (req) => + submitWorkerCancelRequest( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '取消申请已提交,等待客服审核', + errorMessage: '提交取消申请失败', + scope: '[worker/orders/:workOrderId/cancel-requests]', + }, + ), +) + +router.get( + '/orders/:workOrderId/events', + requireActiveWorker, + createRouteHandler( + (req) => + getWorkerMyOrderEvents(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), + { + successMessage: 'ok', + errorMessage: '读取订单流转记录失败', + scope: '[worker/orders/:workOrderId/events]', + }, + ), +) + +router.post( + '/orders/:workOrderId/read', + requireActiveWorker, + createRouteHandler( + (req) => + markWorkerOrderRead(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)), + { + successMessage: '订单已标记为已读', + errorMessage: '标记订单已读失败', + scope: '[worker/orders/:workOrderId/read]', + }, + ), +) + +router.post( + '/orders/:workOrderId/note', + requireActiveWorker, + createRouteHandler( + (req) => + saveWorkerOrderNote( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '备注已保存', + errorMessage: '保存备注失败', + scope: '[worker/orders/:workOrderId/note]', + }, + ), +) + +router.post( + '/orders/:workOrderId/submit-acceptance', + requireActiveWorker, + createRouteHandler( + (req) => + submitWorkerOrderAcceptance( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '验收资料已提交', + errorMessage: '提交验收资料失败', + scope: '[worker/orders/:workOrderId/submit-acceptance]', + }, + ), +) + +router.post( + '/orders/:workOrderId/remind-acceptance', + requireActiveWorker, + createRouteHandler( + (req) => + remindWorkerOrderAcceptance( + String(req.params.workOrderId || ''), + getRequiredWorkerSession(req), + ), + { + successMessage: '已催促管理员验收', + errorMessage: '催验收失败', + scope: '[worker/orders/:workOrderId/remind-acceptance]', + }, + ), +) + +router.post( + '/orders/:workOrderId/acceptance-draft', + requireActiveWorker, + createRouteHandler( + (req) => + saveWorkerOrderAcceptanceDraft( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '验收图片已保存', + errorMessage: '保存验收图片失败', + scope: '[worker/orders/:workOrderId/acceptance-draft]', + }, + ), +) + +router.post( + '/orders/:workOrderId/acceptance-evidence', + requireActiveWorker, + createRouteHandler( + (req) => + supplementWorkerAcceptedOrderEvidence( + String(req.params.workOrderId || ''), + req.body || {}, + getRequiredWorkerSession(req), + ), + { + successMessage: '验收图片已补充', + errorMessage: '补充验收图片失败', + scope: '[worker/orders/:workOrderId/acceptance-evidence]', + }, + ), +) + +export default router diff --git a/apps/backend/src/routes/worker/profile.ts b/apps/backend/src/routes/worker/profile.ts new file mode 100644 index 00000000..644f4987 --- /dev/null +++ b/apps/backend/src/routes/worker/profile.ts @@ -0,0 +1,139 @@ +import { Router } from 'express' + +import { uploadSingleFile } from '../file-upload.js' +import { uploadFileAsset } from '../../services/file-storage/file-storage-service.js' +import { + changeWorkerPassword, + createWorkerRechargeRequest, + createWorkerWithdrawRequest, + createWorkerWithdrawalAccount, + getWorkerProfile, + listWorkerProfileFinanceRequests, + listWorkerProfileWalletLedgers, + saveWorkerAlipayWithdrawalQrCode, +} from '../../services/worker-platform/index.js' +import { + getRequiredWorkerSession, + requireActiveWorker, +} from './session.js' +import { createRouteHandler } from '../../utils/http.js' + +const router = Router() + +router.get( + '/profile', + createRouteHandler((req) => getWorkerProfile(getRequiredWorkerSession(req)), { + successMessage: 'ok', + errorMessage: '读取打手信息失败', + scope: '[worker/profile]', + }), +) + +router.get( + '/profile/wallet-ledgers', + createRouteHandler( + (req) => listWorkerProfileWalletLedgers(req.query, getRequiredWorkerSession(req)), + { + successMessage: 'ok', + errorMessage: '读取钱包流水失败', + scope: '[worker/profile/wallet-ledgers]', + }, + ), +) + +router.get( + '/profile/finance-requests', + createRouteHandler( + (req) => listWorkerProfileFinanceRequests(req.query, getRequiredWorkerSession(req)), + { + successMessage: 'ok', + errorMessage: '读取资金申请记录失败', + scope: '[worker/profile/finance-requests]', + }, + ), +) + +router.post( + '/profile/recharge-requests', + requireActiveWorker, + createRouteHandler( + (req) => createWorkerRechargeRequest(req.body || {}, getRequiredWorkerSession(req)), + { + successMessage: '充值申请已提交', + errorMessage: '提交充值申请失败', + scope: '[worker/profile/recharge-requests]', + }, + ), +) + +router.post( + '/profile/withdrawal-account', + requireActiveWorker, + createRouteHandler( + (req) => createWorkerWithdrawalAccount(req.body || {}, getRequiredWorkerSession(req)), + { + successMessage: '提现信息已添加', + errorMessage: '添加提现信息失败', + scope: '[worker/profile/withdrawal-account]', + }, + ), +) + +router.put( + '/profile/withdrawal-account/alipay-qr-code', + requireActiveWorker, + createRouteHandler( + (req) => saveWorkerAlipayWithdrawalQrCode(req.body || {}, getRequiredWorkerSession(req)), + { + successMessage: '支付宝收款二维码已保存', + errorMessage: '保存支付宝收款二维码失败', + scope: '[worker/profile/withdrawal-account/alipay-qr-code]', + }, + ), +) + +router.post( + '/profile/withdraw-requests', + requireActiveWorker, + createRouteHandler( + (req) => createWorkerWithdrawRequest(req.body || {}, getRequiredWorkerSession(req)), + { + successMessage: '提现申请已提交', + errorMessage: '提交提现申请失败', + scope: '[worker/profile/withdraw-requests]', + }, + ), +) + +router.post( + '/profile/change-password', + createRouteHandler((req) => changeWorkerPassword(req.body || {}, getRequiredWorkerSession(req)), { + successMessage: '密码已修改', + errorMessage: '修改密码失败', + scope: '[worker/profile/change-password]', + }), +) + +router.post( + '/files/upload', + requireActiveWorker, + uploadSingleFile, + createRouteHandler( + (req) => { + const session = getRequiredWorkerSession(req) + return uploadFileAsset({ + file: req.file, + scene: req.body?.scene || 'worker-acceptance', + uploaderType: 'worker', + uploaderId: String(session.workerId), + }) + }, + { + successMessage: '文件已上传', + errorMessage: '文件上传失败', + scope: '[worker/files/upload]', + }, + ), +) + +export default router