emby-server媒体库硬链接.md

现在emby和plex等媒体ggi库对于刮削的命名要求很高,而PT下载后我们却很难将下载的媒体资源添加到影音库中,因为要保持源文件名进行保种,而复制一份文件的话,又很占硬盘空间,造成浪费。
在这种情况下,使用Linux连接就能便捷得一石二鸟。

软链接和硬链接

软连接可以便捷地创立整个文件夹的连接,但软连接类似快捷方式,删除源文件后,连接就会失效。
这种情况下,如果你删除PT保种的文件,在媒体库中整理好的资源也会失效。
相比起来,硬链接更加方便,在创立连接后,即便删除了源文件,只要没有删除所有的硬链接文件,硬链接仍然有效。
但Linux的硬链接却有一个小小的缺点:为了避免递归问题,硬链接只能创建单个文件的连接,而无法连接整个文件夹。

linux硬链接脚本

Windows下有一些好用的硬链接工具,但在Linux系统下,却没有找到类似的工具,于是我就就从零搞起,网上东抄抄西抄抄,自己写了个简单的批量硬链接脚本,进行对文件夹的硬链接,在我的openmediavault系统(基于debian)下测试了几周,还没出过问题。
还没有在别的系统上测试过,但理论上适用于所有Linux系统,包括群晖、铁骑马、威联通、openmediavault、unas等等。

使用教程

  1. 编辑两个bash脚本文件hardlink.bash和2.bash,复制到Linux下的/usr/local/bin/文件夹中。
  2. 使用cd命令切换到想要让硬链接文件存在的文件夹,如test。
  3. 以root账户或具有root权限的账户执行:sudo bash hardlink.bash [你的PT下载影音资源所在的目录]

** 注:root权限下不再需要输入sudo,链接文件和源文件必须处于同一个硬盘之下,不能跨硬盘执行硬链接操作。**
如此一来,在test这个文件夹下,就出现了你想要硬链接的文件夹下的所有子文件夹和文件。
对链接文件进行修改文件名,删除操作,均不会影响源文件,仍然能pt保种,但修改链接文件内容,会造成源文件内容改变,同时,对于大部分的程序来说,硬链接文件和源文件是相同的。
如果感觉每次都需要输入很烦,可以创建一个计划任务,可以让系统自动创建硬链接。

hardlink.bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
PRE_IFS=$IFS
IFS=$'\n'
distdir=$1
echo $distdir
newdir=`basename $distdir`
mkdir $newdir
cd $newdir
filename=$(ls $distdir)
currentdir=`pwd`
mod=$currentdir
echo $currentdir
for file in $filename
do
echo $file
if [ -f $distdir/$file ]
then
ln $distdir/$file $currentdir/$file
fi
if [ -d $distdir/$file ]
then
cd $currentdir
mkdir $currentdir/$file
cd $currentdir/$file
bash /usr/local/bin/2.bash $distdir/$file
fi
done
# 任务执行完毕,把IFS还原回默认值
chmod -R 777 $mod
IFS=$PRE_IFS

2.bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
PRE_IFS=$IFS
IFS=$'\n'
currentdir=`pwd`
echo $currentdir
distdir=$1
echo $distdir
filename=$(ls $distdir)
for file in $filename
do
echo $file
if [ -f $distdir/$file ]
then
ln $distdir/$file $currentdir/$file
fi
if [ -d $distdir/$file ]
then
cd $currentdir
mkdir $currentdir/$file
cd $currentdir/$file
bash /usr/local/bin/${0##*/} $distdir/$file
fi
done
# 任务执行完毕,把IFS还原回默认值
IFS=$PRE_IFS
l

Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端。当然 Spring Boot 2.x 仍然支持 Jedis,并且你可以任意切换客户端。

Lettuce

Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如 Sentinel、集群、流水线、自动重新连接和 Redis 数据模型

Jedis 实现通过直接连接的 redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个 Jedis 实例增加物理连接。

Lettuce 的连接是基于 Netty 的,连接实例 (StatefulRedisConnection) 可以在多个线程间并发访问,因为 StatefulRedisConnection 是线程安全的,所以一个连接实例 (StatefulRedisConnection) 就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。        

Spring Boot 2.0 集成 redis

一般需要4步

  1. 引入依赖
  2. 配置 redis
  3. 自定义 RedisTemplate (推荐)
  4. 自定义 redis 操作类 (推荐)

引入依赖

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

<!-- lettuce pool 缓存连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<!--jackson-->
<!--<dependency>-->
<!-- <groupId>com.fasterxml.jackson.core</groupId>-->
<!-- <artifactId>jackson-databind</artifactId>-->
<!--</dependency>-->

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
  • 如果用的是 lettuce 客户端,需要引入 commons-pool2 连接池。
  • 如果想用 json 序列化 redis 的 value 值,需要引入 jackson

配置 redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# redis 服务端相关配置
# 服务器地址
spring.redis.host=localhost
# 端口号
spring.redis.port=6379
# 密码,默认为 null
spring.redis.password=
# 使用的数据库,默认选择下标为0的数据库
spring.redis.database=0
# 客户端超时时间,默认是2000ms
spring.redis.timeout=2000ms


## jedis 客户端配置(从 Spring Boot 2.x 开始,不再推荐使用 jedis 客户端)
## 建立连接最大等待时间,默认1ms,超出该时间会抛异常。设为-1表示无限等待,直到分配成功。
#spring.redis.jedis.pool.max-wait=1ms
## 最大连连接数,默认为8,负值表示没有限制
#spring.redis.jedis.pool.max-active=8
## 最大空闲连接数,默认8。负值表示没有限制
#spring.redis.jedis.pool.max-idle=8
## 最小空闲连接数,默认0。
#spring.redis.jedis.pool.min-idle=0


# lettuce 客户端配置(从 Spring Boot 2.x 开始,推荐使用 lettuce 客户端)
# 建立连接最大等待时间,默认1ms,超出该时间会抛异常。设为-1表示无限等待,直到分配成功。
spring.redis.lettuce.pool.max-wait=1ms
# 最大连连接数,默认为8,负值表示没有限制
spring.redis.lettuce.pool.max-active=8
# 最大空闲连接数,默认8。负值表示没有限制
spring.redis.lettuce.pool.max-idle=8
# 最小空闲连接数,默认0。
spring.redis.lettuce.pool.min-idle=0
# 设置关闭连接的超时时间
spring.redis.lettuce.shutdown-timeout=100ms

自定义 RedisTemplate

RedisTemplate 是 spring 为我们提供的 redis 操作类,通过它我们可以完成大部分 redis 操作。

只要我们引入了 redis 依赖,并将 redis 的连接信息配置正确,springboot 就会根据我们的配置会给我们生成默认 RedisTemplate。

但是默认生成的 RedisTemplate 有两个地方不是很符合日常开发中的使用习惯

  1. 默认生成的 RedisTemplate<K, V> 接收的keyvalue为泛型,经常需要类型转换,直接使用不是很方便
  2. 默认生成的 RedisTemplate 序列化时,使用的是 JdkSerializationRedisSerializer ,存储到 redis 中后,内容为二进制字节,不利于查看原始内容

对于第一个问题,一般习惯将 RedisTemplate<K, V> 改为 RedisTemplate<String, Object>,即接收的 keyString 类型,接收的 valueObject 类型 对于第二个问题,一般会把数据序列化为 json 格式,然后存储到 redis 中,序列化成 json 格式还有一个好处就是跨语言,其他语言也可以读取你存储在 redis 中的内容

为了实现上面两个目的,我们需要自定义自己的 RedisTemplate

如下,创建一个 config 类,在里面配置 自定义的 RedisTemplate

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Bean
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 配置 json 序列化器 - Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper()
// 扩展序列化器,增加对 java.time.* 包中时间类的序列化、反序列化支持
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
jacksonSerializer.setObjectMapper(objectMapper);

// 创建并配置自定义 RedisTemplateRedisOperator
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 将 key 序列化成字符串
template.setKeySerializer(new StringRedisSerializer());
// 将 hash 的 key 序列化成字符串
template.setHashKeySerializer(new StringRedisSerializer());
// 将 value 序列化成 json
template.setValueSerializer(jacksonSerializer);
// 将 hash 的 value 序列化成 json
template.setHashValueSerializer(jacksonSerializer);
// 设置连接器
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}

@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}

@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}

@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}

@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}

}

自定义 Redis 操作类

虽然 RedisTemplate 已经对 redis 的操作进行了一定程度的封装,但是直接使用还是有些不方便,实际开发中,一般会对 RedisTemplate 做近一步封装,形成一个简单、方便使用的Redis 操作类。

当然你也可以选择不封装,看个人喜好。

具体的封装类参考基于 RedisTemplate 自定义 Redis 操作类

Spring Cache

Spring Cache 是 Spring 为缓存场景提供的一套解决方案。通过使用 @CachePut@CacheEvict@Cacheable等注解实现对缓存的,存储、查询、删除等操作

当我们引入了 spring-boot-starter-data-redis 后,只要在带有@Configuration类上使用 @EnableCaching 注解 Spring Cache 就会被“激活”。

Spring Cache 会为我们配置默认的缓存管理器key生成器,但是缓存管理器对缓存的序列化和key生成器生成的key,不易阅读。建议自定义缓存管理器key生成器

如果用不上 Spring Cache ,可以不用管。

1
注意:Spring Cache 并不是只能使用 Redis 作为缓存容器,其他例如 MemCache 等缓存中间件,都支持。

配置 Spring Cache

img

1
2
3
4
5
## spring cache 配置
# 使用的缓存的类型
spring.cache.type=redis
# 通过 spring cache 注解添加的缓存 的到期时间,单位秒(这是一个自定义属性)
cache.expireTime=60

最重要的就是指定使用的缓存的类型
另外是一个自定义的变量,后面配置缓存管理器会用到

配置缓存管理器和 key 生成器

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

@Configuration
// 开启 Spring Cache
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

@Value("${cache.expireTime}")
// 缓存超时时间
private int cacheExpireTime;

/**
* 配置@Cacheable@CacheEvict等注解在没有指定Key的情况下,key生成策略
* 该配置作用于缓存管理器管理的所有缓存
* 最终生成的key 为 cache类注解指定的cacheNames::类名:方法名#参数值1,参数值2...
*
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuffer sb = new StringBuffer();
sb.append(target.getClass().getName());
sb.append(":");
sb.append(method.getName());
sb.append("#");
for (Object obj : params) {
sb.append(obj.toString());
sb.append(",");
}
return sb.substring(0, sb.length() - 1);
}
};
}


/**
* 配置缓存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 配置 json 序列化器 - Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(objectMapper);

//关键点,spring cache 的注解使用的序列化都从这来,没有这个配置的话使用的jdk自己的序列化,实际上不影响使用,只是打印出来不适合人眼识别
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
// 将 key 序列化成字符串
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 将 value 序列化成 json
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer))//value序列化方式
// 设置缓存过期时间,单位秒
.entryTtl(Duration.ofSeconds(cacheExpireTime))
// 不缓存空值
.disableCachingNullValues();

return RedisCacheManager.builder(factory)
.cacheDefaults(cacheConfig)
.build();
}
}

总结

网上 Spring Boot 集成 redis 的教程大多都是,将 redis 和 spring cache 一块配置,很容易让人产生误解。

其实 redis 和 spring cache 是两个不同的东西,所以,上面的教程我特意分为了两个配置文件。

你可以只使用 redis 而不使用 spring cache,也可以反过来。

那为什么两者经常放在一起去讨论呢?
原因在于两者也有一定的联系

站在 reids 的角度看,spring cache 提供了一种便捷的操作 reids 的途径,为缓存场景提供了优秀的解决方案。

站在 spring cache 的角度看, reids 提供了一种缓存容器,可以把缓存放在 reids 中。

缓存管理器对 reids 的操作也是通过 redisTemplate 实现的。

l

xkeysnail for ubuntu 键盘映射

1
2
3
sudo xkeysnail xkeysnail.py --device /dev/input/event4    'AT Translated Set 2 keyboard' 
xhost +SI:localuser:root
sudo xkeysnail --watch xkeysnail-gte60.py --device /dev/input/event24 'GT BLE60 0AEBCB Keyboard'
l

1、【对线面试官】今天来聊聊Java注解

什么是注解?

  • 注解在我的理解下,就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。

开发中用到的注

  1. 注解其实在开发中是非常常见的,比如我们在使用各种框架时(像我们Java程序员接触最多的还是Spring框架一套) ,就会用到非常多的注解,@Controller I@Param / @Select等等
  2. 一些项目也用到lombok的注解,@SIf4j/@Data等等
  3. 除了框架实现的注解,Java原生也有@ Overried、 @Deprecated、 @Functional Interface等基本注解
  4. 不过Java原生的基本注解大多数用于「标记」和「检查」
    • 原生Java除了这些提供基本注解之外,还有一种叫做元Annotation(元注解),所谓的元Annotation就是用来修饰注解的
    • 常用的元Annotation有@Retention和@Target
    • @Retention注解可以简单理解为设置注解的生命周期,而@Target表示这个注解可以修饰哪些地方(比如方法、还是成员变量、还是包等等)

自己定义过的注解,在项目里边用的

  1. 嗯,写过的。背景是这样的:我司有个监控告警系统,对外提供了客户端供我们自己使用。监控一般的指标就是QPS、RT和错误嘛。
  2. 原生的客户端需要在代码里指定上报这会导致这种监控的代码会跟业务代码混合,比较恶心。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void send(String userName) {
try {
// qps 上报
qps(params);
long startTime = System.currentTimeMillis();

// 构建上下文(模拟业务代码)
ProcessContext processContext = new ProcessContext();
UserModel userModel = new UserModel();
userModel.setAge("22");
userModel.setName(userName);
//...

// rt 上报
long endTime = System.currentTimeMillis();
rt(endTime - startTime);
} catch (Exception e) {

// 出错上报
error(params);
}
}
  1. 其实这种基础的监控信息,显然都可以通过AOP切面的方式去处理掉(可以看到都是方法级的)。而再用注解这个载体配置相关的信息,配合AOP解析就会比较优雅

  2. 要写自定义的注解,首先考虑我们是在什么时候解析这个注解。这就需要用到前面所说的@Retention注解,这个注解会修饰我们自定义注解生命周期。

  3. @Retention注解传入的是RetentionPolic y枚举,该枚举有三个常量,分别是SOU RCE、 CLASS和RUNTIME

  4. 理解这块就得了解从.java文件到class文件再到class被jvm加载的过程了。下面的图描述着从.java文件到编译为class文件的过程

  5. 从上面的图可以发现有个「注解抽象语法树」,这里其实就会去解析注解,然后做处理的逻辑。

  6. 所以重点来了,如果你想要在编译期间处理注解相关的逻辑,你需要继承AbstractProcessor并实现process方法。比如可以看到lombok就用AnnotationProcessor继承了AbstractProcessor。

  7. 一般来说,只要自定义的注解中@Retention注解设置为SOURCE和CLASS这俩个级别,那么就需要继承并实现

  8. 因为SOURCE和CLASS这俩个级别等加载到jvm的时候,注解就被抹除了

  9. 从这里又引申出:lombok的实现原理就是在这(为什么使用了个@Data这样的注解就能有set/get等方法了,就是在这里加上去的)

自定义注解的级别

  1. 一般来说,我们自己定义的注解都是RUNTIME级别的,因为大多数情况我们是根据运行时环境去做一些处理。
  2. 我们现实在开发的过程中写自定义注解需要配合反射来使用
  3. 因为反射是Java获取运行时的信息的重要手段
  4. 所以,我当时就用了自定义注解,在SpringAOP的逻辑处理中,判断是否带有自定义注解,如果有则将监控的逻辑写在方法的前后
  5. 这样,只要在方法上加上我的注解,那就可以有对方法监控的效果(RT、QPS、ERROR)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Around("@annotation(com.sanwai.service.openapi.monitor.Monitor)")
public Object antispan(ProceedingJoinPoint pjp) throws Throwable {

String functionName = pjp.getSignature().getName();
Map<String, String> tags = new HashMap<>();

logger.info(functionName);

tags.put("functionName", functionName);
tags.put("flag", "done");

monitor.sum(functionName, "start", 1);

//方法执行开始时间
long startTime = System.currentTimeMillis();

Object o = null;
try {
o = pjp.proceed();
} catch (Exception e) {
//方法执行结束时间
long endTime = System.currentTimeMillis();

tags.put("flag", "fail");
monitor.avg("rt", tags, endTime - startTime);

monitor.sum(functionName, "fail", 1);
throw e;
}

//方法执行结束时间
long endTime = System.currentTimeMillis();

monitor.avg("rt", tags, endTime - startTime);

if (null != o) {
monitor.sum(functionName, "done", 1);
}
return o;
}

总结

  1. 注解是代码的特殊标记,可以在编译、类加载、运行时被读取
  2. 其实对应的就是RetentionPolicy枚举三种级别
  3. SOURCE和CLASS级别需要继承AbstractProcessor,实现process方法去处理我们自定义的注解
  4. 而RUNTIME级别是我们日常开发用得最多了,配合Java反射机制可以在很多场景优化我们的代码

展示态度(嗯,总体来看,你对注解这块基础还是扎实的。)

  • 主要是在工作中遇到注解的时候就多看看原理是怎么实现的,然后遇到业务机会,还是会写写,优化优化下代码
l

Hexo博客进阶:为 Next 主题添加 Waline 评论系统

发表于 2022-01-20 分类于 Hexo博客 阅读次数: 44 Waline: 本文字数: 2.2k 阅读时长 ≈ 4 分钟

文章发出之后,往往我们想要得到读者更多地反馈,那么拥有一个评论系统是至关重要的。

本篇带大家通过一些简单的配置,在 Hexo Next 主题下添加 Waline 评论系统。

前言

在之前的 Hexo博客进阶:为Next主题添加Valine评论系统 | 谢同学的博客 (qianfanguojin.top) 文章中,我叙述了如何 在 Next主题下配置 Valine 评论系统。

但是,根据读者反馈,Valine 评论系统在 Next 主题高版本 (7.+) 以上已没有支持,且 Valine 已经很久没有更新维护了。不过,有大佬在 Valine 的基础之上开发了 Waline
这次,我们就来描述如何快速上手安装配置更加人性化且带后端的 Waline 评论系统。

1. 第一步,配置评论数据库

Waline 和 Valine 一样,也是支持基于 LeanCloud 作为数据存储的,但是 Waline 支持的部署方式更多:

Waline
Client Server Storage
@waline/client Vercel LeanCloud
MiniValine Deta CloudBase
AprilComment CloudBase MongoDB
InspireCloud MySQL
Railway SQLite
Render PostgreSQL
Docker GitHub
Virtual Host Deta Base
InspireCloud

为了方便,这里我只讲述最简单,零成本的数据库建立方法。

我们需要注册一个 Leancloud 国际版 的账号,注意,一定要是 国际版,国内版需要绑定备案的域名,比较麻烦。具体可以在注册时的左上角看到:

img

注册完成后,登录,然后我们找到创建应用

img

在这里填写你的应用名称,名称可以自己定义,然后,下面选择开发版 点击创建

然后点击应用进入设置。

img

点击应用凭证,取得我们 AppKeyApp id 、以及 MasterKey

img

数据库配置完毕,接下来安装服务端。

2. 安装服务端

由上面的表格可以看到,Waline 支持多种服务端,为了最简便上手,我们使用第一种方式,即在 Vercl 上安装服务端。首先,点击下面的按钮,一键部署:

Vercel

应该需要注册一个账号,支持使用 Github 账号直接登录:

img

登录后重新点进来,点击 Create

img

然后等待下面 Deploy 构建完成,点击 Go to Dashboard

img

找到 Settings => Environment Variables,配置环境变量:

img

我们需要配置三个环境变量,对应如下表:

Lean Cloud Vercel Environment
AppID LEAN_ID
AppKey LEAN_KEY
MasterKey LEAN_MASTER_KEY

img

提示

如果你使用 LeanCloud 国内版,请额外配置 LEAN_SERVER 环境变量,值为你绑定好的域名。

为了使环境变量生效,我们需要重新构建一次。在上方找到 Deployments ,选择第一个右边的三个点,点击 Redeploy 。

img

等待其构建结束,然后记住 DOMAINS 中的域名地址:

img

好了,服务端部署到此结束,下面我们开始在 Hexo Next 主题中配置客户端。

3. 在Hexo Next主题中配置

由于 Next 主题中并不自带 Waline 的评论配置,我们需要安装官方提供的插件。在 Hexo 根目录执行:

1
npm install @waline/hexo-next

找到 Next 的主题配置文件,在最后加上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Waline
# For more information: https://waline.js.org, https://github.com/walinejs/waline
waline:
enable: true #是否开启
serverURL: waline-server-pearl.vercel.app # Waline #服务端地址,我们这里就是上面部署的 Vercel 地址
placeholder: 请文明评论呀 # #评论框的默认文字
avatar: mm # 头像风格
meta: [nick, mail, link] # 自定义评论框上面的三个输入框的内容
pageSize: 10 # 评论数量多少时显示分页
lang: zh-cn # 语言, 可选值: en, zh-cn
# Warning: 不要同时启用 `waline.visitor` 以及 `leancloud_visitors`.
visitor: false # 文章阅读统计
comment_count: true # 如果为 false , 评论数量只会在当前评论页面显示, 主页则不显示
requiredFields: [] # 设置用户评论时必填的信息,[nick,mail]: [nick] | [nick, mail]
libUrl: # Set custom library cdn url

重新部署 Hexo ,就可以看到结果了。

据反馈,Hexo 似乎在 8.x 的版本使用 waline 比较稳定,如果出现 hexo g 出错,可尝试升级 hexo 版本。

4. 登录服务端

由于 Waline 有服务端,支持评论管理。我们需要注册一个账号作为管理员。

找到评论框,点击 登录 按钮,会弹出一个窗口,找到用户注册,默认第一个注册的用户为管理员,所以部署好一定要记得及时注册。

img

注册好,登录之后即可进入评论管理的后台,可以对评论进行管理。

l

hexo同步

以下操作在你的第二个平台上进行,并确定已安装 node.js & npm。

在你想要同步博客的文件夹下执行

1
2
3
4
5
6
7
8
9
10
git clone <远端博客仓库地址>
cd # 进入到主题文件夹
git clone <远端主题仓库地址>
cd # 进入到第三方主题文件夹
git checkout customize #切换到customize分支
# 回到 hexo 根目录,安装依赖
npm install hexo
npm install hexo-deployer-git
npm install hexo-cli -g
npm install

执行hexo指令

1
hexo clean && proxy4 hexo d -g
l
l

Hexo-Next 主题博客个性化配置(2022年更行版本)

网页预览:

swimminghao.netlify.app
在这里插入图片描述
因为本人比较喜欢简介风格的,所以整个界面都是简约风格的,一个好的博客,应该让人一眼就能看清楚技术分类,文章也应该就是文章,让人能够最好的阅读你的博客 这才是我们应该做的,所以没有太多花里胡哨的东西。

使用工具:

Git
Github
visual studio code
Chrome

Hexo简易安装

前置条件

软件版本

HEXO: 6.0.6
Hero-theme-next: 8.10.0

安装hexo

1
npm install -g hexo-cli

主题下载安装

进入命令行,下载 NexT 主题,输入:

1
git clone https://github.com/next-theme/hexo-theme-next/ themes/next

修改站点配置文件_config.yml,找到如下代码:

1
2
## Themes: https://hexo.io/themes/
theme: landscape => next

将 landscape 修改为 next 即可。

配置文件

在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于站点根目录下,主要包含 Hexo 本身的站点配置;另一份位于主题目录下,这份配置由主题作者提供,主要用于配置主题相关的选项。

为了描述方便,在以下说明中,将前者称为 **站点配置文件**, 后者称为 **主题配置文件**。

1
2
/hexo/_config.yml
/hexo/themes/next/_config.yml

修改语言

打开站点配置文件,搜索 language,找到如下代码:

1
2
3
author: authorName
language: zh-CN
timezone: Asia/Shanghai

新建标签及分类界面

打开 主题配置文件,搜索 menu,找到如下代码:

1
2
3
4
5
6
7
8
9
menu:
home: / || fa fa-home
about: /about/ || fa fa-user
tags: /tags/ || fa fa-tags
categories: /categories/ || fa fa-th
archives: /archives/ || fa fa-archive
#schedule: /schedule/ || fa fa-calendar
sitemap: /sitemap.xml || fa fa-sitemap
#commonweal: /404/ || fa fa-heartbeat

把 tags 和 categories 前面的 # 删除,

切换主题

next 主题自带四种样式

在主题配置文件/next/_config.yml中查找:scheme,找到如下代码:

1
2
3
4
5
6
# Schemes
scheme: Muse
#scheme: Mist
#scheme: Pisces
#scheme: Gemini
选择你喜欢的一种样式,去掉前面的 #,其他主题前加上 # 即可。

隐藏网页底部 powered By Hexo / 强力驱动

打开 themes/next/layout/_partials/footer.njk

找到:

1
2
3
4
5
6
{\%- if theme.footer.powered %}
<div class="powered-by">
{\%- set next_site = 'https://theme-next.js.org' if theme.scheme === 'Gemini' else 'https://theme-next.js.org/' + theme.scheme | lower + '/' %}
{{- __('footer.powered', next_url('https://hexo.io', 'Hexo') + ' & ' + next_url(next_site, 'NexT.' + theme.scheme)) }}
</div>
{\%- endif %}

把这段代码首尾分别加上:<!---->,或者直接删除。

主页文章添加阴影

参考: Hexo NexT 主题美化记录
打开themes\next\source\css\_common\components\post\index.styl文件,将post-block更改为如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (hexo-config('motion.transition.post_block')) {
.post-block{
margin-top: 60px;
margin-bottom: 60px;
padding: 25px;
background:rgba(255,255,255,0.9) none repeat scroll !important; //添加透明效果
-webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5);
-moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5);
}
.pagination, .comments {
opacity: 0;
}
}

页脚增加网站运行时间统计

  1. 打开themes/next/layout/_partials/footer.njk文件,在如下图位置加入代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{\%- if config.symbols_count_time.total_symbols or config.symbols_count_time.total_time %}
<div class="wordcount">
{\%- if config.symbols_count_time.total_symbols %}
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-chart-line"></i>
</span>
{\%- if theme.symbols_count_time.item_text_total %}
<span>{{ __('symbols_count_time.count_total') + __('symbol.colon') }}</span>
{\%- endif %}
<span title="{{ __('symbols_count_time.count_total') }}">{{ symbolsCountTotal(site) }}</span>
</span>
{\%- endif %}

{\%- if config.symbols_count_time.total_time %}
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-coffee"></i>
</span>
{\%- if theme.symbols_count_time.item_text_total %}
<span>{{ __('symbols_count_time.time_total') }} &asymp;</span>
{\%- endif %}
<span title="{{ __('symbols_count_time.time_total') }}">{{ symbolsTimeTotal(site, config.symbols_count_time.awl, config.symbols_count_time.wpm, __('symbols_count_time.time_minutes')) }}</span>
</span>
{\%- endif %}
</div>
{\%- endif %}

//此位置插入代码

{\%- if theme.busuanzi_count.enable %}
<div class="busuanzi-count">

倒计时代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<span id="sitetime"></span>
<script language=javascript>
function siteTime(){
window.setTimeout("siteTime()", 1000);
var seconds = 1000;
var minutes = seconds * 60;
var hours = minutes * 60;
var days = hours * 24;
var years = days * 365;
var today = new Date();
var todayYear = today.getFullYear();
var todayMonth = today.getMonth()+1;
var todayDate = today.getDate();
var todayHour = today.getHours();
var todayMinute = today.getMinutes();
var todaySecond = today.getSeconds();
/* Date.UTC() -- 返回date对象距世界标准时间(UTC)1970年1月1日午夜之间的毫秒数(时间戳)
year - 作为date对象的年份,为4位年份值
month - 0-11之间的整数,做为date对象的月份
day - 1-31之间的整数,做为date对象的天数
hours - 0(午夜24点)-23之间的整数,做为date对象的小时数
minutes - 0-59之间的整数,做为date对象的分钟数
seconds - 0-59之间的整数,做为date对象的秒数
microseconds - 0-999之间的整数,做为date对象的毫秒数 */
var t1 = Date.UTC(2022,01,04,00,00,00); //你的建站时间
var t2 = Date.UTC(todayYear,todayMonth,todayDate,todayHour,todayMinute,todaySecond);
var diff = t2-t1;
var diffYears = Math.floor(diff/years);
var diffDays = Math.floor((diff/days)-diffYears*365);
var diffHours = Math.floor((diff-(diffYears*365+diffDays)*days)/hours);
var diffMinutes = Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours)/minutes);
var diffSeconds = Math.floor((diff-(diffYears*365+diffDays)*days-diffHours*hours-diffMinutes*minutes)/seconds);
document.getElementById("sitetime").innerHTML=" Run for "+diffYears+" Year "+diffDays+" Days "+diffHours+" Hours "+diffMinutes+" m "+diffSeconds+" s";
}
siteTime();
</script>
  1. themes\next\source\css\main.styl文件中给倒计时添加样式

不生效

1
2
3
4
5
#sitetime {
background-image: -webkit-linear-gradient(left, #aa4b6b, #6b6b83, #3b8d99);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}

浏览页面显示当前浏览进度

打开 themes/next/_config.yml,搜索关键字 scrollpercent,把 false 改为 true。

效果图:
在这里插入图片描述

Local Search本地搜索

安装插件hexo-generator-searchdb,执行以下命令:

1
npm install hexo-generator-searchdb --save	

修改hexo/_config.yml站点配置文件,新增以下内容到任意位置:

1
2
3
4
5
search:
path: search.xml
field: post
format: html
limit: 10000

编辑 主题配置文件,启用本地搜索功能:

1
2
3
# Local search
local_search:
enable: true

效果图:
在这里插入图片描述

设置网站图标

EasyIcon 中找一张(32 * 32)的 ico 图标,或者去别的网站下载或者制作,并将图标名称改为 favicon.ico,然后把图标放在 /themes/next/source/images 里,并且修改主题配置文件:

1
2
Put your favicon.ico into `hexo-site/source/` directory.
favicon: /favicon.ico

修改文章底部的#号的标签,改为图标

修改模板/themes/next/layout/_macro/post.swig

搜索 rel="tag">{{ tag_indicate }},将 {{ tag_indicate }} 换成<i class="fa fa-tag"></i>

效果图:
在这里插入图片描述

文章分享功能

打开themes/next/_config.yml 搜索关键字needmoreshare2 修改为下面设置

用npm卸载掉hexo-next-share,搜索所有hexo-next-share文件夹删除干净,然后切换到网站文件夹下,运行npm install theme-next/hexo-next-share --save,将以下代码都复制进主题配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# NeedMoreShare2
# Dependencies: https://github.com/theme-next/theme-next-needmoreshare2
# For more information: https://github.com/revir/need-more-share2
# iconStyle: default | box
# boxForm: horizontal | vertical
# position: top / middle / bottom + Left / Center / Right
# networks:
# Weibo | Wechat | Douban | QQZone | Twitter | Facebook | Linkedin | Mailto | Reddit | Delicious | StumbleUpon | Pinterest
# GooglePlus | Tumblr | GoogleBookmarks | Newsvine | Evernote | Friendfeed | Vkontakte | Odnoklassniki | Mailru
needmoreshare:
enable: true
cdn:
js: //cdn.jsdelivr.net/gh/theme-next/theme-next-needmoreshare2@1/needsharebutton.min.js
css: //cdn.jsdelivr.net/gh/theme-next/theme-next-needmoreshare2@1/needsharebutton.min.css
postbottom:
enable: true
options:
iconStyle: default
boxForm: horizontal
position: middleCenter
networks: Weibo,Wechat,Douban,QQZone,Twitter,Facebook
float:
enable: false
options:
iconStyle: default
boxForm: horizontal
position: middleCenter
networks: Weibo,Wechat,Douban,QQZone,Twitter,Facebook


# Likely Share
# See: https://ilyabirman.net/projects/likely/, https://github.com/ilyabirman/Likely
# Likely supports four looks, nine social networks, any button text.
# You are free to modify the text value and order of any network.
likely:
enable: false
cdn:
js: //cdn.jsdelivr.net/npm/ilyabirman-likely@2/release/likely.min.js
css: //cdn.jsdelivr.net/npm/ilyabirman-likely@2/release/likely.min.css
look: light # available values: normal, light, small, big
networks:
twitter: Tweet
facebook: Share
linkedin: Link
gplus: Plus
vkontakte: Share
odnoklassniki: Class
telegram: Send
whatsapp: Send
pinterest: Pin

# share.js
# See: https://github.com/overtrue/share.js
# networks: weibo,qq,wechat,tencent,douban,qzone,linkedin,diandian,facebook,twitter,google
sharejs:
enable: false
cdn:
js: //cdn.jsdelivr.net/npm/social-share.js@1/dist/js/social-share.min.js
css: //cdn.jsdelivr.net/npm/social-share.js@1/dist/js/social-share.min.css
networks: weibo,qq,wechat,tencent,douban,qzone,linkedin,diandian,facebook,twitter,google
wechat_qrcode:
title: share.title
prompt: share.prompt

效果图:
postbottom为文章末尾分享 float则是在页面侧端分享
在这里插入图片描述

文章加密访问

参考链接: hexo文章加密访问

增加文章字数统计及阅读时常功能

安装字数统计插件 npm i hexo-symbols-count-time
hexo_config.yml下找到# Extensions在下面配置插件配置如下

1
2
3
4
5
6
7
# 字数统计插件 npm i hexo-symbols-count-time
symbols_count_time:
symbols: true # 文章字数统计
time: true # 文章时长统计
total_symbols: true # 全局字数统计
total_time: true # 全局时长统计
exclude_codeblock: false # 排除代码字数统计

文章置顶功能

移除默认安装的插件:

npm uninstall hexo-generator-index --save
安装新插件:

npm install hexo-generator-index-pin-top --save
最后编辑有这需求的相关文章时,在Front-matter(文件最上方以—分隔的区域)加上一行:

1
top: true

如果你置顶了多篇,怎么控制顺序呢?设置top的值(大的在前面),比如:

1
2
3
4
5
6
7
8
# Post a.md
title: a
top: 1

# Post b.md
title: b
top: 10
1234567

文章 b 便会显示在文章 a 的前面

设置置顶图标
打开/themes/next/layout/_macro/post.swig文件,在<div class="post-meta-container">下方,插入如下代码:

1
2
3
4
5
{\% if post.top %}
<i class="fa fa-thumb-tack"></i>
<font color=7D26CD>置顶</font>
<span class="post-meta-divider">|</span>
{\% endif %}

在这里插入图片描述

修改[Read More]按钮样式

修改themes/next/source/css/_common/components/post/index.styl文件,加入自定义样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// [Read More]按钮样式
.post-button .btn {
color: #555 !important;
background-color: rgb(255, 255, 255);
border-radius: 3px;
font-size: 15px;
box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.35);
border: none !important;
transition-property: unset;
padding: 0px 15px;
}

.post-button .btn:hover {
color: rgb(255, 255, 255) !important;
border-radius: 3px;
font-size: 15px;
box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.35);
background-image: linear-gradient(90deg, #a166ab 0%, #ef4e7b 25%, #f37055 50%, #ef4e7b 75%, #a166ab 100%);
}

效果图:
在这里插入图片描述

修改 阅读全文 前显示文字数量即位置

打开 themes/next/_config.yml,搜索关键字 auto_excerpt, 修改length即可修改阅读全文前显示文字数量

1
2
3
auto_excerpt:
enable: true
length: 150

或者在文章中任意位置添加<!-- more -->

建议在文章中加入 <!-- more -->
自定义 [Read More] 按钮之前要显示的内容!

修改链接文字样式

打开themes/next/source/css/_common/components/post/index.styl添加以下代码:

1
2
3
4
5
6
7
8
.post-body p a{
color: #0593d3;
border-bottom: none;
&:hover {
color: #ff106c;
text-decoration: underline;
}
}

效果图:
在这里插入图片描述

头像设置圆形,停留旋转效果

修改next主题配置文件,修改成以下代码:

1
2
3
4
5
6
7
8
9
# Sidebar Avatar
avatar:
# Replace the default image and set the url here.
url: /images/lion.png
# lion.png放置在next/source/images文件夹下
# If true, the avatar will be displayed in circle.
rounded: true
# If true, the avatar will be rotated with the cursor.
rotated: false

效果图:
在这里插入图片描述

增加近期文章

hexo主站source 目录下创建 _data/sidebar.njk 文件,加入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{# recent posts #}
{\% if theme.recent_posts %}
<div class="links-of-blogroll motion-element {{ "links-of-blogroll-" + theme.recent_posts_layout }}">
<div class="links-of-blogroll-title">
<!-- modify icon to fire by szw -->
<i class="fa fa-history fa-{{ theme.recent_posts_icon | lower }}" aria-hidden="true"></i>
{{ theme.recent_posts_title }}
</div>
<ul class="links-of-blogroll-list">
{\% set posts = site.posts.sort('-date') %}
{\% for post in posts.slice('0', '5') %}
<li class="recent_posts_li">
<a href="{{ url_for(post.path) }}" title="{{ post.title }}" target="_blank">{{ post.title }}</a>
</li>
{\% endfor %}
</ul>
</div>
{\% endif %}

并修改theme主题配置文件,取消sidebar的注释:

1
2
3
4
5
6
7
8
9
10
11
12
custom_file_path:
#head: source/_data/head.njk
#header: source/_data/header.njk
sidebar: source/_data/sidebar.njk
#postMeta: source/_data/post-meta.njk
#postBodyEnd: source/_data/post-body-end.njk
#footer: source/_data/footer.njk
footer: source/_data/footer.swig
#bodyEnd: source/_data/body-end.njk
#variable: source/_data/variables.styl
#mixin: source/_data/mixins.styl
style: source/_data/styles.styl

编辑themes/next/source/css/_common/outline/sidebar/sidebar-blogroll.styl文件,标题溢出隐藏

1
2
3
4
5
6
7
8
9
10
11
.links-of-blogroll-list {
list-style: none;
margin: 0;
padding: 0;
text-align: cengter;
display: block;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

themes/next/_config.yml中修改成下方代码

1
2
3
4
# 近期文章设置
recent_posts_title: 近期文章
recent_posts_layout: block
recent_posts: true

效果图:
在这里插入图片描述

文章末尾添加”本文结束”标记

  • 在目录themes/next/layout/_macro/下添加passage-end-tag.swig,内容如下:

    1
    2
    3
    4
    5
    <div>
    {\% if not is_index %}
    <div style="text-align:center;color: #ccc;font-size:20px;">------------- 本 文 结 束&nbsp&nbsp&nbsp&nbsp&nbsp感 谢 您 的 阅 读 -------------</div>
    {\% endif %}
    </div>
  • 打开themes/next/layout/_macro/post.swig文件,新增内容如下:

  • ```HTML

    //以下为新增代码

    {\% if not is_index %} {\% include 'passage-end-tag.swig' %} {\% endif %}
    1
    2
    3
    4
    5
    6
      
    - 打开`主题配置文件`,添加代码如下:
    - ```js
    # 文章末尾添加“本文结束”标记
    passage_end_tag:
    enabled: true

为博客加上妹子

live2d与busuanzi组件有bug冲突,安装了live2d,busuanzi就失效,所以我没有使用。

npm install -save hexo-helper-live2d
然后在在 hexo 的 _config.yml中添加参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
live2d:
enable: true
scriptFrom: local
pluginRootPath: live2dw/
pluginJsPath: lib/
pluginModelPath: assets/
tagMode: false
log: false
model:
use: live2d-widget-model-<你喜欢的模型名字>s
display:
position: right
width: 150
height: 300
mobile:
show: true

12345678910111213141516

可供选择模型:

  • live2d-widget-model-chitose
  • live2d-widget-model-epsilon2_1
  • live2d-widget-model-gf
  • live2d-widget-model-haru/01 (use npm install --save live2d-widget-model-haru)
  • live2d-widget-model-haru/02 (use npm install --save live2d-widget-model-haru)
  • live2d-widget-model-haruto
  • live2d-widget-model-hibiki
  • live2d-widget-model-hijiki
  • live2d-widget-model-izumi
  • live2d-widget-model-koharu
  • live2d-widget-model-miku
  • live2d-widget-model-ni-j
  • live2d-widget-model-nico
  • live2d-widget-model-nietzsche
  • live2d-widget-model-nipsilon
  • live2d-widget-model-nito
  • live2d-widget-model-shizuku
  • live2d-widget-model-tororo
  • live2d-widget-model-tsumiki
  • live2d-widget-model-unitychan
  • live2d-widget-model-wanko
  • live2d-widget-model-z16

在站点目录下建文件夹live2d_models

再在live2d_models下建文件夹<你喜欢的模型名字>,

再在<你喜欢的模型名字>下建json文件:<你喜欢的模型名字>.model.json

安装模型。在命令行(即Git Bash)运行以下命令即可:

1
npm install --save live2d-widget-model-<你喜欢的模型名字>

复制你喜欢的模型名字:

代码块复制选项

Next6 中自带了复制代码按钮,Next5 需要自己手动配置。

搜索 codeblock,找到如下配置:

1
2
3
4
5
codeblock:
border_radius: 8 # 按钮圆滑度
copy_button: # 设置是否开启代码块复制按钮
enable: true
show_result: true # 是否显示复制成功信息

修改加载特效

由于网页不可能一直都秒进,总会等待一段时间的,所以可以设置顶部加载条。Next 已经集成了很多加载特效,可以在下面选项中在线调试测试一下。

next主题配置文件搜索pace,找到如下代码:

1
2
3
4
5
6
7
8
9
10
11
# Progress bar in the top during page loading.
G# For more information: https://github.com/CodeByZach/pace
pace:
enable: true
# All available colors:
# black | blue | green | orange | pink | purple | red | silver | white | yellow
color: blue
# All available themes:
# big-counter | bounce | barber-shop | center-atom | center-circle | center-radar | center-simple
# corner-indicator | fill-left | flat-top | flash | loading-bar | mac-osx | material | minimal
theme: loading-bar

修改文章链接

在做次优化之前,hexo-next文章链接默认的生成规则是::year/:month/:day/:title,是按照年、月、日、标题来生成的。
比如:https://zxiaoxuan.github.io/2019/08/12/hello-world/ 这样,如果文章标题是中文的话,URL链接是也会是中文,
在这里插入图片描述

那么要生存简洁且唯一的URL,怎么办呢

安装插件

1
npm install hexo-abbrlink --save

执行此命令可能会不成功,提示你缺少相应的依赖,比如babel-eslint、mini-css-extract-plugin、webpack-cli…
使用npm命令安装即可,比如npm install eslint@4.x babel-eslint@8 –save-dev

修改根目录站点配置文件config.yml,改为:

1
2
3
4
permalink: posts/:abbrlink/
abbrlink:
alg: crc32 #算法: crc16(default) and crc32
rep: hex #进制: dec(default) and hex

生成的链接将会是这样的(官方样例):
四种可供选择

1
2
3
4
5
6
7
8
9
10
11
crc16 & hex
https://post.zz173.com/posts/66c8.html

crc16 & dec
https://post.zz173.com/posts/65535.html
crc32 & hex
https://post.zz173.com/posts/8ddf18fb.html

crc32 & dec
https://post.zz173.com/posts/1690090958.html
12345678910

生成完后,原md文件的Front-matter 内会增加abbrlink 字段,值为生成的ID 。这个字段确保了在我们修改了Front-matter 内的博客标题title或创建日期date字段之后而不会改变链接地址。

评论 Waline 增强版

参考链接Hexo NexT Waline评论

各版块透明度修改

内容板块透明
博客根目录 themes\next\source\css\_schemes\Pisces\_layout.styl文件 .content-wrap 标签下 background: white修改为:

1
background: rgba(255,255,255,0.7); //0.7是透明度

菜单栏背景
博客根目录 themes\next\source\css\_schemes\Pisces\_layout.styl文件.header-inner标签下 background: white修改为:

1
background: rgba(255,255,255,0.7); //0.7是透明度

站点概况背景
博客根目录themes\next\source\css\_schemes\Pisces\_sidebar.styl 文件.sidebar-inner 标签下 background: white修改为:

1
background: rgba(255,255,255,0.7); //0.7是透明度

然后修改博客根目录themes\next\source\css\_schemes\Pisces\_layout.styl文件.sidebar 标签下 background: $body-bg-color修改为:

1
background: rgba(255,255,255,0.7); //0.7是透明度

按钮背景
博客根目录themes\next\source\css\_common\components\post\post-button.styl 同上修改对应位置为 background: transparent;

标签修改

打开themes/next/layout/page.swig

修改这里可以修改标签页的标签显示
在这里插入图片描述

在这里添加东西会在标签页面上显示
在这里插入图片描述

彩色标签云

/themes/next/layout/目录下,新增tag-color.swig文件,加入下方代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<script type="text/javascript">
var alltags = document.getElementsByClassName('tag-cloud-tags');
var tags = alltags[0].getElementsByTagName('a');
for (var i = tags.length - 1; i >= 0; i--) {
var r=Math.floor(Math.random()*75+130);
var g=Math.floor(Math.random()*75+100);
var b=Math.floor(Math.random()*75+80);
tags[i].style.background = "rgb("+r+","+g+","+b+")";
}
</script>

<style>
.tag-cloud-tags{
/*font-family: Helvetica, Tahoma, Arial;*/
/*font-weight: 100;*/
text-align: center;
counter-reset: tags;
}
.tag-cloud-tags a{
border-radius: 6px;
padding-right: 5px;
padding-left: 5px;
margin: 8px 5px 0px 0px;
}
.tag-cloud-tags a:before{
content: "?";
}

.tag-cloud-tags a:hover{
box-shadow: 0px 5px 15px 0px rgba(0,0,0,.4);
transform: scale(1.1);
/*box-shadow: 10px 10px 15px 2px rgba(0,0,0,.12), 0 0 6px 0 rgba(104, 104, 105, 0.1);*/
transition-duration: 0.15s;
}
</style>

在/themes/next/layout/page.swig/中引入tag-color.swig:

在下方加上 {\% include 'tag-color.swig' %} 代码

1
2
3
4
5
6
7
8
9
 <div class="tag-cloud">
<!-- <div class="tag-cloud-title">
{{ _p('counter.tag_cloud', site.tags.length) }}
</div> -->
<div class="tag-cloud-tags" id="tags">
{{ tagcloud({min_font: 16, max_font: 16, amount: 300, color: true, start_color: '#FFF', end_color: '#FFF'}) }}
</div>
</div>
+ {\% include 'tag-color.swig' %}

或者将上方代码直接添加到下方

在这里插入图片描述

将标签云放到首页

在路径:/themes/next/layout/index.swig

{\% block content %}下面添加下方代码

1
2
3
4
5
6
7
8
9
10
{\% block content %}

<div class="tag-cloud">
<div class="tag-cloud-tags" id="tags">
{{ tagcloud({min_font: 16, max_font: 16, amount: 300, color: true, start_color: '#fff', end_color: '#fff'}) }}
</div>
</div>
<br>

{\% include 'tag-color.swig' %}

在这里插入图片描述

归档页美化

修改/themes/next/layout/_macro/post-collapse.swig后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{\% macro render(post) %}

<article class="post post-type-{{ post.type | default('normal') }}" itemscope itemtype="http://schema.org/Article">
<header class="post-header">

<{\% if theme.seo %}h3{\% else %}h2{\% endif %} class="post-title">
{\% if post.link %}{# Link posts #}
<a class="post-title-link post-title-link-external" target="_blank" href="{{ url_for(post.link) }}" itemprop="url">
{{ post.title or post.link }}
<i class="fa fa-external-link"></i>
</a>
{\% else %}
<a class="post-title-link" href="{{ url_for(post.path) }}" itemprop="url">
{\% if post.type === 'picture' %}
{{ post.content }}
{\% else %}
<span itemprop="name">{{ post.title | default(__('post.untitled')) }}</span>
{\% endif %}
</a>
{\% endif %}
</{\% if theme.seo %}h3{\% else %}h2{\% endif %}>

<div class="post-meta">
<time class="post-time" itemprop="dateCreated"
datetime="{{ moment(post.date).format() }}"
content="{{ date(post.date, config.date_format) }}" >
{{ date(post.date, 'MM-DD') }}
</time>
</div>

</header>
</article>

{\% endmacro %}
l

版本:1.6

使用 Docker 部署 Halo 和 MySQL

简介

该章节我们将分三种情况为您说明该如何同时使用 Docker + MySQL 来部署 Halo

前提条件: 我们默认您的机器上已经安装好 Docker

  • 如果你想完全通过 Docker 运行 MySQLHalo 请参考小节《统一使用 Docker 安装》
  • 如果你已经有 Docker部署的 MySQL,想安装 Halo 请参考小节《MySQL 部署在 Docker 如何使用 Docker 安装 Halo》
  • 如果你已有 MySQL 但部署在宿主机,想通过 Docker 安装 Halo 请参考小节《MySQL 在宿主机如何通过 Docker 安装 Halo》

统一使用 Docker 安装

如果你的机器上没有现成的 MySQL 可供使用,那么您可以选择使用 Docker 来运行 MySQLHalo

  1. 创建 Docker 自定义桥接网络
1
docker network create halo-net

提示

如果你之前有 Docker 使用经验,你可能已经习惯了使用 --link 参数来使容器互联。

但随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 –link 参数。 Docker 官方文档中称:该–link 标志是 Docker 的遗留功能。它可能最终会被删除。除非您确定需要继续使用它,否则我们建议您使用用户定义的网络来促进两个容器之间的通信,而不是使用 –link。

  1. 拉取 MySQL 镜像
1
docker pull mysql:8.0.27
  1. 创建 MySQL 数据目录
1
mkdir -p ~/.halo/mysql
  1. 启动 MySQL 实例
1
docker run --name some-mysql -v ~/.halo/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw --net halo-net --restart=unless-stopped -d mysql:8.0.27

注意: 请将 my-secret-pw 修改为自己需要的密码后再执行,密码尽量包含小写字母、大写字母、数字和特殊字符且长度超过 8 位。

释意

1
-e MYSQL_ROOT_PASSWORD=my-secret-pw`: 指定`MySQL`的登录密码为 `my-secret-pw

-v ~/.halo/mysql:/var/lib/mysql 命令: 将宿主机的目录 ~/.halo/mysql 挂载到容器内部的目录 /var/lib/mysql,默认情况下 MySQL 将向 ~/.halo/mysql 写入其数据文件。

--net halo-net: 将该容器加入到 halo-net 网络,连接到 halo-net 网络的任何其他容器都可以访问 some-mysql容器上的所有端口。

  1. 进入 MySQL 容器中登录 MySQL 并创建 Halo 需要的数据库
  • (1) some-mysql 为 MySQL 实例的容器名称

    1
    docker exec -it some-mysql /bin/bash
  • (2) 登录 MySQL

    1
    mysql -u root -p
  • (3) 输入 MySQL 数据库密码

  • (4) 创建数据库

    1
    create database halodb character set utf8mb4 collate utf8mb4_bin;
  • (5) 使用 exit退出MySQL 并退出容器

  1. 创建 Halo 工作目录
1
mkdir ~/.halo && cd ~/.halo
  1. 下载示例配置文件到工作目录
1
wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml
  1. 编辑配置文件,配置数据库,其他配置请参考参考配置
1
vim application.yaml

你需要做如下几个步骤:

  • 注释 H2 database configuration.部分
  • 启用 MySQL database configuration.部分
  • 修改 datasource 下的 url 中的 ip 地址部分为容器名称并修改密码为您设定的 MySQL 密码

修改后的内容如下:

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://some-mysql:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: my-secret-pw
  1. 创建 Halo 容器实例
1
docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --net halo-net --restart=unless-stopped halohub/halo:1.6.0
  1. 打开 http://ip:端口号 即可看到安装引导界面。

MySQL 部署在 Docker 如何使用 Docker 安装 Halo

如果您已有 Docker 部署的 MySQL 实例,那么为了保证 HaloMySQL 两个容器的网络可以互通,和上文同样的思路可以创建一个网络让 MySQLHalo 都加入进来。

  1. 使用 docker ps 来查看的你 MySQL 容器实例的名称或 container id, 例如 some-mysql
  2. 创建一个桥接网络,让 MySQL 加入,首先使用 docker network ls 来查看一下都有哪些网络名称,起一个不会冲突的网络名称,例如 halo-net,其次让已经存在的 MySQL 容器实例加入到该网络中
1
docker network connect halo-net some-mysql
  1. 同之前一样创建 Halo 工作目录
1
mkdir ~/.halo && cd ~/.halo
  1. 下载示例配置文件到工作目录
1
wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml
  1. 编辑配置文件,修改 MySQL 的数据库连接和密码
1
vim application.yaml

你需要做如下几个步骤:

  • 注释 H2 database configuration.部分
  • 启用 MySQL database configuration.部分
  • 修改 datasource 下的 url 中的 ip 地址部分为容器名称并修改密码为您设定的 MySQL 密码

修改后的内容如下:

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://some-mysql:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: my-secret-pw
  1. 创建 Halo 容器实例,并使用 --net 指定网络为刚才创建的halo-net
1
docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --net halo-net --restart=unless-stopped halohub/halo:1.6.0

MySQL 在宿主机如何通过 Docker 安装 Halo

如果你已有 MySQL 但安装在宿主机,你想使用 Docker 安装 Halo 那么此时为了保证 MySQLHalo 能网络互通,可以使用 host 网络模式即 --net host

  1. 创建 Halo 的工作目录
1
mkdir ~/.halo && cd ~/.halo
  1. 拉取配置
1
wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml
  1. 使用 Docker 启动 Halo 实例并指定网络模式为 host
1
docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --net host --restart=unless-stopped halohub/halo:1.6.0

编辑此页

l
l