当前位置: 首页 > news >正文

《校园生活平台从 0 到 1 的搭建》第五篇:商品后端

一、功能目标(FUNCTION GOALS)

本次后端开发的目标是构建一个商品管理平台,提供商品的管理和展示功能,目标如下:

  1. 商品 CRUD 操作
    • 支持商品的创建、读取、更新、删除功能。
    • 用户能够发布新商品,修改商品信息,删除自己发布的商品。
  1. 商品列表展示
    • 提供商品列表接口,支持分页和筛选,用户可以查看所有上架商品。
  1. 商品分类管理
    • 提供商品分类的静态数据接口,供前端展示分类选项,支持商品按照分类进行筛选。
  1. 收藏功能支持
    • 用户可以收藏商品、取消收藏,查看自己的收藏商品。
  1. 权限控制
    • 所有商品管理操作(发布、修改、删除)都需要登录验证,确保只有授权用户能够执行这些操作。
  1. 数据安全与一致性
    • 确保每个操作都具备适当的错误处理和反馈,避免非法操作和数据不一致的情况发生。

✅ 二、后端模块目录结构规划(新增部分)

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

字段名类型说明
idINT, PK, AUTO_INCREMENT商品ID
user_idINT发布者ID(外键 users.id)
titleVARCHAR(100)标题
descriptionTEXT描述
priceDECIMAL(10,2)价格
categoryVARCHAR(50)分类(前端从 categoryList.js选取)
imagesTEXT图片(JSON字符串数组)
statusTINYINT状态(1=上架,0=下架)
created_atDATETIME发布时间
updated_atDATETIME更新时间

3.创建表的 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

const categories = [{name: '电子数码',children: ['手机', '平板', '电脑', '耳机', '相机', '游戏设备']},{name: '学习资料',children: ['教材', '课本', '教辅', '笔记', '文具', '四六级资料']},{name: '生活用品',children: ['床上用品', '收纳用品', '衣架', '杯子', '雨伞', '电风扇']},{name: '衣物鞋包',children: ['男装', '女装', '鞋子', '包包', '配饰']},{name: '运动健身',children: ['球类', '运动鞋', '瑜伽垫', '健身器材']},{name: '美妆护肤',children: ['护肤品', '彩妆', '香水', '洗护用品']},{name: '校园服务',children: ['代拿快递', '跑腿服务', '打印复印', '拼车', '课程辅导', '代购', '租赁服务']},{name: '其他',children: ['未分类']}
];
module.exports = categories;

路由定义routes/productRoutes.js

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

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

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 和更新的数据。
    • 校验字段是否完整,确保更新的字段有效且不为空。
    • 确认当前用户是否为该商品的发布者,避免其他用户修改该商品。
    • 执行更新操作,如果更新失败或没有权限,返回错误信息。
    • 更新成功后返回成功消息。

http://www.lryc.cn/news/605029.html

相关文章:

  • Qt 嵌入式 Linux 系统定制全指南
  • Nuxt3 全栈作品【通用信息管理系统】用户管理(含重置密码)
  • 第十二天:C++ 标准库函数分类总结
  • spark入门-helloword
  • 干货 | ANSYS复合材料前后处理
  • 跨云部署实战:前端、后端 + RSYNC、全栈场景统一落地方案
  • Nestjs框架: 关于 OOP / FP / FRP 编程
  • Map 集合
  • 高可靠液晶屏系统解决方案深度解析
  • AI 驱动的软件测试革新:框架、检测与优化实践
  • 原生C++实现信号与槽机制:原理详解
  • 如何选择GEO优化公司哪家好?
  • Apache FOP实践——pdf模板引擎
  • 推扫式和凝视型高光谱相机分别采用哪些分光方式?
  • MaxKB+MinerU:通过API实现PDF文档解析并存储至知识库
  • 梳理Ego-Planner模式下5通道、6通道与无人机模式的关系
  • Camera相机人脸识别系列专题分析之十九:MTK ISP6S平台FDNode传递三方FFD到APP流程解析
  • 不可变类字段修复建议
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘dash’问题
  • Python 程序设计讲义(43):组合数据类型——元组类型:元组的常用操作
  • WSL2搭建基于Docker的ESP32开发环境
  • 机器学习项目完整流程详解
  • 基于C-MTEB/CMedQAv2-rerankingv的Qwen3-1.7b模型微调-demo
  • Android基础(二)了解Android项目
  • 端侧大模型迎来“轻”革命!移远通信 × RWKV 打造“轻量AI大脑”
  • 单片机电路基础
  • 【NCS随笔】如何在hello_world添加蓝牙功能(一)
  • sqli-labs:Less-7关卡详细解析
  • 国内数据集成厂商有哪些?如何选择最适合的数据集成平台?
  • Qt 与物联网(IoT)开发