ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于SSM与微信小程序的文玩销售后端系统设计

2026/9/13 16:43:06 拓冰建站 浏览量
基于SSM与微信小程序的文玩销售后端系统设计 简介微信小程序为文玩销售提供了轻量便捷的载体该项目正是一套完整的基于微信平台的文玩销售小程序源码后端采用Java与springboot/ssm组合前端为微信小程序与Vue页面适用于毕业设计、课程实训及商业原型开发。压缩包内含1224个文件约15.32MB涵盖java源码、小程序wxml/wxss页面、vue组件、js逻辑、png/svg图标资源及sql数据库脚本等目录划分清晰方便按模块学习。项目已通过调试可直接运行并配套功能介绍文档便于理解商品展示、购物车、订单、用户评论等核心流程。开发环境建议使用JDK1.8、Tomcat7和MySQL5.7整体技术栈主流且易于上手。资源目前已吸引2708人学习浏览适合想掌握SSM框架小程序开发全流程的读者。1. 基于微信平台的文玩销售小程序ssm 是如何撑起后端与交易的文玩销售这类垂直电商小程序业务链路其实比普通货架电商更敏感商品是孤品或小批量价格随品相浮动买卖双方都依赖图片细节和信任背书。用微信小程序做前端触达、用 ssmSpring SpringMVC MyBatis做后端接口几乎是这类毕业设计和中小型外包项目的标准组合。标题里那个 zip 包通常就装着小程序前端工程、ssm 后端工程和数据库脚本三部分。这个标题要解决的实际问题是一个非电商背景的团队如何用最成熟的 Java 技术栈在微信生态里跑通商品展示、购物车、订单和支付闭环。ssm 负责把 MySQL 里的商品、分类、订单数据以 JSON 接口的形式暴露给小程序端微信小程序负责渲染页面和发起交易请求。适合的人群很明确正在做毕设的在校生、需要交付外包项目的开发者、以及打算在微信生态里做二手或非标品交易的独立开发者。接下来按我实际会做的顺序把这条技术主干拆开讲。2. ssm 后端与小程序前端的接口设计先定好 JSON 结构再写代码拿到项目压缩包后第一件事不是启动 Tomcat而是把后端接口的返回格式统一清楚。smm 项目最常见的接口风格是返回一个约定的 JSON 包装类前端拿到后统一判断 code 再取 data避免小程序端每个页面各自处理异常。2.1.1 统一返回体的定义方式后端可以定义一个 Result 类放在 common 包下。所有 Controller 方法都返回它。public class ResultT { private Integer code; // 0 成功非 0 失败 private String msg; // 提示信息 private T data; // 业务数据 public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.msg ok; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 1; r.msg msg; return r; } // getter / setter 省略 }这段代码的逻辑是把成功和失败的返回逻辑收敛到静态工厂方法里。前端只需要判断 code 是否为 0其余字段不用关心。这样做的好处是新增接口时不会出现“有人返回 Map、有人返回 String”的混乱局面。2.1.2 商品列表和详情接口的最小实现文玩销售的核心接口有三个首页的商品分类列表、分类下的商品分页列表、商品详情。小程序端典型的请求路径是/api/goods/list和/api/goods/detailController 层这样写RestController RequestMapping(/api/goods) public class GoodsController { Autowired private GoodsService goodsService; GetMapping(/list) public ResultPageResultGoods list(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, Integer categoryId) { PageResultGoods result goodsService.listByPage(page, size, categoryId); return Result.success(result); } GetMapping(/detail) public ResultGoods detail(RequestParam long id) { Goods goods goodsService.getById(id); if (goods null) { return Result.error(商品不存在或已下架); } return Result.success(goods); } }参数说明page 默认从 1 开始size 默认 10categoryId 可空为空时表示查询全部分类。PageResult 是分页结果的封装包含 total、list 两个字段。小程序端拿到后直接用 list 渲染商品卡片用 total 判断是否还有下一页。2.1.3 小程序端的请求封装与拦截微信小程序没有 axios但可以用wx.request做一层 Promise 封装。常见的做法是在 utils/request.js 里写一个公共函数。const BASE_URL http://localhost:8080; // 开发环境地址 function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method: method, data: data, header: { Content-Type: application/json }, success: (res) { if (res.statusCode 200 res.data.code 0) { 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 };这里的关键参数是 header 里的 Content-Type 必须和后端 SpringMVC 的RequestBody解析规则匹配。如果后端方法签名写的是RequestBody User user前端必须传 JSON 字符串如果后端用普通参数接收前端 data 直接传对象即可。很多联调问题都出在这个匹配上。3. MySQL 表结构与 MyBatis 映射文玩商品的 sku 和图片存法文玩商品的属性很特殊同一种商品可能有不同的尺寸、密度、纹理品相直接决定价格。用传统电商的 spu/sku 模型去套会非常别扭。多数 ssm 项目的做法是商品主表存基本信息图片表存多图规格直接冗余在商品表里一个 JSON 字段。3.1.1 商品表的字段设计CREATE TABLE goods ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id int(11) DEFAULT NULL COMMENT 分类id, name varchar(128) NOT NULL COMMENT 商品标题, price decimal(10,2) NOT NULL COMMENT 价格, original_price decimal(10,2) DEFAULT NULL COMMENT 划线价, main_image varchar(255) DEFAULT NULL COMMENT 主图url, detail_images text COMMENT 轮播图url逗号或JSON分隔, spec_json varchar(1024) DEFAULT NULL COMMENT 规格JSON如尺寸/重量/瑕疵说明, stock int(11) DEFAULT 0 COMMENT 库存, status tinyint(4) DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;spec_json字段是文玩商品和普通商品最大的区别。比如手串的“2.0尺寸/12颗/密度2.1”这类信息如果用单独的 sku 表联表查询复杂库存也不是按规格维度管理性价比不高。直接存 JSON 字符串前端拿到后解析渲染到详情页即可。3.1.2 MyBatis 的 XML 写法在 GoodsMapper.xml 里分页查询要手写 LIMIT 语句这是 ssm 项目里最容易出错的地方。select idlistByPage resultTypecom.example.entity.Goods SELECT id, category_id, name, price, main_image, stock FROM goods where if testcategoryId ! null AND category_id #{categoryId} /if AND status 1 /where ORDER BY create_time DESC LIMIT #{offset}, #{size} /select注意LIMIT #{offset}, #{size}这两个参数。Service 层传入的通常是 page 和 size需要手动计算 offset (page - 1) * size。很多新手直接传 page 进去导致第一页正常第二页数据错乱或缺失。3.1.3 购物车放在本地还是后端文玩销售小程序的一个常见决策点购物车数据放哪。实际项目里两种方案都有。放在小程序本地 Storage 的优点是实现简单、不占后端资源缺点是换设备后购物车丢失放在后端 cart 表则要考虑游客用户和登录用户的关联。对于 ssm 项目我建议购物车直接放本地 Storage理由有三第一文玩类商品客单价高、购买决策周期长购物车更像收藏夹而非结算前置动作第二后端只需要做订单表和订单明细表少一张表少一套增删改查第三避免处理“用户未登录但加购了商品”的边界状态。下单时再把本地购物车数据提交到后端生成订单即可。4. 微信登录与支付在 ssm 里的实现路径从 code 换 openid 到统一下单微信平台的“平台”二字体现在两个关键环节登录和支付。这两块是 ssm 项目里最容易照抄旧代码而踩坑的地方因为微信接口的调用方式和回调验签规则经常会更新。4.1.1 小程序登录的 code2Session 流程小程序端调用wx.login()拿到临时 code传给后端后端拿 code 去微信接口换 openid。public String code2Session(String code) { String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; RestTemplate restTemplate new RestTemplate(); String result restTemplate.getForObject(url, String.class); // 解析返回的 JSON取 openid 和 session_key JSONObject json JSON.parseObject(result); String openid json.getString(openid); // 根据 openid 查用户表不存在则自动注册 return openid; }这里的逻辑分三步先用 code 换 openid再用 openid 查用户表最后把 userId 返回给前端存储。小程序端之后每次请求都带上 userId后端用它区分权限和数据归属。提示session_key 不要返回给前端。它用于解密手机号和支付后校验用户信息一旦泄露会有安全风险。4.1.2 小程序端 wx.login 的调用时机首页加载时先调 wx.login 可能因为 code 过期导致登录失败常见的做法是全局只在启动时调用一次。App({ onLaunch() { wx.login({ success: (res) { if (res.code) { wx.request({ url: ${BASE_URL}/api/user/login, method: POST, data: { code: res.code }, success: (resp) { getApp().globalData.userId resp.data.data.id; } }); } } }); } });这个时机选在 onLaunch意味着冷启动后第一次进入页面拿 userId 的操作和页面数据加载是并行的。页面如果依赖 userId 才能请求数据需要在请求里等待全局变量赋值完成不能直接在 onLoad 里同步读。4.1.3 微信支付的统一下单与回调支付环节 ssm 项目通常引入weixin-java-pay这类封装库或直接使用官方支付 SDK v3。核心步骤是后端构造支付参数、调用微信支付统一下单接口、拿到预支付交易标识后回传小程序端。// 构造请求参数签名由SDK完成 WxPayUnifiedOrderRequest orderRequest new WxPayUnifiedOrderRequest(); orderRequest.setOutTradeNo(orderNo); orderRequest.setTotalFee((int)(price * 100)); // 单位是分 orderRequest.setBody(文玩商品- goodsName); orderRequest.setOpenid(openid); orderRequest.setTradeType(JSAPI); WxPayUnifiedOrderResult result wxPayService.createOrder(orderRequest); // 将 prepayId 等参数返回给小程序端由小程序端调 wx.requestPayment参数的坑集中在两点金额单位一定要换算成分订单号必须全局唯一且长度不能超过 32 位。回调接口写好后务必用微信支付提供的解密工具验证报文不能只验签名不验内容。4.1.4 支付回调的验签与幂等处理支付回调是重试机制下最容易出问题的环节。微信支付会多次通知同一个结果后端在回调处理里必须做幂等判断。if (orderService.getByOrderNo(outTradeNo).getStatus() 0) { // 只有未支付的订单才更新状态避免重复发货 orderService.updateStatus(outTradeNo, 1); }这段逻辑解决的是网络抖动导致微信重复回调时同一订单只处理一次。状态判断用 status 字段标记0 表示未支付1 表示已支付待发货。如果漏掉这个判断同一订单可能会被重复发货或重复调发货接口。5. 小程序 request 合法域名与 ssm 部署的配合三步调试打通开发环境里一切正常但真机预览时页面白屏或请求失败绝大多数情况出在微信平台的域名校验。这是 ssm 项目从本地走向线上的最后一公里我把排查路径整理成固定的三步。5.1.1 第一步检查 request 合法域名小程序后台的“开发管理 - 开发设置 - 服务器域名”里request 合法域名只能填 HTTPS 地址并且域名要 ICP 备案。IP 地址和未经备案的域名一律不行。ssm 项目部署在 Tomcat如果用 IP 访问微信平台会直接拦截控制台报url not in domain list。开发阶段的临时处理办法是在微信开发者工具右上角“详情 - 本地设置”里勾选“不校验合法域名”。这个选项只能用于开发调试项目发布前必须关闭。5.1.2 第二步检查 Tomcat 的 HTTPS 配置ssm 项目默认是 HTTP而微信平台要求 HTTPS。生产环境常见做法是用 Nginx 做 HTTPS 反向代理转发到内网 Tomcat 的 8080。server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/nginx/cert/yourdomain.pem; ssl_certificate_key /etc/nginx/cert/yourdomain.key; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这段配置的核心是location /api/的转发规则。小程序端所有请求都带/api前缀Nginx 统一拦截后反代到本地 Tomcat后端代码里不需要写死域名拿到请求时根据 Host 头就能正确生成回调地址。5.1.3 第三步验证支付回调地址能否被外网访问支付回调地址是微信服务器主动访问你后端的 URL不能被防火墙拦住。验证方法很简单用外网机器或手机网络直接访问回调地址看能否正常返回。如果返回 404 或超时再检查 Nginx 有没有放行对应路径、Tomcat 的访问日志有没有收到请求。5.1.4 把 baseUrl 抽成环境变量最后建议在小程序端把 BASE_URL 从代码里抽离用配置文件管理开发和线上两套地址。const config { develop: { BASE_URL: http://localhost:8080 }, trial: { BASE_URL: https://yourdomain.com/api }, release: { BASE_URL: https://yourdomain.com/api } }; const env wx.getAccountInfoSync().miniProgram.envVersion; module.exports config[env] || config.release;wx.getAccountInfoSync返回的 envVersion 有三个值develop、trial、release分别对应开发版、体验版、正式版。切换环境时小程序端不需要改代码发布到体验版自动走正式域名这点在团队协作时特别有用——后端联调时各自连本地测试时用体验版发布后直接是正式环境。本文还有配套的精品资源点击获取