完成子账户权限控制
This commit is contained in:
parent
497b8b8b04
commit
65763f5c24
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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字符串
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import java.util.Date;
|
||||
@Data
|
||||
public class AuthorizeVO {
|
||||
String username;
|
||||
String email;
|
||||
String role;
|
||||
String token;
|
||||
Date expire;
|
||||
|
54
itbaima-monitor-web/package-lock.json
generated
54
itbaima-monitor-web/package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
19
itbaima-monitor-web/src/store/index.js
Normal file
19
itbaima-monitor-web/src/store/index.js
Normal 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
|
||||
})
|
@ -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())
|
||||
|
@ -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>
|
||||
|
@ -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()"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user