336 lines
10 KiB
TypeScript
336 lines
10 KiB
TypeScript
import {
|
|
files,
|
|
menuRoutes,
|
|
miniApps,
|
|
type MockFile,
|
|
users,
|
|
USER_STATUS,
|
|
type MockMiniApp,
|
|
} from './data'
|
|
|
|
export interface MockContext {
|
|
url: string
|
|
params: string[]
|
|
query: Record<string, string>
|
|
body: Record<string, unknown>
|
|
}
|
|
|
|
export interface MockRule {
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
|
pattern: RegExp
|
|
handler: (ctx: MockContext) => unknown
|
|
/** 全局 mock 关闭时仍生效(用于单独 mock 新增接口) */
|
|
alwaysMock?: boolean
|
|
}
|
|
|
|
const toNumber = (value: unknown, fallback = 0) => {
|
|
const n = Number(value)
|
|
return Number.isFinite(n) ? n : fallback
|
|
}
|
|
|
|
const pickString = (value: unknown, fallback = '') => (typeof value === 'string' ? value : fallback)
|
|
|
|
export const mockRules: MockRule[] = [
|
|
{
|
|
method: 'POST',
|
|
pattern: /^\/api\/auth\/login$/,
|
|
handler: (ctx) => ({
|
|
accessToken: 'mock-access-token',
|
|
refreshToken: 'mock-refresh-token',
|
|
tokenType: 'Bearer',
|
|
expiresIn: 7200,
|
|
username: pickString(ctx.body.username, 'admin'),
|
|
}),
|
|
},
|
|
{
|
|
method: 'POST',
|
|
pattern: /^\/api\/auth\/logout$/,
|
|
handler: () => true,
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/auth\/me$/,
|
|
handler: () => ({
|
|
userId: 1,
|
|
username: 'admin',
|
|
nickname: '超级管理员',
|
|
avatar: '',
|
|
roles: ['ADMIN'],
|
|
permissions: [
|
|
'sys:user:add',
|
|
'sys:user:edit',
|
|
'sys:user:delete',
|
|
'sys:user:query',
|
|
'miniapp:add',
|
|
'miniapp:edit',
|
|
'miniapp:delete',
|
|
'miniapp:query',
|
|
],
|
|
}),
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/auth\/routes$/,
|
|
handler: () => menuRoutes,
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/users$/,
|
|
handler: (ctx) => {
|
|
const page = toNumber(ctx.query.page, 1)
|
|
const size = toNumber(ctx.query.size, 20)
|
|
const miniId = ctx.query.mini_id ? toNumber(ctx.query.mini_id) : 0
|
|
const keyword = (ctx.query.keyword as string) || ''
|
|
|
|
const filtered = users.filter((item) => {
|
|
if (miniId && item.mini_id !== miniId) return false
|
|
if (keyword && !item.nickname.includes(keyword) && !item.openid.includes(keyword)) return false
|
|
return true
|
|
})
|
|
|
|
const start = (page - 1) * size
|
|
return { list: filtered.slice(start, start + size), total: filtered.length }
|
|
},
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/users\/(\d+)$/,
|
|
handler: (ctx) => users.find((item) => item.id === toNumber(ctx.params[0])) ?? null,
|
|
},
|
|
{
|
|
method: 'PUT',
|
|
pattern: /^\/api\/api\/admin\/users\/(\d+)$/,
|
|
handler: (ctx) => {
|
|
const target = users.find((item) => item.id === toNumber(ctx.params[0]))
|
|
if (!target) return false
|
|
const body = ctx.body as Record<string, unknown>
|
|
if (typeof body.nickname === 'string') target.nickname = body.nickname
|
|
if (typeof body.avatar === 'string') target.avatar = body.avatar
|
|
if (typeof body.status !== 'undefined') target.status = toNumber(body.status) === 0 ? 0 : 1
|
|
target.updated_at = new Date().toISOString()
|
|
return target
|
|
},
|
|
},
|
|
{
|
|
method: 'POST',
|
|
pattern: /^\/api\/api\/admin\/users\/(\d+)\/status$/,
|
|
handler: (ctx) => {
|
|
const target = users.find((item) => item.id === toNumber(ctx.params[0]))
|
|
if (!target) return false
|
|
const body = ctx.body as { status?: number }
|
|
target.status = toNumber(body.status) === 0 ? 0 : 1
|
|
target.updated_at = new Date().toISOString()
|
|
return target
|
|
},
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/miniapps$/,
|
|
handler: (ctx) => {
|
|
const { status } = ctx.query
|
|
const pageNum = toNumber(ctx.query.pageNum, 1)
|
|
const pageSize = toNumber(ctx.query.pageSize, 10)
|
|
|
|
const filtered = miniApps.filter((item) => !status || String(item.status) === status)
|
|
const start = (pageNum - 1) * pageSize
|
|
const list = filtered.slice(start, start + pageSize).map((item) => {
|
|
const { app_secret: _app_secret, ...rest } = item
|
|
return rest
|
|
})
|
|
return { list, total: filtered.length }
|
|
},
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/miniapps\/(\d+)$/,
|
|
handler: (ctx) => {
|
|
const target = miniApps.find((item) => item.id === toNumber(ctx.params[0]))
|
|
if (!target) return null
|
|
const { app_secret: _app_secret, ...rest } = target
|
|
return rest
|
|
},
|
|
},
|
|
{
|
|
method: 'POST',
|
|
pattern: /^\/api\/api\/admin\/miniapps$/,
|
|
handler: (ctx) => {
|
|
const id = miniApps.length + 1
|
|
const created: MockMiniApp = {
|
|
id,
|
|
name: pickString(ctx.body.name),
|
|
description: pickString(ctx.body.description),
|
|
app_id: pickString(ctx.body.app_id),
|
|
app_secret: pickString(ctx.body.app_secret),
|
|
status: toNumber(ctx.body.status, USER_STATUS.ENABLED) === 0 ? 0 : 1,
|
|
ext: pickString(ctx.body.ext, '{}'),
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString(),
|
|
}
|
|
miniApps.unshift(created)
|
|
return id
|
|
},
|
|
},
|
|
{
|
|
method: 'PUT',
|
|
pattern: /^\/api\/api\/admin\/miniapps\/(\d+)$/,
|
|
handler: (ctx) => {
|
|
const target = miniApps.find((item) => item.id === toNumber(ctx.params[0]))
|
|
if (!target) return false
|
|
const body = ctx.body as Record<string, unknown>
|
|
if (typeof body.name === 'string') target.name = body.name
|
|
if (typeof body.description === 'string') target.description = body.description
|
|
if (typeof body.app_id === 'string') target.app_id = body.app_id
|
|
if (typeof body.app_secret === 'string' && body.app_secret) target.app_secret = body.app_secret
|
|
if (typeof body.status !== 'undefined') target.status = toNumber(body.status) === 0 ? 0 : 1
|
|
if (typeof body.ext === 'string') target.ext = body.ext
|
|
target.updated_at = new Date().toISOString()
|
|
return true
|
|
},
|
|
},
|
|
{
|
|
method: 'DELETE',
|
|
pattern: /^\/api\/api\/admin\/miniapps\/([\d,]+)$/,
|
|
handler: (ctx) => {
|
|
const ids = ctx.params[0].split(',').map((id) => toNumber(id))
|
|
ids.forEach((id) => {
|
|
const index = miniApps.findIndex((item) => item.id === id)
|
|
if (index > -1) miniApps.splice(index, 1)
|
|
})
|
|
return true
|
|
},
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/files$/,
|
|
handler: (ctx) => {
|
|
const { keyword, uploader_type, page, size } = ctx.query
|
|
const pageNum = toNumber(page, 1)
|
|
const pageSize = toNumber(size, 20)
|
|
const kw = (keyword as string) || ''
|
|
const type = (uploader_type as string) || ''
|
|
|
|
const filtered = files.filter((item) => {
|
|
if (type && item.uploader_type !== type) return false
|
|
if (kw && !item.origin_name.includes(kw) && !item.filename.includes(kw)) return false
|
|
return true
|
|
})
|
|
|
|
const start = (pageNum - 1) * pageSize
|
|
return { list: filtered.slice(start, start + pageSize), total: filtered.length }
|
|
},
|
|
},
|
|
{
|
|
method: 'POST',
|
|
pattern: /^\/api\/api\/admin\/files\/upload$/,
|
|
handler: () => {
|
|
const id = files.length + 1
|
|
const rand = Math.random().toString(36).slice(2, 10)
|
|
const created: MockFile = {
|
|
id,
|
|
storage_key: `2026/10/27/${rand}.png`,
|
|
filename: `${rand}.png`,
|
|
origin_name: 'mock-upload.png',
|
|
mime_type: 'image/png',
|
|
ext: 'png',
|
|
size: 1024,
|
|
uploader_type: 'admin',
|
|
uploader_id: 1,
|
|
url: `/files/static/2026/10/27/${rand}.png`,
|
|
created_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
|
}
|
|
files.unshift(created)
|
|
return {
|
|
id,
|
|
url: created.url,
|
|
filename: created.filename,
|
|
origin_name: created.origin_name,
|
|
mime_type: created.mime_type,
|
|
size: created.size,
|
|
}
|
|
},
|
|
},
|
|
{
|
|
method: 'DELETE',
|
|
pattern: /^\/api\/api\/admin\/files\/(\d+)$/,
|
|
handler: (ctx) => {
|
|
const id = toNumber(ctx.params[0])
|
|
const index = files.findIndex((item) => item.id === id)
|
|
if (index > -1) files.splice(index, 1)
|
|
return { id }
|
|
},
|
|
},
|
|
{
|
|
// 统计每个小程序的用户数(有真实用户的取真实值,其余返回稳定模拟值)
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/dashboard\/user-count-by-miniapp$/,
|
|
alwaysMock: true,
|
|
handler: () =>
|
|
miniApps.map((app) => {
|
|
const real = users.filter((item) => item.mini_id === app.id).length
|
|
return {
|
|
miniAppId: app.id,
|
|
miniAppName: app.name,
|
|
userCount: real > 0 ? real : ((app.id * 37) % 200) + 8,
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/monitor\/server-info$/,
|
|
alwaysMock: true,
|
|
handler: () => ({
|
|
hostname: 'portal-server-01',
|
|
os: 'Linux',
|
|
osVersion: 'Ubuntu 22.04.3 LTS',
|
|
arch: 'amd64',
|
|
kernel: '5.15.0-91-generic',
|
|
cpuModel: 'Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz',
|
|
cpuCores: 8,
|
|
runtimeVersion: 'go1.21.5',
|
|
uptime: 1234560,
|
|
}),
|
|
},
|
|
{
|
|
// 实时状态:每次请求生成浮动指标,模拟监控采集中
|
|
method: 'GET',
|
|
pattern: /^\/api\/api\/admin\/monitor\/server-status$/,
|
|
alwaysMock: true,
|
|
handler: () => {
|
|
const cpuUsage = Math.round((20 + Math.random() * 60) * 10) / 10
|
|
const memTotal = 16 * 1024 ** 3
|
|
const memUsed = Math.round(memTotal * (0.4 + Math.random() * 0.3))
|
|
const loadBase = (cpuUsage / 100) * 8
|
|
const gb = 1024 ** 3
|
|
const disks = [
|
|
{ mount: '/', filesystem: '/dev/vda1', total: 100 * gb, used: 62 * gb, free: 38 * gb },
|
|
{ mount: '/data', filesystem: '/dev/vdb1', total: 500 * gb, used: 311 * gb, free: 189 * gb },
|
|
{ mount: '/logs', filesystem: '/dev/vdc1', total: 50 * gb, used: 9 * gb, free: 41 * gb },
|
|
].map((disk) => ({
|
|
...disk,
|
|
usagePercent: Math.round((disk.used / disk.total) * 1000) / 10,
|
|
}))
|
|
|
|
return {
|
|
cpu: {
|
|
usagePercent: cpuUsage,
|
|
cores: 8,
|
|
model: 'Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz',
|
|
loadAverage: [
|
|
Math.round((loadBase + Math.random()) * 100) / 100,
|
|
Math.round((loadBase + Math.random()) * 100) / 100,
|
|
Math.round((loadBase + Math.random()) * 100) / 100,
|
|
],
|
|
},
|
|
memory: {
|
|
total: memTotal,
|
|
used: memUsed,
|
|
free: memTotal - memUsed,
|
|
usagePercent: Math.round((memUsed / memTotal) * 1000) / 10,
|
|
},
|
|
disks,
|
|
timestamp: Date.now(),
|
|
}
|
|
},
|
|
},
|
|
]
|