From 5d13f7cd01138d469cb72d6df36ae5ab7e6e4df4 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Wed, 25 Feb 2026 16:56:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 168 +- .../ScreenShot_2026-02-25_162807_126.png | Bin 0 -> 53666 bytes .../ScreenShot_2026-02-25_162819_927.png | Bin 0 -> 42321 bytes .../ScreenShot_2026-02-25_162831_473.png | Bin 0 -> 55532 bytes doc/参数配置功能/task.md | 0 .../2026-02-11-cust-fmy-relation-backend.md | 2008 ----------------- .../2026-02-11-cust-fmy-relation-frontend.md | 1084 --------- ...-02-11-cust-fmy-relation-implementation.md | 962 -------- ...2-11-cust-fmy-relation-import-alignment.md | 373 --- 9 files changed, 167 insertions(+), 4428 deletions(-) create mode 100644 doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png create mode 100644 doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png create mode 100644 doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png create mode 100644 doc/参数配置功能/task.md delete mode 100644 docs/plans/2026-02-11-cust-fmy-relation-backend.md delete mode 100644 docs/plans/2026-02-11-cust-fmy-relation-frontend.md delete mode 100644 docs/plans/2026-02-11-cust-fmy-relation-implementation.md delete mode 100644 docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md diff --git a/CLAUDE.md b/CLAUDE.md index ba7b98d..4cd78f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,29 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## 快速参考 + +**启动项目:** +- 后端: `mvn spring-boot:run` 或运行 `ry.bat` +- 前端: `cd ruoyi-ui && npm run dev` + +**访问地址:** +- 前端: http://localhost:80 +- 后端: http://localhost:8080 +- Swagger: http://localhost:8080/swagger-ui/index.html +- Druid 监控: http://localhost:8080/druid/ (ruoyi/123456) + +**测试账号:** +- 用户名: `admin` +- 密码: `admin123` + +**获取 Token:** +```bash +POST http://localhost:8080/login/test?username=admin&password=admin123 +``` + +--- + ## 项目概述 **纪检初核系统** - 基于 **若依管理系统 v3.9.1** 构建的企业级前后端分离管理系统,用于员工异常行为风险识别。 @@ -62,10 +85,23 @@ npm run preview ### 数据库初始化 ```bash +# 初始化若依框架基础表 mysql -u root -p < sql/ry_20250522.sql + +# 初始化定时任务表 mysql -u root -p < sql/quartz.sql + +# 导入业务表(根据需要执行) +mysql -u root -p ccdi < sql/dpc_employee.sql +mysql -u root -p ccdi < sql/dpc_intermediary_blacklist.sql +# ... 其他业务表脚本 ``` +**注意:** +- 业务表脚本文件名以 `ccdi_` 或 `dpc_` 开头 +- 部分脚本包含菜单数据,需要按顺序执行 +- 数据库需要先创建(数据库名: `ccdi`) + --- ## 模块架构 @@ -95,8 +131,15 @@ ruoyi-admin (启动模块) ├── ruoyi-quartz (定时任务) ├── ruoyi-generator (代码生成) └── ruoyi-info-collection (信息采集模块) + └── 依赖 ruoyi-common ``` +**添加新业务模块:** +1. 在根目录 `pom.xml` 的 `` 中添加新模块 +2. 在新模块的 `pom.xml` 中添加对 `ruoyi-common` 的依赖 +3. 在 `ruoyi-admin/pom.xml` 中添加对新模块的依赖 +4. 在新模块中按照分层规范创建 controller/service/mapper/domain 包 + ### ruoyi-info-collection 业务模块 (核心) 自定义业务模块,包含以下核心功能: @@ -161,6 +204,14 @@ public class CcdiBaseStaff { - **Controller**: 所有接口添加 Swagger 注释,分页使用 MyBatis Plus Page - **Service**: 简单 CRUD 用 MyBatis Plus 方法,复杂操作在 XML 写 SQL - **DTO/VO**: 接口传参使用独立 DTO,返回使用独立 VO,不与 entity 混用 +- **Mapper**: 简单操作继承 BaseMapper,复杂操作在 XML 中定义 + +### 禁止事项 + +- **禁止使用全限定类名**: 必须使用 `import` 语句导入类,不要在代码中使用 `java.util.List` 这样的全限定名 +- **禁止使用 `extends ServiceImpl<>`**: Service 接口和实现类分离定义 +- **禁止 Entity 混用**: DTO、VO、Excel 类必须独立,不与 Entity 混用 +- **禁止缺少 `@Resource`**: Service 注入必须使用 `@Resource` 注解 ### API 响应格式 @@ -234,9 +285,20 @@ public AjaxResult getImportStatus(@PathVariable String taskId) { } ``` +**导入流程:** +1. 前端上传 Excel 文件 +2. 后端异步处理,返回 taskId +3. 前端轮询 `/import/status/{taskId}` 获取导入进度 +4. 导入完成后,可获取成功/失败数据统计 + +**导入结果处理:** +- 只返回导入失败的数据(含失败原因) +- 成功数据不返回,减少响应体积 +- 支持批量插入,提高性能 + ### EasyExcel 字典下拉框 -导入模板支持字典下拉框配置,提升数据录入准确性。 +导入模板支持字典下拉框配置,提升数据录入准确性。使用 `DictDropdownWriteHandler` 实现。 ### 权限控制 @@ -272,6 +334,24 @@ POST /login/test?username=admin&password=admin123 - 生成可执行的测试脚本进行验证 - 测试完成后保存接口输出并生成测试用例报告 +### 开发调试技巧 + +**使用 Swagger 测试接口:** +1. 访问 `/swagger-ui/index.html` +2. 点击接口展开详情 +3. 点击 "Try it out" 进行测试 +4. 填写参数后点击 "Execute" 执行 + +**查看 SQL 执行日志:** +- 在 `application.yml` 中设置日志级别: `com.ruoyi: debug` +- 使用 Druid 监控台查看慢 SQL + +**前端代理配置:** +前端开发服务器通过代理转发请求到后端: +- 前端地址: `http://localhost:80` +- 后端地址: `http://localhost:8080` +- 代理配置文件: `ruoyi-ui/vue.config.js` + --- ## 配置说明 @@ -293,6 +373,30 @@ POST /login/test?username=admin&password=admin123 | 数据库连接 | `application-dev.yml` | | Redis 配置 | `application-dev.yml` | +### 数据源配置 + +项目使用 Druid 连接池,支持主从分离(默认关闭从库): + +- **数据库连接**: `jdbc:mysql://host:3306/ccdi` +- **初始连接数**: 5 +- **最小连接数**: 10 +- **最大连接数**: 20 +- **慢 SQL 记录**: 超过 1000ms 的 SQL 会被记录 + +### Redis 配置 + +- **默认端口**: 6379 +- **数据库索引**: 0 +- **连接超时**: 10s + +### Druid 监控台 + +访问地址: `http://localhost:8080/druid/` +- 用户名: `ruoyi` +- 密码: `123456` + +用于监控 SQL 执行情况、连接池状态等。 + --- ## 重要文件路径 @@ -360,3 +464,65 @@ doc/ ## 沟通规范 - 永远使用简体中文进行思考和对话 + +--- + +## 常见问题排查 + +### 数据库连接失败 + +**检查项:** +1. 确认 MySQL 服务已启动 +2. 检查 `application-dev.yml` 中的数据库连接配置 +3. 确认数据库用户名和密码正确 +4. 检查数据库是否已创建(数据库名: `ccdi`) + +### Redis 连接失败 + +**检查项:** +1. 确认 Redis 服务已启动 +2. 检查 `application-dev.yml` 中的 Redis 配置 +3. 如果 Redis 不需要密码,将 `password` 配置注释掉 + +### 前端无法访问后端接口 + +**检查项:** +1. 确认后端已启动(端口 8080) +2. 检查前端代理配置(`ruoyi-ui/vue.config.js`) +3. 确认后端接口路径正确(查看 Controller 的 `@RequestMapping`) + +### 导入功能无响应 + +**检查项:** +1. 检查文件大小是否超过限制(默认 10MB) +2. 查看后端日志是否有异常 +3. 确认 Excel 模板格式正确 +4. 检查必填字段是否为空 + +--- + +## MyBatis Plus 分页使用 + +```java +// Controller 层 +@GetMapping("/list") +public TableDataInfo list(QueryDTO queryDTO) { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Page page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); + Page result = service.selectPage(page, queryDTO); + return getDataTable(result.getRecords(), result.getTotal()); +} + +// Service 层 +Page selectPage(Page page, QueryDTO queryDTO); + +// Mapper 层 (使用 XML) + +``` diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png b/doc/参数配置功能/ScreenShot_2026-02-25_162807_126.png new file mode 100644 index 0000000000000000000000000000000000000000..0840a2e390b8c79b93bd54171e4a97833d4c32b4 GIT binary patch literal 53666 zcmdqJbySsI^fij8C;}p(v`8b}9ZEM!OGx-QC?K2e=D) z-}~J=#_zv7zA?UW&lnEzczB*?@4fb1bI!H)33{d=iE)SU4iXX)hP0HJ5)#r)dL*Rl zJhzeIoxJalyx}iYJE<3rNJw{oT>iNhO@EgN3F#4%wAfP>x5V`cR}Gva(p%fzI@ljr znjX?+XWym`tIit~v>>SzR~awY#B&~Cyqk|^uX1xWhEJ3)Md1qp<;i$Y{zTDm@3_D&&7tA>_G zz-nWftjXsi%L`gBpS{im?mzphgP)#Kq!4meTvQh*E&Mt?I#14My@NTm-9q|KF-bt* znCdjskf!OtawuPSzwb5oNj!4u%eC?(=hc4irf=&T8?Nh!+QhuI1eL4tY&T{Y_l2RY zXS*tUMFG7`bptdA`M80b`L&x9ew`_V4C>}*?%H|+V`$>RPTKosg=Or^G z)7o&NBWB$$%UW^dBPI$rYh>oQ;{nA!i!xT$p~ah>VWFY+_K(>e2NPF?IBh3_W83n@ z$)CFoj}mdus(sODiW(FY$M|6{M>VJy{f$2g}|ZeNkee+ zY-uS6xxouH>PXds)sei4sk!;jj!Ru0ZYs5&M+$Ll#&uTX`zhAH6M1$_6%<^G$}xyJ zK3i5id~@rdqO45oVKv?2pE6Y8_e4Bqfxh+E{N`E&6cjvST`#vj64FaK?hhXc#zrg_ z&W*oYY53!y9mrx9lieCInH--|jC)nMkCAJK-6PCI-~T){=BKNhbX2Em&;CZfk~}Zn zs7u({DQaZ2q-tW-PgUM?NxuTqk&=qIs#<542i;r0lq1Us@cLzKncSsS#Xh%YeHA8< zr0?qbuqmkj(>qy{pDi z>3`lL^;B8$i;25U^Nf-<(*}fWYmCy==xl7V%=6`2lJnTlmqw{6;$`(zMNvh6-@nt0 z>1P;;jT1ymwXn0T|0YCdd&oa~Q-@zXe`~$_VeIQn?9t`~6?u`yc)9f{ zzpG8`V=b=7kDq7t`SKgw_NAE0I#VrK)Z?`Dne8@Q>PjlmD)cch5Gk_|a$M}d!Cg`B z?(UYAkqHi~(_ZgM;I`#)d#R@hFN@rIJ;gH3PP~VFPLBi`I|m03J10s1&UeEt7y398 zD^n`!sFt`bSDiY~HLHRYQT2l7;~FCS|w2!juuGrbm>5CQ14! zH^ueIel1Ena)%Mkc5WFFp2N@i7?}M`6dNbL zcVXecvebzph>o{sT!@eSmpXepW)Wsz>pu;V>>rkC2d>5}vpbqYc( z>PH5$w`W2Qi?|~{3UHPcHdkpl5|Zdtb@zXHay?YeOaS>0&TrcV(W0HR!WhW#Er|>;1mmA4aOgTR8=keXl&&bH26BcGABQxvjc>NmL z=i%{`?-Zx@c;N-o)R&16F583L-w>O3Nw^HAKczNn+87%j*f67C?P`4D{OqcV$;sUX z-V#&Mvz!Df@!zOP`vKz#S=8*T2Ns{FT*7LjJLm@FoYlKZta9@84>j4?cP;aWR%v$y z#`;4Xi#-O>%VO^88739**8lV#f`yn z7bTs!bFp3=S=d>&gld)ZTLgi&PxivMM$`2+cHA!yU!M2oO#Aqp{n_8CcU*GWMttgr z-Ik=2iQutrkC7KiwOh>pY;mVxfZSUneBaHNCsq$D)9-h#0iYnG$B|DTUU$0r?rf(o30(lu52L~A$ zMg5C56}i_0w_Zh2Q4zt;M(effDDuf{*2}+b(fV%^4Zf?em{`m!jBn2`$Wz^umU1() zHLbTC3hPW5qM;PXU0pG@Jo3sYrBPJ6A>vn3f`*kLO%t!Xu+yes1(pJA*XmILeWr;8vFDXw``@KVx6eE^YES56)6(`H{M{<hk_>!yQei+f^Jd=cfEC@w@Xk$Zn&TUe4J^TGF zf-{fKBX*)yJAWj0Q-b>59pIoiIN0%=w$F2v79eUi&%DE*Q6ltbjnEuJW<+nrB_!Os zDE)kh=X@^Uv)^#;9Tuj5oN9Mo^J(gEeb$o4XWcBxP$=M`xg*-13o(|Vv{F-5LMr6NVz&L;WuyKsrk3Y^I7L?% zQ=du4XsP{~9$p&CT|$QynfGGkWaPdV=aZ8I518+i7Zs7P+teuJp}i>ApyRwB{C04V ztFCSW@2vC*U39rwPq2RSg~hCHj#A;IPZ8^oxQKr9olRzHyCj7y6*@*n#*j&Iv4hot zt)6uCo)7Em>;8>c6^{ zyw+9<{XfR*S);YGWba1Or}Nqt63nYJb2qgU_K)Y{4@ z`0W(?c|!R{9jTy=u~{oj;Obxjx7D%h;^HELQHawbna957)2C04v0tXV`rHB7OG``1 zMBnib^WTezjKryzuB`GG@3}Qm?`pfapF>qjNkNhBIi$eQj;JANXbO=n;ckA7pkW&866Rn5cf{=zPa*u3NAqS9oI6;ql4Yw zs>rF^stKVBi$+UJOD|ru@obXTd(jr_9#5eBji~9q`Iq939{2V7el>pZj3k&om)~FE zeV-t>WTMEJ8;v_u;dK$uqNn1lZu1PyTfSoHtalMRepuRf>+zgL%g)(`&RRr)agqP< zGQDEbr`B-Pfp+_LWG5~YN;+&8PR(B(6htMnnAYl%mg;+qgeRNl#>h+DL=nbrsHPW*gaPgL66VoeE|$j*b(Gf z`q>3fbRNZKMp6nIf9qm0aU5UxEm4C*e720qpef#i@K@%cf|EB&gXtAw*LA>{JVRrHA1wDiHU^Y zg_^upSI-I6iTs01hC$Ks);HI&avK}k{hgfDp_QNS&o?ccI7s_3Nrdc9b~DnrSDiHL zk|wHMa6_m{Om3A~4J=~l-TW6*0`QH`6Kq%xC#V;`<@+m$EoHVbP!`HfS5;L}1YmY` zbeIj~TEnUf3Jn_<3x-+6O4BZN__Y-fKT`}Pe4xy8+!xw5fLlU0uVk6yCWztr;Z z_yaL|3zHyoz+&hEdvkSlace4t)Zgb%e_Og_xYKICxDmOGtUPDGm4Sg$p^s>en8U)a zh5Y;uZiGcNy}Es&$_eAtzwQ#^mi?9abfQJMo#s6){;hG!4%dT#fJkU~7K7==)hLeN z*1o%;+plHKbrsl1+b3?_s0jJND{g~?bc|kmapPZ428r226*`luu|D$q*VMwl zj?MFb#+$dN4pGA9WU{EP9tVjOzlkM79j3re$P@33`|8~WU(ud*9T{FkD0U^8^V)&O z=EX64Mi2GBze8bg+}~+9V!Bs0<$2e)PspxTw{U2UU{;QbJVL6W0`ux))Ag_W%$#zO zLls(z7%W?7wtCiZc&6qGhSqo$Y>gi?4zTY0_YAiU4ajkWZUhrY>t5tvN1RDabN|}+ zsMXEtla_lMICb^0ZPK4C?|+D8_pR)EoOu=^I8n!0*0c(T(ENw1uV^{a`P^HPLx1t# zli}QV28df$4*2q`|6R5Vcj5@$Li$j~2Hli~lqH|_C;#g|)CozZZAWh@yxqsRx>ZVu zQgDOq#|B=jv;W=+Rg!Mh(Ej&d4fy{wT+tDv_t#K~uYT$O^P4jCevac>h`np(WuxQc z<3}2)G8wbyC($Z&(F$3f;o(=ed8HWfHEPwHRh={>qrlWWRhU=vEImFN3AvQce6_p@90Vtsa2M2tM5wW$fzLzx$~u-EZ67@?A(gwZh1 zDJiy=mX?4yrAenpTlKydbqZN?L=s})c1p|1^_y{kffUDXdRNcX%xw8*C=rITmyp`PXoMBFy}2M6FU+B|*w)XmMUK(oxEKfB0! zjElV5`?NeIh4f+^G;-LS$Km>mOgRGb;^N}f-i(lM-%jBU9E6=+N&K}nH39+x;M_a_ zAcr;l`jtgevL#jYU9ebM00z-RUI&;yBV%LlqX}1tIu>qjOf)op_g$lOiBO0|a&mG8 zMn-rLuibfRVPWCA#(=_xD-R8+!B-;Z$nUhW=QymJzqZBI%jMqJT~2ObcPKY+Qc+Xy zCUrl4{P^t80THYlmNexDh{{rn0U5ARb5+_VCImS-5B?mid0B;lB$1aNFynas{5b&u z0byw7@)LXA)58r|g_Evroo_DU;^N*HXGgGsXws&9POTwGAwY;*78VzobZb7^UdK8+ zSxWIbQ&3RA4l?=?h~@6?uCK2z=y~Ym>KfZ-H06Dy19P}GSg^OZ2f77T$bDzF8TOs` z*}>4}<|aHSI9tGA86L<|Vnfgb=)=4e6coh8#Yv>cvFIZUb9j#w8*iO<|BEm}UzHT6 z!_?-@#0g0@s-MKkajoot>SYfdQy6hf1DOPNsaq100+t z-+3MdrdXWkMn>}hBN(yr30$Em9Cg5eSq*<~N0tIzie=J4oSj|2e%)V0{|ySpafe=p z%zapEa6|(F0!&OznKetZm+^vvga0fh*dEVO$;LoTS&dirjE+_hAKecUE3+K(`LmxR zlA5hs>*3;3E|Ti@4)5QzK^m(hH{D{eqQStpPeUI6P`c{oyon^cPlox#a(nLloP;dq zZxLUI54FW8RoY#OF=6lD&(oN+8x_L}@0ltqDRm_awv%$(P6|*{FG8q?2L-*T_c>ea zOgM2KrBna1zO8s&oOK~nT%Zk;v_z2cnD&wgOxrSAt50yu5#5pHJJ=HF0R8`#M6H> zNz12FY&Wa#B~?>GHemT0V4JPs7qikhK28eDV$SP54l8vMmLC-%lB_NMke!_kkhAAl z0@jvpKKHke%gf6^np6ukqf<&cODZa+zPZ}!>L%-F*cWLK-@A7Y2dBiWCoL)}DxTBQ zWqsHl_G&bps%Ev5xt-m%p6?megAlmX=FAMJEIE03(~gg1k!>(Pp0M`|b!!*eV*&eq zcXX@+qES;*$Hc@O9v*tpxIk$lN@gKVJOuC{gt4@^_(Pi!{11AQO0G%{Yyg-;u#Ix{ z>gz;O8)%*W4)xVVd9TcL7@I>>&yL2fJ%utW*7LMPs}AX8Q8xD3cb{6QpG{F0=95uS zP>_?qo|69Nb+UWMUnFl}vfg*d!uoSUPKzjo zES%dnn3$L#0S$lEhlp@;a#|m&DAZ<35%#T1NYH8tBY`Mh8!FU;aRmn6*3+9h+!#|# z7T|sK=n*~rS_`Q!K(~B62e0Fz93bVy`1lvVi0OuF0s6V>g`3OEt>NMq8x>QK^>|^= zr9R_vS=XS?GkvpOQc@x+A_6KKeXbkC!RE@!B@EoTPbZhecQD%=;#FchQQaIavUG|# z@x3C64ZcrusO;!9mlY$+f5))TPeoO8etN9crXmN*XzX2}S?>dj4bgLXy%$Y6%2^=m z=2utaKYmqAy7_EE>kX!&7=e0XYA|r^fWAa z*GE>v_2D8&V*HMa=q>gP?Z1rJ{GPD5ZNE%XXt5uUdaO)|E!~|gSX)~wDk?f)QC3i} z(UUHzRciLU#H3Runr>l!K3}_vjf~81YRn3*1F0QQLZ{BleIQr$0Gh_5 zr#Ce{n52kE2{Fk!muf^`MJ5KBQ8lXF-l6x{k0)x{?n5~ z``vY^>rawDk=Fp(2${joCt?=&^C&)e_fq}HNwApZ# z`L;;NZg>nD|9tq+TVXr(JY8aECxP_G`SH%&>({S=yBV37c(}VCY)&LVp={Lo^rgOW z>wmofht^376~KV_oLBKJ7HfxZ={haAfD3|;?*?#?q+n`pK9c=B9URhad+)JwYsfxe zwisk7B%uo;t_HbH`m7qYrkW_jL|L1hBs{(HhQ)$tWcfeo?}-aqTM9lGv<6B&x`+Ss zC17}A-Jex}%7};vm`$J-0Paw#Bf<(L;U63y*Dg@=^zhidUgh2#;JBv|vq19}?Z#`E z3{@~!8yjZ^3$(DXurMDA*f~1`+mTM-vc7@rHxmQzI}+m&69a^ci2>yUczbaB2EAH- zwpu}aNy%OWg``PWA};`38X2NlXuGnq64*=A2*f&Q)iT?u$@X^H)MwiDK1ssSiHRMB z0_x>fFD?}_U?=^9t*fw|R{lY|S4Ne%$fHm=WLZX7rfF?yt#3f?(d2RP$Ja})GgDfN zR`qFtx0zRqZ~3cKb-6HVpVAWm%xc#yNQW!?`}+`CXUB8ldR{yJfKu$HT|09vRM>40 z(4C!~lJD_%M%6~f#%v*#PE6=zC_z5jgiO@d*2b6g#h^JDXvlg``n#$s_nx)x6k#AT zQ%egARn^rfH*UbbHUZu%k_uJ1p2kLf_!;;+8alcFFn7R&08CN^F@%hj_^l4SgWl&0 z0M}lsa~K#t8|qj1^y(<>>$Av+X!-{s{vufI#f6&Pc{hl&h8`@|YS?_fVT12`KHF(yDAo4+Wt7)a|D1aJkQIkE2E-P?l_?pU3-r_bvnuxYq$ z#%Ujfyt_-H>9kdPe0&V~YoMhi3$}lAa++G6-oSAh#=k<3;&}f*5#ErM4L_eIYu>&J zQ6#GWqkB&N=Qfzs9nzd0GY&$G7W<#R97tY5_yMe}tXOyS_A18pTvwjbdC&Wj#of-4 zaL=9HO&$9iq+36SQ4wvCG<4CwdV4?U<5LI|A_>uZ)Cy5iHUIkc3%Fzko}Cyw`AV-~ zdd2$GYTrlXc;97-%UNpRXR$z5r;CQ3l>78*9E+mYrb~>WTr}NyJx9gD#^!u>q)bvc z<5*rSVyQJ9oiqQxdtSsmU-E-692p%Y+zUe5jw>lDYU}FqS;T-Vg&gSpa z)m%@f1}}jbJLvt17}9Y^kQkUz7rn|=bmcDJ4icNma1qvh{@<0%9US|{s z2geaOv_XqFnylLQp=-zEH%^tm`tE#>UofAzZLzYqZg^o*O0j(+dHDp#Rs^2H+v+e1 zX!OLb#!Sf=+7SK)RhK75&o&y%n(D5Tj zeJOlAJUl>fPn|m0!(Mw2%~E9+p!GR%9f-4@(E^DA0qb+R!L6=7^!@vH0(O(ugFXCkdZANI-V!p>*T*6uNlH$xd)bKW_vG37Q%Dj3ZlHPTt0z1_V^&nS_GC#) zNZj%N?sxsxB=AAV5Muomwt7uZL24u=CjJZ~@!VUKkC38#5W>#JW@T-C3-e+AfJL5K zfd_mRul{bs(kB!5_}~8Py2ofM;Va$Ys{W<3EJ@$wo3wDSqKi^>L#1dx1tpmqEG@-S zeeV0pDPi%~2J@m6in)=Tm`IF^TMADM#sbix^WAo4$gt$(=M57RwU9sU?d_Gb6yy~Z zq2%^FZvdn%&`eTx6G+%{z|zzUGzV(j_kh%hCXI}YKs$`Ql+@RX3hs3jZ9rmMTiY_t zTI2Q@P$0VXK7#D*eJw3CJUnhtL?VyK1~Aw<8_USdtkcL=%7LM3X=rFvIV^xyWqMIO z54zz}uI=w93b?bi7ef>n;!_I>)dT1bkMwVoKHkp1hO*VOp=`N@mR%5ZJYH(1KW+2 zp}m+PZ)gqUmPc(JByoP1_2sdOEf9U1leNG?x&i|O`}_MZDTsORXAKO1NdVIiYI)Y< zm8>bks&oTEI5d3lOB)-E4?@;J4;C3TfBg9I_wU~z{ip!|e*DN*$pv1v%UNnUwE2$I z=h$_lyR8k@BOrWbWrZ#psz{yv{ZY(Vcl-^Ch9UUpDglDK1V|HZN6WiMP*fh$P{ni~di zv6@%-=WxRkvQ3^EBRO=w3_PZ$b;k)#+T{unnHtgOwE zlB^dcCfBZA110C>6$&d57Pfeu25TVuIR$($ypXtMW2B^|x0emnOLt0Nro3mYd)K8x z_d)RZcGxFsGts1hX+?nY4x;Ma+qWGM!QeuHY7pA3(pi z8WjVMc%5!kfGh&J`_1Ek4vR$CM}UpZ0kSmQ^8NewHTH7>V&o%`V)gZBz`w!9#@6*3 zL)sFDt~C)+Q6Q5c(ZT>kG9t$nbM=NbF!2|D-EE5+h#{9m8t zsQH^V^{_jfv)bt-%ZB(|eC@on-(O)arb|sn(kR-Dmae=-#ZmjB9TyYB<+eQyN<-Lf z`;{=!y?ZRa=LiT*P&)e{oMGQXnujDc%kSA9!zdKR4s|OkDr0bOe1Yf$Vo`v&;R5vo zuH4pWnI)_r*ezaJrQlu(mwGP$P6czC78e{NP%4)mrJtW45GS~}TdXYyjOU!^@#7o_ zHHRtzV9v0^0xW@Tfc(X}e;+Qz6d@%bCrvr-TZvl2o;f+}JK454tEU%@+yU;>WpuwANzFZR zNkTH7GA7@*C@no;u;vqI^v)i-xVYQ;!}sqlNl6JeU~~$bI|n&erCnz=!j_>F7aQve zA{NY9*yHEB3+(|uL-4q#Cns%2#gOD6A_qrDJ>ZEUG@YHC82GTiK~95khY;QcDFk*& zBR~|je0-v?uMg}7uX;W#;IpKFWlJf(z{x}Kn8SjCMgUY{S)*kZ`mz*_*+u~RA-EB` zbvjm7`4Z{&j*h3mY(oz7WHXd}eJ>#B)pAwLVTvw$#{SU;;Sbwr5+1E3l;~)$TiABA zG|1O*tim=0-i96ZAu4S^(tvC_4knlOtQcyIF zk9*C0_Xh&8^*gmG@q+z*!xE1TBqW$;^WUGI_Mmhp@|Hm2K}AK~TIfW==i%qyBfBJ6 zUO{9&MCT=E_*Q)4B#V6oM%9P^jH2AXSQ-`Oa|q?@U=~`_qWoMZzZ|~8QN$i6t^0!a z66z64-nib=5Dqj7iT_AZ!a~c)$bk0(NRBNW#wRE^!Sb4qo7)vOE9`1Iy*e)y6y#<%usPbH=%lTqQ;*n{Ra8_YXJcpgToQJFHH%J$xGXCpY^{}oQ5Y4Cl!9k^0EPV0^lWBCL&@G#~^D!av&~Oe4^%8G`*>f z&F0z~a3OG#1c9m5*5WbiYNv|$16kM!iEJ}sOMNyBnS)8E`eR(&%^Nq4Pfxk6NBg() zA>%*`wSOQSNvHawcEPrYq9^o0>7zV#on zaQXU~qDB-oN7y4i`lHXsJ0uLJ#OBFn-v;V8Oyv+W{OlBfk_#6i5$YWLR8$#i1)5MJ zLD(2UE+XQxVp$IY7F=M!Zwv0wSbKXO*tm%81MG)%*g?=@GX@R~%wG`$2>XP{$Uazg z0PH<^@OaNp_Li+@%Yrp_>B zt`hus@IbzQ7ZE~$J37$VNXE|Y2s?vb{mVQ(gcrC`%lBhqZZ;^#J6hnd+8DY}2gd;L?C*Zjz7X9WQ}PM`W@=?X4yTXLw# z(g1_v!us;B+Fl##zX0L{0J8LsW3N2`hzZ#5oOE=nmsOXRa9@xE;7RiV7Q%gZ)KO4S z2siIwVot&Rf&2t-)~MpP*hAGoDMuyXLuDn;dBfnK3phevGJr_vO{I`e!QJsfrb&a` z`J<(UKKhq$L<1*b=X(Nng+v}!kA1VI*Yg5+c$ejx&X26=P@8~tD=95C!e0bG4K6LC zpil=^CHT75R#q`gI;&u_EHCr;AdaCtV>MZG0%cQ3WF$gfZeWidbM{`6DySucJIh5$ z`5Bms@Y#MOG|>m&r$2;XC^l|~pClwC9`ZSv&He}i!~lmUUFz3rJqQ$7R3$Uw`=0 zvufsvixoj0E^_zxfn@2@Y>D!k#6JmwJAyMbw)p9df1~-ie$>e*wt^%0P-|tHp_&Ed zW#ufaShej<$pY>MAe5JjnN|T!0Cgdrhan$;5CZ4(M7j$Yb~4lfpoWP!*@H}LF>I8e zXUWLTU9l{(Z2T(3^RSQz72EM9Ub14Q`*PlmoSHFyfe*6(8MO zcH&c3R*o?a(T$0Zr^CHtZEcM)7K8`2@RM)H;o$3i`qTiTOCP^Xvp^|FBZd?4vk{W> z5+)oTDiRKZ>Rp7?k9yHp7q7r z;4xzuP|fkwma}X%?}k(PS(c4Le>D{6q0%cW+n#1$^yw3ri?<#=d?=2lC!5vx821iH zxU_KPyaBL95l%Y>Lw1)|MXBax@sh0b!QLYDLI`1Bxi8!0GR%Iyxaq%7%-g(*z3s#4 zgT$0dNl!6QZ@k7!FV%OMGI4VsH6M_;>l%lG@1)keAX@SF%9DQ2aVq7g+d*7(bciYPAVqN{DbU2W z@9pkF1xMH8Y06It+rDo((@!P{z5Z`EY}IF4>~oJ;SGU0}jT0{K&)0`#4$# z2xW1%k2VxWK;U{Ij(1`hwZJohb`23R*yK=bfNDNl-1~Y4n!m4$0qAw6!bY890S?z| z3ALji21PSHe?tXnyRBzsKle2h@LP+K1N89&+)w#9ozDReQ>9b&@ZiIOY%$Q;8Hzc4X(xf91?>5B zf5j1`2go{4cXvju@{Y;LezuLcOh(l^4GX|2@Sh zB=}Qd8s-{y^!fQYIEmo(0C>V_s6I0eWWR>qUI(yt5XO8-HjrvFGc$pmK-ZDiKcL<& zao=Tw5DSBV?FX=8kU!-IqGprdneOaqIDx)T>L8aumeoo|Og zgn{S*<4a3RgT5+Qr`*1kKr?8Gs;jFLMr>bCX*l_$AZAeH2HX^clo*HM&-a%dM-mbc zSUHe6#V%X7@bK^`>fwOz(%pRAk08K7qX((ocO@Of|sc&9uAJOhDIyRB=Bfx zR7JaioTT6i>j+!}s2ik=iF#jQIy#xcq0>KqB*eu#Kkisze^bbkm6xYVgPMvntiRU` zwDACOCd9|D1f%1zawaY=F2p-zRwyg_08hK*55NY%4+U=th^+^#PJh43Wrqt^i#?3t zj{j`)PerJJH#e@L;W2R#@4Xq=SYMafyMJZfH)Jhcr?dlzhsoNj{RN=~_DLua_vS!e ztVB8irv)xn60ajQ9bN7+BNPas`i67IA2jKYARMn$NaR4KV7Fd2H9%<#G@-kjTNJ&z z4a6^9v;{Qlz`GDQqtNPQXJ-fRLPrd=;{#`fHk}-`ycaQ{AtCLZotzA}TaJ7}#K_ML zwY3uq3vF#|m|E-|s>a*ef&yQ^d1Klc@4V7W1=|o75E~n42D{ZTOOb}QlMCO~N<3jD zedLGJ1H)j}u6z_B1#LJQ3_?(80Mip(B;GKJG&bX392Wh7diJ5FhK7BhrEqW}A=`kO z-ScN?htHY;g9FnGO4A)(T|jVMfulpA;Bv1+`y7|exEpv{5Q*4#{L9PB;m`^Uq#MW{ zx7F}h;3$cSiO|xJq4WZ5-ZbkxrHo$?JSTO3-oHn;wS}$gi4y_E25#;p)6chV-h`7o zcVXAE*b9O)$iQF)VFaazV1UJKi-9NXu}@1$`Bop_r=L&nV`3uMH;b^@mk<7cMJ7f00gFOHyV6b{-YmqBoO4o=0h4PDn`A!@$KgsO3{mBR}u9}aq^w?G*B2dH8ro<1)fa+LV!S!nTw|l zdeB9{4vj)NDs-{!l3~Pcc!SW|GhXEgLKIpZ2w7gf^7j|l(D0EV{#BnsiQpLTpO250v= zx9rrMUJ6{WQc>MROTDVBK0y_Ff~=mS0)<#Je+Q}(cp=THsi_d$6i_eo-JX65kr@@m0l*e2(O0M^42>b$ zl}|3SBqV^Yt}cYc0weo79X(A>ODaqJwtb`NM=kBh4Uga=&%R=FWSN5H|rXQh?>l|FIu9^jlots^5R@He=C zV^FSwjSiWc2g)h{F+21{D+hlJHegS|{h`|bj4m3?Ft#Wlu&_VyT^>iaFJxSFT}AFR z&fzueN?1TvgSS~(ranGC&;8ctWTQi4sKuLgxgZty?Lp1C~(1A}=7cc?cVyHBJ1Et9-6ei0}4 z)Ass95)yea=qzZKedkZ~IV%g=01H8p!vZ2=UN%EkfCL6jFe5CcTF_rr<=E2HBuWd# zNLP^9w{G1E68nCX7t(CtQ_BryLfkt*dXK+>-uSEpeSE(yWIYVgo4}T8D*1Qb`;;`Y z+gg9!4`~)j4KyA})2XoyoZBD$UW3;jn)F}yw2(EVaC7;xO3k_H^2m*t|(y_H z=a~;9q&R-EV_C|PLz#mv`cL(PWOq(vQuRtoq*smqTz=?1)*tj~`H%kpN{4BT!2edE z<{H+2rxUmz{r5c^+bI9*zOQay-36(E=-z)v8r-gv|M%q_wom>ODbpC(dg=|_@d*jc zI@M8$i43vr^mKGQVX{(EpJLsTnd#}%g@M~N)zt~{U0T*3K9I+@`)?WFR8vvu9~`Xq zl6mr^AyQEY5V`ia6X|9C+ab2@J(M^4V0eS;{GF>Y!}}2x6&0qyb_3XMAfwyiyF}_# zpFhXK#xB0>I7xhLHI;3QQ&$8MH7(5miVQLtAC^vldVl%CbZ8+Wf}8~YIHE_A6ee;; zC@=|?Wu!_E%AZtBN!RqlU(Z!S33qf<^A5R}iseVUO!XT!Wtzf&meYZT+zE#kARRX` zrdZ+ahU=u>`#o2F0`MY@{)yh+-J|-8vliiV))sqc=uPq3MV+vv#BKwE1h(+A#=qOG3FG_gA95LU&uz9s~0)P!> z8jv{`1xrhM{{;ps1t}?>tJ)YG##N#71|$A)HtplbSP!6&7huXgIZHQ2o5>U_usseb zQd30aJLJn&d*XvT<4v|@X6@tXsE_k*RTo*Xy*-7FLkd_ZTX-gReFB^(WQ6sN8P2SygbIF z4%+clR8+*o#jB=xpxCm0@CO;`Jxmx4H8m_TFq5Zw1CV>bUw0seJdz8=D&PGcNhiXa zsNnKx*LrZ0*eh<@6XRiFK}G5AFOA`iL->p*;#tWEV)NTpZ~cpu;Iiz?^{@2SL1M=0 zI)Db)mxih^;rUHZ&Td4u;Sv&Rs;NOGijKkW3s%QJ{wojtD!?Y;=Dt`}ugBI!fmSMD zG0+F5mKVLLX!u-9tDQ;QWh?dN`Q#80lF!!)GAJ@0dqz8*_U)*J|XCr>g9J1ZV#0L6v}1t zu7ScAC*44lKl?(FeJO;w4#uhhe2(NkK9nD8W>ERpkXU-ay&ExBe3zB%5v+^dWs$ni z<1DcvA)Cq*!-?GRmI^%=^~@b=>wijMOQfg2@$X~8Aas7I@iUwnWz?!1Y*Cl4>zD#h z%%6B{-EKr#ywey(mbf-kY(W7e?)$z(K6yV%OI@fsc}Lopbs`f>9H2+-M*3i;D*Z* zqN1?;g&l#fjFi+%ZhXGWo4+cWusM3}eeGq#tCyDlJT){K=KD0i!iGHYN-Wn5zVh49&r}=k&vQ?a1;5 z=)CyQ_3aNzx`p_)|6DUi{U{^R>F4U|3SFi#F%S5%)$#xrf9wg|+<-zO=v)FW!#C!5 zewatYEZ+wmdut+~{NhOLdU|LL2pHK`qpOUBFL`lVf03t_H#R!DtSg0ymKwW!P8Wd? z?xb*jGB-O52Pbm#@}Nvz1Rf>897JzN(T!I@0ct;i1a^QK3>}V80t^E+fQeaHP!Qc_ z1SQ!A0V59`r@sq>C#kJXT)<*#Tcla=oke#zIP4j2!#tQ7e0-J)iXZ6u1h%KFnr&6 zW0Z6K1eIk6U=HL4d`C@B3dPc8h)a@sPu|`yw%pWuq9Hm%THZJg+Z%aE=L9WdLW~ab91X7Pg9}r+I{@4 zo%~MZZOQLoCpYUt}4K z)g?lD5DJjTm4M;-^%})$5wC>X$chsj3*sd{gz)|66Bs?cq`Ufe-x)%s^Ig=osN1;Y zj|ot12n6Dx+w`^co=WpRY8ThXjEop}?|#Y2vF9OXsvrngq=B}-FWK4mVvvw@DX@bQ z6_#$({OnC4r!OV=0R;lcCs0%31O|Yh|;kQhRqC=-1726 zD3j`aa{=G`a$`X}0;o*bY^|>oGl`gVYh);d$xbFDtNygtwz691p7LV1Xx_dd`LR`E zgT0c744IjcQRnN+r>d&aEjDjjRCwRt0(u&K{^_^#%Z5qgD#pk*O6(jspe03F-G`mn zMb`4_U7KVdE%L2bD|1YnnMg>HI@Z<&cJ&-H_JkHj_MAv8%j|N}fejbFz>gaE?YQAM z>#L$CDf%+;tyq;)m^lyrWn=HL{zn4zzkyXLzmuZeb-o`!bw&TcTe7F~va^FDMB&Oa zNGw%6NnJtXyU0l6IgO1^gT!Xx5VsE2tDFEPCcPo#O;%ur`WLnb7G-}pqoVeM+<|lul?^Ji~r+X zNqOn~tpZdULQ%$nhq&&`&G#Y<59p(1U^Gb7_pWGZWYB;Ggi%zRky1|okh9-&9^x?Q zI9Ogb1I%BmMrwF_MOt6>;u90+7ZikshGL+i?l|kblmpSDs;rCy&=cG4t%HZOEtr4( zvMK2!N8QEw>FMS-%$XxfHVE09Xb-%g3Rd^!Hj>=!t7oMSJwNyH7nxbpY)FFdHWKJQ zt$u*SLiRthW)>1x1NJyMus50Oj>*R`n~d0?OtcFY&AL4n%A3^8Olk^>H(h|cU|~Wl z6`VDB*iJliBY6o-Dndd+fjU+wF!S=po#phcEOw`)Bqqw;jPEo#gvN6yReZnBh_neg z2gr6pOw7+O40!`!F!eyOKX<@_&wwoseBW~43m+G;tUh~UIEF97eA=#9cg*IM0$#Ks`5vSM|1y2@l7Xs2t?$_&T8 zLb)fWd6<})nW3xktQKjz32MF2pxDtNSDiL-Nr7SdfadCYAE^Fugj>Ma;QGzk;3qI$ zOVuoZP72d&bT@~ITdbj(v;Rr_TxAMko`xywxghk*2u@BvJ_b$$Req)=d~}pQW?^&7 z^q(^`!C0!s6oZbg#f619IqE-~-WL&<&i2P(vOW+2c3G)I2imyvk7Zhp>u2Oi!A2D31!w6>y2yA+aPQPI&w zKB)s16VT3dD)sTJ-0J_u+nWbs*{*$~ccnp!MucdTN`{hoN~J=Sd7kHF$~>#2GF8eg zLZwKQDVZZelE_WS95N4?hp>N_o@cG~?RUNJ`|Wq{Z~JHUEX93Y*Lj`iar~y^!l!Qg z$jZ{35iUg~r9L(&4}f#sJ;Mrdc&$)ea0dJ@G&IcqdMovFwo1p>uG~bQz<%HWbC7g; zqX#sb&TXD#DMJumR&31yFO!mtr_32ghYKD`V=D|rQl_$%r^ zbktX5Ye$EL#nlghw()KsVPj%?`IV5JnBT#vNP$^q!)(@V|IZ!zbyXPKtN zEGlDNXMBe_2G|Z_-K2|Lq8Z{6T6(k;w~5)CO21MCQUWI{7?y`Q_3)7T6;6)6XlJiWlt zdjyr_ceaja=46fDB8s_uZiVPhsk_HAbczIkr2!Bv{qO-nOdj z86+0kkJHDj&A06m6I%d`WF;n#`&Z%O{!IB$p9mgeB`hGrUao-O!osA#9#6cXn;ic$ zc#+IL*-{)vum5RaXs88*6P|{?{;r+WbaYGY*XZEk1B>DTc?LujYHDg{PM?kv_Y~&g zNy*K1hkO8A(jOpEL2R-X^b`!Cj*gSSMxpP*SG4QThtrVKq?WP9CiZ3bM1wZMBf|Cz z*&y^z*~tZbq{4m2cJ8$$AHP$iEdzvI^OfmN!EdKw_A%Zu^6i@{e|e}|AqYKv8!TZs zF@>Y#8;-LaKUO^c8_Io-fYY5T@hb{a1P(r0y`D6ETQ`~E=TpCjqu?4~3FR{WcGNsW zlmCGYTFRy4wjg2 z%I3YeI)LzP;54Ge++q5i0jQl$7=3YP_{pM zb)6gp{n%a&4fn|UD0Ph-m6AEomiX6wzI^Etq)^_!N*MggHm)fq_ONKN$UAqcYir-Y zUgg~C+5-Uw5JR+;s3CRUn13bfd1a$NCv@0AE5zV8@2^XQ@bF-0xvu+A1{5v$E<~Vh z3&YOP&jBWI5@dm>2znU(ix=(bsk+TS!KF$WdXN^{lM$- zxyU6)$i+c%3HgtinVHgKE^-A3($7xJW9rZ6=G^?GE+X4dS^3!oj<+^#T-v2V(4^VT z^OE8+?p|pxZgGJ6N+&J3NURW|Ffb_uO?11dQIhrW?TE)9LC41Ir-$Cd#{jn?lw`eC zC@0Ubx?VpoSOD3vW72V^^RPnLioLZB;k);s3K!ee3z3`^EWbb%5(XLIoI z;rTuVZ<(EX-}3pHg0|J<_V{s_oH{0NQDTJCMn`AoH?LJL?>`FJk|VhS-e@ov9j)UF z&|)mj-OzP0_iju~mGm4aD2e&OV{gYFo?#Cq+OnjU$4?4z*HZ^2U>>=#BY9~JuMoqzClyJk(obG)Bokmmp5*#K_|=dK{>GwQ3(1H z2{O-_tS@D{H`Ex)%7%|6sa|q+o-SBuDgc6p+v+D}2c8cuX6y{7Po09iD!B91o{Q)Q z(ahqvK|wTCeUk3nzAeF>D_5;5E-r>SMvh%cPfyRqMexB6oSIv<)F5o3rUruMk=zk+ zCv8spwj9Tb_%_DEM3?B1{{At?Bl7m{StQ^c+d1w*p`Bv`~9hJN~O z+p0M)ye;FNKZCb;l<(6U<_&AYR^Y%oiA#9YY(cBd9_p%XA%_LxnLGNpjJhsRn@i!` zxontH)*0*$SkjokfTuwx!{VsOaw+SD39RwYY{~bGM5|~r1#&@Q0p(jnCm#9Dg`(Bp z-w!P|R;R+rP%bQg*emr@)q~k2baj zq1LxJ*_sLqCDqj%aA)3V4pmoQz&4M=G(%#&-_<>r8CZMn%%f7LqRL`Jq5x~iDmgYd zoFIOv&+sX~{QY$M-p}&J+}T})OB3-Dcm)gu>i0)UiCOR$D2Mm(G6ezLvdQ~(Lz^pJ zT7pr#9}Bj=It8`iIf45xD{lq9FL_u1V>SuUYbyd2OD zX9s%6$HkSFl_e^_gq0Xu3yP85?ChnU^e;r-zI}8#Y&bZs^!kk(?|zjsqfp!CrF8#r zQe+v~{kBEbXws9A!hSO9-?HWIDu(QV{B%EWk!PP>oM-xLYe~A_?`r*Ul;kTYf zAD%o8|Iz6M&Hj5Rw7%z&`PJe*Z}oPV0ZxM`6Wcbf1_p&U(@k_9b6li#)NFDe*O@(m z;c@%+61!&NV~TQkT;Q)77p+1U6T!s_xR;6PlWE#IhCB#!1GP7PpE*BM19Ss*DMYLH zffeo8;R-@6y)j8K0oBLw){b|B8Vz)h$sBZY1F05s0+qcGuPxeMySJ?gq80D)N&&7b zR1M=*FJj)aZZP0sysj0SK1Av5kvVtHV22Hp_(w1`8fRI`z9z9*k`C%cJ$T>&^MXKTq^V#xc{E?oS&GySxh zU94|vq%Hj@%iYMLz(<+GiIy5Sw*1~V5O^+X!U7QG2m*1+UARUkzK)vu7Ct(R zxX=m`I3!qJs6ha)a>5h@qY_ZC-<39ASKw#?&F($d7mqt%{`oU10PRd`9WSp%;3h=S z2o$4`K7mR{iZoQ6dDE@#(1sIkq1jnF&@QkVdn`O(k>}g;M$r5gTqf*K(K=$GCk3%-(c zQg{^_voL6e02M4KzEth34Uq-njX>%Jm65_{WH7^jhYH2XN z3a!XxE9su<-$RfvME6X(EkJ!^Z*SjlV11&@!0Uv~Mg{k5j|h9Rl-W((w-0GDOKIbQ zI-G;!Z1^fV)VAPJH9Bgi#6drK&sOLS8_3e|tmZp@{M>WJw@MLrD1D-kC?_Vr6NW1y zvJz*~7|^y~9sX>lWk6S9xX#DUSg-4T{?LQ(LsRI)x{yD!b}bI70X$H=9dHG~r3F#1 z8SbjS{$p(}Zca{Z*aXY$GBPsYae=C6&E;PNJ{M9)a)x3yMV%A%*OcSwcI)b`k}V^r ziXz3EG}gog`mS&%cfXZm4?X(~jP;(oz4EfQ!u~9)@`uaf9hb>Ap6tL)zFTu!tW(ty z*Kpy&os^1(wl?@63BwX2BZp`~W@cu5{24&?#z&o?ebeL&18{_wIaU30FdZ!|8e1h! z`rK`_drwl)^7IAjQHckcE(;RmTmh+cj#)jF<2U=lGTjM1H5aeY5XJ7;EA79v0Q4*@ zgI+j203+r*KUhr)cl!MoYy-5+;(w1%)UUZyZj99lfT#H(3W*NYaS~6|?qvcpoDV?_ zgv+Nb&y`ZY-8Z!#Sgy4-yp*q#53eL`T^TLC29OPp#m$l*D1(pl@j;l6xG6ogwbC87 zf`E9oLzGUSSp*cc>))fE=Dh^afdBgu3gZ83-zjEe4j_#{2!oR zI3Gfde*doajxJg@8uCw&FlSf}CUzC}CG>(~sG&xMu~k|MA&wa;JT++ZzOJ--v( zk)ooa=-U|Jm9Ji%2lFfN;OIk6IIN!^#0xkoJ{_5K0fIki=^JHDVm`S*uhLo5-Ivtk zDPO5^Ckf3PI_$F6aiv2KfN>(@3s8V#!Ec0$B6g!$HugpI>uVI`jYyCK3qZDF`mm)Y4)GBW+y1gGDV zdh)~o@r)qaEv4ggf-E=?8SY zjd$nXQBWC5Gd&OlH5P!z>t%w_X#~$i`lh=-GxgRm>76p!+Scif1V|au4RoJ3n=AD;y9pDDWIB#L4vhx#-d0b zt57$2WMF`xu-<)&5bVffMdk~Cq@!?@HdhENIOXM=zifjO3N09z&4 z6-j^4&D}i{p3sH{Mn=Z7XNro8C1%=JK~oKtR18eH&eh> zz{;el`CUmt%yFXkB3uJHI)oWNW%g5<{vv!rl+UblrX$ z1y=Xi-AT8~jfJ60gSqZ{I^|5b;DJYe!&iR>L5t{`)Dvm*t}nV&fN=xhRKCn+le2r^ zR(ttUMv&?1IZqNR8s>A)Yeq+Hr^(P{{6eQBjw%IY!PC*K_xdiVfq=LD6C{Gnq8<(T zOflQy=ogWZ-;>GS|HO&tqvWUY0~W`~1z4Wqu4B*S*ntw&Pq-n#?%~;eu%}Vb!9uyY zleP?RBzka!J{~%BsF}tazetQ;s@R)S4Ur3W2INzLKq4_F{;y}Cl~YxNP}Ip_U)|}> zkG{Ea>FS?urcvDTbzP3mhq{H;uFy#}TpjQlxQ@F!w8LAc;?Gq2j*EIjXAJp8SVa!l z6~8O>jku#(2Y@2rN#ilA%d=*G)xT@}2ht?!xh~s|@@;e>sb3k7uk~^cT9ZGJ5FZU9 z(Qspw>`f8vkCpG(E9|jDB&(+unOH%wgLDZf9wQ?97q=r+5kC{UTQII5%n|6Wu@dg~ zw3jbGnG`LVFIABWmtvEXtE@B6oH>)r0*U~u_&F5as*qZ~OG>hW0w3KO-kj$e2OzDi zH|GOx8EKvdXC(GT^eoGK39f`Aw@CN{XMqkR*=dx)$L_R`S|R9=r)2it17QRiePD1f zn(nxmI$*kBW>^*)O-RQSenWChV zYaAv%emqOIraIzll&i)VB>gS!m#7}#*?ae-_CtOd{^dzqnPo=*gy+mzj#S34_H*ZPPUW@rL-Um9v#tn9Ewv?THpV7+{;nej3q;ozPS*_(Hq~HyU7@e|SD|y9lTLjTTH4E!qjb#SJ zLtqu~tQ{;4Z(D!+jgCw6u1XNLyLJ^Wz^63s(rnaGynQ8)>o3u^y1H&0D9;2S%V7Ed zZjLkCn1}I&gj+n#tHyF{=)f)6Lm}Y;q!6M0MHgiT!d2k92I{Ie6#}bQhXtQ!i9Sz? zPB3I|cUG+(-8~|}gfjr4lJ|Yh(rvUCc}u0Q8QPFrLK^t&-!46*36cid&=xKi*WncV zEJ3Wu>?M9AfKn^CRIU8-Dtux`6_`#uPR>S4%*5~!>zGB;-F?2nTamO0qWn5oK>FH{ zE&b3e`rx5MbAZ>uAt2?Zu+XQVKtcqLXkdlYdEkD2y0%(oM=d|>FCk6Gs^u>BU&1?lJMLOETo?xr-ck*%k&MuM73b-b zp?SpQd6)ex7PTBZ1!W8>CIkuboZi~5Yj9Be31lrup5q<+*&&6B6jB7Jr2!$mk&F<( z8keke^3;kcM&^yk&#A87v}H>XzeQ|y)a647iV32sA*VmSiH{h44qFVK4{`bGDZ3;j zVm4mVLPgvL9-dKX&Y{#S2aB2y8S zg54f<60&Ncq5KKg1$HJNG6(4c$ebEut?nZ@?I$NEyL);FsVi9R?|H82?j1zvf%?s4 z@7+GR2L_9C3vM?H2DU(>rt#wvNYtWEhxb`lO3&(%ww*8QW|_58LtZYPLLKw|Bmsi4 zN_ahrUI=~g;B*rMMN(5%mgev)By0+&)gCYn8GjOd@cg-*J$ma%i)_xd=CyToUyo(B zuxa(`w%Z2X;K`I@&d1=TJ5w9tKTg3v4;Mk~u``$XfMP>7CxMy+*%-Jr)hUl{>G-9# z7FiS<&+Y1XeT)l=C2-0FvjggJf}&7XR>nY2U*q5~m1P6Y8or11MH(U3f3Bd8-%!mh zEtK;gfUhm2Qgbk|a96l?_%R@M5kw6r-^H0Si5vd@ND`3{5pf45pPV-)16l!^g=nui zi{Q>^mmjyV`|JJw!GQ7(;)J;&8}D3b6z-mNfFTOmSU5QZ;kw`=;}fAR19j^!@!J6B zw8Y%k5W!$AL`B3bP8`VAUIhD)&A|0Q#{V#t{!v3`j*g&mEBW}iE#g3vHYePI?-euUInX+-NW7<9v3wfT8rt~peiM@-v=|$zNMr z#kF@66eF-69>SE1sx2lxS)!=8bVXeD=IFKZgHE|6h7dmi!YEqXFf~1m-2w?T07lkV zjeth&>$`Xe&0FA32jM6%^tQGZc#c9MWak3a#N^M`4>d&Y~HK^PJ0~kooZPH1xte4X-T00l)i1ZP7%mc zq1ZJq+CstiCCB$;lR4k8q?ONvD%vKqsVu=(LZSd7r$3_qLjMnjyo7dZ1+-iH9>{F= zy~0w4SFIwFjZib;i^ooX=idsh|HH_Q5s+3||6*V$7lFuurC)|neE;a8!sT*ua{N8|o04DE)5Sn9JefVW$u1MjNF8Ww^8)iS!3( zV-Ll@d?4dD>b`{b!3CF>pwGE$J+6PLw5T_1=yGjcUQhq>5dOVj>(#WB zz~2(7&cweVk1^30gj-Zs7wRI>7^e)%%m+%Enh|&J?j~2XNc=!CWITc#l|Gymeh0F) zttPb^XJ~@DrPS2im)S33|3{UTmRMta^sh2$5Ps86!7oOXjzy=^zr|RT7)TFL8gTW% zodYKo`5Hb!R+dZF4XJnvv>XyZj4U&-gZxtirg-Q9duT#d)(lk4xXMl;+$~U109qWl z+w^%5CsviOij_d|pFgh(o~zVo7peX0o8Uq*`)q=?*Wgx-r{K7{s8DNs6g7B&W1LWM zQ@(qqy9LTsI7tVCfDS>+154|~p{j9IldW^6q;{DEWmqOq-_#r*=Jn!2R&sy8+hmx4rv<1(W$ix_Z{M~pNh_0ZO*X%yA_W%d^Yl)-%}vvA=M@Gk{eBb?A!C=;XeAC?YVOE8 z*#vu}pTe3B-(kmw2sAx6gAz6}ECgTJ7;?^(X(O<-XA0Tqlp)37lq(@@G@V(HO`MAT zdg0^;{&S|618kYTpmT%b0-9{p)o^_G8S{Tflpk~jHjw?XNetTs#(3RH_2wpxQsdzM zgOU^$7DDK-usB_SJUdWN-DEv+z@4zLz^@Sd^eIdqI~f@pz;ht%9Rw88Q~@r+RnXu2 zH5O`zoE#zwlJFB77=VHOj9B!g2BQ#a8a5UdD=RBeAeG1j^FQ2NUqD4Fr1D$W{6H~5 zPF6OSPjK=Jv?7ykV%@pD-hN8w!O@`vpk)>9Z8baXzg_>T5ko?SgTp7~aZx=LmD6ic ztm!H$rn!!ru2=0Fb3VsZUtiy3_4daPLvjhbGq1$?cnSdjb)jeu5Ce!W$m_rfWoSmh zLx2(~W`AfoM2!t@J1i48TJd1E(Qf-AbwOCk%}&8(+0_eG`|L+jo@8FH&r)l{PvOX* z!_~-BkF0?w*~QKkf1TmHIN`z#qQdtNuyr&}^B384cOV zq^sC0JH_92#QPfl|*(I>=`KL zo~*A{zCzCYgYICBps&AW-#KyIuV+`+n(#dI0DzK^Eub)7i1q{BvTO8TSjzxVkpFqFHLC@&$Qmqq(X_`Qyfj_zzJiRa!w)J`p# z29$wG#7z+u)-^urM?q~-f_75)+uwbmON2%wH8W*97vTZ&dy|<-6FP)HLXd$tF1$%E zUYvb$!K&?D)bUf>q$mY`#lZl4>EnK2uAYXk15>^Xw>T5&&XYQ_n=p=svbpKcdmlisTxBwE#E z#ZQhFCb_iz(I{P_zfLFk_{b+D)p~j^Afo8HpOWgM{m;d62EIwgLej^idAJoxwrXlU zc6!uIPTROHFudMiW;BJ#EU?$|<(}apmz9*jQ81XL3vaRlX` zT)6OTNGhv>sq>Fy-HQB}>kd!v0ncFJJwf(&JM}q^xUPtv34V$HaMSHlXl6y zR2m#Q+S$^rmAk~r6oiQw?|2`|xm?&&XzT!LKyRU6t@geJA5&x{`@f1=ESAMA@G58J z>2T7c{>#wR36_0cgH8?9LOP$|;Sc$?J_!gn*%h!xqM9$L(1O<(R1J1w40}k4Ef;RC zwTWW^0ktFl(_9A86Tu!LxAlZyQ&3hF!+sR}&DdxEFi-GsB@EhUvuHga&3szNyg!Ys zZQqWw9?jI19ZST>6w8cSEm%U1HElP{nzaCFi|q>vO+-Gexwe~+?|yLlOmp|$1FTxx z!#>63<;y&G>wcg{OyaQ9mXwr4%Ti+a_%Ck^@B7>VaS zBzU5z1khIh4q2L^`@{xwI^+xl93CH!G~b2}TV*xK#QPzhL_`Gy*6X8RsSZHy5XP+s zNzHN0*co{@Z;f-lmb?GI!$X4es|{rOdwWwFt%kSih>2Hj(-(vcJwD+XF6;52gN)4j z3Ku1Rkz>a~r#8kN7*MPVSM|KY0y4?*j6r(2@t-(`Q?sKFVdJm<{vCzq2}&(96B7>62KLL_ za%hhAvnGBj-J^17`}vP+eQu55Oh;BKOi3;SuQH!09u#?py$LZF-q7@8(Z5Pac+%&N zwi*V+Z zbAM|ArdtC70tP@5RaFt02n!1xbj_E(K#txfFP&EF?G zBdSXpWg>g-&@p}DeoDx;V<7`7QL*kzwF=v~Y&Kgra&X3&)F=3%S}p(hag!HPeVhC` z2T)V<1xxz!U25y4!Np-h5}FG_VuBYy{@l4I_x#*bs0Uy$a&;Bj-{e>BO$z4(-i}*> zn3_65$~Oo_En)V>7J@Xe=}(7~G45xiA+-~@&`-hPTL+?FBquMGaC>cLVevqf1h6R+ z|FiYhDV&v!Sg@c}(DMNnHZa&-`E3>yP6|p5wuRm^EM-TuvqlxX=R&o)ykY+prKjb2 zkfJWhxDrKlbPxUs;=y3fS2(;b^wx{ApNt(Q@f0CGzAoDa9IGdoi{cS-(L2}}%Sm-ZMVnF|+%CYU;YZ_RLTv&;2y|9Y{P~SGNHsIPVNlto1FU-#x zd_M@yJW!Z3w9A0V7;>wAIpT-62OCKKW$6F!z4`xEeDQBP;eTuRzxE-L4e!oz9-j6} z1tMWBeC4t<;*Q`76600`@uEf2t1mj1EZvU11+?p<_0EDUTW+Zp5TDS;8MJzWGU#uT z+=+F2;`RX#8aT9}B5^4LBKBYNM*t0!B^cvxlG@N`zk9v1u=e_{m3bQ|4~hQ|U;kgm z6#Vm6ocJ+7WU`S8qadD}fr-T;vkB}cqaQTP3CgIxrX5d{^q>BvDR~GzmtW5;oMFfm zod-U#J{J?XTD|bHqt%?r;rbWd$RD9J6n?z}w+q!_K|(sf2-J3nZy=uc4C}Y$ECo3x z)x^nn^TXqgN7}zMgWRGIDESw-qVO3nUiKdU#io6oJw52HyKo2LZlfm_0`a;3eI!G; zuKSa5(U@f+1qNZ9XIDZ3j<(|0pf3wW0QGTN z;VqDPUbCQtNsYFv6=hBMn3|fJbTs#cmMd#(|Narcog=UsSB=IJh6RG_<6JZ@oLNVE#7RNTAjn7wPeuCFeIgN{TsByu;60!3J}nOc zU1ln?BOFTe7PwxZ8G~^!H$c`Ld=yELA>Bkf%g&zm{yfvRI~Co?taKU4pBAM>N1fFV zz0>e85Hl>0i5@jQ&pHYPJah#1n>5NF5achHlT#Gh$Nw7!qWZIeW<|K$ADv98OB$tb zaA*i#aD-6?Loomq2xQbXDnf_I?$hc&Hiw@=^o1^1IfP{t(QsdIzLK>qOzMfCeb-Ic zby}J`Qff0(IArz@HD`y;eS$#(z!*pundfP*Uk?KZAbJ~mDFzjsciYufRE|qo!+D>> z_H|$q94Awg4diPrH1G*6prZzI0VWxxsKAjU=bv0iQ6nP4LcD)pd)>?%wu6aDriYFp zX-kJOy;RVa&HU$~I0)CJkA|K{W~m|Lk{Vnjb(T9{M?;Ja7Zg{hPw7k$$9H6XvofFG zG(`=V%fgrk@_EzrukEJp?jli$`a+SS3FyB|AKgYeZKH-bti~Bu7IFbtH8?>yfI|apoVbhkG$BVw}g=Jtd`a&|NelpODiB zao2=RbdM~cnAljfc-`V+i_y<&Nj^}T5s4chVDPC3ie+KM+Zz=RLgK0uAx?ohhjZ7o z;5n{YcD$4g7^Q?A_3mIUH5dg`GU`q9MI-p`hz*HyMw5I}3}PH`X7kxCb%( z6B$l$^(N;}X-Z3%B2wVgp06Wb)42c^@eYfy1jEUNH1HHeVLW~M17Tf2t1u13+|u$p z^tB=*dE*fJwq4v^v_YtF0?$|i1OXBvyfF`fkWRz#UUa2d?aiM;J0!Dn^N-D^)`osX zxj-0)lMB9#=Z!udmC){I$jLjrZ{I1wo>N)FSMzz7l>%Z56O`I~C${DqLL(p@PeD6~ zgvqxup*^nYjVzef6_4iu9|4j544d(c)3RI&4!cIT3V0|w^cK7IhFfe_@eDO%?GuX% z{jvG2G9I7!r#NHysZIDY(ydWxirV@3BE{Hvfpq0pHAJr0Q1Z$SHo z)>^24t@xX4AFv3m{qi#(z$r0-0OcFwXnHpPePwLMr%#_AuJ~;JMP5;HOVv2Arf0bj zh4b#uDlLMXuJR{5bD9OQ2$uDV_N0=sCXF;8>I%Wo*x3jHcFr6~LXdLOJLm8L;0{3< z1CC(;H4k#Kwm_VfK#Uv10GSS^4Ag5g4onq#-7_eC8paiXYk+}=w|+Tu^Njw{2x*8q z9{dE*Ze(b9otkTRWM%2Mb4Z~K{;el;x&-bP$n{?*C3&F3Z1Io935=4Sx!cfl7-nm% z0#E1l6*^p}!<6HHrQoE4g}Fm`^rHJHsr@48Fxjm@^EPhSP(6HuPem^FD`IR?*FEZ) zg%tlY9zdiC2q+Vi{TXyP#5vCf2Lv#4_0vCna1dnga6E21#cdFR3jQ6@KQq%yyAVoP z>u?HKvrk*6ycLGFWM-1tZy)2*I*SQu0FCkz69c?h_!Y{YGe||M5S_lN{~Xc5d5PSOS`j=Gbezx-5pg-Cx*xF;^X7wA ztr0 z%T1V0x4*e|)%IvSs=CG{PI2*8&z9Afj~X9+c)gxFpa&fa)=QA|-T~M2qm3WFpj_xE zX+&KFJIB|rC$jDO+hT;S5Hr@e9OIH^J}Sv!1P!J@YRdTJ8u3RG1U=~I>({SeIQzTB zxoxPepddXxG^+EL{5gRNu&yzpuJl3x_`iKs(C6kYVmMQJ0 z2{2X2w(=<>cs0?D9_--eEO_za=J%YA){o<&TJ7i6xvyW=>_~IpA1UhHUwe>g|Fby9 zu9%gi(pxaANJ{$5+abv9SC-V1J7={;{GBz|6)KmEi*8g1X~#hTD?o_FK4e!60U`wm z6Em8|WUU@^IRzUoku)2I;v>Ogo9{8K4HQ7o@4#j+mgV>m*`9+~%%2XXUb8v_6#wja zmETScXl-G=#-&ws+b7m(Dk+7+!v6_Fgoy-LSpGx`-o1;af9$6H^MsM9RoPy0e@14e znyTs-jNX91Z@QOZWjGgJec;QOD{zv+?(XJBI2!;I`*$u~8foo}Jw*3AM8b^gVL55P z!2Pp@wAA`ilIt|{p5GrbT(vpC&d&QF2gAWj&+OE8J-uXb+2ta`=8V!ipWc4pIy;!i z{gq#F=Hcsu1xdA%I}GJcoY=x~OY+>&-G#>XQEhB=(X3`t>&`}(caM8%L_!1c$Sf}E zSkJt66Fy!W;(vylh2SmE){}X776X!1E?j7@pVZXd4=Dl7`t`!EPF=~?Mn-zttrK{UNAp*?iS19k(eWg4&Ah<#$xzFKFRnV5jxB2bYz@lwaP zliOJ)PH#nIdi6p!rAyFu_XmhwCVn-L2gK{)Sw^LFE|lE_gD&amKe=izsImv#x^KmWIj!#}+gTs2^G+)>*JDG{I3U%Bs*Fk}R%s$;hY^i5fOJ zeeAnG;CkS&*7Lwcz=C)Jf{B8JS_+jq5vj+ZGBXRaZGm08PToG-x9f6S-a(|`Ig!CKfH9Mt-d|C#fkwtNn^4@bR!7!%yOWyi z7j*=d9S-QR&ju?K4doKvi}Iaa?L^Wu^px1u_4p$z`YYtsWR4x>G9qR~UE&>XY6 z^*%wngoG*B{J=3TzNFnUD6ny2n~*t7R2dX4XNWNzdaI$#!HA91t!jZt=77X|tg&#r zO3ve@w@wE_9&Ibj=DDvB~vnN@&<^fU(4pPE7PIi zlCz^@h85KmrE;<`_Z$~l(B#uLBb&_8(NPpu2<6m{xuO!RqnGDmXKz1N-`9Dh>2O^O-8!xu@u)&f?rd`%b$p^qI8D|91GqxdCAwzK=QG_gWJyDh}O_%#_&}= zX9eVQ*b9o>ydPo=Re9)~jv4HJM9Ao~Zm$Y|Ct-#hyDc*L%4FvGBZ)MV@qzU@c;&nh z=||){hc$*&nuZqtM2X%~vYxB7ib`@C4;Kuer~TOBu~`r6%-sBZF$jW3#`fl}tgElb zS|r}ZEoTrdWo>Sb%<$32C$7Do-8BjndIyl1iX)j+xx2FHy#!i?7p5=rD&tM;=|F3yDdvtJSOB}Kj|#- z_k6_na&iu0a?5+J)vv!`L2Asc@Zn4OMi|6;DX7>Ww%3CuAN!x*_3Ko*f>~G5miPBt z##A|X1_`Xm>G&Qo&vm*5+R?)=Gdy5RdKvh$xmBISjs29^w9 z{;HkLrs4(m@BX|*CVh*;U zOI#wAKm49TWyBKNwllDV`^m`wl4Xt_>*MvfBHZSc75?{h4nTxH1y%a zKBrtz4G4A$szOu`YGXvm5Yx3wCz*A))PSxbF(BT*W_qIO$D|tqzilz3Y1Rq?LIhuG z+2kL;{GAW;I_R{Nl#aPiS?#ykS*YePw;i)F7f{M}3n;IRW^MK_Xh&HOfnNGxV8 z7NOkmz<)w-5$0UQA9)$wDQ2%gv3%&zC&t=UnaBl$c}OTq-qp#-6|)=xGvT|+oAQ} zSOowT4mi{`rs*2u%>~{|5K9jO{>2;ts$2)jwoWxYqC4>~zI*46ZT5nvcpG9gu#JI; zmx3;A?P4B&(v-*hud*AHs9pmg4A1MJ&6AavkwLn63l0m^0RW1~7+8r811VYLiv2cG zCfW>AoSJXW@&}Zwi^D4p3&)O3OcF9PHC?~E*?JmHKdSQ}6WeTUNadxSocIGyt~_Co z6vqE8t6Vrs-kV#*WUzlv$T6IwA!SC*dlHAPZ$isSh@de`>(@G{1-SDkYJmK8pGHvN zdGY&o9(X4F^a>*{_iFv5V-egXfCN~?CV`FXv%&J}e~+^o7P;XpMJhNTXJqXU|b6NiqvWvqxObaacq;Ccq39;I{gSFB~C5G;5@A9zGT9EA-`lE;+N?r`|Jk?aKEsMCN;6qJz3J7 zQ?b!gseIF7t(WiSsBbf5J=KP3_lLN+!d$hi?S!L1uC~F~1}fjqE_Lvfk!Vu$nKeJ5 zGPm&@&`N7^MLCSANQ4o<>TC@E<%m`q)Cs7KhJbkCl^>JpvK2Jm|G34Pyw(-4Mo>vC zgK)LKD3BUk0jC1!kI*-u zHb+~F@h$~1@=AFVBLrYDLdMhTgp}T)H5XRen490ZERt4!c1jB5%^c$fR~m8LQDm`) zF!DmiG69H{ppBtG1BPmx*@XNXVge#)_)n`KuY$->dd&=E$Gt)NbAk%HKWtSH{c2%= zqYzj4s{hrhUNF19b`D{w2|_8I&J4)8Lro2U3`{dIRstn#;R!u_)~I*-R@26zgJYW zc_c+Q*%?hWOcbw@lJ3_9favgQvAx+W*>)Uo8;~R-5eBm$(o|mqPXrZzPj4Ks1-3Gj zad!)SFWtF+f1gM(Fs2)Remi>9d7a|Xku1PGs`d_ah1~*&#Kb(Y5#wvD5v(F6MfA>kK!1X>1sx%-eQ+WiZP}Ou z^zxxso{Ti6pNZMLQ}TjC`o`b4kEN=psWD%B?Qz-I*f{<`omMhr99Yk=^X&^UC>NvNjjVZ84}G z=S}Pz#ouBk!R}L@p7}}pjztLC{lJ@+UAFpaZ%*K<`oy8Y;#Ve#X?BWlFsn$++$WQXJptm|B@~$!jpE#98nSY7&2KM9ZZcf_Jv(**5S73UFNHGgFifWDoL%<|+9tdvC=}Q4mXVbW8kqjb)$L{ziiY6R5Ds|k zK5lICQC8Ag+4j#{D|B94+`o6vFtbT5Nxj298yhu`)1Z2%c@=O#NIi$`eD(VbO-C+8 zPiRf4kqy+7^+IXf{QdmO?=RvPVSrPU;MVI1rW9m>*5%%cdGzh}GnpRWy6v%u7CI#s zjUa@o=)N}(KJ$5eJl@EP^xjK+f>H89lL3qN{>3icq)`*$WIoWtTXSt1CDDY_OXR+lxjce) zHT&fq?e%AEE{wV5qdkn`vrx3RPiwnZ1niqY9)Uw$?u^QBu@-nO^z8RIX$@Z|$s(%l zsh+*|#CAH9!Q-)XZ@wKKgkT4__yo5X-b%2Ss1tDuQfUHNdiD(b45TRbR?vU)gvGXN z-DM1`N7T}FG@f3&V(yhUciZEwM>YYh1}+~~5IqyQCwy121n2a#HXAiMLJ^w`k7a*lDw9@Ed^yV;nUy0A@84BJ5W zfqSMDC&wFE`JRh7dhZubu6coD5Z++dlbj4x@K>3*xMV#l!{A+_tNMctoVYdZQ# zwg)Z8C;4p=PT+MLqDX>lL&b=L!tDhiy|%Q3+=&s$9U_TYT8YvJ<8Fw#ut?3hZYwEf z#O$cg?I593^&xIL;%@!=+1ksQEvj&qqc||S>X@yK)p^YH^PSLy9PQWOJdh#>tGOw# zR-7r2aX!SbS`fU(yl8(>6Y9Bitah!WrlE;M3KNv2-P?_z@+W+im8L*MF~i1}Md2MJ zKDaH}E!M7Y0M-~3-&i1sJ$!nT%qe8lb+TC9)4rpv+Fx-gR(ohL$E%B^oUZu_fD2(> zI+EFZ)Vz8ZKq1e0Cwh)GuT`7{lHpCNYZnI%nxdOYPdd4&3W&5mdP6p&IRs%E>psaRrqu19h`tPLoY;n^jNL zum%t@JgBHBd^nT*NU!y-uW(Ey*#%SQiyjSHJLONdn1wO!L5Ba_N826slQgoZdJvsY zfPIMD$xhLTmslE`qi13|bl}FIo50UkB)4l|5ui$~vh_MG9#b{sggl!wr%qM#F>s=O zwaabEY_^w8p}BhLfRZHEdi&L=wBqluLxQj#oG#uOzHMSCX`!(qumGVXn9E5#-`>0NY`bI(k|7GV~ZH9LRn;p^J z#Y0eGnht2-?_NHJ7h#P+H9@AV&E3`e8g(bmGYkh4ZCH&?Q~jzAI#!YcxzQ?`$Dj!H z*V>It#u;gHRyOs^;CVK7K&(G~@@mQCLbrDOT~7t<#9CTv&UU>UKbu~`oTL=l740u4 zo88*qEMo&=%MI^JQu|BZWrfiH2m<)Kr(Q-p@LT_ZBIsXqO+(Ha!uDB(v*IkUru=DX zAKUzz^@xZguG_Z7R%4ahy4_FpV01tr#(S>QJ@?s1;lGF$aX~gq$bs^rV|Y9v`!kNiDeMRM?4ncGRUq+YuvEZ z)vlRk+y=k^KR^2SH3XJ~n+0lt#_8Zceok@ExCM$z#rA&lh@a!LS9R8t^8dpF;n`$n zh=MW_ZKk4PP4`=%mPFB302>F?%(5~v(t(V-onN2_N7fmT!){tGaw0a`y?bN9AY!8E zh&IYnzUYqwlh$~nYo49M-y5D4Sl_-L1vyR#jBG}p>%#mAvv8MBQN%7e=TSB#g1;9N7J+9H1wwEVCY#HI~yp}Q}T+c{es zVASVNpT-W`Iyp_jkBHF)*ATN{B*ov#MrfR+*14V-ci-#vNFD>J-sxV$%f5^59_lbn z&3^mrtW;b#9bRb2ge(P|r)E9CcD`w}QcYA@e75GBOog7p2Dw!nzRZzeG4|O&!4wR zWwi{0c6c5a#}v|TpWW)x8H94w$O;EBtSoMjbzt!jpa$A)q^%ms&g3JDZmjE^hWm>) zdi-M`L;YpEUWRGk?I1ltWj@w|Q!=D%Z_IfG1#S)wXd{7aj3!kT{Q;)fiJoamc;m_g~FytwkUY}mVfu|6Vcy0Y(HkQ(9^R!^QfMv zq1kz%9Ssan61ot)7pNuC9sqk4pQt{GZ~=UUh>m?s_L@w@#`h_)x!`w20qSFr%Y--v zq>l~SqXy^=VD>IRRs|6*j1g;sDCR(N&@%D$=CJH*VB7Hc3(DLZbZkVF<>1xF%MEa= zF>36x(o!_?#LI&k)OXb}AdDiOmoZoZDxe8}L=4Y-2S?~Wl223;b3w^@=Q5gx(9>b; zB4VcJu$6$`%0*GuSc9})V!{rd2Gdm^{ev7FJYIK$j&z`bS95>!x~XvTZr#{NF9x=S z4tU!AHeu&Y+hYUh$gO8*r{MU3XqTsyqu2fYA>-EcMq{pqpKDxx4~>0cMGJ6Au+)Gp zBktWx!-O-;i|yOk4rONPyXjIHDjftV5xfmlyZ!Zw8ZLYH2t2MKV z<05Ud@+jy~J$qs4N=!8v=`HOl72$YX)n_Qp81+c_5Js40WK2RCPQlwFJ3_bIYoUn7 zh2n#bbzm`oQ4FHIJm)nX0q4W(L4J!jp!=XIV9xBlR@$9sJ?E6$vWQq945|Jo{{)yC zmTe1`5W;}4W3Mi za#j)BRzS3A8<0>6f+9%HP=JbpjmVSG62%0xjgkcejvIo_o&s?Qeg3i&_vVU$cnA=QlVK{Ylbvh_7shXOUU6BaDn5 z2Y*ogJ8Hoz_7$}#3yyp~6h25m5kIF**l6Ex#WN{V2Ek6s{F^^2wNcU`S7V-{Ydf1VuWE7LmivFR;&d zbNkJO*!ze!33EGy+<9_xI>u6Y@kAGQ8v5RXFcdEaTC1!ku@ZnI2}-P0V5vwzwcJ&h1`#^q zh>jW$1vS$5Tzeus=2Zk%lIds`1|9{5v%YJ0TbbWT08DQw8tE96WODdB>Wv7&?|Oe3 zMOp{hCHJ8w0N6W6-KSqwPe%v#*#{|L96-5XAqes8lb7`}z zM;JO_%D97QGr3ieEAY)HKW7h9F4ie-FCNom6nQ<6(V=hVndFv*s8wE+2+GO6s~L{U zol#NSl&_se0)tkrSKhOBJwYdtGCL@NR1gsnfg)Jw4&|_)fBx)NfUaoty284Hih41M zm)HT-uc^kLYSxyOkkD}>kJBA-Bg(q=?%bPyIkM;$n{kg7uuWa= zEe|hKaD&9oH*#`QSS(O4@qiE@=NHqg*5QdF_vAqqn(M_3!$LEAH2QVAK@A4jP+8fx zMQe?742&gyM##^s`jIFF&9*BG?5h?6v3_`}J)l|1QsnE9*8BtO_-5zAE#y-nDfb4o z9q2a{purd790u#8i&j=R#6r6wI#Me8DgsDEN~{u(xd^5x$n_w?I^`vqjHn{#hlEvv zcX?>Wi3+73dHngyXwPM5G#jP2w5y#*xQrqT5=JO7?+JKUUVh@#DFubw0`A2on`h`% zQUbq#3IsL{NH-DiPRxz-@bsJqgSdwfi!`)3pWBLkcIqZ~U!t@_?Qx6d-m`Wn=uP~3 z76F%w3IM1xhK75SKmJV>Ol>TB_ILsoo`-v>Qde%fE_4qVlpu&jFPfe2(?5|}X4{-Y zm)0jhy#gOVf`I&6iL&4V=_k@BaNFTu0;v*&VvA@Bh7m9czF0``pzEAmx1En~cAC4y zWMF{3&@9blRd(Io6!Zfl^hN9#Cu8}O$vRSA9DyGNeCunv$G%`V2hTD>G{7%|r5dOL z8W}Z%JBrN|h=hrh0Ul56va%o5vRnph0jnUKMZNBHYG)RF^r92`u8tsffN=c}-+%vD zda_6HLM&N8$^YSj89xq8ntouYgIj%o^dB7Asrsv=S)pOtv}}&8yO$ItpU*;X?$40R z(tm(lMlca@dLW95*{ni4UTWYNVt$Ze-hFnQKes(AgyM6@`BmjC$g%EoW8K(oiKcne z0)_qiZNS@uP8H!PQ_P>9am^IW79=2S+(PI`yg0;NxS91dZP4XZtb*jBLw?{n9lgcb z3>G6EaqKbBF@kgeqmsPhgc}L~7JD4ge1*kS6)*u}mKCh{gsmbTMChNvDnu3QTACke zv(kbJ#ahq870y#H8tI5auyzx{RazP$S>3fH{r)xEp_a9J#(a8O@jRCab2_=GeByUe`m<9rl`^PDu@IG(c zsomkuMePEU*GF=$!8OKPO{hm=1koZWI)J--MMKp#^YS)WbF&U6tPCa~QWuS#EogdC zXKl^ma@^7~vHnVZ^0ynN(6eUSmUkwVeFY3D%#3stwf|tNAeZIU1eZvB1Fr=7{qDrw zbVSVre1oZZA5@tGSG0ierf6(Ny!(kBa^7Yn`fXO-^S4FLqGkp&8uyBl$~Ns zHpQ2pINCx#_B$7}11|(VTlOIXzkpwhZg&)OJT5gOe5EQqQgrC{4HlLFQqp-oKnI|0toqNvfvN3K z(|Gbkv?-BRZ;k3jbRL~;CzDD+IRR+WgoTsx4!fmFd<7Sc<+aHG4n-*g?hJ1?RF+Z2 zJF2O9-E@b^5%>;ZE`lzMxFx-k^`XJv(F82jm}1N56oozo|4?SvdI{c2xD_b@Fgnk- z=CJ({0(Ba&fKp?_pk^D3zu8^KF5-+?{u@k=f8ZSdgc<{Z}Bcw+DrUrbLzn5lt;B=)FU3-s##k%849k{d)#3b|4po`D&cm*{gHs zd^B|H2zVmTuBEjVxWEn5^$&%2f%1U|WVeGz0PF@0{-At#Su@-2zOrd>J%3UCWwvmP5p+M4B?YC}*eBu8~m9s=)jvHrAIMZSMQ*7de&N z8aqYvW-MD85ZrW!kGG1y>n1*Z;NdT)x`qY8adBDGJLX%JOxd2J8&fTJp;k_}AflAx z>zfYm)y12N8{BGYL|P~%{gdT8gqoy_4%J94AGflvdd?AkB1%^yT&_Q|( zgvJAvJ2+#K;HrTLBc&eGgE-5>p*Wmz%bT*!D}EXZ4y`_n(^5iF-v|}f@$JecIqnOf zj~5`cLNhhd#s#eRhVh4ogkS~g+;Y~nPno;|3F1f&%lmY@RJnt$?Vu*QpL}^+aj~qe zSC&J!E}&f8hpcczSlvqwxdoH=!g&_~E{eXyYv{}c?*#|&r+<7pagjac2V|fNa{-sF zx4sZ8p5dzLE|A+^*hn897}%CRZk*qJO8`OoS5%Go-n^9vF$eqLimYif-Kxil?Trmm zDk{Y$4|GwRKMa5_MK0i%fgV|)cZQbS7+^o2lKJ(l!nX}^naqF}k%KH~9);U-f zt%k==JdqU^7n{8)C%%YTmbe z)|{Kl57HK;Q8WvFKGrfb+R0e#WcUJ;aw{-p8^YKSeZTS0ts+BS6Qm{YSAqc~to@7` zd&UgbX@FY%xkT=8ja>+VH!wJ1)Sck6gj%|YUcMh=1x#nEg%J1wTJMacAz~r&`|l zJIgBD)2H+O?{_Yqe8>1Wzsr2=Wubh6o@oLL_&wp_vM?oJ7xH*WR|m5gyXqpE!HA7Q z<{TBtZ*77n?q2mw2Kv< zv=<>W9QwW4%I5axdq&MsQ>2&UWsIKc6i?0Sck<~`$nS!DT~pmBe8I>-65jcaN^tvq zD!LueFCz;e3XB2Rh9nmo?`*D?3>foJ%Y4z=GhPS=u8ga|tV!m@a_IPbG3hT&_<=0Y z3o@0kweu=Oo02^|jz8Elr^rH{Sv-xZdge~?nL_uj3hyuRw1h%E9RX=4H`k7}rnNm@ zCfOv-_u9OZVbase+%9KL+vb(^qElCF_|!|u z-ox_C_JO5PjSgF0oJA^$&iRW#)v%rEsf`B=#jG@6N+@r~2=Wl6IAC6kabZSHsr?pW z=oSibYT|b{i}0SA?0bD7Jl@YfxS^grx7Zn8yw5I}$9u)8&!cehVUWPetl7Y9TkX7^ zUdQY#uf1r|w6Xg}T{<~%N0=aL;DBR5v>5{zDgKaO@!KAPGUDh3T9;Sw6sC{w;&84o zW)gNUJT=p>9w=srl&Z zhyJ$Pnj)9`W|suYyiW+^z3mzIPy=E98QNlkgT22zOU8pU>Y&6#vZ2qy;ZqM_tDugp zV_Inp49aL4cHN5-82+Np8cYcA9Zw6!P1+?R zGl6K{NgMt(ml7yB0M&5gK{_oD@pTS!Pu{4uYgdWAxpR?ZJIG*agm%(?-ZTIJJ>w|E zFUsEmc@vRIgEx;y(6Ob?8=t(x-vl{~k9@O2$Oqt;`q6hL3l3t2Wutfw2=j^=(^2yynaIM_fxZZZ9@mJN%r8ID zQc+onTAUGXDVf}Ptj7()EntSj)`&2i^DZ(|c}!e{|L@*Ba}$cM-No07XoVQRpU;e) z44=JiOx;()cAFZ{6YEA^UoT@dIv*Sqx=F#N-?V!ASxsMI-$J2H=GLORvsHIg1g}fg zKcLRcdYF2sJphIrBB(m!-MRSSZI36oV2GP=^HJHDKG?cY?Wi0Y)9Eu(e!|j1dGxJoi4C^@Cz#5V8=Kr?I7kgLG#wvKPt{y{|tngyRWAm_#jV2H1 zWUHWAE|YWL6z0CXCNSJlWSmM83y4B_7uh9BjqU&l(0}q}93e7vmQaPX1L>nYY>!u6 zb{tt!M&^5=-}2ISNzd!WZ}=t5E%obqaAG(K&2>H7^Zy8 z$Yf~pIpYj5i!ic8k1O_9+D03$#0*{dU`br@~RU~9xV zswfiYyDfd;Fl&O+Nv!z3MYG=rIy^}{#K%ykPLfjf&UgI2tAg=`VViuli?R3&m$cH+ zusm;lPriTjXoYkmn-qIAyXCho@pn3jQVUxrE@6Im zg!)d2QiAeIyMuy*!`mCt2LkQz97u@cQA9c7c-e~KC?{y>FcuwxWxILB1-s*x3 zM|6SZT!Y*r>vN!#rw8-;fHwaP2l2+`u-Y|3e8c*l`DXN3#W|grzcB_b$jZrOLs=^$ zV|=~zNF$crW?L>mxNb*?Paq=9=Qpb8y*tr!&b8g&t~+jXtB`8jrGwFkx)o-=2p>lr z1zs|$Hj2gLAzi#Odr`(D#PreFf)YG&jA~Itp(#e*F5k8aYbXf9$E`uiM>A+u?cP)l z-Joicqqff5-!n8~k7b<;Xx{z)mX(7p57ekOaBv{=f%4SFbnP8$P)T_J3tzO)&YQ>LaMpJvrUKEtTVub;(nXtP>kD*2SK64!Eq4!fW0j zvFgchH%TC*397hUJ{`J`h2$g$UA?ZD=G_uj z7qWIwSsH1?b8QjD5fvU9k|fgqY*UY79^u-1&y!IuZ0 z$U3JLWV9jUCsMH0LGqXDQu<#%=cgY_t9o22_I&tU!P#v0b@Y?0T!HJ0|?Z-oAq zQhk%8IUraie>B-;xK{s&*KGwBg++kR|s_KRM_sqwe#Vh3V=O5MAkvm1*Q#P8^$%U8u zteESgA$3|^oq3d^Uv){= z+|Pa0SF$#(*&N-v>NT9cH`hqvomtI8lk)+jerb=7@dZuY^fCNVOgCgkye!v`)FJsM z-ejIZ2opEk%9t`+l92tCP!WZmX#onVkRH;NtI#a+`Kfn&pP<4mMwk zUfl}?hiF>gAO(z}OZVnuzc#rHEXwuB^~x}2UR&o!(x#p1lp3l|*Vmz8Ur@VTw#A3y zn!B+rZH@X5{LGiH|8l%;5m_-Cy(K|Kq&4$>6Tqz)4Lw9A!rABqxrY(n{4lfBoM8E%>RV literal 0 HcmV?d00001 diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png b/doc/参数配置功能/ScreenShot_2026-02-25_162819_927.png new file mode 100644 index 0000000000000000000000000000000000000000..75c5aba83aec79eacc45ea46d9915f550ab8bc5e GIT binary patch literal 42321 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 QdZ6zG83pN-)B1n@FPRGj@c;k- literal 0 HcmV?d00001 diff --git a/doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png b/doc/参数配置功能/ScreenShot_2026-02-25_162831_473.png new file mode 100644 index 0000000000000000000000000000000000000000..2321a36be656854ec808b40eb31ef6c97ca0a1f9 GIT binary patch literal 55532 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

sj0&$ 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 literal 0 HcmV?d00001 diff --git a/doc/参数配置功能/task.md b/doc/参数配置功能/task.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-backend.md b/docs/plans/2026-02-11-cust-fmy-relation-backend.md deleted file mode 100644 index 9af7315..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-backend.md +++ /dev/null @@ -1,2008 +0,0 @@ -# 信贷客户家庭关系维护功能 - 后端实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**目标:** 开发信贷客户家庭关系维护功能的完整后端实现,包括数据库设计、实体类、DTO/VO、Mapper、Service和Controller - -**架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立模块 `CustFamilyRelation`,新建独立表 `ccdi_cust_fmy_relation` - -**技术栈:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10 + EasyExcel + Redis - ---- - -## 前置条件 - -### 环境要求 -- JDK 17+ -- Maven 3.6+ -- MySQL 8.2.0 -- Redis (用于导入任务状态管理) - -### 依赖服务 -- 数据库连接配置在 `application.yml` 中已配置 -- MyBatis Plus 3.5.10 已集成 -- EasyExcel 已添加到项目依赖 - -### 参考模块 -- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/**/CcdiStaffFmyRelation*` - 员工亲属关系实现(参考模板) - ---- - -## 任务列表 - -### Task 0: 创建数据库表 - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation.sql` - -**Step 1: 创建建表SQL文件** - -创建 `sql/ccdi_cust_fmy_relation.sql` 文件: - -```sql --- 信贷客户家庭关系表 -CREATE TABLE `ccdi_cust_fmy_relation` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `person_id` VARCHAR(50) NOT NULL COMMENT '信贷客户身份证号', - `relation_type` VARCHAR(50) NOT NULL COMMENT '关系类型', - `relation_name` VARCHAR(100) NOT NULL COMMENT '关系人姓名', - `gender` CHAR(1) DEFAULT NULL COMMENT '性别:M-男,F-女,O-其他', - `birth_date` DATE DEFAULT NULL COMMENT '关系人出生日期', - `relation_cert_type` VARCHAR(50) NOT NULL COMMENT '证件类型', - `relation_cert_no` VARCHAR(50) NOT NULL COMMENT '证件号码', - `mobile_phone1` VARCHAR(20) DEFAULT NULL COMMENT '手机号码1', - `mobile_phone2` VARCHAR(20) DEFAULT NULL COMMENT '手机号码2', - `wechat_no1` VARCHAR(50) DEFAULT NULL COMMENT '微信名称1', - `wechat_no2` VARCHAR(50) DEFAULT NULL COMMENT '微信名称2', - `wechat_no3` VARCHAR(50) DEFAULT NULL COMMENT '微信名称3', - `contact_address` VARCHAR(500) DEFAULT NULL COMMENT '详细联系地址', - `relation_desc` VARCHAR(500) DEFAULT NULL COMMENT '关系详细描述', - `status` INT NOT NULL DEFAULT 1 COMMENT '状态:0-无效,1-有效', - `effective_date` DATETIME DEFAULT NULL COMMENT '关系生效日期', - `invalid_date` DATETIME DEFAULT NULL COMMENT '关系失效日期', - `remark` TEXT COMMENT '备注信息', - `data_source` VARCHAR(50) DEFAULT NULL COMMENT '数据来源:MANUAL-手动录入,IMPORT-批量导入', - `is_emp_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工的家庭关系:0-否', - `is_cust_family` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否是信贷客户的家庭关系:1-是', - `created_by` VARCHAR(50) NOT NULL COMMENT '记录创建人', - `updated_by` VARCHAR(50) DEFAULT NULL COMMENT '记录更新人', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', - `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', - PRIMARY KEY (`id`), - KEY `idx_person_id` (`person_id`), - KEY `idx_relation_cert_no` (`relation_cert_no`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='信贷客户家庭关系表'; -``` - -**Step 2: 执行SQL创建表** - -使用MCP连接数据库工具执行SQL文件: - -```bash -# 连接数据库并执行建表脚本 -mysql -u -p < sql/ccdi_cust_fmy_relation.sql -``` - -**验证方式:** -```sql -SHOW CREATE TABLE ccdi_cust_fmy_relation; -``` - -**预期结果:** 表创建成功,包含所有字段和索引 - -**Step 3: Commit** - -```bash -git add sql/ccdi_cust_fmy_relation.sql -git commit -m "feat: 创建信贷客户家庭关系表" -``` - ---- - -### Task 1: 创建实体类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java` - -**Step 1: 复制并修改实体类** - -复制 `CcdiStaffFmyRelation.java`,进行以下修改: - -1. **类名和注释:** -```java -/** - * 信贷客户家庭关系对象 ccdi_cust_fmy_relation - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@TableName("ccdi_cust_fmy_relation") -public class CcdiCustFmyRelation implements Serializable { -``` - -2. **关键注释修改:** - - `/** 信贷客户身份证号 */` (原"员工身份证号") - - `/** 是否是客户亲属:1-是 */` - - `/** 是否是员工亲属:0-否 */` - -3. **完整代码结构:** -```java -package com.ruoyi.ccdi.domain; - -import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -@Data -@TableName("ccdi_cust_fmy_relation") -public class CcdiCustFmyRelation implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 主键ID */ - @TableId(type = IdType.AUTO) - private Long id; - - /** 信贷客户身份证号 */ - private String personId; - - /** 关系类型 */ - private String relationType; - - /** 关系人姓名 */ - private String relationName; - - /** 性别:M-男,F-女,O-其他 */ - private String gender; - - /** 出生日期 */ - private Date birthDate; - - /** 关系人证件类型 */ - private String relationCertType; - - /** 关系人证件号码 */ - private String relationCertNo; - - /** 手机号码1 */ - private String mobilePhone1; - - /** 手机号码2 */ - private String mobilePhone2; - - /** 微信名称1 */ - private String wechatNo1; - - /** 微信名称2 */ - private String wechatNo2; - - /** 微信名称3 */ - private String wechatNo3; - - /** 详细联系地址 */ - private String contactAddress; - - /** 关系详细描述 */ - private String relationDesc; - - /** 状态:0-无效,1-有效 */ - private Integer status; - - /** 生效日期 */ - private Date effectiveDate; - - /** 失效日期 */ - private Date invalidDate; - - /** 备注 */ - private String remark; - - /** 数据来源:MANUAL-手工录入,IMPORT-导入 */ - private String dataSource; - - /** 是否是员工亲属:0-否 */ - private Boolean isEmpFamily; - - /** 是否是客户亲属:1-是 */ - private Boolean isCustFamily; - - /** 创建时间 */ - @TableField(fill = FieldFill.INSERT) - private Date createTime; - - /** 更新时间 */ - @TableField(fill = FieldFill.INSERT_UPDATE) - private Date updateTime; - - /** 创建人 */ - @TableField(fill = FieldFill.INSERT) - private String createdBy; - - /** 更新人 */ - @TableField(fill = FieldFill.INSERT_UPDATE) - private String updatedBy; -} -``` - -**Step 2: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java -git commit -m "feat: 添加信贷客户家庭关系实体类" -``` - ---- - -### Task 2: 创建DTO类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationAddDTO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationEditDTO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationQueryDTO.java` - -**Step 1: 创建AddDTO** - -复制 `CcdiStaffFmyRelationAddDTO.java`,修改: - -```java -package com.ruoyi.ccdi.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -/** - * 信贷客户家庭关系新增DTO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系新增") -public class CcdiCustFmyRelationAddDTO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - @NotBlank(message = "信贷客户身份证号不能为空") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - @NotBlank(message = "关系类型不能为空") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - @NotBlank(message = "关系人姓名不能为空") - private String relationName; - - /** 性别 */ - @Schema(description = "性别:M-男,F-女,O-其他") - private String gender; - - /** 出生日期 */ - @Schema(description = "关系人出生日期") - private Date birthDate; - - /** 关系人证件类型 */ - @Schema(description = "关系人证件类型") - @NotBlank(message = "关系人证件类型不能为空") - private String relationCertType; - - /** 关系人证件号码 */ - @Schema(description = "关系人证件号码") - @NotBlank(message = "关系人证件号码不能为空") - private String relationCertNo; - - /** 手机号码1 */ - @Schema(description = "手机号码1") - private String mobilePhone1; - - /** 手机号码2 */ - @Schema(description = "手机号码2") - private String mobilePhone2; - - /** 微信名称1 */ - @Schema(description = "微信名称1") - private String wechatNo1; - - /** 微信名称2 */ - @Schema(description = "微信名称2") - private String wechatNo2; - - /** 微信名称3 */ - @Schema(description = "微信名称3") - private String wechatNo3; - - /** 详细联系地址 */ - @Schema(description = "详细联系地址") - private String contactAddress; - - /** 关系详细描述 */ - @Schema(description = "关系详细描述") - private String relationDesc; - - /** 生效日期 */ - @Schema(description = "关系生效日期") - private Date effectiveDate; - - /** 失效日期 */ - @Schema(description = "关系失效日期") - private Date invalidDate; - - /** 备注 */ - @Schema(description = "备注信息") - private String remark; -} -``` - -**Step 2: 创建EditDTO** - -复制 `CcdiStaffFmyRelationEditDTO.java`,修改: - -```java -package com.ruoyi.ccdi.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -/** - * 信贷客户家庭关系编辑DTO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系编辑") -public class CcdiCustFmyRelationEditDTO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 主键ID */ - @Schema(description = "主键ID") - @NotNull(message = "ID不能为空") - private Long id; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - @NotBlank(message = "信贷客户身份证号不能为空") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - @NotBlank(message = "关系类型不能为空") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - @NotBlank(message = "关系人姓名不能为空") - private String relationName; - - /** 性别 */ - @Schema(description = "性别:M-男,F-女,O-其他") - private String gender; - - /** 出生日期 */ - @Schema(description = "关系人出生日期") - private Date birthDate; - - /** 关系人证件类型 */ - @Schema(description = "关系人证件类型") - @NotBlank(message = "关系人证件类型不能为空") - private String relationCertType; - - /** 关系人证件号码 */ - @Schema(description = "关系人证件号码") - @NotBlank(message = "关系人证件号码不能为空") - private String relationCertNo; - - /** 手机号码1 */ - @Schema(description = "手机号码1") - private String mobilePhone1; - - /** 手机号码2 */ - @Schema(description = "手机号码2") - private String mobilePhone2; - - /** 微信名称1 */ - @Schema(description = "微信名称1") - private String wechatNo1; - - /** 微信名称2 */ - @Schema(description = "微信名称2") - private String wechatNo2; - - /** 微信名称3 */ - @Schema(description = "微信名称3") - private String wechatNo3; - - /** 详细联系地址 */ - @Schema(description = "详细联系地址") - private String contactAddress; - - /** 关系详细描述 */ - @Schema(description = "关系详细描述") - private String relationDesc; - - /** 状态 */ - @Schema(description = "状态:0-无效,1-有效") - @NotNull(message = "状态不能为空") - private Integer status; - - /** 生效日期 */ - @Schema(description = "关系生效日期") - private Date effectiveDate; - - /** 失效日期 */ - @Schema(description = "关系失效日期") - private Date invalidDate; - - /** 备注 */ - @Schema(description = "备注信息") - private String remark; -} -``` - -**Step 3: 创建QueryDTO** - -```java -package com.ruoyi.ccdi.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 信贷客户家庭关系查询DTO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系查询") -public class CcdiCustFmyRelationQueryDTO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - private String relationName; -} -``` - -**Step 4: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 5: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/ -git commit -m "feat: 添加信贷客户家庭关系DTO类" -``` - ---- - -### Task 3: 创建VO类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiCustFmyRelationVO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CustFmyRelationImportFailureVO.java` - -**Step 1: 创建主VO** - -复制 `CcdiStaffFmyRelationVO.java`,进行以下修改: - -1. **移除 personName 字段** (不关联员工表) -2. **修改类名和注释** - -```java -package com.ruoyi.ccdi.domain.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -/** - * 信贷客户家庭关系VO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系") -public class CcdiCustFmyRelationVO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 主键ID */ - @Schema(description = "主键ID") - private Long id; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - private String relationName; - - /** 性别 */ - @Schema(description = "性别:M-男,F-女,O-其他") - private String gender; - - /** 出生日期 */ - @Schema(description = "关系人出生日期") - private Date birthDate; - - /** 关系人证件类型 */ - @Schema(description = "关系人证件类型") - private String relationCertType; - - /** 关系人证件号码 */ - @Schema(description = "关系人证件号码") - private String relationCertNo; - - /** 手机号码1 */ - @Schema(description = "手机号码1") - private String mobilePhone1; - - /** 手机号码2 */ - @Schema(description = "手机号码2") - private String mobilePhone2; - - /** 微信名称1 */ - @Schema(description = "微信名称1") - private String wechatNo1; - - /** 微信名称2 */ - @Schema(description = "微信名称2") - private String wechatNo2; - - /** 微信名称3 */ - @Schema(description = "微信名称3") - private String wechatNo3; - - /** 详细联系地址 */ - @Schema(description = "详细联系地址") - private String contactAddress; - - /** 关系详细描述 */ - @Schema(description = "关系详细描述") - private String relationDesc; - - /** 状态 */ - @Schema(description = "状态:0-无效,1-有效") - private Integer status; - - /** 生效日期 */ - @Schema(description = "关系生效日期") - private Date effectiveDate; - - /** 失效日期 */ - @Schema(description = "关系失效日期") - private Date invalidDate; - - /** 备注 */ - @Schema(description = "备注信息") - private String remark; - - /** 数据来源 */ - @Schema(description = "数据来源:MANUAL-手工录入,IMPORT-导入") - private String dataSource; - - /** 是否是员工亲属 */ - @Schema(description = "是否是员工亲属:0-否") - private Boolean isEmpFamily; - - /** 是否是客户亲属 */ - @Schema(description = "是否是客户亲属:1-是") - private Boolean isCustFamily; - - /** 创建时间 */ - @Schema(description = "创建时间") - private Date createTime; - - /** 更新时间 */ - @Schema(description = "更新时间") - private Date updateTime; - - /** 创建人 */ - @Schema(description = "创建人") - private String createdBy; - - /** 更新人 */ - @Schema(description = "更新人") - private String updatedBy; -} -``` - -**Step 2: 创建导入失败VO** - -复制 `StaffFmyRelationImportFailureVO.java`,修改: - -```java -package com.ruoyi.ccdi.domain.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 信贷客户家庭关系导入失败VO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系导入失败记录") -public class CustFmyRelationImportFailureVO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 行号 */ - @Schema(description = "行号") - private Integer rowNum; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - private String relationName; - - /** 错误消息 */ - @Schema(description = "错误消息") - private String errorMessage; -} -``` - -**Step 3: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/ -git commit -m "feat: 添加信贷客户家庭关系VO类" -``` - ---- - -### Task 4: 创建Excel类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java` - -**Step 1: 创建Excel类** - -复制 `CcdiStaffFmyRelationExcel.java`,修改: - -```java -package com.ruoyi.ccdi.domain.excel; - -import com.alibaba.excel.annotation.ExcelProperty; -import com.alibaba.excel.annotation.write.style.ColumnWidth; -import com.alibaba.excel.annotation.write.style.ContentRowHeight; -import com.alibaba.excel.annotation.write.style.HeadRowHeight; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -/** - * 信贷客户家庭关系Excel导入导出对象 - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@ContentRowHeight(20) -@HeadRowHeight(30) -public class CcdiCustFmyRelationExcel implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 信贷客户身份证号 */ - @ExcelProperty(value = "信贷客户身份证号*", index = 0) - @ColumnWidth(20) - private String personId; - - /** 关系类型 */ - @ExcelProperty(value = "关系类型*", index = 1) - @ColumnWidth(15) - private String relationType; - - /** 关系人姓名 */ - @ExcelProperty(value = "关系人姓名*", index = 2) - @ColumnWidth(15) - private String relationName; - - /** 性别 */ - @ExcelProperty(value = "性别", index = 3) - @ColumnWidth(10) - private String gender; - - /** 出生日期 */ - @ExcelProperty(value = "出生日期", index = 4) - @ColumnWidth(15) - private Date birthDate; - - /** 关系人证件类型 */ - @ExcelProperty(value = "关系人证件类型*", index = 5) - @ColumnWidth(15) - private String relationCertType; - - /** 关系人证件号码 */ - @ExcelProperty(value = "关系人证件号码*", index = 6) - @ColumnWidth(20) - private String relationCertNo; - - /** 手机号码1 */ - @ExcelProperty(value = "手机号码1", index = 7) - @ColumnWidth(15) - private String mobilePhone1; - - /** 手机号码2 */ - @ExcelProperty(value = "手机号码2", index = 8) - @ColumnWidth(15) - private String mobilePhone2; - - /** 微信名称1 */ - @ExcelProperty(value = "微信名称1", index = 9) - @ColumnWidth(15) - private String wechatNo1; - - /** 微信名称2 */ - @ExcelProperty(value = "微信名称2", index = 10) - @ColumnWidth(15) - private String wechatNo2; - - /** 微信名称3 */ - @ExcelProperty(value = "微信名称3", index = 11) - @ColumnWidth(15) - private String wechatNo3; - - /** 详细联系地址 */ - @ExcelProperty(value = "详细联系地址", index = 12) - @ColumnWidth(30) - private String contactAddress; - - /** 关系详细描述 */ - @ExcelProperty(value = "关系详细描述", index = 13) - @ColumnWidth(30) - private String relationDesc; - - /** 状态 */ - @ExcelProperty(value = "状态", index = 14) - @ColumnWidth(10) - private Integer status; - - /** 生效日期 */ - @ExcelProperty(value = "生效日期", index = 15) - @ColumnWidth(18) - private Date effectiveDate; - - /** 失效日期 */ - @ExcelProperty(value = "失效日期", index = 16) - @ColumnWidth(18) - private Date invalidDate; - - /** 备注 */ - @ExcelProperty(value = "备注", index = 17) - @ColumnWidth(30) - private String remark; -} -``` - -**Step 2: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java -git commit -m "feat: 添加信贷客户家庭关系Excel类" -``` - ---- - -### Task 5: 创建Mapper接口 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java` - -**Step 1: 创建Mapper接口** - -复制 `CcdiStaffFmyRelationMapper.java`,修改: - -```java -package com.ruoyi.ccdi.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.ruoyi.ccdi.domain.CcdiCustFmyRelation; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; -import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * 信贷客户家庭关系Mapper接口 - * - * @author ruoyi - * @date 2026-02-11 - */ -public interface CcdiCustFmyRelationMapper extends BaseMapper { - - /** - * 分页查询信贷客户家庭关系 - * - * @param page 分页对象 - * @param query 查询条件 - * @return 信贷客户家庭关系VO列表 - */ - Page selectRelationPage(Page page, - @Param("query") CcdiCustFmyRelationQueryDTO query); - - /** - * 根据ID查询信贷客户家庭关系详情 - * - * @param id 主键ID - * @return 信贷客户家庭关系VO - */ - CcdiCustFmyRelationVO selectRelationById(@Param("id") Long id); - - /** - * 查询已存在的关系记录(用于导入校验) - * - * @param personId 信贷客户身份证号 - * @param relationType 关系类型 - * @param relationCertNo 关系人证件号码 - * @return 已存在的关系记录 - */ - CcdiCustFmyRelation selectExistingRelations(@Param("personId") String personId, - @Param("relationType") String relationType, - @Param("relationCertNo") String relationCertNo); - - /** - * 批量插入信贷客户家庭关系 - * - * @param relations 信贷客户家庭关系列表 - * @return 插入条数 - */ - int insertBatch(@Param("relations") List relations); - - /** - * 根据证件号码查询关系数量 - * - * @param relationCertNo 关系人证件号码 - * @return 关系数量 - */ - int countByCertNo(@Param("relationCertNo") String relationCertNo); -} -``` - -**Step 2: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java -git commit -m "feat: 添加信贷客户家庭关系Mapper接口" -``` - ---- - -### Task 6: 创建Mapper XML映射 - -**Files:** -- Create: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml` - -**Step 1: 创建XML映射文件** - -复制 `CcdiStaffFmyRelationMapper.xml`,进行以下关键修改: - -1. **修改namespace和resultMap** -2. **移除LEFT JOIN员工表** -3. **修改WHERE条件为 `is_cust_family = 1`** -4. **移除personName相关字段** - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSERT INTO ccdi_cust_fmy_relation ( - person_id, relation_type, relation_name, gender, birth_date, - relation_cert_type, relation_cert_no, mobile_phone1, mobile_phone2, - wechat_no1, wechat_no2, wechat_no3, contact_address, relation_desc, - status, effective_date, invalid_date, remark, data_source, - is_emp_family, is_cust_family, created_by, create_time - ) VALUES - - ( - #{item.personId}, #{item.relationType}, #{item.relationName}, - #{item.gender}, #{item.birthDate}, #{item.relationCertType}, - #{item.relationCertNo}, #{item.mobilePhone1}, #{item.mobilePhone2}, - #{item.wechatNo1}, #{item.wechatNo2}, #{item.wechatNo3}, - #{item.contactAddress}, #{item.relationDesc}, #{item.status}, - #{item.effectiveDate}, #{item.invalidDate}, #{item.remark}, - #{item.dataSource}, #{item.isEmpFamily}, #{item.isCustFamily}, - #{item.createdBy}, #{item.createTime} - ) - - - - - - - -``` - -**Step 2: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml -git commit -m "feat: 添加信贷客户家庭关系Mapper XML映射" -``` - ---- - -### Task 7: 创建Service接口 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationService.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationImportService.java` - -**Step 1: 创建主Service接口** - -复制 `ICcdiStaffFmyRelationService.java`,修改: - -```java -package com.ruoyi.ccdi.service; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; -import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; -import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; - -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -/** - * 信贷客户家庭关系Service接口 - * - * @author ruoyi - * @date 2026-02-11 - */ -public interface ICcdiCustFmyRelationService { - - /** - * 分页查询信贷客户家庭关系 - * - * @param query 查询条件 - * @param pageNum 页码 - * @param pageSize 每页条数 - * @return 分页结果 - */ - Page selectRelationPage(CcdiCustFmyRelationQueryDTO query, - Integer pageNum, Integer pageSize); - - /** - * 根据ID查询信贷客户家庭关系详情 - * - * @param id 主键ID - * @return 信贷客户家庭关系VO - */ - CcdiCustFmyRelationVO selectRelationById(Long id); - - /** - * 新增信贷客户家庭关系 - * - * @param addDTO 新增DTO - * @return 是否成功 - */ - boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO); - - /** - * 修改信贷客户家庭关系 - * - * @param editDTO 编辑DTO - * @return 是否成功 - */ - boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO); - - /** - * 删除信贷客户家庭关系 - * - * @param ids 主键ID数组 - * @return 是否成功 - */ - boolean deleteRelationByIds(Long[] ids); - - /** - * 导出信贷客户家庭关系 - * - * @param query 查询条件 - * @param response HTTP响应 - */ - void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response); - - /** - * 生成导入模板 - * - * @param response HTTP响应 - */ - void importTemplate(HttpServletResponse response); - - /** - * 批量导入信贷客户家庭关系 - * - * @param excels Excel数据列表 - * @return 导入任务ID - */ - String importRelations(List excels); -} -``` - -**Step 2: 创建导入Service接口** - -复制 `ICcdiStaffFmyRelationImportService.java`,修改: - -```java -package com.ruoyi.ccdi.service; - -import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; -import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; - -import java.util.List; - -/** - * 信贷客户家庭关系导入Service接口 - * - * @author ruoyi - * @date 2026-02-11 - */ -public interface ICcdiCustFmyRelationImportService { - - /** - * 异步导入信贷客户家庭关系 - * - * @param excels Excel数据列表 - * @param taskId 任务ID - */ - void importRelationsAsync(List excels, String taskId); - - /** - * 校验单条数据 - * - * @param excel Excel数据 - * @param rowNum 行号 - * @return 错误消息,为null表示校验通过 - */ - String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum); - - /** - * 获取导入失败记录 - * - * @param taskId 任务ID - * @return 失败记录列表 - */ - List getImportFailures(String taskId); -} -``` - -**Step 3: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ -git commit -m "feat: 添加信贷客户家庭关系Service接口" -``` - ---- - -### Task 8: 创建Service实现类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationServiceImpl.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationImportServiceImpl.java` - -**Step 1: 创建主Service实现类** - -复制 `CcdiStaffFmyRelationServiceImpl.java`,进行以下关键修改: - -1. **类名和注入的Mapper/Service** -2. **设置 `isEmpFamily=false, isCustFamily=true`** -3. **Redis Key为 `import:custFmyRelation:`** - -```java -package com.ruoyi.ccdi.service.impl; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.ruoyi.ccdi.domain.CcdiCustFmyRelation; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; -import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; -import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; -import com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper; -import com.ruoyi.ccdi.service.ICcdiCustFmyRelationImportService; -import com.ruoyi.ccdi.service.ICcdiCustFmyRelationService; -import com.ruoyi.common.utils.SecurityUtils; -import com.ruoyi.common.utils.StringUtils; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.beans.BeanUtils; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * 信贷客户家庭关系Service实现 - * - * @author ruoyi - * @date 2026-02-11 - */ -@Service -public class CcdiCustFmyRelationServiceImpl implements ICcdiCustFmyRelationService { - - @Resource - private CcdiCustFmyRelationMapper mapper; - - @Resource - private ICcdiCustFmyRelationImportService importService; - - @Resource - private RedisTemplate redisTemplate; - - private static final String IMPORT_TASK_KEY_PREFIX = "import:custFmyRelation:"; - - @Override - public Page selectRelationPage(CcdiCustFmyRelationQueryDTO query, - Integer pageNum, Integer pageSize) { - Page page = new Page<>(pageNum, pageSize); - return mapper.selectRelationPage(page, query); - } - - @Override - public CcdiCustFmyRelationVO selectRelationById(Long id) { - return mapper.selectRelationById(id); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO) { - CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); - BeanUtils.copyProperties(addDTO, relation); - - // 关键设置:客户家庭关系 - relation.setIsEmpFamily(false); - relation.setIsCustFamily(true); - relation.setStatus(1); - relation.setDataSource("MANUAL"); - - return mapper.insert(relation) > 0; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO) { - CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); - BeanUtils.copyProperties(editDTO, relation); - - return mapper.updateById(relation) > 0; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public boolean deleteRelationByIds(Long[] ids) { - return mapper.deleteBatchIds(List.of(ids)) > 0; - } - - @Override - public void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response) { - // 查询所有符合条件的数据(不分页) - Page page = new Page<>(1, 10000); - Page result = mapper.selectRelationPage(page, query); - - List excels = result.getRecords().stream() - .map(this::convertToExcel) - .toList(); - - // 使用EasyExcel导出 - try { - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - response.setCharacterEncoding("utf-8"); - String fileName = URLEncoder.encode("信贷客户家庭关系", StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); - - // 这里使用EasyExcel工具类导出 - // EasyExcel.write(response.getOutputStream(), CcdiCustFmyRelationExcel.class) - // .sheet("信贷客户家庭关系") - // .doWrite(excels); - } catch (Exception e) { - throw new RuntimeException("导出失败", e); - } - } - - @Override - public void importTemplate(HttpServletResponse response) { - try { - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - response.setCharacterEncoding("utf-8"); - String fileName = URLEncoder.encode("信贷客户家庭关系导入模板", StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); - - // EasyExcel.write(response.getOutputStream(), CcdiCustFmyRelationExcel.class) - // .sheet("模板") - // .doWrite(Collections.emptyList()); - } catch (Exception e) { - throw new RuntimeException("模板下载失败", e); - } - } - - @Override - public String importRelations(List excels) { - String taskId = UUID.randomUUID().toString(); - - // 保存任务状态到Redis - redisTemplate.opsForValue().set(IMPORT_TASK_KEY_PREFIX + taskId, "PROCESSING", 1, TimeUnit.HOURS); - - // 异步导入 - importService.importRelationsAsync(excels, taskId); - - return taskId; - } - - private CcdiCustFmyRelationExcel convertToExcel(CcdiCustFmyRelationVO vo) { - CcdiCustFmyRelationExcel excel = new CcdiCustFmyRelationExcel(); - BeanUtils.copyProperties(vo, excel); - return excel; - } -} -``` - -**Step 2: 创建导入Service实现类** - -复制 `CcdiStaffFmyRelationImportServiceImpl.java`,修改: - -```java -package com.ruoyi.ccdi.service.impl; - -import com.alibaba.excel.EasyExcel; -import com.ruoyi.ccdi.domain.CcdiCustFmyRelation; -import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; -import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; -import com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper; -import com.ruoyi.ccdi.service.ICcdiCustFmyRelationImportService; -import com.ruoyi.common.utils.SecurityUtils; -import jakarta.annotation.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * 信贷客户家庭关系导入Service实现 - * - * @author ruoyi - * @date 2026-02-11 - */ -@Service -public class CcdiCustFmyRelationImportServiceImpl implements ICcdiCustFmyRelationImportService { - - private static final Logger log = LoggerFactory.getLogger(CcdiCustFmyRelationImportServiceImpl.class); - - @Resource - private CcdiCustFmyRelationMapper mapper; - - @Resource - private RedisTemplate redisTemplate; - - private static final String IMPORT_TASK_KEY_PREFIX = "import:custFmyRelation:"; - private static final String IMPORT_FAILURE_KEY_PREFIX = "import:custFmyRelation:failures:"; - - @Async - @Override - @Transactional(rollbackFor = Exception.class) - public void importRelationsAsync(List excels, String taskId) { - List validRelations = new ArrayList<>(); - List failures = new ArrayList<>(); - - try { - for (int i = 0; i < excels.size(); i++) { - CcdiCustFmyRelationExcel excel = excels.get(i); - Integer rowNum = i + 2; // Excel行号从2开始(第1行是表头) - - String errorMessage = validateExcelRow(excel, rowNum); - if (errorMessage != null) { - CustFmyRelationImportFailureVO failure = new CustFmyRelationImportFailureVO(); - failure.setRowNum(rowNum); - failure.setPersonId(excel.getPersonId()); - failure.setRelationType(excel.getRelationType()); - failure.setRelationName(excel.getRelationName()); - failure.setErrorMessage(errorMessage); - failures.add(failure); - continue; - } - - CcdiCustFmyRelation relation = convertToRelation(excel); - validRelations.add(relation); - } - - // 批量插入有效数据 - if (!validRelations.isEmpty()) { - mapper.insertBatch(validRelations); - } - - // 保存失败记录到Redis(24小时过期) - if (!failures.isEmpty()) { - redisTemplate.opsForValue().set( - IMPORT_FAILURE_KEY_PREFIX + taskId, - failures, - 24, - TimeUnit.HOURS - ); - } - - // 更新任务状态 - redisTemplate.opsForValue().set( - IMPORT_TASK_KEY_PREFIX + taskId, - "COMPLETED:" + validRelations.size() + ":" + failures.size(), - 1, - TimeUnit.HOURS - ); - - } catch (Exception e) { - log.error("导入失败", e); - redisTemplate.opsForValue().set( - IMPORT_TASK_KEY_PREFIX + taskId, - "FAILED:" + e.getMessage(), - 1, - TimeUnit.HOURS - ); - } - } - - @Override - public String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum) { - if (excel.getPersonId() == null || excel.getPersonId().trim().isEmpty()) { - return "信贷客户身份证号不能为空"; - } - - if (excel.getRelationType() == null || excel.getRelationType().trim().isEmpty()) { - return "关系类型不能为空"; - } - - if (excel.getRelationName() == null || excel.getRelationName().trim().isEmpty()) { - return "关系人姓名不能为空"; - } - - if (excel.getRelationCertType() == null || excel.getRelationCertType().trim().isEmpty()) { - return "关系人证件类型不能为空"; - } - - if (excel.getRelationCertNo() == null || excel.getRelationCertNo().trim().isEmpty()) { - return "关系人证件号码不能为空"; - } - - // 检查是否已存在相同的关系 - CcdiCustFmyRelation existing = mapper.selectExistingRelations( - excel.getPersonId(), - excel.getRelationType(), - excel.getRelationCertNo() - ); - - if (existing != null) { - return "该关系已存在,请勿重复导入"; - } - - return null; // 校验通过 - } - - @Override - @SuppressWarnings("unchecked") - public List getImportFailures(String taskId) { - Object obj = redisTemplate.opsForValue().get(IMPORT_FAILURE_KEY_PREFIX + taskId); - if (obj != null) { - return (List) obj; - } - return new ArrayList<>(); - } - - private CcdiCustFmyRelation convertToRelation(CcdiCustFmyRelationExcel excel) { - CcdiCustFmyRelation relation = new CcdiCustFmyRelation(); - org.springframework.beans.BeanUtils.copyProperties(excel, relation); - - relation.setIsEmpFamily(false); - relation.setIsCustFamily(true); - relation.setStatus(excel.getStatus() != null ? excel.getStatus() : 1); - relation.setDataSource("IMPORT"); - relation.setCreatedBy(SecurityUtils.getUsername()); - relation.setCreateTime(new Date()); - - return relation; - } -} -``` - -**Step 3: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/ -git commit -m "feat: 添加信贷客户家庭关系Service实现类" -``` - ---- - -### Task 9: 创建Controller - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java` - -**Step 1: 创建Controller** - -复制 `CcdiStaffFmyRelationController.java`,修改: - -```java -package com.ruoyi.ccdi.controller; - -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationAddDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationEditDTO; -import com.ruoyi.ccdi.domain.dto.CcdiCustFmyRelationQueryDTO; -import com.ruoyi.ccdi.domain.excel.CcdiCustFmyRelationExcel; -import com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO; -import com.ruoyi.ccdi.domain.vo.CustFmyRelationImportFailureVO; -import com.ruoyi.ccdi.service.ICcdiCustFmyRelationService; -import com.ruoyi.common.core.controller.BaseController; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.core.page.TableDataInfo; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.List; - -/** - * 信贷客户家庭关系Controller - * - * @author ruoyi - * @date 2026-02-11 - */ -@Tag(name = "信贷客户家庭关系管理") -@RestController -@RequestMapping("/ccdi/custFmyRelation") -public class CcdiCustFmyRelationController extends BaseController { - - @Resource - private ICcdiCustFmyRelationService relationService; - - /** - * 查询信贷客户家庭关系列表 - */ - @Operation(summary = "查询信贷客户家庭关系列表") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") - @GetMapping("/list") - public TableDataInfo list(CcdiCustFmyRelationQueryDTO query) { - startPage(); - Page page = relationService.selectRelationPage(query, getPageNum(), getPageSize()); - return getDataTable(page.getRecords(), page.getTotal()); - } - - /** - * 根据ID查询信贷客户家庭关系详情 - */ - @Operation(summary = "查询信贷客户家庭关系详情") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") - @GetMapping("/{id}") - public AjaxResult getInfo(@PathVariable("id") Long id) { - CcdiCustFmyRelationVO relation = relationService.selectRelationById(id); - return success(relation); - } - - /** - * 新增信贷客户家庭关系 - */ - @Operation(summary = "新增信贷客户家庭关系") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:add')") - @PostMapping - public AjaxResult add(@Validated @RequestBody CcdiCustFmyRelationAddDTO addDTO) { - return toAjax(relationService.insertRelation(addDTO)); - } - - /** - * 修改信贷客户家庭关系 - */ - @Operation(summary = "修改信贷客户家庭关系") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:edit')") - @PutMapping - public AjaxResult edit(@Validated @RequestBody CcdiCustFmyRelationEditDTO editDTO) { - return toAjax(relationService.updateRelation(editDTO)); - } - - /** - * 删除信贷客户家庭关系 - */ - @Operation(summary = "删除信贷客户家庭关系") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:remove')") - @DeleteMapping("/{ids}") - public AjaxResult remove(@PathVariable Long[] ids) { - return toAjax(relationService.deleteRelationByIds(ids)); - } - - /** - * 导出信贷客户家庭关系 - */ - @Operation(summary = "导出信贷客户家庭关系") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:export')") - @PostMapping("/export") - public void export(HttpServletResponse response, CcdiCustFmyRelationQueryDTO query) { - relationService.exportRelations(query, response); - } - - /** - * 下载导入模板 - */ - @Operation(summary = "下载导入模板") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:import')") - @PostMapping("/importTemplate") - public void importTemplate(HttpServletResponse response) { - relationService.importTemplate(response); - } - - /** - * 导入信贷客户家庭关系 - */ - @Operation(summary = "导入信贷客户家庭关系") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:import')") - @PostMapping("/importData") - public AjaxResult importData(@RequestParam("file") MultipartFile file) throws IOException { - List excels = EasyExcel.read(file.getInputStream()) - .head(CcdiCustFmyRelationExcel.class) - .sheet() - .doReadSync(); - - String taskId = relationService.importRelations(excels); - return success("导入任务已提交,任务ID: " + taskId); - } - - /** - * 查询导入状态 - */ - @Operation(summary = "查询导入状态") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") - @GetMapping("/importStatus/{taskId}") - public AjaxResult getImportStatus(@PathVariable String taskId) { - // 从Redis获取任务状态 - Object status = redisTemplate.opsForValue().get("import:custFmyRelation:" + taskId); - return success(status); - } - - /** - * 查询导入失败记录 - */ - @Operation(summary = "查询导入失败记录") - @PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')") - @GetMapping("/importFailures/{taskId}") - public TableDataInfo getImportFailures(@PathVariable String taskId) { - startPage(); - List failures = relationService.getImportFailures(taskId); - return getDataTable(failures, (long) failures.size()); - } -} -``` - -**Step 2: 编译验证** - -```bash -mvn compile -pl ruoyi-ccdi -``` - -**预期结果:** BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java -git commit -m "feat: 添加信贷客户家庭关系Controller" -``` - ---- - -### Task 12: 创建菜单权限SQL - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation_menu.sql` - -**Step 1: 创建菜单SQL** - -创建 `sql/ccdi_cust_fmy_relation_menu.sql`: - -```sql --- 信贷客户家庭关系菜单 -INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) -VALUES -('信贷客户家庭关系', (SELECT menu_id FROM sys_menu WHERE menu_name = '信息维护' LIMIT 1), 5, 'custFmyRelation', 'ccdiCustFmyRelation/index', 1, 0, 'C', '0', '0', 'ccdi:custFmyRelation:list', 'peoples', 'admin', NOW(), '', NULL, '信贷客户家庭关系菜单'); - --- 获取刚插入的菜单ID -SET @parent_id = LAST_INSERT_ID(); - --- 添加按钮权限 -INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES -('信贷客户家庭关系查询', @parent_id, 1, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:query', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系新增', @parent_id, 2, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:add', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系修改', @parent_id, 3, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:edit', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系删除', @parent_id, 4, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:remove', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系导出', @parent_id, 5, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:export', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系导入', @parent_id, 6, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:import', '#', 'admin', NOW(), '', NULL, ''); -``` - -**Step 2: Commit** - -```bash -git add sql/ccdi_cust_fmy_relation_menu.sql -git commit -m "feat: 添加信贷客户家庭关系菜单权限" -``` - ---- - -## 测试验证 - -### Task 14: 后端接口测试 - -**前置条件:** -- 后端服务已启动 (`mvn spring-boot:run -pl ruoyi-admin`) -- 数据库连接正常 -- Redis服务正常运行 - -**Step 1: 测试登录获取token** - -```bash -curl -X POST "http://localhost:8080/login" \ - -H "Content-Type: application/json" \ - -d '{"username":"admin","password":"admin123"}' -``` - -**预期结果:** 返回token - -**Step 2: 测试查询列表接口** - -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/list?pageNum=1&pageSize=10" \ - -H "Authorization: Bearer " -``` - -**预期结果:** 返回空列表(无数据) - -**Step 3: 测试新增接口** - -```bash -curl -X POST "http://localhost:8080/ccdi/custFmyRelation" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "personId": "110101199001011234", - "relationType": "配偶", - "relationName": "张三", - "gender": "M", - "relationCertType": "身份证", - "relationCertNo": "110101199001015678" - }' -``` - -**预期结果:** 返回成功 - -**Step 4: 测试查询详情接口** - -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/1" \ - -H "Authorization: Bearer " -``` - -**预期结果:** 返回刚插入的记录 - -**Step 5: 测试修改接口** - -```bash -curl -X PUT "http://localhost:8080/ccdi/custFmyRelation" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "id": 1, - "personId": "110101199001011234", - "relationType": "配偶", - "relationName": "张三丰", - "gender": "M", - "relationCertType": "身份证", - "relationCertNo": "110101199001015678", - "status": 1 - }' -``` - -**预期结果:** 返回成功 - -**Step 6: 测试删除接口** - -```bash -curl -X DELETE "http://localhost:8080/ccdi/custFmyRelation/1" \ - -H "Authorization: Bearer " -``` - -**预期结果:** 返回成功 - -**Step 7: 验证Swagger文档** - -访问 http://localhost:8080/swagger-ui/index.html - -**预期结果:** 看到"信贷客户家庭关系管理"分组,所有接口正常显示 - ---- - -## 完成检查清单 - -- [ ] 数据库表创建成功 -- [ ] 实体类编译通过 -- [ ] DTO类编译通过 -- [ ] VO类编译通过 -- [ ] Excel类编译通过 -- [ ] Mapper接口编译通过 -- [ ] Mapper XML映射配置正确 -- [ ] Service接口编译通过 -- [ ] Service实现类编译通过 -- [ ] Controller编译通过 -- [ ] Controller所有接口在Swagger正常显示 -- [ ] 菜单权限SQL已执行 -- [ ] 登录接口测试通过 -- [ ] 查询列表接口测试通过 -- [ ] 新增接口测试通过 -- [ ] 修改接口测试通过 -- [ ] 删除接口测试通过 -- [ ] 导出接口测试通过 -- [ ] 导入模板下载测试通过 - ---- - -## 预期结果 - -完成后,后端将提供以下功能: - -1. **完整的CRUD接口** - - 分页查询信贷客户家庭关系列表 - - 根据ID查询详情 - - 新增信贷客户家庭关系 - - 修改信贷客户家庭关系 - - 删除信贷客户家庭关系 - -2. **导入导出功能** - - 导出Excel数据 - - 下载导入模板 - - 异步批量导入 - - 导入状态查询 - - 导入失败记录查看 - -3. **权限控制** - - 完整的CRUD权限标识 - - 按钮级权限控制 - -4. **数据隔离** - - 独立表 `ccdi_cust_fmy_relation` - - `is_cust_family = 1` 过滤条件 - - 与员工亲属关系完全分离 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-frontend.md b/docs/plans/2026-02-11-cust-fmy-relation-frontend.md deleted file mode 100644 index 423095f..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-frontend.md +++ /dev/null @@ -1,1084 +0,0 @@ -# 信贷客户家庭关系维护功能 - 前端实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**目标:** 开发信贷客户家庭关系维护功能的完整前端实现,包括API接口、页面组件和交互功能 - -**架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立页面组件 `ccdiCustFmyRelation` - -**技术栈:** Vue 2.6.12 + Element UI 2.15.14 + Vuex 3.6.0 + Axios 0.28.1 - ---- - -## 前置条件 - -### 环境要求 -- Node.js 14+ -- npm 6+ -- 现代浏览器(Chrome/Edge/Firefox) - -### 依赖服务 -- 后端服务已启动并运行在 `http://localhost:8080` -- 菜单权限SQL已执行,菜单已添加到系统 - -### 参考模块 -- `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - 员工亲属关系API(参考模板) -- `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - 员工亲属关系页面(参考模板) - ---- - -## 任务列表 - -### Task 10: 创建API接口文件 - -**Files:** -- Create: `ruoyi-ui/src/api/ccdiCustFmyRelation.js` -- Reference: `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - -**Step 1: 创建API文件** - -创建 `ruoyi-ui/src/api/ccdiCustFmyRelation.js`: - -```javascript -import request from '@/utils/request' - -// 查询信贷客户家庭关系列表 -export function listRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/list', - method: 'get', - params: query - }) -} - -// 查询信贷客户家庭关系详细 -export function getRelation(id) { - return request({ - url: '/ccdi/custFmyRelation/' + id, - method: 'get' - }) -} - -// 新增信贷客户家庭关系 -export function addRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'post', - data: data - }) -} - -// 修改信贷客户家庭关系 -export function updateRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'put', - data: data - }) -} - -// 删除信贷客户家庭关系 -export function delRelation(ids) { - return request({ - url: '/ccdi/custFmyRelation/' + ids, - method: 'delete' - }) -} - -// 导出信贷客户家庭关系 -export function exportRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/export', - method: 'post', - params: query - }) -} - -// 下载导入模板 -export function importTemplate() { - return request({ - url: '/ccdi/custFmyRelation/importTemplate', - method: 'post' - }) -} - -// 导入信贷客户家庭关系 -export function importData(file) { - const formData = new FormData() - formData.append('file', file) - return request({ - url: '/ccdi/custFmyRelation/importData', - method: 'post', - data: formData - }) -} - -// 查询导入状态 -export function getImportStatus(taskId) { - return request({ - url: '/ccdi/custFmyRelation/importStatus/' + taskId, - method: 'get' - }) -} - -// 查询导入失败记录 -export function getImportFailures(taskId, pageNum, pageSize) { - return request({ - url: '/ccdi/custFmyRelation/importFailures/' + taskId, - method: 'get', - params: { pageNum, pageSize } - }) -} -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/api/ccdiCustFmyRelation.js -git commit -m "feat: 添加信贷客户家庭关系API接口" -``` - ---- - -### Task 11: 创建主页面组件 - -**Files:** -- Create: `ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue` -- Reference: `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - -**Step 1: 创建页面组件** - -复制 `ccdiStaffFmyRelation/index.vue`,进行以下关键修改: - -1. **移除员工姓名相关功能** - 只保留信贷客户身份证号输入 -2. **简化查询条件** - 移除状态下拉框 -3. **修改表单** - personId改为普通输入框,不使用远程搜索 -4. **修改权限标识** - 全部 `staffFmyRelation` → `custFmyRelation` -5. **修改localStorage键** - `cust_fmy_relation_import_last_task` -6. **修改字典类型引用** - -完整代码结构: - -```vue - - - -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue -git commit -m "feat: 添加信贷客户家庭关系页面组件" -``` - ---- - -### Task 13: 配置字典数据 - -**前置条件:** -- 已登录系统 -- 具有系统管理权限 - -**Step 1: 确认字典类型存在** - -1. 登录系统 -2. 导航到: 系统管理 → 字典管理 -3. 确认以下字典类型已存在: - - `ccdi_relation_type`: 关系类型 - - `ccdi_indiv_gender`: 性别 - - `ccdi_certificate_type`: 证件类型 - -**Step 2: 添加字典数据(如不存在)** - -如果字典类型不存在,参考以下数据添加: - -**关系类型 (ccdi_relation_type):** -``` -配偶 | 配偶 -父亲 | 父亲 -母亲 | 母亲 -子女 | 子女 -其他 | 其他 -``` - -**性别 (ccdi_indiv_gender):** -``` -M | 男 -F | 女 -O | 其他 -``` - -**证件类型 (ccdi_certificate_type):** -``` -身份证 | 身份证 -护照 | 护照 -军官证 | 军官证 -其他 | 其他 -``` - ---- - -## 测试验证 - -### Task 15: 前端功能测试 - -**前置条件:** -- 后端服务已启动并运行正常 -- 前端服务已启动 (`npm run dev`) -- 菜单权限已配置 -- 已登录系统(admin/admin123) - -**Step 1: 启动前端服务** - -```bash -cd ruoyi-ui -npm run dev -``` - -**预期结果:** 服务启动成功,访问 http://localhost - -**Step 2: 登录系统** - -用户名: `admin` -密码: `admin123` - -**Step 3: 导航到信贷客户家庭关系页面** - -路径: 信息维护 → 信贷客户家庭关系 - -**预期结果:** 页面正常加载,显示查询条件和列表 - -**Step 4: 测试新增功能** - -1. 点击"新增"按钮 -2. 填写表单: - - 信贷客户身份证号: `110101199001011234` - - 关系类型: `配偶` - - 关系人姓名: `张三` - - 性别: `男` - - 证件类型: `身份证` - - 证件号码: `110101199001015678` - - 手机号码1: `13800138000` -3. 点击"确定" - -**预期结果:** 新增成功,列表显示新记录,弹出成功提示 - -**Step 5: 测试查询功能** - -1. 在查询条件中输入: - - 信贷客户身份证号: `110101199001011234` -2. 点击"搜索" - -**预期结果:** 列表显示符合条件的记录 - -**Step 6: 测试编辑功能** - -1. 点击记录的"编辑"按钮 -2. 修改关系人姓名为 `张三丰` -3. 点击"确定" - -**预期结果:** 修改成功,列表显示更新后的数据 - -**Step 7: 测试删除功能** - -1. 勾选一条记录 -2. 点击"删除"按钮 -3. 确认删除 - -**预期结果:** 删除成功,列表不再显示该记录 - -**Step 8: 测试导出功能** - -1. 添加几条测试数据 -2. 点击"导出"按钮 - -**预期结果:** 下载Excel文件,数据正确 - -**Step 9: 测试导入模板下载** - -1. 点击"导入"按钮 -2. 在导入对话框中点击"下载模板" - -**预期结果:** 下载Excel模板文件,包含所有必填字段 - -**Step 10: 测试导入功能** - -1. 准备测试数据Excel文件 -2. 点击"导入"按钮 -3. 上传准备好的Excel文件 -4. 等待异步导入完成 - -**预期结果:** -- 显示导入任务已提交提示 -- 导入完成后显示导入结果对话框 -- 成功数据出现在列表中 -- 失败数据显示在失败记录表格中 - -**Step 11: 测试字典显示** - -验证以下字段正确显示字典标签: -- 关系类型: 显示"配偶"、"父亲"等 -- 性别: 显示"男"、"女"等 -- 证件类型: 显示"身份证"、"护照"等 - -**预期结果:** 所有字典字段正确显示标签值 - -**Step 12: 测试权限控制** - -1. 退出登录,使用没有权限的账号登录 -2. 导航到信贷客户家庭关系页面 - -**预期结果:** 相应的操作按钮不显示或禁用 - ---- - -### Task 16: 浏览器控制台测试 - -**Step 1: 打开浏览器开发者工具** - -按 `F12` 打开开发者工具 - -**Step 2: 检查网络请求** - -切换到 Network 标签,执行以下操作并检查请求: - -1. **列表请求:** - - Method: `GET` - - URL: `/ccdi/custFmyRelation/list` - - Status: `200` - - Response: 包含 `rows` 和 `total` - -2. **新增请求:** - - Method: `POST` - - URL: `/ccdi/custFmyRelation` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -3. **修改请求:** - - Method: `PUT` - - URL: `/ccdi/custFmyRelation` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -4. **删除请求:** - - Method: `DELETE` - - URL: `/ccdi/custFmyRelation/{ids}` - - Status: `200` - - Response: `{ "code": 200, "msg": "操作成功" }` - -**Step 3: 检查控制台错误** - -切换到 Console 标签,确认没有JavaScript错误 - -**预期结果:** 控制台无红色错误信息 - -**Step 4: 检查页面性能** - -1. 切换到 Performance 标签 -2. 录制页面操作 -3. 检查页面渲染性能 - -**预期结果:** 页面响应流畅,无明显卡顿 - ---- - -## 完成检查清单 - -### API接口 -- [ ] API文件创建完成 -- [ ] 所有接口方法定义正确 -- [ ] 请求路径正确(`/ccdi/custFmyRelation`) -- [ ] 请求方法正确(GET/POST/PUT/DELETE) - -### 页面组件 -- [ ] 页面组件创建完成 -- [ ] 查询条件显示正确 -- [ ] 列表数据显示正确 -- [ ] 新增对话框正常 -- [ ] 编辑对话框正常 -- [ ] 表单验证规则正确 -- [ ] 字典数据正确显示 -- [ ] 权限标识正确 - -### 导入导出 -- [ ] 导出功能正常 -- [ ] 导入模板下载正常 -- [ ] 导入对话框正常 -- [ ] 文件上传功能正常 -- [ ] 异步导入状态轮询正常 -- [ ] 导入结果显示正常 -- [ ] 失败记录显示正常 - -### 交互功能 -- [ ] 分页功能正常 -- [ ] 搜索功能正常 -- [ ] 重置功能正常 -- [ ] 多选功能正常 -- [ ] 批量删除功能正常 -- [ ] 单条删除功能正常 -- [ ] 新增功能正常 -- [ ] 编辑功能正常 -- [ ] 表单验证正常 - -### 权限控制 -- [ ] 菜单权限已配置 -- [ ] 按钮权限控制生效 -- [ ] 无权限时按钮不显示 - -### 用户体验 -- [ ] 页面加载速度正常 -- [ ] 操作响应及时 -- [ ] 错误提示清晰 -- [ ] 成功提示友好 -- [ ] 控制台无错误 - ---- - -## 预期结果 - -完成后,前端将提供以下功能: - -1. **完整的CRUD界面** - - 列表展示(分页) - - 简化查询(身份证号、关系类型、关系人姓名) - - 新增/编辑/删除/详情 - -2. **导入导出界面** - - 一键导出Excel - - 下载导入模板 - - 文件拖拽上传 - - 异步导入状态轮询 - - 导入结果可视化展示 - - 失败记录详细显示 - -3. **用户体验优化** - - 响应式布局 - - 字典下拉选择 - - 表单验证提示 - - 加载状态提示 - - 操作结果反馈 - -4. **权限控制** - - 菜单级权限 - - 按钮级权限 - - 操作前权限校验 diff --git a/docs/plans/2026-02-11-cust-fmy-relation-implementation.md b/docs/plans/2026-02-11-cust-fmy-relation-implementation.md deleted file mode 100644 index 5bc34e1..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-implementation.md +++ /dev/null @@ -1,962 +0,0 @@ -# 信贷客户家庭关系维护功能实施计划 - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**目标:** 开发信贷客户家庭关系维护功能,实现对信贷客户家庭成员信息的完整CRUD操作 - -**架构:** 完全复用员工亲属关系维护功能的实现逻辑,创建独立模块 `CustFamilyRelation`,新建独立表 `ccdi_cust_fmy_relation` - -**技术栈:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10 + Vue 2.6.12 + Element UI 2.15.14 + EasyExcel + Redis - ---- - -## 前置准备 - -### Task 0: 创建数据库表 - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation.sql` - -**Step 1: 创建建表SQL文件** - -```sql --- 信贷客户家庭关系表 -CREATE TABLE `ccdi_cust_fmy_relation` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `person_id` VARCHAR(50) NOT NULL COMMENT '信贷客户身份证号', - `relation_type` VARCHAR(50) NOT NULL COMMENT '关系类型', - `relation_name` VARCHAR(100) NOT NULL COMMENT '关系人姓名', - `gender` CHAR(1) DEFAULT NULL COMMENT '性别:M-男,F-女,O-其他', - `birth_date` DATE DEFAULT NULL COMMENT '关系人出生日期', - `relation_cert_type` VARCHAR(50) NOT NULL COMMENT '证件类型', - `relation_cert_no` VARCHAR(50) NOT NULL COMMENT '证件号码', - `mobile_phone1` VARCHAR(20) DEFAULT NULL COMMENT '手机号码1', - `mobile_phone2` VARCHAR(20) DEFAULT NULL COMMENT '手机号码2', - `wechat_no1` VARCHAR(50) DEFAULT NULL COMMENT '微信名称1', - `wechat_no2` VARCHAR(50) DEFAULT NULL COMMENT '微信名称2', - `wechat_no3` VARCHAR(50) DEFAULT NULL COMMENT '微信名称3', - `contact_address` VARCHAR(500) DEFAULT NULL COMMENT '详细联系地址', - `relation_desc` VARCHAR(500) DEFAULT NULL COMMENT '关系详细描述', - `status` INT NOT NULL DEFAULT 1 COMMENT '状态:0-无效,1-有效', - `effective_date` DATETIME DEFAULT NULL COMMENT '关系生效日期', - `invalid_date` DATETIME DEFAULT NULL COMMENT '关系失效日期', - `remark` TEXT COMMENT '备注信息', - `data_source` VARCHAR(50) DEFAULT NULL COMMENT '数据来源:MANUAL-手动录入,IMPORT-批量导入', - `is_emp_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是员工的家庭关系:0-否', - `is_cust_family` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否是信贷客户的家庭关系:1-是', - `created_by` VARCHAR(50) NOT NULL COMMENT '记录创建人', - `updated_by` VARCHAR(50) DEFAULT NULL COMMENT '记录更新人', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', - `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', - PRIMARY KEY (`id`), - KEY `idx_person_id` (`person_id`), - KEY `idx_relation_cert_no` (`relation_cert_no`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='信贷客户家庭关系表'; -``` - -**Step 2: 执行SQL创建表** - -Run: 连接数据库并执行 `sql/ccdi_cust_fmy_relation.sql` -Expected: 表创建成功 - -**Step 3: Commit** - -```bash -git add sql/ccdi_cust_fmy_relation.sql -git commit -m "feat: 创建信贷客户家庭关系表" -``` - ---- - -## 后端开发 - -### Task 1: 创建实体类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java:1-108` - -**Step 1: 创建实体类** - -复制 `CcdiStaffFmyRelation.java`,修改以下内容: -- 类名: `CcdiCustFmyRelation` -- 注释: `信贷客户家庭关系对象 ccdi_cust_fmy_relation` -- 表名: `@TableName("ccdi_cust_fmy_relation")` -- JavaDoc: 全部替换"员工"为"信贷客户" - -```java -package com.ruoyi.ccdi.domain; - -import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Date; - -/** - * 信贷客户家庭关系对象 ccdi_cust_fmy_relation - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@TableName("ccdi_cust_fmy_relation") -public class CcdiCustFmyRelation implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 主键ID */ - @TableId(type = IdType.AUTO) - private Long id; - - /** 信贷客户身份证号 */ - private String personId; - - /** 关系类型 */ - private String relationType; - - /** 关系人姓名 */ - private String relationName; - - /** 性别:M-男,F-女,O-其他 */ - private String gender; - - /** 出生日期 */ - private Date birthDate; - - /** 关系人证件类型 */ - private String relationCertType; - - /** 关系人证件号码 */ - private String relationCertNo; - - /** 手机号码1 */ - private String mobilePhone1; - - /** 手机号码2 */ - private String mobilePhone2; - - /** 微信名称1 */ - private String wechatNo1; - - /** 微信名称2 */ - private String wechatNo2; - - /** 微信名称3 */ - private String wechatNo3; - - /** 详细联系地址 */ - private String contactAddress; - - /** 关系详细描述 */ - private String relationDesc; - - /** 状态:0-无效,1-有效 */ - private Integer status; - - /** 生效日期 */ - private Date effectiveDate; - - /** 失效日期 */ - private Date invalidDate; - - /** 备注 */ - private String remark; - - /** 数据来源:MANUAL-手工录入,IMPORT-导入 */ - private String dataSource; - - /** 是否是员工亲属:0-否 */ - private Boolean isEmpFamily; - - /** 是否是客户亲属:1-是 */ - private Boolean isCustFamily; - - /** 创建时间 */ - @TableField(fill = FieldFill.INSERT) - private Date createTime; - - /** 更新时间 */ - @TableField(fill = FieldFill.INSERT_UPDATE) - private Date updateTime; - - /** 创建人 */ - @TableField(fill = FieldFill.INSERT) - private String createdBy; - - /** 更新人 */ - @TableField(fill = FieldFill.INSERT_UPDATE) - private String updatedBy; -} -``` - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiCustFmyRelation.java -git commit -m "feat: 添加信贷客户家庭关系实体类" -``` - ---- - -### Task 2: 创建DTO类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationAddDTO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationEditDTO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiCustFmyRelationQueryDTO.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationAddDTO.java` - -**Step 1: 创建AddDTO** - -复制 `CcdiStaffFmyRelationAddDTO.java`,修改: -- 类名: `CcdiCustFmyRelationAddDTO` -- 注释中"员工" → "信贷客户" -- personId字段注释: `@Schema(description = "信贷客户身份证号")` -- 验证消息: "员工身份证号" → "信贷客户身份证号" - -**Step 2: 创建EditDTO** - -复制 `CcdiStaffFmyRelationEditDTO.java`,修改: -- 类名: `CcdiCustFmyRelationEditDTO` -- 注释中"员工" → "信贷客户" -- 添加 `id` 字段和 `@NotNull` 验证 - -**Step 3: 创建QueryDTO(简化版)** - -```java -package com.ruoyi.ccdi.domain.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 信贷客户家庭关系查询DTO - * - * @author ruoyi - * @date 2026-02-11 - */ -@Data -@Schema(description = "信贷客户家庭关系查询") -public class CcdiCustFmyRelationQueryDTO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 信贷客户身份证号 */ - @Schema(description = "信贷客户身份证号") - private String personId; - - /** 关系类型 */ - @Schema(description = "关系类型") - private String relationType; - - /** 关系人姓名 */ - @Schema(description = "关系人姓名") - private String relationName; -} -``` - -**Step 4: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 5: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/ -git commit -m "feat: 添加信贷客户家庭关系DTO类" -``` - ---- - -### Task 3: 创建VO类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiCustFmyRelationVO.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CustFmyRelationImportFailureVO.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffFmyRelationVO.java` - -**Step 1: 创建主VO** - -复制 `CcdiStaffFmyRelationVO.java`,修改: -- 类名: `CcdiCustFmyRelationVO` -- 移除 `personName` 字段(不关联其他表) -- 注释中"员工" → "信贷客户" - -**Step 2: 创建导入失败VO** - -复制 `StaffFmyRelationImportFailureVO.java`,修改: -- 类名: `CustFmyRelationImportFailureVO` -- 注释中"员工" → "信贷客户" - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/ -git commit -m "feat: 添加信贷客户家庭关系VO类" -``` - ---- - -### Task 4: 创建Excel类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiStaffFmyRelationExcel.java` - -**Step 1: 创建Excel类** - -复制 `CcdiStaffFmyRelationExcel.java`,修改: -- 类名: `CcdiCustFmyRelationExcel` -- 注释: `信贷客户家庭关系Excel导入导出对象` -- personId字段: `@ExcelProperty(value = "信贷客户身份证号*", index = 0)` -- 其他保持不变 - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiCustFmyRelationExcel.java -git commit -m "feat: 添加信贷客户家庭关系Excel类" -``` - ---- - -### Task 5: 创建Mapper接口 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffFmyRelationMapper.java` - -**Step 1: 创建Mapper接口** - -复制 `CcdiStaffFmyRelationMapper.java`,修改: -- 包名和导入: 全部 `Staff` → `Cust` -- 类名: `CcdiCustFmyRelationMapper` -- 泛型: `CcdiCustFmyRelation` -- DTO: `CcdiCustFmyRelationQueryDTO` -- VO: `CcdiCustFmyRelationVO` -- 方法注释: "员工" → "信贷客户" - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiCustFmyRelationMapper.java -git commit -m "feat: 添加信贷客户家庭关系Mapper接口" -``` - ---- - -### Task 6: 创建Mapper XML映射 - -**Files:** -- Create: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml` -- Reference: `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml` - -**Step 1: 创建XML映射文件** - -复制 `CcdiStaffFmyRelationMapper.xml`,修改: -- namespace: `com.ruoyi.ccdi.mapper.CcdiCustFmyRelationMapper` -- resultMap: `CcdiCustFmyRelationVOResult` -- type: `com.ruoyi.ccdi.domain.vo.CcdiCustFmyRelationVO` -- 表名: `ccdi_cust_fmy_relation` -- **移除 LEFT JOIN**(不关联员工表) -- WHERE条件: `r.is_cust_family = 1` -- **移除 personName 相关字段** - -```xml - - -``` - -- selectExistingRelations: `is_cust_family = 1` - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiCustFmyRelationMapper.xml -git commit -m "feat: 添加信贷客户家庭关系Mapper XML映射" -``` - ---- - -### Task 7: 创建Service接口 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationService.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiCustFmyRelationImportService.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationService.java` - -**Step 1: 创建主Service接口** - -复制 `ICcdiStaffFmyRelationService.java`,修改: -- 接口名: `ICcdiCustFmyRelationService` -- 泛型: `CcdiCustFmyRelationVO`, `CcdiCustFmyRelationQueryDTO`, `CcdiCustFmyRelationAddDTO`, `CcdiCustFmyRelationEditDTO`, `CcdiCustFmyRelationExcel` - -**Step 2: 创建导入Service接口** - -复制 `ICcdiStaffFmyRelationImportService.java`,修改: -- 接口名: `ICcdiCustFmyRelationImportService` -- 泛型: `CcdiCustFmyRelationExcel`, `CustFmyRelationImportFailureVO` - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ -git commit -m "feat: 添加信贷客户家庭关系Service接口" -``` - ---- - -### Task 8: 创建Service实现类 - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationServiceImpl.java` -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiCustFmyRelationImportServiceImpl.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java` - -**Step 1: 创建主Service实现类** - -复制 `CcdiStaffFmyRelationServiceImpl.java`,修改: -- 类名: `CcdiCustFmyRelationServiceImpl` -- Mapper注入: `CcdiCustFmyRelationMapper` -- ImportService注入: `ICcdiCustFmyRelationImportService` -- 泛型: `CcdiCustFmyRelationVO`, `CcdiCustFmyRelationQueryDTO` 等 -- **关键修改**: - - `relation.setIsEmpFamily(false);` - - `relation.setIsCustFamily(true);` - - Redis Key: `import:custFmyRelation:` - -**Step 2: 创建导入Service实现类** - -复制 `CcdiStaffFmyRelationImportServiceImpl.java`,修改: -- 类名: `CcdiCustFmyRelationImportServiceImpl` -- Mapper注入: `CcdiCustFmyRelationMapper` -- 泛型: `CcdiCustFmyRelationExcel`, `CustFmyRelationImportFailureVO` -- Redis Key: `import:custFmyRelation:` -- 错误消息: "信贷客户身份证号" - -**Step 3: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 4: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/ -git commit -m "feat: 添加信贷客户家庭关系Service实现类" -``` - ---- - -### Task 9: 创建Controller - -**Files:** -- Create: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java` -- Reference: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiStaffFmyRelationController.java` - -**Step 1: 创建Controller** - -复制 `CcdiStaffFmyRelationController.java`,修改: -- 类名: `CcdiCustFmyRelationController` -- Tag: `@Tag(name = "信贷客户家庭关系管理")` -- RequestMapping: `/ccdi/custFmyRelation` -- Service注入: `ICcdiCustFmyRelationService`, `ICcdiCustFmyRelationImportService` -- DTO/VO: 对应的 `CcdiCust...` 类型 -- 权限标识: `ccdi:custFmyRelation:*` -- 注释: "员工" → "信贷客户" - -**Step 2: Compile** - -Run: `mvn compile -pl ruoyi-ccdi` -Expected: BUILD SUCCESS - -**Step 3: Commit** - -```bash -git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiCustFmyRelationController.java -git commit -m "feat: 添加信贷客户家庭关系Controller" -``` - ---- - -## 前端开发 - -### Task 10: 创建API接口文件 - -**Files:** -- Create: `ruoyi-ui/src/api/ccdiCustFmyRelation.js` -- Reference: `ruoyi-ui/src/api/ccdiStaffFmyRelation.js` - -**Step 1: 创建API文件** - -复制 `ccdiStaffFmyRelation.js`,修改: -- url路径: `/ccdi/custFmyRelation` -- 移除 `getStaffList` 方法(不需要) - -```javascript -import request from '@/utils/request' - -// 查询信贷客户家庭关系列表 -export function listRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/list', - method: 'get', - params: query - }) -} - -// 查询信贷客户家庭关系详细 -export function getRelation(id) { - return request({ - url: '/ccdi/custFmyRelation/' + id, - method: 'get' - }) -} - -// 新增信贷客户家庭关系 -export function addRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'post', - data: data - }) -} - -// 修改信贷客户家庭关系 -export function updateRelation(data) { - return request({ - url: '/ccdi/custFmyRelation', - method: 'put', - data: data - }) -} - -// 删除信贷客户家庭关系 -export function delRelation(ids) { - return request({ - url: '/ccdi/custFmyRelation/' + ids, - method: 'delete' - }) -} - -// 导出信贷客户家庭关系 -export function exportRelation(query) { - return request({ - url: '/ccdi/custFmyRelation/export', - method: 'post', - params: query - }) -} - -// 下载导入模板 -export function importTemplate() { - return request({ - url: '/ccdi/custFmyRelation/importTemplate', - method: 'post' - }) -} - -// 导入信贷客户家庭关系 -export function importData(file) { - const formData = new FormData() - formData.append('file', file) - return request({ - url: '/ccdi/custFmyRelation/importData', - method: 'post', - data: formData - }) -} - -// 查询导入状态 -export function getImportStatus(taskId) { - return request({ - url: '/ccdi/custFmyRelation/importStatus/' + taskId, - method: 'get' - }) -} - -// 查询导入失败记录 -export function getImportFailures(taskId, pageNum, pageSize) { - return request({ - url: '/ccdi/custFmyRelation/importFailures/' + taskId, - method: 'get', - params: { pageNum, pageSize } - }) -} -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/api/ccdiCustFmyRelation.js -git commit -m "feat: 添加信贷客户家庭关系API接口" -``` - ---- - -### Task 11: 创建主页面组件 - -**Files:** -- Create: `ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue` -- Reference: `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue` - -**Step 1: 创建页面组件** - -复制 `ccdiStaffFmyRelation/index.vue`,修改: - -1. **查询条件**(简化版): -```vue - - - - - - - - - - - - - -``` - -2. **列表列**(移除personName): -```vue - - -``` - -3. **表单**(使用普通输入框): -```vue - - - - -``` - -4. **权限标识**:全部 `staffFmyRelation` → `custFmyRelation` - -5. **导入localStorage**: -```javascript -const STORAGE_KEY = 'cust_fmy_relation_import_last_task'; -``` - -6. **字典类型**: -```vue - - -``` - -**Step 2: Commit** - -```bash -git add ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue -git commit -m "feat: 添加信贷客户家庭关系页面组件" -``` - ---- - -## 系统配置 - -### Task 12: 创建菜单权限SQL - -**Files:** -- Create: `sql/ccdi_cust_fmy_relation_menu.sql` -- Reference: `sql/ccdi_staff_fmy_relation_menu.sql` - -**Step 1: 创建菜单SQL** - -```sql --- 信贷客户家庭关系菜单 -INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) -VALUES -('信贷客户家庭关系', (SELECT menu_id FROM sys_menu WHERE menu_name = '信息维护' LIMIT 1), 5, 'custFmyRelation', 'ccdiCustFmyRelation/index', 1, 0, 'C', '0', '0', 'ccdi:custFmyRelation:list', 'peoples', 'admin', NOW(), '', NULL, '信贷客户家庭关系菜单'); - --- 获取刚插入的菜单ID -SET @parent_id = LAST_INSERT_ID(); - --- 添加按钮权限 -INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES -('信贷客户家庭关系查询', @parent_id, 1, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:query', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系新增', @parent_id, 2, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:add', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系修改', @parent_id, 3, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:edit', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系删除', @parent_id, 4, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:remove', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系导出', @parent_id, 5, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:export', '#', 'admin', NOW(), '', NULL, ''), -('信贷客户家庭关系导入', @parent_id, 6, '#', '', 1, 0, 'F', '0', '0', 'ccdi:custFmyRelation:import', '#', 'admin', NOW(), '', NULL, ''); -``` - -**Step 2: Commit** - -```bash -git add sql/ccdi_cust_fmy_relation_menu.sql -git commit -m "feat: 添加信贷客户家庭关系菜单权限" -``` - ---- - -### Task 13: 配置字典数据 - -**Files:** -- Modify: 通过系统管理界面配置 - -**Step 1: 确认字典存在** - -登录系统 → 系统管理 → 字典管理,确认以下字典类型已存在: -- `ccdi_relation_type`:关系类型 -- `ccdi_indiv_gender`:性别 -- `ccdi_certificate_type`:证件类型 - -如不存在,参考员工亲属关系的字典数据添加。 - ---- - -## 测试验证 - -### Task 14: 后端接口测试 - -**Files:** -- Create: `doc/reviews/cust-fmy-relation-api-test.md` - -**Step 1: 启动后端服务** - -Run: `mvn spring-boot:run -pl ruoyi-admin` -Expected: 服务启动成功,访问 http://localhost:8080/swagger-ui/index.html - -**Step 2: 测试登录获取token** - -Run: -```bash -curl -X POST "http://localhost:8080/login" \ - -H "Content-Type: application/json" \ - -d '{"username":"admin","password":"admin123"}' -``` -Expected: 返回token - -**Step 3: 测试查询列表接口** - -Run: -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/list?pageNum=1&pageSize=10" \ - -H "Authorization: Bearer " -``` -Expected: 返回空列表(无数据) - -**Step 4: 测试新增接口** - -Run: -```bash -curl -X POST "http://localhost:8080/ccdi/custFmyRelation" \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "personId": "110101199001011234", - "relationType": "配偶", - "relationName": "张三", - "gender": "M", - "relationCertType": "身份证", - "relationCertNo": "110101199001015678" - }' -``` -Expected: 返回成功 - -**Step 5: 测试查询详情接口** - -Run: -```bash -curl -X GET "http://localhost:8080/ccdi/custFmyRelation/1" \ - -H "Authorization: Bearer " -``` -Expected: 返回刚插入的记录 - ---- - -### Task 15: 前端功能测试 - -**Step 1: 启动前端服务** - -Run: `cd ruoyi-ui && npm run dev` -Expected: 服务启动成功,访问 http://localhost - -**Step 2: 登录系统** - -用户名: admin -密码: admin123 - -**Step 3: 导航到信贷客户家庭关系页面** - -路径: 信息维护 → 信贷客户家庭关系 - -**Step 4: 测试新增功能** - -1. 点击"新增"按钮 -2. 填写表单: - - 信贷客户身份证号: `110101199001011234` - - 关系类型: `配偶` - - 关系人姓名: `张三` - - 性别: `男` - - 证件类型: `身份证` - - 证件号码: `110101199001015678` -3. 点击"确定" -Expected: 新增成功,列表显示新记录 - -**Step 5: 测试编辑功能** - -1. 点击"编辑"按钮 -2. 修改关系人姓名为 `张三丰` -3. 点击"确定" -Expected: 修改成功,列表显示更新 - -**Step 6: 测试删除功能** - -1. 勾选记录 -2. 点击"删除"按钮 -3. 确认删除 -Expected: 删除成功,列表不再显示该记录 - -**Step 7: 测试导出功能** - -1. 添加几条测试数据 -2. 点击"导出"按钮 -Expected: 下载Excel文件,数据正确 - -**Step 8: 测试导入功能** - -1. 点击"导入"按钮 -2. 下载模板 -3. 填写数据后上传 -4. 等待异步导入完成 -Expected: 导入成功,显示结果通知 - ---- - -### Task 16: API文档生成 - -**Step 1: 访问Swagger文档** - -URL: http://localhost:8080/swagger-ui/index.html -Expected: 看到"信贷客户家庭关系管理"分组,所有接口正常显示 - -**Step 2: 导出API文档** - -使用 Swagger 导出功能,保存到: `doc/api-docs/cust-fmy-relation-api.md` - ---- - -## 完成检查清单 - -- [ ] 数据库表创建成功 -- [ ] 后端所有类编译通过 -- [ ] Controller所有接口在Swagger正常显示 -- [ ] 前端页面正常加载 -- [ ] 增删改查功能正常 -- [ ] 导入导出功能正常 -- [ ] 权限控制生效 -- [ ] 字典数据正确显示 -- [ ] 测试文档完整 - ---- - -## 预期结果 - -完成后,系统将具备以下功能: - -1. **信贷客户家庭关系管理页面** - - 列表展示(分页) - - 简化查询(身份证号、关系类型、关系人姓名) - - 新增/编辑/删除/详情 - -2. **导入导出功能** - - 带字典下拉框的Excel模板 - - 异步导入,实时状态查询 - - 失败记录查看 - -3. **权限控制** - - 完整的CRUD权限 - - 按钮级权限控制 - -4. **数据隔离** - - 独立表 `ccdi_cust_fmy_relation` - - `is_cust_family = 1` diff --git a/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md b/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md deleted file mode 100644 index 269be46..0000000 --- a/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md +++ /dev/null @@ -1,373 +0,0 @@ -# 信贷客户家庭关系导入功能对齐方案 - -## 概述 - -本文档描述了如何将**信贷客户家庭关系**功能的导入实现完全对齐到**员工亲属关系**的成熟模式。 - -**参考模板**: `CcdiStaffEnterpriseRelationImportServiceImpl` -**修改对象**: `CcdiCustFmyRelationImportServiceImpl` - -## 设计目标 - -1. 提升代码质量和可维护性 -2. 优化性能,避免 N+1 查询问题 -3. 改善用户体验,提供详细的导入进度和状态反馈 -4. 统一日志记录和错误处理机制 - -## 架构调整 - -### 1. 引入导入工具类 - -复用 `ImportLogUtils` 进行统一的日志记录: -- 导入开始/结束日志 -- 批量查询日志 -- 进度跟踪日志 -- 验证错误日志 -- 批量操作日志 - -### 2. Redis 状态管理升级 - -**现状**: 简单 String 值存储状态 -``` -"COMPLETED:10:5" -``` - -**优化**: Hash 结构存储详细状态 -```java -{ - "taskId": "uuid", - "status": "SUCCESS" | "PARTIAL_SUCCESS" | "PROCESSING", - "totalCount": 100, - "successCount": 95, - "failureCount": 5, - "progress": 100, - "startTime": 1234567890, - "endTime": 1234567900, - "message": "成功95条,失败5条" -} -``` - -- 过期时间: 7 天 -- 失败记录: 单独 Key, JSON 序列化, 7 天过期 - -### 3. 批量查询优化 - -**实现 `batchExistsByCombinations` 方法**: -- 提取所有 `person_id + relation_type + relation_cert_no` 组合 -- 一次性批量查询已存在的组合 -- 避免循环查询导致的 N+1 问题 - -### 4. 导入结果封装 - -创建/复用统一的 VO: -- `ImportStatusVO`: 导入状态详情 -- `ImportResultVO`: 导入提交结果 -- `CustFmyRelationImportFailureVO`: 失败记录详情 - -## 数据验证逻辑 - -### 唯一性检查 - -**优化前**: 每条记录单独查询 -```java -for (excel : excels) { - CcdiCustFmyRelation existing = mapper.selectExistingRelations(...); - // N 次数据库查询 -} -``` - -**优化后**: 批量查询 -```java -Set existingCombinations = getExistingCombinations(excels); -// 1 次数据库查询 - -for (excel : excels) { - String combination = excel.getPersonId() + "|" + ...; - if (existingCombinations.contains(combination)) { - throw new RuntimeException("该关系已存在"); - } -} -``` - -### Excel 内部重复检查 - -```java -Set processedCombinations = new HashSet<>(); - -for (excel : excels) { - String combination = ...; - - if (processedCombinations.contains(combination)) { - throw new RuntimeException("该关系在导入文件中重复"); - } - - processedCombinations.add(combination); -} -``` - -### 验证规则 - -**必填字段**: -- 信贷客户身份证号 -- 关系类型 -- 关系人姓名 -- 关系人证件类型 -- 关系人证件号码 - -**格式验证**: -- 身份证号: 18位有效格式 -- 证件号码: 根据证件类型验证 - -**长度限制**: -- 关系人姓名: ≤ 50 -- 关系类型: ≤ 20 -- 证件号码: ≤ 50 - -## 批量操作优化 - -### 分批插入策略 - -```java -private void saveBatch(List list, int batchSize) { - for (int i = 0; i < list.size(); i += batchSize) { - int end = Math.min(i + batchSize, list.size()); - List subList = list.subList(i, end); - mapper.insertBatch(subList); - } -} - -// 调用: 每 500 条为一批 -saveBatch(newRecords, 500); -``` - -### 批量操作日志 - -``` -开始批量插入: 总批次数=5, 每批大小=500 -批量插入完成: 成功=2500, 耗时=1234ms -``` - -## 失败记录处理 - -### 失败记录数据结构 - -```java -public class CustFmyRelationImportFailureVO { - private Integer rowNum; // Excel 行号 - private String personId; // 信贷客户身份证号 - private String relationType; // 关系类型 - private String relationName; // 关系人姓名 - private String errorMessage; // 错误消息 -} -``` - -### Redis 存储优化 - -**Key**: `import:custFmyRelation:{taskId}:failures` -**序列化**: JSON -**过期时间**: 7 天 -**反序列化**: -```java -return JSON.parseArray( - JSON.toJSONString(failuresObj), - CustFmyRelationImportFailureVO.class -); -``` - -## Controller 层调整 - -### 导入接口 - -```java -@PostMapping("/importData") -public AjaxResult importData(@RequestParam("file") MultipartFile file) { - List excels = - EasyExcelUtil.importExcel(file.getInputStream(), ...); - - if (excels == null || excels.isEmpty()) { - return error("至少需要一条数据"); - } - - String taskId = relationService.importRelations(excels); - - ImportResultVO result = new ImportResultVO(); - result.setTaskId(taskId); - result.setStatus("PROCESSING"); - result.setMessage("导入任务已提交,正在后台处理"); - - return AjaxResult.success("导入任务已提交,正在后台处理", result); -} -``` - -### 导入状态查询 - -```java -@GetMapping("/importStatus/{taskId}") -public AjaxResult getImportStatus(@PathVariable String taskId) { - ImportStatusVO statusVO = relationImportService.getImportStatus(taskId); - return success(statusVO); -} -``` - -### 失败记录查询 - -```java -@GetMapping("/importFailures/{taskId}") -public TableDataInfo getImportFailures( - @PathVariable String taskId, - @RequestParam(defaultValue = "1") Integer pageNum, - @RequestParam(defaultValue = "10") Integer pageSize -) { - List failures = - relationImportService.getImportFailures(taskId); - - // 手动分页 - int fromIndex = (pageNum - 1) * pageSize; - int toIndex = Math.min(fromIndex + pageSize, failures.size()); - - if (fromIndex >= failures.size()) { - return getDataTable(new ArrayList<>(), failures.size()); - } - - List pageData = - failures.subList(fromIndex, toIndex); - - return getDataTable(pageData, failures.size()); -} -``` - -## 导入模板改进 - -### 使用字典下拉框 - -```java -@PostMapping("/importTemplate") -public void importTemplate(HttpServletResponse response) { - EasyExcelUtil.importTemplateWithDictDropdown( - response, - CcdiCustFmyRelationExcel.class, - "信贷客户家庭关系" - ); -} -``` - -### Excel 实体注解增强 - -```java -@DictDropdown(type = "ccdi_relation_type") -private String relationType; - -@DictDropdown(type = "ccdi_cert_type") -private String relationCertType; -``` - -## 修改文件清单 - -### 1. Service 层 -- `CcdiCustFmyRelationImportServiceImpl.java` - 核心导入逻辑重构 -- `CcdiCustFmyRelationServiceImpl.java` - 导入入口方法调整 - -### 2. Controller 层 -- `CcdiCustFmyRelationController.java` - 接口返回值优化 - -### 3. Mapper 层 -- `CcdiCustFmyRelationMapper.java` - 添加批量查询方法 -- Mapper XML - 实现批量查询 SQL - -### 4. VO 类 -- 检查/创建 `ImportStatusVO.java` -- 检查/创建 `ImportResultVO.java` -- 优化 `CustFmyRelationImportFailureVO.java` - -### 5. Excel 实体 -- `CcdiCustFmyRelationExcel.java` - 添加字典注解 - -### 6. 工具类 -- 复用 `ImportLogUtils.java` -- 复用 `EasyExcelUtil.java` - -## 关键代码片段 - -### Mapper 批量查询 - -```java -// Mapper 接口 -List batchExistsByCombinations( - @Param("combinations") List combinations -); - -// XML 实现 - -``` - -### 异步导入方法 - -```java -@Async -@Transactional(rollbackFor = Exception.class) -public void importRelationsAsync( - List excels, - String taskId, - String userName // 新增参数,用于审计 -) { - // 实现逻辑... -} -``` - -## 实施步骤 - -1. **添加 Mapper 批量查询方法** - - 在 Mapper 接口添加 `batchExistsByCombinations` - - 在 XML 实现 SQL - -2. **重构 ImportServiceImpl** - - 引入 `ImportLogUtils` - - 实现批量查询逻辑 - - 添加 Excel 内部重复检查 - - 优化 Redis 状态管理 - - 改进失败记录存储 - -3. **创建/优化 VO 类** - - 检查并复用已有的 `ImportStatusVO` - - 检查并复用已有的 `ImportResultVO` - - 优化失败记录 VO - -4. **调整 Controller** - - 修改导入接口返回值 - - 优化状态查询接口 - - 优化失败记录查询接口 - -5. **更新 Excel 实体** - - 添加 `@DictDropdown` 注解 - -6. **测试验证** - - 单元测试 - - 集成测试 - - 性能对比测试 - -## 预期效果 - -### 性能提升 -- 批量查询: 从 N 次减少到 1 次 -- 导入 1000 条数据预计提升 50-70% - -### 用户体验 -- 实时进度反馈 -- 详细的错误信息 -- 清晰的成功/失败统计 - -### 代码质量 -- 统一的日志记录 -- 完善的错误处理 -- 更好的可维护性 - -## 创建日期 - -2026-02-11