知识点总结

176.20.100.49
配置环境变量
新建:JAVA-HOME
新建:CLASS-PATH
Path:%JAVA-HOME%/bin   D:\project4\mariadb-10.6\bin
属性——高级系统设置——环境变量||系统变量
变量名 变量值(变量的存储路径)
数组的工具类
Arrays.toString()
Arrays.sort()
Arrays.binarySearch()
Arrays.copyOf()

JVM的体系结构是怎样的?
主要分为三大部分:
1.类加载系统 (ClassLoader System) :负责加载类到内存。
2.运行时数据区 (Runtime Data Area):负责存储数据信息。
3.执行引擎 (Execution Engine):负责调用对象执行业务。
JVM有两种运行模式Server与Client/brows。
两种模式的区别在于,Client模式启动速度较快(轻量级),Server模式(重量级)启动较慢,但稳定后速度比Client远远要快。

十进制转二进制
Integer.toBinaryString(int i)
二进制转十进制
Integer.parseInt(String s, int radix)

递归
递归是指在方法中调用自身
void f(){   f()  }
比如递归求阶乘:
package demo1;
import java.math.BigInteger;
import java.util.Scanner;
/*
递归求阶乘
 */
public class Test1 {
    public static void main(String[] args) {
        while (true) {
            System.out.print("求几的阶乘:");
            int n = new Scanner(System.in).nextInt();
            BigInteger r = f(BigInteger.valueOf(n));
            System.out.println("结果:\n"+r.toString());
        }
    }

    private static BigInteger f(BigInteger n) {
        if (n.equals(BigInteger.ZERO)) {
            return BigInteger.ONE;
        }

        return n.multiply(f(n.subtract(BigInteger.ONE)));
    }
}
@Override
重载和重写的区别:
1.重载;在一个类中的现象,同一个类中,存在方法名相同 参数列表不同的方法
2.重写:建立了继承关系之后,子类对父类方法的改写;子类重新定义所继承是方法
    在不修改源码的情况下,进行功能的修改与拓展
类不是真实存在的,是一种对象的数据类型,对象是类创建出的独立且具体的案例,
对象之间互不影响
类是对象的抽象。对象是类的实体
对象创建的过程:
1.在栈内存中开辟一块空间,存放引用类型变量P,并将其压入栈底
2.在堆内存开辟一块空间,存放对象; 
3.完成对象的初始化,赋予默认值,成员变量的初始值是null;
4.给初始化后的对象赋予唯一的地址值;
5.把地址值交给引用类型变量p来保存。
this:本类的引用,本类的成员变量与局部变量同名时;
super:父类的引用,子类的成员变量与父类的成员变量同名时
在子类的构造方法中可以通过super()访问父类的构造方法,super.xxx访问父类的属性
多态的特征:
1.多态的前提是继承;
2.子类中要有方法的重写
3.父类引用子类对象;
4.编译看左边,运行看右边
变量:
成员变量有自己的默认值null,可以不用手动赋值;
局部变量使用的时候必须赋值;
基本类型保存的是基本值,引用类型保存的是地址值
就近原则:
八大基本类型&其他引用类型
字面值规则:
System.out.println(0b100);//4  二进制2^2-2^1-2^0 从右到左
System.out.println(0100);//64  八进制8^2-8^1-8^0
System.out.println(0x100);//256 十六进制16^2-16^1-16^0
运算规则:
运算结果的数据类型与参与运算的最大类型保持一致
整数运算会出现溢出现象
引用类型之间的转换取决于是否有继承关系
数组元素值即使相同,但是是引用类型,比较的是地址值,所以是不相同的
三目运算符:1?2:3;
复合赋值运算符:运算时会自动进行类型转换
数组所有元素之和,数组元素的最大值,逆向输出
 判断语句
/**单分支结构if(){}
         * 多分支结构if(){}else{}
         * 嵌套分支:if(){}else if(){}else if{}
         * return关键字除了可以返回方法的返回值以外,
         * 还可以直接结束当前的方法,如果遇到return,本方法会直接结束*/
        System.out.println("请您输入需要查看的月份:");
        int month = new Scanner(System.in).nextInt();
        if (month<0||month>12) return;//可以不用大括号,表示直接结束当前判断
        if (month>=3&&month<=5){
            System.out.println("此月是春天");
        }else if (month>=6&&month<=8){
            System.out.println("此月是夏天");
        }else if (month>=9&&month<=11){
            System.out.println("此月是秋天");
        }else {
            System.out.println("此月是冬天");
        }
循环结构:打印奇数
int num = 1;//初始值
        while (num < 100){//boolean表达式
            if (num%2!=0) {
                System.out.println(num);
            }
            num++;
        }

小括号中变量支持的类型:byte short char int String
注意: 如果配置了default默认选项,而且没有任何的case被匹配到,就会执行default后的操作
case的个数 是否加break 是否加default全部都是可选的,根据自己的具体业务做决定
小括号中变量的类型必须与case后value的类型一致
执行顺序:先拿着变量的值,依次与每个case后的值做比较,如果相等,就执行case后的语句 
若这个case后没有break,就会继续向下执行下一个case,如果一直没有遇到break,就会发生穿透现象,包括default
switch (变量名){
            case 值1:操作1;break;
            case 值2:操作2;break;
            case 值3:操作3;break;
            case 值4:操作4;break;
            case 值5:操作5;break;
            default:保底选项;
        }

形参:方法参数列表的参数名,无法确定这个变量的值是多少
每个方法上都有一个异常的抛出管道runtime
方法直接抛出异常时,异常类型<=runtimeexception
子类方法抛出的异常类型<=父类方法抛出的异常类型
toString(数组名):查看具体元素时
一个类可以创建出多个对象,对象是根据类的设计创建出来的
对象之间是相互独立的,互不影响
面向对象的三大特征:封装、继承、多态
封装方法:被private修饰的方法只能在本类中使用,外界调用需要调用公共方法,私有资源只能在本类中使用
多态:在程序中我们通常将多态特性应用在方法的返回值和参数类型上,方法的返回值和参数类型能用抽象则用抽象,便于之后进行多态拓展。
进程:独立性、动态的、并发性
线程:进程运行调度的最小单位,可以只有一个即为单线程,其执行具有随机性,因此会产生安全问题
创建、就绪、执行、阻塞、结束
线程安全问题会发生的三大条件:多线程程序+有共享数据+多条语句操作共享数据
一:继承Thread类
class MyThread extends Thread( ){
         调用父类Thread的run()方法但需要重写
         public void run(){
               for(int i =0;i<10;i++){
                    System.out.println(i+"="+getName());
               }
        }
}
在main()方法中创建子类自己的对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
start()表示的就是就绪状态,将线程加入就绪队列
二:实现Runnable接口
class MyRunnable implements Runnable(){
实现类需要重写接口中的有且只有一个的run()方法
        public void run(){
              for(int i = 0;i <= 9;i++){
                   System.out.println(i+"="+Thread.currentThread.getName());
              }
        }
}
        MyRunnable target = new MyRunnable();
        //由于MyRunnable无法调用start()方法,需要Thread来转
        //Thread(Runnable target) :分配新的 Thread 对象。
        //Thread(Runnable target, String name): 分配新的 Thread 对象并且带有一个线程名字。
        //将一个target业务对象交给多个线程对象来处理,
        // 因此票数被所有线程对象Thread类的对象共享
        Thread r1 = new Thread(target);
        Thread r2 = new Thread(target);
        r1.start();
        r2.start();
三:
class TicketThread extends Thread{
        static int tickets = 100;
        public void run(){
              while(true){
                     synchronized(TicketThread.class){
                            if(tickets > 0){
                                try {
                                     Thread.sleep(10);
                                 } catch (Exception e){ 
                                      e.printStackTrace();
                                 }
                               System.out.println(getName()+"="+tickets--) 
                            }
                            if (tickets <= 0) break;
                      }      
              }
       }
}
       TicketThread t1 = new TicketThread();
       TicketThread t2 = new TicketThread();
       TicketThread t3 = new TicketThread();
       t1.start();
       t2.start();
       t3.start();
线程常用方法
start()
join()
interrupt()
sleep()
yield()//让步,让其他线程获取执行权
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。
这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。

synchronized同步关键字
synchronized (锁对象){
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}
使用的锁对象类型任意,但必须唯一
同步代码块可以保证同一个时刻只有一个线程进入

IO:
FileInputStream / OuputStream 字节流父类
FileInputStream / FileOutputStream 读写文件
BufferedInputStream / BufferedOutputStream 缓冲流
ObjectInputStream / ObjectOutputStream 序列化流
Reader / Writer 字符流父类
InputStreamReader / OutputStreamWriter 字符编码转换流
BufferedReader readLine()方法可以一次读取一行字符
PrintWriter / PrintStream print()、println() 

Serializable 接口 只有实现这个接口的对象才允许被序列化

一、通过单元测试方法获取字节码对象
@Test
public void getClazz() throws ClassNotFoundException{
    Class<?> clazz1 = Class.forName("cn.tedu.reflection.Student");
    Class<?> clazz2 = Student.class;
    Class<?> clazz3 = new Student().getClass();
    System.out.println(clazz1);
    System.out.println(clazz2.getName());
    System.out.println(clazz3.getSimpleName());
    System.out.println(clazz1.getPackage());
    System.out.println(clazz1.getPackage().getName());
}
二、通过单元测试方法获取Student类中的方法
@Test
 public void getFunction() throws ClassNotFoundException {
    Class<?> clazz = Class.forName("cn.tedu.reflection.Student");
    Method[] ms = clazz.getMethod();
    System.out.println(Arrays.toString(ms));
    for(Method m : ms){
        System.out.println(m.getName());
        Class<?>[] types = m.getParameterTypes();
        System.out.println(Arrays.toString(types));
    }
}
集合类与数组的不同之处:数组的长度是固定的,集合的长度是可变的,
                                     且集合的访问方式比较灵活

集合接口:collection
list接口:数据有下标,有序,可重复
set接口:无下标,无序,不可重复
map接口:键值对的方式
Collection的常用方法:添加对象:add();移除对象:remove();
isEmpty(); size()返回集合中的元素数;contains()是否包含指定元素;
toArray()将集合转为数组[];c.interator();获取集合的迭代器
测试:Collection<Integer> c = new ArrayList();
c.add(100);System.out.println(c);
c.clear();清空集合中的所有元素
Iterator<Integer> it = c.interator();可自动补全等号左边
while(it.hasNext()){
        Integer num = it.next();获取当前迭代到的元素
        System.out.println(num);
}
泛型可以在接口、类、方法上使用
泛型也是是编译时的一种类型,此类型仅仅在编译阶段有效,运行时无效,运行时候都会变成Object类型
public interface Collection<E>{}
public class Eat<Person>{}
public <E> void print(E,e){}

List<String> list1 = new ArrayList<String>();
总结:
泛型类和泛型接口用于约束类或接口中实例方法参数类型,返回值类型。
泛型类或泛型接口中实际泛型类型可以在定义子类或构建其对象时传入。
泛型方法用于约束本方法(实例方法或静态方法)的参数类型或返回值类型。
泛型类上的泛型不能约束类中静态方法的泛型类型。

高效for循环:foreach
语法:for(1 2 : 3){代码块} 
3表示要遍历的数据,1是遍历后得到的数据类型,2是遍历到的元素名

Map接口:Map<Key,Value>,Key不可以重复
Map<Integer,String> map = new HashMap<>();
map.put(int,"");
map.containskey();  map.containsvalue();
map.get(int);根据键获取对应的值
map.values();System.out.println();将值以数组的形式打印出来
map本身没有迭代器,先转换成set集合
Set<Integer> keyset = map.keyset();把map中的所有key值存入到set集合当中
Iterator<Integer> it = keyset.interator();由keyset来调用迭代器
String value = map.get(key);
Set<Student> set = new HashSet<>();
Student s1 = new Student("",1);
Student s2 = new Student("",2);
Student s3 = new Student("",3);
set.add(s1);将自定义类的对象存入set集合中
set.add(s2);
set.add(s3);
System.out.println(set);

单例模式:就是一个类中就创建一个对象
1.对本类的构造方法私有化,不让外界调用去创建对象
2.创建全局唯一的对象且做私有化处理;
3.通过自定义的公共方法将创建好的对象返回getxxx()
public class Runtime{
          private static Runtime currentRuntime = new Runtime();
          private Runtime(){}
          public static Runtime getRuntime(){
                  return currentRuntime;
}
}
synchronized public static Runtime getRuntime(){
         synchronized(o){
                 if(runtime== null){
                          runtime = new Runtime();
                  }
                 return runtime;
          }
}
元注解:@Target表示注解用在哪里:(
ElementType.TYPE\METHOD\FIELD\CONSTRUCTOR\LOCAL_VARIABLE\PARAMETER)
@Retention(RetentionPolicy.RUNTIME)到运行时有效
String value() default "xxx";定义特殊属性并给其赋予默认值
网络编程:
客户端的Socket类:
    方法有getRemoteSocketAddress()、getOutputStream()、getPort()
    getInputStream()、
服务端的serverSocket类:
    accept()返回值是Socket对象
反射需要用到的API
1.首先:必须先获取字节码类对象clazz,有3种方法
每个类在加载(将类读到内存)时都会创建一个字节码对象,其类型为Class类型,且这个对象在一个JVM内存中是唯一的,
此对象中存储的是类的结构信息(元数据信息)
Class.forName("类的全路径")
类名.getClass
对象.getClass()
2.常用的方法
1)获取包名、类名
clazz.getPackage().getName();
clazz.getSimple.getName();
clazz.getName();
2)获取成员变量定义信息
Field[] fs=getFields()获取所有公开的成员变量,包括继承变量
getDeclaredFields()获取本类定义的成员变量,包括私有
getField(变量名)
getDeclaredFields(变量名)
3)获取构造方法定义信息
getConstructor(参数类型列表)公开的构造方法
getConstructors();获取所有公开的构造方法
getDeclaredConstructors()获取所有的构造方法,包括私有
Constructor<?> c =getDeclaredConstructor(int.class,String.class)
4)获取普通方法定义信息
Method[] ms =getMethods()获取所有可见的方法,包括继承的方法m.getParameterTypes()
getMethod(方法名,参数类型列表)
getDeclaredMethods()包括私有
getDeclaredMethod(方法名,int.class,String.class)
5)反射新建实例:创建实例,并执行无参构造
Object o =clazz.newInstance();触发无参构造
clazz.newInstance(int,"")触发含参构造
clazz.getConstructor(int.class,String.class)
6)反射调用成员变量
Field f = clazz.getDeclaredField(变量名)
clazz.setAccessible(true)
f.set(实例,值)
f.get(实例)
7)反射调用成员方法
Method m = clazz.getDeclaredMethod(方法名,参数类型列表)
m.setAccessible(true)
m.invoke(实例,参数数据)
eg:
Class<?> clazz = Person.Class;
Method m =clazz.getDeclaredMethod("方法名",int.class,String.class);
Object obj = clazz.NewInstance();
m.setAccessible(true);
m.invoke(obj,3,"鸭哒");//通过反射技术invoke(),执行目标对象obj的目标方法


第二阶段:
JDBC:
如果动态的拼接字符串时u,数据在中间的位置 "+变量名+"
问题:SQL攻击:特殊符号#等的出现,导致SQL语义发生改变,注释掉了后面的条件
解决方案:替换传输器 Statement这种低级低效不安全的传输器
                使用新的传输器PreparedStatement(sql)高级安全的
SQL骨架:用?代替参数的位置,?叫做占位符
String sql = "select * from tb_user where name=? and password=?";
先执行SQL骨架,再给SQL设置参数
s.setString(1,a);给第一个?设置值a
s.setString(2,b);给第二个?设置值b
executeQuery():用来执行查询SQL语句,且返回结果集ResultSet
executeUpdate():用来执行增删改的SQL语句,且返回影响的行数
.close()用来释放资源

JDBC的六步曲:
1+2:获取字节码对象+通过用户名和密码获取连接器Connection c = JDBCUtils.getConnection();
3.创建SQL语句,交于字符串类型保存sql值 String sql = "SQL语句";
4.获取传输器:PreparedStatement p = c.PreparedStatement(sql)
5.执行SQL语句:1)查询:resultSet r = p.executeQuery(sql)  if(r.next()){ }else{ }
                         2)增删改:int row = p.executeUpdate();
6.释放资源

url="jdbc:mysql://localhost:3306/cgb2109?characterEncoding=utf8"
防止中文乱码
String url ="jdbc:mysql://localhost:3306/mydb?
    characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";

为了资源一定会被释放,把释放资源的代码放入finally里+扩大变量的作用范围
在try里修改变量是默认值null+在finally里每一条资源各自都要进行捕获异常
try { 执行代码 }catch(Exception e){ 异常反馈 }finally{ 释放资源 }

反馈给程序员的异常:e.printStackTrace();跟踪报错信息
空指针异常nullpointerexception
算数异常mathmaticexception

前端代码
HBuilder存放位置:C:/Users/ui-cgb/Documents/HBuilderProjects

循环结构:由于顺序结构的程序语句只能执行一次,因此在同样的操作需要执行多次时
就要使用循环结构
while(布尔表达式){循环内容}
do...while需要即使不满足条件也有至少执行一次
do{ 代码语句}while(布尔表达式)
foreach增强循环,主要用于数组
for(声明语句:表达式){
    循环语句
}
声明语句:声明新的局部变量,且变量类型与数组类型匹配,作用域限定在循环语句中
表达式:要访问的数组名
switch case语句:判断一个变量与一系列值中某个值是否相等,每个值一个分支
case语句值的类型必须与变量的数据类型一样,并且只能是常量或字面常量
default一般作为最后一个分支,在没有值和变量值相等时执行

当需要对字符串进行修改时,需要使用StringBuffer和StringBuilder,不会生成新的对象
sb.append("xxx");拼接
sb.insert(位置,"xxx")在哪个位置插入值
sb.delete(3,7)删除3下标到7下标的值,不包含7

BufferedReader br = new BufferedReader(new InputStreamReader(System.in))
OutputStream f = new FileOutputStream("c:/")

File f = new File("/tmp/user/java/bin")
f.mkdirs();创建目录
String s[] = f.list();用来提取包含的文件和文件夹的列表


经典的业务请求过程:
前端页面发起请求,访问SpringMVC框架的控制层,SpringMVC框架解析请求,找到要调用的Controller,找到其中的方法,同时把请求提交的参数封装到java对象中。之后控制层把请求传递给Spring框架的service业务层,service业务层把请求传递给Mybatis框架的Mapper持久层,Mapper访问MySQL
数据库进行数据库表的查询,将查询结果返回给Mapper层,Mapper-->Service层-->Controller,
控制层把java数据转换成json字符串,返回给ajax调用,ajax进行回调并把json字符串转换为js对象,
再在页面中可以通过js/vue解析js对象,最终把数据展示到html页面中

开发工具:前端--HBuilderX,后端--idea;
项目管理:前端--npm,webpack; 后端--Maven,SpringBoot;
web中间件:前端--NodeJS, 后端--Tomcat


关于三大框架SSM
1.Maven:  发明pom模型(project object model)pom.xml
   四大特征:1)仓库repository---*仓库、镜像仓库、本地仓库    
        2)依赖dependency:
            每个核心jar包形成一个依赖,maven底层进行它相关的jar的自动导入 

<dependency>
    <groupId>mysql</groupId>//分组
    <artifactId>mysql-connector-java</artifactId>//项目or模块
    <version>5.1.32</version>//版本
</dependency>
         3)坐标coorinate  唯一标识
         4)命令mvn cmd
2.SpringBoot 是Maven的延伸
    创建独立的Spring应用程序
    嵌入了Tomcat  自动配置Spring
    相关注解:@SpringBootApplication启动服务器
3.SpringMVC:Spring框架提供了构建Web应用程序的全功能MVC模块(Model  View  Controller)
    数据处理模型(Model),POJO就是Model层
    相关注解:类上--@RestController  类里方法外--@RsquestMapping("")

工作原理具体描述:
1、用户发送请求至前端控制器DispatcherServlet。 

2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。 

3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 

4、DispatcherServlet调用HandlerAdapter处理器适配器。
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。 

6、Controller执行完成返回给ModelAndView。 

7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。 

8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。 

9、ViewReslover解析后返回具体View。 

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。 

11、DispatcherServlet响应用户。

处理请求参数的方式
1.Get方式访问:
类似于此链接:http://localhost:8080/car/obj?id=100&name=BMW&color=red
2.优化GET传参的restful方式:http://localhost:8080/car/insert/1/张三/18
         restful配合@PathVariable(标识接收单个参数)注解一起用,使用{资源名}获取传过来的值
3.处理Post请求的参数:表单提交
案列
1.Springmvc的服务器启动
@SpringBootApplication
public class RunApp{
      public static void main(String[] args){
    SpringApplication.run(RunApp.class,args)
       }
}
2.Student类用来封装数据,记录数据的
public class Student{
    private String user;
    private Integer age;
    private Integer sex;
    private String[] hobby;//此数据是多选框,有多值且是字符串,因此定义为引用类型数组
    private Integer edu;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date intime;
    
    public String setUser(String user){ this.user = user;}
    public String getUser(){ return user;}
    public String setAge(String age){ this.age = age;}
    public String getAge(){ return age;}
    public String setSex(String sex){ this.sex = sex;}
    public String getSex(){ return sex;}
    public String setHobby(String hobby){ this.hobby = hobby;}
    public String getHobby(){ return hobby;}
    public String setEdu(String edu){ this.edu = edu;}
    public String getEdu(){ return edu;}
    public Date getIntime() { return intime;}
        public void setIntime(Date intime) { this.intime = intime;}
    @Override
        public String toString(){
        return "Student{"+"User=' "+user+'\''+",age="+age+",sex="+sex+",
        hobby='+Arrays.tostring(hobby)+",edu="+edu+",intime="+intime+'}';    
    }
}
3.StudentController控制层
@RestController
@RsquestMapping("student")
public class StudentController{
    @RequestMapping("save")
    public String save(Student s) throws Exception{
        Class.forName("com.mysql.jdbc.Driver");
        String url="jdbc:mysql://localhost:3306/cgb2109?characterEncoding=utf8";
        String user="root";
        String pwd="root";
        Connection c = DriverManager.getConnectiion(url,user,pwd);
        String sql="insert into tb_students values(null,?,?,?,?,?,?);
        PreparedStatement ps = c.PrepareStatement(sql);
        ps.setString(1,s.getUser());
        ps.setObject(2,s.getAge());
        ps.setObject(3,s.getSex());
        ps.setObject(4,Arrays.toString(s.getHobby()));
        ps.setObject(5,s.getEdu());
        ps.setObject(6,getIntime());
        
        ps.ExecuteUpdate();
        System.out.println("数据入库成功");
        return "访问成功"+s;
    }
}

1.修改服务器的端口号(默认的是8080)
在resources里面创建application.yml文件(文件名固定)
server:
       port: 6666
2.创建配置文件spring.xml
1)IOC控制反转 Spring框架会帮你创建对象,你来获取对象
有两种创建对象的方式
a: bean标签
     <bean id="hello" class="cn.tedu.spring.Hello"></bean>
其中key是id的值(类名) value是通过反射创建的对象
b: 包扫描机制
只要在扫描范围内的包且添加了spring提供的@Component注解
<context:component-scan base-package="cn.tedu.spring"></context:component-scan>
其中base-package是指定包的路径
测试的步骤一就是读取配置文件
ClassPathXmlAoolicationContext spring = new ClassPathXmlAoolicationContext("spring.xml");
步骤二是获取对象
spring.getBean("类名小写")

3.DI依赖注入
是描述两个类之间的依赖的关系,把对方当做成员变量
    @Autowired
    对方类名 对象名;
可以直接使用依赖的对方来调用其对象的属性和方法

4.AOP面向切面编程
提取共性代码形成切面Aspect类   聚焦了程序员的关注点,只关注业务本身.
使用步骤
1)添加jar包的坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2)创建切面类,在类外需添加2个注解@Component、@Aspect
由通知(就是方法) 和 切点组成
//切点:什么时候要触发通知的功能
                             //方法返回值    包名       类名 方法名(参数列表)
    @Pointcut("execution(* cn.tedu.controller.*.*(..))")
    public void pointcut(){ }
i:  前置通知,在调用目标方法前 触发执行
    @Before("pointcut()")
ii:  这是后置通知,在调用目标方法后 触发执行
    @After("pointcut()")
    public void afterMethod(JoinPoint joinPoint){ }
iii:  @Around("pointcut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint)
                throws Throwable {  }

第三阶段:
SpringMVC常见问题
1.404错误  是请求的url不存在
地址都是小写
2.500错误  请求的url未绑定参数
解决办法:把方法的参数改为引用类型(包含包装类),就算不传参,也没有异常,使用了默认值null
3.400错误 url中的参数类型 和 服务器需要的 参数类型不同

MyBatis相关
  持久层框架,利用ORM思想实现数据库持久化操作;半自动化ORM映射框架
  ORM:以对象的方式操作数据库
    对象映射表
    属性映射字段
  导入jar包
 <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!--jdbc依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
  MyBatis只支持单值传参
    单值:数字、字符串、对象、Map集合
创建类封装数据--->创建接口--->编辑对应的映射文件xml--->编辑核心配置文件--->编辑测试类
1.Car类---pojo包
@data
@Accessor
@Noargsconstructor
@Allargsconstructor
public class Car{
    private Integer carId;
    private String  carName;
    private Double carPrice;
    private String carColor;
}
2.Car接口---mapper包
public interface CarMapper{
    
}
3.CarMapper.xml
<mapper namespace="com.jt.mapper.CarMapper">//对应的接口
              <select id="findAll" resultMap="carRM">
                          select * from demo_car
              </select>
             <resultMap id="carRM  type="com.jt.pojo.Car">
                      <id column="car_id" property="carIdid"/>
                      <result column="car_name" property="carName"/>
                      <result column="car_price" property="carPrice"/>
                      <result column="car_color" property="carColor"/>
             </resultMap>
</mapper>
4.mybatis-config.xml
添加<mapper resource="mybatis/mybatis/mappers/CarMapper.xml"/>
5.测试
private SqlSessionFactor sqlSessionFactory;
@Beforeach
//@Beforeach表示当每次执行@Test注解方法时都会优先执行该方法
public void init() throws IOException{
    //定义源是核心配置文件全路径
    String resource = "mybatis/mybatis-config.xml";
    //根据源读取数据,即输入流
    InputStream inputStream = Resources.getResourcesAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@test
public void testCar(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    CarMapper carMapper = sqlSession.getMapper(CarMapper.class);
    List<Car> carList = carMapper.findAll();
    System.out.println(carList);
    sqlSession.close();
}
6.各sql语句的写法
1) 集合操作
<select id="findIn" resultMap="carRM">
        select * from demo_car where id in
    (<foreach collection="array" separator="," item="id">
        id=#{id} // 取值写法,获取属性值
    </foreach>)
        <resultMap id="carRM type="com.jt.pojo.Car">
                      <id column="car_id" property="carIdid"/>
                      //下列的可以自动映射
          <result column="car_name" property="carName"/>
                      <result column="car_price" property="carPrice"/>
                      <result column="car_color" property="carColor"/>
         </resultMap>
</select>
2)动态sql语句:根据不为null的属性查询
<select id="findSqlWhere" resultMap="carRM">
    select * from demo_car
    <where>//此标签可以去掉多余的and
        <if test="car_id!=null">car_ id=#{car_id}></if>
        <if test="car_name!=null">and car_name=#{car_name}></if>
        <if test="car_price!=null">and car_price=#{car_price}></if>
        <if test="car_color!=null">and car_color=#{car_color}></if>
    </where>
     <resultMap id="carRM type="com.jt.pojo.Car">
                      <id column="car_id" property="carIdid"/>
                      <result column="car_name" property="carName"/>
                      <result column="car_price" property="carPrice"/>
                      <result column="car_color" property="carColor"/>
             </resultMap>
</select>
3) <update id="findSqlSet">
        update demo_user
        <set>//此标签可以去掉多余的逗号
            <if test="car_name != null">car_name =#{car_name},</if>
            <if test="car_price != null">car_price  = #{car_price}, </if>
            <if test="car_color!= null">car_color =#{car_color},  </if>
        </set>
        where id = #{id}
    </update>
4)分支结构 只需要一个条件查询
<select id="findSqlChoose" resultMap="carRM">
    select * from demo_car
    <where>
        <choose>
            <when test="car_name!=null">car_name=#{car_name}</when>
            <when test="car_price!=null">car_price=#{car_price}</when>
            <oterwise>car_color=#{car_color}</oterwise>
        </choose>
    </when>
     <resultMap id="carRM type="com.jt.pojo.Car">
                      <id column="car_id" property="carIdid"/>
                      <result column="car_name" property="carName"/>
                      <result column="car_price" property="carPrice"/>
                      <result column="car_color" property="carColor"/>
             </resultMap>
</select>

@RestController(springmvc)=@Controller(spring)+@ResponseBody(springmvc)
@RequestMapping("接收请求的方法名")
@Component注解
  表示将User对象交给Spring容器管理
@Configuration  写在类上
表示该类是一个配置类,可以在此类中编辑自定义对象
@Bean  写在类里方法外    map<方法名,返回值对象>
   是Spring专门为管理自定义对象的注解   在配置类文件中使用
@SpringBootTest  测试类上
   在该类中执行@Test测试方法,就会启动spring容器
@Test  测试方法外
   测试方法不能有返回值,不能有参数
@Param(key1) 值类型 value1,@@Param(key2) 值类型 value2

sql语句:
  新增操作:
   自动返回影响的行数
   <insert id="saveUser">
    insert into demo_user(id,name,age,sex) values(null,#{name},#{age},#{sex})
   </insert>
  修改操作:
   <update id="updateUser">
    update demo_user set name=#{name},#{age} ,#{sex} where id=#{id}
   </update>


#参数列表
方法的参数列表指定要传什么样的信息给方法,在参数列表中必须指定每个所传递对象的类型及名称。
象java中任何传递对象的场合一样,传递的实际上也是引用,并且引用的参数类型必须正确。

一对一<association javatype>
一对多<conllection oftype>

主启动类上添加@MapperScan

@Resetcontroller
public class TestController{
    @Autowired
    private UserService userService;

    @RequestMapping("findAll")
    public  List<User> findAll(User user){
        return userService.findAll(user);
    }
}
//@Service注解是告诉Spring,当Spring要创建UserServiceImpl的的实例时,bean的名字必须叫做"userService",
//这样当Action需要使用UserServiceImpl的的实例时,就可以由Spring创建好的"userService",然后注入给Action。
 
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;
    
    @Override//实现接口的方法
    public List<User> findAll(User user)){
        return userMapper.findAll(user);
    }
}
public interface UserService{
    List<User> findAll(User user));
}

关于跨域:
     三要素:协议、域名、端口号  三者一致时即是同域
     若有一个不一致则是跨域   需要在控制层的controller类上添加注解@@CrossOrigin
ajax请求地址:"http://localhost:8080/findAll"
浏览器地址:http://127.0.0.1:8848/vuejs/1.入门案例/3-Axios入门案例.html


双向数据绑定
规则: 看到input文本输入框,首先想到使用双向数据绑定.
用户输入框 双向数据绑定: v-model=""

指定公共前缀:
axios.defaults.baseURL = "http://localhost:8080"
async addUserBtn(){
    let {data: result,status: status} = await axios.get("/axios/findAll")
    this.userList = result
}

要求用户访问页面的时候, 初始化的时候 就要求访问后台服务器.,发起Ajax请求. 实现用户列表页面的渲染.
解决方案: 使用mounted 函数

4.5.5 关于秘钥说明 

说明: 当用户登录之后,可以跳转到系统的首页. 到了系统首页之后,用户可以进行其它的业务操作. 系统如何判断正在操作业务的用户 已经登录? 

业务说明: 一般在登录认证系统中,都会返回秘钥信息.来作为用户登录的凭证. 
秘钥特点: 最好独一无二. 
动态生成秘钥: UUID

//用户登录操作
public class UserController{
    @Autowired
    private UserService userService;

    @postMapping("login")
    public SysResult login(@RequestBody User user){
        String token = userService.login(user);
        if(token == null){
            return SysResult.fail();
        }
        return SysResult.success(token);
    }
}

@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper usermapper;

@Override
public String login(User user){
    //1.先将密码进行加密处理
    //获取密码值
    String password = user.getPassword();
    //利用nd5加密算法进行密码加密
    String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
    user.setPassword(md5Pass);
    //2.查询数据
    User userDB = userMapper.findAllByUp(user);
    if ( userDB == null){
        return null;
    }
    String uuid = UUID.randomUUID().toString().replace("-","");
    return uuid;
    }
}


4.6 关于Session和Cookie说明
 
4.6.1 业务需求说明 
用户的请求是一次请求,一次响应. 当响应结束时,服务器返回的数据 也会销毁. 
问题: 如果销毁了token 则认为用户没有登录.需要重复登录. 如何解决该问题: 应该持久化token信息. 

4.6.2 Session 
Session:在计算机中,尤其是在网络应用中,称为“会话控制”。
Session对象存储特定用户会话所需的属性及配置信息。
    这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
    当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。
Session 对象最常见的一个用法就是存储用户的首选项。
    例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。
有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。 

特点: Session总结
    Session 称之为 会话控制 技术
    Session生命周期, 会话结束 对象销毁.
    Session的数据存储在内存中.
    Session只可以临时存储数据.不能永久存储.
4.6.2 Cookie总结 

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,
进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。 

特点: 1. 类型: 小型文本文件. 
        2. 文件通常是加密的. 
        3. cookie 可以临时或者永久存储.


@Override
public PageResult getUserList(PageResult pageResult){
    Long total = userMapper.getTotal();
    int size = pageResult.getPageSize();
    int start = (pageResult.getPageNum()-1)*size;
    List<User> rows = userMapper.findUserListByPage(start,size);
    return pageResult.setTotal(total).setRows(rows);
}

前端相关说明:
node.js 作用Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境。
VUE脚手架 是前端开发模拟后端设计的一种开发框架体系 xxx.vue
VUE.js 是将javascript封装的高级渐进式的JS的框架
    node.js:相当于js的运行环境
    vue脚手架xxx.vue:js的开发框架体系
    vue.js:运行js脚本的框架

ElementUI组件声明步骤:
    1.导入需要的组件:import{ Button } from 'element-ui'
    2.对外声明组件:Vue.use(Button)

用户登录操作:
@PostMapping("login")
public SysResult login(@RequestBody User user){
    String token = userService.login(user);
    if(token == null){
        return Sysresult.fail();
    }
    return Sysresult.success(token);
}
public interface UserService{
    String login(User user);
}
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;

    @Override
    public String login(User user){
        //将用户输入的密码进行加密处理:
        //先获取密码,再加密,最后重写赋值
        String password = user.getPassword();
        //获取加密密码
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        //封装新密码
        user.setPassword(md5Pass);
        //查询数据
        User userDB = userMapper.findAllByUp(user);
        if(userDB == null){
             return null;
        }
        String uuid = UUID.randomUUID().toString().replace("-","");
        return uuid;
    }
}
public interface userMapper{
    
    @Select("select * from user where username=#{username} and password=#{password}")
    User findAllByUp(User user);
}

Spring提供的注解,用来控制事务,一般用于新增、修改、删除等业务
@Transactional类上
    默认只处理运行时异常
readOnly=true 只读,用户不能修改,比如公告、合同

添加try-catch进行异常的捕获,是为了返回用户可以识别的有效数据
定义全局异常处理类,封装AOP
在类上添加@RestControllerAdvice 用来标识控制层
代码:     @ExceptionHandler(value=RuntimeException.class)//类里方法上
    public SysResult fail(Exception e){
        e.printStackTrace();
        return SysResult.fail();
    }

@Test
public void test1(){
    //获取条件构造器
    QueryWrapper<User> queryWrapper = new QueryWrapper();
    queryWrapper.gt("age",18).likeLeft("name","乔");//先条件查询再模糊查询
    //将queryWrapper的查询数据传入selectList()当做参数
    List<User> userList = userMapper.selectList(queryWrapper);
    System.out.printLn(userList);
}
@Test
public void test2(){
    String name = null;
    String sex = "女";
    QueryWrapper<User> queryWrapper = new QueryWrapper();
    //判断字符串是否为空
    boolean nameFlag = StringUtils.hasLength(name);
    boolean sexFlag = StringUtils.hasLength(sex);
    queryWrapper.eq(nameFlag,"name",name).eq(sexFlag,"sex",sex);
    List<User> userList = userMapper.selectList(queryWrapper);
    System.out.printLn(userList);
}

实现商品的分页查询
控制层
@GetMapping("getItemList")
public SysResult getItemList(PageResult pageResult){//PageResult中封装了5个属性
    //已知3个参数:query,pageNum,pageSize
    pageResult = itemService.getItemList(pageResult);
    return SysResult.success(pageResult);
}
业务层
@Override
@Transactional
public PageResult getItemList (PageResult pageResult){
    //构建分页对象,包括第几页,第几条
    Page<Item> page = new page<>
            (pageResult.getPageNum( ),pageResult.getPageSize( ));
    //准备条件构造器 进行模糊查询
    QueryWrapper<Item> queryWrapper = new QueryWrapper<>( );
    String query = pageResult.getQuery( );
    boolean flag = StringUtils.hasLength(query);//判断用户输入的查询内容是否为空
//query代表用户搜索的询问内容,如果flag是true,则拼接值query,即查询结果的title显示的是相关内容
    //select * from item where title like "%query%";
    queryWrapper.like(flag,"title",query);
    //将MP查询的分页数据进行封装
    page = itemMapper.selectPage(page,queryWrapper);
    Long total = page.getTotal();//获取total总数
    List<Item> rows = page.getRecords();//获取最终的分页查询结果
    return pageResult.setTotal(total).setRows(rows);
}

实现图片的上传
@PostMapping("upload")
public SysResult upLoad(MultipartFile file) throws IOException{
    ImageVo imagevo = fileService.upLoad(file);
    if(imageVo == null){
        return Sysresult.fail();
    }
    return SysResult.success(imageVo);
}
@Override
public ImageVo upLoad(MultipartFile file){
    //1.获取文件名称
    String fileName = file.getOriginalFilename( );
    //1.1将文件名称全部转化为小写
    fileName = fileName.toLowerCase();
    //1.2判断图片类型是否匹配
    if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
        return null;
    }
    //2.校验图片 通过图片的宽度和高度校验是否为恶意程序
    BufferedImage bufferedImage = ImageIo.read(file.getInputStream());
    int height = bufferedImage.getHeight();
    int width = bufferedImage.getWidth();
    if(height == 0 || width == 0){
        return null;
    }
    //3.将图片按年月日分目录存储
    String dateDir = new SimpleDateFormat("/yyyy/MM//dd/")
            .format(new date());//获取动态地址,根据指定的样式
    String localDirPath =  "E:/project3/images/"; //固定地址
    String dateDirPath = localDirPath + dateDir;//拼接为全地址
    File dirfile = new File(dateDirPath);//封装成对象
    if(!dirFile.exists()){
        dirFile.mkdirs();//创建目录
    }
    //4.防止文件重名 动态生成UUID来替换图片名
    String uuid = UUID.randomUUID().toString().replace("-","");
    String fileType = fileName.subString(fileName.lastIndexOf("."));//截取图片类型,比如jpg/png/gif
    String newFileName = uuid +fileType;//生成新的图片名字
    //5.封装对象实现上传
    String path = dateDirPath + newFileName;//图片路径+新图片名
    file.transferTo(new File(path));
    //6. 实现ImageVO数据的返回
               //6.1 准备虚拟路径 /2021/11/11/uuid.jpg
               String virtualPath = dateDir + newFileName;
              //6.2 准备URL地址
              String url = "https://img14.360buyimg.com/n0/jfs/t1/143609/21/21574/393533/619207beEb9674812/ac4a3f32ea776da3.jpg";
              return new ImageVO(virtualPath,url,newFileName);
}
图片预览回显效果的相关操作:
利用nginx反向代理服务机制,将用户请求地址拦截,物理地址
找80端口号命令: netstat -ano | findstr "80"
关于nginx的相关命令:start nginx    /重启  nginx -s reload    /关闭  nginx -s stop
编辑nginx-conf: 
#配置图片代理
    server {#开启一个反向代理服务
        listen 80;   #监听的端口号
        server_name image.jt.com;   #拦截的用户请求地址 域名
        location / {
            root D:/project3/images;  #磁盘地址的根目录
        }
    }
#配置后端服务器代理
    server {#开启一个反向代理服务
        listen 80;   #监听端口号
        server_name manage.jt.com;   
        location / {
            #代理的请求协议
            proxy_pass http://localhost:8091;  
        }
    }
#配置前端服务器代理
    server {#开启一个反向代理服务
        listen 80;   #监听端口号
        server_name www.jt.com;   
        location / {
            proxy_pass http://localhost:8080;  
        }
    }
编辑hosts文件
位置:C:\Windows\System32\drivers\etc\hosts
域名和IP请求的映射
127.0.0.1       localhost
#图片服务器
127.0.0.1      image.jt.com
#后台服务器
127.0.0.1      manage.jt.com
#前端服务器
127.0.0.1      www.jt.com

运维相关知识:
负载均衡配置:
定义tomcat服务器集群
upstream tomcats{
        server localhost:8091;
        server localhost:8092;
        server localhost:8093;
    }
1.2.1 轮询策略
1.2.2 权重策略(关键字weight)
1.2.3 IPHASH策略(ip_hash):
    不能保证均匀分布,出现负载不均衡的现象
    关键字down:告知nginx该服务器已下线
    关键字backup:标识为设定备用机

linux安装mariadb数据库的步骤命令:
ping www.baidu.com
yum install mariadb-server  安装数据库
systemctl start mariadb  启动
systemctl enable mariadb  开机自启
mysql_secure_installation  数据库初始化
update user set host="%" where host="localhost";
flush privileges;  刷新数据库权限
配置linux防火墙策略:
firewall-cmd --state  当前防火墙状态
systemctl disable firewalld.service  开机后不需要启动防火墙
systemctl stop firewalld.service 手动关闭防火墙
更改mysqlyog的主机IP地址,重新导入表
linux安装nginx的步骤命令:
导入压缩包后解压:tar -xvf nginx-1.19.4.tar.gz
rm - f nginx-1.21.3.tar.gz 解压后删除压缩包
mv nginx-1.19.4 nginx-source  改名
./configure 安装nginx服务器
[root@localhost nginx-source]# make
make install
whereis nginx  查找工作目录
[root@localhost sbin]# ./nginx   启动
./nginx -s reload   重启

从spring容器中获取数据,需要指定key
格式为: @Value("${key}") //key一般来说位于配置文件中 key: value

JVM版本:Java HotSpot(TM) 64-Bit Server VM

NoSuchBeanDefinitionException
先查找当前类,包,再去上一级

@BeforeEach 表示在执行@Test注解方法之前执行此方法
192.168.126.129
RPC:服务与服务之间的通信 跨进程远程调用,比如服务的消费方调用服务的提供方

注册服务中心nacos:
命令:startup.cmd -m standalone
http://localhost:8848/nacos
服务提供者为服务消费者提供远端调用服务

第三阶段增删改查复习:
1.查询
编辑UserMapper.xml映射文件:
    根据ID查询用户数据
    <select id="findUserById" parameterType="Integer" resultType="User">
        select * from demo_user(id,name,age,sex) where id=#{id}
    </select>
@Test
public void testUserById(){
    int id = 1;
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findUserById(id);//返回值为User对象
    System.out.println(user);
    sqlSession.close();
}
User findUserById(Integer id);
2.新增操作需要传参且参数为整个对象,同时不需要返回值,只是自动的返回影响的行数rows
<insert id = "saveUser">//无resultType
    insert into demo_user(id,name,age,sex) value(null,#{name},#{age},#{sex})
</insert>
Integer saveUser(User user);
User user = new User(null,"xxx","xxx","xxx");//封装对象
int rows = userMapper.saveUser(user);
3.修改操作:不会改变id的值,可作为条件,也不需要返回值,传入的参数可以用对象接
<update id = "updateUser">
    update demo_user set name=#{name},age=#{age} where id=#{id}
</update>
将需要的值在测试类封装对象时赋值
4.删除操作:一般来说只需要一个name参数
String name = "xxx";
int rows = UserMapper.deleteUserByName(name);
int deleteUserByName(String name);
<delete id = "deleteUserByName">
    delete from demo_user where name=#{name}
</delete>
动态sql语句,如果有重名属性,一般使用map集合封装数据
Map map = new hashMap();
map.put("key",value);
List<User> userList = userMapper.findByAge(map);
关于@Param注解的说明:
关于Mybatis参数封装说明:
    1.mybatis中只支持单值传参
    2.单值可以是数字,字符串,对象
    3.多值转化为单值,首选Map集合
    4.@Param("minAge") int minAge 将参数封装为map
    解析:Map = {minAge=100,maxAge=1000}
List<User> findParam(@Param("minAge") int minAge,
                               @Param("maxAge") int maxAge);

模糊查询的结果为集合,参数是条件属性
List<User> findUserByLike(String name);
<select id = findUserByLike resultType="User">
    select * from demo_user where name like "%"#{name}"%"

别名包的设置:<typeAliases>

简化Sql语句:
<select id = "findUser" resultType="User">
    select <include refid="user_cloumn"/> from demo_user
</select>
<sql id = "user_cloumn">
    id,name,age,sex
</sql>

网络编程:
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。
客户端要获取一个 Socket 对象通过实例化,而服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。

多线程编程:
新建:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。
就绪状态:
当线程对象调用start()方法后,线程进入就绪状态
运行状态:
就绪状态的线程获取到cpu资源就可以执行run(),后续会成为死亡状态(完成)、阻塞状态、就绪状态
线程的优先级:是一个整数
其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )
3种创建线程是方法:
1)实现Runnable接口
    调用run()方法进行重写
    Thread(Runnable threadOb,String threadName);//threadName指定新线程的名字
    新线程创建之后,你调用它的 start() 方法它才会运行
2) 继承Thread类
       继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
3)通过Callable接口和Future创建
public class CallableThreadTest implements Callable<Integer>{
    public atatic void main(String[ ] args){
         CallableThreadTest ctt = new CallableThreadTest();
        //使用 FutureTask 类来包装 Callable 对象
         FutureTask<Integer> ft = new FuctureTask<>(ctt);
    }
}
@Override  
public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  

mybatis遇到集合参数操作时,需要将集合进行遍历:
foreach标签表示循环遍历集合
collection属性表示集合的类型:array数组int[ ]、List集合、Map集合
item属性表示循环遍历的数据的变量
1. array集合操作
<select id = "findIn" resultType="User">
    select * from demo_user where id in (
        <foreach collection="array" separator="," item="id">
            #{id}
        </foreach>)
</select>
2.List集合操作
数组转化时,需要使用包装类型.
基本类型没有get/set方法  包装类型是对象 对象中有方法
//将数组转化为list集合
Integer[] ids = {1,2,4,5,7};
List list = Arrays.asList(ids);
List<User> userList = userMapper.findInList(list);
3. Map集合操作
<select id="findInMap" resultType="User">
        select * from demo_user where id in (
        <foreach collection="ids" item="id" separator=",">
            #{id}
        </foreach>
        )
        and sex = #{sex}
</select>
多值封装为Map集合
List<User> findInMap(@param("ids") int[ ] ids, @param("sex") String sex);
Sql:
     select * from demo_user where id in (1,3....)
     and sex = "男";

关联查询一对一封装:
<resultMap="empRM" type="Emp">
    <id property="empId" column="emp_id"/>//主字段
    <result property="empName" column-"emp_name"/>//其他字段
    <association property="dept" javaType="Dept">
        <id property="deptId" column="dept_id"/>
        <id property="deptIName" column="dept_name"/>
    </association>
</resultMap>
一对多:private List<Emp> emps;
<collection property="emps" ofType="Emp">
            <id column="emp_id" property="empId"/>
            <result column="emp_name" property="empName"/>
</collection>
<!--在配置文件中配置settings信息-->
    <settings>
        <!--开启驼峰映射规则-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
映射文件需要配置的属性是autoMapping="true",主键还是得标识
一级缓存SqlSession默认开启
二级缓存SqlSessionFactory,创建不同的SqlSession,
    在映射文件中添加缓存的标签<cache/>
如果需要使用一级/二级缓存,则POJO对象必须实现序列化接口. 否则数据不可以被缓存.
implements Serializable
关于增删改查注意的点:
@ResponseBody  将List集合转化为json
参数接收:单个参数就只传参数类型 参数名
    如果是多个参数,并且参数名称与属性名称一致,就可以用对象来接
@RequestMapping("findUserByAS")
public List<User> findUserByAS(User user){
    return userService.findUserByAS(user);
}
RestFul风格的参数传递:地址里面直接是值
 http://localhost:8090/updateById/1/黑熊精/3000/男
参数接收:1.单个参数使用@PathVariable Integer id接收;
    2.如果是多个参数就使用对象
@PutMapping("/updateById/{id}/{name}/{age}/{sex}")
public String updateById(User user){
    userService.updateById(user);//无返回值
    return "修改成功"
}
@Update("update demo_user set name=#{name},age=#{age},sex=#{sex} where id=#{id}
void updateById(User user);

双向数据绑定:v-model
    data中的数据<---->页面的数据
分支结构:v-if\v-else-if  判断为true,则显示标签
循环结构:v-for="item in list"

同源策略要求 协议://域名:端口号都相同
注解@MapperScan是Spring管理Mybatis接口的注解,不是管理框架本身.
注解@MapperScan来自 org.mybatis.spring.annotation.MapperScan;
MP中的条件构造器用来动态拼接where条件MP可以用在CURD的方法中,并且不是必须的/可以为null
注解@TableField是用来标识属性与字段的映射关系,是MP中的核心注解
git  branch表示选择分支;  git中创建分支结构的命令是 git checkout -b "新分支的名称"git  merge合并分支
SpringBoot读取properties文件默认采用ISO-8859-1,和程序默认字符集没有关系. 所以有中文乱码问题
Linux系统中没有盘符的概念,是树形结构,以/开头. 有/的是绝对路径 没有/的是相对路径
1.用户需要继承BaseMapper接口 2.接口是MP提供好的无需用户编辑3.用户可以不使用该接口,只不过需要自己手动编辑xml映射文件
firewall-cmd --zone=public --add-port=80/tcp --permanent 
Ajax特点是局部刷新异步访问. Ajax是JS原生自带的 不是jQuery/Axios所特有的
tail -f  xxx.log是动态查看日志的命令.

页面参数说明:
用户在js中写的是js对象,经过浏览器解析发起Ajax请求,
js对象被解析为json串传给后端服务器的控制层
交互的媒介: HTTP协议 协议中要求传递字符串

打印日志:logging:level:com.jt:debug
前端传递的是JSON,后端不可以直接使用User对象接收.
解决方案:
     * 1.对象可以转化为JSON串  @ResponseBody
     * 2.JSON串转化为对象     @RequestBody
@Autowired:
属性取值包括:byType,byName,default(默认)

模拟浏览器:
public class Browser{
    public static void main(String[ ] args) throws IOException{
        Socket socket = new Socket("127.0.0.1",9000);
        InputStream in = socket.getInputStream();
        byte[ ] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println("server say:"+new String(buf,0,len));
        socket.close();
    }
}

http://www.axios-js.com
https://spring.io

路由导航守卫:拦截跳转路径
编辑index.js文件:
router.beforeEach((to,from,next)=>{
    if(to.path==='/login') return next()
    let token = window.sessionStorage.getItem('token')
    if(token===null || token===" "){//校验是否有token
        return next("/login")
    }
    next()
})

一对多:private List<Rights> childern;
@RequestMapping("/rights")
@Autowired
private RightsService rightsService;
@GetMapping("/getRightsList")
public SysResult getRightsList(){
    List<Rights> rights = rightsService.getRightsList();
    return SysResult.success(rights);
}

ajax:局部刷新,异步访问  ajax引擎

定义嵌套路由对象:
const router = new VueRouter({
    routers: [
        {path:'/shopping',component:shopping,
        children:[
            {path:'/shopping/shoe',component:shoe},
            {path:'/shopping/phone',component:phone}
        ]}
    ]
)}
利用钩子函数实现数据查询:
在页面刷新的时候就执行此函数
mounted(){
this.getUserList()
}
参数:
query:查询关键字,是用户查询的数据,可以为null
rows:分页查询的结果,对象集合,显示所有属性/字段信息
后台使用PageResult对象来接,包含五个参数,已知三个:query,pageNum,pageSize
rows是通过pageNum,pageSize得到的
select * from user limit 起始位置,查询的条数
public PageResult getUserList(PageResult pageResult){
    long total = userMapper.getTotal();//@Select("select count(1) from user")
 
    int size = pageResult.getPageSize();
    int start = (pageResult.getPageNum()-1)*size;
    List<User> rows = userMapper.findUserListByPage(start,size);
    return pageResult.setTotal(total).setRows(rows);
}

List<User> findUserListByPage(@param("start") int start,
            @param("size") int size,
            @param("query") String query);

作用域插槽:scope.row.status 动态获取当前行对象
请求路径:/user/status/{id}/{status}
@PutMapping("/status/{id}/{status}")
可以用对象接,可以没有返回值,更新时间参数也需要修改new Date();
新增操作:将整个form表单数据封装为js对象进行参数传递
步骤:将密码加密再封装;添加状态码信息true;添加创建时间;完成入库操作

Spring控制事务策略:
1. Spring中的事务注解@Transactional 默认条件下只处理运行时异常.如果遇到检查异常(编译异常)事务控制没有效果. 
2. 注解的属性控制 
    rollbackFor = IOException.class , 遇到某种异常实现事务的回滚 
    noRollbackFor = 遇到某种异常事务不回滚. 
    readOnly = true 该操作是只读的,不能修改. 公告/合同等

全局异常处理机制:AOP
捕获异常是为了按照特定的要求 返回用户可以识别的有效数据.
@RestControllerAdvice
public class SystemException{//运行时异常
    @ExceptionHandler(value = RuntimeException)
    public SystemResult fail(Exception e){
        e.printStackTrace();
        return SysResult.fail();
    }
}
mybatis-plus:
<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
</dependency>

MP工作原理:
实质: MP动态生成Sql语句. 
铺垫: 1. insert into 表名(字段名…) value (属性值…) 

工作原理: 1. 用户执行userMapper.insert(user); 操作 
2. 根据UserMapper的接口找到父级BaseMapper.根据BaseMapper的接口查找泛型对象User. 
3. 根据User.class 获取注解@TableName(“demo_user”),获取表名 
4. 根据User.class 获取所有的属性,根据属性获取指定的注解@TableField(value =“name”),获取字段名称 
5. 根据属性获取属性的值.之后动态拼接成Sql语句
6. 将生成的Sql交给Mybatis执行入库操作. 
1. insert into demo_user(id,name,age,sex) value (null,xx,xx,xx) 

MP使用特点: 根据其中不为null的属性进行业务操作!!!
条件构造器:
条件构造器如果多个条件时,默认使用and
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq(nameFlag,"name",name);
UpdateWrapper也可以封装where条件
userMapper.update(user,updateWrapper);
MP的依赖导入包:
<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
</dependency>

@TableName("item_cat")//关联数据表
@TableId(type = IdType.AUTO)//主键自增
@TableField(exist = false) //属性不是表字段
spring与MP的整合
配置文件:
管理数据源
spring:
    datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver //驱动
        url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
        username: root
        password: root

定义初始化函数:
created(){ //页面加载时默认获取商品分类列表数据 this.findItemCatList()  }

@Pathvariable("level") Integer level
多级嵌套:Map<parentId,List>
利用Map集合封装所有的数据信息
构建map对象;
查询所有的数据库信息itemCatList
利用for循环将数据封装到map集合中
for(ItemCat itemCat : itemCatList){
    //获取parentId当做key
    Integer key = itemCat.getParentId();
    //判断map集合中是否有值
    if(map.containsKey(key)){
        map.get(key).add(itemCat);
    }else{  //作为第一个数据填充
        List<ItemCat> list = new ArrayList<>();
                    list.add(itemCat);
                    map.put(key,list);
           }
    return map;
}
Map<Integer,List<ItemCat>> map = getMap();

Date date = new Date();
itemCat.setStatus(true).setCreated(date).setUpdated(date);//启动
itemCatService.deleteItemCat(id,level);
删除二级菜单的,应该先获取三级数据之后删除
delete from item_cat where parent_id=#{id} or id=#{id}
queryWrapper.eq("parent_id",id)
                        .or()
                        .eq("id",id);

获取三级列表信息的实现思路:
遍历一级集合,获取二级数据,封装三级菜单
for(ItemCat oneItemCat : oneList){
    List<ItemCat> twoList = oneItemCat.getChildren();
    if(twoList == null || twoList.size()==0){
        continue;
    }
    for(ItemCat  twoItemCat : twoList){
        int twoId = twoItemCat.getId();
        List<ItemCat> threeList = map.get(twoId);
        twoItemCat.setChildren(threeList);
    }
    return oenList;
}

发起ajax请求:
const{data: result} = await this.$http.delete("/itemCat/deletaItemCat",
        {params:{id:itemCat.id,level:itemCat.level}})
if(result.status!=200) return this.$message.error("删除商品分类失败")

修改商品分类名称name:参数是整个form表单json串
@Transactional
public void updateItemCat(ItemCat itemCat){
    ItemCat temp = new ItemCat();
    temp.setId(itemCat.getId()).setName(itemCat.getName())
        .setUpdate(new Date());
    itemCatMapper.updateById(temp);
}
新增/修改操作时自动填充注解:
@TableField(fill = FieldFill.INSERT_UPDATE)
MetaObjectHandler指定默认的规则
列入配置自动填充类
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //框架用法说明: MP根据实现类,自动的完成数据的注入. MP框架自动调用
    //metaObject: 指定默认的规则
    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created", date, metaObject);
        this.setFieldValByName("updated", date, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {

        this.setFieldValByName("updated", new Date(), metaObject);
    }
}
页面开始加载时就执行:created() { this.getItemList() }
分页查询业务层代码:
@Service
public class ItemServiceImpl implements ItemService{

    @Autowired
    private ItemMapper itemMapper;

    /**
     *  要求: 3+2(总记录数,分页结果)
     *  关于selectPage(参数说明)
     *   参数1: page MP提供的分页对象
     *   参数2: 条件构造器
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //1.根据已知参数先封装构建分页对象  参数1: 第几页   参数2: 多少条
        Page<Item> page = new Page<>
            (pageResult.getPageNum(),pageResult.getPageSize());
        //2.准备条件构造器 构建模糊查询,获取queryWrapper值
        QueryWrapper queryWrapper = new QueryWrapper();
        String query = pageResult.getQuery();//已知
        boolean flag = StringUtils.hasLength(query);
        queryWrapper.like(flag,"title",query);  //query搜索关键字title like %query%

        //3.根据1、2条MP查询 实现分页数据的自动封装
        page = itemMapper.selectPage(page,queryWrapper);

        //4.获取数据,返回分页对象
        long total = page.getTotal();
        //获取分页结果
        List<Item> rows = page.getRecords();
        return pageResult.setTotal(total).setRows(rows);
    }
}
MP框架提供:分页配置类
@Configuration  //这是配置类
public class MybatisConfig {
    //需要通过配置文件 指定数据库类型.
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
        return interceptor;
    }
}

json串:{id:xxx,status:xxx}

//1.完成表单校验
        this.$refs.addItemFormRef.validate( valid => {
          if(!valid) return this.$message.error("请输入商品必填项")
        })
//完成商品参数的封装
//2.0 将商品价格扩大100倍
        this.addItemForm.price = this.addItemForm.price * 100
//2.1 将商品图片的数据转化为字符串
        this.addItemForm.images = this.addItemForm.images.join(",")
 //2.5 实现商品数据提交 用一个大对象 包裹2个小对象
        let submitAddItem = {
          item : this.addItemForm,
          itemDesc: this.itemDesc
        }
可以使用ItemVO对象接收,包含2个参数:item, itemDesc,两者根据id来实现一对一关系
 item.id = itemDesc.id
 @Override
 @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        itemMapper.insert(item);
    }
/**   主键自动回显原理:
     * 问题: id是主键自增. 入库之后才有主键所以
     *      应该让主键动态回显
     * 1.Mybatis 动态实现回显
     *      <insert id="xxxx" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
     *         insertinto xxxx
     *     </insert>
     * 2.MP是mybatis的增强版本.所以可以实现自动的主键回显!!!
     * @param itemVO
     */
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        //获取商品基本信息
        itemMapper.insert(item);
        //获取商品详情
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }
校验规则rules:{ xxx }
rules: {
          title: [
                  { required: true, message: '请输入商品标题信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          sellPoint: [
                  { required: true, message: '请输入商品卖点信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          price: [
                  { required: true, message: '请输入商品价格信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          num: [
                  { required: true, message: '请输入商品数量信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
        }
required: true表示此项为必填项
message: '请输入商品标题信息'是给客户的提示填写内容
<!-- 图片上传的JS
        1. action: 代表图片上传的地址url
        2. file-list: 图片列表数据的集合[{name:"xx",url:"xxx"},{}]
        3. 钩子函数: 满足某些条件时触发.
        4. on-preview 当点击图片时触发
        5. on-remove  当移除列表中的图片时触发
        5.on-success 图片上传成功时触发
        6.multiple   可以支持多张图片上传
        7.drag       是否允许拖拽
-->
编辑ImageVO对象类:
参数:virtualPath图片的虚拟路径、urlPath图片服务路径、fileNam文件名称(UUID.type)
/**
     * 业务说明: 实现图片上传
     * URL: http://localhost:8091/file/upload
     * 类型: post
     * 参数: file 字节信息
     * 返回值: SysResult.success()
     * 扩展:
     *      一般情况下:
     *          一般前端向后端服务器发送字节信息.由外到内实现数据传输.
     *      采用输入流信息. InputStream file
     *          使用字节流的弊端: 1.必须手动关闭, 2.代码操作繁琐
     *          底层代码的实现.
     *       SpringMVC高级API  MultipartFile 专门处理IO流操作
     *  文件上传步骤:
     *        1.获取文件名称
     *        2.准备文件上传的目录
     *        3.判断目录是否存在  存在目录: 实现上传  没有目录:创建目录
     *        4.利用工具API方法,实现文件上传.
     *  注意事项: MultipartFile 默认支持1M的数据
     */
 @PostMapping("/upload")
     public SysResult upload(MultipartFile file) throws IOException {
        //1.获取文件名称
         String fileName = file.getOriginalFilename();
         //2.准备磁盘地址
         String dirPath = "E:/project3/images/";
         //3.将这个文件目录 封装为File对象
         File dirFile = new File(dirPath);
         //4.判断对象是否存在
         if(!dirFile.exists()){
            //如果文件目录不存在,则创建目录
             dirFile.mkdirs(); //表示多级目录上传.
         }
         //5.封装文件全路径 E:xxx/xxx/a.jpg
         String path = dirPath + fileName;
         File allFile = new File(path);
         //6.实现文件上传 将IO流按照指定的对象格式进行输出.
         file.transferTo(allFile);
         return SysResult.success();
     }
正则表达式:[a-zA-Z0-9-_]
/**
     * 业务说明: 实现图片上传
     * URL: http://localhost:8091/file/upload
     * 类型: post
     * 参数: MultipartFile file 字节信息
     * 返回值: SysResult.success()
     * 问题思考:
     *      1.完成图片类型校验 jpg|png|gif....
     *      2.防止恶意程序    a.exe.jpg
     *      3.将图片分目录存储
     *             3.1.按照类型分   理论可以但是得多分配几个
     *             3.2.按照时间划分. yyyy/MM/dd
     *      4.自定义文件名称. 利用UUID充当图片名称.
     */
fileName.toLowerCase();//转化为小写字母
//1.3正则校验是否为图片类型
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //图片类型 不匹配  程序应该终止
            return null;
        }
校验是否为恶意程序 怎么判断就是一张图 高度和宽度
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int height = bufferedImage.getHeight();
            int width = bufferedImage.getWidth();
            if(height == 0 || width == 0){
                return null;
            }
//3.将图片分目录存储 yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
                                .format(new Date());
            String dateDirPath = localDirPath + dateDir;
            File dirFile = new File(dateDirPath);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }
//4.防止文件重名  动态生成UUID.类型
            //4.1 动态生成UUID
            String uuid = UUID.randomUUID().toString()
                              .replace("-","");
 //4.2 获取图片类型        abc.jpg    .jpg
            String fileType = fileName.substring(fileName.lastIndexOf("."));
//全新的文件名称
String newFileName = uuid + fileType;
属性动态赋值,方便后期维护
@Service
@PropertySource("classpath:/image.properties")//配置文件的路径
public class FileServiceImpl implements FileService{
    @value("${image.localDirPath}")
    private String localDirPath;
    @value("${image.preUrl}")
    private String preUrl;
}
负载均衡配置
1.轮询策略: 根据配置文件中的服务器的顺序,依次访问服务器
#定义tomcat服务器集群
    upstream tomcats {
        server  localhost:8091;
        server  localhost:8092;
        server  localhost:8093;
    }
    
    #配置后端服务器代理  manage.jt.com  localhost:8091
    server {
        listen 80;
        server_name manage.jt.com;
        location / {
            #代理请求协议 
            #proxy_pass http://localhost:8091;
            proxy_pass  http://tomcats;
        }
    }
2.权重策略:根据服务器的性能,灵活的进行访问的配比
upstream tomcats {
        server  localhost:8091 weight=6;
        server  localhost:8092 weight=3;
        server  localhost:8093 weight=1;
    }
3.IPHASH策略
将用户数据存储到后端服务器的session中,并且保证每次用户访问都访问到同一个服务器
upstream tomcats {
        ip_hash;
        server  localhost:8091 weight=6;
        server  localhost:8092 weight=3;
        server  localhost:8093 weight=1;
    }

前端访问后端,是通过ajax的方式动态访问. 需要将Ajax请求改为后端域名访问.
dist就是编译之后的前端项目.
#配置前端服务器代理  www.jt.com  dist/index.html
    server {
        listen 80;
        server_name www.jt.com;
        location / {
            root dist;
            index index.html;
        }
    }
项目说明: 
1. 前端项目 是静态资源文件 部署在nginx内部 
2. 后端项目 多个tomcat服务器 8091/8092/8093 
3. 用户通过www.jt.com 反向代理到前端服务 
4. 前端获取数据通过manage.jt.com 访问后端服务器集群. 通过nginx实现负载均衡
 
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,
提高程序的可重用性,同时提高了开发的效率。
Spring中的AOP 利用代理对象在不修改源代码的条件下,对方法进行扩展.
 /**
     * 知识回顾: AOP利用动态代理扩展目标方法.
     * 公式:    切面 = 切入点表达式 + 通知方法
     * 切入点表达式: 如果目标对象满足切入点表达式的判断(if),则spring自动为其创建代理对象
     * 通知方法:  对目标方法进行扩展的封装方法.
     * 目标对象的bean的ID: userServiceImpl
     * 切入点表达式:
     *      1. bean("bean的ID")
     * AOP规则:  如果目标对象满足切入点表达式,则执行通知方法
     */
当我们在项目中定义了AOP切面以后,系统启动时,会对有@Aspect注解描述的类进行加载分析,
基于切入点的描述为目标类型对象,创建代理对象,并在代理对象内部创建一个执行链,
这个执行链中包含拦截器(封装了切入点信息),通知(Around,…),目标对象等,
我们请求目标对象资源时,会直接按执行链的顺序对资源进行调用。

ajax请求的入口对象是xmlhttprequest对象
网关服务器在哪里做了负载均衡:lb://
采用AOP的设计获取用户行为日志
反射技术的应用:bean对象的创建、依赖注入、方法对象的获取、方法上注解的获取等等
@PreAuthorize访问此方法需要授权

第四阶段:
关于微服务的小结:
一种微服务架构设计思想(分而治之~服务治理)
一套微服务解决方案(Spring Cloud Alibba)
一种项目创建方式(Maven聚合项目)
五大微服务核心组件(Nacos,Feign,Ribbon,Sentineal,Gateway)
一套单点登录(SSO)系统,三个核心业务(微服务架构+5个核心组件+3个认证和授权技术)
常用的设计模式(策略模式,门面模式,代理模式,工厂模式,建造模式,责任链模式,
桥接模式,装饰模式,适配器模式,单例设计,模板方法模式,享元模式(线程池等)。。)

参数传递机制有两种:值传递和引用传递。
基本类型的值传递:是指在调用函数时将实际参数复制一份传递到函数中,
这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用类型的引用传递:是指在调用函数时将实际参数的地址传递到函数中,
那么在函数中对参数所进行的修改,将影响到实际参数。

apt-get update在linux中下载vim指令
若依微服务项目部署的准备工作:
基础环境要求:
JDK >= 1.8 (推荐1.8版本)
Mysql >= 5.7.0 (推荐5.7版本)
Redis >= 5.0 (市场主流版本,最新版本为6.xx)
Maven >= 3.6 (http://maven.apache.org/download.cgi)
Node >= 10 (稳定版14.X,https://nodejs.org/en/download/)
nacos >= 1.4.1 (https://github.com/alibaba/nacos/releases)

Redis官网:
http://www.redis.cn/
http://www.redis.io/
安装windows下安装服务,在redis根目录启动命令行(cmd),执行

redis-server --service-install redis.windows.conf

启动redis服务(启动成功以后会出现successful) 
redis-server --service-start

其它redis指令(这个操作可以不执行) 
redis-server --service-stop //停止服务
1 redis-server --service-uninstall //卸载服务

连接测试redis,在redis根路径执行(默认端口6379) 
redis-cli -h 127.0.0.1 -p 6379

安装Nacos服务治理业务
https://github.com/alibaba/nacos/releases
打开/conf/application.properties里打开默认配置,并基于你当前环境配置要连接的数据库,
连接数据库时使用的用户名和密码:
启动Nacos服务:startup.cmd -m standalone
默认登陆用户名和密码都为nacos
在命令行输入npm install 安装项目依赖
在命令行输入 npm run dev 启动运行前端服务

分布式系统:多个模块部署在不同的服务器上;
高可用:系统中部分节点失效时,其他节点能够接替它继续提供服务
集群:一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群
反向代理:for外部请求,与之交互的只有代理服务器
在多台服务器上分别部署Tomcat,反向代理使应用服务器可支持的并发量大大增加
正向代理是代理服务器代替系统内部来访问外部网络的过程,
反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。
Web服务与数据库分开
数据库支持的连接数是有限的,连接用尽时,其它用户就会阻塞,
    同时频繁磁盘读写也会使系统性越来越差
在Tomcat同服务器上增加本地缓存,并在外部增加分布式缓存redis
微服务架构:
微服务架构主要解决的是如何快速地开发和部署我们的服务
基于Spring Cloud Stream 为微服务应用构建消息驱动能力
微服务项目一般都会采用聚合工程结构,可通过聚合工程结构实现共享资源的复用,
简化项目的管理方式
GitCGB2108IVProjects (工作区/空项目Empty Project)
├── 01-sca   //(微服务父工程)
     ├── sca-provider            //服务提供方法
     ├── sca-consumer         //服务消费方法
     ├── sca-gateway            //网关服务
<!--maven父工程的pom文件中一般要定义子模块,
    子工程中所需依赖版本的管理,公共依赖并且父工程的
    打包方式一般为pom方式,scope为import导入-->
<!--第一步: 定义子工程中核心依赖的版本管理(注意,只是版本管理)--><dependencymanage>
<!--第二步: 添加子工程的所需要的公共依赖-->
<!--第三步: 定义当前工程模块及子工程的的统一编译和运行版本-->
    其中:<build><!--项目构建配置,我们基于maven完成项目的编译,测试,打包等操作,
    都是基于pom.xml完成这一列的操作,但是编译和打包的配置都是要写到build元素
    内的,而具体的编译和打包配置,又需要plugin去实现,plugin元素不是必须的,maven
    有默认的plugin配置,常用插件可去本地库进行查看-->
 <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                 <!--假如本地库没有这个版本,这里会出现红色字体错误-->
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
   </plugin>

基于RestTemplate对象实现远端服务调用,注入到控制层
@Bean
public RestTemplate restTemplate(){//基于此对象实现远端服务调用
    return new RestTemplate();
}
Nacos中服务的负载均衡(Nacos客户端consumer端负载均衡)
LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,其也是注入到控制层
ServiceInstance serviceInstance = loadBalancerClient.choose("sca-provider");
String url = String.format("http://%s:%s/provider/echo/%s",
        serviceInstance.getHost(),serviceInstance.getPort(),appName);
占位符的使用,后面的值也要按顺序
@Value("${spring.application.name:8090}")
  private String appName;
负载均衡实现默认是因为Nacos集成了Ribbon来实现的,
Ribbon配合RestTemplate,可以非常容易的实现服务之间的访问。
当使用RestTemplate进行远程服务调用时还需要负载均衡,可以在RestTemplate对象构建时,
使用@LoadBalanced对构建RestTemplate的方法进行修饰,
@Bean
@LoadBalanced//属于Spring
public RestTemplate loadBalancedRestTemplate(){
    return new RestTemplate();
}
在需要RestTemplate实现负载均衡调用的地方进行依赖注入
面试分析:
1.@Bean注解的作用?(一般用于配置类内部,描述相关方法,用于告诉spring此方法的返回值要交给spring管理,
bean的名字默认为方法名,假如需要指定名字可以@Bean(“bean的名字”),最多的应用场景是整合第三方的资源-对象)
2.@Autowired注解的作用?(此注解用于描述属性,构造方法,set方法等,用于告诉spring框架,按照一定的规则为属性进行DI操作,
默认按属性,方法参数类型查找对应的对象,假如只找到一个,则直接注入,类型多个时还会按照属性名或方法参数名进行值的注入,
假如名字也不同,就出报错.)
3.Nacos中的负责均衡底层是如何实现的?(通过Ribbon实现,Ribbon中定义了一些负载均衡算法,
然后基于这些算法从服务实例中获取一个实例为消费方法提供服务)
4.Ribbon 是什么?(Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法)
5.Ribbon 可以解决什么问题? (基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口)
6.Ribbon 内置的负载策略都有哪些?(8种,可以通过查看IRule接口的实现类进行分析)
7.@LoadBalanced的作用是什么?(描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,
这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。)
8.我们可以自己定义负载均衡策略吗?(可以,基于IRule接口进行策略定义,也可以参考NacosRule进行实现)

Feign应用实践:
在服务消费方添加依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动类上添加@EnableFeignClients,来开启Feign的远程服务调用
@FeignClient(name="sca-provider")//sca-provider为服务提供者名称
其中@FeignClient注解所描述的远程服务调用接口RemoteOtherService(可有多个)底层会为其创建实现类。
多个时需要为远程调用服务接口指定一个contextId,作为远程调用服务的唯一标识
(这个标识是Bean对象的名字remoteOtherService)即可
feign方式的远程服务调用,底层会自动基于ribbon组件实现负载均衡。
基于FallbackFactory接口的实现类对象处理RemoteProviderService接口调用时出现的服务中断,超时等问题
在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制:
feign:  
  hystrix:
    enabled: true #默认值为false
注册中心的核心数据是什么?(服务的名字和它对应的网络地址)
注册中心中心核心数据的存取为什么会采用读写锁?(底层安全和性能)
Nacos健康检查的方式?(基于心跳包机制进行实现)
Nacos是如何保证高可用的?(重试,本地缓存、集群)

配置中心:经常变化的配置信息
进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项,
//log对象在哪个类中创建,getLogger方法中的就传入哪个类的字节码对象
//记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可.
//假如在log对象所在的类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建
private static Logger log=LoggerFactory.getLogger(ProviderLogController.class);
bootstrap.yml(启动优先级最高)
config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
@RefreshScope注解的作用是在配置中心的相关配置发生变化以后,
能够及时看到类中属性值的更新(底层是通过重新创建Controller对象的方式,对属性进行了重新初始化)。
在类ProviderLogController中添加一个获取日志级别(debug<info<warn<error)的属性和方法
@Value("${logging.level.com.jt:error}")
private String logLevel;
客户端获取了配置中心的配置信息以后,会将配置信息在本地内存中存储一份
1.4.x版本的nacos客户端会基于长轮询机制从nacos获取配置信息,
    所谓的长轮询就是没有配置更新时,会在nacos服务端的队列进行等待
日志规范:@SLF4J
配置管理模型:环境不同则配置不同
namespace:
group:
dataId:
useLocalCache为自己定义的配置值,表示是否使用本地缓存.

Sentinel限流熔断:
Sentinel核心分为两个部分:
核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
https://github.com/alibaba/Sentinel/releases
命令启动:
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
在消费方添加依赖如下:
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    sentinel:
      transport:
         dashboard: localhost:8180 # 指定sentinel控制台地址
Sentinel是如何对请求进行限流的?(基于sentinel依赖提供的拦截器)
你了解哪些限流算法?(计数器、令牌桶、漏斗算法,滑动窗口算法,…)
Sentinel 默认的限流算法是什么?(滑动窗口算法)
QPS达到单机阈值时,就会限流
Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。
被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用
@SentinelResource("doGetResource")
流控模式为链路模式时,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_context,
因此单独对指定链路限流会不生效,需要在application.yml添加如下语句来关闭URL PATH聚合
sentinel:
     web-context-unify: false
对限流后的异常进行自定义处理:
@Slf4j
@Component
public class ResourceBlockHandler {
    /**
     * 注意此方法中的异常类型必须为BlockException (
     * 它是所有限流,降级等异常的父类类型),方法的返回
     * 值类型为@SentinelResource注解描述的返回值类型,
     * 方法的其他参数为@SentinelResource注解描述的方法参数,
     * 并且此方法必须为静态方法
     * @param ex
     * @return
     */
    public static String call(BlockException ex){
        log.error("block exception {}", ex.getMessage());
        return "访问太频繁了,稍等片刻再访问";
    }
}
修改@SentinelResource注解中的属性定义,例如: 

@SentinelResource(value="doGetResource",
        blockHandlerClass = ResourceBlockHandler.class,
        blockHandler = "call")
public String doGetResource(){
    return "do get resource";
}
Sentinel中限流,降级的异常父类是谁?(BlockException)
Sentinel 出现降级熔断时,系统底层抛出的异常是谁?(DegradeException)
Sentinel中异常处理接口是谁?(BlockExceptionHandler)
Sentinel中异常处理接口下默认的实现类为? (DefaultBlockExceptionHandler)
热点规则的限流模式只有QPS模式
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。
入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务。
Sentinel 的黑白名单控制功能
黑白名单根据资源的请求来源(origin)限制资源是否通过,
若配置白名单则只有请求来源位于白名单内时才可通过;
定义请求解析器,用于对请求进行解析,并返回解析结果,
sentinel底层在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则,
@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getParameter("origin");//这里的参数名会与请求中的参数名一致
        return origin;
        //获取访问请求中的ip地址,基于ip地址进行黑白名单设计(例如在流控应用栏写ip地址)
        String ip= request.getRemoteAddr();
        System.out.println("ip="+ip);
        return ip;
    }
}
Sentinel诞生的背景?(计算机的数量是否有限,处理能力是否有限,并发比较大或突发流量比较大)
服务中Sentinel环境的集成,初始化?(添加依赖-两个,sentinel配置)
Sentinel 的限流规则?(阈值类型-QPS&线程数,限流模式-直接,关联,链路)
Sentinel 的降级(熔断)策略?(慢调用,异常比例,异常数)

/**
 * 构建一个数据源对象JedisDataSource,在此类中构建一个方法
 * getConnection(),能通过此方法对外提供一个连接,这个连接
 * 每次都是从JedisPool获取,但是不能每次
 * 获取连接都创建一个JedisPool对象
 */
在类JedisDataSource中通过懒汉式设计获取jedis连接
public class JedisDataSource{
   private static final String HOST = "主机地址";
   private static final int PORT=xxx;//端口号
   public static Jedis getConnection(){
        if(jedisPool==null) {
             synchronized (JedisDataSource.class) {
                 if (jedisPool == null) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(1000);
                    poolConfig.setMaxIdle(60);
                    jedisPool = new JedisPool(poolConfig,HOST, PORT);
                    //1.分配内存
                    //2.初始化属性
                    //3.执行构造方法
                    //4.将对象赋值给引用变量
                }
            }
        }
        return jedisPool.getResource();
  }
 public static void close(){
        jedisPool.close();
    }
}
/**简易单点登录系统业务分析
 * 1)完成用户身份认证
 * 2)完成用户资源访问时的授权
 * SSO系统简易设计
 * 1)用户登录成功后,将用户信息存储到redis,并返回给客户端一个token
 * 2)用户携带token访问资源,资源服务器要检查token的有效性,并基于token查询用户信息确定
 * 用户是否已认证,假如已认证还要判定用户是否有这个资源的访问权限。
 */
一、执行登录操作
1.判断用户传入参数的有效性:用户名和密码不能为空,若为空抛出异常信息返回给客户
if(username==null || "".equals(username))
throw new IllegalArgumentException("用户名不能为空");
密码同理
2.判断用户名是否存在
3.判断用户输入的密码是否正确
if(!"123456".equals(password))
4.登录成功后将用户信息存储到redis
4.1从连接池中获取jedis数据库连接
JedisDataSource.getConnection();
4.2通过uuid动态获取token
String token = UUID.randomUUID().toString();
4.3以键值对的方式存值
jedis.hset(token,"username","xxx");
jedis.hset(token,"permission","xxx,xxx");
4.4//设置key值的有效期
jedis.expire(token,2);
5.返回token
二、携带token访问用户的资源
1.检验token值是否为空
if(token==null || "".equals(token));//字符串是否为空
2.基于token查询用户信息
Jedis jedis = JedisDataSource.getConnection();
Map<String,String> user = jedis.hgetAll(token);
3.判断用户是否已经登录
if(user==null || user.size()==0)//集合是否为空
4.判断用户是否有该资源的访问权限
String permissionStr = user.get("permission");//根据key获取值value=user.get("key")
4.1由多个权限的逗号为分隔符转换为数组-->list集合
String[] permissions = permissionStr.split(",");
List<String> permissionList = Arrays.asList(permissions);
4.2判断
if(permissionList.contains("该资源的访问权限"));
5.返回用户要访问的资源

创建一个缓存配置类CacheManagerConfig,修好默认的redis序列化方式
//构建RedisCacheConfig对象
        RedisCacheConfiguration cacheConfig=
                RedisCacheConfiguration.defaultCacheConfig()//默认的序列化
                        //定义key的序列方式(默认的key的序列化方式就是string类型)
                        .serializeKeysWith(RedisSerializationContext
                                           .SerializationPair.fromSerializer(
                                                                RedisSerializer.string()))
                        //定义值的序列化方式
                        .serializeValuesWith(RedisSerializationContext
                                              .SerializationPair.fromSerializer(
                                                            RedisSerializer.json()));//jackson

自定义序列化规则,基于jackson api
1.构建RedisSerializer对象
2.定义序列化和反序列化的规则,通过ObjectMapper进行定义和封装
2.1 new一个ObjectMapper对象
2.2 设置对象序列化,调用setVisiblity方法来获取key和value,构建json串
2.3 设置是否允许json串中key对应的值为null
 objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
2.4设置是否在序列化时,存储对象类型
        objectMapper.activateDefaultTyping(
                objectMapper.getPolymorphicTypeValidator(),//默认的多态校验
                ObjectMapper.DefaultTyping.NON_FINAL,//非final类型,要将类型信息写入到json中
                JsonTypeInfo.As.PROPERTY);//类型信息以json属性形式存储到json中
        serializer.setObjectMapper(objectMapper); 
 //更新RedisTemplate对象配置
        redisTemplate.afterPropertiesSet();
*  1)如何认证(认证逻辑的设计)
 * 2)认证通过以后如何颁发令牌(令牌的规范)
 * 3)为谁颁发令牌(客户端标识,client_id,...)

配置令牌的生成并解析令牌的合法性
//这里采用JWT方式生成和存储令牌信息
        return new JwtTokenStore(jwtAccessTokenConverter());
@Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter=
                new JwtAccessTokenConverter();
        //JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
        //这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
        //这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
        jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
        return jwtAccessTokenConverter;
    }

第五阶段:
微信: wanght6666666
笔记
http://wanght.blog.csdn.net
https://blog.csdn.net/weixin_38305440/article/details/108609574

随堂笔记
http://code.tarena.com.cn/CGBCode/cgb2108/
http://218.247.142.198/CGBCode/cgb2108/
tarenacode
code_2013

Gitee代码仓库
https://gitee.com/benwang6/cgb2108
课前资料,百度网盘:
https://pan.baidu.com/s/19tOuVOYJsplssj3kLvfzcA

Eureka注册中心的四条运行机制:
@EnableEurekaServer
注册:
拉取:客户端每30秒拉取一次注册表,来刷新注册表,不是时时
心跳包:客户端每30秒向注册中心发送一次心跳数据,连续3次收不到心跳,会删除它的注册信息
自我保护模式:15分钟内,85%的服务器出现心跳异常(大多由于网络故障),自动进入自我保护模式
    即保护所有注册信息不删除,网络恢复后就恢复正常模式;
    开发调试期间关闭保护模式server:
                    enable-self-preservation: false
http://hostname:port/eureka
集群需要在C盘的host文件中添加
高可用:将应用在不同的服务器部署启动
命令行配置优先于yml配置文件:java -jar xxx.jar --server.port=8001

Feign集成Ribbon
@EnableFeignClients于启动类上
声明式客户端,可以通过声明抽象接口或抽象方法来调用远程服务@FeignClient
    负载均衡:默认启用
    重试:调用后台服务失败(异常、服务器奔溃、超时),可以自动发起重试调用
重试参数(有默认值):
ribbon.MaxAutoRetries   单台服务器的重试次数  0
ribbon.MaxAutoRetriesNextServer   更换服务器的次数 1
ribbon.ReadTimeout    接收响应的超时时间 1000ms  运行缓慢
ribbon.ConnectTimeout   与后台服务器建立连接等待超时时间,默认1000ms
ribbon.OkToRetryOnAllOperations  是否对所有类型请求都进行重试,默认get

Zuul网关:转发调用用户请求
@EnableZuulProxy
1.所有服务模块的统一调用入口:向后台模块转发,只需在配置文件中配置路由规则
2.统一的权限校验:继承Zuul的过滤器,在过滤器中判断用户权限
       过滤器filters:
              前置过滤器pre(包含自定义过滤器)、路由过滤器routing(负责后台转发调用)、后置过滤器post
3.集成Ribbon:默认已经启动,默认不启用重试
在网关重试可能造成后台服务大面积出现压力翻倍,重试功能应该尽量往后放
       若启用重试:pom文件添加spring-retry依赖
                          yml文件添加zuul.retryable=true
4.集成hytrix:容错和限流工具,默认已经启用
         容错:通过降级,添加降级类,实现FallbackProvider接口
        从spring容器自动发信降级类的实例,完成自动配置
        调用后台服务失败,执行当前模块的一段降级代码,返回降级结果:错误提示
         限流:通过熔断来限制后台服务的流量
                流量过大时,后台服务出现大量错误,会自动触发熔断
              10秒20次请求(必须首先满足),50%出错
              断路器打开一段时间会进入半开状态,会尝试发送一次客户端调用,调用成功则恢复正常链路
         hystrix数据监控:利用springboot的Actuator工具来暴露自己的监控数据
               Actuator的多种监控数据:
        健康状态
        环境变量、配置参数
        spring mvc的映射路径
        JVM虚拟机堆内存镜像
        hystrix-dashboard仪表盘
    pom添加依赖
    yml配置
turbine:
  app-config: zuul  #注册表里面的名字
  cluster-name-expression: new String("default")  #集群名表达式
    启动类添加@EnableHystrixDashboard
    Turbine从多台服务器抓取Hystrix日志进行聚合,hystrix-dashboard去抓取聚合后的日志数据
    @EnableTurbine
eclipse MAT内存分析工具
acphe压力测试:
ab -n 20000 -c 50 http://localhost:3001/item-service/76juh4yg4t?token=uy4t43

配置中心:Spring cloud config只做配置
集中维护和管理配置文件
数据库、磁盘、git仓库(默认)
新建config文件夹 包含各服务的配置文件
新建config模块
添加依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类:@EnableConfigServer
yml:cloud:
    config:
      server:
        git: #远程仓库地址
          uri: https://gitee.com/lq13175101667/springcloud01.git
          search-paths: config
配置中心客户端依赖:config-client

消息中间件Rabbitmq:
分布式系统中用于模块之间传递数据的工具组件
./ip-dhcp
ifconfig   自动获取ip地址
解决网络设置问题:
# centos 7 禁用 NetworkManager 系统服务
systemctl stop NetworkManager
systemctl disable NetworkManager

# centos 8 开启 VMware 托管
nmcli n on
systemctl restart NetworkManager
VMware Workstation 16许可证密钥:ZF3R0-FHED2-M80TY-8QYGC-NPKYF

简单模式:只有一个消费者
工作模式:多个消费者
三种交换机 BuiltinExchangeType:
Topic:*.orange.*   *.*.rabbit   lazy.# 订阅模式
Fanout:群发模式下的虚拟机
Direct:路由模式下的直连虚拟机

配置中心+Bus配置刷新
向消息服务器发送刷新指令,从消息服务器接收指令

Sleuth+Zipkin链路跟踪
Sleuth 服务名,链路id,当前模块id,是否发送给zipkin
Sleuth只需添加依赖,不需要再进行配置
<!--添加sleuth依赖-->

-<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-sleuth</artifactId>

</dependency>

<!--添加zipkin依赖-->

-<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-zipkin</artifactId>

</dependency>
eureka客户端注册正确的地址
选择正确的网卡:
bootstrap.yml
命令ipconfig查看自己的物理网络
注册ip,而不是主机名:
application.yml

订单的流量削峰
订单系统生成订单发送到rabbitmq,后台的消费者模块接收处理订单存储到数据库

rabbitmq-springboot整合

谷歌浏览器插件下载地址https://github.com/jinliming2/Chrome-Charset

分布式事务
幂等性控制:
多次操作的结果要和一次操作的结果相同

Linux查看进程的方法有四种,分别为:1、使用“ps aux”命令来查看,能以简单列表的形式显示出进程信息;2、使用“ps -elf”命令来查看;3、使用“top”命令来查看;4、使用“pstree -aup”命令来查看。”

rocketmq

docker run -d --name cat2
docker run -d --name cat1 --restart=always tomcat//docker重启后cat1的tomcat会自动启动
docker exec -it cat1 bash//进入容器
systemctl restart docker
docker start cat2
docker logs cat2
docker ps -a
docker cp xxx: /xxx/xxx/xxx...
vim server.xml//修改文件
-v ~/server.xml:  //目录挂载
docker rm -f $(docker ps -aq)//强制删除运行中的容器
docker volume create xxx
docker inspect xxx//查看详细目录
-P//自动分配端口号
-net=host//直接使用宿主机网络
docker exec -it cat1 ping cat2
docker network rm my-net


测试jedis分片访问 
redis.clients.jedis.对象
一致性哈希 顺时针方向去找一个物理节点(容器)
启动3个docker容器,镜像是redisxxx,映射到7000/1/2三个端口
docker run -d --name redis7000 --restart=always -p 7000:6379 redis
docker run -d --name redis7001 --restart=always -p 7001:6379 redis
docker run -d --name redis7002 --restart=always -p 7002:6379 redis
进入7000这个容器 执行redis-cli客户端
docker exec -it redis7000 redis-cli
set a 11-->ok
get a-->11

清理容器
docker rm -f $(docker ps -aq)
设置主服务器
docker run -d --name redis6379 --restart=always --net=host redis
设置2个从服务器
docker run -d --name redis6380 --restart=always --net=host redis \
redis-server --port 6380 --slaveof 192.168.64.150 6379

docker run -d --name redis6381 --restart=always --net=host redis \
redis-server --port 6381 --slaveof 192.168.64.150 6379
查看主从关系
info replication
客户端--域名--DNS--IP--端口--Nginx--远端服务
--cluster create创建集群
docker exec -it redis-cli -p 7000 -c
创建数据卷
docker volume create mysql-data

volume是存在于一个或多个容器中的特定文件或文件夹,这个目录以独立于联合文件系统的形式在宿主机中存在,并为数据的共享与持久化提供以下便利。
volume在容器创建时就会初始化,在容器运行时就可以使用其中的文件。
volume能在不同的容器之间共享和重用。 
对volume中数据的操作会马上生效。
对volume中数据的操作不会影响到镜像本身
volume的生存周期独立于容器的生存周期,即使删除容器,volume仍然会存在,没有任何容器使用的volume也不会被Docker删除。

一台PC上可以安装多个JDK,具体环境变量配置哪个JDK,哪个就生效
JAVA_HOME : 配置的是JDK安装的目录
Path : 配置的是JDK的bin目录,不新建的
CLASS_PATH:配置的是JDK的lib目录

全文搜索引擎ElasticSearch
重启服务器
shutdown -r now
设置ip
./ip-static
ip:xxxxxx
ipconfig
导入镜像
docker load -i es-img.gz
设置max_map_count
查看
cat /etc/sysctl/

安装ik分词器:
es-node1 analysis-ik 7.9.2
ik_max_word:最细粒度拆分
ik_smart:最粗粒度拆分

启动kibana容器
docker run -d --name kibana --net es-net -p 5601:5601 
数据库的文档增删改查
put完整替换
post修改部分字段
get
delete

@Document(indexName="students",shards=3,replicas=2)
接口:ElasticsearchRepository,数据库访问规范,定义了基础方法
Pageable,Page
对查询结果做高亮设置(注解):@HighLight(parameters=@HighLightParameters(preTags="<em>",postTags="</em>"),fields={@HighLightField(name="name")})
SearchHit<对象类>封装了高亮字段的结果
list.for-->for()

stringBuilder s = new stringBuilder();
for(String s1 : name){
         s.append(s1);
}
return s.toString();

yml:es连接服务器列表
搜索框的回填:value

Kubernetes/k8s集群方案
kubectl命令
3个核心概念:
Pod:k8s的容器,是对docker容器的封装对象
控制器RC(Replication Controller):自动控制容器部署、销毁的工具
Service: 提供一个不变的访问地址,转发请求地址到容器(即调用)
手动创建pod
Pod设置标签:根据标签来选中对应的服务器
kubectl create -f kubia-manual-with-labels.yml
kubectl get po --show-labels
新添加标签
kubectl label po kubia-manual-v2 env=debug
修改标签
kubectl label po kubia-manual-v2 env=debug --overwrite
env=prod/product
creation_method=manual
删除标签:kubectl label po kubia-22f7k env-
节点选择器:根据节点把pod部署到匹配的工作节点服务器
kubectl label node 192.168.64.191 gpu=true
namespace/ns:同一个服务器里不能重名
kubetcl delete po kubia-gpu
标签过滤删除:
kubectl delete po -l creation_method=manual
kubectl delete ns custom-namespace
全部容器删除:kubectl delete po --all
删除后控制器会自动重写创建新的容器
存活探针:
创建配置文件:
kubectl create -f kubia-liveness-probe.yml
查看日志:
kubectl logs kubia-liveness --
livenessProbe:
       httpget:
              path:
              port: 8080
设置延迟: initialDelaySeconds: 15
避免容器内应用没有完全启动的情况下就开始探测
kubectl get svc -ns kube-system
edit编辑
ReplicaSet(新版控制器rs)-->replicas:3-->selector
增强选择器:app in(xxx,xxx)
DS监控器ssd
job控制器
schedule:
cronjob:计划任务控制器
Endpoints:pod和service之间的一种资源
是地址列表,通过双方的name来和service绑定,自动创建
kubectl get ep//查看Endpoints
kubectl describe svc kubia//查看service详情,有关联的endpoints对象
用ip地址来关联容器
手动创建endpoints对象来调用外部系统服务external-service
kubectl get svc//获取IP集群
curl http://xxxxx.com//服务地址
在external-service里面直接指定要调用的一个域名
kubectl exec -it kubia-xxx bash//进入容器执行
service对外暴露:端口暴露到服务器
ports: nodeport节点端口  port  targetport
k8s支持数据挂载:
EmptyDir:临时数据文件
index-xml
共享文件夹
安装NFS文件系统:dnf install nfs-utils
mkdir /etc/nfs_data
async同步,no_root_squash:服务器端使用root权限
启动
systemctl enable nfs-server
systemctl enable rpcbind
systemctl start nfs-server
systemctl start rpcbind
挂载到另一服务器的文件夹
mount -t nfs 当前服务器:文件夹 挂载文件夹
mongodb:键值对存储
persistent volume持久数据卷(like实现类):
    存储位置
    存储条件:
        空间:1G
        访问模式:readwriteonce/readonlimany
persistent volume claim声明(like接口):存储条件
fortune: args间隔时间覆盖cmd命令参数
fortune: env通过设置环境变量interval覆盖间隔时间参数
config Maps:配置存储cm
       sleep-interval: 5
停机升级(新旧版本不兼容时)&逐步替换,滚动升级
Deployment对象:自动化实现滚动升级
创建过程:deploy-->rs-->po
修改镜像image: luksa/kubia: v1-->v2--v3...版本
创建出RS控制器,并控制控制器的容器数量
配置的容器数量spec: replicas: 3
添加属性:kubectl patch deploy kubia -p xxx
通过service来回调用容器pod
kubectl rollout status deploy kubia//查看滚动升级状态
kubectl get deploy
hash值标签hash=xxxx
新版本测试失败需要回滚到上一版本:kubectl rollout undo deploy kubia
控制滚动升级速率:
maxSurge 25% 滚动过程中可以超出的容器数量
maxUnavailable 25% 最多允许多少容器不可用
kubectl get deploy -o yaml
pause暂停升级
就绪探针:readinessProbe: periodSeconds: 1
探测成功,容器变为就绪状态
工作中可以使用Deployment对象来部署容器,方便之后的升级
K8S部署springcloud项目:
构建镜像:上传文件到root
镜像jdk-->各个模块的镜像-->压缩复制到其他服务器-scp->导入load
-->部署控制器、service
docker build -t centos7-jdk8:v1 jdk/
docker build -t sp-eureka:v1 eureka/
控制器名:metadata: name: eureka1

栈:LIFO push(), pop()
队列:FIFO offer(),  peek() , pull()
key.hashCode()
链表长度到8则转成红黑树
ThreadLocal:线程的数据绑定 set() get() remove() 
volatile:可见性(多线程场景下频繁修改的数据)、禁止指令重排


大数据
数据存储:HDFS框架
加载.vmx文件-->修改内存、处理器个数-->修改静态IP地址
进入目录cd/etc/sysconfig/network-scripts
修改IP:vim ifcfg-ens33
重启:systemctl restart network.service
systemctl stop firewalld禁止服务
systemctl disable firewalld禁止开启
firewall-cmd --state检查防火墙状态
修改主机名vi /etc/hostname-->修改hosts文件vi /etc/hosts
生成公钥和私钥:ssh-keygen
ssh-copy-id hadoop01

实现MetaObjectHandler自动填充元数据接口
TableField(fill = FieldFill.INSERT) strictInsertFill(metaObject,字段名,填充类型,填充的具体内容 )
TableField(fill = FieldFill.UPDATE)
雪花算法生成分布式ID,mybatis默认集成,64bit@TableId(type = IdType.ASSIGN_ID)
@TableLogic//逻辑删除is_deleted
Swagger2文档生成docket.@Api/.ApiInfo
定义统一结果格式:ResultVo
统一异常处理:@RestControllerAdvice @ExceptionHandler(value=Exceoption.class)
log.error( );
日志多环境配置SpringProfile
数据字典管理模块
easyExcel:数据的导入和导出 属于alibaba,采用一行一行的解析模式,大大减少内存占用
 和依赖xmlbeans
eventslistener
封装表对象,生成标题属性@ExcelProperty
mutipartfile
通过redisTemplate.opsForValue().set( )来进行数据的缓存  
阿里云短信服务sms:依赖aliyun-java-sdk-core
token单点登录:JSON Web Token, Base64URL算法
JWT令牌:JSON Web Token,自包含令牌,是包含关于用户的元数据和声明
作用:
    能够实现无状态分布式身份验证,对token信息有防伪作用;
原理:
    一个JWT是由三部分组成:JWT头、有效载荷、签名哈希
    最后有这三者组合进行Base64URL编码得到JWT
有效载荷是主体内容部分,也是一个json对象,包含需要传递的数据,有七个字段
浏览器端保存令牌
token是无状态的,Session是有状态的(服务器会记住)
集成JWT:依赖:jjwt
jwtBuilder.
    头(alg,typ).claim(载荷:自定义信息).subject(载荷:默认信息).signWith(签名).compact(组装)
jwtParser.解析
jwtUtils.createToken( )
HttpServerletRequest request.getHeader("token")
LocalDateTime的json序列化问题
openFeign的超时控制配置文件配置、
    配置日志bean(配置类OpenFeignConfig-->feignLoggerLevel)
fallback = " 回调类"feign对sentinel的支持:
       feign: sentinel: enabled: true
网关路由配置:
gateway: routes: id: 各服务名称 uri: lb 赋值均衡 predicates: path: 路径策略
网关跨域配置:
   配置类CorsConfig-->CorsWebFilter
动态生成表单formHelper.builsForm
封装表单数据为VO对象/组装表单数据进行提交
BeanUtils.copyProperties(xxxVo,xxx)
ajax远程调用:远程服务器必须开放跨域访问权限
form表单远程调用:不受跨域控制,但组装表单比较复杂
java远程调用net.HttpURLConnection
在第三方的异步回调通知后要处理的业务:
     幂等性判断-->更新表数据-->更新信息-->返回result_code-->发送消息
JSONObject.toString( )
添加rabbit-mq模块
rabbit-amqp依赖是springboot整合的 
MqService
      sendMessage(   )
           {amqpTemplate.convertAndSend(exchange,routingkey,message)};
MQConfig{  MessageConverter}
rabbitmq: host: port: virtual-host: username: password
@Component//spring的一个组件,随spring的启动而自动生成
生产者:MqService.sendMessage(  )
消费者:@RabbitListerner(bindings=@QueueBinding(
value=@Queue(value=,durable= ), 
exchange=@Exchange(value ),
key={  } ))
HttpServerletRequest request
String token = request.getHeader("token");
mybatis-plus-boot-starter
项目二
2020.8-2021.11
项目名称:碳银户用光伏贷系统 | 项目工具:windows
项目描述:工商银行提供资金,用户在此系统申请贷款来获得 
项目描述:
该系统是浙江碳银在与第三方托管平台工商银行的合作下为小微企业提供金融扶持,无区域限制、无资产抵押、无中间费用,实现流程等提供的线上化办理,使用户申请更加明晰、规范,高效,提高了合伙人、用户的服务质量及业务整合能力。
功能模块: 
【管理员模块】【积分等级管理】 【系统设置】【会员管理】【借款管理 】【标的管理】
【用户模块】【账户总览】【资金记录】【借款记录】【还款计划】【资金记录】
责任描述:三大模块代码的编码实现
1【用户模块】实现了用户的注册、登录需求。
1.1 注册
基本步骤:用户服务:控制层:校验手机号码--将验证码存入redis;业务层:判断是否已注册--插入用户信息
发送短信服务:openFeign远程调用用户服务中的校验手机号是否已注册
1.2 登录
引入JWT--封装登录表单信息--校验手机号码、密码是否为空--判断用户是否存在--密码是否正确--用户是否被禁用--记录登录日志--生成token--封装对象返回
基于Token模式的单点登录,在微服务分布式项目中,客户发送登录请求后通过授权中心的验证后颁发JWT令牌并存储到HTTP请求的Header字段之中返回给用户,之后用户就可以携带此令牌访问其他服务而不用重新登录,减少访问数据库的次数。
2【开通第三方账户】
商户以POST方式提交绑定信息跳转到工行绑定账户页面,用户完成绑定后同步返回到商户页,同时异步通知平台。
3【积分等级管理】积分等级列表、新增积分等级(查询、添加、删除、修改)。
4【借款额度申请】实现用户基本信息、身份认证、房产车辆信息的封装,并提交到第三方,额度申请成功后返回。   
5【数据字典】实现用户信息的分页展示,并将列表结果存入redis;基于alibaba-easyexcel实现用户详情信息的上传和下载        
开发环境: jdk1.8  maven3.6 IdeaIU-2020.2.3
技术选型:
前端:HTML CSS JavaScript VUE
后端:SpringBoot SSM MyBatis Plus SpringCloud Alibaba Lombok Swagger2 LogBack     alibaba-easyexcel Spring Data Redis HttpClient SpringTask SpringProfile
第三方接口:工商银行  阿里云sms短信服务  OSS对象存储服务

项目一
maven作为依赖、构建管理的工具
官网:mvnrepository.com
spring是管理项目的组件,包括框架的组件和我们写的组件
value=?表示就是具体的值;ref=""表示要关联的bean
logback日志级别可以控制批量打印logback.xml
获取logger对象logger=loggerFactory.getLogger(xx.class)
logger.debug(" ")
logger.info(" ")
同步:在同一个线程内部按顺序执行
异步:拆分成多个线程并行执行,不会互相等待
在配置类中:
@Bean//相当于XML标签的配置,把方法的返回值放入IOC容器
public Employee getEmplotee(){ return new Employee()  }
xx.getBean( Employee.class )=employee
SpringSecurity工作原理:具体任务由具体过滤器来完成
依赖:spring-security-web;spring-security-config;
此权限框架使用的是过滤器,可以用于整个web应用;拦截器只能在springMvc中使用
@EnableWebSecurity//启用web环境下的权限控制功能
extends WebSecurityConfigureAdapter
调用HttpSecurity设置资源、角色请求、注销功能loginout()
跨站请求伪造:_csrf,要求退出必须是post请求
AuthenticationManagerBuilder设置用户登录时具备的角色
小范围具体设置-->大范围模糊设置,不然会被覆盖
SpringSecurity底层用"ROLE_"区分角色和权限,因为在数据库查询出来两者是放到同一个集合里面的
查询数据库完成认证:实现UserDetailsService接口。注入JdbcTemplate
加密需要实现的接口:passwordEncoder
为了使加密效果更强,使用带盐值加密,
new BCryptPasswordEncoder().Encode()/校验.matches(rawpassword,encodedPassword)
找不到bean异常:NoSuchBeanDefinitionException
三大组件的启动顺序:
ContextLoaderListener:创建spring的IOC容器--过滤器Filter初始化--创建springMvc的IOC容器
退出登录:
.logout().logoutUrl(" 指定控制层的退出登录地址").logoutSuccessUrl(" ")
SpringBoot是对Spring的进一步封装,自带一个内置的Tomcat可以独立运行,基于注解的开发,舍弃重量的XML配置文件,使用YML或properties配置就行;打成jar包
sarter引入所需依赖,@Enablexxx启用某种功能
SpringBoot工作原理:
读取spring.factories文件;加载xxxProperties类;根据注解加载组件
整合MyBatis:mybatis-spring-boot-starter;mysql-connector-java
整合Redis: spring-boot-starter-data-redis
SpringCloud核心:
是基于HTTP协议,Dubbo的核心是RPC
消费者调用生产者,在没有Feign的环境下是使用RestTemplate来实现调用
restTemplate.getForObject( host,url,xxx.class);
Ribbon属于客户端的负载均衡@Loadbalanced
eureka服务器端:eureka: instance: hostname: localhost
            client: 注册:true拉取:true 服务地址:
客户端:eureka: client: service-url: dafaultZone: 配置当前微服务作为客户端访问注册中心时使用的地址
Feign声明式远程调用,在远程调用接口里面要求方法的声明一致、注解映射的地址一致
Hystrix在生产者是熔断,消费者是降级
启动类加@EnableCircuitBreaker开启断路器功能
降级:启用:feign: hystrix: enabled: true
实现FallbackFactory接口的方法,泛型是@FeignClient标记的接口类型
在@FeignClient中添加fallbackFactory属性值=xxx.class
监控:在provider添加依赖starter-actuator
yml:
management: endpoints: web: exposure: include: hystrix.stream
@EnableHystrixDashboard启用仪表盘功能
Zuul:路由(转发外部请求到微服务实例),过滤(对请求处理过程进行干预)
@EnableZuulProxy//启用网关代理功能
extends ZuulFilter,重写方法
获取request对象:RequestContext.getCurrentContext().getRequest()
父工程(配置依赖管理)--注册中心--Zuul网关--实体类模块--common公共工程--数据库服务--Redis服务(抽取所有对redis的数据set,get操作,对外暴露接口)--会员中心(注册登录认证)--项目维护--资产总览--订单维护--支付
common公共工程:api模块(存放远程调用接口),constant(存放常量),util工具类
实体类模块:
VO关于浏览器接收、发送的对象 
PO持久化对象,与数据库表对应 
DO第三方或中间件等查询到的数据封装 
DTO数据传输对象,发生在程序内的
进行数据的拼接组装:
spring提供的BeanUtils.copyProperties(xxxVo,xxxPo),左边复制给右边相同属性的值
@Transactional(readOnly=true)//针对查询属性设置事务属性
网页首页的映射地址用/代表的是:用户访问域名就可以直接访问首页信息的展示
验证码服务工具包:需要接受的参数:host,url,path,phone,appcode,sign,skin等等
   随机生成四位数的验证码:
    StringBuilder builder = new StringBuilder();
    for(int i = 0; i<4; i++){
           int random = (int)(Math.random()*10);
           builder.append(random);
    String code = builder.toString();
注册过程中的验证码服务流程:控制层:发送验证码--判断发送结果--若发送成功则存到redis--判断发送的验证码和存储的验证码是否相等--返回成功结果
执行整体注册流程:
提交表单--会员中心服务--使用VO接收表单数据--到redis中查询验证码--比较表单验证码和redis验证码--检查用户是否已存在(selectOne)--对密码进行盐值(BCryptPasswordEncoder())加密--执行保存(insert/调用数据库服务提供的接口mysql-provider)
执行登录:
    比较用户密码是否正确:
         new BCryptPasswordEncoder().matches(userpwd, userPO.getUserPassword( ))
将LoginVO登录表单数据存入session域:
       HttpSession().session.setAttribute( );
退出登录:session.invalidate( );
使用Session的共享技术SpringSession来解决分布式项目中session不一致的问题
在Zuul的yml中配置Spring Session
extends ZuulFilter: 
获取当前请求对象:
    requestContext = RequestContext.getCurrentContext( );
    request = requestContext.getRequest( );
获取当前Session对象:
session = request.getSession( );
从Session对象中获取已经登录的用户:session.getAttribute( );
浏览器:发送Cookie数据
服务器:解析
服务器:查找对应的Session,如果没有则新建
OSS对象存储服务
将文件地址存储到数据库
java程序调用OSS,需要访问秘钥AccessKey才能调用服务API
引入OSS客户端SDK依赖:aliyun-sdk-oss
浏览器--文件上传--java程序接收输入流--上传OSS
文件上传工具包:
创建OSSClient实例:
ossCLient = new OSSClientBuilder( ).build(endpoint,accessKeyId,accessKeySecert);
生成上传文件的目录:
dateName = new SimpleDateFormat("yyy-MM-dd").format(new LocalDateTime());
生成上传文件在OSS服务器上保存的文件名:
objectName=dateName+UUID.randomUUID().toString().raplace("-","")+文件扩展名
文件扩展名=originalName.substring(originalName.lastIndexOf("."));
调用OSSClient对象的方法实现上传
ossCLient.putObject();
内网穿透:实现外网的支付宝调用内网的接口
NATAPP
项目整体:
使用Maven作为构建和和管理的工具
使用SpringMVC作为Web层框架
使用Spring提供的容器来管理项目中的组件
使用MyBatis作为持久层框架
使用SpringSecurity接管项目的登录、登录检查、权限验证
用户注册、登录:
调用第三方接口SMS短信服务给用户手机发送验证码;
使用BCryptPasswordEncoder()实现带盐值的加密
使用Redis存储
在Zuul中使用ZuulFilter实现登录状态检查
在Zuul中配置访问各个具体微服务的路由规则

项目名称:光伏产品融资平台 | 项目工具:windows
项目描述:
该系统是公司通过向碳银用户募集资金来支持光伏产品的一个在线众筹平台,会员可以获得与支持金额相当的回报。
功能模块: 
【管理员登录】【权限管理】 【业务审核】【业务管理】【参数管理 】
【注册中心】【会员中心】【资产总览】【发起项目】【项目维护】【发起订单】【支付】
责任描述:
1【权限管理】实现了在后台权限管理模块中用户维护、角色维护、菜单维护三部分的分页展示以及关键字查询、新增、删除、修改操作,
以ajax交互方式实现数据的增删改查
其中角色维护引进了RBAC权限模型进行权限控制。
通过将用户具备的角色权限与受保护的资源所要求具备的对应权限进行比对
角色关联:给用户分配role--给role分配auth--给菜单分配auth
以树形结构展示:
控制层:查询全部对象--声明一个变量存储根节点--遍历查询到的list集合--获取遍历到的对象的parent_id属性值--检查parent_id是否为空--若为空,则当前对象就为父节点
2【业务审核】
3【发起项目】项目信息表单页提交--跳转到收益信息表单页--收益信息确认并保存分类信息   
开发环境: jdk1.8  maven3.6 Idea
技术选型:
前端:HTML CSS JavaScript VUE
后端:Maven SpringBoot SSM MyBatis Plus SpringCloud-Nefie SpringSecurity  Lombok  Redis LogBack 
第三方接口:蚂蚁金服  阿里云sms短信服务  OSS对象存储服务
ZooKeeper分布式协调服务的框架,解决系统一致性
目录树方式的数据存储
选举机制:1个主节点leader:调度者,处理写操作
--follower1--follower2:处理客户端非事务读的操作
特性:全局数据一致性、可靠性、顺序性、数据更新原子性、实时性

github项目搜索
in:name xxx // 按照项目名搜索
in:readme xxx // 按照README搜索
in:description xxx // 按照description搜索

您好,我叫刘卿,2019年研究生毕业,最近一份工作是在浙江碳银从事java开发这一岗位,工作的主要内容就是一些业务模块的需求实现,在这家公司我参与了两个项目的开发,第一个项目是一个光伏产品众筹平台,它的后台系统是基于SSM整合的单一架构,前台系统是基于SpringBoot+SpringCloud的微服务架构实现的,我主要负责后台的权限管理模块,前台系统的首页展示以及发起项目模块;第二个项目是一个基于SpringCloud-Alibaba微服务分布式的光伏贷系统,主要负责了五个模块的功能实现;在我作为java程序员的这期间,让我能够快速理解业务需求,擅于使用框架、插件等技术,工作之余也学习了JVM调优的相关知识,我的性格比较沉稳,改bug的时候有耐心,平时有不断学习,提升自己的意识,今天是来面试公司的java开发岗位,以上就是我的自我介绍。
基本数据类型:数据之间存储在栈上
引用数据类型:数据存储在堆上,栈上只存储引用地址
try catch finally 的基础用法,在 return 前会先执行 finally 语句块
join()用于等待当前线程终止
线程池的核心属性
对这些JVM认为已经“死掉”的对象进行垃圾收集,新生代使用复制算法,老年代使用标记-清除和标记-整理算法。
;rabbitmq的三大作用:流量消峰、应用解耦、异步处理;
四大核心:生产者、消费者、队列、交换机
一个连接有多个信道
rabbitmq的消息应答机制,保证消息不丢失;分为自动应答和手动应答
持久化表示存储到磁盘
默认轮训分发消息chanel.basicQos(0);
设置不公平分发(能者多劳)chanel.basicQos(1);
设置队列持久化、队列中的消息持久化、发布确认(MQ收到消息告诉生产者:单个确认、批量确认、异步确认)
死信队列机制:死信发生的原因:消息存活时间过期、队列满了、消息被拒绝
延迟队列:未在指定时间内处理则自动作废
普通队列里面设置私信交换机、死信routingKey
添加延迟插件:基于交换机延迟
备份交换机优先级高于回退回调
推荐利用redis的原子性来完成幂等性保障
优先级队列0-255:越大越先执行

redis是单线程+多路IO复用技术,避免了多线程的切换和竞争,让CPU发挥出最大的效能
redis事务的主要作用:串联多个命令防止别的命令插队
事务执行过程:mutil组队阶段-->exec执行阶段-->discard放弃组队
组队有失败则整体失败不提交;执行阶段有失败不会影响其他命令操作
事务的三大特性:单独的隔离操作、没有隔离级别的概念、不保证原子性
持久化:RDB每隔一段时间将数据集写入磁盘,开启一个子进程写临时文件,最后一次持久化可能会造成数据丢失;AOF以日志的形式记录每个写操作,有性能压力
同时开启是以AOF数据为准的
主从复制:读写分离(主以写为主),性能扩展
复制原理:1.当从服务器连上主服务器之后,主动向主服务器发送进行数据同步消息;
               2.当主服务器连接到从服务器发送过来的同步消息,会先把数据进行持久化。rdb文件发送给从服务器,从服务器拿到rdb进行读取;
                3.每次主服务器进行写操作后,和从服务器进行数据同步
并发写操作:创建集群来扩容
缓存穿透(黑客攻击):应用服务器访问压力过大,会发生瘫痪,redis的命中率降低,一直查询数据库 :空值也做缓存,设置可访问名单;
缓存击穿:热门访问的某个key过期了,数据库崩溃:预先设置、实时调整过期时长
缓存雪崩:访问的大量key过期了:构建多级缓存架构、均匀分散key的时效

jdk1.8的新特性:Lambda表达式、default关键字(所有的实现类都不需要去实现就可以直接调用)、函数式接口、DateApi更新
数据库相关:
索引是存储到磁盘中 的,但是会预先加载到内存中,且在进行加载时是分页加载
使用B+Tree:千万级别查询最多3-4层,,最后一行叶子节点记录整行数据;索引字段key值要尽可能的少占用空间,磁盘块尽可能的大
数据和索引放在一起的就是聚簇索引;反之是非聚簇索引;innodb:两者都有,myisam只有非聚簇索引
一张表无论有多少索引,都只存储一份;
回表:效率不高,Io的次数会变多 
索引覆盖:效率高,只查询索引字段
最左匹配:在联合索引中,
索引下推:多个条件可以直接从存储引擎中拉取结果
线程池的执行流程:核心线程是否已满--任务队列是否已满--最大线程数是否达到--根据拒绝策略处理任务
阻塞队列:保证任务队列中没有任务时阻塞获取任务的线程,自带阻塞和唤醒的功能,不至于一直占用CPU;
线程复用:同一个线程可以执行不断获取的新任务
BeanFactory:是延迟加载来注入bean的,使用到某个bean时才会进行实例加载
ApplicationContext:是BeanFactory的子接口,在容器启动时一次性创建了所有的Bean
spring bean的生命周期:从创建Spring容器开始,直到最终Spring容器销毁Bean
spring事务为什么会失效:发生自调用、方法不是被public修饰的、数据库不支持事务、没有被spring管理、异常被吃掉事务不会回滚

工作原理:
第一步:通过 HashMap 自己提供的hash 算法算出当前 key 的hash 值;
第二步:通过计算出的hash 值去调用 indexFor 方法计算当前Entry对象应该存储在数组的几号位置
第三步:判断size(当前容器中已有 Entry 的数量)是否已经达到了当前阈值,如果没有,继续;如果已经达到阈值,则先进行数组扩容,将数组长度扩容为原来的2倍。
第四步:将当前对应的 hash,key,value封装成一个 Entry,去数组中查找下标有没有元素,如果没有,放在这个位置上;如果此位置上已经存在链表,那么遍历链表,如果链表上某个节点的 key 与当前key 进行 equals 比较后结果为 true,则把原来节点上的value 返回,将当前新的 value替换掉原来的value,如果遍历完链表,没有找到key 与当前 key equals为 true的,就把刚才封装的新的 Entry中next 指向当前链表的始节点,也就是说当前节点现在在链表的第一个位置,简单来说即,先来的往后退。我们已经将当前的 key-value 存储到了容器中。
spring:ApplicationContext()启动容器
循环依赖:
一级缓存:是单例池,所有完整的bean;
二级缓存:为了解决循环依赖,以及能够解决并发条件下获取完整bean

synchroized锁:重量级(依赖性较高)锁,本质是将多线程变为单线程,但是java是应用层(用户态),不能直接操作线程,是依赖操作系统内核(内核态)提供的接口;
这个锁会根据运行环境进行一个锁变更,偏向锁(单线程环境)-->CAS自旋锁(轻度竞争,无线程阻塞,线程交替执行)-->重量锁(重度竞争,锁的竞争超过了上下文的切换)

nacos源码精髓:这个更新注册表内存方法里,为了防止读写并发冲突,大量的运用了CopyOnWrite思想防止,具体做法就是把原内存结构复制一份,操作完成后再替换回真正的注册表内存里面去;异步注册逻辑,

上一篇:ES基本命令使用


下一篇:MFC窗口风格 WS_style/WS_EX_style(超详细)