docs: create roadmap (2 phases)

This commit is contained in:
win 2026-03-21 16:37:24 +08:00
parent 4f3b9b8fa7
commit e3b0ab7cca
4 changed files with 414 additions and 26 deletions

View File

@ -74,35 +74,35 @@ Deferred to future release.
| Requirement | Phase | Status |
|-------------|-------|--------|
| PNL-01 | | Pending |
| PNL-02 | | Pending |
| PNL-03 | | Pending |
| PNL-04 | | Pending |
| PNL-05 | | Pending |
| PNL-06 | | Pending |
| PNL-07 | | Pending |
| PNL-08 | | Pending |
| DIM-01 | | Pending |
| DIM-02 | | Pending |
| DIM-03 | | Pending |
| DIM-04 | | Pending |
| RET-01 | | Pending |
| RET-02 | | Pending |
| RET-03 | | Pending |
| AST-01 | | Pending |
| AST-02 | | Pending |
| AST-03 | | Pending |
| QUA-01 | | Pending |
| QUA-02 | | Pending |
| QUA-03 | | Pending |
| QUA-04 | | Pending |
| QUA-05 | | Pending |
| PNL-01 | Phase 1 | Pending |
| PNL-02 | Phase 1 | Pending |
| PNL-03 | Phase 1 | Pending |
| PNL-04 | Phase 1 | Pending |
| PNL-05 | Phase 1 | Pending |
| PNL-06 | Phase 1 | Pending |
| PNL-07 | Phase 1 | Pending |
| PNL-08 | Phase 1 | Pending |
| DIM-01 | Phase 1 | Pending |
| DIM-02 | Phase 1 | Pending |
| DIM-03 | Phase 1 | Pending |
| DIM-04 | Phase 1 | Pending |
| RET-01 | Phase 1 | Pending |
| RET-02 | Phase 2 | Pending |
| RET-03 | Phase 1 | Pending |
| AST-01 | Phase 1 | Pending |
| AST-02 | Phase 2 | Pending |
| AST-03 | Phase 2 | Pending |
| QUA-01 | Phase 1 | Pending |
| QUA-02 | Phase 1 | Pending |
| QUA-03 | Phase 1 | Pending |
| QUA-04 | Phase 1 | Pending |
| QUA-05 | Phase 1 | Pending |
**Coverage:**
- v1 requirements: 23 total
- Mapped to phases: 0
- Unmapped: 23 ⚠️
- Mapped to phases: 23
- Unmapped: 0 ✓
---
*Requirements defined: 2026-03-21*
*Last updated: 2026-03-21 after initial definition*
*Last updated: 2026-03-21 after roadmap creation*

50
.planning/ROADMAP.md Normal file
View File

@ -0,0 +1,50 @@
# Roadmap: Bindbox Game 盈亏统计函数
## Overview
Two reusable service-layer functions — `QueryUserProfitLoss` and `QueryActivityProfitLoss` — are built in a new `internal/service/finance/` package. Phase 1 delivers correct, working functions covering the full P&L computation path. Phase 2 populates the per-asset-type breakdown slice, including Fragment synthesis cost. The result is a clean, testable service that callers can invoke without touching HTTP handler logic.
## Phases
**Phase Numbering:**
- Integer phases (1, 2, 3): Planned milestone work
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
Decimal phases appear between their surrounding integers in numeric order.
- [ ] **Phase 1: Core P&L Functions** - Scaffold the finance package and deliver working QueryUserProfitLoss / QueryActivityProfitLoss with correct revenue, cost, and profit
- [ ] **Phase 2: Per-Asset-Type Breakdown** - Populate the ProfitLossBreakdown slice for all 5 asset types, including Fragment synthesis cost from its own table
## Phase Details
### Phase 1: Core P&L Functions
**Goal**: Callers can invoke QueryUserProfitLoss and QueryActivityProfitLoss and receive a correct ProfitLossResult with total revenue, cost, profit, and profit rate — with all edge cases handled
**Depends on**: Nothing (first phase)
**Requirements**: PNL-01, PNL-02, PNL-03, PNL-04, PNL-05, PNL-06, PNL-07, PNL-08, DIM-01, DIM-02, DIM-03, DIM-04, RET-01, RET-03, AST-01, QUA-01, QUA-02, QUA-03, QUA-04, QUA-05
**Success Criteria** (what must be TRUE):
1. Calling QueryUserProfitLoss with a list of user IDs returns a ProfitLossResult where Revenue equals actual_amount + discount_amount for non-refunded orders only, and game-pass orders contribute draw_count × activity_price instead of cash revenue
2. Calling QueryActivityProfitLoss with an activity ID returns a ProfitLossResult where Revenue is proportionally attributed per order (no double-counting when one order covers multiple activities)
3. Both functions return an error (not silent zero) when any db.Scan() call fails
4. Passing nil for StartTime/EndTime applies no time filter; passing nil/0 for AssetType returns aggregated totals across all asset types
5. The package contains no call to GetDbW() — all queries route through the injected DbR handle; voided inventory (remark LIKE '%void%' or status=2) and refunded orders (status=3,4) are excluded from cost and revenue respectively
**Plans**: TBD
### Phase 2: Per-Asset-Type Breakdown
**Goal**: The ProfitLossBreakdown slice in every ProfitLossResult contains one entry per relevant asset type (Points, Coupon, ItemCard, Product, Fragment), with correct per-type cost including Fragment synthesis cost sourced from fragment_synthesis_logs
**Depends on**: Phase 1
**Requirements**: AST-02, AST-03, RET-02
**Success Criteria** (what must be TRUE):
1. A ProfitLossResult for an activity that awarded Points, Coupons, and Items contains exactly one ProfitLossBreakdown entry per asset type that had non-zero cost, with the correct cost value for each
2. Fragment cost in the breakdown is sourced from fragment_synthesis_logs using the verified join path (not from user_inventory), and matches manual spot-checks against the database
3. Summing all breakdown entries' Cost fields equals the total Cost field in the parent ProfitLossResult (no gaps or double-counting between breakdown and totals)
**Plans**: TBD
## Progress
**Execution Order:**
Phases execute in numeric order: 1 → 2
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Core P&L Functions | 0/TBD | Not started | - |
| 2. Per-Asset-Type Breakdown | 0/TBD | Not started | - |

63
.planning/STATE.md Normal file
View File

@ -0,0 +1,63 @@
# Project State
## Project Reference
See: .planning/PROJECT.md (updated 2026-03-21)
**Core value:** 提供可复用的盈亏统计方法,使平台运营能从用户和活动两个维度快速了解各类资产的收支状况
**Current focus:** Phase 1 — Core P&L Functions
## Current Position
Phase: 1 of 2 (Core P&L Functions)
Plan: 0 of TBD in current phase
Status: Ready to plan
Last activity: 2026-03-21 — Roadmap created, ready to begin Phase 1 planning
Progress: [░░░░░░░░░░] 0%
## Performance Metrics
**Velocity:**
- Total plans completed: 0
- Average duration: — min
- Total execution time: 0 hours
**By Phase:**
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
| - | - | - | - |
**Recent Trend:**
- Last 5 plans: —
- Trend: —
*Updated after each plan completion*
## Accumulated Context
### Decisions
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- Init: Fan-out + in-memory merge query pattern chosen (avoids Cartesian product JOINs)
- Init: Read-only DB routing enforced — constructor injects DbR only, no GetDbW() in package
- Init: All existing finance.* utilities (IsGamePassOrder, ComputeProfit, etc.) must be reused, not re-derived
- Init: Phase 2 (Fragment breakdown) requires schema verification of fragment_synthesis_logs join path before implementation
### Pending Todos
None yet.
### Blockers/Concerns
- Phase 2: fragment_synthesis_logs join path and cost formula not yet verified — requires schema review during Phase 2 planning
- Phase 1: SQLite test compatibility for CAST(AS SIGNED), GREATEST(), LIKE 'GP%' — must use Go-layer helpers or conditional SQL paths in integration tests
## Session Continuity
Last session: 2026-03-21
Stopped at: Roadmap created — ROADMAP.md and STATE.md written, REQUIREMENTS.md traceability updated
Resume file: None

275
CLAUDE.md
View File

@ -289,3 +289,278 @@ Services should contain business logic and call DAOs for data access. Keep handl
- **GORM generation fails**: Check database connectivity and ensure `cmd/gormgen/main.go` has correct DB credentials
- **Frontend build errors**: Clear node_modules and reinstall: `cd web/admin && rm -rf node_modules && pnpm install`
- **JWT token issues**: If admin tokens are invalid, check `ADMIN_JWT_SECRET` environment variable matches config
<!-- GSD:project-start source:PROJECT.md -->
## Project
**Bindbox Game 盈亏统计函数**
为 Bindbox Game 平台新增两个 Service 层通用盈亏统计函数,支持按用户维度和活动维度查询平台盈亏情况。函数接收资产类型、维度 ID、时间范围等参数返回汇总数据和按资产类型拆分的明细。
**Core Value:** 提供可复用的盈亏统计方法,使平台运营能从用户和活动两个维度快速了解各类资产的收支状况。
### Constraints
- **Tech Stack**: Go, GORM, MySQL — 遵循现有项目架构
- **Performance**: 统计查询走从库 (DbR),避免影响写库性能
- **Compatibility**: 新函数放在 `internal/service/finance/` 下,不修改现有接口
<!-- GSD:project-end -->
<!-- GSD:stack-start source:codebase/STACK.md -->
## Technology Stack
## Languages
- Go 1.24.0 - Backend server, all business logic, API handlers
- TypeScript ~5.6.3 - Frontend admin panel (`web/admin/src/`)
- SQL - Database migrations (`migrations/` directory)
- TOML - Configuration files (`configs/*.toml`)
- SCSS - Frontend styles (`web/admin/src/assets/styles/`)
## Runtime
- Go runtime 1.24.0 (toolchain go1.24.2)
- Docker: `golang:1.24-alpine` build stage, `alpine:latest` final stage
- Node.js >= 18.0.0
- Go modules (`go.mod` / `go.sum`) - lockfile present
- pnpm >= 8.8.0 - frontend (`web/admin/pnpm-lock.yaml`) - lockfile present
## Frameworks
- `github.com/gin-gonic/gin v1.9.1` - HTTP web framework
- `gorm.io/gorm v1.25.9` - ORM for MySQL
- `gorm.io/gen v0.3.26` - GORM code generation from schema
- `gorm.io/plugin/dbresolver v1.5.0` - Read/write split support
- Vue 3 `^3.5.21` - UI framework (`web/admin/src/`)
- Vite `^5.4.10` - Build tool and dev server
- Element Plus `^2.11.2` - UI component library
- Pinia `^3.0.3` - State management
- Vue Router `^4.5.1` - Client-side routing
- Tailwind CSS `^4.1.14` - Utility-first CSS
- `github.com/stretchr/testify v1.11.1` - Assertions
- `github.com/DATA-DOG/go-sqlmock v1.5.2` - MySQL mock
- `github.com/alicebob/miniredis/v2 v2.36.1` - In-memory Redis for tests
- `gorm.io/driver/sqlite v1.4.3` - SQLite for in-memory test DB (`internal/repository/mysql/testrepo_sqlite.go`)
- Vitest `^1.0.0` - Unit test runner
- `@vue/test-utils ^2.4.0` - Vue component testing
- Makefile - Task runner (`Makefile`)
- `golangci-lint` - Linter (install via `make tools`)
- `go-swagger` - Swagger generation (install via `make tools`)
- `cmd/mfmt/main.go` - Custom import formatter (groups: stdlib, local, third-party)
- `cmd/gormgen/main.go` - GORM model/DAO code generator
- ESLint `^9.9.1` + TypeScript ESLint `^8.3.0` - Linting
- Prettier `^3.5.3` - Code formatting
- Stylelint `^16.20.0` - CSS/SCSS linting
- Husky `^9.1.5` + lint-staged - Pre-commit hooks
- Terser `^5.36.0` - Minification
- `vite-plugin-compression ^0.5.1` - Gzip compression for production
## Key Dependencies
- `github.com/spf13/viper v1.17.0` - Configuration management (TOML, env var overrides)
- `go.uber.org/zap v1.26.0` - Structured logging
- `gopkg.in/natefinch/lumberjack.v2 v2.2.1` - Log file rotation
- `github.com/golang-jwt/jwt/v5 v5.2.0` - JWT auth tokens
- `github.com/redis/go-redis/v9 v9.17.2` - Redis client (singleton)
- `github.com/go-sql-driver/mysql v1.7.1` - MySQL driver
- `github.com/bytedance/sonic v1.13.2` - High-performance JSON encoder/decoder
- `github.com/bwmarrin/snowflake v0.3.0` - Distributed ID generation
- `github.com/go-resty/resty/v2 v2.10.0` - HTTP client for external API calls
- `github.com/prometheus/client_golang v1.17.0` - Prometheus metrics
- `golang.org/x/crypto v0.44.0` - Cryptographic utilities
- Axios `^1.12.2` - HTTP client for API calls
- Echarts `^6.0.0` - Charts and data visualization
- `@vueuse/core ^13.9.0` - Vue composition utilities
- `pinia-plugin-persistedstate ^4.3.0` - Persistent state storage
- `dayjs ^1.11.19` - Date/time manipulation
- `crypto-js ^4.2.0` - Client-side cryptography
- `xlsx ^0.18.5` - Excel file generation/parsing
- `@wangeditor/editor ^5.1.23` - Rich text editor
- `go.opentelemetry.io/otel v1.39.0` - Distributed tracing (OTLP HTTP exporter)
- `github.com/gin-contrib/pprof v1.4.0` - Go profiling endpoint
- `github.com/swaggo/gin-swagger v1.6.0` - Swagger UI embedded in Gin
- `github.com/tealeg/xlsx v1.0.5` - Excel file generation (server-side)
- `github.com/rs/cors/wrapper/gin v0.0.0-20231013084403-73f81b45a644` - CORS middleware
## Configuration
- Set via `ENV` environment variable: `dev` | `fat` | `uat` | `pro` (default: `fat`)
- Config files embedded into binary at build time via `//go:embed` directives
- Config files: `configs/dev_configs.toml`, `configs/fat_configs.toml`, `configs/uat_configs.toml`, `configs/pro_configs.toml`
- TOML format parsed via Viper (`github.com/spf13/viper`)
- `MYSQL_ADDR`, `MYSQL_READ_ADDR`, `MYSQL_WRITE_ADDR`, `MYSQL_USER`, `MYSQL_PASS`, `MYSQL_NAME`
- `REDIS_ADDR`, `REDIS_PASS`
- `WECHAT_MCHID`, `WECHAT_SERIAL_NO`, `WECHAT_PRIVATE_KEY_PATH`, `WECHAT_API_V3_KEY`, `WECHAT_NOTIFY_URL`, `WECHAT_PUBLIC_KEY_ID`, `WECHAT_PUBLIC_KEY_PATH`
- `ALIYUN_SMS_ACCESS_KEY_ID`, `ALIYUN_SMS_ACCESS_KEY_SECRET`, `ALIYUN_SMS_SIGN_NAME`, `ALIYUN_SMS_TEMPLATE_CODE`
- `ADMIN_JWT_SECRET` - Admin JWT signing secret override
- Vite env vars: `VITE_VERSION`, `VITE_PORT`, `VITE_BASE_URL`, `VITE_API_URL`, `VITE_API_PROXY_URL`
- Dev proxy: `/api` requests forwarded to `VITE_API_PROXY_URL`
- Backend: `Dockerfile` (multi-stage, `golang:1.24-alpine``alpine:latest`)
- Server port: `9991` (constant in `configs/constants.go`)
- Container exposes port `9991`
## Platform Requirements
- Go 1.24+
- Node.js >= 18.0.0, pnpm >= 8.8.0
- MySQL instance (read/write addresses)
- Redis instance
- `golangci-lint` and `go-swagger` for linting/docs
- Docker (Linux/amd64 binary, CGO_ENABLED=0)
- Alpine Linux container
- MySQL with optional read replica (master-slave)
- Redis single-node
- Optional: OpenTelemetry-compatible collector (Tempo) at configured OTLP endpoint
<!-- GSD:stack-end -->
<!-- GSD:conventions-start source:CONVENTIONS.md -->
## Conventions
## Naming Patterns
- snake_case for all Go source files: `activity_order_service.go`, `draw_config_save.go`
- Test files co-located with source: `reward_snapshot_test.go` next to `rewards_create.go`
- Generated files suffixed with `.gen.go`: never edited manually
- Package names match directory name: `package activity` in `internal/service/activity/`
- kebab-case for TypeScript API files: `pay-orders.ts`, `order-snapshots.ts`
- kebab-case for view directories: `player-manage/`, `shipping-orders/`
- PascalCase for Vue component filenames where applicable
- PascalCase for exported: `NewActivityOrderService`, `CreateActivityOrder`, `ListProductsForApp`
- camelCase for unexported: `newRewardSnapshotTestService`, `shouldTriggerInstantDraw`, `assertAttribution`
- Constructor functions named `New<Type>` for service constructors: `NewProduct(...)`, `NewStore(...)`
- Handler methods return `core.HandlerFunc` (closure pattern): `func (h *productHandler) ListProductsForApp() core.HandlerFunc`
- `fetch` prefix for API functions: `fetchGetActivities`, `fetchGetActivityDetail`
- camelCase for all functions
- camelCase in Go: `userID`, `activityID`, `testLogger`
- Named ID variables use int64 type consistently: `userID int64`, `activityID int64`
- PascalCase for exported: `CreateActivityOrderRequest`, `ActivityOrderService`
- Unexported structs for implementation: `activityOrderService`, `productHandler`, `context`
- Request structs named `<verb><Domain>Request`: `listAppProductsRequest`, `CreateActivityOrderRequest`
- Response structs named `<verb><Domain>Response`: `listAppProductsResponse`, `getAppProductDetailResponse`
- Interface types use verb-noun: `ActivityOrderService`, `Service`, `Repo`
- 5-digit pattern: service level (1) + module level (2) + specific error (2)
- All-caps with CamelCase words: `ServerError = 10101`, `ParamBindError = 10102`
- Grouped by domain in `internal/code/code.go`
## Code Style
- `gofmt -s` via `make fmt` (uses standard gofmt)
- Import grouping via `go run cmd/mfmt/main.go`: stdlib → local module (`bindbox-game/...`) → third-party
- Line length not strictly enforced but long lines occur in handler code
- `golangci-lint run -D staticcheck` via `make lint`
- staticcheck disabled; other default golangci-lint checks active
- Prettier for all file types, configured via lint-staged hooks
- ESLint with `eslint-plugin-prettier/recommended`
- Single quotes enforced: `quotes: ['error', 'single']`
- No semicolons: `semi: ['error', 'never']`
- No `var`: `'no-var': 'error'` — use `let` or `const`
- `@typescript-eslint/no-explicit-any` disabled (any is allowed)
- Vue multi-word component name rule disabled
## Import Organization
- `import request from '@/utils/http'`
- `import { getActivityDetail } from './adminActivities'` (relative for same-level)
## Error Handling
## Logging
- Logger injected into handlers and services via constructor
- Handler structs hold `logger logger.CustomLogger` field
- Service structs hold `logger logger.CustomLogger` field
- Use structured fields: `zap.Field` variadic args
- Exported methods: `Info`, `Error`, `Warn`, `Debug`
- In tests, use `logger.NewCustomLogger(nil, logger.WithOutputInConsole())`
## Comments
## Function Design
## Module Design
- `internal/api/` → handlers only, thin, call services
- `internal/service/` → business logic, call DAOs and other services
- `internal/repository/mysql/` → data access via GORM DAOs (generated)
- `internal/pkg/` → shared utilities, no business logic
<!-- GSD:conventions-end -->
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
## Architecture
## Overview
## Architectural Pattern
```
```
## Key Layers
### 1. Router Layer (`internal/router/`)
- `router.go` — Single file defining all routes via `NewHTTPMux()`
- Routes organized into groups:
### 2. Interceptor / Middleware Layer (`internal/router/interceptor/`)
- `admin_auth.go` — JWT token verification for admin users
- `admin_rbac.go` — Role-based access control with action-level permissions
- `app_auth.go` — App user token verification
- `blacklist.go` — Douyin user blacklist checking
- `interceptor.go` — Base interceptor struct with shared dependencies
### 3. API Handler Layer (`internal/api/`)
- `admin/` — Admin panel handlers (largest, ~30+ files)
- `activity/` — Lottery/game activity handlers
- `app/` — Store, product, banner, category handlers
- `game/` — Game ticket and minesweeper handlers
- `pay/` — Payment handlers
- `user/` — User management, orders, addresses
- `task_center/` — Task center handlers
- `common/` — Shared utilities (upload, openid)
- `public/` — Public livestream handlers
- `internal/` — Internal API handlers (Nakama integration)
### 4. Service Layer (`internal/service/`)
- `activity/` — Activity CRUD, lottery processing, matching game, settlements, strategy pattern for draw types
- `admin/` — Admin user management, login
- `user/` — User management, orders, points, coupons, inventory, shipping, synthesis
- `order/` — Order processing
- `game/` — Game ticket management, minesweeper
- `douyin/` — Douyin order sync, reward dispatching
- `task_center/` — Task definitions, progress tracking, worker
- `product/` — Product management
- `finance/` — Financial operations, ledger
- `channel/` — Marketing channel management
- `title/` — User title/badge system
- `banner/`, `sysconfig/`, `common/`, `snapshot/`, `recycle/`, `synthesis/`, `livestream/`
### 5. Repository Layer (`internal/repository/mysql/`)
- `mysql.go` — Database connection management (read/write split via `Repo` interface)
- `plugin.go` — GORM plugins
- `model/*.gen.go` — Generated GORM models (do not edit)
- `dao/*.gen.go` — Generated GORM DAOs (do not edit)
- `task_center/models.go` — Task center specific models
- `test_helper.go`, `testrepo_sqlite.go` — Test infrastructure
## Entry Point
## Data Flow
### Typical API Request Flow
```
```
### Background Task Flow
```
```
### Payment Flow
```
```
## Key Design Decisions
| Decision | Rationale |
|----------|-----------|
| Read/write DB split | Performance: heavy reads go to slave, writes to master |
| GORM code generation | Consistency: models and DAOs auto-generated from schema |
| Custom `core.Context` wrapper | Standardized error handling, tracing, session management across all handlers |
| Strategy pattern for lottery | Different draw types (standard, ichiban) share interface but have different logic |
| Background workers in main process | Simplicity: no separate worker binary, uses goroutines |
| JWT with hash verification | Security: stored token hash prevents concurrent sessions |
## Cross-Cutting Concerns
- **Logging**: Zap-based with file rotation (`internal/pkg/logger/`)
- **Tracing**: OpenTelemetry integration (`internal/pkg/otel/`)
- **Error Codes**: 5-digit system in `internal/code/` (service level + module + specific)
- **Alerts**: Alert notification system (`internal/alert/`)
- **Metrics**: Prometheus metrics (`internal/metrics/`)
## External Service Boundaries
- WeChat Mini Program API (`wechat/`, `miniprogram/`)
- WeChat Pay v3 API (`pay/`)
- Douyin/TikTok API (`douyin/`)
- Aliyun SMS (`sms/`)
- Tencent COS (object storage)
- OpenTelemetry collector
<!-- GSD:architecture-end -->
<!-- GSD:workflow-start source:GSD defaults -->
## GSD Workflow Enforcement
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks
- `/gsd:debug` for investigation and bug fixing
- `/gsd:execute-phase` for planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
<!-- GSD:workflow-end -->
<!-- GSD:profile-start -->
## Developer Profile
> Profile not yet configured. Run `/gsd:profile-user` to generate your developer profile.
> This section is managed by `generate-claude-profile` -- do not edit manually.
<!-- GSD:profile-end -->