Merge remote-tracking branch 'origin/main'

This commit is contained in:
2025-09-12 13:15:03 +08:00
committed by aiotagro
28 changed files with 10237 additions and 1945 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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