# 工作问题解决方案汇总

# 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 中有三个物理卷,且属于不同硬盘的

# 影响

由于后续不能在扩容了或者其他目录需要缩容,现在目录又被正在使用的达梦数据库给占用了

# 解决方案

先将达梦占用的这个目录换一个路径,移动到其他目录。在将挂载的目录卸载,最后卸载卷组后重新挂载

根据当时的情况进行一下操作步骤处理

  1. 由于达梦数据库保存的归档日志和备份文件都在 /dmbak 目录下,所以需要修改达梦归档日志和备份文件的路径 (达梦数据库的使用和安装看另一篇文章)
  2. 修改归档文件的路径
-- 先查下这个
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;
-- 由于是在达梦客户端代理配置定时备份任务,直接修改路径即可
  1. 处理数据:这里有两种方式处理数据,以下列举
#第一种:分配一块新的硬盘并将它加入到 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 目录下的所有文件和子目录。
#继续执行后续操作 卸载文件系统 - 卸载逻辑卷 - 卸载卷组
  1. 卸载文件系统 /dmbak 目录 首先,确保卷组中的逻辑卷上的文件系统已经被卸载。您可以使用以下命令来卸载文件系统
## 确保在卸载之前没有任何进程在使用的目录。
umount /dmbak
  1. 清除硬盘数据: 使用 dd 命令
#清除 /dev/vdc 硬盘上的数据:
  1. 卸载逻辑卷:接下来,您需要卸载卷组中的逻辑卷。您可以使用 lvremove 命令来删除逻辑卷
lvremove /dev/dmbak/dmbaklv
  1. 卸载卷组:一旦逻辑卷被删除,您可以使用 vgremove 命令来删除卷组:
vgremove dmbak
  1. 卸载物理卷: 卷组删除之后可以删除物理卷,重新设置物理卷分区合并分区等。否则会有提示,设备已包含一个 'LVM2_member' 签名,写入命令会将其移除
pvmove /dev/vdb3
pvmove /dev/vdc1
pvmove /dev/vdc2
  1. 最后进行重新分卷分组 (参考 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%
print
toggle
1
lvm
mkpart
vdc2
ext4
50%
10%
print
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
print
rm 3
mkpart
primary
ext4
print
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 在官网找解决方案,排除云服务的达梦是否需要什么配置

  1. 询问云服务部署的工作人员,无问题。
  2. 用监视工具追语句在客户端查询正常
  3. 排除项目查询出来的结果是否有问题,进行输出打印结果。
  4. 根据打印结果分析,发现数据库 clob 类型的值是 null,但是查询出来的结果却又值 是 clob 字段类型的 hash 值,调试 clob 值是包含了达梦连接信息的元数据
  5. 因为项目是前后端分离的,以 json 返回的方式,那么查询的结果会转为 json,那么对应的元数据值也会被转换,所以就返回了这些信息

所以只需要在查询出结果的时候把 clob 转成字符串就行, 有两种方式

  1. 循环结果处理 clob 类型
  2. 通过达梦官网可以加参数在查询的时候自动将 clob 转成 string 类型。 clobAsString=true;
  3. 或者改成 longvarchar 类型也可以

将连接改成:jdbc:dm://DWM?clobAsString=true 后 正常!

# 3、mount 挂载不上,不提示任何信息

# 问题描述

mount 挂载不上目录

# 影响

在不改目录的情况下 无法重置挂载新的硬盘

# 解决方案

  1. 使用 cat /var/log/message 查看系统日志信息
  2. 如果出现类似:systemd: Unit data1.mount is bound to inactive unit dev-sdj.device. Stopping, too. 的字样表示系统记录信息有误
  3. 查看 /etc/fstab 是否手动加了开机自启的挂载目录的配置
  4. 如果存在删除这条记录
  5. 重试如果还不行就执行 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 上无法正常运行该统计表内容

# 解决方案

  1. 需要将子查询的内容进行拆分查询
  2. 首先查询出主查询的结果(不带子查询的内容)做为临时表并确定一个唯一值 a
  3. 在和 workday 表做笛卡儿积查询并根据原有的条件过滤,并根据唯一值和 solar_date 分组 排序, 那么结果就得到每个主键下的所有的日期
  4. 在更具 solar_date 排序,keyword 分组获取每组中排第 7 的时间
  5. 最后就可以根据 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 的不会

# 影响

影响用户正常使用

# 解决方案

  1. 修改两边的 nginx 超时时间短一些(几秒钟),发现正常超时
  2. 修改 nginx 超时时间长一些(5 分钟)域名访问的提前超时,ip 访问的正常超时
  3. 查看 nginx 的配置是否生效 nginx -t 正常
  4. 查看 nginx 的访问日志 tail -f log/access.log tail -f log/error.log 发现只有 ip 访问的有超时的日志,域名访问的没有。这就表示域名访问的提前超时了
  5. 最终确定就是域名解析的问题,最后交给相应的工作人员设置了超时时间,域名访问正常!

# 7、a 项目访问附件失败就去访问 b 项目,b 项目也是如此

# 问题描述

现在有 a、b 两个项目一个域名访问,一个 ip 访问(两个不同的 tomcat),一开始没考虑好,由于用户操作上传的附件是上传到 tomcat 相对路径下,导致如果我在 ip 访问的项目上传的图片,想去域名访问的项目查看就看不到图片

# 影响

用户无法正常看到图片

# 解决方案

  1. 修改数据库配置,将存储附件的目录设置为同一个,需要移动已经上传的附件,这种方式有点麻烦需要停项目

  2. 直接在 nginx 配置,当 a 连接访问不到内容,就去访问 b 连接

    #请求的连接
    #这个是域名访问的 nginx 配置 域名实际访问的 ip 端口是 192.168.20.30:8088
    location /tdlz_ht/uploads/new/ {
        proxy_intercept_errors on;
        #当请求返回的是 404 页面 就转发到 @service_static_file
        error_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 默认允许的最大请求体大小时

# 影响

无法正常上传附件

# 解决方案

  1. 修改 nginx 配置

    http {
        # 其他配置...
        client_max_body_size 8M;(配置请求体缓存区大小, 不配的话) 
        client_body_buffer_size 128k;(设置客户端请求体最大值) 
        fastcgi_intercept_errors on;
        # 其他配置...
    }
    #请注意,在增加限制时要谨慎,确保服务器有足够的资源来处理大型请求。

# 9、不需要登陆的内部系统需要调用权限控制的项目的接口

# 问题描述

有一个内部定时任务项目,需要调用 cas 认证的项目的接口,需要在代码里做登陆认证调接口

# 影响

功能无法正常使用

# 解决方案

  1. 一步步模拟 cas 登陆流程做对应的处理,最后得到项目有效的 sessionid, 请求的时候带上 sessionid 就可以跳过登陆了

  2. 如果有用到前端 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);
                // 设置请求头,请求类型为 json
                HttpHeaders 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;
                }
                // 登陆成功返回的 cookies
                List<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()
                {
                    @Override
                    public 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>()
                {
                    @Override
                    public 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>()
            {
                @Override
                public 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 版本不同可能导致缺少依赖

# 解决方案

  1. 修改源码或者更换 jdk 版本

  2. 当打包的 java jar 包需要更换依赖或者依赖其他 jar 包可以用下面的方法

然后执行命令:

​ java -Djava.ext.dirs=D:\xxx\lib -jar map-0.0.1-SNAPSHOT.jar

# 11、使用 spoon 遇到的类型转换问题

# 问题描述

使用 spoon 遇到的问题 需要输入的数据为时间类型 试了很多组件都不行

# 影响

导致输出的类型不是字符串 插入失败

# 解决方案

  1. 使用 tochar () 函数 直接将输入的类型转成字符串

# 12、dockerhub 国内镜像站集体下线?解决方法

# 问题描述

今天重新部署项目发现 eclipse-temurin:8-jre 拉取超时了,问了同事也是一样拉去不了,去了解了一下发现国内镜像都下线不可用了,找了半天也是找到了解决方法,感谢网络好心人。

# 影响

镜像无法拉取

# 解决方案

  1. 通过 docker 配置文件配置可用的国内镜像源(不好使,很多不能用的,我用阿里云的镜像加速器也不行)

  2. 设置代理 (这个要科学上网、得会配置代理,麻烦,没有尝试)

  3. 自建镜像仓库 (推荐这种,简单。在 github 上有现成的开源的) 地址感谢大佬

    1. 登录阿里云,找到容器镜像服务,创建一个个人版实例。(第一次使用的话,会让设置访问密码。记住,后面会用)

    2. 创建实例成功后找到仓库管理 - 命名空间,新建一个命名空间且设置为公开或私有(私有在拉取或推送需要密码验证)

    3. 点击访问凭证获取凭证信息 第一个是用户名, 第二是仓库地址

      sudo docker login --username=阿里云用户名 registry.cn-beijing.aliyuncs.com

    4. 准备工作完成,配置 github,fork 项目,地址: docker_image_pusher

    5. 在 fork 后的项目中通过 Settings -> Secret and variables -> Actions -> New Repository secret 路径,配置 4 个环境变量

      • ALIYUN_NAME_SPACE - 命名空间

      • ALIYUN_REGISTRY_USER - 阿里云用户名

      • ALIYUN_REGISTRY_PASSWORD - 访问密码

      • ALIYUN_REGISTRY - 仓库地址

    6. 配置要拉取的镜像 打开项目 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

# 影响:

当需要加密的内容变多就会报错,导致无法正常加密。

# 解决方案:

有两个解决方案:

  1. 两种加密方法结合

    RSA+AES 的方式

    • 首先生成长度不大于 53 字节的 AES 密钥
    • 通过 AES 密钥加密文本内容
    • 通过 RSA 加密 AES 的密钥
  2. 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,该如何做?

  1. 使用 cron 定时任务

    推荐用这种,因为 linux 系统基本都有这个命令,不用额外安装什么其他依赖。

  2. 使用 at 命令

    at 命令用于在指定时间执行一次任务。虽然 at 命令不如 cron 常用,但它非常适合单次任务。但是需要安装 at 命令

# 影响:

由于新年期间需要停止项目,过完年重新启动,可能会有不及时的情况,肯定不能手动踩点去启停。

# 解决方案:

用 linux 只带的定时任务,让服务器自己启停我们的项目,下面是用 cron 定时任务的做法:

  1. 检查系统是否启动了定时任务

    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: 1
       Memory: 1.9M
       CGroup: /system.slice/crond.service
               └─784 /usr/sbin/crond -n
    1123 20:37:05 localhost.localdomain systemd[1]: Started Command Scheduler.
    1123 20:37:05 localhost.localdomain crond[784]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 29% if used.)
    #出现 Active: active (running) 代表启用
  2. 如果未启用者执行下面的命令启用

    sudo systemctl enable cron  # Debian/Ubuntu
    sudo systemctl start cron   # Debian/Ubuntu
    sudo systemctl enable crond # CentOS
    sudo systemctl start crond  # CentOS
  3. 添加启动或停止程序的脚本,这里用 nginx 演示

    启动 nginx 的脚本:

    #!/bin/bash
    # /data/zfj/start_server.sh
    # 设置日志文件路径
    LOG_FILE="/data/zfj/start_server.log"
    # 记录脚本开始执行的时间
    echo "Script started at $(date)" >> "$LOG_FILE"
    # 启动 Nginx
    if /home/software/nginx/nginx-1.25.5/sbin/nginx; then
        echo "Nginx Started successfully at $(date)" >> "$LOG_FILE"
    else
        echo "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"
    # 关闭 Nginx
    if /home/software/nginx/nginx-1.25.5/sbin/nginx -s stop; then
        echo "Nginx stopped successfully at $(date)" >> "$LOG_FILE"
    else
        echo "Failed to stop Nginx at $(date)" >> "$LOG_FILE"
    fi
    # 记录脚本结束执行的时间
    echo "Script ended at $(date)" >> "$LOG_FILE"
  4. 配置定时任务 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 :脚本的绝对路径。
  5. 验证是否成功

    [root@localhost zfj]# crontab -l
    0 18 27 1 * /data/zfj/stop_server.sh
    [root@localhost zfj]#
  6. 测试定时任务是否生效,可以改成临近的时间,随便输出内容

  7. 需要确保脚本 有执行的权限

    chmod +x /data/zfj/stop_server.sh

# 18、poi 模板导出 excel 文件慢卡死,换成 easyexcel 模板导出

# 问题描述:

原本项目使用 poi 以模板的方式导出 excel 文件,数据超过 20 万条导致导出卡死,系统慢。

# 影响:

导致正常使用的系统慢,影响用户体验

# 解决方案:

换成 easyexcel 来导出,并修改原本的模板文件,具体流程是:

  1. easyexcel 复制模板内容到多个 sheet
  2. 每个 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 的解析器和查询优化器,直接将数据写入或读取到物理文件中,从而提高数据传输效率。

具体处理步骤:

  1. 执行导出命令

    ## 批量导出
    bcp zcjy.dbo.test_table OUT F:\testtable.bcp -T -n
  2. 执行导入命令

    ## 批量导入
    bcp zcjy.dbo.test_table_1 IN F:\testtable.bcp -T -n

# 多表导出小技巧:

如果我有很多表需要导出,如果一张一张导出很费时,可以这样:

  1. 表导出的执行命令

    -- 查询出对应表导出的执行命令
    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')
  2. 表导出的脚本

    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
  3. 看结果

    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
  4. 表导入的执行命令

    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')
  5. 表导入的脚本

    #注意 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 使用 自制证书

具体处理步骤:

  1. 首先需要安装 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
  2. 安装自制证书

    #创建 req.cnf 配置文件:
    vi req.cnf
    #内容:
    [req]  # 必须添加 [req] 配置节
    distinguished_name = req_distinguished_name  # 声明 DN 配置位置
    x509_extensions = v3_req  # 证书扩展项
    prompt = no  # 禁用交互式提示
    [req_distinguished_name]
    C = CN
    ST = gd
    L = zs
    O = tz
    OU = tz
    CN = 192.168.0.101  # 证书主体名
    [v3_req]
    keyUsage = critical, digitalSignature, keyAgreement
    extendedKeyUsage = serverAuth
    subjectAltName = @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 文件
  3. 配置 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://;
    	}
    	
    }
  4. 验证

    # 需要指定证书 或者绕过证书才能 使用 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