
简介一套基于微信平台的鲜花销售微信小程序项目资源包含完整源码和配套说明文档面向小程序开发初学者、Java后端学习者及毕业设计学生解决从零实现一个鲜花电商小程序的前后端联调与部署问题。资源包共1342个文件约19.19MB类型覆盖图片、脚本、组件、样式、数据配置等png、jpg素材用于产品与界面展示js与json支撑小程序逻辑和配置wxml与wxss定义页面结构样式vue与java构建后台管理及SSM框架服务sql为数据库初始化脚本bat文件提供安装运行快捷入口。已有1573人学习下载。说明文档包含需求分析、可行性研究、数据库设计、系统流程图、前后台功能模块、系统测试与总结等章节源码目录规划清晰管理员、商家与用户端功能分层明确可直接导入开发工具运行适合二次开发、课程设计或毕业设计参考。1. 微信鲜花小程序的源码结构先弄懂再动手手里这套项目不是那种只有一个前端页面、后端用 JSON 文件模拟数据的演示品。它是一个完整的微信小程序电商闭环小程序端用原生 WXML/WXSS/JavaScript 写页面后端基于 Java 的 SSM 框架Spring SpringMVC MyBatis提供接口数据落在 MySQL 里后台分管理员和商家两个角色入口。如果你正打算做微信小程序毕业设计或者在公司里从零搭一个轻量电商小程序这套源码最值得借鉴的地方在于它把微信登录、商品管理、订单流转、购物车这类通用逻辑按标准的 B/S 三层结构拆开了你能直接看到一次请求从 button 点击到数据库落库的完整路径。拿到压缩包之后你会发现里面有一批.bak文件像IndexMain.vue.bak、IndexHeader.vue.bak、update-password.vue.bak这些是备份文件不是主代码。真正启动顺序是1-install.bat装依赖、2-run.bat跑后端、3-build.bat构建前端三个批处理把环境串起来了。后面章节我会按「数据库 → 后端接口 → 小程序端联调」的顺序拆开讲最后补上真机调试时最容易踩的坑。2. SSM 后端 微信小程序端的架构适配2.1 为什么这套方案还在用 SSM而不是 Spring Boot看到 SSM 先别急着觉得老。这个项目选 Spring SpringMVC MyBatis 是有实际考虑的第一课程设计和毕业设计场景里SSM 的知识点覆盖更完整分层清晰答辩时好讲第二SSM 的配置是显式的web.xml、spring-mvc.xml、mybatis-config.xml每个文件职责单一对理解请求是怎么从前端走到数据库的反而有帮助。Spring Boot 把这些都自动装配了代码是少写很多但原理反而被藏起来了。实际部署时这套结构分为三层层次组件职责表现层微信小程序前端WXML 渲染、wx.request 调用接口、本地缓存 token控制层SpringMVC Controller接收请求、参数校验、返回 JSON业务层Service MyBatis Mapper业务逻辑、SQL 映射、事务控制数据层MySQL 5.7用户、商品、订单、购物车等表这种架构和微信小程序的天然契合点在于小程序端本身不直接连数据库所有数据操作都通过wx.request发 HTTP 请求到后端接口。也就是说只要 Controller 层把接口地址暴露出来小程序端不管用什么框架写都能对接。这也解释了为什么源码里的小程序端是原生写法——它不需要任何额外依赖微信开发者工具打开就能跑。2.2 微信开发者工具里的项目初始化用微信开发者工具导入项目时注意不是直接选源码根目录而是选到小程序前端代码所在的那一层也就是包含app.js、app.json、pages/的目录。导入后第一件事是改app.js里的全局配置// app.js 片段 App({ globalData: { // 后端接口地址本地调试用 localhost真机调试要改成局域网 IP 或已备案域名 baseUrl: http://localhost:8080/flower-api, // 微信小程序 AppID自己注册的测试号也行 appId: wx1234567890abcdef, userInfo: null, token: }, onLaunch: function () { // 从本地缓存恢复登录态 const token wx.getStorageSync(token) if (token) { this.globalData.token token } } })这里的baseUrl是整个联调的关键。模拟器里可以填localhost但真机预览时手机访问不到你电脑的 localhost必须改成电脑的局域网 IP比如http://192.168.1.101:8080/flower-api。另外微信公众平台后台需要把该域名加入 request 合法域名否则开发工具里会报url not in domain list。开发阶段可以在工具里勾选「不校验合法域名」但体验版和正式版一定得配真实域名加上 HTTPS。2.3 B/S 架构下前后端的数据契约小程序端和后端之间走的是 JSON 格式的数据契约。后端返回的统一结构一般是这样的{ code: 200, message: success, data: { flowerId: 1, name: 红玫瑰, price: 99.00, stock: 50, coverImage: https://cdn.example.com/rose.jpg } }这套约定里code表示业务状态码message给前端提示用data才是真正的业务数据。前端拿到响应后先判断code再渲染页面。源码里封装了一个request.js工具类统一处理wx.request的 Promise 化和错误拦截建议你直接用不要在每个页面里单独调wx.request。3. 数据库设计鲜花电商的订单状态流转3.1 核心表结构拆解这套项目的数据库设计是典型的电商五表模型用户表、鲜花表、购物车表、订单表、订单明细表。订单单独拆出明细表是因为一个订单可能包含多束花每束花的数量、单价、小计必须独立记录否则后续对账和商家结算会非常痛苦。商家和管理员各自有独立表后台登录时区分角色。-- 鲜花表 CREATE TABLE flower ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 鲜花名称, category VARCHAR(50) DEFAULT NULL COMMENT 分类玫瑰/百合/康乃馨等, price DECIMAL(10,2) NOT NULL COMMENT 销售单价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, cover VARCHAR(255) DEFAULT NULL COMMENT 封面图URL, detail_desc TEXT COMMENT 详情描述, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表 CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id INT NOT NULL COMMENT 下单用户ID, total_price DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, receiver_name VARCHAR(50) NOT NULL, receiver_phone VARCHAR(20) NOT NULL, receiver_address VARCHAR(255) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表里status字段是整个流程的核心。0 到 4 的状态定义不是随意写的它对应着实际业务中的动作用户提交订单后是待付款支付回调成功变待发货商家发货后变待收货用户确认收货变已完成超时未支付或用户主动取消则是已取消。前端小程序端的订单列表页、商家后台的订单管理页都依赖这个状态值做条件渲染。3.2 数据库参数设计和索引注意点DECIMAL(10,2)不要换成FLOAT金额用浮点类型会出现精度丢失比如 0.1 加 0.2 得到 0.30000000000000004这在涉及价格计算的场景里是不能接受的。orders表的order_no加了唯一索引因为订单号是用户查询、对账、售后时的关键检索条件频繁等值查询必须走索引。flower表的category加普通索引就够了它的区分度不高不需要唯一约束。初始化数据库时直接用flower.sql脚本导入即可。MySQL 字符集建议设成utf8mb4而不是utf8因为utf8在 MySQL 里最多存 3 字节鲜花名称里如果需要放 emoji 表情符号或者生僻字utf8会直接报错。3.3 购物车与订单联动的小程序端实现购物车页面在pages/cart/目录下核心逻辑是对勾选状态和数量的管理。批量结算时前端把选中的商品拼成参数传给后端下单接口// pages/cart/cart.js 中结算方法的核心逻辑 submitOrder() { const checkedItems this.data.cartList.filter(item item.checked) if (checkedItems.length 0) { wx.showToast({ title: 请先选择商品, icon: none }) return } const totalPrice checkedItems.reduce((sum, item) { return sum item.price * item.count }, 0) const params { items: checkedItems.map(item ({ flowerId: item.flowerId, count: item.count })), totalPrice: totalPrice.toFixed(2), receiverName: this.data.receiverName, receiverPhone: this.data.receiverPhone, receiverAddress: this.data.receiverAddress } wx.request({ url: ${app.globalData.baseUrl}/order/create, method: POST, data: params, header: { Authorization: wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { // 下单成功后跳转到订单详情页 wx.redirectTo({ url: /pages/order/detail?id${res.data.data.orderId} }) } } }) }注意这里items是一个对象数组后端接收时用RequestBody OrderCreateDTO来映射。DTO 里的items字段是ListOrderItemDTOMyBatis 层面需要做批量插入。批量插入的 SQL 写法是insert idbatchInsertOrderItems INSERT INTO order_item (order_id, flower_id, flower_name, price, count, subtotal) VALUES foreach collectionlist itemitem separator, (#{item.orderId}, #{item.flowerId}, #{item.flowerName}, #{item.price}, #{item.count}, #{item.subtotal}) /foreach /insertforeach的separator,会把多条记录拼进一条 INSERT 语句比循环单条插入快得多而且能保证同一个订单的明细要么全部插入成功、要么全部失败。这层事务控制在 Service 层加上Transactional注解一旦任何一条明细插入失败整个订单创建回滚不会出现订单主表有记录但明细缺失的脏数据。4. SSM 后端接口设计与微信登录态处理4.1 Controller 层的统一返回格式与参数接收后端的 Controller 层在com.flower.controller包下每个业务模块对应一个 Controller。以订单模块为例接口列表是这么设计的接口路径方法参数格式说明/order/createPOSTJSON Body创建订单/order/listGETQuery String按用户查订单列表/order/detailGETorderId订单详情/order/cancelPOSTorderId取消订单/order/updateStatusPOSTorderIdstatus商家发货或确认收货Controller 的写法遵循一个约定参数用 DTO 接收返回统一用ResultT包装。ResultT这个泛型类包含code、message、data三个字段每个接口方法体里只写业务逻辑异常处理交给ControllerAdvice全局捕获。RestController RequestMapping(/order) public class OrderController { Autowired private OrderService orderService; PostMapping(/create) public ResultOrderVO create(RequestBody Valid OrderCreateDTO dto, RequestHeader(Authorization) String token) { // 从 token 解析出 userId防止用户伪造 Integer userId JwtUtil.parseToken(token); OrderVO order orderService.createOrder(userId, dto); return Result.success(order); } GetMapping(/list) public ResultListOrderVO list(RequestParam Integer userId, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer pageSize) { // 分页查询page 从 1 开始 return Result.success(orderService.listByUser(userId, page, pageSize)); } }Valid注解加上 DTO 字段上的NotNull、Min等校验注解可以在请求到达 Service 之前就把非法参数拦下来。比自己在方法体里写一堆 if 判断干净得多答辩时也能体现出对参数校验的考虑。4.2 微信登录的完整链路wx.login 到 openid 再到 token微信小程序登录和后端 Session 登录最大的区别是小程序端没有 Cookie 机制。每次wx.request默认不带身份凭证所以这套项目采用了业界最常见的方案——用wx.login获取临时code后端拿code换openid自己生成 token 返回给前端前端把 token 存到 Storage 里后续请求都带上。// pages/login/login.js 中的微信一键登录 wx.login({ success: (res) { const code res.code wx.request({ url: ${app.globalData.baseUrl}/auth/login, method: POST, data: { code: code }, success: (response) { const { token, userId } response.data.data wx.setStorageSync(token, token) wx.setStorageSync(userId, userId) // 跳转到首页 wx.switchTab({ url: /pages/index/index }) } }) } })后端的/auth/login接口做的事有三步调用微信接口jscode2session用code换openid拿着openid查数据库查不到就自动注册新用户最后用 JWT 生成 token。这个 token 默认 7 天过期过期后需要重新登录。4.3 MyBatis 动态 SQL 处理订单列表筛选订单列表的查询条件往往是不固定的用户端按状态筛选「待付款、待发货、待收货、已完成」商家端按时间范围筛选。MyBatis 的if标签可以动态拼接 SQL根据传入参数决定是否追加条件。select idselectOrderList resultTypecom.flower.entity.Order SELECT * FROM orders where if testuserId ! null AND user_id #{userId} /if if teststatus ! null AND status #{status} /if if teststartTime ! null AND create_time gt; #{startTime} /if if testendTime ! null AND create_time lt; #{endTime} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签会自动去掉第一个多余的AND。gt;和lt;是 XML 里对、的转义写法直接写会报 XML 解析错误。分页用的是LIMIT #{offset}, #{pageSize}offset在 Service 层计算公式是(page - 1) * pageSize。如果数据量超过十万行这个写法会有深分页性能问题但鲜花电商的场景下数据量完全够用。4.4 商家后台的订单发货操作商家角色在后台管理页面操作订单发货本质上就是一次状态更新Service public class OrderServiceImpl implements OrderService { Override Transactional public void updateStatus(Integer orderId, Integer targetStatus) { Order order orderMapper.selectById(orderId); if (order null) { throw new BusinessException(订单不存在); } // 状态机校验只能从待发货(1)流转到待收货(2) if (order.getStatus() 1 targetStatus 2) { order.setStatus(2); orderMapper.updateById(order); } else { throw new BusinessException(非法状态流转); } } }状态机校验是这里最容易忽略的点。如果不做校验用户自己调接口把订单状态从待付款直接改成已完成业务就乱了。所以每一条状态流转路径都要显式判断合法性和权限——商家只能操作自己店铺的订单这个权限判断写在 Mapper 的 SQL 条件里AND merchant_id #{merchantId}用户端连传参的机会都没有。5. 小程序端渲染细节和常见报错5.1 WXML 条件渲染与订单状态展示订单列表页每个订单卡片要展示不同状态下的不同操作按钮WXML 里用wx:if和wx:elif控制。注意wx:elif的写法一个block标签内可以包含多个条件渲染节点且block本身不会被渲染成真实 DOM。!-- pages/order/list.wxml 订单卡片操作区 -- view classorder-card wx:for{{orderList}} wx:keyorderId view classorder-status block wx:if{{item.status 0}}待付款/block block wx:elif{{item.status 1}}待发货/block block wx:elif{{item.status 2}}待收货/block block wx:elif{{item.status 3}}已完成/block block wx:else已取消/block /view view classorder-actions button wx:if{{item.status 0}} bindtapcancelOrder style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />