Git Product home page Git Product logo

blog's Introduction

blog's People

Contributors

annie3310 avatar

Stargazers

 avatar

Watchers

 avatar

blog's Issues

Java Optional 类的使用

前言

要说 Java 中最 "臭名昭著" 的异常, NPE 说排第二没人敢说第一, 业务中往往都有大量的代码用于规避 NPE, 一条规避代码就要占至少 3 行, 3 行又 3 行, 令人苦不堪言.

有龙就一定有屠龙者, Java 8 中就引入了 Optional 类来解决 NPE 的问题, 这个解决不是说不会产生 NPE, 而是可以用更 "优雅" 的方式去处理 NPE.

Optional 类的使用

Optional 直译为 "可选择的", 事实也是如此, 使用 Optional 很多时候都是在进行二选一.
将一个对象包裹在 Optional 中, 使用时通过 Optional 的 API 来判断该值是否为 null, 再根据不同的分支执行不同的代码, 听起来和 if else 很像, 但是代码观感上要远优于 if else.

前置知识点

使用 Optional 类需要了解其他 Java 8 的新特性

  • Lambda 表达式
  • 函数式接口
  • 方法引用

而且 Optional 的使用和 Stream 的使用有很多相像之处.

方法介绍

方法名 返回值类型 功能
of(T) Optional 创建不允许值为 null 的 Optional 对象, 如果对象为 null, 则会抛出 NoSuchElementException
ofNullable(T) Optional 创建允许值为 null 的 Optional 对象
empty() Optional<?> 创建一个空的 Optional 对象
get() T 获取 Optional 中的对象
isPresent() boolean 判断值是否为 null
ifPresent(Consumer<? extends T>) void 如果该值存在则调用 Consumer 中传入的方法
filter(Predicate<? extends T>) T 如果 predicate 判断该值符合条件, 则返回该 Optional 对象本身, 如果不符合则返回一个空的 Optional 对象
map(Function<? super T, ? extends U>) Optional 如果值存在, 则对 Optional 中的值执行传入的方法, 并返回映射类型 U 的 Optional 对象, 如果值不存在, 则返回空的 Optional 对象
flatMap(Function<? super T, Optional> mapper) Optional 和 map() 的区别是, map() 在执行时使用 ofNullable() 将返回值包装成一个 Optional 返回, 而 flapMap() 返回的是值本身, 需要手动将其包装成 Optional 返回.
orElse(T) T 如果值存在, 则返回值, 如果值不存在, 则返回值入的参数
orElseGet(Supplier<? extends T>) T 和 orElse() 的区别是, orElse() 的 else 返回的值是手动提供的, 所以无论如何都会创建 else 对象, 而 orElseGet() 则是当值不存在时执行 Supplier 来创建值.类似于懒加载
orElseThrow(Supplier<? extends X> exceptionSupplier) 和 orElse() 的区别是, 当值不存在时, 则抛出 Supplier 传入的方法提供的异常

方法演示

创建演示实体类

public class OptionalEntity {
    private Integer nullableInteger;
    private Integer integer = 1;
    private String nullableString;
    private String string = "example";

    public OptionalEntity() {
    }

    public OptionalEntity(Integer nullableInteger, String nullableString) {
        this.nullableInteger = nullableInteger;
        this.nullableString = nullableString;
    }

    public Integer getNullableInteger() {
        return nullableInteger;
    }

    public void setNullableInteger(Integer nullableInteger) {
        this.nullableInteger = nullableInteger;
    }

    public Integer getInteger() {
        return integer;
    }

    public void setInteger(Integer integer) {
        this.integer = integer;
    }

    public String getNullableString() {
        return nullableString;
    }

    public void setNullableString(String nullableString) {
        this.nullableString = nullableString;
    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    @Override
    public String toString() {
        return "OptionalEntity{" +
                "nullAbleInteger=" + nullableInteger +
                ", integer=" + integer +
                ", nullAbleString='" + nullableString + '\'' +
                ", string='" + string + '\'' +
                '}';
    }
}

get of ofNullable

public static void main(String[] args) {
        final OptionalEntity entity1 = new OptionalEntity();
        final OptionalEntity entity2 = new OptionalEntity(2, "val");

        get(entity1);
        getNullable(entity1)
}

private static void getNullable(OptionalEntity entity) {
        Optional<OptionalEntity> entity1 = Optional.ofNullable(entity);
        // get() 的结果为 null 时会抛出 noSuchElementException
        OptionalEntity entity2 = entity1.get();
        System.out.println(entity2);
    }

    private static void get(OptionalEntity entity) {
        // 传 null 会抛出 NPE
        Optional<OptionalEntity> entity1 = Optional.of(entity);
        OptionalEntity entity2 = entity1.get();
        System.out.println(entity2);
    }

因为传入的都不为 null, 所以两个方法都正常输出的结果

OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}

而当两个方法都传入 null 时, 则会产生以下两种结果

结果 1: 当 of()遇到 null 时则会抛出 NPE:

get(null);
Exception in thread "main" java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Objects.java:203)
	at java.util.Optional.<init>(Optional.java:96)
	at java.util.Optional.of(Optional.java:108)
	at share.OptionalTest.get(OptionalTest.java:54)
	at share.OptionalTest.main(OptionalTest.java:17)

结果 2: 当 ofNullable() 遇到 null 时, 并不会抛出 NPE, 而当传入结果为 null, 并调用 get() 时则会抛出 NoSuchElementException:

getNullable(null)
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at share.OptionalTest.getNullable(OptionalTest.java:48)
	at share.OptionalTest.main(OptionalTest.java:18)

orElse orElseGet

orElse(entity2);
orElse(null);

orElseGet(entity2);
orElseGet(null);

private static void orElse(OptionalEntity entity) {
        Optional<OptionalEntity> entity1 = Optional.ofNullable(entity);
        OptionalEntity entity2 = entity1.orElse(new OptionalEntity());
        System.out.println(entity2);
    }

    private static void orElseGet(OptionalEntity entity) {
        Optional<OptionalEntity> entity1 = Optional.ofNullable(entity);
        OptionalEntity entity2 = entity1.orElseGet(OptionalEntity::new);
        System.out.println(entity2);
    }

执行结果

OptionalEntity{nullAbleInteger=2, integer=1, nullAbleString='val', string='example'}
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}

OptionalEntity{nullAbleInteger=2, integer=1, nullAbleString='val', string='example'}
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}

传入 null 时, 二者都返回了 else 值

orElse 和 orElseGet 的区别
    differentBetweenOrElseAndOrElseGet(entity2);
    differentBetweenOrElseAndOrElseGet(null);

    private static void differentBetweenOrElseAndOrElseGet(OptionalEntity entity) {
        Optional<OptionalEntity> entity1 = Optional.ofNullable(entity);
        OptionalEntity entity2 = entity1.orElse(new OptionalEntity());
        // orElse() 不管 Optional 中有没有值都会创建对象, 而 orElseGet() 只会有 Optional 中值为 null 时执行 lambda 表达式
        OptionalEntity entity3 = entity1.orElseGet(() -> {
            System.out.println("$orElseGet: 创建对象");
            return new OptionalEntity();
        });
        System.out.println(entity2);
        System.out.println(entity3);
    }

执行结果

OptionalEntity{nullAbleInteger=2, integer=1, nullAbleString='val', string='example'}
OptionalEntity{nullAbleInteger=2, integer=1, nullAbleString='val', string='example'}
$orElseGet: 创建对象
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}

可以看出, 当传入值不为 null 时, 没有执行 lambda 表达式的方法, 也就是没有创建 else 对象

orElseThrow

    differentBetweenOrElseAndOrElseGet(entity2);
    differentBetweenOrElseAndOrElseGet(null);

    private static void orElseThrow(OptionalEntity entity) {
        OptionalEntity entity1 = Optional.ofNullable(entity).orElseThrow(RuntimeException::new);
        System.out.println(entity1);
    }

执行结果

OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}
Exception in thread "main" java.lang.RuntimeException
	at java.util.Optional.orElseThrow(Optional.java:290)
	at share.OptionalTest.orElseThrow(OptionalTest.java:84)
	at share.OptionalTest.main(OptionalTest.java:30)

当传入值为 null 时, 抛出了异常

ifPresent

    ifPresent(entity1);
    ifPresent(null);

    private static void ifPresent(OptionalEntity entity) {
        System.out.println("$ifPresent: ");
        Optional.ofNullable(entity).ifPresent(System.out::println);
    }

执行结果

$ifPresent: 
OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}
$ifPresent: 

当传入值为 null 时没有调用输入方法

filter

    filter(entity1);

    OptionalEntity temp = new OptionalEntity();
    temp.setInteger(2);
    filter(temp);

    private static void filter(OptionalEntity entity) {
        OptionalEntity entity1 = Optional.ofNullable(entity).filter(o -> o.getInteger() == 1).get();
        System.out.println(entity1);
    }

执行结果

OptionalEntity{nullAbleInteger=null, integer=1, nullAbleString='null', string='example'}
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at share.OptionalTest.filter(OptionalTest.java:101)
	at share.OptionalTest.main(OptionalTest.java:42)

可以看出, 当 filter 中结果为 true 时, 返回 Optional 本身, 而当 filter 中结果为 false 时, 返回的是空 Optional, 所以 get() 会抛出异常

map flatMap

    mapAndFlatMap(entity1);

    private static void mapAndFlatMap(OptionalEntity entity) {
        Optional<OptionalEntity> optionalEntityOptional = Optional.ofNullable(entity);
        // map 会调用 Optional.ofNullable() 将结果包装成 Optional
        Optional<Integer> integer = optionalEntityOptional.map(o -> {
            return o.getInteger();
        });
        System.out.println(integer.get());

        // flatMap() 中 Function 返回的值会直接返回, 需要手动包装成 Optional
        Optional<Integer> integer1 = optionalEntityOptional.flatMap(o -> {
            Integer integer2 = o.getInteger();
            return Optional.of(integer2 + 1);
        });
        System.out.println(integer1.get());
    }

执行结果

1
2

可以看出, map() 和 flatMap() 都映射了值并返回了映射到的值类型的 Optional, 但 flatMap() 返回时需要手动包装一层 Optional 才可以

MyBatis 动态 SQL

if / where标签

<!--
if 标签,如果
	test:表达式
-->
<if test="">

# select * from t_account where id= #{id} and username=#{username} and password = #{password} and age = #{age}

# 如果其中任意一个条件为空,则查不出该条数据

# 改为:

<select id="findByAccount" parameterType="com.wjy.utils.Account" resultType="com.wjy.utils.Account">
        select * from t_account
				<where>
					<if test="id!=0">
						<!--
						Long类型在值为空时为0
						-->
            id= #{id}
	        </if>
	        <if test="username!=null">
	            and username=#{username}
	        </if>
	        <if test="password!=null">
	            and password = #{password}
	        </if>
	        <if test="age!=0">
	            and age = #{age}
	        </if>
				</where>
        
    </select>
# 此时如果不输入其中一个变量,则不会写入SQL语句

# <if>可以自动根据表达式的结果来决定是否将对应的语句添加到SQL中

# <where>可以自动判断是否删除语句中的and关键字,如果检测到where和and直接拼接,则删除and,通常情况下
# where和if标签同时使用

choose / when标签

<select id="findByAccount" resultType="com.wjy.utils.Account" parameterType="com.wjy.utils.Account">
        select * from t_account
        <where>
            <choose>
                <when test="id!=0">
                    id=#{id}
                </when>
                <when test="username!=null">
                    username=#{username}
                </when>
                <when test="password!=null">
                    password=#{password}
                </when>
                <when test="age!=0">
                    age=#{age}
                </when>
            </choose>
        </where>
    </select>

trim 标签

trim标签中的prefix和suffix属性会被用于生成实际的SQL语句,会和标签内部的语句进行拼接,如果说语句前后出现了prefixOverrides或者suffixOverrides,属性中指定的值,MyBatis框架会自动将其删除

<select id="findByAccount" resultType="com.wjy.utils.Account" parameterType="com.wjy.utils.Account">
        select * from t_account
        <trim prefix="where" prefixOverrides="and">
            <if test="id!=0">
                id=#{id}
            </if>
            <if test="username!=null">
                and username=#{username}
            </if>
            <if test="password!=null">
                and password=#{password}
            </if>
            <if test="age!=0">
                and age=#{age}
            </if>
        </trim>
    </select>

# 如果在SQL语句中,where遇到了and,那么会将and删除

set 标签

set标签用到update操作,会自动根据参数选择生成SQL语句.

<update id="update" parameterType="com.wjy.utils.Account">
        update t_account
        <set>
            <if test="username!=null">username=#{username}</if>
            <if test="password!=null">password=#{password}</if>
            <if test="age!=0">age=#{age}</if>
        </set>
    </update>

# 防止无效赋值.

foreach 标签

可以迭代生成一系列的值,这个标签主要用SQL中的in语句

<select id="findByIds" parameterType="com.wjy.utils.Account" resultType="com.wjy.utils.Account">
<!--
select * from t_account where id in(2,4,5)
-->
select * from t_account
<where>
<!--
collection:要遍历的元素
open:前置语句
close:后置语句
item:循环中变量
separator:分隔符
-->
    <foreach collection="ids" open="id in (" close=")" item="id" separator=",">
        #{id}
    </foreach>
</where>
    </select>

布隆过滤器的原理

简介

Wiki 百科对布隆过滤器的介绍如下

布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

这里不对布隆过滤器的误算率进行详细说明.

具体表现为:

  • 如果布隆过滤器表示该元素不存在, 则一定不存在
  • 如果布隆过滤器表示该元素存在, 该元素不一定存在

对比 hash 函数:

  • 如果 hash 值不相等, 则两个元素一定不同
  • 如果 hash 值相等, 两个元素不一定相同

二者有着相似的地方, 也可以做同一件事, 而 hash 有着 100% 的准确率, 但效率没有布隆过滤器高, 布隆过滤器有误识别的可能, 但效率高, 应该根据不同的场景选用不同的数据结构.

实现

该图结构取自 《Redis 深度历险:核心原理与应用实践》 1.7.5 节


可以看出, 当一个值进入过滤器时, 会有多个"无偏" hash 函数 (所谓无偏就是可以把 hash 值算得比较均匀的 hash 函数), 算出不同的 hash 值, 再对 hash 值进行运算后得到容器中对应的位置, 将对应位置的值置为 1.

这样当有值需要校验的时候, 先通过多个 hash 函数算出值所在的容器, 如果其中一个为 0, 则说明没有该值, 如果都为 1, 说明该值可能存在. 因为 hash 碰撞的存在, 该值所在的容器里的值可能都是由函数算其他值所得, 所以不能断定该值一定存在.

Test1

测试文章1

测试1

代码块测试

select b.number      as number,
               b.title       as title,
               a.body        as body,
               a.toc         as toc,
               b.state       as state,
               b.created_at  as created_at,
               b.updated_at  as updated_at,
               l.id          as l_id,
               l.name        as name,
               l.color       as color,
               l.description as description
        from article a
                 left join (select number, title, state, created_at, updated_at
                            from blog
                            where state = 'open' order by created_at desc limit #{page},20) b on b.number = a.b_number 蕈,在就卡死的科技孵化klaSJKLDHFJKLASHDFJLKHASLDJKFHKASJDFHKASD
                 left join labels_for_articles lfa on a.b_number = lfa.b_number
                 left join label l on lfa.l_id = l.id
        where b.state = 'open'
        order by b.created_at desc

殊途同归

人啊,就算坚持了梦想,谁又能保证梦想实现的时候还是当初的自己呢。

使用 Redis 做分布式锁

单机的锁

单机程序中, 一个进程可以有多个线程, 多个线程可以共享内存中的信息, 当同时接收到多个请求时, 为了保证数据的一致性, 在操作数据前需要拿到 "锁", 只有当线程持有锁时才可以执行操作, 没有拿到锁的线程则进入阻塞. 而分布式系统中, 线程与线程之间没有共享到内存, 所以无法在本地加锁, 这时就需要一个第三方的介入.

分布式锁

Redis 就可以很好的充当这个"第三方", 可以将 Redis 视为两个 Server 共享的资源, 这样就将两个部署在不同物理空间的服务器"串联"到了一起.

  • 线程 + 线程 + 共享内存中的锁 --> 同步执行
    转化为
  • Server + Server + 共享的 Redis 中的锁 --> 同步执行

使用 Redis 做分布式锁

使用 Redis 做分布式锁其实就是使用 Redis 的 setnx 命令创建一个 Key, 规定该 Key 作为锁, 如果 Redis 中存在该 Key, 则说明已经有服务拿到了锁且正在执行, 此时进入的请求就要进入阻塞状态. 如果该 Key 不存在, 则说明没有服务持有该锁, 在 Redis 中创建一个该 Key, 以向后面的请求声明已经有服务持有了锁.

# 声明锁
setnx lock true
# 执行
# 释放锁
del lock

使用 Redis 做分布式锁面临的问题

死锁

此死锁的含义并非如 MySQL 中的死锁 (互相争夺对方持有的资源), 而是在锁没有设置超时时间的情况下, 持有锁的服务中途停止工作没有释放锁, 导致后面的请求无法正常拿到锁的情况.

为了解决这种情况, 我们可以给锁加一个过期时间, 以便在服务宕机的情况下 Redis 服务器可以在有效时间内释放该锁.

setnx lock true
expire lock 60000

但是从上面的命令中又引申出来一个问题: 如果在执行第一条命令之后服务器进程突然挂了, 第二条命令没有执行成功, 就又会导致死锁的情况.

所以针对这一情况, Redis 引入了 set 指令的扩展参数

# ex: 过期时间, nx: if not exist
set lock true ex 60000 nxset

将两条指令合并成一条原子性指令, 从而解决这一问题.

超时问题

当使用过期时间解决了死锁问题后, 又出现了另一个问题: 超时问题.

超时问题就是死锁的相对面, 死锁是锁得太紧, 超时是锁得太松.

假设, 当程序 A 拿到锁并设置了超时时间, 但程序 A 的执行时间超过了锁的过期时间, 锁会在程序 A 执行完成前提前释放, 这样会使阻塞队列中的请求 B 可以拿到锁, 这将导致 A 与 B 并行运行, 会有数据不一致的风险.

为了解决这一问题, 就需要给这把锁加一个钥匙, 使该锁只能由加锁的线程来解锁, 从而保证

正则表达式

语法

普通字符

字符 用途
[ABC] 匹配 [] 中的所有字符
[^ABC] 匹配除 [] 中的所有字符
\w 匹配数字, 字母, 下划线

非打印字符

字符 用途
\s 匹配所有空白符
\S 匹配所有非空白符
\f 换页符
\n 换行符
\r 回车符
\t 制表符
\v 垂直制表符

特殊字符

字符 用途
() 子字符串
. 匹配除了换行符 \n 以外任何单字符

限定字符

字符 用途
{n} 匹配 n 次
{n, } 至少匹配 n 次
{n, m} 匹配 n 到 m 次
$ 匹配一个字符串结尾的位置
? 前面的子表达式==一次或零次==, 等价于 {0, 1}
* 前面的子表达式==零次或多次==, 等价于 {0, }
+ 前面的子表达式==一次或多次==, 等价于 {1, }

定位符

字符 用途
^ 匹配一个字符串的开始, 配合修饰符使用
$ 匹配一个字符串的结尾, 配合修饰符使用
\b 匹配一个单词的边界
\B 非单词边界

修饰符

字符 用途
i 忽略大小写, /(Java)+/i 可以匹配到 java
g 全局匹配
m 多行匹配, 使用 ^$ 匹配行的开头和结尾
s 使 . 匹配中包含 \n

在 Java 中使用正则表达式

package me.wjy;  
  
import java.util.regex.Matcher;  
import java.util.regex.Pattern;  
  
/**  
 * 正则表达式测试  
 *  
 * @author 王金义  
 * @date 2021/10/12  
 */
 public class RegularExpressionsTest {  
 	public static void main(String[] args) {  
 		String str = "java";  
 		String pattern = "(ja)\\w+";  
  		
	 	Pattern r = Pattern.compile(pattern);  
	 	Matcher m = r.matcher(str);  
 		System.out.println(m.matches());  
 	}  
}

输出 true

test

对阿里 《Java 开发手册》中 boolean 型变量不要使用 "is" 开头的解释

阿里在 《 Java 开发手册(嵩山版)》中提到

其中

定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),框架在反向解析的时
候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

也就是说如果基本数据类型定义成了 isXxx 的形式, 则可能会使某些框架 (如 RPC) 反序列化时无法正确识别变量名.

Idea 中自动生成的 getter 和 setter 如下

public class BooleanTest {
    private boolean success;
    private Boolean complete;
    
    // 依然是 getComplete
    public Boolean getComplete() {
        return complete;
    }

    public void setComplete(Boolean complete) {
        this.complete = complete;
    }

    // isSuccess 而不是 getSuccess
    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }
}

可以看到, Idea 为基本数据类型 boolean 生成的 setter 没有变化, 但 getter 变成了 isSuccess(), 但对于包装类型 Boolean 则生成的 getter 没有变化.

lombok 生成的 getter 和 setter 也是如此

public class BooleanTest {

    public static void main(String[] args) {
        Test test = new Test();
        test.setSuccess(true);
        test.isSuccess();
        test.setComplete(true);
        test.getComplete();
    }

    @Data
    public static class Test{
        private boolean success;
        private Boolean complete;
    }
}

使用 Gitalk

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="gitalk-container"></div>
</body>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<script>
    var gitalk = new Gitalk({
        //Client ID
        clientID: 'ed2b52113703aaee60cc',
        //Client Secret
        clientSecret: 'f3a3f52e1e90fde8eb943c55410b5826f3209974',

        //仓库名称
        repo: 'blog',
        //仓库拥有者
        owner: 'Annie3310',
        admin: ['Annie3310'],
        // Ensure uniqueness and length less than 50
        id: location.href,
        // Facebook-like distraction free mode
        distractionFreeMode: false
    })

    gitalk.render('gitalk-container')
</script>

</html>

GitHub 地址: https://github.com/gitalk/gitalk/blob/master/readme-cn.md

tocbot 测试

问题描述

我的服务器是腾讯云 CVM, 带宽 1M, 在博客的 Vue 前端项目上线后遇到了加载慢的问题, 一开始以为只是宽带的问题 (虽然就是带宽的问题), 但是看了 Chrome 的控制台, 发现了一个体积达到了 2.8M 的文件, 也就有了下面的文章

Vue 项目打包过大问题的解决方案

按需引入

对于 Vue 中引入的第三方模块, 打包时会在 js 文件夹下生成一个 chunk-vendors.xxx.js, 该文件会在网页加载时加载, 如果整体引入模块, 如组件库, 则会导致该文件过大, ( 我只引入了几个必要组件 + Ant Design Vue 就达到了 2.8M+ 相当恐怖 ) 在加载网页时需要先将静态文件加载完成后才会显示网页, 鉴于服务器的 1M 带宽, 这 2.8M 的文件竟然加载了 50 多秒 🤦‍♂️.于是

  1. import Antd from 'ant-design-vue' 改为 import {XXX, XXX} from 'ant-design-vue';Vue.use(XXX)
  2. 将非必要的组件及静态文件放入 public/index.html 中使用 CDN 引入

在 index.html 中引入静态资源

修改路由组件为懒加载

router/index.js 的引入方式从

import XXX from 'XXX';

修改为

const XXX = () => import('XXX');

原因参考: https://www.jianshu.com/p/11c1d85ccd71路由懒加载 一节

传输优化

此时的 chunk-vendors.xxx.js 文件已经从 2.8M 下降到了 800K 左右, 但是加载仍需要 20 多秒, 无法达到预期, 此时就需要用到 gzip

使用 gzip 压缩静态资源

gzip 可以将静态资源在服务器压缩后传输, 客户端拿到 .gz 文件后在浏览器中解压, ( 可以先创建 .gz 文件而不是等 nginx 创建 ) 从而达到加快传输和节省流量的目的

在 nginx 中开启 gzip

在配置文件 server 块中加入

# 如果能找到 .gz 文件, 则直接返回, 不启用服务端压缩
gzip_static on;
# 开启 gzip
gzip  on;
gzip_proxied any;
# 大于指定大小后开启压缩
gzip_min_length  1k;
# 请求压缩的缓冲区数量和大小,以 4k 为单位,32 为倍数。
gzip_buffers     4 16k;
gzip_http_version 1.0;
# 压缩级别数值与压缩比成正比
gzip_comp_level 5;
# 压缩文件类型
gzip_types text/plain application/javascript application/css text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 是否添加响应头 Vary: Accept-Encoding 建议开启。
gzip_vary on;

此时 chunk-vendors.xxx.js 文件就已经变成了 256K, 即使是 1M 的带宽加载速度也在可接受范围内

使用到的网站或工具

参考文章

 @Override
    public PublicResponse getBlog(String number) {
        BlogDTO blog = blogDAO.getBlog(number);
        if (blog == null) {
            return PublicResponse.getInstance(ErrorResponseEnum.NO_BLOG);
        }
        BlogVO blogVO = Converter.blogVO(blog);
        return PublicResponse.getInstance(SuccessResponseEnum.GET_BLOG_SUCCESS, blogVO);
    }

使用 GitHub 基于 OAuth 2.0 做第三方登录

什么是 OAuth

OAuth (Open Authorization[1][2]) is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.[3][4] This mechanism is used by companies such as Amazon,[5] Google, Facebook, Microsoft and Twitter to permit the users to share information about their accounts with third-party applications or websites.

OAuth (Open Authorization) 是一个开放授权的开放标准, 通常作为一种在网络上不提供密码, 但允许其他网站或应用访问他们的信息的方式

试想象, 如果 A 网站需要访问你的微信头像, 而访问头像却需要你提供你的微信密码, 由 A 网站去登录你的微信去获取, 这显然是不能接受的.

于是 OAuth 应运而生.
注意 OAuth 是一种标准, 而不是具体的实现

OAuth 的流程

这里以 GitHub OAuth 举例

实现

创建 GitHub OAuth App

1 创建 App 并获取 Client ID

访问该地址来创建一个 GitHub OAuth App: https://github.com/settings/applications/new

2 获取 Client Secret

点击 generate a new app secret 来创建一个 client secret, 它用于请求 access token, 记住该 secret, 你再也无法看到它, 如果忘记只能重新生成.

GitHub OAuth 流程

1 请求用户授权

申请好 App 之后, 就可以使用了, 首先需要访问

GET https://github.com/login/oauth/authorize

该请求需要携带以下参数

参数 说明
client_id 必填 客户端唯一标识, 与 GitHub App 唯一对应, 可以在设置中找到
redirect_uri 在用户确认授权后, 响应返回的地址, 如果不填, 则返回到 App 设置中默认的地址
scope 该授权可以访问的信息, 默认只可以访问 user, repo, 如果还想要访问其他的内容, 参照 官方文档
state 用于标识该次请求, 最好是一串随机字符串, 用于防跨站请求伪造攻击, 这里会在下面介绍

还有两个不常用的参数, 一般不填, 如果有需要可以参考下方官方文档

2 请求授权码

有了授权码就可以访问网站信息了.
上一步中的请求在用户确认授权后, GitHub 会向 redirect_uri 中传入的地址发起一次请求, 并附带一个参数 code, 该参数是一串随机字符串, 用于 GitHub 认证服务器确认身份, 也是下一步请求的参数.
如果上一步中请求携带了 state 参数, GitHub 的请求也会将此参数原封不动的带回, 这样就可以确定 GitHub 的这次请求是来自于用户的授权请求, 而不是第三方在得知 Client ID 后伪造的请求. 如果没有 state 参数或再次请求的值不匹配, 则可以确定该次请求来自第三方伪造.
在拿到 code 参数之后, 需要发起如下请求

POST https://github.com/login/oauth/access_token

该请求需要携带以下参数

参数 说明
client_id 必填 同上
client_secret 必填 OAuth 设置中申请的 secret
code 必填 GitHub 请求中的参数
redirect_uri 在发起授权后用户会被送往你的网站中的该地址

该请求会返回如下格式的字符串

access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer

如果想让该请求返回其他格式的字符串, 也可以在上一步的请求头中加入 Accept: application/jsonAccept: application/xml 来让该请求返回 jsonxml 格式的文本

3 访问信息

上一步的请求中拿到了 access_token, 这就是授权码.
访问

GET https://api.github.com/user

该请求需要在请求头中加入 Authorization: token OAUTH-TOKEN 其中 OAUTH-TOKEN 就是上一步的 access_token, 该请求就会返回用户的信息, 此时, 一次 OAuth 认证就完成了.

总结

可以看出 OAuth 其实和 TCP 的三次握手有些相似
TCP 的三次握手中
第一次: 客户端确定了自己的发信能力, 服务端确定了自己的收信能力和客户端的发信能力
第二次: 客户端确定了自己和服务器都可以收发, 服务端确定了自己的发信, 收信能力和客户端的发信能力
第三次: 客户端和服务端都互相确定了双方的收发能力, 通信建立.
而 OAuth 本质上也是用户-网站-与资源持有方互相交换凭证确认关系的一种方式.

参考文档

  1. GitHub OAuth 文档
  2. OAuth Wikipedia

抽象类和接口的区别

概述

在 Java 中, 接口和抽象类的使用方式很为相似.

抽象类:

  • 不能被实例化
  • 可以有抽象方法, 也可以有实例方法

接口:

  • 不能被实例化
  • Java 8 之前只能有抽象方法
  • 在 Java 8 中, 加入了 default 关键字, 这进一步减少了与抽象类的区别

可以看出这两者实现的功能基本是一致的, 所以我在使用时很多时候都会选择使用更 "纯粹" 的接口, 而不是抽象类.

那么? 抽象类为什么还存在?

抽象类的用处

首先, 存在即合理, 抽象类的存在肯定不只是照顾老项目.

可以来看一下以下代码:

1. 首先声明动作接口

public interface Action {
    /**
     * 如何飞
     */
    void fly();

    /**
     * 如何补充能量
     */
    void power();
}

2. 声明两个抽象类
抽象类 Bird 实现了 Action 的两个方法, 而 Plane 只实现了 power()

public abstract class Bird implements Action{
    @Override
    public void fly() {
        System.out.println("使用翅膀");
    }

    @Override
    public void power() {
        System.out.println("吃东西");
    }
}
public abstract class Plane implements Action{
    @Override
    public void power() {
        System.out.println("加油");
    }
}

3. 为每个抽象类声明多个子类
Bird

public class Bird1 extends Bird {

}
public class Bird2 extends Bird {
    @Override
    public void power() {
        System.out.println("吃虫子");
    }
}

Plane

public class Plane1 extends Plane{
    @Override
    public void fly() {
        System.out.println("轰轰轰");
    }
}
public class Plane2 extends Plane{
    @Override
    public void fly() {
        System.out.println("呜呜呜");
    }
}

4.定义执行类

public class Test {
    public static void main(String[] args) {
        Bird1 bird1 = new Bird1();
        Bird2 bird2 = new Bird2();
        Plane1 plane1 = new Plane1();
        Plane2 plane2 = new Plane2();

        bird1.fly();
        bird2.fly();
        plane1.fly();
        plane2.fly();

        bird1.power();
        bird2.power();
        plane1.power();
        plane2.power();
    }
}

输出如下

使用翅膀
使用翅膀
轰轰轰
呜呜呜
吃东西
吃虫子
加油
加油

类结构图如下
类结构图

可以看出, Bird1Bird2 都使用了父类的 fly() 方法, 虽然父类 Bird 已经实现了 power() 方法, 但 Bird2 重写了该方法, 所以 Bird2power() 并不和 Bird1 相同, Plane 的两个子类同理.

生物分为界门钢目科属种, 例如

马属于 动物界-脊索动物门-哺乳纲-奇蹄目-马科, 而奇蹄目下还有犀牛等.

牛属于 动物界-脊索动物门-哺乳纲-偶蹄目-牛科, 偶蹄目下还有猪、羊等.

对比上面代码的例子, 从界到纲, 可以类比为类 Action, 而奇蹄目和偶蹄目可以类比为 BirdPlane, 马、犀牛、牛、羊就可以类比为 BirdPlane 的子类, 有着父类共同有的特征, 也有着很多各自不同的特点.

抽象类更偏向于优化代码结构, 当然接口的 default 方法也可以实现这些, 但接口不可以定义成员变量, 用起来还会有诸多不便, 让接口保持 "纯粹", 也可以让自己的代码结构性更强, 更好维护.

pom.xml 文件标签解析

原博地址: 菜鸟教程


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">
    <!--父项目的坐标。如果项目中没有规定某个元素的值,那么父项目中的对应值即为项目的默认值。 坐标包括group ID,artifact ID和 
        version。 -->
    <parent>
        <!--被继承的父项目的构件标识符 -->
        <artifactId />
        <!--被继承的父项目的全球唯一标识符 -->
        <groupId />
        <!--被继承的父项目的版本 -->
        <version />
        <!-- 父项目的pom.xml文件的相对路径。相对路径允许你选择一个不同的路径。默认值是../pom.xml。Maven首先在构建当前项目的地方寻找父项 
            目的pom,其次在文件系统的这个位置(relativePath位置),然后在本地仓库,最后在远程仓库寻找父项目的pom。 -->
        <relativePath />
    </parent>
    <!--声明项目描述符遵循哪一个POM模型版本。模型本身的版本很少改变,虽然如此,但它仍然是必不可少的,这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。 -->
    <modelVersion>4.0.0</modelVersion>
    <!--项目的全球唯一标识符,通常使用全限定的包名区分该项目和其他项目。并且构建时生成的路径也是由此生成, 如com.mycompany.app生成的相对路径为:/com/mycompany/app -->
    <groupId>asia.banseon</groupId>
    <!-- 构件的标识符,它和group ID一起唯一标识一个构件。换句话说,你不能有两个不同的项目拥有同样的artifact ID和groupID;在某个 
        特定的group ID下,artifact ID也必须是唯一的。构件是项目产生的或使用的一个东西,Maven为项目产生的构件包括:JARs,源 码,二进制发布和WARs等。 -->
    <artifactId>banseon-maven2</artifactId>
    <!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型 -->
    <packaging>jar</packaging>
    <!--项目当前版本,格式为:主版本.次版本.增量版本-限定版本号 -->
    <version>1.0-SNAPSHOT</version>
    <!--项目的名称, Maven产生的文档用 -->
    <name>banseon-maven</name>
    <!--项目主页的URL, Maven产生的文档用 -->
    <url>http://www.baidu.com/banseon</url>
    <!-- 项目的详细描述, Maven 产生的文档用。 当这个元素能够用HTML格式描述时(例如,CDATA中的文本会被解析器忽略,就可以包含HTML标 
        签), 不鼓励使用纯文本描述。如果你需要修改产生的web站点的索引页面,你应该修改你自己的索引页文件,而不是调整这里的文档。 -->
    <description>A maven project to study maven.</description>
    <!--描述了这个项目构建环境中的前提条件。 -->
    <prerequisites>
        <!--构建该项目或使用该插件所需要的Maven的最低版本 -->
        <maven />
    </prerequisites>
    <!--项目的问题管理系统(Bugzilla, Jira, Scarab,或任何你喜欢的问题管理系统)的名称和URL,本例为 jira -->
    <issueManagement>
        <!--问题管理系统(例如jira)的名字, -->
        <system>jira</system>
        <!--该项目使用的问题管理系统的URL -->
        <url>http://jira.baidu.com/banseon</url>
    </issueManagement>
    <!--项目持续集成信息 -->
    <ciManagement>
        <!--持续集成系统的名字,例如continuum -->
        <system />
        <!--该项目使用的持续集成系统的URL(如果持续集成系统有web接口的话)。 -->
        <url />
        <!--构建完成时,需要通知的开发者/用户的配置项。包括被通知者信息和通知条件(错误,失败,成功,警告) -->
        <notifiers>
            <!--配置一种方式,当构建中断时,以该方式通知用户/开发者 -->
            <notifier>
                <!--传送通知的途径 -->
                <type />
                <!--发生错误时是否通知 -->
                <sendOnError />
                <!--构建失败时是否通知 -->
                <sendOnFailure />
                <!--构建成功时是否通知 -->
                <sendOnSuccess />
                <!--发生警告时是否通知 -->
                <sendOnWarning />
                <!--不赞成使用。通知发送到哪里 -->
                <address />
                <!--扩展配置项 -->
                <configuration />
            </notifier>
        </notifiers>
    </ciManagement>
    <!--项目创建年份,4位数字。当产生版权信息时需要使用这个值。 -->
    <inceptionYear />
    <!--项目相关邮件列表信息 -->
    <mailingLists>
        <!--该元素描述了项目相关的所有邮件列表。自动产生的网站引用这些信息。 -->
        <mailingList>
            <!--邮件的名称 -->
            <name>Demo</name>
            <!--发送邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建 -->
            <post>[email protected]</post>
            <!--订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建 -->
            <subscribe>[email protected]</subscribe>
            <!--取消订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建 -->
            <unsubscribe>[email protected]</unsubscribe>
            <!--你可以浏览邮件信息的URL -->
            <archive>http:/hi.baidu.com/banseon/demo/dev/</archive>
        </mailingList>
    </mailingLists>
    <!--项目开发者列表 -->
    <developers>
        <!--某个项目开发者的信息 -->
        <developer>
            <!--SCM里项目开发者的唯一标识符 -->
            <id>HELLO WORLD</id>
            <!--项目开发者的全名 -->
            <name>banseon</name>
            <!--项目开发者的email -->
            <email>[email protected]</email>
            <!--项目开发者的主页的URL -->
            <url />
            <!--项目开发者在项目中扮演的角色,角色元素描述了各种角色 -->
            <roles>
                <role>Project Manager</role>
                <role>Architect</role>
            </roles>
            <!--项目开发者所属组织 -->
            <organization>demo</organization>
            <!--项目开发者所属组织的URL -->
            <organizationUrl>http://hi.baidu.com/banseon</organizationUrl>
            <!--项目开发者属性,如即时消息如何处理等 -->
            <properties>
                <dept>No</dept>
            </properties>
            <!--项目开发者所在时区, -11到12范围内的整数。 -->
            <timezone>-5</timezone>
        </developer>
    </developers>
    <!--项目的其他贡献者列表 -->
    <contributors>
        <!--项目的其他贡献者。参见developers/developer元素 -->
        <contributor>
            <name />
            <email />
            <url />
            <organization />
            <organizationUrl />
            <roles />
            <timezone />
            <properties />
        </contributor>
    </contributors>
    <!--该元素描述了项目所有License列表。 应该只列出该项目的license列表,不要列出依赖项目的 license列表。如果列出多个license,用户可以选择它们中的一个而不是接受所有license。 -->
    <licenses>
        <!--描述了项目的license,用于生成项目的web站点的license页面,其他一些报表和validation也会用到该元素。 -->
        <license>
            <!--license用于法律上的名称 -->
            <name>Apache 2</name>
            <!--官方的license正文页面的URL -->
            <url>http://www.baidu.com/banseon/LICENSE-2.0.txt</url>
            <!--项目分发的主要方式: repo,可以从Maven库下载 manual, 用户必须手动下载和安装依赖 -->
            <distribution>repo</distribution>
            <!--关于license的补充信息 -->
            <comments>A business-friendly OSS license</comments>
        </license>
    </licenses>
    <!--SCM(Source Control Management)标签允许你配置你的代码库,供Maven web站点和其它插件使用。 -->
    <scm>
        <!--SCM的URL,该URL描述了版本库和如何连接到版本库。欲知详情,请看SCMs提供的URL格式和列表。该连接只读。 -->
        <connection>
            scm:svn:http://svn.baidu.com/banseon/maven/banseon/banseon-maven2-trunk(dao-trunk)
        </connection>
        <!--给开发者使用的,类似connection元素。即该连接不仅仅只读 -->
        <developerConnection>
            scm:svn:http://svn.baidu.com/banseon/maven/banseon/dao-trunk
        </developerConnection>
        <!--当前代码的标签,在开发阶段默认为HEAD -->
        <tag />
        <!--指向项目的可浏览SCM库(例如ViewVC或者Fisheye)的URL。 -->
        <url>http://svn.baidu.com/banseon</url>
    </scm>
    <!--描述项目所属组织的各种属性。Maven产生的文档用 -->
    <organization>
        <!--组织的全名 -->
        <name>demo</name>
        <!--组织主页的URL -->
        <url>http://www.baidu.com/banseon</url>
    </organization>
    <!--构建项目需要的信息 -->
    <build>
        <!--该元素设置了项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。 -->
        <sourceDirectory />
        <!--该元素设置了项目脚本源码目录,该目录和源码目录不同:绝大多数情况下,该目录下的内容 会被拷贝到输出目录(因为脚本是被解释的,而不是被编译的)。 -->
        <scriptSourceDirectory />
        <!--该元素设置了项目单元测试使用的源码目录,当测试项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。 -->
        <testSourceDirectory />
        <!--被编译过的应用程序class文件存放的目录。 -->
        <outputDirectory />
        <!--被编译过的测试class文件存放的目录。 -->
        <testOutputDirectory />
        <!--使用来自该项目的一系列构建扩展 -->
        <extensions>
            <!--描述使用到的构建扩展。 -->
            <extension>
                <!--构建扩展的groupId -->
                <groupId />
                <!--构建扩展的artifactId -->
                <artifactId />
                <!--构建扩展的版本 -->
                <version />
            </extension>
        </extensions>
        <!--当项目没有规定目标(Maven2 叫做阶段)时的默认值 -->
        <defaultGoal />
        <!--这个元素描述了项目相关的所有资源路径列表,例如和项目相关的属性文件,这些资源被包含在最终的打包文件里。 -->
        <resources>
            <!--这个元素描述了项目相关或测试相关的所有资源路径 -->
            <resource>
                <!-- 描述了资源的目标路径。该路径相对target/classes目录(例如${project.build.outputDirectory})。举个例 
                    子,如果你想资源在特定的包里(org.apache.maven.messages),你就必须该元素设置为org/apache/maven /messages。然而,如果你只是想把资源放到源码目录结构里,就不需要该配置。 -->
                <targetPath />
                <!--是否使用参数值代替参数名。参数值取自properties元素或者文件里配置的属性,文件在filters元素里列出。 -->
                <filtering />
                <!--描述存放资源的目录,该路径相对POM路径 -->
                <directory />
                <!--包含的模式列表,例如**/*.xml. -->
                <includes />
                <!--排除的模式列表,例如**/*.xml -->
                <excludes />
            </resource>
        </resources>
        <!--这个元素描述了单元测试相关的所有资源路径,例如和单元测试相关的属性文件。 -->
        <testResources>
            <!--这个元素描述了测试相关的所有资源路径,参见build/resources/resource元素的说明 -->
            <testResource>
                <targetPath />
                <filtering />
                <directory />
                <includes />
                <excludes />
            </testResource>
        </testResources>
        <!--构建产生的所有文件存放的目录 -->
        <directory />
        <!--产生的构件的文件名,默认值是${artifactId}-${version}。 -->
        <finalName />
        <!--当filtering开关打开时,使用到的过滤器属性文件列表 -->
        <filters />
        <!--子项目可以引用的默认插件信息。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置 -->
        <pluginManagement>
            <!--使用的插件列表 。 -->
            <plugins>
                <!--plugin元素包含描述插件所需要的信息。 -->
                <plugin>
                    <!--插件在仓库里的group ID -->
                    <groupId />
                    <!--插件在仓库里的artifact ID -->
                    <artifactId />
                    <!--被使用的插件的版本(或版本范围) -->
                    <version />
                    <!--是否从该插件下载Maven扩展(例如打包和类型处理器),由于性能原因,只有在真需要下载时,该元素才被设置成enabled。 -->
                    <extensions />
                    <!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。 -->
                    <executions>
                        <!--execution元素包含了插件执行需要的信息 -->
                        <execution>
                            <!--执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标 -->
                            <id />
                            <!--绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段 -->
                            <phase />
                            <!--配置的执行目标 -->
                            <goals />
                            <!--配置是否被传播到子POM -->
                            <inherited />
                            <!--作为DOM对象的配置 -->
                            <configuration />
                        </execution>
                    </executions>
                    <!--项目引入插件所需要的额外依赖 -->
                    <dependencies>
                        <!--参见dependencies/dependency元素 -->
                        <dependency>
                            ......
                        </dependency>
                    </dependencies>
                    <!--任何配置是否被传播到子项目 -->
                    <inherited />
                    <!--作为DOM对象的配置 -->
                    <configuration />
                </plugin>
            </plugins>
        </pluginManagement>
        <!--使用的插件列表 -->
        <plugins>
            <!--参见build/pluginManagement/plugins/plugin元素 -->
            <plugin>
                <groupId />
                <artifactId />
                <version />
                <extensions />
                <executions>
                    <execution>
                        <id />
                        <phase />
                        <goals />
                        <inherited />
                        <configuration />
                    </execution>
                </executions>
                <dependencies>
                    <!--参见dependencies/dependency元素 -->
                    <dependency>
                        ......
                    </dependency>
                </dependencies>
                <goals />
                <inherited />
                <configuration />
            </plugin>
        </plugins>
    </build>
    <!--在列的项目构建profile,如果被激活,会修改构建处理 -->
    <profiles>
        <!--根据环境参数或命令行参数激活某个构建处理 -->
        <profile>
            <!--构建配置的唯一标识符。即用于命令行激活,也用于在继承时合并具有相同标识符的profile。 -->
            <id />
            <!--自动触发profile的条件逻辑。Activation是profile的开启钥匙。profile的力量来自于它 能够在某些特定的环境中自动使用某些特定的值;这些环境通过activation元素指定。activation元素并不是激活profile的唯一方式。 -->
            <activation>
                <!--profile默认是否激活的标志 -->
                <activeByDefault />
                <!--当匹配的jdk被检测到,profile被激活。例如,1.4激活JDK1.4,1.4.0_2,而!1.4激活所有版本不是以1.4开头的JDK。 -->
                <jdk />
                <!--当匹配的操作系统属性被检测到,profile被激活。os元素可以定义一些操作系统相关的属性。 -->
                <os>
                    <!--激活profile的操作系统的名字 -->
                    <name>Windows XP</name>
                    <!--激活profile的操作系统所属家族(如 'windows') -->
                    <family>Windows</family>
                    <!--激活profile的操作系统体系结构 -->
                    <arch>x86</arch>
                    <!--激活profile的操作系统版本 -->
                    <version>5.1.2600</version>
                </os>
                <!--如果Maven检测到某一个属性(其值可以在POM中通过${名称}引用),其拥有对应的名称和值,Profile就会被激活。如果值 字段是空的,那么存在属性名称字段就会激活profile,否则按区分大小写方式匹配属性值字段 -->
                <property>
                    <!--激活profile的属性的名称 -->
                    <name>mavenVersion</name>
                    <!--激活profile的属性的值 -->
                    <value>2.0.3</value>
                </property>
                <!--提供一个文件名,通过检测该文件的存在或不存在来激活profile。missing检查文件是否存在,如果不存在则激活 profile。另一方面,exists则会检查文件是否存在,如果存在则激活profile。 -->
                <file>
                    <!--如果指定的文件存在,则激活profile。 -->
                    <exists>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/
                    </exists>
                    <!--如果指定的文件不存在,则激活profile。 -->
                    <missing>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/
                    </missing>
                </file>
            </activation>
            <!--构建项目所需要的信息。参见build元素 -->
            <build>
                <defaultGoal />
                <resources>
                    <resource>
                        <targetPath />
                        <filtering />
                        <directory />
                        <includes />
                        <excludes />
                    </resource>
                </resources>
                <testResources>
                    <testResource>
                        <targetPath />
                        <filtering />
                        <directory />
                        <includes />
                        <excludes />
                    </testResource>
                </testResources>
                <directory />
                <finalName />
                <filters />
                <pluginManagement>
                    <plugins>
                        <!--参见build/pluginManagement/plugins/plugin元素 -->
                        <plugin>
                            <groupId />
                            <artifactId />
                            <version />
                            <extensions />
                            <executions>
                                <execution>
                                    <id />
                                    <phase />
                                    <goals />
                                    <inherited />
                                    <configuration />
                                </execution>
                            </executions>
                            <dependencies>
                                <!--参见dependencies/dependency元素 -->
                                <dependency>
                                    ......
                                </dependency>
                            </dependencies>
                            <goals />
                            <inherited />
                            <configuration />
                        </plugin>
                    </plugins>
                </pluginManagement>
                <plugins>
                    <!--参见build/pluginManagement/plugins/plugin元素 -->
                    <plugin>
                        <groupId />
                        <artifactId />
                        <version />
                        <extensions />
                        <executions>
                            <execution>
                                <id />
                                <phase />
                                <goals />
                                <inherited />
                                <configuration />
                            </execution>
                        </executions>
                        <dependencies>
                            <!--参见dependencies/dependency元素 -->
                            <dependency>
                                ......
                            </dependency>
                        </dependencies>
                        <goals />
                        <inherited />
                        <configuration />
                    </plugin>
                </plugins>
            </build>
            <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径 -->
            <modules />
            <!--发现依赖和扩展的远程仓库列表。 -->
            <repositories>
                <!--参见repositories/repository元素 -->
                <repository>
                    <releases>
                        <enabled />
                        <updatePolicy />
                        <checksumPolicy />
                    </releases>
                    <snapshots>
                        <enabled />
                        <updatePolicy />
                        <checksumPolicy />
                    </snapshots>
                    <id />
                    <name />
                    <url />
                    <layout />
                </repository>
            </repositories>
            <!--发现插件的远程仓库列表,这些插件用于构建和报表 -->
            <pluginRepositories>
                <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素 -->
                <pluginRepository>
                    <releases>
                        <enabled />
                        <updatePolicy />
                        <checksumPolicy />
                    </releases>
                    <snapshots>
                        <enabled />
                        <updatePolicy />
                        <checksumPolicy />
                    </snapshots>
                    <id />
                    <name />
                    <url />
                    <layout />
                </pluginRepository>
            </pluginRepositories>
            <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。 -->
            <dependencies>
                <!--参见dependencies/dependency元素 -->
                <dependency>
                    ......
                </dependency>
            </dependencies>
            <!--不赞成使用. 现在Maven忽略该元素. -->
            <reports />
            <!--该元素包括使用报表插件产生报表的规范。当用户执行"mvn site",这些报表就会运行。 在页面导航栏能看到所有报表的链接。参见reporting元素 -->
            <reporting>
                ......
            </reporting>
            <!--参见dependencyManagement元素 -->
            <dependencyManagement>
                <dependencies>
                    <!--参见dependencies/dependency元素 -->
                    <dependency>
                        ......
                    </dependency>
                </dependencies>
            </dependencyManagement>
            <!--参见distributionManagement元素 -->
            <distributionManagement>
                ......
            </distributionManagement>
            <!--参见properties元素 -->
            <properties />
        </profile>
    </profiles>
    <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径 -->
    <modules />
    <!--发现依赖和扩展的远程仓库列表。 -->
    <repositories>
        <!--包含需要连接到远程仓库的信息 -->
        <repository>
            <!--如何处理远程仓库里发布版本的下载 -->
            <releases>
                <!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 -->
                <enabled />
                <!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。 -->
                <updatePolicy />
                <!--当Maven验证构件校验文件失败时该怎么做:ignore(忽略),fail(失败),或者warn(警告)。 -->
                <checksumPolicy />
            </releases>
            <!-- 如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的 
                策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 -->
            <snapshots>
                <enabled />
                <updatePolicy />
                <checksumPolicy />
            </snapshots>
            <!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库 -->
            <id>banseon-repository-proxy</id>
            <!--远程仓库名称 -->
            <name>banseon-repository-proxy</name>
            <!--远程仓库URL,按protocol://hostname/path形式 -->
            <url>http://192.168.1.169:9999/repository/</url>
            <!-- 用于定位和排序构件的仓库布局类型-可以是default(默认)或者legacy(遗留)。Maven 2为其仓库提供了一个默认的布局;然 
                而,Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default(默认)还是legacy(遗留)。 -->
            <layout>default</layout>
        </repository>
    </repositories>
    <!--发现插件的远程仓库列表,这些插件用于构建和报表 -->
    <pluginRepositories>
        <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素 -->
        <pluginRepository>
            ......
        </pluginRepository>
    </pluginRepositories>
 
 
    <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。 -->
    <dependencies>
        <dependency>
            <!--依赖的group ID -->
            <groupId>org.apache.maven</groupId>
            <!--依赖的artifact ID -->
            <artifactId>maven-artifact</artifactId>
            <!--依赖的版本号。 在Maven 2里, 也可以配置成版本号的范围。 -->
            <version>3.8.1</version>
            <!-- 依赖类型,默认类型是jar。它通常表示依赖的文件的扩展名,但也有例外。一个类型可以被映射成另外一个扩展名或分类器。类型经常和使用的打包方式对应, 
                尽管这也有例外。一些类型的例子:jar,war,ejb-client和test-jar。如果设置extensions为 true,就可以在 plugin里定义新的类型。所以前面的类型的例子不完整。 -->
            <type>jar</type>
            <!-- 依赖的分类器。分类器可以区分属于同一个POM,但不同构建方式的构件。分类器名被附加到文件名的版本号后面。例如,如果你想要构建两个单独的构件成 
                JAR,一个使用Java 1.4编译器,另一个使用Java 6编译器,你就可以使用分类器来生成两个单独的JAR构件。 -->
            <classifier></classifier>
            <!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来。欲知详情请参考依赖机制。 - compile :默认范围,用于编译 - provided:类似于编译,但支持你期待jdk或者容器提供,类似于classpath 
                - runtime: 在执行时需要使用 - test: 用于test任务时使用 - system: 需要外在提供相应的元素。通过systemPath来取得 
                - systemPath: 仅用于范围为system。提供相应的路径 - optional: 当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用 -->
            <scope>test</scope>
            <!--仅供system范围使用。注意,不鼓励使用这个元素,并且在新的版本中该元素可能被覆盖掉。该元素为依赖规定了文件系统上的路径。需要绝对路径而不是相对路径。推荐使用属性匹配绝对路径,例如${java.home}。 -->
            <systemPath></systemPath>
            <!--当计算传递依赖时, 从依赖构件列表里,列出被排除的依赖构件集。即告诉maven你只依赖指定的项目,不依赖项目的依赖。此元素主要用于解决版本冲突问题 -->
            <exclusions>
                <exclusion>
                    <artifactId>spring-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
            <!--可选依赖,如果你在项目B中把C依赖声明为可选,你就需要在依赖于B的项目(例如项目A)中显式的引用对C的依赖。可选依赖阻断依赖的传递性。 -->
            <optional>true</optional>
        </dependency>
    </dependencies>
    <!--不赞成使用. 现在Maven忽略该元素. -->
    <reports></reports>
    <!--该元素描述使用报表插件产生报表的规范。当用户执行"mvn site",这些报表就会运行。 在页面导航栏能看到所有报表的链接。 -->
    <reporting>
        <!--true,则,网站不包括默认的报表。这包括"项目信息"菜单中的报表。 -->
        <excludeDefaults />
        <!--所有产生的报表存放到哪里。默认值是${project.build.directory}/site。 -->
        <outputDirectory />
        <!--使用的报表插件和他们的配置。 -->
        <plugins>
            <!--plugin元素包含描述报表插件需要的信息 -->
            <plugin>
                <!--报表插件在仓库里的group ID -->
                <groupId />
                <!--报表插件在仓库里的artifact ID -->
                <artifactId />
                <!--被使用的报表插件的版本(或版本范围) -->
                <version />
                <!--任何配置是否被传播到子项目 -->
                <inherited />
                <!--报表插件的配置 -->
                <configuration />
                <!--一组报表的多重规范,每个规范可能有不同的配置。一个规范(报表集)对应一个执行目标 。例如,有1,2,3,4,5,6,7,8,9个报表。1,2,5构成A报表集,对应一个执行目标。2,5,8构成B报表集,对应另一个执行目标 -->
                <reportSets>
                    <!--表示报表的一个集合,以及产生该集合的配置 -->
                    <reportSet>
                        <!--报表集合的唯一标识符,POM继承时用到 -->
                        <id />
                        <!--产生报表集合时,被使用的报表的配置 -->
                        <configuration />
                        <!--配置是否被继承到子POMs -->
                        <inherited />
                        <!--这个集合里使用到哪些报表 -->
                        <reports />
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>
    <!-- 继承自该项目的所有子项目的默认依赖信息。这部分的依赖信息不会被立即解析,而是当子项目声明一个依赖(必须描述group ID和 artifact 
        ID信息),如果group ID和artifact ID以外的一些信息没有描述,则通过group ID和artifact ID 匹配到这里的依赖,并使用这里的依赖信息。 -->
    <dependencyManagement>
        <dependencies>
            <!--参见dependencies/dependency元素 -->
            <dependency>
                ......
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--项目分发信息,在执行mvn deploy后表示要发布的位置。有了这些信息就可以把网站部署到远程服务器或者把构件部署到远程仓库。 -->
    <distributionManagement>
        <!--部署项目产生的构件到远程仓库需要的信息 -->
        <repository>
            <!--是分配给快照一个唯一的版本号(由时间戳和构建流水号)?还是每次都使用相同的版本号?参见repositories/repository元素 -->
            <uniqueVersion />
            <id>banseon-maven2</id>
            <name>banseon maven2</name>
            <url>file://${basedir}/target/deploy</url>
            <layout />
        </repository>
        <!--构件的快照部署到哪里?如果没有配置该元素,默认部署到repository元素配置的仓库,参见distributionManagement/repository元素 -->
        <snapshotRepository>
            <uniqueVersion />
            <id>banseon-maven2</id>
            <name>Banseon-maven2 Snapshot Repository</name>
            <url>scp://svn.baidu.com/banseon:/usr/local/maven-snapshot</url>
            <layout />
        </snapshotRepository>
        <!--部署项目的网站需要的信息 -->
        <site>
            <!--部署位置的唯一标识符,用来匹配站点和settings.xml文件里的配置 -->
            <id>banseon-site</id>
            <!--部署位置的名称 -->
            <name>business api website</name>
            <!--部署位置的URL,按protocol://hostname/path形式 -->
            <url>
                scp://svn.baidu.com/banseon:/var/www/localhost/banseon-web
            </url>
        </site>
        <!--项目下载页面的URL。如果没有该元素,用户应该参考主页。使用该元素的原因是:帮助定位那些不在仓库里的构件(由于license限制)。 -->
        <downloadUrl />
        <!--如果构件有了新的group ID和artifact ID(构件移到了新的位置),这里列出构件的重定位信息。 -->
        <relocation>
            <!--构件新的group ID -->
            <groupId />
            <!--构件新的artifact ID -->
            <artifactId />
            <!--构件新的版本号 -->
            <version />
            <!--显示给用户的,关于移动的额外信息,例如原因。 -->
            <message />
        </relocation>
        <!-- 给出该构件在远程仓库的状态。不得在本地项目中设置该元素,因为这是工具自动更新的。有效的值有:none(默认),converted(仓库管理员从 
            Maven 1 POM转换过来),partner(直接从伙伴Maven 2仓库同步过来),deployed(从Maven 2实例部 署),verified(被核实时正确的和最终的)。 -->
        <status />
    </distributionManagement>
    <!--以值替代名称,Properties可以在整个POM中使用,也可以作为触发条件(见settings.xml配置文件里activation元素的说明)。格式是<name>value</name>。 -->
    <properties />
</project>

标题测试

Title1

Title2

Title3

Title4

Title4-1

Title4-1

Title3-1

Title3-2

Title2-1

Vue 打包项目过大优化

问题描述

我的服务器是腾讯云 CVM, 带宽 1M, 在博客的 Vue 前端项目上线后遇到了加载慢的问题, 一开始以为只是宽带的问题 (虽然就是带宽的问题), 但是看了 Chrome 的控制台, 发现了一个体积达到了 2.8M 的文件, 也就有了下面的文章

Vue 项目打包过大问题的解决方案

按需引入

对于 Vue 中引入的第三方模块, 打包时会在 js 文件夹下生成一个 chunk-vendors.xxx.js, 该文件会在网页加载时加载, 如果整体引入模块, 如组件库, 则会导致该文件过大, ( 我只引入了几个必要组件 + Ant Design Vue 就达到了 2.8M+ 相当恐怖 ) 在加载网页时需要先将静态文件加载完成后才会显示网页, 鉴于服务器的 1M 带宽, 这 2.8M 的文件竟然加载了 50 多秒 🤦‍♂️.于是

  1. import Antd from 'ant-design-vue' 改为 import {XXX, XXX} from 'ant-design-vue';Vue.use(XXX)
  2. 将非必要的组件及静态文件放入 public/index.html 中使用 CDN 引入

在 index.html 中引入静态资源

修改路由组件为懒加载

router/index.js 的引入方式从

import XXX from 'XXX';

修改为

const XXX = () => import('XXX');

原因参考: https://www.jianshu.com/p/11c1d85ccd71路由懒加载 一节

传输优化

此时的 chunk-vendors.xxx.js 文件已经从 2.8M 下降到了 800K 左右, 但是加载仍需要 20 多秒, 无法达到预期, 此时就需要用到 gzip

使用 gzip 压缩静态资源

gzip 可以将静态资源在服务器压缩后传输, 客户端拿到 .gz 文件后在浏览器中解压, ( 可以先创建 .gz 文件而不是等 nginx 创建 ) 从而达到加快传输和节省流量的目的

在 nginx 中开启 gzip

在配置文件 server 块中加入

# 如果能找到 .gz 文件, 则直接返回, 不启用服务端压缩
gzip_static on;
# 开启 gzip
gzip  on;
gzip_proxied any;
# 大于指定大小后开启压缩
gzip_min_length  1k;
# 请求压缩的缓冲区数量和大小,以 4k 为单位,32 为倍数。
gzip_buffers     4 16k;
gzip_http_version 1.0;
# 压缩级别数值与压缩比成正比
gzip_comp_level 5;
# 压缩文件类型
gzip_types text/plain application/javascript application/css text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 是否添加响应头 Vary: Accept-Encoding 建议开启。
gzip_vary on;

此时 chunk-vendors.xxx.js 文件就已经变成了 256K, 即使是 1M 的带宽加载速度也在可接受范围内

使用到的网站或工具

参考文章

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.