微信小程序购物商城毕设:SSM+MySQL全链路实现与答辩避坑指南
简介这份资源面向计算机相关专业的毕业生与需要完成课程设计的学生提供一套可直接参考的购物商城小程序完整实现方案采用微信小程序前端、SSM后端与MySQL数据库组合开发适合具备Java与前端基础、希望快速搭建电商类毕设项目的学习者。压缩包共791个文件约35.98MB涵盖114个Java后端源码、140个Vue组件、20个wxss与19个wxml小程序页面文件以及数据库sql脚本、properties配置、json与xml资源等另附开题报告、论文视频与配套视频教程结构完整便于按模块查阅。目前已有175人学习下载。读者可从中获得从需求分析、数据库设计到前后端联调的完整赛题方案借助视频教程与论文材料理解商家星级、商品类型、商品信息、商品评价、订单与用户管理等模块的实现思路并参考目录组织与配置方式排查环境搭建中的常见问题降低毕设开发与文档撰写的门槛。1. 购物商城小程序毕设从跑通到讲清一套能扛住答辩的完整链路每年毕业季做微信小程序商城的学生里十个有八个卡在同一个地方代码能跑但说不清为什么这么写。导师问一句“你的 SSM 后端怎么处理并发下单”就哑了。这个标题背后其实是一条完整的工程链路——微信小程序做前端展示与交互SSMSpring 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 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表商城核心含分类、价格、库存 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表注意订单号用字符串避免自增 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时几个关键决策金额字段用DECIMAL(10,2)而不是FLOAT浮点数在累加时会出现精度丢失答辩时导师如果问“为什么不用 double”这就是得分点。订单号用字符串而非自增 ID避免用户从订单号推算出平台真实单量。商品表加status字段做逻辑删除而不是物理删除保留历史订单关联数据。注意MySQL 8.0 安装后如果命令行报ERROR 2002 (HY000)通常是服务没启动Windows 下执行net start mysql80Linux 下systemctl start mysqld即可。2.2 SSM 后端分层与核心配置SSM 的经典分层是 Controller → Service → MapperDAO每层职责分明。毕设答辩时导师最爱问“你的项目分层是怎么做的”答清楚这三层就够了。先看 Maven 依赖的核心部分!-- pom.xml 关键依赖 -- dependencies !-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.20/version /dependency !-- SpringMVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency !-- MyBatis 与 Spring 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependency !-- Druid 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.11/version /dependency /dependencies数据库连接配置放在jdbc.properties里不要硬编码在 XML 中jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/shop_mall?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的密码serverTimezoneAsia/Shanghai这个参数不加MySQL 8.0 会报时区错误这是血泪经验。useSSLfalse在本地开发时关掉避免证书警告。Spring 整合 MyBatis 的核心配置!-- applicationContext-dao.xml -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ !-- 连接池参数初始连接数、最大活跃数、等待超时 -- property nameinitialSize value5/ property namemaxActive value20/ property namemaxWait value60000/ /bean !-- Mapper 扫描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.shop.mapper/ /bean连接池参数maxActive20对毕设演示够用但如果答辩时被问到“并发上来怎么办”可以答生产环境会调到 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 groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.3.1/version /dependency// ProductServiceImpl.java Service public class ProductServiceImpl implements ProductService { Autowired private ProductMapper productMapper; Override public PageInfoProduct listByCategory(Integer categoryId, int pageNum, int pageSize) { // 开启分页紧跟其后的第一条查询会被自动分页 PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectByCategory(categoryId); return new PageInfo(list); } }!-- ProductMapper.xml -- select idselectByCategory resultTypecom.shop.entity.Product SELECT product_id, category_id, name, price, stock, main_image FROM t_product WHERE status 1 if testcategoryId ! null and categoryId ! 0 AND category_id #{categoryId} /if ORDER BY create_time DESC /selectPageHelper.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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;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, ListOrderItem 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 idreduceStock UPDATE t_product SET stock stock - #{quantity} WHERE product_id #{productId} AND stock #{quantity} /updateWHERE 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-methodsGET,POST,PUT,DELETE,OPTIONS allowed-headers* allow-credentialstrue/ /mvc:cors如果用了拦截器确保拦截器放行 OPTIONS 预检请求否则跨域配置不生效。4.2 MySQL 时区错误导致插入时间差 8 小时现象订单创建时间是凌晨 2 点数据库里存的是前一天 18 点。原因JDBC URL 没加serverTimezoneMySQL 用了 UTC 时区。解决URL 中加serverTimezoneAsia/Shanghai同时检查 MySQL 全局时区SHOW VARIABLES LIKE %time_zone%如果不是08:00在 my.cnf 中加default-time-zone08: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?pageNum1pageSize10把结果里的Requests per second、Time per request、Failed requests三个指标记下来。如果 QPS 只有几十就诚实说“当前配置下 QPS 约 XX瓶颈在数据库查询后续可以加 Redis 缓存优化”。导师要的不是高并发是你知道瓶颈在哪、怎么优化。5.2 论文里怎么描述 SSM 分层论文不要贴大段代码用一张分层架构表说清楚每层职责层级职责核心类/文件Controller接收请求、参数校验、返回结果ProductController、OrderControllerService业务逻辑、事务控制ProductServiceImpl、OrderServiceImplMapper数据库 CRUDProductMapper.xml、OrderMapper.xmlEntity数据载体Product、Order、UserUtils工具类JwtUtil、DateUtil这张表放在论文“系统设计”章节比贴 200 行代码管用。5.3 视频教程怎么用才不浪费时间配套视频教程不要从头看到尾。我的习惯是先自己跑代码跑不通了再定位到视频里对应的章节看。重点看三个部分——环境搭建、数据库建表、登录流程。这三块跑通剩下的业务代码照着抄改就行。视频里如果用了和你不一样的 MySQL 版本或 IDEA 版本不用换按自己的来遇到报错再搜。5.4 一个我踩过的坑开题报告和论文的技术栈要一致开题报告里写了“使用 Redis 做缓存”论文里却只字未提答辩时被导师追问“你的 Redis 用在哪了”。要么开题报告别写要么论文里补上。毕设的每一处技术名词都要能在代码里找到对应实现这是最基本的自洽。希望帮到你。本文还有配套的精品资源点击获取