Add Copy Button to Code Blocks in Hexo

Hexo 为代码块添加复制功能按钮
本文详细记录如何在 Hexo 中为代码块添加一个 “复制” 按钮,实现一键复制代码功能。

项目地址:https://github.com/EvannZhongg/Blog-Learning.git

结构要求与适配说明
本复制功能脚本适用于以下结构的代码块:

1
2
3
4
5
6
7
8
<figure class="highlight">
<table>
<tr>
<td class="gutter">...</td>
<td class="code"><pre><code>...</code></pre></td>
</tr>
</table>
</figure>

这是 Hexo 中多数主题(包括 Chic、NexT、Butterfly 等)默认的代码块渲染结构。

如何检查自己主题的结构是否符合?
启动本地博客:hexo s
在浏览器中打开博客页面
右键代码块 → 点击“检查”
查看代码块的外层 HTML 标签是否为 figure.highlight
或者直接在浏览器中点击 F12 ,在 Elements 中直接搜索是否含有 figure.highlight

  1. 创建 JavaScript 脚本文件
    在 Hexo 博客项目的根目录下创建 JS 脚本文件 code-copy.js ,如果没有js文件夹则自己创建:
1
source/js/code-copy.js

并填入以下完整内容:

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
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('figure.highlight').forEach((figure) => {
if (figure.querySelector('.copy-btn')) return;

const copyBtn = document.createElement('button');
copyBtn.className = 'copy-btn';
copyBtn.title = '复制';

// 缩小后的复制图标(14*15)
const copyIcon = `
<svg xmlns="http://www.w3.org/2000/svg" height="14" width="15" viewBox="0 0 24 24" fill="white">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 18H8V7h11v16z"/>
</svg>
`;

// 成功后显示的勾(14*15)
const checkIcon = `
<svg xmlns="http://www.w3.org/2000/svg" height="14" width="15" viewBox="0 0 24 24" fill="#00cc66">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
`;

copyBtn.innerHTML = copyIcon;

// 按钮样式(浅灰底、缩小)
Object.assign(copyBtn.style, {
position: 'absolute',
top: '8px',
right: '8px',
height: '28px',
width: '28px',
padding: '4px',
background: '#aaa',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
opacity: '0.85',
zIndex: 1000,
transition: 'opacity 0.2s ease',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.15)'
});

copyBtn.addEventListener('mouseover', () => copyBtn.style.opacity = '1');
copyBtn.addEventListener('mouseout', () => copyBtn.style.opacity = '0.85');

copyBtn.addEventListener('click', () => {
const code = figure.querySelector('td.code');
const text = code ? code.innerText : '';
navigator.clipboard.writeText(text).then(() => {
copyBtn.innerHTML = checkIcon;
setTimeout(() => {
copyBtn.innerHTML = copyIcon;
}, 1000);
});
});

figure.style.position = 'relative';
figure.appendChild(copyBtn);
});
});

  1. 在页面底部引入 JS 文件
    打开文件:
1
themes/hexo-theme-Chic/layout/_partial/footer.ejs

在 标签之后添加以下代码:

1
<script src="/js/code-copy.js"></script>

这样可以确保复制按钮脚本在页面加载完毕后自动运行。

  1. 生成并本地预览效果
    运行以下命令,重新生成并启动本地预览:
1
2
3
hexo clean
hexo g
hexo d

然后访问 http://localhost:4000 ,查看任意一段代码块,右上角应出现复制图标按钮。

修改后的相关完整代码可以在文章开头的项目地址中获取

该项目代码基于 Hexo 和 hexo-theme-Chic 。

所有软件中国镜像源

WEB

Npm

为中国内地的Node.js开发者准备的镜像配置,大大提高node模块安装速度。

1
npm i -g mirror-config-china --registry=https://registry.npm.taobao.org

前端构建注意事项

1, 修改 下载仓库为淘宝镜像

1
npm config set registry http://registry.npm.taobao.org/

2, 如果要发布自己的镜像需要修改回来

1
npm config set registry https://registry.npmjs.org/

3, 安装cnpm

1
2
3
npm install -g cnpm --registry=https://registry.npm.taobao.org

npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver

前端如果装不上node-sass 适当的提高node-sass的版本,可能由于node 的版本过高,装不上node-sass

1
2
3
4
5
brew install yarn

yarn install

yarn add [package_name]

PHP COMPSOER

安装

下载安装脚本

1
php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"

执行脚本

1
php composer-setup.php

删除脚本

1
php -r "unlink('composer-setup.php');"

复制到系统目录

1
sudo mv composer.phar /usr/local/bin/composer

全局配置(推荐)

  • 所有项目都会使用该镜像地址:
    1
    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
  • 取消配置:
    1
    composer config -g --unset repos.packagist

GO

1
go env -w GOPROXY=https://goproxy.cn,direct

Python

pip

瓣镜像地址:https://pypi.douban.com/simple/

虽然用easy_install和pip来安装第三方库很方便
它们的原理其实就是从Python的官方源pypi.python.org/pypi 下载到本地,然后解包安装。
不过因为某些原因,访问官方的pypi不稳定,很慢甚至有些还时不时的访问不了。

跟ubuntu的apt和centos的yum有各个镜像源一样,pypi也有。

在国内的强烈推荐豆瓣的源
http://pypi.douban.com/simple/
注意后面要有/simple目录。

使用镜像源很简单,用-i指定就行了:

1
2
sudo easy_install -i http://pypi.douban.com/simple/ ipython
sudo pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com/simple ipython

每次都要这样写? no!,做个别名吧,额,类似于这样

1
pip  install  -i  https://pypi.doubanio.com/simple/  --trusted-host pypi.doubanio.com  django

命令设置默认源

1
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

好像还不太好,肿么办?写在配置文件里吧。

    1. linux/mac用户将它命名为pip.conf, windows用户将它命名为pip.ini. 文件中写如下内容:
1
2
3
[global]
timeout = 60
index-url = https://pypi.doubanio.com/simple

** 注意: **如果使用http链接,需要指定trusted-host参数

1
2
3
4
[global]
timeout = 60
index-url = http://pypi.douban.com/simple
trusted-host = pypi.douban.com
    1. 将该文件放置在指定位置.

linux下指定位置为

$HOME/.config/pip/pip.conf
或者
$HOME/.pip/pip.conf

mac下指定位置为

$HOME/Library/Application Support/pip/pip.conf
或者
$HOME/.pip/pip.conf

windows下指定位置为

%APPDATA%\pip\pip.ini
或者
%HOME%\pip\pip.ini

centos

centos7 修改yum源为阿里源
首先是到yum源设置文件夹里

  1. 查看yum源信息:
1
yum repolist
  1. 定位到base reop源位置
1
cd /etc/yum.repos.d
  1. 接着备份旧的配置文件
1
sudo mv CentOS-Base.repo CentOS-Base.repo.bak
  1. 下载阿里源的文件
1
sudo wget -O CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

安装epel repo源:

1
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

5.清理缓存

1
yum clean all

6.重新生成缓存

1
yum makecache
  1. 再次查看yum源信息
  2. yum repolist

java maven

1
2
3
wget https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz --no-check-certificate
https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.zip
https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.8.9/binaries/apache-maven-3.8.9-bin.zip

mvn

1
2
3
4
5
6
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

Jackson使用

属性只读无法修改

如果你使用Jackson和requestbody,在参数类需要忽略的字段上加上注解 jsonproperty access readonly就可以。

1
2
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String number;

追加属性

单独配置
1
2
3
4
5
6
7
8
public class User {
private String number;

@JsonProperty("customNumber")
private String getCustomNumber(){
return "no"+number;
}
}

格式化的时候,会多出一个customNumber的属性

序列化器

根据当前字段名称,写入另外一个字段,一般用于字典的文本描述写入

1
2
3
4
5
6
7
8
9
public class DictSerializer extends JsonSerializer<String> {
@Override
public void serialize(String o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 当前字段名称
String currentName = serializerProvider.getGenerator().getOutputContext().getCurrentName();

jsonGenerator.writeStringField(currentName+"Ext", o+"扩展");
}
}

SpringBoot整合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
/**
* 8.整合 SpringCache简化缓存开发
* 1.引入依赖
* spring-boot-starter-cache
* spring-boot-starter-data-redis
* 2.写配置
* 1. 自动配置了哪些
* CacheAuthConfiguration 会导入 RedisCacheConfiguration;
* 自动配好了缓存管理器RedisCacheManager
* 2. 配置使用redis作为缓存
* spring.cache.type=redis
* 3. 测试使用缓存
* @Cacheable Triggers Cache population触发将数据保存到缓存的操作
* @CacheEvict 将数据从缓存删除的操作
* @CachePut 不影响方法执行更新操作
* @Caching 组合以上多个操作
* @CacheConfig 在类级别共享缓存的相
* 1.开启缓存功能 @EnableCaching
* 2.只需要使用注解就能完成缓存操作
* 4. 原理
* CacheAutoConfiguration => RedisCacheConfiguration
* 自动配置了 RedisCacheManager -> 初始化所有的缓存 -> 每个缓存决定使用什么配置
* 如果redisCacheConfiguration 有就用已有的,没有就用默认配置
* ->想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可
* ->就会应用到当前RedisCacheManager管理的所有缓存分区中
*/

配置文件类

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
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
/**
* 将配置作为参数传入,从容器中自动获取
*
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置键的序列化方式
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
// 设置值的序列化方式,将类名也序列化
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 将配置文件中的配置生效

// 获取redis的配置
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
// 设置过期时间
if (redisProperties.getTimeToLive() != null) {
System.out.println(redisProperties.getTimeToLive());
config = config.entryTtl(redisProperties.getTimeToLive());
}
// 设置key前缀
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}

// if (!redisProperties.isCacheNullValues()) {
// config = config.disableCachingNullValues();
// }
//
// if (!redisProperties.isUseKeyPrefix()) {
// config = config.disableKeyPrefix();
// }
return config;
}
}

SpringBoot全局跨域配置

通过实现 WebMvcConfigurer ,推荐,不能配置多个,只能配置一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
}

通过过滤器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class CorsConfig {
@Bean
CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有方式
config.addAllowedMethod("*");
// 允许所有来源
config.addAllowedOrigin("*");
// 允许附带cookie信息
config.setAllowCredentials(true);

// 适用于所有请求
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}

log4j2日志配置

application.yml中指定日志配置文件

1
2
logging:
config: classpath:log4j2.xml

log4j2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">

<properties>
<!-- 日志打印级别 -->

<property name="LOG_LEVEL">INFO</property>
<!-- APP名称 -->
<property name="APP_NAME" value="framework-project"/>
<!-- 日志文件存储路径 -->
<property name="LOG_HOME">./logs/</property>
<!-- 存储天数 -->
<property name="LOG_MAX_HISTORY" value="60d"/>
<!-- 单个日志文件最大值, 单位 = KB, MB, GB -->
<property name="LOG_MAX_FILE_SIZE" value="10 MB"/>
<!-- 每天每个日志级别产生的文件最大数量 -->
<property name="LOG_TOTAL_NUMBER_DAILY" value="10"/>
<!-- 压缩文件的类型,支持zip和gz,建议Linux用gz,Windows用zip -->
<property name="ARCHIVE_FILE_SUFFIX" value="zip"/>
<!-- 日志文件名 -->
<property name="LOG_FILE_NAME" value="${LOG_HOME}"/>
<property name="FILE_NAME_PATTERN" value="${LOG_HOME}%d{yyyy-MM-dd}"/>

<!--
格式化输出:
%date{yyyy-MM-dd HH:mm:ss.SSS}: 简写为%d 日期 2023-08-12 15:04:30,123
%thread: %t 线程名, main
%-5level:%p 日志级别,从左往右至少显示5个字符宽度,不足补空格 INFO
%msg:%m 日志消息 info msg
%n: 换行符
{cyan}: 蓝绿色(青色)
%logger{36}: %c 表示 Logger 名字最长36个字符
%C: 类路径 com.qq.demolog4j2.TestLog4j2
%M: 方法名 main
%F: 类名 TestLog4j2.java
%L: 行号 12
%l: 日志位置, 相当于 %C.%M(%F.%L) com.qq.demolog4j2.TestLog4j2.main(TestLog4j2.java:16)
-->
<!-- %d: 日期
%-5level: 日志级别,显示时占5个字符不足
[%t]: 线程名
%c{1.}: 显示调用者,只显示包名最后一截及方法名,前面的只取首字母
.%M(代码行号%L):
%msg%n": 需要打印的日志信息,换行:INFO>[MsgToMP:99]
Bright: 加粗 -->
<!--日志输出格式-控制台彩色打印-->
<property name="ENCODER_PATTERN_CONSOLE">%blue{%d{yyyy-MM-dd HH:mm:ss.SSS}} | %highlight{%-5level}{ERROR=Bright RED, WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White} | %yellow{%t} | %cyan{%c{1.}} : %white{%msg%n}</property>
<!--日志输出格式-文件-->
<property name="ENCODER_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %5pid --- [%15.15t] %c{1.} [%L] : %m%n</property>
<!--日志输出格式-控制台彩色打印-->
<property name="DEFAULT_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} %style{%5pid}{bright,magenta} --- [%15.15t] %cyan{%c{1.} [%L]} : %m%n</property>
</properties>

<Appenders>
<!-- 控制台的输出配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${DEFAULT_PATTERN}" />
</Console>
<!-- 打印出所有的info及以下级别的信息,每次大小超过size进行压缩,作为存档-->
<RollingFile name="RollingFileAll" fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/info.log" filePattern="${FILE_NAME_PATTERN}/info.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${LOG_LEVEL}" onMatch="ACCEPT" onMismatch="DENY" />
<!--输出日志的格式-->
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9" max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob=".info.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileDebug"
fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/debug.log"
filePattern="${FILE_NAME_PATTERN}/debug.log">
<Filters>
<ThresholdFilter level="DEBUG" />
<ThresholdFilter level="INFO" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9"
max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.debug.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileWarn" fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/warn.log"
filePattern="${FILE_NAME_PATTERN}.warn.log">
<Filters>
<ThresholdFilter level="WARN" />
<ThresholdFilter level="ERROR" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9"
max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.warn.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileError"
fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/error.log"
filePattern="${FILE_NAME_PATTERN}.error.log">
<Filters>
<ThresholdFilter level="ERROR" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<DefaultRolloverStrategy compressionLevel="9" max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.error.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>

<!--只有定义了logger并引入以上Appenders,Appender才会生效-->

<Loggers>
<!-- 开发环境debug,不是开发环境,删除掉这个logger -->
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileAll"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
</Logger>
<root level="${LOG_LEVEL}">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileAll"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</Loggers>
</configuration>

aop切面日志

依赖添加

1
2
3
4
 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

切面日志类

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
package com.example.yunxiao_deploy_demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Slf4j
@Aspect
@Component
public class LogAspect {



/**
* 定义切入点
*/
@Pointcut("execution(* *(..)) && @within(org.springframework.web.bind.annotation.RestController)")
public void pointcut(){

}

/**
* @description 在连接点执行之前执行的通知
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint){

}

/**
* @description 在连接点执行之后执行的通知(返回通知)
*/
@AfterReturning(pointcut = "pointcut()", returning = "object")
public void afterReturning(JoinPoint joinPoint, Object object){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
log.info("请求地址 : " + request.getRequestURL().toString());
log.info("请求参数 : " + Arrays.toString(joinPoint.getArgs()));
ObjectMapper objectMapper = new ObjectMapper();
try {
log.debug("请求结果 : " + objectMapper.writeValueAsString(object));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

/**
* @description 在连接点执行之后执行的通知(异常通知)
*/
@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
log.error("请求地址 : " + request.getRequestURL().toString());
log.error("请求参数 : " + Arrays.toString(joinPoint.getArgs()));
if(ex.getStackTrace().length > 0){
StackTraceElement stackTraceElement = ex.getStackTrace()[0];
log.error("异常位置 : " + ex.getMessage() + " :" +stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "("+stackTraceElement.getLineNumber()+")", ex);
} else {
log.error("异常位置 : " + ex.getMessage());
}
}
}

idea远程调试

远程应用配置

1
2
3
4
5
6
7
8
9
10
11
12
FROM registry.cn-hangzhou.aliyuncs.com/ppiian/openjdk:8-jdk-alpine

VOLUME /tmp

ADD ./target/*.jar app.jar

RUN ln -snf /usr/share/zoneinfo/PRC /etc/localtime && echo PRC > /etc/timezone

#在docker配置中修改JAVA_OPTS,达到切换环境的效果
ENV JAVA_OPTS="-Xms128m -Xmx512m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006"

ENTRYPOINT java $JAVA_OPTS -jar app.jar

远程启动配置项中添加

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006

这里的端口5006需要提供给idea

idea配置

【运行/调试配置】中添加一个 【远程jvm调试】

host(主持人)这里填远程的ip地址,端口填远程开放的端口

image-20240818191426153

lambda受检异常

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
public class Try {
public Try() {
}

public static <T, R> Function<T, R> of(Try.UncheckedFunction<T, R> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
return mapper.apply(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

public static <T> Consumer<T> of(Try.UncheckedConsumer<T> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
mapper.accept(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

public static <T> Supplier<T> of(Try.UncheckedSupplier<T> mapper) {
Objects.requireNonNull(mapper);
return () -> {
try {
return mapper.get();
} catch (Exception var2) {
throw Exceptions.unchecked(var2);
}
};
}

@FunctionalInterface
public interface UncheckedSupplier<T> {
@Nullable
T get() throws Exception;
}

@FunctionalInterface
public interface UncheckedConsumer<T> {
@Nullable
void accept(@Nullable T t) throws Exception;
}

@FunctionalInterface
public interface UncheckedFunction<T, R> {
@Nullable
R apply(@Nullable T t) throws Exception;
}
}

Function 用法

1
2
3
4
5
User apply = Try.of((String userStr) -> {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(userStr, User.class);
}).apply(s3);
System.out.println(apply);

Supplier 用法

1
2
3
4
5
User aaa = Try.of(() -> {
System.out.println("走if");
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(s3, User.class);
}).get();

Consumer用法

1
2
3
4
5
6
7
String s3 = "{\"name\":221, \"id\":1}";

Try.of((String str) -> {
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(str, User.class);
System.out.println(user);
}).accept(s3);

lambda中使用

1
2
3
4
5
User user2 = Optional.ofNullable(s3)
.map(Try.of((String userStr) -> {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(userStr, User.class);
})).orElse(null);

predicate

1
2
3
4
5
objects.stream().filter(Try.predicate(user -> {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(s3, User.class);
return user.getId().equals(1);
})).collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11
public static <T> Predicate<T> predicate(Try.UncheckedPredicate<T> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
return mapper.test(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

1
2
3
4
5
@FunctionalInterface
public interface UncheckedPredicate<T> {
@Nullable
boolean test(T t) throws Exception;
}

Linux防火墙firewalld

CentOS7 防火墙(firewall)的操作命令

安装:yum install firewalld

1、firewalld的基本使用

启动: systemctl start firewalld

查看状态: systemctl status firewalld

禁用,禁止开机启动: systemctl disable firewalld

停止运行: systemctl stop firewalld

2.配置firewalld-cmd

查看版本: firewall-cmd –version

查看帮助: firewall-cmd –help

显示状态: firewall-cmd –state

查看所有打开的端口: firewall-cmd –zone=public –list-ports

更新防火墙规则: firewall-cmd –reload

更新防火墙规则,重启服务: firewall-cmd –completely-reload

查看已激活的Zone信息: firewall-cmd –get-active-zones

查看指定接口所属区域: firewall-cmd –get-zone-of-interface=eth0

拒绝所有包:firewall-cmd –panic-on

取消拒绝状态: firewall-cmd –panic-off

查看是否拒绝: firewall-cmd –query-panic

3.信任级别,通过Zone的值指定

drop: 丢弃所有进入的包,而不给出任何响应
block: 拒绝所有外部发起的连接,允许内部发起的连接
public: 允许指定的进入连接
external: 同上,对伪装的进入连接,一般用于路由转发
dmz: 允许受限制的进入连接
work: 允许受信任的计算机被限制的进入连接,类似 workgroup
home: 同上,类似 homegroup
internal: 同上,范围针对所有互联网用户
trusted: 信任所有连接

4.firewall开启和关闭端口

以下都是指在public的zone下的操作,不同的Zone只要改变Zone后面的值就可以

添加:

firewall-cmd –zone=public –add-port=80/tcp –permanent (–permanent永久生效,没有此参数重启后失效)

重新载入:

firewall-cmd –reload

查看:

firewall-cmd –zone=public –query-port=80/tcp

删除:

firewall-cmd –zone=public –remove-port=80/tcp –permanent

5.管理服务

以smtp服务为例, 添加到work zone

添加:

firewall-cmd –zone=work –add-service=smtp

查看:

firewall-cmd –zone=work –query-service=smtp

删除:

firewall-cmd –zone=work –remove-service=smtp

5.配置 IP 地址伪装

查看:

firewall-cmd –zone=external –query-masquerade

打开:

firewall-cmd –zone=external –add-masquerade

关闭:

firewall-cmd –zone=external –remove-masquerade

6.端口转发

打开端口转发,首先需要打开IP地址伪装

firewall-cmd –zone=external –add-masquerade

转发 tcp 22 端口至 3753:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp:toport=3753

转发端口数据至另一个IP的相同端口:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp:toaddr=192.168.1.112

转发端口数据至另一个IP的 3753 端口:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp::toport=3753:toaddr=192.168.1.112

6.systemctl是CentOS7的服务管理工具中主要的工具,它融合之前service和chkconfig的功能于一体。

启动一个服务:systemctl start firewalld.service
关闭一个服务:systemctl stop firewalld.service
重启一个服务:systemctl restart firewalld.service
显示一个服务的状态:systemctl status firewalld.service
在开机时启用一个服务:systemctl enable firewalld.service
在开机时禁用一个服务:systemctl disable firewalld.service
查看服务是否开机启动:systemctl is-enabled firewalld.service
查看已启动的服务列表:systemctl list-unit-files|grep enabled
查看启动失败的服务列表:systemctl –failed