feat(worker-platform): 实现打手寄卖白名单权限控制与存量发布打手平滑过渡
1. 新增数据库迁移 082_worker_consignment_whitelist.sql,worker_users 表增加 can_consign 与 consignment_activated_at 字段,存量在 consignment_submissions 提报过的打手默认自动开启; 2. 打手端提报接口 publishWorkerConsignment 增加白名单权限服务端强校验拦截(403 consignment_permission_required); 3. 管理后台打手管理表格增加「寄卖权限」实时 Switch 开关列与打手详情抽屉权限展示,支持客服即时开通或关闭打手寄卖权限; 4. 打手端我要寄卖页面在未开通权限时展示专属白名单引导卡片,一键呼起联系客服开通寄卖弹窗; 5. 补充寄卖权限拦截与路由单测,前端59项单测与后端507项单测全部通过。
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
import { mapWorkerUser } from './worker-finance-mappers.js'
|
||||
import type { WorkerUserRow } from '../../repositories/worker-platform/types.js'
|
||||
|
||||
test('mapWorkerUser 准确回显打手寄卖白名单权限与激活时间', () => {
|
||||
const fakeWorker = {
|
||||
id: 101,
|
||||
username: 'worker_a',
|
||||
display_name: '打手A',
|
||||
phone: '13800138000',
|
||||
status: 'active',
|
||||
worker_type: 'external',
|
||||
created_at: '2026-09-01T00:00:00.000Z',
|
||||
updated_at: '2026-09-01T00:00:00.000Z',
|
||||
can_consign: true,
|
||||
consignment_activated_at: '2026-09-20T12:00:00.000Z',
|
||||
} as unknown as WorkerUserRow
|
||||
|
||||
const mapped = mapWorkerUser(fakeWorker)
|
||||
assert.equal(mapped.canConsign, true)
|
||||
assert.equal(mapped.consignmentActivatedAt, '2026-09-20T12:00:00.000Z')
|
||||
|
||||
const unactivatedWorker = {
|
||||
...fakeWorker,
|
||||
can_consign: false,
|
||||
consignment_activated_at: null,
|
||||
} as unknown as WorkerUserRow
|
||||
|
||||
const mappedUnactivated = mapWorkerUser(unactivatedWorker)
|
||||
assert.equal(mappedUnactivated.canConsign, false)
|
||||
assert.equal(mappedUnactivated.consignmentActivatedAt, null)
|
||||
})
|
||||
|
||||
test('管理员打手管理路由注册了寄卖白名单权限更新接口', async () => {
|
||||
const routeSource = await readFile(
|
||||
new URL('../../routes/admin/worker-platform/workers.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
routeSource.includes("'/worker-platform/workers/:workerId/consignment-permission'"),
|
||||
'必须注册 consignment-permission 路由',
|
||||
)
|
||||
assert.ok(
|
||||
routeSource.includes('updateAdminWorkerConsignmentPermission'),
|
||||
'路由必须调用 updateAdminWorkerConsignmentPermission',
|
||||
)
|
||||
})
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
updateWorkerPassword,
|
||||
updateWorkerUser,
|
||||
updateWorkerWithdrawalBlacklist,
|
||||
updateWorkerConsignmentPermission,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
@@ -178,3 +179,18 @@ export async function updateAdminWorkerWithdrawalBlacklist(
|
||||
})
|
||||
return { worker: mapWorkerUser(updated || worker) }
|
||||
}
|
||||
|
||||
export async function updateAdminWorkerConsignmentPermission(
|
||||
workerId: number | string,
|
||||
payload: JsonObject = {},
|
||||
) {
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const canConsign = normalizeBoolean(payload.canConsign ?? payload.enabled, false)
|
||||
const now = nowIso()
|
||||
const updated = await updateWorkerConsignmentPermission({
|
||||
workerId: worker.id,
|
||||
canConsign,
|
||||
now,
|
||||
})
|
||||
return { worker: mapWorkerUser(updated || worker) }
|
||||
}
|
||||
|
||||
@@ -83,3 +83,15 @@ test('寄卖提报硬校验:璀璨数不能超过钻石等级(1钻最多1璀
|
||||
'超限必须返回专门的错误码',
|
||||
)
|
||||
})
|
||||
|
||||
test('未开通寄卖白名单权限的打手提报寄卖时被强校验拦截', async () => {
|
||||
const serviceSource = await readServiceSource()
|
||||
assert.ok(
|
||||
serviceSource.includes('!worker.can_consign'),
|
||||
'服务端必须拦截未开通寄卖权限的打手',
|
||||
)
|
||||
assert.ok(
|
||||
serviceSource.includes("errorCode: 'consignment_permission_required'"),
|
||||
'未开通必须返回专门的 consignment_permission_required 错误码',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -144,6 +144,12 @@ export async function publishWorkerConsignment(workerId: number, payload: JsonOb
|
||||
if (!worker) {
|
||||
throw createHttpError('打手账户不存在', { statusCode: 404, errorCode: 'worker_not_found' })
|
||||
}
|
||||
if (!worker.can_consign) {
|
||||
throw createHttpError('您暂未开通寄卖权限,请联系客服开通', {
|
||||
statusCode: 403,
|
||||
errorCode: 'consignment_permission_required',
|
||||
})
|
||||
}
|
||||
|
||||
const system = String(payload.system || '').trim()
|
||||
const serverZone = String(payload.serverZone || '').trim()
|
||||
|
||||
@@ -110,6 +110,8 @@ export function mapWorkerUser(worker: WorkerUserRow) {
|
||||
renameUnlockedAt: worker.last_renamed_at
|
||||
? new Date(Date.parse(worker.last_renamed_at) + WORKER_RENAME_COOLDOWN_MS).toISOString()
|
||||
: null,
|
||||
canConsign: Boolean(worker.can_consign),
|
||||
consignmentActivatedAt: worker.consignment_activated_at || null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user