添加数据贯通代码未调试

This commit is contained in:
2026-04-25 18:19:29 +08:00
parent 7fa0a7d38f
commit 73b65962f2
9 changed files with 1197 additions and 0 deletions

View File

@@ -0,0 +1,411 @@
package com.klp.framework.client;
import com.klp.framework.config.SqlServerApiProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* sql-server-api 统一调用客户端。
* <p>
* 对齐 {@code sql-server-api/app.py} 的接口规范:
* <ul>
* <li>GET /health</li>
* <li>GET /heartbeat</li>
* <li>POST /table-schema</li>
* <li>POST /execute-sql</li>
* </ul>
*/
@Component
public class SqlServerApiClient {
private final RestTemplate restTemplate;
private final SqlServerApiProperties properties;
public SqlServerApiClient(RestTemplate sqlServerApiRestTemplate, SqlServerApiProperties properties) {
this.restTemplate = sqlServerApiRestTemplate;
this.properties = properties;
}
public String getBaseUrl() {
return normalizeBaseUrl();
}
public Map<String, Object> health() {
return getForMap("/health");
}
public Map<String, Object> heartbeat() {
return getForMap("/heartbeat");
}
public TableSchemaResponse tableSchema(String tableType, String tableName) {
return postForObject("/table-schema", new TableSchemaRequest(tableType, tableName), TableSchemaResponse.class);
}
public ExecuteSqlResponse executeSql(String tableType, String sql, Map<String, Object> params) {
return postForObject("/execute-sql", new ExecuteSqlRequest(tableType, sql, params), ExecuteSqlResponse.class);
}
public <T> T get(String path, Class<T> responseType) {
return restTemplate.getForObject(buildUri(path), responseType);
}
public <T> T get(String path, Map<String, ?> queryParams, Class<T> responseType) {
URI uri = UriComponentsBuilder.fromHttpUrl(normalizeBaseUrl())
.path(normalizePath(path))
.queryParams(convertToQueryParams(queryParams))
.build(true)
.toUri();
return restTemplate.getForObject(uri, responseType);
}
public <T, R> T post(String path, R requestBody, Class<T> responseType) {
return restTemplate.postForObject(buildUri(path), requestBody, responseType);
}
public static class TableSchemaRequest {
private String tableType;
private String tableName;
public TableSchemaRequest() {
}
public TableSchemaRequest(String tableType, String tableName) {
this.tableType = tableType;
this.tableName = tableName;
}
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
}
public static class ExecuteSqlRequest {
private String tableType;
private String sql;
private Map<String, Object> params;
public ExecuteSqlRequest() {
}
public ExecuteSqlRequest(String tableType, String sql, Map<String, Object> params) {
this.tableType = tableType;
this.sql = sql;
this.params = params;
}
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
}
public static class TableSchemaResponse {
private String tableType;
private String tableName;
private Schema schema;
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Schema getSchema() {
return schema;
}
public void setSchema(Schema schema) {
this.schema = schema;
}
public static class Schema {
private List<Column> columns = Collections.emptyList();
public List<Column> getColumns() {
return columns;
}
public void setColumns(List<Column> columns) {
this.columns = columns;
}
}
public static class Column {
private String name;
private String type;
private boolean nullable;
private Object defaultValue;
private boolean primaryKey;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isNullable() {
return nullable;
}
public void setNullable(boolean nullable) {
this.nullable = nullable;
}
public Object getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public boolean isPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(boolean primaryKey) {
this.primaryKey = primaryKey;
}
}
}
public static class ExecuteSqlResponse {
private String tableType;
private String resultType;
private Integer rowCount;
private Object result;
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public Integer getRowCount() {
return rowCount;
}
public void setRowCount(Integer rowCount) {
this.rowCount = rowCount;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
private Map<String, Object> getForMap(String path) {
ResponseEntity<Map> responseEntity = restTemplate.exchange(buildUri(path), HttpMethod.GET, HttpEntity.EMPTY, Map.class);
Map<String, Object> body = responseEntity.getBody();
return body == null ? Collections.emptyMap() : body;
}
private <T> T postForObject(String path, Object requestBody, Class<T> responseType) {
return restTemplate.postForObject(buildUri(path), requestBody, responseType);
}
private URI buildUri(String path) {
return UriComponentsBuilder.fromHttpUrl(normalizeBaseUrl())
.path(normalizePath(path))
.build(true)
.toUri();
}
private String normalizeBaseUrl() {
if (properties.getBaseUrl() != null && properties.getBaseUrl().trim().length() > 0) {
return properties.getBaseUrl();
}
if (properties.getHost() == null || properties.getHost().trim().length() == 0 || properties.getPort() == null) {
throw new IllegalStateException("sql-server-api 配置缺失,请检查 host/port/base-url");
}
return "http://" + properties.getHost() + ":" + properties.getPort();
}
private String normalizePath(String path) {
if (path == null || path.trim().length() == 0) {
return "";
}
return path.startsWith("/") ? path : "/" + path;
}
public ExecuteSqlResponse queryPlanByCoilId(String coilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where COILID = :coilId",
singletonParam("coilId", coilId)
);
}
public ExecuteSqlResponse queryPlanList() {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN order by INSDATE desc",
emptyParams()
);
}
public ExecuteSqlResponse queryPlanListByProcessCode(String processCode) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where PROCESS_CODE = :processCode order by INSDATE desc",
singletonParam("processCode", processCode)
);
}
public ExecuteSqlResponse queryPlanListByStatus(String status) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where STATUS = :status order by INSDATE desc",
singletonParam("status", status)
);
}
public ExecuteSqlResponse queryPlanByHotCoilId(String hotCoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where HOT_COILID = :hotCoilId",
singletonParam("hotCoilId", hotCoilId)
);
}
public ExecuteSqlResponse queryProSegByEncoilId(String encoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PRO_SEG where ENCOILID = :encoilId order by SEGNO",
singletonParam("encoilId", encoilId)
);
}
public ExecuteSqlResponse queryProSegByExcoilId(String excoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PRO_SEG where EXCOILID = :excoilId order by SEGNO",
singletonParam("excoilId", excoilId)
);
}
public ExecuteSqlResponse queryQualityByExcoilId(String excoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_PLTCM_PDO_QUALITY where EXCOILID = :excoilId order by END_DATE desc",
singletonParam("excoilId", excoilId)
);
}
public ExecuteSqlResponse queryGaugeByMatId(String matId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_VBDA_GAUGE where MATID = :matId order by XTIME asc",
singletonParam("matId", matId)
);
}
public ExecuteSqlResponse queryShapeByMatId(String matId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_VBDA_SHAPE where MATID = :matId order by XTIME asc",
singletonParam("matId", matId)
);
}
private org.springframework.util.MultiValueMap<String, String> convertToQueryParams(Map<String, ?> queryParams) {
org.springframework.util.LinkedMultiValueMap<String, String> multiValueMap = new org.springframework.util.LinkedMultiValueMap<>();
if (queryParams == null || queryParams.isEmpty()) {
return multiValueMap;
}
for (Map.Entry<String, ?> entry : queryParams.entrySet()) {
Object value = entry.getValue();
if (value != null) {
multiValueMap.add(entry.getKey(), String.valueOf(value));
}
}
return multiValueMap;
}
private Map<String, Object> singletonParam(String key, Object value) {
java.util.HashMap<String, Object> params = new java.util.HashMap<String, Object>();
if (value != null) {
params.put(key, value);
}
return params;
}
private Map<String, Object> emptyParams() {
return new java.util.HashMap<String, Object>();
}
}

View File

@@ -0,0 +1,23 @@
package com.klp.framework.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
/**
* sql-server-api HTTP 客户端配置
*/
@Configuration
public class SqlServerApiClientConfig {
@Bean
public RestTemplate sqlServerApiRestTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(30))
.build();
}
}

View File

@@ -0,0 +1,51 @@
package com.klp.framework.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* sql-server-api 中间件配置
*/
@Component
@ConfigurationProperties(prefix = "sql-server-api")
public class SqlServerApiProperties {
/**
* 请求地址
*/
private String host;
/**
* 请求端口
*/
private Integer port;
/**
* 基础访问地址
*/
private String baseUrl;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}

View File

@@ -0,0 +1,306 @@
package com.klp.framework.service;
import com.klp.framework.client.SqlServerApiClient;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 面向业务场景的 sql-server-api 查询服务。
* <p>
* 当前抽象为三个功能:
* <ol>
* <li>计划查询</li>
* <li>钢卷实际 / SEG 查询</li>
* <li>实时数据Gauge / Shape查询</li>
* </ol>
*/
@Service
public class SqlServerApiBusinessService {
private final SqlServerApiClient client;
public SqlServerApiBusinessService(SqlServerApiClient client) {
this.client = client;
}
/**
* 计划列表:查询所有计划,按时间倒序。
* <p>
* 这是后续按 coilId 关联 SEG、实时数据的入口。
*/
public PlanListView getPlanList() {
return PlanListView.fromExecuteSqlResponse(client.queryPlanList());
}
/**
* 计划列表:按工艺流程编码筛选。
*/
public PlanListView getPlanListByProcessCode(String processCode) {
return PlanListView.fromExecuteSqlResponse(client.queryPlanListByProcessCode(processCode));
}
/**
* 计划列表:按状态筛选。
*/
public PlanListView getPlanListByStatus(String status) {
return PlanListView.fromExecuteSqlResponse(client.queryPlanListByStatus(status));
}
/**
* 计划详情:按成品卷号查询单条计划。
*/
public PlanDetailView getPlanByCoilId(String coilId) {
return PlanDetailView.fromExecuteSqlResponse(coilId, client.queryPlanByCoilId(coilId));
}
/**
* 计划详情:按热卷号查询。
*/
public PlanDetailView getPlanByHotCoilId(String hotCoilId) {
return PlanDetailView.fromExecuteSqlResponse(hotCoilId, client.queryPlanByHotCoilId(hotCoilId));
}
/**
* 钢卷实际 SEG 查询:按入口卷号查询。
* <p>
* 返回的是按 SEGNO 排序后的原始行数据。
*/
public SegSeriesView getSegByEncoilId(String encoilId) {
return SegSeriesView.fromExecuteSqlResponse(encoilId, client.queryProSegByEncoilId(encoilId));
}
/**
* 钢卷实际 SEG 查询:按出口卷号查询。
*/
public SegSeriesView getSegByExcoilId(String excoilId) {
return SegSeriesView.fromExecuteSqlResponse(excoilId, client.queryProSegByExcoilId(excoilId));
}
/**
* 钢卷 SEG 视图数据:把同一钢卷下按 SEGNO 排序的多行数据,整理成“一个钢卷 id + 一个属性对应一个列表”的结构。
* <p>
* 例如:厚度、张力、速度都会被整理成数组,方便前端直接画表或曲线。
*/
public SegSeriesView getSegSeriesViewByEncoilId(String encoilId) {
return getSegByEncoilId(encoilId);
}
/**
* 实时数据:厚度/仪表数据。
*/
public SqlServerApiClient.ExecuteSqlResponse getRealtimeGaugeByMatId(String matId) {
return client.queryGaugeByMatId(matId);
}
/**
* 实时数据:板形/形状数据。
*/
public SqlServerApiClient.ExecuteSqlResponse getRealtimeShapeByMatId(String matId) {
return client.queryShapeByMatId(matId);
}
/**
* 实时数据总入口:一次性返回厚度与板形两类数据。
*/
public RealtimeDataBundle getRealtimeData(String matId) {
return new RealtimeDataBundle(
getRealtimeGaugeByMatId(matId),
getRealtimeShapeByMatId(matId)
);
}
private static PlanListView toPlanListView(SqlServerApiClient.ExecuteSqlResponse response) {
return PlanListView.fromExecuteSqlResponse(response);
}
private static List<Map<String, Object>> asRowList(SqlServerApiClient.ExecuteSqlResponse response) {
if (response == null || response.getResult() == null) {
return Collections.emptyList();
}
Object result = response.getResult();
if (result instanceof List) {
List<?> list = (List<?>) result;
List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
for (Object item : list) {
if (item instanceof Map) {
rows.add((Map<String, Object>) item);
}
}
return rows;
}
return Collections.emptyList();
}
private static Number asNumber(Object value) {
if (value instanceof Number) {
return (Number) value;
}
if (value == null) {
return null;
}
try {
return new BigDecimal(String.valueOf(value));
} catch (Exception ex) {
return null;
}
}
private static String asString(Object value) {
return value == null ? null : String.valueOf(value);
}
public static class PlanListView {
private final List<String> coilIds;
private final List<Map<String, Object>> rows;
public PlanListView(List<String> coilIds, List<Map<String, Object>> rows) {
this.coilIds = coilIds;
this.rows = rows;
}
public List<String> getCoilIds() {
return coilIds;
}
public List<Map<String, Object>> getRows() {
return rows;
}
public static PlanListView fromExecuteSqlResponse(SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
List<String> coilIds = rows.stream()
.map(row -> asString(row.get("COILID")))
.filter(value -> value != null && value.trim().length() > 0)
.distinct()
.collect(Collectors.toList());
return new PlanListView(coilIds, rows);
}
}
public static class PlanDetailView {
private final String coilId;
private final List<Map<String, Object>> rows;
private final Map<String, Object> firstRow;
public PlanDetailView(String coilId, List<Map<String, Object>> rows, Map<String, Object> firstRow) {
this.coilId = coilId;
this.rows = rows;
this.firstRow = firstRow;
}
public String getCoilId() {
return coilId;
}
public List<Map<String, Object>> getRows() {
return rows;
}
public Map<String, Object> getFirstRow() {
return firstRow;
}
public static PlanDetailView fromExecuteSqlResponse(String coilId, SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
Map<String, Object> firstRow = rows.isEmpty() ? Collections.<String, Object>emptyMap() : rows.get(0);
return new PlanDetailView(coilId, rows, firstRow);
}
}
public static class SegSeriesView {
private final String coilId;
private final List<Integer> segNo;
private final Map<String, List<Object>> series;
private final List<Map<String, Object>> rows;
public SegSeriesView(String coilId, List<Integer> segNo, Map<String, List<Object>> series, List<Map<String, Object>> rows) {
this.coilId = coilId;
this.segNo = segNo;
this.series = series;
this.rows = rows;
}
public String getCoilId() {
return coilId;
}
public List<Integer> getSegNo() {
return segNo;
}
public Map<String, List<Object>> getSeries() {
return series;
}
public List<Map<String, Object>> getRows() {
return rows;
}
@SuppressWarnings("unchecked")
public static SegSeriesView fromExecuteSqlResponse(String coilId, SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
Collections.sort(rows, new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> left, Map<String, Object> right) {
Number leftSeg = asNumber(left.get("SEGNO"));
Number rightSeg = asNumber(right.get("SEGNO"));
int leftValue = leftSeg == null ? Integer.MAX_VALUE : leftSeg.intValue();
int rightValue = rightSeg == null ? Integer.MAX_VALUE : rightSeg.intValue();
if (leftValue > rightValue) {
return 1;
}
if (leftValue < rightValue) {
return -1;
}
return 0;
}
});
List<Integer> segNo = new ArrayList<Integer>();
for (Map<String, Object> row : rows) {
Number seg = asNumber(row.get("SEGNO"));
segNo.add(seg == null ? null : seg.intValue());
}
Map<String, List<Object>> series = new LinkedHashMap<String, List<Object>>();
for (Map<String, Object> row : rows) {
for (Map.Entry<String, Object> entry : row.entrySet()) {
String key = entry.getKey();
if ("SEGNO".equalsIgnoreCase(key)) {
continue;
}
series.computeIfAbsent(key, ignored -> new ArrayList<>()).add(entry.getValue());
}
}
return new SegSeriesView(coilId, segNo, series, rows);
}
}
public static class RealtimeDataBundle {
private final SqlServerApiClient.ExecuteSqlResponse gauge;
private final SqlServerApiClient.ExecuteSqlResponse shape;
public RealtimeDataBundle(SqlServerApiClient.ExecuteSqlResponse gauge,
SqlServerApiClient.ExecuteSqlResponse shape) {
this.gauge = gauge;
this.shape = shape;
}
public SqlServerApiClient.ExecuteSqlResponse getGauge() {
return gauge;
}
public SqlServerApiClient.ExecuteSqlResponse getShape() {
return shape;
}
}
}

View File

@@ -0,0 +1,62 @@
package com.klp.web.controller.sqlserver;
import com.klp.common.core.domain.R;
import com.klp.framework.service.SqlServerApiBusinessService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* sql-server-api 业务查询接口。
* <p>
* 提供计划、SEG、实时数据三个入口方便前端联动展示。
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/sql-server-api")
public class SqlServerApiController {
private final SqlServerApiBusinessService businessService;
/**
* 计划列表。
*/
@GetMapping("/plans")
public R<SqlServerApiBusinessService.PlanListView> planList() {
return R.ok(businessService.getPlanList());
}
/**
* 计划详情。
*/
@GetMapping("/plans/{coilId}")
public R<SqlServerApiBusinessService.PlanDetailView> planDetail(@PathVariable String coilId) {
return R.ok(businessService.getPlanByCoilId(coilId));
}
/**
* 钢卷实际 SEG按入口卷号查询。
*/
@GetMapping("/seg/{encoilId}")
public R<SqlServerApiBusinessService.SegSeriesView> segByEncoilId(@PathVariable String encoilId) {
return R.ok(businessService.getSegSeriesViewByEncoilId(encoilId));
}
/**
* 钢卷实际 SEG按出口卷号查询。
*/
@GetMapping("/seg-by-excoil/{excoilId}")
public R<SqlServerApiBusinessService.SegSeriesView> segByExcoilId(@PathVariable String excoilId) {
return R.ok(businessService.getSegByExcoilId(excoilId));
}
/**
* 实时数据:厚度、板形。
*/
@GetMapping("/realtime/{matId}")
public R<SqlServerApiBusinessService.RealtimeDataBundle> realtime(@PathVariable String matId) {
return R.ok(businessService.getRealtimeData(matId));
}
}

View File

@@ -65,6 +65,15 @@ user:
# 密码锁定时间默认10分钟
lockTime: 10
# sql-server-api 中间件配置
sql-server-api:
# 请求地址,请替换为实际 IP
host: 127.0.0.1
# 请求端口,请替换为实际端口
port: 8080
# 基础访问地址(可直接注入使用)
base-url: http://${sql-server-api.host}:${sql-server-api.port}
# Spring配置
spring:
application: