ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序购物商城Java后端实战:从源码到下单事务避坑

微信小程序购物商城Java后端实战:从源码到下单事务避坑 简介这是一套面向高校计算机相关专业学生的微信小程序购物商城完整项目适用于毕业设计、期末大作业与课程设计场景帮助需要快速搭建可运行商城系统的同学解决从选题到部署的全流程问题。资源包共1247个文件约36.23MB涵盖微信小程序前端与Java后端两大部分前端以js、wxml、wxss、json及大量png、gif图片资源构成页面与交互后端包含java源码、jsp页面、jar依赖、properties配置与sql数据库脚本另有css、html等辅助文件结构完整、层次清晰。代码中附有详细注释新手也能读懂逻辑下载后简单部署即可运行。目前已有499人学习关注。项目涵盖商品展示、购物车、订单管理、用户登录等核心模块配套文档说明与数据库文件可作为高分毕设模板直接参考也能帮助读者理解小程序与Java后端联调的完整思路。1. 从一份能跑通的微信小程序购物商城源码说起Java 后端到底扛了什么很多人拿到「基于微信小程序购物商城app设计带Java后端源代码文档说明数据库高分」这类题目时第一反应是去搜一套现成源码解压、导入、点运行然后卡在登录接口 500。我见过太多这样的场景前端页面能滑能点商品列表是写死的假数据一到下单就报「请求失败」。问题不在小程序端而在 Java 后端那条链路——数据库连没连上、跨域放没放行、微信登录的 code 换没换成 openid任何一环断了整个商城就是个空壳。这篇笔记要讲清楚的就是这套东西一个微信小程序购物商城前端用小程序原生或 uni-app 打包后端用 Java常见是 Spring Boot MyBatis-Plus数据库用 MySQL配套源代码、文档说明和建表脚本。它适合两类人一是要做课程设计或毕业设计、需要一套能演示下单流程的完整工程二是想练前后端分离项目实战、把「小程序发请求 → Java 接请求 → 数据库增删改查」这条线走通的开发者。下面按「先立住结构、再动手复现、最后避坑」的顺序拆开讲参数和命令都能直接抄。2. 商城后端的分层结构与数据库表怎么定先想清楚再写代码2.1 为什么购物商城后端一定要分 Controller / Service / Mapper 三层购物商城的业务比「增删改查 demo」复杂得多。一个下单动作背后要查商品库存、算价格、写订单主表、写订单明细、扣库存、清购物车任何一步失败都要回滚。如果把这些逻辑全塞进一个 Controller 方法里代码会迅速膨胀到没法维护出问题也定位不到是哪一步。常见做法是分三层Controller 只负责接参数、做基础校验、返回统一格式Service 写业务逻辑和事务边界Mapper 只跟数据库打交道。这样下单的事务注解加在 Service 方法上回滚范围清晰。我一般会再抽一个common包放统一返回体Result、全局异常处理、常量避免每个接口各写各的返回格式。选型上Spring Boot 是因为它把 Tomcat、Jackson、数据源配置都自动装配好了起步快MyBatis-Plus 是因为它自带单表增删改查和分页插件商城这种大量单表查询的场景能省掉一半样板代码。热搜里常出现的「mybatisplus根据java实体类生成创建表的sql语句」其实就是它的代码生成器能力建表脚本可以反过来由实体类推但正式项目我还是建议手写建表 SQL字段类型和索引可控。2.2 商城核心表的设计与建表脚本一个能跑通下单流程的商城最少需要这几张表用户表、商品表、商品分类表、购物车表、订单主表、订单明细表、收货地址表。下面给出关键几张的建表脚本字段名和后面 Java 实体类要对齐。-- 用户表微信登录后落库openid 唯一 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) NOT NULL COMMENT 微信openid唯一标识, nickname VARCHAR(64) DEFAULT NULL COMMENT 昵称, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 商品表价格用 DECIMAL别用 FLOAT CREATE TABLE product ( id BIGINT NOT NULL AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT 分类id, name VARCHAR(128) NOT NULL COMMENT 商品名, price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, cover VARCHAR(255) DEFAULT NULL COMMENT 封面图, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 订单主表订单号单独存别用自增id当订单号 CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;逻辑说明user表用openid做唯一键是因为微信登录拿到的 openid 对同一小程序是稳定的用它判断「这个用户是不是已经注册过」最可靠。product表价格用DECIMAL(10,2)而不是FLOAT浮点数在金额计算上会出现0.10.20.30000000000000004这种玄学问题对账时能让你怀疑人生。orders表单独存order_no是因为自增 id 会暴露订单量而且分库分表时自增 id 不好用。参数说明utf8mb4是为了支持 emoji用户昵称里带表情很常见ENGINEInnoDB是必须的因为要事务索引只加在真正会作为查询条件的字段上category_id和user_id是高频查询字段加索引合理但别给每个字段都加写入会变慢。2.3 用 MyBatis-Plus 把实体类和表对上建完表Java 侧要写实体类。MyBatis-Plus 靠注解把类映射到表字段名和列名不一致时用TableField指定。Data TableName(product) public class Product { TableId(type IdType.AUTO) private Long id; private Long categoryId; // 驼峰自动映射 category_id private String name; private BigDecimal price; // 金额用 BigDecimal private Integer stock; private String cover; private Integer status; }逻辑说明TableName(product)告诉 MyBatis-Plus 这张表叫什么TableId(type IdType.AUTO)表示主键由数据库自增插入时不用手动赋值。字段categoryId会自动映射到category_id这是 MyBatis-Plus 默认开启的驼峰转下划线规则不用额外配置。价格字段用BigDecimal和数据库的DECIMAL对应保证精度一致。参数说明如果项目里全局配置了map-underscore-to-camel-case: trueSpring Boot 下 MyBatis-Plus 默认就是 true驼峰映射自动生效如果表字段和实体字段名完全对不上才需要TableField(xxx)手动指定。这一步做完单表的增删改查就不用写 XML 了直接调productMapper.selectById(id)即可。3. 微信登录与前后端联调code 换 openid 这条链路怎么打通3.1 小程序端 wx.login 拿到 code 之后发生了什么微信小程序的登录不是传统账号密码。流程是小程序调wx.login()拿到一个临时code把这个 code 发给自己的 Java 后端后端拿 code 加上小程序的appid和secret去请求微信的接口换回openid和session_key然后后端根据 openid 查库或建号最后签发一个自己的 token 返回给小程序。之后小程序的每个请求都带这个 token后端解析 token 拿到用户身份。这里的关键点是appid和secret绝对不能放在小程序端必须放后端。小程序端的代码是能被反编译看到的secret 泄露等于别人可以冒充你的小程序调接口。热搜里「微信小程序登录获取手机号」是另一条链路需要用户点击授权按钮拿到加密数据后由后端解密和登录是分开的两步别混在一起做。3.2 Java 后端实现 code 换 openid 的接口下面是一个最小可用的登录接口用 Spring Boot 的RestTemplate请求微信接口。RestController RequestMapping(/api/auth) public class AuthController { Value(${wx.appid}) private String appid; Value(${wx.secret}) private String secret; Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 1. 用 code 换 openid String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code dto.getCode() grant_typeauthorization_code; RestTemplate restTemplate new RestTemplate(); String resp restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(resp); // 2. 微信返回错误时直接抛出别吞掉 if (json.containsKey(errcode)) { throw new BizException(微信登录失败: json.getString(errmsg)); } String openid json.getString(openid); // 3. 查库或建号签发 token User user userService.findOrCreateByOpenid(openid); String token JwtUtil.sign(user.getId()); return Result.ok(token); } }逻辑说明第一步拼 URL 请求微信的jscode2session接口这个接口是 GET 请求参数直接拼在 URL 上。第二步判断返回里有没有errcode微信出错时不会返回 HTTP 错误码而是返回一个带errcode的 JSON如果不判断后面取openid会拿到 null问题会飘到很远的地方才暴露。第三步findOrCreateByOpenid是「有就查、没有就建」保证同一个微信用户只对应一条记录。最后用 JWT 签发 token里面放用户 id。参数说明appid和secret从配置文件读不要硬编码在代码里grant_type固定是authorization_code写错微信会报错RestTemplate是最简单的 HTTP 客户端生产环境建议配连接超时和读取超时否则微信接口慢的时候会拖住线程。3.3 前后端分离下的跨域和请求封装小程序端请求后端域名必须在微信公众平台配置为合法域名本地开发时可以在开发者工具里勾选「不校验合法域名」。后端这边如果小程序端和接口不同源需要处理跨域。Spring Boot 里加一个配置类即可。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }逻辑说明addMapping(/api/**)只对接口路径放行静态资源不用管。allowedOriginPatterns(*)在带allowCredentials(true)时不能用allowedOrigins(*)这是 Spring 的硬性限制用 pattern 才能同时允许携带凭证和任意来源。maxAge(3600)表示预检请求结果缓存一小时减少 OPTIONS 请求次数。参数说明生产环境要把allowedOriginPatterns收窄到具体域名别用*allowCredentials(true)只有在需要带 cookie 时才开如果只用 token 放 header可以关掉安全性更好。小程序端封装请求时把 token 放在 header 的Authorization字段后端用一个拦截器统一校验别在每个接口里手动验。4. 购物车与下单事务库存扣减和订单号生成最容易翻车的地方4.1 购物车用数据库存还是本地缓存购物车有两种做法存数据库或者存小程序本地Storage。存本地简单但换设备就没了而且下单时要把本地数据传给后端容易被篡改。存数据库的好处是跨设备同步、后端可控缺点是每次加减都要请求接口。我一般选数据库方案表结构是cart(id, user_id, product_id, quantity, checked)。加购时先查这个用户这个商品有没有记录有就数量累加没有就插入。这里有个并发问题同一个用户快速点两次加购可能两条记录都查不到然后都插入导致重复。解决办法是给(user_id, product_id)加唯一索引插入冲突时改成更新。ALTER TABLE cart ADD UNIQUE KEY uk_user_product (user_id, product_id);逻辑说明唯一索引保证一个用户对一个商品只有一条购物车记录。代码里用insert ... on duplicate key update quantity quantity ?这种写法一条 SQL 搞定「有则累加、无则插入」避免先查后插的竞态。参数说明quantity要限制上限比如单商品最多 99 件防止有人恶意刷数量checked字段标记是否勾选结算时只算勾选的。4.2 下单事务扣库存和写订单必须在一个事务里下单是商城最核心也最容易出问题的环节。正确顺序是校验库存 → 扣库存 → 写订单主表 → 写订单明细 → 清购物车。这几步必须在同一个事务里任何一步失败全部回滚。Service public class OrderServiceImpl implements OrderService { Autowired private ProductMapper productMapper; Autowired private OrdersMapper ordersMapper; Autowired private OrderItemMapper orderItemMapper; Override Transactional(rollbackFor Exception.class) public String createOrder(Long userId, ListOrderItemDTO items) { BigDecimal total BigDecimal.ZERO; for (OrderItemDTO item : items) { // 扣库存带 stock quantity 条件防止超卖 int affected productMapper.deductStock(item.getProductId(), item.getQuantity()); if (affected 0) { throw new BizException(库存不足: item.getProductId()); } Product p productMapper.selectById(item.getProductId()); total total.add(p.getPrice().multiply(new BigDecimal(item.getQuantity()))); } // 写订单主表 Orders order new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(total); order.setStatus(0); ordersMapper.insert(order); // 写明细、清购物车略 return order.getOrderNo(); } }逻辑说明扣库存的 SQL 是UPDATE product SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}用affected 0判断库存不足。这种「带条件的更新」比「先查库存再更新」安全因为查和更新之间可能有其他请求把库存扣走这就是超卖的根源。Transactional(rollbackFor Exception.class)里的rollbackFor必须写因为 Spring 默认只对运行时异常回滚业务里抛的自定义异常如果是受检异常就不会回滚。参数说明generateOrderNo()建议用「时间戳 用户id后几位 随机数」别用 UUID太长且无序订单号要唯一数据库加唯一索引兜底。事务方法里不要做远程调用比如调微信支付远程调用耗时长会占着数据库连接正确做法是先落订单再异步发起支付。4.3 订单号生成和幂等用户手抖点两次「提交订单」可能生成两笔订单。解决办法是前端按钮点击后置灰后端加幂等控制。简单做法是下单接口带一个前端生成的requestId后端用 Redis 或数据库唯一索引去重同一个requestId只处理一次。// 用 requestId 做幂等键插入成功才继续冲突说明重复提交 int inserted idempotentMapper.insertIgnore(requestId); if (inserted 0) { throw new BizException(请勿重复提交); }逻辑说明insertIgnore对应INSERT IGNORE唯一键冲突时返回 0 行受影响据此判断重复。这个操作要在事务最前面做重复请求直接挡掉不进入后续扣库存逻辑。参数说明requestId由前端在进入下单页时生成一次整个下单过程复用幂等记录要设过期时间否则表会无限增长用 Redis 的SETNX加过期是更常见的做法。5. 这套商城源码落地时的避坑清单现象、原因、解决5.1 小程序请求后端报「不在以下 request 合法域名列表中」现象开发者工具里接口能通真机预览时报域名不合法。原因微信要求所有请求域名在公众平台备案并配置开发者工具的「不校验合法域名」选项只对工具生效。解决本地调试勾选该选项上线前把后端域名配到小程序后台的 request 合法域名里必须是 https且域名要备案。5.2 登录接口返回 openid 为 null现象jscode2session返回的 JSON 里没有 openid只有 errcode。原因code 只能用一次且有效期五分钟重复使用或过期都会失败appid 和 secret 不匹配也会失败。解决确认 code 是刚wx.login()拿到的没被复用核对 appid 和 secret 是否属于同一个小程序把微信返回的 errmsg 打日志别吞异常。5.3 下单时库存扣成负数现象并发测试时库存出现负数。原因用了「先 select 查库存再 update 扣减」的写法两个请求同时查到库存 1都判断够都扣变成 -1。解决改成带条件的原子更新UPDATE ... SET stock stock - ? WHERE id ? AND stock ?用受影响行数判断是否成功。5.4 金额计算出现小数误差现象订单总价和商品单价乘数量对不上差几分钱。原因用了float或double存金额。解决数据库用DECIMALJava 用BigDecimal且BigDecimal比较用compareTo不用equals因为equals会比较精度1.0和1.00不相等。5.5 事务不回滚现象扣库存成功但写订单失败库存没恢复。原因异常被 catch 后没重新抛出或者抛的是受检异常而Transactional没配rollbackFor。解决Transactional(rollbackFor Exception.class)catch 里如果要处理日志处理完必须throw出去别吞掉。6. 把这套商城改造成能写进简历的项目三个进阶动作第一个动作是加缓存。商品详情是读多写少的典型场景每次请求都查数据库没必要。用 Redis 缓存商品信息key 用product:{id}设置过期时间更新商品时删缓存而不是改缓存避免并发下缓存和数据库不一致。这一步做完接口响应能从几十毫秒降到几毫秒面试时也能讲清楚缓存穿透、击穿、雪崩的区别。第二个动作是把下单改成异步。前面说过事务里不要做远程调用正确做法是下单落库后发一条消息到队列由消费者去调支付、发通知。本地练手可以用 Spring 的Async模拟生产用 RocketMQ 或 RabbitMQ。这样下单接口的响应时间只取决于数据库写入用户体验明显提升。第三个动作是补文档和接口测试。一套能拿高分的项目文档说明和数据库脚本一样重要。用 Swagger 或 Knife4j 自动生成接口文档每个接口写清楚入参、出参、错误码。再写几个 JUnit 测试覆盖登录、加购、下单三条主链路跑一遍全绿比口头说「我做过」有说服力得多。进阶动作解决什么问题关键改动点加 Redis 缓存商品详情查询压力大查缓存未命中再查库更新时删缓存下单异步化事务里远程调用拖慢响应落库后发消息消费者处理后续补接口文档与测试项目无法演示、无法验证Swagger 注解 JUnit 主链路用例我自己的习惯是每做完一个模块先把数据库脚本和接口文档更新一遍再写两条测试。这样即使隔两周回头看也能快速捡起来不至于对着一堆代码发呆。这套微信小程序购物商城的技术栈不新但把登录、购物车、下单事务这几条线走通前后端分离项目实战的底子就打牢了。希望帮到你。本文还有配套的精品资源点击获取
返回列表