这些年,这些事(权限中心整理)之二

作者: 撸二行代码 | 来源:发表于2017-10-31 20:53 被阅读285次

前言

   任意一个系统,权限系统都属于“非功能”模块,你要做的细那只是“锦上添花”而不是“雪中送炭”。所以我们往往都是参考之前的项目然后改来改去。最近静下心来做了一个通用的权限中心系统(因为最近一直做微服务,并且做的都是基础服务,所以单独做了权限中心)。

章节

  • 系统架构
  • 系统优势
  • 数据结构
  • 权限资源
  • 权限管理中心
  • 权限二次开发
  • 接入权限服务

系统架构

技术框架
  • 开发语言:Java
  • 数据存储:MongoDB和Redis
  • 前台框架:DWZ+thymeleaf
  • 后台框架:基于Spring Boot二次封装
Spring Boot二次封装业务框架
  • 在线API:Swagger
在线Rest API
  • 开发规约:阿里开发规约
  • 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);
    }
}
整体架构
image.png
  • 实际情况可能不会出现业务系统之间访问权限中心,对于接口通常我们是在Zuul中处理
  • 页面登录和获得权限通常会使用SSO做担当登录然后使用拦截器或者Filter做权限验证。

系统优势

  • 采用最新的Spring Boot版本进行开发,可以最简单部署和发布
  • 采用MongoDB和Redis进行数据持久化(权限数据是树形结构数据)
  • 提供在线API测试(Swagger)
  • 本权限系统只是对于权限和资源进行管理,对于用户可以独立开发(后期可以将用户作为可选择模块进行自由扩展)
  • 本系统提供Rest接口提供给任何第三方和语言进行调用
  • 权限管理时,可通过系统配置来满足不同的需求
  • 系统拥有导入、导出功能,部署方便
  • 完整的API和开发文档可以自由进行二次开发,并且该系统版权使用MIT协议。

数据结构

整体资源数据结构

根据以上结构可以很明确的看出,对于页面资源其实就是一个树形结构的数据。所以在数据存储的介质上面选择了MongoDB。

整体数据结构UML

权限资源

权限整体资源 权限分配 权限访问资源
  • 功能访问权限:是指拥有菜单的权限。菜单为树形结构,可调整菜单的层级和顺序
  • 操作访问权限 :是指页面的操作点,可定义N个操作点。操作代码的定义与权限的使用者为约定关系。通过操作代码来或者自定义标签判断是否拥有该菜单某个功能的操作权限。
  • 接口访问权限:是指每一个接口第三方使用者可以访问哪些现有的接口URL

权限管理中心

权限管理中心 导入系统数据模板 导入模块数据模板
  • 导入可以通过页面和Junit将数据到MongoDB中
Swagger返回数据
  • 根据Swagger可以一次性将数据导入,然后通过Junit将数据导入到MongoDB中,避免数据遗漏
JavaBean定义文档
  • 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 <T> 实体对象
 */
public interface BaseService<T extends BaseEntity> {
    /**
     * 默认分页页数: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<T> findAll() {
        logger.info("获得所有数据!");
        return this.getMongoRepository().findAll();
    }

    /**
     * 更加业务参数查询条件,进行查询
     *
     * @param param 业务参数
     * @param clazz 返回数据类型
     * @param <P>   业务参数类型
     * @return 查询返回结果集
     */
    default <P> List<T> findList(P param, Class<T> clazz) {
        Query query = new Query();
        //设置查询业务查询条件
        List<Criteria> 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 <P, R extends BaseResult> PageResult<R> findPageResult(P param, PageParam pageParam, Class<R> clazz) {
        PageResult<R> 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<Criteria> 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<T> dataList = this.getMongoTemplate().find(query, this.getEntityClass());
        List<R> 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<T, Serializable> getMongoRepository();

    /**
     * 自定义分页使用Mongo Template进行分页查询
     *
     * @return MongoTemplate
     */
    default MongoTemplate getMongoTemplate() {
        return null;
    }

    default Class<T> 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 <T> 返回Entity
 * @author jackjiang 
 */
public interface BasePageableService<T extends BaseEntity> extends BaseService<T>{
    /**
     * 分页查询返回List数据
     * @param param 查询参数
     * @param pageParam 分页参数
     * @param <P> 分页参数类型
     * @return List数据
     */
    default <P> List<T> 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<T> dataList = this.getMongoTemplate().find(query, this.getEntityClass());
        return dataList;
    }

    /**
     * 数据Count
     * @param param 查询参数
     * @param <P> 参数类型
     * @return 数据Count
     */
    default <P> long totalCount(P param) {
        Query query = this.createQuery(param);
        long totalCount = this.getMongoTemplate().count(query, this.getEntityClass());
        return totalCount;
    }

    /**
     * 拼接查询条件
     * @param param 查询条件
     * @param <P> 参数类型
     * @return 查询条件
     */
    default <P> Query createQuery(P param){
        Query query = new Query();
        //设置查询业务查询条件
        List<Criteria> 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 <C> 分页条件
 */
public interface BaseListController<C extends BasePageableCondition, R extends BaseResult> 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<R> 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 <C> 保存条件
 */
public interface BaseSaveController<C extends BaseCondition> 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<SystemPageableCondition, SystemResult>, BaseSaveController<SystemCondition> {
    /**
     * 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 日志配置

接入权限服务

完整的API列表 完整API测试

PS:
1.MIT协议说明

Copyright <YEAR> <COPYRIGHT HOLDER>

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和做一个前台框架使用使用了一个比较来的框架,但是对于权限中心而言已经完全够用了,我一直对自己不擅长的事情都是信奉适用注意者。

相关文章

  • 这些年,这些事(权限中心整理)之二

    前言 章节 系统架构 系统优势 数据结构 权限资源 权限管理中心 权限二次开发 接入权限服务 系统架构 技术框架 ...

  • 这些年,这些事(技术整理)之一

    序 忙忙碌碌,忙忙碌碌接近十年的工作经验了,不知何时起开始有感慨的感觉了。从一个小白变成大白。最近一段时间...

  • 这些年,这些事

    从此以后,就真的没关系了。 我从那个新开不久的咖啡店走出来,扯扯让浑身上下都难受的齐膝连衣短裙,看着左下角...

  • 这些年,这些事

    很久以前就有一种想写东西的冲动,只是感觉一直是级别不够,没东西可写,或者说是不会写东西,但但当有时候心血来潮的...

  • 胡言

    好好整理自己这些年来的思绪,好好完善这些年来的想法! 梳理梳理想做的事,梳理梳理能完成的任务 列一些想去做,尚因各...

  • 复盘日记18

    每日复盘(18/100) 【第一件事】: 与先生一起整理和总结了这些年来的家庭经济情况! 【第二件事】: 整理与反...

  • 这些年这些事01

    总是会有那么些时候,去掉赋予的意义,面对现实。现实就是旁边同事月薪高我5K,年薪高我10W;现实就是我越来越老,找...

  • 这些年,这些人,这些事

    最近在听罗胖的2017跨年演讲,历时4小时,调侃中不失中肯,总结了2016年的诸多大事、热点事,展望了2017年。...

  • 这些年这些人这些事

    序: 很久以前就想写点东西,可只是纯粹的“想”写,至于写什么,怎么写,什么时候写,都没有涉及到过,现在终于有时间,...

  • 这些年,那些事

    还未来及反应, 青春的尾巴就快溜走。 狗日的中年。

网友评论

    本文标题:这些年,这些事(权限中心整理)之二

    本文链接:https://www.haomeiwen.com/subject/fiuppxtx.html