3
This commit is contained in:
parent
f6dbf915f3
commit
b3880fe3d2
2
pom.xml
2
pom.xml
@ -16,6 +16,8 @@
|
|||||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>
|
||||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
|
||||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
|
||||||
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>
|
||||||
|
<dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><scope>runtime</scope></dependency>
|
||||||
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency>
|
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency>
|
||||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.company.agentfoundation.crm;
|
||||||
|
|
||||||
|
import com.company.agentfoundation.security.AgentAuthService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/v1/crm/db")
|
||||||
|
public class CrmDatabaseController {
|
||||||
|
private final AgentAuthService authService;
|
||||||
|
private final CrmDatabaseService crmDatabaseService;
|
||||||
|
|
||||||
|
public CrmDatabaseController(AgentAuthService authService, CrmDatabaseService crmDatabaseService) {
|
||||||
|
this.authService = authService;
|
||||||
|
this.crmDatabaseService = crmDatabaseService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ping")
|
||||||
|
public ResponseEntity<Map<String, Object>> ping(
|
||||||
|
@RequestHeader("X-Agent-Id") String agentId,
|
||||||
|
@RequestHeader("X-Internal-Key") String internalKey
|
||||||
|
) {
|
||||||
|
authService.authenticate(agentId, internalKey);
|
||||||
|
return ResponseEntity.ok(crmDatabaseService.ping());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tables")
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> tables(
|
||||||
|
@RequestHeader("X-Agent-Id") String agentId,
|
||||||
|
@RequestHeader("X-Internal-Key") String internalKey,
|
||||||
|
@RequestParam(value = "schema", defaultValue = "dbo") String schema,
|
||||||
|
@RequestParam(value = "keyword", defaultValue = "") String keyword,
|
||||||
|
@RequestParam(value = "limit", defaultValue = "100") int limit
|
||||||
|
) {
|
||||||
|
authService.authenticate(agentId, internalKey);
|
||||||
|
return ResponseEntity.ok(crmDatabaseService.listTables(schema, keyword, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tables/{table}/columns")
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> columns(
|
||||||
|
@RequestHeader("X-Agent-Id") String agentId,
|
||||||
|
@RequestHeader("X-Internal-Key") String internalKey,
|
||||||
|
@PathVariable String table,
|
||||||
|
@RequestParam(value = "schema", defaultValue = "dbo") String schema
|
||||||
|
) {
|
||||||
|
authService.authenticate(agentId, internalKey);
|
||||||
|
return ResponseEntity.ok(crmDatabaseService.listColumns(schema, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tables/{table}/sample")
|
||||||
|
public ResponseEntity<List<Map<String, Object>>> sample(
|
||||||
|
@RequestHeader("X-Agent-Id") String agentId,
|
||||||
|
@RequestHeader("X-Internal-Key") String internalKey,
|
||||||
|
@PathVariable String table,
|
||||||
|
@RequestParam(value = "schema", defaultValue = "dbo") String schema,
|
||||||
|
@RequestParam(value = "limit", defaultValue = "5") int limit
|
||||||
|
) {
|
||||||
|
authService.authenticate(agentId, internalKey);
|
||||||
|
return ResponseEntity.ok(crmDatabaseService.sampleRows(schema, table, limit));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package com.company.agentfoundation.crm;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CrmDatabaseService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CrmDatabaseService.class);
|
||||||
|
private static final String IDENTIFIER_PATTERN = "^[A-Za-z0-9_]+$";
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public CrmDatabaseService(JdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> ping() {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
Integer result = jdbcTemplate.queryForObject("SELECT 1", Integer.class);
|
||||||
|
long costMs = System.currentTimeMillis() - start;
|
||||||
|
log.info("crm db ping success costMs={}", costMs);
|
||||||
|
return Map.of("ok", result != null && result == 1, "costMs", costMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> listTables(String schema, String keyword, int limit) {
|
||||||
|
int safeLimit = clamp(limit, 1, 200);
|
||||||
|
String safeSchema = blankToDefault(schema, "dbo");
|
||||||
|
String safeKeyword = keyword == null ? "" : keyword.trim();
|
||||||
|
log.info("crm db listTables schema={} keyword={} limit={}", safeSchema, safeKeyword, safeLimit);
|
||||||
|
return jdbcTemplate.queryForList("""
|
||||||
|
SELECT TOP %d TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE
|
||||||
|
FROM INFORMATION_SCHEMA.TABLES
|
||||||
|
WHERE TABLE_SCHEMA = ?
|
||||||
|
AND (? = '' OR TABLE_NAME LIKE ?)
|
||||||
|
ORDER BY TABLE_NAME
|
||||||
|
""".formatted(safeLimit), safeSchema, safeKeyword, "%" + safeKeyword + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> listColumns(String schema, String table) {
|
||||||
|
String safeSchema = blankToDefault(schema, "dbo");
|
||||||
|
validateIdentifier("schema", safeSchema);
|
||||||
|
validateIdentifier("table", table);
|
||||||
|
log.info("crm db listColumns schema={} table={}", safeSchema, table);
|
||||||
|
return jdbcTemplate.queryForList("""
|
||||||
|
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, ORDINAL_POSITION
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||||
|
ORDER BY ORDINAL_POSITION
|
||||||
|
""", safeSchema, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> sampleRows(String schema, String table, int limit) {
|
||||||
|
String safeSchema = blankToDefault(schema, "dbo");
|
||||||
|
validateIdentifier("schema", safeSchema);
|
||||||
|
validateIdentifier("table", table);
|
||||||
|
int safeLimit = clamp(limit, 1, 20);
|
||||||
|
log.info("crm db sampleRows schema={} table={} limit={}", safeSchema, table, safeLimit);
|
||||||
|
return jdbcTemplate.queryForList("SELECT TOP " + safeLimit + " * FROM " + quoted(safeSchema) + "." + quoted(table));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String blankToDefault(String value, String defaultValue) {
|
||||||
|
return value == null || value.isBlank() ? defaultValue : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int clamp(int value, int min, int max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIdentifier(String name, String value) {
|
||||||
|
if (value == null || !value.matches(IDENTIFIER_PATTERN)) {
|
||||||
|
throw new IllegalArgumentException("Invalid " + name + " identifier");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String quoted(String identifier) {
|
||||||
|
return "[" + identifier.replace("]", "]]") + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,15 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: agent-foundation
|
name: agent-foundation
|
||||||
|
datasource:
|
||||||
|
url: ${CRM_DB_URL:jdbc:sqlserver://rm-uf64b8oxdt19pfyxfao.sqlserver.rds.aliyuncs.com:1433;databaseName=SmartSportsServerDB_QA;encrypt=true;trustServerCertificate=true;loginTimeout=30}
|
||||||
|
username: ${CRM_DB_USERNAME:yd}
|
||||||
|
password: ${CRM_DB_PASSWORD:}
|
||||||
|
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||||
|
hikari:
|
||||||
|
maximum-pool-size: 5
|
||||||
|
minimum-idle: 1
|
||||||
|
connection-timeout: 30000
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
web:
|
web:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user