# 工作问题解决方案汇总

# 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;
            }
        }

# 总结

总结工作中遇到的问题及其解决方案,强调解决问题的重要性和效果。

# 参考资料

  • linux LVM 和 RAID 管理命令
  • https://blog.csdn.net/sinat_41836475/article/details/125408783