ARTICLE DETAIL

资讯详情

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

Spring Boot面点连锁店管理系统:设计、实现与部署

Spring Boot面点连锁店管理系统:设计、实现与部署 每年计算机毕设高峰期“面点连锁店管理系统”这种题目一抓一大把但真正能把订单、库存、会员几条业务线捋清楚的项目十个里面未必能挑出三个。这个项目拿到手的时候我扫了一眼标题——Spring Boot 面点连锁店管理系统——心里大概就有数了它考的核心不是某个花哨的技术而是业务建模能力、表结构设计水平以及事务处理的基本功。面点连锁店和普通餐饮管理系统最大的区别在于“连锁”二字涉及多门店、统一商品库、跨店报表甚至原料配送和库存联动。用 Spring Boot 做单体架构既能覆盖这些核心场景又不会像微服务那样把毕设复杂度推向失控边缘。这篇文章我打算把整个系统从需求拆解、数据库设计、核心功能实现到部署调试中踩过的坑完整过一遍。无论你是准备拿这个题目开题还是正在找一套能看懂、能复现的毕业设计参考都应该能从里面找到能直接用的东西。源码部分我放在了文末后面几章我会对着具体代码讲清楚每个关键节点为什么这么写。1. 项目定位与核心需求拆解1.1 面点连锁店业务的痛点到底在哪面点行业有它的特殊性商品标准化程度高、保质期短、门店数量多但单个门店体量不大、早高峰订单集中。这意味着系统不能只做一个“进销存”还要考虑门店间的数据隔离、营业高峰期的高并发写入以及原料过期损耗的控制。我在设计这个系统时最先做的是业务痛点梳理而不是直接建表。面点连锁店最常遇到的几个问题总部不知道各个门店每天到底卖出多少只能靠门店手工报数原料采购靠店长拍脑袋经常出现馒头卖光了但面粉库存还有三百斤会员在各个门店不通用顾客办卡只能在本店消费总部想搞促销没法统一给所有门店下活动策略。这个毕业设计题目之所以年年有人做就是因为它具备完整的小型管理系统闭环商品、库存、订单、会员、报表五大模块相互咬合缺一块整个系统就不成立。而且每个模块单独拿出来难度不大组合起来却能很好地考察一个学生的综合设计能力。1.2 功能边界划分与模块拆解根据连锁面点的业务特征我把整个系统拆成六个核心模块基础数据管理门店信息、员工信息、供应商信息、商品分类与商品档案。这个模块主要解决“谁在卖、在哪卖、卖什么”的问题。库存管理原料出入库、成品库存查询、库存预警、采购订单管理。连锁店的核心在于统一采购、分别配送所以库存模型必须支持多门店独立库存。订单管理前台收银开单、订单状态流转、订单明细记录、退款与取消。这里要处理一个比较关键的点面点商品既有称重商品也有按个售卖的商品订单明细需要兼容两种计价方式。会员管理会员开卡、储值余额、积分累计、等级折扣。连锁场景下会员数据必须挂在总部门店下而不是某个门店单独持有。报表统计按门店按日期的销售汇总、商品销售排行、库存预警列表、会员消费频次。这部分是连锁管理者和指导老师最看重的功能模块也是答辩时最容易展开讲的内容。系统管理登录认证、JWT令牌、操作日志、数据字典。模块拆完之后我给这个项目定了一个技术基调Spring Boot 负责后端接口MyBatis-Plus 负责数据持久层MySQL 8 存储数据前端用 Vue 或者 Thymeleaf 模板引擎二选一。因为标题里明确写了 Spring Boot所以在技术选型上我不建议再引入微服务或者分布式中间件把单体架构做扎实才是正路。1.3 适合什么人参考这个项目这套系统最适合两类人。第一类是准备做 Java 方向毕业设计的学生需要一套完整且不过度复杂的业务系统作为参考能够从头到尾讲清楚模块设计和代码实现。第二类是求职面试前想补一下业务项目经验的人Spring Boot 的 REST API 开发、JWT 认证、MyBatis-Plus 操作、事务管理这些点在这个项目里都能得到实际训练。我也见过不少同学拿到类似题目后一上来就想把什么 Redis、RabbitMQ、Elasticsearch 全部塞进来。理由很简单简历好看。但毕设答辩的核心永远是业务逻辑的正确性和系统完整性你写一个订单模块连库存扣减都做不对用再多中间件也掩盖不了基本功的问题。这个项目让我最满意的地方就是全部功能用 Spring Boot 原生能力加一个 MyBatis-Plus 就能跑起来没有冗余依赖逻辑清晰非常适合作为单体业务系统的学习样本。2. 技术选型与项目搭建思路2.1 为什么是 Spring Boot 而不是 SSM 或微服务这个标题最有价值的一个词就是 Spring Boot。它解决了传统 SSM 整合时的大量 XML 配置问题内嵌 Tomcat可以直接打成 jar 包运行对于毕业设计这种需要快速出成果的项目再合适不过。而且 Spring Boot 的自动配置机制能帮你省掉至少三天的环境搭建时间把精力放在业务代码上。我在搭这个项目骨架时Spring Boot 版本锁定为 2.7.x而不是最新的 3.x。原因就一个3.x 版本基于 Jakarta EEjavax.servlet 包全部改名为 jakarta.servlet很多教程和第三方依赖如果没有升级会直接报 ClassNotFoundException。对于毕设项目稳定压倒一切Spring Boot 2.7 是老牌稳定版本参考资料最多遇到问题搜索引擎一查就有解决方案。这算是给后来者的一句真心话版本不是越新越好适合项目的才是对的。工程结构上我采用了标准的 maven 多包分层架构com.example.bakery ├── controller // 控制层接收请求参数并调用服务 ├── service // 业务层核心业务逻辑 │ └── impl ├── mapper // 数据访问层基于 MyBatis-Plus ├── entity // 实体类 ├── dto // 前端交互数据对象 ├── vo // 视图返回对象 ├── config // 配置类跨域、拦截器、分页插件 ├── common // 统一返回结果、异常处理、常量 ├── utils // JWT工具、日期工具等 └── BakeryApplication.java这种分包方式的好处是职责单一controller 里不写业务代码service 里不直接操作数据库每一层都有明确的接口边界。答辩的时候老师如果问“你这项目怎么体现分层思想”直接拿这个结构说就能加印象分。2.2 核心依赖与配置文件要点pom.xml 里的依赖我做了精确控制核心依赖只有五个spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、jjwt。这里重点说一下 MyBatis-Plus。很多人纠结用 JPA 还是 MyBatis我的建议是毕业设计选 MyBatis-Plus因为它提供了 BaseMapper 和 IService 接口单表 CRUD 不用写一行 SQL多表查询又可以自定义 XML灵活性和开发效率兼顾。application.yml 配置中有四个位置是必坑点spring: datasource: url: jdbc:mysql://localhost:3306/bakery_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto第一个坑是 serverTimezone 必须配置MySQL 8 驱动默认要求设置时区不写会报 CST 相关错误。第二个坑是 jackson 日期格式要显式声明否则 LocalDateTime 返回给前端是一串时间戳数组。第三个坑是 map-underscore-to-camel-case 这个配置必须打开不然数据库下划线字段无法映射到实体类驼峰属性。第四个坑是 mybatis-plus 的 id-type 设置为 auto不然主键策略不对插入数据时主键会乱生成。2.3 统一结果封装与全局异常处理整个系统的接口返回格式我统一设计为 Result 对象Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这样做的好处是前端只需要根据 code 字段判断请求是否成功不用每次去解析 HTTP 状态码。全局异常处理我用 RestControllerAdvice 实现业务异常和系统异常分开处理避免堆栈信息直接暴露给前端。这一层设计成统一规范后后面写所有接口都非常省事。对于自己搭项目的同学来说先把这个基础设施做好再开始写业务接口可以少走很多弯路。3. 数据库设计与关键表结构3.1 数据库设计的总原则围绕“单据 流水”展开面点连锁店管理系统最忌讳的就是把数据表设计成“一次性存量表”。我见过很多学生把订单表和订单明细表合成一张表把库存变动直接更新到商品表总数表面上看数据是对的但要查历史流水的时候就傻眼了。我的设计思路是“存量 流水”分离。商品表里的库存字段只是一个当前快照每一次库存变动都要写入库存流水表这样既能查当前值也能追溯。订单主表和明细表分离一个订单可以包含多个面点品种后面做报表统计聚合时只需要联查明细表。数据库整体分为八张核心表表名用途关键字段store门店表store_code, store_name, address, manager_id, statusemployee员工表emp_no, emp_name, store_id, position, phonecategory商品分类表category_name, sort_order, statusproduct商品表product_name, category_id, price, cost_price, unit, stock, warn_stockraw_material原料表material_name, unit, stock, warn_stock, supplier_idsupplier供应商表supplier_name, contact, phone, addressmember会员表mobile, member_name, level, points, balanceorders订单主表order_no, store_id, member_id, total_amount, pay_amount, discount_amount, pay_type, status, order_timeorder_item订单明细表order_id, product_id, product_name, quantity, price, subtotalstock_record库存流水表product_id, change_type, change_num, before_stock, after_stock, related_no, operator, create_time每个连锁店都要在商品表里通过 store_id 隔离库存而商品基本信息名称、售价、单位属于公共档案。这一点很关键连锁店的总部可以统一维护商品但各门店的库存数量独立计算。3.2 订单表状态机设计订单是整个系统的核心它的状态必须严谨。我设计了六个状态并通过 int 值存储状态值含义状态说明0待支付订单已生成但顾客尚未付款1已支付收款成功等待后厨制作2制作中后厨已经开始制作3待取餐制作完成等待顾客取餐4已完成顾客已取餐订单结束5已取消订单被取消库存回滚状态流转逻辑上待支付可以到已支付已支付到制作中制作中到待取餐待取餐到已完成。退款场景下已支付和制作中状态可以取消并回滚库存。我在代码里用 if-else 判断状态之间的合法性不允许跳级流转。比如一个已完成订单不能被改成待支付这个状态校验放在 service 层做避免数据库层面出现脏状态。为什么要用数字存状态而不是字符串因为数字在数据库里占用空间小、检索快写代码时用常量定义状态值还能防止字符串拼写错误。这也是一个可以拿到答辩上讲的设计细节。3.3 订单创建时库存扣减的核心 SQL订单创建是整个系统中最容易出 Bug 的地方关键在于“扣库存”和“生成订单”要在一个事务里完成否则会出现库存扣了但订单没生成或者反过来。我在实现扣库存时没有用“先查询再更新”的常规写法而是直接用一条带条件的 SQL 保证原子性UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}这条 SQL 妙就妙在 WHERE 条件里加了stock #{quantity}如果库存不足受影响行数为 0通过 MyBatis-Plus 的 update 返回值就能判断扣减失败从而在 Java 代码中抛出“库存不足”的异常并回滚事务。有些同学喜欢先 SELECT 查库存再判断够不够然后再 UPDATE。这在单用户测试时看着没问题但一旦有两个用户同时下单查询 A 的时候库存还有 5 个A 下单 3 个B 下单 3 个两个判断都通过了实际库存只剩 2 个最后就会出现超卖。事务加行锁虽然能解决一部分问题但直接通过数据库条件更新才是最简单可靠的方式。3.4 多门店库存隔离如何落表连锁系统的门店隔离如果做不好总部查询所有门店报表时就会数据串门。我在所有业务表里都加了一个 store_id 字段作为逻辑隔离查询时强制带上门店条件而不是依赖数据库的 schema 隔离。原因很简单一个数据库实例在毕业设计场景下足够支撑物理隔离会增加代码复杂度而且总监级别的跨店报表也没法做。源码工程里提供了一个 clear 权限拦截器登录用户信息存储在 ThreadLocal 中所有需要区分门店的 service 层方法会直接从登录用户上下文中取 store_id避免在 controller 层手动传递。这套设计在答辩时可以讲得很充实它体现了业务数据隔离意识。4. 核心功能模块实现细节4.1 登录认证与权限拦截登录功能我用 JWT 拦截器实现。用户输入账号密码后后端验证通过就生成一个包含用户 ID、门店 ID、用户角色的 token 返回给前端。前端在请求头里带上 token后端拦截器统一校验。JWT 的结构分为三部分头部、载荷、签名。我封装了一个 JwtUtils 工具类核心方法只有三个生成 token、解析 token、判断 token 是否过期。载荷里我放进去了 userId、storeId、role 三个字段这样后续业务方法中可以通过工具类随时获取当前操作者信息。拦截器注册我放在了 WebConfig 配置类中Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login) .excludePathPatterns(/api/auth/register); }要注意的是静态资源和登录接口必须放行否则前端页面都加载不出来。我也见过一些项目把登录拦截配置错了导致 token 校验失败循环跳转这个问题在后面的常见问题章节会展开讲。4.2 商品管理分类、规格与上下架商品模块做的是面点连锁店的“菜单中枢”。商品表里的关键设计是一个“售卖方式”字段用来区分按个卖和按重量卖。包子、馒头、烧麦按个卖单价固定桃酥、蛋糕这类散称商品按克计算。订单明细表里我加了一个 amount 字段同时支持两种模式按个卖就是数量乘以单价按重量卖是后端根据电子秤传来的克数自动计算金额。商品管理的 Controller 层我采用了 RESTful 风格路径为 /api/productPOST 创建商品、PUT 修改商品、DELETE 删除商品、GET 分页查询。上下架操作是单独两个接口上架时检查商品是否有对应分类下架时检查门店是否有未完成的订单涉及该商品。这个细节很多初学者会忽略直接 DELETE 商品会造成历史订单显示异常。4.3 下单流程从购物车支付到订单明细用户在收银台选择商品后系统生成一个临时订单数据提交到后端。核心的 createOrder 方法我用 Transactional 标注方法内部依次执行校验商品 ID 是否存在且处于上架状态计算订单总金额、折扣金额、实际支付金额扣减商品库存并记录库存流水生成订单主表和订单明细表如果是会员支付扣减会员余额并累计积分。这个顺序不能乱。先算金额再扣库存有一个好处如果金额计算失败不会白扣库存扣库存成功但订单生成失败时事务回滚库存自动恢复不需要手动补偿。我在项目里打印过完整的日志方便答辩时展示整个流程的执行顺序。支付方式字段我用 int 类型存储1 表示现金、2 表示微信支付、3 表示支付宝、4 表示会员储值卡。这里没有对接真实支付网关毕设阶段使用“模拟支付”即可在 service 层写一个支付处理分支不同支付方式走不同的校验逻辑。4.4 库存预警与原料联动库存预警是本系统的亮点功能。每个商品表里都有一个 warn_stock 预警阈值后台定时任务每秒扫描一次库存低于阈值的商品自动加入预警列表。为什么不用定时任务而是每次查询时顺便判断省一个分布式锁也避免定时扫描对数据库造成不必要的压力。预警列表查询 SQL 很直观SELECT * FROM product WHERE stock warn_stock AND status 1 ORDER BY stock ASC我在 service 层封装了一个 StockWarnVO返回结果中除了商品基本信息还带一个“建议补货量”计算公式为(warn_stock - stock) * 2保证补一次货能用较长时间。这个细节让功能不再只是简单的列表展示而是有业务逻辑的决策参考。原料联动的思路类似每个成品面点对应一张配方表当订单完成时系统根据配方自动扣减面粉、酵母等原料库存。不过考虑到毕设的展示重点我把这一部分设计成“手动一键扣料”模式店长在后台确认用料后执行扣减这样既能演示配方联动逻辑又不会因为配方没配置而报错。4.5 报表统计的三种典型写法报表模块直接决定了这个毕设是“及格分”还是“优秀分”。我实现了三个核心报表今日销售额按门店统计、商品销售排行 Top10、会员月度消费汇总。按门店统计今日销售额的 SQLSELECT s.store_name, COUNT(o.id) AS order_count, SUM(o.pay_amount) AS total_sales FROM orders o JOIN store s ON o.store_id s.id WHERE o.order_time CURDATE() AND o.status IN (1, 2, 3, 4) GROUP BY s.store_name ORDER BY total_sales DESC注意订单状态的过滤条件这里不能统计已取消的订单金额也不能用 total_amount因为退款和优惠会影响实际收入。经过这一层过滤后报表数据才是财务认可的营业额。商品销售排行 Top10 是通过 JOIN 订单明细表实现的同时按门店分组获得各店的畅销榜。会员消费汇总则使用 DATE_FORMAT 按月格式化时间让运营者看到会员的消费趋势。统计报表跑出来之后我还在后端缓存了同一时间段的汇总结果避免高频刷新时反复查库。5. 关键代码解析与实战复盘5.1 订单服务核心代码逐段拆解订单模块是代码里最复杂的一块我拆两段讲。第一段是订单主表的组装逻辑Override Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateDTO dto, Long storeId) { // 1. 校验门店是否存在 Store store storeMapper.selectById(storeId); if (store null || store.getStatus() 0) { throw new BusinessException(门店不存在或已停用); } // 2. 计算金额 BigDecimal totalAmount BigDecimal.ZERO; ListOrderItem itemList new ArrayList(); for (OrderItemDTO itemDTO : dto.getItems()) { Product product productMapper.selectById(itemDTO.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } BigDecimal subtotal product.getPrice().multiply(itemDTO.getQuantity()); totalAmount totalAmount.add(subtotal); OrderItem item new OrderItem(); item.setProductId(product.getId()); item.setProductName(product.getProductName()); item.setPrice(product.getPrice()); item.setQuantity(itemDTO.getQuantity()); item.setSubtotal(subtotal); itemList.add(item); } // 3. 组装订单主表 Orders order new Orders(); order.setOrderNo(generateOrderNo()); order.setStoreId(storeId); order.setTotalAmount(totalAmount); order.setDiscountAmount(dto.getDiscountAmount()); order.setPayAmount(totalAmount.subtract(dto.getDiscountAmount())); order.setPayType(dto.getPayType()); order.setStatus(OrderStatus.PENDING_PAYMENT); orderMapper.insert(order); // 4. 批量插入明细 itemList.forEach(item - { item.setOrderId(order.getId()); orderItemMapper.insert(item); }); return convertToVO(order); }逐段看下来这个方法的每一步都是可解释的为什么要先查门店因为要保证订单归属合法为什么要用 BigDecimal 而不是 double因为金额运算用浮点类型会产生精度丢失为什么 rel For rollbackFor Exception.class因为默认注解只回滚 RuntimeException而很多自定义异常可能继承自 Exception。这些都是答辩时老师喜欢追问的点。5.2 库存扣减与流水记录的组合写法库存扣减必须和生成订单在同一个事务中我把它放在订单插入之后执行。核心代码有两段第一段是扣减操作第二段是流水记录// 扣减库存 UpdateWrapperProduct updateWrapper new UpdateWrapper(); updateWrapper.eq(id, product.getId()) .ge(stock, quantity) .setSql(stock stock - quantity); int updateCount productMapper.update(null, updateWrapper); if (updateCount 0) { throw new BusinessException(商品【 product.getProductName() 】库存不足); } // 记录库存流水 StockRecord record new StockRecord(); record.setProductId(product.getId()); record.setChangeType(StockType.OUT); record.setChangeNum(quantity); record.setBeforeStock(product.getStock()); record.setAfterStock(product.getStock() - quantity); record.setRelatedNo(order.getOrderNo()); record.setOperator(operatorId); stockRecordMapper.insert(record);这里的 SetSql 用法比 Java 代码里先查询再 set 更高效因为它直接把“库存减数量”的操作下推到数据库。流水记录必须拿到扣减前的库存值这样才能保证 before_stock 和 after_stock 的连续性。一旦中途抛出异常整个事务回滚流水也不会留下脏记录。5.3 会员储值支付的事务边界会员支付比普通支付多一步余额扣减。我在账户模块中维护了一个 balance 字段会员消费时直接执行条件更新int updateBalance memberMapper.updateBalance(memberId, payAmount); if (updateBalance 0) { throw new BusinessException(会员余额不足); }updateBalance 对应的 SQL 是UPDATE member SET balance balance - #{payAmount} WHERE id #{memberId} AND balance #{payAmount}同样是条件更新防止并发超扣。会员积分累计则在支付完成后单独执行增加积分数值为实际支付金额的取整值这样能有效避免各模块之间互相干扰。5.4 定时任务与缓存加速的落地方式我用了 Spring Boot 自带的 Scheduled 注解实现了一个简单的库存预警定时任务。每天早上的营业前后各执行一次把低于预警值的商品写入一张预警通知表。定时任务的好处是实现成本低、不引入额外中间件完全符合单体项目的体量。查询缓存我用了 Spring 自带的 Cacheable 注解针对今日营业额这类高频统计接口做缓存Cacheable(value saleStats, key #storeId _ #date) public SaleStatsVO getDailySaleStats(Long storeId, String date) { // 查询数据库最多耗时300ms }缓存的 key 包含门店和时间保证不同门店不同日期的数据不会串。这类小优化在系统答辩中能体现对性能的考虑又不会因为引入 Redis 把环境复杂度提高一个量级。6. 常见问题与排查技巧实录6.1 Spring Boot 版本太高导致依赖冲突这个项目最开始我试过 Spring Boot 3.2结果 MyBatis-Plus 的启动器一直报警告原因是新版本使用 jakarta 命名空间旧版 mybatis-plus 还在用 javax。解决方法只有两个要么升级 mybatis-plus 到适配 Spring Boot 3 的新版本要么降低 Spring Boot 版本。我选择了后者。毕业设计的核心是业务代码不是给框架踩坑。Spring Boot 2.7.18 是 2.x 的最后一个版本安全更新也都合入了完全够用。如果你的电脑上装的是较新的 JDK 21建议在 pom.xml 里把 Java 版本调成 1.8 或 11避免编译报错。6.2 数据库连接 URL 引号粘贴错误这个坑看着不起眼但第一次运行必报。很多人复制 application.yml 里的数据库连接配置时URL 中间不小心多了空格或者把serverTimezoneAsia/Shanghai写成了serverTimezoneAsia/Shanghai但 mysql-connector 版本太老导致无法识别这个时区格式。经验法则MySQL 8 用com.mysql.cj.jdbc.DriverMySQL 5.7 用com.mysql.jdbc.Driver。URL 中的参数用连接在 yml 文件里不用转义但在 properties 文件里必须写为amp;。我第一次用 yml 就踩过这个坑项目能启动但接口查不到数据排查了半天才发现是配置文件的编码问题。6.3 分页查询不生效的典型症状MyBatis-Plus 的分页插件需要单独配置如果不加这个配置Page 对象分页数据能返回但 total 总数始终是 0。这个问题的排查方式很简单看控制台打印的 SQL如果只有一条查询语句说明拦截器没生效。正确配置方式Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }6.4 跨域请求被拦截前端如果单独使用 Vue 开发和后端接口不在同一个端口就会遇到跨域问题。我在 WebConfig 中配置了跨域映射规则允许所有来源访问 /api/**允许 GET/POST/PUT/DELETE 方法允许携带凭证。如果不允许携带凭证JWT 放在 Header 里的场景不受影响但如果把 token 存在 Cookie 里就会导致前端无法保存登录状态。另一个容易忽略的问题是拦截器和跨域配置的执行顺序。如果拦截器先于跨域处理执行那么带 token 的跨域预检请求会先被拦截器拦截返回 401导致真正的接口请求永远发不出去。解决方案是拦截器放行 OPTIONS 请求。6.5 中文乱码问题合集中文乱码在系统中出现在三个位置数据库表数据乱码、接口返回乱码、导出文件乱码。数据库层面创建表时统一使用 utf8mb4 字符集接口返回层面在 application.yml 配置 Jackson 编码文件导出层面使用 Excel 工具时设置 UTF-8 编码。最容易被忽略的是前端 axios 请求没有设置Content-Type: application/json;charsetUTF-8导致后端接收到的中文参数变成问号。我建议做毕设的同学在项目启动后第一件事就是写一个测试接口把中文参数原样返回验证编码链路是否通畅。如果这一步都有问题后面所有功能都会在中文输入上报错。6.6 问题速查表症状可能原因解决方案启动报 ClassNotFoundErrorSpring Boot 3.x 与 MyBatis-Plus 版本不匹配改用 Spring Boot 2.7.x数据库时间差 8 小时驱动时区未设置URL 加上 serverTimezoneAsia/Shanghai分页 total 为 0分页拦截器未注册配置 PaginationInnerInterceptor前端传中文变问号请求头缺少 UTF-8 编码设置 Content-Type: application/json;charsetUTF-8接口返回时间为一串数字Jackson 未配置日期格式配置 jackson date-format删除商品报外键错误订单明细表关联了商品下架代替删除库存为负数扣减 SQL 未加条件使用 stock #{quantity}7. 从开发到交付的几点体会这个项目前后花了我两周时间其中数据库设计占掉了一半。回头看最值得的不是写出来的代码量而是把“连锁”这个概念吃透了。做连锁店铺系统本质上是做一套数据隔离和汇总规则门店独立经营数据独立存储总部统一查看和管控。这个设计思路一旦想明白后面的编码只是时间问题。我特别想提醒的是毕设项目的代码能自己敲就自己敲哪怕照着源码抄也要一行一行抄完再理解。原因很简单答辩时老师会随机挑一个方法让你讲思路如果连核心订单流程都讲不清项目做得再漂亮也很难拿到理想的分数。看完这篇内容你可以直接对着源码把 orderService、stockService、reportService 这三个类逐行过一遍这三块是整个系统的灵魂。最后分享一个小技巧源码工程里我附带了一份接口文档把所有 REST 接口按模块分类列好了入参和出参这个对答辩准备非常有帮助。建议你拿到源码后先启动项目用浏览器调一遍所有接口再对照本文去读核心代码效果会比闷头看代码好得多。
返回列表