20260722173146
This commit is contained in:
1 parent
e9f099f044
commit
d04fad24ed
24 files changed
+1309
-422
No files matched your search
@@ -3,6 +3,7 @@ package cn.g3soft.fmsapi.controller;
|
||||
import cn.g3soft.fmsapi.service.DataSaveService;
|
||||
import cn.g3soft.fmsapi.service.DataService;
|
||||
import cn.g3soft.fmsapi.utils.ApiResponse;
|
||||
import cn.g3soft.fmsapi.utils.ParamUtils;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -58,4 +59,11 @@ public class DataController {
|
||||
public ApiResponse<Long> nextId() {
|
||||
return ApiResponse.success(IdGenerator.nextId());
|
||||
}
|
||||
|
||||
@PostMapping("/describe")
|
||||
public ApiResponse<List<Map<String, Object>>> describe(
|
||||
@RequestBody(required = false) Map<String, Object> params
|
||||
) throws SQLException {
|
||||
return ApiResponse.success(dataService.describe(ParamUtils.getRequiredString(params, "table")));
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class DataSaveService {
|
||||
switch (action) {
|
||||
case "delete" -> dbUtils.delete(connection, table, keyColumn, rows.get(index));
|
||||
case "update" -> dbUtils.update(connection, table, keyColumn, rows.get(index));
|
||||
case "insert" -> dbUtils.insert(connection, table, rows.get(index));
|
||||
case "insert" -> dbUtils.insert(connection, table, keyColumn, rows.get(index));
|
||||
default -> throw new IllegalArgumentException("不支持的保存操作: " + action);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
|
||||
@@ -4,8 +4,11 @@ import cn.g3soft.fmsapi.exception.BusinessException;
|
||||
import cn.g3soft.fmsapi.utils.DbUtils;
|
||||
import cn.g3soft.fmsapi.utils.ParamUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -17,6 +20,18 @@ public class DataService {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
public List<Map<String, Object>> describe(String table) throws SQLException {
|
||||
Connection connection = DataSourceUtils.getConnection(dataSource);
|
||||
try {
|
||||
return dbUtils.describeColumns(connection, table);
|
||||
} finally {
|
||||
DataSourceUtils.releaseConnection(connection, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> loadData(Map<String, Object> params) throws SQLException {
|
||||
return dbUtils.loadData(
|
||||
ParamUtils.getRequiredString(params, "view_name"),
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.g3soft.fmsapi.utils;
|
||||
|
||||
import cn.g3soft.fmsapi.database.OrgDataSourceManager;
|
||||
import cn.g3soft.fmsapi.exception.BusinessException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -222,9 +223,11 @@ public class DbUtils {
|
||||
|| metadata.isReadOnly(index)
|
||||
|| "timestamp".equalsIgnoreCase(typeName)
|
||||
|| "rowversion".equalsIgnoreCase(typeName);
|
||||
boolean nullable = metadata.isNullable(index) != ResultSetMetaData.columnNoNulls;
|
||||
int size = metadata.getPrecision(index);
|
||||
columns.put(
|
||||
normalizedName,
|
||||
new ColumnMetadata(columnName, normalizedName, !generated)
|
||||
new ColumnMetadata(columnName, normalizedName, !generated, typeName, nullable, size)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -241,12 +244,54 @@ public class DbUtils {
|
||||
return table.columnsByName().get(normalizeName(requiredFieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取表或视图的列结构(列名、类型、可空、长度),用于前端同步字段等场景。
|
||||
* 入参 table 会经后端标识符校验,不会拼接到 SQL。
|
||||
*/
|
||||
public List<Map<String, Object>> describeColumns(
|
||||
Connection connection,
|
||||
String tableName
|
||||
) throws SQLException {
|
||||
TableMetadata table = loadTableMetadata(connection, tableName);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (ColumnMetadata column : table.columnsByName().values()) {
|
||||
Map<String, Object> describe = new LinkedHashMap<>();
|
||||
describe.put("name", column.name());
|
||||
describe.put("type", column.typeName());
|
||||
describe.put("nullable", column.nullable());
|
||||
describe.put("size", column.size());
|
||||
result.add(describe);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean shouldAssignKey(Object keyValue) {
|
||||
if (keyValue == null) {
|
||||
return true;
|
||||
}
|
||||
if (keyValue instanceof Number number) {
|
||||
return number.longValue() < 0;
|
||||
}
|
||||
if (keyValue instanceof String text && !text.isBlank()) {
|
||||
try {
|
||||
return Long.parseLong(text) < 0;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int insert(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
ColumnMetadata keyColumn,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
if (keyColumn != null && shouldAssignKey(normalizedRow.get(keyColumn.normalizedName()))) {
|
||||
normalizedRow.put(keyColumn.normalizedName(), IdGenerator.nextId());
|
||||
}
|
||||
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
|
||||
table,
|
||||
normalizedRow,
|
||||
@@ -600,7 +645,10 @@ public class DbUtils {
|
||||
public record ColumnMetadata(
|
||||
String name,
|
||||
String normalizedName,
|
||||
boolean writable
|
||||
boolean writable,
|
||||
String typeName,
|
||||
boolean nullable,
|
||||
int size
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.g3soft.fmsapi.mock;
|
||||
|
||||
import cn.g3soft.fmsapi.database.OrgContext;
|
||||
import cn.g3soft.fmsapi.database.OrgDataSourceManager;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* 为「用户管理」模块(b_id = 829648877883461)插入 5000 条模拟操作权限(b_module_power)。
|
||||
*
|
||||
* 运行方式:
|
||||
* cd fms-api && ./mvnw test -Dtest=ModulePowerMockDataTests
|
||||
*
|
||||
* 说明:
|
||||
* - 使用硬编码的模块 b_id(829648877883461),即用户管理模块;
|
||||
* 行级 b_id 由雪花 ID 生成器产生,保证唯一且符合系统规范。
|
||||
* - 幂等:每次执行先删除该模块下 mock_ 前缀的旧数据,再重新插入 5000 条,
|
||||
* 可重复运行而不会造成 (b_module_id, b_code) 或 b_id 冲突。
|
||||
* - 通过 OrgDataSourceManager 命中 g3hd.properties 对应的数据源,需本机可连接该库。
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
class ModulePowerMockDataTests {
|
||||
|
||||
private static final String ORG_ID = "G3HD";
|
||||
private static final long USER_MODULE_ID = 829648877883461L;
|
||||
private static final int MOCK_COUNT = 5000;
|
||||
private static final int BATCH_SIZE = 500;
|
||||
|
||||
@Autowired
|
||||
private OrgDataSourceManager dataSourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void initIdGenerator() {
|
||||
// @SpringBootTest 不执行 main(),需在此初始化雪花 ID 生成器
|
||||
IdGeneratorOptions options = new IdGeneratorOptions();
|
||||
options.WorkerId = 1;
|
||||
IdGenerator.setIdGenerator(options);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearOrgContext() {
|
||||
OrgContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertModulePowerMockData() throws SQLException {
|
||||
OrgContext.setOrgId(ORG_ID);
|
||||
try (Connection connection = dataSourceManager.getConnection(ORG_ID)) {
|
||||
long moduleId = requireUserModule(connection, USER_MODULE_ID);
|
||||
|
||||
// 1) 清理旧的 mock 数据,保证可重复运行
|
||||
try (PreparedStatement delete = connection.prepareStatement(
|
||||
"DELETE FROM [dbo].[b_module_power] WHERE [b_module_id] = ? AND [b_code] LIKE 'mock_%'")) {
|
||||
delete.setLong(1, moduleId);
|
||||
int removed = delete.executeUpdate();
|
||||
System.out.println("清理旧 mock 数据: " + removed + " 条");
|
||||
}
|
||||
|
||||
// 2) 批量插入 5000 条模拟操作权限
|
||||
String sql = "INSERT INTO [dbo].[b_module_power] "
|
||||
+ "([b_id], [b_module_id], [b_code], [b_name], [b_i18n], [b_xh], [b_canuse]) "
|
||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
int total = 0;
|
||||
try (PreparedStatement insert = connection.prepareStatement(sql)) {
|
||||
for (int n = 1; n <= MOCK_COUNT; n++) {
|
||||
String seq = String.format("%06d", n);
|
||||
insert.setLong(1, IdGenerator.nextId());
|
||||
insert.setLong(2, moduleId);
|
||||
insert.setString(3, "mock_" + seq);
|
||||
insert.setString(4, "模拟操作 " + seq);
|
||||
insert.setString(5, "power.m_user.mock_" + seq);
|
||||
insert.setInt(6, n);
|
||||
insert.setInt(7, 1);
|
||||
insert.addBatch();
|
||||
|
||||
if (n % BATCH_SIZE == 0) {
|
||||
total += sum(insert.executeBatch());
|
||||
}
|
||||
}
|
||||
total += sum(insert.executeBatch());
|
||||
}
|
||||
|
||||
System.out.println("插入 " + total + " 条模拟操作权限(模块 b_id=" + moduleId + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private long requireUserModule(Connection connection, long moduleId) throws SQLException {
|
||||
try (PreparedStatement ps = connection.prepareStatement(
|
||||
"SELECT 1 FROM [dbo].[b_module] WHERE [b_id] = ?")) {
|
||||
ps.setLong(1, moduleId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException(
|
||||
"未找到用户管理模块 b_id=" + moduleId + ",请确认该模块在目标库中已存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
private static int sum(int[] counts) {
|
||||
int sum = 0;
|
||||
for (int c : counts) {
|
||||
sum += c;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user