Files
wenzi/docs/plans/2026-01-26-mosquito-system-implementation-plan.md
Your Name 91a0b77f7a test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl
- 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE
- 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查
- 所有1266个测试用例通过
- 覆盖率: 指令81.89%, 行88.48%, 分支51.55%

docs: 添加项目状态报告
- 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态
- 包含质量指标、已完成功能、待办事项和技术债务
2026-03-02 13:31:54 +08:00

342 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mosquito System Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 统一鉴权与响应契约,补齐三端前端工程骨架,并让前后端可在同一契约下联调。
**Architecture:** 在后端引入 introspection 校验与缓存,统一 API 响应为 `ApiResponse`,并将鉴权策略按路由分层。前端三端共享组件库与 Design Tokens使用一致的 API Client 与错误处理。
**Tech Stack:** Spring Boot 3, Java 17, Redis, Vite, Vue 3, TypeScript, Pinia, Vue Router, Tailwind CSS
---
> 注意:根据项目指令,本计划不包含 git commit 步骤。
### Task 1: 定义并落地 introspection 协议与缓存结构
**Files:**
- Create: `src/main/java/com/mosquito/project/security/IntrospectionRequest.java`
- Create: `src/main/java/com/mosquito/project/security/IntrospectionResponse.java`
- Create: `src/main/java/com/mosquito/project/security/UserIntrospectionService.java`
- Modify: `src/main/java/com/mosquito/project/config/AppConfig.java`
- Modify: `src/main/resources/application.properties`
**Step 1: Write the failing test**
```java
// src/test/java/com/mosquito/project/security/UserIntrospectionServiceTest.java
@Test
void shouldReturnInactive_whenTokenInvalid() {
UserIntrospectionService service = buildServiceWithMockResponse(false);
var result = service.introspect("bad-token");
assertFalse(result.isActive());
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=UserIntrospectionServiceTest test`
Expected: FAIL (class not found)
**Step 3: Write minimal implementation**
```java
public class IntrospectionResponse {
private boolean active;
private String userId;
private String tenantId;
private java.util.List<String> roles;
private java.util.List<String> scopes;
private long exp;
private long iat;
private String jti;
// getters/setters
}
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=UserIntrospectionServiceTest test`
Expected: PASS
### Task 2: 实现 API Key + 用户态双重鉴权拦截器
**Files:**
- Create: `src/main/java/com/mosquito/project/web/UserAuthInterceptor.java`
- Modify: `src/main/java/com/mosquito/project/config/WebMvcConfig.java`
- Modify: `src/main/java/com/mosquito/project/web/ApiKeyAuthInterceptor.java`
**Step 1: Write the failing test**
```java
// src/test/java/com/mosquito/project/web/UserAuthInterceptorTest.java
@Test
void shouldRejectRequest_whenMissingAuthorization() {
var request = mockRequestWithoutAuth();
var response = new MockHttpServletResponse();
var result = interceptor.preHandle(request, response, new Object());
assertFalse(result);
assertEquals(401, response.getStatus());
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=UserAuthInterceptorTest test`
Expected: FAIL (class not found)
**Step 3: Write minimal implementation**
```java
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// call UserIntrospectionService
return true;
}
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=UserAuthInterceptorTest test`
Expected: PASS
### Task 3: 路由分层鉴权策略
**Files:**
- Modify: `src/main/java/com/mosquito/project/config/WebMvcConfig.java`
**Step 1: Write the failing test**
```java
// src/test/java/com/mosquito/project/config/WebMvcConfigTest.java
@Test
void shouldProtectMeEndpoints_withApiKeyAndUserAuth() {
// verify interceptors order and path patterns
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=WebMvcConfigTest test`
Expected: FAIL
**Step 3: Write minimal implementation**
```java
registry.addInterceptor(apiKeyAuthInterceptor).addPathPatterns("/api/**");
registry.addInterceptor(userAuthInterceptor).addPathPatterns("/api/v1/me/**", "/api/v1/activities/**", "/api/v1/api-keys/**", "/api/v1/share/**");
registry.addInterceptor(apiKeyAuthInterceptor).excludePathPatterns("/r/**", "/actuator/**");
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=WebMvcConfigTest test`
Expected: PASS
### Task 4: 统一 API 响应为 ApiResponse
**Files:**
- Modify: `src/main/java/com/mosquito/project/controller/ActivityController.java`
- Modify: `src/main/java/com/mosquito/project/controller/ApiKeyController.java`
- Modify: `src/main/java/com/mosquito/project/controller/UserExperienceController.java`
- Modify: `src/main/java/com/mosquito/project/controller/ShareTrackingController.java`
- Modify: `src/main/java/com/mosquito/project/exception/GlobalExceptionHandler.java`
**Step 1: Write the failing test**
```java
// src/test/java/com/mosquito/project/controller/ActivityControllerContractTest.java
@Test
void shouldReturnApiResponseEnvelope() throws Exception {
mockMvc.perform(get("/api/v1/activities/1"))
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").exists());
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=ActivityControllerContractTest test`
Expected: FAIL
**Step 3: Write minimal implementation**
```java
return ResponseEntity.ok(ApiResponse.success(activity));
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=ActivityControllerContractTest test`
Expected: PASS
### Task 5: 排行榜分页与元数据
**Files:**
- Modify: `src/main/java/com/mosquito/project/controller/ActivityController.java`
- Modify: `src/main/java/com/mosquito/project/service/ActivityService.java`
- Modify: `src/main/java/com/mosquito/project/persistence/repository/ActivityRepository.java`
- Modify: `src/test/java/com/mosquito/project/controller/ActivityStatsAndGraphControllerTest.java`
**Step 1: Write the failing test**
```java
// add pagination meta assertion
.andExpect(jsonPath("$.meta.pagination.total").value(3))
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=ActivityStatsAndGraphControllerTest test`
Expected: FAIL
**Step 3: Write minimal implementation**
```java
var data = list.subList(from, to);
return ResponseEntity.ok(ApiResponse.paginated(data, page, size, list.size()));
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=ActivityStatsAndGraphControllerTest test`
Expected: PASS
### Task 6: 更新 Java SDK 与前端 API Client
**Files:**
- Modify: `src/main/java/com/mosquito/project/sdk/ApiClient.java`
- Modify: `src/main/java/com/mosquito/project/sdk/MosquitoClient.java`
- Modify: `frontend/index.ts`
- Modify: `frontend/components/MosquitoLeaderboard.vue`
**Step 1: Write the failing test**
```java
// src/test/java/com/mosquito/project/sdk/ApiClientTest.java
@Test
void shouldUnwrapApiResponse() {
// response: { code: 200, data: {...} }
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=ApiClientTest test`
Expected: FAIL
**Step 3: Write minimal implementation**
```java
// ApiClient: parse ApiResponse<T>, return data field
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=ApiClientTest test`
Expected: PASS
### Task 7: H5 与管理端基础页面接通组件库
**Files:**
- Create: `frontend/h5/src/views/ShareView.vue`
- Create: `frontend/admin/src/views/ActivityListView.vue`
- Modify: `frontend/h5/src/router/index.ts`
- Modify: `frontend/admin/src/router/index.ts`
**Step 1: Write the failing test**
```js
// frontend/h5/src/tests/appRoutes.test.ts
it('should render share page', () => {
// mount router and assert route
})
```
**Step 2: Run test to verify it fails**
Run: `npm --prefix "frontend/h5" run type-check`
Expected: FAIL
**Step 3: Write minimal implementation**
```vue
<MosquitoShareButton :activity-id="1" :user-id="1" />
```
**Step 4: Run test to verify it passes**
Run: `npm --prefix "frontend/h5" run type-check`
Expected: PASS
### Task 8: 更新 API 文档与对外契约
**Files:**
- Modify: `docs/api.md`
- Modify: `README.md`
**Step 1: Write the failing test**
```text
# 手动校对:文档端点与控制器一致
```
**Step 2: Run verification**
Run: `rg "api/v1/me" "docs/api.md"`
Expected: path consistent with controllers
**Step 3: Apply updates**
```text
- 错误响应改为 ApiResponse
- /api/v1/me/poster -> /api/v1/me/poster/image|html|config
```
### Task 9: 安全与配置校验
**Files:**
- Modify: `src/main/java/com/mosquito/project/service/ApiKeyEncryptionService.java`
- Modify: `src/main/resources/application-prod.yml`
- Modify: `src/main/java/com/mosquito/project/config/CacheConfig.java`
**Step 1: Write the failing test**
```java
@Test
void shouldFailStartup_whenEncryptionKeyDefault() {
// assert illegal state
}
```
**Step 2: Run test to verify it fails**
Run: `mvn -Dtest=ApiKeyEncryptionServiceTest test`
Expected: FAIL
**Step 3: Write minimal implementation**
```java
if (isDefaultKey(encryptionKey)) {
throw new IllegalStateException("Encryption key must be set in production");
}
```
**Step 4: Run test to verify it passes**
Run: `mvn -Dtest=ApiKeyEncryptionServiceTest test`
Expected: PASS
---
Plan complete and saved to `docs/plans/2026-01-26-mosquito-system-implementation-plan.md`. Two execution options:
1. Subagent-Driven (this session)
2. Parallel Session (separate)
Which approach?