From beb9799a065ed9a278183c5e3ecb3306d92be953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=BA=86=E6=B3=B0?= <12369755+htcloud1@user.noreply.gitee.com> Date: Tue, 1 Sep 2026 20:45:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20OA(=E6=B3=9B=E5=BE=AE=20ecology)?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E8=BA=AB=E4=BB=BD=E6=A0=A1=E9=AA=8C=20+=20?= =?UTF-8?q?=E5=85=AC=E7=A4=BA=E8=AF=A6=E6=83=85=E6=82=AC=E6=B5=AE=E6=8C=89?= =?UTF-8?q?=E9=92=AE/PDF=E7=BC=A9=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OA 登录校验 (manager/leader 单一可信源 = ecology 合规视图 v_compliance_project_manager): - ComplianceLoginService: 手机号登录查视图(type=1→manager/type=2→leader), 自动建号 sys_user+biz_person, 角色不匹配软删重建; 密码登录门禁 gateForPasswordLogin - EcologyDataSourceConfig: 独立只读 JdbcTemplate(ecologyJdbcTemplate) 接泛微库, 不接入主库动态数据源 - SysLoginController: 密码登录前 complianceGate 校验 manager/leader 仍存于视图 - BizAuthController: smsLogin 改走 reconcileForLogin; 发码放行 ecology 视图内未注册手机号 - application*.yml: 新增 spring.datasource.ecology 只读连接(prod/test) - Login.vue: 登录标题居中微调 + 供应商注册跳生产域名 公示详情 (publicity/:id): - 右侧悬浮操作按钮从垂直居中改为主区右上(紧贴版心右缘), 中窄屏兜底贴右缘 - PDF 预览 #zoom 由 page-width 改为数值缩放(121), 放大正文占满宽度 文档: - shouce/ 手册 md 入仓 注: oalogin/ (含 OA 管理员密码的临时笔记) 已加 .gitignore, 不入库 --- .gitignore | 3 + .../controller/system/SysLoginController.java | 37 + .../src/main/resources/application-prod.yml | 18 +- .../src/main/resources/application-test.yml | 6 + .../src/main/resources/application.yml | 2 +- .../config/EcologyDataSourceConfig.java | 42 + .../controller/BizAuthController.java | 23 +- .../service/ComplianceLoginService.java | 200 ++++ ry-vue3/.env.test | 4 +- ry-vue3/src/assets/login.jpg | Bin 489615 -> 120541 bytes ry-vue3/src/views/auth/Login.vue | 9 +- ry-vue3/src/views/portal/PublicityDetail.vue | 22 +- shouce/admin.md | 209 ++++ shouce/doctor.md | 105 ++ shouce/executor.md | 190 ++++ shouce/flow.md | 96 ++ shouce/leader.md | 63 ++ shouce/manager.md | 300 ++++++ shouce/manual.md | 954 ++++++++++++++++++ shouce/sponsor.md | 172 ++++ 20 files changed, 2425 insertions(+), 30 deletions(-) create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/config/EcologyDataSourceConfig.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/ComplianceLoginService.java create mode 100644 shouce/admin.md create mode 100644 shouce/doctor.md create mode 100644 shouce/executor.md create mode 100644 shouce/flow.md create mode 100644 shouce/leader.md create mode 100644 shouce/manager.md create mode 100644 shouce/manual.md create mode 100644 shouce/sponsor.md diff --git a/.gitignore b/.gitignore index efeb494..2f4543c 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,6 @@ ry-vue2/ # 旧 vue 控制台 # ===== OCR 模型 (大二进制, 不进版本库, 部署时放 jar 同目录 models/) ===== ry-api/models/ ry-ocr-java/src/main/resources/models/ + +# ===== OA 单点登录临时笔记 (含 OA 管理员密码, 不入库) ===== +oalogin/ diff --git a/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 43bb07a..abbe8db 100644 --- a/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -25,6 +25,7 @@ import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysUserService; import com.ruoyi.business.domain.BizExpert; +import com.ruoyi.business.service.ComplianceLoginService; import com.ruoyi.business.service.IBizExpertService; /** @@ -56,6 +57,9 @@ public class SysLoginController @Autowired private IBizExpertService expertService; + @Autowired + private ComplianceLoginService complianceLoginService; + /** * 登录方法 * @@ -70,6 +74,12 @@ public class SysLoginController { return AjaxResult.error("您的信息正在审核中"); } + // 合规人员/项目负责人门禁: 校验其仍存在于 ecology 视图, 角色变更则软删重建 + String complianceGate = complianceGate(loginBody.getUsername()); + if (complianceGate != null) + { + return AjaxResult.error(complianceGate); + } AjaxResult ajax = AjaxResult.success(); // 生成令牌 String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), @@ -101,6 +111,33 @@ public class SysLoginController return expert != null && "1".equals(expert.getAuditStatus()); } + /** + * 密码登录门禁: 用户名命中 manager/leader 时校验其仍存在于 ecology 视图. + * 返回 null=放行, 非 null=错误提示 (直接报错, 不走密码登录). + */ + private String complianceGate(String username) + { + if (username == null || username.isEmpty()) + { + return null; + } + SysUser probe = userService.selectUserByUserName(username); + if (probe == null && username.contains("@")) + { + probe = userService.selectUserByEmail(username); + } + if (probe == null || probe.getUserId() == null) + { + return null; + } + String role = probe.getRoleType(); + if (!"manager".equals(role) && !"leader".equals(role)) + { + return null; + } + return complianceLoginService.gateForPasswordLogin(probe); + } + /** * 获取用户信息 * diff --git a/ry-api/ruoyi-admin/src/main/resources/application-prod.yml b/ry-api/ruoyi-admin/src/main/resources/application-prod.yml index 125515b..6c3343a 100644 --- a/ry-api/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ry-api/ruoyi-admin/src/main/resources/application-prod.yml @@ -10,6 +10,12 @@ spring: url: jdbc:mysql://47.95.12.80:3306/hegui?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: hegui password: C55zwt5L2TeB4kLB + # 合规(泛微 ecology)外部只读库, 登录校验合规人员/项目负责人用 + ecology: + url: jdbc:mysql://oagm228.guojustar.com:3306/ecology?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 + username: user_compliance + password: 'ComplexPass@2026' + driver-class-name: com.mysql.cj.jdbc.Driver # 生产环境外部接口地址 ruoyi: @@ -20,15 +26,15 @@ ruoyi: base-url: https://hegui.bahim.org.cn/camera/ supplier-account-api: -# base-url: https://zbsupplier.guojustar.com/supplier-api/bidding/supplier/openapi/accounts - base-url: https://zbsuppliertest.guojustar.com/supplier-api/bidding/supplier/openapi/accounts + base-url: https://zbsupplier.guojustar.com/supplier-api/bidding/supplier/openapi/accounts +# base-url: https://zbsuppliertest.guojustar.com/supplier-api/bidding/supplier/openapi/accounts supplier-account-api-aes: key-id: supplier-api-key key: 'kY0+oIO/laeaaaYDd+9TCYbCQR0b/vqMeJ9sUnSVd9U=' bidding-api: -# notice-list-url: https://zbpublic.guojustar.com/portal-api/bidding/public/notice/list -# detail-url: https://zbpublic.guojustar.com/#/detail/ - notice-list-url: https://zbpublictest.guojustar.com/portal-api/bidding/public/notice/list - detail-url: https://zbpublictest.guojustar.com/#/detail/ + notice-list-url: https://zbpublic.guojustar.com/portal-api/bidding/public/notice/list + detail-url: https://zbpublic.guojustar.com/#/detail/ +# notice-list-url: https://zbpublictest.guojustar.com/portal-api/bidding/public/notice/list +# detail-url: https://zbpublictest.guojustar.com/#/detail/ diff --git a/ry-api/ruoyi-admin/src/main/resources/application-test.yml b/ry-api/ruoyi-admin/src/main/resources/application-test.yml index 10211c1..9e936a2 100644 --- a/ry-api/ruoyi-admin/src/main/resources/application-test.yml +++ b/ry-api/ruoyi-admin/src/main/resources/application-test.yml @@ -6,6 +6,12 @@ spring: url: jdbc:mysql://mmos.uvwcloud.com:3306/guoju0808?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: Qljl1rh_ + # 合规(泛微 ecology)外部只读库, 登录校验合规人员/项目负责人用 + ecology: + url: jdbc:mysql://101.254.99.156:11120/ecology?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 + username: user_compliance + password: 'ComplexPass@2026' + driver-class-name: com.mysql.cj.jdbc.Driver # 测试环境外部接口地址 ruoyi: diff --git a/ry-api/ruoyi-admin/src/main/resources/application.yml b/ry-api/ruoyi-admin/src/main/resources/application.yml index 60135b7..257c176 100644 --- a/ry-api/ruoyi-admin/src/main/resources/application.yml +++ b/ry-api/ruoyi-admin/src/main/resources/application.yml @@ -97,7 +97,7 @@ spring: basename: i18n/messages profiles: # 默认 prod 环境; 本地测试用 --spring.profiles.active=test (group 自动展开为 druid,test) - active: prod + active: test group: test: druid,test prod: druid,prod diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/config/EcologyDataSourceConfig.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/config/EcologyDataSourceConfig.java new file mode 100644 index 0000000..0ee1ffb --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/config/EcologyDataSourceConfig.java @@ -0,0 +1,42 @@ +package com.ruoyi.business.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * 合规(泛微 ecology)外部只读库数据源. + *
+ * 用于登录时校验合规人员(manager) / 项目负责人(leader) 是否仍存在于 + * {@code v_compliance_project_manager} 视图 (按 mobile 手机号匹配). + *
+ * 独立 JdbcTemplate (bean 名 ecologyJdbcTemplate), 不接入主库事务 / MyBatis, + * 避免与 DruidConfig 的主从动态数据源 (dynamicDataSource @Primary) 产生二义性. + */ +@Configuration +public class EcologyDataSourceConfig +{ + @Bean(name = "ecologyJdbcTemplate") + public JdbcTemplate ecologyJdbcTemplate(Environment env) + { + String url = env.getProperty("spring.datasource.ecology.url"); + String username = env.getProperty("spring.datasource.ecology.username"); + String password = env.getProperty("spring.datasource.ecology.password"); + String driver = env.getProperty("spring.datasource.ecology.driver-class-name", "com.mysql.cj.jdbc.Driver"); + + DruidDataSource ds = new DruidDataSource(); + ds.setUrl(url); + ds.setUsername(username); + ds.setPassword(password); + ds.setDriverClassName(driver); + // 只读小连接池, 登录校验用 + ds.setInitialSize(1); + ds.setMinIdle(1); + ds.setMaxActive(3); + ds.setMaxWait(3000); + ds.setValidationQuery("SELECT 1"); + return new JdbcTemplate(ds); + } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java index 0d74d1c..9cb6489 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java @@ -19,7 +19,9 @@ import com.ruoyi.business.dto.SmsValidForm; import com.ruoyi.business.mapper.BizPersonMapper; import com.ruoyi.business.service.IBizExpertService; import com.ruoyi.business.service.IBizOrgService; +import com.ruoyi.business.service.ComplianceLoginService; import com.ruoyi.business.service.SysSmsService; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.id.SnowflakeId; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.controller.BaseController; @@ -69,6 +71,9 @@ public class BizAuthController extends BaseController { @Autowired private IBizExpertService expertService; + @Autowired + private ComplianceLoginService complianceLoginService; + @Autowired private BizPersonMapper bizPersonMapper; @@ -101,8 +106,8 @@ public class BizAuthController extends BaseController { if (phone == null || !phone.matches("^1\\d{10}$")) { return error("请输入正确的手机号"); } - // 登录反义: 手机号未注册则不发 - if (!userService.isPhoneRegistered(phone)) { + // 登录反义: 手机号未注册则不发; 但合规人员/项目负责人可从 ecology 视图自动建号, 放行发码 + if (!userService.isPhoneRegistered(phone) && !complianceLoginService.existsInEcology(phone)) { return error("该手机号未注册, 请先注册账号"); } String uuid = smsService.sendCode(phone); @@ -138,15 +143,15 @@ public class BizAuthController extends BaseController { return error("验证码错误或已过期: " + e.getMessage()); } - // 3. 查用户 (checkPhoneUnique 只 select user_id+phonenumber, 缺 role_type/user_name/status, - // 需再按 userId 查完整 user, 否则 LoginUser 里 roleType=null 导致前端拿不到角色而跳回登录页) - SysUser probe = userMapper.checkPhoneUnique(phone); - if (probe == null || probe.getUserId() == null) { - return error("该手机号未注册"); + // 3. 查用户 + 合规人员/项目负责人身份校验 (可能从 ecology 视图自动建号) + SysUser user; + try { + user = complianceLoginService.reconcileForLogin(phone); + } catch (ServiceException e) { + return error(e.getMessage()); } - SysUser user = userMapper.selectUserById(probe.getUserId()); if (user == null) { - return error("该手机号未注册"); + return error("用户不存在"); } // 4. 校验用户状态 diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/ComplianceLoginService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/ComplianceLoginService.java new file mode 100644 index 0000000..6eba4e2 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/ComplianceLoginService.java @@ -0,0 +1,200 @@ +package com.ruoyi.business.service; + +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.business.domain.BizPerson; +import com.ruoyi.business.mapper.BizPersonMapper; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.id.SnowflakeId; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.service.ISysUserService; + +/** + * 合规人员(manager) / 项目负责人(leader) 登录身份校验 + 自动建号. + *
+ * 单一可信源 = 泛微 ecology 的 {@code v_compliance_project_manager} 视图 (按 mobile 手机号): + * - type=1 → 合规人员 → role_type=manager + * - type=2 → 项目负责人 → role_type=leader + *
+ * 登录决策 (手机号 vs 视图 vs sys_user): + * - 已注册且非 manager/leader → 不查视图, 直接放行 (走原逻辑) + * - 视图里没有 → 报"用户不存在" + * - 视图里有 + sys_user 没有 → 入库(sys_user + biz_person, 登录名=手机号, 密码留空) 后放行 + * - 视图里有 + sys_user 有 + 角色匹配 → 放行 + * - 视图里有 + sys_user 有 + 角色不匹配 → 软删旧 + 建新 后放行 + */ +@Service +public class ComplianceLoginService +{ + private static final Logger log = LoggerFactory.getLogger(ComplianceLoginService.class); + + private static final String ECOLOGY_LOOKUP_SQL = + "SELECT lastname, type FROM v_compliance_project_manager WHERE mobile = ? LIMIT 1"; + + @Autowired + @Qualifier("ecologyJdbcTemplate") + private JdbcTemplate ecologyJdbcTemplate; + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private ISysUserService userService; + + @Autowired + private BizPersonMapper bizPersonMapper; + + /** + * 短信登录主路径: 查用户 + 合规/负责人校验, 返回最终要登录的 SysUser. + * 无资格时抛 {@link ServiceException}("用户不存在"). + */ + @Transactional(rollbackFor = Exception.class) + public SysUser reconcileForLogin(String phone) + { + SysUser user = findUserByPhone(phone); + String role = user == null ? null : user.getRoleType(); + boolean needEcology = user == null || "manager".equals(role) || "leader".equals(role); + if (!needEcology) + { + return user; + } + + EcologyPerson p = queryEcology(phone); + if (p == null) + { + throw new ServiceException("用户不存在"); + } + String expectedRole = p.type == 1 ? "manager" : "leader"; + + if (user == null) + { + // sys_user 没有 → 自动入库 + return provision(p, phone, expectedRole); + } + if (expectedRole.equals(role)) + { + // 角色匹配 → 直接放行 + return user; + } + // 角色不匹配 → 软删旧 + 建新 + userService.deleteUserByIds(new Long[] { user.getUserId() }); + return provision(p, phone, expectedRole); + } + + /** + * 密码登录门禁: 已解析出的 manager/leader 校验其是否仍在视图、角色是否一致. + * 返回 null=放行; 非 null=错误提示 (此时调用方应直接报错, 不再走密码登录). + */ + @Transactional(rollbackFor = Exception.class) + public String gateForPasswordLogin(SysUser user) + { + String phone = user.getPhonenumber(); + EcologyPerson p = queryEcology(phone == null ? "" : phone); + if (p == null) + { + return "用户不存在"; + } + String expectedRole = p.type == 1 ? "manager" : "leader"; + if (expectedRole.equals(user.getRoleType())) + { + return null; + } + // 角色不匹配 → 软删旧 + 建新 (新账号密码留空, 只能短信登录) + userService.deleteUserByIds(new Long[] { user.getUserId() }); + provision(p, phone, expectedRole); + return "账号角色已更新, 请使用手机验证码登录"; + } + + /** + * 手机号是否存在于 ecology 视图 (短信发码前放行合规/负责人未注册手机号用). + */ + public boolean existsInEcology(String phone) + { + return queryEcology(phone) != null; + } + + private SysUser findUserByPhone(String phone) + { + SysUser probe = userMapper.checkPhoneUnique(phone); + if (probe == null || probe.getUserId() == null) + { + return null; + } + return userMapper.selectUserById(probe.getUserId()); + } + + private EcologyPerson queryEcology(String phone) + { + if (phone == null || phone.isEmpty()) + { + return null; + } + try + { + List