1. 修复回滚父子角色功能时错误的代码逻辑,补全单元测试用例
2. 新增支持切换后业务菜单查询需限定只查询该公司业务数据能力
This commit is contained in:
@@ -38,4 +38,7 @@ public interface GlobalErrorCodeConstants {
|
||||
|
||||
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
||||
|
||||
// ========== 业务错误段 ==========
|
||||
// 用户未设置公司信息,无法办理当前业务
|
||||
ErrorCode USER_NOT_SET_DEPT = new ErrorCode(1000, "用户未设置有效的公司或部门信息,无法办理当前业务");
|
||||
}
|
||||
|
||||
@@ -49,6 +49,33 @@ public class CommonResult<T> implements Serializable {
|
||||
return error(result.getCode(), result.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的中间返回
|
||||
* @param data
|
||||
* @param code
|
||||
* @return
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> CommonResult<T> customize(T data, int code, String msg) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = code;
|
||||
result.data = data;
|
||||
result.msg = msg;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的中间返回
|
||||
*/
|
||||
public static <T> CommonResult<T> customize(T data, CommonResultCodeEnum commonResultCodeEnum) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = commonResultCodeEnum.getCode();
|
||||
result.data = data;
|
||||
result.msg = commonResultCodeEnum.getMessage();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.framework.common.pojo;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
|
||||
public enum CommonResultCodeEnum {
|
||||
// 成功
|
||||
SUCCESS(0, "成功"),
|
||||
|
||||
// 根据返回重试
|
||||
NEED_ADJUST(400, "需要根据返回二次重新请求"),
|
||||
|
||||
//
|
||||
ERROR(500, "错误");
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
CommonResultCodeEnum(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.framework.common.pojo;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Data
|
||||
public class CompanyDeptInfo {
|
||||
/**
|
||||
* 公司Id
|
||||
*/
|
||||
private Long companyId;
|
||||
/**
|
||||
* 公司名称
|
||||
*/
|
||||
private String companyName;
|
||||
/**
|
||||
* 部门Id
|
||||
*/
|
||||
private Long deptId;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
}
|
||||
@@ -11,20 +11,26 @@
|
||||
<artifactId>yudao-spring-boot-starter-biz-business</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId>
|
||||
<version>2.6.0-SNAPSHOT</version>
|
||||
<version>${revision}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.iocoder.yudao.framework.business.annotation;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*
|
||||
* 业务代码自动补全的注解,在 DO filed 中与 @TableField(fill = FieldFill.INSERT) 一起标注后自动新增时生成 Code chenbowen
|
||||
*/
|
||||
public @interface BusinessCode {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.framework.business.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.business.interceptor.BusinessHeaderInterceptor;
|
||||
import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@AutoConfiguration(after = YudaoWebAutoConfiguration.class)
|
||||
public class YudaoBusinessAutoConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 只拦截增删改和 set 相关的 url
|
||||
registry.addInterceptor(new BusinessHeaderInterceptor())
|
||||
.addPathPatterns("/**/add**", "/**/create**", "/**/update**", "/**/edit**", "/**/delete**", "/**/remove**", "/**/set**");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.framework.business.framework;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class BusinessDataPermissionConfiguration {
|
||||
@Bean
|
||||
public CompanyDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
|
||||
return rule -> {
|
||||
// companyId
|
||||
rule.addCompanyColumn("demo_contract", "company_id");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.iocoder.yudao.framework.business.interceptor;
|
||||
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
* 标记是否业务接口,如果是业务接口需要确定唯一的公司和部门信息才能放行
|
||||
*/
|
||||
public interface BusinessControllerMarker {
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.iocoder.yudao.framework.business.interceptor;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResultCodeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BusinessHeaderInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
Object bean = handlerMethod.getBean();
|
||||
if (!(bean instanceof BusinessControllerMarker)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
String companyId = request.getHeader("visit-company-id");
|
||||
String deptId = request.getHeader("visit-dept-id");
|
||||
LoginUser loginUser = Optional.ofNullable(getLoginUser()).orElse(new LoginUser().setInfo(new HashMap<>()));
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
Set<CompanyDeptInfo> companyDeptSet = JSONUtil.parseArray(loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream()
|
||||
.map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 1. 有 companyId
|
||||
if (companyId != null && !companyId.isBlank()) {
|
||||
Set<CompanyDeptInfo> companyDeptSetByCompanyId = companyDeptSet.stream().filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyId)).collect(Collectors.toSet());
|
||||
List<CompanyDeptInfo> filtered = companyDeptSetByCompanyId.stream()
|
||||
.filter(info -> String.valueOf(info.getCompanyId()).equals(companyId)).toList();
|
||||
if (filtered.isEmpty()) {
|
||||
// 当前公司下没有部门
|
||||
CompanyDeptInfo data = new CompanyDeptInfo();
|
||||
data.setCompanyId(Long.valueOf(companyId));
|
||||
data.setDeptId(0L);
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(singleton(data), CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
// 如果有 deptId,校验其是否属于该 companyId
|
||||
if (deptId != null) {
|
||||
boolean valid = filtered.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptId));
|
||||
if (!valid) {
|
||||
return writeResponse(response, HttpStatus.BAD_REQUEST.value(), CommonResult.customize(null, CommonResultCodeEnum.ERROR.getCode(),"当前用户匹配部门不属于此公司"), objectMapper);
|
||||
}else{
|
||||
// 部门存在,放行
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (filtered.size() == 1) {
|
||||
// 唯一部门
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
} else {
|
||||
// 多个部门
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
}
|
||||
// 2. 没有公司信息,尝试唯一性自动推断
|
||||
// 如果当前用户下只有一个公司和部门的对于关系
|
||||
if (companyDeptSet.size() == 1) {
|
||||
CompanyDeptInfo info = new CompanyDeptInfo();
|
||||
CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next();
|
||||
info.setCompanyId(companyDeptInfo.getCompanyId());
|
||||
info.setDeptId(companyDeptInfo.getDeptId());
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.success(singleton(info)), objectMapper);
|
||||
} else {
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(companyDeptSet,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean writeResponse(HttpServletResponse response, int status, CommonResult<?> result, ObjectMapper objectMapper) throws Exception {
|
||||
response.setStatus(status);
|
||||
response.getWriter().write(objectMapper.writeValueAsString(result));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.iocoder.yudao.framework.business.config.YudaoBusinessAutoConfiguration
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.config;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于部门的数据权限 AutoConfiguration
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(LoginUser.class)
|
||||
@ConditionalOnBean(value = {CompanyDataPermissionRuleCustomizer.class})
|
||||
public class YudaoCompanyDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public CompanyDataPermissionRule companyDataPermissionRule(List<CompanyDataPermissionRuleCustomizer> customizers) {
|
||||
|
||||
// 创建 CompanyDataPermissionRule 对象
|
||||
CompanyDataPermissionRule rule = new CompanyDataPermissionRule();
|
||||
// 补全表配置
|
||||
customizers.forEach(customizer -> customizer.customize(rule));
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ import java.util.List;
|
||||
public class YudaoDeptDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi,
|
||||
List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi, List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
// Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用
|
||||
// 原因:在创建租户时,租户还没创建好,导致 Feign 调用获取数据权限时,报“租户不存在”的错误
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserCompanyId;
|
||||
|
||||
/**
|
||||
* 基于公司类型部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||
* 注意,使用 CompanyDataPermissionRule 时,需要保证表中有 company_id 公司编号的字段,可自定义。
|
||||
*
|
||||
* @author chenbowen
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class CompanyDataPermissionRule implements DataPermissionRule {
|
||||
static final Expression EXPRESSION_NULL = new NullValue();
|
||||
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
private final Map<String, String> companyColumns = new HashMap<>();
|
||||
/**
|
||||
* 所有表名,是 {@link #companyColumns} 的合集
|
||||
*/
|
||||
private final Set<String> TABLE_NAMES = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Set<String> getTableNames() {
|
||||
return TABLE_NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||
// 业务拼接 Company 的条件
|
||||
if (getLoginUserCompanyId() == null) {
|
||||
// 如果没有登录用户的公司编号,则不需要拼接条件
|
||||
return null;
|
||||
}
|
||||
Expression companyExpression = buildCompanyExpression(tableName, tableAlias, Collections.singleton(getLoginUserCompanyId()));
|
||||
return Objects.requireNonNullElse(companyExpression, EXPRESSION_NULL);
|
||||
}
|
||||
|
||||
private Expression buildCompanyExpression(String tableName, Alias tableAlias, Set<Long> companyIds) {
|
||||
// 如果不存在配置,则无需作为条件
|
||||
String columnName = companyColumns.get(tableName);
|
||||
if (StrUtil.isEmpty(columnName)) {
|
||||
return null;
|
||||
}
|
||||
// 如果为空,则无条件
|
||||
if (CollUtil.isEmpty(companyIds)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接条件
|
||||
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
||||
// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
|
||||
new ParenthesedExpressionList<>(new ExpressionList<>(CollectionUtils.convertList(companyIds, LongValue::new))));
|
||||
}
|
||||
|
||||
public void addCompanyColumn(String tableName, String columnName) {
|
||||
companyColumns.put(tableName, columnName);
|
||||
TABLE_NAMES.add(tableName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||
|
||||
/**
|
||||
* {@link DeptDataPermissionRule} 的自定义配置接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CompanyDataPermissionRuleCustomizer {
|
||||
|
||||
/**
|
||||
* 自定义该权限规则
|
||||
* 1. 调用 {@link CompanyDataPermissionRule#addCompanyColumn(Class, String)} 方法,配置基于 dept_id 的过滤规则
|
||||
*
|
||||
* @param rule 权限规则
|
||||
*/
|
||||
void customize(CompanyDataPermissionRule rule);
|
||||
|
||||
}
|
||||
@@ -121,6 +121,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
// 添加到上下文中,避免重复计算
|
||||
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
|
||||
}
|
||||
// 如果不归属任何部门,且无可查询所有部门的权限,提示当前用户未关联任何部门
|
||||
// if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) && !deptDataPermission.getAll()) {
|
||||
// log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 未关联任何部门]", JsonUtils.toJsonString(loginUser), tableName, tableAlias.getName(), JsonUtils.toJsonString(deptDataPermission));
|
||||
// throw new NullPointerException("当前登录用户未关联任何部门,请先联系管理员进行部门关联");
|
||||
// }
|
||||
// 如果开启了公司上下文,且缓存的公司编号不等于 CompanyContextHolder 的公司编号,则更新缓存
|
||||
if(!CompanyContextHolder.isIgnore()) {
|
||||
Long companyId = CompanyContextHolder.getCompanyId();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoCompanyDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionRpcAutoConfiguration
|
||||
|
||||
@@ -99,6 +99,10 @@
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.framework.business.core.db;
|
||||
package cn.iocoder.yudao.framework.mybatis.core.dataobject;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
@@ -12,7 +11,7 @@ import org.apache.ibatis.type.JdbcType;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BusinessBaseDO extends TenantBaseDO {
|
||||
public class BusinessBaseDO extends BaseDO {
|
||||
|
||||
/** 公司编号 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@@ -29,6 +28,10 @@ public class BusinessBaseDO extends TenantBaseDO {
|
||||
/** 岗位编号 */
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private Long postId;
|
||||
/**
|
||||
* 多租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 清除 creator、createTime、updateTime、updater 等字段,避免前端直接传递这些字段,导致被更新
|
||||
@@ -44,3 +47,4 @@ public class BusinessBaseDO extends TenantBaseDO {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BusinessBaseDO;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.USER_NOT_SET_DEPT;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
|
||||
|
||||
/**
|
||||
* 通用参数填充实现类
|
||||
*
|
||||
* 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
|
||||
*
|
||||
* @author hexiaowu
|
||||
@@ -41,6 +52,30 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
baseDO.setUpdater(userId.toString());
|
||||
}
|
||||
}
|
||||
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) {
|
||||
// 公司编号、公司名称、部门编号、部门名称、岗位编号等字段,默认不填充
|
||||
// 需要在业务层手动设置
|
||||
LoginUser loginUser = getLoginUser();
|
||||
Long visitCompanyId = loginUser.getVisitCompanyId();
|
||||
Long visitDeptId = loginUser.getVisitDeptId();
|
||||
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS,"[]");
|
||||
// 更加合理的写法
|
||||
Set<Long> postIds = new HashSet<>(JSONUtil.parseArray(
|
||||
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS, "[]")
|
||||
).toList(Long.class));
|
||||
// 如果 visitCompanyId 不存在,不能进行业务办理
|
||||
if (Objects.isNull(visitCompanyId) || Objects.isNull(visitDeptId)) {
|
||||
throw exception(USER_NOT_SET_DEPT);
|
||||
}
|
||||
businessBaseDO.setCompanyId(visitCompanyId);
|
||||
businessBaseDO.setCompanyName(loginUser.getVisitCompanyName());
|
||||
businessBaseDO.setDeptId(visitDeptId);
|
||||
businessBaseDO.setDeptName(loginUser.getVisitDeptName());
|
||||
// 暂时没有具体业务要求,岗位默认当前用户第一个 todo chenbowen
|
||||
businessBaseDO.setPostId(postIds.isEmpty() ? 0L : postIds.iterator().next());
|
||||
// 多租户编号,默认不填充
|
||||
businessBaseDO.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,14 @@ public class LoginUser {
|
||||
|
||||
public static final String INFO_KEY_NICKNAME = "nickname";
|
||||
public static final String INFO_KEY_TENANT_ID = "tenantId";
|
||||
// 用户关联的公司 Id
|
||||
public static final String INFO_KEY_COMPANY_IDS = "companyIds";
|
||||
// 用户关联的部门 Id
|
||||
public static final String INFO_KEY_DEPT_IDS = "deptIds";
|
||||
// 用户关联的公司与部门关联关系
|
||||
public static final String INFO_KEY_COMPANY_DEPT_SET = "companyDeptSet";
|
||||
// 用户关联的岗位信息
|
||||
public static final String INFO_KEY_POST_IDS = "postIds";
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
|
||||
@@ -82,6 +82,17 @@ public class SecurityFrameworkUtils {
|
||||
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户访问的公司 Id,从上下文中
|
||||
*
|
||||
* @return 用户编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserCompanyId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? loginUser.getVisitCompanyId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的编号,从上下文中
|
||||
*
|
||||
|
||||
@@ -29,6 +29,8 @@ public class WebFrameworkUtils {
|
||||
public static final String HEADER_VISIT_TENANT_ID = "visit-tenant-id";
|
||||
public static final String HEADER_VISIT_COMPANY_ID = "visit-company-id";
|
||||
public static final String HEADER_VISIT_COMPANY_NAME = "visit-company-name";
|
||||
public static final String HEADER_VISIT_DEPT_ID = "visit-dept-id";
|
||||
public static final String HEADER_VISIT_DEPT_NAME = "visit-dept-name";
|
||||
|
||||
/**
|
||||
* 终端的 Header
|
||||
@@ -199,13 +201,22 @@ public class WebFrameworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的公司名称,从 header 中
|
||||
* 获得访问的公司名称,从 header 中,并进行 URL 解码
|
||||
* @param request 请求
|
||||
* @return 公司名称,解析失败或无效时返回空字符串
|
||||
*/
|
||||
public static String getCompanyName(HttpServletRequest request) {
|
||||
String companyName = request.getHeader(HEADER_VISIT_COMPANY_NAME);
|
||||
return StrUtil.isBlank(companyName) ? StrUtil.EMPTY : companyName;
|
||||
if (StrUtil.isBlank(companyName)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
try {
|
||||
// URL 解码
|
||||
return java.net.URLDecoder.decode(companyName, java.nio.charset.StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
// 解码失败,返回原始值
|
||||
return companyName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +225,7 @@ public class WebFrameworkUtils {
|
||||
* @return 部门编号,解析失败或无效时返回 0
|
||||
*/
|
||||
public static Long getDeptId(HttpServletRequest request) {
|
||||
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_TENANT_ID);
|
||||
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_DEPT_ID);
|
||||
if (StrUtil.isBlank(deptIdHeader)) {
|
||||
return 0L;
|
||||
}
|
||||
@@ -228,12 +239,21 @@ public class WebFrameworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的部门名称,从 header 中
|
||||
* 获得访问的部门名称,从 header 中,并进行 URL 解码
|
||||
* @param request 请求
|
||||
* @return 部门名称,解析失败或无效时返回空字符串
|
||||
*/
|
||||
public static String getDeptName(HttpServletRequest request) {
|
||||
String deptName = request.getHeader(WebFrameworkUtils.HEADER_VISIT_COMPANY_NAME);
|
||||
return StrUtil.isBlank(deptName) ? StrUtil.EMPTY : deptName;
|
||||
String deptName = request.getHeader(WebFrameworkUtils.HEADER_VISIT_DEPT_NAME);
|
||||
if (StrUtil.isBlank(deptName)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
try {
|
||||
// URL 解码
|
||||
return java.net.URLDecoder.decode(deptName, java.nio.charset.StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
// 解码失败,返回原始值
|
||||
return deptName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ spring:
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ xxl:
|
||||
job:
|
||||
enabled: false # 是否开启调度中心,默认为 true 开启
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ xxl:
|
||||
job:
|
||||
enabled: false # 是否开启调度中心,默认为 true 开启
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
* System 错误码枚举类
|
||||
*
|
||||
* system 系统,使用 1-002-000-000 段
|
||||
* @author chenbowen
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
@@ -33,8 +34,9 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能操作类型为系统内置的角色");
|
||||
ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用");
|
||||
ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用");
|
||||
|
||||
ErrorCode ROLE_CAN_NOT_UPDATE_NORMAL_TYPE_ROLE = new ErrorCode(1_002_002_006, "不能操作类型为标准的角色,除非是管理员角色");
|
||||
ErrorCode ROLE_CAN_NOT_DELETE_HAS_CHILDREN = new ErrorCode(1_002_002_007, " 角色【{}】存在子角色,不允许删除");
|
||||
ErrorCode ROLE_PARENT_IS_CHILD = new ErrorCode(1_002_002_008, "不能设置自己的子角色为父角色");
|
||||
|
||||
// ========== 用户模块 1-002-003-000 ==========
|
||||
ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在");
|
||||
|
||||
@@ -25,11 +25,15 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Tag(name = "管理后台 - 角色")
|
||||
@RestController
|
||||
@RequestMapping("/system/role")
|
||||
@@ -43,7 +47,7 @@ public class RoleController {
|
||||
@Operation(summary = "创建角色")
|
||||
@PreAuthorize("@ss.hasPermission('system:role:create')")
|
||||
public CommonResult<Long> createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) {
|
||||
return success(roleService.createRole(createReqVO, null));
|
||||
return success(roleService.createRole(createReqVO, createReqVO.getType() == null ? null : Integer.valueOf(createReqVO.getType())));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@@ -76,6 +80,20 @@ public class RoleController {
|
||||
@PreAuthorize("@ss.hasPermission('system:role:query')")
|
||||
public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
|
||||
PageResult<RoleDO> pageResult = roleService.getRolePage(pageReqVO);
|
||||
// 获取所有父级角色信息
|
||||
List<Long> parentIds = pageResult.getList().stream().filter(role -> role.getParentId() != null && role.getParentId() > 0)
|
||||
.map(RoleDO::getParentId)
|
||||
.distinct()
|
||||
.toList();
|
||||
List<RoleDO> parentRoles = roleService.getRoleList(parentIds);
|
||||
// 将父级角色信息转换为 id 与 name 的 Map
|
||||
var parentRoleMap = parentRoles.stream().collect(Collectors.toMap(RoleDO::getId, RoleDO::getName, (v1, v2) -> v1));
|
||||
// 补全父级角色名称
|
||||
pageResult.getList().forEach(role -> {
|
||||
if (role.getParentId() != null && role.getParentId() > 0) {
|
||||
role.setParentName(parentRoleMap.get(role.getParentId()));
|
||||
}
|
||||
});
|
||||
return success(BeanUtils.toBean(pageResult, RoleRespVO.class));
|
||||
}
|
||||
|
||||
@@ -87,6 +105,16 @@ public class RoleController {
|
||||
return success(BeanUtils.toBean(list, RoleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping({"/list-all-extend-simple", "/simple-extend-list"})
|
||||
@Operation(summary = "获取所有可继承角色精简信息列表", description = "只包含被开启的角色,主要用于前端的下拉选项")
|
||||
public CommonResult<List<RoleRespVO>> getParentSimpleRoleList() {
|
||||
List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 过滤掉系统内置角色(如有需要)
|
||||
list.removeIf(role -> role.getType() != null && role.getType().equals(1));
|
||||
list.sort(Comparator.comparing(RoleDO::getSort));
|
||||
return success(BeanUtils.toBean(list, RoleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出角色 Excel")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
|
||||
@@ -12,6 +12,9 @@ import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Schema(description = "管理后台 - 角色信息 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@@ -56,4 +59,11 @@ public class RoleRespVO {
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "父级角色名称", example = "1")
|
||||
@ExcelProperty("父级角色名称")
|
||||
private String parentName;
|
||||
|
||||
@Schema(description = "父级角色 Id", example = "1")
|
||||
@ExcelProperty("父级角色 Id")
|
||||
private Long parentId;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class RoleDO extends TenantBaseDO {
|
||||
private String name;
|
||||
/**
|
||||
* 角色标识
|
||||
*
|
||||
* 枚举
|
||||
*/
|
||||
private String code;
|
||||
@@ -43,13 +42,11 @@ public class RoleDO extends TenantBaseDO {
|
||||
private Integer sort;
|
||||
/**
|
||||
* 角色状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 角色类型
|
||||
*
|
||||
* 枚举 {@link RoleTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
@@ -60,16 +57,27 @@ public class RoleDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 数据范围
|
||||
*
|
||||
* 枚举 {@link DataScopeEnum}
|
||||
*/
|
||||
private Integer dataScope;
|
||||
/**
|
||||
* 数据范围(指定部门数组)
|
||||
*
|
||||
* 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Set<Long> dataScopeDeptIds;
|
||||
|
||||
/**
|
||||
* 父级标准角色 Id : 继承的标准角色Id,系统角色为 -1、标准角色为 0
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 父级角色名称
|
||||
* 仅用于前端角色界面展示
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String parentName;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 角色菜单剔除 DO
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
@TableName("system_role_menu_exclusion")
|
||||
@KeySequence("system_role_menu_exclusion_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleMenuExclusionDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
private Long menuId;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.system.dal.dataobject.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
@@ -53,6 +54,16 @@ public class AdminUserDO extends TenantBaseDO {
|
||||
*/
|
||||
@TableField(exist = false, typeHandler = JacksonTypeHandler.class )
|
||||
private Set<Long> deptIds;
|
||||
/**
|
||||
* 公司 ID 列表
|
||||
*/
|
||||
@TableField(exist = false, typeHandler = JacksonTypeHandler.class )
|
||||
private Set<Long> companyIds;
|
||||
/**
|
||||
* 公司与部门关系列表
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Set<CompanyDeptInfo> companyDeptInfos;
|
||||
/**
|
||||
* 岗位编号数组
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,9 @@ import org.springframework.lang.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Mapper
|
||||
public interface RoleMapper extends BaseMapperX<RoleDO> {
|
||||
|
||||
@@ -36,4 +39,8 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
|
||||
return selectList(RoleDO::getStatus, statuses);
|
||||
}
|
||||
|
||||
default long selectCountByParentId(Long parentId) {
|
||||
return selectCount(RoleDO::getParentId, parentId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色菜单剔除 Mapper
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
@Mapper
|
||||
public interface RoleMenuExclusionMapper extends BaseMapperX<RoleMenuExclusionDO> {
|
||||
|
||||
/**
|
||||
* 根据角色编号,查询角色菜单剔除列表
|
||||
*
|
||||
* @param roleIds 角色编号
|
||||
*/
|
||||
default List<RoleMenuExclusionDO> selectMenuIdListByRoleId(Collection<Long> roleIds) {
|
||||
return selectList(RoleMenuExclusionDO::getRoleId, roleIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色编号,菜单编号,删除角色菜单剔除列表
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @param menuIds 菜单编号
|
||||
*/
|
||||
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
|
||||
delete(new LambdaQueryWrapper<RoleMenuExclusionDO>()
|
||||
.eq(RoleMenuExclusionDO::getRoleId, roleId)
|
||||
.in(RoleMenuExclusionDO::getMenuId, menuIds));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.service.dept;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
|
||||
@@ -115,4 +116,6 @@ public interface DeptService {
|
||||
void validateDeptList(Collection<Long> ids);
|
||||
|
||||
List<DeptDO> getUserCompanyList();
|
||||
|
||||
Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.dept;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||
@@ -263,4 +264,43 @@ public class DeptServiceImpl implements DeptService {
|
||||
return getDeptList(companyIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID查询其归属公司及直属部门关系列表(不递归下级公司)
|
||||
*/
|
||||
@Override
|
||||
public Set<CompanyDeptInfo> getCompanyDeptInfoListByUserId(Long userId) {
|
||||
// 查询用户所属部门
|
||||
Set<Long> deptIds = userDeptMapper.selectValidListByUserIds(singleton(userId))
|
||||
.stream()
|
||||
.map(UserDeptDO::getDeptId)
|
||||
.collect(Collectors.toSet());
|
||||
if (CollUtil.isEmpty(deptIds)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// 查询所有部门信息
|
||||
Map<Long, DeptDO> deptMap = getDeptList(deptIds).stream()
|
||||
.collect(Collectors.toMap(DeptDO::getId, d -> d));
|
||||
Set<CompanyDeptInfo> result = new HashSet<>();
|
||||
for (Long deptId : deptIds) {
|
||||
DeptDO dept = deptMap.get(deptId);
|
||||
if (dept == null) continue;
|
||||
// 向上查找公司,如果到达顶层(parentId为PARENT_ID_ROOT)还没找到公司,则用顶层部门作为公司
|
||||
DeptDO company = dept;
|
||||
while (company != null && !Boolean.TRUE.equals(company.getIsCompany())) {
|
||||
if (company.getParentId() == null || DeptDO.PARENT_ID_ROOT.equals(company.getParentId())) {
|
||||
break;
|
||||
}
|
||||
company = getDept(company.getParentId());
|
||||
}
|
||||
if (company == null) continue;
|
||||
CompanyDeptInfo info = new CompanyDeptInfo();
|
||||
info.setCompanyId(company.getId());
|
||||
info.setCompanyName(company.getName());
|
||||
info.setDeptId(dept.getId());
|
||||
info.setDeptName(dept.getName());
|
||||
result.add(info);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
@@ -199,7 +200,12 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
|
||||
if (userType.equals(UserTypeEnum.ADMIN.getValue())) {
|
||||
AdminUserDO user = adminUserService.getUser(userId);
|
||||
return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())
|
||||
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()).build();
|
||||
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString())
|
||||
.put(LoginUser.INFO_KEY_COMPANY_IDS, CollUtil.isEmpty(user.getCompanyIds()) ? "[]" : JsonUtils.toJsonString(user.getCompanyIds()))
|
||||
.put(LoginUser.INFO_KEY_DEPT_IDS, CollUtil.isEmpty(user.getDeptIds()) ? "[]" : JsonUtils.toJsonString(user.getDeptIds()))
|
||||
.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, CollUtil.isEmpty(user.getCompanyDeptInfos()) ? "[]" : JsonUtils.toJsonString(user.getCompanyDeptInfos()))
|
||||
.put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds()))
|
||||
.build();
|
||||
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {
|
||||
// 注意:目前 Member 暂时不读取,可以按需实现
|
||||
return Collections.emptyMap();
|
||||
|
||||
@@ -12,9 +12,11 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.userdept.UserDeptDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
||||
@@ -69,6 +71,8 @@ public class PermissionServiceImpl implements PermissionService {
|
||||
@Resource
|
||||
private AdminUserService userService;
|
||||
@Resource
|
||||
private RoleMenuExclusionMapper roleMenuExclusionMapper;
|
||||
@Resource
|
||||
private UserDeptService userDeptService;
|
||||
@Autowired
|
||||
private PermissionService permissionService;
|
||||
@@ -210,8 +214,14 @@ public class PermissionServiceImpl implements PermissionService {
|
||||
if (roleService.hasAnySuperAdmin(roleIds)) {
|
||||
return convertSet(menuService.getMenuList(), MenuDO::getId);
|
||||
}
|
||||
// 如果是非管理员的情况下,获得拥有的菜单编号
|
||||
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
|
||||
// 递归获取所有父角色id
|
||||
Set<Long> allRoleIds = roleService.getAllParentAndSelfRoleIds(roleIds);
|
||||
// 如果是非管理员的情况下,获得拥有的菜单编号(含父角色,需要剔除当前角色排除的菜单)
|
||||
Set<Long> menuIds = convertSet(roleMenuMapper.selectListByRoleId(allRoleIds), RoleMenuDO::getMenuId);
|
||||
// 排除当前角色排除的菜单编号
|
||||
Set<Long> excludeMenuIds = convertSet(roleMenuExclusionMapper.selectMenuIdListByRoleId(allRoleIds), RoleMenuExclusionDO::getMenuId);
|
||||
menuIds.removeAll(excludeMenuIds);
|
||||
return menuIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -123,4 +123,12 @@ public interface RoleService {
|
||||
*/
|
||||
void validateRoleList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获取所有父角色id(递归)
|
||||
* @param roleIds 当前角色id集合
|
||||
* @return 包含自身和所有父级的id集合
|
||||
*/
|
||||
Set<Long> getAllParentAndSelfRoleIds(Collection<Long> roleIds);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.mzt.logapi.context.LogRecordContext;
|
||||
import com.mzt.logapi.service.impl.DiffParseFunction;
|
||||
import com.mzt.logapi.starter.annotation.LogRecord;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
@@ -69,8 +71,11 @@ public class RoleServiceImpl implements RoleService {
|
||||
// 2. 插入到数据库
|
||||
RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class)
|
||||
.setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType()))
|
||||
// 如果类型不为公司角色则设置 parentId 为 0
|
||||
.setParentId(!ObjectUtil.equal(RoleTypeEnum.CUSTOM.getType(), type) ? 0L : createReqVO.getParentId())
|
||||
.setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus()))
|
||||
.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
|
||||
// 默认可查看所有数据。原因是,可能一些项目不需要项目权限
|
||||
.setDataScope(DataScopeEnum.ALL.getScope());
|
||||
roleMapper.insert(role);
|
||||
|
||||
// 3. 记录操作日志上下文
|
||||
@@ -87,6 +92,11 @@ public class RoleServiceImpl implements RoleService {
|
||||
RoleDO role = validateRoleForUpdate(updateReqVO.getId());
|
||||
// 1.2 校验角色的唯一字段是否重复
|
||||
validateRoleDuplicate(updateReqVO.getName(), updateReqVO.getCode(), updateReqVO.getId());
|
||||
// 1.3 校验角色当前修改的父角色是否为当前角色的子角色
|
||||
if (updateReqVO.getParentId() != null && !updateReqVO.getParentId().equals(0L) && isChildRole(updateReqVO.getId(), updateReqVO.getParentId())) {
|
||||
throw exception(ROLE_PARENT_IS_CHILD, updateReqVO.getName());
|
||||
}
|
||||
|
||||
|
||||
// 2. 更新到数据库
|
||||
RoleDO updateObj = BeanUtils.toBean(updateReqVO, RoleDO.class);
|
||||
@@ -112,6 +122,7 @@ public class RoleServiceImpl implements RoleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
|
||||
@LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
|
||||
@@ -120,6 +131,11 @@ public class RoleServiceImpl implements RoleService {
|
||||
// 1. 校验是否可以更新
|
||||
RoleDO role = validateRoleForUpdate(id);
|
||||
|
||||
// 1.1 校验角色是否存在子角色,如果存在,则不允许删除
|
||||
if (roleMapper.selectCountByParentId(id) > 0) {
|
||||
throw exception(ROLE_CAN_NOT_DELETE_HAS_CHILDREN , role.getName());
|
||||
}
|
||||
|
||||
// 2.1 标记删除
|
||||
roleMapper.deleteById(id);
|
||||
// 2.2 删除相关数据
|
||||
@@ -214,7 +230,7 @@ public class RoleServiceImpl implements RoleService {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return roleMapper.selectBatchIds(ids);
|
||||
return roleMapper.selectByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -262,7 +278,7 @@ public class RoleServiceImpl implements RoleService {
|
||||
return;
|
||||
}
|
||||
// 获得角色信息
|
||||
List<RoleDO> roles = roleMapper.selectBatchIds(ids);
|
||||
List<RoleDO> roles = roleMapper.selectByIds(ids);
|
||||
Map<Long, RoleDO> roleMap = convertMap(roles, RoleDO::getId);
|
||||
// 校验
|
||||
ids.forEach(id -> {
|
||||
@@ -276,6 +292,29 @@ public class RoleServiceImpl implements RoleService {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getAllParentAndSelfRoleIds(Collection<Long> roleIds) {
|
||||
// 递归获取所有父角色id,最多递归5层,防止环
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
RoleServiceImpl self = getSelf();
|
||||
return roleIds.stream()
|
||||
.flatMap(id -> {
|
||||
Set<Long> chain = new LinkedHashSet<>();
|
||||
Long current = id;
|
||||
for (int depth = 0; current != null && current > 0 && depth < 5 && chain.add(current); depth++) {
|
||||
RoleDO role = self.getRoleFromCache(current);
|
||||
if (role == null || role.getParentId() == null || role.getParentId() <= 0) {
|
||||
break;
|
||||
}
|
||||
current = role.getParentId();
|
||||
}
|
||||
return chain.stream();
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
@@ -285,4 +324,24 @@ public class RoleServiceImpl implements RoleService {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 parentId 是否为 roleId 的子孙节点,递归最多5次
|
||||
*/
|
||||
public boolean isChildRole(Long parentId, Long id) {
|
||||
return isChildRole(parentId, id, 0);
|
||||
}
|
||||
public boolean isChildRole(Long parentId, Long id, int depth) {
|
||||
if (parentId.equals(id)) {
|
||||
return true;
|
||||
}
|
||||
if (depth >= 5) {
|
||||
return false;
|
||||
}
|
||||
RoleDO parent = roleMapper.selectById(id);
|
||||
if (parent == null || parent.getParentId() == null || parent.getParentId().equals(0L) || parent.getParentId().equals(-1L)) {
|
||||
return false;
|
||||
}
|
||||
return isChildRole(parentId, parent.getParentId(), depth + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
@@ -284,9 +286,11 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Override
|
||||
public AdminUserDO getUser(Long id) {
|
||||
AdminUserDO adminUserDO = userMapper.selectListByIds(singleton(id)).stream().findFirst().orElseThrow(() -> exception(USER_NOT_EXISTS));
|
||||
// 查询用户关联的部门编号
|
||||
List<UserDeptDO> userDeptList = userDeptService.getValidUserDeptListByUserIds(singleton(id));
|
||||
adminUserDO.setDeptIds(convertSet(userDeptList, UserDeptDO::getDeptId));
|
||||
Set<CompanyDeptInfo> companyDeptInfoListByUserId = deptService.getCompanyDeptInfoListByUserId(id);
|
||||
adminUserDO.setDeptIds(companyDeptInfoListByUserId.stream().map(CompanyDeptInfo::getDeptId).collect(Collectors.toSet()));
|
||||
adminUserDO.setCompanyIds(companyDeptInfoListByUserId.stream().map(CompanyDeptInfo::getCompanyId).collect(Collectors.toSet()));
|
||||
adminUserDO.setCompanyDeptInfos(companyDeptInfoListByUserId);
|
||||
// 设置用户的部门名称集合
|
||||
return adminUserDO;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ xxl:
|
||||
job:
|
||||
enabled: false # 是否开启调度中心,默认为 true 开启
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
|
||||
assertPojoEquals(accessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted");
|
||||
assertEquals(userId, accessTokenDO.getUserId());
|
||||
assertEquals(userType, accessTokenDO.getUserType());
|
||||
assertEquals(2, accessTokenDO.getUserInfo().size());
|
||||
assertEquals(6, accessTokenDO.getUserInfo().size());
|
||||
assertEquals(user.getNickname(), accessTokenDO.getUserInfo().get("nickname"));
|
||||
assertEquals(clientId, accessTokenDO.getClientId());
|
||||
assertEquals(scopes, accessTokenDO.getScopes());
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package cn.iocoder.yudao.module.system.service.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.rolemenuexclusion.RoleMenuExclusionDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
@@ -17,6 +21,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -25,6 +30,9 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Import({PermissionServiceImpl.class, RoleServiceImpl.class})
|
||||
public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
@@ -35,6 +43,8 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
@Resource
|
||||
private RoleMenuMapper roleMenuMapper;
|
||||
@Resource
|
||||
private RoleMenuExclusionMapper roleMenuExclusionMapper;
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@Resource
|
||||
@@ -263,5 +273,114 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
|
||||
// ========== 用户-部门的相关方法 ==========
|
||||
|
||||
// ========== 父子角色的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testGetAllParentAndSelfRoleIds() {
|
||||
// mock 3层父子关系 A->B->C
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
|
||||
roleMapper.insert(roleC);
|
||||
// 调用 PermissionService 的父子角色链路能力
|
||||
Set<Long> ids = roleService.getAllParentAndSelfRoleIds(asSet(roleC.getId()));
|
||||
// 断言递归能拿到所有父节点和自身
|
||||
assertEquals(asSet(roleA.getId(), roleB.getId(), roleC.getId()), ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsChildRole_trueAndFalse() {
|
||||
// mock 3层父子关系 A->B->C
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
|
||||
roleMapper.insert(roleC);
|
||||
// 断言 C 是 A 的子孙节点
|
||||
assertTrue(roleService.isChildRole(roleA.getId(), roleC.getId()));
|
||||
// 断言 A 不是 C 的子孙节点
|
||||
assertFalse(roleService.isChildRole(roleC.getId(), roleA.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRole_parentIsChildException() {
|
||||
// mock 2层父子关系 A->B
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
// 尝试把A的父节点改为B(形成环)
|
||||
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(roleA.getId()).setParentId(roleB.getId()));
|
||||
assertThrows(ServiceException.class, () -> roleService.updateRole(reqVO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildRoleInheritParentRoleMenus() {
|
||||
// mock 父子关系 A->B
|
||||
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(parentRole);
|
||||
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
|
||||
roleMapper.insert(childRole);
|
||||
// 给父角色分配菜单
|
||||
RoleMenuDO parentMenu1 = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
|
||||
roleMenuMapper.insert(parentMenu1);
|
||||
RoleMenuDO parentMenu2 = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(102L);
|
||||
roleMenuMapper.insert(parentMenu2);
|
||||
// 给子角色分配部分菜单
|
||||
RoleMenuDO childMenu = randomPojo(RoleMenuDO.class).setRoleId(childRole.getId()).setMenuId(201L);
|
||||
roleMenuMapper.insert(childMenu);
|
||||
// 调用:获取子角色的所有菜单(应包含父角色的菜单)
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
|
||||
// 断言:包含父角色和子角色自己的菜单
|
||||
assertEquals(asSet(101L, 102L, 201L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildRoleExcludeParentMenu() {
|
||||
// mock 父子关系 A->B
|
||||
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(parentRole);
|
||||
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
|
||||
roleMapper.insert(childRole);
|
||||
// 父角色分配菜单
|
||||
RoleMenuDO parentMenu = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
|
||||
roleMenuMapper.insert(parentMenu);
|
||||
// 子角色排除父菜单(模拟排除表)
|
||||
RoleMenuExclusionDO exclusion = new RoleMenuExclusionDO();
|
||||
exclusion.setRoleId(childRole.getId());
|
||||
exclusion.setMenuId(101L);
|
||||
roleMenuExclusionMapper.insert(exclusion);
|
||||
// 调用:获取子角色菜单(应不包含父菜单)
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
|
||||
assertFalse(menuIds.contains(101L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildRoleRemoveExclusionThenInheritMenu() {
|
||||
// mock 父子关系 A->B
|
||||
RoleDO parentRole = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(parentRole);
|
||||
RoleDO childRole = randomPojo(RoleDO.class, o -> o.setParentId(parentRole.getId()));
|
||||
roleMapper.insert(childRole);
|
||||
// 父角色分配菜单
|
||||
RoleMenuDO parentMenu = randomPojo(RoleMenuDO.class).setRoleId(parentRole.getId()).setMenuId(101L);
|
||||
roleMenuMapper.insert(parentMenu);
|
||||
// 子角色排除父菜单
|
||||
RoleMenuExclusionDO exclusion = new RoleMenuExclusionDO();
|
||||
exclusion.setRoleId(childRole.getId());
|
||||
exclusion.setMenuId(101L);
|
||||
roleMenuExclusionMapper.insert(exclusion);
|
||||
// 先断言排除
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(childRole.getId());
|
||||
assertFalse(menuIds.contains(101L));
|
||||
// 取消排除
|
||||
roleMenuExclusionMapper.deleteListByRoleIdAndMenuIds(childRole.getId(), Collections.singleton(101L));
|
||||
// 再次获取,应能继承
|
||||
Set<Long> menuIds2 = permissionService.getRoleMenuListByRoleId(childRole.getId());
|
||||
assertTrue(menuIds2.contains(101L));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -390,4 +390,122 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleList(ids), ROLE_IS_DISABLE, RoleDO.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsChildRole_trueAndFalse() {
|
||||
// mock 3层父子关系 A->B->C
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
|
||||
roleMapper.insert(roleC);
|
||||
// 断言 C 的 parentId 是 A 的子孙节点
|
||||
assertTrue(roleService.getClass().cast(roleService).isChildRole(roleA.getId(), roleC.getId()));
|
||||
// 断言 A 不是 C 的子孙节点
|
||||
assertFalse(roleService.getClass().cast(roleService).isChildRole(roleC.getId(), roleA.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllParentAndSelfRoleIds_multiLevelAndLoop() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
// mock 3层父子关系 A->B->C
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
RoleDO roleC = randomPojo(RoleDO.class, o -> o.setParentId(roleB.getId()));
|
||||
roleMapper.insert(roleC);
|
||||
// 断言递归能拿到所有父节点
|
||||
Set<Long> ids = roleService.getAllParentAndSelfRoleIds(singleton(roleC.getId()));
|
||||
assertTrue(ids.contains(roleA.getId()));
|
||||
assertTrue(ids.contains(roleB.getId()));
|
||||
assertTrue(ids.contains(roleC.getId()));
|
||||
// mock 环路(C->A)
|
||||
roleC.setParentId(roleA.getId());
|
||||
roleMapper.updateById(roleC);
|
||||
// 不会死循环
|
||||
Set<Long> idsLoop = roleService.getAllParentAndSelfRoleIds(singleton(roleC.getId()));
|
||||
assertTrue(idsLoop.contains(roleA.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyAdmin_trueAndFalse() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
// mock admin
|
||||
RoleDO adminRole = randomPojo(RoleDO.class).setCode("super_admin");
|
||||
roleMapper.insert(adminRole);
|
||||
assertTrue(roleService.hasAnyAdmin(singletonList(adminRole.getId())));
|
||||
// mock 普通
|
||||
RoleDO normalRole = randomPojo(RoleDO.class).setCode("user");
|
||||
roleMapper.insert(normalRole);
|
||||
assertFalse(roleService.hasAnyAdmin(singletonList(normalRole.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRole_parentIsChildException() {
|
||||
// mock 2层父子关系 A->B
|
||||
RoleDO roleA = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(roleA);
|
||||
RoleDO roleB = randomPojo(RoleDO.class, o -> o.setParentId(roleA.getId()));
|
||||
roleMapper.insert(roleB);
|
||||
// 尝试把A的父节点改为B(形成环)
|
||||
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(roleA.getId()).setParentId(roleB.getId()));
|
||||
assertServiceException(() -> roleService.updateRole(reqVO), ROLE_PARENT_IS_CHILD, reqVO.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListFromCache_empty() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
// 空集合
|
||||
List<RoleDO> list = roleService.getRoleListFromCache(List.of());
|
||||
assertTrue(list.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleList_empty() {
|
||||
// 空集合
|
||||
roleService.validateRoleList(List.of()); // 不抛异常
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRoleDataScope_roleNotExist() {
|
||||
assertServiceException(() -> roleService.updateRoleDataScope(randomLongId(), 1, Set.of(1L)), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRole_hasChildrenException() {
|
||||
// mock 父子关系
|
||||
RoleDO parent = randomPojo(RoleDO.class, o -> o.setParentId(0L));
|
||||
roleMapper.insert(parent);
|
||||
RoleDO child = randomPojo(RoleDO.class, o -> o.setParentId(parent.getId()));
|
||||
roleMapper.insert(child);
|
||||
assertServiceException(() -> roleService.deleteRole(parent.getId()), ROLE_CAN_NOT_DELETE_HAS_CHILDREN, parent.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRole_roleNotExist() {
|
||||
assertServiceException(() -> roleService.deleteRole(randomLongId()), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRole_superAdminCodeError() {
|
||||
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class).setCode("super_admin");
|
||||
assertServiceException(() -> roleService.createRole(reqVO, null), ROLE_ADMIN_CODE_ERROR, "super_admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_codeEmpty() {
|
||||
// name 不重复,code 为空
|
||||
roleService.validateRoleDuplicate(randomString(), "", null); // 不抛异常
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.userdept.UserDeptDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.userdept.UserDeptMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptServiceImpl;
|
||||
import cn.iocoder.yudao.module.system.service.dept.PostService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
|
||||
@@ -56,7 +58,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@Import({AdminUserServiceImpl.class,UserDeptServiceImpl.class})
|
||||
@Import({AdminUserServiceImpl.class,UserDeptServiceImpl.class,DeptServiceImpl.class})
|
||||
public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
@@ -69,10 +71,11 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
@Resource
|
||||
private UserDeptMapper userDeptMapper;
|
||||
@Resource
|
||||
private DeptMapper deptMapper;
|
||||
@Resource
|
||||
private UserDeptServiceImpl userDeptService;
|
||||
|
||||
@MockBean
|
||||
private DeptService deptService;
|
||||
@Resource
|
||||
private DeptServiceImpl deptService;
|
||||
@MockBean
|
||||
private PostService postService;
|
||||
@MockBean
|
||||
@@ -99,7 +102,12 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
|
||||
o.setMobile(randomString());
|
||||
o.setPostIds(asSet(1L, 2L));
|
||||
o.setDeptIds(asSet(1L, 2L));
|
||||
}).setId(null); // 避免 id 被赋值
|
||||
// 新建对应的部门
|
||||
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(1L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
|
||||
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(2L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
|
||||
|
||||
// mock 账户额度充足
|
||||
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));
|
||||
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
|
||||
@@ -147,16 +155,20 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
public void testUpdateUser_success() {
|
||||
// mock 数据
|
||||
AdminUserDO dbUser = randomAdminUserDO(o -> o.setPostIds(asSet(1L, 2L)));
|
||||
AdminUserDO dbUser = randomAdminUserDO(o -> o.setPostIds(asSet(1L, 2L)).setDeptIds(asSet(1L, 2L)));
|
||||
userMapper.insert(dbUser);
|
||||
userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(1L));
|
||||
userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(2L));
|
||||
// 新增对应的部门
|
||||
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(1L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
|
||||
deptMapper.insert(randomPojo(DeptDO.class, o -> {o.setId(2L);o.setStatus(CommonStatusEnum.ENABLE.getStatus());}));
|
||||
// 准备参数
|
||||
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
|
||||
o.setId(dbUser.getId());
|
||||
o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
|
||||
o.setMobile(randomString());
|
||||
o.setPostIds(asSet(2L, 3L));
|
||||
o.setDeptIds(asSet(1L, 2L));
|
||||
});
|
||||
// mock postService 的方法
|
||||
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
|
||||
@@ -299,7 +311,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
// 调用
|
||||
AdminUserDO user = userService.getUserByUsername(username);
|
||||
// 断言
|
||||
assertPojoEquals(dbUser, user,"deptIds");
|
||||
assertPojoEquals(dbUser, user,"deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -313,7 +325,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
// 调用
|
||||
AdminUserDO user = userService.getUserByMobile(mobile);
|
||||
// 断言
|
||||
assertPojoEquals(dbUser, user,"deptIds");
|
||||
assertPojoEquals(dbUser, user,"deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -330,7 +342,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
// reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
|
||||
// mock 方法
|
||||
List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
|
||||
when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
|
||||
deptService.getChildDeptList(reqVO.getDeptId());
|
||||
// 新增 1L 和用户关联关系
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
|
||||
// 调用
|
||||
@@ -338,7 +350,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbUser, pageResult.getList().get(0), "deptIds");
|
||||
assertPojoEquals(dbUser, pageResult.getList().get(0), "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,7 +388,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
// 调用
|
||||
AdminUserDO user = userService.getUser(userId);
|
||||
// 断言
|
||||
assertPojoEquals(dbUser, user, "deptIds");
|
||||
assertPojoEquals(dbUser, user, "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -397,7 +409,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
List<AdminUserDO> list = userService.getUserListByDeptIds(deptIds);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbUser, list.get(0), "deptIds");
|
||||
assertPojoEquals(dbUser, list.get(0), "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -520,7 +532,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
List<AdminUserDO> result = userService.getUserListByPostIds(postIds);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(user1, result.get(0), "deptIds");
|
||||
assertPojoEquals(user1, result.get(0), "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -539,7 +551,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
List<AdminUserDO> result = userService.getUserList(ids);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(user, result.get(0), "deptIds");
|
||||
assertPojoEquals(user, result.get(0), "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -558,7 +570,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
Map<Long, AdminUserDO> result = userService.getUserMap(ids);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(user, result.get(user.getId()), "deptIds");
|
||||
assertPojoEquals(user, result.get(user.getId()), "deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -575,7 +587,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
List<AdminUserDO> result = userService.getUserListByNickname(nickname);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(user, result.get(0),"deptIds");
|
||||
assertPojoEquals(user, result.get(0),"deptIds", "companyIds", "companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -588,15 +600,12 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
userMapper.insert(user2);
|
||||
// 准备参数
|
||||
Integer status = CommonStatusEnum.DISABLE.getStatus();
|
||||
// 新增 1L 和用户关联关系 未关联 部门的用户无法被正确查询
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(user2.getId()).setDeptId(1L));
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(user.getId()).setDeptId(1L));
|
||||
// 调用
|
||||
List<AdminUserDO> result = userService.getUserListByStatus(status);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
AdminUserDO user1 = userService.getUser(result.get(0).getId());
|
||||
assertPojoEquals(user, user1, "deptIds");
|
||||
assertPojoEquals(user, user1, "deptIds","companyIds","companyDeptInfos");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -640,7 +649,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
Consumer<AdminUserDO> consumer = (o) -> {
|
||||
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
|
||||
o.setSex(randomEle(SexEnum.values()).getSex());
|
||||
o.setDeptIds(new HashSet<>(asSet(1L, 2L))); // 保证 deptIds 的范围
|
||||
o.setDeptIds(new HashSet<>(asSet(1L, 2L)));
|
||||
o.setCompanyDeptInfos(null);// 保证 deptIds 的范围
|
||||
};
|
||||
return randomPojo(AdminUserDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
@@ -651,4 +661,88 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
||||
return randomPojo(UserDeptDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUser_withMultipleDepts() {
|
||||
// 构造带多个部门的用户
|
||||
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
|
||||
o.setDeptIds(asSet(1L, 2L, 3L));
|
||||
o.setSex(1);
|
||||
}).setId(null);
|
||||
// 新建对应的部门
|
||||
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
deptMapper.insert(new DeptDO().setId(3L).setName("部门3").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// mock 账户额度充足、部门可用
|
||||
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));
|
||||
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
|
||||
handler.handle(tenant);
|
||||
return true;
|
||||
}));
|
||||
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
|
||||
randomPojo(PostDO.class, o -> {
|
||||
o.setId(postId);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
}));
|
||||
when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
|
||||
when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn("yudaoyuanma");
|
||||
|
||||
// 调用
|
||||
Long userId = userService.createUser(reqVO);
|
||||
// 校验 user_dept 表有3条数据
|
||||
List<UserDeptDO> userDepts = userDeptMapper.selectValidListByUserIds(singleton(userId));
|
||||
assertEquals(3, userDepts.size());
|
||||
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(1L)));
|
||||
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(2L)));
|
||||
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(3L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateUser_changeDepts() {
|
||||
// 先插入用户和2个部门
|
||||
AdminUserDO dbUser = randomAdminUserDO(o -> o.setDeptIds(asSet(1L, 2L)));
|
||||
userMapper.insert(dbUser);
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(2L));
|
||||
// 新建对应的部门
|
||||
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 更新为3个新部门
|
||||
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {
|
||||
o.setId(dbUser.getId());
|
||||
o.setSex(1);
|
||||
o.setDeptIds(asSet(1L, 2L));
|
||||
});
|
||||
// mock postService 的方法
|
||||
List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
|
||||
randomPojo(PostDO.class, o -> {
|
||||
o.setId(postId);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
}));
|
||||
when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
|
||||
|
||||
// 调用
|
||||
userService.updateUser(reqVO);
|
||||
// 校验 user_dept 表
|
||||
List<UserDeptDO> userDepts = userDeptMapper.selectValidListByUserIds(singleton(dbUser.getId()));
|
||||
assertEquals(2, userDepts.size());
|
||||
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(2L)));
|
||||
assertTrue(userDepts.stream().anyMatch(ud -> ud.getDeptId().equals(1L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUser_withMultipleDepts() {
|
||||
// 插入用户和多个部门
|
||||
AdminUserDO dbUser = randomAdminUserDO();
|
||||
userMapper.insert(dbUser);
|
||||
// 插入用户部门关系
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(1L));
|
||||
userDeptMapper.insert(new UserDeptDO().setUserId(dbUser.getId()).setDeptId(2L));
|
||||
// 插入部门
|
||||
deptMapper.insert(new DeptDO().setId(1L).setName("部门1").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
deptMapper.insert(new DeptDO().setId(2L).setName("部门2").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
|
||||
AdminUserDO user = userService.getUser(dbUser.getId());
|
||||
assertTrue(user.getDeptIds().contains(1L));
|
||||
assertTrue(user.getDeptIds().contains(2L));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,7 @@ public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 模板样例 1_100_000_000 ==========
|
||||
ErrorCode EXAMPLE_NOT_EXISTS = new ErrorCode(1_100_000_000, "模板样例不存在");
|
||||
// ========== 合同 补充编号 ==========
|
||||
ErrorCode DEMO_CONTRACT_NOT_EXISTS = new ErrorCode(2_100_000_000, "合同不存在");
|
||||
|
||||
}
|
||||
|
||||
@@ -121,6 +121,11 @@
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-business</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package cn.iocoder.yudao.module.template;
|
||||
|
||||
import cn.iocoder.yudao.framework.business.framework.BusinessDataPermissionConfiguration;
|
||||
import cn.iocoder.yudao.framework.datapermission.config.YudaoCompanyDataPermissionAutoConfiguration;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* 项目的启动类
|
||||
@@ -9,6 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
* @author 周迪
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Import(BusinessDataPermissionConfiguration.class)
|
||||
public class TemplateServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package cn.iocoder.yudao.module.template.controller.admin.contract;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.yudao.framework.business.interceptor.BusinessControllerMarker;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.DemoContractPageReqVO;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.DemoContractRespVO;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.DemoContractSaveReqVO;
|
||||
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
|
||||
import cn.iocoder.yudao.module.template.service.contract.DemoContractService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Tag(name = "管理后台 - 合同")
|
||||
@RestController
|
||||
@RequestMapping("/template/demo-contract")
|
||||
@Validated
|
||||
public class DemoContractController implements BusinessControllerMarker {
|
||||
|
||||
@Resource
|
||||
private DemoContractService demoContractService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建合同")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:create')")
|
||||
public CommonResult<Long> createDemoContract(@Valid @RequestBody DemoContractSaveReqVO createReqVO) {
|
||||
return success(demoContractService.createDemoContract(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新合同")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:update')")
|
||||
public CommonResult<Boolean> updateDemoContract(@Valid @RequestBody DemoContractSaveReqVO updateReqVO) {
|
||||
demoContractService.updateDemoContract(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除合同")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:delete')")
|
||||
public CommonResult<Boolean> deleteDemoContract(@RequestParam("id") Long id) {
|
||||
demoContractService.deleteDemoContract(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除合同")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:delete')")
|
||||
public CommonResult<Boolean> deleteDemoContractList(@RequestParam("ids") List<Long> ids) {
|
||||
demoContractService.deleteDemoContractListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得合同")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:query')")
|
||||
public CommonResult<DemoContractRespVO> getDemoContract(@RequestParam("id") Long id) {
|
||||
DemoContractDO demoContract = demoContractService.getDemoContract(id);
|
||||
return success(BeanUtils.toBean(demoContract, DemoContractRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得合同分页")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:query')")
|
||||
public CommonResult<PageResult<DemoContractRespVO>> getDemoContractPage(@Valid DemoContractPageReqVO pageReqVO) {
|
||||
PageResult<DemoContractDO> pageResult = demoContractService.getDemoContractPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, DemoContractRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出合同 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('template:demo-contract:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemoContractExcel(@Valid DemoContractPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<DemoContractDO> list = demoContractService.getDemoContractPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "合同.xls", "数据", DemoContractRespVO.class,
|
||||
BeanUtils.toBean(list, DemoContractRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.iocoder.yudao.module.template.controller.admin.contract.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import java.math.BigDecimal;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 合同分页 Request VO")
|
||||
@Data
|
||||
public class DemoContractPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "合同编号")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "合同名称", example = "李四")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "合同状态", example = "1")
|
||||
private Short status;
|
||||
|
||||
@Schema(description = "流程实例ID", example = "24962")
|
||||
private Long processInstanceId;
|
||||
|
||||
@Schema(description = "签订日期")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] signDate;
|
||||
|
||||
@Schema(description = "合同开始日期")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] startDate;
|
||||
|
||||
@Schema(description = "合同结束日期")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] endDate;
|
||||
|
||||
@Schema(description = "合同金额")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "备注", example = "随便")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
@Schema(description = "公司ID", example = "4180")
|
||||
private Long companyId;
|
||||
|
||||
@Schema(description = "公司名称", example = "张三")
|
||||
private String companyName;
|
||||
|
||||
@Schema(description = "部门ID", example = "1707")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "部门名称", example = "张三")
|
||||
private String deptName;
|
||||
|
||||
@Schema(description = "岗位ID", example = "26779")
|
||||
private Long postId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cn.iocoder.yudao.module.template.controller.admin.contract.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.math.BigDecimal;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import com.alibaba.excel.annotation.*;
|
||||
|
||||
@Schema(description = "管理后台 - 合同 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class DemoContractRespVO {
|
||||
|
||||
@Schema(description = "合同ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "9541")
|
||||
@ExcelProperty("合同ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("合同编号")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@ExcelProperty("合同名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "合同状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("合同状态")
|
||||
private Short status;
|
||||
|
||||
@Schema(description = "流程实例ID", example = "24962")
|
||||
@ExcelProperty("流程实例ID")
|
||||
private Long processInstanceId;
|
||||
|
||||
@Schema(description = "签订日期")
|
||||
@ExcelProperty("签订日期")
|
||||
private LocalDateTime signDate;
|
||||
|
||||
@Schema(description = "合同开始日期")
|
||||
@ExcelProperty("合同开始日期")
|
||||
private LocalDateTime startDate;
|
||||
|
||||
@Schema(description = "合同结束日期")
|
||||
@ExcelProperty("合同结束日期")
|
||||
private LocalDateTime endDate;
|
||||
|
||||
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("合同金额")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "备注", example = "随便")
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "公司ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4180")
|
||||
@ExcelProperty("公司ID")
|
||||
private Long companyId;
|
||||
|
||||
@Schema(description = "公司名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@ExcelProperty("公司名称")
|
||||
private String companyName;
|
||||
|
||||
@Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1707")
|
||||
@ExcelProperty("部门ID")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@ExcelProperty("部门名称")
|
||||
private String deptName;
|
||||
|
||||
@Schema(description = "岗位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26779")
|
||||
@ExcelProperty("岗位ID")
|
||||
private Long postId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.template.controller.admin.contract.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 合同新增/修改 Request VO")
|
||||
@Data
|
||||
public class DemoContractSaveReqVO {
|
||||
|
||||
@Schema(description = "合同ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "9541")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
|
||||
@NotEmpty(message = "合同名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "签订日期")
|
||||
private LocalDateTime signDate;
|
||||
|
||||
@Schema(description = "合同开始日期")
|
||||
private LocalDateTime startDate;
|
||||
|
||||
@Schema(description = "合同结束日期")
|
||||
private LocalDateTime endDate;
|
||||
|
||||
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "合同金额不能为空")
|
||||
private BigDecimal amount;
|
||||
|
||||
@Schema(description = "备注", example = "随便")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "公司ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4180")
|
||||
@NotNull(message = "公司ID不能为空")
|
||||
private Long companyId;
|
||||
|
||||
@Schema(description = "公司名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@NotEmpty(message = "公司名称不能为空")
|
||||
private String companyName;
|
||||
|
||||
@Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1707")
|
||||
@NotNull(message = "部门ID不能为空")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@NotEmpty(message = "部门名称不能为空")
|
||||
private String deptName;
|
||||
|
||||
@Schema(description = "岗位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26779")
|
||||
@NotNull(message = "岗位ID不能为空")
|
||||
private Long postId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cn.iocoder.yudao.module.template.dal.dataobject.contract;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BusinessBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 合同 DO
|
||||
*
|
||||
* @author 后台管理
|
||||
*/
|
||||
@TableName("demo_contract")
|
||||
@KeySequence("demo_contract_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DemoContractDO extends BusinessBaseDO {
|
||||
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/**
|
||||
* 合同编号
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 合同名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 合同状态
|
||||
*/
|
||||
private Short status;
|
||||
/**
|
||||
* 流程实例ID
|
||||
*/
|
||||
private Long processInstanceId;
|
||||
/**
|
||||
* 签订日期
|
||||
*/
|
||||
private LocalDateTime signDate;
|
||||
/**
|
||||
* 合同开始日期
|
||||
*/
|
||||
private LocalDateTime startDate;
|
||||
/**
|
||||
* 合同结束日期
|
||||
*/
|
||||
private LocalDateTime endDate;
|
||||
/**
|
||||
* 合同金额
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.yudao.module.template.dal.mysql.contract;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.*;
|
||||
|
||||
/**
|
||||
* 合同 Mapper
|
||||
*
|
||||
* @author 后台管理
|
||||
*/
|
||||
@Mapper
|
||||
public interface DemoContractMapper extends BaseMapperX<DemoContractDO> {
|
||||
|
||||
default PageResult<DemoContractDO> selectPage(DemoContractPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<DemoContractDO>()
|
||||
.eqIfPresent(DemoContractDO::getCode, reqVO.getCode())
|
||||
.likeIfPresent(DemoContractDO::getName, reqVO.getName())
|
||||
.eqIfPresent(DemoContractDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(DemoContractDO::getProcessInstanceId, reqVO.getProcessInstanceId())
|
||||
.betweenIfPresent(DemoContractDO::getSignDate, reqVO.getSignDate())
|
||||
.betweenIfPresent(DemoContractDO::getStartDate, reqVO.getStartDate())
|
||||
.betweenIfPresent(DemoContractDO::getEndDate, reqVO.getEndDate())
|
||||
.eqIfPresent(DemoContractDO::getAmount, reqVO.getAmount())
|
||||
.eqIfPresent(DemoContractDO::getRemark, reqVO.getRemark())
|
||||
.betweenIfPresent(DemoContractDO::getCreateTime, reqVO.getCreateTime())
|
||||
.eqIfPresent(DemoContractDO::getCompanyId, reqVO.getCompanyId())
|
||||
.likeIfPresent(DemoContractDO::getCompanyName, reqVO.getCompanyName())
|
||||
.eqIfPresent(DemoContractDO::getDeptId, reqVO.getDeptId())
|
||||
.likeIfPresent(DemoContractDO::getDeptName, reqVO.getDeptName())
|
||||
.eqIfPresent(DemoContractDO::getPostId, reqVO.getPostId())
|
||||
.orderByDesc(DemoContractDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.iocoder.yudao.module.template.service.contract;
|
||||
|
||||
import java.util.*;
|
||||
import jakarta.validation.*;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.*;
|
||||
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 合同 Service 接口
|
||||
*
|
||||
* @author 后台管理
|
||||
*/
|
||||
public interface DemoContractService {
|
||||
|
||||
/**
|
||||
* 创建合同
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createDemoContract(@Valid DemoContractSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新合同
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateDemoContract(@Valid DemoContractSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除合同
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteDemoContract(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除合同
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteDemoContractListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得合同
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 合同
|
||||
*/
|
||||
DemoContractDO getDemoContract(Long id);
|
||||
|
||||
/**
|
||||
* 获得合同分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 合同分页
|
||||
*/
|
||||
PageResult<DemoContractDO> getDemoContractPage(DemoContractPageReqVO pageReqVO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cn.iocoder.yudao.module.template.service.contract;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.template.controller.admin.contract.vo.*;
|
||||
import cn.iocoder.yudao.module.template.dal.dataobject.contract.DemoContractDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
|
||||
import cn.iocoder.yudao.module.template.dal.mysql.contract.DemoContractMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
|
||||
import static cn.iocoder.yudao.module.template.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 合同 Service 实现类
|
||||
*
|
||||
* @author 后台管理
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class DemoContractServiceImpl implements DemoContractService {
|
||||
|
||||
@Resource
|
||||
private DemoContractMapper demoContractMapper;
|
||||
|
||||
@Override
|
||||
public Long createDemoContract(DemoContractSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
DemoContractDO demoContract = BeanUtils.toBean(createReqVO, DemoContractDO.class);
|
||||
demoContract.setCode("0");
|
||||
demoContractMapper.insert(demoContract);
|
||||
// 返回
|
||||
return demoContract.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDemoContract(DemoContractSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateDemoContractExists(updateReqVO.getId());
|
||||
// 更新
|
||||
DemoContractDO updateObj = BeanUtils.toBean(updateReqVO, DemoContractDO.class);
|
||||
demoContractMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDemoContract(Long id) {
|
||||
// 校验存在
|
||||
validateDemoContractExists(id);
|
||||
// 删除
|
||||
demoContractMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDemoContractListByIds(List<Long> ids) {
|
||||
// 校验存在
|
||||
validateDemoContractExists(ids);
|
||||
// 删除
|
||||
demoContractMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
private void validateDemoContractExists(List<Long> ids) {
|
||||
List<DemoContractDO> list = demoContractMapper.selectByIds(ids);
|
||||
if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
|
||||
throw exception(DEMO_CONTRACT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDemoContractExists(Long id) {
|
||||
if (demoContractMapper.selectById(id) == null) {
|
||||
throw exception(DEMO_CONTRACT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DemoContractDO getDemoContract(Long id) {
|
||||
return demoContractMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<DemoContractDO> getDemoContractPage(DemoContractPageReqVO pageReqVO) {
|
||||
return demoContractMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,7 +81,7 @@ spring:
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 服务保障相关配置 ####################
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.template.dal.mysql.contract.DemoContractMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
|
||||
</mapper>
|
||||
@@ -91,7 +91,7 @@ xxl:
|
||||
job:
|
||||
enabled: false # 是否开启调度中心,默认为 true 开启
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
|
||||
addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址
|
||||
|
||||
--- #################### 消息队列相关 ####################
|
||||
|
||||
|
||||
Reference in New Issue
Block a user