docs: 映射 qy-lty-admin 代码库

This commit is contained in:
pmc 2026-05-07 10:58:29 +08:00
parent 7223c52e9f
commit a85b6a79a8
7 changed files with 1972 additions and 0 deletions

View File

@ -0,0 +1,203 @@
# Architecture
**Analysis Date:** 2026-05-07
## Pattern Overview
**Overall:** Next.js 15 App Router with client-centric admin dashboard, role-based access control (RBAC) via localStorage permission matrix, and layered API abstraction.
**Key Characteristics:**
- Client-side rendering-first pattern with permission checks in layout/shell components
- Token-based authentication with Axios interceptors for transparent token injection
- Role-based module access controlled by permission matrix in `lib/permissions.ts`
- Form handling via React Hook Form + Zod for validation
- Shadcn-style UI components copied into `components/ui/` (not npm packages)
## Layers
**Page Layer (Presentation/Routing):**
- Purpose: App Router page components that mount client-side shells and load data
- Location: `app/[module]/page.tsx` (e.g., `app/outfits/page.tsx`, `app/login/page.tsx`)
- Contains: "use client" page entries, form pages, list views
- Depends on: DashboardShell, custom feature components, API clients
- Used by: Next.js App Router, browser navigation
**Component Layer (UI & Business Logic):**
- Purpose: Reusable business components (modals, dialogs, tables, feature-specific UI)
- Location: `components/` (feature folders) and `components/ui/` (primitives)
- Contains: DashboardShell, Sidebar, feature dialogs (AddOutfitDialog, DeleteConfirmationDialog), stat cards
- Depends on: UI primitives, API clients, React Hook Form, icons (lucide-react)
- Used by: Page components
**API Client Layer (HTTP Communication):**
- Purpose: Axios-based API communication with interceptors for auth, error handling
- Location: `lib/api/` (e.g., `lib/api/client.ts`, `lib/api/auth.ts`, `lib/api/outfits.ts`)
- Contains: API singleton instance, interceptors, module-specific request/response adapters
- Depends on: Axios, localStorage (for token retrieval), environment variables
- Used by: Page components and feature components
**Permission/Auth Layer (Access Control):**
- Purpose: Role-based module visibility and path-level permission checks
- Location: `lib/permissions.ts` (RBAC matrix), `middleware.ts` (route protection)
- Contains: PERMISSION_MATRIX (role → module[]), utility functions (hasPermission, getUserRole, getModuleFromPath)
- Depends on: localStorage.user_role, localStorage.is_superuser
- Used by: DashboardShell, Sidebar, middleware
**Utility Layer:**
- Purpose: Type definitions, helpers, environment detection
- Location: `lib/utils.ts`, `lib/api/types.ts`, `lib/api/adapters.ts`
- Contains: Tailwind utilities (cn()), type mappings, mock data
- Depends on: Nothing (standalone)
- Used by: All layers
## Data Flow
**Authentication Flow (Login → Authenticated State):**
1. User fills login form on `app/login/page.tsx`
2. Form submission calls `emailLogin(email, password)` from `lib/api/auth.ts`
3. Axios POSTs to `{NEXT_PUBLIC_API_BASE_URL}/v1/admin/login/`
4. Response contains `token`, `role`, `is_superuser`
5. `saveAuthToken(token, isSuperUser, role)` stores to localStorage and Cookie
6. Page redirects to `/` (dashboard)
7. Middleware checks Cookie `auth_token`; on subsequent page loads, Axios request interceptor injects token from localStorage
**Protected Page Access Flow:**
1. User navigates to protected route (e.g., `/outfits`)
2. Middleware checks cookie; if missing, redirects to `/login`
3. Page mounts, DashboardShell component renders
4. DashboardShell calls `hasPathPermission(pathname)` at mount
5. Function checks localStorage.user_role against PERMISSION_MATRIX
6. If denied, renders access-denied UI; if allowed, renders children
7. Sidebar calls `hasPermission(module)` for each menu item (mounted only) to filter visible items
8. Sidebar also displays current role from localStorage.user_role
**Data Fetching Flow (Page → Component → API → Backend):**
1. Page component (e.g., `app/outfits/page.tsx`) mounts as "use client"
2. Page renders feature components (e.g., OutfitsList, OutfitsTable)
3. Feature component calls `getOutfits(params)` from `lib/api/outfits.ts`
4. API function calls `apiClient.get('/card/category/clothing/?...')`
5. Request interceptor injects token: `Authorization: Bearer {token}`
6. Backend receives request with token in header
7. Response returns `{ success, code, data, message }`
8. API adapter function maps backend response to frontend type (e.g., `mapBackendOutfit`)
9. Component stores result in React state or updates UI
10. Response interceptor handles 401 (token expired) → clears localStorage → redirects to `/login`
**State Management:**
- **Token/Auth state:** Persisted in `localStorage.auth_token`, `localStorage.user_role`, `localStorage.is_superuser`; synced to Cookie `auth_token`
- **Component state:** React useState (shallow, page-scoped)
- **Form state:** React Hook Form (form-scoped)
- **No global state manager** (Redux/Zustand not used)
**Authentication Token Lifecycle:**
1. Stored in localStorage + Cookie (7-day expiry) after login
2. On every API request, request interceptor reads from localStorage
3. If 401 response, token deleted from localStorage, user redirected to `/login`
4. Cookie-based fallback allows middleware to check auth on route entry
## Client/Server Component Split
**Server Components:**
- `app/layout.tsx` — Root layout with metadata only; does NOT render protected content
- Most pages are **"use client"** due to permission checks and hooks
**Client Components ("use client"):**
- All pages under `app/[module]/` — Need React hooks for permission checks, form handling
- `components/dashboard-shell.tsx` — Runs permission checks via `hasPathPermission()`
- `components/sidebar.tsx` — Uses `usePathname()`, `useRouter()`, `useState()` for role-based filtering
- Feature components — Use useState, API calls, form handling
## Key Abstractions
**DashboardShell:**
- Purpose: Layout wrapper that enforces route-level permissions
- Location: `components/dashboard-shell.tsx`
- Pattern: Client wrapper checking `hasPathPermission(pathname)` on mount; renders either access-denied UI or children
- Used by: All page components (wraps page content)
**Sidebar:**
- Purpose: Dynamic navigation menu filtered by user role
- Location: `components/sidebar.tsx`
- Pattern: Client component reading `localStorage.user_role` after mount; filters menu items via `hasPermission(module)`
- Menu structure: Three sections (AI Admin, Content Admin, System Admin) with conditional rendering
- Icon mapping: MenuItem interface + lucide-react icons
**API Client Singleton:**
- Purpose: Centralized HTTP client with reusable interceptors
- Location: `lib/api/client.ts` exports `apiClient` (Axios instance)
- Pattern: Single instance created at module load; request/response interceptors for auth/error handling
- Interceptors: Request injects token; response handles 401 by clearing token + redirecting
**Permission Matrix:**
- Purpose: Role → Module[] mapping
- Location: `lib/permissions.ts` PERMISSION_MATRIX constant
- Pattern: Record<RoleName, PermissionModule[]> with 5 roles × 13 modules
- Functions: getUserRole() → RoleName; hasPermission(module) → boolean; getModuleFromPath(pathname) → module
**API Adapter Pattern:**
- Purpose: Map backend response schema to frontend type
- Example: `mapBackendOutfit()` in `lib/api/outfits.ts`
- Pattern: Backend returns `{ id, name, image_url, rarity_display, ... }`; adapter extracts and renames to `{ id, name, imageUrl, rarity, ... }`
- Why: Frontend type contracts differ from backend schema
## Entry Points
**Browser Entry:**
- Location: `app/layout.tsx` (Root layout)
- Triggers: Initial page load or navigation
- Responsibilities: Renders HTML shell; child routes rendered by App Router
**Auth Entry:**
- Location: `app/login/page.tsx`
- Triggers: Unauthenticated users accessing protected routes (redirected by middleware)
- Responsibilities: Render login form, call `emailLogin()`, save auth token, redirect to dashboard
**Dashboard Entry:**
- Location: `app/page.tsx`
- Triggers: Authenticated users navigating to `/` or post-login redirect
- Responsibilities: Check auth status, render stats/charts, provide links to submodules
**Protected Route Entry (Example):**
- Location: `app/outfits/page.tsx`
- Triggers: User navigates to `/outfits`
- Responsibilities: Wrap content in DashboardShell (permission check), render OutfitsList component, fetch data
## Error Handling
**Strategy:** Try-catch at API level; Axios interceptor handles 401; UI displays error toast or inline messages.
**Patterns:**
- **API Request Errors:** Caught in try-catch, logged, thrown to caller (page/component)
- **401 Unauthorized:** Interceptor clears token, redirects to `/login`
- **Form Validation Errors:** React Hook Form + Zod pre-submission; validation errors displayed inline
- **Permission Denied:** DashboardShell renders access-denied UI (no error thrown)
- **Mock Data Fallback:** `lib/api/client.ts` exports mock users/roles; can be used for offline testing
## Cross-Cutting Concerns
**Logging:**
- Verbose Axios logging in request/response interceptors (console.log with emoji prefixes)
- Error logging in catch blocks
**Validation:**
- React Hook Form + Zod for form fields
- Backend response type-checked against ApiResponse interface
**Authentication:**
- localStorage.auth_token + Axios Bearer header injection
- Middleware checks Cookie auth_token for route protection
- 401 response triggers token cleanup + redirect
**Internationalization:**
- Chinese UI text hardcoded in components
- No i18n library used
---
*Architecture analysis: 2026-05-07*

View File

@ -0,0 +1,655 @@
# Codebase Concerns
**Analysis Date:** 2026-05-07
## Tech Debt
### Multiple Lock Files Conflict Risk
**Issue:** Three conflicting lock files coexist: `package-lock.json`, `pnpm-lock.yaml`, and `yarn.lock`.
**Files:**
- `package-lock.json` (177KB)
- `pnpm-lock.yaml` (2.3KB)
- `yarn.lock` (91KB)
**Impact:**
- Risk of installing different dependency versions across environments
- CI/CD, Docker builds, and local development may pull incompatible versions
- Lock file merge conflicts during multi-developer collaboration
- Dockerfile uses yarn exclusively, but npm/pnpm may be used locally, creating version drift
**Current State:**
- `Dockerfile` (line 12, 34) explicitly uses `yarn.lock*`
- `.gitignore` likely checks in all three files
- CLAUDE.md explicitly warns "不要混用" but provides no automation to enforce it
**Fix Approach:**
1. Choose single package manager (recommend yarn, per Dockerfile)
2. Delete `package-lock.json` and `pnpm-lock.yaml` from git history
3. Add enforcement to `.gitignore` and CI/CD (fail if multiple lock files modified)
4. Document in CLAUDE.md that ONLY yarn should be used
**Priority:** High - impacts build reproducibility and team workflow
---
### Production Build Ignores TypeScript & ESLint Errors
**Issue:** Build succeeds despite type/lint errors; production may contain undetected bugs.
**Files:**
- `next.config.mjs:16-21` — Both `ignoreDuringBuilds: true`
**Current Configuration:**
```javascript
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
```
**Impact:**
- TypeScript errors (type mismatches, missing nullchecks) not caught at build time
- ESLint warnings (unused imports, unreachable code) ignored
- QA burden shifts to runtime discovery
- Breaking changes in dependencies not detected early
**Evidence:** README.md (line 121) acknowledges this: "务必在本地执行 `next lint``tsc --noEmit` 保证质量" — manual step, not enforced
**Fix Approach:**
1. Set both to `false` in production config
2. Fix all current type/lint errors (likely small count given ~27K LOC)
3. Add pre-commit hooks to `husky` to block commits with errors
4. Make lint/type checks mandatory in CI/CD
**Priority:** High - gates correctness
---
### Console Logging in Client Code (Debug Artifact)
**Issue:** Production code contains console.log/warn/error calls, exposing debug info and token prefixes.
**Files & Line Numbers:**
- `lib/api/client.ts:23``console.log('🔍 Token检查:', ...)`
- `lib/api/client.ts:29``console.warn('⚠️ 未找到auth_token...')`
- `lib/api/client.ts:35``console.log('📡 发送请求:', ...)`
- `lib/api/client.ts:36``console.log('📋 请求头:', ...)`
- `lib/api/client.ts:47``console.log('✅ 响应成功:', ...)`
- `lib/api/client.ts:51``console.error('❌ 响应错误:', ...)`
- `lib/api/client.ts:55``console.warn('🚫 认证失败...')`
- `lib/api/client.ts:56``console.log('响应详情:', ...)`
- `lib/api/upload.ts:33``console.log('📤 开始上传文件:', ...)`
- `lib/api/upload.ts:43``console.log(...上传进度...)`
- `lib/api/upload.ts:59``console.log('✅ 文件上传成功:', ...)`
- `lib/api/upload.ts:62``console.error("文件上传失败:", error)`
- Similar issues in upload multifile (lines 83, 93)
- `components/sidebar.tsx:97``console.error("退出登录失败:", error)`
- `app/outfits/page.tsx:72, 117, 135, 154` — console.error calls
- Many other pages: `app/*/page.tsx` contain error logging
**Impact:**
- Token prefixes exposed in browser console → XSS/developer tools leak risk
- Error details (e.g., API paths, status codes) visible to end users
- Production debugging should use structured error handling, not console
- Could be scraped by malicious actors with browser console access
**Evidence:** `lib/api/token-debug.ts` is entire debug utility with `console.log` throughout (lines 7-54), showing token prefix extraction logic
**Fix Approach:**
1. Remove all console.log/warn/error in client code, replace with structured error handling
2. Create error boundary/toast notification pattern for user-facing errors
3. Optional: Create client-side error tracking service (Sentry) for production
4. Add ESLint rule: `no-console` (already available in Next.js config, just not enabled)
**Priority:** High - security and code cleanliness
---
## Security Concerns
### Client-Side Permission Enforcement Only (Trust Boundary Violation)
**Issue:** Permissions are enforced ONLY on client; backend may not re-validate on API calls.
**Files:**
- `lib/permissions.ts:65-87``getUserRole()`, `hasPermission()` read from `localStorage.user_role`
- `components/sidebar.tsx:102-104` — Filter menu items client-side only
- `middleware.ts:17-44` — Token validation in middleware, but NO permission check against module being accessed
**Risk Model:**
```
User (attacker) → Modify localStorage.user_role="超级管理员" →
Sidebar shows all menus (useless) BUT
Direct API call to DELETE /api/v1/admin/users/123 → Backend accepts if no re-check
```
**Current Evidence:**
- `lib/api/users.ts:24-43``getUsers()` makes raw API call; no permission guard in client code
- No evidence of backend role check in Django API (not inspectable from frontend code, but CRITICAL to verify)
**Impact:**
- **CRITICAL:** If backend doesn't re-validate role on each endpoint, attackers can:
- Modify localStorage role → make admin API calls
- Bypass middleware token check (SSR can't access localStorage) → direct API calls with stolen token
- Perform unauthorized operations (delete users, change settings, etc.)
**Trust Boundary Rule:** Backend MUST re-check permission for EVERY admin API call, regardless of client claims.
**Fix Approach:**
1. **URGENT:** Audit Django backend API (`qy_lty/lib/api/v1/admin/`) to confirm all endpoints validate user role/permissions
2. Document in CLAUDE.md: "Client-side permission filters are UI courtesy only; backend MUST validate on all admin/ endpoints"
3. Add explicit checks in frontend error handling for 403 Forbidden responses
4. Consider adding request logging/monitoring to catch unauthorized attempts
**Priority:** CRITICAL - security vulnerability if backend doesn't re-check
---
### Token Storage in localStorage (XSS Vulnerability)
**Issue:** Auth token stored in `localStorage` instead of secure HttpOnly cookie; vulnerable to XSS.
**Files:**
- `lib/api/auth.ts:48-59` — Stores token in localStorage AND sets js-cookie
- `lib/api/client.ts:21-22` — Reads token from localStorage in request interceptor
- `middleware.ts:32` — Token read from Cookie (correct), but localStorage is fallback
**Current Implementation:**
```typescript
// Stored in BOTH localStorage and cookie
localStorage.setItem("auth_token", token); // XSS risk
Cookies.set("auth_token", token, { expires: 7, path: '/' }); // No HttpOnly flag
```
**Impact:**
- XSS attack can steal token from localStorage
- js-cookie (js-cookie, no HttpOnly flag) also vulnerable to XSS
- 7-day expiry without rotation = long XSS window
- `localStorage.user_role` also stored unencrypted (attackable)
**Proper Secure Pattern:**
- HttpOnly, Secure, SameSite cookies (set by backend during login)
- Token never exposed to JavaScript
- Client reads from Bearer token response, never stores in accessible storage
**Fix Approach:**
1. Backend should set HttpOnly cookie during login response
2. Remove localStorage token storage entirely
3. Keep only sealed HTTP-only cookie for auth
4. If SSR requires token access, use secure session storage (not localStorage)
5. Add CSP (Content-Security-Policy) headers to mitigate XSS
**Priority:** High - significant security risk
---
### No Token Rotation / Refresh Strategy
**Issue:** Token expires in 7 days, no refresh token or rotation mechanism visible.
**Files:**
- `lib/api/auth.ts:63``Cookies.set("auth_token", token, { expires: 7, path: '/' })`
- `lib/api/client.ts:54-60` — 401 handler just redirects to /login, doesn't attempt refresh
**Impact:**
- User activity > 7 days → auto-logout (UX degradation)
- No short-lived access token + long-lived refresh token pattern
- Stolen token valid for full 7 days
- No token revocation (logout doesn't invalidate server-side if stateless JWT)
**Fix Approach:**
1. Implement short-lived access token (15-30 min) + refresh token (7-14 days)
2. Refresh interceptor: if 401, attempt token refresh before redirecting to /login
3. Backend session should blacklist tokens on logout
4. Document token lifecycle in CLAUDE.md
**Priority:** Medium - acceptable for internal admin tool, but should improve for production
---
### No Input Validation on File Upload
**Issue:** File uploads accept any mimetype/size without validation.
**Files:**
- `lib/api/upload.ts:25-65``uploadFile()` accepts File object, no size/type checks in client
- `components/ui/file-upload.tsx` — Likely has upload UI, needs validation
**Impact:**
- Malicious files (executables, oversized) may reach backend
- Backend must validate, but client-side check is first defense
- No maximum file size enforced client-side
**Fix Approach:**
1. Add file size limits (e.g., max 50MB)
2. Whitelist MIME types (images: image/jpeg, image/png, image/webp only)
3. Show user error before upload attempt
4. Verify backend enforces same limits
**Priority:** Medium - backend should catch it, but client should be first defense
---
## Fragile Areas
### Large Pages with Mixed Concerns (1000+ line files)
**Issue:** Several pages handle data fetching, UI rendering, and business logic in single component.
**Large Files:**
- `app/affinity/page.tsx` (1005 lines) — Affinity rules, levels, interactions all in one page
- `components/ui/sidebar.tsx` (763 lines) — Navigation logic, permission filtering, logout in one component
- `app/songs/[id]/page.tsx` (651 lines) — Song detail page, editing, deletion mixed
- `app/songs/page.tsx` (644 lines) — Song list, search, pagination, CRUD operations
**Fragility Risks:**
- Hard to test individual business logic
- Changes to one feature (e.g., affinity rules) risk breaking another (levels)
- Difficult to reuse UI logic across pages
- High cognitive load for maintenance
**Safe Modification Pattern:**
1. Extract data-fetching logic to `lib/api/` hooks or services
2. Create smaller sub-components (e.g., `<AffinityRuleSection>`, `<AffinityLevelSection>`)
3. Use custom hooks for state management per feature
4. Keep pages as composition only
**Example Refactor:**
```typescript
// Current: 1005-line page
// After:
export default function AffinityPage() {
return (
<DashboardShell>
<DashboardHeader />
<AffinityRulesSection />
<AffinityLevelsSection />
<InteractionLogsSection />
</DashboardShell>
)
}
```
**Priority:** Medium - refactor as part of feature changes, not urgent
---
### No Error Boundaries (Next.js error.tsx Files Missing)
**Issue:** No error boundary components for graceful error handling in App Router.
**Files:**
- `app/` has NO `error.tsx` files
- Only some leaf routes have `loading.tsx` (e.g., `app/outfits/loading.tsx`)
**Current Error Handling:**
- Pages catch errors in `try/catch`, show toast notifications
- Uncaught errors propagate, may show raw error UI
- No fallback UI for API failures
**Impact:**
- User sees blank page or browser error on network failure
- No recovery mechanism (retry button, fallback content)
- Log data lost if error page crashes
**Next.js Error Boundary Pattern:**
```typescript
// app/error.tsx (catches errors in all children)
'use client'
export default function Error({ error, reset }) {
return (
<div>
<h1>Something went wrong</h1>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
```
**Fix Approach:**
1. Add `app/error.tsx` for app-level errors
2. Add segment-level `error.tsx` for feature-specific handling (e.g., `app/outfits/error.tsx`)
3. Keep try/catch for API calls, render fallback UI
4. Add `app/not-found.tsx` for 404 routes
**Priority:** Low - pages have basic error handling via toasts, but UX could improve
---
### Hardcoded API Base URL Fallback
**Issue:** API client falls back to hardcoded URL if env var not set.
**Files:**
- `lib/api/client.ts:9``API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"`
**Risk:**
- If `NEXT_PUBLIC_API_BASE_URL` not set in production, silently uses localhost
- Requests fail silently in production, confusing to debug
- No warning log when fallback is used
**Fix Approach:**
1. Remove fallback; throw error if env var not set at build time
2. Use `if (!process.env.NEXT_PUBLIC_API_BASE_URL) throw new Error("...")`
3. Document required env vars in deployment guide
**Priority:** Low - environmental issue, unlikely in production if properly configured
---
## Performance Bottlenecks
### No Memoization on Permission Checks
**Issue:** `hasPermission()`, `getUserRole()` called on every render without memoization.
**Files:**
- `components/sidebar.tsx:102-104` — Calls `hasPermission(item.module)` in `.filter()` on every render
- `lib/permissions.ts:65-87` — No caching; reads localStorage every time
**Current Pattern:**
```typescript
const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : []
```
**Impact:**
- Sidebar re-renders, re-filters items, re-reads localStorage unnecessarily
- Minimal impact on small arrays (3-15 items), but scales poorly
**Fix Approach:**
1. Memoize `getUserRole()` result in sidebar:
```typescript
const userRole = useMemo(() => getUserRole(), [mounted])
const visibleItems = useMemo(() =>
aiMenuItems.filter(item => hasPermission(item.module)),
[userRole]
)
```
2. Or move to custom hook: `const { role, allowedModules } = usePermissions()`
**Priority:** Low - not a visible performance issue, but good practice
---
## Cross-Repo Coupling Risks
### Strong Runtime Dependency on qy_lty Backend
**Issue:** Front-end depends on specific API contract from `qy_lty/` Django backend; contract drift risk.
**Files:**
- `lib/api/users.ts:6-21` — Maps backend `ParadiseUser` to `User` type (field mapping)
- `lib/api/outfits.ts` (not read, but expected) — Maps backend outfit schema
- ALL API modules expect specific response structure from `/api/v1/admin/*`
**Coupling Points:**
- Backend field names: `username`, `email`, `phone_number`, `date_joined`, `last_login`, `is_superuser`, `is_staff`
- Response structure: `{ results: [...], count: number }` (Django REST Framework default)
- Endpoint paths: `/v1/admin/login/`, `/user/`, `/common/upload/`
- Token storage key in Redis: `admin_token:{token}` (per CLAUDE.md)
**Risk Scenario:**
1. Backend dev changes `/v1/admin/users/` response format
2. Frontend still sends old field names or expects old response shape
3. Silent failures or type mismatches
4. Detected late in testing or production
**Mitigation in Place:**
- CLAUDE.md (line 34) requires both repos update `docs/修改记录.md` when contract changes
- But no automated contract testing (OpenAPI, TypeScript codegen)
**Fix Approach:**
1. **Shared types/API contract** (OpenAPI/GraphQL schema) versioned in git
2. Generate TypeScript types from backend OpenAPI spec (e.g., with Swagger-Codegen)
3. Add pre-commit hook to detect schema changes
4. Document in CLAUDE.md: "Any API response structure change requires updating both repos' docs"
**Priority:** Medium - mitigated by manual process, but automatable
---
## Dependencies at Risk
### No Enforcement of Dependency Updates
**Issue:** Many dependencies pinned to `latest`, creating unpredictable updates.
**Files:**
- `package.json:12, 56, 63``"latest"` version specifiers
**Current Versions Using "latest":**
```json
"@hookform/resolvers": "latest",
"react-hook-form": "latest",
"recharts": "latest",
"zod": "latest"
```
**Impact:**
- `npm install` at different times installs different major versions
- Breaking changes in `react-hook-form` or `recharts` not caught by lock file
- Tests pass locally, fail in CI if new major version published
**Fix Approach:**
1. Replace all `"latest"` with specific versions (e.g., `"^7.0.0"` for caret range)
2. Use Dependabot or renovate to automate safe updates
3. Pin devDependencies to exact versions
**Example:**
```json
"@hookform/resolvers": "^3.3.4",
"react-hook-form": "^7.51.0",
"recharts": "^2.12.0",
"zod": "^3.22.0"
```
**Priority:** Medium - not urgent, but good practice
---
### Unused Dependencies or Overlapping Packages
**Issue:** Unclear if all installed packages are actively used.
**Potential Issues:**
- Multiple notification libraries: `sonner` + `@radix-ui/react-toast` (which is used?)
- Figure out which is preferred and remove duplication
**Files:**
- `package.json:36, 59` — Both toast packages listed
**Fix Approach:**
1. Audit each dependency in `package.json`
2. Remove unused packages
3. Consolidate duplicate functionality
**Priority:** Low - cleanup task
---
## Test Coverage Gaps
### No Tests Found
**Issue:** No Jest/Vitest configuration or test files detected in codebase.
**Expected Test Files:**
- `*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.spec.tsx` not found
- No `jest.config.*`, `vitest.config.*`
**Impact:**
- Complex logic (permission matrix, API adapters) not tested
- Refactoring risky (no regression protection)
- Onboarding new developers without safety net
- No confidence in critical paths (auth, user management)
**Critical Areas Needing Tests:**
- `lib/permissions.ts` — Permission matrix logic (many branches)
- `lib/api/client.ts` — Request/response interceptors (error handling paths)
- `lib/api/users.ts` — User mapping logic (backend schema mismatches)
- Custom hooks in pages (data fetching, state management)
**Fix Approach:**
1. Set up Jest or Vitest (Vitest recommended for ESM + modern tooling)
2. Create basic test for `lib/permissions.ts`:
```typescript
// lib/permissions.test.ts
describe('Permission checks', () => {
it('should allow super admin all modules', () => {
// Mock localStorage with 超级管理员
expect(hasPermission('users')).toBe(true)
expect(hasPermission('ai-model')).toBe(true)
})
it('should restrict content admin to content modules', () => {
// Mock localStorage with 内容管理员
expect(hasPermission('users')).toBe(false)
expect(hasPermission('outfits')).toBe(true)
})
})
```
3. Aim for 80%+ coverage of critical API functions
**Priority:** Medium - important for long-term maintainability
---
## Missing Critical Features
### No Logout Token Revocation
**Issue:** `logout()` clears client-side storage but doesn't invalidate server-side token.
**Files:**
- `lib/api/auth.ts:108-131``logout()` calls API endpoint, then clears localStorage
**Problem:**
- If using stateless JWT, backend has no record of logout
- Token remains valid until expiry (7 days)
- Attacker with stolen token can still use it after target user "logs out"
**Expected Behavior:**
- Server should blacklist token on logout (Redis blacklist or DB flag)
- Verify endpoint `/v1/admin/logout/` actually invalidates token
**Fix Approach:**
1. Audit backend logout endpoint to confirm token is invalidated
2. Add token blacklist to backend (Redis: token → blacklisted, TTL=7 days)
3. Check token against blacklist on every request
4. Document in CLAUDE.md
**Priority:** High - security issue if not handled on backend
---
### No Rate Limiting on Client (Brute Force Risk)
**Issue:** Login page may be vulnerable to brute force attacks.
**Files:**
- `app/login/page.tsx` — Likely has login form
- `lib/api/auth.ts:25-40` — No rate limiting before API call
**Impact:**
- Attacker can spam login attempts to guess password
- Backend must implement rate limiting, but client can't spam if it has throttling
**Fix Approach:**
1. Client-side: Disable login button for 30 seconds after failed attempt
2. Backend: Implement account lockout (3 failed attempts = 10 min lockout)
3. Monitor login failures in logs
**Priority:** Medium - backend responsibility, but client should assist
---
## Build Configuration Issues
### Next.js Standalone Output May Have Deployment Pitfalls
**Issue:** Standalone mode used, but edge case behaviors not fully documented.
**Files:**
- `next.config.mjs:30``output: 'standalone'`
**What Standalone Does:**
- Bundles all dependencies into `.next/standalone` folder
- Reduces Docker image size (~100MB vs ~500MB)
- Requires explicit `COPY .next .next` in Dockerfile (correctly done)
**Potential Issues:**
- Public assets not automatically copied; must COPY separately (done in Dockerfile:44-45)
- `next.config.mjs` must be manually copied (done in Dockerfile:46)
- Environment variables must be passed at runtime, not build time
- Debugging bundle issues harder (bundled deps not in node_modules)
**Current Dockerfile (lines 44-46) handles this correctly:**
```dockerfile
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.mjs ./
```
**Risk:**
- If dependencies add new output files in future versions, they won't be copied
- Node version mismatch between builder (22.10.0) and runner (22.10.0) is correct but fragile
**Fix Approach:**
1. Document standalone output caveats in deployment guide
2. Test Docker image monthly to ensure no drift in builder/runner
3. Consider base image pinning: `FROM node:22.10.0-alpine@sha256:...`
**Priority:** Low - current setup is correct, just needs documentation
---
### No Pre-commit Hooks or Husky Setup
**Issue:** No automated checks before commits; developers can commit broken code.
**Files:**
- No `.husky/` directory
- No `pre-commit` config
- CLAUDE.md doesn't mention hook setup
**Risk:**
- Developer commits console.log, broken types, or lint errors
- CI catches them later, but feedback loop is slow
- No enforcement of modification record updates (CLAUDE.md requirement)
**Fix Approach:**
1. Install husky: `npm install husky --save-dev`
2. Setup hook: `npx husky install`
3. Create `.husky/pre-commit`:
```bash
#!/bin/sh
npm run lint && npm run type-check
```
4. Create `.husky/prepare-commit-msg` to remind user about docs/修改记录.md
**Priority:** Low - improvement for team workflow
---
## Summary Table
| Category | Issue | Severity | Fix Effort |
|----------|-------|----------|-----------|
| Security | Client-only permissions (backend trust) | CRITICAL | High |
| Security | Token in localStorage (XSS) | High | Medium |
| Security | No token revocation on logout | High | High |
| Tech Debt | Lock file conflicts | High | Low |
| Tech Debt | Build ignores TS/ESLint errors | High | Low |
| Tech Debt | Console logging in production | High | Low |
| Fragility | Large 1000+ line pages | Medium | Medium |
| Performance | No memoization on permissions | Low | Low |
| Testing | Zero test coverage | Medium | High |
| Build | Standalone output not fully documented | Low | Low |
---
*Concerns audit: 2026-05-07*

View File

@ -0,0 +1,210 @@
# 代码规范
**分析日期:** 2026-05-07
## 命名规范
**文件命名:**
- 组件文件PascalCase + `.tsx` 扩展名(如 `DashboardShell.tsx``AddOutfitDialog.tsx`
- 工具函数/库文件kebab-case + `.ts` 扩展名(如 `error-handler.ts``client.ts`
- 特殊情况:对话框类组件使用 kebab-case`add-outfit-dialog.tsx``delete-confirmation-dialog.tsx`
**函数命名:**
- 使用 camelCase`handleLogin``fetchOutfits``mapBackendOutfit`
- API 函数:动词 + 名词camelCase`getOutfits``createOutfit``updateOutfit``deleteOutfit`
- 事件处理函数:`handle` + 事件名(如 `handleSubmit``handleChange``handleSendVerificationCode`
- 工具函数:动词 + 目标(如 `formatDate``toDisplayOutfit`
**变量命名:**
- 状态变量camelCase`email``password``isLoading``selectedOutfit`
- 布尔值:`is``has` 前缀(如 `isLoading``isSubmitting``hasPermission`
- 常量UPPER_SNAKE_CASE`TOAST_LIMIT``TOAST_REMOVE_DELAY``API_BASE_URL`
**类型/接口命名:**
- PascalCase`EmailLoginResponse``DashboardShellProps``ApiResponse<T>`
- Props 接口:`组件名Props` 后缀(如 `ButtonProps``DashboardShellProps`
- 联合类型:`RoleName``PermissionModule`
## 代码样式
**格式化:**
- 使用 TypeScript strict 模式(`tsconfig.json``"strict": true`
- 目标ES6`target: "ES6"`
- 模块化解析:`bundler` 模式(用于 Next.js
- 无 ESLint/Prettier 配置文件(`next.config.mjs``eslint.ignoreDuringBuilds: true`
- 代码风格通过 TypeScript 编译器和 Next.js 内置检查
**缩进和空格:**
- 使用 2 空格缩进(根据代码库一致性)
- 函数声明和块语句间使用一致空格
**类型注解:**
- 所有函数参数必须有类型注解
- 所有变量应有类型注解(特别是导出的接口和公共函数返回值)
- 使用 TypeScript 严格模式防止隐式 `any`
## 导入组织
**顺序:**
1. React 和 Next.js 核心库(`import React from "react"``import { useState } from "react"`
2. Next.js 功能(`import { useRouter } from "next/navigation"``import Link from "next/link"`
3. 第三方库(`import axios from "axios"``import Cookies from "js-cookie"`
4. 本项目组件(`import { Button } from "@/components/ui/button"`
5. 本项目库和工具(`import { cn } from "@/lib/utils"`
6. 本项目 hooks`import { useToast } from "@/components/ui/use-toast"`
7. 类型导入(`import type { ApiResponse } from "./client"`
**路径别名:**
- 项目配置 `@/*` 指向项目根目录
- 组件:`@/components/...`
- 库函数:`@/lib/...`
- Hooks`@/hooks/...`
- 类型和接口:通过 `@/lib/api/types` 集中导入
- UI 组件:`@/components/ui/...`
## 错误处理
**模式:**
- 使用 `try-catch` 处理异步操作和 API 调用(见 `auth.ts``outfits.ts`
- 创建自定义错误类 `ApiError` 扩展 `Error`,包含 `status` 属性(见 `error-handler.ts` 第 46-54 行)
- 统一的错误处理函数 `handleApiException()` 将不同类型的错误转换为用户友好的消息(见 `error-handler.ts` 第 79-90 行)
- 错误信息映射表 `errorMessages` 定义特定错误代码(`USER_NOT_FOUND``ROLE_EXISTS` 等)的消息
**API 错误处理:**
- 在 Axios 响应拦截器中处理 401 未授权错误:清除 token 并重定向到登录页面(见 `client.ts` 第 54-61 行)
- 对 API 失败使用 Radix Toast 组件显示错误提示(见 `error-handler.ts` 第 69-76 行、第 93-100 行)
- 统一的 `handleApiRequest()` 包装器函数处理成功/失败情况并自动显示 toast`error-handler.ts` 第 102-144 行)
**日志:**
- 使用 `console.log()``console.warn()``console.error()` 进行调试和错误追踪
- API 请求/响应在拦截器中记录详细日志,包括 token 检查、请求头、响应状态(见 `client.ts` 第 20-66 行)
- 使用 emoji 表情增强日志可读性(`🔍 Token检查``✅ Token已添加``❌ 响应错误`
## 日志记录
**框架:** `console`(浏览器原生)
**使用规范:**
- API 请求/响应日志在 Axios 拦截器中集中管理
- 所有认证流程加日志token 检查、保存、清除)
- 错误信息带上上下文URL、状态码、方法
- 生产环境应考虑减少日志或使用第三方服务(未配置)
## 注释
**何时写注释:**
- JSDoc 风格注释用于公共 API 函数(见 `auth.ts` 第 5-7 行、第 24-30 行)
- 注释解释"为什么"而不是"是什么"
- 复杂业务逻辑或算法需要行内注释
**JSDoc/TSDoc 风格:**
```typescript
/**
* 邮箱登录接口
* @param email 邮箱
* @param password 密码
* @returns 包含token的响应
*/
export const emailLogin = async (email: string, password: string): Promise<EmailLoginResponse> => {
// ...
}
```
**中文注释:** 所有注释使用中文(符合 CLAUDE.md 的语言偏好)
## 函数设计
**大小:**
- 函数长度通常 20-50 行(见 `handleLogin``login/page.tsx` 第 28-63 行)
- 超过 100 行的函数应考虑拆分(如 `AddOutfitDialog` 组件中的多步表单分离为 Tabs
**参数:**
- 单个参数优于多个参数
- 对象参数用于选项(如 `handleApiRequest()` 的 options 对象,第 104-110 行)
- 类型参数用于泛型函数(如 `handleResponse<T>()``ApiResponse<T>`
**返回值:**
- 异步函数返回 `Promise<T>`
- 布尔查询函数返回 `boolean`(如 `hasPermission()``isAuthenticated()`
- 数据获取返回具体类型或 `null`(见 `getOutfit()` 返回 `Promise<Outfit>`
## 模块设计
**导出:**
- 公共 API 函数从 `lib/api/` 模块导出(如 `emailLogin``getOutfits`
- 类型从 `lib/api/types.ts` 集中导出
- Utility 函数从 `lib/utils.ts` 导出(如 `cn()` 用于样式合并)
**Barrel 文件:**
- `lib/api/index.ts` 作为 barrel 文件,导出所有 API 函数以便统一导入
- 组件库采用 Radix UI + shadcn 风格,每个组件文件独立,无 barrel 文件
## React 和组件规范
**状态管理:**
- 使用 React Hooks`useState``useEffect``useCallback`)进行本地状态管理
- 在客户端组件中集中管理表单状态(见 `add-outfit-dialog.tsx` 第 24-36 行)
**组件模式:**
- 所有表单和交互组件用 `"use client"` 指令标记
- Page 组件(`app/*/page.tsx`)使用 `"use client"` 支持交互
- 使用 Radix UI 和 shadcn 风格组件库,组件文件在 `components/ui/`
- Props 接口扩展原生 HTML 属性(如 `ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>`
**样式:**
- 使用 Tailwind CSS 类名
- 使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)合并条件样式(见 `button.tsx` 第 47 行)
- 使用 `class-variance-authority` (CVA) 定义组件变体(见 `button.tsx` 第 7-34 行)
- 颜色、间距、圆角通过 Tailwind 配置变量CSS 变量)定义
## 表单与验证
**表单状态:**
- 使用 `useState` 管理表单字段状态
- 表单提交通过 `handleSubmit()` 函数处理(见 `add-outfit-dialog.tsx` 第 38-56 行)
**验证:**
- 基本的客户端验证(如检查空值:`if (!formData.name || !formData.description)`
- React Hook Form + Zod 在 package.json 中定义但代码中未广泛使用
- 服务器端验证通过 API 响应处理(返回 400/422 等错误状态)
**通知:**
- 成功/错误提示使用 Sonner toast 或 Radix Toast通过 `useToast()` hook
- toast 样式变体:`"default"``"destructive"`(见 `error-handler.ts` 第 71-75 行)
## 权限控制
**权限检查:**
- 运行时权限检查通过 `hasPermission(module)` 函数(见 `permissions.ts` 第 85-87 行)
- 权限矩阵定义在 `PERMISSION_MATRIX` 对象,映射角色到模块列表
- 路由保护:中间件 `middleware.ts` 检查 cookie 中的 token无 token 重定向到登录
- 组件级权限检查:`DashboardShell` 使用 `hasPathPermission()` 处理访问拒绝(见 `dashboard-shell.tsx` 第 23-24 行)
**角色存储:**
- 登录后在 `localStorage` 中存储 `user_role` 字符串(见 `auth.ts` 第 58 行)
- 超级管理员标识存储为 `is_superuser`(见 `auth.ts` 第 53 行)
- token 同时存储在 `localStorage` 和 Cookie 中(见 `auth.ts` 第 49、63 行)
## 异步操作
**模式:**
- 使用 `async/await` 处理异步 API 调用
- 加载状态通过 `isLoading` 布尔值跟踪(见 `login/page.tsx` 第 19、30 行)
- 模拟延迟用于开发和测试(见 `client.ts` `simulateDelay()``mockResponse()` 函数)
## API 集成
**Axios 配置:**
- 在 `lib/api/client.ts` 创建 Axios 实例,基础 URL 来自环境变量 `NEXT_PUBLIC_API_BASE_URL`
- 请求拦截器自动注入 Authorization 头Bearer token
- 响应拦截器处理 401 错误并触发重新登录
- API 端点前缀:`/api/v1/admin/`
**适配器模式:**
- 后端返回数据结构与前端显示结构不同
- 使用 `mapBackendOutfit()` 等适配器函数转换数据(见 `outfits.ts` 第 5-22 行)
- 适配器在 `lib/api/adapters.ts` 中集中管理
---
*规范分析日期2026-05-07*

View File

@ -0,0 +1,182 @@
# External Integrations
**Analysis Date:** 2026-05-07
## APIs & External Services
**qy_lty Backend (Django REST API):**
- Primary backend for all business logic
- URL: Base configured via `NEXT_PUBLIC_API_BASE_URL` (default: `http://localhost:8000/api`)
- SDK/Client: Axios (custom instance with interceptors)
- Auth: Bearer token via `Authorization: Bearer {token}` header
- Primary modules consumed:
- `/api/v1/admin/login/` - User authentication
- `/api/v1/admin/logout/` - User logout
- `/ai/bots/` - AI model CRUD
- `/card/category/clothing/` - Outfit/clothing items
- `/card/category/props/` - Props/accessories
- `/card/category/home-decor/` - Home decoration items
- `/card/category/food/` - Food items
- `/music/songs/` - Song management
- `/dances/` - Dance content
- `/achievements/` - Achievement system
- `/affinity/` - Affinity/favorability system
- `/common/upload/` - File upload endpoint
- `/common/upload/info/` - File metadata retrieval
**Request/Response Pattern:**
```typescript
// Axios configuration: lib/api/client.ts
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: { 'Content-Type': 'application/json' }
})
// Request interceptor: auto-injects token
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// Response interceptor: handles 401 (redirect to /login)
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('auth_token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
```
## Data Storage
**Databases:**
- No direct database connection from frontend
- Backend (qy_lty) manages all persistent data (PostgreSQL/MySQL presumed)
- Frontend uses in-memory mock data for fallback/demo scenarios only
**File Storage:**
- Backend-managed via `/common/upload/` endpoint
- Upload types supported:
- **Images** (JPEG, PNG, GIF, WebP) - max 10MB
- **Avatars** (JPEG, PNG) - max 2MB
- **Audio** (MP3, WAV, OGG, AAC, FLAC, WMA, M4A) - max 20MB
- **Animations** (MP4, AVI, MOV, WMV, FLV, GIF, Lottie JSON) - max 50MB
- **General files** - multipart/form-data
- Upload library: File API (FormData) via Axios
- Progress tracking: `onUploadProgress` callback support
**Caching:**
- Browser localStorage for:
- `auth_token` - Authentication token (auto-removed on 401)
- `is_superuser` - Superuser flag
- `user_role` - User role string (used for permission checks)
- `isLoggedIn` - Session state flag
- No server-side caching configured (Redis presumed in backend qy_lty)
## Authentication & Identity
**Auth Provider:**
- Custom implementation via qy_lty backend
- Backend OAuth/token system: admin token key format `admin_token:{token}` (in Redis)
**Login Flow:**
1. POST `/api/v1/admin/login/` with email + password
2. Backend returns: `{ success, code, data: { token, is_superuser?, role? }, message }`
3. Frontend stores token in localStorage + cookies (7-day expiry)
4. All subsequent requests include `Authorization: Bearer {token}`
5. On 401 response: clear tokens, redirect to `/login`
**Token Storage:**
- Primary: `localStorage.auth_token` (checked on every request)
- Secondary: `js-cookie` cookie `auth_token` (7-day expiry) for middleware access
- Logout clears both storages
**Role-Based Access:**
- Roles stored in localStorage: `user_role`
- Permission matrix defined in `lib/permissions.ts`
- Supported roles: 超级管理员, 内容管理员, AI模型管理员, 卡牌管理员, 查看者, 管理员
- Module-level access control via `hasPermission()` and `hasPathPermission()` functions
**Protected Routes:**
- Middleware: `middleware.ts` checks for token on protected paths
- Protected paths: `/`, `/dashboard`, `/users`, `/roles`, `/ai-models`, `/outfits`, `/props`, `/songs`, `/settings`
- Public paths: `/login`, `/register`, `/forgot-password` (no token required)
## Monitoring & Observability
**Error Tracking:**
- Not detected - errors logged to console only
- Error messages mapped in `lib/api/error-handler.ts`
- Toast notifications via Sonner for user-facing errors
**Logs:**
- Console logging (development-focused)
- Request/response logging in Axios interceptors (logs token status, URLs, headers, status codes)
- Client-side logging only (no centralized log aggregation)
## CI/CD & Deployment
**Hosting:**
- Docker containerization: `Dockerfile` (multi-stage build)
- Runtime: Node.js 22.10.0 Alpine Linux
- Port: 3000
- Command: `yarn start` (runs Next.js production server)
**CI Pipeline:**
- Not detected in codebase (likely external to this repo)
**Build Output:**
- Format: Next.js standalone (self-contained, no `node_modules` in runtime image)
- Files included: `.next/standalone/`, `public/`
- Size optimization: devDependencies not included in runner stage
## Environment Configuration
**Required env vars:**
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (must be public, prefixed with `NEXT_PUBLIC_`)
- Example: `http://localhost:8000/api` (development), `https://api.production.com/api` (production)
**Optional env vars:**
- `NODE_ENV` - Set to `production` in Docker runner stage
- `.env.local` - Overrides all other env files (gitignored)
- `.env.development` - Dev-specific overrides
- `.env.production` - Production-specific overrides
**Secrets location:**
- Authentication tokens: browser localStorage + cookies
- No API keys or credentials hardcoded in source
- Environment variable `NEXT_PUBLIC_API_BASE_URL` is the sole configuration bridge to backend
## Webhooks & Callbacks
**Incoming:**
- None detected
- Backend (qy_lty) may have webhooks, but frontend is purely client-side consumer
**Outgoing:**
- None detected
- All communication is request-response (REST API calls to qy_lty)
## Cross-Repo Dependencies
**qy_lty Backend (Sibling Repo):**
- Location: `C:\Users\admin\Desktop\Lila-Server\qy_lty\` (Django)
- Contract: `/api/v1/admin/` endpoint suite
- Shared concerns: Token format (`admin_token:{token}`), role names, permission structure
- Change coordination required: Both `docs/修改记录.md` files must be updated when API contracts change
**Notes:**
- Frontend is tightly coupled to backend API schema (no API versioning detected)
- Backend controls: authentication, authorization, data persistence, file storage
- Frontend is purely a UI/UX layer consuming backend HTTP APIs
---
*Integration audit: 2026-05-07*

View File

@ -0,0 +1,152 @@
# Technology Stack
**Analysis Date:** 2026-05-07
## Languages
**Primary:**
- TypeScript 5 - All application code, types, and configuration
- React 19 - UI components and application logic (via Next.js)
- CSS / Tailwind - Styling
**Secondary:**
- JavaScript - Configuration files, optional runtime
## Runtime
**Environment:**
- Node.js 22.10.0 (Alpine Linux) - as specified in Dockerfile
**Package Manager:**
- Yarn (primary for Docker builds; npm/pnpm also supported locally)
- Lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` present
- Registry: npm registry (Dockerfile uses Taobao mirror `registry.npmmirror.com` for builds)
## Frameworks
**Core:**
- Next.js 15.2.4 - App Router, React Server Components, standalone output mode
- Configuration: `next.config.mjs`
- Build output: standalone (optimized for Docker)
- Experimental features: webpackBuildWorker, parallelServerBuildTraces, parallelServerCompiles
**Styling:**
- Tailwind CSS 3.4.17 - Utility-first CSS framework
- Animation plugin: `tailwindcss-animate` 1.0.7
- Config: `tailwind.config.ts`
- CSS Variables enabled for theming (light/dark mode)
**UI Components:**
- Radix UI (15+ component libraries) - Headless, accessible primitives
- Core: accordion, alert-dialog, avatar, checkbox, dialog, dropdown-menu, label, popover, select, tabs, toast, tooltip, etc.
- shadcn/ui style components (local copies, not npm packages)
- Config: `components.json`
- Aliases: `@/components`, `@/components/ui`, `@/lib`, `@/hooks`
**Form Management:**
- React Hook Form (latest) - Lightweight form state management
- Zod (latest) - Schema validation and type-safe form schemas
- @hookform/resolvers (latest) - Integration between React Hook Form and validation libraries
**Data Visualization:**
- Recharts (latest) - React charting library built on D3.js
- Used for dashboard analytics/metrics visualization
**Notifications & Toasts:**
- Sonner 1.7.1 - Toast notification library
- Radix UI Toast 1.2.4 - Low-level toast primitive (wrapped by use-toast)
**Navigation & Theming:**
- next-themes 0.4.4 - Dark/light mode switching
- lucide-react 0.454.0 - Icon library (240+ icons)
**HTTP Client:**
- Axios 1.9.0 - Promise-based HTTP client
- Configured with request/response interceptors in `lib/api/client.ts`
- Automatic token injection via Authorization header
- 401 error handling (token expiry redirect to /login)
**Utilities:**
- js-cookie 3.0.5 - Cookie management (auth token persistence)
- date-fns 4.1.0 - Date utility library
- class-variance-authority 0.7.1 - Component variant utilities
- clsx 2.1.1 - Conditional className merging
- tailwind-merge 2.5.5 - Tailwind class conflict resolution
- cmdk 1.0.4 - Command palette component
- embla-carousel-react 8.5.1 - Carousel component
- react-day-picker 8.10.1 - Calendar/date picker
- input-otp 1.4.1 - OTP input component
- react-resizable-panels 2.1.7 - Resizable panel layout
- vaul 0.9.6 - Drawer component
## Testing & Development
**Type Checking:**
- TypeScript 5 (strict mode enabled)
- Type checking via `npm run lint`
**Linting:**
- Next.js ESLint (build-time linting)
- Config: ignored during builds (`eslint.ignoreDuringBuilds: true`)
**Development Server:**
- Next.js dev server: `npm run dev` (default port 3000)
## Configuration
**Environment:**
- Location: `.env.local` (local overrides), `.env.development`, `.env.production`, `.env.example`
- Example file: `c:\Users\admin\Desktop\Lila-Server\qy-lty-admin\.env.example`
- Key env vars:
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (e.g., `http://localhost:8000/api`)
- `NODE_ENV` - Set to `production` in Docker runtime
**TypeScript:**
- Config: `tsconfig.json`
- Strict mode: enabled
- Path aliases: `@/*` maps to project root
**Next.js:**
- Config: `next.config.mjs`
- Output mode: `standalone` (self-contained app)
- Images: unoptimized (external image optimization)
- Build optimization: parallel webpack, server build traces, server compilation
## Platform Requirements
**Development:**
- Node.js 22+ (or compatible version)
- Package manager: yarn, npm, or pnpm
- Environment variables: `.env.local` with `NEXT_PUBLIC_API_BASE_URL`
**Production:**
- Docker runtime: Node.js 22.10.0 Alpine
- Memory: Typical SPA requirements (~500MB minimum)
- Port: 3000 (exposed in Dockerfile)
- Build artifacts: `.next/standalone`, `public/`
## Build Process
**Development Build:**
```bash
npm install # Install dependencies
npm run dev # Start dev server (hot reload)
```
**Production Build:**
```bash
npm install # Install all dependencies
npm run build # Next.js standalone build → .next/ directory
npm run start # Start production server (requires .next/ + public/)
```
**Docker Build (CI/CD):**
- Multi-stage build (builder + runner)
- Builder stage: installs full dependencies, runs `npm run build`
- Runner stage: installs production-only dependencies, copies .next/ and public/
- Yarn mirror: Taobao registry (`registry.npmmirror.com`)
- Final CMD: `yarn start` (port 3000)
---
*Stack analysis: 2026-05-07*

View File

@ -0,0 +1,291 @@
# Codebase Structure
**Analysis Date:** 2026-05-07
## Directory Layout
```
qy-lty-admin/
├── app/ # Next.js App Router pages & layouts
│ ├── layout.tsx # Root layout with metadata
│ ├── page.tsx # Dashboard homepage
│ ├── login/page.tsx # Login page
│ ├── register/page.tsx # Registration page
│ ├── forgot-password/page.tsx # Password reset page
│ ├── ai-model/page.tsx # AI Model management
│ ├── outfits/ # Outfit content module
│ │ ├── page.tsx # List view
│ │ ├── [id]/page.tsx # Detail view
│ │ ├── edit/[id]/page.tsx # Edit form
│ │ └── loading.tsx # Loading skeleton
│ ├── props/ # Props module (same structure as outfits)
│ ├── home-decor/ # Home decor module
│ ├── food/ # Food module
│ ├── songs/ # Songs module
│ ├── dances/ # Dances module
│ ├── achievements/ # Achievements module
│ ├── affinity/ # Affinity/favorability module
│ ├── users/ # User management module
│ ├── permissions/ # Permissions management module
│ ├── settings/ # System settings module
│ └── globals.css # Global Tailwind + custom styles
├── components/ # Reusable React components
│ ├── ui/ # Shadcn-style primitive components (copy-paste)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── select.tsx
│ │ ├── form.tsx
│ │ ├── alert-dialog.tsx
│ │ └── ... (30+ primitive components)
│ ├── outfits/ # Outfit feature components
│ │ ├── OutfitsList.tsx
│ │ ├── OutfitsTable.tsx
│ │ ├── AddOutfitDialog.tsx
│ │ └── OutfitDetailCard.tsx
│ ├── songs/ # Songs feature components
│ ├── dances/ # Dances feature components
│ ├── food/ # Food feature components
│ ├── home-decor/ # Home decor feature components
│ ├── props/ # Props feature components
│ ├── affinity/ # Affinity feature components
│ ├── achievements/ # Achievements feature components
│ ├── permissions/ # Permissions feature components
│ ├── users/ # Users feature components
│ ├── sidebar.tsx # Main navigation sidebar (client)
│ ├── dashboard-shell.tsx # Layout wrapper with permission checks (client)
│ ├── dashboard-header.tsx # Page header component
│ ├── overview.tsx # Dashboard overview chart
│ ├── recent-activity.tsx # Recent activity feed
│ ├── stat-card.tsx # KPI stat card
│ ├── delete-confirmation-dialog.tsx
│ ├── publish-confirmation-dialog.tsx
│ ├── theme-provider.tsx
│ └── add-outfit-dialog.tsx # Shared modal for outfit creation
├── lib/ # Utilities, types, API clients
│ ├── api/
│ │ ├── client.ts # Axios instance + interceptors + interfaces
│ │ ├── auth.ts # Login, logout, token management
│ │ ├── outfits.ts # Outfit CRUD operations
│ │ ├── props.ts # Props CRUD operations
│ │ ├── songs.ts # Songs CRUD operations
│ │ ├── dances.ts # Dances CRUD operations
│ │ ├── food.ts # Food CRUD operations
│ │ ├── home-decor.ts # Home decor CRUD operations
│ │ ├── achievements.ts # Achievements CRUD operations
│ │ ├── affinity.ts # Affinity system API
│ │ ├── ai-models.ts # AI model management
│ │ ├── users.ts # User management
│ │ ├── roles.ts # Role/permission management
│ │ ├── error-handler.ts # Centralized error handling
│ │ ├── adapters.ts # Response schema mappers
│ │ ├── types.ts # Shared API type definitions
│ │ ├── token-debug.ts # Token debugging utilities
│ │ ├── card.ts # Card-related API
│ │ └── index.ts # Export barrel
│ ├── permissions.ts # RBAC matrix + permission utilities
│ └── utils.ts # Utility functions (Tailwind cn)
├── hooks/ # Custom React hooks
│ ├── use-mobile.tsx # Mobile breakpoint detection hook
│ └── use-toast.ts # Toast notification hook
├── middleware.ts # Next.js route protection middleware
├── public/ # Static assets
├── styles/ # Additional stylesheets (if any)
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── postcss.config.mjs # PostCSS configuration
├── next.config.mjs # Next.js configuration
├── Dockerfile # Docker build (yarn + Taobao mirror)
├── docker-compose.yml # Local dev Docker Compose
├── README.md # Project overview & permissions matrix
├── CLAUDE.md # Codebase instructions for Claude
└── docs/
└── 修改记录.md # Change log (Chinese)
```
## Directory Purposes
**app/:**
- Purpose: Next.js App Router pages and layouts; each module has a folder with page.tsx + optional nested routes
- Contains: Page components ("use client"), form pages, dynamic routes with [id]
- Key files: `page.tsx` (list), `[id]/page.tsx` (detail), `edit/[id]/page.tsx` (edit form), `loading.tsx` (skeleton)
**components/:**
- Purpose: Reusable React components organized by feature
- Contains: UI primitives (components/ui/), feature-specific components (components/[feature]/), layout components (sidebar, shell)
- Key pattern: Feature folders mirror app module structure; components within are sub-components (no subfolders unless complex)
**components/ui/:**
- Purpose: Shadcn-style copy-paste UI primitives
- Contains: 30+ Base UI components (Button, Card, Dialog, Form, Table, Tabs, Select, etc.)
- Note: These are copied into the repo, not npm packages; safe to modify directly
**lib/api/:**
- Purpose: HTTP client and API communication layer
- Contains: Axios singleton instance, interceptors, module-specific API functions, response adapters, type definitions
- Pattern: One file per backend module (outfits.ts, songs.ts, etc.) + shared client.ts
**lib/:**
- Purpose: Shared utilities, types, configuration
- Contains: permissions.ts (RBAC logic), utils.ts (Tailwind helpers), api/ subfolder
**hooks/:**
- Purpose: Custom React hooks
- Contains: use-mobile (responsive detection), use-toast (notification system)
**middleware.ts:**
- Purpose: Next.js request middleware for route protection
- Functionality: Checks Cookie auth_token; redirects to /login if missing; whitelists public routes
**public/:**
- Purpose: Static assets (images, icons, etc.)
**styles/:**
- Purpose: Additional CSS if needed (most styling via Tailwind in components)
## Key File Locations
**Entry Points:**
- `app/layout.tsx`: Root layout (metadata only)
- `app/page.tsx`: Dashboard homepage (stats, charts, links)
- `app/login/page.tsx`: Authentication entry point
- `middleware.ts`: Route protection middleware
**Configuration:**
- `next.config.mjs`: Next.js config (standalone output)
- `tsconfig.json`: TypeScript strict mode
- `tailwind.config.ts`: Tailwind CSS theme + animations
- `postcss.config.mjs`: PostCSS with Tailwind + autoprefixer
- `.env.local`: Environment variables (API_BASE_URL)
**Core Logic:**
- `lib/permissions.ts`: Role-based access control matrix & utilities
- `lib/api/client.ts`: Axios instance with auth/error interceptors
- `components/dashboard-shell.tsx`: Permission-checking layout wrapper
- `components/sidebar.tsx`: Role-filtered navigation menu
**Authentication:**
- `lib/api/auth.ts`: Login, logout, token persistence
- `middleware.ts`: Route-level token validation
**Testing:**
- No test files detected; testing not yet set up
## Naming Conventions
**Files:**
- **Page components:** `page.tsx` (App Router standard)
- **Feature pages:** `/app/[module]/page.tsx` (e.g., app/outfits/page.tsx)
- **Dynamic routes:** `/app/[module]/[id]/page.tsx` (e.g., app/outfits/[id]/page.tsx)
- **Layout files:** `layout.tsx`
- **Loading states:** `loading.tsx`
- **API modules:** `/lib/api/[feature].ts` (e.g., lib/api/outfits.ts)
- **Component files:** PascalCase (e.g., OutfitsList.tsx, AddOutfitDialog.tsx)
- **Hook files:** `use-[name].ts` (e.g., use-mobile.tsx)
**Directories:**
- **App modules:** kebab-case (e.g., `/app/ai-model`, `/app/home-decor`, `/app/forgot-password`)
- **Component folders:** kebab-case for feature groups, `ui` for primitives
- **API folder:** All in `lib/api/` (no nested folders)
**Functions:**
- **API functions:** camelCase, verb-first (e.g., `getOutfits()`, `createOutfit()`, `updateOutfit()`)
- **Utility functions:** camelCase (e.g., `hasPermission()`, `getUserRole()`)
- **Components:** PascalCase (e.g., `DashboardShell`, `Sidebar`, `OutfitsList`)
- **Hooks:** camelCase with `use` prefix (e.g., `useMobile`, `useToast`)
**Variables/Constants:**
- **Type definitions:** PascalCase (e.g., `Outfit`, `RoleName`, `ApiResponse`)
- **Constants:** UPPER_SNAKE_CASE (e.g., `API_BASE_URL`, `PERMISSION_MATRIX`)
- **Local variables:** camelCase (e.g., `outfits`, `isLoading`, `userRole`)
**Routes:**
- **Module routes:** kebab-case URLs (e.g., `/ai-model`, `/home-decor`, `/forgot-password`)
- **Dynamic routes:** `[paramName]` (e.g., `/outfits/[id]`, `/dances/[id]/edit`)
## Where to Add New Code
**New Feature Module (e.g., New Content Type):**
1. **Page component:** Create `app/[module-name]/page.tsx`
- Wrap with DashboardShell
- Import feature components from `components/[module-name]/`
- Call API functions from `lib/api/[module-name].ts`
2. **API client:** Create `lib/api/[module-name].ts`
- Export `get[Feature]s()`, `get[Feature]()`, `create[Feature]()`, `update[Feature]()`, `delete[Feature]()`
- Use `apiClient.get/post/put/delete()` from `lib/api/client.ts`
- Include adapter function to map backend response to frontend type
- Export types or import from `lib/api/types.ts`
3. **Components:** Create `components/[module-name]/` folder
- List component (e.g., `[Feature]List.tsx`)
- Detail/edit component
- Dialog/modal for creation
- Table component if displaying tabular data
4. **Permission matrix:** Add to `lib/permissions.ts`
- Add module key to `PermissionModule` type
- Add module to relevant roles in `PERMISSION_MATRIX`
- Add path mapping in `getModuleFromPath()`
5. **Sidebar:** Update `components/sidebar.tsx`
- Add menu item to appropriate section (AI Admin, Content Admin, or System Admin)
- Use lucide-react icon
6. **Middleware:** Update `middleware.ts`
- Add route to `protectedPaths` array if requiring auth
**New Component:**
- Location: `components/[feature-name]/[ComponentName].tsx`
- Import from UI primitives: `components/ui/button.tsx`, etc.
- Use React Hook Form for forms
- Use icons from lucide-react
- Mark as "use client" if using hooks/state
**New Utility Function:**
- Location: `lib/utils.ts` (for generic utilities) or `lib/api/[module].ts` (for API helpers)
- Type everything with TypeScript
- Export from `lib/api/index.ts` if API-related
**New Hook:**
- Location: `hooks/use-[name].ts`
- Follow React hook naming convention (use- prefix)
## Special Directories
**components/ui/:**
- Purpose: Shadcn-style primitives (Button, Card, Dialog, Form, Input, etc.)
- Generated: No (manually copied from shadcn/ui)
- Committed: Yes (safe to modify without breaking npm upgrades)
- Note: Each file is a single component; complex components like Dialog may have sub-exports
**public/:**
- Purpose: Static assets served at root level
- Generated: No
- Committed: Yes
**node_modules/:**
- Purpose: Installed dependencies
- Generated: Yes (by npm/pnpm/yarn)
- Committed: No (.gitignore)
**.next/:**
- Purpose: Next.js build output
- Generated: Yes (by `npm run build`)
- Committed: No (.gitignore)
**lib/api/:**
- Purpose: All API communication in one folder for easy discovery
- Generated: No (hand-written)
- Committed: Yes
- Note: No subfolders; keep flat for single-module-per-file pattern
---
*Structure analysis: 2026-05-07*

View File

@ -0,0 +1,279 @@
# 测试模式
**分析日期:** 2026-05-07
## 测试框架
**当前状态:** 未检测到测试基础设施
- **运行器:** 未配置package.json 中无 Jest、Vitest、Playwright 等依赖)
- **断言库:** 未配置
- **测试脚本:** 未在 package.json 中定义(仅有 `dev``build``start``lint`
## 测试文件组织
**当前:** 无测试文件
项目中未发现 `*.test.ts``*.test.tsx``*.spec.ts``*.spec.tsx``__tests__/` 目录等标准测试文件结构。
## 测试覆盖
**要求:** 未强制
**影响:**
- 新增代码(尤其是 API 集成、表单验证、权限逻辑)缺乏自动化验证
- 技术债风险:`next.config.mjs``typescript.ignoreBuildErrors: true``eslint.ignoreDuringBuilds: true`,导致构建时不检查类型和 lint 错误
- 手动测试依赖性高
## 关键需要测试的区域
### API 集成层
**位置:** `lib/api/*.ts`
**建议测试:**
- 认证流程(`emailLogin``saveAuthToken``logout`
- API 请求拦截器token 注入、过期处理)
- 响应拦截器401 处理、重定向)
- 数据适配器(如 `mapBackendOutfit()` 将后端结构映射到前端类型)
- 错误处理(`handleApiException``handleApiRequest` 的成功/失败路径)
**示例测试需求:**
```typescript
// 应测试token 自动注入
test('请求拦截器应在请求头中添加 Bearer token', () => {
// 前置条件localStorage 中有 auth_token
// 发送 API 请求
// 验证Authorization 头包含 "Bearer <token>"
})
// 应测试401 处理
test('响应拦截器应在收到 401 时清除 token 并重定向到登录', () => {
// 前置条件:发送请求后服务器返回 401
// 验证localStorage 中 auth_token 被移除
// 验证:浏览器重定向到 /login
})
// 应测试:数据适配
test('mapBackendOutfit 应正确转换后端服装数据', () => {
const backendData = { id: 1, name: '测试服装', image_url: '...' }
const result = mapBackendOutfit(backendData)
expect(result).toHaveProperty('imageUrl') // 字段映射
expect(result.id).toBe('1') // 类型转换
})
```
### 权限控制
**位置:** `lib/permissions.ts`
**建议测试:**
- `hasPermission(module)` 根据用户角色返回正确的权限
- `getUserRole()` 从 localStorage 获取当前角色
- `getModuleFromPath(pathname)` 正确提取路径中的模块
- `hasPathPermission(pathname)` 组合权限检查
**示例测试需求:**
```typescript
test('超级管理员应有所有模块的权限', () => {
// 前置localStorage.user_role = '超级管理员'
// 验证hasPermission('users') === true
// 验证hasPermission('ai-model') === true
// 等等所有模块
})
test('内容管理员应无权限访问用户管理', () => {
// 前置localStorage.user_role = '内容管理员'
// 验证hasPermission('users') === false
// 验证hasPermission('outfits') === true
})
```
### 表单验证与提交
**位置:** `components/*/add-*-dialog.tsx``app/*/page.tsx`
**当前模式:**
- 使用 `useState` 管理表单状态
- 基本的非空检查(如 `if (!formData.name || !formData.description)``add-achievement-dialog.tsx`
- 客户端验证不完整
**建议测试:**
- 必填字段验证(空值拒绝)
- 表单提交成功调用正确的 API
- 提交过程中加载状态变化(`isSubmitting`
- 表单重置(关闭后清空状态)
- 错误显示API 失败时显示 toast
**示例测试需求:**
```typescript
test('添加服装对话框应在名称为空时禁用提交', () => {
// 渲染 AddOutfitDialog
// 验证:提交按钮禁用状态
})
test('提交表单应调用 createOutfit API', () => {
// 填写表单字段
// 点击提交
// 验证createOutfit 被调用,参数正确
})
```
### 组件交互
**位置:** `components/`
**建议测试:**
- 对话框打开/关闭状态
- 模态框表单的步骤导航(`step` 状态变化)
- 确认对话框的确认/取消操作
- 列表分页和搜索
**示例测试需求:**
```typescript
test('AddOutfitDialog 应在提交后关闭', () => {
// 渲染对话框
// 填写并提交表单
// 验证对话框关闭open === false
})
test('多步表单应在点击"下一步"时更新步骤', () => {
// 验证:初始 step === 1
// 点击"下一步"按钮
// 验证step === 2
})
```
### 中间件和路由保护
**位置:** `middleware.ts`
**建议测试:**
- 无 token 访问受保护路由时重定向到 `/login`
- 有 token 时允许访问受保护路由
- 公共路由(登录、注册)不需要 token
**示例测试需求:**
```typescript
test('中间件应将无 token 的请求重定向到登录', () => {
// 模拟请求GET /outfits无 auth_token cookie
// 验证:重定向到 /login?callbackUrl=/outfits
})
test('中间件应允许有 token 的请求', () => {
// 模拟请求GET /users包含有效 auth_token cookie
// 验证:通过,继续处理
})
```
## 建议的测试设置
### 推荐框架组合
**单元/集成测试:**
- **运行器:** Vitest与 Next.js 15 兼容,开发体验优于 Jest
- **断言:** Vitest 内置(或 @testing-library/jest-dom
- **Mock 库:** vitest 的 `vi` 模块或 `@testing-library/react`
**E2E 测试(可选):**
- **框架:** Playwright 或 Cypress
- **用途:** 完整登录流、权限控制、多步表单
### 安装建议
```bash
# 安装 Vitest 和相关工具
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom
# 创建配置文件 vitest.config.ts
```
### 最小化配置示例
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./test/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
})
// test/setup.ts
import '@testing-library/jest-dom'
```
### package.json 脚本建议
```json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:run": "vitest run"
}
}
```
## 测试文件结构建议
```
project-root/
├── lib/
│ ├── api/
│ │ ├── auth.ts
│ │ ├── auth.test.ts # API 认证测试
│ │ ├── outfits.ts
│ │ ├── outfits.test.ts # API 服装测试
│ │ └── ...
│ ├── permissions.ts
│ └── permissions.test.ts # 权限逻辑测试
├── components/
│ ├── add-outfit-dialog.tsx
│ ├── add-outfit-dialog.test.tsx # 组件交互测试
│ └── ...
├── app/
│ ├── login/
│ │ ├── page.tsx
│ │ └── page.test.tsx # 登录页测试
│ └── ...
├── test/
│ ├── setup.ts # 测试环境初始化
│ ├── mocks/
│ │ ├── handlers.ts # MSW 请求拦截器
│ │ └── server.ts # MSW 服务器配置
│ └── fixtures/
│ ├── auth.fixture.ts # 认证数据 fixtures
│ └── outfits.fixture.ts # 服装数据 fixtures
└── vitest.config.ts
```
## 当前缺失的覆盖范围
**高优先级(生产关键):**
- ✗ API 集成和数据适配
- ✗ 认证流程登录、token 管理、登出)
- ✗ 权限检查和路由保护
- ✗ 表单验证和提交
**中优先级(功能完整性):**
- ✗ 错误处理和用户通知
- ✗ 组件交互(对话框、模态框)
- ✗ 列表管理(分页、搜索)
**低优先级UX 细节):**
- ✗ 样式和响应性
- ✗ 无障碍访问 (a11y)
---
*测试分析日期2026-05-07*
**关键建议:** 该项目缺乏测试基础设施强烈建议在新增核心功能特别是认证、权限、API 集成)时同步添加测试。考虑从 API 层和权限控制开始,这些是最容易产生 bug 的关键区域。