Git Product home page Git Product logo

notes's Issues

CentOS 7 部署 Java 环境

CentOS 7 部署 Java 环境

安装 SDKMAN

为了方便,直接使用 SDKMAN 安装。

curl -s "https://get.sdkman.io" | bash

完事儿以后,把这句加入到 .bashrc 中:

source "$HOME/.sdkman/bin/sdkman-init.sh"

安装 Java

我这里直接安装微软的 JDK 17,执行此命令查找相关版本:

sdk ls java | grep ms | grep 17

显示如下:

 Microsoft     | >>> | 17.0.7       | ms      | installed  | 17.0.7-ms           
               |     | 17.0.6       | ms      |            | 17.0.6-ms           
               |     | 17.0.5       | ms      |            | 17.0.5-ms           
               |     | 11.0.17      | ms      |            | 11.0.17-ms 

这个 17.0.7-ms 就是版本号,可以用于 SDKMAN 安装:

sdk install java 17.0.7-ms

安装完成以后,SDKMAN 会自动把这个版本设置为默认版本。

安装 Gradle

sdk install gradle

Laravel Purifier 提示 mkdir(): File exists 错误

Laravel Purifier 提示 mkdir(): File exists 错误

一、背景

最近在接触一个前后端分离项目时,涉及到 XSS 安全问题,采用了 Laravel Purifier 这个扩展包。

我个人的思路是写了一个 XSS 的中间件,在这个中间件里统一对所有参数进行过滤处理。

实际上,我这是一种错误的用法,如果条件允许的话不应该这么做,应该只对富文本字段进行过滤。

上到测试环境以后,发现了一个偶发的 BUG,会提示 mkdir(): File exists 错误。排查了一翻下来,锁定到确实是这个扩展包导致的。

扩展包在 Laravel 项目里,storage/app 目录下会创建一个名为 purifier 文件夹,其下会包含三个文件夹(目录我还没有发现有别的文件夹)用来存储扩展包的缓存文件,分别是:CSSHTMLURI,。

二、问题产生的原因

在项目容器重新建立以后,访问一个面板的页面会同时有多个 Ajax 请求到后台,而后台因为全局用了这个中间件来过滤参数,所以就会导致同时有多个请求去检查 purifier 文件夹是否存在。如果缓存文件夹不存在的话就需要创建,这时候,假设有 AB 两个请求同时到达后台,也就同时得到结果:缓存文件夹不存在,则他们就都去创建,再假设 A 请求先创建完了缓存文件夹,这时候 B 请求还想创建缓存文件夹,也就造成了 mkdir(): File exists 错误。

三、解决思路

既然已经知道了(虽然是推测的)问题产生的原因,那么解决起来就比较容易了。

我的思路是把所有的缓存文件夹都事先创建好,并纳入版本控制,这样就不存在再去创建缓存文件夹的需要了,也就解决了重复创建的问题。

  1. storage/app/.gitignore 中加入 !purifier/

  2. 创建 storage/app/purifier 文件夹。

  3. 创建 storage/app/purifier/.gitignore 文件,并在其中写入以下内容:

    *
    !CSS/
    !HTML/
    !URI/
    !.gitignore
  4. storage/app/purifier 文件夹中创建 CSSHTMLURI 文件夹。

  5. 分别在刚刚创建的 CSSHTMLURI 文件夹中创建 .gitignore 文件,并在其中写入以下内容:

    *
    !.gitignore

最后,为了方便下次使用,我将这份文件夹上传到了 Github 上,使用的时候直接下载下来放到 storage/app 目录下就可以了。

Jenkins 报错:mvn not found

系统管理 -> 系统配置

添加全局变量

MAVEN_HOME
/data/apache-maven-3.3.9(你的maven的解压路径)
PATH
$(PATH):$(MAVEN_HOME)/bin

Homestead 配置一个文件服务器

after.sh 里加入以下:

sudo sed -i 's/try_files $uri $uri\/ \/index.php?$query_string;/autoindex on;\n\tautoindex_exact_size off;\n\tautoindex_localtime on;\n\tcharset utf-8;/g' /etc/nginx/sites-available/files.test

sudo nginx -s reload

Java 日历

2022-11-18

  • MyBatis Plus 撸完了,感觉用起来就像吃了屎一样难受,接下来打算看看 Spring Data JPA 如何。

搭建 Composer 私有仓库

docker run --name nginx --restart always -p 80:80 -v /root/satis/build:/usr/share/nginx/html:ro -v /etc/nginx/conf.d/:/etc/nginx/conf.d/ -d nginx

https://github.com/composer/satis

https://learnku.com/articles/14856/composersatis-speed-up-composer-private-active

# 拷贝nginx 镜像中的 默认的 nginx 部分配置文件
docker run --name tmp-nginx-container -d nginx
docker cp tmp-nginx-container:/etc/nginx/conf.d/ /etc/nginx/conf.d/
docker rm -f tmp-nginx-container

Electron **镜像

NPM

npm config set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/

SpringBoot 中 Redis Template 配置

Redis Config

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.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Author: lvxianchao
 * Date: 2023-03-03 14:32
 * Description: Redis Config
 */
@Configuration
public class RedisConfig {
    
    @Bean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        // 连接池
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 序列化器
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        // 支持事务
        redisTemplate.setEnableTransactionSupport(true);
        
        return redisTemplate;
    }

    /**
     * Redis Key 过期监听
     *
     * @param redisConnectionFactory RedisConnectionFactory
     *
     * @return RedisMessageListenerContainer
     */
    @Bean
    RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        
        return container;
    }
    
}

Java

Java 编译器查找 class 时的顺序

  • 如果是完整类名,就直接根据完整类名查找这个 class;
  • 如果是简单类名,按下面的顺序依次查找:
    • 查找当前 package 是否存在这个 class;
    • 查找 import 的包是否包含这个 class
    • 查找 java.lang 包是否包含这个 class;

如果按照上面的规则还无法确定类名,则编译报错。

Windows 10 使用 Scoop 包管理器

1、安装

打开 Windows PowerShell 输入以下命令:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

遇到以下选择项选择 A

Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): A

继续执行 irm get.scoop.sh | iex 得到以下:

Initializing...
Downloading...
Extracting...
Creating shim...
Adding ~\scoop\shims to your path.
Scoop was installed successfully!
Type 'scoop help' for instructions.

执行 scoop -v 查看版本:

Current Scoop version:
v0.2.3 - Released at 2022-07-07

2、设置镜仓库

使用 南京大学镜像 加速,提供有 mainextrasnonportableversions 4 个镜像仓库。

首先执行 scoop bucket rm main 来删除 main 仓库,不然执行以下命令时,会提示:

WARN  The 'main' bucket already exists. To add this bucket again, first remove it by running 'scoop bucket rm main'.

添加镜像源:

# main
scoop bucket add main https://mirror.nju.edu.cn/git/scoop-main.git/

# extras
scoop bucket add main https://mirror.nju.edu.cn/git/scoop-extras.git/

# nonportable
scoop bucket add main https://mirror.nju.edu.cn/git/scoop-nonportable.git/

# versions
scoop bucket add main https://mirror.nju.edu.cn/git/scoop-versions.git/

完成后执行 scoop update,这一步我本地处于报错状态,提示连接到 github.com 超时:

~ scoop update
Updating Scoop...
fatal: unable to access 'https://github.com/ScoopInstaller/Scoop/': Failed to connect to github.com port 443 after 21039 ms: Timed out
Update failed.

于是决定直接使用代理。

3、使用代理

scoop config proxy [username:password]host:port

执行 scoop update 会输出以下内容:

~ scoop update
Updating Scoop...
Updating 'extras' bucket...
Updating 'main' bucket...
Updating 'nonportable' bucket...
Updating 'versions' bucket...
Scoop was updated successfully!

CentOS 7 中使用 YUM 安装 Jenkins

CentOS 7 中使用 YUM 安装 Jenkins

安装

Jenkins 官网首页,点击 下载 按钮,在左侧列出的都是 LTS 版本,点击 CentOS/Fedora/Red Hat 进入:

image

跟随官网步骤:

sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo

sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key

yum install jenkins

相关文件所在位置:

# Jenkins 安装目录
/var/lib/jenkins

# Jenkins 日志目录
/var/log/jenkins

# Jenkins 缓存目录
/var/cache/jenkins

# 配置文件
/etc/sysconfig/jenkins

调整配置

/etc/sysconfig/jenkins 配置文件中,可以调整相关配置信息,我这里只调整 JENKINS_JAVA_CMDJENKINS_USER

JENKINS_JAVA_CMD="/usr/local/sdkman/candidates/java/current/bin/java"

JENKINS_USER="root"

加速

在启动 Jenkins 启动之前先调整一下配置,让 Jenkins 安装插件变快,同时第一次启动也会变快。

编辑 /var/lib/jenkins/hudson.model.UpdateCenter.xml 把里面的 url 字段值改为**的镜像,我这里使用了腾讯的:

<?xml version='1.1' encoding='UTF-8'?>
<sites>
  <site>
    <id>default</id>
    <url>https://mirrors.cloud.tencent.com/jenkins/updates/update-center.json</url>
  </site>
</sites>

编辑完成后,执行 systemctl start jenkins 启动 Jenkins。

访问 主机:端口 便可以访问,执行 cat /var/lib/jenkins/secrets/initialAdminPassword 便可以查看密码

执行 systemctl enable jenkins 添加到开机启动。

stderr: git: 'remote-http' is not a git command. See 'git --help'.

在使用 Jenkins 配置的从节点进行构建时,遇到如下错误:

The recommended git tool is: NONE
using credential 7d1762c9-7d53-4021-84f7-9dea4c8f3306
Cloning the remote Git repository
ERROR: Error cloning remote repo 'origin'
hudson.plugins.git.GitException: Command "git fetch --tags --force --progress -- http://192.168.56.152/lagou/jenkinsdemo.git +refs/heads/*:refs/remotes/origin/*" returned status code 128:
stdout: 
stderr: git: 'remote-http' is not a git command. See 'git --help'.
fatal: full write to remote helper failed: Broken pipe

	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:2675)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandWithCredentials(CliGitAPIImpl.java:2099)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.access$500(CliGitAPIImpl.java:85)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$1.execute(CliGitAPIImpl.java:619)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$2.execute(CliGitAPIImpl.java:848)
	at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$GitCommandMasterToSlaveCallable.call(RemoteGitImpl.java:158)
	at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$GitCommandMasterToSlaveCallable.call(RemoteGitImpl.java:151)
	at hudson.remoting.UserRequest.perform(UserRequest.java:211)
	at hudson.remoting.UserRequest.perform(UserRequest.java:54)
	at hudson.remoting.Request$2.run(Request.java:376)
	at hudson.remoting.InterceptingExecutorService.lambda$wrap$0(InterceptingExecutorService.java:78)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
	Suppressed: hudson.remoting.Channel$CallSiteStackTrace: Remote call to jenkins-agent-154
		at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1784)
		at hudson.remoting.UserRequest$ExceptionResponse.retrieve(UserRequest.java:356)
		at hudson.remoting.Channel.call(Channel.java:1000)
		at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.execute(RemoteGitImpl.java:143)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
		at java.base/java.lang.reflect.Method.invoke(Unknown Source)
		at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.invoke(RemoteGitImpl.java:129)
		at com.sun.proxy.$Proxy95.execute(Unknown Source)
		at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1229)
		at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1312)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep.checkout(SCMStep.java:129)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:97)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:84)
		at org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
		at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
		at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
		at java.base/java.lang.Thread.run(Unknown Source)

从节点的 Git 是下载的源码本地编译安装的,需要在安装 libcurl-develcurl-devel 以后,重新编译安装一次。

yum install -y libcurl-devel curl-devel

ImageUtil

/**
 * 获取远程图片的 base64
 *
 * @param remoteImageUrl 远程图片地址
 *
 * @return 图片的 base64
 */
public static String getRemoteImageBase64(String remoteImageUrl) {
    try {
        StringBuilder builder = new StringBuilder("data:");
        
        URL url = new URL(remoteImageUrl);
        String type = url.openConnection().getContentType();
        builder.append(type).append(";base64,");
        
        BufferedImage bufferedImage = ImageIO.read(url.openStream());
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, "png", stream);
        String base64 = Base64.getEncoder().encodeToString(stream.toByteArray());
        builder.append(base64);
        
        return builder.toString();
    } catch (IOException e) {
        log.warn("读取远程图片信息失败: {}", e.getMessage());
        return null;
    }
}

PNPM

安装

npm install -g pnpm

IDEA 模板

File | Settings | Editor | File and Code Templates

处理前后端通讯 Long 类型精度丢失的问题

处理前后端通讯 Long 类型精度丢失的问题

后端处理

后端处理的思路是配置 Jackson,在序列化时将 Long 类型和 long 类型序列化成 String 类型:

package BusinessHelper.config;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.math.BigInteger;

/**
 * Author: hcryeo
 * Date: 2023-05-11 13:44
 * Description:
 */
@Slf4j
@Configuration
public class JacksonConfig {

    /**
     * 注册 Jackson 全局 Long 类型转为 String 类型,解决前端 Long 类型精度丢失的问题
     *
     * @return Jackson2ObjectMapperBuilderCustomizer
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
        };
    }

}

以上代码只处理了 Long 类型,并没有处理 long 类型,如果想要处理 long 类型,再加一行:

jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);

前端处理

前端处理的思路是,利用 Axios 的 transformResponse 在 JSON 数据尚未传递给 then/catch 之前,对内容进行拦截处理,使用 json-bigint 库进行解析 JSON:

import JSONbig from "json-bigint";

axios({
    transformResponse: [
        data => JSONbig.parse(data),
    ],
})

IDEA 中设置 SpringBoot 项目环境变量

IDEA 中设置 SpringBoot 项目环境变量

打开配置:

image

找到 Environment variables 配置项,右侧的文本框里可以直接输入,不过这个不方便。可以直接点击右侧的 $ 字样的图标,在新开的窗口中编辑:

image

image

如果界面中没有显示 Environment variables 配置项,那就先点击 Modify options,然后把 Environment variables 选中:

image

解决 Jenkins 因字符集乱码导致构建失败(java.nio.charset.UnmappableCharacterException: Input length = 1)

解决 Jenkins 因字符集乱码导致构建失败

操作系统: CentOS 7
Jenkins 安装方式: 官方文档 yum 安装

背景

安装完了 Jenkins 以后,我随便去创建了一个自由风格的项目测试一下,然后就构建失败了,漫长的折腾之路开始了。

我在构建的执行 Shell 里输入如下:

echo "构建 - 开始"
whoami
echo "构建 - 结束"

报错

结果构建失败,查看控制台输出发现如下文:

Checking out Revision d147f439ea5cf57e460e48886db15a3971828499 (refs/remotes/origin/main)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f d147f439ea5cf57e460e48886db15a3971828499 # timeout=10
Commit message: "???? 01"
 > git rev-list --no-walk d147f439ea5cf57e460e48886db15a3971828499 # timeout=10
FATAL: Unable to produce a script file
java.nio.charset.UnmappableCharacterException: Input length = 1
	at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:275)
	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:307)
	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:282)
	at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:132)
	at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:205)
	at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120)
	at java.base/java.io.BufferedWriter.close(BufferedWriter.java:268)
	at hudson.FilePath$CreateTextTempFile.invoke(FilePath.java:1658)
	at hudson.FilePath$CreateTextTempFile.invoke(FilePath.java:1628)
	at hudson.FilePath.act(FilePath.java:1198)
	at hudson.FilePath.act(FilePath.java:1181)
	at hudson.FilePath.createTextTempFile(FilePath.java:1622)
Caused: java.io.IOException: Failed to create a temp file on /var/lib/jenkins/workspace/XXXXXXXX
	at hudson.FilePath.createTextTempFile(FilePath.java:1624)
	at hudson.tasks.CommandInterpreter.createScriptFile(CommandInterpreter.java:202)
	at hudson.tasks.CommandInterpreter.perform(CommandInterpreter.java:120)
	at hudson.tasks.CommandInterpreter.perform(CommandInterpreter.java:92)
	at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20)
	at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:818)
	at hudson.model.Build$BuildExecution.build(Build.java:199)
	at hudson.model.Build$BuildExecution.doRun(Build.java:164)
	at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:526)
	at hudson.model.Run.execute(Run.java:1900)
	at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:44)
	at hudson.model.ResourceController.execute(ResourceController.java:101)
	at hudson.model.Executor.run(Executor.java:442)
Build step 'Execute shell' marked build as failure
Finished: FAILURE

在这段信息之前还有一些信息,都是 Git 拉取代码相关的,没什么用。

定位

这段报错信息中,有 3 段有用的信息可以提供线索。

第一个有用的信息

Commit message: "???? 01"

这里明显是 Git Commit 信息乱码了,在 01 前面还有 4 个中文。

想便大概是和字符集有关了,但我当时认为,乱码就先乱码吧,应该不至于影响后面的构建吧。

第二个有用的信息

接着往下看,发现 Java 的异常信息:

java.nio.charset.UnmappableCharacterException: Input length = 1

这个比较关键,去查了一下说是字符集设置不正确导致,于是上网搜索了一堆答案,都没有效果,暂时放弃。

第三个有用的信息

Caused: java.io.IOException: Failed to create a temp file on /var/lib/jenkins/workspace/XXXXXXXX

看起来意思是 Jenkins 在编译构建的过程中,拉取了 Git 代码以后,创建临时目录失败了,但是我去服务器上看了,目录都还在,什么鬼?

把目录删除了,重新构建也不行。

心态崩了

从编译失败的信息中, 一共获取到了三段有用的信息,两个尝试了未果,也许有可能都是串联的吧?

那就先从第一个下手好了,搜索出来的结果操作起来都比较简单,都是些什么在这个位置添加环境变量,设置 LANG,在那个位置添加环境变量设置 Lang,结果没有一个好用的,就一个小小的字符集问题,搞起来这么难?心态崩了。

进一步发现线索

在上面的 搜索问题 - 得到答案 - 尝试 - 失败 的循环中,发现了一条比较有用的线索,那就是可以通过 Jenkins 的系统信息中看到一些 Java 相关的运行参数,其中便有字符集相关的,最主要的一个,就是 file.encoding

在我的 Jenkins 上可以看到是 ANSI_X3.4-1968,网上的老司机们都说这个不对,搞成 UTF-8,他们的问题就解决了,但是我按照他们的该当一顿操作下来,死活改不掉这个字符集,继续崩溃。

柳暗花明

就在我苦苦地一遍又一遍地问候着 Jenkins 这个玩意儿怎么这么多坑的时候,突然想起来,就在刚刚安装好 Jenkins 的时候,遇到过类似的情况,改 Jenkins 配置结果死活都不生效,最后是去修改的 service 文件才解决!

立马执行 systemctl status jenkins 查看到 Jenkins 的 service 文件位置 /usr/lib/systemd/system/jenkins.service,Vim 它!

在里面翻到了一个 Environment="JAVA_OPTS=-Djava.awt.headless=true" 的配置项,想必就是你了吧,把 -Dfile.encoding=UTF-8 加进去,就成了:

Environment="JAVA_OPTS=-Djava.awt.headless=true -Dfile.encoding=UTF-8"

systemctl daemon-reload && systemctl restart jenkins 以后,再次查看 Jenkins 系统信息,终于变过来了!

执行构建,查看结果也没有问题了,在编译过程中的中文的 Git Commit 也能正常显示了,哦特么噎死~

Jenkins 配置从节点

服务端

修改配置文件:/etc/ssh/sshd_config

# 是否允许 root 远程登录
PermitRootLogin yes

# 开启公钥认证
PubkeyAuthentication yes

# 存放登录用户公钥的文件位置
AuthorizedKeysFile .ssh/authorized_keys

客户端

# 一路回车
ssh-keygen -t rsa

客户端~/.ssh/id_rsa.pub 里的内容粘贴到 服务器~/.ssh/authorized_keys 里。

配置别名

# 登录的服务器别名 ssh server 就可以了
Host server 
    # 要登录的服务器ip
    HostName 0.0.0.0
    # 端口
    Port 22
    # 登录名
    User root 
    # 私钥路径
    IdentityFile ~/.ssh/id_rsa 
    ServerAliveInterval 30
    TCPKeepAlive yes

Chrome 设置 HTTP 不跳转 HTTPS

进入 chrome://net-internals/#hsts

Delete domain security policies 里输入域名并点击 delete

Query HSTS/PKP domain 里输入域名点击 query 下面出现提示 Not found 代表成功。

SpringBoot 多模块项目中引入本地 Jar 包

一、在需要的模块里引入本地 jar

先将本地的 jar 包放到需要的子模块下的 src/main/resources/lib 目录下,lib 是我手动新建的,然后修改子模块的 pom.xml,添加依赖

<dependencies>
    <dependency>
        <groupId>com.hikvision</groupId>
        <artifactId>artemis-http-client</artifactId>
        <version>1.1.11</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/src/main/resources/lib/artemis-http-client-1.1.11.RELEASE.jar</systemPath>
    </dependency>
</dependencies>

二、配置项目根目录的 pom.xml

在项目要目录的 pom.xml 里添加上:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
            </configuration>
        </plugin>
    </plugins>
</build>

SpringBoot + Gradle + Spring Data JPA + QueryDSL

记录一下在 SpringBoot 2 中使用 Gradle 整合 QueryDSL 以配合 JPA 查询。

Java 17
SpringBoot 2.7.7
Gradle 7.6
QueryDSL Version 5.0.0

最终目的是提供两个 Restful API:

  • / 路由提供用户列表
  • /{id} 路由提供用户详情

重点

使用 QueryDSl 的重点就是在定义好了 Entity 以后,通过 Gradle Build 出 QEntity 类。如在以下演示文件中定义的是 User Entity,在 build 以后,在项目中可以看到自动生成的 Q 类:

image

安装依赖

build.gradle 中,添加以下:

// 在最外层场景 QueryDSL 版本
var queryDslVersion = '5.0.0'

// 以下需要添加到 dependencies 里
implementation ("com.querydsl:querydsl-jpa:${queryDslVersion}")
annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:jpa")
annotationProcessor("jakarta.persistence:jakarta.persistence-api")

项目文件

  • application.yml (项目配置文件)
  • build.gradle (引入依赖)
  • UserController
  • UserService (为了方便,直接在 Service 层实现查询逻辑,去掉 Impl 层)
  • UserIndexDTO
  • UserRepository
  • User (实体类)

application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  datasource:
    driverClassName: org.mariadb.jdbc.Driver
    password: root
    username: root
    url: jdbc:mariadb://10.15.1.175:43306/demo

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.7'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
var queryDslVersion = '5.0.0'


configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation ("com.querydsl:querydsl-jpa:${queryDslVersion}")
    annotationProcessor("com.querydsl:querydsl-apt:${queryDslVersion}:jpa")
    annotationProcessor("jakarta.persistence:jakarta.persistence-api")
}

tasks.named('test') {
    useJUnitPlatform()
}

User

package com.example.demo.entity;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.Hibernate;

import javax.annotation.PostConstruct;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;

/**
 * Author: lvxianchao
 * Date: 2023-01-09 11:16
 * Description:
 */
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity
@Table(name = "user")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "email")
    private String email;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        User user = (User) o;
        return id != null && Objects.equals(id, user.id);
    }
    
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
    @PostConstruct
    public void init(){
    
    }
}

UserController

package com.example.demo.controller;

import com.example.demo.dto.UserIndexDTO;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * Author: lvxianchao
 * Date: 2023-01-09 11:16
 * Description:
 */
@RestController
public class UserController {
    
    @Resource
    UserService service;
    
    @GetMapping("/")
    public List<User> index(UserIndexDTO dto) {
        System.out.println("dto = " + dto);
        return service.index(dto);
    }
    
    @GetMapping("/{id}")
    public User show(@PathVariable Long id) {
        return service.show(id);
    }
}

UserIndexDTO

package com.example.demo.dto;

import lombok.Data;

/**
 * Author: lvxianchao
 * Date: 2023-01-09 13:08
 * Description:
 */
@Data
public class UserIndexDTO {
    private Long id;
    private String name;
    private String email;
}

UserService

package com.example.demo.service;

import com.example.demo.dto.UserIndexDTO;
import com.example.demo.entity.QUser;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

/**
 * Author: lvxianchao
 * Date: 2023-01-09 11:17
 * Description:
 */
@Service
public class UserService {
    
    @Resource
    UserRepository repository;
    
    @PersistenceContext
    private EntityManager entityManager;
    
    public List<User> index(UserIndexDTO dto) {
        QUser user = QUser.user;
        JPAQueryFactory query = new JPAQueryFactory(entityManager);
        JPAQuery<User> selectFrom = query.selectFrom(user);
        
        if (dto.getId() != null) {
            System.out.println("dto.getId() = " + dto.getId());
            selectFrom.where(user.id.eq(dto.getId()));
        }
        
        if (dto.getName() != null) {
            System.out.println("dto.getName() = " + dto.getName());
            selectFrom.where(user.name.eq(dto.getName()));
        }
        
        if (dto.getEmail() != null) {
            System.out.println("dto.getEmail() = " + dto.getEmail());
            selectFrom.where(user.email.eq(dto.getEmail()));
        }
        
        return selectFrom.fetch();
    }
    
    public User show(Long id) {
        QUser user = QUser.user;
        JPAQueryFactory query = new JPAQueryFactory(entityManager);
        User first = query.selectFrom(user).where(user.id.eq(id)).fetchFirst();
        return first;
    }
}

UserRepository

package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Author: lvxianchao
 * Date: 2023-01-09 11:16
 * Description:
 */
public interface UserRepository extends JpaRepository<User, Long> {
}

CentOS 7 关闭防火墙以后 Docker 报错 iptables failed

CentOS 7 执行 systemctl stop firewalld 以后,Docker 运行容器报错:

Error response from daemon: driver failed programming external connectivity on endpoint nginx (492fe5bacabeff80220a680de0855668f26982f06346cd532048caf70534e58a):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 80 -j DNAT --to-destination 172.17.0.2:80 ! -i docker0: iptables: No chain/target/match by that name.
 (exit status 1))
Error: failed to start containers: nginx

IDEA

配置 HTTP 代理

设置 -> 插件 中,点击设置选项按钮,选中 自动检测代理设置,勾选 自动代理配置 URL,前在输入框里输入代理 URL,这里我使用的在 Homestead 环境中使用 Nginx 搭建的文件服务器,提面包含了一个 PAC 文件,白名单模式。

image

可以点击下面的 检查连接,并在弹窗中输入 https://google.com 然后点击 确定 按钮来检测代理是否好用,如果出现 连接成功 的提示框则证明没有问题。

image

新版 IDEA 设置 SpringBoot 项目热部署

在 Gradle 依赖里需要安装 developmentOnly 'org.springframework.boot:spring-boot-devtools'

Settings > Build, Execution, Deployment > Compiler 中勾选 Build project automatically,顺便也可以把 Compile independent modules inparalle 也给勾选上。

image

至此,已经可以通过 Ctrl/Commnd + F9 来手动构建触发自动热部署了(其实就是把活跃的 Java 类重新编译,然后 IDE 替我们自动重新启动项目)。

如果还想再自动化一点,可以将自动编译按如下方式开启:

Advanced Settings 里,勾选 Allow auto-make to start even if developed application is currently running

image

Docker 挂代理

sudo mkdir -p /etc/systemd/system/docker.service.d

/etc/systemd/system/docker.service.d/http-proxy.conf

[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80"
Environment="HTTPS_PROXY=https://proxy.example.com:443"
Environment="NO_PROXY=your-registry.com,10.10.10.10,*.example.com"

sudo systemctl daemon-reload
sudo systemctl restart docker

Windows 10 上将 VSCode 的默认终端设置为 Git Bash

打开设置,搜索 terminal.integrated.profiles.windows,然后点击 在 settings.json 中编辑:

image

添加如下配置项,其中 path 路径需要换成你自己的:

  "GitBash": {
      "path": "D:\\Program Files\\Git\\bin\\bash.exe",
      "args": [],
      "icon": "terminal-bash"
  },

terminal.integrated.profiles.windows 的值改为 GitBash,重启 VSCode。

GitBash 中间不能有空格,否则无法识别

解决 Github 报错: kex_exchange_identification: Connection closed by remote host

kex_exchange_identification: Connection closed by remote host
Connection closed by 20.205.243.166 port 22
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

生成:ssh-keygen -t ed25519 -C "备注tliy"

~/.ssh/config 里新增:

Host github.com
    HostName ssh.github.com
    User git
    Port 443

验证:ssh -T [email protected]

ssh -T [email protected]
The authenticity of host '[ssh.github.com]:443 ([20.205.243.160]:443)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[ssh.github.com]:443' (ED25519) to the list of known hosts.
Hi lvxianchao! You've successfully authenticated, but GitHub does not provide shell access.

指定 Spring Data JPA 通过 Entity 创建数据表时使用 UTF8MB4 字符集

默认情况下 Spring Data JPA 通过 Entity 创建的数据表的字符集是 latin, 通常我都是用 utf8mb4 字符集,可以通过以下操作实现。

application.yml 里添加一个配置项用于指定一个我们自定义的 Dialect 类(为了方便以下写为 property 的格式,如果是 yml 格式需要自行调整):

spring.jpa.properties.hibernate.dialect: com.lvxianchao.translation.config.MySQL5UTF8mb4Dialect

config 包下创建 MySQL5UT8MB4Dialect,其内容如下:

package com.lvxianchao.translation.config;

import org.hibernate.dialect.MySQLDialect;

/**
 * Author: lvxianchao
 * Date: 2023-01-12 17:36
 * Description:
 */
public class MySQL5UTF8mb4Dialect extends MySQLDialect {
    
    @Override
    public String getTableTypeString() {
        return "engine = innodb default charset = utf8mb4";
    }
    
}

Vue 编码经验

组件化编码流程:

  1. 拆分静态组件:组件要按照功能点拆分,命名不要与 HTML 元素冲突。
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
  • 一个组件在用:放在组件自身即可。
  • 一些组件在用:放在他们共同的父组件上(状态提升)。
  1. 实现交互:从绑定事件开始。

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.