Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mall 商城后端

SpringBoot 2.7 + DDD 五模块 + Maven

JDK SpringBoot Maven MyBatis MySQL Redis Elasticsearch License Frontend


🏪 项目简介

生产级 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

  1. 校验用户已登录,查询购物车选中商品
  2. 检查商品库存(ProductEntity.remainQuantity
  3. 匹配可用优惠券:根据券类型(满减/折扣/现金)和绑定商品范围,策略模式计算优惠金额
  4. 汇总 totalAmount(商品总额)、subtractMoney(优惠金额)、finalMoney(实付金额)
  5. 生成订单编号(雪花 ID),将完整确认数据序列化为 JSON 存入 Redis,key=userId,TTL=1h

存 Redis 的目的:防止用户绕过确认页直接调提交接口,同时保证确认-提交之间数据一致性

Step 2 — 提交订单 POST /v1/web/trade/submit

  1. Redis 读取确认数据,校验存在且 tradeCode 未过期
  2. 校验收货地址存在
  3. 分布式锁扣库存:对每件商品 reduceStockLock:{productId} 加 Redisson 多锁(超时 20s),锁内 TransactionTemplate 事务扣减 product.remainQuantity
  4. ShardingSphere 分片写入:按订单号 hashCode 取模路由到具体分片(2 库 × 2 表),事务插入 TradeEntity + TradeItemEntity
  5. 保存收货地址快照 TradeDeliveryAddressEntity
  6. 异步后处理(独立事务):核销优惠券(useStatus=1)、清理购物车(软删除)
  7. 发送 RocketMQ 延迟消息(延迟 30min)→ 超时未支付自动取消

Step 3 — 支付 POST /v1/web/pay/doPay

  1. 支付宝 EasySDK FaceToFace.preCreate() 生成扫码支付串
  2. Hutool QrCodeUtil 将支付 URL 生成 PNG 二维码返给前端
  3. 支付宝异步回调 → 验签 → 更新订单状态为"已支付"

测试环境可用 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

  1. 校验源商品存在 → 插入 SeckillProductEntity(秒杀价、预扣库存、起止时间)
  2. 三向同步:MySQL 落库 + ES 写索引(供搜索/首页展示)+ Redis 写库存计数器 seckillProductStock:{id} 和详情缓存 seckillProductDetail:{id}

用户端 — 秒杀下单 POST /v1/web/trade/createForSeckill

  1. 接口标记 @VerifySign(签名防篡改)+ @NoLogin(无需登录,userName 识别用户)
  2. 查用户 + 商品 → 构造 TradeEntity(orderType=SECKILL_PRODUCT)
  3. Redisson 锁扣库存 → 插入订单 → RocketMQ 超时取消
秒杀 vs 普通订单区别:
  认证: 签名校验 / JWT Token
  来源: 直接指定商品 / 购物车批量
  优惠券: 不可用 / 可用
  收货地址: 不保存 / 保存快照

三、优惠券全链路

[管理员建券模板] → [配置发放规则] → [用户浏览可领券] → [领取] → [下单核销]
   6种类型          绑定商品范围      检查是否已领         行锁排队    策略计算

优惠券三层数据模型

层级 实体 含义
模板 CouponEntity 定义类型、面额、门槛、有效期、发放总量
发放 CouponUserProvideEntity 将模板绑定到商品(productId=0=全场通用)
领取 CouponUserReceiveEntity 用户领取记录 + 使用状态

6 种优惠策略(策略模式,CouponContext.calcPayMoney()):

类型 计算逻辑
现金券 CASH 直接减 offMoney
阶梯满减 NORMAL_FULL_REDUCTION minMoneyoffMoney
每满减 EVERY_FULL_REDUCTION 每满 minMoney 就减一次 offMoney
通用折扣 NORMAL_DISCOUNT 总价 × discount / 100
满件折扣 FULL_COUNT_DISCOUNT minProductCount 件打折
满额折扣 FULL_AMOUNT_DISCOUNT minMoney 元打折

领取流程POST /v1/web/coupon/receiveCoupon): TransactionTemplate 事务 → 行锁 findByIdForUpdatereceiveCount+1 → 插入领取记录

核销流程: 确认订单时过滤可用券(时间有效 + 商品匹配)→ 提交订单时批量 updateForBatch 标记已使用


四、退款售后全链路

[用户申请退款] → [上传凭证图] → [订单标记 REJECT] → [管理员审核] → [同意/拒绝] → [退款完成]
  1. 根据 tradeCode + tradeItemId 查订单和明细,防重校验(同一商品不能重复申请)
  2. TransactionTemplate 事务:插入 RefundEntity(auditStatus=待审核, refundStatus=进行中)+ 批量插入凭证 RefundPhotoEntity
  3. 更新 TradeEntity.orderStatus = REJECT
  4. 管理员审核:auditStatus 待审核→同意(退款处理)/ 拒绝(填写原因)
  5. 退款完成: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
🏢 部门 → DeptEntity🔖 角色 → UserRoleEntity → RoleEntity → MenuEntity
👁 浏览 → ProductViewRecordEntity → Mahout⭐ 收藏 → ProductFavoritesEntity
📝 评价 → ProductCommentEntity🛒 购物车 → ShoppingCartEntity
🎷 领券 → CouponUserEntity ← CouponEntity📧 下单 → TradeEntity
📦 ProductEntity
📂 分类 → CategoryEntity(树形)🏷 品牌 → BrandEntity
⚙ 属性 → ProductAttributeEntity → AttributeValueEntity📸 分组 → ProductGroupEntity
🖼 图片 → ProductPhotoEntity🔍 同步 → Elasticsearch 索引
⚡ 秒杀 → SeckillProductEntity🎢 展示 → 首页轮播/公告
⬇️ 下单后产生的核心实体 ⬇️
💰 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:run

API 文档: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:usernameJWT Token 缓存会话有效期
user:username用户详情会话有效期
seckill_product_stock:id秒杀库存(预热写入)持久化
seckill_product_detail:id秒杀商品详情更新时刷新
userId订单确认临时缓存3600s
reduceStockLock:productId库存扣减分布式锁20s

重构日志

2026-06-03 可读性重构

动机: 上一轮 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

2026-06-07 防呆/安全加固

动机: 系统性审计代码后发现的 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 锁定
🔴 高危 全局异常处理器穿透 GlobalExceptionHandlerMethodArgumentNotValidException 的 else 分支补 return 参数校验失败被误报为"服务器内部错误"
🟡 中危 验证码解析 fallback 错误 captcha.text() 返回算术表达式时不抛异常则手动解析 + - * 旧代码 catch 里用表达式字符串(如 "12+34")当答案,验证码形同虚设
🟡 中危 退款缺少必填字段校验 RefundVO.content@NotEmptyrefundAmount@PositiveWebRefundController@Valid 用户可提交空原因的退款单、负金额退款
🟡 中危 阿里云短信异常无日志 AliyunSmsService.send()catch(Exception) 块补 log.error 短信发送失败无迹可查,排查困难
🟢 低危 @Limit key 复制错误 WebUserController 两个登录接口的 key="getCity" 改为 key="login" / "loginByPhone" 限流计数器 key 冲突,两个接口共用一个计数器

2026-06-07 结构化日志修复

动机: 项目完全依赖 Spring Boot 默认日志——无 logback 配置、无文件持久化、无 MDC、多处字符串拼接和 e.printStackTrace(),生产故障排查困难。

严重 问题 修复 影响
🔴 高危 无 logback 配置 新增 mall-apimall-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/AccessDeniedExceptionlog.info 升为 log.warnMethodArgumentNotValidException 补日志;所有异常新增 request.getRequestURI() 生产定位异常有 URI 可循;业务异常 WARN 级别可被监控系统采集
🟡 中危 IOException 重包装前不记原始异常 CommonAreaService 三处 catch(IOException e) { throw new RuntimeException(e); }log.error 含 URL 上下文 爬虫失败时有足够信息排查
🟡 中危 BizLogAspect 异常静默 @AfterThrowinglog.error("业务日志记录异常", e) + bizLogService.save() 外包 try-catch 防二次异常丢失 切面异常不再被无声吞噬
🟢 低危 System.out.println 残留 CommonAreaService 删除调试输出;RandomUtil.main() 改用 @Slf4jlog.info 避免杂散输出污染 stdout
🟢 低危 无生产日志级别抑制 prod logback 配置 org.springframework: WARNcom.alibaba: WARNorg.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 完成)

Built with SpringBoot + MyBatis + Maven

About

生产级 B2C 电商后端 —— SpringBoot 多模块架构,覆盖商品/订单/秒杀/优惠券/搜索/推荐/权限,开箱即用

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages