From 8029ac31daa435c90ed75da5d6e934a09ee0ee84 Mon Sep 17 00:00:00 2001 From: zhangjf Date: Sat, 14 Feb 2026 00:34:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9E=B6=E6=9E=84=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=A1=A5=E5=85=85=E5=A4=9A=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=92=8CHead=E6=97=A5=E5=BF=97=E8=BF=BD?= =?UTF-8?q?=E8=B8=AA=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/资金服务平台 FundPlatform 架构设计文档.md | 783 +++++++++++++++++- 1 file changed, 780 insertions(+), 3 deletions(-) diff --git a/doc/资金服务平台 FundPlatform 架构设计文档.md b/doc/资金服务平台 FundPlatform 架构设计文档.md index 7187380..79ce7f7 100644 --- a/doc/资金服务平台 FundPlatform 架构设计文档.md +++ b/doc/资金服务平台 FundPlatform 架构设计文档.md @@ -18,18 +18,794 @@ | **可扩展性** | 微服务架构,支持水平扩展 | | **安全性** | 数据加密传输,完善的权限控制 | | **可维护性** | 模块化设计,代码结构清晰 | +| **多租户** | 支持一库多租户和一库一租户两种模式 | +| **可观测性** | 全链路日志跟踪,支持 Head 日志追踪 | ### 1.2 架构风格 -采用 **微服务架构** + **前后端分离** 模式: +采用 **微服务架构** + **前后端分离** + **多租户架构** 模式: - 后端:Spring Cloud Alibaba 微服务框架 - 前端:Vue 3 + UniApp 多端应用 -- 数据层:MySQL + Redis 缓存 +- 数据层:MySQL + Redis 缓存(支持多租户隔离) - 基础设施:Nacos 服务治理、Nginx 负载均衡 +- 可观测性:Head 日志追踪 + 全链路监控 --- -## 二、系统整体架构 +## 二、多租户架构设计 + +### 2.1 多租户架构概述 + +系统支持两种多租户模式,可根据业务需求灵活选择: + +| 模式 | 说明 | 适用场景 | +|------|------|----------| +| **一库多租户** | 所有租户共享一个数据库,通过 `tenant_id` 字段隔离 | 中小租户、数据量较小、成本敏感 | +| **一库一租户** | 每个租户独立数据库,物理隔离 | 大客户、数据量大、安全要求高 | + +### 2.2 一库多租户模式 + +#### 2.2.1 架构设计 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用服务层 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ fund-sys │ │ fund-cust │ │ fund-proj │ │ +│ │ (多租户) │ │ (多租户) │ │ (多租户) │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +└─────────┼────────────────┼────────────────┼─────────────────┘ + │ │ │ + └────────────────┴────────────────┘ + │ + ┌───────────────┴───────────────┐ + │ TenantContextHolder │ + │ (线程级租户上下文存储) │ + └───────────────┬───────────────┘ + │ +┌─────────────────────────┼─────────────────────────────────────┐ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ MySQL 数据库 │ │ +│ │ ┌─────────────────────────────────────────────────┐ │ │ +│ │ │ fund_platform 库 │ │ │ +│ │ │ ┌───────────┬───────────┬───────────┬─────────┐ │ │ │ +│ │ │ │ tenant_id │ user_id │ username │ ... │ │ │ │ +│ │ │ ├───────────┼───────────┼───────────┼─────────┤ │ │ │ +│ │ │ │ 1 │ 1 │ admin │ ... │ │ │ │ +│ │ │ │ 1 │ 2 │ user1 │ ... │ │ │ │ +│ │ │ │ 2 │ 1 │ admin │ ... │ │ │ │ +│ │ │ └───────────┴───────────┴───────────┴─────────┘ │ │ │ +│ │ │ 所有表包含 tenant_id 字段用于数据隔离 │ │ │ +│ │ └─────────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└───────────────────────────────────────────────────────────────┘ +``` + +#### 2.2.2 技术实现 + +**1. 租户识别** + +```java +/** + * 租户上下文持有者 + */ +public class TenantContextHolder { + private static final ThreadLocal CURRENT_TENANT = new ThreadLocal<>(); + + public static void setTenantId(Long tenantId) { + CURRENT_TENANT.set(tenantId); + } + + public static Long getTenantId() { + return CURRENT_TENANT.get(); + } + + public static void clear() { + CURRENT_TENANT.remove(); + } +} +``` + +**2. 租户拦截器** + +```java +/** + * 租户拦截器 - 从请求头或JWT中提取租户ID + */ +@Component +public class TenantInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) { + // 从请求头获取租户ID + String tenantId = request.getHeader("X-Tenant-Id"); + + // 或从JWT Token中解析 + if (StringUtils.isEmpty(tenantId)) { + String token = request.getHeader("Authorization"); + tenantId = JwtUtil.getTenantId(token); + } + + if (StringUtils.isNotEmpty(tenantId)) { + TenantContextHolder.setTenantId(Long.valueOf(tenantId)); + } + + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, Exception ex) { + TenantContextHolder.clear(); + } +} +``` + +**3. MyBatis-Plus 租户插件** + +```java +/** + * 多租户 SQL 拦截器 + */ +@Component +public class TenantLineInnerInterceptor implements InnerInterceptor { + + @Override + public void beforeQuery(Executor executor, + MappedStatement ms, + Object parameter, + RowBounds rowBounds, + ResultHandler resultHandler, + BoundSql boundSql) { + // 获取当前租户ID + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId == null) { + return; + } + + // 获取SQL + String originalSql = boundSql.getSql(); + + // 忽略特定表(如租户表本身) + if (isIgnoreTable(ms.getId())) { + return; + } + + // 添加租户条件 + String tenantSql = addTenantCondition(originalSql, tenantId); + + // 重写SQL + reflectSetField(boundSql, "sql", tenantSql); + } + + private String addTenantCondition(String sql, Long tenantId) { + // 使用 JSqlParser 解析并修改 SQL + try { + Statement statement = CCJSqlParserUtil.parse(sql); + if (statement instanceof Select) { + Select select = (Select) statement; + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + + // 添加 WHERE 条件 + EqualsTo equalsTo = new EqualsTo(); + equalsTo.setLeftExpression(new Column("tenant_id")); + equalsTo.setRightExpression(new LongValue(tenantId)); + + Expression where = plainSelect.getWhere(); + if (where == null) { + plainSelect.setWhere(equalsTo); + } else { + AndExpression and = new AndExpression(where, equalsTo); + plainSelect.setWhere(and); + } + + return select.toString(); + } + } catch (JSQLParserException e) { + log.error("SQL解析失败", e); + } + return sql; + } +} +``` + +**4. 实体基类** + +```java +/** + * 多租户实体基类 + */ +@Data +public abstract class TenantBaseEntity extends BaseEntity { + + @TableField(fill = FieldFill.INSERT) + private Long tenantId; +} +``` + +#### 2.2.3 数据隔离策略 + +| 隔离级别 | 实现方式 | 说明 | +|----------|----------|------| +| **行级隔离** | tenant_id 字段过滤 | 默认方式,所有业务表包含 tenant_id | +| **schema隔离** | 不同租户使用不同schema | 可选,适用于数据量大的租户 | +| **缓存隔离** | Redis Key 添加租户前缀 | `tenant:{id}:user:info` | +| **文件隔离** | COS 目录按租户划分 | `/tenant/{id}/files/` | + +### 2.3 一库一租户模式 + +#### 2.3.1 架构设计 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用服务层 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ fund-sys │ │ fund-cust │ │ fund-proj │ │ +│ │ (多租户) │ │ (多租户) │ │ (多租户) │ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +└─────────┼────────────────┼────────────────┼─────────────────┘ + │ │ │ + └────────────────┴────────────────┘ + │ + ┌───────────────┴───────────────┐ + │ DynamicDataSource │ + │ (动态数据源路由) │ + └───────────────┬───────────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ tenant_1_db │ │ tenant_2_db │ │ tenant_3_db │ +│ (租户1数据库) │ │ (租户2数据库) │ │ (租户3数据库) │ +│ │ │ │ │ │ +│ • sys_user │ │ • sys_user │ │ • sys_user │ +│ • customer │ │ • customer │ │ • customer │ +│ • project │ │ • project │ │ • project │ +│ • expense │ │ • expense │ │ • expense │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +#### 2.3.2 技术实现 + +**1. 动态数据源配置** + +```java +/** + * 动态数据源上下文 + */ +public class DynamicDataSourceContextHolder { + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + public static void setDataSourceKey(String key) { + CONTEXT_HOLDER.set(key); + } + + public static String getDataSourceKey() { + return CONTEXT_HOLDER.get(); + } + + public static void clear() { + CONTEXT_HOLDER.remove(); + } +} + +/** + * 动态数据源 + */ +public class DynamicDataSource extends AbstractRoutingDataSource { + + @Override + protected Object determineCurrentLookupKey() { + return DynamicDataSourceContextHolder.getDataSourceKey(); + } +} +``` + +**2. 数据源管理器** + +```java +/** + * 租户数据源管理器 + */ +@Component +public class TenantDataSourceManager { + + @Autowired + private DynamicDataSource dynamicDataSource; + + @Autowired + private TenantDataSourceConfigRepository configRepository; + + /** + * 添加租户数据源 + */ + public void addDataSource(Long tenantId) { + TenantDataSourceConfig config = configRepository.findByTenantId(tenantId); + + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setDriverClassName(config.getDriverClassName()); + dataSource.setUrl(config.getJdbcUrl()); + dataSource.setUsername(config.getUsername()); + dataSource.setPassword(config.getPassword()); + dataSource.setInitialSize(5); + dataSource.setMaxActive(20); + + // 添加到动态数据源 + dynamicDataSource.addDataSource("tenant_" + tenantId, dataSource); + } + + /** + * 切换租户数据源 + */ + public void switchDataSource(Long tenantId) { + String key = "tenant_" + tenantId; + + // 检查数据源是否存在 + if (!dynamicDataSource.hasDataSource(key)) { + addDataSource(tenantId); + } + + DynamicDataSourceContextHolder.setDataSourceKey(key); + } +} +``` + +**3. 租户数据源拦截器** + +```java +/** + * 租户数据源切换拦截器 + */ +@Component +public class TenantDataSourceInterceptor implements HandlerInterceptor { + + @Autowired + private TenantDataSourceManager dataSourceManager; + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) { + // 获取租户ID + String tenantId = request.getHeader("X-Tenant-Id"); + + if (StringUtils.isNotEmpty(tenantId)) { + // 切换数据源 + dataSourceManager.switchDataSource(Long.valueOf(tenantId)); + } + + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, Exception ex) { + DynamicDataSourceContextHolder.clear(); + } +} +``` + +### 2.4 租户管理 + +#### 2.4.1 租户表设计 + +```sql +CREATE TABLE sys_tenant ( + tenant_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID', + tenant_code VARCHAR(50) NOT NULL UNIQUE COMMENT '租户编码', + tenant_name VARCHAR(100) NOT NULL COMMENT '租户名称', + tenant_type TINYINT DEFAULT 1 COMMENT '租户类型:1-一库多租户,2-一库一租户', + db_config JSON COMMENT '数据库配置(一库一租户模式使用)', + status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用', + expire_time DATETIME COMMENT '过期时间', + created_time DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_code (tenant_code), + INDEX idx_status (status) +) COMMENT='租户表'; +``` + +#### 2.4.2 租户创建流程 + +``` +┌─────────┐ 创建租户 ┌─────────┐ +│ 管理员 │ ───────────────> │ 系统 │ +└─────────┘ └────┬────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────┐ + │ 保存租户 │ │ 初始化数据 │ │ 创建数据源 │ + │ 基本信息 │ │ (菜单/角色)│ │ (一库一租户)│ + └───────────┘ └───────────┘ └───────────┘ +``` + +### 2.5 两种模式对比 + +| 特性 | 一库多租户 | 一库一租户 | +|------|-----------|-----------| +| **数据隔离** | 逻辑隔离(tenant_id) | 物理隔离(独立数据库) | +| **部署成本** | 低(共享资源) | 高(独立资源) | +| **运维复杂度** | 低 | 高 | +| **扩展性** | 垂直扩展为主 | 水平扩展 | +| **安全性** | 中 | 高 | +| **适用场景** | 中小客户、SaaS | 大客户、私有化部署 | +| **数据迁移** | 复杂(需筛选数据) | 简单(整库迁移) | + +--- + +## 三、Head 日志追踪设计 + +### 3.1 概述 + +Head 日志追踪(Header-based Logging)是一种基于请求头的全链路日志追踪方案,通过在每个请求中传递唯一的 Trace ID,实现跨服务的调用链追踪。 + +### 3.2 架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Head 日志追踪架构 │ +│ │ +│ ┌─────────┐ │ +│ │ 客户端 │ X-Trace-Id: abc123 │ +│ └────┬────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Gateway (网关) │ │ +│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ +│ │ │ TraceIdFilter │ │ │ +│ │ │ • 生成/接收 TraceId │ │ │ +│ │ │ • 写入 MDC (Mapped Diagnostic Context) │ │ │ +│ │ │ • 传递 TraceId 到下游服务 │ │ │ +│ │ └─────────────────────────────────────────────────────────────┘ │ │ +│ └────────────────────────────────┬───────────────────────────────────┘ │ +│ │ │ +│ ┌───────────────────────────┼───────────────────────────┐ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │fund-sys │ │fund-cust│ │fund-proj│ │ +│ │ │ │ │ │ │ │ +│ │• 接收 │ │• 接收 │ │• 接收 │ │ +│ │ TraceId│ │ TraceId│ │ TraceId│ │ +│ │• 写入 │ │• 写入 │ │• 写入 │ │ +│ │ MDC │ │ MDC │ │ MDC │ │ +│ │• 记录 │ │• 记录 │ │• 记录 │ │ +│ │ 日志 │ │ 日志 │ │ 日志 │ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ +│ └─────────────────────────┼─────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ ELK / Loki 日志平台 │ │ +│ │ • 日志收集 → 解析 → 存储 → 检索 → 可视化 │ │ +│ │ • 支持 TraceId 全链路查询 │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 3.3 技术实现 + +#### 3.3.1 TraceId 生成与传递 + +```java +/** + * TraceId 工具类 + */ +public class TraceIdUtil { + + public static final String TRACE_ID_HEADER = "X-Trace-Id"; + public static final String TRACE_ID_MDC_KEY = "traceId"; + public static final String SPAN_ID_MDC_KEY = "spanId"; + + /** + * 生成 TraceId + */ + public static String generateTraceId() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 16); + } + + /** + * 生成 SpanId + */ + public static String generateSpanId() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 8); + } + + /** + * 获取当前 TraceId + */ + public static String getCurrentTraceId() { + return MDC.get(TRACE_ID_MDC_KEY); + } + + /** + * 设置 TraceId 到 MDC + */ + public static void setTraceId(String traceId) { + MDC.put(TRACE_ID_MDC_KEY, traceId); + } + + /** + * 清除 MDC + */ + public static void clear() { + MDC.clear(); + } +} +``` + +#### 3.3.2 Gateway TraceId 过滤器 + +```java +/** + * Gateway TraceId 过滤器 + */ +@Component +@Order(-100) +public class TraceIdGatewayFilter implements GlobalFilter { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + HttpHeaders headers = request.getHeaders(); + + // 获取或生成 TraceId + String traceId = headers.getFirst(TraceIdUtil.TRACE_ID_HEADER); + if (StringUtils.isEmpty(traceId)) { + traceId = TraceIdUtil.generateTraceId(); + } + + // 生成 SpanId + String spanId = TraceIdUtil.generateSpanId(); + + // 添加到 MDC + TraceIdUtil.setTraceId(traceId); + MDC.put(TraceIdUtil.SPAN_ID_MDC_KEY, spanId); + + // 添加到请求头,传递给下游服务 + ServerHttpRequest mutatedRequest = request.mutate() + .header(TraceIdUtil.TRACE_ID_HEADER, traceId) + .header("X-Span-Id", spanId) + .header("X-Parent-Span-Id", MDC.get(TraceIdUtil.SPAN_ID_MDC_KEY)) + .build(); + + // 记录请求日志 + log.info("[Gateway] Request: {} {}, TraceId: {}", + request.getMethod(), + request.getURI(), + traceId); + + final String finalTraceId = traceId; + + return chain.filter(exchange.mutate().request(mutatedRequest).build()) + .doFinally(signalType -> { + // 记录响应日志 + log.info("[Gateway] Response: {}, TraceId: {}", + exchange.getResponse().getStatusCode(), + finalTraceId); + TraceIdUtil.clear(); + }); + } +} +``` + +#### 3.3.3 服务间 TraceId 传递 + +```java +/** + * Feign 请求拦截器 - 传递 TraceId + */ +@Component +public class FeignTraceIdInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate template) { + String traceId = TraceIdUtil.getCurrentTraceId(); + if (StringUtils.isNotEmpty(traceId)) { + template.header(TraceIdUtil.TRACE_ID_HEADER, traceId); + } + } +} + +/** + * 服务端 TraceId 过滤器 + */ +@Component +public class TraceIdFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + + // 从请求头获取 TraceId + String traceId = httpRequest.getHeader(TraceIdUtil.TRACE_ID_HEADER); + if (StringUtils.isEmpty(traceId)) { + traceId = TraceIdUtil.generateTraceId(); + } + + // 设置到 MDC + TraceIdUtil.setTraceId(traceId); + MDC.put(TraceIdUtil.SPAN_ID_MDC_KEY, TraceIdUtil.generateSpanId()); + + try { + chain.doFilter(request, response); + } finally { + TraceIdUtil.clear(); + } + } +} +``` + +#### 3.3.4 日志配置 + +```xml + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{spanId}] %-5level %logger{36} - %msg%n + + + + + + logs/application.log + + logs/application.%d{yyyy-MM-dd}.%i.log + 30 + 100MB + + + true + traceId + spanId + tenantId + + + + + + + + +``` + +### 3.4 日志格式规范 + +#### 3.4.1 标准日志格式 + +``` +[时间] [线程] [TraceId] [SpanId] [租户ID] [级别] [类名] - [消息] + +示例: +2026-02-13 14:30:25.123 [http-nio-8100-exec-1] [abc123def456] [span789] [tenant_1] INFO c.f.s.c.UserController - 用户登录成功: admin +``` + +#### 3.4.2 JSON 日志格式 + +```json +{ + "@timestamp": "2026-02-13T14:30:25.123+08:00", + "level": "INFO", + "logger_name": "com.fundplatform.sys.controller.UserController", + "message": "用户登录成功: admin", + "thread_name": "http-nio-8100-exec-1", + "traceId": "abc123def456", + "spanId": "span789", + "tenantId": "tenant_1", + "service": "fund-sys", + "host": "192.168.1.100" +} +``` + +### 3.5 日志收集与分析 + +#### 3.5.1 ELK Stack 架构 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 应用服务 │────>│ Filebeat │────>│ Logstash │────>│ Elasticsearch│ +│ (日志文件) │ │ (日志收集) │ │ (日志处理) │ │ (日志存储) │ +└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ + │ + ▼ + ┌─────────────┐ + │ Kibana │ + │ (日志查询) │ + └─────────────┘ +``` + +#### 3.5.2 Kibana 查询示例 + +``` +# 根据 TraceId 查询全链路日志 +traceId: "abc123def456" + +# 根据租户ID查询日志 +tenantId: "tenant_1" + +# 查询特定服务的错误日志 +service: "fund-sys" AND level: "ERROR" + +# 查询特定用户的操作日志 +message: "admin" AND tenantId: "tenant_1" +``` + +### 3.6 调用链追踪增强 + +#### 3.6.1 与 SkyWalking 集成 + +```java +/** + * SkyWalking 插件配置 + */ +@Component +public class SkyWalkingConfig { + + /** + * 自定义 SkyWalking 标签 + */ + @TraceContext + public void addCustomTags() { + // 添加租户ID标签 + String tenantId = TenantContextHolder.getTenantId(); + if (tenantId != null) { + Tags.ofKey("tenant.id").set(tenantId); + } + + // 添加用户ID标签 + String userId = SecurityUtils.getCurrentUserId(); + if (userId != null) { + Tags.ofKey("user.id").set(userId); + } + } +} +``` + +#### 3.6.2 性能指标采集 + +```java +/** + * 接口性能监控 + */ +@Aspect +@Component +public class PerformanceAspect { + + private static final Logger perfLog = LoggerFactory.getLogger("PERFORMANCE"); + + @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)") + public Object around(ProceedingJoinPoint point) throws Throwable { + long start = System.currentTimeMillis(); + String method = point.getSignature().toShortString(); + String traceId = TraceIdUtil.getCurrentTraceId(); + + try { + return point.proceed(); + } finally { + long cost = System.currentTimeMillis() - start; + perfLog.info("[Performance] Method: {}, Cost: {}ms, TraceId: {}", + method, cost, traceId); + } + } +} +``` + +--- + +## 四、系统整体架构 ### 2.1 架构全景图 @@ -504,6 +1280,7 @@ fund-sys/ | 版本 | 修订日期 | 修订内容 | 修订人 | |------|----------|----------|--------| | v1.0 | 2026-02-13 | 初始版本 | zhangjf | +| v1.1 | 2026-02-13 | 补充多租户架构(一库多租户/一库一租户)和 Head 日志追踪设计 | zhangjf | ---