提交 5598f624 authored 作者: YunaiV's avatar YunaiV

完成 TenantApi 的 feign 支持

上级 71930d49
...@@ -11,9 +11,11 @@ import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; ...@@ -11,9 +11,11 @@ import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
...@@ -29,6 +31,11 @@ import org.springframework.context.annotation.Configuration; ...@@ -29,6 +31,11 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(TenantProperties.class) @EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantAutoConfiguration { public class YudaoTenantAutoConfiguration {
@Bean
public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {
return new TenantFrameworkServiceImpl(tenantApi);
}
// ========== AOP ========== // ========== AOP ==========
@Bean @Bean
......
package cn.iocoder.yudao.framework.tenant.config; package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.tenant.core.rpc.TenantRequestInterceptor; import cn.iocoder.yudao.framework.tenant.core.rpc.TenantRequestInterceptor;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户 @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@EnableFeignClients(clients = TenantApi.class) // 主要是引入相关的 API 服务
public class YudaoTenantRpcAutoConfiguration { public class YudaoTenantRpcAutoConfiguration {
@Bean @Bean
......
package cn.iocoder.yudao.framework.tenant.core.service;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import lombok.RequiredArgsConstructor;
import java.util.List;
/**
* Tenant 框架 Service 实现类
*
* @author 芋道源码
*/
@RequiredArgsConstructor
public class TenantFrameworkServiceImpl implements TenantFrameworkService {
private final TenantApi tenantApi;
@Override
public List<Long> getTenantIds() {
CommonResult<List<Long>> tenantIdsResult = tenantApi.getTenantIds();
tenantIdsResult.checkError();
return tenantIdsResult.getData();
}
@Override
public void validTenant(Long id) {
tenantApi.validTenant(id).checkError();
}
}
package cn.iocoder.yudao.module.infra.framework;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class TmpConfiguration {
@Bean
public TenantFrameworkService tenantFrameworkService() {
return new TenantFrameworkService() {
@Override
public List<Long> getTenantIds() {
return null;
}
@Override
public void validTenant(Long id) {
}
};
}
}
...@@ -33,6 +33,7 @@ public interface DeptApi { ...@@ -33,6 +33,7 @@ public interface DeptApi {
CommonResult<List<DeptRespDTO>> getDepts(@RequestParam("ids") Collection<Long> ids); CommonResult<List<DeptRespDTO>> getDepts(@RequestParam("ids") Collection<Long> ids);
@GetMapping(PREFIX + "/valid") @GetMapping(PREFIX + "/valid")
@ApiOperation("校验部门是否合法")
@ApiImplicitParam(name = "ids", value = "部门编号数组", example = "1,2", required = true, allowMultiple = true) @ApiImplicitParam(name = "ids", value = "部门编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<Boolean> validDepts(@RequestParam("ids") Collection<Long> ids); CommonResult<Boolean> validDepts(@RequestParam("ids") Collection<Long> ids);
......
...@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; ...@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.enums.ApiConstants; import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
...@@ -17,7 +18,8 @@ public interface PostApi { ...@@ -17,7 +18,8 @@ public interface PostApi {
String PREFIX = ApiConstants.PREFIX + "/post"; String PREFIX = ApiConstants.PREFIX + "/post";
@GetMapping(PREFIX + "/valid") @GetMapping(PREFIX + "/valid")
@ApiImplicitParam(name = "ids", value = "部门编号数组", example = "1,2", required = true, allowMultiple = true) @ApiOperation("校验岗位是否合法")
@ApiImplicitParam(name = "ids", value = "岗位编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<Boolean> validPosts(@RequestParam("ids") Collection<Long> ids); CommonResult<Boolean> validPosts(@RequestParam("ids") Collection<Long> ids);
} }
...@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; ...@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.enums.ApiConstants; import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
...@@ -17,6 +18,7 @@ public interface RoleApi { ...@@ -17,6 +18,7 @@ public interface RoleApi {
String PREFIX = ApiConstants.PREFIX + "/role"; String PREFIX = ApiConstants.PREFIX + "/role";
@GetMapping(PREFIX + "/valid") @GetMapping(PREFIX + "/valid")
@ApiOperation("校验角色是否合法")
@ApiImplicitParam(name = "ids", value = "角色编号数组", example = "1,2", required = true, allowMultiple = true) @ApiImplicitParam(name = "ids", value = "角色编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<Boolean> validRoles(@RequestParam("ids") Collection<Long> ids); CommonResult<Boolean> validRoles(@RequestParam("ids") Collection<Long> ids);
......
...@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*; ...@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import javax.validation.Valid;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory =
@Api(tags = "RPC 服务 - 社交用户") @Api(tags = "RPC 服务 - 社交用户")
public interface SocialUserApi { public interface SocialUserApi {
String PREFIX = ApiConstants.PREFIX + "/social-user"; String PREFIX = ApiConstants.PREFIX + "/social-user";
......
package cn.iocoder.yudao.module.system.api.tenant;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory =
@Api(tags = "RPC 服务 - 多租户")
public interface TenantApi {
String PREFIX = ApiConstants.PREFIX + "/tenant";
@GetMapping(PREFIX + "/id-list")
@ApiOperation("获得所有租户编号")
CommonResult<List<Long>> getTenantIds();
@GetMapping(PREFIX + "/valid")
@ApiOperation("校验租户是否合法")
@ApiImplicitParam(name = "id", value = "租户编号", required = true, example = "1024", dataTypeClass = Long.class)
CommonResult<Boolean> validTenant(@RequestParam("id") Long id);
}
package cn.iocoder.yudao.module.system.api.tenant;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.system.enums.ApiConstants.VERSION;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@DubboService(version = VERSION) // 提供 Dubbo RPC 接口,给 Dubbo Consumer 调用
@Validated
public class TenantApiImpl implements TenantApi {
@Resource
private TenantService tenantService;
@Override
public CommonResult<List<Long>> getTenantIds() {
return success(tenantService.getTenantIds());
}
@Override
public CommonResult<Boolean> validTenant(Long id) {
tenantService.validTenant(id);
return success(true);
}
}
...@@ -20,7 +20,7 @@ import java.util.Set; ...@@ -20,7 +20,7 @@ import java.util.Set;
* *
* @author 芋道源码 * @author 芋道源码
*/ */
public interface TenantService extends TenantFrameworkService { public interface TenantService {
/** /**
* 初始化租户的本地缓存 * 初始化租户的本地缓存
...@@ -120,4 +120,18 @@ public interface TenantService extends TenantFrameworkService { ...@@ -120,4 +120,18 @@ public interface TenantService extends TenantFrameworkService {
* @param handler 处理器 * @param handler 处理器
*/ */
void handleTenantMenu(TenantMenuHandler handler); void handleTenantMenu(TenantMenuHandler handler);
/**
* 获得所有租户
*
* @return 租户编号数组
*/
List<Long> getTenantIds();
/**
* 校验租户是否合法
*
* @param id 租户编号
*/
void validTenant(Long id);
} }
...@@ -77,6 +77,7 @@ yudao: ...@@ -77,6 +77,7 @@ yudao:
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
- /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
- /rpc-api/system/tenant/valid # 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验
ignore-tables: ignore-tables:
- system_tenant - system_tenant
- system_tenant_package - system_tenant_package
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-framework</artifactId>
<dependencies>
<!-- Web 相关 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<optional>true</optional>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试相关 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<!-- 工具相关 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>
package cn.iocoder.common.framework.core;
/**
* 可生成 Int 数组的接口
*/
public interface IntArrayValuable {
/**
* @return int 数组
*/
int[] array();
}
package cn.iocoder.common.framework.enums;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 通用状态枚举
*/
public enum CommonStatusEnum implements IntArrayValuable {
ENABLE(1, "开启"),
DISABLE(2, "关闭");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getValue).toArray();
/**
* 状态值
*/
private final Integer value;
/**
* 状态名
*/
private final String name;
CommonStatusEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.common.framework.enums;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 全局用户类型枚举
*/
public enum UserTypeEnum implements IntArrayValuable {
USER(1, "用户"),
ADMIN(2, "管理员");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(UserTypeEnum::getValue).toArray();
/**
* 类型
*/
private final Integer value;
/**
* 类型名
*/
private final String name;
UserTypeEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}
package cn.iocoder.common.framework.exception;
import cn.iocoder.common.framework.exception.enums.ServiceErrorCodeRange;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999],参见 {@link GlobalException}
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
*
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
*/
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String message;
public ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
package cn.iocoder.common.framework.exception.util;
import cn.iocoder.common.framework.exception.ErrorCode;
import cn.iocoder.common.framework.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* {@link ServiceException} 工具类
*
* 目的在于,格式化异常信息提示。
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
*
* 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式:
*
* 1. 异常提示信息,写在枚举类中,例如说,cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration
* 2. 异常提示信息,写在 .properties 等等配置文件
* 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新
* 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新
*/
public class ServiceExceptionUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExceptionUtil.class);
/**
* 错误码提示模板
*/
private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
public static void putAll(Map<Integer, String> messages) {
ServiceExceptionUtil.MESSAGES.putAll(messages);
}
public static void put(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.put(code, message);
}
public static void delete(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.remove(code, message);
}
// ========== 和 ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
return exception0(errorCode.getCode(), messagePattern);
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
return exception0(errorCode.getCode(), messagePattern, params);
}
/**
* 创建指定编号的 ServiceException 的异常
*
* @param code 编号
* @return 异常
*/
public static ServiceException exception(Integer code) {
return exception0(code, MESSAGES.get(code));
}
/**
* 创建指定编号的 ServiceException 的异常
*
* @param code 编号
* @param params 消息提示的占位符对应的参数
* @return 异常
*/
public static ServiceException exception(Integer code, Object... params) {
return exception0(code, MESSAGES.get(code), params);
}
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
String message = doFormat(code, messagePattern, params);
return new ServiceException(code, message);
}
// ========== 格式化方法 ==========
/**
* 将错误编号对应的消息使用 params 进行格式化。
*
* @param code 错误编号
* @param messagePattern 消息模版
* @param params 参数
* @return 格式化后的提示
*/
private static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
int l;
for (l = 0; l < params.length; l++) {
j = messagePattern.indexOf("{}", i);
if (j == -1) {
LOGGER.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
if (i == 0) {
return messagePattern;
} else {
sbuf.append(messagePattern.substring(i, messagePattern.length()));
return sbuf.toString();
}
} else {
sbuf.append(messagePattern.substring(i, j));
sbuf.append(params[l]);
i = j + 2;
}
}
if (messagePattern.indexOf("{}", i) != -1) {
LOGGER.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
}
sbuf.append(messagePattern.substring(i, messagePattern.length()));
return sbuf.toString();
}
}
package cn.iocoder.common.framework.util;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CollectionUtils {
public static boolean isEmpty(Collection collection) {
return collection == null || collection.isEmpty();
}
public static boolean isEmpty(Object[] arrays) {
return arrays == null || arrays.length == 0;
}
public static <T> Set<T> asSet(T... objs) {
return new HashSet<>(Arrays.asList(objs));
}
public static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
return from.stream().map(func).collect(Collectors.toList());
}
public static <T, U> Set<U> convertSet(List<T> from, Function<T, U> func) {
return from.stream().map(func).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
}
public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
}
public static <T, K> Map<K, List<T>> convertMultiMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc,
Collectors.mapping(t -> t, Collectors.toList())));
}
public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc,
Collectors.mapping(valueFunc, Collectors.toList())));
}
// 暂时没想好名字,先以 2 结尾噶
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
}
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
}
public static <T> T getFirst(List<T> from) {
return !isEmpty(from) ? from.get(0) : null;
}
}
package cn.iocoder.common.framework.util;
import org.springframework.util.Assert;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateUtil {
/**
* 计算当期时间相差的日期
*
* @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
* @param amount 相差的数值
* @return 计算后的日志
*/
public static Date addDate(int field, int amount) {
return addDate(null, field, amount);
}
/**
* 计算当期时间相差的日期
*
* @param date 设置时间
* @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
* @param amount 相差的数值
* @return 计算后的日志
*/
public static Date addDate(Date date, int field, int amount) {
if (amount == 0) {
return date;
}
Calendar c = Calendar.getInstance();
if (date != null) {
c.setTime(date);
}
c.add(field, amount);
return c.getTime();
}
/**
* @param date 时间。若为空,则返回空串
* @param pattern 时间格式化
* @return 格式化后的时间字符串.
*/
public static String format(Date date, String pattern) {
if (date == null) {
return "";
}
// TODO 芋艿,后面改成缓存
return new SimpleDateFormat(pattern).format(date);
}
/**
* 获取指定天结束时间
*
* @param date 日期
* @return 获得该日期的开始
*/
public static Date getDayBegin(Date date) {
if (date == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, 0, 0, 0, 0);
return calendar.getTime();
}
/**
* 获取当天开始时间
*
* @return 获得该日期的开始
*/
public static Date getDayBegin() {
return getDayBegin(new Date());
}
/**
* 获取指定天结束时间
*
* @param date 日期
* @return 获得该日期的结束
*/
public static Date getDayEnd(Date date) {
if (date == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, 23, 59, 59, 999);
return calendar.getTime();
}
/**
* 获取当天结束时间
*
* @return 获得该日期的开始
*/
public static Date getDayEnd() {
return getDayEnd(new Date());
}
/**
* 设置Calendar的小时、分钟、秒、毫秒
*
* @param calendar 日历
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @param milliSecond 毫秒
*/
public static void setCalender(Calendar calendar, int hour, int minute, int second, int milliSecond) {
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, milliSecond);
}
/**
* 判断当前时间,是否在该时间范围内
*
* @param beginTime 开始时间
* @param endTime 结束时间
* @return 是否在
*/
public static boolean isBetween(Date beginTime, Date endTime) {
Assert.notNull(beginTime, "开始时间不能为空");
Assert.notNull(endTime, "结束时间不能为空");
Date now = new Date();
return beginTime.getTime() <= now.getTime()
&& now.getTime() <= endTime.getTime();
}
public static Date max(Date a, Date b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.compareTo(b) > 0 ? a : b;
}
}
package cn.iocoder.common.framework.util;
import cn.hutool.crypto.digest.BCrypt;
/**
* 加解密工具类
*/
public class DigestUtils {
public static String genBcryptSalt() {
return BCrypt.gensalt();
}
public static String bcrypt(String key, String salt) {
return BCrypt.hashpw(key, salt);
}
// TODO 稍后移到单元测试
public static void main(String[] args) {
String salt = genBcryptSalt();
String password = "buzhidao";
System.out.println(salt);
System.out.println(bcrypt(password, salt));
}
}
package cn.iocoder.common.framework.util;
import org.apache.commons.lang3.exception.ExceptionUtils;
public class ExceptionUtil {
public static String getMessage(Throwable th) {
return ExceptionUtils.getMessage(th);
}
public static String getRootCauseMessage(Throwable th) {
return ExceptionUtils.getRootCauseMessage(th);
}
public static String getStackTrace(Throwable th) {
return ExceptionUtils.getStackTrace(th);
}
}
package cn.iocoder.common.framework.util;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import java.util.UUID;
public class MallUtils {
/**
* 获得链路追踪编号
*
* 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
*
* 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。
*
* @return 链路追踪编号
*/
public static String getTraceId() {
// 通过 SkyWalking 获取链路编号
try {
String traceId = TraceContext.traceId();
if (StringUtils.hasText(traceId)) {
return traceId;
}
} catch (Throwable ignore) {}
// TODO 芋艿 多次调用会问题
return UUID.randomUUID().toString();
}
}
package cn.iocoder.common.framework.util;
import cn.hutool.core.lang.UUID;
import java.util.*;
public class StringUtils {
public static boolean hasText(String str) {
return org.springframework.util.StringUtils.hasText(str);
}
public static String join(Collection<?> coll, String delim) {
return org.springframework.util.StringUtils.collectionToDelimitedString(coll, delim);
}
public static List<String> split(String toSplit, String delim) {
String[] stringArray = org.springframework.util.StringUtils.tokenizeToStringArray(toSplit, delim);
return Arrays.asList(stringArray);
}
public static List<Integer> splitToInt(String toSplit, String delim) {
String[] stringArray = org.springframework.util.StringUtils.tokenizeToStringArray(toSplit, delim);
List<Integer> array = new ArrayList<>(stringArray.length);
for (String string : stringArray) {
array.add(Integer.valueOf(string));
}
return array;
}
public static String substring(String str, int start) {
return org.apache.commons.lang3.StringUtils.substring(str, start);
}
public static String uuid(boolean isSimple) {
return UUID.fastUUID().toString(isSimple);
}
}
package cn.iocoder.common.framework.util;
import java.util.regex.Pattern;
/**
* 校验工具类
*/
public class ValidationUtil {
private static Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
public static boolean isMobile(String mobile) {
if (mobile == null || mobile.length() != 11) {
return false;
}
// TODO 芋艿,后面完善手机校验
return true;
}
public static boolean isURL(String url) {
return StringUtils.hasText(url)
&& PATTERN_URL.matcher(url).matches();
}
public static void main(String[] args) {
System.out.println(isURL("http://www.iocoder.cn"));
}
}
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.core.IntArrayValuable;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = InEnumValidator.class
)
public @interface InEnum {
/**
* @return 实现 EnumValuable 接口的
*/
Class<? extends IntArrayValuable> value();
String message() default "必须在指定范围 {value}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.core.IntArrayValuable;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class InEnumValidator implements ConstraintValidator<InEnum, Integer> {
private List<Integer> values;
@Override
public void initialize(InEnum annotation) {
IntArrayValuable[] values = annotation.value().getEnumConstants();
if (values.length == 0) {
this.values = Collections.emptyList();
} else {
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 为空时,默认不校验,即认为通过
if (value == null) {
return true;
}
// 校验通过
if (values.contains(value)) {
return true;
}
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
.replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句
return false;
}
}
package cn.iocoder.common.framework.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = MobileValidator.class
)
public @interface Mobile {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.common.framework.util.ValidationUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<Mobile, String> {
@Override
public void initialize(Mobile annotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果手机号为空,默认不校验,即校验通过
if (!StringUtils.hasText(value)) {
return true;
}
// 校验手机
return ValidationUtil.isMobile(value);
}
}
package cn.iocoder.common.framework.util;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.Date;
import java.util.GregorianCalendar;
public class DateUtilTest {
@Test
@Ignore // 暂时忽略,测试不通过,add by 芋艿
public void testAddDate() {
Assert.assertNull(DateUtil.addDate(0, 0));
Assert.assertEquals(new Date(1_778_410_800_000L), DateUtil.addDate(
new Date(1_515_585_600_000L), 2, 100));
}
@Test
@Ignore // 暂时忽略,测试不通过,add by 芋艿
public void testFormat() {
Assert.assertEquals("", DateUtil.format(null, null));
Assert.assertEquals("2018-01-10:12:00:00", DateUtil.format(
new Date(1_515_585_600_000L), "yyyy-MM-dd:HH:mm:ss"));
}
@Test
@Ignore // 暂时忽略,测试不通过,add by 芋艿
public void testGetDayBegin() {
Assert.assertNull(DateUtil.getDayBegin(null));
Assert.assertEquals(new Date(1_515_542_400_000L),
DateUtil.getDayBegin(new Date(1_515_585_600_000L)));
}
@Test
@Ignore // 暂时忽略,测试不通过,add by 芋艿
public void testGetDayEnd() {
Assert.assertNull(DateUtil.getDayEnd(null));
Assert.assertEquals(new Date(1_515_628_799_999L), DateUtil.getDayEnd(
new Date(1_515_585_600_000L)));
}
@Test
public void testIsBetween() {
Assert.assertTrue(DateUtil.isBetween(DateUtil.getDayBegin(),
DateUtil.getDayEnd()));
Assert.assertFalse(DateUtil.isBetween(
DateUtil.getDayBegin(new Date(0L)),
DateUtil.getDayEnd(new Date(100_000L))));
}
@Test
public void testSetCalender() {
final GregorianCalendar calendar = new GregorianCalendar();
DateUtil.setCalender(calendar, 2, 30, 50, 0);
Assert.assertEquals(2, calendar.getTime().getHours());
Assert.assertEquals(30, calendar.getTime().getMinutes());
Assert.assertEquals(50, calendar.getTime().getSeconds());
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论