20260905172534

This commit is contained in:
oneao committed 2026-09-05 17:25:35 +08:00
1 parent 1b01a31252
commit 7b00eeb88c
711 files changed
+2849 -959

No files matched your search

@@ -46,7 +46,7 @@ public class AuthService {
if (users.isEmpty()) {
loginLogService.recordLogin(
normalizedOrgId, normalizedUserId, null,
normalizedOrgId, normalizedUserId,
false, "账号不存在", null, request);
return Optional.empty();
}
@@ -55,24 +55,27 @@ public class AuthService {
String storedPassword = getString(account, "b_password");
if (!passwordMatches(storedPassword, password)) {
loginLogService.recordLogin(
normalizedOrgId, normalizedUserId, null,
normalizedOrgId, normalizedUserId,
false, "密码错误", null, request);
return Optional.empty();
}
String accountId = getString(account, "b_account");
long userPrimaryKey = getLong(account, "b_id");
// 字符串业务键方案:b_user.b_id 即用户账号本身
String accountId = getString(account, "b_id");
if (accountId == null || accountId.isBlank()) {
throw new IllegalStateException("登录用户缺少有效的 b_id");
}
String name = getString(account, "b_name");
String sessionId = UUID.randomUUID().toString();
String token = jwtUtils.generateToken(accountId, normalizedOrgId, sessionId);
activeSessionRegistry.activate(normalizedOrgId, accountId, sessionId);
loginLogService.recordLogin(
normalizedOrgId, accountId, userPrimaryKey,
normalizedOrgId, accountId,
true, null, sessionId, request);
Map<String, Object> user = new LinkedHashMap<>();
user.put("id", userPrimaryKey);
user.put("id", accountId);
user.put("account", accountId);
user.put("name", name);
@@ -88,21 +91,6 @@ public class AuthService {
return value == null ? null : value.toString();
}
private long getLong(Map<String, Object> row, String column) {
Object value = getValue(row, column);
if (value instanceof Number number) {
return number.longValue();
}
if (value != null) {
try {
return Long.parseLong(value.toString());
} catch (NumberFormatException ignored) {
// Report the same configuration error below.
}
}
throw new IllegalStateException("登录用户缺少有效的" + column);
}
private Object getValue(Map<String, Object> row, String column) {
for (Map.Entry<String, Object> entry : row.entrySet()) {
if (entry.getKey().equalsIgnoreCase(column)) {
@@ -65,18 +65,14 @@ public class DataSaveService {
throw new BusinessException("count 必须是 1 到 1000 之间的整数");
}
final long moduleIdValue;
try {
moduleIdValue = Long.parseLong(moduleId);
} catch (NumberFormatException exception) {
throw new BusinessException("moduleId 必须是有效的数字");
}
// 字符串业务键方案:moduleId 即模块编码(如 's_i18n_type'),非 bigint
String normalizedModuleId = moduleId.trim();
try (Connection connection = dataSource.getConnection()) {
boolean originalAutoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
try {
AutoCodeRule rule = loadAutoCodeRule(connection, moduleIdValue);
AutoCodeRule rule = loadAutoCodeRule(connection, normalizedModuleId);
DbUtils.TableMetadata targetTable = dbUtils.loadTableMetadata(connection, rule.saveTable());
DbUtils.ColumnMetadata targetColumn = dbUtils.getColumn(targetTable, rule.fieldName());
if (targetColumn == null) {
@@ -119,14 +115,16 @@ public class DataSaveService {
}
long lastValue = nextValue - 1;
// s_autocode 主键为 (b_module_id, b_field)
try (PreparedStatement statement = connection.prepareStatement(
"UPDATE dbo.s_module_auto_code "
+ "SET b_current_period = ?, b_current_value = ? "
+ "WHERE b_id = ?"
"UPDATE dbo.s_autocode "
+ "SET b_currentperiod = ?, b_currentvalue = ? "
+ "WHERE b_module_id = ? AND b_field = ?"
)) {
statement.setString(1, period);
statement.setLong(2, lastValue);
statement.setLong(3, rule.id());
statement.setString(3, rule.moduleId());
statement.setString(4, rule.fieldName());
statement.executeUpdate();
}
connection.commit();
@@ -142,18 +140,19 @@ public class DataSaveService {
}
}
private AutoCodeRule loadAutoCodeRule(Connection connection, long moduleId) throws SQLException {
String sql = "SELECT TOP 1 r.b_id, r.b_prefix, r.b_date_format, "
+ "r.b_separator, r.b_sequence_width, r.b_reset_type, "
+ "r.b_start_value, r.b_current_period, r.b_current_value, "
+ "m.b_savetable, f.b_field "
+ "FROM dbo.s_module_auto_code r WITH (UPDLOCK, HOLDLOCK, ROWLOCK) "
private AutoCodeRule loadAutoCodeRule(Connection connection, String moduleId) throws SQLException {
// 字符串业务键方案:s_autocode 主键为 (b_module_id, b_field),
// 字段名直接存于 s_autocode.b_field,不再经 s_module_field 中间表。
String sql = "SELECT TOP 1 r.b_prefix, r.b_dateformat, r.b_separator, "
+ "r.b_seqwidth, r.b_resettype, r.b_startvalue, "
+ "r.b_currentperiod, r.b_currentvalue, r.b_field, m.b_savetable "
+ "FROM dbo.s_autocode r WITH (UPDLOCK, HOLDLOCK, ROWLOCK) "
+ "INNER JOIN dbo.s_module m ON m.b_id = r.b_module_id "
+ "INNER JOIN dbo.s_module_field f ON f.b_id = r.b_field_id "
+ "WHERE r.b_module_id = ? AND r.b_canuse = 1 "
+ "AND m.b_module_type = 'data' AND f.b_canuse = 1";
+ "AND m.b_module_type = 'data' "
+ "ORDER BY r.b_field";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, moduleId);
statement.setString(1, moduleId);
try (ResultSet resultSet = statement.executeQuery()) {
if (!resultSet.next()) {
throw new BusinessException("当前模块未配置启用的自动编码规则");
@@ -163,16 +162,19 @@ public class DataSaveService {
if (saveTable == null || saveTable.isBlank()) {
throw new BusinessException("模块未配置保存表,无法生成自动编号");
}
if (fieldName == null || fieldName.isBlank()) {
throw new BusinessException("自动编码规则缺少字段配置");
}
return new AutoCodeRule(
resultSet.getLong("b_id"),
moduleId,
resultSet.getString("b_prefix"),
resultSet.getString("b_date_format"),
resultSet.getString("b_dateformat"),
resultSet.getString("b_separator"),
resultSet.getInt("b_sequence_width"),
resultSet.getString("b_reset_type"),
resultSet.getLong("b_start_value"),
resultSet.getString("b_current_period"),
resultSet.getLong("b_current_value"),
resultSet.getInt("b_seqwidth"),
resultSet.getString("b_resettype"),
resultSet.getLong("b_startvalue"),
resultSet.getString("b_currentperiod"),
resultSet.getLong("b_currentvalue"),
saveTable,
fieldName
);
@@ -353,7 +355,7 @@ public class DataSaveService {
}
private record AutoCodeRule(
long id,
String moduleId,
String prefix,
String dateFormat,
String separator,
@@ -394,29 +396,42 @@ public class DataSaveService {
tableName,
metadataCache
);
DbUtils.ColumnMetadata keyColumn = dbUtils.getColumn(table, keyField);
if (keyColumn == null) {
throw new BusinessException("主键字段不存在: " + tableName + "." + keyField);
// 字符串业务键方案支持复合主键:key_field 可用逗号分隔多个列,
// 例如 s_field 传 "b_module_id,b_field";单列主键传 "b_id"。
List<DbUtils.ColumnMetadata> keyColumns = new ArrayList<>();
for (String name : keyField.split(",")) {
String trimmed = name.trim();
if (trimmed.isEmpty()) {
continue;
}
DbUtils.ColumnMetadata column = dbUtils.getColumn(table, trimmed);
if (column == null) {
throw new BusinessException("主键字段不存在: " + tableName + "." + trimmed);
}
keyColumns.add(column);
}
if (keyColumns.isEmpty()) {
throw new BusinessException("主键字段不能为空: " + tableName);
}
executeRows(
connection,
table,
keyColumn,
keyColumns,
"delete",
rows(request, "deletes")
);
executeRows(
connection,
table,
keyColumn,
keyColumns,
"update",
rows(request, "updates")
);
executeRows(
connection,
table,
keyColumn,
keyColumns,
"insert",
rows(request, "inserts")
);
@@ -445,7 +460,7 @@ public class DataSaveService {
private void executeRows(
Connection connection,
DbUtils.TableMetadata table,
DbUtils.ColumnMetadata keyColumn,
List<DbUtils.ColumnMetadata> keyColumns,
String action,
List<Map<String, Object>> rows
) {
@@ -453,8 +468,8 @@ public class DataSaveService {
try {
switch (action) {
case "delete" -> dbUtils.delete(connection, table, rows.get(index));
case "update" -> dbUtils.update(connection, table, keyColumn, rows.get(index));
case "insert" -> dbUtils.insert(connection, table, keyColumn, rows.get(index));
case "update" -> dbUtils.update(connection, table, keyColumns, rows.get(index));
case "insert" -> dbUtils.insert(connection, table, null, rows.get(index));
default -> throw new IllegalArgumentException("不支持的保存操作: " + action);
}
} catch (SQLException exception) {
@@ -132,14 +132,16 @@ public class FileService {
""";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setLong(1, subid);
// father 自引用 bf_files.subid,仍为 bigint
ps.setObject(2, toLongOrNull(father));
ps.setString(3, filename);
ps.setString(4, filesize);
ps.setString(5, fileext);
ps.setString(6, mapfilename);
ps.setObject(7, toLongOrNull(moduleId));
ps.setObject(8, toLongOrNull(cateId));
ps.setObject(9, toLongOrNull(userId));
// 字符串业务键:模块编码 / 分类编码 / 用户账号均按字符串存储
ps.setString(7, moduleId);
ps.setString(8, cateId);
ps.setString(9, userId);
ps.executeUpdate();
}
} finally {
@@ -3,7 +3,7 @@ package cn.g3soft.fmsapi.service;
import cn.g3soft.fmsapi.database.OrgContext;
import cn.g3soft.fmsapi.utils.ClientInfo;
import cn.g3soft.fmsapi.utils.ClientInfoExtractor;
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
import cn.g3soft.fmsapi.utils.UuidV7Generator;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,7 +37,6 @@ public class LoginLogService {
public void recordLogin(
String orgId,
String account,
Long userId,
boolean success,
String failReason,
String sessionId,
@@ -50,9 +49,10 @@ public class LoginLogService {
ClientInfo info = clientInfoExtractor.extract(request);
Map<String, Object> row = new LinkedHashMap<>();
row.put("b_id", IdGenerator.nextId());
row.put("b_user_id", userId);
row.put("b_account", account);
// 字符串业务键方案:主键为业务层生成的 UUIDv7
row.put("b_id", UuidV7Generator.nextUuid().toString());
// b_user_id 引用 b_user.b_id(账号字符串,即稳定用户标识);仅登录成功时存在
row.put("b_user_id", success ? account : null);
row.put("b_login_type", LOGIN_TYPE_PASSWORD);
row.put("b_occurdatetime", Timestamp.from(java.time.Instant.now()));
row.put("b_ip", info.ip());
@@ -2,7 +2,6 @@ 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;
@@ -166,8 +165,9 @@ public class DbUtils {
String account
) throws SQLException {
try (Connection connection = dataSourceManager.getConnection(orgId)) {
String sql = "SELECT [b_id], [b_account], [b_name], [b_password] "
+ "FROM [b_user] WHERE [b_account] = ? AND [b_canuse] = 1";
// 字符串业务键方案:b_user.b_id 即用户账号本身,不再有独立 b_account
String sql = "SELECT [b_id], [b_name], [b_password] "
+ "FROM [b_user] WHERE [b_id] = ? AND [b_canuse] = 1";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, account);
try (ResultSet resultSet = statement.executeQuery()) {
@@ -244,23 +244,12 @@ public class DbUtils {
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;
}
/**
* 通用新增:不再自动生成主键(字符串业务键方案下,主键由请求方提供,
* 例如 s_module.b_id=模块编码、s_field 的 (b_module_id,b_field))。
* 需要数据库生成主键的场景(如未来 bigint 业务表、bf_files 文件表)
* 由调用方先取号(IdGenerator / 文件服务 subid)再随行写入。
*/
public int insert(
Connection connection,
TableMetadata table,
@@ -268,9 +257,6 @@ public class DbUtils {
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,
@@ -293,13 +279,34 @@ public class DbUtils {
ColumnMetadata keyColumn,
Map<String, Object> row
) throws SQLException {
return update(connection, table, List.of(keyColumn), row);
}
/**
* 通用修改:支持单列或多列业务键定位(字符串业务键方案下,
* 如 s_field 以 (b_module_id, b_field) 为键)。主键值从 row 中读取。
*/
public int update(
Connection connection,
TableMetadata table,
List<ColumnMetadata> keyColumns,
Map<String, Object> row
) throws SQLException {
if (keyColumns == null || keyColumns.isEmpty()) {
throw new BusinessException("更新操作必须指定主键字段");
}
Map<String, Object> normalizedRow = normalizeRow(row);
Object keyValue = requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
for (ColumnMetadata keyColumn : keyColumns) {
requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
}
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
table,
normalizedRow,
keyColumn.normalizedName()
null
);
for (ColumnMetadata keyColumn : keyColumns) {
values.remove(keyColumn);
}
if (values.isEmpty()) {
throw new BusinessException("没有可修改的字段: " + table.requestedName());
}
@@ -307,12 +314,17 @@ public class DbUtils {
String setSql = values.keySet().stream()
.map(column -> quoteColumn(column) + " = ?")
.collect(Collectors.joining(", "));
String whereSql = keyColumns.stream()
.map(column -> quoteColumn(column) + " = ?")
.collect(Collectors.joining(" AND "));
String sql = "UPDATE " + table.quotedName()
+ " SET " + setSql
+ " WHERE " + quoteColumn(keyColumn) + " = ?";
+ " WHERE " + whereSql;
List<Object> parameters = new ArrayList<>(values.values());
parameters.add(keyValue);
for (ColumnMetadata keyColumn : keyColumns) {
parameters.add(normalizedRow.get(keyColumn.normalizedName()));
}
return executeWrite(connection, sql, parameters, "修改");
}
@@ -0,0 +1,37 @@
package cn.g3soft.fmsapi.utils;
import java.security.SecureRandom;
import java.util.UUID;
/**
* 业务层 UUIDv7 生成器(RFC 9562 时间有序版本)。
* <p>UUID 主键不再依赖雪花 bigint:日志表 s_log_* 使用 uniqueidentifier 主键,
* 主键非聚集、按 b_occurdatetime 建立聚集索引,因此只需按时间大致有序即可。
* <p>生成结果满足 UUIDv7 布局:
* <pre>
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | unix_ts_ms |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | unix_ts_ms | ver | rand_a |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |var| rand_b |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* </pre>
*/
public final class UuidV7Generator {
private static final SecureRandom RANDOM = new SecureRandom();
private UuidV7Generator() {
}
public static UUID nextUuid() {
long unixTsMs = System.currentTimeMillis() & 0xFFFFFFFFFFFFL;
long mostSigBits = (unixTsMs << 16) | (RANDOM.nextLong() & 0x0FFFL);
mostSigBits = (mostSigBits & ~0xF000L) | 0x7000L;
long leastSigBits = (RANDOM.nextLong() & 0x3FFFFFFFFFFFFFFFL) | 0x8000000000000000L;
return new UUID(mostSigBits, leastSigBits);
}
}