diff --git a/knowledge_base/domain/spring-ai-tool-best-practices.md b/knowledge_base/domain/spring-ai-tool-best-practices.md new file mode 100644 index 0000000..c8263f4 --- /dev/null +++ b/knowledge_base/domain/spring-ai-tool-best-practices.md @@ -0,0 +1,259 @@ +--- +title: Spring AI 工具定义最佳实践 +keywords: [Spring AI, @Tool, 工具定义, Agent, 函数调用] +summary: 如何为 Spring AI Agent 定义高质量的工具(Tool),包括命名、描述、参数设计和错误处理 +category: domain +--- + +# Spring AI 工具定义最佳实践 + +## 工具定义基础 + +### 基本注解 +```java +@Component +public class MyTools { + + @Tool(description = "查询用户信息。参数 userId: 用户ID(必填)") + public UserInfo getUserInfo(String userId) { + // 实现 + } +} +``` + +### 关键要素 +1. **@Component** - 让 Spring 扫描到 +2. **@Tool** - 标记为 Agent 可调用的工具 +3. **description** - 告诉 Agent 这个工具做什么 + +## 描述(Description)编写规范 + +### 好的描述 +```java +@Tool(description = "查询知识库文档。优先精确匹配关键词,未命中或多个匹配时自动补充语义相关片段。" + + "参数 query: 查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'") +public LookupResult lookupKnowledge(String query) { ... } +``` + +**要点**: +- ✅ 说明工具用途(查询知识库) +- ✅ 说明工作机制(精确匹配 → 语义补充) +- ✅ 说明参数含义和示例 + +### 差的描述 +```java +@Tool(description = "查询文档") // ❌ 太简略 +public LookupResult lookup(String q) { ... } +``` + +## 参数设计 + +### 参数命名 +```java +// ✅ 好的命名 - 语义清晰 +public Result search(String query, int maxResults, String category) + +// ❌ 差的命名 - 缩写难懂 +public Result search(String q, int max, String cat) +``` + +### 参数类型 +```java +// ✅ 使用明确的类型 +public UserInfo getUser(String userId) +public List getOrders(LocalDate startDate, LocalDate endDate) + +// ❌ 使用 Object 或 Map +public Object getUser(Map params) // Agent 不知道传什么 +``` + +### 可选参数处理 +```java +@Tool(description = "查询订单。参数 status: 订单状态(可选,不传则查所有)") +public List getOrders( + @Nullable String status // 使用 @Nullable 标注 +) { + if (status == null) { + return orderRepository.findAll(); + } + return orderRepository.findByStatus(status); +} +``` + +## 返回值设计 + +### 使用明确的返回类型 +```java +// ✅ 好的返回类型 +public class LookupResult { + private boolean found; + private PrimaryResult primary; + private SupplementResult supplement; +} + +// ❌ 返回 String - Agent 难以解析 +public String lookup(String query) { + return "找到文档: xxx"; // 非结构化 +} +``` + +### 返回错误信息 +```java +public LookupResult lookup(String query) { + if (query == null || query.isEmpty()) { + return LookupResult.builder() + .found(false) + .error("查询关键词不能为空") + .build(); + } + // 正常逻辑 +} +``` + +## 错误处理 + +### 优雅降级 +```java +@Tool(description = "查询用户信息") +public UserInfo getUser(String userId) { + try { + return userService.findById(userId); + } catch (UserNotFoundException e) { + log.warn("用户不存在: userId={}", userId); + return UserInfo.notFound(userId); // 返回特殊对象,不抛异常 + } catch (Exception e) { + log.error("查询用户失败: userId={}", userId, e); + return UserInfo.error("系统错误,请稍后重试"); + } +} +``` + +### 不要抛出未捕获的异常 +```java +// ❌ 不要这样做 +@Tool(description = "查询用户") +public UserInfo getUser(String userId) { + return userService.findById(userId); // 可能抛出异常,Agent 无法处理 +} +``` + +## 可观测性 + +### 日志规范 +```java +@Tool(description = "查询订单") +public List getOrders(String userId) { + String requestId = UUID.randomUUID().toString().substring(0, 8); + long startTime = System.currentTimeMillis(); + + log.info("[{}] 收到订单查询请求: userId={}", requestId, userId); + + try { + List orders = orderService.findByUserId(userId); + long elapsed = System.currentTimeMillis() - startTime; + log.info("[{}] 查询完成: count={}, time={}ms", requestId, orders.size(), elapsed); + return orders; + } catch (Exception e) { + log.error("[{}] 查询失败: userId={}", requestId, userId, e); + throw e; + } +} +``` + +## 性能优化 + +### 设置合理的超时 +```java +@Tool(description = "查询大数据集") +public DataResult queryBigData(String query) { + // 设置超时保护 + return CompletableFuture + .supplyAsync(() -> heavyQuery(query)) + .orTimeout(5, TimeUnit.SECONDS) + .exceptionally(ex -> DataResult.timeout()) + .join(); +} +``` + +### 避免返回超大数据 +```java +// ✅ 分页或限制数量 +@Tool(description = "查询用户列表(最多返回 100 条)") +public List listUsers(int page, int size) { + size = Math.min(size, 100); // 强制上限 + return userService.findAll(PageRequest.of(page, size)); +} + +// ❌ 返回全量数据 +public List listAllUsers() { + return userService.findAll(); // 可能几万条 +} +``` + +## 工具组合示例 + +### 查询 + 操作的组合 +```java +@Component +public class OrderTools { + + @Tool(description = "查询订单详情") + public OrderDetail getOrder(String orderId) { ... } + + @Tool(description = "取消订单") + public CancelResult cancelOrder(String orderId, String reason) { ... } + + @Tool(description = "申请退款") + public RefundResult refund(String orderId, Double amount) { ... } +} +``` + +**Agent 使用场景**: +1. 用户:"帮我查一下订单 12345" +2. Agent 调用 `getOrder("12345")` +3. 用户:"帮我取消这个订单" +4. Agent 调用 `cancelOrder("12345", "用户主动取消")` + +## 常见陷阱 + +### ❌ 工具做太多事 +```java +// 不要把整个业务流程塞进一个工具 +@Tool(description = "处理订单") +public void processOrder(String orderId) { + // 查询订单 + // 验证库存 + // 扣减库存 + // 创建物流单 + // 发送通知 + // ... 太多步骤,Agent 无法介入 +} +``` + +### ✅ 拆分成多个工具 +```java +@Tool(description = "查询订单") +public Order getOrder(String orderId) { ... } + +@Tool(description = "验证库存") +public StockResult checkStock(String productId, int quantity) { ... } + +@Tool(description = "创建物流单") +public ShipmentResult createShipment(String orderId) { ... } +``` + +### ❌ 描述不准确 +```java +@Tool(description = "查询用户") +public UserInfo getUser(String query) { + // 实际上支持按 userId、email、手机号查询 + // 但描述没说清楚,Agent 不知道 +} +``` + +### ✅ 描述完整 +```java +@Tool(description = "查询用户信息。支持按 userId、email 或手机号查询。" + + "参数 query: 用户ID、邮箱或手机号") +public UserInfo getUser(String query) { ... } +``` diff --git a/knowledge_base/infrastructure/flyway-best-practices.md b/knowledge_base/infrastructure/flyway-best-practices.md new file mode 100644 index 0000000..9a7a298 --- /dev/null +++ b/knowledge_base/infrastructure/flyway-best-practices.md @@ -0,0 +1,310 @@ +--- +title: Flyway 数据库迁移最佳实践 +keywords: [Flyway, 数据库迁移, 版本管理, schema, migration] +summary: Flyway 数据库迁移的命名规范、编写技巧、回滚策略和常见问题处理 +category: infrastructure +--- + +# Flyway 数据库迁移最佳实践 + +## 命名规范 + +### 标准格式 +``` +V{version}__{description}.sql + +示例: +V001__create_user_table.sql +V002__add_email_to_user.sql +V003__create_order_table.sql +V004__add_metadata_to_api_document.sql +``` + +**规则**: +- `V` 大写,表示 Versioned migration +- 版本号用 3 位数字(001, 002...) +- 两个下划线 `__` 分隔版本号和描述 +- 描述用小写字母和下划线 + +### 版本号管理 +``` +V001 - 初始表结构 +V002 - 添加字段 +V003 - 创建索引 +V004 - 修改字段类型 +... +``` + +**建议**: +- 预留版本号空间(001, 010, 020...) +- 紧急修复用中间号(V005_hotfix__...) + +## SQL 编写规范 + +### 添加列 +```sql +-- ✅ 好的写法 - 包含默认值和注释 +ALTER TABLE user +ADD COLUMN email VARCHAR(100) DEFAULT '' COMMENT '用户邮箱'; + +-- ❌ 不好的写法 - 缺少默认值 +ALTER TABLE user +ADD COLUMN email VARCHAR(100); -- 已有数据会是 NULL +``` + +### 修改列 +```sql +-- ✅ 先添加新列,再迁移数据,最后删除旧列 +ALTER TABLE user ADD COLUMN new_status VARCHAR(20) DEFAULT 'active'; +UPDATE user SET new_status = old_status WHERE old_status IS NOT NULL; +ALTER TABLE user DROP COLUMN old_status; +ALTER TABLE user CHANGE COLUMN new_status status VARCHAR(20); + +-- ❌ 直接修改 - 可能导致数据丢失 +ALTER TABLE user MODIFY COLUMN status INT; +``` + +### 创建索引 +```sql +-- ✅ 指定索引名称 +CREATE INDEX idx_user_email ON user(email); +CREATE INDEX idx_order_user_id ON `order`(user_id); + +-- ❌ 不指定名称 - 自动生成的名称难以管理 +CREATE INDEX ON user(email); +``` + +### 外键约束 +```sql +-- ✅ 命名规范 +ALTER TABLE `order` +ADD CONSTRAINT fk_order_user_id +FOREIGN KEY (user_id) REFERENCES user(id) +ON DELETE CASCADE; + +-- ❌ 不指定名称 +ALTER TABLE `order` +ADD FOREIGN KEY (user_id) REFERENCES user(id); +``` + +## 幂等性保证 + +### 检查表是否存在 +```sql +-- 创建表前检查 +CREATE TABLE IF NOT EXISTS user ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL +); +``` + +### 检查列是否存在 +```sql +-- 添加列前检查 +ALTER TABLE user +ADD COLUMN IF NOT EXISTS email VARCHAR(100); + +-- 或使用存储过程(MySQL < 8.0) +SET @col_exists = ( + SELECT COUNT(*) FROM information_schema.columns + WHERE table_name = 'user' AND column_name = 'email' +); + +SET @query = IF(@col_exists = 0, + 'ALTER TABLE user ADD COLUMN email VARCHAR(100)', + 'SELECT "Column exists" AS msg' +); + +PREPARE stmt FROM @query; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +``` + +### 检查索引是否存在 +```sql +CREATE INDEX IF NOT EXISTS idx_user_email ON user(email); +``` + +## 数据迁移 + +### 分批处理大表 +```sql +-- ❌ 一次更新全部 - 可能锁表很久 +UPDATE large_table SET status = 'active' WHERE status IS NULL; + +-- ✅ 分批更新 +UPDATE large_table +SET status = 'active' +WHERE status IS NULL +LIMIT 1000; + +-- 重复执行直到影响行数为 0 +``` + +### 使用事务(DDL 语句除外) +```sql +START TRANSACTION; + +UPDATE user SET status = 'active' WHERE status = 'enabled'; +UPDATE user SET status = 'inactive' WHERE status = 'disabled'; + +COMMIT; +``` + +## 回滚策略 + +### 不支持自动回滚 +Flyway 社区版不支持自动回滚,需要手动编写撤销脚本: + +```sql +-- V005__add_email_to_user.sql +ALTER TABLE user ADD COLUMN email VARCHAR(100); + +-- V005__add_email_to_user.undo.sql (手动执行) +ALTER TABLE user DROP COLUMN email; +``` + +### 建议使用新版本修复 +```sql +-- V005 出错了,不要回滚 +-- 而是创建 V006 修复 + +-- V006__fix_user_email.sql +ALTER TABLE user MODIFY COLUMN email VARCHAR(200); +``` + +## 常见问题 + +### 问题 1: 迁移失败后状态卡住 + +**症状**: +``` +FlywayException: Migration failed! +Schema history table shows failed migration. +``` + +**解决**: +```sql +-- 查看迁移历史 +SELECT * FROM flyway_schema_history ORDER BY installed_rank DESC; + +-- 删除失败记录 +DELETE FROM flyway_schema_history WHERE version = '005' AND success = 0; + +-- 修复 SQL 脚本后重新启动 +``` + +### 问题 2: Checksum 不匹配 + +**症状**: +``` +FlywayException: Checksum mismatch for migration version 005 +``` + +**原因**:迁移脚本被修改了 + +**解决**: +```sql +-- 方案 1: 修复 checksum(仅开发环境) +UPDATE flyway_schema_history +SET checksum = NULL +WHERE version = '005'; + +-- 方案 2: 创建新版本(推荐) +-- 不要修改已执行的迁移脚本,创建 V006 +``` + +### 问题 3: 多个开发者同时创建迁移 + +**场景**: +- 开发者 A 创建 V005 +- 开发者 B 创建 V005 +- 冲突! + +**预防**: +``` +使用时间戳版本号: +V20260624001__add_user_email.sql +V20260624002__add_order_index.sql +``` + +## 生产环境最佳实践 + +### 1. 先验证后应用 +```bash +# 开发环境测试 +mvn flyway:migrate + +# 预生产环境验证 +mvn flyway:migrate -Dflyway.url=jdbc:mysql://pre-prod-db:3306/db + +# 生产环境应用 +mvn flyway:migrate -Dflyway.url=jdbc:mysql://prod-db:3306/db +``` + +### 2. 备份数据库 +```bash +# 应用迁移前备份 +mysqldump -u root -p superbiz_agent > backup_before_v005.sql + +# 应用迁移 +mvn spring-boot:run + +# 出问题时恢复 +mysql -u root -p superbiz_agent < backup_before_v005.sql +``` + +### 3. 限制自动迁移 +```yaml +# 生产环境配置 +spring: + flyway: + enabled: false # 禁用自动迁移 + +# 手动触发 +mvn flyway:migrate -Dspring.profiles.active=prod +``` + +### 4. 监控迁移时间 +```sql +SELECT version, description, type, installed_on, execution_time +FROM flyway_schema_history +ORDER BY installed_rank DESC +LIMIT 10; +``` + +## 工具和命令 + +### Maven 命令 +```bash +# 查看迁移信息 +mvn flyway:info + +# 执行迁移 +mvn flyway:migrate + +# 验证迁移 +mvn flyway:validate + +# 清空数据库(危险!仅开发环境) +mvn flyway:clean +``` + +### 配置文件 +```yaml +spring: + flyway: + enabled: true + baseline-on-migrate: true # 已有数据库时从当前版本开始 + locations: classpath:db/migration + table: flyway_schema_history + validate-on-migrate: true +``` + +## 团队协作规范 + +1. **迁移脚本不可修改**:已合并的脚本禁止修改 +2. **版本号递增**:新脚本必须比最新版本号大 +3. **命名规范统一**:遵循 `V{version}__{description}.sql` +4. **Code Review**:迁移脚本必须经过审查 +5. **测试覆盖**:每个迁移都要测试(空库 + 有数据) diff --git a/knowledge_base/infrastructure/mysql-connection-pool.md b/knowledge_base/infrastructure/mysql-connection-pool.md new file mode 100644 index 0000000..02bf299 --- /dev/null +++ b/knowledge_base/infrastructure/mysql-connection-pool.md @@ -0,0 +1,99 @@ +--- +title: MySQL 数据库连接池配置 +keywords: [MySQL, HikariCP, 连接池, 数据库, 性能优化] +summary: MySQL 连接池的配置参数、性能调优和故障排查指南 +category: infrastructure +--- + +# MySQL 数据库连接池配置 + +## HikariCP 配置 + +### 基础配置 +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/superbiz_agent?useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: password + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 +``` + +## 关键参数说明 + +### maximum-pool-size +- **默认值**:10 +- **建议值**:根据并发量调整 +- **公式**:connections = ((core_count * 2) + effective_spindle_count) +- **注意**:不是越大越好,过大会增加数据库负担 + +### connection-timeout +- **默认值**:30000ms (30秒) +- **说明**:等待连接的最大时间 +- **建议**:根据业务超时要求调整 + +### idle-timeout +- **默认值**:600000ms (10分钟) +- **说明**:连接空闲多久后被释放 +- **建议**:小于 MySQL wait_timeout + +## 常见问题 + +### 连接泄漏 + +**症状**: +- 应用无法获取数据库连接 +- 日志显示 "Connection is not available" + +**排查**: +```java +// 检查是否有未关闭的连接 +try (Connection conn = dataSource.getConnection()) { + // 使用连接 +} // 自动关闭 +``` + +**解决**: +- 使用 try-with-resources +- 检查事务是否正常提交/回滚 + +### wait_timeout 超时 + +**症状**:MySQL 错误 "The last packet successfully received from the server was X milliseconds ago" + +**排查**: +```sql +SHOW VARIABLES LIKE 'wait_timeout'; +``` + +**解决**: +```yaml +hikari: + max-lifetime: 1800000 # 小于 MySQL wait_timeout +``` + +## 性能监控 + +### HikariCP 指标 +```java +HikariPoolMXBean poolMXBean = hikariDataSource.getHikariPoolMXBean(); +int active = poolMXBean.getActiveConnections(); +int idle = poolMXBean.getIdleConnections(); +int total = poolMXBean.getTotalConnections(); +``` + +### 慢查询监控 +```sql +-- 开启慢查询日志 +SET GLOBAL slow_query_log = 'ON'; +SET GLOBAL long_query_time = 2; + +-- 查看慢查询 +SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10; +``` diff --git a/knowledge_base/infrastructure/redis-config.md b/knowledge_base/infrastructure/redis-config.md new file mode 100644 index 0000000..715f8c9 --- /dev/null +++ b/knowledge_base/infrastructure/redis-config.md @@ -0,0 +1,64 @@ +--- +title: Redis 缓存配置指南 +keywords: [Redis, 缓存, 配置, 连接池, 超时] +summary: Redis 缓存的配置参数说明、连接池设置和常见问题排查 +category: infrastructure +--- + +# Redis 缓存配置指南 + +## 基础配置 + +### 连接参数 +```yaml +spring: + redis: + host: localhost + port: 6379 + password: your_password + database: 0 + timeout: 3000ms +``` + +### 连接池配置 +```yaml +spring: + redis: + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: -1ms +``` + +## 常见问题 + +### 超时问题排查 + +**症状**:Redis 操作超时 + +**排查步骤**: +1. 检查网络连接:`ping redis_host` +2. 检查 Redis 服务状态:`redis-cli ping` +3. 查看慢查询日志:`redis-cli slowlog get 10` +4. 检查连接池状态 + +**解决方案**: +- 增加超时时间 +- 优化慢查询 +- 调整连接池大小 + +### 连接数过多 + +**症状**:达到 Redis 最大连接数限制 + +**排查**: +```bash +redis-cli info clients +``` + +**解决**: +- 调整 `maxclients` 参数 +- 检查连接泄漏 +- 启用连接池复用 diff --git a/knowledge_base/troubleshooting/fault-diagnosis-process.md b/knowledge_base/troubleshooting/fault-diagnosis-process.md new file mode 100644 index 0000000..f277383 --- /dev/null +++ b/knowledge_base/troubleshooting/fault-diagnosis-process.md @@ -0,0 +1,157 @@ +--- +title: 故障诊断流程规范 +keywords: [故障诊断, 排查, 根因分析, RCA, 应急响应] +summary: 生产环境故障的标准诊断流程、根因分析方法和文档规范 +category: troubleshooting +--- + +# 故障诊断流程规范 + +## 应急响应流程 + +### 1. 初步评估(5 分钟内) + +**关键问题**: +- 影响范围:多少用户受影响? +- 严重程度:P0(全站挂)/ P1(核心功能)/ P2(次要功能) +- 开始时间:什么时候开始的? + +**立即行动**: +- 通知相关人员 +- 开启故障战室 +- 记录时间线 + +### 2. 快速止血(15-30 分钟) + +**优先级**:恢复服务 > 找根因 + +**常见止血手段**: +- 回滚最近部署 +- 重启服务 +- 流量切换 +- 降级非核心功能 + +**验证止血**: +- 检查监控指标恢复 +- 抽样验证用户功能 +- 确认错误日志减少 + +### 3. 根因分析 + +**信息收集**: +- 错误日志(ELK/Kibana) +- 监控指标(Grafana) +- 慢查询日志 +- 堆栈信息 +- 最近变更记录 + +**分析方法**: +- 5-Why 分析法 +- 时间线对比(问题前后变化) +- 相关性分析(哪些指标同时异常) + +## 5-Why 分析法 + +**示例:API 超时故障** + +1. **为什么 API 超时?** + - 数据库查询慢 + +2. **为什么数据库查询慢?** + - 索引失效 + +3. **为什么索引失效?** + - 表数据量暴增,执行计划变更 + +4. **为什么表数据量暴增?** + - 定时清理任务失败 + +5. **为什么清理任务失败?** + - 磁盘空间不足,任务异常退出 + +**根因**:磁盘空间监控未配置告警 + +## 故障报告模板 + +### 1. 故障概要 +- 发生时间: +- 影响时长: +- 影响范围: +- 严重程度: + +### 2. 故障现象 +- 用户反馈: +- 错误日志: +- 监控截图: + +### 3. 根本原因 +- 直接原因: +- 根本原因:(5-Why 分析) +- 相关变更: + +### 4. 解决方案 +- 临时方案: +- 长期方案: +- 预防措施: + +### 5. 时间线 +``` +10:00 - 用户反馈 API 超时 +10:05 - 确认影响范围,通知团队 +10:10 - 发现数据库慢查询 +10:15 - 执行索引优化,服务恢复 +10:30 - 根因分析完成 +``` + +### 6. 改进措施 +- 技术改进: +- 流程改进: +- 监控增强: + +## 常见故障分类 + +### 性能类 +- 慢查询 +- 内存溢出 +- CPU 飙高 +- 线程池耗尽 + +### 可用性类 +- 服务宕机 +- 网络故障 +- 依赖服务挂 +- 数据库连接池满 + +### 数据类 +- 数据不一致 +- 数据丢失 +- 重复数据 + +### 安全类 +- 认证失败 +- 权限绕过 +- SQL 注入 +- DDoS 攻击 + +## 最佳实践 + +### 日志规范 +```java +// 关键操作记录请求 ID +log.info("[{}] 开始处理支付请求: userId={}, amount={}", + requestId, userId, amount); + +// 异常必须记录完整堆栈 +log.error("[{}] 支付失败", requestId, e); +``` + +### 监控指标 +- **Golden Signals**:延迟、流量、错误率、饱和度 +- **业务指标**:订单量、支付成功率 +- **资源指标**:CPU、内存、磁盘、网络 + +### 告警阈值 +- 错误率 > 1% +- P99 延迟 > 2s +- 数据库连接池使用率 > 80% +- 内存使用率 > 85%