1288 lines
61 KiB
Markdown
1288 lines
61 KiB
Markdown
# 资金服务平台 (FundPlatform) - 架构设计文档
|
||
|
||
> **文档版本**: v1.0
|
||
> **创建日期**: 2026-02-13
|
||
> **项目名称**: 资金服务平台
|
||
> **项目代号**: fundplatform
|
||
|
||
---
|
||
|
||
## 一、架构设计概述
|
||
|
||
### 1.1 设计目标
|
||
|
||
| 目标 | 描述 |
|
||
|------|------|
|
||
| **高可用性** | 系统可用性≥99.5%,支持故障自动恢复 |
|
||
| **高性能** | 支持≥100人并发,接口响应<3秒 |
|
||
| **可扩展性** | 微服务架构,支持水平扩展 |
|
||
| **安全性** | 数据加密传输,完善的权限控制 |
|
||
| **可维护性** | 模块化设计,代码结构清晰 |
|
||
| **多租户** | 支持一库多租户和一库一租户两种模式 |
|
||
| **可观测性** | 全链路日志跟踪,支持 Head 日志追踪 |
|
||
|
||
### 1.2 架构风格
|
||
|
||
采用 **微服务架构** + **前后端分离** + **多租户架构** 模式:
|
||
- 后端:Spring Cloud Alibaba 微服务框架
|
||
- 前端:Vue 3 + UniApp 多端应用
|
||
- 数据层: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<Long> 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<String> 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<Void> 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
|
||
<!-- logback-spring.xml -->
|
||
<configuration>
|
||
<!-- 控制台输出 -->
|
||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||
<encoder>
|
||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{spanId}] %-5level %logger{36} - %msg%n</pattern>
|
||
</encoder>
|
||
</appender>
|
||
|
||
<!-- 文件输出(JSON格式,便于ELK收集) -->
|
||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||
<file>logs/application.log</file>
|
||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||
<maxHistory>30</maxHistory>
|
||
<maxFileSize>100MB</maxFileSize>
|
||
</rollingPolicy>
|
||
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
||
<includeContext>true</includeContext>
|
||
<includeMdcKeyName>traceId</includeMdcKeyName>
|
||
<includeMdcKeyName>spanId</includeMdcKeyName>
|
||
<includeMdcKeyName>tenantId</includeMdcKeyName>
|
||
</encoder>
|
||
</appender>
|
||
|
||
<root level="INFO">
|
||
<appender-ref ref="CONSOLE" />
|
||
<appender-ref ref="FILE" />
|
||
</root>
|
||
</configuration>
|
||
```
|
||
|
||
### 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 架构全景图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ 接入层 (Access Layer) │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ Web端 │ │ 移动端 │ │ 小程序 │ │ H5页面 │ │
|
||
│ │ (Vue3) │ │ (UniApp) │ │ (UniApp) │ │ (UniApp) │ │
|
||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||
└─────────┼────────────────┼────────────────┼────────────────┼───────────────┘
|
||
│ │ │ │
|
||
└────────────────┴────────────────┴────────────────┘
|
||
│
|
||
┌────────────────────────────────────┼────────────────────────────────────────┐
|
||
│ 网关层 (Gateway Layer) │
|
||
│ ┌─────────────────────────────────┴─────────────────────────────────────┐ │
|
||
│ │ Nginx 负载均衡 │ │
|
||
│ │ SSL终止 / 静态资源 / 反向代理 │ │
|
||
│ └─────────────────────────────────┬─────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌─────────────────────────────────┴─────────────────────────────────────┐ │
|
||
│ │ Spring Cloud Gateway │ │
|
||
│ │ 路由转发 / 限流熔断 / 鉴权认证 / 日志记录 │ │
|
||
│ └─────────────────────────────────┬─────────────────────────────────────┘ │
|
||
└────────────────────────────────────┼────────────────────────────────────────┘
|
||
│
|
||
┌────────────────────────────────────┼────────────────────────────────────────┐
|
||
│ 服务层 (Service Layer) │
|
||
│ │ │
|
||
│ ┌─────────────────────────────────┴─────────────────────────────────────┐ │
|
||
│ │ Nacos 服务注册与配置中心 │ │
|
||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ 系统服务 │ │ 客户中心 │ │ 项目中心 │ │ 需求中心 │ │
|
||
│ │ fund-sys │ │ fund-cust │ │ fund-proj │ │ fund-req │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ │ • 用户管理 │ │ • 客户管理 │ │ • 项目管理 │ │ • 需求工单 │ │
|
||
│ │ • 权限管理 │ │ • 联系人管理 │ │ • 成员管理 │ │ • 应收款管理 │ │
|
||
│ │ • 部门管理 │ │ │ │ │ │ │ │
|
||
│ │ • 日志管理 │ │ │ │ │ │ │ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||
│ │ │ │ │ │
|
||
│ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ │
|
||
│ │ 支出中心 │ │ 收款中心 │ │ 报表中心 │ │ 文件中心 │ │
|
||
│ │ fund-exp │ │ fund-receipt │ │ fund-report │ │ fund-file │ │
|
||
│ │ │ │ │ │ │ │ │ │
|
||
│ │ • 支出类型 │ │ • 收款记录 │ │ • 收支统计 │ │ • 文件上传 │ │
|
||
│ │ • 支出申请 │ │ • 收款凭证 │ │ • 趋势分析 │ │ • 文件存储 │ │
|
||
│ │ • 付款管理 │ │ • 账期管理 │ │ • 数据导出 │ │ • 文件下载 │ │
|
||
│ │ • 统计分析 │ │ │ │ │ │ │ │
|
||
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌────────────────────────────────────┼────────────────────────────────────────┐
|
||
│ 数据层 (Data Layer) │
|
||
│ │ │
|
||
│ ┌─────────────────────────────────┴─────────────────────────────────────┐ │
|
||
│ │ Redis 缓存集群 │ │
|
||
│ │ 会话缓存 / 热点数据 / 分布式锁 / 限流计数器 │ │
|
||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ MySQL 8.0 主从集群 │ │
|
||
│ │ 用户库 / 业务库 / 日志库 / 报表库 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 腾讯COS 对象存储 │ │
|
||
│ │ 文件存储 / 图片存储 / 凭证附件 │ │
|
||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 服务划分
|
||
|
||
| 服务名 | 服务编码 | 职责 | 端口范围 |
|
||
|--------|----------|------|----------|
|
||
| 网关服务 | fund-gateway | 路由、鉴权、限流 | 8080 |
|
||
| 系统服务 | fund-sys | 用户、权限、部门、日志 | 8100-8109 |
|
||
| 客户中心 | fund-cust | 客户、联系人管理 | 8110-8119 |
|
||
| 项目中心 | fund-proj | 项目、成员管理 | 8120-8129 |
|
||
| 需求中心 | fund-req | 需求工单、应收款 | 8130-8139 |
|
||
| 支出中心 | fund-exp | 支出类型、支出管理 | 8140-8149 |
|
||
| 收款中心 | fund-receipt | 收款记录、账期管理 | 8150-8159 |
|
||
| 报表中心 | fund-report | 统计分析、数据导出 | 8160-8169 |
|
||
| 文件中心 | fund-file | 文件上传、存储管理 | 8170-8179 |
|
||
|
||
---
|
||
|
||
## 三、技术架构
|
||
|
||
### 3.1 技术栈选型
|
||
|
||
#### 3.1.1 后端技术栈
|
||
|
||
| 层级 | 技术选型 | 版本 | 用途 |
|
||
|------|----------|------|------|
|
||
| **基础框架** | Spring Boot | 3.2.x | 应用基础框架 |
|
||
| **微服务框架** | Spring Cloud Alibaba | 2022.x | 微服务治理 |
|
||
| **服务注册** | Nacos | 3.0.0 | 服务注册与配置中心 |
|
||
| **服务网关** | Spring Cloud Gateway | 4.x | API网关 |
|
||
| **负载均衡** | Spring Cloud LoadBalancer | 4.x | 客户端负载均衡 |
|
||
| **服务调用** | OpenFeign | 4.x | 声明式HTTP客户端 |
|
||
| **服务容错** | Sentinel | 1.8.x | 限流、熔断、降级 |
|
||
| **ORM框架** | MyBatis-Plus | 3.5.x | 数据访问层 |
|
||
| **数据库连接池** | Druid | 1.2.x | 连接池、监控 |
|
||
| **缓存框架** | Spring Data Redis | 3.x | Redis操作 |
|
||
| **安全框架** | Spring Security | 6.x | 认证授权 |
|
||
| **JWT令牌** | jjwt | 0.12.x | Token生成与验证 |
|
||
| **API文档** | Knife4j | 4.x | Swagger增强 |
|
||
| **对象存储** | 腾讯云COS SDK | 5.x | 文件存储 |
|
||
| **定时任务** | XXL-JOB | 2.4.x | 分布式任务调度 |
|
||
| **日志框架** | Logback + SLF4J | - | 日志记录 |
|
||
| **工具类库** | Hutool | 5.8.x | 常用工具 |
|
||
|
||
#### 3.1.2 前端技术栈
|
||
|
||
| 端 | 技术选型 | 版本 | 用途 |
|
||
|----|----------|------|------|
|
||
| **管理后台** | Vue | 3.4.x | 前端框架 |
|
||
| | TypeScript | 5.x | 类型安全 |
|
||
| | Element Plus | 2.5.x | UI组件库 |
|
||
| | Pinia | 2.x | 状态管理 |
|
||
| | Vue Router | 4.x | 路由管理 |
|
||
| | Axios | 1.x | HTTP客户端 |
|
||
| | ECharts | 5.x | 图表库 |
|
||
| | Vite | 5.x | 构建工具 |
|
||
| **移动端** | UniApp | 3.x | 跨端框架 |
|
||
| | Vue | 3.x | 前端框架 |
|
||
| | uView UI | 2.x | 移动端UI库 |
|
||
|
||
### 3.2 架构分层
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 表现层 (Presentation) │
|
||
│ Controller / DTO / VO / 参数校验 / 异常处理 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 业务层 (Business) │
|
||
│ Service / 业务逻辑 / 事务管理 / 权限校验 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 数据访问层 (Data Access) │
|
||
│ Mapper / DAO / MyBatis-Plus / 数据库操作 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 基础设施层 (Infrastructure) │
|
||
│ Redis / MySQL / COS / MQ / 缓存 / 消息队列 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 四、数据架构
|
||
|
||
### 4.1 数据库设计
|
||
|
||
#### 4.1.1 数据库划分
|
||
|
||
| 数据库 | 用途 | 主要表 |
|
||
|--------|------|--------|
|
||
| fund_sys | 系统数据 | sys_user, sys_dept, sys_role, sys_menu, sys_log |
|
||
| fund_biz | 业务数据 | customer, project, requirement, expense, receivable, receipt |
|
||
| fund_file | 文件数据 | file_record, file_chunk |
|
||
|
||
#### 4.1.2 分库分表策略
|
||
|
||
| 表名 | 策略 | 分片键 | 说明 |
|
||
|------|------|--------|------|
|
||
| sys_operation_log | 按时间分表 | created_time | 每月一张表 |
|
||
| expense | 按时间分表 | expense_date | 每季度一张表 |
|
||
| receipt | 按时间分表 | receipt_date | 每季度一张表 |
|
||
|
||
### 4.2 缓存设计
|
||
|
||
#### 4.2.1 缓存策略
|
||
|
||
| 缓存类型 | Key前缀 | 过期时间 | 说明 |
|
||
|----------|---------|----------|------|
|
||
| 用户会话 | `session:` | 30分钟 | JWT Token 黑名单 |
|
||
| 用户信息 | `user:info:` | 1小时 | 用户基本信息缓存 |
|
||
| 字典数据 | `dict:` | 24小时 | 系统字典数据 |
|
||
| 热点数据 | `hot:` | 10分钟 | 频繁访问数据 |
|
||
| 限流计数 | `limit:` | 1分钟 | API限流计数 |
|
||
| 分布式锁 | `lock:` | 30秒 | 并发控制 |
|
||
|
||
#### 4.2.2 缓存更新策略
|
||
|
||
```
|
||
┌─────────────┐ 查询 ┌─────────────┐
|
||
│ 业务请求 │ ───────────> │ 查询缓存 │
|
||
└─────────────┘ └──────┬──────┘
|
||
│
|
||
┌───────────────┼───────────────┐
|
||
│ 命中 │ 未命中 │
|
||
▼ ▼ │
|
||
┌─────────────┐ ┌─────────────┐ │
|
||
│ 返回缓存 │ │ 查询数据库 │ │
|
||
│ 数据 │ │ │ │
|
||
└─────────────┘ └──────┬──────┘ │
|
||
│ │
|
||
▼ │
|
||
┌─────────────┐ │
|
||
│ 写入缓存 │<─────┘
|
||
│ 设置过期 │
|
||
└─────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 五、安全架构
|
||
|
||
### 5.1 认证授权
|
||
|
||
#### 5.1.1 JWT认证流程
|
||
|
||
```
|
||
┌─────────┐ ┌─────────┐
|
||
│ 客户端 │ │ 服务端 │
|
||
└────┬────┘ └────┬────┘
|
||
│ │
|
||
│ 1. 登录请求 (username/password) │
|
||
│ ──────────────────────────────────────> │
|
||
│ │
|
||
│ ┌─────────┴─────────┐
|
||
│ │ 验证用户名密码 │
|
||
│ │ 生成JWT Token │
|
||
│ └─────────┬─────────┘
|
||
│ │
|
||
│ 2. 返回 Token │
|
||
│ <────────────────────────────────────── │
|
||
│ │
|
||
│ 3. 业务请求 (Header: Authorization) │
|
||
│ ──────────────────────────────────────> │
|
||
│ ┌─────────┴─────────┐
|
||
│ │ 验证Token有效性 │
|
||
│ │ 解析用户信息 │
|
||
│ │ 权限校验 │
|
||
│ └─────────┬─────────┘
|
||
│ │
|
||
│ 4. 返回业务数据 │
|
||
│ <────────────────────────────────────── │
|
||
```
|
||
|
||
#### 5.1.2 权限模型
|
||
|
||
采用 **RBAC** (Role-Based Access Control) 模型:
|
||
|
||
```
|
||
用户 (User) ──N:M──> 角色 (Role) ──N:M──> 权限 (Permission)
|
||
│
|
||
└─N:M──> 菜单/按钮 (Menu)
|
||
```
|
||
|
||
### 5.2 数据安全
|
||
|
||
| 安全措施 | 实现方式 |
|
||
|----------|----------|
|
||
| 传输加密 | HTTPS/TLS 1.3 |
|
||
| 密码加密 | BCrypt (强度10) |
|
||
| 敏感数据 | AES-256 加密存储 |
|
||
| SQL注入 | MyBatis预编译语句 |
|
||
| XSS攻击 | 前端转义 + 后端过滤 |
|
||
| CSRF攻击 | Token验证 + SameSite Cookie |
|
||
|
||
---
|
||
|
||
## 六、部署架构
|
||
|
||
### 6.1 生产环境部署
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 负载均衡层 │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ Nginx (主) + Nginx (备) │ │
|
||
│ │ Keepalived VIP: 192.168.1.10 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 应用服务层 │
|
||
│ │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ Gateway-1 │ │ Gateway-2 │ │ Gateway-3 │ │
|
||
│ │ :8080 │ │ :8080 │ │ :8080 │ │
|
||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||
│ │ │ │ │
|
||
│ ┌──────┴────────────────┴────────────────┴──────┐ │
|
||
│ │ Nacos 集群 (3节点) │ │
|
||
│ │ 8848 / 9848 / 7848 │ │
|
||
│ └───────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||
│ │fund-sys │ │fund-cust │ │fund-proj │ │fund-req │ │
|
||
│ │ x 2 │ │ x 2 │ │ x 2 │ │ x 2 │ │
|
||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||
│ │fund-exp │ │fund-rec │ │fund-rpt │ │fund-file │ │
|
||
│ │ x 2 │ │ x 2 │ │ x 2 │ │ x 2 │ │
|
||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 数据存储层 │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ Redis 集群 (6节点,3主3从) │ │
|
||
│ │ 6379 / 6380 / 6381 │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ MySQL 主从集群 │ │
|
||
│ │ Master: 3306 ──> Slave: 3306 (x2) │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 6.2 容器化部署
|
||
|
||
#### 6.2.1 Docker Compose 配置
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
services:
|
||
# Nacos
|
||
nacos:
|
||
image: nacos/nacos-server:v3.0.0
|
||
ports:
|
||
- "8848:8848"
|
||
- "9848:9848"
|
||
environment:
|
||
- MODE=standalone
|
||
|
||
# Redis
|
||
redis:
|
||
image: redis:8.0.2
|
||
ports:
|
||
- "6379:6379"
|
||
command: redis-server --requirepass zjf@123456
|
||
|
||
# MySQL
|
||
mysql:
|
||
image: mysql:8.0
|
||
ports:
|
||
- "3306:3306"
|
||
environment:
|
||
- MYSQL_ROOT_PASSWORD=zjf@123456
|
||
volumes:
|
||
- ./sql:/docker-entrypoint-initdb.d
|
||
|
||
# Gateway
|
||
gateway:
|
||
image: fundplatform/fund-gateway:1.0.0
|
||
ports:
|
||
- "8080:8080"
|
||
environment:
|
||
- NACOS_SERVER=nacos:8848
|
||
|
||
# Services
|
||
fund-sys:
|
||
image: fundplatform/fund-sys:1.0.0
|
||
deploy:
|
||
replicas: 2
|
||
environment:
|
||
- NACOS_SERVER=nacos:8848
|
||
- DB_HOST=mysql
|
||
- REDIS_HOST=redis
|
||
```
|
||
|
||
---
|
||
|
||
## 七、监控与运维
|
||
|
||
### 7.1 监控体系
|
||
|
||
| 监控类型 | 工具 | 监控内容 |
|
||
|----------|------|----------|
|
||
| **应用监控** | Prometheus + Grafana | JVM、接口响应、错误率 |
|
||
| **链路追踪** | SkyWalking | 调用链、性能瓶颈 |
|
||
| **日志收集** | ELK Stack | 业务日志、错误日志 |
|
||
| **告警通知** | AlertManager | 钉钉/邮件/短信告警 |
|
||
|
||
### 7.2 运维脚本
|
||
|
||
```bash
|
||
# 服务启动脚本
|
||
#!/bin/bash
|
||
SERVICES=("gateway" "sys" "cust" "proj" "req" "exp" "receipt" "report" "file")
|
||
for service in "${SERVICES[@]}"; do
|
||
docker-compose up -d fund-${service}
|
||
sleep 5
|
||
done
|
||
|
||
# 健康检查脚本
|
||
#!/bin/bash
|
||
curl -f http://localhost:8080/actuator/health || exit 1
|
||
```
|
||
|
||
---
|
||
|
||
## 八、开发规范
|
||
|
||
### 8.1 代码结构
|
||
|
||
```
|
||
fund-sys/
|
||
├── src/
|
||
│ ├── main/
|
||
│ │ ├── java/
|
||
│ │ │ └── com/fundplatform/sys/
|
||
│ │ │ ├── SysApplication.java
|
||
│ │ │ ├── config/ # 配置类
|
||
│ │ │ ├── controller/ # 控制器层
|
||
│ │ │ ├── service/ # 业务层
|
||
│ │ │ │ └── impl/
|
||
│ │ │ ├── mapper/ # 数据访问层
|
||
│ │ │ ├── entity/ # 实体类
|
||
│ │ │ ├── dto/ # 数据传输对象
|
||
│ │ │ ├── vo/ # 视图对象
|
||
│ │ │ ├── enums/ # 枚举类
|
||
│ │ │ ├── utils/ # 工具类
|
||
│ │ │ └── handler/ # 处理器
|
||
│ │ └── resources/
|
||
│ │ ├── application.yml
|
||
│ │ ├── application-dev.yml
|
||
│ │ ├── application-prod.yml
|
||
│ │ └── mapper/ # XML映射文件
|
||
│ └── test/
|
||
├── Dockerfile
|
||
├── pom.xml
|
||
└── README.md
|
||
```
|
||
|
||
### 8.2 API 设计规范
|
||
|
||
| 规范项 | 要求 |
|
||
|--------|------|
|
||
| **URL** | /api/{service}/{version}/{resource}/{action} |
|
||
| **HTTP方法** | GET(查)、POST(增)、PUT(改)、DELETE(删) |
|
||
| **响应格式** | { "code": 200, "message": "", "data": {} } |
|
||
| **分页** | pageNum, pageSize, total, list |
|
||
| **版本** | v1, v2 在URL中体现 |
|
||
|
||
---
|
||
|
||
## 九、附录
|
||
|
||
### 9.1 端口分配表
|
||
|
||
| 服务 | 端口 | 说明 |
|
||
|------|------|------|
|
||
| Nginx | 80/443 | 负载均衡 |
|
||
| Gateway | 8080 | API网关 |
|
||
| Nacos | 8848/9848 | 注册配置中心 |
|
||
| Redis | 6379 | 缓存服务 |
|
||
| MySQL | 3306 | 数据库 |
|
||
| XXL-JOB | 8081 | 任务调度中心 |
|
||
|
||
### 9.2 文档修订记录
|
||
|
||
| 版本 | 修订日期 | 修订内容 | 修订人 |
|
||
|------|----------|----------|--------|
|
||
| v1.0 | 2026-02-13 | 初始版本 | zhangjf |
|
||
| v1.1 | 2026-02-13 | 补充多租户架构(一库多租户/一库一租户)和 Head 日志追踪设计 | zhangjf |
|
||
|
||
---
|
||
|
||
**文档结束**
|