From 647c42a147c882fc6dfbe998dd23e1e351d0e6bb Mon Sep 17 00:00:00 2001 From: Jorbenzhu Date: Tue, 16 Jul 2024 20:34:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=8A=A0=E5=85=A5/?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=94=A8=E6=88=B7=E7=BB=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E7=94=A8=E6=88=B7=E7=BB=84=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/errs/errors.go | 2 + dal/casbindal.go | 10 ++++ router/api/user.go | 38 ++++++++++++++ router/router.go | 2 + service/userservice.go | 22 ++++++++ web/src/pages/admin/user/UserDetail.js | 2 +- web/src/pages/admin/user/UserDetailGroup.js | 56 ++++++++++++++++++--- 7 files changed, 123 insertions(+), 9 deletions(-) diff --git a/common/errs/errors.go b/common/errs/errors.go index dab77bd..2b12614 100644 --- a/common/errs/errors.go +++ b/common/errs/errors.go @@ -11,6 +11,7 @@ const ( ErrAuthNoLogin = -10005 // 未登录 ErrAuthUnauthorized = -10006 // 未授权 ErrAuthUnexpired = -10007 // 未过期 + ErrAuthGroup = -10008 //权限组操作失败 ErrMenu = -10030 // 获取菜单失败 ErrNoRecord = -10900 // 没有匹配到预期的记录,无数据 @@ -31,6 +32,7 @@ var errorMsg = map[int]string{ ErrAuthNoLogin: "未登录或登录态已过期", ErrAuthUnauthorized: "未授权或权限不足", ErrAuthUnexpired: "刷新登录态失败,当前登录态还有足够长的有效期", + ErrAuthGroup: "用户组操作失败", ErrMenu: "获取菜单失败,请稍后重试", ErrNoRecord: "未查询到相关数据", ErrDbSelect: "查询失败,请稍后重试", diff --git a/dal/casbindal.go b/dal/casbindal.go index 247a0d6..8231a2b 100644 --- a/dal/casbindal.go +++ b/dal/casbindal.go @@ -97,3 +97,13 @@ func (c *CasbinDal) GetUserGroup(id []string, ptype string) (map[string][]string } return group, nil } + +// JoinGroups 为用户加入用户组 +func (c *CasbinDal) JoinGroups(id string, groups []string) (bool, error) { + return c.e.AddRolesForUser(id, groups) +} + +// ExitGroup 为用户退出用户组 +func (c *CasbinDal) ExitGroup(id string, groups string) (bool, error) { + return c.e.DeleteRoleForUser(id, groups) +} diff --git a/router/api/user.go b/router/api/user.go index 78aa3f0..c2c7533 100644 --- a/router/api/user.go +++ b/router/api/user.go @@ -112,6 +112,44 @@ func UserDelete(ctx *gin.Context) { c.CJSON(errs.Success) } +// UserJoinGroup 加入用户组 +func UserJoinGroup(ctx *gin.Context) { + c := context.CustomContext{Context: ctx} + param := &struct { + Id uint `json:"id"` + Group string `json:"group"` + }{} + if err := ctx.ShouldBindBodyWithJSON(param); err != nil { + c.CJSON(errs.ErrParam, "用户id或角色值不符合要求") + return + } + userService := service.NewUserService(ctx) + if _, err := userService.JoinGroup(param.Id, param.Group); err != nil { + c.CJSON(errs.ErrAuthGroup, err.Error()) + return + } + c.CJSON(errs.Success) +} + +// UserExitGroup 退出用户组 +func UserExitGroup(ctx *gin.Context) { + c := context.CustomContext{Context: ctx} + param := &struct { + Id uint `json:"id"` + Group string `json:"group"` + }{} + if err := ctx.ShouldBindBodyWithJSON(param); err != nil { + c.CJSON(errs.ErrParam, "用户id或角色值不符合要求") + return + } + userService := service.NewUserService(ctx) + if _, err := userService.ExitGroup(param.Id, param.Group); err != nil { + c.CJSON(errs.ErrAuthGroup, err.Error()) + return + } + c.CJSON(errs.Success) +} + // UserList 获取用户列表 func UserList(ctx *gin.Context) { c := context.CustomContext{Context: ctx} diff --git a/router/router.go b/router/router.go index 901e975..d390e50 100644 --- a/router/router.go +++ b/router/router.go @@ -57,6 +57,8 @@ func SetupRouter(s *gin.Engine, feEmbed embed.FS) { backendAdmin.POST("/user/status", api.UserStatus) backendAdmin.POST("/user/source", api.UserUnbind) backendAdmin.POST("/user/delete", api.UserDelete) + backendAdmin.POST("/user/join_group", api.UserJoinGroup) + backendAdmin.POST("/user/exit_group", api.UserExitGroup) s.Use(gzip.Gzip(gzip.DefaultCompression)).StaticFS("/static", getFileSystem(feEmbed, "web/build/static")) s.NoRoute(func(ctx *gin.Context) { diff --git a/service/userservice.go b/service/userservice.go index bfbe89b..650ee72 100644 --- a/service/userservice.go +++ b/service/userservice.go @@ -25,6 +25,28 @@ func NewUserService(ctx *gin.Context) *UserService { } } +// JoinGroup 加入用户组 +func (u *UserService) JoinGroup(id uint, group string) (bool, error) { + strId := strconv.Itoa(int(id)) + result, err := u.CasbinDal.JoinGroups(strId, []string{group}) + if err != nil { + log.Errorf(u.Ctx, "Casbin join groups failed, id: %d, group: %s, err: %s", id, group, err.Error()) + return false, err + } + return result, nil +} + +// ExitGroup 退出用户组 +func (u *UserService) ExitGroup(id uint, group string) (bool, error) { + strId := strconv.Itoa(int(id)) + result, err := u.CasbinDal.ExitGroup(strId, group) + if err != nil { + log.Errorf(u.Ctx, "Casbin exit group failed, id: %d, group: %s, err: %s", id, group, err.Error()) + return false, err + } + return result, nil +} + func (u *UserService) DeleteUser(id uint) (bool, error) { // 解绑所有该用户的登录渠道 if _, err := u.UnbindUserSource(id, ""); err != nil { diff --git a/web/src/pages/admin/user/UserDetail.js b/web/src/pages/admin/user/UserDetail.js index 795a521..4083fd8 100644 --- a/web/src/pages/admin/user/UserDetail.js +++ b/web/src/pages/admin/user/UserDetail.js @@ -286,7 +286,7 @@ const UserDetail = ({ user, setOpenDrawer, setSearchParam, searchParam }) => { }} /> 关联角色 - + 账户状态 diff --git a/web/src/pages/admin/user/UserDetailGroup.js b/web/src/pages/admin/user/UserDetailGroup.js index 3c63311..b32ca2d 100644 --- a/web/src/pages/admin/user/UserDetailGroup.js +++ b/web/src/pages/admin/user/UserDetailGroup.js @@ -1,22 +1,27 @@ import React, { useEffect, useRef, useState } from "react"; import { PlusOutlined } from "@ant-design/icons"; -import { Input, Tag, theme } from "antd"; +import { Input, Tag, theme, message } from "antd"; +import ApiClient from "../../../services/client"; // import { TweenOneGroup } from "rc-tween-one"; -const UserDetailGroup = () => { +const UserDetailGroup = ({ userId, group }) => { const { token } = theme.useToken(); - const [tags, setTags] = useState(["Tag 1", "Tag 2", "Tag 3"]); + const [tags, setTags] = useState([]); const [inputVisible, setInputVisible] = useState(false); const [inputValue, setInputValue] = useState(""); const inputRef = useRef(null); + const [messageApi, contextHolder] = message.useMessage(); + useEffect(() => { if (inputVisible) { inputRef.current?.focus(); } }, [inputVisible]); + + useEffect(() => { + setTags(group ?? []); + }, [group]); const handleClose = (removedTag) => { - const newTags = tags.filter((tag) => tag !== removedTag); - console.log(newTags); - setTags(newTags); + exitGroup(removedTag); }; const showInput = () => { setInputVisible(true); @@ -26,7 +31,8 @@ const UserDetailGroup = () => { }; const handleInputConfirm = () => { if (inputValue && tags.indexOf(inputValue) === -1) { - setTags([...tags, inputValue]); + // 加入用户组 + joinGroup(inputValue); } setInputVisible(false); setInputValue(""); @@ -50,11 +56,44 @@ const UserDetailGroup = () => { ); - const tagChild = tags.map(forMap); + const tagChild = tags ? tags.map(forMap) : ""; const tagPlusStyle = { background: token.colorBgContainer, borderStyle: "dashed", }; + const joinGroup = async (groupName) => { + const data = { id: userId, group: groupName }; + ApiClient.post("/admin/user/join_group", data) + .then((response) => { + if (response.data?.code === 0) { + setTags([...tags, groupName]); + messageApi.success("已加入角色"); + } else { + messageApi.error(response.data?.message); + } + }) + .catch((error) => { + console.log(error); + messageApi.error("请求失败,请稍后重试!"); + }); + }; + const exitGroup = async (groupName) => { + const data = { id: userId, group: groupName }; + ApiClient.post("/admin/user/exit_group", data) + .then((response) => { + if (response.data?.code === 0) { + const newTags = tags.filter((tag) => tag !== groupName); + setTags(newTags); + messageApi.success("已退出角色"); + } else { + messageApi.error(response.data?.message); + } + }) + .catch((error) => { + console.log(error); + messageApi.error("请求失败,请稍后重试!"); + }); + }; return ( <>
{ 加入角色 )} + {contextHolder} ); };