feat: add user ban for admin

pull/80/head
ROC 2 years ago
parent 315060233b
commit c1ed0dfb4a

@ -0,0 +1,25 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/model"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/errcode"
)
func Admin() gin.HandlerFunc {
return func(c *gin.Context) {
if user, exist := c.Get("USER"); exist {
if userModel, ok := user.(*model.User); ok {
if userModel.Status == model.UserStatusNormal && userModel.IsAdmin {
c.Next()
return
}
}
}
response := app.NewResponse(c)
response.ToErrorResponse(errcode.NoAdminPermission)
c.Abort()
}
}

@ -251,6 +251,36 @@ func BindUserPhone(c *gin.Context) {
response.ToResponse(nil) response.ToResponse(nil)
} }
// 修改用户状态
func ChangeUserStatus(c *gin.Context) {
param := service.ChangeUserStatusReq{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
logrus.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
if param.Status != model.UserStatusNormal && param.Status != model.UserStatusClosed {
response.ToErrorResponse(errcode.InvalidParams)
return
}
user, err := service.GetUserByID(param.ID)
if err != nil {
logrus.Errorf("service.GetUserByID err: %v\n", err)
response.ToErrorResponse(errcode.NoExistUsername)
return
}
// 执行更新
user.Status = param.Status
service.UpdateUserInfo(user)
response.ToResponse(nil)
}
func GetUserProfile(c *gin.Context) { func GetUserProfile(c *gin.Context) {
response := app.NewResponse(c) response := app.NewResponse(c)
username := c.Query("username") username := c.Query("username")

@ -76,6 +76,7 @@ func NewRouter() *gin.Engine {
// 鉴权路由组 // 鉴权路由组
authApi := r.Group("/").Use(middleware.JWT()) authApi := r.Group("/").Use(middleware.JWT())
privApi := r.Group("/").Use(middleware.JWT()).Use(middleware.Priv()) privApi := r.Group("/").Use(middleware.JWT()).Use(middleware.Priv())
adminApi := r.Group("/").Use(middleware.JWT()).Use(middleware.Admin())
{ {
// 同步索引 // 同步索引
authApi.GET("/sync/index", api.SyncSearchIndex) authApi.GET("/sync/index", api.SyncSearchIndex)
@ -173,6 +174,8 @@ func NewRouter() *gin.Engine {
// 删除评论回复 // 删除评论回复
privApi.DELETE("/post/comment/reply", api.DeletePostCommentReply) privApi.DELETE("/post/comment/reply", api.DeletePostCommentReply)
// 管理·禁言/解封用户
adminApi.POST("/admin/user/status", api.ChangeUserStatus)
} }
// 默认404 // 默认404
e.NoRoute(func(c *gin.Context) { e.NoRoute(func(c *gin.Context) {

@ -52,6 +52,11 @@ type ChangeAvatarReq struct {
Avatar string `json:"avatar" form:"avatar" binding:"required"` Avatar string `json:"avatar" form:"avatar" binding:"required"`
} }
type ChangeUserStatusReq struct {
ID int64 `json:"id" form:"id" binding:"required"`
Status int `json:"status" form:"status" binding:"required"`
}
const LOGIN_ERR_KEY = "PaoPaoUserLoginErr" const LOGIN_ERR_KEY = "PaoPaoUserLoginErr"
const MAX_LOGIN_ERR_TIMES = 10 const MAX_LOGIN_ERR_TIMES = 10
@ -226,6 +231,20 @@ func GetUserInfo(param *AuthRequest) (*model.User, error) {
return nil, errcode.UnauthorizedAuthNotExist return nil, errcode.UnauthorizedAuthNotExist
} }
func GetUserByID(id int64) (*model.User, error) {
user, err := ds.GetUserByID(id)
if err != nil {
return nil, err
}
if user.Model != nil && user.ID > 0 {
return user, nil
}
return nil, errcode.NoExistUsername
}
func GetUserByUsername(username string) (*model.User, error) { func GetUserByUsername(username string) (*model.User, error) {
user, err := ds.GetUserByUsername(username) user, err := ds.GetUserByUsername(username)

@ -22,6 +22,7 @@ var (
MaxPhoneCaptchaUseTimes = NewError(20019, "手机验证码已达最大使用次数") MaxPhoneCaptchaUseTimes = NewError(20019, "手机验证码已达最大使用次数")
NicknameLengthLimit = NewError(20020, "昵称长度2~12") NicknameLengthLimit = NewError(20020, "昵称长度2~12")
NoExistUsername = NewError(20021, "用户不存在") NoExistUsername = NewError(20021, "用户不存在")
NoAdminPermission = NewError(20022, "无管理权限")
GetPostsFailed = NewError(30001, "获取动态列表失败") GetPostsFailed = NewError(30001, "获取动态列表失败")
CreatePostFailed = NewError(30002, "动态发布失败") CreatePostFailed = NewError(30002, "动态发布失败")

@ -1,35 +1,40 @@
<template> <template>
<n-config-provider :theme="theme"> <n-config-provider :theme="theme">
<n-message-provider> <n-message-provider>
<div <n-dialog-provider>
class="app-container" <div
:class="{ dark: theme?.name === 'dark' }" class="app-container"
> :class="{ dark: theme?.name === 'dark' }"
<div has-sider class="main-wrap" position="static"> >
<!-- --> <div has-sider class="main-wrap" position="static">
<sidebar /> <!-- -->
<sidebar />
<div class="content-wrap"> <div class="content-wrap">
<router-view class="app-wrap" v-slot="{ Component }"> <router-view
<keep-alive> class="app-wrap"
v-slot="{ Component }"
>
<keep-alive>
<component
v-if="$route.meta.keepAlive"
:is="Component"
/>
</keep-alive>
<component <component
v-if="$route.meta.keepAlive" v-if="!$route.meta.keepAlive"
:is="Component" :is="Component"
/> />
</keep-alive> </router-view>
<component </div>
v-if="!$route.meta.keepAlive"
:is="Component"
/>
</router-view>
</div>
<!-- --> <!-- -->
<rightbar /> <rightbar />
</div>
<!-- / -->
<auth />
</div> </div>
<!-- / --> </n-dialog-provider>
<auth />
</div>
</n-message-provider> </n-message-provider>
<n-global-style /> <n-global-style />
</n-config-provider> </n-config-provider>
@ -39,17 +44,18 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { darkTheme } from 'naive-ui'; import { darkTheme } from 'naive-ui';
import { version, buildTime } from "../build/info.json"; import { version, buildTime } from '../build/info.json';
const store = useStore(); const store = useStore();
const theme = computed(() => (store.state.theme === 'dark' ? darkTheme : null)); const theme = computed(() => (store.state.theme === 'dark' ? darkTheme : null));
console.log(`%c Release Build Info console.log(
`%c Release Build Info
%cVersion v${version} %cVersion v${version}
BuildTime ${buildTime}` BuildTime ${buildTime}`,
, "background:#000;color:#FFF;font-weight:bold;" 'background:#000;color:#FFF;font-weight:bold;',
, "background:#FFF;color:#000;" 'background:#FFF;color:#000;'
) );
</script> </script>
<style lang="less"> <style lang="less">

@ -232,3 +232,16 @@ export const getAttachment = (params: NetParams.UserGetAttachment): Promise<NetR
params params
}); });
}; };
/**
* ·/
* @param {Object} data
* @returns Promise
*/
export const changeUserStatus = (data: NetParams.UserStatusReq): Promise<NetReq.UserChangeStatus> => {
return request({
method: 'post',
url: '/v1/admin/user/status',
data
});
};

@ -66,6 +66,11 @@ declare module NetParams {
page_size: number page_size: number
} }
interface UserStatusReq {
id: number,
status: number
}
interface UserReqRecharge { interface UserReqRecharge {
amount: number amount: number
} }
@ -181,7 +186,7 @@ declare module NetParams {
content: string content: string
} }
interface PostDeleteCommentReply{ interface PostDeleteCommentReply {
id: number id: number
} }

@ -86,13 +86,17 @@ declare module NetReq {
} }
interface UserChangeNickname { interface UserChangeNickname {
} }
interface UserChangePassword { interface UserChangePassword {
} }
interface UserChangeStatus {
}
type PostGetPost = Item.PostProps type PostGetPost = Item.PostProps
interface PostGetPosts { interface PostGetPosts {
@ -140,19 +144,19 @@ declare module NetReq {
type PostCreatePost = Item.PostProps type PostCreatePost = Item.PostProps
interface PostDeletePost { interface PostDeletePost {
} }
type PostCreateComment = Item.CommentProps type PostCreateComment = Item.CommentProps
interface PostDeleteComment { interface PostDeleteComment {
} }
type PostCreateCommentReply = Item.ReplyProps type PostCreateCommentReply = Item.ReplyProps
interface PostDeleteCommentReply{ interface PostDeleteCommentReply {
} }
} }

@ -16,7 +16,7 @@ declare module Item {
/** 用户余额(分) */ /** 用户余额(分) */
balance?: number, balance?: number,
/** 用户状态 */ /** 用户状态 */
status?: 0 | 1 status?: 1 | 2
} }
/** 评论内容 */ /** 评论内容 */
@ -160,7 +160,7 @@ declare module Item {
/** 内容列表 */ /** 内容列表 */
contents: PostItemProps[], contents: PostItemProps[],
/** 标签列表 */ /** 标签列表 */
tags: {[key: string]: number} | string, tags: { [key: string]: number } | string,
/** 是否锁定 */ /** 是否锁定 */
is_lock: number, is_lock: number,
/** 是否置顶 */ /** 是否置顶 */

@ -17,8 +17,8 @@
<div class="uid">UID. {{ user.id }}</div> <div class="uid">UID. {{ user.id }}</div>
</div> </div>
<div class="user-opts"> <div class="user-opts" v-if="store.state.userInfo.id > 0">
<n-space vertical> <n-space>
<n-button <n-button
size="small" size="small"
secondary secondary
@ -27,6 +27,15 @@
> >
</n-button> </n-button>
<n-button
v-if="store.state.userInfo.is_admin"
size="small"
secondary
:type="user.status === 1 ? 'error' : 'warning'"
@click="banUser"
>
{{ user.status === 1 ? '' : '' }}
</n-button>
</n-space> </n-space>
</div> </div>
</div> </div>
@ -72,17 +81,22 @@
import { ref, reactive, watch, onMounted } from 'vue'; import { ref, reactive, watch, onMounted } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { getUserProfile, getUserPosts } from '@/api/user'; import { getUserProfile, getUserPosts, changeUserStatus } from '@/api/user';
import { useDialog, useMessage } from 'naive-ui';
const message = useMessage();
const dialog = useDialog();
const store = useStore(); const store = useStore();
const route = useRoute(); const route = useRoute();
const loading = ref(false); const loading = ref(false);
const user = reactive({ const user = reactive<Item.UserInfo>({
id: 0, id: 0,
avatar: '', avatar: '',
username: '', username: '',
nickname: '', nickname: '',
is_admin: false,
status: 1,
}); });
const userLoading = ref(false); const userLoading = ref(false);
const showWhisper = ref(false); const showWhisper = ref(false);
@ -121,6 +135,8 @@ const loadUser = () => {
user.avatar = res.avatar; user.avatar = res.avatar;
user.username = res.username; user.username = res.username;
user.nickname = res.nickname; user.nickname = res.nickname;
user.is_admin = res.is_admin;
user.status = res.status;
loadPosts(); loadPosts();
}) })
.catch((err) => { .catch((err) => {
@ -135,12 +151,37 @@ const updatePage = (p: number) => {
}; };
const openWhisper = () => { const openWhisper = () => {
// window.$message.warning('您尚未获得私信权限');
showWhisper.value = true; showWhisper.value = true;
}; };
const whisperSuccess = () => { const whisperSuccess = () => {
showWhisper.value = false; showWhisper.value = false;
}; };
const banUser = () => {
dialog.warning({
title: '',
content:
'' +
(user.status === 1 ? '' : '') +
'',
positiveText: '',
negativeText: '',
onPositiveClick: () => {
userLoading.value = true;
changeUserStatus({
id: user.id,
status: user.status === 1 ? 2 : 1,
})
.then((res) => {
userLoading.value = false;
loadUser();
})
.catch((err) => {
userLoading.value = false;
console.log(err);
});
},
});
};
watch( watch(
() => ({ () => ({
path: route.path, path: route.path,
@ -186,6 +227,12 @@ onMounted(() => {
opacity: 0.75; opacity: 0.75;
} }
} }
.user-opts {
position: absolute;
top: 16px;
right: 16px;
}
} }
.pagination-wrap { .pagination-wrap {

Loading…
Cancel
Save