From 49dad1f911ef7fd7f50c1fa161d71233cdd0ed4d Mon Sep 17 00:00:00 2001 From: liuxiaoxu <1793812695@qq.com> Date: Fri, 13 Sep 2024 08:33:50 +0800 Subject: [PATCH] =?UTF-8?q?[fix]=20=E9=80=9A=E7=94=A8=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=AE=A1=E7=90=86=20=E5=85=A8=E5=B1=80=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E5=A2=9E=E5=8A=A0=20LocalDateTime?= =?UTF-8?q?=20=3D=3D>=20Date=20=E5=85=A8=E5=B1=80=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E5=A2=9E=E5=8A=A0=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20LocalDate=20=3D=3D>=20Date=20=E6=96=B0=E5=A2=9E=20Excel?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E5=A4=84=E7=90=86=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=EF=BC=9A=20value=20=E5=8D=95=E5=85=83=E6=A0=BC?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=80=BC=E3=80=81excel=E6=B3=A8=E8=A7=A3args?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E7=BB=84=E3=80=81=20cell=20=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=A0=BC=E5=AF=B9=E8=B1=A1=E3=80=81=20wb=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E7=B0=BF=E5=AF=B9=E8=B1=A1=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=B1=BB=EF=BC=9A=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=B9=E6=B3=95=E3=80=81=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E6=96=B9=E6=B3=95=E3=80=81=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=A7=E5=B0=8F=E6=A0=A1=E9=AA=8C=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E3=80=81=E8=8E=B7=E5=8F=96=E6=96=87=E4=BB=B6=E5=90=8E=E7=BC=80?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=B1=BBseq:=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=9A=E7=94=A8=E5=BA=8F=E5=88=97=E5=8F=B7?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E3=80=81=E9=BB=98=E8=AE=A416=E4=BD=8D?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8F=B7=20yyMMddHHmmss=20+=20=E4=B8=80?= =?UTF-8?q?=E4=BD=8D=E6=9C=BA=E5=99=A8=E6=A0=87=E8=AF=86=20+=203=E9=95=BF?= =?UTF-8?q?=E5=BA=A6=E5=BE=AA=E7=8E=AF=E9=80=92=E5=A2=9E=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E6=96=B9=E6=B3=95=E3=80=81=E9=80=9A=E7=94=A8=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=BA=8F=E5=88=97=E5=8F=B7=20yyMMddHHmmss=20+=20?= =?UTF-8?q?=E4=B8=80=E4=BD=8D=E6=9C=BA=E5=99=A8=E6=A0=87=E8=AF=86=20+=20le?= =?UTF-8?q?ngth=E9=95=BF=E5=BA=A6=E5=BE=AA=E7=8E=AF=E9=80=92=E5=A2=9E?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=96=B9=E6=B3=95=E3=80=81=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=BE=AA=E7=8E=AF=E9=80=92=E5=A2=9E=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2[1,=2010=20=E7=9A=84=20(length)=E5=B9=82=E6=AC=A1?= =?UTF-8?q?=E6=96=B9),=20=E7=94=A80=E5=B7=A6=E8=A1=A5=E9=BD=90length?= =?UTF-8?q?=E4=BD=8D=E6=95=B0=E6=96=B9=E6=B3=95=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E5=A4=84=E7=90=86=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=9A?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E5=A4=84=E7=90=86=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=B7=A6=E8=BE=B9=E8=A1=A5=E9=BD=900=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E4=B9=8B=E8=BE=BE=E5=88=B0=E6=8C=87=E5=AE=9A=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E3=80=82=E6=B3=A8=E6=84=8F=EF=BC=8C=E5=A6=82=E6=9E=9C=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E8=BD=AC=E6=8D=A2=E4=B8=BA=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E9=95=BF=E5=BA=A6=E5=A4=A7=E4=BA=8Esize?= =?UTF-8?q?=EF=BC=8C=E5=88=99=E5=8F=AA=E4=BF=9D=E7=95=99=20=E6=9C=80?= =?UTF-8?q?=E5=90=8Esize=E4=B8=AA=E5=AD=97=E7=AC=A6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=80=82=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=B7=A6=E8=A1=A5=E9=BD=90=E3=80=82?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E5=8E=9F=E5=A7=8B=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?s=E9=95=BF=E5=BA=A6=E5=A4=A7=E4=BA=8Esize=EF=BC=8C=E5=88=99?= =?UTF-8?q?=E5=8F=AA=E4=BF=9D=E7=95=99=E6=9C=80=E5=90=8Esize=E4=B8=AA?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E7=9A=84=E6=96=B9=E6=B3=95=E3=80=82=20?= =?UTF-8?q?=E9=80=9A=E7=94=A8excelUtil=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=9A?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=E7=94=A8=E4=BA=8EdictType=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E6=95=B0=E6=8D=AE=E5=AD=98=E5=82=A8=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E9=87=8D=E5=A4=8D=E6=9F=A5=E7=BC=93=E5=AD=98=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=BD=93=E5=89=8D=E8=A1=8C?= =?UTF-8?q?=E5=8F=B7=20=E6=96=B0=E5=A2=9E=20=E6=A0=87=E9=A2=98=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E5=90=88=E5=B9=B6=E5=90=8E=E6=9C=80=E5=90=8E?= =?UTF-8?q?=E8=A1=8C=E6=95=B0=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=90=8E=E5=BC=80=E5=A7=8B=E8=A1=8C=E6=95=B0?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E7=9A=84=E5=AD=90=E5=88=97=E8=A1=A8=E6=96=B9=E6=B3=95=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=AF=B9=E8=B1=A1=E7=9A=84?= =?UTF-8?q?=E5=AD=90=E5=88=97=E8=A1=A8=E5=B1=9E=E6=80=A7=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=E9=9C=80=E8=A6=81=E6=8E=92=E9=99=A4?= =?UTF-8?q?=E5=88=97=E5=B1=9E=E6=80=A7=E6=96=B9=E6=B3=95=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E9=9A=90=E8=97=8FExcel=E4=B8=AD=E5=88=97=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BAexcel=E7=AC=AC=E4=B8=80=E8=A1=8C=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=AD=90=E5=88=97=E8=A1=A8=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E5=AF=B9exc?= =?UTF-8?q?el=E8=A1=A8=E5=8D=95=E9=BB=98=E8=AE=A4=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=B4=A2=E5=BC=95=E5=90=8D=E8=BD=AC=E6=8D=A2=E6=88=90?= =?UTF-8?q?list=E6=96=B9=E6=B3=95=EF=BC=88=E4=B8=A4=E4=B8=AA=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=89=20=E4=BF=AE=E6=94=B9=20=E5=AF=B9excel?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E9=BB=98=E8=AE=A4=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E5=90=8D=E8=BD=AC=E6=8D=A2=E6=88=90list?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=88=E4=B8=80=E4=B8=AA=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=89=20=E4=BF=AE=E6=94=B9=20=E5=AF=B9excel=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=8C=87=E5=AE=9A=E8=A1=A8=E6=A0=BC=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E5=90=8D=E8=BD=AC=E6=8D=A2=E6=88=90list=EF=BC=88=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E5=8F=82=E6=95=B0=EF=BC=89=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E5=AF=B9list=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86=E5=85=B6?= =?UTF-8?q?=E9=87=8C=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=88=B0excel=E8=A1=A8=E5=8D=95=EF=BC=88=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=89=20=E6=96=B0=E5=A2=9E=20=E5=AF=B9lis?= =?UTF-8?q?t=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86=E5=85=B6=E9=87=8C?= =?UTF-8?q?=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=88=B0?= =?UTF-8?q?excel=E8=A1=A8=E5=8D=95=EF=BC=88=E4=B8=A4=E4=B8=AA=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=89=20=E6=96=B0=E5=A2=9E=20=E5=AF=B9list?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86=E5=85=B6=E9=87=8C=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=88=B0excel?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=EF=BC=88=E8=AE=BE=E7=BD=AE=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=A4=B4=E5=8F=82=E6=95=B0=EF=BC=89=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E5=AF=B9list=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86=E5=85=B6?= =?UTF-8?q?=E9=87=8C=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=88=B0excel=E8=A1=A8=E5=8D=95=EF=BC=88=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E7=B1=BB=E5=9E=8B=EF=BC=89=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E5=AF=B9list=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86?= =?UTF-8?q?=E5=85=B6=E9=87=8C=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=88=B0excel=E8=A1=A8=E5=8D=95=20=EF=BC=88=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E8=A1=A8=E6=A0=BC=E5=A4=B4=EF=BC=89=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E5=AF=B9list=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86?= =?UTF-8?q?=E5=85=B6=E9=87=8C=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=88=B0excel=E8=A1=A8=E5=8D=95=EF=BC=88=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=AF=BC=E5=85=A5=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=89=20=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E5=AF=B9lis?= =?UTF-8?q?t=E6=95=B0=E6=8D=AE=E6=BA=90=E5=B0=86=E5=85=B6=E9=87=8C?= =?UTF-8?q?=E9=9D=A2=E7=9A=84=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=88=B0?= =?UTF-8?q?excel=E8=A1=A8=E5=8D=95=E6=96=B9=E6=B3=95=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=86=99=E5=85=A5=E6=95=B0=E6=8D=AE=E5=88=B0Sheet=E6=96=B9?= =?UTF-8?q?=E6=B3=95=EF=BC=8C=E5=8A=A0=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8?= =?UTF-8?q?=E7=9A=84=E5=A4=84=E7=90=86=20=E4=BF=AE=E6=94=B9=20=E6=97=A7?= =?UTF-8?q?=E7=89=88=20=E5=A1=AB=E5=85=85excel=E6=95=B0=E6=8D=AE=EF=BC=8C?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=20=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=A1=A8=E6=A0=BC=E6=A0=B7=E5=BC=8F=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=E6=A0=B9=E6=8D=AEExcel=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E5=88=9B=E5=BB=BA=E8=A1=A8=E6=A0=BC=E5=A4=B4=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E6=96=B9=E6=B3=95=20=E4=BF=AE=E6=94=B9=20=E6=97=A7?= =?UTF-8?q?=E7=89=88=E6=A0=B9=E6=8D=AEExcel=E6=B3=A8=E8=A7=A3=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=A1=A8=E6=A0=BC=E5=88=97=E6=A0=B7=E5=BC=8F=20?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E6=96=B9=E6=B3=95=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E5=AF=B9=E5=AD=90=E8=A1=A8=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=20=E6=97=A7=E7=89=88=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E6=A0=B7=E5=BC=8F=EF=BC=9A=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=EF=BC=9A=E5=A6=82=E6=9E=9C=E4=B8=8B=E6=8B=89=E6=95=B0=E5=A4=A7?= =?UTF-8?q?=E4=BA=8E15=E6=88=96=E5=AD=97=E7=AC=A6=E4=B8=B2=E9=95=BF?= =?UTF-8?q?=E5=BA=A6=E5=A4=A7=E4=BA=8E255=EF=BC=8C=E5=88=99=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E4=B8=80=E4=B8=AA=E6=96=B0sheet=E5=AD=98=E5=82=A8?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E7=94=9F=E6=88=90=E7=9A=84=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=8B=E6=8B=89=E5=80=BC=E8=8E=B7=E5=8F=96=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=EF=BC=9B=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF=E6=88=96?= =?UTF-8?q?=E5=8F=AA=E8=83=BD=E9=80=89=E6=8B=A9=E4=B8=8D=E8=83=BD=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E7=9A=84=E5=88=97=E5=86=85=E5=AE=B9=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20=E6=97=A7=E7=89=88=E6=B7=BB=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=A0=BC=EF=BC=8C=E5=8A=A0=E5=85=A5=E5=AF=B9=E5=AD=90?= =?UTF-8?q?=E8=A1=A8=E7=9A=84=E5=A4=84=E7=90=86=20=E4=BF=AE=E6=94=B9=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=20POI=20XSSFSheet=20=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E6=8F=90=E7=A4=BA=E6=88=96=E9=80=89=E6=8B=A9=E6=A1=86?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=BA=86=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=88=99=E9=BC=A0=E6=A0=87=E6=94=BE=E4=B8=8A=E5=8E=BB=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=EF=BC=9B=E5=A4=84=E7=90=86Excel=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E9=97=AE=E9=A2=98=20=E4=BF=AE=E6=94=B9=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E6=9F=90=E4=BA=9B=E5=88=97=E7=9A=84=E5=80=BC=E5=8F=AA?= =?UTF-8?q?=E8=83=BD=E8=BE=93=E5=85=A5=E9=A2=84=E5=88=B6=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE,=E6=98=BE=E7=A4=BA=E4=B8=8B=E6=8B=89=E6=A1=86?= =?UTF-8?q?=EF=BC=88=E5=85=BC=E5=AE=B9=E8=B6=85=E5=87=BA=E4=B8=80=E5=AE=9A?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E7=9A=84=E4=B8=8B=E6=8B=89=E6=A1=86=EF=BC=89?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=9A=E6=96=B0=E5=A2=9E=20=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E5=88=9B=E5=BB=BA=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E8=A2=AB=E5=85=B6=E4=BB=96=E5=8D=95=E5=85=83=E6=A0=BC=E5=BC=95?= =?UTF-8?q?=E7=94=A8=EF=BC=9B=E6=96=B0=E5=A2=9E=20=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E4=B8=8B=E6=8B=89=E5=88=97=E8=A1=A8=E5=86=85=E5=AE=B9=EF=BC=9B?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A6=82=E6=9E=9C=E8=AE=BE=E7=BD=AE=E4=BA=86?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF=E5=88=99=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E6=94=BE=E4=B8=8A=E5=8E=BB=E6=8F=90=E7=A4=BA=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E8=AE=BE=E7=BD=AEhiddenSheet=E9=9A=90=E8=97=8F=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=99=A8=E6=96=B9=E6=B3=95=EF=BC=8C=E5=A4=84=E7=90=86=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E6=95=B0=E6=8D=AE=20=E4=BF=AE=E6=94=B9=20?= =?UTF-8?q?=E6=97=A7=E7=89=88=E5=88=9B=E5=BB=BA=E7=BB=9F=E8=AE=A1=E8=A1=8C?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=9A=E6=96=B0=E5=A2=9E=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E5=8D=95=E5=85=83=E6=A0=BC=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E5=88=A4=E6=96=AD=20=E6=96=B0=E5=A2=9E=20=E5=BE=97=E5=88=B0?= =?UTF-8?q?=E6=89=80=E6=9C=89=E5=AE=9A=E4=B9=89=E5=AD=97=E6=AE=B5=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E8=8E=B7=E5=8F=96=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=B3=A8=E8=A7=A3=E4=BF=A1=E6=81=AF=E6=96=B9=E6=B3=95?= =?UTF-8?q?=EF=BC=9A=E5=88=86=E5=8D=95=E6=B3=A8=E8=A7=A3=E5=92=8C=E5=A4=9A?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=20=E4=BF=AE=E6=94=B9=20=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E8=8E=B7=E5=8F=96=E6=9C=80=E5=A4=A7=E8=A1=8C?= =?UTF-8?q?=E9=AB=98=E6=96=B9=E6=B3=95=EF=BC=9A=E9=87=87=E7=94=A8=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E7=BB=9F=E8=AE=A1=E6=96=B9=E6=B3=95=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20=E5=88=9B=E5=BB=BA=E4=B8=80=E4=B8=AA=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E7=B0=BF=E6=96=B9=E6=B3=95=EF=BC=8C=E9=87=87=E7=94=A8?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=88=9B=E5=BB=BA=E5=8D=95=E5=85=83=E6=A0=BC?= =?UTF-8?q?=E5=92=8C=E8=A1=A8=E5=A4=B4=E6=96=B9=E6=B3=95=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E4=BF=AE=E6=94=B9=E5=88=9B=E5=BB=BA=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E8=A1=A8=E6=96=B9=E6=B3=95=EF=BC=8C=E9=87=87=E7=94=A8?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=88=9B=E5=BB=BA=E5=8D=95=E5=85=83=E6=A0=BC?= =?UTF-8?q?=E5=92=8C=E8=A1=A8=E5=A4=B4=E6=96=B9=E6=B3=95=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E8=8E=B7=E5=8F=96Excel2003=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E8=8E=B7=E5=8F=96Exc?= =?UTF-8?q?el2007=E5=9B=BE=E7=89=87=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=B8=8D=E5=90=8C=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E6=97=A5=E6=9C=9F=E5=AF=B9=E8=B1=A1=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E6=98=AF=E5=90=A6=E6=9C=89?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=AD=90=E5=88=97=E8=A1=A8=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20=E6=96=B0=E5=A2=9E=20=E6=98=AF=E5=90=A6=E6=9C=89?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=AD=90=E5=88=97=E8=A1=A8=EF=BC=8C?= =?UTF-8?q?=E9=9B=86=E5=90=88=E4=B8=8D=E4=B8=BA=E7=A9=BA=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=20=E8=8E=B7=E5=8F=96=E9=9B=86=E5=90=88?= =?UTF-8?q?=E7=9A=84=E5=80=BC=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=AD=90=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Excel接口类: 新增 导出时在excel中每个列的高度抽象方法 新增 导出时在excel中每个列的宽度抽象方法 新增 是否需要纵向合并单元格,应对需求:含有list集合单元格)抽象方法 新增 导出类型(0数字 1字符串 2图片)抽象方法 新增 导出列头背景颜色抽象方法 新增 导出列头字体颜色抽象方法 新增 导出单元格背景颜色抽象方法 新增 导出单元格字体颜色抽象方法 新增 导出字段对齐方式抽象方法 新增 自定义数据处理器抽象方法 新增 自定义数据处理器参数抽象方法 若依全局配置类:新增 获取导入上传路径方法 --- .../com/ruoyi/common/annotation/Excel.java | 70 +- .../com/ruoyi/common/config/RuoYiConfig.java | 10 +- .../com/ruoyi/common/utils/DateUtils.java | 20 + .../com/ruoyi/common/utils/StringUtils.java | 50 + .../common/utils/file/FileUploadUtils.java | 48 +- .../ruoyi/common/utils/file/FileUtils.java | 132 +- .../common/utils/poi/ExcelHandlerAdapter.java | 24 + .../com/ruoyi/common/utils/poi/ExcelUtil.java | 1098 +++++++++++++---- .../java/com/ruoyi/common/utils/uuid/Seq.java | 87 ++ 9 files changed, 1245 insertions(+), 294 deletions(-) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java index b09f4cec..8f885f34 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java @@ -5,10 +5,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.math.BigDecimal; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import com.ruoyi.common.utils.poi.ExcelHandlerAdapter; /** * 自定义导出Excel数据注解 - * + * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @@ -56,17 +59,12 @@ public @interface Excel public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; /** - * 导出类型(0数字 1字符串) - */ - public ColumnType cellType() default ColumnType.STRING; - - /** - * 导出时在excel中每个列的高度 单位为字符 + * 导出时在excel中每个列的高度 */ public double height() default 14; /** - * 导出时在excel中每个列的宽 单位为字符 + * 导出时在excel中每个列的宽度 */ public double width() default 16; @@ -90,6 +88,11 @@ public @interface Excel */ public String[] combo() default {}; + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + /** * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. */ @@ -106,25 +109,44 @@ public @interface Excel public boolean isStatistics() default false; /** - * 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右) + * 导出类型(0数字 1字符串 2图片) */ - Align align() default Align.AUTO; + public ColumnType cellType() default ColumnType.STRING; - public enum Align - { - AUTO(0), LEFT(1), CENTER(2), RIGHT(3); - private final int value; + /** + * 导出列头背景颜色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; - Align(int value) - { - this.value = value; - } + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; - public int value() - { - return this.value; - } - } + /** + * 导出单元格背景颜色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; /** * 字段类型(0:导出导入;1:仅导出;2:仅导入) @@ -149,7 +171,7 @@ public @interface Excel public enum ColumnType { - NUMERIC(0), STRING(1), IMAGE(2); + NUMERIC(0), STRING(1), IMAGE(2), TEXT(3); private final int value; ColumnType(int value) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java index fda72829..bce628d9 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Component; /** * 全局配置类 - * + * * @author ruoyi */ @Component @@ -90,6 +90,14 @@ public class RuoYiConfig RuoYiConfig.addressEnabled = addressEnabled; } + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + /** * 获取头像上传路径 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java index dc1c0700..13f69dcb 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -3,6 +3,7 @@ package com.ruoyi.common.utils; import java.lang.management.ManagementFactory; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.*; import java.util.Date; import org.apache.commons.lang3.time.DateFormatUtils; @@ -160,4 +161,23 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils // long sec = diff % nd % nh % nm / ns; return day + "天" + hour + "小时" + min + "分钟"; } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) + { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) + { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java index 597fef36..7241ae22 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -405,4 +405,54 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { return (T) obj; } + + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java index b298dd98..95424b10 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -1,5 +1,11 @@ package com.ruoyi.common.utils.file; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; @@ -7,16 +13,11 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException; import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.uuid.IdUtils; -import org.apache.commons.io.FilenameUtils; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; +import com.ruoyi.common.utils.uuid.Seq; /** * 文件上传工具类 - * + * * @author ruoyi */ public class FileUploadUtils @@ -101,8 +102,8 @@ public class FileUploadUtils throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { - int fileNamelength = file.getOriginalFilename().length(); - if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } @@ -111,10 +112,9 @@ public class FileUploadUtils String fileName = extractFilename(file); - File desc = getAbsoluteFile(baseDir, fileName); - file.transferTo(desc); - String pathFileName = getPathFileName(baseDir, fileName); - return pathFileName; + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); } /** @@ -122,15 +122,11 @@ public class FileUploadUtils */ public static final String extractFilename(MultipartFile file) { - String fileName = file.getOriginalFilename(); - - fileName = file.getOriginalFilename().substring(0,fileName.lastIndexOf(".")); - String extension = getExtension(file); - fileName = DateUtils.datePath() + "/" + fileName+"-"+IdUtils.fastUUID() + "." + extension; - return fileName; + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } - private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { File desc = new File(uploadDir + File.separator + fileName); @@ -144,13 +140,11 @@ public class FileUploadUtils return desc; } - private static final String getPathFileName(String uploadDir, String fileName) throws IOException + public static final String getPathFileName(String uploadDir, String fileName) throws IOException { int dirLastIndex = RuoYiConfig.getProfile().length() + 1; String currentDir = StringUtils.substring(uploadDir, dirLastIndex); -// String pathFileName = "/" + fileName; - String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; - return pathFileName; + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; } /** @@ -165,7 +159,7 @@ public class FileUploadUtils throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); - if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) + if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } @@ -222,7 +216,7 @@ public class FileUploadUtils /** * 获取文件名的后缀 - * + * * @param file 表单文件 * @return 后缀名 */ @@ -231,7 +225,7 @@ public class FileUploadUtils String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { - extension = MimeTypeUtils.getExtension(file.getContentType()); + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java index acdb9794..103252b6 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -3,6 +3,7 @@ package com.ruoyi.common.utils.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; @@ -10,21 +11,26 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; /** * 文件处理工具类 - * + * * @author ruoyi */ -public class FileUtils extends org.apache.commons.io.FileUtils +public class FileUtils { public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; /** * 输出指定文件的byte数组 - * + * * @param filePath 文件路径 * @param os 输出流 * @return @@ -53,34 +59,53 @@ public class FileUtils extends org.apache.commons.io.FileUtils } finally { - if (os != null) - { - try - { - os.close(); - } - catch (IOException e1) - { - e1.printStackTrace(); - } - } - if (fis != null) - { - try - { - fis.close(); - } - catch (IOException e1) - { - e1.printStackTrace(); - } - } + IOUtils.close(os); + IOUtils.close(fis); } } + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException + { + return writeBytes(data, RuoYiConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException + { + FileOutputStream fos = null; + String pathName = ""; + try + { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } + finally + { + IOUtils.close(fos); + } + return FileUploadUtils.getPathFileName(uploadDir, pathName); + } + /** * 删除文件 - * + * * @param filePath 文件 * @return */ @@ -91,15 +116,14 @@ public class FileUtils extends org.apache.commons.io.FileUtils // 路径为文件且不为空则进行删除 if (file.isFile() && file.exists()) { - file.delete(); - flag = true; + flag = file.delete(); } return flag; } /** * 文件名称验证 - * + * * @param filename 文件名称 * @return true 正常 false 非法 */ @@ -110,7 +134,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils /** * 检查文件是否可下载 - * + * * @param resource 需要下载的文件 * @return true 正常 false 非法 */ @@ -134,7 +158,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils /** * 下载文件名重新编码 - * + * * @param request 请求对象 * @param fileName 文件名 * @return 编码后的文件名 @@ -201,6 +225,35 @@ public class FileUtils extends org.apache.commons.io.FileUtils return encode.replaceAll("\\+", "%20"); } + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "gif"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "jpg"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "bmp"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "png"; + } + return strFileExtendName; + } + /** * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png * @@ -218,4 +271,21 @@ public class FileUtils extends org.apache.commons.io.FileUtils int index = Math.max(lastUnixPos, lastWindowsPos); return fileName.substring(index + 1); } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } } + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 00000000..ccab2881 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.poi; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * @param cell 单元格对象 + * @param wb 工作簿对象 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args, Cell cell, Workbook wb); +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java index d2656cee..3e2e5731 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -6,10 +6,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -18,11 +23,23 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationHelper; @@ -32,15 +49,25 @@ import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.annotation.Excel; @@ -50,23 +77,33 @@ import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.text.Convert; -import com.ruoyi.common.exception.BusinessException; +import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileTypeUtils; +import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.ImageUtils; import com.ruoyi.common.utils.reflect.ReflectUtils; /** * Excel相关处理 - * + * * @author ruoyi */ public class ExcelUtil { private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map sysDictMap = new HashMap(); + /** * Excel sheet最大行数,默认65536 */ @@ -107,32 +144,78 @@ public class ExcelUtil */ private List fields; + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + /** * 最大高度 */ private short maxHeight; + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + /** * 统计列表 */ private Map statistics = new HashMap(); - + /** * 数字格式 */ private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); - + /** * 实体对象 */ public Class clazz; + /** + * 需要排除列属性 + */ + public String[] excludeFields; + public ExcelUtil(Class clazz) { this.clazz = clazz; } - public void init(List list, String sheetName, Type type) + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + * @throws Exception + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) { if (list == null) { @@ -141,59 +224,139 @@ public class ExcelUtil this.list = list; this.sheetName = sheetName; this.type = type; + this.title = title; createExcelField(); createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + Row subRow = sheet.createRow(rownum); + int excelNum = 0; + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Cell headCell1 = subRow.createCell(excelNum); + headCell1.setCellValue(attr.name()); + headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + excelNum++; + } + int headFirstRow = excelNum - 1; + int headLastRow = headFirstRow + subFields.size() - 1; + if (headLastRow > headFirstRow) + { + sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); + } + rownum++; + } } /** * 对excel表单默认第一个索引名转换成list - * + * * @param is 输入流 * @return 转换后集合 */ - public List importExcel(InputStream is) throws Exception + public List importExcel(InputStream is) { - return importExcel(StringUtils.EMPTY, is); + List list = null; + try + { + list = importExcel(is, 0); + } + catch (Exception e) + { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } + finally + { + IOUtils.closeQuietly(is); + } + return list; + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); } /** * 对excel表单指定表格索引名转换成list - * + * * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 * @param is 输入流 * @return 转换后集合 */ - public List importExcel(String sheetName, InputStream is) throws Exception + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception { this.type = Type.IMPORT; this.wb = WorkbookFactory.create(is); List list = new ArrayList(); - Sheet sheet = null; - if (StringUtils.isNotEmpty(sheetName)) + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) { - // 如果指定sheet名,则取指定sheet中的内容. - sheet = wb.getSheet(sheetName); + throw new IOException("文件sheet不存在"); } - else + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map pictures; + if (isXSSFWorkbook) { - // 如果传入的sheet名不存在则默认指向第1个sheet. - sheet = wb.getSheetAt(0); + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); } - - if (sheet == null) + else { - throw new IOException("文件sheet不存在"); + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); } - // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 int rows = sheet.getLastRowNum(); - if (rows > 0) { // 定义一个map用于存放excel列的序号和field. Map cellMap = new HashMap(); // 获取表头 - Row heard = sheet.getRow(0); + Row heard = sheet.getRow(titleNum); for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) { Cell cell = heard.getCell(i); @@ -208,25 +371,18 @@ public class ExcelUtil } } // 有数据时才处理 得到类的所有field. - Field[] allFields = clazz.getDeclaredFields(); - // 定义一个map用于存放列的序号和field. - Map fieldsMap = new HashMap(); - for (int col = 0; col < allFields.length; col++) + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) { - Field field = allFields[col]; - Excel attr = field.getAnnotation(Excel.class); - if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) { - // 设置类的私有字段属性可访问. - field.setAccessible(true); - Integer column = cellMap.get(attr.name()); - if (column != null) - { - fieldsMap.put(column, field); - } + fieldsMap.put(column, objects); } } - for (int i = 1; i <= rows; i++) + for (int i = titleNum + 1; i <= rows; i++) { // 从第2行开始取数据,默认第一行是表头. Row row = sheet.getRow(i); @@ -236,14 +392,15 @@ public class ExcelUtil continue; } T entity = null; - for (Map.Entry entry : fieldsMap.entrySet()) + for (Map.Entry entry : fieldsMap.entrySet()) { Object val = this.getCellValue(row, entry.getKey()); // 如果不存在实例则新建. entity = (entity == null ? clazz.newInstance() : entity); // 从map中得到对应列的field. - Field field = fieldsMap.get(entry.getKey()); + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; // 取得类型,并根据对象类型设置值. Class fieldType = field.getType(); if (String.class == fieldType) @@ -258,7 +415,7 @@ public class ExcelUtil String dateFormat = field.getAnnotation(Excel.class).dateFormat(); if (StringUtils.isNotEmpty(dateFormat)) { - val = DateUtils.parseDateToStr(dateFormat, (Date) val); + val = parseDateToStr(dateFormat, val); } else { @@ -270,7 +427,7 @@ public class ExcelUtil { val = Convert.toInt(val); } - else if (Long.TYPE == fieldType || Long.class == fieldType) + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toLong(val); } @@ -303,19 +460,40 @@ public class ExcelUtil } if (StringUtils.isNotNull(fieldType)) { - Excel attr = field.getAnnotation(Excel.class); String propertyName = field.getName(); if (StringUtils.isNotEmpty(attr.targetAttr())) { propertyName = field.getName() + "." + attr.targetAttr(); } - else if (StringUtils.isNotEmpty(attr.readConverterExp())) + if (StringUtils.isNotEmpty(attr.readConverterExp())) { val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); } else if (StringUtils.isNotEmpty(attr.dictType())) { - val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + if (!sysDictMap.containsKey(attr.dictType() + val)) + { + String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + sysDictMap.put(attr.dictType() + val, dictValue); + } + val = sysDictMap.get(attr.dictType() + val); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr, null); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); + if (image == null) + { + val = ""; + } + else + { + byte[] data = image.getData(); + val = FileUtils.writeImportBytes(data); + } } ReflectUtils.invokeSetter(entity, propertyName, val); } @@ -328,32 +506,135 @@ public class ExcelUtil /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @param list 导出数据集合 * @param sheetName 工作表的名称 * @return 结果 */ public AjaxResult exportExcel(List list, String sheetName) { - this.init(list, sheetName, Type.EXPORT); + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); return exportExcel(); } /** * 对list数据源将其里面的数据导入到excel表单 - * + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * * @param sheetName 工作表的名称 * @return 结果 */ public AjaxResult importTemplateExcel(String sheetName) { - this.init(null, sheetName, Type.IMPORT); + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); return exportExcel(); } /** * 对list数据源将其里面的数据导入到excel表单 - * + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * * @return 结果 */ public AjaxResult exportExcel() @@ -361,27 +642,7 @@ public class ExcelUtil OutputStream out = null; try { - // 取出一共有多少个sheet. - double sheetNo = Math.ceil(list.size() / sheetSize); - for (int index = 0; index <= sheetNo; index++) - { - createSheet(sheetNo, index); - - // 产生一行 - Row row = sheet.createRow(0); - int column = 0; - // 写入各个字段的列头名称 - for (Object[] os : fields) - { - Excel excel = (Excel) os[1]; - this.createCell(excel, row, column++); - } - if (Type.EXPORT.equals(type)) - { - fillExcelData(index, row); - addStatisticsRow(); - } - } + writeSheet(); // String filename = encodingFilename(sheetName); String filename = sheetName + ".xlsx"; out = new FileOutputStream(getAbsoluteFile(filename)); @@ -391,65 +652,129 @@ public class ExcelUtil catch (Exception e) { log.error("导出Excel异常{}", e.getMessage()); - throw new BusinessException("导出Excel失败,请联系网站管理员!"); + throw new UtilException("导出Excel失败,请联系网站管理员!"); } finally { - if (wb != null) + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) { - try + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) { - wb.close(); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } } - catch (IOException e1) + else { - e1.printStackTrace(); + this.createHeadCell(excel, row, column++); } } - if (out != null) + if (Type.EXPORT.equals(type)) { - try - { - out.close(); - } - catch (IOException e1) - { - e1.printStackTrace(); - } + fillExcelData(index, row); + addStatisticsRow(); } } } /** * 填充excel数据 - * + * * @param index 序号 * @param row 单元格行 */ + @SuppressWarnings("unchecked") public void fillExcelData(int index, Row row) { int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); + int rowNo = (1 + rownum) - startNo; for (int i = startNo; i < endNo; i++) { - row = sheet.createRow(i + 1 - startNo); + rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; + row = sheet.createRow(rowNo); // 得到导出对象. T vo = (T) list.get(i); + Collection subList = null; + if (isSubList()) + { + if (isSubListValue(vo)) + { + subList = getListCellValue(vo); + subMergedLastRowNum = subMergedLastRowNum + subList.size(); + } + else + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + } + } int column = 0; for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; - // 设置实体类私有属性可访问 - field.setAccessible(true); - this.addCell(excel, row, vo, field, column++); + if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) + { + boolean subFirst = false; + for (Object obj : subList) + { + if (subFirst) + { + rowNo++; + row = sheet.createRow(rowNo); + } + List subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); + int subIndex = 0; + for (Field subField : subFields) + { + if (subField.isAnnotationPresent(Excel.class)) + { + subField.setAccessible(true); + Excel attr = subField.getAnnotation(Excel.class); + this.addCell(attr, row, (T) obj, subField, column + subIndex); + } + subIndex++; + } + subFirst = true; + } + this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); + } + else + { + this.addCell(excel, row, vo, field, column++); + } } } } /** * 创建表格样式 - * + * * @param wb 工作薄对象 * @return 样式列表 */ @@ -460,6 +785,18 @@ public class ExcelUtil CellStyle style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); @@ -474,20 +811,6 @@ public class ExcelUtil style.setFont(dataFont); styles.put("data", style); - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(HorizontalAlignment.CENTER); - style.setVerticalAlignment(VerticalAlignment.CENTER); - style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setFillPattern(FillPatternType.SOLID_FOREGROUND); - Font headerFont = wb.createFont(); - headerFont.setFontName("Arial"); - headerFont.setFontHeightInPoints((short) 10); - headerFont.setBold(true); - headerFont.setColor(IndexedColors.WHITE.getIndex()); - style.setFont(headerFont); - styles.put("header", style); - style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); @@ -497,50 +820,165 @@ public class ExcelUtil style.setFont(totalFont); styles.put("total", style); - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(HorizontalAlignment.LEFT); - styles.put("data1", style); + styles.putAll(annotationHeaderStyles(wb, styles)); - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(HorizontalAlignment.CENTER); - styles.put("data2", style); + styles.putAll(annotationDataStyles(wb)); - style = wb.createCellStyle(); - style.cloneStyleFrom(styles.get("data")); - style.setAlignment(HorizontalAlignment.RIGHT); - styles.put("data3", style); + return styles; + } + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + // 设置表格头单元格文本形式 + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + annotationDataStyles(styles, subField, subExcel); + } + } + else + { + annotationDataStyles(styles, field, excel); + } + } return styles; } + /** + * 根据Excel注解创建表格列样式 + * + * @param styles 自定义样式列表 + * @param field 属性列信息 + * @param excel 注解信息 + */ + public void annotationDataStyles(Map styles, Field field, Excel excel) + { + String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + if (ColumnType.TEXT == excel.cellType()) + { + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + } + styles.put(key, style); + } + } + /** * 创建单元格 */ - public Cell createCell(Excel attr, Row row, int column) + public Cell createHeadCell(Excel attr, Row row, int column) { // 创建列 Cell cell = row.createCell(column); // 写入列信息 cell.setCellValue(attr.name()); setDataValidation(attr, row, column); - cell.setCellStyle(styles.get("header")); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } return cell; } /** * 设置单元格信息 - * + * * @param value 单元格值 * @param attr 注解相关 * @param cell 单元格信息 */ public void setCellVo(Object value, Excel attr, Cell cell) { - if (ColumnType.STRING == attr.cellType()) + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) { - cell.setCellValue(StringUtils.isNull(value) ? attr.defaultValue() : value + attr.suffix()); + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); } else if (ColumnType.NUMERIC == attr.cellType()) { @@ -551,8 +989,7 @@ public class ExcelUtil } else if (ColumnType.IMAGE == attr.cellType()) { - ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), - cell.getRow().getRowNum() + 1); + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); String imagePath = Convert.toStr(value); if (StringUtils.isNotEmpty(imagePath)) { @@ -562,7 +999,7 @@ public class ExcelUtil } } } - + /** * 获取画布 */ @@ -606,17 +1043,18 @@ public class ExcelUtil // 设置列宽 sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); } - // 如果设置了提示信息则鼠标放上去提示. - if (StringUtils.isNotEmpty(attr.prompt())) + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) { - // 这里默认设了2-101列提示. - setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column); - } - // 如果设置了combo属性则本列只能选择不能输入 - if (attr.combo().length > 0) - { - // 这里默认设了2-101列只能选择不能输入. - setXSSFValidation(sheet, attr.combo(), 1, 100, column, column); + if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } } } @@ -635,8 +1073,12 @@ public class ExcelUtil { // 创建cell cell = row.createCell(column); - int align = attr.align().value(); - cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : ""))); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); + sheet.addMergedRegion(cellAddress); + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); // 用于读取对象中的属性 Object value = getTargetValue(vo, field, attr); @@ -646,7 +1088,7 @@ public class ExcelUtil String dictType = attr.dictType(); if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) { - cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value)); + cell.setCellValue(parseDateToStr(dateFormat, value)); } else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) { @@ -654,11 +1096,20 @@ public class ExcelUtil } else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) { - cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator)); + if (!sysDictMap.containsKey(dictType + value)) + { + String lable = convertDictByExp(Convert.toStr(value), dictType, separator); + sysDictMap.put(dictType + value, lable); + } + cell.setCellValue(sysDictMap.get(dictType + value)); } else if (value instanceof BigDecimal && -1 != attr.scale()) { - cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString()); + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); } else { @@ -676,48 +1127,78 @@ public class ExcelUtil } /** - * 设置 POI XSSFSheet 单元格提示 - * + * 设置 POI XSSFSheet 单元格提示或选择框 + * * @param sheet 表单 - * @param promptTitle 提示标题 + * @param textlist 下拉框显示的内容 * @param promptContent 提示内容 * @param firstRow 开始行 * @param endRow 结束行 * @param firstCol 开始列 * @param endCol 结束列 */ - public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow, - int firstCol, int endCol) + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) { DataValidationHelper helper = sheet.getDataValidationHelper(); - DataValidationConstraint constraint = helper.createCustomConstraint("DD1"); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); DataValidation dataValidation = helper.createValidation(constraint, regions); - dataValidation.createPromptBox(promptTitle, promptContent); - dataValidation.setShowPromptBox(true); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } sheet.addValidationData(dataValidation); } /** - * 设置某些列的值只能输入预制的数据,显示下拉框. - * + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * * @param sheet 要设置的sheet. * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 * @param firstRow 开始行 * @param endRow 结束行 * @param firstCol 开始列 * @param endCol 结束列 - * @return 设置好的sheet. */ - public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); DataValidationHelper helper = sheet.getDataValidationHelper(); // 加载下拉列表内容 - DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist); + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 数据有效性对象 DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } // 处理Excel兼容性问题 if (dataValidation instanceof XSSFDataValidation) { @@ -730,11 +1211,13 @@ public class ExcelUtil } sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); } /** * 解析导出值 0=男,1=女,2=未知 - * + * * @param propertyValue 参数值 * @param converterExp 翻译注解 * @param separator 分隔符 @@ -747,7 +1230,7 @@ public class ExcelUtil for (String item : convertSource) { String[] itemArray = item.split("="); - if (StringUtils.containsAny(separator, propertyValue)) + if (StringUtils.containsAny(propertyValue, separator)) { for (String value : propertyValue.split(separator)) { @@ -771,7 +1254,7 @@ public class ExcelUtil /** * 反向解析值 男=0,女=1,未知=2 - * + * * @param propertyValue 参数值 * @param converterExp 翻译注解 * @param separator 分隔符 @@ -784,7 +1267,7 @@ public class ExcelUtil for (String item : convertSource) { String[] itemArray = item.split("="); - if (StringUtils.containsAny(separator, propertyValue)) + if (StringUtils.containsAny(propertyValue, separator)) { for (String value : propertyValue.split(separator)) { @@ -805,10 +1288,10 @@ public class ExcelUtil } return StringUtils.stripEnd(propertyString.toString(), separator); } - + /** * 解析字典值 - * + * * @param dictValue 字典值 * @param dictType 字典类型 * @param separator 分隔符 @@ -821,7 +1304,7 @@ public class ExcelUtil /** * 反向解析值字典值 - * + * * @param dictLabel 字典标签 * @param dictType 字典类型 * @param separator 分隔符 @@ -831,7 +1314,29 @@ public class ExcelUtil { return DictUtils.getDictValue(dictType, dictLabel, separator); } - + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + /** * 合计统计信息 */ @@ -862,13 +1367,12 @@ public class ExcelUtil { if (statistics.size() > 0) { - Cell cell = null; Row row = sheet.createRow(sheet.getLastRowNum() + 1); Set keys = statistics.keySet(); - cell = row.createCell(0); + Cell cell = row.createCell(0); cell.setCellStyle(styles.get("total")); cell.setCellValue("合计"); - + for (Integer key : keys) { cell = row.createCell(key); @@ -884,13 +1388,13 @@ public class ExcelUtil */ public String encodingFilename(String filename) { - filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx"; + filename = UUID.randomUUID() + "_" + filename + ".xlsx"; return filename; } /** * 获取下载路径 - * + * * @param filename 文件名称 */ public String getAbsoluteFile(String filename) @@ -906,7 +1410,7 @@ public class ExcelUtil /** * 获取bean中的属性值 - * + * * @param vo 实体对象 * @param field 字段 * @param excel 注解 @@ -919,7 +1423,7 @@ public class ExcelUtil if (StringUtils.isNotEmpty(excel.targetAttr())) { String target = excel.targetAttr(); - if (target.indexOf(".") > -1) + if (target.contains(".")) { String[] targets = target.split("[.]"); for (String name : targets) @@ -937,7 +1441,7 @@ public class ExcelUtil /** * 以类的属性的get方法方法形式获取值 - * + * * @param o * @param name * @return value @@ -960,33 +1464,62 @@ public class ExcelUtil */ private void createExcelField() { - this.fields = new ArrayList(); + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); List tempFields = new ArrayList<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); for (Field field : tempFields) { - // 单注解 - if (field.isAnnotationPresent(Excel.class)) + if (!ArrayUtils.contains(this.excludeFields, field.getName())) { - putToField(field, field.getAnnotation(Excel.class)); - } + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } - // 多注解 - if (field.isAnnotationPresent(Excels.class)) - { - Excels attrs = field.getAnnotation(Excels.class); - Excel[] excels = attrs.value(); - for (Excel excel : excels) + // 多注解 + if (field.isAnnotationPresent(Excels.class)) { - putToField(field, excel); + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + } } } } - this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); - this.maxHeight = getRowHeight(); + return fields; } - + /** * 根据注解获取最大行高 */ @@ -996,54 +1529,42 @@ public class ExcelUtil for (Object[] os : this.fields) { Excel excel = (Excel) os[1]; - maxHeight = maxHeight > excel.height() ? maxHeight : excel.height(); + maxHeight = Math.max(maxHeight, excel.height()); } return (short) (maxHeight * 20); } - /** - * 放到字段集合中 - */ - private void putToField(Field field, Excel attr) - { - if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) - { - this.fields.add(new Object[] { field, attr }); - } - } - /** * 创建一个工作簿 */ public void createWorkbook() { this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); } /** * 创建工作表 - * + * * @param sheetNo sheet数量 * @param index 序号 */ - public void createSheet(double sheetNo, int index) + public void createSheet(int sheetNo, int index) { - this.sheet = wb.createSheet(); - this.styles = createStyles(wb); // 设置工作表的名称. - if (sheetNo == 0) - { - wb.setSheetName(index, sheetName); - } - else + if (sheetNo > 1 && index > 0) { + this.sheet = wb.createSheet(); + this.createTitle(); wb.setSheetName(index, sheetName + index); } } /** * 获取单元格值 - * + * * @param row 获取的行 * @param column 获取单元格列号 * @return 单元格值 @@ -1100,10 +1621,10 @@ public class ExcelUtil } return val; } - + /** * 判断是否是空行 - * + * * @param row 判断的行 * @return */ @@ -1113,7 +1634,7 @@ public class ExcelUtil { return true; } - for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { Cell cell = row.getCell(i); if (cell != null && cell.getCellType() != CellType.BLANK) @@ -1123,4 +1644,159 @@ public class ExcelUtil } return true; } -} \ No newline at end of file + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty()) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + int pictureIndex = pic.getPictureIndex() - 1; + HSSFPictureData picData = pictures.get(pictureIndex); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.put(picIndex, picData); + } + } + return sheetIndexPicMap; + } + else + { + return sheetIndexPicMap; + } + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.put(picIndex, pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java new file mode 100644 index 00000000..14d324df --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java @@ -0,0 +1,87 @@ +package com.ruoyi.common.utils.uuid; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author ruoyi 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +}