摘要:朋友们好!昨天刷到头条里一位条友说关于使用第三方插件实现解耦的权限控制的方法(至于是什么插件,我不记得了),依是有了写一下我自已开发使用的基础业务框架的想法,篇幅比较大,可能一篇写不完,要分几篇,在下文笔不好及精力有限,有没讲楚的,还请多见谅。作为一位多年从事
朋友们好!昨天刷到头条里一位条友说关于使用第三方插件实现解耦的权限控制的方法(至于是什么插件,我不记得了),依是有了写一下我自已开发使用的基础业务框架的想法,篇幅比较大,可能一篇写不完,要分几篇,在下文笔不好及精力有限,有没讲楚的,还请多见谅。作为一位多年从事软件开发外包业务的老码农,提高软件开发效率至关重要,每个应用基本上都有如角色权限、日志,短信等等的基础业务。不应该为每个新项目都去重做这些工作。自己开发一个基础的业务框架可以大大缩短项目开发周期...
选插一句,我是从C#转JAVA的,之前为东莞一间医院做过OA系统,不过现在的C#对于我来说比较陌生了,因为离开它的时间太久了。
关于我自家开发使用的基础业务框架有:
1、rock-framework:只要包含用户、角色与权限、过滤器、拦截器、用户操作日志、及定义了一些扩展的interface等
2、rock-pay:支付框架:主要集成了wechatPay、aliPay平台相关接口。
3、rock-wechat:主要集成了微信公众平台、开放平台及企业微信的相关接口。
4、rock-message:封装mqtt、websocket、TCPServer等通信
5、rock-sms:短信息,主要集成了阿里短信接口、邮件。
6、rock-file:文件系统
7、rock-cache:缓存相关,集成redis+caffeine,使用自定义注解给方法加上缓存。
8、rock-live:集成美团、抖音等本地生活相关接口
9、rock-logistics:物流相关,集成顺丰同城、闪送、达达等。
10、rock-common:基础依赖,就是一些工具类、自定义异常基类、i18n、基础entity等等。
上面这些都是做过的项目中触及到的业务功能,在做项目时觉得以后可能会复用,所以会将一些可能会复用的功能业务抽出来形成一个单独的SDK。每次有新项目把该依赖加上就可以得到相应的功能、我们只需专注于业务开发就可以,这样就可以大大提高开发效率。
首先我就说说rock-framework的框架设计思路,这是我最基础的业务框架,我每个项目都用到,新项目只要依赖它就拥有了相关的用户权限分组,用户登录、权限鉴别,用户操作日志等等功能
一、rock-framework主要依赖项为JPA、Security、MVC、jjwt-jackson、jjwt-impl等
1、先看看WebMvcConfigurationSupport自定义配置实现
上面代码添加了三个拦截器UriInterceptor(URL请求拦截器,用于请求鉴权)、LogInterceptor(用于URL请求日志记录)、RegisterInterceptor(这个是用于用户注册接口鉴别)。这里重点看看UriInterceptor
package com.rock.frame.interceptors;import com.rock.common.IApiConfig;import com.rock.frame.config.Config;import com.rock.frame.config.WebSecurityConfig;import com.rock.frame.exception.UriAuthenticationException;import com.rock.frame.service.PermissionService;import com.rock.frame.service.UserContextHolder;import lombok.extern.slf4j.Slf4j;import org.springframework.lang.NonNull;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import jakarta.annotation.Resource;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.util.List;/*** description:* URI拦截器 功能权限控制* @author :rock.chen* @date :2021/2/20 7:13 下午
**/@Slf4j@Componentpublic class UriInterceptor implements HandlerInterceptor {@Resourceprivate PermissionService permissionService;@Resourceprivate Config config;@Overridepublic Boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {//静态资源直接放行log.debug("UriInterceptor preHandle ...");Object isStatic = request.getAttribute(WebSecurityConfig.IS_STATIC_RESOURCES);if(isStatic!=null && (Boolean) isStatic){log.debug("UriInterceptor preHandle isStatic ...{}", request.getRequestURI);return true;}String uri = request.getRequestURI;if(this.isOpenUrl(uri)){log.debug("UriInterceptor preHandle isOpenUrl ...{}", request.getRequestURI);return true;}Object sse = request.getAttribute(WebSecurityConfig.IS_SSE_REQUEST);if(sse!=null && (Boolean) sse){log.debug("UriInterceptor preHandle Server-Sent Events...{}", request.getRequestURI);request.setAttribute(WebSecurityConfig.IS_SSE_REQUEST, Boolean.FALSE);return true;}if(uri.startsWith(IApiConfig.GUEST_URL)){log.debug("UriInterceptor preHandle isGuest ...{}", request.getRequestURI);return true;}//鉴别当前登录用户是否有权访问该URIlog.info("To validate URI functionality permissions (URI >>> {})", request.getRequestURI);boolean pass = this.permissionService.checkPermission(request.getRequestURI, UserContextHolder.getCurrentLoginUser);if(!pass){throw new UriAuthenticationException;}if(isSseRequest(uri)){//Server-Sent Events 首次请过通过后,表明已建立长连接,给它一个标识request.setAttribute(WebSecurityConfig.IS_SSE_REQUEST, Boolean.TRUE);}return true;}private boolean isSseRequest(String uri){List uriList = config.getSseUrls;return uriList != null && uriList.contains(uri);}private boolean isOpenUrl(String uri){List uriList = WebSecurityConfig.getOpenUrls;uriList.addAll(config.getOpenUrls);for(String openUrl : uriList){if(uri.equals(openUrl) || (openUrl.endsWith("**") && uri.startsWith(openUrl.substring(0, openUrl.indexOf("*"))))){log.debug("UriInterceptor preHandle isOpenUrl ...uri >>> {}, openUrl >>> {}", uri, openUrl);return true;}}return false;}}
所有URI请求权限由该拦截器处理。
二、看看ISecurityConfig的实现配置
@Slf4j@Configuration@EnableWebSecuritypublic class WebSecurityConfig implements ISecurityConfig {/*** 管理员用户 B 端验证用户*/public static final String ADMIN_ROLE = "ADMIN";/*** 一般用户 C 端验证用户*/public static final String USER_ROLE = "USER";/*** 系统角色前辍*/public static final String ROLE_PREFIX = "ROLE_";public static final String IS_SSE_REQUEST = "_sseRequest";public static List getOpenUrls{List permitList = new ArrayList;permitList.add(STATIC_RESOURCES_PATH + "**");permitList.add(GUEST_API + "**");permitList.add(FROM_WX_CALLBACK + "**");permitList.add(FROM_ALI_CALLBACK + "**");permitList.add(FAVICON_FILE);permitList.add("/login");permitList.add("/logout");permitList.add("/error");return permitList;}//用户登录支持@Resourceprivate LoginProvider loginProvider;//登录成功处理@Resourceprivate LoginSuccessHandler loginSuccessHandler;//登录失败处理@Resourceprivate LoginFailHandler loginFailHandler;//登出处理@Resourceprivate AppLogoutSuccessHandler appLogoutSuccessHandler;//登录异常处理@Resourceprivate AuthenticationExceptionHandler authenticationExceptionHandler;//拒绝访问处理@Resourceprivate AccessDeniedExceptionHandler accessDeniedExceptionHandler;//API签名处理@Resourceprivate RockAPIFilter rockAPIFilter;//Token验证处理@Resourceprivate RockTokenFilter rockTokenFilter;@Resourceprivate Config config;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {this.uriSecurity(http).formLogin(form -> form.loginPage("/login").successHandler(loginSuccessHandler).failureHandler(loginFailHandler)).logout(logout ->logout.logoutUrl("/logout").clearAuthentication(true).logoutSuccessHandler(appLogoutSuccessHandler)).sessionManagement(session ->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authenticationProvider(loginProvider).exceptionHandling(exception -> exception.accessDeniedHandler(accessDeniedExceptionHandler)).exceptionHandling(exception->exception.authenticationEntryPoint(authenticationExceptionHandler));this.addExtBeforeFilters(http);if(config.isCsrfDisable){http.csrf(AbstractHttpConfigurer::disable);}else{http.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse));}return http.build;}private HttpSecurity uriSecurity(HttpSecurity http) throws Exception {List permitList = getPermitList;List sseUrls = config.getSseUrls;if(sseUrls == null || sseUrls.isEmpty){return http.authorizeHttpRequests(authorize -> authorize.requestMatchers(permitList.toArray(new String[0])).permitAll.requestMatchers("/api/**").hasRole(USER_ROLE).requestMatchers("/admin/**").hasRole(ADMIN_ROLE).anyRequest.authenticated);}return http.authorizeHttpRequests(authorize -> authorize.requestMatchers(permitList.toArray(new String[0])).permitAll.requestMatchers(sseUrls.toArray(new String[0])).access(sseAwareAuthorizationManager).requestMatchers("/api/**").hasRole(USER_ROLE).requestMatchers("/admin/**").hasRole(ADMIN_ROLE).anyRequest.authenticated);}private List getPermitList {List permitList = getOpenUrls;List openUrlList = config.getOpenUrls;if(Objects.nonNull(openUrlList) && !openUrlList.isEmpty){permitList.addAll(openUrlList);}List staticFiles = config.getStaticFiles;if(Objects.nonNull(staticFiles) && !staticFiles.isEmpty){permitList.addAll(staticFiles);}return permitList;}private void addExtBeforeFilters(HttpSecurity httpSecurity) {List extFilterList = new ArrayList;Map list = BeanUtils.getBeansByAnnotation(ExtBeforeFilter.class);for (Map.Entry entry: list.entrySet){Object filter = entry.getValue;if (!(filter instanceof Filter)) {continue;}ExtBeforeFilter extBeforeFilter = filter.getClass.getAnnotation(ExtBeforeFilter.class);ExtFilter extFilter = ExtFilter.builder.sort(extBeforeFilter.sort).filter((Filter) filter).build;extFilterList.add(extFilter);}extFilterList.sort(Comparator.comparingInt(ExtFilter::getSort));for(ExtFilter filter:extFilterList){log.info("add ExtBeforeFilter {}...", filter.getFilter.getClass.getName);httpSecurity.addFilterBefore(filter.getFilter, UsernamePasswordAuthenticationFilter.class);}httpSecurity.addFilterBefore(rockAPIFilter, UsernamePasswordAuthenticationFilter.class);httpSecurity.addFilterBefore(rockTokenFilter, UsernamePasswordAuthenticationFilter.class);}@Beanpublic HttpMessageConverter responseBodyConverter {return new StringHttpMessageConverter(StandardCharsets.UTF_8);}@Beanpublic BCryptPasswordEncoder passwordEncoder{return new BCryptPasswordEncoder;}@Beanpublic AuthorizationManager sseAwareAuthorizationManager {return new MyAuthorizationManager;}/*** 长连接使用该配置时存在一个BUG(比如拉流场景,即长连接断开时会触发重新验证URI, 而重新验证时首次请求的验证成功的信息已被清除,从而导拒绝请求报错)* 为了被免这一问题,对于长连接使用单独的验证管理器*/private static class MyAuthorizationManager implements AuthorizationManager{@Deprecated@Overridepublic AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) {return null;}@Overridepublic AuthorizationResult authorize(Supplier authentication, RequestAuthorizationContext context) {HttpServletRequest request = context.getRequest;String uri = request.getRequestURI;log.info("MyAuthorizationManager URI:{}",uri);return new AuthorizationDecision(authentication.get != null &&authentication.get.isAuthenticated);//return AuthorizationManager.super.authorize(authentication, context);}}@Getter@Builderprivate static class ExtFilter{private int sort;private Filter filter;}}1、先说说URI访问权限:上面配置中除了admin、api开头的URI需要系统角色(USER、ADMIN,这个角色是SpringSecurity内置的)外,其它的URI资源全部放行,所以我在写Controller时,需要权限验证的接口全部会以这/admin/**或/api/**开头。再配合自定义的权限鉴权。
2、自定义的Filter上面有RockAPIFilter及RockTokenFilter还有一个检查扩展Filter的注解 @ExtBeforeFilter(上面代码134行)用于新项目有时需要一些额外的过滤器,像我最近的项目iBootCMS就用到了,用来检查客户网站的apiKey,通过apiKey隔离客户网站。
3、限于篇幅,这里RockAPIFilter及RockTokenFilter等其它的实现留以后有时间再写,先说说Controller权限部分的实现。
三、用户、角色与权限
使用spring的开发者都非常熟悉,一个项目无非就是写controller、dao与 service三部分,而权限控制主要是鉴别用户是否有请求controller权利,而我们也不可能每个项目都使用硬编码,使用硬编码对日常维护及扩展都不友好的。那么应该怎么做?直接使用spring原生注解+自定义注解,如
如上面的示例代码中除了 @MenuGroupPoint 分组(可以理解成controller分组或菜单分组)、@SaveLog及方法注解(需要记录日志的接口加上)外,我直接使用@RequestMapping、@PostMapping、@GetMapping这些controller必需要的注解,只要加上name参数后,系统启动时会将其加入到数据库menus表中。加上@Deprecated时会使其从数据表中移除(这是可能调整接口名称时才使用,调整合理后再移除@Deprecated)
@MenuGroupPoint
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface MenuGroupPoint {//分组ID,对应application.yml中的设置int value default 0;//用户类型(本系统将用户分为三个类型,0.营运管理端用户,1.C端用户,2.b端用户)int userType default 0;//该菜单项是否不可见boolean invisible default false;//模块划分int module default 0;//同级分组String parentClass default "";}数据表menu实做结构如下:
/*** 菜单项实体* @author: rock.chen* @date: 2021/2/15 12:46 下午
**/@Getter@Setter@Entity@Table(name = "sys_menu")@JsonIgnoreProperties(value = { "hibernateLazyInitializer"}, ignoreUnknown = true)public class Menu extends IIDModel implements ITreeModel {@Serialprivate static final long serialVersionUID = 8643763084655783766L;@Column(length = 50)private String name;@Column(length = 50)private String icon;@Column(length = 128, unique = true)private String uri;@Column(length = 128, unique = true)private String clsName;private boolean menu;//是否禁用,true禁用, false 可用private boolean disabled;//是否不可见,true不可见,false可见private boolean invisible;//可见方式是否为手动设置private boolean manualVisible;private int sortBy;private int groupId;/*** 0:营运端,1:C端, 2:B端*/private int userType;private int module;//允许修改disabled 状态private boolean allowEditDisabled;//启开分割线private boolean divider;//父类(子菜单归类)private String parentClass;@ManyToMany(fetch = FetchType.EAGER, mappedBy = "menus", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})private List roles;@JsonIgnore@ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)@JoinColumn(name = "parentId", referencedColumnName = "id")private Menu parent;@Transientprivate String nodeId;@Transientprivate List children;@Transientprivate String level;@Overridepublic void addChildren(ITreeModel sub){if(this.children == null){this.children = new ArrayList;}this.children.add(sub);}}
1.实现ApplicationListener接口,使应用在启动时做些事情。将Controller控制器注入到数据表menu中
/*** description:* Spring初始化完成后执行* @author: rock.chen* @date: 2021/2/17 12:29 上午
**/@Slf4j@Component@Transactionalpublic class ApplicationListenerImp extends RockApplicationListener {@Resourceprivate MenuGroupRepository menuGroupRepository;@Resourceprivate MenuRepository menuRepository;@Resourceprivate LoginUserService loginUserService;@Resourceprivate RoleRepository roleRepository;@Resourceprivate Config config;private IAppSpringAfterServiceInit appSpringAfterServiceInit;private IExtPermissionService extPermissionService;@Overridepublic void onApplicationEvent(@NonNull ContextRefreshedEvent contextRefreshedEvent) {log.info("Spring boot initialized and start initialize application data...");if(!config.isUriValidate){log.warn("系统已关闭URI权限验证功能...");}if(!config.isRegister){log.warn("系统已关闭管理端注册接口...");}if(config.isDebug){log.warn("系统处于测试状态...");}if(Objects.isNull(extPermissionService)){this.extPermissionService = (IExtPermissionService) BeanUtils.getBean(IExtPermissionService.class);}super.onApplicationEvent(contextRefreshedEvent);//初始化管理端的注册接口initRegisterApi;//初始化菜单分组(读取application.yml中设置的分组)initMenuGroup;//初始化菜单项的接口(即controller中的方法,注入的数据库menu)initMenuFunctionData;//初始化系统角色及创建一个默人的营运端管理员用户admininitRolesAndAdmin;//创建一个虚拟的系统用户(只要用户信息推送-发送者)initVirtualSystemUser;//其它扩展项-实现了IAppSpringAfterServiceInit接口的项appSpringAfterServiceInit;if(this.appSpringAfterServiceInit!=null){this.appSpringAfterServiceInit.init;}}private void appSpringAfterServiceInit{if(appSpringAfterServiceInit==null){appSpringAfterServiceInit = (IAppSpringAfterServiceInit) BeanUtils.getBean(IAppSpringAfterServiceInit.class);}//启动服务端的TcpServer服务ITCPServer itcpServer = (ITCPServer) BeanUtils.getBean(ITCPServer.class);if(itcpServer!=null){itcpServer.start;}//启动服务端连接MQTT服务器IMqttClientService mqttClientService = (IMqttClientService) BeanUtils.getBean(IMqttClientService.class);if(mqttClientService!=null){mqttClientService.connect;}//邮件服务MailConfig mailConfig = config.getMailConfig;if(mailConfig!=null){try {MailService.getInstance.initJavaMailSender(mailConfig);} catch (GeneralSecurityException e) {log.error("邮件服务初始化失败!", e);}}}private void initMenuGroup{this.menuGroupRepository.deleteAll;List menuGroupList = config.getMenuGroup;this.menuGroupRepository.saveAll(menuGroupList);}/*** 初始化系统菜单及功能URI*/private void initMenuFunctionData{Map controllerMap = BeanUtils.getBeansByAnnotation(RequestMapping.class);for (Map.Entry entry : controllerMap.entrySet) {Object controller = entry.getValue;if (!(controller instanceof IController)) {continue;}Deprecated deprecatedAnnotation = controller.getClass.getAnnotation(Deprecated.class);if(deprecatedAnnotation!=null){this.deleteParentMenu(controller);continue;}RequestMapping requestMappingAnnotation = controller.getClass.getAnnotation(RequestMapping.class);if (requestMappingAnnotation == null) {continue;}String parentName = requestMappingAnnotation.name;if (StringUtils.isBlank(parentName)) {this.deleteParentMenu(controller);continue;}MenuGroupPoint menuGroupPoint = controller.getClass.getAnnotation(MenuGroupPoint.class);int groupId = 0;int userType = ILoginUser.USER_TYPE_MGT;int module = 0;boolean invisible = true;String parentClass = null;if(menuGroupPoint!=null){groupId = menuGroupPoint.value;userType = menuGroupPoint.userType;invisible = menuGroupPoint.invisible;module = menuGroupPoint.module;parentClass = menuGroupPoint.parentClass;}MenuGroup menuGroup = this.menuGroupRepository.findByGid(groupId);if(menuGroup == null){throw new ServiceException("菜单组配置错误!");}boolean allowEditDisabled = controller.getClass.getAnnotation(NonEditDisabled.class)==null; //指定是否能修改菜单Disabled 字段状态String className = controller.getClass.getName;Menu parentMenu = this.menuRepository.findByClsName(className);String parentPath = requestMappingAnnotation.value[0];if (parentMenu != null && parentMenu.getId != null) {parentMenu.setName(parentName);parentMenu.setMenu(true);parentMenu.setUri(parentPath);parentMenu.setGroupId(groupId);parentMenu.setUserType(userType);parentMenu.setModule(module);parentMenu.setAllowEditDisabled(allowEditDisabled);parentMenu.setParentClass(parentClass);if(!parentMenu.isManualVisible){parentMenu.setInvisible(invisible);}} else {parentMenu = this.menuRepository.findByUri(parentPath);if (parentMenu != null && parentMenu.getId != null) {parentMenu.setName(parentName);parentMenu.setMenu(true);parentMenu.setClsName(className);parentMenu.setGroupId(groupId);parentMenu.setUserType(userType);parentMenu.setModule(module);parentMenu.setAllowEditDisabled(allowEditDisabled);parentMenu.setParentClass(parentClass);if(!parentMenu.isManualVisible){parentMenu.setInvisible(invisible);}}else{parentMenu = saveMenu(parentName, className, parentPath, true, null,groupId, userType, module, allowEditDisabled, invisible, parentClass);}}Method methods = controller.getClass.getMethods;for (Method method : methods) {Deprecated deprecated = method.getAnnotation(Deprecated.class);if(deprecated!=null || !config.isUriValidate){deleteFunction(className + "." + method.getName);continue;}NonEditDisabled nonEditDisabled1 = method.getAnnotation(NonEditDisabled.class);boolean allowEdit = nonEditDisabled1 == null;RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);if (requestMapping != null) {if(StringUtils.isNotBlank(requestMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, requestMapping.name,parentPath + requestMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}continue;}GetMapping getMapping = method.getAnnotation(GetMapping.class);if (getMapping != null) {if(StringUtils.isNotBlank(getMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, getMapping.name, parentPath + getMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}continue;}PostMapping postMapping = method.getAnnotation(PostMapping.class);if (postMapping != null) {if(StringUtils.isNotBlank(postMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, postMapping.name, parentPath + postMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}continue;}PutMapping putMapping = method.getAnnotation(PutMapping.class);if (putMapping != null) {if(StringUtils.isNotBlank(putMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, putMapping.name, parentPath + putMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}continue;}PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);if (patchMapping != null) {if(StringUtils.isNotBlank(patchMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, patchMapping.name, parentPath + patchMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}continue;}DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);if (deleteMapping != null) {if(StringUtils.isNotBlank(deleteMapping.name) && config.isUriValidate){createFunction(className, parentMenu, method, deleteMapping.name, parentPath + deleteMapping.value[0], groupId, userType, module, allowEdit);}else{deleteFunction(className + "." + method.getName);}}}}}private void deleteParentMenu(Object controller) {String className = controller.getClass.getName;Menu parentMenu = this.menuRepository.findByClsName(className);if (parentMenu != null && parentMenu.getId != null) {this.menuRepository.updateParentNull(parentMenu.getUri);this.menuRepository.deleteUriLike(parentMenu.getUri);if(Objects.nonNull(this.extPermissionService)){this.extPermissionService.onDeleteMenu(parentMenu);}}}private void deleteFunction(String funName){Menu funMenu = this.menuRepository.findByClsName(funName);if (funMenu != null && funMenu.getId != null) {Collection roles = funMenu.getRoles;if(Objects.nonNull(roles)){roles.forEach(role -> role.getMenus.removeIf(m->m.getId.equals(funMenu.getId)));roles.clear;}this.menuRepository.delete(funMenu);if(Objects.nonNull(this.extPermissionService)){this.extPermissionService.onDeleteMenu(funMenu);}}}private void createFunction(String className, Menu parentMenu, Method method, String name,String uri, int groupId, int userType, int module, boolean allowEditDisabled) {String functionName = className + "." + method.getName;Menu funMenu = this.menuRepository.findByClsName(functionName);if (funMenu != null && funMenu.getId != null) {funMenu.setName(name);funMenu.setMenu(false);funMenu.setUri(uri);funMenu.setParent(parentMenu);funMenu.setGroupId(groupId);funMenu.setUserType(userType);funMenu.setModule(module);funMenu.setAllowEditDisabled(allowEditDisabled);} else {saveMenu(name, functionName, uri, false,parentMenu, groupId, userType, module, allowEditDisabled, true, null);}}private Menu saveMenu(String menuName, String className, String uri,boolean isMenu, Menu parent, int groupId, int userType, int module,boolean allowEditDisabled, boolean invisible, String parentClass) {Menu menu = new Menu;menu.setMenu(isMenu);menu.setName(menuName);menu.setUri(uri);menu.setClsName(className);menu.setGroupId(groupId);menu.setUserType(userType);menu.setModule(module);menu.setAllowEditDisabled(allowEditDisabled);menu.setInvisible(invisible);menu.setParentClass(parentClass);if(parent!=null && parent.getId!=null){menu.setParent(parent);}return this.menuRepository.save(menu);}private void initRolesAndAdmin{String adminRoleName = WebSecurityConfig.ROLE_PREFIX + WebSecurityConfig.ADMIN_ROLE;Role adminRole = this.roleRepository.findRoleByNameAndRoleType(adminRoleName, Role.ROLE_SYS);if(adminRole==null){adminRole = new Role;adminRole.setName(adminRoleName);adminRole.setRoleType(Role.ROLE_SYS);this.roleRepository.save(adminRole);}String userRoleName = WebSecurityConfig.ROLE_PREFIX + WebSecurityConfig.USER_ROLE;Role userRole = this.roleRepository.findRoleByNameAndRoleType(userRoleName, Role.ROLE_SYS);if(userRole == null){userRole = new Role;userRole.setName(userRoleName);adminRole.setRoleType(Role.ROLE_SYS);this.roleRepository.save(userRole);}this.loginUserService.setToCRoleList(userRole);initAdminUser(adminRole);}/*** 初始化系统管理员*/private void initAdminUser(Role role){LoginUser loginUser = this.loginUserService.findOneByUserName(Config.ADMIN_USER_NAME);if(loginUser == null){loginUser = new LoginUser;loginUser.setUsername(Config.ADMIN_USER_NAME);loginUser.setUserType(LoginUser.USER_TYPE_MGT);loginUser.setUserFrom(LoginUser.USER_TYPE_MGT);loginUser.setAccountType(ILoginUser.ACCOUNT_TYPE_GENERAL);loginUser.setSuperAdmin(true);loginUser.setName(Config.ADMIN_USER_NAME);loginUser.setPassword(LoginUserService.passwordEncoder.encode(Config.ADMIN_USER_PASSWORD));loginUser.setAccountNonExpired(true);loginUser.setAccountNonLocked(true);loginUser.setEnabled(true);loginUser.setCredentialsNonExpired(true);List roles = new ArrayList;roles.add(role);loginUser.setAuthorities(roles);loginUser.setCreateTime(Calendar.getInstance.getTime);this.loginUserService.save(loginUser);if(role.getLoginUsers == null){role.setLoginUsers(new ArrayList);}role.getLoginUsers.add(loginUser);}}/*** 初始化一个虚拟的系统用户*/private void initVirtualSystemUser{LoginUser loginUser = this.loginUserService.findOneByUserName(ILoginUser.VIRTUAL_SYS_USER_NAME);if(loginUser == null){loginUser = new LoginUser;loginUser.setUsername(ILoginUser.VIRTUAL_SYS_USER_NAME);loginUser.setUserType(LoginUser.USER_VIRTUAL_SYS);loginUser.setUserFrom(LoginUser.USER_TYPE_MGT);loginUser.setAccountType(ILoginUser.ACCOUNT_TYPE_GENERAL);loginUser.setName("系统用户");loginUser.setSuperAdmin(false);loginUser.setPassword(LoginUserService.passwordEncoder.encode(CommonUtils.getRandomStr(8, 10).get(0)));loginUser.setAccountNonExpired(false);loginUser.setAccountNonLocked(false);loginUser.setEnabled(false);loginUser.setCredentialsNonExpired(false);loginUser.setCreateTime(Calendar.getInstance.getTime);this.loginUserService.save(loginUser);}UserContextHolder.putVsUser(loginUser);}private void initRegisterApi{if(config.isRegister) return;log.info("禁止管理端注册接口...");Map controllerMap = BeanUtils.getBeansByAnnotation(RequestMapping.class);for (Map.Entry entry : controllerMap.entrySet) {Object controller = entry.getValue;if (!(controller instanceof IController)) {continue;}RequestMapping requestMappingAnnotation = controller.getClass.getAnnotation(RequestMapping.class);if (requestMappingAnnotation == null) {continue;}String parentPath = requestMappingAnnotation.value[0];Method methods = controller.getClass.getDeclaredMethods;for (Method method : methods) {RegisterApi registerApi = method.getAnnotation(RegisterApi.class);if(registerApi == null) continue;RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);if(requestMapping != null){String uri = parentPath + requestMapping.value[0];Config.REGISTER_URI_LIST.add(uri);continue;}GetMapping getMapping = method.getAnnotation(GetMapping.class);if(getMapping!=null){String uri = parentPath + getMapping.value[0];Config.REGISTER_URI_LIST.add(uri);continue;}PostMapping postMapping = method.getAnnotation(PostMapping.class);if(postMapping!=null){String uri = parentPath + postMapping.value[0];Config.REGISTER_URI_LIST.add(uri);continue;}PutMapping putMapping = method.getAnnotation(PutMapping.class);if(putMapping!=null){String uri = parentPath + putMapping.value[0];Config.REGISTER_URI_LIST.add(uri);continue;}PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);if(patchMapping!=null){String uri = parentPath + patchMapping.value[0];Config.REGISTER_URI_LIST.add(uri);}}}}}
2.菜单组配置application.yml中,格式如下
menu-group:- gid: 0name: 设置icon: settinguser_type: 0module : 0- gid: 1name: 团队icon: teamuser_type: 0module : 0- gid: 2name: 网站icon: webuser_type: 0module: 1#B端- gid: 10name: 设置icon: settinguser_type: 2module: 1- gid: 12name: 内容icon: contentuser_type: 2module: 1- gid: 13name: 用户icon: memberuser_type: 2module: 1- gid: 14name: 评论icon: commentuser_type: 2module: 1经过上面的方法,Controller就方便的注入到数据库表中了,配合role,menu,rolemenu,loginUser表的关系即可完成相关的权限控制。由于时间问题本集先讲到这里,下集再见~
iboot.fun
后台权限管理
来源:开发软件的石头哥