diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 6754fe7..c1c10e5 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -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* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md new file mode 100644 index 0000000..ce2854b --- /dev/null +++ b/.planning/ROADMAP.md @@ -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 | - | diff --git a/.planning/STATE.md b/.planning/STATE.md new file mode 100644 index 0000000..a80cf7f --- /dev/null +++ b/.planning/STATE.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index f4da708..cb8b7c6 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 + + +## Project + +**Bindbox Game 盈亏统计函数** + +为 Bindbox Game 平台新增两个 Service 层通用盈亏统计函数,支持按用户维度和活动维度查询平台盈亏情况。函数接收资产类型、维度 ID、时间范围等参数,返回汇总数据和按资产类型拆分的明细。 + +**Core Value:** 提供可复用的盈亏统计方法,使平台运营能从用户和活动两个维度快速了解各类资产的收支状况。 + +### Constraints + +- **Tech Stack**: Go, GORM, MySQL — 遵循现有项目架构 +- **Performance**: 统计查询走从库 (DbR),避免影响写库性能 +- **Compatibility**: 新函数放在 `internal/service/finance/` 下,不修改现有接口 + + + +## 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 + + + +## 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` 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 `Request`: `listAppProductsRequest`, `CreateActivityOrderRequest` +- Response structs named `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 + + + +## 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 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. + + + +## 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. +