feat: 完善entity-db-sync和maven-assembly技能包 - 增强部署目录管理和实践规范

This commit is contained in:
zhangjf 2026-02-25 18:59:33 +08:00
parent e5d9db10a8
commit fb6554f32c
6 changed files with 3078 additions and 0 deletions

View File

@ -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. 团队协作规范
- 实体类修改必须附带变更脚本
- 变更前必须进行影响评估
- 多人协作时建立变更协调机制
- 定期审查变更历史和文档同步情况
这个增强版的技能不仅确保实体类与数据库脚本的技术同步,还建立了完整的文档维护和生产环境变更管理流程。

View File

@ -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 <Entity.java>' 生成SQL"
exit 1
fi
# 自动生成变更脚本
echo "生成变更脚本..."
python3 scripts/generate-change-scripts.py --auto-generate
echo "✅ 同步检查通过"
fi
```
这些示例展示了从简单到复杂的各种同步场景,包括变更脚本生成、文档同步和生产环境部署的完整流程。

View File

@ -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<String> tags;
// 或者使用单独的关联表
@ElementCollection
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
private Set<String> 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分钟
## 影响评估
### 业务影响
- [ ] 用户注册/登录功能
- [ ] 数据查询性能
- [ ] 报表统计准确性
- [ ] 第三方系统对接
### 技术风险
- [ ] 数据一致性风险
- [ ] 性能下降风险
- [ ] 锁表时间过长
- [ ] 空间不足风险
## 预防措施
- [ ] 执行前完整备份
- [ ] 低峰期执行变更
- [ ] 准备回滚方案
- [ ] 安排专人监控
## 应急预案
- **回滚步骤**: [详细回滚操作步骤]
- **联系人员**: [紧急联系人列表]
- **监控指标**: [关键监控项]
```
这份参考手册提供了完整的实体类与数据库脚本同步的技术细节、工具脚本和生产环境部署规范。

View File

@ -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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}</finalName>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
```
### assembly.xml配置
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- bin目录使用项目根目录的统一脚本 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>start.sh</include>
<include>stop.sh</include>
<include>restart.sh</include>
<include>status.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<!-- conf目录统一配置 + 个性化配置 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>env.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>service.properties</include>
<include>application.yml</include>
<include>application-*.yml</include>
<include>logback-spring.xml</include>
</includes>
</fileSet>
</fileSets>
<!-- lib目录服务JAR + 依赖 -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
```
## 自动构建脚本
### 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集成**: 部署目录结构便于自动化部署和版本发布管理

View File

@ -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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fundplatform</groupId>
<artifactId>fund-platform</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>fund-sys</artifactId>
<packaging>jar</packaging>
<properties>
<!-- 从service.properties读取主类 -->
<main.class>com.fundplatform.sys.SysApplication</main.class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}</finalName>
</configuration>
</plugin>
</plugins>
</build>
</project>
```
**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
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- 使用项目根目录的统一脚本 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>start.sh</include>
<include>stop.sh</include>
<include>restart.sh</include>
<include>status.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<!-- 统一配置文件 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>env.properties</include>
</includes>
</fileSet>
<!-- 服务个性化配置 -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>service.properties</include>
<include>application.yml</include>
<include>logback-spring.xml</include>
</includes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
```
## 实际应用场景
### 场景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
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/dev-assembly.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>prod</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/prod-assembly.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
```
### 场景5: 条件配置打包
**assembly.xml支持条件包含:**
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0">
<id>conditional-dist</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<!-- 基础文件 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>start.sh</include>
<include>stop.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<!-- 根据环境包含不同配置 -->
<fileSet>
<directory>${project.basedir}/../../scripts/${env.name}</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>env.properties</include>
</includes>
</fileSet>
<!-- 可选安全组件 -->
<fileSet>
<directory>src/main/security</directory>
<outputDirectory>security</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
</fileSets>
</assembly>
```
## 构建验证示例
### 自动化验证脚本
**创建验证脚本 (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配置的各种实际应用场景涵盖了从基础配置到高级特性的完整使用方式。

View File

@ -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
<build>
<plugins>
<!-- Spring Boot插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${main.class}</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Assembly插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<!-- 从service.properties读取主类 -->
<main.class>${service.main.class}</main.class>
</properties>
```
### 项目级Assembly Descriptor
```xml
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- bin目录使用项目根目录统一脚本 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>start.sh</include>
<include>stop.sh</include>
<include>restart.sh</include>
<include>status.sh</include>
</includes>
<fileMode>0755</fileMode>
<directoryMode>0755</directoryMode>
</fileSet>
<!-- 统一配置文件 -->
<fileSet>
<directory>${project.basedir}/../../scripts</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>env.properties</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
<!-- 服务个性化配置 -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>service.properties</include>
<include>application.yml</include>
<include>application-*.yml</include>
<include>logback-spring.xml</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveDependencies>true</useTransitiveDependencies>
<scope>runtime</scope>
<fileMode>0644</fileMode>
</dependencySet>
</dependencySets>
</assembly>
```
## 配置文件加载机制
### 启动脚本中的配置加载顺序
```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
```
这套配置完全适配资金服务平台的架构特点,特别处理了统一配置和个性化配置的分离管理。