union-ark-web 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROJECT_OVERVIEW.md +405 -0
- package/README.md +177 -0
- package/ark-control/pom.xml +142 -0
- package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SecurityResult.java +14 -0
- package/ark-control/src/local-mock/java/com/epcc/commons/securityproxy/api/SymmetricalSecurityService.java +11 -0
- package/ark-control/src/local-mock/java/com/epcc/dubbo/result/Result.java +29 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptField.java +16 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldLong.java +17 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/annotation/EnDecryptFieldWithTag.java +16 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/BaseDataErrorCode.java +26 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/CheckException.java +33 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/exception/ErrorCode.java +9 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/redis/RedisCacheService.java +131 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/Constant.java +24 -0
- package/ark-control/src/local-mock/java/com/nucc/channel/ark/common/util/ResultUtil.java +9 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalMockSensitiveProxy.java +40 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalRedisCacheService.java +45 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveHostConfiguration.java +27 -0
- package/ark-control/src/local-mock/java/com/union/control/local/sensitive/LocalSensitiveProxyConfiguration.java +16 -0
- package/ark-control/src/main/java/com/union/control/ArkControlApplication.java +17 -0
- package/ark-control/src/main/java/com/union/control/dubbo/ProviderAccessLogFilter.java +45 -0
- package/ark-control/src/main/java/com/union/control/mapper/AgentExecutionMapper.java +35 -0
- package/ark-control/src/main/java/com/union/control/mapper/ConversationMapper.java +44 -0
- package/ark-control/src/main/java/com/union/control/mapper/MemoryStoreMapper.java +41 -0
- package/ark-control/src/main/java/com/union/control/mapper/ScheduledTaskMapper.java +96 -0
- package/ark-control/src/main/java/com/union/control/mapper/SensitiveAddressBookDemo.java +31 -0
- package/ark-control/src/main/java/com/union/control/mapper/SensitiveDataDemoMapper.java +24 -0
- package/ark-control/src/main/java/com/union/control/mapper/interceptor/AESInterceptor.java +265 -0
- package/ark-control/src/main/java/com/union/control/mapper/interceptor/AddressBookHandler.java +105 -0
- package/ark-control/src/main/java/com/union/control/schedule/ScheduledExecutionToken.java +55 -0
- package/ark-control/src/main/java/com/union/control/schedule/ScheduledTaskScheduler.java +206 -0
- package/ark-control/src/main/java/com/union/control/service/impl/AgentExecutionServiceImpl.java +212 -0
- package/ark-control/src/main/java/com/union/control/service/impl/AgentProxyServiceImpl.java +143 -0
- package/ark-control/src/main/java/com/union/control/service/impl/ConversationServiceImpl.java +245 -0
- package/ark-control/src/main/java/com/union/control/service/impl/MemoryStoreServiceImpl.java +252 -0
- package/ark-control/src/main/java/com/union/control/service/impl/RunningAnalysisMockServiceImpl.java +301 -0
- package/ark-control/src/main/java/com/union/control/service/impl/ScheduledTaskServiceImpl.java +576 -0
- package/ark-control/src/main/java/com/union/control/service/impl/SensitiveDataDemoServiceImpl.java +144 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/RedisRevealTokenStore.java +90 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealProcessor.java +319 -0
- package/ark-control/src/main/java/com/union/control/service/sensitive/SensitiveRevealServiceImpl.java +72 -0
- package/ark-control/src/main/java/com/union/control/utils/AgentSupport.java +103 -0
- package/ark-control/src/main/java/com/union/control/utils/security/SymmetricalSecurityUtils.java +160 -0
- package/ark-control/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter +1 -0
- package/ark-control/src/main/resources/application.properties +4 -0
- package/ark-control/src/main/resources/application.yml +32 -0
- package/ark-control/src/main/resources/dubbo-provider.xml +20 -0
- package/ark-control/src/main/resources/mapper/AgentExecutionMapper.xml +75 -0
- package/ark-control/src/main/resources/mapper/ConversationMapper.xml +110 -0
- package/ark-control/src/main/resources/mapper/MemoryStoreMapper.xml +74 -0
- package/ark-control/src/main/resources/mapper/ScheduledTaskMapper.xml +286 -0
- package/ark-control/src/main/resources/mapper/SensitiveDataDemoMapper.xml +55 -0
- package/ark-control/src/main/resources/schema.sql +146 -0
- package/ark-control/src/test/java/com/union/control/AgentExecutionServiceTest.java +170 -0
- package/ark-control/src/test/java/com/union/control/ArkControlStructureContractTest.java +107 -0
- package/ark-control/src/test/java/com/union/control/ConversationServiceTest.java +89 -0
- package/ark-control/src/test/java/com/union/control/MemoryStoreServiceTest.java +26 -0
- package/ark-control/src/test/java/com/union/control/PydanticAiControlContractTest.java +52 -0
- package/ark-control/src/test/java/com/union/control/TestJson.java +45 -0
- package/ark-control/src/test/java/com/union/control/dubbo/ProviderAccessLogFilterTest.java +53 -0
- package/ark-control/src/test/java/com/union/control/local/sensitive/LocalSensitiveProxyConfigurationTest.java +23 -0
- package/ark-control/src/test/java/com/union/control/mapper/interceptor/AESInterceptorCompatibilityTest.java +505 -0
- package/ark-control/src/test/java/com/union/control/schedule/ScheduledExecutionTokenTest.java +27 -0
- package/ark-control/src/test/java/com/union/control/schedule/ScheduledTaskSchedulerTest.java +51 -0
- package/ark-control/src/test/java/com/union/control/service/AgentProxyServiceTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/service/ScheduledTaskServiceTest.java +483 -0
- package/ark-control/src/test/java/com/union/control/service/impl/SensitiveDataDemoServiceImplTest.java +104 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/RedisRevealTokenStoreTest.java +65 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealBoundaryTest.java +99 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorCodecTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/service/sensitive/SensitiveRevealProcessorTest.java +111 -0
- package/ark-control/src/test/java/com/union/control/utils/security/SymmetricalSecurityUtilsCompatibilityTest.java +87 -0
- package/ark-control-facade/pom.xml +16 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentExecutionService.java +8 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentProxyService.java +7 -0
- package/ark-control-facade/src/main/java/com/union/control/service/AgentResponse.java +22 -0
- package/ark-control-facade/src/main/java/com/union/control/service/ConversationService.java +12 -0
- package/ark-control-facade/src/main/java/com/union/control/service/MemoryStoreService.java +12 -0
- package/ark-control-facade/src/main/java/com/union/control/service/RunningAnalysisMockService.java +10 -0
- package/ark-control-facade/src/main/java/com/union/control/service/ScheduledTaskService.java +40 -0
- package/ark-control-facade/src/main/java/com/union/control/service/SensitiveDataDemoService.java +13 -0
- package/ark-control-facade/src/main/java/com/union/control/service/sensitive/SensitiveRevealService.java +18 -0
- package/ark-web/README.md +80 -0
- package/ark-web/pom.xml +105 -0
- package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/ArkAuthServiceMock.java +105 -0
- package/ark-web/src/local-mock/java/com/epcc/arkweb/mock/LocalKaptchaConfiguration.java +26 -0
- package/ark-web/src/main/java/com/epcc/arkweb/Application.java +13 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/InterceptorConfig.java +20 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/Realm.java +64 -0
- package/ark-web/src/main/java/com/epcc/arkweb/config/ShiroConfig.java +129 -0
- package/ark-web/src/main/java/com/epcc/arkweb/filter/LoginFormFilter.java +43 -0
- package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthContextHolder.java +39 -0
- package/ark-web/src/main/java/com/epcc/arkweb/helper/AuthenticatedRequest.java +91 -0
- package/ark-web/src/main/java/com/epcc/arkweb/model/ShiroUser.java +21 -0
- package/ark-web/src/main/java/com/epcc/arkweb/utils/ResultMsg.java +19 -0
- package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskCommandVO.java +30 -0
- package/ark-web/src/main/java/com/epcc/arkweb/vo/llm/ScheduledTaskQueryVO.java +20 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/CommonController.java +25 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptor.java +229 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentController.java +165 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/AgentPermission.java +13 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/LlmController.java +209 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/llm/ScheduledTaskController.java +160 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveCaptchaService.java +146 -0
- package/ark-web/src/main/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealController.java +163 -0
- package/ark-web/src/main/resources/application.properties +1 -0
- package/ark-web/src/main/resources/application.yml +23 -0
- package/ark-web/src/main/resources/dubbo-consumer.xml +19 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/CasShiroConfig.java +229 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/InterceptorConfig.java +35 -0
- package/ark-web/src/production-overlay/java/com/epcc/arkweb/config/ShiroConfig.java +161 -0
- package/ark-web/src/test/java/com/epcc/arkweb/ArkWebStructureContractTest.java +290 -0
- package/ark-web/src/test/java/com/epcc/arkweb/ShiroTestSupport.java +32 -0
- package/ark-web/src/test/java/com/epcc/arkweb/config/LocalShiroIntegrationTest.java +121 -0
- package/ark-web/src/test/java/com/epcc/arkweb/helper/AuthenticatedRequestTest.java +131 -0
- package/ark-web/src/test/java/com/epcc/arkweb/local/LocalArkAuthServiceMockTest.java +17 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentAuthorizationInterceptorTest.java +134 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/AgentControllerAuthorizationTest.java +97 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/llm/LlmControllerTest.java +113 -0
- package/ark-web/src/test/java/com/epcc/arkweb/web/sensitive/reveal/SensitiveRevealControllerTest.java +273 -0
- package/package.json +18 -0
- package/pom.xml +16 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
package com.union.control.service.impl;
|
|
2
|
+
|
|
3
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
4
|
+
import com.union.control.service.AgentProxyService;
|
|
5
|
+
import com.union.control.service.AgentResponse;
|
|
6
|
+
|
|
7
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
8
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
9
|
+
import org.springframework.http.HttpEntity;
|
|
10
|
+
import org.springframework.http.HttpHeaders;
|
|
11
|
+
import org.springframework.http.HttpMethod;
|
|
12
|
+
import org.springframework.http.MediaType;
|
|
13
|
+
import org.springframework.http.ResponseEntity;
|
|
14
|
+
import org.springframework.http.client.ClientHttpResponse;
|
|
15
|
+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
|
16
|
+
import org.springframework.stereotype.Service;
|
|
17
|
+
import org.springframework.web.client.ResponseErrorHandler;
|
|
18
|
+
import org.springframework.web.client.RestTemplate;
|
|
19
|
+
import org.slf4j.Logger;
|
|
20
|
+
import org.slf4j.LoggerFactory;
|
|
21
|
+
|
|
22
|
+
import java.nio.charset.StandardCharsets;
|
|
23
|
+
import java.util.LinkedHashMap;
|
|
24
|
+
import java.util.Map;
|
|
25
|
+
|
|
26
|
+
@Service("agentProxyService")
|
|
27
|
+
public class AgentProxyServiceImpl implements AgentProxyService {
|
|
28
|
+
private static final Logger logger = LoggerFactory.getLogger(AgentProxyServiceImpl.class);
|
|
29
|
+
private final String pyAppBaseUrl;
|
|
30
|
+
private final RestTemplate http;
|
|
31
|
+
private final ObjectMapper json;
|
|
32
|
+
|
|
33
|
+
@Autowired
|
|
34
|
+
public AgentProxyServiceImpl(
|
|
35
|
+
@Value("${agent.py-app-base-url}") String pyAppBaseUrl,
|
|
36
|
+
ObjectMapper json,
|
|
37
|
+
@Value("${AGENT_MAX_RUN_SECONDS:900}") double maxRunSeconds) {
|
|
38
|
+
this(pyAppBaseUrl, new RestTemplate(), json);
|
|
39
|
+
double millis = Math.ceil(maxRunSeconds * 1000);
|
|
40
|
+
if (!Double.isFinite(millis) || millis < 1 || millis > Integer.MAX_VALUE)
|
|
41
|
+
throw new IllegalArgumentException("AGENT_MAX_RUN_SECONDS is out of range");
|
|
42
|
+
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
|
43
|
+
factory.setConnectTimeout((int) millis);
|
|
44
|
+
factory.setReadTimeout((int) millis);
|
|
45
|
+
http.setRequestFactory(factory);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public AgentProxyServiceImpl(String pyAppBaseUrl, RestTemplate http, ObjectMapper json) {
|
|
49
|
+
this.pyAppBaseUrl = pyAppBaseUrl.replaceAll("/+$", "");
|
|
50
|
+
this.http = http;
|
|
51
|
+
this.json = json;
|
|
52
|
+
ResponseErrorHandler passThroughErrors = new ResponseErrorHandler() {
|
|
53
|
+
@Override
|
|
54
|
+
public boolean hasError(ClientHttpResponse response) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Override
|
|
59
|
+
public void handleError(ClientHttpResponse response) {}
|
|
60
|
+
};
|
|
61
|
+
this.http.setErrorHandler(passThroughErrors);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public AgentResponse sync(String cookie, String payload) {
|
|
65
|
+
long startedAt = System.nanoTime();
|
|
66
|
+
logger.info("Agent call started mode=sync payload_chars={}", payload.length());
|
|
67
|
+
HttpHeaders headers = new HttpHeaders();
|
|
68
|
+
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
69
|
+
headers.set(HttpHeaders.COOKIE, cookie);
|
|
70
|
+
try {
|
|
71
|
+
ResponseEntity<byte[]> response = http.exchange(
|
|
72
|
+
pyAppBaseUrl + "/agent/v1/runs/sync",
|
|
73
|
+
HttpMethod.POST,
|
|
74
|
+
new HttpEntity<>(payload.getBytes(StandardCharsets.UTF_8), headers),
|
|
75
|
+
byte[].class
|
|
76
|
+
);
|
|
77
|
+
logCompleted("sync", response.getStatusCode().value(), startedAt);
|
|
78
|
+
return result(response);
|
|
79
|
+
} catch (RuntimeException error) {
|
|
80
|
+
logFailed("sync", startedAt, error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public AgentResponse scheduled(String authorization) {
|
|
86
|
+
long startedAt = System.nanoTime();
|
|
87
|
+
logger.info("Agent call started mode=scheduled");
|
|
88
|
+
HttpHeaders headers = new HttpHeaders();
|
|
89
|
+
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
90
|
+
headers.set(HttpHeaders.AUTHORIZATION, authorization);
|
|
91
|
+
try {
|
|
92
|
+
ResponseEntity<byte[]> response = http.exchange(
|
|
93
|
+
pyAppBaseUrl + "/agent/v1/runs/scheduled",
|
|
94
|
+
HttpMethod.POST,
|
|
95
|
+
new HttpEntity<byte[]>(new byte[]{'{', '}'}, headers),
|
|
96
|
+
byte[].class);
|
|
97
|
+
logCompleted("scheduled", response.getStatusCode().value(), startedAt);
|
|
98
|
+
return result(response);
|
|
99
|
+
} catch (RuntimeException error) {
|
|
100
|
+
logFailed("scheduled", startedAt, error);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public AgentResponse cancel(String cookie, String conversationId, String runId) {
|
|
106
|
+
HttpHeaders headers = new HttpHeaders();
|
|
107
|
+
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
108
|
+
headers.set(HttpHeaders.COOKIE, cookie);
|
|
109
|
+
Map<String, Object> body = new LinkedHashMap<>();
|
|
110
|
+
body.put("conversationId", conversationId);
|
|
111
|
+
body.put("runId", runId);
|
|
112
|
+
return result(http.exchange(
|
|
113
|
+
pyAppBaseUrl + "/agent/v1/runs/cancel",
|
|
114
|
+
HttpMethod.POST,
|
|
115
|
+
new HttpEntity<Map<String, Object>>(body, headers),
|
|
116
|
+
byte[].class
|
|
117
|
+
));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private AgentResponse result(ResponseEntity<byte[]> response) {
|
|
121
|
+
try {
|
|
122
|
+
String body = json.writeValueAsString(json.readTree(
|
|
123
|
+
new String(response.getBody(), StandardCharsets.UTF_8)));
|
|
124
|
+
return new AgentResponse(response.getStatusCode().value(), body);
|
|
125
|
+
} catch (Exception error) {
|
|
126
|
+
throw new IllegalStateException("Agent 返回的不是合法 JSON", error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static void logCompleted(String mode, int status, long startedAt) {
|
|
131
|
+
long duration = (System.nanoTime() - startedAt) / 1_000_000L;
|
|
132
|
+
if (status >= 400)
|
|
133
|
+
logger.warn("Agent call completed mode={} status={} duration_ms={}", mode, status, duration);
|
|
134
|
+
else
|
|
135
|
+
logger.info("Agent call completed mode={} status={} duration_ms={}", mode, status, duration);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static void logFailed(String mode, long startedAt, RuntimeException error) {
|
|
139
|
+
logger.warn("Agent call failed mode={} duration_ms={} error_type={}", mode,
|
|
140
|
+
(System.nanoTime() - startedAt) / 1_000_000L, error.getClass().getSimpleName());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
package com.union.control.service.impl;
|
|
2
|
+
|
|
3
|
+
import com.fasterxml.jackson.core.type.TypeReference;
|
|
4
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
5
|
+
import com.union.control.mapper.AgentExecutionMapper;
|
|
6
|
+
import com.union.control.mapper.ConversationMapper;
|
|
7
|
+
import com.union.control.mapper.ScheduledTaskMapper;
|
|
8
|
+
import com.union.control.service.ConversationService;
|
|
9
|
+
import org.springframework.stereotype.Service;
|
|
10
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
11
|
+
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import java.util.LinkedHashMap;
|
|
14
|
+
import java.util.List;
|
|
15
|
+
import java.util.Map;
|
|
16
|
+
import java.util.NoSuchElementException;
|
|
17
|
+
import java.util.UUID;
|
|
18
|
+
|
|
19
|
+
import static com.union.control.utils.AgentSupport.*;
|
|
20
|
+
|
|
21
|
+
@Service("conversationService")
|
|
22
|
+
public class ConversationServiceImpl implements ConversationService {
|
|
23
|
+
private final ConversationMapper conversationMapper;
|
|
24
|
+
private final AgentExecutionMapper executionMapper;
|
|
25
|
+
private final ScheduledTaskMapper scheduledTaskMapper;
|
|
26
|
+
private final ObjectMapper json;
|
|
27
|
+
|
|
28
|
+
public ConversationServiceImpl(
|
|
29
|
+
ConversationMapper conversationMapper,
|
|
30
|
+
AgentExecutionMapper executionMapper,
|
|
31
|
+
ScheduledTaskMapper scheduledTaskMapper,
|
|
32
|
+
ObjectMapper json) {
|
|
33
|
+
this.conversationMapper = conversationMapper;
|
|
34
|
+
this.executionMapper = executionMapper;
|
|
35
|
+
this.scheduledTaskMapper = scheduledTaskMapper;
|
|
36
|
+
this.json = json;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public Map<String, Object> userInfo(String input) {
|
|
40
|
+
Map<String, Object> request = request(json, input);
|
|
41
|
+
Map<String, Object> user = new LinkedHashMap<>();
|
|
42
|
+
user.put("userId", userId(request));
|
|
43
|
+
user.put("orgCode", identity(request, "orgCode"));
|
|
44
|
+
return ok(user);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public Map<String, Object> conversations(String input) {
|
|
48
|
+
Map<String, Object> request = request(json, input);
|
|
49
|
+
String userId = userId(request);
|
|
50
|
+
int limit = integer(request, "limit", 1, 100);
|
|
51
|
+
requireRange(limit, 1, 100, "limit");
|
|
52
|
+
return ok(conversationMapper.findConversations(userId, limit));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public Map<String, Object> conversation(String input) {
|
|
56
|
+
Map<String, Object> request = request(json, input);
|
|
57
|
+
String userId = userId(request);
|
|
58
|
+
String conversationId = text(request, "conversationId", 64, true);
|
|
59
|
+
requireId(conversationId);
|
|
60
|
+
Map<String, Object> result = loadConversation(userId, conversationId);
|
|
61
|
+
List<Map<String, Object>> executions = executionMapper.findExecutions(userId, conversationId);
|
|
62
|
+
result.put("messages", loadBrowserMessages(userId, conversationId, executions));
|
|
63
|
+
result.put("executions", publicExecutions(executions));
|
|
64
|
+
return ok(result);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public Map<String, Object> conversationMessages(String input) {
|
|
68
|
+
Map<String, Object> payload = request(json, input);
|
|
69
|
+
String userId = userId(payload);
|
|
70
|
+
String conversationId = text(payload, "conversationId", 64, true);
|
|
71
|
+
requireId(conversationId);
|
|
72
|
+
requireOwned(userId, conversationId, false);
|
|
73
|
+
Map<String, Object> response = new LinkedHashMap<>();
|
|
74
|
+
response.put("success", true);
|
|
75
|
+
response.put("messages", loadRootMessages(userId, conversationId));
|
|
76
|
+
return response;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public Map<String, Object> rename(String input) {
|
|
80
|
+
Map<String, Object> payload = request(json, input);
|
|
81
|
+
String userId = userId(payload);
|
|
82
|
+
String conversationId = text(payload, "conversationId", 64, true);
|
|
83
|
+
String title = text(payload, "title", 255, true);
|
|
84
|
+
requireId(conversationId);
|
|
85
|
+
if (conversationMapper.updateConversationTitle(title, conversationId, userId) != 1)
|
|
86
|
+
throw new NoSuchElementException("会话不存在");
|
|
87
|
+
return ok(loadConversation(userId, conversationId));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Transactional
|
|
91
|
+
public Map<String, Object> deleteConversation(String input) {
|
|
92
|
+
Map<String, Object> payload = request(json, input);
|
|
93
|
+
String userId = userId(payload);
|
|
94
|
+
String conversationId = text(payload, "conversationId", 64, true);
|
|
95
|
+
requireId(conversationId);
|
|
96
|
+
requireOwned(userId, conversationId, true);
|
|
97
|
+
conversationMapper.softDeleteMessages(conversationId, userId);
|
|
98
|
+
executionMapper.softDeleteExecutions(conversationId, userId);
|
|
99
|
+
scheduledTaskMapper.softDeleteByConversation(conversationId, userId);
|
|
100
|
+
int updated = conversationMapper.softDeleteConversation(conversationId, userId);
|
|
101
|
+
if (updated != 1) throw new NoSuchElementException("会话不存在");
|
|
102
|
+
return ok(null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Materializes a trusted completed result through the normal conversation path. */
|
|
106
|
+
@Transactional
|
|
107
|
+
String materializeCompletedConversation(
|
|
108
|
+
String userId, String title, String prompt, String content, String agentName) {
|
|
109
|
+
String random = UUID.randomUUID().toString().replace("-", "");
|
|
110
|
+
String conversationId = "scheduled-" + random;
|
|
111
|
+
String runId = "scheduled-open-" + random;
|
|
112
|
+
conversationMapper.insertConversation(conversationId, userId, title);
|
|
113
|
+
Map<String, Object> execution = new LinkedHashMap<>();
|
|
114
|
+
execution.put("runId", runId);
|
|
115
|
+
execution.put("conversationId", conversationId);
|
|
116
|
+
execution.put("userId", userId);
|
|
117
|
+
execution.put("agentName", agentName);
|
|
118
|
+
executionMapper.insertCompletedRootExecution(execution);
|
|
119
|
+
insertTrustedMessage(conversationId, userId,
|
|
120
|
+
"scheduled-user-" + random, number(execution.get("id")), "user", 1, prompt);
|
|
121
|
+
insertTrustedMessage(conversationId, userId,
|
|
122
|
+
"scheduled-assistant-" + random, number(execution.get("id")), "assistant", 2, content);
|
|
123
|
+
return conversationId;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private void insertTrustedMessage(
|
|
127
|
+
String conversationId, String userId, String messageId, long executionId,
|
|
128
|
+
String role, long sequence, String content) {
|
|
129
|
+
Map<String, Object> payload = new LinkedHashMap<>();
|
|
130
|
+
payload.put("content", content);
|
|
131
|
+
try {
|
|
132
|
+
conversationMapper.insertMessage(conversationId, userId, messageId, executionId,
|
|
133
|
+
role, sequence, json.writeValueAsString(payload));
|
|
134
|
+
} catch (Exception error) {
|
|
135
|
+
throw new IllegalArgumentException("可信消息无法持久化", error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
private List<Object> loadRootMessages(String userId, String conversationId) {
|
|
141
|
+
List<Map<String, Object>> rows = conversationMapper.findRootMessages(userId, conversationId);
|
|
142
|
+
List<Object> messages = new ArrayList<>();
|
|
143
|
+
for (Map<String, Object> row : rows) {
|
|
144
|
+
messages.add(message(row));
|
|
145
|
+
}
|
|
146
|
+
return messages;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private List<Object> loadBrowserMessages(
|
|
150
|
+
String userId, String conversationId, List<Map<String, Object>> executions) {
|
|
151
|
+
Map<String, Map<String, Object>> byRun = new LinkedHashMap<>();
|
|
152
|
+
for (Map<String, Object> execution : executions)
|
|
153
|
+
byRun.put(String.valueOf(execution.get("runId")), execution);
|
|
154
|
+
List<Map<String, Object>> rows = conversationMapper.findBrowserMessages(userId, conversationId);
|
|
155
|
+
List<Object> result = new ArrayList<>();
|
|
156
|
+
Map<String, List<Object>> childMessages = new LinkedHashMap<>();
|
|
157
|
+
for (Map<String, Object> row : rows) {
|
|
158
|
+
if (row.get("parentExecutionId") == null) {
|
|
159
|
+
result.add(message(row));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
String runId = String.valueOf(row.get("runId"));
|
|
163
|
+
List<Object> messages = childMessages.get(runId);
|
|
164
|
+
if (messages == null) {
|
|
165
|
+
messages = new ArrayList<>();
|
|
166
|
+
childMessages.put(runId, messages);
|
|
167
|
+
result.add(activity(byRun.get(runId), messages));
|
|
168
|
+
}
|
|
169
|
+
messages.add(message(row));
|
|
170
|
+
}
|
|
171
|
+
for (Map<String, Object> execution : executions) {
|
|
172
|
+
if (execution.get("parentExecutionId") == null) continue;
|
|
173
|
+
String runId = String.valueOf(execution.get("runId"));
|
|
174
|
+
if (!childMessages.containsKey(runId))
|
|
175
|
+
result.add(activity(execution, new ArrayList<>()));
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private Map<String, Object> message(Map<String, Object> row) {
|
|
181
|
+
try {
|
|
182
|
+
Object parsed = json.readValue(String.valueOf(row.get("payload")),
|
|
183
|
+
new TypeReference<Object>() {});
|
|
184
|
+
if (!(parsed instanceof Map)) throw new IllegalStateException("消息数据损坏");
|
|
185
|
+
Map<String, Object> message = new LinkedHashMap<>();
|
|
186
|
+
message.put("id", row.get("messageId"));
|
|
187
|
+
message.put("role", row.get("role"));
|
|
188
|
+
@SuppressWarnings("unchecked") Map<String, Object> payload =
|
|
189
|
+
(Map<String, Object>) parsed;
|
|
190
|
+
message.putAll(payload);
|
|
191
|
+
return message;
|
|
192
|
+
} catch (IllegalStateException error) {
|
|
193
|
+
throw error;
|
|
194
|
+
} catch (Exception error) {
|
|
195
|
+
throw new IllegalStateException("消息数据损坏", error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private static Map<String, Object> activity(
|
|
200
|
+
Map<String, Object> execution, List<Object> messages) {
|
|
201
|
+
if (execution == null) throw new IllegalStateException("execution 数据损坏");
|
|
202
|
+
Map<String, Object> content = new LinkedHashMap<>();
|
|
203
|
+
content.put("runId", execution.get("runId"));
|
|
204
|
+
content.put("parentRunId", execution.get("parentRunId"));
|
|
205
|
+
content.put("agentName", execution.get("agentName"));
|
|
206
|
+
content.put("delegationToolCallId", execution.get("delegationToolCallId"));
|
|
207
|
+
content.put("task", execution.get("task"));
|
|
208
|
+
content.put("status", execution.get("status"));
|
|
209
|
+
content.put("messages", messages);
|
|
210
|
+
content.put("errorCode", execution.get("errorCode"));
|
|
211
|
+
Map<String, Object> activity = new LinkedHashMap<>();
|
|
212
|
+
activity.put("id", "subagent:" + execution.get("runId"));
|
|
213
|
+
activity.put("role", "activity");
|
|
214
|
+
activity.put("activityType", "subagent_execution");
|
|
215
|
+
activity.put("content", content);
|
|
216
|
+
return activity;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private Map<String, Object> loadConversation(String userId, String conversationId) {
|
|
220
|
+
Map<String, Object> conversation =
|
|
221
|
+
conversationMapper.findConversation(userId, conversationId);
|
|
222
|
+
if (conversation == null) throw new NoSuchElementException("会话不存在");
|
|
223
|
+
return conversation;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private static List<Map<String, Object>> publicExecutions(List<Map<String, Object>> rows) {
|
|
227
|
+
List<Map<String, Object>> result = new ArrayList<>();
|
|
228
|
+
for (Map<String, Object> row : rows) result.add(publicExecution(row));
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private static Map<String, Object> publicExecution(Map<String, Object> row) {
|
|
233
|
+
Map<String, Object> result = new LinkedHashMap<>(row);
|
|
234
|
+
result.remove("id");
|
|
235
|
+
result.remove("parentExecutionId");
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
private void requireOwned(String userId, String conversationId, boolean lock) {
|
|
241
|
+
if (conversationMapper.requireOwned(conversationId, userId, lock) == null) {
|
|
242
|
+
throw new NoSuchElementException("会话不存在");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
package com.union.control.service.impl;
|
|
2
|
+
|
|
3
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
4
|
+
import com.union.control.mapper.MemoryStoreMapper;
|
|
5
|
+
import com.union.control.service.MemoryStoreService;
|
|
6
|
+
import org.springframework.stereotype.Service;
|
|
7
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
8
|
+
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.LinkedHashMap;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
import java.util.Map;
|
|
13
|
+
import java.util.regex.Pattern;
|
|
14
|
+
|
|
15
|
+
import static com.union.control.utils.AgentSupport.*;
|
|
16
|
+
|
|
17
|
+
@Service("memoryStoreService")
|
|
18
|
+
public class MemoryStoreServiceImpl implements MemoryStoreService {
|
|
19
|
+
private static final Pattern MEMORY_PATH =
|
|
20
|
+
Pattern.compile("[A-Za-z0-9._:@+-]+(?:/[A-Za-z0-9._:@+-]+)*");
|
|
21
|
+
|
|
22
|
+
private final MemoryStoreMapper mapper;
|
|
23
|
+
private final ObjectMapper json;
|
|
24
|
+
|
|
25
|
+
public MemoryStoreServiceImpl(MemoryStoreMapper mapper, ObjectMapper json) {
|
|
26
|
+
this.mapper = mapper;
|
|
27
|
+
this.json = json;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public Map<String, Object> memoryRead(String input) {
|
|
31
|
+
Map<String, Object> payload = request(json, input);
|
|
32
|
+
String userId = userId(payload);
|
|
33
|
+
String path = memoryPath(userId, payload, "path");
|
|
34
|
+
int maxChars = integer(payload, "maxChars", 1, 65536);
|
|
35
|
+
List<Map<String, Object>> rows = mapper.readMemory(userId, path);
|
|
36
|
+
Map<String, Object> response = success();
|
|
37
|
+
if (rows.isEmpty()) {
|
|
38
|
+
response.put("file", null);
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
Map<String, Object> row = rows.get(0);
|
|
42
|
+
String content = String.valueOf(row.get("content"));
|
|
43
|
+
Map<String, Object> file = new LinkedHashMap<>();
|
|
44
|
+
file.put("content", content.substring(0, Math.min(content.length(), maxChars)));
|
|
45
|
+
file.put("version", String.valueOf(row.get("version")));
|
|
46
|
+
file.put("operationId", row.get("operationId"));
|
|
47
|
+
file.put("truncated", content.length() > maxChars);
|
|
48
|
+
response.put("file", file);
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public Map<String, Object> memoryList(String input) {
|
|
53
|
+
Map<String, Object> payload = request(json, input);
|
|
54
|
+
String userId = userId(payload);
|
|
55
|
+
String prefix = memoryPrefix(userId, payload);
|
|
56
|
+
int limit = integer(payload, "limit", 1, 1000);
|
|
57
|
+
List<String> paths = mapper.listMemoryPaths(userId, likePrefix(prefix), limit);
|
|
58
|
+
Map<String, Object> response = success();
|
|
59
|
+
response.put("paths", paths);
|
|
60
|
+
return response;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public Map<String, Object> memoryOperation(String input) {
|
|
64
|
+
Map<String, Object> payload = request(json, input);
|
|
65
|
+
String userId = userId(payload);
|
|
66
|
+
String operationId = text(payload, "operationId", 128, true);
|
|
67
|
+
String fingerprint = text(payload, "fingerprint", 128, true);
|
|
68
|
+
List<Map<String, Object>> rows = mapper.findMemoryOperation(userId, operationId, false);
|
|
69
|
+
if (rows.isEmpty()) {
|
|
70
|
+
Map<String, Object> response = success();
|
|
71
|
+
response.put("mutation", null);
|
|
72
|
+
return response;
|
|
73
|
+
}
|
|
74
|
+
return replayResponse(rows.get(0), fingerprint);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Transactional
|
|
78
|
+
public Map<String, Object> memoryWrite(String input) {
|
|
79
|
+
Map<String, Object> payload = request(json, input);
|
|
80
|
+
String userId = userId(payload);
|
|
81
|
+
String path = memoryPath(userId, payload, "path");
|
|
82
|
+
String content = text(payload, "content", 65536, true);
|
|
83
|
+
String expected = optionalText(payload, "expectedVersion", 32);
|
|
84
|
+
Operation operation = operation(payload);
|
|
85
|
+
Map<String, Object> replay = replay(userId, operation);
|
|
86
|
+
if (replay != null) return replay;
|
|
87
|
+
List<Map<String, Object>> rows = memoryRow(userId, path);
|
|
88
|
+
String currentVersion = rows.isEmpty() ? null : String.valueOf(rows.get(0).get("version"));
|
|
89
|
+
if (!same(currentVersion, expected))
|
|
90
|
+
return memoryError("version_conflict", "memory version 已变化");
|
|
91
|
+
boolean existed = !rows.isEmpty();
|
|
92
|
+
Long prior = mapper.currentMemoryVersion(userId, path);
|
|
93
|
+
long version = (prior == null ? 0 : prior) + 1;
|
|
94
|
+
if (existed) {
|
|
95
|
+
mapper.updateMemoryFile(content, version, operation == null ? null : operation.id,
|
|
96
|
+
rows.get(0).get("id"));
|
|
97
|
+
} else {
|
|
98
|
+
mapper.insertMemoryFile(userId, path, content, version,
|
|
99
|
+
operation == null ? null : operation.id);
|
|
100
|
+
}
|
|
101
|
+
saveReceipt(userId, operation, version, existed);
|
|
102
|
+
Map<String, Object> response = success();
|
|
103
|
+
response.put("mutation", mutation(version, false, existed));
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Transactional
|
|
108
|
+
public Map<String, Object> memoryDelete(String input) {
|
|
109
|
+
Map<String, Object> payload = request(json, input);
|
|
110
|
+
String userId = userId(payload);
|
|
111
|
+
String path = memoryPath(userId, payload, "path");
|
|
112
|
+
String expected = optionalText(payload, "expectedVersion", 32);
|
|
113
|
+
Operation operation = operation(payload);
|
|
114
|
+
Map<String, Object> replay = replay(userId, operation);
|
|
115
|
+
if (replay != null) return replay;
|
|
116
|
+
List<Map<String, Object>> rows = memoryRow(userId, path);
|
|
117
|
+
String currentVersion = rows.isEmpty() ? null : String.valueOf(rows.get(0).get("version"));
|
|
118
|
+
if (!same(currentVersion, expected))
|
|
119
|
+
return memoryError("version_conflict", "memory version 已变化");
|
|
120
|
+
boolean existed = !rows.isEmpty();
|
|
121
|
+
if (existed) {
|
|
122
|
+
mapper.softDeleteMemoryFile(rows.get(0).get("id"));
|
|
123
|
+
}
|
|
124
|
+
saveReceipt(userId, operation, null, existed);
|
|
125
|
+
Map<String, Object> response = success();
|
|
126
|
+
response.put("mutation", mutation(null, false, existed));
|
|
127
|
+
return response;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public Map<String, Object> memorySearch(String input) {
|
|
131
|
+
Map<String, Object> payload = request(json, input);
|
|
132
|
+
String userId = userId(payload);
|
|
133
|
+
String prefix = memoryPrefix(userId, payload);
|
|
134
|
+
String query = text(payload, "query", 512, true);
|
|
135
|
+
int limit = integer(payload, "limit", 1, 100);
|
|
136
|
+
int maxFiles = integer(payload, "maxFiles", 1, 1000);
|
|
137
|
+
int maxChars = integer(payload, "maxChars", 1, 100000);
|
|
138
|
+
int maxFileChars = integer(payload, "maxFileChars", 1, 65536);
|
|
139
|
+
List<Map<String, Object>> rows = mapper.searchMemory(
|
|
140
|
+
userId, likePrefix(prefix), maxFiles + 1);
|
|
141
|
+
boolean truncated = rows.size() > maxFiles;
|
|
142
|
+
List<Map<String, Object>> matches = new ArrayList<>();
|
|
143
|
+
int remaining = maxChars;
|
|
144
|
+
int scanned = Math.min(rows.size(), maxFiles);
|
|
145
|
+
String lowerQuery = query.toLowerCase();
|
|
146
|
+
for (int index = 0; index < scanned && matches.size() < limit && remaining > 0; index++) {
|
|
147
|
+
Map<String, Object> row = rows.get(index);
|
|
148
|
+
String path = String.valueOf(row.get("path"));
|
|
149
|
+
String content = String.valueOf(row.get("content"));
|
|
150
|
+
String bounded = content.substring(0, Math.min(content.length(), maxFileChars));
|
|
151
|
+
if (!path.toLowerCase().contains(lowerQuery) && !bounded.toLowerCase().contains(lowerQuery))
|
|
152
|
+
continue;
|
|
153
|
+
String snippet = bounded.substring(0, Math.min(bounded.length(), remaining));
|
|
154
|
+
Map<String, Object> match = new LinkedHashMap<>();
|
|
155
|
+
match.put("path", path);
|
|
156
|
+
match.put("snippet", snippet);
|
|
157
|
+
match.put("score", 1.0d);
|
|
158
|
+
matches.add(match);
|
|
159
|
+
remaining -= path.length() + snippet.length();
|
|
160
|
+
if (content.length() > maxFileChars) truncated = true;
|
|
161
|
+
}
|
|
162
|
+
Map<String, Object> result = new LinkedHashMap<>();
|
|
163
|
+
result.put("matches", matches);
|
|
164
|
+
result.put("scanned", scanned);
|
|
165
|
+
result.put("truncated", truncated);
|
|
166
|
+
Map<String, Object> response = success();
|
|
167
|
+
response.put("result", result);
|
|
168
|
+
return response;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private List<Map<String, Object>> memoryRow(String userId, String path) {
|
|
172
|
+
return mapper.findMemoryRow(userId, path);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private Map<String, Object> replay(String userId, Operation operation) {
|
|
176
|
+
if (operation == null) return null;
|
|
177
|
+
List<Map<String, Object>> rows = mapper.findMemoryOperation(userId, operation.id, true);
|
|
178
|
+
if (rows.isEmpty()) return null;
|
|
179
|
+
return replayResponse(rows.get(0), operation.fingerprint);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private Map<String, Object> replayResponse(
|
|
183
|
+
Map<String, Object> row, String fingerprint) {
|
|
184
|
+
if (!fingerprint.equals(String.valueOf(row.get("fingerprint"))))
|
|
185
|
+
return memoryError("operation_conflict", "operation id 已被不同参数使用");
|
|
186
|
+
Map<String, Object> response = success();
|
|
187
|
+
response.put("mutation", mutation(row.get("resultVersion"), true,
|
|
188
|
+
number(row.get("existed")) != 0));
|
|
189
|
+
return response;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private void saveReceipt(String userId, Operation operation, Long version, boolean existed) {
|
|
193
|
+
if (operation == null) return;
|
|
194
|
+
mapper.insertMemoryOperation(userId, operation.id, operation.fingerprint, version,
|
|
195
|
+
existed ? 1 : 0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private Operation operation(Map<String, Object> payload) {
|
|
199
|
+
Object raw = payload.get("operation");
|
|
200
|
+
if (raw == null) return null;
|
|
201
|
+
if (!(raw instanceof Map)) throw new IllegalArgumentException("operation 非法");
|
|
202
|
+
@SuppressWarnings("unchecked")
|
|
203
|
+
Map<String, Object> value = (Map<String, Object>) raw;
|
|
204
|
+
return new Operation(text(value, "id", 128, true),
|
|
205
|
+
text(value, "fingerprint", 128, true));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private String memoryPath(String userId, Map<String, Object> payload, String key) {
|
|
209
|
+
String path = text(payload, key, 512, true);
|
|
210
|
+
if (!MEMORY_PATH.matcher(path).matches() || path.contains("..") ||
|
|
211
|
+
!path.startsWith(userId + "/personal/"))
|
|
212
|
+
throw new IllegalArgumentException("memory path 非法");
|
|
213
|
+
return path;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private String memoryPrefix(String userId, Map<String, Object> payload) {
|
|
217
|
+
String prefix = optionalText(payload, "prefix", 512);
|
|
218
|
+
if (prefix == null) prefix = "";
|
|
219
|
+
if (prefix.isEmpty()) prefix = userId + "/personal/";
|
|
220
|
+
if (!prefix.startsWith(userId + "/personal/") || prefix.contains(".."))
|
|
221
|
+
throw new IllegalArgumentException("memory prefix 非法");
|
|
222
|
+
return prefix;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
private static Map<String, Object> mutation(
|
|
227
|
+
Object version, boolean replayed, boolean existed) {
|
|
228
|
+
Map<String, Object> result = new LinkedHashMap<>();
|
|
229
|
+
result.put("version", version == null ? null : String.valueOf(version));
|
|
230
|
+
result.put("replayed", replayed);
|
|
231
|
+
result.put("existed", existed);
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private static Map<String, Object> memoryError(String code, String message) {
|
|
236
|
+
Map<String, Object> response = new LinkedHashMap<>();
|
|
237
|
+
response.put("success", false);
|
|
238
|
+
response.put("errorCode", code);
|
|
239
|
+
response.put("errorMsg", message);
|
|
240
|
+
return response;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private static final class Operation {
|
|
244
|
+
private final String id;
|
|
245
|
+
private final String fingerprint;
|
|
246
|
+
|
|
247
|
+
private Operation(String id, String fingerprint) {
|
|
248
|
+
this.id = id;
|
|
249
|
+
this.fingerprint = fingerprint;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|