生产级 B2C 电商后端系统,SpringBoot 五模块架构,覆盖从商品管理到售后退款的全链路。
C 端(移动端)入口:浏览商品 → 搜索 → 购物车 → 确认订单 → 支付 → 收货 → 评价 → 售后 B 端(管理后台)入口:商品/分类/品牌管理 → 订单管理 → 用户管理 → 优惠券配置 → 退款审批 → 定时任务调度
API 文档(Knife4j):
http://localhost:8011/doc.html,按 "01-管理后台" / "02-移动端" 分组。
这是系统最核心的链路,涉及 Redis / Redisson / RocketMQ / ShardingSphere:
[用户选品] → [确认订单] → [提交订单] → [支付] → [发货] → [确认收货] → [完成]
购物车 Redis缓存 Redisson锁 支付宝 后台操作 用户操作
1h TTL 扣库存 二维码
Step 1 — 确认订单 POST /v1/web/trade/confirm
- 校验用户已登录,查询购物车选中商品
- 检查商品库存(
ProductEntity.remainQuantity) - 匹配可用优惠券:根据券类型(满减/折扣/现金)和绑定商品范围,策略模式计算优惠金额
- 汇总
totalAmount(商品总额)、subtractMoney(优惠金额)、finalMoney(实付金额) - 生成订单编号(雪花 ID),将完整确认数据序列化为 JSON 存入 Redis,key=
userId,TTL=1h
存 Redis 的目的:防止用户绕过确认页直接调提交接口,同时保证确认-提交之间数据一致性
Step 2 — 提交订单 POST /v1/web/trade/submit
- 从 Redis 读取确认数据,校验存在且
tradeCode未过期 - 校验收货地址存在
- 分布式锁扣库存:对每件商品
reduceStockLock:{productId}加 Redisson 多锁(超时 20s),锁内TransactionTemplate事务扣减product.remainQuantity - ShardingSphere 分片写入:按订单号 hashCode 取模路由到具体分片(2 库 × 2 表),事务插入
TradeEntity+TradeItemEntity - 保存收货地址快照
TradeDeliveryAddressEntity - 异步后处理(独立事务):核销优惠券(
useStatus=1)、清理购物车(软删除) - 发送 RocketMQ 延迟消息(延迟 30min)→ 超时未支付自动取消
Step 3 — 支付 POST /v1/web/pay/doPay
- 支付宝 EasySDK
FaceToFace.preCreate()生成扫码支付串 - Hutool
QrCodeUtil将支付 URL 生成 PNG 二维码返给前端 - 支付宝异步回调 → 验签 → 更新订单状态为"已支付"
测试环境可用
POST /v1/web/pay/mockPay模拟支付
Step 4 — 超时取消(自动)
mall-job模块OverTimeCancelTradeConsumer消费 RocketMQ 延迟消息- 订单仍为「待支付」→ 状态改为「已取消」;秒杀订单额外发送通知
订单状态流转:
CREATE(已下单) → PAY(已支付) → SHIPPED(已发货) → FINISH(已完成)
↓ 30min超时 ↓ 可退货
CANCEL(已取消) REJECT(已退货) → 触发退款流程
| 环节 | 中间件 | 作用 |
|---|---|---|
| 确认订单缓存 | Redis | key=userId, TTL=1h,防绕路 |
| 库存扣减锁 | Redisson | reduceStockLock:{productId},超时 20s |
| 下单后处理 | RocketMQ | 30min 延迟取消(delayLevel=16) |
| 订单状态广播 | RabbitMQ | trade_status_change_exchange → 下游通知 |
| 分库分表 | ShardingSphere | 2 库 × 2 表,按订单号 hashCode 路由 |
[管理员] → 创建秒杀商品 → Redis预热库存 + ES索引
[用户] → 秒杀下单 → 签名校验 → 分布式锁扣库存 → 创建订单 → 异步削峰
管理端 — 秒杀商品录入 POST /v1/seckillProduct/insert
- 校验源商品存在 → 插入
SeckillProductEntity(秒杀价、预扣库存、起止时间) - 三向同步:MySQL 落库 + ES 写索引(供搜索/首页展示)+ Redis 写库存计数器
seckillProductStock:{id}和详情缓存seckillProductDetail:{id}
用户端 — 秒杀下单 POST /v1/web/trade/createForSeckill
- 接口标记
@VerifySign(签名防篡改)+@NoLogin(无需登录,userName 识别用户) - 查用户 + 商品 → 构造
TradeEntity(orderType=SECKILL_PRODUCT) - Redisson 锁扣库存 → 插入订单 → RocketMQ 超时取消
秒杀 vs 普通订单区别:
认证: 签名校验 / JWT Token
来源: 直接指定商品 / 购物车批量
优惠券: 不可用 / 可用
收货地址: 不保存 / 保存快照
[管理员建券模板] → [配置发放规则] → [用户浏览可领券] → [领取] → [下单核销]
6种类型 绑定商品范围 检查是否已领 行锁排队 策略计算
优惠券三层数据模型:
| 层级 | 实体 | 含义 |
|---|---|---|
| 模板 | CouponEntity |
定义类型、面额、门槛、有效期、发放总量 |
| 发放 | CouponUserProvideEntity |
将模板绑定到商品(productId=0=全场通用) |
| 领取 | CouponUserReceiveEntity |
用户领取记录 + 使用状态 |
6 种优惠策略(策略模式,CouponContext.calcPayMoney()):
| 类型 | 计算逻辑 |
|---|---|
现金券 CASH |
直接减 offMoney |
阶梯满减 NORMAL_FULL_REDUCTION |
满 minMoney 减 offMoney |
每满减 EVERY_FULL_REDUCTION |
每满 minMoney 就减一次 offMoney |
通用折扣 NORMAL_DISCOUNT |
总价 × discount / 100 |
满件折扣 FULL_COUNT_DISCOUNT |
满 minProductCount 件打折 |
满额折扣 FULL_AMOUNT_DISCOUNT |
满 minMoney 元打折 |
领取流程(POST /v1/web/coupon/receiveCoupon):
TransactionTemplate 事务 → 行锁 findByIdForUpdate → receiveCount+1 → 插入领取记录
核销流程:
确认订单时过滤可用券(时间有效 + 商品匹配)→ 提交订单时批量 updateForBatch 标记已使用
[用户申请退款] → [上传凭证图] → [订单标记 REJECT] → [管理员审核] → [同意/拒绝] → [退款完成]
- 根据
tradeCode+tradeItemId查订单和明细,防重校验(同一商品不能重复申请) TransactionTemplate事务:插入RefundEntity(auditStatus=待审核, refundStatus=进行中)+ 批量插入凭证RefundPhotoEntity- 更新
TradeEntity.orderStatus = REJECT - 管理员审核:
auditStatus待审核→同意(退款处理)/ 拒绝(填写原因) - 退款完成:
refundStatus进行中→已完成
[商品上架] → ES 索引同步(HanLP 分词:拼音/同义词/高亮)
[用户搜索] → ES 全文检索 → 结果高亮返回
[Mahout 定时计算] → UserBased 协同过滤 → 推荐 ID 存 Redis
[用户访问推荐] → Redis 取推荐 ID → ES IdsQuery 取详情
| 接 入 层 |
|
🎯 Admin API(管理后台) 商品 · 订单 · 用户 · 优惠券 · 退款审批 · 数据字典 · 任务调度 |
📱 Mobile API(移动端) 商品浏览 · 购物车 · 下单支付 · 秒杀 · 领券 · 收藏 · 收货地址 |
| 核 心 业 务 领 域 |
|
🔒 用户与权限
多方式登录 · RBAC
JWT 无状态认证 GeoIP 异地检测 数据脱敏 |
📦 商品与搜索
多级分类树 · SKU
ES + HanLP 搜索 Mahout 协同过滤 评价 · 收藏 |
💰 交易与订单
购物车 → 下单 → 支付
Redisson 分布式锁控库存 ShardingSphere 分库分表 支付宝 + 30min 超时取消 秒杀(预热+原子扣减) 退款 / 售后 |
🎷 营销优惠券
券模板管理
用户领券/核销 策略模式计算 6 种优惠类型 |
| 支 撑 领 域 |
|
📨 消息与任务
RabbitMQ · RocketMQ
Quartz 动态调度 WebSocket 推送 重试(最多 3 次) |
📁 文件与存储
策略工厂 3 后端
MinIO · 七牛 · RustFS 百万 Excel 异步导出 |
📬 通讯与通知
阿里云短信
邮件(文本/HTML/附件) FreeMarker 模板 mock 模式开发 |
⚙️ 公共能力
Caffeine + Redis 两级缓存
Jsoup 行政区划爬虫 HanLP 敏感词过滤 @BizLog · @Limit · @RepeatSubmit |
| 🗄 MySQL 8.0 | 🔴 Redis | 🔍 Elasticsearch 7.x | 📄 MongoDB | 📨 RabbitMQ | 🚀 RocketMQ |
|
👤 UserEntity
|
📦 ProductEntity
|
| 💰 TradeEntity(ShardingSphere 分库分表) | |||
| 📦 订单项 → TradeItemEntity | 📍 收货地址 → TradeDeliveryAddressEntity | 🎷 核销券 → CouponUserEntity | 🔄 退款 → RefundEntity → RefundPhotoEntity |
核心框架
持久层
缓存
消息队列
安全
调度 & 通信
文件存储
支付 & 短信 & 邮件
NLP & 推荐
工具库
API 文档 & 监控
登录认证 — 密码登录 RSA 解密 + BCrypt 比对,短信登录 Redis 验证码校验 → JWT HS512 签发 → JwtTokenFilter 无状态鉴权。GeoIP 双数据源(MaxMind + 淘宝 API)异地检测,FreeMarker 渲染告警邮件。
搜索推荐 — 商品上架同步 ES 索引,HanLP 分词(拼音/同义词/高亮)。Mahout GenericUserBasedRecommender + UncenteredCosineSimilarity 定时计算推荐,结果缓存 Redis。
库存并发控制 — Redisson 分布式锁(tryMultiLock,超时 20s)+ TransactionTemplate 事务扣减,确保不超卖。
订单分片 — ShardingSphere 4.1.1 分库分表,按订单号 hashCode 取模路由(2 库 × 2 表 = 4 物理分片),Snowflake ID 全局唯一。
双 MQ — RabbitMQ 管通用消息(Excel 导出通知、订单状态变更);RocketMQ 管高吞吐和延时消息(秒杀削峰、30min 取消订单)。
异步 Excel — @ExcelExport → 异步任务创建 → EasyExcel 流式写入(不占内存)→ OSS 上传 → RabbitMQ 通知 → WebSocket 按 userId 推送下载链接,失败自动重试。
动态定时任务 — 管理端 CRUD 任务 → 持久化 MySQL → RocketMQ 发指令 → QuartzManage 执行 addJob/resumeJob/deleteJob 等操作。
三存储后端 — FileStorageFactory 启动时自动选择:RustFS > 七牛 Kodo > MinIO,统一 FileStorageService 接口。
限流防重 — @Limit 双实现:Guava RateLimiter 令牌桶(单机)+ Redis Lua 脚本(分布式)。@RepeatSubmit:URI+IP+参数MD5 → Redis TTL 5s 去重。
工程化 — GlobalApiResultHandler 统一包装 {code, message, data};GlobalExceptionHandler 分层捕获异常;@BizLog 自动记录操作人/IP/城市/耗时;Caffeine + Redis 两级缓存字典数据。
pom.xml(父 POM)
├── mall-common 基础设施层 — util · enum · exception · security · annotation
├── mall-domain 领域模型层 — entity · mapper · MyBatis XML(约 30 张表)
├── mall-service 业务服务层 — 全部业务逻辑 + 中间件集成
├── mall-api REST API 层 — 管理后台 + 移动端 Controller(端口 8011)
└── mall-job 定时任务层 — Quartz + MQ 消费者 + WebSocket(端口 8012)
依赖流向:mall-common ← mall-domain ← mall-service ← mall-api / mall-job
- JDK 8+ · Maven 3.6+ · MySQL 8.0 · Redis · Elasticsearch 7.x
- RabbitMQ / RocketMQ(可选)
项目使用 Spring Profiles 隔离环境,敏感信息通过环境变量注入。
# 开发环境(默认)— mock 全开、连接本地服务
mvn spring-boot:run
# 生产环境 — 所有密钥从环境变量读取
# 先设置环境变量(见 application-prod.yml 中的 ${ENV_VAR} 占位符)
java -jar mall-api.jar --spring.profiles.active=prod配置文件结构:
| 文件 | 用途 | Git |
|---|---|---|
application.yml |
公共配置 + profiles.active: dev |
✅ 追踪 |
application-dev.yml |
开发环境(当前内容) | ❌ 排除 |
application-prod.yml |
生产模板(${ENV_VAR} 占位) |
✅ 追踪 |
application.example.yml |
旧版参考 | ✅ 追踪 |
application-dev.yml已在.gitignore排除。不使用的中间件(RocketMQ、七牛云等)可忽略对应配置段。
git clone <repo-url> && cd mall_server
# 1. 导入 SQL(参考 sql/sql导入和审计操作小技巧.md)
# 2. 编译
mvn clean install -DskipTests
# 3. 启动 API 服务(端口 8011)
cd mall-api && mvn spring-boot:run
# 4. 启动 Job 服务(端口 8012,新终端)
cd mall-job && mvn spring-boot:runAPI 文档:
http://localhost:8011/doc.html(Knife4j + Swagger3)
mall_server/
├── pom.xml # 父 POM
├── .gitignore # 排除 application.yml 等敏感文件
├── README.md
├── sql/
│ ├── feature_1.0.1/ # 建表 + 改表 SQL
│ └── sql导入和审计操作小技巧.md # SQL 导入 & 审计指南
│
├── mall-common/ # 基础设施层
│ └── src/main/java/com/mall/common/
│ ├── annotation/ # @NoLogin @RepeatSubmit @Limit @BizLog @Sensitive
│ ├── base/ # BaseEntity BaseMapper BaseService
│ ├── enums/ # 订单/支付/删除状态
│ ├── exception/ # BusinessException
│ ├── handler/ # GlobalExceptionHandler GlobalApiResultHandler
│ ├── helper/ # IdGenerateHelper(Snowflake) GeoIpHelper UserTokenHelper
│ ├── mybatis/ # UserInterceptor(Snowflake ID 注入)
│ ├── security/ # JWT SpringSecurity 配置
│ ├── util/ # RedisUtil RedissonUtil PasswordUtil(RSA) ExcelUtil
│ └── valid/ # 自定义校验注解
│
├── mall-domain/ # 领域模型层
│ ├── src/main/java/com/mall/domain/
│ │ ├── product/ # 商品/分类/品牌/属性/单位
│ │ ├── order/ # 订单/订单项/收货地址
│ │ ├── user/ # 用户/角色/部门/菜单/字典
│ │ ├── marketing/ # 优惠券/领券记录
│ │ ├── payment/ # 支付实体
│ │ ├── aftersale/ # 退款/退款地址/退款凭证
│ │ ├── shopping/ # 购物车/评价/收藏/地址
│ │ ├── seckill/ # 秒杀商品
│ │ ├── common/ # 地区/任务/通知/图片/短信
│ │ └── log/ # 业务日志
│ └── src/main/resources/mapper/ # MyBatis XML 映射
│
├── mall-service/ # 业务服务层
│ └── src/main/java/com/mall/service/
│ ├── product/ · order/ · user/ · marketing/ · payment/
│ ├── aftersale/ · shopping/ · seckill/ · common/
│ ├── storage/ # FileStorageFactory + MinIO/七牛/RustFS
│ ├── search/ # EsTemplate
│ ├── sms/ # AliyunSmsService
│ ├── recommend/ # RecommendProductService(Mahout)
│ ├── sharding/ # OrderDataBase/TablePreciseShardingAlgorithm
│ ├── task/ # ExcelExportTask SendEmailTask
│ ├── aspect/ # BizLogAspect RepeatSubmitAspect LimitAspect
│ ├── config/ # SpringSecurityConfig RabbitConfig EsConfig
│ ├── filter/ # JwtTokenFilter
│ └── helper/ # MqHelper IpWhiteListHelper
│
├── mall-api/ # REST API 层(:8011)
│ ├── src/main/java/com/mall/api/
│ │ ├── admin/ # 管理后台 Controller(8 个业务包)
│ │ ├── mobile/ # 移动端 Controller(11 个 Controller)
│ │ └── config/ # Swagger Cors WebConfig
│ └── src/main/resources/
│ ├── application.example.yml # 配置模板(Git 追踪)
│ └── application.yml # 本地配置(Git 忽略)
│
└── mall-job/ # 定时任务层(:8012)
├── src/main/java/com/mall/job/
│ ├── quartz/ # QuartzManage QuartzExecutionJob
│ ├── consumer/ # ExcelExport OverTimeCancel DynamicJob Consumer
│ └── websocket/ # WebSocketServer
└── src/main/resources/
├── application.example.yml
└── application.yml
| 🔀 ShardingSphere 订单分库分表 | |
| 分片键 | code(订单号) |
| 数据源 | 2 个逻辑库(ds0、ds1),每库 2 张表(_0、_1)→ 4 个物理分片 |
| 路由算法 | 订单号 hashCode 取模 → OrderDataBasePreciseShardingAlgorithm + OrderTablePreciseShardingAlgorithm |
| 🔴 Redis 缓存策略 | ||
| Key | 用途 | 过期 |
token:username | JWT Token 缓存 | 会话有效期 |
user:username | 用户详情 | 会话有效期 |
seckill_product_stock:id | 秒杀库存(预热写入) | 持久化 |
seckill_product_detail:id | 秒杀商品详情 | 更新时刷新 |
userId | 订单确认临时缓存 | 3600s |
reduceStockLock:productId | 库存扣减分布式锁 | 20s |
动机: 上一轮 AI 重构后代码可读性下降 — God Class 膨胀(ProductService 1011行、TradeService 1027行、UserService 712行)、Javadoc 只重复方法名不解释 WHY、异常被静默吞掉、命名不一致、魔法数字散落各处。
原则: 所有 Controller 的 URL / HTTP 方法 / 请求响应结构均不变,内部实现拆分不影响外部 API。
设计决策:
- 拆分后的原类变成
@Deprecated门面(Facade),内部委托给新服务,保证向后兼容 - 库存扣减使用 Redisson 多锁(联锁),因为一个订单可能包含多个商品,必须所有商品锁都获取成功才能扣减,否则数据不一致
- 订单创建使用
TransactionTemplate而非@Transactional,因为 ShardingSphere 分库分表下声明式事务不生效 - 订单确认数据存入 Redis(TTL 1小时),防止用户绕过确认步骤直接构造下单请求篡改金额
- 下单成功后发送 RocketMQ 延迟消息(30分钟),消费者检查是否已支付,未支付则自动取消
- 登录成功后查询 IP 归属地做异地检测,若本次城市与上次不同且时间间隔在阈值内,触发告警邮件
服务地图(新人阅读顺序):
mall-api(Controller 层,入口)
├── admin/ → 管理端接口
└── mobile/ → 移动端接口
mall-service(业务逻辑层)
├── mall/ → 商品域: ProductQueryService, ProductCommandService, ProductSearchService, ProductPhotoService
├── order/ → 订单域: TradeConfirmService(确认), TradeSubmitService(提交), TradeStockService(库存), TradeSeckillService(秒杀)
├── sys/ → 用户域: UserAuthService(认证), UserGeoIpService(异地检测)
├── marketing/→ 营销域: CouponCalcService(优惠券计算), CouponService
├── shopping/ → 购物域: ShoppingCartService
├── seckill/ → 秒杀域: SeckillProductService
└── pay/ → 支付域: PayService
阅读建议: ProductQueryService → TradeConfirmService → TradeSubmitService → TradeStockService → UserAuthService
改动详情:
| 类别 | 改动 | 原因 |
|---|---|---|
| God Class 拆分 | ProductService(1011行)→ProductQueryService(374L) + ProductCommandService(523L) + ProductSearchService(127L) + ProductPhotoService(218L) | 查询/命令/搜索/图片各司其职,看类名就知道干什么 |
| God Class 拆分 | TradeService(1027行)→TradeStockService(102L) + TradeConfirmService(291L) + TradeSubmitService(287L) + TradeSeckillService(102L) | 下单流程的每一步独立成类,原类重写为340行@Deprecated门面 |
| God Class 拆分 | UserService(712行)→UserAuthService(280L) + UserGeoIpService(85L) | 认证登录与IP检测独立,原类重写为215行@Deprecated门面 |
| 逻辑收敛 | 优惠券计算从 ShoppingCartService 移到 CouponCalcService(30L) | 策略模式调用封装,不再散落在购物车逻辑里 |
| 泛型清理 | BaseMapper<K,V>→BaseMapper<V>(45个Mapper同步更新) | K泛型从未使用,误导开发者以为需要两个类型参数 |
| 常量提取 | 魔法数字迁移到 TradeConstant / UserConstant / ProductConstant | 数字有名字和注释说明含义,修改一处全局生效 |
| 异常修复 | RedisUtil 12个方法 + MqHelper 5个send() 不静默吞异常 → 抛RuntimeException/BusinessException | 调用方能感知失败并决定如何处理 |
| 命名修复 | buildDeptTree→buildCategoryTree, SECKILL_PRODUCT_DETAIL_PFREFIX→PREFIX | 消除误导:不是部门树是品类树,不是PFREFIX是PREFIX |
| 死代码标注 | TradeSaveService.getOvertimeCancelTradeKey() 等 → @Deprecated + "从未被项目调用" | 不删代码但明确标注,防止新人以为是可用API |
| 风格统一 | BrandService Tab→4空格缩进;多余空行清理 | 全项目缩进一致 |
| WHY注释 | 9处 TransactionTemplate、RocketMQ延迟级别、AopContext代理、@Lazy循环依赖、Redis缓存无TTL、CompletableFuture异步 全部加注释 | 新人不必猜测为什么不用@Transactional、为什么16=30min、为什么getSelfProxy() |
| 编译修复 | ProductSearchService 中 ProductDetailEntity 导包修正(mall.entity→mongo.entity) | 前次重构遗留错误,编译失败,现修复 |
| 安全修复 | UserController.insert() 删除 @NoLogin;WebUserController.login() Javadoc从"手机号登录"修正为"用户名+密码登录" | 创建用户需要认证,文档描述不能误导 |
| BeanUtil→MapStruct | 5处 BeanUtil.copyProperties 替换为 MapStruct ConvertMapper 方法:RefundService、TradeService、ShoppingCartService、IndexNoticeService、ProductQueryService | 编译期生成转换代码,字段映射可追踪、编译期报错,比反射黑盒更安全可读 |
| 空指针防御 | IndexNoticeEntity.sort 设默认值0;IndexNoticeService.insert() 加 null guard | Jackson反序列化给null时MyBatis XML 跳过NOT NULL列导致DataIntegrityViolationException |
动机: 系统性审计代码后发现的 8 个可被利用的漏洞——短信可被轰炸、订单可重复提交、登录锁定可被并发绕过、验证码解析有 bug 等。
| 严重 | 问题 | 修复 | 攻击面 |
|---|---|---|---|
| 🔴 高危 | 短信接口无频率限制 | SmsController.sendSmsCode() 加 @Limit(permitsPerSecond=1, limitType=IP) |
攻击者 OCR 算术验证码后无限发短信,消耗费用 |
| 🔴 高危 | 订单提交流程未防重 | WebTradeController.submit() 加 @RepeatSubmit |
用户双击/网络重试创建重复订单 |
| 🔴 高危 | 登录错误计数非原子操作 | recordLoginErrorUser() 的 GET→parseInt→++→SET 改为 Redis INCR + expire |
并发请求同时读到相同 count,全部绕过 MAX_LOGIN_ERROR_COUNT 锁定 |
| 🔴 高危 | 全局异常处理器穿透 | GlobalExceptionHandler 中 MethodArgumentNotValidException 的 else 分支补 return |
参数校验失败被误报为"服务器内部错误" |
| 🟡 中危 | 验证码解析 fallback 错误 | captcha.text() 返回算术表达式时不抛异常则手动解析 + - * |
旧代码 catch 里用表达式字符串(如 "12+34")当答案,验证码形同虚设 |
| 🟡 中危 | 退款缺少必填字段校验 | RefundVO.content 加 @NotEmpty,refundAmount 加 @Positive,WebRefundController 加 @Valid |
用户可提交空原因的退款单、负金额退款 |
| 🟡 中危 | 阿里云短信异常无日志 | AliyunSmsService.send() 的 catch(Exception) 块补 log.error |
短信发送失败无迹可查,排查困难 |
| 🟢 低危 | @Limit key 复制错误 |
WebUserController 两个登录接口的 key="getCity" 改为 key="login" / "loginByPhone" |
限流计数器 key 冲突,两个接口共用一个计数器 |
动机: 项目完全依赖 Spring Boot 默认日志——无 logback 配置、无文件持久化、无 MDC、多处字符串拼接和 e.printStackTrace(),生产故障排查困难。
| 严重 | 问题 | 修复 | 影响 |
|---|---|---|---|
| 🔴 高危 | 无 logback 配置 | 新增 mall-api、mall-job 两个模块的 logback-spring.xml:dev 环境控制台输出;prod 环境控制台 + 文件双通道,按天轮转 + 100MB 切分 + 保留 30 天 + 总量上限 10GB |
生产日志可持续化,容器重启不丢失 |
| 🔴 高危 | WebSocketServer 全 8 处日志用 + 拼接 |
改为 SLF4J 参数化:log.info("用户连接:{}, 当前在线人数为:{}", userId, getOnlineCount()) |
日志级别关闭时免拼接开销,支持日志框架结构化索引 |
| 🔴 高危 | 异步回调异常栈丢失 | MqHelper 两处 async send callback onException(Throwable throwable) 中 throwable 从占位符值改为末位异常参数 |
异步投递失败时完整打印异常栈 |
| 🔴 高危 | e.printStackTrace() 直接写 stderr |
WebSocketServer.onError() 和 CommonAreaService.init() 改为 log.error("...", e) |
纳入日志框架统一管理,与业务日志同一通道 |
| 🟡 中危 | GlobalExceptionHandler 日志级别不当 | BusinessException/AccessDeniedException 从 log.info 升为 log.warn;MethodArgumentNotValidException 补日志;所有异常新增 request.getRequestURI() |
生产定位异常有 URI 可循;业务异常 WARN 级别可被监控系统采集 |
| 🟡 中危 | IOException 重包装前不记原始异常 | CommonAreaService 三处 catch(IOException e) { throw new RuntimeException(e); } 补 log.error 含 URL 上下文 |
爬虫失败时有足够信息排查 |
| 🟡 中危 | BizLogAspect 异常静默 | @AfterThrowing 补 log.error("业务日志记录异常", e) + bizLogService.save() 外包 try-catch 防二次异常丢失 |
切面异常不再被无声吞噬 |
| 🟢 低危 | System.out.println 残留 |
CommonAreaService 删除调试输出;RandomUtil.main() 改用 @Slf4j 的 log.info |
避免杂散输出污染 stdout |
| 🟢 低危 | 无生产日志级别抑制 | prod logback 配置 org.springframework: WARN、com.alibaba: WARN、org.apache: WARN |
减少框架层噪音,聚焦业务日志 |
mall-api/src/main/resources/logback-spring.xml
mall-job/src/main/resources/logback-spring.xml
- dev 环境: console appender,
%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n - prod 环境: console +
logs/mall-*.log,按天轮转单文件 ≤100MB,保留 30 天总 ≤10GB - 框架日志抑制:Spring / Alibaba / Apache 设为 WARN 级别
评估日期:2026-06-07 | 评估范围:全模块
| # | 问题 | 现状 | 风险 |
|---|---|---|---|
| 1 | 零测试 | 整个项目 0 个单元测试、0 个集成测试 | 改一行代码就可能打挂核心下单链路 |
| 2 | 短信不可用 | dypnsapi 签名/模板在号码验证服务控制台不存在,报 isv.INVALID_PARAMETERS;切回 dysmsapi 又因阿里云个人资质审核不过 |
用户收不到验证码,手机号登录和注册全挂 |
| 3 | 无数据库版本管理 | SQL 脚本丢在 sql/ 目录手工执行,没有 Flyway/Liquibase |
无法追踪某台机器跑过哪些 DDL,回滚靠猜 |
| 4 | application.yml(公共)+ application-dev.yml(开发)+ application-prod.yml(生产模板,${ENV_VAR} 占位) |
开发 mock 不再泄漏到生产 |
| # | 问题 | 现状 | 风险 |
|---|---|---|---|
| 5 | 两个模块 application.yml 均配置 management.endpoints.web.exposure.include: health,info |
K8s 探针就绪 | |
| 6 | 无熔断降级 | 调阿里云短信、支付宝、GeoIP 全是直连,无 Resilience4j/Sentinel | 外部服务一抖,请求线程全挂 |
| 7 | 文件上传无限制 | 没配 max-file-size,没校验 MIME 类型 |
一个超大文件就能打爆磁盘 |
| 8 | 无 CI/CD | 没 GitHub Actions、没 Dockerfile | 部署靠手敲 mvn spring-boot:run |
| 9 | 无结构化日志 | 纯文本 log,无 traceId 串联 | 用户请求经过 5 个 Service 后无法串起来排查 |
| # | 问题 | 现状 |
|---|---|---|
| 10 | 无压测报告 | 不知道单机能扛多少 QPS,秒杀链路瓶颈在哪 |
| 11 | 无监控告警 | 没接 Prometheus/Grafana,JVM 内存爆了都没人知道 |
| 12 | API 限流覆盖不全 | 全站 50+ 个接口只有 3 个加了 @Limit |
| 13 | 无数据脱敏 | 手机号、邮箱原样落库和打日志,不符合个保法 |
| 14 | WebSocket 无认证 | WebSocketServer 没有 token 校验 |
| 15 | 密钥未外置 | 虽然 .gitignore 排除了 application.yml,但密钥仍明文写在配置文件里,无 Vault/环境变量注入 |
预估:距「能放心上生产」还需约 2 周全职工时,核心卡在测试、短信、DB 迁移 3 项。(环境隔离 + 健康检查已于 2026-06-07 完成)