6.7 KiB
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.gonext torewards_create.go - Generated files suffixed with
.gen.go: never edited manually - Package names match directory name:
package activityininternal/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<Type>for service constructors:NewProduct(...),NewStore(...) - Handler methods return
core.HandlerFunc(closure pattern):func (h *productHandler) ListProductsForApp() core.HandlerFunc
Functions (TypeScript frontend):
fetchprefix 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
<verb><Domain>Request:listAppProductsRequest,CreateActivityOrderRequest - Response structs named
<verb><Domain>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 -sviamake 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 staticcheckviamake 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'— useletorconst @typescript-eslint/no-explicit-anydisabled (any is allowed)- Vue multi-word component name rule disabled
Import Organization
Go — Three groups (enforced by cmd/mfmt/main.go):
- Standard library:
"context","net/http","testing" - Local module:
"bindbox-game/internal/pkg/core","bindbox-game/internal/repository/mysql" - 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:
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
Service-to-handler errors:
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):
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.CustomLoggerfield - Service structs hold
logger logger.CustomLoggerfield - Use structured fields:
zap.Fieldvariadic args - Exported methods:
Info,Error,Warn,Debug - In tests, use
logger.NewCustomLogger(nil, logger.WithOutputInConsole())
Comments
Swagger annotations on every exported handler:
// 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:
// 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:
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 servicesinternal/service/→ business logic, call DAOs and other servicesinternal/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