20260905172534
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user