From 5dde003ab3a7fd44cdbef5145f8f4e0e9f5b14f8 Mon Sep 17 00:00:00 2001 From: genggengtang Date: Fri, 27 Mar 2026 18:03:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AE=A1=E8=AE=A1=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/尾页.docx | Bin 0 -> 13081 bytes .../ai/controller/AiHistoryController.java | 34 +- .../ai/controller/AuditReportController.java | 793 ++++++++++++++++-- .../gxwebsoft/ai/enums/AuditReportEnum.java | 11 +- .../ai/service/AiHistoryService.java | 13 +- .../ai/service/AuditReportService.java | 9 +- .../ai/service/impl/AiHistoryServiceImpl.java | 13 + .../service/impl/AuditReportServiceImpl.java | 111 ++- 8 files changed, 866 insertions(+), 118 deletions(-) create mode 100644 doc/尾页.docx diff --git a/doc/尾页.docx b/doc/尾页.docx new file mode 100644 index 0000000000000000000000000000000000000000..92a20b39623ed7873330e9cb0f700c06f504ca0d GIT binary patch literal 13081 zcmb7qWmsLw((cCH-GaNjy9Rf6cL?qpT!ItaJ-9mrcXxMp3lQLvnRCv}h!kj9!$T_&Ndo;b4 zhFT4Rmk-VoU`O?h85-_C>xB1{3*pE0zsWnG7k)wpCupV$;u-&LQyt!KppO?%T9)`( z!wn2){OATn1E-*y1BmSsX?pzoFek6*;59V1sZnQ=kE48{!MK#8$gw>lZc=y_b`1O$ z2^L`!M+|8~YzIWJ&i3u;D~=}B`t?%fb}26dDHuzODg2>$gwjBP1Dv31^0y@<8X2f# z@EWDUIu^oW90^^0y{6gl=f@^G2C&7$Ms*wp7|0!U^hfQaJ6Rgtb^qJV;@g&Q#RY5E7=F&I;E`Sn5 zGS`JENd{|4o7iSX<(#O~S1HdC;I=o{{D8l=DzB^6R9z2rLfS+TQS zD45-ff~~(hq5@#jyEV{(zojXCLOvzd8aZA;TK3@RYaA5mF5>`%nrkP}xkf!LzupF) z?mCW~@9b!v;kaJN6fH^Ujn2hW`R$hS!K-V_Co#Ec!4{Bg>nAPs5z{ZjECW3?C=@(=AhJ386f{L+7YoSbvrX}M+Y08KIdm86?Mcmq$sN(`;fp<4n&gu zSv*zaL@(gxifC;#RNW;I9#q|Dr#lc2Qqsruh6NC1VI5*5XFYCUAlBVx@}9JeDF^#x_cbL~Yg?94@9QLxt%1s9qub2yO6-mbB{MZ`FxVb8};wUj_RKN>7ogcEybi`4CL%Pnt&jpZ&6aCgT! zF4ETa>Sp4%g7Tln-E^!NKl;>qicqe*4iWLuVpx4OwYukQVul)io@=b6F}b4b{*;v- zmT1Tn&3XIElKzP^s8Kkh9r%|S|9jj6dpD!2orCdjG0%_&|2*y;0c8#VApBdz(aGJ~ z#PJU+>#R8JG$DD*sl594743E>*%@6liPf5nWZo%T4`?i<44{e#p}R-~0DTOZGg3PJh6Hy%qw1^uzb@f&)1SDiawrKmBly-vs}K=P2UP z{gjW_XZg9?IW9qz55~B6;2cM86`%k03IRXq;NuheWiOB)1t&s#b*se84=)zUHZTN@ z8hWD--9ydFgi>2Y9v_0Fy~nnB<$@Wd`lLaLym9dGaMN4BB5_+&!pC7k+vVLP$s96| zwZ`|n=y-I0QeMcnUhcc7Cn8`_qihv9ARl!K>AL%T_5Lr0OU z8{J6Q=Ahn<+BCeREtL>p=`t`xBBh9>Tb-cdHnQj7i$(sdtNc}`tiERlDP41Imn+Bh z^^r3!d<+>q(|GDS>RdpbckL!0c~bb2#Uvbl_a$W00T_&E|FE(i5MSRMgW#DmKI{h?X*ZhLX& z0D?3>Km@n-Vgm!huHYf@c?LCmQPaE%SJdvTAP3eeK$0nGm!oB7k(q0AM(4ALA@)yGOyAX~ugirJM+aa%r7dV6- zU=5*Ougl2xAOQA|EkM^;Hz&Z}aS$G`iaYpg=;3`xkpF#%3Kk$8Lr`){F)iGKOcD}I zDgg&46@!VLL_o_zBdBPu9*{6u1qzj805wf$2g#YWRgF~8!ymn`SQsna9DluF>L#5> zXNO%486)Fbir1bDe$;@W#v+^3HJ0@v8c{uU3TMX7&dF_Qio$72iV78-B=SwLVY+YLGoyayv^7_;v{ySoX}Zg;VGDC0>;%fhJWPzA+5 zv|xw<;@2X_xu;ZDNEKS&@t6OPbRNPEbV~cvhFeBz|k=x6x zOXZFUl#tPf_koM|fennyv5tW{dd+@Kd{Jx`c0-S$bCQ^+v))Tf2DAyJzQ+Q5&PVp~=?ZUj->(aFvlEj=!=*EwsJ(rxY^dwNZzKI=Kp9A_o z%yGyuBZ#)FolKG7NsugeQbBav)@$OA#J`;5w;I&gB)mI9#cpXU$@etfUuc>2m>ZL z6nGR|x89ExX{N6XtUsz+{C)j^6qpal_mokU(ECw!M2+5vL*J0agek%E;kq|cke}eh z^WGR!(_M*R7~QOc&gWPv+eM^mmgrZB4>Pf61nHgk6x%%YddSTi%ePQi+#P;??9y)` z9oqfu49tX(NJ`*}B#7T+9}AnGY6hf}AS(&pqDg^uSSkpvfk~P#%LJOh#{{}dmm8_9 z1ROAx%7f;k_`RsiAW&Ad9X#WkEgY9dAGXu7Ch1oxl?bc=N)=Y8fD{$Z7AzB+%;L2& ze-&Z!2v;L+jW!&o8aMVs<15?Aq3yNo_`X&_@`#5W>X3%4VrPgf9%UgxeXtiF+MZNCo*=_h;IOqLq=Oae%^5g5}Sbfe|aB^g%57PhB0 z^OI0S-be!lZPYzPZ!F#Kl#7NbYn9<4GU=VHu(I0GCL(52=sem!bFo@2e*2;M7Tb@1 z#OkbSzcwlofUnhc(L`TU^~@{B6V&R+=bJMpqe-oE06BVhj#?9Y(WyMh z29k1V7jNQxeeX5jc!^;bHIwTGB3*(wA z$>wHLXHs>wPxqwD8o|%VXWs^R!(34IJ4o16ig(N94?DEkY>Jz}5p$T+c$7ti59Q6S z`q_Z3=jv$J)zP)UY$Di``c%1XwxjmGhq5P?)w&slalMZf|E)tD))K~^)LRIhIBxSU zGg1hLrl!+YEyCtr9D2MRMY8O@nd@~<=&8;ft$%A$tuoo*W;9}0mce$_?_ki2Gcu>~ zE}YSaBQ^WCPOQjUFOF0Ik{wO53=Wk{`TLxMWC&;qI&JS)`fwg~8}i(YG_2A7T<2Nc z`wC!)*VQY=x1&gpY1Y+q|K1@vCRwqBOEu>AYtj_KA1E$H$3aNbvxZFALM zvuIf)(^jpG`Mx-3bUPY_cYPrLy+fkFe$^kR|GsXMnP5q!llS>*hJ;0xG3-bbT(88& zsZJ^WKQ#yXzHYiSHsw@J3_F@wnGaOCEp9kOi!~h`&u#0uYhL$2`@{uR!H%c>doEla z;mD_=mF#QuY{~2xK5Q!!C(pOgl1=+T-Hjl}S{)c=jAKGeb4gcwi4c#y9E*e-_5-6c zX-z#9FOKRw(A@KB3wiqkw5JYVzY>E(vu%DJ+4m@d^9H{k;b&M-6(@r!-1P9qwDk)> z$Gxlv$P~kC?Jt}++wDkjf);p#uzKbrVeKR?^Oy=+vzJq^euiw}epCh_Wf zq7r&B?PSttWSI$E_NWl_~ZUv{`%OnbuFn*s;%e-FI6qgw&g>I{>>9a-#I-;27lF2*@mH6{v$2vE~k`pfb!S@j_LatTrK%lQ$2hNo#gGs+2zliTqEZWD6j<%_q+2ahDw z0crJl#c%1KQ;HTf6=lwmG?d+6GR97DxWC#qbx3$dp1?FO)9GK^u#lr81QeGMu6phRo6?KTAF+UmrWf=kXDnMod{;EuqUl#Xq4 zx9py%UAd4h^cIG_uXYMP7E~Aoy2lk!0_4wUZbGfQ5=!Qa0Vn*8Xz;$Apg~cjnJul2 zbSY%TvgaPVb30S5II2*e7F{A7v4TYV%%EloCfmCdfnm;~D#L?9d4g=s;&L?{USX_cWF-e7_1xJxgxQ7@GG;lwgp6l06RDKz9X zqM;PSG>v&DS<~!tJEF07)*804;)YQuC^UqBz(uF~^mC8C4EEN!ga(I>xMQt_G0wCh z7(e#-E6@%HX>Ku;s3PiEbabFyGezMet zkbMH4HyGyM&5;;iIB-rIR>Tw*oP^PC=fC0waW=8?JzoV|rk^=pg4B;Vb3sC=$Xj);*918V z;eR7&;JT5jHe%ZzM_gASN8~yhxnQjr&)v3qK*K$+mrbhjtv*rH?nb+m*{eXC*SqCF zPfDh)nTa|v4jd6xug9z+LSEsKf4+0i!y*~9gx#2sU+v=0u&6}T^qf~~S{dZWo?hf! z^qS;UZ_4P(r&PNX=TJID5$pa$7~=8P0b$1p7%tj};UjTv#ZD4ni1-2e!r$MLN()wL z$6*WPSdKYpJu$5jUEeN_la$nxzw%TN`zbTnyuMRuA07U#KzjD)r4w7>m`zZ z6RW1z+5ws&n7NDq=Kg&0q)4U9{*r^=a@9O zx;zPPZ1aKG;5S7Z-K{3f?m3Y2&u+K3_2@#4T@^&1GTR7(NStQu%Ztr1^#eE4Oed;?mmjFJBRs2WCT@_Op>(@5oK>uD$5#haEs5^0Jdwb`J*Nk<>iTEzr9XRs!09=oyKEyLr?Vsxltf zSW>XX!Q_C28_XrE1dU9e=vV%175nSdQcfQZU4 zKX$AWzu~D_p7IH)x!reWr0IZRNP8*5Yjj93y2=qm@EGI8ZzlZAp&l7@gfTR9YqEjd zlA5lbFZ$ZVy7{B9a8;-wQpg3b9^|_!`3N<1GrxL`fJO7p2i!bj6?B@jQ)?@0-{$+E zB$!mVmJNwz!CjFRrroydI2W?zkPxbF5YH#-@OI<-CO?i2{N%Y{*SX1 zB#ixXmVpb9C-~s=>QgpaXku9eI5yNp1A#^ec@s`_%h&)C@sSldm!GkHf|G?9D=~xk zZzM7mnXjc>%TPw7LbB5wgt36=pwBk4uRmWq+(=@bVWCxtL(@-#Naaq_?5MM#C;%B% z3Icv(ZTp-h=`&abNoqojIMhRMnlw723JNq*LtuC2&>m^|A0G{~~gN}(}dMCpRIn96nxX|{OllOuqDJ5FN`dI&}+$e+aFf!5x9(s~)GqlXf0qAsXDFS3Sd@REkqsXR2FN{PzvQQ@$HYccC&nCLrzlogse`?8=! zdsMGEu1J3fEg4eK@e1+3*WF9hltG=z(V_!qR`%Nn*0D$&S!9TYW`F*$>($NNSsW!F=1{W*-SuV7nFFT zfVt__YZymfemSrIuxg448Fr1SkhG)9(L>bM_HrejJeef@DHtSXq7U6Ns3al2JPM96 zz<^}D7HWKJ2dQQ6RTmzyK4hlyeeYY~Gcb1({mdvPB@0BV|z4%{d?&5nWS{_w8!# zrl|WU(5wi-htvDVQ-i3Y3XCN(LSMQvk269HS8?A%jfEW*4ED) zpLhv~$3byMHtC?2L*0a#2!umqU~+il1a(dMKnOQvt>JAUY{a~EU9YMmK9>s28vO() zkw>acD(8+CIruSILU`d1&6*ke_KUMskdqj5btizeW5R63H+OgIQ6DX2`~R5n5im=M z*p0(|bUG*Qw>D13b5=}{@mk~?Qo?x-;{XoN-HiE&|I@D-Eq9#|fkH3sd%5o>6|dc!ohaiJ+krQ^qa^}xAny~&0B zc6myBfml0y<7y^%92mZ z22HAoPXa|tQCExUwA5oBsAJ@l2MS;1<&qDLj4*S;?rds_IxE?*TxkxS9s_ z7Q0*KeiYG(VL_kILSBBR@)am)KRH!shB*G<-cHKxQ~YjWTa>lm4W%t#3>nl7DWkcC zDQk53Rfa%T=u8PwW)t3sqNhK@lsQ6To;0Zb#RMUk2PrY)mzY!(sO8%~`FX>yC=9J~HGh z`Je!)P%9MVaNwc{Iw{iItEp6?&2O6{;4o`14cm3P!h_W)B*#4fgqQS;W38bGRm9h&N0j3EQW>d76 z#khvM{f#1`)z{*J$-4ORrkZ?6q1!SMn(m~sk{F!;(K?0j9wotK1)Yr`jj zt^GLVnnzyb`r_`C`Vm98GYNe+0-A-iVmdSX@W2?Wmo0@E^)Kq-B1RQ1l(ugjv;dr8 z5mVG=9t}%2x=$s6~9PF-H`sfvLl>&)6etX41c)Tb1PR$!O6k*%-sV{38Ho(G5W7NY1BUPDfT!Jh+e2Ck z`^Eap4nr}n`^V#*VnT_uT=^>^4A-B<3y2?ho!DS=;*e3D<^6~&3Dez5?0rwxT+dDJ z;9VecFk+!&LZ>~@Tj6r#B(y74l^QRCDAr)T*}Eyv<76;GR94TPD%8g8r%Q%&Nop!N zx+{_kyC%e@kR*fBwK-(q26RE&S~#GPKC*wBE(@mM#O=iq;1%oTzXZuj2iPdk4{ioC zNaT9ZO^Q(>o+%yx?^WiCGqHwadAU!hRsi)^r@8?R`sLMK4CEPTCt|}^>xDY8*ddA= z$k&Y+e$3cbgLjYRG$*A9EWt!dCt?@I!ds_=ZTfKdGurfys81A9R*~W`c<_s4cLm!d zqsqr^o`U@(K`^Z9*+S#2GOd)Q2h0&n?PD8UWA5(2CRFnKjp$`rC-v#v%olzVl)I9g zg13mSgo}$ih110C-V$C3D0r>V>^R#blQfNV+ZF|Q5~Fo;N|j=4XJo1zt&Jm54nYW@ zNZR;WNiqwxV}kGK`U^nk>67f7kk<>galWUq@m*!ccf_bHprXb5eOI=?LH-M*vH?9I z>x@86EF?hCD4h#gbYSwNV}oZ#094>H3#lKRw8Mt8fk@0q+uqqK%$9u@|fy&GYQNn?nwjGfi#vpK^%f2NNCL6eQB3n zTDlL|^zKy&ECKDZM8_+|(4`KmGL*jJ>;m1txqg)nN=7MWVom&hx#7^?fYK>!Rsvy% z1CbtCdWekQFH&uceH>}1fv&+=q>V@dr5>gEMIpe2i{a9xY%b0wLIY8K+;*C8ygUyT z4z%6JW4KS+A&T_Eeb!J=ZZwlbboMaejvrfIPjJ^iL{TVFq+Y?vVM$^w{c~Cx-VD=i z1_SjntsNv8bD_YTvPm9T1akoxyr^EvMt4dSaa>i35ptMUFvFrdd)LxAfrFIAO$&<{ z9gT9=Q&%7y&|o=ZtBB6Ff6q|dxW$jrr&gc1I0(KV&N&9Ru_D_X0K+&R{Rjj%(Rl9= z92sx@Om0MaGFa%40NT8?H=u0hHTVUH6kJk&b~EEk0v!863jM1+&U47MjZ*?IuH#M9Kp`czX=l!kCbp)B#wb4OCm#4?T2SrBcnQ zue6PdMWotLTGo0KC)SV%pgE7sJ1ev(__eIEid&teF4c^3qmf!SavBwpTWO9=%}4OL z!t|Aq!_c6{m79kH4UjhI=`C2CrP897+K$+Vw#U+ST&^RH+oKGyt5`}UnJl>`F`=Kv z;Eha1-k-T-y>XdTHLZLvf>m&q9{E;}QCGWo+{Gml*zSHAd-nNIMX8IZk`CV79vu#{ zP<-qbHCOl)#83*6(r`<-MQ3#A8wIZO7(mu#867k$5zN_ZCV>eHFAcv?>ovvJHs2QP zlPycSNb(xRc1 z@*0n4CL*%Ak%GEwBur^$SFt((a^scg^4oCI*5Pmz?1=WGfFcUsfNh$qBvz;QoTbBz zOYaIa9xMdVeg;W0N^(Fr|||_ou7nPZDkn zDB<^fIn83{@2;)U!+O1k;hE_4tm*W9{rNmHMkwy{wB76*e^b&3S8IXo#fQMamygx& z2};TWG*t9af7X}?Rmkt~imf5Wb8xNLe8m(>yp2>2#ITW|20*KDwL7&GI3uT#`3$c1{1ZQ*fgq-T1y>$IF zl*240!{zJ*r|bwkf~b*XL{lOJsUJvdwX4h#fjRk@25-ihb=~L$0Pe~$8?Zw$m(?dI z7`;X2z@&8AErA#ELXlkN&6pfs*ZERED3}HsUw@;%919Tr1b(k5TfXn*|5p65by7Ak zwEly6w5PUayTXRrfnVi{=(O=Qe@}w#7_|Y#25(+p9p%&^+hm!HEF7G2UnDlL)I#+gNeR0(#NLelyF)|As9v5*kwXQ+J)~5Zn5M;^Zj2`joU~u)!#tOb8#5 zeO4~raSnhF)CbIGz*sU#*&^uzoCqURse5LHsMK1Q+o$iwSfkgjc-z##HhLe%t!iG0 z<#tO#o+DgnL-ca;3xQ#U&F8M%Rj}Kt+LqQ(y!b^P7szNMW^keyGwzX!+8qjIq1~M{ ze*U)SR&YpI0RjL3!<7_q$^s41DMJdQGo&YS?1*b@gkiA{c-fU9h9X?14ito;2`Lyr zw&lX`6WEExq?kJPaH((Yxl{x=R$O@7cH(@>^jA z9be~5Su@>}U*AG8jAyv9^~iGfWE9yajxg~MhE4L~h%jyH@r2k>Y}wD*2;L*$#7TnG zvXGocRGzSkph32)bNwhW+jr$(hmW@p1uMG%05L+uz^=~8vy&<-Wl0g81#?v?VS^$_ zJq=%728%nOCWSKO#IxFVNdb5KVo(-kSFW;4{O4dLEh5VpeYd+% zqNd|xseR2lf>aW1huLbYPxD-7931O8jEmv?=DDO`7!d~Fcraf;HjfBi?QX;UNGkkA z92M!wQ{>eLG4-*R-BQB_fOz}CkPYbgYt_*?Q6lPzAmI*d!|!n zzlF$@%_mwar-%7Ugq4{Tlp||7#7ybpAu&_};ochH;T2G+mg5Xov)(+N!u;>mp7&Y~ zK+nO%`gdw65D^jn%loyhcS7B-iqM}jbiX9aBUKgo9AoEv13ibGc zH>9GoF?c%zk{VmIhz(|8RUvpPTW$Ti!d+KB=Y)UT9eWUl=J1VT{419sSS2Br1s_CN z0IWl*sWac$MY5!LBsf+9d>A@oVmk6l+$p=ffjiB_qYfiP9S^>_*H2|0C*$kjQ6A)e z4soZuERm-e&L;OvgfWabT<_6+xc^;~|EZh(H+7Q6!fwC4tFa0M0Kk3!|Kr&2Pr<)$ zfd15P{Dfr>18o0AV0++{_orzalSr=i1i?yTPXN8bYn2Fe16NXG78FT-o^i4^><0PI zqYz{KXnvitj;Y7sApzvR>=*|+>{3`SV><-;>^5~O>ygjk;J5bD7Z>msi<(f$UTe3y zL8#Z@ma(rYR#^*+8u*{Spi+^xj!VW=Ot+79S_gSx40xU;Va4d0lsX!8Wkm26TOSG^ zywx_-q~Xe?>v54ZPYESU$1P2u3l}@1h5Ijs15+NZAWmorNnnlx>nC{d#Ipt$(PG6w z8$z|FA24uQL4MI9N*KKkF=&`4aM2QSLxqy_W(*n95SCyQjmgoW`~Jnlva<43Qis4~y zDHU)8UlJ|M6Ho=OfAN)YrvLX1S7b<-Y|!vU5y{i$hacm;fm73}L%>8y>wCFbcOt;D zo=PnibbRD?M|l!43CyL}@DA0$sCipwWBN7Feo$)MqkWMSJRie6z*P@#s_E96NW!RvokI{w@Zsn(+^$NJ;xE=+`TnGYicnhndF#vK92;$8#T9%jpQ3lq9y7Ti=A%Z#_WAizxh|Yl zlTCHXK8<1{y0_UR+9Z}!>2Onp7}{|($&I6I_92~qsvFLL1bah3xl?(`yW_T5G+LC* z0&E>g3ZnWDuR-|>^vbjeF>CJGf@-QPd{hcNL7rOS{$^SFyzd7T- z!+)na{{?4!4=jJd|3iBIzdiIXg6*H}*F*n!Pa*$BxBd0N|J^9RV%(qY7o?c_AA#)m z#r_`iegXf~)YLzL|04qaey86HM}OUE{=>hP@b`QC4*&hY>o2$t`oG|RKmPh1{(FM` z7o3LdUvSg+p!A<~`*-~Br?Y?I>EGX-KXm?&v)bSBzf(s3!Y{uQp#KMf removeBatch(@RequestBody List ids) { if (aiHistoryService.removeByIds(ids)) { @@ -124,4 +126,32 @@ public class AiHistoryController extends BaseController { } return fail("删除失败"); } + + /** + * 根据接口名称和项目 ID 查询最新的历史记录 + */ + @Operation(summary = "查询最新的历史记录") + @GetMapping("/latest") + public ApiResult getLatestHistory( + @RequestParam String interfaceName, + @RequestParam Long projectId) { + try { + log.info("===== 查询最新历史记录 ====="); + log.info("interfaceName: {}", interfaceName); + log.info("projectId: {}", projectId); + + AiHistory latestHistory = aiHistoryService.getLatestHistory(interfaceName, projectId); + + if (latestHistory != null) { + log.info("查询到历史记录 - id: {}, interfaceName: {}", latestHistory.getId(), latestHistory.getInterfaceName()); + } else { + log.warn("未查询到历史记录 - interfaceName: {}, projectId: {}", interfaceName, projectId); + } + + return success(latestHistory); + } catch (Exception e) { + log.error("查询最新历史记录异常", e); + return fail("查询最新历史记录异常:" + e.getMessage(), null); + } + } } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java index 4d10585..2415b25 100644 --- a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java @@ -1,8 +1,8 @@ package com.gxwebsoft.ai.controller; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; -import java.math.BigInteger; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; @@ -15,6 +15,7 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; +import cn.hutool.crypto.digest.DigestUtil; import com.alibaba.fastjson.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.gxwebsoft.ai.entity.AuditEvidence; @@ -31,20 +32,15 @@ import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFTable; import org.apache.poi.xwpf.usermodel.XWPFTableCell; import org.apache.poi.xwpf.usermodel.XWPFTableRow; -import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; -import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; -import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; import com.alibaba.fastjson.JSONObject; import com.gxwebsoft.ai.config.TemplateConfig; @@ -55,6 +51,7 @@ import com.gxwebsoft.ai.entity.AuditReport; import com.gxwebsoft.ai.enums.AuditReportEnum; import com.gxwebsoft.ai.service.AuditReportService; import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.service.AiHistoryService; import com.gxwebsoft.ai.util.AuditReportUtil; import com.gxwebsoft.common.core.web.ApiResult; import com.gxwebsoft.common.core.web.BaseController; @@ -92,6 +89,12 @@ public class AuditReportController extends BaseController { @Autowired private AuditEvidenceMapper auditEvidenceMapper; +// @Autowired +// private AuditEvidenceService auditEvidenceService; + + @Autowired + private AiHistoryService aiHistoryService; + private String invok(String query, String knowledge, String history, String suggestion, String title, String userName) { // 构建请求体 JSONObject requestBody = new JSONObject(); @@ -121,8 +124,7 @@ public class AuditReportController extends BaseController { // 获取outputs字段 JSONObject outputs = data.getJSONObject("outputs"); // 获取outputs中的result字符串 - String resultStr = outputs.getString("result"); - return resultStr; + return outputs.getString("result"); } /** @@ -240,7 +242,8 @@ public class AuditReportController extends BaseController { } // 调用 Service 方法,传入证据 ID 列表 - Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds); + PwlProject project = pwlProjectService.getById(projectId); + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds); return success(data); } catch (Exception e) { log.error("查询审计数据异常", e); @@ -280,7 +283,8 @@ public class AuditReportController extends BaseController { log.info("勾选取证单 IDs: {}", allEvidenceIds); // 调用带分析的接口 - Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, allEvidenceIds); + PwlProject project = pwlProjectService.getById(projectId); + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, allEvidenceIds); List reports = (List) data.get("reports"); String evaluate = (String) data.get("evaluate"); // 审计总体评价 String suggestion = (String) data.get("suggestion"); // 改进意见和建议 @@ -302,8 +306,7 @@ public class AuditReportController extends BaseController { } // 3. 获取项目信息 - PwlProject project = pwlProjectService.getById(projectId); - String projectName = project != null ? (project.getName() != null ? project.getName() : "") : ""; + String projectName = project.getName() != null ? project.getName() : ""; // 4. 使用 XWPFDocument 直接生成 Word 文档(不使用模板) XWPFDocument document = new XWPFDocument(); @@ -332,41 +335,41 @@ public class AuditReportController extends BaseController { // 10. 按照前端页面的固定目录结构生成内容(带标题,支持子章节) addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project); - - addSectionWithTitle(document, String.format("二、%s主要业绩", project.getPersonName()), null, contentMap, project); - - addSectionWithTitle(document, "三、履行经济责任的主要情况", - Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); - - // 四、审计总体评价(从接口获取 evaluate 字段) + + // 二、总体评价(从接口获取 evaluate 字段) if (StrUtil.isNotBlank(evaluate)) { - XWPFParagraph sectionPara4 = document.createParagraph(); - sectionPara4.setAlignment(ParagraphAlignment.LEFT); - XWPFRun sectionRun4 = sectionPara4.createRun(); - sectionRun4.setText("四、审计总体评价"); - sectionRun4.setBold(true); - sectionRun4.setFontSize(18); - sectionRun4.setFontFamily("仿宋_GB2312"); - sectionRun4.setColor("2c3e50"); + XWPFParagraph sectionPara2 = document.createParagraph(); + sectionPara2.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun2 = sectionPara2.createRun(); + sectionRun2.setText("二、总体评价"); + sectionRun2.setBold(true); + sectionRun2.setFontSize(18); + sectionRun2.setFontFamily("仿宋_GB2312"); + sectionRun2.setColor("2c3e50"); document.createParagraph(); - - // 添加内容,首行缩进 2 字符 - addParagraphWithIndent(document, evaluate); + + // 添加内容,无首行缩进 + addParagraphWithoutIndent(document, evaluate); } - - // 五、改进意见和建议(从接口获取 suggestion 字段) + + addSectionWithTitle(document, String.format("三、%s主要业绩", project.getPersonName()), null, contentMap, project); + + addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定", + Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); + + // 五、审计建议(从接口获取 suggestion 字段) if (StrUtil.isNotBlank(suggestion)) { XWPFParagraph sectionPara5 = document.createParagraph(); sectionPara5.setAlignment(ParagraphAlignment.LEFT); XWPFRun sectionRun5 = sectionPara5.createRun(); - sectionRun5.setText("五、改进意见和建议"); + sectionRun5.setText("五、审计建议"); sectionRun5.setBold(true); sectionRun5.setFontSize(18); sectionRun5.setFontFamily("仿宋_GB2312"); sectionRun5.setColor("2c3e50"); document.createParagraph(); - - // 添加内容,首行缩进 2 字符 + + // 添加内容,首行缩进 addParagraphWithIndent(document, suggestion); } @@ -435,7 +438,8 @@ public class AuditReportController extends BaseController { log.info("根据项目 ID 和选中的取证单生成审计报告 - 项目 ID: {}, 取证单 IDs: {}", projectId, evidenceIds); // 2. 查询该项目的所有审计数据(包括报告和取证单),并对选中的取证单进行 AI 分析 - Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds); + PwlProject project = pwlProjectService.getById(projectId); + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds); List reports = (List) data.get("reports"); String evaluate = (String) data.get("evaluate"); // 审计总体评价 String suggestion = (String) data.get("suggestion"); // 改进意见和建议 @@ -469,8 +473,7 @@ public class AuditReportController extends BaseController { } // 5. 获取项目信息 - PwlProject project = pwlProjectService.getById(projectId); - String projectName = project != null ? (project.getName() != null ? project.getName() : "") : ""; + String projectName = project.getName() != null ? project.getName() : ""; // 6. 使用 XWPFDocument 直接生成 Word 文档(不使用模板) XWPFDocument document = new XWPFDocument(); @@ -499,41 +502,55 @@ public class AuditReportController extends BaseController { // 12. 按照前端页面的固定目录结构生成内容(带标题,支持子章节) addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project); - - addSectionWithTitle(document, String.format("二、%s主要业绩", project.getPersonName()), null, contentMap, project); - - addSectionWithTitle(document, "三、履行经济责任的主要情况", - Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); - - // 四、审计总体评价(从接口获取 evaluate 字段) + + // 二、总体评价(从接口获取 evaluate 字段) if (StrUtil.isNotBlank(evaluate)) { - XWPFParagraph sectionPara4 = document.createParagraph(); - sectionPara4.setAlignment(ParagraphAlignment.LEFT); - XWPFRun sectionRun4 = sectionPara4.createRun(); - sectionRun4.setText("四、审计总体评价"); - sectionRun4.setBold(true); - sectionRun4.setFontSize(18); - sectionRun4.setFontFamily("仿宋_GB2312"); - sectionRun4.setColor("2c3e50"); + XWPFParagraph sectionPara2 = document.createParagraph(); + sectionPara2.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun2 = sectionPara2.createRun(); + sectionRun2.setText("二、总体评价"); + sectionRun2.setBold(true); + sectionRun2.setFontSize(18); + sectionRun2.setFontFamily("仿宋_GB2312"); + sectionRun2.setColor("2c3e50"); document.createParagraph(); - - // 添加内容,首行缩进 2 字符 + + // 添加内容,首行缩进 addParagraphWithIndent(document, evaluate); } - - // 五、改进意见和建议(从接口获取 suggestion 字段) + + // 三、主要业绩(从 contentMap 获取 formCommit=30 的数据) + if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) { + XWPFParagraph sectionPara3 = document.createParagraph(); + sectionPara3.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun3 = sectionPara3.createRun(); + sectionRun3.setText(String.format("三、%s主要业绩", project.getPersonName())); + sectionRun3.setBold(true); + sectionRun3.setFontSize(18); + sectionRun3.setFontFamily("仿宋_GB2312"); + sectionRun3.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 + addParagraphWithIndent(document, contentMap.get(30)); + } + + addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定", + Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); + + // 五、审计建议(从接口获取 suggestion 字段) if (StrUtil.isNotBlank(suggestion)) { XWPFParagraph sectionPara5 = document.createParagraph(); sectionPara5.setAlignment(ParagraphAlignment.LEFT); XWPFRun sectionRun5 = sectionPara5.createRun(); - sectionRun5.setText("五、改进意见和建议"); + sectionRun5.setText("五、审计建议"); sectionRun5.setBold(true); sectionRun5.setFontSize(18); sectionRun5.setFontFamily("仿宋_GB2312"); sectionRun5.setColor("2c3e50"); document.createParagraph(); - - // 添加内容,首行缩进 2 字符 + + // 添加内容,首行缩进 addParagraphWithIndent(document, suggestion); } @@ -580,6 +597,175 @@ public class AuditReportController extends BaseController { ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); } } + + /** + * 根据项目 ID、选中的取证单和章节内容生成审计报告(使用前端传递的内容,不查数据库) + */ + @Operation(summary = "根据项目 ID 和章节内容生成审计报告") + @PostMapping("/generateWithContent") + public void generateAuditReportWithContent( + @RequestParam Integer projectId, + @RequestBody Map requestBody, + HttpServletResponse response) { + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + + try { + ZipSecureFile.setMinInflateRatio(0.001); + + // 1. 从请求体中获取选中的取证单 ID 列表 + List evidenceIds = new ArrayList<>(); + if (requestBody.containsKey("evidenceIds")) { + List evidenceIdInts = (List) requestBody.get("evidenceIds"); + evidenceIds = evidenceIdInts.stream().distinct().map(Long::valueOf).collect(Collectors.toList()); + } + + // 2. 获取前端传递的章节内容 + List> chapters = (List>) requestBody.get("chapters"); + + log.info("=== 生成 Word 报告(使用前端传递内容)==="); + log.info("项目 ID: {}", projectId); + log.info("勾选取证单 IDs: {}", evidenceIds); + log.info("章节数量:{}", chapters != null ? chapters.size() : 0); + + // 3. 构建内容映射(直接使用前端传递的内容) + Map contentMap = new HashMap<>(); + if (chapters != null) { + for (Map chapter : chapters) { + Integer formCommit = (Integer) chapter.get("formCommit"); + String reportContent = (String) chapter.get("reportContent"); + + if (formCommit != null && StrUtil.isNotBlank(reportContent)) { + // 直接使用前端传递的内容,不需要从数据库读取 + contentMap.put(formCommit, reportContent); + log.info("加载章节:formCommit={}, content 长度={}", formCommit, reportContent.length()); + } + } + } + + // 4. 获取项目信息 + PwlProject project = pwlProjectService.getById(projectId); + String projectName = project.getName() != null ? project.getName() : ""; + + // 5. 获取 evaluate 和 suggestion(从 request body 获取) + String evaluate = requestBody.containsKey("evaluate") ? (String) requestBody.get("evaluate") : ""; + String suggestion = requestBody.containsKey("suggestion") ? (String) requestBody.get("suggestion") : ""; + + // 6. 使用 XWPFDocument 直接生成 Word 文档(不使用模板) + XWPFDocument document = new XWPFDocument(); + + // 7. 添加封面页 + addCoverPage(document, project); + + // 添加分页符 + addPageBreak(document); + + // 8. 添加正文标题(居中) + addBodyTitle(document, "经济责任审计报告"); + + // 9. 添加文号(右对齐,蓝色) + if (project != null && StrUtil.isNotBlank(project.getCaseIndex())) { + addDocumentNumber(document, project.getCaseIndex()); + } else { + addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号"); + } + + // 10. 添加委托单位名称 + addClientUnit(document, project); + + // 11. 添加审计引言段落(从 form_commit=30 获取) + addAuditIntro(document, contentMap, project); + + // 12. 按照前端页面的固定目录结构生成内容(带标题,支持子章节) + addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project); + + // 二、总体评价 + if (StrUtil.isNotBlank(evaluate)) { + XWPFParagraph sectionPara2 = document.createParagraph(); + sectionPara2.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun2 = sectionPara2.createRun(); + sectionRun2.setText("二、总体评价"); + sectionRun2.setBold(true); + sectionRun2.setFontSize(18); + sectionRun2.setFontFamily("仿宋_GB2312"); + sectionRun2.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 + addParagraphWithIndent(document, evaluate); + } + + // 三、主要业绩(从前端传递的 contentMap 获取) + if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) { + XWPFParagraph sectionPara3 = document.createParagraph(); + sectionPara3.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun3 = sectionPara3.createRun(); + sectionRun3.setText(String.format("三、%s同志主要业绩", project.getPersonName())); + sectionRun3.setBold(true); + sectionRun3.setFontSize(18); + sectionRun3.setFontFamily("仿宋_GB2312"); + sectionRun3.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 + addParagraphWithIndent(document, contentMap.get(30)); + } + + addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定", + Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); + + // 五、审计建议 + if (StrUtil.isNotBlank(suggestion)) { + XWPFParagraph sectionPara5 = document.createParagraph(); + sectionPara5.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun5 = sectionPara5.createRun(); + sectionRun5.setText("五、审计建议"); + sectionRun5.setBold(true); + sectionRun5.setFontSize(18); + sectionRun5.setFontFamily("仿宋_GB2312"); + sectionRun5.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 + addParagraphWithIndent(document, suggestion); + } + + // 六、其他事项说明(先添加标题) + XWPFParagraph sectionPara6 = document.createParagraph(); + sectionPara6.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun6 = sectionPara6.createRun(); + sectionRun6.setText("六、其他事项说明"); + sectionRun6.setBold(true); + sectionRun6.setFontSize(18); + sectionRun6.setFontFamily("仿宋_GB2312"); + sectionRun6.setColor("2c3e50"); + document.createParagraph(); + + // 添加固定内容(两段话) + addOtherMattersSectionFixedContent(document, project); + + // 添加分页符 + addPageBreak(document); + + // 13. 添加尾页(签名页) + addSignaturePage(document); + + // 14. 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + String fileName = "经济责任审计报告_" + projectName + ".docx"; + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); + + // 15. 输出文档 + try (OutputStream out = response.getOutputStream()) { + document.write(out); + out.flush(); + } + + } catch (Exception e) { + throw new RuntimeException("生成审计报告失败", e); + } finally { + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + } /** * 添加封面页 @@ -676,9 +862,192 @@ public class AuditReportController extends BaseController { } /** - * 添加签名页(尾页) + * 添加无首行缩进的段落(支持多段文本,用于总体评价和审计建议) + * @param document Word 文档对象 + * @param text 文本内容(可能包含多个段落,用换行符分隔) + */ + private void addParagraphWithoutIndent(XWPFDocument document, String text) { + if (StrUtil.isBlank(text)) { + return; + } + + // 按换行符分割成多个段落 + String[] paragraphs = text.split("\n"); + + for (String paragraph : paragraphs) { + if (StrUtil.isNotBlank(paragraph.trim())) { + XWPFParagraph para = document.createParagraph(); + // 不设置首行缩进 + + XWPFRun run = para.createRun(); + run.setText(paragraph.trim()); + run.setFontSize(12); // 小四 + run.setFontFamily("仿宋_GB2312"); + } + } + } + + /** + * 添加签名页(尾页)- 从 doc/尾页.docx 读取 */ private void addSignaturePage(XWPFDocument document) { + try (InputStream is = this.getClass().getResourceAsStream("/doc/尾页.docx")) { + if (is == null) { + log.warn("未找到尾页模板文件,使用默认表格方式创建"); + createSignaturePageWithTable(document); + return; + } + + XWPFDocument signatureDoc = new XWPFDocument(is); + + // 使用底层 XML 复制方式,确保样式 100% 保留 + // 1. 复制所有段落 - 直接追加到文档末尾 + for (XWPFParagraph srcPara : signatureDoc.getParagraphs()) { + document.createParagraph(); + int paraPos = document.getParagraphPos(document.getParagraphs().size() - 1); + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP ctp = (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP)srcPara.getCTP().copy(); + document.getDocument().getBody().setPArray(paraPos, ctp); + } + + // 2. 复制所有表格 + for (XWPFTable srcTable : signatureDoc.getTables()) { + document.createTable(); + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl cttbl = (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl)srcTable.getCTTbl().copy(); + // 获取文档中所有表格的 XML 数组并替换最后一个 + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody body = document.getDocument().getBody(); + // 添加新的 tbl 元素 + body.addNewTbl(); + body.setTblArray(body.getTblArray().length - 1, cttbl); + } + + log.info("成功添加尾页(从 doc/尾页.docx 读取,使用底层 XML 复制)"); + } catch (IOException e) { + log.error("读取尾页文档失败", e); + createSignaturePageWithTable(document); + } + } + + /** + * 复制段落内容和样式 + */ + private void copyParagraph(XWPFParagraph src, XWPFParagraph dest) { + // 复制段落属性 + if (src.getAlignment() != null) { + dest.setAlignment(src.getAlignment()); + } + if (src.getIndentationFirstLine() != -1) { + dest.setIndentationFirstLine(src.getIndentationFirstLine()); + } + if (src.getIndentationLeft() != -1) { + dest.setIndentationLeft(src.getIndentationLeft()); + } + if (src.getIndentationRight() != -1) { + dest.setIndentationRight(src.getIndentationRight()); + } + if (src.getSpacingBefore() != -1) { + dest.setSpacingBefore(src.getSpacingBefore()); + } + if (src.getSpacingAfter() != -1) { + dest.setSpacingAfter(src.getSpacingAfter()); + } + + // 复制 Run(文本片段) + for (XWPFRun srcRun : src.getRuns()) { + XWPFRun destRun = dest.createRun(); + String text = srcRun.getText(0); + if (text != null) { + destRun.setText(text); + } + + // 复制所有样式属性 + try { + // 字体相关 + if (srcRun.getFontFamily() != null) { + destRun.setFontFamily(srcRun.getFontFamily()); + } + // 尝试使用 setFontName(某些版本的 POI 支持) + try { + java.lang.reflect.Method setFontNameMethod = destRun.getClass().getMethod("setFontName", String.class); + if (srcRun.getFontName() != null) { + setFontNameMethod.invoke(destRun, srcRun.getFontName()); + } + } catch (Exception e) { + // 忽略,不使用 setFontName + } + + // 字体大小 + if (srcRun.getFontSize() != -1) { + destRun.setFontSize(srcRun.getFontSize()); + } + + // 字体样式 + destRun.setBold(srcRun.isBold()); + destRun.setItalic(srcRun.isItalic()); + + // 下划线 + if (srcRun.getUnderline() != null) { + destRun.setUnderline(srcRun.getUnderline()); + } + + // 删除线 + destRun.setStrike(srcRun.isStrike()); + + // 颜色 + if (srcRun.getColor() != null) { + destRun.setColor(srcRun.getColor()); + } + + // 上标/下标 + if (srcRun.getSubscript() != null) { + destRun.setSubscript(srcRun.getSubscript()); + } + } catch (Exception e) { + log.warn("复制 Run 样式时出错", e); + } + } + } + + /** + * 复制表格结构和内容 + */ + private void copyTable(XWPFTable src, XWPFTable dest) { + for (XWPFTableRow srcRow : src.getRows()) { + // 创建新行并复制高度 + XWPFTableRow destRow = dest.createRow(); + destRow.setHeight(srcRow.getHeight()); + + List srcCells = srcRow.getTableCells(); + + // 先确保目标行有足够数量的单元格 + while (destRow.getTableCells().size() < srcCells.size()) { + destRow.addNewTableCell(); + } + + List destCells = destRow.getTableCells(); + + // 复制每个单元格的内容 + for (int i = 0; i < srcCells.size(); i++) { + XWPFTableCell srcCell = srcCells.get(i); + XWPFTableCell destCell = destCells.get(i); + + // 清空目标单元格的默认段落 + if (destCell.getParagraphs().size() > 0) { + destCell.removeParagraph(0); + } + + // 复制源单元格的所有段落 + for (XWPFParagraph srcPara : srcCell.getParagraphs()) { + XWPFParagraph destPara = destCell.addParagraph(); + copyParagraph(srcPara, destPara); + } + } + } + } + + /** + * 使用表格方式创建尾页(备用方案) + */ + private void createSignaturePageWithTable(XWPFDocument document) { // 创建一个 3 行 3 列的表格 XWPFTable table = document.createTable(3, 3); @@ -1202,7 +1571,7 @@ public class AuditReportController extends BaseController { } /** - * 根据projectId 和 formCommit 查询审计报告 + * 根据 projectId 和 formCommit 查询审计报告 */ @Operation(summary = "查询审计报告") @PostMapping("/query") @@ -1217,5 +1586,305 @@ public class AuditReportController extends BaseController { return fail("查询审计报告异常:" + e.getMessage(), null); } } + + /** + * AI 生成默认话术(根据章节标题) + */ + @Operation(summary = "AI 生成默认话术") + @PostMapping("/generateDefaultText") + public ApiResult generateDefaultText( + @RequestParam Integer projectId, + @RequestParam Integer formCommit, + @RequestParam(required = false) String chapterTitle) { + try { + final User loginUser = getLoginUser(); + log.info("AI 生成默认话术 - formCommit: {}, chapterTitle: {}", formCommit, chapterTitle); + + // 获取章节标题 + String title = chapterTitle; + if (title == null || title.trim().isEmpty()) { + title = AuditReportEnum.getByCode(formCommit) != null + ? AuditReportEnum.getByCode(formCommit).getDesc() + : "审计内容"; + } + + // 构建提示词,根据 formCommit 使用不同的默认 prompt + PwlProject project = pwlProjectService.getById(projectId); + String companyName = project.getName(); + String personName = project.getPersonName(); + String prompt; + if (formCommit == 20) { + // 二、总体评价的特殊提示词模板 + // 查询该项目的所有取证单,判断是否有发现问题 + LambdaQueryWrapper evidenceWrapper = new LambdaQueryWrapper<>(); + evidenceWrapper.eq(AuditEvidence::getProjectId, projectId) + .eq(AuditEvidence::getDeleted, 0); + List allEvidences = auditEvidenceMapper.selectList(evidenceWrapper); + + // 判断是否有问题(检查是否存在 auditFinding 不为空或有问题的记录) + boolean hasIssues = allEvidences.stream() + .anyMatch(e -> e.getAuditFinding() != null && !e.getAuditFinding().trim().isEmpty()); + + StringBuilder promptBuilder = new StringBuilder(); + promptBuilder.append(String.format("综合本次经济责任审计情况,我们对%s 任职期间经济责任履行情况的总体评价如下(本部分应基于审计事实,作出客观、公正的概括;既要肯定成绩,也需指出不足,并与第三、%s 主要业绩、四部分履行经济责任的主要情况及审计发现的问题与责任认定相呼应):\n", personName, personName)); + + // 如果审计没有问题,添加特定段落 + if (!hasIssues) { + promptBuilder.append("\n审计期间未收到涉及").append(personName).append("的举报信息;根据现有审计程序与所获证据,也未发现其存在违反党风廉政及廉洁从业规定的明显问题或行为线索。"); + } + + prompt = promptBuilder.toString(); + } else if (formCommit == 30) { + // 三、XX 同志主要业绩的特殊提示词模板 + prompt = String.format("请根据传入的取证单及相关资料库的数据,总结%s 任职期间的主要业绩。要求:1.基于审计事实和数据;2.突出重要贡献和成绩;3.内容客观真实;4.条理清晰。", personName); + } else if (formCommit == 41) { + // (二)XX 公司概况的特殊提示词模板 + prompt = String.format("请生成关于'%s'的详细说明。要求按以下格式返回:\n" + + "%s 为(说明企业性质,如:国有独资/控股公司),成立于 XXXX 年 X 月 X 日,注册资本**元,法定代表人 XX,统一社会信用代码:XXX。\n" + + "公司主营业务为……(简述主营业务)。\n" + + "%s 下设……(组织架构)。", title, companyName, companyName); + } else if (formCommit == 42) { + // (三)XX 同志任职及分工情况的特殊提示词模板 + prompt = String.format("请生成关于《%s任职及分工情况》的详细说明。按以下格式返回:\n" + + "%s(同本章节标题人名) 自 XXXX 年 X 月至 XXXX 年 X 月任 XXXX(单位名称)XXX(职务名称),其主要职责是:...(描述主要工作职责)。", personName, personName); + } else if (formCommit == 43) { + // (四)实施审计的基本情况,要求返回两段内容 + prompt = String.format("请生成关于'%s'的详细说明。要求返回两段内容:\n" + + "第一段格式:本次审计的时间范围是 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日。本次审计以 %s 会计报表、账簿、凭证及相关经济活动资料为基础,对 %s 任职期间履行经济责任的情况进行审查,主要包括:...(具体内容)。\n" + + "第二段格式:%s 及 %s 对所提供的与审计相关的会计资料以及其他证明材料作出了书面承诺,对其真实性和完整性负责。我们按照审计实施方案确定的范围和内容,实施了在当时情况下认为有必要采取的审计程序和方法,包括(具体的程序和方案)等,并对重要事项进行了必要的延伸和追溯。", + title, companyName, personName, companyName, personName); + } else if (formCommit >= 51 && formCommit <= 57) { + // 四、履行经济责任的主要情况及审计发现的问题与责任认定的所有子章节 + // 要求返回三段:第一段概述和问题列示,第二段原因分析,第三段责任界定 + prompt = String.format("请生成关于'%s'的详细说明。要求返回三段内容:\n" + + "第一段(概述和问题列示):概述%s 任职期间履行经济责任所涉及的主要工作,重点说明其在职责范围内开展的经济活动和管理行为的核心内容;列示审计中发现的问题,例如:未严格落实国家相关政策要求、在某类业务操作中违反国有资产监管程序等,需列明具体事实、涉及金额以及所违反的具体规定条文;并对问题进行责任认定。\n" + + "第二段标题'原因分析:',针对上述问题,深入分析问题产生的原因,包括主观原因和客观原因,从制度机制、内部管理、人员素质等多个角度进行剖析。\n" + + "第三段标题'责任界定:',根据问题性质和情节轻重,结合%s 的职责分工,对其应承担的责任进行界定,明确是直接责任、主管责任还是领导责任,并说明认定依据。", + title, personName, personName); + } + else { + // 其他章节使用通用 prompt - 查询审计相关法规 + prompt = String.format("请查询与'%s'相关的所有审计法规、制度和政策文件。要求:1.列出完整的法规名称;2.注明颁布单位;3.注明发文字号(如有);4.列出相关条款;5.按重要性排序;6.使用规范的格式。", title); + } + + prompt += "(请注意:数据源都必须来自资料库或者数据库相关数据,不能从其他地方获取数据。)"; + log.info("生成审计报告标题:{},AI提示词: {}", chapterTitle, prompt); + // 调用 AI 接口 + String result = invokeDefaultTextGeneration(prompt, loginUser.getUsername()); + + // 保存历史记录到数据库 + try { + // 构建请求数据 + JSONObject requestData = new JSONObject(); + requestData.put("projectId", projectId); + requestData.put("formCommit", formCommit); + requestData.put("chapterTitle", chapterTitle); + + // 根据 formCommit 生成不同的接口名称 + String interfaceName = "/api/ai/auditReport/generateDefaultText_" + formCommit; + + // 生成请求哈希 + String requestHash = DigestUtil.md5Hex(interfaceName + ":" + requestData.toJSONString()); + + // 保存历史记录 + aiHistoryService.saveHistory( + Long.valueOf(projectId), + requestHash, + interfaceName, + requestData.toJSONString(), + result, + loginUser.getUserId(), + loginUser.getUsername(), + loginUser.getTenantId() + ); + log.info("保存 AI 生成历史记录成功 - projectId: {}, interfaceName: {}", projectId, interfaceName); + } catch (Exception e) { + log.warn("保存 AI 生成历史记录失败", e); + } + + return success(result); + } catch (Exception e) { + log.error("AI 生成默认话术异常", e); + return fail("AI 生成默认话术异常:" + e.getMessage(), null); + } + } + + /** + * AI 分析用户自定义输入 + */ + @Operation(summary = "AI 分析用户自定义输入") + @PostMapping("/analyzeUserInput") + public ApiResult analyzeUserInput( + @RequestParam Integer formCommit, + @RequestParam String userQuestion, + @RequestParam(required = false) String chapterContent) { + try { + final User loginUser = getLoginUser(); + log.info("AI 分析用户自定义输入 - formCommit: {}, userQuestion: {}", formCommit, userQuestion); + + // 获取章节标题 + String chapterTitle = AuditReportEnum.getByCode(formCommit) != null + ? AuditReportEnum.getByCode(formCommit).getDesc() + : "审计内容"; + + // 构建提示词 + StringBuilder promptBuilder = new StringBuilder(); + promptBuilder.append(String.format("当前章节:%s\n", chapterTitle)); + if (chapterContent != null && !chapterContent.trim().isEmpty()) { + promptBuilder.append(String.format("当前章节内容:%s\n\n", chapterContent)); + } + promptBuilder.append(String.format("用户问题:%s\n\n", userQuestion)); + promptBuilder.append("请根据用户的问题,为该审计报告章节提供专业、详细的分析内容。要求:1.回答要针对用户问题;2.使用审计专业术语;3.内容要有实际价值;4.字数在 300-500 字左右。"); + + // 调用 AI 接口 + String result = invokeAIAnalysis(promptBuilder.toString(), loginUser.getUsername()); + + return success(result); + } catch (Exception e) { + log.error("AI 分析用户自定义输入异常", e); + return fail("AI 分析用户自定义输入异常:" + e.getMessage(), null); + } + } + + /** + * 调用 AI 生成默认话术 + */ + private String invokeDefaultTextGeneration(String prompt, String userName) { + // 构建请求体 + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + inputs.put("query", prompt); + inputs.put("knowledge", ""); + inputs.put("history", ""); + inputs.put("suggestion", ""); + inputs.put("title", "审计报告默认话术生成"); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + + // 发送 POST 请求 + String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run") + .header("Authorization", "Bearer app-d7Ok9FECVZG2Ybw9wpg7tGu9") + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(600000) + .execute() + .body(); + + // 解析返回的 JSON 字符串 + JSONObject jsonResponse = JSONObject.parseObject(result); + JSONObject data = jsonResponse.getJSONObject("data"); + JSONObject outputs = data.getJSONObject("outputs"); + String resultStr = outputs.getString("result"); + + return resultStr != null ? resultStr : ""; + } + + /** + * 调用 AI 分析用户输入 + */ + private String invokeAIAnalysis(String prompt, String userName) { + // 构建请求体 + JSONObject requestBody = new JSONObject(); + JSONObject inputs = new JSONObject(); + inputs.put("query", prompt); + inputs.put("knowledge", ""); + inputs.put("history", ""); + inputs.put("suggestion", ""); + inputs.put("title", "审计报告 AI 分析"); + + requestBody.put("inputs", inputs); + requestBody.put("response_mode", "blocking"); + requestBody.put("user", userName); + + // 发送 POST 请求 + String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run") + .header("Authorization", "Bearer app-d7Ok9FECVZG2Ybw9wpg7tGu9") + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(600000) + .execute() + .body(); + + // 解析返回的 JSON 字符串 + JSONObject jsonResponse = JSONObject.parseObject(result); + JSONObject data = jsonResponse.getJSONObject("data"); + JSONObject outputs = data.getJSONObject("outputs"); + String resultStr = outputs.getString("result"); + + return resultStr != null ? resultStr : ""; + } + + /** + * 根据取证单生成审计建议 + */ + @Operation(summary = "根据取证单生成审计建议") + @PostMapping("/generateAuditSuggestion") + public ApiResult generateAuditSuggestion( + @RequestParam Integer projectId, + @RequestBody List evidenceIds) { + try { + final User loginUser = getLoginUser(); + log.info("根据取证单生成审计建议 - projectId: {}, evidenceIds: {}", projectId, evidenceIds); + + if (evidenceIds == null || evidenceIds.isEmpty()) { + return fail("请提供取证单 ID 列表", null); + } + + // 查询项目信息 + PwlProject project = pwlProjectService.getById(projectId); + if (project == null) { + return fail("项目不存在", null); + } + + String personName = project.getPersonName(); + + // 根据取证单 ID 查询所有取证单数据 + List evidences = auditEvidenceMapper.selectBatchIds(evidenceIds); + if (evidences == null || evidences.isEmpty()) { + return fail("未找到对应的取证单", null); + } + + // 收集所有取证单的改进或整改建议 + StringBuilder suggestionsBuilder = new StringBuilder(); + suggestionsBuilder.append("以下是各个取证单中的改进或整改建议:\n\n"); + + int index = 1; + for (AuditEvidence evidence : evidences) { + String suggestion = evidence.getSuggestion(); + if (suggestion != null && !suggestion.trim().isEmpty()) { + suggestionsBuilder.append(String.format("%d. %s(取证单 ID:%d)\n", index, suggestion, evidence.getId())); + index++; + } + } + + if (index == 1) { + return fail("所选取证单中没有改进或整改建议内容", null); + } + + // 构建提示词 + String prompt = String.format( + "请根据以下各个取证单中的改进或整改建议,总结生成一份完整的审计建议。\n\n" + + "要求:\n" + + "1.对提供的建议进行分类整理和归纳;\n" + + "2.使用规范的审计报告语言;\n" + + "3.建议要具体、可操作;\n" + + "4.结构清晰,层次分明;\n" + + "5.字数控制在 800-1500 字。\n\n" + + "%s\n\n" + + "请根据以上内容,生成审计建议:", + suggestionsBuilder.toString() + ); + + // 调用 AI 接口 + String result = invokeDefaultTextGeneration(prompt, loginUser.getUsername()); + + return success(result); + } catch (Exception e) { + log.error("根据取证单生成审计建议异常", e); + return fail("根据取证单生成审计建议异常:" + e.getMessage(), null); + } + } } diff --git a/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java index 14e4872..0a8132a 100644 --- a/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java +++ b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java @@ -1,5 +1,8 @@ package com.gxwebsoft.ai.enums; +import lombok.Getter; + +@Getter public enum AuditReportEnum { // 基础信息 @@ -43,14 +46,6 @@ public enum AuditReportEnum { this.desc = desc; } - public Integer getCode() { - return code; - } - - public String getDesc() { - return desc; - } - public String getCodeStr() { return String.format("%02d", code); } diff --git a/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java b/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java index 0c18f51..a13e566 100644 --- a/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java +++ b/src/main/java/com/gxwebsoft/ai/service/AiHistoryService.java @@ -51,13 +51,22 @@ public interface AiHistoryService extends IService { /** * 保存历史记录 * - * @param projectId 项目ID + * @param projectId 项目 ID * @param requestHash 请求哈希值 * @param interfaceName 接口名称 * @param requestData 请求数据 * @param responseData 响应数据 - * @param userId 用户ID + * @param userId 用户 ID * @param username 用户名 */ void saveHistory(Long projectId, String requestHash, String interfaceName, String requestData, String responseData, Integer userId, String username, Integer tenantId); + + /** + * 根据接口名称和项目 ID 查询最新的历史记录 + * + * @param interfaceName 接口名称 + * @param projectId 项目 ID + * @return AiHistory + */ + AiHistory getLatestHistory(String interfaceName, Long projectId); } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java index 76b68ac..fb392b6 100644 --- a/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java +++ b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java @@ -5,6 +5,7 @@ import com.gxwebsoft.ai.dto.AuditReportSaveRequest; import com.gxwebsoft.ai.entity.AuditEvidence; import com.gxwebsoft.ai.entity.AuditReport; import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.pwl.entity.PwlProject; import java.util.List; import java.util.Map; @@ -31,16 +32,16 @@ public interface AuditReportService { /** * 根据项目 ID 查询所有审计报告和取证单数据 - * @param projectId 项目 ID + * @param project 项目 * @return Map,包含 reports (AuditReport 列表) 和 evidences (AuditEvidence 列表) */ - Map queryAuditDataByProjectId(Integer projectId, List selectedEvidenceIds); + Map queryAuditDataByProjectId(PwlProject project, List selectedEvidenceIds); /** * 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析 - * @param projectId 项目 ID + * @param project 项目 * @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null) * @return 包含 reports、evidences、evaluate、suggestion 的 Map */ - Map queryAuditDataByProjectIdWithAnalysis(Integer projectId, List selectedEvidenceIds); + Map queryAuditDataByProjectIdWithAnalysis(PwlProject project, List selectedEvidenceIds); } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java index f69837f..c65aecc 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiHistoryServiceImpl.java @@ -72,4 +72,17 @@ public class AiHistoryServiceImpl extends ServiceImpl wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(AiHistory::getInterfaceName, interfaceName) + .eq(AiHistory::getProjectId, projectId) + .eq(AiHistory::getDeleted, 0) + .orderByDesc(AiHistory::getCreateTime) + .last("LIMIT 1"); + + return getOne(wrapper); + } } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java index 75e33ed..cb4843d 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java @@ -1,5 +1,6 @@ package com.gxwebsoft.ai.service.impl; +import cn.hutool.core.collection.CollectionUtil; import com.aliyun.bailian20231229.Client; import com.aliyun.bailian20231229.models.RetrieveResponse; import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes; @@ -17,11 +18,17 @@ import com.gxwebsoft.pwl.service.PwlProjectLibraryService; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; -import com.gxwebsoft.pwl.service.PwlProjectService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -43,9 +50,6 @@ public class AuditReportServiceImpl extends ServiceImpl queryAuditDataByProjectId(Integer projectId, List selectedEvidenceIds) { + public Map queryAuditDataByProjectId(PwlProject project, List selectedEvidenceIds) { // 调用新方法,不传入 selectedEvidenceIds(兼容旧接口) - return queryAuditDataByProjectIdWithAnalysis(projectId, selectedEvidenceIds); + return queryAuditDataByProjectIdWithAnalysis(project, selectedEvidenceIds); } /** * 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析 - * @param projectId 项目 ID + * @param project 项目 ID * @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null) * @return 包含 reports、evidences、evaluate、suggestion 的 Map */ @Override - public Map queryAuditDataByProjectIdWithAnalysis(Integer projectId, List selectedEvidenceIds) { + public Map queryAuditDataByProjectIdWithAnalysis(PwlProject project, List selectedEvidenceIds) { + Integer projectId = project.getId(); log.info("=== 开始查询审计数据 ==="); log.info("项目 ID: {}, 勾选取证单 IDs: {}", projectId, selectedEvidenceIds); - + Map result = new HashMap<>(); + + // 添加章节顺序配置,供前端使用 + List> sectionOrder = new ArrayList<>(); + sectionOrder.add(createSectionInfo(1, "基本情况", Arrays.asList(41, 42, 43))); + sectionOrder.add(createSectionInfo(2, "总体评价", null)); + sectionOrder.add(createSectionInfo(3, String.format("%s同志主要业绩", project.getPersonName()), null)); + sectionOrder.add(createSectionInfo(4, "履行经济责任的主要情况及审计发现的问题与责任认定", Arrays.asList(51, 52, 53, 54, 55, 56, 57))); + sectionOrder.add(createSectionInfo(5, "审计建议", null)); + sectionOrder.add(createSectionInfo(6, "其他事项说明", null)); + result.put("sectionOrder", sectionOrder); try { if (projectId == null) { @@ -499,12 +519,23 @@ public class AuditReportServiceImpl extends ServiceImpl reports = new ArrayList<>(); + AuditReport report10 = new AuditReport(); + report10.setFormCommit(10); + report10.setProjectId(projectId); + report10.setReportContent(QUOTA_STR); + reports.add(report10); + LambdaQueryWrapper reportWrapper = new LambdaQueryWrapper<>(); reportWrapper.eq(AuditReport::getProjectId, projectId) .eq(AuditReport::getDeleted, 0) + .in(AuditReport::getFormCommit, Arrays.asList(41, 42, 43)) .orderByAsc(AuditReport::getFormCommit); - - List reports = baseMapper.selectList(reportWrapper); + + List reportList = baseMapper.selectList(reportWrapper); + if(CollectionUtil.isNotEmpty(reportList)){ + reports.addAll(reportList); + } log.info("查询到 {} 条审计报告记录", reports.size()); // 2. 查询审计取证单数据 - 查询该项目下所有的取证单 @@ -513,7 +544,7 @@ public class AuditReportServiceImpl extends ServiceImpl evidences = auditEvidenceMapper.selectList(evidenceWrapper); log.info("查询到 {} 条审计取证单记录", evidences.size()); @@ -521,8 +552,7 @@ public class AuditReportServiceImpl extends ServiceImpl evidenceWrapper = new LambdaQueryWrapper<>(); +// evidenceWrapper.eq(AuditEvidence::getProjectId, projectId) +// .eq(AuditEvidence::getDeleted, 0) +// .orderByAsc(AuditEvidence::getId); // 筛选出勾选的取证单 List selectedEvidences = evidences.stream() .filter(e -> selectedEvidenceIds.contains(e.getId())) .collect(Collectors.toList()); - +// List selectedEvidences = auditEvidenceMapper.selectList(evidenceWrapper); log.info("实际筛选出 {} 条取证单进行叠加", selectedEvidences.size()); if (!selectedEvidences.isEmpty()) { @@ -601,29 +635,26 @@ public class AuditReportServiceImpl extends ServiceImpl createSectionInfo(int order, String title, List formCommits) { + Map sectionInfo = new HashMap<>(); + sectionInfo.put("order", order); + sectionInfo.put("title", title); + sectionInfo.put("formCommits", formCommits); + return sectionInfo; + } + private String concatenateField(List selectedEvidences, String fieldName) { log.info("开始叠加字段:{}", fieldName); StringBuilder result = new StringBuilder(); @@ -684,7 +715,7 @@ public class AuditReportServiceImpl extends ServiceImpl= 1 && number <= 10) { return chineseNumbers[number]; } else if (number > 10 && number <= 20) { - return "十" + (number == 10 ? "" : chineseNumbers[number - 10]); + return "十" + chineseNumbers[number - 10]; } else { // 超过 20 的数字,直接返回阿拉伯数字 return String.valueOf(number);