对接mq和模型上传管理和绑定构件,构建树形
This commit is contained in:
@@ -20,10 +20,11 @@ public class PartCodeController {
|
||||
|
||||
@GetMapping("/byPart/{partId}")
|
||||
@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<>();
|
||||
try {
|
||||
List<PartCodeRelation> list = service.findByPartId(partId);
|
||||
List<PartCodeRelation> list = service.findByPartId(partId, modelId);
|
||||
result.put("code", 200);
|
||||
result.put("data", list);
|
||||
} catch (Exception e) {
|
||||
@@ -69,6 +70,7 @@ public class PartCodeController {
|
||||
try {
|
||||
Object partIdsObj = params.get("partIds");
|
||||
Object codeIdsObj = params.get("codeIds");
|
||||
Long modelId = parseLong(params.get("modelId"));
|
||||
|
||||
if (partIdsObj == null || codeIdsObj == null) {
|
||||
result.put("code", 500);
|
||||
@@ -100,7 +102,7 @@ public class PartCodeController {
|
||||
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("data", list);
|
||||
} catch (Exception e) {
|
||||
@@ -117,6 +119,7 @@ public class PartCodeController {
|
||||
try {
|
||||
Object partIdsObj = params.get("partIds");
|
||||
Object codeIdsObj = params.get("codeIds");
|
||||
Long modelId = parseLong(params.get("modelId"));
|
||||
|
||||
if (partIdsObj == null || codeIdsObj == null) {
|
||||
result.put("code", 500);
|
||||
@@ -148,7 +151,7 @@ public class PartCodeController {
|
||||
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("data", list);
|
||||
} catch (Exception e) {
|
||||
@@ -157,4 +160,11 @@ public class PartCodeController {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long parseLong(Object value) {
|
||||
if (value == null || String.valueOf(value).isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return Long.valueOf(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,6 @@ public class PartCodeRelation {
|
||||
private Boolean isLeaf;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private Long modelId;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -34,6 +35,12 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
|
||||
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() {
|
||||
List<PartCodeRelation> relations = list(new LambdaQueryWrapper<PartCodeRelation>()
|
||||
.orderByAsc(PartCodeRelation::getCodeId)
|
||||
@@ -207,6 +214,11 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
|
||||
|
||||
@Transactional
|
||||
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<>();
|
||||
if (partInfoList == null || partInfoList.isEmpty()) {
|
||||
return result;
|
||||
@@ -222,12 +234,14 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
|
||||
}
|
||||
long cnt = count(new LambdaQueryWrapper<PartCodeRelation>()
|
||||
.eq(PartCodeRelation::getPartId, partId)
|
||||
.eq(PartCodeRelation::getCodeId, codeId));
|
||||
.eq(PartCodeRelation::getCodeId, codeId)
|
||||
.eq(modelId != null, PartCodeRelation::getModelId, modelId));
|
||||
if (cnt == 0) {
|
||||
PartCodeRelation relation = new PartCodeRelation();
|
||||
relation.setPartId(partId);
|
||||
relation.setCodeId(codeId);
|
||||
relation.setCodeData(codeData);
|
||||
relation.setModelId(modelId);
|
||||
relation.setCreateTime(LocalDateTime.now());
|
||||
String createDate = safeString(partInfo.get("createDate"));
|
||||
if (createDate != null && !createDate.isEmpty()) {
|
||||
@@ -246,6 +260,11 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
|
||||
|
||||
@Transactional
|
||||
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()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -261,10 +280,23 @@ public class PartCodeRelationService extends ServiceImpl<PartCodeRelationMapper,
|
||||
return List.of();
|
||||
}
|
||||
|
||||
remove(new LambdaQueryWrapper<PartCodeRelation>()
|
||||
.in(PartCodeRelation::getPartId, partIds));
|
||||
List<String> targetCodeIds = Arrays.stream(codeIds == null ? new String[0] : codeIds)
|
||||
.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) {
|
||||
|
||||
327
src/main/java/com/bim/api/util/SignUtil.java
Normal file
327
src/main/java/com/bim/api/util/SignUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,15 @@ spring:
|
||||
password: root
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
host: 113.45.226.10
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
database: 0
|
||||
# 数据库索引
|
||||
database: 13
|
||||
# redis 密码必须配置
|
||||
password: lyzbim2016
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
|
||||
@@ -21,6 +21,15 @@ spring:
|
||||
multipart:
|
||||
max-file-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:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
@@ -31,4 +40,10 @@ mybatis-plus:
|
||||
|
||||
logging:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user