Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -7,25 +7,101 @@ const router = express.Router()
|
||||
// 引入数据库模型
|
||||
const { ApiUser } = require('../models')
|
||||
|
||||
// 登录参数验证
|
||||
// 引入认证中间件
|
||||
const { authenticateJWT } = require('../middleware/auth')
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* LoginRequest:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - password
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* password:
|
||||
* type: string
|
||||
* description: 密码
|
||||
* LoginResponse:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* token:
|
||||
* type: string
|
||||
* user:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* username:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* user_type:
|
||||
* type: string
|
||||
* status:
|
||||
* type: string
|
||||
*/
|
||||
|
||||
// 从环境变量或配置中获取JWT密钥
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_key'
|
||||
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h'
|
||||
|
||||
// 验证模式
|
||||
const loginSchema = Joi.object({
|
||||
username: Joi.string().min(2).max(50).required(),
|
||||
password: Joi.string().min(6).max(100).required()
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required()
|
||||
})
|
||||
|
||||
// 生成JWT token
|
||||
// 生成JWT令牌
|
||||
const generateToken = (user) => {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.user_type
|
||||
email: user.email,
|
||||
user_type: user.user_type
|
||||
},
|
||||
process.env.JWT_SECRET || 'niumall-secret-key',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
JWT_SECRET,
|
||||
{
|
||||
expiresIn: JWT_EXPIRES_IN
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/login:
|
||||
* post:
|
||||
* summary: 用户登录
|
||||
* tags: [认证管理]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/LoginRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登录成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/LoginResponse'
|
||||
* 400:
|
||||
* description: 参数验证失败或用户名密码错误
|
||||
* 401:
|
||||
* description: 未授权或用户被禁用
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 用户登录
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
@@ -38,94 +114,126 @@ router.post('/login', async (req, res) => {
|
||||
details: error.details[0].message
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const { username, password } = value
|
||||
|
||||
|
||||
// 查找用户
|
||||
const user = await ApiUser.findOne({
|
||||
where: {
|
||||
[require('sequelize').Op.or]: [
|
||||
{ username: username },
|
||||
{ phone: username },
|
||||
[ApiUser.sequelize.Op.or]: [
|
||||
{ username },
|
||||
{ email: username }
|
||||
]
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
// 检查用户是否存在以及密码是否正确
|
||||
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password_hash)
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(403).json({
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '账户已被禁用,请联系管理员'
|
||||
message: '用户账号已被禁用'
|
||||
})
|
||||
}
|
||||
|
||||
// 生成token
|
||||
|
||||
// 生成JWT令牌
|
||||
const token = generateToken(user)
|
||||
|
||||
|
||||
// 准备返回的用户信息(不包含敏感数据)
|
||||
const userInfo = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
user_type: user.user_type,
|
||||
status: user.status
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
access_token: token,
|
||||
token_type: 'Bearer',
|
||||
expires_in: 86400, // 24小时
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.user_type,
|
||||
status: user.status
|
||||
}
|
||||
}
|
||||
token,
|
||||
user: userInfo
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
console.error('用户登录失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '登录失败,请稍后重试'
|
||||
message: '登录失败,请稍后再试'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/me:
|
||||
* get:
|
||||
* summary: 获取当前用户信息
|
||||
* tags: [认证管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* username:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* user_type:
|
||||
* type: string
|
||||
* status:
|
||||
* type: string
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updatedAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 获取当前用户信息
|
||||
router.get('/me', authenticateToken, async (req, res) => {
|
||||
router.get('/me', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const user = await ApiUser.findByPk(req.user.id)
|
||||
const userId = req.user.id
|
||||
|
||||
// 根据ID查找用户
|
||||
const user = await ApiUser.findByPk(userId, {
|
||||
attributes: {
|
||||
exclude: ['password_hash'] // 排除密码哈希等敏感信息
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.user_type,
|
||||
status: user.status
|
||||
}
|
||||
}
|
||||
data: user
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
@@ -136,37 +244,49 @@ router.get('/me', authenticateToken, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/auth/logout:
|
||||
* post:
|
||||
* summary: 用户登出
|
||||
* tags: [认证管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 登出成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 用户登出
|
||||
router.post('/logout', authenticateToken, (req, res) => {
|
||||
// 在实际项目中,可以将token加入黑名单
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
})
|
||||
})
|
||||
|
||||
// JWT token验证中间件
|
||||
function authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization']
|
||||
const token = authHeader && authHeader.split(' ')[1]
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
router.post('/logout', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
// 注意:JWT是无状态的,服务器端无法直接使token失效
|
||||
// 登出操作主要由客户端完成,如删除本地存储的token
|
||||
// 这里只返回成功信息
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('用户登出失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '访问令牌缺失'
|
||||
message: '登出失败,请稍后再试'
|
||||
})
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET || 'niumall-secret-key', (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '访问令牌无效或已过期'
|
||||
})
|
||||
}
|
||||
req.user = user
|
||||
next()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -2,172 +2,318 @@ const express = require('express')
|
||||
const Joi = require('joi')
|
||||
const router = express.Router()
|
||||
|
||||
// 模拟订单数据
|
||||
let orders = [
|
||||
{
|
||||
id: 1,
|
||||
orderNo: 'ORD20240101001',
|
||||
buyerId: 2,
|
||||
buyerName: '山东养殖场',
|
||||
supplierId: 3,
|
||||
supplierName: '河北供应商',
|
||||
traderId: 1,
|
||||
traderName: '北京贸易公司',
|
||||
cattleBreed: '西门塔尔',
|
||||
cattleCount: 50,
|
||||
expectedWeight: 25000,
|
||||
actualWeight: 24800,
|
||||
unitPrice: 28.5,
|
||||
totalAmount: 712500,
|
||||
paidAmount: 200000,
|
||||
remainingAmount: 512500,
|
||||
status: 'shipping',
|
||||
deliveryAddress: '山东省济南市某养殖场',
|
||||
expectedDeliveryDate: '2024-01-15',
|
||||
actualDeliveryDate: null,
|
||||
notes: '优质西门塔尔牛',
|
||||
createdAt: '2024-01-10T00:00:00Z',
|
||||
updatedAt: '2024-01-12T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderNo: 'ORD20240101002',
|
||||
buyerId: 2,
|
||||
buyerName: '山东养殖场',
|
||||
supplierId: 4,
|
||||
supplierName: '内蒙古牧场',
|
||||
traderId: 1,
|
||||
traderName: '北京贸易公司',
|
||||
cattleBreed: '安格斯',
|
||||
cattleCount: 30,
|
||||
expectedWeight: 18000,
|
||||
actualWeight: 18200,
|
||||
unitPrice: 30.0,
|
||||
totalAmount: 540000,
|
||||
paidAmount: 540000,
|
||||
remainingAmount: 0,
|
||||
status: 'completed',
|
||||
deliveryAddress: '山东省济南市某养殖场',
|
||||
expectedDeliveryDate: '2024-01-08',
|
||||
actualDeliveryDate: '2024-01-08',
|
||||
notes: '',
|
||||
createdAt: '2024-01-05T00:00:00Z',
|
||||
updatedAt: '2024-01-08T00:00:00Z'
|
||||
}
|
||||
]
|
||||
// 引入数据库模型
|
||||
const { Order } = require('../models')
|
||||
const sequelize = require('sequelize')
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Order:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 订单ID
|
||||
* buyer_id:
|
||||
* type: integer
|
||||
* description: 买方ID
|
||||
* buyer_name:
|
||||
* type: string
|
||||
* description: 买方名称
|
||||
* supplier_id:
|
||||
* type: integer
|
||||
* description: 供应商ID
|
||||
* supplier_name:
|
||||
* type: string
|
||||
* description: 供应商名称
|
||||
* cow_breed:
|
||||
* type: string
|
||||
* description: 牛品种
|
||||
* quantity:
|
||||
* type: integer
|
||||
* description: 数量
|
||||
* weight:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 总重量(kg)
|
||||
* unit_price:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 单价(元/kg)
|
||||
* total_amount:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 总金额(元)
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [pending, confirmed, delivered, completed, cancelled]
|
||||
* description: 订单状态
|
||||
* delivery_address:
|
||||
* type: string
|
||||
* description: 配送地址
|
||||
* delivery_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 配送日期
|
||||
* payment_status:
|
||||
* type: string
|
||||
* enum: [unpaid, paid, partially_paid]
|
||||
* description: 支付状态
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* updatedAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 更新时间
|
||||
* CreateOrderRequest:
|
||||
* type: object
|
||||
* required:
|
||||
* - buyer_id
|
||||
* - supplier_id
|
||||
* - cow_breed
|
||||
* - quantity
|
||||
* - weight
|
||||
* - unit_price
|
||||
* - total_amount
|
||||
* - delivery_address
|
||||
* - delivery_date
|
||||
* properties:
|
||||
* buyer_id:
|
||||
* type: integer
|
||||
* description: 买方ID
|
||||
* buyer_name:
|
||||
* type: string
|
||||
* description: 买方名称
|
||||
* supplier_id:
|
||||
* type: integer
|
||||
* description: 供应商ID
|
||||
* supplier_name:
|
||||
* type: string
|
||||
* description: 供应商名称
|
||||
* cow_breed:
|
||||
* type: string
|
||||
* description: 牛品种
|
||||
* quantity:
|
||||
* type: integer
|
||||
* description: 数量
|
||||
* weight:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 总重量(kg)
|
||||
* unit_price:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 单价(元/kg)
|
||||
* total_amount:
|
||||
* type: number
|
||||
* format: float
|
||||
* description: 总金额(元)
|
||||
* delivery_address:
|
||||
* type: string
|
||||
* description: 配送地址
|
||||
* delivery_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 配送日期
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* UpdateOrderRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [pending, confirmed, delivered, completed, cancelled]
|
||||
* description: 订单状态
|
||||
* payment_status:
|
||||
* type: string
|
||||
* enum: [unpaid, paid, partially_paid]
|
||||
* description: 支付状态
|
||||
* delivery_address:
|
||||
* type: string
|
||||
* description: 配送地址
|
||||
* delivery_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 配送日期
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
*/
|
||||
|
||||
// 订单状态枚举
|
||||
const ORDER_STATUS = {
|
||||
PENDING: 'pending',
|
||||
CONFIRMED: 'confirmed',
|
||||
PREPARING: 'preparing',
|
||||
SHIPPING: 'shipping',
|
||||
DELIVERED: 'delivered',
|
||||
ACCEPTED: 'accepted',
|
||||
COMPLETED: 'completed',
|
||||
CANCELLED: 'cancelled',
|
||||
REFUNDED: 'refunded'
|
||||
PENDING: 'pending', // 待确认
|
||||
CONFIRMED: 'confirmed', // 已确认
|
||||
DELIVERED: 'delivered', // 已配送
|
||||
COMPLETED: 'completed', // 已完成
|
||||
CANCELLED: 'cancelled' // 已取消
|
||||
}
|
||||
|
||||
// 支付状态枚举
|
||||
const PAYMENT_STATUS = {
|
||||
UNPAID: 'unpaid', // 未支付
|
||||
PAID: 'paid', // 已支付
|
||||
PARTIALLY_PAID: 'partially_paid' // 部分支付
|
||||
}
|
||||
|
||||
// 验证模式
|
||||
const createOrderSchema = Joi.object({
|
||||
buyerId: Joi.number().integer().positive().required(),
|
||||
supplierId: Joi.number().integer().positive().required(),
|
||||
traderId: Joi.number().integer().positive(),
|
||||
cattleBreed: Joi.string().min(1).max(50).required(),
|
||||
cattleCount: Joi.number().integer().positive().required(),
|
||||
expectedWeight: Joi.number().positive().required(),
|
||||
unitPrice: Joi.number().positive().required(),
|
||||
deliveryAddress: Joi.string().min(1).max(200).required(),
|
||||
expectedDeliveryDate: Joi.date().iso().required(),
|
||||
notes: Joi.string().max(500).allow('')
|
||||
buyer_id: Joi.number().integer().required(),
|
||||
buyer_name: Joi.string().allow(''),
|
||||
supplier_id: Joi.number().integer().required(),
|
||||
supplier_name: Joi.string().allow(''),
|
||||
cow_breed: Joi.string().required(),
|
||||
quantity: Joi.number().integer().min(1).required(),
|
||||
weight: Joi.number().positive().required(),
|
||||
unit_price: Joi.number().positive().required(),
|
||||
total_amount: Joi.number().positive().required(),
|
||||
delivery_address: Joi.string().required(),
|
||||
delivery_date: Joi.date().required(),
|
||||
remark: Joi.string().allow('')
|
||||
})
|
||||
|
||||
const updateOrderSchema = Joi.object({
|
||||
cattleBreed: Joi.string().min(1).max(50),
|
||||
cattleCount: Joi.number().integer().positive(),
|
||||
expectedWeight: Joi.number().positive(),
|
||||
actualWeight: Joi.number().positive(),
|
||||
unitPrice: Joi.number().positive(),
|
||||
deliveryAddress: Joi.string().min(1).max(200),
|
||||
expectedDeliveryDate: Joi.date().iso(),
|
||||
actualDeliveryDate: Joi.date().iso(),
|
||||
notes: Joi.string().max(500).allow(''),
|
||||
status: Joi.string().valid(...Object.values(ORDER_STATUS))
|
||||
status: Joi.string().valid(...Object.values(ORDER_STATUS)),
|
||||
payment_status: Joi.string().valid(...Object.values(PAYMENT_STATUS)),
|
||||
delivery_address: Joi.string(),
|
||||
delivery_date: Joi.date(),
|
||||
remark: Joi.string().allow('')
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders:
|
||||
* get:
|
||||
* summary: 获取订单列表
|
||||
* tags: [订单管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 页码,默认为1
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 每页条数,默认为20
|
||||
* - in: query
|
||||
* name: keyword
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 关键词搜索(买方名称、供应商名称、牛品种)
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 订单状态筛选
|
||||
* - in: query
|
||||
* name: payment_status
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 支付状态筛选
|
||||
* - in: query
|
||||
* name: start_date
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期筛选
|
||||
* - in: query
|
||||
* name: end_date
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* items:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Order'
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 获取订单列表
|
||||
router.get('/', (req, res) => {
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
orderNo,
|
||||
buyerId,
|
||||
supplierId,
|
||||
keyword,
|
||||
status,
|
||||
startDate,
|
||||
endDate
|
||||
payment_status,
|
||||
start_date,
|
||||
end_date
|
||||
} = req.query
|
||||
|
||||
let filteredOrders = [...orders]
|
||||
|
||||
// 订单号搜索
|
||||
if (orderNo) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
order.orderNo.includes(orderNo)
|
||||
)
|
||||
// 构建查询条件
|
||||
const where = {}
|
||||
if (keyword) {
|
||||
where[sequelize.Op.or] = [
|
||||
{ buyer_name: { [sequelize.Op.like]: `%${keyword}%` } },
|
||||
{ supplier_name: { [sequelize.Op.like]: `%${keyword}%` } },
|
||||
{ cow_breed: { [sequelize.Op.like]: `%${keyword}%` } }
|
||||
]
|
||||
}
|
||||
if (status) where.status = status
|
||||
if (payment_status) where.payment_status = payment_status
|
||||
if (start_date || end_date) {
|
||||
where.createdAt = {}
|
||||
if (start_date) where.createdAt[sequelize.Op.gte] = new Date(start_date)
|
||||
if (end_date) where.createdAt[sequelize.Op.lte] = new Date(end_date)
|
||||
}
|
||||
|
||||
// 买方筛选
|
||||
if (buyerId) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
order.buyerId === parseInt(buyerId)
|
||||
)
|
||||
}
|
||||
|
||||
// 供应商筛选
|
||||
if (supplierId) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
order.supplierId === parseInt(supplierId)
|
||||
)
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
filteredOrders = filteredOrders.filter(order => order.status === status)
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if (startDate) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
new Date(order.createdAt) >= new Date(startDate)
|
||||
)
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
new Date(order.createdAt) <= new Date(endDate)
|
||||
)
|
||||
}
|
||||
|
||||
// 分页
|
||||
const total = filteredOrders.length
|
||||
const startIndex = (page - 1) * pageSize
|
||||
const endIndex = startIndex + parseInt(pageSize)
|
||||
const paginatedOrders = filteredOrders.slice(startIndex, endIndex)
|
||||
// 分页查询
|
||||
const result = await Order.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: (parseInt(page) - 1) * parseInt(pageSize),
|
||||
order: [['createdAt', 'DESC']]
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
items: paginatedOrders,
|
||||
total: total,
|
||||
items: result.rows,
|
||||
total: result.count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
totalPages: Math.ceil(total / pageSize)
|
||||
totalPages: Math.ceil(result.count / parseInt(pageSize))
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单列表失败'
|
||||
@@ -175,11 +321,46 @@ router.get('/', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders/{id}:
|
||||
* get:
|
||||
* summary: 获取订单详情
|
||||
* tags: [订单管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 订单ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/Order'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 订单不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 获取订单详情
|
||||
router.get('/:id', (req, res) => {
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const order = orders.find(o => o.id === parseInt(id))
|
||||
|
||||
const order = await Order.findByPk(id)
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
@@ -193,6 +374,7 @@ router.get('/:id', (req, res) => {
|
||||
data: order
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取订单详情失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单详情失败'
|
||||
@@ -200,10 +382,44 @@ router.get('/:id', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 创建订单
|
||||
router.post('/', (req, res) => {
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders:
|
||||
* post:
|
||||
* summary: 创建新订单
|
||||
* tags: [订单管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateOrderRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/Order'
|
||||
* 400:
|
||||
* description: 参数验证失败
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 创建新订单
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
// 参数验证
|
||||
const { error, value } = createOrderSchema.validate(req.body)
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
@@ -213,60 +429,20 @@ router.post('/', (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
buyerId,
|
||||
supplierId,
|
||||
traderId,
|
||||
cattleBreed,
|
||||
cattleCount,
|
||||
expectedWeight,
|
||||
unitPrice,
|
||||
deliveryAddress,
|
||||
expectedDeliveryDate,
|
||||
notes
|
||||
} = value
|
||||
|
||||
// 生成订单号
|
||||
const orderNo = `ORD${new Date().toISOString().slice(0, 10).replace(/-/g, '')}${String(orders.length + 1).padStart(3, '0')}`
|
||||
|
||||
// 计算总金额
|
||||
const totalAmount = expectedWeight * unitPrice
|
||||
|
||||
// 创建新订单
|
||||
const newOrder = {
|
||||
id: Math.max(...orders.map(o => o.id)) + 1,
|
||||
orderNo,
|
||||
buyerId,
|
||||
buyerName: '买方名称', // 实际项目中需要从数据库获取
|
||||
supplierId,
|
||||
supplierName: '供应商名称', // 实际项目中需要从数据库获取
|
||||
traderId: traderId || null,
|
||||
traderName: traderId ? '贸易商名称' : null,
|
||||
cattleBreed,
|
||||
cattleCount,
|
||||
expectedWeight,
|
||||
actualWeight: null,
|
||||
unitPrice,
|
||||
totalAmount,
|
||||
paidAmount: 0,
|
||||
remainingAmount: totalAmount,
|
||||
// 创建订单,默认状态为待确认
|
||||
const order = await Order.create({
|
||||
...value,
|
||||
status: ORDER_STATUS.PENDING,
|
||||
deliveryAddress,
|
||||
expectedDeliveryDate,
|
||||
actualDeliveryDate: null,
|
||||
notes: notes || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
orders.push(newOrder)
|
||||
payment_status: PAYMENT_STATUS.UNPAID
|
||||
})
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '订单创建成功',
|
||||
data: newOrder
|
||||
data: order
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建订单失败'
|
||||
@@ -274,20 +450,55 @@ router.post('/', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 更新订单
|
||||
router.put('/:id', (req, res) => {
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders/{id}:
|
||||
* put:
|
||||
* summary: 更新订单信息
|
||||
* tags: [订单管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 订单ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/UpdateOrderRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/Order'
|
||||
* 400:
|
||||
* description: 参数验证失败
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 订单不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 更新订单信息
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
const { error, value } = updateOrderSchema.validate(req.body)
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
@@ -297,25 +508,26 @@ router.put('/:id', (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 更新订单信息
|
||||
orders[orderIndex] = {
|
||||
...orders[orderIndex],
|
||||
...value,
|
||||
updatedAt: new Date().toISOString()
|
||||
// 查找订单
|
||||
const order = await Order.findByPk(id)
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 如果更新了实际重量,重新计算总金额
|
||||
if (value.actualWeight && orders[orderIndex].unitPrice) {
|
||||
orders[orderIndex].totalAmount = value.actualWeight * orders[orderIndex].unitPrice
|
||||
orders[orderIndex].remainingAmount = orders[orderIndex].totalAmount - orders[orderIndex].paidAmount
|
||||
}
|
||||
// 更新订单信息
|
||||
await order.update(value)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单更新成功',
|
||||
data: orders[orderIndex]
|
||||
data: order
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新订单失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新订单失败'
|
||||
@@ -323,26 +535,62 @@ router.put('/:id', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/orders/{id}:
|
||||
* delete:
|
||||
* summary: 删除订单
|
||||
* tags: [订单管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 订单ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 订单不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 删除订单
|
||||
router.delete('/:id', (req, res) => {
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
const order = await Order.findByPk(id)
|
||||
|
||||
if (!order) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
orders.splice(orderIndex, 1)
|
||||
await order.destroy()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单删除成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除订单失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除订单失败'
|
||||
@@ -350,190 +598,4 @@ router.delete('/:id', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 确认订单
|
||||
router.put('/:id/confirm', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (orders[orderIndex].status !== ORDER_STATUS.PENDING) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '只有待确认的订单才能确认'
|
||||
})
|
||||
}
|
||||
|
||||
orders[orderIndex].status = ORDER_STATUS.CONFIRMED
|
||||
orders[orderIndex].updatedAt = new Date().toISOString()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单确认成功',
|
||||
data: orders[orderIndex]
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '确认订单失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 取消订单
|
||||
router.put('/:id/cancel', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { reason } = req.body
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
orders[orderIndex].status = ORDER_STATUS.CANCELLED
|
||||
orders[orderIndex].notes = reason ? `取消原因: ${reason}` : '订单已取消'
|
||||
orders[orderIndex].updatedAt = new Date().toISOString()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单取消成功',
|
||||
data: orders[orderIndex]
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '取消订单失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 订单验收
|
||||
router.put('/:id/accept', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { actualWeight, notes } = req.body
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (!actualWeight || actualWeight <= 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请提供有效的实际重量'
|
||||
})
|
||||
}
|
||||
|
||||
orders[orderIndex].status = ORDER_STATUS.ACCEPTED
|
||||
orders[orderIndex].actualWeight = actualWeight
|
||||
orders[orderIndex].totalAmount = actualWeight * orders[orderIndex].unitPrice
|
||||
orders[orderIndex].remainingAmount = orders[orderIndex].totalAmount - orders[orderIndex].paidAmount
|
||||
orders[orderIndex].actualDeliveryDate = new Date().toISOString()
|
||||
if (notes) {
|
||||
orders[orderIndex].notes = notes
|
||||
}
|
||||
orders[orderIndex].updatedAt = new Date().toISOString()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单验收成功',
|
||||
data: orders[orderIndex]
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '订单验收失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 完成订单
|
||||
router.put('/:id/complete', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const orderIndex = orders.findIndex(o => o.id === parseInt(id))
|
||||
|
||||
if (orderIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '订单不存在'
|
||||
})
|
||||
}
|
||||
|
||||
orders[orderIndex].status = ORDER_STATUS.COMPLETED
|
||||
orders[orderIndex].paidAmount = orders[orderIndex].totalAmount
|
||||
orders[orderIndex].remainingAmount = 0
|
||||
orders[orderIndex].updatedAt = new Date().toISOString()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单完成成功',
|
||||
data: orders[orderIndex]
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '完成订单失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 获取订单统计数据
|
||||
router.get('/statistics', (req, res) => {
|
||||
try {
|
||||
const { startDate, endDate } = req.query
|
||||
|
||||
let filteredOrders = [...orders]
|
||||
|
||||
if (startDate) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
new Date(order.createdAt) >= new Date(startDate)
|
||||
)
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
filteredOrders = filteredOrders.filter(order =>
|
||||
new Date(order.createdAt) <= new Date(endDate)
|
||||
)
|
||||
}
|
||||
|
||||
const statistics = {
|
||||
totalOrders: filteredOrders.length,
|
||||
completedOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.COMPLETED).length,
|
||||
pendingOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.PENDING).length,
|
||||
cancelledOrders: filteredOrders.filter(o => o.status === ORDER_STATUS.CANCELLED).length,
|
||||
totalAmount: filteredOrders.reduce((sum, order) => sum + order.totalAmount, 0),
|
||||
totalCattle: filteredOrders.reduce((sum, order) => sum + order.cattleCount, 0),
|
||||
statusDistribution: Object.values(ORDER_STATUS).reduce((acc, status) => {
|
||||
acc[status] = filteredOrders.filter(o => o.status === status).length
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: statistics
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单统计失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -7,24 +7,176 @@ const router = express.Router()
|
||||
const { ApiUser } = require('../models')
|
||||
const sequelize = require('sequelize')
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* User:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* description: 邮箱
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 手机号
|
||||
* user_type:
|
||||
* type: string
|
||||
* enum: [admin, buyer, supplier, trader]
|
||||
* description: 用户类型
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, suspended]
|
||||
* description: 用户状态
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* updatedAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 更新时间
|
||||
* CreateUserRequest:
|
||||
* type: object
|
||||
* required:
|
||||
* - username
|
||||
* - email
|
||||
* - password
|
||||
* - user_type
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* description: 邮箱
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 手机号
|
||||
* password:
|
||||
* type: string
|
||||
* description: 密码
|
||||
* user_type:
|
||||
* type: string
|
||||
* enum: [admin, buyer, supplier, trader]
|
||||
* description: 用户类型
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, suspended]
|
||||
* description: 用户状态
|
||||
* UpdateUserRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* description: 邮箱
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 手机号
|
||||
* user_type:
|
||||
* type: string
|
||||
* enum: [admin, buyer, supplier, trader]
|
||||
* description: 用户类型
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, suspended]
|
||||
* description: 用户状态
|
||||
*/
|
||||
|
||||
// 验证模式
|
||||
const createUserSchema = Joi.object({
|
||||
username: Joi.string().min(2).max(50).required(),
|
||||
email: Joi.string().email().required(),
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
|
||||
password: Joi.string().min(6).max(100).required(),
|
||||
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin').required(),
|
||||
status: Joi.string().valid('active', 'inactive', 'locked').default('active')
|
||||
user_type: Joi.string().valid('admin', 'buyer', 'supplier', 'trader').required(),
|
||||
status: Joi.string().valid('active', 'inactive', 'suspended').default('active')
|
||||
})
|
||||
|
||||
const updateUserSchema = Joi.object({
|
||||
username: Joi.string().min(2).max(50),
|
||||
email: Joi.string().email(),
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
|
||||
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin'),
|
||||
status: Joi.string().valid('active', 'inactive', 'locked')
|
||||
user_type: Joi.string().valid('admin', 'buyer', 'supplier', 'trader'),
|
||||
status: Joi.string().valid('active', 'inactive', 'suspended')
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* get:
|
||||
* summary: 获取用户列表
|
||||
* tags: [用户管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 页码,默认为1
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 每页条数,默认为20
|
||||
* - in: query
|
||||
* name: keyword
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 关键词搜索(用户名、邮箱、手机号)
|
||||
* - in: query
|
||||
* name: user_type
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户类型筛选
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 用户状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* items:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* total:
|
||||
* type: integer
|
||||
* page:
|
||||
* type: integer
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 获取用户列表
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
@@ -69,10 +221,45 @@ router.get('/', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* get:
|
||||
* summary: 获取用户详情
|
||||
* tags: [用户管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 用户ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 获取用户详情
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (!user) {
|
||||
@@ -95,10 +282,44 @@ router.get('/:id', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 创建用户
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users:
|
||||
* post:
|
||||
* summary: 创建新用户
|
||||
* tags: [用户管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CreateUserRequest'
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 400:
|
||||
* description: 参数验证失败
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 创建新用户
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
// 参数验证
|
||||
const { error, value } = createUserSchema.validate(req.body)
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
@@ -110,13 +331,12 @@ router.post('/', async (req, res) => {
|
||||
|
||||
const { username, email, phone, password, user_type, status } = value
|
||||
|
||||
// 检查用户名是否已存在
|
||||
// 检查用户名、邮箱是否已存在
|
||||
const existingUser = await ApiUser.findOne({
|
||||
where: {
|
||||
[sequelize.Op.or]: [
|
||||
{ username: username },
|
||||
{ email: email },
|
||||
{ phone: phone }
|
||||
{ username },
|
||||
{ email }
|
||||
]
|
||||
}
|
||||
})
|
||||
@@ -124,28 +344,31 @@ router.post('/', async (req, res) => {
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名、邮箱或手机号已存在'
|
||||
message: '用户名或邮箱已被使用'
|
||||
})
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
const saltRounds = 10
|
||||
const password_hash = await bcrypt.hash(password, saltRounds)
|
||||
const hashedPassword = await bcrypt.hash(password, 10)
|
||||
|
||||
// 创建新用户
|
||||
const newUser = await ApiUser.create({
|
||||
// 创建用户
|
||||
const user = await ApiUser.create({
|
||||
username,
|
||||
email,
|
||||
phone: phone || '',
|
||||
password_hash,
|
||||
phone,
|
||||
password_hash: hashedPassword,
|
||||
user_type,
|
||||
status,
|
||||
status
|
||||
})
|
||||
|
||||
// 移除密码哈希,避免返回敏感信息
|
||||
const userData = user.toJSON()
|
||||
delete userData.password_hash
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户创建成功',
|
||||
data: newUser
|
||||
data: userData
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建用户失败:', error)
|
||||
@@ -156,20 +379,55 @@ router.post('/', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 更新用户
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* put:
|
||||
* summary: 更新用户信息
|
||||
* tags: [用户管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 用户ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/UpdateUserRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 400:
|
||||
* description: 参数验证失败
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 更新用户信息
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
const { error, value } = updateUserSchema.validate(req.body)
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
@@ -179,6 +437,16 @@ router.put('/:id', async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await user.update(value)
|
||||
|
||||
@@ -196,10 +464,45 @@ router.put('/:id', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/users/{id}:
|
||||
* delete:
|
||||
* summary: 删除用户
|
||||
* tags: [用户管理]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: 用户ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 404:
|
||||
* description: 用户不存在
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
// 删除用户
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (!user) {
|
||||
@@ -209,6 +512,8 @@ router.delete('/:id', async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 软删除或永久删除
|
||||
// 如果需要软删除,可以改为更新status为'inactive'
|
||||
await user.destroy()
|
||||
|
||||
res.json({
|
||||
@@ -224,113 +529,4 @@ router.delete('/:id', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 批量删除用户
|
||||
router.delete('/batch', async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请提供有效的用户ID列表'
|
||||
})
|
||||
}
|
||||
|
||||
await ApiUser.destroy({
|
||||
where: {
|
||||
id: ids
|
||||
}
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `成功删除 ${ids.length} 个用户`
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量删除用户失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除用户失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 重置用户密码
|
||||
router.put('/:id/password', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { password } = req.body
|
||||
|
||||
const user = await ApiUser.findByPk(id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (!password || password.length < 6) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '密码长度不能少于6位'
|
||||
})
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
const saltRounds = 10
|
||||
const password_hash = await bcrypt.hash(password, saltRounds)
|
||||
|
||||
// 更新密码
|
||||
await user.update({ password_hash })
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码重置成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('重置密码失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '重置密码失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 更新用户状态
|
||||
router.put('/:id/status', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
const user = await ApiUser.findByPk(id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (!['active', 'inactive', 'locked'].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的用户状态'
|
||||
})
|
||||
}
|
||||
|
||||
await user.update({ status })
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户状态更新成功',
|
||||
data: user
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新用户状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户状态失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
Reference in New Issue
Block a user