# 工作问题解决方案汇总
# 1、硬盘分卷问题(linux)
# 问题描述:
linux 我有两块硬盘 vdc 和 vdb 。vdc 分了两个分区 vdc1、vdc2,vdb 分了 3 个分区 vdb1、vdb2、vdb3, 然后有两个卷组 dmbak、storage。卷组 dmbak 中的物理卷有 vdc1,vdc2 和 vdb3,现在的问题是我的 dmbak 中的卷组用到了 vdb3 分区现在我想我想卸载 /dmbak 目录挂载的硬盘,并把 dmbak 卷组的 vdb3 给还回去,重新进行硬盘规划,因为原来的分的很乱。 (关于分卷这些概率可以参考 linux 文章)
pvs情况: | |
[root@localhost ~]# pvs | |
PV VG Fmt Attr PSize PFree | |
/dev/vda3 uos lvm2 a-- 78.80g 0 | |
/dev/vda4 uos lvm2 a-- <120.00g 0 | |
/dev/vdb1 storage lvm2 a-- <400.00g 56.00m | |
/dev/vdb2 storage lvm2 a-- <400.00g 0 | |
/dev/vdb3 dmbak lvm2 a-- <800.00g 0 | |
/dev/vdc1 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdc2 dmbak lvm2 a-- <400.00g 0 | |
vgs情况: | |
[root@localhost ~]# vgs | |
VG #PV #LV #SN Attr VSize VFree | |
dmbak 3 1 0 wz--n- 1.56t 0 | |
storage 2 1 0 wz--n- 799.99g 56.00m | |
uos 2 3 0 wz--n- <198.80g 0 | |
lvs情况: | |
[root@localhost ~]# lvs | |
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert | |
dmbaklv dmbak -wi-ao---- 1.56t | |
vo storage -wi-ao---- <799.94g | |
home uos -wi-ao---- <24.77g | |
root uos -wi-ao---- <170.00g | |
swap uos -wi-ao---- <4.04g | |
#根据上述的查看结果可以清楚的得知在卷组 dmbak 中有三个物理卷,且属于不同硬盘的 |
# 影响:
由于后续不能在扩容了或者其他目录需要缩容,现在目录又被正在使用的达梦数据库给占用了
# 解决方案:
先将达梦占用的这个目录换一个路径,移动到其他目录。在将挂载的目录卸载,最后卸载卷组后重新挂载
根据当时的情况进行一下操作步骤处理
- 由于达梦数据库保存的归档日志和备份文件都在 /dmbak 目录下,所以需要修改达梦归档日志和备份文件的路径 (达梦数据库的使用和安装看另一篇文章)
- 修改归档文件的路径
-- 先查下这个 | |
select ARCH_FILE_SIZE,ARCH_SPACE_LIMIT from v$dm_arch_ini; | |
-- 修改过程会短暂影响数据库,调整过程无法访问数据库对象,修改路径前最好数据库备份 | |
alter database mount; | |
alter database noarchivelog; | |
alter database add archivelog 'type=local,dest=新归档路径,file_size=单个归档文件大小(m),space_limit=归档文件上限大小(m)'; | |
alter database delete archivelog 'dest=原归档路径'; | |
alter database archivelog; | |
alter database open; | |
-- 由于是在达梦客户端代理配置定时备份任务,直接修改路径即可 |
- 处理数据:这里有两种方式处理数据,以下列举
#第一种:分配一块新的硬盘并将它加入到 dmbak 这个 vg 组, 在把同组其他分区的数据通过 pvmove 命令移动到新加的硬盘 (注意:这种方式需要保证足够的空间) | |
PV VG Fmt Attr PSize PFree | |
/dev/vda3 uos lvm2 a-- 78.80g 0 | |
/dev/vda4 uos lvm2 a-- <120.00g 0 | |
/dev/vdb1 storage lvm2 a-- <400.00g 56.00m | |
/dev/vdb2 storage lvm2 a-- <400.00g 0 | |
/dev/vdb3 dmbak lvm2 a-- <800.00g 0 | |
/dev/vdc1 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdc2 dmbak lvm2 a-- <400.00g 0 | |
/dev/vdd dmbak lvm2 a-- <1000.00g 200.00g | |
将 /dev/vdb3、 /dev/vdc1、/dev/vdc2的数据都移动到 /dev/vdd 上 | |
#移动数据:使用 pvmove 命令将该物理卷中的数据移动到其他物理卷上。 | |
pvmove /dev/vdb3 /dev/vdd | |
pvmove /dev/vdc1 /dev/vdd | |
pvmove /dev/vdc2 /dev/vdd | |
pvmove /dev/vdc1 /dev/vdb3 | |
pvmove /dev/vdc2 /dev/vdb3 | |
#移除物理卷:移动数据完成后,可以使用 vgreduce 命令将该物理卷从卷组中移除。假设要移除的物理卷名为 /dev/vdb3: | |
vgreduce dmbak /dev/vdb3 | |
#然后将 /dev/vdc 释放 合成一块 800g 大小的硬盘 在重新加入组 dmbak | |
vgreduce dmbak /dev/vdc1 | |
vgreduce dmbak /dev/vdc2 | |
#合并后的硬盘在追加回来 | |
vgextend dmbak /dev/vdc | |
#最后在将 vdd 移动到 vdc | |
pvmove /dev/vdb3 /dev/vdc | |
#结果: | |
#移除物理卷:移动数据完成后,可以使用 vgreduce 命令将该物理卷从卷组中移除。假设要移除的物理卷名为 /dev/vdb3: | |
vgreduce dmbak /dev/vdb3 | |
vgreduce dmbak /dev/vdd | |
#上面这种方式比较麻烦 | |
#第二种:将新的临时硬盘挂载到临时目录 /temp_dmbak 再将 /dmbak 目录下的内容移动到临时目录 /temp_dmbak | |
nohup cp -r /dmbak /temp_dmbak & | |
#清除 /dmdbk 目录上的数据: | |
rm -rf /dmdbk/* | |
#这将递归删除 /dmdbk 目录下的所有文件和子目录。 | |
#继续执行后续操作 卸载文件系统 - 卸载逻辑卷 - 卸载卷组 |
- 卸载文件系统 /dmbak 目录 首先,确保卷组中的逻辑卷上的文件系统已经被卸载。您可以使用以下命令来卸载文件系统
## 确保在卸载之前没有任何进程在使用的目录。 | |
umount /dmbak |
- 清除硬盘数据: 使用
dd
命令
#清除 /dev/vdc 硬盘上的数据: |
- 卸载逻辑卷:接下来,您需要卸载卷组中的逻辑卷。您可以使用
lvremove
命令来删除逻辑卷
lvremove /dev/dmbak/dmbaklv |
- 卸载卷组:一旦逻辑卷被删除,您可以使用
vgremove
命令来删除卷组:
vgremove dmbak |
- 卸载物理卷: 卷组删除之后可以删除物理卷,重新设置物理卷分区合并分区等。否则会有提示,设备已包含一个 'LVM2_member' 签名,写入命令会将其移除
pvmove /dev/vdb3 | |
pvmove /dev/vdc1 | |
pvmove /dev/vdc2 |
- 最后进行重新分卷分组 (参考 linux: 使用物理卷 (PV)、逻辑卷 (LV)、卷组 (VG) 管理管理的方式挂载硬盘这一章)
对应 231 服务器的问题解决步骤
#停止达梦服务 | |
./DmServiceDM stop | |
#--- | |
#alter database mount; | |
#alter database noarchivelog; | |
#alter database add archivelog 'type=local,dest=/data/dmbak,file_size=2048,space_limit=102400'; | |
#alter database delete archivelog 'dest=/dmbak/dmarch'; | |
#alter database archivelog; | |
#alter database open; | |
#拷贝到新挂载的硬盘 这里新加的临时硬盘为 vdd | |
mkfs.ext4 /dev/vdd | |
mkdir -p /tempbak | |
mount /dev/vdd /tempbak | |
nohup cp -r /dmbak /tempbak & | |
#为了校验达梦换了归档文件路径是否能正常启动 | |
#nohup cp -r /tempbak/dmbak/dmarch/* /data/dmbak & | |
#./DmServiceDM start | |
#删除文件内容 | |
rm -rf /dmbak/* | |
#卸载 | |
umount /dmbak | |
#删除文件夹 | |
rm -rf dmbak | |
#删除逻辑卷 | |
lvremove /dev/dmbak/dmbaklv | |
#卸载卷组 | |
vgremove dmbak | |
#卸载物理卷 | |
pvremove /dev/vdc1 | |
pvremove /dev/vdc2 | |
pvremove /dev/vdb3 | |
#重新分区 | |
parted 命令 | |
mklable | |
gpt | |
mkpart | |
vdc1 | |
ext4 | |
0% | |
50% | |
toggle | |
1 | |
lvm | |
mkpart | |
vdc2 | |
ext4 | |
50% | |
10% | |
toggle | |
2 | |
lvm | |
quit | |
#重新创建物理卷 | |
pvcreate /dev/vdc1 | |
pvcreate /dev/vdc2 | |
#创建新卷组 | |
vgcreate dmbakvg /dev/vdc1 /dev/vdc2 | |
#创建逻辑卷 | |
lvcreate -L 799.98g -n dmbaklv dmbakvg | |
#创建新目录 | |
mkdir dmbaknew | |
#格式化逻辑卷 | |
mkfs.ext4 /dev/dmbakvg/dmbaklv | |
#重新挂载 | |
mount /dev/dmbakvg/dmbaklv /dmbak/ | |
#可能出现挂载不了的情况 原因可能是系统记录的信息有误 | |
#修改挂载记录: | |
/etc/fstab | |
#执行以下命令 | |
systemctl daemon-reload | |
#重新挂载 | |
mount /dev/dmbakvg/dmbaklv /dmbak/ | |
#移动回数据 | |
nohup cp -r /tempbak/dmbak/* /dmbaknew & | |
#将 vdb3 追加 | |
parted /dev/vdb | |
rm 3 | |
mkpart | |
primary | |
ext4 | |
toggle | |
3 | |
lba | |
toggle | |
3 | |
lvm | |
quit | |
pvcreate /dev/vdb3 | |
vgextend storage /dev/vdb3 | |
lvexted -L +800g /dev/storage/vo | |
resize2fs /dev/storage/vo |
对应 216 服务器的问题解决步骤
#由于是没有做如何分区分组,直接将物理磁盘挂载那么可以用以下方式
#直接可以使用:resize2fs /dev/vdb 3T 命令扩展
#备份文件
nohup cp -r /java /test &
#卸载
umount /java
#分区
parted /dev/vdb
#分两个
vdb1 vdb2
#创建物理卷
pvcreate /dev/vdb1
pvcreate /dev/vdb2
#创建卷组
vgcreate projectvg /dev/vdb1
#创建逻辑卷
lvcreate -L +1.95T -n projectlv projectvg
rm -rf /java
mkdir -p java
mount /dev/projectvg/projectlv /java
# 2、云服务部署的达梦数据库查询字段类型有 Clob 类型会返回所有连接的元数据
# 问题描述:
连接 jdbcurl:jdbc:dm://DWM,DWM 是云服务连接,本地是直接连接 jdbcurl: jdbc:dm://localhost:5236。 我在本地查询接口字段名:distinfo, 类型为:clob,值为 null。 在本地查出来是为空的 但是换成云服务器连接查询结果就变成了一大串元数据 比如:connection:.... 之类的。
# 影响:
请求接口响应的内容就会有一大串没有用的元数据,响应的大小也会影响速度
# 解决方案:
确定数据库 clob 类型的字段是否存在值,不存在就按照 clob 在官网找解决方案,排除云服务的达梦是否需要什么配置
- 询问云服务部署的工作人员,无问题。
- 用监视工具追语句在客户端查询正常
- 排除项目查询出来的结果是否有问题,进行输出打印结果。
- 根据打印结果分析,发现数据库 clob 类型的值是 null,但是查询出来的结果却又值 是 clob 字段类型的 hash 值,调试 clob 值是包含了达梦连接信息的元数据
- 因为项目是前后端分离的,以 json 返回的方式,那么查询的结果会转为 json,那么对应的元数据值也会被转换,所以就返回了这些信息
所以只需要在查询出结果的时候把 clob 转成字符串就行, 有两种方式
- 循环结果处理 clob 类型
- 通过达梦官网可以加参数在查询的时候自动将 clob 转成 string 类型。 clobAsString=true;
- 或者改成 longvarchar 类型也可以
将连接改成:jdbc:dm://DWM?clobAsString=true 后 正常!
# 3、mount 挂载不上,不提示任何信息
# 问题描述:
mount 挂载不上目录
# 影响:
在不改目录的情况下 无法重置挂载新的硬盘
# 解决方案:
- 使用 cat /var/log/message 查看系统日志信息
- 如果出现类似:systemd: Unit data1.mount is bound to inactive unit dev-sdj.device. Stopping, too. 的字样表示系统记录信息有误
- 查看 /etc/fstab 是否手动加了开机自启的挂载目录的配置
- 如果存在删除这条记录
- 重试如果还不行就执行 systemctl daemon-reload 命令,作用是重新加载系统配置
# 4、硬盘有 4T 大小,实际只有 2T
# 问题描述:
当一块超过 2T 大小的硬盘直接格式化挂载到指定目录后(这种方式是直接把物理卷挂载到了目录),最大只能是 2T 的容量
# 影响:
导致多余的容量闲置浪费
# 解决方案:
可以使用 resize2fs /dev/vdb 3T
命令将 /dev/vdb 挂载的目录的容量扩展到 3T,缺点是无法分配超过硬盘本身的大小
# 5、starrocks 不支持子查询语法的问题
# 问题描述:
sqlserver 语句转为 starrocks 数据库的语法,不支持子查询,并且子查询更主查询没有关联项
--sqlserver 语句 | |
select count(*) AS num | |
from ( | |
select (select top 1 STUFF(STUFF(CONVERT(char(8),DATEADD(DAY,0,solar_date),112),5,0,N'-'),8,0,N'-') from ( | |
select top 7 * from zcjy_shq..workday where is_workday=1 and solar_date>( | |
select convert(varchar (100),b.pub_date,112) ) | |
) a order by a.solar_date desc) as epub_date , | |
a.createDate,a.cno,a.prj_jiaoyi_no,a.distno,a.ztid | |
from zcjy_shq..ht_contract a,zcjy_shq..policyinfo b | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
) c where datediff(day,c.epub_date,c.createdate)>30 and c.distNo like '0607%' | |
workday的内容为: | |
id solar_date is_workday | |
1 20220101 1 | |
2 20220102 1 | |
3 20220103 0 | |
-- 语句说明,子查询部分其实是想查出 b.pub_date 时间之后的 7 天工作日的最后一天的时间 workday | |
-- 由于不支持子查询所以得换种方式查询出一样的效果 |
# 影响:
在 starrocks 上无法正常运行该统计表内容
# 解决方案:
- 需要将子查询的内容进行拆分查询
- 首先查询出主查询的结果(不带子查询的内容)做为临时表并确定一个唯一值 a
- 在和 workday 表做笛卡儿积查询并根据原有的条件过滤,并根据唯一值和 solar_date 分组 排序, 那么结果就得到每个主键下的所有的日期
- 在更具 solar_date 排序,keyword 分组获取每组中排第 7 的时间
- 最后就可以根据 keyword 关联上主查询了
-- 最终改造的语句 | |
with a as( | |
select b.pub_date as pub_date, concat(a.cno, b.pub_date) as keyword, | |
a.createDate, a.cno, a.prj_jiaoyi_no, a.distno, a.ztid | |
from zcjy_shq.ht_contract a,zcjy_shq.policyinfo as b | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
), | |
b as ( | |
select keyword , solar_date from a, zcjy_shq.workday as b where b.solar_date > jodatime_format(a.pub_date, 'yyyyMMdd') and is_workday=1 group by keyword , solar_date order by solar_date | |
), | |
-- 核心 | |
tempc as ( | |
SELECT t1.keyword, concat(substr(t1.solar_date, 1, 4), '-', substr(t1.solar_date, 5, 2), '-', substr(t1.solar_date, 7, 2)) as epub_date | |
FROM ( | |
SELECT *, | |
ROW_NUMBER() OVER(PARTITION BY keyword ORDER BY solar_date asc) AS row_num | |
FROM b | |
WHERE 1 = 1 | |
) t1 | |
WHERE t1.row_num = 7 | |
) | |
select count(*) AS num | |
from ( | |
select | |
cc.epub_date, | |
a.createDate,a.cno,a.prj_jiaoyi_no,a.distno,a.ztid | |
from zcjy_shq.ht_contract a,zcjy_shq.policyinfo as b, tempc as cc | |
where a.prj_jiaoyi_no=b.prj_jiaoyi_no and concat(a.cno, b.pub_date) = cc.keyword and b.menuno='07' and a.okflag=1 and a.runflag=1 | |
and year(a.setdate )>=2023 | |
) c | |
where DATEDIFF(timestamp(c.createdate), timestamp(c.epub_date)) > 30 | |
and c.distNo like '0607%' |
# 6、域名访问的项目超时
# 问题描述:
在同一台服务器上搭建了两个完全一样的项目,一个用 ip 访问一个用域名访问。都通过 nginx 转发,nginx 配置的超时时间是一样的,而且是没到 nginx 配置的超时时间就一样超时报 504
域名访问的项目会这样,ip 的不会
# 影响:
影响用户正常使用
# 解决方案:
- 修改两边的 nginx 超时时间短一些(几秒钟),发现正常超时
- 修改 nginx 超时时间长一些(5 分钟)域名访问的提前超时,ip 访问的正常超时
- 查看 nginx 的配置是否生效
nginx -t
正常 - 查看 nginx 的访问日志
tail -f log/access.log
tail -f log/error.log
发现只有 ip 访问的有超时的日志,域名访问的没有。这就表示域名访问的提前超时了 - 最终确定就是域名解析的问题,最后交给相应的工作人员设置了超时时间,域名访问正常!
# 7、a 项目访问附件失败就去访问 b 项目,b 项目也是如此
# 问题描述:
现在有 a、b 两个项目一个域名访问,一个 ip 访问(两个不同的 tomcat),一开始没考虑好,由于用户操作上传的附件是上传到 tomcat 相对路径下,导致如果我在 ip 访问的项目上传的图片,想去域名访问的项目查看就看不到图片
# 影响:
用户无法正常看到图片
# 解决方案:
修改数据库配置,将存储附件的目录设置为同一个,需要移动已经上传的附件,这种方式有点麻烦需要停项目
直接在 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 默认允许的最大请求体大小时
# 影响:
无法正常上传附件
# 解决方案:
修改 nginx 配置
http {
# 其他配置...
client_max_body_size 8M;(配置请求体缓存区大小, 不配的话)
client_body_buffer_size 128k;(设置客户端请求体最大值)
fastcgi_intercept_errors on;
# 其他配置...
}
#请注意,在增加限制时要谨慎,确保服务器有足够的资源来处理大型请求。
# 9、不需要登陆的内部系统需要调用权限控制的项目的接口
# 问题描述:
有一个内部定时任务项目,需要调用 cas 认证的项目的接口,需要在代码里做登陆认证调接口
# 影响:
功能无法正常使用
# 解决方案:
一步步模拟 cas 登陆流程做对应的处理,最后得到项目有效的 sessionid, 请求的时候带上 sessionid 就可以跳过登陆了
如果有用到前端 js 的 RSA 加密 则可以用 java 的 ScriptEngine 解析执行 js 方法
@Value("${monthlyReport.isStart}")
private String isStart;
@Value("${monthlyReport.login.loginCasUrl}")
private String loginCasUrl;
@Value("${monthlyReport.login.loginModuleUrl}")
private String loginModuleUrl;
@Value("${monthlyReport.keepLoginUrl}")
private String keepLoginUrl;
@Value("${monthlyReport.rsaKeyUrl}")
private String rsaKeyUrl;
@Value("${monthlyReport.login.username}")
private String username;
@Value("${monthlyReport.login.password}")
private String password;
@Value("${monthlyReport.distNo}")
private String distNo;
@Value("${monthlyReport.years}")
private String years;
@Value("${monthlyReport.beginMonths}")
private String beginMonths;
@Value("${monthlyReport.endMonths}")
private String endMonths;
@Value("${monthlyReport.selTblInfoIndex}")
private String selTblInfoIndex;
@Value("${monthlyReport.dataInput.inputUrl}")
private String inputUrl;
@Value("${monthlyReport.dataInput.importType}")
private String importType;
@Value("${monthlyReport.dataSummary.dataSummaryUrl}")
private String dataSummaryUrl;
@Value("${monthlyReport.dataSummary.months}")
private String months;
@Value("${monthlyReport.dataSummary.hzType}")
private String hzType;
@Value("${monthlyReport.retries}")
private Integer retries;
@Value("${monthlyReport.delayDuration}")
private Integer delayDuration;
RestTemplate restTemplate = null;
public void execReportDataOperationTask()
{
StopWatch stopWatch = new StopWatch("财务月报-->数据的转入汇总操作");
// 定时执行的任务 用于登陆的用户定时请求保持连接状态
ScheduledFuture<?> future = null;
try
{
stopWatch.start("cas登陆验证");
logger.info("============" + DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN) + " 开始执行cas验证登录============");
// RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
// restTemplate = restTemplateBuilder.build();
// 处理 https 请求报错的问题
TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
SSLContext sslContext = null;
try
{
sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
}
catch (Exception e)
{
logger.error("-------------配置https请求失败!!-------------");
return;
}
SSLConnectionSocketFactory sslConnectionSocketFactory
= new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
// 默认开启了 cooke 管理
CloseableHttpClient closeableHttpClient = HttpClientBuilder
.create()
.setMaxConnPerRoute(10)
.setSSLSocketFactory(sslConnectionSocketFactory)
.useSystemProperties()
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(closeableHttpClient);
// requestFactory.setConnectionRequestTimeout(10*1000);
// requestFactory.setConnectTimeout(10*1000);
// 请求超时时间 25 分钟
requestFactory.setReadTimeout(30 * 60 * 1000);
restTemplate = new RestTemplate(requestFactory);
// 设置请求头,请求类型为 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 版本不同可能导致缺少依赖
# 解决方案:
修改源码或者更换 jdk 版本
当打包的 java jar 包需要更换依赖或者依赖其他 jar 包可以用下面的方法
然后执行命令:
java -Djava.ext.dirs=D:\xxx\lib -jar map-0.0.1-SNAPSHOT.jar
# 11、使用 spoon 遇到的类型转换问题
# 问题描述:
使用 spoon 遇到的问题 需要输入的数据为时间类型 试了很多组件都不行
# 影响:
导致输出的类型不是字符串 插入失败
# 解决方案:
- 使用 tochar () 函数 直接将输入的类型转成字符串
# 12、dockerhub 国内镜像站集体下线?解决方法
# 问题描述:
今天重新部署项目发现 eclipse-temurin:8-jre
拉取超时了,问了同事也是一样拉去不了,去了解了一下发现国内镜像都下线不可用了,找了半天也是找到了解决方法,感谢网络好心人。
# 影响:
镜像无法拉取
# 解决方案:
通过 docker 配置文件配置可用的国内镜像源(不好使,很多不能用的,我用阿里云的镜像加速器也不行)
设置代理 (这个要科学上网、得会配置代理,麻烦,没有尝试)
自建镜像仓库 (推荐这种,简单。在 github 上有现成的开源的) 地址感谢大佬
登录阿里云,找到容器镜像服务,创建一个个人版实例。(第一次使用的话,会让设置访问密码。记住,后面会用)
创建实例成功后找到仓库管理 - 命名空间,新建一个命名空间且设置为公开或私有(私有在拉取或推送需要密码验证)
点击访问凭证获取凭证信息 第一个是用户名, 第二是仓库地址
sudo docker login --username=阿里云用户名 registry.cn-beijing.aliyuncs.com
准备工作完成,配置 github,fork 项目,地址: docker_image_pusher
在 fork 后的项目中通过 Settings -> Secret and variables -> Actions -> New Repository secret 路径,配置 4 个环境变量
ALIYUN_NAME_SPACE - 命名空间
ALIYUN_REGISTRY_USER - 阿里云用户名
ALIYUN_REGISTRY_PASSWORD - 访问密码
ALIYUN_REGISTRY - 仓库地址
配置要拉取的镜像 打开项目 images.txt,每一行配置一个镜像,格式:name:tag 比如我要拉取
eclipse-temurin:8-jre
配置如下: (注意:需要开启 Actions)功能提交修改的文件,则会自动在
Actions
中创建一个 workflow。等待片刻即可。 回到阿里云容器镜像服务查看是否拉取成功最后尝试拉取到其他服务器:
[root@xxx ~]# docker pull registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
8-jre: Pulling from tzting/eclipse-temurin
eb993dcd6942: Pull complete
62ad162d7203: Pull complete
b7a2406f4d02: Pull complete
a7b7a8ecda96: Pull complete
8f36a51d249a: Pull complete
Digest: sha256:087c52e8bb5146630f2e14d650c125155449a4aadf0dc39db64632e488aeb924
Status: Downloaded newer image for registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
registry.cn-guangzhou.aliyuncs.com/xxx/eclipse-temurin:8-jre
可以看到已经能拉取到自己本地啦, 再次感谢大佬!
# 13、jstl 和 jsp-api 兼容问题
# 问题描述:
今天在搭建旧项目(不是用 maven 管理的)的时候项目正常启动了,访问 jsp 页面报错: HTTP Status 500 - java.lang.ClassNotFoundException: org.apache.jsp.index_jsp
# 影响:
项目无法正常访问。
# 解决方案:
使用的包的版本:
jsp-api :2.0
jstl:1.1.2
通过在 tomcat 的日志文件中 localhost.log
文件中找到相关的错误信息:
严重: Servlet.service () for servlet jsp threw exception
org.apache.jasper.JasperException: Unable to compile class for JSP:
An error occurred at line: [35] in the generated java file: [/home/software/java/xz_dm/apache-tomcat-7.0.81/work/Catalina/localhost/cwjk/org/apache/jsp/commons/error_jsp.java]
The method getJspApplicationContext(ServletContext) is undefined for the type JspFactory
Stacktrace:
at org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:103)
原因是:在 jsp-api 中没有找到 getJspApplicationContext(ServletContext)
这个方法,又因为该文件是通过 jstl 将 jsp 文件转成 java 文件的,所以猜测 jsp-api 版本和 jstl 版本不对应
最后在网上找到类似的问题:
JSP API 2.0 不包含该方法: getJspApplicationContext(ServletContext)
方法是在 JSP 2.1 规范中引入的。如果您的项目依赖于 JSP API 2.0,那么将无法使用这个方法。
所以升级了 JSP-API 到 2.1 版本 成功解决!
# 14、RSA 加密报错:数据长度不得超过 53 字节
# 问题描述:
在做接口数据加密时,使用了 RSA 非对称加密报错:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes
# 影响:
当需要加密的内容变多就会报错,导致无法正常加密。
# 解决方案:
有两个解决方案:
两种加密方法结合
RSA+AES 的方式
- 首先生成长度不大于 53 字节的 AES 密钥
- 通过 AES 密钥加密文本内容
- 通过 RSA 加密 AES 的密钥
RSA 切片加密
可以将待加密的内容在指定大小内容进行切片加密
- 循环文本内容到加密的大小,每次加密完一批加一个自定义特殊符号来区分是一批(后面方便解密)
- 解密的时候按照自定义的特殊字符分割来解密
具体代码:
/**
* 公钥加密
*
* @param encryptingStr
* @return
*/
public static String encryptByPublic(String encryptingStr, String publicKeyStr) {
StringBuilder encryptedDataBuilder = new StringBuilder();
try {
// Base64 解码公钥字符串
byte[] publicKeyBytes = decryptBase64(publicKeyStr);
// 构造 X509EncodedKeySpec 对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
// 初始化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
// 生成公钥对象
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// 确保公钥是 RSA 公钥
if (publicKey instanceof RSAPublicKey) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
// 获取密钥长度(以字节为单位)
int keyLength = rsaPublicKey.getModulus().bitLength() / 8;
// 计算最大加密数据长度(适用于 PKCS#1 v1.5)
// 11 字节用于填充
int maxDataLength = keyLength - 11;
// 待加密数据
byte[] data = encryptingStr.getBytes(StandardCharsets.UTF_8);
// 分割数据并逐块加密
for (int i = 0; i < data.length; i += maxDataLength) {
int chunkSize = Math.min(maxDataLength, data.length - i);
byte[] dataChunk = Arrays.copyOfRange(data, i, i + chunkSize);
// 初始化 Cipher 对象,使用 PKCS#1 填充
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 设置加密模式
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 加密数据块
byte[] encryptedDataChunk = cipher.doFinal(dataChunk);
// 将加密块的 Base64 编码添加到构建器中
if (encryptedDataBuilder.length() > 0) {
// 添加分隔符
encryptedDataBuilder.append(",");
}
encryptedDataBuilder.append(encryptBase64(encryptedDataChunk));
}
return encryptedDataBuilder.toString();
} else {
throw new InvalidKeyException("Provided key is not a RSA Public Key.");
}
} catch (Exception e) {
e.printStackTrace();
// 如果发生异常,返回 null 或错误信息
return null;
}
}
/**
* 私钥解密
* @param encryptedDataList
* @param privateKeyStr
* @return
*/
public static String decryptByPrivate(String encryptedDataList, String privateKeyStr) {
StringBuilder decryptedDataBuilder = new StringBuilder();
try {
// 根据分隔符分割加密数据块
String[] encryptedDataChunks = encryptedDataList.split(",");
for (String encryptedDataChunk : encryptedDataChunks) {
// Base64 解码加密的数据块
byte[] encryptedDataBytes = decryptBase64(encryptedDataChunk);
// 其余解密步骤与前面的 decryptByPrivate 方法相同
// Base64 解码私钥字符串
byte[] privateKeyBytes = decryptBase64(privateKeyStr);
// 构造 PKCS8EncodedKeySpec 对象
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
// 初始化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
// 生成私钥对象
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 初始化 Cipher 对象,使用 PKCS#1 填充
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// 设置解密模式
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 解密数据块
byte[] decryptedDataChunk = cipher.doFinal(encryptedDataBytes);
// 将解密数据块转换为字符串,并添加到构建器中
String decryptedDataChunkStr = new String(decryptedDataChunk, StandardCharsets.UTF_8);
if (decryptedDataBuilder.length() > 0) {
// 如果不是第一个块,则不添加分隔符
decryptedDataBuilder.append("");
}
decryptedDataBuilder.append(decryptedDataChunkStr);
}
return decryptedDataBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
// 如果发生异常,返回 null 或错误信息
return null;
}
}
# 总结
总结工作中遇到的问题及其解决方案,强调解决问题的重要性和效果。
# 参考资料
- linux LVM 和 RAID 管理命令
- https://blog.csdn.net/sinat_41836475/article/details/125408783