完成子账户页面操作权限控制

This commit is contained in:
柏码の讲师 2023-12-04 14:47:04 +08:00
parent 87d469a47b
commit 2e1514d15c
14 changed files with 224 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package com.example.controller; package com.example.controller;
import com.example.entity.RestBean; import com.example.entity.RestBean;
import com.example.entity.dto.Account;
import com.example.entity.vo.request.RenameClientVO; import com.example.entity.vo.request.RenameClientVO;
import com.example.entity.vo.request.RenameNodeVO; import com.example.entity.vo.request.RenameNodeVO;
import com.example.entity.vo.request.RuntimeDetailVO; 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.ClientPreviewVO;
import com.example.entity.vo.response.ClientSimpleVO; import com.example.entity.vo.response.ClientSimpleVO;
import com.example.entity.vo.response.RuntimeDetailsVO; import com.example.entity.vo.response.RuntimeDetailsVO;
import com.example.service.AccountService;
import com.example.service.ClientService; import com.example.service.ClientService;
import com.example.utils.Const;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -22,51 +25,121 @@ public class MonitorController {
@Resource @Resource
ClientService service; ClientService service;
@Resource
AccountService accountService;
@GetMapping("/list") @GetMapping("/list")
public RestBean<List<ClientPreviewVO>> listAllClient() { public RestBean<List<ClientPreviewVO>> listAllClient(@RequestAttribute(Const.ATTR_USER_ID) int userId,
return RestBean.success(service.listClients()); @RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
List<Integer> clients = this.accountAccessClients(userId);
List<ClientPreviewVO> data = service.listClients();
if(this.isAdminAccount(userRole)) {
return RestBean.success(data);
} else {
return RestBean.success(data.stream()
.filter(vo -> clients.contains(vo.getId()))
.toList());
}
} }
@GetMapping("/simple-list") @GetMapping("/simple-list")
public RestBean<List<ClientSimpleVO>> simpleClientList() { public RestBean<List<ClientSimpleVO>> simpleClientList(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if (this.isAdminAccount(userRole)) {
return RestBean.success(service.listSimpleList()); return RestBean.success(service.listSimpleList());
} else {
return RestBean.noPermission();
}
} }
@PostMapping("/rename") @PostMapping("/rename")
public RestBean<Void> renameClient(@RequestBody @Valid RenameClientVO vo) { 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); service.renameClient(vo);
return RestBean.success(); return RestBean.success();
} else {
return RestBean.noPermission();
}
} }
@PostMapping("/node") @PostMapping("/node")
public RestBean<Void> renameNode(@RequestBody @Valid RenameNodeVO vo) { 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); service.renameNode(vo);
return RestBean.success(); return RestBean.success();
} else {
return RestBean.noPermission();
}
} }
@GetMapping("/details") @GetMapping("/details")
public RestBean<ClientDetailsVO> clientDetails(int clientId) { public RestBean<ClientDetailsVO> clientDetails(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)); return RestBean.success(service.clientDetails(clientId));
} else {
return RestBean.noPermission();
}
} }
@GetMapping("/runtime-history") @GetMapping("/runtime-history")
public RestBean<RuntimeDetailsVO> runtimeDetailsHistory(int clientId) { public RestBean<RuntimeDetailsVO> 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)); return RestBean.success(service.clientRuntimeDetailsHistory(clientId));
} else {
return RestBean.noPermission();
}
} }
@GetMapping("/runtime-now") @GetMapping("/runtime-now")
public RestBean<RuntimeDetailVO> runtimeDetailsNow(int 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)); return RestBean.success(service.clientRuntimeDetailsNow(clientId));
} else {
return RestBean.noPermission();
}
} }
@GetMapping("/register") @GetMapping("/register")
public RestBean<String> registerToken() { public RestBean<String> registerToken(@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if (this.isAdminAccount(userRole)) {
return RestBean.success(service.registerToken()); return RestBean.success(service.registerToken());
} else {
return RestBean.noPermission();
}
} }
@GetMapping("/delete") @GetMapping("/delete")
public RestBean<String> deleteClient(int clientId) { public RestBean<String> deleteClient(int clientId,
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
if (this.isAdminAccount(userRole)) {
service.deleteClient(clientId); service.deleteClient(clientId);
return RestBean.success(); 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); return new RestBean<>(requestId(), code, null, message);
} }
public static <T> RestBean<T> noPermission() {
return new RestBean<>(requestId(), 401, null, "权限不足,拒绝访问");
}
/** /**
* 快速将当前实体转换为JSON字符串格式 * 快速将当前实体转换为JSON字符串格式
* @return JSON字符串 * @return JSON字符串

View File

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

View File

@ -16,5 +16,5 @@ public class CreateSubAccountVO {
@Length(min = 6, max = 20) @Length(min = 6, max = 20)
String password; String password;
@Size(min = 1) @Size(min = 1)
List<String> clients; List<Integer> clients;
} }

View File

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

View File

@ -156,7 +156,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
return this.list(Wrappers.<Account>query().eq("role", "user")) return this.list(Wrappers.<Account>query().eq("role", "user"))
.stream().map(account -> { .stream().map(account -> {
SubAccountVO vo = account.asViewObject(SubAccountVO.class); SubAccountVO vo = account.asViewObject(SubAccountVO.class);
vo.setClientList(JSONArray.parse(account.getClients())); vo.setClientList(JSONArray.copyOf(account.getClientList()));
return vo; return vo;
}).toList(); }).toList();
} }

View File

@ -14,6 +14,8 @@
"echarts": "^5.4.3", "echarts": "^5.4.3",
"element-plus": "^2.3.9", "element-plus": "^2.3.9",
"flag-icon-css": "^4.1.7", "flag-icon-css": "^4.1.7",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.4" "vue-router": "^4.2.4"
}, },
@ -1526,6 +1528,58 @@
"node": ">=6" "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": { "node_modules/pkg-types": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz",

View File

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

View File

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

View File

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

@ -12,6 +12,14 @@
v-model="dark" active-color="#424242" v-model="dark" active-color="#424242"
:active-action-icon="Moon" :active-action-icon="Moon"
:inactive-action-icon="Sunny"/> :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-dropdown>
<el-avatar class="avatar" <el-avatar class="avatar"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/> src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
@ -45,6 +53,9 @@ import TabItem from "@/component/TabItem.vue";
import {ref} from "vue"; import {ref} from "vue";
import {useDark} from "@vueuse/core"; import {useDark} from "@vueuse/core";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import {useStore} from "@/store";
const store = useStore()
function userLogout() { function userLogout() {
logout(() => router.push("/")) logout(() => router.push("/"))

View File

@ -6,7 +6,9 @@ import ClientDetails from "@/component/ClientDetails.vue";
import {Plus} from "@element-plus/icons-vue"; import {Plus} from "@element-plus/icons-vue";
import RegisterCard from "@/component/RegisterCard.vue"; import RegisterCard from "@/component/RegisterCard.vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import {useStore} from "@/store";
const store = useStore()
const route = useRoute() const route = useRoute()
const list = ref([]) const list = ref([])
@ -40,7 +42,8 @@ updateList()
<div class="description">在这里管理所有已经注册的主机实例实时监控主机运行状态快速进行管理和操作</div> <div class="description">在这里管理所有已经注册的主机实例实时监控主机运行状态快速进行管理和操作</div>
</div> </div>
<div> <div>
<el-button :icon="Plus" type="primary" @click="register.show = true" plain>添加新主机</el-button> <el-button :icon="Plus" type="primary" @click="register.show = true"
plain :disabled="!store.isAdmin">添加新主机</el-button>
</div> </div>
</div> </div>
<el-divider style="margin: 10px 0"/> <el-divider style="margin: 10px 0"/>

View File

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