做完了一部分,备份一下,防止以外删除

This commit is contained in:
2025-11-11 15:16:38 +08:00
parent 9600e5b6e0
commit e98dd4d725
39 changed files with 2423 additions and 183 deletions

View File

@@ -1,42 +1,36 @@
<!--
SYNC IMPACT REPORT - Constitution Amendment
============================================
Version Change: 1.1.0 → 2.0.0
Date: 2025-11-10
Version Change: 2.1.0 → 2.1.1
Date: 2025-11-11
MODIFIED PRINCIPLES:
- I. Tech Stack Adherence → Added explicit Go tooling requirements
- II. Code Quality Standards → Completely rewritten to emphasize Go idioms and eliminate Java patterns
- NEW: Go Idiomatic Design Principles (Principle VI)
- II. Code Quality Standards → Added "中文注释和输出规范" (Chinese Comments and Output Standards)
ADDED SECTIONS:
- Principle VI: Go Idiomatic Design Principles (Go 味道 vs Java 味道)
- Package organization (flat, not deep hierarchies)
- Interface design (small, focused interfaces)
- Error handling (explicit, not exceptions)
- Concurrency patterns (goroutines and channels)
- Naming conventions (Go-style, not Java camelCase)
- Anti-patterns to avoid (getters/setters, builder patterns, etc.)
SECTIONS EXPANDED:
- Code Quality Standards now explicitly forbid Java-style patterns
- Added Go project structure requirements
- Added explicit guidance against OOP over-engineering
EXPANDED SECTIONS:
- Added "中文注释和输出规范" subsection to Principle II
- Rule: Code comments SHOULD be in Chinese for Chinese-speaking developers
- Rule: Log messages SHOULD be in Chinese
- Rule: User-facing error messages MUST be in Chinese (with bilingual support)
- Rule: Internal error messages and debug logs SHOULD be in Chinese
- Exceptions: Go doc comments for exported APIs may remain in English for ecosystem compatibility
- Examples of correct Chinese comments and log messages
- Rationale for Chinese-first approach
TEMPLATES REQUIRING UPDATES:
✅ .specify/templates/plan-template.md - Added Go idiomatic checks
✅ .specify/templates/spec-template.md - Added Go design requirements
✅ .specify/templates/tasks-template.md - Added Go code review tasks
✅ .specify/templates/plan-template.md - Will add Chinese comments check
✅ .specify/templates/spec-template.md - Will add Chinese output requirement
✅ .specify/templates/tasks-template.md - Will add Chinese usage quality gate
FOLLOW-UP ACTIONS:
- None - all placeholders resolved
- Update templates with Chinese comments/logs checks
RATIONALE:
MAJOR version bump (2.0.0) - Fundamental shift in design philosophy from Java-style to
Go-idiomatic patterns. This is a breaking change in coding standards that affects all
existing and future code. Added new principle (VI) defining mandatory Go idioms and
explicitly forbidding Java patterns like deep class hierarchies, getter/setter methods,
excessive abstraction, and OOP-heavy designs.
PATCH version bump (2.1.1) - Clarification of existing code quality standards to
prefer Chinese for comments and logs, targeting Chinese-speaking development team.
This is not a breaking change but a refinement of communication standards within
Principle II. The rule acknowledges the project's primary audience (Chinese developers)
while maintaining ecosystem compatibility for exported APIs.
-->
# 君鸿卡管系统 Constitution
@@ -107,10 +101,322 @@ excessive abstraction, and OOP-heavy designs.
key := "sim:status:" + iccid
```
**Magic Numbers 和硬编码规则 (Magic Numbers and Hardcoding Rules):**
- **MUST NOT** 在代码中直接使用 magic numbers未定义含义的数字字面量
- **MUST NOT** 在代码中硬编码字符串字面量URL、状态码、配置值、业务规则等
- 当相同的字面量值在 **3 个或以上位置**使用时,**MUST** 提取为常量
- 已定义的常量 **MUST** 被使用,**MUST NOT** 重复硬编码相同的值
- 只允许在以下情况使用字面量:
- 语言特性:`nil`、`true`、`false`
- 数学常量:`0`、`1`、`-1`(用于循环、索引、比较等明确的上下文)
- 一次性使用的临时值(测试数据、日志消息等)
**正确的常量使用:**
```go
// pkg/constants/constants.go
const (
DefaultPageSize = 20
MaxPageSize = 100
MinPageSize = 1
SIMStatusActive = "active"
SIMStatusInactive = "inactive"
SIMStatusSuspended = "suspended"
OrderTypeRecharge = "recharge"
OrderTypeTransfer = "transfer"
)
// internal/handler/user.go
func (h *Handler) ListUsers(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", constants.DefaultPageSize) // ✅ 使用常量
if pageSize > constants.MaxPageSize { // ✅ 使用常量
pageSize = constants.MaxPageSize
}
users, err := h.service.List(page, pageSize)
// ...
}
// internal/service/sim.go
func (s *Service) Activate(iccid string) error {
return s.store.UpdateStatus(iccid, constants.SIMStatusActive) // ✅ 使用常量
}
```
**错误的硬编码模式:**
```go
// ❌ 硬编码 magic number在多处使用
func ListUsers(c *fiber.Ctx) error {
pageSize := c.QueryInt("page_size", 20) // ❌ 硬编码 20
if pageSize > 100 { // ❌ 硬编码 100
pageSize = 100
}
// ...
}
func ListOrders(c *fiber.Ctx) error {
pageSize := c.QueryInt("page_size", 20) // ❌ 重复硬编码 20
// ...
}
// ❌ 硬编码字符串(在多处使用)
func ActivateSIM(iccid string) error {
return UpdateStatus(iccid, "active") // ❌ 硬编码 "active"
}
func IsSIMActive(sim *SIM) bool {
return sim.Status == "active" // ❌ 重复硬编码 "active"
}
// ❌ 已定义常量但不使用
// 常量已定义在 pkg/response/codes.go
func Success(c *fiber.Ctx, data any) error {
return c.JSON(Response{
Code: 0, // ❌ 应该使用 errors.CodeSuccess
Data: data,
})
}
```
**可接受的字面量使用:**
```go
// ✅ 语言特性和明确上下文的数字
if user == nil { // ✅ nil
return errors.New("user not found")
}
for i := 0; i < len(items); i++ { // ✅ 0, 1 用于循环
// ...
}
if count == 1 { // ✅ 1 用于比较
// 特殊处理单个元素
}
// ✅ 测试数据和日志消息
func TestUserCreate(t *testing.T) {
user := &User{
Name: "Test User", // ✅ 测试数据
Email: "test@example.com",
}
// ...
}
logger.Info("processing request", // ✅ 日志消息
zap.String("path", c.Path()),
zap.Int("status", 200))
```
**中文注释和输出规范 (Chinese Comments and Output Standards):**
本项目面向中文开发者,为提高代码可读性和维护效率,**SHOULD** 优先使用中文进行注释和输出。
- 代码注释implementation comments**SHOULD** 使用中文
- 日志消息log messages**SHOULD** 使用中文
- 用户可见的错误消息 **MUST** 使用中文(通过 `pkg/errors/` 的双语消息支持)
- 内部错误消息和调试日志 **SHOULD** 使用中文
- Go 文档注释doc comments for exported APIs**MAY** 使用英文以保持生态兼容性,但中文注释更佳
- 变量名、函数名、类型名 **MUST** 使用英文(遵循 Go 命名规范)
**正确的中文注释使用:**
```go
// GetUserByID 根据用户 ID 获取用户信息
// 如果用户不存在,返回 NotFoundError
func (s *Service) GetUserByID(ctx context.Context, id string) (*User, error) {
// 参数验证
if id == "" {
return nil, errors.New(errors.CodeInvalidParam, "用户 ID 不能为空")
}
// 从存储层获取用户
user, err := s.store.GetByID(ctx, id)
if err != nil {
s.logger.Error("获取用户失败",
zap.String("user_id", id),
zap.Error(err))
return nil, fmt.Errorf("获取用户失败: %w", err)
}
// 检查用户状态
if user.Status != constants.UserStatusActive {
s.logger.Warn("用户状态异常",
zap.String("user_id", id),
zap.String("status", user.Status))
}
return user, nil
}
// CreateOrder 创建新订单
func (s *Service) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) {
// 开启事务
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
s.logger.Error("创建订单时发生panic", zap.Any("panic", r))
}
}()
// 验证库存
if err := s.validateStock(ctx, req.ProductID, req.Quantity); err != nil {
tx.Rollback()
return nil, fmt.Errorf("库存验证失败: %w", err)
}
// 创建订单记录
order := &Order{
UserID: req.UserID,
ProductID: req.ProductID,
Quantity: req.Quantity,
Status: constants.OrderStatusPending,
}
if err := tx.Create(order).Error; err != nil {
tx.Rollback()
s.logger.Error("创建订单失败",
zap.String("user_id", req.UserID),
zap.Error(err))
return nil, err
}
tx.Commit()
s.logger.Info("订单创建成功",
zap.String("order_id", order.ID),
zap.String("user_id", req.UserID))
return order, nil
}
```
**正确的中文日志使用:**
```go
// 信息日志
logger.Info("服务启动成功",
zap.String("host", cfg.Server.Host),
zap.Int("port", cfg.Server.Port))
logger.Info("配置热重载成功",
zap.String("config_file", "config.yaml"),
zap.Time("reload_time", time.Now()))
// 警告日志
logger.Warn("Redis 连接延迟较高",
zap.Duration("latency", latency),
zap.String("threshold", "50ms"))
// 错误日志
logger.Error("数据库连接失败",
zap.String("host", cfg.DB.Host),
zap.Int("port", cfg.DB.Port),
zap.Error(err))
logger.Error("令牌验证失败",
zap.String("token", token[:10]+"..."),
zap.String("client_ip", clientIP),
zap.Error(err))
// 调试日志
logger.Debug("处理用户请求",
zap.String("request_id", requestID),
zap.String("method", c.Method()),
zap.String("path", c.Path()),
zap.String("user_id", userID))
```
**用户可见错误消息(双语支持):**
```go
// pkg/errors/codes.go
var errorMessages = map[int]ErrorMessage{
CodeSuccess: {"Success", "成功"},
CodeInternalError: {"Internal server error", "内部服务器错误"},
CodeMissingToken: {"Missing authentication token", "缺失认证令牌"},
CodeInvalidToken: {"Invalid or expired token", "令牌无效或已过期"},
CodeTooManyRequests: {"Too many requests", "请求过于频繁"},
CodeAuthServiceUnavailable: {"Authentication service unavailable", "认证服务不可用"},
}
// 使用时根据语言返回对应消息
message := errors.GetMessage(code, "zh") // 返回中文消息
```
**错误的英文注释(不推荐):**
```go
// ❌ 全英文注释(可读性较差,不便于中文团队维护)
// GetUserByID retrieves user information by user ID
// Returns NotFoundError if user does not exist
func (s *Service) GetUserByID(ctx context.Context, id string) (*User, error) {
// Validate parameters
if id == "" {
return nil, errors.New(errors.CodeInvalidParam, "用户 ID 不能为空")
}
// Fetch user from store
user, err := s.store.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get user", // ❌ 英文日志
zap.String("user_id", id),
zap.Error(err))
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
```
**例外情况(可使用英文):**
```go
// ✅ 导出的包级别文档注释可使用英文(生态兼容性)
// Package user provides user management functionality.
// It includes user CRUD operations, authentication, and authorization.
package user
// ✅ 导出的 API 文档注释可使用英文
// UserService provides user-related business logic operations.
type UserService struct {
store UserStorer
logger *zap.Logger
}
// ✅ 但内部实现注释应使用中文
func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*User, error) {
// 验证用户输入
if err := req.Validate(); err != nil {
return nil, err
}
// 检查用户是否已存在
existing, _ := s.store.GetByEmail(ctx, req.Email)
if existing != nil {
return nil, errors.New(errors.CodeUserExists, "用户邮箱已存在")
}
// 创建用户记录
user := &User{
Name: req.Name,
Email: req.Email,
}
return s.store.Create(ctx, user)
}
```
**理由 (RATIONALE):**
清晰的分层架构和代码组织使代码易于理解、测试和维护。统一的错误处理和响应格式提升 API 一致性和客户端集成体验。依赖注入模式便于单元测试和模块替换。集中管理常量和 Redis key 避免拼写错误、重复定义和命名不一致提升代码可维护性和重构安全性。Redis key 统一管理便于监控、调试和缓存策略调整。遵循 Go 官方代码风格确保代码一致性和可读性。
避免硬编码和强制使用常量的规则能够:
1. **提高可维护性**:修改常量值只需改一处,不需要搜索所有硬编码位置
2. **减少错误**:避免手动输入错误(拼写错误、大小写错误)
3. **增强可读性**`constants.MaxPageSize` 比 `100` 更能表达意图
4. **便于重构**IDE 可以追踪常量使用,重命名时不会遗漏
5. **统一业务规则**:确保所有地方使用相同的业务规则值
6. "3 次规则"提供明确的阈值,避免过早优化,同时确保重复值被及时抽取
---
### III. Testing Standards (测试标准)
@@ -689,7 +995,12 @@ Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的
- [ ] 无 TODO/FIXME 遗留(或已记录 Issue
- [ ] API 文档已更新(如有 API 变更)
- [ ] 数据库迁移文件已创建(如有 schema 变更)
- [ ] 常量和 Redis key 使用符合规范(无硬编码字符串)
- [ ] **常量和 Redis key 使用符合规范**(无硬编码字符串)
- [ ] **无重复硬编码值**3 次以上相同字面量已提取为常量)
- [ ] **已定义的常量被正确使用**(无重复硬编码已有常量的值)
- [ ] **代码注释优先使用中文**(实现注释使用中文,提高团队可读性)
- [ ] **日志消息使用中文**Info/Warn/Error/Debug 日志使用中文描述)
- [ ] **错误消息支持中文**(用户可见错误有中文消息)
- [ ] **无 Java 风格反模式**(无 getter/setter、无不必要接口、无过度抽象
- [ ] **遵循 Go 命名约定**(缩写大小写一致、包名简短、无下划线)
- [ ] **使用 Go 惯用并发模式**goroutines/channels无线程池类
@@ -726,12 +1037,13 @@ Go 语言的设计哲学强调简单性、直接性和实用性。Java 风格的
- 所有 PR 审查 **MUST** 验证是否符合本宪章
- 违反宪章的代码 **MUST** 在合并前修正
- 特别关注 Go 惯用法原则,拒绝 Java 风格的代码
- 特别关注常量使用规范,拒绝不必要的硬编码
- 任何复杂性增加(新依赖、新架构层)**MUST** 在设计文档中明确说明必要性
### 运行时开发指导
开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:**写 Go 代码,不是用 Go 语法写 Java**。
开发时参考本宪章确保一致性。如有疑问,优先遵守原则,再讨论例外情况。记住:**写 Go 代码,不是用 Go 语法写 Java**。记住:**定义常量是为了使用,不是为了装饰**。
---
**Version**: 2.0.0 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-10
**Version**: 2.1.1 | **Ratified**: 2025-11-10 | **Last Amended**: 2025-11-11