20260717170047
This commit is contained in:
1 parent
49dafe524c
commit
35aa108a34
40 files changed
+6218
-413
No files matched your search
@@ -1,5 +1,7 @@
|
||||
package cn.g3soft.fmsapi;
|
||||
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.idgen.IdGenerator;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
@@ -9,6 +11,10 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
public class FmsApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
IdGeneratorOptions options = new IdGeneratorOptions();
|
||||
options.WorkerId = 1;
|
||||
IdGenerator.setIdGenerator(options);
|
||||
|
||||
SpringApplication.run(FmsApiApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.g3soft.fmsapi.config;
|
||||
|
||||
import cn.g3soft.fmsapi.database.OrgContext;
|
||||
import cn.g3soft.fmsapi.service.ActiveSessionRegistry;
|
||||
import cn.g3soft.fmsapi.utils.ApiResponse;
|
||||
import cn.g3soft.fmsapi.utils.JwtUtils;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
@@ -24,6 +25,9 @@ public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
@Resource
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
@Resource
|
||||
private ActiveSessionRegistry activeSessionRegistry;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@@ -39,16 +43,42 @@ public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
String orgId;
|
||||
JwtUtils.TokenClaims claims;
|
||||
try {
|
||||
String token = getToken(request);
|
||||
orgId = jwtUtils.getOrgId(token);
|
||||
OrgContext.setOrgId(orgId);
|
||||
claims = jwtUtils.parseToken(token);
|
||||
} catch (JwtException | IllegalArgumentException exception) {
|
||||
writeUnauthorized(response);
|
||||
writeUnauthorized(
|
||||
response,
|
||||
ApiResponse.AUTH_ERROR_CODE,
|
||||
"未登录或登录已失效"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeSessionRegistry.hasActiveSession(claims.orgId(), claims.userId())) {
|
||||
writeUnauthorized(
|
||||
response,
|
||||
ApiResponse.AUTH_ERROR_CODE,
|
||||
"未登录或登录已失效"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeSessionRegistry.isActive(
|
||||
claims.orgId(),
|
||||
claims.userId(),
|
||||
claims.sessionId()
|
||||
)) {
|
||||
writeUnauthorized(
|
||||
response,
|
||||
ApiResponse.SESSION_REPLACED_CODE,
|
||||
"账号已在其他设备登录,请重新登录"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
OrgContext.setOrgId(claims.orgId());
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} finally {
|
||||
@@ -64,11 +94,12 @@ public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
return authorization.substring(BEARER_PREFIX.length()).trim();
|
||||
}
|
||||
|
||||
private void writeUnauthorized(HttpServletResponse response) throws IOException {
|
||||
ApiResponse<Void> body = ApiResponse.fail(
|
||||
ApiResponse.AUTH_ERROR_CODE,
|
||||
"未登录或登录已失效"
|
||||
);
|
||||
private void writeUnauthorized(
|
||||
HttpServletResponse response,
|
||||
int code,
|
||||
String message
|
||||
) throws IOException {
|
||||
ApiResponse<Void> body = ApiResponse.fail(code, message);
|
||||
response.setStatus(200);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
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.snowflake.idgen.IdGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -19,6 +22,9 @@ public class DataController {
|
||||
@Resource
|
||||
private DataService dataService;
|
||||
|
||||
@Resource
|
||||
private DataSaveService dataSaveService;
|
||||
|
||||
@PostMapping("/loaddata")
|
||||
public ApiResponse<List<Map<String, Object>>> loadData(
|
||||
@RequestBody(required = false) Map<String, Object> params
|
||||
@@ -32,4 +38,17 @@ public class DataController {
|
||||
) throws SQLException {
|
||||
return ApiResponse.success(dataService.loadDataBySql(params));
|
||||
}
|
||||
|
||||
@PostMapping("/saveobjt")
|
||||
public ApiResponse<Void> saveObject(
|
||||
@RequestBody(required = false) List<Map<String, Object>> requests
|
||||
) throws SQLException {
|
||||
dataSaveService.save(requests);
|
||||
return ApiResponse.success("保存成功", null);
|
||||
}
|
||||
|
||||
@GetMapping("/nextid")
|
||||
public ApiResponse<Long> nextId() {
|
||||
return ApiResponse.success(IdGenerator.nextId());
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
@@ -25,6 +27,24 @@ public class GlobalExceptionHandler {
|
||||
return ApiResponse.fail(exception.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(SaveObjectException.class)
|
||||
public ApiResponse<Map<String, Object>> handleSaveObjectException(
|
||||
SaveObjectException exception
|
||||
) {
|
||||
LOGGER.error(
|
||||
"保存失败,table={},action={},index={}",
|
||||
exception.getTable(),
|
||||
exception.getAction(),
|
||||
exception.getIndex(),
|
||||
exception
|
||||
);
|
||||
Map<String, Object> details = new LinkedHashMap<>();
|
||||
details.put("table", exception.getTable());
|
||||
details.put("action", exception.getAction());
|
||||
details.put("index", exception.getIndex());
|
||||
return new ApiResponse<>(ApiResponse.SAVE_ERROR_CODE, "保存失败", details);
|
||||
}
|
||||
|
||||
@ExceptionHandler({SQLException.class, DataAccessException.class})
|
||||
public ApiResponse<Void> handleDatabaseException(
|
||||
Exception exception,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.g3soft.fmsapi.exception;
|
||||
|
||||
public class SaveObjectException extends RuntimeException {
|
||||
|
||||
private final String table;
|
||||
private final String action;
|
||||
private final int index;
|
||||
|
||||
public SaveObjectException(
|
||||
String table,
|
||||
String action,
|
||||
int index,
|
||||
Throwable cause
|
||||
) {
|
||||
super("保存失败", cause);
|
||||
this.table = table;
|
||||
this.action = action;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.g3soft.fmsapi.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
public class ActiveSessionRegistry {
|
||||
|
||||
private final Map<AccountKey, String> activeSessions = new ConcurrentHashMap<>();
|
||||
|
||||
public void activate(String orgId, String userId, String sessionId) {
|
||||
activeSessions.put(new AccountKey(orgId, userId), sessionId);
|
||||
}
|
||||
|
||||
public boolean isActive(String orgId, String userId, String sessionId) {
|
||||
if (sessionId == null || sessionId.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return sessionId.equals(activeSessions.get(new AccountKey(orgId, userId)));
|
||||
}
|
||||
|
||||
public boolean hasActiveSession(String orgId, String userId) {
|
||||
return activeSessions.containsKey(new AccountKey(orgId, userId));
|
||||
}
|
||||
|
||||
private record AccountKey(String orgId, String userId) {
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
@@ -23,6 +24,9 @@ public class AuthService {
|
||||
@Resource
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
@Resource
|
||||
private ActiveSessionRegistry activeSessionRegistry;
|
||||
|
||||
public Optional<Map<String, Object>> login(
|
||||
String orgId,
|
||||
String userId,
|
||||
@@ -33,7 +37,8 @@ public class AuthService {
|
||||
List<Map<String, Object>> users = dbUtils.loadData(
|
||||
normalizedOrgId,
|
||||
"b_user",
|
||||
"b_id = " + dbUtils.toSqlStringLiteral(normalizedUserId),
|
||||
"b_id = " + dbUtils.toSqlStringLiteral(normalizedUserId)
|
||||
+ " AND b_canuse = '1'",
|
||||
null,
|
||||
List.of("b_id", "b_name", "b_password")
|
||||
);
|
||||
@@ -49,7 +54,9 @@ public class AuthService {
|
||||
}
|
||||
|
||||
String accountId = getString(account, "b_id");
|
||||
String token = jwtUtils.generateToken(accountId, normalizedOrgId);
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
String token = jwtUtils.generateToken(accountId, normalizedOrgId, sessionId);
|
||||
activeSessionRegistry.activate(normalizedOrgId, accountId, sessionId);
|
||||
|
||||
Map<String, Object> user = new LinkedHashMap<>();
|
||||
user.put("id", accountId);
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package cn.g3soft.fmsapi.service;
|
||||
|
||||
import cn.g3soft.fmsapi.exception.BusinessException;
|
||||
import cn.g3soft.fmsapi.exception.SaveObjectException;
|
||||
import cn.g3soft.fmsapi.utils.DbUtils;
|
||||
import cn.g3soft.fmsapi.utils.ParamUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
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;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class DataSaveService {
|
||||
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
public void save(List<Map<String, Object>> requests) throws SQLException {
|
||||
if (requests == null || requests.isEmpty()) {
|
||||
throw new BusinessException("保存数据不能为空");
|
||||
}
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
boolean originalAutoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
|
||||
try {
|
||||
Map<String, DbUtils.TableMetadata> metadataCache = new LinkedHashMap<>();
|
||||
for (Map<String, Object> request : requests) {
|
||||
saveTable(connection, request, metadataCache);
|
||||
}
|
||||
connection.commit();
|
||||
} catch (SQLException | RuntimeException exception) {
|
||||
rollback(connection, exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
if (originalAutoCommit) {
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTable(
|
||||
Connection connection,
|
||||
Map<String, Object> request,
|
||||
Map<String, DbUtils.TableMetadata> metadataCache
|
||||
) {
|
||||
if (request == null) {
|
||||
throw new BusinessException("表保存配置不能为空");
|
||||
}
|
||||
|
||||
String tableName = ParamUtils.getRequiredString(request, "table");
|
||||
String keyField = ParamUtils.getRequiredString(request, "key_field");
|
||||
DbUtils.TableMetadata table = loadMetadata(
|
||||
connection,
|
||||
tableName,
|
||||
metadataCache
|
||||
);
|
||||
DbUtils.ColumnMetadata keyColumn = dbUtils.getColumn(table, keyField);
|
||||
if (keyColumn == null) {
|
||||
throw new BusinessException("主键字段不存在: " + tableName + "." + keyField);
|
||||
}
|
||||
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
"delete",
|
||||
rows(request, "deletes")
|
||||
);
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
"update",
|
||||
rows(request, "updates")
|
||||
);
|
||||
executeRows(
|
||||
connection,
|
||||
table,
|
||||
keyColumn,
|
||||
"insert",
|
||||
rows(request, "inserts")
|
||||
);
|
||||
}
|
||||
|
||||
private DbUtils.TableMetadata loadMetadata(
|
||||
Connection connection,
|
||||
String tableName,
|
||||
Map<String, DbUtils.TableMetadata> metadataCache
|
||||
) {
|
||||
String cacheKey = tableName.toLowerCase(Locale.ROOT);
|
||||
DbUtils.TableMetadata cached = metadataCache.get(cacheKey);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
DbUtils.TableMetadata loaded = dbUtils.loadTableMetadata(connection, tableName);
|
||||
metadataCache.put(cacheKey, loaded);
|
||||
return loaded;
|
||||
} catch (SQLException exception) {
|
||||
throw new SaveObjectException(tableName, "metadata", -1, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRows(
|
||||
Connection connection,
|
||||
DbUtils.TableMetadata table,
|
||||
DbUtils.ColumnMetadata keyColumn,
|
||||
String action,
|
||||
List<Map<String, Object>> rows
|
||||
) {
|
||||
for (int index = 0; index < rows.size(); index++) {
|
||||
try {
|
||||
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));
|
||||
default -> throw new IllegalArgumentException("不支持的保存操作: " + action);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
throw new SaveObjectException(table.requestedName(), action, index, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> rows(
|
||||
Map<String, Object> request,
|
||||
String fieldName
|
||||
) {
|
||||
List<?> values = ParamUtils.getList(request, fieldName);
|
||||
if (values == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
for (Object value : values) {
|
||||
if (!(value instanceof Map<?, ?>)) {
|
||||
throw new BusinessException(fieldName + " 参数类型不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> rows = (List<Map<String, Object>>) (List<?>) values;
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void rollback(Connection connection, Throwable originalException) {
|
||||
try {
|
||||
connection.rollback();
|
||||
} catch (SQLException rollbackException) {
|
||||
originalException.addSuppressed(rollbackException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ public record ApiResponse<T>(
|
||||
|
||||
public static final int SUCCESS_CODE = 0;
|
||||
public static final int AUTH_ERROR_CODE = 401;
|
||||
public static final int SESSION_REPLACED_CODE = 40101;
|
||||
public static final int SAVE_ERROR_CODE = 2001;
|
||||
public static final int SYSTEM_ERROR_CODE = 500;
|
||||
public static final int BUSINESS_ERROR_CODE = 1000;
|
||||
|
||||
@@ -15,6 +17,10 @@ public record ApiResponse<T>(
|
||||
return new ApiResponse<>(SUCCESS_CODE, "操作成功", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return new ApiResponse<>(SUCCESS_CODE, message, data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> fail(String message) {
|
||||
return fail(BUSINESS_ERROR_CODE, message);
|
||||
}
|
||||
|
||||
@@ -8,14 +8,17 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -115,6 +118,114 @@ public class DbUtils {
|
||||
return executeQuery(connection, sql);
|
||||
}
|
||||
|
||||
public TableMetadata loadTableMetadata(
|
||||
Connection connection,
|
||||
String tableName
|
||||
) throws SQLException {
|
||||
if (connection == null) {
|
||||
throw new IllegalArgumentException("数据库连接不能为空");
|
||||
}
|
||||
|
||||
String requestedName = requireText(tableName, "table");
|
||||
String quotedName = quoteQualifiedIdentifier(requestedName);
|
||||
String sql = "SELECT * FROM " + quotedName + " WHERE 1 = 0";
|
||||
LinkedHashMap<String, ColumnMetadata> columns = new LinkedHashMap<>();
|
||||
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(sql)) {
|
||||
ResultSetMetaData metadata = resultSet.getMetaData();
|
||||
for (int index = 1; index <= metadata.getColumnCount(); index++) {
|
||||
String columnName = metadata.getColumnName(index);
|
||||
validateIdentifier(columnName);
|
||||
String normalizedName = normalizeName(columnName);
|
||||
String typeName = metadata.getColumnTypeName(index);
|
||||
boolean generated = metadata.isAutoIncrement(index)
|
||||
|| metadata.isReadOnly(index)
|
||||
|| "timestamp".equalsIgnoreCase(typeName)
|
||||
|| "rowversion".equalsIgnoreCase(typeName);
|
||||
columns.put(
|
||||
normalizedName,
|
||||
new ColumnMetadata(columnName, normalizedName, !generated)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (columns.isEmpty()) {
|
||||
throw new BusinessException("目标表没有可识别字段: " + requestedName);
|
||||
}
|
||||
return new TableMetadata(requestedName, quotedName, columns);
|
||||
}
|
||||
|
||||
public ColumnMetadata getColumn(TableMetadata table, String fieldName) {
|
||||
String requiredFieldName = requireText(fieldName, "字段名");
|
||||
validateIdentifier(requiredFieldName);
|
||||
return table.columnsByName().get(normalizeName(requiredFieldName));
|
||||
}
|
||||
|
||||
public int insert(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
|
||||
table,
|
||||
normalizedRow,
|
||||
null
|
||||
);
|
||||
if (values.isEmpty()) {
|
||||
throw new BusinessException("没有可新增的字段: " + table.requestedName());
|
||||
}
|
||||
|
||||
String columnsSql = joinColumns(values.keySet().stream().toList());
|
||||
String placeholders = String.join(", ", Collections.nCopies(values.size(), "?"));
|
||||
String sql = "INSERT INTO " + table.quotedName()
|
||||
+ " (" + columnsSql + ") VALUES (" + placeholders + ")";
|
||||
return executeWrite(connection, sql, new ArrayList<>(values.values()), "新增");
|
||||
}
|
||||
|
||||
public int update(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
ColumnMetadata keyColumn,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
Object keyValue = requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
|
||||
LinkedHashMap<ColumnMetadata, Object> values = writableValues(
|
||||
table,
|
||||
normalizedRow,
|
||||
keyColumn.normalizedName()
|
||||
);
|
||||
if (values.isEmpty()) {
|
||||
throw new BusinessException("没有可修改的字段: " + table.requestedName());
|
||||
}
|
||||
|
||||
String setSql = values.keySet().stream()
|
||||
.map(column -> quoteColumn(column) + " = ?")
|
||||
.collect(Collectors.joining(", "));
|
||||
String sql = "UPDATE " + table.quotedName()
|
||||
+ " SET " + setSql
|
||||
+ " WHERE " + quoteColumn(keyColumn) + " = ?";
|
||||
|
||||
List<Object> parameters = new ArrayList<>(values.values());
|
||||
parameters.add(keyValue);
|
||||
return executeWrite(connection, sql, parameters, "修改");
|
||||
}
|
||||
|
||||
public int delete(
|
||||
Connection connection,
|
||||
TableMetadata table,
|
||||
ColumnMetadata keyColumn,
|
||||
Map<String, Object> row
|
||||
) throws SQLException {
|
||||
Map<String, Object> normalizedRow = normalizeRow(row);
|
||||
Object keyValue = requiredKeyValue(normalizedRow, keyColumn, table.requestedName());
|
||||
String sql = "DELETE FROM " + table.quotedName()
|
||||
+ " WHERE " + quoteColumn(keyColumn) + " = ?";
|
||||
return executeWrite(connection, sql, List.of(keyValue), "删除");
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> executeQuery(
|
||||
Connection connection,
|
||||
String sql
|
||||
@@ -126,6 +237,24 @@ public class DbUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private int executeWrite(
|
||||
Connection connection,
|
||||
String sql,
|
||||
List<Object> parameters,
|
||||
String action
|
||||
) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (int index = 0; index < parameters.size(); index++) {
|
||||
statement.setObject(index + 1, parameters.get(index));
|
||||
}
|
||||
int affectedRows = statement.executeUpdate();
|
||||
if (affectedRows != 1) {
|
||||
throw new SQLException(action + "记录数异常: " + affectedRows);
|
||||
}
|
||||
return affectedRows;
|
||||
}
|
||||
}
|
||||
|
||||
public String toSqlStringLiteral(String value) {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
@@ -212,7 +341,7 @@ public class DbUtils {
|
||||
return quoteQualifiedIdentifier(parts[0]) + direction;
|
||||
}
|
||||
|
||||
private String quoteQualifiedIdentifier(String identifier) {
|
||||
public String quoteQualifiedIdentifier(String identifier) {
|
||||
if (!hasText(identifier)) {
|
||||
throw new BusinessException("数据库标识符不能为空");
|
||||
}
|
||||
@@ -223,6 +352,75 @@ public class DbUtils {
|
||||
.collect(Collectors.joining("."));
|
||||
}
|
||||
|
||||
private LinkedHashMap<ColumnMetadata, Object> writableValues(
|
||||
TableMetadata table,
|
||||
Map<String, Object> normalizedRow,
|
||||
String excludedColumn
|
||||
) {
|
||||
LinkedHashMap<ColumnMetadata, Object> values = new LinkedHashMap<>();
|
||||
for (ColumnMetadata column : table.columnsByName().values()) {
|
||||
if (!column.writable()
|
||||
|| column.normalizedName().equals(excludedColumn)
|
||||
|| !normalizedRow.containsKey(column.normalizedName())) {
|
||||
continue;
|
||||
}
|
||||
values.put(column, normalizedRow.get(column.normalizedName()));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private Object requiredKeyValue(
|
||||
Map<String, Object> normalizedRow,
|
||||
ColumnMetadata keyColumn,
|
||||
String tableName
|
||||
) {
|
||||
Object value = normalizedRow.get(keyColumn.normalizedName());
|
||||
if (value == null || value instanceof String text && text.isBlank()) {
|
||||
throw new BusinessException(
|
||||
"缺少主键字段: " + tableName + "." + keyColumn.name()
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Map<String, Object> normalizeRow(Map<String, Object> row) {
|
||||
if (row == null) {
|
||||
throw new BusinessException("保存记录不能为空");
|
||||
}
|
||||
|
||||
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Object> entry : row.entrySet()) {
|
||||
String fieldName = requireText(entry.getKey(), "字段名");
|
||||
String normalizedName = normalizeName(fieldName);
|
||||
if (normalized.containsKey(normalizedName)) {
|
||||
throw new BusinessException("保存记录包含重复字段: " + fieldName);
|
||||
}
|
||||
normalized.put(normalizedName, entry.getValue());
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String joinColumns(List<ColumnMetadata> columns) {
|
||||
return columns.stream()
|
||||
.map(this::quoteColumn)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
private String quoteColumn(ColumnMetadata column) {
|
||||
return quoteQualifiedIdentifier(column.name());
|
||||
}
|
||||
|
||||
private String requireText(String value, String fieldName) {
|
||||
if (!hasText(value)) {
|
||||
throw new BusinessException(fieldName + " 不能为空");
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private String normalizeName(String value) {
|
||||
return value.trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> readRows(ResultSet resultSet) throws SQLException {
|
||||
ResultSetMetaData metadata = resultSet.getMetaData();
|
||||
int columnCount = metadata.getColumnCount();
|
||||
@@ -242,4 +440,18 @@ public class DbUtils {
|
||||
private boolean hasText(String value) {
|
||||
return value != null && !value.isBlank();
|
||||
}
|
||||
|
||||
public record ColumnMetadata(
|
||||
String name,
|
||||
String normalizedName,
|
||||
boolean writable
|
||||
) {
|
||||
}
|
||||
|
||||
public record TableMetadata(
|
||||
String requestedName,
|
||||
String quotedName,
|
||||
LinkedHashMap<String, ColumnMetadata> columnsByName
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package cn.g3soft.fmsapi.utils;
|
||||
|
||||
import cn.g3soft.fmsapi.config.AuthProperties;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -21,6 +23,7 @@ public class JwtUtils {
|
||||
|
||||
private SecretKey signingKey;
|
||||
private Duration expiration;
|
||||
private JwtParser jwtParser;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
@@ -36,14 +39,16 @@ public class JwtUtils {
|
||||
|
||||
this.signingKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
|
||||
this.expiration = properties.getJwtExpiration();
|
||||
this.jwtParser = Jwts.parser().verifyWith(signingKey).build();
|
||||
}
|
||||
|
||||
public String generateToken(String userId, String orgId) {
|
||||
public String generateToken(String userId, String orgId, String sessionId) {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(expiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.subject(userId)
|
||||
.id(sessionId)
|
||||
.claim("orgid", orgId)
|
||||
.issuedAt(Date.from(issuedAt))
|
||||
.expiration(Date.from(expiresAt))
|
||||
@@ -51,12 +56,26 @@ public class JwtUtils {
|
||||
.compact();
|
||||
}
|
||||
|
||||
public TokenClaims parseToken(String token) {
|
||||
Claims claims = jwtParser.parseSignedClaims(token).getPayload();
|
||||
return new TokenClaims(
|
||||
required(claims.getSubject(), "用户ID"),
|
||||
required(claims.get("orgid", String.class), "机构码"),
|
||||
required(claims.getId(), "会话ID")
|
||||
);
|
||||
}
|
||||
|
||||
public String getOrgId(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(signingKey)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload()
|
||||
.get("orgid", String.class);
|
||||
return parseToken(token).orgId();
|
||||
}
|
||||
|
||||
private String required(String value, String claimName) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("登录凭证缺少" + claimName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public record TokenClaims(String userId, String orgId, String sessionId) {
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.contract;
|
||||
|
||||
public interface IIdGenerator {
|
||||
long newLong() throws IdGeneratorException;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.contract;
|
||||
|
||||
public interface ISnowWorker {
|
||||
long nextId() throws IdGeneratorException;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.contract;
|
||||
|
||||
public class IdGeneratorException extends RuntimeException {
|
||||
|
||||
public IdGeneratorException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IdGeneratorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdGeneratorException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public IdGeneratorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IdGeneratorException(String msgFormat, Object... args) {
|
||||
super(String.format(msgFormat, args));
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.contract;
|
||||
|
||||
public class IdGeneratorOptions {
|
||||
|
||||
/**
|
||||
* 雪花计算方法
|
||||
* (1-漂移算法|2-传统算法),默认1
|
||||
*/
|
||||
public short Method = 1;
|
||||
|
||||
/**
|
||||
* 基础时间(ms单位)
|
||||
* 不能超过当前系统时间
|
||||
*/
|
||||
public long BaseTime = 1582136402000L;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
* 必须由外部设定,最大值 2^WorkerIdBitLength-1
|
||||
*/
|
||||
public short WorkerId = 0;
|
||||
|
||||
/**
|
||||
* 机器码位长
|
||||
* 默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
|
||||
*/
|
||||
public byte WorkerIdBitLength = 6;
|
||||
|
||||
/**
|
||||
* 序列数位长
|
||||
* 默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
|
||||
*/
|
||||
public byte SeqBitLength = 6;
|
||||
|
||||
/**
|
||||
* 最大序列数(含)
|
||||
* 设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1])
|
||||
*/
|
||||
public short MaxSeqNumber = 0;
|
||||
|
||||
/**
|
||||
* 最小序列数(含)
|
||||
* 默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号是0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位
|
||||
*/
|
||||
public short MinSeqNumber = 5;
|
||||
|
||||
/**
|
||||
* 最大漂移次数(含)
|
||||
* 默认2000,推荐范围500-10000(与计算能力有关)
|
||||
*/
|
||||
public short TopOverCostCount = 2000;
|
||||
|
||||
public IdGeneratorOptions() {
|
||||
|
||||
}
|
||||
|
||||
public IdGeneratorOptions(short workerId) {
|
||||
WorkerId = workerId;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.contract;
|
||||
/**
|
||||
* Id生成时回调参数
|
||||
*/
|
||||
public class OverCostActionArg {
|
||||
/**
|
||||
* 事件类型
|
||||
* 1-开始,2-结束,8-漂移
|
||||
*/
|
||||
public int ActionType = 0;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
public long TimeTick = 0;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
*/
|
||||
public short WorkerId = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public int OverCostCountInOneTerm = 0;
|
||||
|
||||
/**
|
||||
* 漂移期间生产ID个数
|
||||
*/
|
||||
public int GenCountInOneTerm = 0;
|
||||
|
||||
/**
|
||||
* 漂移周期
|
||||
*/
|
||||
public int TermIndex = 0;
|
||||
|
||||
public OverCostActionArg(short workerId, long timeTick, int actionType, int overCostCountInOneTerm, int genCountWhenOverCost, int index) {
|
||||
ActionType = actionType;
|
||||
TimeTick = timeTick;
|
||||
WorkerId = workerId;
|
||||
OverCostCountInOneTerm = overCostCountInOneTerm;
|
||||
GenCountInOneTerm = genCountWhenOverCost;
|
||||
TermIndex = index;
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.core;
|
||||
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.ISnowWorker;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.OverCostActionArg;
|
||||
|
||||
public class SnowWorkerM1 implements ISnowWorker {
|
||||
/**
|
||||
* 基础时间
|
||||
*/
|
||||
protected final long BaseTime;
|
||||
|
||||
/**
|
||||
* 机器码
|
||||
*/
|
||||
protected final short WorkerId;
|
||||
|
||||
/**
|
||||
* 机器码位长
|
||||
*/
|
||||
protected final byte WorkerIdBitLength;
|
||||
|
||||
/**
|
||||
* 自增序列数位长
|
||||
*/
|
||||
protected final byte SeqBitLength;
|
||||
|
||||
/**
|
||||
* 最大序列数(含)
|
||||
*/
|
||||
protected final int MaxSeqNumber;
|
||||
|
||||
/**
|
||||
* 最小序列数(含)
|
||||
*/
|
||||
protected final short MinSeqNumber;
|
||||
|
||||
/**
|
||||
* 最大漂移次数(含)
|
||||
*/
|
||||
protected final int TopOverCostCount;
|
||||
|
||||
protected final byte _TimestampShift;
|
||||
protected final static byte[] _SyncLock = new byte[0];
|
||||
|
||||
protected short _CurrentSeqNumber;
|
||||
protected long _LastTimeTick = 0;
|
||||
protected long _TurnBackTimeTick = 0;
|
||||
protected byte _TurnBackIndex = 0;
|
||||
|
||||
protected boolean _IsOverCost = false;
|
||||
protected int _OverCostCountInOneTerm = 0;
|
||||
protected int _GenCountInOneTerm = 0;
|
||||
protected int _TermIndex = 0;
|
||||
|
||||
public SnowWorkerM1(IdGeneratorOptions options) {
|
||||
BaseTime = options.BaseTime != 0 ? options.BaseTime : 1582136402000L;
|
||||
WorkerIdBitLength = options.WorkerIdBitLength == 0 ? 6 : options.WorkerIdBitLength;
|
||||
WorkerId = options.WorkerId;
|
||||
SeqBitLength = options.SeqBitLength == 0 ? 6 : options.SeqBitLength;
|
||||
MaxSeqNumber = options.MaxSeqNumber <= 0 ? (1 << SeqBitLength) - 1 : options.MaxSeqNumber;
|
||||
MinSeqNumber = options.MinSeqNumber;
|
||||
// TopOverCostCount = options.TopOverCostCount == 0 ? 2000 : options.TopOverCostCount;
|
||||
TopOverCostCount = options.TopOverCostCount;
|
||||
_TimestampShift = (byte) (WorkerIdBitLength + SeqBitLength);
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
}
|
||||
|
||||
private void DoGenIdAction(OverCostActionArg arg) {
|
||||
|
||||
}
|
||||
|
||||
private void BeginOverCostAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private void EndOverCostAction(long useTimeTick) {
|
||||
// if (_TermIndex > 10000) {
|
||||
// _TermIndex = 0;
|
||||
// }
|
||||
}
|
||||
|
||||
private void BeginTurnBackAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private void EndTurnBackAction(long useTimeTick) {
|
||||
|
||||
}
|
||||
|
||||
private long NextOverCostId() {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (currentTimeTick > _LastTimeTick) {
|
||||
EndOverCostAction(currentTimeTick);
|
||||
|
||||
_LastTimeTick = currentTimeTick;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = false;
|
||||
_OverCostCountInOneTerm = 0;
|
||||
_GenCountInOneTerm = 0;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_OverCostCountInOneTerm >= TopOverCostCount) {
|
||||
EndOverCostAction(currentTimeTick);
|
||||
|
||||
_LastTimeTick = GetNextTimeTick();
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = false;
|
||||
_OverCostCountInOneTerm = 0;
|
||||
_GenCountInOneTerm = 0;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_CurrentSeqNumber > MaxSeqNumber) {
|
||||
_LastTimeTick++;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = true;
|
||||
_OverCostCountInOneTerm++;
|
||||
_GenCountInOneTerm++;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
_GenCountInOneTerm++;
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
private long NextNormalId() throws IdGeneratorException {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (currentTimeTick < _LastTimeTick) {
|
||||
if (_TurnBackTimeTick < 1) {
|
||||
_TurnBackTimeTick = _LastTimeTick - 1;
|
||||
_TurnBackIndex++;
|
||||
// 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序
|
||||
// 支持4次回拨次序(避免回拨重叠导致ID重复),可无限次回拨(次序循环使用)。
|
||||
if (_TurnBackIndex > 4) {
|
||||
_TurnBackIndex = 1;
|
||||
}
|
||||
//BeginTurnBackAction(_TurnBackTimeTick);
|
||||
}
|
||||
|
||||
// try {
|
||||
// Thread.sleep(1);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
return CalcTurnBackId(_TurnBackTimeTick);
|
||||
}
|
||||
|
||||
// 时间追平时,_TurnBackTimeTick清零
|
||||
if (_TurnBackTimeTick > 0) {
|
||||
EndTurnBackAction(_TurnBackTimeTick);
|
||||
_TurnBackTimeTick = 0;
|
||||
}
|
||||
|
||||
if (currentTimeTick > _LastTimeTick) {
|
||||
_LastTimeTick = currentTimeTick;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
if (_CurrentSeqNumber > MaxSeqNumber) {
|
||||
BeginOverCostAction(currentTimeTick);
|
||||
|
||||
_TermIndex++;
|
||||
_LastTimeTick++;
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
_IsOverCost = true;
|
||||
_OverCostCountInOneTerm = 1;
|
||||
_GenCountInOneTerm = 1;
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
return CalcId(_LastTimeTick);
|
||||
}
|
||||
|
||||
private long CalcId(long useTimeTick) {
|
||||
long result = ((useTimeTick << _TimestampShift) +
|
||||
((long) WorkerId << SeqBitLength) +
|
||||
(int) _CurrentSeqNumber);
|
||||
|
||||
_CurrentSeqNumber++;
|
||||
return result;
|
||||
}
|
||||
|
||||
private long CalcTurnBackId(long useTimeTick) {
|
||||
long result = ((useTimeTick << _TimestampShift) +
|
||||
((long) WorkerId << SeqBitLength) + _TurnBackIndex);
|
||||
|
||||
_TurnBackTimeTick--;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected long GetCurrentTimeTick() {
|
||||
long millis = System.currentTimeMillis();
|
||||
return millis - BaseTime;
|
||||
}
|
||||
|
||||
protected long GetNextTimeTick() {
|
||||
long tempTimeTicker = GetCurrentTimeTick();
|
||||
|
||||
while (tempTimeTicker <= _LastTimeTick) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
tempTimeTicker = GetCurrentTimeTick();
|
||||
}
|
||||
|
||||
return tempTimeTicker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextId() {
|
||||
synchronized (_SyncLock) {
|
||||
return _IsOverCost ? NextOverCostId() : NextNormalId();
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.core;
|
||||
|
||||
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
|
||||
public class SnowWorkerM2 extends SnowWorkerM1 {
|
||||
public SnowWorkerM2(IdGeneratorOptions options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextId() {
|
||||
synchronized (_SyncLock) {
|
||||
long currentTimeTick = GetCurrentTimeTick();
|
||||
|
||||
if (_LastTimeTick == currentTimeTick) {
|
||||
if (_CurrentSeqNumber++ > MaxSeqNumber) {
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
currentTimeTick = GetNextTimeTick();
|
||||
}
|
||||
} else {
|
||||
_CurrentSeqNumber = MinSeqNumber;
|
||||
}
|
||||
|
||||
if (currentTimeTick < _LastTimeTick) {
|
||||
throw new IdGeneratorException("Time error for {0} milliseconds", _LastTimeTick - currentTimeTick);
|
||||
}
|
||||
|
||||
_LastTimeTick = currentTimeTick;
|
||||
long result = ((currentTimeTick << _TimestampShift) + ((long) WorkerId << SeqBitLength) + (int) _CurrentSeqNumber);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.idgen;
|
||||
|
||||
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IIdGenerator;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.ISnowWorker;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.core.SnowWorkerM1;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.core.SnowWorkerM2;
|
||||
|
||||
public class DefaultIdGenerator implements IIdGenerator {
|
||||
|
||||
private static ISnowWorker _SnowWorker = null;
|
||||
|
||||
public DefaultIdGenerator(IdGeneratorOptions options) throws IdGeneratorException {
|
||||
if (options == null) {
|
||||
throw new IdGeneratorException("options error.");
|
||||
}
|
||||
|
||||
// 1.BaseTime
|
||||
if (options.BaseTime < 315504000000L || options.BaseTime > System.currentTimeMillis()) {
|
||||
throw new IdGeneratorException("BaseTime error.");
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if (options.WorkerIdBitLength <= 0) {
|
||||
throw new IdGeneratorException("WorkerIdBitLength error.(range:[1, 21])");
|
||||
}
|
||||
if (options.WorkerIdBitLength + options.SeqBitLength > 22) {
|
||||
throw new IdGeneratorException("error:WorkerIdBitLength + SeqBitLength <= 22");
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
int maxWorkerIdNumber = (1 << options.WorkerIdBitLength) - 1;
|
||||
if (maxWorkerIdNumber == 0) {
|
||||
maxWorkerIdNumber = 63;
|
||||
}
|
||||
if (options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber) {
|
||||
throw new IdGeneratorException(
|
||||
"WorkerId error. (range:[0, " + (maxWorkerIdNumber > 0 ? maxWorkerIdNumber : 63) + "]");
|
||||
}
|
||||
|
||||
// 4.SeqBitLength
|
||||
if (options.SeqBitLength < 2 || options.SeqBitLength > 21) {
|
||||
throw new IdGeneratorException("SeqBitLength error. (range:[2, 21])");
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
int maxSeqNumber = (1 << options.SeqBitLength) - 1;
|
||||
if (maxSeqNumber == 0) {
|
||||
maxSeqNumber = 63;
|
||||
}
|
||||
if (options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber) {
|
||||
throw new IdGeneratorException("MaxSeqNumber error. (range:[1, " + maxSeqNumber + "]");
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
if (options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber) {
|
||||
throw new IdGeneratorException("MinSeqNumber error. (range:[5, " + maxSeqNumber + "]");
|
||||
}
|
||||
|
||||
// 7.TopOverCostCount
|
||||
if (options.TopOverCostCount < 0 || options.TopOverCostCount > 10000) {
|
||||
throw new IdGeneratorException("TopOverCostCount error. (range:[0, 10000]");
|
||||
}
|
||||
|
||||
switch (options.Method) {
|
||||
case 2:
|
||||
_SnowWorker = new SnowWorkerM2(options);
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
_SnowWorker = new SnowWorkerM1(options);
|
||||
break;
|
||||
}
|
||||
|
||||
if (options.Method == 1) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newLong() {
|
||||
return _SnowWorker.nextId();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.g3soft.fmsapi.utils.snowflake.idgen;
|
||||
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IIdGenerator;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorException;
|
||||
import cn.g3soft.fmsapi.utils.snowflake.contract.IdGeneratorOptions;
|
||||
import lombok.Getter;
|
||||
|
||||
public class IdGenerator {
|
||||
@Getter
|
||||
private static IIdGenerator idGenInstance = null;
|
||||
|
||||
/**
|
||||
* 设置参数,建议程序初始化时执行一次
|
||||
*/
|
||||
public static void setIdGenerator(IdGeneratorOptions options) throws IdGeneratorException {
|
||||
idGenInstance = new DefaultIdGenerator(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新的Id
|
||||
* 调用本方法前,请确保调用了 SetIdGenerator 方法做初始化。
|
||||
*
|
||||
*/
|
||||
public static long nextId() throws IdGeneratorException {
|
||||
// if (idGenInstance == null) {
|
||||
// idGenInstance = new DefaultIdGenerator(new IdGeneratorOptions((short) 1));
|
||||
// }
|
||||
|
||||
if (idGenInstance == null)
|
||||
throw new IdGeneratorException("Please initialize Yitter.IdGeneratorOptions first.");
|
||||
|
||||
return idGenInstance.newLong();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
server:
|
||||
port: 8088
|
||||
servlet:
|
||||
context-path: /api
|
||||
|
||||
|
||||
Reference in new issue
Block a user