jindie xiangguangognneng
This commit is contained in:
parent
a83e250c37
commit
eafb53c54d
2
pom.xml
2
pom.xml
@ -35,7 +35,7 @@
|
||||
<dynamic-ds.version>3.5.2</dynamic-ds.version>
|
||||
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
|
||||
<xxl-job.version>2.4.0</xxl-job.version>
|
||||
<lombok.version>1.18.26</lombok.version>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<bouncycastle.version>1.72</bouncycastle.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
|
||||
@ -110,10 +110,20 @@
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>2.2.41</version>
|
||||
<version>2.0.15</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.11</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
package com.ruoyi.web.controller.dingtalk;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.dingding.DingUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ding/talk")
|
||||
public class DingTalkController {
|
||||
/**
|
||||
* 获取参数配置列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<String> uploadMedia(String filePath) {
|
||||
String appKey = "dingebfrpzqko25vo8w6";
|
||||
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
|
||||
|
||||
// 如果未提供 filePath,使用默认值
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
filePath = "E:/新建文件夹 (2)/4eae3472-de71-4f4b-97cc-4212a88e17c8.png";
|
||||
}
|
||||
|
||||
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
|
||||
// type 固定为 image,也可以根据文件名后缀判断
|
||||
String mediaId = DingUtil.uploadMedia(accessToken, "image", filePath);
|
||||
|
||||
return R.ok("上传成功", mediaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送互动卡片消息 (IM 1.0)
|
||||
*/
|
||||
@GetMapping("/sendCard")
|
||||
public R<String> sendCard() {
|
||||
String appKey = "dingebfrpzqko25vo8w6";
|
||||
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
|
||||
String robotCode = "dingebfrpzqko25vo8w6";
|
||||
String openConversationId = "cidCzwPP4a1xhnl9c8hrGXFuw==";
|
||||
String cardTemplateId = "2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema";
|
||||
|
||||
// 获取 AccessToken
|
||||
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
|
||||
|
||||
// 生成唯一 outTrackId
|
||||
String outTrackId = "group-card-" + System.currentTimeMillis();
|
||||
|
||||
// 构造卡片数据
|
||||
Map<String, String> cardDataMap = new HashMap<>();
|
||||
cardDataMap.put("drawing_time", "2026/1/13 11:03");
|
||||
cardDataMap.put("production_order_no", "CP-026-054-023");
|
||||
cardDataMap.put("drawing_by", "毕敏霞");
|
||||
cardDataMap.put("bom_uptime", "2025/12/13 11:03");
|
||||
cardDataMap.put("drawing_type", "60R");
|
||||
cardDataMap.put("project_end_time", "2026/2/13 11:03");
|
||||
cardDataMap.put("route_uptime", "2025/12/13 11:03");
|
||||
cardDataMap.put("material_count", "2312");
|
||||
cardDataMap.put("route_count", "100");
|
||||
cardDataMap.put("plan_uptime", "2024/12/13 11:03");
|
||||
cardDataMap.put("bom_count", "121");
|
||||
cardDataMap.put("img_id", "@lALPM137gRk79qPNA9fNBT4");
|
||||
|
||||
String trackId = DingUtil.createAndDeliverCard(
|
||||
accessToken,
|
||||
cardTemplateId,
|
||||
robotCode,
|
||||
openConversationId,
|
||||
outTrackId,
|
||||
cardDataMap
|
||||
);
|
||||
|
||||
return R.ok("发送卡片成功", trackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人发送群聊消息
|
||||
*/
|
||||
@GetMapping("/sendRobotMessage")
|
||||
public R<String> sendRobotMessage() {
|
||||
String appKey = "dingebfrpzqko25vo8w6";
|
||||
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
|
||||
//机器人编码
|
||||
String robotCode = "dingebfrpzqko25vo8w6";
|
||||
//群回话的id
|
||||
String openConversationId = "cidCzwPP4a1xhnl9c8hrGXFuw==";
|
||||
//消息类型
|
||||
String msgKey = "sampleText";
|
||||
|
||||
// 获取 AccessToken
|
||||
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
|
||||
|
||||
Map<String, Object> msgContent = new HashMap<>();
|
||||
msgContent.put("content", "Hello");
|
||||
|
||||
// 调用机器人发送消息方法
|
||||
String processQueryKey = DingUtil.robotGroupSend(accessToken, msgContent, msgKey, openConversationId, robotCode, null);
|
||||
|
||||
return R.ok("发送成功", processQueryKey);
|
||||
}
|
||||
}
|
||||
@ -65,7 +65,7 @@ spring:
|
||||
sqlserver:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
url: jdbc:sqlserver://192.168.5.8:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
|
||||
url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
|
||||
username: sa
|
||||
password: 1a!
|
||||
hikari:
|
||||
|
||||
@ -68,7 +68,7 @@ spring:
|
||||
sqlserver:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
url: jdbc:sqlserver://192.168.5.8:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
|
||||
url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
|
||||
username: sa
|
||||
password: 1a!
|
||||
# oracle:
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#??ID-PROD
|
||||
X-KDApi-AcctID = 670768a85463de
|
||||
#X-KDApi-AcctID = 6723465a38c722
|
||||
X-KDApi-AcctID = 695f86f96090b2
|
||||
X-KDApi-UserName = Administrator
|
||||
#??IDID
|
||||
X-KDApi-AppID = 288012_Rc0C0zCG2lga0/Vs2Y4pzYSL6hQcWOko
|
||||
|
||||
@ -266,6 +266,18 @@
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>2.0.15</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package com.ruoyi.common.dingding;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 创建群聊返回参数DTO
|
||||
*/
|
||||
@Data
|
||||
public class DingChatCreateResult {
|
||||
/**
|
||||
* 返回码
|
||||
*/
|
||||
private Long errcode;
|
||||
|
||||
/**
|
||||
* 返回码描述
|
||||
*/
|
||||
private String errmsg;
|
||||
|
||||
/**
|
||||
* 群会话的ID (旧版)
|
||||
* 后续版本中chatid将不再使用,请将openConversationId作为群会话唯一标识
|
||||
*/
|
||||
private String chatid;
|
||||
|
||||
/**
|
||||
* 群会话的ID
|
||||
*/
|
||||
private String openConversationId;
|
||||
|
||||
/**
|
||||
* 会话类型:2:企业群
|
||||
*/
|
||||
private Long conversationTag;
|
||||
}
|
||||
@ -0,0 +1,401 @@
|
||||
package com.ruoyi.common.dingding;
|
||||
|
||||
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders;
|
||||
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest;
|
||||
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardResponse;
|
||||
import com.aliyun.dingtalkim_1_0.Client;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendResponse;
|
||||
import com.aliyun.tea.TeaConverter;
|
||||
import com.aliyun.tea.TeaPair;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.dingtalk.api.DefaultDingTalkClient;
|
||||
import com.dingtalk.api.DingTalkClient;
|
||||
import com.dingtalk.api.request.OapiChatCreateRequest;
|
||||
import com.dingtalk.api.response.OapiChatCreateResponse;
|
||||
import com.dingtalk.api.request.OapiMediaUploadRequest;
|
||||
import com.dingtalk.api.response.OapiMediaUploadResponse;
|
||||
import com.dingtalk.api.request.OapiRobotSendRequest;
|
||||
import com.dingtalk.api.response.OapiRobotSendResponse;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Arrays;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DingUtil {
|
||||
public static final String CUSTOM_ROBOT_TOKEN = "1f3711d72605f77e8ba8e3e3fe0d98989361a7bfb9c30b51a23520eeb783652e";
|
||||
|
||||
public static final String USER_ID = "4345285524672471";
|
||||
|
||||
public static final String SECRET = "SECae018c965ccba100318cc2cd5ef7df2e7a3d0379cf96ef39614fd9558209f18c";
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
// 固定模板和群 ID
|
||||
private static final String CARD_TEMPLATE_ID = "2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema";
|
||||
private static final String OPEN_CONVERSATION_ID = "cidCzwPP4a1xhnl9c8hrGXFuw==";
|
||||
/**
|
||||
* 发送钉钉文本消息
|
||||
*
|
||||
* @param content 消息内容
|
||||
* @param atUserIds 需要@的用户ID列表(可选,传null则不@特定人)
|
||||
* @param isAtAll 是否@所有人
|
||||
*/
|
||||
public static void sendText(String content, List<String> atUserIds, boolean isAtAll) {
|
||||
try {
|
||||
Long timestamp = System.currentTimeMillis();
|
||||
String stringToSign = timestamp + "\n" + SECRET;
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(SECRET.getBytes("UTF-8"), "HmacSHA256"));
|
||||
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
|
||||
String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
|
||||
// sign字段和timestamp字段必须拼接到请求URL上
|
||||
String serverUrl = "https://oapi.dingtalk.com/robot/send?sign=" + sign + "×tamp=" + timestamp;
|
||||
DingTalkClient client = new DefaultDingTalkClient(serverUrl);
|
||||
OapiRobotSendRequest req = new OapiRobotSendRequest();
|
||||
// 设置消息类型
|
||||
req.setMsgtype("text");
|
||||
|
||||
// 定义文本内容
|
||||
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
|
||||
text.setContent(content);
|
||||
req.setText(text);
|
||||
|
||||
// 定义 @ 对象
|
||||
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
|
||||
if (atUserIds != null && !atUserIds.isEmpty()) {
|
||||
at.setAtUserIds(atUserIds);
|
||||
}
|
||||
if (isAtAll) {
|
||||
at.setIsAtAll(true);
|
||||
}
|
||||
req.setAt(at);
|
||||
OapiRobotSendResponse rsp = client.execute(req, CUSTOM_ROBOT_TOKEN);
|
||||
System.out.println("DingTalk Response: " + rsp.getBody());
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("发送钉钉消息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Token 初始化账号Client (IM)
|
||||
* @return Client
|
||||
* @throws Exception
|
||||
*/
|
||||
public static com.aliyun.dingtalkim_1_0.Client createImClient() throws Exception {
|
||||
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkim_1_0.Client(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Token 初始化账号Client (OAuth2)
|
||||
* @return Client
|
||||
* @throws Exception
|
||||
*/
|
||||
public static com.aliyun.dingtalkoauth2_1_0.Client createOauthClient() throws Exception {
|
||||
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkoauth2_1_0.Client(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业内部应用的accessToken
|
||||
* @param appKey
|
||||
* @param appSecret
|
||||
* @return
|
||||
*/
|
||||
public static String getAccessToken(String appKey, String appSecret) {
|
||||
try {
|
||||
com.aliyun.dingtalkoauth2_1_0.Client client = createOauthClient();
|
||||
GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
|
||||
.setAppKey(appKey)
|
||||
.setAppSecret(appSecret);
|
||||
GetAccessTokenResponse response = client.getAccessToken(getAccessTokenRequest);
|
||||
return response.getBody().getAccessToken();
|
||||
} catch (com.aliyun.tea.TeaException err) {
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||
System.err.println("TeaException: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(err);
|
||||
|
||||
} catch (Exception _err) {
|
||||
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(_err.getMessage(), _err);
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||
System.err.println("Exception: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(_err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传媒体文件
|
||||
* @param accessToken
|
||||
* @param type 媒体文件类型:image, voice, video, file
|
||||
* @param filePath 文件路径
|
||||
* @return media_id
|
||||
*/
|
||||
public static String uploadMedia(String accessToken, String type, String filePath) {
|
||||
try {
|
||||
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload");
|
||||
OapiMediaUploadRequest req = new OapiMediaUploadRequest();
|
||||
req.setType(type);
|
||||
com.taobao.api.FileItem item = new com.taobao.api.FileItem(filePath);
|
||||
req.setMedia(item);
|
||||
OapiMediaUploadResponse rsp = client.execute(req, accessToken);
|
||||
if (rsp.isSuccess()) {
|
||||
return rsp.getMediaId();
|
||||
} else {
|
||||
throw new RuntimeException("上传媒体文件失败: " + rsp.getErrmsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("上传媒体文件异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建群聊
|
||||
* @param accessToken
|
||||
* @param name 群名称
|
||||
* @param owner 群主userid
|
||||
* @param userIdList 群成员userid列表
|
||||
* @return DingChatCreateResult
|
||||
*/
|
||||
public static DingChatCreateResult createChat(String accessToken, String name, String owner, List<String> userIdList) {
|
||||
try {
|
||||
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/chat/create");
|
||||
OapiChatCreateRequest req = new OapiChatCreateRequest();
|
||||
//群名称
|
||||
req.setName(name);
|
||||
//群主的userId
|
||||
req.setOwner(owner);
|
||||
//群成员列表
|
||||
req.setUseridlist(userIdList);
|
||||
/*
|
||||
新成员是否可查看100条历史消息:1:可查看 0:不可查看
|
||||
*/
|
||||
req.setShowHistoryType(1L);
|
||||
/*
|
||||
群是否可以被搜索:0(默认):不可搜索 1:可搜索
|
||||
*/
|
||||
req.setSearchable(0L);
|
||||
//入群是否需要验证
|
||||
req.setValidationType(0L);
|
||||
//@all 使用范围
|
||||
req.setMentionAllAuthority(0L);
|
||||
//群管理类型:1:仅群主可管理
|
||||
req.setManagementType(1L);
|
||||
//是否开启群禁言
|
||||
req.setChatBannedType(0L);
|
||||
OapiChatCreateResponse rsp = client.execute(req, accessToken);
|
||||
if (rsp.isSuccess()) {
|
||||
DingChatCreateResult result = new DingChatCreateResult();
|
||||
result.setErrcode(rsp.getErrcode());
|
||||
result.setErrmsg(rsp.getErrmsg());
|
||||
result.setChatid(rsp.getChatid());
|
||||
result.setOpenConversationId(rsp.getOpenConversationId());
|
||||
result.setConversationTag(rsp.getConversationTag());
|
||||
return result;
|
||||
} else {
|
||||
throw new RuntimeException("创建群聊失败: " + rsp.getErrmsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("创建群聊异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人发送群聊消息(支持 Map 直接传入)
|
||||
* @param accessToken AccessToken
|
||||
* @param msgContent 消息内容 Map
|
||||
* @param msgKey 消息模板 Key
|
||||
* @param openConversationId 群 ID
|
||||
* @param robotCode 机器人 Code
|
||||
* @param coolAppCode 酷应用 Code (可选)
|
||||
* @return processQueryKey
|
||||
*/
|
||||
public static String robotGroupSend(
|
||||
String accessToken,
|
||||
Map<String, Object> msgContent,
|
||||
String msgKey,
|
||||
String openConversationId,
|
||||
String robotCode,
|
||||
String coolAppCode) {
|
||||
|
||||
try {
|
||||
// 1. 初始化 SDK 客户端
|
||||
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
com.aliyun.dingtalkrobot_1_0.Client client = new com.aliyun.dingtalkrobot_1_0.Client(config);
|
||||
|
||||
// 2. 设置请求头
|
||||
OrgGroupSendHeaders headers = new OrgGroupSendHeaders();
|
||||
headers.xAcsDingtalkAccessToken = accessToken;
|
||||
|
||||
// 3. Map 转 JSON 字符串
|
||||
String msgParam = OBJECT_MAPPER.writeValueAsString(msgContent);
|
||||
|
||||
// 4. 构建消息请求对象
|
||||
OrgGroupSendRequest request = new OrgGroupSendRequest()
|
||||
.setMsgParam(msgParam)
|
||||
.setMsgKey(msgKey)
|
||||
.setOpenConversationId(openConversationId)
|
||||
.setRobotCode(robotCode);
|
||||
|
||||
if (coolAppCode != null && !coolAppCode.isEmpty()) {
|
||||
request.setCoolAppCode(coolAppCode);
|
||||
}
|
||||
|
||||
// 5. 发送消息
|
||||
OrgGroupSendResponse response = client.orgGroupSendWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||
return response.getBody().getProcessQueryKey();
|
||||
|
||||
} catch (com.aliyun.tea.TeaException err) {
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
System.err.println("TeaException: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(err);
|
||||
} catch (Exception e) {
|
||||
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(e.getMessage(), e);
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
System.err.println("Exception: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送互动消息卡片 (IM 1.0)
|
||||
* @param accessToken AccessToken
|
||||
* @param cardTemplateId 卡片模板 ID
|
||||
* @param robotCode 机器人 Code
|
||||
* @param openConversationId 群 ID (可选,如果发送到群)
|
||||
* @param outTrackId 外部跟踪 ID
|
||||
* @param cardDataMap 卡片数据 Map (key-value)
|
||||
* @return outTrackId
|
||||
*/
|
||||
public static String createAndDeliverCard(String accessToken, String cardTemplateId, String robotCode,
|
||||
String openConversationId, String outTrackId,
|
||||
Map<String, String> cardDataMap) {
|
||||
try {
|
||||
com.aliyun.dingtalkim_1_0.Client client = createImClient();
|
||||
|
||||
SendInteractiveCardHeaders sendInteractiveCardHeaders = new SendInteractiveCardHeaders();
|
||||
sendInteractiveCardHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||
|
||||
SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions cardOptions = new SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions()
|
||||
.setSupportForward(true);
|
||||
|
||||
// 转换数据为 Tea 格式 Map (虽然直接传 Map<String, String> 也是兼容的,但为了稳妥可以使用 buildMap)
|
||||
// 这里直接使用传入的 cardDataMap
|
||||
|
||||
SendInteractiveCardRequest.SendInteractiveCardRequestCardData cardData = new SendInteractiveCardRequest.SendInteractiveCardRequestCardData()
|
||||
.setCardParamMap(cardDataMap);
|
||||
|
||||
SendInteractiveCardRequest sendInteractiveCardRequest = new SendInteractiveCardRequest()
|
||||
.setConversationType(1) // 1: 群聊
|
||||
.setCardData(cardData)
|
||||
.setCardTemplateId(cardTemplateId)
|
||||
.setUserIdType(1)
|
||||
.setOutTrackId(outTrackId)
|
||||
.setPullStrategy(false)
|
||||
.setRobotCode(robotCode)
|
||||
.setOpenConversationId(openConversationId)
|
||||
.setCardOptions(cardOptions);
|
||||
|
||||
client.sendInteractiveCardWithOptions(sendInteractiveCardRequest, sendInteractiveCardHeaders, new com.aliyun.teautil.models.RuntimeOptions());
|
||||
|
||||
return outTrackId;
|
||||
|
||||
} catch (com.aliyun.tea.TeaException err) {
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
System.err.println("TeaException: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(err);
|
||||
} catch (Exception _err) {
|
||||
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(_err.getMessage(), _err);
|
||||
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
|
||||
System.err.println("Exception: " + err.code + ", " + err.message);
|
||||
}
|
||||
throw new RuntimeException(_err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 由于接口要求卡片数据的键值对均为 string 类型,需要对卡片数据进行预处理
|
||||
*/
|
||||
public static Map<String, String> convertJsonValuesToString(Map<String, Object> obj) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : obj.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof String) {
|
||||
result.put(key, (String) value);
|
||||
} else {
|
||||
try {
|
||||
result.put(key, OBJECT_MAPPER.writeValueAsString(value));
|
||||
} catch (JsonProcessingException e) {
|
||||
result.put(key, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送群卡片(高级版)
|
||||
* @param cardParamMap 模板变量映射,例如 production_order_no、bom_count 等
|
||||
* @return outTrackId 返回卡片幂等 ID
|
||||
*/
|
||||
public static String pushGroupCard(Map<String, String> cardParamMap) {
|
||||
try {
|
||||
// 获取 AccessToken
|
||||
String accessToken = getAccessToken(CUSTOM_ROBOT_TOKEN, SECRET); // 注意:这里可能需要改为 AppKey/AppSecret
|
||||
|
||||
// 生成唯一 outTrackId
|
||||
String outTrackId = "track-" + System.currentTimeMillis();
|
||||
|
||||
// 调用已写好的 createAndDeliverCard 方法
|
||||
return createAndDeliverCard(
|
||||
accessToken,
|
||||
CARD_TEMPLATE_ID,
|
||||
CUSTOM_ROBOT_TOKEN, // 这里的 robotCode 可能需要根据实际情况调整
|
||||
OPEN_CONVERSATION_ID,
|
||||
outTrackId,
|
||||
cardParamMap
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("推送群卡片失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 测试发送
|
||||
sendText("测试消息:刘开~~~,刘开~~~刘开~~~刘开~~~刘开~~~", Arrays.asList(USER_ID), false);
|
||||
}
|
||||
}
|
||||
@ -579,7 +579,7 @@ public class FtpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载FTP指定目录中的所有文件到本地
|
||||
* 下载FTP指定目录中的所有文件到本地(支持文件名过滤)
|
||||
*
|
||||
* @param ftpHost FTP服务器IP
|
||||
* @param ftpUserName FTP用户名
|
||||
@ -587,11 +587,12 @@ public class FtpUtil {
|
||||
* @param ftpPort FTP端口
|
||||
* @param remoteDir 远程目录路径
|
||||
* @param localDir 本地保存目录
|
||||
* @param fileNameFilter 文件名过滤字符串(只下载包含此字符串的文件),传null或空字符串则下载所有
|
||||
* @return 下载结果
|
||||
*/
|
||||
public static R<String> downloadFtpDirectoryFiles(String ftpHost, String ftpUserName,
|
||||
String ftpPassword, int ftpPort,
|
||||
String remoteDir, String localDir) {
|
||||
String remoteDir, String localDir, String fileNameFilter) {
|
||||
FTPClient ftpClient = null;
|
||||
try {
|
||||
// 1. 连接FTP服务器
|
||||
@ -601,14 +602,19 @@ public class FtpUtil {
|
||||
}
|
||||
|
||||
// 2. 设置FTP参数
|
||||
ftpClient.setControlEncoding("UTF-8");
|
||||
// 强制使用GBK编码,避免Windows FTP服务器UTF-8兼容性问题导致的文件名乱码或451错误
|
||||
ftpClient.setControlEncoding("GBK");
|
||||
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
|
||||
ftpClient.enterLocalPassiveMode();
|
||||
|
||||
// 3. 切换到远程目录
|
||||
if (!ftpClient.changeWorkingDirectory(remoteDir)) {
|
||||
// 如果切换失败,尝试用ISO-8859-1转码再试
|
||||
String isoPath = new String(remoteDir.getBytes("GBK"), "ISO-8859-1");
|
||||
if (!ftpClient.changeWorkingDirectory(isoPath)) {
|
||||
return R.fail("远程目录不存在: " + remoteDir);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 创建本地目录
|
||||
File localDirectory = new File(localDir);
|
||||
@ -626,7 +632,7 @@ public class FtpUtil {
|
||||
|
||||
// 6. 遍历下载文件
|
||||
int successCount = 0;
|
||||
int totalCount = files.length;
|
||||
int totalCount = 0; // 仅统计符合过滤条件的文件
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
|
||||
for (FTPFile file : files) {
|
||||
@ -637,6 +643,13 @@ public class FtpUtil {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 过滤文件名
|
||||
if (fileNameFilter != null && !fileNameFilter.isEmpty() && !fileName.contains(fileNameFilter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalCount++; // 计入待下载总数
|
||||
|
||||
try {
|
||||
// 构建本地文件路径
|
||||
String localFilePath = localDir + File.separator + fileName;
|
||||
@ -644,19 +657,37 @@ public class FtpUtil {
|
||||
|
||||
// 下载文件
|
||||
try (OutputStream os = new FileOutputStream(localFile)) {
|
||||
// 尝试直接下载
|
||||
boolean downloadSuccess = ftpClient.retrieveFile(fileName, os);
|
||||
|
||||
// 如果失败,尝试转码下载 (GBK -> ISO-8859-1)
|
||||
if (!downloadSuccess) {
|
||||
String isoFileName = new String(fileName.getBytes("GBK"), "ISO-8859-1");
|
||||
downloadSuccess = ftpClient.retrieveFile(isoFileName, os);
|
||||
}
|
||||
|
||||
if (downloadSuccess) {
|
||||
successCount++;
|
||||
log.info("文件下载成功: {}", fileName);
|
||||
} else {
|
||||
errorMsg.append("文件下载失败: ").append(fileName).append("; ");
|
||||
log.error("文件下载失败: {}", fileName);
|
||||
errorMsg.append("文件下载失败: ").append(fileName).append(" - ").append(ftpClient.getReplyString()).append("; ");
|
||||
log.error("文件下载失败: {} - 响应: {}", fileName, ftpClient.getReplyString());
|
||||
// 删除下载失败的空文件
|
||||
os.close();
|
||||
if (localFile.exists()) {
|
||||
localFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMsg.append("文件下载异常: ").append(fileName).append(" - ").append(e.getMessage()).append("; ");
|
||||
log.error("文件下载异常: {}", fileName, e);
|
||||
// 删除异常的空文件
|
||||
File localFile = new File(localDir + File.separator + fileName);
|
||||
if (localFile.exists()) {
|
||||
localFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,6 +715,15 @@ public class FtpUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载FTP指定目录中的所有文件到本地(兼容旧方法调用,默认下载所有)
|
||||
*/
|
||||
public static R<String> downloadFtpDirectoryFiles(String ftpHost, String ftpUserName,
|
||||
String ftpPassword, int ftpPort,
|
||||
String remoteDir, String localDir) {
|
||||
return downloadFtpDirectoryFiles(ftpHost, ftpUserName, ftpPassword, ftpPort, remoteDir, localDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主方法 - 用于测试FTP目录文件下载功能
|
||||
*/
|
||||
@ -695,7 +735,7 @@ public class FtpUtil {
|
||||
int ftpPort = 21;
|
||||
|
||||
// 远程目录和本地目录配置
|
||||
String remoteDir = "/FB-25-039-FS-01/40S-R-2720-T(FS039-25)-PDF"; // 远程目录路径
|
||||
String remoteDir = "2026/SH-26-001-LT/T40P1-L-1440-D-PDF"; // 远程目录路径
|
||||
String localDir = "F:/DownloadedFiles"; // 本地保存目录
|
||||
|
||||
System.out.println("开始下载FTP文件...");
|
||||
|
||||
@ -1701,10 +1701,10 @@ public class BomDetailsController extends BaseController {
|
||||
}
|
||||
subHeadEntity5.addProperty("FIssueType", "1");
|
||||
|
||||
// 创建FPickStockId对象,并加入SubHeadEntity5
|
||||
/*// 创建FPickStockId对象,并加入SubHeadEntity5
|
||||
JsonObject fPickStockId = new JsonObject();
|
||||
fPickStockId.addProperty("FNumber", " 010");
|
||||
subHeadEntity1.add("FPickStockId", fPickStockId);
|
||||
subHeadEntity1.add("FPickStockId", fPickStockId);*/
|
||||
|
||||
subHeadEntity5.addProperty("FOverControlMode", "1");
|
||||
// 标准人员实作工时
|
||||
|
||||
@ -11,10 +11,7 @@ import com.ruoyi.common.utils.JdUtils;
|
||||
import com.ruoyi.system.domain.ImMaterial;
|
||||
import com.ruoyi.system.domain.bo.ImMaterialBo;
|
||||
import com.ruoyi.system.domain.bo.ImProductionPlanBo;
|
||||
import com.ruoyi.system.domain.dto.JDInventoryDTO;
|
||||
import com.ruoyi.system.domain.dto.JDProductionDTO;
|
||||
import com.ruoyi.system.domain.dto.ProMoDTO;
|
||||
import com.ruoyi.system.domain.dto.PurchaseOrderDTO;
|
||||
import com.ruoyi.system.domain.dto.*;
|
||||
import com.ruoyi.system.domain.vo.ImMaterialVo;
|
||||
import com.ruoyi.system.domain.vo.InventoryInfoVO;
|
||||
import com.ruoyi.system.runner.JdUtil;
|
||||
@ -95,18 +92,27 @@ public class IndexController {
|
||||
.sum())
|
||||
.orElse(0.0);
|
||||
}, executorService);
|
||||
CompletableFuture<Double> salesOrderFuture = CompletableFuture.supplyAsync(() -> {
|
||||
log.info("开始查询销售订单未入库数量: {}", materialCode);
|
||||
return Optional.ofNullable(JdUtil.getSalesOrderList(materialCode))
|
||||
.map(list -> list.stream()
|
||||
.mapToDouble(SalesOrderDTO::getFBaseRemainOutQty)
|
||||
.sum())
|
||||
.orElse(0.0);
|
||||
}, executorService);
|
||||
|
||||
// 等待所有任务完成并获取结果
|
||||
CompletableFuture<InventoryInfoVO> resultFuture = CompletableFuture.allOf(inventoryFuture, noPickedFuture, productionFuture, purchaseFuture)
|
||||
CompletableFuture<InventoryInfoVO> resultFuture = CompletableFuture.allOf(inventoryFuture, noPickedFuture, productionFuture, purchaseFuture,salesOrderFuture)
|
||||
.thenApply(v -> {
|
||||
try {
|
||||
double inventoryQty = inventoryFuture.get();
|
||||
double fNoPickedQty = noPickedFuture.get();
|
||||
double productionQty = productionFuture.get();
|
||||
double purchaseQty = purchaseFuture.get();
|
||||
double salesOrder = salesOrderFuture.get();
|
||||
|
||||
// 计算预计可用库存 即时库存+生产未入库+(采购申请-采购未入库)-子项未领料
|
||||
double keyong = inventoryQty + productionQty + purchaseQty - fNoPickedQty;
|
||||
// 计算预计可用库存 即时库存+生产未入库+(采购申请-采购未入库)-子项未领料-销售实际未出库数量
|
||||
double keyong = inventoryQty + productionQty + purchaseQty - fNoPickedQty-salesOrder;
|
||||
InventoryInfoVO inventoryInfoVO = new InventoryInfoVO();
|
||||
inventoryInfoVO.setMaterialCode(materialCode);
|
||||
inventoryInfoVO.setKucun(String.valueOf(inventoryQty));
|
||||
@ -114,6 +120,7 @@ public class IndexController {
|
||||
inventoryInfoVO.setMaterialName(String.valueOf(purchaseQty));
|
||||
inventoryInfoVO.setStockName(String.valueOf(productionQty));
|
||||
inventoryInfoVO.setStockUnit(String.valueOf(keyong));
|
||||
inventoryInfoVO.setSalesOrder(String.valueOf(salesOrder));
|
||||
|
||||
return inventoryInfoVO;
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -456,29 +456,9 @@ public class KingdeeWorkCenterDataController extends BaseController {
|
||||
@PostMapping("/getKingdeeDelayData")
|
||||
public R<List<KingdeeWorkCenterDataBo>> getKingdeeDelayData(@RequestParam(value = "workCenter") String workCenter) {
|
||||
try {
|
||||
/* K3CloudApi client = new K3CloudApi();
|
||||
JsonObject parameter = new JsonObject();
|
||||
List<KingdeeWorkCenterDataBo> kingdeeWorkCenterDataVos = new ArrayList<>();
|
||||
parameter.addProperty("FWorkCenterName", workCenter);
|
||||
Object[] parameters = new Object[]{parameter.toString()};
|
||||
String execute = client.execute("Ljint.Kingdee.YiTe.KanBan.WebApi.ProduceWebApi.ExecuteService,Ljint.Kingdee.YiTe.KanBan.WebApi", parameters);
|
||||
log.info("金蝶接口:" + workCenter + "===> 返回数据: {}", execute);
|
||||
|
||||
// 解析响应
|
||||
JSONObject response = JSONObject.parseObject(execute);
|
||||
if (!"true".equals(response.getString("IsSuccess"))) {
|
||||
String errorMsg = response.getString("Message");
|
||||
return R.fail("获取工段数据失败:" + errorMsg);
|
||||
}*/
|
||||
// 获取明天的日期字符串 (格式: yyyy-MM-dd)
|
||||
String yesterday = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-dd");
|
||||
|
||||
/* // 获取数据数组
|
||||
JSONArray dataArray = response.getJSONArray("data");
|
||||
if (dataArray == null || dataArray.isEmpty()) {
|
||||
return R.ok("无数据");
|
||||
}
|
||||
*/
|
||||
List<KingdeeWorkCenterDataBo> kingdeeProduceData = mssqlQueryService.getKingdeeProduceData(workCenter);
|
||||
List<KingdeeWorkCenterDataBo> kingdeeWorkCenterDataVos = new ArrayList<>();
|
||||
for (KingdeeWorkCenterDataBo kingnum : kingdeeProduceData) {
|
||||
|
||||
@ -3,13 +3,20 @@ package com.ruoyi.system.controller;
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.ruoyi.common.excel.DefaultExcelListener;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.FtpUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.VersionComparator;
|
||||
import com.ruoyi.common.utils.file.SmbUtil;
|
||||
@ -113,6 +120,11 @@ public class ProcessOrderProController extends BaseController {
|
||||
return R.ok(iProcessOrderProService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 钉钉数据同步 Webhook 地址
|
||||
*/
|
||||
private static final String DINGTALK_WEBHOOK_URL = "https://connector.dingtalk.com/webhook/flow/103694935fdc210503b10006";
|
||||
|
||||
/**
|
||||
* 新增项目令号
|
||||
*/
|
||||
@ -121,7 +133,24 @@ public class ProcessOrderProController extends BaseController {
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ProcessOrderProBo bo) {
|
||||
return toAjax(iProcessOrderProService.insertByBo(bo));
|
||||
boolean result = iProcessOrderProService.insertByBo(bo);
|
||||
if (result) {
|
||||
// 异步同步数据到钉钉 Webhook
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// 将业务对象转换为 JSON 字符串
|
||||
String jsonBody = JSONUtil.toJsonStr(bo);
|
||||
System.out.println(jsonBody);
|
||||
// 发送 POST 请求
|
||||
String response = HttpUtil.post(DINGTALK_WEBHOOK_URL, jsonBody);
|
||||
System.out.println("钉钉Webhook响应: " + response);
|
||||
} catch (Exception e) {
|
||||
// 仅记录日志,不影响主流程
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
return toAjax(result);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -693,6 +722,8 @@ public class ProcessOrderProController extends BaseController {
|
||||
// 单重
|
||||
vo.setBatchQuantity(getCellValueAsLong(row.getCell(18))); // 批次数量
|
||||
vo.setUnitQuantity(getCellValueAsDouble(row.getCell(17))); // 批次数量
|
||||
vo.setXuEndTime(getCellValueAsDate(row.getCell(21))); // 批次数量
|
||||
vo.setXuStartTime(getCellValueAsDate(row.getCell(20))); // 批次数量
|
||||
|
||||
resultList.add(vo);
|
||||
}
|
||||
@ -832,6 +863,42 @@ public class ProcessOrderProController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格的Date值
|
||||
*/
|
||||
private Date getCellValueAsDate(XSSFCell cell) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case NUMERIC:
|
||||
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue();
|
||||
}
|
||||
return null;
|
||||
case STRING:
|
||||
try {
|
||||
String dateStr = cell.getStringCellValue();
|
||||
if (dateStr == null || dateStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 尝试解析字符串日期
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
try {
|
||||
return sdf.parse(dateStr);
|
||||
} catch (ParseException e) {
|
||||
sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
return sdf.parse(dateStr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格的Long值
|
||||
*/
|
||||
@ -1672,17 +1739,70 @@ public class ProcessOrderProController extends BaseController {
|
||||
try {
|
||||
ProcessOrderPro orderPro = processOrderProMapper.selectById(id);
|
||||
// 下载Excel文件
|
||||
SmbUtil.downloadExcelFiles(orderPro.getProductionOrderNo());
|
||||
|
||||
// 1. 解析年份
|
||||
String productionCode = orderPro.getProductionOrderNo();
|
||||
String year = "2025"; // 默认
|
||||
if (productionCode != null) {
|
||||
if (productionCode.startsWith("EY")) {
|
||||
if (productionCode.length() >= 4) {
|
||||
try {
|
||||
Integer.parseInt(productionCode.substring(2, 4));
|
||||
year = "20" + productionCode.substring(2, 4);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
} else {
|
||||
int firstDash = productionCode.indexOf("-");
|
||||
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
|
||||
try {
|
||||
Integer.parseInt(productionCode.substring(firstDash + 1, firstDash + 3));
|
||||
year = "20" + productionCode.substring(firstDash + 1, firstDash + 3);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 构建FTP路径和本地路径
|
||||
// 假设Excel文件在 /2026/SH-26-001-LT/ 目录下
|
||||
String ftpPath = "/" + year + "/" + productionCode;
|
||||
String localPath = "D:\\file\\";
|
||||
|
||||
// 3. 下载文件 (只下载汇总表.xlsx)
|
||||
FtpUtil.downloadFtpDirectoryFiles("192.168.5.18", "admin", "hbyt2025", 21, ftpPath, localPath, "汇总表.xlsx");
|
||||
|
||||
// 构建文件路径
|
||||
String excelName = "D:\\file\\" + orderPro.getProductionOrderNo() + "汇总表.xlsx";
|
||||
String rawDataFile = "D:\\file\\RawDataTable.xlsx";
|
||||
File file = new File(excelName);
|
||||
if (!file.exists()) {
|
||||
throw new ServiceException("项目 " + orderPro.getProductionOrderNo() + " 未出图");
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
// 如果文件不存在或为空(下载失败),抛出业务异常
|
||||
throw new ServiceException("项目 " + orderPro.getProductionOrderNo() + " 汇总表下载失败或不存在");
|
||||
}
|
||||
// 1. 读取第一个sheet的数据list - 使用POI直接读取以保留空格
|
||||
List<ProductionOrderVo> allDataList = readExcelWithPOI(excelName,orderPro.getProductionOrderNo());
|
||||
List<ProcessRoute> routeList = readExcelPOIRoute(excelName,orderPro.getProductionOrderNo());
|
||||
List<ProcessRoute> routeList2 = processRouteService.selectByProjectNumber(orderPro.getProductionOrderNo());
|
||||
|
||||
// 将routeList2中有但routeList中没有的记录添加到routeList
|
||||
if (routeList2 != null && !routeList2.isEmpty()) {
|
||||
if (routeList == null) {
|
||||
routeList = new ArrayList<>();
|
||||
}
|
||||
|
||||
// 使用 Set 存储 routeList 中已有的 materialCode,用于快速排重
|
||||
Set<String> existingMaterialCodes = routeList.stream()
|
||||
.map(ProcessRoute::getMaterialCode)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (ProcessRoute route : routeList2) {
|
||||
if (StringUtils.isNotBlank(route.getMaterialCode()) && !existingMaterialCodes.contains(route.getMaterialCode())) {
|
||||
routeList.add(route);
|
||||
existingMaterialCodes.add(route.getMaterialCode()); // 避免重复添加
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ProcessRoute> routes = new ArrayList<>();
|
||||
List<Map<String, Object>> kingdeeBomRows = new ArrayList<>();
|
||||
for (ProcessRoute base : routeList) {
|
||||
@ -1702,6 +1822,8 @@ public class ProcessOrderProController extends BaseController {
|
||||
item.setDiscWeight(base.getDiscWeight());
|
||||
item.setUnitQuantity(base.getUnitQuantity());
|
||||
item.setBatchQuantity(base.getBatchQuantity());
|
||||
item.setXuEndTime(base.getXuEndTime());
|
||||
item.setXuStartTime(base.getXuStartTime());
|
||||
routes.add(item);
|
||||
continue;
|
||||
}
|
||||
@ -1715,6 +1837,8 @@ public class ProcessOrderProController extends BaseController {
|
||||
item.setDiscWeight(base.getDiscWeight());
|
||||
item.setUnitQuantity(base.getUnitQuantity());
|
||||
item.setBatchQuantity(base.getBatchQuantity());
|
||||
item.setXuEndTime(base.getXuEndTime());
|
||||
item.setXuStartTime(base.getXuStartTime());
|
||||
routes.add(item);
|
||||
continue;
|
||||
}
|
||||
@ -1740,6 +1864,8 @@ public class ProcessOrderProController extends BaseController {
|
||||
item.setProcessControl(r.getProcessControl());
|
||||
item.setActivityDuration(r.getActivityDuration());
|
||||
item.setActivityUnit(r.getActivityUnit());
|
||||
item.setXuStartTime(r.getXuStartTime());
|
||||
item.setXuEndTime(r.getXuEndTime());
|
||||
routes.add(item);
|
||||
});
|
||||
} else {
|
||||
@ -1751,6 +1877,8 @@ public class ProcessOrderProController extends BaseController {
|
||||
item.setDiscWeight(base.getDiscWeight());
|
||||
item.setUnitQuantity(base.getUnitQuantity());
|
||||
item.setBatchQuantity(base.getBatchQuantity());
|
||||
item.setXuStartTime(base.getXuStartTime());
|
||||
item.setXuEndTime(base.getXuEndTime());
|
||||
// 不写入BOM字段,保持纯工艺数据行
|
||||
routes.add(item);
|
||||
}
|
||||
|
||||
@ -891,7 +891,7 @@ public class ProcessRouteController extends BaseController {
|
||||
* 更新生产订单仓库字段
|
||||
*/
|
||||
@SaCheckPermission("system:route:updateProductionOrders")
|
||||
@Log(title = "获取全部工艺路线和物料清单", businessType = BusinessType.OTHER)
|
||||
@Log(title = "更新生产订单仓库", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/updateProductionOrders")
|
||||
public R updateProductionOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception {
|
||||
//判断此项目是否有
|
||||
@ -900,10 +900,10 @@ public class ProcessRouteController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新采购申请仓库字段
|
||||
* 更新采购申请入库仓库仓库
|
||||
*/
|
||||
@SaCheckPermission("system:route:updateProductionOrders")
|
||||
@Log(title = "获取全部工艺路线和物料清单", businessType = BusinessType.OTHER)
|
||||
@Log(title = "更新采购申请单仓库", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/updateCgOrders")
|
||||
public R updateCgOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception {
|
||||
//判断此项目是否有
|
||||
|
||||
@ -9,4 +9,6 @@ public class CgDTO {
|
||||
private String orderBillNo;
|
||||
@JsonProperty("FMaterialId.FNumber")
|
||||
private String materialCode;
|
||||
@JsonProperty("FMaterialName")
|
||||
private String materialName;
|
||||
}
|
||||
|
||||
@ -33,6 +33,6 @@ public class PlanPrcessNumDTO {
|
||||
private Date FOperPlanFinishTime;
|
||||
@JsonProperty("FMONumber")
|
||||
private String FMONumber;
|
||||
@JsonProperty("F_HBYT_RKCK.FName")
|
||||
@JsonProperty("F_HBYT_RKCK")
|
||||
private String FRKCKFName;
|
||||
}
|
||||
|
||||
@ -2,7 +2,11 @@ package com.ruoyi.system.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 采购订单未入库数量
|
||||
* @author tzy
|
||||
* @date 2025-10-26
|
||||
*/
|
||||
@Data
|
||||
public class PurchaseOrderDTO {
|
||||
@JsonProperty("FMaterialId.FNumber")
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package com.ruoyi.system.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SalesOrderDTO {
|
||||
@JsonProperty("FMaterialId.FNumber")
|
||||
private String FMaterialIdFNumber;
|
||||
@JsonProperty("FMaterialName")
|
||||
private String FMaterialName;
|
||||
@JsonProperty("FBaseRemainOutQty")
|
||||
private Double FBaseRemainOutQty;
|
||||
}
|
||||
@ -10,4 +10,5 @@ public class InventoryInfoVO {
|
||||
private String stockName; // 仓库
|
||||
private String stockUnit; // 单位
|
||||
private String quantity; // 数量
|
||||
private String salesOrder; // 销售数量
|
||||
}
|
||||
|
||||
@ -21,6 +21,12 @@ public class FSubEntity {
|
||||
/*
|
||||
入库仓库
|
||||
*/
|
||||
@JsonProperty("F_HBYT_RKCK.FName")
|
||||
private String rkckName;
|
||||
@JsonProperty("F_HBYT_RKCK")
|
||||
private F_HBYT_RKCK F_HBYT_RKCK;
|
||||
|
||||
@Data
|
||||
public static class F_HBYT_RKCK {
|
||||
@JsonProperty("FNUMBER")
|
||||
private String FNUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import java.util.List;
|
||||
public class Model {
|
||||
@JsonProperty("FID")
|
||||
private int FID;
|
||||
|
||||
@JsonProperty("FPlanStartTime")
|
||||
private Date FPlanStartTime;
|
||||
@JsonProperty("FProcessId_number") // 添加 FProcessId_number 字段
|
||||
|
||||
@ -3049,6 +3049,7 @@ public class JdUtil {
|
||||
pageJson.addProperty("StartRow", startRow);
|
||||
pageJson.addProperty("Limit", pageSize);
|
||||
String resultJson = String.valueOf(client.billQuery(pageJson.toString()));
|
||||
System.out.println("0000000000000000"+ pageJson);
|
||||
JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class);
|
||||
if (jsonArray == null || jsonArray.size() == 0) {
|
||||
break;
|
||||
@ -3432,7 +3433,7 @@ public class JdUtil {
|
||||
// 请求参数,要求为json字符串
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("FormId", "PUR_Requisition");
|
||||
json.addProperty("FieldKeys", "FBillNo,FMaterialId.FNumber");
|
||||
json.addProperty("FieldKeys", "FBillNo,FMaterialId.FNumber,FMaterialName");
|
||||
JsonArray filterString = new JsonArray();
|
||||
JsonObject filterObject = new JsonObject();
|
||||
filterObject.addProperty("FieldName", "F_UCHN_Text");
|
||||
@ -3473,7 +3474,8 @@ public class JdUtil {
|
||||
|
||||
JsonArray needUpDateFields = new JsonArray();
|
||||
needUpDateFields.add("FID");
|
||||
needUpDateFields.add("F_HBYT_RKCK.FName");
|
||||
needUpDateFields.add("FENTRYID");
|
||||
needUpDateFields.add("F_HBYT_RKCK");
|
||||
needUpDateFields.add("FTreeEntity");
|
||||
json.add("NeedUpDateFields", needUpDateFields);
|
||||
|
||||
@ -3484,7 +3486,11 @@ public class JdUtil {
|
||||
|
||||
JsonArray fTreeEntity = new JsonArray();
|
||||
JsonObject fTreeEntityItem = new JsonObject();
|
||||
fTreeEntityItem.addProperty("F_HBYT_RKCK.FName", cangKuNum);
|
||||
// 修改:使用 FNUMBER 对象结构设置仓库
|
||||
JsonObject rkck = new JsonObject();
|
||||
rkck.addProperty("FNUMBER", cangKuNum);
|
||||
fTreeEntityItem.add("F_HBYT_RKCK", rkck);
|
||||
|
||||
fTreeEntityItem.addProperty("FENTRYID", jdHuoZhu.getFTreeEntityFENTRYID());
|
||||
fTreeEntity.add(fTreeEntityItem);
|
||||
model.add("FTreeEntity", fTreeEntity);
|
||||
@ -3523,8 +3529,12 @@ public class JdUtil {
|
||||
|
||||
JsonArray fTreeEntity = new JsonArray();
|
||||
JsonObject fTreeEntityItem = new JsonObject();
|
||||
fTreeEntityItem.addProperty("F_HBYT_RKCK.FName", cangKuNum);
|
||||
fTreeEntityItem.addProperty("FEntity_FEntryID", fidEntryIdDTO.getFEntryId());
|
||||
// 修改:使用 FNUMBER 对象结构设置仓库
|
||||
JsonObject rkck = new JsonObject();
|
||||
rkck.addProperty("FNUMBER", cangKuNum);
|
||||
fTreeEntityItem.add("F_HBYT_RKCK", rkck);
|
||||
|
||||
fTreeEntityItem.addProperty("FEntryID", fidEntryIdDTO.getFEntryId());
|
||||
fTreeEntity.add(fTreeEntityItem);
|
||||
model.add("FEntity", fTreeEntity);
|
||||
json.add("Model", model);
|
||||
@ -3539,9 +3549,9 @@ public class JdUtil {
|
||||
RepoRet sRet = gson.fromJson(result, RepoRet.class);
|
||||
|
||||
if (sRet.isSuccessfully()) {
|
||||
return "采购申请单保存成功";
|
||||
return gson.toJson(sRet.getResult());
|
||||
} else {
|
||||
return "采购申请单成功失败:" + gson.toJson(sRet.getResult());
|
||||
return gson.toJson(sRet.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3588,4 +3598,65 @@ public class JdUtil {
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销售订单的查询
|
||||
* @param materialCode
|
||||
* @return
|
||||
*/
|
||||
public static List<SalesOrderDTO> getSalesOrderList(String materialCode) {
|
||||
|
||||
K3CloudApi client = new K3CloudApi();
|
||||
|
||||
// 请求参数,要求为json字符串
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("FormId", "SAL_SaleOrder");
|
||||
json.addProperty("FieldKeys",
|
||||
"FMaterialId.FNumber,FMaterialName,FBaseRemainOutQty");
|
||||
// 创建过滤条件
|
||||
JsonArray filterString = new JsonArray();
|
||||
JsonObject filterObject = new JsonObject();
|
||||
filterObject.addProperty("FieldName", "FMaterialId.FNumber");
|
||||
filterObject.addProperty("Compare", "=");
|
||||
filterObject.addProperty("Value", materialCode);
|
||||
filterObject.addProperty("Left", "");
|
||||
filterObject.addProperty("Right", "");
|
||||
filterObject.addProperty("Logic", 0);
|
||||
filterString.add(filterObject);
|
||||
JsonObject filterObject1 = new JsonObject();
|
||||
filterObject1.addProperty("FieldName", "FCloseStatus");
|
||||
filterObject1.addProperty("Compare", "105");
|
||||
filterObject1.addProperty("Value", "A");
|
||||
filterObject1.addProperty("Left", "");
|
||||
filterObject1.addProperty("Right", "");
|
||||
filterObject1.addProperty("Logic", 0);
|
||||
filterString.add(filterObject1);
|
||||
json.add("FilterString", filterString);
|
||||
json.addProperty("OrderString", "");
|
||||
json.addProperty("TopRowCount", 0);
|
||||
json.addProperty("StartRow", 0);
|
||||
json.addProperty("Limit", 2000);
|
||||
json.addProperty("SubSystemId", "");
|
||||
|
||||
String jsonData = json.toString();
|
||||
List<SalesOrderDTO> plannedProcessList = null;
|
||||
try {
|
||||
// 调用接口
|
||||
String resultJson = String.valueOf(client.billQuery(jsonData));
|
||||
JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class);
|
||||
|
||||
// 使用 ObjectMapper 将 JsonArray 转换为 List<PlannedProcessVo>
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
plannedProcessList = objectMapper.readValue(jsonArray.toString(),
|
||||
new TypeReference<List<SalesOrderDTO>>() {
|
||||
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(); // 输出异常日志
|
||||
}
|
||||
|
||||
return plannedProcessList; // 返回结果
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ public class updatePcessPlanConver {
|
||||
for (Model model : numDTO) {
|
||||
MainModel mainModel = new MainModel();
|
||||
List<String> needUpDateFields = Arrays.asList(
|
||||
"FEntity", "FSubEntity", "F_HBYT_RKCK.FName"
|
||||
"FEntity", "FSubEntity", "F_HBYT_RKCK"
|
||||
);
|
||||
mainModel.setNeedUpDateFields(needUpDateFields);
|
||||
mainModel.setDeleteEntry(false);
|
||||
|
||||
@ -58,4 +58,5 @@ public interface IBomDetailsService {
|
||||
List<BomDetails> selectByFNumberAndTotalWeight(String fnumber, String totalWeight);
|
||||
|
||||
List<BomDetails> selectByProjectNumber(String totalWeight);
|
||||
List<BomDetails> selectMaterialZH(String totalWeight,String partNumber);
|
||||
}
|
||||
|
||||
@ -119,6 +119,7 @@ public interface IProcessRouteService {
|
||||
boolean isAnTuDingGou(String materialCode);
|
||||
|
||||
List<ProcessRouteSelectDTO> getSelectProcessRoute(String materilCode);
|
||||
|
||||
List<ProcessRouteSelectDTO> getSelectStandProcessRoute(String materilCode);
|
||||
//根据项目令号删除 材料bom 总装bom 工艺路线
|
||||
R<Void> delByProjectCode(String productionOrderNo);
|
||||
|
||||
@ -26,6 +26,32 @@ public class MssqlQueryService {
|
||||
return jdbcTemplate.queryForList(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据物料编码查询是否是VMI业务
|
||||
* @param materialNumber 物料编码
|
||||
* @return true:是VMI业务, false:否
|
||||
*/
|
||||
@DS("sqlserver")
|
||||
public boolean checkIfVmiBusiness(String materialNumber) {
|
||||
String sql = "SELECT p.FISVMIBUSINESS " +
|
||||
" FROM T_BD_MATERIAL m " +
|
||||
" INNER JOIN T_BD_MATERIALPURCHASE p ON m.FMATERIALID = p.FMATERIALID " +
|
||||
" WHERE m.FNUMBER = ?";
|
||||
try {
|
||||
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, materialNumber);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
Object val = list.get(0).get("FISVMIBUSINESS");
|
||||
if (val != null) {
|
||||
String s = val.toString();
|
||||
return "1".equals(s) || "true".equalsIgnoreCase(s);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@DS("sqlserver")
|
||||
public List<KingdeeWorkCenterDataBo> getKingdeeProduceData(String workCenterName) {
|
||||
String sql = "select t8.FBillNo as MoBillNo,t8.F_HBYT_SCLH as MoOrderNo, \n" +
|
||||
|
||||
@ -246,6 +246,24 @@ public class BomDetailsServiceImpl implements IBomDetailsService {
|
||||
return baseMapper.selectList(bomDetailsLambdaQueryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部件是否为组焊件
|
||||
* @param totalWeight 总权重/版本号
|
||||
* @param partNumber 部件编码
|
||||
* @return 如果是组焊件,返回包含该记录的列表;否则返回空列表
|
||||
*/
|
||||
@Override
|
||||
public List<BomDetails> selectMaterialZH(String totalWeight, String partNumber) {
|
||||
// 你的逻辑:fProcessIdNumber作为partNumber,totalWeight,Material=组焊件
|
||||
// 查询出数据 就代表他就是组焊件子件(或者说需要走CK025)
|
||||
LambdaQueryWrapper<BomDetails> bq = new LambdaQueryWrapper<>();
|
||||
bq.eq(BomDetails::getTotalWeight, totalWeight);
|
||||
bq.eq(BomDetails::getPartNumber, partNumber);
|
||||
bq.eq(BomDetails::getMaterial, "组焊件");
|
||||
|
||||
return baseMapper.selectList(bq);
|
||||
}
|
||||
|
||||
|
||||
public int loadMaterialPreservation(BomDetails bomDetails1) {
|
||||
K3CloudApi client = new K3CloudApi();
|
||||
|
||||
@ -1004,14 +1004,16 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
String code = processOrderPro.getProductionOrderNo();
|
||||
log.info("开始处理项目PDF文件,项目令号: {}", code);
|
||||
|
||||
// 3. 查询产品信息
|
||||
// 3. 查询产品信息/system/orderPro/uploadPDF
|
||||
|
||||
List<FigureSave> fiseList = iFigureSaveService.selectByFid(id);
|
||||
if (fiseList == null || fiseList.isEmpty()) {
|
||||
return R.fail("项目下没有找到产品信息");
|
||||
}
|
||||
|
||||
// 4. 构建本地根目录
|
||||
String localBaseDir = "D:/2025/";
|
||||
// 修正:不再硬编码为 D:/2025/,而是根据 code 动态构建(与后续处理逻辑保持一致)
|
||||
String localBaseDir = buildWorkDirectory(code);
|
||||
// 5. 清空本地目标文件夹
|
||||
String targetDir = localBaseDir + code;
|
||||
File targetDirectory = new File(targetDir);
|
||||
@ -1029,9 +1031,46 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
// 6. 下载FTP文件到本地
|
||||
log.info("开始从FTP下载文件...");
|
||||
for (FigureSave figureSave : fiseList) {
|
||||
String productionCode = figureSave.getProductionCode();
|
||||
//解析出年份 从左往右第一个- 后面就是 年份25 =2025 26 就是2026
|
||||
//如果这个是EY开头的那就取EY后面的数字 25 26 27 作为年份 2025 2026 2027
|
||||
try {
|
||||
String year = "2025"; // 默认2025
|
||||
|
||||
if (productionCode != null) {
|
||||
if (productionCode.startsWith("EY")) {
|
||||
// EY开头,取第2位开始的两位 (索引2和3)
|
||||
if (productionCode.length() >= 4) {
|
||||
String yearShort = productionCode.substring(2, 4);
|
||||
try {
|
||||
Integer.parseInt(yearShort);
|
||||
year = "20" + yearShort;
|
||||
} catch (NumberFormatException e) {
|
||||
// 解析失败,保持默认
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 原有逻辑:取第一个横杠后的两位
|
||||
int firstDash = productionCode.indexOf("-");
|
||||
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
|
||||
String yearShort = productionCode.substring(firstDash + 1, firstDash + 3);
|
||||
try {
|
||||
Integer.parseInt(yearShort); // 验证是否为数字
|
||||
year = "20" + yearShort;
|
||||
} catch (NumberFormatException e) {
|
||||
// 解析失败,保持默认
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接远程文件夹路径
|
||||
String ftpPath = "/" + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF";
|
||||
// 确保路径使用正斜杠,且不包含本地盘符
|
||||
String ftpPath = year + "/" + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF";
|
||||
ftpPath = ftpPath.replace("\\", "/"); // 强制替换反斜杠
|
||||
if (!ftpPath.startsWith("/")) {
|
||||
ftpPath = "/" + ftpPath; // 确保以/开头,表示从FTP根目录开始
|
||||
}
|
||||
// 拼接本地存储路径
|
||||
String localPath = localBaseDir + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF";
|
||||
|
||||
@ -1147,10 +1186,10 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
* 将 PDF 按工作中心分类复制到目标目录(如 zip/工作中心/)
|
||||
*/
|
||||
private void classifyPdfByWorkCenterToTarget(String productionOrderNo,String sourceDirPath, String code, String targetBaseDirPath) {
|
||||
//按工作中心 分类时 查找是否有采购申请单 和生产订单
|
||||
/* //按工作中心 分类时 查找是否有采购申请单 和生产订单
|
||||
//获取这个项目的所有生产令号
|
||||
List<String> orderList = JdUtil.getOrderNumberCollection(productionOrderNo);
|
||||
List<String> cgorderList = JdUtil.getCaiGouOrderNumberList(productionOrderNo);
|
||||
List<String> cgorderList = JdUtil.getCaiGouOrderNumberList(productionOrderNo);*/
|
||||
|
||||
|
||||
File sourceDir = new File(sourceDirPath);
|
||||
@ -1166,16 +1205,16 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
if (materialCode == null) continue;
|
||||
|
||||
String workCenter;
|
||||
//如果这个文件的生产令号 在 生产令号列表 中 或者 采购申请单列表 中 ,则 可以分类到对应的文件夹中
|
||||
/* //如果这个文件的生产令号 在 生产令号列表 中 或者 采购申请单列表 中 ,则 可以分类到对应的文件夹中
|
||||
// 由于物料名近似(如KP08和KP08.7),使用equals精确匹配,不使用contains
|
||||
if (!orderList.contains(materialCode) && !cgorderList.contains(materialCode)) {
|
||||
unmovedMaterials.add(materialCode);
|
||||
workCenter = "未匹配订单";
|
||||
} else {
|
||||
} else {*/
|
||||
workCenter = iProcessRouteService.getRouteCode(materialCode, code);
|
||||
if (iProductionOrderService.isPurchas(productionOrderNo, materialCode)) workCenter = "外购件";
|
||||
if (workCenter == null || workCenter.isEmpty()) workCenter = "无工段";
|
||||
}
|
||||
/*}*/
|
||||
|
||||
File workCenterDir = new File(targetBaseDirPath, workCenter);
|
||||
if (!workCenterDir.exists()) workCenterDir.mkdirs();
|
||||
|
||||
@ -19,8 +19,6 @@ import com.ruoyi.common.core.domain.PageQuery;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.system.domain.dto.BOMItem;
|
||||
import com.ruoyi.system.domain.dto.BOMUploadResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.controller.ProcessRouteController;
|
||||
import com.ruoyi.system.domain.*;
|
||||
@ -55,7 +53,6 @@ import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.system.controller.BomDetailsController.*;
|
||||
import static com.ruoyi.system.runner.JdUtil.getCaiGouOrderNumberList;
|
||||
import static com.ruoyi.system.runner.JdUtil.updateCgOrder1;
|
||||
import static com.ruoyi.system.runner.JsonConverter.createProcessModel;
|
||||
import static com.ruoyi.system.runner.updatePcessPlanConver.updatePcessPlan1;
|
||||
@ -70,8 +67,9 @@ import static com.ruoyi.system.runner.updatePcessPlanConver.updatePcessPlan2;
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
private final IMaterialTotalService iMaterialTotalService;
|
||||
private final IImMaterialService imMaterialService;
|
||||
private final IBomDetailsService iBomDetailsService;
|
||||
private final MssqlQueryService sqlQueryService;
|
||||
private final ProcessRouteMapper baseMapper;
|
||||
private final BomDetailsMapper bomDetailsMapper;
|
||||
private final IMaterialPropertiesService iMaterialPropertiesService;
|
||||
@ -84,6 +82,17 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProcessRouteController.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(IProcessRouteService.class);
|
||||
|
||||
// 全局线程池:核心线程10,最大线程50,存活60s,有界队列
|
||||
private static final ExecutorService GLOBAL_EXECUTOR = new ThreadPoolExecutor(
|
||||
10, 50, 60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(1000),
|
||||
Executors.defaultThreadFactory(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行,防止丢任务
|
||||
);
|
||||
|
||||
// 单据锁 Map
|
||||
private static final ConcurrentHashMap<String, Object> BILL_LOCK = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 查询工艺路线
|
||||
*/
|
||||
@ -335,7 +344,6 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String generateKey1(BomDetails bomDetail, ProcessRoute processRoute) {
|
||||
return String.format("%s:%s:%s", processRoute.getMaterialCode(), processRoute.getMaterialName(), bomDetail.getName(), bomDetail.getPartNumber());
|
||||
}
|
||||
@ -1412,8 +1420,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
// 请求参数,要求为json字符串
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("FormId", "SFC_OperationPlanning");
|
||||
json.addProperty("FieldKeys",
|
||||
"FBillNo,FOperNumber,FProcessId.FName,FOptCtrlCodeId.FName,FActivity1BaseQty,FOperPlanStartTime,FOperPlanFinishTime,FOperQty,FDepartmentId.FName,FWorkCenterId.FName,FOperDescription,FPlanStartTime,FPlanFinishTime,FMOQty,FOperUnitId.FName");
|
||||
json.addProperty("FieldKeys", "FBillNo,FOperNumber,FProcessId.FName,FOptCtrlCodeId.FName,FActivity1BaseQty,FOperPlanStartTime,FOperPlanFinishTime,FOperQty,FDepartmentId.FName,FWorkCenterId.FName,FOperDescription,FPlanStartTime,FPlanFinishTime,FMOQty,FOperUnitId.FName");
|
||||
// 创建过滤条件
|
||||
JsonArray filterString = new JsonArray();
|
||||
JsonObject filterObject = new JsonObject();
|
||||
@ -2031,7 +2038,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
json.addProperty("FieldKeys",
|
||||
"FID,FSubEntity_FDetailID,FProductId.FNumber,FOperNumber,FEntity_FEntryID,FProcessId.FName,FSeqNumber,FSeqName,FPlanStartTime,FPlanFinishTime,"
|
||||
+
|
||||
"FOperPlanStartTime,FOperPlanFinishTime,FMONumber,F_HBYT_RKCK.FName");
|
||||
"FOperPlanStartTime,FOperPlanFinishTime,FMONumber,F_HBYT_RKCK");
|
||||
|
||||
JsonArray filterString = new JsonArray();
|
||||
JsonObject filterObject = new JsonObject();
|
||||
@ -2115,7 +2122,11 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
fSubEntity.setFOperNumber(dto.getFOperNumber());
|
||||
fSubEntity.setFOperPlanStartTime((dto.getFOperPlanStartTime()));
|
||||
fSubEntity.setFOperPlanFinishTime(dto.getFOperPlanFinishTime());
|
||||
fSubEntity.setRkckName(dto.getFRKCKFName());
|
||||
|
||||
FSubEntity.F_HBYT_RKCK rkck = new FSubEntity.F_HBYT_RKCK();
|
||||
rkck.setFNUMBER(dto.getFRKCKFName());
|
||||
fSubEntity.setF_HBYT_RKCK(rkck);
|
||||
|
||||
fEntity.getFSubEntity().add(fSubEntity);
|
||||
}
|
||||
|
||||
@ -2601,20 +2612,71 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
*/
|
||||
@Override
|
||||
public List<String> updateProductionOrders(String rooteProdet, String cangKuNum) throws Exception {
|
||||
// 1. 更新生产订单 (PRD_MO)
|
||||
// 创建一个线程安全的List来收集结果
|
||||
List<String> results = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
List<PlanOrderVo> planOrderList = getSelectProceOrder1(rooteProdet);
|
||||
for (PlanOrderVo planOrderVo : planOrderList) {
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
|
||||
// 1. 更新生产订单 (PRD_MO)
|
||||
if (planOrderList != null && !planOrderList.isEmpty()) {
|
||||
// 按单据号分组,减少锁竞争和线程数
|
||||
Map<String, List<PlanOrderVo>> groupedByBillNo = planOrderList.stream()
|
||||
.collect(Collectors.groupingBy(PlanOrderVo::getFBillNo));
|
||||
|
||||
for (Map.Entry<String, List<PlanOrderVo>> entry : groupedByBillNo.entrySet()) {
|
||||
String billNo = entry.getKey();
|
||||
List<PlanOrderVo> orders = entry.getValue();
|
||||
|
||||
// 使用全局线程池,每个单据号一个任务
|
||||
Future<?> future = GLOBAL_EXECUTOR.submit(() -> {
|
||||
// 获取锁对象,防止跨请求并发
|
||||
Object lock = BILL_LOCK.computeIfAbsent(billNo, k -> new Object());
|
||||
|
||||
synchronized (lock) {
|
||||
try {
|
||||
for (PlanOrderVo planOrderVo : orders) {
|
||||
String currentCangKuNum = cangKuNum;
|
||||
// 判断是否为组焊件
|
||||
if (planOrderVo.getFmaterialidFnumber() != null) {
|
||||
List<BomDetails> bomDetails = iBomDetailsService.selectMaterialZH(rooteProdet, planOrderVo.getFmaterialidFnumber());
|
||||
if (bomDetails != null && !bomDetails.isEmpty()) {
|
||||
currentCangKuNum = "CK025";
|
||||
}
|
||||
}
|
||||
|
||||
JdHuoZhu fidToProductionOrder = JdUtil.getFIDToProductionOrder(planOrderVo);
|
||||
if (fidToProductionOrder != null) {
|
||||
// 更新生产订单的仓库
|
||||
JdUtil.updateOrder2(fidToProductionOrder, cangKuNum);
|
||||
String result = JdUtil.updateOrder2(fidToProductionOrder, currentCangKuNum);
|
||||
results.add("生产订单[" + planOrderVo.getFBillNo() + "]更新结果: " + result);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新生产订单失败: {}", billNo, e);
|
||||
results.add("生产订单[" + billNo + "]更新失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 更新工序计划单 (SFC_OperationPlanning)
|
||||
// 将此逻辑也放入 Future 中,实现与生产订单更新的并行处理
|
||||
Future<?> planUpdateFuture = GLOBAL_EXECUTOR.submit(() -> {
|
||||
try {
|
||||
List<Model> numDTOS = getSelecPlan(rooteProdet);
|
||||
boolean needUpdate = false;
|
||||
if (numDTOS != null) {
|
||||
for (Model numDTO : numDTOS) {
|
||||
String fProcessIdNumber = numDTO.getFProcessId_number();
|
||||
String finalCurrentCangKuNum = cangKuNum;
|
||||
// 判断是否为组焊件
|
||||
List<BomDetails> bomDetails = iBomDetailsService.selectMaterialZH(rooteProdet, fProcessIdNumber);
|
||||
if (bomDetails != null && !bomDetails.isEmpty()) {
|
||||
// 这里假设 selectMaterialZH 返回的是匹配的记录
|
||||
finalCurrentCangKuNum = "CK025";
|
||||
}
|
||||
List<FEntity> fEntityList = numDTO.getFEntity();
|
||||
if (fEntityList != null && !fEntityList.isEmpty()) {
|
||||
for (FEntity fEntity : fEntityList) {
|
||||
@ -2622,34 +2684,126 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
if (fSubEntityList != null && !fSubEntityList.isEmpty()) {
|
||||
for (FSubEntity fSubEntity : fSubEntityList) {
|
||||
// 设置入库仓库
|
||||
fSubEntity.setRkckName(cangKuNum);
|
||||
FSubEntity.F_HBYT_RKCK rkck = new FSubEntity.F_HBYT_RKCK();
|
||||
rkck.setFNUMBER(finalCurrentCangKuNum);
|
||||
fSubEntity.setF_HBYT_RKCK(rkck);
|
||||
needUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needUpdate) {
|
||||
updatePcessPlan2(numDTOS);
|
||||
RepoRet repoRet = updatePcessPlan2(numDTOS);
|
||||
if (repoRet != null) {
|
||||
results.add("工序计划单批量更新结果: " + (repoRet.isSuccessfully() ? "成功" : "失败"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新工序计划单失败", e);
|
||||
results.add("工序计划单更新失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
futures.add(planUpdateFuture);
|
||||
|
||||
// 等待所有任务(生产订单 + 工序计划单)完成
|
||||
for (Future<?> future : futures) {
|
||||
try {
|
||||
future.get();
|
||||
} catch (Exception e) {
|
||||
log.error("等待线程执行异常", e);
|
||||
}
|
||||
}
|
||||
// 注意:全局线程池不需要shutdown
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
/**
|
||||
* 更新采购申请仓库字段
|
||||
*/
|
||||
public List<String> updateCgOrders(String rooteProdet, String cangKuNum) throws Exception {
|
||||
List<CgDTO> list = JdUtil.getCGOrderFBillNoList2(rooteProdet);
|
||||
for (CgDTO orderBillNo : list) {
|
||||
List<FidEntryIdDTO> fidEntryIdDTOS = JdUtil.queryFidAndEntryId(orderBillNo.getOrderBillNo());
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 创建一个线程安全的List来收集结果
|
||||
List<String> results = Collections.synchronizedList(new ArrayList<>());
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
|
||||
// 按单据号分组,减少锁竞争和线程数
|
||||
Map<String, List<CgDTO>> groupedByBillNo = list.stream()
|
||||
.collect(Collectors.groupingBy(CgDTO::getOrderBillNo));
|
||||
|
||||
for (Map.Entry<String, List<CgDTO>> entry : groupedByBillNo.entrySet()) {
|
||||
String billNo = entry.getKey();
|
||||
List<CgDTO> cgList = entry.getValue();
|
||||
|
||||
// 使用全局线程池,每个单据号一个任务
|
||||
Future<?> future = GLOBAL_EXECUTOR.submit(() -> {
|
||||
// 获取锁对象,防止跨请求并发
|
||||
Object lock = BILL_LOCK.computeIfAbsent(billNo, k -> new Object());
|
||||
|
||||
synchronized (lock) {
|
||||
try {
|
||||
for (CgDTO cg : cgList) {
|
||||
ImMaterial material = imMaterialService.selectByCodeAndName(cg.getMaterialCode(), cg.getMaterialName());
|
||||
List<FidEntryIdDTO> fidEntryIdDTOS = JdUtil.queryFidAndEntryId(cg.getOrderBillNo());
|
||||
|
||||
if (fidEntryIdDTOS != null && !fidEntryIdDTOS.isEmpty()) {
|
||||
for (FidEntryIdDTO fidEntryIdDTO : fidEntryIdDTOS) {
|
||||
String s = updateCgOrder1(fidEntryIdDTO, cangKuNum);
|
||||
|
||||
String currentCangKuNum = cangKuNum;
|
||||
String materialCode = cg.getMaterialCode();
|
||||
|
||||
if (materialCode != null) {
|
||||
// VMI库
|
||||
boolean result = sqlQueryService.checkIfVmiBusiness(materialCode);
|
||||
if (material != null && result) {
|
||||
continue;
|
||||
}
|
||||
// 015开头:原材料入库
|
||||
else if (materialCode.startsWith("015") || materialCode.startsWith("AB")) {
|
||||
currentCangKuNum = "CK001";
|
||||
}
|
||||
// 毛坯库
|
||||
else if (materialCode.startsWith("(M)")) {
|
||||
currentCangKuNum = "CK002";
|
||||
}
|
||||
// 电器入库
|
||||
/*else if (materialCode.startsWith("DK") || materialCode.startsWith("DC") || materialCode.startsWith("DP") || materialCode.startsWith("DT")
|
||||
|| materialCode.startsWith("DQ") || materialCode.startsWith("DD") || materialCode.startsWith("DF") || materialCode.startsWith("DX")
|
||||
|| materialCode.startsWith("DL") || materialCode.startsWith("DH") || materialCode.startsWith("DJ") || materialCode.startsWith("DZ") || materialCode.startsWith("DB")) {
|
||||
currentCangKuNum = "CK006";
|
||||
}*/
|
||||
}
|
||||
|
||||
String resultStr = updateCgOrder1(fidEntryIdDTO, currentCangKuNum);
|
||||
results.add(resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新采购订单失败: {}", billNo, e);
|
||||
results.add("更新失败: " + billNo + " - " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
return null;
|
||||
// 等待所有任务完成
|
||||
for (Future<?> future : futures) {
|
||||
try {
|
||||
future.get();
|
||||
} catch (Exception e) {
|
||||
log.error("等待线程执行异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user