新版本
This commit is contained in:
parent
52d5bdacf3
commit
e89fbd3141
@ -25,13 +25,13 @@
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"query": "查看本月EC电话合规监控日报",
|
"query": "查看本月EC电话合规监控日报"
|
||||||
"userId": "YiNianYiQingJing"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `query` 同时兼容 `content`、`userText`、`message` 和 `input`。
|
- `query` 同时兼容 `content`、`userText`、`message` 和 `input`。
|
||||||
- `userId` 同时兼容 `wecomUserId`、`userid` 和 `user_id`。
|
- UserID 由后端从企业微信调用上下文请求头自动读取,不需要作为插件业务参数填写。
|
||||||
|
- 本地联调仍兼容请求体中的 `userId`、`wecomUserId`、`userid` 和 `user_id`。
|
||||||
- 处理逻辑保持不变:DeepSeek 语义识别 → 日期解析 → UserID 数据权限 → 对应日报处理器。
|
- 处理逻辑保持不变:DeepSeek 语义识别 → 日期解析 → UserID 数据权限 → 对应日报处理器。
|
||||||
- 同一个 UserID 同时只能执行一条运动馆指令。
|
- 同一个 UserID 同时只能执行一条运动馆指令。
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.company.agentfoundation.integration.coze.service.CozeWorkflowService;
|
|||||||
import com.company.agentfoundation.semantic.SemanticDispatchResult;
|
import com.company.agentfoundation.semantic.SemanticDispatchResult;
|
||||||
import com.company.agentfoundation.semantic.SemanticDispatchService;
|
import com.company.agentfoundation.semantic.SemanticDispatchService;
|
||||||
import com.company.agentfoundation.semantic.SemanticReplyAttachment;
|
import com.company.agentfoundation.semantic.SemanticReplyAttachment;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ContentDisposition;
|
import org.springframework.http.ContentDisposition;
|
||||||
@ -57,11 +58,17 @@ public class AssistantApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/sports-venue/query")
|
@PostMapping("/sports-venue/query")
|
||||||
public AssistantQueryResponse sportsVenueQuery(@RequestBody AssistantQueryRequest request) {
|
public AssistantQueryResponse sportsVenueQuery(
|
||||||
|
@RequestBody AssistantQueryRequest request,
|
||||||
|
HttpServletRequest httpRequest
|
||||||
|
) {
|
||||||
String query = request == null ? "" : request.normalizedQuery();
|
String query = request == null ? "" : request.normalizedQuery();
|
||||||
String userId = request == null ? "" : request.normalizedUserId();
|
String userId = resolveUserId(request, httpRequest);
|
||||||
if (query.isEmpty()) return AssistantQueryResponse.failure("query不能为空");
|
if (query.isEmpty()) return AssistantQueryResponse.failure("query不能为空");
|
||||||
if (userId.isEmpty()) return AssistantQueryResponse.failure("userId不能为空,数据权限需要企业微信UserID");
|
if (userId.isEmpty()) {
|
||||||
|
return AssistantQueryResponse.failure(
|
||||||
|
"未能从企业微信调用上下文获取UserID,暂时无法判断数据权限。");
|
||||||
|
}
|
||||||
|
|
||||||
String runningKey = userId.toLowerCase(Locale.ROOT);
|
String runningKey = userId.toLowerCase(Locale.ROOT);
|
||||||
if (!runningUsers.add(runningKey)) {
|
if (!runningUsers.add(runningKey)) {
|
||||||
@ -121,4 +128,28 @@ public class AssistantApiController {
|
|||||||
}
|
}
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveUserId(
|
||||||
|
AssistantQueryRequest request,
|
||||||
|
HttpServletRequest httpRequest
|
||||||
|
) {
|
||||||
|
String[] headerNames = {
|
||||||
|
"X-WeCom-UserId",
|
||||||
|
"X-WeWork-UserId",
|
||||||
|
"X-WXWork-UserId",
|
||||||
|
"X-WX-UserId",
|
||||||
|
"X-User-Id",
|
||||||
|
"UserId"
|
||||||
|
};
|
||||||
|
for (String headerName : headerNames) {
|
||||||
|
String value = httpRequest.getHeader(headerName);
|
||||||
|
if (value != null && !value.isBlank()) return value.trim();
|
||||||
|
}
|
||||||
|
if (httpRequest.getUserPrincipal() != null
|
||||||
|
&& httpRequest.getUserPrincipal().getName() != null
|
||||||
|
&& !httpRequest.getUserPrincipal().getName().isBlank()) {
|
||||||
|
return httpRequest.getUserPrincipal().getName().trim();
|
||||||
|
}
|
||||||
|
return request == null ? "" : request.normalizedUserId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package com.company.agentfoundation.assistant.api;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -18,8 +17,7 @@ public class AssistantOpenApiController {
|
|||||||
"问答小助手API",
|
"问答小助手API",
|
||||||
"/api/assistant/general/query",
|
"/api/assistant/general/query",
|
||||||
"generalAssistantQuery",
|
"generalAssistantQuery",
|
||||||
"将用户输入发送给扣子问答工作流并返回回答",
|
"将用户输入发送给扣子问答工作流并返回回答");
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping({"/sports-venue", "/sports-venue.json"})
|
@GetMapping({"/sports-venue", "/sports-venue.json"})
|
||||||
@ -28,32 +26,26 @@ public class AssistantOpenApiController {
|
|||||||
"运动馆智能助手API",
|
"运动馆智能助手API",
|
||||||
"/api/assistant/sports-venue/query",
|
"/api/assistant/sports-venue/query",
|
||||||
"sportsVenueAssistantQuery",
|
"sportsVenueAssistantQuery",
|
||||||
"根据用户输入进行语义识别,并按企业微信UserID权限查询客资、学员、企业微信客户运营或EC日报",
|
"根据用户输入进行语义识别,并按企业微信调用上下文中的UserID权限查询客资、学员、企业微信客户运营或EC日报");
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> specification(
|
private Map<String, Object> specification(
|
||||||
String title,
|
String title,
|
||||||
String path,
|
String path,
|
||||||
String operationId,
|
String operationId,
|
||||||
String description,
|
String description
|
||||||
boolean requireUserId
|
|
||||||
) {
|
) {
|
||||||
Map<String, Object> properties = new LinkedHashMap<>();
|
Map<String, Object> properties = new LinkedHashMap<>();
|
||||||
properties.put("query", Map.of(
|
properties.put("query", Map.of(
|
||||||
"type", "string",
|
"type", "string",
|
||||||
"description", "用户在企业微信中输入的完整问题或指令",
|
"description", "用户在企业微信中输入的完整问题或指令",
|
||||||
"example", requireUserId ? "查看今日客资管理日报" : "测试表"));
|
"example", operationId.equals("sportsVenueAssistantQuery")
|
||||||
if (requireUserId) {
|
? "查看今日客资管理日报"
|
||||||
properties.put("userId", Map.of(
|
: "测试表"));
|
||||||
"type", "string",
|
|
||||||
"description", "当前提问人的企业微信UserID,用于数据权限判断",
|
|
||||||
"example", "YiNianYiQingJing"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> requestSchema = new LinkedHashMap<>();
|
Map<String, Object> requestSchema = new LinkedHashMap<>();
|
||||||
requestSchema.put("type", "object");
|
requestSchema.put("type", "object");
|
||||||
requestSchema.put("required", requireUserId ? List.of("query", "userId") : List.of("query"));
|
requestSchema.put("required", List.of("query"));
|
||||||
requestSchema.put("properties", properties);
|
requestSchema.put("properties", properties);
|
||||||
|
|
||||||
Map<String, Object> attachmentProperties = Map.of(
|
Map<String, Object> attachmentProperties = Map.of(
|
||||||
@ -86,11 +78,9 @@ public class AssistantOpenApiController {
|
|||||||
"description", "处理结果",
|
"description", "处理结果",
|
||||||
"content", Map.of("application/json", Map.of("schema", responseSchema)))));
|
"content", Map.of("application/json", Map.of("schema", responseSchema)))));
|
||||||
|
|
||||||
String server = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
|
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"openapi", "3.0.1",
|
"openapi", "3.0.1",
|
||||||
"info", Map.of("title", title, "version", "1.0.0", "description", description),
|
"info", Map.of("title", title, "version", "1.0.0", "description", description),
|
||||||
"servers", List.of(Map.of("url", server)),
|
|
||||||
"paths", Map.of(path, Map.of("post", operation)));
|
"paths", Map.of(path, Map.of("post", operation)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,27 @@ class AssistantApiControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sportsVenueApiRequiresUserIdForPermissionScope() throws Exception {
|
void sportsVenueApiReadsUserIdFromWecomContextHeader() throws Exception {
|
||||||
|
SemanticDispatchService semantic = mock(SemanticDispatchService.class);
|
||||||
|
when(semantic.dispatchWithAttachments("YiNianYiQingJing", "查看今日客资日报"))
|
||||||
|
.thenReturn(new SemanticDispatchResult("客资日报", List.of()));
|
||||||
|
MockMvc mvc = mvc(
|
||||||
|
mock(CozeWorkflowService.class),
|
||||||
|
semantic,
|
||||||
|
new AssistantFileStore());
|
||||||
|
|
||||||
|
mvc.perform(post("/api/assistant/sports-venue/query")
|
||||||
|
.header("X-WeCom-UserId", "YiNianYiQingJing")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"query\":\"查看今日客资日报\"}"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.success").value(true));
|
||||||
|
|
||||||
|
verify(semantic).dispatchWithAttachments("YiNianYiQingJing", "查看今日客资日报");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sportsVenueApiRejectsRequestWhenWecomContextHasNoUserId() throws Exception {
|
||||||
MockMvc mvc = mvc(
|
MockMvc mvc = mvc(
|
||||||
mock(CozeWorkflowService.class),
|
mock(CozeWorkflowService.class),
|
||||||
mock(SemanticDispatchService.class),
|
mock(SemanticDispatchService.class),
|
||||||
@ -84,7 +104,7 @@ class AssistantApiControllerTest {
|
|||||||
.content("{\"query\":\"查看今日客资日报\"}"))
|
.content("{\"query\":\"查看今日客资日报\"}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.success").value(false))
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
.andExpect(jsonPath("$.content").value(org.hamcrest.Matchers.containsString("userId")));
|
.andExpect(jsonPath("$.content").value(org.hamcrest.Matchers.containsString("UserID")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockMvc mvc(
|
private MockMvc mvc(
|
||||||
|
|||||||
@ -31,7 +31,11 @@ class AssistantOpenApiControllerTest {
|
|||||||
.andExpect(jsonPath("$.paths['/api/assistant/sports-venue/query'].post.operationId")
|
.andExpect(jsonPath("$.paths['/api/assistant/sports-venue/query'].post.operationId")
|
||||||
.value("sportsVenueAssistantQuery"))
|
.value("sportsVenueAssistantQuery"))
|
||||||
.andExpect(jsonPath(
|
.andExpect(jsonPath(
|
||||||
"$.paths['/api/assistant/sports-venue/query'].post.requestBody.content['application/json'].schema.required[1]")
|
"$.paths['/api/assistant/sports-venue/query'].post.requestBody.content['application/json'].schema.required.length()")
|
||||||
.value("userId"));
|
.value(1))
|
||||||
|
.andExpect(jsonPath(
|
||||||
|
"$.paths['/api/assistant/sports-venue/query'].post.requestBody.content['application/json'].schema.properties.userId")
|
||||||
|
.doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.servers").doesNotExist());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user