30 KiB
资金服务平台 - 开发问题清单
文档版本: 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 认证配置 | 高 | 已解决 |
| 10 | 连接池监控 | HikariMonitorConfig空指针异常 | 中 | 已解决 |
| 11 | 链路追踪 | ContextInterceptor响应头未设置traceId和spanId | 高 | 已解决 |
问题详情与解决方案
问题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)
// 在请求头中添加 X-User-Id
request.headers['X-User-Id'] = localStorage.getItem('userId') || ''
2. 修改用户登录逻辑 (fund-admin/src/stores/user.ts)
// 登录成功后保存 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 两个命令的处理为空实现。
// 问题代码
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
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,导致前端路由请求被错误代理到后端服务。
// 问题配置
proxy: {
'/sys': { // 会匹配 /sys 开头的所有路径,包括 /system
target: 'http://localhost:8100',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/sys/, '')
}
}
解决方案
修改 fund-admin/vite.config.ts
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)
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
2. 配置 ObjectMapper (ApiLogAspect.java)
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
// 注册JavaTimeModule以支持Java 8日期时间类型
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
3. 序列化异常兜底处理
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
// 修改前
@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 调用时拦截器不生效。
// 问题代码
public class FeignChainInterceptor implements RequestInterceptor {
// 没有 @Component 注解
}
解决方案
1. 添加 Bean 注解 (fund-common/src/main/java/com/fundplatform/common/feign/FeignChainInterceptor.java)
@Component // 添加此注解
public class FeignChainInterceptor implements RequestInterceptor {
// ...
}
2. 配置组件扫描 (fund-report/src/main/java/com/fundplatform/report/ReportApplication.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,导致:
- 业务逻辑无法获取 Gateway 生成的 TraceId
- Feign 调用时通过
getOrCreateTraceId()生成新的 TraceId - 日志系统中出现多个不同的 TraceId,无法串联完整调用链
// 问题代码 - 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
- 添加导入和常量定义
import com.fundplatform.common.context.TraceContextHolder;
public static final String HEADER_TRACE_ID = "X-Trace-Id";
- 在 preHandle 中提取 TraceId
// 提取 TraceId(如不存在,后续会在需要时自动生成)
String traceId = request.getHeader(HEADER_TRACE_ID);
if (traceId != null && !traceId.isEmpty()) {
TraceContextHolder.setTraceId(traceId);
}
- 在 afterCompletion 中清理 TraceContextHolder
@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}" 硬编码地址,导致:
- Nacos 服务注册中心形同虚设
- Feign 不会通过 Nacos 进行服务发现和负载均衡
- 无法实现服务动态扩缩容和高可用
// 问题代码 - ExpenseFeignClient.java
@FeignClient(name = "fund-exp", url = "${feign.fund-exp.url:http://localhost:8140}")
public interface ExpenseFeignClient {
// ...
}
问题原因
架构设计不一致:
- ✅ 已在
application.yml中配置 Nacos 服务发现 - ❌ 但 FeignClient 仍指定了固定的 URL 地址
可能的历史原因:
- 开发环境过渡方案:本地开发时 Nacos 未部署,临时使用直连方式
- 迁移遗留问题:从单体架构迁移到微服务时的过渡配置
- 理解偏差:误以为 FeignClient 必须指定 url
解决方案
修复前(❌):
@FeignClient(name = "fund-exp", url = "${feign.fund-exp.url:http://localhost:8140}")
// ↓ 直接访问固定地址,Nacos 不生效
修复后(✅):
@FeignClient(name = "fund-exp")
// ↓ 通过 Nacos 服务发现 + Ribbon 负载均衡
涉及的文件修改:
ExpenseFeignClient.java- 移除url属性ReceivableFeignClient.java- 移除url属性ProjectFeignClient.java- 移除url属性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 配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos 地址
namespace: fund-platform # 命名空间
group: DEFAULT_GROUP # 分组
2. 启动类注解
@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 服务发现的模块都需要添加认证信息。
解决方案
修复前(❌):
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP
# 缺少 username 和 password
修复后(✅):
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. 测试服务调用
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 错误,而不是连接失败的错误
问题 10:HikariMonitorConfig 空指针异常
问题现象
服务启动后,定时任务日志中持续报错:
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
问题原因
HikariCP 连接池初始化时机问题:
HikariDataSource.getHikariPoolMXBean()方法在连接池尚未初始化时返回null- 定时任务在应用启动后立即执行,此时连接池可能还未完成初始化
- 直接调用
getActiveConnections()等方法会导致空指针异常
相关代码:
// 问题代码
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
public void monitorHikariPool() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
// 未检查 getHikariPoolMXBean() 返回值是否为 null
log.info("活跃连接数: {}", hikariDataSource.getHikariPoolMXBean().getActiveConnections());
}
}
解决方案
修复后代码:
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
public void monitorHikariPool() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariConfigMXBean config = hikariDataSource.getHikariConfigMXBean();
var poolMXBean = hikariDataSource.getHikariPoolMXBean();
// 检查连接池是否已初始化
if (poolMXBean == null) {
log.debug("HikariCP 连接池尚未初始化,跳过监控");
return;
}
log.info("=== HikariCP 连接池状态 ===");
log.info("连接池名称: {}", config.getPoolName());
log.info("活跃连接数: {}", poolMXBean.getActiveConnections());
log.info("空闲连接数: {}", poolMXBean.getIdleConnections());
log.info("等待获取连接的线程数: {}", poolMXBean.getThreadsAwaitingConnection());
log.info("最大连接数: {}", config.getMaximumPoolSize());
log.info("最小空闲连接数: {}", config.getMinimumIdle());
}
}
修复要点:
- 先获取
HikariPoolMXBean实例并存储到变量 - 检查是否为
null,为null则跳过本次监控 - 使用变量访问连接池信息,避免重复调用
getHikariPoolMXBean()
修复文件
fund-sys/src/main/java/com/fundplatform/sys/config/HikariMonitorConfig.java
经验总结
- 调用可能返回 null 的方法前必须做空值检查
- 定时任务执行时需考虑资源初始化时机,不能假设资源已就绪
- 使用变量存储对象引用,避免重复调用 getter 方法
问题11:ContextInterceptor响应头未设置traceId和spanId
问题现象
用户反馈API响应头中没有返回X-Trace-Id和X-Span-Id,导致无法通过响应头直接获取追踪标识进行问题排查。
# 预期响应头
HTTP/1.1 200 OK
X-Trace-Id: abc123def456789abc123def456789ab
X-Span-Id: 1a2b3c4d5e6f7g8h
# 实际响应头(缺失追踪标识)
HTTP/1.1 200 OK
Content-Type: application/json
问题原因
ContextInterceptor.java文件虽然在Git提交中包含了响应头设置代码,但文件实际内容与提交记录不符,缺少以下关键代码:
// 缺少的代码
public static final String HEADER_SPAN_ID = "X-Span-Id";
// 生成 SpanId
String spanId = TraceContextHolder.getOrCreateSpanId();
// 在响应头中返回 traceId 和 spanId
response.setHeader(HEADER_TRACE_ID, traceId);
response.setHeader(HEADER_SPAN_ID, spanId);
解决方案
修复 fund-common/src/main/java/com/fundplatform/common/web/ContextInterceptor.java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// ... 其他代码 ...
// 提取或生成 TraceId(全链路唯一标识)
String traceId = request.getHeader(HEADER_TRACE_ID);
if (traceId != null && !traceId.isEmpty()) {
TraceContextHolder.setTraceId(traceId);
} else {
traceId = TraceContextHolder.getOrCreateTraceId();
}
// 生成 SpanId(当前服务处理标识,每个请求唯一)
String spanId = TraceContextHolder.getOrCreateSpanId();
// 在响应头中返回 traceId 和 spanId,方便客户端追踪
response.setHeader(HEADER_TRACE_ID, traceId);
response.setHeader(HEADER_SPAN_ID, spanId);
// ... 其他代码 ...
}
同时需要在 TraceContextHolder.java 中添加 SpanId 支持:
private static final ThreadLocal<String> SPAN_ID_HOLDER = new ThreadLocal<>();
private static final String MDC_KEY_SPAN_ID = "spanId";
public static void setSpanId(String spanId) {
SPAN_ID_HOLDER.set(spanId);
MDC.put(MDC_KEY_SPAN_ID, spanId);
}
public static String getSpanId() {
return SPAN_ID_HOLDER.get();
}
public static String getOrCreateSpanId() {
String spanId = SPAN_ID_HOLDER.get();
if (spanId == null || spanId.isEmpty()) {
spanId = generateSpanId();
setSpanId(spanId);
}
return spanId;
}
public static String generateSpanId() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
修复文件
fund-common/src/main/java/com/fundplatform/common/context/TraceContextHolder.javafund-common/src/main/java/com/fundplatform/common/web/ContextInterceptor.java
经验总结
- 文件提交后需验证实际内容,不能仅依赖Git提交记录
- 代码审查时需检查文件实际内容,确保修改确实生效
- 响应头设置应在 preHandle 中完成,确保所有请求都能返回追踪标识
预防措施清单
前后端接口对接
| 检查项 | 说明 |
|---|---|
| 请求头确认 | 确认所有必需的请求头(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) |
| 定时任务资源检查 | 定时任务访问资源前需检查资源是否已初始化(如连接池) |
| 链路追踪响应头 | API响应必须返回X-Trace-Id和X-Span-Id,便于客户端追踪 |
功能开发流程
| 检查项 | 说明 |
|---|---|
| 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-common/src/main/java/com/fundplatform/common/context/TraceContextHolder.java- 添加 SpanId 支持fund-common/src/main/java/com/fundplatform/common/web/ContextInterceptor.java- 响应头设置 traceId 和 spanId
前端文件:
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 |
HikariPoolMXBean NullPointerException |
定时任务执行时连接池未初始化 | 添加 poolMXBean 空值检查 |
响应头缺失X-Trace-Id/X-Span-Id |
ContextInterceptor未设置响应头 | 在preHandle中调用response.setHeader() |
备注: 本文档将根据后续开发过程中的问题持续更新。