# 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的工程

## 选择依赖

# 3.进行git上传
**具体操作看spring boot集成git**
# 4.配置日志输出
> 将logback-spring.xml放到resources之下,可以自动识别


## 修改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在里面进行复制黏贴想要显示的图案

> 浏览器直接访问地址是GET请求,会报405错误
> componentscan只能扫描子类之下的包
# 5.使用HTTP Client测试接口

> 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的配置文件

```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

```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请求)
}
}
```

# 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
```

## 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",
```

### 2.在main.ts中进行一个打印输出提示

## 3.修改axios请求地址支持多环境

# 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中可以直接加入一个语句

这个语句就代表了,开启分页功能
```java
PageInfo pageInfo = new PageInfo<>(ebookList);LOG.info("总行数:{}", pageInfo.getTotal());LOG.info("总页数:{}", pageInfo.getPages());
```
同时也有这个,用pageInfo来进行一个定义,可以返回,总行数,总页数

> **最基本四个数据: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//进行字符映射的,若是请求有参数,可以准确的指向参数
```

# 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);
}
}
```
==定时器的线程只有一个,会相互影响==