第10章:项目实战

学习目标

通过本章学习,你将掌握: - 完整Go项目的开发流程 - Web服务开发和RESTful API设计 - CLI工具的开发和发布 - 数据库集成和ORM使用 - 项目结构设计和最佳实践 - 部署和运维相关技能

项目一:RESTful API服务

项目概述

我们将开发一个任务管理系统的RESTful API,包含用户管理、任务CRUD操作、认证授权等功能。

项目初始化

echo "=== 创建RESTful API项目 ==="

# 创建项目目录
mkdir -p task-api
cd task-api

# 初始化Go模块
go mod init task-api

# 创建项目结构
mkdir -p cmd/server
mkdir -p internal/{handler,service,repository,model}
mkdir -p pkg/{database,middleware,utils}
mkdir -p configs
mkdir -p docs
mkdir -p scripts

echo "项目结构创建完成"
tree . 2>/dev/null || find . -type d | sed 's/[^-][^/]*//  /g;s/^/  /;s/-/|/'

cd ..

定义数据模型

echo 'package model

import (
    "time"
    "gorm.io/gorm"
)

// User 用户模型
type User struct {
    ID        uint           `json:"id" gorm:"primarykey"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
    
    Username string `json:"username" gorm:"uniqueIndex;not null"`
    Email    string `json:"email" gorm:"uniqueIndex;not null"`
    Password string `json:"-" gorm:"not null"`
    
    Tasks []Task `json:"tasks,omitempty" gorm:"foreignKey:UserID"`
}

// Task 任务模型
type Task struct {
    ID        uint           `json:"id" gorm:"primarykey"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
    
    Title       string     `json:"title" gorm:"not null"`
    Description string     `json:"description"`
    Status      TaskStatus `json:"status" gorm:"default:pending"`
    Priority    Priority   `json:"priority" gorm:"default:medium"`
    DueDate     *time.Time `json:"due_date,omitempty"`
    
    UserID uint `json:"user_id" gorm:"not null;index"`
    User   User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}

// TaskStatus 任务状态枚举
type TaskStatus string

const (
    TaskStatusPending    TaskStatus = "pending"
    TaskStatusInProgress TaskStatus = "in_progress"
    TaskStatusCompleted  TaskStatus = "completed"
    TaskStatusCancelled  TaskStatus = "cancelled"
)

// Priority 优先级枚举
type Priority string

const (
    PriorityLow    Priority = "low"
    PriorityMedium Priority = "medium"
    PriorityHigh   Priority = "high"
    PriorityUrgent Priority = "urgent"
)

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=50"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required,min=6"`
}

// LoginRequest 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

// CreateTaskRequest 创建任务请求
type CreateTaskRequest struct {
    Title       string     `json:"title" binding:"required,max=200"`
    Description string     `json:"description"`
    Priority    Priority   `json:"priority"`
    DueDate     *time.Time `json:"due_date,omitempty"`
}

// UpdateTaskRequest 更新任务请求
type UpdateTaskRequest struct {
    Title       *string     `json:"title,omitempty"`
    Description *string     `json:"description,omitempty"`
    Status      *TaskStatus `json:"status,omitempty"`
    Priority    *Priority   `json:"priority,omitempty"`
    DueDate     *time.Time  `json:"due_date,omitempty"`
}

// APIResponse 统一API响应格式
type APIResponse struct {
    Success bool        `json:"success"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
}

// PaginationResponse 分页响应
type PaginationResponse struct {
    Data       interface{} `json:"data"`
    Total      int64       `json:"total"`
    Page       int         `json:"page"`
    PageSize   int         `json:"page_size"`
    TotalPages int         `json:"total_pages"`
}

// TaskFilter 任务过滤器
type TaskFilter struct {
    Status   *TaskStatus `form:"status"`
    Priority *Priority   `form:"priority"`
    Page     int         `form:"page,default=1"`
    PageSize int         `form:"page_size,default=10"`
    Search   string      `form:"search"`
}' > task-api/internal/model/models.go

echo "数据模型定义完成"

数据库配置

echo 'package database

import (
    "fmt"
    "log"
    "os"
    "time"
    
    "task-api/internal/model"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

// DB 全局数据库实例
var DB *gorm.DB

// Config 数据库配置
type Config struct {
    Driver   string
    Host     string
    Port     string
    Database string
    Username string
    Password string
    SSLMode  string
}

// Initialize 初始化数据库连接
func Initialize() error {
    var err error
    
    // 从环境变量获取配置,默认使用SQLite
    dbDriver := getEnv("DB_DRIVER", "sqlite")
    
    switch dbDriver {
    case "sqlite":
        DB, err = initSQLite()
    case "postgres":
        DB, err = initPostgreSQL()
    default:
        return fmt.Errorf("不支持的数据库驱动: %s", dbDriver)
    }
    
    if err != nil {
        return fmt.Errorf("数据库连接失败: %w", err)
    }
    
    // 自动迁移
    if err := autoMigrate(); err != nil {
        return fmt.Errorf("数据库迁移失败: %w", err)
    }
    
    log.Println("数据库初始化成功")
    return nil
}

// initSQLite 初始化SQLite数据库
func initSQLite() (*gorm.DB, error) {
    dbPath := getEnv("DB_PATH", "./task.db")
    
    db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    
    if err != nil {
        return nil, err
    }
    
    // 配置连接池
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)
    
    return db, nil
}

// initPostgreSQL 初始化PostgreSQL数据库
func initPostgreSQL() (*gorm.DB, error) {
    config := Config{
        Host:     getEnv("DB_HOST", "localhost"),
        Port:     getEnv("DB_PORT", "5432"),
        Database: getEnv("DB_NAME", "taskdb"),
        Username: getEnv("DB_USER", "postgres"),
        Password: getEnv("DB_PASSWORD", ""),
        SSLMode:  getEnv("DB_SSLMODE", "disable"),
    }
    
    dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
        config.Host, config.Port, config.Username, config.Password, config.Database, config.SSLMode)
    
    // 注意:这里需要导入PostgreSQL驱动
    // "gorm.io/driver/postgres"
    // db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    
    return nil, fmt.Errorf("PostgreSQL支持需要导入相应驱动")
}

// autoMigrate 自动迁移数据库表
func autoMigrate() error {
    return DB.AutoMigrate(
        &model.User{},
        &model.Task{},
    )
}

// SeedData 初始化测试数据
func SeedData() error {
    // 检查是否已有数据
    var userCount int64
    if err := DB.Model(&model.User{}).Count(&userCount).Error; err != nil {
        return err
    }
    
    if userCount > 0 {
        log.Println("数据库已有数据,跳过初始化")
        return nil
    }
    
    // 创建测试用户
    users := []model.User{
        {
            Username: "admin",
            Email:    "admin@example.com",
            Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password
        },
        {
            Username: "user1",
            Email:    "user1@example.com",
            Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password
        },
    }
    
    for _, user := range users {
        if err := DB.Create(&user).Error; err != nil {
            return err
        }
    }
    
    // 创建测试任务
    dueDate := time.Now().AddDate(0, 0, 7) // 一周后
    tasks := []model.Task{
        {
            Title:       "完成项目文档",
            Description: "编写项目的技术文档和用户手册",
            Status:      model.TaskStatusPending,
            Priority:    model.PriorityHigh,
            DueDate:     &dueDate,
            UserID:      1,
        },
        {
            Title:       "代码审查",
            Description: "审查团队成员提交的代码",
            Status:      model.TaskStatusInProgress,
            Priority:    model.PriorityMedium,
            UserID:      1,
        },
        {
            Title:       "学习Go语言",
            Description: "深入学习Go语言的高级特性",
            Status:      model.TaskStatusPending,
            Priority:    model.PriorityLow,
            UserID:      2,
        },
    }
    
    for _, task := range tasks {
        if err := DB.Create(&task).Error; err != nil {
            return err
        }
    }
    
    log.Println("测试数据初始化完成")
    return nil
}

// Close 关闭数据库连接
func Close() error {
    if DB != nil {
        sqlDB, err := DB.DB()
        if err != nil {
            return err
        }
        return sqlDB.Close()
    }
    return nil
}

// getEnv 获取环境变量,如果不存在则返回默认值
func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}' > task-api/pkg/database/database.go

echo "数据库配置完成"

中间件实现

echo 'package middleware

import (
    "net/http"
    "strings"
    "time"
    
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
    "task-api/internal/model"
)

// JWTSecret JWT密钥
var JWTSecret = []byte("your-secret-key")

// Claims JWT声明
type Claims struct {
    UserID   uint   `json:"user_id"`
    Username string `json:"username"`n    jwt.RegisteredClaims
}

// CORS 跨域中间件
func CORS() gin.HandlerFunc {
    return gin.HandlerFunc(func(c *gin.Context) {
        origin := c.Request.Header.Get("Origin")
        
        c.Header("Access-Control-Allow-Origin", origin)
        c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
        c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
        c.Header("Access-Control-Allow-Credentials", "true")
        
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        
        c.Next()
    })
}

// Logger 日志中间件
func Logger() gin.HandlerFunc {
    return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\
",
            param.ClientIP,
            param.TimeStamp.Format(time.RFC1123),
            param.Method,
            param.Path,
            param.Request.Proto,
            param.StatusCode,
            param.Latency,
            param.Request.UserAgent(),
            param.ErrorMessage,
        )
    })
}

// Recovery 恢复中间件
func Recovery() gin.HandlerFunc {
    return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
        if err, ok := recovered.(string); ok {
            c.JSON(http.StatusInternalServerError, model.APIResponse{
                Success: false,
                Message: "服务器内部错误",
                Error:   err,
            })
        }
        c.AbortWithStatus(http.StatusInternalServerError)
    })
}

// RateLimit 简单的速率限制中间件
func RateLimit() gin.HandlerFunc {
    // 这里实现一个简单的内存速率限制
    // 生产环境建议使用Redis等外部存储
    
    type client struct {
        limiter  *time.Ticker
        lastSeen time.Time
    }
    
    clients := make(map[string]*client)
    
    return func(c *gin.Context) {
        ip := c.ClientIP()
        
        // 清理过期的客户端
        for k, v := range clients {
            if time.Since(v.lastSeen) > time.Minute {
                v.limiter.Stop()
                delete(clients, k)
            }
        }
        
        // 检查客户端是否存在
        if _, exists := clients[ip]; !exists {
            clients[ip] = &client{
                limiter:  time.NewTicker(time.Second), // 每秒一个请求
                lastSeen: time.Now(),
            }
        }
        
        clients[ip].lastSeen = time.Now()
        
        select {
        case <-clients[ip].limiter.C:
            c.Next()
        default:
            c.JSON(http.StatusTooManyRequests, model.APIResponse{
                Success: false,
                Message: "请求过于频繁,请稍后再试",
            })
            c.Abort()
        }
    }
}

// AuthRequired JWT认证中间件
func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, model.APIResponse{
                Success: false,
                Message: "缺少认证令牌",
            })
            c.Abort()
            return
        }
        
        // 检查Bearer前缀
        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        if tokenString == authHeader {
            c.JSON(http.StatusUnauthorized, model.APIResponse{
                Success: false,
                Message: "认证令牌格式错误",
            })
            c.Abort()
            return
        }
        
        // 解析JWT令牌
        token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
            return JWTSecret, nil
        })
        
        if err != nil {
            c.JSON(http.StatusUnauthorized, model.APIResponse{
                Success: false,
                Message: "认证令牌无效",
                Error:   err.Error(),
            })
            c.Abort()
            return
        }
        
        if claims, ok := token.Claims.(*Claims); ok && token.Valid {
            // 将用户信息存储到上下文中
            c.Set("user_id", claims.UserID)
            c.Set("username", claims.Username)
            c.Next()
        } else {
            c.JSON(http.StatusUnauthorized, model.APIResponse{
                Success: false,
                Message: "认证令牌无效",
            })
            c.Abort()
        }
    }
}

// GenerateToken 生成JWT令牌
func GenerateToken(userID uint, username string) (string, error) {
    claims := Claims{
        UserID:   userID,
        Username: username,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(JWTSecret)
}

// GetUserFromContext 从上下文获取用户信息
func GetUserFromContext(c *gin.Context) (uint, string, bool) {
    userID, exists1 := c.Get("user_id")
    username, exists2 := c.Get("username")
    
    if !exists1 || !exists2 {
        return 0, "", false
    }
    
    return userID.(uint), username.(string), true
}' > task-api/pkg/middleware/middleware.go

echo "中间件实现完成"

服务层实现

echo 'package service

import (
    "errors"
    "fmt"
    "math"
    "strings"
    
    "golang.org/x/crypto/bcrypt"
    "gorm.io/gorm"
    
    "task-api/internal/model"
    "task-api/pkg/database"
)

// UserService 用户服务
type UserService struct {
    db *gorm.DB
}

// NewUserService 创建用户服务
func NewUserService() *UserService {
    return &UserService{
        db: database.DB,
    }
}

// CreateUser 创建用户
func (s *UserService) CreateUser(req *model.CreateUserRequest) (*model.User, error) {
    // 检查用户名是否已存在
    var existingUser model.User
    if err := s.db.Where("username = ? OR email = ?", req.Username, req.Email).First(&existingUser).Error; err == nil {
        return nil, errors.New("用户名或邮箱已存在")
    }
    
    // 加密密码
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
    if err != nil {
        return nil, fmt.Errorf("密码加密失败: %w", err)
    }
    
    user := &model.User{
        Username: req.Username,
        Email:    req.Email,
        Password: string(hashedPassword),
    }
    
    if err := s.db.Create(user).Error; err != nil {
        return nil, fmt.Errorf("创建用户失败: %w", err)
    }
    
    return user, nil
}

// AuthenticateUser 用户认证
func (s *UserService) AuthenticateUser(req *model.LoginRequest) (*model.User, error) {
    var user model.User
    if err := s.db.Where("username = ?", req.Username).First(&user).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, errors.New("用户名或密码错误")
        }
        return nil, fmt.Errorf("查询用户失败: %w", err)
    }
    
    // 验证密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
        return nil, errors.New("用户名或密码错误")
    }
    
    return &user, nil
}

// GetUserByID 根据ID获取用户
func (s *UserService) GetUserByID(id uint) (*model.User, error) {
    var user model.User
    if err := s.db.First(&user, id).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, errors.New("用户不存在")
        }
        return nil, fmt.Errorf("查询用户失败: %w", err)
    }
    
    return &user, nil
}

// TaskService 任务服务
type TaskService struct {
    db *gorm.DB
}

// NewTaskService 创建任务服务
func NewTaskService() *TaskService {
    return &TaskService{
        db: database.DB,
    }
}

// CreateTask 创建任务
func (s *TaskService) CreateTask(userID uint, req *model.CreateTaskRequest) (*model.Task, error) {
    task := &model.Task{
        Title:       req.Title,
        Description: req.Description,
        Status:      model.TaskStatusPending,
        Priority:    req.Priority,
        DueDate:     req.DueDate,
        UserID:      userID,
    }
    
    if task.Priority == "" {
        task.Priority = model.PriorityMedium
    }
    
    if err := s.db.Create(task).Error; err != nil {
        return nil, fmt.Errorf("创建任务失败: %w", err)
    }
    
    // 预加载用户信息
    if err := s.db.Preload("User").First(task, task.ID).Error; err != nil {
        return nil, fmt.Errorf("加载任务信息失败: %w", err)
    }
    
    return task, nil
}

// GetTasks 获取任务列表
func (s *TaskService) GetTasks(userID uint, filter *model.TaskFilter) (*model.PaginationResponse, error) {
    query := s.db.Model(&model.Task{}).Where("user_id = ?", userID)
    
    // 应用过滤条件
    if filter.Status != nil {
        query = query.Where("status = ?", *filter.Status)
    }
    
    if filter.Priority != nil {
        query = query.Where("priority = ?", *filter.Priority)
    }
    
    if filter.Search != "" {
        searchTerm := "%" + strings.ToLower(filter.Search) + "%"
        query = query.Where("LOWER(title) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm)
    }
    
    // 计算总数
    var total int64
    if err := query.Count(&total).Error; err != nil {
        return nil, fmt.Errorf("计算任务总数失败: %w", err)
    }
    
    // 分页
    offset := (filter.Page - 1) * filter.PageSize
    var tasks []model.Task
    
    if err := query.Preload("User").Order("created_at DESC").Offset(offset).Limit(filter.PageSize).Find(&tasks).Error; err != nil {
        return nil, fmt.Errorf("查询任务失败: %w", err)
    }
    
    totalPages := int(math.Ceil(float64(total) / float64(filter.PageSize)))
    
    return &model.PaginationResponse{
        Data:       tasks,
        Total:      total,
        Page:       filter.Page,
        PageSize:   filter.PageSize,
        TotalPages: totalPages,
    }, nil
}

// GetTaskByID 根据ID获取任务
func (s *TaskService) GetTaskByID(userID, taskID uint) (*model.Task, error) {
    var task model.Task
    if err := s.db.Preload("User").Where("id = ? AND user_id = ?", taskID, userID).First(&task).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, errors.New("任务不存在")
        }
        return nil, fmt.Errorf("查询任务失败: %w", err)
    }
    
    return &task, nil
}

// UpdateTask 更新任务
func (s *TaskService) UpdateTask(userID, taskID uint, req *model.UpdateTaskRequest) (*model.Task, error) {
    var task model.Task
    if err := s.db.Where("id = ? AND user_id = ?", taskID, userID).First(&task).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, errors.New("任务不存在")
        }
        return nil, fmt.Errorf("查询任务失败: %w", err)
    }
    
    // 更新字段
    updates := make(map[string]interface{})
    
    if req.Title != nil {
        updates["title"] = *req.Title
    }
    
    if req.Description != nil {
        updates["description"] = *req.Description
    }
    
    if req.Status != nil {
        updates["status"] = *req.Status
    }
    
    if req.Priority != nil {
        updates["priority"] = *req.Priority
    }
    
    if req.DueDate != nil {
        updates["due_date"] = *req.DueDate
    }
    
    if err := s.db.Model(&task).Updates(updates).Error; err != nil {
        return nil, fmt.Errorf("更新任务失败: %w", err)
    }
    
    // 重新加载任务
    if err := s.db.Preload("User").First(&task, task.ID).Error; err != nil {
        return nil, fmt.Errorf("加载任务信息失败: %w", err)
    }
    
    return &task, nil
}

// DeleteTask 删除任务
func (s *TaskService) DeleteTask(userID, taskID uint) error {
    result := s.db.Where("id = ? AND user_id = ?", taskID, userID).Delete(&model.Task{})
    
    if result.Error != nil {
        return fmt.Errorf("删除任务失败: %w", result.Error)
    }
    
    if result.RowsAffected == 0 {
        return errors.New("任务不存在")
    }
    
    return nil
}

// GetTaskStats 获取任务统计
func (s *TaskService) GetTaskStats(userID uint) (map[string]interface{}, error) {
    stats := make(map[string]interface{})
    
    // 总任务数
    var total int64
    if err := s.db.Model(&model.Task{}).Where("user_id = ?", userID).Count(&total).Error; err != nil {
        return nil, fmt.Errorf("统计总任务数失败: %w", err)
    }
    stats["total"] = total
    
    // 按状态统计
    statusStats := make(map[string]int64)
    statuses := []model.TaskStatus{
        model.TaskStatusPending,
        model.TaskStatusInProgress,
        model.TaskStatusCompleted,
        model.TaskStatusCancelled,
    }
    
    for _, status := range statuses {
        var count int64
        if err := s.db.Model(&model.Task{}).Where("user_id = ? AND status = ?", userID, status).Count(&count).Error; err != nil {
            return nil, fmt.Errorf("统计状态 %s 失败: %w", status, err)
        }
        statusStats[string(status)] = count
    }
    stats["by_status"] = statusStats
    
    // 按优先级统计
    priorityStats := make(map[string]int64)
    priorities := []model.Priority{
        model.PriorityLow,
        model.PriorityMedium,
        model.PriorityHigh,
        model.PriorityUrgent,
    }
    
    for _, priority := range priorities {
        var count int64
        if err := s.db.Model(&model.Task{}).Where("user_id = ? AND priority = ?", userID, priority).Count(&count).Error; err != nil {
            return nil, fmt.Errorf("统计优先级 %s 失败: %w", priority, err)
        }
        priorityStats[string(priority)] = count
    }
    stats["by_priority"] = priorityStats
    
    return stats, nil
}' > task-api/internal/service/service.go

echo "服务层实现完成"

处理器实现

echo 'package handler

import (
    "net/http"
    "strconv"
    
    "github.com/gin-gonic/gin"
    
    "task-api/internal/model"
    "task-api/internal/service"
    "task-api/pkg/middleware"
)

// UserHandler 用户处理器
type UserHandler struct {
    userService *service.UserService
}

// NewUserHandler 创建用户处理器
func NewUserHandler() *UserHandler {
    return &UserHandler{
        userService: service.NewUserService(),
    }
}

// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
    var req model.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "请求参数错误",
            Error:   err.Error(),
        })
        return
    }
    
    user, err := h.userService.CreateUser(&req)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "注册失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusCreated, model.APIResponse{
        Success: true,
        Message: "注册成功",
        Data:    user,
    })
}

// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
    var req model.LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "请求参数错误",
            Error:   err.Error(),
        })
        return
    }
    
    user, err := h.userService.AuthenticateUser(&req)
    if err != nil {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "登录失败",
            Error:   err.Error(),
        })
        return
    }
    
    // 生成JWT令牌
    token, err := middleware.GenerateToken(user.ID, user.Username)
    if err != nil {
        c.JSON(http.StatusInternalServerError, model.APIResponse{
            Success: false,
            Message: "生成令牌失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "登录成功",
        Data: gin.H{
            "user":  user,
            "token": token,
        },
    })
}

// Profile 获取用户信息
func (h *UserHandler) Profile(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    user, err := h.userService.GetUserByID(userID)
    if err != nil {
        c.JSON(http.StatusNotFound, model.APIResponse{
            Success: false,
            Message: "用户不存在",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "获取用户信息成功",
        Data:    user,
    })
}

// TaskHandler 任务处理器
type TaskHandler struct {
    taskService *service.TaskService
}

// NewTaskHandler 创建任务处理器
func NewTaskHandler() *TaskHandler {
    return &TaskHandler{
        taskService: service.NewTaskService(),
    }
}

// CreateTask 创建任务
func (h *TaskHandler) CreateTask(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    var req model.CreateTaskRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "请求参数错误",
            Error:   err.Error(),
        })
        return
    }
    
    task, err := h.taskService.CreateTask(userID, &req)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "创建任务失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusCreated, model.APIResponse{
        Success: true,
        Message: "创建任务成功",
        Data:    task,
    })
}

// GetTasks 获取任务列表
func (h *TaskHandler) GetTasks(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    var filter model.TaskFilter
    if err := c.ShouldBindQuery(&filter); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "查询参数错误",
            Error:   err.Error(),
        })
        return
    }
    
    // 设置默认值
    if filter.Page <= 0 {
        filter.Page = 1
    }
    if filter.PageSize <= 0 || filter.PageSize > 100 {
        filter.PageSize = 10
    }
    
    result, err := h.taskService.GetTasks(userID, &filter)
    if err != nil {
        c.JSON(http.StatusInternalServerError, model.APIResponse{
            Success: false,
            Message: "获取任务列表失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "获取任务列表成功",
        Data:    result,
    })
}

// GetTask 获取单个任务
func (h *TaskHandler) GetTask(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    taskIDStr := c.Param("id")
    taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "任务ID格式错误",
        })
        return
    }
    
    task, err := h.taskService.GetTaskByID(userID, uint(taskID))
    if err != nil {
        c.JSON(http.StatusNotFound, model.APIResponse{
            Success: false,
            Message: "任务不存在",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "获取任务成功",
        Data:    task,
    })
}

// UpdateTask 更新任务
func (h *TaskHandler) UpdateTask(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    taskIDStr := c.Param("id")
    taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "任务ID格式错误",
        })
        return
    }
    
    var req model.UpdateTaskRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "请求参数错误",
            Error:   err.Error(),
        })
        return
    }
    
    task, err := h.taskService.UpdateTask(userID, uint(taskID), &req)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "更新任务失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "更新任务成功",
        Data:    task,
    })
}

// DeleteTask 删除任务
func (h *TaskHandler) DeleteTask(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    taskIDStr := c.Param("id")
    taskID, err := strconv.ParseUint(taskIDStr, 10, 32)
    if err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "任务ID格式错误",
        })
        return
    }
    
    if err := h.taskService.DeleteTask(userID, uint(taskID)); err != nil {
        c.JSON(http.StatusBadRequest, model.APIResponse{
            Success: false,
            Message: "删除任务失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "删除任务成功",
    })
}

// GetTaskStats 获取任务统计
func (h *TaskHandler) GetTaskStats(c *gin.Context) {
    userID, _, ok := middleware.GetUserFromContext(c)
    if !ok {
        c.JSON(http.StatusUnauthorized, model.APIResponse{
            Success: false,
            Message: "未授权",
        })
        return
    }
    
    stats, err := h.taskService.GetTaskStats(userID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, model.APIResponse{
            Success: false,
            Message: "获取统计信息失败",
            Error:   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, model.APIResponse{
        Success: true,
        Message: "获取统计信息成功",
        Data:    stats,
    })
}' > task-api/internal/handler/handler.go

echo "处理器实现完成"

# 添加必要的导入
echo 'package middleware

import (
    "fmt"
    "net/http"
    "strings"
    "time"
    
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
    "task-api/internal/model"
)' > task-api/pkg/middleware/imports_fix.go

echo "修复导入完成"

#### 主程序和路由配置
```bash
# 创建主程序
echo 'package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    
    "github.com/gin-gonic/gin"
    
    "task-api/internal/handler"
    "task-api/pkg/database"
    "task-api/pkg/middleware"
)

func main() {
    // 初始化数据库
    if err := database.Initialize(); err != nil {
        log.Fatalf("数据库初始化失败: %v", err)
    }
    defer database.Close()
    
    // 初始化测试数据
    if err := database.SeedData(); err != nil {
        log.Printf("初始化测试数据失败: %v", err)
    }
    
    // 设置Gin模式
    if os.Getenv("GIN_MODE") == "" {
        gin.SetMode(gin.DebugMode)
    }
    
    // 创建路由
    router := setupRouter()
    
    // 配置服务器
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    
    srv := &http.Server{
        Addr:    ":" + port,
        Handler: router,
    }
    
    // 启动服务器
    go func() {
        log.Printf("服务器启动在端口 %s", port)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务器启动失败: %v", err)
        }
    }()
    
    // 优雅关闭
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务器...")
    
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("服务器强制关闭:", err)
    }
    
    log.Println("服务器已关闭")
}

// setupRouter 设置路由
func setupRouter() *gin.Engine {
    router := gin.New()
    
    // 添加中间件
    router.Use(middleware.Logger())
    router.Use(middleware.Recovery())
    router.Use(middleware.CORS())
    
    // 健康检查
    router.GET("/health", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "status": "ok",
            "time":   time.Now().Format(time.RFC3339),
        })
    })
    
    // API版本分组
    v1 := router.Group("/api/v1")
    {
        // 用户相关路由
        userHandler := handler.NewUserHandler()
        auth := v1.Group("/auth")
        {
            auth.POST("/register", userHandler.Register)
            auth.POST("/login", userHandler.Login)
        }
        
        // 需要认证的路由
        protected := v1.Group("/")
        protected.Use(middleware.AuthRequired())
        {
            // 用户信息
            protected.GET("/profile", userHandler.Profile)
            
            // 任务相关路由
            taskHandler := handler.NewTaskHandler()
            tasks := protected.Group("/tasks")
            {
                tasks.POST("/", taskHandler.CreateTask)
                tasks.GET("/", taskHandler.GetTasks)
                tasks.GET("/stats", taskHandler.GetTaskStats)
                tasks.GET("/:id", taskHandler.GetTask)
                tasks.PUT("/:id", taskHandler.UpdateTask)
                tasks.DELETE("/:id", taskHandler.DeleteTask)
            }
        }
    }
    
    // 404处理
    router.NoRoute(func(c *gin.Context) {
        c.JSON(http.StatusNotFound, gin.H{
            "success": false,
            "message": "接口不存在",
        })
    })
    
    return router
}' > task-api/cmd/server/main.go

echo "主程序创建完成"

# 创建依赖管理文件
echo 'module task-api

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/golang-jwt/jwt/v5 v5.0.0
    golang.org/x/crypto v0.14.0
    gorm.io/driver/sqlite v1.5.4
    gorm.io/gorm v1.25.5
)

require (
    github.com/bytedance/sonic v1.9.1 // indirect
    github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
    github.com/gabriel-vasile/mimetype v1.4.2 // indirect
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/go-playground/locales v0.14.1 // indirect
    github.com/go-playground/universal-translator v0.18.1 // indirect
    github.com/go-playground/validator/v10 v10.14.0 // indirect
    github.com/goccy/go-json v0.10.2 // indirect
    github.com/jinzhu/inflection v1.0.0 // indirect
    github.com/jinzhu/now v1.1.5 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/klauspost/cpuid/v2 v2.2.4 // indirect
    github.com/leodido/go-urn v1.2.4 // indirect
    github.com/mattn/go-isatty v0.0.19 // indirect
    github.com/mattn/go-sqlite3 v1.14.17 // indirect
    github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/pelletier/go-toml/v2 v2.0.8 // indirect
    github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
    github.com/ugorji/go/codec v1.2.11 // indirect
    golang.org/x/arch v0.3.0 // indirect
    golang.org/x/net v0.10.0 // indirect
    golang.org/x/sys v0.13.0 // indirect
    golang.org/x/text v0.13.0 // indirect
    google.golang.org/protobuf v1.30.0 // indirect
    gopkg.in/yaml.v3 v3.0.1 // indirect
)' > task-api/go.mod

echo "依赖文件创建完成"

# 创建配置文件
echo '# 数据库配置
DB_DRIVER=sqlite
DB_PATH=./task.db

# 服务器配置
PORT=8080
GIN_MODE=debug

# JWT配置
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production

# 日志配置
LOG_LEVEL=info' > task-api/.env.example

cp task-api/.env.example task-api/.env

echo "配置文件创建完成"

# 创建Makefile
echo '.PHONY: build run test clean docker-build docker-run

# 变量定义
APP_NAME=task-api
BUILD_DIR=./bin
MAIN_FILE=./cmd/server/main.go

# 构建应用
build:
	@echo "构建应用..."
	@mkdir -p $(BUILD_DIR)
	@go build -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_FILE)
	@echo "构建完成: $(BUILD_DIR)/$(APP_NAME)"

# 运行应用
run:
	@echo "启动应用..."
	@go run $(MAIN_FILE)

# 运行测试
test:
	@echo "运行测试..."
	@go test -v ./...

# 运行测试并生成覆盖率报告
test-coverage:
	@echo "运行测试并生成覆盖率报告..."
	@go test -v -coverprofile=coverage.out ./...
	@go tool cover -html=coverage.out -o coverage.html
	@echo "覆盖率报告已生成: coverage.html"

# 格式化代码
fmt:
	@echo "格式化代码..."
	@go fmt ./...

# 代码检查
vet:
	@echo "代码检查..."
	@go vet ./...

# 安装依赖
deps:
	@echo "安装依赖..."
	@go mod download
	@go mod tidy

# 清理构建文件
clean:
	@echo "清理构建文件..."
	@rm -rf $(BUILD_DIR)
	@rm -f *.db
	@rm -f coverage.out coverage.html

# Docker构建
docker-build:
	@echo "构建Docker镜像..."
	@docker build -t $(APP_NAME) .

# Docker运行
docker-run:
	@echo "运行Docker容器..."
	@docker run -p 8080:8080 --name $(APP_NAME)-container $(APP_NAME)

# 开发模式(热重载)
dev:
	@echo "启动开发模式..."
	@if command -v air > /dev/null; then \
		air; \
	else \
		echo "请先安装air: go install github.com/cosmtrek/air@latest"; \
		go run $(MAIN_FILE); \
	fi

# 生成API文档
docs:
	@echo "生成API文档..."
	@if command -v swag > /dev/null; then \
		swag init -g $(MAIN_FILE) -o ./docs; \
	else \
		echo "请先安装swag: go install github.com/swaggo/swag/cmd/swag@latest"; \
	fi

# 数据库迁移
migrate:
	@echo "执行数据库迁移..."
	@go run $(MAIN_FILE) -migrate

# 初始化项目
init: deps fmt vet test build
	@echo "项目初始化完成"

# 帮助信息
help:
	@echo "可用命令:"
	@echo "  build        - 构建应用"
	@echo "  run          - 运行应用"
	@echo "  test         - 运行测试"
	@echo "  test-coverage- 运行测试并生成覆盖率报告"
	@echo "  fmt          - 格式化代码"
	@echo "  vet          - 代码检查"
	@echo "  deps         - 安装依赖"
	@echo "  clean        - 清理构建文件"
	@echo "  docker-build - 构建Docker镜像"
	@echo "  docker-run   - 运行Docker容器"
	@echo "  dev          - 启动开发模式"
	@echo "  docs         - 生成API文档"
	@echo "  migrate      - 执行数据库迁移"
	@echo "  init         - 初始化项目"
	@echo "  help         - 显示帮助信息"' > task-api/Makefile

echo "Makefile创建完成"

# 创建Dockerfile
echo 'FROM golang:1.21-alpine AS builder

# 设置工作目录
WORKDIR /app

# 安装必要的包
RUN apk add --no-cache git

# 复制go mod文件
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server

# 运行阶段
FROM alpine:latest

# 安装ca证书和sqlite
RUN apk --no-cache add ca-certificates sqlite

WORKDIR /root/

# 从构建阶段复制二进制文件
COPY --from=builder /app/main .

# 暴露端口
EXPOSE 8080

# 运行应用
CMD ["./main"]' > task-api/Dockerfile

echo "Dockerfile创建完成"

# 创建.gitignore
echo '# 二进制文件
/bin/
*.exe
*.exe~
*.dll
*.so
*.dylib

# 测试二进制文件
*.test

# 输出覆盖率文件
*.out
*.html

# 依赖目录
vendor/

# Go工作区文件
go.work

# 环境变量文件
.env

# 数据库文件
*.db
*.sqlite
*.sqlite3

# 日志文件
*.log
logs/

# IDE文件
.vscode/
.idea/
*.swp
*.swo
*~

# 操作系统文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# 临时文件
tmp/
temp/

# 文档生成文件
docs/docs.go
docs/swagger.json
docs/swagger.yaml' > task-api/.gitignore

echo ".gitignore创建完成"

# 创建README文件
echo '# Task API

一个使用Go语言开发的RESTful任务管理API服务。

## 功能特性

- 用户注册和登录
- JWT认证
- 任务CRUD操作
- 任务状态管理
- 任务优先级设置
- 分页查询
- 搜索过滤
- 任务统计

## 技术栈

- **语言**: Go 1.21+
- **Web框架**: Gin
- **数据库**: SQLite (可扩展到PostgreSQL)
- **ORM**: GORM
- **认证**: JWT
- **密码加密**: bcrypt

## 项目结构

task-api/ ├── cmd/server/ # 主程序入口 ├── internal/ # 内部包 │ ├── handler/ # HTTP处理器 │ ├── service/ # 业务逻辑层 │ ├── repository/ # 数据访问层 │ └── model/ # 数据模型 ├── pkg/ # 公共包 │ ├── database/ # 数据库配置 │ ├── middleware/ # 中间件 │ └── utils/ # 工具函数 ├── configs/ # 配置文件 ├── docs/ # API文档 ├── scripts/ # 脚本文件 ├── Dockerfile # Docker配置 ├── Makefile # 构建脚本 └── README.md # 项目说明


## 快速开始

### 环境要求

- Go 1.21+
- Git

### 安装和运行

1. 克隆项目
```bash
git clone <repository-url>
cd task-api
  1. 安装依赖

    make deps
    
    1. 运行应用 bash make run
  2. 或者使用开发模式(热重载)

    make dev
    

    使用Docker

    1. 构建镜像 bash make docker-build
  3. 运行容器

    make docker-run
    

    API文档

    认证相关

    用户注册

    ”`

POST /api/v1/auth/register Content-Type: application/json

{ “username”: “testuser”, “email”: “test@example.com”, “password”: “password123” }


#### 用户登录

POST /api/v1/auth/login Content-Type: application/json

{ “username”: “testuser”, “password”: “password123” }


### 任务相关

#### 创建任务

POST /api/v1/tasks Authorization: Bearer Content-Type: application/json

{ “title”: “完成项目文档”, “description”: “编写项目的技术文档”, “priority”: “high”, “due_date”: “2024-12-31T23:59:59Z” }


#### 获取任务列表

GET /api/v1/tasks?page=1&page_size=10&status=pending&priority=high&search=文档 Authorization: Bearer


#### 更新任务

PUT /api/v1/tasks/{id} Authorization: Bearer Content-Type: application/json

{ “status”: “completed” }


#### 删除任务

DELETE /api/v1/tasks/{id} Authorization: Bearer


#### 获取任务统计

GET /api/v1/tasks/stats Authorization: Bearer


## 开发指南

### 可用命令

```bash
make help          # 查看所有可用命令
make build         # 构建应用
make run           # 运行应用
make test          # 运行测试
make test-coverage # 运行测试并生成覆盖率报告
make fmt           # 格式化代码
make vet           # 代码检查
make clean         # 清理构建文件

环境变量

复制 .env.example.env 并根据需要修改配置:

cp .env.example .env

数据库

默认使用SQLite数据库,数据库文件会自动创建。如需使用PostgreSQL,请修改环境变量:

DB_DRIVER=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=taskdb
DB_USER=postgres
DB_PASSWORD=password

测试

运行所有测试:

make test

生成覆盖率报告:

make test-coverage

部署

使用Docker

  1. 构建镜像 bash docker build -t task-api . 2. 运行容器 bash docker run -p 8080:8080 -e DB_PATH=/data/task.db -v $(pwd)/data:/data task-api

使用二进制文件

  1. 构建应用 bash make build 2. 运行二进制文件 bash ./bin/task-api

贡献

  1. Fork 项目
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m "Add some AmazingFeature")
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 打开 Pull Request

许可证

本项目采用 MIT 许可证 - 查看 LICENSE 文件了解详情。’ > task-api/README.md

echo “README文件创建完成”

创建API测试脚本

mkdir -p task-api/scripts echo ‘#!/bin/bash

API测试脚本

使用方法: ./scripts/test_api.sh

BASE_URL=”http://localhost:8080/api/v1” USERNAME=“testuser” PASSWORD=“password123” EMAIL=“test@example.com”

echo “=== Task API 测试脚本 ===” echo “基础URL: $BASE_URL” echo

测试健康检查

echo “1. 测试健康检查…” curl -s “$BASE_URL/health” | jq . echo

用户注册

echo “2. 用户注册…” REGISTER_RESPONSE=$(curl -s -X POST “$BASE_URL/auth/register”
-H “Content-Type: application/json”
-d “{ \“username\”: \“$USERNAME\”, \“email\”: \“$EMAIL\”, \“password\”: \“$PASSWORD\” }“) echo $REGISTER_RESPONSE | jq . echo

用户登录

echo “3. 用户登录…” LOGIN_RESPONSE=$(curl -s -X POST “$BASE_URL/auth/login”
-H “Content-Type: application/json”
-d “{ \“username\”: \“$USERNAME\”, \“password\”: \“$PASSWORD\” }“) echo $LOGIN_RESPONSE | jq .

提取token

TOKEN=$(echo $LOGIN_RESPONSE | jq -r “.data.token”) if [ “$TOKEN” = “null” ] || [ -z “$TOKEN” ]; then echo “登录失败,无法获取token” exit 1 fi echo “Token: $TOKEN” echo

创建任务

echo “4. 创建任务…” CREATE_TASK_RESPONSE=$(curl -s -X POST “$BASE_URL/tasks”
-H “Content-Type: application/json”
-H “Authorization: Bearer $TOKEN”
-d “{ \“title\”: \“测试任务1\”, \“description\”: \“这是一个测试任务\”, \“priority\”: \“high\”, \“due_date\”: \“2024-12-31T23:59:59Z\” }“) echo $CREATE_TASK_RESPONSE | jq .

提取任务ID

TASK_ID=$(echo $CREATE_TASK_RESPONSE | jq -r “.data.id”) echo “任务ID: $TASK_ID” echo

获取任务列表

echo “5. 获取任务列表…” curl -s -X GET “$BASE_URL/tasks?page=1&page_size=10”
-H “Authorization: Bearer $TOKEN” | jq . echo

获取单个任务

echo “6. 获取单个任务…” curl -s -X GET “$BASE_URL/tasks/$TASK_ID”
-H “Authorization: Bearer $TOKEN” | jq . echo

更新任务

echo “7. 更新任务状态…” UPDATE_TASK_RESPONSE=$(curl -s -X PUT “$BASE_URL/tasks/$TASK_ID”
-H “Content-Type: application/json”
-H “Authorization: Bearer $TOKEN”
-d “{ \“status\”: \“in_progress\” }“) echo $UPDATE_TASK_RESPONSE | jq . echo

获取任务统计

echo “8. 获取任务统计…” curl -s -X GET “$BASE_URL/tasks/stats”
-H “Authorization: Bearer $TOKEN” | jq . echo

删除任务

echo “9. 删除任务…” DELETE_TASK_RESPONSE=$(curl -s -X DELETE “$BASE_URL/tasks/$TASK_ID”
-H “Authorization: Bearer $TOKEN”) echo $DELETE_TASK_RESPONSE | jq . echo

echo “=== API测试完成 ===”’ > task-api/scripts/test_api.sh

chmod +x task-api/scripts/test_api.sh echo “API测试脚本创建完成”

创建性能测试脚本

echo ‘#!/bin/bash

性能测试脚本

使用wrk进行压力测试

BASE_URL=”http://localhost:8080”

echo “=== Task API 性能测试 ===” echo “基础URL: $BASE_URL” echo

检查wrk是否安装

if ! command -v wrk &> /dev/null; then echo “wrk未安装,请先安装wrk工具” echo “Ubuntu/Debian: sudo apt-get install wrk” echo “macOS: brew install wrk” exit 1 fi

健康检查接口压测

echo “1. 健康检查接口压测 (10秒, 10连接, 100线程)…” wrk -t100 -c10 -d10s “$BASE_URL/api/v1/health” echo

登录接口压测

echo “2. 登录接口压测 (10秒, 10连接, 100线程)…” wrk -t100 -c10 -d10s -s scripts/login.lua “$BASE_URL/api/v1/auth/login” echo

echo “=== 性能测试完成 ===”’ > task-api/scripts/benchmark.sh

chmod +x task-api/scripts/benchmark.sh echo “性能测试脚本创建完成”

创建wrk lua脚本

echo ‘wrk.method = “POST” wrk.body = “{ \“username\”: \“testuser\”, \“password\”: \“password123\” }” wrk.headers[“Content-Type”] = “application/json”’ > task-api/scripts/login.lua

echo “wrk lua脚本创建完成”

echo “\n=== 项目一:RESTful API服务创建完成 ===” echo “\n现在可以运行以下命令来测试项目:” echo “cd task-api” echo “make init # 初始化项目” echo “make run # 运行服务” echo “./scripts/test_api.sh # 测试API”

echo “\n\n## 项目二:CLI工具开发” echo “\n接下来我们开发一个命令行工具来管理任务。这个CLI工具将演示:” echo “- 命令行参数解析” echo “- 配置文件管理” echo “- 文件操作” echo “- JSON处理” echo “- 彩色输出” echo “- 子命令设计”

echo “\n### 创建CLI项目结构” echo “\nbash" echo "# 创建CLI工具项目" echo "mkdir -p task-cli" echo "cd task-cli" echo "\n# 初始化Go模块" echo "go mod init task-cli" echo "\n# 创建项目结构" echo "mkdir -p {cmd,internal/{config,task,ui},pkg/utils}" echo "

echo “\n### CLI工具主要文件”

创建CLI项目

mkdir -p task-cli/{cmd,internal/{config,task,ui},pkg/utils}

创建go.mod

echo ‘module task-cli

go 1.21

require ( github.com/fatih/color v1.16.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 )

require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )’ > task-cli/go.mod

echo “CLI项目go.mod创建完成”

创建主程序文件

echo ‘package main

import ( “fmt” “os”

"task-cli/cmd"

)

func main() { if err := cmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, “Error: %v\n”, err) os.Exit(1) } }’ > task-cli/main.go

echo “主程序文件创建完成”

创建根命令

echo ‘package cmd

import ( “fmt” “os”

"github.com/spf13/cobra"
"github.com/spf13/viper"
"task-cli/internal/config"

)

var cfgFile string

// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: “task-cli”, Short: “一个简单的任务管理CLI工具”, Long: `Task CLI是一个用Go语言开发的命令行任务管理工具。

它提供了以下功能: - 创建、查看、更新和删除任务 - 任务状态管理 - 任务优先级设置 - 任务搜索和过滤 - 配置管理 - 数据导入导出`, }

// Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() error { return rootCmd.Execute() }

func init() { cobra.OnInitialize(initConfig)

// 全局标志
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件路径 (默认: $HOME/.task-cli.yaml)")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "详细输出")

// 绑定标志到viper
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))

}

// initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != “” { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err)

	// Search config in home directory with name ".task-cli" (without extension).
	viper.AddConfigPath(home)
	viper.AddConfigPath(".")
	viper.SetConfigType("yaml")
	viper.SetConfigName(".task-cli")
}

viper.AutomaticEnv() // read in environment variables that match

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
	if viper.GetBool("verbose") {
		fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
	}
} else {
	// 创建默认配置
	config.CreateDefaultConfig()
}

}’ > task-cli/cmd/root.go

echo “根命令文件创建完成”

创建配置管理

echo ‘package config

import ( “fmt” “os” “path/filepath”

"github.com/spf13/viper"

)

type Config struct { DataFile string mapstructure:"data_file" DateFormat string mapstructure:"date_format" ColorMode bool mapstructure:"color_mode" PageSize int mapstructure:"page_size" }

// GetConfig 获取配置 func GetConfig() *Config { var cfg Config viper.Unmarshal(&cfg) return &cfg }

// CreateDefaultConfig 创建默认配置文件 func CreateDefaultConfig() error { home, err := os.UserHomeDir() if err != nil { return err }

configPath := filepath.Join(home, ".task-cli.yaml")

// 检查配置文件是否已存在
if _, err := os.Stat(configPath); err == nil {
	return nil // 配置文件已存在
}

// 设置默认值
viper.SetDefault("data_file", filepath.Join(home, ".task-cli-data.json"))
viper.SetDefault("date_format", "2006-01-02 15:04:05")
viper.SetDefault("color_mode", true)
viper.SetDefault("page_size", 10)

// 写入配置文件
viper.SetConfigFile(configPath)
err = viper.WriteConfig()
if err != nil {
	return fmt.Errorf("无法创建配置文件: %w", err)
}

fmt.Printf("默认配置文件已创建: %s\n", configPath)
return nil

}

// GetDataFilePath 获取数据文件路径 func GetDataFilePath() string { dataFile := viper.GetString(“data_file”) if dataFile == “” { home, _ := os.UserHomeDir() dataFile = filepath.Join(home, “.task-cli-data.json”) } return dataFile }’ > task-cli/internal/config/config.go

echo “配置管理文件创建完成”

创建任务模型

echo ‘package task

import ( “encoding/json” “fmt” “io/ioutil” “os” “sort” “strconv” “strings” “time”

"task-cli/internal/config"

)

type Status string type Priority string

const ( StatusPending Status = “pending” StatusInProgress Status = “in_progress” StatusCompleted Status = “completed” StatusCancelled Status = “cancelled” )

const ( PriorityLow Priority = “low” PriorityMedium Priority = “medium” PriorityHigh Priority = “high” PriorityUrgent Priority = “urgent” )

type Task struct { ID int json:"id" Title string json:"title" Description string json:"description" Status Status json:"status" Priority Priority json:"priority" CreatedAt time.Time json:"created_at" UpdatedAt time.Time json:"updated_at" DueDate *time.Time json:"due_date,omitempty" Tags []string json:"tags" }

type TaskManager struct { tasks []Task nextID int dataFile string }

// NewTaskManager 创建新的任务管理器 func NewTaskManager() *TaskManager { tm := &TaskManager{ tasks: make([]Task, 0), nextID: 1, dataFile: config.GetDataFilePath(), } tm.LoadTasks() return tm }

// LoadTasks 从文件加载任务 func (tm *TaskManager) LoadTasks() error { if _, err := os.Stat(tm.dataFile); os.IsNotExist(err) { return nil // 文件不存在,返回空任务列表 }

data, err := ioutil.ReadFile(tm.dataFile)
if err != nil {
	return fmt.Errorf("读取任务文件失败: %w", err)
}

if len(data) == 0 {
	return nil // 空文件
}

var fileData struct {
	Tasks  []Task `json:"tasks"`
	NextID int    `json:"next_id"`
}

err = json.Unmarshal(data, &fileData)
if err != nil {
	return fmt.Errorf("解析任务文件失败: %w", err)
}

tm.tasks = fileData.Tasks
tm.nextID = fileData.NextID

return nil

}

// SaveTasks 保存任务到文件 func (tm *TaskManager) SaveTasks() error { fileData := struct { Tasks []Task json:"tasks" NextID int json:"next_id" }{ Tasks: tm.tasks, NextID: tm.nextID, }

data, err := json.MarshalIndent(fileData, "", "  ")
if err != nil {
	return fmt.Errorf("序列化任务数据失败: %w", err)
}

err = ioutil.WriteFile(tm.dataFile, data, 0644)
if err != nil {
	return fmt.Errorf("写入任务文件失败: %w", err)
}

return nil

}

// CreateTask 创建新任务 func (tm *TaskManager) CreateTask(title, description string, priority Priority, dueDate *time.Time, tags []string) (*Task, error) { if strings.TrimSpace(title) == “” { return nil, fmt.Errorf(“任务标题不能为空”) }

task := Task{
	ID:          tm.nextID,
	Title:       strings.TrimSpace(title),
	Description: strings.TrimSpace(description),
	Status:      StatusPending,
	Priority:    priority,
	CreatedAt:   time.Now(),
	UpdatedAt:   time.Now(),
	DueDate:     dueDate,
	Tags:        tags,
}

tm.tasks = append(tm.tasks, task)
tm.nextID++

err := tm.SaveTasks()
if err != nil {
	return nil, err
}

return &task, nil

}

// GetTask 根据ID获取任务 func (tm *TaskManager) GetTask(id int) (*Task, error) { for i, task := range tm.tasks { if task.ID == id { return &tm.tasks[i], nil } } return nil, fmt.Errorf(“任务 ID %d 不存在”, id) }

// UpdateTask 更新任务 func (tm *TaskManager) UpdateTask(id int, updates map[string]interface{}) error { task, err := tm.GetTask(id) if err != nil { return err }

// 更新字段
for field, value := range updates {
	switch field {
	case "title":
		if title, ok := value.(string); ok && strings.TrimSpace(title) != "" {
			task.Title = strings.TrimSpace(title)
		}
	case "description":
		if desc, ok := value.(string); ok {
			task.Description = strings.TrimSpace(desc)
		}
	case "status":
		if status, ok := value.(Status); ok {
			task.Status = status
		}
	case "priority":
		if priority, ok := value.(Priority); ok {
			task.Priority = priority
		}
	case "due_date":
		if dueDate, ok := value.(*time.Time); ok {
			task.DueDate = dueDate
		}
	case "tags":
		if tags, ok := value.([]string); ok {
			task.Tags = tags
		}
	}
}

task.UpdatedAt = time.Now()

return tm.SaveTasks()

}

// DeleteTask 删除任务 func (tm *TaskManager) DeleteTask(id int) error { for i, task := range tm.tasks { if task.ID == id { tm.tasks = append(tm.tasks[:i], tm.tasks[i+1:]…) return tm.SaveTasks() } } return fmt.Errorf(“任务 ID %d 不存在”, id) }

// ListTasks 列出任务 func (tm *TaskManager) ListTasks(filters map[string]interface{}) []Task { var filtered []Task

for _, task := range tm.tasks {
	match := true

	// 状态过滤
	if status, ok := filters["status"]; ok {
		if task.Status != status.(Status) {
			match = false
		}
	}

	// 优先级过滤
	if priority, ok := filters["priority"]; ok {
		if task.Priority != priority.(Priority) {
			match = false
		}
	}

	// 搜索过滤
	if search, ok := filters["search"]; ok {
		searchStr := strings.ToLower(search.(string))
		if !strings.Contains(strings.ToLower(task.Title), searchStr) &&
			!strings.Contains(strings.ToLower(task.Description), searchStr) {
			match = false
		}
	}

	// 标签过滤
	if tag, ok := filters["tag"]; ok {
		tagStr := tag.(string)
		found := false
		for _, t := range task.Tags {
			if strings.EqualFold(t, tagStr) {
				found = true
				break
			}
		}
		if !found {
			match = false
		}
	}

	if match {
		filtered = append(filtered, task)
	}
}

// 排序
sort.Slice(filtered, func(i, j int) bool {
	// 按优先级排序
	priorityOrder := map[Priority]int{
		PriorityUrgent: 4,
		PriorityHigh:   3,
		PriorityMedium: 2,
		PriorityLow:    1,
	}

	if priorityOrder[filtered[i].Priority] != priorityOrder[filtered[j].Priority] {
		return priorityOrder[filtered[i].Priority] > priorityOrder[filtered[j].Priority]
	}

	// 按创建时间排序
	return filtered[i].CreatedAt.After(filtered[j].CreatedAt)
})

return filtered

}

// GetStats 获取任务统计 func (tm *TaskManager) GetStats() map[string]int { stats := map[string]int{ “total”: len(tm.tasks), “pending”: 0, “in_progress”: 0, “completed”: 0, “cancelled”: 0, “overdue”: 0, }

now := time.Now()
for _, task := range tm.tasks {
	switch task.Status {
	case StatusPending:
		stats["pending"]++
	case StatusInProgress:
		stats["in_progress"]++
	case StatusCompleted:
		stats["completed"]++
	case StatusCancelled:
		stats["cancelled"]++
	}

	// 检查过期任务
	if task.DueDate != nil && task.DueDate.Before(now) && task.Status != StatusCompleted {
		stats["overdue"]++
	}
}

return stats

}

// ParseStatus 解析状态字符串 func ParseStatus(s string) (Status, error) { switch strings.ToLower(s) { case “pending”, “p”: return StatusPending, nil case “in_progress”, “in-progress”, “progress”, “i”: return StatusInProgress, nil case “completed”, “complete”, “done”, “c”: return StatusCompleted, nil case “cancelled”, “canceled”, “cancel”, “x”: return StatusCancelled, nil default: return “”, fmt.Errorf(“无效的状态: %s”, s) } }

// ParsePriority 解析优先级字符串 func ParsePriority(s string) (Priority, error) { switch strings.ToLower(s) { case “low”, “l”, “1”: return PriorityLow, nil case “medium”, “med”, “m”, “2”: return PriorityMedium, nil case “high”, “h”, “3”: return PriorityHigh, nil case “urgent”, “u”, “4”: return PriorityUrgent, nil default: return “”, fmt.Errorf(“无效的优先级: %s”, s) } }

// ParseID 解析ID字符串 func ParseID(s string) (int, error) { id, err := strconv.Atoi(s) if err != nil { return 0, fmt.Errorf(“无效的任务ID: %s”, s) } if id <= 0 { return 0, fmt.Errorf(“任务ID必须大于0: %d”, id) } return id, nil }’ > task-cli/internal/task/task.go

echo “任务模型文件创建完成”

创建UI组件

echo ‘package ui

import ( “fmt” “strconv” “strings” “time”

"github.com/fatih/color"
"github.com/spf13/viper"
"task-cli/internal/task"

)

// 颜色定义 var ( ColorRed = color.New(color.FgRed) ColorGreen = color.New(color.FgGreen) ColorYellow = color.New(color.FgYellow) ColorBlue = color.New(color.FgBlue) ColorMagenta = color.New(color.FgMagenta) ColorCyan = color.New(color.FgCyan) ColorWhite = color.New(color.FgWhite) ColorBold = color.New(color.Bold) )

// PrintTask 打印单个任务 func PrintTask(t *task.Task, detailed bool) { if !viper.GetBool(“color_mode”) { color.NoColor = true }

// 状态颜色
var statusColor *color.Color
switch t.Status {
case task.StatusPending:
	statusColor = ColorYellow
case task.StatusInProgress:
	statusColor = ColorBlue
case task.StatusCompleted:
	statusColor = ColorGreen
case task.StatusCancelled:
	statusColor = ColorRed
}

// 优先级颜色
var priorityColor *color.Color
switch t.Priority {
case task.PriorityLow:
	priorityColor = ColorWhite
case task.PriorityMedium:
	priorityColor = ColorYellow
case task.PriorityHigh:
	priorityColor = ColorMagenta
case task.PriorityUrgent:
	priorityColor = ColorRed
}

if detailed {
	// 详细视图
	ColorBold.Printf("任务 #%d\n", t.ID)
	fmt.Printf("标题: %s\n", t.Title)
	if t.Description != "" {
		fmt.Printf("描述: %s\n", t.Description)
	}
	fmt.Printf("状态: ")
	statusColor.Printf("%s\n", string(t.Status))
	fmt.Printf("优先级: ")
	priorityColor.Printf("%s\n", string(t.Priority))
	fmt.Printf("创建时间: %s\n", t.CreatedAt.Format(viper.GetString("date_format")))
	fmt.Printf("更新时间: %s\n", t.UpdatedAt.Format(viper.GetString("date_format")))
	if t.DueDate != nil {
		fmt.Printf("截止时间: ")
		if t.DueDate.Before(time.Now()) && t.Status != task.StatusCompleted {
			ColorRed.Printf("%s (已过期)\n", t.DueDate.Format(viper.GetString("date_format")))
		} else {
			fmt.Printf("%s\n", t.DueDate.Format(viper.GetString("date_format")))
		}
	}
	if len(t.Tags) > 0 {
		fmt.Printf("标签: ")
		ColorCyan.Printf("%s\n", strings.Join(t.Tags, ", "))
	}
	fmt.Println()
} else {
	// 简洁视图
	ColorBold.Printf("#%-3d ", t.ID)
	statusColor.Printf("[%s] ", strings.ToUpper(string(t.Status)[:1]))
	priorityColor.Printf("(%s) ", strings.ToUpper(string(t.Priority)[:1]))
	fmt.Printf("%s", t.Title)
	if t.DueDate != nil && t.DueDate.Before(time.Now()) && t.Status != task.StatusCompleted {
		ColorRed.Printf(" [过期]")
	}
	fmt.Println()
}

}

// PrintTaskList 打印任务列表 func PrintTaskList(tasks []task.Task, page, pageSize int) { if len(tasks) == 0 { ColorYellow.Println(“没有找到任务”) return }

// 分页
start := (page - 1) * pageSize
end := start + pageSize
if end > len(tasks) {
	end = len(tasks)
}

if start >= len(tasks) {
	ColorRed.Println("页码超出范围")
	return
}

ColorBold.Printf("任务列表 (第 %d 页,共 %d 个任务)\n", page, len(tasks))
fmt.Println(strings.Repeat("-", 60))

for i := start; i < end; i++ {
	PrintTask(&tasks[i], false)
}

// 分页信息
totalPages := (len(tasks) + pageSize - 1) / pageSize
if totalPages > 1 {
	fmt.Println(strings.Repeat("-", 60))
	ColorCyan.Printf("第 %d/%d 页\n", page, totalPages)
}

}

// PrintStats 打印统计信息 func PrintStats(stats map[string]int) { ColorBold.Println(“任务统计”) fmt.Println(strings.Repeat(“-”, 30)) fmt.Printf(“总计: %d\n”, stats[“total”]) ColorYellow.Printf(“待处理: %d\n”, stats[“pending”]) ColorBlue.Printf(“进行中: %d\n”, stats[“in_progress”]) ColorGreen.Printf(“已完成: %d\n”, stats[“completed”]) ColorRed.Printf(“已取消: %d\n”, stats[“cancelled”]) if stats[“overdue”] > 0 { ColorRed.Printf(“已过期: %d\n”, stats[“overdue”]) } }

// PromptConfirm 确认提示 func PromptConfirm(message string) bool { ColorYellow.Printf(“%s (y/N): “, message) var response string fmt.Scanln(&response) return strings.ToLower(response) == “y” || strings.ToLower(response) == “yes” }

// PrintError 打印错误信息 func PrintError(err error) { ColorRed.Printf(“错误: %v\n”, err) }

// PrintSuccess 打印成功信息 func PrintSuccess(message string) { ColorGreen.Printf(“✓ %s\n”, message) }

// PrintWarning 打印警告信息 func PrintWarning(message string) { ColorYellow.Printf(“⚠ %s\n”, message) }

// PrintInfo 打印信息 func PrintInfo(message string) { ColorCyan.Printf(“ℹ %s\n”, message) }’ > task-cli/internal/ui/ui.go

echo “UI组件文件创建完成”

创建工具函数

echo ‘package utils

import ( “strings” “time” )

// ParseTags 解析标签字符串 func ParseTags(tagStr string) []string { if strings.TrimSpace(tagStr) == “” { return nil }

tags := strings.Split(tagStr, ",")
var result []string
for _, tag := range tags {
	tag = strings.TrimSpace(tag)
	if tag != "" {
		result = append(result, tag)
	}
}
return result

}

// ParseDate 解析日期字符串 func ParseDate(dateStr string) (*time.Time, error) { if strings.TrimSpace(dateStr) == “” { return nil, nil }

// 支持多种日期格式
formats := []string{
	"2006-01-02",
	"2006-01-02 15:04",
	"2006-01-02 15:04:05",
	"01-02",
	"01-02 15:04",
}

for _, format := range formats {
	if t, err := time.Parse(format, dateStr); err == nil {
		// 如果只有月日,设置为当前年份
		if !strings.Contains(dateStr, "-") || len(strings.Split(dateStr, "-")) == 2 {
			now := time.Now()
			t = time.Date(now.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local)
			// 如果日期已过,设置为明年
			if t.Before(now) {
				t = t.AddDate(1, 0, 0)
			}
		}
		return &t, nil
	}
}

return nil, fmt.Errorf("无效的日期格式: %s", dateStr)

}

// FormatDuration 格式化持续时间 func FormatDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf(“%.0f秒”, d.Seconds()) } if d < time.Hour { return fmt.Sprintf(“%.0f分钟”, d.Minutes()) } if d < 24*time.Hour { return fmt.Sprintf(“%.1f小时”, d.Hours()) } return fmt.Sprintf(“%.1f天”, d.Hours()/24) }’ > task-cli/pkg/utils/utils.go

echo “工具函数文件创建完成”

修复工具函数中的导入

echo ‘package utils

import ( “fmt” “strings” “time” )

// ParseTags 解析标签字符串 func ParseTags(tagStr string) []string { if strings.TrimSpace(tagStr) == “” { return nil }

tags := strings.Split(tagStr, ",")
var result []string
for _, tag := range tags {
	tag = strings.TrimSpace(tag)
	if tag != "" {
		result = append(result, tag)
	}
}
return result

}

// ParseDate 解析日期字符串 func ParseDate(dateStr string) (*time.Time, error) { if strings.TrimSpace(dateStr) == “” { return nil, nil }

// 支持多种日期格式
formats := []string{
	"2006-01-02",
	"2006-01-02 15:04",
	"2006-01-02 15:04:05",
	"01-02",
	"01-02 15:04",
}

for _, format := range formats {
	if t, err := time.Parse(format, dateStr); err == nil {
		// 如果只有月日,设置为当前年份
		if !strings.Contains(dateStr, "-") || len(strings.Split(dateStr, "-")) == 2 {
			now := time.Now()
			t = time.Date(now.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local)
			// 如果日期已过,设置为明年
			if t.Before(now) {
				t = t.AddDate(1, 0, 0)
			}
		}
		return &t, nil
	}
}

return nil, fmt.Errorf("无效的日期格式: %s", dateStr)

}

// FormatDuration 格式化持续时间 func FormatDuration(d time.Duration) string { if d < time.Minute { return fmt.Sprintf(“%.0f秒”, d.Seconds()) } if d < time.Hour { return fmt.Sprintf(“%.0f分钟”, d.Minutes()) } if d < 24*time.Hour { return fmt.Sprintf(“%.1f小时”, d.Hours()) } return fmt.Sprintf(“%.1f天”, d.Hours()/24) }’ > task-cli/pkg/utils/utils.go

echo “工具函数文件已修复”

创建add命令

echo ‘package cmd

import ( “github.com/spf13/cobra” “task-cli/internal/task” “task-cli/internal/ui” “task-cli/pkg/utils” )

var ( addTitle string addDescription string addPriority string addDueDate string addTags string )

// addCmd represents the add command var addCmd = &cobra.Command{ Use: “add”, Short: “创建新任务”, Long: `创建一个新的任务。

示例: task-cli add “完成项目文档” –priority high –due “2024-12-31” –tags “工作,文档” task-cli add “买菜” -p low -d “12-25 18:00” -t “生活,购物”`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 解析优先级
	priority, err := task.ParsePriority(addPriority)
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 解析截止日期
	dueDate, err := utils.ParseDate(addDueDate)
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 解析标签
	tags := utils.ParseTags(addTags)

	// 创建任务
	newTask, err := tm.CreateTask(args[0], addDescription, priority, dueDate, tags)
	if err != nil {
		ui.PrintError(err)
		return
	}

	ui.PrintSuccess("任务创建成功")
	ui.PrintTask(newTask, true)
},

}

func init() { rootCmd.AddCommand(addCmd)

addCmd.Flags().StringVarP(&addDescription, "description", "d", "", "任务描述")
addCmd.Flags().StringVarP(&addPriority, "priority", "p", "medium", "任务优先级 (low, medium, high, urgent)")
addCmd.Flags().StringVar(&addDueDate, "due", "", "截止日期 (YYYY-MM-DD 或 MM-DD)")
addCmd.Flags().StringVarP(&addTags, "tags", "t", "", "任务标签,用逗号分隔")

}’ > task-cli/cmd/add.go

echo “add命令文件创建完成”

创建list命令

echo ‘package cmd

import ( “strconv”

"github.com/spf13/cobra"
"github.com/spf13/viper"
"task-cli/internal/task"
"task-cli/internal/ui"

)

var ( listStatus string listPriority string listSearch string listTag string listPage int listPageSize int )

// listCmd represents the list command var listCmd = &cobra.Command{ Use: “list”, Short: “列出任务”, Long: `列出任务,支持过滤和分页。

示例: task-cli list # 列出所有任务 task-cli list –status pending # 列出待处理任务 task-cli list –priority high # 列出高优先级任务 task-cli list –search “文档” # 搜索包含”文档”的任务 task-cli list –tag “工作” # 列出标签为”工作”的任务 task-cli list –page 2 –page-size 5 # 第2页,每页5个任务`, Aliases: []string{“ls”}, Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 构建过滤条件
	filters := make(map[string]interface{})

	if listStatus != "" {
		status, err := task.ParseStatus(listStatus)
		if err != nil {
			ui.PrintError(err)
			return
		}
		filters["status"] = status
	}

	if listPriority != "" {
		priority, err := task.ParsePriority(listPriority)
		if err != nil {
			ui.PrintError(err)
			return
		}
		filters["priority"] = priority
	}

	if listSearch != "" {
		filters["search"] = listSearch
	}

	if listTag != "" {
		filters["tag"] = listTag
	}

	// 获取任务列表
	tasks := tm.ListTasks(filters)

	// 使用配置的页面大小
	pageSize := listPageSize
	if pageSize == 0 {
		pageSize = viper.GetInt("page_size")
	}

	// 打印任务列表
	ui.PrintTaskList(tasks, listPage, pageSize)
},

}

func init() { rootCmd.AddCommand(listCmd)

listCmd.Flags().StringVarP(&listStatus, "status", "s", "", "按状态过滤 (pending, in_progress, completed, cancelled)")
listCmd.Flags().StringVarP(&listPriority, "priority", "p", "", "按优先级过滤 (low, medium, high, urgent)")
listCmd.Flags().StringVar(&listSearch, "search", "", "搜索任务标题和描述")
listCmd.Flags().StringVarP(&listTag, "tag", "t", "", "按标签过滤")
listCmd.Flags().IntVar(&listPage, "page", 1, "页码")
listCmd.Flags().IntVar(&listPageSize, "page-size", 0, "每页任务数量")

}’ > task-cli/cmd/list.go

echo “list命令文件创建完成”

创建show命令

echo ‘package cmd

import ( “github.com/spf13/cobra” “task-cli/internal/task” “task-cli/internal/ui” )

// showCmd represents the show command var showCmd = &cobra.Command{ Use: “show ”, Short: “显示任务详情”, Long: `显示指定任务的详细信息。

示例: task-cli show 1 # 显示ID为1的任务详情`, Aliases: []string{“get”, “view”}, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 解析任务ID
	id, err := task.ParseID(args[0])
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 获取任务
	taskItem, err := tm.GetTask(id)
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 显示任务详情
	ui.PrintTask(taskItem, true)
},

}

func init() { rootCmd.AddCommand(showCmd) }’ > task-cli/cmd/show.go

echo “show命令文件创建完成”

创建update命令

echo ‘package cmd

import ( “github.com/spf13/cobra” “task-cli/internal/task” “task-cli/internal/ui” “task-cli/pkg/utils” )

var ( updateTitle string updateDescription string updateStatus string updatePriority string updateDueDate string updateTags string )

// updateCmd represents the update command var updateCmd = &cobra.Command{ Use: “update ”, Short: “更新任务”, Long: `更新指定任务的信息。

示例: task-cli update 1 –status completed # 标记任务为已完成 task-cli update 1 –priority high # 设置高优先级 task-cli update 1 –title “新标题” # 更新标题 task-cli update 1 –due “2024-12-31” # 更新截止日期`, Aliases: []string{“edit”, “modify”}, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 解析任务ID
	id, err := task.ParseID(args[0])
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 构建更新数据
	updates := make(map[string]interface{})

	if updateTitle != "" {
		updates["title"] = updateTitle
	}

	if updateDescription != "" {
		updates["description"] = updateDescription
	}

	if updateStatus != "" {
		status, err := task.ParseStatus(updateStatus)
		if err != nil {
			ui.PrintError(err)
			return
		}
		updates["status"] = status
	}

	if updatePriority != "" {
		priority, err := task.ParsePriority(updatePriority)
		if err != nil {
			ui.PrintError(err)
			return
		}
		updates["priority"] = priority
	}

	if updateDueDate != "" {
		dueDate, err := utils.ParseDate(updateDueDate)
		if err != nil {
			ui.PrintError(err)
			return
		}
		updates["due_date"] = dueDate
	}

	if updateTags != "" {
		tags := utils.ParseTags(updateTags)
		updates["tags"] = tags
	}

	if len(updates) == 0 {
		ui.PrintWarning("没有指定要更新的字段")
		return
	}

	// 更新任务
	err = tm.UpdateTask(id, updates)
	if err != nil {
		ui.PrintError(err)
		return
	}

	ui.PrintSuccess("任务更新成功")

	// 显示更新后的任务
	updatedTask, _ := tm.GetTask(id)
	ui.PrintTask(updatedTask, true)
},

}

func init() { rootCmd.AddCommand(updateCmd)

updateCmd.Flags().StringVar(&updateTitle, "title", "", "更新任务标题")
updateCmd.Flags().StringVarP(&updateDescription, "description", "d", "", "更新任务描述")
updateCmd.Flags().StringVarP(&updateStatus, "status", "s", "", "更新任务状态 (pending, in_progress, completed, cancelled)")
updateCmd.Flags().StringVarP(&updatePriority, "priority", "p", "", "更新任务优先级 (low, medium, high, urgent)")
updateCmd.Flags().StringVar(&updateDueDate, "due", "", "更新截止日期 (YYYY-MM-DD 或 MM-DD)")
updateCmd.Flags().StringVarP(&updateTags, "tags", "t", "", "更新任务标签,用逗号分隔")

}’ > task-cli/cmd/update.go

echo “update命令文件创建完成”

创建delete命令

echo ‘package cmd

import ( “github.com/spf13/cobra” “task-cli/internal/task” “task-cli/internal/ui” )

var deleteForce bool

// deleteCmd represents the delete command var deleteCmd = &cobra.Command{ Use: “delete ”, Short: “删除任务”, Long: `删除指定的任务。

示例: task-cli delete 1 # 删除ID为1的任务(需要确认) task-cli delete 1 –force # 强制删除,不需要确认`, Aliases: []string{“del”, “rm”, “remove”}, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 解析任务ID
	id, err := task.ParseID(args[0])
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 获取任务信息用于确认
	taskItem, err := tm.GetTask(id)
	if err != nil {
		ui.PrintError(err)
		return
	}

	// 确认删除
	if !deleteForce {
		ui.PrintTask(taskItem, false)
		if !ui.PromptConfirm("确定要删除这个任务吗?") {
			ui.PrintInfo("取消删除")
			return
		}
	}

	// 删除任务
	err = tm.DeleteTask(id)
	if err != nil {
		ui.PrintError(err)
		return
	}

	ui.PrintSuccess("任务删除成功")
},

}

func init() { rootCmd.AddCommand(deleteCmd)

deleteCmd.Flags().BoolVarP(&deleteForce, "force", "f", false, "强制删除,不需要确认")

}’ > task-cli/cmd/delete.go

echo “delete命令文件创建完成”

创建stats命令

echo ‘package cmd

import ( “github.com/spf13/cobra” “task-cli/internal/task” “task-cli/internal/ui” )

// statsCmd represents the stats command var statsCmd = &cobra.Command{ Use: “stats”, Short: “显示任务统计”, Long: `显示任务的统计信息,包括总数、各状态任务数量等。

示例: task-cli stats # 显示任务统计信息`, Aliases: []string{“statistics”, “summary”}, Run: func(cmd *cobra.Command, args []string) { tm := task.NewTaskManager()

	// 获取统计信息
	stats := tm.GetStats()

	// 显示统计信息
	ui.PrintStats(stats)
},

}

func init() { rootCmd.AddCommand(statsCmd) }’ > task-cli/cmd/stats.go

echo “stats命令文件创建完成”

创建CLI项目的Makefile

echo ‘BINARY_NAME=task-cli BUILD_DIR=bin MAIN_FILE=main.go

.PHONY: help build run clean test fmt vet deps install

默认目标

all: build

显示帮助信息

help: @echo “可用命令:” @echo “ build - 构建应用” @echo “ run - 运行应用” @echo “ clean - 清理构建文件” @echo “ test - 运行测试” @echo “ fmt - 格式化代码” @echo “ vet - 代码检查” @echo “ deps - 下载依赖” @echo “ install - 安装到系统”

下载依赖

deps: go mod download go mod tidy

构建应用

build: deps @mkdir -p $(BUILD_DIR) go build -o $(BUILD_DIR)/$(BINARY_NAME) $(MAIN_FILE)

运行应用

run: build ./$(BUILD_DIR)/$(BINARY_NAME)

清理构建文件

clean: @rm -rf $(BUILD_DIR) go clean

运行测试

test: go test -v ./…

格式化代码

fmt: go fmt ./…

代码检查

vet: go vet ./…

安装到系统

install: build go install

开发模式(监听文件变化)

dev: @echo “开发模式需要安装 air 工具” @echo “安装命令: go install github.com/cosmtrek/air@latest” air

交叉编译

build-all: deps @mkdir -p $(BUILD_DIR) GOOS=linux GOARCH=amd64 go build -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 $(MAIN_FILE) GOOS=windows GOARCH=amd64 go build -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(MAIN_FILE) GOOS=darwin GOARCH=amd64 go build -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 $(MAIN_FILE) GOOS=darwin GOARCH=arm64 go build -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 $(MAIN_FILE)’ > task-cli/Makefile

echo “CLI项目Makefile创建完成”

创建CLI项目的README

echo ‘# Task CLI

一个用Go语言开发的简单任务管理命令行工具。

功能特性

  • ✅ 创建、查看、更新和删除任务
  • 🏷️ 任务标签管理
  • 📅 截止日期设置
  • 🎯 优先级管理
  • 🔍 任务搜索和过滤
  • 📊 任务统计
  • 🎨 彩色输出
  • ⚙️ 配置文件支持
  • 📄 分页显示

安装

从源码构建

git clone <repository-url>
cd task-cli
make build

安装到系统

make install

使用方法

基本命令

# 创建任务
task-cli add "完成项目文档" --priority high --due "2024-12-31" --tags "工作,文档"

# 列出所有任务
task-cli list

# 列出待处理任务
task-cli list --status pending

# 搜索任务
task-cli list --search "文档"

# 显示任务详情
task-cli show 1

# 更新任务状态
task-cli update 1 --status completed

# 删除任务
task-cli delete 1

# 显示统计信息
task-cli stats

命令详解

创建任务 (add)

task-cli add "任务标题" [选项]

选项:
  -d, --description string   任务描述
  -p, --priority string      优先级 (low, medium, high, urgent) (默认 "medium")
      --due string           截止日期 (YYYY-MM-DD 或 MM-DD)
  -t, --tags string          标签,用逗号分隔

列出任务 (list)

task-cli list [选项]

选项:
  -s, --status string        按状态过滤 (pending, in_progress, completed, cancelled)
  -p, --priority string      按优先级过滤 (low, medium, high, urgent)
      --search string        搜索任务标题和描述
  -t, --tag string           按标签过滤
      --page int             页码 (默认 1)
      --page-size int        每页任务数量

更新任务 (update)

task-cli update <id> [选项]

选项:
      --title string         更新任务标题
  -d, --description string   更新任务描述
  -s, --status string        更新任务状态
  -p, --priority string      更新任务优先级
      --due string           更新截止日期
  -t, --tags string          更新任务标签

状态说明

  • pending (p) - 待处理
  • in_progress (i) - 进行中
  • completed © - 已完成
  • cancelled (x) - 已取消

优先级说明

  • low (l, 1) - 低优先级
  • medium (m, 2) - 中等优先级
  • high (h, 3) - 高优先级
  • urgent (u, 4) - 紧急

配置

配置文件位置:~/.task-cli.yaml

data_file: ~/.task-cli-data.json  # 数据文件路径
date_format: "2006-01-02 15:04:05"  # 日期格式
color_mode: true                   # 彩色输出
page_size: 10                      # 默认页面大小

数据存储

任务数据以JSON格式存储在 ~/.task-cli-data.json 文件中。

开发

项目结构

task-cli/
├── cmd/                 # 命令定义
├── internal/            # 内部包
│   ├── config/          # 配置管理
│   ├── task/            # 任务模型
│   └── ui/              # 用户界面
├── pkg/                 # 公共包
│   └── utils/           # 工具函数
├── main.go              # 主程序
├── go.mod               # Go模块
├── Makefile             # 构建脚本
└── README.md            # 项目说明

可用命令

make help          # 查看所有可用命令
make build         # 构建应用
make run           # 运行应用
make test          # 运行测试
make fmt           # 格式化代码
make vet           # 代码检查
make clean         # 清理构建文件
make install       # 安装到系统
make build-all     # 交叉编译

许可证

MIT License’ > task-cli/README.md

echo “CLI项目README创建完成”

echo “\n=== 项目二:CLI工具开发完成 ===” echo “\n现在可以运行以下命令来测试CLI工具:” echo “cd task-cli” echo “make build # 构建CLI工具” echo “./bin/task-cli add \“测试任务\” –priority high # 创建任务” echo “./bin/task-cli list # 列出任务” echo “./bin/task-cli stats # 查看统计”

echo “\n\n## 项目实战总结” echo “\n通过本章的学习,我们完成了两个完整的Go语言项目:” echo “\n### 1. RESTful API服务 (task-api)” echo “- 技术栈: Gin + GORM + SQLite/PostgreSQL + JWT” echo “- 功能: 用户认证、任务CRUD、分页查询、搜索过滤” echo “- 架构: 分层架构(Handler-Service-Repository)” echo “- 特性: 中间件、错误处理、配置管理、Docker支持” echo “\n### 2. CLI工具 (task-cli)” echo “- 技术栈: Cobra + Viper + Color” echo “- 功能: 任务管理、配置管理、彩色输出、分页显示” echo “- 架构: 命令模式、模块化设计” echo “- 特性: 子命令、参数解析、数据持久化、交叉编译”

echo “\n### 学到的关键技能” echo “\n#### Web开发” echo “- RESTful API设计原则” echo “- HTTP路由和中间件” echo “- 数据库操作和ORM使用” echo “- JWT认证和授权” echo “- 请求验证和错误处理” echo “- API文档和测试”

echo “\n#### CLI开发” echo “- 命令行参数解析” echo “- 子命令设计模式” echo “- 配置文件管理” echo “- 用户交互和输出格式化” echo “- 数据持久化” echo “- 跨平台构建”

echo “\n#### 项目管理” echo “- 项目结构组织” echo “- 依赖管理” echo “- 构建和部署” echo “- 文档编写” echo “- 测试策略” echo “- Docker容器化”

echo “\n#### Go语言进阶” echo “- 接口设计和实现” echo “- 错误处理最佳实践” echo “- 并发编程应用” echo “- 包和模块管理” echo “- 性能优化” echo “- 代码组织和重构”

echo “\n### 下一步建议” echo “\n1. 扩展功能” echo “ - 为API添加更多业务功能” echo “ - 为CLI添加数据导入导出” echo “ - 实现任务提醒和通知”

echo “\n2. 性能优化” echo “ - 添加缓存层” echo “ - 数据库查询优化” echo “ - 并发处理优化”

echo “\n3. 部署和运维” echo “ - CI/CD流水线” echo “ - 监控和日志” echo “ - 容器编排”

echo “\n4. 测试完善” echo “ - 单元测试覆盖” echo “ - 集成测试” echo “ - 性能测试”

echo “\n5. 学习更多” echo “ - 微服务架构” echo “ - 消息队列” echo “ - 分布式系统” echo “ - 云原生技术”

echo “\n恭喜你完成了Go语言的项目实战!通过这两个项目,你已经掌握了Go语言开发的核心技能,” echo “可以开始构建更复杂的应用程序了。继续实践和学习,你将成为一名优秀的Go开发者!” “`

由于内容较长,我需要继续完成剩余部分。让我继续添加主程序和其他必要文件。