权限管理个人笔记之后端实现(二)


集成Druid数据源

   首先添加依赖:



com.alibaba
druid-spring-boot-starter
1.1.10


  添加配置:
    application.yml
      
spring:
datasource:
name: druidDataSource
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/qxgl?useUnicode=true&zeroDateBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password: root
#配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,wall用于防火墙
filters: stat,wall,log4j,config
#最大连接数
max-active: 100
#初始化大小
initial-size: 1
#获取连接等待超时时间
max-wait: 60000
#最小连接数
min-idle: 1
#间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
#一个连接在连接池中最小的生存时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-while-idle: true
test-on-return: false
test-on-borrow: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20

  DruidDataSourceProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DruidDataSourceProperties {
//jdbc
private String driverClassName;
private String url;
private String username;
private String password;
//jdbc connection pool
private int initialSize;
private int minIdle;
private int maxActive = 100;
private long maxWait;
private long timeBetweenEvictionRunsMillis;
private long MinEvictableIdleTimeMillis;
private String ValidationQuery;
private boolean testWhileIdle;
private boolean testOnReturn;
private boolean testOnBorrow;
private boolean poolPreparedStatements;
private int maxPoolPreparedStatementPerConnectionSize;

//filter
private String filters;

public long getMinEvictableIdleTimeMillis() {
return MinEvictableIdleTimeMillis;
}

public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
MinEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}

public boolean isTestOnBorrow() {
return testOnBorrow;
}

public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}

public String getDriverClassName() {
return driverClassName;
}

public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getInitialSize() {
return initialSize;
}

public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}

public int getMinIdle() {
return minIdle;
}

public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}

public int getMaxActive() {
return maxActive;
}

public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}

public long getMaxWait() {
return maxWait;
}

public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}

public long getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}

public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}



public String getValidationQuery() {
return ValidationQuery;
}

public void setValidationQuery(String validationQuery) {
ValidationQuery = validationQuery;
}

public boolean isTestWhileIdle() {
return testWhileIdle;
}

public void setTestWhileIdle(boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}

public boolean isTestOnReturn() {
return testOnReturn;
}

public void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}

public boolean isPoolPreparedStatements() {
return poolPreparedStatements;
}

public void setPoolPreparedStatements(boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}

public int getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
}

public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}

public String getFilters() {
return filters;
}

public void setFilters(String filters) {
this.filters = filters;
}
}

  配置Servlet和Filter
    在config包下新建一个DruidConfig配置类,主要是注入属性和配置连接池相关的配置。如:黑名单白名单、监控管理后台登录账号密码等:
      DruidConfig.java
    
@Configuration
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DruidConfig {
@Autowired
private DruidDataSourceProperties properties;

@Bean
@ConditionalOnMissingBean
public DataSource druidDataSource(){
//中间省略属性设置
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getDriverClassName());
druidDataSource.setUrl(properties.getUrl());
druidDataSource.setUsername(properties.getUsername());
druidDataSource.setPassword(properties.getPassword());
druidDataSource.setInitialSize(properties.getInitialSize());
druidDataSource.setMinIdle(properties.getMinIdle());
druidDataSource.setMaxActive(properties.getMaxActive());
druidDataSource.setMaxWait(properties.getMaxWait());
druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(properties.getValidationQuery());
druidDataSource.setTestWhileIdle(properties.isTestWhileIdle());
druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());
druidDataSource.setTestOnReturn(properties.isTestOnReturn());
druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(properties.getMaxPoolPreparedStatementPerConnectionSize());

try {
druidDataSource.setFilters(properties.getFilters());
druidDataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
return druidDataSource;
}
/*
* 注册Servlet信息,配置监控视图
* */
@Bean
@ConditionalOnMissingBean
public ServletRegistrationBeandruidServlet(){
ServletRegistrationBeanservletServletRegistrationBean=new
ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//白名单
servletServletRegistrationBean.addInitParameter("allow","127.0.0.1,139.196.87.48");
//IP黑名单(如果共存时,deny优先于allow)
//如果满足deny的话提示:Sprry,you are not permitted to view this page.
servletServletRegistrationBean.addInitParameter("deny","192.168.1.119");
//登录查看信息的账号密码,用于登录Druid监控后台
servletServletRegistrationBean.addInitParameter("loginUsername","admin");
servletServletRegistrationBean.addInitParameter("loginPassword","admin");
//是否能够重置数据
servletServletRegistrationBean.addInitParameter("resetEnable","true");
return servletServletRegistrationBean;
}

/*注册Filter信息,监控拦截器*/
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBeanfilterFilterRegistrationBean(){
//省略具体注册逻辑
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}

编译启动会报一个错,没有log4j的依赖
  1.首先在pom.xml添加log4j依赖


log4j
log4j
1.2.17


2.添加log4j配置
在resource目录下,新建一个log4j参数文件
  log4j.properties
### set log levels ###
log4j.rootLogger = INFO,DEBUG, console, infoFile, errorFile ,debugfile,mail
LocationInfo=true

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout

log4j.appender.console.layout.ConversionPattern =[%d{yyyy-MM-dd HH:mm:ss,SSS}]-[%p]:%m %x %n

log4j.appender.infoFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.infoFile.Threshold = INFO
log4j.appender.infoFile.File = C:/logs/log
log4j.appender.infoFile.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.infoFile.Append=true
log4j.appender.infoFile.layout = org.apache.log4j.PatternLayout
log4j.appender.infoFile.layout.ConversionPattern =[%d{yyyy-MM-dd HH:mm:ss,SSS}]-[%p]:%m%x %n

log4j.appender.errorFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorFile.Threshold = ERROR
log4j.appender.errorFile.File = C:/logs/error
log4j.appender.errorFile.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.errorFile.Append=true
log4j.appender.errorFile.layout = org.apache.log4j.PatternLayout
log4j.appender.errorFile.layout.ConversionPattern =[%d{yyyy-MM-dd HH:mm:ss,SSS}]-[%p]:%m%x %n

log4j.appender.debugfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.debugfile.Threshold = DEBUG
log4j.appender.debugfile.File = C:/logs/debug
log4j.appender.debugfile.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.debugfile.Append=true
log4j.appender.debugfile.layout = org.apache.log4j.PatternLayout
log4j.appender.debugfile.layout.ConversionPattern =[%d{yyyy-MM-dd HH:mm:ss,SSS}]-[%p]:%m%x %n


  查看监控
启动成功之后http://localhost:8081/druid/login.html
里面有监控首页:用户名是Config配置的登陆账号和密码,还有数据源,SQL监控。SQL监控在接口调用成功之后,可以看见SQL监控的执行记录,可以查看和分析的SQL性能,方便进行数据库性能优化。

跨域解决方案


CORS实现:在config包下新建一个CORS配置类,实现WebMvcConfigurer接口
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**") //运行跨域访问的路径
.allowedOrigins("*")//运行跨域访问的源
.allowedMethods("POST","GET","PUT","OPTIONS","DELETE")//运行请求方法
.maxAge(168000)//预检间隔时间
.allowedHeaders("*")//允许头部设置
.allowCredentials(true);//是否发送cookie
}
}

业务功能实现

因为我们要采用的是微服务的架构,虽然现在我们只有一个工程,但是随着项目越来越大,代码的可重用性和可维护性就会变得越来越难,所以尽早对工程结构进行合理的规划,对项目前期开发和后期维护都是很重要的。

  1.mango-admin   后台管理模块,包含用户、角色、菜单管理等

     首先将原来的项目更名为mango-admin

    1.先删除mango工程,不要勾选删除的。

    2.找到文件的位置,修改工程名

    3.编辑pom.xml将需要替换的mango字符串替换为mango-admin

   4.  在把mango-admin放入父项目中

  5.重构包结构,将基础包重构成:

 多一个.admin以区分不同工程的包

6.重构包之后XML文件要是没有更新就会报错,把mapper和Model的包路径修改成正确的路径

7.将启动器加Admin用于区分

mango-common 公共代码模块,主要放置一些工具类
  新建一个空的maven工程,除了pom.xnl,没有其他内容


mango-core 核心业务代码模块,主要封装公共业务模块
  新建一个空的maven工程,后续放一些公共核心业务代码封装,如HTTP交互格式封装,业务基类封装和分页工具封装等

mango-pom 聚合模块,仅简化打包,一键执行打包所有模块
为了方便统一打包,新建一个mango-pom工程,这个工程依赖所有模块,负责统一进行打包(不然编译的时候需要每一个都进行编译,工程一多很麻烦),但我们采用的是微服务架构,每一个工程模块使用的依赖版本可能都是不一样的,所以mango-pom
与所有的模块不存在实质性的父子模块,也不由mango-pom统一进行版本和依赖管理方便打包。

打包测试:
  因为我们每一个模块间的依赖还没有加,所以第一次还需要遵循以下步骤进行操作:
    现在mango-core下的pom添加依赖:

      

     在mango-admin下的pom.xml添加mango-core的dependency依赖

     最后在mango-pom下pom.xml添加以上的所有模块为modules依赖然后执行打包命令

      pom.xml

        
        ../mango-admin
        ../mango-common
         ../mango-core
        

   

 业务代码封装


      为了统一业务代码接口,保持代码整洁、提升代码性能,这里对一些通用的代码进行一些封装

        通用CURD接口:
  curdService是对通用增删改查接口的封装,统一定义包含保存、删除、批量删除、根据ID查询和分页查询的方法,一般的业务Service接口会继承此接口,提供基础增、删、改、查服务、
这几个接口能满足大部分基础CURD业务的需求。
在mango-core下建包名如:
  

内容目录:

   

在service中新建CurdService

/*通用CURD接口*/
public interface CurdService {
/*
* 保存操作
* */
int save(T record);

/*
* /删除操作
* */
int delete(T record);

/*
* 批量删除操作
* */
int delete(List record);

/*根据id查询*/
T findById(Long id);


/**
* 分页查询
* 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
* 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
* 影响服务层以上的分页接口,起到了解耦的作用
* @param pageRequest 自定义,统一分页查询请求
* @return PageResult 自定义,统一分页查询结果
*/
PageResult findPage(PageRequest pageRequest);

}
分页请求封装:
  对分页请求的参数进行统一的封装,传入分页查询的页码和数量即可。
  pageRequest.java
    
/**
* 分页请求
* @author Louis
* @date Jan 12, 2019
*/
public class PageRequest {
/**
* 当前页码
*/
private int pageNum = 1;
/**
* 每页数量
*/
private int pageSize = 10;
/**
* 查询参数
*/
private Map params = new HashMap<>();

public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public Map getParams() {
return params;
}
public void setParams(Map params) {
this.params = params;
}
public Object getParam(String key) {
return getParams().get(key);
}

}
分页结果封装
对分页查询的结果进行了统一封装,结果返回业务数据和分页数据。
  PageResult.java
/**
* 分页返回结果
* @author Louis
* @date Jan 12, 2019
*/
public class PageResult {
/**
* 当前页码
*/
private int pageNum;
/**
* 每页数量
*/
private int pageSize;
/**
* 记录总数
*/
private long totalSize;
/**
* 页码总数
*/
private int totalPages;
/**
* 分页数据
*/
private List<?> content;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public List<?> getContent() {
return content;
}
public void setContent(List<?> content) {
this.content = content;
}
}
分页助手封装
对MyBatis的分页查询业务代码进行了统一封装,通过分页助手可以极大简化Service查询业务的编写。
  MybatisPageHelper.java
     
/*
* MyBatis 分页查询助手
*/



public class MybatisPageHelper {

public static final String findPage = "findPage";

/**
* 分页查询, 约定查询方法名为 “findPage”
* @param pageRequest 分页请求
* @param mapper Dao对象,MyBatis的 Mapper
* @return*/


public static PageResult findPage(PageRequest pageRequest, Object mapper) {
return findPage(pageRequest, mapper, findPage);
}

/*
*
* 调用分页插件进行分页查询
* @param pageRequest 分页请求
* @param mapper Dao对象,MyBatis的 Mapper
* @param queryMethodName 要分页的查询方法名
* @param args 方法参数
* @return
*/


@SuppressWarnings({ "unchecked", "rawtypes" })
public static PageResult findPage(PageRequest pageRequest, Object mapper, String queryMethodName, Object... args) {
// 设置分页参数
int pageNum = pageRequest.getPageNum();
int pageSize = pageRequest.getPageSize();
PageHelper.startPage(pageNum, pageSize);
// 利用反射调用查询方法
Object result = ReflectionUtils.invoke(mapper, queryMethodName, args);
return getPageResult(pageRequest, new PageInfo((List) result));
}

/*
*
* 将分页信息封装到统一的接口
* @param pageRequest
* @param page
* @return
*/


private static PageResult getPageResult(PageRequest pageRequest, PageInfo<?> pageInfo) {
PageResult pageResult = new PageResult();
pageResult.setPageNum(pageInfo.getPageNum());
pageResult.setPageSize(pageInfo.getPageSize());
pageResult.setTotalSize(pageInfo.getTotal());
pageResult.setTotalPages(pageInfo.getPages());
pageResult.setContent(pageInfo.getList());
return pageResult;
}

}

这里用到mango-common下的ReflectionUtils.java
  
/**
* 反射相关辅助方法
* @author Louis
* @date Aug 19, 2018
*/
public class ReflectionUtils {


/**
* 根据方法名调用指定对象的方法
* @param object 要调用方法的对象
* @param method 要调用的方法名
* @param args 参数对象数组
* @return
*/
public static Object invoke(Object object, String method, Object... args) {
Object result = null;
Class<? extends Object> clazz = object.getClass();
Method queryMethod = getMethod(clazz, method, args);
if(queryMethod != null) {
try {
result = queryMethod.invoke(object, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else {
try {
throw new NoSuchMethodException(clazz.getName() + " 类中没有找到 " + method + " 方法。");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
return result;
}

/**
* 根据方法名和参数对象查找方法
* @param clazz
* @param name
* @param args 参数实例数据
* @return
*/
public static Method getMethod(Class<? extends Object> clazz, String name, Object[] args) {
Method queryMethod = null;
Method[] methods = clazz.getMethods();
for(Method method:methods) {
if(method.getName().equals(name)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length == args.length) {
boolean isSameMethod = true;
for(int i=0; i Object arg = args[i];
if(arg == null) {
arg = "";
}
if(!parameterTypes[i].equals(args[i].getClass())) {
isSameMethod = false;
}
}
if(isSameMethod) {
queryMethod = method;
break ;
}
}
}
}
return queryMethod;
}
}

下面是Http结果封装
对接口调用返回结果统一封装,方便前端或移动端对返回结果进行统一处理。
  HttpResult.java
/**
* HTTP结果封装
* @author Louis
* @date Jan 12, 2019
*/
public class HttpResult {

private int code = 200;
private String msg;
private Object data;

public static HttpResult error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}

public static HttpResult error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}

public static HttpResult error(int code, String msg) {
HttpResult r = new HttpResult();
r.setCode(code);
r.setMsg(msg);
return r;
}

public static HttpResult ok(String msg) {
HttpResult r = new HttpResult();
r.setMsg(msg);
return r;
}

public static HttpResult ok(Object data) {
HttpResult r = new HttpResult();
r.setData(data);
return r;
}

public static HttpResult ok() {
return new HttpResult();
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

}
MyBatis分页查询
利用pagegelper分页插件帮助我们快速实现分页功能。
添加依赖:
在mango-core中pom.xml添加分页插件依赖包
pom.xml

com.github.pagehelper
pagehelper-spring-boot-starter
1.2.10


添加配置:
在mango-admin下添加配置
#pagehelper
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql

分页代码:
现在Dao层添加一个分页查询的方法
 UserMapper.java中