Files
order_site/apps/backend/src/routes/worker.ts
T

650 lines
16 KiB
TypeScript

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,
getWorkerAnnouncement,
getWorkerOrderFeedbackSummary,
submitWorkerCancelRequest,
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'
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(requireWorkerSession)
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,
)
})
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.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((req, res) => {
res.status(404).json(buildNotFoundPayload(req))
})
export default router