feat(deploy): 优化版本管理和多租户合并逻辑
- 修改 readCurrentVersion 方法优先读取镜像内的 /app/VERSION 文件 - 添加对宿主机挂载目录 VERSION 文件的兼容性支持 - 移除 bumpVersionFile 方法,不再在更新后写入版本号 - 重构多租户合并逻辑,优化数据库查询和更新操作 - 简化孤儿数据修复逻辑,直接更新为保留租户ID - 在 Dockerfile 中复制 VERSION 文件到镜像内部 - 在 Jenkinsfile 中添加自动递增构建号功能
这个提交包含在:
父节点
898597d6b6
当前提交
eb8bc70ff5
@ -46,5 +46,6 @@ ARG SERVICE_MODULE
|
|||||||
RUN apk add --no-cache curl docker-cli docker-compose
|
RUN apk add --no-cache curl docker-cli docker-compose
|
||||||
|
|
||||||
COPY --from=build /workspace/${SERVICE_MODULE}/target/${SERVICE_MODULE}-0.1.0-SNAPSHOT.jar /app/app.jar
|
COPY --from=build /workspace/${SERVICE_MODULE}/target/${SERVICE_MODULE}-0.1.0-SNAPSHOT.jar /app/app.jar
|
||||||
|
COPY VERSION /app/VERSION
|
||||||
|
|
||||||
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||||
|
|||||||
14
Jenkinsfile
vendored
14
Jenkinsfile
vendored
@ -39,6 +39,20 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) {
|
||||||
script {
|
script {
|
||||||
|
// 自动递增 VERSION 文件中的构建号
|
||||||
|
def versionFile = 'VERSION'
|
||||||
|
if (fileExists(versionFile)) {
|
||||||
|
def version = readFile(versionFile).trim()
|
||||||
|
// 格式: 2026.05.20-private.3 → 递增末尾数字
|
||||||
|
def matcher = version =~ /^(.+\.)(\d+)$/
|
||||||
|
if (matcher.matches()) {
|
||||||
|
def prefix = matcher.group(1)
|
||||||
|
def buildNum = matcher.group(2).toInteger() + 1
|
||||||
|
def newVersion = "${prefix}${buildNum}"
|
||||||
|
writeFile file: versionFile, text: newVersion
|
||||||
|
echo "VERSION: ${version} → ${newVersion}"
|
||||||
|
}
|
||||||
|
}
|
||||||
def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}"
|
def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}"
|
||||||
bat """
|
bat """
|
||||||
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
||||||
|
|||||||
@ -126,32 +126,25 @@ public class SystemUpdateService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 读取部署目录的 VERSION 文件,返回当前版本号,文件不存在时返回 "unknown"。 */
|
/** 读取镜像内打包的 VERSION 文件,返回当前版本号,文件不存在时返回 "unknown"。 */
|
||||||
public String readCurrentVersion() {
|
public String readCurrentVersion() {
|
||||||
Path versionFile = Paths.get(deployRoot, "VERSION");
|
// 优先读镜像内 /app/VERSION
|
||||||
|
Path containerVersion = Paths.get("/app/VERSION");
|
||||||
try {
|
try {
|
||||||
if (Files.exists(versionFile)) {
|
if (Files.exists(containerVersion)) {
|
||||||
return Files.readString(versionFile).trim();
|
return Files.readString(containerVersion).trim();
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
// 兼容旧路径:宿主机挂载目录
|
||||||
|
Path hostVersion = Paths.get(deployRoot, "VERSION");
|
||||||
|
try {
|
||||||
|
if (Files.exists(hostVersion)) {
|
||||||
|
return Files.readString(hostVersion).trim();
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {}
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新完成后写入新版本号到 VERSION 文件。 */
|
|
||||||
private void bumpVersionFile(Consumer<String> emit) {
|
|
||||||
String oldVersion = readCurrentVersion();
|
|
||||||
String newVersion = java.time.LocalDate.now().toString(); // yyyy-MM-dd
|
|
||||||
try {
|
|
||||||
Path versionFile = Paths.get(deployRoot, "VERSION");
|
|
||||||
Files.createDirectories(versionFile.getParent());
|
|
||||||
Files.writeString(versionFile, newVersion,
|
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
|
||||||
emit.accept(">>> 版本号: " + oldVersion + " → " + newVersion);
|
|
||||||
} catch (IOException e) {
|
|
||||||
emit.accept(" [警告] 写入 VERSION 文件失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 拉取最新镜像并重建所有容器。 */
|
/** 拉取最新镜像并重建所有容器。 */
|
||||||
public void runUpdate(Consumer<String> emit) {
|
public void runUpdate(Consumer<String> emit) {
|
||||||
String composeFile = deployRoot + "/docker-compose.yml";
|
String composeFile = deployRoot + "/docker-compose.yml";
|
||||||
@ -169,8 +162,6 @@ public class SystemUpdateService {
|
|||||||
exec(emit, "docker", "compose", "-f", composeFile, "pull", "--quiet", "tenant-service");
|
exec(emit, "docker", "compose", "-f", composeFile, "pull", "--quiet", "tenant-service");
|
||||||
emit.accept(">>> 镜像拉取完成");
|
emit.accept(">>> 镜像拉取完成");
|
||||||
|
|
||||||
bumpVersionFile(emit);
|
|
||||||
|
|
||||||
restartAndSelfUpdate(emit, composeFile);
|
restartAndSelfUpdate(emit, composeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,46 +554,40 @@ public class SystemUpdateService {
|
|||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
// 2. 多租户合并:删除多余租户
|
// 2. 多租户合并:删除多余租户
|
||||||
List<String> extraTenants = new java.util.ArrayList<>();
|
int extraCount;
|
||||||
try (PreparedStatement ps = conn.prepareStatement("SELECT id FROM t_tenant WHERE id != ?");
|
|
||||||
ResultSet rs = ps.executeQuery()) {
|
|
||||||
ps.setString(1, keepId);
|
|
||||||
while (rs.next()) {
|
|
||||||
extraTenants.add(rs.getString("id"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!extraTenants.isEmpty()) {
|
|
||||||
emit.accept(" [合并] 保留最早租户 " + keepId + ",删除 " + extraTenants.size() + " 个多余租户");
|
|
||||||
// 子账号 parent_id 指向保留的租户
|
|
||||||
try (PreparedStatement ps = conn.prepareStatement(
|
try (PreparedStatement ps = conn.prepareStatement(
|
||||||
"UPDATE t_tenant SET parent_id = ? WHERE parent_id IS NOT NULL AND parent_id != ?")) {
|
"SELECT COUNT(*) FROM t_tenant WHERE id != ?")) {
|
||||||
ps.setString(1, keepId);
|
ps.setString(1, keepId);
|
||||||
ps.setString(2, keepId);
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
ps.executeUpdate();
|
rs.next();
|
||||||
|
extraCount = rs.getInt(1);
|
||||||
}
|
}
|
||||||
// 删除多余租户
|
}
|
||||||
try (PreparedStatement ps = conn.prepareStatement("DELETE FROM t_tenant WHERE id != ?")) {
|
if (extraCount > 0) {
|
||||||
ps.setString(1, keepId);
|
emit.accept(" [合并] 保留最早租户 " + keepId + ",删除 " + extraCount + " 个多余租户");
|
||||||
int deleted = ps.executeUpdate();
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.executeUpdate("UPDATE t_tenant SET parent_id = '" + keepId
|
||||||
|
+ "' WHERE parent_id IS NOT NULL AND parent_id != '" + keepId + "'");
|
||||||
|
}
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
int deleted = stmt.executeUpdate("DELETE FROM t_tenant WHERE id != '" + keepId + "'");
|
||||||
emit.accept(" 删除多余租户: " + deleted + " 个");
|
emit.accept(" 删除多余租户: " + deleted + " 个");
|
||||||
}
|
}
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 修复孤儿数据:tenant_id 不在 t_tenant 中的记录
|
// 3. 修复孤儿数据:tenant_id 与唯一租户不匹配的记录(包括指向不存在租户的情况)
|
||||||
String[][] tables = {
|
String[][] tables = {
|
||||||
{"t_app", "tenant_id"},
|
{"t_app", "tenant_id"},
|
||||||
{"t_operation_log", "tenant_id"},
|
{"t_operation_log", "tenant_id"},
|
||||||
{"t_migrate_key", "tenant_id"},
|
{"t_migrate_key", "tenant_id"},
|
||||||
};
|
};
|
||||||
for (String[] tbl : tables) {
|
for (String[] tbl : tables) {
|
||||||
try (PreparedStatement ps = conn.prepareStatement(
|
try (Statement stmt = conn.createStatement()) {
|
||||||
"UPDATE " + tbl[0] + " SET " + tbl[1] + " = ?"
|
int rows = stmt.executeUpdate("UPDATE " + tbl[0] + " SET " + tbl[1] + " = '"
|
||||||
+ " WHERE " + tbl[1] + " NOT IN (SELECT id FROM t_tenant)")) {
|
+ keepId + "' WHERE " + tbl[1] + " != '" + keepId + "'");
|
||||||
ps.setString(1, keepId);
|
|
||||||
int rows = ps.executeUpdate();
|
|
||||||
if (rows > 0) {
|
if (rows > 0) {
|
||||||
emit.accept(" " + tbl[0] + ": 修复 " + rows + " 条孤儿记录");
|
emit.accept(" " + tbl[0] + ": 修复 " + rows + " 条记录");
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户