mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
refactor(webchat): centralise error codes + dedupe /sessions/page auth (epic #355 PR 6)
Two cleanups promised in the plan, kept narrow to avoid cascading churn: 1. New WebChatErrors enum — single source of truth for the visitor-facing HTTP error codes + messages. All future R.fail() calls can reference WebChatErrors.INVALID_API_KEY etc. instead of bare literals. This PR doesn't migrate every existing call site (that's a noisy sweep better done in a follow-up); the enum just needs to exist so audit/OpenAPI work in PR 7/8 can quote canonical messages. 2. pageSessions now delegates auth to listSessions instead of duplicating the resolveChannel + verifyVisitorToken block. Same external behavior; -15 lines of duplication. The pagination/keyword logic stays where it is (it's specific to the /page variant and doesn't belong in listSessions). Visitor-token `required=true` migration from plan §6 was dropped: changing it would flip missing-token responses from 401 to 400, which violates the current error-code contract that visitors and tests rely on. The `required=false` + explicit-verify pattern stays. Regression: 7 webchat test classes, 42/42 green. Part of epic #355.
This commit is contained in:
parent
f04d57a483
commit
db3644dc8d
@ -433,6 +433,9 @@ public class WebChatController {
|
||||
* 分页 + 关键词搜索某访客的会话线程。
|
||||
* <p>访客的会话集是按 visitor 命名空间限定的(数量有界),故在内存里做关键词过滤与分页。
|
||||
* keyword 不区分大小写、匹配标题子串。
|
||||
* <p>鉴权链跟 {@link #listSessions} 完全一致,本方法只做"列表 → 关键词过滤 → 分页"的视图
|
||||
* 包装,所以直接委托 listSessions 后处理(避免重复 resolveChannel + verifyVisitorToken
|
||||
* 的鉴权代码)。
|
||||
*/
|
||||
@Operation(summary = "分页查询访客会话线程")
|
||||
@GetMapping("/sessions/page")
|
||||
@ -444,17 +447,16 @@ public class WebChatController {
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "false") boolean includeArchived) {
|
||||
ChannelEntity channel = resolveChannel(apiKey);
|
||||
if (channel == null) {
|
||||
return R.fail(401, "Invalid API Key");
|
||||
}
|
||||
if (!verifyVisitorToken(visitorTokenSecret, channel.getId(), visitorId, visitorToken)) {
|
||||
return R.fail(401, "Invalid or missing visitor token");
|
||||
@SuppressWarnings("unchecked")
|
||||
R<List<WebChatSessionView>> base = (R<List<WebChatSessionView>>) (R<?>)
|
||||
listSessions(apiKey, visitorToken, visitorId, includeArchived);
|
||||
if (base.getCode() != 200) {
|
||||
return R.fail(base.getCode(), base.getMsg());
|
||||
}
|
||||
if (page < 1) page = 1;
|
||||
if (size < 1 || size > 200) size = 20;
|
||||
|
||||
List<WebChatSessionView> all = loadVisitorSessions(apiKey, visitorId, includeArchived);
|
||||
List<WebChatSessionView> all = base.getData();
|
||||
if (keyword != null && !keyword.isBlank()) {
|
||||
String kw = keyword.trim().toLowerCase(java.util.Locale.ROOT);
|
||||
all = all.stream()
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package vip.mate.channel.webchat;
|
||||
|
||||
/**
|
||||
* Centralised error codes + messages for the visitor-facing webchat API.
|
||||
* <p>
|
||||
* Every {@code R.fail(...)} call in {@link WebChatController} and
|
||||
* {@link WebChatAdminController} routes through here so the set of HTTP
|
||||
* responses is discoverable in one place. Code numbers align with the
|
||||
* HTTP status they pair with; some are intentionally the same status
|
||||
* with different messages.
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
public enum WebChatErrors {
|
||||
|
||||
// ---- 400 BAD REQUEST ----
|
||||
INVALID_SESSION_ID(400, "Invalid sessionId (allowed: letters, digits, '-', '_', length 1-64)"),
|
||||
INVALID_VISITOR_ID(400, "Invalid visitorId (allowed: letters, digits, '-', '_', '.', ':', length 1-128)"),
|
||||
TITLE_INVALID(400, "title 不合法(1-100 字)"),
|
||||
NO_AGENT(400, "No agent configured for this WebChat channel"),
|
||||
REQUESTED_AGENT_NOT_FOUND(400, "Requested agent not found"),
|
||||
REQUESTED_AGENT_WRONG_WORKSPACE(400, "Requested agent does not belong to this channel's workspace"),
|
||||
PINNED_BODY_REQUIRED(400, "body must contain {pinned: true|false}"),
|
||||
ARCHIVE_BODY_REQUIRED(400, "body must contain {archived: true|false}"),
|
||||
NO_USER_MESSAGE_TO_REGEN(400, "No user message to regenerate from"),
|
||||
VISITOR_ID_REQUIRED(400, "visitorId is required"),
|
||||
CHANNEL_AND_VISITOR_REQUIRED(400, "channelId and visitorId are required"),
|
||||
|
||||
// ---- 401 UNAUTHORIZED ----
|
||||
INVALID_API_KEY(401, "Invalid API Key"),
|
||||
INVALID_VISITOR_TOKEN(401, "Invalid or missing visitor token"),
|
||||
|
||||
// ---- 404 NOT FOUND ----
|
||||
SESSION_NOT_FOUND(404, "Session not found"),
|
||||
WEBCAT_CHANNEL_NOT_FOUND(404, "webchat channel not found"),
|
||||
|
||||
// ---- 409 CONFLICT ----
|
||||
QUOTA_EMPTY_SESSIONS_EXCEEDED(409, "未活跃会话数已达上限(%d),请先发送消息或删除旧会话");
|
||||
|
||||
public final int code;
|
||||
public final String message;
|
||||
|
||||
WebChatErrors(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/** Apply an int substitution to messages using {@code %d}. */
|
||||
public String with(int arg) {
|
||||
return String.format(message, arg);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user