简介:一套基于微信平台的鲜花销售微信小程序项目资源,包含完整源码和配套说明文档,面向小程序开发初学者、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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单主表 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;订单表里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字段是List<OrderItemDTO>,MyBatis 层面需要做批量插入。批量插入的 SQL 写法是:
<insert id="batchInsertOrderItems"> INSERT INTO order_item (order_id, flower_id, flower_name, price, count, subtotal) VALUES <foreach collection="list" item="item" separator=","> (#{item.orderId}, #{item.flowerId}, #{item.flowerName}, #{item.price}, #{item.count}, #{item.subtotal}) </foreach> </insert>foreach的separator=","会把多条记录拼进一条 INSERT 语句,比循环单条插入快得多,而且能保证同一个订单的明细要么全部插入成功、要么全部失败。这层事务控制在 Service 层加上@Transactional注解,一旦任何一条明细插入失败,整个订单创建回滚,不会出现订单主表有记录但明细缺失的脏数据。
4. SSM 后端接口设计与微信登录态处理
4.1 Controller 层的统一返回格式与参数接收
后端的 Controller 层在com.flower.controller包下,每个业务模块对应一个 Controller。以订单模块为例,接口列表是这么设计的:
| 接口路径 | 方法 | 参数格式 | 说明 |
|---|---|---|---|
/order/create | POST | JSON Body | 创建订单 |
/order/list | GET | Query String | 按用户查订单列表 |
/order/detail | GET | orderId | 订单详情 |
/order/cancel | POST | orderId | 取消订单 |
/order/updateStatus | POST | orderId+status | 商家发货或确认收货 |
Controller 的写法遵循一个约定:参数用 DTO 接收,返回统一用Result<T>包装。Result<T>这个泛型类包含code、message、data三个字段,每个接口方法体里只写业务逻辑,异常处理交给@ControllerAdvice全局捕获。
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public Result<OrderVO> 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 Result<List<OrderVO>> 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 id="selectOrderList" resultType="com.flower.entity.Order"> SELECT * FROM orders <where> <if test="userId != null"> AND user_id = #{userId} </if> <if test="status != null"> AND status = #{status} </if> <if test="startTime != null"> AND create_time >= #{startTime} </if> <if test="endTime != null"> AND create_time <= #{endTime} </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select><where>标签会自动去掉第一个多余的AND。>=和<=是 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 class="order-card" wx:for="{{orderList}}" wx:key="orderId"> <view class="order-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 class="order-actions"> <button wx:if="{{item.status === 0}}" bindtap="cancelOrder" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />