进阶开发概述
作者:唐亚峰 | battcn
字数统计:2.3k 字
适合人群
已掌握基础开发,希望深入了解架构设计和最佳实践的开发者
进阶内容
本章节涵盖以下进阶内容:
架构设计
- 微服务拆分原则 - 如何合理拆分服务边界
- 领域驱动设计 - DDD 在项目中的应用
- 事件驱动架构 - 使用消息队列解耦服务
性能优化
- 数据库优化 - 索引、慢查询、分库分表
- 缓存策略 - 多级缓存、缓存一致性
- 接口优化 - 并发、异步、批量处理
安全加固
- 认证授权 - Sa-Token 高级特性
- 数据安全 - 加密、脱敏、审计
- 接口安全 - 防重放、签名验证
运维部署
- CI/CD - 自动化构建和部署
- 监控告警 - Prometheus + Grafana
- 日志管理 - ELK 日志收集
架构全景
┌──────────────────────────────────────────────────────────────────┐
│ 负载均衡 (Nginx/SLB) │
└─────────────────────────────┬────────────────────────────────────┘
│
┌─────────────────────────────▼────────────────────────────────────┐
│ API 网关 (Gateway) │
│ ├─ 统一入口 ├─ 路由转发 ├─ 限流熔断 │
│ ├─ 身份认证 ├─ 请求日志 └─ 灰度发布 │
└─────────────────────────────┬────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌───────▼───────┐ ┌────────▼────────┐ ┌───────▼───────┐
│ IAM 认证中心 │ │ Suite 业务 │ │ Plugin 插件 │
│ ├─ 用户管理 │ │ ├─ 核心业务 │ │ ├─ BPM 审批 │
│ ├─ 权限管理 │ │ └─ 扩展业务 │ │ ├─ Job 任务 │
│ └─ 租户管理 │ │ │ │ └─ AI 能力 │
└───────┬───────┘ └────────┬────────┘ └───────┬───────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
┌─────────────────────────────▼────────────────────────────────────┐
│ 基础设施层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Nacos │ │ Redis │ │ MySQL │ │ MinIO │ │
│ │ 注册配置 │ │ 缓存 │ │ 数据库 │ │ 文件存储 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────────┘技术选型原则
框架选择
| 场景 | 推荐方案 | 备选方案 |
|---|---|---|
| 服务通信 | OpenFeign | Dubbo |
| 配置中心 | Nacos | Apollo |
| 注册中心 | Nacos | Eureka |
| 网关 | Spring Cloud Gateway | Zuul |
| 限流熔断 | Sentinel | Resilience4j |
| 分布式事务 | Seata | RocketMQ 事务消息 |
| 定时任务 | Snail Job | XXL-Job |
| 审批流程 | Warm-Flow | Camunda |
数据库选择
| 场景 | 推荐方案 |
|---|---|
| 业务数据 | MySQL 8.0 |
| 缓存数据 | Redis 6+ |
| 文档存储 | MongoDB |
| 搜索引擎 | Elasticsearch |
| 时序数据 | InfluxDB / TDengine |
多租户架构(真实代码)
Wemirr Platform 支持三种多租户模式,通过 DatabaseProperties 配置:
多租户模式
java
// 来自 MultiTenantType.java
public enum MultiTenantType {
NONE("非租户模式"), // 不启用多租户
COLUMN("字段模式"), // 通过 tenant_id 字段隔离(推荐,数据量 < 1000w)
DATASOURCE("独立数据源模式"), // 每个租户独立数据库(大客户)
}配置示例
yaml
# application.yml
extend:
mybatis-plus:
multi-tenant:
type: COLUMN # 租户模式:NONE/COLUMN/DATASOURCE
super-tenant-code: "0000" # 超级租户编码
tenant-id-column: "tenant_id" # 租户字段名
include-tables: # 需要租户过滤的表
- t_user
- t_role
- t_orgTenantHelper 工具类
java
// 来自 TenantHelper.java - 租户操作工具
// 判断是否是超级租户
boolean isSuper = TenantHelper.isSuperTenant();
// 使用主数据源执行(查询平台级数据)
List<Dict> dictList = TenantHelper.executeWithMaster(() -> {
return dictMapper.selectList(SysDict::getType, 1);
});
// 切换到指定租户数据源执行
TenantHelper.executeWithTenantDb("8888", () -> {
List<User> users = userMapper.selectByTenantId(tenantId);
return users;
});
// 临时忽略租户过滤
User user = TenantHelper.withIgnoreStrategy(() -> {
return userMapper.selectById(userId);
});租户拦截器原理
java
// 来自 BaseMybatisConfiguration.java
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 自动获取当前登录用户的租户ID
return context.tenantId() == null ? null : new LongValue(context.tenantId());
}
@Override
public boolean ignoreTable(String tableName) {
// 匿名用户或不在配置表中的表,不添加租户过滤
return context.anonymous() || !tables.contains(tableName);
}
}));数据权限(真实代码)
框架内置数据权限支持,支持多维度控制。
数据权限类型
java
// 来自 DataScopeType.java
public enum DataScopeType {
ALL, // 全部数据
THIS_LEVEL, // 本级数据
THIS_LEVEL_CHILDREN,// 本级及子级
CUSTOMIZE, // 自定义
SELF // 仅本人
}数据权限实现
java
// 来自 DataScopeServiceImpl.java
@Override
public DataPermission getDataScopeById(Long userId, Long orgId) {
List<Role> list = roleMapper.findRoleByUserId(userId);
// 找到权限最大的角色
Role role = list.stream()
.max(Comparator.comparingInt(item -> item.getScopeType().getType()))
.get();
DataPermission permission = DataPermission.builder()
.scopeType(role.getScopeType())
.build();
List<Long> userIdList = null;
if (role.getScopeType() == CUSTOMIZE) {
// 自定义:查询角色关联的组织下的用户
List<Long> orgIdList = dataPermissionRefMapper.selectList(...)
.stream().map(DataPermissionRef::getDataId).toList();
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.in(User::getOrgId, orgIdList))
.stream().map(Entity::getId).toList();
} else if (role.getScopeType() == THIS_LEVEL) {
// 本级:只查询同组织用户
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.eq(User::getOrgId, orgId))
.stream().map(Entity::getId).toList();
} else if (role.getScopeType() == THIS_LEVEL_CHILDREN) {
// 本级及子级
List<Long> orgIdList = orgService.getFullTreeIdPath(orgId);
userIdList = userMapper.selectList(Wraps.<User>lbQ()
.in(User::getOrgId, orgIdList))
.stream().map(Entity::getId).toList();
}
permission.getDataPermissionMap().put(DataResourceType.USER, userIdList);
return permission;
}使用数据权限
java
// 在 Service 中使用
@Override
@RemoteResult
public IPage<UserPageResp> pageList(UserPageReq req) {
// 使用 DataPermissionUtils 包装查询,自动应用数据权限
return DataPermissionUtils.executeWithRule(
DataPermissionRule.builder()
.columns(List.of(new DataPermissionRule.Column()))
.build(),
() -> baseMapper.selectPage(req.buildPage(), wrapper)
);
}分布式锁(真实代码)
框架提供注解式分布式锁,基于 Redisson 实现。
@RedisLock 注解
java
// 来自 RedisLockInterceptor.java
@RedisLock(
prefix = "order:create", // 锁前缀
delimiter = ":", // 分隔符
expire = 30, // 过期时间(秒)
waitTime = 10, // 等待时间(秒)
timeUnit = TimeUnit.SECONDS,
lockType = LockType.REENTRANT_LOCK, // 锁类型
message = "操作过于频繁,请稍后重试"
)
public void createOrder(OrderDTO dto) {
// 自动加锁,方法执行完自动释放
}锁类型
java
public enum LockType {
REENTRANT_LOCK, // 可重入锁(默认)
FAIR_LOCK, // 公平锁
READ_LOCK, // 读锁
WRITE_LOCK, // 写锁
RED_LOCK, // 红锁
MULTI_LOCK // 联锁
}支持 SpEL 表达式
java
// 动态锁 key
@RedisLock(prefix = "'order:' + #dto.userId", expire = 30)
public void createOrder(OrderDTO dto) {
// 锁 key = order:{userId}
}动态数据源
独立数据源模式下,框架自动切换租户数据源。
自动切换原理
java
// 来自 DynamicDataSourceWebMvcAutoConfiguration.java
@Override
public boolean preHandle(HttpServletRequest request, ...) {
String tenantCode = context.tenantCode();
String dsKey;
if (multiTenant.isSuperTenant(tenantCode)) {
// 超级租户使用主库
dsKey = multiTenant.getDefaultDsName(); // master
} else {
// 普通租户使用租户库
dsKey = multiTenant.getDsPrefix() + tenantCode; // wemirr_tenant_8888
}
DynamicDataSourceContextHolder.push(dsKey);
return true;
}差异日志(真实代码)
框架内置差异日志功能,自动记录数据修改前后的变化。
@DiffLog 注解
java
// 来自 DiffLog.java
@DiffLog(
group = "用户管理", // 业务分组
tag = "编辑用户", // 操作标签
businessKey = "{{#id}}", // 业务标识(SpEL)
success = "更新用户信息 {_DIFF{#_newObj}}", // 成功日志
fail = "更新用户信息异常 {{#id}}" // 失败日志
)
public void modify(Long id, UserUpdateReq req) {
User oldUser = userMapper.selectById(id);
User newUser = BeanUtil.toBean(req, User.class);
// 设置差异对比对象
DiffLogContext.putDiffItem(oldUser, newUser);
userMapper.updateById(newUser);
}自动记录字段变化
java
// 在实体字段上标记需要对比的字段
@Data
public class User {
@DiffField(name = "用户名", strategy = DiffFieldStrategy.NOT_NULL)
private String username;
@DiffField(name = "昵称")
private String nickName;
@DiffField(name = "手机号")
private String mobile;
}日志输出示例:更新用户信息 【用户名】由【张三】改为【李四】,【手机号】由【138****8888】改为【139****9999】
异步处理(真实代码)
框架内置异步线程池配置,支持 @Async 注解。
配置
yaml
# application.yml
extend:
boot:
async:
core-pool-size: 10 # 核心线程数
max-pool-size: 50 # 最大线程数
queue-capacity: 10000 # 队列容量
keep-alive-seconds: 60 # 线程存活时间
thread-name-prefix: "wemirr-async-thread-"使用
java
@Service
@RequiredArgsConstructor
public class NotificationService {
private final EmailService emailService;
private final SmsService smsService;
// 异步发送通知,不阻塞主流程
@Async
public void sendNotification(Order order) {
emailService.send(order.getEmail(), "订单创建成功");
smsService.send(order.getMobile(), "订单创建成功");
}
}上下文传递
框架自动处理异步线程的 Request 上下文复制:
java
// 来自 AsyncConfiguration.java - 自动复制请求上下文
private static class RequestAttributesTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(attributes);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
}缓存配置(真实代码)
框架基于 Spring Cache + Redis 实现缓存功能。
配置
yaml
# application.yml
extend:
redis:
cache:
enabled: true
prefix: "wemirr_cache_"
timeout: 86400 # 默认 24 小时
items:
- name: user
timeout: 3600 # 用户缓存 1 小时
enabled: true
- name: dict
timeout: 86400 # 字典缓存 24 小时
enabled: true使用
java
@Service
public class DictService {
// 查询时缓存
@Cacheable(value = "dict", key = "#code")
public List<DictItem> getByCode(String code) {
return dictMapper.selectByCode(code);
}
// 更新时清除缓存
@CacheEvict(value = "dict", key = "#dto.code")
public void update(DictDTO dto) {
dictMapper.updateById(dto);
}
// 更新时更新缓存
@CachePut(value = "dict", key = "#dto.code")
public DictDTO updateAndReturn(DictDTO dto) {
dictMapper.updateById(dto);
return dto;
}
}Feign 远程调用
框架封装了 Feign 调用,自动传递认证信息和租户信息。
官方文档
📖 Spring Cloud OpenFeign:https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
定义 Feign Client
java
@FeignClient(name = "wemirr-platform-iam", path = "/iam")
public interface IamFeignClient {
@GetMapping("/users/{id}")
UserVO getUserById(@PathVariable Long id);
@PostMapping("/users/batch_ids")
Map<Long, UserVO> batchGetUsers(@RequestBody Set<Long> ids);
}自动传递 Header
java
// 来自 FeignPluginInterceptor.java - 自动传递认证信息
@Override
public void apply(RequestTemplate template) {
// 传递 Token
template.header("Authorization", "Bearer " + token);
// 传递租户信息
template.header("Tenant-Code", tenantCode);
// 传递追踪ID
template.header("Trace-Id", traceId);
}@RemoteResult 自动填充
java
@Override
@RemoteResult // 自动填充远程数据
public IPage<OrderVO> pageList(OrderPageReq req) {
// 查询订单列表
IPage<OrderVO> page = orderMapper.selectPage(...);
// @RemoteResult 会自动调用 Feign 填充 userName 等远程字段
return page;
}操作日志
框架提供 @AccessLog 注解记录操作日志。
java
@PostMapping("/create")
@AccessLog(module = "用户管理", description = "添加用户")
public void create(@RequestBody UserSaveReq req) {
userService.create(req);
}自动记录:IP、URI、用户、耗时、参数等信息。
开发规范
代码规范
java
// 来自 TenantServiceImpl.java - 真实的服务实现规范
@Slf4j
@Service
@RequiredArgsConstructor
public class TenantServiceImpl extends SuperServiceImpl<TenantMapper, Tenant>
implements TenantService {
private final AuthenticationContext context;
private final DatabaseProperties properties;
@Override
@DSTransactional(rollbackFor = Exception.class)
public void create(TenantSaveReq req) {
// 1. 业务校验
long nameCount = this.baseMapper.selectCount(Tenant::getName, req.getName());
if (nameCount > 0) {
throw CheckedException.badRequest("租户名称重复");
}
long codeCount = this.baseMapper.selectCount(Tenant::getCode, req.getCode());
if (codeCount > 0) {
throw CheckedException.badRequest("租户编码重复");
}
// 2. 数据转换
Tenant tenant = BeanUtil.toBean(req, Tenant.class);
// 3. 持久化
this.baseMapper.insert(tenant);
}
}异常处理规范
java
// 来自 CheckedException.java - 框架统一异常
// 400 错误
throw CheckedException.badRequest("租户名称重复");
throw CheckedException.badRequest("订单 {0} 不存在", orderId);
// 404 错误
throw CheckedException.notFound("用户不存在");
// 403 错误
throw CheckedException.forbidden("登录过期,请重新登录");
// 使用 Optional 优雅处理
final Tenant tenant = Optional.ofNullable(this.baseMapper.selectById(id))
.orElseThrow(() -> CheckedException.notFound("租户不存在"));学习路线
入门阶段(1-2周)
├─ 环境搭建
├─ 项目运行
└─ 基础 CRUD
进阶阶段(2-4周)
├─ 理解架构设计
├─ 掌握核心组件
├─ 多租户开发
└─ 数据权限
精通阶段(4周+)
├─ 性能优化
├─ 高可用设计
├─ 安全加固
└─ 源码阅读