diff --git a/app.js b/app.js index 7729576..f2203f9 100644 --- a/app.js +++ b/app.js @@ -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; \ No newline at end of file +module.exports = app; + diff --git a/controllers/productController.js b/controllers/productController.js new file mode 100644 index 0000000..1805bd7 --- /dev/null +++ b/controllers/productController.js @@ -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 +}; \ No newline at end of file diff --git a/controllers/userController.js b/controllers/userController.js index b96cf64..13eca05 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -9,7 +9,7 @@ const login = async (req, res) => { if (!req.body) { return res.status(400).json({ code: 400, - msg: '请求体不能为空' + msg: '用户名或密码不能为空' }); } diff --git a/image.png b/image.png new file mode 100644 index 0000000..8481d4d Binary files /dev/null and b/image.png differ diff --git a/middlewares/auth.js b/middlewares/auth.js new file mode 100644 index 0000000..d624621 --- /dev/null +++ b/middlewares/auth.js @@ -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 }; \ No newline at end of file diff --git a/models/Product.js b/models/Product.js new file mode 100644 index 0000000..e7a952a --- /dev/null +++ b/models/Product.js @@ -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; \ No newline at end of file diff --git a/routes/productRoutes.js b/routes/productRoutes.js new file mode 100644 index 0000000..a92f1a5 --- /dev/null +++ b/routes/productRoutes.js @@ -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; \ No newline at end of file