后端迁移后台基础路由

This commit is contained in:
yml
2026-05-21 17:19:23 +08:00
parent 717866e5fb
commit f96bfe0b20
6 changed files with 107 additions and 101 deletions
@@ -1,5 +1,3 @@
// @ts-check
import { Router } from 'express'
import { getAdminDashboardSummary } from '../../services/admin/admin-dashboard-service.js'
@@ -1,46 +1,46 @@
// @ts-check
import { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js'
import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js'
import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js'
import { logWarn } from '../../utils/logger.js'
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
/** @typedef {import('express').NextFunction} NextFunction */
/** @typedef {import('express').Request} ExpressRequest */
/** @typedef {import('express').Response} ExpressResponse */
/** @typedef {import('../../types/admin-route-inputs.js').AdminRouteAdminSession} AdminRouteAdminSession */
/** @typedef {ExpressRequest & { adminSession?: AdminRouteAdminSession | null }} AdminExpressRequest */
type NextFunction = (error?: unknown) => void
type ExpressResponse = {
json: (body: unknown) => unknown
sendFile: (path: string) => unknown
}
export type AdminExpressRequest = {
headers: { authorization?: string | string[] }
body?: any
params?: Record<string, string | undefined>
query?: Record<string, unknown>
adminSession?: AdminSession | null
[key: string]: unknown
}
/**
* @typedef {{
* action?: string
* targetType?: string
* targetId?: string
* data?: Record<string, unknown>
* }} AdminAuditPayload
*/
type AdminAuditPayload = {
action?: string
targetType?: string
targetId?: string
data?: Record<string, unknown>
}
/**
* @typedef {{
* successMessage?: string
* errorMessage?: string
* scope?: string
* audit?: (req: AdminExpressRequest, data: unknown) => (AdminAuditPayload | null | undefined)
* }} AdminJsonHandlerOptions
*/
type AdminJsonHandlerOptions = {
successMessage?: string
errorMessage?: string
scope?: string
audit?: (req: AdminExpressRequest, data: unknown) => AdminAuditPayload | null | undefined
}
/**
* @typedef {{
* errorMessage?: string
* scope?: string
* }} AdminFileHandlerOptions
*/
type AdminFileHandlerOptions = {
errorMessage?: string
scope?: string
}
/**
* @param {(req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>} action
* @param {AdminJsonHandlerOptions} [options]
*/
export function createJsonHandler(action, { successMessage = 'ok', errorMessage, scope, audit } = {}) {
export function createJsonHandler(
action: (req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>,
{ successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {},
) {
return async (req, res) => {
try {
const data = await action(req, res)
@@ -52,11 +52,10 @@ export function createJsonHandler(action, { successMessage = 'ok', errorMessage,
}
}
/**
* @param {(req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>} action
* @param {AdminFileHandlerOptions} [options]
*/
export function createFileHandler(action, { errorMessage, scope } = {}) {
export function createFileHandler(
action: (req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>,
{ errorMessage, scope }: AdminFileHandlerOptions = {},
) {
return async (req, res) => {
try {
const filePath = await action(req, res)
@@ -67,10 +66,7 @@ export function createFileHandler(action, { errorMessage, scope } = {}) {
}
}
/** @param {AdminExpressRequest} req */
/** @param {ExpressResponse} res */
/** @param {NextFunction} next */
export async function requireAdminSession(req, res, next) {
export async function requireAdminSession(req: AdminExpressRequest, res: ExpressResponse, next: NextFunction) {
try {
req.adminSession = await verifyAdminSessionToken(extractBearerToken(req))
next()
@@ -79,8 +75,7 @@ export async function requireAdminSession(req, res, next) {
}
}
/** @param {string[]} allowedRoles */
export function requireAdminRoles(allowedRoles) {
export function requireAdminRoles(allowedRoles: string[]) {
return (req, res, next) => {
try {
requireAdminRole(req.adminSession, allowedRoles)
@@ -91,7 +86,12 @@ export function requireAdminRoles(allowedRoles) {
}
}
async function recordAdminAudit(session, req, data, audit) {
async function recordAdminAudit(
session: AdminSession | null | undefined,
req: AdminExpressRequest,
data: unknown,
audit: AdminJsonHandlerOptions['audit'],
) {
if (!audit) {
return
}
@@ -114,7 +114,7 @@ async function recordAdminAudit(session, req, data, audit) {
}
}
export function extractBearerToken(req) {
export function extractBearerToken(req: AdminExpressRequest) {
const authorization = String(req.headers.authorization || '').trim()
const matched = authorization.match(/^Bearer\s+(.+)$/i)
@@ -10,17 +10,15 @@ import {
} from '../../services/admin/admin-auth-service.js'
import { createJsonHandler, requireAdminRoles } from './shared.js'
/**
* @typedef {{
* user: {
* userId: number
* username: string
* role: string
* status?: string
* inventoryGroupCodes?: string[]
* }
* }} AdminUserMutationResult
*/
type AdminUserMutationResult = {
user: {
userId: number
username: string
role: string
status?: string
inventoryGroupCodes?: string[]
}
}
const router = Router()
@@ -42,16 +40,16 @@ router.post('/users', createJsonHandler(
errorMessage: '创建后台用户失败',
scope: '[admin/users:create]',
audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
const result = data as AdminUserMutationResult
return {
action: 'admin_user_created',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
role: result.user.role,
status: result.user.status,
},
action: 'admin_user_created',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
role: result.user.role,
status: result.user.status,
},
}
},
},
@@ -64,15 +62,15 @@ router.post('/users/:userId/role', createJsonHandler(
errorMessage: '更新用户角色失败',
scope: '[admin/users/:userId/role]',
audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
const result = data as AdminUserMutationResult
return {
action: 'admin_user_role_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
role: result.user.role,
},
action: 'admin_user_role_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
role: result.user.role,
},
}
},
},
@@ -85,15 +83,15 @@ router.post('/users/:userId/status', createJsonHandler(
errorMessage: '更新用户状态失败',
scope: '[admin/users/:userId/status]',
audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
const result = data as AdminUserMutationResult
return {
action: 'admin_user_status_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
status: result.user.status,
},
action: 'admin_user_status_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
status: result.user.status,
},
}
},
},
@@ -106,15 +104,15 @@ router.post('/users/:userId/inventory-groups', createJsonHandler(
errorMessage: '更新用户库存组失败',
scope: '[admin/users/:userId/inventory-groups]',
audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
const result = data as AdminUserMutationResult
return {
action: 'admin_user_inventory_groups_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
inventoryGroupCodes: result.user.inventoryGroupCodes,
},
action: 'admin_user_inventory_groups_updated',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
inventoryGroupCodes: result.user.inventoryGroupCodes,
},
}
},
},
@@ -127,14 +125,14 @@ router.post('/users/:userId/reset-password', createJsonHandler(
errorMessage: '重置用户密码失败',
scope: '[admin/users/:userId/reset-password]',
audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data)
const result = data as AdminUserMutationResult
return {
action: 'admin_user_password_reset',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
},
action: 'admin_user_password_reset',
targetType: 'admin_user',
targetId: String(result.user.userId),
data: {
username: result.user.username,
},
}
},
},
@@ -18,7 +18,7 @@ type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
type JsonObject = Record<string, any>
type AdminSession = {
export type AdminSession = {
sessionId: string
userId: number
username: string
+10
View File
@@ -808,6 +808,16 @@
- `npm run build`
- `npm test` 共 139 个用例通过
208. `src/services` 目录下非测试 `.js` 服务文件已全部迁移为 `.ts`
209. 第一批后台 Admin 路由迁移到 `.ts`
- `src/routes/admin/shared.ts`
- `src/routes/admin/auth.ts`
- `src/routes/admin/dashboard.ts`
- `src/routes/admin/users.ts`
210. 后台 JSON / 文件 handler、session 鉴权中间件、角色中间件、登录 / session / logout、概览摘要和后台用户管理路由已进入 TS 编译链路;Admin session、route request、审计 payload、用户 mutation result 与 handler options 补齐类型,并导出真实 `AdminSession` 给路由层复用
211. Docker 内验证通过:
- `npm run typecheck`
- `npm run build`
- `npm test` 共 139 个用例通过
## 下一步建议