Initial commit

This commit is contained in:
jackyu66git
2026-03-23 11:52:51 +08:00
commit ea8d889fbe
1114 changed files with 92438 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# oneone-common
+44
View File
@@ -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>
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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
+119
View File
@@ -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;
}
@@ -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";
}
@@ -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";
}
@@ -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:";
}
@@ -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";
}
@@ -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";
}
@@ -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();
}
@@ -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);
}
@@ -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);
}
}
@@ -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";
}
@@ -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 "******";
}
@@ -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);
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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 "*";
}
@@ -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;
}
@@ -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);
}
@@ -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);
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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));
}
}
}
@@ -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();
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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&sup2;) 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);
}
}
@@ -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