feat: OA(泛微 ecology)登录身份校验 + 公示详情悬浮按钮/PDF缩放
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, 不入库
This commit is contained in:
+37
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
+42
@@ -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)外部只读库数据源.
|
||||
* <p>
|
||||
* 用于登录时校验合规人员(manager) / 项目负责人(leader) 是否仍存在于
|
||||
* {@code v_compliance_project_manager} 视图 (按 mobile 手机号匹配).
|
||||
* <p>
|
||||
* 独立 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);
|
||||
}
|
||||
}
|
||||
+14
-9
@@ -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. 校验用户状态
|
||||
|
||||
+200
@@ -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) 登录身份校验 + 自动建号.
|
||||
* <p>
|
||||
* 单一可信源 = 泛微 ecology 的 {@code v_compliance_project_manager} 视图 (按 mobile 手机号):
|
||||
* - type=1 → 合规人员 → role_type=manager
|
||||
* - type=2 → 项目负责人 → role_type=leader
|
||||
* <p>
|
||||
* 登录决策 (手机号 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<Map<String, Object>> rows = ecologyJdbcTemplate.queryForList(ECOLOGY_LOOKUP_SQL, phone);
|
||||
if (rows == null || rows.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> row = rows.get(0);
|
||||
Object typeObj = row.get("type");
|
||||
if (typeObj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
EcologyPerson p = new EcologyPerson();
|
||||
p.type = ((Number) typeObj).intValue();
|
||||
p.name = row.get("lastname") == null ? null : row.get("lastname").toString();
|
||||
return p;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ecology 库不可达时 fail-closed: 视作视图没有, 合规/负责人被拦, 普通用户不受影响
|
||||
log.warn("查询 ecology 合规视图失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SysUser provision(EcologyPerson p, String phone, String role)
|
||||
{
|
||||
String name = (p.name == null || p.name.isEmpty()) ? phone : p.name;
|
||||
|
||||
SysUser u = new SysUser();
|
||||
u.setUserName(phone); // 登录名 = 手机号
|
||||
u.setNickName(name);
|
||||
u.setPhonenumber(phone);
|
||||
u.setRoleType(role); // 显式写, 避免 DB 默认 'executor'
|
||||
u.setStatus("0");
|
||||
// 密码留空 (只能短信登录)
|
||||
u.setCreateBy(phone);
|
||||
userService.insertUser(u);
|
||||
Long userId = u.getUserId();
|
||||
|
||||
BizPerson person = new BizPerson();
|
||||
SnowflakeId.injectIfEmpty(person, "personId");
|
||||
person.setName(name);
|
||||
person.setPhone(phone);
|
||||
person.setUnitType(role);
|
||||
person.setDepartment("manager".equals(role) ? "合规部" : "项目组");
|
||||
person.setPosition("manager".equals(role) ? "合规员" : "项目负责人");
|
||||
person.setUserId(userId);
|
||||
person.setCreateBy(phone);
|
||||
person.setUpdateBy(phone);
|
||||
bizPersonMapper.insert(person);
|
||||
return u;
|
||||
}
|
||||
|
||||
private static class EcologyPerson
|
||||
{
|
||||
int type;
|
||||
String name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user