done
This commit is contained in:
199
server.js
199
server.js
@@ -1,15 +1,20 @@
|
||||
// server.js —— 启动服务 + 鉴权中间件 + 3 个接口
|
||||
// server.js —— 启动服务 + 鉴权中间件 + 全部 CRUD 路由
|
||||
require('dotenv').config()
|
||||
const express = require('express')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const { pool, initDB } = require('./db')
|
||||
|
||||
// 导入各模块路由
|
||||
const users = require('./routes/users')
|
||||
const customers = require('./routes/customers')
|
||||
const employees = require('./routes/employees')
|
||||
const contracts = require('./routes/contracts')
|
||||
const afterSales = require('./routes/afterSales')
|
||||
const products = require('./routes/products')
|
||||
|
||||
const app = express()
|
||||
app.use(express.json()) // 解析 application/json 请求体
|
||||
|
||||
// ============ 鉴权中间件 ============
|
||||
// 验证请求头里的 Bearer token,把解析出来的用户挂到 req.user
|
||||
function auth(req, res, next) {
|
||||
const header = req.headers.authorization || ''
|
||||
const token = header.replace(/^Bearer\s+/i, '')
|
||||
@@ -17,6 +22,7 @@ function auth(req, res, next) {
|
||||
return res.status(401).json({ code: 401, message: '未登录' })
|
||||
}
|
||||
try {
|
||||
const jwt = require('jsonwebtoken')
|
||||
req.user = jwt.verify(token, process.env.JWT_SECRET)
|
||||
next()
|
||||
} catch (e) {
|
||||
@@ -25,7 +31,6 @@ function auth(req, res, next) {
|
||||
}
|
||||
|
||||
// ============ 管理员校验中间件 ============
|
||||
// 必须排在 auth 之后使用(依赖 req.user)
|
||||
function requireAdmin(req, res, next) {
|
||||
if (!req.user || req.user.role !== 'admin') {
|
||||
return res.status(403).json({ code: 403, message: '需要管理员权限' })
|
||||
@@ -33,141 +38,71 @@ function requireAdmin(req, res, next) {
|
||||
next()
|
||||
}
|
||||
|
||||
// ============ POST /api/login ============
|
||||
app.post('/api/user/login', async (req, res) => {
|
||||
const { username, password } = req.body || {}
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ code: 400, message: '用户名和密码必填' })
|
||||
}
|
||||
// ============ 登录/登出/个人信息 ============
|
||||
app.post('/api/user/login', users.login)
|
||||
app.get('/api/user/info', auth, users.info)
|
||||
app.post('/api/user/logout', auth, users.logout)
|
||||
app.put('/api/user/password', auth, users.changePassword)
|
||||
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id, username, password, nickname, avatar, role FROM users WHERE username = ?',
|
||||
[username]
|
||||
)
|
||||
// ============ 用户管理 CRUD(管理员)/api/users ============
|
||||
app.get('/api/users', auth, requireAdmin, users.list)
|
||||
app.get('/api/users/:id', auth, requireAdmin, users.detail)
|
||||
app.post('/api/users', auth, requireAdmin, users.create)
|
||||
app.put('/api/users/:id', auth, requireAdmin, users.update)
|
||||
app.delete('/api/users/:id', auth, requireAdmin, users.remove)
|
||||
|
||||
// 用户不存在 或 密码错 —— 都用同一条提示,避免被枚举账号
|
||||
if (rows.length === 0) {
|
||||
return res.status(400).json({ code: 400, message: '账号或密码错误' })
|
||||
}
|
||||
const user = rows[0]
|
||||
const ok = await bcrypt.compare(password, user.password)
|
||||
if (!ok) {
|
||||
return res.status(400).json({ code: 400, message: '账号或密码错误' })
|
||||
}
|
||||
// ============ 客户管理 /api/customers ============
|
||||
app.get('/api/customers', auth, customers.list)
|
||||
app.get('/api/customers/:id', auth, customers.detail)
|
||||
app.post('/api/customers', auth, customers.create)
|
||||
app.put('/api/customers/:id', auth, customers.update)
|
||||
app.delete('/api/customers/:id', auth, customers.remove)
|
||||
|
||||
// 签 token:payload 里放 id / username / role
|
||||
const token = jwt.sign(
|
||||
{ id: user.id,
|
||||
username: user.username,
|
||||
role: user.role },
|
||||
// ============ 员工管理 /api/employees ============
|
||||
app.get('/api/employees', auth, employees.list)
|
||||
app.get('/api/employees/:id', auth, employees.detail)
|
||||
app.post('/api/employees', auth, employees.create)
|
||||
app.put('/api/employees/:id', auth, employees.update)
|
||||
app.delete('/api/employees/:id', auth, employees.remove)
|
||||
|
||||
process.env.JWT_SECRET,
|
||||
// ============ 合同管理 /api/contracts ============
|
||||
app.get('/api/contracts', auth, contracts.list)
|
||||
app.get('/api/contracts/:id', auth, contracts.detail)
|
||||
app.post('/api/contracts', auth, contracts.create)
|
||||
app.put('/api/contracts/:id', auth, contracts.update)
|
||||
app.delete('/api/contracts/:id', auth, contracts.remove)
|
||||
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '2h' }
|
||||
)
|
||||
// ============ 售后管理 /api/after-sales ============
|
||||
app.get('/api/after-sales', auth, afterSales.list)
|
||||
app.get('/api/after-sales/:id', auth, afterSales.detail)
|
||||
app.post('/api/after-sales', auth, afterSales.create)
|
||||
app.put('/api/after-sales/:id', auth, afterSales.update)
|
||||
app.delete('/api/after-sales/:id', auth, afterSales.remove)
|
||||
|
||||
res.status(200).json({
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
data: {
|
||||
token,
|
||||
userInfo: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar,
|
||||
role: user.role,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[login] error:', e)
|
||||
res.status(500).json({ code: 500, message: e.message })
|
||||
}
|
||||
})
|
||||
|
||||
// ============ GET /api/info ============
|
||||
// 需要登录后访问,前端刷新页面时调用,用来拿最新的用户信息
|
||||
app.get('/api/user/info', auth, async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id, username, nickname, avatar, role FROM users WHERE id = ?',
|
||||
[req.user.id]
|
||||
)
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).json({ code: 404, message: '用户不存在' })
|
||||
}
|
||||
res.json({ code: 0, message: 'ok', data: rows[0] })
|
||||
} catch (e) {
|
||||
console.error('[info] error:', e)
|
||||
res.status(500).json({ code: 500, message: e.message })
|
||||
}
|
||||
})
|
||||
|
||||
// ============ POST /api/logout ============
|
||||
// JWT 是无状态的:后端没法"作废"token,主流做法是前端清掉 localStorage 里的 token
|
||||
// 这里只是给前端一个明确的应答,告诉你"我收到了"
|
||||
app.post('/api/user/logout', auth, (req, res) => {
|
||||
res.json({ code: 0, message: 'ok' })
|
||||
})
|
||||
|
||||
// ============ POST /api/user/create ============
|
||||
// 仅管理员可调用:在 users 表里新增一行
|
||||
app.post('/api/user/create', auth, requireAdmin, async (req, res) => {
|
||||
//role不填默认user
|
||||
const { username, password, nickname = '', role = 'user' } = req.body || {}
|
||||
|
||||
// 1) 入参校验
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ code: 400, message: '用户名和密码必填' })
|
||||
}
|
||||
if (typeof username !== 'string' || username.length < 3 || username.length > 50) {
|
||||
return res.status(400).json({ code: 400, message: '用户名长度需在 3-50 之间' })
|
||||
}
|
||||
if (typeof password !== 'string' || password.length < 6) {
|
||||
return res.status(400).json({ code: 400, message: '密码至少 6 位' })
|
||||
}
|
||||
|
||||
try {
|
||||
// 2) 密码哈希(10 轮 salt,与 db.js initDB 的 admin 同强度)
|
||||
const hash = await bcrypt.hash(password, 10)
|
||||
|
||||
// 3) 写入数据库;role 不强制 'user',允许前端传来的 role
|
||||
const [result] = await pool.query(
|
||||
'INSERT INTO users (username, password, nickname, role) VALUES (?, ?, ?, ?)',
|
||||
[username, hash, nickname, role]
|
||||
)
|
||||
|
||||
// 4) 把新行的关键信息回给前端(不包含 password 哈希)
|
||||
res.json({
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: { id: result.insertId, username, nickname, role: role },
|
||||
})
|
||||
} catch (e) {
|
||||
// users.username 是 UNIQUE,重复时 MySQL 抛 ER_DUP_ENTRY (errno 1062)
|
||||
if (e.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(409).json({ code: 409, message: '用户名已存在' })
|
||||
}
|
||||
console.error('[create user] error:', e)
|
||||
res.status(500).json({ code: 500, message: e.message })
|
||||
}
|
||||
})
|
||||
// ============ 产品管理 /api/products ============
|
||||
app.get('/api/products', auth, products.list)
|
||||
app.get('/api/products/:id', auth, products.detail)
|
||||
app.post('/api/products', auth, products.create)
|
||||
app.put('/api/products/:id', auth, products.update)
|
||||
app.delete('/api/products/:id', auth, products.remove)
|
||||
|
||||
// ============ 启动 ============
|
||||
const PORT = Number(process.env.PORT) || 3000
|
||||
|
||||
initDB()
|
||||
.then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[server] 已启动 → http://127.0.0.1:${PORT}`)
|
||||
console.log(`[test] curl -X POST http://127.0.0.1:${PORT}/api/login \\`)
|
||||
console.log(` -H "Content-Type: application/json" \\`)
|
||||
console.log(` -d '{"username":"admin","password":"123456"}'`)
|
||||
module.exports = app
|
||||
|
||||
if (require.main === module) {
|
||||
initDB()
|
||||
.then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[server] 已启动 → http://127.0.0.1:${PORT}`)
|
||||
console.log(`[test] curl -X POST http://127.0.0.1:${PORT}/api/user/login \\`)
|
||||
console.log(` -H "Content-Type: application/json" \\`)
|
||||
console.log(` -d '{"username":"admin","password":"123456"}'`)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[init] 数据库初始化失败:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[init] 数据库初始化失败:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user