简介:这份资源面向计算机相关专业的毕业生与需要完成课程设计的学生,提供一套可直接参考的购物商城小程序完整实现方案,采用微信小程序前端、SSM后端与MySQL数据库组合开发,适合具备Java与前端基础、希望快速搭建电商类毕设项目的学习者。压缩包共791个文件,约35.98MB,涵盖114个Java后端源码、140个Vue组件、20个wxss与19个wxml小程序页面文件,以及数据库sql脚本、properties配置、json与xml资源等,另附开题报告、论文视频与配套视频教程,结构完整便于按模块查阅。目前已有175人学习下载。读者可从中获得从需求分析、数据库设计到前后端联调的完整赛题方案,借助视频教程与论文材料理解商家星级、商品类型、商品信息、商品评价、订单与用户管理等模块的实现思路,并参考目录组织与配置方式排查环境搭建中的常见问题,降低毕设开发与文档撰写的门槛。
1. 购物商城小程序毕设:从跑通到讲清,一套能扛住答辩的完整链路
每年毕业季,做微信小程序商城的学生里,十个有八个卡在同一个地方:代码能跑,但说不清为什么这么写。导师问一句“你的 SSM 后端怎么处理并发下单”,就哑了。这个标题背后其实是一条完整的工程链路——微信小程序做前端展示与交互,SSM(Spring + SpringMVC + MyBatis)做后端服务,MySQL 做数据持久化,再配上开题报告、论文和视频教程,构成一份能交付、能演示、能答辩的毕设作品。它适合正在选毕设题目的本科生,也适合已经选了商城方向但不知道怎么把代码讲出逻辑的人。接下来我按实际搭建顺序,把每一步拆开讲清楚,包括参数怎么设、坑在哪、答辩时怎么解释你的技术选型。
2. 环境搭建与项目骨架:先把 MySQL 和 SSM 跑起来
2.1 MySQL 安装配置与建库建表
毕设环境最怕的是“在我电脑上能跑”。MySQL 版本选择上,我一般推荐 5.7 或 8.0,两者在商城场景下差异不大,但 8.0 的默认字符集是 utf8mb4,省去很多中文乱码的麻烦。安装时记住 root 密码,后面 SSM 的 JDBC 配置要用。
安装完成后,用命令行或 MySQL Workbench 建库:
-- 创建数据库,字符集用 utf8mb4 支持 emoji 和特殊字符 CREATE DATABASE shop_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE shop_mall; -- 用户表:存微信 openid、昵称、头像等 CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE COMMENT '微信用户唯一标识', nickname VARCHAR(64) DEFAULT '', avatar_url VARCHAR(512) DEFAULT '', phone VARCHAR(20) DEFAULT '', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 商品表:商城核心,含分类、价格、库存 CREATE TABLE t_product ( product_id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(128) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, main_image VARCHAR(512) DEFAULT '', detail TEXT, status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单表:注意订单号用字符串,避免自增 ID 暴露业务量 CREATE TABLE t_order ( order_id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, pay_status TINYINT DEFAULT 0 COMMENT '0未支付 1已支付 2已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;建表时几个关键决策:金额字段用DECIMAL(10,2)而不是FLOAT,浮点数在累加时会出现精度丢失,答辩时导师如果问“为什么不用 double”,这就是得分点。订单号用字符串而非自增 ID,避免用户从订单号推算出平台真实单量。商品表加status字段做逻辑删除,而不是物理删除,保留历史订单关联数据。
注意:MySQL 8.0 安装后如果命令行报
ERROR 2002 (HY000),通常是服务没启动,Windows 下执行net start mysql80,Linux 下systemctl start mysqld即可。
2.2 SSM 后端分层与核心配置
SSM 的经典分层是 Controller → Service → Mapper(DAO),每层职责分明。毕设答辩时导师最爱问“你的项目分层是怎么做的”,答清楚这三层就够了。
先看 Maven 依赖的核心部分:
<!-- pom.xml 关键依赖 --> <dependencies> <!-- Spring 核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.20</version> </dependency> <!-- SpringMVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <!-- Druid 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version> </dependency> </dependencies>数据库连接配置放在jdbc.properties里,不要硬编码在 XML 中:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shop_mall?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码serverTimezone=Asia/Shanghai这个参数不加,MySQL 8.0 会报时区错误,这是血泪经验。useSSL=false在本地开发时关掉,避免证书警告。
Spring 整合 MyBatis 的核心配置:
<!-- applicationContext-dao.xml --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 连接池参数:初始连接数、最大活跃数、等待超时 --> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> <property name="maxWait" value="60000"/> </bean> <!-- Mapper 扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.shop.mapper"/> </bean>连接池参数maxActive=20对毕设演示够用,但如果答辩时被问到“并发上来怎么办”,可以答:生产环境会调到 50-100,并配合 Redis 缓存热点商品数据。
2.3 微信小程序端请求封装
小程序端不要在每个页面里写wx.request,封装成统一模块。这是导师看代码时判断你工程素养的直接依据。
// utils/request.js const BASE_URL = 'http://localhost:8080/shop'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { // token 过期,跳转登录 wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { request };这段封装做了三件事:统一拼接 baseURL、统一携带 token、统一处理 401 和业务错误码。小程序顶部导航栏高度在不同机型上不一致,如果要做自定义导航栏,用wx.getSystemInfoSync().statusBarHeight获取状态栏高度,再叠加 44px 的标题栏高度,这是常见做法。
3. 商城核心业务实现:商品、购物车、订单三件套
3.1 商品列表分页与分类筛选
商品列表是商城小程序的流量入口,分页查询必须做,否则数据量一上来小程序直接卡死。后端用 MyBatis 的分页插件 PageHelper:
<!-- pom.xml 加入 PageHelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.1</version> </dependency>// ProductServiceImpl.java @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public PageInfo<Product> listByCategory(Integer categoryId, int pageNum, int pageSize) { // 开启分页,紧跟其后的第一条查询会被自动分页 PageHelper.startPage(pageNum, pageSize); List<Product> list = productMapper.selectByCategory(categoryId); return new PageInfo<>(list); } }<!-- ProductMapper.xml --> <select id="selectByCategory" resultType="com.shop.entity.Product"> SELECT product_id, category_id, name, price, stock, main_image FROM t_product WHERE status = 1 <if test="categoryId != null and categoryId != 0"> AND category_id = #{categoryId} </if> ORDER BY create_time DESC </select>PageHelper.startPage()必须紧挨着查询语句,中间不能插入其他数据库操作,否则分页会作用到错误的查询上。这个坑我在调试时踩过,分页数据对不上,排查了半天才发现是中间多了一行查询。
小程序端用onReachBottom做上拉加载:
// pages/goods/list.js Page({ data: { list: [], pageNum: 1, hasMore: true }, onLoad() { this.loadData(); }, loadData() { if (!this.data.hasMore) return; request({ url: '/product/list', data: { categoryId: this.data.categoryId, pageNum: this.data.pageNum, pageSize: 10 } }).then(res => { this.setData({ list: this.data.list.concat(res.list), pageNum: this.data.pageNum + 1, hasMore: res.hasNextPage }); }); }, onReachBottom() { this.loadData(); } });3.2 购物车与订单事务处理
购物车在小程序端可以存本地 Storage,也可以存服务端。毕设推荐存服务端,因为答辩时“购物车数据持久化”是一个加分项。核心表结构:
CREATE TABLE t_cart ( cart_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;UNIQUE KEY uk_user_product保证同一用户对同一商品只有一条购物车记录,加购时用INSERT ... ON DUPLICATE KEY UPDATE quantity = quantity + 1实现数量累加。
下单是最容易翻车的环节。用户点击“提交订单”后,后端要做三件事:扣库存、生成订单、清购物车。这三步必须在同一个事务里:
@Service public class OrderServiceImpl implements OrderService { @Autowired private ProductMapper productMapper; @Autowired private OrderMapper orderMapper; @Autowired private CartMapper cartMapper; @Override @Transactional(rollbackFor = Exception.class) public String createOrder(Integer userId, List<OrderItem> items) { // 1. 扣库存:用乐观锁防止超卖 for (OrderItem item : items) { int affected = productMapper.reduceStock(item.getProductId(), item.getQuantity()); if (affected == 0) { throw new RuntimeException("商品库存不足:" + item.getProductId()); } } // 2. 生成订单 String orderNo = generateOrderNo(); Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(calcTotal(items)); orderMapper.insert(order); // 3. 清空已下单的购物车记录 cartMapper.deleteByUserAndProducts(userId, items); return orderNo; } }<!-- ProductMapper.xml 乐观锁扣库存 --> <update id="reduceStock"> UPDATE t_product SET stock = stock - #{quantity} WHERE product_id = #{productId} AND stock >= #{quantity} </update>WHERE stock >= #{quantity}就是乐观锁的核心——只有库存足够时才更新,返回影响行数为 0 说明库存不足。这比SELECT ... FOR UPDATE性能好,答辩时能讲清楚这个区别,导师基本不会追问了。
3.3 微信登录与 Token 鉴权
小程序登录流程:wx.login()拿到 code → 传给后端 → 后端调微信接口换 openid → 生成 token 返回小程序。毕设中为了简化,可以跳过真实调用微信接口,用 code 模拟 openid,但流程要写完整。
@RestController @RequestMapping("/auth") public class AuthController { @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 实际项目调微信接口:https://api.weixin.qq.com/sns/jscode2session // 毕设演示可用 code 模拟 openid String openid = "mock_" + dto.getCode(); User user = userService.findByOpenid(openid); if (user == null) { user = userService.createUser(openid); } // 生成 token(毕设用 JWT 或 UUID 均可) String token = UUID.randomUUID().toString(); redisTemplate.opsForValue().set("token:" + token, user.getUserId(), 7, TimeUnit.DAYS); return Result.success(new LoginVO(token, user)); } }Token 存 Redis 并设 7 天过期,小程序端存在 Storage 里,每次请求带在 header 中。后端用拦截器统一校验:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("token"); if (token == null || !redisTemplate.hasKey("token:" + token)) { response.setStatus(401); return false; } return true; } }4. 避坑与排查:毕设商城最容易翻车的 5 个地方
4.1 跨域问题导致小程序请求全部失败
现象:小程序开发者工具里请求后端接口,控制台报request:fail或 403,但浏览器直接访问接口正常。
原因:小程序请求不受浏览器同源策略限制,但 SpringMVC 默认没有配置 CORS,或者配置了但拦截器顺序不对。
解决:在 SpringMVC 配置中加 CORS 映射,注意要放在拦截器之前生效:
<mvc:cors> <mvc:mapping path="/**" allowed-origins="*" allowed-methods="GET,POST,PUT,DELETE,OPTIONS" allowed-headers="*" allow-credentials="true"/> </mvc:cors>如果用了拦截器,确保拦截器放行 OPTIONS 预检请求,否则跨域配置不生效。
4.2 MySQL 时区错误导致插入时间差 8 小时
现象:订单创建时间是凌晨 2 点,数据库里存的是前一天 18 点。
原因:JDBC URL 没加serverTimezone,MySQL 用了 UTC 时区。
解决:URL 中加serverTimezone=Asia/Shanghai,同时检查 MySQL 全局时区SHOW VARIABLES LIKE '%time_zone%',如果不是+08:00,在 my.cnf 中加default-time-zone='+08:00'。
4.3 图片上传后小程序显示空白
现象:后台上传商品图片成功,数据库里也有路径,但小程序<image>标签显示空白。
原因:小程序要求图片地址必须是 HTTPS,且域名要在小程序后台配置白名单。本地开发时http://localhost不被允许。
解决:开发阶段在微信开发者工具中勾选“不校验合法域名”,生产环境必须配 HTTPS 域名。另外检查图片路径拼接是否正确,常见错误是数据库存了相对路径但小程序端没拼 baseURL。
4.4 订单重复提交
现象:用户快速点击“提交订单”两次,生成了两笔相同订单。
原因:前端没做防抖,后端没做幂等。
解决:前端按钮点击后立即置灰,后端用 Redis 做幂等——以userId + 商品ID列表为 key,设置 5 秒过期,重复请求直接返回上次订单号。这是毕设答辩时能体现工程思维的细节。
4.5 PageHelper 分页失效返回全部数据
现象:调用了PageHelper.startPage(),但返回的还是全部数据。
原因:startPage后面没有紧跟查询语句,中间插了其他数据库操作;或者 Mapper 返回的是List但被手动包装成了PageInfo之外的类型。
解决:确保startPage和查询之间没有其他 DB 调用。如果用了多数据源,PageHelper 需要配置helperDialect参数。
5. 答辩加分项:把技术选型讲成故事,而不是背文档
5.1 用数据说话:压测你的接口
答辩时导师问“你的系统能扛多少并发”,不要只说“应该没问题”。用 JMeter 或 Apache Bench 跑一组数据:
# 用 ab 压测商品列表接口,100 并发,总共 1000 次请求 ab -n 1000 -c 100 http://localhost:8080/shop/product/list?pageNum=1&pageSize=10把结果里的Requests per second、Time per request、Failed requests三个指标记下来。如果 QPS 只有几十,就诚实说“当前配置下 QPS 约 XX,瓶颈在数据库查询,后续可以加 Redis 缓存优化”。导师要的不是高并发,是你知道瓶颈在哪、怎么优化。
5.2 论文里怎么描述 SSM 分层
论文不要贴大段代码,用一张分层架构表说清楚每层职责:
| 层级 | 职责 | 核心类/文件 |
|---|---|---|
| Controller | 接收请求、参数校验、返回结果 | ProductController、OrderController |
| Service | 业务逻辑、事务控制 | ProductServiceImpl、OrderServiceImpl |
| Mapper | 数据库 CRUD | ProductMapper.xml、OrderMapper.xml |
| Entity | 数据载体 | Product、Order、User |
| Utils | 工具类 | JwtUtil、DateUtil |
这张表放在论文“系统设计”章节,比贴 200 行代码管用。
5.3 视频教程怎么用才不浪费时间
配套视频教程不要从头看到尾。我的习惯是:先自己跑代码,跑不通了再定位到视频里对应的章节看。重点看三个部分——环境搭建、数据库建表、登录流程。这三块跑通,剩下的业务代码照着抄改就行。视频里如果用了和你不一样的 MySQL 版本或 IDEA 版本,不用换,按自己的来,遇到报错再搜。
5.4 一个我踩过的坑:开题报告和论文的技术栈要一致
开题报告里写了“使用 Redis 做缓存”,论文里却只字未提,答辩时被导师追问“你的 Redis 用在哪了”。要么开题报告别写,要么论文里补上。毕设的每一处技术名词,都要能在代码里找到对应实现,这是最基本的自洽。
希望帮到你。
本文还有配套的精品资源,点击获取