后端迁移后台基础路由

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 { Router } from 'express'
import { getAdminDashboardSummary } from '../../services/admin/admin-dashboard-service.js' 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 { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js'
import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js' import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js'
import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js' import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js'
import { logWarn } from '../../utils/logger.js' import { logWarn } from '../../utils/logger.js'
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
/** @typedef {import('express').NextFunction} NextFunction */ type NextFunction = (error?: unknown) => void
/** @typedef {import('express').Request} ExpressRequest */ type ExpressResponse = {
/** @typedef {import('express').Response} ExpressResponse */ json: (body: unknown) => unknown
/** @typedef {import('../../types/admin-route-inputs.js').AdminRouteAdminSession} AdminRouteAdminSession */ sendFile: (path: string) => unknown
/** @typedef {ExpressRequest & { adminSession?: AdminRouteAdminSession | null }} AdminExpressRequest */ }
export type AdminExpressRequest = {
headers: { authorization?: string | string[] }
body?: any
params?: Record<string, string | undefined>
query?: Record<string, unknown>
adminSession?: AdminSession | null
[key: string]: unknown
}
/** type AdminAuditPayload = {
* @typedef {{ action?: string
* action?: string targetType?: string
* targetType?: string targetId?: string
* targetId?: string data?: Record<string, unknown>
* data?: Record<string, unknown> }
* }} AdminAuditPayload
*/
/** type AdminJsonHandlerOptions = {
* @typedef {{ successMessage?: string
* successMessage?: string errorMessage?: string
* errorMessage?: string scope?: string
* scope?: string audit?: (req: AdminExpressRequest, data: unknown) => AdminAuditPayload | null | undefined
* audit?: (req: AdminExpressRequest, data: unknown) => (AdminAuditPayload | null | undefined) }
* }} AdminJsonHandlerOptions
*/
/** type AdminFileHandlerOptions = {
* @typedef {{ errorMessage?: string
* errorMessage?: string scope?: string
* scope?: string }
* }} AdminFileHandlerOptions
*/
/** export function createJsonHandler(
* @param {(req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>} action action: (req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise<unknown>,
* @param {AdminJsonHandlerOptions} [options] { successMessage = 'ok', errorMessage, scope, audit }: AdminJsonHandlerOptions = {},
*/ ) {
export function createJsonHandler(action, { successMessage = 'ok', errorMessage, scope, audit } = {}) {
return async (req, res) => { return async (req, res) => {
try { try {
const data = await action(req, res) const data = await action(req, res)
@@ -52,11 +52,10 @@ export function createJsonHandler(action, { successMessage = 'ok', errorMessage,
} }
} }
/** export function createFileHandler(
* @param {(req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>} action action: (req: AdminExpressRequest, res: ExpressResponse) => string | Promise<string>,
* @param {AdminFileHandlerOptions} [options] { errorMessage, scope }: AdminFileHandlerOptions = {},
*/ ) {
export function createFileHandler(action, { errorMessage, scope } = {}) {
return async (req, res) => { return async (req, res) => {
try { try {
const filePath = await action(req, res) const filePath = await action(req, res)
@@ -67,10 +66,7 @@ export function createFileHandler(action, { errorMessage, scope } = {}) {
} }
} }
/** @param {AdminExpressRequest} req */ export async function requireAdminSession(req: AdminExpressRequest, res: ExpressResponse, next: NextFunction) {
/** @param {ExpressResponse} res */
/** @param {NextFunction} next */
export async function requireAdminSession(req, res, next) {
try { try {
req.adminSession = await verifyAdminSessionToken(extractBearerToken(req)) req.adminSession = await verifyAdminSessionToken(extractBearerToken(req))
next() next()
@@ -79,8 +75,7 @@ export async function requireAdminSession(req, res, next) {
} }
} }
/** @param {string[]} allowedRoles */ export function requireAdminRoles(allowedRoles: string[]) {
export function requireAdminRoles(allowedRoles) {
return (req, res, next) => { return (req, res, next) => {
try { try {
requireAdminRole(req.adminSession, allowedRoles) 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) { if (!audit) {
return 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 authorization = String(req.headers.authorization || '').trim()
const matched = authorization.match(/^Bearer\s+(.+)$/i) const matched = authorization.match(/^Bearer\s+(.+)$/i)
@@ -10,17 +10,15 @@ import {
} from '../../services/admin/admin-auth-service.js' } from '../../services/admin/admin-auth-service.js'
import { createJsonHandler, requireAdminRoles } from './shared.js' import { createJsonHandler, requireAdminRoles } from './shared.js'
/** type AdminUserMutationResult = {
* @typedef {{ user: {
* user: { userId: number
* userId: number username: string
* username: string role: string
* role: string status?: string
* status?: string inventoryGroupCodes?: string[]
* inventoryGroupCodes?: string[] }
* } }
* }} AdminUserMutationResult
*/
const router = Router() const router = Router()
@@ -42,7 +40,7 @@ router.post('/users', createJsonHandler(
errorMessage: '创建后台用户失败', errorMessage: '创建后台用户失败',
scope: '[admin/users:create]', scope: '[admin/users:create]',
audit: (_req, data) => { audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data) const result = data as AdminUserMutationResult
return { return {
action: 'admin_user_created', action: 'admin_user_created',
targetType: 'admin_user', targetType: 'admin_user',
@@ -64,7 +62,7 @@ router.post('/users/:userId/role', createJsonHandler(
errorMessage: '更新用户角色失败', errorMessage: '更新用户角色失败',
scope: '[admin/users/:userId/role]', scope: '[admin/users/:userId/role]',
audit: (_req, data) => { audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data) const result = data as AdminUserMutationResult
return { return {
action: 'admin_user_role_updated', action: 'admin_user_role_updated',
targetType: 'admin_user', targetType: 'admin_user',
@@ -85,7 +83,7 @@ router.post('/users/:userId/status', createJsonHandler(
errorMessage: '更新用户状态失败', errorMessage: '更新用户状态失败',
scope: '[admin/users/:userId/status]', scope: '[admin/users/:userId/status]',
audit: (_req, data) => { audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data) const result = data as AdminUserMutationResult
return { return {
action: 'admin_user_status_updated', action: 'admin_user_status_updated',
targetType: 'admin_user', targetType: 'admin_user',
@@ -106,7 +104,7 @@ router.post('/users/:userId/inventory-groups', createJsonHandler(
errorMessage: '更新用户库存组失败', errorMessage: '更新用户库存组失败',
scope: '[admin/users/:userId/inventory-groups]', scope: '[admin/users/:userId/inventory-groups]',
audit: (_req, data) => { audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data) const result = data as AdminUserMutationResult
return { return {
action: 'admin_user_inventory_groups_updated', action: 'admin_user_inventory_groups_updated',
targetType: 'admin_user', targetType: 'admin_user',
@@ -127,7 +125,7 @@ router.post('/users/:userId/reset-password', createJsonHandler(
errorMessage: '重置用户密码失败', errorMessage: '重置用户密码失败',
scope: '[admin/users/:userId/reset-password]', scope: '[admin/users/:userId/reset-password]',
audit: (_req, data) => { audit: (_req, data) => {
const result = /** @type {AdminUserMutationResult} */ (data) const result = data as AdminUserMutationResult
return { return {
action: 'admin_user_password_reset', action: 'admin_user_password_reset',
targetType: 'admin_user', targetType: 'admin_user',
@@ -18,7 +18,7 @@ type AdminUserRow = NonNullable<Awaited<ReturnType<typeof getAdminUserById>>>
type JsonObject = Record<string, any> type JsonObject = Record<string, any>
type AdminSession = { export type AdminSession = {
sessionId: string sessionId: string
userId: number userId: number
username: string username: string
+10
View File
@@ -808,6 +808,16 @@
- `npm run build` - `npm run build`
- `npm test` 共 139 个用例通过 - `npm test` 共 139 个用例通过
208. `src/services` 目录下非测试 `.js` 服务文件已全部迁移为 `.ts` 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 个用例通过
## 下一步建议 ## 下一步建议