第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
安装依赖
make deps- 运行应用
bash make run
- 运行应用
或者使用开发模式(热重载)
make dev使用Docker
- 构建镜像
bash make docker-build
- 构建镜像
运行容器
make docker-runAPI文档
认证相关
用户注册
”`
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
{ “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
{ “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
- 构建镜像
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
使用二进制文件
- 构建应用
bash make build2. 运行二进制文件bash ./bin/task-api
贡献
- Fork 项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m "Add some AmazingFeature") - 推送到分支 (
git push origin feature/AmazingFeature) - 打开 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
示例: 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
示例: 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
示例: 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开发者!” “`
由于内容较长,我需要继续完成剩余部分。让我继续添加主程序和其他必要文件。