☰
Java物流管理系统实战:状态机与并发控制的工程实践
2026/10/8 22:05:30 网站建设 项目流程

简介:基于Java的物流管理系统设计与实现文档,是一份面向计算机专业毕业设计、课程设计及物流信息化初学者的完整参考资料。文档以电商与物流行业高速发展为背景,围绕客户信息管理、物流信息管理、客户订单管理与货物配送管理四大核心模块,展示了基于JSP与B/S架构的物流管理系统从需求分析、可行性论证、数据库E-R图设计、数据表构建到功能实现与乱码处理的全流程,可直接作为系统开发或论文撰写的框架参考。压缩包内含1个doc文档,约1.09MB,内容结构完整,包含中英文摘要、目录、绪论、功能模块需求详解、性能与界面需求、数据库设计及系统实现等章节,便于系统阅读与二次加工。目前已有1706人学习浏览,适合需要快速掌握物流管理系统设计思路、查阅JSP项目开发文档规范,或借鉴订单管理、配送管理等业务模块设计的读者使用。

1. 基于Java的物流管理系统到底在做什么:一张业务图画清边界与职责

看到这个标题,你八成是在赶一门课程设计,或者在准备毕业设计的开题。先说一个反直觉的结论:这类系统的难点从来不在增删改查,而在状态流转和并发控制。一张运单从创建、分配车辆、出库、送达、签收,每一步都在改状态,任何一步没校验,后面的统计报表就全是脏数据。这篇笔记按“选型 → 表设计 → 核心代码 → 接口约定 → 排坑 → 进阶”的顺序,把一套能在本地跑通的 Java 物流管理系统讲透,适合需要在一两周内交出可演示成果的学生,以及刚接手供应链模块的初级后端工程师。

2. 技术选型为什么这么定:Spring Boot + MyBatis-Plus + MySQL 的组合逻辑与扩展点

标题里只有“Java”这个硬约束,但真动手时第一个要决策的就是框架组合。常见做法是 Spring Boot + MyBatis-Plus + MySQL,这套组合在课程设计和中小团队里几乎是默认答案,原因不是它最潮,而是它最能平衡开发速度和答辩可解释性。

2.1 为什么 Spring Boot + MyBatis-Plus 是课程设计与中小团队的主流组合

先解决“为什么不用 SSM/SSH”的问题。SSH 的年代已经过去了,光 XML 配置就得写小半天,Spring Boot 的自动装配和内嵌容器把环境搭建压到一条mvn spring-boot:run命令,这对要在两周内出成果的场景太关键。MyBatis-Plus 的价值在于把单表 CRUD、分页、逻辑删除都做了封装,你不需要为每个实体类单独写 mapper XML,实体类加几个注解就能直接用。

一个很实用的做法是:用实体类注解 + MyBatis-Plus 的TableName、TableId、TableField直接生成创建表的 SQL 语句。这也是网上搜“mybatisplus 根据 java 实体类生成创建表的 sql 语句”的人真正想要的东西。开发阶段我可以先写实体类,把字段和注释定义清楚,再用工具生成 DDL,比手写 SQL 再对着改实体类快得多。下面2.2的表设计就是按这个思路来的。

这套选型的边界也要说清楚。MyBatis-Plus 的单表能力很强,但一旦出现多表联查加复杂统计,它生成的 SQL 执行计划往往不是最优的,我一般会退回写原生 SQL。另一个边界是性能:单表数据量上百万之后,分页插件默认的LIMIT offset, size会越翻越慢,到时需要换游标或覆盖索引方案。这些等系统真正上线再处理,课程设计阶段不用过度设计。

2.2 数据库表设计:订单、运单、库存、车辆、用户五张核心表的关系

物流管理系统的核心表不需要设计得太多,先把五张表定下来:用户表(管理员、司机、客户)、订单表、运单表、车辆表、库存表。其中订单和运单是拆开的,一个订单可能因为商品拆在不同仓库而被拆成多个运单,这种一对多关系是后续状态机设计的基石。

表名职责关键字段
t_user登录、权限id、username、password、role
t_order客户下单id、order_no、customer_id、status、total_amount
t_waybill运输任务id、waybill_no、order_id、vehicle_id、driver_id、status
t_vehicle车辆台账id、plate_no、model、load_capacity、volume、status
t_inventory商品库存id、product_id、warehouse_id、stock、version

下面给出订单表和运单表的 DDL 片段,注意字段类型的选择理由。

CREATE TABLE t_order ( id BIGINT PRIMARY KEY COMMENT '主键', order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号', customer_id BIGINT NOT NULL COMMENT '客户ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待支付,1待发货,2运输中,3已签收,4已取消', total_amount DECIMAL(10, 2) NOT NULL COMMENT '订单金额,单位元', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_customer (customer_id), KEY idx_status (status) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '订单表'; CREATE TABLE t_waybill ( id BIGINT PRIMARY KEY COMMENT '主键', waybill_no VARCHAR(32) NOT NULL UNIQUE COMMENT '运单号', order_id BIGINT NOT NULL COMMENT '订单ID', vehicle_id BIGINT COMMENT '分配车辆ID,未分配时为空', driver_id BIGINT COMMENT '司机ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待分配,1已分配,2已出库,3运输中,4已签收,5异常', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_order (order_id), KEY idx_status (status) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '运单表';

金额字段用DECIMAL(10, 2)而不是FLOAT,这是血泪经验:浮点类型在累加运费和结算时会产生精度误差,对账差一分钱都很难查。状态字段用TINYINT加注释,代码里再用枚举对应,而不是直接存中文,查询和索引都更友好。库存表里我预留了version字段,这是为后续3.3的乐观锁做的准备,并发扣库存时它会派上用场。时间字段统一用DATETIME,配合代码里的LocalDateTime,可以避开后面5.1讲到的时区坑。

2.3 分页、鉴权、日志这三件套怎么落地

没有这三个基础能力,后面的功能模块再多也都是空中楼阁。分页我直接用 MyBatis-Plus 的分页插件,不再手写LIMIT;鉴权用拦截器做简单的 Token 校验;日志用 Logback 按级别输出。

统一返回体是前后端联调的第一步,下面是我常用的封装。

@Data public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 0; r.message = "success"; r.data = data; return r; } public static <T> Result<T> fail(int code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }

code = 0约定为成功,非 0 为业务失败,HTTP 状态码在协议层处理,业务层不建议直接用 400、500 这种语义来区分。下面是分页插件的配置。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }

setMaxLimit(500L)这个参数值得注意,它把单次查询条数上限卡在 500,防止有人拿pageSize=10000把数据库拖垮。鉴权这块,课程设计阶段没必要引入 Spring Security 全家桶,我用一个HandlerInterceptor拦截/api/**,放行/api/login,从请求头取 Token 再查 Redis 或内存缓存。

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token == null || !TokenStore.validate(token)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } return true; } }

这套实现没有引入第三方的东西,答辩时讲师问“登录怎么做”你能从头讲到尾。日志方面,我在logback-spring.xml里把 SQL 日志单独开了一个 logger,开发环境打印、生产环境关掉,因为 MyBatis 的 SQL 日志在高并发下会刷屏到影响性能,这是常见误用。

3. 后端核心模块实现:从运单创建到车辆分配的最小可运行代码

第二章把地基打好了,这一章进入业务的核心链路。我会按照一笔订单从生成到派车的顺序给出可运行的 Java 代码,并解释为什么这样写、参数怎么调、以及哪些地方最容易写错。

3.1 运单创建的服务层:事务与状态机

订单创建成功后,系统要生成一个初始状态的运单。这里最容易犯的错是“想改状态就改状态”,比如把待分配的运单直接改成运输中,中间完全没有任何校验。为了避免这种脏数据,我维护了一个状态流转矩阵,每次变更前先做合法性校验。

@Service public class WaybillService { private static final Map<Integer, Set<Integer>> STATE_MACHINE = new HashMap<>(); static { STATE_MACHINE.put(0, Collections.singleton(1)); // 待分配 -> 已分配 STATE_MACHINE.put(1, Collections.singleton(2)); // 已分配 -> 已出库 STATE_MACHINE.put(2, Collections.singleton(3)); // 已出库 -> 运输中 STATE_MACHINE.put(3, Collections.singleton(4)); // 运输中 -> 已签收 STATE_MACHINE.put(3, Collections.singleton(5)); // 运输中 -> 异常 } @Transactional(rollbackFor = Exception.class) public Waybill createWaybill(Order order) { Waybill waybill = new Waybill(); waybill.setOrderId(order.getId()); waybill.setStatus(0); waybill.setWaybillNo(generateWaybillNo()); waybillMapper.insert(waybill); return waybill; } public void changeStatus(Long waybillId, Integer targetStatus) { Waybill waybill = waybillMapper.selectById(waybillId); Set<Integer> allowed = STATE_MACHINE.get(waybill.getStatus()); if (allowed == null || !allowed.contains(targetStatus)) { throw new BizException("当前状态不允许流转到目标状态"); } waybill.setStatus(targetStatus); waybillMapper.updateById(waybill); } }

@Transactional(rollbackFor = Exception.class)这个参数不是随便写的。Spring 默认只在运行时异常时回滚,如果业务里抛的是受检异常,不加rollbackFor就会造成事务不生效的假象。事务的粒度也要控制:把状态校验和updateById放在同一个事务里,保证“校验通过则必更新成功”。不要在事务里做远程调用或长时间 IO,否则连接池会被占满,这是大事务带来过的教训。

3.2 车辆路径分配的贪心算法实现与边界处理

运单进入待分配状态后,后台要做车辆匹配。完整的最优路径规划是一个车辆路径问题(VRP),直接上启发式算法对课程设计来说复杂度太高,而且数据量不到那个规模时效果不见得更好。我一般用贪心策略:按车辆剩余载重和距离评分排个序,选最合适的一辆。

public void dispatch(Long waybillId) { Waybill waybill = waybillMapper.selectById(waybillId); List<Vehicle> candidates = vehicleMapper.findAvailable(); candidates.sort(Comparator .comparing(Vehicle::getLoadCapacity).reversed() .thenComparing(v -> calcDistance(v, waybill))); Vehicle picked = candidates.stream() .filter(v -> v.getLoadCapacity().compareTo(waybill.getTotalWeight()) >= 0) .findFirst() .orElse(null); if (picked == null) { waybill.setStatus(5); // 没有可用车辆,标记为异常并人工介入 waybillMapper.updateById(waybill); return; } picked.setStatus(1); // 车辆状态置为占用 vehicleMapper.updateById(picked); waybill.setVehicleId(picked.getId()); waybill.setStatus(1); // 运单状态置为已分配 waybillMapper.updateById(waybill); }

这里排序用了Comparator.comparing(...).reversed(),Java 的排序逻辑对新手容易踩坑:.reversed()只作用于前一个Comparator而不是整个链,所以如果你想“载重降序、距离升序”,要写成先comparing().reversed()再thenComparing()。载重过滤放在排序之后也一样能选出合格车辆,但先过滤再排序可以减少无效比较。

边界处理也要提前想好。车辆状态必须是“空闲”才能进候选池,不然会出现一趟车被分配两个运单的情况;超载校验用compareTo而不是直接减,避免精度问题;没有可用车辆时不能静默失败,我把运单置为异常状态,让调度员人工兜底。这套逻辑对课程设计的演示场景足够,答辩时解释“为什么用贪心不用动态规划”反而比复杂算法更好讲。

3.3 库存扣减与回滚:乐观锁还是悲观锁

物流系统里库存扣减是最容易出现并发问题的地方,也是面试官最常追问的点。核心逻辑是:扣减库存和生成运单必须放在同一个事务里,任何一步失败整体回滚,否则会出现“运单建了但库存没扣”的数据不一致。库存扣减先看三条 SQL。

-- 乐观锁实现:先比较版本号再扣减 UPDATE t_inventory SET stock = stock - #{num}, version = version + 1 WHERE id = #{id} AND stock >= #{num} AND version = #{version};
@Transactional(rollbackFor = Exception.class) public void deductStock(Long productId, Integer num) { int retry = 0; while (retry < 3) { Inventory inventory = inventoryMapper.selectById(productId); int rows = inventoryMapper.deductByVersion(inventory.getId(), num, inventory.getVersion()); if (rows > 0) { return; } retry++; // 让出 CPU,避免两个线程同时重试 Thread.sleep(10L + retry * 20L); } throw new BizException("库存扣减失败,请稍后重试"); }

stock >= #{num}这个条件保证了不会扣成负数,是防超卖的第一道关卡。乐观锁失败时如果直接抛异常,在高并发下会有大量无谓的事务回滚,所以我用重试机制,重试次数设为 3 次,退避时间递增,给其他线程提交的机会。

什么情况下用悲观锁?当你面对看似极其集中的单 SKU 热点(比如秒杀场景),乐观锁重试会导致 CPU 空转,这时候SELECT ... FOR UPDATE配合索引反而更可靠。普通物流系统我首选乐观锁,因为它不加锁、吞吐高,而且 stock 字段更新频率不高时,版本冲突概率很低。

4. 接口约定:管理端与司机端的 RESTful 规范,前端才好接

后端写得再漂亮,接口文档对不上前端也白搭。这一章说的是前后端联调的接口规范,以及几个传参上的隐蔽坑,按我的经验,把这些定好可以少一半无意义的返工。

4.1 业务码与统一返回体:列表、详情、操作类接口怎么定

管理端和司机端功能不同,但接口风格应该一致。列表接口用GET /api/waybill/page,详情用GET /api/waybill/{id},操作类用POST,这样前端套 axios 封装时逻辑统一。

功能方法与路径说明
运单分页列表GET /api/waybill/page支持状态、时间范围筛选
运单详情GET /api/waybill/{id}返回运单及关联订单信息
创建运单POST /api/waybill由订单生成运单
运单签收POST /api/waybill/{id}/sign司机端操作
库存查询GET /api/inventory/page分页加仓库筛选

统一返回体里我保留了业务码字段,约定是这样:0表示成功,10001参数错误,10002未登录或登录过期,20001库存不足,20002运单状态不允许操作。前端 axios 拦截器只看code,不是 0 直接提示message。下面是一个运单分页接口的响应示例。

{ "code": 0, "message": "success", "data": { "list": [ { "waybillNo": "WB20250101001", "status": 3, "createTime": "2025-01-01 10:30:00" } ], "total": 128, "pageNum": 1, "pageSize": 20 } }

时间字段我要求后端全部格式化成String返回,前端不需要再做时区换算。分页参数统一叫pageNum和pageSize,前端组件也按这个约定封装,避免出现不同接口一个用page、一个用current的混乱局面。

4.2 前端联调必踩的传参坑:数组、日期、Long 精度与文件下载

第一个坑是 JavaScript 数字精度丢失。MyBatis-Plus 默认的雪花 ID 是 19 位数字,JavaScript 的Number类型精确表示范围只有 16 位,ID 直接传回前端会变成类似1941524481219086300的失真值,拿这个值再查详情必然查不到。后端处理很简单,让 Jackson 把 Long 类型序列化为字符串。

@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer longToStringCustomizer() { return builder -> { builder.serializerByType(Long.class, ToStringSerializer.instance); builder.serializerByType(Long.TYPE, ToStringSerializer.instance); }; } }

第二个坑是数组参数的序列化方式。前端传数组给后端时,如果直接用params: { ids: [1, 2] },axios 会序列化成ids[]=1&ids[]=2这种格式,Spring MVC 默认绑不上。要么后端用List<Long> ids接收并配合@RequestParam("ids[]"),要么前端用ids.join(',')转成逗号分隔字符串。我倾向于后者,简单且日志里好看。

第三个坑是 Excel 导出。前端如果用response.data直接拿导出接口的返回值,得到的是一个乱码对象,因为响应体是二进制流。正确做法是先指定responseType: 'blob',再手动创建下载链接。

export function exportWaybill(params) { return request({ url: '/api/waybill/export', method: 'get', params, responseType: 'blob' }).then(response => { const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.download = 'waybill.xlsx'; link.click(); window.URL.revokeObjectURL(url); }); }

有人问“java 后端怎样和产品经理确定接口字段”,我的习惯是:先写一个最小可用的接口文档给前端对着写,字段名定下之后不轻易改,因为前端页面绑定字段的成本比后端改字段高得多。真到了不得不改的时候,先同步前端负责人再改,避免“后端悄悄改了字段、前端上线才报错”的事故。

5. 上线前最容易翻车的五个排查点:时区、序列化、并发、导出一网打尽

这一章是我建议你在答辩或上线前认真核对一遍的清单。每条都按“现象 → 原因 → 解决”来讲,你照着排查一遍,能省去很多不必要的麻烦。

5.1 时区与日期统计对不上:现象、原因、解决

现象是早上打开报表,查“今日订单”发现少了一批,或者导出 Excel 里签收时间比系统界面显示的早八个小时。原因分两头:MySQL 连接串里没指定时区,数据库会话时区默认是CST(中国标准时间),而 JVM 用的是Asia/Shanghai,两者之间出现偏移;另外代码里还在用java.util.Date,序列化时按 JVM 默认时区输出,前端解析时又按浏览器时区再算一次,时间就乱了。

解决办法有三步。第一,JDBC 连接串加参数。

jdbc:mysql://localhost:3306/logistics?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

第二,Java 实体类时间字段统一用LocalDateTime,配合 MyBatis-Plus 的自动填充把createTime和updateTime一次写好,不要在每个 mapper 里重复赋值。第三,数据库字段类型用DATETIME,不要用TIMESTAMP,前者不会受 MySQL 全局时区影响导致插入后自动偏移。

5.2 MyBatis-Plus 自动填充不生效与逻辑删除的副作用

现象是实体类里加了@TableField(fill = FieldFill.INSERT)的createTime一直是null;或者某个表用了逻辑删除后,删除过的记录再次插入时触发唯一索引冲突。第一个问题很简单,缺了元数据处理器。

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

第二个问题容易被忽略。逻辑删除本质上是在每张表加了deleted字段,查询时自动拼WHERE deleted = 0,但数据库索引不认这个逻辑,它只认物理行。假如t_vehicle表的车牌号有唯一索引,你删了一辆车再插入同车牌的新车,会因为旧记录还在而失败。解决方式是:唯一约束把deleted字段一起包进去,或者这种强唯一字段改用物理删除。这个坑有点玄学,不压一把业务数据很难撞上。

5.3 并发下单库存超卖:现象、原因、解决

现象是压测 100 个并发,库存从 10 变成 -5。原因是典型的“先查后改”竞态:线程 A 查出库存为 10,线程 B 也查出 10,A 更新为 9,B 更新为 9,实际扣了两次却只减了 1。解决方式前面3.3已经给出了乐观锁方案,这里补充一个简单的复现命令。

ab -n 200 -c 50 -T application/json -p order.json http://localhost:8080/api/order/create

-n 200表示总请求数,-c 50表示并发数,order.json是请求体文件。压测前先确认库存只有 10 件,压完看库存是否为负数。如果修复后库存不为负但出现了大量“库存扣减失败”的报错,说明重试参数还需要调,把重试次数从 3 提到 5,或者把退避间隔改小一点,就能把成功率拉上来。

数据一致性是这个问题的核心,不要在 Service 层用同步锁扛并发。单机锁在集群环境下会失效,而且把整个应用线程池拖慢;数据库层的原子更新才是正解。

5.4 Excel 导出乱码和内存溢出:现象、原因、解决

现象是导出文件名中文乱码,或者一次性导出十万行数据时 JVM 直接 OutOfMemoryError。文件名乱码是因为响应头里的文件名没有做 URL 编码;内存溢出是因为用了XSSFWorkbook把全量数据一次性加载进内存。解决方案是先设置响应头,再用流式写入。

@GetMapping("/api/waybill/export") public void export(HttpServletResponse response) throws IOException { String fileName = URLEncoder.encode("运单数据.xlsx", "UTF-8"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName); try (SXSSFWorkbook workbook = new SXSSFWorkbook(500)) { Sheet sheet = workbook.createSheet("运单"); // 分页查询并逐行写入 // rowNum 大于 500 时 SXSSFWorkbook 会自动将旧数据刷出内存 workbook.write(response.getOutputStream()); } }

SXSSFWorkbook(500)的 500 是内存中保留的行数阈值,超过的部分自动写入临时文件,这招是导出大数据量时的后悔药。另外注意URLEncoder.encode的结果不要再去手动替换空格,不同 Tomcat 版本对+号的处理会有差异,统一用标准编码输出最稳。

6. 把毕设做成生产级系统:限流与数据脱敏这两个技巧值得先做

6.1 接口限流:一个拦截器就能挡住刷单

物流系统上线后,第一个拿你接口做压力测试的不一定是正经客户,很可能是脚本。最便宜的方案是在拦截器里加一个基于 Redis 计数器的限流器,按用户维度限制每分钟最大调用次数。

@Component public class RateLimitInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId = request.getHeader("userId"); String key = "rate:" + userId + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); Long count = redisTemplate.opsForValue().increment(key); if (count != null && count == 1) { redisTemplate.expire(key, 2, TimeUnit.MINUTES); } if (count != null && count > 100) { response.setStatus(429); response.getWriter().write("{\"code\":10003,\"message\":\"请求过于频繁\"}"); return false; } return true; } }

限流参数按接口性质分开设,查询类接口可以放宽到每分钟 100 次,创建订单这类写操作压到 30 次。注意 Redis key 的过期时间要略大于统计窗口,避免窗口刚结束就被清掉,导致计数漏掉。

6.2 敏感数据脱敏:日志与接口两处都要处理

物流系统里有客户手机号、身份证号、住址,不能在接口返回和日志里裸奔。接口层脱敏可以在实体类的getter上做处理。

public String getPhone() { if (phone != null && phone.length() == 11) { return phone.substring(0, 3) + "****" + phone.substring(7); } return phone; }

日志层面则用 Logback 的MessageConverter统一处理,凡是日志里出现了手机号正则匹配的内容,统一替换成掩码,避免在排查问题时把敏感信息打到日志文件里。顺序很重要,我一般先做限流再做脱敏:限流解决的是“系统会不会被打崩”,脱敏解决的是“要不要担责任”。这个顺序是我踩过一次坑才定下来的,当时限流还没做,先上了脱敏,结果被脚本把查询接口打满了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询