- 微服务阶段

javase:OOP

mysql:持久化

html+css+js+jquery+框架

javaweb:独立开发MVC三层架构的网站

SSM:框架;简化我们的开发流程,配置也开始较为复杂

war:tomcat运行

Spring再简化:SpringBoot -jar:内嵌tomcat;微服务·架构!

服务越来越多:springcloud

  • SpringBoot学习路线

SpringBoot

  • 是什么
  • 如何配置编写yaml
  • 自动装配的原理
  • 集成web开发:业务的核心
  • 分布式开发:Dubbo+zookeeper
  • swagger:接口文档
  • 任务调度
  • SpringSecurity:Shiro

SpringCould

  • 微服务
  • springcould入门
  • Resful风格
  • Eureka
  • Ribbon
  • Feign
  • HyStrix
  • Zuul路由网关
  • SpringCloud config:git

- 原理初探

自动配置:

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中
  • 我们写或引入一些Springboot依赖的时候,不需要指定版本,就因为由这些版本仓库

启动器:

  • ```xml

    org.springframework.boot spring-boot-starter
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    - **启动器:**Springboot的启动场景

    - 比如spring-boot-starter-web,他会帮我们导入web环境的所有依赖!

    - springboot会将

    - 注解

    - ```java
    @SpringBootApplication:标注这个类是一个springboot的应用:启动类下的所有资源被导入

    - SpringBootConfiguration:Springboot配置
    - @Configuration:Spring的配置类
    - @Component:说明这也是一个Spring的组件
    - @EnableAutoConfiguration:自动配置
    - @AutoConfigurationPackage:自动配置包
    - @Import(AutoConfigurationPackages.Registrat.class):自动配置"包注册"
    - @Import(AutoConfigurationImportSelector.class):自动配置导入选择
    //获取所有配置
    List<String> configurations=getCandidateConfigurations(annotationMetadata,attribuates);

    总结:springboot所有的自动配置都是在启动的时候扫面并加载:spring.factories所有的自动配置类都在这里,但不一定生效,要判断条件是否成立,只有导入对应的star,就有对应的启动器,有了启动器,自动配置就会生效,然后就配置成功。

  • 步骤

    1. springboot启动,从类路径下/META-INF/spring.factories获取指定的值;
    2. 将这些自动配置的类导入容器,自动装配生效
    3. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfig-2.2.0.RELEASE.JAR中
    4. 他会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器

- 了解主启动类怎么运行

开始执行run()方法

  1. 推断应用类型是普通项目还是Web项目
  2. 查找并加载所有可用初始化器,设置带initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类

- yaml语法

语法结构:key: 空格 value

1
2
3
4
5
6
7
8
9
10
11
12
#对象
student:
name: qinjing
age: 3
#行内写法
Student: {name: qinjaing,age: 3}
#数组
pets:
- cat
- dog
- pig
pets: [cat,dog,pig]

- 给属性赋值的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8081
person:
name: qianjaing
age: 3
happy: false
birth: 2019/11/02
maps: {k1: v1,k2: v2}
lists:
- code,
- music,
- girl
dog:
name: 旺财
age: 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@Component
@ConfigurationProperties(prefix = "person")#将配置文件中配置的属性值,映射到这个组件中
public class person {

private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;

public person() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Boolean getHappy() {
return happy;
}

public void setHappy(Boolean happy) {
this.happy = happy;
}

public Date getBirth() {
return birth;
}

public void setBirth(Date birth) {
this.birth = birth;
}

public Map<String, Object> getMaps() {
return maps;
}

public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}

public List<Object> getLists() {
return lists;
}

public void setLists(List<Object> lists) {
this.lists = lists;
}

public Dog getDog() {
return dog;
}

public void setDog(Dog dog) {
this.dog = dog;
}

@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}

- JSR303校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Null        被注释元素必须为空
@NotNull 被注释元素必须不为空
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
@AssertTrue 被注释元素必须为True
@AssertFalse 被注释元素必须为False
@Min(value) 被注释元素必须为数字,其值必须大于等于指定的最小值
@Max(value) 被注释元素必须为数字,其值必须小于等于指定的最大值
@DeciamlMin(value) 被注释元素必须为数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释元素必须为数字,其值必须小于等于指定的最大值
@Size(max,min)
@Digits(integer,fraction) 被注释元素必须是一个数字,其值在可接受范围内
@Past 被注释元素是过去的日期
@Future 被注释元素必须是将来的日期
@Pattern 被注释元素必须符合指定的正则表达式
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串必须非空
@Range 被注释的元素必须在合适的范围内

- 多环境切换及配置文件位置

配置文件可以存放的位置:

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/
1
2
3
4
5
6
#多个properties文件,配置文件的指定
- application.properties 主环境配置文件
- application-test.properties 测试环境配置文件
- application-dev.properties 开发环境配置文件
#可以选择激活哪一个配置文件
spring.profiles.active=dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#yaml文件中环境的切换
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod

---
server:
port: 8083
spring:
profiles: dev #配置环境的名称


---

server:
port: 8084
spring:
profiles: prod #配置环境的名称
#注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

- 导入静态资源

  1. 在springboot中,我们可以使用以下方式处理静态资源
    • webjars localhost:8080/webjars/
    • public,static,/**,resources localhost:8080/
  2. 优先级:resource>static(默认)>public

- Thymeleaf模板引擎

templates目录下的所有页面,只能通过controller来跳转

实现过程:

  1. 导入依赖
1
2
3
4
5
6
7
8
9
<!--导入模板引擎依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
  1. Thymeleaf的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>

<!--1.导入头文件xmlns:th="http://www.thymeleaf.org-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}"/>
</head>
<body>
<h1>Test</h1>
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>

</body>
</html>

Thymeleaf语法:
- 变量:${……}
- 选择表达式:*{……}
- 消息:#{……}
- 链接:@{……}
- Framgent Expressions:~{……}
- 包含:th:insert th:replace
- 遍历:th:each
- 条件判断:th:if th:unless th:switch th:case
- 定义变量:th:object th:with
  1. 配置静态资源目录
1
2
3
4
5
6
7
@Configuration
public class MymvcController implements WebMvcConfigurer {
@Override//静态资源过滤器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}

- MVC配置原理

- 自定义视图解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class MvcConfig implements WebMvcConfigurer {

//自定义一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}

//ViewResolver 实现了视图解析器接口的类,我们就可以把他看作视图解析器
@Bean
public ViewResolver mvcConfig(){
return new MyViewResolver();
}
}

- 扩展springmvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class MymvcController implements WebMvcConfigurer {

//自定义视图解析器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}

@Bean//注入容器,自定义国际化组件生效了
public LocaleResolver MyLocalResolver(){
return new MyLocalResolver();
}
//自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {//拦截哪些
registry.addInterceptor(new LoginHandleInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/*","/js/**","/img/**");//排除哪些
}

@Override//自定义资源过滤器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}

- 国际化

  1. 首页配置:所有页面静态资源都需要thymeleaf接管

  2. 页面国际化:

    • 配置i18n文件
    • 如果需要在项目中进行按钮自动切换,需要自定义组件LocalResolver
    • 需要将自己写的组件配置到spring容器中@Bean
    • #{}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //自定义国际化
    @Configuration
    public class MyLocalResolver implements LocaleResolver {
    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
    //获取参数中的请求连接
    String language=request.getParameter("l");
    Locale local=Locale.getDefault();//如果没有使用默认

    if(!StringUtils.isEmpty(language)){
    String[] split=language.split(language);
    local=new Locale(split[0],split[1]);//国家。地区
    }
    return local;
    }
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //自定义视图解析器
    @Configuration
    public class MymvcController implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
    }

    @Bean//注入容器
    public LocaleResolver MyLocalResolver(){
    return new MyLocalResolver();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    #关闭模板引擎的缓存
    spring:
    thymeleaf:
    cache: false
    #国际化配置文件的文件位置
    message.basename: i18n.login
    mvc:
    data-format: yyyy-MM-dd

- 拦截器(Aop)

使用场景:

  • 登陆验证
  • 权限验证(校验token)
  • 日志记录(用户ip,访问时间)
  • 处理cookie,本地化,国际化,主题等
  • 性能监控,监控处理时长
  • 通用行为:读取cookie得到用户信息并将用户对象放入请求,方便后续流程使用,还有如提取Locale,Theme信息等,只要是多个处理器都需要的即可使用拦截器实现

主要方法:

(1)preHandle:该方法在调用Controller方法或获取静态资源前被调用(静态资源包括html、js等)。

(2)postHandle:该方法在调用Controller方法或获取静态资源后,但是视图还没有被渲染前调用。

(3)afterCompletion:该方法在视图渲染后进行调用,主要用来清除资源。

实现过程:

  1. 实现HandlerInterceptor中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//自定义拦截器
@Configuration
public class LoginHandleInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登陆成功则可以获取到用户的session retun true 放行;
//否则给出提示,并返回登陆页面request.getRequestDispatcher("/index.html").forward(request,response);
Object login=request.getSession().getAttribute("loginUser");
if(login==null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}
}
  1. 编写拦截器的配置类文件,实现WebMvcConfigurer接口中的方法,配置类上添加@Configuration注解
1
2
3
4
5
6
7
8
9
@Configuration
public class MymvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyIntercepter())
.addPathPatterns("/**") //拦截所有路径
.excludePathPatterns("/login", "/**/*.js","/**/*.html","/**/*.css");//放行静态资源,登录接口等
}
}

- 过滤器

使用场景:

  • 过滤敏感词汇,防止sql注入

  • 设置字符编码

  • URL级别的权限访问控制

  • 压缩响应信息

  • 权限控制、用户登录(非前端后端分离场景)

    实现过程:实现Filter接口,启动类上添加@ServletComponentScan注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@WebFilter(value="myfilter",urlPatterns = "")
public class myfilter implements Filter {
@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;
HttpServletResponse response=(HttpServletResponse)servletResponse;
String requestURI = request.getRequestURI();
if (requestURI.contains("/login")){
filterChain.doFilter(servletRequest,servletResponse);
}
User userInfo = (User) request.getSession().getAttribute("userInfo");
if(userInfo!=null){
System.out.println(userInfo);
filterChain.doFilter(servletRequest,servletResponse);
}
System.out.println("未登录");
response.setContentType("*/*;charset=utf-8");
PrintWriter out=response.getWriter();
out.write("未登录,请先登录");
}

@Override
public void destroy() {

}
}

- 拦截器和过滤器的区别

拓展:Servlet和Controller的区别

使用Servlet可以收集来自网页表单的用户输入,还可以动态创建网页。DispatcherServlet是SpringMVC中唯一的Servlet,Servlet容器(Tomcat)把所有的请求都转发到DispatcherServlet,然后通过HandlerMapping把请求路由到具体的Controller中。因此,Controller只是一个普通的Java Bean。

执行顺序:tomcat—>filter—>Servlet—>Interceptor—>controller

区别:

  1. 实现原理不同

    • Filter是基于函数回调实现的
    • Interceptor是基于反射实现的
  2. 触发时机不同

    • 过滤器Filter是在请求进入Tomcat等容器后,servlet处理之前进行调用的。
    • 拦截器Interceptor是在请求进入servlet后,执行Controller之前进行调用的
  3. 使用范围不同

    • 依赖于Tomcat等容器,所以它只能在web程序中使用
    • 由Spring容器进行管理,并不依赖Tomcat等容器,既可以应用在web程序中,也可以应用在非web程序中
  4. 拦截范围不同

    • 过滤器Filter几乎可以拦截所有进入容器的请求
    • 拦截器Interceptor只会对Controller请求或访问static目录下的静态资源请求起作用
  5. 初始化时机不同

    • 过滤器Filter是随着Tomcat等web容器启动时而进行初始化。
    • 拦截器Interceptor时随着spring启动而进行初始化

使用场景:二者相比拦截器功能更强大些,Filter能做的事情,它都能做,而且可以在请求前,请求后执行,比较灵活。

  • Filter主要用来设置字符编码、过滤敏感词汇和URL级别的简单权限控制
  • 拦截器更适合记录比较详细的信息或比较复杂的权限管理

- 上传

储存方式:

  • 搭建Nginx静态资源服务器
  • OSS对象存储

上传类型:

  • 本地文件上传:普通io流

  • 网页资源上传:URL,HttpURLConnection,InputStream

    1
    2
    3
    URL url=new URL(url)
    HttpURLConnection coon=(HttpURLConnection)url.openConnection();
    InputStream in=coon.getOutputStream();

- 下载

- 服务端设置

1
2
3
response.setHeader("Content-Type","application/octet-stream");
response.setHeader("Content-Disposition","attachment;filename"+filename);
response.setContentType("\*/\*;charset=utf-8");

- 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//网络端资源文件下载到本地
String filename = request.getParameter("filename");
String folder="http://localhost/"+filename;
URL url=new URL(folder);

HttpURLConnection conn= (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5*1000);

response.setHeader("Content-Type","application/octet-stream");
response.setHeader("Content-Disposition","attachment;filename"+filename);
response.setContentType("*/*;charset=utf-8");

InputStream inputStream=conn.getInputStream();
OutputStream outputStream=response.getOutputStream();
byte[] buffer=new byte[1024];
int len;
while ((len= inputStream.read(buffer))!=-1){
outputStream.write(buffer,0,len);
}
outputStream.flush();
outputStream.close();
inputStream.close();

- 学习目标

  • JDBC
  • Mybatis
  • Druid
  • Shiro
  • Spring Security
  • 异步任务,邮件发送,定时任务
  • Swagger
  • Dubbo+Zookeeper

- JDBC

配置文件:

1
2
3
4
5
6
Spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver

- Druid

1
2
3
4
5
6
7
8
#整合Druid   https://www.cnblogs.com/hellokuangshen/p/12497041.html
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource

- Mybatis

  1. ```xml

    org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    2. ```java
    //编写mapper接口
    @Mapper
    @Repository
    public interface UserMapper {
    @Select("select * from EmployeeMap")//例:注解方式
    List<EmployeeMap> queryUserList();
    user queryUserById(String name);
    int addUser(user user);
    int updateUser(user user);
    int deleteUser(String name);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <!--编写sql语句抽取到xml文件中-->

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.kuang.mapper.EmployeeMapper">
    <!--<cache/>开启缓存-->
    <resultMap id="EmployeeMap" type="Employee">
    <id property="id" column="eid"/>
    <result property="lastName" column="last_name"/>
    <result property="email" column="email"/>
    <result property="gender" column="gender"/>
    <result property="birth" column="birth"/>
    <association property="eDepartment" javaType="Department">
    <id property="id" column="did"/>
    <result property="departmentName" column="dname"/>
    </association>
    </resultMap>

    <select id="getEmployees" resultMap="EmployeeMap">
    select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname
    from department d,employee e
    where d.id = e.department
    </select>

    <insert id="save" parameterType="Employee">
    insert into employee (last_name,email,gender,department,birth)
    values (#{lastName},#{email},#{gender},#{department},#{birth});
    </insert>

    <select id="get" resultType="Employee">
    select * from employee where id = #{id}
    </select>

    <delete id="delete" parameterType="int">
    delete from employee where id = #{id}
    </delete>
    </mapper>
  2. ```yaml
    #连接数据库
    spring:
    datasource:

    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    
    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    
    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    

    #整合mybatis
    mybatis:

    #实体类存在的位置
    

    type-aliases-package: com.jilf.demo.pojo
    #扫描sql抽取xml的位置
    mapper-locations: classpath:mybatis/mapper/*.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    ### - MybatisPlus

    #### - 配置文件

    ```properties
    #开启日志,方便调试
    mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mybatis-plus.mapper-locations=classpath:com/atguigu/eduservice/mapper/xml/*.xml

    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8


    <!--项目打包时会将java目录中的*.xml文件也进行打包-->
    <resources>
    <resource>
    <directory>src/main/java</directory>
    <includes>
    <include>**/*.xml</include>
    </includes>
    <filtering>false</filtering>
    </resource>
    </resources>

- pom依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--导入依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>

- 代码生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//代码生成器
public class CodeGenerator {
@Test
public void run() {

// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();

// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("C:\\Users\\Jilfoyle\\Desktop\\项目源码\\day02\\guli_parent\\service\\service_edu" + "/src/main/java");

gc.setAuthor("testjava");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖

//UserServie
gc.setServiceName("%sService"); //去掉Service接口的首字母I

gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式

mpg.setGlobalConfig(gc);

// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);

// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("eduservice"); //模块名
//包 com.atguigu.eduservice
pc.setParent("com.atguigu");
//包 com.atguigu.eduservice.controller
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);

// 5、策略配置
StrategyConfig strategy = new StrategyConfig();

strategy.setInclude("edu_course","edu_course_description","edu_chapter","edu_video");//表

strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

mpg.setStrategy(strategy);


// 6、执行
mpg.execute();
}
}
1
2
3
4
5
//UserMapper继承BaseMapper
@Repository
public interface UserMapper extends BaseMapper<User> {

}
1
2
3
4
5
6
7
8
9
@Autowired
private UserMapper userMapper;

@Test
public void contextLoads() {
//查询操作
List<User> users=userMapper.selectList(null);
System.out.println(users);
}
  • 命名特点:表中字段create_time,实体类中createTime

- 自动填充

不用set,用mp方法实现

  1. 在实体类的属性上添加注解@TableField(fill=FieldFill.INSERT)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    public class User {
    //value="字段",可字段和属性的映射,即使不符合命名特点
    //type,设置主键类型和主键生成策略
    @TableId(type = IdType.UUID)//主键自动填充,需要设置主键自增
    private long id;
    private String name;
    private Integer age;
    private String email;
    @TableField(fill = FieldFill.INSERT)//添加时自动填充
    private Date createTime;
    @TableField(fill=FieldFill.INSERT_UPDATE)//修改时自动填充
    private Date updateTime;
    }
  2. 实现MetaObjectHandler中的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //实现MetaObjectHandler中的方法
    @Component
    public class MymetaObjectHandler implements MetaObjectHandler {

    //使用mp实现添加操作,该方法执行
    @Override
    public void insertFill(MetaObject metaObject) {
    //字段 要修改的值
    this.setFieldValByName("createTime",new Date(),metaObject);
    this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    //使用mp修改操作,该方法执行
    @Override
    public void updateFill(MetaObject metaObject) {
    this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    }

- 乐观锁

作用:

  • 主要解决 丢失更新

  • 若不考虑事务隔离性,产生读问题:脏读,不可重复读,幻读(虚读)

  • 实现方式:

    • 取出记录时,获得当前version
    • 更新时,带上这个version
    • 执行更新时,set version=newVersion where version=oldVersion
    • 如果version不对,就更新失败

实现过程:

  1. 数据库中添加version字段

    1
    AlTER TABLE 'user' add column 'version' INT
  2. 实体类添加@Version注解

    1
    2
    3
    @Version
    @TableField(fill=FieldFill.INSERT)
    private Integer version;
  3. 元对象处理接口添加version的inser默认值

    1
    2
    3
    4
    5
    //在MymetaObjectHandler中
    @Override
    public void insertFill(MetaObject metaObject){
    this.setFieldValByName("version",1,metaObject);
    }
  4. ```java
    //在MyBatisPlus的接口中注册Bean,创建配置类;插件固定
    @EnableTransactionManagement
    @Configuration
    @MapperScan(“扫描mapper包”)
    public class MybatisPlusConfig{

    /**
     *乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockInterceptor(){
        return new OptimisticLockerInterceptor();
    }
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29


    #### - 悲观锁

    - 串行

    #### - mp的简单查询

    ```java
    @Test//根据id查询
    public void testSelectById(){
    User user= userMapper.selectById(1);
    System.out.println(user);
    }

    @Test//通过多个id,批量查询
    public void testSelectBatchIds(){
    List<User> users=userMapper.selectBatchIds(Arrays.asList(1L,2L,3L));
    users.forEach(System.out::println);
    }
    @Test//通过map封装条件查询,批量查询
    public void testSelectByMap(){
    HashMap<String,Object> map= new HashMap<>();
    map.put("name","zzzzz");
    map.put("age",20);
    List<User> users=userMapper.selectByMap(map);
    for (User user:users) {
    System.out.println(user);
    }

- MyBatisPlus实现分页

  1. 配置分页插件
1
2
3
4
@Bean
public PaginationInterceptor pagintionInterceptor(){
return new PaginationInterceptor();
}
  1. 构建Page对象并测试分页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test//分页操作
public void testPage(){
//创建Page对象:(当前页,每页记录数)
Page<User> page=new Page<>(1,3);
//调用mp分页查询方法
userMapper.selectPage(page,null);

System.out.println(page.getCurrent());//获取当前页
System.out.println(page.getRecords());//获取记录数
System.out.println(page.getSize());//获取当前记录数
System.out.println(page.getTotal());//获取总记录数
System.out.println(page.hasNext());//下一页
System.out.println(page.hasPrevious());//上一页
}

- mp实现逻辑删除

  • 表中添加逻辑删除字段,对应实体类添加属性,属性添加注解**@TableLogic**

  • 配置逻辑删除插件

    1
    2
    3
    4
    @Bean//逻辑删除插件
    public ISqlInjector sqlInjector(){
    return new LogicSqlInjector();
    }
    1
    2
    3
    4
    5
    mybatis:
    global-config: #逻辑删除默认值为0,1.不设置也可以
    db-config:
    logic-delete-value: 1
    logic-not-delete-value: 0
    1
    2
    3
    4
    5
    @Test//逻辑删除测试文件
    public void testSqlInjector(){
    int result=userMapper.deleteById(4L);
    System.out.println(result);
    }

- 配置性能分析插件

1
2
3
4
5
6
7
8
@Bean//SQL执行性能分析插件,开发环境使用,线上不推荐,maxTime指的是sql最大执行时长
@Profile({"dev","test"})//设置dev test环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//超过此处设定的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}

- 条件查询

1
2
3
4
5
6
7
8
9
@Test
public void t1(){//条件查询 ge>= gt> ne!= eq= it< le<= between
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.ge("age",30);
List<User> users=userMapper.selectList(wrapper);
for(User user:users){
System.out.println(user);
}
}

- 插件配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
@MapperScan("com.jilf.mpdemo.mapper")
public class MpConfig {

//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}

@Bean//分页插件
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

@Bean//逻辑删除插件
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}

@Bean//SQL执行性能分析插件,开发环境使用,线上不推荐,maxTime指的是sql最大执行时长
@Profile({"dev","test"})//设置dev test环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//超过此处设定的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}

- 自动填充处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component//自动填充
public class MymetaObjectHandler implements MetaObjectHandler {

//使用mp实现添加操作,该方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
this.setFieldValByName("version",1,metaObject);//版本号初始为1
}
//使用mp修改操作,该方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}

- SpringSecurity(安全)


简介:针对Spring boot的安全框架,对于安全控制,仅需引入spring-boot-starter-security模块。进行少量配置,即可实现强大的安全管理!

几个重要的类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuillder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式,@Enablelxxxx开启某个功能

Spring Security的两个主要目标是“认证”和”授权”(访问控制)

  • “认证”:(Authentication)
  • “授权”:(Authorization)

常见的权限控制模块:

  • 功能权限
  • 访问权限
  • 菜单权限

- 基本用法

  1. 创建项目添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--导包-->
    <!--security-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--security-thymeleaf整合包-->
    <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
  2. 添加hello接口

    1
    2
    3
    4
    5
    6
    7
    8
    @RestController
    public class HelloController{

    @GetMapping("/hello")
    public String hello(){
    return "hello";
    }
    }
  3. 启动项目测试:访问hello接口,默认security账户名为user,密码控制台随机生成

- 配置用户名和密码

若开发者不满意默认生成,可在application.properties中配置默认用户名密码

1
2
3
spring.security.user.name=sang
spring.security.user.password=123
spring.security.user.roles=admin

- 基于内存的认证

  1. 通过继承WebSecurityConfigurerAdapter,重写configure(AuthenticationManagerBuilder auth),实现基于内存的认证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();//不对称加密
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.inMemoryAuthentication()
    .withUser("admin").password("123").roles("admin","user")
    .and()
    .withUser("sang").password("1234").roles("user");
    }
    }

- HttpSecurity

根据实际情况自定义角色管理功能,重写WebSecurityConfigurerAdapter的另一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.jilf.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;



//@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}

@Override//基于内存的认证
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.inMemoryAuthentication()
.withUser("root").password("123").roles("ADMIN","DBA")
.and()
.withUser("admin").password("1234").roles("ADMIN","USER")
.and()
.withUser("sang").password("12345").roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {

http.authorizeHttpRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasAnyRole("ADMIN,USER")
.antMatchers("/db/**")
.hasAnyRole("ADMIN,USER")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/index")//自定义登录页面
.loginProcessingUrl("/login")//登录接口
.usernameParameter("name")
.passwordParameter("passwd")
.permitAll()
.and()
.csrf()
.disable();
}
}

- 自定义登陆页面

  1. 在resource下创建static文件夹,将登录表单页面的css,js,img放在static下
  2. 重写configure(WebSecurity web)放行静态资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Configuration
public class MySecutity extends WebSecurityConfigurerAdapter {

@Override//资源放行
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**","/img/**","/css/**");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/db/**")
.hasRole("DBA")
.antMatchers("/error/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.failureForwardUrl("/error")
.permitAll()
.and()
.csrf()
.disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("1234").roles("ADMIN")
.and()
.withUser("user").password("user").roles("USER")
.and()
.withUser("dba").password("1234").roles("DBA");
}

@Bean//密码加密
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}


- 整合Swagger

-2.0版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.13</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Configuration//配置类
@EnableSwagger2 //swagger注解
@EnableWebMvc
public class SwaggerConfig {

@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();

}

private ApiInfo webApiInfo(){

return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("java", "http://atguigu.com", "1123@qq.com"))
.build();
}
}
/**
*若在其他模块中需要引用
*1.引入模块所在依赖
*2.在启动类中中开启组件扫描ComponentScan(basePachages={"包路径"})
- 注解:
- 定义在类上:@Api(describe="描述")
- 定义在方法上:@ApiOperation(value="描述")
- 定义在参数上:@ApiParam(name="id",value="讲师ID",required=true)
- 访问Swagger:localhost:端口号/swagger-ui.html
*/

-3.0版本(推荐)

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
    </dependency>
    <!--在启动类或配置类上添加@EnableOpenApi注解-->
    <!--引入该依赖就已经可以访问swagger接口文档,路径地址swagger-ui/index.html-->
    <!--推荐springboot2.6一下整合-->
    <!--2.6以上需要配置文件添加:spring.mvc.pathmatch.matching-strategy: ant_path_matcher>

  2. 配置信息(自定义)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Configuration
    public class Swagger3Config {
    // 这个需要在配置文件中配置是否开启Swagger
    @Value("${swagger.enable}")
    private Boolean SwaggerEnable;
    @Bean
    public Docket createRestApi() {
    return new Docket(DocumentationType.OAS_30)
    .apiInfo(apiInfo())// 添加文档信息
    .select()
    // 选择要显示的包 如果是下面的写法则会将所有的接口都会展示出来 无论你有没有写注解
    .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
    // 而这种写法则是将所有的有注解的接口展示出来
    .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
    .paths(PathSelectors.any())
    .build()
    // 是否启用Swagger
    .enable(SwaggerEnable);
    }

    /**
    * 文档信息
    * @return ApiInfo
    */
    private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
    .title("这是swagger3的接口文档")
    .description("更多请咨询服务开发者mt1020")
    .contact(new Contact("mt1020", "浙江省嘉兴市", "3174393"))
    .version("1.0")
    .build();
    }
    }

- 统一返回结果类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//统一返回结果的类
@Data
public class R {

@ApiModelProperty(value = "是否成功")
private Boolean success;

@ApiModelProperty(value = "返回码")
private Integer code;

@ApiModelProperty(value = "返回消息")
private String message;

@ApiModelProperty(value = "返回数据")
private Map<String, Object> data = new HashMap<String, Object>();

//把构造方法私有
private R() {}

//成功静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}

//失败静态方法
public static R error() {
R r = new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}

public R success(Boolean success){
this.setSuccess(success);
return this;
}

public R message(String message){
this.setMessage(message);
return this;
}

public R code(Integer code){
this.setCode(code);
return this;
}

public R data(String key, Object value){
this.data.put(key, value);
return this;
}

public R data(Map<String, Object> map){
this.setData(map);
return this;
}
}

- 带条件的分页查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@PathVariable long current,@PathVariable long limit,
@RequestBody(required = false) TeacherQuery teacherQuery) {
//创建page对象 //值可以没有
Page<EduTeacher> pageTeacher = new Page<>(current,limit);

//构建条件
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
// 多条件组合查询
// mybatis学过 动态sql
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
//判断条件值是否为空,如果不为空拼接条件
if(!StringUtils.isEmpty(name)) {
//构建条件
wrapper.like("name",name);
}
if(!StringUtils.isEmpty(level)) {
wrapper.eq("level",level);
}
if(!StringUtils.isEmpty(begin)) {
wrapper.ge("gmt_create",begin);
}
if(!StringUtils.isEmpty(end)) {
wrapper.le("gmt_create",end);
}

//调用方法实现条件查询分页
teacherService.page(pageTeacher,wrapper);

long total = pageTeacher.getTotal();//总记录数
List<EduTeacher> records = pageTeacher.getRecords(); //数据list集合
return R.ok().data("total",total).data("rows",records);

- 全局统一异常处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ControllerAdvice
public class GlobalExceptionHandler {

//指定出现什么异常执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody //为了返回数据
public R error(Exception e) {
e.printStackTrace();
return R.error().message("执行了全局异常处理..");
}

//特定异常处理类
@ExceptionHandler(ArithmeticException.class)
@ResponseBody//
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("执行了特定异常处理类")
}
}

- 自定义异常处理类

  1. 创建自定义异常类
1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException{

@ApiModelProperty(type="状态码")
private Integer code;
private String msg;
}
  1. 业务中需要的位置抛出GuliException
1
2
3
4
5
try{
int a=10/0;
}catch(Exception e){
throw new GuliException(20001,"自定义异常")
}
  1. 添加异常处理方法
1
2
3
4
5
6
@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
e.printStackTrace();
return R.error().message(e.getMsg()).code(e.getCode);
}

- 统一日志处理

  1. 配置日志级别:OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL

    默认情况下,spring boot从控制台打印出来日志级别只有INFO及以上级别,可以配置日志级别

    1
    2
    3
    #设置日志级别
    logging.level.root=WARN
    #这种方式只能将日志打印在控制台
  2. 配置Logback日志

    1
    2
    #删除: logging.level.root=WARN
    #mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  3. 在resource中创建logback-spring.xml

     https://blog.csdn.net/datastructure18/article/details/120919329
    
     如果程序运行出现异常,把异常信息输出到文件中
    
     - 在异常处理类上添加@Slf4j注解
     - 在异常方法中输出异常语句:log.error(e.getMessage());
    

    - ES6前端技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// var 声明的变量没有局部作用域
// let 声明的变量 有局部作用域
{
var a = 0
let b = 1
}
console.log(a) // 0
console.log(b) // ReferenceError: b is not defined

// var 可以声明多次
// let 只能声明一次
var m = 1
var m = 2
let n = 3
let n = 4

console.log(m) // 2
console.log(n) // Identifier 'n' has already been declared


// 1、声明之后不允许改变
const PI = "3.1415926"
PI = 3 // TypeError: Assignment to constant variable.

// 2、一但声明必须初始化,否则会报错
const MY_AGE // SyntaxError: Missing initializer in const declaration


//解构赋值
//1、数组解构
// 传统
let a = 1, b = 2, c = 3
console.log(a, b, c)
// ES6
let [x, y, z] = [1, 2, 3]
console.log(x, y, z)


//对象解构
//2、对象解构
let user = {name: 'Helen', age: 18}
// 传统
let name1 = user.name
let age1 = user.age
console.log(name1, age1)
// ES6
let { name, age } = user//注意:结构的变量必须是user中的属性
console.log(name, age)


//模板字符串
// 1、多行字符串

let string1 = `Hey,
can you stop angry now?`
console.log(string1)
// Hey,
// can you stop angry now?2、对象解构


// 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "Mike"
let age = 27
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info)
// My Name is Mike,I am 28 years old next year.


// 3、字符串中调用函数
function f(){
return "have fun!"
}
let string2 = `Game start,${f()}`
console.log(string2); // Game start,have fun!


//声明对象简写
const age = 12
const name = "Amy"
// 传统
const person1 = {age: age, name: name}
console.log(person1)
// ES6
const person2 = {age, name}
console.log(person2) //{age: 12, name: "Amy"}


//定义方法简写

// 传统
const person1 = {
sayHi:function(){
console.log("Hi")
}
}
person1.sayHi();//"Hi"

// ES6
const person2 = {
sayHi(){
console.log("Hi")
}
}
person2.sayHi() //"Hi"


// 1、拷贝对象
let person1 = {name: "Amy", age: 15}
let someone = { ...person1 }
console.log(someone) //{name: "Amy", age: 15}


// 2、合并对象
let age = {age: 15}
let name = {name: "Amy"}
let person2 = {...age, ...name}
console.log(person2) //{age: 15, name: "Amy"}


//箭头函数
// 传统
var f1 = function(a){
return a
}
console.log(f1(1))


// ES6
var f2 = a => a
console.log(f2(1))


// 当箭头函数没有参数或者有多个参数,要用 () 括起来。
// 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
// 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f3 = (a,b) => {
let result = a+b
return result
}
console.log(f3(6,2)) // 8
// 前面代码相当于:
var f4 = (a,b) => a+b

- OSS云存储

https://www.aliyun.com/product/oss?spm=5176.19720258.J_3207526240.36.e9392c4aCHGaCR

  1. 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--阿里云oss依赖-->
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.1.0</version>
    </dependency>
    <!--日期工具栏依赖-->
    <dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version></version>
    </dependency>
  2. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #服务端口
    server.port=8002
    #服务名
    spring.application.name=service-oss

    #环境设置
    spring.profiles.active=dev

    #阿里云OSS
    #不用的服务器,地址不同
    aliyun.oss.file.endpoint=oss-cn-hongkong.aliyuncs.com
    aliyun.oss.file.keyid=xxxxxxxxxxxxxxxxxxxxx
    aliyun.oss.file.keysecret=xxxxxxxxxxxxxxxxxxxxx
    #bucket可以在控制台创建,也可以用java代码创建
    aliyun.oss.file.bucketname=xxxxxxxxxxx
  3. 创建工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Component
    public class ConstantPropertiesUtils implements InitializingBean {//初始化接口,重写afterPropertiesSet()方法
    //读取配置文件
    // Endpoint
    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;
    // 阿里云账号AccessKey
    @Value("${aliyun.oss.file.keyid}")
    private String accessKeyId;
    @Value("${aliyun.oss.file.keysecret}")
    private String accessKeySecret;
    // 填写Bucket名称
    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;

    //定义静态常量
    public static String END_POINT;
    public static String ACCESS_KEYID;
    public static String ACCESS_KEYSECRET;
    public static String BUCKET_NAME;
    @Override
    public void afterPropertiesSet() throws Exception {
    END_POINT=endpoint;
    ACCESS_KEYID=accessKeyId;
    ACCESS_KEYSECRET=accessKeySecret;
    BUCKET_NAME=bucketName;
    }
    }

- EasyExcel

应用场景:数据导入,导出,异构系统之间的数据传输

  1. ```xml

    com.alibaba easyexcel 2.1.1
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
        </dependency>
        <!--xlsx-->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
        </dependency>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    2. ```java
    <!--封装实体-->
    @Data
    public class DemoData {
    @ExcelProperty("学生编号")
    private Integer sno;
    @ExcelProperty("学生姓名")
    private String name;
    }

  2. ```java

    public class test { public static void main(String[] args) { //写法一: //写入文件夹地址和excel文件名称 String filename="D://write.xlsx"; EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData()); //写法二: //需要手动关闭流 String fileName = "F:\\112.xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("写入方法二").build(); excelWriter.write(data(), writeSheet); /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); } private static List getData(){ List list=new ArrayList<>(); for(int i=0; i<10; i++){ DemoData demoData=new DemoData(); demoData.setSno(i); demoData.setName("张三"+i); list.add(demoData); } return list; } }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    4. ```java
    //测试读操作
    @RestController
    @CrossOrigin
    @RequestMapping("/eduservice/subject")
    public class EduSubjectController {

    @Autowired
    private EduSubjectService subjectService;

    //添加课程分类
    //获取上传过来的文件,把文件内容读出来
    @PostMapping("addSubject")
    public R addSubject(MultipartFile file){
    //上传过来的文件
    subjectService.saveSubject(file,subjectService);
    return R.ok();
    }
    }
    • ```java
      @Service
      public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

      @Override//添加课程分类
      public void saveSubject(MultipartFile file,EduSubjectService subjectService) {
      
          try{
              //文件输入流
              InputStream in=file.getInputStream();
              //调用方法进行读取
              EasyExcel.read(in, SubjectData.class,
                             new SubjectExcelListener(subjectService)).sheet().doRead();
          }catch (Exception e){
              e.printStackTrace();
          }
      }
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66

      - ```java
      public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {

      //因为SubjectListener不能交给spring进行管理,需要自己new,不能注入其他对象
      //不能实现数据库操作
      public EduSubjectService subjectService;
      public SubjectExcelListener() { }

      public SubjectExcelListener(EduSubjectService subjectService) {
      this.subjectService = subjectService;
      }


      @Override
      public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
      if (subjectData == null){
      throw new GuliException(20001,"文件数据为空");
      }

      //一行一行读,每次读取两个值,第一个值为一级分类,第二个值为二级分类
      //判断一级分类是否重复
      EduSubject existOneSubject=this.existOneSubject(subjectService,subjectData.getOneSubjectName());
      if(existOneSubject == null){//没有一级分类,进行添加
      existOneSubject=new EduSubject();
      existOneSubject.setParentId("0");
      existOneSubject.setTitle(subjectData.getOneSubjectName());//一级分类名称
      subjectService.save(existOneSubject);
      }

      String pid=existOneSubject.getId();


      //添加二级分类
      EduSubject existTwoSubject=this.existOneSubject(subjectService,subjectData.getTwoSubjectName(),pid);
      if(existTwoSubject == null){
      existTwoSubject = new EduSubject();
      existTwoSubject.setParentId(pid);
      existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
      subjectService.save(existTwoSubject);
      }

      }

      //判断一级分类不重复添加
      private EduSubject existOneSubject(EduSubjectService subjectService,String name ){
      QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
      wrapper.eq("title",name);
      wrapper.eq("parent_id",'0');
      EduSubject onesubject=subjectService.getOne(wrapper);
      return onesubject;
      }
      //判断二级分类不重复添加
      private EduSubject existOneSubject(EduSubjectService subjectService,String name,String pid ){
      QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
      wrapper.eq("title",name);
      wrapper.eq("parent_id",pid);
      EduSubject twosubject=subjectService.getOne(wrapper);
      return twosubject;
      }

      @Override
      public void doAfterAllAnalysed(AnalysisContext analysisContext) {

      }
      }
    • ```java
      @Data
      public class SubjectData {

      @ExcelProperty(index = 0)
      private String oneSubjectName;
      
      @ExcelProperty(index = 1)
      private String twoSubjectName;
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      ### - 富文本编辑器

      参考:[首页 - Vue Element Admin (gitee.io)](https://panjiachen.gitee.io/vue-element-admin/#/dashboard)

      1. 复制脚本库:将脚本库复制到static目录下(在vue-element-admin-master的static路径下)

      2. 配置html变量

      - 在webpack.dev.conf.js中添加配置,

      - 使在html页面中可以使用这里定义的Base_URL变量

      ```javascript
      new HtmlWedpackPlugin({
      ……,
      templateParameters: {
      BASE_URL:config.dev.assetsPublicPath+config.dev.assetsSubDirectory
      }
      })
  3. 引入脚本:在guli-admin/index.html中引入js脚本

    1
    2
    <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
    <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>

组件使用:

  1. 复制组件:src/components/Tinymce

  2. 引入组件:课程信息中引入Tinymce

    1
    2
    3
    4
    5
    6
    7
    <script>
    import Tinymce from '@/components/Tinymce'

    export default {
    components:{Tinymce},
    }
    </script>
  3. 组件模板

    1
    2
    3
    <el-form-item label="课程简介">
    <tinymce :height="300" />
    </el-form-item>
  4. 组件样式

    1
    2
    3
    4
    5
    <style scoped>
    .tinymce-container {
    line-height: 29px;
    }
    <style>

- Git

  1. 安装:https://registry.npmmirror.com/binary.html?path=git-for-windows/v2.36.1.windows.1/

  2. 配置

    1
    2
    3
    4
    5
    #配置用户·和邮箱
    git config --global user.name "Jilfoyle"
    git config --global user.email "918703864@qq.com"
    #查询配置信息
    git config --global --list
  3. git基本理论:

    • 工作区域:工作目录(working),暂存区(Stage/Index),资源库(Repository),远程仓库(Remote)

  4. 项目搭建

    1
    2
    git init #本地初始化
    git clone url #远程clone
  5. git文件操作

    • 文件

- nginx

1
2
3
4
5
6
7
8
9
10
11
12
#常用命令
#启动停止:
#先进入nginx目录,再进入子目录sbin

nginx启动:./nginx
nginx停止:./nginx -s stop
nginx重新加载nginx.conf:./nginx -s reload
nginx查看nginx.conf配置是否正确:./nginx -t

修改配置:
先进入nginx目录,再进入子目录conf
修改nginx.conf文件

- 打包操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--1.项目打包时会将java目录中的*.xml文件也进行打包-->
<build>
<resources>
<resource>
<directory>src/main/jave</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
<!--2.配置文件中添加-->
mybatis-plus.mapper-locations=classpath:com/atguigu/eduservice/mapper/xml/*.xml

- 添加jar包到本地maven仓库

1
2
3
4
5
6
7
#例:添加aliyun-java-vod-upload-1.4.14.jar
mvn install:install-file
-DgroupId=com.aliyun
-DartifactId=aliyun-sdk-vod-upload
-Dversion=1.4.14
-Dpackaging=jar
-Dfile=aliyun-java-vod-upload-1.4.14.jar

- 微服务

1
2
1.微服务是架构风格
2.有多个服务,多个服务是独立运行,每个服务占用独立进程

- SpringCloud

1
SpringCloud是一系列框架的集合,它利用Spring Boot的简化了分布式基础设施的开发,如服务发现。服务注册,配置中心,消息总线,负载均衡,熔断器,数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署

- SpringCloud基础服务组件

  • 服务发现—Nacos
  • 服务调用—Netflix Feign
  • 熔断器—Netflix Hystrix
  • 服务网关—Spring Cloud GateWay
  • 分布式配置—Spring Cloud Config(Nacos)
  • 消息总线—Spring Cloud Bus(Nacos)

- SpringCloud小版本

  • SNAPSHOT:快照版本,随时可能修改
  • M:MileStone,M1表示里程碑版本,一般同时标注PRE,表示预览版
  • SR:Service Release,SR1表示第一个正式版本,一般同时标注GA,表示稳定版本

- Nacos下载和安装

地址:https://github.com/alibaba/nacos/releases

版本:nacos-server-1.1.4.tar.gz或nacos-server-1.1.4.zip解压任意目录

- 服务注册

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!--引入依赖-->	
    <!--服务注册-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--服务调用-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  2. 在要注册的服务配置文件application中配置Nacos地址

    1
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
  3. 启动类添加**@EnableDiscoveryClient**注解

- 服务调用

  1. 调用端的服务启动类上添加**@EnableFeignClients**注解

  2. 创建接口,在接口上添加注解**@Component@FeignClient(“服务名字”)**

    1
    2
    3
    4
    5
    6
    7
    8
    //@PathVariable注解一定要指定参数名称,否则出错
    @Component
    @FeignClient("service-vod")
    public interface Vodclient{
    //定义调用的方法路径
    @DeleteMapping("/eduvod/video/removeAlyVideo/{id}")//完整路径
    public R removeAlyVideo(@PathVariable("id") String id);
    }
  3. 在需要调用的controller中注入Vodclient实现远程调用

- SpringCloud调用接口流程

1
接口化请求调用--call-->(1)Feign --Hystrix熔断处理机制--> (2)Hystrix --请求通过,则通过Ribbon负载均衡器,挑选合适的服务提供端-->(3)Ribbon--请求包转换成Http Client客户端框架做真实的http通信请求-->(4)HttpClient/OkHttp

- 整合熔断器

  1. 添加熔断器依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--负载均衡-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

    <!--hystrix依赖,主要是用 @HystrixCommand -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    </dependency>
  2. 在调用端配置文件中开启熔断器

    1
    2
    #开启熔断机制
    feign.hystrix.enabled=true
  3. 在创建interface之后,还需要创建interface的实现类,在实现类中实现方法,出错了输出内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Component
    public class VodFileDegradeFeignClient implements VodClient {
    //远程调用出错会执行
    @Override//删除一个视频
    public R removeAlyVideo(String id) {
    return R.error().message("删除视频出错");
    }

    @Override//批量删除视频
    public R deleteBatch(List<String> videoList) {
    return R.error().message("删除多个视频出错");
    }
    }
  4. 在接口上添加注解

    1
    @FeignClient(name = "service-vod",fallback = 实现类.class)
  • Redis

特点:

  • 基于Key-value进行存储的
  • 支持多种数据结构:string(字符串),list(列表),hash(哈希),set(集合),zset(有序集合)
  • 支持持久化,通过内存存储,也可以存到硬盘里
  • 支持过期时间,支持事务,消息订阅
  • 读取速度110000次/s,写的速度是81000次/s
  • 所有操作都是原子性的,同时支持对几个操作全并后的原子性执行

场景:

  • 经常查询,不经常修改,不是特别重要的数据放到redis中缓存

- Redis设置

redis.config设置:

1
2
# bind 127.0.0.1   1.注释
# protected-mode no 2.修改

先启动redis-server,再启动redis-cli

- Springboot整合redis

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--redis-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!--spring2.x集成redis所需的common-pool2-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
    </dependency>
  2. redis配置

    1
    2
    3
    4
    5
    6
    # redis配置
    spring.redis.host=127.0.0.1
    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
  3. 创建配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.atguigu.servicebase;

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    import java.time.Duration;

    @EnableCaching
    @Configuration
    public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setConnectionFactory(factory);
    //key序列化方式
    template.setKeySerializer(redisSerializer);
    //value序列化
    template.setValueSerializer(jackson2JsonRedisSerializer);
    //value hashmap序列化
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    // 配置序列化(解决乱码的问题),过期时间600秒
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(600))
    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
    .disableCachingNullValues();
    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
    .cacheDefaults(config)
    .build();
    return cacheManager;
    }
    }
  4. 开启缓存注解:

    (1)@Cacheable:

    根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存返回数据,若缓存不存在,则执行方法,并把结果存入缓存中,一般用在查询方法

    属性/方法名 解释
    value 缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames 与value差不多,二选一即可
    key 可选属性,可以使用SpEL标签自定义缓存的key

    (2)缓存@CachePut

    使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。

    查看源码,属性值如下:

    属性/方法名 解释
    value 缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames 与value差不多,二选一即可
    key 可选属性,可以使用SpEL标签自定义缓存的key

    (3)缓存@CacheEvict

    使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上

    查看源码,属性值如下:

    属性/方法名 解释
    value 缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames 与value差不多,二选一即可
    key 可选属性,可以使用SpEL标签自定义缓存的key
    allEntries 是否清空所有缓存,默认为false.如果指定为true,则方法调用后将立即清空所有缓存
    beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

- 邮件发送

  1. 导入依赖

    1
    2
    3
    4
    5
    6
    <!--邮件发送-->
    <!--javax.mail-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
  2. 配置文件

    1
    2
    3
    4
    5
    #javax.mail配置
    spring.mail.username=918703864@qq.com
    spring.mail.password=oiidxvtzb
    spring.mail.host=smtp.qq.com
    spring.mail.properties.mail.smtl.ssl.enable=true
  3. 简单邮件测试

    1
    2
    3
    4
    5
    SimpleMailMessage Message = new SimpleMailMessage();
    Message.setSubject("你家大狗子");
    Message.setText("okkk");
    Message.setTo("目标邮箱");
    Message.setFrom("发送端邮箱");
  4. 复杂邮件测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
       //一个复杂的邮件
    MimeMessage mailMessage=mailSender.createMimeMessage();
    //组装
    MimeMessageHelper helper=new MimeMessageHelper(mailMessage,true,"utf-8");
    helper.setSubject("党中央发起的第一次警告!!!");
    helper.setText("内容",true); //true解析内容为html
    //附件
    helper.addAttachment("附件名称",new File("附件路径"));
    helper.setTo("a918703864@foxmail.com");
    helper.setFrom("918703864@qq.com");
    //复杂邮件
    mailSender.send(mailMessage);

- 异步任务

  1. 启动类上添加@EnableAsync注解
  2. 异步方法上添加@Async

- 定时任务

  1. 在启动类上添加注解:@EnableScheduling
  2. 创建定时任务类:在这个类里面使用表达式设置什么时候去执行
    • 类上@Component
    • 方法上@Scheduled(cron="0/5 * * * * ?"):每五秒执行一次,参考:https://www.pppet.net

- canal数据同步

1.应用场景

在前面的统计分析功能中,我们采取了服务调用获取统计数据,这样耦合度高,效率相对较低,目前我采取另一种实现方式,通过实时同步数据库表的方式实现,例如我们要统计每天注册与登录人数,我们只需把会员表同步到统计库中,实现本地统计就可以了,这样效率更高,耦合度更低,Canal就是一个很好的数据库同步工具。canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL。

2.Canal环境搭建

开启mysql服务:service mysql start

(1)开启binlog功能·是否来开启

1
mysql>show variables like 'log_bin';

(2)如果显示状态为OFF表示该功能未开启,开启binlog功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1,修改 mysql 的配置文件 my.cnf
vi /etc/my.cnf
追加内容:
log-bin=mysql-bin #binlog文件名
binlog_format=ROW #选择row模式
server_id=1 #mysql实例id,不能和canal的slaveId重复

2,重启 mysql:
service mysql restart
3,登录 mysql 客户端,查看 log_bin 变量
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON|
+---------------+-------+
1 row in set (0.00 sec)
————————————————
如果显示状态为ON表示该功能已开启

(3)在mysql里面添加以下的相关用户和权限

1
2
3
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';
GRANT SHOW VIEW, SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

3.下载安装Canal服务

下载地址:https://github.com/alibaba/canal/releases

(1)下载并上传到服务器后,放到目录中,解压文件

1
2
cd /usr/local/canal
tar zxvf canal.deployer-1.1.4.tar.gz

(2)修改配置文件:vi conf/example/instance.properties

1
2
3
4
5
6
7
8
9
10
#需要改成自己的数据库信息
canal.instance.master.address=192.168.44.132:3306
#需要改成自己的数据库用户名与密码

canal.instance.dbUsername=canal
canal.instance.dbPassword=canal

#需要改成同步的数据库表规则,例如只是同步一下表
#canal.instance.filter.regex=.*\\..*
canal.instance.filter.regex=guli_ucenter.ucenter_member
1
2
3
4
5
6
7
8
9
10
注意:
mysql 数据解析关注的表,Perl正则表达式.
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\)
常见例子:
1. 所有表:.* or .*\\..*
2. canal schema下所有表: canal\\..*
3. canal下的以canal打头的表:canal\\.canal.*
4. canal schema下的一张表:canal.test1
5. 多个规则组合使用:canal\\..*,mysql.test1,mysql.test2 (逗号分隔)
注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤)

(3) 进入bin目录下启动:sb bin/startup.sh

4.项目中引入canal

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--mysql-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    </dependency>
    </dependencies>
  2. 创建application.properties配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 服务端口
    server.port=10000

    # 服务名
    spring.application.name=canal-client

    # 环境设置:dev、test、prod
    spring.profiles.active=dev

    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=root
  3. 编写canal客户端类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    import com.alibaba.otter.canal.client.CanalConnector;
    import com.alibaba.otter.canal.client.CanalConnectors;
    import com.alibaba.otter.canal.protocol.CanalEntry.*;
    import com.alibaba.otter.canal.protocol.Message;
    import com.google.protobuf.InvalidProtocolBufferException;
    import org.apache.commons.dbutils.DbUtils;
    import org.apache.commons.dbutils.QueryRunner;
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    import java.net.InetSocketAddress;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Queue;
    import java.util.concurrent.ConcurrentLinkedQueue;

    @Component
    public class CanalClient {

    //sql队列
    private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();

    @Resource
    private DataSource dataSource;

    /**
    * canal入库方法
    */
    public void run() {
    CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.44.132",11111), "example", "", "");

    int batchSize = 1000;
    try {
    connector.connect();
    connector.subscribe(".*\\..*");
    connector.rollback();
    try {
    while (true) {
    //尝试从master那边拉去数据batchSize条记录,有多少取多少
    Message message = connector.getWithoutAck(batchSize);
    long batchId = message.getId();
    int size = message.getEntries().size();
    if (batchId == -1 || size == 0) {
    Thread.sleep(1000);
    } else {
    dataHandle(message.getEntries());
    }
    connector.ack(batchId);
    //当队列里面堆积的sql大于一定数值的时候就模拟执行
    if (SQL_QUEUE.size() >= 1) {
    executeQueueSql();
    }
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
    }
    } finally {
    connector.disconnect();
    }
    }

    /**
    * 模拟执行队列里面的sql语句
    */
    public void executeQueueSql() {
    int size = SQL_QUEUE.size();
    for (int i = 0; i < size; i++) {
    String sql = SQL_QUEUE.poll();
    System.out.println("[sql]----> " + sql);
    this.execute(sql.toString());
    }
    }

    /**
    * 数据处理
    *
    * @param entrys
    */
    private void dataHandle(List<Entry> entrys) throws InvalidProtocolBufferException {
    for (Entry entry : entrys) {
    if (EntryType.ROWDATA == entry.getEntryType()) {
    RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
    EventType eventType = rowChange.getEventType();
    if (eventType == EventType.DELETE) {
    saveDeleteSql(entry);
    } else if (eventType == EventType.UPDATE) {
    saveUpdateSql(entry);
    } else if (eventType == EventType.INSERT) {
    saveInsertSql(entry);
    }
    }
    }
    }
    /**
    * 保存更新语句
    *
    * @param entry
    */
    private void saveUpdateSql(Entry entry) {
    try {
    RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
    List<RowData> rowDatasList = rowChange.getRowDatasList();
    for (RowData rowData : rowDatasList) {
    List<Column> newColumnList = rowData.getAfterColumnsList();
    StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set ");
    for (int i = 0; i < newColumnList.size(); i++) {
    sql.append(" " + newColumnList.get(i).getName()
    + " = '" + newColumnList.get(i).getValue() + "'");
    if (i != newColumnList.size() - 1) {
    sql.append(",");
    }
    }
    sql.append(" where ");
    List<Column> oldColumnList = rowData.getBeforeColumnsList();
    for (Column column : oldColumnList) {
    if (column.getIsKey()) {
    //暂时只支持单一主键
    sql.append(column.getName() + "=" + column.getValue());
    break;
    }
    }
    SQL_QUEUE.add(sql.toString());
    }
    } catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
    }
    }

    /**
    * 保存删除语句
    *
    * @param entry
    */
    private void saveDeleteSql(Entry entry) {
    try {
    RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
    List<RowData> rowDatasList = rowChange.getRowDatasList();
    for (RowData rowData : rowDatasList) {
    List<Column> columnList = rowData.getBeforeColumnsList();
    StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");
    for (Column column : columnList) {
    if (column.getIsKey()) {
    //暂时只支持单一主键
    sql.append(column.getName() + "=" + column.getValue());
    break;
    }
    }
    SQL_QUEUE.add(sql.toString());
    }
    } catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
    }
    }
    /**
    * 保存插入语句
    *
    * @param entry
    */
    private void saveInsertSql(Entry entry) {
    try {
    RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
    List<RowData> rowDatasList = rowChange.getRowDatasList();
    for (RowData rowData : rowDatasList) {
    List<Column> columnList = rowData.getAfterColumnsList();
    StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " (");
    for (int i = 0; i < columnList.size(); i++) {
    sql.append(columnList.get(i).getName());
    if (i != columnList.size() - 1) {
    sql.append(",");
    }
    }
    sql.append(") VALUES (");
    for (int i = 0; i < columnList.size(); i++) {
    sql.append("'" + columnList.get(i).getValue() + "'");
    if (i != columnList.size() - 1) {
    sql.append(",");
    }
    }
    sql.append(")");
    SQL_QUEUE.add(sql.toString());
    }
    } catch (InvalidProtocolBufferException e) {
    e.printStackTrace();
    }
    }
    /**
    * 入库
    * @param sql
    */
    public void execute(String sql) {
    Connection con = null;
    try {
    if(null == sql) return;
    con = dataSource.getConnection();
    QueryRunner qr = new QueryRunner();
    int row = qr.execute(con, sql);
    System.out.println("update: "+ row);
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    DbUtils.closeQuietly(con);
    }
    }
    }
  4. 创建启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @SpringBootApplication
    public class CanalApplication implements CommandLineRunner {
    @Resource
    private CanalClient canalClient;
    public static void main(String[] args) {
    SpringApplication.run(CanalApplication.class, args);
    }
    @Override
    public void run(String... strings) throws Exception {
    //项目启动,执行canal客户端监听
    canalClient.run();
    }
    }

- Gateway网关

作用:对相同模块自动实现负载均衡,轮询权重最少请求时间

  • 全局性流控
  • 日志统计
  • 防止SQL注入
  • 防止Web攻击
  • 屏蔽工具扫面
  • 黑白IP名单
  • 证书/加解密处理
  • 服务级别流控
  • 服务降级与熔断
  • 路由与负载均衡,灰度策略
  • 服务过滤,聚合与发现
  • 权限验证与用户等级策略
  • 业务规则与参数校验
  • 多级缓存策略

功能特征:

  • 基于Spring Framework5,Project Reactor和Spring Boot2.0进行构建
  • 动态路由,能够匹配任何请求属性
  • 支持路径重写
  • 集成Spring Cloud服务发现功能(Nacos,Eruka)
  • 可集成流控降级功能(Sentinel,Hystrix)
  • 可以对路由器指定易于编写的Predicate(断言)Filter(过滤器)
  1. 创建微服务模块

  2. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!--gson-->
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    </dependency>

    <!--服务调用-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  3. 创建配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #服务端口
    server.port=8222
    #服务名
    spring.application.name=service-gateway
    #nacos服务地址
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

    #使用服务发现路由
    spring.cloud.gateway.discovery.locator.enabled=true

    #设置路由id
    spring.cloud.gateway.routes[0].id=service-cms
    #设置路由的uri
    spring.cloud.gateway.routes[0].uri=lb://service-cms
    #设置路由断言,代理serviceId为auth-service的/auth/路径
    spring.cloud.gateway.routes[0].predicates=Path=/educms/**

    #配置service-edu服务
    spring.cloud.gateway.routes[1].id=service-edu
    spring.cloud.gateway.routes[1].uri=lb://service-edu
    spring.cloud.gateway.routes[1].predicates=Path=/eduservice/**
    #http://localhost/order-service/order/add
    #http://localhost/order/add
    spring.cloud.gateway.routes[2].filters.StripPrefix=1//转发之前去掉第一层路径
    spring.cloud.gateway.roters[2].predicates.cookie=键,正则表达式
    spring.cloud.gateway.router[2].predicates.Header=X-Request-Id,\d+ 根据请求头匹配
    spring.cloud.gateway.router[2].predicates.Host


  4. 启动类上添加服务发现注解@EnableDiscoveryClient

  5. 通过网关地址访问

- 注册业务

实现方式:

  • 单一服务器模式

    1. 使用session对象广播机制实现—》session复制(默认30分钟session过期)

    2. 使用cookie+redis实现—》

      • 在项目任意模块进行登录,登录之后,把数据放到两个地方
        • redis,在key:生成唯一随机值(ip,用户id等等),在value:用户数据
        • cookie:把redis里面生成key值放到cookie里面
      • 访问项目中的其他模块,发送请求带着cookie进行发送,获取cookie值,拿着cookie做事情
        1. 获取cookie,到redis进行查询,根据key进行查询,如果查询数据存在,登录
    3. 使用token实现

      • 在项目某个模块进行登录,登录之后,按照规则生成字符传,把登录之后的用户信息包含到生成的字符串里面,

        返回字符串

        1. 可以把字符串通过cookie返回
        2. 把字符串通过地址栏返回
      • 在去访问其他模块,每次访问在地址栏带着生成字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息,如果可以获取到,就登录

- JWT令牌

JWT生成字符串组成:

  1. JWT头信息
  2. 有效荷载 包含主体信息(用户信息)
  3. 签名哈希,防伪标志

使用:

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <!--JWT-->
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    </dependency>
    </dependencies>
  2. 创建工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    package com.atguigu.oss.commonutils;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.util.StringUtils;

    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;

    /**
    * @author helen
    * @since 2019/10/16
    */
    public class JwtUtils {

    public static final long EXPIRE = 1000 * 60 * 60 * 24;//token过期时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//密钥

    public static String getJwtToken(String id, String nickname){

    String JwtToken = Jwts.builder()
    .setHeaderParam("typ", "JWT")
    .setHeaderParam("alg", "HS256")
    .setSubject("guli-user")//分类
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置过期时间
    //设置token主体部分,存储用户信息
    .claim("id", id)
    .claim("nickname", nickname)

    .signWith(SignatureAlgorithm.HS256, APP_SECRET)
    .compact();

    return JwtToken;
    }

    /**
    * 判断token是否存在与有效
    * @param jwtToken
    * @return
    */
    public static boolean checkToken(String jwtToken) {
    if(StringUtils.isEmpty(jwtToken)) return false;
    try {
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 判断token是否存在与有效
    * @param request
    * @return
    */
    public static boolean checkToken(HttpServletRequest request) {
    try {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return false;
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 根据token获取会员id
    * @param request
    * @return
    */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return "";
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    Claims claims = claimsJws.getBody();
    return (String)claims.get("id");
    }
    }

- MD5加密工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.atguigu.oss.commonutils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


public final class MD5 {

public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}

public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}

}

- 验证码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.atguigu.oss.commonutils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;

public class VerifyCode {

public static String drawRandomText(int width, int height, BufferedImage verifyImg) {
Graphics2D graphics = (Graphics2D)verifyImg.getGraphics();
graphics.setColor(Color.WHITE);//设置画笔颜色-验证码背景色
graphics.fillRect(0, 0, width, height);//填充背景
graphics.setFont(new Font("微软雅黑", Font.BOLD, 40));
//数字和字母的组合
String baseNumLetter= "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";

StringBuffer sBuffer = new StringBuffer();
int x = 10; //旋转原点的 x 坐标
String ch = "";
Random random = new Random();
for(int i = 0;i < 4;i++){
graphics.setColor(getRandomColor());
//设置字体旋转角度
int degree = random.nextInt() % 30; //角度小于30度
int dot = random.nextInt(baseNumLetter.length());
ch = baseNumLetter.charAt(dot) + "";
sBuffer.append(ch);
//正向旋转
graphics.rotate(degree * Math.PI / 180, x, 45);
graphics.drawString(ch, x, 45);
//反向旋转
graphics.rotate(-degree * Math.PI / 180, x, 45);
x += 48;
}
//画干扰线
for (int i = 0; i <6; i++) {
// 设置随机颜色
graphics.setColor(getRandomColor());
// 随机画线
graphics.drawLine(random.nextInt(width), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
//添加噪点
for(int i=0;i<30;i++){
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.fillRect(x1, y1, 2,2);
}
return sBuffer.toString();
}
/**
* 随机取色

*/
private static Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256),
ran.nextInt(256), ran.nextInt(256));
return color;
}
}

- 验证码请求接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public String getVerifyCode(HttpServletResponse response,HttpServletRequest request) {
String code=null;
try {
int width=200;
int height=69;
BufferedImage verifyImg=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//生成对应宽高的初始图片
String randomText = VerifyCode.drawRandomText(width,height,verifyImg);
//单独的一个类方法,出于代码复用考虑,进行了封装。

//功能是生成验证码字符并加上噪点,干扰线,返回值为验证码字符
request.getSession().setAttribute("verifyCode", randomText);
response.setContentType("image/png");//必须设置响应内容类型为图片,否则前台不识别
OutputStream os = response.getOutputStream(); //获取文件输出流
ImageIO.write(verifyImg,"png",os);//输出图片流
os.flush();
os.close();//关闭流
code= (String) request.getSession().getAttribute("verifyCode");
System.out.println(request.getSession().getAttribute("verifyCode"));
} catch (IOException e) {
// this.logger.error(e.getMessage());
e.printStackTrace();
}
return code;
}

- (前端)定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
//倒计时
timeDown(){

let result=setInterval(()=>{
--this.second;
this.codeTest=this.second
if(this.second<1){
clearInterval(result);
this.second=60;//设置时间为60
this.codeTest="获取验证码"
},1000)//每隔1000ms执行一次代码
}
}

</script>

- 登录业务

- 登录流程

  1. 调用接口登录返回token字符串
  2. 将返回的返回的token放到cookie里面
  3. 创建前端拦截器,判断cookie里面是否有token字符串
    • 若有,把token放到header(请求头)
  4. 根据token值,调用接口,根据token获取用户信息,为了首页面显示
  5. 在首页面中根据cookie显示用户信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//登录的方法
submitLogin() {
//第一步 调用接口进行登录,返回token字符串
loginApi.submitLoginUser(this.user)
.then(response => {
//第二步 获取token字符串放到cookie里面
//第一个参数cookie名称,第二个参数值,第三个参数作用范围
cookie.set('guli_token',response.data.data.token,{domain: 'localhost'})

//第四步 调用接口 根据token获取用户信息,为了首页面显示
loginApi.getLoginUserInfo()
.then(response => {
this.loginInfo = response.data.data.userInfo
//获取返回用户信息,放到cookie里面
cookie.set('guli_ucenter',this.loginInfo,{domain: 'localhost'})

//跳转页面
window.location.href = "/";
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import axios from 'axios'

//创建axios实例
const service=axios.create({
baseURL:'http://localhost:9001',//api的baseurl
timeout:2000 //请求超时时间
})

//http request 拦截器
service.interceptors.request.use(
config=>{
//debugger
//判断cookie里面是否有名称guii_token的数据
if (cookie.get('guli_token')){
//将获取到的cookie值放到header里面
config.headers['token']=cookie.get('guli_token');
}
return config
},
err=>{
return Promise.reject(err);
}
)

export default service

- 微信扫码登录的应用

OAuth2:特定问题解决方案

  1. 开放系统间授权
  2. 分布式访问问题

微信扫码登录注册流程:

  1. 注册https://open.weixin.qq.com
  2. 邮箱激活
  3. 完善开发者资料
  4. 完善开发者资质认证:准备营业执照,1-2个工作日审批,300元
  5. 创建网站应用:提交审核,7个工作日审批
  6. 熟悉微信登录流程

- Springboot工具类

- 断言

  1. 断言是一个逻辑判断,用于检查不应该发生的情况
  2. Assert 关键字在 JDK1.4 中引入,可通过 JVM 参数-enableassertions开启
  3. SpringBoot 中提供了 Assert 断言工具类,通常用于数据合法性检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 要求参数 object 必须为非空(Not Null),否则抛出异常,不予放行  
// 参数 message 参数用于定制异常信息。
void notNull(Object object, String message)
// 要求参数必须空(Null),否则抛出异常,不予『放行』。
// 和 notNull() 方法断言规则相反
void isNull(Object object, String message)
// 要求参数必须为真(True),否则抛出异常,不予『放行』。
void isTrue(boolean expression, String message)
// 要求参数(List/Set)必须非空(Not Empty),否则抛出异常,不予放行
void notEmpty(Collection collection, String message)
// 要求参数(String)必须有长度(即,Not Empty),否则抛出异常,不予放行
void hasLength(String text, String message)
// 要求参数(String)必须有内容(即,Not Blank),否则抛出异常,不予放行
void hasText(String text, String message)
// 要求参数是指定类型的实例,否则抛出异常,不予放行
void isInstanceOf(Class type, Object obj, String message)
// 要求参数 `subType` 必须是参数 superType 的子类或实现类,否则抛出异常,不予放行
void isAssignable(Class superType, Class subType, String message)

- 对象、数组、集合

- ObjectUtils
  1. 获取对象的基本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取对象的类名。参数为 null 时,返回字符串:"null"   
String nullSafeClassName(Object obj)
// 参数为 null 时,返回 0
int nullSafeHashCode(Object object)
// 参数为 null 时,返回字符串:"null"
String nullSafeToString(boolean[] array)
// 获取对象 HashCode(十六进制形式字符串)。参数为 null 时,返回 0 
String getIdentityHexString(Object obj)
// 获取对象的类名和 HashCode。 参数为 null 时,返回字符串:"" 
String identityToString(Object obj)
// 相当于 toString()方法,但参数为 null 时,返回字符串:""
String getDisplayString(Object obj)

  1. 判断工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 判断数组是否为空  
boolean isEmpty(Object[] array)
// 判断参数对象是否是数组
boolean isArray(Object obj)
// 判断数组中是否包含指定元素
boolean containsElement(Object[] array, Object element)
// 相等,或同为 null时,返回 true
boolean nullSafeEquals(Object o1, Object o2)
/*
判断参数对象是否为空,判断标准为:
    Optional: Optional.empty()
       Array: length == 0
CharSequence: length == 0
  Collection: Collection.isEmpty()
         Map: Map.isEmpty()
 */
boolean isEmpty(Object obj)

  1. 其他工具方法
1
2
3
4
5
// 向参数数组的末尾追加新元素,并返回一个新数组  
<A, O extends A> A[] addObjectToArray(A[] array, O obj)
// 原生基础类型数组 --> 包装类数组
Object[] toObjectArray(Object source)

- StringUtils
  1. 字符串判断工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 判断字符串是否为 null,或 ""。注意,包含空白符的字符串为非空  
boolean isEmpty(Object str)
// 判断字符串是否是以指定内容结束。忽略大小写
boolean endsWithIgnoreCase(String str, String suffix)
// 判断字符串是否已指定内容开头。忽略大小写
boolean startsWithIgnoreCase(String str, String prefix) 
// 是否包含空白符
boolean containsWhitespace(String str)
// 判断字符串非空且长度不为 0,即,Not Empty
boolean hasLength(CharSequence str)
// 判断字符串是否包含实际内容,即非仅包含空白符,也就是 Not Blank
boolean hasText(CharSequence str)
// 判断字符串指定索引处是否包含一个子串。
boolean substringMatch(CharSequence str, int index, CharSequence substring)
// 计算一个字符串中指定子串的出现次数
int countOccurrencesOf(String str, String sub)

  1. 字符串操作工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 查找并替换指定子串  
String replace(String inString, String oldPattern, String newPattern)
// 去除尾部的特定字符
String trimTrailingCharacter(String str, char trailingCharacter) 
// 去除头部的特定字符
String trimLeadingCharacter(String str, char leadingCharacter)
// 去除头部的空白符
String trimLeadingWhitespace(String str)
// 去除头部的空白符
String trimTrailingWhitespace(String str)
// 去除头部和尾部的空白符
String trimWhitespace(String str)
// 删除开头、结尾和中间的空白符
String trimAllWhitespace(String str)
// 删除指定子串
String delete(String inString, String pattern)
// 删除指定字符(可以是多个)
String deleteAny(String inString, String charsToDelete)
// 对数组的每一项执行 trim() 方法
String[] trimArrayElements(String[] array)
// 将 URL 字符串进行解码
String uriDecode(String source, Charset charset)

  1. 路径相关工具方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 解析路径字符串,优化其中的 “..”   
String cleanPath(String path)
// 解析路径字符串,解析出文件名部分
String getFilename(String path)
// 解析路径字符串,解析出文件后缀名
String getFilenameExtension(String path)
// 比较两个两个字符串,判断是否是同一个路径。会自动处理路径中的 “..” 
boolean pathEquals(String path1, String path2)
// 删除文件路径名中的后缀部分
String stripFilenameExtension(String path) 
// 以 “. 作为分隔符,获取其最后一部分
String unqualify(String qualifiedName)
// 以指定字符作为分隔符,获取其最后一部分
String unqualify(String qualifiedName, char separator)

- CollectionUtils
  1. 集合判断工具.  我是程序汪
1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断 List/Set 是否为空  
boolean isEmpty(Collection<?> collection)
// 判断 Map 是否为空
boolean isEmpty(Map<?,?> map)
// 判断 List/Set 中是否包含某个对象
boolean containsInstance(Collection<?> collection, Object element)
// 以迭代器的方式,判断 List/Set 中是否包含某个对象
boolean contains(Iterator<?> iterator, Object element)
// 判断 List/Set 是否包含某些对象中的任意一个
boolean containsAny(Collection<?> source, Collection<?> candidates)
// 判断 List/Set 中的每个元素是否唯一。即 List/Set 中不存在重复元素
boolean hasUniqueObject(Collection<?> collection)

  1. 集合操作工具。  Java项目分享  最新整理全集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将 Array 中的元素都添加到 List/Set 中  
<E> void mergeArrayIntoCollection(Object array, Collection<E> collection)  
// 将 Properties 中的键值对都添加到 Map 中
<K,V> void mergePropertiesIntoMap(Properties props, Map<K,V> map)
// 返回 List 中最后一个元素
<T> T lastElement(List<T> list)  
// 返回 Set 中最后一个元素
<T> T lastElement(Set<T> set) 
// 返回参数 candidates 中第一个存在于参数 source 中的元素
<E> E findFirstMatch(Collection<?> source, Collection<E> candidates)
// 返回 List/Set 中指定类型的元素。
<T> T findValueOfType(Collection<?> collection, Class<T> type)
// 返回 List/Set 中指定类型的元素。如果第一种类型未找到,则查找第二种类型,以此类推
Object findValueOfType(Collection<?> collection, Class<?>[] types)
// 返回 List/Set 中元素的类型
Class<?> findCommonElementType(Collection<?> collection)

- 文件、资源、IO 流

- FileCopyUtils
  1. 输入
1
2
3
4
5
6
7
// 从文件中读入到字节数组中  
byte[] copyToByteArray(File in)
// 从输入流中读入到字节数组中
byte[] copyToByteArray(InputStream in)
// 从输入流中读入到字符串中
String copyToString(Reader in)

  1. 输出
1
2
3
4
5
6
7
8
9
10
11
12
13
// 从字节数组到文件  
void copy(byte[] in, File out)
// 从文件到文件
int copy(File in, File out)
// 从字节数组到输出流
void copy(byte[] in, OutputStream out) 
// 从输入流到输出流
int copy(InputStream in, OutputStream out) 
// 从输入流到输出流
int copy(Reader in, Writer out)
// 从字符串到输出流
void copy(String in, Writer out)

- ResourceUtils
  1. 从资源路径获取文件
1
2
3
4
5
6
7
// 判断字符串是否是一个合法的 URL 字符串。  
static boolean isUrl(String resourceLocation)
// 获取 URL
static URL getURL(String resourceLocation) 
// 获取文件(在 JAR 包内无法正常使用,需要是一个独立的文件)
static File getFile(String resourceLocation)

  1. Resource
1
2
3
4
5
6
7
8
9
// 文件系统资源 D:\...  
FileSystemResource
// URL 资源,如 file://... http://...
UrlResource
// 类路径下的资源,classpth:...
ClassPathResource
// Web 容器上下文中的资源(jar 包、war 包)
ServletContextResource

1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断资源是否存在  
boolean exists()
// 从资源中获得 File 对象
File getFile()
// 从资源中获得 URI 对象
URI getURI()
// 从资源中获得 URI 对象
URL getURL()
// 获得资源的 InputStream
InputStream getInputStream()
// 获得资源的描述信息
String getDescription()

- StreamUtils
  1. 输入
1
2
3
4
5
void copy(byte[] in, OutputStream out)  
int copy(InputStream in, OutputStream out)
void copy(String in, Charset charset, OutputStream out)
long copyRange(InputStream in, OutputStream out, long start, long end)

  1. 输出
1
2
3
4
5
byte[] copyToByteArray(InputStream in)  
String copyToString(InputStream in, Charset charset)
// 舍弃输入流中的内容
int drain(InputStream in)

- 反射、AOP

- ReflectionUtils
  1. 获取方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在类中查找指定方法  
Method findMethod(Class<?> clazz, String name) 
// 同上,额外提供方法参数类型作查找条件
Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) 
// 获得类中所有方法,包括继承而来的
Method[] getAllDeclaredMethods(Class<?> leafClass) 
// 在类中查找指定构造方法
Constructor<T> accessibleConstructor(Class<T> clazz, Class<?>... parameterTypes) 
// 是否是 equals() 方法
boolean isEqualsMethod(Method method) 
// 是否是 hashCode() 方法 
boolean isHashCodeMethod(Method method) 
// 是否是 toString() 方法
boolean isToStringMethod(Method method) 
// 是否是从 Object 类继承而来的方法
boolean isObjectMethod(Method method) 
// 检查一个方法是否声明抛出指定异常
boolean declaresException(Method method, Class<?> exceptionType)

  1. 执行方法
1
2
3
4
5
6
7
8
9
// 执行方法  
Object invokeMethod(Method method, Object target)  
// 同上,提供方法参数
Object invokeMethod(Method method, Object target, Object... args) 
// 取消 Java 权限检查。以便后续执行该私有方法
void makeAccessible(Method method) 
// 取消 Java 权限检查。以便后续执行私有构造方法
void makeAccessible(Constructor<?> ctor)

  1. 获取字段
1
2
3
4
5
6
7
// 在类中查找指定属性  
Field findField(Class<?> clazz, String name) 
// 同上,多提供了属性的类型
Field findField(Class<?> clazz, String name, Class<?> type) 
// 是否为一个 "public static final" 属性
boolean isPublicStaticFinal(Field field)

  1. 设置字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取 target 对象的 field 属性值  
Object getField(Field field, Object target) 
// 设置 target 对象的 field 属性值,值为 value
void setField(Field field, Object target, Object value) 
// 同类对象属性对等赋值
void shallowCopyFieldState(Object src, Object dest)
// 取消 Java 的权限控制检查。以便后续读写该私有属性
void makeAccessible(Field field) 
// 对类的每个属性执行 callback
void doWithFields(Class<?> clazz, ReflectionUtils.FieldCallback fc) 
// 同上,多了个属性过滤功能。
void doWithFields(Class<?> clazz, ReflectionUtils.FieldCallback fc, 
                  ReflectionUtils.FieldFilter ff) 
// 同上,但不包括继承而来的属性
void doWithLocalFields(Class<?> clazz, ReflectionUtils.FieldCallback fc)

- AopUtils
  1. 判断代理类型
1
2
3
4
5
6
7
// 判断是不是 Spring 代理对象  
boolean isAopProxy()
// 判断是不是 jdk 动态代理对象
isJdkDynamicProxy()
// 判断是不是 CGLIB 代理对象
boolean isCglibProxy()

  1. 获取被代理对象的 class
1
2
3
// 获取被代理的目标 class  
Class<?> getTargetClass()

- AopContext
  1. 获取当前对象的代理对象
1
Object currentProxy()  

- 第三方整合

- 阿里云播放器

- 获取播放地址

参考文档:https://help.aliyun.com/document_detail/61064.html

前面的 03-使用服务端SDK 介绍了如何获取非加密视频的播放地址。直接使用03节的例子获取加密视频播放地址会返回如下错误信息

Currently only the AliyunVoDEncryption stream exists, you must use the Aliyun player to play or set the value of ResultType to Multiple.

目前只有AliyunVoDEncryption流存在,您必须使用Aliyun player来播放或将ResultType的值设置为Multiple。

因此在testGetPlayInfo测试方法中添加 ResultType 参数,并设置为true

1
privateParams.put("ResultType","Multiple");

此种方式获取的视频文件不能直接播放,必须使用阿里云播放器播放

- 集成播放器
  1. 引入脚本

    1
    2
    <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" />
    <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
  2. 初始化视频播放器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <div class="prism-player" id="J_prismPlayer"></div>
    <script>
    var player = new Aliplayer({
    id: 'J_prismPlayer',
    width: '100%',
    autoplay: false,
    cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
    //播放配置
    },function(player){
    console.log('播放器创建好了。')
    });
    </script>
    </body>
  3. 视频地址播放

    在Aliplayer的配置参数中添加如下属性

    1
    2
    //播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频
    source:"视频播放地址"

    启动浏览器运行,测试视频的播放

  4. 播放凭证播放

    阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。如果凭证过期则无法获取播放地址,需要重新获取凭证。

    1
    2
    3
    encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
    vid : '视频id',
    playauth : '视频授权码',

    注意:播放凭证有过期时间,默认值:100秒 。取值范围:100~3000

    设置播放凭证的有效期

    在获取播放凭证的测试用例中添加如下代码

    1
    request.setAuthInfoTimeout(200L)

    在线视频播放地址:https://player.alicdn.com/aliplayer/setting/setting.html

- 微信支付

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencies>
    <dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    </dependency>
    </dependencies>

- 功能实现

- windows端口查询

windows:查看端口状态 netstat -ano

1
2
3
4
//获取路由中id值
if(this.$route.params && this.$route.params.id){
this.courseId=this.$route.params.id
}

- oss对象存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# coding=utf8
# date: Tue Oct 21 16:49:27 CST 2014
# author: tanb

import os, json

import string
import random

from oss.oss.oss_api import *
from oss.oss.oss_xml_handler import *

__host = ''
__access_key = ''
__secret_key = ''
__bucket = ''
__upload_file_name = ''
__obj_dir = ''

def upload(oss):
# upload file
obj = __obj_dir + __upload_file_name
res = oss.put_object_from_file(__bucket, obj, __upload_file_name, 'text/HTML', headers = {})

if res.status == 200:
print 'Upload OK'
print ('object name: %s' % obj)
return

print 'Upload fail'
print 'http return code: %d' % res.status
print 'request-id: %s' % res.getheader('x-oss-request-id')
print 'reason: %s' % res.read()

if __name__ == '__main__':
if len(sys.argv) == 2:
__upload_file_name = sys.argv[1]
if os.path.isfile(__upload_file_name) == False:
print('请确认上传的数据文件存在,并且它不是文件夹。')
sys.exit(-1)
else:
print 'Please input the file name which you want to upload!'

json_data = open('cfg.json', 'r')
try:
cfg = json.load(json_data)
if cfg['id'] == '' or cfg['secret'] == '':
print('请配置你的 access_key 和 secret_key,这些信息你可以从公开课的网页上获取。')
sys.exit(1)
except:
print('cfg.json 文件有误,不符合 JSON 的语法要求,请仔细检查')
sys.exit(1)

json_data.close()

__access_key = cfg['id']
__secret_key = cfg['secret']
__host = cfg['endpoint']

if cfg['bucket'] == '':
print('请先创建 bucket:python oss_api_tutorial.py create_bucket')
sys.exit(1)

__bucket = cfg['bucket']
__obj_dir = cfg['obj_dir']

oss = OssAPI(__host, __access_key, __secret_key)
oss.set_debug(True)

upload(oss)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.ClientException ;
import com.aliyun.oss.ServerException ;
import com.aliyun.oss.OSSClient ;
import com.aliyun.oss.OSSErrorCode ;
import com.aliyun.oss.OSSException ;

import org.apache.commons.compress.utils.IOUtils;

//
// init OSS client
//
public OSSClient initOSSClient(String accessKey, String accessKeySecret) {
OSSClient client = new OSSClient(accessKey, accessKeySecret);
return client;
}

//
// create bucket
//
public void createBucket(OSSClient client, String bucketName) {
return client.createBucket(bucketName);
}

//
// upload @filePath to @bucketName using @key as the object ID
//
public void putObject(OSSClient client, String bucketName, String key, String filePath) {
// new input stream
File file = new File(filePath);
InputStream content = new FileInputStream(file);

// create metadata of the object
ObjectMetadata meta = new ObjectMetaData();
meta.setContentLength(file.length()); // we MUST set the content length

PutObjectResult res = client.putObject(bucketName, key, content, meta);

// print etag of the file on OSS
System.out.println(result.getEtag());
}

//
// list all the object on @bucketName
//
public void listObject(OSSClient client, String bucketName) {
ObjectListing listing = client.listObject(bucketName);

for (OSSObjectSummary os:listing.getObjectSummaries())
System.out.println(os.getKey());
}

//
// get object
//
public void getObject(OSSClient client, String bucketName, String key) {
// get object
OSSObject obj = client.getObject(bucketName, key);

InputStream content = obj.getObjectContent();
OutputStream output = new FileOutputStream("/tmp/obj");

IOUtils.copy(content, output);
content.close();
out.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class OssServiceApplication extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
return builder.sources(OssServiceApplication.class);
}

public static void main(String[] args) {
SpringApplication.run(OssServiceApplication.class, args);
}

}

- 查询ip地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//java工具类实现
public class IpUtils {
/**
* 获取ip属地
* @param ip
* @return
* @throws Exception
*/
public static String getCityInfo(String ip) throws Exception {
//获得文件流时,因为读取的文件是在打好jar文件里面,不能直接通过文件资源路径拿到文件,但是可以在jar包中拿到文件流
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("ip2region.db");
Resource resource = resources[0];
InputStream is = resource.getInputStream();
File target = new File("ip2region.db");
FileUtils.copyInputStreamToFile(is, target);
is.close();

if (StringUtils.isEmpty(String.valueOf(target))) {
// log.error("Error: Invalid ip2region.db file");
return null;
}
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, String.valueOf(target));
//查询算法
//B-tree, B树搜索(更快)
int algorithm = DbSearcher.BTREE_ALGORITHM;
try {
//define the method
Method method;
method = searcher.getClass().getMethod("btreeSearch", String.class);
DataBlock dataBlock;
if (!Util.isIpAddress(ip)) {
// log.error("Error: Invalid ip address");
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
String ipInfo = dataBlock.getRegion();
if (!StringUtils.isEmpty(ipInfo)) {
ipInfo = ipInfo.replace("|0", "");
ipInfo = ipInfo.replace("0|", "");
}
return ipInfo;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public static String getIpPossession(String ip) throws Exception {
String cityInfo = IpUtils.getCityInfo(ip);
if (!StringUtils.isEmpty(cityInfo)) {
cityInfo = cityInfo.replace("|", " ");
String[] cityList = cityInfo.split(" ");
if (cityList.length > 0) {
// 国内的显示到具体的省
if ("中国".equals(cityList[0])) {
if (cityList.length > 1) {
return cityList[1];
}
}
// 国外显示到国家
return cityList[0];
}
}
return "未知";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# python 实现

#-*- coding:utf-8 -*-
"""
" ip2region python seacher client module
"
" Author: koma<komazhang@foxmail.com>
" Date : 2015-11-06
"""
import struct, io, socket, sys

class Ip2Region(object):

def __init__(self, dbfile):
self.__INDEX_BLOCK_LENGTH = 12
self.__TOTAL_HEADER_LENGTH = 8192
self.__f = None
self.__headerSip = []
self.__headerPtr = []
self.__headerLen = 0
self.__indexSPtr = 0
self.__indexLPtr = 0
self.__indexCount = 0
self.__dbBinStr = ''
self.initDatabase(dbfile)

def memorySearch(self, ip):
"""
" memory search method
" param: ip
"""
if not ip.isdigit(): ip = self.ip2long(ip)

if self.__dbBinStr == '':
self.__dbBinStr = self.__f.read() #read all the contents in file
self.__indexSPtr = self.getLong(self.__dbBinStr, 0)
self.__indexLPtr = self.getLong(self.__dbBinStr, 4)
self.__indexCount = int((self.__indexLPtr - self.__indexSPtr)/self.__INDEX_BLOCK_LENGTH)+1

l, h, dataPtr = (0, self.__indexCount, 0)
while l <= h:
m = int((l+h) >> 1)
p = self.__indexSPtr + m*self.__INDEX_BLOCK_LENGTH
sip = self.getLong(self.__dbBinStr, p)

if ip < sip:
h = m -1
else:
eip = self.getLong(self.__dbBinStr, p+4)
if ip > eip:
l = m + 1;
else:
dataPtr = self.getLong(self.__dbBinStr, p+8)
break

if dataPtr == 0: raise Exception("Data pointer not found")

return self.returnData(dataPtr)

def binarySearch(self, ip):
"""
" binary search method
" param: ip
"""
if not ip.isdigit(): ip = self.ip2long(ip)

if self.__indexCount == 0:
self.__f.seek(0)
superBlock = self.__f.read(8)
self.__indexSPtr = self.getLong(superBlock, 0)
self.__indexLPtr = self.getLong(superBlock, 4)
self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) / self.__INDEX_BLOCK_LENGTH) + 1

l, h, dataPtr = (0, self.__indexCount, 0)
while l <= h:
m = int((l+h) >> 1)
p = m*self.__INDEX_BLOCK_LENGTH

self.__f.seek(self.__indexSPtr+p)
buffer = self.__f.read(self.__INDEX_BLOCK_LENGTH)
sip = self.getLong(buffer, 0)
if ip < sip:
h = m - 1
else:
eip = self.getLong(buffer, 4)
if ip > eip:
l = m + 1
else:
dataPtr = self.getLong(buffer, 8)
break

if dataPtr == 0: raise Exception("Data pointer not found")

return self.returnData(dataPtr)

def btreeSearch(self, ip):
"""
" b-tree search method
" param: ip
"""
if not ip.isdigit(): ip = self.ip2long(ip)

if len(self.__headerSip) < 1:
headerLen = 0
#pass the super block
self.__f.seek(8)
#read the header block
b = self.__f.read(self.__TOTAL_HEADER_LENGTH)
#parse the header block
for i in range(0, len(b), 8):
sip = self.getLong(b, i)
ptr = self.getLong(b, i+4)
if ptr == 0:
break
self.__headerSip.append(sip)
self.__headerPtr.append(ptr)
headerLen += 1
self.__headerLen = headerLen

l, h, sptr, eptr = (0, self.__headerLen, 0, 0)
while l <= h:
m = int((l+h) >> 1)

if ip == self.__headerSip[m]:
if m > 0:
sptr = self.__headerPtr[m-1]
eptr = self.__headerPtr[m]
else:
sptr = self.__headerPtr[m]
eptr = self.__headerPtr[m+1]
break

if ip < self.__headerSip[m]:
if m == 0:
sptr = self.__headerPtr[m]
eptr = self.__headerPtr[m+1]
break
elif ip > self.__headerSip[m-1]:
sptr = self.__headerPtr[m-1]
eptr = self.__headerPtr[m]
break
h = m - 1
else:
if m == self.__headerLen - 1:
sptr = self.__headerPtr[m-1]
eptr = self.__headerPtr[m]
break
elif ip <= self.__headerSip[m+1]:
sptr = self.__headerPtr[m]
eptr = self.__headerPtr[m+1]
break
l = m + 1

if sptr == 0: raise Exception("Index pointer not found")

indexLen = eptr - sptr
self.__f.seek(sptr)
index = self.__f.read(indexLen + self.__INDEX_BLOCK_LENGTH)

l, h, dataPrt = (0, int(indexLen/self.__INDEX_BLOCK_LENGTH), 0)
while l <= h:
m = int((l+h) >> 1)
offset = int(m * self.__INDEX_BLOCK_LENGTH)
sip = self.getLong(index, offset)

if ip < sip:
h = m - 1
else:
eip = self.getLong(index, offset+4)
if ip > eip:
l = m + 1;
else:
dataPrt = self.getLong(index, offset+8)
break

if dataPrt == 0: raise Exception("Data pointer not found")

return self.returnData(dataPrt)

def initDatabase(self, dbfile):
"""
" initialize the database for search
" param: dbFile
"""
try:
self.__f = io.open(dbfile, "rb")
except IOError as e:
print("[Error]: %s" % e)
sys.exit()

def returnData(self, dataPtr):
"""
" get ip data from db file by data start ptr
" param: dsptr
"""
dataLen = (dataPtr >> 24) & 0xFF
dataPtr = dataPtr & 0x00FFFFFF

self.__f.seek(dataPtr)
data = self.__f.read(dataLen)

return {
"city_id": self.getLong(data, 0),
"region" : data[4:]
}

def ip2long(self, ip):
_ip = socket.inet_aton(ip)
return struct.unpack("!L", _ip)[0]

def isip(self, ip):
p = ip.split(".")

if len(p) != 4 : return False
for pp in p:
if not pp.isdigit() : return False
if len(pp) > 3 : return False
if int(pp) > 255 : return False

return True

def getLong(self, b, offset):
if len(b[offset:offset+4]) == 4:
return struct.unpack('I', b[offset:offset+4])[0]
return 0

def close(self):
if self.__f != None:
self.__f.close()

self.__dbBinStr = None
self.__headerPtr = None
self.__headerSip = None

1
2
3
4
5
6
7
8
9
from binding.python.ip2Region import Ip2Region

if __name__ == "__main__":
dbFile = "../../data/ip2region.db"
searcher = Ip2Region(dbFile)
Ip_info=searcher.btreeSearch("114.115.245.186")
str=Ip_info.get("region").decode("utf-8")
list=str.split("|")
print(list[2])