feat(app): 支持多平台包名配置和应用信息编辑功能
- 后端增加至少填写一个平台包名的验证逻辑 - 前端调整应用数据模型,将包名字段改为可选类型 - 添加应用详情页的编辑功能和表单验证 - 优化应用列表页包名显示逻辑,支持多平台包名展示 - 重构应用配置指引页面,按平台分类展示商店配置指南 - 在版本管理页面增加包名配置检查和相应提示 - 新增应用信息编辑弹窗组件和相关业务逻辑
这个提交包含在:
父节点
3e2db6441e
当前提交
77553cd105
@ -69,8 +69,8 @@ public class AppController {
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<AppEntity>> create(@RequestBody CreateAppRequest req,
|
||||
@AuthenticationPrincipal String tenantId) {
|
||||
requireNonBlank(req.packageName(), "packageName");
|
||||
requireNonBlank(req.name(), "name");
|
||||
requireAtLeastOnePackageName(req);
|
||||
return ResponseEntity.ok(ApiResponse.success(appService.create(tenantId, req)));
|
||||
}
|
||||
|
||||
@ -78,8 +78,8 @@ public class AppController {
|
||||
public ResponseEntity<ApiResponse<AppEntity>> update(@PathVariable String appKey,
|
||||
@RequestBody CreateAppRequest req,
|
||||
@AuthenticationPrincipal String tenantId) {
|
||||
requireNonBlank(req.packageName(), "packageName");
|
||||
requireNonBlank(req.name(), "name");
|
||||
requireAtLeastOnePackageName(req);
|
||||
return ResponseEntity.ok(ApiResponse.success(appService.update(appKey, tenantId, req)));
|
||||
}
|
||||
|
||||
@ -89,6 +89,15 @@ public class AppController {
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireAtLeastOnePackageName(CreateAppRequest req) {
|
||||
boolean hasAny = (req.packageName() != null && !req.packageName().isBlank())
|
||||
|| (req.iosBundleId() != null && !req.iosBundleId().isBlank())
|
||||
|| (req.harmonyBundleName() != null && !req.harmonyBundleName().isBlank());
|
||||
if (!hasAny) {
|
||||
throw new BusinessException(400, "至少填写一个平台的包名(packageName / iosBundleId / harmonyBundleName)");
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{appKey}")
|
||||
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable String appKey,
|
||||
@AuthenticationPrincipal String tenantId) {
|
||||
|
||||
@ -0,0 +1,124 @@
|
||||
package com.xuqm.update.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* update-service 数据库迁移,在 Hibernate 初始化之前执行。
|
||||
*
|
||||
* 使用 BeanFactoryPostProcessor + PriorityOrdered 确保在所有 bean 创建之前运行,
|
||||
* 避免旧的 GrayMode 枚举值(IM_PUSH_USERS/CUSTOMER_SYNC/CUSTOMER_CALLBACK)
|
||||
* 导致 Hibernate 反序列化失败。
|
||||
*/
|
||||
@Component
|
||||
public class SchemaMigrationRunner implements BeanFactoryPostProcessor, PriorityOrdered {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SchemaMigrationRunner.class);
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return PriorityOrdered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
DataSource dataSource = beanFactory.getBean(DataSource.class);
|
||||
migrate_v20260610_gray_mode_simplify(dataSource);
|
||||
} catch (Exception e) {
|
||||
log.warn("Schema migration skipped (DataSource not ready): {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将旧灰度模式迁移到新的 PERCENT / MEMBERS 模式。
|
||||
*
|
||||
* 旧值:IM_PUSH_USERS / CUSTOMER_SYNC / CUSTOMER_CALLBACK → MEMBERS
|
||||
* 清理:grayCallbackUrl 旧数据(已废弃,改为配置级 publishCallbackUrl)
|
||||
*/
|
||||
private void migrate_v20260610_gray_mode_simplify(DataSource dataSource) {
|
||||
String migrationId = "v20260610_gray_mode_simplify";
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
// 确保迁移记录表存在
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("""
|
||||
CREATE TABLE IF NOT EXISTS _schema_migrations (
|
||||
id VARCHAR(128) NOT NULL PRIMARY KEY,
|
||||
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
description VARCHAR(255)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""");
|
||||
}
|
||||
|
||||
// 检查是否已执行
|
||||
try (var ps = conn.prepareStatement(
|
||||
"SELECT COUNT(*) FROM _schema_migrations WHERE id = ?")) {
|
||||
ps.setString(1, migrationId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next() && rs.getInt(1) > 0) {
|
||||
return; // 已执行,跳过
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Applying migration: {}", migrationId);
|
||||
|
||||
// 迁移 update_app_version 表
|
||||
int appVersions = 0;
|
||||
try (var ps = conn.prepareStatement(
|
||||
"UPDATE update_app_version SET gray_mode = 'MEMBERS' " +
|
||||
"WHERE gray_mode IN ('IM_PUSH_USERS', 'CUSTOMER_SYNC', 'CUSTOMER_CALLBACK')")) {
|
||||
appVersions = ps.executeUpdate();
|
||||
}
|
||||
|
||||
// 迁移 update_rn_bundle 表
|
||||
int rnBundles = 0;
|
||||
try (var ps = conn.prepareStatement(
|
||||
"UPDATE update_rn_bundle SET gray_mode = 'MEMBERS' " +
|
||||
"WHERE gray_mode IN ('IM_PUSH_USERS', 'CUSTOMER_SYNC', 'CUSTOMER_CALLBACK')")) {
|
||||
rnBundles = ps.executeUpdate();
|
||||
}
|
||||
|
||||
// 清理旧的 grayCallbackUrl
|
||||
try (var ps = conn.prepareStatement(
|
||||
"UPDATE update_app_version SET gray_callback_url = NULL " +
|
||||
"WHERE gray_callback_url IS NOT NULL AND gray_callback_url != ''")) {
|
||||
int cleaned = ps.executeUpdate();
|
||||
if (cleaned > 0) {
|
||||
log.info("Cleared {} old grayCallbackUrl from update_app_version", cleaned);
|
||||
}
|
||||
}
|
||||
try (var ps = conn.prepareStatement(
|
||||
"UPDATE update_rn_bundle SET gray_callback_url = NULL " +
|
||||
"WHERE gray_callback_url IS NOT NULL AND gray_callback_url != ''")) {
|
||||
int cleaned = ps.executeUpdate();
|
||||
if (cleaned > 0) {
|
||||
log.info("Cleared {} old grayCallbackUrl from update_rn_bundle", cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录迁移
|
||||
try (var ps = conn.prepareStatement(
|
||||
"INSERT IGNORE INTO _schema_migrations (id, description) VALUES (?, ?)")) {
|
||||
ps.setString(1, migrationId);
|
||||
ps.setString(2, "Simplify GrayMode to PERCENT/MEMBERS, clear grayCallbackUrl");
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
log.info("Migration {} done: {} app versions, {} RN bundles converted to MEMBERS",
|
||||
migrationId, appVersions, rnBundles);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Migration {} failed: {}", migrationId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
正在加载...
在新工单中引用
屏蔽一个用户