# Wiki知识库 **Repository Path**: aihs_work/wiki-knowledge-base ## Basic Information - **Project Name**: Wiki知识库 - **Description**: 仿慕课网wiki知识库 - **Primary Language**: Java - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 9 - **Created**: 2022-12-05 - **Last Updated**: 2022-12-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # wiki知识库 controller(一个总体逻辑,业务)——>serviceImpl(接口具体的实现方法)——>service(单纯某一项业务调用的接口) mapper(serviceimpl调用的一个sql处理的东西)mapper的xml文件,写具体的sql语句 # 1.开发环境 **Mysql8.0** **jdk8** **Spring boot 2.52** **Vue Cli 4.5.13** # 2.新建spring boot的工程 ![image-20210722101509571](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722101509571.png) ## 选择依赖 ![image-20210722101546172](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722101546172.png) # 3.进行git上传 **具体操作看spring boot集成git** # 4.配置日志输出 > 将logback-spring.xml放到resources之下,可以自动识别 ![image-20210722103436027](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722103436027.png) ![image-20210722103521986](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722103521986.png) ## 修改spring boot启动类,进行一个日志输出改变 ```java package com.yyg.wiki; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.env.Environment; @SpringBootApplication public class WikiApplication { private static final Logger LOG=LoggerFactory.getLogger(WikiApplication.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(WikiApplication.class); Environment env=app.run(args).getEnvironment(); LOG.info("启动成功"); LOG.info("地址:\thttp:127.0.0.1:{}",env.getProperty("server.port")); } } ``` > 可以自定义一些配置,可以打印配置提交,可以打印测试地址 ## 修改启动图案 增加banner.txt在里面进行复制黏贴想要显示的图案 ![image-20210722110009134](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722110009134.png) > 浏览器直接访问地址是GET请求,会报405错误 > componentscan只能扫描子类之下的包 # 5.使用HTTP Client测试接口 ![image-20210722111319775](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722111319775.png) > gtr进行测试的是get请求 > > ptr进行测试的post请求 # 6.mybatis generator代码生成插件 ```xml org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok org.mybatis.generator mybatis-generator-maven-plugin 1.4.0 src/main/resources/generator/generator-config.xml true true ``` **pom中引入插件mybatis-generator-maven-plugin** > 一定要是mybatis才有,mybatis-plus就是代码生成器了,逆向工程工具 ## 创建generator的配置文件 ![image-20210722152836411](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210722152836411.png) ```java ``` > 一定要正确,不正确无法正确创建出来,比较麻烦。 ## pom文件 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.0 com.yyg wiki 0.0.1-SNAPSHOT wiki yyg Wiki 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true org.freemarker freemarker 2.3.28 com.baomidou mybatis-plus-generator 3.4.1 io.springfox springfox-swagger2 2.7.0 com.github.xiaoymin swagger-bootstrap-ui 1.9.6 org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 mysql mysql-connector-java 8.0.22 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok org.mybatis.generator mybatis-generator-maven-plugin 1.4.0 src/main/resources/generator/generator-config.xml true true mysql mysql-connector-java 8.0.22 ``` # 7.设置通用返回类resp ![image-20210723094156513](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210723094156513.png) ```java package com.yyg.wiki.resp; /** * ClassName: CommonResp * Description: * date: 2021/7/23 9:38 * * @author 17878 * @since JDK 1.8 */ public class CommonResp {//代表泛型数据,可以存储基本类型和对象类型 /** * 业务上的成功或失败 */ private boolean success = true; /** * 返回信息 */ private String message; /** * 返回泛型数据,自定义类型 */ private T content; public boolean getSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getContent() { return content; } public void setContent(T content) { this.content = content; } @Override public String toString() { final StringBuffer sb = new StringBuffer("ResponseDto{"); sb.append("success=").append(success); sb.append(", message='").append(message).append('\''); sb.append(", content=").append(content); sb.append('}'); return sb.toString(); } } ``` > tips:可以在通用返回类中加入其他通用属性,接口版本号,或者返回码等等 # 8.封装beanUtils工具类(进行单体复制) ```java package com.yyg.wiki.util; import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; /** * ClassName: CopyUtil * Description: * date: 2021/7/23 10:42 * * @author 17878 * @since JDK 1.8 */ public class CopyUtil { /** * 单体复制 */ public static T copy(Object source, Class clazz) { if (source == null) { return null; } T obj = null; try { obj = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); return null; } BeanUtils.copyProperties(source, obj); return obj; } /** * 列表复制 */ public static List copyList(List source, Class clazz) { List target = new ArrayList<>(); if (!CollectionUtils.isEmpty(source)){ for (Object c: source) { T obj = copy(c, clazz); target.add(obj); } } return target; } } ``` ## 用封装类后 ```java package com.yyg.wiki.service; import com.yyg.wiki.domain.Ebook; import com.yyg.wiki.domain.EbookExample; import com.yyg.wiki.mapper.EbookMapper; import com.yyg.wiki.req.EbookReq; import com.yyg.wiki.resp.EbookResp; import com.yyg.wiki.util.CopyUtil; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * ClassName: EbookService * Description: * date: 2021/7/22 15:00 * * @author 17878 * @since JDK 1.8 */ @Service public class EbookService { @Resource private EbookMapper ebookMapper; public List list(EbookReq Req){ EbookExample ebookExample = new EbookExample(); EbookExample.Criteria criteria = ebookExample.createCriteria(); criteria.andNameLike("%"+Req.getName()+"%"); List ebookList = ebookMapper.selectByExample(ebookExample); // List respList=new ArrayList<>(); // for (Ebook ebook : ebookList) { // // EbookResp ebookResp = CopyUtil.copy(ebook, EbookResp.class); // respList.add(ebookResp); // } List list = CopyUtil.copyList(ebookList, EbookResp.class); return list; } } ``` # 9.进行跨域处理 ## 1.vue端进行axios的导入,进行一个跨域处理 ## 2.后端写一个config类,进行一个跨域处理类 ```java package com.yyg.wiki.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**")//针对所有的接口 .allowedOriginPatterns("*")//允许来源 .allowedHeaders(CorsConfiguration.ALL)//所有的 .allowedMethods(CorsConfiguration.ALL)//方法都支持 .allowCredentials(true)//带上凭证,cookies什么的 .maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求) } } ``` ![image-20210723155717823](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210723155717823.png) # 10.多环境配置 ## 1.增加开发和生产配置文件 ### vue进行配置 在web文件夹下创建.env.dev ``` //工作环境NODE_ENV=developmentVUE_APP_SERVER=http://127.0.0.1:8880 ``` ``` //生成环境NODE_ENV=productionVUE_APP_SERVER=http://127.0.0.1:8880 ``` ![image-20210726093017262](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726093017262.png) ## 2.修改编译和启动支持多环境 ### 1.在package.json中进行配置 ```json "serve-dev": "vue-cli-service serve --mode dev","serve-prod": "vue-cli-service serve --mode prod","build-dev": "vue-cli-service build --mode dev","build-prod": "vue-cli-service build --mode prod", ``` ![image-20210726093714938](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726093714938.png) ### 2.在main.ts中进行一个打印输出提示 ![image-20210726093802616](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726093802616.png) ## 3.修改axios请求地址支持多环境 ![image-20210726095712221](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726095712221.png) # 11.axios拦截器打印前端日志 ```js /** * axios拦截器 */axios.interceptors.request.use(function (config) { console.log('请求参数:', config); return config;}, error => { return Promise.reject(error);});axios.interceptors.response.use(function (response) { console.log('返回结果:', response); return response;}, error => { console.log('返回错误:', error); return Promise.reject(error);}); ``` # 12.spring boot配置过滤器 ## 1.创建filter包 创建LogFilter ## 2.偏固定代码 ```java package com.yyg.wiki.filter;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException; @Component public class LogFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(LogFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 打印请求信息 HttpServletRequest request = (HttpServletRequest) servletRequest; LOG.info("------------- LogFilter 开始 -------------"); LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); LOG.info("远程地址: {}", request.getRemoteAddr()); long startTime = System.currentTimeMillis(); filterChain.doFilter(servletRequest, servletResponse); LOG.info("------------- LogFilter 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); } } ``` # 13.java中的拦截器 ## 1.创建interceptor包 在包中创建LogInterceptor进行一个拦截器的编写 再在config包中创建一个SpringMvcConfig类,进行一个配置 ## 2.代码编写 ### 1.LogInterceptor ```java package com.yyg.wiki.interceptor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 /login */@Componentpublic class LogInterceptor implements HandlerInterceptor { private static final Logger LOG = LoggerFactory.getLogger(LogInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 打印请求信息 LOG.info("------------- LogInterceptor 开始 -------------"); LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); LOG.info("远程地址: {}", request.getRemoteAddr()); long startTime = System.currentTimeMillis(); request.setAttribute("requestStartTime", startTime); return true;//当时false时,后面的逻辑就不会执行,就会结束进程 } //结束的打印的参数 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long startTime = (Long) request.getAttribute("requestStartTime"); LOG.info("------------- LogInterceptor 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); }} ``` ### 2.SpringMvcConfig ```java package com.yyg.wiki.config;import com.yyg.wiki.interceptor.LogInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configurationpublic class SpringMvcConfig implements WebMvcConfigurer { @Resource LogInterceptor logInterceptor; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logInterceptor) .addPathPatterns("/**").excludePathPatterns("/login");//可以排除,不拦截的接口,范围比过滤器小了一些 }} ``` # **拦截器和过滤器的区别**! **①拦截器是基于java的反射机制的,而过滤器是基于函数回调。   ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。   ③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。   ④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。   ⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。**   **⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。**   ***\*⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。\****   **⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。**   ***\*⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。\**** ***\*拦截器可以获取ioc中的service bean实现业务逻辑,\*\*\*\*拦截器可以获取ioc中的service bean实现业务逻辑,\*\*\*\*\*\*\*\*拦截器可以获取ioc中的service bean实现业务逻辑,\*\*\*\*\**** # 14.Spring bootAOP的使用 配置AOP打印接口耗时,请求参数,返回参数 创建aspect的包,在包下,建立LogAspect ```java package com.yyg.wiki.aspect;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.support.spring.PropertyPreFilters;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.multipart.MultipartFile;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;@Aspect@Componentpublic class LogAspect { private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class); /** 定义一个切点 */ @Pointcut("execution(public * com.yyg.*.controller..*Controller.*(..))") public void controllerPointcut() {} @Before("controllerPointcut()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Signature signature = joinPoint.getSignature(); String name = signature.getName(); // 打印请求信息 LOG.info("------------- 开始 -------------"); LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name); LOG.info("远程地址: {}", request.getRemoteAddr()); // 打印请求参数 Object[] args = joinPoint.getArgs(); // LOG.info("请求参数: {}", JSONObject.toJSONString(args)); Object[] arguments = new Object[args.length]; for (int i = 0; i < args.length; i++) { if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) { continue; } arguments[i] = args[i]; } // 排除字段,敏感字段或太长的字段不显示 String[] excludeProperties = {"password", "file"}; PropertyPreFilters filters = new PropertyPreFilters(); PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); excludefilter.addExcludes(excludeProperties); LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); } @Around("controllerPointcut()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); // 排除字段,敏感字段或太长的字段不显示 String[] excludeProperties = {"password", "file"}; PropertyPreFilters filters = new PropertyPreFilters(); PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); excludefilter.addExcludes(excludeProperties); LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter)); LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); return result; }} ``` # 15.使用PageHelper进行后端分页 ## 1.导入依赖 ```xml com.github.pagehelper pagehelper-spring-boot-starter 1.2.13 ``` ## 2.直接在service中可以直接加入一个语句 ![image-20210726153635407](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726153635407.png) 这个语句就代表了,开启分页功能 ```java PageInfo pageInfo = new PageInfo<>(ebookList);LOG.info("总行数:{}", pageInfo.getTotal());LOG.info("总页数:{}", pageInfo.getPages()); ``` 同时也有这个,用pageInfo来进行一个定义,可以返回,总行数,总页数 ![image-20210726154554594](C:\Users\17878\AppData\Roaming\Typora\typora-user-images\image-20210726154554594.png) > **最基本四个数据:PageHelper.startPage(pageNum:1,pageSize:3)** > > **pageInfo.getTotal()**:总行数 > > **pageInfo.getPages()**:总页数 # 16:封装分页请求参数和返回参数 ## 请求封装参数:PageReq ## 返回结果封装:PageResp # 17.雪花算法 见雪花算法笔记 # 18.集成Validation做参数校验 看另一个笔记 # 19.设置异常自定义 详情见,另一个文档:自定义文档 # 20.密码的两层加密处理 ## 1.后端使用md5加密算法 ```java //算法内置,直接调用DigestUtils就可以使用md5的加密算法.@PostMapping("/save")public CommonResp save(@Valid @RequestBody UserSaveReq req) { req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes())); CommonResp resp = new CommonResp(); userService.save(req); return resp;} ``` ## 2.前端密文传递到后端 ```js var KEY = "!@#QWERT";//盐值,在加密后的密码加上一些盐值,让别人更难破解/* * Configurable variables. You may need to tweak these to be compatible with * the server-side, but the defaults work in most cases. */var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode *//* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */function hexMd5(s) { return hex_md5(s);}function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }/* * Perform a simple self-test to see if the VM is working */function md5_vm_test(){ return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";}/* * Calculate the MD5 of an array of little-endian words, and a bit length */function core_md5(x, len){ /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return Array(a, b, c, d);}/* * These functions implement the four basic operations the algorithm uses. */function md5_cmn(q, a, b, x, s, t){ return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);}function md5_ff(a, b, c, d, x, s, t){ return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);}function md5_gg(a, b, c, d, x, s, t){ return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);}function md5_hh(a, b, c, d, x, s, t){ return md5_cmn(b ^ c ^ d, a, b, x, s, t);}function md5_ii(a, b, c, d, x, s, t){ return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);}/* * Calculate the HMAC-MD5, of a key and some data */function core_hmac_md5(key, data){ var bkey = str2binl(key); if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); var ipad = Array(16), opad = Array(16); for(var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); return core_md5(opad.concat(hash), 512 + 128);}/* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */function safe_add(x, y){ var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF);}/* * Bitwise rotate a 32-bit number to the left. */function bit_rol(num, cnt){ return (num << cnt) | (num >>> (32 - cnt));}/* * Convert a string to an array of little-endian words * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. */function str2binl(str){ var bin = Array(); var mask = (1 << chrsz) - 1; for(var i = 0; i < str.length * chrsz; i += chrsz) bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); return bin;}/* * Convert an array of little-endian words to a string */function binl2str(bin){ var str = ""; var mask = (1 << chrsz) - 1; for(var i = 0; i < bin.length * 32; i += chrsz) str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); return str;}/* * Convert an array of little-endian words to a hex string. */function binl2hex(binarray){ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); } return str;}/* * Convert an array of little-endian words to a base-64 string */function binl2b64(binarray){ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var str = ""; for(var i = 0; i < binarray.length * 4; i += 3) { var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); for(var j = 0; j < 4; j++) { if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } } return str;} ``` ==当要引用别的js的文件的时候,可以使用定义的方式,将js的文件进行引入进来== ```js declare let hexMd5: any; declare let KEY: any; ``` # 21.单点登录token与JWT介绍 ## 登录 ### 登录 1.前端输入用户名和密码 2.校验用户名密码 3.生成token 4.后端保存token(redis) 5.前端保存token ### 校验 1.前端请求时,带上token(放在header) 2.登录要有拦截器,校验token(到redis获取token) 3.校验成功则继续后面的业务 4.校验失败则回到登录页面 ## 单点登陆系统 淘宝,支付宝.所有需要登陆的地方,都在一个地方.调用一次登录就可以所有都登陆 单点一般负责:用户管理.登录.登录校验.退出登录 | | 注册,有可能只提供接口,也可能包括界面 ## token和JWT token和redis的组合:token是无意义的.可以用MD5字符串,可以用时间戳 JWT:token是由意义的,加密的,包含业务信息,一般是用户信息,可以被解出来.不需要存到redis ```xml io.jsonwebtoken jjwt 0.9.1 com.auth0 java-jwt 3.5.0 JwtUtil.sign 加密方法JwtUtil.verity 校验方法 ``` ==登录流程的一个处理== 前端使用md5加密算法,先加密之后,将加密的值传递到后端,与后端的数据库的值进行对比,如果正确就会进行登陆操作。 如果错误,弹出错误的提示信息 # 22.登录成功处理 ## 1.后端保存用户信息 集成redis 登陆成功后,生成token,以token为key,以用户信息为value,放入redis中 ## 2.前端显示登录用户 header显示登录昵称 使用vuex+sessionStorage保存登录信息 # 23.集成redis ```xml org.springframework.boot spring-boot-starter-data-redis ``` userController导入redis的工具类 ```java @Resourceprivate RedisTemplate redisTemplate; ``` 注入雪花算法工具类 ```java @Resourceprivate SnowFlake snowFlake; ``` 在登录方法中,赋予这个登录用户名一个token。存储到reids ```java @PostMapping("/login") public CommonResp login(@Valid @RequestBody UserLoginReq req) { req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes())); CommonResp resp = new CommonResp(); UserLoginResp userLoginResp=userService.login(req); //生成单点登录token将token放入redis,同时将resp的返回传递到前端 Long token=snowFlake.nextId(); LOG.info("生成单点登录token:{},放入redis中",token); userLoginResp.setToken(token.toString()); //将一个类,放入reids中需要进行一个序列化 redisTemplate.opsForValue().set(token,userLoginResp,3600*24, TimeUnit.SECONDS); resp.setContent(userLoginResp); return resp; } ``` ==将一个类放入reids中需要进行序列化,不然回出问题== ==序列化是做远程传输的,一般都要序列化== 可以使用,返回的类集成public class UserLoginResp implements Serializable来进行序列化操作 也可以,将类,转化为json字符串的操作,进行序列化 ## 1.本地redis配置 ```properties #redis单机配置redis.pool.host=127.0.0.1redis.pool.port=6379#最大能够保持idel状态的对象数redis.pool.maxIdle=100#最大分配的对象数redis.pool.maxTotal=6000#等待可用连接的最大时间,单位是毫秒,默认值为-1,表示永不超时。#如果超过等待时间,则直接抛出JedisConnectionExceptionredis.pool.maxWaitMillis=10000redis.pool.timeOut=20#多长时间检查一次连接池中空闲的连接redis.pool.timeBetweenEvictionRunsMillis=30000#空闲连接多长时间后会被收回redis.pool.minEvictableIdleTimeMillis=30000#当调用borrow Object方法时,是否进行有效性检查#在borrow(用)一个jedis实例时,是否提前进行validate(验证)操作;#如果为true,则得到的jedis实例均是可用的redis.pool.testOnBorrow=true########reids编码格式redis.encode=utf-8######缓存过期时间 秒 1000*60*60*24*7 七天redis.expire=604800000####是否开启Redis服务应用redis.unlock=false ``` # 23.vuex是一个全局性响应变量。 可以定义一些方法,进行一个响应式触发 # 24.集成swagger接口文档 ```xml io.springfox springfox-boot-starter 3.0.0 com.github.xiaoymin knife4j-spring-boot-starter 3.0.2 ``` # 25.集成H5的sessionStorage # 26.后端接口增加登录校验 ## 后端增加拦截器,校验token有效性 ### 配置拦截器 ==拦截器的好处,在于可以拦截多种操作,不用写大量的if/else判断== ```java package com.yyg.wiki.interceptor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 */@Componentpublic class LoginInterceptor implements HandlerInterceptor { private static final Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class); @Resource private RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 打印请求信息 LOG.info("------------- LoginInterceptor 开始 -------------"); long startTime = System.currentTimeMillis(); request.setAttribute("requestStartTime", startTime); // OPTIONS请求不做校验, // 前后端分离的架构, 前端会发一个OPTIONS请求先做预检, 对预检请求不做校验 if(request.getMethod().toUpperCase().equals("OPTIONS")){ return true; } String path = request.getRequestURL().toString(); LOG.info("接口登录拦截:,path:{}", path); //获取header的token参数 String token = request.getHeader("token"); LOG.info("登录校验开始,token:{}", token); if (token == null || token.isEmpty()) { LOG.info( "token为空,请求被拦截" ); response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } Object object = redisTemplate.opsForValue().get(token); if (object == null) { LOG.warn( "token无效,请求被拦截" ); //返回401,代表没有权限 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } else { LOG.info("已登录:{}", object); return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long startTime = (Long) request.getAttribute("requestStartTime"); LOG.info("------------- LoginInterceptor 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// LOG.info("LogInterceptor 结束"); }} ``` 在SpringMvcConfig中进行配置 ```javad import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configurationpublic class SpringMvcConfig implements WebMvcConfigurer { @Resource LoginInterceptor loginInterceptor; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/test/**", "/redis/**", "/user/login", "/category/all", "/ebook/list", "/doc/all/**", "/doc/find-content/**" ); }} ``` ## 前端请求增加token的参数 # 27.路由登录拦截器 ```java // 路由登录拦截router.beforeEach((to, from, next) => { // 要不要对meta.loginRequire属性做监控拦截 if (to.matched.some(function (item) { console.log(item, "是否需要登录校验:", item.meta.loginRequire); return item.meta.loginRequire })) { const loginUser = store.state.user; if (Tool.isEmpty(loginUser)) { console.log("用户未登录!"); next('/'); } else { next(); } } else { next(); }}); ``` # 28.sql新增会出现的问题 1.当insert的时候,如果写了字段不去执行,就会产生null的问题。当没有写这个字段的时候,这个字段才会走默认值 # 29.spring boot3中注解用法 ```java @PathVariable//是spring3.0的一个新功能:接收请求路径中占位符的值@PathVariable("xxx")通过 @PathVariable 可以将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariable(“xxx“) @RequestMapping(value=”user/{id}/{name}”)请求路径:http://localhost:8080/hello/show5/1/james//进行字符映射的,若是请求有参数,可以准确的指向参数 ``` ![img](https://img-blog.csdnimg.cn/2018111800431828.png) # 30.服务器拿到远程ip,根据ip做过滤 **1.点暂时先看看redis中有没有ip+docid,有表示点赞过,没有就没有点赞过** **2.点赞成功后往redis中放ip+docid** **在aspect中进行nginx的反向代理**在发布后,前后端不会直接进行交互,需要用到nginx进行反向代理。进行代理的操作 ```java RequestContext.setRemoteAddr(getRemoteIp(request)); ``` ```java /** * 使用nginx做反向代理,需要用该方法才能取到真实的远程IP * @param request * @return */public String getRemoteIp(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip;} ``` **写redis的工具类。进行ip地址的获取** ```java package com.yyg.wiki.util; import java.io.Serializable; /** * * 功能描述:获取当前远程ip的。进行线程获取,每一个线程可以获取一个IP地址 * * @param: * @return: * @auther: 17878 * @date: 2021/8/9 15:47 */ public class RequestContext implements Serializable { private static ThreadLocal remoteAddr = new ThreadLocal<>(); public static String getRemoteAddr() { return remoteAddr.get(); } public static void setRemoteAddr(String remoteAddr) { RequestContext.remoteAddr.set(remoteAddr); } } ``` 写redis的方法类,进行ip地址的书写进去,同时设置过期时间 ```java package com.yyg.wiki.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; @Component public class RedisUtil { private static final Logger LOG = LoggerFactory.getLogger(RedisUtil.class); @Resource private RedisTemplate redisTemplate; /** * true:不存在,放一个KEY * false:已存在 * @param key * @param second * @return */ public boolean validateRepeat(String key, long second) { if (redisTemplate.hasKey(key)) { LOG.info("key已存在:{}", key); return false; } else { LOG.info("key不存在,放入:{},过期 {} 秒", key, second); redisTemplate.opsForValue().set(key, key, second, TimeUnit.SECONDS); return true; } } } ``` # 31.启动定时器 ## 1.在spring boot启动类加@enableScheduling ## 2.写一个job包,进行一个定时任务 ```java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class TestJob { private static final Logger LOG = LoggerFactory.getLogger(TestJob.class); /** * 固定时间间隔,fixedRate单位毫秒 */ @Scheduled(fixedRate = 1000) public void simple() throws InterruptedException { SimpleDateFormat formatter = new SimpleDateFormat("mm:ss"); String dateString = formatter.format(new Date()); Thread.sleep(2000); LOG.info("每隔5秒钟执行一次: {}", dateString); } /** * 自定义cron表达式跑批 * 只有等上一次执行完成,下一次才会在下一个时间点执行,错过就错过 */ @Scheduled(cron = "*/1 * * * * ?") public void cron() throws InterruptedException { SimpleDateFormat formatter = new SimpleDateFormat("mm:ss SSS"); String dateString = formatter.format(new Date()); Thread.sleep(1500); LOG.info("每隔1秒钟执行一次: {}", dateString); } } ``` ==定时器的线程只有一个,会相互影响==