feat: 新增开发期 mock-server 鉴权接口
parent
d86afa7799
commit
3ee4aa6ee7
|
|
@ -0,0 +1,62 @@
|
|||
export const menuTree = [
|
||||
{
|
||||
id: 'system',
|
||||
name: '系统管理',
|
||||
path: '/system',
|
||||
icon: 'SettingOutlined',
|
||||
type: 'menu',
|
||||
children: [
|
||||
{
|
||||
id: 'sys_user',
|
||||
name: '用户管理',
|
||||
path: '/system/user',
|
||||
component: 'system/user/UserList',
|
||||
type: 'menu',
|
||||
buttons: [
|
||||
{ code: 'user:add', name: '新增' },
|
||||
{ code: 'user:edit', name: '修改' },
|
||||
{ code: 'user:delete', name: '删除' },
|
||||
{ code: 'user:lock', name: '锁定' },
|
||||
{ code: 'user:unlock', name: '解锁' },
|
||||
{ code: 'user:resetPwd', name: '密码重置' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sys_role',
|
||||
name: '角色管理',
|
||||
path: '/system/role',
|
||||
component: 'system/role/RoleList',
|
||||
type: 'menu',
|
||||
buttons: [
|
||||
{ code: 'role:add', name: '新增' },
|
||||
{ code: 'role:edit', name: '修改' },
|
||||
{ code: 'role:delete', name: '删除' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sys_org',
|
||||
name: '机构管理',
|
||||
path: '/system/org',
|
||||
component: 'system/org/OrgList',
|
||||
type: 'menu',
|
||||
buttons: [
|
||||
{ code: 'org:add', name: '新增' },
|
||||
{ code: 'org:edit', name: '修改' },
|
||||
{ code: 'org:delete', name: '删除' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export function flattenButtons(tree) {
|
||||
const codes = []
|
||||
function walk(nodes) {
|
||||
for (const n of nodes) {
|
||||
if (n.buttons) codes.push(...n.buttons.map((b) => b.code))
|
||||
if (n.children) walk(n.children)
|
||||
}
|
||||
}
|
||||
walk(tree)
|
||||
return codes
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export const users = [
|
||||
{
|
||||
id: 'u001',
|
||||
username: 'admin',
|
||||
password: 'Admin@123',
|
||||
realName: '系统管理员',
|
||||
phone: '13800000000',
|
||||
orgId: 'org001',
|
||||
roleIds: ['role_admin'],
|
||||
status: 'enabled', // enabled | locked
|
||||
failCount: 0,
|
||||
createdAt: '2026-01-01 00:00:00'
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import authRoutes from './routes/auth.js'
|
||||
|
||||
const app = express()
|
||||
app.use(cors({ origin: 'http://localhost:5173', credentials: true }))
|
||||
app.use(express.json({ limit: '10mb' }))
|
||||
app.use(cookieParser())
|
||||
|
||||
app.use('/auth', authRoutes)
|
||||
|
||||
const PORT = 8888
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[mock-server] listening on http://localhost:${PORT}`)
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { accessTokens } from '../state.js'
|
||||
|
||||
export function requireAuth(req, res, next) {
|
||||
const header = req.headers.authorization || ''
|
||||
const token = header.startsWith('Bearer ') ? header.slice(7) : ''
|
||||
const record = accessTokens.get(token)
|
||||
if (!record || record.expireAt < Date.now()) {
|
||||
return res.status(401).json({ code: 40100, msg: '登录已过期', data: null })
|
||||
}
|
||||
req.userId = record.userId
|
||||
next()
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import express from 'express'
|
||||
import crypto from 'crypto'
|
||||
import { users } from '../data/users.js'
|
||||
import { menuTree, flattenButtons } from '../data/menu.js'
|
||||
import {
|
||||
accessTokens,
|
||||
refreshTokens,
|
||||
ACCESS_TOKEN_TTL_MS,
|
||||
REFRESH_TOKEN_TTL_MS,
|
||||
FIXED_SMS_CODE
|
||||
} from '../state.js'
|
||||
import { requireAuth } from '../middleware/requireAuth.js'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
function findUser(username) {
|
||||
return users.find((u) => u.username === username)
|
||||
}
|
||||
|
||||
router.post('/sms/send', (req, res) => {
|
||||
const { username, password } = req.body || {}
|
||||
const user = findUser(username)
|
||||
if (!user) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
||||
if (user.status === 'locked') return res.json({ code: 40002, msg: '账号已锁定,请联系管理员', data: null })
|
||||
if (user.password !== password) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
||||
console.log(`[mock-server] 验证码已发送至 ${user.phone}: ${FIXED_SMS_CODE}`)
|
||||
res.json({ code: 200, msg: 'ok', data: null })
|
||||
})
|
||||
|
||||
router.post('/login', (req, res) => {
|
||||
const { username, password, code } = req.body || {}
|
||||
const user = findUser(username)
|
||||
if (!user) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
||||
if (user.status === 'locked') return res.json({ code: 40002, msg: '账号已锁定,请联系管理员', data: null })
|
||||
if (user.password !== password) {
|
||||
user.failCount += 1
|
||||
if (user.failCount >= 5) {
|
||||
user.status = 'locked'
|
||||
return res.json({ code: 40002, msg: '连续输错5次,账号已锁定', data: null })
|
||||
}
|
||||
return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
||||
}
|
||||
if (code !== FIXED_SMS_CODE) return res.json({ code: 40003, msg: '验证码错误或已过期', data: null })
|
||||
user.failCount = 0
|
||||
|
||||
const accessToken = crypto.randomUUID()
|
||||
const refreshToken = crypto.randomUUID()
|
||||
accessTokens.set(accessToken, { userId: user.id, expireAt: Date.now() + ACCESS_TOKEN_TTL_MS })
|
||||
refreshTokens.set(refreshToken, { userId: user.id, expireAt: Date.now() + REFRESH_TOKEN_TTL_MS })
|
||||
|
||||
res.cookie('refresh_token', refreshToken, {
|
||||
httpOnly: true,
|
||||
maxAge: REFRESH_TOKEN_TTL_MS,
|
||||
sameSite: 'lax'
|
||||
})
|
||||
res.json({
|
||||
code: 200,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
accessToken,
|
||||
userInfo: { id: user.id, username: user.username, realName: user.realName, orgId: user.orgId, roleIds: user.roleIds },
|
||||
menuTree,
|
||||
buttonCodes: flattenButtons(menuTree)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/refresh', (req, res) => {
|
||||
const refreshToken = req.cookies?.refresh_token
|
||||
const record = refreshToken && refreshTokens.get(refreshToken)
|
||||
if (!record || record.expireAt < Date.now()) {
|
||||
return res.status(401).json({ code: 40100, msg: '登录已过期,请重新登录', data: null })
|
||||
}
|
||||
const accessToken = crypto.randomUUID()
|
||||
accessTokens.set(accessToken, { userId: record.userId, expireAt: Date.now() + ACCESS_TOKEN_TTL_MS })
|
||||
res.json({ code: 200, msg: 'ok', data: { accessToken } })
|
||||
})
|
||||
|
||||
router.post('/logout', (req, res) => {
|
||||
const refreshToken = req.cookies?.refresh_token
|
||||
if (refreshToken) refreshTokens.delete(refreshToken)
|
||||
res.clearCookie('refresh_token')
|
||||
res.json({ code: 200, msg: 'ok', data: null })
|
||||
})
|
||||
|
||||
router.get('/userinfo', requireAuth, (req, res) => {
|
||||
const user = users.find((u) => u.id === req.userId)
|
||||
res.json({ code: 200, msg: 'ok', data: { id: user.id, username: user.username, realName: user.realName, orgId: user.orgId, roleIds: user.roleIds } })
|
||||
})
|
||||
|
||||
router.get('/menu', requireAuth, (req, res) => {
|
||||
res.json({ code: 200, msg: 'ok', data: { menuTree, buttonCodes: flattenButtons(menuTree) } })
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export const accessTokens = new Map() // token -> { userId, expireAt }
|
||||
export const refreshTokens = new Map() // token -> { userId, expireAt }
|
||||
|
||||
// 开发调试用:验证 401 自动刷新流程时可临时调小,验证完成后必须还原
|
||||
export const ACCESS_TOKEN_TTL_MS = 30 * 60 * 1000
|
||||
export const REFRESH_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000
|
||||
export const FIXED_SMS_CODE = '123456'
|
||||
Loading…
Reference in New Issue