This commit is contained in:
2025-12-11 18:30:35 +08:00
parent 99937e9feb
commit 8b211fbad6
35 changed files with 527 additions and 302 deletions

View File

@@ -13,11 +13,13 @@ CREATE TABLE sys.tb_sys_user (
phone VARCHAR(500), -- 电话号码
phone_hash VARCHAR(200), -- 电话hash
wechat_id VARCHAR(50), -- 微信ID
creator VARCHAR(50) DEFAULT NULL, -- 创建者
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间(使用带时区时间)
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除(使用 BOOLEAN
status INTEGER NOT NULL DEFAULT 1, -- 状态
remark VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (user_id),
UNIQUE (optsn),
UNIQUE (email),
@@ -59,6 +61,7 @@ CREATE TABLE sys.tb_sys_user_info (
level INTEGER DEFAULT 1, -- 等级
id_card VARCHAR(50), -- 身份证号
address VARCHAR(255), -- 地址
creator VARCHAR(50) DEFAULT NULL, -- 创建者
remark VARCHAR(500) DEFAULT NULL, -- 备注
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(触发器维护)

View File

@@ -1,6 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "AesEncryptUtil",
"request": "launch",
"mainClass": "org.xyzh.common.utils.crypto.AesEncryptUtil",
"projectName": "common-utils"
},
{
"type": "java",
"name": "Gateway (8180)",
@@ -8,8 +15,12 @@
"mainClass": "org.xyzh.gateway.GatewayApplication",
"projectName": "gateway",
"cwd": "${workspaceFolder}/gateway",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8180"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8180"
],
"console": "integratedTerminal"
},
{
@@ -19,8 +30,12 @@
"mainClass": "org.xyzh.auth.AuthApp",
"projectName": "auth",
"cwd": "${workspaceFolder}/auth",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8181"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8181"
],
"console": "integratedTerminal"
},
{
@@ -30,8 +45,12 @@
"mainClass": "org.xyzh.system.SystemApp",
"projectName": "system",
"cwd": "${workspaceFolder}/system",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8182"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8182"
],
"console": "integratedTerminal"
},
{
@@ -41,8 +60,12 @@
"mainClass": "org.xyzh.log.LogApp",
"projectName": "log",
"cwd": "${workspaceFolder}/log",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8183"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8183"
],
"console": "integratedTerminal"
},
{
@@ -52,8 +75,12 @@
"mainClass": "org.xyzh.file.FileApp",
"projectName": "file",
"cwd": "${workspaceFolder}/file",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8184"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8184"
],
"console": "integratedTerminal"
},
{
@@ -63,8 +90,12 @@
"mainClass": "org.xyzh.message.MessageApp",
"projectName": "message",
"cwd": "${workspaceFolder}/message",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8185"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8185"
],
"console": "integratedTerminal"
},
{
@@ -74,8 +105,12 @@
"mainClass": "org.xyzh.bidding.BiddingApp",
"projectName": "bidding",
"cwd": "${workspaceFolder}/bidding",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8186"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8186"
],
"console": "integratedTerminal"
},
{
@@ -85,8 +120,12 @@
"mainClass": "org.xyzh.platform.PlatformApp",
"projectName": "platform",
"cwd": "${workspaceFolder}/platform",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8187"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8187"
],
"console": "integratedTerminal"
},
{
@@ -96,8 +135,12 @@
"mainClass": "org.xyzh.workcase.WorkcaseApp",
"projectName": "workcase",
"cwd": "${workspaceFolder}/workcase",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8188"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8188"
],
"console": "integratedTerminal"
},
{
@@ -107,8 +150,12 @@
"mainClass": "org.xyzh.crontab.CrontabApp",
"projectName": "crontab",
"cwd": "${workspaceFolder}/crontab",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8189"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8189"
],
"console": "integratedTerminal"
},
{
@@ -118,8 +165,12 @@
"mainClass": "org.xyzh.agent.AgentApp",
"projectName": "agent",
"cwd": "${workspaceFolder}/agent",
"args": ["--spring.profiles.active=dev"],
"vmArgs": ["-Dserver.port=8190"],
"args": [
"--spring.profiles.active=dev"
],
"vmArgs": [
"-Dserver.port=8190"
],
"console": "integratedTerminal"
}
],

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8190
servlet:
context-path: /urban-lifeline/agent
# servlet:
# context-path: /urban-lifeline/agent # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -18,5 +18,17 @@
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-dto</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -7,6 +7,7 @@ import org.xyzh.common.vo.BaseVO;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.utils.crypto.AesEncryptUtil;
import java.util.List;
@@ -47,8 +48,8 @@ public class SysUserVO extends BaseVO {
@Schema(description = "用户状态")
private String status;
@Schema(description = "用户类型")
private String userType;
@Schema(description = "创建人")
private String creator;
// TbSysUserInfoDTO对应字段
@Schema(description = "用户名")
@@ -79,6 +80,11 @@ public class SysUserVO extends BaseVO {
@Schema(description = "用户视图权限列表")
private List<PermissionVO> viewPermissions;
public void setPhone(String phone){
this.phone = phone;
this.phoneHash = AesEncryptUtil.maskPhone(phone);
}
public static TbSysUserDTO toDTO(SysUserVO vo) {
if (vo == null) {
return null;
@@ -91,10 +97,8 @@ public class SysUserVO extends BaseVO {
dto.setPhone(vo.getPhone());
dto.setWechatId(vo.getWechatId());
dto.setStatus(vo.getStatus());
dto.setUserType(vo.getUserType());
dto.setOptsn(vo.getOptsn());
dto.setCreator(vo.getCreator());
dto.setUpdater(vo.getUpdater());
dto.setDeptPath(vo.getDeptPath());
dto.setRemark(vo.getRemark());
dto.setCreateTime(vo.getCreateTime());
@@ -120,7 +124,6 @@ public class SysUserVO extends BaseVO {
// 继承自 BaseDTO 的字段
userInfoDTO.setOptsn(userVO.getOptsn());
userInfoDTO.setCreator(userVO.getCreator());
userInfoDTO.setUpdater(userVO.getUpdater());
userInfoDTO.setDeptPath(userVO.getDeptPath());
userInfoDTO.setRemark(userVO.getRemark());
userInfoDTO.setCreateTime(userVO.getCreateTime());

View File

@@ -5,10 +5,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDubbo // 启用 Dubbo 服务
@EnableDiscoveryClient // 启用 Nacos 服务注册与发现(用于 Gateway 路由)
@ComponentScan(basePackages = {
"org.xyzh.auth", // 当前auth模块
"org.xyzh.common" // 公共模块

View File

@@ -24,6 +24,7 @@ import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysViewDTO;
import org.xyzh.common.redis.service.RedisService;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.common.utils.crypto.AesEncryptUtil;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
@@ -67,6 +68,9 @@ public class AuthServiceImpl implements AuthService{
@Autowired
private LoginStrategyFactory loginStrategyFactory;
@Autowired
private AesEncryptUtil aesEncryptUtil;
@Override
public ResultDomain<LoginDomain> getCaptcha(LoginParam loginParam) {
try {
@@ -236,7 +240,8 @@ public class AuthServiceImpl implements AuthService{
// 5. 验证凭据
if ("password".equals(loginType)) {
if (!strategy.verifyCredential(loginParam.getPassword(), user.getPassword())) {
String pwd = aesEncryptUtil.decrypt(loginParam.getPassword());
if (!strategy.verifyCredential(pwd, user.getPassword())) {
logLoginAttempt(loginParam, user, false, loginAttempt, "密码错误");
return ResultDomain.failure("密码错误");
}

View File

@@ -7,6 +7,7 @@ import org.xyzh.auth.strategy.LoginStrategy;
import org.xyzh.common.core.domain.LoginParam;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.utils.NonUtils;
import org.xyzh.common.utils.crypto.AesEncryptUtil;
import org.xyzh.common.utils.validation.method.EmailValidateMethod;
import org.xyzh.common.utils.validation.method.PhoneValidateMethod;
import org.xyzh.api.system.service.SysUserService;
@@ -77,6 +78,7 @@ public class PasswordLoginStrategy implements LoginStrategy {
@Override
public boolean verifyCredential(String inputCredential, String storedCredential) {
// 使用BCrypt的matches方法验证密码内部会自动处理salt
logger.info(passwordEncoder.encode(inputCredential));
return passwordEncoder.matches(inputCredential, storedCredential);
}
}

View File

@@ -5,10 +5,11 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.xyzh.auth.strategy.LoginStrategy;
import org.xyzh.common.core.domain.LoginParam;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.api.system.service.SysUserService;
import org.xyzh.api.system.vo.SysUserVO;
import org.xyzh.common.redis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @description PhoneLoginStrategy.java文件描述 手机号登录策略
@@ -19,6 +20,7 @@ import org.xyzh.common.redis.service.RedisService;
*/
@Component
public class PhoneLoginStrategy implements LoginStrategy {
private static final Logger logger = LoggerFactory.getLogger(PhoneLoginStrategy.class);
@Autowired
private SysUserService userService;
@@ -57,7 +59,6 @@ public class PhoneLoginStrategy implements LoginStrategy {
@Override
public boolean verifyCredential(String inputCredential, String storedCredential) {
// 密码验证
return passwordEncoder.matches(inputCredential, storedCredential);
}

View File

@@ -1,18 +1,20 @@
# ================== Server ==================
server:
port: 8181
servlet:
context-path: /urban-lifeline/auth
# servlet:
# context-path: /urban-lifeline/auth # 微服务架构下context-path由Gateway管理服务本身不需要设置
# ================== Auth ====================
urban-lifeline:
auth:
enabled: false # 认证服务自己不需要认证
whitelist:
- /** # 认证服务的所有接口都放行
auth:
enabled: false # 认证服务自己不需要认证
gateway-mode: false # 不使用gateway模式auth服务作为独立服务
whitelist:
- /** # 认证服务的所有接口都放行
security:
aes:
secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI= # Base64 编码,32字节256位
# AES-256 密钥(Base64编码必须与所有服务保持一致
# 警告:这是开发环境密钥,生产环境请使用密钥管理系统
secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=
# ================== Spring ==================
spring:
application:

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8186
servlet:
context-path: /urban-lifeline/bidding
# servlet:
# context-path: /urban-lifeline/bidding # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -30,6 +30,11 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xyzh.common</groupId>
<artifactId>common-utils</artifactId>
<version>${urban-lifeline.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -3,6 +3,10 @@ package org.xyzh.common.dto.sys;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import org.xyzh.common.utils.crypto.AesEncryptUtil;
import org.xyzh.common.utils.crypto.EncryptedStringTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
/**
@@ -29,6 +33,7 @@ public class TbSysUserDTO extends BaseDTO {
@Schema(description = "邮箱")
private String email;
@TableField(typeHandler = EncryptedStringTypeHandler.class)
@Schema(description = "手机(加密)")
private String phone;
@@ -41,7 +46,10 @@ public class TbSysUserDTO extends BaseDTO {
@Schema(description = "用户状态")
private String status;
@Schema(description = "用户类型")
private String userType;
public void setPhone(String phone){
this.phone = phone;
this.phoneHash = AesEncryptUtil.maskPhone(phone);
}
}

View File

@@ -36,7 +36,10 @@
<artifactId>mybatis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- Spring Boot (用于加密工具) -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -35,13 +35,24 @@ public class AesEncryptUtil {
* 从配置文件读取密钥,如果没有则生成新密钥
*/
public AesEncryptUtil(@Value("${security.aes.secret-key:}") String secretKeyString) {
if (secretKeyString == null || secretKeyString.isEmpty()) {
// 生产环境应该从配置中心或密钥管理系统获取
try {
if (secretKeyString == null || secretKeyString.isEmpty()) {
// 生产环境应该从配置中心或密钥管理系统获取
this.secretKey = generateKey();
System.err.println("警告: 未配置 AES 密钥,使用临时生成的密钥。生产环境请配置 security.aes.secret-key");
} else {
byte[] decodedKey = Base64.getDecoder().decode(secretKeyString);
if (decodedKey.length != 32) {
throw new IllegalArgumentException("AES-256 密钥长度必须是 32 字节,当前: " + decodedKey.length);
}
this.secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, ALGORITHM);
System.out.println("✓ AES-256 加密工具初始化成功");
}
} catch (Exception e) {
System.err.println("❌ AES 密钥初始化失败: " + e.getMessage());
// 生成临时密钥以避免启动失败
this.secretKey = generateKey();
System.err.println("警告: 未配置 AES 密钥,使用临时生成的密钥。生产环境请配置 security.aes.secret-key");
} else {
byte[] decodedKey = Base64.getDecoder().decode(secretKeyString);
this.secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, ALGORITHM);
System.err.println("已生成临时密钥,请尽快配置正确的 security.aes.secret-key");
}
}
@@ -169,9 +180,32 @@ public class AesEncryptUtil {
}
/**
* 脱敏显示手机号
* 生成手机号Hash用于数据库查询
* 使用SHA-256生成固定Hash相同手机号总是得到相同Hash
*/
public static String maskPhone(String phone) {
if (phone == null || phone.isEmpty()) {
return phone;
}
try {
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(phone.getBytes(java.nio.charset.StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("生成手机号Hash失败", e);
}
}
/**
* 脱敏显示手机号(用于前端显示)
*/
public static String maskPhoneDisplay(String phone) {
if (phone == null || phone.length() < 11) {
return phone;
}
@@ -187,4 +221,26 @@ public class AesEncryptUtil {
}
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
public static void main(String[] args) {
AesEncryptUtil aesEncryptUtil = new AesEncryptUtil("MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=");
String phone = "17857100375";
// 测试加密(每次都不同,不能用于查询)
String encryptedPhone1 = aesEncryptUtil.encryptPhone(phone);
String encryptedPhone2 = aesEncryptUtil.encryptPhone(phone);
System.out.println("第一次加密: " + encryptedPhone1);
System.out.println("第二次加密: " + encryptedPhone2);
System.out.println("两次加密相同? " + encryptedPhone1.equals(encryptedPhone2));
// 测试Hash用于查询相同手机号总是得到相同Hash
String hash1 = AesEncryptUtil.maskPhone(phone);
String hash2 = AesEncryptUtil.maskPhone(phone);
System.out.println("\n第一次Hash: " + hash1);
System.out.println("第二次Hash: " + hash2);
System.out.println("两次Hash相同? " + hash1.equals(hash2));
// 测试脱敏显示
System.out.println("\n脱敏显示: " + AesEncryptUtil.maskPhoneDisplay(phone));
}
}

View File

@@ -0,0 +1,37 @@
package org.xyzh.common.utils.crypto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
/**
* TypeHandler 配置类
* 用于初始化 EncryptedStringTypeHandler 中的 AesEncryptUtil
*
* @author yslg
* @since 2025-12-11
*/
@Configuration
@ConditionalOnClass(AesEncryptUtil.class)
public class TypeHandlerConfig {
private final AesEncryptUtil aesEncryptUtil;
@Autowired(required = false)
public TypeHandlerConfig(AesEncryptUtil aesEncryptUtil) {
this.aesEncryptUtil = aesEncryptUtil;
}
@PostConstruct
public void init() {
// 初始化 TypeHandler 中的静态 AesEncryptUtil 实例
if (aesEncryptUtil != null) {
EncryptedStringTypeHandler.setAesEncryptUtil(aesEncryptUtil);
System.out.println("✓ TypeHandler 已初始化 AES 加密工具");
} else {
System.err.println("警告: AesEncryptUtil 未能注入,加密功能可能无法正常工作");
}
}
}

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8189
servlet:
context-path: /urban-lifeline/crontab
# servlet:
# context-path: /urban-lifeline/crontab # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8184
servlet:
context-path: /urban-lifeline/file
# servlet:
# context-path: /urban-lifeline/file # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -28,20 +28,9 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<!-- 排除旧的 gateway-server使用新的 webflux 版本 -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 明确使用新的 WebFlux Gateway Server推荐 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server-webflux</artifactId>
</dependency>
<!-- Nacos 服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>

View File

@@ -7,6 +7,8 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.xyzh.common.auth.config.SecurityConfig;
import org.xyzh.common.auth.config.WebMvcConfig;
import org.xyzh.common.auth.config.GatewayAuthConfig;
/**
* @description Gateway 网关启动类
@@ -23,8 +25,12 @@ import org.xyzh.common.auth.config.SecurityConfig;
"org.xyzh.common" // 公共模块(包括 common-auth
},
excludeFilters = {
// 排除 Spring MVC 的 SecurityConfigGateway 使用 WebFlux Security
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
// 排除 Spring MVC 相关配置Gateway 使用 WebFlux
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
SecurityConfig.class, // Spring MVC Security配置
WebMvcConfig.class, // Spring MVC配置
GatewayAuthConfig.class // 微服务Gateway模式配置使用Servlet Filter
})
}
)
public class GatewayApplication {

View File

@@ -0,0 +1,32 @@
package org.xyzh.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* Gateway WebFlux Security 配置
* 完全禁用Spring Security的默认行为由AuthGlobalFilter处理认证
*
* @author yslg
* @since 2025-12-11
*/
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.logout(ServerHttpSecurity.LogoutSpec::disable)
.authorizeExchange(exchange -> exchange
.anyExchange().permitAll() // 允许所有请求由AuthGlobalFilter处理认证
)
.build();
}
}

View File

@@ -0,0 +1,59 @@
package org.xyzh.gateway.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Gateway诊断控制器 - 用于检查路由配置
*
* @author yslg
* @since 2025-12-11
*/
@RestController
@RequestMapping("/gateway-diagnostic")
public class GatewayDiagnosticController {
@Autowired
private RouteLocator routeLocator;
/**
* 获取所有路由信息
* 访问: http://localhost:8180/gateway-diagnostic/routes
*/
@GetMapping("/routes")
public Flux<Map<String, Object>> getRoutes() {
return routeLocator.getRoutes()
.map(route -> {
Map<String, Object> routeInfo = new HashMap<>();
routeInfo.put("id", route.getId());
routeInfo.put("uri", route.getUri().toString());
routeInfo.put("order", route.getOrder());
routeInfo.put("predicates", route.getPredicate().toString());
routeInfo.put("filters", route.getFilters().toString());
return routeInfo;
});
}
/**
* 健康检查
* 访问: http://localhost:8180/gateway-diagnostic/health
*/
@GetMapping("/health")
public Map<String, Object> health() {
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("message", "Gateway is running");
health.put("timestamp", System.currentTimeMillis());
return health;
}
}

View File

@@ -1,16 +1,6 @@
spring:
cloud:
gateway:
server:
webflux:
routes:
# 开发环境可以添加更详细的路由配置或测试路由
# Nacos 管理界面路由(开发专用)
- id: nacos-console
uri: http://${NACOS_SERVER_ADDR:localhost:8848}
predicates:
- Path=/nacos/**
# 开发环境专用配置
# 注意不要在这里配置routes会覆盖application.yml的配置
# 路由配置统一在application.yml中管理
# 开发环境日志
logging:

View File

@@ -17,102 +17,119 @@ spring:
namespace: dev
group: DEFAULT_GROUP
config:
enabled: false # 禁用Nacos配置中心使用本地配置
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
file-extension: yml
namespace: dev
group: DEFAULT_GROUP
# Gateway 路由配置(使用新的 webflux 配置路径)
# Gateway 路由配置
gateway:
server:
webflux:
# 服务发现路由(自动路由)
discovery:
locator:
enabled: false # 关闭自动路由,使用手动配置
# 服务发现路由(自动路由)
discovery:
locator:
enabled: false # 关闭自动路由,使用手动配置
# 手动配置路由
routes:
# ==================== 认证服务路由 ====================
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/urban-lifeline/auth/**
filters:
# 不需要重写,直接转发保持原路径
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
# 手动配置路由
routes:
# ==================== 认证服务路由 ====================
- id: auth-service
uri: lb://auth-service
predicates:
- Path=/urban-lifeline/auth/**
filters:
- StripPrefix=1 # 去掉前缀:/urban-lifeline/auth/login → /auth/login
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
# ==================== 系统服务路由 ====================
- id: system-service
uri: lb://system-service
predicates:
- Path=/urban-lifeline/system/**
# ==================== 系统服务路由 ====================
- id: system-service
uri: lb://system-service
predicates:
- Path=/urban-lifeline/system/**
filters:
- StripPrefix=1
# ==================== 日志服务路由 ====================
- id: log-service
uri: lb://log-service
predicates:
- Path=/urban-lifeline/log/**
# ==================== 日志服务路由 ====================
- id: log-service
uri: lb://log-service
predicates:
- Path=/urban-lifeline/log/**
filters:
- StripPrefix=1
# ==================== 文件服务路由 ====================
- id: file-service
uri: lb://file-service
predicates:
- Path=/urban-lifeline/file/**
# ==================== 文件服务路由 ====================
- id: file-service
uri: lb://file-service
predicates:
- Path=/urban-lifeline/file/**
filters:
- StripPrefix=1
# ==================== 消息服务路由 ====================
- id: message-service
uri: lb://message-service
predicates:
- Path=/urban-lifeline/message/**
# ==================== 消息服务路由 ====================
- id: message-service
uri: lb://message-service
predicates:
- Path=/urban-lifeline/message/**
filters:
- StripPrefix=1
# ==================== 招投标服务路由 ====================
- id: bidding-service
uri: lb://bidding-service
predicates:
- Path=/urban-lifeline/bidding/**
# ==================== 招投标服务路由 ====================
- id: bidding-service
uri: lb://bidding-service
predicates:
- Path=/urban-lifeline/bidding/**
filters:
- StripPrefix=1
# ==================== 平台服务路由 ====================
- id: platform-service
uri: lb://platform-service
predicates:
- Path=/urban-lifeline/platform/**
# ==================== 平台服务路由 ====================
- id: platform-service
uri: lb://platform-service
predicates:
- Path=/urban-lifeline/platform/**
filters:
- StripPrefix=1
# ==================== 工单服务路由 ====================
- id: workcase-service
uri: lb://workcase-service
predicates:
- Path=/urban-lifeline/workcase/**
# ==================== 工单服务路由 ====================
- id: workcase-service
uri: lb://workcase-service
predicates:
- Path=/urban-lifeline/workcase/**
filters:
- StripPrefix=1
# ==================== 定时任务服务路由 ====================
- id: crontab-service
uri: lb://crontab-service
predicates:
- Path=/urban-lifeline/crontab/**
# ==================== 定时任务服务路由 ====================
- id: crontab-service
uri: lb://crontab-service
predicates:
- Path=/urban-lifeline/crontab/**
filters:
- StripPrefix=1
# ==================== AI Agent 服务路由 ====================
- id: agent-service
uri: lb://agent-service
predicates:
- Path=/urban-lifeline/agent/**
# ==================== AI Agent 服务路由 ====================
- id: agent-service
uri: lb://agent-service
predicates:
- Path=/urban-lifeline/agent/**
filters:
- StripPrefix=1
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
# 全局跨域配置
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
allowCredentials: true
maxAge: 3600
datasource:
# 按你的实际库名改一下,比如 urban-lifeline_system
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline # 换成你的 PG 库名
@@ -137,7 +154,7 @@ spring:
# 认证配置
auth:
enabled: true
gateway-mode: true
# gateway-mode 是给下游微服务用的gateway本身不需要此配置
token-header: Authorization
token-prefix: "Bearer "
# 认证接口白名单login/logout/captcha/refresh

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8183
servlet:
context-path: /urban-lifeline/log
# servlet:
# context-path: /urban-lifeline/log # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8185
servlet:
context-path: /urban-lifeline/message
# servlet:
# context-path: /urban-lifeline/message # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8187
servlet:
context-path: /urban-lifeline/platform
# servlet:
# context-path: /urban-lifeline/platform # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -82,8 +82,7 @@ public class SysUserServiceImpl implements SysUserService {
if (StringUtils.isBlank(dto.getUserId())) {
dto.setUserId(IDUtils.generateID());
}
dto.setPhone(aesEncryptUtil.encrypt(userVO.getPhone()));
dto.setPhoneHash(AesEncryptUtil.maskPhone(userVO.getPhone()));
dto.setPhone(userVO.getPhone());
dto.setCreateTime(now);
dto.setDeleted(false);
@@ -142,8 +141,7 @@ public class SysUserServiceImpl implements SysUserService {
// 检查手机号是否存在
if (StringUtils.isNotBlank(userVO.getPhone())) {
filter = new SysUserVO();
filter.setPhone(aesEncryptUtil.encrypt(userVO.getPhone()));
filter.setPhoneHash(AesEncryptUtil.maskPhone(userVO.getPhone()));
filter.setPhone(userVO.getPhone());
List<SysUserVO> users = userMapper.getUserByFilter(filter);
if (users != null && !users.isEmpty()) {
logger.warn("手机号已存在: {}", userVO.getPhone());
@@ -228,8 +226,7 @@ public class SysUserServiceImpl implements SysUserService {
public ResultDomain<SysUserVO> getLoginUser(SysUserVO filter) {
// 登录查询语义与 getUser 相同(可根据用户名/手机号/邮箱查询)
if(NonUtils.isNotNull(filter.getPhone())){
filter.setPhone(aesEncryptUtil.encrypt(filter.getPhone()));
filter.setPhoneHash(AesEncryptUtil.maskPhone(filter.getPhone()));
filter.setPhone(filter.getPhone());
}
SysUserVO userVO = userMapper.getUserByFilter(filter).get(0);
return ResultDomain.success("查询成功", userVO);

View File

@@ -35,6 +35,13 @@ urban-lifeline:
# - /api/public/**
# ================== Security ==================
security:
aes:
# AES-256 密钥Base64编码
# 警告:这是开发环境密钥,生产环境请使用密钥管理系统
secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=
# ================== Spring ==================
spring:
# ================== DataSource ==================

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8182
servlet:
context-path: /urban-lifeline/system
# servlet:
# context-path: /urban-lifeline/system # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:
auth:

View File

@@ -8,17 +8,13 @@
<id column="user_id" property="userId" jdbcType="VARCHAR"/>
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="INTEGER"/>
<result column="family_name" property="familyName" jdbcType="VARCHAR"/>
<result column="given_name" property="givenName" jdbcType="VARCHAR"/>
<result column="full_name" property="fullName" jdbcType="VARCHAR"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="level" property="level" jdbcType="INTEGER"/>
<result column="id_card" property="idCard" jdbcType="VARCHAR"/>
<result column="address" property="address" jdbcType="VARCHAR"/>
<!-- 基础字段 -->
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="updater" property="updater" jdbcType="VARCHAR"/>
<result column="dept_path" property="deptPath" jdbcType="VARCHAR"/>
<result column="remark" property="remark" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="DATE"/>
<result column="update_time" property="updateTime" jdbcType="DATE"/>
@@ -28,8 +24,8 @@
<!-- 基础列 -->
<sql id="Base_Column_List">
user_id, avatar, gender, family_name, given_name, full_name, level, id_card, address,
optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted
user_id, avatar, gender, username, level, id_card, address,
optsn, creator, remark, create_time, update_time, delete_time, deleted
</sql>
<!-- 插入系统用户信息(按表字段 + 默认值动态列,仅修改 insert -->
@@ -42,9 +38,7 @@
<!-- 可空/有默认值字段:按是否有入参动态拼接 -->
<if test="avatar != null and avatar != ''">avatar,</if>
<if test="gender != null">gender,</if>
<if test="familyName != null and familyName != ''">family_name,</if>
<if test="givenName != null and givenName != ''">given_name,</if>
<if test="fullName != null and fullName != ''">full_name,</if>
<if test="username != null and username != ''">username,</if>
<if test="level != null">level,</if>
<if test="idCard != null and idCard != ''">id_card,</if>
<if test="address != null and address != ''">address,</if>
@@ -61,9 +55,7 @@
<!-- 可空/有默认值字段对应的值 -->
<if test="avatar != null and avatar != ''">#{avatar},</if>
<if test="gender != null">#{gender},</if>
<if test="familyName != null and familyName != ''">#{familyName},</if>
<if test="givenName != null and givenName != ''">#{givenName},</if>
<if test="fullName != null and fullName != ''">#{fullName},</if>
<if test="username != null and username != ''">#{username},</if>
<if test="level != null">#{level},</if>
<if test="idCard != null and idCard != ''">#{idCard},</if>
<if test="address != null and address != ''">#{address},</if>
@@ -84,14 +76,8 @@
<if test="gender != null">
gender = #{gender},
</if>
<if test="familyName != null">
family_name = #{familyName},
</if>
<if test="givenName != null">
given_name = #{givenName},
</if>
<if test="fullName != null">
full_name = #{fullName},
<if test="username != null">
username = #{username},
</if>
<if test="level != null">
level = #{level},
@@ -102,12 +88,6 @@
<if test="address != null">
address = #{address},
</if>
<if test="updater != null and updater != ''">
updater = #{updater},
</if>
<if test="deptPath != null and deptPath != ''">
dept_path = #{deptPath},
</if>
<if test="remark != null">
remark = #{remark},
</if>
@@ -147,14 +127,8 @@
<if test="filter.userId != null and filter.userId != ''">
AND user_id = #{filter.userId}
</if>
<if test="filter.fullName != null and filter.fullName != ''">
AND full_name LIKE CONCAT('%', #{filter.fullName}, '%')
</if>
<if test="filter.familyName != null and filter.familyName != ''">
AND family_name LIKE CONCAT('%', #{filter.familyName}, '%')
</if>
<if test="filter.givenName != null and filter.givenName != ''">
AND given_name LIKE CONCAT('%', #{filter.givenName}, '%')
<if test="filter.username != null and filter.username != ''">
AND username LIKE CONCAT('%', #{filter.username}, '%')
</if>
<if test="filter.idCard != null and filter.idCard != ''">
AND id_card = #{filter.idCard}
@@ -162,9 +136,6 @@
<if test="filter.gender != null">
AND gender = #{filter.gender}
</if>
<if test="filter.deptPath != null and filter.deptPath != ''">
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
</if>
AND (deleted IS NULL OR deleted = false)
</where>
ORDER BY create_time DESC
@@ -179,14 +150,8 @@
<if test="filter.userId != null and filter.userId != ''">
AND user_id = #{filter.userId}
</if>
<if test="filter.fullName != null and filter.fullName != ''">
AND full_name LIKE CONCAT('%', #{filter.fullName}, '%')
</if>
<if test="filter.familyName != null and filter.familyName != ''">
AND family_name LIKE CONCAT('%', #{filter.familyName}, '%')
</if>
<if test="filter.givenName != null and filter.givenName != ''">
AND given_name LIKE CONCAT('%', #{filter.givenName}, '%')
<if test="filter.username != null and filter.username != ''">
AND username LIKE CONCAT('%', #{filter.username}, '%')
</if>
<if test="filter.idCard != null and filter.idCard != ''">
AND id_card = #{filter.idCard}
@@ -194,9 +159,6 @@
<if test="filter.gender != null">
AND gender = #{filter.gender}
</if>
<if test="filter.deptPath != null and filter.deptPath != ''">
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
</if>
AND (deleted IS NULL OR deleted = false)
</where>
ORDER BY create_time DESC
@@ -211,14 +173,8 @@
<if test="filter.userId != null and filter.userId != ''">
AND user_id = #{filter.userId}
</if>
<if test="filter.fullName != null and filter.fullName != ''">
AND full_name LIKE CONCAT('%', #{filter.fullName}, '%')
</if>
<if test="filter.familyName != null and filter.familyName != ''">
AND family_name LIKE CONCAT('%', #{filter.familyName}, '%')
</if>
<if test="filter.givenName != null and filter.givenName != ''">
AND given_name LIKE CONCAT('%', #{filter.givenName}, '%')
<if test="filter.username != null and filter.username != ''">
AND username LIKE CONCAT('%', #{filter.username}, '%')
</if>
<if test="filter.idCard != null and filter.idCard != ''">
AND id_card = #{filter.idCard}
@@ -226,9 +182,6 @@
<if test="filter.gender != null">
AND gender = #{filter.gender}
</if>
<if test="filter.deptPath != null and filter.deptPath != ''">
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
</if>
AND (deleted IS NULL OR deleted = false)
</where>
</select>

View File

@@ -8,12 +8,13 @@
<id column="user_id" property="userId" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="email" property="email" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR" typeHandler="org.xyzh.common.utils.crypto.EncryptedStringTypeHandler"/>
<result column="wechat_id" property="wechatId" jdbcType="VARCHAR"/>
<result column="status" property="status" jdbcType="INTEGER"/>
<!-- BaseDTO 中在该表实际存在的字段 -->
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="DATE"/>
<result column="update_time" property="updateTime" jdbcType="DATE"/>
<result column="delete_time" property="deleteTime" jdbcType="DATE"/>
@@ -26,15 +27,15 @@
<id column="user_id" property="userId" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="email" property="email" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR" typeHandler="org.xyzh.common.utils.crypto.EncryptedStringTypeHandler"/>
<result column="wechat_id" property="wechatId" jdbcType="VARCHAR"/>
<result column="status" property="status" jdbcType="INTEGER"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<!-- 用户信息关联字段 -->
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="INTEGER"/>
<result column="family_name" property="familyName" jdbcType="VARCHAR"/>
<result column="given_name" property="givenName" jdbcType="VARCHAR"/>
<result column="full_name" property="fullName" jdbcType="VARCHAR"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="level" property="level" jdbcType="INTEGER"/>
<result column="id_card" property="idCard" jdbcType="VARCHAR"/>
<result column="address" property="address" jdbcType="VARCHAR"/>
@@ -78,7 +79,7 @@
#{password},
<!-- 可空/有默认值字段对应的值 -->
<if test="email != null and email != ''">#{email},</if>
<if test="phone != null and phone != ''">#{phone},</if>
<if test="phone != null and phone != ''">#{phone, typeHandler=org.xyzh.common.utils.crypto.EncryptedStringTypeHandler},</if>
<if test="wechatId != null and wechatId != ''">#{wechatId},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
@@ -102,7 +103,7 @@
email = #{email},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
phone = #{phone, typeHandler=org.xyzh.common.utils.crypto.EncryptedStringTypeHandler},
</if>
<if test="wechatId != null and wechatId != ''">
wechat_id = #{wechatId},
@@ -110,12 +111,6 @@
<if test="status != null and status != ''">
status = #{status},
</if>
<if test="userType != null and userType != ''">
user_type = #{userType},
</if>
<if test="updater != null and updater != ''">
updater = #{updater},
</if>
<if test="deptPath != null and deptPath != ''">
dept_path = #{deptPath},
</if>
@@ -145,7 +140,7 @@
SELECT
u.user_id, u.password, u.email, u.phone, u.wechat_id, u.status,
u.optsn, u.create_time, u.update_time, u.delete_time, u.deleted,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address
FROM sys.tb_sys_user u
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
WHERE u.user_id = #{userId}
@@ -155,9 +150,9 @@
<!-- 根据条件查询用户列表 -->
<select id="getUserByFilter" resultMap="UserVOResultMap">
SELECT
u.user_id, u.username, u.password, u.email, u.phone, u.wechat_id, u.status, u.user_type,
u.optsn, u.creator, u.updater, u.dept_path, u.remark, u.create_time, u.update_time, u.delete_time, u.deleted,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address
u.user_id, ui.username, u.password, u.email, u.phone, u.wechat_id, u.status,
u.optsn, u.creator, u.remark, u.create_time, u.update_time, u.delete_time, u.deleted,
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address
FROM sys.tb_sys_user u
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
<where>
@@ -167,9 +162,7 @@
<if test="filter.email != null and filter.email != ''">
AND u.email = #{filter.email}
</if>
<if test="filter.phone != null and filter.phone != ''">
AND u.phone = #{filter.phone}
</if>
<!-- 手机号查询统一使用phoneHash不使用phone字段 -->
<if test="filter.phoneHash != null and filter.phoneHash != ''">
AND u.phone_hash = #{filter.phoneHash}
</if>
@@ -188,9 +181,9 @@
<!-- 根据条件查询用户分页列表 -->
<select id="getUserPageByFilter" resultMap="UserVOResultMap">
SELECT
u.user_id, u.username, u.password, u.email, u.phone, u.wechat_id, u.status, u.user_type,
u.optsn, u.creator, u.updater, u.dept_path, u.remark, u.create_time, u.update_time, u.delete_time, u.deleted,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address
u.user_id, ui.username, u.password, u.email, u.phone, u.wechat_id, u.status,
u.optsn, u.creator, u.remark, u.create_time, u.update_time, u.delete_time, u.deleted,
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address
FROM sys.tb_sys_user u
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
<where>
@@ -198,23 +191,18 @@
AND u.user_id = #{filter.userId}
</if>
<if test="filter.username != null and filter.username != ''">
AND u.username LIKE CONCAT('%', #{filter.username}, '%')
AND ui.username LIKE CONCAT('%', #{filter.username}, '%')
</if>
<if test="filter.email != null and filter.email != ''">
AND u.email = #{filter.email}
</if>
<if test="filter.phone != null and filter.phone != ''">
AND u.phone = #{filter.phone}
<!-- 手机号查询统一使用phoneHash不使用phone字段 -->
<if test="filter.phoneHash != null and filter.phoneHash != ''">
AND u.phone_hash = #{filter.phoneHash}
</if>
<if test="filter.status != null and filter.status != ''">
AND u.status = #{filter.status}
</if>
<if test="filter.userType != null and filter.userType != ''">
AND u.user_type = #{filter.userType}
</if>
<if test="filter.deptPath != null and filter.deptPath != ''">
AND u.dept_path LIKE CONCAT(#{filter.deptPath}, '%')
</if>
AND (u.deleted IS NULL OR u.deleted = false)
</where>
ORDER BY u.create_time DESC
@@ -232,8 +220,9 @@
<if test="filter.email != null and filter.email != ''">
AND email = #{filter.email}
</if>
<if test="filter.phone != null and filter.phone != ''">
AND phone = #{filter.phone}
<!-- 手机号查询统一使用phoneHash不使用phone字段 -->
<if test="filter.phoneHash != null and filter.phoneHash != ''">
AND phone_hash = #{filter.phoneHash}
</if>
<if test="filter.status != null">
AND status = #{filter.status}

View File

@@ -29,13 +29,10 @@
<result column="phone" property="phone" jdbcType="VARCHAR"/>
<result column="wechat_id" property="wechatId" jdbcType="VARCHAR"/>
<result column="status" property="status" jdbcType="VARCHAR"/>
<result column="user_type" property="userType" jdbcType="VARCHAR"/>
<!-- 用户信息字段 -->
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
<result column="gender" property="gender" jdbcType="INTEGER"/>
<result column="family_name" property="familyName" jdbcType="VARCHAR"/>
<result column="given_name" property="givenName" jdbcType="VARCHAR"/>
<result column="full_name" property="fullName" jdbcType="VARCHAR"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="level" property="level" jdbcType="INTEGER"/>
<result column="id_card" property="idCard" jdbcType="VARCHAR"/>
<result column="address" property="address" jdbcType="VARCHAR"/>
@@ -137,15 +134,15 @@
<!-- 根据用户ID查询系统用户角色关系 -->
<select id="getUserRoleByUserId" resultMap="UserDeptRoleVOResultMap" parameterType="java.lang.String">
SELECT
u.user_id, u.username, u.password, u.email, u.phone, u.wechat_id, u.status, u.user_type,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address,
u.user_id, u.password, u.email, u.phone, u.wechat_id, u.status,
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address,
d.dept_id, d.name AS dept_name, d.parent_id, d.description AS dept_description,
r.role_id, r.name AS role_name, r.description AS role_description, r.scope, r.owner_dept_id, r.status AS role_status,
ur.optsn, ur.creator, ur.updater, ur.dept_path, ur.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
ur.optsn, ur.creator, ur.updater, ur.dept_path, u.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
FROM sys.tb_sys_user_role ur
LEFT JOIN sys.tb_sys_user u ON ur.user_id = u.user_id AND (u.deleted IS NULL OR u.deleted = false)
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON u.dept_path LIKE CONCAT('%/', d.dept_id, '/%') AND (d.deleted IS NULL OR d.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON d.dept_id = ur.dept_id
LEFT JOIN sys.tb_sys_role r ON ur.role_id = r.role_id AND (r.deleted IS NULL OR r.deleted = false)
WHERE ur.user_id = #{userId}
AND (ur.deleted IS NULL OR ur.deleted = false)
@@ -155,16 +152,15 @@
<!-- 根据条件查询系统用户角色关系列表 -->
<select id="getUserRoleByFilter" resultMap="UserDeptRoleVOResultMap" parameterType="org.xyzh.common.dto.sys.TbSysUserRoleDTO">
SELECT DISTINCT
u.user_id, u.username, u.password, u.email, u.phone, u.wechat_id, u.status, u.user_type,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address,
u.user_id, u.password, u.email, u.phone, u.wechat_id, u.status,
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address,
d.dept_id, d.name AS dept_name, d.parent_id, d.description AS dept_description,
r.role_id, r.name AS role_name, r.description AS role_description, r.scope, r.owner_dept_id, r.status AS role_status,
ur.optsn, ur.creator, ur.updater, ur.dept_path, ur.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
ur.optsn, ur.creator, ur.updater, ur.dept_path, u.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
FROM sys.tb_sys_user_role ur
LEFT JOIN sys.tb_sys_user u ON ur.user_id = u.user_id AND (u.deleted IS NULL OR u.deleted = false)
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON u.dept_path LIKE CONCAT('%/', d.dept_id, '/%') AND (d.deleted IS NULL OR d.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON d.dept_id = ur.dept_id
LEFT JOIN sys.tb_sys_role r ON ur.role_id = r.role_id AND (r.deleted IS NULL OR r.deleted = false)
<where>
<if test="filter.userId != null and filter.userId != ''">
@@ -184,8 +180,8 @@
<!-- 根据条件查询系统用户角色关系分页列表 -->
<select id="getUserRolePageByFilter" resultMap="UserDeptRoleVOResultMap">
SELECT DISTINCT
u.user_id, u.username, u.password, u.email, u.phone, u.wechat_id, u.status, u.user_type,
ui.avatar, ui.gender, ui.family_name, ui.given_name, ui.full_name, ui.level, ui.id_card, ui.address,
u.user_id, u.password, u.email, u.phone, u.wechat_id, u.status,
ui.avatar, ui.gender, ui.username, ui.level, ui.id_card, ui.address,
d.dept_id, d.name AS dept_name, d.parent_id, d.description AS dept_description,
r.role_id, r.name AS role_name, r.description AS role_description, r.scope, r.owner_dept_id, r.status AS role_status,
(SELECT m.module_id FROM sys.tb_sys_role_permission rp2
@@ -203,11 +199,11 @@
LEFT JOIN sys.tb_sys_module m ON p2.module_id = m.module_id AND (m.deleted IS NULL OR m.deleted = false)
WHERE rp2.role_id = r.role_id AND (rp2.deleted IS NULL OR rp2.deleted = false)
ORDER BY rp2.create_time ASC LIMIT 1) AS module_description,
ur.optsn, ur.creator, ur.updater, ur.dept_path, ur.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
ur.optsn, ur.creator, ur.updater, ur.dept_path, u.remark, ur.create_time, ur.update_time, ur.delete_time, ur.deleted
FROM sys.tb_sys_user_role ur
LEFT JOIN sys.tb_sys_user u ON ur.user_id = u.user_id AND (u.deleted IS NULL OR u.deleted = false)
LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id AND (ui.deleted IS NULL OR ui.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON u.dept_path LIKE CONCAT('%/', d.dept_id, '/%') AND (d.deleted IS NULL OR d.deleted = false)
LEFT JOIN sys.tb_sys_dept d ON d.dept_id = ur.dept_id AND (d.deleted IS NULL OR d.deleted = false)
LEFT JOIN sys.tb_sys_role r ON ur.role_id = r.role_id AND (r.deleted IS NULL OR r.deleted = false)
<where>
<if test="filter.userId != null and filter.userId != ''">

View File

@@ -1,8 +1,8 @@
# ================== Server ==================
server:
port: 8188
servlet:
context-path: /urban-lifeline/workcase
# servlet:
# context-path: /urban-lifeline/workcase # 微服务架构下context-path由Gateway管理
# ================== Auth ====================
urban-lifeline:

View File

@@ -115,9 +115,9 @@
}
const usedRemotes = [
{
entryGlobalName: "http://localhost:5000/remoteEntry.js",
entryGlobalName: "shared",
name: "shared",
type: "var",
type: "module",
entry: "http://localhost:5000/remoteEntry.js",
shareScope: "default",
}