Skip to content

进阶开发概述

作者:唐亚峰 | 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   │            │
│  │ 注册配置  │ │   缓存   │ │  数据库  │ │ 文件存储  │            │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘            │
└──────────────────────────────────────────────────────────────────┘

技术选型原则

框架选择

场景推荐方案备选方案
服务通信OpenFeignDubbo
配置中心NacosApollo
注册中心NacosEureka
网关Spring Cloud GatewayZuul
限流熔断SentinelResilience4j
分布式事务SeataRocketMQ 事务消息
定时任务Snail JobXXL-Job
审批流程Warm-FlowCamunda

数据库选择

场景推荐方案
业务数据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_org

TenantHelper 工具类

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 调用,自动传递认证信息和租户信息。

定义 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周+)
├─ 性能优化
├─ 高可用设计
├─ 安全加固
└─ 源码阅读

下一步