diff --git a/docs/03-authentication-design.md b/docs/03-authentication-design.md index a8951b9e..a07bc69c 100644 --- a/docs/03-authentication-design.md +++ b/docs/03-authentication-design.md @@ -233,7 +233,7 @@ public class SecurityConfig { .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .ignoringRequestMatchers("/api/v1/cli/**")) + .ignoringRequestMatchers("/api/v1/**")) // ... ; } @@ -458,7 +458,7 @@ Session 中存储以下字段: - 后端设置 `XSRF-TOKEN` Cookie(`HttpOnly=false`) - 前端从 Cookie 读取 Token,放入请求 Header `X-XSRF-TOKEN` - 后端校验 Header 与 Cookie 是否一致 -- CLI API(`/api/v1/cli/**`)与兼容层(`/api/compat/v1/**`)豁免 CSRF(使用 Bearer Token,无 Cookie) +- CLI API(`/api/v1/**`)与兼容层(`/api/compat/v1/**`)豁免 CSRF(使用 Bearer Token,无 Cookie) ## 9. 前端权限控制 @@ -609,8 +609,8 @@ window.location.href = '/oauth2/authorization/github' | 接口 | 所需凭证 | 额外判定 | |------|---------|---------| -| `GET /api/v1/cli/whoami` | 任意有效 Bearer Token | 无 | -| `POST /api/v1/cli/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上 | +| `GET /api/v1/whoami` | 任意有效 Bearer Token | 无 | +| `POST /api/v1/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上 | ### 10.4 Admin API diff --git a/docs/05-business-flows.md b/docs/05-business-flows.md index 1c5f8425..e01549c4 100644 --- a/docs/05-business-flows.md +++ b/docs/05-business-flows.md @@ -54,7 +54,7 @@ Phase 3 在不改变发布入口的前提下,把后半段切换为“创建 DR - 审核通过后才转为 `PUBLISHED` - 审核拒绝后转为 `REJECTED` - 例外:提交人持有 `SUPER_ADMIN` 平台角色时,发布入口直接创建 `skill_version(status=PUBLISHED)`,跳过 `review_task` 创建,同时不再要求其必须是目标 namespace 成员 -- 上述例外必须对 Web、`/api/v1/cli/publish`、`/api/compat/v1/publish` 保持一致 +- 上述例外必须对 Web、`/api/v1/publish`、`/api/compat/v1/publish` 保持一致 ### 对象存储写入策略 @@ -67,7 +67,7 @@ Phase 3 在不改变发布入口的前提下,把后半段切换为“创建 DR ### CLI publish 请求规范 ``` -POST /api/v1/cli/publish +POST /api/v1/publish Content-Type: multipart/form-data Parts: - file: zip 包(必需) @@ -81,7 +81,7 @@ Phase 3 CLI 默认行为:上传 → 创建 DRAFT → 自动提交审核。 如果调用方持有 `SUPER_ADMIN`,则直接发布为 `PUBLISHED`。 Web 端可保留“发布后再提交审核”的两段式体验,但这属于 Phase 3 能力。 -`/api/v1/cli/publish` 响应: +`/api/v1/publish` 响应: ```json { diff --git a/docs/06-api-design.md b/docs/06-api-design.md index 6e1da75c..6b6b5efc 100644 --- a/docs/06-api-design.md +++ b/docs/06-api-design.md @@ -239,7 +239,7 @@ Public API 的可见性规则: 发布状态约束: - 普通用户发布成功后,`status` 为 `PENDING_REVIEW` -- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/cli/publish`、`/api/compat/v1/publish` 发布时,`status` 为 `PUBLISHED`,且不要求其必须是目标 namespace 成员 +- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish`、`/api/compat/v1/publish` 发布时,`status` 为 `PUBLISHED`,且不要求其必须是目标 namespace 成员 ## 7.4 Token API(需登录) @@ -253,10 +253,10 @@ Public API 的可见性规则: | 方法 | 路径 | 说明 | |------|------|------| -| GET | `/api/v1/cli/whoami` | 当前 Bearer Token 对应的用户信息 | -| POST | `/api/v1/cli/publish` | 发布技能包(Phase 2 直接返回 `PUBLISHED`,Phase 3 恢复审核流;`SUPER_ADMIN` 始终直发) | -| GET | `/api/v1/cli/resolve/{namespace}/{slug}` | 解析版本 | -| GET | `/api/v1/cli/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 | +| GET | `/api/v1/whoami` | 当前 Bearer Token 对应的用户信息 | +| POST | `/api/v1/publish` | 发布技能包(Phase 2 直接返回 `PUBLISHED`,Phase 3 恢复审核流;`SUPER_ADMIN` 始终直发) | +| GET | `/api/v1/resolve/{namespace}/{slug}` | 解析版本 | +| GET | `/api/v1/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 | ### ClawHub CLI 协议兼容层 @@ -266,7 +266,7 @@ Public API 的可见性规则: - 范围:一期聚焦覆盖 ClawHub CLI 所依赖的核心接口:查询、版本解析、下载、发布、whoami - 要求:兼容层优先保持 ClawHub CLI 既有请求/响应语义;若内部领域模型不同,通过 adapter 层完成协议转换,而不是要求客户端适配 skillhub 私有协议 - 要求:兼容层纳入 OpenAPI 或独立兼容协议文档,并作为正式对外契约维护 -- 要求:兼容层与 skillhub 自有 `/api/v1/cli/**` 并存,二者共享同一套权限、审计、限流与领域服务 +- 要求:兼容层与 skillhub 自有 `/api/v1/**` 并存,二者共享同一套权限、审计、限流与领域服务 - 非目标:前端页面不直接依赖兼容层;兼容层用于服务已有 ClawHub CLI 和相关自动化脚本 兼容层最少需要覆盖的能力类别: diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/CliController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/CliController.java index 13d8071a..e24948c2 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/CliController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/CliController.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; @RestController -@RequestMapping("/api/v1/cli") +@RequestMapping("/api/v1") public class CliController extends BaseApiController { private final SkillPackageValidator skillPackageValidator; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/DeviceAuthController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/DeviceAuthController.java index 2b61b7c9..02f7d311 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/DeviceAuthController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/DeviceAuthController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/v1/cli/auth/device") +@RequestMapping("/api/v1/auth/device") public class DeviceAuthController extends BaseApiController { private final DeviceAuthService deviceAuthService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/cli/CliPublishController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/cli/CliPublishController.java index 73cbd1fa..cf915326 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/cli/CliPublishController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/cli/CliPublishController.java @@ -23,7 +23,7 @@ import java.io.IOException; import java.util.List; @RestController -@RequestMapping("/api/v1/cli") +@RequestMapping("/api/v1") public class CliPublishController extends BaseApiController { private final SkillPublishService skillPublishService; diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/CliControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/CliControllerTest.java index 58d721ba..62ebe34d 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/CliControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/CliControllerTest.java @@ -43,7 +43,7 @@ class CliControllerTest { @Test void whoamiShouldReturnUnauthorizedForAnonymousRequest() throws Exception { - mockMvc.perform(get("/api/v1/cli/whoami")) + mockMvc.perform(get("/api/v1/whoami")) .andExpect(status().isUnauthorized()); } @@ -66,7 +66,7 @@ class CliControllerTest { List.of(new SimpleGrantedAuthority("ROLE_SKILL_ADMIN")) ); - mockMvc.perform(get("/api/v1/cli/whoami").with(authentication(auth))) + mockMvc.perform(get("/api/v1/whoami").with(authentication(auth))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.msg").isNotEmpty()) @@ -88,7 +88,7 @@ class CliControllerTest { zipBytes ); - mockMvc.perform(multipart("/api/v1/cli/check").file(file)) + mockMvc.perform(multipart("/api/v1/check").file(file)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.valid").value(true)) @@ -107,7 +107,7 @@ class CliControllerTest { zipBytes ); - mockMvc.perform(multipart("/api/v1/cli/check").file(file)) + mockMvc.perform(multipart("/api/v1/check").file(file)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.valid").value(false)) @@ -125,7 +125,7 @@ class CliControllerTest { zipBytes ); - mockMvc.perform(multipart("/api/v1/cli/check").file(file)) + mockMvc.perform(multipart("/api/v1/check").file(file)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.valid").value(false)) @@ -142,7 +142,7 @@ class CliControllerTest { zipBytes ); - mockMvc.perform(multipart("/api/v1/cli/check").file(file)) + mockMvc.perform(multipart("/api/v1/check").file(file)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.valid").value(false)) diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/DeviceAuthControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/DeviceAuthControllerTest.java index f1ede80c..6523a754 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/DeviceAuthControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/DeviceAuthControllerTest.java @@ -44,7 +44,7 @@ class DeviceAuthControllerTest { given(deviceAuthService.generateDeviceCode()).willReturn(response); - mockMvc.perform(post("/api/v1/cli/auth/device/code") + mockMvc.perform(post("/api/v1/auth/device/code") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) @@ -61,7 +61,7 @@ class DeviceAuthControllerTest { given(deviceAuthService.pollToken("device_abc123")).willReturn(response); - mockMvc.perform(post("/api/v1/cli/auth/device/token") + mockMvc.perform(post("/api/v1/auth/device/token") .contentType(MediaType.APPLICATION_JSON) .content("{\"deviceCode\": \"device_abc123\"}")) .andExpect(status().isOk()) @@ -77,7 +77,7 @@ class DeviceAuthControllerTest { given(deviceAuthService.pollToken("device_abc123")).willReturn(response); - mockMvc.perform(post("/api/v1/cli/auth/device/token") + mockMvc.perform(post("/api/v1/auth/device/token") .contentType(MediaType.APPLICATION_JSON) .content("{\"deviceCode\": \"device_abc123\"}")) .andExpect(status().isOk()) diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java index 889cd1cb..c9afab70 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java @@ -70,7 +70,7 @@ public class SecurityConfig { .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(csrfHandler) - .ignoringRequestMatchers("/api/v1/cli/**", "/api/compat/**") + .ignoringRequestMatchers("/api/v1/**", "/api/compat/**") ) .authorizeHttpRequests(auth -> auth .requestMatchers( @@ -81,8 +81,8 @@ public class SecurityConfig { "/api/v1/auth/session/bootstrap", "/api/v1/auth/direct/login", "/api/v1/auth/local/**", - "/api/v1/cli/auth/device/**", - "/api/v1/cli/check", + "/api/v1/auth/device/**", + "/api/v1/check", "/actuator/health", "/actuator/prometheus", "/v3/api-docs/**", diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java index 09445f69..d18571df 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java @@ -19,9 +19,9 @@ public class ApiTokenScopeService { ScopeRule.allow(null, "/api/v1/health"), ScopeRule.allow(null, "/api/v1/auth/providers"), ScopeRule.allow(null, "/api/v1/auth/me"), - ScopeRule.allow(null, "/api/v1/cli/auth/device/**"), - ScopeRule.allow(null, "/api/v1/cli/check"), - ScopeRule.allow("GET", "/api/v1/cli/whoami"), + ScopeRule.allow(null, "/api/v1/auth/device/**"), + ScopeRule.allow(null, "/api/v1/check"), + ScopeRule.allow("GET", "/api/v1/whoami"), ScopeRule.allow("GET", "/api/v1/skills"), ScopeRule.allow("GET", "/api/v1/skills/**"), ScopeRule.allow("GET", "/api/v1/namespaces"), @@ -39,7 +39,7 @@ public class ApiTokenScopeService { ScopeRule.require(null, "/api/v1/tokens", "token:manage"), ScopeRule.require(null, "/api/v1/tokens/**", "token:manage"), ScopeRule.require("POST", "/api/v1/skills/*/publish", "skill:publish"), - ScopeRule.require("POST", "/api/v1/cli/publish", "skill:publish"), + ScopeRule.require("POST", "/api/v1/publish", "skill:publish"), ScopeRule.require("POST", "/api/compat/v1/publish", "skill:publish") ); diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilterTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilterTest.java index a981ffd1..0c76132e 100644 --- a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilterTest.java +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenAuthenticationFilterTest.java @@ -58,7 +58,7 @@ class ApiTokenAuthenticationFilterTest { when(role.getCode()).thenReturn("SKILL_ADMIN"); MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI("/api/v1/cli/whoami"); + request.setRequestURI("/api/v1/whoami"); request.addHeader("Authorization", "Bearer raw-token"); filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); @@ -84,7 +84,7 @@ class ApiTokenAuthenticationFilterTest { when(userAccountRepository.findById("user-2")).thenReturn(Optional.of(user)); MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI("/api/v1/cli/publish"); + request.setRequestURI("/api/v1/publish"); request.addHeader("Authorization", "Bearer raw-token"); filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain()); diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeFilterTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeFilterTest.java index fd50db75..10e33853 100644 --- a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeFilterTest.java +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeFilterTest.java @@ -59,7 +59,7 @@ class ApiTokenScopeFilterTest { ); SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/cli/publish"); + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/publish"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain chain = mock(FilterChain.class); diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeServiceTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeServiceTest.java index 70c73b2d..002ec8a7 100644 --- a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeServiceTest.java +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/token/ApiTokenScopeServiceTest.java @@ -24,7 +24,7 @@ class ApiTokenScopeServiceTest { void authorizeShouldAllowCliWhoamiWithoutScope() { ApiTokenScopeService.AuthorizationDecision decision = scopeService.authorize( "GET", - "/api/v1/cli/whoami", + "/api/v1/whoami", Set.of() ); diff --git a/web/src/api/generated/schema.d.ts b/web/src/api/generated/schema.d.ts index 8107fca8..b1917563 100644 --- a/web/src/api/generated/schema.d.ts +++ b/web/src/api/generated/schema.d.ts @@ -308,7 +308,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/publish": { + "/api/v1/publish": { parameters: { query?: never; header?: never; @@ -324,7 +324,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/check": { + "/api/v1/check": { parameters: { query?: never; header?: never; @@ -340,7 +340,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/auth/device/token": { + "/api/v1/auth/device/token": { parameters: { query?: never; header?: never; @@ -356,7 +356,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/auth/device/code": { + "/api/v1/auth/device/code": { parameters: { query?: never; header?: never; @@ -916,7 +916,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/whoami": { + "/api/v1/whoami": { parameters: { query?: never; header?: never; @@ -932,7 +932,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/v1/cli/resolve/{namespace}/{slug}": { + "/api/v1/resolve/{namespace}/{slug}": { parameters: { query?: never; header?: never;