ARTICLE DETAIL

资讯详情

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

SpringBoot+微信小程序开发农产品电商系统技术详解

SpringBoot+微信小程序开发农产品电商系统技术详解 1. 整体设计思路拆解农产品销售这个场景我在做之前其实琢磨了很久。市面上的电商系统很多但农产品有几个天然的行业特点跟标准电商不太一样一是SKU极不标准比如“土鸡蛋30枚装”“当季蔬菜套餐”这种规格字段很难统一二是价格波动大今天地里摘的菜和明天行情完全不同三是用户群体里既有想买新鲜食材的城市用户也有吃不了辣要备注的细节需求。这些特点决定了我们不能直接套一个通用商城模板而是要围绕“产地直供、新鲜直达”这个核心来设计业务流。我选择SpringBoot搭后端、微信小程序做前端核心原因有两个。第一SpringBoot的生态足够成熟做一个小型电商系统SpringMVC、MyBatis-Plus、Redis、JWT这些组件组合起来非常顺手社区里踩坑案例多遇到问题搜得到答案。第二微信小程序对农产品这类“本地生活电商”的场景很友好用户扫描即用不用下载App尤其适合农村集市、社区团购这类流量分散的入口。整体架构上我采用的是前后端分离小程序端只负责展示和交互所有业务逻辑都收敛到后端接口这样后续如果要加管理后台或者做数据分析不需要动小程序端。项目整体功能划分为三个端用户端小程序、商家端小程序内嵌管理页面、管理端Web管理后台。用户端覆盖浏览商品、加入购物车、下单支付、订单跟踪、收货评价这条完整链路商家端处理商品上下架、库存管理、订单发货、售后处理管理端负责用户管理、类目管理、数据统计。三个端共享同一个后端服务通过角色权限来区分可见范围这样一套代码就能支撑完整的业务闭环。数据库设计是整个项目最需要花心思的地方。农产品电商的订单状态流转待付款、已付款、已发货、已完成、已取消和退款流程必须用状态机来管理不能靠散落的字段拼凑。我的做法是在订单表里设计了一个order_status字段配合status_time记录每次状态变更的时间戳再加一张order_log表专门记录操作轨迹这样无论是用户查询物流节点还是后台排查问题都能拿到完整的上下文。2. 核心功能模块与数据库设计2.1 功能模块划分农产品销售小程序的用户端功能我按用户的使用路径来划分浏览-下单-支付-收货-评价。每一环都有对应的小程序页面和后端接口支撑。浏览环节强调的是“逛”的体验。首页做了轮播图推荐当季主推品下方是分类导航比如“叶菜类”“根茎类”“水果类”“禽蛋肉类”。每个商品卡片上会突出显示产地信息和“今日直采”的标签这是农产品区别于标准电商的关键——用户买的不只是货还有“从地里到餐桌”的信任感。商品详情页除了展示图片和价格还有一个“产地溯源”板块用文案和图片介绍种植/养殖过程。这个功能开发成本不高但对转化率的提升非常明显很多用户就是冲着“看得见来源”才下单的。下单环节是业务逻辑最复杂的一块。购物车支持多商品合并结算结算页需要用户选择收货地址填写备注比如“不要切开”“要老一点的姜”然后生成订单。生成订单时后端要做三件硬校验一是库存是否足够二是商品是否处于在售状态三是价格是否与当前售价一致。第三点很容易漏但农产品价格波动大用户在购物车里放了三天再结算价格可能已经变了我们必须让前端展示实时价结算时后端再锁定价格否则就会出现“下单成功但付款金额对不上”的纠纷。商家端我嵌在小程序里以“商家工作台”的形式呈现。商家登录后可以看到今日订单数、待发货数、库存预警等统计卡片下方是订单管理列表支持按状态筛选。商家操作核心是两步发货填写物流单号或选择“自提”和商品管理改价、上下架、改库存。商家端的权限控制我用了简单的接口鉴权方式后端通过小程序登录拿到的openid映射到商家表再校验该商家的状态是否正常。管理后台我用的是一个独立的前端页面技术上就是常规的Vue Element UI后端接口和用户端共用一套Service层。管理后台提供用户管理、商品类目管理、全平台订单查询、退款审核、销售数据报表。这里想强调的是开发顺序上一定要先做好用户端核心链路再做商家端最后做管理后台。因为管理后台本质是“对数据的操作”如果业务数据模型都没稳定后台做出来也是返工。2.2 数据库表设计我设计数据库的原则是“够用但不冗余”。核心总共六张表外加三张辅助表就能支撑整个业务流程。下面把关键表的结构和设计思路说清楚。用户表user这张表存两类用户普通消费者和商家。关键字段是openid微信唯一标识、nickname、avatar、phone、role0消费者/1商家、status0正常/1禁用。openid一定要加唯一索引因为一个微信用户只会有一个openid登录时都是靠它来识别身份的。手机号字段不要做成必填农产品场景里很多用户不愿意绑定手机号我们通过微信授权拿到基本资料就够了。商品表product字段包括name、category_id、price当前售价、original_price原价用于展示划线价、stock库存、unit单位比如“斤”“份”“盒”、origin产地、description、cover_image、detail_images多图用JSON数组存字段类型设计为varchar解析时转成List。农产品的一个细节是unit字段很关键必须让用户明确知道“39元/5斤”和“8元/斤”的区别否则售后率会很高。订单表orders字段包括order_no订单号、user_id、merchant_id商家ID下单时根据商品归属写入、total_amount、freight_amount、receiver_name、receiver_phone、receiver_address、order_status0待付款/1待发货/2待收货/3已完成/4已取消/5待退款、remark。order_no我建议自己生成不要用自增ID直接暴露给用户格式可以是“yyyyMMddHHmmss 6位随机数”既好看又能避免别人通过订单号推测你的交易量。订单明细表order_item这张表记录订单里的每一项商品快照字段有order_id、product_id、product_name、product_image、price下单时的价格防止之后商品改价影响历史订单、quantity、subtotal。注意这里一定要做“快照”不能直接关联商品表去取名字和价格因为商品信息是会变的用户下单后三个月再来看订单必须看到下单时的信息而不是现在的。地址表address字段有user_id、receiver_name、receiver_phone、province、city、district、detail_address、is_default。农产品配送到村里或者小区自提点的情况很常见所以detail_address要允许长文本输入不要限制太死。购物车表cart就是一个简单的关联表user_id、product_id、quantity、checked是否勾选。设计时让用户在小程序端也可以用本地存储存购物车但考虑到换设备的问题我最终选择存后端数据库接口上做增删改查。辅助表包括category类目、banner轮播图、order_log订单操作日志。类目表很简单就是ID、名称、排序。日志表用于记录订单从创建到完成的每一次状态流转字段有order_id、operator、action、create_time这对排查问题非常有用。2.3 数据库设计的坑与心得这里分享几个实际踩过的坑。第一个是关于价格字段的类型刚开始我用的是double结果发现MySQL对浮点数计算会有精度丢失比如0.10.2算出来不是0.3订单金额对不上账。后来统一改成decimal(10,2)所有金额计算都转成BigDecimal来做问题彻底解决。第二个坑是库存字段的并发问题。农产品活动期间会有很多人同时抢购直接用“先查库存再扣减”的写法在高并发下会超卖。我的解决方式是扣减库存时用一条SQL原子操作UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}如果返回影响行数为0说明库存不足直接下单失败。这个方案简单不用引入分布式锁对于中小规模项目足够。第三个心得是关于“逻辑删除”和“物理删除”的选择。商品表我用了逻辑删除加一个is_deleted字段因为商品下架后历史订单仍然需要看到它如果物理删除关联查询会断裂。用户地址表则是物理删除因为地址数据没有历史意义删了也就删了。3. SpringBoot后端核心实现与代码讲解3.1 项目结构与分层设计后端项目我采用的是标准的Controller-Service-Mapper三层架构再加上config、entity、dto、vo、common几个包。整体结构如下src/main/java/com/agri/sale/ ├── common/ # 通用类Result响应体、异常处理、工具类 ├── config/ # 配置类WebMvc、Redis、MyBatisPlus、拦截器 ├── controller/ # 接口层 ├── dto/ # 入参对象 ├── vo/ # 出参对象 ├── entity/ # 数据库实体 ├── mapper/ # MyBatis-Plus的Mapper接口 ├── service/ # 业务逻辑 └── utils/ # 工具类JWT、日期处理等分层的核心价值在于“可替换性”。比如早期我用的是原生MyBatis写SQL后来发现MyBatis-Plus的IService接口自带CRUD方法能省一半代码量就切换到了MyBatis-Plus。如果代码都写在Controller里这种替换就是灾难。分层之后只需要改Mapper层继承的基类业务代码完全不用动。Result响应体我设计成统一格式{ code: 200, message: success, data: {...} }。前端小程序端和后端约定好当code为401时自动跳转登录页面为500时弹出错误提示。这里踩过一个坑一开始我用的是code1表示成功code0表示失败后来发现很多开源组件默认是HTTP状态码的语义容易混淆干脆统一改成200/500/401直白不易错。3.2 登录鉴权小程序登录与JWT机制小程序登录是后端接微信生态的第一道关卡。流程是小程序端调用wx.login()拿到临时凭证code传给后端/api/auth/login接口后端拿着这个code去请求微信的jscode2session接口换取openid和session_key然后用openid查用户表有记录就直接登录没记录就自动注册一个用户最后签发JWT令牌返回给前端。这里有个关键点服务端不能信任小程序端传过来的任何用户信息必须靠code去微信服务器换取。有些同学图省事让前端直接传nickname和avatar后端就存入了用户表这是有安全隐患的因为接口可以被伪造调用。JWT我用的jjwt库生成时把userId和role放进token的claims里设置7天有效期。然后写一个SpringMVC的拦截器在WebMvcConfig里注册排除登录接口和静态资源路径其余接口都要校验Authorization请求头。拦截器逻辑如下public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { try { Claims claims JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { // token过期或非法 } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSON.toJSONString(Result.error(401, 未登录或登录已过期))); return false; } }这个拦截器写法上要注意两个细节一是token从Authorization头里取要处理“Bearer ”前缀很多新手会忘记导致解析失败二是不要把CrossOrigin注解加在Controller上因为拦截器执行顺序在CORS处理之前直接返回401时前端拿不到跨域头会报跨域错误正确的做法是配置一个CorsFilter优先级调高。3.3 核心业务接口实现商品列表接口是一个典型的分页查询加多条件过滤。我用了MyBatis-Plus的LambdaQueryWrapper来动态拼接条件public PageProductVO getProductPage(ProductQueryDTO dto) { LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(Product::getStatus, 1) // 只在售商品 .eq(Product::getIsDeleted, 0) .eq(StringUtils.hasText(dto.getCategoryId()), Product::getCategoryId, dto.getCategoryId()) .like(StringUtils.hasText(dto.getKeyword()), Product::getName, dto.getKeyword()) .orderByDesc(Product::getCreateTime); PageProduct pageResult productMapper.selectPage(new Page(dto.getPageNum(), dto.getPageSize()), wrapper); // 转VO补充销量、评分字段 return convertToVO(pageResult); }LambdaQueryWrapper的好处是字段名写法是类型安全的编译期就能发现字段名拼写错误比直接写字符串的QueryWrapper更不容易出bug。分页插件配置也很简单MyBatis-Plus里添加PaginationInnerInterceptor到MybatisPlusInterceptor即可。下单接口是跨表的典型业务。我把整个下单逻辑放在一个方法里用Transactional注解保证原子性Transactional(rollbackFor Exception.class) public OrderVO createOrder(CreateOrderDTO dto, Long userId) { // 1. 从购物车或直接购买的商品列表构造订单明细 ListCartItem cartItems getSelectedCartItems(dto.getCartItemIds(), userId); if (cartItems.isEmpty()) { throw new BizException(请选择商品); } // 2. 计算总价读取商品最新价格 BigDecimal totalAmount BigDecimal.ZERO; ListOrderItem orderItems new ArrayList(); for (CartItem item : cartItems) { Product product productService.getById(item.getProductId()); if (product null || product.getStatus() ! 1) { throw new BizException(商品已下架 item.getProductName()); } // 3. 原子扣库存 int updated productService.deductStock(product.getId(), item.getQuantity()); if (updated 0) { throw new BizException(库存不足 product.getName()); } totalAmount totalAmount.add(product.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); orderItems.add(buildOrderItem(product, item.getQuantity())); } // 4. 生成订单主记录 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setOrderStatus(0); // 待付款 order.setReceiverInfo(dto.getReceiverInfo()); orderMapper.insert(order); // 5. 插入订单明细 orderItems.forEach(item - item.setOrderId(order.getId())); orderItemMapper.insertBatch(orderItems); // 6. 清理购物车已购项 cartMapper.deleteBatchIds(dto.getCartItemIds()); return OrderVO.from(order); }Transactional注解千万别漏了rollbackFor Exception.class因为Spring默认只在遇到RuntimeException时才回滚如果业务代码抛的是自定义Exception事务不会回滚数据就会半写入。我一开始在这个问题上栽过跟头有两个用户测试下单说扣了款但订单没生成查日志发现是地址信息解析报错事务没回滚库存已经扣了。后来加上rollbackFor就好了。订单支付回调这块我接的是微信支付。开发环境里没有真实商户号我实现的是“模拟支付”用户点击“去支付”后后端直接生成支付成功回调把订单状态改成待发货。生产环境接入时只需要把模拟回调的Controller替换成微信支付回调地址里解析XML的逻辑。这里想提醒一句支付回调处理幂等问题微信会多次通知同样的结果必须用“根据订单号查询当前状态如果是已支付就不要再重复更新”的方式做幂等校验。3.4 后端代码讲解文档怎么写源码交付时代码讲解文档和源码本身同样重要。我的习惯是每个核心类写一个短注释块说明这个类解决了什么问题、调用了什么外部依赖、核心方法的设计意图。比如在OrderServiceImpl这类关键业务类文件的类注释会这样写/** * 订单服务实现类 * 说明处理订单创建、支付回调、发货、确认收货、取消等操作。 * 核心设计所有状态变更都通过OrderStatusHandler接口分发避免if-else堆积。 * 依赖ProductMapper库存、CartMapper购物车、OrderLogMapper日志、WechatPayService支付。 * 注意createOrder方法的事务边界是完整的下单流程任何一步失败都会整体回滚。 */这种注释不是给机器看的是给接手这个项目的同学看的。用一句话说清楚“这个类是干嘛的”比贴一大段设计文档更高效。4. 小程序前端设计与接口联调4.1 页面结构与导航设计小程序端的目录结构我按页面来组织miniprogram/ ├── pages/ │ ├── index/ # 首页商品列表 │ ├── category/ # 分类页 │ ├── cart/ # 购物车 │ ├── order/ # 订单列表 │ ├── order-detail/ # 订单详情 │ ├── mine/ # 个人中心 │ ├── merchant/ # 商家工作台 │ └── login/ # 登录页 ├── components/ # 商品卡片、数量选择器、空态组件等 ├── utils/ │ ├── request.js # 封装wx.request │ ├── auth.js # 登录状态管理 │ └── util.js # 日期格式化等 └── app.js底部TabBar包含四个入口首页、分类、购物车、我的。这里踩过一个设计坑一开始觉得“商家工作台”是独立角色想做成单独的页面后来发现小程序底部TabBar固定最多5个而且每个tab对应的都是页面路径硬塞会造成普通用户也能看到入口。最后方案是“我的页面”里根据role字段动态展示“商家工作台”入口普通用户看不到商家点进去是独立页面。4.2 请求封装与登录态管理小程序端请求封装的核心是统一加Authorization头和处理401跳转。我写的request.js核心逻辑如下const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method: method, data: data, header: { Content-Type: application/json, Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data) } else if (res.data.code 401) { // token过期重新登录 wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject(new Error(未登录)) } else { wx.showToast({ title: res.data.message, icon: none }) reject(new Error(res.data.message)) } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) }微信小程序的wx.request不支持同步写法用Promise包一层是必须的。另外有个注意点wx.getStorageSync(token)在首次登录前是undefined而不是空字符串header里带undefined字符串会让后端解析token失败得先判断一下。我一般这样处理const token wx.getStorageSync(token) if (token) { header[Authorization] Bearer token }登录态管理还有个细节是登录页的静默模式。用户进入小程序时先检查本地有没有token有就直接进入首页没有就尝试调用wx.login拿code换token这个过程中不需要用户点击任何按钮。只有静默登录失败了比如微信接口异常才引导用户到登录页手动授权。这样用户体验最自然。4.3 商品列表、购物车与订单流程实现商品列表页面我用了onPullDownRefresh做下拉刷新onReachBottom做上拉加载更多。数据加载时会有一个loading状态防止重复请求还有一个finished状态标记是否还有下一页。列表数据结构是{ records, total, current, size }后端返回的是MyBatis-Plus的Page格式前端直接用current * size total判断是否加载完毕。购物车页面是一个典型的“本地状态远程同步”场景。每次加减数量、勾选商品前端先把状态记录到本地data里然后调用后端/api/cart/update接口同步。这里有一个体验细节不要每次操作都同步因为用户连续点“1”时如果每次都发请求接口压力大而且可能顺序错乱。我的做法是防抖用户点击后先本地更新UI延迟300ms再批量上报这样即使快速点击多次最终也只发一次同步请求。订单流程的页面逻辑是一环套一环的。订单列表页按状态筛选顶部有“全部/待付款/待发货/待收货/已完成”五个标签每个标签对应一个请求参数。订单详情页展示完整的地址、商品列表、金额明细商品总额、运费、实付、订单状态进度条。对用户来说最关心的两个动作是“去支付”和“确认收货”。支付按钮点击后调用/api/order/pay接口成功后刷新订单状态并跳转到订单详情确认收货则调用/api/order/confirm接口。这里要提醒一个容易踩的坑小程序的页面栈最多10层如果用户从订单列表进入详情再进入商品的详情页再返回路径会很长。我的做法是订单详情页的“再次购买”按钮直接跳转到/pages/index/indexreLaunch清空页面栈避免层级过深导致卡死。4.4 小程序与后端的接口联调经验联调阶段最容易出问题的是跨域和域名配置。开发环境中小程序开发者工具可以勾选“不校验合法域名”直接请求http://localhost:8080但真机预览时必须在小程序后台配置服务器域名而且必须是HTTPS。后端生产环境一定要配好SSL证书否则小程序真机请求会被拦截。另一个经验是关于接口返回数据格式的约定。我强烈建议一开始就定义好时间字段的格式统一用字符串yyyy-MM-dd HH:mm:ss返回不要在接口里返回时间戳因为小程序端Date对象处理时间戳虽然不难但时区问题和格式转换白白消耗开发时间。后端在实体类上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解全局统一前端拿到的直接是可展示的字符串。5. 源码部署全流程与部署文档编写5.1 环境准备部署这个项目需要的环境组件如下表组件版本建议用途JDK1.8运行SpringBoot应用MySQL5.7业务数据存储Redis5.0缓存、分布式锁本项目主要用作缓存Maven3.6构建打包Nginx1.18反向代理、静态资源服务微信小程序账号已注册小程序端发布做这个项目的时候我的服务器是2核4G的云主机跑这套系统绰绰有余。生产环境不要用java -jar直接丢那里推荐用systemd做进程守护这样服务挂了能自动重启。5.2 SpringBoot打包与配置首先在application.yml里配置数据源、Redis、JWT密钥等信息。配置文件我用了多环境方案application-dev.yml和application-prod.yml通过启动参数--spring.profiles.activeprod切换。打包命令很简单mvn clean package -DskipTests。这里提醒一下skipTests一定要加不然每次打包都要跑一遍单元测试耗时很长。打出来的jar包在target/目录下。部署时我推荐走systemd方式。新建/etc/systemd/system/agri-sale.service文件[Unit] DescriptionAgri Sale Application Afternetwork.target [Service] Typesimple Userroot WorkingDirectory/opt/agri-sale ExecStart/usr/local/java/bin/java -Xms256m -Xmx512m -jar /opt/agri-sale/agri-sale.jar --spring.profiles.activeprod Restartalways RestartSec10 [Install] WantedBymulti-user.target-Xms和-Xmx设置别贪大512M对于这个项目足够。设置Restartalways后服务崩溃10秒后会自动拉起。启动服务后用systemctl start agri-sale再执行systemctl status agri-sale查看状态。如果启动失败用journalctl -u agri-sale -f实时看日志排查问题。5.3 数据库初始化MySQL端需要做的操作分三步。第一步创建数据库并设置字符集CREATE DATABASE agri_sale DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第二步导入项目里提供的sql/init.sql文件这个文件里包含了建表语句和初始数据。第三步创建专用账号不要直接用root跑业务CREATE USER agri_user% IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON agri_sale.* TO agri_user%; FLUSH PRIVILEGES;初始化数据里通常会有一个管理员账号和几个测试商品、测试用户方便部署后立刻验收功能。5.4 小程序端发布配置小程序端在开发者工具里填上自己的AppID修改utils/config.js里的baseUrl为线上后端接口地址。注意线上的baseUrl一定是https://开头并且这个域名必须在微信公众平台后台完成“服务器域名”配置和ICP备案。发布流程是在开发者工具中点击“上传”填写版本号和备注然后在微信公众平台后台进入“版本管理”把刚上传的版本设为体验版用体验版二维码测试无误后再提交审核。审核周期一般1-7天不等。5.5 部署文档的编写规范源码附件里我提供的部署文档结构上遵循“环境准备-后端部署-前端部署-验收测试-常见问题”这个顺序。每个步骤都要包含具体的命令、预期输出和检测方法。比如“MySQL启动成功”这个步骤不能只写“启动MySQL”而要写清楚用什么命令启动、如何确认端口3306在监听、连接测试命令是什么。还有一点很重要部署文档里必须包含回滚方案。比如“如果新版本jar包启动失败执行systemctl stop agri-sale用/opt/agri-sale/backup/目录下的上一个版本jar包替换重新启动”。没有回滚方案的部署文档是不完整的真正出问题的时候临时䏍瞎找上一个包就是灾难。6. 常见问题与排查技巧实录6.1 经典问题速查表以下是我在开发和部署过程中遇到的高频问题整理成表格方便检索问题现象可能原因排查思路与解决方案启动报Failed to configure a DataSource依赖了spring-boot-starter-jdbc但没配置数据源检查application.yml里spring.datasource.url/username/password是否填写正确确认MySQL服务是否运行、账号密码是否OK小程序请求接口报“不在以下request合法域名列表中”微信小程序正式环境强制校验域名在微信公众平台配置服务器域名且必须是HTTPS开发者工具可暂时勾选“不校验合法域名”登录接口返回401token缺失或过期前端检查request.js里header是否正确携带Authorization后端检查拦截器排除路径是否正确下单成功但库存没扣事务没有生效检查方法上是否加了Transactional(rollbackFor Exception.class)检查Service方法是不是被同类内部调用Spring代理失效导致事务不生效商品图片加载不出来图片域名未在小程序后台配置微信公众平台后台“开发管理-服务器域名”中添加图片域名或把图片转成base64存后端不推荐存储开销大订单金额有精度问题数据库字段用了double/float统一改为decimal(10,2)Java中用BigDecimal做计算selectPage返回total为0但数据有值MyBatis-Plus分页插件未配置检查是否添加了PaginationInnerInterceptor到MybatisPlusInterceptor6.2 排查思路和工具线上问题排查时我习惯按这个顺序来第一步看日志。SpringBoot默认日志在控制台systemd部署可以用journalctl -u agri-sale -f。生产环境强烈建议配置logback-spring.xml按天滚动生成日志文件保留30天。日志里出现了ERROR直接搜索异常栈顶部的类名和方法名。第二步看接口响应。前端的Network面板里看请求URL、请求体、响应体。后端接口错误时返回的Result里一定有message字段大部分时候错误信息已经足够定位。第三步看数据库。如果日志里没有明显报错但数据不对直接连上MySQL看看订单表、商品表的数据状态。比如用户说“付款了没看到订单”很可能订单状态写的是待付款但支付回调没有更新成功手工确认状态即可。第四步用测试接口复现。我对每个核心接口都维护了一份curl测试命令方便快速复现问题。比如创建订单接口curl -X POST http://localhost:8080/api/order/create \ -H Content-Type: application/json \ -H Authorization: Bearer eyJhbGciOi... \ -d {cartItemIds:[1,2],receiverInfo:{name:张三,phone:13800138000,address:某某小区1栋}}如果curl能成功但小程序失败问题大概率在前端传参格式或header上反过来则问题在后端。6.3 代码讲解的授课技巧项目源码里附带代码讲解视频或文档讲解顺序我建议和开发顺序一致先讲入口Application启动类再讲配置application.yml、WebMvcConfig、再讲实体和Mapper、再讲Service、最后讲Controller。每讲一个Service方法都先让观众看数据库表结构因为代码的很多逻辑是跟着表结构走的。讲解时有句常用的话“这块代码的逻辑是业务规则决定的不是技术炫技”。比如库存扣减用一条SQL完成不是因为我喜欢SQL而是因为这是在高并发下保证数据一致性的最简单方案。让听众理解“代码是业务的翻译”比教他们背语法重要得多。6.4 保护好自己的源码开源一个项目源码里不要暴露真实敏感信息。部署文档里的application.yml示例密码用占位符比如your_mysql_passwordJWT密钥不要用真实值。我见过有些同学把阿里云AccessKey放到GitHub上被爬虫扫走后云服务器被拿来挖矿账单直接爆炸。这个教训值得放到第一位。6.5 后续扩展方向这个项目如果继续往下做有两条线值得投入。一条是做数据可视化大屏把销售数据、订单趋势、品类占比做成管理后台的首页这是管理方最直观的需求。另一条是做小程序的直播带货模块农产品直播是现阶段流量转化非常高的形式技术上需要加一个直播房间的数据模型和推流地址管理。从学习角度看这个项目的核心价值在于打通了从数据库设计、后端接口、前端页面到实际部署的完整链路知识密度刚好覆盖了一个全栈开发者需要掌握的大部分基础技能。把这个项目吃透再去接触微服务、消息队列这类进阶技术时会发现很多概念都是相通的。我在实际开发中最深的体会是这个项目的难点不在于某一个技术点有多深而在于把“农产品销售”这个业务场景翻译成代码时每个细节都要贴合真实使用场景。数据库的字段设计、接口的参数校验、前端页面的交互流程每一样都要从用户的角度倒推回来想清楚。技术是工具而让这套工具真正解决问题的是对业务的理解。希望这份讲解能让你少走一些我走过的弯路。
返回列表