首页 / 资讯中心 / 文章详情

SpringBoot+Vue快递驿站分发系统:入库核销与状态流转实战

SpringBoot+Vue快递驿站分发系统:入库核销与状态流转实战 ★ FEATURED ARTICLE
简介以菜鸟驿站快递分发为主题的管理系统采用 Java 技术栈构建面向正在准备毕业设计选题、需要可运行项目作参考的计算机专业本科与专科学生。压缩包共 385 个文件、约 10.65MB其中 111 个 java 源文件承载后端业务逻辑20 个 xml 与 2 个 sql 分别对应框架配置和数据库脚本前端部分由 8 个 vm 模板、16 个 js、2 个 scss 以及 vue、css 文件构成另含 162 个 svg 与 jpg、png 等图标素材3 个 bat 批处理脚本用于依赖安装与服务启动。目前已有 833 人学习下载。项目按需求分析、总体设计、详细设计到功能测试的流程展开总体设计覆盖系统功能、总体结构、数据结构与安全设计详细设计落实到数据库访问实现与核心模块关键代码并配套源码一份、数据库一份环境配置说明齐全可直接运行。读者可据此理解快递分发业务的模块划分、数据表组织与前后端交互方式也能作为论文撰写与答辩演示的实物支撑。1. 社区代收点下午六点的翻找现场这套快递分发系统管的是哪段活下午六点社区快递代收点的货架前挤了七八个人。三百多件快递堆在三排铁架上按楼栋粗略分区取件的人报手机尾号店员弯腰一层层翻翻错货架是常事翻半天找不到更常见后面排队的人开始叹气。快递驿站快递分发系统要做的就是把「人找件」换成「码找件」——入库时扫运单号登记系统按规则分配货架号和取件码出库时凭码核销谁取的、几点取的、有没有超期全部留痕。这类系统是计算机毕业设计里很典型的一类题目业务闭环完整既有增删改查也有状态流转和并发写入答辩时能讲的东西不少。它适合两类人正在找毕设题目、想找一个业务讲得清又不算烂大街的学生以及想给小型驿站搭一套轻量管理工具的开发者。下面按「能干什么、怎么跑、怎么改」的顺序把这份工程拆开讲。2. 技术栈与工程结构SpringBoot Vue 前后端分离怎么搭起来2.1 技术选型为什么毕设普遍走 SpringBoot MyBatis-Plus MySQL快递分发这个场景的技术诉求其实不复杂接口数量中等单表数据量在几万到几十万之间没有高并发抢购那种极端压力但要保证运单号不重复、取件码能查得到、状态不能乱。常见做法是后端用 SpringBoot 2.x 打底持久层用 MyBatis-Plus前端 Vue Element UI数据库 MySQL 8。这套组合在毕设里出现频率极高原因不是它多先进而是答辩时每一层都能讲清楚出问题也容易搜到答案。具体到分层Controller 只做参数校验和返回包装Service 承担业务编排Mapper 走 MyBatis-Plus 的 BaseMapper 加少量 XML 手写 SQL。MyBatis-Plus 的价值在于单表 CRUD 不用写 XML省下来的时间可以放在状态机和取件码这类真正需要设计的地方。如果你所在学校要求 SSM 原生或 Django这套思路完全可以平移核心是「入库、分配、核销」三段链路跟框架关系不大。我在类似项目里一般会额外加三样东西统一返回体ResultT、全局异常处理器RestControllerAdvice、以及一个BizException自定义异常。看起来是样板代码但后面排查问题时能省很多事——前端拿到的永远是{code, msg, data}结构不会一会儿 500 一会儿返回字符串。数据库连接池不用特意配SpringBoot 默认的 HikariCP 对毕设体量完全够。提示如果工程里用的是 SpringBoot 3.x注意javax.*已经换成jakarta.*老代码直接拷进来会编译不过这类报错跟业务无关别在业务代码里找半天。2.2 目录结构前后端两个工程怎么划分解压后通常是两个独立工程加一份 SQL 脚本后端和前端各管各的用接口通信。目录大致长这样express-station/ ├── station-server/ # 后端 SpringBoot 工程 │ ├── src/main/java/com/station/ │ │ ├── controller/ # 接口层ParcelController、ShelfController │ │ ├── service/ # 业务层ParcelService、NoticeService │ │ ├── mapper/ # MyBatis-Plus Mapper 接口 │ │ ├── entity/ # 数据库实体与表一一对应 │ │ ├── dto/ # 入参对象InboundDTO、PickupDTO │ │ ├── config/ # 跨域、拦截器、线程池配置 │ │ └── common/ # Result、BizException、常量枚举 │ └── src/main/resources/ │ ├── application.yml # 数据源、端口、MyBatis 配置 │ └── mapper/ # 手写 SQL 的 XML ├── station-web/ # 前端 Vue 工程 │ ├── src/ │ │ ├── api/ # 按模块拆的请求函数 │ │ ├── views/ # 入库页、取件页、货架管理页、统计页 │ │ ├── router/ # 路由与登录守卫 │ │ └── store/ # 登录态、当前门店信息 │ └── package.json └── sql/ └── station.sql # 建库建表 初始管理员账号后端entity和dto分开这一点值得保留。实体对应表字段DTO 对应前端提交的字段两者混用早期省事等要做参数校验、字段脱敏、多端复用时就会难受。前端api目录按业务模块拆文件比如parcel.js里放inbound、pickup、list三个函数页面里只调函数不直接写 axios改接口地址时只改一处。2.3 启动顺序先建库再起后端最后起前端顺序错了会白折腾。第一步把sql/station.sql导进 MySQL脚本里一般包含建库语句和初始账号数据第二步改application.yml的数据源第三步启动后端确认端口通第四步装前端依赖跑起来。server: port: 8080 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/express_station?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplserverTimezoneAsia/Shanghai必须带否则 MySQL 8 连接时区不匹配会直接抛异常characterEncodingutf8不加中文收件人姓名入库会变问号map-underscore-to-camel-case打开后数据库的pickup_code才能自动映射到实体的pickupCode关掉的话查出来全是 null这种问题最难查。最后一行日志实现建议只在开发期开答辩演示时控制台刷屏不好看。启动命令分三条# 后端在 station-server 目录下 mvn spring-boot:run # 前端在 station-web 目录下 npm install npm run serve前端如果配了 devServer 的请求转发就不用管跨域没配的话浏览器控制台会报 CORS 错误这时候要么在后端加CrossOrigin要么在前端vue.config.js里把/api转发到http://localhost:8080。登录账号一般在 SQL 脚本末尾的INSERT语句里密码多是明文或 MD5第一次进去先改掉。3. 核心链路拆解一件快递从扫码入库到取件出库的状态流转3.1 入库接口幂等、分配货架、生成取件码入库是整个系统的入口也是最容易写出并发 bug 的地方。前端扫一下运单号提交后端要做四件事判断这个运单号是不是已经入过库、挑一个有空位的货架、生成一个不重复的取件码、落库并触发通知。先看入参对象字段不用多够用就行Data public class InboundDTO { NotBlank(message 运单号不能为空) private String trackingNo; // 运单号全局唯一 NotBlank(message 手机号不能为空) private String phone; // 收件人手机号取件时核对用 private String shelfNo; // 货架号为空则由系统自动分配 private String expressCompany; // 快递公司用于统计 }Service 层的实现是整条链路的核心注意这里用Transactional包住写操作并且第一步就做幂等判断Transactional(rollbackFor Exception.class) public String inbound(InboundDTO dto) { // 1. 幂等同一运单号重复扫码直接返回已有取件码不重复入库 Parcel exist parcelMapper.selectByTrackingNo(dto.getTrackingNo()); if (exist ! null) { return exist.getPickupCode(); } // 2. 分配货架前端指定优先否则按剩余容量挑一个 String shelfNo StringUtils.hasText(dto.getShelfNo()) ? dto.getShelfNo() : shelfService.pickAvailableShelf(); // 3. 生成取件码冲突重试最多 5 次 String pickupCode null; for (int i 0; i 5; i) { String candidate pickupCodeGenerator.next(shelfNo); if (parcelMapper.countByCodeAndStatus(candidate, ParcelStatus.IN_STORAGE.getCode()) 0) { pickupCode candidate; break; } } if (pickupCode null) { throw new BizException(取件码分配失败请稍后重试); } // 4. 落库状态置为在库 Parcel parcel new Parcel(); parcel.setTrackingNo(dto.getTrackingNo()); parcel.setPhone(dto.getPhone()); parcel.setShelfNo(shelfNo); parcel.setExpressCompany(dto.getExpressCompany()); parcel.setPickupCode(pickupCode); parcel.setStatus(ParcelStatus.IN_STORAGE.getCode()); parcel.setInboundTime(LocalDateTime.now()); parcelMapper.insert(parcel); // 5. 通知异步发失败了不影响入库结果 noticeService.asyncSend(parcel); return pickupCode; }rollbackFor Exception.class不能省默认只回滚运行时异常受检异常抛出时事务不会回滚。第 1 步的幂等判断针对的是「店员手抖扫了两次」这种真实场景没有它就会产生两条记录、两个取件码取件时对不上。第 2 步货架分配如果做成「按剩余容量倒序取第一个」要注意并发下两个请求可能同时选到同一个货架常见做法是在更新货架占用数时加条件where used capacity并判断影响行数。第 3 步的循环重试是应用层的兜底真正保证唯一的是数据库唯一索引两层配合才可靠。第 5 步的通知必须异步短信接口响应慢的时候实测有几百毫秒的同步调用会把入库接口拖到一秒以上扫码枪连着扫十几件就卡住了。3.2 取件码生成规则为什么不用纯自增也不建议纯随机取件码是人和系统之间的唯一约定设计得好不好直接影响取件效率。常见的几种规则各有取舍规则示例优点问题纯自增序号0001、0002绝对不重复可被枚举隔天不重置会位数变长纯随机 4 位4271简单几千件在库时冲突率明显上升货架号 随机尾号A3-0427一眼看出在哪个货架需要保证格式统一日期 当日序号0521-0087便于按天清理跨天要单独维护计数器我一般用「货架号 4 位随机数」这一种因为取件时店员看一眼码就知道往哪排走减少了翻找环节Component public class PickupCodeGenerator { /** * 规则货架号去掉分隔符 短横线 4 位随机数 * 例A-3 货架 - A3-0427 */ public String next(String shelfNo) { int suffix ThreadLocalRandom.current().nextInt(1000, 10000); return shelfNo.replace(-, ) - suffix; } }ThreadLocalRandom比new Random()在多线程下少了竞争毕设体量其实感觉不出来但这是个不花钱的好习惯。nextInt(1000, 10000)是左闭右开区间保证结果一定是四位不会出现A3-42这种长短不一的情况前端展示时也好对齐。冲突率要心里有数单个货架下 4 位随机数的空间是 9000如果这个货架同时有 500 件在库按生日问题估算冲突概率已经不是可以忽略的量级。所以前面 Service 里的重试循环是必要的同时数据库上要有对应索引支撑查询。如果驿站体量再大把位数扩到 5 位或者改成「货架号 货架内自增序号」用 Redis 的INCR维护计数器规则稳定且天然唯一。3.3 出库核销用条件更新替代「先查后改」出库看似简单实际上是并发问题最集中的地方。两个人同时拿同一个取件码来取比如家人转发取件码如果实现成「先 select 判断状态是在库再 update 改成已取」两个请求都能通过判断最后都执行 update流水表里就会出现两条取件记录。正确做法是把状态判断塞进 where 条件里让数据库来做这件事Transactional(rollbackFor Exception.class) public void pickup(String pickupCode, String operator) { // 条件更新只有当前状态为「在库」才允许改为「已取件」 int rows parcelMapper.updateStatusByCode( pickupCode, ParcelStatus.IN_STORAGE.getCode(), ParcelStatus.PICKED_UP.getCode()); if (rows 0) { throw new BizException(取件码无效或该件已出库); } // 更新成功才记录流水保证一对一 PickupLog log new PickupLog(); log.setPickupCode(pickupCode); log.setOperator(operator); log.setPickupTime(LocalDateTime.now()); pickupLogMapper.insert(log); }对应的 SQL 只有一句靠在where里带上status 原状态实现乐观锁效果UPDATE parcel SET status #{toStatus}, pickup_time NOW() WHERE pickup_code #{code} AND status #{fromStatus};rows 0就是「没抢到」的信号此时直接抛业务异常让前端提示而不是继续往下走。MySQL 的 UPDATE 语句本身持行锁两个并发请求会串行执行第二个执行时status已经不是原值影响行数为 0天然幂等。这套写法比在应用层加锁简单得多也不依赖 Redis 之类的额外组件。状态流转建议用枚举固定下来不要在各处散落魔法数字public enum ParcelStatus { IN_STORAGE(1, 在库), PICKED_UP(2, 已取件), OVERDUE(3, 超期), RETURNED(4, 已退回); private final int code; private final String desc; // 构造方法、getter 略 }枚举里只允许「在库 → 已取件」「在库 → 超期」「超期 → 已取件」「超期 → 已退回」这四条边其他组合在 Service 里直接拒绝。把状态机画清楚后面加功能比如代取、暂存延期时才不会把状态图搞成一团乱麻。4. 数据与货架几张核心表怎么设计储位和超期件怎么管4.1 三张核心表快递、货架、取件流水表设计决定了后面能改到什么程度值得花时间。快递主表是核心字段和索引如下CREATE TABLE parcel ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, tracking_no VARCHAR(40) NOT NULL COMMENT 运单号, pickup_code VARCHAR(20) NOT NULL COMMENT 取件码, shelf_no VARCHAR(20) NOT NULL COMMENT 货架号, phone VARCHAR(20) NOT NULL COMMENT 收件人手机号, express_company VARCHAR(30) DEFAULT NULL COMMENT 快递公司, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在库 2已取 3超期 4退回, inbound_time DATETIME NOT NULL COMMENT 入库时间, pickup_time DATETIME DEFAULT NULL COMMENT 出库时间, PRIMARY KEY (id), UNIQUE KEY uk_tracking_no (tracking_no), KEY idx_pickup_code (pickup_code), KEY idx_phone (phone), KEY idx_status_inbound (status, inbound_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT快递记录表;uk_tracking_no是防止重复入库的最后一道闸Service 层的幂等判断失效时比如两个请求几乎同时进来select 都没查到唯一索引会让第二个 insert 直接报错事务回滚数据不会脏。idx_pickup_code支撑取件时的等值查询idx_phone支撑用户按手机号查自己的件idx_status_inbound是给超期扫描用的联合索引——先按状态过滤再按时间比较顺序不能反。这里有个坑要提前想运单号做单列唯一意味着一件快递退回之后想重新入库会冲突。常见处理是把唯一键改成(tracking_no, status)之类的组合或者干脆允许重复、在业务层按「最近一条在库记录」判断。前者改起来干净后者更贴近现实同一运单号确实可能对应多次入库我倾向后者但要把查询逻辑写对。货架表结构简单关键是容量和已用数CREATE TABLE shelf ( id BIGINT NOT NULL AUTO_INCREMENT, shelf_no VARCHAR(20) NOT NULL COMMENT 货架编号如 A-3, area VARCHAR(10) DEFAULT NULL COMMENT 所属区域, capacity INT NOT NULL DEFAULT 100 COMMENT 最大容量, used INT NOT NULL DEFAULT 0 COMMENT 当前占用, PRIMARY KEY (id), UNIQUE KEY uk_shelf_no (shelf_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT货架表;占用数的增减必须用条件更新避免出现used大于capacity或变成负数-- 入库占用一个位置满了就不更新 UPDATE shelf SET used used 1 WHERE shelf_no #{shelfNo} AND used capacity; -- 出库释放一个位置不允许减到负数 UPDATE shelf SET used used - 1 WHERE shelf_no #{shelfNo} AND used 0;判断used是否真的变化看返回的影响行数。入库时如果返回 0说明这个货架刚好被别人占满重新挑一个再来。这套写法不用加锁也不需要定时任务校准。取件流水表独立存放一是留痕二是统计时不用回查主表CREATE TABLE pickup_log ( id BIGINT NOT NULL AUTO_INCREMENT, pickup_code VARCHAR(20) NOT NULL COMMENT 取件码, operator VARCHAR(30) DEFAULT NULL COMMENT 操作人账号, pickup_time DATETIME NOT NULL COMMENT 取件时间, PRIMARY KEY (id), KEY idx_pickup_time (pickup_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT取件流水表;4.2 超期件处理定时扫描 状态迁移快递在驿站放太久占货架也占地方超过约定天数要标记超期甚至退回。做法是定时任务扫一遍在库且入库时间早于阈值的记录Scheduled(cron 0 0 9 * * ?) public void markOverdue() { // 阈值做成配置项别写死在代码里 LocalDateTime deadline LocalDateTime.now().minusHours(overdueHours); int count parcelMapper.markOverdue( ParcelStatus.IN_STORAGE.getCode(), ParcelStatus.OVERDUE.getCode(), deadline); log.info(本次扫描标记超期件 {} 件, count); }UPDATE parcel SET status #{toStatus} WHERE status #{fromStatus} AND inbound_time #{deadline};cron 0 0 9 * * ?表示每天上午九点执行一次。批量更新而不是逐条查出来再改几万条数据也就一次 SQL 的事。注意这条 SQL 同样带了status 原状态防止把已经被取走的件又标成超期。overdueHours用Value(${station.overdue-hours:72})注入默认 72 小时不同驿站规定不一样做成可配置省得改代码重打包。更严谨的做法是分批更新比如每次LIMIT 500循环执行避免一次性锁住太多行影响入库接口。毕设体量用不上但知道有这么回事被问到「数据量大怎么办」时能答上来。4.3 通知模块把短信和模板消息抽成接口通知这块最好做成可替换的。不同学校、不同驿站能拿到的通道不一样有的用短信服务商有的走模板消息硬编码在某处以后换起来要命。定义一个发送接口按渠道实现public interface NoticeSender { /** 该实现支持的渠道 */ boolean support(NoticeChannel channel); /** 发送取件通知失败不抛异常由调用方记录 */ boolean send(Parcel parcel); }NoticeChannel是个枚举SMS、TEMPLATE_MSG、NONE几个值。Service 里注入ListNoticeSender按配置的渠道挑一个执行找不到匹配的就静默跳过——毕设演示环境没有真实短信通道这时候不能因为发不出去就把入库流程搞挂。真正调用时套一层线程池Async注解配合一个自定义ThreadPoolTaskExecutor就行Async(noticeExecutor) public void asyncSend(Parcel parcel) { try { senders.stream() .filter(s - s.support(currentChannel)) .findFirst() .ifPresent(s - s.send(parcel)); } catch (Exception e) { // 通知失败只记日志不影响主流程 log.warn(取件通知发送失败单号{}, parcel.getTrackingNo(), e); } }线程池核心线程数不用大2 到 4 个足够队列容量给个几百。要配CallerRunsPolicy之外的拒绝策略队列满了直接丢弃并记日志比阻塞入库线程强。这块如果答辩时被追问「消息丢了怎么办」可以答「落一张通知记录表失败的定时重试」虽然毕设里通常没做但思路要有。5. 踩坑排查这套系统跑起来最容易翻车的五个地方5.1 环境和连接类启动就报错压根进不去第一种情况启动时抛Communications link failure或Unknown database express_station。原因是 SQL 脚本没执行或者执行了但库名和application.yml里写的不一致。别急着怀疑代码先登进 MySQL 执行show databases;看一眼库在不在、名字拼写对不对两分钟能定位。第二种情况连上了但报The server time zone value XXX is unrecognized。这是 MySQL 8 的时区问题连接串里没带serverTimezone。解决就是在 url 后面补serverTimezoneAsia/Shanghai。这个错误信息挺唬人其实就是少了个参数我第一次遇到时翻了半天驱动版本血泪经验。第三种中文全部变成???。两个地方要同时确认连接串有没有characterEncodingutf8建表语句有没有DEFAULT CHARSETutf8mb4。只改一处不够库、表、连接三层字符集要一致缺一层就乱码。已经乱掉的数据改完配置也恢复不了得重新导一遍。5.2 数据并发类取件码冲突和状态回滚现象是入库时偶发Duplicate entry A3-0427 for key uk_pickup_code日志里能看到唯一索引冲突。原因是应用层的「先查再插」在并发下不可靠两个请求可能都查到该码不存在然后都去插。解决分两层应用层保留重试循环捕获DuplicateKeyException后重新生成数据库层确保唯一索引真的建了。只做应用层重试不做唯一索引等于没兜底。另一个更隐蔽的现象用户明明取走了过一会儿状态又变回「在库」。原因是出库逻辑写成了「先 select 拿到对象改完 status 再 update by id」两个请求一个取件一个超期扫描同时进来后执行的把前面的覆盖了。解决就是把所有状态变更都改成带原状态条件的 UPDATEwhere status 期望的原状态影响行数为 0 就放弃。从那以后凡是涉及状态字段的写入我一律用条件更新不用updateById。5.3 业务边界类重复入库和货架占满现象是同一件快递在列表里出现两条取件码还不一样。原因通常是运单号没有唯一约束加上店员扫码时手抖扫了两次。解决是加uk_tracking_no同时 Service 入口先查一次返回已有取件码让重复扫码变成一个无感知的操作。还有个现象是入库时报「货架已满」但实际上货架上明显还有空位。原因是used字段长期没校准出库时释放失败或者根本没释放日积月累就虚高了。排查方法很简单写条 SQL 对比实际在库数和usedselect shelf_no, count(*) from parcel where status in (1,3) group by shelf_no;跟shelf.used一比就露馅。修的时候除了改数据还要回头看出库释放那段是不是漏了used 0的条件导致更新失败被吞掉了。最后一个是前端跨域控制台报has been blocked by CORS policy但后端明明加了CrossOrigin。多半是拦截器把 OPTIONS 预检请求也拦了没放行。在拦截器里判断HttpMethod.OPTIONS直接return true或者在vue.config.js里配 devServer 请求转发让同源请求绕开这个问题。6. 二次开发从能跑到能交我会在这几个地方动手拿到一份能跑的毕设工程最忌讳上来就大改架构。我的习惯是先按「数据层 → 接口层 → 页面层」的顺序通读一遍把入库、取件两条链路在纸上画一遍确认自己能说清楚每个字段从哪来到哪去再动手加功能。下面几个改造方向是我认为性价比最高的。第一个是加用户自助查询。现在很多这类系统只有管理员端取件全靠店员查。加一个不需要登录的查询页凭手机号后四位加运单号后四位查取件码接口层加个限流同一 IP 每分钟 10 次防止被枚举GetMapping(/public/query) public ResultString queryCode(RequestParam String phoneTail, RequestParam String trackTail) { // 只返回取件码不返回完整手机号和运单号避免信息泄露 Parcel parcel parcelMapper.selectByTail(phoneTail, trackTail); if (parcel null || parcel.getStatus() ! ParcelStatus.IN_STORAGE.getCode()) { return Result.fail(未查询到在库快递); } return Result.ok(parcel.getPickupCode()); }注意这个接口返回的数据要克制别图省事把整个Parcel对象扔回去。第二个是加统计看板。答辩时老师最爱问「这个系统有什么价值」一张图比十句话管用。核心指标就三个当日入库量、平均滞留时长、超期率。一条 SQL 全出来SELECT DATE(inbound_time) AS day, COUNT(*) AS inbound_cnt, AVG(TIMESTAMPDIFF(HOUR, inbound_time, IFNULL(pickup_time, NOW()))) AS avg_stay_hours, SUM(CASE WHEN status 3 THEN 1 ELSE 0 END) / COUNT(*) AS overdue_rate FROM parcel WHERE inbound_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(inbound_time) ORDER BY day;第三个是多门店支持这是把「毕设」往「能用」推一步的关键改造。加一个station_id字段所有查询带上门店过滤管理员账号绑定门店。改动量集中在 Mapper 的 where 条件和管理端的门店切换工作量可控但故事讲得通。改造点改动范围难度答辩加分用户自助查询新增 1 接口 1 页面低中统计看板1 统计接口 图表组件中高多门店支持全表加字段 查询改造中高高取件码规则可配抽策略接口 配置项低中改完一定要做一遍回归验证我自己的清单是新入库 3 件不同货架的、重复扫同一个运单号、并发点两次出库、超期扫描跑一次、切换门店后列表是否隔离。这五步走完基本能确认没把原来的链路改坏。从那以后我每次动核心表结构之前都强制先把数据库导一份备份出来改坏了能一键回滚这个习惯救过我至少两次。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站