support user role attachment

This commit is contained in:
yanghao05
2023-02-05 13:30:43 +08:00
parent 285e1f1c51
commit ee631b9714
17 changed files with 518 additions and 50 deletions

1
common/structure/sex.go Normal file
View File

@@ -0,0 +1 @@
package structure

View File

@@ -40,10 +40,10 @@ func (m *Migration20230131_165134CreateSysDictionary) table() interface{} {
type SysDictionary struct {
gorm.Model
Name string `gorm:"column:name;comment:字典名(中)"` // 字典名(中)
Type string `gorm:"column:type;comment:字典名(英)"` // 字典名(英)
Status *bool `gorm:"column:status;comment:状态"` // 状态
Desc string `gorm:"column:desc;comment:描述"` // 描述
Name string `gorm:"comment:字典名(中)"` // 字典名(中)
Alias string `gorm:"comment:字典名(英)"` // 字典名(英)
Status bool `gorm:"comment:状态"` // 状态
Description string `gorm:"comment:描述"` // 描述
}
return SysDictionary{}

View File

@@ -40,11 +40,11 @@ func (m *Migration20230131_165218CreateSysDictionaryDetail) table() interface{}
type SysDictionaryDetail struct {
gorm.Model
Label string `gorm:"column:label;comment:展示值"` // 展示值
Value int `gorm:"column:value;comment:字典值"` // 字典值
Status *bool `gorm:"column:status;comment:启用状态"` // 启用状态
Sort int `gorm:"column:sort;comment:排序标记"` // 排序标记
SysDictionaryID int `gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记
SysDictionaryID int `gorm:"comment:关联标记"`
Label string `gorm:"comment:展示值"`
Value string `gorm:"comment:字典值"`
Status bool `gorm:"comment:启用状态"`
Weight int `gorm:"comment:排序权重"`
}
return SysDictionaryDetail{}

View File

@@ -14,14 +14,14 @@ const TableNameSysDictionary = "sys_dictionaries"
// SysDictionary mapped from table <sys_dictionaries>
type SysDictionary struct {
ID uint64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey;autoIncrement:true" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
Name string `gorm:"column:name;type:varchar(191)" json:"name"` // 字典名(中)
Type string `gorm:"column:type;type:varchar(191)" json:"type"` // 字典名(英)
Status bool `gorm:"column:status;type:tinyint(1)" json:"status"` // 状态
Desc string `gorm:"column:desc;type:varchar(191)" json:"desc"` // 描述
ID uint64 `gorm:"column:id;type:bigint(20) unsigned;primaryKey;autoIncrement:true" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
Name string `gorm:"column:name;type:varchar(191)" json:"name"` // 字典名(中)
Alias_ string `gorm:"column:alias;type:varchar(191)" json:"alias"` // 字典名(英)
Status bool `gorm:"column:status;type:tinyint(1)" json:"status"` // 状态
Description string `gorm:"column:description;type:varchar(191)" json:"description"` // 描述
}
// TableName SysDictionary's table name

View File

@@ -18,11 +18,11 @@ type SysDictionaryDetail struct {
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
Label string `gorm:"column:label;type:varchar(191)" json:"label"` // 展示值
Value int64 `gorm:"column:value;type:bigint(20)" json:"value"` // 字典值
Status bool `gorm:"column:status;type:tinyint(1)" json:"status"` // 启用状态
Sort int64 `gorm:"column:sort;type:bigint(20)" json:"sort"` // 排序标记
SysDictionaryID int64 `gorm:"column:sys_dictionary_id;type:bigint(20)" json:"sys_dictionary_id"` // 关联标记
Label string `gorm:"column:label;type:varchar(191)" json:"label"` // 展示值
Value string `gorm:"column:value;type:varchar(191)" json:"value"` // 字典值
Status bool `gorm:"column:status;type:tinyint(1)" json:"status"` // 启用状态
Weight int64 `gorm:"column:weight;type:bigint(20)" json:"weight"` // 排序权重
}
// TableName SysDictionaryDetail's table name

View File

@@ -32,9 +32,9 @@ func newSysDictionary(db *gorm.DB, opts ...gen.DOOption) sysDictionary {
_sysDictionary.UpdatedAt = field.NewTime(tableName, "updated_at")
_sysDictionary.DeletedAt = field.NewField(tableName, "deleted_at")
_sysDictionary.Name = field.NewString(tableName, "name")
_sysDictionary.Type = field.NewString(tableName, "type")
_sysDictionary.Alias_ = field.NewString(tableName, "alias")
_sysDictionary.Status = field.NewBool(tableName, "status")
_sysDictionary.Desc = field.NewString(tableName, "desc")
_sysDictionary.Description = field.NewString(tableName, "description")
_sysDictionary.fillFieldMap()
@@ -44,15 +44,15 @@ func newSysDictionary(db *gorm.DB, opts ...gen.DOOption) sysDictionary {
type sysDictionary struct {
sysDictionaryDo sysDictionaryDo
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Name field.String // 字典名(中)
Type field.String // 字典名(英)
Status field.Bool // 状态
Desc field.String // 描述
ALL field.Asterisk
ID field.Uint64
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Name field.String // 字典名(中)
Alias_ field.String // 字典名(英)
Status field.Bool // 状态
Description field.String // 描述
fieldMap map[string]field.Expr
}
@@ -74,9 +74,9 @@ func (s *sysDictionary) updateTableName(table string) *sysDictionary {
s.UpdatedAt = field.NewTime(table, "updated_at")
s.DeletedAt = field.NewField(table, "deleted_at")
s.Name = field.NewString(table, "name")
s.Type = field.NewString(table, "type")
s.Alias_ = field.NewString(table, "alias")
s.Status = field.NewBool(table, "status")
s.Desc = field.NewString(table, "desc")
s.Description = field.NewString(table, "description")
s.fillFieldMap()
@@ -107,9 +107,9 @@ func (s *sysDictionary) fillFieldMap() {
s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt
s.fieldMap["name"] = s.Name
s.fieldMap["type"] = s.Type
s.fieldMap["alias"] = s.Alias_
s.fieldMap["status"] = s.Status
s.fieldMap["desc"] = s.Desc
s.fieldMap["description"] = s.Description
}
func (s sysDictionary) clone(db *gorm.DB) sysDictionary {

View File

@@ -31,11 +31,11 @@ func newSysDictionaryDetail(db *gorm.DB, opts ...gen.DOOption) sysDictionaryDeta
_sysDictionaryDetail.CreatedAt = field.NewTime(tableName, "created_at")
_sysDictionaryDetail.UpdatedAt = field.NewTime(tableName, "updated_at")
_sysDictionaryDetail.DeletedAt = field.NewField(tableName, "deleted_at")
_sysDictionaryDetail.Label = field.NewString(tableName, "label")
_sysDictionaryDetail.Value = field.NewInt64(tableName, "value")
_sysDictionaryDetail.Status = field.NewBool(tableName, "status")
_sysDictionaryDetail.Sort = field.NewInt64(tableName, "sort")
_sysDictionaryDetail.SysDictionaryID = field.NewInt64(tableName, "sys_dictionary_id")
_sysDictionaryDetail.Label = field.NewString(tableName, "label")
_sysDictionaryDetail.Value = field.NewString(tableName, "value")
_sysDictionaryDetail.Status = field.NewBool(tableName, "status")
_sysDictionaryDetail.Weight = field.NewInt64(tableName, "weight")
_sysDictionaryDetail.fillFieldMap()
@@ -50,11 +50,11 @@ type sysDictionaryDetail struct {
CreatedAt field.Time
UpdatedAt field.Time
DeletedAt field.Field
Label field.String // 展示值
Value field.Int64 // 字典值
Status field.Bool // 启用状态
Sort field.Int64 // 排序标记
SysDictionaryID field.Int64 // 关联标记
Label field.String // 展示值
Value field.String // 字典值
Status field.Bool // 启用状态
Weight field.Int64 // 排序权重
fieldMap map[string]field.Expr
}
@@ -75,11 +75,11 @@ func (s *sysDictionaryDetail) updateTableName(table string) *sysDictionaryDetail
s.CreatedAt = field.NewTime(table, "created_at")
s.UpdatedAt = field.NewTime(table, "updated_at")
s.DeletedAt = field.NewField(table, "deleted_at")
s.Label = field.NewString(table, "label")
s.Value = field.NewInt64(table, "value")
s.Status = field.NewBool(table, "status")
s.Sort = field.NewInt64(table, "sort")
s.SysDictionaryID = field.NewInt64(table, "sys_dictionary_id")
s.Label = field.NewString(table, "label")
s.Value = field.NewString(table, "value")
s.Status = field.NewBool(table, "status")
s.Weight = field.NewInt64(table, "weight")
s.fillFieldMap()
@@ -109,11 +109,11 @@ func (s *sysDictionaryDetail) fillFieldMap() {
s.fieldMap["created_at"] = s.CreatedAt
s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt
s.fieldMap["sys_dictionary_id"] = s.SysDictionaryID
s.fieldMap["label"] = s.Label
s.fieldMap["value"] = s.Value
s.fieldMap["status"] = s.Status
s.fieldMap["sort"] = s.Sort
s.fieldMap["sys_dictionary_id"] = s.SysDictionaryID
s.fieldMap["weight"] = s.Weight
}
func (s sysDictionaryDetail) clone(db *gorm.DB) sysDictionaryDetail {

View File

@@ -18,6 +18,10 @@ func init() {
log.Fatal(err)
}
if err := container.Container.Provide(controller.NewUserController); err != nil {
log.Fatal(err)
}
if err := container.Container.Provide(controller.NewPermissionController); err != nil {
log.Fatal(err)
}
@@ -27,11 +31,24 @@ func init() {
log.Fatal(err)
}
if err := container.Container.Provide(service.NewUserService); err != nil {
log.Fatal(err)
}
// dao
if err := container.Container.Provide(dao.NewRoleDao); err != nil {
log.Fatal(err)
}
if err := container.Container.Provide(dao.NewUserDao); err != nil {
log.Fatal(err)
}
if err := container.Container.Provide(dao.NewUserRoleDao); err != nil {
log.Fatal(err)
}
// routes
if err := container.Container.Provide(routes.NewRoute, dig.Group("route")); err != nil {
log.Fatal(err)
}

23
modules/auth/controller/user.go Executable file
View File

@@ -0,0 +1,23 @@
package controller
import (
"atom/providers/config"
"github.com/gin-gonic/gin"
)
type UserController interface {
GetName(*gin.Context) (string, error)
}
type userControllerImpl struct {
conf *config.Config
}
func NewUserController(conf *config.Config) UserController {
return &userControllerImpl{conf: conf}
}
func (c *userControllerImpl) GetName(ctx *gin.Context) (string, error) {
return "User",nil
}

31
modules/auth/dao/user.go Executable file
View File

@@ -0,0 +1,31 @@
package dao
import (
"atom/database/models"
"atom/database/query"
"context"
)
type UserDao interface {
Create(context.Context, *models.User) (*models.User, error)
}
type userDaoImpl struct {
query *query.Query
}
func NewUserDao(query *query.Query) UserDao {
return &userDaoImpl{query: query}
}
func (dao *userDaoImpl) FindByID(ctx context.Context, id uint64) (*models.User, error) {
user := dao.query.User
return user.WithContext(ctx).Where(user.ID.Eq(id)).First()
}
func (dao *userDaoImpl) Create(ctx context.Context, model *models.User) (*models.User, error) {
user := dao.query.User
if err := user.WithContext(ctx).Create(model); err != nil {
return nil, err
}
return model, nil
}

51
modules/auth/dao/user_role.go Executable file
View File

@@ -0,0 +1,51 @@
package dao
import (
"atom/database/models"
"atom/database/query"
"context"
)
type UserRoleDao interface {
Exists(context.Context, int) bool
Create(context.Context, int, int) error
Update(context.Context, int, int) error
Delete(context.Context, int, int) error
}
type userRoleDaoImpl struct {
query *query.Query
}
func NewUserRoleDao(query *query.Query) UserRoleDao {
return &userRoleDaoImpl{query: query}
}
func (dao *userRoleDaoImpl) Exists(ctx context.Context, userID int) bool {
userRole := dao.query.UserRole
count, _ := userRole.WithContext(ctx).Where(userRole.UserID.Eq(uint64(userID))).Count()
return count > 0
}
func (dao *userRoleDaoImpl) Create(ctx context.Context, userID, roleID int) error {
userRole := dao.query.UserRole
return userRole.WithContext(ctx).Create(&models.UserRole{
UserID: uint64(userID),
RoleID: uint64(roleID),
})
}
func (dao *userRoleDaoImpl) Update(ctx context.Context, userID, roleID int) error {
userRole := dao.query.UserRole
_, err := userRole.WithContext(ctx).Where(userRole.UserID.Eq(uint64(userID))).Update(userRole.RoleID, roleID)
return err
}
func (dao *userRoleDaoImpl) Delete(ctx context.Context, userID, roleID int) error {
userRole := dao.query.UserRole
_, err := userRole.WithContext(ctx).
Where(userRole.UserID.Eq(uint64(userID))).
Where(userRole.RoleID.Eq(uint64(roleID))).
Delete()
return err
}

View File

@@ -0,0 +1,79 @@
package dao
import (
"context"
"log"
"testing"
// 这里的依赖需要被导入,否则会报错
"atom/container"
"atom/database/models"
"atom/database/query"
_ "atom/providers"
"atom/utils"
"github.com/brianvoe/gofakeit/v6"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/dig"
"gorm.io/gorm"
)
type UserRoleInjectParams struct {
dig.In
DB *gorm.DB
Dao UserRoleDao
Query *query.Query
Faker *gofakeit.Faker
}
type UserRoleSuite struct {
suite.Suite
UserRoleInjectParams
}
func init() {
if err := container.Container.Provide(NewUserRoleDao); err != nil {
log.Fatal(err)
}
}
func Test_UserRoleSuite(t *testing.T) {
err := container.Container.Invoke(func(p UserRoleInjectParams) {
s := &UserRoleSuite{}
s.UserRoleInjectParams = p
suite.Run(t, s)
})
assert.NoError(t, err)
}
func (s *UserRoleSuite) BeforeTest(suiteName, testName string) {
utils.TruncateTable(s.DB, s.Query.UserRole.TableName())
}
func (s *UserRoleSuite) Test_GetByUserID() {
Convey("Test_GetByUserID", s.T(), func() {
Reset(func() {
s.BeforeTest("_", "Test_GetByUserID")
})
Convey("not exists", func() {
has := s.Dao.Exists(context.Background(), 1)
So(has, ShouldBeFalse)
})
Convey("exists", func() {
_ = s.Query.UserRole.WithContext(context.Background()).Create(&models.UserRole{
UserID: 1,
RoleID: 1,
})
has := s.Dao.Exists(context.Background(), 1)
So(has, ShouldBeTrue)
})
})
}

93
modules/auth/dao/user_test.go Executable file
View File

@@ -0,0 +1,93 @@
package dao
import (
"context"
"log"
"testing"
// 这里的依赖需要被导入,否则会报错
"atom/container"
"atom/database/models"
"atom/database/query"
_ "atom/providers"
"atom/utils"
"github.com/brianvoe/gofakeit/v6"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/dig"
"gorm.io/gorm"
)
type UserInjectParams struct {
dig.In
DB *gorm.DB
Dao UserDao
Query *query.Query
Faker *gofakeit.Faker
}
type UserSuite struct {
suite.Suite
UserInjectParams
}
func init() {
if err := container.Container.Provide(NewUserDao); err != nil {
log.Fatal(err)
}
}
func Test_UserSuite(t *testing.T) {
err := container.Container.Invoke(func(p UserInjectParams) {
s := &UserSuite{}
s.UserInjectParams = p
suite.Run(t, s)
})
assert.NoError(t, err)
}
func (s *UserSuite) BeforeTest(suiteName, testName string) {
log.Println("BeforeTest: ", testName)
utils.TruncateTable(s.DB, s.Query.User.TableName())
switch testName {
case "":
log.Println("BeforeTest: insert test data")
_, _ = s.Dao.Create(context.Background(), &models.User{
UUID: s.Faker.UUID(),
Username: s.Faker.Username(),
Password: s.Faker.Password(true, true, true, true, false, 16),
Nickname: s.Faker.Name(),
Avatar: s.Faker.ImageURL(100, 100),
RoleID: 0,
Phone: s.Faker.Phone(),
Email: s.Faker.Email(),
Status: s.Faker.RandomString([]string{"enable", "disabled"}),
})
}
}
func (s *UserSuite) Test_Create() {
Convey("Test_Create", s.T(), func() {
Convey("create", func() {
model, err := s.Dao.Create(context.Background(), &models.User{
UUID: s.Faker.UUID(),
Username: s.Faker.Username(),
Password: s.Faker.Password(true, true, true, true, false, 16),
Nickname: s.Faker.Name(),
Avatar: s.Faker.ImageURL(100, 100),
RoleID: 0,
Phone: s.Faker.Phone(),
Email: s.Faker.Email(),
Status: s.Faker.RandomString([]string{"enable", "disabled"}),
})
So(err, ShouldBeNil)
So(model.ID, ShouldEqual, 1)
})
})
}

39
modules/auth/service/user.go Executable file
View File

@@ -0,0 +1,39 @@
package service
import (
"atom/modules/auth/dao"
"context"
)
type UserService interface {
AttachRole(context.Context, int, int) error
}
type userService struct {
userRoleDao dao.UserRoleDao
userDao dao.UserDao
}
func NewUserService(
userRoleDao dao.UserRoleDao,
userDao dao.UserDao,
) UserService {
return &userService{
userRoleDao: userRoleDao,
userDao: userDao,
}
}
func (svc *userService) AttachRole(ctx context.Context, userID, roleID int) error {
if svc.userRoleDao.Exists(ctx, userID) {
return svc.userRoleDao.Update(ctx, userID, roleID)
}
return svc.userRoleDao.Create(ctx, userID, roleID)
}
func (svc *userService) DetachRole(ctx context.Context, userID, roleID int) error {
if !svc.userRoleDao.Exists(ctx, userID) {
return nil
}
return svc.userRoleDao.Delete(ctx, userID, roleID)
}

View File

@@ -0,0 +1,119 @@
package dictionary
import (
"atom/container"
"atom/database/query"
"context"
"errors"
"log"
"time"
)
type Dict struct {
dict map[uint64]*DictInfo
dictItems []*DictInfo
mapAlias map[string]uint64
query *query.Query
}
func init() {
if err := container.Container.Provide(NewDictionary); err != nil {
log.Fatal(err)
}
}
func NewDictionary(query *query.Query) (*Dict, error) {
dict := &Dict{
query: query,
dict: make(map[uint64]*DictInfo),
dictItems: []*DictInfo{},
mapAlias: make(map[string]uint64),
}
if err := dict.Load(); err != nil {
return nil, err
}
return dict, nil
}
func (dict *Dict) Load() error {
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
dictTable := dict.query.SysDictionary
items, err := dictTable.WithContext(ctx).Where(dictTable.Status.Is(true)).Find()
if err != nil {
return err
}
ids := []uint64{}
for _, item := range items {
ids = append(ids, item.ID)
dict.mapAlias[item.Alias_] = item.ID
}
ctx, _ = context.WithTimeout(context.Background(), time.Second*5)
dictDetailTable := dict.query.SysDictionaryDetail
dictItems, err := dictDetailTable.WithContext(ctx).
Where(dictDetailTable.Status.Is(true)).
Where(dictDetailTable.ID.In(ids...)).
Order(dictDetailTable.Weight.Desc()).
Find()
if err != nil {
return err
}
idItems := make(map[uint64][]*DictItem)
for _, dictItem := range dictItems {
id := uint64(dictItem.SysDictionaryID)
idItems[id] = append(idItems[id], &DictItem{
Label: dictItem.Label,
Value: dictItem.Value,
})
}
for _, item := range items {
info := &DictInfo{
ID: item.ID,
Name: item.Name,
Alias: item.Alias_,
Description: item.Description,
Items: idItems[item.ID],
}
dict.dictItems = append(dict.dictItems, info)
dict.dict[item.ID] = info
}
return nil
}
// GetLabelByValue
func (dict *Dict) GetLabelByValue(alias, value string) (string, error) {
items, err := dict.GetItems(alias)
if err != nil {
return "", err
}
for _, item := range items {
if item.Value == value {
return item.Label, nil
}
}
return "", errors.New("dict item not exists")
}
// GetLabelByValue
func (dict *Dict) GetItems(alias string) ([]*DictItem, error) {
dictID, ok := dict.mapAlias[alias]
if !ok {
return nil, errors.New("dict not exists")
}
dictItem, ok := dict.dict[dictID]
if !ok {
return nil, errors.New("dict not exists")
}
return dictItem.Items, nil
}
func (dict *Dict) All(alias string) []*DictInfo {
return dict.dictItems
}

View File

@@ -0,0 +1,14 @@
package dictionary
type DictInfo struct {
ID uint64
Name string
Alias string
Description string
Items []*DictItem
}
type DictItem struct {
Label string
Value string
}

View File

@@ -5,6 +5,7 @@ import (
_ "atom/providers/captcha/driver"
_ "atom/providers/config"
_ "atom/providers/database"
_ "atom/providers/dictionary"
_ "atom/providers/faker"
_ "atom/providers/http"
_ "atom/providers/jwt"