【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(四)用户管理、部门管理模块

第一篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(一)搭建项目

第二篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。

第三篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(三)日志管理(登录日志、操作日志)、用户登录模块

碎碎念:最近脖子一直好痛啊,已经持续有一段时间了,从早痛到晚,早上起床就痛,到晚上睡觉都还在痛,导致我经常睡不着觉。之前也会脖子痛,但都是痛一两天就好了。真的坐着痛、躺着痛,普通枕头痛、U型枕痛、不枕枕头也痛。。。大家有什么办法可以缓解一下吗?是要去按摩?

接收参数

gofiber中,接收前端传过来的参数,有以下几种传参方式及接收方式:

  • 用问号拼接在地址后面的参数,我们一般用 c.Query("参数名") 获取;
  • 路径参数(直接拼接在地址后面,比如:/sys/user/getById/1,这里的 1 就是路径参数),用 c.Params("参数名")
  • FormData参数,用 c.FormValue("参数名") 接收;
  • FormData传的文件参数,用 c.MultipartForm() 获取;
  • JSON参数,用 c.BodyParser(结构体指针) 接收,就是将json转为具体的某个结构体。

数据库操作

  • 新增:用 Create() ,新增单条数据和批量新增都是用这个方法:db.Create(&user)、db.Create(&list)
  • 修改:
    • Update() 、UpdateColumn() :更新指定的列,每次只更新一个字段。
    • Updates() 、UpdateColumns():更新整个结构体或map,但是注意这两个方法不会更新零值。
    • Save() :插入或更新,传结构体或map,可以插入、更新零值。根据主键进行更新,如果主键不存在则插入。
  • 删除:Delete() 默认根据主键删除或批量删除,也可以在 Delete() 前面使用 Where() ,指定条件删除。
  • 查询:可以使用 Find() 查询单条数据或多条数据。

Update() 、UpdateColumn() 和 Updates() 、UpdateColumns() 其实是有一点区别的,具体我们可以看看源码:

在这里插入图片描述

tx.Statement.SkipHooks = true 这句代码是关于事务(transaction)的设置。当这个设置被设置为 true 时,GORM 将跳过与该事务相关的所有钩子。

然后更新时,可以使用 Select() 指定要更新的字段,更新或新增时,可以使用 Omit() 忽略指定字段。

具体怎么操作数据库,我们直接看下面的代码。

用户管理

首先是用户管理模块,有这几个接口:

在这里插入图片描述

废话不多说,我们直接上代码

controller层:sys_user.go

package sysimport ("fmt""github.com/gofiber/fiber/v2""go-web2/app/common/config""go-web2/app/common/util""go-web2/app/model/sys""path/filepath""strings""time"
)type UserController struct{}// 获取当前登录的用户
func (UserController) GetLoginUser(c *fiber.Ctx) error {// 获取请求中的 Tokentoken := c.Get(config.TokenHeader)user := sys.GetLoginUser(token)result := map[string]any{}result["user"] = userresult["roles"] = user.RoleIdresult["permissions"] = sys.GetPermsMenuByRoleId(user.RoleId)return c.Status(200).JSON(config.Success(result))
}// 用户列表
func (UserController) GetPage(c *fiber.Ctx) error {user := sys.SysUserView{}user.UserName = c.Query("code")user.RealName = c.Query("name")user.AncestorId = c.Query("parentId")user.Token = c.Get(config.TokenHeader)pageSize := c.QueryInt("pageSize", 10)pageNum := c.QueryInt("pageNum", 1)return c.Status(200).JSON(config.Success(user.GetPage(pageSize, pageNum)))
}// 根据id获取用户
func (UserController) GetById(c *fiber.Ctx) error {user := sys.SysUser{}user.Id = c.Params("id")user.Token = c.Get(config.TokenHeader)err := user.GetUser()if err != nil {return c.Status(200).JSON(config.Error("获取用户失败,或没有查看权限"))}return c.Status(200).JSON(config.Success(user))
}// 新增用户
func (UserController) Insert(c *fiber.Ctx) error {var user sys.SysUserif err := c.BodyParser(&user); err != nil {return c.Status(200).JSON(config.Error(err.Error()))}pwd, err := util.GetEncryptedPassword(user.Password)if err != nil {return c.Status(200).JSON(config.Error("密码加密失败"))}user.Password = pwduser.Token = c.Get(config.TokenHeader)err = user.Insert()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 修改用户
func (UserController) Update(c *fiber.Ctx) error {var user sys.SysUserif err := c.BodyParser(&user); err != nil {return c.Status(200).JSON(config.Error(err.Error()))}user.Token = c.Get(config.TokenHeader)err := user.Update()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 删除用户
func (UserController) Delete(c *fiber.Ctx) error {var user sys.SysUserif err := c.BodyParser(&user); err != nil {return c.Status(200).JSON(config.Error(err.Error()))}user.Token = c.Get(config.TokenHeader)ids := strings.Split(user.Id, ",")if err := user.Delete(ids); err != nil {return c.Status(200).JSON(config.Error("删除用户失败"))}return c.Status(200).JSON(config.Success(nil))
}// 修改密码
func (UserController) UpdatePassword(c *fiber.Ctx) error {// 正常是需要加解密的(没有前端就暂时不先加密解密)//newPassword := util.RSADecrypt(c.FormValue("newPassword"))//oldPassword := util.RSADecrypt(c.FormValue("oldPassword"))//id := util.RSADecrypt(c.FormValue("id"))newPassword := c.FormValue("newPassword")oldPassword := c.FormValue("oldPassword")id := c.FormValue("id")var password sys.Passwordpassword.Id = idpassword.OldPassword = oldPasswordpassword.NewPassword = newPasswordpassword.Token = c.Get(config.TokenHeader)err := password.UpdatePassword()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 重置密码
func (UserController) ResetPassword(c *fiber.Ctx) error {var user sys.SysUseruser.Id = c.FormValue("id")if user.Id == "" {return c.Status(200).JSON(config.Error("用户id不可为空"))}user.Token = c.Get(config.TokenHeader)err := user.ResetPassword()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 上传头像
func (UserController) Upload(c *fiber.Ctx) error {form, err := c.MultipartForm()if err != nil {fmt.Println(err)return c.Status(200).JSON(config.Error("头像上传失败"))}// 获取新的文件名name := form.File["file"][0].Filename                           // 获取文件名suffix := name[strings.LastIndex(name, "."):]                   // 文件后缀:.jpgfileName := fmt.Sprintf("%d%s", time.Now().UnixMilli(), suffix) // 新文件名// 相对路径:/upload/20231208relative := filepath.Join(config.FilePath, time.Now().Format("20060102"))// 保存文件err = util.SaveFile(form, relative, fileName)if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}var user sys.SysUseruser.Token = c.Get(config.TokenHeader)relative = filepath.Join(relative, fileName)user.Picture = &relative // 数据库保存相对路径user.Upload()            // 保存头像到数据库return c.Status(200).JSON(config.Success(relative))
}

上面这几个接口,就用到了 c.Query()c.Params()c.FormValue()c.MultipartForm()c.BodyParser() 这几种方式。

model层:sys_user.go

接口有了,那接下来就是获取数据了,数据从数据库来的,所以下面就是来操作数据库了。

package sysimport ("errors""fmt""github.com/google/uuid""go-web2/app/common/config""go-web2/app/common/util""strings""time"
)// 用户信息model,对应数据库的 sys_user 表
type SysUser struct {config.BaseModel // 嵌套公共的model,这样就可以使用 BaseModel 的字段了SysUserViewPassword string `gorm:"password" json:"password" form:"password"` // 加密密码
}// 用户信息model,用于展示给前端
type SysUserView struct {config.BaseModel         // 嵌套公共的model,这样就可以使用 BaseModel 的字段了UserName         string  `json:"userName" form:"userName"`         // 用户名称RealName         string  `json:"realName" form:"realName"`         // 真实姓名DeptId           string  `json:"deptId" form:"deptId"`             // 部门idDeptName         string  `json:"deptName" form:"deptName"`         // 部门名称AncestorId       string  `json:"ancestorId" form:"ancestorId"`     // 祖级idAncestorName     string  `json:"ancestorName" form:"ancestorName"` // 祖级名称ChildId          string  `json:"childId" form:"childId"`           // 部门id及子级idChildName        string  `json:"childName" form:"childName"`       // 部门名称及子级名称RoleId           string  `json:"roleId" form:"roleId"`             // 角色idRoleKey          string  `json:"roleKey" form:"roleKey"`           // 角色代码RoleName         string  `json:"roleName" form:"roleName"`         // 角色名称Phone            *string `json:"phone" form:"phone"`               // 联系电话 这里用指针,是因为可以传空(这个空不是指空字符串,而是null)State            int     `json:"state" form:"state"`               // 状态(1 启用 2 停用)Picture          *string `json:"picture" form:"picture"`           // 头像地址
}// 密码结构体,用于修改密码
type Password struct {config.BaseModel        // 嵌套公共的model,这样就可以使用 BaseModel 的字段了OldPassword      string `json:"oldPassword" form:"oldPassword"` // 旧密码NewPassword      string `json:"newPassword" form:"newPassword"` // 新密码
}// 新增、更新用户信息时,要忽略的字段
var omit = "dept_name,ancestor_id,ancestor_name,child_id,child_name,role_key,role_name"// 获取用户管理的表名
func (SysUserView) TableName() string {return "sys_user"
}// 列表
func (e *SysUserView) GetPage(pageSize int, pageNum int) config.PageInfo {var list []SysUserView // 查询结果var total int64        // 总数query := config.DB.Table(e.TableName())if e.UserName != "" {query.Where("user_name like ?", fmt.Sprintf("%%%s%%", e.UserName))}if e.RealName != "" {query.Where("real_name like ?", fmt.Sprintf("%%%s%%", e.RealName))}if e.AncestorId != "" {ids, _ := GetDeptChild(e.AncestorId) // 获取当前 parentId 的所有子节点包括它本身query.Where("FIND_IN_SET(dept_id,?)", ids)}// 数据过滤scope := AppendQueryDataScope(e.Token, "dept_id", "2", false, true)if scope != "" {query.Where(scope)}// 关联部门和角色表查询offset := (pageNum - 1) * pageSize // 计算跳过的记录数query.Debug().Order("sys_user.create_time desc").Select("sys_user.*, b.name as dept_name,role_key,role_name").Joins("left join sys_dept b on b.id = sys_user.dept_id").Joins("left join sys_role c on c.id = sys_user.role_id").Offset(offset).Limit(pageSize).Find(&list) // 分页查询,根据offset和limit来查询query.Count(&total) // 查询总数用Count,注意查总数不能直接连在Find()后面,需要分开来单独一句才能正确查询到总数。return config.PageInfo{list, total}
}// 详情
func (e *SysUser) GetUser() (err error) {query := config.DB.Table(e.TableName())sql := `SELECT a.*,b.name dept_name,role_key,role_nameFROM sys_user aLEFT JOIN sys_dept b on FIND_IN_SET(b.id,a.dept_id)LEFT JOIN sys_role c on a.role_id=c.id`args := []interface{}{}if e.Id != "" {sql = sql + "WHERE a.id = ?"args = append(args, e.Id)}if e.UserName != "" {sql = sql + "WHERE user_name = ?"args = append(args, e.UserName)}// 数据过滤scope := AppendQueryDataScope(e.Token, "dept_id", "2", false, true)if scope != "" {sql = sql + " AND " + scope}if err = query.Raw(sql, args...).Find(e).Error; err != nil {return}if e.Id == "" || e.UserName == "" {err = errors.New("没有查看权限!")return}return
}// 新增
func (e *SysUser) Insert() (err error) {// 校验新增的用户和当前用户是否是同一部门或子部门if !CheckDataScope(e.Token, e.DeptId, false, true) {err = errors.New("没有操作权限!")return}// 校验用户名和手机号码var count int64db := config.DB.Table(e.TableName())db.Where("user_name = ?", e.UserName).Count(&count)if count > 0 {err = errors.New("用户名称已存在!")return}if e.Phone != nil {db.Where("phone = ?", e.Phone).Count(&count)if count > 0 {err = errors.New("手机号码已存在!")return}}e.Id = strings.ReplaceAll(uuid.NewString(), "-", "")e.CreatorId = GetLoginId(e.Token)e.CreateTime = time.Now()config.DB.Table(e.TableName()).Omit(omit).Create(e)return
}// 修改
func (e *SysUser) Update() (err error) {// 校验修改的用户和当前用户是否是同一部门或子部门if !CheckDataScope(e.Token, e.DeptId, false, true) {err = errors.New("没有操作权限!")return}var byId SysUserconfig.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(&byId)if byId.DeptId != e.DeptId && !CheckDataScope(e.Token, byId.DeptId, false, true) {err = errors.New("没有操作权限!")return}// 校验用户名和手机号码var count int64db := config.DB.Table(e.TableName())db.Where("user_name = ? and id <> ?", e.UserName, e.Id).Count(&count)if count > 0 {err = errors.New("用户名称已存在!")return}if e.Phone != nil {db.Where("phone = ? and id <> ?", e.Phone, e.Id).Count(&count)if count > 0 {err = errors.New("手机号码已存在!")return}}config.DB.Table(e.TableName()).Omit(omit).Model(&SysUser{}).Where("id = ?", e.Id).Updates(e)return
}// 删除
func (e *SysUser) Delete(ids []string) (err error) {// 先查询要删除的用户scope := GetDataScope(e.Token, false, true)if scope != "" {var list []SysUserconfig.DB.Table(e.TableName()).Where("id in (?)", ids).Find(&list)split := strings.Split(scope, ",")for _, user := range list {if !util.IsContain(split, user.DeptId) {err = errors.New("没有操作权限!")return}}}config.DB.Table(e.TableName()).Delete(&SysUser{}, ids)return
}// 修改密码
func (e *Password) UpdatePassword() (err error) {if e.NewPassword == "" || e.OldPassword == "" || e.Id == "" {err = errors.New("数据解密失败")return}var user SysUserconfig.DB.Table(user.TableName()).Where("id = ?", e.Id).Find(&user)if !CheckDataScope(e.Token, user.DeptId, false, true) {err = errors.New("没有操作权限!")return}if e.NewPassword == e.OldPassword {err = errors.New("新密码不可于旧密码相同")return}if e.NewPassword == config.InitPassword {err = errors.New("新密码不可于初始密码相同")return}// 正则表达式我去你大爷!!!不校验了,我校验你大爷!!!从别的项目复制过来的正则,为什么你就死活校验不上!!// 同样的正则,我一模一样复制到校验工具去验证都可以匹配上,就你不行,我去你大爷,不校验了,校验你爹!!!/*reg := "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[_#?!@$%^&*-]).{8,}$"m, _ := regexp.MatchString(reg, e.NewPassword)if !m {err = errors.New("密码长度大于7,且必须由数字、大小写字母、特殊字符组成")return}*/b := util.AuthenticatePassword(e.OldPassword, user.Password)if !b {err = errors.New("旧密码错误")return}newPassword, err := util.GetEncryptedPassword(e.NewPassword)if err = config.DB.Table(user.TableName()).Where("id = ?", e.Id).Update("password", newPassword).Error; err != nil {err = errors.New("密码修改失败")return}return
}// 重置密码为初始密码
func (e *SysUser) ResetPassword() (err error) {var user SysUserconfig.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(&user)if !CheckDataScope(e.Token, user.DeptId, false, true) {err = errors.New("没有操作权限!")return}password, err := util.GetEncryptedPassword(config.InitPassword)if err = config.DB.Table(e.TableName()).Where("id = ?", e.Id).Update("password", password).Error; err != nil {err = errors.New("密码重置失败")return}return
}// 上传头像
func (e *SysUser) Upload() {id := GetLoginId(e.Token)config.DB.Table(e.TableName()).Where("id = ?", id).Update("picture", e.Picture)
}// 根据部门id校验是否存在用户
func CheckDeptExistUser(deptId string) bool {var count int64query := config.DB.Table(SysUserView{}.TableName())query.Where("dept_id = ?", deptId).Count(&count)return count > 0
}// 根据角色id校验是否存在用户
func CheckRoleExistUser(roleId string) bool {var count int64query := config.DB.Table(SysUserView{}.TableName())query.Where("role_id = ?", roleId).Count(&count)return count > 0
}

通过上面controller、model层的方法、函数,不难看出,我很多函数都绑定了结构体,controller层绑定的结构体是 UserController ,model层也绑定了对应的结构体。像这种绑定了具体的数据类型的函数我们称为方法。

在这里插入图片描述

将函数绑定数据类型,这样子就可以让函数只属于这个数据类型了。我们知道,在go中,同一个包下函数名是不可以重复的,但我们给那些重名的函数绑定不同数据类型,就可以了。这样可以省去好多命名的烦恼。

假如某个函数是专门用来处理某个结构体的业务时,那就可以给这个函数绑定对应的结构体(不绑定也可以,就是函数命名的时候需要注意)。

部门管理

接下来是部门管理模块,有以下几个接口:

在这里插入图片描述

controller层:sys_dept.go

package sysimport ("github.com/gofiber/fiber/v2""go-web2/app/common/config""go-web2/app/model/sys"
)type DeptController struct{}// 部门树列表
func (DeptController) GetList(c *fiber.Ctx) error {dept := sys.SysDept{}dept.Token = c.Get(config.TokenHeader)return c.Status(200).JSON(config.Success(dept.GetListTree()))
}// 根据id获取部门
func (DeptController) GetById(c *fiber.Ctx) error {dept := sys.SysDept{}dept.Id = c.Params("id")dept.Token = c.Get(config.TokenHeader)err := dept.GetById()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(dept))
}// 新增部门
func (DeptController) Insert(c *fiber.Ctx) error {var dept sys.SysDeptif err := c.BodyParser(&dept); err != nil {//log.Error(err.Error())return c.Status(200).JSON(config.Error(err.Error()))}dept.Token = c.Get(config.TokenHeader)err := dept.Insert()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 修改部门
func (DeptController) Update(c *fiber.Ctx) error {var dept sys.SysDeptif err := c.BodyParser(&dept); err != nil {//log.Error(err.Error())return c.Status(200).JSON(config.Error(err.Error()))}dept.Token = c.Get(config.TokenHeader)err := dept.Update()if err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 删除部门
func (DeptController) Delete(c *fiber.Ctx) error {var dept sys.SysDeptdept.Id = c.Params("id")dept.Token = c.Get(config.TokenHeader)if err := dept.Delete(); err != nil {return c.Status(200).JSON(config.Error(err.Error()))}return c.Status(200).JSON(config.Success(nil))
}// 部门下拉树列表
func (DeptController) GetSelectList(c *fiber.Ctx) error {dept := sys.SysDept{}dept.Token = c.Get(config.TokenHeader)return c.Status(200).JSON(config.Success(dept.GetListTree()))
}

model层:sys_dept.go

package sysimport ("github.com/google/uuid""github.com/pkg/errors""go-web2/app/common/config""strings""time"
)// 部门管理
type SysDept struct {config.BaseModelName     string    `json:"name" form:"name"`         // 名称ParentId string    `json:"parentId" form:"parentId"` // 上级部门idLevel    int       `json:"level" form:"level"`       // 层级(1 根目录 2 单位 3 部门 4 小组)Sort     int       `json:"sort" form:"sort"`         // 序号Children []SysDept `gorm:"-" json:"children"`        // 子级数据
}// 获取表名
func (SysDept) TableName() string {return "sys_dept"
}// 递归所有子节点
func (e *SysDept) ChildList() []SysDept {// 获取所以子节点包括它本身sql := `WITH RECURSIVE temp_dept AS (SELECT id,name,parent_id FROM sys_dept WHERE id = ?UNION ALLSELECT d.id,d.name,d.parent_id FROM sys_dept dJOIN temp_dept sd ON sd.id = d.parent_id)SELECT * FROM temp_dept`var childList []SysDeptconfig.DB.Raw(sql, e.ParentId).Find(&childList)return childList
}// 根据parentId得到所有子节点的id和name(包括它本身),以逗号分隔
func GetDeptChild(parentId string) (string, string) {dept := SysDept{}dept.ParentId = parentIdlist := dept.ChildList()if len(list) > 0 {idList := []string{}nameList := []string{}for _, t := range list {idList = append(idList, t.Id)nameList = append(nameList, t.Name)}return strings.Join(idList, ","), strings.Join(nameList, ",")}return "", ""
}// 递归所有父节点,获取获取祖级列表(不包括它本身)
func (e *SysDept) GetAncestor() (string, string) {sql := `WITH RECURSIVE temp_dept(id,name,parent_id,level) AS (SELECT id,name,parent_id,levelFROM sys_deptWHERE id = ?UNION ALLSELECT d.id,d.name,d.parent_id,d.levelFROM sys_dept dJOIN temp_dept c ON d.id = c.parent_id)SELECT id,name,parent_id,levelFROM temp_deptWHERE id != ?ORDER BY level,parent_id`var ancestorList []SysDeptconfig.DB.Raw(sql, e.Id, e.Id).Find(&ancestorList)idList := []string{}nameList := []string{}for _, t := range ancestorList {idList = append(idList, t.Id)nameList = append(nameList, t.Name)}return strings.Join(idList, ","), strings.Join(nameList, ",")
}// 树形列表
func (e *SysDept) GetListTree() []SysDept {var list []SysDept // 查询结果sql := ""args := []interface{}{}loginUser := GetLoginUser(e.Token)e.Id = loginUser.DeptIdif e.Id != "" {// 这里用于递归 e.Id 的父级根节点,因为转为树结构时,是从根节点开始递归的(不包括e.Id本身)sql = `WITH RECURSIVE temp_dept(id,parent_id,name,level,sort) AS (SELECT id,parent_id,name,level,sortFROM sys_deptWHERE id = ?UNION ALLSELECT d.id,d.parent_id,d.name,d.level,d.sortFROM sys_dept dJOIN temp_dept c ON d.id = c.parent_id)SELECT id,parent_id,name,level,sortFROM temp_deptWHERE id != ?UNION ALL`args = append(args, e.Id, e.Id)}sql += ` SELECT id,parent_id,name,level,sort FROM sys_dept`// 设置数据范围查询scope := AppendQueryDataScope(e.Token, "id", "2", false, true)if scope != "" {sql = sql + " WHERE " + scope}config.DB.Table(e.TableName()).Debug().Order("`level`,parent_id,sort asc").Raw(sql, args...).Find(&list)return buildDeptTree(list, "ROOT")
}// 获取详情
func (e *SysDept) GetById() (err error) {if !CheckDataScope(e.Token, e.Id, false, true) {err = errors.New("没有操作权限!")return}config.DB.Table(e.TableName()).Where("id = ?", e.Id).Find(e)return
}// 新增
func (e *SysDept) Insert() (err error) {// 新增部门时,只允许新增子部门(也就是只允许给当前用户所在部门新增子部门)if !CheckDataScope(e.Token, e.ParentId, false, true) {err = errors.New("没有操作权限!")return}// 校验用户名和手机号码var count int64query := config.DB.Table(e.TableName())query.Where("name = ? and parent_id = ?", e.Name, e.ParentId).Count(&count)if count > 0 {err = errors.New("名称已存在!")return}err = e.getLevel()if err != nil {return err}e.Id = strings.ReplaceAll(uuid.NewString(), "-", "")e.CreatorId = GetLoginId(e.Token)e.CreateTime = time.Now()config.DB.Table(e.TableName()).Create(e)// 新增成功,更新数据权限缓存exists, _ := config.RedisConn.Exists(config.DATA_SCOPE + e.ParentId).Result()if exists > 0 {childId := config.RedisConn.HGet(config.DATA_SCOPE+e.ParentId, "childId").Val()childName := config.RedisConn.HGet(config.DATA_SCOPE+e.ParentId, "childName").Val()config.RedisConn.HSet(config.DATA_SCOPE+e.ParentId, "childId", childId+","+e.Id)config.RedisConn.HSet(config.DATA_SCOPE+e.ParentId, "childName", childName+","+e.Name)}return
}// 修改
func (e *SysDept) Update() (err error) {// 修改部门时,只允许修改当前部门和子部门数据if !CheckDataScope(e.Token, e.Id, false, true) {err = errors.New("没有操作权限!")return}// 校验用户名和手机号码var count int64query := config.DB.Table(e.TableName())query.Where("name = ? and parent_id = ? and id <> ?", e.Name, e.ParentId, e.Id).Count(&count)if count > 0 {err = errors.New("名称已存在!")return}err = e.getLevel()if err != nil {return err}config.DB.Table(e.TableName()).Model(&SysDept{}).Where("id = ?", e.Id).Updates(e)return
}// 删除
func (e *SysDept) Delete() (err error) {// 修改部门时,只允许修改当前部门和子部门数据if !CheckDataScope(e.Token, e.Id, false, true) {err = errors.New("没有操作权限!")return}// 1、校验是否存在下级var count int64query := config.DB.Table(e.TableName())query.Where("parent_id = ?", e.Id).Count(&count)if count > 0 {err = errors.New("存在下级,不允许删除")return}// 2、校验是否存在用户if CheckDeptExistUser(e.Id) {err = errors.New("该组织存在用户,不允许删除")return}if err = config.DB.Table(e.TableName()).Delete(e).Error; err != nil {return}return
}// 新增或修改部门时,根据父级的level获取当前部门的level
func (e *SysDept) getLevel() (err error) {if e.ParentId == "ROOT" {e.Level = 1} else {var parent SysDeptparent.Id = e.ParentIdparent.GetById()if parent.Name == "" {err = errors.New("上级不存在!")return}e.Level = parent.Level + 1}return
}// 构建树结构
func (e *SysDept) BuildTree(list []SysDept, parentId string) []SysDept {var tree []SysDeptfor _, item := range list {if item.ParentId == parentId {children := e.BuildTree(list, item.Id)if len(children) > 0 {item.Children = children}tree = append(tree, item)}}return tree
}

最后

其实上面的代码都没有什么特别需要注意的地方,都是一些业务代码来的。对新手来说,需要注意的地方,可能就是查询数据的时候,特别是用原生sql拼接查询条件时,要用占位符的形式(也就是用 “?” 表示)拼接,那些查询条件不要用加号或者是fmt.Sprintf() 去拼接,不然容易有sql注入的问题。

还有就是分页查询,需要查询总数时,Count() 方法需要分开单独一句写,不然查的就不是总数,而是每一页的数量。


好啦,以上就是本篇文章的全部内容辣,等我更完这个项目的全部文章,我会放出完整代码的地址,欢迎大家多多点赞支持下,最后可以关注我不迷路~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/230020.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

眼镜店验光配镜处方单打印管理系统软件教程

一、前言 1、眼镜店原始的手写处方单逐步被电脑打印单取代 2、使用电脑开单&#xff0c;记录可以保存可以查询&#xff0c;而且同一个人配镜可以对比之前的信息 软件下载或技术支持可以点击最下方官网卡片 如上图&#xff0c;该软件有顾客信息模块&#xff0c;旧镜检查模块…

Acre1-6000电气火灾监控系统在工矿企业的应用——安科瑞 顾烊宇

摘要&#xff1a;主要介绍了电气火灾的主要原因、几种电气火灾监控系统的构成和设立意义。参照各规范&#xff0c;讨论了宜设立电气火灾监控系统的场所。该系统的设立可大大减少电气火灾事故的发生&#xff0c;对保证人们的生命财产安全具有重要意义。 关键词:电气火灾&#x…

极智开发 | macwindows本地部署安装AIGC绘图工具Stable Diffusion WebUI

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文分享一下 mac&windows本地部署安装AIGC绘图工具Stable Diffusion WebUI。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0ai…

Redis-对象

参考资料 极客时间Redis&#xff08;亚风&#xff09; Redis对象 String • 基本编码⽅式是RAW&#xff0c;基于简单动态字符串&#xff08;SDS&#xff09;实现&#xff0c;存储上限为512mb。 • 如果存储的SDS⻓度⼩于44字节&#xff0c;则会采⽤EMBSTR编码&#xff0c;此…

2023年国家基地“楚慧杯”网络空间安全实践能力竞赛 Wp 一点WP

MISC 参考文章&#xff1a; 天权信安“”2023年国家基地“楚慧杯”网络安全实践能力竞赛初赛WriteUp ez-zip 使用脚本解套娃压缩包 import io import zipfilewith open("4096.zip", "rb") as f:data f.read()info "666"while True:with zi…

AttributeError: module ‘jax‘ has no attribute ‘Array‘解决方案

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

Linux——Ubuntu搭建FTP 时ftp: connect: Connection refused

如何解决ftp: connect: Connection refused&#xff1f; 分析&#xff1a;vsftpd.conf配置文件中默认ipv4:listenNO,ipv6:listen_ipv6YES,默认使用ipv6地址 解决方法&#xff1a;在配置文件中将listenYES开启&#xff0c;并且把listen_ipv6YES注释&#xff0c;重新启动vsftpd…

金蝶云星空和聚水潭单据接口对接

金蝶云星空和聚水潭单据接口对接 接入系统&#xff1a;聚水潭 聚水潭SaaSERP于2014年4月上线&#xff0c;目前累计超过2.5万商家注册使用&#xff0c;成为淘宝应用服务市场ERP类目商家数和商家月订单增速最快的ERP。2014年及2015年“双十一”当天&#xff0c;聚水潭SaaSERP平稳…

初识迭代器(Iterator)——迭代器模式——迭代加深(后续更新...)

学习网页&#xff1a; Welcome to Python.orghttps://www.python.org/ 迭代器&#xff08;Iterator&#xff09; 迭代器是一个非常有用的Python特性&#xff0c;它允许我们遍历一个容器&#xff08;如列表、元组、字典、集合等&#xff09;的元素。迭代器提供了一种方法&…

Vue中的数据变化监控与响应——深入理解Watchers

目录 ​编辑 前言 1. 基本用法&#xff1a; 2. 深度监听&#xff1a; 3. 立即执行&#xff1a; 4. 监听多个数据&#xff1a; 5. 清理监听器&#xff1a; 6. 监听路由变化&#xff1a; 总结&#xff1a; 我的其他博客 前言 在Vue.js中&#xff0c;watch是一种用于监听…

Kafka本地安装⭐️(Windows)并测试生产消息以及消费消息的可用性

2023.12.17 天气晴 温度较低 十点半&#xff0c;不是不想起实在是阳光浴太nice了日常三连&#xff0c;喂&#xff0c;刷&#xff0c;肝刷会儿博客&#xff0c;看会儿设计模式冷冷冷 进被窝 刷视频 睡觉看看kafka的本地部署 》》实践》》成功写会儿博客&#xff0c…

Web前端-HTML(表格与表单)

文章目录 1.表格与表单1.1 概述 2.表格 table2.1 表格概述2.2. 创建表格2.3 表格属性2.4. 表头单元格标签th2.5 表格标题caption&#xff08;了解&#xff09;2.6 合并单元格(难点)2.7 总结表格 3. 表单标签(重点)3.1 概述3.2 form表单3.3 input 控件(重点)type 属性value属性值…

Android 蓝牙BluetoothAdapter 相关(一)

Android 蓝牙相关 本文主要讲述android 蓝牙的简单使用. 1: 是否支持蓝牙 /*** 是否支持蓝牙** return*/ private boolean isSupportBluetooth() {BluetoothAdapter bluetoothAdapter BluetoothAdapter.getDefaultAdapter();return bluetoothAdapter ! null; }2: 开启蓝牙 …

Java之Clonable接口和深浅拷贝

Clonable接口 我们船舰了一个人的对象&#xff0c;想要克隆一个一模一样的对象&#xff0c;可以用到object类里面的克隆方法 object不是所有类的父类吗&#xff1f;那为什么用person1点不出这个方法呢&#xff1f;可以看一下源码 这是Object类里面的clone方法的声明&#xff0…

HackTheBox-Machines--Analytics

文章目录 1 端口扫描2 测试思路3 漏洞探测4 权限获取5 权限提升 Analytics 测试过程 1 端口扫描 nmap -sC -sV 10.129.205.1422 测试思路 目标服务器只开启了80端口&#xff0c;所以出发点从80端口开始。对于web页面&#xff0c;能做的大致思路有&#xff1a; 1.目录扫描   2…

干货下载丨不分业态、不关注核心需求,怎么做得好项目管理?!

项目管理&#xff1a;装备制造业的破局利刃 对于装备制造行业而言&#xff0c;每一笔订单都是非标定制、小批量制造。这种特性决定了其行业企业普遍存在新品开发周期长、生产效率低、质量不稳定、交货期不稳定、成本预算难控制、非标品报价慢等问题。 如何提升企业的管理水平…

国产or进口?台阶仪为何要选择国产

在微观轮廓测量领域&#xff0c;选择一款合适的台阶仪对于获得精准的测量结果至关重要。随着科技的不断发展&#xff0c;台阶仪市场上涌现了许多国产和进口产品&#xff0c;消费者在选择时可能会面临一些疑虑。 什么是台阶仪 台阶仪是一种超精密接触式微观轮廓测量仪&#xf…

桌面概率长按键盘无法连续输入问题

问题描述&#xff1a;概率性长按键盘无法连续输入文本 问题定位&#xff1a; 系统按键流程分析 图一 系统按键流程 按键是由X Server接收的&#xff0c;这一点只要明白了X Window的工作机制就不难理解了。X Server在接收到按键后&#xff0c;会转发到相应程序的窗口中。在窗…

助力智能人群检测计数,基于YOLOv5全系列模型【n/s/m/l/x】开发构建通用场景下人群检测计数识别系统

在一些人流量比较大的场合&#xff0c;或者是一些特殊时刻、时段、节假日等特殊时期下&#xff0c;密切关注当前系统所承载的人流量是十分必要的&#xff0c;对于超出系统负荷容量的情况做到及时预警对于管理团队来说是保障人员安全的重要手段&#xff0c;本文的主要目的是想要…

SpringIOC之作用域Scope

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…