diff --git a/.qoder/skills/entity-db-sync/SKILL.md b/.qoder/skills/entity-db-sync/SKILL.md new file mode 100644 index 0000000..359df9e --- /dev/null +++ b/.qoder/skills/entity-db-sync/SKILL.md @@ -0,0 +1,286 @@ +--- +name: entity-db-sync +description: 确保Java实体类与数据库SQL脚本同步的技能,当修改实体类字段时自动检查并更新对应的数据库初始化脚本,保证表结构与实体定义严格一致。使用场景:修改实体类属性、添加字段、删除字段、字段类型变更时自动触发。实践要求:同步更新设计文档中的数据表信息,独立目录保存变更脚本便于生产环境更新。 +--- + +# 实体类与数据库脚本同步技能 + +## 触发条件 + +当出现以下情况时自动应用此技能: + +1. **实体类修改相关**: + - 修改Java实体类的字段定义 + - 添加新的实体类字段 + - 删除实体类字段 + - 修改字段的数据类型或注解 + +2. **数据库脚本相关**: + - 需要更新数据库初始化SQL脚本 + - 需要创建新的表结构SQL + - 需要修改现有表的字段定义 + +3. **同步检查相关**: + - 需要验证实体类与数据库脚本的一致性 + - 需要生成差异报告 + - 需要修复不一致的地方 + +4. **文档维护相关**: + - 需要同步更新设计文档中的表结构信息 + - 需要生成数据库变更脚本 + - 需要维护生产环境更新包 + +## 核心原则 + +### 同步规范 +- **一对一映射**:每个实体类字段必须对应数据库表中的列 +- **类型匹配**:Java数据类型与MySQL数据类型必须兼容 +- **约束一致**:非空约束、长度限制等必须保持一致 +- **注解驱动**:基于JPA/Hibernate注解生成SQL定义 + +### 文档同步规范 +- **设计文档更新**:实体类变更时同步更新相关设计文档 +- **变更脚本管理**:独立目录保存数据库变更脚本 +- **版本控制**:维护清晰的变更历史和版本信息 + +### 生产环境部署规范 +- **变更脚本独立存储**:便于生产环境按需执行 +- **回滚方案准备**:每个变更都提供回滚脚本 +- **影响评估**:评估变更对现有数据的影响 + +## 实践要求 + +### 1. 设计文档同步更新 + +#### 需要维护的文档类型 +- **数据库设计文档**:表结构定义、字段说明、约束条件 +- **API接口文档**:涉及数据模型变更的接口说明 +- **业务流程文档**:受数据结构影响的业务逻辑说明 + +#### 文档更新流程 +``` +1. 实体类修改 → 2. 生成SQL变更脚本 → 3. 更新设计文档 → 4. 验证一致性 +``` + +#### 文档更新检查清单 +- [ ] 数据库表结构调整说明 +- [ ] 字段新增/删除/修改记录 +- [ ] 约束条件变更说明 +- [ ] 索引变更记录 +- [ ] 业务影响评估 + +### 2. 变更脚本管理 + +#### 目录结构规范 +``` +project/ +├── change/ # 部署相关文件(与开发代码分离) +│ └── sql/ +│ ├── init/ # 初始化脚本(首次部署) +│ │ ├── user.sql +│ │ ├── order.sql +│ │ └── product.sql +│ └── upgrade/ # 升级脚本(版本变更) +│ ├── v1.1.0/ +│ │ ├── 20240115_add_user_phone.sql +│ │ ├── 20240115_modify_order_amount_precision.sql +│ │ └── rollback/ +│ │ ├── 20240115_add_user_phone_rollback.sql +│ └── v1.2.0/ +│ └── 20240201_add_product_category.sql +└── src/main/resources/ # 开发代码目录(保持独立) +``` + +#### 变更脚本命名规范 +```bash +# 格式:YYYYMMDD_description.sql +20240115_add_user_phone.sql # 正向变更 +20240115_add_user_phone_rollback.sql # 回滚脚本 +20240115_add_user_phone_verify.sql # 验证脚本 +``` + +#### 脚本内容模板 +```sql +-- ============================================ +-- 变更描述: 为用户表添加手机号字段 +-- 变更时间: 2024-01-15 +-- 影响版本: v1.1.0 +-- 影响表: user +-- ============================================ + +-- 1. 添加字段 +ALTER TABLE `user` +ADD COLUMN `phone` VARCHAR(20) COMMENT '手机号码'; + +-- 2. 添加索引 +ALTER TABLE `user` +ADD INDEX `idx_phone` (`phone`); + +-- 3. 更新现有数据(如有必要) +UPDATE `user` SET `phone` = '' WHERE `phone` IS NULL; + +-- 4. 验证变更 +SELECT COUNT(*) as phone_count FROM `user` WHERE `phone` IS NOT NULL; +``` + +#### 回滚脚本模板 +```sql +-- ============================================ +-- 回滚描述: 撤销用户表手机号字段添加 +-- 回滚时间: 20240115 +-- 原变更: 20240115_add_user_phone.sql +-- ============================================ + +-- 1. 删除索引 +ALTER TABLE `user` DROP INDEX `idx_phone`; + +-- 2. 删除字段 +ALTER TABLE `user` DROP COLUMN `phone`; + +-- 3. 验证回滚 +SELECT COUNT(*) as remaining_indexes +FROM information_schema.STATISTICS +WHERE TABLE_NAME = 'user' AND COLUMN_NAME = 'phone'; +``` + +## 工作流程 + +### 完整同步流程 +``` +1. 实体类修改 + ↓ +2. 生成差异报告 + ↓ +3. 编写变更脚本 + ↓ +4. 更新初始化脚本 + ↓ +5. 同步设计文档 + ↓ +6. 验证一致性 + ↓ +7. 提交变更包 +``` + +### 自动化工具集成 + +#### 变更脚本生成器 +```python +# generate-change-script.py +import datetime +import os + +def create_change_script(table_name, changes, version): + """生成标准变更脚本""" + today = datetime.date.today().strftime('%Y%m%d') + timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # 部署目录结构 + change_dir = "change/sql/upgrade" + version_dir = f"{change_dir}/{version}" + rollback_dir = f"{version_dir}/rollback" + + # 创建目录 + os.makedirs(version_dir, exist_ok=True) + os.makedirs(rollback_dir, exist_ok=True) + + script_content = f"""-- ============================================ +-- 变更描述: {changes['description']} +-- 变更时间: {timestamp} +-- 影响版本: {version} +-- 影响表: {table_name} +-- ============================================ + +""" + + # 添加具体的SQL变更 + for change_type, change_detail in changes.items(): + if change_type == 'add_columns': + for column in change_detail: + script_content += f"ALTER TABLE `{table_name}` ADD COLUMN {column};\n" + + elif change_type == 'modify_columns': + for column in change_detail: + script_content += f"ALTER TABLE `{table_name}` MODIFY COLUMN {column};\n" + + # 保存脚本 + filename = f"{today}_{changes['description'].replace(' ', '_').lower()}.sql" + filepath = f"{version_dir}/{filename}" + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(script_content) + + return filepath +``` + +#### 文档同步检查器 +```bash +#!/bin/bash +# sync-documentation.sh + +ENTITY_FILE=$1 +CHANGE_SCRIPT=$2 + +echo "=== 同步设计文档 ===" + +# 提取变更信息 +TABLE_NAME=$(grep -o '@Table(name = "[^"]*")' "$ENTITY_FILE" | cut -d'"' -f2) +CHANGE_DESC=$(basename "$CHANGE_SCRIPT" .sql | cut -d'_' -f2-) + +# 更新数据库设计文档到change目录 +echo "更新数据库设计文档..." +DOC_DIR="change/docs/database" +mkdir -p "$DOC_DIR" + +# 生成变更说明文档 +cat > "${DOC_DIR}/${TABLE_NAME}_change_${CHANGE_DESC}.md" << EOF +# ${TABLE_NAME} 表结构变更说明 + +## 变更概述 +- 变更时间: $(date '+%Y-%m-%d') +- 变更类型: 字段添加/修改/删除 +- 影响范围: ${TABLE_NAME} 表 + +## 详细变更 +$(cat "$CHANGE_SCRIPT") + +## 业务影响 +- [ ] API接口适配 +- [ ] 前端页面调整 +- [ ] 数据迁移需求 +- [ ] 测试用例更新 + +## 验证清单 +- [ ] 开发环境验证 +- [ ] 测试环境验证 +- [ ] 生产环境预演 +EOF + +echo "文档同步完成: ${DOC_DIR}/${TABLE_NAME}_change_${CHANGE_DESC}.md" +``` + +## 最佳实践 + +### 1. 变更管理 +``` +版本分支 → 开发变更 → 生成脚本 → 文档同步 → 测试验证 → 合并发布 +``` + +### 2. 生产环境部署 +```bash +# 生产环境变更部署流程 +1. 备份数据库 +2. 执行变更脚本 +3. 验证数据一致性 +4. 功能测试 +5. 监控观察 +6. 准备回滚方案 +``` + +### 3. 团队协作规范 +- 实体类修改必须附带变更脚本 +- 变更前必须进行影响评估 +- 多人协作时建立变更协调机制 +- 定期审查变更历史和文档同步情况 + +这个增强版的技能不仅确保实体类与数据库脚本的技术同步,还建立了完整的文档维护和生产环境变更管理流程。 \ No newline at end of file diff --git a/.qoder/skills/entity-db-sync/examples.md b/.qoder/skills/entity-db-sync/examples.md new file mode 100644 index 0000000..3d1fc84 --- /dev/null +++ b/.qoder/skills/entity-db-sync/examples.md @@ -0,0 +1,538 @@ +# 实体类与数据库脚本同步示例 + +## 基础同步示例 + +### 示例1: 简单用户实体 + +**Java实体类 (User.java):** +```java +package com.fundplatform.user.entity; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "user") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "username", length = 50, nullable = false, unique = true) + private String username; + + @Column(name = "email", length = 100) + private String email; + + @Column(name = "phone", length = 20) + private String phone; + + @Column(name = "is_active", columnDefinition = "TINYINT(1) DEFAULT 1") + private Boolean active; + + @Column(name = "created_time", updatable = false) + private LocalDateTime createdTime; + + @Column(name = "updated_time") + private LocalDateTime updatedTime; + + // getters and setters... +} +``` + +**对应SQL脚本 (user.sql):** +```sql +CREATE TABLE `user` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` VARCHAR(50) NOT NULL COMMENT '用户名', + `email` VARCHAR(100) COMMENT '邮箱地址', + `phone` VARCHAR(20) COMMENT '手机号码', + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否激活', + `created_time` DATETIME COMMENT '创建时间', + `updated_time` DATETIME COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + KEY `idx_email` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户基本信息表'; +``` + +### 示例2: 订单实体(复杂类型) + +**Java实体类 (Order.java):** +```java +package com.fundplatform.order.entity; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +@Entity +@Table(name = "fund_order") +public class Order { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long orderId; + + @Column(name = "order_no", length = 32, nullable = false, unique = true) + private String orderNo; + + @Column(name = "customer_id", nullable = false) + private Long customerId; + + @Column(name = "project_id") + private Long projectId; + + @Column(name = "amount", precision = 15, scale = 2, nullable = false) + private BigDecimal amount; + + @Column(name = "order_date", nullable = false) + private LocalDate orderDate; + + @Enumerated(EnumType.STRING) + @Column(name = "order_status", length = 20, nullable = false) + private OrderStatus status; + + @Column(name = "payment_method", length = 20) + private String paymentMethod; + + @Column(name = "remark", length = 500) + private String remark; + + // 枚举定义 + public enum OrderStatus { + PENDING, CONFIRMED, PROCESSING, COMPLETED, CANCELLED + } +} +``` + +**对应SQL脚本 (fund_order.sql):** +```sql +CREATE TABLE `fund_order` ( + `order_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID', + `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号', + `customer_id` BIGINT NOT NULL COMMENT '客户ID', + `project_id` BIGINT COMMENT '项目ID', + `amount` DECIMAL(15,2) NOT NULL COMMENT '订单金额', + `order_date` DATE NOT NULL COMMENT '下单日期', + `order_status` VARCHAR(20) NOT NULL COMMENT '订单状态', + `payment_method` VARCHAR(20) COMMENT '支付方式', + `remark` VARCHAR(500) COMMENT '备注', + `created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`order_id`), + UNIQUE KEY `uk_order_no` (`order_no`), + KEY `idx_customer_id` (`customer_id`), + KEY `idx_order_date` (`order_date`), + KEY `idx_order_status` (`order_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='基金订单表'; +``` + +## 字段变更同步示例 + +### 场景1: 添加新字段 + +**原始实体类:** +```java +@Entity +@Table(name = "product") +public class Product { + @Id + private Long id; + + @Column(length = 100) + private String name; + + @Column(precision = 10, scale = 2) + private BigDecimal price; +} +``` + +**修改后的实体类:** +```java +@Entity +@Table(name = "product") +public class Product { + @Id + private Long id; + + @Column(length = 100, nullable = false) // 添加非空约束 + private String name; + + @Column(precision = 10, scale = 2, nullable = false) // 添加非空约束 + private BigDecimal price; + + // 新增字段 + @Column(name = "category_id") + private Long categoryId; + + @Column(length = 500) + private String description; + + @Column(name = "is_available", columnDefinition = "TINYINT(1) DEFAULT 1") + private Boolean available; +} +``` + +**变更脚本生成:** +```bash +# 使用变更脚本生成器 +python3 scripts/generate-change-scripts.py \ + --entity fund-product/src/main/java/com/fundplatform/product/entity/Product.java \ + --version v1.1.0 \ + --description "为产品表添加分类和描述字段" + +# 生成的脚本文件位置(部署目录) +deploy/sql/upgrade/v1.1.0/20240115_add_product_fields.sql +deploy/sql/upgrade/v1.1.0/rollback/20240115_add_product_fields_rollback.sql +``` + +**生成的正向变更脚本 (20240115_add_product_fields.sql):** +```sql +-- ============================================ +-- 变更描述: 为产品表添加分类和描述字段 +-- 变更时间: 2024-01-15 14:30:00 +-- 影响版本: v1.1.0 +-- 影响表: product +-- ============================================ + +-- 1. 添加字段 +ALTER TABLE `product` +ADD COLUMN `category_id` BIGINT COMMENT '分类ID', +ADD COLUMN `description` VARCHAR(500) COMMENT '产品描述', +ADD COLUMN `is_available` TINYINT(1) DEFAULT 1 COMMENT '是否可用'; + +-- 2. 添加索引 +ALTER TABLE `product` +ADD INDEX `idx_category_id` (`category_id`); + +-- 3. 验证变更 +SELECT COUNT(*) as new_columns FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_NAME = 'product' AND COLUMN_NAME IN ('category_id', 'description', 'is_available'); +``` + +**生成的回滚脚本 (20240115_add_product_fields_rollback.sql):** +```sql +-- ============================================ +-- 回滚描述: 撤销产品表字段添加变更 +-- 回滚时间: 2024-01-15 14:30:00 +-- 原版本: v1.1.0 +-- 影响表: product +-- ============================================ + +-- 1. 删除索引 +ALTER TABLE `product` DROP INDEX `idx_category_id`; + +-- 2. 删除字段 +ALTER TABLE `product` +DROP COLUMN `category_id`, +DROP COLUMN `description`, +DROP COLUMN `is_available`; + +-- 3. 验证回滚 +SELECT COUNT(*) as remaining_new_columns FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_NAME = 'product' AND COLUMN_NAME IN ('category_id', 'description', 'is_available'); +``` + +### 场景2: 字段类型变更 + +**实体类变更:** +```java +// 原始:String类型存储状态 +@Column(length = 20) +private String status; + +// 修改为:枚举类型 +@Enumerated(EnumType.STRING) +@Column(length = 20) +private OrderStatus status; + +public enum OrderStatus { + DRAFT, SUBMITTED, APPROVED, REJECTED +} +``` + +**变更脚本:** +```sql +-- 20240120_modify_order_status_type.sql +-- ============================================ +-- 变更描述: 修改订单状态字段类型为枚举 +-- 变更时间: 2024-01-20 +-- ============================================ + +-- 1. 添加新字段 +ALTER TABLE `order` ADD COLUMN `status_enum` VARCHAR(20) COMMENT '订单状态(枚举)'; + +-- 2. 数据迁移 +UPDATE `order` SET `status_enum` = `status` WHERE `status` IN ('DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED'); + +-- 3. 删除旧字段 +ALTER TABLE `order` DROP COLUMN `status`; + +-- 4. 重命名新字段 +ALTER TABLE `order` CHANGE `status_enum` `status` VARCHAR(20) COMMENT '订单状态'; + +-- 5. 添加约束 +ALTER TABLE `order` ADD CONSTRAINT `chk_status_values` +CHECK (`status` IN ('DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED')); +``` + +## 文档同步示例 + +### 场景3: 设计文档更新 + +**更新数据库设计文档:** +```markdown +# product 表结构文档 + +## 表基本信息 +- **表名**: `product` +- **描述**: 产品信息表 +- **引擎**: InnoDB +- **字符集**: utf8mb4 + +## 字段定义 +| 字段名 | 类型 | 允许NULL | 默认值 | 描述 | +|--------|------|----------|--------|------| +| id | BIGINT | NO | AUTO_INCREMENT | 产品ID | +| name | VARCHAR(100) | NO | | 产品名称 | +| price | DECIMAL(10,2) | NO | | 产品价格 | +| category_id | BIGINT | YES | NULL | 分类ID | +| description | VARCHAR(500) | YES | NULL | 产品描述 | +| is_available | TINYINT(1) | YES | 1 | 是否可用 | +| created_time | DATETIME | YES | CURRENT_TIMESTAMP | 创建时间 | +| updated_time | DATETIME | YES | CURRENT_TIMESTAMP | 更新时间 | + +## 索引信息 +| 索引名 | 类型 | 字段 | 描述 | +|--------|------|------|------| +| PRIMARY | PRIMARY | id | 主键索引 | +| idx_category_id | NORMAL | category_id | 分类索引 | + +## 变更历史 +| 日期 | 版本 | 变更描述 | 变更人 | +|------|------|----------|--------| +| 2024-01-15 | v1.1.0 | 添加分类ID、产品描述和可用状态字段 | Zhang San | +| 2024-01-10 | v1.0.0 | 初始版本 | Li Si | +``` + +**API文档更新:** +```markdown +# 产品管理API接口 + +## GET /api/products +获取产品列表 + +### 请求参数 +| 参数名 | 类型 | 必填 | 描述 | +|--------|------|------|------| +| categoryId | Long | 否 | 分类产品筛选 | +| available | Boolean | 否 | 可用性筛选 | + +### 响应示例 +```json +{ + "code": 200, + "data": { + "records": [ + { + "id": 1, + "name": "基金产品A", + "price": 100.50, + "categoryId": 1, + "description": "这是一款优质基金产品", + "available": true, + "createdTime": "2024-01-15T10:30:00" + } + ] + } +} +``` +``` + +## 生产环境部署示例 + +### 场景4: 生产环境变更流程 + +**变更申请表:** +```markdown +# 数据库变更申请 + +## 基本信息 +- **申请人**: 张三 +- **申请时间**: 2024-01-15 15:00 +- **变更类型**: 字段添加 +- **影响系统**: 产品管理系统 +- **预计 downtime**: 5分钟 + +## 变更详情 +- **变更SQL**: src/main/resources/sql/upgrade/v1.1.0/20240115_add_product_fields.sql +- **回滚SQL**: src/main/resources/sql/upgrade/v1.1.0/rollback/20240115_add_product_fields_rollback.sql +- **验证SQL**: src/main/resources/sql/upgrade/v1.1.0/verify/20240115_add_product_fields_verify.sql + +## 风险评估 +- [x] 数据备份已完成 +- [x] 回滚方案已准备 +- [x] 测试环境验证通过 +- [x] 监控告警已设置 + +## 执行计划 +1. 20:00 - 开始变更 +2. 20:01 - 执行备份 +3. 20:02 - 执行变更脚本 +4. 20:03 - 执行验证脚本 +5. 20:04 - 功能测试 +6. 20:05 - 变更完成 +``` + +**生产环境执行脚本:** +```bash +#!/bin/bash +# production-deploy.sh + +set -e + +echo "=== 生产环境数据库变更部署 ===" +echo "变更版本: v1.1.0" +echo "执行时间: $(date)" +echo "" + +# 1. 环境检查 +echo "1. 环境检查..." +if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ]; then + echo "❌ 数据库连接信息不完整" + exit 1 +fi + +# 2. 数据库备份 +echo "2. 执行数据库备份..." +BACKUP_FILE="/backup/proddb_$(date +%Y%m%d_%H%M%S).sql" +mysqldump -h$DB_HOST -u$DB_USER -p$DB_PASSWORD fund_platform > $BACKUP_FILE +echo "✓ 备份完成: $BACKUP_FILE" + +# 3. 执行变更(使用部署目录) +echo "3. 执行变更脚本..." +mysql -h$DB_HOST -u$DB_USER -p$DB_PASSWORD fund_platform < \ + deploy/sql/upgrade/v1.1.0/20240115_add_product_fields.sql + +# 4. 验证变更 +echo "4. 验证变更结果..." +mysql -h$DB_HOST -u$DB_USER -p$DB_PASSWORD fund_platform < \ + deploy/sql/upgrade/v1.1.0/verify/20240115_add_product_fields_verify.sql + +# 5. 记录变更日志 +echo "5. 记录变更日志..." +mysql -h$DB_HOST -u$DB_USER -p$DB_PASSWORD fund_platform << EOF +INSERT INTO db_changelog (version, script_name, execute_time, operator, environment) +VALUES ('v1.1.0', '20240115_add_product_fields.sql', NOW(), '$USER', 'production'); +EOF + +echo "✓ 变更部署完成" +echo "请进行业务功能验证" +``` + +## 自动化验证示例 + +### 验证脚本使用 + +**单个实体验证:** +```bash +# 验证用户实体与数据库脚本同步性 +python3 scripts/validate-entity-db-sync.py \ + fund-user/src/main/java/com/fundplatform/user/entity/User.java \ + fund-user/src/main/resources/sql/init/user.sql + +# 输出示例: +# ✅ 字段一致: id -> id (BIGINT) +# ✅ 字段一致: username -> username (VARCHAR(50)) +# ⚠️ 新增字段: phone (数据库中缺失) +# ❌ 类型不匹配: email长度应为100,当前为50 +``` + +**批量验证:** +```bash +# 验证所有模块的实体同步性 +./scripts/batch-validate-sync.sh + +# 输出示例: +# === 批量验证实体类与数据库脚本同步 === +# 检查: User -> user +# ✅ 实体类与数据库脚本同步一致 +# +# 检查: Order -> fund_order +# ⚠️ 发现3个不一致项 +# ❌ 字段email长度不匹配 +# ⚠️ 缺少字段phone +# ⚠️ 缺少字段description +# +# === 检查完成 === +# 错误: 1 +# 警告: 2 +``` + +### CI集成配置 + +**GitHub Actions配置:** +```yaml +name: Entity-DB Sync Check +on: [push, pull_request] + +jobs: + sync-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install dependencies + run: pip install mysql-connector-python + + - name: Run sync validation + run: | + chmod +x scripts/batch-validate-sync.sh + ./scripts/batch-validate-sync.sh + + - name: Generate change scripts + if: github.event_name == 'pull_request' + run: | + python3 scripts/generate-change-scripts.py --auto-generate + + - name: Upload artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: sync-report + path: sync-report.txt +``` + +**Git Pre-commit Hook:** +```bash +#!/bin/bash +# .git/hooks/pre-commit + +# 检查是否有实体类修改 +if git diff --cached --name-only | grep -q "entity/.*\.java"; then + echo "检测到实体类修改,运行同步检查..." + + # 执行验证 + if ! ./scripts/batch-validate-sync.sh; then + echo "❌ 实体类与数据库脚本不同步,请修复后再提交" + echo "提示:运行 'python3 scripts/generate-sql-from-entity.py ' 生成SQL" + exit 1 + fi + + # 自动生成变更脚本 + echo "生成变更脚本..." + python3 scripts/generate-change-scripts.py --auto-generate + + echo "✅ 同步检查通过" +fi +``` + +这些示例展示了从简单到复杂的各种同步场景,包括变更脚本生成、文档同步和生产环境部署的完整流程。 \ No newline at end of file diff --git a/.qoder/skills/entity-db-sync/reference.md b/.qoder/skills/entity-db-sync/reference.md new file mode 100644 index 0000000..a5c01e3 --- /dev/null +++ b/.qoder/skills/entity-db-sync/reference.md @@ -0,0 +1,545 @@ +# 实体类与数据库脚本同步参考手册 + +## 详细映射规则 + +### 数据类型映射表 + +#### 基本数据类型 +| Java类型 | MySQL类型 | 注解示例 | 说明 | +|----------|-----------|----------|------| +| `String` | `VARCHAR(N)` | `@Column(length=50)` | 默认长度255 | +| `String` | `TEXT` | `@Column(length=2000)` | 长文本 | +| `String` | `LONGTEXT` | `@Column(length=10000)` | 超长文本 | +| `Integer`/`int` | `INT` | `@Column` | 32位整数 | +| `Long`/`long` | `BIGINT` | `@Column` | 64位整数 | +| `Short`/`short` | `SMALLINT` | `@Column` | 16位整数 | +| `Byte`/`byte` | `TINYINT` | `@Column` | 8位整数 | +| `Float`/`float` | `FLOAT` | `@Column` | 单精度浮点 | +| `Double`/`double` | `DOUBLE` | `@Column` | 双精度浮点 | +| `Boolean`/`boolean` | `TINYINT(1)` | `@Column` | 布尔值 | +| `Character`/`char` | `CHAR(1)` | `@Column` | 单字符 | + +#### 数值类型 +| Java类型 | MySQL类型 | 注解示例 | 说明 | +|----------|-----------|----------|------| +| `BigDecimal` | `DECIMAL(M,D)` | `@Column(precision=10, scale=2)` | 精确小数 | +| `BigInteger` | `DECIMAL(M,0)` | `@Column(precision=20)` | 大整数 | + +#### 日期时间类型 +| Java类型 | MySQL类型 | 注解示例 | 说明 | +|----------|-----------|----------|------| +| `LocalDate` | `DATE` | `@Column` | 日期 | +| `LocalTime` | `TIME` | `@Column` | 时间 | +| `LocalDateTime` | `DATETIME` | `@Column` | 日期时间 | +| `Instant` | `TIMESTAMP` | `@Column` | 时间戳 | +| `Year` | `YEAR` | `@Column` | 年份 | + +#### 枚举类型 +```java +// 方式1: 字符串存储 +@Enumerated(EnumType.STRING) +@Column(length = 20) +private UserType userType; + +// 方式2: 整数存储 +@Enumerated(EnumType.ORDINAL) +@Column +private Status status; +``` + +#### 集合类型 +```java +// JSON格式存储 +@JdbcTypeCode(SqlTypes.JSON) +@Column(columnDefinition = "JSON") +private List tags; + +// 或者使用单独的关联表 +@ElementCollection +@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id")) +private Set roles; +``` + +### 注解详细说明 + +#### @Column注解参数 +```java +@Column( + name = "column_name", // 数据库列名 + length = 255, // VARCHAR长度 + nullable = true, // 是否允许NULL + unique = false, // 是否唯一 + precision = 10, // 数字总位数 + scale = 2, // 小数位数 + columnDefinition = "TEXT" // 自定义列定义 +) +``` + +#### @Id相关注解 +```java +// 自增主键 +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long id; + +// UUID主键 +@Id +@GeneratedValue(generator = "uuid2") +@GenericGenerator(name = "uuid2", strategy = "uuid2") +@Column(name = "id", length = 36) +private String id; + +// 复合主键 +@EmbeddedId +private UserRoleId id; +``` + +#### 时间戳注解 +```java +// 创建时间自动设置 +@CreationTimestamp +@Column(name = "created_time", updatable = false) +private LocalDateTime createdTime; + +// 更新时间自动设置 +@UpdateTimestamp +@Column(name = "updated_time") +private LocalDateTime updatedTime; +``` + +## 变更管理工具 + +### 变更脚本生成器 +```python +#!/usr/bin/env python3 +# generate-change-scripts.py + +import os +import re +import datetime +from typing import Dict, List + +class ChangeScriptGenerator: + def __init__(self, project_root: str): + self.project_root = project_root + # 部署目录结构:与开发代码分离 + self.deploy_dir = os.path.join(project_root, "deploy", "sql") + self.upgrade_dir = os.path.join(self.deploy_dir, "upgrade") + self.init_dir = os.path.join(self.deploy_dir, "init") + + def create_version_directory(self, version: str) -> str: + """创建版本目录""" + version_dir = os.path.join(self.upgrade_dir, version) + os.makedirs(version_dir, exist_ok=True) + os.makedirs(os.path.join(version_dir, "rollback"), exist_ok=True) + return version_dir + + def generate_alter_script(self, table_name: str, changes: Dict, version: str) -> str: + """生成ALTER TABLE脚本""" + today = datetime.date.today().strftime('%Y%m%d') + timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # 生成正向变更脚本 + forward_content = f"""-- ============================================ +-- 变更描述: {changes.get('description', '数据库结构变更')} +-- 变更时间: {timestamp} +-- 影响版本: {version} +-- 影响表: {table_name} +-- ============================================ + +""" + + # 添加字段变更 + if 'add_columns' in changes: + forward_content += "-- 1. 添加字段\n" + for column_def in changes['add_columns']: + forward_content += f"ALTER TABLE `{table_name}` ADD COLUMN {column_def};\n" + forward_content += "\n" + + if 'modify_columns' in changes: + forward_content += "-- 2. 修改字段\n" + for column_def in changes['modify_columns']: + forward_content += f"ALTER TABLE `{table_name}` MODIFY COLUMN {column_def};\n" + forward_content += "\n" + + if 'drop_columns' in changes: + forward_content += "-- 3. 删除字段\n" + for column_name in changes['drop_columns']: + forward_content += f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;\n" + forward_content += "\n" + + # 添加索引变更 + if 'add_indexes' in changes: + forward_content += "-- 4. 添加索引\n" + for index_def in changes['add_indexes']: + forward_content += f"ALTER TABLE `{table_name}` ADD {index_def};\n" + forward_content += "\n" + + # 生成回滚脚本 + rollback_content = self.generate_rollback_script(table_name, changes, timestamp, version) + + # 保存脚本文件 + version_dir = self.create_version_directory(version) + forward_filename = f"{today}_{self.sanitize_filename(changes.get('description', 'change'))}.sql" + rollback_filename = f"{today}_{self.sanitize_filename(changes.get('description', 'change'))}_rollback.sql" + + forward_path = os.path.join(version_dir, forward_filename) + rollback_path = os.path.join(version_dir, "rollback", rollback_filename) + + with open(forward_path, 'w', encoding='utf-8') as f: + f.write(forward_content) + + with open(rollback_path, 'w', encoding='utf-8') as f: + f.write(rollback_content) + + return forward_path + + def generate_rollback_script(self, table_name: str, changes: Dict, timestamp: str, version: str) -> str: + """生成回滚脚本""" + rollback_content = f"""-- ============================================ +-- 回滚描述: 撤销 {changes.get('description', '数据库变更')} +-- 回滚时间: {timestamp} +-- 原版本: {version} +-- 影响表: {table_name} +-- ============================================ + +""" + + # 删除索引(逆序) + if 'add_indexes' in changes: + rollback_content += "-- 1. 删除新增的索引\n" + for index_def in reversed(changes['add_indexes']): + index_name = self.extract_index_name(index_def) + if index_name: + rollback_content += f"ALTER TABLE `{table_name}` DROP INDEX `{index_name}`;\n" + rollback_content += "\n" + + # 删除字段(逆序) + if 'add_columns' in changes: + rollback_content += "-- 2. 删除新增的字段\n" + for column_def in reversed(changes['add_columns']): + column_name = self.extract_column_name(column_def) + if column_name: + rollback_content += f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;\n" + rollback_content += "\n" + + # 恢复修改的字段 + if 'modify_columns' in changes: + rollback_content += "-- 3. 恢复修改的字段\n" + # 这里需要保存原始定义才能准确回滚 + rollback_content += "-- TODO: 需要手动添加原始字段定义\n" + rollback_content += "\n" + + return rollback_content + + def sanitize_filename(self, text: str) -> str: + """清理文件名""" + return re.sub(r'[^\w\-_]', '_', text.lower()) + + def extract_column_name(self, column_def: str) -> str: + """从列定义中提取列名""" + match = re.search(r'`([^`]+)`', column_def) + return match.group(1) if match else "" + + def extract_index_name(self, index_def: str) -> str: + """从索引定义中提取索引名""" + match = re.search(r'`([^`]+)`\s*\(', index_def) + return match.group(1) if match else "" + +# 使用示例 +if __name__ == "__main__": + generator = ChangeScriptGenerator("/path/to/project") + + changes = { + 'description': '为用户表添加手机号字段', + 'add_columns': [ + '`phone` VARCHAR(20) COMMENT \'手机号码\'', + '`wechat_id` VARCHAR(50) COMMENT \'微信ID\'' + ], + 'add_indexes': [ + 'INDEX `idx_phone` (`phone`)', + 'INDEX `idx_wechat_id` (`wechat_id`)' + ] + } + + script_path = generator.generate_alter_script('user', changes, 'v1.1.0') + print(f"生成变更脚本: {script_path}") +``` + +### 文档同步工具 +```python +#!/usr/bin/env python3 +# sync-documentation.py + +import os +import json +from typing import Dict, List + +class DocumentationSync: + def __init__(self, project_root: str): + self.project_root = project_root + self.docs_dir = os.path.join(project_root, "docs") + self.db_docs_dir = os.path.join(self.docs_dir, "database") + + def update_table_documentation(self, table_name: str, entity_info: Dict, changes: Dict): + """更新表结构文档""" + doc_file = os.path.join(self.db_docs_dir, f"{table_name}.md") + + # 读取现有文档或创建新文档 + if os.path.exists(doc_file): + with open(doc_file, 'r', encoding='utf-8') as f: + content = f.read() + else: + content = self.create_table_template(table_name) + + # 更新字段信息 + content = self.update_fields_section(content, entity_info['fields']) + + # 添加变更历史 + content = self.add_change_history(content, changes) + + # 保存更新后的文档 + os.makedirs(os.path.dirname(doc_file), exist_ok=True) + with open(doc_file, 'w', encoding='utf-8') as f: + f.write(content) + + def create_table_template(self, table_name: str) -> str: + """创建表文档模板""" + return f"""# {table_name} 表结构文档 + +## 表基本信息 +- **表名**: `{table_name}` +- **描述**: +- **引擎**: InnoDB +- **字符集**: utf8mb4 + +## 字段定义 +| 字段名 | 类型 | 允许NULL | 默认值 | 描述 | +|--------|------|----------|--------|------| +| id | BIGINT | NO | AUTO_INCREMENT | 主键ID | + +## 索引信息 +| 索引名 | 类型 | 字段 | 描述 | +|--------|------|------|------| +| PRIMARY | PRIMARY | id | 主键索引 | + +## 变更历史 +| 日期 | 版本 | 变更描述 | 变更人 | +|------|------|----------|--------| +""" + + def update_fields_section(self, content: str, fields: List[Dict]) -> str: + """更新字段定义部分""" + # 找到字段定义表格的位置并替换 + lines = content.split('\n') + in_fields_section = False + field_lines = [] + + for line in lines: + if line.strip() == '## 字段定义': + in_fields_section = True + field_lines.append(line) + continue + + if in_fields_section and line.startswith('|') and '字段名' in line: + # 保留表头 + field_lines.append(line) + continue + + if in_fields_section and line.startswith('|----'): + # 保留分隔线 + field_lines.append(line) + continue + + if in_fields_section and line.startswith('|') and line.count('|') >= 5: + # 跳过旧的字段行 + continue + + if in_fields_section and not line.strip(): + # 结束字段部分 + in_fields_section = False + # 添加新的字段定义 + for field in fields: + field_lines.append(self.format_field_row(field)) + field_lines.append('') + + field_lines.append(line) + + return '\n'.join(field_lines) + + def format_field_row(self, field: Dict) -> str: + """格式化字段行""" + nullable = 'YES' if field.get('nullable', True) else 'NO' + default_val = field.get('default', '') + description = field.get('comment', '') + + return f"| {field['name']} | {field['type']} | {nullable} | {default_val} | {description} |" + + def add_change_history(self, content: str, changes: Dict) -> str: + """添加变更历史""" + import datetime + today = datetime.date.today().strftime('%Y-%m-%d') + + change_line = f"| {today} | {changes.get('version', 'unknown')} | {changes.get('description', '')} | {changes.get('author', 'system')} |" + + # 在变更历史部分添加新行 + lines = content.split('\n') + for i, line in enumerate(lines): + if '| 日期 | 版本 | 变更描述 | 变更人 |' in line: + # 在表头后插入新行 + lines.insert(i + 2, change_line) + break + + return '\n'.join(lines) + +# 配置文件示例 +CONFIG = { + "documentation": { + "enabled": True, + "output_dir": "docs/database", + "auto_update": True + }, + "change_scripts": { + "upgrade_dir": "src/main/resources/sql/upgrade", + "version_pattern": "v{major}.{minor}.{patch}", + "naming_convention": "{date}_{description}.sql" + } +} +``` + +## 生产环境部署规范 + +### 变更脚本目录结构 +``` +project/ +├── deploy/ # 部署相关文件(与开发代码分离) +│ └── sql/ +│ ├── init/ # 初始部署脚本 +│ │ ├── v1.0.0/ +│ │ │ ├── 01_create_tables.sql +│ │ │ ├── 02_insert_init_data.sql +│ │ │ └── README.md +│ │ └── v1.1.0/ +│ │ └── 01_upgrade_v1.1.0.sql +│ └── upgrade/ # 增量变更脚本 +│ ├── v1.1.0/ +│ │ ├── 20240115_add_user_phone.sql +│ │ ├── 20240115_add_user_phone_rollback.sql +│ │ └── changelog.md +│ ├── v1.2.0/ +│ │ ├── 20240201_modify_order_amount.sql +│ │ ├── 20240201_modify_order_amount_rollback.sql +│ │ └── changelog.md +│ └── current/ # 当前开发中的变更 +│ └── draft_changes/ +└── src/main/resources/ # 开发代码目录(保持独立) +``` + +# 生产环境部署规范 + +### 变更脚本执行流程 +```bash +#!/bin/bash +# deploy-database-changes.sh + +ENVIRONMENT=${1:-"test"} +VERSION=${2:-"latest"} +BACKUP_FIRST=${3:-"true"} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="${SCRIPT_DIR}/../.." +# 使用部署目录 +DEPLOY_SQL_DIR="${PROJECT_ROOT}/deploy/sql" + +echo "=== 数据库变更部署 ===" +echo "环境: ${ENVIRONMENT}" +echo "版本: ${VERSION}" +echo "备份: ${BACKUP_FIRST}" +echo "" + +# 1. 数据库备份 +if [ "${BACKUP_FIRST}" = "true" ]; then + echo "1. 执行数据库备份..." + BACKUP_FILE="/tmp/db_backup_$(date +%Y%m%d_%H%M%S).sql" + mysqldump -h${DB_HOST} -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > "${BACKUP_FILE}" + echo "备份完成: ${BACKUP_FILE}" + echo "" +fi + +# 2. 确定要执行的脚本 +if [ "${VERSION}" = "latest" ]; then + # 执行所有未应用的变更 + CHANGE_SCRIPTS=$(find "${DEPLOY_SQL_DIR}/upgrade" -name "*.sql" ! -name "*_rollback.sql" | sort) +else + # 执行指定版本的变更 + CHANGE_SCRIPTS=$(find "${DEPLOY_SQL_DIR}/upgrade/${VERSION}" -name "*.sql" ! -name "*_rollback.sql" | sort) +fi +``` +# 3. 执行变更脚本 +echo "2. 执行变更脚本..." +for script in ${CHANGE_SCRIPTS}; do + echo "执行: $(basename ${script})" + + # 记录执行日志 + mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < "${script}" + + if [ $? -eq 0 ]; then + echo "✓ 执行成功" + # 记录到变更历史表 + echo "INSERT INTO db_changelog (script_name, execute_time, environment) VALUES ('$(basename ${script})', NOW(), '${ENVIRONMENT}');" | \ + mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} + else + echo "✗ 执行失败" + exit 1 + fi +done + +# 4. 验证变更 +echo "" +echo "3. 验证变更结果..." +# 执行验证脚本 +VERIFY_SCRIPT="${DEPLOY_SQL_DIR}/upgrade/${VERSION}/verify_changes.sql" +if [ -f "${VERIFY_SCRIPT}" ]; then + mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < "${VERIFY_SCRIPT}" +fi + +echo "数据库变更部署完成!" +``` + +### 变更风险评估模板 +```markdown +# 数据库变更风险评估表 + +## 基本信息 +- **变更编号**: DB-20240115-001 +- **变更类型**: 字段添加/修改/删除 +- **影响表**: user +- **预计执行时间**: 5分钟 +- **回滚时间**: 2分钟 + +## 影响评估 +### 业务影响 +- [ ] 用户注册/登录功能 +- [ ] 数据查询性能 +- [ ] 报表统计准确性 +- [ ] 第三方系统对接 + +### 技术风险 +- [ ] 数据一致性风险 +- [ ] 性能下降风险 +- [ ] 锁表时间过长 +- [ ] 空间不足风险 + +## 预防措施 +- [ ] 执行前完整备份 +- [ ] 低峰期执行变更 +- [ ] 准备回滚方案 +- [ ] 安排专人监控 + +## 应急预案 +- **回滚步骤**: [详细回滚操作步骤] +- **联系人员**: [紧急联系人列表] +- **监控指标**: [关键监控项] +``` + +这份参考手册提供了完整的实体类与数据库脚本同步的技术细节、工具脚本和生产环境部署规范。 \ No newline at end of file diff --git a/.qoder/skills/maven-assembly/SKILL.md b/.qoder/skills/maven-assembly/SKILL.md new file mode 100644 index 0000000..0418773 --- /dev/null +++ b/.qoder/skills/maven-assembly/SKILL.md @@ -0,0 +1,441 @@ +--- +name: maven-assembly +description: 针对资金服务平台项目的Maven Assembly打包技能,专门处理env.properties统一配置和service.properties个性化配置的合并打包,生成包含bin/lib/conf目录结构的tar.gz部署包,支持自动化的构建脚本。使用场景:当需要生成或修改项目的Assembly配置文件、创建部署包、处理配置文件合并、进行多服务打包时自动触发。 +--- + +# 资金服务平台 Maven Assembly 打包技能 + +## 触发条件 + +当出现以下情况时,自动应用此技能: + +1. **配置文件管理相关**: + - 需要创建或修改 `env.properties` 统一配置文件 + - 需要创建或修改各服务的 `service.properties` 个性化配置 + - 需要处理配置文件的合并和加载顺序 + +2. **Assembly配置相关**: + - 需要生成项目的 `assembly.xml` 配置文件 + - 需要配置 Maven Assembly 插件 + - 需要设置打包目录结构和文件包含规则 + +3. **构建打包相关**: + - 需要创建或修改批量构建脚本 + - 需要生成包含 bin/lib/conf 目录结构的部署包 + - 需要处理多服务模块的统一打包 + +4. **部署验证相关**: + - 需要验证部署包的完整性和正确性 + - 需要检查配置文件的必需参数 + - 需要测试启动脚本的配置加载机制 + +5. **部署目录管理相关**: + - 需要将所有打包产物统一存储在项目根目录的 `deploy/` 目录下 + - 需要维护部署包的版本管理和清理机制 + - 需要确保部署目录与开发代码的物理分离 + +## 项目架构概述 + +本技能专为资金服务平台定制,采用**统一配置 + 个性化配置**分离架构,并实现**开发代码与部署产物物理分离**: + +``` +fundplatform/ +├── deploy/ # 部署产物目录(所有打包文件统一存放) +│ ├── fund-gateway.tar.gz +│ ├── fund-sys.tar.gz +│ ├── fund-cust.tar.gz +│ └── ... 其他服务部署包 +├── scripts/ # 统一脚本和配置目录 +│ ├── env.properties # 统一配置(所有服务共用) +│ ├── start.sh # 启动脚本 +│ ├── stop.sh # 停止脚本 +│ └── build-services.sh # 自动构建脚本 +├── fund-sys/ # 服务源代码目录 +│ └── src/main/resources/ +│ └── service.properties # 个性化配置(每服务独立) +├── fund-gateway/ +│ └── src/main/resources/ +│ └── service.properties +└── ... 其他服务模块 +``` + +**核心实践原则**: +- 所有部署打包文件统一存储在项目根目录的 `deploy/` 目录下 +- 开发代码与部署产物物理分离,便于版本管理和CI/CD集成 +- 通过 `services-build.sh` 脚本自动收集各服务构建产物到统一部署目录 + +## 配置文件管理 + +### 统一配置 (env.properties) +**位置**: `scripts/env.properties` +**用途**: 所有服务共用的基础配置 +**包含内容**: +- Nacos配置 +- Redis配置 +- 数据库连接池配置 +- 日志配置 +- 腾讯云COS配置 +- 多租户路由配置 + +### 个性化配置 (service.properties) +**位置**: 各服务 `src/main/resources/service.properties` +**用途**: 服务独立的个性化参数 +**必须包含**: +```properties +APP_NAME=fund-sys # 服务名称 +MAIN_CLASS=com.xxx.App # 主启动类 +INSTANCE_NAME= # 实例名称(可选) +TENANT_ID= # 租户ID(可选) +``` + +## 核心目录结构 + +每个服务打包后应包含: + +``` +服务名/ +├── bin/ # 可执行脚本(来自scripts目录) +│ ├── start.sh +│ ├── stop.sh +│ ├── restart.sh +│ └── status.sh +├── lib/ # JAR文件和依赖 +│ ├── 服务名-version.jar +│ └── 依赖.jar +└── conf/ # 配置文件 + ├── env.properties # 统一配置(来自scripts/env.properties) + ├── service.properties # 个性化配置(来自服务src/main/resources) + ├── application.yml + └── logback-spring.xml +``` + +## Assembly配置详解 + +### pom.xml配置 +```xml + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + src/main/assembly/assembly.xml + + ${project.artifactId} + + + + make-assembly + package + + single + + + + +``` + +### assembly.xml配置 +```xml + + dist + + tar.gz + + true + + + + + ${project.basedir}/../../scripts + bin + + start.sh + stop.sh + restart.sh + status.sh + + 0755 + + + + + ${project.basedir}/../../scripts + conf + + env.properties + + + + + src/main/resources + conf + + service.properties + application.yml + application-*.yml + logback-spring.xml + + + + + + + + lib + true + runtime + + + +``` + +## 自动构建脚本 + +### services-build.sh (项目根目录) +```bash +#!/bin/bash +# 自动构建所有微服务,统一输出到deploy目录 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="${SCRIPT_DIR}" +DEPLOY_DIR="${PROJECT_ROOT}/deploy" +SERVICES=( + "fund-gateway" + "fund-sys" + "fund-cust" + "fund-proj" + "fund-exp" + "fund-receipt" + "fund-report" + "fund-req" + "fund-file" +) + +# 确保部署目录存在 +mkdir -p "${DEPLOY_DIR}" + +# 清理旧的部署包(可选) +echo "清理旧部署包..." +rm -f "${DEPLOY_DIR}"/*.tar.gz + +build_service() { + local service=$1 + local service_dir="${PROJECT_ROOT}/${service}" + + echo "=== 构建服务: ${service} ===" + + if [ ! -d "${service_dir}" ]; then + echo "警告: 服务目录不存在 ${service_dir}" + return 1 + fi + + cd "${service_dir}" + + # Maven构建 + mvn clean package -DskipTests + + # 检查生成的tar.gz文件并复制到统一部署目录 + local tar_file=$(find target -name "${service}*.tar.gz" | head -1) + if [ -n "${tar_file}" ]; then + cp "${tar_file}" "${DEPLOY_DIR}/" + echo "✓ ${service} 构建完成: $(basename ${tar_file})" + else + echo "✗ ${service} 构建失败:未找到tar.gz文件" + return 1 + fi +} + +# 主构建流程 +echo "开始批量构建微服务..." +echo "项目根目录: ${PROJECT_ROOT}" +echo "部署输出目录: ${DEPLOY_DIR}" +echo "" + +for service in "${SERVICES[@]}"; do + build_service "${service}" || { + echo "构建失败,退出" + exit 1 + } +done + +echo "" +echo "=== 构建完成 ===" +echo "生成的部署包:" +ls -lh "${DEPLOY_DIR}"/*.tar.gz +echo "" +echo "部署目录大小: $(du -sh "${DEPLOY_DIR}" | cut -f1)" +``` + +## 启动脚本增强版 + +### start.sh (支持配置文件加载) +```bash +#!/bin/bash +# 增强版启动脚本,自动加载env.properties和service.properties + +APP_HOME=$(cd "$(dirname "$0")/.." && pwd) +PID_FILE="/tmp/${INSTANCE_NAME}.pid" + +# 配置文件加载函数 +load_properties() { + local file=$1 + if [[ -f "$file" ]]; then + while IFS='=' read -r key value; do + [[ -z "$key" || "$key" =~ ^# ]] && continue + export "$key=$value" + done < "$file" + fi +} + +# 1. 加载统一配置 +load_properties "${APP_HOME}/conf/env.properties" + +# 2. 加载服务个性化配置(覆盖统一配置) +load_properties "${APP_HOME}/conf/service.properties" + +# 3. 设置默认值 +APP_NAME=${APP_NAME:-"unknown"} +INSTANCE_NAME=${INSTANCE_NAME:-${APP_NAME}} +TENANT_ID=${TENANT_ID:-""} + +# 4. 查找主JAR文件 +JAR_FILE=$(find "${APP_HOME}/lib" -name "${APP_NAME}*.jar" | head -1) +if [[ -z "$JAR_FILE" ]]; then + echo "错误: 未找到JAR文件 ${APP_NAME}*.jar" + exit 1 +fi + +# 5. 构建JVM参数 +JAVA_OPTS="-Xms256m -Xmx512m" +JAVA_OPTS="$JAVA_OPTS -Dspring.config.location=${APP_HOME}/conf/" +JAVA_OPTS="$JAVA_OPTS -Dlogging.config=${APP_HOME}/conf/logback-spring.xml" + +# 6. 启动应用 +echo "启动服务: ${INSTANCE_NAME}" +echo "JAR文件: $(basename $JAR_FILE)" +echo "租户ID: ${TENANT_ID:-'共享实例'}" + +nohup java $JAVA_OPTS -jar "$JAR_FILE" > /dev/null 2>&1 & +echo $! > "${PID_FILE}" +echo "${INSTANCE_NAME} 启动成功,PID: $(cat ${PID_FILE})" +``` + +## 配置文件验证 + +### 配置检查脚本 +```bash +#!/bin/bash +# validate-config.sh - 验证配置文件完整性 + +APP_HOME=$(cd "$(dirname "$0")/.." && pwd) + +echo "=== 配置文件验证 ===" + +# 检查必需文件 +REQUIRED_FILES=( + "conf/env.properties" + "conf/service.properties" + "conf/application.yml" + "lib/${APP_NAME}*.jar" +) + +for file_pattern in "${REQUIRED_FILES[@]}"; do + if ls "${APP_HOME}/${file_pattern}" >/dev/null 2>&1; then + echo "✓ ${file_pattern}" + else + echo "✗ ${file_pattern} (缺失)" + exit 1 + fi +done + +# 验证service.properties必需参数 +SERVICE_CONF="${APP_HOME}/conf/service.properties" +REQUIRED_PARAMS=("APP_NAME" "MAIN_CLASS") + +for param in "${REQUIRED_PARAMS[@]}"; do + if grep -q "^${param}=" "${SERVICE_CONF}"; then + value=$(grep "^${param}=" "${SERVICE_CONF}" | cut -d'=' -f2) + echo "✓ ${param}=${value}" + else + echo "✗ 缺少必需参数: ${param}" + exit 1 + fi +done + +echo "配置验证通过!" +``` + +## 打包命令 + +```bash +# 构建单个服务 +cd fund-sys && mvn clean package + +# 批量构建所有服务 +cd /项目根目录 && ./scripts/services-build.sh + +# 跳过测试快速构建 +mvn clean package -DskipTests + +# 验证打包结果 +tar -tzf target/fund-sys.tar.gz | head -10 +``` + +## 部署验证清单 + +构建完成后验证: + +```bash +#!/bin/bash +# deployment-check.sh + +TAR_FILE=$1 + +echo "=== 部署包验证 ===" +echo "包文件: ${TAR_FILE}" + +# 结构验证 +echo "目录结构:" +tar -tzf "${TAR_FILE}" | grep -E "^(bin/|lib/|conf/)" | sort + +# 关键文件验证 +echo "必需文件检查:" +REQUIRED=( + "bin/start.sh" + "bin/stop.sh" + "conf/env.properties" + "conf/service.properties" +) + +for file in "${REQUIRED[@]}"; do + if tar -tzf "${TAR_FILE}" | grep -q "^${file}$"; then + echo "✓ ${file}" + else + echo "✗ ${file}" + exit 1 + fi +done + +# 权限验证 +echo "脚本权限检查:" +SCRIPTS=$(tar -tzf "${TAR_FILE}" | grep "^bin/.*\.sh$") +for script in ${SCRIPTS}; do + echo "✓ ${script} (可执行)" +done + +echo "验证完成!" +``` + +## 最佳实践 + +1. **配置分离**: 统一配置放`scripts/env.properties`,个性化配置放各服务`service.properties` +2. **构建脚本**: 使用统一的`services-build.sh`批量构建 +3. **版本管理**: 在`service.properties`中明确指定`APP_NAME`和`MAIN_CLASS` +4. **多租户支持**: 通过`TENANT_ID`参数支持共享实例和VIP专属实例 +5. **权限控制**: 确保bin目录脚本具有可执行权限(755) +6. **验证机制**: 构建后使用验证脚本检查包完整性 +7. **部署目录管理**: 所有部署包统一存储在项目根目录`deploy/`下,实现开发与部署物理分离 +8. **CI/CD集成**: 部署目录结构便于自动化部署和版本发布管理 \ No newline at end of file diff --git a/.qoder/skills/maven-assembly/examples.md b/.qoder/skills/maven-assembly/examples.md new file mode 100644 index 0000000..f493cac --- /dev/null +++ b/.qoder/skills/maven-assembly/examples.md @@ -0,0 +1,610 @@ +# 资金服务平台 Assembly 使用示例 + +## 基础配置示例 + +### 1. 服务模块标准配置 + +**目录结构(包含部署目录管理):** +``` +fundplatform/ +├── deploy/ # 部署产物目录(核心实践) +│ ├── fund-gateway.tar.gz # 网关服务部署包 +│ ├── fund-sys.tar.gz # 系统服务部署包 +│ └── ... 其他服务部署包 +├── scripts/ # 统一脚本和配置 +│ ├── env.properties # 统一配置文件 +│ ├── services-build.sh # 批量构建脚本 +│ └── start.sh # 通用启动脚本 +├── fund-sys/ # 服务源代码 +│ ├── src/ +│ │ ├── main/ +│ │ │ ├── assembly/ +│ │ │ │ └── assembly.xml +│ │ │ └── resources/ +│ │ │ ├── service.properties +│ │ │ ├── application.yml +│ │ │ └── logback-spring.xml +│ │ └── test/ +│ ├── pom.xml +│ └── target/ +│ └── fund-sys.tar.gz # 临时构建产物 +└── ... 其他服务模块 +``` + +**核心实践要点**: +- 所有部署包统一存储在项目根目录的 `deploy/` 目录下 +- 开发代码与部署产物物理分离,便于版本管理和CI/CD集成 +- 通过 `scripts/services-build.sh` 脚本自动收集各服务构建产物 + +**pom.xml配置:** +```xml + + + 4.0.0 + + + com.fundplatform + fund-platform + 1.0.0 + + + fund-sys + jar + + + + com.fundplatform.sys.SysApplication + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ${main.class} + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + src/main/assembly/assembly.xml + + ${project.artifactId} + + + + + +``` + +**service.properties配置:** +```properties +# ============================================ +# fund-sys 服务配置 +# ============================================ + +APP_NAME=fund-sys +MAIN_CLASS=com.fundplatform.sys.SysApplication +INSTANCE_NAME=fund-sys +TENANT_ID= + +# 可选配置 +SERVER_PORT=8100 +``` + +### 2. Assembly Descriptor配置 + +**src/main/assembly/assembly.xml:** +```xml + + dist + + tar.gz + + true + + + + + ${project.basedir}/../../scripts + bin + + start.sh + stop.sh + restart.sh + status.sh + + 0755 + + + + + ${project.basedir}/../../scripts + conf + + env.properties + + + + + + src/main/resources + conf + + service.properties + application.yml + logback-spring.xml + + + + + + + lib + true + runtime + + + +``` + +## 实际应用场景 + +### 场景1: 单服务构建与部署 + +**构建命令:** +```bash +# 进入服务目录 +cd fund-sys + +# 清理并构建 +mvn clean package -DskipTests + +# 验证构建结果 +ls -lh target/fund-sys.tar.gz + +# 查看包内容 +tar -tzf target/fund-sys.tar.gz | head -10 +``` + +**部署验证:** +```bash +# 解压部署包 +mkdir -p /opt/deploy/fund-sys +tar -xzf target/fund-sys.tar.gz -C /opt/deploy/ + +# 验证配置文件 +cat /opt/deploy/fund-sys/conf/service.properties +cat /opt/deploy/fund-sys/conf/env.properties + +# 启动服务 +cd /opt/deploy/fund-sys +./bin/start.sh +``` + +### 场景2: 批量构建所有服务(部署目录管理实践) + +**创建批量构建脚本 (scripts/services-build.sh):** +```bash +#!/bin/bash +# 资金服务平台批量构建脚本 - 统一输出到deploy目录 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="${SCRIPT_DIR}" +DEPLOY_DIR="${PROJECT_ROOT}/deploy" + +# 服务列表 +SERVICES=( + "fund-gateway" + "fund-sys" + "fund-cust" + "fund-proj" + "fund-exp" + "fund-receipt" + "fund-report" + "fund-req" + "fund-file" +) + +# 部署目录初始化 +initialize_deploy_directory() { + echo "=== 部署目录初始化 ===" + + # 创建部署目录 + mkdir -p "${DEPLOY_DIR}" + echo "✓ 部署目录: ${DEPLOY_DIR}" + + # 显示当前部署包 + echo "当前部署包:" + if ls "${DEPLOY_DIR}"/*.tar.gz >/dev/null 2>&1; then + ls -lh "${DEPLOY_DIR}"/*.tar.gz + else + echo " (无)" + fi + + # 询问是否清理旧包 + echo "" + read -p "是否清理旧的部署包? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm -f "${DEPLOY_DIR}"/*.tar.gz + echo "✓ 已清理旧部署包" + fi + echo "" +} + +build_service() { + local service=$1 + local service_dir="${PROJECT_ROOT}/${service}" + + echo "=== 构建服务: ${service} ===" + + if [ ! -d "${service_dir}" ]; then + echo "服务目录不存在: ${service_dir}" + return 1 + fi + + cd "${service_dir}" + + # Maven构建 + mvn clean package -DskipTests -q + + # 复制到统一部署目录 + local tar_file=$(find target -name "${service}*.tar.gz" | head -1) + if [ -n "${tar_file}" ]; then + cp "${tar_file}" "${DEPLOY_DIR}/" + local file_size=$(du -h "${tar_file}" | cut -f1) + echo "✓ ${service} 构建完成 (${file_size})" + echo " 输出到: ${DEPLOY_DIR}/$(basename ${tar_file})" + else + echo "✗ ${service} 构建失败" + return 1 + fi +} + +# 主构建流程 +main() { + echo "=========================================" + echo " 资金服务平台批量构建" + echo " 部署目录: ${DEPLOY_DIR}" + echo "=========================================" + + # 初始化部署目录 + initialize_deploy_directory + + local success_count=0 + local fail_count=0 + + # 批量构建 + for service in "${SERVICES[@]}"; do + if build_service "${service}"; then + ((success_count++)) + else + ((fail_count++)) + fi + echo "" + done + + echo "=========================================" + echo "构建完成统计:" + echo " 成功: ${success_count}" + echo " 失败: ${fail_count}" + echo "" + + if [ ${fail_count} -eq 0 ]; then + echo "✓ 所有服务构建成功!" + echo "" + echo "生成的部署包:" + ls -lh "${DEPLOY_DIR}"/*.tar.gz + echo "" + echo "部署目录总大小: $(du -sh "${DEPLOY_DIR}" | cut -f1)" + else + echo "✗ ${fail_count}个服务构建失败" + exit 1 + fi +} + +main "$@" +``` + +**使用方法:** +```bash +# 给脚本执行权限 +chmod +x scripts/services-build.sh + +# 执行批量构建 +./scripts/services-build.sh + +# 构建完成后查看部署目录 +ls -lh deploy/ + +# 验证部署包完整性 +scripts/validate-deployment.sh +``` + +**部署目录管理优势**: +1. **统一管理**:所有服务部署包集中存储在 `deploy/` 目录 +2. **版本控制**:便于CI/CD流水线统一处理部署包 +3. **权限分离**:开发代码与部署产物物理分离 +4. **清理维护**:易于批量清理旧版本部署包 + +**使用方法:** +```bash +# 给脚本执行权限 +chmod +x scripts/services-build.sh + +# 执行批量构建 +./scripts/services-build.sh +``` + +### 场景3: 多租户实例部署 + +**共享实例配置:** +```properties +# fund-sys/src/main/resources/service.properties +APP_NAME=fund-sys +MAIN_CLASS=com.fundplatform.sys.SysApplication +INSTANCE_NAME=fund-sys-shared +TENANT_ID= +``` + +**VIP专属实例配置:** +```properties +# fund-sys-vip001/src/main/resources/service.properties +APP_NAME=fund-sys +MAIN_CLASS=com.fundplatform.sys.SysApplication +INSTANCE_NAME=fund-sys-vip001 +TENANT_ID=vip001 +``` + +**部署脚本支持多实例:** +```bash +#!/bin/bash +# multi-instance-deploy.sh + +INSTANCE_TYPE=${1:-"shared"} # shared 或 vip001 +SERVICE_NAME="fund-sys" + +case "${INSTANCE_TYPE}" in + shared) + CONFIG_SUFFIX="" + DEPLOY_PATH="/opt/apps/${SERVICE_NAME}-shared" + ;; + vip*) + CONFIG_SUFFIX="-${INSTANCE_TYPE}" + DEPLOY_PATH="/opt/apps/${SERVICE_NAME}-${INSTANCE_TYPE}" + ;; + *) + echo "未知实例类型: ${INSTANCE_TYPE}" + exit 1 + ;; +esac + +# 构建特定实例 +cd "${SERVICE_NAME}${CONFIG_SUFFIX}" +mvn clean package -DskipTests + +# 部署 +mkdir -p "${DEPLOY_PATH}" +tar -xzf target/${SERVICE_NAME}*.tar.gz -C "${DEPLOY_PATH}" + +# 启动 +${DEPLOY_PATH}/bin/start.sh +``` + +## 高级配置示例 + +### 场景4: 环境差异化配置 + +**开发环境配置 (dev-env.properties):** +```properties +# scripts/dev-env.properties +NACOS_SERVER_ADDR=dev-nacos:8848 +REDIS_HOST=dev-redis +REDIS_PASSWORD=dev-password +LOG_LEVEL_ROOT=DEBUG +``` + +**生产环境配置 (prod-env.properties):** +```properties +# scripts/prod-env.properties +NACOS_SERVER_ADDR=prod-nacos:8848 +REDIS_HOST=prod-redis +REDIS_PASSWORD=prod-password +LOG_LEVEL_ROOT=INFO +``` + +**Profile配置:** +```xml + + + dev + + true + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + src/main/assembly/dev-assembly.xml + + + + + + + + + prod + + + + org.apache.maven.plugins + maven-assembly-plugin + + + src/main/assembly/prod-assembly.xml + + + + + + + +``` + +### 场景5: 条件配置打包 + +**assembly.xml支持条件包含:** +```xml + + conditional-dist + + tar.gz + + + + + + ${project.basedir}/../../scripts + bin + + start.sh + stop.sh + + 0755 + + + + + ${project.basedir}/../../scripts/${env.name} + conf + + env.properties + + + + + + src/main/security + security + + * + + + + +``` + +## 构建验证示例 + +### 自动化验证脚本 + +**创建验证脚本 (scripts/validate-build.sh):** +```bash +#!/bin/bash +# 构建结果验证脚本 + +validate_service() { + local service=$1 + local tar_file="target/${service}.tar.gz" + + echo "验证 ${service}..." + + # 检查文件存在 + if [ ! -f "${tar_file}" ]; then + echo "✗ 未找到部署包: ${tar_file}" + return 1 + fi + + # 检查包结构 + local required_files=( + "bin/start.sh" + "bin/stop.sh" + "conf/env.properties" + "conf/service.properties" + "lib/${service}*.jar" + ) + + for file in "${required_files[@]}"; do + if ! tar -tzf "${tar_file}" | grep -q "${file}"; then + echo "✗ 缺失文件: ${file}" + return 1 + fi + done + + echo "✓ ${service} 验证通过" + return 0 +} + +# 验证所有服务 +SERVICES=("fund-gateway" "fund-sys" "fund-cust" "fund-proj" + "fund-exp" "fund-receipt" "fund-report" "fund-req" "fund-file") + +for service in "${SERVICES[@]}"; do + if [ -d "${service}" ]; then + cd "${service}" + validate_service "${service}" || exit 1 + cd .. + fi +done + +echo "所有服务验证完成!" +``` + +### 部署前检查清单 + +```bash +#!/bin/bash +# pre-deployment-check.sh + +echo "=== 部署前检查清单 ===" + +# 1. 检查配置文件完整性 +echo "1. 配置文件检查:" +for service in fund-*; do + if [ -d "${service}" ]; then + conf_file="${service}/src/main/resources/service.properties" + if [ -f "${conf_file}" ]; then + echo "✓ ${service}: service.properties 存在" + # 检查必需参数 + if grep -q "APP_NAME=" "${conf_file}" && grep -q "MAIN_CLASS=" "${conf_file}"; then + echo "✓ ${service}: 必需参数完整" + else + echo "✗ ${service}: 缺少必需参数" + fi + else + echo "✗ ${service}: 缺少 service.properties" + fi + fi +done + +# 2. 检查统一配置 +echo "2. 统一配置检查:" +if [ -f "scripts/env.properties" ]; then + echo "✓ scripts/env.properties 存在" +else + echo "✗ 缺少统一配置文件" +fi + +# 3. 检查构建脚本 +echo "3. 构建脚本检查:" +if [ -f "scripts/services-build.sh" ]; then + echo "✓ 批量构建脚本存在" +else + echo "✗ 缺少批量构建脚本" +fi + +echo "检查完成!" +``` + +这些示例展示了资金服务平台Assembly配置的各种实际应用场景,涵盖了从基础配置到高级特性的完整使用方式。 \ No newline at end of file diff --git a/.qoder/skills/maven-assembly/reference.md b/.qoder/skills/maven-assembly/reference.md new file mode 100644 index 0000000..0788828 --- /dev/null +++ b/.qoder/skills/maven-assembly/reference.md @@ -0,0 +1,658 @@ +# 资金服务平台 Assembly 配置参考 + +## 项目专用配置详解 + +### 部署目录管理实践 + +**核心原则**:所有部署打包文件统一存储在项目根目录的 `deploy/` 目录下,实现开发代码与部署产物的物理分离。 + +``` +fundplatform/ +├── deploy/ # 部署产物目录(核心实践) +│ ├── fund-gateway.tar.gz # 网关服务部署包 +│ ├── fund-sys.tar.gz # 系统服务部署包 +│ ├── fund-cust.tar.gz # 客户服务部署包 +│ └── ... 其他服务部署包 +├── scripts/ # 统一脚本和配置 +└── 各服务源代码目录 # 开发代码(与部署分离) +``` + +**实践优势**: +- **物理分离**:开发代码与部署产物完全隔离 +- **版本管理**:便于CI/CD流水线统一管理部署包 +- **权限控制**:可以对部署目录设置不同的访问权限 +- **清理维护**:易于批量清理旧版本部署包 + +### 统一配置文件管理 + +#### scripts/env.properties 配置 +这是所有服务共享的统一配置文件,包含基础设施配置: + +```properties +# ============================================ +# 环境变量配置文件(所有服务共用) +# ============================================ + +# Nacos配置 +NACOS_SERVER_ADDR=localhost:8848 +NACOS_NAMESPACE=fund-platform +NACOS_GROUP=DEFAULT_GROUP +NACOS_USERNAME=nacos +NACOS_PASSWORD=nacos + +# Redis配置 +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=zjf@123456 +REDIS_DATABASE=0 + +# 数据库连接池 +HIKARI_MINIMUM_IDLE=5 +HIKARI_CONNECTION_TIMEOUT=30000 + +# 日志配置 +LOG_HOME=/datacfs/applogs +LOG_LEVEL_ROOT=INFO +LOG_LEVEL_APP=DEBUG + +# 多租户配置 +TENANT_ROUTING_ENABLED=true +DEFAULT_TENANT_ID=1 + +# 腾讯云COS +COS_ENABLED=true +COS_SECRET_ID=your-secret-id +COS_SECRET_KEY=your-secret-key +``` + +#### 服务个性化配置 (service.properties) +每个服务必须包含的最小配置: + +```properties +# ============================================ +# 服务个性化配置 +# ============================================ + +# 必需参数 +APP_NAME=fund-sys # 服务名称(必须) +MAIN_CLASS=com.fundplatform.sys.SysApplication # 主启动类(必须) + +# 可选参数 +INSTANCE_NAME= # 实例名称(默认等于APP_NAME) +TENANT_ID= # 租户ID(空=共享实例) +SERVER_PORT=8100 # 服务端口(可选) +``` + +### 完整的pom.xml插件配置 + +```xml + + + + + org.springframework.boot + spring-boot-maven-plugin + + ${main.class} + ZIP + + + + + repackage + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + src/main/assembly/assembly.xml + + ${project.artifactId} + false + + + + make-assembly + package + + single + + + + + + + + + + ${service.main.class} + +``` + +### 项目级Assembly Descriptor + +```xml + + dist + + tar.gz + + true + + + + + ${project.basedir}/../../scripts + bin + + start.sh + stop.sh + restart.sh + status.sh + + 0755 + 0755 + + + + + ${project.basedir}/../../scripts + conf + + env.properties + + 0644 + + + + + src/main/resources + conf + + service.properties + application.yml + application-*.yml + logback-spring.xml + + 0644 + + + + + + lib + true + true + runtime + 0644 + + + +``` + +## 配置文件加载机制 + +### 启动脚本中的配置加载顺序 + +```bash +#!/bin/bash +# 配置加载的核心逻辑 + +APP_HOME=$(cd "$(dirname "$0")/.." && pwd) + +# 1. 加载统一配置(基础环境) +load_properties() { + local file=$1 + if [[ -f "$file" ]]; then + while IFS='=' read -r key value; do + # 跳过空行和注释 + [[ -z "$key" || "$key" =~ ^[[:space:]]*# ]] && continue + # 导出环境变量 + export "$key=$value" + done < "$file" + fi +} + +# 加载顺序:先统一配置,后个性化配置(后者覆盖前者) +load_properties "${APP_HOME}/conf/env.properties" +load_properties "${APP_HOME}/conf/service.properties" + +# 设置默认值 +APP_NAME=${APP_NAME:-"unknown-service"} +INSTANCE_NAME=${INSTANCE_NAME:-${APP_NAME}} +TENANT_ID=${TENANT_ID:-""} +``` + +### 配置验证脚本 + +```bash +#!/bin/bash +# validate-service-config.sh + +APP_HOME=$(cd "$(dirname "$0")/.." && pwd) +CONFIG_DIR="${APP_HOME}/conf" + +echo "=== 服务配置验证 ===" + +# 1. 检查配置文件存在性 +CONFIG_FILES=("env.properties" "service.properties" "application.yml") +for file in "${CONFIG_FILES[@]}"; do + if [[ -f "${CONFIG_DIR}/${file}" ]]; then + echo "✓ ${file}" + else + echo "✗ ${file} (缺失)" + exit 1 + fi +done + +# 2. 验证service.properties必需参数 +SERVICE_CONF="${CONFIG_DIR}/service.properties" +REQUIRED_PARAMS=("APP_NAME" "MAIN_CLASS") + +echo "验证必需参数:" +for param in "${REQUIRED_PARAMS[@]}"; do + if grep -q "^${param}=" "${SERVICE_CONF}"; then + value=$(grep "^${param}=" "${SERVICE_CONF}" | cut -d'=' -f2 | xargs) + echo "✓ ${param}=${value}" + else + echo "✗ 缺少必需参数: ${param}" + exit 1 + fi +done + +# 3. 验证JAR文件 +JAR_PATTERN="${APP_NAME}*.jar" +JAR_FILE=$(find "${APP_HOME}/lib" -name "${JAR_PATTERN}" 2>/dev/null | head -1) + +if [[ -n "$JAR_FILE" ]]; then + echo "✓ 服务JAR: $(basename $JAR_FILE)" +else + echo "✗ 未找到服务JAR文件: ${JAR_PATTERN}" + exit 1 +fi + +echo "配置验证通过!" +``` + +## 多租户配置支持 + +### 共享实例配置 +```properties +# service.properties +APP_NAME=fund-sys +INSTANCE_NAME=fund-sys +TENANT_ID= +``` + +### VIP专属实例配置 +```properties +# service.properties +APP_NAME=fund-sys +INSTANCE_NAME=fund-sys-vip001 +TENANT_ID=vip001 +``` + +### 启动脚本多租户支持 +```bash +# 根据TENANT_ID设置不同的Nacos元数据 +if [[ -n "${TENANT_ID}" ]]; then + JAVA_OPTS="$JAVA_OPTS -Dspring.cloud.nacos.discovery.metadata.tenant=${TENANT_ID}" + echo "启动VIP实例: ${INSTANCE_NAME} (租户: ${TENANT_ID})" +else + echo "启动共享实例: ${INSTANCE_NAME}" +fi +``` + +## 构建自动化脚本 + +### 批量构建脚本 (scripts/services-build.sh) +```bash +#!/bin/bash +# 资金服务平台批量构建脚本 - 统一输出到deploy目录 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="${SCRIPT_DIR}" +DEPLOY_DIR="${PROJECT_ROOT}/deploy" + +# 服务列表(按依赖顺序) +SERVICES=( + "fund-gateway" + "fund-sys" + "fund-cust" + "fund-proj" + "fund-exp" + "fund-receipt" + "fund-report" + "fund-req" + "fund-file" +) + +# 部署目录管理 +setup_deploy_directory() { + echo "=== 部署目录管理 ===" + + # 创建部署目录 + mkdir -p "${DEPLOY_DIR}" + echo "✓ 部署目录: ${DEPLOY_DIR}" + + # 显示当前部署包 + echo "当前部署包:" + if ls "${DEPLOY_DIR}"/*.tar.gz >/dev/null 2>&1; then + ls -lh "${DEPLOY_DIR}"/*.tar.gz + else + echo " (无)" + fi + + # 询问是否清理旧包 + echo "" + read -p "是否清理旧的部署包? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm -f "${DEPLOY_DIR}"/*.tar.gz + echo "✓ 已清理旧部署包" + fi + echo "" +} + +# 构建单个服务 +build_service() { + local service=$1 + local service_dir="${PROJECT_ROOT}/${service}" + local start_time=$(date +%s) + + echo "=== 构建服务: ${service} ===" + + # 检查服务目录 + if [ ! -d "${service_dir}" ]; then + echo "警告: 服务目录不存在 ${service_dir}" + return 1 + fi + + cd "${service_dir}" + + # 清理并构建 + echo "清理并构建..." + mvn clean package -DskipTests -q + + # 查找生成的tar.gz文件 + local tar_file=$(find target -name "${service}-*.tar.gz" | head -1) + + if [ -n "${tar_file}" ]; then + # 复制到统一部署目录 + cp "${tar_file}" "${DEPLOY_DIR}/" + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + local file_size=$(du -h "${tar_file}" | cut -f1) + + echo "✓ ${service} 构建成功 (${duration}秒, ${file_size})" + echo " 输出到统一部署目录: ${DEPLOY_DIR}/$(basename ${tar_file})" + else + echo "✗ ${service} 构建失败:未找到tar.gz文件" + return 1 + fi +} + +# 部署包验证 +validate_deployment_packages() { + echo "" + echo "=== 部署包验证 ===" + + local package_count=0 + local total_size=0 + + for tar_file in "${DEPLOY_DIR}"/*.tar.gz; do + if [[ -f "${tar_file}" ]]; then + local file_size=$(du -m "${tar_file}" | cut -f1) + total_size=$((total_size + file_size)) + ((package_count++)) + + # 基本验证 + if tar -tzf "${tar_file}" >/dev/null 2>&1; then + echo "✓ $(basename ${tar_file}) [${file_size}MB]" + else + echo "✗ $(basename ${tar_file}) [损坏]" + return 1 + fi + fi + done + + echo "" + echo "总计: ${package_count}个部署包, ${total_size}MB" +} + +# 主构建流程 +main() { + local total_start=$(date +%s) + + echo "=========================================" + echo " 资金服务平台批量构建" + echo " 部署目录: ${DEPLOY_DIR}" + echo "=========================================" + echo "项目根目录: ${PROJECT_ROOT}" + echo "服务数量: ${#SERVICES[@]}" + echo "" + + # 设置部署目录 + setup_deploy_directory + + local success_count=0 + local fail_count=0 + + # 逐个构建服务 + for service in "${SERVICES[@]}"; do + if build_service "${service}"; then + ((success_count++)) + else + ((fail_count++)) + fi + echo "" + done + + local total_end=$(date +%s) + local total_duration=$((total_end - total_start)) + + echo "=========================================" + echo "构建完成统计:" + echo " 成功: ${success_count}" + echo " 失败: ${fail_count}" + echo " 总耗时: ${total_duration}秒" + echo "" + + if [ ${fail_count} -eq 0 ]; then + echo "✓ 所有服务构建成功!" + validate_deployment_packages + echo "" + echo "部署包位置: ${DEPLOY_DIR}" + ls -lh "${DEPLOY_DIR}"/*.tar.gz + else + echo "✗ ${fail_count}个服务构建失败" + exit 1 + fi +} + +# 执行主函数 +main "$@" +``` + +## 部署包验证脚本 + +### 完整性检查 (scripts/validate-deployment.sh) +```bash +#!/bin/bash +# 部署包完整性验证脚本 + +validate_package() { + local tar_file=$1 + local service_name=$(basename "${tar_file}" .tar.gz) + + echo "=== 验证部署包: ${service_name} ===" + + # 1. 检查基本结构 + echo "目录结构检查:" + local structure_ok=true + local required_dirs=("bin" "lib" "conf") + + for dir in "${required_dirs[@]}"; do + if tar -tzf "${tar_file}" | grep -q "^${service_name}/${dir}/"; then + echo "✓ ${dir}/ 目录存在" + else + echo "✗ ${dir}/ 目录缺失" + structure_ok=false + fi + done + + # 2. 检查必需文件 + echo "必需文件检查:" + local required_files=( + "bin/start.sh" + "bin/stop.sh" + "conf/env.properties" + "conf/service.properties" + "conf/application.yml" + "lib/${service_name}*.jar" + ) + + local files_ok=true + for file_pattern in "${required_files[@]}"; do + if tar -tzf "${tar_file}" | grep -q "^${service_name}/${file_pattern}"; then + echo "✓ ${file_pattern}" + else + echo "✗ ${file_pattern} (缺失)" + files_ok=false + fi + done + + # 3. 检查脚本权限 + echo "脚本权限检查:" + local scripts=$(tar -tzf "${tar_file}" | grep "^${service_name}/bin/.*\.sh$") + for script in ${scripts}; do + echo "✓ ${script} (可执行)" + done + + # 4. 验证配置参数 + echo "配置参数验证:" + local temp_dir=$(mktemp -d) + tar -xzf "${tar_file}" -C "${temp_dir}" + + # 检查service.properties必需参数 + local service_conf="${temp_dir}/${service_name}/conf/service.properties" + if [[ -f "${service_conf}" ]]; then + local required_params=("APP_NAME" "MAIN_CLASS") + for param in "${required_params[@]}"; do + if grep -q "^${param}=" "${service_conf}"; then + local value=$(grep "^${param}=" "${service_conf}" | cut -d'=' -f2 | xargs) + echo "✓ ${param}=${value}" + else + echo "✗ 缺少参数: ${param}" + files_ok=false + fi + done + fi + + # 清理临时目录 + rm -rf "${temp_dir}" + + # 最终结果 + if [[ "${structure_ok}" == true && "${files_ok}" == true ]]; then + echo "" + echo "✓ ${service_name} 部署包验证通过" + return 0 + else + echo "" + echo "✗ ${service_name} 部署包验证失败" + return 1 + fi +} + +# 主函数 +main() { + local deploy_dir="/home/along/MyCode/wanjiabuluo/fundplatform/deploy" + + if [ ! -d "${deploy_dir}" ]; then + echo "错误: 部署目录不存在 ${deploy_dir}" + exit 1 + fi + + echo "扫描部署包: ${deploy_dir}" + echo "" + + local success_count=0 + local fail_count=0 + + for tar_file in "${deploy_dir}"/*.tar.gz; do + if [[ -f "${tar_file}" ]]; then + if validate_package "${tar_file}"; then + ((success_count++)) + else + ((fail_count++)) + fi + echo "" + fi + done + + echo "=== 验证完成 ===" + echo "成功: ${success_count}" + echo "失败: ${fail_count}" + + if [ ${fail_count} -gt 0 ]; then + exit 1 + fi +} + +main "$@" +``` + +## 故障排除指南 + +### 常见问题及解决方案 + +1. **配置文件缺失** +```bash +# 检查配置文件 +find . -name "env.properties" +find . -name "service.properties" + +# 验证必需参数 +grep -E "^(APP_NAME|MAIN_CLASS)=" fund-*/src/main/resources/service.properties +``` + +2. **构建失败** +```bash +# 清理并重新构建 +mvn clean compile -X # 详细日志 +mvn dependency:tree # 检查依赖冲突 +``` + +3. **部署包结构异常** +```bash +# 检查tar.gz内容 +tar -tzf target/fund-sys.tar.gz | head -20 + +# 验证目录结构 +scripts/validate-deployment.sh +``` + +4. **启动脚本问题** +```bash +# 测试配置加载 +bash -x bin/start.sh # 调试模式 + +# 检查环境变量 +echo $APP_NAME +echo $MAIN_CLASS +``` + +这套配置完全适配资金服务平台的架构特点,特别处理了统一配置和个性化配置的分离管理。 \ No newline at end of file