Compare commits

..

6 Commits

  1. 18
      .env.example
  2. 17
      README.md
  3. 1
      cmd/main.go
  4. 2
      internal/entities/company.go
  5. 2
      internal/entities/company_department.go
  6. 2
      internal/entities/company_staff.go
  7. 2
      internal/entities/config.go
  8. 4
      internal/entities/config_group.go
  9. 2
      internal/entities/feature.go
  10. 2
      internal/entities/feature_category.go
  11. 2
      internal/entities/feature_config.go
  12. 21
      internal/entities/feature_content.go
  13. 23
      internal/entities/feature_content_chapter.go
  14. 31
      internal/entities/feature_content_detail.go
  15. 2
      internal/entities/resource.go
  16. 2
      internal/entities/resource_category.go
  17. 2
      internal/entities/system_menu.go
  18. 2
      internal/entities/system_permission.go
  19. 2
      internal/entities/system_role.go
  20. 5
      internal/entities/system_user.go
  21. 64
      internal/init.go
  22. 140
      internal/middleware/jwt.go
  23. 104
      internal/middleware/ticket.go
  24. 5
      internal/repositories/company.go
  25. 5
      internal/repositories/company_department.go
  26. 5
      internal/repositories/company_staff.go
  27. 5
      internal/repositories/config.go
  28. 5
      internal/repositories/config_group.go
  29. 5
      internal/repositories/feature.go
  30. 5
      internal/repositories/feature_category.go
  31. 5
      internal/repositories/feature_config.go
  32. 5
      internal/repositories/feature_content.go
  33. 5
      internal/repositories/feature_content_chapter.go
  34. 5
      internal/repositories/feature_content_detail.go
  35. 5
      internal/repositories/resource.go
  36. 5
      internal/repositories/resource_category.go
  37. 5
      internal/repositories/system_log.go
  38. 5
      internal/repositories/system_menu.go
  39. 5
      internal/repositories/system_permission.go
  40. 5
      internal/repositories/system_role.go
  41. 5
      internal/repositories/system_role_power.go
  42. 5
      internal/repositories/system_user.go
  43. 16
      internal/services/company/controller/company_controller.go
  44. 16
      internal/services/company/controller/company_department_controller.go
  45. 16
      internal/services/company/controller/company_staff_controller.go
  46. 42
      internal/services/company/request/company_department_upsert_request.go
  47. 49
      internal/services/company/request/company_staff_upsert_request.go
  48. 39
      internal/services/company/request/company_upsert_request.go
  49. 23
      internal/services/company/service.go
  50. 16
      internal/services/config/controller/config_controller.go
  51. 17
      internal/services/config/controller/config_group_controller.go
  52. 34
      internal/services/config/request/config_group_upsert_request.go
  53. 50
      internal/services/config/request/config_upsert_request.go
  54. 23
      internal/services/config/service.go
  55. 16
      internal/services/feature/controller/feature_category_controller.go
  56. 16
      internal/services/feature/controller/feature_config_controller.go
  57. 16
      internal/services/feature/controller/feature_content_chapter_controller.go
  58. 16
      internal/services/feature/controller/feature_content_controller.go
  59. 16
      internal/services/feature/controller/feature_content_detail_controller.go
  60. 16
      internal/services/feature/controller/feature_controller.go
  61. 43
      internal/services/feature/request/feature_category_upsert_request.go
  62. 40
      internal/services/feature/request/feature_config_upsert_request.go
  63. 43
      internal/services/feature/request/feature_content_chapter_upsert_request.go
  64. 55
      internal/services/feature/request/feature_content_detail_upsert_request.go
  65. 40
      internal/services/feature/request/feature_content_upsert_request.go
  66. 37
      internal/services/feature/request/feature_upsert_request.go
  67. 25
      internal/services/feature/service.go
  68. 16
      internal/services/resource/controller/resource_category_controller.go
  69. 16
      internal/services/resource/controller/resource_controller.go
  70. 37
      internal/services/resource/request/resource_category_upsert_request.go
  71. 52
      internal/services/resource/request/resource_upsert_request.go
  72. 22
      internal/services/resource/service.go
  73. 24
      internal/services/service.go
  74. 16
      internal/services/system/controller/system_log_controller.go
  75. 16
      internal/services/system/controller/system_menu_controller.go
  76. 16
      internal/services/system/controller/system_permission_controller.go
  77. 16
      internal/services/system/controller/system_role_controller.go
  78. 16
      internal/services/system/controller/system_role_power_controller.go
  79. 16
      internal/services/system/controller/system_user_controller.go
  80. 55
      internal/services/system/request/system_log_upsert_request.go
  81. 40
      internal/services/system/request/system_menu_upsert_request.go
  82. 37
      internal/services/system/request/system_permission_upsert_request.go
  83. 34
      internal/services/system/request/system_role_power_upsert_request.go
  84. 28
      internal/services/system/request/system_role_upsert_request.go
  85. 34
      internal/services/system/request/system_user_upsert_request.go
  86. 26
      internal/services/system/service.go
  87. 312
      internal/util/controller.go
  88. 43
      internal/util/echo_bind.go
  89. 7
      internal/util/echo_context.go
  90. 38
      internal/util/ticket.go
  91. 75
      main.go
  92. 55
      pkg/app/context.go
  93. 15
      pkg/app/echo.go
  94. 16
      pkg/app/service.go
  95. 10
      pkg/db/query_builder.go
  96. 67
      pkg/logs/attr.go
  97. 44
      pkg/logs/color.go
  98. 1
      pkg/logs/handler.go
  99. 59
      pkg/logs/level.go
  100. 38
      pkg/logs/log.go
  101. Some files were not shown because too many files have changed in this diff Show More

@ -31,10 +31,22 @@ DB_CONN_MAX_LIFETIME=
DB_CODE_FIRST=
################
# 授权认证
################
JWT_PRIVATE_KEY=
JWT_PUBLIC_KEY=
# 在线获取公私钥网站 https://travistidwell.com/jsencrypt/demo/
# 或者使用 scripts/rs256/rs256.go 生成
# 授权令牌私钥
TICKET_PRIVATE_KEY=
# 授权令牌公钥
TICKET_PUBLIC_KEY=
# 令牌时长
TICKET_TTL=
# 令牌签发者
TICKET_ISSUER=
# 令牌主题
TICKET_SUBJECT=
# 令牌受众,多个值可以使用逗号分开,比如:app,pc,wap
TICKET_AUDIENCE=

@ -1,20 +1,3 @@
## 开发接口文档
> [swag 文档地址](https://github.com/swaggo/swag)
生成开发文档命令
```shell
swag init
```
注释文档格式化命令
```shell
swag fmt
```
## 关于 Api
参考 Restful 设计风格。

@ -0,0 +1 @@
package main

@ -16,7 +16,7 @@ type Company struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Principal *CompanyStaff `json:"principal" xml:"principal"`
Staffs []*CompanyStaff `json:"staffs" xml:"staffs"`

@ -19,7 +19,7 @@ type CompanyDepartment struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Staffs []*CompanyStaff `json:"staffs" xml:"staffs" gorm:"many2many:company_staff_to_department_relations"`
Courses []*FeatureContent `json:"courses" xml:"courses" gorm:"many2many:company_course_to_department_relations"`

@ -20,7 +20,7 @@ type CompanyStaff struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Departments []*CompanyDepartment `json:"departments" xml:"departments" gorm:"many2many:company_staff_to_department_relations"`
}

@ -20,5 +20,5 @@ type Config struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
}

@ -8,14 +8,14 @@ import (
// ConfigGroup 配置组表
type ConfigGroup struct {
ID int64 `json:"id" xml:"id" gorm:"primaryKey;not null;comment:配置组编号"`
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:配置组编号"`
Name string `json:"name" xml:"name" gorm:"size:25;not null;uniqueIndex;comment:配置组名称"`
Description string `json:"description" xml:"description" gorm:"comment:配置组描述"`
Sort int32 `json:"sort" xml:"sort" gorm:"default:0;comment:排序"`
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Configs []*Config `json:"configs" xml:"configs" gorm:"foreignKey:GroupID"`
}

@ -16,7 +16,7 @@ type Feature struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Config *FeatureConfig `json:"config" xml:"config"`
Categories []*FeatureCategory `json:"categories" xml:"categories"`

@ -18,7 +18,7 @@ type FeatureCategory struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Children []*FeatureCategory `json:"children" xml:"children" gorm:"foreignKey:PID"`
}

@ -17,5 +17,5 @@ type FeatureConfig struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
}

@ -2,21 +2,22 @@ package entities
import (
"gorm.io/gorm"
"gorm.io/plugin/optimisticlock"
"time"
)
// FeatureContent 栏目内容表(文章、视频、课程)
type FeatureContent struct {
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:内容编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
CategoryID *uint `json:"category_id" xml:"category_id" gorm:"default:null;comment:所属分类编号"`
Type string `json:"type" xml:"type" gorm:"not null;comment:内容类型"`
Title string `json:"title" xml:"title" gorm:"size:100;not null;comment:内容标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:内容简介"`
Version int `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:内容编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
CategoryID *uint `json:"category_id" xml:"category_id" gorm:"default:null;comment:所属分类编号"`
Type string `json:"type" xml:"type" gorm:"not null;comment:内容类型"`
Title string `json:"title" xml:"title" gorm:"size:100;not null;comment:内容标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:内容简介"`
Version optimisticlock.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Chapters []*FeatureContentChapter `json:"chapters" xml:"chapters" gorm:"foreignKey:ContentID"`
Details []*FeatureContentDetail `json:"details" xml:"details" gorm:"foreignKey:ContentID"`

@ -2,22 +2,23 @@ package entities
import (
"gorm.io/gorm"
"gorm.io/plugin/optimisticlock"
"time"
)
// FeatureContentChapter 栏目内容章回表
type FeatureContentChapter struct {
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:章回编号"`
PID *uint `json:"pid" xml:"pid" gorm:"comment:上级章回编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
ContentID uint `json:"content_id" xml:"content_id" gorm:"comment:所属内容编号"`
Title string `json:"title" xml:"title" gorm:"size:100;not null;comment:章回标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:章回描述"`
Sort int32 `json:"sort" xml:"sort" gorm:"size:4;default:0;comment:排序"`
Version int `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:章回编号"`
PID *uint `json:"pid" xml:"pid" gorm:"comment:上级章回编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
ContentID uint `json:"content_id" xml:"content_id" gorm:"comment:所属内容编号"`
Title string `json:"title" xml:"title" gorm:"size:100;not null;comment:章回标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:章回描述"`
Sort int32 `json:"sort" xml:"sort" gorm:"size:4;default:0;comment:排序"`
Version optimisticlock.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Details []FeatureContentDetail `json:"details" xml:"details" gorm:"foreignKey:ChapterID"`
Children []*FeatureContentChapter `json:"children" xml:"children" gorm:"foreignKey:PID"`

@ -2,26 +2,27 @@ package entities
import (
"gorm.io/gorm"
"gorm.io/plugin/optimisticlock"
"time"
)
// FeatureContentDetail 内容详情表
type FeatureContentDetail struct {
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:内容详情编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
ChapterID *uint `json:"chapter_id" xml:"chapter_id" gorm:"comment:所属章回编号"`
ContentID uint `json:"content_id" xml:"content_id" gorm:"comment:所属内容编号"`
Type string `json:"type" xml:"type" gorm:"not null;comment:内容类型"`
Title string `json:"title" xml:"title" gorm:"size:25;not null;comment:标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:简介"`
PosterUrl string `json:"poster_url" xml:"poster_url" gorm:"size:250;comment:封面链接"`
VideoUrl string `json:"video_url" xml:"video_url" gorm:"size:250;comment:视频描述"`
Text string `json:"text" xml:"text" gorm:"type:longtext;comment:具体内容"`
Attributes map[string]any `json:"attributes" xml:"attributes" gorm:"serializer:json;type:text;comment:相关属性值"`
Version int `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:内容详情编号"`
FeatureID uint `json:"feature_id" xml:"feature_id" gorm:"comment:所属栏目编号"`
ChapterID *uint `json:"chapter_id" xml:"chapter_id" gorm:"comment:所属章回编号"`
ContentID uint `json:"content_id" xml:"content_id" gorm:"comment:所属内容编号"`
Type string `json:"type" xml:"type" gorm:"not null;comment:内容类型"`
Title string `json:"title" xml:"title" gorm:"size:25;not null;comment:标题"`
Intro string `json:"intro" xml:"intro" gorm:"size:250;comment:简介"`
PosterUrl string `json:"poster_url" xml:"poster_url" gorm:"size:250;comment:封面链接"`
VideoUrl string `json:"video_url" xml:"video_url" gorm:"size:250;comment:视频描述"`
Text string `json:"text" xml:"text" gorm:"type:longtext;comment:具体内容"`
Attributes map[string]any `json:"attributes" xml:"attributes" gorm:"serializer:json;type:text;comment:相关属性值"`
Version optimisticlock.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Chapter *FeatureContentChapter `json:"chapter" xml:"chapter" gorm:"foreignKey:ChapterID"`
Content *FeatureContent `json:"content" xml:"content" gorm:"foreignKey:ContentID"`

@ -21,5 +21,5 @@ type Resource struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
}

@ -16,7 +16,7 @@ type ResourceCategory struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Resources []*Resource `json:"resources" xml:"resources" gorm:"foreignKey:CategoryID"`
Children []*ResourceCategory `json:"children" xml:"children" gorm:"foreignKey:PID"`

@ -17,7 +17,7 @@ type SystemMenu struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Children []*SystemMenu `json:"children" xml:"children" gorm:"foreignKey:PID"`
}

@ -13,7 +13,7 @@ type SystemPermission struct {
Identifier string `json:"identifier" xml:"identifier" gorm:"size:25;not null;uniqueIndex;comment:权限标识"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Children []*SystemPermission `json:"children" xml:"children" gorm:"foreignKey:PID"`
}

@ -13,7 +13,7 @@ type SystemRole struct {
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Powers []*SystemRolePower `json:"powers" xml:"powers" gorm:"foreignKey:RoleID"`
Users []*SystemUser `json:"users" xml:"users" gorm:"many2many:system_user_to_role_relations"`

@ -2,6 +2,7 @@ package entities
import (
"gorm.io/gorm"
"sorbet/pkg/db"
"time"
)
@ -11,10 +12,10 @@ type SystemUser struct {
Username string `json:"username" xml:"username" gorm:"size:25;not null;uniqueIndex;comment:用户名"`
Password string `json:"password" xml:"password" gorm:"size:25;not null;comment:登录密码"`
Status bool `json:"status" xml:"status" gorm:"comment:状态"`
Version int `json:"-" xml:"-" gorm:"comment:乐观锁"`
Version db.Version `json:"-" xml:"-" gorm:"comment:乐观锁"`
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"`
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"`
DeletedAt gorm.DeletedAt `json:"delete_time" xml:"delete_time" gorm:"comment:删除时间"`
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"`
Roles []*SystemRole `json:"roles" xml:"roles" gorm:"many2many:system_user_to_role_relations"`
}

@ -2,12 +2,20 @@ package internal
import (
"errors"
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
"gorm.io/gorm"
"net/http"
"sorbet/internal/entities"
"sorbet/internal/middleware"
"sorbet/internal/repositories"
"sorbet/internal/util"
"sorbet/pkg/db"
"sorbet/pkg/env"
"sorbet/pkg/ioc"
"sorbet/pkg/log"
"sorbet/pkg/rsp"
"sorbet/pkg/ticket"
)
func Init() error {
@ -51,3 +59,59 @@ func syncEntities() error {
&entities.SystemUser{},
)
}
func Start() error {
e := echo.New()
e.HideBanner = true
e.HidePort = true
e.HTTPErrorHandler = func(err error, c echo.Context) {
if !c.Response().Committed {
http.Error(c.Response(), err.Error(), 500)
}
}
e.Logger = util.NewLogger()
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.Use(middleware.Logger)
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
db := ioc.MustGet[gorm.DB]().WithContext(c.Request().Context())
ci := ioc.Fork()
ci.Bind(db)
c.Set("db", db)
c.Set("ioc", ci)
return next(c)
}
})
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.GET("/", func(c echo.Context) error {
repo := repositories.NewCompanyRepository(c.Get("db").(*gorm.DB))
company, err := repo.GetByID(c.Request().Context(), 1)
if err != nil {
return err
}
token, err := util.CreateTicket(&ticket.Claims{
UID: company.ID,
Role: "system",
Issuer: "chshs",
Subject: "subject",
})
if err != nil {
return err
}
return rsp.Ok(c, echo.Map{
"token": token,
})
})
e.Group("", middleware.Ticket(false, "system")).GET("/u", func(c echo.Context) error {
return rsp.Ok(c, echo.Map{
"ticket": c.Get("ticket"),
"claims": c.Get("ticket_claims").(*ticket.Claims),
})
})
e.Logger.Fatal(e.Start(":1323"))
return nil
}

@ -1,140 +0,0 @@
package middleware
import (
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
)
// JWTConfig defines the config for JWT middleware.
type JWTConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc BeforeFunc
// SuccessHandler defines a function which is executed for a valid token.
SuccessHandler func(c echo.Context)
// ErrorHandler defines a function which is executed when all lookups have been done and none of them passed Validator
// function. ErrorHandler is executed with last missing (ErrExtractionValueMissing) or an invalid key.
// It may be used to define a custom JWT error.
//
// Note: when error handler swallows the error (returns nil) middleware continues handler chain execution towards handler.
// This is useful in cases when portion of your site/api is publicly accessible and has extra features for authorized users
// In that case you can use ErrorHandler to set default public JWT token value to request and continue with handler chain.
ErrorHandler func(c echo.Context, err error) error
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
// ignore the error (by returning `nil`).
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
// In that case you can use ErrorHandler to set a default public JWT token value in the request context
// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
ContinueOnIgnoredError bool
// Context key to store user information from the token into context.
// Optional. Default value "user".
ContextKey string
// Signing key to validate token.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither user-defined KeyFunc nor SigningKeys is provided.
SigningKey any
// Map of signing keys to validate token with kid field usage.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither user-defined KeyFunc nor SigningKey is provided.
SigningKeys map[string]any
// Signing method used to check the token's signing algorithm.
// Optional. Default value HS256.
SigningMethod string
// KeyFunc defines a user-defined function that supplies the public key for a token validation.
// The function shall take care of verifying the signing algorithm and selecting the proper key.
// A user-defined KeyFunc can be useful if tokens are issued by an external party.
// Used by default ParseTokenFunc implementation.
//
// When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
// Required if neither SigningKeys nor SigningKey is provided.
// Not used if custom ParseTokenFunc is set.
// Default to an internal implementation verifying the signing algorithm and selecting the proper key.
KeyFunc jwt.Keyfunc
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
// If prefix is left empty the whole value is returned.
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
// - "form:<name>"
// Multiple sources example:
// - "header:Authorization:Bearer ,cookie:myowncookie"
TokenLookup string
// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
// This is one of the two options to provide a token extractor.
// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
// You can also provide both if you want.
TokenLookupFuncs []ValuesExtractor
// ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token
// parsing fails or parsed token is invalid.
// Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library
ParseTokenFunc func(c echo.Context, auth string) (any, error)
// Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation.
// Not used if custom ParseTokenFunc is set.
// Optional. Defaults to function returning jwt.MapClaims
NewClaimsFunc func(c echo.Context) jwt.Claims
}
// Errors
var (
ErrJWTMissing = echojwt.ErrJWTMissing
ErrJWTInvalid = echojwt.ErrJWTInvalid
)
func (config *JWTConfig) ToMiddleware() echo.MiddlewareFunc {
return echojwt.WithConfig(echojwt.Config{
Skipper: config.Skipper,
BeforeFunc: config.BeforeFunc,
SuccessHandler: config.SuccessHandler,
ErrorHandler: config.ErrorHandler,
ContinueOnIgnoredError: config.ContinueOnIgnoredError,
ContextKey: config.ContextKey,
SigningKey: config.SigningKey,
SigningKeys: config.SigningKeys,
SigningMethod: config.SigningMethod,
KeyFunc: config.KeyFunc,
TokenLookup: config.TokenLookup,
TokenLookupFuncs: config.TokenLookupFuncs,
ParseTokenFunc: config.ParseTokenFunc,
NewClaimsFunc: nil,
})
}
// JWT returns a JSON Web Token (JWT) auth middleware.
//
// For valid token, it sets the user in context and calls next handler.
// For invalid token, it returns "401 - Unauthorized" error.
// For missing token, it returns "400 - Bad Request" error.
//
// See: https://jwt.io/introduction
// See `JWTConfig.TokenLookup`
// See https://github.com/labstack/echo-jwt
func JWT(signingKey any) echo.MiddlewareFunc {
return echojwt.JWT(signingKey)
}

@ -0,0 +1,104 @@
package middleware
import (
"crypto/rsa"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
"slices"
"sorbet/pkg/env"
"sorbet/pkg/ticket"
)
var ticketPublicKey *rsa.PublicKey
type TicketConfig struct {
Skipper Skipper
Anonymously bool
Audiences []string
PublicKey *rsa.PublicKey
TicketFinder ticket.Finder
ClaimsLooker func(c echo.Context, claims *ticket.Claims) error
ErrorHandler func(c echo.Context, err error) error
SuccessHandler func(c echo.Context)
}
func Ticket(anonymously bool, roles ...string) echo.MiddlewareFunc {
return TicketWithConfig(TicketConfig{
Anonymously: anonymously,
TicketFinder: ticket.DefaultFinder,
ClaimsLooker: func(c echo.Context, claims *ticket.Claims) error {
if len(roles) > 0 && slices.Contains(roles, claims.Role) {
return nil
}
return ticket.ErrUnauthorized
},
})
}
func TicketWithConfig(config TicketConfig) echo.MiddlewareFunc {
return config.ToMiddleware()
}
func (t *TicketConfig) ToMiddleware() echo.MiddlewareFunc {
if t.Skipper == nil {
t.Skipper = DefaultSkipper
}
if len(t.Audiences) == 0 {
t.Audiences = append(t.Audiences, "*")
}
if t.TicketFinder == nil {
t.TicketFinder = ticket.DefaultFinder
}
if t.ErrorHandler == nil {
t.ErrorHandler = func(c echo.Context, err error) error {
return err
}
}
if t.ClaimsLooker == nil {
t.ClaimsLooker = func(c echo.Context, claims *ticket.Claims) error {
return nil
}
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if t.Skipper(c) {
return next(c)
}
ticketString := t.TicketFinder(c.Request())
if ticketString == "" {
if t.Anonymously {
return next(c)
}
return t.ErrorHandler(c, ticket.ErrNoTicketFound)
}
publicKey := t.PublicKey
if publicKey == nil {
key, err := getTicketPublicKey()
if err != nil {
return err
}
publicKey = key
}
claims, err := ticket.Verify(ticketString, publicKey, t.Audiences...)
if err != nil {
return t.ErrorHandler(c, err)
}
if err = t.ClaimsLooker(c, claims); err != nil {
return t.ErrorHandler(c, err)
}
c.Set("ticket", ticketString)
c.Set("ticket_claims", claims)
return next(c)
}
}
}
func getTicketPublicKey() (*rsa.PublicKey, error) {
if ticketPublicKey != nil {
return ticketPublicKey, nil
}
var err error
source := []byte(env.String("TICKET_PUBLIC_KEY"))
ticketPublicKey, err = jwt.ParseRSAPublicKeyFromPEM(source)
return ticketPublicKey, err
}

@ -13,7 +13,6 @@ type CompanyRepository struct {
// NewCompanyRepository 创建公司仓库
func NewCompanyRepository(orm *gorm.DB) *CompanyRepository {
return &CompanyRepository{
db.NewRepositoryWith[entities.Company](orm, "id"),
}
db.NewRepositoryWith[entities.Company](orm, "id"),
}
}

@ -13,7 +13,6 @@ type CompanyDepartmentRepository struct {
// NewCompanyDepartmentRepository 创建公司部门仓库
func NewCompanyDepartmentRepository(orm *gorm.DB) *CompanyDepartmentRepository {
return &CompanyDepartmentRepository{
db.NewRepositoryWith[entities.CompanyDepartment](orm, "id"),
}
db.NewRepositoryWith[entities.CompanyDepartment](orm, "id"),
}
}

@ -13,7 +13,6 @@ type CompanyStaffRepository struct {
// NewCompanyStaffRepository 创建公司员工仓库
func NewCompanyStaffRepository(orm *gorm.DB) *CompanyStaffRepository {
return &CompanyStaffRepository{
db.NewRepositoryWith[entities.CompanyStaff](orm, "id"),
}
db.NewRepositoryWith[entities.CompanyStaff](orm, "id"),
}
}

@ -13,7 +13,6 @@ type ConfigRepository struct {
// NewConfigRepository 创建配置仓库
func NewConfigRepository(orm *gorm.DB) *ConfigRepository {
return &ConfigRepository{
db.NewRepositoryWith[entities.Config](orm, "id"),
}
db.NewRepositoryWith[entities.Config](orm, "id"),
}
}

@ -13,7 +13,6 @@ type ConfigGroupRepository struct {
// NewConfigGroupRepository 创建配置组仓库
func NewConfigGroupRepository(orm *gorm.DB) *ConfigGroupRepository {
return &ConfigGroupRepository{
db.NewRepositoryWith[entities.ConfigGroup](orm, "id"),
}
db.NewRepositoryWith[entities.ConfigGroup](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureRepository struct {
// NewFeatureRepository 创建栏目仓库
func NewFeatureRepository(orm *gorm.DB) *FeatureRepository {
return &FeatureRepository{
db.NewRepositoryWith[entities.Feature](orm, "id"),
}
db.NewRepositoryWith[entities.Feature](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureCategoryRepository struct {
// NewFeatureCategoryRepository 创建栏目分类仓库
func NewFeatureCategoryRepository(orm *gorm.DB) *FeatureCategoryRepository {
return &FeatureCategoryRepository{
db.NewRepositoryWith[entities.FeatureCategory](orm, "id"),
}
db.NewRepositoryWith[entities.FeatureCategory](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureConfigRepository struct {
// NewFeatureConfigRepository 创建栏目配置仓库
func NewFeatureConfigRepository(orm *gorm.DB) *FeatureConfigRepository {
return &FeatureConfigRepository{
db.NewRepositoryWith[entities.FeatureConfig](orm, "id"),
}
db.NewRepositoryWith[entities.FeatureConfig](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureContentRepository struct {
// NewFeatureContentRepository 创建栏目内容仓库
func NewFeatureContentRepository(orm *gorm.DB) *FeatureContentRepository {
return &FeatureContentRepository{
db.NewRepositoryWith[entities.FeatureContent](orm, "id"),
}
db.NewRepositoryWith[entities.FeatureContent](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureContentChapterRepository struct {
// NewFeatureContentChapterRepository 创建栏目内容章回仓库
func NewFeatureContentChapterRepository(orm *gorm.DB) *FeatureContentChapterRepository {
return &FeatureContentChapterRepository{
db.NewRepositoryWith[entities.FeatureContentChapter](orm, "id"),
}
db.NewRepositoryWith[entities.FeatureContentChapter](orm, "id"),
}
}

@ -13,7 +13,6 @@ type FeatureContentDetailRepository struct {
// NewFeatureContentDetailRepository 创建栏目内容详情仓库
func NewFeatureContentDetailRepository(orm *gorm.DB) *FeatureContentDetailRepository {
return &FeatureContentDetailRepository{
db.NewRepositoryWith[entities.FeatureContentDetail](orm, "id"),
}
db.NewRepositoryWith[entities.FeatureContentDetail](orm, "id"),
}
}

@ -13,7 +13,6 @@ type ResourceRepository struct {
// NewResourceRepository 创建资源仓库
func NewResourceRepository(orm *gorm.DB) *ResourceRepository {
return &ResourceRepository{
db.NewRepositoryWith[entities.Resource](orm, "id"),
}
db.NewRepositoryWith[entities.Resource](orm, "id"),
}
}

@ -13,7 +13,6 @@ type ResourceCategoryRepository struct {
// NewResourceCategoryRepository 创建资源分类仓库
func NewResourceCategoryRepository(orm *gorm.DB) *ResourceCategoryRepository {
return &ResourceCategoryRepository{
db.NewRepositoryWith[entities.ResourceCategory](orm, "id"),
}
db.NewRepositoryWith[entities.ResourceCategory](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemLogRepository struct {
// NewSystemLogRepository 创建系统日志仓库
func NewSystemLogRepository(orm *gorm.DB) *SystemLogRepository {
return &SystemLogRepository{
db.NewRepositoryWith[entities.SystemLog](orm, "id"),
}
db.NewRepositoryWith[entities.SystemLog](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemMenuRepository struct {
// NewSystemMenuRepository 创建系统菜单仓库
func NewSystemMenuRepository(orm *gorm.DB) *SystemMenuRepository {
return &SystemMenuRepository{
db.NewRepositoryWith[entities.SystemMenu](orm, "id"),
}
db.NewRepositoryWith[entities.SystemMenu](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemPermissionRepository struct {
// NewSystemPermissionRepository 创建系统权限仓库
func NewSystemPermissionRepository(orm *gorm.DB) *SystemPermissionRepository {
return &SystemPermissionRepository{
db.NewRepositoryWith[entities.SystemPermission](orm, "id"),
}
db.NewRepositoryWith[entities.SystemPermission](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemRoleRepository struct {
// NewSystemRoleRepository 创建系统用户角色仓库
func NewSystemRoleRepository(orm *gorm.DB) *SystemRoleRepository {
return &SystemRoleRepository{
db.NewRepositoryWith[entities.SystemRole](orm, "id"),
}
db.NewRepositoryWith[entities.SystemRole](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemRolePowerRepository struct {
// NewSystemRolePowerRepository 创建角色授权仓库
func NewSystemRolePowerRepository(orm *gorm.DB) *SystemRolePowerRepository {
return &SystemRolePowerRepository{
db.NewRepositoryWith[entities.SystemRolePower](orm, "id"),
}
db.NewRepositoryWith[entities.SystemRolePower](orm, "id"),
}
}

@ -13,7 +13,6 @@ type SystemUserRepository struct {
// NewSystemUserRepository 创建系统用户仓库
func NewSystemUserRepository(orm *gorm.DB) *SystemUserRepository {
return &SystemUserRepository{
db.NewRepositoryWith[entities.SystemUser](orm, "id"),
}
db.NewRepositoryWith[entities.SystemUser](orm, "id"),
}
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
)
type CompanyController struct {
util.Controller[entities.Company, request.CompanyUpsertRequest]
}
func (ctr *CompanyController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/companies", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
)
type CompanyDepartmentController struct {
util.Controller[entities.CompanyDepartment, request.CompanyDepartmentUpsertRequest]
}
func (c *CompanyDepartmentController) InitRoutes(r *echo.Group) {
c.RegisterRoutes("/company/departments", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
)
type CompanyStaffController struct {
util.Controller[entities.CompanyStaff, request.CompanyStaffUpsertRequest]
}
func (c *CompanyStaffController) InitRoutes(r *echo.Group) {
c.RegisterRoutes("/company/staffs", r)
}

@ -0,0 +1,42 @@
package request
import (
"sorbet/internal/entities"
"sorbet/pkg/db"
)
type CompanyDepartmentUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
CompanyID uint `json:"company_id" xml:"company_id" form:"company_id"`
PrincipalID uint `json:"principal_id" xml:"principal_id" form:"principal_id"`
Name string `json:"name" xml:"name" form:"name"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
}
func (c *CompanyDepartmentUpsertRequest) GetID() any {
return c.ID
}
func (c *CompanyDepartmentUpsertRequest) ToEntity() any {
return &entities.CompanyDepartment{
ID: c.ID,
PID: &c.PID,
CompanyID: c.CompanyID,
PrincipalID: &c.PrincipalID,
Name: c.Name,
Sort: c.Sort,
Version: db.Version{Int64: 1, Valid: true},
}
}
func (c *CompanyDepartmentUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": c.ID,
"pid": c.PID,
"company_id": c.CompanyID,
"principal_id": c.PrincipalID,
"name": c.Name,
"sort": c.Sort,
}
}

@ -0,0 +1,49 @@
package request
import (
"sorbet/internal/entities"
)
type CompanyStaffUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
CompanyID uint `json:"company_id" xml:"company_id" form:"company_id"`
Name string `json:"name" xml:"name" form:"name"`
Gender string `json:"gender" xml:"gender" form:"gender"`
Position string `json:"position" xml:"position" form:"position"`
PhoneNumber string `json:"phone_number" xml:"phone_number" form:"phone_number"`
WechatOpenid string `json:"wechat_openid" xml:"wechat_openid" form:"wechat_openid"`
WithoutStudy bool `json:"without_study" xml:"without_study" form:"without_study"`
IsAdmin bool `json:"is_admin" xml:"is_admin" form:"is_admin"`
}
func (c *CompanyStaffUpsertRequest) GetID() any {
return c.ID
}
func (c *CompanyStaffUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": c.ID,
"company_id": c.CompanyID,
"name": c.Name,
"gender": c.Gender,
"position": c.Position,
"phone_number": c.PhoneNumber,
"wechat_openid": c.WechatOpenid,
"without_study": c.WithoutStudy,
"is_admin": c.IsAdmin,
}
}
func (c *CompanyStaffUpsertRequest) ToEntity() any {
return &entities.CompanyStaff{
ID: c.ID,
CompanyID: c.CompanyID,
Name: c.Name,
Gender: c.Gender,
Position: c.Position,
PhoneNumber: c.PhoneNumber,
WechatOpenid: c.WechatOpenid,
WithoutStudy: c.WithoutStudy,
IsAdmin: c.IsAdmin,
}
}

@ -0,0 +1,39 @@
package request
import (
"sorbet/internal/entities"
"sorbet/pkg/db"
)
type CompanyUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PrincipalID uint `json:"principal_id" xml:"principal_id" form:"principal_id"`
Name string `json:"name" xml:"name" form:"name"`
Logo string `json:"logo" xml:"logo" form:"logo"`
Status bool `json:"status" xml:"status" form:"status"`
}
func (r *CompanyUpsertRequest) GetID() any {
return r.ID
}
func (r *CompanyUpsertRequest) ToEntity() *entities.Company {
return &entities.Company{
ID: r.ID,
PrincipalID: &r.PrincipalID,
Name: r.Name,
Logo: r.Logo,
Status: r.Status,
Version: db.Version{Int64: 1, Valid: true},
}
}
func (r *CompanyUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": r.ID,
"principal_id": r.PrincipalID,
"name": r.Name,
"logo": r.Logo,
"status": r.Status,
}
}

@ -0,0 +1,23 @@
package company
import (
"sorbet/internal/services/company/controller"
"sorbet/pkg/app"
)
type Service struct{}
func (s *Service) Init(ctx *app.Context) error {
ctx.Routes(&controller.CompanyController{})
ctx.Routes(&controller.CompanyDepartmentController{})
ctx.Routes(&controller.CompanyStaffController{})
return nil
}
func (s *Service) Start() error {
return nil
}
func (s *Service) Stop() error {
return nil
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/config/request"
"sorbet/internal/util"
)
type ConfigController struct {
util.Controller[entities.Config, request.ConfigUpsertRequest]
}
func (ctr *ConfigController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/config", r)
}

@ -0,0 +1,17 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/config/request"
"sorbet/internal/util"
)
type ConfigGroupController struct {
util.Controller[entities.ConfigGroup, request.ConfigGroupUpsertRequest]
}
// InitRoutes 实现路由注册接口
func (ctr *ConfigGroupController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/config/groups", r)
}

@ -0,0 +1,34 @@
package request
import (
"sorbet/internal/entities"
"sorbet/pkg/db"
)
type ConfigGroupUpsertRequest struct {
ID uint `json:"id" xml:"id" path:"id"`
Name string `json:"name" xml:"name" form:"name"`
Description string `json:"description" xml:"description" form:"description"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
}
func (c *ConfigGroupUpsertRequest) GetID() any {
return c.ID
}
func (c *ConfigGroupUpsertRequest) ToEntity() *entities.ConfigGroup {
return &entities.ConfigGroup{
Name: c.Name,
Description: c.Description,
Sort: c.Sort,
Version: db.Version{Int64: 1, Valid: true},
}
}
func (c *ConfigGroupUpsertRequest) ToMap() map[string]any {
return map[string]any{
"name": c.Name,
"description": c.Description,
"sort": c.Sort,
}
}

@ -0,0 +1,50 @@
package request
import (
"sorbet/internal/entities"
"sorbet/pkg/db"
)
type ConfigUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
GroupID uint `json:"group_id" xml:"group_id" form:"group_id"`
Name string `json:"name" xml:"name" form:"name"`
Title string `json:"title" xml:"title" form:"title"`
Description string `json:"description" xml:"description" form:"description"`
DataType string `json:"data_type" xml:"data_type" form:"data_type"`
Attributes map[string]any `json:"attributes" xml:"attributes" form:"attributes"`
Value any `json:"value" xml:"value" form:"value"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
}
func (c *ConfigUpsertRequest) GetID() any {
return c.ID
}
func (c *ConfigUpsertRequest) ToEntity() *entities.Config {
return &entities.Config{
ID: c.ID,
GroupID: c.GroupID,
Name: c.Name,
Title: c.Title,
Description: c.Description,
DataType: c.DataType,
Attributes: c.Attributes,
Value: c.Value,
Sort: c.Sort,
Version: db.Version{Int64: 1, Valid: true},
}
}
func (c *ConfigUpsertRequest) ToMap() map[string]any {
return map[string]any{
"group_id": c.GroupID,
"name": c.Name,
"title": c.Title,
"description": c.Description,
"data_type": c.DataType,
"attributes": c.Attributes,
"value": c.Value,
"sort": c.Sort,
}
}

@ -0,0 +1,23 @@
package config
import (
"sorbet/internal/services/config/controller"
"sorbet/pkg/app"
)
type Service struct {
}
func (s *Service) Init(ctx *app.Context) error {
ctx.Routes(&controller.ConfigGroupController{})
ctx.Routes(&controller.ConfigController{})
return nil
}
func (s *Service) Start() error {
return nil
}
func (s *Service) Stop() error {
return nil
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureCategoryController struct {
util.Controller[entities.FeatureCategory, request.FeatureCategoryUpsertRequest]
}
func (f *FeatureCategoryController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/feature/categories", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureConfigController struct {
util.Controller[entities.FeatureConfig, request.FeatureConfigUpsertRequest]
}
func (f *FeatureConfigController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/feature/configs", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureContentChapterController struct {
util.Controller[entities.FeatureContentChapter, request.FeatureContentChapterUpsertRequest]
}
func (f *FeatureContentChapterController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/feature/content/chapters", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureContentController struct {
util.Controller[entities.FeatureContent, request.FeatureContentUpsertRequest]
}
func (f *FeatureContentController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/feature/contents", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureContentDetailController struct {
util.Controller[entities.FeatureContentDetail, request.FeatureContentDetailUpsertRequest]
}
func (f *FeatureContentDetailController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/feature/content/detail", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
)
type FeatureController struct {
util.Controller[entities.Feature, request.FeatureUpsertRequest]
}
func (f *FeatureController) InitRoutes(r *echo.Group) {
f.RegisterRoutes("/features", r)
}

@ -0,0 +1,43 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureCategoryUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"`
Title string `json:"title" xml:"title" form:"title"`
Description string `json:"description" xml:"description" form:"description"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
Status bool `json:"status" xml:"status" form:"status"`
}
func (f *FeatureCategoryUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureCategoryUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"pid": f.PID,
"feature_id": f.FeatureID,
"title": f.Title,
"description": f.Description,
"sort": f.Sort,
"status": f.Status,
}
}
func (f *FeatureCategoryUpsertRequest) ToEntity() any {
return &entities.FeatureCategory{
ID: f.ID,
PID: &f.PID,
FeatureID: f.FeatureID,
Title: f.Title,
Description: f.Description,
Sort: f.Sort,
Status: f.Status,
}
}

@ -0,0 +1,40 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureConfigUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"`
Status bool `json:"status" xml:"status" form:"status"`
Categorizable bool `json:"categorizable" xml:"categorizable" form:"categorizable"`
CategoryDepth int64 `json:"category_depth" xml:"category_depth" form:"category_depth"`
ContentTypes []string `json:"content_types" xml:"content_types" form:"content_types"`
}
func (f *FeatureConfigUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureConfigUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"feature_id": f.FeatureID,
"status": f.Status,
"categorizable": f.Categorizable,
"category_depth": f.CategoryDepth,
"content_types": f.ContentTypes,
}
}
func (f *FeatureConfigUpsertRequest) ToEntity() any {
return &entities.FeatureConfig{
ID: f.ID,
FeatureID: f.FeatureID,
Status: f.Status,
Categorizable: f.Categorizable,
CategoryDepth: f.CategoryDepth,
ContentTypes: f.ContentTypes,
}
}

@ -0,0 +1,43 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureContentChapterUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"`
ContentID uint `json:"content_id" xml:"content_id" form:"content_id"`
Title string `json:"title" xml:"title" form:"title"`
Intro string `json:"intro" xml:"intro" form:"intro"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
}
func (f *FeatureContentChapterUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureContentChapterUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"pid": f.PID,
"feature_id": f.FeatureID,
"content_id": f.ContentID,
"title": f.Title,
"intro": f.Intro,
"sort": f.Sort,
}
}
func (f *FeatureContentChapterUpsertRequest) ToEntity() any {
return &entities.FeatureContentChapter{
ID: f.ID,
PID: &f.PID,
FeatureID: f.FeatureID,
ContentID: f.ContentID,
Title: f.Title,
Intro: f.Intro,
Sort: f.Sort,
}
}

@ -0,0 +1,55 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureContentDetailUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"`
ChapterID uint `json:"chapter_id" xml:"chapter_id" form:"chapter_id"`
ContentID uint `json:"content_id" xml:"content_id" form:"content_id"`
Type string `json:"type" xml:"type" form:"type"`
Title string `json:"title" xml:"title" form:"title"`
Intro string `json:"intro" xml:"intro" form:"intro"`
PosterUrl string `json:"poster_url" xml:"poster_url" form:"poster_url"`
VideoUrl string `json:"video_url" xml:"video_url" form:"video_url"`
Text string `json:"text" xml:"text" form:"text"`
Attributes map[string]any `json:"attributes" xml:"attributes" form:"attributes"`
}
func (f *FeatureContentDetailUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureContentDetailUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"feature_id": f.FeatureID,
"chapter_id": f.ChapterID,
"content_id": f.ContentID,
"type": f.Type,
"title": f.Title,
"intro": f.Intro,
"poster_url": f.PosterUrl,
"video_url": f.VideoUrl,
"text": f.Text,
"attributes": f.Attributes,
}
}
func (f *FeatureContentDetailUpsertRequest) ToEntity() any {
return &entities.FeatureContentDetail{
ID: f.ID,
FeatureID: f.FeatureID,
ChapterID: &f.ChapterID,
ContentID: f.ContentID,
Type: f.Type,
Title: f.Title,
Intro: f.Intro,
PosterUrl: f.PosterUrl,
VideoUrl: f.VideoUrl,
Text: f.Text,
Attributes: f.Attributes,
}
}

@ -0,0 +1,40 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureContentUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
FeatureID uint `json:"feature_id" xml:"feature_id" form:"feature_id"`
CategoryID *uint `json:"category_id" xml:"category_id" form:"category_id"`
Type string `json:"type" xml:"type" form:"type"`
Title string `json:"title" xml:"title" form:"title"`
Intro string `json:"intro" xml:"intro" form:"intro"`
}
func (f *FeatureContentUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureContentUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"feature_id": f.FeatureID,
"category_id": f.CategoryID,
"type": f.Type,
"title": f.Title,
"intro": f.Intro,
}
}
func (f *FeatureContentUpsertRequest) ToEntity() any {
return &entities.FeatureContent{
ID: f.ID,
FeatureID: f.FeatureID,
CategoryID: f.CategoryID,
Type: f.Type,
Title: f.Title,
Intro: f.Intro,
}
}

@ -0,0 +1,37 @@
package request
import (
"sorbet/internal/entities"
)
type FeatureUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
Title string `json:"title" xml:"title" form:"title"`
Intro string `json:"intro" xml:"intro" form:"intro"`
Icon string `json:"icon" xml:"icon" form:"icon"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
}
func (f *FeatureUpsertRequest) GetID() any {
return f.ID
}
func (f *FeatureUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": f.ID,
"title": f.Title,
"intro": f.Intro,
"icon": f.Icon,
"sort": f.Sort,
}
}
func (f *FeatureUpsertRequest) ToEntity() any {
return &entities.Feature{
ID: f.ID,
Title: f.Title,
Intro: f.Intro,
Icon: f.Icon,
Sort: f.Sort,
}
}

@ -0,0 +1,25 @@
package feature
import (
"sorbet/internal/services/feature/controller"
"sorbet/pkg/app"
)
type Service struct{}
func (s *Service) Init(ctx *app.Context) error {
ctx.Routes(&controller.FeatureCategoryController{})
ctx.Routes(&controller.FeatureConfigController{})
ctx.Routes(&controller.FeatureContentChapterController{})
ctx.Routes(&controller.FeatureContentDetailController{})
ctx.Routes(&controller.FeatureController{})
return nil
}
func (s *Service) Start() error {
return nil
}
func (s *Service) Stop() error {
return nil
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/resource/request"
"sorbet/internal/util"
)
type ResourceCategoryController struct {
util.Controller[entities.Resource, request.ResourceCategoryUpsertRequest]
}
func (ctr *ResourceCategoryController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/resource/categories", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/resource/request"
"sorbet/internal/util"
)
type ResourceController struct {
util.Controller[entities.Resource, request.ResourceUpsertRequest]
}
func (ctr *ResourceController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/resources", r)
}

@ -0,0 +1,37 @@
package request
import (
"sorbet/internal/entities"
)
type ResourceCategoryUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
Title string `json:"title" xml:"title" form:"title"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
Status bool `json:"status" xml:"status" form:"status"`
}
func (r *ResourceCategoryUpsertRequest) GetID() any {
return r.ID
}
func (r *ResourceCategoryUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": r.ID,
"pid": r.PID,
"title": r.Title,
"sort": r.Sort,
"status": r.Status,
}
}
func (r *ResourceCategoryUpsertRequest) ToEntity() any {
return &entities.ResourceCategory{
ID: r.ID,
PID: &r.PID,
Title: r.Title,
Sort: r.Sort,
Status: r.Status,
}
}

@ -0,0 +1,52 @@
package request
import (
"sorbet/internal/entities"
)
type ResourceUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
CategoryID uint `json:"category_id" xml:"category_id" form:"category_id"`
Title string `json:"title" xml:"title" form:"title"`
Path string `json:"path" xml:"path" form:"path"`
Width int32 `json:"width" xml:"width" form:"width"`
Height int32 `json:"height" xml:"height" form:"height"`
Duration int32 `json:"duration" xml:"duration" form:"duration"`
MimeType string `json:"mime_type" xml:"mime_type" form:"mime_type"`
Extension string `json:"extension" xml:"extension" form:"extension"`
Size int64 `json:"size" xml:"size" form:"size"`
}
func (r *ResourceUpsertRequest) GetID() any {
return r.ID
}
func (r *ResourceUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": r.ID,
"category_id": r.CategoryID,
"title": r.Title,
"path": r.Path,
"width": r.Width,
"height": r.Height,
"duration": r.Duration,
"mime_type": r.MimeType,
"extension": r.Extension,
"size": r.Size,
}
}
func (r *ResourceUpsertRequest) ToEntity() any {
return &entities.Resource{
ID: r.ID,
CategoryID: r.CategoryID,
Title: r.Title,
Path: r.Path,
Width: r.Width,
Height: r.Height,
Duration: r.Duration,
MimeType: r.MimeType,
Extension: r.Extension,
Size: r.Size,
}
}

@ -0,0 +1,22 @@
package resource
import (
"sorbet/internal/services/resource/controller"
"sorbet/pkg/app"
)
type Service struct{}
func (s *Service) Init(ctx *app.Context) error {
ctx.Routes(&controller.ResourceController{})
ctx.Routes(&controller.ResourceCategoryController{})
return nil
}
func (s *Service) Start() error {
return nil
}
func (s *Service) Stop() error {
return nil
}

@ -0,0 +1,24 @@
package services
import (
"sorbet/pkg/app"
)
type Service struct {
inners []Service
}
func (s Service) Init(ctx *app.Context) error {
//TODO implement me
panic("implement me")
}
func (s Service) Start() error {
//TODO implement me
panic("implement me")
}
func (s Service) Stop() error {
//TODO implement me
panic("implement me")
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemLogController struct {
util.Controller[entities.SystemLog, request.SystemLogUpsertRequest]
}
func (s *SystemLogController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/logs", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemMenuController struct {
util.Controller[entities.SystemMenu, request.SystemMenuUpsertRequest]
}
func (s *SystemMenuController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/menus", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemPermissionController struct {
util.Controller[entities.SystemPermission, request.SystemPermissionUpsertRequest]
}
func (s *SystemPermissionController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/permissions", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemRoleController struct {
util.Controller[entities.SystemRole, request.SystemRoleUpsertRequest]
}
func (s *SystemRoleController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/roles", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemRolePowerController struct {
util.Controller[entities.SystemRolePower, request.SystemRolePowerUpsertRequest]
}
func (s *SystemRolePowerController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/role/powers", r)
}

@ -0,0 +1,16 @@
package controller
import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
)
type SystemUserController struct {
util.Controller[entities.SystemUser, request.SystemUserUpsertRequest]
}
func (s *SystemUserController) InitRoutes(r *echo.Group) {
s.RegisterRoutes("/system/users", r)
}

@ -0,0 +1,55 @@
package request
import (
"sorbet/internal/entities"
)
type SystemLogUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
Table string `json:"table" xml:"table" form:"table"`
RowID uint `json:"row_id" xml:"row_id" form:"row_id"`
Operation string `json:"operation" xml:"operation" form:"operation"`
IP string `json:"ip" xml:"ip" form:"ip"`
Comment string `json:"comment" xml:"comment" form:"comment"`
RequestID string `json:"request_id" xml:"request_id" form:"request_id"`
RequestInfo string `json:"request_info" xml:"request_info" form:"request_info"`
ColumnInfo string `json:"column_info" xml:"column_info" form:"column_info"`
UserID int64 `json:"user_id" xml:"user_id" form:"user_id"`
UserType int64 `json:"user_type" xml:"user_type" form:"user_type"`
}
func (s *SystemLogUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemLogUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"table": s.Table,
"row_id": s.RowID,
"operation": s.Operation,
"ip": s.IP,
"comment": s.Comment,
"request_id": s.RequestID,
"request_info": s.RequestInfo,
"column_info": s.ColumnInfo,
"user_id": s.UserID,
"user_type": s.UserType,
}
}
func (s *SystemLogUpsertRequest) ToEntity() any {
return &entities.SystemLog{
ID: s.ID,
Table: s.Table,
RowID: s.RowID,
Operation: s.Operation,
IP: s.IP,
Comment: s.Comment,
RequestID: s.RequestID,
RequestInfo: s.RequestInfo,
ColumnInfo: s.ColumnInfo,
UserID: s.UserID,
UserType: s.UserType,
}
}

@ -0,0 +1,40 @@
package request
import (
"sorbet/internal/entities"
)
type SystemMenuUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
Title string `json:"title" xml:"title" form:"title"`
Icon string `json:"icon" xml:"icon" form:"icon"`
Sort int32 `json:"sort" xml:"sort" form:"sort"`
Path string `json:"path" xml:"path" form:"path"`
}
func (s *SystemMenuUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemMenuUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"pid": s.PID,
"title": s.Title,
"icon": s.Icon,
"sort": s.Sort,
"path": s.Path,
}
}
func (s *SystemMenuUpsertRequest) ToEntity() any {
return &entities.SystemMenu{
ID: s.ID,
PID: &s.PID,
Title: s.Title,
Icon: s.Icon,
Sort: s.Sort,
Path: s.Path,
}
}

@ -0,0 +1,37 @@
package request
import (
"sorbet/internal/entities"
)
type SystemPermissionUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
PID uint `json:"pid" xml:"pid" form:"pid"`
Name string `json:"name" xml:"name" form:"name"`
Type string `json:"type" xml:"type" form:"type"`
Identifier string `json:"identifier" xml:"identifier" form:"identifier"`
}
func (s *SystemPermissionUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemPermissionUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"pid": s.PID,
"name": s.Name,
"type": s.Type,
"identifier": s.Identifier,
}
}
func (s *SystemPermissionUpsertRequest) ToEntity() any {
return &entities.SystemPermission{
ID: s.ID,
PID: &s.PID,
Name: s.Name,
Type: s.Type,
Identifier: s.Identifier,
}
}

@ -0,0 +1,34 @@
package request
import (
"sorbet/internal/entities"
)
type SystemRolePowerUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
RoleID uint `json:"role_id" xml:"role_id" form:"role_id"`
WithType string `json:"with_type" xml:"with_type" form:"with_type"`
WithID uint `json:"with_id" xml:"with_id" form:"with_id"`
}
func (s *SystemRolePowerUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemRolePowerUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"role_id": s.RoleID,
"with_type": s.WithType,
"with_id": s.WithID,
}
}
func (s *SystemRolePowerUpsertRequest) ToEntity() any {
return &entities.SystemRolePower{
ID: s.ID,
RoleID: s.RoleID,
WithType: s.WithType,
WithID: s.WithID,
}
}

@ -0,0 +1,28 @@
package request
import (
"sorbet/internal/entities"
)
type SystemRoleUpsertRequest struct {
ID uint `json:"id" xml:"id" form:"id" path:"id"`
Name string `json:"name" xml:"name" form:"name"`
}
func (s *SystemRoleUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemRoleUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"name": s.Name,
}
}
func (s *SystemRoleUpsertRequest) ToEntity() any {
return &entities.SystemRole{
ID: s.ID,
Name: s.Name,
}
}

@ -0,0 +1,34 @@
package request
import (
"sorbet/internal/entities"
)
type SystemUserUpsertRequest struct {
ID int64 `json:"id" xml:"id" form:"id" path:"id"`
Username string `json:"username" xml:"username" form:"username"`
Password string `json:"password" xml:"password" form:"password"`
Status bool `json:"status" xml:"status" form:"status"`
}
func (s *SystemUserUpsertRequest) GetID() any {
return s.ID
}
func (s *SystemUserUpsertRequest) ToMap() map[string]any {
return map[string]any{
"id": s.ID,
"username": s.Username,
"password": s.Password,
"status": s.Status,
}
}
func (s *SystemUserUpsertRequest) ToEntity() any {
return &entities.SystemUser{
ID: s.ID,
Username: s.Username,
Password: s.Password,
Status: s.Status,
}
}

@ -0,0 +1,26 @@
package system
import (
"sorbet/internal/services/system/controller"
"sorbet/pkg/app"
)
type Service struct{}
func (s *Service) Init(ctx *app.Context) error {
ctx.Routes(&controller.SystemLogController{})
ctx.Routes(&controller.SystemMenuController{})
ctx.Routes(&controller.SystemPermissionController{})
ctx.Routes(&controller.SystemRoleController{})
ctx.Routes(&controller.SystemRolePowerController{})
ctx.Routes(&controller.SystemUserController{})
return nil
}
func (s *Service) Start() error {
return nil
}
func (s *Service) Stop() error {
return nil
}

@ -0,0 +1,312 @@
package util
import (
"github.com/labstack/echo/v4"
"gorm.io/gorm"
"net/url"
"reflect"
"sorbet/pkg/db"
"sorbet/pkg/ioc"
"sorbet/pkg/rsp"
"strconv"
"strings"
)
type GetID interface {
GetID() any
}
type ToMap interface {
ToMap() map[string]any
}
type ToEntity interface {
ToEntity() any
}
type ControllerRequest interface {
GetID
ToMap
ToEntity
}
func ParseQuery[T any](query url.Values, qb *db.QueryBuilder[T]) (page, limit int, err error) {
var paginating bool
for key, values := range query {
switch key {
case "sortby":
for _, s := range values {
if s[0] == '+' {
qb.AscentBy(s[1:])
} else if s[0] == '-' {
qb.DescentBy(s[1:])
} else {
qb.AscentBy(s)
}
}
case "limit", "page":
var v int
if values[0] != "" {
v, err = strconv.Atoi(values[0])
if err != nil {
return
}
}
if v <= 0 {
return 0, 0, rsp.ErrInternal
}
if key == "limit" {
qb.Limit(v)
limit = v
} else {
paginating = true
page = max(v, 1)
}
default:
v := values[0]
i := strings.IndexByte(key, '#')
if i == -1 {
qb.Eq(key, v)
continue
}
switch k, op := key[:i], key[i+1:]; op {
case "=":
qb.Eq(k, v)
case "!=":
qb.Neq(k, v)
case "<":
qb.Lt(k, v)
case "<=":
qb.Lte(k, v)
case ">":
qb.Gt(k, v)
case ">=":
qb.Gte(k, v)
case "<>", "><":
var less, more any
switch len(values) {
case 2:
less, more = values[0], values[1]
case 1:
vs := strings.Split(v, ",")
if len(vs) != 2 || vs[0] == "" || vs[1] == "" {
return 0, 0, rsp.ErrBadParams
}
less, more = vs[0], vs[1]
}
if op == "<>" {
qb.Between(k, less, more)
} else {
qb.NotBetween(k, key, more)
}
case "nil":
qb.IsNull(k)
case "!nil":
qb.NotNull(k)
case "~":
qb.Like(k, v)
case "!~":
qb.NotLike(k, v)
case "in", "!in":
if len(values) == 1 {
values = strings.Split(v, ",")
}
vs := make([]any, len(values))
for i, value := range values {
vs[i] = value
}
if op == "in" {
qb.In(k, vs...)
} else {
qb.NotIn(k, vs...)
}
default:
qb.Eq(key, v)
}
}
}
if paginating {
return
}
if limit == 0 {
limit = 30
qb.Limit(limit)
}
qb.Offset((page - 1) * limit)
return
}
func getID(req any) (val any) {
defer func() {
if recover() != nil {
val = nil
}
}()
val = req.(GetID).GetID()
rv := reflect.ValueOf(val)
if rv.IsZero() || rv.IsNil() {
val = nil
}
return
}
func getValues(req any) map[string]any {
if v, ok := req.(ToMap); ok {
return v.ToMap()
}
return nil
}
func getEntity[T any](request any) *T {
v, ok := request.(ToEntity)
if !ok {
return nil
}
ent, ok := v.ToEntity().(*T)
if ok {
return ent
}
return nil
}
// Controller 控制器基类
//
// 泛型 [Entity] 表示操作的具体数据;
// 泛型 [Upsert] 表示创建或更新时需要的数据。
type Controller[Entity any, Upsert any] struct{}
func (ctr *Controller[Entity, Upsert]) RegisterRoutes(path string, r *echo.Group) {
r.PUT(path, ctr.Create)
r.DELETE(path+"/:id", ctr.Delete)
r.POST(path+"/:id", ctr.Update)
r.GET(path+"/:id", ctr.Get)
r.GET(path, ctr.List)
}
func (ctr *Controller[Entity, Upsert]) ORM(c echo.Context) (*gorm.DB, error) {
orm, err := ioc.Get[gorm.DB]()
if err != nil {
return nil, err
}
return orm.WithContext(c.Request().Context()), nil
}
func (ctr *Controller[Entity, Upsert]) Repository() (*db.Repository[Entity], error) {
orm, err := ioc.Get[gorm.DB]()
if err != nil {
return nil, err
}
return db.NewRepository[Entity](orm), nil
}
// Create 创建数据
func (ctr *Controller[Entity, Upsert]) Create(c echo.Context) error {
return ctr.upsert(c, true)
}
// Update 更新数据
func (ctr *Controller[Entity, Upsert]) Update(c echo.Context) error {
return ctr.upsert(c, false)
}
func (ctr *Controller[Entity, Upsert]) upsert(c echo.Context, isCreate bool) error {
request, err := Bind[Upsert](c)
if err != nil {
return err
}
repo, err := ctr.Repository()
if err != nil {
return err
}
id := getID(request)
if isCreate != reflect.ValueOf(id).IsZero() {
return rsp.BadParams(c, "参数错误")
}
// 更新数据
if !isCreate {
values := getValues(request)
if values == nil {
return rsp.ErrInternal
}
err = repo.UpdateByID(c.Request().Context(), id, values)
if err == nil {
// TODO(hupeh): 返回更新后的实体数据
return rsp.Ok(c, nil)
}
return err
}
// 创建数据
group := getEntity[Entity](request)
if group == nil {
return rsp.ErrInternal
}
err = repo.Create(c.Request().Context(), group)
if err != nil {
return rsp.Created(c, group)
}
return err
}
// Delete 通过ID删除数据
func (ctr *Controller[Entity, Upsert]) Delete(c echo.Context) error {
id, err := BindId(c, true)
if err != nil {
return err
}
repo, err := ctr.Repository()
if err != nil {
return err
}
err = repo.DeleteByID(c.Request().Context(), id)
if err != nil {
return err
}
return rsp.Ok(c, nil)
}
// Get 通过ID获取数据
func (ctr *Controller[Entity, Upsert]) Get(c echo.Context) error {
id, err := BindId(c, true)
if err != nil {
return err
}
repo, err := ctr.Repository()
if err != nil {
return err
}
entity, err := repo.GetByID(c.Request().Context(), id)
if err != nil {
return err
}
return rsp.Ok(c, entity)
}
// List 获取数据列表
func (ctr *Controller[Entity, Upsert]) List(c echo.Context) error {
repo, err := ctr.Repository()
if err != nil {
return err
}
qb := repo.NewQueryBuilder(c.Request().Context())
_, _, err = ParseQuery[Entity](c.QueryParams(), qb)
if err != nil {
return err
}
// 不是分页查询
if !c.QueryParams().Has("page") {
var result []*Entity
err = qb.Find(&result)
if err != nil {
return err
}
return rsp.Ok(c, result)
}
// 分页查询
pager, err := qb.Paginate()
if err != nil {
return err
}
return rsp.Ok(c, pager)
}

@ -0,0 +1,43 @@
package util
import (
"github.com/labstack/echo/v4"
"sorbet/pkg/rsp"
)
// RequestGuarder 参数守卫函数签名
type RequestGuarder[T any] func(c echo.Context, req *T) error
// Bind 将提交的参数绑定到泛型 T 的实例上
func Bind[T any](c echo.Context, guards ...RequestGuarder[T]) (*T, error) {
var req T
if err := c.Bind(&req); err != nil {
return nil, err
}
if err := c.Validate(&req); err != nil {
return nil, err
}
for _, guard := range guards {
if err := guard(c, &req); err != nil {
return nil, err
}
}
return &req, nil
}
func BindId(c echo.Context, must bool) (uint, error) {
request, err := Bind[struct {
ID uint `json:"id" xml:"id" path:"id"`
}](c)
if err != nil {
return 0, err
}
if must {
if request.ID <= 0 {
return 0, rsp.ErrBadParams
}
} else if request.ID < 0 {
return 0, rsp.ErrBadParams
}
return request.ID, err
}

@ -1,7 +0,0 @@
package util
import "github.com/labstack/echo/v4"
type EchoContext struct {
echo.Context
}

@ -0,0 +1,38 @@
package util
import (
"github.com/golang-jwt/jwt/v5"
"github.com/rs/xid"
"sorbet/pkg/env"
"sorbet/pkg/ticket"
"time"
)
func CreateTicket(claims *ticket.Claims) (string, error) {
if claims.ID == "" {
claims.ID = xid.New().String()
}
if claims.Issuer == "" {
claims.Issuer = env.String("TICKET_ISSUER")
}
if claims.Subject == "" {
claims.Issuer = env.String("TICKET_SUBJECT")
}
if claims.Audience == nil {
claims.Audience = env.List("TICKET_AUDIENCE")
}
if claims.ExpiresAt == nil {
ttl := env.Duration("TICKET_TTL", time.Hour)
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(ttl))
}
source := []byte(env.String("TICKET_PRIVATE_KEY"))
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(source)
if err != nil {
return "", err
}
signedString, err := ticket.Create(claims, privateKey)
if err != nil {
return "", err
}
return signedString, nil
}

@ -1,88 +1,19 @@
package main
import (
"github.com/labstack/echo/v4"
"github.com/swaggo/echo-swagger"
"gorm.io/gorm"
"net/http"
_ "sorbet/docs" // 开发文档
"log"
"sorbet/internal"
"sorbet/internal/entities"
"sorbet/internal/middleware"
"sorbet/internal/repositories"
"sorbet/internal/util"
"sorbet/pkg/env"
"sorbet/pkg/ioc"
"sorbet/pkg/rsp"
)
// @title 博客系统
// @version 1.0
// @description 基于 Echo 框架的基本库
//
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
//
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
//
// @accept json
func main() {
if err := env.Init(); err != nil {
panic(err)
}
if err := internal.Init(); err != nil {
panic(err)
}
repositories.Init()
e := echo.New()
e.HideBanner = true
e.HidePort = true
e.HTTPErrorHandler = func(err error, c echo.Context) {
if !c.Response().Committed {
http.Error(c.Response(), err.Error(), 500)
}
}
e.Logger = util.NewLogger()
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.Use(middleware.Logger)
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
db := ioc.MustGet[gorm.DB]().WithContext(c.Request().Context())
ci := ioc.Fork()
ci.Bind(db)
c.Set("db", db)
c.Set("ioc", ci)
return next(c)
}
})
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.GET("/", func(c echo.Context) error {
repo := repositories.NewCompanyRepository(c.Get("db").(*gorm.DB))
//err := c.Get("ioc").(*ioc.Container).Resolve(&repo)
//if err != nil {
// return err
//}
//db := ioc.MustGet[gorm.DB]().WithContext(c.Request().Context())
//ioc.Fork().Bind(db)
//repo := ioc.MustGet[repositories.CompanyRepository]()
repo.Create(c.Request().Context(), &entities.Company{Name: "海苔一诺"})
pager, err := repo.Paginate(c.Request().Context())
if err != nil {
return err
}
return rsp.Ok(c, pager)
})
e.Logger.Fatal(e.Start(":1323"))
}
func panicIf(e error) {
if e != nil {
panic(e)
if err := internal.Start(); err != nil {
log.Panicln(err)
}
}

@ -0,0 +1,55 @@
package app
import (
"context"
"github.com/labstack/echo/v4"
"sync"
)
type Context struct {
context.Context
prefix string
store map[any]any
router *echo.Group
mu sync.RWMutex
}
// Prefix 设置路由前缀
func (c *Context) Prefix(prefix string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.prefix != "" {
panic("already prefixed")
}
c.prefix = prefix
}
// Routes 注册路由
func (c *Context) Routes(routes Routable) {
c.mu.Lock()
defer c.mu.Unlock()
routes.InitRoutes(c.router)
}
// Set 设置值
func (c *Context) Set(key, val any) {
c.mu.Lock()
defer c.mu.Unlock()
c.store[key] = val
}
// Get 获取值,只会获取通过 Set 方法设置的值
func (c *Context) Get(key any) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.store[key]
return val, ok
}
// Value 获取值
func (c *Context) Value(key any) any {
if val, ok := c.Get(key); ok {
return val
}
return c.Value(key)
}

@ -0,0 +1,15 @@
package app
import "github.com/labstack/echo/v4"
type echoContext struct {
echo.Context
ctx *Context
}
func (e *echoContext) Get(key string) any {
if val, ok := e.ctx.Get(key); ok && val != nil {
return val
}
return e.Context.Get(key)
}

@ -0,0 +1,16 @@
package app
import "github.com/labstack/echo/v4"
type Service interface {
// Init 初始化服务
Init(ctx *Context) error
// Start 启动服务
Start() error
// Stop 停止服务
Stop() error
}
type Routable interface {
InitRoutes(r *echo.Group)
}

@ -122,20 +122,20 @@ func (q *QueryBuilder[T]) Count() (int64, error) {
return count, err
}
func (q *QueryBuilder[T]) First(entity any) error {
func (q *QueryBuilder[T]) First(entity *T) error {
return q.db.Scopes(q.Scopes).First(entity).Error
}
func (q *QueryBuilder[T]) Take(entity any) error {
func (q *QueryBuilder[T]) Take(entity *T) error {
return q.db.Scopes(q.Scopes).Take(entity).Error
}
func (q *QueryBuilder[T]) Last(entity any) error {
func (q *QueryBuilder[T]) Last(entity *T) error {
return q.db.Scopes(q.Scopes).Last(entity).Error
}
func (q *QueryBuilder[T]) Find(entity any) error {
return q.db.Scopes(q.Scopes).Find(entity).Error
func (q *QueryBuilder[T]) Find(entities *[]*T) error {
return q.db.Scopes(q.Scopes).Find(entities).Error
}
func (q *QueryBuilder[T]) Paginate() (*Pager[T], error) {

@ -1,67 +0,0 @@
package logs
import (
"log/slog"
"time"
)
type Attr = slog.Attr
// String returns an Attr for a string value.
func String(key, value string) Attr {
return slog.String(key, value)
}
// Int64 returns an Attr for an int64.
func Int64(key string, value int64) Attr {
return slog.Int64(key, value)
}
// Int converts an int to an int64 and returns
// an Attr with that value.
func Int(key string, value int) Attr {
return slog.Int(key, value)
}
// Uint64 returns an Attr for a uint64.
func Uint64(key string, v uint64) Attr {
return slog.Uint64(key, v)
}
// Float64 returns an Attr for a floating-point number.
func Float64(key string, v float64) Attr {
return slog.Float64(key, v)
}
// Bool returns an Attr for a bool.
func Bool(key string, v bool) Attr {
return slog.Bool(key, v)
}
// Time returns an Attr for a time.Time.
// It discards the monotonic portion.
func Time(key string, v time.Time) Attr {
return slog.Time(key, v)
}
// Duration returns an Attr for a time.Duration.
func Duration(key string, v time.Duration) Attr {
return slog.Duration(key, v)
}
// Group returns an Attr for a Group Instance.
// The first argument is the key; the remaining arguments
// are converted to Attrs as in [Logger.Log].
//
// Use Group to collect several key-value pairs under a single
// key on a log line, or as the result of LogValue
// in order to log a single value as multiple Attrs.
func Group(key string, args ...any) Attr {
return slog.Group(key, args...)
}
// Any returns an Attr for the supplied value.
// See [AnyValue] for how values are treated.
func Any(key string, value any) Attr {
return slog.Any(key, value)
}

@ -1,44 +0,0 @@
package logs
import (
"github.com/mattn/go-isatty"
"os"
)
type Color string
var (
//fgBlack Color = "\x1b[30m"
//fgWhiteItalic Color = "\x1b[37;3m"
FgRed Color = "\x1b[31m"
FgGreen Color = "\x1b[32m"
FgYellow Color = "\x1b[33m"
FgBlue Color = "\x1b[34m"
FgMagenta Color = "\x1b[35m"
FgCyan Color = "\x1b[36m"
FgWhite Color = "\x1b[37m"
FgHiBlack Color = "\x1b[90m"
fgGreenItalic Color = "\x1b[32;3m"
// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. It's also set to true if the NO_COLOR environment variable is
// set (regardless of its value). This is a global option and affects all
// colors. For more control over each Color block use the methods
// DisableColor() individually.
noColor = noColorIsSet() || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
)
// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string.
func noColorIsSet() bool {
return os.Getenv("NO_COLOR") != ""
}
func (c Color) Wrap(msg string) string {
if noColorIsSet() || noColor {
return msg
}
return string(c) + msg + "\x1b[0m"
}

@ -1 +0,0 @@
package logs

@ -1,59 +0,0 @@
package logs
import (
"log/slog"
)
type Level int
const (
LevelTrace Level = iota
LevelDebug // 用于程序调试
LevelInfo // 用于程序运行
LevelWarn // 潜在错误或非预期结果
LevelError // 发生错误,但不影响系统的继续运行
LevelFatal
LevelSilent
)
// 越界取近值
func (l Level) real() Level {
return min(LevelSilent, max(l, LevelTrace))
}
// Level 实现 slog.Leveler 接口
func (l Level) Level() slog.Level {
return slog.Level(16 - int(LevelSilent-l.real())*4)
}
func (l Level) slog() slog.Leveler {
return l.Level()
}
func (l Level) String() string {
switch l {
case LevelTrace:
return "TRACE"
case LevelDebug:
return "DEBUG"
case LevelInfo:
return "INFO"
case LevelWarn:
return "WARN"
case LevelError:
return "ERROR"
case LevelFatal:
return "FATAL"
case LevelSilent:
return "OFF"
}
if l < LevelTrace {
return "TRACE"
} else {
return "OFF"
}
}
func parseSlogLevel(level slog.Level) Level {
return Level(level/4 + 2).real()
}

@ -1,38 +0,0 @@
package logs
import (
"io"
"time"
)
var std = New(&Options{Level: LevelInfo})
func Default() Logger { return std }
func SetFlags(flags int) { Default().SetFlags(flags) }
func Flags() int { return Default().Flags() }
func SetTimezone(loc *time.Location) { Default().SetTimezone(loc) }
func Timezone() *time.Location { return Default().Timezone() }
func SetLevel(level Level) { Default().SetLevel(level) }
func GetLevel() Level { return Default().Level() }
func SetPersistWriter(w io.Writer) { Default().SetPersistWriter(w) }
func SetWriter(w io.Writer) { Default().SetWriter(w) }
func With(args ...Attr) Logger { return Default().With(args...) }
func WithGroup(name string) Logger { return Default().WithGroup(name) }
func Enabled(level Level) bool { return Default().Enabled(level) }
func Log(level Level, msg string, args ...any) { Default().Log(level, msg, args...) }
func ForkLevel(level Level, msg string, args ...any) ChildLogger {
return Default().ForkLevel(level, msg, args...)
}
func Trace(msg string, args ...any) { Default().Trace(msg, args...) }
func ForkTrace(msg string, args ...any) ChildLogger { return Default().ForkTrace(msg, args...) }
func Debug(msg string, args ...any) { Default().Debug(msg, args...) }
func ForkDebug(msg string, args ...any) ChildLogger { return Default().ForkDebug(msg, args...) }
func Info(msg string, args ...any) { Default().Info(msg) }
func ForkInfo(msg string, args ...any) ChildLogger { return Default().ForkInfo(msg, args...) }
func Warn(msg string, args ...any) { Default().Warn(msg, args...) }
func ForkWarn(msg string, args ...any) ChildLogger { return Default().ForkWarn(msg, args...) }
func Error(msg string, args ...any) { Default().Error(msg, args...) }
func ForkError(msg string, args ...any) ChildLogger { return Default().ForkError(msg, args...) }
func Fatal(msg string, args ...any) { Default().Fatal(msg, args...) }
func ForkFatal(msg string, args ...any) ChildLogger { return Default().ForkFatal(msg, args...) }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save