springboot

springboot

函数式编程

1. 概念

主要强调对数据进行了什么操作,而不需要关心以何种形式实现的

2. 优点

大数量下处理集合效率高

代码的可读性高

消灭嵌套地狱

开发迅速,易于理解

易于"并发编程"

3. lambda

  1. 将符合条件的匿名内部类进行改造,只留下参数和方法体,并使用->进行连接,参数使用(),方法体使用{}

  2. 下面是关于lambda的省略规则

​ 参数类型可以省略。如果有多个参数的情况下,不能只省略一个

​ 如果参数有且仅有一个,那么小括号可以省略

​ 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return

  1. 与匿名内部类的区别

​ 1. 所需类型不同:

​ 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类

​ Lambda表达式:只能是接口(接口中有且仅有一个抽象方法)

​ 2. 使用限制不同:

​ 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类

​ 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式

​ 3. 实现原理不同:

​ 匿名内部类:编译之后,产生一个单独的.class字节码文件

​ Lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行的时候动态生成

4. 代码示例

效果一样,但是却可以省略很多代码,并且代码更加简洁,可以使用alt+enter来进行lambda和带有普通匿名内部类之间的转换

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是新线程");
        }
    }).start();
    //使用lambda表达式
    new Thread(()->{
        System.out.println("我是新线程");
    }).start();
}
1. 带有参数的lambda表达式
public interface Converter {
    int convert(String s);
}
public static void main(String[] args) {
    test(new Converter() {
        @Override
        public int convert(String s) {
            return Integer.parseInt(s);
        }
    });
    //使用lambda表达式,并进行省略以后,代码简介,而且效果一样
    test(s->Integer.parseInt(s));
}
public static void test(Converter converter){
    int convert = converter.convert("1234");
    System.out.println(convert);
}
2. 两个参数的lambda表达式
public interface Converter {
    int convert(int a,int b);
}
public static void main(String[] args) {
    test(new Converter() {
        @Override
        public int convert(int a, int b) {
            return a+b;
        }
    });
    //使用lambda表达式,并进行省略以后,代码简介,而且效果一样
    test((a,b)->a+b);
}
public static void test(Converter converter){
    int convert = converter.convert(2, 3);
    System.out.println(convert);
}
3. 方法的引用

::称为引用运算符,而::所在的表达式称为方法引用符号,在被自动推导以后,可以进行省略

public interface Converter {
    void converter(String s);
}
public static void main(String[] args) {
  test(new Converter() {
      @Override
      public void converter(String s) {
          System.out.println(s);
      }
  });
  //使用lambda表达式
  test(s-> System.out.println(s));
  //使用方法引用,可以写成如下格式
  test(System.out::println);
}
public static void test(Converter converter){
    converter.converter("hello");
}
4. 调用静态方法
public interface Converter {
    int converter(String s);
}
public static void main(String[] args) {
  test(new Converter() {
      @Override
      public int converter(String s) {
          return Integer.parseInt(s);
      }
  });
  //使用lambda表达式
  test(s->Integer.parseInt(s));
  //使用方法引用
  test(Integer::parseInt);
}
public static void test(Converter converter){
    int converter1 = converter.converter("12345");
    System.out.println(converter1);
}
5. 引用对象实例方法
public interface Printer {
    void printUpperCase(String s);
}
public class PrintString {
    public void printUpper(String s){
        String result = s.toUpperCase();
        System.out.println(result);
    }
}
    public static void main(String[] args) {
//一般情况下使用lambda表达式
        usePrinter(s -> System.out.println(s.toUpperCase()));
//可以调用对象的方法实现
        PrintString printString = new PrintString();
        usePrinter(new Printer() {
            @Override
            public void printUpperCase(String s) {
                printString.printUpper(s);
            }
        });
//通过上述调用对象的方法演变为如下方法
        usePrinter(s->printString.printUpper(s));
//上述方法可以通过使用引用运算符进一步简化为
  usePrinter(printString::printUpper);
    }

    private static void usePrinter(Printer printer) {
        printer.printUpperCase("HelloWorld");
    }
6. 调用类的方法
public interface MyString {
    String mySubString(String s, int x, int y);
}
    public static void main(String[] args) {
//使用匿名内部类
        useMyString(new MyString() {
            @Override
            public String mySubString(String s, int x, int y) {
                return s.substring(x, y);
            }
        });
//使用lambda表达式进行简化
        useMyString((s, x, y) -> s.substring(x, y));
//使用引用运算符进一步简化
        useMyString(String::substring);
    }

    private static void useMyString(MyString myString){
        String s = myString.mySubString("HelloWorld", 5, 10);
        System.out.println(s);
    }
7.引用构造器
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String address;
}
public interface UserBu {
    User build(int id,String name,String address);
}
    public static void main(String[] args) {
//初始的匿名内部类方式
        oo(new UserBu() {
            @Override
            public User build(int id, String name, String address) {
                return new User(id, name, address);
            }
        });
//通过使用lambda表达式进行简化
        oo((id,name,address)->new User(id,name,address));
//通过使用引用运算符进行进一步简化
        oo(User::new);
    }
    public static void oo(UserBu userBuilder) {
        User build = userBuilder.build(1, "张三", "北京");
        System.out.println(build);
    }
8.函数式接口

@FunctionalInterface用来检测是否是函数式接口的注解,如果某个接口上有此注解,但是此注解并不符合函数式接口的条件,则编译报错,在自定义接口的时候,如果接口满足函数式编程的条件,也可以进行函数式编程,但是建议加上此注解

@FunctionalInterface
public interface MyInterface {
    void show();
}
public static void main(String[] args) {
    //使用普通的匿名内部类的方式创建接口对象
    MyInterface myInterface = new MyInterface() {
        @Override
        public void show() {
            System.out.println("hello");
        }
    };
    //使用lambda表达式创建接口对象
    MyInterface myInterface1 = ()->System.out.println("hello1");
    myInterface.show();
}
9. 函数式接口作为方法的参数
public static void main(String[] args) {
//使用匿名内部类的方式
        test(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"启动了");
            }
        });
//使用lambda方式
        test(()-> System.out.println(Thread.currentThread().getName()+"启动了"));
    }
    public static void test(Runnable run){
        new Thread(run).start();
    }
10. 函数式接口作为方法的返回值
public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<String>();

        arrayList.add("ccc");
        arrayList.add("aa");
        arrayList.add("dddd");
        arrayList.add("b");
        System.out.println("排序前" + arrayList);
        Collections.sort(arrayList);
        System.out.println("排序后" + arrayList);
        Collections.sort(arrayList, getComparator());
        System.out.println("使用定义比较器排序方法后:" + arrayList);
    }

    private static Comparator<String> getComparator() {
//        return new Comparator<String>() {
//            @Override
//            public int compare(String s1, String s2) {
//                return s1.length() - s2.length();
//            }
//        };
        return (s1,s2) -> s1.length() - s2.length();
    }
11. 常用的函数式接口

​ Supplier接口

​ Consumer接口

​ Predicate接口

​ Function接口

  1. Supplier接口

    Supplier 接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用

        public static void main(String[] args) {
            String s = null;
    //匿名内部类方式
            s = getString(new Supplier<String>() {
                @Override
                public String get() {
                    return "xuanxuan1";
                }
            });
    //使用lambda方式
            s = getString(() -> "xuanxuan");
            System.out.println(s);
    //匿名内部类方式
            Integer i = null;
            i = getInteger(new Supplier<Integer>() {
                @Override
                public Integer get() {
                    return 6666;
                }
            });
    //lambda方式
            i = getInteger(() -> 666);
            System.out.println(i);
        }
    
        public static String getString(Supplier<String> supplier) {
            return supplier.get();
        }
    
        public static Integer getInteger(Supplier<Integer> supplier) {
            return supplier.get();
        }
    
    1. Consumer接口

    Consumer 接口也被称为消费型接口,它消费的数据类型由泛型指定

     public static void main(String[] args) {
    //使用匿名内部类进行操作
            operatorString("aaa", new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
    //        使用lambda表达式进行操作
            operatorString("aaaaa",s->System.out.println(s));
    //        使用引用运算符进行操作
            operatorString("aaaaaaaaaa",System.out::println);
            operatorString("aaAA", s->System.out.println(s.toLowerCase()),
                    s-> System.out.println(s.toUpperCase()));
        }
    
        private static void operatorString(String name, Consumer<String> consumer) {
            consumer.accept(name);
        }
    
        private static void operatorString(String name, Consumer<String> consumer1, Consumer<String> consumer2) {
    //andThen是将两个接口连到一起进行消费效果等同于如下
    //        consumer1.accept(name);
    //        consumer2.accept(name);
            consumer1.andThen(consumer2).accept(name);
        }
    
    1. Predicate接口

    predicate接口的方法以及作用

    boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值

    default Predicate negate():返回一个逻辑的否定,对应逻辑非

    default Predicate and (Predicate other):返回一个组合判断,对应短路与

    default Predicate or (Predicate other):返回一个组合判断,对应短路或

     public static void main(String[] args) {
            boolean b1 = checkString("hello", s -> s.length() > 5);
            System.out.println(b1);
    
            boolean b2 = checkString("helloworld", s -> s.length() > 8);
            System.out.println(b2);
    
            boolean b3 = checkString("hello", s -> s.length() > 5, s -> s.length() > 8);
            System.out.println(b3);
    
            boolean b4 = checkString("helloworld", s -> s.length() > 5, s -> s.length() > 8);
            System.out.println(b4);
        }
    
        private static boolean checkString(String s, Predicate<String> predicate) {
            return predicate.test(s);
        }
    
        private static boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
    //        return predicate.and(predicate2).test(s);
            return predicate.or(predicate2).test(s);
        }
    
    1. Function接口

    R apply(T t):将此函数应用于给定的参数。

    default Function<T,V> andThen(Function after):返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。

    public static void main(String[] args) {
        convert("100", s -> Integer.parseInt(s));
        convert("100", Integer::parseInt);
    
        convert(100, i -> String.valueOf(100 + i));
    
        convert("100", s -> Integer.parseInt(s), i -> String.valueOf(i + 566));
    
    }
    
    //定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
    private static void convert(String s, Function<String, Integer> function) {
        Integer i = function.apply(s);
        System.out.println(i);
    }
    
    //定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
    private static void convert(int i, Function<Integer, String> function) {
        String s = function.apply(i);
        System.out.println(s);
    }
    
    //定义一个方法,把一个字符串转换为int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出
    private static void convert(String s, Function<String, Integer> function1, Function<Integer, String> function2) {
        String ss = function2.apply(function1.apply(s));
        System.out.println(ss);
    }
    
12. Stream流

生成流:通过数据源(集合、数组等)生成流

Collection体系的集合可以使用默认方法stream()生成流

Map体系的集合间接的生成流

数组可以通过Stream接口的静态方法of(T…values)生成流

List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();

Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();

Map<String, Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

String[] strArray = {"hello", "world", "java"};
Stream<String> strArrayStream = Stream.of(strArray);
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> strArrayStream3 = Stream.of(10, 20, 30);
1. 中间操作

一个流后面可以跟随零个或者多个中间操作,其目的主要是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用

Stream filter(Predicate predicate):用于对流中的数据进行过滤

Predicate接口中的方法:boolean test(T t):对给定的参数进行判断,返回一个布尔值

Stream limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据

Stream skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流

Stream Stream concat(Stream a,Stream b):合并a和b两个流为一个流

Stream distinct:返回由该流的不同元素(根据Objectequals(Object))组成的流

Stream sorted():返回由此流的元素组成的流,根据自然顺序排序

Stream sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序

Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流(Function接口中的方法 R apply(T t))

IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果

ArrayList<String> list = new ArrayList<String>();
list.add("张飞");
list.add("张三丰");
list.add("张三");
list.add("李四");
list.add("孙悟空");
list.add("张一飞");
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
System.out.println("----------------------");
list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
System.out.println("----------------------");
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
2. 终结操作

终结操作:一个流只能有一个终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作

void forEach(Consumer action):对此流的每个元素执行操作(Consumer接口中的方法 void accept(T t):对给定的参数执行此操作)

long count():返回此流中的元素数

ArrayList<String> list = new ArrayList<String>();
        list.add("张飞");
        list.add("张三丰");
        list.add("张三");
        list.add("李四");
        list.add("孙悟空");
        list.add("张一飞");

        //需求1:把集合中的元素在控制台输出
        list.stream().forEach(System.out::println);

        //需求2:统计集合中有几个姓张的元素并在控制台输出
        list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
3. stream流的收集操作

对数据使用Stream流的方式操作完毕后,如何把流中的数据收集到集合中?

Stream流的手机方法

R collect(Collector collector)

但是这个收集方法的参数是一个Collector接口

工具类Collectors提供了具体的收集方式

public static Collector toList():把元素收到List集合中

public static Collector toSet():把元素收集到Set集合中

public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    list.add("张飞");
    list.add("张三丰");
    list.add("张三");
    list.add("李四");
    list.add("孙悟空");
    list.add("张一飞");

    //需求1:得到名字为3个字的流
    Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
    //需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
    List<String> collect = listStream.collect(Collectors.toList());
    for (String s : collect) {
        System.out.println(s);
    }

    Set<Integer> set = new HashSet<Integer>();
    set.add(10);
    set.add(20);
    set.add(30);
    set.add(33);
    set.add(35);

    //需求3:得到年龄大于25的流
    Stream<Integer> integerStream = set.stream().filter(age -> age > 25);

    //需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
    Set<Integer> collect2 = integerStream.collect(Collectors.toSet());
    for (Integer i : collect2) {
        System.out.println(i);
    }

    String[] strArray = {"张飞,28", "张三丰,33", "张三,26", "李四,44"};

    //需求5:得到字符串年龄中数据大于28的流
    Stream<String> stringStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);

    //需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串的姓名作为键,年龄作为值
    Map<String, Integer> map = stringStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
    Set<String> keySet = map.keySet();
    for (String key : keySet) {
        Integer value = map.get(key);
        System.out.println(key + "," + value);
    }
}

resource资源

1. webapp下的资源读取

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ij3Nw6BH-1689319751852)(C:\Users\wanguoyin\AppData\Roaming\Typora\typora-user-images\image-20230712150748839.png)]

    @Resource
    ResourceLoader resourceLoader;
    
    @Test
    public void test1() throws IOException {
        //加载webapp的resources下的文件
        InputStream inputStream2 = resourceLoader.getResource(web).getInputStream();
        contextLoads(inputStream2,"resources/test.txt");

    }

2. 项目resources下的资源读取

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFdKYqUu-1689319751853)(C:\Users\wanguoyin\AppData\Roaming\Typora\typora-user-images\image-20230712150806173.png)]

在springboot中使用资源加载器读取资源

    @Resource
    ResourceLoader resourceLoader;
    
    @Test
    public void test1() throws IOException {

        //可以加载项目resource下的文件
        InputStream inputStream1 = resourceLoader.getResource("classpath:/test.txt").getInputStream();
    }

ffmpeg使用

1. 安装X264

如果使用H.264进行编码,就需要先安装X264,此处我们在linux上部署ffmpeg和X264,在windows上部署X264比较麻烦,如果单纯的使用ffmpeg,跳过这一步,直接安装ffmpeg就可以了

1.操作yum

基于yum安装比较方便,所以我们可以先在电脑上安装yum,如果已经安装的可以跳过此步骤

  1. 下载yum压缩包

    wget http://yum.baseurl.org/download/3.2/yum-3.2.28.tar.gz tar xvf yum-3.2.28.tar.gz 
    
  2. 安装

    cd yum-3.2.28
    sudo apt install yum
    
  3. 更新yum

    sudo yum update
    
2. 安装Git

从git上下拉X264需要安装git,如果执行下面命令出错,就需要更新yum源,使用安装yum的第三步

yum -y install git
3. 安装X264
  1. 使用git下载X264

    git clone https://git.videolan.org/git/x264.git
    
  2. 编译

     ./configure --enable-shared --enable-static --prefix=/usr
    

    如果报错为

    [root@localhost x264]# ./configure --prefix=/usr/softinstall/x264/ --enable-static --enable-shared
                 Unknown option --enable-share, ignored Found no assembler Minimum > version is nasm-2.13
                 If you really want to compile without asm, configure with --disable-asm.
    

    需要安装nasm

    wget https://www.nasm.us/pub/nasm/releasebuilds/2.14/nasm-2.14.tar.gz
    ./configure
    make
    make install
    
  3. 安装

    make
    sudo make install
    
  4. 验证

    x264 -V
    
4. 安装ffmpeg
  1. 如果已经安装了ffmpeg,然后安装了X264,就需要重新安装ffmpeg,此时我们可以进行如下操作

    # 进入到ffmpeg文件夹
    cd ffmpeg-5.1/
    # 执行卸载操作
    make uninstall
    
  2. 如果之前没有安装过ffmpeg,则直接从此步骤开始,下载ffmpeg,解压ffmpeg

    wget http://www.ffmpeg.org/releases/ffmpeg-5.1.tar.gz
    tar -zxvf ffmpeg-5.1.tar.gz 
    
  3. 执行安装

    cd ffmpeg-5.1
    ./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-libx264
    make
    make install
    
  4. 执行切片任务

    ffmpeg -i /home/aaaa.mp4 -c:v libx264 -c:a copy -f ssegment -segment_format mpegts -segment_list /home/test.m3u8 -segment_time 10 /home/aaaa/test%05d.ts
    

    如果不使用x264编码,可以将参数-c:v libx264改成-c:v copy

    ffmpeg -i /home/aaaa.mp4 -c:v copy -c:a copy -f ssegment -segment_format mpegts -segment_list /home/test.m3u8 -segment_time 10 /home/aaaa/test%05d.ts
    

2. 对切片视频进行播放

1. 前端操作
  1. 首先需要加载hls库

    <script src="https://dns.lmwa.cn/npm/hls.js/1.1.5/dist/hls.min.js"></script>
    
  2. 使用video标签

    <video id="video"  width="500" height="400" controls="controls"></video>
    
  3. 使用hls加载资源

    <script>
        const video = document.getElementById('video');
        function play(){
            var hls = new Hls();
            // 此处资源地址因为测试用,所以是写死的,使用了本机测试,可以按照需要更改此处的参数
            hls.loadSource("/20220101/aaaa/test.m3u8");
            hls.attachMedia(video);
        }
        play();
    </script>
    
  4. 注意此处不能使用file协议,需要使用http协议,也就是说此处需要使用http请求,否则会出现跨域问题,例如上述,最终的请求是http://localhost:8080/20220101/aaaa/test.m3u8,如果通过网络查看时file://D:video/20220101/aaaa/test.m3u8就会出现跨域问题

  5. 完整html

    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://dns.lmwa.cn/npm/hls.js/1.1.5/dist/hls.min.js"></script>
    </head>
    <body>
    <video id="video"  width="500" height="400" controls="controls"></video>
    </body>
    <script>
        const video = document.getElementById('video');
        function play(){
            var hls = new Hls();
            // 此处资源地址因为测试用,所以是写死的,使用了本机测试,可以按照需要更改此处的参数
            hls.loadSource("/200/aaaa/test.m3u8");
            hls.attachMedia(video);
        }
        play();
    </script>
    </html>
    
    
2. 后端操作

后端需要将此目录设置为web静态资源目录,然后通过读取m3u8文件,就可以播放视频了

在application.yml文件中添加如下配置

spring:
	web:
		resources:
			static-locations:
				- "classpath:/static/"
				- "file:D://video"

启动服务,打开网页,请求即可

POM.xml文件

1. <packaging>解析

  1. 当packaging值为pom的时候,此pom文件是被继承的文件
  2. 当packaging值为jar的时候,此项目会被打成jar包
  3. 当packaging值为war的时候,此项目会被打成war包,可以在tomcat上部署

2. <dependencyManagement>和<dependencies>

这两个都是对项目依赖进行管理的,只不过区别主要在<dependencyManagement>只对依赖版本做要求,子模块或者子工程是不会继承依赖的,而会对依赖进行传递,父项目引入了依赖,子项也会引入相应的依赖

3. 如何使用打包插件

通常我们创建spring boot项目的时候,需要考虑打包的问题,只有被引用的pom文件和带有启动类的模块才会有maven的打包插件,下面是springboot继承maven打包插件,但是默认的插件打成的jar包启动会有一点问题,比如找不到主清单属性,此时我们需要对默认文件做一些调整

  1. 去掉configuration下的skip标签,此标签代表会跳过设置启动类
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.demo.DemoApplication</mainClass>
                    <!--打包找不到主清单需要将skip去掉-->
                    <!--<skip>true</skip>-->
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  1. 如果是父项目,只需要保留下面的内容即可
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
            </plugin>
        </plugins>
    </build>

4. 对资源进行扫描

我们有时候在项目中会有一些资源找不到的情况,在编译后的文件夹中发现这些文件并未编译进来,此时需要执行资源编译的文件夹

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
        <!--将java下的所有文件全部编译-->
            <include>**/**</include>
        </includes>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
        <!--将resources下的所有文件全部编译-->
            <include>**/**</include>
        </includes>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/webapp</directory>
        <!--编译完成以后将jsp文件放到如下路径下才可以访问得到 -->
        <targetPath>META-INF/resources</targetPath>
        <includes>
            <!-- 将webapp下的所有文件全部编译 -->
            <include>**/**</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

5. 有关依赖引入的问题

  1. 编译的时候有如下警告,说明此依赖已经搬迁,需要重新导入依赖文件,下面有新老版本的依赖对比

    [WARNING] The artifact mysql:mysql-connector-java:jar:8.0.31 has been relocated to com.mysql:mysql-connector-j:jar:8.0.31
    
    

    修改之前的版本

    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<version>${jdbc.version}</version>
    </dependency>
    

    修改之后的版本

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>${jdbc.version}</version>
    </dependency>
    

MyBatis

1. 简介

Java常见的访问数据库的方式有三种:JDBC、Hibernate、MbBatis,后两种都是在第一种的基础上实现的,JDBC是由SUN(SUN公司后来被Oracle公司收购)公司提出的接口规范,由各数据库厂商负责是实现。

1. JDBC

Jdbc连接方式主要流程有以下五部分

  1. 注册数据库和驱动信息

  2. 操作Connection打开Statement对象

  3. 使用Statement执行SQL,将结果返回ResultSet中

  4. 将ResultSet中的数据信息封装到POJO(简单java对象)中

  5. 关闭连接资源

使用JDBC连接数据库的缺点很明显,连接步骤比较繁琐,异常不好处理,返回数据处理也比较麻烦,资源的关闭等等都需要考虑

注释:POJO(Plain Ordinary Java Object)简单java对象,没有继承、实现、其他框架入侵的,只有无参构造和setter和getter方法的java类

2. ORM模型

ORM(Object Relational Mapping)对象关系映射,主要是为了解决数据库表中的字段和java对象对象属性的映射问题,通过此模型可以更快的将数据库表中的数据转化为POJO,以提高开发效率,降低维护难度,节省维护成本

3. Hibernate

在对象关系映射和POJO的基础上,Hibernate诞生了,对SQL语句高度封装,通过xml文件全表映射,使我们通过POJO可以直接操作数据库,缺点也很明显,性能比较差,灵活性也比较差等。

4. MyBatis

Mybatis称为半自动映射框架,因为要提供POJO,SQL和映射关系,MyBatis前身是Apache的一个开源项目iBatis,在2013年11月迁移到GitHub,之后由GitHub进行维护

2. 清单文件

MyBatis清单文件有两部分,一部分作为MyBatis的核心配置文件,包括连接信息,类和配置文件之间的对应信息,日志信息等,另外一部分是Mapper映射清单,内容包括接口和清单映射信息,和实体类映射信息,SQL语句等内容

1. 配置清单文件
<!--environments:配置多个连接数据库的环境 属性:default:设置默认使用的环境的id -->
    <environments default="development">
    <!--environment:配置某个具体的环境 属性:id:表示连接数据库的环境的唯一标识,不能重复 -->
        <environment id="development">
            <!--transactionManager:设置事务管理方式 属性:type="JDBC|MANAGED" JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事 务的提交或回滚需要手动处理MANAGED:被管理,例如Spring -->
            <transactionManager type="JDBC"/>
            <!--dataSource:配置数据源 属性:type:设置数据源的类型 type="POOLED|UNPOOLED|JNDI" POOLED:表示使用数据库连接池缓存数据库连接 UNPOOLED:表示不使用数据库连接池 JNDI:表示使用上下文中的数据源 -->
            <dataSource type="POOLED">
                <!--设置连接数据库的驱动-->
                <property name="driver" value="${jdbc.driver}"/>
                <!--设置连接数据库的连接地址-->
                <property name="url" value="${jdbc.url}"/>
                <!--设置连接数据库的用户名-->
                <property name="username" value="${jdbc.username}"/>
                <!--设置连接数据库的密码-->
                <property name="password" value="${jdbc.password}"/>
            </dataSource> </environment>
        <environment id="test">
        <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ssmserverTimezone=UTC"/>
        <property name="username" value="root"/> <property name="password" value="123456"/>
    </dataSource> 
        </environment>
    </environments>
2. 映射清单文件
<?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="mapper接口路径">
    <resultMap type="实体类路径" id="xxxxMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
    </resultMap>
    <sql id="Base_Column_List">
        id
    </sql>
</mapper>

3. 基础应用

1. MyBatis环境搭建
  1. 配置依赖项

可以通过mybatis官网进行jar包下载,现在的大多数项目都使用maven进行依赖管理,只需要将mybatis的依赖添加到pom.xml文件中就可以了

依赖搜索网址:https://mvnrepository.com/

MyBatis常用的依赖主要有以下几个

<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--DM驱动-->
<!--        <dependency>-->
<!--            <groupId>com.dameng</groupId>-->
<!--            <artifactId>Dm8JdbcDriver18</artifactId>-->
<!--            <version>8.1.1.49</version>-->
<!--        </dependency>-->
<!--mybatis集成springboot依赖-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
<!--分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>
<!--mybatisPlus依赖如果使用mybatisPlus的话,需要去掉mybaits的依赖-->
<!--        <dependency>-->
<!--            <groupId>com.baomidou</groupId>-->
<!--            <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!--            <version>3.5.3</version>-->
<!--        </dependency>-->

<!--此项用于编译的时候将制定目录下的properties和xml格式文件都进行编译-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory><!--所在的目录-->
            <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>
2. Springboot配置文件application.yml

Springboot中的application.yml配置主要有以下配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?allowPublicKeyRetrieval=true&allowMultiQueries=true
&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123

mybatis:
mapper-locations: classpath:mappers/*.xml

如果连接达梦数据库,更改数据源配置

spring:
  datasource:
          #达梦数据库driver类
    driver-class-name: dm.jdbc.driver.DmDriver
          #连接池
    url: jdbc:dm://127.0.0.1:5236/DM_DB?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
          #数据库账户
    username: SYSDBA
          #登录密码
    password: SYSDBA
mybatis-plus:
  mapper-locations: classpath:mapper/**.xml

对于url后的部分参数进行解析

useSSL=false:false使用账号密码机型验证,如果是true则使用ssl认证,虽然安全性更高,但是在跨平台的时候容易出现问题,所以一般设置为false

allowPublicKeyRetrieval:允许从服务端获取公钥

allowMultiQueries=true:允许携带分号,多sql语句执行

serverTimezone=Asia/Shanghai:时区设置为亚洲,上海时区

serverTimezone=GMT%2B8 :北京时间东八区

serverTimezone=Asia/Shanghai :上海时间

serverTimezone=Asia/Hongkong :香港时间

serverTimezone=UTC :世界标准时间,北京时间比UTC时间早8小时

useUnicode=true&characterEncoding=utf-8:存数据的时候,将项目来的数据使用utf-8解码,使用mysql的编码方式重新编码,然后放入数据库,从数据库取的时候,先使用数据库的编码格式解码,在使用utf-8进行编码,返回给项目

mapper-locations配置mapper的路径,如果在resources下创建了mappers则使用上述配置即可

映射清单在2.2中有模板

3. Mybatis逆向工程

添加生成工具插件

<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.2</version>
    <configuration>
        <verbose>true</verbose>
        <overwrite>true</overwrite>
    </configuration>
</plugin>

创建名称为generatorConfig.xml配置文件,有几处需要修改,

<classPathEntry location=此处要需要加入jdbc驱动的绝对路径

<jdbcConnection 四项连接信息可以参考application.yml配置文件内的连接信息

<javaModelGenerator 配置实体类生成位置

<sqlMapGenerator 配置mapper的xml文件生成位置

<javaClientGenerator 配置mapper接口的生成位置

<table tableName=“user” domainObjectName=“User” 配置表名和实体类名称对应关系

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="E:\java\repository\mysql\mysql-connector-java\5.1.9\mysql-connector-java-5.1.9.jar"></classPathEntry>
<!-- 配置 table 表信息内容体,targetRuntime 指定采用 MyBatis3 的版本 -->
<context id="tables" targetRuntime="MyBatis3">
<!-- 抑制生成注释,由于生成的注释都是英文的,可以不让它生成 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 配置数据库连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test"
userId="root"
password="123">
</jdbcConnection>
<!-- 生成model类,targetPackage指定domain类的包名, targetProject指定生成的domain放在IDEA的哪个工程下面-->
<javaModelGenerator targetPackage="com.example.springboot_001_demo_homework.domain"
targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="false" />
</javaModelGenerator>
<!-- 生成MyBatis的Mapper.xml文件,targetPackage指定mapper.xml文件的包名, targetProject指定生成的mapper.xml放在IDEA的哪个工程下面 -->
<sqlMapGenerator targetPackage="com.example.springboot_001_demo_homework.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成MyBatis的Mapper接口类文件,targetPackage指定Mapper接口类的包名, targetProject指定生成的Mapper接口放在IDEA的哪个工程下面 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.springboot_001_demo_homework.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 数据库表名及对应的 Java 模型类名 -->
<table tableName="student" domainObjectName="Student"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>

4. 日志配置

此处是mybatis的日志配置项,用来输出Sql执行的详细信息,如果是mybatis-plus,则需要将mybatis更换成mybatis-plus

#在springboot中使用mybatis,如果需要控制台打印日志,只要在配置文件中添加如下配置
#properties中
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#yml中
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5. 分页查询

在mybatis环境搭建的时候,在依赖项中添加了PageHelper依赖,此依赖为分页插件依赖使用方式,需要在查询语句的上一句添加PageHelper.startPage(pageNum, pageSize);其他的相关信息可以使用下面的语句进行获取,比如总条数

//此处pageNum是页码数,pageSize是每页数量
int pageNum = 1;
int pageSize = 3;
PageHelper.startPage(pageNum, pageSize);
List<User> list = userDao.queryAll();
//使用pageInfo可以查询一些详细的信息
PageInfo<User> pageInfo = new PageInfo<>(list);
//获取数据总条数
long total = pageInfo.getTotal();
//获取数据总页数
int pages = pageInfo.getPages();
//获取每页数量
int pageSize1 = pageInfo.getPageSize();
//获取当前页码
int pageNum1 = pageInfo.getPageNum();
//获取上一页页码
int prePage = pageInfo.getPrePage();
//获取下一页页码
int nextPage = pageInfo.getNextPage();
6. 初始化表

在springboot中添加配置执行sql文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qsJeWJQc-1689319751853)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps6.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fKcdFtkz-1689319751854)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps7.jpg)]

mode:属性有三个,always是每次启动都会执行,never是不执行,embedded还没搞明白啥意思

7. 常见问题及解决方案
1. 在mybatis的xml文件中使用模糊查询语句

使用concat拼接查询条件和占位符,以生成一条完整的查询语句

Select * from user where name like concat(‘%’,#{param},’%’)

2. 使用分页插件循环依赖问题

使用PageHelper插件,启动项目报错循环依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeobxJnS-1689319751854)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml53928\wps1.jpg)]

这个问题是版本不兼容,使用springboot2.6以后的版本,如果使用PageHelper1.4.0及以下,则会出现循环依赖的问题,再PageHelper1.4.1已经解决了这个问题

3. MyBatis的if标签对字符串的判断

当判断的对象为单个字符的时候,则需要进行特殊处理,通常使用了@Param以后,判断条件是需要添加指定的名称,此时的判断就如下操作,但是判断单个字符的时候需要转型操作

不需要进行特殊处理的

<if test = "user.name == 'zhangsan'">
    and name = 'zhangsan'
</if>

需要进行特殊处理的

<if test = "user.id == '2'">
    and id = 2
</if>

如果不处理会报错如下

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.NumberFormatException: For input string: "qqq"
### Cause: java.lang.NumberFormatException: For input string: "qqq"

处理方式有两种:

  1. 将引号进行更换
<if test = 'id == "2"'>
    and id = 2
</if>
  1. 进行字符和字符串之间进行转换
<if test = "id == '2'.toString()">
    and id = 2
</if>

Base64

使用base64可以将数据进行编码等操作,编码以后的数据可以交给浏览器进行识别或者进行存储主要分为编码和解码

public void sss() throws IOException {
    //加密使用
    Base64.Encoder encoder = Base64.getEncoder();
    //取得文件并转化成字节数组
    File file = new File("D:/addafeawf.png");
    FileInputStream fileInputStream = new FileInputStream(file);
    byte[] buffer = new byte[fileInputStream.available()];
    fileInputStream.read(buffer);

    //data: 文本数据
    //data: text/plain, ------- 文本数据
    //data: text/html, -------- HTML代码
    //data: text/html;base64, -------- base64编码的HTML代码
    //data: text/css, ---------- CSS代码
    //data: text/css;base64, ---------- base64编码的CSS代码
    //data: text/javascript, ------------ Javascript代码
    //data: text/javascript;base64, --------- base64编码的Javascript代码
    //data: image/gif;base64, ---------------- base64编码的gif图片数据
    //data: image/png;base64, -------------- base64编码的png图片数据
    //data: image/jpeg;base64, ------------- base64编码的jpeg图片数据
    //data: image/x-icon;base64, ---------- base64编码的icon图片数据
    //通常我们需要添加头,浏览器才能正确识别
    //data表示取得数据协定名称(不可更改),image/png表示数据类型,base64表示编码方式
    String header = "data:image/png;base64,";
    //转换成字符串
    String s = header+encoder.encodeToString(buffer);

    //解密使用
    Base64.Decoder decoder = Base64.getDecoder();
    //创建文件流
    File file1 = new File("D:/addafeawf1.png");
    file1.createNewFile();
    FileOutputStream out = new FileOutputStream(file1);

    //注意,如果不是传到浏览器,而是生成文件,则需要将编码去掉
    s = s.replace(header,"");
    //转换成字节数组并写出文件
    byte[] decode = decoder.decode(s);
    out.write(decode);
    out.flush();
}

Selenium

1. 环境搭建

1. 通过手动下载驱动的方式

下载地址

https://chromedriver.chromium.org/downloads

根据本机谷歌浏览器的版本下载驱动的版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qf6yfX45-1689319751855)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps1.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZWlhXMm-1689319751855)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps2.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fktrZKZz-1689319751855)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps3.jpg)]

将驱动解压复制到resources下即可

2. 通过pom文件中添加驱动管理器
<!--selenium依赖-->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.141.59</version>
</dependency>
<!--驱动管理器-->
<dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>webdrivermanager</artifactId>
    <version>5.0.3</version>
    <exclusions>
        <exclusion>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. 基础使用模板

//启动谷歌浏览器驱动,也可以使用ie,火狐,edge等浏览器进行操作
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
//获取网页
driver.get("http://www.baidu.com");
Thread.sleep(1000);
//退出网页
driver.quit();

4. 定位元素

//定位元素的方法有八种,尽量使用id,calss,name,tagName,再不济使用xpath,超链接使用linkText
//按照标签id进行定位
WebElement button1 = driver.findElement(By.id("app"));

//按照标签名称定位
WebElement button2 = driver.findElement(By.name("button"));

//按照标签类型(input,dev.......等等)定位
WebElement input1 = driver.findElement(By.tagName("input"));

//根据风格类名进行定位
WebElement input2 = driver.findElement(By.className("input-class"));

//使用文字进行定位,必须要完全匹配,不可以使用部分匹配,常用于超链接匹配,测试的时候,只有超链接可以定位到
WebElement href1 = driver.findElement(By.linkText("点击跳转百度"));

//对于linkTest的补充,可以使用模糊查询方式定位
WebElement href2 = driver.findElement(By.partialLinkText("跳转百度"));

//利用ccss进行定位,可以通过id,class,层级,兄弟节点等定位,下面是使用id进行定位
WebElement css = driver.findElement(By.cssSelector("#app"));

//使用xPath,使用绝对定位使用/,如果是相对定位使用//,比如
//绝对定位
WebElement xPath = driver.findElement(By.xpath("/html/body/dev[@id='app']"));
//相对定位
WebElement xPath1 = driver.findElement(By.xpath("//dev[@id='app']"));

5. 常用操作

//可以添加cookie
Cookie c = new Cookie("token", "aioefiajfeoaadofijaoefadfawe");
driver.manage().addCookie(c);
//如果有弹窗,可以使用此方法点击弹窗的确定按钮
driver.switchTo().alert().accept();
//如果有弹窗,可以使用此方法点击弹窗的取消按钮
driver.switchTo().alert().dismiss();
//可以通过sendKeys方法对输入框填充值
WebElement userName = driver.findElement(By.id("userName"));
userName.sendKeys("aaaa");
//可以通过click对按钮进行点击操作
WebElement login = driver.findElement(By.id("login"));
login.click();
//将焦点改为父框架
driver.switchTo().parentFrame();
//刷新当前页面
driver.navigate().refresh();

结巴分词

1. 添加依赖

<dependency>
    <groupId>com.huaban</groupId>
    <artifactId>jieba-analysis</artifactId>
    <version>1.0.2</version>
</dependency>

2. 创建词典

使用结巴分词,结巴分词的词典如果使用自定义的,则需要创建词典

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uFMYPOB6-1689319751855)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps4.jpg)]

词典的格式,(词,词频,词性),其中词和词频是必须要填写的,词性也填写上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2B4vNtI1-1689319751856)(file:///C:\Users\WANGUO~1\AppData\Local\Temp\ksohtml35988\wps5.jpg)]

3. 代码使用

public void jieba() {
    //第一种在resources下创建自定义分词,通过相对路径取出来
    Path path = Paths.get(new File( getClass().getClassLoader().getResource("dicts/jieba.dict").getPath() ).getAbsolutePath() ) ;
    //加载自定义的词典进词库
    WordDictionary.getInstance().loadUserDict( path ) ;

    //第二种通过绝对路径取出自定义词典
    Path path1 = Paths.get("D:/testClass/demo4/src/main/resources/static/jieba.txt");
    WordDictionary.getInstance().loadUserDict(path1);

    //上述两种是添加自定义词库,如果不需要自定义词库,则从此处开始执行即可
    JiebaSegmenter jiebaSegmenter = new JiebaSegmenter();
    //ss是我们要分词的数
    String ss = "规划查找最大概率路径, 找出基于词频的顶梁柱,无情哈拉少";
    
    //一、直接使用此方法可以使用默认分词,可以快速分词,对于空格等某些数据会规避掉
    List<String> list =
            jiebaSegmenter.sentenceProcess(ss);
    for (String s : list) {
        System.out.println(s);
    }
    //二、使用此种方法可以按照模式进行分词,分词比较精准
    //第一种模式JiebaSegmenter.SegMode.INDEX 将长度大于2的词继续拆分,按照下标精确拆分
    //第二种模式JiebaSegmenter.SegMode.SEARCH 精确查找,但是不会对长度大于2的词进行拆分
    List<SegToken> process = jiebaSegmenter.process(ss, JiebaSegmenter.SegMode.SEARCH);
    for (SegToken segToken : process) {
        System.out.println(segToken);
    }
}

Socket与WebSocket

1. Socket

一个完整的socket通常有三部分组成,负责连接,发送信息,接收信息,其中需要注意的是accept()等待连接的方法是会阻塞的,readObject()读取数据的方法也会阻塞,所以在创建连接的时候需要将getConnection方法设置为异步,否则其他方法访问会受到影响,GetMsg处于随时监听状态,可以将收到的流打印到屏幕,SetMsg处于随时监听键盘状态,可以将键盘输入的数据打印到输出到流,这两个方法中都有阻塞,所以都需要开启线程

@Component
public class SocketTest {
    private static List<Socket> list = new ArrayList<>();

    @Bean
    @Async
    public void getConnection() throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接......");
            Socket socket = serverSocket.accept();
            System.out.println("连接成功");
            list.add(socket);
            new GetMsg(socket).start();
            new SetMsg(socket).start();
        }
    }
}
class SetMsg extends Thread{
    private Socket socket;

    public SetMsg(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        ObjectOutputStream out;
        try {
            Scanner scanner = new Scanner(System.in);
            out = new ObjectOutputStream(socket.getOutputStream());
            while (true) {
                out.writeObject(scanner.nextLine());
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class GetMsg extends Thread{
    private Socket socket;

    public GetMsg(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        super.run();
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(socket.getInputStream());
            while (true) {
                System.out.println(ois.readObject());
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }


    }
}

2. WebSocket的使用方法

webSocket是一种web端的socket,可以为web端提供长连接,此处我们以做一个页面聊天来认识websocket,websocket主要分两部分,前端和后端的实现,在后端实现中需要添加依赖

  1. 添加依赖
<!--WebSocket-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 添加配置类
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
  1. 添加处理类,主要包括@OnOpen连接成功,@OnClose关闭连接,@OnMessage消息处理,@OnError错误时调用等四个部分
@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {
    //用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        System.out.println("有一连接关闭!当前在线人数为" + webSocketSet.size());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
        //群发消息
        broadcast(message);
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 群发自定义消息
     */
    public void broadcast(String message) {
        for (MyWebSocket item : webSocketSet) {
            if (item != this) {
                item.session.getAsyncRemote().sendText(message);
            }
        }
    }
}

  1. 前端
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>My WebSocket</title>
</head>
<body>
<button onclick="conectWebSocket()">连接WebSocket</button>
<button onclick="closeWebSocket()">断开连接</button>
<hr />
<br />
消息:<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;
    function conectWebSocket(){
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket'in window) {
            websocket = new WebSocket("ws://localhost:8080/websocket");
        } else {
            alert('Not support websocket')
        }
        //连接发生错误的回调方法
        websocket.onerror = function() {
            setMessageInnerHTML("error");
        };
        //连接成功建立的回调方法
        websocket.onopen = function(event) {
            setMessageInnerHTML("Loc MSG: 成功建立连接");
        }
        //接收到消息的回调方法
        websocket.onmessage = function(event) {
            setMessageInnerHTML(event.data);
        }
        //连接关闭的回调方法
        websocket.onclose = function() {
            setMessageInnerHTML("Loc MSG:关闭连接");
        }
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function() {
            websocket.close();
        }
    }
    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
    //关闭连接
    function closeWebSocket() {
        websocket.close();
    }
    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
        setMessageInnerHTML(message)
    }
</script>
<!--样式-->
<style>
    #message{
        margin-top:40px;
        border:1px solid gray;
        padding:20px;
    }
</style>
</html>

异常统一处理

通常需要将springboot项目中的异常进行统一处理,处理方式常用的方式有两种,一种是通过springboot中提供的注解进行统一处理,另外一种是通过实现

1. 通过添加注解进行异常处理

@RestControllerAdvice
public class ExceptionConfig {
    @ExceptionHandler(value = Exception.class)
    public String exception(Exception e) {
        return "error";
    }
}

如果需要捕捉404异常,则需要在配置文件中添加如下配置

spring:
  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false

2. 通过异常处理器实现

  1. 使用此种方式通常需要添加依赖,支持html的请求
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. 实现异常处理器
@Component
public class ExceptionHand implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        //此处返回错误页面
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tfpaE2kz-1689319751856)(C:\Users\wanguoyin\AppData\Roaming\Typora\typora-user-images\image-20230714115316738.png)]

经过测试,如果是404找不到页面,或默认使用templates中error.html页面返回,如果名称不为error.html,则返回默认的错误页面

Excel

1. easyPoi操作excel

1. 添加依赖
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>
2. 代码示例
  1. 使用这种方式比较简洁,必须要创建实体类使用注解与excel表建立映射关系
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

@Excel(name = "id")
    private String id;
@Excel(name = "姓名")
    private String name;
@Excel(name = "密码")
    private String pwd;
}
  1. 然后可以对文件进行读取,可以通过设置ImportParams来进行设置导入读取文件的一些参数
@PostMapping("/upload")
@ResponseBody
public String upload1(MultipartFile file,HttpServletResponse response) throws Exception {
    //设置导入参数,主要是对关键数据的一些定位,如果没有特殊的样式,使用默认参数就可以
    ImportParams importParams = new ImportParams();
    //读取的sheet的数量,读取的数量不能大于总数量,如果不设置,默认为1
    importParams.setSheetNum(1);
    //从哪个sheet开始读取,开始读取的位置和读取数量不能超过总数量,默认为0,从第一个文件开始读取
    importParams.setStartSheetIndex(0);
    List<User> objects = ExcelImportUtil.importExcel(file.getInputStream(), User.class, importParams);
}
  1. 文件写出,保存到本地
@PostMapping("/upload1")
@ResponseBody
public String upload1(HttpServletResponse response) throws IOException {
    //准备要导出表中的数据
    List<User> list = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        list.add(new User(String.valueOf(i),"name"+i,"pwd"+i));
    }
    //设置导出数据的参数,表格的一些常规信息,颜色,宽高,顺序
    ExportParams params = new ExportParams();
    //设置工作簿名称
    params.setSheetName("部门信息");

    //导出,此语句执行过后,会清空list内的数据
    Workbook sheets = ExcelExportUtil.exportExcel(params, User.class, list);
    
    //创建输出流
    File file = new File("D:/test/测试表.xlsx");
    OutputStream out = new FileOutputStream(file);
    //保存到本地
    sheets.write(out);

    return "success";
}
  1. 文件写出到响应信息,注意写入到请求头种的信息,文件名如果有中文,如果不使用URLEncoder.encode进行编码,前端接收的时候会编程乱码
@PostMapping("/upload")
@ResponseBody
public void upload2(HttpServletResponse response) throws IOException {
    //准备要导出表中的数据
    List<User> list = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        list.add(new User(String.valueOf(i),"name"+i,"pwd"+i));
    }
    //设置导出数据的参数,表格的一些常规信息,颜色,宽高,顺序
    ExportParams params = new ExportParams();
    //设置工作簿名称
    params.setSheetName("部门信息");

    //导出
    Workbook sheets = ExcelExportUtil.exportExcel(params, User.class, list);

    File tempFile = File.createTempFile("123123123123", ".xslx");
    //创建输出流
    ServletOutputStream out = response.getOutputStream();

    //设置表名称
    String pathName = "测试下载表";
    //设置响应数据为
    response.setContentType("application/vnd.ms-excel");
    //也可以使用如下配置
//response.setContentType("application/x-download");

    response.setCharacterEncoding("UTF8");
    response.setHeader("Content-disposition","filename="+URLEncoder.encode(pathName,"UTF8")+".xls");

    //保存到本地
    sheets.write(out);
}

2. 阿里巴巴easyExcel

1. 添加依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.2.1</version>
</dependency>
2. 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @ExcelProperty(value = "id")
    private String id;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "密码")
    private String pwd;
}
3. 创建监听器
package com.example.demo.controller;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.demo.User;

import java.util.Map;

public class ExcelListen extends AnalysisEventListener<User> {
    // 一行一行读取 excel 内容
    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
        System.out.println("***"+user+"***");
    }

    // 读取表头内容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:" + headMap);
    }
    // 读取完成之后
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}
4. 操作文件
  1. 读取文件
@RequestMapping("/upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
    //传入的可以是一个流,也可以是一个文件,也可以是文件的路径等
    EasyExcel.read(file.getInputStream(), User.class,new ExcelListen()).sheet().doRead();
    return "ok";
}
  1. 写出文件
@RequestMapping("/upload")
@ResponseBody
public String upload(HttpServletResponse response) throws IOException {
    List<User> arrayList = new ArrayList<>();
    arrayList.add(new User("1","name","hello"));
    EasyExcel.write(response.getOutputStream(), User.class)
            .sheet()
            .doWrite(arrayList);
    return "ok";
}

3. POI中常用的操作方法

1. 读取文件
//创建一个临时文件,用来接收前端传进来的文件
File tempFile = File.createTempFile("aaaa",".xslx");
byte[] bytes = file.getBytes();
OutputStream outputStream = new FileOutputStream(tempFile);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();

//使用此方法可以获得workbook,这三个之间的区别是
//HSSF支持97-2003版本后缀名为.xsl
//XSSF是从2007开始的后缀名为.xslx
//SXSSF是在XSSF的基础上从POI3.8版本开始的一种低内存占用的操作,后缀名为.xslx
Workbook workbook = new XSSFWorkbook(tempFile);
SXSSFWorkbook workbook1 = new SXSSFWorkbook();
HSSFWorkbook workbook2 = new HSSFWorkbook();

//获取sheet,可以通过名称获取,也可以通过序号获取
Sheet sheet = workbook.getSheet("sheet1");
Sheet sheet1 = workbook.getSheetAt(1);

//获取row,通过所在行获取,从零开始
Row row = sheet.getRow(0);

//过去单元格,通过所在列获取,从零开始
Cell cell = row.getCell(0);

//可以对单元格的颜色进行设置
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
cell.setCellStyle(cellStyle);

//获取单元格内的内容,此处尽量进行类型判断,根据类型获取数据
String stringCellValue = cell.getStringCellValue();

//使用完成以后记得删除临时文件
tempFile.delete();
2. 写入方法
//新建一个表格
Workbook workbook = new SXSSFWorkbook();
//创建一个工作簿并命名
Sheet sheet = workbook.createSheet("飞行物信息");
//创建一个行
Row row = sheet.createRow(0);
//创建一个单元格
Cell cell = row.createCell(0);
//给单元格赋值
cell.setCellValue("类型");
//写出到文件
workbook.write(new FileOutputStream(new File("D:/test/2342342342343243.xslx")));

网络请求

1. http请求

@Test
public void httpRequestTest() throws IOException {
    //使用原生的http连接,首先需要创建url
    URL url = new URL("www.baidu.com");
    //打开连接,此处有两种方式,通常选择第二种方式
    //第一种,为原生的url请求
    URLConnection urlCon = url.openConnection();
    //第二种集成了URLConnection,但是多了几个方法,而且URLConnection中的方法他都可以使用
    HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();

    //请求中设置请求头的一些基本操作
    httpCon.setRequestProperty("Content-Type", "application/json");
    httpCon.setRequestProperty("charset", "utf-8");
    httpCon.setRequestProperty("Authorization", "Bearer YOUR_ACCESS_TOKEN");

    //设置请求方式,源码中列举了其中请求方式 "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
    httpCon.setRequestMethod("POST");
    httpCon.connect();
    //获取请求方法
    String requestMethod = httpCon.getRequestMethod();
    //获取响应码
    int responseCode = httpCon.getResponseCode();
    //获取相应信息
    String responseMessage = httpCon.getResponseMessage();

    //通常使用此种方式获取数据流
    InputStream inputStream = httpCon.getInputStream();
}

2. restTemplate

可以发送请求的方式有很多,RestTemplate是从spring3.0开始支持的一个http请求工具,提供了常见的REST请求方案模板,GET,POST,PUT,DELETE请求,以及一些通用的请求执行方法exchange

1. 添加配置
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        // 支持中文编码
        restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//单位为ms
        factory.setConnectTimeout(5000);//单位为ms
        return factory;
    }
}
2. Get请求

常用的get请求有两种,getForObject和geForEntity,这两种类型在使用的时候需要注意接口返回的数据的类型和接收的类型要匹配

没有请求参数

  1. 请求参数类型为(utl,接收响应数据的数据类型)
//使用getForEntity请求的格式
ResponseEntity<Object> response = restTemplate.getForEntity("http://localhost:8090/two", Object.class);
//使用getForObject请求的格式
User string = restTemplate.getForObject("http://localhost:8090/two", User.class);
  1. 使用请求参数除了上述参数,还可以添加拼接在请求url上的参数
Map<String, String> params = new HashMap<>();
params.put("name","zhangsan");
params.put("pwd","123");
//使用getForEntity请求的格式
ResponseEntity<Object> response = restTemplate.getForEntity("http://localhost:8090/two?name={name}&password={pwd}", Object.class,params);
2. Post请求

常用的post请求有三种,除了和get相同的两种请求之外,还有一种是postForLaction,返回一个Uri,postForLaction的用法,还没搞明白,等弄明白了,再过来补充资料

//使用getForEntity请求的格式
ResponseEntity<User> response = restTemplate.postForEntity("http://localhost:8090/two?name={name}",new User("aaa","bbb"), User.class,params);
//使用getForObject请求的格式
User string = restTemplate.postForObject("http://localhost:8090/two",user,User.class);
3. Exchange请求
  1. 使用exchange方式请求的好处是比较灵活,我们可以根据自己的需要定制请求的内容
User user = new User("ccc", "ddd");
//使用exchange请求,在httpEntity中需要传入两个参数
//一个为请求体,一个为请求头,在请求头中可以设置请求相关的一些信息
//在请求体中添加请求参数,请求头中也可以参数,与请求体携带的参数互不干扰
HttpEntity httpEntity = new HttpEntity(user,new HttpHeaders());
ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:8090/two?name={name}", HttpMethod.POST, httpEntity, String.class,params);
  1. 对请求头进行一定的设置
User user = new User("张三","qwe123");
//创建请求头
HttpHeaders headers = new HttpHeaders();

//设置内容为application/json,在开始的时候是可以在此处设置编码格式,
//但是现在已经被启用,官方给出的解释是主流的浏览器已经支持UTF-8,所以不用再设置
//内容类型可以使用httpServletRequest.getHeader("Content-Type")进行获取
headers.setContentType(MediaType.APPLICATION_JSON);

//虽然不需要设置默认的编码格式,但是如果我们需要携带编码信息,可以在AcceptCharset中添加,支持添加多个
//如果要取出则可以在请求头中httpServletRequest.getHeader("Accept-charset")中取出
Charset charset = Charset.forName("GB2312");
Charset charset1 = Charset.forName("GBk");
List<Charset> charsets = new ArrayList<>();
charsets.add(charset);
charsets.add(charset1);
headers.setAcceptCharset(charsets);

//如果需要添加token则可以使用如下方式
List<String> cookies = new ArrayList<>();
cookies.add("cookies1=111111111");
cookies.add("cookies2=222222222");
cookies.add("cookies3=333333333");
headers.put(HttpHeaders.COOKIE,cookies);

//除此之外,可以在请求头中添加一些请求信息
headers.add("aa","aaaa");

HttpEntity entity = new HttpEntity(user, headers);
ResponseEntity<User> exchange = restTemplate.exchange("http://localhost:8090/two", HttpMethod.POST, entity, User.class);

File操作

1. 对文件操作

通常有对文件夹,和文件两种操作,创建和删除文件,写入和读取文件

File file = new File("D:/test/test/test");

//创建文件夹,可以多级目录创建
boolean mkdirs = file.mkdirs();
System.out.println(mkdirs);

//删除末级非空文件夹
boolean delete = file.delete();
System.out.println(delete);

File file1 = new File("D:/test/test/asas.text");
//创建文件
boolean newFile = file1.createNewFile();
System.out.println(newFile);

//写入数据,\n表示换行
FileWriter writer = new FileWriter(file1);
writer.write("张三");
writer.write("\n");
writer.write("李四");
writer.write("\n");
writer.write("aaaa");
writer.write("\n");
writer.write("aaaa");
writer.flush();
writer.close();

//读取数据流常用有两种方式,第一种可以进行编码格式的设置
InputStream inputStream = new FileInputStream(file1);
InputStreamReader in = new InputStreamReader(inputStream,"utf-8");
BufferedReader br = new BufferedReader(in);
String s = "";
while((s = br.readLine()) != null){
    System.out.println(s);
}

//第二种用起来比较简介
FileReader file2 = new FileReader(file1);
BufferedReader brr = new BufferedReader(file2);
String ss = "";
while((ss = brr.readLine()) != null){
    System.out.println(ss);
}

//删除文件
boolean delete1 = file1.delete();

File file3 = new File("D:/");
//返回所有文件及文件夹的名称,不会递归遍历
String[] list = file3.list();
//返回所有文件的file数组,不会递归遍历
File[] files = file3.listFiles();

//此处会进行递归遍历,查询出所有的文件及文件夹
Stream<Path> walk = Files.walk(Paths.get("D:\\test"));
List<Path> collect = walk.filter(Files::isDirectory).collect(Collectors.toList());
for (Path path : collect) {
    System.out.println(path);
}

2. 网络请求携带文件

通常我们可以通过表单携带文件进行文件传输,在接收文件的时候,会有如下常用的操作

@PostMapping("/upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
    //获取文件名称
    String originalFilename = file.getOriginalFilename();
    //获取输入流
    InputStream inputStream = file.getInputStream();
    //获取字节数组
    byte[] bytes = file.getBytes();
    //获取内容类型
    String contentType = file.getContentType();
    //获取上传的键名
    String name = file.getName();
    //获取文件大小
    long size = file.getSize();
    //获取资源其他的一些信息
    Resource resource = file.getResource();
    return "ok";
}

3. 网络响应携带文件

//对响应头进行基础的设置
response.setContentType("application/x-download");
String fileName = URLEncoder.encode("模板.xlsx", "UTF-8");
response.setCharacterEncoding("UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);

//读取文件
File file = new File(path+fileName);
//将文件写入流
InputStream in = new FileInputStream(file);
//获得数组长度
int available = in.available();
//转换成字节数组
byte[] b = new byte[available];
//将流写进数组
in.read(b);
//将数组写进输出流,并刷新
ServletOutputStream out = response.getOutputStream();
out.write(b);
out.flush();

4. 其他常用方法

//判断是否是文件夹
boolean directory = file.isDirectory();
//判断是否是文件
boolean file4 = file.isFile();
//判断此文件是否存在
boolean exists = file.exists();
//获取path文件
Path path = file.toPath();
//读取文件内容
List<String> list1 = Files.readAllLines(file.toPath());
//往文件内写入数据
Files.write(file4.toPath(),"测试数据1".getBytes());
//复制文件
Files.copy(file1.toPath(), file2.toPath());
//获取文件大小
long length = source1.length();
long size = Files.size(source1.toPath());
//文件移动
File source1 = new File("D:/test/test/bbb.txt");
File source2 = new File("D:/test/ccc.txt");
Path move = Files.move(source1.toPath(), source2.toPath());
//参数1和2通常用作文件的名称和后缀,注意这是生成了一个临时文件,在文件夹的//C:\Users\administrator\AppData\Local\Temp文件夹下可以找到,临时文件使用file.deleteOnExit()可以在推出虚拟机的时候删除,经过测试,然并卵,所以还是老老实实的使用完成使用delete进行删除比较靠谱
File file = File.createTempFile(参数1,参数2)

Java过滤器Filter

1. 构建一个过滤器

@Configuration
public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //实现过滤器接口,重写其中的doFilter方法,通常拦截请求的内容,需要将请求参数类型进行转换
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //获取请求头中的内容
        String cccc = request.getHeader("cccc");
        String dddd = request.getHeader("dddd");

        //获取cookie中的内容
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            System.out.println(cookie.getName() + "token");
        }

//获取服务器信息
StringBuffer requestURL = request.getRequestURL();

//获取请求设备地址
String remoteAddr = request.getRemoteAddr();

//获取请求设备详情
String header = request.getHeader("User-Agent");

        //获取请求内容form表单请求
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (String s : parameterMap.keySet()) {
            System.out.println(s);
        }

        //使用Post请求参数为json数据
        MyRequestWrapper requestWrapper = new MyRequestWrapper(request);
        String body = requestWrapper.getBody();
        System.out.println(body);
//进行放行操作
        filterChain.doFilter(requestWrapper, servletResponse);
    }
}

2. 获取请求体

上述过滤器中已经包含了一些常用的方法,如果我们要读取请求体中的内容,需要构建读取的类,因为请求体中的内容需要使用流进行读取,而且只能调用一次,如果在过滤器中进行调用,则接口会接收不到数据,出现异常情况,所以使用辅助类将请求体中的内容复制一份,继续传递下去

public class MyRequestWrapper extends HttpServletRequestWrapper {
 
    private byte[] body;
    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        if(ServletFileUpload.isMultipartContent(request)){
            return;
        }
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = request.getReader();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        String body = sb.toString();
        this.body = body.getBytes(StandardCharsets.UTF_8);
    }
    public String getBody() {
        return new String(this.body , StandardCharsets.UTF_8) ;
    }
    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read(){
                return bais.read();
            }
        };
    }
    @Override
    public BufferedReader getReader(){
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

AOP

1. 添加aop依赖

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

2. 指定切入点

1. 注解方式
  1. 创建注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestAnnotation {
}
  1. 创建切面
@Aspect
@Component
public class TestAop {

    //@Before("@within(controller)") 此注解用于拦截类上的注解
    @Before("@annotation(testAnnotation)")
    public void before(JoinPoint joinPoint, TestAnnotation testAnnotation) {
        System.out.println(joinPoint.getSignature().getName() + "aaa");
    }
}

2. 在切面中指定
@Aspect
@Component
public class TestAop {

    @Pointcut("execution(public * com.example.demo.demos.web.controller.UserController.*(..))")
    public void pointcut(){}
    
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println(joinPoint.getSignature().getName() + "aaa");
    }
}

3. 使用@Order对切面执行顺序进行排序

网上有资料说@Order可以作用在方法上,但是个人认为此注解作用在类上才有效,做了测试以后,发现在类上才可以控制切面的执行顺序,在切面类上加上此注解,可以控制切面的执行顺序,序号越小,执行顺序越靠前

@Aspect
@Component
@Order(1)
public class TestAop {

    @Pointcut("execution(public * com.example.demo.demos.web.controller.UserController.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println(joinPoint.getSignature().getName() + "aaa");
    }
}

执行dos命令

对于某些dos命令我们需要获取输入流,并读出来,否则可能会卡住,在springboot项目中,例如ffmpeg,如果命令执行以后不读取流,则不会输出切片文件,只有当手动停止项目的时候,才会输出执行的内容

Runtime rt = Runtime.getRuntime();
//推送dos命令
Process exec = rt.exec("cmd /c ping www.baidu.com");
InputStream in = exec.getInputStream();
//此处可以设定编码
BufferedReader bf = new BufferedReader(new InputStreamReader(in,"gbk"));
String s = "";
//使用流读取回显数据
while ((s = bf.readLine()) != null) {
    System.out.println(s);
}

正则

//被正则的数据
String s = "aabbccdd123ccdd334";
//要使用的正则
String regexp = "[\\d]+";
Matcher p = Pattern.compile(regexp).matcher(s);
while (p.find()) {
    System.out.println(p.group());
}

常用工具类

1. lang3

1. 添加依赖
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
2. 常用的判断段空工具类
  1. 集合类型判断

​ CollectionUtils.isEmpty()

​ CollectionUtils.isNotEmpty()

  1. 对象判断

​ ObjectUtils.isEmpty()

​ ObjectUtils.isNotEmpty()

  1. 字符串类判断

​ StringUtils.isEmpty()

​ StringUtils.isNotEmpty()

​ StringUtils.isBlank()

​ StringUtils.isNotBlank()

​ StringUtils.defaultIfEmpty(null,”aa”)

​ 如果第一个参数为空,则返回第二个参数

​ StringUtils.defaultIfBlank(null,”aa”)

​ 如果第一个参数为空或者为空格,则返回第二个参数

​ Empty和Blank的区别在于是否判断空格,如果使用isEmpty则” ”判断为非空,而isBlank判断为空

​ 三种类型的isEmpty方法在spring框架中都有,其他方法需要添加相应的工具类依赖,在使用中尽量添加相应的工具类,实际上在使用的时候集合判断可以使用ObjectUtils.isEmpty和Object Utils.isNotEmpty,这两个类可以判断对象,数组,集合,map,迭代器,字符串

3. 常用字符串操作工具
  1. StringUtils.join(参数1,参数2)

参数1为数据,参数2为分割符,参数1可以为各种类型数组,集合,迭代器等等

  1. StringUtils.joinWith(参数1,可变参数…)

参数1为分隔符,可变参数可以为多种类型,会调用join方法,可变参数最终以数组的形式拼接

  1. StringUtils.abbreviateMiddle(参数1,参数2,参数3)

中间替换

  1. StringUtils.abbreviate(参数1,参数2,参数3)

结尾替换

其中参数1为要操作的字符串,参数2为要代替的字符串,参数3为截取的个数,参数3的长度大于参数2的长度加2,而且参数三的长度小于参数1,这样替换才会生效,如果参数三大于等于参数1的长度,则提取参数1所有内容而且不替换

  1. StringUtils.contains(参数1,参数2)

参数1是否包含参数2

  1. StringUtils.containsAny(参数1,参数2…)

参数1是否包含参数2,或者包含参数n

  1. StringUtils.detaultString(参数)

如果参数为null,此方法会将null转换为空字符串

  1. StringUtils.replace()

三个参数,会将参数1中的参数2,替换为参数3

4. 生成随机数工具类
  1. 数字数组类型

RandomUtils.nextInt(参数1,参数2)

无参情况下随意生成一个正数,如果有参数,在参数1和参数2中间随意生成 一个整数

RandomUtils.nextBoolean()

随意生成一个布尔值

RandomUtils.nextBytes(参数1)

生成一个字节数组,参数1为生成数组的长度

RandomUtils.nextDouble(参数1,参数2)

与整数生成一样,可以随意生成参数1到参数2之间的随意小数,如果无参则随意生成一个小数

RandomUtils.nextLong和RandomUtils.nextFloat

与上面生成整数和小数一致

  1. 字符串类型

使用RandomStringUtils类,共有5个方法

RandomStringUtils.random(参数1)

随机字符串

RandomStringUtils.randomAlphabetic(参数1)

随机字母(不区分大小写)

RandomStringUtils.randomAlphanumeric(参数1)

随机字母加数字(不区分大小写)

RandomStringUtils.randomAscii(参数1)

随机Ascii字符串

RandomStringUtils.randomNumeric(参数1)

随机字符串数字

参数1为生成的字符串个数

5. 日期类型数据的操作
  1. DateFormatUtils

DateFormatUtils.format(参数1,参数2)

参数1为日期类型,参数2 要格式化的类型”yyyy-MM-dd HH:mm:SS”

返回一个字符串类型

  1. DateUtils

DateUtils.parseDate(参数1,参数2)

参数1 为字符串”2012-02-02 12:38:44”,参数2 为”yyyy-MM-dd HH:mm:SS”, 返回一个日期类型

增加时间

addDays(参数1,参数2)

addYears(参数1,参数2)

addMonths(参数1,参数2)

addHours(参数1,参数2)

addMinutes(参数1,参数2)

addSeconds(参数1,参数2)

addWeeks(参数1,参数2)

addMilliseconds(参数1,参数2)

参数1为Date格式,年 月 日 时 分 秒 周 毫秒在参数1的基础上增加参数 2个单位,如果想减去参数2个单位则将参数2置为负数即可

  1. 获取时间片段

DateUtils.getFragmentInDays(参数1,参数2)

DateUtils.getFragmentInHours(参数1,参数2)

DateUtils.getFragmentInMinutes(参数1,参数2)

DateUtils.getFragmentInSeconds(参数1,参数2)

DateUtils.getFragmentInMillieseconds(参数1,参数2)

参数1为日期,参数2需要使用Calendar.Date(天),Calendar.Year(年)…

其中1表示距离本年或月过去多少天,2表示距离本年或月或日过去多少小时,注意,在使用的时候本小时过去多少分钟的时候不能使用calendar.hour,因为查询分钟的时候不支持10号分片,所以查询的时候需要使用Calendar.HOUR_OF_DAY

  1. 清除数据

DateUtils.ceiling(参数1,参数2)

参数1 为日期格式,参数2为Calendar.YEAR…,如果参数2为年则得到年命开始时间(清除月日和时分秒信息),如果参数2为月则得到下个月开始时间(清除日时分秒时间)

  1. 判断是否是同一天

DateUtils.isSameDay(参数1,参数2 )

参数1 和参数2 如果是同一天,忽略时分秒,则返回true,否则返回false

DateUtils.isSameInstant(参数1,参数2)

判断参数1和参数2 是否是同一时刻,精确到毫秒

  1. 设置年月日

DateUtils.setDays(参数1,参数2)

DateUtils.setYears(参数1,参数2)

DateUtils.setMonth(参数1,参数2)

参数1为日期格式,参数2为设置的数字

2. MD5加密字符串

//导包使用import org.apache.commons.codec.digest.DigestUtils;
String s = DigestUtils.md5Hex("hello");

常见问题及解决方案

1. Java 使用@JsonFormat出现时差问题

查看是否在配置文件application.yml中添加时区的配置

spring:
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss

如果配置文件中没有时区配置,则在使用@JsonFormat时添加时区参数

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:SS",timezone = "GMT+8")

2. Maven打包

Package是将项目打包放到target下,install则是除了执行package还将打包后的jar包放到maven仓库

设置项目打包以后的名称

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuBHZyBZ-1689319751857)(C:\Users\wanguoyin\AppData\Roaming\Typora\typora-user-images\image-20230714141032267.png)]

3. 启动报错

如果我们的文件中已经配置了数据库相关信息,但是打包以后启动还是报错如下,查看是否是同时使用application.properties和application.yml文件,如果两个文件同时存在,将所有配置移动到application.yml后删除application.properties

如果删除application.properties以后仍然不能解决这个问题,尝试clean一下

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
        If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
        If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

StringUtils.random(参数1)

随机字符串

RandomStringUtils.randomAlphabetic(参数1)

随机字母(不区分大小写)

RandomStringUtils.randomAlphanumeric(参数1)

随机字母加数字(不区分大小写)

RandomStringUtils.randomAscii(参数1)

随机Ascii字符串

RandomStringUtils.randomNumeric(参数1)

随机字符串数字

参数1为生成的字符串个数

5. 日期类型数据的操作
  1. DateFormatUtils

DateFormatUtils.format(参数1,参数2)

参数1为日期类型,参数2 要格式化的类型”yyyy-MM-dd HH:mm:SS”

返回一个字符串类型

  1. DateUtils

DateUtils.parseDate(参数1,参数2)

参数1 为字符串”2012-02-02 12:38:44”,参数2 为”yyyy-MM-dd HH:mm:SS”, 返回一个日期类型

增加时间

addDays(参数1,参数2)

addYears(参数1,参数2)

addMonths(参数1,参数2)

addHours(参数1,参数2)

addMinutes(参数1,参数2)

addSeconds(参数1,参数2)

addWeeks(参数1,参数2)

addMilliseconds(参数1,参数2)

参数1为Date格式,年 月 日 时 分 秒 周 毫秒在参数1的基础上增加参数 2个单位,如果想减去参数2个单位则将参数2置为负数即可

  1. 获取时间片段

DateUtils.getFragmentInDays(参数1,参数2)

DateUtils.getFragmentInHours(参数1,参数2)

DateUtils.getFragmentInMinutes(参数1,参数2)

DateUtils.getFragmentInSeconds(参数1,参数2)

DateUtils.getFragmentInMillieseconds(参数1,参数2)

参数1为日期,参数2需要使用Calendar.Date(天),Calendar.Year(年)…

其中1表示距离本年或月过去多少天,2表示距离本年或月或日过去多少小时,注意,在使用的时候本小时过去多少分钟的时候不能使用calendar.hour,因为查询分钟的时候不支持10号分片,所以查询的时候需要使用Calendar.HOUR_OF_DAY

  1. 清除数据

DateUtils.ceiling(参数1,参数2)

参数1 为日期格式,参数2为Calendar.YEAR…,如果参数2为年则得到年命开始时间(清除月日和时分秒信息),如果参数2为月则得到下个月开始时间(清除日时分秒时间)

  1. 判断是否是同一天

DateUtils.isSameDay(参数1,参数2 )

参数1 和参数2 如果是同一天,忽略时分秒,则返回true,否则返回false

DateUtils.isSameInstant(参数1,参数2)

判断参数1和参数2 是否是同一时刻,精确到毫秒

  1. 设置年月日

DateUtils.setDays(参数1,参数2)

DateUtils.setYears(参数1,参数2)

DateUtils.setMonth(参数1,参数2)

参数1为日期格式,参数2为设置的数字

2. MD5加密字符串

//导包使用import org.apache.commons.codec.digest.DigestUtils;
String s = DigestUtils.md5Hex("hello");

常见问题及解决方案

1. Java 使用@JsonFormat出现时差问题

查看是否在配置文件application.yml中添加时区的配置

spring:
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss

如果配置文件中没有时区配置,则在使用@JsonFormat时添加时区参数

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:SS",timezone = "GMT+8")

2. Maven打包

Package是将项目打包放到target下,install则是除了执行package还将打包后的jar包放到maven仓库

设置项目打包以后的名称

[外链图片转存中…(img-OuBHZyBZ-1689319751857)]

3. 启动报错

如果我们的文件中已经配置了数据库相关信息,但是打包以后启动还是报错如下,查看是否是同时使用application.properties和application.yml文件,如果两个文件同时存在,将所有配置移动到application.yml后删除application.properties

如果删除application.properties以后仍然不能解决这个问题,尝试clean一下

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
        If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
        If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值