From fa896b66b6c9af181b3f8319a13476f9bf3d7246 Mon Sep 17 00:00:00 2001 From: hupeh Date: Wed, 20 Sep 2023 00:00:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 18 +- README.md | 17 - cmd/main.go | 1 + internal/entities/company.go | 2 +- internal/entities/company_department.go | 2 +- internal/entities/company_staff.go | 2 +- internal/entities/config.go | 2 +- internal/entities/config_group.go | 4 +- internal/entities/feature.go | 2 +- internal/entities/feature_category.go | 2 +- internal/entities/feature_config.go | 2 +- internal/entities/feature_content.go | 21 +- internal/entities/feature_content_chapter.go | 23 +- internal/entities/feature_content_detail.go | 31 +- internal/entities/resource.go | 2 +- internal/entities/resource_category.go | 2 +- internal/entities/system_menu.go | 2 +- internal/entities/system_permission.go | 2 +- internal/entities/system_role.go | 2 +- internal/entities/system_user.go | 5 +- internal/init.go | 64 +++ internal/middleware/jwt.go | 140 ----- internal/middleware/ticket.go | 104 ++++ internal/repositories/company.go | 5 +- internal/repositories/company_department.go | 5 +- internal/repositories/company_staff.go | 5 +- internal/repositories/config.go | 5 +- internal/repositories/config_group.go | 5 +- internal/repositories/feature.go | 5 +- internal/repositories/feature_category.go | 5 +- internal/repositories/feature_config.go | 5 +- internal/repositories/feature_content.go | 5 +- .../repositories/feature_content_chapter.go | 5 +- .../repositories/feature_content_detail.go | 5 +- internal/repositories/resource.go | 5 +- internal/repositories/resource_category.go | 5 +- internal/repositories/system_log.go | 5 +- internal/repositories/system_menu.go | 5 +- internal/repositories/system_permission.go | 5 +- internal/repositories/system_role.go | 5 +- internal/repositories/system_role_power.go | 5 +- internal/repositories/system_user.go | 5 +- .../company/controller/company_controller.go | 16 + .../company_department_controller.go | 16 + .../controller/company_staff_controller.go | 16 + .../company_department_upsert_request.go | 42 ++ .../request/company_staff_upsert_request.go | 49 ++ .../company/request/company_upsert_request.go | 39 ++ internal/services/company/service.go | 23 + .../config/controller/config_controller.go | 16 + .../config/request/config_upsert_request.go | 50 ++ .../controller/feature_category_controller.go | 16 + .../controller/feature_config_controller.go | 16 + .../feature_content_chapter_controller.go | 16 + .../controller/feature_content_controller.go | 16 + .../feature_content_detail_controller.go | 16 + .../feature/controller/feature_controller.go | 16 + .../feature_category_upsert_request.go | 43 ++ .../request/feature_config_upsert_request.go | 40 ++ .../feature_content_chapter_upsert_request.go | 43 ++ .../feature_content_detail_upsert_request.go | 55 ++ .../request/feature_content_upsert_request.go | 40 ++ .../feature/request/feature_upsert_request.go | 37 ++ internal/services/feature/service.go | 25 + .../resource_category_controller.go | 16 + .../controller/resource_controller.go | 16 + .../resource_category_upsert_request.go | 37 ++ .../request/resource_upsert_request.go | 52 ++ internal/services/resource/service.go | 22 + internal/services/service.go | 24 + .../controller/system_log_controller.go | 16 + .../controller/system_menu_controller.go | 16 + .../system_permission_controller.go | 16 + .../controller/system_role_controller.go | 16 + .../system_role_power_controller.go | 16 + .../controller/system_user_controller.go | 16 + .../request/system_log_upsert_request.go | 55 ++ .../request/system_menu_upsert_request.go | 40 ++ .../system_permission_upsert_request.go | 37 ++ .../system_role_power_upsert_request.go | 34 ++ .../request/system_role_upsert_request.go | 28 + .../request/system_user_upsert_request.go | 34 ++ internal/services/system/service.go | 26 + internal/util/controller.go | 312 +++++++++++ internal/util/echo_bind.go | 43 ++ internal/util/echo_context.go | 7 - main.go | 75 +-- pkg/db/query_builder.go | 10 +- pkg/logs/attr.go | 67 --- pkg/logs/color.go | 44 -- pkg/logs/handler.go | 1 - pkg/logs/level.go | 59 -- pkg/logs/log.go | 38 -- pkg/logs/logger.go | 512 ------------------ pkg/rsp/error.go | 4 +- pkg/rsp/handler.go | 83 +++ pkg/rsp/{rsp.go => respond_utils.go} | 18 +- pkg/rsp/{sse.go => server_sent_events.go} | 0 98 files changed, 1906 insertions(+), 1077 deletions(-) create mode 100644 cmd/main.go delete mode 100644 internal/middleware/jwt.go create mode 100644 internal/middleware/ticket.go create mode 100644 internal/services/company/controller/company_controller.go create mode 100644 internal/services/company/controller/company_department_controller.go create mode 100644 internal/services/company/controller/company_staff_controller.go create mode 100644 internal/services/company/request/company_department_upsert_request.go create mode 100644 internal/services/company/request/company_staff_upsert_request.go create mode 100644 internal/services/company/request/company_upsert_request.go create mode 100644 internal/services/company/service.go create mode 100644 internal/services/config/controller/config_controller.go create mode 100644 internal/services/config/request/config_upsert_request.go create mode 100644 internal/services/feature/controller/feature_category_controller.go create mode 100644 internal/services/feature/controller/feature_config_controller.go create mode 100644 internal/services/feature/controller/feature_content_chapter_controller.go create mode 100644 internal/services/feature/controller/feature_content_controller.go create mode 100644 internal/services/feature/controller/feature_content_detail_controller.go create mode 100644 internal/services/feature/controller/feature_controller.go create mode 100644 internal/services/feature/request/feature_category_upsert_request.go create mode 100644 internal/services/feature/request/feature_config_upsert_request.go create mode 100644 internal/services/feature/request/feature_content_chapter_upsert_request.go create mode 100644 internal/services/feature/request/feature_content_detail_upsert_request.go create mode 100644 internal/services/feature/request/feature_content_upsert_request.go create mode 100644 internal/services/feature/request/feature_upsert_request.go create mode 100644 internal/services/feature/service.go create mode 100644 internal/services/resource/controller/resource_category_controller.go create mode 100644 internal/services/resource/controller/resource_controller.go create mode 100644 internal/services/resource/request/resource_category_upsert_request.go create mode 100644 internal/services/resource/request/resource_upsert_request.go create mode 100644 internal/services/resource/service.go create mode 100644 internal/services/service.go create mode 100644 internal/services/system/controller/system_log_controller.go create mode 100644 internal/services/system/controller/system_menu_controller.go create mode 100644 internal/services/system/controller/system_permission_controller.go create mode 100644 internal/services/system/controller/system_role_controller.go create mode 100644 internal/services/system/controller/system_role_power_controller.go create mode 100644 internal/services/system/controller/system_user_controller.go create mode 100644 internal/services/system/request/system_log_upsert_request.go create mode 100644 internal/services/system/request/system_menu_upsert_request.go create mode 100644 internal/services/system/request/system_permission_upsert_request.go create mode 100644 internal/services/system/request/system_role_power_upsert_request.go create mode 100644 internal/services/system/request/system_role_upsert_request.go create mode 100644 internal/services/system/request/system_user_upsert_request.go create mode 100644 internal/services/system/service.go create mode 100644 internal/util/controller.go create mode 100644 internal/util/echo_bind.go delete mode 100644 internal/util/echo_context.go delete mode 100644 pkg/logs/attr.go delete mode 100644 pkg/logs/color.go delete mode 100644 pkg/logs/handler.go delete mode 100644 pkg/logs/level.go delete mode 100644 pkg/logs/log.go delete mode 100644 pkg/logs/logger.go create mode 100644 pkg/rsp/handler.go rename pkg/rsp/{rsp.go => respond_utils.go} (95%) rename pkg/rsp/{sse.go => server_sent_events.go} (100%) diff --git a/.env.example b/.env.example index a72359f..5dd566e 100644 --- a/.env.example +++ b/.env.example @@ -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= \ No newline at end of file diff --git a/README.md b/README.md index 242e9cc..940e2c9 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,3 @@ -## 开发接口文档 - -> [swag 文档地址](https://github.com/swaggo/swag) - -生成开发文档命令 - -```shell -swag init -``` - -注释文档格式化命令 - -```shell -swag fmt -``` - - ## 关于 Api 参考 Restful 设计风格。 diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1 @@ +package main diff --git a/internal/entities/company.go b/internal/entities/company.go index b9e0a86..38d5d40 100644 --- a/internal/entities/company.go +++ b/internal/entities/company.go @@ -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"` diff --git a/internal/entities/company_department.go b/internal/entities/company_department.go index b993b55..406cd41 100644 --- a/internal/entities/company_department.go +++ b/internal/entities/company_department.go @@ -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"` diff --git a/internal/entities/company_staff.go b/internal/entities/company_staff.go index 2d651d3..3f4e9c1 100644 --- a/internal/entities/company_staff.go +++ b/internal/entities/company_staff.go @@ -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"` } diff --git a/internal/entities/config.go b/internal/entities/config.go index e42b6bf..2747f4a 100644 --- a/internal/entities/config.go +++ b/internal/entities/config.go @@ -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:删除时间"` } diff --git a/internal/entities/config_group.go b/internal/entities/config_group.go index 2380d37..4eede81 100644 --- a/internal/entities/config_group.go +++ b/internal/entities/config_group.go @@ -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"` } diff --git a/internal/entities/feature.go b/internal/entities/feature.go index 7c5b181..151f782 100644 --- a/internal/entities/feature.go +++ b/internal/entities/feature.go @@ -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"` diff --git a/internal/entities/feature_category.go b/internal/entities/feature_category.go index 96fc337..1f17a7b 100644 --- a/internal/entities/feature_category.go +++ b/internal/entities/feature_category.go @@ -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"` } diff --git a/internal/entities/feature_config.go b/internal/entities/feature_config.go index ca9ecdd..f48ba12 100644 --- a/internal/entities/feature_config.go +++ b/internal/entities/feature_config.go @@ -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:删除时间"` } diff --git a/internal/entities/feature_content.go b/internal/entities/feature_content.go index 670c8b7..f179b5f 100644 --- a/internal/entities/feature_content.go +++ b/internal/entities/feature_content.go @@ -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"` diff --git a/internal/entities/feature_content_chapter.go b/internal/entities/feature_content_chapter.go index 47c2c7d..a239c02 100644 --- a/internal/entities/feature_content_chapter.go +++ b/internal/entities/feature_content_chapter.go @@ -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"` diff --git a/internal/entities/feature_content_detail.go b/internal/entities/feature_content_detail.go index bb60a47..d001774 100644 --- a/internal/entities/feature_content_detail.go +++ b/internal/entities/feature_content_detail.go @@ -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"` diff --git a/internal/entities/resource.go b/internal/entities/resource.go index 7a60137..a7c08d5 100644 --- a/internal/entities/resource.go +++ b/internal/entities/resource.go @@ -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:删除时间"` } diff --git a/internal/entities/resource_category.go b/internal/entities/resource_category.go index 4e1831c..a6680dc 100644 --- a/internal/entities/resource_category.go +++ b/internal/entities/resource_category.go @@ -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"` diff --git a/internal/entities/system_menu.go b/internal/entities/system_menu.go index b3f672b..e4ed1b4 100644 --- a/internal/entities/system_menu.go +++ b/internal/entities/system_menu.go @@ -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"` } diff --git a/internal/entities/system_permission.go b/internal/entities/system_permission.go index e650cad..d429378 100644 --- a/internal/entities/system_permission.go +++ b/internal/entities/system_permission.go @@ -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"` } diff --git a/internal/entities/system_role.go b/internal/entities/system_role.go index 62d3be3..9d99ee1 100644 --- a/internal/entities/system_role.go +++ b/internal/entities/system_role.go @@ -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"` diff --git a/internal/entities/system_user.go b/internal/entities/system_user.go index c7fc8dc..6b8fb78 100644 --- a/internal/entities/system_user.go +++ b/internal/entities/system_user.go @@ -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"` } diff --git a/internal/init.go b/internal/init.go index 22f642f..871e157 100644 --- a/internal/init.go +++ b/internal/init.go @@ -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 +} diff --git a/internal/middleware/jwt.go b/internal/middleware/jwt.go deleted file mode 100644 index 05eda14..0000000 --- a/internal/middleware/jwt.go +++ /dev/null @@ -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 ":" or ":,:" that is used - // to extract token from the request. - // Optional. Default value "header:Authorization". - // Possible values: - // - "header:" or "header::" - // `` is argument value to cut/trim prefix of the extracted value. This is useful if header - // value has static prefix like `Authorization: ` where part that we - // want to cut is ` ` note the space at the end. - // In case of JWT tokens `Authorization: Bearer ` prefix we cut is `Bearer `. - // If prefix is left empty the whole value is returned. - // - "query:" - // - "param:" - // - "cookie:" - // - "form:" - // 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) -} diff --git a/internal/middleware/ticket.go b/internal/middleware/ticket.go new file mode 100644 index 0000000..9a2fc60 --- /dev/null +++ b/internal/middleware/ticket.go @@ -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 +} diff --git a/internal/repositories/company.go b/internal/repositories/company.go index 21f3f55..cf8c704 100644 --- a/internal/repositories/company.go +++ b/internal/repositories/company.go @@ -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"), + } } - diff --git a/internal/repositories/company_department.go b/internal/repositories/company_department.go index 2d127b2..26f65b4 100755 --- a/internal/repositories/company_department.go +++ b/internal/repositories/company_department.go @@ -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"), + } } - diff --git a/internal/repositories/company_staff.go b/internal/repositories/company_staff.go index 93cb3c8..3ce524e 100755 --- a/internal/repositories/company_staff.go +++ b/internal/repositories/company_staff.go @@ -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"), + } } - diff --git a/internal/repositories/config.go b/internal/repositories/config.go index 8141718..8f12c4d 100755 --- a/internal/repositories/config.go +++ b/internal/repositories/config.go @@ -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"), + } } - diff --git a/internal/repositories/config_group.go b/internal/repositories/config_group.go index 73161a2..942e714 100755 --- a/internal/repositories/config_group.go +++ b/internal/repositories/config_group.go @@ -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"), + } } - diff --git a/internal/repositories/feature.go b/internal/repositories/feature.go index 851ffe8..b891eeb 100755 --- a/internal/repositories/feature.go +++ b/internal/repositories/feature.go @@ -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"), + } } - diff --git a/internal/repositories/feature_category.go b/internal/repositories/feature_category.go index ba0fd45..97bc4b3 100755 --- a/internal/repositories/feature_category.go +++ b/internal/repositories/feature_category.go @@ -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"), + } } - diff --git a/internal/repositories/feature_config.go b/internal/repositories/feature_config.go index f8be8cb..b4b359f 100755 --- a/internal/repositories/feature_config.go +++ b/internal/repositories/feature_config.go @@ -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"), + } } - diff --git a/internal/repositories/feature_content.go b/internal/repositories/feature_content.go index f3e40c1..676b893 100755 --- a/internal/repositories/feature_content.go +++ b/internal/repositories/feature_content.go @@ -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"), + } } - diff --git a/internal/repositories/feature_content_chapter.go b/internal/repositories/feature_content_chapter.go index 9dc8832..f594ba5 100755 --- a/internal/repositories/feature_content_chapter.go +++ b/internal/repositories/feature_content_chapter.go @@ -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"), + } } - diff --git a/internal/repositories/feature_content_detail.go b/internal/repositories/feature_content_detail.go index 6c67632..d8c5ff3 100755 --- a/internal/repositories/feature_content_detail.go +++ b/internal/repositories/feature_content_detail.go @@ -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"), + } } - diff --git a/internal/repositories/resource.go b/internal/repositories/resource.go index a8230da..eea5739 100755 --- a/internal/repositories/resource.go +++ b/internal/repositories/resource.go @@ -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"), + } } - diff --git a/internal/repositories/resource_category.go b/internal/repositories/resource_category.go index 47e2202..a964383 100755 --- a/internal/repositories/resource_category.go +++ b/internal/repositories/resource_category.go @@ -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"), + } } - diff --git a/internal/repositories/system_log.go b/internal/repositories/system_log.go index 1988f4e..30635af 100755 --- a/internal/repositories/system_log.go +++ b/internal/repositories/system_log.go @@ -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"), + } } - diff --git a/internal/repositories/system_menu.go b/internal/repositories/system_menu.go index d8d1868..659b3e3 100755 --- a/internal/repositories/system_menu.go +++ b/internal/repositories/system_menu.go @@ -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"), + } } - diff --git a/internal/repositories/system_permission.go b/internal/repositories/system_permission.go index 457ec68..e8a7c7d 100755 --- a/internal/repositories/system_permission.go +++ b/internal/repositories/system_permission.go @@ -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"), + } } - diff --git a/internal/repositories/system_role.go b/internal/repositories/system_role.go index 7da9cde..ba59325 100755 --- a/internal/repositories/system_role.go +++ b/internal/repositories/system_role.go @@ -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"), + } } - diff --git a/internal/repositories/system_role_power.go b/internal/repositories/system_role_power.go index 385ea29..64c86d8 100755 --- a/internal/repositories/system_role_power.go +++ b/internal/repositories/system_role_power.go @@ -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"), + } } - diff --git a/internal/repositories/system_user.go b/internal/repositories/system_user.go index 98b537c..b89828f 100755 --- a/internal/repositories/system_user.go +++ b/internal/repositories/system_user.go @@ -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"), + } } - diff --git a/internal/services/company/controller/company_controller.go b/internal/services/company/controller/company_controller.go new file mode 100644 index 0000000..37545e5 --- /dev/null +++ b/internal/services/company/controller/company_controller.go @@ -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) +} diff --git a/internal/services/company/controller/company_department_controller.go b/internal/services/company/controller/company_department_controller.go new file mode 100644 index 0000000..e9eae72 --- /dev/null +++ b/internal/services/company/controller/company_department_controller.go @@ -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) +} diff --git a/internal/services/company/controller/company_staff_controller.go b/internal/services/company/controller/company_staff_controller.go new file mode 100644 index 0000000..d1fac85 --- /dev/null +++ b/internal/services/company/controller/company_staff_controller.go @@ -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) +} diff --git a/internal/services/company/request/company_department_upsert_request.go b/internal/services/company/request/company_department_upsert_request.go new file mode 100644 index 0000000..36b8168 --- /dev/null +++ b/internal/services/company/request/company_department_upsert_request.go @@ -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, + } +} diff --git a/internal/services/company/request/company_staff_upsert_request.go b/internal/services/company/request/company_staff_upsert_request.go new file mode 100644 index 0000000..42f4fb0 --- /dev/null +++ b/internal/services/company/request/company_staff_upsert_request.go @@ -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, + } +} diff --git a/internal/services/company/request/company_upsert_request.go b/internal/services/company/request/company_upsert_request.go new file mode 100644 index 0000000..d7fdaf5 --- /dev/null +++ b/internal/services/company/request/company_upsert_request.go @@ -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, + } +} diff --git a/internal/services/company/service.go b/internal/services/company/service.go new file mode 100644 index 0000000..fc16e19 --- /dev/null +++ b/internal/services/company/service.go @@ -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 +} diff --git a/internal/services/config/controller/config_controller.go b/internal/services/config/controller/config_controller.go new file mode 100644 index 0000000..6c62b6f --- /dev/null +++ b/internal/services/config/controller/config_controller.go @@ -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) +} diff --git a/internal/services/config/request/config_upsert_request.go b/internal/services/config/request/config_upsert_request.go new file mode 100644 index 0000000..def43d6 --- /dev/null +++ b/internal/services/config/request/config_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/controller/feature_category_controller.go b/internal/services/feature/controller/feature_category_controller.go new file mode 100644 index 0000000..feb6416 --- /dev/null +++ b/internal/services/feature/controller/feature_category_controller.go @@ -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) +} diff --git a/internal/services/feature/controller/feature_config_controller.go b/internal/services/feature/controller/feature_config_controller.go new file mode 100644 index 0000000..267890a --- /dev/null +++ b/internal/services/feature/controller/feature_config_controller.go @@ -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) +} diff --git a/internal/services/feature/controller/feature_content_chapter_controller.go b/internal/services/feature/controller/feature_content_chapter_controller.go new file mode 100644 index 0000000..2c4caa9 --- /dev/null +++ b/internal/services/feature/controller/feature_content_chapter_controller.go @@ -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) +} diff --git a/internal/services/feature/controller/feature_content_controller.go b/internal/services/feature/controller/feature_content_controller.go new file mode 100644 index 0000000..365ab89 --- /dev/null +++ b/internal/services/feature/controller/feature_content_controller.go @@ -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) +} diff --git a/internal/services/feature/controller/feature_content_detail_controller.go b/internal/services/feature/controller/feature_content_detail_controller.go new file mode 100644 index 0000000..e7bd84d --- /dev/null +++ b/internal/services/feature/controller/feature_content_detail_controller.go @@ -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) +} diff --git a/internal/services/feature/controller/feature_controller.go b/internal/services/feature/controller/feature_controller.go new file mode 100644 index 0000000..f8bf938 --- /dev/null +++ b/internal/services/feature/controller/feature_controller.go @@ -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) +} diff --git a/internal/services/feature/request/feature_category_upsert_request.go b/internal/services/feature/request/feature_category_upsert_request.go new file mode 100644 index 0000000..fe907e8 --- /dev/null +++ b/internal/services/feature/request/feature_category_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/request/feature_config_upsert_request.go b/internal/services/feature/request/feature_config_upsert_request.go new file mode 100644 index 0000000..6214f8d --- /dev/null +++ b/internal/services/feature/request/feature_config_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/request/feature_content_chapter_upsert_request.go b/internal/services/feature/request/feature_content_chapter_upsert_request.go new file mode 100644 index 0000000..53e77d6 --- /dev/null +++ b/internal/services/feature/request/feature_content_chapter_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/request/feature_content_detail_upsert_request.go b/internal/services/feature/request/feature_content_detail_upsert_request.go new file mode 100644 index 0000000..032dcc5 --- /dev/null +++ b/internal/services/feature/request/feature_content_detail_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/request/feature_content_upsert_request.go b/internal/services/feature/request/feature_content_upsert_request.go new file mode 100644 index 0000000..ccaddb1 --- /dev/null +++ b/internal/services/feature/request/feature_content_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/request/feature_upsert_request.go b/internal/services/feature/request/feature_upsert_request.go new file mode 100644 index 0000000..ec27f61 --- /dev/null +++ b/internal/services/feature/request/feature_upsert_request.go @@ -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, + } +} diff --git a/internal/services/feature/service.go b/internal/services/feature/service.go new file mode 100644 index 0000000..48dcbe2 --- /dev/null +++ b/internal/services/feature/service.go @@ -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 +} diff --git a/internal/services/resource/controller/resource_category_controller.go b/internal/services/resource/controller/resource_category_controller.go new file mode 100644 index 0000000..f3b5c44 --- /dev/null +++ b/internal/services/resource/controller/resource_category_controller.go @@ -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) +} diff --git a/internal/services/resource/controller/resource_controller.go b/internal/services/resource/controller/resource_controller.go new file mode 100644 index 0000000..38b3f45 --- /dev/null +++ b/internal/services/resource/controller/resource_controller.go @@ -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) +} diff --git a/internal/services/resource/request/resource_category_upsert_request.go b/internal/services/resource/request/resource_category_upsert_request.go new file mode 100644 index 0000000..dac7d8a --- /dev/null +++ b/internal/services/resource/request/resource_category_upsert_request.go @@ -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, + } +} diff --git a/internal/services/resource/request/resource_upsert_request.go b/internal/services/resource/request/resource_upsert_request.go new file mode 100644 index 0000000..0d478c5 --- /dev/null +++ b/internal/services/resource/request/resource_upsert_request.go @@ -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, + } +} diff --git a/internal/services/resource/service.go b/internal/services/resource/service.go new file mode 100644 index 0000000..6caff4f --- /dev/null +++ b/internal/services/resource/service.go @@ -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 +} diff --git a/internal/services/service.go b/internal/services/service.go new file mode 100644 index 0000000..dd422c8 --- /dev/null +++ b/internal/services/service.go @@ -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") +} diff --git a/internal/services/system/controller/system_log_controller.go b/internal/services/system/controller/system_log_controller.go new file mode 100644 index 0000000..5adef52 --- /dev/null +++ b/internal/services/system/controller/system_log_controller.go @@ -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) +} diff --git a/internal/services/system/controller/system_menu_controller.go b/internal/services/system/controller/system_menu_controller.go new file mode 100644 index 0000000..2fce501 --- /dev/null +++ b/internal/services/system/controller/system_menu_controller.go @@ -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) +} diff --git a/internal/services/system/controller/system_permission_controller.go b/internal/services/system/controller/system_permission_controller.go new file mode 100644 index 0000000..e3c1124 --- /dev/null +++ b/internal/services/system/controller/system_permission_controller.go @@ -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) +} diff --git a/internal/services/system/controller/system_role_controller.go b/internal/services/system/controller/system_role_controller.go new file mode 100644 index 0000000..df44a96 --- /dev/null +++ b/internal/services/system/controller/system_role_controller.go @@ -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) +} diff --git a/internal/services/system/controller/system_role_power_controller.go b/internal/services/system/controller/system_role_power_controller.go new file mode 100644 index 0000000..7eb0951 --- /dev/null +++ b/internal/services/system/controller/system_role_power_controller.go @@ -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) +} diff --git a/internal/services/system/controller/system_user_controller.go b/internal/services/system/controller/system_user_controller.go new file mode 100644 index 0000000..92218c5 --- /dev/null +++ b/internal/services/system/controller/system_user_controller.go @@ -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) +} diff --git a/internal/services/system/request/system_log_upsert_request.go b/internal/services/system/request/system_log_upsert_request.go new file mode 100644 index 0000000..3ce56f8 --- /dev/null +++ b/internal/services/system/request/system_log_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/request/system_menu_upsert_request.go b/internal/services/system/request/system_menu_upsert_request.go new file mode 100644 index 0000000..711dee1 --- /dev/null +++ b/internal/services/system/request/system_menu_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/request/system_permission_upsert_request.go b/internal/services/system/request/system_permission_upsert_request.go new file mode 100644 index 0000000..d68b7cc --- /dev/null +++ b/internal/services/system/request/system_permission_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/request/system_role_power_upsert_request.go b/internal/services/system/request/system_role_power_upsert_request.go new file mode 100644 index 0000000..36721f9 --- /dev/null +++ b/internal/services/system/request/system_role_power_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/request/system_role_upsert_request.go b/internal/services/system/request/system_role_upsert_request.go new file mode 100644 index 0000000..2982cc3 --- /dev/null +++ b/internal/services/system/request/system_role_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/request/system_user_upsert_request.go b/internal/services/system/request/system_user_upsert_request.go new file mode 100644 index 0000000..fa741e3 --- /dev/null +++ b/internal/services/system/request/system_user_upsert_request.go @@ -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, + } +} diff --git a/internal/services/system/service.go b/internal/services/system/service.go new file mode 100644 index 0000000..6cb21bc --- /dev/null +++ b/internal/services/system/service.go @@ -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 +} diff --git a/internal/util/controller.go b/internal/util/controller.go new file mode 100644 index 0000000..2f917e1 --- /dev/null +++ b/internal/util/controller.go @@ -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) +} diff --git a/internal/util/echo_bind.go b/internal/util/echo_bind.go new file mode 100644 index 0000000..f4829e8 --- /dev/null +++ b/internal/util/echo_bind.go @@ -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 +} diff --git a/internal/util/echo_context.go b/internal/util/echo_context.go deleted file mode 100644 index 3c2628c..0000000 --- a/internal/util/echo_context.go +++ /dev/null @@ -1,7 +0,0 @@ -package util - -import "github.com/labstack/echo/v4" - -type EchoContext struct { - echo.Context -} diff --git a/main.go b/main.go index 52c6deb..571bc08 100644 --- a/main.go +++ b/main.go @@ -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) } } diff --git a/pkg/db/query_builder.go b/pkg/db/query_builder.go index a1380d3..4f29a3a 100644 --- a/pkg/db/query_builder.go +++ b/pkg/db/query_builder.go @@ -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) { diff --git a/pkg/logs/attr.go b/pkg/logs/attr.go deleted file mode 100644 index 48e12d3..0000000 --- a/pkg/logs/attr.go +++ /dev/null @@ -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) -} diff --git a/pkg/logs/color.go b/pkg/logs/color.go deleted file mode 100644 index 74dbce4..0000000 --- a/pkg/logs/color.go +++ /dev/null @@ -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" -} diff --git a/pkg/logs/handler.go b/pkg/logs/handler.go deleted file mode 100644 index 80aeb27..0000000 --- a/pkg/logs/handler.go +++ /dev/null @@ -1 +0,0 @@ -package logs diff --git a/pkg/logs/level.go b/pkg/logs/level.go deleted file mode 100644 index d7a6e90..0000000 --- a/pkg/logs/level.go +++ /dev/null @@ -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() -} diff --git a/pkg/logs/log.go b/pkg/logs/log.go deleted file mode 100644 index 90c2fe0..0000000 --- a/pkg/logs/log.go +++ /dev/null @@ -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...) } diff --git a/pkg/logs/logger.go b/pkg/logs/logger.go deleted file mode 100644 index b1ac0e6..0000000 --- a/pkg/logs/logger.go +++ /dev/null @@ -1,512 +0,0 @@ -package logs - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "log" - "log/slog" - "os" - "runtime" - "strings" - "sync" - "sync/atomic" - "time" - "unsafe" -) - -var ( - // 使用东八区时间 - // https://cloud.tencent.com/developer/article/1805859 - cstZone = time.FixedZone("CST", 8*3600) - childLoggerKey = "sorbet/log:ChildLogger" -) - -const ( - Ldate = 1 << iota - Ltime - Lmicroseconds - Llongfile - Lshortfile - Lfields - Lcolor - LstdFlags = Ltime | Lmicroseconds | Lfields | Lcolor -) - -type Logger interface { - SetFlags(flags int) - Flags() int - SetTimezone(loc *time.Location) - Timezone() *time.Location - SetLevel(level Level) - Level() Level - SetPersistWriter(w io.Writer) - SetWriter(w io.Writer) - With(args ...Attr) Logger - WithGroup(name string) Logger - Enabled(level Level) bool - Log(level Level, msg string, args ...any) - ForkLevel(level Level, msg string, args ...any) ChildLogger - Trace(msg string, args ...any) - ForkTrace(msg string, args ...any) ChildLogger - Debug(msg string, args ...any) - ForkDebug(msg string, args ...any) ChildLogger - Info(msg string, args ...any) - ForkInfo(msg string, args ...any) ChildLogger - Warn(msg string, args ...any) - ForkWarn(msg string, args ...any) ChildLogger - Error(msg string, args ...any) - ForkError(msg string, args ...any) ChildLogger - Fatal(msg string, args ...any) - ForkFatal(msg string, args ...any) ChildLogger -} - -type ChildLogger interface { - Print(msg string, args ...any) - Finish() -} - -type Options struct { - Flags int - Level Level - Timezone *time.Location - PersistWriter io.Writer - Writer io.Writer -} - -type logger struct { - parent *logger - isChild int32 - indent int32 - - level int32 - flags int32 - timezone unsafe.Pointer - - outMu *sync.Mutex - isPersistDiscard int32 - isDiscard int32 - persistWriter io.Writer - writer io.Writer - - handler slog.Handler - l *log.Logger -} - -func New(opts *Options) Logger { - if opts.Flags == 0 { - opts.Flags = LstdFlags - } - if opts.Timezone == nil { - opts.Timezone = cstZone - } - if opts.PersistWriter == nil { - opts.PersistWriter = io.Discard - } - if opts.Writer == nil { - opts.Writer = os.Stderr - } - - var l *logger - l = &logger{ - outMu: &sync.Mutex{}, - persistWriter: opts.PersistWriter, - writer: opts.Writer, - l: log.New(opts.Writer, "", 0), - } - - l.SetLevel(opts.Level) - l.SetFlags(opts.Flags) - l.SetTimezone(opts.Timezone) - l.SetPersistWriter(opts.PersistWriter) - l.SetWriter(opts.Writer) - - l.handler = slog.NewJSONHandler(opts.PersistWriter, &slog.HandlerOptions{ - AddSource: true, - Level: opts.Level, - ReplaceAttr: l.onAttr, - }) - - return l -} - -func (l *logger) SetFlags(flags int) { - atomic.StoreInt32(&l.flags, int32(flags)) -} - -func (l *logger) Flags() int { - return int(atomic.LoadInt32(&l.flags)) -} - -func (l *logger) SetTimezone(loc *time.Location) { - // FIXME(hupeh): 如何原子化储存结构体实例 - atomic.StorePointer(&l.timezone, unsafe.Pointer(loc)) -} - -func (l *logger) Timezone() *time.Location { - return (*time.Location)(atomic.LoadPointer(&l.timezone)) -} - -func (l *logger) SetLevel(level Level) { - atomic.StoreInt32(&l.level, int32(level)) -} - -func (l *logger) Level() Level { - return Level(int(atomic.LoadInt32(&l.level))) -} - -func (l *logger) SetPersistWriter(w io.Writer) { - l.outMu.Lock() - defer l.outMu.Unlock() - l.persistWriter = w - atomic.StoreInt32(&l.isPersistDiscard, discard(w)) -} - -func (l *logger) SetWriter(w io.Writer) { - l.outMu.Lock() - defer l.outMu.Unlock() - l.writer = w - atomic.StoreInt32(&l.isDiscard, discard(w)) -} - -func discard(w io.Writer) int32 { - if w == io.Discard { - return 1 - } - return 0 -} - -func (l *logger) onAttr(_ []string, a slog.Attr) slog.Attr { - switch a.Key { - case slog.LevelKey: - level := a.Value.Any().(slog.Level) - levelLabel := parseSlogLevel(level).String() - a.Value = slog.StringValue(levelLabel) - case slog.TimeKey: - t := a.Value.Any().(time.Time) - a.Value = slog.TimeValue(t.In(l.Timezone())) - case slog.SourceKey: - s := a.Value.Any().(*slog.Source) - var as []Attr - if s.Function != "" { - as = append(as, String("func", s.Function)) - } - if s.File != "" { - as = append(as, String("file", s.File)) - } - if s.Line != 0 { - as = append(as, Int("line", s.Line)) - } - a.Value = slog.GroupValue(as...) - } - return a -} - -func (l *logger) Handle(ctx context.Context, r slog.Record) error { - if atomic.LoadInt32(&l.isDiscard) == 0 { - child, ok := ctx.Value(childLoggerKey).(*childLogger) - indent, err := l.println(child, r) - if err != nil { - return err - } - if ok && indent > 0 { - atomic.StoreInt32(&child.indent, indent) - } - } - if atomic.LoadInt32(&l.isPersistDiscard) == 0 { - return l.handler.Handle(ctx, r) - } - return nil -} - -func (l *logger) println(child *childLogger, r slog.Record) (int32, error) { - var output string - var sep string - var indent int32 - - write := func(s string) { - if sep == "" { - sep = " " - } else { - output += sep - } - output += s - } - - flags := l.Flags() - colorful := flags&Lcolor != 0 - msg := r.Message - level := parseSlogLevel(r.Level) - levelStr := level.String() - withChild := child != nil - - if withChild { - indent = atomic.LoadInt32(&child.indent) - withChild = indent > 0 - } - - if withChild { - write(strings.Repeat(" ", int(indent))) - indent = 0 - } else { - if flags&(Ldate|Ltime|Lmicroseconds) != 0 { - t := r.Time.In(l.Timezone()) - if flags&Ldate != 0 { - write(t.Format("2006/01/02")) - } - if flags&(Ltime|Lmicroseconds) != 0 { - if flags&Lmicroseconds != 0 { - write(t.Format("15:04:05.000")) - } else { - write(t.Format("15:04:05")) - } - } - } - - // 保存缩进 - indent += int32(len(output) + len(levelStr) + 3) - - if colorful { - switch level { - case LevelDebug: - levelStr = FgCyan.Wrap(levelStr) - msg = FgCyan.Wrap(msg) - case LevelInfo: - levelStr = FgBlue.Wrap(levelStr) + " " - msg = FgBlue.Wrap(msg) - indent += 1 - case LevelWarn: - levelStr = FgYellow.Wrap(levelStr) + " " - msg = FgYellow.Wrap(msg) - indent += 1 - case LevelError: - levelStr = FgRed.Wrap(levelStr) - msg = FgRed.Wrap(msg) - case LevelFatal: - levelStr = FgMagenta.Wrap(levelStr) - msg = FgMagenta.Wrap(msg) - } - levelStr = FgHiBlack.Wrap("[") + levelStr + FgHiBlack.Wrap("]") - } else { - levelStr = "[" + r.Level.String() + "]" - } - - write(levelStr) - } - - write(msg) - - if flags&(Lshortfile|Llongfile) != 0 && r.PC > 0 { - var fileStr string - fs := runtime.CallersFrames([]uintptr{r.PC}) - f, _ := fs.Next() - file := f.File - if flags&Lshortfile != 0 { - i := strings.LastIndexAny(file, "\\/") - if i > -1 { - file = file[i+1:] - } - } - fileStr = fmt.Sprintf("%s:%s:%d", f.Function, file, f.Line) - if colorful { - fileStr = fgGreenItalic.Wrap(fileStr) - } - write(fileStr) - } - - if numAttrs := r.NumAttrs(); flags&Lfields != 0 && numAttrs > 0 { - fields := make(map[string]any, numAttrs) - r.Attrs(func(a Attr) bool { - fields[a.Key] = a.Value.Any() - return true - }) - b, err := json.Marshal(fields) - if err != nil { - return 0, err - } - fieldsStr := string(bytes.TrimSpace(b)) - if fieldsStr != "" { - if colorful { - fieldsStr = FgHiBlack.Wrap(fieldsStr) - } - write(fieldsStr) - } - } - - l.l.Println(output) - - return indent, nil -} - -func (l *logger) clone() *logger { - c := *l - return &c -} - -func (l *logger) With(args ...Attr) Logger { - if len(args) == 0 { - return l - } - c := l.clone() - c.handler = c.handler.WithAttrs(args) - return c -} - -func (l *logger) WithGroup(name string) Logger { - if name == "" { - return l - } - c := l.clone() - c.handler = c.handler.WithGroup(name) - return c -} - -func (l *logger) Enabled(level Level) bool { - return l.handler.Enabled(nil, level.slog().Level()) -} - -// Log logs at level. -func (l *logger) Log(level Level, msg string, args ...any) { - l.log(nil, level, msg, args...) -} - -func (l *logger) ForkLevel(level Level, msg string, args ...any) ChildLogger { - c := &childLogger{ - parent: l, - level: level, - indent: 0, - records: make([]slog.Record, 0), - closed: make(chan struct{}), - } - c.Print(msg, args...) - return c -} - -// Trace logs at LevelTrace. -func (l *logger) Trace(msg string, args ...any) { - l.log(nil, LevelTrace, msg, args...) -} - -func (l *logger) ForkTrace(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelTrace, msg, args...) -} - -// Debug logs at LevelDebug. -func (l *logger) Debug(msg string, args ...any) { - l.log(nil, LevelDebug, msg, args...) -} - -func (l *logger) ForkDebug(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelDebug, msg, args...) -} - -// Info logs at LevelInfo. -func (l *logger) Info(msg string, args ...any) { - l.log(nil, LevelInfo, msg, args...) -} - -func (l *logger) ForkInfo(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelInfo, msg, args...) -} - -// Warn logs at LevelWarn. -func (l *logger) Warn(msg string, args ...any) { - l.log(nil, LevelWarn, msg, args...) -} - -func (l *logger) ForkWarn(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelWarn, msg, args...) -} - -// Error logs at LevelError. -func (l *logger) Error(msg string, args ...any) { - l.log(nil, LevelError, msg, args...) -} - -func (l *logger) ForkError(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelError, msg, args...) -} - -// Fatal logs at LevelFatal. -func (l *logger) Fatal(msg string, args ...any) { - l.log(nil, LevelFatal, msg, args...) -} - -func (l *logger) ForkFatal(msg string, args ...any) ChildLogger { - return l.ForkLevel(LevelFatal, msg, args...) -} - -func (l *logger) log(ctx context.Context, level Level, msg string, args ...any) { - if !l.Enabled(level) { - return - } - if ctx == nil { - ctx = context.Background() - } - _ = l.Handle(ctx, newRecord(level, msg, args)) -} - -func newRecord(level Level, msg string, args []any) slog.Record { - //var pc uintptr - //if !internal.IgnorePC { - // var pcs [1]uintptr - // // skip [runtime.Callers, this function, this function's caller] - // runtime.Callers(3, pcs[:]) - // pc = pcs[0] - //} - //r := slog.NewRecord(time.Now(), level.slog().Level(), msg, pc) - r := slog.NewRecord(time.Now(), level.slog().Level(), msg, 0) - if len(args) > 0 { - var sprintfArgs []any - for _, arg := range args { - switch v := arg.(type) { - case Attr: - r.AddAttrs(v) - default: - sprintfArgs = append(sprintfArgs, arg) - } - } - if len(sprintfArgs) > 0 { - r.Message = fmt.Sprintf(msg, sprintfArgs...) - } - } - return r -} - -type childLogger struct { - parent *logger - level Level - indent int32 - begin slog.Record - finish slog.Record - records []slog.Record - closed chan struct{} -} - -func (c *childLogger) Print(msg string, args ...any) { - select { - case <-c.closed: - default: - c.records = append( - c.records, - newRecord(c.level, msg, args), - ) - } -} - -func (c *childLogger) Finish() { - select { - case <-c.closed: - return - default: - close(c.closed) - } - - ctx := context.Background() - ctx = context.WithValue(ctx, childLoggerKey, c) - for _, record := range c.records { - _ = c.parent.Handle(ctx, record) - } -} diff --git a/pkg/rsp/error.go b/pkg/rsp/error.go index 154ac56..3a8cde9 100644 --- a/pkg/rsp/error.go +++ b/pkg/rsp/error.go @@ -13,7 +13,7 @@ var ( // ErrInternal 客户端请求有效,但服务器处理时发生了意外。 // 对应 HTTP 响应状态码为 500。 - ErrInternal = NewError(-100, "internal error") + ErrInternal = NewError(-100, "系统内部错误") // ErrServiceUnavailable 服务器无法处理请求,一般用于网站维护状态。 // 对应 HTTP 响应状态码为 503。 @@ -55,7 +55,7 @@ var ( // ErrBadParams 客户端提交的参数不符合要求 // 对应 HTTP 状态码为 400。 - ErrBadParams = NewError(-110, "bad parameters") + ErrBadParams = NewError(-110, "参数错误") // ErrRecordNotFound 访问的数据不存在 // 对应 HTTP 状态码为 404。 diff --git a/pkg/rsp/handler.go b/pkg/rsp/handler.go new file mode 100644 index 0000000..76f307e --- /dev/null +++ b/pkg/rsp/handler.go @@ -0,0 +1,83 @@ +package rsp + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// GuardFunc 参数守卫函数前面 +type GuardFunc[T any] func(c echo.Context, req *T) error + +// ServeFunc 参数处理函数签名 +type ServeFunc[T any, R any] func(c echo.Context, req *T) (*R, error) + +// ServeWithDataFunc 参数处理函数签名,支持自定义数据 +type ServeWithDataFunc[T any, R any, O any] func(c echo.Context, req *T, opt O) (*R, error) + +// RespondFunc 数据响应函数前面 +type RespondFunc[R any] func(c echo.Context, res *R) error + +// Handle 通用 CRUD 函数构造器,具体参数与函数 HandleWithData 保持一致 +func Handle[T any, R any](guard GuardFunc[T], serve ServeFunc[T, R], respond ...RespondFunc[R]) echo.HandlerFunc { + return HandleWithData[T, R, any](guard, func(c echo.Context, req *T, opt any) (*R, error) { + return serve(c, req) + }, nil, respond...) +} + +// HandleWithData 通用 CRUD 函数构造器,可以预置数据 +// +// 参数 guard 可以为空值,表示无参数守卫; +// 参数 serve 必传; +// 参数 data 为自定义数据,该值最好不可被修改; +// 参数 respond 为自定义响应函数,若未指定,内部将使用 Ok 或 Created 来响应结果。 +func HandleWithData[T any, R any, D any](guard GuardFunc[T], serve ServeWithDataFunc[T, R, D], data D, respond ...RespondFunc[R]) echo.HandlerFunc { + if serve == nil { + panic("miss ServeFunc") + } + return func(c echo.Context) error { + var req T + if err := c.Bind(&req); err != nil { + return err + } + if err := c.Validate(&req); err != nil { + return err + } + if guard != nil { + err := guard(c, &req) + if err != nil { + return err + } + } + res, err := serve(c, &req, data) + if err != nil { + return err + } + for _, send := range respond { + if send != nil { + return send(c, res) + } + } + // 我们认为凡是 PUT 请求,都是创建数据 + // 所以这里使用 Created 函数来响应数据。 + if c.Request().Method == http.MethodPut { + return Created(c, res) + } + return Ok(c, res) + } +} + +func Bind[T any](c echo.Context, guards ...GuardFunc[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 +} diff --git a/pkg/rsp/rsp.go b/pkg/rsp/respond_utils.go similarity index 95% rename from pkg/rsp/rsp.go rename to pkg/rsp/respond_utils.go index 2356538..9ac2ab4 100644 --- a/pkg/rsp/rsp.go +++ b/pkg/rsp/respond_utils.go @@ -33,6 +33,18 @@ func toText(m map[string]any) (string, error) { } } +type RespondValuer interface { + RespondValue() any +} + +type RespondData[T any] struct { + data T +} + +func (r *RespondData[T]) RespondValue() any { + return r.data +} + type response struct { code int headers map[string]string @@ -109,7 +121,11 @@ func respond(c echo.Context, o *response) error { m["code"] = 0 m["success"] = true if o.data != nil { - m["data"] = o.data + if v, ok := o.data.(RespondValuer); ok { + m["data"] = v.RespondValue() + } else { + m["data"] = o.data + } } } if m["message"] == "" { diff --git a/pkg/rsp/sse.go b/pkg/rsp/server_sent_events.go similarity index 100% rename from pkg/rsp/sse.go rename to pkg/rsp/server_sent_events.go