对接mq和模型上传管理和绑定构件,构建树形

This commit is contained in:
cjh
2026-06-10 17:02:51 +08:00
parent f33bf970d7
commit ee15a3415c
7 changed files with 413 additions and 12 deletions

11
pom.xml
View File

@@ -58,6 +58,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
@@ -88,6 +92,11 @@
<artifactId>fastjson2</artifactId> <artifactId>fastjson2</artifactId>
<version>2.0.32</version> <version>2.0.32</version>
</dependency> </dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.28.22</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -124,4 +133,4 @@
</properties> </properties>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@@ -20,10 +20,11 @@ public class PartCodeController {
@GetMapping("/byPart/{partId}") @GetMapping("/byPart/{partId}")
@ResponseBody @ResponseBody
public Map<String, Object> getByPartId(@PathVariable String partId) { public Map<String, Object> getByPartId(@PathVariable String partId,
@RequestParam(required = false) Long modelId) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
try { try {
List<PartCodeRelation> list = service.findByPartId(partId); List<PartCodeRelation> list = service.findByPartId(partId, modelId);
result.put("code", 200); result.put("code", 200);
result.put("data", list); result.put("data", list);
} catch (Exception e) { } catch (Exception e) {
@@ -69,6 +70,7 @@ public class PartCodeController {
try { try {
Object partIdsObj = params.get("partIds"); Object partIdsObj = params.get("partIds");
Object codeIdsObj = params.get("codeIds"); Object codeIdsObj = params.get("codeIds");
Long modelId = parseLong(params.get("modelId"));
if (partIdsObj == null || codeIdsObj == null) { if (partIdsObj == null || codeIdsObj == null) {
result.put("code", 500); result.put("code", 500);
@@ -100,7 +102,7 @@ public class PartCodeController {
codeDatas[i] = data != null ? data.toString() : ""; codeDatas[i] = data != null ? data.toString() : "";
} }
List<PartCodeRelation> list = service.saveBatchWithData(partInfoList, codeIds, codeDatas); List<PartCodeRelation> list = service.saveBatchWithData(partInfoList, codeIds, codeDatas, modelId);
result.put("code", 200); result.put("code", 200);
result.put("data", list); result.put("data", list);
} catch (Exception e) { } catch (Exception e) {
@@ -117,6 +119,7 @@ public class PartCodeController {
try { try {
Object partIdsObj = params.get("partIds"); Object partIdsObj = params.get("partIds");
Object codeIdsObj = params.get("codeIds"); Object codeIdsObj = params.get("codeIds");
Long modelId = parseLong(params.get("modelId"));
if (partIdsObj == null || codeIdsObj == null) { if (partIdsObj == null || codeIdsObj == null) {
result.put("code", 500); result.put("code", 500);
@@ -148,7 +151,7 @@ public class PartCodeController {
codeDatas[i] = data != null ? data.toString() : ""; codeDatas[i] = data != null ? data.toString() : "";
} }
List<PartCodeRelation> list = service.deleteBatch(partInfoList, codeIds, codeDatas); List<PartCodeRelation> list = service.deleteBatch(partInfoList, codeIds, codeDatas, modelId);
result.put("code", 200); result.put("code", 200);
result.put("data", list); result.put("data", list);
} catch (Exception e) { } catch (Exception e) {
@@ -157,4 +160,11 @@ public class PartCodeController {
} }
return result; return result;
} }
private Long parseLong(Object value) {
if (value == null || String.valueOf(value).isBlank()) {
return null;
}
return Long.valueOf(String.valueOf(value));
}
} }

View File

@@ -21,4 +21,6 @@ public class PartCodeRelation {
private Boolean isLeaf; private Boolean isLeaf;
private LocalDateTime createTime; private LocalDateTime createTime;
private Long modelId;
} }

View File

@@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@@ -34,6 +35,12 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
return list(new LambdaQueryWrapper<PartCodeRelation>().eq(PartCodeRelation::getPartId, partId)); return list(new LambdaQueryWrapper<PartCodeRelation>().eq(PartCodeRelation::getPartId, partId));
} }
public List<PartCodeRelation> findByPartId(String partId, Long modelId) {
return list(new LambdaQueryWrapper<PartCodeRelation>()
.eq(PartCodeRelation::getPartId, partId)
.eq(modelId != null, PartCodeRelation::getModelId, modelId));
}
public List<Map<String, String>> findCodeWbsMappings() { public List<Map<String, String>> findCodeWbsMappings() {
List<PartCodeRelation> relations = list(new LambdaQueryWrapper<PartCodeRelation>() List<PartCodeRelation> relations = list(new LambdaQueryWrapper<PartCodeRelation>()
.orderByAsc(PartCodeRelation::getCodeId) .orderByAsc(PartCodeRelation::getCodeId)
@@ -207,6 +214,11 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
@Transactional @Transactional
public List<PartCodeRelation> saveBatchWithData(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas) { public List<PartCodeRelation> saveBatchWithData(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas) {
return saveBatchWithData(partInfoList, codeIds, codeDatas, null);
}
@Transactional
public List<PartCodeRelation> saveBatchWithData(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas, Long modelId) {
List<PartCodeRelation> result = new ArrayList<>(); List<PartCodeRelation> result = new ArrayList<>();
if (partInfoList == null || partInfoList.isEmpty()) { if (partInfoList == null || partInfoList.isEmpty()) {
return result; return result;
@@ -222,12 +234,14 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
} }
long cnt = count(new LambdaQueryWrapper<PartCodeRelation>() long cnt = count(new LambdaQueryWrapper<PartCodeRelation>()
.eq(PartCodeRelation::getPartId, partId) .eq(PartCodeRelation::getPartId, partId)
.eq(PartCodeRelation::getCodeId, codeId)); .eq(PartCodeRelation::getCodeId, codeId)
.eq(modelId != null, PartCodeRelation::getModelId, modelId));
if (cnt == 0) { if (cnt == 0) {
PartCodeRelation relation = new PartCodeRelation(); PartCodeRelation relation = new PartCodeRelation();
relation.setPartId(partId); relation.setPartId(partId);
relation.setCodeId(codeId); relation.setCodeId(codeId);
relation.setCodeData(codeData); relation.setCodeData(codeData);
relation.setModelId(modelId);
relation.setCreateTime(LocalDateTime.now()); relation.setCreateTime(LocalDateTime.now());
String createDate = safeString(partInfo.get("createDate")); String createDate = safeString(partInfo.get("createDate"));
if (createDate != null && !createDate.isEmpty()) { if (createDate != null && !createDate.isEmpty()) {
@@ -246,6 +260,11 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
@Transactional @Transactional
public List<PartCodeRelation> deleteBatch(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas) { public List<PartCodeRelation> deleteBatch(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas) {
return deleteBatch(partInfoList, codeIds, codeDatas, null);
}
@Transactional
public List<PartCodeRelation> deleteBatch(List<Map<String, Object>> partInfoList, String[] codeIds, String[] codeDatas, Long modelId) {
if (partInfoList == null || partInfoList.isEmpty()) { if (partInfoList == null || partInfoList.isEmpty()) {
return List.of(); return List.of();
} }
@@ -261,10 +280,23 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
return List.of(); return List.of();
} }
remove(new LambdaQueryWrapper<PartCodeRelation>() List<String> targetCodeIds = Arrays.stream(codeIds == null ? new String[0] : codeIds)
.in(PartCodeRelation::getPartId, partIds)); .map(this::safeString)
.filter(codeId -> codeId != null && !codeId.isBlank())
.distinct()
.toList();
if (targetCodeIds.isEmpty()) {
return List.of();
}
return saveBatchWithData(partInfoList, codeIds, codeDatas); remove(new LambdaQueryWrapper<PartCodeRelation>()
.in(PartCodeRelation::getPartId, partIds)
.in(PartCodeRelation::getCodeId, targetCodeIds)
.eq(modelId != null, PartCodeRelation::getModelId, modelId));
return list(new LambdaQueryWrapper<PartCodeRelation>()
.in(PartCodeRelation::getPartId, partIds)
.eq(modelId != null, PartCodeRelation::getModelId, modelId));
} }
private String safeString(Object value) { private String safeString(Object value) {

View File

@@ -0,0 +1,327 @@
package com.bim.api.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* 转换服务签名工具
* <p>
* 按转换服务接口规范生成 HMAC-SHA256 签名:
* 1. 排除 sign 字段
* 2. 字段名按 ASCII 升序排序
* 3. 按 key=value 格式拼接,& 连接
* 4. 对象/数组使用稳定 JSON无多余空格字段 ASCII 升序排序)
* 5. 签名算法Hex(HMAC-SHA256(signString, secretKey))
*
* @author bim-engine
*/
public class SignUtil {
private SignUtil() {
}
/**
* 生成签名
*
* @param params 请求参数(必须包含 sign 占位字段,值为空字符串或 null
* @param secretKey 共享密钥
* @return 十六进制小写签名
*/
public static String generateSign(Map<String, Object> params, String secretKey) {
try {
String signString = buildSignString(params);
return hmacSha256Hex(signString, secretKey);
} catch (Exception e) {
throw new RuntimeException("生成签名失败: " + e.getMessage(), e);
}
}
/**
* 验证签名
*
* @param params 请求参数(包含 sign 字段)
* @param secretKey 共享密钥
* @param sign 待验证的签名
* @return true 表示验证通过
*/
public static boolean verifySign(Map<String, Object> params, String secretKey, String sign) {
if (sign == null || sign.isEmpty()) {
return false;
}
try {
String expectedSign = generateSign(params, secretKey);
return expectedSign.equals(sign);
} catch (Exception e) {
return false;
}
}
/**
* 验证签名,并允许指定部分字符串字段按原始 JSON 转义值参与签名。
* <p>
* 适用于回调消息中存在 JSON 字符串字段的场景,例如 extraData 在原始消息中为
* {\"id\":1},解析后会变成 {"id":1},直接用解析值会改变签名串。
*
* @param params 请求参数(包含 sign 字段)
* @param rawJson 原始 JSON 消息
* @param rawStringFields 需要保留原始转义内容的顶层字符串字段名
* @param secretKey 共享密钥
* @param sign 待验证的签名
* @return true 表示验证通过
*/
public static boolean verifySignWithRawStringFields(Map<String, Object> params, String rawJson,
Set<String> rawStringFields, String secretKey, String sign) {
if (sign == null || sign.isEmpty()) {
return false;
}
try {
String signString = buildSignString(params, rawJson, rawStringFields, false);
String expectedSign = hmacSha256Hex(signString, secretKey);
return expectedSign.equals(sign);
} catch (Exception e) {
return false;
}
}
/**
* 构建待签名字符串
*/
public static String buildSignString(Map<String, Object> params) {
TreeMap<String, Object> sortedParams = new TreeMap<>(params);
sortedParams.remove("sign");
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (builder.length() > 0) {
builder.append("&");
}
builder.append(entry.getKey())
.append("=")
.append(toSignValue(entry.getValue()));
}
return builder.toString();
}
/**
* 构建待签名字符串,并对指定顶层字符串字段使用原始 JSON 中的转义内容。
*/
public static String buildSignString(Map<String, Object> params, String rawJson, Set<String> rawStringFields) {
return buildSignString(params, rawJson, rawStringFields, false);
}
/**
* 构建待签名字符串
*/
public static String buildSignString(Map<String, Object> params, String rawJson, Set<String> rawStringFields, boolean nullAsEmpty) {
TreeMap<String, Object> sortedParams = new TreeMap<>(params);
sortedParams.remove("sign");
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (builder.length() > 0) {
builder.append("&");
}
builder.append(entry.getKey()).append("=");
String rawStringValue = null;
if (rawStringFields != null && rawStringFields.contains(entry.getKey()) && entry.getValue() instanceof String) {
rawStringValue = extractRawJsonStringValue(rawJson, entry.getKey());
}
builder.append(rawStringValue == null ? toSignValue(entry.getValue(), nullAsEmpty) : rawStringValue);
}
return builder.toString();
}
/**
* 提取顶层 JSON 字符串字段的原始内容,不反转义,保留 \"、\\ 等字符。
*/
private static String extractRawJsonStringValue(String json, String fieldName) {
if (json == null || fieldName == null) {
return null;
}
String fieldToken = "\"" + fieldName + "\"";
int fieldIndex = json.indexOf(fieldToken);
if (fieldIndex < 0) {
return null;
}
int colonIndex = json.indexOf(':', fieldIndex + fieldToken.length());
if (colonIndex < 0) {
return null;
}
int valueStart = colonIndex + 1;
while (valueStart < json.length() && Character.isWhitespace(json.charAt(valueStart))) {
valueStart++;
}
if (valueStart >= json.length() || json.charAt(valueStart) != '"') {
return null;
}
StringBuilder rawValue = new StringBuilder();
for (int i = valueStart + 1; i < json.length(); i++) {
char current = json.charAt(i);
if (current == '\\') {
if (i + 1 < json.length()) {
rawValue.append(current).append(json.charAt(i + 1));
i++;
} else {
rawValue.append(current);
}
continue;
}
if (current == '"') {
return rawValue.toString();
}
rawValue.append(current);
}
return null;
}
/**
* 将值转换为签名字符串中的表示形式
*/
private static String toSignValue(Object value) {
return toSignValue(value, false);
}
/**
* 将值转换为签名字符串中的表示形式。
*/
private static String toSignValue(Object value, boolean nullAsEmpty) {
if (value == null) {
return nullAsEmpty ? "" : "null";
}
if (value instanceof Map || value instanceof Iterable || value.getClass().isArray()) {
return toStableJson(value);
}
if (value instanceof Boolean) {
return Boolean.TRUE.equals(value) ? "true" : "false";
}
return String.valueOf(value);
}
/**
* 生成稳定 JSON 字符串
* <p>
* 规则:
* - 无多余空格
* - 对象字段按 ASCII 升序排序
* - 嵌套对象也按 ASCII 升序排序
* - 数组顺序保持不变
*/
private static String toStableJson(Object value) {
if (value == null) {
return "null";
}
if (value instanceof String || value instanceof Character) {
return "\"" + escapeJson(String.valueOf(value)) + "\"";
}
if (value instanceof Number || value instanceof Boolean) {
return String.valueOf(value);
}
if (value instanceof Map) {
TreeMap<String, Object> sortedMap = new TreeMap<>();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
sortedMap.put(String.valueOf(entry.getKey()), entry.getValue());
}
StringBuilder builder = new StringBuilder("{");
Iterator<Map.Entry<String, Object>> iterator = sortedMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
builder.append("\"")
.append(escapeJson(entry.getKey()))
.append("\":")
.append(toStableJson(entry.getValue()));
if (iterator.hasNext()) {
builder.append(",");
}
}
return builder.append("}").toString();
}
if (value instanceof Iterable) {
StringBuilder builder = new StringBuilder("[");
Iterator<?> iterator = ((Iterable<?>) value).iterator();
while (iterator.hasNext()) {
builder.append(toStableJson(iterator.next()));
if (iterator.hasNext()) {
builder.append(",");
}
}
return builder.append("]").toString();
}
if (value.getClass().isArray()) {
List<Object> list = new ArrayList<>();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
list.add(Array.get(value, i));
}
return toStableJson(list);
}
return "\"" + escapeJson(String.valueOf(value)) + "\"";
}
/**
* JSON 字符串转义
*/
private static String escapeJson(String value) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
case '"':
builder.append("\\\"");
break;
case '\\':
builder.append("\\\\");
break;
case '\b':
builder.append("\\b");
break;
case '\f':
builder.append("\\f");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
case '\t':
builder.append("\\t");
break;
default:
if (c < 0x20) {
builder.append(String.format("\\u%04x", (int) c));
} else {
builder.append(c);
}
}
}
return builder.toString();
}
/**
* HMAC-SHA256 签名,返回十六进制小写字符串
*/
private static String hmacSha256Hex(String data, String secretKey) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : bytes) {
hex.append(String.format("%02x", b & 0xff));
}
return hex.toString();
}
}

View File

@@ -5,9 +5,15 @@ spring:
password: root password: root
data: data:
redis: redis:
host: localhost host: 113.45.226.10
# 端口默认为6379
port: 6379 port: 6379
database: 0 # 数据库索引
database: 13
# redis 密码必须配置
password: lyzbim2016
# 连接超时时间
timeout: 10s
mybatis-plus: mybatis-plus:
configuration: configuration:

View File

@@ -21,6 +21,15 @@ spring:
multipart: multipart:
max-file-size: 100MB max-file-size: 100MB
max-request-size: 100MB max-request-size: 100MB
rabbitmq:
host: 38.54.88.163
port: 5672
username: iflow
password: iflow
virtual-host: bim_engine
listener:
simple:
acknowledge-mode: auto
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml mapper-locations: classpath*:/mapper/**/*.xml
@@ -31,4 +40,10 @@ mybatis-plus:
logging: logging:
level: level:
com.bim.api: debug com.bim.api: debug
file-convert:
secret-key: bim-engine-secret
model-queue-name: bim_engine_queue_file_convert
callback-queue-name: system_file_convert_callback_queue
current-language: zh-CN