ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Java商城小程序源码实战:linjiashop跑通与避坑指南

Java商城小程序源码实战:linjiashop跑通与避坑指南 简介这是一份基于 Java 开发的轻量级单商户商城系统源码适合初中级 Java 开发者学习电商项目完整流程也可作为小型商家搭建线上销售平台的基础。项目以 Spring Boot 为核心结合 MyBatis 数据映射、Thymeleaf 模板渲染、Redis 缓存与 Maven 工程管理覆盖商品、订单、用户、支付等电商核心模块同时体现微服务拆分、微信小程序接口对接、OAuth2 登录鉴权、Docker 容器化部署、Prometheus 监控与日志采集等常见工程化实践。资源为 ZIP 压缩包整体约 13.2MB目前已有 166 人学习下载。通过源码目录与核心代码可逐步看懂 MyBatis 的 SQL 映射写法、Redis 缓存策略、Thymeleaf 页面渲染方式、小程序端 API 调用流程以及支付、订单等核心模块的数据表设计与状态流转对于希望入门 Java 电商开发、理解前后端交互或进行二次改造的开发者均有不错的参考价值。1. 这项目值得跑吗一个 Java 商城小程序源码能给你什么linjiashop 是一个典型的 Java 后端商城项目microapp 是它的微信小程序前端目录。你拿到手的是一个带完整下单链路、后台管理、数据库初始化脚本的源码包不是那种只有几个页面骨架的演示项目。这套东西适合两类人一类是拿它做毕设或课设需要快速出一个能演示的商城系统另一类是工作一两年、想补电商基础的后端开发想搞明白“用户在小程序里下单后端到底经历了什么”。我的判断是它比想象中重跑通过程会逼你补齐 MySQL、Redis、Maven、微信开发者工具和支付回调的全套常识。这篇我会按自己复现时的顺序写从源码结构讲到数据表关系再到本地启动和踩坑尽量让代码和配置都能直接抄。2. 拆开 linjiashop 源码microapp 小程序与 Java 后端的模块地图拿到源码包先别急着启动先花十分钟把目录结构读明白。这个项目名里的 master 是 Git 主干分支名打包时被带进了目录名microapp 则是小程序端代码的存放位置。这类开源商城大多采用 maven 多模块单体架构linjiashop 的实际目录可能因为版本不同略有出入但模块边界通常一致一个给小程序提供接口的后端模块、一个后台管理模块、一个放通用工具和数据库脚本的核心模块。2.1 先看懂目录结构master、microapp 和几个 maven 模块的关系我见过的大多数同类项目会按下面这样的方式组织linjiashop-master/ ├── linjiashop-admin # 后台管理接口与页面资源 ├── linjiashop-api # 小程序端接口模块 ├── linjiashop-core # 通用工具、常量、异常处理 ├── linjiashop-db # 数据库脚本与实体映射 ├── microapp # 微信小程序前端 ├── pom.xml └── sql/先说 maven 模块的依赖方向。linjiashop-api 是你真正要关注的模块小程序每个页面请求的接口都定义在这里linjiashop-admin 是管理员用的后台和 api 模块共享 core 与 db 的基础能力。db 模块通常放数据库初始化脚本和 MyBatis 的 mapper 接口core 模块放 JWT 工具、统一返回体、分页封装这些与业务无关的东西。microapp 目录里是原生微信小程序代码注意它不是 uniapp 写的所以你不需要装 HBuilderX直接用微信开发者工具打开这个目录就可以编译。如果你之前用惯了 uniapp 那套“一套代码多端发布”的流程回到原生小程序会有个适应期页面逻辑在 Page 里公共请求封装在 utils 目录请求地址通常集中在一个 config.js 里。这个文件等会改后端地址时一定要找到。2.2 商城主链路的数据表会员、商品、订单、支付记录先建好关系商城系统不管前端怎么变后端核心永远是“谁、在什么时间、买了哪个商品、付了多少钱、订单现在什么状态”。linjiashop 这类项目的表结构大体围绕这四类数据展开我按常见实现简化成下面的样式CREATE TABLE mall_member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL, nickname VARCHAR(64), phone VARCHAR(20), status TINYINT DEFAULT 1, created_at DATETIME ); CREATE TABLE mall_goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, is_on_sale TINYINT DEFAULT 1, created_at DATETIME ); CREATE TABLE mall_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, member_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, pay_time DATETIME, created_at DATETIME ); CREATE TABLE mall_pay_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, transaction_id VARCHAR(64), amount DECIMAL(10,2), notify_time DATETIME );member 表通过 openid 关联微信用户goods 表是商品信息order 表里的 status 是整个系统的核心状态机0 待支付、1 已支付、2 已发货、3 已完成这类数值语义通常写死在常量类里pay_record 表负责记录每一次支付回调结果用来做对账。这四张表的关系其实很简单member 下单产生 orderorder 支触发 pay_record 写入pay_record 反过来更新 order 状态。这里有个容易忽略的点订单金额 total_amount 不应该来自前端传参而应该由后端根据商品单价和数量重新计算。很多二次开发的新手直接把前端传来的金额落到订单表支付时又按这个金额请求微信支付结果前端一改价格就出大问题。正确做法是后端查商品表价格重新算一遍前端只能传商品 id 和数量。2.3 改一处业务要从哪层动手Controller、Service、Mapper 的职责边界拿到源码后你迟早会改业务比如给商品加个标签、给订单加个状态。如果不知道从哪层下手代码会越改越乱。这套项目的分层思路和主流 Spring Boot 项目一致Controller 只负责收参数和回结果Service 写业务规则Mapper 操数据库。RestController RequestMapping(/api/goods) public class GoodsController { Resource private GoodsService goodsService; GetMapping(/page) public PageResultGoodsVO page(RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize) { return goodsService.page(pageNum, pageSize); } }Controller 层不要写业务判断比如“库存不足就返回失败”这类逻辑必须放到 Service。原因是小程序端、后台管理端可能共用同一个 Service如果业务规则写在 Controller另一端调用时就得再写一遍极容易写出两套不一致的判断。Service 层通常长这样public interface GoodsService { PageResultGoodsVO page(int pageNum, int pageSize); GoodsVO detail(Long goodsId); Boolean reduceStock(Long goodsId, Integer count); }reduceStock 这个方法名值得注意库存扣减是商城并发问题的高发区后面避坑章节会专门讲。刚开始改代码时建议沿这条线找文件小程序页面 - 找到请求的 url - 在 api 模块 Controller 里搜这个路径 - 进 Service - 进 Mapper。按这个顺序读三个文件就能搞清楚一次请求的完整路径比在 IDE 里全局搜关键字高效得多。3. 本地跑起商城数据库初始化、后端配置与小程序编译的完整路子这章是整篇最容易被卡住的地方。很多新手拿到源码后的做法是先用微信开发者工具打开 microapp看到缺接口报错再回头搞后端这个顺序是反的。正确顺序是先建数据库、启动后端、用 curl 验证接口通最后再编译小程序端。后端多模块项目还涉及依赖安装顺序第一次跑建议从根目录的 pom.xml 开始构建。3.1 先把数据库准备好初始化 SQL 脚本与字符集设置源码包里一般会在 sql 或 doc 目录放初始化脚本文件名通常是 install.sql、linjiashop.sql 这种带 sql 后缀的。打开脚本先看前三行确认它有没有自动建库语句。如果没有你要手动建库再导入否则会直接报 Unknown database。mysql -uroot -p -e CREATE DATABASE linjiashop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p --default-character-setutf8mb4 linjiashop sql/linjiashop.sql第一句创建数据库时指定了 utf8mb4 字符集第二句导入数据时也带了 --default-character-setutf8mb4。这两处必须一致否则 Windows 下终端默认的 gbk 会把中文数据写乱后面前端页面显示商品名全是问号你很难第一时间想到是导入环节出了问题。导入完成后验证一下表数量比如用 SHOW TABLES 看看结果再 select 一条商品记录检查中文是否正常。我一般还会顺手查一下管理员账号的密码字段确认它是明文还是加密这关系到后台管理系统能不能登录。常见项目初始账号是 admin密码可能在 README 或 sql 注释里。3.2 后端必调的三个配置数据源、Redis、JWT 密钥改配置是启动后端的核心环节。linjiashop 的 api 模块和 admin 模块各自有 application.yml但你只需要先改 api 模块。打开这个文件后重点看三个配置项数据库连接、Redis 连接、JWT 密钥。spring: datasource: url: jdbc:mysql://localhost:3306/linjiashop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 password: linjiashop: jwt: secret: change-me-to-a-random-string expire-in: 604800数据源 url 里有两个参数很容易被忽略。serverTimezone 必须设置否则高版本 MySQL 驱动连接时会报时区错误characterEncodingutf8 要和建库时的 utf8mb4 语义一致虽然这里写法是 utf8但 MySQL 驱动一般能正确识别。Redis 如果本地没装赶紧先装一个现在主流版本基本支持 Windows 和 macOS启动后确认 6379 端口能访问。整个项目登录态和部分缓存都依赖 Redis跳过去会报一堆连接异常。JWT 密钥建议直接改成一串随机字符串长度至少 32 位。这个密钥负责生成登录 token一旦泄漏任何人可以伪造管理员身份调用后台接口。expire-in 单位是秒604800 是七天按需调整就行。改完配置用 maven 命令从根目录构建并启动。如果你本机 maven 没配镜像下载依赖可能很慢等看到 BUILD SUCCESS 再继续。启动 api 模块后观察控制台日志出现 Tomcat started on port 字样说明后端起来了。curl 一下健康检查接口或任意一个公开接口能通再往下走。curl http://127.0.0.1:8080/api/goods/page?pageNum1pageSize10返回 JSON 里有商品列表就是正常的。如果 404先去看端口和 context-path有的项目接口前缀是 /api有的还会加模块名这两个地方对不上就会 404。3.3 小程序端编译微信开发者工具打开 microapp 后的第一步后端就绪后用微信开发者工具导入 microapp 目录。这一步有几个新手常犯的错误。第一个是导入时选错了目录把整个 linjiashop-master 导进去了结果开发者工具提示找不到 app.json。microapp 目录下才有 app.json、app.js、pages 目录认准这个结构。导入后先别急着点编译打开 project.config.json 确认 appid 配置。个人开发没有企业小程序账号很正常微信开发者工具支持测试号把 appid 改成 touristappid 即可不需要注册任何东西。{ appid: touristappid, projectname: linjiashop-miniapp, setting: { urlCheck: false } }urlCheck 是本地开发的关键开关。小程序默认要求请求地址必须是配置在后台的 HTTPS 域名本地开发连的是 http://127.0.0.1:8080属于不合规地址不开这个开关编译直接报“不在以下 request 合法域名列表中”。把它设成 false 后工具里的模拟器和真机调试都能访问本地接口。编译后通常控制台还会出现一个“开发版小程序已过期”提示这不是代码问题是你上次打开的时间离现在太久了。处理方式是让微信开发者工具重新扫码登录再编译就正常了。初次打开什么都不显示先看控制台把报错贴出来逐个解决而不是反复点编译。3.4 验证最小请求链从登录接口拿到首页商品列表小程序页面默认会走登录逻辑拿到的登录凭证 code 传给后端后端再用 code 向微信接口换 openid最后返回一个业务 token。这个链路如果没走通首页根本拉不到商品列表。为了确认问题在哪一环我习惯先用 curl 手动模拟登录接口把这个环节单独摘出来验证。curl -X POST http://127.0.0.1:8080/api/member/login \ -H Content-Type: application/json \ -d {code:test-code}这里你本地没有真实 code所以接口大概率返回 code 无效之类的错误这没关系重点看错误类型。如果返回 401 或“无效 code”说明后端服务、数据库、Redis 都正常只是微信登录凭证校验卡住了等小程序端真实调用时 code 是有效的。如果返回 404 或连接拒绝说明问题在配置或服务没起好。真实跑通的方式是在小程序里调用 wx.login 拿 code再发送登录请求。你可以在 app.js 的 onLaunch 里打一个 console.log 把 code 打出来确认它有值。拿不到 code 的原因通常是基础库版本太旧在开发者工具详情里把调试基础库调到最新再看。首页商品列表加载完成后整个最小链路才算真正打通这条链是后续所有订单功能的地基。4. 读懂商城购买链路购物车、订单状态与支付回调的代码咬合前面能跑通只是第一步你还需要读懂购买链路否则改需求时完全不敢动代码。商城最核心的部分不是商品展示而是下单时的库存扣减、支付回调的状态更新、登录态的凭证处理。这三段代码直接决定业务正确性也是面试时最容易追问的点。4.1 从加购到下单事务边界与库存扣减的写法加购本质是往购物车表插一条记录真正有技术含量的是从购物车生成订单的过程。新手最容易犯的错误是把下单逻辑写成“先查库存再扣库存最后插入订单”这三步之间没有任何保护两个用户同时下单同一个商品时库存直接变成负数。常见做法是把库存扣减放到一条 SQL 里做条件更新同时用事务保证订单和库存变更的一致性。Transactional(rollbackFor Exception.class) public String createOrder(Long memberId, Long goodsId, Integer count) { int affected goodsMapper.reduceStock(goodsId, count); if (affected 0) { throw new BizException(库存不足); } String orderNo generateOrderNo(); orderMapper.insert(memberId, goodsId, count, orderNo); return orderNo; }reduceStock 的实现是关键UPDATE mall_goods SET stock stock - #{count} WHERE id #{goodsId} AND stock #{count}这条 SQL 利用了数据库行锁的特性只有当前库存大于等于扣减数量时才更新并且让受影响行数为 0。affected 等于 0 就说明库存被别的请求抢先扣完了无需再查一次库存。放在 Transactional 里能保证扣库存和插订单要么都成功、要么都失败不会出现订单生成了但库存没扣的脏数据。这里的参数 count 必须做上限校验比如单次最多 99 件、最小 1 件防止有人恶意下单。生成订单号建议用“时间戳 随机数 用户标识”组合不要用数据库自增主键直接当订单号暴露给前端很容易被遍历出平台每日订单量。4.2 支付回调要做两件事验签与幂等处理微信支付完成后微信服务器会异步 POST 一个支付结果到你的回调地址。这个回调地址就是后端 Controller 里的一个方法它和普通接口最大的区别是它可能被调用多次而且任何人只要能构造报文都可能往这个地址 POST 数据。所以回调处理必须同时解决验签和幂等。public String onNotify(PayNotify notify) { if (!payService.verifySign(notify)) { return fail; } Order order orderMapper.findByOrderNo(notify.getOrderNo()); if (order null) { return fail; } if (order.getStatus() PAYED) { return success; } if (order.getStatus() ! UNPAY) { return fail; } orderMapper.updateStatusByOrderNo(notify.getOrderNo(), PAYED); return success; }验签逻辑在 payService 里本质是用支付平台下发的密钥对回调报文里的几个关键字段重新签名和微信传来的 sign 字段比对。比对通过才继续不通过直接返回 fail。幂等处理在代码里表现为先查订单状态如果已经是已支付说明之前已经处理过这次回调直接返回 success 而不是再更新一次状态。如果不做这个判断同一笔订单的重复回调可能把支付时间覆盖成两次不同的值或触发发货逻辑重复执行。最后返回的字符串必须是 success 或 fail微信服务器看到 success 才认为回调处理完成返回其他内容它会重试多次。4.3 小程序登录态code 换 openid 的常见实现小程序端的登录和传统用户名密码登录不一样它依赖微信的 jscode2session 接口。用户在小程序里调用 wx.login 拿到一个临时 code这个 code 有效期只有五分钟且只能用一次小程序端把它传给后端后端再用 appid、secret 和 code 去微信服务器换 openid 和 session_key。public String code2Session(String code) { String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code code grant_typeauthorization_code; String result restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(result); if (json.getInteger(errcode) ! null) { throw new BizException(code 已失效); } return json.getString(openid); }这个接口返回的 openid 是用户在小程序里的唯一标识适合作为 mall_member 表的业务主键。拿到 openid 后先查表有没有这个用户没有就注册一条新记录。这里的 session_key 要特别注意它用于解密手机号和用户敏感信息拿到后不应该写进日志更不应该放到 Redis 以外的持久化里。有些老代码会把 session_key 和 openid 一起返回给前端这是安全隐患。正确做法是后端自己持有 openid返回给前端的是你们自己生成的业务 token后续请求都带这个 token。5. 避坑手册linjiashop 跑通与二次开发的 5 个常见问题前面讲的都是正向流程实际跑的时候大概率会翻车。这一章我按自己多次复现这类项目的经验把最容易卡住新手的五个问题列出来每个都按现象、原因、解决的顺序写。建议先收藏遇到问题再翻到这里。5.1 接口 404上下文路径与小程序请求地址不一致现象后端启动成功浏览器直接访问后端地址能出数据但小程序里请求接口全部 404。原因大部分项目配置了 context-path也就是接口统一前缀。你浏览器里访问的是 http://127.0.0.1:8080/api/goods/page小程序 config.js 里配的却是 http://127.0.0.1:8080/goods/page少了一段路径请求直接打到不存在的路由上。这类问题在小程序端表现为一个白页加一串 404 报错想确认请求到底去了哪在开发者工具的控制台看 network 面板就行比对着代码猜快得多。解决打开小程序 public 配置或 utils/request.js找 baseUrl改成带完整前缀的地址。后端如果设置的是 /api那就两者保持一致。// config.js module.exports { baseUrl: http://127.0.0.1:8080/api }注意改完要重新编译小程序只改文件不重新编译有时不会生效。先 curl 一下完整地址确认后端能通再在小程序里请求两个环节分开验证翻车概率会低很多。5.2 订单状态乱跳并发下单时的库存扣减没有加条件现象压测或多人同时下单时库存变成负数订单却能正常生成。原因代码逻辑是“先 select 查库存库存大于 0 再 update 扣减”。这种写法在并发下有两个线程同时读到库存为 1都认为可以下单执行 update 时库存已经变成 -1。问题本质是“检查”和“扣减”不是原子操作。这个坑不只在商城项目里有凡是涉及库存、余额、优惠券数量这类读改写场景都会遇到。解决改成前面 4.1 节那种带条件更新的 SQL让检查与扣减在同一条语句中完成。顺手在商品的 stock 字段上建普通索引避免 update 时全表扫描锁住过多行。UPDATE mall_goods SET stock stock - #{count} WHERE id #{goodsId} AND stock #{count}改完后再压测一次affected 为 0 时你的事务会抛出异常并回滚订单不会插入库存也不会被扣成负数。5.3 真机预览白屏域名校验、调试基础库与抓包定位现象开发者工具里一切正常点“真机预览”扫码后手机打开小程序白屏什么都显示不出来。原因真机上没有“不校验合法域名”这个开关微信会强制检查请求地址域名是否在后台配置过。你的本地地址是 http://127.0.0.1:8080既不是合法域名也不带 https请求直接在真机侧被拦截页面拿不到数据自然白屏。另一个常见原因是调试基础库版本太低老旧基础库不支持某些新语法页面脚本直接报错。解决分两步排查。先在开发者工具的“详情 - 本地设置”里勾选“不校验合法域名”这个只对模拟器有效真机还得做域名配置。开发阶段如果没有现成域名可以用局域网 IP 加开发者工具的真机调试模式绕过或者用内网穿透工具把本地接口映射成一个 https 地址再把小程序后台配置到 request 合法域名里。这里给一个血泪经验真机白屏时先别急着反编译小程序看源码也不要在代码里乱加 console.log。把手机通过数据线连电脑在开发者工具里打开“真机调试”直接看手机端的 console 报错定位速度最快。小程序是黑匣子你不看它的报错就只能瞎猜。5.4 数据库导入乱码Windows 下默认字符集导致的典型现象现象数据库导入 SQL 脚本后后台管理页面和商品列表的商品名全是问号或乱码。原因Windows 命令行终端的默认编码是 GBK执行 mysql 命令导入文件时终端把 utf8 编码的 SQL 脚本内容按 GBK 解析后写入库表中文数据就坏了。问题不在代码而在导入环节。解决重新导入导入命令里强制指定字符集mysql -uroot -p --default-character-setutf8mb4 linjiashop sql/linjiashop.sql如果你用的是 Navicat 这类图形化工具执行 SQL 文件前先在连接属性里把编码设成 utf8mb4。导入完成后再次查询验证select title from mall_goods limit 5。改完这个再重启后端乱码问题基本一次解决。这个坑之所以常见是因为它不像接口报错那么直接数据看着是“有值”的前端渲染时才对不上。5.5 支付回调进不来内网穿透与回调地址白名单现象本地调起微信支付成功但订单状态始终不变成已支付后台日志也没有打印支付回调相关的信息。原因微信支付回调地址必须是公网可访问的 https 地址。你本地开发时后端跑在 127.0.0.1微信服务器根本访问不到回调自然进不来。第二个原因是回调地址没有配置到支付商户平台的白名单里微信会拒绝向其未校验的域名发送回调。解决先在本地自测把问题和网络隔离。用 curl 模拟微信服务器向你的回调接口发一条 POST 请求接口能正常返回 success说明业务代码没问题。curl -X POST http://127.0.0.1:8080/api/pay/notify \ -H Content-Type: application/json \ -d {orderNo:测试单,sign:本地调试用}接着把接口通过内网穿透工具暴露成公网 https 地址在支付平台的回调地址配置里填这个公网地址。配置完成后重新发起一笔小额支付观察后端日志有没有收到 POST。我见过有人在这里卡了两天最后发现是穿透工具免费版的域名被微信风控拦截了换了一个域名配置就好。支付回调的排查顺序永远是本地自测 - 公网可达性 - 白名单配置。6. 进阶改造把 linjiashop 当脚手架用的三个心法项目跑通只是开始你要是拿它做毕设或二次开发后面肯定要加需求。我自己的习惯是把它当成一个可拆解的脚手架而不是一个完整交付物。下面这三个心法是我在改类似商城项目时最常用的思路。第一个心法动手改代码前先把订单状态机画出来。linjiashop 的订单状态在数据库里只有一个 status 字段但状态和状态之间不是随便跳的。待支付可以关闭、可以支付已支付可以发货已发货可以确认收货已收货可以评价。你画完这张图再改代码就不会出现“已支付订单直接改成已关闭”这种逻辑错误。代码里用枚举约束状态变更比裸判断整数常量好维护得多。第二个心法支付结果不能只依赖回调要加一个定时对账补偿。回调可能延迟、丢失甚至微信侧处理完成但你本地网络断了。我的做法是建一张支付记录表每五分钟跑一个任务把超时未支付订单标记为关闭把本地显示未支付但微信侧已扣款的订单捞出来主动调用微信查单接口确认最终状态。这个机制是支付系统的后悔药没有它偶尔丢一笔回调就够你赔半天收益。第三个心法后台管理不要和小程序会员共用同一张权限表。我有一次图省事在 member 表加了一个 is_admin 字段后来想加角色权限完全没法扩展。正确做法是管理员单独建表用角色关联菜单权限。这块代码写完以后毕设答辩时也能讲出亮点面试官问“你怎么设计权限模型”时你就有实战案例了。public enum OrderStatus { WAIT_PAY(0, 待支付), PAYED(1, 已支付), DELIVERED(2, 已发货), FINISHED(3, 已完成), CLOSED(4, 已关闭); private final int value; private final String desc; }线上经验说到底是“先把边界搞清楚再动手”。我以前拿到一个项目源码就急着跑起来结果支付回调没配好硬是排查了两天。后来养成了一个习惯先画状态图、列外部依赖、写最小验证用例。这三步做完百分之八十的坑都提前避开了。希望能帮到你少走点弯路。本文还有配套的精品资源点击获取
返回列表