From 330f16a3f67e69da87f58b3129faefe67e02b2c4 Mon Sep 17 00:00:00 2001 From: guosl Date: Fri, 5 Jul 2024 18:45:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BA=94=E7=94=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/sql/init/user.sql | 8 +- go.mod | 1 + go.sum | 2 + internal/models/application/application.go | 40 + internal/models/application/request.go | 81 ++ internal/models/{ => user}/user.go | 0 internal/repo/application.go | 96 +++ internal/repo/base.go | 8 + internal/repo/convert.go | 61 ++ internal/repo/menu.go | 91 +++ internal/repo/user.go | 10 +- internal/service/application/application.go | 145 ++++ internal/service/application/interface.go | 15 + internal/service/application/util.go | 1 + internal/service/user/interface.go | 19 +- internal/service/user/user.go | 22 +- internal/service/user/util.go | 12 +- server/application/application.go | 60 ++ server/user/user.go | 18 +- vendor/github.com/jinzhu/copier/.gitignore | 2 + vendor/github.com/jinzhu/copier/License | 20 + vendor/github.com/jinzhu/copier/README.md | 132 ++++ vendor/github.com/jinzhu/copier/copier.go | 828 ++++++++++++++++++++ vendor/github.com/jinzhu/copier/errors.go | 11 + vendor/modules.txt | 3 + 25 files changed, 1643 insertions(+), 43 deletions(-) create mode 100644 internal/models/application/application.go create mode 100644 internal/models/application/request.go rename internal/models/{ => user}/user.go (100%) create mode 100644 internal/repo/application.go create mode 100644 internal/repo/base.go create mode 100644 internal/repo/convert.go create mode 100644 internal/repo/menu.go create mode 100644 internal/service/application/application.go create mode 100644 internal/service/application/interface.go create mode 100644 internal/service/application/util.go create mode 100644 server/application/application.go create mode 100644 vendor/github.com/jinzhu/copier/.gitignore create mode 100644 vendor/github.com/jinzhu/copier/License create mode 100644 vendor/github.com/jinzhu/copier/README.md create mode 100644 vendor/github.com/jinzhu/copier/copier.go create mode 100644 vendor/github.com/jinzhu/copier/errors.go diff --git a/deploy/sql/init/user.sql b/deploy/sql/init/user.sql index 1e42adf..c241fd5 100644 --- a/deploy/sql/init/user.sql +++ b/deploy/sql/init/user.sql @@ -105,7 +105,8 @@ CREATE TABLE IF NOT EXISTS `application`( `created_on` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '记录创建时间', `modified_by` VARCHAR(64) DEFAULT '' COMMENT '修改人', `modified_on` DATETIME DEFAULT CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP COMMENT '记录修改时间', - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code`(`code`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='应用表'; @@ -127,8 +128,9 @@ CREATE TABLE IF NOT EXISTS `menu`( `created_on` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '记录创建时间', `modified_by` VARCHAR(64) DEFAULT '' COMMENT '修改人', `modified_on` DATETIME DEFAULT CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP COMMENT '记录修改时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='用户表'; + PRIMARY KEY (`id`), + UNIQUE KEY `uk_menu_code`(`app_code`,`code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='菜单表'; -- 角色菜单权限表 CREATE TABLE IF NOT EXISTS `role_menu_permission`( diff --git a/go.mod b/go.mod index 2fc5343..3a064e0 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index a8df57b..c511d1c 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= diff --git a/internal/models/application/application.go b/internal/models/application/application.go new file mode 100644 index 0000000..20c5734 --- /dev/null +++ b/internal/models/application/application.go @@ -0,0 +1,40 @@ +package application + +import "time" + +type ApplicationInfo struct { + ID uint `json:"id"` // id + Code string `json:"code"` // code + Secret string `json:"secret"` // secret + Name string `json:"name"` // name + Description string `json:"description"` // 描述 + CreatedBy string `json:"created_by"` // 创建人 + CreatedOn time.Time `json:"created_on"` // 记录创建时间 + ModifiedBy string `json:"modified_by"` // 修改人 + ModifiedOn time.Time `json:"modified_on"` // 记录修改时间 +} + +type Menu struct { + Id uint32 `json:"id"` // id + AppCode string `json:"app_code"` // 应用code + Code string `json:"code"` // 编码 + Name string `json:"name"` // 名称 + ParentCode string `json:"parent_code"` // 父级菜单编码 + Type int32 `json:"type"` // 类型(1:一级导航;2:二级导航;3:三级导航) + Order int32 `json:"order"` // 排序 + Icon string `json:"icon"` // 图标 + Path string `json:"path"` // 访问路径 + ChildPath string `json:"child_path"` // 子级路径(前端用) + GoFirstChild int8 `json:"go_first_child"` // 前端用 + IsShow int32 `json:"is_show"` // 是否显示(0:否;1:是) + Children []Menu `json:"children"` +} + +type MenuTree struct { + Menus []Menu `json:"menus"` +} + +type Application struct { + Info ApplicationInfo `json:"info"` + Menus []Menu `json:"menus"` +} diff --git a/internal/models/application/request.go b/internal/models/application/request.go new file mode 100644 index 0000000..026f664 --- /dev/null +++ b/internal/models/application/request.go @@ -0,0 +1,81 @@ +package application + +type CreateReq struct { + Code string `json:"code" binding:"required"` + Secret string `json:"secret" binding:"required,len=32"` + Name string `json:"Name" binding:"required"` + Description string `json:"description"` +} + +type ResetSecretReq struct { + Id uint `json:"id" binding:"required"` + Secret string `json:"secret" binding:"required,len=32"` +} + +type Query struct { + Page int `form:"page" json:"page" ` + PageSize int `form:"page_size" json:"page_size"` + Keyword string `form:"keyword" Json:"keyword"` + Sort string `form:"sort" json:"sort" binding:"sql_sort"` +} + +func (q *Query) Default() { + if q.Page < 0 { + q.Page = 0 + } + + if q.PageSize <= 0 { + q.PageSize = 20 + } + + if len(q.Sort) == 0 { + q.Sort = "created_on desc" + } +} + +type List struct { + List []ApplicationInfo `json:"list"` +} + +type MenusReq struct { + AppCode string `json:"app_code" binding:"required"` +} + +type DeleteAppReq struct { + Id uint `json:"id" binding:"required"` +} + +type GetAppReq struct { + Id uint `form:"id" json:"id" binding:"required"` +} + +type CreateMenuReq struct { + AppCode string `json:"app_code" binding:"required"` // 应用code + Code string `json:"code" binding:"required"` // 编码 + Name string `json:"name" binding:"required"` // 名称 + ParentCode string `json:"parent_code"` // 父级菜单编码 + Order int32 `json:"order" binding:"required"` // 排序 + Icon string `json:"icon"` // 图标 + Path string `json:"path" binding:"required"` // 访问路径 + ChildPath string `json:"child_path"` // 子级路径(前端用) + GoFirstChild int8 `json:"go_first_child"` // 前端用 + IsShow int32 `json:"is_show" binding:"oneof=0,1"` // 是否显示(0:否;1:是) +} + +type DelMenuReq struct { + Id uint `json:"id" binding:"required"` +} + +type ModifyMenuReq struct { + Id uint `json:"id" binding:"required"` + AppCode string `json:"app_code" binding:"required"` // 应用code + Code string `json:"code" binding:"required"` // 编码 + Name string `json:"name" binding:"required"` // 名称 + ParentCode string `json:"parent_code"` // 父级菜单编码 + Order int `json:"order" binding:"required"` // 排序 + Icon string `json:"icon"` // 图标 + Path string `json:"path" binding:"required"` // 访问路径 + ChildPath string `json:"child_path"` // 子级路径(前端用) + GoFirstChild int `json:"go_first_child"` // 前端用 + IsShow int `json:"is_show" binding:"oneof=0,1"` // 是否显示(0:否;1:是) +} diff --git a/internal/models/user.go b/internal/models/user/user.go similarity index 100% rename from internal/models/user.go rename to internal/models/user/user.go diff --git a/internal/repo/application.go b/internal/repo/application.go new file mode 100644 index 0000000..05f3e21 --- /dev/null +++ b/internal/repo/application.go @@ -0,0 +1,96 @@ +// Code generated by sql2gorm. DO NOT EDIT. +package repo + +import ( + "time" + "context" + "github.com/samber/do" + "gorm.io/gorm" + "fmt" +) + +func init(){ + do.Provide[ApplicationRepo](nil, NewApplicationRepoS) +} + +// 应用表 +type Application struct { + ID uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // id + Code string `gorm:"column:code;NOT NULL"` // code + Secret string `gorm:"column:secret;NOT NULL"` // secret + Name string `gorm:"column:name;NOT NULL"` // name + Description string `gorm:"column:description"` // 描述 + CreatedBy string `gorm:"column:created_by"` // 创建人 + CreatedOn time.Time `gorm:"column:created_on;default:CURRENT_TIMESTAMP;NOT NULL"` // 记录创建时间 + ModifiedBy string `gorm:"column:modified_by"` // 修改人 + ModifiedOn time.Time `gorm:"column:modified_on;default:CURRENT_TIMESTAMP"` // 记录修改时间 +} + +func (m *Application) TableName() string { + return "application" +} + +type ApplicationRepo interface { + GetAppByCode(ctx context.Context, code string) (app Application, err error) + GetAppById(ctx context.Context, id uint) (app Application, err error) + SaveApplication(ctx context.Context, app Application) error + CreateApplication(ctx context.Context, app Application) error + Search(ctx context.Context, query Query) ([]Application, error) + ResetSecret(ctx context.Context, app Application) error + DelApp(ctx context.Context,id uint)error +} + +type applicationRepoS struct { + db *gorm.DB +} + +func NewApplicationRepoS(i *do.Injector) (ApplicationRepo, error) { + return &applicationRepoS{ + db: do.MustInvoke[*gorm.DB](i), + }, nil +} + +func (u *applicationRepoS) GetAppByCode(ctx context.Context, code string) (application Application, err error) { + err = u.db.Where("code = ?", code).Take(&application).Error + return +} + +func (u *applicationRepoS)GetAppById(ctx context.Context, id uint) (app Application, err error) { + err = u.db.Where("id = ?", id).Take(&app).Error + return +} + +func (u *applicationRepoS) SaveApplication(ctx context.Context, application Application) error { + if application.ID <= 0 { + return fmt.Errorf("错误应用id:%d", application.ID) + } + + return u.db.Save(application).Error +} + +func (u *applicationRepoS) CreateApplication(ctx context.Context, application Application) error { + return u.db.Create(application).Error +} + + +func (u *applicationRepoS) Search(ctx context.Context, query Query) ([]Application, error) { + applications := make([]Application, 0) + + keyword := fmt.Sprintf("%%%s%%", query.Keyword) + db := u.db.Model(&Application{}) + if query.Keyword != "" { + db = db.Where("code like ? or name like ?", keyword, keyword) + } + + err := db.Order(query.Sort).Limit(query.PageSize).Offset(query.Page * query.PageSize).Find(&applications).Error + return applications, err +} + +func (u *applicationRepoS) ResetSecret(ctx context.Context, app Application) error { + err := u.db.Model(&Application{}).Where("id = ?", app.ID).Update("secret",app.Secret).Error + return err +} + +func (u *applicationRepoS) DelApp(ctx context.Context,id uint)error{ + return u.db.Where("id = ?",id).Delete(&Application{}).Error +} \ No newline at end of file diff --git a/internal/repo/base.go b/internal/repo/base.go new file mode 100644 index 0000000..c84a878 --- /dev/null +++ b/internal/repo/base.go @@ -0,0 +1,8 @@ +package repo + +type Query struct { + Page int `form:"page" json:"page" ` + PageSize int `form:"page_size" json:"page_size"` + Keyword string `form:"keyword" Json:"keyword"` + Sort string `form:"sort" json:"sort" binding:"sql_sort"` +} diff --git a/internal/repo/convert.go b/internal/repo/convert.go new file mode 100644 index 0000000..8f86a0d --- /dev/null +++ b/internal/repo/convert.go @@ -0,0 +1,61 @@ +package repo + +import ( + appModel "busniess-user-center/internal/models/application" + "sort" + + "github.com/jinzhu/copier" +) + +func convertMenu(menu Menu) appModel.Menu { + aMenu := appModel.Menu{} + copier.Copy(aMenu, menu) + return aMenu +} + +func makeTreeStructure(menuArr []Menu) []appModel.Menu { + // 1、组装树形结构 + // 数组转map + var levelOneMenuArr []appModel.Menu + parentCodeMenuMap := make(map[string][]appModel.Menu) + for _, menu := range menuArr { + // 一级菜单不处理 + if len(menu.ParentCode) == 0 { + levelOneMenuArr = append(levelOneMenuArr, convertMenu(menu)) + continue + } + + // 二级菜单以下按照parentCode分组 + parentCode := menu.ParentCode + if _, ok := parentCodeMenuMap[parentCode]; !ok { + parentCodeMenuMap[parentCode] = []appModel.Menu{convertMenu(menu)} + } else { + parentCodeMenuMap[parentCode] = append(parentCodeMenuMap[parentCode], convertMenu(menu)) + } + + } + + // 2、处理子级并排序 + for _, subMenuArr := range parentCodeMenuMap { + for _, menu := range subMenuArr { + children := parentCodeMenuMap[menu.Code] + sort.Slice(children, func(i, j int) bool { + return children[i].Order < children[j].Order + }) + menu.Children = children + } + } + + for _, menu := range levelOneMenuArr { + children := parentCodeMenuMap[menu.Code] + sort.Slice(children, func(i, j int) bool { + return children[i].Order < children[j].Order + }) + menu.Children = children + } + + sort.Slice(levelOneMenuArr, func(i, j int) bool { + return levelOneMenuArr[i].Order < levelOneMenuArr[j].Order + }) + return levelOneMenuArr +} diff --git a/internal/repo/menu.go b/internal/repo/menu.go new file mode 100644 index 0000000..63b311e --- /dev/null +++ b/internal/repo/menu.go @@ -0,0 +1,91 @@ +// Code generated by sql2gorm. DO NOT EDIT. +package repo + +import ( + "context" + "time" + + "github.com/samber/do" + "gorm.io/gorm" + appModel "busniess-user-center/internal/models/application" +) + +func init(){ + do.Provide[ApplicationRepo](nil, NewApplicationRepoS) +} + +// 菜单 +type Menu struct { + ID uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // id + AppCode string `gorm:"column:app_code;NOT NULL"` // 应用code + Code string `gorm:"column:code;NOT NULL"` // 编码 + Name string `gorm:"column:name;NOT NULL"` // 名称 + ParentCode string `gorm:"column:parent_code;NOT NULL"` // 父级菜单编码 + Order int `gorm:"column:order;default:0;NOT NULL"` // 排序 + Icon string `gorm:"column:icon;NOT NULL"` // 图标 + Path string `gorm:"column:path;NOT NULL"` // 访问路径 + ChildPath string `gorm:"column:child_path;NOT NULL"` // 子级路径(前端用) + GoFirstChild int `gorm:"column:go_first_child;default:0;NOT NULL"` // 前端用 + IsShow int `gorm:"column:is_show;default:0;NOT NULL"` // 是否显示(0:否;1:是) + CreatedBy string `gorm:"column:created_by"` // 创建人 + CreatedOn time.Time `gorm:"column:created_on;default:CURRENT_TIMESTAMP;NOT NULL"` // 记录创建时间 + ModifiedBy string `gorm:"column:modified_by"` // 修改人 + ModifiedOn time.Time `gorm:"column:modified_on;default:CURRENT_TIMESTAMP"` // 记录修改时间 +} + +func (m *Menu) TableName() string { + return "menu" +} + +type MenuRepo interface{ + GetAppMenus(ctx context.Context,appCode string)([]appModel.Menu,error) + Create(ctx context.Context,menu *Menu)(id uint,err error) + DelMenuById(ctx context.Context,id uint)error + SaveMenu(ctx context.Context,menu *Menu)error + GetMenuById(ctx context.Context,id uint)(menu Menu,err error) +} + +type menuRepo struct{ + db *gorm.DB +} + +func NewMenuRepo(i *do.Injector)(MenuRepo,error){ + return &menuRepo{ + db: do.MustInvoke[*gorm.DB](i), + }, nil +} + +func (m *menuRepo)GetAppMenus(ctx context.Context,appCode string)(menus []appModel.Menu,err error){ + dbMenus,err := m.getAppMenu(appCode) + if err != nil{ + return + } + + menus = makeTreeStructure(dbMenus) + return +} + +func (m *menuRepo)getAppMenu(appCode string)(menus []Menu,err error){ + err = m.db.Where("app_code = ?",appCode).Find(&menus).Error + return +} + +func (m *menuRepo)Create(ctx context.Context,menu *Menu)(id uint,err error){ + menu.ID = 0 + err = m.db.Create(menu).Error + id = menu.ID + return +} + +func (m *menuRepo)DelMenuById(ctx context.Context,id uint)error{ + return m.db.Where("id = ?",id).Delete(&Menu{}).Error +} + +func (m *menuRepo)SaveMenu(ctx context.Context,menu *Menu)error{ + return m.db.Save(&menu).Error +} + +func (m *menuRepo)GetMenuById(ctx context.Context,id uint)(menu Menu,err error){ + err = m.db.Where("id = ?",id).Take(&menu).Error + return +} \ No newline at end of file diff --git a/internal/repo/user.go b/internal/repo/user.go index b113453..fad976a 100644 --- a/internal/repo/user.go +++ b/internal/repo/user.go @@ -1,7 +1,7 @@ package repo import ( - "busniess-user-center/internal/models" + userModel "busniess-user-center/internal/models/user" "context" "fmt" "time" @@ -40,8 +40,8 @@ type UserRepo interface { GetUserByEmail(ctx context.Context, mobile string) (user User, err error) SaveUser(ctx context.Context, user User) error CreateUser(ctx context.Context, user *User) error - SetUserStatus(ctx context.Context, id uint, status models.UserStatus) error - Search(ctx context.Context, query *models.Query) ([]User, error) + SetUserStatus(ctx context.Context, id uint, status userModel.UserStatus) error + Search(ctx context.Context, query *userModel.Query) ([]User, error) ResetPwd(ctx context.Context, user User) error } @@ -82,12 +82,12 @@ func (u *userRepoS) GetUserByEmail(ctx context.Context, email string) (user User return } -func (u *userRepoS) SetUserStatus(ctx context.Context, id uint, status models.UserStatus) error { +func (u *userRepoS) SetUserStatus(ctx context.Context, id uint, status userModel.UserStatus) error { err := u.db.Model(&User{}).Where("id = ?", id).Update("status", status).Error return err } -func (u *userRepoS) Search(ctx context.Context, query *models.Query) ([]User, error) { +func (u *userRepoS) Search(ctx context.Context, query *userModel.Query) ([]User, error) { users := make([]User, 0) keyword := fmt.Sprintf("%%%s%%", query.Keyword) diff --git a/internal/service/application/application.go b/internal/service/application/application.go new file mode 100644 index 0000000..c8005fb --- /dev/null +++ b/internal/service/application/application.go @@ -0,0 +1,145 @@ +package application + +import ( + "busniess-user-center/config" + appModel "busniess-user-center/internal/models/application" + "busniess-user-center/internal/repo" + "busniess-user-center/pkg/redis" + "context" + "fmt" + + "github.com/jinzhu/copier" + "github.com/samber/do" + "go.uber.org/zap" + "gorm.io/gorm" +) + +func init() { + do.Provide(nil, NewApplicationService) +} + +type applicationService struct { + logger *zap.SugaredLogger + appRepo repo.ApplicationRepo + menuRepo repo.MenuRepo + redis *redis.Redis + conf *config.AppConfig +} + +func NewApplicationService(i *do.Injector) (ApplicationService, error) { + return &applicationService{ + logger: do.MustInvoke[*zap.SugaredLogger](i), + appRepo: do.MustInvoke[repo.ApplicationRepo](i), + menuRepo: do.MustInvoke[repo.MenuRepo](i), + redis: do.MustInvoke[*redis.Redis](i), + conf: do.MustInvoke[*config.AppConfig](i), + }, nil +} + +func (a *applicationService) Create(ctx context.Context, info *appModel.CreateReq) error { + dbApp := repo.Application{} + err := copier.Copy(dbApp, info) + if err != nil { + return err + } + + return a.appRepo.CreateApplication(ctx, dbApp) +} + +func (a *applicationService) ResetSecret(ctx context.Context, info *appModel.ResetSecretReq) error { + dbApp := repo.Application{} + err := copier.Copy(dbApp, info) + if err != nil { + return err + } + + // todo 校验权限 + return a.appRepo.ResetSecret(ctx, dbApp) +} + +func (a *applicationService) Delete(ctx context.Context, req *appModel.DeleteAppReq) error { + // todo 校验权限 + return a.appRepo.DelApp(ctx, req.Id) +} + +func (a *applicationService) GetApp(ctx context.Context, req *appModel.GetAppReq) (app appModel.ApplicationInfo, err error) { + dbApp, err := a.appRepo.GetAppById(ctx, req.Id) + if err != nil && err != gorm.ErrRecordNotFound { + return + } + + if err == gorm.ErrRecordNotFound { + return + } + + err = copier.Copy(app, dbApp) + return +} + +func (a *applicationService) Search(ctx context.Context, query *appModel.Query) (rsp appModel.List, err error) { + query.Default() + dbQuery := repo.Query{} + err = copier.Copy(dbQuery, query) + if err != nil { + return + } + + dbList, err := a.appRepo.Search(ctx, dbQuery) + if err != nil { + return + } + + for _, item := range dbList { + info := appModel.ApplicationInfo{} + copier.Copy(info, item) + rsp.List = append(rsp.List, info) + } + + return +} + +func (a *applicationService) Menus(ctx context.Context, info *appModel.MenusReq) (rsp []appModel.Menu, err error) { + return a.menuRepo.GetAppMenus(ctx, info.AppCode) +} + +func (a *applicationService) CreateMenu(ctx context.Context, info *appModel.CreateMenuReq) error { + // todo 校验权限 + menu := repo.Menu{} + err := copier.Copy(menu, info) + if err != nil { + return err + } + + _, err = a.menuRepo.Create(ctx, &menu) + return err +} + +func (a *applicationService) ModifyMenu(ctx context.Context, info *appModel.ModifyMenuReq) error { + // todo 校验权限 + dMenu, err := a.menuRepo.GetMenuById(ctx, info.Id) + if err != nil && err != gorm.ErrRecordNotFound { + return err + } + + if err == gorm.ErrRecordNotFound { + return fmt.Errorf("菜单不存在") + } + + dMenu.Name = info.Name + dMenu.ParentCode = info.ParentCode + dMenu.Order = info.Order + dMenu.Icon = info.Icon + dMenu.Path = info.Path + dMenu.ChildPath = info.ChildPath + dMenu.GoFirstChild = info.GoFirstChild + dMenu.IsShow = info.IsShow + + err = a.menuRepo.SaveMenu(ctx, &dMenu) + return err +} + +func (a *applicationService) DeleteMenu(ctx context.Context, info *appModel.DelMenuReq) error { + // todo 校验权限 + err := a.menuRepo.DelMenuById(ctx, info.Id) + return err +} diff --git a/internal/service/application/interface.go b/internal/service/application/interface.go new file mode 100644 index 0000000..57feccc --- /dev/null +++ b/internal/service/application/interface.go @@ -0,0 +1,15 @@ +package application + +import ( + appModel "busniess-user-center/internal/models/application" + "context" +) + +type ApplicationService interface { + Create(ctx context.Context, info *appModel.CreateReq) error + ResetSecret(ctx context.Context, info *appModel.ResetSecretReq) error + Delete(ctx context.Context, req *appModel.DeleteAppReq) error + GetApp(ctx context.Context, req *appModel.GetAppReq) (app appModel.ApplicationInfo, err error) + Search(ctx context.Context, query *appModel.Query) (rsp appModel.List, err error) + Menus(ctx context.Context, info *appModel.MenusReq) (rsp []appModel.Menu, err error) +} diff --git a/internal/service/application/util.go b/internal/service/application/util.go new file mode 100644 index 0000000..b584a8a --- /dev/null +++ b/internal/service/application/util.go @@ -0,0 +1 @@ +package application diff --git a/internal/service/user/interface.go b/internal/service/user/interface.go index 27aa694..30f2c23 100644 --- a/internal/service/user/interface.go +++ b/internal/service/user/interface.go @@ -1,18 +1,19 @@ package user import ( - "busniess-user-center/internal/models" + userModel "busniess-user-center/internal/models/user" + "context" ) type UserService interface { - Add(ctx context.Context, info *models.AddInfo) (id uint, err error) - Login(ctx context.Context, lInfo models.LoginInfo) error + Add(ctx context.Context, info *userModel.AddInfo) (id uint, err error) + Login(ctx context.Context, lInfo userModel.LoginInfo) error Logout(ctx context.Context) error - Modify(ctx context.Context, mInfo *models.ModifyInfo) error - Disable(ctx context.Context, req *models.Enable) error - Enable(ctx context.Context, req *models.Enable) error - Search(ctx context.Context, query *models.Query) ([]models.User, error) - ResetPwd(ctx context.Context, req *models.ResetPwdReq) error - GetUser(ctx context.Context, req *models.GetUserReq) (user models.User, err error) + Modify(ctx context.Context, mInfo *userModel.ModifyInfo) error + Disable(ctx context.Context, req *userModel.Enable) error + Enable(ctx context.Context, req *userModel.Enable) error + Search(ctx context.Context, query *userModel.Query) ([]userModel.User, error) + ResetPwd(ctx context.Context, req *userModel.ResetPwdReq) error + GetUser(ctx context.Context, req *userModel.GetUserReq) (user userModel.User, err error) } diff --git a/internal/service/user/user.go b/internal/service/user/user.go index 256b6c6..56db663 100644 --- a/internal/service/user/user.go +++ b/internal/service/user/user.go @@ -2,7 +2,7 @@ package user import ( "busniess-user-center/config" - "busniess-user-center/internal/models" + userModel "busniess-user-center/internal/models/user" "busniess-user-center/internal/repo" "busniess-user-center/pkg/redis" contextUtil "busniess-user-center/pkg/utils/context" @@ -45,7 +45,7 @@ func NewUserService(i *do.Injector) (UserService, error) { }, nil } -func (u *userService) Add(ctx context.Context, info *models.AddInfo) (id uint, err error) { +func (u *userService) Add(ctx context.Context, info *userModel.AddInfo) (id uint, err error) { session, err := contextUtil.GetSession(ctx) if err != nil { return @@ -101,7 +101,7 @@ func (u *userService) Add(ctx context.Context, info *models.AddInfo) (id uint, e return } -func (u *userService) Login(ctx context.Context, info models.LoginInfo) error { +func (u *userService) Login(ctx context.Context, info userModel.LoginInfo) error { // 通过account获取用户信息 user, err := u.repo.GetUserByAccount(ctx, info.Account) if err == gorm.ErrRecordNotFound { @@ -140,7 +140,7 @@ func (u *userService) Logout(ctx context.Context) error { return nil } -func (u *userService) Modify(ctx context.Context, mInfo *models.ModifyInfo) error { +func (u *userService) Modify(ctx context.Context, mInfo *userModel.ModifyInfo) error { // 获取当前操作用户 session, err := contextUtil.GetSession(ctx) if err != nil { @@ -158,7 +158,7 @@ func (u *userService) Modify(ctx context.Context, mInfo *models.ModifyInfo) erro return u.repo.SaveUser(ctx, user) } -func (u *userService) Disable(ctx context.Context, req *models.Enable) error { +func (u *userService) Disable(ctx context.Context, req *userModel.Enable) error { // 获取操作用户 _, err := contextUtil.GetSession(ctx) if err != nil { @@ -168,10 +168,10 @@ func (u *userService) Disable(ctx context.Context, req *models.Enable) error { // todo 判断是否有权限 // 修改对应用户状态 - return u.repo.SetUserStatus(ctx, req.Id, models.DisableUserStatus) + return u.repo.SetUserStatus(ctx, req.Id, userModel.DisableUserStatus) } -func (u *userService) Enable(ctx context.Context, req *models.Enable) error { +func (u *userService) Enable(ctx context.Context, req *userModel.Enable) error { _, err := contextUtil.GetSession(ctx) if err != nil { return err @@ -180,10 +180,10 @@ func (u *userService) Enable(ctx context.Context, req *models.Enable) error { // todo 判断是否有权限 // 修改对应用户状态 - return u.repo.SetUserStatus(ctx, req.Id, models.EnableUserStatus) + return u.repo.SetUserStatus(ctx, req.Id, userModel.EnableUserStatus) } -func (u *userService) Search(ctx context.Context, query *models.Query) ([]models.User, error) { +func (u *userService) Search(ctx context.Context, query *userModel.Query) ([]userModel.User, error) { // 获取操作用户 _, err := contextUtil.GetSession(ctx) if err != nil { @@ -200,7 +200,7 @@ func (u *userService) Search(ctx context.Context, query *models.Query) ([]models return list, err } -func (u *userService) ResetPwd(ctx context.Context, req *models.ResetPwdReq) error { +func (u *userService) ResetPwd(ctx context.Context, req *userModel.ResetPwdReq) error { // 获取操作用户 session, err := contextUtil.GetSession(ctx) if err != nil { @@ -243,7 +243,7 @@ func (u *userService) ResetPwd(ctx context.Context, req *models.ResetPwdReq) err return nil } -func (u *userService) GetUser(ctx context.Context, req *models.GetUserReq) (user models.User, err error) { +func (u *userService) GetUser(ctx context.Context, req *userModel.GetUserReq) (user userModel.User, err error) { rUser, err := u.repo.GetUserByAccount(ctx, req.Account) if err != nil { return diff --git a/internal/service/user/util.go b/internal/service/user/util.go index e1ece2a..45346ff 100644 --- a/internal/service/user/util.go +++ b/internal/service/user/util.go @@ -1,7 +1,7 @@ package user import ( - "busniess-user-center/internal/models" + userModel "busniess-user-center/internal/models/user" "busniess-user-center/internal/repo" "context" "fmt" @@ -68,8 +68,8 @@ func (u *userService) removeCookie(ctx context.Context) { } } -func convertUserList(users []repo.User) []models.User { - list := make([]models.User, len(users)) +func convertUserList(users []repo.User) []userModel.User { + list := make([]userModel.User, len(users)) for _, item := range users { list = append(list, convertUser(item)) } @@ -77,10 +77,10 @@ func convertUserList(users []repo.User) []models.User { return list } -func convertUser(user repo.User) models.User { - return models.User{ +func convertUser(user repo.User) userModel.User { + return userModel.User{ Id: user.ID, - UserInfo: models.UserInfo{ + UserInfo: userModel.UserInfo{ Name: user.Name, Account: user.Account, Mobile: user.Mobile, diff --git a/server/application/application.go b/server/application/application.go new file mode 100644 index 0000000..d137ef5 --- /dev/null +++ b/server/application/application.go @@ -0,0 +1,60 @@ +package application + +import ( + "context" + + appModel "busniess-user-center/internal/models/application" + applicationService "busniess-user-center/internal/service/application" + ginUtil "busniess-user-center/pkg/utils/gin" + + "github.com/gin-gonic/gin" + "github.com/samber/do" + "go.uber.org/zap" +) + +func init() { + do.Provide(nil, NewApplicationServer) +} + +type ApplicationServer struct { + applicationService applicationService.ApplicationService + logger *zap.SugaredLogger +} + +func NewApplicationServer(i *do.Injector) (*ApplicationServer, error) { + return &ApplicationServer{ + applicationService: do.MustInvoke[applicationService.ApplicationService](i), + logger: do.MustInvoke[*zap.SugaredLogger](i), + }, nil +} + +func RegisterRoute(api *gin.RouterGroup) { + server := do.MustInvoke[*ApplicationServer](nil) + api.POST("/create", ginUtil.WrapNoRsp(server.Create)) + api.POST("/reset", ginUtil.WrapNoRsp(server.ResetSecret)) + api.POST("/delete", ginUtil.WrapNoRsp(server.DelApp)) + api.GET("/search", ginUtil.Wrap(server.Search)) + api.GET("/menus", ginUtil.WrapNoRsp(server.ResetSecret)) +} + +func (u *ApplicationServer) Create(ctx context.Context, req *appModel.CreateReq) (err error) { + // 转换dto + return u.applicationService.Create(ctx, req) +} + +func (u *ApplicationServer) Get(ctx context.Context, req *appModel.GetAppReq) (app appModel.ApplicationInfo, err error) { + return u.applicationService.GetApp(ctx, req) +} + +func (u *ApplicationServer) Search(ctx context.Context, query *appModel.Query) (appModel.List, error) { + query.Default() + return u.applicationService.Search(ctx, query) +} + +func (u *ApplicationServer) ResetSecret(ctx context.Context, req *appModel.ResetSecretReq) error { + return u.applicationService.ResetSecret(ctx, req) +} + +func (u *ApplicationServer) DelApp(ctx context.Context, req *appModel.DeleteAppReq) error { + return u.applicationService.Delete(ctx, req) +} diff --git a/server/user/user.go b/server/user/user.go index 5248b26..7a26b9a 100644 --- a/server/user/user.go +++ b/server/user/user.go @@ -1,7 +1,7 @@ package user import ( - "busniess-user-center/internal/models" + userModel "busniess-user-center/internal/models/user" "busniess-user-center/internal/service/user" ginUtil "busniess-user-center/pkg/utils/gin" @@ -42,7 +42,7 @@ func RegisterRoute(api *gin.RouterGroup) { api.POST("/disable", ginUtil.WrapNoRsp(server.Disable)) } -func (u *UserServer) Add(ctx context.Context, req *models.AddInfo) (rsp proto.AddResponse, err error) { +func (u *UserServer) Add(ctx context.Context, req *userModel.AddInfo) (rsp proto.AddResponse, err error) { // 转换dto id, err := u.userService.Add(ctx, req) if err != nil { @@ -59,7 +59,7 @@ func (u *UserServer) Add(ctx context.Context, req *models.AddInfo) (rsp proto.Ad func (u *UserServer) Login(ctx context.Context, req *proto.LoginRequest) (err error) { // 转换dto - info := models.LoginInfo{ + info := userModel.LoginInfo{ Account: req.Account, Pwd: req.Pwd, } @@ -71,27 +71,27 @@ func (u *UserServer) Logout(ctx context.Context) error { return u.userService.Logout(ctx) } -func (u *UserServer) Modify(ctx context.Context, req *models.ModifyInfo) error { +func (u *UserServer) Modify(ctx context.Context, req *userModel.ModifyInfo) error { return u.userService.Modify(ctx, req) } -func (u *UserServer) Disable(ctx context.Context, req *models.Enable) error { +func (u *UserServer) Disable(ctx context.Context, req *userModel.Enable) error { return u.userService.Disable(ctx, req) } -func (u *UserServer) Enable(ctx context.Context, req *models.Enable) error { +func (u *UserServer) Enable(ctx context.Context, req *userModel.Enable) error { return u.userService.Enable(ctx, req) } -func (u *UserServer) Search(ctx context.Context, query *models.Query) ([]models.User, error) { +func (u *UserServer) Search(ctx context.Context, query *userModel.Query) ([]userModel.User, error) { query.Default() return u.userService.Search(ctx, query) } -func (u *UserServer) GetUser(ctx context.Context, req *models.GetUserReq) (models.User, error) { +func (u *UserServer) GetUser(ctx context.Context, req *userModel.GetUserReq) (userModel.User, error) { return u.userService.GetUser(ctx, req) } -func (u *UserServer) ResetPwd(ctx context.Context, req *models.ResetPwdReq) error { +func (u *UserServer) ResetPwd(ctx context.Context, req *userModel.ResetPwdReq) error { return u.userService.ResetPwd(ctx, req) } diff --git a/vendor/github.com/jinzhu/copier/.gitignore b/vendor/github.com/jinzhu/copier/.gitignore new file mode 100644 index 0000000..6d742b3 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/.gitignore @@ -0,0 +1,2 @@ +.idea/ +ttt/ diff --git a/vendor/github.com/jinzhu/copier/License b/vendor/github.com/jinzhu/copier/License new file mode 100644 index 0000000..e2dc538 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/License @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/jinzhu/copier/README.md b/vendor/github.com/jinzhu/copier/README.md new file mode 100644 index 0000000..079dc57 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/README.md @@ -0,0 +1,132 @@ +# Copier + + I am a copier, I copy everything from one to another + +[![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions) + +## Features + +* Copy from field to field with same name +* Copy from method to field with same name +* Copy from field to method with same name +* Copy from slice to slice +* Copy from struct to slice +* Copy from map to map +* Enforce copying a field with a tag +* Ignore a field with a tag +* Deep Copy + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/jinzhu/copier" +) + +type User struct { + Name string + Role string + Age int32 + EmployeeCode int64 `copier:"EmployeeNum"` // specify field name + + // Explicitly ignored in the destination struct. + Salary int +} + +func (user *User) DoubleAge() int32 { + return 2 * user.Age +} + +// Tags in the destination Struct provide instructions to copier.Copy to ignore +// or enforce copying and to panic or return an error if a field was not copied. +type Employee struct { + // Tell copier.Copy to panic if this field is not copied. + Name string `copier:"must"` + + // Tell copier.Copy to return an error if this field is not copied. + Age int32 `copier:"must,nopanic"` + + // Tell copier.Copy to explicitly ignore copying this field. + Salary int `copier:"-"` + + DoubleAge int32 + EmployeeId int64 `copier:"EmployeeNum"` // specify field name + SuperRole string +} + +func (employee *Employee) Role(role string) { + employee.SuperRole = "Super " + role +} + +func main() { + var ( + user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000} + users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}} + employee = Employee{Salary: 150000} + employees = []Employee{} + ) + + copier.Copy(&employee, &user) + + fmt.Printf("%#v \n", employee) + // Employee{ + // Name: "Jinzhu", // Copy from field + // Age: 18, // Copy from field + // Salary:150000, // Copying explicitly ignored + // DoubleAge: 36, // Copy from method + // EmployeeId: 0, // Ignored + // SuperRole: "Super Admin", // Copy to method + // } + + // Copy struct to slice + copier.Copy(&employees, &user) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"} + // } + + // Copy slice to slice + employees = []Employee{} + copier.Copy(&employees, &users) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"}, + // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeeId: 0, SuperRole: "Super Dev"}, + // } + + // Copy map to map + map1 := map[int]int{3: 6, 4: 8} + map2 := map[int32]int8{} + copier.Copy(&map2, map1) + + fmt.Printf("%#v \n", map2) + // map[int32]int8{3:6, 4:8} +} +``` + +### Copy with Option + +```go +copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) +``` + +## Contributing + +You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. + +# Author + +**jinzhu** + +* +* +* + +## License + +Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License). diff --git a/vendor/github.com/jinzhu/copier/copier.go b/vendor/github.com/jinzhu/copier/copier.go new file mode 100644 index 0000000..43a14f1 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier.go @@ -0,0 +1,828 @@ +package copier + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "strings" + "sync" + "unicode" +) + +// These flags define options for tag handling +const ( + // Denotes that a destination field must be copied to. If copying fails then a panic will ensue. + tagMust uint8 = 1 << iota + + // Denotes that the program should not panic when the must flag is on and + // value is not copied. The program will return an error instead. + tagNoPanic + + // Ignore a destination field from being copied to. + tagIgnore + + // Denotes that the value as been copied + hasCopied + + // Some default converter types for a nicer syntax + String string = "" + Bool bool = false + Int int = 0 + Float32 float32 = 0 + Float64 float64 = 0 +) + +// Option sets copy options +type Option struct { + // setting this value to true will ignore copying zero values of all the fields, including bools, as well as a + // struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go) + IgnoreEmpty bool + CaseSensitive bool + DeepCopy bool + Converters []TypeConverter + // Custom field name mappings to copy values with different names in `fromValue` and `toValue` types. + // Examples can be found in `copier_field_name_mapping_test.go`. + FieldNameMapping []FieldNameMapping +} + +func (opt Option) converters() map[converterPair]TypeConverter { + var converters = map[converterPair]TypeConverter{} + + // save converters into map for faster lookup + for i := range opt.Converters { + pair := converterPair{ + SrcType: reflect.TypeOf(opt.Converters[i].SrcType), + DstType: reflect.TypeOf(opt.Converters[i].DstType), + } + + converters[pair] = opt.Converters[i] + } + + return converters +} + +type TypeConverter struct { + SrcType interface{} + DstType interface{} + Fn func(src interface{}) (dst interface{}, err error) +} + +type converterPair struct { + SrcType reflect.Type + DstType reflect.Type +} + +func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping { + var mapping = map[converterPair]FieldNameMapping{} + + for i := range opt.FieldNameMapping { + pair := converterPair{ + SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType), + DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType), + } + + mapping[pair] = opt.FieldNameMapping[i] + } + + return mapping +} + +type FieldNameMapping struct { + SrcType interface{} + DstType interface{} + Mapping map[string]string +} + +// Tag Flags +type flags struct { + BitFlags map[string]uint8 + SrcNames tagNameMapping + DestNames tagNameMapping +} + +// Field Tag name mapping +type tagNameMapping struct { + FieldNameToTag map[string]string + TagToFieldName map[string]string +} + +// Copy copy things +func Copy(toValue interface{}, fromValue interface{}) (err error) { + return copier(toValue, fromValue, Option{}) +} + +// CopyWithOption copy with option +func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { + return copier(toValue, fromValue, opt) +} + +func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { + var ( + isSlice bool + amount = 1 + from = indirect(reflect.ValueOf(fromValue)) + to = indirect(reflect.ValueOf(toValue)) + converters = opt.converters() + mappings = opt.fieldNameMapping() + ) + + if !to.CanAddr() { + return ErrInvalidCopyDestination + } + + // Return is from value is invalid + if !from.IsValid() { + return ErrInvalidCopyFrom + } + + fromType, isPtrFrom := indirectType(from.Type()) + toType, _ := indirectType(to.Type()) + + if fromType.Kind() == reflect.Interface { + fromType = reflect.TypeOf(from.Interface()) + } + + if toType.Kind() == reflect.Interface { + toType, _ = indirectType(reflect.TypeOf(to.Interface())) + oldTo := to + to = reflect.New(reflect.TypeOf(to.Interface())).Elem() + defer func() { + oldTo.Set(to) + }() + } + + // Just set it if possible to assign for normal types + if from.Kind() != reflect.Slice && from.Kind() != reflect.Struct && from.Kind() != reflect.Map && (from.Type().AssignableTo(to.Type()) || from.Type().ConvertibleTo(to.Type())) { + if !isPtrFrom || !opt.DeepCopy { + to.Set(from.Convert(to.Type())) + } else { + fromCopy := reflect.New(from.Type()) + fromCopy.Set(from.Elem()) + to.Set(fromCopy.Convert(to.Type())) + } + return + } + + if from.Kind() != reflect.Slice && fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map { + if !fromType.Key().ConvertibleTo(toType.Key()) { + return ErrMapKeyNotMatch + } + + if to.IsNil() { + to.Set(reflect.MakeMapWithSize(toType, from.Len())) + } + + for _, k := range from.MapKeys() { + toKey := indirect(reflect.New(toType.Key())) + isSet, err := set(toKey, k, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key()) + } + + elemType := toType.Elem() + if elemType.Kind() != reflect.Slice { + elemType, _ = indirectType(elemType) + } + toValue := indirect(reflect.New(elemType)) + isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { + return err + } + } + + for { + if elemType == toType.Elem() { + to.SetMapIndex(toKey, toValue) + break + } + elemType = reflect.PtrTo(elemType) + toValue = toValue.Addr() + } + } + return + } + + if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice { + if to.IsNil() { + slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap()) + to.Set(slice) + } + if fromType.ConvertibleTo(toType) { + for i := 0; i < from.Len(); i++ { + if to.Len() < i+1 { + to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem())) + } + isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) + if err != nil { + continue + } + } + } + return + } + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + // skip not supported type + return + } + + if len(converters) > 0 { + if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok { + // converter supported + return + } + } + + if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = indirect(from.Index(i)) + } else { + source = indirect(from) + } + // dest + dest = indirect(reflect.New(toType).Elem()) + } else { + source = indirect(from) + dest = indirect(to) + } + + if len(converters) > 0 { + if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok { + if isSlice { + // FIXME: maybe should check the other types? + if to.Type().Elem().Kind() == reflect.Ptr { + to.Index(i).Set(dest.Addr()) + } else { + if to.Len() < i+1 { + reflect.Append(to, dest) + } else { + to.Index(i).Set(dest) + } + } + } else { + to.Set(dest) + } + + continue + } + } + + destKind := dest.Kind() + initDest := false + if destKind == reflect.Interface { + initDest = true + dest = indirect(reflect.New(toType)) + } + + // Get tag options + flgs, err := getFlags(dest, source, toType, fromType) + if err != nil { + return err + } + + // check source + if source.IsValid() { + copyUnexportedStructFields(dest, source) + + // Copy from source field to dest field or method + fromTypeFields := deepFields(fromType) + for _, field := range fromTypeFields { + name := field.Name + + // Get bit flags for field + fieldFlags := flgs.BitFlags[name] + + // Check if we should ignore copying + if (fieldFlags & tagIgnore) != 0 { + continue + } + + fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType) + + srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) + if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) { + // process for nested anonymous field + destFieldNotSet := false + if f, ok := dest.Type().FieldByName(destFieldName); ok { + // only initialize parent embedded struct pointer in the path + for idx := range f.Index[:len(f.Index)-1] { + destField := dest.FieldByIndex(f.Index[:idx+1]) + + if destField.Kind() != reflect.Ptr { + continue + } + + if !destField.IsNil() { + continue + } + if !destField.CanSet() { + destFieldNotSet = true + break + } + + // destField is a nil pointer that can be set + newValue := reflect.New(destField.Type().Elem()) + destField.Set(newValue) + } + } + + if destFieldNotSet { + break + } + + toField := fieldByName(dest, destFieldName, opt.CaseSensitive) + if toField.IsValid() { + if toField.CanSet() { + isSet, err := set(toField, fromField, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { + return err + } + } + if fieldFlags != 0 { + // Note that a copy was made + flgs.BitFlags[name] = fieldFlags | hasCopied + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(destFieldName) + } else { + toMethod = dest.MethodByName(destFieldName) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from from method to dest field + for _, field := range deepFields(toType) { + name := field.Name + srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType)) + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(srcFieldName) + } else { + fromMethod = source.MethodByName(srcFieldName) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) { + if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0], opt.DeepCopy, converters) + } + } + } + } + } + + if isSlice && to.Kind() == reflect.Slice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest.Addr())) + } else { + isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt) + if err != nil { + continue + } + } + } + } else if dest.Type().AssignableTo(to.Type().Elem()) { + if to.Len() < i+1 { + to.Set(reflect.Append(to, dest)) + } else { + isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters) + if err != nil { + return err + } + if !isSet { + // ignore error while copy slice element + err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt) + if err != nil { + continue + } + } + } + } + } else if initDest { + to.Set(dest) + } + + err = checkBitFlags(flgs.BitFlags) + } + + return +} + +func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string { + var fieldNamesMapping map[string]string + + if len(mappings) > 0 { + pair := converterPair{ + SrcType: fromType, + DstType: toType, + } + if v, ok := mappings[pair]; ok { + fieldNamesMapping = v.Mapping + } + } + return fieldNamesMapping +} + +func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) { + defer func() { + if err := recover(); err != nil { + value = reflect.Value{} + } + }() + + return source.FieldByName(fieldName) +} + +func copyUnexportedStructFields(to, from reflect.Value) { + if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) { + return + } + + // create a shallow copy of 'to' to get all fields + tmp := indirect(reflect.New(to.Type())) + tmp.Set(from) + + // revert exported fields + for i := 0; i < to.NumField(); i++ { + if tmp.Field(i).CanSet() { + tmp.Field(i).Set(to.Field(i)) + } + } + to.Set(tmp) +} + +func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { + return ignoreEmpty && v.IsZero() +} + +var deepFieldsLock sync.RWMutex +var deepFieldsMap = make(map[reflect.Type][]reflect.StructField) + +func deepFields(reflectType reflect.Type) []reflect.StructField { + deepFieldsLock.RLock() + cache, ok := deepFieldsMap[reflectType] + deepFieldsLock.RUnlock() + if ok { + return cache + } + var res []reflect.StructField + if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct { + fields := make([]reflect.StructField, 0, reflectType.NumField()) + + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + // PkgPath is the package path that qualifies a lower case (unexported) + // field name. It is empty for upper case (exported) field names. + // See https://golang.org/ref/spec#Uniqueness_of_identifiers + if v.PkgPath == "" { + fields = append(fields, v) + if v.Anonymous { + // also consider fields of anonymous fields as fields of the root + fields = append(fields, deepFields(v.Type)...) + } + } + } + res = fields + } + + deepFieldsLock.Lock() + deepFieldsMap[reflectType] = res + deepFieldsLock.Unlock() + return res +} + +func indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + isPtr = true + } + return reflectType, isPtr +} + +func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) { + if !from.IsValid() { + return true, nil + } + if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil { + return false, err + } else if ok { + return true, nil + } + + if to.Kind() == reflect.Ptr { + // set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true, nil + } else if to.IsNil() { + // `from` -> `to` + // sql.NullString -> *string + if fromValuer, ok := driverValuer(from); ok { + v, err := fromValuer.Value() + if err != nil { + return true, nil + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true, nil + } + } + // allocate new `to` variable with default value (eg. *string -> new(string)) + to.Set(reflect.New(to.Type().Elem())) + } + // depointer `to` + to = to.Elem() + } + + if deepCopy { + toKind := to.Kind() + if toKind == reflect.Interface && to.IsNil() { + if reflect.TypeOf(from.Interface()) != nil { + to.Set(reflect.New(reflect.TypeOf(from.Interface())).Elem()) + toKind = reflect.TypeOf(to.Interface()).Kind() + } + } + if from.Kind() == reflect.Ptr && from.IsNil() { + return true, nil + } + if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) { + return false, nil + } + } + + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { + // `from` -> `to` + // *string -> sql.NullString + if from.Kind() == reflect.Ptr { + // if `from` is nil do nothing with `to` + if from.IsNil() { + return true, nil + } + // depointer `from` + from = indirect(from) + } + // `from` -> `to` + // string -> sql.NullString + // set `to` by invoking method Scan(`from`) + err := toScanner.Scan(from.Interface()) + if err != nil { + return false, nil + } + } else if fromValuer, ok := driverValuer(from); ok { + // `from` -> `to` + // sql.NullString -> string + v, err := fromValuer.Value() + if err != nil { + return false, nil + } + // if `from` is not valid do nothing with `to` + if v == nil { + return true, nil + } + rv := reflect.ValueOf(v) + if rv.Type().AssignableTo(to.Type()) { + to.Set(rv) + } else if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) { + to.Set(rv.Convert(to.Type())) + } + } else if from.Kind() == reflect.Ptr { + return set(to, from.Elem(), deepCopy, converters) + } else { + return false, nil + } + + return true, nil +} + +// lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field. +func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converterPair]TypeConverter) (copied bool, err error) { + pair := converterPair{ + SrcType: from.Type(), + DstType: to.Type(), + } + + if cnv, ok := converters[pair]; ok { + result, err := cnv.Fn(from.Interface()) + if err != nil { + return false, err + } + + if result != nil { + to.Set(reflect.ValueOf(result)) + } else { + // in case we've got a nil value to copy + to.Set(reflect.Zero(to.Type())) + } + + return true, nil + } + + return false, nil +} + +// parseTags Parses struct tags and returns uint8 bit flags. +func parseTags(tag string) (flg uint8, name string, err error) { + for _, t := range strings.Split(tag, ",") { + switch t { + case "-": + flg = tagIgnore + return + case "must": + flg = flg | tagMust + case "nopanic": + flg = flg | tagNoPanic + default: + if unicode.IsUpper([]rune(t)[0]) { + name = strings.TrimSpace(t) + } else { + err = ErrFieldNameTagStartNotUpperCase + } + } + } + return +} + +// getTagFlags Parses struct tags for bit flags, field name. +func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, error) { + flgs := flags{ + BitFlags: map[string]uint8{}, + SrcNames: tagNameMapping{ + FieldNameToTag: map[string]string{}, + TagToFieldName: map[string]string{}, + }, + DestNames: tagNameMapping{ + FieldNameToTag: map[string]string{}, + TagToFieldName: map[string]string{}, + }, + } + var toTypeFields, fromTypeFields []reflect.StructField + if dest.IsValid() { + toTypeFields = deepFields(toType) + } + if src.IsValid() { + fromTypeFields = deepFields(fromType) + } + + // Get a list dest of tags + for _, field := range toTypeFields { + tags := field.Tag.Get("copier") + if tags != "" { + var name string + var err error + if flgs.BitFlags[field.Name], name, err = parseTags(tags); err != nil { + return flags{}, err + } else if name != "" { + flgs.DestNames.FieldNameToTag[field.Name] = name + flgs.DestNames.TagToFieldName[name] = field.Name + } + } + } + + // Get a list source of tags + for _, field := range fromTypeFields { + tags := field.Tag.Get("copier") + if tags != "" { + var name string + var err error + if _, name, err = parseTags(tags); err != nil { + return flags{}, err + } else if name != "" { + flgs.SrcNames.FieldNameToTag[field.Name] = name + flgs.SrcNames.TagToFieldName[name] = field.Name + } + } + } + return flgs, nil +} + +// checkBitFlags Checks flags for error or panic conditions. +func checkBitFlags(flagsList map[string]uint8) (err error) { + // Check flag conditions were met + for name, flgs := range flagsList { + if flgs&hasCopied == 0 { + switch { + case flgs&tagMust != 0 && flgs&tagNoPanic != 0: + err = fmt.Errorf("field %s has must tag but was not copied", name) + return + case flgs&(tagMust) != 0: + panic(fmt.Sprintf("Field %s has must tag but was not copied", name)) + } + } + } + return +} + +func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) { + // get dest field name + if name, ok := fieldNameMapping[fieldName]; ok { + srcFieldName = fieldName + destFieldName = name + return + } + + if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok { + destFieldName = srcTagName + if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok { + destFieldName = destTagName + } + } else { + if destTagName, ok := flgs.DestNames.TagToFieldName[fieldName]; ok { + destFieldName = destTagName + } + } + if destFieldName == "" { + destFieldName = fieldName + } + + // get source field name + if destTagName, ok := flgs.DestNames.FieldNameToTag[fieldName]; ok { + srcFieldName = destTagName + if srcField, ok := flgs.SrcNames.TagToFieldName[destTagName]; ok { + srcFieldName = srcField + } + } else { + if srcField, ok := flgs.SrcNames.TagToFieldName[fieldName]; ok { + srcFieldName = srcField + } + } + + if srcFieldName == "" { + srcFieldName = fieldName + } + return +} + +func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { + if !v.CanAddr() { + i, ok = v.Interface().(driver.Valuer) + return + } + + i, ok = v.Addr().Interface().(driver.Valuer) + return +} + +func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value { + if caseSensitive { + return v.FieldByName(name) + } + + return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) }) +} diff --git a/vendor/github.com/jinzhu/copier/errors.go b/vendor/github.com/jinzhu/copier/errors.go new file mode 100644 index 0000000..f50ea32 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/errors.go @@ -0,0 +1,11 @@ +package copier + +import "errors" + +var ( + ErrInvalidCopyDestination = errors.New("copy destination must be non-nil and addressable") + ErrInvalidCopyFrom = errors.New("copy from must be non-nil and addressable") + ErrMapKeyNotMatch = errors.New("map's key type doesn't match") + ErrNotSupported = errors.New("not supported") + ErrFieldNameTagStartNotUpperCase = errors.New("copier field name tag must be start upper case") +) diff --git a/vendor/modules.txt b/vendor/modules.txt index d05339b..45c5233 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -132,6 +132,9 @@ github.com/hashicorp/hcl/hcl/token github.com/hashicorp/hcl/json/parser github.com/hashicorp/hcl/json/scanner github.com/hashicorp/hcl/json/token +# github.com/jinzhu/copier v0.4.0 +## explicit; go 1.13 +github.com/jinzhu/copier # github.com/jinzhu/inflection v1.0.0 ## explicit github.com/jinzhu/inflection