主要用于写@ExcelVoAttribute注解,可以更灵活的配置Excel数据表导出的一些基本配置
主要用于写@LogDirection注解,注解里包含一个字段(接口描述),通过接口添加这个注解,填写上接口的功能,当用户在访问接口时候,可以利用aop技术,把用户的操作记到日志里
主要定义了aop切点的位置和增强的方法
/** * @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/>
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(); } }
主要是为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 |
主要是为跨域做一些基本的配置,学习阶段后端必备
代码展示,防止以后忘记
/** * @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; } }
主要是为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; } }
代码生成器,功能强大无比,根据数据表,一键生成各种层级,各种实体
/** * @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缓存数据库进行一些基本配置
/** * @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; } }
这个类中主要配置了拦截器,可以对部分页面进行统一拦截,当然这个只是统一的拦截,具体的拦截所要进行的操作,还在下文
/** * @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层常用注解汇总整理下
目前还没有将entity、vo、dto统一放到domain里,dto里主要是用户请求接口时需要传的参数,vo主要时后端返回前端展示的参数,而entity就是我们和数据库表所吻合的类。举例:我们数据库中的用户表的字段肯定不只时账号和密码,会有一些其他字段,比如加密盐、用户基本信息等等,但是用户请求登录接口时,只需要传账号和密码就可以了,此时此刻,为了防止资源的浪费,我们就定义一个dto来接收前端传来的参数。当用户进入主界面时,查看个人信息,后端并不需要将用户id、加密盐等私密信息展示到前端,此时,同样为了节省资源,我们定义vo视图对象,将前端需要的参数放进去,再传入前端。
entity层主要是将数据库中的表一对一的写好,此处是直接用代码生成器生成
定义全局异常处理,使用时直接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; } }
也是全局异常处理,但和上面略有区别,非手动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); } }
同CustomException类格式一样,但这个侧重于处理Jwt模块的异常
/** * @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() { } }
这个类主要针对用户登录进行过滤
/** * @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() { } }
定义全局异常
/** * @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); }
处理全局的响应头(当接口返回类型非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); } } }
/** * @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; } } }
/** * @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; // } } }
这个目前不做过多赘述,我会尽快整理下mybatisplus的常用语法发出来
/** * @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(); } }
/** * @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; } }
/** * @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; } }
/** * @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; } }
/** * @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; } }
/** * @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()); } }
/** * @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()); }
/** * @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; } }
/** * @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; } }
/** * @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博客