Chuenhung的个人网站

chuenhung.github.io

1、SpringBoot的总体读取顺序优先级包外大于包内,properties大于yml
2、 SpringBoot的总体读取顺序如下所示:

  • config/application.properties(项目同级目录中config目录下)
  • config/application.yml
  • application.properties(项目同级目录下)
  • application.yml
  • resources/config/application.properties(项目resources目录中config目录下)
  • resources/config/application.yml
  • resources/application.properties(项目的resources目录下)
  • resources/application.yml

项目同级目录,相当于生产环境中的JAR包。

阅读全文 »

需求概述

支持按勾选的成果申请记录批量下载附件。按"年份"+“成果名称"将对应的成果附件放入文件夹,最后将所有文件夹打包成一个压缩包,名称未"成果附件”+“下载时间(精确到秒)”。例如:
在这里插入图片描述
在这里插入图片描述

工具类

在网上找了个Zip压缩工具类,基本满足了我的需求,代码如下:

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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* Zip压缩工具类:支持压缩文件列表(包括压缩包)和文件夹
*
* @author chuenhung
* @createTime 2022/06/08
*/
public class ZipUtil {

private static final int BUFFER_SIZE = 2 * 1024;

/**
* @param srcFiles 需要压缩的文件列表
* @param out 输出流
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<File> srcFiles ,OutputStream out) throws RuntimeException, FileNotFoundException {
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* @param srcFiles 需要压缩的文件列表
* @param outDir 输出文件目录
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<File> srcFiles ,String outDir) throws RuntimeException, FileNotFoundException {
OutputStream out = new FileOutputStream(new File(outDir));
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* @param outDirList 压缩文件夹路径
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<String> outDirList, OutputStream out,
boolean KeepDirStructure) throws RuntimeException, Exception {

ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
List<File> sourceFileList = new ArrayList<>();
for (String dir : outDirList) {
File sourceFile = new File(dir);
sourceFileList.add(sourceFile);
}
compress(sourceFileList, zos, KeepDirStructure);
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil", e);
} finally {
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

/**
* 递归压缩方法
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception
*/
private static void compress(File sourceFile, ZipOutputStream zos,
String name, boolean KeepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
if (sourceFile.isFile()) {
zos.putNextEntry(new ZipEntry(name));
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
// Complete the entry
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
if (KeepDirStructure) {
zos.putNextEntry(new ZipEntry(name + "/"));
zos.closeEntry();
}

} else {
for (File file : listFiles) {
if (KeepDirStructure) {
compress(file, zos, name + "/" + file.getName(),
KeepDirStructure);
} else {
compress(file, zos, file.getName(), KeepDirStructure);
}

}
}
}
}

private static void compress(List<File> sourceFileList,
ZipOutputStream zos, boolean KeepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
for (File sourceFile : sourceFileList) {
String name = sourceFile.getName();
if (sourceFile.isFile()) {
zos.putNextEntry(new ZipEntry(name));
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
if (KeepDirStructure) {
zos.putNextEntry(new ZipEntry(name + "/"));
zos.closeEntry();
}

} else {
for (File file : listFiles) {
if (KeepDirStructure) {
compress(file, zos, name + "/" + file.getName(),
KeepDirStructure);
} else {
compress(file, zos, file.getName(),
KeepDirStructure);
}
}
}
}
}
}
}

业务中需要删除临时文件夹以及下面的所有文件,用到的工具类代码如下所示:

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
/**
* 文件工具类
* @author chuenhung
* @createTime 2022/06/09
*/
public class FileUtils {

/**
* 删除文件或文件夹
*
* @param: file 如果是文件删除该文件,如果是文件夹删除该文件夹以及下面的所有文件
* @return void
* @author chuenhung
* @date 2022/6/9/
*/
public static void deleteFile(File file) {
if (file.isFile()) {
file.delete();
} else {
String[] childFilePaths = file.list();
for (String childFilePath : childFilePaths) {
File childFile = new File(file.getAbsolutePath() + "/" + childFilePath);
deleteFile(childFile);
}
file.delete();
}
}
}

参考链接1参考链接2

业务代码

核心逻辑就是要把每个成果申请的所有文件放入到成果申请临时文件夹中,成果申请临时文件夹名:“年份”+“成果名称”,然后对每个成果申请文件夹压缩,最后删除临时文件夹。我在代码中也有详细地写注释,业务代码如下所示:

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
@Override
public void batchDownload(HttpServletResponse response, HttpServletRequest request, List<Long> idList) throws Exception {
// 1、参数校验
if(CollectionUtil.isEmpty(idList)){
throw new CommonsException(MessageCode.PARAM_NULL);
}
// 2、设置response相关内容(包括文件名)
StringBuilder builder = new StringBuilder();
builder.append("成果附件");
builder.append(DateUtil.getNowDate("yyyyMMddHHmmss"));
builder.append(".zip");
String fileName = HttpHelper.createDownloadFileName(builder.toString());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
// 3、根据成果主键批量查询成果信息
Map<String,Object> map = new HashMap<>();
map.put("idList",idList);
List<AchievementApplication> applicationList = achievementApplicationMapper.queryAchievementApplicationList(map);
if(CollectionUtil.isEmpty(applicationList)){
throw new CommonsException(MessageCode.DATA_NOT_FOUND);
}
// 4、把每个成果申请的所有文件放入到成果申请临时文件夹中,成果申请临时文件夹名:年份+成果名称
// 每一个压缩的成果附件路径
List<String> outDirList = new ArrayList<>();
for(AchievementApplication application: applicationList){
if(StringUtil.isNotEmpty(application.getAttachment())){
List<AttachmentFileBo> attachmentFileBoList = attachmentService.listFormalAttachmentFile(application.getAttachment());
if(CollectionUtil.isNotEmpty(attachmentFileBoList)){
// 成果申请临时文件夹名:年份+成果名称
builder.delete(0,builder.length());
builder.append(application.getYear());
builder.append("-");
builder.append(application.getName());
// 循环对每个成果申请的所有文件压缩
List<String> fileNameList = new ArrayList<>();
// 每个成果的临时复制后存放的文件夹,之后对这个文件夹压缩
String outDir = attachementUploadUrl+builder.toString();
File outDirFile = new File(outDir);
outDirFile.mkdirs();
for(AttachmentFileBo fileBo: attachmentFileBoList){
// 每个成果如有重复附件名则不处理
if(!fileNameList.contains(fileBo.getFileName())){
File sourceFile = new File(attachementUploadUrl+fileBo.getFileSaveName());
if (!sourceFile.exists()) {
log.error("AchievementApplicationServiceImpl.batchDownload附件{}丢失",fileBo.getFileName());
throw new CommonsException(MessageCode.ATTACHMENT_LOST, "附件:" + fileBo.getFileName() + "丢失");
}
// 复制到临时文件夹
File destinctFile = new File(outDir+"/"+fileBo.getFileName());
Files.copy(sourceFile.toPath(), destinctFile.toPath());
}
fileNameList.add(fileBo.getFileName());
}
outDirList.add(outDir);
}
}
}
// 4、对成果申请文件夹压缩
if(CollectionUtil.isNotEmpty(outDirList)){
ZipUtil.toZip(outDirList, response.getOutputStream(),true);
}
// 5、删除临时文件夹
for(String outDir: outDirList){
File file = new File(outDir);
FileUtils.deleteFile(file);
}
}

作为码农平时搜集一些小知识点个人认为是个不错的习惯,书上说

好记性不如烂笔头

我想即使是以前忽略或者新get的很简单的东西,自己动手记下来不管如何印象也会更深刻。

mybatis的配置打印的是info级别日志 线上环境建议去除

1
2
3
4
5
6
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration:
#打印sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
call-setters-on-nulls: true

MySQL的GROUP_CONCAT给值加上单引号后再拼接

语法:

1
SELECT GROUP_CONCAT(DISTINCT '''',待拼接字段,'''') FROM 表名

示例代码:

1
2
select GROUP_CONCAT(DISTINCT '''',company,'''')  from t_awards_detail WHERE company_type='1';
UPDATE t_awards_detail SET company_type='3'

返回数据:
‘上海产业研究院’,‘上海公司’,‘上海曦智科技有限公司’

Linux查询某个进程JVM占用情况

语法:

1
jstat -gc 进程号  刷新时间

示例代码:

1
2
-- 查询进程6126的JVM占用情况 每隔3秒刷新一次
jstat -gc 6126 3000

Linux查询某个进程CPU、内存占用情况

1
top -H -p 进程id

MySQL的concat()、concat_ws()和group_concat()的用法

group_concat()对多字段拼接操作示例代码:

1
group_concat(column1,'拼接字符串'column2 SEPARATOR '分割字符串')

例如,筛选不同状态的数量、部门和用户拼接数据sql如下:

1
2
3
SELECT STATUS,COUNT(id),GROUP_CONCAT(reply_department,'-',reply_user SEPARATOR '、') 
FROM t_sub_order WHERE main_order_id='1'
GROUP BY status

查询结果如下所示:
在这里插入图片描述
参考链接

Java导出大批量数据的优化过程

  • 使用EasyExcel生成导出文件流
  • 异步把文件流上传到服务器,成功后更新文件上传状态
  • 用户去页面下载
    10W+ 的数据从生成Excel文件流到上传只要8秒,原来约要8分钟。
    参考链接

request.getParameter(“ticket”)方法

  • 接口传参为form表单格式提交,即Content-Type为application/x-www-form-urlencoded类型
  • /web/auth/v1/login/getUserRight?ticket=1234567。
    只有这两种格式可以取到参数,取不到JSON格式的参数。

MySQL按中文拼音字母排序

如果表字段使用的GBK编码的话,我们可以直接order by value ,因为GBK本身就是按照拼音字母排序ABCDEFGHIGK…,当第一位相同的时候会比较第二位,以此类推。 如果表字段使用的UTF-8编码的话,通常我们都会的编码,这样我们可以使用MySQL的convert方法开转换gbk进行排序。
举例:

1
SELECT * FROM TEST ORDER BY CONVERT(value USING GBK) ASC/DESC

自定义排序

自定义排序是根据自己想要的特定字符串(数字)顺序进行排序。
主要是使用函数 FIELD(str,str1,str2,str3,…)
MySQL的自定义排序,str与str1、str2、str3…进行比较,并按照str1,str2,str3…的顺序输出,如果遇到str为null或者不存在str1,str2,str3…中的情况的则序列为0,
例1:

1
SELECT * FROM TEST ORDER BY FIELD(value,'test1','test2','test3','test4') ASC/DESC

例2:

1
SELECT * FROM TEST WHERE VALUE IN('test1','test2','test3','test4') ORDER BY FIELD(value,'test1','test2','test3','test4') ASC/DESC 

自定义排序(详细)

  • case when 转换实现自定义排序
  • mysql排序函数filed 实现自定义排序(推荐)
  • 函数locate 实现自定义排序
  • 函数lnstr 实现自定义排序
  • 利用数据字典实现自定义排序

参考链接

Linux将一个服务器上的文件或者文件夹复制到另一个服务器上

scp命令

命令语法:

1
2
3
4
5
6
7
8
9
10
11
// 从本地主机传输文件到远程主机
scp [本地文件路径] [用户名]@[远程主机IP地址]:[目标路径]

// 从远程主机传输文件到本地主机
scp [用户名]@[远程主机IP地址]:[远程文件路径] [本地目标路径]

// 传输整个目录
scp -r [本地目录路径] [用户名]@[远程主机IP地址]:[目标路径]

// 指定端口
scp -P [端口号] [本地文件路径] [用户名]@[远程主机IP地址]:[目标路径]

示例:

1
[award_dev@localhost ~]$ scp -r /opt/aspire/product/award_dev/testMove award_dev@10.12.7.182:/home/award_dev/
1
2
3
4
5
6
7
The authenticity of host '10.12.7.182 (10.12.7.182)' can't be established.
ED25519 key fingerprint is SHA256:2wVRM0wwyLswkz2qgc/osZnRRvMlLRYrOSrHqTUVqVA.
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:1: 10.12.7.124
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.12.7.182' (ED25519) to the list of known hosts.
award_dev@10.12.7.182's password:

密码输入成功就可以迁移过去,不过存在中文文件名的会有问题(文件名改为了数字且内容为空)。

解决中文文件名迁移问题:
1、现在本地机器给待迁移文件夹打压缩包
命令: tar -zcvf filename.tar.gz dir/file
2、scp命令迁移到新服务器
3、在新服务器中解压
命令: tar -zxvf tarname.tar.gz -C 指定解压后文件存放地址
参考链接1参考链接2

如果远程服务器是sftp服务器,用scp命令可能会不成功,这时候需要使用sftp命令传输。

sftp命令

命令语法:

1
sftp -oPort=[端口号] [用户名]@[ip地址]

使用示例:
先进入本机待传文件的目录 -> 用命令连接上远程sftp服务器 -> 进入远程待接收文件的目录 -> put 文件名

1
2
3
4
5
6
7
8
9
10
[award_server@localhost jlwz-innovation]$ sftp -oPort=55521 package@10.217.247.13
Authorized only. All activity will be monitored and reported
package@10.217.247.13's password:
Connected to 10.217.247.13.

sftp> cd /upload/jlwz/testsftp/
sftp> put reward-innovation-1.0.0.1-SNAPSHOT.jar
Uploading reward-innovation-1.0.0.1-SNAPSHOT.jar to /upload/jlwz/testsftp/reward-innovation-1.0.0.1 -SNAPSHOT.jar
reward-innovation-1.0.0.1-SNAPSHOT.jar 100% 221MB 1.8MB/s 02:02 s
sftp>

spring占位符打印日志

1
2
3
4
5
try{
int zero = 2/0;
}catch (Exception e){
log.error("测试占位符打印日志,{},{}",null,b,e);
}

1、前面的参数必须和占位符一一对应。
2、最后面参数是异常信息可以打印,非异常信息不会打印。
3、占位符的值可以为null。

Linux复制文件夹下面的所有文件到另一个文件夹

1
cp -r 当前路径/* 目标路径

注意:目标路径必须先存在。
例如:

1
cp -r /home/award_dev/nginx-1.20.2/conf/* /usr/local/nginx/conf

Nginx指定安装位置

默认位置是/usr/local/nginx,需要进入nginx-1.20.2目录后执行下面的命令
./configure --prefix=/home/award_dev/nginx
参考链接

Nginx安装步骤

参考链接

Linux查看端口

1
netstat -anp | grep 8888

SpringBoot+Vue.js前后端分离实现大文件分块上传

参考链接

Java 8 Predicate

参考链接

SpringCloud读取配置中心配置

需要引入下面的依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- bootstrap依赖,加载bootstrap.yml-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

客户端添加spring-cloud-starter-config依赖后 会默认最高优先级读取(即使bootstrap.yml不配置)localhost:8888链接下的application.yml配置。
具体可以通过http://10.12.7.182:8888/应用名(对应bootstrap引导文件中的config.name配置)/profile 访问查看读取的配置 例如:http://10.12.7.182:8888/webbas32-gateway-v1/dev
或者直接访问文件 http://10.12.7.182:8888/webbas32-application-gateway-v1-dev.yml
spring-cloud-starter-bootstrap依赖非必须
参考链接

SpringBoot的jasypt加解密

StringEncryptor类中有加解密方法
配置文件配置ENC(“密文”)后,@Value注解可以自动解密
参考链接1参考链接2

0%