Initial commit
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<?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>oneone-common</artifactId>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--mvc 相关配置-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--server-api-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<!--hibernate-validator-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--aop-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-annotation</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import com.oneone.common.enums.AreaTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 区域节点,包括国家、省份、城市、地区等信息
|
||||
*
|
||||
* 数据可见 resources/area.csv 文件
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Area {
|
||||
|
||||
/**
|
||||
* 编号 - 全球,即根目录
|
||||
*/
|
||||
public static final Integer ID_GLOBAL = 0;
|
||||
/**
|
||||
* 编号 - 中国
|
||||
*/
|
||||
public static final Integer ID_CHINA = 1;
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* 枚举 {@link AreaTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 父节点
|
||||
*/
|
||||
private Area parent;
|
||||
/**
|
||||
* 子节点
|
||||
*/
|
||||
private List<Area> children;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @desc VO 基类
|
||||
* @email
|
||||
* @date 2021/1/11
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class BaseVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-03-30 20:59
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class IdParam {
|
||||
@Schema(description = "id")
|
||||
@NotNull
|
||||
private Long id;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class KeyValue<K, V> implements Serializable {
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import com.oneone.common.enums.ChannelEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-02 11:38
|
||||
*/
|
||||
@Data
|
||||
public class LocalToken implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
@Schema(description = "有效期")
|
||||
private Long expiresIn;
|
||||
@Schema(description = "授权方式: refresh, sms_code, wechat")
|
||||
private String grantType;
|
||||
@Schema(description = "用户id")
|
||||
private Long memberId;
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
@Schema(description = "账号类型")
|
||||
private String accountType;
|
||||
@Schema(description = "用户类型")
|
||||
private Integer userType;
|
||||
@Schema(description = "渠道")
|
||||
private ChannelEnum channel = ChannelEnum.LOCAL;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-02 14:35
|
||||
*/
|
||||
@Data
|
||||
public class LocalUser {
|
||||
private Long userId;
|
||||
private Long memberId;
|
||||
private int userType;
|
||||
|
||||
|
||||
/**
|
||||
* 上下文字段,不进行持久化
|
||||
*
|
||||
* 1. 用于基于 LocalUser 维度的临时缓存
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Map<String, Object> context;
|
||||
|
||||
public void setContext(String key, Object value) {
|
||||
if (context == null) {
|
||||
context = new HashMap<>();
|
||||
}
|
||||
context.put(key, value);
|
||||
}
|
||||
|
||||
public <T> T getContext(String key, Class<T> type) {
|
||||
return MapUtil.get(context, key, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description="分页参数")
|
||||
@Data
|
||||
public class PageParam implements Serializable {
|
||||
|
||||
private static final Integer PAGE_NO = 1;
|
||||
private static final Integer PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 每页条数 - 不分页
|
||||
*
|
||||
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
|
||||
*/
|
||||
public static final Integer PAGE_SIZE_NONE = -1;
|
||||
|
||||
@Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
|
||||
@NotNull(message = "页码不能为空")
|
||||
@Min(value = 1, message = "页码最小值为 1")
|
||||
private Integer pageNo = PAGE_NO;
|
||||
|
||||
@Schema(description = "每页条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
@NotNull(message = "每页条数不能为空")
|
||||
@Min(value = 1, message = "每页条数最小值为 1")
|
||||
@Max(value = 100, message = "每页条数最大值为 100")
|
||||
private Integer pageSize = PAGE_SIZE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.oneone.common.base;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 排序字段 DTO
|
||||
*
|
||||
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SortingField implements Serializable {
|
||||
|
||||
/**
|
||||
* 顺序 - 升序
|
||||
*/
|
||||
public static final String ORDER_ASC = "asc";
|
||||
/**
|
||||
* 顺序 - 降序
|
||||
*/
|
||||
public static final String ORDER_DESC = "desc";
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
private String field;
|
||||
/**
|
||||
* 顺序
|
||||
*/
|
||||
private String order;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.oneone.common.constant;
|
||||
|
||||
public class CommonConstant {
|
||||
|
||||
// 禁用 否
|
||||
public static final int STATUS_NO = 0;
|
||||
// 启用 是
|
||||
public static final int STATUS_YES = 1;
|
||||
// 失败 未知
|
||||
public static final int STATUS_FAILED = -1;
|
||||
/**
|
||||
* log
|
||||
*/
|
||||
public static final String EVENT_LOG = "log";
|
||||
/**
|
||||
* request
|
||||
*/
|
||||
public static final String EVENT_REQUEST = "request";
|
||||
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.oneone.common.constant;
|
||||
|
||||
/**
|
||||
* 全局常量
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2021/10/30 9:32
|
||||
*/
|
||||
public interface GlobalConstants {
|
||||
|
||||
/**
|
||||
* 全局状态-是
|
||||
*/
|
||||
Integer STATUS_YES = 1;
|
||||
|
||||
/**
|
||||
* 超级管理员角色编码
|
||||
*/
|
||||
String ROOT_ROLE_CODE = "ROOT";
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
String USER_INFO = "user_info";
|
||||
|
||||
String HEX_PREFIX = "0x";
|
||||
|
||||
String POUND_SIGN = "#";
|
||||
|
||||
String SLASH = "/";
|
||||
|
||||
String FEIGN_TOKEN = "feign_token";
|
||||
|
||||
String INNER_RPC = "inner";
|
||||
|
||||
/**
|
||||
* 数据源名称 master
|
||||
*/
|
||||
String DATASOURCE_MASTER = "master";
|
||||
/**
|
||||
* 数据源名称 slave
|
||||
*/
|
||||
String DATASOURCE_SLAVE= "slave";
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.oneone.common.constant;
|
||||
|
||||
public interface RedisConstants {
|
||||
|
||||
String BUSINESS_NO_PREFIX = "business_no:";
|
||||
|
||||
/**
|
||||
* 优惠券码KEY前缀
|
||||
*/
|
||||
String SMS_COUPON_TEMPLATE_CODE_KEY = "sms_coupon_template_code_";
|
||||
|
||||
/**
|
||||
* 用户当前所有可用优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_USABLE_KEY = "sms_user_coupon_usable_";
|
||||
|
||||
/**
|
||||
* 用户当前所有已使用优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_USED_KEY = "sms_user_coupon_used_";
|
||||
|
||||
/**
|
||||
* 用户当前所有已过期优惠券key
|
||||
*/
|
||||
String SMS_USER_COUPON_EXPIRED_KEY = "sms_user_coupon_expired_";
|
||||
|
||||
/**
|
||||
* 图片验证码
|
||||
*/
|
||||
public static final String IMAGE_VALIDATE_CODE = "image_validate_code:";
|
||||
|
||||
|
||||
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.oneone.common.constant;
|
||||
|
||||
public interface SecurityConstants {
|
||||
|
||||
/**
|
||||
* 认证请求头key
|
||||
*/
|
||||
String AUTHORIZATION_KEY = "Authorization";
|
||||
|
||||
String AUTHORIZATION_BEARER = "Bearer";
|
||||
|
||||
|
||||
/**
|
||||
* JWT令牌前缀
|
||||
*/
|
||||
String JWT_PREFIX = "Bearer ";
|
||||
|
||||
|
||||
/**
|
||||
* Basic认证前缀
|
||||
*/
|
||||
String BASIC_PREFIX = "Basic ";
|
||||
|
||||
/**
|
||||
* JWT载体key
|
||||
*/
|
||||
String JWT_PAYLOAD_KEY = "payload";
|
||||
|
||||
/**
|
||||
* JWT ID 唯一标识
|
||||
*/
|
||||
String JWT_JTI = "jti";
|
||||
|
||||
/**
|
||||
* JWT ID 唯一标识
|
||||
*/
|
||||
String JWT_EXP = "exp";
|
||||
|
||||
/**
|
||||
* 黑名单token前缀
|
||||
*/
|
||||
String TOKEN_BLACKLIST_PREFIX = "auth:token:blacklist:";
|
||||
|
||||
String USER_ID_KEY = "userId";
|
||||
|
||||
String USER_NAME_KEY = "username";
|
||||
|
||||
String CLIENT_ID_KEY = "client_id";
|
||||
|
||||
/**
|
||||
* JWT存储权限前缀
|
||||
*/
|
||||
String AUTHORITY_PREFIX = "ROLE_";
|
||||
|
||||
/**
|
||||
* JWT存储权限属性
|
||||
*/
|
||||
String JWT_AUTHORITIES_KEY = "authorities";
|
||||
|
||||
String GRANT_TYPE_KEY = "grant_type";
|
||||
|
||||
String REFRESH_TOKEN_KEY = "refresh_token";
|
||||
|
||||
/**
|
||||
* 认证身份标识
|
||||
*/
|
||||
String AUTHENTICATION_IDENTITY_KEY = "authenticationIdentity";
|
||||
|
||||
String APP_API_PATTERN = "/*/app-api/**";
|
||||
|
||||
String LOGOUT_PATH = "/oneone-auth/oauth/logout";
|
||||
|
||||
/**
|
||||
* 新增菜单路径,新增不存在的路由会导致系统无法访问,线上禁止新增菜单的操作
|
||||
*/
|
||||
String SAVE_MENU_PATH = "/oneone-admin/api/v1/menus";
|
||||
|
||||
/**
|
||||
* 验证码key前缀
|
||||
*/
|
||||
String VALIDATE_CODE_PREFIX = "VALIDATE_CODE:";
|
||||
|
||||
/**
|
||||
* 短信验证码key前缀
|
||||
*/
|
||||
String SMS_CODE_PREFIX = "SMS_CODE:";
|
||||
|
||||
/**
|
||||
* 接口文档 Knife4j 测试客户端ID
|
||||
*/
|
||||
String TEST_CLIENT_ID = "client";
|
||||
|
||||
/**
|
||||
* 系统管理 web 客户端ID
|
||||
*/
|
||||
String ADMIN_CLIENT_ID = "oneone-admin-web";
|
||||
|
||||
/**
|
||||
* 移动端(H5/Android/IOS)客户端ID
|
||||
*/
|
||||
String APP_CLIENT_ID = "oneone-app";
|
||||
|
||||
/**
|
||||
* 小程序端(微信小程序、....) 客户端ID
|
||||
*/
|
||||
String WEAPP_CLIENT_ID = "oneone-weapp";
|
||||
|
||||
|
||||
/**
|
||||
* 用户ID字段
|
||||
*/
|
||||
public static final String DETAILS_USER_ID = "user_id";
|
||||
|
||||
/**
|
||||
* 用户名字段
|
||||
*/
|
||||
public static final String DETAILS_USERNAME = "username";
|
||||
|
||||
/**
|
||||
* 授权信息字段
|
||||
*/
|
||||
public static final String AUTHORIZATION_HEADER = "authorization";
|
||||
|
||||
/**
|
||||
* 请求来源
|
||||
*/
|
||||
public static final String FROM_SOURCE = "from-source";
|
||||
|
||||
/**
|
||||
* 内部请求
|
||||
*/
|
||||
public static final String INNER = "inner";
|
||||
|
||||
/**
|
||||
* 用户标识
|
||||
*/
|
||||
public static final String USER_KEY = "user_key";
|
||||
|
||||
/**
|
||||
* 登录用户
|
||||
*/
|
||||
public static final String LOGIN_USER = "login_user";
|
||||
|
||||
/**
|
||||
* 角色权限
|
||||
*/
|
||||
public static final String ROLE_PERMISSION = "role_permission";
|
||||
|
||||
public static final String BACK_TOKEN = "back_token";
|
||||
|
||||
String MINI_APP_TOKEN_PREFIX = "mini ";
|
||||
String OFFICIAL_ACCOUNT_TOKEN_PREFIX = "official ";
|
||||
String APP_URI_PREFIX = "/app-api";
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.oneone.common.constant;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class ServiceNameConstants {
|
||||
/**
|
||||
* 认证服务的serviceid
|
||||
*/
|
||||
public static final String AUTH_SERVICE = "ruoyi-auth";
|
||||
|
||||
/**
|
||||
* 系统模块的serviceid
|
||||
*/
|
||||
public static final String SYSTEM_SERVICE = "ruoyi-system";
|
||||
|
||||
/**
|
||||
* 文件服务的serviceid
|
||||
*/
|
||||
public static final String FILE_SERVICE = "ruoyi-file";
|
||||
|
||||
public static final String VERIFY_SERVICE = "verification-system";
|
||||
|
||||
|
||||
public static final String PATH_INNER = "inner";
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.oneone.common.desensitize.base.annotation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.oneone.common.desensitize.base.handler.DesensitizationHandler;
|
||||
import com.oneone.common.desensitize.base.serializer.StringDesensitizeSerializer;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 顶级脱敏注解,自定义注解需要使用此注解
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
|
||||
@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
|
||||
public @interface DesensitizeBy {
|
||||
|
||||
/**
|
||||
* 脱敏处理器
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends DesensitizationHandler> handler();
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.oneone.common.desensitize.base.handler;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* 脱敏处理器接口
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public interface DesensitizationHandler<T extends Annotation> {
|
||||
|
||||
/**
|
||||
* 脱敏
|
||||
*
|
||||
* @param origin 原始字符串
|
||||
* @param annotation 注解信息
|
||||
* @return 脱敏后的字符串
|
||||
*/
|
||||
String desensitize(String origin, T annotation);
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package com.oneone.common.desensitize.base.serializer;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.lang.Singleton;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.base.handler.DesensitizationHandler;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 脱敏序列化器
|
||||
*
|
||||
* 实现 JSON 返回数据时,使用 {@link DesensitizationHandler} 对声明脱敏注解的字段,进行脱敏处理。
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Getter
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
|
||||
|
||||
@Setter
|
||||
private DesensitizationHandler desensitizationHandler;
|
||||
|
||||
protected StringDesensitizeSerializer() {
|
||||
super(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
|
||||
DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
|
||||
if (annotation == null) {
|
||||
return this;
|
||||
}
|
||||
// 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
|
||||
StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
|
||||
serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
}
|
||||
// 获取序列化字段
|
||||
Field field = getField(gen);
|
||||
|
||||
// 自定义处理器
|
||||
DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
|
||||
if (ArrayUtil.isEmpty(annotations)) {
|
||||
gen.writeString(value);
|
||||
return;
|
||||
}
|
||||
for (Annotation annotation : field.getAnnotations()) {
|
||||
if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
|
||||
value = this.desensitizationHandler.desensitize(value, annotation);
|
||||
gen.writeString(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
gen.writeString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段
|
||||
*
|
||||
* @param generator JsonGenerator
|
||||
* @return 字段
|
||||
*/
|
||||
private Field getField(JsonGenerator generator) {
|
||||
String currentName = generator.getOutputContext().getCurrentName();
|
||||
Object currentValue = generator.getCurrentValue();
|
||||
Class<?> currentValueClass = currentValue.getClass();
|
||||
return ReflectUtil.getField(currentValueClass, currentName);
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.oneone.common.desensitize.regex.annotation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.regex.handler.EmailDesensitizationHandler;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 邮箱脱敏注解
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = EmailDesensitizationHandler.class)
|
||||
public @interface EmailDesensitize {
|
||||
|
||||
/**
|
||||
* 匹配的正则表达式
|
||||
*/
|
||||
String regex() default "(^.)[^@]*(@.*$)";
|
||||
|
||||
/**
|
||||
* 替换规则,邮箱;
|
||||
*
|
||||
* 比如:example@gmail.com 脱敏之后为 e****@gmail.com
|
||||
*/
|
||||
String replacer() default "$1****$2";
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.oneone.common.desensitize.regex.annotation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.regex.handler.DefaultRegexDesensitizationHandler;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 正则脱敏注解
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)
|
||||
public @interface RegexDesensitize {
|
||||
|
||||
/**
|
||||
* 匹配的正则表达式(默认匹配所有)
|
||||
*/
|
||||
String regex() default "^[\\s\\S]*$";
|
||||
|
||||
/**
|
||||
* 替换规则,会将匹配到的字符串全部替换成 replacer
|
||||
*
|
||||
* 例如:regex=123; replacer=******
|
||||
* 原始字符串 123456789
|
||||
* 脱敏后字符串 ******456789
|
||||
*/
|
||||
String replacer() default "******";
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.oneone.common.desensitize.regex.handler;
|
||||
|
||||
import com.oneone.common.desensitize.base.handler.DesensitizationHandler;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* 正则表达式脱敏处理器抽象类,已实现通用的方法
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
|
||||
implements DesensitizationHandler<T> {
|
||||
|
||||
@Override
|
||||
public String desensitize(String origin, T annotation) {
|
||||
String regex = getRegex(annotation);
|
||||
String replacer = getReplacer(annotation);
|
||||
return origin.replaceAll(regex, replacer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解上的 regex 参数
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 正则表达式
|
||||
*/
|
||||
abstract String getRegex(T annotation);
|
||||
|
||||
/**
|
||||
* 获取注解上的 replacer 参数
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 待替换的字符串
|
||||
*/
|
||||
abstract String getReplacer(T annotation);
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.oneone.common.desensitize.regex.handler;
|
||||
|
||||
|
||||
import com.oneone.common.desensitize.regex.annotation.RegexDesensitize;
|
||||
|
||||
/**
|
||||
* {@link RegexDesensitize} 的正则脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler<RegexDesensitize> {
|
||||
|
||||
@Override
|
||||
String getRegex(RegexDesensitize annotation) {
|
||||
return annotation.regex();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(RegexDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.oneone.common.desensitize.regex.handler;
|
||||
|
||||
import com.oneone.common.desensitize.regex.annotation.EmailDesensitize;
|
||||
|
||||
/**
|
||||
* {@link EmailDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {
|
||||
|
||||
@Override
|
||||
String getRegex(EmailDesensitize annotation) {
|
||||
return annotation.regex();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(EmailDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.slider.handler.BankCardDesensitization;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 银行卡号
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = BankCardDesensitization.class)
|
||||
public @interface BankCardDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 6;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 2;
|
||||
|
||||
/**
|
||||
* 替换规则,银行卡号; 比如:9988002866797031 脱敏之后为 998800********31
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.slider.handler.CarLicenseDesensitization;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 车牌号
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = CarLicenseDesensitization.class)
|
||||
public @interface CarLicenseDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 3;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 1;
|
||||
|
||||
/**
|
||||
* 替换规则,车牌号;比如:粤A66666 脱敏之后为粤A6***6
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.slider.handler.ChineseNameDesensitization;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 中文名
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = ChineseNameDesensitization.class)
|
||||
public @interface ChineseNameDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 1;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 0;
|
||||
|
||||
/**
|
||||
* 替换规则,中文名;比如:刘子豪脱敏之后为刘**
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.oneone.common.desensitize.slider.handler.FixedPhoneDesensitization;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 固定电话
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = FixedPhoneDesensitization.class)
|
||||
public @interface FixedPhoneDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 4;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 2;
|
||||
|
||||
/**
|
||||
* 替换规则,固定电话;比如:01086551122 脱敏之后为 0108*****22
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.slider.handler.IdCardDesensitization;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 身份证
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = IdCardDesensitization.class)
|
||||
public @interface IdCardDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 6;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 2;
|
||||
|
||||
/**
|
||||
* 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.slider.handler.MobileDesensitization;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = MobileDesensitization.class)
|
||||
public @interface MobileDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 3;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 4;
|
||||
|
||||
/**
|
||||
* 替换规则,手机号;比如:13248765917 脱敏之后为 132****5917
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.slider.handler.PasswordDesensitization;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = PasswordDesensitization.class)
|
||||
public @interface PasswordDesensitize {
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 0;
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 0;
|
||||
|
||||
/**
|
||||
* 替换规则,密码;
|
||||
*
|
||||
* 比如:123456 脱敏之后为 ******
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.oneone.common.desensitize.slider.annotation;
|
||||
|
||||
import com.oneone.common.desensitize.base.annotation.DesensitizeBy;
|
||||
import com.oneone.common.desensitize.slider.handler.DefaultDesensitizationHandler;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 滑动脱敏注解
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@JacksonAnnotationsInside
|
||||
@DesensitizeBy(handler = DefaultDesensitizationHandler.class)
|
||||
public @interface SliderDesensitize {
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*/
|
||||
int suffixKeep() default 0;
|
||||
|
||||
/**
|
||||
* 替换规则,会将前缀后缀保留后,全部替换成 replacer
|
||||
*
|
||||
* 例如:prefixKeep = 1; suffixKeep = 2; replacer = "*";
|
||||
* 原始字符串 123456
|
||||
* 脱敏后 1***56
|
||||
*/
|
||||
String replacer() default "*";
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*/
|
||||
int prefixKeep() default 0;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
|
||||
import com.oneone.common.desensitize.base.handler.DesensitizationHandler;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* 滑动脱敏处理器抽象类,已实现通用的方法
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public abstract class AbstractDesensitizationHandler<T extends Annotation>
|
||||
implements DesensitizationHandler<T> {
|
||||
|
||||
@Override
|
||||
public String desensitize(String origin, T annotation) {
|
||||
int prefixKeep = getPrefixKeep(annotation);
|
||||
int suffixKeep = getSuffixKeep(annotation);
|
||||
String replacer = getReplacer(annotation);
|
||||
int length = origin.length();
|
||||
|
||||
// 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
|
||||
if (prefixKeep >= length || suffixKeep >= length) {
|
||||
return buildReplacerByLength(replacer, length);
|
||||
}
|
||||
|
||||
// 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
|
||||
if ((prefixKeep + suffixKeep) >= length) {
|
||||
return buildReplacerByLength(replacer, length);
|
||||
}
|
||||
|
||||
// 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
|
||||
int interval = length - prefixKeep - suffixKeep;
|
||||
return origin.substring(0, prefixKeep) +
|
||||
buildReplacerByLength(replacer, interval) +
|
||||
origin.substring(prefixKeep + interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度循环构建替换符
|
||||
*
|
||||
* @param replacer 替换符
|
||||
* @param length 长度
|
||||
* @return 构建后的替换符
|
||||
*/
|
||||
private String buildReplacerByLength(String replacer, int length) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
builder.append(replacer);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 前缀保留长度
|
||||
*/
|
||||
abstract Integer getPrefixKeep(T annotation);
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 后缀保留长度
|
||||
*/
|
||||
abstract Integer getSuffixKeep(T annotation);
|
||||
|
||||
/**
|
||||
* 替换符
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 替换符
|
||||
*/
|
||||
abstract String getReplacer(T annotation);
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.base.handler.DesensitizationHandler;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* 滑动脱敏处理器抽象类,已实现通用的方法
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
|
||||
implements DesensitizationHandler<T> {
|
||||
|
||||
@Override
|
||||
public String desensitize(String origin, T annotation) {
|
||||
int prefixKeep = getPrefixKeep(annotation);
|
||||
int suffixKeep = getSuffixKeep(annotation);
|
||||
String replacer = getReplacer(annotation);
|
||||
int length = origin.length();
|
||||
|
||||
// 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
|
||||
if (prefixKeep >= length || suffixKeep >= length) {
|
||||
return buildReplacerByLength(replacer, length);
|
||||
}
|
||||
|
||||
// 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
|
||||
if ((prefixKeep + suffixKeep) >= length) {
|
||||
return buildReplacerByLength(replacer, length);
|
||||
}
|
||||
|
||||
// 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
|
||||
int interval = length - prefixKeep - suffixKeep;
|
||||
return origin.substring(0, prefixKeep) +
|
||||
buildReplacerByLength(replacer, interval) +
|
||||
origin.substring(prefixKeep + interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度循环构建替换符
|
||||
*
|
||||
* @param replacer 替换符
|
||||
* @param length 长度
|
||||
* @return 构建后的替换符
|
||||
*/
|
||||
private String buildReplacerByLength(String replacer, int length) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
builder.append(replacer);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前缀保留长度
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 前缀保留长度
|
||||
*/
|
||||
abstract Integer getPrefixKeep(T annotation);
|
||||
|
||||
/**
|
||||
* 后缀保留长度
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 后缀保留长度
|
||||
*/
|
||||
abstract Integer getSuffixKeep(T annotation);
|
||||
|
||||
/**
|
||||
* 替换符
|
||||
*
|
||||
* @param annotation 注解信息
|
||||
* @return 替换符
|
||||
*/
|
||||
abstract String getReplacer(T annotation);
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.BankCardDesensitize;
|
||||
|
||||
/**
|
||||
* {@link BankCardDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class BankCardDesensitization extends AbstractSliderDesensitizationHandler<BankCardDesensitize> {
|
||||
|
||||
@Override
|
||||
Integer getPrefixKeep(BankCardDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(BankCardDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(BankCardDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.CarLicenseDesensitize;
|
||||
|
||||
/**
|
||||
* {@link CarLicenseDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {
|
||||
@Override
|
||||
Integer getPrefixKeep(CarLicenseDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(CarLicenseDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(CarLicenseDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.ChineseNameDesensitize;
|
||||
|
||||
/**
|
||||
* {@link ChineseNameDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler<ChineseNameDesensitize> {
|
||||
|
||||
@Override
|
||||
Integer getPrefixKeep(ChineseNameDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(ChineseNameDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(ChineseNameDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.SliderDesensitize;
|
||||
|
||||
/**
|
||||
* {@link SliderDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {
|
||||
@Override
|
||||
Integer getPrefixKeep(SliderDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(SliderDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(SliderDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.FixedPhoneDesensitize;
|
||||
|
||||
/**
|
||||
* {@link FixedPhoneDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {
|
||||
@Override
|
||||
Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(FixedPhoneDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(FixedPhoneDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.IdCardDesensitize;
|
||||
|
||||
/**
|
||||
* {@link IdCardDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class IdCardDesensitization extends AbstractSliderDesensitizationHandler<IdCardDesensitize> {
|
||||
@Override
|
||||
Integer getPrefixKeep(IdCardDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(IdCardDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(IdCardDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.MobileDesensitize;
|
||||
|
||||
/**
|
||||
* {@link MobileDesensitize} 的脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class MobileDesensitization extends AbstractSliderDesensitizationHandler<MobileDesensitize> {
|
||||
|
||||
@Override
|
||||
Integer getPrefixKeep(MobileDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(MobileDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(MobileDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.desensitize.slider.handler;
|
||||
|
||||
import com.oneone.common.desensitize.slider.annotation.PasswordDesensitize;
|
||||
|
||||
/**
|
||||
* {@link PasswordDesensitize} 的码脱敏处理器
|
||||
*
|
||||
* @author gaibu
|
||||
*/
|
||||
public class PasswordDesensitization extends AbstractSliderDesensitizationHandler<PasswordDesensitize> {
|
||||
@Override
|
||||
Integer getPrefixKeep(PasswordDesensitize annotation) {
|
||||
return annotation.prefixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer getSuffixKeep(PasswordDesensitize annotation) {
|
||||
return annotation.suffixKeep();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getReplacer(PasswordDesensitize annotation) {
|
||||
return annotation.replacer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.oneone.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @description
|
||||
* @createTime 2021/6/5 17:57
|
||||
*/
|
||||
|
||||
@Getter
|
||||
public enum AccountTypeEnum {
|
||||
|
||||
MOBILE("mobile","手机"),
|
||||
USERNAME("username","账号"),
|
||||
EMAIL("email","邮件"),
|
||||
TWITTER("twitter","twitter"),
|
||||
WALLET("wallet","钱包登录"),
|
||||
ALIPAY("alipay","支付宝登录"),
|
||||
WECHAT("wechat","微信登录"),
|
||||
MINI_APP("mini_app","微信小程序"),
|
||||
OFFICIAL_ACCOUNT("official_account","微信公众号"),
|
||||
APPLE_JWT("apple","苹果jwt"),
|
||||
DEVICE("device","设备id,游客登录"),
|
||||
FACEBOOK("facebook","脸书登录"),
|
||||
GOOGLE("google","谷歌登录"),
|
||||
;
|
||||
|
||||
public static AccountTypeEnum getByCode(String code){
|
||||
for (AccountTypeEnum value : values()) {
|
||||
if (value.getCode().equals(code)){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
AccountTypeEnum(String code, String desc){
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.oneone.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 区域类型枚举
|
||||
*
|
||||
*
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum AreaTypeEnum {
|
||||
|
||||
COUNTRY(1, "国家"),
|
||||
PROVINCE(2, "省份"),
|
||||
CITY(3, "城市"),
|
||||
DISTRICT(4, "地区"), // 县、镇、区等
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.oneone.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum ChannelEnum {
|
||||
LOCAL("local","本地"),
|
||||
|
||||
;
|
||||
|
||||
public static ChannelEnum getByCode(String code){
|
||||
for (ChannelEnum value : values()) {
|
||||
if (value.getCode().equals(code)){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
ChannelEnum(String code, String desc){
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.oneone.common.enums;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CommonStatusEnum {
|
||||
|
||||
ENABLE(0, "开启" ),
|
||||
DISABLE(1, "关闭" );
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static boolean isEnable(Integer status) {
|
||||
return ObjUtil.equal(ENABLE.status, status);
|
||||
}
|
||||
|
||||
public static boolean isDisable(Integer status) {
|
||||
return ObjUtil.equal(DISABLE.status, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.oneone.common.enums;
|
||||
|
||||
/**
|
||||
* 可生成 Int 数组的接口
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface IntArrayValuable {
|
||||
|
||||
/**
|
||||
* @return int 数组
|
||||
*/
|
||||
int[] array();
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.oneone.common.exception;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.oneone.common.result.BaseEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
public BaseEnum resultCode;
|
||||
|
||||
|
||||
public BusinessException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause){
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BusinessException(Throwable cause){
|
||||
super(cause);
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private String code;
|
||||
|
||||
public BusinessException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(BaseEnum baseEnum) {
|
||||
super(baseEnum.getMsg());
|
||||
this.code = baseEnum.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode){
|
||||
super(errorCode.getMsg());
|
||||
this.code = errorCode.getCode()+"";
|
||||
}
|
||||
|
||||
public static BusinessException exception(ErrorCode errorCode) {
|
||||
return new BusinessException(errorCode.getCode()+"", errorCode.getMsg());
|
||||
}
|
||||
|
||||
public static BusinessException exception(ErrorCode errorCode, Object... params) {
|
||||
String message = doFormat(errorCode.getCode(), errorCode.getMsg(), params);
|
||||
return new BusinessException(errorCode.getCode()+"", message);
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
public 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) {
|
||||
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
if (i == 0) {
|
||||
return messagePattern;
|
||||
} else {
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
} else {
|
||||
sbuf.append(messagePattern, i, j);
|
||||
sbuf.append(params[l]);
|
||||
i = j + 2;
|
||||
}
|
||||
}
|
||||
if (messagePattern.indexOf("{}", i) != -1) {
|
||||
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
}
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.oneone.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 错误码对象
|
||||
*
|
||||
* 全局错误码,占用 [0, 999], 参见 {@link }
|
||||
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link }
|
||||
*
|
||||
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
|
||||
*/
|
||||
@Data
|
||||
public class ErrorCode {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private final String msg;
|
||||
|
||||
public ErrorCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.msg = message;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.exception;
|
||||
|
||||
public class IdempotentException extends RuntimeException {
|
||||
|
||||
public IdempotentException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IdempotentException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdempotentException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IdempotentException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
protected IdempotentException(String message, Throwable cause, boolean enableSuppression,
|
||||
boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.oneone.common.exception;
|
||||
|
||||
import com.oneone.common.result.BaseEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LockFailedException extends RuntimeException {
|
||||
|
||||
public BaseEnum resultCode;
|
||||
|
||||
|
||||
public LockFailedException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public LockFailedException(String message, Throwable cause){
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public LockFailedException(Throwable cause){
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
*
|
||||
* @Date: 2018/11/27 17:40
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
public class SocketBusinessException extends RuntimeException {
|
||||
private static final long serialVersionUID = 2332608236621015980L;
|
||||
|
||||
private String code;
|
||||
// 协议号
|
||||
private String cmd;
|
||||
|
||||
public SocketBusinessException(String cmd, String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.oneone.common.jsonserializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class LocalDateTimeToDateSerializer extends JsonSerializer<LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
if(date!=null){
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
jsonGenerator.writeString(formatter.format(date));
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.jsonserializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.oneone.common.util.Func;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class LocalDateTimeToLocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||
String timestamp = jsonParser.getValueAsString();
|
||||
if (Func.isEmpty(timestamp)) {
|
||||
return null;
|
||||
}
|
||||
return LocalDate.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atStartOfDay();
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.oneone.common.jsonserializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
public class LocalDateTimeToLongDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||
long timestamp = jsonParser.getValueAsLong();
|
||||
if (timestamp == 0) {
|
||||
return null;
|
||||
}
|
||||
Instant instant = Instant.ofEpochMilli(timestamp);
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.oneone.common.jsonserializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
public class LocalDateTimeToLongSerializer extends JsonSerializer<LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeNumber(date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.oneone.common.jsonserializer;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
public class LocalDateTimeToStringSerializer extends JsonSerializer<LocalDateTime> {
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
|
||||
jsonGenerator.writeString(LocalDateTimeUtil.formatNormal(date));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.oneone.common.result;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
**/
|
||||
public interface BaseEnum {
|
||||
|
||||
String getCode();
|
||||
|
||||
String getMsg();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.oneone.common.result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-01 15:44
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class BasePage<T> {
|
||||
|
||||
private int pageNum;
|
||||
private int pageSize;
|
||||
private long total;
|
||||
|
||||
private List<T> list;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.oneone.common.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "分页结果")
|
||||
@Data
|
||||
public final class PageResult<T> implements Serializable {
|
||||
|
||||
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<T> list;
|
||||
|
||||
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long total;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(List<T> list, Long total) {
|
||||
this.list = list;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public PageResult(Long total) {
|
||||
this.list = new ArrayList<>();
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> empty() {
|
||||
return new PageResult<>(0L);
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> empty(Long total) {
|
||||
return new PageResult<>(total);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.oneone.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结构体
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2022/1/30
|
||||
**/
|
||||
@Data
|
||||
@ToString
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private String code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String msg;
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed() {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(String msg) {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> judge(boolean status) {
|
||||
if (status) {
|
||||
return success();
|
||||
} else {
|
||||
return failed();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(BaseEnum resultCode) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(BaseEnum resultCode, String msg) {
|
||||
return result(resultCode.getCode(), msg, null);
|
||||
}
|
||||
|
||||
public static Result failed(String code, String msg) {
|
||||
Result result = new Result();
|
||||
result.setCode(code);
|
||||
result.setMsg(msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(BaseEnum resultCode, T data) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), data);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(String code, String msg, T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(code);
|
||||
result.setData(data);
|
||||
result.setMsg(msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(Result<?> result) {
|
||||
return result != null && (ResultCode.SUCCESS.getCode().equals(result.getCode())
|
||||
|| ResultCode.SUCCESS_200.getCode().equals(result.getCode())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出
|
||||
* 如果没有,则返回 {@link #data} 数据
|
||||
*/
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public T getCheckedData() {
|
||||
checkError();
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出 {} 异常
|
||||
*/
|
||||
public void checkError() throws BusinessException {
|
||||
if (isSuccess()) {
|
||||
return;
|
||||
}
|
||||
// 业务异常
|
||||
throw new BusinessException(code, msg);
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isSuccess() {
|
||||
return (ResultCode.SUCCESS.getCode().equals(code)
|
||||
|| ResultCode.SUCCESS_200.getCode().equals(code)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.oneone.common.result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @date 2020-06-23
|
||||
**/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum ResultCode implements BaseEnum, Serializable {
|
||||
|
||||
SUCCESS("0", ""),
|
||||
SUCCESS_200("200", ""),
|
||||
TOKEN_INVALID_OR_EXPIRED("401", "token无效或已过期"),
|
||||
SYSTEM_EXECUTION_ERROR("1000", "系统太忙碌了,请稍后再试"),
|
||||
VALIDATE_CODE_ERROR("1001", "验证码错误"),
|
||||
IMAGE_VALIDATE_CODE_ERROR("1002", "图形验证码错误"),
|
||||
TOKEN_ACCESS_FORBIDDEN("1003", "token已被禁止访问"),
|
||||
PARAM_ERROR("1004", "用户请求参数错误"),
|
||||
RESOURCE_NOT_FOUND("1005", "请求资源不存在"),
|
||||
PARAM_IS_NULL("1006", "请求必填参数为空"),
|
||||
;
|
||||
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
private String code;
|
||||
|
||||
private String msg;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"\"code\":\"" + code + '\"' +
|
||||
", \"msg\":\"" + msg + '\"' +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
public static ResultCode getValue(String code) {
|
||||
for (ResultCode value : values()) {
|
||||
if (value.getCode().equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return SYSTEM_EXECUTION_ERROR; // 默认系统执行错误
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.oneone.common.server;
|
||||
|
||||
import com.oneone.common.util.INetUtil;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 服务器信息
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Getter
|
||||
@Configuration
|
||||
@Component
|
||||
public class ServerInfo implements SmartInitializingSingleton {
|
||||
@Value("${spring.application.name}")
|
||||
private String applicationName;
|
||||
|
||||
private final ServerProperties serverProperties;
|
||||
private String hostName;
|
||||
private String ip;
|
||||
private Integer port;
|
||||
private String ipWithPort;
|
||||
|
||||
@Autowired(required = false)
|
||||
public ServerInfo(ServerProperties serverProperties) {
|
||||
this.serverProperties = serverProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.hostName = INetUtil.getHostName();
|
||||
this.ip = INetUtil.getHostIp();
|
||||
this.port = serverProperties.getPort();
|
||||
this.ipWithPort = String.format("%s:%d", ip, port);
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2015 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.oneone.common.util;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class AddressFormatException extends IllegalArgumentException {
|
||||
public AddressFormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AddressFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and a character isn't valid. You shouldn't allow the user to proceed in this
|
||||
* case.
|
||||
*/
|
||||
public static class InvalidCharacter extends AddressFormatException {
|
||||
public final char character;
|
||||
public final int position;
|
||||
|
||||
public InvalidCharacter(char character, int position) {
|
||||
super("Invalid character '" + Character.toString(character) + "' at position " + position);
|
||||
this.character = character;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and the data isn't of the right size. You shouldn't allow the user to proceed
|
||||
* in this case.
|
||||
*/
|
||||
public static class InvalidDataLength extends AddressFormatException {
|
||||
public InvalidDataLength() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidDataLength(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link Base58}, {@link Bech32} and the {@link PrefixedChecksummedBytes} hierarchy of
|
||||
* classes when you try to decode data and the checksum isn't valid. You shouldn't allow the user to proceed in this
|
||||
* case.
|
||||
*/
|
||||
public static class InvalidChecksum extends AddressFormatException {
|
||||
public InvalidChecksum() {
|
||||
super("Checksum does not validate");
|
||||
}
|
||||
|
||||
public InvalidChecksum(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by {@link SegwitAddress} when you try to decode data and the witness version doesn't
|
||||
* match the Bech32 encoding as per BIP350. You shouldn't allow the user to proceed in this case.
|
||||
*/
|
||||
public static class UnexpectedWitnessVersion extends AddressFormatException {
|
||||
public UnexpectedWitnessVersion() {
|
||||
super("Unexpected witness version");
|
||||
}
|
||||
|
||||
public UnexpectedWitnessVersion(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by the {@link PrefixedChecksummedBytes} hierarchy of classes when you try and decode an
|
||||
* address or private key with an invalid prefix (version header or human-readable part). You shouldn't allow the
|
||||
* user to proceed in this case.
|
||||
*/
|
||||
public static class InvalidPrefix extends AddressFormatException {
|
||||
public InvalidPrefix() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidPrefix(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This exception is thrown by the {@link PrefixedChecksummedBytes} hierarchy of classes when you try and decode an
|
||||
* address with a prefix (version header or human-readable part) that used by another network (usually: mainnet vs
|
||||
* testnet). You shouldn't allow the user to proceed in this case as they are trying to send money across different
|
||||
* chains, an operation that is guaranteed to destroy the money.
|
||||
*/
|
||||
public static class WrongNetwork extends InvalidPrefix {
|
||||
public WrongNetwork(int versionHeader) {
|
||||
super("Version code of address did not match acceptable versions for network: " + versionHeader);
|
||||
}
|
||||
|
||||
public WrongNetwork(String hrp) {
|
||||
super("Human readable part of address did not match acceptable HRPs for network: " + hrp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.csv.CsvRow;
|
||||
import cn.hutool.core.text.csv.CsvUtil;
|
||||
import com.oneone.common.base.Area;
|
||||
import com.oneone.common.enums.AreaTypeEnum;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.oneone.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
|
||||
/**
|
||||
* 区域工具类
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class AreaUtils {
|
||||
|
||||
/**
|
||||
* 初始化 SEARCHER
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
private final static AreaUtils INSTANCE = new AreaUtils();
|
||||
|
||||
/**
|
||||
* IP 查询器,启动加载到内存中
|
||||
*/
|
||||
private static Searcher SEARCHER;
|
||||
|
||||
|
||||
/**
|
||||
* Area 内存缓存,提升访问速度
|
||||
*/
|
||||
private static Map<Integer, Area> areas;
|
||||
|
||||
private AreaUtils() {
|
||||
long now = System.currentTimeMillis();
|
||||
areas = new HashMap<>();
|
||||
areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0,
|
||||
null, new ArrayList<>()));
|
||||
// 从 csv 中加载数据
|
||||
List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
|
||||
rows.remove(0); // 删除 header
|
||||
for (CsvRow row : rows) {
|
||||
// 创建 Area 对象
|
||||
Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),
|
||||
null, new ArrayList<>());
|
||||
// 添加到 areas 中
|
||||
areas.put(area.getId(), area);
|
||||
}
|
||||
|
||||
// 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
|
||||
for (CsvRow row : rows) {
|
||||
Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
|
||||
Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
|
||||
Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
|
||||
area.setParent(parent);
|
||||
parent.getChildren().add(area);
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = ResourceUtil.readBytes("ip2region.xdb");
|
||||
SEARCHER = Searcher.newWithBuffer(bytes);
|
||||
} catch (IOException e) {
|
||||
log.error("启动加载 AreaUtils 失败", e);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定编号对应的区域
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @return 区域
|
||||
*/
|
||||
public static Area getArea(Integer id) {
|
||||
return areas.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化区域
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @return 格式化后的区域
|
||||
*/
|
||||
public static String format(Integer id) {
|
||||
return format(id, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化区域
|
||||
*
|
||||
* 例如说:
|
||||
* 1. id = “静安区”时:上海 上海市 静安区
|
||||
* 2. id = “上海市”时:上海 上海市
|
||||
* 3. id = “上海”时:上海
|
||||
* 4. id = “美国”时:美国
|
||||
* 当区域在中国时,默认不显示中国
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @param separator 分隔符
|
||||
* @return 格式化后的区域
|
||||
*/
|
||||
public static String format(Integer id, String separator) {
|
||||
// 获得区域
|
||||
Area area = areas.get(id);
|
||||
if (area == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 格式化
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环
|
||||
sb.insert(0, area.getName());
|
||||
// “递归”父节点
|
||||
area = area.getParent();
|
||||
if (area == null
|
||||
|| ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
|
||||
break;
|
||||
}
|
||||
sb.insert(0, separator);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的区域列表
|
||||
*
|
||||
* @param type 区域类型
|
||||
* @param func 转换函数
|
||||
* @param <T> 结果类型
|
||||
* @return 区域列表
|
||||
*/
|
||||
public static <T> List<T> getByType(AreaTypeEnum type, Function<Area, T> func) {
|
||||
return convertList(areas.values(), func, area -> type.getType().equals(area.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域编号、上级区域类型,获取上级区域编号
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @param type 区域类型
|
||||
* @return 上级区域编号
|
||||
*/
|
||||
public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
|
||||
for (int i = 0; i < Byte.MAX_VALUE; i++) {
|
||||
Area area = AreaUtils.getArea(id);
|
||||
if (area == null) {
|
||||
return null;
|
||||
}
|
||||
// 情况一:匹配到,返回它
|
||||
if (type.getType().equals(area.getType())) {
|
||||
return area.getId();
|
||||
}
|
||||
// 情况二:找到根节点,返回空
|
||||
if (area.getParent() == null || area.getParent().getId() == null) {
|
||||
return null;
|
||||
}
|
||||
// 其它:继续向上查找
|
||||
id = area.getParent().getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区编号
|
||||
*
|
||||
* @param ip IP 地址,格式为 127.0.0.1
|
||||
* @return 地区id
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Integer getAreaId(String ip) {
|
||||
return Integer.parseInt(SEARCHER.search(ip.trim()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区编号
|
||||
*
|
||||
* @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
|
||||
* @return 地区编号
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Integer getAreaId(long ip) {
|
||||
return Integer.parseInt(SEARCHER.search(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区
|
||||
*
|
||||
* @param ip IP 地址,格式为 127.0.0.1
|
||||
* @return 地区
|
||||
*/
|
||||
public static Area getArea(String ip) {
|
||||
return AreaUtils.getArea(getAreaId(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区
|
||||
*
|
||||
* @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
|
||||
* @return 地区
|
||||
*/
|
||||
public static Area getArea(long ip) {
|
||||
return AreaUtils.getArea(getAreaId(ip));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2018 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.oneone.common.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base58 is a way to encode Bitcoin addresses (or arbitrary data) as alphanumeric strings.
|
||||
* <p>
|
||||
* Note that this is not the same base58 as used by Flickr, which you may find referenced around the Internet.
|
||||
* <p>
|
||||
* You may want to consider working with { PrefixedChecksummedBytes} instead, which
|
||||
* adds support for testing the prefix and suffix bytes commonly found in addresses.
|
||||
* <p>
|
||||
* Satoshi explains: why base-58 instead of standard base-64 encoding?
|
||||
* <ul>
|
||||
* <li>Don't want 0OIl characters that look the same in some fonts and
|
||||
* could be used to create visually identical looking account numbers.</li>
|
||||
* <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
|
||||
* <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
|
||||
* <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* However, note that the encoding/decoding runs in O(n²) time, so it is not useful for large data.
|
||||
* <p>
|
||||
* The basic idea of the encoding is to treat the data bytes as a large number represented using
|
||||
* base-256 digits, convert the number to be represented using base-58 digits, preserve the exact
|
||||
* number of leading zeros (which are otherwise lost during the mathematical operations on the
|
||||
* numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters.
|
||||
*/
|
||||
public class Base58 {
|
||||
public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||
private static final char ENCODED_ZERO = ALPHABET[0];
|
||||
private static final int[] INDEXES = new int[128];
|
||||
static {
|
||||
Arrays.fill(INDEXES, -1);
|
||||
for (int i = 0; i < ALPHABET.length; i++) {
|
||||
INDEXES[ALPHABET[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given bytes as a base58 string (no checksum is appended).
|
||||
*
|
||||
* @param input the bytes to encode
|
||||
* @return the base58-encoded string
|
||||
*/
|
||||
public static String encode(byte[] input) {
|
||||
if (input.length == 0) {
|
||||
return "";
|
||||
}
|
||||
// Count leading zeros.
|
||||
int zeros = 0;
|
||||
while (zeros < input.length && input[zeros] == 0) {
|
||||
++zeros;
|
||||
}
|
||||
// Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
|
||||
input = Arrays.copyOf(input, input.length); // since we modify it in-place
|
||||
char[] encoded = new char[input.length * 2]; // upper bound
|
||||
int outputStart = encoded.length;
|
||||
for (int inputStart = zeros; inputStart < input.length; ) {
|
||||
encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
|
||||
if (input[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
|
||||
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
|
||||
++outputStart;
|
||||
}
|
||||
while (--zeros >= 0) {
|
||||
encoded[--outputStart] = ENCODED_ZERO;
|
||||
}
|
||||
// Return encoded string (including encoded leading zeros).
|
||||
return new String(encoded, outputStart, encoded.length - outputStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given version and bytes as a base58 string. A checksum is appended.
|
||||
*
|
||||
* @param version the version to encode
|
||||
* @param payload the bytes to encode, e.g. pubkey hash
|
||||
* @return the base58-encoded string
|
||||
*/
|
||||
public static String encodeChecked(int version, byte[] payload) {
|
||||
if (version < 0 || version > 255)
|
||||
throw new IllegalArgumentException("Version not in range.");
|
||||
|
||||
// A stringified buffer is:
|
||||
// 1 byte version + data bytes + 4 bytes check code (a truncated hash)
|
||||
byte[] addressBytes = new byte[1 + payload.length + 4];
|
||||
addressBytes[0] = (byte) version;
|
||||
System.arraycopy(payload, 0, addressBytes, 1, payload.length);
|
||||
byte[] checksum = hashTwice(addressBytes, 0, payload.length + 1);
|
||||
System.arraycopy(checksum, 0, addressBytes, payload.length + 1, 4);
|
||||
return Base58.encode(addressBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the given base58 string into the original data bytes.
|
||||
*
|
||||
* @param input the base58-encoded string to decode
|
||||
* @return the decoded data bytes
|
||||
* @throws AddressFormatException if the given string is not a valid base58 string
|
||||
*/
|
||||
public static byte[] decode(String input) throws AddressFormatException {
|
||||
if (input.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
|
||||
byte[] input58 = new byte[input.length()];
|
||||
for (int i = 0; i < input.length(); ++i) {
|
||||
char c = input.charAt(i);
|
||||
int digit = c < 128 ? INDEXES[c] : -1;
|
||||
if (digit < 0) {
|
||||
throw new AddressFormatException.InvalidCharacter(c, i);
|
||||
}
|
||||
input58[i] = (byte) digit;
|
||||
}
|
||||
// Count leading zeros.
|
||||
int zeros = 0;
|
||||
while (zeros < input58.length && input58[zeros] == 0) {
|
||||
++zeros;
|
||||
}
|
||||
// Convert base-58 digits to base-256 digits.
|
||||
byte[] decoded = new byte[input.length()];
|
||||
int outputStart = decoded.length;
|
||||
for (int inputStart = zeros; inputStart < input58.length; ) {
|
||||
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
|
||||
if (input58[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Ignore extra leading zeroes that were added during the calculation.
|
||||
while (outputStart < decoded.length && decoded[outputStart] == 0) {
|
||||
++outputStart;
|
||||
}
|
||||
// Return decoded data (including original number of leading zeros).
|
||||
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
|
||||
}
|
||||
|
||||
public static BigInteger decodeToBigInteger(String input) throws AddressFormatException {
|
||||
return new BigInteger(1, decode(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the given base58 string into the original data bytes, using the checksum in the
|
||||
* last 4 bytes of the decoded data to verify that the rest are correct. The checksum is
|
||||
* removed from the returned data.
|
||||
*
|
||||
* @param input the base58-encoded string to decode (which should include the checksum)
|
||||
* @throws AddressFormatException if the input is not base 58 or the checksum does not validate.
|
||||
*/
|
||||
public static byte[] decodeChecked(String input) throws AddressFormatException {
|
||||
byte[] decoded = decode(input);
|
||||
if (decoded.length < 4)
|
||||
throw new AddressFormatException.InvalidDataLength("Input too short: " + decoded.length);
|
||||
byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4);
|
||||
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length);
|
||||
byte[] actualChecksum = Arrays.copyOfRange(hashTwice(data), 0, 4);
|
||||
if (!Arrays.equals(checksum, actualChecksum))
|
||||
throw new AddressFormatException.InvalidChecksum();
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides a number, represented as an array of bytes each containing a single digit
|
||||
* in the specified base, by the given divisor. The given number is modified in-place
|
||||
* to contain the quotient, and the return value is the remainder.
|
||||
*
|
||||
* @param number the number to divide
|
||||
* @param firstDigit the index within the array of the first non-zero digit
|
||||
* (this is used for optimization by skipping the leading zeros)
|
||||
* @param base the base in which the number's digits are represented (up to 256)
|
||||
* @param divisor the number to divide by (up to 256)
|
||||
* @return the remainder of the division operation
|
||||
*/
|
||||
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
|
||||
// this is just long division which accounts for the base of the input digits
|
||||
int remainder = 0;
|
||||
for (int i = firstDigit; i < number.length; i++) {
|
||||
int digit = (int) number[i] & 0xFF;
|
||||
int temp = remainder * base + digit;
|
||||
number[i] = (byte) (temp / divisor);
|
||||
remainder = temp % divisor;
|
||||
}
|
||||
return (byte) remainder;
|
||||
}
|
||||
|
||||
public static byte[] hashTwice(byte[] input, int offset, int length) {
|
||||
MessageDigest digest = newDigest();
|
||||
digest.update(input, offset, length);
|
||||
return digest.digest(digest.digest());
|
||||
}
|
||||
|
||||
public static byte[] hashTwice(byte[] input) {
|
||||
return hashTwice(input,0,input.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new SHA-256 MessageDigest instance.
|
||||
*
|
||||
* This is a convenience method which wraps the checked
|
||||
* exception that can never occur with a RuntimeException.
|
||||
*
|
||||
* @return a new SHA-256 MessageDigest instance
|
||||
*/
|
||||
public static MessageDigest newDigest() {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e); // Can't happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.cglib.core.*;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* spring cglib 魔改
|
||||
*
|
||||
* <p>
|
||||
* 1. 支持链式 bean
|
||||
* 2. 自定义的 BeanCopier 解决 spring boot 和 cglib ClassLoader classLoader 不一致的问题
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public abstract class BaseBeanCopier {
|
||||
private static final BeanCopierKey KEY_FACTORY = (BeanCopierKey) KeyFactory.create(BeanCopierKey.class);
|
||||
private static final Type CONVERTER = TypeUtils.parseType("org.springframework.cglib.core.Converter");
|
||||
private static final Type BEAN_COPIER = TypeUtils.parseType(BaseBeanCopier.class.getName());
|
||||
private static final Signature COPY = new Signature("copy", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER});
|
||||
private static final Signature CONVERT = TypeUtils.parseSignature("Object convert(Object, Class, Object)");
|
||||
|
||||
interface BeanCopierKey {
|
||||
/**
|
||||
* 实例化
|
||||
* @param source 源
|
||||
* @param target 目标
|
||||
* @param useConverter 是否使用转换
|
||||
* @return
|
||||
*/
|
||||
Object newInstance(String source, String target, boolean useConverter);
|
||||
}
|
||||
|
||||
public static BaseBeanCopier create(Class source, Class target, boolean useConverter) {
|
||||
return BaseBeanCopier.create(source, target, null, useConverter);
|
||||
}
|
||||
|
||||
public static BaseBeanCopier create(Class source, Class target, ClassLoader classLoader, boolean useConverter) {
|
||||
Generator gen;
|
||||
if (classLoader == null) {
|
||||
gen = new Generator();
|
||||
} else {
|
||||
gen = new Generator(classLoader);
|
||||
}
|
||||
gen.setSource(source);
|
||||
gen.setTarget(target);
|
||||
gen.setUseConverter(useConverter);
|
||||
return gen.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝
|
||||
* @param from 源
|
||||
* @param to 目标
|
||||
* @param converter 转换器
|
||||
*/
|
||||
abstract public void copy(Object from, Object to, Converter converter);
|
||||
|
||||
public static class Generator extends AbstractClassGenerator {
|
||||
private static final Source SOURCE = new Source(BaseBeanCopier.class.getName());
|
||||
private final ClassLoader classLoader;
|
||||
private Class source;
|
||||
private Class target;
|
||||
private boolean useConverter;
|
||||
|
||||
Generator() {
|
||||
super(SOURCE);
|
||||
this.classLoader = null;
|
||||
}
|
||||
|
||||
Generator(ClassLoader classLoader) {
|
||||
super(SOURCE);
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public void setSource(Class source) {
|
||||
if (!Modifier.isPublic(source.getModifiers())) {
|
||||
setNamePrefix(source.getName());
|
||||
}
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public void setTarget(Class target) {
|
||||
if (!Modifier.isPublic(target.getModifiers())) {
|
||||
setNamePrefix(target.getName());
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void setUseConverter(boolean useConverter) {
|
||||
this.useConverter = useConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader getDefaultClassLoader() {
|
||||
return target.getClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProtectionDomain getProtectionDomain() {
|
||||
return ReflectUtils.getProtectionDomain(source);
|
||||
}
|
||||
|
||||
public BaseBeanCopier create() {
|
||||
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
|
||||
return (BaseBeanCopier) super.create(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateClass(ClassVisitor v) {
|
||||
Type sourceType = Type.getType(source);
|
||||
Type targetType = Type.getType(target);
|
||||
ClassEmitter ce = new ClassEmitter(v);
|
||||
ce.begin_class(Constants.V1_2,
|
||||
Constants.ACC_PUBLIC,
|
||||
getClassName(),
|
||||
BEAN_COPIER,
|
||||
null,
|
||||
Constants.SOURCE_FILE);
|
||||
|
||||
EmitUtils.null_constructor(ce);
|
||||
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
|
||||
|
||||
// 2018.12.27 by L.cm 支持链式 bean
|
||||
PropertyDescriptor[] getters = BeanUtil.getBeanGetters(source);
|
||||
PropertyDescriptor[] setters = BeanUtil.getBeanSetters(target);
|
||||
Map<String, Object> names = new HashMap<String, Object>(16);
|
||||
for (PropertyDescriptor getter : getters) {
|
||||
names.put(getter.getName(), getter);
|
||||
}
|
||||
|
||||
Local targetLocal = e.make_local();
|
||||
Local sourceLocal = e.make_local();
|
||||
e.load_arg(1);
|
||||
e.checkcast(targetType);
|
||||
e.store_local(targetLocal);
|
||||
e.load_arg(0);
|
||||
e.checkcast(sourceType);
|
||||
e.store_local(sourceLocal);
|
||||
|
||||
for (int i = 0; i < setters.length; i++) {
|
||||
PropertyDescriptor setter = setters[i];
|
||||
PropertyDescriptor getter = (PropertyDescriptor) names.get(setter.getName());
|
||||
if (getter != null) {
|
||||
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
|
||||
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
|
||||
if (useConverter) {
|
||||
Type setterType = write.getSignature().getArgumentTypes()[0];
|
||||
e.load_local(targetLocal);
|
||||
e.load_arg(2);
|
||||
e.load_local(sourceLocal);
|
||||
e.invoke(read);
|
||||
e.box(read.getSignature().getReturnType());
|
||||
EmitUtils.load_class(e, setterType);
|
||||
e.push(write.getSignature().getName());
|
||||
e.invoke_interface(CONVERTER, CONVERT);
|
||||
e.unbox_or_zero(setterType);
|
||||
e.invoke(write);
|
||||
} else if (compatible(getter, setter)) {
|
||||
// 2018.12.27 by L.cm 支持链式 bean
|
||||
e.load_local(targetLocal);
|
||||
e.load_local(sourceLocal);
|
||||
e.invoke(read);
|
||||
e.invoke(write);
|
||||
}
|
||||
}
|
||||
}
|
||||
e.return_value();
|
||||
e.end_method();
|
||||
ce.end_class();
|
||||
}
|
||||
|
||||
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
|
||||
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object firstInstance(Class type) {
|
||||
return ReflectUtils.newInstance(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object nextInstance(Object instance) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cglib.beans.BeanGenerator;
|
||||
import org.springframework.cglib.beans.BeanMap;
|
||||
import org.springframework.cglib.core.CodeGenerationException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 实体工具类
|
||||
*
|
||||
*/
|
||||
public class BeanUtil extends org.springframework.beans.BeanUtils {
|
||||
|
||||
/**
|
||||
* 实例化对象
|
||||
* @param clazz 类
|
||||
* @param <T> 泛型标记
|
||||
* @return 对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T newInstance(Class<?> clazz) {
|
||||
return (T) instantiateClass(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化对象
|
||||
* @param clazzStr 类名
|
||||
* @param <T> 泛型标记
|
||||
* @return 对象
|
||||
*/
|
||||
public static <T> T newInstance(String clazzStr) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(clazzStr);
|
||||
return newInstance(clazz);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean的属性
|
||||
* @param bean bean
|
||||
* @param propertyName 属性名
|
||||
* @return 属性值
|
||||
*/
|
||||
public static Object getProperty(Object bean, String propertyName) {
|
||||
Assert.notNull(bean, "bean Could not null");
|
||||
return BeanMap.create(bean).get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Bean属性
|
||||
* @param bean bean
|
||||
* @param propertyName 属性名
|
||||
* @param value 属性值
|
||||
*/
|
||||
public static void setProperty(Object bean, String propertyName, Object value) {
|
||||
Assert.notNull(bean, "bean Could not null");
|
||||
BeanMap.create(bean).put(propertyName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 深复制
|
||||
*
|
||||
* 注意:不支持链式Bean
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param <T> 泛型标记
|
||||
* @return T
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T clone(T source) {
|
||||
return (T) BeanUtil.copy(source, source.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* copy 对象属性到另一个对象,默认不使用Convert
|
||||
*
|
||||
* 注意:不支持链式Bean,链式用 copyProperties
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param clazz 类名
|
||||
* @param <T> 泛型标记
|
||||
* @return T
|
||||
*/
|
||||
public static <T> T copy(Object source, Class<T> clazz) {
|
||||
BaseBeanCopier copier = BaseBeanCopier.create(source.getClass(), clazz, false);
|
||||
|
||||
T to = newInstance(clazz);
|
||||
copier.copy(source, to, null);
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝对象
|
||||
*
|
||||
* 注意:不支持链式Bean,链式用 copyProperties
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param targetBean 需要赋值的对象
|
||||
*/
|
||||
public static void copy(Object source, Object targetBean) {
|
||||
BaseBeanCopier copier = BaseBeanCopier
|
||||
.create(source.getClass(), targetBean.getClass(), false);
|
||||
|
||||
copier.copy(source, targetBean, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the property values of the given source bean into the target class.
|
||||
* <p>Note: The source and target classes do not have to match or even be derived
|
||||
* from each other, as long as the properties match. Any bean properties that the
|
||||
* source bean exposes but the target bean does not will silently be ignored.
|
||||
* <p>This is just a convenience method. For more complex transfer needs,
|
||||
* @param source the source bean
|
||||
* @param target the target bean class
|
||||
* @param <T> 泛型标记
|
||||
* @throws BeansException if the copying failed
|
||||
* @return T
|
||||
*/
|
||||
public static <T> T copyProperties(Object source, Class<T> target) throws BeansException {
|
||||
T to = newInstance(target);
|
||||
BeanUtil.copyProperties(source, to);
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* 两个对象间复制属性(忽略空值)
|
||||
* @param source
|
||||
* @param target
|
||||
*/
|
||||
public static void copyPropertiesIgnoreNull(Object source, Object target) {
|
||||
final BeanWrapper src = new BeanWrapperImpl(source);
|
||||
PropertyDescriptor[] pds = src.getPropertyDescriptors();
|
||||
|
||||
Set<String> emptyNames = new HashSet<>();
|
||||
for(PropertyDescriptor pd : pds) {
|
||||
Object srcValue = src.getPropertyValue(pd.getName());
|
||||
if (srcValue == null) {
|
||||
emptyNames.add(pd.getName());
|
||||
}
|
||||
}
|
||||
String[] ignoreProperties = new String[emptyNames.size()];
|
||||
emptyNames.toArray(ignoreProperties);
|
||||
copyProperties(source, target, ignoreProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象装成map形式
|
||||
* @param bean 源对象
|
||||
* @return {Map}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> toMap(Object bean) {
|
||||
return BeanMap.create(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将map 转为 bean
|
||||
* @param beanMap map
|
||||
* @param valueType 对象类型
|
||||
* @param <T> 泛型标记
|
||||
* @return {T}
|
||||
*/
|
||||
public static <T> T toBean(Map<String, Object> beanMap, Class<T> valueType) {
|
||||
T bean = BeanUtil.newInstance(valueType);
|
||||
BeanMap.create(bean).putAll(beanMap);
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给一个Bean添加字段
|
||||
* @param superBean 父级Bean
|
||||
* @param props 新增属性
|
||||
* @return {Object}
|
||||
*/
|
||||
public static Object generator(Object superBean, BeanProperty... props) {
|
||||
Class<?> superclass = superBean.getClass();
|
||||
Object genBean = generator(superclass, props);
|
||||
BeanUtil.copy(superBean, genBean);
|
||||
return genBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给一个class添加字段
|
||||
* @param superclass 父级
|
||||
* @param props 新增属性
|
||||
* @return {Object}
|
||||
*/
|
||||
public static Object generator(Class<?> superclass, BeanProperty... props) {
|
||||
BeanGenerator generator = new BeanGenerator();
|
||||
generator.setSuperclass(superclass);
|
||||
generator.setUseCache(true);
|
||||
for (BeanProperty prop : props) {
|
||||
generator.addProperty(prop.getName(), prop.getType());
|
||||
}
|
||||
return generator.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 的所有 get方法
|
||||
* @param type 类
|
||||
* @return PropertyDescriptor数组
|
||||
*/
|
||||
public static PropertyDescriptor[] getBeanGetters(Class type) {
|
||||
return getPropertiesHelper(type, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 的所有 set方法
|
||||
* @param type 类
|
||||
* @return PropertyDescriptor数组
|
||||
*/
|
||||
public static PropertyDescriptor[] getBeanSetters(Class type) {
|
||||
return getPropertiesHelper(type, false, true);
|
||||
}
|
||||
|
||||
private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
|
||||
try {
|
||||
PropertyDescriptor[] all = BeanUtil.getPropertyDescriptors(type);
|
||||
if (read && write) {
|
||||
return all;
|
||||
} else {
|
||||
List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>(all.length);
|
||||
for (PropertyDescriptor pd : all) {
|
||||
if (read && pd.getReadMethod() != null) {
|
||||
properties.add(pd);
|
||||
} else if (write && pd.getWriteMethod() != null) {
|
||||
properties.add(pd);
|
||||
}
|
||||
}
|
||||
return properties.toArray(new PropertyDescriptor[0]);
|
||||
}
|
||||
} catch (BeansException ex) {
|
||||
throw new CodeGenerationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
class BeanProperty {
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.oneone.common.result.PageResult;
|
||||
import com.oneone.common.util.collection.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bean 工具类
|
||||
*
|
||||
* 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
|
||||
* 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class BeanUtils {
|
||||
|
||||
public static <T> T toBean(Object source, Class<T> targetClass) {
|
||||
return BeanUtil.toBean(source, targetClass);
|
||||
}
|
||||
|
||||
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return CollectionUtils.convertList(source, s -> toBean(s, targetType));
|
||||
}
|
||||
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return new PageResult<>(toBean(source.getList(), targetType), source.getTotal());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
public class CacheUtils {
|
||||
|
||||
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
|
||||
return CacheBuilder.newBuilder()
|
||||
// 只阻塞当前数据加载线程,其他线程返回旧值
|
||||
.refreshAfterWrite(duration)
|
||||
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
|
||||
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 时区 - 默认
|
||||
*/
|
||||
public static final String TIME_ZONE_DEFAULT = "GMT+8";
|
||||
|
||||
/**
|
||||
* 秒转换成毫秒
|
||||
*/
|
||||
public static final long SECOND_MILLIS = 1000;
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将 LocalDateTime 转换成 Date
|
||||
*
|
||||
* @param date LocalDateTime
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static Date of(LocalDateTime date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return Date.from(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Date 转换成 LocalDateTime
|
||||
*
|
||||
* @param date Date
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime of(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 转为时间戳
|
||||
Instant instant = date.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public static Date addTime(Duration duration) {
|
||||
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||
}
|
||||
|
||||
public static boolean isExpired(Date time) {
|
||||
return System.currentTimeMillis() > time.getTime();
|
||||
}
|
||||
|
||||
public static boolean isExpired(LocalDateTime time) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return now.isAfter(time);
|
||||
}
|
||||
|
||||
public static long diff(Date endTime, Date startTime) {
|
||||
return endTime.getTime() - startTime.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day) {
|
||||
return buildTime(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param second 秒
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day,
|
||||
int hour, int minute, int second) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, year);
|
||||
calendar.set(Calendar.MONTH, mouth - 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, day);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
calendar.set(Calendar.SECOND, second);
|
||||
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
|
||||
return calendar.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;
|
||||
}
|
||||
|
||||
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.isAfter(b) ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当期时间相差的日期
|
||||
*
|
||||
* @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 日历字段 例如说,{@link Calendar#DAY_OF_MONTH} 等
|
||||
* @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 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isToday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel 工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ExcelUtils {
|
||||
|
||||
/**
|
||||
* 将列表以 Excel 响应给前端
|
||||
*
|
||||
* @param response 响应
|
||||
* @param filename 文件名
|
||||
* @param sheetName Excel sheet 名
|
||||
* @param head Excel head 头
|
||||
* @param data 数据列表哦
|
||||
* @param <T> 泛型,保证 head 和 data 类型的一致性
|
||||
* @throws IOException 写入失败的情况
|
||||
*/
|
||||
public static <T> void write(HttpServletResponse response, String filename, String sheetName,
|
||||
Class<T> head, List<T> data) throws IOException {
|
||||
// 输出 Excel
|
||||
EasyExcel.write(response.getOutputStream(), head)
|
||||
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
|
||||
.sheet(sheetName).doWrite(data);
|
||||
// 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
|
||||
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
||||
}
|
||||
|
||||
public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
|
||||
return EasyExcel.read(file.getInputStream(), head, null)
|
||||
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
|
||||
.doReadAllSync();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
|
||||
import cn.hutool.core.io.FastStringWriter;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
|
||||
/**
|
||||
* 异常处理工具类
|
||||
*
|
||||
*/
|
||||
public class Exceptions {
|
||||
|
||||
/**
|
||||
* 将CheckedException转换为UncheckedException.
|
||||
*
|
||||
* @param e Throwable
|
||||
* @return {RuntimeException}
|
||||
*/
|
||||
public static RuntimeException unchecked(Throwable e) {
|
||||
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|
||||
|| e instanceof NoSuchMethodException) {
|
||||
return new IllegalArgumentException(e);
|
||||
} else if (e instanceof InvocationTargetException) {
|
||||
return new RuntimeException(((InvocationTargetException) e).getTargetException());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
return (RuntimeException) e;
|
||||
} else {
|
||||
return new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理异常解包
|
||||
*
|
||||
* @param wrapped 包装过得异常
|
||||
* @return 解包后的异常
|
||||
*/
|
||||
public static Throwable unwrap(Throwable wrapped) {
|
||||
Throwable unwrapped = wrapped;
|
||||
while (true) {
|
||||
if (unwrapped instanceof InvocationTargetException) {
|
||||
unwrapped = ((InvocationTargetException) unwrapped).getTargetException();
|
||||
} else if (unwrapped instanceof UndeclaredThrowableException) {
|
||||
unwrapped = ((UndeclaredThrowableException) unwrapped).getUndeclaredThrowable();
|
||||
} else {
|
||||
return unwrapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将ErrorStack转化为String.
|
||||
*
|
||||
* @param ex Throwable
|
||||
* @return {String}
|
||||
*/
|
||||
public static String getStackTraceAsString(Throwable ex) {
|
||||
FastStringWriter stringWriter = new FastStringWriter();
|
||||
ex.printStackTrace(new PrintWriter(stringWriter));
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cglib.beans.BeanGenerator;
|
||||
import org.springframework.cglib.beans.BeanMap;
|
||||
import org.springframework.cglib.core.CodeGenerationException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static cn.hutool.core.text.CharSequenceUtil.subPre;
|
||||
|
||||
public class Func extends org.springframework.beans.BeanUtils{
|
||||
|
||||
public static boolean isEmpty(Object obj) {
|
||||
return ObjectUtil.isEmpty(obj);
|
||||
}
|
||||
|
||||
public static boolean isNotEmpty(Object obj) {
|
||||
return !ObjectUtil.isEmpty(obj);
|
||||
}
|
||||
|
||||
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
|
||||
//d当前时间
|
||||
//系统的默认时区
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
//时区的日期和时间
|
||||
ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
|
||||
//获取时刻
|
||||
Date date = Date.from(zonedDateTime.toInstant());
|
||||
//格式化LocalDateTime、Date
|
||||
// DateTimeFormatter localDateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return date;
|
||||
}
|
||||
public static LocalDateTime dateToLocalDateTime(Date date ) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
LocalDateTime localDateTime = date.toInstant().atZone(zoneId).toLocalDateTime();
|
||||
return localDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* resultMin=after-before
|
||||
* @param after
|
||||
* @param before
|
||||
* @return
|
||||
*/
|
||||
public static int timeDifferenceOfMin(Date after ,Date before ) {
|
||||
int minute = (int) ((after.getTime() - before.getTime()) / (1000 * 60));
|
||||
return minute;
|
||||
}
|
||||
public static int timeDifferenceOfMin(LocalDateTime after ,LocalDateTime before ) {
|
||||
int minute = (int) ((localDateTimeToDate(after).getTime() - localDateTimeToDate(before).getTime()) / (1000 * 60));
|
||||
return minute;
|
||||
}
|
||||
public static int timeDifferenceOfMin(Date after ,LocalDateTime before ) {
|
||||
int minute = (int) ((after.getTime() - localDateTimeToDate(before).getTime()) / (1000 * 60));
|
||||
return minute;
|
||||
}
|
||||
public static int timeDifferenceOfMin(LocalDateTime after ,Date before ) {
|
||||
int minute = (int) ((localDateTimeToDate(after).getTime() - before.getTime()) / (1000 * 60));
|
||||
return minute;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建StringBuilder对象
|
||||
*
|
||||
* @param sb 初始StringBuilder
|
||||
* @param strs 初始字符串列表
|
||||
* @return StringBuilder对象
|
||||
*/
|
||||
public static StringBuilder appendBuilder(StringBuilder sb, CharSequence... strs) {
|
||||
for (CharSequence str : strs) {
|
||||
sb.append(str);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉指定后缀
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param suffix 后缀
|
||||
* @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串
|
||||
*/
|
||||
public static String removeSuffix(CharSequence str, CharSequence suffix) {
|
||||
if (isEmpty(str) || isEmpty(suffix)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final String str2 = str.toString();
|
||||
if (str2.endsWith(suffix.toString())) {
|
||||
return subPre(str2, str2.length() - suffix.length());
|
||||
}
|
||||
return str2;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-08-06 15:33
|
||||
*/
|
||||
public class IDCardUtil {
|
||||
|
||||
/**
|
||||
* 根据身份证号判断性别
|
||||
*
|
||||
* @param idNumber
|
||||
* @return
|
||||
*/
|
||||
public static String judgeGender(String idNumber) throws IllegalArgumentException {
|
||||
System.out.println(idNumber.length());
|
||||
if (idNumber.length() != 18 && idNumber.length() != 15) {
|
||||
throw new IllegalArgumentException("身份证号长度错误");
|
||||
}
|
||||
int gender = 0;
|
||||
if (idNumber.length() == 18) {
|
||||
//如果身份证号18位,取身份证号倒数第二位
|
||||
char c = idNumber.charAt(idNumber.length() - 2);
|
||||
gender = Integer.parseInt(String.valueOf(c));
|
||||
} else {
|
||||
//如果身份证号15位,取身份证号最后一位
|
||||
char c = idNumber.charAt(idNumber.length() - 1);
|
||||
gender = Integer.parseInt(String.valueOf(c));
|
||||
}
|
||||
if (gender % 2 == 1) {
|
||||
return "男";
|
||||
} else {
|
||||
return "女";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据身份证的号码算出当前身份证持有者的年龄
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static int countAge(String idNumber) {
|
||||
if (idNumber.length() != 18 && idNumber.length() != 15) {
|
||||
throw new IllegalArgumentException("身份证号长度错误");
|
||||
}
|
||||
String year;
|
||||
String yue;
|
||||
String day;
|
||||
if (idNumber.length() == 18) {
|
||||
year = idNumber.substring(6).substring(0, 4);// 得到年份
|
||||
yue = idNumber.substring(10).substring(0, 2);// 得到月份
|
||||
day = idNumber.substring(12).substring(0, 2);//得到日
|
||||
} else {
|
||||
year = "19" + idNumber.substring(6, 8);// 年份
|
||||
yue = idNumber.substring(8, 10);// 月份
|
||||
day = idNumber.substring(10, 12);//日
|
||||
}
|
||||
Date date = new Date();// 得到当前的系统时间
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
|
||||
String fyear = format.format(date).substring(0, 4);// 当前年份
|
||||
String fyue = format.format(date).substring(5, 7);// 月份
|
||||
String fday = format.format(date).substring(8, 10);//
|
||||
int age = 0;
|
||||
if (Integer.parseInt(yue) == Integer.parseInt(fyue)) {//如果月份相同
|
||||
if (Integer.parseInt(day) <= Integer.parseInt(fday)) {//说明已经过了生日或者今天是生日
|
||||
age = Integer.parseInt(fyear) - Integer.parseInt(year);
|
||||
} else {
|
||||
age = Integer.parseInt(fyear) - Integer.parseInt(year) - 1;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (Integer.parseInt(yue) < Integer.parseInt(fyue)) {
|
||||
//如果当前月份大于出生月份
|
||||
age = Integer.parseInt(fyear) - Integer.parseInt(year);
|
||||
} else {
|
||||
//如果当前月份小于出生月份,说明生日还没过
|
||||
age = Integer.parseInt(fyear) - Integer.parseInt(year) - 1;
|
||||
}
|
||||
}
|
||||
return age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* INet 相关工具
|
||||
*/
|
||||
public class INetUtil {
|
||||
public static final String LOCAL_HOST = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* 获取 服务器 hostname
|
||||
*
|
||||
* @return hostname
|
||||
*/
|
||||
public static String getHostName() {
|
||||
String hostname;
|
||||
try {
|
||||
InetAddress address = InetAddress.getLocalHost();
|
||||
// force a best effort reverse DNS lookup
|
||||
hostname = address.getHostName();
|
||||
if (hostname == null || "".equals(hostname)) {
|
||||
hostname = address.toString();
|
||||
}
|
||||
} catch (UnknownHostException ignore) {
|
||||
hostname = LOCAL_HOST;
|
||||
}
|
||||
return hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 服务器 HostIp
|
||||
*
|
||||
* @return HostIp
|
||||
*/
|
||||
public static String getHostIp() {
|
||||
String hostAddress;
|
||||
try {
|
||||
InetAddress address = INetUtil.getLocalHostLANAddress();
|
||||
// force a best effort reverse DNS lookup
|
||||
hostAddress = address.getHostAddress();
|
||||
if (hostAddress == null || "".equals(hostAddress)) {
|
||||
hostAddress = address.toString();
|
||||
}
|
||||
} catch (UnknownHostException ignore) {
|
||||
hostAddress = LOCAL_HOST;
|
||||
}
|
||||
return hostAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java
|
||||
*
|
||||
* <p>
|
||||
* Returns an <code>InetAddress</code> object encapsulating what is most likely the machine's LAN IP address.
|
||||
* <p/>
|
||||
* This method is intended for use as a replacement of JDK method <code>InetAddress.getLocalHost</code>, because
|
||||
* that method is ambiguous on Linux systems. Linux systems enumerate the loopback network interface the same
|
||||
* way as regular LAN network interfaces, but the JDK <code>InetAddress.getLocalHost</code> method does not
|
||||
* specify the algorithm used to select the address returned under such circumstances, and will often return the
|
||||
* loopback address, which is not valid for network communication. Details
|
||||
* <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4665037">here</a>.
|
||||
* <p/>
|
||||
* This method will scan all IP addresses on all network interfaces on the host machine to determine the IP address
|
||||
* most likely to be the machine's LAN address. If the machine has multiple IP addresses, this method will prefer
|
||||
* a site-local IP address (e.g. 192.168.x.x or 10.10.x.x, usually IPv4) if the machine has one (and will return the
|
||||
* first site-local address if the machine has more than one), but if the machine does not hold a site-local
|
||||
* address, this method will return simply the first non-loopback address found (IPv4 or IPv6).
|
||||
* <p/>
|
||||
* If this method cannot find a non-loopback address using this selection algorithm, it will fall back to
|
||||
* calling and returning the result of JDK method <code>InetAddress.getLocalHost</code>.
|
||||
* <p/>
|
||||
*
|
||||
* @throws UnknownHostException If the LAN address of the machine cannot be found.
|
||||
*/
|
||||
private static InetAddress getLocalHostLANAddress() throws UnknownHostException {
|
||||
try {
|
||||
InetAddress candidateAddress = null;
|
||||
// Iterate all NICs (network interface cards)...
|
||||
for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
|
||||
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
|
||||
// Iterate all IP addresses assigned to each card...
|
||||
for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
|
||||
InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
|
||||
if (!inetAddr.isLoopbackAddress()) {
|
||||
|
||||
if (inetAddr.isSiteLocalAddress()) {
|
||||
// Found non-loopback site-local address. Return it immediately...
|
||||
return inetAddr;
|
||||
} else if (candidateAddress == null) {
|
||||
// Found non-loopback address, but not necessarily site-local.
|
||||
// Store it as a candidate to be returned if site-local address is not subsequently found...
|
||||
candidateAddress = inetAddr;
|
||||
// Note that we don't repeatedly assign non-loopback non-site-local addresses as candidates,
|
||||
// only the first. For subsequent iterations, candidate will be non-null.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (candidateAddress != null) {
|
||||
// We did not find a site-local address, but we found some other non-loopback address.
|
||||
// Server might have a non-site-local address assigned to its NIC (or it might be running
|
||||
// IPv6 which deprecates the "site-local" concept).
|
||||
// Return this non-loopback candidate address...
|
||||
return candidateAddress;
|
||||
}
|
||||
// At this point, we did not find a non-loopback address.
|
||||
// Fall back to returning whatever InetAddress.getLocalHost() returns...
|
||||
InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
|
||||
if (jdkSuppliedAddress == null) {
|
||||
throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
|
||||
}
|
||||
return jdkSuppliedAddress;
|
||||
} catch (Exception e) {
|
||||
UnknownHostException unknownHostException = new UnknownHostException("Failed to determine LAN address: " + e);
|
||||
unknownHostException.initCause(e);
|
||||
throw unknownHostException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试端口时候被占用
|
||||
*
|
||||
* @param port 端口号
|
||||
* @return 没有被占用:true,被占用:false
|
||||
*/
|
||||
public static boolean tryPort(int port) {
|
||||
try (ServerSocket ignore = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-22 17:33
|
||||
*/
|
||||
@UtilityClass
|
||||
public class ImageUtil {
|
||||
|
||||
public static InputStream getImageInputStream(String imageUrl) {
|
||||
InputStream inputStream = null;
|
||||
HttpURLConnection httpURLConnection = null;
|
||||
try {
|
||||
URL url = new URL(imageUrl);
|
||||
httpURLConnection = (HttpURLConnection) url.openConnection();
|
||||
// 设置网络连接超时时间
|
||||
httpURLConnection.setConnectTimeout(3000);
|
||||
// 设置应用程序要从网络连接读取数据
|
||||
httpURLConnection.setDoInput(true);
|
||||
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
int responseCode = httpURLConnection.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
// 从服务器返回一个输入流
|
||||
inputStream = httpURLConnection.getInputStream();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* jackson转换工具类
|
||||
*
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2021/7/16 17:30
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
//序列化的时候序列对象的所有属性
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
|
||||
//反序列化的时候如果多了其他属性,不抛出异常
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
//如果是空对象的时候,不抛异常
|
||||
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
//取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
|
||||
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" ));
|
||||
|
||||
mapper.registerModules(new JavaTimeModule());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转为Json字符串
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static String toJSONString(Object data) {
|
||||
String jsonStr = null;
|
||||
try {
|
||||
jsonStr = mapper.writeValueAsString(data);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return jsonStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转为byte数组
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static byte[] transferToBytes(Object data) {
|
||||
byte[] byteArr = null;
|
||||
try {
|
||||
byteArr = mapper.writeValueAsBytes(data);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return byteArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* json字符串转为对象
|
||||
*
|
||||
* @param str
|
||||
* @param valueType
|
||||
* @return
|
||||
*/
|
||||
public static <T> T strToClass(String str, Class<T> valueType) {
|
||||
try {
|
||||
return mapper.readValue(str, valueType);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String str, Class<T> valueType) {
|
||||
try {
|
||||
return mapper.readValue(str, valueType);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, Type type) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(text, mapper.getTypeFactory().constructType(type));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
|
||||
try {
|
||||
return mapper.readValue(text, typeReference);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* byte数组转为对象
|
||||
*
|
||||
* @param byteArr
|
||||
* @param valueType
|
||||
* @return
|
||||
*/
|
||||
public static <T> T bytesToTransfer(byte[] byteArr, Class<T> valueType) {
|
||||
T data = null;
|
||||
try {
|
||||
data = mapper.readValue(byteArr, valueType);
|
||||
} catch (JsonParseException e) {
|
||||
e.printStackTrace();
|
||||
} catch (JsonMappingException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转指定对象
|
||||
*
|
||||
* @param o
|
||||
* @param valueType
|
||||
* @return
|
||||
*/
|
||||
public static <T> T objectToClass(Object o, Class<T> valueType) {
|
||||
return mapper.convertValue(o, valueType);
|
||||
}
|
||||
|
||||
public static boolean isJson(String text) {
|
||||
return JSONUtil.isTypeJSON(text);
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
|
||||
/**
|
||||
* 时间工具类,用于 {@link LocalDateTime}
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class LocalDateTimeUtils {
|
||||
|
||||
/**
|
||||
* 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
|
||||
*/
|
||||
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
|
||||
|
||||
public static LocalDateTime addTime(Duration duration) {
|
||||
return LocalDateTime.now().plus(duration);
|
||||
}
|
||||
|
||||
public static LocalDateTime minusTime(Duration duration) {
|
||||
return LocalDateTime.now().minus(duration);
|
||||
}
|
||||
|
||||
public static boolean beforeNow(LocalDateTime date) {
|
||||
return date.isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public static boolean afterNow(LocalDateTime date) {
|
||||
return date.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static LocalDateTime buildTime(int year, int mouth, int day) {
|
||||
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
|
||||
int year2, int mouth2, int day2) {
|
||||
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否在该时间范围内
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否在该时间范围内
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isBetween(String startTime, String endTime) {
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
LocalDate nowDate = LocalDate.now();
|
||||
return LocalDateTimeUtil.isIn(LocalDateTime.now(),
|
||||
LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
|
||||
LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断时间段是否重叠
|
||||
*
|
||||
* @param startTime1 开始 time1
|
||||
* @param endTime1 结束 time1
|
||||
* @param startTime2 开始 time2
|
||||
* @param endTime2 结束 time2
|
||||
* @return 重叠:true 不重叠:false
|
||||
*/
|
||||
public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
|
||||
LocalDate nowDate = LocalDate.now();
|
||||
return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
|
||||
LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在的月份的开始时间
|
||||
* 例如:2023-09-30 00:00:00,000
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 月份的开始时间
|
||||
*/
|
||||
public static LocalDateTime beginOfMonth(LocalDateTime date) {
|
||||
return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在的月份的最后时间
|
||||
* 例如:2023-09-30 23:59:59,999
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 月份的结束时间
|
||||
*/
|
||||
public static LocalDateTime endOfMonth(LocalDateTime date) {
|
||||
return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Object 工具类
|
||||
*
|
||||
*/
|
||||
public class ObjectUtils {
|
||||
|
||||
/**
|
||||
* 复制对象,并忽略 Id 编号
|
||||
*
|
||||
* @param object 被复制对象
|
||||
* @param consumer 消费者,可以二次编辑被复制对象
|
||||
* @return 复制后的对象
|
||||
*/
|
||||
public static <T> T cloneIgnoreId(T object, Consumer<T> consumer) {
|
||||
T result = ObjectUtil.clone(object);
|
||||
// 忽略 id 编号
|
||||
Field field = ReflectUtil.getField(object.getClass(), "id");
|
||||
if (field != null) {
|
||||
ReflectUtil.setFieldValue(result, field, null);
|
||||
}
|
||||
// 二次编辑
|
||||
if (result != null) {
|
||||
consumer.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> T max(T obj1, T obj2) {
|
||||
if (obj1 == null) {
|
||||
return obj2;
|
||||
}
|
||||
if (obj2 == null) {
|
||||
return obj1;
|
||||
}
|
||||
return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> T defaultIfNull(T... array) {
|
||||
for (T item : array) {
|
||||
if (item != null) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> boolean equalsAny(T obj, T... array) {
|
||||
return Arrays.asList(array).contains(obj);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-09 17:49
|
||||
*/
|
||||
@Data
|
||||
public class RandomUtils {
|
||||
/**
|
||||
* 用于随机选的数字
|
||||
*/
|
||||
public static final String BASE_NUMBER = "0123456789";
|
||||
/**
|
||||
* 用于随机选的字符
|
||||
*/
|
||||
public static final String BASE_CHAR = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
/**
|
||||
* 用于随机选的字符
|
||||
*/
|
||||
public static final String BASE_UPPER_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
/**
|
||||
* 用于随机选的字符和数字
|
||||
*/
|
||||
public static final String BASE_CHAR_NUMBER = BASE_CHAR + BASE_NUMBER;
|
||||
|
||||
/**
|
||||
* 用于随机选的字符和数字
|
||||
*/
|
||||
public static final String BASE_ALL_CHAR_NUMBER = BASE_CHAR + BASE_NUMBER + BASE_UPPER_CHAR;
|
||||
|
||||
public static final String BASE_ALL_CHAR_NUMBER2 = BASE_CHAR + BASE_NUMBER;
|
||||
|
||||
public static final String BASE_ALL_CHAR_NUMBER3 = BASE_NUMBER + BASE_UPPER_CHAR;
|
||||
|
||||
public static String randomAllStringUpper(int length) {
|
||||
return randomString(BASE_ALL_CHAR_NUMBER3, length);
|
||||
}
|
||||
|
||||
public static String randomAllStringLower(int length) {
|
||||
return randomString(BASE_ALL_CHAR_NUMBER2, length);
|
||||
}
|
||||
|
||||
public static String randomAllString(int length) {
|
||||
return randomString(BASE_ALL_CHAR_NUMBER, length);
|
||||
}
|
||||
|
||||
public static String randomAllNumberString(int length) {
|
||||
return randomString(BASE_NUMBER, length);
|
||||
}
|
||||
|
||||
public static String randomString(String baseString, int length) {
|
||||
if (StrUtil.isEmpty(baseString)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder(length);
|
||||
|
||||
if (length < 1) {
|
||||
length = 1;
|
||||
}
|
||||
int baseLength = baseString.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int number = ThreadLocalRandom.current().nextInt(baseLength);
|
||||
sb.append(baseString.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static List getRandomList(List list){
|
||||
if(list==null||list.size()==0) {
|
||||
return null;
|
||||
}
|
||||
//1、建立一个随机数生成器对象
|
||||
SecureRandom random = new SecureRandom();
|
||||
//2、建立一个乱序的对象
|
||||
List result = new ArrayList();
|
||||
boolean flag = true;
|
||||
int size = 0;
|
||||
while(flag) {
|
||||
size = list.size();
|
||||
//随机生成一个下标
|
||||
int index = random.nextInt(size);
|
||||
//将内容添加入list中
|
||||
result.add(list.get(index));
|
||||
//从原有的list中移除
|
||||
list.remove(index);
|
||||
if(list.size()==0) {
|
||||
flag=false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 客户端工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
/**
|
||||
* 返回 JSON 字符串
|
||||
*
|
||||
* @param response 响应
|
||||
* @param object 对象,会序列化成 JSON 字符串
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
||||
String content = JsonUtils.toJSONString(object);
|
||||
ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回附件
|
||||
*
|
||||
* @param response 响应
|
||||
* @param filename 文件名
|
||||
* @param content 附件内容
|
||||
*/
|
||||
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
|
||||
// 设置 header 和 contentType
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
// 输出附件
|
||||
IoUtil.write(response.getOutputStream(), false, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request 请求
|
||||
* @return ua
|
||||
*/
|
||||
public static String getUserAgent(HttpServletRequest request) {
|
||||
String ua = request.getHeader("User-Agent");
|
||||
return ua != null ? ua : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求
|
||||
*
|
||||
* @return HttpServletRequest
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
if (!(requestAttributes instanceof ServletRequestAttributes)) {
|
||||
return null;
|
||||
}
|
||||
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
}
|
||||
|
||||
public static String getUserAgent() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return getUserAgent(request);
|
||||
}
|
||||
|
||||
public static String getClientIP() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return ServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
// TODO @疯狂:terminal 还是从 ServletUtils 里拿,更容易全局治理;
|
||||
|
||||
public static boolean isJsonRequest(ServletRequest request) {
|
||||
return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
public static String getBody(HttpServletRequest request) {
|
||||
return ServletUtil.getBody(request);
|
||||
}
|
||||
|
||||
public static byte[] getBodyBytes(HttpServletRequest request) {
|
||||
return ServletUtil.getBodyBytes(request);
|
||||
}
|
||||
|
||||
public static String getClientIP(HttpServletRequest request) {
|
||||
return ServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static Map<String, String> getParamMap(HttpServletRequest request) {
|
||||
return ServletUtil.getParamMap(request);
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
public class SnowflakeIdGenerator {
|
||||
|
||||
private final long workerId;
|
||||
private final static long START_TIMESTAMP = 1613036400000L; // 设置起始时间戳,这里以2021-02-11 00:00:00为起始时间
|
||||
private long sequence = 0L;
|
||||
private final static long WORKER_ID_BITS = 5L;
|
||||
private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
|
||||
private final static long SEQUENCE_BITS = 12L;
|
||||
private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;
|
||||
private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
|
||||
private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
|
||||
private long lastTimestamp = -1L;
|
||||
|
||||
public SnowflakeIdGenerator(long workerId) {
|
||||
if (workerId > MAX_WORKER_ID || workerId < 0) {
|
||||
throw new IllegalArgumentException(String.format("Worker ID must be between 0 and %d", MAX_WORKER_ID));
|
||||
}
|
||||
this.workerId = workerId;
|
||||
}
|
||||
|
||||
public synchronized long nextId() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
if (timestamp < lastTimestamp) {
|
||||
throw new RuntimeException("Clock moved backwards. Refusing to generate id.");
|
||||
}
|
||||
|
||||
if (timestamp == lastTimestamp) {
|
||||
sequence = (sequence + 1) & SEQUENCE_MASK;
|
||||
if (sequence == 0) {
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
} else {
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
|
||||
}
|
||||
|
||||
private long tilNextMillis(long lastTimestamp) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
while (timestamp <= lastTimestamp) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
||||
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1); // 设置工作机器ID
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
long id = idGenerator.nextId();
|
||||
System.out.println(id+"长度="+String.valueOf(id).length());
|
||||
|
||||
}
|
||||
|
||||
System.out.println("结束:"+System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字符串工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class StrUtils {
|
||||
|
||||
public static String maxLength(CharSequence str, int maxLength) {
|
||||
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否以任何一个字符串开始
|
||||
* 给定字符串和数组为空都返回 false
|
||||
*
|
||||
* @param str 给定字符串
|
||||
* @param prefixes 需要检测的开始字符串
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static boolean startWithAny(String str, Collection<String> prefixes) {
|
||||
if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CharSequence suffix : prefixes) {
|
||||
if (StrUtil.startWith(str, suffix, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<Long> splitToLong(String value, CharSequence separator) {
|
||||
long[] longs = StrUtil.splitToLong(value, separator);
|
||||
return Arrays.stream(longs).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Integer> splitToInteger(String value, CharSequence separator) {
|
||||
int[] integers = StrUtil.splitToInt(value, separator);
|
||||
return Arrays.stream(integers).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除字符串中,包含指定字符串的行
|
||||
*
|
||||
* @param content 字符串
|
||||
* @param sequence 包含的字符串
|
||||
* @return 移除后的字符串
|
||||
*/
|
||||
public static String removeLineContains(String content, String sequence) {
|
||||
if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
|
||||
return content;
|
||||
}
|
||||
return Arrays.stream(content.split("\n"))
|
||||
.filter(line -> !line.contains(sequence))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
|
||||
import org.slf4j.MDC;
|
||||
|
||||
/**
|
||||
* 链路追踪工具类
|
||||
* <p>
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
|
||||
*/
|
||||
public class TracerUtils {
|
||||
|
||||
/**
|
||||
* 私有化构造方法
|
||||
*/
|
||||
private TracerUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得链路追踪编号,直接返回 TraceId。
|
||||
*
|
||||
* @return 链路追踪编号
|
||||
*/
|
||||
public static String getTraceId() {
|
||||
return MDC.get("traceId");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.oneone.common.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ValidationUtils {
|
||||
|
||||
private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$" );
|
||||
|
||||
private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]" );
|
||||
|
||||
private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*" );
|
||||
|
||||
public static boolean isMobile(String mobile) {
|
||||
return StringUtils.hasText(mobile)
|
||||
&& PATTERN_MOBILE.matcher(mobile).matches();
|
||||
}
|
||||
|
||||
public static boolean isURL(String url) {
|
||||
return StringUtils.hasText(url)
|
||||
&& PATTERN_URL.matcher(url).matches();
|
||||
}
|
||||
|
||||
public static boolean isXmlNCName(String str) {
|
||||
return StringUtils.hasText(str)
|
||||
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static void validate(Object object, Class<?>... groups) {
|
||||
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
Assert.notNull(validator);
|
||||
validate(validator, object, groups);
|
||||
}
|
||||
|
||||
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||
throw new ConstraintViolationException(constraintViolations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.oneone.common.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.IterUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.oneone.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
|
||||
/**
|
||||
* Array 工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ArrayUtils {
|
||||
|
||||
/**
|
||||
* 将 object 和 newElements 合并成一个数组
|
||||
*
|
||||
* @param object 对象
|
||||
* @param newElements 数组
|
||||
* @param <T> 泛型
|
||||
* @return 结果数组
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
|
||||
if (object == null) {
|
||||
return newElements;
|
||||
}
|
||||
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
|
||||
result[0] = object;
|
||||
System.arraycopy(newElements, 0, result, 1, newElements.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
|
||||
return toArray(convertList(from, mapper));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] toArray(Collection<T> from) {
|
||||
if (CollectionUtil.isEmpty(from)) {
|
||||
return (T[]) (new Object[0]);
|
||||
}
|
||||
return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
|
||||
}
|
||||
|
||||
public static <T> T get(T[] array, int index) {
|
||||
if (null == array || index >= array.length) {
|
||||
return null;
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
package com.oneone.common.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Collection 工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class CollectionUtils {
|
||||
|
||||
public static boolean containsAny(Object source, Object... targets) {
|
||||
return asList(targets).contains(source);
|
||||
}
|
||||
|
||||
public static boolean isAnyEmpty(Collection<?>... collections) {
|
||||
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
|
||||
}
|
||||
|
||||
public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
|
||||
return from.stream().anyMatch(predicate);
|
||||
}
|
||||
|
||||
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return distinct(from, keyMapper, (t1, t2) -> t1);
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
|
||||
if (ArrayUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return convertList(Arrays.asList(from), func);
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
|
||||
return map.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity(), supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream()
|
||||
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||
}
|
||||
|
||||
// 暂时没想好名字,先以 2 结尾噶
|
||||
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
|
||||
from.forEach(item -> builder.put(keyFunc.apply(item), item));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比老、新两个列表,找出新增、修改、删除的数据
|
||||
*
|
||||
* @param oldList 老列表
|
||||
* @param newList 新列表
|
||||
* @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
|
||||
* 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
|
||||
* @return [新增列表、修改列表、删除列表]
|
||||
*/
|
||||
public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
|
||||
BiFunction<T, T, Boolean> sameFunc) {
|
||||
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
|
||||
List<T> updateList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
|
||||
for (T oldObj : oldList) {
|
||||
// 1. 寻找是否有匹配的
|
||||
T foundObj = null;
|
||||
for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
|
||||
T newObj = iterator.next();
|
||||
// 1.1 不匹配,则直接跳过
|
||||
if (!sameFunc.apply(oldObj, newObj)) {
|
||||
continue;
|
||||
}
|
||||
// 1.2 匹配,则移除,并结束寻找
|
||||
iterator.remove();
|
||||
foundObj = newObj;
|
||||
break;
|
||||
}
|
||||
// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
|
||||
if (foundObj != null) {
|
||||
updateList.add(foundObj);
|
||||
} else {
|
||||
deleteList.add(oldObj);
|
||||
}
|
||||
}
|
||||
return asList(createList, updateList, deleteList);
|
||||
}
|
||||
|
||||
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 !CollectionUtil.isEmpty(from) ? from.get(0) : null;
|
||||
}
|
||||
|
||||
public static <T> T findFirst(List<T> from, Predicate<T> predicate) {
|
||||
return findFirst(from, predicate, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, U> U findFirst(List<T> from, Predicate<T> predicate, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert !from.isEmpty(); // 断言,避免告警
|
||||
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
T t = from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
|
||||
BinaryOperator<V> accumulator) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
return from.stream().map(valueFunc).reduce(accumulator).get();
|
||||
}
|
||||
|
||||
public static <T> void addIfNotNull(Collection<T> coll, T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
coll.add(item);
|
||||
}
|
||||
|
||||
public static <T> Collection<T> singleton(T deptId) {
|
||||
return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.oneone.common.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.oneone.common.base.KeyValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Map 工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class MapUtils {
|
||||
|
||||
/**
|
||||
* 从哈希表表中,获得 keys 对应的所有 value 数组
|
||||
*
|
||||
* @param multimap 哈希表
|
||||
* @param keys keys
|
||||
* @return value 数组
|
||||
*/
|
||||
public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
|
||||
List<V> result = new ArrayList<>();
|
||||
keys.forEach(k -> {
|
||||
Collection<V> values = multimap.get(k);
|
||||
if (CollectionUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
result.addAll(values);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从哈希表查找到 key 对应的 value,然后进一步处理
|
||||
* 注意,如果查找到的 value 为 null 时,不进行处理
|
||||
*
|
||||
* @param map 哈希表
|
||||
* @param key key
|
||||
* @param consumer 进一步处理的逻辑
|
||||
*/
|
||||
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
|
||||
if (CollUtil.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
V value = map.get(key);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
consumer.accept(value);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
|
||||
Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
|
||||
keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.oneone.common.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Set 工具类
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SetUtils {
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> asSet(T... objs) {
|
||||
return CollUtil.newHashSet(objs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.oneone.common.util.prob;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-10 16:16
|
||||
*/
|
||||
@Data
|
||||
public class BaseProb {
|
||||
private Integer index;
|
||||
private Integer prob;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.oneone.common.util.prob;
|
||||
|
||||
public interface IBaseProb {
|
||||
public int getProb();
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.oneone.common.util.prob;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 概率生成器
|
||||
* @author mice
|
||||
* @date 2021/12/7 21:32
|
||||
* @version 1.0
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProbabilityGenerator<T extends BaseProb> {
|
||||
protected int totalProbability;
|
||||
protected List<ProbabilityInterval<T>> probabilityIntervals;
|
||||
|
||||
public T generateResult(){
|
||||
int random = RandomUtil.randomInt(totalProbability);
|
||||
for (ProbabilityInterval<T> probabilityInterval : probabilityIntervals){
|
||||
if (random>=probabilityInterval.getStart() && random<probabilityInterval.getEnd()){
|
||||
return probabilityInterval.getProb();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ProbabilityGenerator initGenerator(List<T> list) {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
throw new BusinessException("数据不能为空");
|
||||
}
|
||||
List<ProbabilityInterval> probabilityIntervals = new ArrayList<>();
|
||||
int start = 0;
|
||||
int totalProbability = 0;
|
||||
for (T obj : list) {
|
||||
Integer probability = obj.getProb();
|
||||
totalProbability += probability;
|
||||
int end = start + probability;
|
||||
ProbabilityInterval<T> probabilityInterval = new ProbabilityInterval(obj.getIndex(), start, end, obj);
|
||||
probabilityIntervals.add(probabilityInterval);
|
||||
start = end;
|
||||
}
|
||||
ProbabilityGenerator probabilityGenerator = new ProbabilityGenerator(totalProbability, probabilityIntervals);
|
||||
return probabilityGenerator;
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.oneone.common.util.prob;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 概率生成器
|
||||
* @author mice
|
||||
* @date 2021/12/7 21:32
|
||||
* @version 1.0
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
public class ProbabilityGeneratorByInterface<T extends IBaseProb> {
|
||||
protected int totalProbability;
|
||||
protected List<ProbabilityInterval<T>> probabilityIntervals;
|
||||
|
||||
public T generateResult(){
|
||||
if(totalProbability<=0){
|
||||
return null;
|
||||
}
|
||||
int random = RandomUtil.randomInt(totalProbability);
|
||||
for (ProbabilityInterval<T> probabilityInterval : probabilityIntervals){
|
||||
if (random>=probabilityInterval.getStart() && random<probabilityInterval.getEnd()){
|
||||
return probabilityInterval.getProb();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ProbabilityGeneratorByInterface initGenerator(List<T> list) {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
throw new BusinessException("数据不能为空");
|
||||
}
|
||||
List<ProbabilityInterval> probabilityIntervals = new ArrayList<>();
|
||||
int start = 0;
|
||||
int totalProbability = 0;
|
||||
for (T obj : list) {
|
||||
Integer probability = obj.getProb();
|
||||
totalProbability += probability;
|
||||
int end = start + probability;
|
||||
ProbabilityInterval<T> probabilityInterval = new ProbabilityInterval(start, end, obj);
|
||||
probabilityIntervals.add(probabilityInterval);
|
||||
start = end;
|
||||
}
|
||||
ProbabilityGeneratorByInterface probabilityGenerator = new ProbabilityGeneratorByInterface(totalProbability, probabilityIntervals);
|
||||
log.info("随机器初始化:"+JSON.toJSONString(probabilityGenerator.probabilityIntervals)+" \n"
|
||||
+"总权重:"+probabilityGenerator.totalProbability);
|
||||
return probabilityGenerator;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.util.prob;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 概率区间
|
||||
* @author mice
|
||||
* @date 2021/12/7 21:31
|
||||
* @version 1.0
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class
|
||||
ProbabilityInterval<T> {
|
||||
private int index;
|
||||
private int start;
|
||||
private int end;
|
||||
private T prob;
|
||||
|
||||
public ProbabilityInterval(int start, int end, T prob) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.prob = prob;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.oneone.common.validation;
|
||||
|
||||
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 {};
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.validation;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.oneone.common.util.ValidationUtils;
|
||||
|
||||
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 (StrUtil.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
// 校验手机
|
||||
return ValidationUtils.isMobile(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# \u901A\u7528\u5F02\u5E38\u4FE1\u606F
|
||||
id.positive=id\u5FC5\u987B\u4E3A\u6B63\u6574\u6570
|
||||
page.count.min=\u5206\u9875\u6570\u91CF\u5FC5\u987B\u4E3A\u6B63\u6574\u6570
|
||||
page.count.max=\u5206\u9875\u6570\u91CF\u5FC5\u987B\u5C0F\u4E8E{value}
|
||||
page.number.min=\u5206\u9875\u9875\u7801\u5FC5\u987B\u4E3A\u6B63\u6574\u6570
|
||||
date.past=\u65E5\u671F\u5FC5\u987B\u662F\u8FC7\u53BB\u7684\u65F6\u95F4
|
||||
date.interval=\u7ED3\u675F\u65E5\u671F\u5FC5\u987B\u65E9\u4E8E\u5F00\u59CB\u65E5\u671F
|
||||
text.length.min=\u6700\u5C0F\u957F\u5EA6\u5FC5\u987B\u5927\u4E8E{min}
|
||||
text.length.max=\u6700\u5927\u957F\u5EA6\u5FC5\u987B\u5C0F\u4E8E{max}
|
||||
phone.valid=\u5FC5\u987B\u662F\u5927\u964611\u4F4D\u624B\u673A\u53F7
|
||||
zipcode.valid=\u90AE\u7F16\u5FC5\u987B\u4E3A6\u4F4D\u6570\u5B57
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
_____ _____ _____ _____ _____
|
||||
/\ \ /\ \ /\ \ /\ \ /\ \
|
||||
/::\ \ /::\ \ /::\ \ /::\ \ /::\ \
|
||||
/::::\ \ /::::\ \ \:::\ \ /::::\ \ /::::\ \
|
||||
/::::::\ \ /::::::\ \ \:::\ \ /::::::\ \ /::::::\ \
|
||||
/:::/\:::\ \ /:::/\:::\ \ \:::\ \ /:::/\:::\ \ /:::/\:::\ \
|
||||
/:::/__\:::\ \ /:::/__\:::\ \ \:::\ \ /:::/__\:::\ \ /:::/__\:::\ \
|
||||
/::::\ \:::\ \ \:::\ \:::\ \ /::::\ \ /::::\ \:::\ \ /::::\ \:::\ \
|
||||
/::::::\ \:::\ \ ___\:::\ \:::\ \ /::::::\ \ /::::::\ \:::\ \ /::::::\ \:::\ \
|
||||
/:::/\:::\ \:::\ \ /\ \:::\ \:::\ \ /:::/\:::\ \ /:::/\:::\ \:::\ \ /:::/\:::\ \:::\____\
|
||||
/:::/__\:::\ \:::\____\ /::\ \:::\ \:::\____\ /:::/ \:::\____\/:::/ \:::\ \:::\____\/:::/ \:::\ \:::| |
|
||||
\:::\ \:::\ \::/ / \:::\ \:::\ \::/ / /:::/ \::/ /\::/ \:::\ /:::/ /\::/ |::::\ /:::|____|
|
||||
\:::\ \:::\ \/____/ \:::\ \:::\ \/____/ /:::/ / \/____/ \/____/ \:::\/:::/ / \/____|:::::\/:::/ /
|
||||
\:::\ \:::\ \ \:::\ \:::\ \ /:::/ / \::::::/ / |:::::::::/ /
|
||||
\:::\ \:::\____\ \:::\ \:::\____\ /:::/ / \::::/ / |::|\::::/ /
|
||||
\:::\ \::/ / \:::\ /:::/ / \::/ / /:::/ / |::| \::/____/
|
||||
\:::\ \/____/ \:::\/:::/ / \/____/ /:::/ / |::| ~|
|
||||
\:::\ \ \::::::/ / /:::/ / |::| |
|
||||
\:::\____\ \::::/ / /:::/ / \::| |
|
||||
\::/ / \::/ / \::/ / \:| |
|
||||
\/____/ \/____/ \/____/ \|___|
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user