✅ 一、功能目标(FUNCTION GOALS)
本次后端开发的目标是构建一个商品管理平台,提供商品的管理和展示功能,目标如下:
- 商品 CRUD 操作:
-
- 支持商品的创建、读取、更新、删除功能。
- 用户能够发布新商品,修改商品信息,删除自己发布的商品。
- 商品列表展示:
-
- 提供商品列表接口,支持分页和筛选,用户可以查看所有上架商品。
- 商品分类管理:
-
- 提供商品分类的静态数据接口,供前端展示分类选项,支持商品按照分类进行筛选。
- 收藏功能支持:
- 权限控制:
-
- 所有商品管理操作(发布、修改、删除)都需要登录验证,确保只有授权用户能够执行这些操作。
- 数据安全与一致性:
-
- 确保每个操作都具备适当的错误处理和反馈,避免非法操作和数据不一致的情况发生。
✅ 二、后端模块目录结构规划(新增部分)
bash
复制代码
platform_serve/
├── app.js
├── .env
├── config/
│ ├── config.js
│ └── db.js
│ └── categories.js # 校园服务内容的分类
├── controllers/
│ ├── authController.js
│ ├── userController.js
│ ├── productController.js # 商品/服务控制器
├── middleware/
│ ├── auth.js
│ └── errorHandler.js
├── routes/
│ ├── auth.js
│ ├── user.js
│ ├── productRoutes.js # 商品路由
├── utils/
│ └── response.js
└── package.json
✅ 一、数据库设计(MySQL)
1. 商品 / 服务表 product
字段名 |
类型 |
说明 |
id |
INT, PK, AUTO_INCREMENT |
商品ID |
user_id |
INT |
发布者ID(外键 users.id) |
title |
VARCHAR(100) |
标题 |
description |
TEXT |
描述 |
price |
DECIMAL(10,2) |
价格 |
category |
VARCHAR(50) |
分类(前端从 categoryList.js 选取) |
images |
TEXT |
图片(JSON字符串数组) |
status |
TINYINT |
状态(1=上架,0=下架) |
created_at |
DATETIME |
发布时间 |
updated_at |
DATETIME |
更新时间 |
3.创建表的 SQL 语句
sql
复制代码
-- 商品表
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
user_id INT NOT NULL COMMENT '发布者ID,关联 users.id',
title VARCHAR(100) NOT NULL COMMENT '商品标题',
description TEXT COMMENT '商品描述',
price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '商品价格',
category VARCHAR(50) NOT NULL COMMENT '商品分类(参考 categoryList.js)',
images TEXT COMMENT '商品图片数组(JSON 字符串)',
status TINYINT DEFAULT 1 COMMENT '状态:1=上架,0=下架',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
FOREIGN KEY (user_id) REFERENCES users(id)
);
✅ 三、接口文档表格
接口名称 |
方法 |
路径 |
描述 |
权限 |
发布商品 |
POST |
/api/products |
用户发布商品 |
需登录 |
获取商品列表 |
GET |
/api/products |
获取所有上架商品 |
公共 |
获取商品详情 |
GET |
/api/products/:id |
获取单个商品详情 |
公共 |
更新商品 |
PUT |
/api/products/:id |
用户修改自己发布的商品 |
需登录 |
下架商品 |
PATCH |
/api/products/:id/status |
修改商品状态(上下架) |
需登录 |
删除商品 |
DELETE |
/api/products/:id |
删除自己发布的商品 |
需登录 |
收藏商品 |
POST |
/api/favorites/:id |
收藏某商品(:id为商品ID) |
需登录 |
取消收藏 |
DELETE |
/api/favorites/:id |
取消收藏某商品 |
需登录 |
获取我的收藏 |
GET |
/api/favorites |
获取当前用户的收藏列表 |
需登录 |
获取我的商品 |
GET |
/api/products/mine |
查看自己发布的商品 |
需登录 |
获取商品分类 |
GET |
/api/products/categories |
获取商品分类静态数据 |
公共 |
分类 js
css
复制代码
const categories = [{ name: '电子数码', children: ['手机', '平板', '电脑', '耳机', '相机', '游戏设备']
},
{
name: '学习资料',
children: ['教材', '课本', '教辅', '笔记', '文具', '四六级资料']
},
{
name: '生活用品',
children: ['床上用品', '收纳用品', '衣架', '杯子', '雨伞', '电风扇']
},
{
name: '衣物鞋包',
children: ['男装', '女装', '鞋子', '包包', '配饰']
},
{
name: '运动健身',
children: ['球类', '运动鞋', '瑜伽垫', '健身器材']
},
{
name: '美妆护肤',
children: ['护肤品', '彩妆', '香水', '洗护用品']
},
{
name: '校园服务',
children: ['代拿快递', '跑腿服务', '打印复印', '拼车', '课程辅导', '代购', '租赁服务']
},
{
name: '其他',
children: ['未分类']
}
];
module.exports = categories;
路由定义 (routes/productRoutes.js
)
csharp
复制代码
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
const authMiddleware = require('../middleware/auth');
// 发布商品
router.post('/create', authMiddleware, productController.createProduct);
// 获取商品分类
router.get('/categories', productController.getProductCategories);
// 获取商品列表
router.get('/list', productController.getProductList);
// 获取商品详情
router.get('/:id', productController.getProductDetails);
// 删除商品
router.delete('/:id', authMiddleware, productController.deleteProduct);
// 更新商品
router.put('/:id', authMiddleware, productController.updateProduct);
// 获取我发布的商品列表
router.get('/my', authMiddleware, productController.getMyProducts);
module.exports = router;
控制器实现 (controllers/productController.js
)
javascript
复制代码
const db = require('../config/db');
const response = require('../utils/response');
const categories = require('../utils/category');
// 获取商品分类
exports.getProductCategories = (req, res) => {
res.json({
code: 0,
message: '获取分类成功',
data: categories
});
};
// 发布商品
exports.createProduct = async (req, res) => {
const {
title,
description,
price,
category,
images
} = req.body;
if (!title || !description || !price || !category || !images) {
return response.fail(res, '缺少必要字段');
}
const query = `INSERT INTO products (user_id, title, description, price, category, images)
VALUES (?, ?, ?, ?, ?, ?)`;
db.query(query, [
req.user.id,
title,
description,
price,
category,
JSON.stringify(images)
], (error, results) => {
if (error) {
return response.fail(res, '商品发布失败', error);
}
return response.success(res, {
id: results.insertId
}, '商品发布成功');
});
};
// 获取商品列表
// 获取商品列表接口
exports.getProductList = (req, res) => {
let {
mainCategory = '', // 一级分类
subCategory = '', // 二级分类
searchQuery = '', // 搜索关键词
page = 1,
pageSize = 10
} = req.query;
page = parseInt(page);
pageSize = parseInt(pageSize);
const offset = (page - 1) * pageSize;
// 构造 where 条件
let whereClauses = ['status = 1'];
let queryParams = [];
if (mainCategory) {
whereClauses.push('mainCategory = ?');
queryParams.push(mainCategory);
}
if (subCategory) {
whereClauses.push('category = ?');
queryParams.push(subCategory);
}
if (searchQuery) {
whereClauses.push('(title LIKE ? OR description LIKE ?)');
queryParams.push(`%${searchQuery}%`, `%${searchQuery}%`);
}
const whereSql = whereClauses.length ? ('WHERE ' + whereClauses.join(' AND ')) : '';
// 查询总数
const countSql = `SELECT COUNT(*) AS total FROM products ${whereSql}`;
// 查询商品数据
const dataSql = `SELECT * FROM products ${whereSql} LIMIT ?, ?`;
// 先查询总数
db.query(countSql, queryParams, (err, countResults) => {
if (err) {
return response.fail(res, '查询商品总数失败', err);
}
const total = countResults[0].total;
// 查询商品列表,注意分页参数放到最后
db.query(dataSql, [...queryParams, offset, pageSize], (error, results) => {
if (error) {
return response.fail(res, '获取商品列表失败', error);
}
return response.success(res, {
products: results,
total
}, '商品列表获取成功');
});
});
};
// 获取商品详情
exports.getProductDetails = (req, res) => {
const {
id
} = req.params;
const query = `SELECT * FROM products WHERE id = ? AND status = 1`;
db.query(query, [id], (error, results) => {
if (error) {
return response.fail(res, '获取商品详情失败', error);
}
if (results.length === 0) {
return response.fail(res, '商品不存在');
}
return response.success(res, results[0], '商品详情');
});
};
// 删除商品
exports.deleteProduct = async (req, res) => {
const {
id
} = req.params;
const query = `DELETE FROM products WHERE id = ? AND user_id = ?`;
db.query(query, [id, req.user.id], (error, results) => {
if (error) {
return response.fail(res, '删除商品失败', error);
}
if (results.affectedRows === 0) {
return response.fail(res, '商品不存在或没有权限删除');
}
return response.success(res, {}, '商品删除成功');
});
};
// 更新商品
exports.updateProduct = async (req, res) => {
const {
id
} = req.params;
const {
title,
description,
price,
category,
images,
status
} = req.body;
if (!title || !description || !price || !category || !images) {
return response.fail(res, '缺少必要字段');
}
const query = `UPDATE products SET title = ?, description = ?, price = ?, category = ?, images = ?, status = ?
WHERE id = ? AND user_id = ?`;
db.query(query, [
title,
description,
price,
category,
JSON.stringify(images),
status,
id,
req.user.id,
], (error, results) => {
if (error) {
return response.fail(res, '更新商品信息失败', error);
}
if (results.affectedRows === 0) {
return response.fail(res, '商品不存在或没有权限更新');
}
return response.success(res, {}, '商品更新成功');
});
};
// 获取我发布的商品列表
exports.getMyProducts = (req, res) => {
const userId = req.user.id;
const query = `SELECT * FROM products WHERE user_id = ? ORDER BY created_at DESC`;
db.query(query, [userId], (error, results) => {
if (error) {
return response.fail(res, '获取我的商品列表失败', error);
}
return response.success(res, {
products: results
}, '获取我的商品列表成功');
});
};
路由注册( app.js
)
php
复制代码
const express = require('express');
const cors = require('cors');
const config = require('./config/config');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/user');
const errorHandler = require('./middleware/errorHandler');
const productRoutes = require('./routes/productRoutes');
require('./config/db');
const app = express();
app.use(cors());
app.use(express.json());
// 路由分组挂载
app.use('/api/products', productRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/user', userRoutes);
// ✅ 统一 404 错误格式
app.use((req, res) => {
res.status(404).json({
success: false,
message: '接口不存在',
error: 'Not Found'
});
});
// ✅ 错误处理中间件
app.use(errorHandler);
app.listen(config.PORT, () => {
console.log(`🚀 服务器已启动:http://localhost:${config.PORT}`);
});
✅四、控制器逻辑实现思路
1. 获取商品分类
-
- 从预设的静态分类数据中获取商品分类信息。
- 直接将分类数据返回前端,无需数据库操作。
2. 发布商品
- 目标:用户发布商品,提供商品的基本信息并将其保存到数据库。
- 思路:
-
- 从请求体中获取商品的各项信息(如标题、描述、价格、分类、图片等)。
- 校验必填字段是否完整,若缺少必要字段则返回错误提示。
- 插入商品数据到数据库,并关联当前用户的 ID。
- 如果插入成功,返回商品 ID,否则返回错误信息。
3. 获取商品列表
- 目标:返回符合条件的商品列表,支持分页和筛选。
- 思路:
-
- 获取请求中的筛选条件,如分类、关键词、页码等。
- 根据条件动态构建 SQL 查询语句,支持按分类、关键词等进行过滤。
- 分页查询:先计算符合条件的商品总数,再查询指定页码的数据。
- 返回商品列表和总记录数,便于前端展示分页。
4. 获取商品详情
-
- 根据请求中的商品 ID 查询商品详情。
- 确保查询的商品状态为"上架",避免返回已下架商品。
- 如果商品不存在或状态不符合要求,返回错误信息。
- 成功则返回商品的详细信息。
5. 删除商品
-
- 根据请求中的商品 ID 和当前登录用户的 ID,确认是否可以删除商品(即判断该商品是否是当前用户发布的)。
- 执行删除操作,如果商品不存在或没有权限删除,返回错误信息。
- 删除成功后返回成功消息。
6. 更新商品
-
- 获取请求中的商品 ID 和更新的数据。
- 校验字段是否完整,确保更新的字段有效且不为空。
- 确认当前用户是否为该商品的发布者,避免其他用户修改该商品。
- 执行更新操作,如果更新失败或没有权限,返回错误信息。
- 更新成功后返回成功消息。