## 代码层级梳理
#### 代码层级
![UTOOLS1588253419016.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253419016.png)
#### annotation层
![UTOOLS1588253490167.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253490167.png)
###### ExcelVoAttribut
主要用于写@ExcelVoAttribute注解,可以更灵活的配置Excel数据表导出的一些基本配置
###### LogDirection
主要用于写@LogDirection注解,注解里包含一个字段(接口描述),通过接口添加这个注解,填写上接口的功能,当用户在访问接口时候,可以利用aop技术,把用户的操作记到日志里
#### aspect层
![UTOOLS1588253846594.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253846594.png)
###### LogDrectionAspect
主要定义了aop切点的位置和增强的方法
```java
/**
* @Author Johnny
* @Date 2020/4/16
* @Version 1.0
*/
@Aspect
@Component
@Slf4j
public class LogDirectionAspect {
@Pointcut("@annotation(logDirection)")
public void doLogDirection(LogDirection logDirection){
}
@Around(value = "doLogDirection(logDirection)",argNames = "pjp,logDirection")
public Object doBefore(ProceedingJoinPoint pjp,LogDirection logDirection) throws Throwable{
if (!"".equals(logDirection.direction())) {
log.info(logDirection.direction()+"接口被调用");
return pjp.proceed();
}
return "接口信息不规范";
}
}
```
@Pointcut定义切点,此处定义切点为使用@LogDirection的方法
@Around定义增强的方法,通过around的参数ProceedingJoinPoint可以很好的控制程序能不能继续执行下一步,在权限判定发挥着比较重要的作用
拓展:
利用下面的这种方法,需要在xml文件中配置bean实例,当然也可以在方法中通过手动扫包的方法获得bean实例进行操作
```
/**
* @Author Johnny
* @Date 2020/4/16
* @Version 1.0
*/
@Aspect
public class MyAdvice {
/**
* 配置切点
*/
@Pointcut("execution(* com.soft1851.spring.web.dao..*.insert*(..))")
public void pointCut() {
}
/**
* 配置前置增强
*/
@Before("MyAdvice.pointCut()")
public void beforeMethod() {
System.out.println("等待数据插入");
}
/**
* 配置后置增强
*/
@AfterReturning("execution(* com.soft1851.spring.web.dao..*.*(..))")
public void afterMethod(){
System.out.println("关闭数据库");
}
}
```
xml配置实例
```
<bean name="forumDao" class="com.soft1851.spring.web.dao.impl.ForumDaoImpl"/>
<bean name="myAdvice" class="com.soft1851.spring.web.interceptor.MyAdvice"/>
<aop:aspectj-autoproxy/>
```
#### common层
![UTOOLS1588254433232.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254433232.png)
ResponseResult和ResultCode
这两个类可以将响应体统一进行封装,将接口的响应体规范化,使前端获取数据也更加友好
贴下代码,防止以后找不到
ResponseResult类
```
package com.soft1851.music.admin.common;
import lombok.Data;
import java.io.Serializable;
/**
* @author Johnny
* @Date: 2020/4/21 19:52
* @Description:
*/
@Data
public class ResponseResult implements Serializable {
private static final long serialVersionUID = -3948389268046368059L;
private Integer code;
private String msg;
private Object data;
private ResponseResult() {
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult success() {
ResponseResult result = new ResponseResult();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static ResponseResult success(Object data) {
ResponseResult result = new ResponseResult();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
public static ResponseResult failure(ResultCode resultCode) {
ResponseResult result = new ResponseResult();
result.setResultCode(resultCode);
return result;
}
public static ResponseResult failure(ResultCode resultCode, Object data) {
ResponseResult result = new ResponseResult();
result.setResultCode(resultCode);
result.setData(data);
return result;
}
public void setResultCode(ResultCode code) {
this.code = code.code();
this.msg = code.message();
}
}
```
ResultCode类(枚举)
```
package com.soft1851.music.admin.common;
/**
* @author Johnny
* @Date: 2020/4/21 19:52
* @Description:
*/
public enum ResultCode {
/* 成功状态码 */
SUCCESS(1, "成功"),
/* 通用错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
PARAM_IS_BLANK(10002, "参数为空"),
PARAM_TYPE_BIND_ERROR(10003, "参数类型错误"),
PARAM_NOT_COMPLETE(10004, "参数缺失"),
HTTP_METHOD_ERROR(10005, "请求方法错误"),
HTTP_METHOD_NOT_ALLOWED(10006, "请求方法不允许"),
HTTP_NOT_FOUND(10007, "请求地址错误"),
BOUND_STATEMENT_NOT_FOUND(10008, "Mybatis未绑定"),
CONNECTION_ERROR(10009, "网络连接错误"),
ARITHMETIC_ERROR(100010, "计算错误"),
/* 用户错误:20001-29999*/
USER_NOT_SIGN_IN(20001, "请先登录"),
USER_PASSWORD_ERROR(20002, "密码错误"),
USER_ACCOUNT_ERROR(20003, "账号错误"),
USER_VERIFY_CODE_ERROR(20004, "验证码错误"),
USER_CODE_TIMEOUT(20005, "验证码失效"),
USER_ACCOUNT_FORBIDDEN(20006, "账号已被禁用"),
USER_SIGN_UP_FAIL(20007, "用户注册失败"),
USER_SIGN_IN_FAIL(20008, "用户登录失败"),
USER_NOT_FOUND(20009, "用户不存在"),
USER_NO_AUTH(20019, "用户权限不足"),
USER_TOKEN_EXPIRES(200010, "Token已过期"),
/* 业务错误:30001-39999 */
SMS_ERROR(30001, "短信业务出现问题"),
UPLOAD_ERROR(30002, "上传文件业务出现问题"),
CAPTCHA_ERROR(30003, "验证码业务出现问题"),
/* 数据错误:40001-49999 */
RESULT_CODE_DATA_NONE(50001, "数据未找到"),
DATA_IS_WRONG(50002, "数据有误"),
DATA_ALREADY_EXISTED(50003, "数据已存在"),
DATABASE_ERROR(50004, "数据库操作异常"),
/* 服务器或系统错误:50001-599999 */
SERVER_ERROR(50000, "服务器错误,请稍后重试"),
SYSTEM_ERROR(40001, "系统错误,请稍后重试"),
/* 接口错误:60001-69999 */
INTERFACE_INNER_INVOKE_ERROR(60001, "内部系统接口调用异常"),
INTERFACE_OUTER_INVOKE_ERROR(60002, "外部系统接口调用异常"),
INTERFACE_FORBID_VISIT(60003, "该接口禁止访问"),
INTERFACE_ADDRESS_INVALID(60004, "接口地址无效"),
INTERFACE_REQUEST_TIMEOUT(60005, "接口请求超时"),
INTERFACE_EXCEED_LOAD(60006, "接口负载过高"),
/* 权限错误:70001-79999 */
PERMISSION_NO_ACCESS(70001,"无访问权限");
private Integer code;
private String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
public static String getMessage(String name) {
for (ResultCode item : ResultCode.values()) {
if (item.name().equals(name)) {
return item.message;
}
}
return name;
}
public static Integer getCode(String name) {
for (ResultCode item : ResultCode.values()) {
if (item.name().equals(name)) {
return item.code;
}
}
return null;
}
@Override
public String toString() {
return this.name();
}
}
```
#### config层
![UTOOLS1588254579776.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254579776.png)
###### CaptchaConfig
主要是为kaptcha谷歌验证码的一些基本信息进行配置,比如字体,验证码位数等等
附上一些kaptha的基本配置信息
| **Constant** | **描述** | **默认值** |
| -------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- |
| kaptcha.border | 图片边框,合法值:yes , no | yes |
| kaptcha.border.color | 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black |
| kaptcha.border.thickness | 边框厚度,合法值:>0 | 1 |
| kaptcha.image.width | 图片宽 | 200 |
| kaptcha.image.height | 图片高 | 50 |
| kaptcha.producer.impl | 图片实现类 | com.google.code.kaptcha.impl.DefaultKaptcha |
| kaptcha.textproducer.impl | 文本实现类 | com.google.code.kaptcha.text.impl.DefaultTextCreator |
| kaptcha.textproducer.char.string | 文本集合,验证码值从此集合中获取 | abcde2345678gfynmnpwx |
| kaptcha.textproducer.char.length | 验证码长度 | 5 |
| kaptcha.textproducer.font.names | 字体 | Arial, Courier |
| kaptcha.textproducer.font.size | 字体大小 | 40px. |
| kaptcha.textproducer.font.color | 字体颜色,合法值: r,g,b 或者 white,black,blue. | black |
| kaptcha.textproducer.char.space | 文字间隔 | 2 |
| kaptcha.noise.impl | 干扰实现类 | com.google.code.kaptcha.impl.DefaultNoise |
| kaptcha.noise.color | 干扰 颜色,合法值: r,g,b 或者 white,black,blue. | black |
| kaptcha.obscurificator.impl | 图片样式: 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy | com.google.code.kaptcha.impl.WaterRipple |
| kaptcha.background.impl | 背景实现类 | com.google.code.kaptcha.impl.DefaultBackground |
| kaptcha.background.clear.from | 背景颜色渐变,开始颜色 | light grey |
| kaptcha.background.clear.to | 背景颜色渐变, 结束颜色 | white |
| kaptcha.word.impl | 文字渲染器 | com.google.code.kaptcha.text.impl.DefaultWordRenderer |
| kaptcha.session.key | session key | KAPTCHA_SESSION_KEY |
| kaptcha.session.date | session date | KAPTCHA_SESSION_DATE |
###### CorsConfig
主要是为跨域做一些基本的配置,学习阶段后端必备
代码展示,防止以后忘记
```
/**
* @Author Johnny
* @Date 2020/4/16
* @Version 1.0
*/
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//放行所有跨域的客户端domain
config.addAllowedOrigin("*");
//允许的请求方法列表
String[] requestMethods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};
List<String> allowedRequestMethods = Arrays.asList(requestMethods);
config.setAllowedMethods(allowedRequestMethods);
//允许的客户端请求头列表
String[] requestHeaders = {"x-requested-with", "Content-Type", "Authorization"};
List<String> allowedHeaders = Arrays.asList(requestHeaders);
config.setAllowedHeaders(allowedHeaders);
//允许的响应头列表
String[] responseHeaders = {"Access-Control-Expose-Headers", "Authorization"};
List<String> allowedExposedHeaders = Arrays.asList(responseHeaders);
config.setExposedHeaders(allowedExposedHeaders);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
// 这个顺序很重要,设置在最前
bean.setOrder(0);
return bean;
}
}
```
###### MybatisPlusConfig
主要是为mybatisplus做一些基本配置(如果要有分页查询数据,则这个配置文件必须要有)
```
/**
* @Author Johnny
* @Date 2020/4/16
* @Version 1.0
*/
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//设置请求的页面大于最大页操作,true调回到首页,false继续请求,默认为false
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
```
###### MySqlGenerator
代码生成器,功能强大无比,根据数据表,一键生成各种层级,各种实体
```
/**
* @ClassName MySqlGenerator
* @Description 代码生成器
* @Author Johnny
* @Date 2020/4/16
* @Version 1.0
*/
@Slf4j
public class MySqlGenerator {
public static void main(String[] args) {
//全局策略配置
GlobalConfig config = new GlobalConfig();
//获得当前项目根路径d:/dev/SpringBoot/spring-boot-learning
String projectPath = System.getProperty("user.dir");
config.setActiveRecord(true)
//作者注释
.setAuthor("Johnny")
//代码生成输出路径
.setOutputDir(projectPath + "/src/main/java")
//覆盖已有文件,默认false
.setFileOverride(true)
//是否打开输出目录窗口。默认true
.setOpen(false)
//开启swagger2模式
//.setSwagger2(true)
//开启ActiveRecord模式
.setActiveRecord(true)
//mapper添加restMap
.setBaseResultMap(true)
//mapper添加Base_Column_List
.setBaseColumnList(true)
//时间类型对应策略,默认time_pack
//.setDateType(DateType.TIME_PACK)
//相关包中的接口和类名后缀
.setMapperName("%sMapper")
.setServiceName("%sService")
.setServiceImplName("%sServiceImpl");
//数据库表配置,通过该配置,可指定需要生成哪些表或者排除哪些表
StrategyConfig strategyConfig = new StrategyConfig();
//是否大写命名
strategyConfig.setCapitalMode(true)
//是否跳过视图
.setSkipView(true)
//数据库表映射到实体的命名策略为驼峰式
.setNaming(NamingStrategy.underline_to_camel)
//生成表,可以写多个,如果不加参数,默认为所有表
.setInclude()
.setEntityBuilderModel(true)
.setEntityLombokModel(true)
.setRestControllerStyle(true)
.setEntityTableFieldAnnotationEnable(true);
//包名配置
PackageConfig packageConfig = new PackageConfig();
//父包名
packageConfig.setParent("com.soft1851.music.admin")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setXml("xml")
.setEntity("entity");
//数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.jdbc.Driver")
.setUrl("jdbc:mysql://rm-m5ee476bu350735gjeo.mysql.rds.aliyuncs.com:3306/cloud_music?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true")
.setUsername("root")
.setPassword("XuNiit_#");
AutoGenerator autoGenerator = new AutoGenerator();
autoGenerator.setGlobalConfig(config)
.setStrategy(strategyConfig)
.setDataSource(dataSourceConfig)
.setTemplateEngine(new FreemarkerTemplateEngine())
.setPackageInfo(packageConfig);
autoGenerator.execute();
log.info("=============代码生成成功================");
}
}
```
###### RedisConfig
主要对RedisConfig缓存数据库进行一些基本配置
```
/**
* @author Johnny
* @Date: 2020/4/21 20:45
* @Description:
*/
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化RedisCacheWrite
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认过期时间为1天
defaultCacheConfig.entryTtl(Duration.ofDays(1));
//初始化RedisCacheManger
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object,Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//使用stringRedisSerializer来序列化和反序列化redis的value值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}
```
###### WebConfig
这个类中主要配置了拦截器,可以对部分页面进行统一拦截,当然这个只是统一的拦截,具体的拦截所要进行的操作,还在下文
```
/**
* @author Johnny
* @Date: 2020/4/21 20:58
* @Description:
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Resource
private JwtInterceptor jwtInterceptor;
/**
* 添加拦截器配置
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截路径可自行配置多个 可用 ,分隔开
registry.addInterceptor(loginInterceptor).addPathPatterns("/sysAdmin/login").excludePathPatterns("/**").excludePathPatterns("/static/**");
// registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns("/sysAdmin/login", "/captcha","/songList/page").excludePathPatterns("/static/**");
}
}
```
#### controller层
controller层暂时带过,后面有时间的话把controller层常用注解汇总整理下
#### dto层
目前还没有将entity、vo、dto统一放到domain里,dto里主要是用户请求接口时需要传的参数,vo主要时后端返回前端展示的参数,而entity就是我们和数据库表所吻合的类。举例:我们数据库中的用户表的字段肯定不只时账号和密码,会有一些其他字段,比如加密盐、用户基本信息等等,但是用户请求登录接口时,只需要传账号和密码就可以了,此时此刻,为了防止资源的浪费,我们就定义一个dto来接收前端传来的参数。当用户进入主界面时,查看个人信息,后端并不需要将用户id、加密盐等私密信息展示到前端,此时,同样为了节省资源,我们定义vo视图对象,将前端需要的参数放进去,再传入前端。
#### entity层
entity层主要是将数据库中的表一对一的写好,此处是直接用代码生成器生成
#### exception
![image-20200430222108856](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430222108856.png)
###### CustomException
定义全局异常处理,使用时直接throws CustomException就好
```
/**
* @author Johnny
* @Date: 2020/4/22 10:48
* @Description:
*/
public class CustomException extends RuntimeException {
protected ResultCode resultCode;
public CustomException(String msg, ResultCode resultCode) {
super(msg);
this.resultCode = resultCode;
}
public ResultCode getResultCode() {
return resultCode;
}
}
```
###### GlobalException
也是全局异常处理,但和上面略有区别,非手动throw,spring在遇到异常时,会自动来这个类里查找,如果已经有配置过的,就会按照配置要求输出内容
```
/**
* @author Johnny
* @Date: 2020/4/30 18:45
* @Description:
*/
@ControllerAdvice
public class GlobalException {
@ExceptionHandler(ConstraintViolationException.class)
ResponseResult handleConstraintViolationException(ConstraintViolationException e) {
return ResponseResult.failure(ResultCode.DATA_IS_WRONG);
}
}
```
###### JwtException
同CustomException类格式一样,但这个侧重于处理Jwt模块的异常
#### filter
![UTOOLS1588256707418.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256707418.png)
###### ChannelFilter
```
/**
* @author Johnny
* @Date: 2020/4/21 20:19
* @Description:
*/
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if (requestWrapper == null) {
filterChain.doFilter(servletRequest,servletResponse);
}else{
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
}
```
###### LoginFilter
这个类主要针对用户登录进行过滤
```
/**
* @author Johnny
* @Date: 2020/4/26 10:46
* @Description:
*/
@Slf4j
@WebFilter(urlPatterns = "/login",filterName = "LoginFilter")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
String url = ((HttpServletRequest) servletRequest).getRequestURI();
//判断接口是否为导入接口
if ("/resource/guide".equals(url)) {
Part file = ((HttpServletRequest) servletRequest).getPart("file");
log.info("文件名:" + file);
}
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if (requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
}else{
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
}
```
#### handle层
![UTOOLS1588256874063.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256874063.png)
###### GlobalExceptionHandle
定义全局异常
```
/**
* @author Johnny
* @Date: 2020/4/22 10:40
* @Description:
*/
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class GlobalExceptionHandle {
//自定义异常
/**
* JwtException
* @param exception
* @return
*/
@ExceptionHandler(value = {JwtException.class})
@ResponseBody
public ResponseResult sendError(JwtException exception) {
log.error(exception.getMessage());
return ResponseResult.failure(exception.getResultCode());
}
/**
* CustomException
* @param exception
* @return
*/
@ExceptionHandler(value = {CustomException.class})
@ResponseBody
public ResponseResult sendError(CustomException exception) {
log.error(exception.getMessage());
return ResponseResult.failure(exception.getResultCode());
}
//系统级异常
/**
* InvalidClassException
* @param exception
* @return
*/
@ExceptionHandler(value = {InvalidClassException.class})
@ResponseBody
public ResponseResult sendError(InvalidClaimException exception) {
log.error(exception.getMessage());
//返回token已经过期
return ResponseResult.failure(ResultCode.USER_TOKEN_EXPIRES);
}
/**
* NullPointerException
* @param exception
* @return
*/
@ExceptionHandler(value = {NullPointerException.class})
@ResponseBody
public ResponseResult sendError(NullPointerException exception) {
log.error(exception.getMessage());
//空指针提示数据未找到
return ResponseResult.failure(ResultCode.RESULT_CODE_DATA_NONE);
}
/**
* IOException
* @param exception
* @return
*/
@ExceptionHandler(value = {IOException.class})
@ResponseBody
public ResponseResult sendError(IOException exception) {
log.error(exception.getMessage());
//io异常提示验证码出现问题
return ResponseResult.failure(ResultCode.CAPTCHA_ERROR);
}
```
###### GlobalResponseHandle
处理全局的响应头(当接口返回类型非ResponseResult类型时,可以自动转换)
```
/**
* @author Johnny
* @Date: 2020/4/22 10:55
* @Description:
*/
@ControllerAdvice
public class GlobalResponseHandle implements ResponseBodyAdvice {
/**
* 处理响应的具体方法
* @param returnType
* @param converterType
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof ResponseResult) {
return body;
}else{
return ResponseResult.success(body);
}
}
}
```
#### interceptor
![image-20200430223215350](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430223215350.png)
###### JwtInterceptor
```
/**
* @author Johnny
* @Date: 2020/4/23 19:41
* @Description:
*/
@Slf4j
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Resource
SysRoleService roleService;
@Resource
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
log.info("token———————————"+token);
//认证
if (token == null) {
log.info("用户未登录");
return false;
}else{
//登录之后
//从请求头当中取出用户id
String adminId = request.getHeader("id");
log.info("用户id" + adminId);
//在redis中检查是否存在adminId为key的数据
if (!redisService.existsKey(adminId)) {
log.info("redis中不存在此键");
}else{
String secrect = redisService.getValue(adminId, String.class);
//从token中解析出roles字符串
String roles = JwtTokenUtil.getRoles(token,secrect);
//反序列化成List
List<SysRole> sysRoles = JSONArray.parseArray(roles, SysRole.class);
//从request中取得客户端传输的roleId
String roleId = request.getParameter("roleId");
log.info("roleId:"+roleId);
boolean flag = roleService.checkRole(sysRoles,Integer.parseInt(roleId));
if(flag){
return true;
}else{
log.info("用户不具备此角色");
}
}
return false;
}
}
}
```
###### LoginInterceptor
```
/**
* @author Johnny
* @Date: 2020/4/21 21:00
* @Description:
*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle) {
//将request包装成HttpServletWrapper类型
RequestWrapper requestWrapper = new RequestWrapper(request);
//取得请求的json对象
String body = requestWrapper.getBody();
log.info(body);
//从redis中取得指定用户名的验证码
JSONObject jsonObject = JSONObject.parseObject(body);
String name = jsonObject.getString("name");
System.out.println(name);
String password = jsonObject.getString("password");
System.out.println(password);
String verifyCode = jsonObject.getString("verifyCode");
System.out.println(verifyCode);
LoginDto loginDto = LoginDto.builder().name(name).password(password).verifyCode(verifyCode).build();
return true;
// if (redisService.existsKey(loginDto.getName())) {
// //获取redis中的验证码
// String correctCode = redisService.getValue(name, String.class);
// //忽略大小写,成功则放行到controller调用登录接口
// if (correctCode.equalsIgnoreCase(verifyCode)) {
// return true;
// }else{
// log.info("验证码错误");
// return false;
// }
// }else{
// log.info("验证码失效");
// return false;
// }
}
}
```
#### mapper层
这个目前不做过多赘述,我会尽快整理下mybatisplus的常用语法发出来
#### util层
![UTOOLS1588257313481.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588257313481.png)
###### ExcelConsumer
```
/**
* @author Johnny
* @Date: 2020/4/28 21:53
* @Description:
*/
@Slf4j
public class ExcelConsumer<T> implements Runnable {
/**
* 一张工作表可以容纳的最大行数
*/
private static Integer SHEET_SIZE = 100000;
/**
* 导出的Vo数据类型
*/
private Class<T> clazz;
/**
* 工作簿
*/
private SXSSFWorkbook wb;
/**
* 工作表名称
*/
private String sheetName;
/**
* 数据缓冲区对象
*/
private ExportDataAdapter<T> exportDataAdapter;
/**
* 线程同步
*/
private CountDownLatch latch;
/**
* 构造方法
*
* @param clazz
*/
public ExcelConsumer(Class<T> clazz, ExportDataAdapter<T> exportDataAdapter, SXSSFWorkbook wb, CountDownLatch latch, String sheetName) {
if (clazz == null || wb == null || exportDataAdapter == null || latch == null) {
log.error("ExcelConsumer::初始化对象参数不能为空");
return;
}
this.clazz = clazz;
this.exportDataAdapter = exportDataAdapter;
this.wb = wb;
this.latch = latch;
this.sheetName = sheetName == null ? "UnNamedSheet" : sheetName;
}
@Override
public void run() {
//初始化excel导出工具类
ExcelUtil<T> excelUtil = new ExcelUtil<>(this.clazz);
Sheet sheet = null;
int sheetNo = 0;
int rowNum = 1;
T vo;
//生产者还在生产数据
while (latch.getCount() > 1) {
//生成sheetName
if (rowNum == 1) {
sheetNo++;
sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));
excelUtil.setColumnTitle(sheet);
}
//获取数据
vo = exportDataAdapter.getData();
//往excel添加一行数据
excelUtil.addRowData(vo, wb, sheet, rowNum);
rowNum++;
//准备生成下一个sheetName
if (rowNum == SHEET_SIZE + 1) {
rowNum = 1;
}
}
//生产者不再生产数据,取剩余数据,并将数据写入excel
Integer reminderDataSize = exportDataAdapter.getDataSize();
T reminderData;
if (reminderDataSize > 0) {
for (int i = 0; i < reminderDataSize; i++) {
reminderData = exportDataAdapter.getData();
if (rowNum == 1) {
sheetNo++;
sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));
excelUtil.setColumnTitle(sheet);
}
excelUtil.addRowData(reminderData, wb, sheet, rowNum);
rowNum++;
if (rowNum == SHEET_SIZE + 1) {
rowNum = 1;
}
}
}
log.info("数据导出完成");
latch.countDown();
}
}
```
###### ExcelUtil
```
/**
* @author Johnny
* @Date: 2020/4/28 21:47
* @Description:
*/
public class ExcelUtil<T> {
Class<T> clazz;
/**
* 表头字段列表
*/
private List<Field> fields;
/**
* 数字单元格对象
*/
private CellStyle decimalCellStyle = null;
/**
* 日期时间单元格对象
*/
private CellStyle dateTimeCellStyle = null;
/**
* 构造方法
*
* @param clazz
*/
public ExcelUtil(Class<T> clazz) {
this.clazz = clazz;
}
/**
* 添加一条数据
*
* @param vo:需要导出的vo对象
* @param wb:工作簿
* @param sheet:工作表
* @param rowNum:当前行号
*/
public void addRowData(T vo, SXSSFWorkbook wb, Sheet sheet, int rowNum) {
//创建一行
Row row = sheet.createRow(rowNum);
Field field;
Cell cell;
ExcelVoAttribute attr;
int fieldSize = fields.size();
// 遍历入参对象的所有属性
for (int j = 0; j < fieldSize; j++) {
// 通过反射获得需要导出的入参对象的所有属性
field = fields.get(j);
// 设置实体类私有属性可访问
field.setAccessible(true);
// 获取所有添加了注解的属性
attr = field.getAnnotation(ExcelVoAttribute.class);
// 给每个属性创建一个单元格
cell = row.createCell(j);
try {
this.setCellValue(attr, field.get(vo), wb, cell);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 根据注解类判断字段类型设置excel单元格数据格式方法
*
* @param attr
* @param valueObject
* @param workbook
* @param cell
*/
private void setCellValue(ExcelVoAttribute attr, Object valueObject, SXSSFWorkbook workbook, Cell cell) {
String returnValue;
if (attr.isNumber()) {
cell.setCellStyle(getCellStyle(attr, workbook));
returnValue = valueObject == null || "".equals(valueObject) ? "0" : valueObject.toString();
BigDecimal num = new BigDecimal(returnValue);
cell.setCellValue(num.doubleValue());
} else if (attr.isDateTime()) {
cell.setCellStyle(getCellStyle(attr, workbook));
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
returnValue = df.format((TemporalAccessor) valueObject);
cell.setCellValue(returnValue);
} else {
returnValue = valueObject == null ? "" : valueObject.toString();
cell.setCellValue(returnValue);
}
}
/**
* 根据注解类判断字段类型,返回excel单元格数据格式方法
*
* @param attr
* @param workbook
* @return CellStyle
*/
private CellStyle getCellStyle(ExcelVoAttribute attr, SXSSFWorkbook workbook) {
if (attr.isNumber()) {
if (decimalCellStyle == null) {
decimalCellStyle = workbook.createCellStyle();
//此处设置数字单元格格式
DataFormat df = workbook.createDataFormat();
//千分位,保留1位小数
decimalCellStyle.setDataFormat(df.getFormat("#,##0.0"));
}
return decimalCellStyle;
}
if (attr.isDateTime()) {
if (dateTimeCellStyle == null) {
dateTimeCellStyle = workbook.createCellStyle();
//此处设置日期时间单元格格式
DataFormat df = workbook.createDataFormat();
dateTimeCellStyle.setDataFormat(df.getFormat("yyyy-MM-dd HH:mm:ss"));
}
return dateTimeCellStyle;
}
return null;
}
/**
* 创建工作页Sheet
*
* @param wb
* @param sheetName
* @return Sheet
*/
public Sheet createSheet(SXSSFWorkbook wb, String sheetName) {
return wb.createSheet(sheetName);
}
/**
* 设置excel列头及格式
*
* @param sheet
*/
public void setColumnTitle(Sheet sheet) {
if (fields == null) {
this.fields = this.getSortFields();
}
Row row;
Cell cell;
ExcelVoAttribute attr;
Field field;
int fieldSize = fields.size();
row = sheet.createRow(0);
for (int i = 0; i < fieldSize; i++) {
field = fields.get(i);
attr = field.getAnnotation(ExcelVoAttribute.class);
cell = CellUtil.createCell(row, i, attr.name());
// 设置列宽,根据相应的字段名的长度等比
sheet.setColumnWidth(i, attr.name().getBytes().length * 400);
}
}
/**
* 获取输出对象字段列表,并根据注解进行字段排序
*
* @return
*/
private List<Field> getSortFields() {
List<Field> fields = Arrays.stream(clazz.getDeclaredFields()).filter(x -> x.isAnnotationPresent(ExcelVoAttribute.class)).collect(Collectors.toList());
List<Field> sortList = new ArrayList<>(fields);
//排序
for (Field field : fields) {
ExcelVoAttribute excelVoAttribute = field.getAnnotation(ExcelVoAttribute.class);
int sortNo = excelVoAttribute.column();
sortList.set(sortNo, field);
}
return sortList;
}
}
```
###### ExcelUtils
```
/**
* @author Johnny
* @Date: 2020/4/25 22:56
* @Description:
*/
@Slf4j
public class ExcelUtils {
/**
* 导出歌曲
* @param response
* @param list
* @param map
* @param title
*/
public static void exportExcel(HttpServletResponse response, List list, Map<String, String> map, String title) {
//通过工具类创建writer
ExcelWriter writer = ExcelUtil.getWriter(true);
//自定义标题别名
Set<Map.Entry<String, String>> entries = map.entrySet();
//迭代器遍历数据
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> next = iterator.next();
//自定义表头
writer.addHeaderAlias(next.getKey(), next.getValue());
}
//合并单元格后的标题行,使用默认标题样式
writer.merge(map.size() - 1, title);
//一次性写出内容,使用默认样式,强制输出标题
writer.write(list, true);
//out为outputStream,需要写出到的目标流
try {
writer.flush(response.getOutputStream(), true);
} catch (IOException e) {
log.info("歌单导出异常");
e.printStackTrace();
}
writer.close();
}
/**
* 导入歌曲
* @param file
* @return
*/
public static List<Song> importExcel(File file) {
List<Song> songs = new ArrayList<>();
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//通过getReader方法确定读取那个sheet中的数据
ExcelReader reader = ExcelUtil.getReader(inputStream, "sheet1");
//回去总行数
List<List<Object>> read = reader.read(1, reader.getRowCount());
for (List<Object> objects : read) {
//对每行数据取出构建一个song对象
Song song = Song.builder()
.songName(objects.get(0).toString())
.songId(UUID.randomUUID().toString().replace("-", ""))
.sortId("0")
.singer(objects.get(1).toString())
.duration(objects.get(2).toString())
.thumbnail(objects.get(3).toString())
.url(objects.get(4).toString())
.lyric(objects.get(5).toString())
.commentCount(0)
.playCount(0)
.deleteFlag("0")
.updateTime(LocalDateTime.now())
.createTime(LocalDateTime.now())
.build();
songs.add(song);
}
return songs;
}
}
```
###### ExportDataAdapter
```
/**
* @author Johnny
* @Date: 2020/4/28 21:53
* @Description:
*/
public class ExportDataAdapter<T>{
/**
* 默认队列大小
*/
private static Integer DEFAULT_SIZE = 1000;
private BlockingQueue<T> resourceQueue = null;
public ExportDataAdapter() {
this.resourceQueue = new LinkedBlockingQueue<T>(DEFAULT_SIZE);
}
/**
* 添加数据
*
* @param data
*/
public void addData(T data) {
try {
resourceQueue.put(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 获取剩余数据数量
*
* @return
*/
public Integer getDataSize() {
return resourceQueue.size();
}
/**
* 从队列中获取数据
*
* @return
*/
public T getData() {
try {
return resourceQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
```
###### FileUtil
```
/**
* @author Johnny
* @Date: 2020/4/26 10:41
* @Description:
*/
public class FileUtil {
public static File fileConversion(MultipartFile file) {
int n;
File file1 = new File(file.getOriginalFilename());
try {
InputStream in = file.getInputStream();
OutputStream os = new FileOutputStream(file1);
byte[] bytes = new byte[4096];
while ((n=in.read(bytes,0,4096))!=-1){
os.write(bytes,0,n);
File file2 = new File(file1.toURI());
file2.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
return file1;
}
}
```
###### JwtTokenUtil
```
/**
* @Author Johnny
* @Date 2020/4/15
* @Version 1.0
*/
@Slf4j
public class JwtTokenUtil {
/**
* 加密
*
* @param userId
* @param expiresAt
* @return String
*/
public static String getToken(final String userId, String userRole,final String secrect,Date expiresAt) {
String token = null;
try {
token = JWT.create()
.withIssuer("auth0")
.withClaim("userId", userId)
.withClaim("userRole", userRole)
.withExpiresAt(expiresAt)
// 使用了HMAC256加密算法, mySecret是用来加密数字签名的密钥
.sign(Algorithm.HMAC256(secrect));
} catch (UnsupportedEncodingException e) {
log.error("不支持的编码格式");
}
return token;
}
/**
* 解密
* @param token
* @return DecodedJWT
*/
public static DecodedJWT deToken(final String token,final String secrect) {
DecodedJWT jwt;
JWTVerifier verifier = null;
try {
verifier = JWT.require(Algorithm.HMAC256(secrect))
.withIssuer("auth0")
.build();
} catch (UnsupportedEncodingException e) {
log.error("不支持的编码格式");
}
assert verifier != null;
jwt = verifier.verify(token);
return jwt;
}
/**
* 获取userId
*
* @param token
* @return String
*/
public static String getUserId(String token,String secrect) {
return deToken(token,secrect).getClaim("userId").asString();
}
/**
* 获取role
*
* @param token
* @return String
*/
public static String getRoles(String token,String secrect) {
return deToken(token,secrect).getClaim("userRole").asString();
}
/**
* 验证是否过期
*
* @param token
* @return boolean
*/
public static boolean isExpiration(String token,String secrect) {
return deToken(token,secrect).getExpiresAt().before(new Date());
}
public static void main(String[] args) {
// String token = getToken("2000100193", new Date(System.currentTimeMillis() + 10L * 1000L));
// System.out.println(token);
// while (true) {
// boolean flag = isExpiration(token);
// System.out.println(flag);
// if (flag) {
// System.out.println("token已失效");
// break;
// }
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// String token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsImV4cCI6MTU4NzY3MTQ3OCwidXNlcklkIjoiREUzNUQ3Q0MwNUFGOTZBMjFEN0FERkM4NjUxRTY2MTQifQ.uT2W3QcE3744WNN3inEKT8lUVJs6xAC7TodDCaWkcyM";
// System.out.println(deToken(token).getClaim("userId").asString());
}
}
```
###### Md5Util
```
/**
* @author Johnny
*/
public class Md5Util {
/**
* @param pwd 需要加密的字符串
* @param isUpper 字母大小写(false为默认小写,true为大写)
* @param bit 加密的位数(16,32,64)
* @return String
*/
public static String getMd5(String pwd, boolean isUpper, Integer bit) {
String md5 = "";
try {
// 创建加密对象
MessageDigest md = MessageDigest.getInstance("md5");
if (bit == 64) {
Base64.Encoder encoder = Base64.getEncoder();
md5 = encoder.encodeToString(md.digest(pwd.getBytes(StandardCharsets.UTF_8)));
} else {
// 计算MD5函数
md.update(pwd.getBytes());
byte b[] = md.digest();
int i;
StringBuilder sb = new StringBuilder();
for (byte value : b) {
i = value;
if (i < 0) {
i += 256;
}
if (i < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(i));
}
md5 = sb.toString();
if (bit == 16) {
//截取32位md5为16位
md5 = md5.substring(8, 24).toString();
if (isUpper) {
md5 = md5.toUpperCase();
}
return md5;
}
}
//转换成大写
if (isUpper) {
md5 = md5.toUpperCase();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("md5加密抛出异常!");
}
return md5;
}
public static void main(String[] args) {
String a = "123456";
String md5a = getMd5(a, true, 32);
System.out.println(md5a);
System.out.println(md5a.length());
}
```
###### ThreadPool
```
/**
* @author Johnny
* @Date: 2020/4/28 21:55
* @Description:
*/
public class ThreadPool {
/**
* 异步线程
*/
private final static ThreadPoolExecutor executor =
new ThreadPoolExecutor(20, 100, 10, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(2000),
r -> new Thread(r, "excelExportThread"),
new ThreadPoolExecutor.AbortPolicy());
public static ThreadPoolExecutor getExecutor() {
return executor;
}
}
```
###### TreeBuilder
```
/**
* @author Johnny
* @Date: 2020/4/23 18:46
* @Description:
*/
public class TreeBuilder {
/**
* 两层循环实现建树
*
* @param treeNodes 传入的树节点列表
* @return
*/
public static List<TreeNode> buildTreeByLoop(List<TreeNode> treeNodes) {
List<TreeNode> trees = new ArrayList<>();
for (TreeNode treeNode : treeNodes) {
if (treeNode.getParentId() == 0) {
trees.add(treeNode);
}
for (TreeNode it : treeNodes) {
if (it.getParentId().equals(treeNode.getId())) {
if (treeNode.getSubMenus() == null) {
treeNode.setSubMenus(new ArrayList<>());
}
treeNode.getSubMenus().add(it);
}
}
}
return trees;
}
/**
* 使用递归方法建树
*
* @param treeNodes
* @return
*/
public static List<TreeNode> buildTreeByRecursive(List<TreeNode> treeNodes) {
List<TreeNode> trees = new ArrayList<>();
for (TreeNode treeNode : treeNodes) {
if (treeNode.getParentId() == 0) {
trees.add(findChildren(treeNode, treeNodes));
}
}
return trees;
}
/**
* 递归查找子节点
*
* @param treeNodes
* @return
*/
public static TreeNode findChildren(TreeNode treeNode, List<TreeNode> treeNodes) {
for (TreeNode it : treeNodes) {
if (treeNode.getId().equals(it.getParentId())) {
if (treeNode.getSubMenus() == null) {
treeNode.setSubMenus(new ArrayList<>());
}
treeNode.getSubMenus().add(findChildren(it, treeNodes));
}
}
return treeNode;
}
}
```
###### TreeNode
```
/**
* @author Johnny
* @Date: 2020/4/23 18:46
* @Description:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TreeNode {
private Integer id;
private Integer parentId;
private Integer type;
private String title;
private String icon;
private String path;
private Integer sort;
private List<TreeNode> subMenus;
public TreeNode(Integer id, Integer parentId, Integer type,String title, String icon, String path, Integer sort) {
this.id = id;
this.parentId = parentId;
this.type = type;
this.title = title;
this.icon = icon;
this.path = path;
this.sort = sort;
}
}
```、
更多内容请关注Johnny博客