- 微服务阶段

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

- SpringBoot Web开发

1
2
3
4
5
6
7
8
要解决的问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化
  1. 在springboot中,我们可以使用以下方式处理静态资源
    • webjars localhost:8080/webjars/
    • public,static,/**,resources localhost:8080/
  2. 优先级:resource>static(默认)>public
- Thymeleaf模板引擎
  • templetes目录下的所有页面,只能通过controller来跳转

    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>
  • 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
    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
- 拦截器
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;
}
}
}
- 学习目标
  • JDBC
  • Mybatis
  • Druid
  • Shiro
  • Spring Security
  • 异步任务,邮件发送,定时任务
  • Swagger
  • Dubbo+Zookeeper

- JDBC
  • 配置文件

    1
    2
    3
    4
    5
    6
    Spring:
    datasource:
    username: root
    password: xxxxxx
    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: xxxxx
    #?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

    ##### - MybatisPlus

    - ```yaml
    #开启日志,方便调试
    mybatis-plus:
    configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • ```xml

    com.baomidou mybatis-plus-boot-starter 3.0.5 org.apache.velocity velocity-engine-core 2.0 mysql mysql-connector-java 8.0.29
    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

    - ```java
    //代码生成器
    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();
    }
    }
  • ```java
    //UserMapper继承BaseMapper
    @Repository
    public interface UserMapper extends BaseMapper {

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - ```java
    @Autowired
    private UserMapper userMapper;

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

  • 自动填充:不用set,用mp方法实现

    • 在实体类的属性上添加注解@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;
      }
    • ```java
      //实现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);
      }
      

      }

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

      - **乐观锁**:主要解决 丢失更新

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

      - 实现方式:

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

      - 实现:

      1. 数据库中添加version字段

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

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

        1
        2
        3
        4
        5
        //在MymetaObjectHandler中
        @Override
        public void insertFill(MetaObject metaObject){
        this.setFieldValByName("version",1,metaObject);
        }
      3. ```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

        - **悲观锁**:串行

        - **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
      2
      3
      4
      @Bean
      public PaginationInterceptor pagintionInterceptor(){
      return new PaginationInterceptor();
      }
    • ```java
      @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());//上一页
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      - **mp实现逻辑删除**

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

      - 配置逻辑删除插件

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

- 整合Swagger
  • ```xml
    <!--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>
    
    io.swagger swagger-annotations 1.5.13
    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

    - ```java
    @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={"包路径"})
    */
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
  
- 访问Swagger:**localhost:端口号/swagger-ui.html**

- 注解:

- 定义在类上:**@Api(describe="描述")**
- 定义在方法上:**@ApiOperation(value="描述")**
- 定义在参数上:**@ApiParam(name="id",value="讲师ID",required=true)**

---

##### - 统一返回结果类

​```java
//统一返回结果的类
@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
    2
    3
    4
    5
    6
    7
    8
    9
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class GuliException extends RuntimeException{

    @ApiModelProperty(type="状态码")
    private Integer code;
    private String msg;
    }
  • 业务中需要的位置抛出GuliException

    1
    2
    3
    4
    5
    try{
    int a=10/0;
    }catch(Exception e){
    throw new GuliException(20001,"自定义异常")
    }
  • 添加异常处理方法

    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=xxxxxx
    aliyun.oss.file.keyid=xxxxxxxx
    aliyun.oss.file.keysecret=xxxxxxx
    #bucket可以在控制台创建,也可以用java代码创建
    aliyun.oss.file.bucketname=edu-jilf
  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中缓存

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. 创建配置类

    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;
    }
    }
  3. 开启缓存注解:

    (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,则在方法执行前就会清空缓存
- 杂记
- mysql

* 内连接:

* 左外连接:

* 右外连接:

1
2
3
4
//获取路由中id值
if(this.$route.params && this.$route.params.id){
this.courseId=this.$route.params.id
}
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
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);
}

}

- Springboot面试


6.1、SpringBoot是什么(了解)
是Spring的子项目,主要简化 Spring开发难度,去掉了繁重配置,提供各种启动器,可以让程序员很快上手,节省开发时间。

6.2、SpringBoot的优点(必会)
SpringBoot对上述 Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
版本锁定:解决是maven 依赖版本容易冲突的问题,集合了常用的并且测试过的所有版本
使用了Starter(启动器)管理依赖并能对版本进行集中控制,如下的父工程带有版本号, 就是对版本进行了集中控制;

图片

起步依赖:解决了完成某一个功能要整合的jar 包过多的问题,集合了常用的jar 包;

自动配置:解决了整合框架或者技术的配置文件过多,集合了所有的约定的默认配置;

内置Tomcat:通过内置的tomcat,无需再用其他外置的Tomcat 就直接可以运行javaEE程序。

总之:人们把 Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建 庞大的spring项目,并且尽可能的减少一切 xml配置,做到开箱即用,迅速上手,让我们 关注与业务而非配置。

6.3、运行SpringBoot项目的方式(必会)

可以打包;

可以使用 Maven插件直接运行;

直接运行 main方法。

6.4、SpringBoot的启动器starter(必会)
(1)什么是starter?
starter启动器,可以通过启动器集成其他的技术,比如说: web, mybatis, redis等等。可以提供对应技术的开发和运行环境,比如: pom中引入spring-boot-starter-web, 就可以进行web开发。

**
**

(2)starter执行原理?
SpringBoot在启动时候会去扫描 jar 包中的一个名为spring.factories;
根据文件中的配置,去加载自动配置类. 配置文件格式是 key=value, value 中配置了很 多需要Spring加载的类;
Spring 会去加载这些自动配置类, Spring 读取后,就会创建这些类的对象,放到 Spring 容器中.后期就会从 Spring容器中获取这些类对象。

**
**

(3)SpringBoot 中常用的启动器
spring-boot-starter-web, 提供 web技术支持
spring-boot-starter-test
spring-boot-starter-jdbc
spring-boot-starter-jpa
spring-boot-starter-redis…等等

6.5、SpringBoot运行原理剖析(必会)
(一) SpringApplication类作用及run()方法作用
SpringApplication 这个类整合了其他框架的启动类, 只要运行这一个类,所有的整合 就都完成了;
调用run函数,将当前启动类的字节码传入(主要目的是传入@SpringBootApplication这个注解), 以及 main 函数的args参数;
通过获取当前启动类的核心信息, 创建 IOC容器。
(二) 当前启动类@SpringBootApplication详细剖析
run 函数传入的当前启动类字节码, 最重要的是传入了@SpringBootApplication, 点开该注解源码, 会发现有多个注解组成,接下来会详细解释每个注解的含义. 点开这个注解源码, 发现有4类注解。

图片

(1) 第一类: JDK原生注解4个

@Target(ElementType.TYPE) //当前注解的使用范围 @Retention(RetentionPolicy.RUNTIME) //生命周期

@Documented //声明在生成 doc文档时是否带着注解

@Inherited //声明是否子类会显示父类的注解

(2)第二类: @SpringBootConfiguration

点开该注解源码, 会发现本质是@Configuration,定义该类是个配置类功能等同于xml 配置文件。

图片

提到@Configuration就要提到他的搭档@Bean, 使用这两个注解就可以创建一个简 单的 Spring配置类, 可以用来替代相应的xml配置文件.可以理解为创建了 IOC容器了。

(3)第三类: @ComponentScan, 包扫描功能

这个注解对应 Spring 的XML 配置中的@ComponentScan,其实就是自动扫描并加 载符合条件的组件(比如@Component 和@Repository 等)或者 bean 定义, 最终将这些 bean 定义加载到IoC容器中。

也可以通过basePackages 等属性来细粒度的定制@ComponentScan 自动扫描的范围, 如果不指定, 则默认扫描@ComponentScan所在类的 package 及子包进行扫描。

**注:所以SpringBoot的启动类最好是放在root package 下,因为默认不指定 basePackages, 这样能扫描root package 及子包下的所有类。

**

(4)第四类: @EnableAutoConfiguration

点开源码会发现,本质是@import, 自动导入功能

图片

  1. @EnableAutoConfiguration 也是借助@Import 的帮助,将所有符合自动配置条件的 bean定义加载到 IoC容器. @EnableAutoConfiguration会根据类路径中的 jar 依赖为项目进行自动配置, 如:添加了 spring-boot-starter-web 依赖, 会自动添加 Tomcat 和 SpringMVC 的依赖, SpringBoot 会对 Tomcat和 SpringMVC进行自动配置;

  2. 那么SpringBoot是如何完成自动配置的呢?

A. SpringBoot 自动配置的注解是 @EnableAutoConfiguration;

B. 我们用的时候是在启动类上加@SpringBootApplication,这个注解是复合注解,内部包含 @EnableAutoConfiguration;

C. @EnableAutoConfiguration内部有一个@Import, 这个注解才是完成自动配置的关键;

D. @Import 导入一个类(AutoConfigurationImportSelector),这个类内部提供了一个方法(selectImports). 这个方法会扫描导入的所有 jar 包下的spring.factories文件. 解析文件中自动配置类key=value, 将列表中的类创建,并放到Spring容器中。

6.5.1、总结
总之一个@SpringBootApplication注解就搞定了所有事, 它封装了核心的 @SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan 这三 个类,大大节省了程序员配置时间,这就是SpringBoot 的核心设计思想。

6.6、SpringBoot热部署(了解)
导入 spring-boot-devtools这个 jar 包: 就可以完成热部署了。

6.7、SpringBoot中的配置文件(必会)
(1)有哪些配置文件?
application.yml 或application.properties
bootstrap.yml 或bootstrap.properties

(2)上面两种配置文件有什么区别?
\1. bootstrap 由父 ApplicationContext 加载, 比 application 配置文件优先被加
载;
\2. bootstarp里的属性不能被覆盖;
\3. application: springboot 项目中的自动化配置;
\4. bootstrap:

使用 spring cloud config 配置中心时, 需要加载连接配置中心的配置属性的, 就 可以使用 bootstrap来完成;

加载不能被覆盖的属性;

加载一些加密/解密的数据。

(3)读取配置文件的方式?
读取默认配置文件
需要注入 Environment 类, 使用 environment.getProperty(peorperties 中的 key), 这 样就能获得key 对应的value值;
@value(${key.value}) 直接读取。
读取自定义配置文件
自定义配置文件后缀必须是.propeties;
编写和自定义配置文件对应的java类, 类上放 3个注解:
@ConfigurationProperties(“前缀”)
@PropertySource(“指定配置文件”)
@Component 包扫描
读取的时候就跟读取默认配置文件一样。

6.8、SpringBoot支持哪些日志框架(了解)
Java Utils logging
Log4j2
Lockback
如果你使用了启动器,那么 springboo 默认将 Lockback作为日志框架。

6.9、SpringBoot常用注解(必会)
\1. @SpringBootApplication: 它 封 装 了 核 心 的 @SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan 这三个类,大大节省了程序员配置时间,这就是 SpringBoot的核心设计思想;

2.@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IOC容器;

\3. @MapperScan:spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis 接口的扫描;

4.@RestController 是@Controller 和 @ResponseBody 的 结 合 , 一 个 类 被 加 上 @RestController注解,数据接口中就不再需要添加@ResponseBody,更加简洁;

5.@RequestMapping,我们都需要明确请求的路径;

6.@GetMappping,@PostMapping, @PutMapping, @DeleteMapping 结合 @RequestMapping使用, 是 Rest 风格的, 指定更明确的子路径;

7.@PathVariable:路径变量注解,用{}来定义url 部分的变量名;

8.@Service 这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring 容器。事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,当我们不能确定具体作用的时候 可以用泛指组件的注解托付给 spring容器;

9.@Component 和spring的注解功能一样, 注入到IOC 容器中;

10.@ControllerAdvice 和 @ExceptionHandler 配合完成统一异常拦截处理. 备注: 面试的时候记住6.7 个即可~

7、SpringCloud
7.1、SOA和微服务的区别?(必会)**
谈到 SOA和微服务的区别, 那咱们先谈谈架构的演变。

1. 集中式架构

项目功能简单, 一个项目只需一个应用, 将所有功能部署在一起, 这样的架构好处是减 少了部署节点和成本。

图片

缺点: 代码耦合,开发维护困难, 无法水平扩展, 单点容错率低,并发能力差。

2. 垂直拆分架构

当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:

图片

优点:系统拆分实现了流量分担,解决了并发问题;可以针对不同模块进行优化, 方便水平扩展,负载均衡,容错率提高。

缺点:系统间相互独立,会有很多重复开发工作,影响开发效率。

3. 分布式服务

当垂直应用越来越多, 随着项目业务功能越来越复杂, 并非垂直应用这一条线进行数据调用, 应用和应用之间也会互相调用, 也就是完成某一个功能,需要多个应用互相调用, 这就 是将功能拆完来完成的分布式架构。

图片

优点: 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率。

缺点: 系统间耦合度变高,调用关系错综复杂,难以维护。

4. 服务治理架构SOA

SOA :面向服务的架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键, 而最初的服务治理基石是 Dubbo 服务治理。

图片

以前分布式服务的问题?
服务越来越多,需要管理每个服务的地址, 调用关系错综复杂,难以理清依赖关系;

服务过多,服务状态难以管理,无法根据服务情况动态管理。

SOA服务治理架构的优点:
服务注册中心,实现服务自动注册和发现,无需人为记录服务地址;
服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系;
动态监控服务状态监控报告,人为控制服务状态。

SOA服务治理架构的缺点:
服务间依然会有依赖关系,一旦某个环节出错会影响较大(容错机制);

服务关系复杂,运维、测试部署困难,不符合开发-运维一站式维护的思想。

5. 微服务

前面说的 SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别:
微服务的特点:
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责;

微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。

面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口 API。并不关心 服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供 Rest的接口即可;

自治:自治是说服务间互相独立,互不干扰。

团队独立:每个服务都是一个独立的开发团队,人数不能过多。

技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉。

前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、 移动段开发不同接口。

数据库分离:每个服务都使用自己的数据源。

部署独立:服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持 续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合, 易维护. 基于 docker容器是开发。

图片

目前微服务微服务架构主流的是 SpringBoot+Dubbo 和 SpringBoot+SpringCloud的架构模式。
综上, 无论是 SOA 还是微服务, 都需要进行服务调度, 目前主流的服务调度室 RPC 和 HTTP两种协议, 而 Dubbo 基于 RPC 的远程调度机构, SpringCloud 是基于 Rest 风格(基于http协议实现的)的 Spring 全家桶微服务服务治理框架。说到这里也可以继续说下 Dubbo和SpringCloud的区别。

7.2、SpringCloud是什么?(了解)
SpringCloud是一系列框架的集合,集成SpringBoot,提供很多优秀服务:服务发现 和注册,统一配置中心, 负载均衡,网关, 熔断器等的一个微服务治理框架。

7.3、SpringCloud的优势?(了解)
因为 SpringCloud源于 Spring,所以它的质量,稳定性,持续性都是可以保证的;
SpringCloud天热支持 SpringBoot 框架,就可以提高开发效率,能够实现需求;
SpringCloud更新很快,后期支持很给力;
SpringCloud可以用来开发微服务。

7.4、SpringCloud有哪些核心组件?(必会)
1.Eureka: 注册中心, 服务注册和发现;
2.Ribbon: 负载均衡, 实现服务调用的负载均衡;
3.Hystrix: 熔断器;
4.Feign: 远程调用;
5.Gateway: 网关;
6.Spring Cloud Config: 配置中心;

(1)Eureka

提供服务注册和发现, 是注册中心。有两个组件: Eureka 服务端和 Eureka 客户端。
7.Eureka 服务端: 作为服务的注册中心, 用来提供服务注册, 支持集群部署;
8.Eureka 客户端: 是一个 java 客户端, 将服务注册到服务端, 同事将服务端的信息缓存 到本地, 客户端和服务端定时交互。

\1. 原理

图片

Eureka-Server:就是服务注册中心(可以是一个集群),对外暴露自己的地址;

提供者:启动后向 Eureka 注册自己信息(地址,服务名称等),并且定期进行服务续约;

消费者:服务调用方,会定期去Eureka 拉取服务列表,然后使用负载均衡算法选出一 个服务进行调用;

心跳(续约):提供者定期通过 http方式向Eureka 刷新自己的状态。

2. 服务下线、失效剔除和自我保护

服务的注册和发现都是可控制的,可以关闭也可以开启。默认都是开启;

注册后需要心跳,心跳周期默认 30秒一次,超过 90秒没发心跳认为宕机;

服务拉取默认 30秒拉取一次;

Eureka 每个60 秒会剔除标记为宕机的服务;

Eureka 会有自我保护,当心跳失败比例超过阈值(默认 85%),那么开启自我保护,不再剔除服务;

Eureka 高可用就是多台Eureka互相注册在对方上。

(2)Ribbon
Ribbon 是 Netflix 发布的云中服务开源项目. 给客户端提供负载均衡, 也就是说 Ribbon是作用在消费者方的;
简单来说, 它是一个客户端负载均衡器, 它会自动通过某种算法去分配你要连接的机器;
SpringCloud认为 Ribbon这种功能很好, 就对它进行了封装, 从而完成负载均衡;
Ribbon默认负责均衡策略是轮询策略。

(3)Hystrix熔断器
有时候可能是网络问题, 一些其它问题, 导致代码无法正常运行, 这是服务就挂了, 崩溃了。熔断器就是为了解决无法正常访问服务的时, 提供的一种解决方案;
解决因为一个服务崩溃而引起的一系列问题, 使问题只局限于这个服务中,不会影响其他服务;
Hystrix 提供了两种功能, 一种是服务降级, 一种是服务熔断。

1. 服务降级原理
Hystrix 为每个服务分配了小的线程池, 当用户发请求过来, 会通过线程池创建线 程来执行任务, 当创建的线程池已满或者请求超时(这里和多线程线程池不一样,不 存在任务队列), 则启动服务降级功能;
降级指的请求故障时, 不会阻塞, 会返回一个友好提示(可以自定义, 例如网站维 护中请稍后重试), 也就是说不会影响其他服务的运行。

2. 服务熔断原理

图片

状态机有 3个状态:
Closed:关闭状态(断路器关闭),所有请求都正常访问;

Open:打开状态(断路器打开),所有请求都会被降级。Hystix 会对请求情况计数, 当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于 20次;

HalfOpen:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放 1次请求通过,若这个请求是健康的, 则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。

(4)Feign: 远程调用组件
后台系统中, 微服务和微服务之间的调用可以通过 Feign组件来完成;

Feign组件集成了Ribbon 负载均衡策略(默认开启的, 使用轮询机制), Hystrix 熔断器 (默认关闭的, 需要通过配置文件进行设置开启);

被调用的微服务需要提供一个接口, 加上@@FeignClient(“url”)注解;

调用方需要在启动类上加上@EnableFeignClients, 开启 Feign组件功能。

(5)Gateway: 路由/网关
1.对于项目后台的微服务系统, 每一个微服务都不会直接暴露给用户来调用的, 但是如果用户知道了某一个服务的ip:端口号:url:访问参数, 就能直接访问你. 如果再是恶意访问, 恶意攻击, 就会击垮后台微服务系统.因此, 需要一个看大门的大boss, 来保护我们的后台系统;
2.Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。
Gateway 提供两种过滤器方式:“pre”和“post”
pre 过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、 协议转换等。post 过滤器,在后端微服务响应之后并且给前端响应之前执行,可以做响应内容、 响应头的修改,日志的输出,流量监控等。
3.Gateway 还提供了两种类型过滤器

(一) GatewayFilter:局部过滤器,针对单个路由

\1. GatewayFilter 局部过滤器,是针对单个路由的过滤器。

\2. 在Spring Cloud Gateway 组件中提供了大量内置的局部过滤器,对请求和响应 做过滤操作。

\3. 遵循约定大于配置的思想,只需要在配置文件配置局部过滤器名称,并为其指定对 应的值,就可以让其生效。

(二) GlobalFilter :全局过滤器,针对所有路由

\1. GlobalFilter 全局过滤器,不需要在配置文件中配置,系统初始化时加载,并作用 在每个路由上。
\2. Spring Cloud Gateway 核心的功能也是通过内置的全局过滤器来完成。

\3. 自定义全局过滤器步骤:
1 定义类实现 GlobalFilter 和 Ordered接口;
2 复写方法 3 完成逻辑处理。

(6)Spring Cloud Config
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在 Spring Cloud 中,有分布式配置中心组件 spring CloudConfig ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程 Git仓库中.

7.5、SpringBoot和SpringCloud的关系(必会)
1.SpringBoot是为了解决 Spring配置文件冗余问题, 简化开发的框架;

2.SpringCloud是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡远程调度任务框架;

3.SpringCloud需要依赖 SpringBoot 搭建微服务, SpringBoot使用了默认大于配 置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud 很大的一部分是基于SpringBoot 来实现;

4.SpringBoot不需要依赖 SpringCloud就可以独立开发. SpringBoot 也可以集成 Dubbo 进行开发。

7.6、SpringCloud和Dubbo的区别(高薪常问)
1.SpringCloud和Dubbo 都是主流的微服务架构。
SpringCloud是Apache下的 Spring体系下的微服务解决方案;
Dubbo 是阿里系统中分布式微服务治理框架。
2.技术方面对比
SpringCloud 功能远远超过 Dubbo, Dubbo 只实现了服务治理(注册和发现). 但是SpringCloud提供了很多功能, 有21个子项目;
Dubbo 可以使用 Zookeeper 作为注册中心, 实现服务的注册和发现, SpringCloud 不仅可以使用 Eureka 作为注册中心, 也可以使用 Zookeeper 作为 注册中心;
Dubbo 没有实现网关功能, 只能通过第三方技术去整合. 但是 SpringCloud 有 zuul路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和git 能完美集成。
3.性能方面对比
由于Dubbo 底层是使用Netty 这样的 NIO 框架,是基于 TCP协议传输的,配合以Hession序列化完成 RPC;
而 SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的,相对来说,Http 请求会有更大的报文,占的带宽也会更多;
使用 Dubbo 时, 需要给每个实体类实现序列化接口, 将实体类转化为二进制进行 RPC 通信调用.而使用SpringCloud时, 实体类就不需要进行序列化。

刚才有提到注册中心不一样,那么Eureka和Zookeeper有什么区别? 我们继续往下说~**
**

**
**

7.7 Eureka和Zookeeper的区别(高薪常问)
在谈这个问题前我们先看下 CAP 原则: C(Consistency)-数据一致性; A(Availability)服务可用性; P(Partition tolerance)-服务对网络分区故障的容错性, 这三个特性在任何分 布式系统中不能同时满足, 最多同时满足两个, 而且 P(分区容错性)是一定要满足的。
1.Eureka 满足AP(服务可用性和容错性), Zookeeper 满足 CP(数据一致性和容错性);
2.Zookeeper 满足 CP , 数据一致性, 服务的容错性. 数据在各个服务间同步完成后才返回用户结果, 而且如果服务出现网络波动导致监听不到服务心跳, 会立即从服务列表中 剔除, 服务不可用;
3.Eureka 满足 AP , 可用性, 容错性. 当因网络故障时, Eureka 的自我保护机制不会立即剔除服务, 虽然用户获取到的服务不一定是可用的, 但至少能够获取到服务列表. 用户 访问服务列表时还可以利用重试机制, 找到正确的服务. 更服务分布式服务的高可用需求;
4.Eureka 集群各节点平等, Zookeeper 集群有主从之分。

4.1. 如果 Zk 集群中有服务宕机,会重新进行选举机制,选择出主节点, 因此可能会导致整个集群因为选主而阻塞, 服务不可用;

4.2. Eureka 集群中有服务宕机,因为是平等的各个服务器,所以其他服务器不受影响.
5.Eureka 的服务发现者会主动拉取服务, ZK服务发现者是监听机制。

5.1. Eureka 中获取服务列表后会缓存起来, 每隔 30秒重新拉取服务列表;

5.2. Zk 则是监听节点信息变化, 当服务节点信息变化时, 客户端立即就得到通知。