完成子账户权限控制

This commit is contained in:
柏码の讲师 2023-12-13 18:40:23 +08:00
parent 497b8b8b04
commit 65763f5c24
12 changed files with 222 additions and 30 deletions

View File

@ -1,6 +1,7 @@
package com.example.controller;
import com.example.entity.RestBean;
import com.example.entity.dto.Account;
import com.example.entity.vo.request.RenameClientVO;
import com.example.entity.vo.request.RenameNodeVO;
import com.example.entity.vo.request.RuntimeDetailVO;
@ -8,7 +9,9 @@ import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO;
import com.example.entity.vo.response.ClientSimpleVO;
import com.example.entity.vo.response.RuntimeHistoryVO;
import com.example.service.AccountService;
import com.example.service.ClientService;
import com.example.utils.Const;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@ -22,51 +25,121 @@ public class MonitorController {
@Resource
ClientService service;
@Resource
AccountService accountService;
@GetMapping("/list")
public RestBean<List<ClientPreviewVO>> listAllClient() {
return RestBean.success(service.listClients());
public RestBean<List<ClientPreviewVO>> listAllClient(@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
List<ClientPreviewVO> clients = service.listClients();
if(this.isAdminAccount(userRole)) {
return RestBean.success(clients);
} else {
List<Integer> ids = this.accountAccessClients(userId);
return RestBean.success(clients.stream()
.filter(vo -> ids.contains(vo.getId()))
.toList());
}
}
@GetMapping("/simple-list")
public RestBean<List<ClientSimpleVO>> simpleClientList() {
return RestBean.success(service.listSimpleList());
public RestBean<List<ClientSimpleVO>> simpleClientList(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.isAdminAccount(userRole)) {
return RestBean.success(service.listSimpleList());
} else {
return RestBean.noPermission();
}
}
@PostMapping("/rename")
public RestBean<Void> renameClient(@RequestBody @Valid RenameClientVO vo) {
service.renameClient(vo);
return RestBean.success();
public RestBean<Void> renameClient(@RequestBody @Valid RenameClientVO vo,
@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.permissionCheck(userId, userRole, vo.getId())) {
service.renameClient(vo);
return RestBean.success();
} else {
return RestBean.noPermission();
}
}
@PostMapping("/node")
public RestBean<Void> renameNode(@RequestBody @Valid RenameNodeVO vo) {
service.renameNode(vo);
return RestBean.success();
public RestBean<Void> renameNode(@RequestBody @Valid RenameNodeVO vo,
@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.permissionCheck(userId, userRole, vo.getId())) {
service.renameNode(vo);
return RestBean.success();
} else {
return RestBean.noPermission();
}
}
@GetMapping("/details")
public RestBean<ClientDetailsVO> details(int clientId) {
return RestBean.success(service.clientDetails(clientId));
public RestBean<ClientDetailsVO> details(int clientId,
@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.permissionCheck(userId, userRole, clientId)) {
return RestBean.success(service.clientDetails(clientId));
} else {
return RestBean.noPermission();
}
}
@GetMapping("/runtime-history")
public RestBean<RuntimeHistoryVO> runtimeDetailsHistory(int clientId) {
return RestBean.success(service.clientRuntimeDetailsHistory(clientId));
public RestBean<RuntimeHistoryVO> runtimeDetailsHistory(int clientId,
@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.permissionCheck(userId, userRole, clientId)) {
return RestBean.success(service.clientRuntimeDetailsHistory(clientId));
} else {
return RestBean.noPermission();
}
}
@GetMapping("/runtime-now")
public RestBean<RuntimeDetailVO> runtimeDetailsNow(int clientId) {
return RestBean.success(service.clientRuntimeDetailsNow(clientId));
public RestBean<RuntimeDetailVO> runtimeDetailsNow(int clientId,
@RequestAttribute(Const.ATTR_USER_ID) int userId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if(this.permissionCheck(userId, userRole, clientId)) {
return RestBean.success(service.clientRuntimeDetailsNow(clientId));
} else {
return RestBean.noPermission();
}
}
@GetMapping("/register")
public RestBean<String> registerToken() {
return RestBean.success(service.registerToken());
public RestBean<String> registerToken(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if (this.isAdminAccount(userRole)) {
return RestBean.success(service.registerToken());
} else {
return RestBean.noPermission();
}
}
@GetMapping("/delete")
public RestBean<String> deleteClient(int clientId) {
service.deleteClient(clientId);
return RestBean.success();
public RestBean<String> deleteClient(int clientId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if (this.isAdminAccount(userRole)) {
service.deleteClient(clientId);
return RestBean.success();
} else {
return RestBean.noPermission();
}
}
private List<Integer> accountAccessClients(int uid) {
Account account = accountService.getById(uid);
return account.getClientList();
}
private boolean isAdminAccount(String role) {
role = role.substring(5);
return Const.ROLE_ADMIN.equals(role);
}
private boolean permissionCheck(int uid, String role, int clientId) {
if(this.isAdminAccount(role)) return true;
return this.accountAccessClients(uid).contains(clientId);
}
}

View File

@ -34,6 +34,10 @@ public record RestBean<T> (long id, int code, T data, String message) {
return new RestBean<>(requestId(), code, null, message);
}
public static <T> RestBean<T> noPermission() {
return new RestBean<>(requestId(), 401, null, "权限不足,拒绝访问");
}
/**
* 快速将当前实体转换为JSON字符串格式
* @return JSON字符串

View File

@ -1,5 +1,6 @@
package com.example.entity.dto;
import com.alibaba.fastjson2.JSONArray;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -7,7 +8,9 @@ import com.example.entity.BaseData;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* 数据库中的用户信息
@ -24,4 +27,9 @@ public class Account implements BaseData {
String role;
Date registerTime;
String clients;
public List<Integer> getClientList() {
if(clients == null) return Collections.emptyList();
return JSONArray.parse(clients).toList(Integer.class);
}
}

View File

@ -10,6 +10,7 @@ import java.util.Date;
@Data
public class AuthorizeVO {
String username;
String email;
String role;
String token;
Date expire;

View File

@ -14,6 +14,8 @@
"echarts": "^5.4.3",
"element-plus": "^2.3.9",
"flag-icon-css": "^4.1.7",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},
@ -1515,6 +1517,58 @@
"node": ">=6"
}
},
"node_modules/pinia": {
"version": "2.1.7",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia-plugin-persistedstate": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.0.tgz",
"integrity": "sha512-tZbNGf2vjAQcIm7alK40sE51Qu/m9oWr+rEgNm/2AWr1huFxj72CjvpQcIQzMknDBJEkQznCLAGtJTIcLKrKdw==",
"peerDependencies": {
"pinia": "^2.0.0"
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pkg-types": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz",

View File

@ -14,6 +14,8 @@
"echarts": "^5.4.3",
"element-plus": "^2.3.9",
"flag-icon-css": "^4.1.7",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},

View File

@ -6,11 +6,16 @@ import axios from "axios";
import '@/assets/css/element.less'
import 'flag-icon-css/css/flag-icons.min.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
axios.defaults.baseURL = 'http://localhost:8080'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
pinia.use(piniaPluginPersistedstate)
app.use(router)
app.mount('#app')

View File

@ -1,5 +1,6 @@
import axios from "axios";
import {ElMessage} from "element-plus";
import {useStore} from "@/store";
const authItemName = "authorize"
@ -74,6 +75,10 @@ function login(username, password, remember, success, failure = defaultFailure){
'Content-Type': 'application/x-www-form-urlencoded'
}, (data) => {
storeAccessToken(remember, data.token, data.expire)
const store = useStore()
store.user.role = data.role
store.user.username = data.username
store.user.email = data.email
ElMessage.success(`登录成功,欢迎 ${data.username} 来到我们的系统`)
success(data)
}, failure)

View File

@ -0,0 +1,19 @@
import {defineStore} from "pinia";
export const useStore = defineStore('general', {
state: () => {
return {
user: {
role: '',
username: '',
email: ''
}
}
},
getters: {
isAdmin() {
return this.user.role === 'admin'
}
},
persist: true
})

View File

@ -10,6 +10,14 @@
v-model="dark" active-color="#424242"
:active-action-icon="Moon"
:inactive-action-icon="Sunny"/>
<div style="text-align: right;line-height: 16px;margin-right: 10px">
<div>
<el-tag type="success" v-if="store.isAdmin" size="small">管理员</el-tag>
<el-tag v-else size="small">子账户</el-tag>
{{store.user.username}}
</div>
<div style="font-size: 13px;color: grey">{{store.user.email}}</div>
</div>
<el-dropdown>
<el-avatar class="avatar"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
@ -44,6 +52,9 @@ import {ref} from "vue";
import {useDark} from "@vueuse/core";
import TabItem from "@/component/TabItem.vue";
import {useRoute} from "vue-router";
import {useStore} from "@/store";
const store = useStore()
const route = useRoute()
const dark = ref(useDark())

View File

@ -6,6 +6,7 @@ import ClientDetails from "@/component/ClientDetails.vue";
import RegisterCard from "@/component/RegisterCard.vue";
import {Plus} from "@element-plus/icons-vue";
import {useRoute} from "vue-router";
import {useStore} from "@/store";
const locations = [
{name: 'cn', desc: '中国大陆'},
@ -19,6 +20,7 @@ const locations = [
const checkedNodes = ref([])
const list = ref([])
const store = useStore()
const route = useRoute()
@ -62,7 +64,7 @@ const refreshToken = () => get('/api/monitor/register', token => register.token
<div class="desc">在这里管理所有已经注册的主机实例实时监控主机运行状态快速进行管理和操作</div>
</div>
<div>
<el-button :icon="Plus" type="primary" plain
<el-button :icon="Plus" type="primary" plain :disabled="!store.isAdmin"
@click="register.show = true">添加新主机</el-button>
</div>
</div>

View File

@ -5,6 +5,9 @@ import {get, logout, post} from "@/net";
import {ElMessage} from "element-plus";
import router from "@/router";
import CreateSubAccount from "@/component/CreateSubAccount.vue";
import {useStore} from "@/store";
const store = useStore()
const formRef = ref()
const valid = ref(false)
@ -52,10 +55,12 @@ function resetPassword() {
}
const simpleList = ref([])
get('/api/monitor/simple-list', list => {
simpleList.value = list
initSubAccounts()
})
if(store.isAdmin) {
get('/api/monitor/simple-list', list => {
simpleList.value = list
initSubAccounts()
})
}
const accounts = ref([])
const initSubAccounts = () =>
@ -123,10 +128,13 @@ function deleteAccount(id) {
<el-button :icon="Plus" type="primary"
@click="createAccount = true" plain>添加更多子用户</el-button>
</div>
<el-empty :image-size="100" description="还没有任何子用户哦" v-else>
<el-button :icon="Plus" type="primary" plain
@click="createAccount = true">添加子用户</el-button>
</el-empty>
<div v-else>
<el-empty :image-size="100" description="还没有任何子用户哦" v-if="store.isAdmin">
<el-button :icon="Plus" type="primary" plain
@click="createAccount = true">添加子用户</el-button>
</el-empty>
<el-empty :image-size="100" description="子账户只能由管理员账号进行操作" v-else/>
</div>
</div>
<el-drawer v-model="createAccount" size="350" :with-header="false">
<create-sub-account :clients="simpleList" @create="createAccount = false;initSubAccounts()"/>