# Coding Conventions **Analysis Date:** 2026-03-21 ## Naming Patterns **Files (Go backend):** - 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/` **Files (Vue frontend):** - 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 **Functions (Go):** - 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` **Functions (TypeScript frontend):** - `fetch` prefix for API functions: `fetchGetActivities`, `fetchGetActivityDetail` - camelCase for all functions **Variables:** - camelCase in Go: `userID`, `activityID`, `testLogger` - Named ID variables use int64 type consistently: `userID int64`, `activityID int64` **Types/Structs (Go):** - 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` **Constants (Go error codes):** - 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 **Formatting (Go):** - `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 **Linting (Go):** - `golangci-lint run -D staticcheck` via `make lint` - staticcheck disabled; other default golangci-lint checks active **Formatting (Frontend):** - 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 **Go — Three groups (enforced by `cmd/mfmt/main.go`):** 1. Standard library: `"context"`, `"net/http"`, `"testing"` 2. Local module: `"bindbox-game/internal/pkg/core"`, `"bindbox-game/internal/repository/mysql"` 3. Third-party: `"gorm.io/gorm"`, `"github.com/gin-gonic/gin"`, `"go.uber.org/zap"` **TypeScript — Relative imports with `@/` alias:** - `import request from '@/utils/http'` - `import { getActivityDetail } from './adminActivities'` (relative for same-level) ## Error Handling **Handler layer pattern:** ```go if err := ctx.ShouldBindForm(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } ``` **Service-to-handler errors:** ```go if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, validation.Error(err))) return } ``` **Special string-based sentinel errors (avoid when possible, currently used in product handler):** ```go if err.Error() == "PRODUCT_OFFSHELF" { ctx.AbortWithError(core.Error(http.StatusOK, 20001, "商品已下架")) return } ``` **Business error construction:** Always use `core.Error(httpCode, businessCode, message)`. Optionally chain `.WithError(err)` to attach stack trace or `.WithAlert()` for alerting. **Test error handling:** Use `t.Fatal(err)` for setup failures, `t.Fatalf(...)` with format strings for assertion failures, `t.Skipf(...)` when preconditions fail (e.g., no live DB). ## Logging **Framework:** Zap-based custom logger via `internal/pkg/logger` (`logger.CustomLogger` interface) **Patterns:** - 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 **Swagger annotations on every exported handler:** ```go // ListProductsForApp 商品列表 // @Summary 商品列表 // @Description ... // @Tags APP端.商品 // @Accept json // @Produce json // @Security LoginVerifyToken // @Param ... // @Success 200 {object} listAppProductsResponse // @Failure 400 {object} code.Failure // @Router /api/app/products [get] ``` **Chinese comments common** for domain logic inline comments, struct field descriptions, and test assertions — bilingual codebase. **Interface private guard pattern:** ```go // i 为了避免被其他包实现 i() ``` ## Function Design **Handler functions:** Return `core.HandlerFunc` (closure); keep handler thin — delegate to service layer. **Service constructors:** Always return interface, not concrete struct: ```go func NewActivityOrderService(l logger.CustomLogger, db mysql.Repo) ActivityOrderService { return &activityOrderService{...} } ``` **Service structs hold:** `logger`, `readDB *dao.Query`, `writeDB *dao.Query`, `repo mysql.Repo`, plus nested service interfaces for cross-domain calls. **Context propagation:** Use `core.Context` in handlers (not `gin.Context`); extract `context.Context` via `ctx.RequestContext()` for service calls. **Pagination defaults:** Always default `Page = 1`, `PageSize = 20` when not provided. ## Module Design **Layer boundaries:** - `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 **Barrel files:** Not used in Go. Each file exports its own types. **Repo interface:** All database access goes through `mysql.Repo` interface (`GetDbR()`, `GetDbW()`). Always use `dao.Use(db.GetDbR())` for read queries and `dao.Use(db.GetDbW())` for writes. --- *Convention analysis: 2026-03-21*