[Core] [Permission] Users can assign permissions (#360)

This commit is contained in:
qianmoQ 2023-05-26 11:24:18 +08:00 committed by GitHub
commit 6b1df92850
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 488 additions and 46 deletions

View File

@ -0,0 +1,18 @@
package io.edurt.datacap.server.body.user;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.List;
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class UserRole
{
private Long userId;
private List<Long> roles;
}

View File

@ -1,14 +1,18 @@
package io.edurt.datacap.server.controller.user;
import com.google.common.collect.Sets;
import io.edurt.datacap.server.body.FilterBody;
import io.edurt.datacap.server.body.UserNameBody;
import io.edurt.datacap.server.body.UserPasswordBody;
import io.edurt.datacap.server.body.UserQuestionBody;
import io.edurt.datacap.server.body.user.UserRole;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.RoleEntity;
import io.edurt.datacap.server.entity.UserEntity;
import io.edurt.datacap.server.entity.UserLogEntity;
import io.edurt.datacap.server.record.TreeRecord;
import io.edurt.datacap.server.repository.RoleRepository;
import io.edurt.datacap.server.service.UserLogService;
import io.edurt.datacap.server.service.UserService;
import org.springframework.validation.annotation.Validated;
@ -22,6 +26,8 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@RestController
@RequestMapping(value = "/api/v1/user")
@ -29,11 +35,13 @@ public class UserController
{
private final UserService userService;
private final UserLogService userLogService;
private final RoleRepository roleRepository;
public UserController(UserService userService, UserLogService userLogService)
public UserController(UserService userService, UserLogService userLogService, RoleRepository roleRepository)
{
this.userService = userService;
this.userLogService = userLogService;
this.roleRepository = roleRepository;
}
@GetMapping(value = {"{id}", ""})
@ -83,4 +91,28 @@ public class UserController
{
return this.userService.getMenus();
}
@PostMapping(value = "list")
public Response<PageEntity<UserEntity>> getAllByFilter(@RequestBody FilterBody filter)
{
return this.userService.getAll(filter);
}
@PutMapping(value = "allocationRole")
public Response<UserEntity> allocationRole(@RequestBody UserRole configure)
{
UserEntity user = new UserEntity();
user.setId(configure.getUserId());
Set<RoleEntity> roles = Sets.newHashSet();
configure.getRoles()
.stream()
.forEach(id -> {
Optional<RoleEntity> optionalRole = roleRepository.findById(id);
if (optionalRole.isPresent()) {
roles.add(optionalRole.get());
}
});
user.setRoles(roles);
return this.userService.saveOrUpdate(user);
}
}

View File

@ -34,7 +34,7 @@ public class UserDetailsService
public static UserDetailsService build(UserEntity user)
{
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.map(role -> new SimpleGrantedAuthority(String.valueOf(role.getId())))
.collect(Collectors.toList());
return new UserDetailsService(
user.getId(),

View File

@ -1,10 +1,12 @@
package io.edurt.datacap.server.service;
import io.edurt.datacap.server.body.FilterBody;
import io.edurt.datacap.server.body.UserNameBody;
import io.edurt.datacap.server.body.UserPasswordBody;
import io.edurt.datacap.server.body.UserQuestionBody;
import io.edurt.datacap.server.common.JwtResponse;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.UserEntity;
import io.edurt.datacap.server.record.TreeRecord;
@ -30,4 +32,6 @@ public interface UserService
Response<List<Object>> getSugs(Long id);
Response<List<TreeRecord>> getMenus();
Response<PageEntity<UserEntity>> getAll(FilterBody filter);
}

View File

@ -6,7 +6,9 @@ import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
import com.unfbx.chatgpt.entity.chat.Message;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.server.adapter.PageRequestAdapter;
import io.edurt.datacap.server.audit.AuditUserLog;
import io.edurt.datacap.server.body.FilterBody;
import io.edurt.datacap.server.body.UserNameBody;
import io.edurt.datacap.server.body.UserPasswordBody;
import io.edurt.datacap.server.body.UserQuestionBody;
@ -15,6 +17,7 @@ import io.edurt.datacap.server.common.JSON;
import io.edurt.datacap.server.common.JwtResponse;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.common.ServiceState;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.RoleEntity;
import io.edurt.datacap.server.entity.SourceEntity;
import io.edurt.datacap.server.entity.UserChatEntity;
@ -32,6 +35,7 @@ import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -88,25 +92,29 @@ public class UserServiceImpl
@Override
public Response<UserEntity> saveOrUpdate(UserEntity configure)
{
Optional<UserEntity> userOptional = this.userRepository.findByUsername(configure.getUsername());
if (userOptional.isPresent()) {
return Response.failure(ServiceState.USER_EXISTS);
}
UserEntity user = new UserEntity();
user.setUsername(configure.getUsername());
user.setPassword(encoder.encode(configure.getPassword()));
Set<RoleEntity> userRoles = configure.getRoles();
Set<RoleEntity> roles = new HashSet<>();
if (ObjectUtils.isEmpty(userRoles)) {
Optional<RoleEntity> userRoleOptional = roleRepository.findByName("User");
if (!userRoleOptional.isPresent()) {
return Response.failure(ServiceState.USER_ROLE_NOT_FOUND);
if (ObjectUtils.isEmpty(configure.getId())) {
Optional<UserEntity> userOptional = this.userRepository.findByUsername(configure.getUsername());
if (userOptional.isPresent()) {
return Response.failure(ServiceState.USER_EXISTS);
}
roles.add(userRoleOptional.get());
user.setUsername(configure.getUsername());
user.setPassword(encoder.encode(configure.getPassword()));
Set<RoleEntity> userRoles = configure.getRoles();
Set<RoleEntity> roles = new HashSet<>();
if (ObjectUtils.isEmpty(userRoles)) {
Optional<RoleEntity> userRoleOptional = roleRepository.findByName("User");
if (!userRoleOptional.isPresent()) {
return Response.failure(ServiceState.USER_ROLE_NOT_FOUND);
}
roles.add(userRoleOptional.get());
}
user.setRoles(roles);
}
else {
user = userRepository.findById(configure.getId()).get();
user.setRoles(configure.getRoles());
}
user.setRoles(roles);
return Response.success(userRepository.save(user));
}
@ -349,4 +357,11 @@ public class UserServiceImpl
tree.sort(Comparator.comparing(TreeRecord::getSorted));
return Response.success(tree);
}
@Override
public Response<PageEntity<UserEntity>> getAll(FilterBody filter)
{
Pageable pageable = PageRequestAdapter.of(filter);
return Response.success(PageEntity.build(this.userRepository.findAll(pageable)));
}
}

View File

@ -456,7 +456,9 @@ values ('全局 - 首页', 'HOME', '全局路由:所有用户都可以访问',
('管理员 - 系统 - 权限', 'ROLE', '管理员:管理员权限用户可以访问
', '/admin/role', null, 4, 'VIEW', 8, 1, 'common.authority', 'md-flag'),
('管理员 - 系统 - 菜单', 'MENU', '管理员:管理员权限用户可以访问
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu');
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu'),
('管理员 - 系统 - 用户', 'USERS', '管理员:管理员权限用户可以访问
', '/admin/users', null, 6, 'VIEW', 8, 1, 'common.user', 'ios-man');
insert into role_menu_relation (role_id, menu_id)
values ('2', '7'),
@ -478,4 +480,5 @@ values ('2', '7'),
('1', '2'),
('1', '3'),
('1', '6'),
('1', '11');
('1', '11'),
('1', '14');

View File

@ -411,7 +411,7 @@ CREATE TABLE IF NOT EXISTS `role_menu_relation`
menu_id long
);
CREATE TABLE `menus`
CREATE TABLE IF NOT EXISTS `menus`
(
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
@ -453,7 +453,9 @@ values ('全局 - 首页', 'HOME', '全局路由:所有用户都可以访问',
('管理员 - 系统 - 权限', 'ROLE', '管理员:管理员权限用户可以访问
', '/admin/role', null, 4, 'VIEW', 8, 1, 'common.authority', 'md-flag'),
('管理员 - 系统 - 菜单', 'MENU', '管理员:管理员权限用户可以访问
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu');
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu'),
('管理员 - 系统 - 用户', 'USERS', '管理员:管理员权限用户可以访问
', '/admin/users', null, 6, 'VIEW', 8, 1, 'common.user', 'ios-man');
insert into role_menu_relation (role_id, menu_id)
values ('2', '7'),
@ -475,4 +477,5 @@ values ('2', '7'),
('1', '2'),
('1', '3'),
('1', '6'),
('1', '11');
('1', '11'),
('1', '14');

View File

@ -698,7 +698,7 @@ CREATE TABLE IF NOT EXISTS `role_menu_relation`
menu_id long
);
CREATE TABLE `menus`
CREATE TABLE IF NOT EXISTS `menus`
(
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
@ -738,7 +738,9 @@ values ('全局 - 首页', 'HOME', '全局路由:所有用户都可以访问',
('管理员 - 系统 - 权限', 'ROLE', '管理员:管理员权限用户可以访问
', '/admin/role', null, 4, 'VIEW', 8, 1, 'common.authority', 'md-flag'),
('管理员 - 系统 - 菜单', 'MENU', '管理员:管理员权限用户可以访问
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu');
', '/admin/menu', null, 5, 'VIEW', 8, 1, 'common.menu', 'md-menu'),
('管理员 - 系统 - 用户', 'USERS', '管理员:管理员权限用户可以访问
', '/admin/users', null, 6, 'VIEW', 8, 1, 'common.user', 'ios-man');
insert into role_menu_relation (role_id, menu_id)
values ('2', '7'),
@ -760,4 +762,5 @@ values ('2', '7'),
('1', '2'),
('1', '3'),
('1', '6'),
('1', '11');
('1', '11'),
('1', '14');

View File

@ -1,9 +1,11 @@
const token = 'AuthToken';
const menu = 'AvailableMenus';
const getCurrentUserId = () => {
return JSON.parse(localStorage.getItem(token) || '{}').id;
}
export default {
token: token,
menu: menu,
getCurrentUserId: getCurrentUserId
}

View File

@ -116,4 +116,5 @@ export default {
menu: 'Menu',
i18nKey: 'I18n Key',
icon: 'Icon',
user: 'User',
}

View File

@ -116,4 +116,5 @@ export default {
menu: '菜单',
i18nKey: 'I18n Key',
icon: '图标',
user: '用户'
}

View File

@ -5,7 +5,7 @@ import NProgress from "nprogress";
import "nprogress/nprogress.css";
import Common from "@/common/Common";
import ProfileLayout from "@/views/pages/profile/layout/ProfileLayout.vue";
import _ from 'lodash';
import LayoutWebErrorContainer from "@/views/layout/web-error/Layout.vue";
NProgress.configure({
easing: 'ease',
@ -151,6 +151,13 @@ const routes: Array<RouteRecordRaw> = [
roles: ['Admin']
},
component: () => import("@/views/admin/menu/MenuHome.vue")
},
{
path: "users",
meta: {
roles: ['Admin']
},
component: () => import("@/views/admin/user/UserHome.vue")
}
]
},
@ -209,7 +216,7 @@ const routes: Array<RouteRecordRaw> = [
{
path: "/common",
name: "common",
component: LayoutContainer,
component: LayoutWebErrorContainer,
children: [
{
name: "routerNotFound",
@ -263,12 +270,12 @@ router.beforeEach((to, from, next) => {
if (localStorage.getItem(Common.token)) {
const meta = JSON.parse(localStorage.getItem(Common.token));
// @ts-ignore
if (_.intersection(to.meta.roles, meta['roles']).length > 0) {
next();
}
else {
next({name: "routerNotAuthorized"});
}
// if (_.intersection(to.meta.roles, meta['roles']).length > 0) {
next();
// }
// else {
// next({name: "routerNotAuthorized"});
// }
}
else {
next('/auth/signin');

View File

@ -0,0 +1,32 @@
import {ResponseModel} from '@/model/ResponseModel'
import {BaseService} from "@/services/BaseService";
import {HttpCommon} from "@/common/HttpCommon";
const baseUrl = '/api/v1/user'
class UserService
extends BaseService<any>
{
constructor()
{
super(baseUrl);
}
deleteById(id: number): Promise<ResponseModel>
{
throw new Error('Method not implemented.');
}
getByName<T>(name: string): Promise<ResponseModel>
{
return Promise.resolve(undefined);
}
allocationRole(configure: any): Promise<ResponseModel>
{
return new HttpCommon().put(`${baseUrl}/allocationRole`, configure)
}
}
export default new UserService()

View File

@ -0,0 +1,134 @@
<template>
<div>
<Modal v-model="visible"
:title="$t('common.authority')"
:closable="false"
:mask-closable="false"
@cancel="handlerCancel()">
<CheckboxGroup v-model="selectRoles">
<Checkbox v-for="role in fullRoles"
v-bind:key="role.id"
border
:label="role.id">
{{ role.name }}
</Checkbox>
</CheckboxGroup>
<Spin fix
:show="loading">
</Spin>
<template #footer>
<Button key="cancel"
size="small"
type="error"
:disabled="saving"
@click="handlerCancel()">
{{ $t('common.cancel') }}
</Button>
<Button type="primary"
size="small"
:loading="saving"
@click="handlerSave()">
{{ $t('common.save') }}
</Button>
</template>
</Modal>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import RoleService from "@/services/admin/RoleService";
import {Filter} from "@/model/Filter";
import UserService from "@/services/admin/UserService";
import CommonEntity from '@/entity/CommonEntity';
export default defineComponent({
name: 'RoleAllocationComponent',
props: {
isVisible: {
type: Boolean,
default: () => false
},
userId: {
type: Number,
required: true,
default: () => 0
}
},
data()
{
return {
loading: false,
saving: false,
fullRoles: [],
selectRoles: []
}
},
created()
{
this.handlerInitialize();
},
methods: {
handlerInitialize()
{
this.loading = true;
const filter: Filter = new Filter();
filter.size = 1000;
RoleService.getAll(filter)
.then((response) => {
if (response.status) {
this.fullRoles = response.data.content;
UserService.getById(this.userId)
.then(userResponse => {
if (userResponse.status) {
this.selectRoles = userResponse.data
?.roles
.map(role => role.id)
}
})
}
})
.finally(() => {
this.loading = false
})
},
handlerSave()
{
this.saving = true;
const configure = CommonEntity
.set('roles', this.selectRoles)
.set('userId', this.userId)
.build()['props'];
UserService.allocationRole(configure)
.then((response) => {
if (response.status) {
this.saving = false;
this.handlerCancel();
}
else {
this.$Message.error(response.message);
}
})
.finally(() => {
this.saving = false
})
},
handlerCancel()
{
this.$emit('close', false)
}
},
computed: {
visible: {
get(): boolean
{
return this.isVisible
},
set(value: boolean)
{
this.$emit('close', value)
}
}
},
})
</script>

View File

@ -0,0 +1,33 @@
const createHeaders = (i18n: any) => {
return [
{
title: i18n.t('common.id'),
key: 'id',
ellipsis: true,
tooltip: true
},
{
title: i18n.t('common.username'),
key: 'username',
tooltip: true,
ellipsis: true
},
{
title: i18n.t('common.createTime'),
key: 'createTime',
tooltip: true,
ellipsis: true
},
{
title: i18n.t('common.action'),
slot: 'action',
key: 'action',
width: 150,
align: 'center'
}
];
};
export {
createHeaders
}

View File

@ -0,0 +1,120 @@
<template>
<div>
<Card style="width:100%" :title="$t('common.authority')">
<Table :loading="loading"
:columns="headers"
:data="finalData?.content">
<template #action="{ row }">
<Space>
<Tooltip transfer
:content="$t('common.authority')">
<Button shape="circle"
type="primary"
size="small"
icon="md-hammer"
:disabled="row.default"
@click="handlerAllocationRole(row.id)">
</Button>
</Tooltip>
</Space>
</template>
</Table>
<p v-if="!loading" style="margin-top: 10px;">
<Page v-model="pagination.current"
show-sizer
show-elevator
show-total
:total="pagination.total"
:page-size="pagination.size"
@on-page-size-change="handlerSizeChange"
@on-change="handlerIndexChange">
</Page>
</p>
<RoleAllocationComponent v-if="allocationRole"
:is-visible="allocationRole"
:user-id="userId"
@close="handlerAllocationRole">
</RoleAllocationComponent>
</Card>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import {useI18n} from 'vue-i18n';
import {Filter} from "@/model/Filter";
import {ResponsePage} from "@/model/ResponsePage";
import {Pagination, PaginationBuilder} from "@/model/Pagination";
import {createHeaders} from './UserGenerate';
import UserService from "@/services/admin/UserService";
import RoleAllocationComponent from "@/views/admin/role/components/RoleAllocation.vue";
const filter: Filter = new Filter();
const pagination: Pagination = PaginationBuilder.newInstance();
export default defineComponent({
name: 'UserHome',
components: {RoleAllocationComponent},
setup()
{
const i18n = useI18n();
const headers = createHeaders(i18n);
return {
headers,
filter,
pagination
}
},
data()
{
return {
loading: false,
allocationRole: false,
userId: null,
finalData: null as ResponsePage
}
},
created()
{
this.handlerInitialize(this.filter);
},
methods: {
handlerInitialize(filter: Filter)
{
this.loading = true;
UserService.getAll(filter)
.then((response) => {
if (response.status) {
this.finalData = response.data;
this.pagination.total = response.data.total;
}
})
.finally(() => {
this.loading = false
})
},
handlerSizeChange(size: number)
{
this.pagination.size = size;
this.handlerTableChange(this.pagination);
},
handlerIndexChange(index: number)
{
this.pagination.current = index;
this.handlerTableChange(this.pagination);
},
handlerTableChange(pagination: any)
{
this.pagination.current = pagination.current;
this.pagination.size = pagination.size;
this.filter.page = pagination.current;
this.filter.size = pagination.size;
this.handlerInitialize(this.filter);
},
handlerAllocationRole(value: number)
{
this.userId = value
this.allocationRole = !this.allocationRole
}
}
})
</script>

View File

@ -17,6 +17,7 @@ export default {
methods: {
handlerGoSignIn() {
localStorage.removeItem(Common.token);
localStorage.removeItem(Common.menu)
router.push('/auth/signin');
}
}

View File

@ -14,6 +14,7 @@ import Common from "@/common/Common";
export default {
created() {
localStorage.removeItem(Common.token);
localStorage.removeItem(Common.menu)
},
methods: {
handlerGoSignIn() {

View File

@ -81,7 +81,6 @@ import Common from "@/common/Common";
import {AuthResponse} from "@/model/AuthResponse";
import router from "@/router";
import config from '../../../../package.json';
import UserService from "@/services/UserService";
export default defineComponent({
name: "LayoutHeader",
@ -95,6 +94,7 @@ export default defineComponent({
const handlerLogout = () => {
localStorage.removeItem(Common.token);
localStorage.removeItem(Common.menu)
router.push('/auth/signin')
}
const language = 'zh_cn';
@ -119,15 +119,7 @@ export default defineComponent({
methods: {
handlerInitialize()
{
UserService.getMenus()
.then(response => {
if (response.status) {
this.availableMenus = response.data
}
else {
this.$Message.error(response.message)
}
})
this.availableMenus = JSON.parse(localStorage.getItem(Common.menu));
},
handlerChangeLang(language: string)
{

View File

@ -0,0 +1,28 @@
<template>
<div class="layout">
<layout>
<layout-content style="background-color: #FFFFFF; padding: 0; min-height: 500px"/>
<layout-footer/>
<layout-affix/>
</layout>
</div>
</template>
<script lang="ts">
import LayoutFooter from "@/views/layout/components/LayoutFooter.vue";
import LayoutAffix from "@/views/layout/components/LayoutAffix.vue";
import LayoutContent from "@/views/layout/components/LayoutContent.vue";
export default {
name: "LayoutWebErrorContainer",
components: {LayoutContent, LayoutAffix, LayoutFooter}
}
</script>
<style scoped>
.layout {
background: #f5f7f9;
position: relative;
overflow: hidden;
}
</style>

View File

@ -27,6 +27,7 @@ import {AuthUser} from "@/model/AuthUser";
import {AuthService} from "@/services/AuthService";
import Common from "@/common/Common";
import router from "@/router";
import UserService from "@/services/UserService";
export default defineComponent({
setup()
@ -51,12 +52,21 @@ export default defineComponent({
.then(response => {
if (response.status) {
localStorage.setItem(Common.token, JSON.stringify(response.data));
router.push('/');
UserService.getMenus()
.then(menuResponse => {
if (menuResponse.status) {
localStorage.setItem(Common.menu, JSON.stringify(menuResponse.data))
router.push('/');
}
else {
this.$Message.error(menuResponse.message)
}
})
}
else {
this.$Message.error(response.message);
}
});
})
}
}
}

View File

@ -96,6 +96,7 @@ export default defineComponent({
if (response.status) {
// message.success('Success');
localStorage.removeItem(Common.token);
localStorage.removeItem(Common.menu)
this.changePasswordVisible = false;
router.push('/auth/signin');
}

View File

@ -72,6 +72,7 @@ export default defineComponent({
if (response.status) {
this.$Message.success('Success');
localStorage.removeItem(Common.token);
localStorage.removeItem(Common.menu)
router.push('/auth/signin');
}
else {