新增应用管理
This commit is contained in:
parent
c4f066a0d5
commit
330f16a3f6
|
|
@ -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`(
|
||||
|
|
|
|||
1
go.mod
1
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
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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:是)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package application
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
.idea/
|
||||
ttt/
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
# Copier
|
||||
|
||||
I am a copier, I copy everything from one to another
|
||||
|
||||
[](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**
|
||||
|
||||
* <http://github.com/jinzhu>
|
||||
* <wosmvp@gmail.com>
|
||||
* <http://twitter.com/zhangjinzhu>
|
||||
|
||||
## License
|
||||
|
||||
Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License).
|
||||
|
|
@ -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) })
|
||||
}
|
||||
|
|
@ -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")
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue