## 新增功能 ### 1. 多租户核心组件 - TenantRoutingProperties: 租户路由配置属性 - TenantAwareLoadBalancer: 租户感知负载均衡器 - TenantLineHandlerImpl: MyBatis Plus 租户插件 - TenantIgnoreHelper: 忽略租户过滤工具类 - NacosMetadataConfig: Nacos 元数据自动注册 ### 2. Gateway 租户过滤器 - TenantGatewayFilter: 从 JWT 提取租户信息写入请求头 - 透传 X-Tenant-Id、X-Tenant-Group、X-User-Id、X-Username ### 3. 支持的部署模式 - 一库多租户(SaaS 模式): 通过 tenant_id 字段隔离 - 一库一租户(私有化): 独立服务实例和数据库 - 混合模式: VIP 租户专属实例 + 普通租户共享实例 ### 4. Nacos 3.0 适配 - 所有业务模块添加 username/password 认证配置 - 服务实例自动注册租户标签 ## 问题修复 - #8: FeignClient 硬编码 URL 导致 Nacos 服务发现失效 - #9: Nacos 3.0 客户端缺少 username/password 认证配置 - fund-exp expenseType 字段类型从 Integer 改为 Long ## 测试 - TenantAwareLoadBalancerTest: 负载均衡器单元测试 - 混合模式集成测试脚本
682 lines
23 KiB
Markdown
682 lines
23 KiB
Markdown
# 资金服务平台 - 开发问题清单
|
||
|
||
> **文档版本**: v1.0
|
||
> **创建日期**: 2026-02-13
|
||
> **记录时段**: 2026-02-12 09:00 ~ 2026-02-13 当前时间
|
||
|
||
---
|
||
|
||
## 问题清单概览
|
||
|
||
| 序号 | 问题分类 | 问题描述 | 严重程度 | 状态 |
|
||
|------|----------|----------|----------|------|
|
||
| 1 | 前后端接口 | /auth/info接口返回400 Bad Request | 高 | 已解决 |
|
||
| 2 | 前端路由 | 个人中心和系统设置菜单点击无反应 | 中 | 已解决 |
|
||
| 3 | 前端路由 | 刷新页面显示404 | 高 | 已解决 |
|
||
| 4 | 后端序列化 | AOP日志LocalDateTime序列化失败 | 中 | 已解决 |
|
||
| 5 | 后端接口 | 新增支出expenseType字段雪花ID超出Integer范围 | 高 | 已解决 |
|
||
| 6 | OpenFeign配置 | FeignChainInterceptor未注册为Spring Bean | 高 | 已解决 |
|
||
| 7 | 全链路追踪 | TraceContextHolder未在HTTP入口初始化导致链路断裂 | 高 | 已解决 |
|
||
| 8 | OpenFeign配置 | FeignClient硬编码URL导致Nacos服务发现失效 | 高 | 已解决 |
|
||
| 9 | Nacos 配置 | Nacos 3.0 客户端缺少 username/password 认证配置 | 高 | 已解决 |
|
||
|
||
---
|
||
|
||
## 问题详情与解决方案
|
||
|
||
### 问题1:/auth/info接口返回400 Bad Request
|
||
|
||
#### 问题现象
|
||
```
|
||
Request URL: http://localhost:3002/sys/api/v1/auth/info
|
||
Request Method: GET
|
||
Status Code: 400 Bad Request
|
||
```
|
||
|
||
用户登录成功后,调用 `/auth/info` 获取用户信息时返回400错误。
|
||
|
||
#### 问题原因
|
||
后端接口需要 `X-User-Id` 请求头来识别当前用户,但前端未在请求中传递该header。
|
||
|
||
#### 解决方案
|
||
|
||
**1. 修改前端请求拦截器** (`fund-admin/src/api/request.ts`)
|
||
|
||
```typescript
|
||
// 在请求头中添加 X-User-Id
|
||
request.headers['X-User-Id'] = localStorage.getItem('userId') || ''
|
||
```
|
||
|
||
**2. 修改用户登录逻辑** (`fund-admin/src/stores/user.ts`)
|
||
|
||
```typescript
|
||
// 登录成功后保存 userId 和 tenantId 到 localStorage
|
||
localStorage.setItem('userId', String(res.data.userId))
|
||
localStorage.setItem('tenantId', String(res.data.tenantId))
|
||
```
|
||
|
||
#### 经验总结
|
||
- 前后端接口对接时,需确认所有必需的请求头
|
||
- 用户信息(userId、tenantId)应在登录成功后立即持久化存储
|
||
|
||
---
|
||
|
||
### 问题2:个人中心和系统设置菜单点击无反应
|
||
|
||
#### 问题现象
|
||
点击顶部下拉菜单中的"个人中心"和"系统设置"选项后,页面无任何跳转反应。
|
||
|
||
#### 问题原因
|
||
`MainLayout.vue` 中的 `handleCommand` 函数对 `profile` 和 `settings` 两个命令的处理为空实现。
|
||
|
||
```typescript
|
||
// 问题代码
|
||
const handleCommand = async (command: string) => {
|
||
switch (command) {
|
||
case 'profile':
|
||
// 空实现,无任何代码
|
||
break
|
||
case 'settings':
|
||
// 空实现,无任何代码
|
||
break
|
||
case 'logout':
|
||
await userStore.logout()
|
||
router.push('/login')
|
||
break
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 解决方案
|
||
|
||
**修改 `fund-admin/src/layouts/MainLayout.vue`**
|
||
|
||
```typescript
|
||
const handleCommand = async (command: string) => {
|
||
switch (command) {
|
||
case 'profile':
|
||
router.push('/profile')
|
||
break
|
||
case 'settings':
|
||
router.push('/system/config')
|
||
break
|
||
case 'logout':
|
||
await userStore.logout()
|
||
router.push('/login')
|
||
break
|
||
}
|
||
}
|
||
```
|
||
|
||
同时在侧边栏菜单中添加"参数设置"入口。
|
||
|
||
#### 经验总结
|
||
- 开发新功能时,需确保所有UI交互都有对应的业务实现
|
||
- 代码提交前应进行基础功能测试
|
||
|
||
---
|
||
|
||
### 问题3:刷新页面显示404
|
||
|
||
#### 问题现象
|
||
登录后进入业务页面,刷新浏览器后页面显示404:
|
||
|
||
```
|
||
Whitelabel Error Page
|
||
This application has no explicit mapping for /error, so you are seeing this as a fallback.
|
||
type=Not Found, status=404
|
||
```
|
||
|
||
#### 问题原因
|
||
Vite 开发服务器的代理配置中,`/sys` 路径模糊匹配了前端路由 `/system`,导致前端路由请求被错误代理到后端服务。
|
||
|
||
```typescript
|
||
// 问题配置
|
||
proxy: {
|
||
'/sys': { // 会匹配 /sys 开头的所有路径,包括 /system
|
||
target: 'http://localhost:8100',
|
||
changeOrigin: true,
|
||
rewrite: (path) => path.replace(/^\/sys/, '')
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 解决方案
|
||
|
||
**修改 `fund-admin/vite.config.ts`**
|
||
|
||
```typescript
|
||
proxy: {
|
||
'/sys/': { // 添加斜杠,精确匹配 /sys/ 开头的路径
|
||
target: 'http://localhost:8100',
|
||
changeOrigin: true,
|
||
rewrite: (path) => path.replace(/^\/sys/, '')
|
||
}
|
||
}
|
||
```
|
||
|
||
同时改进路由守卫逻辑,更好地处理页面刷新场景。
|
||
|
||
#### 经验总结
|
||
- 代理路径配置应使用精确匹配,避免模糊匹配导致的路由冲突
|
||
- 前端路由命名应与后端API路径有明显区分
|
||
|
||
---
|
||
|
||
### 问题4:AOP日志LocalDateTime序列化失败
|
||
|
||
#### 问题现象
|
||
为 fund-sys 添加 AOP 日志功能后,日志输出时报错:
|
||
|
||
```
|
||
Java 8 date/time type 'java.time.LocalDateTime' not supported by default:
|
||
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
|
||
to enable handling
|
||
```
|
||
|
||
#### 问题原因
|
||
Jackson 默认不支持 Java 8 的日期时间类型(LocalDateTime、LocalDate等)的序列化,需要额外配置。
|
||
|
||
#### 解决方案
|
||
|
||
**1. 添加 Maven 依赖** (`fund-sys/pom.xml`)
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
**2. 配置 ObjectMapper** (`ApiLogAspect.java`)
|
||
|
||
```java
|
||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||
|
||
static {
|
||
// 注册JavaTimeModule以支持Java 8日期时间类型
|
||
objectMapper.registerModule(new JavaTimeModule());
|
||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||
}
|
||
```
|
||
|
||
**3. 序列化异常兜底处理**
|
||
|
||
```java
|
||
try {
|
||
logInfo.put("responseBody", objectMapper.writeValueAsString(result));
|
||
} catch (Exception e) {
|
||
// 序列化失败时使用toString()兜底
|
||
logInfo.put("responseBody", result.toString());
|
||
}
|
||
```
|
||
|
||
#### 经验总结
|
||
- 使用 Jackson 序列化 Java 8 日期时间类型时,必须注册 `JavaTimeModule`
|
||
- 对于复杂的序列化场景,应添加异常兜底处理,避免日志功能影响业务
|
||
- Spring Boot 项目使用 AOP 需引入 `spring-boot-starter-aop` 依赖
|
||
|
||
---
|
||
|
||
### 问题5:新增支出expenseType字段雪花ID超出Integer范围
|
||
|
||
#### 问题现象
|
||
```
|
||
POST http://localhost:8080/exp/api/v1/exp/expense
|
||
Status Code: 400 Bad Request
|
||
```
|
||
|
||
移动端新增支出时,请求返回 400 错误,无任何响应内容。
|
||
|
||
#### 问题原因
|
||
后端 `FundExpenseDTO.expenseType` 字段类型为 `Integer`,最大值为 2147483647。而前端传递的是雪花ID(如 `2023686600025919489`),远超 Integer 范围,导致 JSON 解析失败。
|
||
|
||
#### 解决方案
|
||
|
||
**修改 `fund-exp/src/main/java/com/fundplatform/exp/dto/FundExpenseDTO.java`**
|
||
|
||
```java
|
||
// 修改前
|
||
@NotNull(message = "支出类型不能为空")
|
||
private Integer expenseType;
|
||
|
||
public Integer getExpenseType() { return expenseType; }
|
||
public void setExpenseType(Integer expenseType) { this.expenseType = expenseType; }
|
||
|
||
// 修改后
|
||
@NotNull(message = "支出类型不能为空")
|
||
private Long expenseType;
|
||
|
||
public Long getExpenseType() { return expenseType; }
|
||
public void setExpenseType(Long expenseType) { this.expenseType = expenseType; }
|
||
```
|
||
|
||
#### 经验总结
|
||
- 使用雪花ID作为主键的实体,其外键字段类型必须使用 `Long` 而非 `Integer`
|
||
- Java Integer 范围:-2147483648 ~ 2147483647(约21亿)
|
||
- 雪花ID范围:通常为19位数字,远超 Integer 范围
|
||
|
||
---
|
||
|
||
### 问题6:FeignChainInterceptor未注册为Spring Bean
|
||
|
||
#### 问题现象
|
||
OpenFeign 调用下游服务时,租户ID、用户信息、TraceId 等上下文信息未透传,导致下游服务无法获取当前请求的租户和用户信息。
|
||
|
||
#### 问题原因
|
||
`FeignChainInterceptor` 虽然实现了 `RequestInterceptor` 接口,但缺少 `@Component` 注解,未被 Spring 注册为 Bean,导致 OpenFeign 调用时拦截器不生效。
|
||
|
||
```java
|
||
// 问题代码
|
||
public class FeignChainInterceptor implements RequestInterceptor {
|
||
// 没有 @Component 注解
|
||
}
|
||
```
|
||
|
||
#### 解决方案
|
||
|
||
**1. 添加 Bean 注解** (`fund-common/src/main/java/com/fundplatform/common/feign/FeignChainInterceptor.java`)
|
||
|
||
```java
|
||
@Component // 添加此注解
|
||
public class FeignChainInterceptor implements RequestInterceptor {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**2. 配置组件扫描** (`fund-report/src/main/java/com/fundplatform/report/ReportApplication.java`)
|
||
|
||
```java
|
||
@SpringBootApplication(scanBasePackages = {"com.fundplatform.report", "com.fundplatform.common"})
|
||
@EnableDiscoveryClient
|
||
@EnableFeignClients
|
||
public class ReportApplication {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### 经验总结
|
||
- Spring 组件(Service、Component、Repository 等)必须添加相应注解才能被容器管理
|
||
- 跨模块的组件需确保使用该组件的模块配置了正确的 `scanBasePackages`
|
||
- Feign 拦截器是实现微服务间上下文透传的关键,必须确保正确注册
|
||
|
||
---
|
||
|
||
### 问题 7:TraceContextHolder 未在 HTTP 入口初始化导致链路断裂
|
||
|
||
#### 问题现象
|
||
Gateway 层已生成 TraceId 并写入请求头 `X-Trace-Id`,但业务服务中调用 `TraceContextHolder.getTraceId()` 返回 null,Feign 调用时会重新生成新的 TraceId,导致全链路追踪断裂。
|
||
|
||
#### 问题原因
|
||
`ContextInterceptor` 只提取了租户 ID 和用户 ID,未从请求头中提取 Gateway 传递的 TraceId 并设置到 `TraceContextHolder`,导致:
|
||
1. 业务逻辑无法获取 Gateway 生成的 TraceId
|
||
2. Feign 调用时通过 `getOrCreateTraceId()` 生成新的 TraceId
|
||
3. 日志系统中出现多个不同的 TraceId,无法串联完整调用链
|
||
|
||
```java
|
||
// 问题代码 - ContextInterceptor.preHandle
|
||
// 缺少 TraceId 的提取逻辑
|
||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||
// 只设置了 TenantId 和 UserId
|
||
TenantContextHolder.setTenantId(tenantId);
|
||
UserContextHolder.setUserId(userId);
|
||
// 缺失:TraceContextHolder.setTraceId(traceId);
|
||
return true;
|
||
}
|
||
```
|
||
|
||
#### 解决方案
|
||
|
||
**修改 `fund-common/src/main/java/com/fundplatform/common/web/ContextInterceptor.java`**
|
||
|
||
1. 添加导入和常量定义
|
||
```java
|
||
import com.fundplatform.common.context.TraceContextHolder;
|
||
|
||
public static final String HEADER_TRACE_ID = "X-Trace-Id";
|
||
```
|
||
|
||
2. 在 preHandle 中提取 TraceId
|
||
```java
|
||
// 提取 TraceId(如不存在,后续会在需要时自动生成)
|
||
String traceId = request.getHeader(HEADER_TRACE_ID);
|
||
if (traceId != null && !traceId.isEmpty()) {
|
||
TraceContextHolder.setTraceId(traceId);
|
||
}
|
||
```
|
||
|
||
3. 在 afterCompletion 中清理 TraceContextHolder
|
||
```java
|
||
@Override
|
||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||
Object handler, Exception ex) {
|
||
TenantContextHolder.clear();
|
||
UserContextHolder.clear();
|
||
TraceContextHolder.clear(); // 新增
|
||
}
|
||
```
|
||
|
||
#### 修复后的完整链路
|
||
|
||
```
|
||
Gateway (GlobalLogFilter)
|
||
↓ 生成 TraceId: abc123 并写入 X-Trace-Id 请求头
|
||
业务服务 A (ContextInterceptor.preHandle)
|
||
↓ 读取 X-Trace-Id: abc123 并设置到 TraceContextHolder
|
||
业务逻辑 A
|
||
↓ 使用 TraceContextHolder.getTraceId() → abc123
|
||
FeignChainInterceptor.apply
|
||
↓ 从 TraceContextHolder 获取 abc123 并透传到下游
|
||
业务服务 B (ContextInterceptor.preHandle)
|
||
↓ 读取 X-Trace-Id: abc123 并设置到 TraceContextHolder
|
||
业务逻辑 B
|
||
↓ 使用 TraceContextHolder.getTraceId() → abc123
|
||
afterCompletion
|
||
↓ 清理所有 ThreadLocal
|
||
```
|
||
|
||
#### 经验总结
|
||
- Gateway 生成的 TraceId 必须在业务服务入口处提取并设置到 ThreadLocal
|
||
- Feign 拦截器应从 ThreadLocal 获取 TraceId 并透传,而不是重新生成
|
||
- 所有 ThreadLocal 使用后必须在 finally 或 afterCompletion 中清理,防止内存泄漏
|
||
- 全链路追踪需要每个环节都正确传递 TraceId,任何一环断裂都会导致追踪失败
|
||
|
||
---
|
||
|
||
### 问题 8:FeignClient 硬编码 URL 导致 Nacos 服务发现失效
|
||
|
||
#### 问题现象
|
||
已配置 Nacos 作为服务注册中心,但各 FeignClient 仍使用 `url = "${feign.xxx.url:http://localhost:xxxx}"` 硬编码地址,导致:
|
||
1. Nacos 服务注册中心形同虚设
|
||
2. Feign 不会通过 Nacos 进行服务发现和负载均衡
|
||
3. 无法实现服务动态扩缩容和高可用
|
||
|
||
```java
|
||
// 问题代码 - ExpenseFeignClient.java
|
||
@FeignClient(name = "fund-exp", url = "${feign.fund-exp.url:http://localhost:8140}")
|
||
public interface ExpenseFeignClient {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### 问题原因
|
||
**架构设计不一致**:
|
||
- ✅ 已在 `application.yml` 中配置 Nacos 服务发现
|
||
- ❌ 但 FeignClient 仍指定了固定的 URL 地址
|
||
|
||
**可能的历史原因**:
|
||
1. **开发环境过渡方案**:本地开发时 Nacos 未部署,临时使用直连方式
|
||
2. **迁移遗留问题**:从单体架构迁移到微服务时的过渡配置
|
||
3. **理解偏差**:误以为 FeignClient 必须指定 url
|
||
|
||
#### 解决方案
|
||
|
||
**修复前(❌)**:
|
||
```java
|
||
@FeignClient(name = "fund-exp", url = "${feign.fund-exp.url:http://localhost:8140}")
|
||
// ↓ 直接访问固定地址,Nacos 不生效
|
||
```
|
||
|
||
**修复后(✅)**:
|
||
```java
|
||
@FeignClient(name = "fund-exp")
|
||
// ↓ 通过 Nacos 服务发现 + Ribbon 负载均衡
|
||
```
|
||
|
||
**涉及的文件修改**:
|
||
1. `ExpenseFeignClient.java` - 移除 `url` 属性
|
||
2. `ReceivableFeignClient.java` - 移除 `url` 属性
|
||
3. `ProjectFeignClient.java` - 移除 `url` 属性
|
||
4. `SysServiceClient.java` - 移除 `url` 属性
|
||
|
||
#### 修复后的架构优势
|
||
|
||
**工作流程:**
|
||
```
|
||
Fund-Report (调用方)
|
||
↓ FeignClient: fund-exp
|
||
↓ Spring Cloud OpenFeign + Nacos Discovery
|
||
↓ 从 Nacos 查询 fund-exp 服务的所有实例
|
||
↓ Ribbon/LoadBalancer 进行负载均衡选择实例
|
||
↓ 发起 HTTP 请求到选中的实例
|
||
```
|
||
|
||
**带来的好处:**
|
||
| 特性 | 修复前 | 修复后 |
|
||
|------|--------|--------|
|
||
| **服务发现** | ❌ 固定地址 | ✅ 自动感知上下线 |
|
||
| **负载均衡** | ❌ 单点 | ✅ 多实例轮询 |
|
||
| **高可用** | ❌ 故障无法切换 | ✅ 自动切换健康实例 |
|
||
| **弹性扩缩容** | ❌ 需修改配置 | ✅ 新增实例无需配置 |
|
||
| **架构一致性** | ❌ Nacos 形同虚设 | ✅ 充分发挥作用 |
|
||
|
||
#### 正确的 FeignClient 配置规范
|
||
|
||
| 场景 | 配置方式 | 示例 |
|
||
|------|---------|------|
|
||
| **有注册中心(推荐)** | 只指定 name | `@FeignClient(name = "fund-exp")` |
|
||
| **无注册中心** | 指定 name + url | `@FeignClient(name = "xxx", url = "http://localhost:8080")` |
|
||
| **需要降级** | 指定 name + fallback | `@FeignClient(name = "fund-exp", fallback = ExpFallback.class)` |
|
||
|
||
#### 必备配置要求
|
||
|
||
**1. application.yml 配置**
|
||
```yaml
|
||
spring:
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848 # Nacos 地址
|
||
namespace: fund-platform # 命名空间
|
||
group: DEFAULT_GROUP # 分组
|
||
```
|
||
|
||
**2. 启动类注解**
|
||
```java
|
||
@SpringBootApplication
|
||
@EnableDiscoveryClient // 启用服务发现
|
||
@EnableFeignClients // 启用 Feign 客户端
|
||
public class ReportApplication {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### 经验总结
|
||
- **有注册中心时必须移除 url 属性**,让 Feign 通过注册中心发现服务
|
||
- **url 属性仅用于开发测试或特殊场景**(如直连第三方服务)
|
||
- **Nacos 的价值在于服务发现与负载均衡**,硬编码 URL 会让这些能力失效
|
||
- **微服务架构应充分利用注册中心**,避免回到点对点调用的老路
|
||
|
||
---
|
||
|
||
### 问题 9:Nacos 3.0 客户端缺少 username/password 认证配置
|
||
|
||
#### 问题现象
|
||
Nacos 3.0 版本升级后,各业务服务启动时无法注册到 Nacos,控制台报错:
|
||
```
|
||
com.alibaba.nacos.api.exception.NacosException:
|
||
Error Code: 403, Error Msg: authorization failed
|
||
```
|
||
|
||
或日志中出现:
|
||
```
|
||
WARN com.alibaba.nacos.client.naming - [NA] failed to call server
|
||
com.alibaba.nacos.api.exception.NacosException:
|
||
Client need token, but token is empty.
|
||
```
|
||
|
||
#### 问题原因
|
||
**Nacos 3.0 安全策略变更**:
|
||
- Nacos 2.x 版本:客户端可以匿名访问服务端
|
||
- Nacos 3.0 版本:**强制要求客户端提供用户名和密码进行认证**
|
||
|
||
**默认凭证**:
|
||
- 用户名:`nacos`
|
||
- 密码:`nacos`
|
||
|
||
**影响范围**:
|
||
所有配置了 Nacos 服务发现的模块都需要添加认证信息。
|
||
|
||
#### 解决方案
|
||
|
||
**修复前(❌)**:
|
||
```yaml
|
||
spring:
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
namespace: fund-platform
|
||
group: DEFAULT_GROUP
|
||
# 缺少 username 和 password
|
||
```
|
||
|
||
**修复后(✅)**:
|
||
```yaml
|
||
spring:
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: localhost:8848
|
||
namespace: fund-platform
|
||
group: DEFAULT_GROUP
|
||
username: nacos # Nacos 3.0 必需
|
||
password: nacos # Nacos 3.0 必需
|
||
```
|
||
|
||
**涉及的所有模块**:
|
||
| 模块 | 服务名 | 配置文件位置 |
|
||
|------|--------|-------------|
|
||
| fund-sys | fund-sys | `fund-sys/src/main/resources/application.yml` |
|
||
| fund-cust | fund-cust | `fund-cust/src/main/resources/application.yml` |
|
||
| fund-exp | fund-exp | `fund-exp/src/main/resources/application.yml` |
|
||
| fund-proj | fund-proj | `fund-proj/src/main/resources/application.yml` |
|
||
| fund-req | fund-req | `fund-req/src/main/resources/application.yml` |
|
||
| fund-receipt | fund-receipt | `fund-receipt/src/main/resources/application.yml` |
|
||
| fund-file | fund-file | `fund-file/src/main/resources/application.yml` |
|
||
| fund-report | fund-report | `fund-report/src/main/resources/application.yml` |
|
||
|
||
#### 验证方法
|
||
|
||
**1. 查看服务注册状态**
|
||
访问 Nacos 控制台:http://localhost:8048/
|
||
- 登录账号:`nacos` / `nacos`
|
||
- 进入「服务管理」→「服务列表」
|
||
- 应该能看到所有服务已成功注册
|
||
|
||
**2. 检查服务日志**
|
||
启动日志中应该出现:
|
||
```
|
||
INFO c.a.cloud.nacos.registry.NacosServiceRegistry -
|
||
nacos registry, DEFAULT_GROUP fund-sys 10.244.21.185:8100 register finished
|
||
```
|
||
|
||
**3. 测试服务调用**
|
||
```bash
|
||
curl http://localhost:8000/sys/api/v1/sys/health
|
||
# 应该能正常返回健康检查结果
|
||
```
|
||
|
||
#### Nacos 3.0 vs 2.x 主要变化
|
||
|
||
| 特性 | Nacos 2.x | Nacos 3.0 | 影响 |
|
||
|------|-----------|-----------|------|
|
||
| **客户端认证** | ❌ 不需要 | ✅ 强制要求 | 必须配置 username/password |
|
||
| **Token 机制** | ❌ 无 | ✅ JWT Token | 客户端需携带有效 Token |
|
||
| **权限控制** | ❌ 弱 | ✅ 强化 | 支持细粒度 RBAC 权限管理 |
|
||
| **审计日志** | ❌ 基础 | ✅ 完整 | 记录所有敏感操作 |
|
||
|
||
#### 经验总结
|
||
- **Nacos 3.0 升级后必须修改客户端配置**,否则无法正常注册
|
||
- **默认用户名和密码都是 `nacos`**,生产环境建议修改为强密码
|
||
- **Gateway 不需要配置 discovery**,但需要通过 `@EnableDiscoveryClient` 启用服务发现
|
||
- **认证失败会报 403 错误**,而不是连接失败的错误
|
||
|
||
---
|
||
|
||
## 预防措施清单
|
||
|
||
### 前后端接口对接
|
||
|
||
| 检查项 | 说明 |
|
||
|--------|------|
|
||
| 请求头确认 | 确认所有必需的请求头(X-User-Id、X-Tenant-Id、Authorization等) |
|
||
| 响应格式确认 | 统一使用 `Result<T>` 包装响应,确认字段命名一致 |
|
||
| 错误码定义 | 明确各类错误码的含义和处理方式 |
|
||
|
||
### 前端路由配置
|
||
|
||
| 检查项 | 说明 |
|
||
|--------|------|
|
||
| 代理路径精确匹配 | 使用 `/api/` 带斜杠的形式,避免模糊匹配 |
|
||
| 路由命名规范 | 前端路由与后端API路径应使用不同的前缀区分 |
|
||
| 刷新兼容性 | SPA 应用需考虑页面刷新时的路由状态恢复 |
|
||
|
||
### 后端开发规范
|
||
|
||
| 检查项 | 说明 |
|
||
|--------|------|
|
||
| AOP 依赖引入 | 使用 AOP 需添加 `spring-boot-starter-aop` 依赖 |
|
||
| Jackson 日期配置 | 序列化 Java 8 日期类型需注册 `JavaTimeModule` |
|
||
| 日志独立输出 | 使用 Logger name 隔离,配置独立的 appender |
|
||
| 外键字段类型 | 使用雪花 ID 的外键字段必须使用 Long 类型 |
|
||
| Feign 拦截器注册 | Feign 拦截器必须添加@Component 注解并配置组件扫描 |
|
||
| TraceId 传递 | ContextInterceptor 必须提取并设置 TraceId |
|
||
| **FeignClient 配置** | **有注册中心时必须移除 url 属性,仅保留 name** |
|
||
| **Nacos 3.0 认证** | **客户端必须配置 username 和 password(默认都是 nacos)** |
|
||
|
||
### 功能开发流程
|
||
|
||
| 检查项 | 说明 |
|
||
|--------|------|
|
||
| UI交互完整性 | 所有菜单、按钮的点击事件都应有对应实现 |
|
||
| 自测覆盖 | 代码提交前进行基础功能自测 |
|
||
| 刷新测试 | 重点测试页面刷新后的状态是否正常 |
|
||
|
||
---
|
||
|
||
## 相关文件索引
|
||
|
||
### 本次问题修复涉及的文件
|
||
|
||
**后端文件:**
|
||
- `fund-sys/pom.xml` - 添加 AOP 和 Jackson 依赖
|
||
- `fund-sys/src/main/java/com/fundplatform/sys/aop/ApiLogAspect.java` - AOP 日志切面
|
||
- `fund-sys/src/main/resources/logback-spring.xml` - 日志配置
|
||
- `fund-exp/src/main/java/com/fundplatform/exp/dto/FundExpenseDTO.java` - 支出 DTO expenseType 字段类型修复
|
||
- `fund-common/src/main/java/com/fundplatform/common/feign/FeignChainInterceptor.java` - Feign 拦截器添加@Component 注解
|
||
- `fund-report/src/main/java/com/fundplatform/report/ReportApplication.java` - 配置组件扫描路径
|
||
- `fund-common/src/main/java/com/fundplatform/common/web/ContextInterceptor.java` - 添加 TraceId 提取与清理逻辑
|
||
- `fund-report/src/main/java/com/fundplatform/report/feign/ExpenseFeignClient.java` - 移除硬编码 URL,使用 Nacos 服务发现
|
||
- `fund-report/src/main/java/com/fundplatform/report/feign/ReceivableFeignClient.java` - 移除硬编码 URL,使用 Nacos 服务发现
|
||
- `fund-report/src/main/java/com/fundplatform/report/feign/ProjectFeignClient.java` - 移除硬编码 URL,使用 Nacos 服务发现
|
||
- `fund-cust/src/main/java/com/fundplatform/cust/feign/SysServiceClient.java` - 移除硬编码 URL,使用 Nacos 服务发现
|
||
- `fund-*/src/main/resources/application.yml` - 为所有业务模块添加 Nacos 3.0 username/password 认证配置
|
||
|
||
**前端文件:**
|
||
- `fund-admin/vite.config.ts` - 代理配置修复
|
||
- `fund-admin/src/router/index.ts` - 路由配置
|
||
- `fund-admin/src/layouts/MainLayout.vue` - 菜单事件处理
|
||
- `fund-admin/src/api/request.ts` - 请求拦截器
|
||
- `fund-admin/src/stores/user.ts` - 用户状态管理
|
||
|
||
---
|
||
|
||
## 附录:常见错误速查
|
||
|
||
| 错误信息 | 可能原因 | 解决方案 |
|
||
|----------|----------|----------|
|
||
| `400 Bad Request` | 缺少必需请求头 | 检查X-User-Id、X-Tenant-Id等 |
|
||
| `404 Not Found (刷新后)` | 代理路径冲突 | 使用精确匹配,区分前后端路由 |
|
||
| `Java 8 date/time type not supported` | Jackson未配置日期模块 | 注册JavaTimeModule |
|
||
| `ClassNotFoundException: Aspect` | 未引入AOP依赖 | 添加spring-boot-starter-aop |
|
||
| `JSON parse error` | 字段类型不匹配(Integer vs Long) | 雪花ID外键必须使用Long类型 |
|
||
| `Feign 调用未透传上下文` | 拦截器未注册为 Bean | 添加@Component 注解并配置扫描 |
|
||
| `TraceId 链路断裂` | ContextInterceptor 未提取 TraceId | 在 preHandle 中添加 TraceId 设置逻辑 |
|
||
| `服务发现失效` | FeignClient 硬编码 URL | 移除 url 属性,仅保留 name 使用 Nacos 发现 |
|
||
| `Nacos 注册失败 (认证错误)` | 缺少 username/password 配置 | 在 discovery 节点添加 username 和 password |
|
||
|
||
---
|
||
|
||
> **备注**: 本文档将根据后续开发过程中的问题持续更新。
|