SSM+微信小程序商品展示项目实战:从建表到接口联调

SSM+微信小程序商品展示项目实战:从建表到接口联调 简介基于SSM与微信小程序的商品展示毕业设计项目面向Java方向毕业生及课程设计学生定位为可直接复用的高分参考方案。项目已获导师指导认可答辩评审分达97分并在Windows10/11测试环境下严格调试下载后按部署说明即可运行。资源共920个文件压缩包约22.89MB涵盖Java后端与Vue管理端源码、微信小程序前端页面WXML/WXSS/JS、SQL数据库脚本、JSON/XML配置以及PPT答辩演示文稿、MP4演示视频和使用文档图片素材以PNG/SVG为主目录结构清晰完整。除业务源码外还附带一键安装、启动、构建等部署脚本以及必要的环境配置与说明文档便于快速启动和二次开发。目前已有88人学习下载适合需要参考完整项目流程、准备毕业答辩或课程设计展示的读者使用。1. 一个毕业设计标题里藏着的完整工程化链路把“基于SSM微信小程序的商品展示”压缩进一个zip包涵盖源码、数据库、PPT、使用文档和演示视频这已经是高分毕业设计的标准交付形态。商品展示看起来是“列表详情”的简易业务可它踩中的恰好是Java开发者在真实工作中最高频的四种能力SSM框架的分层整合、MySQL表设计、小程序端接口联调、以及把一套代码跑通并讲清楚的能力。这篇文章不会带你解压某个神秘压缩包而是顺着标题所代表的通用项目形态把“如果我要从零完成这样一个作品”的每一步还原出来——包括后台怎么写、数据库怎么建、小程序怎么调接口、答辩时会被追问哪些参数。新手可以照着落地有三年以上经验的人也能在这套方案里找到与生产环境的差异和值得改进的边界。2. 为什么是SSM配微信小程序架构职责与请求链路2.1 SSM三件套在商品展示项目里的分工边界SSM是Spring、SpringMVC、MyBatis三个框架的缩写组合在Java毕业设计中的历史地位很像前端界的jQuery——老但稳定资料多答辩老师熟悉。正因为熟悉它反而不容易出错。三个框架的分工非常清晰。Spring负责对象管理和事务控制解决的是“谁来创建Service、谁来管理数据库连接”的容器问题SpringMVC负责Web层的请求路由前端小程序发来的HTTP请求由它决定落到哪个Controller方法MyBatis负责数据库的访问映射把Java方法和Mapper XML里的SQL语句绑定起来。商品展示这类以查询为主、写操作为辅的业务恰好把MyBatis的半自动映射优势放大了——你可以精确控制每条SQL也可以让返回结果自动映射到商品实体类。在商品展示场景里我通常建议把业务拆成这样几层层主要职责常见的错误做法Controller层接收小程序请求、校验参数、封装返回JSON把SQL写在Controller里Service层事务边界、业务规则判断、组装多表数据只有一层空壳、直接透传DAODAO/Mapper层单表或关联查询、分页、条件动态SQL一个大XML查所有混乱实体类与表字段对应的POJO包含商品、分类、用户等把数据库实体和VO混为一谈这四层是SSM项目的骨架子答辩时老师常问的第一句话基本是“MVC分别对应你项目里的哪些类”——能指着代码逐层说清楚这类项目的基础分就稳了。2.2 商品展示为什么常选小程序端而非Web端商品展示的载体从PC网页、H5到小程序都有。近年来的毕业设计高频选择是小程序端原因不仅是“新技术加分”更重要的是小程序形态更贴合真实购物场景入口轻、不需要下载App、适合逛商品。从开发角度微信小程序用的是自家的WXML和WXSS语法上有Vue的影子但不完全相同。逻辑上小程序页面是单向数据流Page的data对象驱动视图渲染用户操作触发事件处理函数事件里再调wx.request去访问Java后端接口。这和传统的JSP页面直接渲染完全不一样和Vue单页应用也不一样因为小程序的运行环境是独立的后端接口只通过HTTPS协议访问。对SSM后端来说它并不关心前端是微信小程序还是安卓App。后端只需要遵循一个约定返回固定结构的数据格式通常包含状态码、消息和数据体。前端拿到后判断code 200就渲染数据否则弹出错误提示。这就是前后端分离的思路。2.3 一次商品列表请求从点击到渲染的完整生命周期我们要在小程序里看到商品列表背后至少要经过七个环节。理解了这条链路后面写代码和答辩都顺。小程序页面的onLoad生命周期里调用wx.request请求地址形如https://localhost:8080/ssm-product/api/product/list?categoryId1pageNum1微信客户端将请求发送到后端服务器SpringMVC的DispatcherServlet接收HandlerMapping根据URL和注解匹配到对应的ProductController.list()方法Controller调ProductService.list(categoryId, pageNum)Service内部调ProductMapper.selectList接口MyBatis根据Mapper的XML文件执行SQL从MySQL读出商品表数据映射到Product对象列表Service返回ListProductController把它封装成ResultData.success(data)统一结构后端把JSON返回给小程序wx.request的success回调里取到data执行setData更新视图// RestController返回统一JSONSpringMVC自动做对象转JSON RestController RequestMapping(/api/product) public class ProductController { Autowired private ProductService productService; GetMapping(/list) public ResultData list(RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize, RequestParam(required false) Integer categoryId) { PageInfoProduct page productService.pageQuery(pageNum, pageSize, categoryId); return ResultData.success(page); } }RestController是关键——它让返回值直接以JSON写回响应体而不是走视图解析器。PageHelper插件的PageInfo对象里包含了list、total、pageNum、pages等属性小程序端只需要result.data.list就能拿到当前页商品数组。3. 商品展示的MySQL数据建模从表关系落到可跑通的SQL3.1 商品展示最少需要哪几张表商品展示的业务范围决定表规模。不做购物车和订单的纯展示型项目三张表就能支撑商品表、商品分类表、轮播图表。但如果要凑“高分优秀项目”我建议再加两张表用户表含收藏关系和商品参数表。原因很简单——相关性表能在答辩时展开讲“多表联合查询”和“索引设计”这是评分里拉开差距的地方。商品展示场景涉及的核心表关系和职责如下表名关键字段要点与商品表的关系用途categoryid, name, sort一对多商品分类轮播图可挂在分类上productid, category_id, name, main_image, price, stock, status, sales_count主表商品详情、上下架状态bannerid, product_id, image_url, sort_order多对一首页轮播推荐位product_paramid, product_id, param_name, param_value一对多商品规格参数如颜色、尺码userid, openid, nickname, avatar无小程序授权用户便于收藏数据落库favoriteid, user_id, product_id, create_time多对多中间表用户收藏商品关系用户表加不加取决于答辩预期。加了就要设计完整注册登录体系处理微信登录的code换openid逻辑工作量多出两三天。我见过不少项目强行加购物车和订单表结果商品展示这个主业务反而没做扎实。与其贪大不如把商品模块做深——价格排序、关键字搜索、库存为0自动隐藏、推荐位管理都比一个残缺的订单流程好。3.2 建库建表SQL脚本字段类型与状态值的取舍商品表是整库的核心。设计时最容易犯的错误是“字段全用int/varchar一把梭”价格用double、描述超长用varchar(255)、状态用乱七八糟的字符串。正确做法是价格用decimal(10,2)避免二进制浮点误差描述用text或varchar(2000)上下架状态用tinyint存0和1删除标记统一用is_deleted。CREATE DATABASE IF NOT EXISTS ssm_product DEFAULT CHARSET utf8mb4; USE ssm_product; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 分类ID, name VARCHAR(50) NOT NULL COMMENT 分类名称, parent_id INT DEFAULT 0 COMMENT 父分类ID顶级为0, sort_order INT DEFAULT 0 COMMENT 排序值越小越靠前, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT商品分类表; CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 商品ID, category_id INT NOT NULL COMMENT 所属分类ID, name VARCHAR(120) NOT NULL COMMENT 商品标题, subtitle VARCHAR(255) COMMENT 副标题/卖点, main_image VARCHAR(500) NOT NULL COMMENT 主图URL, price DECIMAL(10,2) NOT NULL COMMENT 售价, original_price DECIMAL(10,2) COMMENT 原价/划线价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, sales_count INT NOT NULL DEFAULT 0 COMMENT 销量, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, detail TEXT COMMENT 富文本详情, is_deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category_status (category_id, status), KEY idx_sales (sales_count), CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB COMMENT商品表;KEY idx_category_status (category_id, status)是商品列表查询的索引核心。小程序端最频繁的请求是“点击某个分类查看商品列表”SQL的WHERE条件通常是WHERE category_id ? AND status 1这个联合索引能让查询在商品数据量到几千条时依然保持毫秒级响应。fk_product_category外键有没有必要建查操作多、并发写少的毕业设计项目建议保留到企业级高并发场景再考虑去掉外键由应用层保证一致性。3.3 初始化数据与分页查询让演示视频里好看的数据从哪来数据库里只有空表演示视频会非常难看。通常的做法是写一个data.sql或直接用Navicat导入三五十条商品数据覆盖5个以上分类、每类6到10个商品主图尽可能使用真实感强的图片URL。分类数据要设计成有层级的比如“手机数码”下面有“手机壳”“充电器”“数据线”这样小程序首页的分类列表展示更有层次感。分页查询是必考点。不用PageHelper时MySQL的手写分页要写出LIMIT #{offset}, #{pageSize}并先count再list用PageHelper时只需在Service层调用PageHelper.startPage(pageNum, pageSize)随后紧跟执行查询即可。两者对比可以记这一点手写分页更透明但不便统计总数PageHelper自动拦截SQL生成count语句代码更简洁但需要引入com.github.pagehelper:pagehelper-spring-boot-starter依赖。4. 商品展示后端与小程序联调核心代码代码与接口约定4.1 后端实现的最佳顺序代码写的顺序比代码本身更值得讲。我见过很多人一上来就写Controller然后发现Service没有、Mapper没有、表也还没建写一步卡一步。正确的顺序是先设计数据库表 → 生成实体类和Mapper接口 → 写Mapper的XML → 写Service接口和实现类 → 写Controller → 用Postman测试 → 最后写小程序端。实体类与数据库表的映射在MyBatis里有两种形态。字段名和属性名一致时自动映射不一致时要在Mapper XML里写resultMap。商品表字段用的是下划线风格如category_idJava属性是驼峰categoryId所以核心配置里要开启驼峰映射mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entitymap-underscore-to-camel-case: true是SSM项目里极容易漏掉的一行配置。漏掉后的现象是查询结果里所有带下划线的字段全是null而用户和name这种单字字段正常。面试题里也经常出现这个知识点——很多人八股背得熟实际遇到null时却想不到是映射问题。4.2 商品搜索和分类筛选的Mapper SQLMapper层是商品展示项目的技术核心。分类筛选要支持多条件组合分类ID可以为空、关键字可以模糊匹配、排序方式可以是价格升序或销量降序。MyBatis的动态SQL在这里发挥最大价值。select idselectProductList resultTypecom.example.entity.Product SELECT id, category_id, name, main_image, price, sales_count, stock FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR subtitle LIKE CONCAT(%, #{keyword}, %)) /if AND status 1 AND is_deleted 0 /where choose when testorderBy price_asc ORDER BY price ASC /when when testorderBy price_desc ORDER BY price DESC /when when testorderBy sales_desc ORDER BY sales_count DESC /when otherwise ORDER BY id DESC /otherwise /choose LIMIT #{offset}, #{pageSize} /selectwhere标签会自动去掉第一个AND前缀这是动态SQL处理多条件最优雅的方式。choose实现排序方式的切换逻辑上等价于if-else但避免了多条SQL分支。模糊搜索用CONCAT(%, #{keyword}, %)而不是%${keyword}%后者会引发SQL注入——使用${}拼接时用户输入 OR 11 --就能直接绕过条件查出全表这在任何Java面试八股文里都是送分题在真实代码里是致命伤。4.3 小程序端请求封装与商品列表渲染小程序连接后端接口核心就一个wx.request方法但它有细节。所有请求要封装到一个统一的request工具里处理baseURL、请求头、超时和错误提示页面里不直接调用微信API而是调用这个封装方法。这是小程序工程化的基础门槛。// utils/request.js const BASE_URL http://localhost:8080/ssm-product; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, timeout: 8000, header: { Content-Type: application/json }, success: (res) { if (res.data.code 200) { resolve(res.data.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, BASE_URL };小程序页面加载商品列表的流程和Vue项目类似。页面onLoad时拿参数categoryId调用request(/api/product/list, GET, { categoryId, pageNum: 1, pageSize: 10 })then回调里setData。这里有一个关键点request返回的是Promise而不是直接在success里操作页面所以setData的时机完全可控不会出现页面销毁后还在更新数据的报错。商品列表的触底加载是每次演示视频的亮点。在页面json里配置onReachBottomDistance: 100然后在Page里实现onReachBottom判断pageNum totalPages时继续加载下一页并把新数据追加到原数组——注意要用concat而不是直接赋值否则会丢掉已渲染的数据。4.4 首页轮播图与商品详情页的数据组织首页除了商品列表还有轮播图轮播图数据来自banner表。一个常见的接口设计是一次请求同时返回轮播图列表和推荐商品列表减少小程序请求次数。后端用Map组装两个数据结构前端一次wx.request就能拿全。GetMapping(/home) public ResultData home() { ListBanner banners bannerService.findValidBanners(); ListProduct recommendList productService.findRecommendList(6); MapString, Object data new HashMap(); data.put(bannerList, banners); data.put(recommendList, recommendList); return ResultData.success(data); }商品详情页需要商品基本信息、参数列表和推荐商品三个数据块。如果拆成三个接口小程序详情页就要嵌套三个请求非常慢合并成一个详情接口返回Map是最常见做法。细节上要注意详情页的商品参数用ListProductParam而不是一个拼接字符串因为前端需要用wx:for渲染成表格样式结构化的列表数据比一个逗号分隔的字符串好处理得多。5. 跑通zip里的项目后先把这3个坑排掉5.1 小程序只能访问HTTPS但本地开发怎么办微信小程序正式环境的wx.request只允许HTTPS域名这在部署阶段卡住过很多人。本地联调时开发工具里勾选“不校验合法域名”才能访问http://localhost:8080。流程是在微信开发者工具的右上角“详情” → “本地设置” → 勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。很多人卡在测试阶段多半是这一步没有勾选或勾完没重启开发者工具。等到真正部署上线则需要用备案过的HTTPS域名替换BASE_URL并把该域名配置到小程序后台的request合法域名白名单里两者缺一不可。5.2 数据库连接配置适配不同机器压缩包里带的application.properties或db.properties里的数据库连接通常指向作者本机的用户名密码最常见的是root/123456和localhost:3306。拿别人的项目第一次跑90%的报错都是数据库连接失败。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ssm_product?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456serverTimezoneAsia/Shanghai是另一个高频事故点。MySQL 8.x的驱动默认时区是UTC不设置该参数会报The server time zone value异常。用Asia/Shanghai而不是GMT%2B8可读性更好也避免部分环境对号解析出错。5.3 答辩演示前的数据与环境检查清单演示视频里最怕出现的情况是6秒的加载转圈、空白列表、点击轮播图没有反应。我习惯在录制前用一个检查清单过一遍先用Postman把/api/product/list和/api/product/detail两个核心接口跑一遍确认返回JSON里有数据不是空数组检查分类ID的对应关系小程序端首页分类的categoryId参数必须与数据库category表的id一致否则按分类筛选后会看到空页面录制视频前把数据库中status0的商品多建几个演示中可以专门展示“已下架商品不出现”的过滤效果——这个小细节比堆页面更能说明业务逻辑完整性另外送一个答辩加分的技巧准备一个小程序页面的“骨架屏”或loading态。虽然商品展示项目不要求高并发优化但UI层面的加载反馈能直接告诉答辩老师“我知道网络请求是异步的、用户需要等待反馈”这比口头解释异步概念有力得多。最后提醒一句源码里凡是出现System.out.println导致控制台刷屏的地方删除或替换为日志框架输出这是代码整洁度的直观分数。本文还有配套的精品资源点击获取