# 工作问题解决方案汇总
# 1、硬盘分卷问题(linux)
# 问题描述:
linux 我有两块硬盘 vdc 和 vdb 。vdc 分了两个分区 vdc1、vdc2,vdb 分了 3 个分区 vdb1、vdb2、vdb3, 然后有两个卷组 dmbak、storage。卷组 dmbak 中的物理卷有 vdc1,vdc2 和 vdb3,现在的问题是我的 dmbak 中的卷组用到了 vdb3 分区现在我想我想卸载 /dmbak 目录挂载的硬盘,并把 dmbak 卷组的 vdb3 给还回去,重新进行硬盘规划,因为原来的分的很乱。 (关于分卷这些概率可以参考 linux 文章)
pvs情况: | |
[root@localhost ~]# pvs | |
PV VG Fmt Attr PSize PFree | |
/dev/vda3 uos lvm2 a-- 78.80g 0 | |
/dev/vda4 uos lvm2 a-- <120.00g 0 | |
/dev/vdb1 storage lvm2 a-- <400.00g 56.00m | |
/dev/vdb2 storage lvm2 a-- <400.00g 0 | |
/dev/vdb3 dmbak lvm2 a-- <800.00g 0 | |
/dev/vdc1 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdc2 dmbak lvm2 a-- <400.00g 0 | |
vgs情况: | |
[root@localhost ~]# vgs | |
VG #PV #LV #SN Attr VSize VFree | |
dmbak 3 1 0 wz--n- 1.56t 0 | |
storage 2 1 0 wz--n- 799.99g 56.00m | |
uos 2 3 0 wz--n- <198.80g 0 | |
lvs情况: | |
[root@localhost ~]# lvs | |
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert | |
dmbaklv dmbak -wi-ao---- 1.56t | |
vo storage -wi-ao---- <799.94g | |
home uos -wi-ao---- <24.77g | |
root uos -wi-ao---- <170.00g | |
swap uos -wi-ao---- <4.04g | |
#根据上述的查看结果可以清楚的得知在卷组 dmbak 中有三个物理卷,且属于不同硬盘的 |
# 影响:
由于后续不能在扩容了或者其他目录需要缩容,现在目录又被正在使用的达梦数据库给占用了
# 解决方案:
先将达梦占用的这个目录换一个路径,移动到其他目录。在将挂载的目录卸载,最后卸载卷组后重新挂载
根据当时的情况进行一下操作步骤处理
- 由于达梦数据库保存的归档日志和备份文件都在 /dmbak 目录下,所以需要修改达梦归档日志和备份文件的路径 (达梦数据库的使用和安装看另一篇文章)
- 修改归档文件的路径
-- 先查下这个 | |
select ARCH_FILE_SIZE,ARCH_SPACE_LIMIT from v$dm_arch_ini; | |
-- 修改过程会短暂影响数据库,调整过程无法访问数据库对象,修改路径前最好数据库备份 | |
alter database mount; | |
alter database noarchivelog; | |
alter database add archivelog 'type=local,dest=新归档路径,file_size=单个归档文件大小(m),space_limit=归档文件上限大小(m)'; | |
alter database delete archivelog 'dest=原归档路径'; | |
alter database archivelog; | |
alter database open; | |
-- 由于是在达梦客户端代理配置定时备份任务,直接修改路径即可 |
- 处理数据:这里有两种方式处理数据,以下列举
#第一种:分配一块新的硬盘并将它加入到 dmbak 这个 vg 组, 在把同组其他分区的数据通过 pvmove 命令移动到新加的硬盘 (注意:这种方式需要保证足够的空间) | |
PV VG Fmt Attr PSize PFree | |
/dev/vda3 uos lvm2 a-- 78.80g 0 | |
/dev/vda4 uos lvm2 a-- <120.00g 0 | |
/dev/vdb1 storage lvm2 a-- <400.00g 56.00m | |
/dev/vdb2 storage lvm2 a-- <400.00g 0 | |
/dev/vdb3 dmbak lvm2 a-- <800.00g 0 | |
/dev/vdc1 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdc2 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdd dmbak lvm2 a-- <1000.00g 200.00g | |
将 /dev/vdb3、 /dev/vdc1、/dev/vdc2的数据都移动到 /dev/vdd 上 | |
#移动数据:使用 pvmove 命令将该物理卷中的数据移动到其他物理卷上。 | |
pvmove /dev/vdb3 /dev/vdd | |
pvmove /dev/vdc1 /dev/vdd | |
pvmove /dev/vdc2 /dev/vdd | |
pvmove /dev/vdc1 /dev/vdb3 | |
pvmove /dev/vdc2 /dev/vdb3 | |
#移除物理卷:移动数据完成后,可以使用 vgreduce 命令将该物理卷从卷组中移除。假设要移除的物理卷名为 /dev/vdb3: | |
vgreduce dmbak /dev/vdb3 | |
#然后将 /dev/vdc 释放 合成一块 800g 大小的硬盘 在重新加入组 dmbak | |
vgreduce dmbak /dev/vdc1 | |
vgreduce dmbak /dev/vdc2 | |
#合并后的硬盘在追加回来 | |
vgextend dmbak /dev/vdc | |
#最后在将 vdd 移动到 vdc | |
pvmove /dev/vdb3 /dev/vdc | |
#结果: | |
#移除物理卷:移动数据完成后,可以使用 vgreduce 命令将该物理卷从卷组中移除。假设要移除的物理卷名为 /dev/vdb3: | |
vgreduce dmbak /dev/vdb3 | |
vgreduce dmbak /dev/vdd | |
#上面这种方式比较麻烦 | |
#第二种:将新的临时硬盘挂载到临时目录 /temp_dmbak 再将 /dmbak 目录下的内容移动到临时目录 /temp_dmbak | |
nohup cp -r /dmbak /temp_dmbak & | |
#清除 /dmdbk 目录上的数据: | |
rm -rf /dmdbk/* | |
#这将递归删除 /dmdbk 目录下的所有文件和子目录。 | |
#继续执行后续操作 卸载文件系统 - 卸载逻辑卷 - 卸载卷组 |
- 卸载文件系统 /dmbak 目录 首先,确保卷组中的逻辑卷上的文件系统已经被卸载。您可以使用以下命令来卸载文件系统
## 确保在卸载之前没有任何进程在使用的目录。 | |
umount /dmbak |
- 清除硬盘数据: 使用
dd命令
#清除 /dev/vdc 硬盘上的数据: |
- 卸载逻辑卷:接下来,您需要卸载卷组中的逻辑卷。您可以使用
lvremove命令来删除逻辑卷
lvremove /dev/dmbak/dmbaklv |
- 卸载卷组:一旦逻辑卷被删除,您可以使用
vgremove命令来删除卷组:
vgremove dmbak |
- 卸载物理卷: 卷组删除之后可以删除物理卷,重新设置物理卷分区合并分区等。否则会有提示,设备已包含一个 'LVM2_member' 签名,写入命令会将其移除
pvmove /dev/vdb3 | |
pvmove /dev/vdc1 | |
pvmove /dev/vdc2 |
- 最后进行重新分卷分组 (参考 linux: 使用物理卷 (PV)、逻辑卷 (LV)、卷组 (VG) 管理管理的方式挂载硬盘这一章)
对应 231 服务器的问题解决步骤
#停止达梦服务 | |
./DmServiceDM stop | |
#--- | |
#alter database mount; | |
#alter database noarchivelog; | |
#alter database add archivelog 'type=local,dest=/data/dmbak,file_size=2048,space_limit=102400'; | |
#alter database delete archivelog 'dest=/dmbak/dmarch'; | |
#alter database archivelog; | |
#alter database open; | |
#拷贝到新挂载的硬盘 这里新加的临时硬盘为 vdd | |
mkfs.ext4 /dev/vdd | |
mkdir -p /tempbak | |
mount /dev/vdd /tempbak | |
nohup cp -r /dmbak /tempbak & | |
#为了校验达梦换了归档文件路径是否能正常启动 | |
#nohup cp -r /tempbak/dmbak/dmarch/* /data/dmbak & | |
#./DmServiceDM start | |
#删除文件内容 | |
rm -rf /dmbak/* | |
#卸载 | |
umount /dmbak | |
#删除文件夹 | |
rm -rf dmbak | |
#删除逻辑卷 | |
lvremove /dev/dmbak/dmbaklv | |
#卸载卷组 | |
vgremove dmbak | |
#卸载物理卷 | |
pvremove /dev/vdc1 | |
pvremove /dev/vdc2 | |
pvremove /dev/vdb3 | |
#重新分区 | |
parted 命令 | |
mklable | |
gpt | |
mkpart | |
vdc1 | |
ext4 | |
0% | |
50% | |
toggle | |
1 | |
lvm | |
mkpart | |
vdc2 | |
ext4 | |
50% | |
10% | |
toggle | |
2 | |
lvm | |
quit | |
#重新创建物理卷 | |
pvcreate /dev/vdc1 | |
pvcreate /dev/vdc2 | |
#创建新卷组 | |
vgcreate dmbakvg /dev/vdc1 /dev/vdc2 | |
#创建逻辑卷 | |
lvcreate -L 799.98g -n dmbaklv dmbakvg | |
#创建新目录 | |
mkdir dmbaknew | |
#格式化逻辑卷 | |
mkfs.ext4 /dev/dmbakvg/dmbaklv | |
#重新挂载 | |
mount /dev/dmbakvg/dmbaklv /dmbak/ | |
#可能出现挂载不了的情况 原因可能是系统记录的信息有误 | |
#修改挂载记录: | |
/etc/fstab | |
#执行以下命令 | |
systemctl daemon-reload | |
#重新挂载 | |
mount /dev/dmbakvg/dmbaklv /dmbak/ | |
#移动回数据 | |
nohup cp -r /tempbak/dmbak/* /dmbaknew & | |
#将 vdb3 追加 | |
parted /dev/vdb | |
rm 3 | |
mkpart | |
primary | |
ext4 | |
toggle | |
3 | |
lba | |
toggle | |
3 | |
lvm | |
quit | |
pvcreate /dev/vdb3 | |
vgextend storage /dev/vdb3 | |
lvexted -L +800g /dev/storage/vo | |
resize2fs /dev/storage/vo |
对应 216 服务器的问题解决步骤
#由于是没有做如何分区分组,直接将物理磁盘挂载那么可以用以下方式
#直接可以使用:resize2fs /dev/vdb 3T 命令扩展
#备份文件
nohup cp -r /java /test &
#卸载
umount /java
#分区
parted /dev/vdb
#分两个
vdb1 vdb2
#创建物理卷
pvcreate /dev/vdb1
pvcreate /dev/vdb2
#创建卷组
vgcreate projectvg /dev/vdb1
#创建逻辑卷
lvcreate -L +1.95T -n projectlv projectvg
rm -rf /java
mkdir -p java
mount /dev/projectvg/projectlv /java
# 2、云服务部署的达梦数据库查询字段类型有 Clob 类型会返回所有连接的元数据
# 问题描述:
连接 jdbcurl:jdbc:dm://DWM,DWM 是云服务连接,本地是直接连接 jdbcurl: jdbc:dm://localhost:5236。 我在本地查询接口字段名:distinfo, 类型为:clob,值为 null。 在本地查出来是为空的 但是换成云服务器连接查询结果就变成了一大串元数据 比如:connection:.... 之类的。
# 影响:
请求接口响应的内容就会有一大串没有用的元数据,响应的大小也会影响速度
# 解决方案:
确定数据库 clob 类型的字段是否存在值,不存在就按照 clob 在官网找解决方案,排除云服务的达梦是否需要什么配置
- 询问云服务部署的工作人员,无问题。
- 用监视工具追语句在客户端查询正常
- 排除项目查询出来的结果是否有问题,进行输出打印结果。
- 根据打印结果分析,发现数据库 clob 类型的值是 null,但是查询出来的结果却又值 是 clob 字段类型的 hash 值,调试 clob 值是包含了达梦连接信息的元数据
- 因为项目是前后端分离的,以 json 返回的方式,那么查询的结果会转为 json,那么对应的元数据值也会被转换,所以就返回了这些信息
所以只需要在查询出结果的时候把 clob 转成字符串就行, 有两种方式
- 循环结果处理 clob 类型
- 通过达梦官网可以加参数在查询的时候自动将 clob 转成 string 类型。 clobAsString=true;
- 或者改成 longvarchar 类型也可以
将连接改成:jdbc:dm://DWM?clobAsString=true 后 正常!
# 3、mount 挂载不上,不提示任何信息
# 问题描述:
mount 挂载不上目录
# 影响:
在不改目录的情况下 无法重置挂载新的硬盘
# 解决方案:
- 使用 cat /var/log/message 查看系统日志信息
- 如果出现类似:systemd: Unit data1.mount is bound to inactive unit dev-sdj.device. Stopping, too. 的字样表示系统记录信息有误
- 查看 /etc/fstab 是否手动加了开机自启的挂载目录的配置
- 如果存在删除这条记录
- 重试如果还不行就执行 systemctl daemon-reload 命令,作用是重新加载系统配置
# 4、硬盘有 4T 大小,实际只有 2T
# 问题描述:
当一块超过 2T 大小的硬盘直接格式化挂载到指定目录后(这种方式是直接把物理卷挂载到了目录),最大只能是 2T 的容量
# 影响:
导致多余的容量闲置浪费
# 解决方案:
可以使用 resize2fs /dev/vdb 3T 命令将 /dev/vdb 挂载的目录的容量扩展到 3T,缺点是无法分配超过硬盘本身的大小
# 5、starrocks 不支持子查询语法的问题
# 问题描述:
sqlserver 语句转为 starrocks 数据库的语法,不支持子查询,并且子查询更主查询没有关联项
--sqlserver 语句 | |
select count(*) AS num | |
from ( | |
select (select top 1 STUFF(STUFF(CONVERT(char(8),DATEADD(DAY,0,solar_date),112),5,0,N'-'),8,0,N'-') from ( | |
select top 7 * from zcjy_shq..workday where is_workday=1 and solar_date>( | |
select convert(varchar (100),b.pub_date,112) ) | |
) a order by a.solar_date desc) as epub_date , | |
a.createDate,a.cno,a.prj_jiaoyi_no,a.distno,a.ztid | |
from zcjy_shq..ht_contract a,zcjy_shq..policyinfo b | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
) c where datediff(day,c.epub_date,c.createdate)>30 and c.distNo like '0607%' | |
workday的内容为: | |
id solar_date is_workday | |
1 20220101 1 | |
2 20220102 1 | |
3 20220103 0 | |
-- 语句说明,子查询部分其实是想查出 b.pub_date 时间之后的 7 天工作日的最后一天的时间 workday | |
-- 由于不支持子查询所以得换种方式查询出一样的效果 |
# 影响:
在 starrocks 上无法正常运行该统计表内容
# 解决方案:
- 需要将子查询的内容进行拆分查询
- 首先查询出主查询的结果(不带子查询的内容)做为临时表并确定一个唯一值 a
- 在和 workday 表做笛卡儿积查询并根据原有的条件过滤,并根据唯一值和 solar_date 分组 排序, 那么结果就得到每个主键下的所有的日期
- 在更具 solar_date 排序,keyword 分组获取每组中排第 7 的时间
- 最后就可以根据 keyword 关联上主查询了
-- 最终改造的语句 | |
with a as( | |
select b.pub_date as pub_date, concat(a.cno, b.pub_date) as keyword, | |
a.createDate, a.cno, a.prj_jiaoyi_no, a.distno, a.ztid | |
from zcjy_shq.ht_contract a,zcjy_shq.policyinfo as b | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
), | |
b as ( | |
select keyword , solar_date from a, zcjy_shq.workday as b where b.solar_date > jodatime_format(a.pub_date, 'yyyyMMdd') and is_workday=1 group by keyword , solar_date order by solar_date | |
), | |
-- 核心 | |
tempc as ( | |
SELECT t1.keyword, concat(substr(t1.solar_date, 1, 4), '-', substr(t1.solar_date, 5, 2), '-', substr(t1.solar_date, 7, 2)) as epub_date | |
FROM ( | |
SELECT *, | |
ROW_NUMBER() OVER(PARTITION BY keyword ORDER BY solar_date asc) AS row_num | |
FROM b | |
WHERE 1 = 1 | |
) t1 | |
WHERE t1.row_num = 7 | |
) | |
select count(*) AS num | |
from ( | |
select | |
cc.epub_date, | |
a.createDate,a.cno,a.prj_jiaoyi_no,a.distno,a.ztid | |
from zcjy_shq.ht_contract a,zcjy_shq.policyinfo as b, tempc as cc | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and concat(a.cno, b.pub_date) = cc.keyword and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
) c | |
where DATEDIFF(timestamp(c.createdate), timestamp(c.epub_date)) > 30 | |
and c.distNo like '0607%' |
# 6、域名访问的项目超时
# 问题描述:
在同一台服务器上搭建了两个完全一样的项目,一个用 ip 访问一个用域名访问。都通过 nginx 转发,nginx 配置的超时时间是一样的,而且是没到 nginx 配置的超时时间就一样超时报 504 域名访问的项目会这样,ip 的不会
# 影响:
影响用户正常使用
# 解决方案:
- 修改两边的 nginx 超时时间短一些(几秒钟),发现正常超时
- 修改 nginx 超时时间长一些(5 分钟)域名访问的提前超时,ip 访问的正常超时
- 查看 nginx 的配置是否生效
nginx -t正常 - 查看 nginx 的访问日志
tail -f log/access.logtail -f log/error.log发现只有 ip 访问的有超时的日志,域名访问的没有。这就表示域名访问的提前超时了 - 最终确定就是域名解析的问题,最后交给相应的工作人员设置了超时时间,域名访问正常!
# 7、a 项目访问附件失败就去访问 b 项目,b 项目也是如此
# 问题描述:
现在有 a、b 两个项目一个域名访问,一个 ip 访问(两个不同的 tomcat),一开始没考虑好,由于用户操作上传的附件是上传到 tomcat 相对路径下,导致如果我在 ip 访问的项目上传的图片,想去域名访问的项目查看就看不到图片
# 影响:
用户无法正常看到图片
# 解决方案:
修改数据库配置,将存储附件的目录设置为同一个,需要移动已经上传的附件,这种方式有点麻烦需要停项目
直接在 nginx 配置,当 a 连接访问不到内容,就去访问 b 连接
#请求的连接#这个是域名访问的 nginx 配置 域名实际访问的 ip 端口是 192.168.20.30:8088location /tdlz_ht/uploads/new/ {
proxy_intercept_errors on;
#当请求返回的是 404 页面 就转发到 @service_static_fileerror_page 404 = @service_static_file;
proxy_pass https://192.168.20.30:8088/tdlz_ht/uploads/new/;
}location @service_static_file {
set $original_request_uri $request_uri;
return 301 https://192.168.20.30:8888$original_request_uri;
}#比如请求连接为 https://xxxx.xxx.xx.xx.cn/tdlz_ht/uploads/new/20231016/aaa_change.jpg#如果返回 404 那么就回去访问 @service_static_file 也就是 192.168.20.30:8888 这个端口#否则就正常访问 192.168.20.30:8088
# 8、nginx 413 问题
# 问题描述:
nginx 413 问题,这个错误通常发生在上传文件大小超过 Nginx 默认允许的最大请求体大小时
# 影响:
无法正常上传附件
# 解决方案:
修改 nginx 配置
http {
# 其他配置...client_max_body_size 8M;(配置请求体缓存区大小, 不配的话)
client_body_buffer_size 128k;(设置客户端请求体最大值)
fastcgi_intercept_errors on;
# 其他配置...}#请注意,在增加限制时要谨慎,确保服务器有足够的资源来处理大型请求。
# 9、不需要登陆的内部系统需要调用权限控制的项目的接口
# 问题描述:
有一个内部定时任务项目,需要调用 cas 认证的项目的接口,需要在代码里做登陆认证调接口
# 影响:
功能无法正常使用
# 解决方案:
一步步模拟 cas 登陆流程做对应的处理,最后得到项目有效的 sessionid, 请求的时候带上 sessionid 就可以跳过登陆了
如果有用到前端 js 的 RSA 加密 则可以用 java 的 ScriptEngine 解析执行 js 方法
@Value("${monthlyReport.isStart}")
private String isStart;
@Value("${monthlyReport.login.loginCasUrl}")
private String loginCasUrl;
@Value("${monthlyReport.login.loginModuleUrl}")
private String loginModuleUrl;
@Value("${monthlyReport.keepLoginUrl}")
private String keepLoginUrl;
@Value("${monthlyReport.rsaKeyUrl}")
private String rsaKeyUrl;
@Value("${monthlyReport.login.username}")
private String username;
@Value("${monthlyReport.login.password}")
private String password;
@Value("${monthlyReport.distNo}")
private String distNo;
@Value("${monthlyReport.years}")
private String years;
@Value("${monthlyReport.beginMonths}")
private String beginMonths;
@Value("${monthlyReport.endMonths}")
private String endMonths;
@Value("${monthlyReport.selTblInfoIndex}")
private String selTblInfoIndex;
@Value("${monthlyReport.dataInput.inputUrl}")
private String inputUrl;
@Value("${monthlyReport.dataInput.importType}")
private String importType;
@Value("${monthlyReport.dataSummary.dataSummaryUrl}")
private String dataSummaryUrl;
@Value("${monthlyReport.dataSummary.months}")
private String months;
@Value("${monthlyReport.dataSummary.hzType}")
private String hzType;
@Value("${monthlyReport.retries}")
private Integer retries;
@Value("${monthlyReport.delayDuration}")
private Integer delayDuration;
RestTemplate restTemplate = null;
public void execReportDataOperationTask()
{StopWatch stopWatch = new StopWatch("财务月报-->数据的转入汇总操作");
// 定时执行的任务 用于登陆的用户定时请求保持连接状态ScheduledFuture<?> future = null;
try{stopWatch.start("cas登陆验证");
logger.info("============" + DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN) + " 开始执行cas验证登录============");
// RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();// restTemplate = restTemplateBuilder.build();// 处理 https 请求报错的问题TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
SSLContext sslContext = null;
try{sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
}catch (Exception e)
{logger.error("-------------配置https请求失败!!-------------");
return;
}SSLConnectionSocketFactory sslConnectionSocketFactory= new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
// 默认开启了 cooke 管理CloseableHttpClient closeableHttpClient = HttpClientBuilder
.create()
.setMaxConnPerRoute(10)
.setSSLSocketFactory(sslConnectionSocketFactory)
.useSystemProperties()
.build();
HttpComponentsClientHttpRequestFactory requestFactory= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(closeableHttpClient);
// requestFactory.setConnectionRequestTimeout(10*1000);// requestFactory.setConnectTimeout(10*1000);// 请求超时时间 25 分钟requestFactory.setReadTimeout(30 * 60 * 1000);
restTemplate = new RestTemplate(requestFactory);
// 设置请求头,请求类型为 jsonHttpHeaders toLoginHeader = new HttpHeaders();
// 设置请求参数HashMap<String, Object> toLoginParam = new HashMap<>(5);
// 用 HttpEntity 封装整个请求报文HttpEntity<HashMap<String, Object>> toLoginHttpEntity = new HttpEntity<>(toLoginParam, toLoginHeader);
ResponseEntity<String> responseLoginPage = null;
try{logger.info("============开始第一次cas登录 获取登录必要的参数============");
// 第一次请求跳转到登陆页面 并获取登陆必要的条件responseLoginPage = restTemplate.exchange(loginCasUrl, HttpMethod.GET, toLoginHttpEntity, String.class);
logger.info("============第一次登录成功!!");
}catch (ResourceAccessException e)
{logger.error("-------------网络连接异常 请检查网络情况-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}catch (Exception e)
{logger.error("-------------其他错误!-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}// 读取返回值String loginPageBody = responseLoginPage.getBody();
// 获取登陆必要的条件Integer jsessionidIndex = loginPageBody.indexOf("jsessionid=");
loginPageBody = loginPageBody.substring(jsessionidIndex + 11);
String jsessionid = loginPageBody.substring(0, loginPageBody.indexOf("\""));
Integer ltIndex = loginPageBody.indexOf("name=\"lt\" value=\"");
loginPageBody = loginPageBody.substring(ltIndex + 17);
String lt = loginPageBody.substring(0, loginPageBody.indexOf("\""));
Integer executionIndex = loginPageBody.indexOf("name=\"execution\" value=\"");
loginPageBody = loginPageBody.substring(executionIndex + 24);
String execution = loginPageBody.substring(0, loginPageBody.indexOf("\""));
Integer _eventIdIndex = loginPageBody.indexOf("name=\"_eventId\" value=\"");
loginPageBody = loginPageBody.substring(_eventIdIndex + 23);
String _eventId = loginPageBody.substring(0, loginPageBody.indexOf("\""));
// 登陆头信息HttpHeaders getRsaKeyHeaders = new HttpHeaders();
// 伪造登陆头部getRsaKeyHeaders.set("Content-Type", "application/x-www-form-urlencoded");
// 获取 Rsa 加密公钥的请求参数MultiValueMap<String, Object> getRsaKeyParam = new LinkedMultiValueMap<>();
getRsaKeyParam.add("username", username);
// 报文HttpEntity<MultiValueMap<String, Object>> getEncryptionHttpEntity =
new HttpEntity<>(getRsaKeyParam, getRsaKeyHeaders);
ResponseEntity<String> getRsaKeyResult = null;
try{logger.info("============开始获取Rsa加密需要的参数");
// 获取 Rsa 加密 key 请求getRsaKeyResult = restTemplate.exchange(rsaKeyUrl + "?username=" + username
, HttpMethod.GET, getEncryptionHttpEntity
, String.class);
logger.info("============获取Rsa加密参数成功!!");
}catch (ResourceAccessException e)
{logger.error("-------------网络连接异常 请检查网络情况-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}catch (Exception e)
{logger.error("-------------其他错误!-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}HashMap<String, Object> encryptionPass = null;
String encryptionExponent = "";
String modulus = "";
String rsaEncryptPass = null;
// 获取加密 key 成功 进行加密if(getRsaKeyResult.getStatusCode().value() == 200)
{encryptionPass = JSONUtil.toBean(getRsaKeyResult.getBody(), HashMap.class);
if(CollUtil.isNotEmpty(encryptionPass))
{HashMap<String, String> rvalue = JSONUtil.toBean(encryptionPass.get("rvalue").toString(), HashMap.class);
encryptionExponent = rvalue.get("exponent");
modulus = rvalue.get("modulus");
rsaEncryptPass = getRsaEncryptPass(encryptionExponent, modulus, password);
}}// 校验密码加密情况if(StrUtil.isBlank(rsaEncryptPass))
{logger.error("-------------登陆用户:[{}]登陆密码加密失败!!!", username);
return;
}// 进行登录// 登陆头信息HttpHeaders loginHeaders = new HttpHeaders();
// 伪造登陆头部loginHeaders.set("Content-Type", "application/x-www-form-urlencoded");
// 设置请求参数// 因为请求为 post 请求且 content-type 的值为 application/x-www-form-urlencoded// 所有必须用下面的集合来传递参数MultiValueMap<String, Object> loginParam = new LinkedMultiValueMap<>(15);
loginParam.add("username", username);
loginParam.add("password", rsaEncryptPass);
loginParam.add("execution", execution);
loginParam.add("submit", "");
loginParam.add("_eventId", "submit");
loginParam.add("lt", lt);
// 登陆报文HttpEntity<MultiValueMap<String, Object>> loginHttpEntity =
new HttpEntity<>(loginParam, loginHeaders);
// 发起登陆请求ResponseEntity<String> loginResponse = null;
try{logger.info("============开始真正的cas登录============");
// 第一次请求跳转到登陆页面 并获取登陆必要的条件// 发起登陆请求loginResponse = restTemplate.exchange(loginCasUrl, HttpMethod.POST, loginHttpEntity, String.class);
}catch (ResourceAccessException e)
{logger.error("=-------------网络连接异常 请检查网络情况-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}catch (Exception e)
{logger.error("-------------其他错误!-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}// 登陆成功返回的 cookiesList<String> loginSuccessCookies = loginResponse.getHeaders().get("Set-Cookie");
if(loginResponse.getStatusCode() == HttpStatus.OK
&& CollUtil.isNotEmpty(loginSuccessCookies))
{logger.info("==========登陆成功!!!");
}else{logger.error("-------------登陆失败!!!");
return;
}logger.info("============开始获取对应模块的session============");
// 设置请求模块的头信息HttpHeaders getNavigateHeaders = new HttpHeaders();
// 设置请求参数HashMap<String, Object> getNavigateParam = new HashMap<>(5);
// 用 HttpEntity 封装整个请求报文HttpEntity<HashMap<String, Object>> getNavigateHttpEntity
= new HttpEntity<>(getNavigateParam, getNavigateHeaders);
ResponseEntity<String> navigateResult = null;
try{logger.info("============获取对应模块的session成功============");
navigateResult = restTemplate.exchange(loginModuleUrl, HttpMethod.GET, getNavigateHttpEntity, String.class);
}catch (ResourceAccessException e)
{logger.error("-------------网络连接异常 请检查网络情况-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}catch (Exception e)
{logger.error("-------------其他错误!-------------");
logger.error("----------->错误信息:{}", e.getMessage());
return;
}if(navigateResult.getStatusCode() != HttpStatus.OK)
{logger.error("-------------获取对应模块的session失败-------------");
return;
}stopWatch.stop();
TaskScheduler threadPoolTaskScheduler = new ConcurrentTaskScheduler();
future = threadPoolTaskScheduler.schedule(new Runnable()
{@Overridepublic void run()
{keepConnection(restTemplate);
}}, triggerContext -> new CronTrigger("0 0/3 * * * *").nextExecutionTime(triggerContext));
stopWatch.start("财务数据转入和汇总");
Boolean resultState = execDataInputAndSummary(restTemplate);
stopWatch.stop();
if(resultState)
{logger.info("=========数据的转入汇总成功=========");
}else{logger.error("--------数据的转入汇总失败!!!----------");
return;
}}catch (Exception e)
{logger.error("-------------执行数据转入汇总报错-------------");
logger.error("--------> 错误信息:{}", e.getStackTrace());
e.printStackTrace();
}finally{// 有执行的任务 则关闭if(future != null)
{// 关闭定时保持 session 状态的请求future.cancel(true);
}// 如果还有启动的计时任务 则先停止if(stopWatch.isRunning())
{stopWatch.stop();
}logger.info("执行结果: \r\n{}", stopWatch.prettyPrint());
}}/*** 根据给定的 Rsa 模和指数进行公钥加密数据
* @param encryptionExponent Rsa 加密指数
* @param modulus Rsa 加密模
* @param encryptData 需要加密的数据
* @return Rsa 加密后的数据(16 进制)
*/
public String getRsaEncryptPass(String encryptionExponent, String modulus, String encryptData)
{ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("js");
if(engine instanceof Invocable)
{// 创建回调执行任务Callable<String> task = new Callable<String>()
{@Overridepublic String call()
{// 需要执行的 js 文件路径String scriptPath = this.getClass().getClassLoader().getResource("static/js/mySecurity.js").getPath();
// 加载要执行的 js 文件try{engine.eval("load('" + scriptPath + "')");
}catch (ScriptException e)
{logger.error("-------------加载js文件失败!!!");
logger.error("----------->错误信息:{}", e.getMessage());
}Invocable invoke = (Invocable) engine;
// 调用 getRsaEncryptData 方法,并传入参数logger.info("============开始执行js函数中Rsa公钥加密功能。。。");
String encryptResult = null;
try{encryptResult = (String) invoke.invokeFunction("getRsaEncryptData"
, new Object[] {encryptionExponent, modulus, encryptData});
}catch (ScriptException e)
{logger.error("-------------执行js方法失败!!!");
logger.error("----------->错误信息:{}", e.getMessage());
}catch (NoSuchMethodException e)
{logger.error("-------------没有找到对应的执行方法!!!");
logger.error("----------->错误信息:{}", e.getMessage());
}return encryptResult;
}};
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(task);
try{// 执行任务 并设置任务执行的超时时间String encryptResult = future.get(10, TimeUnit.SECONDS);
return encryptResult;
}catch (InterruptedException e)
{logger.error("-------------Rsa公钥加密失败");
logger.error("----------->错误信息:{}", e.getMessage());
}catch (ExecutionException e)
{logger.error("-------------Rsa公钥加密失败");
logger.error("----------->错误信息:{}", e.getMessage());
}catch (TimeoutException e)
{logger.info("-------------执行Rsa公钥加密超时");
logger.error("----------->错误信息:{}", e.getMessage());
}}return null;
}/*** 保持心跳操作
* @param restTemplate
*/
public void keepConnection(RestTemplate restTemplate)
{String keepConnectionUrl = keepLoginUrl + "&_=" + DateUtil.currentSeconds();
ResponseEntity<String> keepConnectionResult
= restTemplate.exchange(keepConnectionUrl, HttpMethod.GET, null, String.class);
if(keepConnectionResult.getStatusCode() == HttpStatus.OK)
{logger.info("保持用户状态 : {}", keepConnectionResult);
}}/*** @param restTemplate 已经有登陆成功用户的状态信息
* @return 返回结果 true-> 成功 false-> 失败
*/
public Boolean execDataInputAndSummary(RestTemplate restTemplate)
{RetryPolicy retryPolicy = RetryPolicy.builder()
// 重试次数.maxRetries(retries)
// 重试延迟执行时间.delayDuration(delayDuration, TimeUnit.SECONDS)
.build();
// 需要执行的任务Callable<Boolean> callable = new Callable<Boolean>()
{@Overridepublic Boolean call()
{// stopWatch.start ("财务数据转入");logger.info("============开始调用数据转入接口============");
// 记录数据转入的状态Boolean dataInputApiRequestStatus = false;
// 设置转入数据接口的请求头HttpHeaders dataInputApiHeaders = new HttpHeaders();
dataInputApiHeaders.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
// 设置请求参数MultiValueMap<String, Object> dataInputApiParam = new LinkedMultiValueMap<>(15);
dataInputApiParam.add("distNo", distNo);
dataInputApiParam.add("years", years);
dataInputApiParam.add("beginmonths", beginMonths);
dataInputApiParam.add("endmonths", endMonths);
dataInputApiParam.add("importtype", importType);
dataInputApiParam.add("selTblInfoIndex", selTblInfoIndex);
// 用 HttpEntity 封装整个请求报文HttpEntity<MultiValueMap<String, Object>> dataInputApiHttpEntity
= new HttpEntity<>(dataInputApiParam, dataInputApiHeaders);
logger.info("============开始执行财务数据转入============");
ResponseEntity<String> dataInputResult =
restTemplate.exchange(inputUrl, HttpMethod.POST, dataInputApiHttpEntity, String.class);
HashMap<String, Object> resultJson = null;
// 如果请求成功 检验返回结果if(dataInputResult.getStatusCode() == HttpStatus.OK)
{if(StrUtil.isNotBlank(dataInputResult.getBody()))
{resultJson = JSONUtil.toBean(dataInputResult.getBody(), HashMap.class);
}}else{logger.error("-------------调用接口:[{}] 失败!!", inputUrl);
return false;
}// 如果结果为成功的状态if(CollUtil.isNotEmpty(resultJson))
{if((Integer)resultJson.get("gaugest") == 2)
{dataInputApiRequestStatus = true;
}}// 如果转入数据的接口失败了 就不在继续执行if(!dataInputApiRequestStatus)
{logger.error("-------------转入数据执行错误 错误信息:{}", resultJson.get("message"));
return false;
}else{logger.info("============转入数据成功!!!!!!!");
}// stopWatch.stop();// stopWatch.start ("财务数据汇总");logger.info("============开始调用数据汇总接口============");
// 记录数据汇总的情况Boolean dataSummaryApiRequestStatus = false;
// 设置数据汇总接口的请求头HttpHeaders dataSummaryApiHeaders = new HttpHeaders();
dataSummaryApiHeaders.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
// 设置请求参数MultiValueMap<String, Object> dataSummaryApiParam = new LinkedMultiValueMap<>(15);
dataSummaryApiParam.add("distNo", distNo);
dataSummaryApiParam.add("years", years);
dataSummaryApiParam.add("months", beginMonths);
dataSummaryApiParam.add("selTblInfoIndex", "0");
dataSummaryApiParam.add("hzType", hzType);
// 用 HttpEntity 封装整个请求报文HttpEntity<MultiValueMap<String, Object>> dataSummaryApiHttpEntity
= new HttpEntity<>(dataSummaryApiParam, dataSummaryApiHeaders);
// 数据汇总的结果logger.info("============开始执行财务数据汇总============");
ResponseEntity<String> dataSummaryApiResult =
restTemplate.exchange(dataSummaryUrl, HttpMethod.POST, dataSummaryApiHttpEntity, String.class);
HashMap<String, Object> dataSummaryResultJson = null;
// 请求成功 获取返回的数据if(dataSummaryApiResult.getStatusCode() == HttpStatus.OK)
{if(StrUtil.isNotBlank(dataSummaryApiResult.getBody()))
{dataSummaryResultJson = JSONUtil.toBean(dataSummaryApiResult.getBody(), HashMap.class);
}}else{logger.error("-------------调用接口:[{}] 失败!!", dataSummaryUrl);
return false;
}// 对返回结果进行校验if(CollUtil.isNotEmpty(dataSummaryResultJson))
{if((boolean)dataSummaryResultJson.get("rvalue"))
{dataSummaryApiRequestStatus = true;
}}// 如果汇总数据的接口失败了 就不在继续执行if(!dataSummaryApiRequestStatus)
{logger.error("-------------汇总数据执行错误");
return false;
}else{logger.info("============汇总数据成功!!!!!!!");
}// stopWatch.stop();return true;
}};
try{return executeWithRetry(callable, retryPolicy);
}catch (Exception e)
{logger.error("---------执行数据转入和汇总出错-----------");
logger.error("---------> 错误信息:{}", e.getStackTrace());
return false;
}}/*** 带返回值的重试方法
*/
public static <T> T executeWithRetry(Callable<T> callable, RetryPolicy retryPolicy) throws Exception
{return executeWithRetry(callable, null, null, retryPolicy);
}/*** 带重试和延时的操作执行
*
* @param callable 执行的操作
* @param retryPolicy 重试策略
* @return 返回值
* @throws Exception 业务异常或者超过最大重试次数后的最后一次尝试抛出的异常
*/
private static <T> T executeWithRetry(Callable<T> callable, Consumer<T> consumer, T data, RetryPolicy retryPolicy) throws Exception {
// 最大重试次数Integer maxRetries = retryPolicy.getMaxRetries();
if (maxRetries != null && maxRetries < 0)
{throw new IllegalArgumentException("最大重试次数不能为负数");
}int retryCount = 0;
Duration delayDuration = retryPolicy.getDelayDuration();
while (true)
{try{// 不带返回值的if (consumer != null)
{consumer.accept(data);
return null;
}// 带返回值的if (callable != null)
{T result = callable.call();
// 不设置终止条件或者设置了且满足则返回,否则还会重试List<Predicate> abortConditions = retryPolicy.getAbortConditions();
/* ---------------- 不需要重试的返回值 -------------- */if (isInCondition(result, abortConditions))
{return result;
}/* ---------------- 需要重试的返回值 -------------- */boolean hasNextRetry = hasNextRetryAfterOperation(++retryCount, maxRetries, delayDuration);
if (!hasNextRetry)
{return result;
}}}catch (Exception e)
{/* ---------------- 不需要重试的异常 -------------- */List<Class<? extends Exception>> abortExceptions = retryPolicy.getAbortExceptions();
if (isInExceptions(e, abortExceptions))
{throw e;
}logger.error("--------> 执行出错信息:{}", e.getMessage());
logger.error("--------判断是否重试--------");
/* ---------------- 需要重试的异常 -------------- */boolean hasNextRetry = hasNextRetryAfterOperation(++retryCount, maxRetries, delayDuration);
if (!hasNextRetry)
{throw e;
}}}}/*** 判断运行之后是否还有下一次重试
*/
private static boolean hasNextRetryAfterOperation(int retryCount, Integer maxRetries, Duration delayDuration) throws InterruptedException
{// 有限次重试if (maxRetries != null)
{if (retryCount > maxRetries)
{return false;
}}// 延时if (delayDuration != null && !delayDuration.isNegative())
{logger.info("延时{}毫秒", delayDuration.toMillis());
Thread.sleep(delayDuration.toMillis());
}logger.info("第{}次重试", retryCount);
return true;
}/*** 是否在异常列表中
*/
private static boolean isInExceptions(Exception e, List<Class<? extends Exception>> abortExceptions) {
if (CollectionUtils.isEmpty(abortExceptions))
{return false;
}for (Class<? extends Exception> clazz : abortExceptions)
{if (clazz.isAssignableFrom(e.getClass()))
{return true;
}}return false;
}/*** 是否符合不需要终止的条件
*/
private static <T> boolean isInCondition(T result, List<Predicate> abortConditions)
{if (CollectionUtils.isEmpty(abortConditions))
{return true;
}for (Predicate predicate : abortConditions)
{if (predicate.test(result))
{return true;
}}return false;
}
方法中任务重试的功能实现:
/** | |
* 执行 | |
* @param restTemplate 已经有登陆成功用户的状态信息 | |
* @return 返回结果 true-> 成功 false-> 失败 | |
*/ | |
public Boolean execDataInputAndSummary(RestTemplate restTemplate) | |
{ | |
RetryPolicy retryPolicy = RetryPolicy.builder() | |
// 重试次数 | |
.maxRetries(retries) | |
// 重试延迟执行时间 | |
.delayDuration(delayDuration, TimeUnit.SECONDS) | |
.build(); | |
// 需要执行的任务 | |
Callable<Boolean> callable = new Callable<Boolean>() | |
{ | |
@Override | |
public Boolean call() | |
{ | |
logger.info("============执行的任务============"); | |
return true; | |
} | |
}; | |
try | |
{ | |
return executeWithRetry(callable, retryPolicy); | |
} | |
catch (Exception e) | |
{ | |
logger.error("---------执行出错-----------"); | |
logger.error("---------> 错误信息:{}", e.getStackTrace()); | |
return false; | |
} | |
} | |
/** | |
* 带返回值的重试方法 | |
*/ | |
public static <T> T executeWithRetry(Callable<T> callable, RetryPolicy retryPolicy) throws Exception | |
{ | |
return executeWithRetry(callable, null, null, retryPolicy); | |
} | |
/** | |
* 带重试和延时的操作执行 | |
* | |
* @param callable 执行的操作 | |
* @param retryPolicy 重试策略 | |
* @return 返回值 | |
* @throws Exception 业务异常或者超过最大重试次数后的最后一次尝试抛出的异常 | |
*/ | |
private static <T> T executeWithRetry(Callable<T> callable, Consumer<T> consumer, T data, RetryPolicy retryPolicy) throws Exception { | |
// 最大重试次数 | |
Integer maxRetries = retryPolicy.getMaxRetries(); | |
if (maxRetries != null && maxRetries < 0) | |
{ | |
throw new IllegalArgumentException("最大重试次数不能为负数"); | |
} | |
int retryCount = 0; | |
Duration delayDuration = retryPolicy.getDelayDuration(); | |
while (true) | |
{ | |
try | |
{ | |
// 不带返回值的 | |
if (consumer != null) | |
{ | |
consumer.accept(data); | |
return null; | |
} | |
// 带返回值的 | |
if (callable != null) | |
{ | |
T result = callable.call(); | |
// 不设置终止条件或者设置了且满足则返回,否则还会重试 | |
List<Predicate> abortConditions = retryPolicy.getAbortConditions(); | |
/* ---------------- 不需要重试的返回值 -------------- */ | |
if (isInCondition(result, abortConditions)) | |
{ | |
return result; | |
} | |
/* ---------------- 需要重试的返回值 -------------- */ | |
boolean hasNextRetry = hasNextRetryAfterOperation(++retryCount, maxRetries, delayDuration); | |
if (!hasNextRetry) | |
{ | |
return result; | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
/* ---------------- 不需要重试的异常 -------------- */ | |
List<Class<? extends Exception>> abortExceptions = retryPolicy.getAbortExceptions(); | |
if (isInExceptions(e, abortExceptions)) | |
{ | |
throw e; | |
} | |
logger.error("--------> 执行出错信息:{}", e.getMessage()); | |
logger.error("--------判断是否重试--------"); | |
/* ---------------- 需要重试的异常 -------------- */ | |
boolean hasNextRetry = hasNextRetryAfterOperation(++retryCount, maxRetries, delayDuration); | |
if (!hasNextRetry) | |
{ | |
throw e; | |
} | |
} | |
} | |
} | |
/** | |
* 判断运行之后是否还有下一次重试 | |
*/ | |
private static boolean hasNextRetryAfterOperation(int retryCount, Integer maxRetries, Duration delayDuration) throws InterruptedException | |
{ | |
// 有限次重试 | |
if (maxRetries != null) | |
{ | |
if (retryCount > maxRetries) | |
{ | |
return false; | |
} | |
} | |
// 延时 | |
if (delayDuration != null && !delayDuration.isNegative()) | |
{ | |
logger.info("延时{}毫秒", delayDuration.toMillis()); | |
Thread.sleep(delayDuration.toMillis()); | |
} | |
logger.info("第{}次重试", retryCount); | |
return true; | |
} | |
/** | |
* 是否在异常列表中 | |
*/ | |
private static boolean isInExceptions(Exception e, List<Class<? extends Exception>> abortExceptions) { | |
if (CollectionUtils.isEmpty(abortExceptions)) | |
{ | |
return false; | |
} | |
for (Class<? extends Exception> clazz : abortExceptions) | |
{ | |
if (clazz.isAssignableFrom(e.getClass())) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* 是否符合不需要终止的条件 | |
*/ | |
private static <T> boolean isInCondition(T result, List<Predicate> abortConditions) | |
{ | |
if (CollectionUtils.isEmpty(abortConditions)) | |
{ | |
return true; | |
} | |
for (Predicate predicate : abortConditions) | |
{ | |
if (predicate.test(result)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} |
# 10、jar 表在不改源码的情况下使用外部 jar 包
# 问题描述:
jar 表在不改源码的情况下使用外部 jar 包,或其他版本的依赖
# 影响:
系统不兼容或 jdk 版本不同可能导致缺少依赖
# 解决方案:
修改源码或者更换 jdk 版本
当打包的 java jar 包需要更换依赖或者依赖其他 jar 包可以用下面的方法
![]()

然后执行命令:
java -Djava.ext.dirs=D:\xxx\lib -jar map-0.0.1-SNAPSHOT.jar
# 11、使用 spoon 遇到的类型转换问题
# 问题描述:
使用 spoon 遇到的问题 需要输入的数据为时间类型 试了很多组件都不行
# 影响:
导致输出的类型不是字符串 插入失败
# 解决方案:
- 使用 tochar () 函数 直接将输入的类型转成字符串
# 12、dockerhub 国内镜像站集体下线?解决方法
# 问题描述:
今天重新部署项目发现 eclipse-temurin:8-jre 拉取超时了,问了同事也是一样拉去不了,去了解了一下发现国内镜像都下线不可用了,找了半天也是找到了解决方法,感谢网络好心人。
# 影响:
镜像无法拉取
# 解决方案:
通过 docker 配置文件配置可用的国内镜像源(不好使,很多不能用的,我用阿里云的镜像加速器也不行)
设置代理 (这个要科学上网、得会配置代理,麻烦,没有尝试)
自建镜像仓库 (推荐这种,简单。在 github 上有现成的开源的) 地址感谢大佬
登录阿里云,找到容器镜像服务,创建一个个人版实例。(第一次使用的话,会让设置访问密码。记住,后面会用)
创建实例成功后找到仓库管理 - 命名空间,新建一个命名空间且设置为公开或私有(私有在拉取或推送需要密码验证)
![]()
点击访问凭证获取凭证信息 第一个是用户名, 第二是仓库地址
sudo docker login --username=阿里云用户名 registry.cn-beijing.aliyuncs.com
![]()
准备工作完成,配置 github,fork 项目,地址: docker_image_pusher
在 fork 后的项目中通过 Settings -> Secret and variables -> Actions -> New Repository secret 路径,配置 4 个环境变量
ALIYUN_NAME_SPACE - 命名空间
ALIYUN_REGISTRY_USER - 阿里云用户名
ALIYUN_REGISTRY_PASSWORD - 访问密码
ALIYUN_REGISTRY - 仓库地址
![]()
配置要拉取的镜像 打开项目 images.txt,每一行配置一个镜像,格式:name:tag 比如我要拉取
eclipse-temurin:8-jre配置如下: (注意:需要开启 Actions)功能![]()
提交修改的文件,则会自动在
Actions中创建一个 workflow。等待片刻即可。 回到阿里云容器镜像服务查看是否拉取成功![]()
最后尝试拉取到其他服务器:
[root@xxx ~]# docker pull registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
8-jre: Pulling from tzting/eclipse-temurin
eb993dcd6942: Pull complete
62ad162d7203: Pull complete
b7a2406f4d02: Pull complete
a7b7a8ecda96: Pull complete
8f36a51d249a: Pull complete
Digest: sha256:087c52e8bb5146630f2e14d650c125155449a4aadf0dc39db64632e488aeb924
Status: Downloaded newer image for registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
可以看到已经能拉取到自己本地啦, 再次感谢大佬!
# 13、jstl 和 jsp-api 兼容问题
# 问题描述:
今天在搭建旧项目(不是用 maven 管理的)的时候项目正常启动了,访问 jsp 页面报错: HTTP Status 500 - java.lang.ClassNotFoundException: org.apache.jsp.index_jsp
# 影响:
项目无法正常访问。
# 解决方案:
使用的包的版本:
jsp-api :2.0
jstl:1.1.2
通过在 tomcat 的日志文件中 localhost.log 文件中找到相关的错误信息:
严重: Servlet.service () for servlet jsp threw exception
org.apache.jasper.JasperException: Unable to compile class for JSP:
An error occurred at line: [35] in the generated java file: [/home/software/java/xz_dm/apache-tomcat-7.0.81/work/Catalina/localhost/cwjk/org/apache/jsp/commons/error_jsp.java]
The method getJspApplicationContext(ServletContext) is undefined for the type JspFactory
Stacktrace:
at org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:103)
原因是:在 jsp-api 中没有找到 getJspApplicationContext(ServletContext) 这个方法,又因为该文件是通过 jstl 将 jsp 文件转成 java 文件的,所以猜测 jsp-api 版本和 jstl 版本不对应
最后在网上找到类似的问题:
JSP API 2.0 不包含该方法: getJspApplicationContext(ServletContext) 方法是在 JSP 2.1 规范中引入的。如果您的项目依赖于 JSP API 2.0,那么将无法使用这个方法。
所以升级了 JSP-API 到 2.1 版本 成功解决!
# 14、RSA 加密报错:数据长度不得超过 53 字节
# 问题描述:
在做接口数据加密时,使用了 RSA 非对称加密报错:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes
# 影响:
当需要加密的内容变多就会报错,导致无法正常加密。
# 解决方案:
有两个解决方案:
两种加密方法结合
RSA+AES 的方式
- 首先生成长度不大于 53 字节的 AES 密钥
- 通过 AES 密钥加密文本内容
- 通过 RSA 加密 AES 的密钥
RSA 切片加密
可以将待加密的内容在指定大小内容进行切片加密
- 循环文本内容到加密的大小,每次加密完一批加一个自定义特殊符号来区分是一批(后面方便解密)
- 解密的时候按照自定义的特殊字符分割来解密
具体代码:
/*** 公钥加密
*
* @param encryptingStr
* @return
*/
public static String encryptByPublic(String encryptingStr, String publicKeyStr) {
StringBuilder encryptedDataBuilder = new StringBuilder();
try {
// Base64 解码公钥字符串byte[] publicKeyBytes = decryptBase64(publicKeyStr);
// 构造 X509EncodedKeySpec 对象X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
// 初始化密钥工厂KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
// 生成公钥对象PublicKey publicKey = keyFactory.generatePublic(keySpec);
// 确保公钥是 RSA 公钥if (publicKey instanceof RSAPublicKey) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
// 获取密钥长度(以字节为单位)int keyLength = rsaPublicKey.getModulus().bitLength() / 8;
// 计算最大加密数据长度(适用于 PKCS#1 v1.5)// 11 字节用于填充int maxDataLength = keyLength - 11;
// 待加密数据byte[] data = encryptingStr.getBytes(StandardCharsets.UTF_8);
// 分割数据并逐块加密for (int i = 0; i < data.length; i += maxDataLength) {
int chunkSize = Math.min(maxDataLength, data.length - i);
byte[] dataChunk = Arrays.copyOfRange(data, i, i + chunkSize);
// 初始化 Cipher 对象,使用 PKCS#1 填充Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 设置加密模式cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 加密数据块byte[] encryptedDataChunk = cipher.doFinal(dataChunk);
// 将加密块的 Base64 编码添加到构建器中if (encryptedDataBuilder.length() > 0) {
// 添加分隔符encryptedDataBuilder.append(",");
}encryptedDataBuilder.append(encryptBase64(encryptedDataChunk));
}return encryptedDataBuilder.toString();
} else {
throw new InvalidKeyException("Provided key is not a RSA Public Key.");
}} catch (Exception e) {
e.printStackTrace();
// 如果发生异常,返回 null 或错误信息return null;
}}/*** 私钥解密
* @param encryptedDataList
* @param privateKeyStr
* @return
*/
public static String decryptByPrivate(String encryptedDataList, String privateKeyStr) {
StringBuilder decryptedDataBuilder = new StringBuilder();
try {
// 根据分隔符分割加密数据块String[] encryptedDataChunks = encryptedDataList.split(",");
for (String encryptedDataChunk : encryptedDataChunks) {
// Base64 解码加密的数据块byte[] encryptedDataBytes = decryptBase64(encryptedDataChunk);
// 其余解密步骤与前面的 decryptByPrivate 方法相同// Base64 解码私钥字符串byte[] privateKeyBytes = decryptBase64(privateKeyStr);
// 构造 PKCS8EncodedKeySpec 对象PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// 初始化密钥工厂KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
// 生成私钥对象PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 初始化 Cipher 对象,使用 PKCS#1 填充Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 设置解密模式cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 解密数据块byte[] decryptedDataChunk = cipher.doFinal(encryptedDataBytes);
// 将解密数据块转换为字符串,并添加到构建器中String decryptedDataChunkStr = new String(decryptedDataChunk, StandardCharsets.UTF_8);
if (decryptedDataBuilder.length() > 0) {
// 如果不是第一个块,则不添加分隔符decryptedDataBuilder.append("");
}decryptedDataBuilder.append(decryptedDataChunkStr);
}return decryptedDataBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
// 如果发生异常,返回 null 或错误信息return null;
}}
# 15、如何提高 cpu 和内存的占用率?
# 问题描述:
有的时候第三方提供的服务器分配的内存或者 cpu 太高,闲置率太高就会被回收,为了防止被回收,需要认为的提供高占用率
# 影响:
服务器资源被回收,下次需要的时候重新申请麻烦
# 解决方案:
通过写脚本来执行提供高占用率
具体脚本代码:
#!/bin/bash | |
ZB=8 | |
printZB=$((ZB * 10)) | |
echo " !!!!!!!!!!!!!!提示开始!!!!!!!!!!!!!!! | |
如果当前目录下有【cpu_pids】文件 代表cpu占用程序启动成功!!! | |
如果当前目录下有【mem_file_pid】文件 代表内存占用(文件的方式,不会使用实际内存,通过top命令可以看到占用,free命令没有显示被使用)程序启动成功!!! | |
如果当前目录下有【mem_pid】文件 代表内存占用(普通方式,会实际使用内存用free命令可以查看的到use被使用)程序启动成功!!! | |
!!!!!!!!!!!!!!提示结束!!!!!!!!!!!!!!! | |
" | |
# 检查命令行参数是否为数字 | |
is_number() { | |
if ! [[ "$1" =~ ^[0-9]+$ ]]; then | |
echo "错误:'$1' 不是一个有效的数字。" | |
tips | |
exit 1 | |
elif [ "$1" -gt 9 ]; then | |
echo "错误:'$1' 必须小于等于9。" | |
tips | |
exit 1 | |
fi | |
} | |
tips() { | |
echo "" | |
echo "警告!!!......提示, 请使用以下命令: sh cpu_memory_auto.sh [start_cpu|stop_cpu|start_mem|stop_mem|start_all|stop_all|start_mem_file|stop_mem_file]. | |
例子: | |
sh cpu_memory_auto.sh start_all 8 启动cpu占用和内存占用80%、 sh cpu_memory_auto.sh start_cpu 7 只启动cpu占用70%、 sh cpu_memory_auto.sh start_mem 9 只启动内存占用90%、 sh cpu_memory_auto.sh start_mem 9 只启动内存占用 90% (文件占用方式,比较快) | |
sh cpu_memory_auto.sh start_all 启动cpu占用和内存占用、 sh cpu_memory_auto.sh start_cpu 只启动cpu占用、 sh cpu_memory_auto.sh start_mem 只启动内存占用、 sh cpu_memory_auto.sh start_mem_file 只启动内存占用(文件占用方式,比较快) | |
sh cpu_memory_auto.sh stop_all 关闭cpu占用和内存占用、 sh cpu_memory_auto.sh stop_cpu 只关闭cpu占用、 sh cpu_memory_auto.sh stop_mem 只关闭内存占用、 sh cpu_memory_auto.sh stop_mem_file 只关闭内存占用(文件占用方式,比较快) | |
注意!!!!:使用start_all 启动的内存占用是非文件占用的方式!!!! | |
" | |
echo "" | |
exit 1 | |
} | |
#文件占用内存的方式 需要保证当前脚本所在目录空间足够 - begin | |
mem_file() { | |
TEMP_LOAD_MEM="$(cd "$(dirname "$0")";pwd)/temp_load_mem" | |
# 获取系统的总内存(单位:kB) | |
total_mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}') | |
# 将总内存从kB转换为MB | |
total_mem_mb=$(($total_mem_kb / 1024)) | |
# 使用提供的ZB值或默认值 | |
JSZB=${1:-$ZB} | |
# 计算需要占用的内存大小(单位:MB) | |
target_mem_mb=$(echo "$total_mem_mb * $JSZB / 10" | bc) | |
# 计算需要占用的内存块大小(单位:kB) | |
target_mem_kb=$(echo "$target_mem_mb * 1024" | bc) | |
# 将变量设置为一个大的文件以增加内存使用率 | |
echo "正在分配 $(echo "$target_mem_mb" | bc) MB 的内存..." | |
mem_file="$TEMP_LOAD_MEM" # 创建一个临时文件,文件名包含进程ID以确保唯一性 | |
# 使用dd命令从/dev/zero创建一个大文件 | |
dd if=/dev/zero of="$mem_file" bs=1K count="$target_mem_kb" | |
# 保持脚本运行,直至手动终止 | |
echo "内存使用率已增加。按 Ctrl+C 退出。" | |
while true; do | |
sleep 1 | |
done | |
} | |
start_mem_file() { | |
MEM_FILE_PID="$(cd "$(dirname "$0")";pwd)/mem_file_pid" | |
if [ -f $MEM_FILE_PID ];then | |
echo "内存占用的pid $pid 存在 $MEM_FILE_PID, 请先停止 !!!" | |
else | |
# 清空或创建PID文件 | |
> "$MEM_FILE_PID" | |
fi | |
pid=$(cat ${MEM_FILE_PID}) | |
if [ -z $pid ]; then | |
echo "............启动的内存占用比:${1:-$ZB}....................." | |
mem_file ${1:-$ZB} >/dev/null 2>&1 & | |
echo $! >${MEM_FILE_PID} | |
echo ".............启动内存占用成功!! pid 保存在 $MEM_FILE_PID............." | |
else | |
echo "内存占用的pid $pid 存在 ${MEM_FILE_PID}, 请先停止 !!!" | |
fi | |
} | |
stop_mem_file() { | |
MEM_FILE_PID="$(cd "$(dirname "$0")";pwd)/mem_file_pid" | |
# 检查PID文件是否存在 | |
if [ ! -f "$MEM_FILE_PID" ]; then | |
echo "内存占用的 pid 文件不存在 ${MEM_FILE_PID},无法处理停止命令。" | |
exit 1 | |
fi | |
pid=$(cat ${MEM_FILE_PID}) | |
TEMP_LOAD_MEM="$(cd "$(dirname "$0")";pwd)/temp_load_mem" | |
if [ -z $pid ]; then | |
echo "内存占用的 pid 文件不存在 ${MEM_FILE_PID}" | |
else | |
kill -9 $pid | |
# 确保在脚本结束时删除临时文件 | |
sleep 1 | |
# 删除PID文件 | |
rm -f "$MEM_FILE_PID" | |
# 删除内存占用的文件 | |
rm -f "$TEMP_LOAD_MEM" | |
echo ".............停止内存占用成功!! 内存占用的文件:$TEMP_LOAD_MEM 已清除, 对应 PID: $pid 已结束, pid文件 $MEM_FILE_PID 已清除 ............." | |
fi | |
} | |
#文件占用内存的方式 需要保证当前脚本所在目录空间足够 - end | |
mem() { | |
# 设置要分配的内存大小(单位:MB) | |
MEMORY_SIZE=4096 # 例如:分配1024MB(1GB) | |
JSZB=${1:-$ZB} | |
# 将变量设置为一个大的数组以增加内存使用率 | |
echo "正在分配 $MEMORY_SIZE MB 的内存..." | |
arr=() | |
# 在数组中填充数据 | |
for ((i=0; i<(MEMORY_SIZE*1024*1024/JSZB); i++)); do | |
arr[i]=0 | |
done | |
# 保持脚本运行,直至手动终止 | |
echo "内存使用率已增加。按 Ctrl+C 退出。" | |
while true; do | |
sleep 1 | |
done | |
} | |
start_mem() { | |
MEM_PID="$(cd "$(dirname "$0")";pwd)/mem_pid" | |
if [ -f $MEM_PID ];then | |
echo "内存占用的pid $pid 存在 $MEM_PID, 请先停止 !!!" | |
else | |
# 清空或创建PID文件 | |
> "$MEM_PID" | |
fi | |
pid=$(cat ${MEM_PID}) | |
if [ -z $pid ]; then | |
echo "............启动的内存占用比:${1:-$ZB}....................." | |
mem ${1:-$ZB} >/dev/null 2>&1 & | |
echo $! >${MEM_PID} | |
echo "..................................启动内存占用成功!! pid 保存在 $MEM_PID.........................." | |
else | |
echo "内存占用的pid $pid 存在 $MEM_PID, 请先停止 !!!" | |
fi | |
} | |
stop_mem() { | |
MEM_PID="$(cd "$(dirname "$0")";pwd)/mem_pid" | |
# 检查PID文件是否存在 | |
if [ ! -f "$MEM_PID" ]; then | |
echo "内存占用的 pid 文件不存在 ${MEM_PID},无法处理停止命令。" | |
exit 1 | |
fi | |
pid=$(cat ${MEM_PID}) | |
if [ -z $pid ]; then | |
echo "内存占用的 pid 文件不存在 ${MEM_PID}" | |
else | |
kill -9 $pid | |
sleep 1 | |
echo ".............停止内存占用成功!! 对应的PID:$pid 已清除,并且文件:$MEM_PID 已删除............." | |
# 删除PID文件 | |
rm -f "$MEM_PID" | |
fi | |
} | |
#启动cpu占用 | |
start_cpu() { | |
# PID 文件路径 | |
PID_FILE_CPU="$(cd "$(dirname "$0")";pwd)/cpu_pids" | |
if [ -f $PID_FILE_CPU ];then | |
echo "cpu占用已启动,pid列表保存在文件 ${PID_FILE_CPU}, 请先停止 !!!" | |
else | |
# 获取CPU核心数 | |
cpu_cores=$(grep -c ^processor /proc/cpuinfo) | |
echo "原始核心:$cpu_cores" | |
target_cores=$(echo "$cpu_cores * ${1:-$ZB} / 10.0" | bc) | |
echo "原始核心数:$cpu_cores , 实际启动核心数:$target_cores" | |
# 清空或创建PID文件 | |
> "$PID_FILE_CPU" | |
# 使用taskset在不同的核心上运行yes命令,并记录每个yes命令的PID | |
for ((i=0; i<target_cores; i++)); do | |
# 计算每个yes命令应该运行的核心 | |
core=$((i % cpu_cores)) | |
taskset -c $core yes > /dev/null 2>&1 & | |
# 将每个yes命令的PID追加到PID文件 | |
echo $! >> "$PID_FILE_CPU" | |
done | |
echo ".............开启CPU占用,已在 $cpu_cores 个核心上启动了 $target_cores 个 'yes' 命令实例。 pid列表保存在:$PID_FILE_CPU ............." | |
fi | |
} | |
#关闭cpu占用 | |
stop_cpu() { | |
# PID 文件路径 | |
PID_FILE_CPU="$(cd "$(dirname "$0")";pwd)/cpu_pids" | |
# 检查PID文件是否存在 | |
if [ ! -f "$PID_FILE_CPU" ]; then | |
echo "Yes进程的PID文件不存在,无法处理停止命令。" | |
exit 1 | |
fi | |
# 读取PID文件中的每个PID并杀死对应的进程 | |
while read -r pid; do | |
kill "$pid" 2>/dev/null | |
done < "$PID_FILE_CPU" | |
# 删除PID文件 | |
rm -f "$PID_FILE_CPU" | |
echo "............关闭cpu占用,所有 'yes' 进程已被停止,并且它们的 PID 已被清除。文件已删除:$PID_FILE_CPU............." | |
} | |
case "$1" in | |
"start_cpu") | |
if [ -z "$2" ]; then | |
start_cpu $ZB | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
if is_number $2; then | |
start_cpu $2 | |
printZB=$(($2 * 10)) | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
echo "请提供一个数字参数作为ZB值。" | |
tips | |
return | |
fi | |
fi | |
;; | |
"stop_cpu") | |
stop_cpu | |
;; | |
"start_mem") | |
if [ -z "$2" ]; then | |
start_mem $ZB | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
if is_number $2; then | |
start_mem $2 | |
printZB=$(($2 * 10)) | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
echo "请提供一个数字参数作为ZB值。" | |
tips | |
return | |
fi | |
fi | |
;; | |
"stop_mem") | |
stop_mem | |
;; | |
"start_all") | |
if [ -z "$2" ]; then | |
start_mem $ZB | |
start_cpu $ZB | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
if is_number $2; then | |
start_mem $2 | |
start_cpu $2 | |
printZB=$(($2 * 10)) | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
echo "请提供一个数字参数作为ZB值。" | |
tips | |
return | |
fi | |
fi | |
;; | |
"stop_all") | |
stop_cpu | |
stop_mem | |
;; | |
"start_mem_file") | |
if [ -z "$2" ]; then | |
start_mem_file $ZB | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
if is_number $2; then | |
start_mem_file $2 | |
printZB=$(($2 * 10)) | |
echo "--------------当前启动的cpu和内存占比为 $printZB-----------" | |
else | |
echo "请提供一个数字参数作为ZB值。" | |
tips | |
return | |
fi | |
fi | |
;; | |
"stop_mem_file") | |
stop_mem_file | |
;; | |
"status") | |
status | |
;; | |
*) | |
tips | |
;; | |
esac |
效果:
#执行前的情况: | |
[root@localhost tool]# top -H | |
top - 11:42:39 up 3 days, 15:06, 3 users, load average: 0.02, 0.04, 0.05 | |
Threads: 491 total, 1 running, 490 sleeping, 0 stopped, 0 zombie | |
%Cpu(s): 0.4 us, 0.7 sy, 0.0 ni, 98.8 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st | |
KiB Mem : 49036764 total, 38290956 free, 5731004 used, 5014804 buff/cache | |
KiB Swap: 8126460 total, 8106124 free, 20336 used. 42577872 avail Mem | |
#可以看到 cpu 信息 id 这一列是 98.8,表示闲置率高达 98.8% | |
#可以看到内存信息 38290956 free,表示内存空闲 36 个 g 左右 | |
##cpu 占用 | |
#下面执行脚本 | |
[root@localhost tool]# sh cpu_memory_auto.sh start_cpu | |
#在看一下情况 | |
[root@localhost tool]# top -H | |
top - 11:46:18 up 3 days, 15:10, 3 users, load average: 2.33, 0.68, 0.27 | |
Threads: 495 total, 6 running, 489 sleeping, 0 stopped, 0 zombie | |
%Cpu(s): 82.6 us, 1.3 sy, 0.0 ni, 15.9 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st | |
KiB Mem : 49036764 total, 38291144 free, 5730632 used, 5014988 buff/cache | |
KiB Swap: 8126460 total, 8106124 free, 20336 used. 42578120 avail Mem | |
#可以看到 cpu 信息 82.6 us:使用 82.6%, 15.9 id:空闲 15.9% | |
## 内存占用 | |
[root@localhost tool]# sh cpu_memory_auto.sh start_mem | |
#在看一下情况 | |
[root@localhost tool]# top -H | |
top - 11:51:55 up 3 days, 15:15, 3 users, load average: 7.64, 4.95, 2.25 | |
Threads: 502 total, 7 running, 495 sleeping, 0 stopped, 0 zombie | |
%Cpu(s): 83.9 us, 11.0 sy, 0.0 ni, 0.0 id, 5.0 wa, 0.0 hi, 0.1 si, 0.0 st | |
KiB Mem : 49036764 total, 275632 free, 5729324 used, 43031808 buff/cache | |
KiB Swap: 8126460 total, 8102272 free, 24188 used. 42576596 avail Mem | |
#可以看到内存信息 275632 free:空闲只有 200M 左右了 |
# 16、MyBaits 参数绑定异常?
# 问题描述:
项目整合 mybatis 出现参数绑定异常:
rg.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error invoking SqlProvider method 'public java.lang.String com.hxj.study.sql.SqlProvider.selectSql(java.lang.String,java.util.Map)' with specify parameter 'class org.apache.ibatis.binding.MapperMethod$ParamMap'. Cause: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [map, param1, tableName, param2]
我定义的方法:
@SelectProvider(type = SqlProvider.class, method = "selectSql") | |
List<Map<String, Object>> selectData(String tableName, @Param("map") Map<String, Object> map); |
实际 mybatis 识别到的参数名是:arg0, arg1
# 影响:
无法正常做 crud 操作
# 解决方案:
通过一步一步调试看源代码发现底层获取不到实际的参数名称,可能是编译后的 class 文件问题。
最后排查到是我的 springboot 项目是依赖 springboot 的 maven 文件 而不是继承,因为 springboot 文件对编译的 class 文件做了处理,如下:
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<configuration> | |
<source>8</source> | |
<target>8</target> | |
<parameters>true</parameters> | |
</configuration> | |
</plugin> |
主要是: <parameters>true</parameters>
在 Java 8 及以上版本中,可以通过在编译时添加 -parameters 选项来保留方法参数的名称。在 Maven 中,通过在 maven-compiler-plugin 的配置中设置 <parameters>true</parameters> 来开启这个功能。
具体来说,当开启这个配置后,编译后的字节码文件中会包含方法参数的名称信息。这在一些场景下非常有用,例如:
反射调用时获取参数名称:在使用反射调用方法时,可以通过 Method.getParameterNames () 方法获取到方法参数的名称。这对于一些基于反射的框架(如 Spring Data JPA 的查询方法解析等)非常有帮助,能够更清晰地知道方法参数的含义,从而更好地进行动态调用或者生成相关的逻辑代码。
调试和代码分析工具:一些调试工具和代码分析工具可以利用这些参数名称信息,更准确地展示方法调用的上下文,方便开发者进行调试和理解代码的逻辑。
如果没有设置 <parameters>true</parameters>,那么编译后的字节码文件中默认不会包含方法参数的名称信息,这可能会给上述场景带来一些不便。
如果是继承的方式如下, 则不需要配置:
<parent> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-parent</artifactId> | |
<version>2.5.2</version> | |
<relativePath/> <!-- lookup parent from repository --> | |
</parent> |
如果是依赖的方式如下,则需要配置:
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter</artifactId> | |
<version>2.7.17</version> | |
</dependency> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<configuration> | |
<source>8</source> | |
<target>8</target> | |
<parameters>true</parameters> | |
</configuration> | |
</plugin> |
# 17、如何定时启停项目?
# 问题描述:
在 linux 环境下,拿 nginx 来说,我需要在某个时间点,停止或关闭 nginx,该如何做?
使用
cron定时任务推荐用这种,因为 linux 系统基本都有这个命令,不用额外安装什么其他依赖。
使用
at命令at命令用于在指定时间执行一次任务。虽然at命令不如cron常用,但它非常适合单次任务。但是需要安装at命令
# 影响:
由于新年期间需要停止项目,过完年重新启动,可能会有不及时的情况,肯定不能手动踩点去启停。
# 解决方案:
用 linux 只带的定时任务,让服务器自己启停我们的项目,下面是用 cron 定时任务的做法:
检查系统是否启动了定时任务
sudo systemctl status cron # Debian/Ubuntu
sudo systemctl status crond # CentOS
#结果如下:[root@localhost xxx]# sudo systemctl status crond
● crond.service - Command Scheduler
Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled)
Active: active (running) since 六 2024-11-23 20:37:05 CST; 2 months 0 days ago
Main PID: 784 (crond)
Tasks: 1Memory: 1.9MCGroup: /system.slice/crond.service
└─784 /usr/sbin/crond -n11月 23 20:37:05 localhost.localdomain systemd[1]: Started Command Scheduler.
11月 23 20:37:05 localhost.localdomain crond[784]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 29% if used.)
#出现 Active: active (running) 代表启用如果未启用者执行下面的命令启用
sudo systemctl enable cron # Debian/Ubuntu
sudo systemctl start cron # Debian/Ubuntu
sudo systemctl enable crond # CentOS
sudo systemctl start crond # CentOS
添加启动或停止程序的脚本,这里用 nginx 演示
启动 nginx 的脚本:
#!/bin/bash# /data/zfj/start_server.sh# 设置日志文件路径LOG_FILE="/data/zfj/start_server.log"
# 记录脚本开始执行的时间echo "Script started at $(date)" >> "$LOG_FILE"
# 启动 Nginxif /home/software/nginx/nginx-1.25.5/sbin/nginx; then
echo "Nginx Started successfully at $(date)" >> "$LOG_FILE"
elseecho "Failed to start Nginx at $(date)" >> "$LOG_FILE"
fi# 记录脚本结束执行的时间echo "Script ended at $(date)" >> "$LOG_FILE"
关闭 nginx 的脚本:
#!/bin/bash# /data/zfj/stop_server.sh# 设置日志文件路径LOG_FILE="/data/zfj/stop_server.log"
# 记录脚本开始执行的时间echo "Script started at $(date)" >> "$LOG_FILE"
# 关闭 Nginxif /home/software/nginx/nginx-1.25.5/sbin/nginx -s stop; then
echo "Nginx stopped successfully at $(date)" >> "$LOG_FILE"
elseecho "Failed to stop Nginx at $(date)" >> "$LOG_FILE"
fi# 记录脚本结束执行的时间echo "Script ended at $(date)" >> "$LOG_FILE"
配置定时任务 crontab -e 命令:
编辑
crontab文件:crontab -e
添加定时任务: 在
crontab文件中添加以下内容:0 18 27 1 * /data/zfj/stop_server.sh
0 18 27 1 *:表示在 1 月 27 日下午 18 点执行。/data/zfj/stop_server.sh:脚本的绝对路径。
验证是否成功
[root@localhost zfj]# crontab -l
0 18 27 1 * /data/zfj/stop_server.sh
[root@localhost zfj]#
测试定时任务是否生效,可以改成临近的时间,随便输出内容
需要确保脚本
有执行的权限chmod +x /data/zfj/stop_server.sh
# 18、poi 模板导出 excel 文件慢卡死,换成 easyexcel 模板导出
# 问题描述:
原本项目使用 poi 以模板的方式导出 excel 文件,数据超过 20 万条导致导出卡死,系统慢。
# 影响:
导致正常使用的系统慢,影响用户体验
# 解决方案:
换成 easyexcel 来导出,并修改原本的模板文件,具体流程是:
- easyexcel 复制模板内容到多个 sheet
- 每个 sheet 在进行每个填充
模板内容:

代码部分:
public static void export(OutputStream outputStream, | |
List<Map<String, Object>> dataList, | |
String templatePath) throws Exception { | |
// 步骤 1:使用 POI 复制模板 Sheet(与原有逻辑一致) | |
InputStream templateStream = new FileInputStream(templatePath); | |
Workbook workbook = new XSSFWorkbook(templateStream); | |
int templateSheetIndex = 0; | |
for (int i = 0; i < dataList.size(); i++) { | |
workbook.cloneSheet(templateSheetIndex); | |
} | |
workbook.removeSheetAt(templateSheetIndex); | |
File tempFile = File.createTempFile("temp", ".xlsx"); | |
try (FileOutputStream tempOut = new FileOutputStream(tempFile)) { | |
workbook.write(tempOut); | |
} | |
workbook.close(); | |
templateStream.close(); | |
// 步骤 2:使用 EasyExcel 填充数据 | |
ExcelWriter excelWriter = null; | |
try { | |
excelWriter = EasyExcel.write(outputStream) | |
.withTemplate(tempFile) | |
.build(); | |
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); | |
for (int i = 0; i < dataList.size(); i++) { | |
Map<String, Object> data = dataList.get(i); | |
// 将循环数据包装为 FillWrapper | |
WriteSheet writeSheet = EasyExcel.writerSheet(i).build(); | |
if (data.containsKey("data")) { | |
List<Map<String, Object>> dataItems = (List<Map<String, Object>>) data.get("data"); | |
excelWriter.fill(new FillWrapper("data", dataItems), fillConfig, writeSheet); | |
} | |
excelWriter.fill(data, writeSheet); | |
} | |
} finally { | |
if (excelWriter != null) { | |
excelWriter.finish(); | |
} | |
tempFile.delete(); | |
} | |
} | |
@GetMapping("/exporttest") | |
public void exportExcel(HttpServletResponse response) throws Exception { | |
List<Map<String, Object>> dataList = new ArrayList<>(); | |
for (int i = 0; i < 10; i++) { | |
Map<String, Object> sheet01 = new HashMap<>(); | |
sheet01.put("unitCode", "1002"); | |
sheet01.put("unitName", "测试单位2"); | |
sheet01.put("subjectCode", "A002"); | |
sheet01.put("subjectName", "测试科目2"); | |
List<Map<String, Object>> data01 = new ArrayList<>(); | |
for (int i1 = 0; i1 < 200; i1++) { | |
HashMap<String, Object> row1 = new HashMap<>(); | |
row1.put("year", "2023"); | |
row1.put("month", "02"); | |
row1.put("voucherNo", "V0003"); | |
row1.put("date", "2023-02-01"); | |
row1.put("summary", "测试摘要3"); | |
row1.put("debit", "200.00"); | |
row1.put("credit", "0.00"); | |
row1.put("balance", "200.00"); | |
data01.add(row1); | |
} | |
sheet01.put("data", data01); | |
dataList.add(sheet01); | |
} | |
// 设置响应头 | |
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); | |
response.setHeader("Content-Disposition", "attachment; filename=export.xlsx"); | |
// 执行导出 | |
export(response.getOutputStream(), dataList, "E:\\templates\\template.xlsx"); | |
} |
# 19、sqlserver 导出一张表,多数据量慢,并且用 sqlcmd 导入也慢,如何处理?
# 问题描述:
sqlserver 导出一张表,数据量比较多,导出的时候慢,并且用 sqlcmd 导入也很慢
# 影响:
当有多个这种大表的时候,又急着用,导入导出太慢,影响工作效率
# 解决方案:
可以使用 sqlserver 的 bcp 命令来导出、导入,关于 bcp 说明:
bcp(Bulk Copy Program)是 SQL Server 提供的一个命令行工具,用于在 SQL Server 实例与用户指定格式的数据文件之间批量复制表或视图数据。bcp 工具非常适合进行大量数据的导入导出操作,因为它可以绕过 SQL Server 的解析器和查询优化器,直接将数据写入或读取到物理文件中,从而提高数据传输效率。
具体处理步骤:
执行导出命令
## 批量导出bcp zcjy.dbo.test_table OUT F:\testtable.bcp -T -n
执行导入命令
## 批量导入bcp zcjy.dbo.test_table_1 IN F:\testtable.bcp -T -n
# 多表导出小技巧:
如果我有很多表需要导出,如果一张一张导出很费时,可以这样:
表导出的执行命令
-- 查询出对应表导出的执行命令select 'bcp zcjy_szqc2024_zq.dbo.' + name + ' OUT D:\zfj\备份\zcjy_szqc2024_zq\' + name + '.bcp -T -n' from sysobjects
where 1 = 1 and xtype='U' and name<>'dtproperties' and name in ('qcsj18_CQJK_1103','qcsj18_CQTZ_07')
表导出的脚本
bcp zcjy_szqc2024_zq.dbo.qcsj18_GDZC02_0802_NEW OUT D:\zfj\备份\zcjy_szqc2024_zq\qcsj18_GDZC02_0802_NEW.bcp -T -n
bcp zcjy_szqc2024_zq.dbo.qcsj18_ZCFZ_19 OUT D:\zfj\备份\zcjy_szqc2024_zq\qcsj18_ZCFZ_19.bcp -T -n
echo 所有命令执行完毕!pause
看结果
D:\zfj\备份\zcjy_szqc2024_zq>bcp zcjy_szqc2024_zq.dbo.qcsj18_SCZC_05 OUT D:\zfj\备份\zcjy_szqc2024_zq\qcsj18_SCZC_05.bcp -T -n
开始复制...已复制 945 行。网络数据包大小(字节): 4096
总时钟时间(毫秒) : 344 平均值: (每秒 2747.09 行。)
D:\zfj\备份\zcjy_szqc2024_zq>echo 所有命令执行完毕!
所有命令执行完毕!
D:\zfj\备份\zcjy_szqc2024_zq>pause
表导入的执行命令
select 'bcp analysis_szqc2024.' + name + ' IN xxxx\' + name + '.bcp -T -n' from sysobjects
where 1 = 1 and xtype='U' and name<>'dtproperties' and name in ('rep_szqc_08_1','rep_szqc_08_2','rep_szqc_09','dist','distex')
表导入的脚本
#注意 xxxx 改为实际文件的路径bcp analysis_szqc2024.dist IN xxxx\dist.bcp -T -n
bcp analysis_szqc2024.distEx IN xxxx\distEx.bcp -T -n
bcp analysis_szqc2024.rep_szqc_08_1 IN xxxx\rep_szqc_08_1.bcp -T -n
bcp analysis_szqc2024.rep_szqc_08_2 IN xxxx\rep_szqc_08_2.bcp -T -n
bcp analysis_szqc2024.rep_szqc_09 IN xxxx\rep_szqc_09.bcp -T -n
echo 所有命令执行完毕!pause
# 20、第三方要求要用 https 的方式访问我们的接口,怎么处理?
# 问题描述:
第三方要求要用 https 的方式访问我们的接口,没有提供证书
# 影响:
影响第三方对接
# 解决方案:
可以使用 nginx 配置 https 使用 自制证书
具体处理步骤:
首先需要安装 nginx 并且要安装
http_ssl_module模块, 可以参考[root sbin]# ./nginx -V
nginx version: nginx/1.21.3
built by gcc 7.3.0 (GCC)
built with OpenSSL 1.1.1f 31 Mar 2020
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --add-module=/root/nginx-plugin-master
安装自制证书
#创建 req.cnf 配置文件:vi req.cnf#内容:[req] # 必须添加 [req] 配置节
distinguished_name = req_distinguished_name # 声明 DN 配置位置
x509_extensions = v3_req # 证书扩展项
prompt = no # 禁用交互式提示
[req_distinguished_name]
C = CNST = gdL = zsO = tzOU = tzCN = 192.168.0.101 # 证书主体名
[v3_req]
keyUsage = critical, digitalSignature, keyAgreementextendedKeyUsage = serverAuthsubjectAltName = @alt_names # 引用备用名称
[alt_names]
IP.1 = 192.168.0.101 # 必须与 CN 一致
# IP.2 = 192.168.11.222#然后执行生成证书命令:openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt -config req.cnf -sha256
#当前目录下会生成 server.key 和 server.crt 文件配置 nginx
server {
listen 8835 ssl;
server_name 192.168.0.101;
ssl_certificate /opt/software/nginx/ssl/server.crt;
ssl_certificate_key /opt/software/nginx/ssl/server.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 120m;
ssl_prefer_server_ciphers on;
ssl_session_tickets off;
ssl_stapling_verify on;
location ^~ /pay/ {
#去掉主机名rewrite ^/pay/(.*)$ /$1 break;
#转发保留原有信息proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header HTTP_X_FORWARDED_FOR $remote_addr;
#自用 标识原请求的协议、上下文路径、端口proxy_set_header Scheme $scheme;
proxy_set_header Context-Path /pay;
proxy_set_header Server-Port $server_port;
proxy_pass http://192.168.0.101:8836;
#后端重定向协议修改(修改 location 的值)proxy_redirect http:// $scheme://;
}}验证
# 需要指定证书 或者绕过证书才能 使用 https 请求, 否则会报错curl --cacert server.crt -X POST https://192.168.0.101:8835/pay/api/search/xxx -H "Content-Type: text/plain" --data-raw "Ejqgrq6HxbKsGBMzN9Nak7SD45Q6AV1auOcgxWMzosBZO+bnbshGG1NuLMvbfx8gnEJBdH4HRXmVnIrkQVuLPIy37EeVCMS8UMMa4wXGEb8hVVQ/6oANK1fNGSZ2IHBQjCz5pieCjwsUYSCudqo5siJJcPiBVwdEzObm9QYGk9C5++18JHc8EVPPEzumcCs9M3JunXbXmwNNto3kQfK/O8aQ7lOs6usdJpCYq6nYJM+6DKkNXL4JdawKqfM5YnB0UglTK3wPaqUmRqrQxW8SN6FJpQFnZcABX0MfqLtsnIjqM="
VO+eU3qHcSwMoheBr3w227kKb19C+OJvFpiKCoAXCYKX6NGqz/txXRgnUW3t+zB5HBU+f3Q619AAssNPjhaDD53PgF9IRYULD6yKP/WWlSBRP44xmJJCLQkw8YZCCYp2VKwrV4z4hFvrsnENyO+60syrBLutO/gYTr6Bq6R+eWsCtC6whdlC7Ga4prLSq9e+H30/LuUN07PXaKRkFW4ZJwXhAd
有响应结果表示成功!
上面执行生成证书命令的参数说明:
req 大致有3个功能:生成证书请求文件、验证证书请求文件和创建根CA-x509 说明生成自签名证书 -nodes openssl req在自动创建私钥时,将总是加密该私钥文件,并提示输入加密的密码。可以使用"-nodes"选项禁止加密私钥文件。-days 指定所颁发的证书有效期。 -newkey 实际上,"-x509"选项和"-new"或"-newkey"配合使用时,可以不指定证书请求文件,它在自签署过程中将在内存中自动创建证书请求文件 "-newkey"选项和"-new"选项类似,只不过"-newkey"选项可以直接指定私钥的算法和长度,所以它主要用在openssl req自动创建私钥时。 rsa:2048 rsa表示创建rsa私钥,2048表示私钥的长度。-keyout 指定私钥保存位置。-out 新的证书请求文件位置。-config 指定req的配置文件,指定后将忽略所有的其他配置文件。如果不指定则默认使用/etc/pki/tls/openssl.cnf中req段落的值
# 总结
总结工作中遇到的问题及其解决方案,强调解决问题的重要性和效果。
# 参考资料
- linux LVM 和 RAID 管理命令
- https://blog.csdn.net/sinat_41836475/article/details/125408783





