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

一、功能目标(FUNCTION GOALS)

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

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

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

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

相关推荐
SoaringHeart6 分钟前
Flutter小技巧:IM音浪效果实现
前端·flutter
小old弟7 分钟前
亲测autojs自动化,关闭应用的三种方法
前端
AndyLaw7 分钟前
我用 ChatGPT 起手、脚本改造,给孩子做了一个绘本
前端·javascript·openai
放空欧巴7 分钟前
学习 elpis 有感 -- 初识 elpis-core (实现简易版 Egg.js)
前端
柊二三9 分钟前
关于项目的一些完善功能
java·数据库·后端·spring
前端开发呀9 分钟前
震惊!开启浏览器翻译竟会导致react应用报错?
前端·react.js
Sun_light11 分钟前
从 0 到 1 实现低代码编辑器的基本功能
前端·react.js·typescript
WildBlue12 分钟前
从 0 到 1 上手 React 中的 mitt,前端小白也能秒懂!🤓
前端·react.js·前端框架
星河那美18 分钟前
使用vis-timeline 完成时间轴事件追踪表
前端·vue.js