ErpClient.java
9.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package com.xly.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* ERP 后端(xlyEntry)薄 HTTP 客户端。
*
* <p>xlyAi 的读写业务逻辑在 ERP 后端,xlyAi 只是像前端一样发同样的 API 请求。本类负责:
* <ul>
* <li>取得可用的 ERP 会话 token —— 本地开发用配置的 dev 账号 {@code /checklogin} 登录并缓存;
* 生产应改为透传用户浏览器里的 ERP 登录 token(per-request,never stored)。</li>
* <li>调用通用表单读接口 {@code getBusinessDataByFormcustomId};会话过期(code=-2)自动重登一次重试。</li>
* </ul>
*/
@Service
public class ErpClient {
private static final Logger log = LoggerFactory.getLogger(ErpClient.class);
private final ObjectMapper mapper;
private final HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
@Value("${erp.baseurl}")
private String baseUrl;
@Value("${erp.dev-login.brand:1111111111}")
private String brand;
@Value("${erp.dev-login.subsidiary:1111111111}")
private String subsidiary;
@Value("${erp.dev-login.username:admin}")
private String username;
@Value("${erp.dev-login.password:666666}")
private String password;
private volatile String cachedToken;
public ErpClient(ObjectMapper mapper) {
this.mapper = mapper;
}
/** 用配置的 dev 账号登录 ERP,缓存返回的 Authorization token。 */
private synchronized String login() {
try {
String url = baseUrl + "/checklogin/" + brand + "/" + subsidiary;
String body = mapper.writeValueAsString(Map.of("username", username, "password", password));
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json;charset=UTF-8")
.timeout(Duration.ofSeconds(20))
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
JsonNode root = mapper.readTree(resp.body());
if (root.path("code").asInt() != 1) {
throw new IllegalStateException("ERP 登录失败: " + root.path("msg").asText());
}
String token = root.path("dataset").path("rows").path(0).path("token").asText(null);
if (token == null || token.isBlank()) {
throw new IllegalStateException("ERP 登录未返回 token");
}
cachedToken = token;
log.info("ERP dev-login ok (user={}), token cached", username);
return token;
} catch (Exception e) {
throw new RuntimeException("ERP 登录异常: " + e.getMessage(), e);
}
}
private String token() {
String t = cachedToken;
return (t != null && !t.isBlank()) ? t : login();
}
/**
* 读取某表单一页数据,返回整个响应根节点(含 code / msg / dataset)。
* 会话过期(code=-2)时自动重登一次并重试。
*/
public JsonNode readForm(String formId, String moduleId, int page, int pageSize,
String filterField, String filterValue) {
JsonNode root = doRead(formId, moduleId, page, pageSize, filterField, filterValue, token());
if (root.path("code").asInt() == -2) {
login();
root = doRead(formId, moduleId, page, pageSize, filterField, filterValue, token());
}
return root;
}
private JsonNode doRead(String formId, String moduleId, int page, int pageSize,
String filterField, String filterValue, String tok) {
try {
String url = baseUrl + "/business/getBusinessDataByFormcustomId/" + formId
+ "?sModelsId=" + moduleId + "&sName=";
List<Map<String, Object>> bFilter = new ArrayList<>();
if (filterField != null && !filterField.isBlank() && filterValue != null && !filterValue.isBlank()) {
Map<String, Object> f = new LinkedHashMap<>();
f.put("bFilterName", filterField);
f.put("bFilterCondition", "like");
f.put("bFilterValue", filterValue.trim());
bFilter.add(f);
}
String body = mapper.writeValueAsString(Map.of(
"pageNum", page, "pageSize", pageSize, "bFilter", bFilter));
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json;charset=UTF-8")
.header("Authorization", tok)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return mapper.readTree(resp.body());
} catch (Exception e) {
throw new RuntimeException("ERP 读取异常: " + e.getMessage(), e);
}
}
/**
* 执行一次字段更新(addUpdateDelBusinessData, handleType=update)。会话过期自动重登重试。
* 请求体格式与 ERP 前端一致:{@code {data:[{sTable, name:"master", column:[{handleType:"update", sId, field:value}]}]}}。
*/
public JsonNode updateForm(String moduleId, String table, String billId, String field, String value) {
JsonNode root = doUpdate(moduleId, table, billId, field, value, token());
if (root.path("code").asInt() == -2) {
login();
root = doUpdate(moduleId, table, billId, field, value, token());
}
return root;
}
/** 删除一条记录(addUpdateDelBusinessData, handleType=del)。会话过期自动重登重试。 */
public JsonNode deleteForm(String moduleId, String table, String billId) {
JsonNode root = doDelete(moduleId, table, billId, token());
if (root.path("code").asInt() == -2) {
login();
root = doDelete(moduleId, table, billId, token());
}
return root;
}
private JsonNode doDelete(String moduleId, String table, String billId, String tok) {
try {
String url = baseUrl + "/business/addUpdateDelBusinessData?sModelsId=" + moduleId;
Map<String, Object> col = new LinkedHashMap<>();
col.put("handleType", "del");
col.put("sId", billId);
Map<String, Object> dataItem = new LinkedHashMap<>();
dataItem.put("sTable", table);
dataItem.put("name", "master");
dataItem.put("column", List.of(col));
String body = mapper.writeValueAsString(Map.of("data", List.of(dataItem)));
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json;charset=UTF-8")
.header("Authorization", tok)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return mapper.readTree(resp.body());
} catch (Exception e) {
throw new RuntimeException("ERP 删除异常: " + e.getMessage(), e);
}
}
private JsonNode doUpdate(String moduleId, String table, String billId, String field, String value, String tok) {
try {
String url = baseUrl + "/business/addUpdateDelBusinessData?sModelsId=" + moduleId;
Map<String, Object> col = new LinkedHashMap<>();
col.put("handleType", "update");
col.put("sId", billId);
col.put(field, value);
Map<String, Object> dataItem = new LinkedHashMap<>();
dataItem.put("sTable", table);
dataItem.put("name", "master");
dataItem.put("column", List.of(col));
String body = mapper.writeValueAsString(Map.of("data", List.of(dataItem)));
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json;charset=UTF-8")
.header("Authorization", tok)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return mapper.readTree(resp.body());
} catch (Exception e) {
throw new RuntimeException("ERP 更新异常: " + e.getMessage(), e);
}
}
}