使用CASE语句进行自定义排序SELECT column_name(s) FROM table_name ORDER BY CASE column_name WHEN 'value1' THEN ... WHEN 'value2' THEN ... ELSE ... END; 使用FIELD函数进行自定义排序SELECT column_name(s) FROM table_name ORDER BY FIELD(column_name, value1, value2, ...) mybatis-plus中querywrapper使用 //设置是否置顶、截至日期优先展示 queryWrapper.orderByDesc("is_top", "deadline"); //设置任务未完成的优先展示，FIELD是数据库函数 queryWrapper.orderByAsc(" FIELD(state,'unfinish','finish') ");
【数据结构】绪论定义数据项：具有独立含义的数据最小单位，也称为字段或域数据对象：性质相同的数据元素的集合，是数据的一个子集数据结构：所有数据以及数据元素之间的关系，可以看成是相互之间存在着某种特定关系的数据元素的集合逻辑结构集合数据元素同属一个集合线性结构数据元素之间存在一对一关系树形结构数据元素之间存在一对多关系存储结构顺序存储结构采用一组连续的存储单元存放所有的数据元素链式存储结构每个元素用一个内存结点存储，结点和结点之间通过指针域进行联系索引存储结构存储数据元素时，同时建立附加的索引表存储所有数据元素信息的表称为主数据表，其中每个数据元素有一个关键字和对应的存储地址。索引表中的每一项称为索引项，索引项一般形式为“关键字，地址”。关键字可以唯一标识一个元素，地址对应元素的存储位置。所有的索引项一般按照关键字进行有序排列。哈希（或散列）存储结构根据元素的关键字通过哈希（或散列）函数直接计算出一个值，并将这个值作为该元素的存储地址算法算法是对特定问题求解步骤的一种描述，是指令的有限序列。具备有穷性、确定性、可行性、有输入（零个或多个）、有输出时间复杂度程序语句执行的最多次数，省略常系数，保留幂次。备注数据结构包括数据的逻辑结构和其存储结构
请根据你的了解，叙述C++的特点。C++对C有那些发展？C语言是一个结构化开发的语言，它主要思想是面向过程进行开发，重点在于设计算法和数据结构，而C++是在C语言的基础上构造了对象模型，更加适合用于工程化开发，主要思想是面向对象进行开发，但是又不完全等同于当下的面向对象的语言如Java，C++保留了C语言操作内存等的特性，同时检错机制要弱一点，而Java是更加简化了开发过程，降低了开发门槛。从时间维度上和实际功能效果上而言，C++是处于C语言过渡到Java语言的一个中间产物，既然是中间产物，就从不同程度上具备了其上级和下级的多种优势点。目前经常见到的C++特性其本质也就是面向对象的特性，继承、多态、封装。所谓继承，就是新类从已有类那里得到已有的特性。类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类，产生的心类称为派生类或子类。所谓多态，简单概括未“一个几口，多种方法”，即使用的是同一个接口，但是效果却各不相同，多态有两种形式的多态，一种是静态多态，一种是动态多态。所谓封装，就是指将抽象得到的数据和行为相结合，形成一个有机的整体，也就是将数据和操作数据进行有机的结合，形成类，其中数据和函数都是类的成员，目的在于将对象的使用者和设计者分开。一个C++程序由哪几部分构成？其中的每一部分分别起什么作用？C++程序的基本组成主要有注释、预编译代码、名字空间、主程序、函数。从接受以恶搞任务到获得最终结果，一般要经过几个步骤？C++中从拿到一个任务到最终结果有比编辑、预处理、编译、连接、装入、运行六步。请说明编辑、编译、连接的作用。在编辑后得到的目标文件为什么不能直接运行？简言之，编辑就是编写程序，编译是将源代码翻译成为目标代码，连接就是将目标文件连接生成可执行文件。C++从程序编辑完成到执行成功都会经过5个步骤，分别是预处理、编译、汇编、连接和执行。预处理过程主要处理那些源代码文件以#开始的预编译指令，预处理时可将所有的宏定义展开并且替换掉#include。编译过程是把预处理完的文件进行一系列的此法分析、语法分析以及优化后产生响应的汇编代码文件，这个过程是整体程序构建的核心部分，也是最复杂的部分。汇编过程实际是指将汇编语言代码翻译成目标机器代码的过程，它没有复杂的语法，也没有语义，也不需要做指令优化，只是根据汇编指令和及其指令的对照表一一翻译。连接过程是指前面提到的汇编过程生成的目标文件属于分离状态，并不能立即被执行，因为各个源文件之间可能是有相互联系的。连接就是将源程序产生的多个目标文件连接未一个整体。运行过程就是指执行前面连接过程产生的二进制文件（.exe文件）。
《深入理解计算机系统》之计算机系统漫游
计算机系统漫游信息就是位+上下文源程序设计上就是一个由值0和1组成的位（又称比特）序列，8个位被组织成一组，称为字节。每个字节表示程序中的某些文本字符。大部分的现代计算机系统都实用ASCII来表示每个文本字符，这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。基本思想：系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据，都是由一串比特进行表示的。区分不同的数据对象的唯一方法是我们读到这些数据对象时的上下文。比如，在不同的上下文中，一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。源文件到目标文件的转化从源文件到目标文件的转化是由编译器驱动程序完成的从源文件到目标文件的翻译一共需要分为四个阶段，执行这四个阶段的程序（预处理器、编译器、汇编器和链接器）一起构成了编译系统预处理阶段预处理器（cpp）根据以字符#开头的命令，修改原始的C语言程序。（相当于完成所有宏命令的引入）编译阶段将C语言编译成为汇编语言汇编阶段汇编器将汇编语言翻译成为机器语言指令链接阶段链接阶段时将系统函数库进行引入，链接器就负责处理这种合并。结果将生成一个可执行部目标文件，可以被加载到内存中，由系统进行执行。知道编译系统工作有哪些好处优化程序性能理解链接时出现的错误避免安全漏洞系统的硬件组成总线贯穿整个系统的是一组电子管道，称作总线，它携带信息字节并负责在各个部件间进行传递。通常总线被设计成传送定长的字节块，也就是字。字中的字节数（即字长）是一个基本的系统参数，各个系统中都不尽相同。现在大多数机器字长要么是4个字节（32位），要么是8个字节（64位）I/O设备I/O（输入/输出）设备是系统和外部世界的联系通道。每个I/O设备都通过一个控制器或/适配器与I/O总线相连。控制器和适配器之间的区别主要是在于它们的封装方式。控制器是I/O设备本身或者系统的主印制电路板（通常称作主板）上的芯片组。而适配器则是一块插在主板插槽上的卡。无论如何，它们的功能都是在I/O总线和I/O设备之间进行传递信息。主存主存是一个临时存储设备，在处理器执行程序时，用来存放程序和程序处理的数据。从物理上来说，主存是由一组动态随机存取存储器（DRAM）芯片组成的。从逻辑上来说，存储器是一个相信的字节数组，每个字节都有其唯一的地址（数组索引），这些地址都是从零开始的。处理器中央处理器单元（CPU），简称处理器，是解释（或执行）存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备（或寄存器），成为程序计数器（PC）。在任何时候，PC都指向主存中的某条机器语言指令（即含有该指令的地址）。高速缓存针对处理器和主存在速度方面的差异，系统设计者采用了更小更快的存储设备，称为高速缓存存储器，作为暂时的集结区域，存放处理器近期可能会需要的信息。L1和L2高速缓存是用一种叫做静态随机访问存储器的硬件技术实现的。比较新的、处理能力更强大的系统甚至用三级高速缓存：L1、L2、L3。系统可以获得一个很大存储器，同时访问速度也很快，原因是利用了高速缓存的局部性原理，即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据，大部分内存操作都能在快速的高速缓存中完成。存储器的层次结构寄存器L1高速缓存（SRAM）L2高速缓存（SRAM）L3高速缓存（SRAM）主存（DRAM）本地二级存储（本地磁盘）远程二级存储（分布式文件系统，Web服务器）操作系统管理硬件文件是对I/O设备的抽象表示虚拟内存是对主存和磁盘I/O设别的抽象表示进程是对处理器、主存和I/O设备的抽象表示进程上下文：操作系统保持跟踪进程运行所需的所有状态信息。这种状态，也就是上下文，包括许多信息，比如PC和寄存器文件的当前值，以及主存的内容。上下文切换：在任何一个时刻，单处理器的系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时，就会进行上下文切换，即保存当前进程的上下文、恢复新进程的上下文，然后将控制权传递到新进程。新进程就会从它上次停止的地方继续开始。从一个进程到另一个进程的转换是由操作系统内核管理的，内核是操作系统代码常驻内存的部分。注意：内核并不是一个独立的进程，相反，它是系统管理全部进程所用代码和数据结构的集合。线程一个进程实际上可以由多个成为线程的执行单元组成，每个线程都运行在进程的上下文中，并共享同样的代码和全局数据。虚拟内存虚拟内存是一个抽象概念，它为每一个进程提供了一个假象，即每个进程都在独占地使用主存。每个进程看到的内存都是一致的，称为虚拟地址空间。（此处暂不赘述）文件文件就是字节序列，仅此而已。每一I/O设备，包括磁盘、键盘、显示器，甚至网络，都可以看成是文件。Amdahl定律主要思想：当我们对系统的某个部分加速时，其对系统整体性能的影响取决于该部分的重要性和加速程度。主要观点：要想显著加速整个系统，就必须提升全系统中相当大的部分的速度。并发和并行并发：两个任务交替执行并行：两个任务同时执行多核处理器是值将多个CPU集成到一个集成电路芯片上。小结计算机系统由硬件和软件组成，它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位，它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式，开始时时ASCII文本，然后被编译器和链接器翻译成二进制可执行文件。处理器读取并解释存放在主存里的二进制指令。因为计算机花费了大量的时间在内存、I/O设别和CPU寄存器之间复制数据，所以将系统中的存储设备划分成层次结构——CPU寄存器在顶部，接着是多层的硬件高速缓存存储器、DRAM主存和硬盘存储器。在层次模型中，位于更高层的存储设备比底层的存储设备要更快，单位比特的造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识，程序员可以优化C程序的性能。操作系统内核是应用程序和硬件之间的媒介。它提供了三个基本的抽象：文件是对I/O设备的抽象，虚拟内存是对主存和磁盘的抽象，进程是处理器、主存和I/O设备的抽象。最后，网络提供了计算机系统之间通信的手段，从特殊系统的角度来看，网络就是一种I/O设备。
vue挂载流程和模板编译基于源码构建的两种版本先了解一下vue基于源码构建的两个版本，一个是runtime only(一个只包含运行时的版本)，另一个是runtime + compiler(一个同时包含编译器和运行时的版本)。而两个版本的区别仅在于后者包含了一个编译器。那么什么叫做编译器：编译器就是将“一种语言（通常为高级语言）”翻译为“另一种语言（通常为低级语言）”的程序Runtime + Compiler一个完整的Vue版本是包含编译器的，我们可以使用template进行模板编写。编译器会自动将模板字符串编译成渲染函数的代码,源码中就是render函数。 如果需要在客户端编译模板 (比如传入一个字符串给 template 选项，或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板)，就需要一个包含编译器的版本Runtime Only只包含运行时的代码拥有创建Vue实例、渲染并处理Virtual DOM等功能，基本上就是除去编译器外的完整代码。Runtime Only的适用场景有两种： 1.我们在选项中通过手写render函数去定义渲染过程，这个时候并不需要包含编译器的版本便可完整执行实例挂载大致流程：确认挂载节点编译模板为render函数渲染函数转换Virtual DOM创建真实节点详细流程：确定挂载的DOM元素,这个DOM需要保证不能为html，body这类根节点。渲染有两种方式，一种是通过template模板字符串，另一种是手写render函数，template模板需要运行时进行编译，而后一个可以直接用render选项作为渲染函数。因此挂载阶段会有两条分支，template模板会先经过模板的解析，最终编译成render渲染函数参与实例挂载，而手写render函数可以绕过编译阶段，直接调用挂载的$mount方法。针对template而言，它会利用Vue内部的编译器进行模板的编译，字符串模板会转换为抽象的语法树，即AST树，并最终转化为一个类似function(){with(){}}的渲染函数。无论是template模板还是手写render函数，最终都将进入mountComponent过程,这个阶段会实例化一个渲染watcher，渲染watcher的回调函数有两个执行时机，一个是在初始化时执行，另一个是当vm实例检测到数据发生变化时会再次执行回调函数。回调函数是执行updateComponent的过程，这个方法有两个阶段，一个是vm._render,另一个是vm._update。 vm._render会执行前面生成的render渲染函数，并生成一个Virtual Dom tree,而vm._update会将这个Virtual Dom tree转化为真实的DOM节点。template模块书写方式编译流程图小结当我们传入选项进行实例化时，最终的目的是将选项渲染成页面真实的可视节点。这个选项有两种形式，一个是以template模板字符串传入，另一个是手写render函数形式传入，不论哪种，最终会以render函数的形式参与挂载，render是一个用函数封装好的with语句。渲染真实节点前需要将render函数解析成虚拟DOM,虚拟DOM是js和真实DOM之间的桥梁。最终的_update过程让将虚拟DOM渲染成真实节点
代码层级梳理代码层级annotation层ExcelVoAttribut主要用于写@ExcelVoAttribute注解，可以更灵活的配置Excel数据表导出的一些基本配置LogDirection主要用于写@LogDirection注解，注解里包含一个字段（接口描述），通过接口添加这个注解，填写上接口的功能，当用户在访问接口时候，可以利用aop技术，把用户的操作记到日志里aspect层LogDrectionAspect主要定义了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/>common层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层CaptchaConfig主要是为kaptcha谷歌验证码的一些基本信息进行配置，比如字体，验证码位数等等附上一些kaptha的基本配置信息Constant描述默认值kaptcha.border图片边框，合法值：yes , noyeskaptcha.border.color边框颜色，合法值： r,g,b (and optional alpha) 或者 white,black,blue.blackkaptcha.border.thickness边框厚度，合法值：>01kaptcha.image.width图片宽200kaptcha.image.height图片高50kaptcha.producer.impl图片实现类com.google.code.kaptcha.impl.DefaultKaptchakaptcha.textproducer.impl文本实现类com.google.code.kaptcha.text.impl.DefaultTextCreatorkaptcha.textproducer.char.string文本集合，验证码值从此集合中获取abcde2345678gfynmnpwxkaptcha.textproducer.char.length验证码长度5kaptcha.textproducer.font.names字体Arial, Courierkaptcha.textproducer.font.size字体大小40px.kaptcha.textproducer.font.color字体颜色，合法值： r,g,b 或者 white,black,blue.blackkaptcha.textproducer.char.space文字间隔2kaptcha.noise.impl干扰实现类com.google.code.kaptcha.impl.DefaultNoisekaptcha.noise.color干扰 颜色，合法值： r,g,b 或者 white,black,blue.blackkaptcha.obscurificator.impl图片样式： 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpycom.google.code.kaptcha.impl.WaterRipplekaptcha.background.impl背景实现类com.google.code.kaptcha.impl.DefaultBackgroundkaptcha.background.clear.from背景颜色渐变，开始颜色light greykaptcha.background.clear.to背景颜色渐变， 结束颜色whitekaptcha.word.impl文字渲染器com.google.code.kaptcha.text.impl.DefaultWordRendererkaptcha.session.keysession keyKAPTCHA_SESSION_KEYkaptcha.session.datesession dateKAPTCHA_SESSION_DATECorsConfig主要是为跨域做一些基本的配置，学习阶段后端必备代码展示，防止以后忘记/** * @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层主要是将数据库中的表一对一的写好，此处是直接用代码生成器生成exceptionCustomException定义全局异常处理，使用时直接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模块的异常filterChannelFilter/** * @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层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); } } }interceptorJwtInterceptor/** * @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层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; } }