第一篇:【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 sys
import (
"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 {
// 获取请求中的 Token
token := c.Get(config.TokenHeader)
user := sys.GetLoginUser(token)
result := map[string]any{}
result["user"] = user
result["roles"] = user.RoleId
result["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.SysUser
if 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 = pwd
user.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.SysUser
if 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.SysUser
if 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.Password
password.Id = id
password.OldPassword = oldPassword
password.NewPassword = newPassword
password.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.SysUser
user.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, "."):] // 文件后缀:.jpg
fileName := fmt.Sprintf("%d%s", time.Now().UnixMilli(), suffix) // 新文件名
// 相对路径:/upload/20231208
relative := 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.SysUser
user.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 sys
import (
"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 的字段了
SysUserView
Password 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"` // 部门id
DeptName string `json:"deptName" form:"deptName"` // 部门名称
AncestorId string `json:"ancestorId" form:"ancestorId"` // 祖级id
AncestorName string `json:"ancestorName" form:"ancestorName"` // 祖级名称
ChildId string `json:"childId" form:"childId"` // 部门id及子级id
ChildName string `json:"childName" form:"childName"` // 部门名称及子级名称
RoleId string `json:"roleId" form:"roleId"` // 角色id
RoleKey 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_name
FROM sys_user a
LEFT 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 int64
db := 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 SysUser
config.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 int64
db := 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 []SysUser
config.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 SysUser
config.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 SysUser
config.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 int64
query := config.DB.Table(SysUserView{}.TableName())
query.Where("dept_id = ?", deptId).Count(&count)
return count > 0
}
// 根据角色id校验是否存在用户
func CheckRoleExistUser(roleId string) bool {
var count int64
query := 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 sys
import (
"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.SysDept
if 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.SysDept
if 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.SysDept
dept.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 sys
import (
"github.com/google/uuid"
"github.com/pkg/errors"
"go-web2/app/common/config"
"strings"
"time"
)
// 部门管理
type SysDept struct {
config.BaseModel
Name string `json:"name" form:"name"` // 名称
ParentId string `json:"parentId" form:"parentId"` // 上级部门id
Level 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 ALL
SELECT d.id,d.name,d.parent_id FROM sys_dept d
JOIN temp_dept sd ON sd.id = d.parent_id
)
SELECT * FROM temp_dept
`
var childList []SysDept
config.DB.Raw(sql, e.ParentId).Find(&childList)
return childList
}
// 根据parentId得到所有子节点的id和name(包括它本身),以逗号分隔
func GetDeptChild(parentId string) (string, string) {
dept := SysDept{}
dept.ParentId = parentId
list := 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,level
FROM sys_dept
WHERE id = ?
UNION ALL
SELECT d.id,d.name,d.parent_id,d.level
FROM sys_dept d
JOIN temp_dept c ON d.id = c.parent_id
)
SELECT id,name,parent_id,level
FROM temp_dept
WHERE id != ?
ORDER BY level,parent_id
`
var ancestorList []SysDept
config.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.DeptId
if e.Id != "" {
// 这里用于递归 e.Id 的父级根节点,因为转为树结构时,是从根节点开始递归的(不包括e.Id本身)
sql = `WITH RECURSIVE temp_dept(id,parent_id,name,level,sort) AS (
SELECT id,parent_id,name,level,sort
FROM sys_dept
WHERE id = ?
UNION ALL
SELECT d.id,d.parent_id,d.name,d.level,d.sort
FROM sys_dept d
JOIN temp_dept c ON d.id = c.parent_id
)
SELECT id,parent_id,name,level,sort
FROM temp_dept
WHERE 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 int64
query := 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 int64
query := 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 int64
query := 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 SysDept
parent.Id = e.ParentId
parent.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 []SysDept
for _, 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() 方法需要分开单独一句写,不然查的就不是总数,而是每一页的数量。
好啦,以上就是本篇文章的全部内容辣,等我更完这个项目的全部文章,我会放出完整代码的地址,欢迎大家多多点赞支持下,最后可以关注我不迷路~