feat(app): 添加产品路由模块

- 导入并挂载产品路由到 /api 路径
- 集成产品相关的API接口

fix(userController): 优化登录错误提示信息

- 将通用的"请求体不能为空"修改为更具体的"用户名或密码不能为空"
- 提升用户登录体验和错误信息准确性
```
master
Lmx 2 weeks ago
parent b58d691fe1
commit f2eee72513

@ -36,6 +36,12 @@ app.use((err, req, res, next) => {
error: err.message
});
});
// 导入产品路由
const productRoutes = require('./routes/productRoutes');
// 挂载产品路由
app.use('/api', productRoutes);
// 导出app实例给server.js调用
module.exports = app;
module.exports = app;

@ -0,0 +1,85 @@
const Product = require('../models/Product');
const { Op } = require('sequelize');
// 获取商品列表
const getProductList = async (req, res) => {
try {
// 从查询参数获取分页信息
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
// 查询条件
const whereClause = {};
if (req.query.category) {
whereClause.category = req.query.category;
}
if (req.query.status !== undefined) {
whereClause.status = req.query.status;
}
if (req.query.name) {
whereClause.name = { [Op.like]: `%${req.query.name}%` };
}
// 查询商品列表
const { count, rows } = await Product.findAndCountAll({
where: whereClause,
limit: limit,
offset: offset,
order: [['created_at', 'DESC']]
});
return res.status(200).json({
code: 200,
msg: '获取商品列表成功',
data: {
products: rows,
pagination: {
currentPage: page,
totalPages: Math.ceil(count / limit),
totalItems: count,
itemsPerPage: limit
}
}
});
} catch (error) {
return res.status(500).json({
code: 500,
msg: '获取商品列表失败',
error: error.message
});
}
};
// 获取单个商品详情
const getProductDetail = async (req, res) => {
try {
const { id } = req.params;
const product = await Product.findByPk(id);
if (!product) {
return res.status(404).json({
code: 404,
msg: '商品不存在'
});
}
return res.status(200).json({
code: 200,
msg: '获取商品详情成功',
data: product
});
} catch (error) {
return res.status(500).json({
code: 500,
msg: '获取商品详情失败',
error: error.message
});
}
};
module.exports = {
getProductList,
getProductDetail
};

@ -9,7 +9,7 @@ const login = async (req, res) => {
if (!req.body) {
return res.status(400).json({
code: 400,
msg: '请求体不能为空'
msg: '用户名或密码不能为空'
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

@ -0,0 +1,26 @@
const { verifyToken } = require('../utils/jwt');
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
code: 401,
msg: '访问被拒绝,缺少令牌'
});
}
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({
code: 403,
msg: '令牌无效或已过期'
});
}
};
module.exports = { authenticateToken };

@ -0,0 +1,67 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/db');
const Product = sequelize.define('Product', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '商品ID'
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '商品名称'
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '商品描述'
},
price: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false,
comment: '商品价格'
},
stock: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: '库存数量'
},
category: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '商品分类'
},
image_url: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '商品图片URL'
},
status: {
type: DataTypes.TINYINT,
defaultValue: 1,
comment: '状态1-上架0-下架'
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
comment: '创建时间'
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
comment: '更新时间'
}
}, {
tableName: 'products',
timestamps: false,
underscored: true, // 自动将驼峰命名转换为下划线命名
paranoid: false,
charset: 'utf8mb4',
collate: 'utf8mb4_unicode_ci',
freezeTableName: true,
syncOnAssociation: false
});
module.exports = Product;

@ -0,0 +1,12 @@
const express = require('express');
const router = express.Router();
const { authenticateToken } = require('../middlewares/auth');
const { getProductList, getProductDetail } = require('../controllers/productController');
// 获取商品列表(需要认证)
router.get('/products', authenticateToken, getProductList);
// 获取单个商品详情(需要认证)
router.get('/products/:id', authenticateToken, getProductDetail);
module.exports = router;
Loading…
Cancel
Save