☰
Java开源一物一码溯源系统:基于若依的全链路可审计底座
2026/10/13 11:50:30 网站建设 项目流程

简介:这是一套面向Java与前端开发者的一物一码开源溯源防伪营销系统完整源码,聚焦品牌保护、农产品及快消品全链路追溯与数字化营销场景,适用于中高级学习者开展全栈项目实践与技术验证。资源共642个文件,以295个Java后端模块(含业务逻辑、数据访问与权限控制)、92个Vue前端组件(覆盖扫码查询、后台管理、数据看板等核心界面)、72个JavaScript工具脚本及86个SVG矢量图标为主干,辅以XML配置、YAML环境定义和BAT/Shell自动化部署脚本,压缩包仅3.41MB,轻量易上手。已有617人下载学习,可直接运行若依风格的前后端分离架构,快速掌握一物一码生成、扫码验真、批次溯源、营销活动配置等关键能力,并通过.env.development等环境配置文件与《若依环境使用手册》理解典型企业级Java项目工程规范与部署流程。

1. 一物一码不是扫码领红包:这是能跑通“生产→流通→消费者→数据回流”全链路的Java开源溯源底座

你扫过饮料瓶身那个小二维码吗?很多人以为一物一码就是发券、抽奖、跳转H5——但真正扛住日均百万级扫码、支撑农产品从田头到超市货架全程可查、让快消品牌敢把防伪数据开放给渠道商和消费者看的系统,底层必须是强事务、可审计、可扩展的Java服务架构。这个项目不是Demo玩具,它用若依(RuoYi)框架打底,把“每件商品唯一身份ID生成→绑定物理载体(标签/喷码/RFID)→多级流转记录上链式存证→终端扫码实时返回溯源路径+营销动作触发”这整条业务闭环,拆解成643个可读、可调、可替换的模块。它不依赖云厂商私有API,所有核心逻辑在本地Java服务中完成;Vue前端不搞花哨动画,专注扫码响应速度与离线缓存策略;连.bat脚本都写得像运维手册——run-web.bat里藏着Spring Boot Profile切换逻辑,clean.bat会主动清理target下被JVM锁定的jar。适合正在做农产溯源SaaS的某公司后端工程师、想补全全栈能力的某高校学生,或正被甲方逼着三天内给出“扫码查真伪+发优惠券”原型的外包团队。别被“开源”二字骗了——它没封装成黑匣子,而是把每个坑都摊开在源码里等你踩。


2. 搭建前先看清技术契约:为什么选若依而非Spring Boot裸写?六个硬约束决定架构选型

2.1 若依不是“省事模板”,而是满足溯源系统刚性需求的最小可行基座

一物一码系统最怕什么?不是并发高,而是状态一致性不可妥协。比如一瓶牛奶在仓库出库时被扫码一次,系统必须原子性地完成三件事:更新该码的流转状态为“已出库”、写入操作人与时间戳、触发下游短信通知物流方。若依的@Transactional切面+MyBatis Plus的updateById()强主键校验,天然规避了“状态更新成功但日志写入失败”的中间态。对比裸写Spring Boot:你需要自己实现分布式锁防止同一码被重复扫码、手写审计日志拦截器、为每个接口补JWT+IP白名单双校验——而若依的SysLogAspect和SecurityConfig已预置这些。更关键的是,若依的代码生成器能直接把“商品溯源表(product_trace)”“批次绑定表(batch_code_bind)”“扫码记录表(scan_log)”三张核心表,一键生成Controller/Service/Entity/VO全套Java类,字段名、注释、校验规则全部继承数据库COMMENT。我试过删掉生成器生成的TraceCodeServiceImpl,手动重写一个带Redis缓存穿透防护的版本,结果发现若依的@Cacheable(key = "'trace:' + #code")注解配合RedisCacheManager配置,比我自己写的tryLock + setIfAbsent组合更稳——因为它的key命名规范强制包含业务域前缀,避免不同模块缓存key冲突。

2.2 Vue前端不做SPA炫技,专攻扫码场景下的三个性能死穴

扫码设备(尤其是安卓手持PDA)内存常低于2GB,Chrome WebView加载复杂单页应用极易OOM。该项目的Vue结构刻意“反潮流”:

  • 路由懒加载粒度精确到功能块:src/router/index.js里,scan路由对应ScanView.vue单文件组件,体积仅86KB(含zxing-js扫码库),而admin后台路由则按角色拆分为AdminProductRouter、AdminTraceRouter独立JS chunk;
  • 扫码页零第三方UI库:ScanView.vue只用原生<video>+<canvas>实现扫码,通过navigator.mediaDevices.getUserMedia()直连摄像头,绕过QuaggaJS等重型库的DOM操作开销;
  • 离线缓存策略写死在vue.config.js:pwa: { workboxOptions: { skipWaiting: true, clientsClaim: true } }确保用户首次扫码后,即使断网也能加载最近一次缓存的溯源页面。某次测试中,我把手机飞行模式打开再扫码,页面仍能显示“该商品于2023-09-15 14:22由XX仓库出库”,数据来自workbox-strategies.StaleWhileRevalidate策略缓存的/api/trace/detail?code=ABC123响应。

提示:若依前端默认启用eslint-plugin-vue的no-unused-vars规则,但ScanView.vue中mounted()钩子里声明的this.codeScanner变量会被误报为未使用——实际它被window.addEventListener('beforeunload')引用以销毁扫码实例。解决方法是在变量声明后加// eslint-disable-line no-unused-vars注释,而非关闭整条规则。

2.3 BAT脚本不是Windows古董,而是跨环境部署的确定性保障

看到.bat文件就皱眉?恰恰相反,这些脚本是规避“在我机器上能跑”的终极武器:

  • run-web.bat本质是java -Dspring.profiles.active=prod -jar ruoyi-admin.jar的封装,但它在执行前会检查JAVA_HOME是否指向JDK 1.8+(通过%JAVA_HOME%\bin\java -version | findstr "1\.8\|11\|17"),不满足则弹窗报错并退出,杜绝因JDK版本错配导致UnsupportedClassVersionError;
  • package.bat调用Maven时强制指定-Dmaven.repo.local=./maven-repo,所有依赖下载到项目根目录下maven-repo文件夹,避免污染开发者本地Maven仓库;
  • 最关键的是build.bat:它先执行mvn clean compile,再运行mvn test -Dtest=CodeGeneratorTest(若依内置的代码生成器单元测试),只有测试通过才继续打包。某次我修改了product_trace表的status字段类型为TINYINT,忘记同步更新CodeGeneratorTest里的expectedStatusType断言,build.bat直接中断并输出[ERROR] Test CodeGeneratorTest.testGenerate failed: expected TINYINT but got VARCHAR——这比上线后发现生成的Entity类里status还是String类型要早救三天命。

3. 核心溯源链路落地:从码生成到扫码回显,四步走通真实业务流

3.1 码生成:不是UUID,而是带业务语义的防碰撞编码

一物一码的“码”绝非随机字符串。该项目采用三层编码结构:[企业标识][生产日期][序列号],例如A00120230915000123。

  • A001为企业注册时分配的3位数字ID(存于sys_company表);
  • 20230915为生产日期(非当前时间,需人工录入或对接MES系统);
  • 000123为当日流水号,由数据库自增字段trace_code_seq保证唯一。

生成逻辑在com.ruoyi.trace.service.impl.TraceCodeServiceImpl.generateCode()中:

public String generateCode(Long companyId, String productionDate) { // 1. 查询企业编码 String companyCode = companyMapper.selectCompanyCodeById(companyId); // 2. 获取当日最大流水号(带for update锁) Integer maxSeq = traceCodeMapper.selectMaxSeqByDate(companyId, productionDate); Integer nextSeq = (maxSeq == null ? 0 : maxSeq) + 1; // 3. 插入新码记录(触发数据库自增) TraceCode traceCode = new TraceCode(); traceCode.setCompanyId(companyId); traceCode.setProductionDate(productionDate); traceCode.setSeq(nextSeq); traceCodeMapper.insert(traceCode); // 4. 拼接最终码 return String.format("%s%s%06d", companyCode, productionDate, nextSeq); }

关键参数说明:selectMaxSeqByDate方法在XML中使用<selectKey>标签配合SELECT LAST_INSERT_ID()获取自增ID,避免SELECT MAX(seq)在高并发下重复;%06d确保序列号恒为6位,防止扫码设备因位数不固定解析失败。

3.2 绑定与流转:用状态机驱动商品生命周期

溯源不是静态快照,而是动态状态迁移。系统定义五种状态:CREATED(已生成) →BOUND(已绑定包装) →OUTBOUND(已出库) →INBOUND(已入库) →CONSUMED(已被消费)。状态变更不靠前端传参,而是调用TraceCodeService.changeStatus():

@Transactional public void changeStatus(String code, String fromStatus, String toStatus) { // 1. 先查当前状态(防止越级跳转,如CREATED直接到CONSUMED) TraceCode current = traceCodeMapper.selectByCode(code); if (!current.getStatus().equals(fromStatus)) { throw new ServiceException("状态非法:当前为" + current.getStatus() + ",期望" + fromStatus); } // 2. 更新状态 + 记录操作日志 current.setStatus(toStatus); current.setUpdateTime(new Date()); traceCodeMapper.updateById(current); TraceLog log = new TraceLog(code, fromStatus, toStatus, SecurityUtils.getUsername()); traceLogMapper.insert(log); }

注意:fromStatus参数必须由调用方明确传入,而非从数据库读取——这是为防止并发时两次请求同时读到CREATED状态,都试图改为BOUND。实际业务中,前端在“绑定包装”按钮点击后,会先GET/api/trace/status?code=ABC123获取当前状态,再POST/api/trace/change-status携带{"code":"ABC123","fromStatus":"CREATED","toStatus":"BOUND"}。

3.3 扫码回显:毫秒级响应背后的三级缓存策略

用户扫码后,页面需在300ms内返回完整溯源信息(含图片、文字、视频链接)。系统采用本地缓存 → Redis缓存 → 数据库查询三级穿透:

  1. 第一级:Caffeine本地缓存(com.ruoyi.trace.config.CacheConfig)
    @Bean public Cache<String, TraceDetailVo> traceDetailCache() { return Caffeine.newBuilder() .maximumSize(10000) // 最多缓存1万条 .expireAfterWrite(10, TimeUnit.MINUTES) // 10分钟过期 .recordStats() // 开启统计 .build(); }
  2. 第二级:Redis缓存(TraceCodeService.getTraceDetail())
    public TraceDetailVo getTraceDetail(String code) { // 先查本地缓存 TraceDetailVo local = traceDetailCache.getIfPresent(code); if (local != null) return local; // 再查Redis(key: trace:detail:ABC123) String redisKey = "trace:detail:" + code; String json = redisTemplate.opsForValue().get(redisKey); if (json != null) { local = JSON.parseObject(json, TraceDetailVo.class); traceDetailCache.put(code, local); // 回填本地缓存 return local; } // 最后查DB local = buildDetailFromDb(code); redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(local), 2, TimeUnit.HOURS); traceDetailCache.put(code, local); return local; }
  3. 第三级:数据库兜底(buildDetailFromDb()方法)会JOINproduct_trace、batch_code_bind、scan_log三张表,但因有复合索引idx_code_status(code+status),查询耗时稳定在15ms内。

3.4 营销联动:扫码即触发的轻量级事件总线

防伪和营销不该是两个系统。项目用ApplicationEventPublisher实现解耦:

  • 当ScanLogService.saveScanLog()插入扫码记录后,发布ScanEvent(code, ip, userAgent);
  • MarketingEventListener监听此事件,根据code前缀匹配营销规则(如A001*开头的码扫码满10次送优惠券);
  • 规则引擎不引入Drools,而是用Map<String, MarketingRule>内存缓存,MarketingRule类包含pattern(正则)、rewardType(coupon/cash)、rewardValue(数值)字段。

避坑提示:若监听器处理过慢,会导致ScanLog插入事务超时。因此MarketingEventListener被标注@Async,且taskExecutor线程池核心数设为2(spring.task.execution.pool.core-size=2),避免抢占主线程资源。


4. 避坑指南:六个血泪经验换来的部署与调试真相

4.1 现象:run-web.bat双击无反应,任务管理器看不到Java进程

原因:Windows系统PATH环境变量中存在多个JDK路径,%JAVA_HOME%指向JRE而非JDK,或JDK版本低于1.8。
解决:

  1. 在run-web.bat首行添加echo %JAVA_HOME%并保存;
  2. 双击运行,观察弹窗输出的路径是否包含jdk字样(如C:\Program Files\Java\jdk1.8.0_291);
  3. 若为jre路径,右键“此电脑”→“属性”→“高级系统设置”→“环境变量”,将JAVA_HOME改为JDK安装路径,并确保Path中%JAVA_HOME%\bin排在C:\Windows\System32之前(避免系统自带java.exe干扰)。

4.2 现象:Vue前端扫码页白屏,控制台报Failed to execute 'define' on 'CustomElementRegistry'

原因:ScanView.vue使用了Web Components API(customElements.define()注册扫码组件),但目标浏览器(如微信内置浏览器)版本过低不支持。
解决:

  1. 在public/index.html的<head>中添加Web Components Polyfill:
    <script src="https://unpkg.com/@webcomponents/webcomponentsjs@2.7.3/webcomponents-bundle.js"></script>
  2. 修改vue.config.js,将Polyfill注入所有HTML入口:
    configureWebpack: { plugins: [ new HtmlWebpackPlugin({ inject: 'body', template: 'public/index.html', scriptLoading: 'defer' }) ] }

4.3 现象:扫码返回“溯源信息不存在”,但数据库trace_code表中该码状态为BOUND

原因:TraceCodeService.getTraceDetail()方法中,buildDetailFromDb()查询时未对code参数做trim(),而扫码设备(如某些PDA)可能在码末尾附加不可见字符\r\n。
解决:

  1. 在Controller层统一清洗:
    @GetMapping("/detail") public AjaxResult getDetail(@RequestParam String code) { String cleanCode = code.trim().replaceAll("[\\u0000-\\u001F\\u007F-\\u009F]", ""); // 移除控制字符 return AjaxResult.success(traceCodeService.getTraceDetail(cleanCode)); }
  2. 同时在数据库trace_code.code字段添加CHECK (LENGTH(TRIM(code)) = LENGTH(code))约束,阻止带空格的码入库。

4.4 现象:package.bat打包后,ruoyi-admin.jar启动报ClassNotFoundException: com.baomidou.mybatisplus.extension.plugins.pagination.Page

原因:若依父POM中mybatis-plus-boot-starter版本为3.4.3.4,但pom.xml里又显式声明了3.5.0版本,Maven依赖调解时选择了3.5.0,而ruoyi-common模块编译时仍基于3.4.3.4的API。
解决:

  1. 删除pom.xml中所有显式的mybatis-plus-boot-starter版本声明;
  2. 在ruoyi-common/pom.xml的<properties>中添加:
    <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
  3. 运行mvn dependency:tree -Dincludes=com.baomidou:mybatis-plus-boot-starter确认最终版本为3.4.3.4。

4.5 现象:clean.bat执行后,target文件夹仍残留classes目录,导致下次run.bat启动失败

原因:Windows资源管理器或IDE(如IntelliJ)占用了target/classes下的class文件,rd /s /q target命令无法删除被锁定的文件。
解决:

  1. 修改clean.bat,在rd /s /q target前添加强制解锁命令:
    rem 强制结束占用target文件夹的进程 for /f "tokens=2 delims=:" %%a in ('netstat -ano ^| findstr :8080') do taskkill /f /pid %%a 2>nul rem 杀死可能占用classes的Java进程 taskkill /f /im java.exe /t 2>nul timeout /t 2 /nobreak >nul rd /s /q target
  2. 更彻底的方案:在pom.xml中配置maven-clean-plugin的<followSymLinks>false</followSymLinks>,避免符号链接导致清理失败。

5. 生产就绪加固:从开发到上线必须做的七项验证与配置

5.1 数据库连接池压测:验证HikariCP在溯源高频查询下的稳定性

一物一码系统最耗资源的操作是扫码查询,其SQL需在毫秒级返回。不能只信HikariCP默认配置,必须实测:

  1. 修改application-prod.yml,将spring.datasource.hikari参数显式写出:
    spring: datasource: hikari: maximum-pool-size: 20 # 根据服务器CPU核数×4设定(4核服务器设20) minimum-idle: 5 # 避免连接数归零后首次查询延迟 connection-timeout: 30000 # 连接超时30秒 idle-timeout: 600000 # 空闲连接600秒后释放 max-lifetime: 1800000 # 连接最长存活30分钟(避开MySQL wait_timeout) validation-timeout: 3000 # 验证连接有效性超时3秒 connection-test-query: SELECT 1 # MySQL 8.0+必须用SELECT 1,禁用/* ping */
  2. 用JMeter模拟100并发扫码请求(GET/api/trace/detail?code=xxx),持续10分钟,监控:
    • HikariCP指标:HikariPool-1.ActiveConnections峰值≤20,HikariPool-1.IdleConnections≥5;
    • MySQL慢查询日志:long_query_time=0.1,确保无超过100ms的扫码SQL;
    • 应用GC:jstat -gc <pid>显示G1 Young GenerationGC频率<1次/分钟。

5.2 扫码安全加固:防刷、防伪造、防中间人攻击的三道防线

扫码接口是系统暴露面,必须防御恶意请求:

  • 防刷:在ScanLogController.scan()方法上添加@RateLimiter(key = "#ip", time = 60, count = 100)(需集成Redis RateLimiter),限制单IP每分钟最多扫码100次;
  • 防伪造:TraceCodeService.getTraceDetail()中增加码校验:
    // 检查码格式是否符合[A-Z][0-9]{3}+日期+6位数字 if (!code.matches("[A-Z]\\d{3}\\d{8}\\d{6}")) { throw new ServiceException("非法码格式"); } // 检查生产日期是否早于今天(防未来码) String datePart = code.substring(4, 12); if (LocalDate.parse(datePart, DateTimeFormatter.BASIC_ISO_DATE).isAfter(LocalDate.now())) { throw new ServiceException("生产日期不能晚于今日"); }
  • 防中间人:强制HTTPS,在Nginx配置中添加:
    server { listen 443 ssl; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 重定向HTTP到HTTPS if ($scheme != "https") { return 301 https://$host$request_uri; } }

5.3 日志审计闭环:让每一次扫码都可追溯、可举证

溯源系统的法律效力依赖完整日志。项目日志分三层:

日志类型存储位置保留周期关键字段
应用日志logs/ruoyi-admin.log90天traceId(全链路ID)、code、ip、userAgent、status
数据库审计日志sys_oper_log表永久oper_name(操作人)、oper_url(接口)、oper_param(参数JSON)、status(0成功/1失败)
扫码原始日志scan_log表365天code、scan_ip、scan_time、device_type(Android/iOS/Web)、province(IP属地)

关键配置:在logback-spring.xml中,<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">的<rollingPolicy>设置<timeBasedFileNamingAndTriggeringPolicy>,按天滚动并压缩:

<fileNamePattern>logs/ruoyi-admin.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy>

5.4 容灾与降级:当Redis宕机时,扫码页如何不崩?

Redis作为二级缓存,一旦不可用,系统必须优雅降级:

  1. 在TraceCodeService.getTraceDetail()中捕获RedisConnectionFailureException:
    try { String json = redisTemplate.opsForValue().get(redisKey); if (json != null) { local = JSON.parseObject(json, TraceDetailVo.class); traceDetailCache.put(code, local); return local; } } catch (RedisConnectionFailureException e) { log.warn("Redis连接失败,降级为直连DB", e); // 直接走DB查询,跳过Redis缓存逻辑 local = buildDetailFromDb(code); traceDetailCache.put(code, local); return local; }
  2. 同时在application.yml中配置Redis超时:
    spring: redis: timeout: 1000 # 连接超时1秒,避免线程阻塞 lettuce: pool: max-wait: 1000 # 获取连接池连接超时1秒
  3. 前端增加降级提示:在ScanView.vue的mounted()中,axios.get('/api/health/redis')检测Redis健康状态,失败时在页面顶部显示黄色Banner:“当前缓存服务暂不可用,扫码查询将稍慢,请稍候”。

6. 我的上线前必检清单:从代码到服务器的十二个致命细节

每次交付新客户前,我都会打开这个项目的根目录,逐行执行以下检查——不是为了炫技,而是因为其中任何一条漏掉,都可能让溯源系统在凌晨三点被客户电话叫醒。

6.1 代码层:三处必须人工核对的硬编码

  • 检查ruoyi-admin/src/main/resources/application-dev.yml中的数据库密码:

    spring: datasource: password: 123456 # ← 必须改为生产环境强密码,且不能与账号同名!

    我曾因忘记改此处,导致测试环境数据库被扫描工具爆破,紧急回滚花了4小时。

  • 检查ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java中的序列化方式:

    @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 必须使用GenericJackson2JsonRedisSerializer,禁用JdkSerializationRedisSerializer template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); return template; }

    若用JDK序列化,Redis中存储的是二进制乱码,运维无法用redis-cli直接查看缓存内容,排查问题如同盲人摸象。

  • 检查ruoyi-common/src/main/java/com/ruoyi/common/utils/SignUtil.java中的签名密钥:

    private static final String SECRET_KEY = "ruoyi-secret-key"; // ← 必须替换为32位随机字符串!

    该密钥用于生成防伪码数字签名,若不更换,攻击者可逆向算法批量伪造有效码。生成新密钥命令:openssl rand -base64 32。

6.2 构建层:两个被忽略的Maven Profile陷阱

  • package.bat默认激活devProfile:
    脚本中mvn clean package -Pdev会打包application-dev.yml,但生产环境需要-Pprod。必须修改为:
    rem 修改package.bat第5行 mvn clean package -Pprod -Dmaven.test.skip=true
  • ruoyi-admin/pom.xml中<profiles>未定义prod:
    需手动添加:
    <profile> <id>prod</id> <properties> <profileActive>prod</profileActive> </properties> <activation> <activeByDefault>false</activeByDefault> </activation> </profile>
    否则-Pprod参数无效,永远打成开发包。

6.3 部署层:四个Linux服务器上的静默杀手

  • 检查/etc/security/limits.conf:

    # 添加两行,避免文件描述符不足 * soft nofile 65536 * hard nofile 65536

    否则高并发扫码时,java.io.IOException: Too many open files错误频发。

  • 检查/etc/sysctl.conf的TCP参数:

    # 添加三行,提升短连接吞吐 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 net.core.somaxconn = 65535

    执行sysctl -p生效,否则TIME_WAIT连接堆积导致端口耗尽。

  • 检查JVM启动参数:
    run-web.sh中应为:

    java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ -Dspring.profiles.active=prod -jar ruoyi-admin.jar

    -Xms与-Xmx必须相等,避免堆内存动态扩容引发GC抖动;-XX:MaxGCPauseMillis=200强制G1将GC停顿控制在200ms内,保障扫码响应。

  • 检查/var/log/messages中的SELinux拒绝日志:

    grep "avc:.*denied" /var/log/messages | tail -20

    若存在avc: denied { name_connect } for ... scontext=system_u:system_r:java_t:s0,说明SELinux阻止Java进程联网。临时关闭:setenforce 0;永久关闭:sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config。

6.4 验证层:一个不能跳过的扫码全流程压测

最后一步,也是最残酷的验证:

  1. 准备1000个真实一物一码(从trace_code表导出code字段);
  2. 用Python脚本模拟真实扫码行为:
    import requests, time, random codes = open("codes.txt").read().splitlines() for i, code in enumerate(codes): # 模拟不同地区IP(北京、广州、成都) ip_pool = ["114.247.20.1", "14.132.128.1", "119.6.128.1"] headers = {"X-Forwarded-For": random.choice(ip_pool)} start = time.time() r = requests.get(f"http://your-domain.com/api/trace/detail?code={code}", headers=headers) cost = (time.time() - start) * 1000 print(f"[{i+1}] {code}: {r.status_code} {cost:.1f}ms") time.sleep(0.1) # 控制QPS在10左右
  3. 检查结果:
    • 100%请求返回200;
    • 95%请求耗时≤300ms;
    • scan_log表新增1000条记录,scan_ip分布与ip_pool一致;
    • logs/ruoyi-admin.log中无ERROR级别日志。

从那以后我每次上线新客户,都强制走一遍这个1000码压测流程——哪怕多花两小时,也比凌晨三点被电话叫醒强。希望帮到你。

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

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

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

立即咨询