From b3880fe3d22eb8f0ce3377177b3b0aa4cd1801c8 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Tue, 2 Jun 2026 18:57:04 +0800 Subject: [PATCH] 3 --- pom.xml | 2 + .../crm/CrmDatabaseController.java | 69 +++++++++++++++ .../crm/CrmDatabaseService.java | 83 +++++++++++++++++++ src/main/resources/application.yml | 9 ++ 4 files changed, 163 insertions(+) create mode 100644 src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java create mode 100644 src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java diff --git a/pom.xml b/pom.xml index a200b36..1d8b91b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ org.springframework.bootspring-boot-starter-webflux org.springframework.bootspring-boot-starter-validation org.springframework.bootspring-boot-starter-actuator + org.springframework.bootspring-boot-starter-jdbc + com.microsoft.sqlservermssql-jdbcruntime com.fasterxml.jackson.dataformatjackson-dataformat-xml org.springframework.bootspring-boot-starter-testtest diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java b/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java new file mode 100644 index 0000000..97826bb --- /dev/null +++ b/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java @@ -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> 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>> 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>> 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>> 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)); + } +} diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java b/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java new file mode 100644 index 0000000..7d3d783 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java @@ -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 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> 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> 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> 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("]", "]]") + "]"; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 32d0eaf..d4486cb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,6 +3,15 @@ server: spring: application: 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: endpoints: web: