前言
任意一个系统,权限系统都属于“非功能”模块,你要做的细那只是“锦上添花”而不是“雪中送炭”。所以我们往往都是参考之前的项目然后改来改去。最近静下心来做了一个通用的权限中心系统(因为最近一直做微服务,并且做的都是基础服务,所以单独做了权限中心)。
章节
- 系统架构
- 系统优势
- 数据结构
- 权限资源
- 权限管理中心
- 权限二次开发
- 接入权限服务
系统架构
技术框架
- 开发语言:Java
- 数据存储:MongoDB和Redis
- 前台框架:DWZ+thymeleaf
- 后台框架:基于Spring Boot二次封装
- 在线API:Swagger
- 开发规约:阿里开发规约
- UML图:PlantUML
- 白盒测试: Junit
package com.micro.service.basic.service.authority.services;
import com.micro.service.basic.service.authority.BaseTest;
import com.micro.service.basic.service.authority.domain.condition.SystemCondition;
import com.micro.service.basic.service.authority.domain.entity.System;
import com.micro.service.doamin.param.PageParam;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class SystemServiceTest extends BaseTest{
@Autowired
private SystemService systemService;
@Test
public void testSave(){
System system = new System();
system.setSystemCode("bbb");
system.setSystemName("aaaaa");
system.setSystemIcon("aa");
system.setSystemUrl("aaaa");
this.systemService.save(system);
}
@Test
public void testFindPageResult(){
SystemCondition systemCondition = new SystemCondition();
systemCondition.setSystemCode("bbb");
PageParam pageParam = new PageParam();
pageParam.setCurrentPage(1);
this.systemService.findPageResult(systemCondition,pageParam,null);
}
}
整体架构
- 实际情况可能不会出现业务系统之间访问权限中心,对于接口通常我们是在Zuul中处理
- 页面登录和获得权限通常会使用SSO做担当登录然后使用拦截器或者Filter做权限验证。
系统优势
- 采用最新的Spring Boot版本进行开发,可以最简单部署和发布
- 采用MongoDB和Redis进行数据持久化(权限数据是树形结构数据)
- 提供在线API测试(Swagger)
- 本权限系统只是对于权限和资源进行管理,对于用户可以独立开发(后期可以将用户作为可选择模块进行自由扩展)
- 本系统提供Rest接口提供给任何第三方和语言进行调用
- 权限管理时,可通过系统配置来满足不同的需求
- 系统拥有导入、导出功能,部署方便
- 完整的API和开发文档可以自由进行二次开发,并且该系统版权使用MIT协议。
数据结构
根据以上结构可以很明确的看出,对于页面资源其实就是一个树形结构的数据。所以在数据存储的介质上面选择了MongoDB。
权限资源
- 功能访问权限:是指拥有菜单的权限。菜单为树形结构,可调整菜单的层级和顺序
- 操作访问权限 :是指页面的操作点,可定义N个操作点。操作代码的定义与权限的使用者为约定关系。通过操作代码来或者自定义标签判断是否拥有该菜单某个功能的操作权限。
- 接口访问权限:是指每一个接口第三方使用者可以访问哪些现有的接口URL
权限管理中心
- 导入可以通过页面和Junit将数据到MongoDB中
- 根据Swagger可以一次性将数据导入,然后通过Junit将数据导入到MongoDB中,避免数据遗漏
- JavaBean定义文档,并且生成对应的JavaBean对象
- 根据Swagger生成的API Excel文档
权限二次开发
- 完整的注释
package com.micro.service.basic.service.authority.services;
import com.micro.service.doamin.constant.NumberConstant;
import com.micro.service.doamin.exception.BusinessException;
import com.micro.service.doamin.param.PageParam;
import com.micro.service.doamin.result.BaseResult;
import com.micro.service.doamin.result.PageResult;
import com.micro.service.mongodb.condition.CriteriaUtils;
import com.micro.service.mongodb.condition.annotation.Condition;
import com.micro.service.mongodb.condition.enums.Where;
import com.micro.service.mongodb.domain.entity.BaseEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.util.ReflectionUtils;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* MongoDB Base Service.
*
* @param 实体对象
*/
public interface BaseService {
/**
* 默认分页页数:10
*/
int DEFAULT_PAGE_SIZE = NumberConstant.Int.INT_TEN;
/**
* 日志
*/
Logger logger = LoggerFactory.getLogger(BaseService.class);
/**
* 插入操作
*
* @param entity 实体对象
* @return 插入成功返回DB中的实体对象结果
*/
default T save(T entity) {
//设置主键,主键为UUID
return this.save(this.getId(), entity);
}
default T save(String id, T entity) {
entity.setId(id);
//检查业务Code是否存在
if (!this.checkBusinessCode(entity)) {
throw new BusinessException();
}
return this.getMongoRepository().save(entity);
}
/**
* 根据主键删除DB中数据
*
* @param serializable 主键
*/
default void remove(Serializable serializable) {
logger.info("删除操作,主键为:{}", serializable);
this.getMongoRepository().delete(serializable);
}
/**
* 删除所有数据
*/
default void removeAll() {
logger.info("删除所有数据!");
this.getMongoRepository().deleteAll();
}
/**
* 根据主键获得当前数据
*
* @param serializable 主键
* @return 数据库返回的数据对象
*/
default T findOne(Serializable serializable) {
logger.info("通过主键获得唯一数据,主键为:{}", serializable);
return this.getMongoRepository().findOne(serializable);
}
/**
* 获得所有DB中数据
*
* @return 所有数据集合
*/
default List findAll() {
logger.info("获得所有数据!");
return this.getMongoRepository().findAll();
}
/**
* 更加业务参数查询条件,进行查询
*
* @param param 业务参数
* @param clazz 返回数据类型
* @param 业务参数类型
* @return 查询返回结果集
*/
default
List findList(P param, Class clazz) {
Query query = new Query();
//设置查询业务查询条件
List criteriaList = CriteriaUtils.createCriteriaList(param);
for (Criteria criteria : criteriaList) {
query.addCriteria(criteria);
}
return this.getMongoTemplate().find(query, clazz);
}
/**
* 页面分页查询
*
* @param param 查询业务条件
* @param pageParam 分页参数
* @param clazz 返回值类型
* @return 分页数据结果
* @parm pageParam 分页查询条件
*/
default PageResult findPageResult(P param, PageParam pageParam, Class clazz) {
PageResult result = new PageResult<>();
int currentPage = pageParam.getCurrentPage();
result.setCurrentPage(currentPage);
int pageSize = pageParam.getPageSize();
//页面传入的分页,一页显示页数小于等于0,则使用默认10条数据一页进行分页
if (pageSize <= NumberConstant.Int.INT_ZERO) {
pageSize = DEFAULT_PAGE_SIZE;
}
//Spring Data Jpa分页从0开始,所有传入页数必须减1
PageRequest pageable = new PageRequest(currentPage - NumberConstant.Int.INT_ONE, pageSize);
Query query = new Query();
//设置查询业务查询条件
List criteriaList = CriteriaUtils.createCriteriaList(param);
for (Criteria criteria : criteriaList) {
query.addCriteria(criteria);
}
//设置分页参数
query.with(pageable);
//获得所有行数
long totalCount = this.getMongoTemplate().count(query, this.getEntityClass());
result.setTotalCount(totalCount);
List dataList = this.getMongoTemplate().find(query, this.getEntityClass());
List items = new ArrayList<>();
com.micro.service.core.utils.CollectionUtils.copyCollection(dataList, items, clazz);
result.setItems(items);
return result;
}
/**
* 获得MongoDB Id值
*
* @return MongoDB ID
*/
default String getId() {
String uuid = UUID.randomUUID().toString();
return uuid;
}
/**
* 检查业务Code是否存在
*
* @param entity 实体对象
* @return 是否存在
*/
default boolean checkBusinessCode(T entity) {
return Boolean.TRUE;
}
/**
* Spring data 包装的Repository
*
* @return MongoRepository
*/
MongoRepository getMongoRepository();
/**
* 自定义分页使用Mongo Template进行分页查询
*
* @return MongoTemplate
*/
default MongoTemplate getMongoTemplate() {
return null;
}
default Class getEntityClass() {
return null;
}
default T getEntity() {
try {
return this.getEntityClass().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据业务Code获得唯一值
*
* @param businessCode 业务Code
* @return 数据库对象
*/
default T findOneByBusinessCode(String businessCode) {
return null;
}
}
package com.micro.service.basic.service.authority.services;
import com.micro.service.doamin.constant.NumberConstant;
import com.micro.service.doamin.param.PageParam;
import com.micro.service.mongodb.condition.CriteriaUtils;
import com.micro.service.mongodb.domain.entity.BaseEntity;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import java.util.List;
/**
* 分页操作
* @param 返回Entity
* @author jackjiang
*/
public interface BasePageableService extends BaseService{
/**
* 分页查询返回List数据
* @param param 查询参数
* @param pageParam 分页参数
* @param 分页参数类型
* @return List数据
*/
default
List findPageList(P param, PageParam pageParam) {
int currentPage = pageParam.getCurrentPage();
int pageSize = pageParam.getPageSize();
//页面传入的分页,一页显示页数小于等于0,则使用默认10条数据一页进行分页
if (pageSize <= NumberConstant.Int.INT_ZERO) {
pageSize = DEFAULT_PAGE_SIZE;
}
//Spring Data Jpa分页从0开始,所有传入页数必须减1
PageRequest pageable = new PageRequest(currentPage - NumberConstant.Int.INT_ONE, pageSize);
Query query = this.createQuery(param);
//设置分页参数
query.with(pageable);
List dataList = this.getMongoTemplate().find(query, this.getEntityClass());
return dataList;
}
/**
* 数据Count
* @param param 查询参数
* @param 参数类型
* @return 数据Count
*/
default
long totalCount(P param) {
Query query = this.createQuery(param);
long totalCount = this.getMongoTemplate().count(query, this.getEntityClass());
return totalCount;
}
/**
* 拼接查询条件
* @param param 查询条件
* @param
参数类型
* @return 查询条件
*/
default
Query createQuery(P param){
Query query = new Query();
//设置查询业务查询条件
List criteriaList = CriteriaUtils.createCriteriaList(param);
for (Criteria criteria : criteriaList) {
query.addCriteria(criteria);
}
return query;
}
}
package com.micro.service.basic.service.authority.base.controller;
import com.micro.service.basic.service.authority.logic.BaseLogic;
/**
* Base Controller.
*
* @author jiang_nan
*/
public interface BaseController {
/**
* Get Base Logic
*
* @return BaseLogic
*/
BaseLogic getBaseLogic();
}
package com.micro.service.basic.service.authority.base.controller;
import com.micro.service.doamin.condition.BasePageableCondition;
import com.micro.service.doamin.result.BaseResult;
import com.micro.service.doamin.result.PageResult;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 列表页面基础Controller。
*
* @param 分页条件
*/
public interface BaseListController extends BaseController {
/**
* Get方式分页页面
*
* @param modelMap Module Map
* @return 分页页面
*/
@RequestMapping(value = "list", method = RequestMethod.GET)
default String list(ModelMap modelMap) {
this.initializeListPageParam(modelMap);
return this.list(this.getPageableCondition(), modelMap);
}
/**
* Post请求分页页面
*
* @param pageableCondition 查询条件
* @param modelMap Module Map
* @return 分页页面
*/
@RequestMapping(value = "list", method = RequestMethod.POST)
default String list(C pageableCondition, ModelMap modelMap) {
PageResult result = this.getBaseLogic().findPageResult(pageableCondition);
modelMap.put("result", result);
return this.listPageUrl();
}
/**
* 初始化List页面参数
* @param modelMap Model Map
*/
default void initializeListPageParam(ModelMap modelMap){
}
/**
* List页面Url
*
* @return List页面Url
*/
String listPageUrl();
/**
* 分页查询条件
*
* @return 分页查询条件
*/
C getPageableCondition();
}
package com.micro.service.basic.service.authority.base.controller;
import com.micro.service.doamin.condition.BaseCondition;
import com.micro.service.doamin.result.WebPageResult;
import com.micro.service.redis.NumberGenerateComponent;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 插入操作基础Controller
*
* @param 保存条件
*/
public interface BaseSaveController extends BaseController {
/**
* 保存操作,默认提供生成Number Code。
*
* @param modelMap Model Map
* @return 保存页面
*/
@RequestMapping(value = "save", method = RequestMethod.GET)
default String save(ModelMap modelMap) {
modelMap.put("id", NumberGenerateComponent.generateNumber());
return savePageUrl();
}
/**
* 保存操作
*
* @param condition 保存条件
* @param modelMap Model Map
* @return JSON返回值
*/
@RequestMapping(value = "save", method = RequestMethod.POST)
default @ResponseBody
WebPageResult save(C condition, ModelMap modelMap) {
this.getBaseLogic().save(condition);
return new WebPageResult();
}
/**
* 保存页面
*
* @return 保存页面
*/
String savePageUrl();
}
- BaseService 接口,因为使用JDK1.8所以可以使用default,实现一个类多继承
- BasePageableService专门为分页特殊业务处理的基类
- BaseController接口,所有的Controller必须继承BaseController
- BaseListController接口,列表页面必须继承BaseListController将列表页面Controller简单化
- BaseSaveController接口,编辑页面必须继承BaseSaveController将View页面Controller简单化
package com.micro.service.basic.service.authority.controller;
import com.micro.service.basic.service.authority.base.controller.BaseListController;
import com.micro.service.basic.service.authority.base.controller.BaseSaveController;
import com.micro.service.basic.service.authority.domain.condition.SystemCondition;
import com.micro.service.basic.service.authority.domain.condition.SystemPageableCondition;
import com.micro.service.basic.service.authority.domain.result.SystemResult;
import com.micro.service.basic.service.authority.logic.BaseLogic;
import com.micro.service.basic.service.authority.logic.SystemLogic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 系统操作Controller
*
* @author jiang_nan
*/
@Controller
@RequestMapping(value = "system")
public class SystemController implements BaseListController, BaseSaveController {
/**
* logger
*/
private static Logger logger = LoggerFactory.getLogger(SystemController.class);
/**
* 系统List页面Url
*/
private final static String LIST_PAGE_URL = "/system/list";
/**
* 系统Add页面Url
*/
private final static String SAVE_PAGE_URL = "/system/add";
@Autowired
private SystemLogic systemLogic;
@RequestMapping(value = "remove/{systemId}", method = {RequestMethod.POST, RequestMethod.GET})
public String remove(@PathVariable("systemId") String systemId, ModelMap modelMap) {
this.systemLogic.remove(systemId);
return this.listPageUrl();
}
@Override
public BaseLogic getBaseLogic() {
return this.systemLogic;
}
@Override
public String listPageUrl() {
return LIST_PAGE_URL;
}
@Override
public SystemPageableCondition getPageableCondition() {
return new SystemPageableCondition();
}
@Override
public String savePageUrl() {
return SAVE_PAGE_URL;
}
}
系统管理模块实现代码。
- base:业务需要的基类定义
- base.controller: Controller类的一些列基类
- config:相关配置
- controller:业务页面相关Controller
- domain:一些参数JavaBean
- domain.condition:页面传入的参数JavaBean
- entity:MongoDB表对应的JavaBean
- param:Rest接口参数JavaBean
- result:页面和接口返回数据JavaBean
- logic:业务处理
- properties:读取配置参数
- repository:单表JPA Repository
- rest:Rest接口
- services:单表Service接口
- services.impl:单表Service接口实现
- Application:启动类
- config:配置文件
- i18n:国际化配置文件
- static:页面静态资源
- templates:静态文件
- application.yml:基础配置
- logback.xml 日志配置
接入权限服务
PS:
1.MIT协议说明
Copyright
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2.该权限将在11中旬加代码全部贡献出来。
3.很不喜欢写CSS和做一个前台框架使用使用了一个比较来的框架,但是对于权限中心而言已经完全够用了,我一直对自己不擅长的事情都是信奉适用注意者。