feat(upms): ❕ 增强多租户支持,支持在线切换租户

- 新增SysTenantUser实体类及Mapper用于租户用户关系管理
- 新增SysUserDept实体类及Mapper实现用户多部门支持
- 重构用户信息查询逻辑,支持跨租户查询
- 优化部门管理,支持多租户下的部门树查询
- 扩展UserInfo DTO增加deptId字段
- 重构Redis授权服务,优化租户信息存储结构

# Conflicts:
#	pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysTenantController.java
#	pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysUserService.java
This commit is contained in:
PIG AI committed 2025-07-04 11:56:14 +08:00
1 parent 8227768849
commit 7205251977
31 files changed
+1008 -634

No files matched your search

@@ -23,19 +23,29 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
/**
* 租户信息拦截配置类
*
* @author lengleng
* @date 2020/4/29
* <p>
* 租户信息拦截
* @date 2025/06/27
*/
@Configuration
public class PigxTenantConfiguration {
/**
* 创建并返回PigxFeignTenantInterceptor实例
*
* @return RequestInterceptor实例
*/
@Bean
public RequestInterceptor pigxFeignTenantInterceptor() {
return new PigxFeignTenantInterceptor();
}
/**
* 创建并返回一个租户请求拦截器实例
*
* @return 配置好的租户请求拦截器
*/
@Bean
public ClientHttpRequestInterceptor pigxTenantRequestInterceptor() {
return new TenantRequestInterceptor();
@@ -43,105 +43,148 @@ import java.util.function.Supplier;
@UtilityClass
public class TenantBroker {
@FunctionalInterface
public interface RunAs<T> {
/**
* 以指定租户身份执行操作
*
* @param tenant 租户ID
* @param func 需要执行的操作
* @throws TenantBrokerExceptionWrapper 执行过程中发生异常时抛出
*/
public void runAs(Long tenant, RunAs<Long> func) {
final Long pre = TenantContextHolder.getTenantId();
try {
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
TenantContextHolder.setTenantId(tenant);
func.run(tenant);
} catch (Exception e) {
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
} finally {
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
TenantContextHolder.setTenantId(pre);
}
}
/**
* 执行业务逻辑
* @param tenantId
* @throws Exception
*/
void run(T tenantId) throws Exception;
/**
* 以指定租户身份执行操作
*
* @param tenant 租户ID
* @param func 需要执行的操作函数
* @param <T> 操作返回的数据类型
* @return 操作函数的执行结果
* @throws TenantBrokerExceptionWrapper 执行过程中发生异常时抛出
*/
public <T> T applyAs(Long tenant, ApplyAs<Long, T> func) {
final Long pre = TenantContextHolder.getTenantId();
try {
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
TenantContextHolder.setTenantId(tenant);
return func.apply(tenant);
} catch (Exception e) {
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
} finally {
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
TenantContextHolder.setTenantId(pre);
}
}
}
/**
* 以指定租户的身份执行操作
*
* @param supplier 租户ID提供者
* @param func 要执行的操作
*/
public void runAs(Supplier<Long> supplier, RunAs<Long> func) {
runAs(supplier.get(), func);
}
@FunctionalInterface
public interface ApplyAs<T, R> {
/**
* 执行给定的NoneAs操作,临时跳过租户上下文
*
* @param noneAs 要执行的操作
* @param <T> 返回值的类型
* @return 操作执行结果
* @throws TenantBrokerExceptionWrapper 执行过程中\发生异常时抛出
*/
public <T> T noneAs(NoneAs<T> noneAs) {
try {
TenantContextHolder.setTenantSkip();
return noneAs.run();
} catch (Exception e) {
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
} finally {
TenantContextHolder.unsetTenantSkip();
}
}
/**
* 执行业务逻辑,返回一个值
* @param tenantId
* @return
* @throws Exception
*/
R apply(T tenantId) throws Exception;
/**
* 以指定租户的身份执行操作
*
* @param supplier 租户ID提供者
* @param func 要执行的操作函数
* @param <T> 返回数据的类型
* @return 操作函数的执行结果
*/
public <T> T applyAs(Supplier<Long> supplier, ApplyAs<Long, T> func) {
return applyAs(supplier.get(), func);
}
}
@FunctionalInterface
public interface RunAs<T> {
/**
* 以某个租户的身份运行
* @param tenant 租户ID
* @param func
*/
public void runAs(Long tenant, RunAs<Long> func) {
final Long pre = TenantContextHolder.getTenantId();
try {
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
TenantContextHolder.setTenantId(tenant);
func.run(tenant);
}
catch (Exception e) {
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
}
finally {
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
TenantContextHolder.setTenantId(pre);
}
}
/**
* 执行业务逻辑
*
* @param tenantId 租户ID
* @throws Exception 执行过程中可能抛出的异常
*/
void run(T tenantId) throws Exception;
/**
* 以某个租户的身份运行
* @param tenant 租户ID
* @param func
* @param <T> 返回数据类型
* @return
*/
public <T> T applyAs(Long tenant, ApplyAs<Long, T> func) {
final Long pre = TenantContextHolder.getTenantId();
try {
log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
TenantContextHolder.setTenantId(tenant);
return func.apply(tenant);
}
catch (Exception e) {
throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
}
finally {
log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
TenantContextHolder.setTenantId(pre);
}
}
}
/**
* 以某个租户的身份运行
* @param supplier
* @param func
*/
public void runAs(Supplier<Long> supplier, RunAs<Long> func) {
runAs(supplier.get(), func);
}
/**
* 以某个租户的身份运行
* @param supplier
* @param func
* @param <T> 返回数据类型
* @return
*/
public <T> T applyAs(Supplier<Long> supplier, ApplyAs<Long, T> func) {
return applyAs(supplier.get(), func);
}
@FunctionalInterface
public interface ApplyAs<T, R> {
public static class TenantBrokerExceptionWrapper extends RuntimeException {
/**
* 执行业务逻辑,返回一个值
*
* @param tenantId
* @return
* @throws Exception
*/
R apply(T tenantId) throws Exception;
public TenantBrokerExceptionWrapper(String message, Throwable cause) {
super(message, cause);
}
}
public TenantBrokerExceptionWrapper(Throwable cause) {
super(cause);
}
/**
* 无参数函数式接口:用于定义无参数但有返回值的业务逻辑执行方法
*
* @author lengleng
* @date 2025/06/27
*/
@FunctionalInterface
public interface NoneAs<R> {
}
/**
* 执行业务逻辑
*
* @return 业务执行结果
* @throws Exception 执行过程中发生异常时抛出
*/
R run() throws Exception;
}
public static class TenantBrokerExceptionWrapper extends RuntimeException {
public TenantBrokerExceptionWrapper(String message, Throwable cause) {
super(message, cause);
}
public TenantBrokerExceptionWrapper(Throwable cause) {
super(cause);
}
}
}
@@ -48,6 +48,13 @@ public class TenantContextHolder {
THREAD_LOCAL_TENANT_SKIP_FLAG.set(Boolean.TRUE);
}
/**
* 清除当前线程的租户跳过标记
*/
public void unsetTenantSkip() {
THREAD_LOCAL_TENANT_SKIP_FLAG.remove();
}
/**
* 获取TTL中的租户ID
* @return
@@ -9,13 +9,22 @@ import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
/**
* RestTemplate 请求拦截器,用于传递租户ID
*
* @author lengleng
* @date 2020/4/29
* <p>
* 传递 RestTemplate 请求的租户ID
* @date 2025/06/27
*/
public class TenantRequestInterceptor implements ClientHttpRequestInterceptor {
/**
* 拦截HTTP请求,添加租户ID到请求头
*
* @param request HTTP请求对象
* @param body 请求体字节数组
* @param execution 请求执行器
* @return 客户端HTTP响应
* @throws IOException 当I/O操作失败时抛出
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
@@ -2,11 +2,11 @@ package com.pig4cloud.pigx.common.security.service;
import cn.hutool.core.collection.CollUtil;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@@ -17,6 +17,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.util.Assert;
import java.security.Principal;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
@@ -37,8 +38,6 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
private final RedisTemplate<String, Object> redisTemplate;
private final KeyStrResolver tenantKeyStrResolver;
private static boolean isState(OAuth2Authorization authorization) {
return Objects.nonNull(authorization.getAttribute("state"));
}
@@ -97,10 +96,14 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between,
TimeUnit.SECONDS);
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
String tokenUsername = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION,
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue());
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = authorization.getAttribute(Principal.class.getName());
if (Objects.nonNull(usernamePasswordAuthenticationToken) && usernamePasswordAuthenticationToken.getPrincipal() instanceof PigxUser pigxUser) {
// 扩展记录 access-token 、username 的关系 token::username::admin::tenantId::xxx
String tokenUsername = String.format("%s::%s::%s::%s::%s::%s", AUTHORIZATION,
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), pigxUser.getTenantId(), accessToken.getTokenValue());
redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
}
}
}
@@ -131,8 +134,8 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
String key = String.format("%s::%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION,
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), accessToken.getTokenValue());
String key = String.format("%s::%s::%s::%s::*::%s", AUTHORIZATION,
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), accessToken.getTokenValue());
keys.add(key);
}
@@ -155,21 +158,20 @@ public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationS
}
private String buildKey(String type, String id) {
return String.format("%s::%s::%s::%s", tenantKeyStrResolver.key(), AUTHORIZATION, type, id);
return String.format("%s::%s::%s", AUTHORIZATION, type, id);
}
/**
* 扩展方法根据 username 查询是否存在存储的
* 根据用户名移除相关授权信息
*
* @param authentication
* @return
* @param authentication 认证信息,包含用户名
*/
public void removeByUsername(Authentication authentication) {
// 根据 username查询对应access-token
String authenticationName = authentication.getName();
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
String tokenUsernameKey = String.format("%s::%s::%s::%s::*", tenantKeyStrResolver.key(), AUTHORIZATION,
String tokenUsernameKey = String.format("%s::%s::%s::*", AUTHORIZATION,
SecurityConstants.DETAILS_USERNAME, authenticationName);
Set<String> keys = redisTemplate.keys(tokenUsernameKey);
if (CollUtil.isEmpty(keys)) {
@@ -98,7 +98,7 @@ public interface PigxUserDetailsService extends UserDetailsService, Ordered {
SysUser user = info.getSysUser();
// 构造security用户
return new PigxUser(user.getUserId(), user.getUsername(), user.getDeptId(), user.getPhone(), user.getAvatar(),
return new PigxUser(user.getUserId(), user.getUsername(), info.getDeptId(), user.getPhone(), user.getAvatar(),
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
SecurityConstants.BCRYPT + user.getPassword()
, true, true, UserTypeEnum.TOB.getStatus()
@@ -33,22 +33,28 @@ import java.io.Serializable;
@Schema(description = "用户信息")
public class UserInfo implements Serializable {
/**
* 用户基本信息
*/
@Schema(description = "用户基本信息")
private SysUser sysUser;
/**
* 用户基本信息
*/
@Schema(description = "用户基本信息")
private SysUser sysUser;
/**
* 权限标识集合
*/
@Schema(description = "权限标识集合")
private String[] permissions;
/**
* 权限标识集合
*/
@Schema(description = "权限标识集合")
private String[] permissions;
/**
* 角色集合
*/
@Schema(description = "角色标识集合")
private Long[] roles;
/**
* 角色集合
*/
@Schema(description = "角色标识集合")
private Long[] roles;
/**
* 部门ID
*/
@Schema(description = "用户所属部门id")
private Long deptId;
}
@@ -0,0 +1,55 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/
package com.pig4cloud.pigx.admin.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 租户用户信息实体类
*
* @author lengleng
* @date 2025/06/27
*/
@Data
@Schema(description = "租户用户信息")
@EqualsAndHashCode(callSuper = true)
public class SysTenantUser extends Model<SysTenantUser> {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@Schema(description = "用户id")
private Long userId;
/**
* 租户ID
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -146,11 +146,6 @@ public class SysUser extends Model<SysUser> implements Serializable {
@Schema(description = "头像地址")
private String avatar;
/**
* 部门ID
*/
@Schema(description = "用户所属部门id")
private Long deptId;
/**
* 租户ID
@@ -219,5 +214,4 @@ public class SysUser extends Model<SysUser> implements Serializable {
@PropertyName("邮箱")
@Schema(description = "邮箱")
private String email;
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pig4cloud.pigx.admin.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.pig4cloud.pigx.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 系统用户部门关联实体类
*
* @author lengleng
* @date 2025/06/27
*/
@Data
@TenantTable
@EqualsAndHashCode(callSuper = true)
public class SysUserDept extends Model<SysUserDept> {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@Schema(description = "用户id")
private Long userId;
/**
* 岗位ID
*/
@Schema(description = "岗位id")
private Long deptId;
}
@@ -17,21 +17,25 @@
package com.pig4cloud.pigx.admin.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.pig4cloud.pigx.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* <p>
* 用户岗位表
* </p>
* 用户岗位表实体类
*
* @author fxz
* @author lengleng
* @date 2025/06/27
*/
@Data
@TenantTable
@EqualsAndHashCode(callSuper = true)
public class SysUserPost extends Model<SysUserPost> {
@Serial
private static final long serialVersionUID = 1L;
/**
@@ -20,10 +20,13 @@
package com.pig4cloud.pigx.admin.api.entity;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.pig4cloud.pigx.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* <p>
* 用户角色表
@@ -33,22 +36,31 @@ import lombok.EqualsAndHashCode;
* @since 2017-10-29
*/
@Data
@TenantTable
@Schema(description = "用户角色")
@EqualsAndHashCode(callSuper = true)
public class SysUserRole extends Model<SysUserRole> {
private static final long serialVersionUID = 1L;
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@Schema(description = "用户id")
private Long userId;
/**
* 用户ID
*/
@Schema(description = "用户id")
private Long userId;
/**
* 角色ID
*/
@Schema(description = "角色id")
private Long roleId;
/**
* 角色ID
*/
@Schema(description = "角色id")
private Long roleId;
/**
* 租户ID
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -4,9 +4,9 @@ import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.pig4cloud.pigx.common.excel.annotation.ExcelLine;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -75,7 +75,7 @@ public class UserExcelVO implements Serializable {
*/
@NotBlank(message = "部门名称不能为空")
@ExcelProperty("部门名称")
private String deptName;
private String deptNameList;
/**
* 角色列表
@@ -20,6 +20,7 @@
package com.pig4cloud.pigx.admin.api.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.pig4cloud.pigx.admin.api.entity.SysDept;
import com.pig4cloud.pigx.admin.api.entity.SysPost;
import com.pig4cloud.pigx.admin.api.entity.SysRole;
import com.pig4cloud.pigx.common.sensitive.annotation.Sensitive;
@@ -27,6 +28,7 @@ import com.pig4cloud.pigx.common.sensitive.core.SensitiveTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@@ -39,163 +41,158 @@ import java.util.List;
@Schema(description = "前端用户展示对象")
public class UserVO implements Serializable {
private static final long serialVersionUID = 1L;
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@Schema(description = "主键")
private Long userId;
/**
* 主键ID
*/
@Schema(description = "主键")
private Long userId;
/**
* 用户名
*/
@Schema(description = "用户名")
private String username;
/**
* 用户名
*/
@Schema(description = "用户名")
private String username;
/**
* 密码
*/
@JsonIgnore
private String password;
/**
* 密码
*/
@JsonIgnore
private String password;
/**
* 随机盐
*/
@JsonIgnore
private String salt;
/**
* 随机盐
*/
@JsonIgnore
private String salt;
/**
* 微信openid
*/
@Schema(description = "微信open id")
private String wxOpenid;
/**
* 微信openid
*/
@Schema(description = "微信open id")
private String wxOpenid;
/**
* QQ openid
*/
@Schema(description = "qq open id")
private String qqOpenid;
/**
* QQ openid
*/
@Schema(description = "qq open id")
private String qqOpenid;
/**
* gitee openid
*/
@Schema(description = "gitee open id")
private String giteeOpenId;
/**
* gitee openid
*/
@Schema(description = "gitee open id")
private String giteeOpenId;
/**
* 开源中国 openid
*/
@Schema(description = "开源中国 open id")
private String oscOpenId;
/**
* 开源中国 openid
*/
@Schema(description = "开源中国 open id")
private String oscOpenId;
/**
* 企微微信 userid
*/
@Schema(description = "企微微信 userid")
private String wxCpUserid;
/**
* 企微微信 userid
*/
@Schema(description = "企微微信 userid")
private String wxCpUserid;
/**
* 钉钉 userid
*/
@Schema(description = "钉钉 userid")
private String wxDingUserid;
/**
* 钉钉 userid
*/
@Schema(description = "钉钉 userid")
private String wxDingUserid;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改时间
*/
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 修改时间
*/
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 0-正常,1-删除
*/
@Schema(description = "删除标记,1:已删除,0:正常")
private String delFlag;
/**
* 0-正常,1-删除
*/
@Schema(description = "删除标记,1:已删除,0:正常")
private String delFlag;
/**
* 锁定标记
*/
@Schema(description = "锁定标记,0:正常,9:已锁定")
private String lockFlag;
/**
* 锁定标记
*/
@Schema(description = "锁定标记,0:正常,9:已锁定")
private String lockFlag;
/**
* 密码过期标记
*/
@Schema(description = "密码过期标记")
private String passwordExpireFlag;
/**
* 密码过期标记
*/
@Schema(description = "密码过期标记")
private String passwordExpireFlag;
/**
* 密码修改时间
*/
@Schema(description = "密码修改时间")
private LocalDateTime passwordModifyTime;
/**
* 密码修改时间
*/
@Schema(description = "密码修改时间")
private LocalDateTime passwordModifyTime;
/**
* 手机号
*/
@Sensitive(type = SensitiveTypeEnum.MOBILE_PHONE)
@Schema(description = "手机号")
private String phone;
/**
* 手机号
*/
@Sensitive(type = SensitiveTypeEnum.MOBILE_PHONE)
@Schema(description = "手机号")
private String phone;
/**
* 头像
*/
@Schema(description = "头像")
private String avatar;
/**
* 头像
*/
@Schema(description = "头像")
private String avatar;
/**
* 部门ID
*/
@Schema(description = "所属部门")
private Long deptId;
/**
* 租户ID
*/
@Schema(description = "所属租户")
private Long tenantId;
/**
* 租户ID
*/
@Schema(description = "所属租户")
private Long tenantId;
/**
* 部门名称
*/
@Schema(description = "所属部门名称")
private String deptName;
/**
* 角色列表
*/
@Schema(description = "拥有的角色列表")
private List<SysRole> roleList;
@Schema(description = "所属部门名称")
private List<SysDept> deptList;
/**
* 岗位列表
*/
private List<SysPost> postList;
/**
* 角色列表
*/
@Schema(description = "拥有的角色列表")
private List<SysRole> roleList;
/**
* 昵称
*/
@Schema(description = "昵称")
private String nickname;
/**
* 岗位列表
*/
@Schema(description = "拥有的岗位列表")
private List<SysPost> postList;
/**
* 姓名
*/
@Schema(description = "姓名")
private String name;
/**
* 昵称
*/
@Schema(description = "昵称")
private String nickname;
/**
* 邮箱
*/
@Schema(description = "邮箱")
private String email;
/**
* 姓名
*/
@Schema(description = "姓名")
private String name;
/**
* 邮箱
*/
@Schema(description = "邮箱")
private String email;
}
@@ -59,118 +59,143 @@ import java.util.List;
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SysTenantController {
private final SysTenantService sysTenantService;
private final SysTenantService sysTenantService;
private final SysMenuService sysMenuService;
private final SysMenuService sysMenuService;
/**
* 分页查询
* @param page 分页对象
* @param sysTenant 租户
* @return
*/
@GetMapping("/page")
public R getSysTenantPage(@ParameterObject Page page, @ParameterObject SysTenant sysTenant) {
return R.ok(sysTenantService.page(page, Wrappers.<SysTenant>lambdaQuery()
.like(StrUtil.isNotBlank(sysTenant.getName()), SysTenant::getName, sysTenant.getName())));
}
/**
* 分页查询
*
* @param page 分页对象
* @param sysTenant 租户
* @return
*/
@GetMapping("/page")
public R getSysTenantPage(@ParameterObject Page page, @ParameterObject SysTenant sysTenant) {
return R.ok(sysTenantService.page(page, Wrappers.<SysTenant>lambdaQuery()
.like(StrUtil.isNotBlank(sysTenant.getName()), SysTenant::getName, sysTenant.getName())));
}
/**
* 通过ID 查询租户信息
* @param id ID
* @return R
*/
@GetMapping("/details/{id}")
public R getById(@PathVariable Long id) {
return R.ok(sysTenantService.getById(id));
}
/**
* 通过ID 查询租户信息
*
* @param id ID
* @return R
*/
@GetMapping("/details/{id}")
public R getById(@PathVariable Long id) {
return R.ok(sysTenantService.getById(id));
}
/**
* 查询租户信息
* @param query 查询条件
* @return 租户信息
*/
@GetMapping("/details")
public R getDetails(@ParameterObject SysTenant query) {
return R.ok(sysTenantService.getOne(Wrappers.query(query), false));
}
/**
* 查询租户信息
*
* @param query 查询条件
* @return 租户信息
*/
@GetMapping("/details")
public R getDetails(@ParameterObject SysTenant query) {
return R.ok(sysTenantService.getOne(Wrappers.query(query), false));
}
/**
* 新增租户
* @param sysTenant 租户
* @return R
*/
@SysLog("新增租户")
@PostMapping
@HasPermission("sys_systenant_add")
@CacheEvict(value = CacheConstants.TENANT_DETAILS, allEntries = true)
public R save(@RequestBody SysTenant sysTenant) {
return R.ok(sysTenantService.saveTenant(sysTenant));
}
/**
* 新增租户
*
* @param sysTenant 租户
* @return R
*/
@SysLog("新增租户")
@PostMapping
@HasPermission("sys_systenant_add")
@CacheEvict(value = CacheConstants.TENANT_DETAILS, allEntries = true)
public R save(@RequestBody SysTenant sysTenant) {
return R.ok(sysTenantService.saveTenant(sysTenant));
}
/**
* 修改租户
* @param sysTenant 租户
* @return R
*/
@SysLog("修改租户")
@PutMapping
@HasPermission("sys_systenant_edit")
public R updateById(@RequestBody SysTenant sysTenant) {
return R.ok(sysTenantService.updateTenant(sysTenant));
}
/**
* 修改租户
*
* @param sysTenant 租户
* @return R
*/
@SysLog("修改租户")
@PutMapping
@HasPermission("sys_systenant_edit")
public R updateById(@RequestBody SysTenant sysTenant) {
return R.ok(sysTenantService.updateTenant(sysTenant));
}
/**
* 通过id删除租户
* <p>
* 为了保证安全,这里只删除租户表的数据,不删除基础表中的租户初始化数据。
* @param ids id 列表
* @return R
*/
@SysLog("删除租户")
@DeleteMapping
@HasPermission("sys_systenant_del")
@CacheEvict(value = CacheConstants.TENANT_DETAILS, allEntries = true)
public R removeById(@RequestBody Long[] ids) {
return R.ok(sysTenantService.removeBatchByIds(CollUtil.toList(ids)));
}
/**
* 通过id删除租户
* <p>
* 为了保证安全,这里只删除租户表的数据,不删除基础表中的租户初始化数据。
*
* @param ids id 列表
* @return R
*/
@SysLog("删除租户")
@DeleteMapping
@HasPermission("sys_systenant_del")
@CacheEvict(value = CacheConstants.TENANT_DETAILS, allEntries = true)
public R removeById(@RequestBody Long[] ids) {
return R.ok(sysTenantService.removeBatchByIds(CollUtil.toList(ids)));
}
/**
* 查询全部有效的租户
* @return
*/
@Inner(value = false)
@GetMapping("/list")
public R list() {
List<SysTenant> tenants = sysTenantService.getNormalTenant()
.stream()
.filter(tenant -> tenant.getStartTime().isBefore(LocalDateTime.now()))
.filter(tenant -> tenant.getEndTime().isAfter(LocalDateTime.now()))
/**
* 查询全部有效的租户列表
*
* @return 包含有效租户列表的响应结果
*/
@Inner(value = false)
@GetMapping("/list")
public R list() {
List<SysTenant> tenants = sysTenantService.getNormalTenant()
.stream()
.filter(tenant -> tenant.getStartTime().isBefore(LocalDateTime.now()))
.filter(tenant -> tenant.getEndTime().isAfter(LocalDateTime.now()))
.toList();
return R.ok(tenants);
}
return R.ok(tenants);
}
/**
* 导出excel 表格
* @return
*/
@ResponseExcel
@GetMapping("/export")
@HasPermission("sys_systenant_export")
public List<SysTenant> export(SysTenant sysTenant, Long[] ids) {
return sysTenantService
.list(Wrappers.lambdaQuery(sysTenant).in(ArrayUtil.isNotEmpty(ids), SysTenant::getId, CollUtil.toList(ids)));
}
/**
* 导出租户信息到Excel
*
* @param sysTenant 租户查询条件
* @param ids 租户ID数组
* @return 符合条件的租户列表
*/
@ResponseExcel
@GetMapping("/export")
@HasPermission("sys_systenant_export")
public List<SysTenant> export(SysTenant sysTenant, Long[] ids) {
return sysTenantService
.list(Wrappers.lambdaQuery(sysTenant).in(ArrayUtil.isNotEmpty(ids), SysTenant::getId, CollUtil.toList(ids)));
}
@GetMapping(value = "/tree/menu")
public R getTree() {
Long defaultId = ParamResolver.getLong("TENANT_DEFAULT_ID", 1L);
List<Tree<Long>> trees = new ArrayList<>();
TenantBroker.runAs(defaultId, (id) -> {
trees.addAll(sysMenuService.treeMenu(null, null, null));
});
/**
* 获取租户菜单
*
* @return 包含菜单树结构的响应结果
*/
@GetMapping(value = "/tree/menu")
public R getTree() {
Long defaultId = ParamResolver.getLong("TENANT_DEFAULT_ID", 1L);
List<Tree<Long>> trees = new ArrayList<>();
TenantBroker.runAs(defaultId, (id) -> {
trees.addAll(sysMenuService.treeMenu(null, null, null));
});
return R.ok(trees);
}
return R.ok(trees);
}
/**
* 获取用户租户信息
*
* @return 包含用户租户信息的响应结果
*/
@GetMapping("/user-tenant")
public R getUserTenant() {
return R.ok(sysTenantService.getUserTenant());
}
}
@@ -31,6 +31,7 @@ import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.exception.ErrorCodes;
import com.pig4cloud.pigx.common.core.util.MsgUtils;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
import com.pig4cloud.pigx.common.excel.annotation.RequestExcel;
import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel;
import com.pig4cloud.pigx.common.log.annotation.SysLog;
@@ -50,8 +51,10 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户管理控制器:提供用户相关的REST接口
*
* @author lengleng
* @date 2018/12/16
* @date 2025/07/04
*/
@RestController
@AllArgsConstructor
@@ -70,11 +73,12 @@ public class SysUserController {
@Inner
@GetMapping("/info/{username}")
public R info(@PathVariable String username) {
SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
SysUser user = TenantBroker.noneAs(() -> userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username), false));
if (user == null) {
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERINFO_EMPTY, username));
}
return R.ok(userService.findUserInfo(user));
return TenantBroker.applyAs(user.getTenantId(), tenantId -> R.ok(userService.findUserInfo(user)));
}
/**
@@ -85,15 +89,18 @@ public class SysUserController {
@GetMapping(value = {"/info"})
public R info() {
String username = SecurityUtils.getUser().getUsername();
SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
SysUser user = TenantBroker.noneAs(() -> userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username), false));
if (user == null) {
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_QUERY_ERROR));
}
// UserInfo 是复用对象,不能通过设置 @jsonIgnore 来忽略密码字段
UserInfo userInfo = userService.findUserInfo(user);
userInfo.getSysUser().setPassword(null);
return R.ok(userInfo);
return TenantBroker.applyAs(user.getTenantId(), tenantId -> {
// UserInfo 是复用对象,不能通过设置 @jsonIgnore 来忽略密码字段
UserInfo userInfo = userService.findUserInfo(user);
userInfo.getSysUser().setPassword(null);
return R.ok(userInfo);
});
}
/**
@@ -191,12 +198,27 @@ public class SysUserController {
* @return R 返回结果对象,包含修改密码操作的结果信息
*/
@PutMapping("/personal/password")
public R updatePassword(@RequestBody UserDTO userDto) {
public R updateUserPassword(@RequestBody UserDTO userDto) {
String username = SecurityUtils.getUser().getUsername();
userDto.setUsername(username);
return userService.changePassword(userDto);
}
/**
* 更新用户租户信息
*
* @param userDto 用户数据传输对象,包含需要更新的用户信息
* @return 操作结果
*/
@PutMapping("/personal/tenant")
public R updateUserTenant(@RequestBody UserDTO userDto) {
String username = SecurityUtils.getUser().getUsername();
userDto.setUsername(username);
userDto.setUserId(SecurityUtils.getUser().getId());
return userService.updateUserTenant(userDto);
}
/**
* @param username 用户名称
* @return 上级部门用户列表
@@ -19,19 +19,31 @@
package com.pig4cloud.pigx.admin.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.pig4cloud.pigx.admin.api.entity.SysDept;
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 部门管理 Mapper 接口
* </p>
*
* @author lengleng
* @since 2018-01-20
* @date 2025/06/27
*/
@Mapper
public interface SysDeptMapper extends PigxBaseMapper<SysDept> {
/**
* 根据用户ID和租户ID查询部门列表
*
* @param userId 用户ID
* @param tenantId 租户ID
* @return 部门列表
*/
@InterceptorIgnore(tenantLine = "true")
List<SysDept> listDeptsByUserId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
}
@@ -17,9 +17,11 @@
package com.pig4cloud.pigx.admin.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.pig4cloud.pigx.admin.api.entity.SysPost;
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -32,11 +34,14 @@ import java.util.List;
@Mapper
public interface SysPostMapper extends PigxBaseMapper<SysPost> {
/**
* 通过用户ID,查询岗位信息
* @param userId 用户id
* @return 岗位信息
*/
List<SysPost> listPostsByUserId(Long userId);
/**
* 通过用户ID和租户ID查询岗位信息
*
* @param userId 用户ID
* @param tenantId 租户ID
* @return 岗位信息列表
*/
@InterceptorIgnore(tenantLine = "true")
List<SysPost> listPostsByUserId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
}
@@ -19,9 +19,11 @@
package com.pig4cloud.pigx.admin.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.pig4cloud.pigx.admin.api.entity.SysRole;
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -36,11 +38,14 @@ import java.util.List;
@Mapper
public interface SysRoleMapper extends PigxBaseMapper<SysRole> {
/**
* 通过用户ID,查询角色信息
* @param userId
* @return
*/
List<SysRole> listRolesByUserId(Long userId);
/**
* 通过用户ID查询角色信息
*
* @param userId 用户ID
* @param tenantId 租户ID
* @return 角色信息列表
*/
@InterceptorIgnore(tenantLine = "true")
List<SysRole> listRolesByUserId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
}
@@ -0,0 +1,36 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/
package com.pig4cloud.pigx.admin.mapper;
import com.pig4cloud.pigx.admin.api.entity.SysTenantUser;
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统租户用户映射接口
*
* @author lengleng
* @date 2025/06/27
*/
@Mapper
public interface SysTenantUserMapper extends PigxBaseMapper<SysTenantUser> {
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pig4cloud.pigx.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pig4cloud.pigx.admin.api.entity.SysUserDept;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户部门关联Mapper接口
*
* @author lengleng
* @date 2025/06/27
*/
@Mapper
public interface SysUserDeptMapper extends BaseMapper<SysUserDept> {
}
@@ -31,23 +31,32 @@ import java.util.List;
public interface SysTenantService extends IService<SysTenant> {
/**
* 获取正常的租户
* @return
* 获取正常的租户列表
*
* @return 正常的租户列表
*/
List<SysTenant> getNormalTenant();
/**
* 保存租户
* @param sysTenant
* @return
* 保存租户信息
*
* @param sysTenant 租户信息对象
* @return 保存是否成功
*/
Boolean saveTenant(SysTenant sysTenant);
/**
* 修改租户
* @param sysTenant
* @return
* 修改租户信息
*
* @param sysTenant 租户信息对象
* @return 修改是否成功
*/
Boolean updateTenant(SysTenant sysTenant);
/**
* 获取用户所属租户列表
*
* @return 用户所属租户列表
*/
List<SysTenant> getUserTenant();
}
@@ -195,4 +195,12 @@ public interface SysUserService extends IService<SysUser> {
* @return
*/
R<Boolean> forgetUserPassword(RegisterUserDTO userDto, String code);
/**
* 更新用户租户信息
*
* @param userDto 用户数据传输对象
* @return 更新结果
*/
R updateUserTenant(UserDTO userDto);
}
@@ -27,6 +27,7 @@ import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.pig4cloud.pigx.admin.api.constant.OrgTypeEnum;
import com.pig4cloud.pigx.admin.api.constant.PostCodeEnum;
import com.pig4cloud.pigx.admin.api.entity.*;
@@ -66,10 +67,13 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
private final SysRoleMapper roleMapper;
private final SysUserDeptMapper userDeptMapper;
private final SysUserPostMapper userPostMapper;
/**
* 删除部门
*
* @param id 部门 ID
* @return 成功、失败
*/
@@ -86,6 +90,7 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
/**
* 查询全部部门树
*
* @param deptName
* @param parentId
* @return 树 部门名称
@@ -94,33 +99,33 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
public List<Tree<Long>> selectTree(String deptName, Long parentId) {
// 查询全部部门
List<SysDept> deptAllList = deptMapper
.selectList(Wrappers.<SysDept>lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName));
.selectList(Wrappers.<SysDept>lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName));
// 查询数据权限内部门
List<Long> deptOwnIdList = deptMapper
.selectListByScope(
Wrappers.<SysDept>lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName),
DataScope.of())
.stream()
.map(SysDept::getDeptId)
.toList();
.selectListByScope(
Wrappers.<SysDept>lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName),
DataScope.of())
.stream()
.map(SysDept::getDeptId)
.toList();
// 权限内部门
List<TreeNode<Long>> collect = deptAllList.stream()
.filter(dept -> dept.getDeptId().intValue() != dept.getParentId())
.sorted(Comparator.comparingInt(SysDept::getSortOrder))
.map(dept -> {
TreeNode<Long> treeNode = new TreeNode<>();
treeNode.setId(dept.getDeptId());
treeNode.setParentId(dept.getParentId());
treeNode.setName(dept.getName());
treeNode.setWeight(dept.getSortOrder());
// 有权限不返回标识
Map<String, Object> extra = new HashMap<>(8);
extra.put("isLock", !deptOwnIdList.contains(dept.getDeptId()));
extra.put("createTime", dept.getCreateTime());
treeNode.setExtra(extra);
return treeNode;
})
.filter(dept -> dept.getDeptId().intValue() != dept.getParentId())
.sorted(Comparator.comparingInt(SysDept::getSortOrder))
.map(dept -> {
TreeNode<Long> treeNode = new TreeNode<>();
treeNode.setId(dept.getDeptId());
treeNode.setParentId(dept.getParentId());
treeNode.setName(dept.getName());
treeNode.setWeight(dept.getSortOrder());
// 有权限不返回标识
Map<String, Object> extra = new HashMap<>(8);
extra.put("isLock", !deptOwnIdList.contains(dept.getDeptId()));
extra.put("createTime", dept.getCreateTime());
treeNode.setExtra(extra);
return treeNode;
})
.toList();
// 模糊查询 不组装树结构 直接返回 表格方便编辑
@@ -138,6 +143,7 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
/**
* 导出部门
*
* @return
*/
@Override
@@ -147,10 +153,10 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
DeptExcelVO deptExcelVo = new DeptExcelVO();
deptExcelVo.setName(item.getName());
Optional<String> first = this.list()
.stream()
.filter(it -> item.getParentId().equals(it.getDeptId()))
.map(SysDept::getName)
.findFirst();
.stream()
.filter(it -> item.getParentId().equals(it.getDeptId()))
.map(SysDept::getName)
.findFirst();
deptExcelVo.setParentName(first.orElse("根部门"));
deptExcelVo.setSortOrder(item.getSortOrder());
return deptExcelVo;
@@ -183,8 +189,7 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
sysDept.setParentId(one.getDeptId());
sysDept.setSortOrder(item.getSortOrder());
baseMapper.insert(sysDept);
}
else {
} else {
// 数据不合法情况
errorMessageList.add(new ErrorMessage(item.getLineNum(), errorMsg));
}
@@ -197,6 +202,7 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
/**
* 查询所有子节点 (包含当前节点)
*
* @param deptId 部门ID 目标部门ID
* @return ID
*/
@@ -216,37 +222,39 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
/**
* 获取部门负责人
*
* <p>
* 1. 根据dept 查询用户 2. 筛选用户列表中 post
*
* @param deptId deptId
* @return user id list
*/
@Override
public List<Long> listDeptLeader(Long deptId) {
List<SysUser> sysUserList = userMapper
.selectList(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getDeptId, deptId));
if (CollUtil.isEmpty(sysUserList)) {
List<SysUserDept> userDeptList = userDeptMapper.selectList(Wrappers.<SysUserDept>lambdaQuery().eq(SysUserDept::getDeptId, deptId));
if (CollUtil.isEmpty(userDeptList)) {
return null;
}
SysPost deptLeader = postMapper
.selectOne(Wrappers.<SysPost>lambdaQuery().eq(SysPost::getPostCode, PostCodeEnum.TEAM_LEADER.getCode()));
.selectOne(Wrappers.<SysPost>lambdaQuery().eq(SysPost::getPostCode, PostCodeEnum.TEAM_LEADER.getCode()));
if (deptLeader == null) {
return null;
}
List<Long> userIdList = sysUserList.stream().map(SysUser::getUserId).toList();
List<Long> userIdList = userDeptList.stream().map(SysUserDept::getUserId).toList();
return userPostMapper.selectList(Wrappers.<SysUserPost>lambdaQuery().in(SysUserPost::getUserId, userIdList))
.stream()
.filter(post -> Objects.equals(post.getPostId(), deptLeader.getPostId()))
.map(SysUserPost::getUserId)
.toList();
.stream()
.filter(post -> Objects.equals(post.getPostId(), deptLeader.getPostId()))
.map(SysUserPost::getUserId)
.toList();
}
/**
* 根据父部门ID和类型获取组织树
*
* @param parentDeptId 父部门ID
* @param type 类型
* @param type 类型
* @return 组织树信息
*/
public Map<String, Object> listOrgTree(Long parentDeptId, String type) {
@@ -267,15 +275,15 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
});
return Dict.create()
.set("roleList", orgs)
.set("childDepartments", orgs)
.set("employees", new ArrayList<>());
.set("roleList", orgs)
.set("childDepartments", orgs)
.set("employees", new ArrayList<>());
}
Dict dict = Dict.create()
.set("titleDepartments", new ArrayList<>())
.set("roleList", new ArrayList<>())
.set("employees", new ArrayList<>());
.set("titleDepartments", new ArrayList<>())
.set("roleList", new ArrayList<>())
.set("employees", new ArrayList<>());
List<SysDept> deptList = this.list(
Wrappers.<SysDept>lambdaQuery().eq(Objects.nonNull(parentDeptId), SysDept::getParentId, parentDeptId));
@@ -296,8 +304,12 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
List<OrgTreeVO> userVoList = new ArrayList<>();
List<SysUser> userList = userMapper
.selectList(Wrappers.<SysUser>lambdaQuery().in(SysUser::getDeptId, CollUtil.toList(parentDeptId)));
MPJLambdaWrapper<SysUser> userQuery = new MPJLambdaWrapper<>();
userQuery.selectAll(SysUser.class)
.leftJoin(SysUserDept.class, SysUserDept::getUserId, SysUser::getUserId)
.eq(SysUserDept::getDeptId, parentDeptId);
List<SysUser> userList = userMapper.selectJoinList(userQuery);
userList.forEach(user -> {
OrgTreeVO orgTreeVo = new OrgTreeVO();
@@ -322,28 +334,30 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
/**
* 模糊搜索用户
*
* @param username 用户名/拼音/首字母
* @return 匹配到的用户
*/
public List<OrgTreeVO> getOrgTreeUser(String username) {
return userMapper.selectList(Wrappers.<SysUser>lambdaQuery().like(SysUser::getUsername, username))
.stream()
.map(user -> {
OrgTreeVO orgTreeVo = new OrgTreeVO();
orgTreeVo.setId(user.getUserId());
orgTreeVo.setName(user.getUsername());
orgTreeVo.setType(OrgTypeEnum.USER.getType());
orgTreeVo.setSelected(false);
orgTreeVo.setAvatar(user.getAvatar());
return orgTreeVo;
})
.stream()
.map(user -> {
OrgTreeVO orgTreeVo = new OrgTreeVO();
orgTreeVo.setId(user.getUserId());
orgTreeVo.setName(user.getUsername());
orgTreeVo.setType(OrgTypeEnum.USER.getType());
orgTreeVo.setSelected(false);
orgTreeVo.setAvatar(user.getAvatar());
return orgTreeVo;
})
.toList();
}
/**
* 递归查询所有子节点。
*
* @param allDeptList 所有部门列表
* @param parentId 父部门ID
* @param parentId 父部门ID
* @param resDeptList 结果集合
*/
private void recursiveDept(List<SysDept> allDeptList, Long parentId, List<SysDept> resDeptList) {
@@ -35,6 +35,7 @@ import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.exception.ErrorCodes;
import com.pig4cloud.pigx.common.core.util.MsgUtils;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.excel.vo.ErrorMessage;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
@@ -61,13 +62,14 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
private SysRoleMenuService roleMenuService;
/**
* 通过用户ID,查询角色信息
* @param userId
* @return
* 通过用户ID查询角色信息
*
* @param userId 用户ID
* @return 角色信息列表
*/
@Override
public List findRolesByUserId(Long userId) {
return baseMapper.listRolesByUserId(userId);
return baseMapper.listRolesByUserId(userId, TenantContextHolder.getTenantId());
}
/**
@@ -22,11 +22,13 @@ import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.pig4cloud.pigx.admin.api.constant.TenantStateEnum;
import com.pig4cloud.pigx.admin.api.entity.*;
import com.pig4cloud.pigx.admin.config.ClientDetailsInitRunner;
import com.pig4cloud.pigx.admin.mapper.SysRoleMenuMapper;
import com.pig4cloud.pigx.admin.mapper.SysTenantMapper;
import com.pig4cloud.pigx.admin.mapper.SysTenantUserMapper;
import com.pig4cloud.pigx.admin.service.*;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
@@ -34,6 +36,7 @@ import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.data.datascope.DataScopeTypeEnum;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
@@ -84,6 +87,8 @@ public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant
private final SysDictService dictService;
private final SysTenantUserMapper tenantUserMapper;
private final RedisTemplate redisTemplate;
/**
@@ -153,7 +158,6 @@ public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant
user.setUsername(tenantDefault.getTenantDefaultUsername());
user.setPasswordModifyTime(LocalDateTime.now());
user.setPassword(ENCODER.encode(tenantDefault.getTenantDefaultPassword()));
user.setDeptId(dept.getDeptId());
userService.save(user);
// 构造普通用户角色
@@ -305,6 +309,20 @@ public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant
return Boolean.TRUE;
}
/**
* 获取用户所属租户列表
*
* @return 用户所属租户列表
*/
@Override
public List<SysTenant> getUserTenant() {
MPJLambdaWrapper<SysTenant> wrapper = new MPJLambdaWrapper<SysTenant>()
.selectAll(SysTenant.class)
.leftJoin(SysTenantUser.class, SysTenantUser::getTenantId, SysTenant::getId)
.eq(SysTenantUser::getUserId, SecurityUtils.getUser().getId());
return baseMapper.selectJoinList(wrapper);
}
/**
* 保存新的租户菜单,维护成新的菜单
*
@@ -36,9 +36,7 @@ import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.entity.*;
import com.pig4cloud.pigx.admin.api.vo.UserExcelVO;
import com.pig4cloud.pigx.admin.api.vo.UserVO;
import com.pig4cloud.pigx.admin.mapper.SysUserMapper;
import com.pig4cloud.pigx.admin.mapper.SysUserPostMapper;
import com.pig4cloud.pigx.admin.mapper.SysUserRoleMapper;
import com.pig4cloud.pigx.admin.mapper.*;
import com.pig4cloud.pigx.admin.service.*;
import com.pig4cloud.pigx.common.audit.annotation.Audit;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
@@ -49,6 +47,7 @@ import com.pig4cloud.pigx.common.core.util.MsgUtils;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.datascope.DataScope;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.common.data.tenant.TenantBroker;
import com.pig4cloud.pigx.common.excel.vo.ErrorMessage;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
@@ -92,7 +91,12 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
private final SysUserPostMapper sysUserPostMapper;
private final SysUserDeptMapper sysUserDeptMapper;
private final SysTenantUserMapper sysTenantUserMapper;
private final CacheManager cacheManager;
private final SysDeptMapper sysDeptMapper;
private final StringRedisTemplate redisTemplate;
@@ -141,14 +145,30 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
userRole.setRoleId(roleId);
return userRole;
}).forEach(sysUserRoleMapper::insert);
// 插入用户部门信息
if (Objects.nonNull(userDto.getDeptId())) {
SysUserDept sysUserDept = new SysUserDept();
sysUserDept.setUserId(sysUser.getUserId());
sysUserDept.setDeptId(userDto.getDeptId());
sysUserDeptMapper.insertOrUpdate(List.of(sysUserDept), (batchSqlSession, sysUserDept1) -> sysUserDeptMapper.exists(Wrappers.<SysUserDept>lambdaQuery()
.eq(SysUserDept::getDeptId, userDto.getDeptId())
.eq(SysUserDept::getUserId, sysUserDept1.getUserId())));
}
// 插入用户租户关系表
SysTenantUser sysTenantUser = new SysTenantUser();
sysTenantUser.setUserId(sysUser.getUserId());
sysTenantUser.setTenantId(SecurityUtils.getUser().getTenantId());
sysTenantUserMapper.insert(sysTenantUser);
return Boolean.TRUE;
}
/**
* 通过查用户的全部信息
* 查询用户全部信息,包括角色和权限
*
* @param sysUser 用户
* @return
* @param sysUser 用户信息
* @return 包含用户角色和权限的用户信息对象
*/
@Override
public UserInfo findUserInfo(SysUser sysUser) {
@@ -184,6 +204,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
*/
@Override
public IPage getUsersWithRolePage(Page page, UserDTO userDTO) {
userDTO.setTenantId(SecurityUtils.getUser().getTenantId());
return baseMapper.getUserVosPage(page, userDTO, DataScope.of());
}
@@ -214,7 +235,12 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
cache.evict(sysUser.getUsername());
}
// 删除用户角色
sysUserRoleMapper.delete(Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getUserId, CollUtil.toList(ids)));
sysUserDeptMapper.delete(Wrappers.<SysUserDept>lambdaQuery().in(SysUserDept::getUserId, CollUtil.toList(ids)));
sysUserPostMapper.delete(Wrappers.<SysUserPost>lambdaQuery().in(SysUserPost::getUserId, CollUtil.toList(ids)));
sysTenantUserMapper.delete(Wrappers.<SysTenantUser>lambdaQuery().in(SysTenantUser::getUserId, CollUtil.toList(ids))
.eq(SysTenantUser::getTenantId, SecurityUtils.getUser().getTenantId()));
this.removeBatchByIds(CollUtil.toList(ids));
return Boolean.TRUE;
}
@@ -273,6 +299,15 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
}).forEach(SysUserPost::insert);
}
// 更新用户部门表
if (Objects.nonNull(userDto.getDeptId())) {
sysUserDeptMapper.delete(Wrappers.<SysUserDept>lambdaQuery().eq(SysUserDept::getUserId, userDto.getUserId()));
SysUserDept sysUserDept = new SysUserDept();
sysUserDept.setUserId(sysUser.getUserId());
sysUserDept.setDeptId(userDto.getDeptId());
sysUserDept.insert();
}
return Boolean.TRUE;
}
@@ -286,13 +321,17 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public List<SysUser> listAncestorUsers(String username) {
SysUser sysUser = this.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
SysDept sysDept = sysDeptService.getById(sysUser.getDeptId());
if (sysDept == null) {
// 查询所属部门
List<SysDept> sysDeptList = sysDeptMapper.listDeptsByUserId(sysUser.getUserId(), sysUser.getTenantId());
if (CollUtil.isEmpty(sysDeptList)) {
return null;
}
Long parentId = sysDept.getParentId();
return this.list(Wrappers.<SysUser>query().lambda().eq(SysUser::getDeptId, parentId));
// 查询用户部门ID
List<Long> parentDeptList = sysDeptList.stream().map(SysDept::getParentId).toList();
List<Long> userIdList = sysUserDeptMapper.selectList(Wrappers.<SysUserDept>lambdaQuery().in(SysUserDept::getDeptId, parentDeptList))
.stream().map(SysUserDept::getUserId).toList();
return this.list(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, userIdList));
}
/**
@@ -305,21 +344,29 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Override
public List<UserExcelVO> listUser(UserDTO userDTO, Long[] ids) {
// 根据数据权限查询全部的用户信息
userDTO.setTenantId(SecurityUtils.getUser().getTenantId());
List<UserVO> voList = baseMapper.selectVoListByScope(userDTO, ids, DataScope.of());
// 转换成execl 对象输出
List<UserExcelVO> userExcelVOList = voList.stream().map(userVO -> {
UserExcelVO excelVO = new UserExcelVO();
BeanUtils.copyProperties(userVO, excelVO);
// 转换角色名称
String roleNameList = userVO.getRoleList()
.stream()
.map(SysRole::getRoleName)
.collect(Collectors.joining(StrUtil.COMMA));
excelVO.setRoleNameList(roleNameList);
// 转化岗位名称
String postNameList = userVO.getPostList()
.stream()
.map(SysPost::getPostName)
.collect(Collectors.joining(StrUtil.COMMA));
excelVO.setPostNameList(postNameList);
// 转化部门名称
String deptNameList = userVO.getDeptList().stream()
.map(SysDept::getName)
.collect(Collectors.joining(StrUtil.COMMA));
excelVO.setDeptNameList(deptNameList);
return excelVO;
}).toList();
return userExcelVOList;
@@ -356,10 +403,10 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 判断输入的部门名称列表是否合法
Optional<SysDept> deptOptional = deptList.stream()
.filter(dept -> excel.getDeptName().equals(dept.getName()))
.filter(dept -> excel.getDeptNameList().equals(dept.getName()))
.findFirst();
if (!deptOptional.isPresent()) {
errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptName()));
errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptNameList()));
}
// 判断输入的角色名称列表是否合法
@@ -423,6 +470,11 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
userDTO.setRole(roleIdList);
// 插入用户
this.saveUser(userDTO);
// 插入用户租户关系表
SysTenantUser sysTenantUser = new SysTenantUser();
sysTenantUser.setUserId(userDTO.getUserId());
sysTenantUser.setTenantId(SecurityUtils.getUser().getTenantId());
sysTenantUserMapper.insert(sysTenantUser);
}
/**
@@ -499,6 +551,21 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return R.ok();
}
/**
* 更新用户租户信息
*
* @param userDto 用户数据传输对象
* @return 更新结果
*/
@Override
@CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username")
public R updateUserTenant(UserDTO userDto) {
return R.ok(TenantBroker.noneAs(() -> this.update(Wrappers.<SysUser>lambdaUpdate()
.set(SysUser::getTenantId, userDto.getTenantId())
.eq(SysUser::getUserId, userDto.getUserId()))));
}
@Override
public R unbinding(String type) {
PigxUser user = SecurityUtils.getUser();
@@ -588,7 +655,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return R.failed("验证码错误");
}
String username = lambdaQuery().select(SysUser::getUsername).eq(SysUser::getPhone, userDto.getPhone()).one().getUsername();
String username = lambdaQuery().select(SysUser::getUsername).
eq(SysUser::getPhone, userDto.getPhone()).one().getUsername();
// 重置密码
String password = ENCODER.encode(userDto.getNewpassword1());
@@ -617,7 +685,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
*/
@Override
public List<SysUser> listUserIdByDeptIds(List<Long> deptIdList) {
return baseMapper.selectList(Wrappers.<SysUser>lambdaQuery().in(SysUser::getDeptId, deptIdList));
List<Long> userList = sysUserDeptMapper.selectList(Wrappers.<SysUserDept>lambdaQuery().in(SysUserDept::getDeptId, deptIdList))
.stream().map(SysUserDept::getUserId).toList();
return baseMapper.selectList(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, userList));
}
}
@@ -20,4 +20,15 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pig4cloud.pigx.admin.mapper.SysDeptMapper">
<select id="listDeptsByUserId" resultType="com.pig4cloud.pigx.admin.api.entity.SysDept">
SELECT sys_dept.dept_id,
sys_dept.name,
sys_dept.sort_order
FROM sys_dept,
sys_user_dept
WHERE sys_dept.dept_id = sys_user_dept.dept_id
AND sys_dept.del_flag = '0'
and sys_user_dept.user_id = #{userId}
and sys_user_dept.tenant_id = #{tenantId}
</select>
</mapper>
@@ -48,6 +48,7 @@
WHERE p.post_id = up.post_id
AND p.del_flag = '0'
and up.user_id = #{userId}
and up.tenant_id = #{tenantId}
</select>
</mapper>
@@ -49,5 +49,6 @@
WHERE sys_role.role_id = sys_user_role.role_id
AND sys_role.del_flag = '0'
and sys_user_role.user_id = #{userId}
and sys_user_role.tenant_id = #{tenantId}
</select>
</mapper>
@@ -1,22 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~
~ Copyright (c) 2018-2025, lengleng All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: lengleng (wangiegie@gmail.com)
~
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pig4cloud.pigx.admin.mapper.SysUserMapper">
@@ -28,203 +10,131 @@
<result column="salt" property="salt"/>
<result column="phone" property="phone"/>
<result column="avatar" property="avatar"/>
<result column="nickname" property="nickname"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="wx_openid" property="wxOpenid"/>
<result column="qq_openid" property="qqOpenid"/>
<result column="gitee_login" property="giteeOpenId"/>
<result column="osc_id" property="oscOpenId"/>
<result column="wx_cp_userid" property="wxCpUserid"/>
<result column="wx_ding_userid" property="wxDingUserid"/>
<result column="ucreate_time" property="createTime"/>
<result column="uupdate_time" property="updateTime"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="lock_flag" property="lockFlag"/>
<result column="password_expire_flag" property="passwordExpireFlag"/>
<result column="password_modify_time" property="passwordModifyTime"/>
<result column="del_flag" property="delFlag"/>
<result column="dept_id" property="deptId"/>
<result column="tenant_id" property="tenantId"/>
<result column="dept_name" property="deptName"/>
<result column="nickname" property="nickname"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<collection property="deptList" ofType="com.pig4cloud.pigx.admin.api.entity.SysDept"
select="com.pig4cloud.pigx.admin.mapper.SysDeptMapper.listDeptsByUserId"
column="{userId=user_id, tenantId=tenant_id}">
</collection>
<collection property="roleList" ofType="com.pig4cloud.pigx.admin.api.entity.SysRole"
select="com.pig4cloud.pigx.admin.mapper.SysRoleMapper.listRolesByUserId" column="user_id">
select="com.pig4cloud.pigx.admin.mapper.SysRoleMapper.listRolesByUserId"
column="{userId=user_id, tenantId=tenant_id}">
</collection>
<collection property="postList" ofType="com.pig4cloud.pigx.admin.api.entity.SysPost"
select="com.pig4cloud.pigx.admin.mapper.SysPostMapper.listPostsByUserId" column="user_id">
select="com.pig4cloud.pigx.admin.mapper.SysPostMapper.listPostsByUserId"
column="{userId=user_id, tenantId=tenant_id}">
</collection>
</resultMap>
<sql id="userRoleSql">
<sql id="userColumns">
u.user_id,
u.username,
u.password,
u.salt,
u.phone,
u.avatar,
u.wx_openid,
u.qq_openid,
u.gitee_login,
u.osc_id,
u.wx_cp_userid,
u.wx_ding_userid,
u.dept_id,
u.del_flag,
u.lock_flag,
u.password_expire_flag,
u.password_modify_time,
u.tenant_id,
u.create_by,
u.create_time ucreate_time,
u.update_time uupdate_time,
r.role_id,
r.role_name,
r.role_code,
r.role_desc,
r.create_time rcreate_time,
r.update_time rupdate_time
</sql>
<sql id="userRoleDeptSql">
u.user_id,
u.username,
u.password,
u.salt,
u.phone,
u.avatar,
u.wx_openid,
u.qq_openid,
u.gitee_login,
u.osc_id,
u.wx_cp_userid,
u.wx_ding_userid,
u.del_flag,
u.lock_flag,
u.password_expire_flag,
u.password_modify_time,
u.tenant_id,
u.nickname,
u.name,
u.nickname,
u.email,
u.wx_openid,
u.qq_openid,
u.gitee_login,
u.osc_id,
u.wx_cp_userid,
u.wx_ding_userid,
u.del_flag,
u.lock_flag,
u.password_expire_flag,
u.password_modify_time,
u.tenant_id,
u.create_by,
u.create_time ucreate_time,
u.update_time uupdate_time,
d.name dept_name,
d.dept_id
u.create_time,
u.update_time
</sql>
<select id="getUserVoByUsername" resultMap="baseResultMap">
SELECT
<include refid="userRoleSql"/>
<include refid="userColumns"/>
FROM
sys_user u
LEFT JOIN sys_user_role urole ON urole.user_id = u.user_id
LEFT JOIN sys_role r ON r.role_id = urole.role_id and r.del_flag = '0'
WHERE u.username = #{username} and u.del_flag = '0'
</select>
<select id="getUserVoById" resultMap="baseResultMap">
SELECT
<include refid="userRoleDeptSql"/>
<include refid="userColumns"/>
FROM
sys_user u
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id and d.del_flag = '0'
WHERE
u.user_id = #{id} and u.del_flag = '0'
</select>
<select id="getUserVosPage" resultMap="baseResultMap">
SELECT
u.user_id,
u.username,
u.password,
u.salt,
u.phone,
u.avatar,
u.wx_openid,
u.qq_openid,
u.gitee_login,
u.osc_id,
u.wx_cp_userid,
u.wx_ding_userid,
u.dept_id,
u.create_by,
u.create_time ucreate_time,
u.update_time uupdate_time,
u.del_flag,
u.lock_flag,
u.password_expire_flag,
u.password_modify_time,
u.tenant_id,
u.nickname,
u.name,
u.email,
d.name dept_name
<include refid="userColumns"/>
FROM
sys_user u
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
<where>
u.del_flag = '0'
INNER JOIN sys_user_dept ud ON u.user_id = ud.user_id
WHERE
ud.tenant_id = #{query.tenantId}
AND u.del_flag = '0'
<!-- 用户名模糊查询 -->
<if test="query.username != null and query.username != ''">
<bind name="usernameLike" value="'%'+query.username+'%'"/>
<bind name="usernameLike" value="'%' + query.username + '%'"/>
AND u.username LIKE #{usernameLike}
</if>
<!-- 部门精确查询 -->
<if test="query.deptId != null and query.deptId != ''">
AND u.dept_id = #{query.deptId}
AND ud.dept_id = #{query.deptId}
</if>
<!-- 手机号模糊查询 -->
<if test="query.phone != null and query.phone != ''">
<bind name="phoneLike" value="'%'+query.phone+'%'"/>
<bind name="phoneLike" value="'%' + query.phone + '%'"/>
AND u.phone LIKE #{phoneLike}
</if>
</where>
ORDER BY u.create_time DESC
ORDER BY
u.create_time DESC
</select>
<select id="selectVoListByScope" resultMap="baseResultMap">
SELECT
u.user_id,
u.username,
u.password,
u.salt,
u.phone,
u.avatar,
u.wx_openid,
u.qq_openid,
u.gitee_login,
u.osc_id,
u.wx_cp_userid,
u.wx_ding_userid,
u.dept_id,
u.create_by,
u.create_time ucreate_time,
u.update_time uupdate_time,
u.del_flag,
u.lock_flag,
u.password_expire_flag,
u.password_modify_time,
u.tenant_id,
u.nickname,
u.name,
u.email,
d.name dept_name
<include refid="userColumns"/>
FROM
sys_user u
LEFT JOIN sys_dept d ON d.dept_id = u.dept_id
<where>
u.del_flag = '0'
INNER JOIN sys_user_dept ud ON u.user_id = ud.user_id
WHERE
ud.tenant_id = #{query.tenantId}
AND u.del_flag = '0'
<!-- 用户名模糊查询 -->
<if test="query.username != null and query.username != ''">
<bind name="usernameLike" value="'%'+query.username+'%'"/>
<bind name="usernameLike" value="'%' + query.username + '%'"/>
AND u.username LIKE #{usernameLike}
</if>
<!-- 部门精确查询 -->
<if test="query.deptId != null and query.deptId != ''">
AND u.dept_id = #{query.deptId}
AND ud.dept_id = #{query.deptId}
</if>
<!-- 用户ID范围查询 -->
<if test="ids != null and ids.length > 0">
AND u.user_id in
<foreach collection="ids" index="index" item="id" open="(" separator="," close=")">
AND u.user_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
ORDER BY u.create_time DESC
ORDER BY
u.create_time DESC
</select>
</mapper>