diff --git a/backend/.gitignore b/backend/.gitignore index 2eea525..17a52bf 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,2 @@ -.env \ No newline at end of file +.env +progress_wall.db \ No newline at end of file diff --git a/backend/README_dev.md b/backend/README_dev.md index 2d5c44b..94c0db6 100644 --- a/backend/README_dev.md +++ b/backend/README_dev.md @@ -16,7 +16,9 @@ backend/ ├── controllers/ # 控制器层 │ └── ... ├── database/ # 数据库连接 -│ └── database.go +| |- seed.go # 初始权限组设定 +| |- migrate.go # 数据库迁移(自动建表) +│ └── database.go # 数据库连接 ├── middleware/ # 中间件 │ └── ... ├── models/ # 数据模型 diff --git a/backend/database/database.go b/backend/database/database.go new file mode 100644 index 0000000..4364294 --- /dev/null +++ b/backend/database/database.go @@ -0,0 +1,51 @@ +package database + +import ( + "fmt" + "log" + "progress-wall-backend/config" + + "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// InitDB 初始化数据库连接 +func InitDB(cfg *config.Config) (*gorm.DB, error) { + var db *gorm.DB + var err error + + // 配置GORM日志 + gormConfig := &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + } + + switch cfg.DB.Type { + case "mysql": + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Port, cfg.DB.Name) + db, err = gorm.Open(mysql.Open(dsn), gormConfig) + case "sqlite": + db, err = gorm.Open(sqlite.Open(cfg.DB.Name+".db"), gormConfig) + default: + return nil, fmt.Errorf("不支持的数据库类型: %s", cfg.DB.Type) + } + + if err != nil { + return nil, err + } + + // 配置连接池 + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + + // 设置连接池参数 + sqlDB.SetMaxIdleConns(10) + sqlDB.SetMaxOpenConns(100) + + log.Printf("数据库连接成功: %s", cfg.DB.Type) + return db, nil +} diff --git a/backend/database/migrate.go b/backend/database/migrate.go new file mode 100644 index 0000000..7bff6b8 --- /dev/null +++ b/backend/database/migrate.go @@ -0,0 +1,44 @@ +package database + +import ( + "log" + "progress-wall-backend/models" + + "gorm.io/gorm" +) + +// Migrate 执行数据库迁移 +func Migrate(db *gorm.DB) error { + log.Println("开始执行数据库迁移...") + + // 迁移所有模型 + err := db.AutoMigrate( + // 用户和权限相关 + &models.User{}, + &models.UserPermission{}, + &models.UserPermissionGroup{}, + &models.UserPermissionGroupUser{}, + &models.UserPermissionGroup{}, + + // 项目和看板相关 + &models.Project{}, + &models.ProjectMember{}, + &models.Board{}, + + // 任务相关 + &models.Column{}, + &models.Task{}, + &models.Comment{}, + &models.Attachment{}, + &models.Label{}, + &models.TaskLabel{}, + ) + + if err != nil { + log.Printf("数据库迁移失败: %v", err) + return err + } + + log.Println("数据库迁移完成") + return nil +} diff --git a/backend/database/seed.go b/backend/database/seed.go new file mode 100644 index 0000000..7da972b --- /dev/null +++ b/backend/database/seed.go @@ -0,0 +1,45 @@ +package database + +import ( + "log" + "progress-wall-backend/models" + + "gorm.io/gorm" +) + +// Seed 初始化基础数据 +func Seed(db *gorm.DB) error { + log.Println("开始初始化基础数据...") + + // 创建默认用户 + if err := createDefaultUsers(db); err != nil { + return err + } + + // 创建默认权限 + if err := createDefaultPermissions(db); err != nil { + return err + } + + log.Println("基础数据初始化完成") + return nil +} + +func createDefaultUsers(_ *gorm.DB) error { + // TODO: 创建默认用户,目前密码加密方法还没确定 + return nil +} + +func createDefaultPermissions(db *gorm.DB) error { + adminPermissionGroup := models.UserPermissionGroup{ + GroupName: "admin", + PermissionStrings: "*", + } + db.Create(&adminPermissionGroup) + guestPermissionGroup := models.UserPermissionGroup{ + GroupName: "guest", + PermissionStrings: "users.list;users.detail", + } + db.Create(&guestPermissionGroup) + return nil +} diff --git a/backend/docs/database/ER-structure.svg b/backend/docs/database/ER-structure.svg new file mode 100644 index 0000000..86f4652 --- /dev/null +++ b/backend/docs/database/ER-structure.svg @@ -0,0 +1,3373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/docs/database/structure.mwb b/backend/docs/database/structure.mwb new file mode 100644 index 0000000..9a02f91 Binary files /dev/null and b/backend/docs/database/structure.mwb differ diff --git a/backend/go.mod b/backend/go.mod index 04afd44..ee2715d 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -2,4 +2,17 @@ module progress-wall-backend go 1.21 -require github.com/joho/godotenv v1.4.0 +require ( + github.com/joho/godotenv v1.4.0 + golang.org/x/crypto v0.15.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect +) diff --git a/backend/go.sum b/backend/go.sum index 8c9f290..fba38f4 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,2 +1,19 @@ +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/backend/main.go b/backend/main.go index 5212720..59061af 100644 --- a/backend/main.go +++ b/backend/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "progress-wall-backend/config" + "progress-wall-backend/database" ) func main() { @@ -19,5 +20,21 @@ func main() { fmt.Printf("- JWT密钥长度: %d\n", len(cfg.JWT.Secret)) fmt.Printf("- CORS允许来源: %s\n", cfg.CORS.AllowOrigins) - log.Println("配置加载完成") + // 初始化数据库 + db, err := database.InitDB(cfg) + if err != nil { + log.Fatal("数据库初始化失败:", err) + } + + // 执行数据库迁移 + if err := database.Migrate(db); err != nil { + log.Fatal("数据库迁移失败:", err) + } + + // 初始化基础数据 + if err := database.Seed(db); err != nil { + log.Fatal("初始化基础数据失败:", err) + } + + log.Println("数据库初始化完成") } diff --git a/backend/models/attachment.go b/backend/models/attachment.go new file mode 100644 index 0000000..74f734b --- /dev/null +++ b/backend/models/attachment.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Attachment 附件表 +type Attachment struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Filename string `json:"filename" gorm:"size:255;not null"` + OriginalName string `json:"original_name" gorm:"size:255;not null"` + FilePath string `json:"file_path" gorm:"size:500;not null"` + FileSize int64 `json:"file_size" gorm:"not null;comment:'文件大小(字节)'"` + MimeType string `json:"mime_type" gorm:"size:100;not null"` + TaskID uint `json:"task_id" gorm:"not null;index"` + UploaderID uint `json:"uploader_id" gorm:"not null;index"` + Status AttachmentStatus `json:"status" gorm:"type:tinyint;default:1;comment:'附件状态:1=正常,2=删除'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Task Task `json:"task,omitempty" gorm:"foreignKey:TaskID"` + Uploader User `json:"uploader,omitempty" gorm:"foreignKey:UploaderID"` +} + +// AttachmentStatus 附件状态枚举 +type AttachmentStatus int + +const ( + AttachmentStatusNormal AttachmentStatus = 1 // 正常 + AttachmentStatusDeleted AttachmentStatus = 2 // 删除 +) diff --git a/backend/models/board.go b/backend/models/board.go new file mode 100644 index 0000000..280d240 --- /dev/null +++ b/backend/models/board.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Board 看板表 +type Board struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"size:100;not null"` + Description string `json:"description" gorm:"type:text"` + Color string `json:"color" gorm:"size:7;default:'#3498db';comment:'看板颜色,十六进制格式'"` + Status BoardStatus `json:"status" gorm:"type:tinyint;default:1;comment:'看板状态:1=活跃,2=归档'"` + ProjectID uint `json:"project_id" gorm:"not null;index"` + OwnerID uint `json:"owner_id" gorm:"not null;index"` + Position int `json:"position" gorm:"default:0;comment:'看板在项目中的排序位置'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"` + Owner User `json:"owner,omitempty" gorm:"foreignKey:OwnerID"` + Columns []Column `json:"columns,omitempty" gorm:"foreignKey:BoardID"` +} + +// BoardStatus 看板状态枚举 +type BoardStatus int + +const ( + BoardStatusActive BoardStatus = 1 // 活跃 + BoardStatusArchived BoardStatus = 2 // 归档 +) diff --git a/backend/models/column.go b/backend/models/column.go new file mode 100644 index 0000000..81c3c5e --- /dev/null +++ b/backend/models/column.go @@ -0,0 +1,33 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Column 列表 +type Column struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"size:50;not null"` + Description string `json:"description" gorm:"size:255"` + Color string `json:"color" gorm:"size:7;default:'#95a5a6';comment:'列颜色,十六进制格式'"` + Position int `json:"position" gorm:"not null;comment:'列在看板中的排序位置'"` + BoardID uint `json:"board_id" gorm:"not null;index"` + Status ColumnStatus `json:"status" gorm:"type:tinyint;default:1;comment:'列状态:1=正常,2=禁用'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Board Board `json:"board,omitempty" gorm:"foreignKey:BoardID"` + Tasks []Task `json:"tasks,omitempty" gorm:"foreignKey:ColumnID"` +} + +// ColumnStatus 列状态枚举 +type ColumnStatus int + +const ( + ColumnStatusActive ColumnStatus = 1 // 正常 + ColumnStatusDisabled ColumnStatus = 2 // 禁用 +) diff --git a/backend/models/comment.go b/backend/models/comment.go new file mode 100644 index 0000000..8dc1c42 --- /dev/null +++ b/backend/models/comment.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Comment 评论表 +type Comment struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Content string `json:"content" gorm:"type:text;not null"` + TaskID uint `json:"task_id" gorm:"not null;index"` + UserID uint `json:"user_id" gorm:"not null;index"` + ParentID *uint `json:"parent_id" gorm:"index;comment:'父评论ID,用于回复评论'"` + Status CommentStatus `json:"status" gorm:"type:tinyint;default:1;comment:'评论状态:1=正常,2=隐藏,3=删除'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Task Task `json:"task,omitempty" gorm:"foreignKey:TaskID"` + User User `json:"user,omitempty" gorm:"foreignKey:UserID"` + Parent *Comment `json:"parent,omitempty" gorm:"foreignKey:ParentID"` + Replies []Comment `json:"replies,omitempty" gorm:"foreignKey:ParentID"` +} + +// CommentStatus 评论状态枚举 +type CommentStatus int + +const ( + CommentStatusNormal CommentStatus = 1 // 正常 + CommentStatusHidden CommentStatus = 2 // 隐藏 + CommentStatusDeleted CommentStatus = 3 // 删除 +) diff --git a/backend/models/label.go b/backend/models/label.go new file mode 100644 index 0000000..d637083 --- /dev/null +++ b/backend/models/label.go @@ -0,0 +1,33 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Label 标签表 +type Label struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"size:50;not null"` + Color string `json:"color" gorm:"size:7;default:'#3498db';comment:'标签颜色,十六进制格式'"` + ProjectID uint `json:"project_id" gorm:"not null;index"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"` + Tasks []Task `json:"tasks,omitempty" gorm:"many2many:task_labels"` +} + +// TaskLabel 任务标签关联表 +type TaskLabel struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + TaskID uint `json:"task_id" gorm:"not null;index"` + LabelID uint `json:"label_id" gorm:"not null;index"` + + // 关联关系 + Task Task `json:"task,omitempty" gorm:"foreignKey:TaskID"` + Label Label `json:"label,omitempty" gorm:"foreignKey:LabelID"` +} diff --git a/backend/models/project.go b/backend/models/project.go new file mode 100644 index 0000000..327da47 --- /dev/null +++ b/backend/models/project.go @@ -0,0 +1,60 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Project 项目表 +type Project struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"size:100;not null"` + Description string `json:"description" gorm:"type:text"` + Status ProjectStatus `json:"status" gorm:"type:tinyint;default:1;comment:'项目状态:1=进行中,2=已完成,3=已暂停,4=已取消'"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + OwnerID uint `json:"owner_id" gorm:"not null;index"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Owner User `json:"owner,omitempty" gorm:"foreignKey:OwnerID" comment:"项目所有者,必须有项目根权限*.project.project_id.*"` + Boards []Board `json:"boards,omitempty" gorm:"foreignKey:ProjectID"` + Members []User `json:"members,omitempty" gorm:"many2many:project_members"` +} + +// ProjectMember 项目成员关联表 +type ProjectMember struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + ProjectID uint `json:"project_id" gorm:"not null;index"` + UserID uint `json:"user_id" gorm:"not null;index"` + PorjectPermission string `json:"project_permission" gorm:"type:text;comment:'项目权限,如*.project.project_id.*'"` + JoinedAt time.Time `json:"joined_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"` + User User `json:"user,omitempty" gorm:"foreignKey:UserID"` +} + +// ProjectStatus 项目状态枚举 +type ProjectStatus int + +const ( + ProjectStatusActive ProjectStatus = 1 // 进行中 + ProjectStatusCompleted ProjectStatus = 2 // 已完成 + ProjectStatusPaused ProjectStatus = 3 // 已暂停 + ProjectStatusCancelled ProjectStatus = 4 // 已取消 +) + +// VisibilityStatus 项目可见性枚举 +type ProjectVisibility int + +const ( + ProjectVisibilityPublic ProjectVisibility = 1 // 公开 + ProjectVisibilityPrivate ProjectVisibility = 2 // 私有 +) diff --git a/backend/models/task.go b/backend/models/task.go new file mode 100644 index 0000000..7e13571 --- /dev/null +++ b/backend/models/task.go @@ -0,0 +1,58 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Task 任务表 +type Task struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Title string `json:"title" gorm:"size:200;not null"` + Description string `json:"description" gorm:"type:text"` + Priority TaskPriority `json:"priority" gorm:"type:tinyint;default:2;comment:'任务优先级:1=低,2=中,3=高,4=紧急'"` + Status TaskStatus `json:"status" gorm:"type:tinyint;default:1;comment:'任务状态:1=待办,2=进行中,3=已完成,4=已取消'"` + Position int `json:"position" gorm:"not null;comment:'任务在列中的排序位置'"` + DueDate *time.Time `json:"due_date"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + EstimatedHours *float64 `json:"estimated_hours" gorm:"type:decimal(8,2);comment:'预估工时(小时)'"` + ActualHours *float64 `json:"actual_hours" gorm:"type:decimal(8,2);comment:'实际工时(小时)'"` + ColumnID uint `json:"column_id" gorm:"not null;index"` + CreatorID uint `json:"creator_id" gorm:"not null;index"` + AssigneeID *uint `json:"assignee_id" gorm:"index"` + ProjectID uint `json:"project_id" gorm:"not null;index"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Column Column `json:"column,omitempty" gorm:"foreignKey:ColumnID"` + Creator User `json:"creator,omitempty" gorm:"foreignKey:CreatorID"` + Assignee *User `json:"assignee,omitempty" gorm:"foreignKey:AssigneeID"` + Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"` + Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:TaskID"` + Attachments []Attachment `json:"attachments,omitempty" gorm:"foreignKey:TaskID"` + Labels []Label `json:"labels,omitempty" gorm:"many2many:task_labels"` +} + +// TaskPriority 任务优先级枚举 +type TaskPriority int + +const ( + TaskPriorityLow TaskPriority = 1 // 低 + TaskPriorityMedium TaskPriority = 2 // 中 + TaskPriorityHigh TaskPriority = 3 // 高 + TaskPriorityUrgent TaskPriority = 4 // 紧急 +) + +// TaskStatus 任务状态枚举 +type TaskStatus int + +const ( + TaskStatusTodo TaskStatus = 1 // 待办 + TaskStatusInProgress TaskStatus = 2 // 进行中 + TaskStatusCompleted TaskStatus = 3 // 已完成 + TaskStatusCancelled TaskStatus = 4 // 已取消 +) diff --git a/backend/models/user.go b/backend/models/user.go new file mode 100644 index 0000000..1844175 --- /dev/null +++ b/backend/models/user.go @@ -0,0 +1,40 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// User 用户表 +type User struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + Username string `json:"username" gorm:"uniqueIndex;size:50;not null"` + Email string `json:"email" gorm:"uniqueIndex;size:100;not null"` + Password string `json:"-" gorm:"size:255;not null"` + Nickname string `json:"nickname" gorm:"size:50"` + Avatar string `json:"avatar" gorm:"size:255"` + Phone string `json:"phone" gorm:"size:20"` + Status UserStatus `json:"status" gorm:"type:tinyint;default:1;comment:'用户状态:1=正常,2=禁用,3=删除'"` + LastLogin *time.Time `json:"last_login"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Permissions []UserPermission `json:"permissions,omitempty" gorm:"foreignKey:UserID"` + PermissionGroups []UserPermissionGroup `json:"permission_groups,omitempty" gorm:"many2many:user_permission_group_users"` + Projects []Project `json:"projects,omitempty" gorm:"many2many:project_members"` + CreatedTasks []Task `json:"created_tasks,omitempty" gorm:"foreignKey:CreatorID"` + AssignedTasks []Task `json:"assigned_tasks,omitempty" gorm:"foreignKey:AssigneeID"` + Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:UserID"` +} + +// UserStatus 用户状态枚举 +type UserStatus int + +const ( + UserStatusActive UserStatus = 1 // 正常 + UserStatusDisabled UserStatus = 2 // 禁用 + UserStatusDeleted UserStatus = 3 // 删除 +) diff --git a/backend/models/user_permission.go b/backend/models/user_permission.go new file mode 100644 index 0000000..e9a2c36 --- /dev/null +++ b/backend/models/user_permission.go @@ -0,0 +1,48 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// UserPermission 用户权限表 +type UserPermission struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + UserID uint `json:"user_id" gorm:"not null;index"` + PermissionString string `json:"permission_string" gorm:"not null;comment:'权限字符串,;分隔,如*.user.read;project.myproject.task.create'"` + GrantedBy uint `json:"granted_by" gorm:"not null;comment:'授权人ID'"` + ExpiresAt *time.Time `json:"expires_at" gorm:"comment:'权限过期时间,为空表示永不过期'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + User User `json:"user,omitempty" gorm:"foreignKey:UserID"` + GrantedByUser User `json:"granted_by_user,omitempty" gorm:"foreignKey:GrantedBy"` +} + +// UserPermissionGroup 用户权限组表 +type UserPermissionGroup struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + GroupName string `json:"group_name" gorm:"size:100;not null;comment:'权限组名称'"` + PermissionStrings string `json:"permission_strings" gorm:"type:text;not null;comment:'权限字符串列表,;分隔'"` + CreatedBy uint `json:"created_by" gorm:"not null;comment:'创建人ID'"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + + // 关联关系 + Users []User `json:"users,omitempty" gorm:"many2many:user_permission_group_users"` + CreatedByUser User `json:"created_by_user,omitempty" gorm:"foreignKey:CreatedBy"` +} + +// UserPermissionGroupUser 用户权限组用户关联表 +type UserPermissionGroupUser struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement"` + UserPermissionGroupID uint `json:"user_permission_group_id" gorm:"not null;index"` + UserID uint `json:"user_id" gorm:"not null;index"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +}