From 7132aab796540f45ed511717b274f6c585d0a968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Tue, 21 Mar 2023 13:56:45 +0800 Subject: [PATCH] add /api/setRole /api/disableUser --- backend/api.go | 68 +++++++++++++++++++ backend/global/user.go | 41 ++++++++--- backend/login.go | 4 ++ backend/user.go | 17 ++++- frontend/vben/src/api/sys/user.ts | 8 +++ .../views/dashboard/account/AccountModal.vue | 3 + .../views/dashboard/account/account.data.ts | 44 +++++++++++- .../src/views/dashboard/account/index.vue | 3 +- 8 files changed, 175 insertions(+), 13 deletions(-) diff --git a/backend/api.go b/backend/api.go index 683c6e5..c7dc55e 100644 --- a/backend/api.go +++ b/backend/api.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/fumiama/paper-manager/backend/global" "github.com/fumiama/paper-manager/backend/utils" ) @@ -238,6 +239,73 @@ func init() { writeresult(w, codeSuccess, &message{M: "成功"}, messageOk, typeSuccess) }} + apimap["/api/setRole"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) { + type setrolebody struct { + ID int `json:"id"` + Role global.UserRole `json:"role"` + } + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + if !user.IsSuper() { + writeresult(w, codeError, nil, errNoSetRolePermission.Error(), typeError) + return + } + var body setrolebody + defer r.Body.Close() + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + if body.ID == *user.ID { + writeresult(w, codeError, nil, "cannot set self", typeError) + return + } + err = setUserRole(body.ID, body.Role, user.Name) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, nil, messageOk, typeSuccess) + }} + + apimap["/api/disableUser"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) { + type disableuserbody struct { + ID int `json:"id"` + } + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + if !user.IsSuper() { + writeresult(w, codeError, nil, errNoSetRolePermission.Error(), typeError) + return + } + var body disableuserbody + defer r.Body.Close() + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + if body.ID == *user.ID { + writeresult(w, codeError, nil, "cannot disbale self", typeError) + return + } + err = global.UserDB.DisableUser(body.ID, user.Name) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, nil, messageOk, typeSuccess) + }} + apimap["/api/resetPassword"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) { type resetpwdbody struct { Username string `json:"username"` diff --git a/backend/global/user.go b/backend/global/user.go index 9ffe1ec..86e0486 100644 --- a/backend/global/user.go +++ b/backend/global/user.go @@ -14,10 +14,15 @@ const ( RoleSuper RoleFileManager RoleUser + RoleTop ) type UserRole uint8 +func (r UserRole) IsVaild() bool { + return r > RoleNil && r < RoleTop +} + func (r UserRole) String() string { switch r { case RoleSuper: @@ -27,7 +32,7 @@ func (r UserRole) String() string { case RoleUser: return "user" } - return "nil" + return "invalid" } func (r UserRole) Nick() string { @@ -39,7 +44,7 @@ func (r UserRole) Nick() string { case RoleUser: return "课程组员" } - return "nil" + return "非法角色" } const ( @@ -156,7 +161,7 @@ func (u *UserDatabase) AddUser(user *User, opname string) error { if err != nil { return err } - return u.SendMessage("创建了账号", opname, *nu.ID) + return u.SendMessage(opname+"创建了账号", opname, *nu.ID) } // UpdateUserInfo ... @@ -180,7 +185,7 @@ func (u *UserDatabase) UpdateUserInfo(id int, opname, nick, avtr, desc string) e if err != nil { return err } - return u.SendMessage("更新了个人信息", opname, *user.ID) + return u.SendMessage(opname+"更新了个人信息", opname, *user.ID) } // UpdateUserRole ... @@ -199,7 +204,25 @@ func (u *UserDatabase) UpdateUserRole(id int, nr UserRole, opname string) error if err != nil { return err } - return u.SendMessage("您的权限被变更为"+user.Role.Nick(), opname, *user.ID) + return u.SendMessage("您的权限被"+opname+"变更为"+user.Role.Nick(), opname, *user.ID) +} + +// DisableUser ... +func (u *UserDatabase) DisableUser(id int, opname string) error { + user, err := u.GetUserByID(id) + if err != nil { + return err + } + user.Last = time.Now().Unix() + user.Pswd = "" + _ = u.SendMessage("账户被"+opname+"禁用", opname, *user.ID) + u.mu.Lock() + err = u.db.Insert(UserTableUser, &user) + u.mu.Unlock() + if err != nil { + return err + } + return u.SendMessage(user.Name+"的账户被"+opname+"禁用", opname, *user.ID) } // UpdateUserPassword ... @@ -220,7 +243,7 @@ func (u *UserDatabase) UpdateUserPassword(id int, opname, npwd string) error { if err != nil { return err } - return u.SendMessage("更新了密码", opname, *user.ID) + return u.SendMessage(opname+"更新了密码", opname, *user.ID) } // UpdateUserContact ... @@ -240,7 +263,7 @@ func (u *UserDatabase) UpdateUserContact(id int, opname, ncont string) error { if err != nil { return err } - return u.SendMessage("更新了联系方式", opname, *user.ID) + return u.SendMessage(opname+"更新了联系方式", opname, *user.ID) } // GetUserByName avoids sql injection by limiting username to 0-9A-Za-z @@ -304,7 +327,9 @@ func (u *UserDatabase) GetUsers() (users []User, err error) { users = make([]User, n) i := 0 err = u.db.FindFor(UserTableUser, &user, "", func() error { - user.Pswd = "" + if user.Pswd != "" { + user.Pswd = "-" + } users[i] = user i++ if i > n { diff --git a/backend/login.go b/backend/login.go index da9732c..e9f9beb 100644 --- a/backend/login.go +++ b/backend/login.go @@ -24,6 +24,7 @@ var ( errEmptySalt = errors.New("empty salt") errWrongPassword = errors.New("invalid username or password") errTooManyFailedLogins = errors.New("too many failed logins") + errAccountIsDisabled = errors.New("account is disabled") ) const ( @@ -119,6 +120,9 @@ func login(username, challenge string) (*loginResult, error) { if err != nil { return nil, err } + if user.Pswd == "" { + return nil, errAccountIsDisabled + } h := md5.New() h.Write(base14.StringToBytes(user.Pswd)) h.Write(base14.StringToBytes(salt.Salt)) diff --git a/backend/user.go b/backend/user.go index 3f1242a..72034f6 100644 --- a/backend/user.go +++ b/backend/user.go @@ -20,6 +20,8 @@ const ( var ( errInvalidToken = errors.New("invalid token") errNoListUsersPermission = errors.New("no list users permission") + errNoSetRolePermission = errors.New("no set role permission") + errInvalidRole = errors.New("invalid role") ) type getUserInfoResult struct { @@ -94,6 +96,7 @@ type getUsersListResult struct { ID int `json:"id"` Name string `json:"name"` Nick string `json:"nick"` + Stat bool `json:"stat"` Role string `json:"role"` Date string `json:"date"` Desc string `json:"desc"` @@ -116,8 +119,9 @@ func getUsersList(token string) ([]getUsersListResult, error) { ret[i].ID = *u.ID ret[i].Name = u.Name ret[i].Nick = u.Nick + ret[i].Stat = u.Pswd != "" ret[i].Role = u.Role.Nick() - ret[i].Date = time.Unix(user.Date, 0).Format(chineseDateLayout) + ret[i].Date = time.Unix(u.Date, 0).Format(chineseDateLayout) ret[i].Desc = u.Desc } return ret, nil @@ -192,6 +196,17 @@ func setUserInfo(id int, nick, desc, avtr *string) error { return global.UserDB.UpdateUserInfo(id, user.Name, n, a, d) } +func setUserRole(id int, role global.UserRole, opname string) error { + if !role.IsVaild() { + return errInvalidRole + } + user, err := global.UserDB.GetUserByID(id) + if err != nil { + return err + } + return global.UserDB.UpdateUserRole(*user.ID, role, opname) +} + func resetPassword(ip, name, mobile string) error { if registerlimit.Get(ip) { return errRequestTooFast diff --git a/frontend/vben/src/api/sys/user.ts b/frontend/vben/src/api/sys/user.ts index c6729b8..148352e 100644 --- a/frontend/vben/src/api/sys/user.ts +++ b/frontend/vben/src/api/sys/user.ts @@ -29,6 +29,8 @@ enum Api { GetUsersCount = '/getUsersCount', GetUsersList = '/getUsersList', IsNameExist = '/isNameExist', + SetRole = '/setRole', + SetStatus = '/setStatus', } /** @@ -162,3 +164,9 @@ export function isNameExist(username: string) { export function doLogout() { return defHttp.get({ url: Api.Logout }, { errorMessageMode: 'none' }) } + +export const setRole = (id: number, role: number) => + defHttp.post({ url: Api.SetRole, params: { id, role } }) + +export const setStatus = (id: number, stat: boolean) => + defHttp.post({ url: Api.SetStatus, params: { id, stat } }) diff --git a/frontend/vben/src/views/dashboard/account/AccountModal.vue b/frontend/vben/src/views/dashboard/account/AccountModal.vue index 152aa29..547b486 100644 --- a/frontend/vben/src/views/dashboard/account/AccountModal.vue +++ b/frontend/vben/src/views/dashboard/account/AccountModal.vue @@ -25,6 +25,9 @@ field: 'name', label: '用户名', component: 'Input', + ifShow: () => { + return !unref(isUpdate) + }, rules: [ { required: true, diff --git a/frontend/vben/src/views/dashboard/account/account.data.ts b/frontend/vben/src/views/dashboard/account/account.data.ts index aa4e763..d76852e 100644 --- a/frontend/vben/src/views/dashboard/account/account.data.ts +++ b/frontend/vben/src/views/dashboard/account/account.data.ts @@ -1,5 +1,9 @@ import { BasicColumn } from '/@/components/Table' import { FormSchema } from '/@/components/Table' +import { h } from 'vue' +import { Switch } from 'ant-design-vue' +import { useMessage } from '/@/hooks/web/useMessage' +import { setStatus } from '/@/api/sys/user' export const columns: BasicColumn[] = [ { @@ -12,15 +16,51 @@ export const columns: BasicColumn[] = [ dataIndex: 'nick', width: 120, }, + { + title: '状态', + dataIndex: 'stat', + width: 120, + customRender: ({ record }) => { + if (!Reflect.has(record, 'pendingStatus')) { + record.pendingStatus = false + } + return h(Switch, { + checked: record.stat, + checkedChildren: '已启用', + unCheckedChildren: '已禁用', + loading: record.pendingStatus, + onChange(checked: boolean) { + const { createMessage } = useMessage() + if (checked) { + record.stat = false + createMessage.error('请让用户通过找回密码启用账户') + return + } + record.pendingStatus = true + setStatus(record.id, checked) + .then(() => { + record.stat = checked + createMessage.success(`已成功禁用账户并清空密码, 如需重新启用, 请让用户找回密码`) + }) + .catch((error) => { + createMessage.error('禁用失败: ' + (error as unknown as Error).message) + }) + .finally(() => { + record.pendingStatus = false + }) + }, + }) + }, + }, { title: '创建时间', dataIndex: 'date', - width: 180, + width: 240, }, { title: '角色', dataIndex: 'role', - width: 200, + width: 120, }, { title: '简介', diff --git a/frontend/vben/src/views/dashboard/account/index.vue b/frontend/vben/src/views/dashboard/account/index.vue index 7cd814f..86a2fbd 100644 --- a/frontend/vben/src/views/dashboard/account/index.vue +++ b/frontend/vben/src/views/dashboard/account/index.vue @@ -97,8 +97,7 @@ if (isUpdate) { // 演示不刷新表格直接更新内部数据。 // 注意:updateTableDataRecord要求表格的rowKey属性为string并且存在于每一行的record的keys中 - const result = updateTableDataRecord(values.id, values) - console.log(result) + updateTableDataRecord(values.id, values) } else { reload() }