Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# oneone-common
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>oneone-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-biz</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>oneone-upms-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>common-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>common-mybatis-plus</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.oneone.common.biz.datapermission.config;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.oneone.common.mybatis.datapermission.core.aop.DataPermissionAnnotationAdvisor;
|
||||
import com.oneone.common.mybatis.datapermission.core.db.DataPermissionDatabaseInterceptor;
|
||||
import com.oneone.common.mybatis.datapermission.core.rule.DataPermissionRule;
|
||||
import com.oneone.common.mybatis.datapermission.core.rule.DataPermissionRuleFactory;
|
||||
import com.oneone.common.mybatis.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
||||
import com.oneone.common.mybatis.util.MyBatisUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据权限的自动配置类
|
||||
*
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class DataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
|
||||
return new DataPermissionRuleFactoryImpl(rules);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,
|
||||
DataPermissionRuleFactory ruleFactory) {
|
||||
// 创建 DataPermissionDatabaseInterceptor 拦截器
|
||||
DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);
|
||||
// 添加到 interceptor 中
|
||||
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
|
||||
MyBatisUtils.addInterceptor(interceptor, inner, 0);
|
||||
return inner;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
|
||||
return new DataPermissionAnnotationAdvisor();
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.oneone.common.biz.datapermission.config;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.oneone.common.biz.datapermission.rule.dept.DeptDataPermissionRule;
|
||||
import com.oneone.common.biz.datapermission.rule.dept.DeptDataPermissionRuleCustomizer;
|
||||
import com.oneone.upms.api.permission.PermissionApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于部门的数据权限 AutoConfiguration
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnBean(value = DeptDataPermissionRuleCustomizer.class)
|
||||
public class DeptDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,
|
||||
List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
// Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用
|
||||
try {
|
||||
PermissionApi permissionApiImpl = SpringUtil.getBean("permissionApiImpl", PermissionApi.class);
|
||||
if (permissionApiImpl != null) {
|
||||
permissionApi = permissionApiImpl;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 创建 DeptDataPermissionRule 对象
|
||||
DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
|
||||
// 补全表配置
|
||||
customizers.forEach(customizer -> customizer.customize(rule));
|
||||
return rule;
|
||||
}
|
||||
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
package com.oneone.common.biz.datapermission.rule.dept;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.oneone.common.base.LocalUser;
|
||||
import com.oneone.common.mybatis.datapermission.core.rule.DataPermissionRule;
|
||||
import com.oneone.common.mybatis.entity.BaseDO;
|
||||
import com.oneone.common.mybatis.util.MyBatisUtils;
|
||||
import com.oneone.common.util.JsonUtils;
|
||||
import com.oneone.common.util.collection.CollectionUtils;
|
||||
import com.oneone.common.web.util.UserContext;
|
||||
import com.oneone.upms.api.enums.UserTypeEnum;
|
||||
import com.oneone.upms.api.permission.PermissionApi;
|
||||
import com.oneone.upms.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||
*
|
||||
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
||||
*
|
||||
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
||||
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【采用该方案】
|
||||
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
||||
* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
|
||||
* 最终过滤条件是 WHERE dept_id = ?
|
||||
* 2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;
|
||||
* 最终过滤条件是 WHERE user_id IN (?, ?, ? ...)
|
||||
* 3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;
|
||||
* 最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)
|
||||
*
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
|
||||
/**
|
||||
* LoginUser 的 Context 缓存 Key
|
||||
*/
|
||||
protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();
|
||||
|
||||
private static final String DEPT_COLUMN_NAME = "dept_id";
|
||||
private static final String USER_COLUMN_NAME = "user_id";
|
||||
|
||||
static final Expression EXPRESSION_NULL = new NullValue();
|
||||
|
||||
private final PermissionApi permissionApi;
|
||||
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
private final Map<String, String> deptColumns = new HashMap<>();
|
||||
/**
|
||||
* 基于用户的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
private final Map<String, String> userColumns = new HashMap<>();
|
||||
/**
|
||||
* 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
|
||||
*/
|
||||
private final Set<String> TABLE_NAMES = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Set<String> getTableNames() {
|
||||
return TABLE_NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||
// 只有有登陆用户的情况下,才进行数据权限的处理
|
||||
LocalUser localUser = UserContext.getUser();
|
||||
if (localUser == null) {
|
||||
return null;
|
||||
}
|
||||
// 只有管理员类型的用户,才进行数据权限的处理
|
||||
if (ObjectUtil.notEqual(localUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获得数据权限
|
||||
DeptDataPermissionRespDTO deptDataPermission = localUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
|
||||
// 从上下文中拿不到,则调用逻辑进行获取
|
||||
if (deptDataPermission == null) {
|
||||
deptDataPermission = permissionApi.getDeptDataPermission(localUser.getUserId()).getCheckedData();
|
||||
if (deptDataPermission == null) {
|
||||
log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJSONString(localUser));
|
||||
throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
|
||||
localUser.getUserId(), tableName, tableAlias.getName()));
|
||||
}
|
||||
// 添加到上下文中,避免重复计算
|
||||
localUser.setContext(CONTEXT_KEY, deptDataPermission);
|
||||
}
|
||||
|
||||
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
|
||||
if (deptDataPermission.getAll()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
|
||||
if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
|
||||
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {
|
||||
return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
|
||||
}
|
||||
|
||||
// 情况三,拼接 Dept 和 User 的条件,最后组合
|
||||
Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
|
||||
Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), localUser.getUserId());
|
||||
if (deptExpression == null && userExpression == null) {
|
||||
// TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
|
||||
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
|
||||
JsonUtils.toJSONString(localUser), tableName, tableAlias, JsonUtils.toJSONString(deptDataPermission));
|
||||
// throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
|
||||
// loginUser.getId(), tableName, tableAlias.getName()));
|
||||
return EXPRESSION_NULL;
|
||||
}
|
||||
if (deptExpression == null) {
|
||||
return userExpression;
|
||||
}
|
||||
if (userExpression == null) {
|
||||
return deptExpression;
|
||||
}
|
||||
// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
|
||||
return new Parenthesis(new OrExpression(deptExpression, userExpression));
|
||||
}
|
||||
|
||||
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
|
||||
// 如果不存在配置,则无需作为条件
|
||||
String columnName = deptColumns.get(tableName);
|
||||
if (StrUtil.isEmpty(columnName)) {
|
||||
return null;
|
||||
}
|
||||
// 如果为空,则无条件
|
||||
if (CollUtil.isEmpty(deptIds)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接条件
|
||||
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
||||
new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
|
||||
}
|
||||
|
||||
private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
|
||||
// 如果不查看自己,则无需作为条件
|
||||
if (Boolean.FALSE.equals(self)) {
|
||||
return null;
|
||||
}
|
||||
String columnName = userColumns.get(tableName);
|
||||
if (StrUtil.isEmpty(columnName)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接条件
|
||||
return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
|
||||
}
|
||||
|
||||
// ==================== 添加配置 ====================
|
||||
|
||||
public void addDeptColumn(Class<? extends BaseDO> entityClass) {
|
||||
addDeptColumn(entityClass, DEPT_COLUMN_NAME);
|
||||
}
|
||||
|
||||
public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
|
||||
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
|
||||
addDeptColumn(tableName, columnName);
|
||||
}
|
||||
|
||||
public void addDeptColumn(String tableName, String columnName) {
|
||||
deptColumns.put(tableName, columnName);
|
||||
TABLE_NAMES.add(tableName);
|
||||
}
|
||||
|
||||
public void addUserColumn(Class<? extends BaseDO> entityClass) {
|
||||
addUserColumn(entityClass, USER_COLUMN_NAME);
|
||||
}
|
||||
|
||||
public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
|
||||
String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
|
||||
addUserColumn(tableName, columnName);
|
||||
}
|
||||
|
||||
public void addUserColumn(String tableName, String columnName) {
|
||||
userColumns.put(tableName, columnName);
|
||||
TABLE_NAMES.add(tableName);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.oneone.common.biz.datapermission.rule.dept;
|
||||
|
||||
/**
|
||||
* {@link DeptDataPermissionRule} 的自定义配置接口
|
||||
*
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DeptDataPermissionRuleCustomizer {
|
||||
|
||||
/**
|
||||
* 自定义该权限规则
|
||||
* 1. 调用 {@link DeptDataPermissionRule#addDeptColumn(Class, String)} 方法,配置基于 dept_id 的过滤规则
|
||||
* 2. 调用 {@link DeptDataPermissionRule#addUserColumn(Class, String)} 方法,配置基于 user_id 的过滤规则
|
||||
*
|
||||
* @param rule 权限规则
|
||||
*/
|
||||
void customize(DeptDataPermissionRule rule);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022/2/23 15:58
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "通用奖励" )
|
||||
public class ActiveReward {
|
||||
@Schema(description = "奖励类型" )
|
||||
private Integer type;
|
||||
@Schema(description = "奖励id" )
|
||||
private Integer propId;
|
||||
@Schema(description = "奖励数量" )
|
||||
private Integer num;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @desc 虚拟店铺商品
|
||||
* @date 2023-04-27 14:41:26
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "虚拟店铺商品" )
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BaseItem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "" )
|
||||
private Long id;
|
||||
/**
|
||||
* 模型编码
|
||||
*/
|
||||
@Schema(description = "模型编码" )
|
||||
private Integer modelCode;
|
||||
|
||||
@Schema(description = "物品类型" )
|
||||
private Integer itemType;
|
||||
|
||||
/**
|
||||
* 物品id
|
||||
*/
|
||||
@Schema(description = "物品id" )
|
||||
private Integer itemId;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
@Schema(description = "商品名称" )
|
||||
private String itemName;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述" )
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "价格表" )
|
||||
private Map<String, ItemPrice> priceMap;
|
||||
|
||||
@Schema(description = "放置点位" )
|
||||
private Integer locationCode;
|
||||
|
||||
@Schema(description = "放置点位模型编码" )
|
||||
private Integer locationModelCode;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-10 17:40
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseUpdateItemParam implements java.io.Serializable {
|
||||
private final static long serialVersionUID = 1L;
|
||||
@Schema(description = "用户id" )
|
||||
protected Long memberId;
|
||||
@Schema(description = "物品id" )
|
||||
protected Integer itemId;
|
||||
@Schema(description = "来源" )
|
||||
protected String origin;
|
||||
@Schema(description = "来源序列号" )
|
||||
protected String originSn;
|
||||
@Schema(description = "价格" )
|
||||
private BigDecimal price;
|
||||
@Schema(description = "币种" )
|
||||
private String currency;
|
||||
|
||||
public BaseUpdateItemParam(Long memberId, Integer itemId, String origin, String originSn) {
|
||||
this.memberId = memberId;
|
||||
this.itemId = itemId;
|
||||
this.origin = origin;
|
||||
this.originSn = originSn;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022/2/23 15:58
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "通用奖励")
|
||||
public class CommonReward {
|
||||
@Schema(description = "奖励类型")
|
||||
private Integer rewardType;
|
||||
@Schema(description = "奖励id")
|
||||
private Long rewardId;
|
||||
@Schema(description = "奖励数量")
|
||||
private BigDecimal rewardValue;
|
||||
@Schema(description = "图片url")
|
||||
private String rewardPicUrl;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-08 15:55
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class ItemPrice implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 币种
|
||||
*/
|
||||
@Schema(description = "币种")
|
||||
@JsonIgnore
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 原始价格
|
||||
*/
|
||||
@Schema(description = "原始价格")
|
||||
private BigDecimal originPrice;
|
||||
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
@Schema(description = "价格")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 折扣
|
||||
*/
|
||||
@Schema(description = "折扣")
|
||||
private BigDecimal discount;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class ItemPriceDTO extends ItemPrice{
|
||||
private Long relationshipId;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.oneone.common.biz.domain;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author shi_chengcai
|
||||
* @version 1.0
|
||||
* @desc
|
||||
* @date 2023/5/8 14:54
|
||||
*/
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class SceneLocationConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 物品类型
|
||||
*/
|
||||
@Schema(description = "物品类型")
|
||||
private Integer itemType;
|
||||
|
||||
/**
|
||||
* 点位code
|
||||
*/
|
||||
@Schema(description = "点位code")
|
||||
private Integer locationCode;
|
||||
|
||||
/**
|
||||
* 点位模型code
|
||||
*/
|
||||
@Schema(description = "点位模型code")
|
||||
private Integer locationModelCode;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.oneone.common.biz.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* APP
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2021/10/4
|
||||
*/
|
||||
@Getter
|
||||
public enum AppNameEnum {
|
||||
|
||||
OMS("oms", "订单服务"),
|
||||
CMS("cms", "藏品服务"),
|
||||
UMS("ums", "用户服务"),
|
||||
BOOKSHOP("bookshop", "书店服务");
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String name;
|
||||
|
||||
AppNameEnum(String value, String name) {
|
||||
this.code = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static AppNameEnum getByCode(String code) {
|
||||
for (AppNameEnum item : values()) {
|
||||
if (item.getCode().equals(code)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.oneone.common.biz.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @date 2021-02-17
|
||||
*/
|
||||
@Getter
|
||||
public enum BusinessTypeEnum {
|
||||
|
||||
USER("user", 100),
|
||||
VS_STORE("vs_store_balance", 110),
|
||||
MEMBER("member", 200),
|
||||
ORDER_SN("order_sn", 300),
|
||||
CONSIGNMENT_ORDER("consignment_order", 310),
|
||||
WAYBILL_ORDER("waybill_order", 320),
|
||||
ORDER_TRANSACTION_SN("order_ts_sn", 400),
|
||||
SYNTHETIC_ORDER_SN("synthetic_order_sn", 500),
|
||||
COLLECTION_AIRDROP_SN("collection_airdrop_sn", 600),
|
||||
TRANSFER_SN("transfer_sn", 700),
|
||||
MALL_SN("mall_sn", 800),
|
||||
CLUB_MALL_SN("club_mall_sn", 810),
|
||||
QUIZ("quiz", 820),
|
||||
|
||||
|
||||
BIG_WHEEL("big_wheel", 900),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
|
||||
private final Integer value;
|
||||
|
||||
BusinessTypeEnum(String code, Integer value) {
|
||||
this.code = code;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static BusinessTypeEnum getValue(String code) {
|
||||
BusinessTypeEnum businessTypeEnum=null;
|
||||
for (BusinessTypeEnum value : values()) {
|
||||
if (value.getCode().equals(code)) {
|
||||
businessTypeEnum =value;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return businessTypeEnum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.oneone.common.biz.enums;
|
||||
|
||||
import com.oneone.common.result.BaseEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-06-21 21:58
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public enum OriginEnum implements BaseEnum {
|
||||
SYS_DEFAULT("SYS_DEFAULT", "系统默认"),
|
||||
ORDER("ORDER", "订单"),
|
||||
MESSAGE("MESSAGE", "消息"),
|
||||
|
||||
ACTIVE_USE("ACTIVE_USE", "分身活动使用"),
|
||||
ACTIVE_RETURN("ACTIVE_RETURN", "分身活动返还"),
|
||||
|
||||
TREASURE("TREASURE", "宝藏"),
|
||||
REGISTER_REWARD("REGISTER_REWARD", "注册有礼"),
|
||||
PHOTO_CLOCK("PHOTO_CLOCK", "打卡活动"),
|
||||
INJECT_METEORITE("INJECT_METEORITE", "星能投放"),
|
||||
GAME_RECORD_BREAK("GAME_RECORD_BREAK", "打破记录"),
|
||||
|
||||
GAME_RACING_ENCOURAGE("GAME_RACING_ENCOURAGE", "AI赛车助威"),
|
||||
RACING_ENCOURAGE_AWARD("RACING_ENCOURAGE_AWARD", "AI赛车助威奖励"),
|
||||
RACING_EXPEND("RACING_EXPEND", "赛车消耗"),
|
||||
|
||||
AI_CREATE("AI_CREATE", "AI创作"),
|
||||
|
||||
NFC("NFC", "NFC"),
|
||||
;
|
||||
|
||||
private final String code;
|
||||
private final String msg;
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.oneone.common.biz.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RewardTypeEnum {
|
||||
UNKNOWN(0, "未知"),
|
||||
IMAGE(1, "图"),
|
||||
COUPONS(2, "优惠券"),
|
||||
COLLECTION(3, "藏品"),
|
||||
GOLD(4, "金币"),
|
||||
PROP(5, "道具"),
|
||||
GEM(6, "钻石"),
|
||||
POINT(7, "点券"),
|
||||
CLOTHING(8, "皮肤/角色"),
|
||||
ARTWORK(9, "艺术品"),
|
||||
BOOK(10, "书"),
|
||||
;
|
||||
private final Integer code;
|
||||
|
||||
private final String text;
|
||||
|
||||
public static RewardTypeEnum getByCode(int code) {
|
||||
for (RewardTypeEnum value : values()) {
|
||||
if (value.code == code) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.oneone.common.biz.logic.config;
|
||||
|
||||
import com.oneone.common.biz.logic.itemprice.mapper.ItemPriceMapper;
|
||||
import com.oneone.common.biz.logic.itemprice.ItemPriceService;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-08 16:30
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.oneone.common.biz.logic.itemprice.mapper")
|
||||
public class LogicConfiguration {
|
||||
|
||||
@Bean
|
||||
public ItemPriceService itemPriceService(ItemPriceMapper itemPriceMapper){
|
||||
return new ItemPriceService(itemPriceMapper);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.oneone.common.biz.logic.itemprice;
|
||||
|
||||
import com.oneone.common.biz.domain.ItemPrice;
|
||||
import com.oneone.common.biz.domain.ItemPriceDTO;
|
||||
import com.oneone.common.biz.logic.itemprice.mapper.ItemPriceMapper;
|
||||
import com.oneone.common.util.BeanUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-08 16:22
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ItemPriceService {
|
||||
private final ItemPriceMapper itemPriceMapper;
|
||||
|
||||
public List<ItemPrice> getItemPrices(String tableName, Long relationshipId) {
|
||||
return itemPriceMapper.selectItemPrices(tableName, relationshipId);
|
||||
}
|
||||
|
||||
public ItemPrice getItemPrice(String tableName, Long relationshipId, String currency) {
|
||||
return itemPriceMapper.selectItemPrice(tableName, relationshipId, currency);
|
||||
}
|
||||
|
||||
public Map<String, ItemPrice> getItemPriceMap(String tableName, Long relationshipId) {
|
||||
List<ItemPrice> itemPrices = itemPriceMapper.selectItemPrices(tableName, relationshipId);
|
||||
if (CollectionUtils.isEmpty(itemPrices)) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
return itemPrices.stream().collect(Collectors.toMap(ItemPrice::getCurrency, ItemPrice -> ItemPrice));
|
||||
}
|
||||
|
||||
public Map<Long, Map<String, ItemPrice>> getItemPriceMap(String tableName, List<Long> relationshipIds) {
|
||||
List<ItemPriceDTO> itemPrices = itemPriceMapper.selectByRelationshipIds(tableName, relationshipIds);
|
||||
if (CollectionUtils.isEmpty(itemPrices)) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
Map<Long, Map<String, ItemPrice>> resultMap = new HashMap<>();
|
||||
for (ItemPriceDTO itemPriceDTO : itemPrices) {
|
||||
Map<String, ItemPrice> itemPriceMap = resultMap.computeIfAbsent(itemPriceDTO.getRelationshipId(), k -> new HashMap<>());
|
||||
itemPriceMap.put(itemPriceDTO.getCurrency(), BeanUtil.copy(itemPriceDTO, ItemPrice.class));
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.oneone.common.biz.logic.itemprice.mapper;
|
||||
|
||||
|
||||
import com.oneone.common.biz.domain.ItemPrice;
|
||||
import com.oneone.common.biz.domain.ItemPriceDTO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2023-02-08 16:15
|
||||
*/
|
||||
@Mapper
|
||||
public interface ItemPriceMapper {
|
||||
|
||||
/**
|
||||
* 通过数据库查询物品价格
|
||||
* @param tableName
|
||||
* @param relationshipId
|
||||
* @return
|
||||
*/
|
||||
@Select("select currency,origin_price,price,discount from ${tableName} where relationship_id = #{relationshipId}")
|
||||
List<ItemPrice> selectItemPrices(@Param("tableName")String tableName, @Param("relationshipId") Long relationshipId);
|
||||
|
||||
/**
|
||||
* 通过数据库查询物品价格
|
||||
* @param tableName
|
||||
* @param relationshipId
|
||||
* @param currency
|
||||
* @return
|
||||
*/
|
||||
@Select("select currency,origin_price,price,discount from ${tableName} where relationship_id = #{relationshipId} and currency = #{currency}")
|
||||
ItemPrice selectItemPrice(@Param("tableName")String tableName, @Param("relationshipId") Long relationshipId,@Param("currency")String currency);
|
||||
|
||||
@Select( "<script> "+
|
||||
"select relationship_id,currency,origin_price,price,discount from ${tableName} where " +
|
||||
" relationship_id in" +
|
||||
"<foreach collection='relationshipIds' item='relationshipId' open='(' separator=',' close=')'>"+
|
||||
" #{relationshipId} " +
|
||||
"</foreach> " +
|
||||
"</script>"
|
||||
)
|
||||
List<ItemPriceDTO> selectByRelationshipIds(@Param("tableName") String tableName, @Param("relationshipIds") List<Long> relationshipIds);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.oneone.common.biz.utils;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.oneone.common.biz.enums.BusinessTypeEnum;
|
||||
import com.oneone.common.constant.RedisConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class BusinessNoGenerator {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
@Value("${spring.profiles.active}")
|
||||
private String env;
|
||||
|
||||
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
/**
|
||||
* @param businessType 业务类型枚举
|
||||
* @param digit 业务序号位数
|
||||
* @return
|
||||
*/
|
||||
public String generate(BusinessTypeEnum businessType, Integer digit) {
|
||||
String date = LocalDateTime.now(ZoneOffset.of("+8")).format(formatter);
|
||||
String key = RedisConstants.BUSINESS_NO_PREFIX + date + ":" + businessType.getCode();
|
||||
Long increment = redisTemplate.opsForValue().increment(key, RandomUtil.randomInt(1,10));
|
||||
String no = businessType.getValue() + date + String.format("%0" + digit + "d", increment);
|
||||
if (!"prod".equals(env)){
|
||||
return "t"+no;
|
||||
}else {
|
||||
return no;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String generate(BusinessTypeEnum businessType) {
|
||||
Integer defaultDigit = 7;
|
||||
return generate(businessType, defaultDigit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.oneone.common.biz.utils.BusinessNoGenerator,\
|
||||
com.oneone.common.biz.logic.config.LogicConfiguration,\
|
||||
com.oneone.common.biz.datapermission.config.DataPermissionAutoConfiguration,\
|
||||
com.oneone.common.biz.datapermission.config.DeptDataPermissionAutoConfiguration
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user