初始化代码 2022-02-11前 最新版本
This commit is contained in:
296
framework/src/main/java/cn/lili/cache/Cache.java
vendored
Normal file
296
framework/src/main/java/cn/lili/cache/Cache.java
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
package cn.lili.cache;
|
||||
|
||||
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 缓存接口
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
public interface Cache<T> {
|
||||
|
||||
/**
|
||||
* Get an item from the cache, nontransactionally
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @return the cached object or <tt>null</tt>
|
||||
*/
|
||||
T get(Object key);
|
||||
|
||||
/**
|
||||
* Get an item from the cache, nontransactionally
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @return the cached object or <tt>null</tt>
|
||||
*/
|
||||
String getString(Object key);
|
||||
|
||||
|
||||
/**
|
||||
* multiGet
|
||||
*
|
||||
* @param keys 要查询的key集合
|
||||
* @return 集合
|
||||
*/
|
||||
List multiGet(Collection keys);
|
||||
|
||||
/**
|
||||
* 批量set
|
||||
*
|
||||
* @param map 键值对
|
||||
*/
|
||||
void multiSet(Map map);
|
||||
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param keys 要删除的key集合
|
||||
*/
|
||||
void multiDel(Collection keys);
|
||||
|
||||
/**
|
||||
* Add an item to the cache, nontransactionally, with
|
||||
* failfast semantics
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param value 缓存value
|
||||
*/
|
||||
void put(Object key, T value);
|
||||
|
||||
/**
|
||||
* 往缓存中写入内容
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param value 缓存value
|
||||
* @param exp 超时时间,单位为秒
|
||||
*/
|
||||
void put(Object key, T value, Long exp);
|
||||
|
||||
/**
|
||||
* 往缓存中写入内容
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param value 缓存value
|
||||
* @param exp 过期时间
|
||||
* @param timeUnit 过期单位
|
||||
*/
|
||||
void put(Object key, T value, Long exp, TimeUnit timeUnit);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param key 缓存key
|
||||
*/
|
||||
Boolean remove(Object key);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param key 缓存key
|
||||
*/
|
||||
void vagueDel(Object key);
|
||||
|
||||
/**
|
||||
* Clear the cache
|
||||
*/
|
||||
void clear();
|
||||
|
||||
|
||||
/**
|
||||
* 往缓存中写入内容
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param hashKey 缓存中hashKey
|
||||
* @param hashValue hash值
|
||||
*/
|
||||
void putHash(Object key, Object hashKey, Object hashValue);
|
||||
|
||||
/**
|
||||
* 玩缓存中写入内容
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param map map value
|
||||
*/
|
||||
void putAllHash(Object key, Map map);
|
||||
|
||||
/**
|
||||
* 读取缓存值
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param hashKey map value
|
||||
* @return 返回缓存中的数据
|
||||
*/
|
||||
T getHash(Object key, Object hashKey);
|
||||
|
||||
/**
|
||||
* 读取缓存值
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @return 缓存中的数据
|
||||
*/
|
||||
Map<Object, Object> getHash(Object key);
|
||||
|
||||
/**
|
||||
* 是否包含
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @return 缓存中的数据
|
||||
*/
|
||||
boolean hasKey(Object key);
|
||||
|
||||
|
||||
/**
|
||||
* 模糊匹配key
|
||||
*
|
||||
* @param pattern 模糊key
|
||||
* @return 缓存中的数据
|
||||
*/
|
||||
List<String> keys(String pattern);
|
||||
|
||||
|
||||
//-----------------------------------------------用于特殊场景,redis去重计数---------------------------------------------
|
||||
|
||||
/**
|
||||
* 累计数目
|
||||
* 效率较高的 计数器
|
||||
* 如需清零,按照普通key 移除即可
|
||||
*
|
||||
* @param key key值
|
||||
* @param value 去重统计值
|
||||
* @return 计数器结果
|
||||
*/
|
||||
Long cumulative(Object key, Object value);
|
||||
|
||||
/**
|
||||
* 计数器结果
|
||||
* <p>
|
||||
* 效率较高的 计数器 统计返回
|
||||
* 如需清零,按照普通key 移除即可
|
||||
*
|
||||
* @param key 计数器key
|
||||
* @return 计数器结果
|
||||
*/
|
||||
Long counter(Object key);
|
||||
|
||||
/**
|
||||
* 批量计数
|
||||
*
|
||||
* @param keys 要查询的key集合
|
||||
* @return 批量计数
|
||||
*/
|
||||
List multiCounter(Collection keys);
|
||||
|
||||
/**
|
||||
* 计数器结果
|
||||
* <p>
|
||||
* 效率较高的 计数器 统计返回
|
||||
* 如需清零,按照普通key 移除即可
|
||||
*
|
||||
* @param key key值
|
||||
* @return 计数器结果
|
||||
*/
|
||||
Long mergeCounter(Object... key);
|
||||
//---------------------------------------------------用于特殊场景,redis去重统计-----------------------------------------
|
||||
|
||||
//-----------------------------------------------redis计数---------------------------------------------
|
||||
|
||||
/**
|
||||
* redis 计数器 累加
|
||||
* 注:到达liveTime之后,该次增加取消,即自动-1,而不是redis值为空
|
||||
*
|
||||
* @param key 为累计的key,同一key每次调用则值 +1
|
||||
* @param liveTime 单位秒后失效
|
||||
* @return 计数器结果
|
||||
*/
|
||||
Long incr(String key, long liveTime);
|
||||
/**
|
||||
* redis 计数器 累加
|
||||
* 注:到达liveTime之后,该次增加取消,即自动-1,而不是redis值为空
|
||||
*
|
||||
* @param key 为累计的key,同一key每次调用则值 +1
|
||||
* @return 计数器结果
|
||||
*/
|
||||
Long incr(String key);
|
||||
//-----------------------------------------------redis计数---------------------------------------------
|
||||
|
||||
/**
|
||||
* 使用Sorted Set记录keyword
|
||||
* zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员
|
||||
*
|
||||
* @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据
|
||||
* @param keyword 关键词
|
||||
*/
|
||||
void incrementScore(String sortedSetName, String keyword);
|
||||
|
||||
/**
|
||||
* 使用Sorted Set记录keyword
|
||||
* zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员
|
||||
*
|
||||
* @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据
|
||||
* @param keyword 关键词
|
||||
* @param score 分数
|
||||
*/
|
||||
void incrementScore(String sortedSetName, String keyword, Integer score);
|
||||
|
||||
/**
|
||||
* zrevrange命令, 查询Sorted Set中指定范围的值
|
||||
* 返回的有序集合中,score大的在前面
|
||||
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
|
||||
*
|
||||
* @param sortedSetName sortedSetName
|
||||
* @param start 查询范围开始位置
|
||||
* @param end 查询范围结束位置
|
||||
* @return 获取满足条件的集合
|
||||
*/
|
||||
Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer start, Integer end);
|
||||
|
||||
/**
|
||||
* zrevrange命令, 查询Sorted Set中指定范围的值
|
||||
* 返回的有序集合中,score大的在前面
|
||||
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
|
||||
*
|
||||
* @param sortedSetName sortedSetName
|
||||
* @param count 查询数量
|
||||
* @return 获取满足条件的集合
|
||||
*/
|
||||
Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer count);
|
||||
|
||||
|
||||
/**
|
||||
* 向Zset里添加成员
|
||||
*
|
||||
* @param key key值
|
||||
* @param score 分数
|
||||
* @param value 值
|
||||
* @return 增加状态
|
||||
*/
|
||||
boolean zAdd(String key, long score, String value);
|
||||
|
||||
|
||||
/**
|
||||
* 获取 某key 下 某一分值区间的队列
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param from 开始时间
|
||||
* @param to 结束时间
|
||||
* @return 数据
|
||||
*/
|
||||
Set<ZSetOperations.TypedTuple<Object>> zRangeByScore(String key, int from, long to);
|
||||
|
||||
/**
|
||||
* 移除 Zset队列值
|
||||
*
|
||||
* @param key key值
|
||||
* @param value 删除的集合
|
||||
* @return 删除数量
|
||||
*/
|
||||
Long zRemove(String key, String... value);
|
||||
}
|
||||
523
framework/src/main/java/cn/lili/cache/CachePrefix.java
vendored
Normal file
523
framework/src/main/java/cn/lili/cache/CachePrefix.java
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
package cn.lili.cache;
|
||||
|
||||
import cn.lili.common.enums.PromotionTypeEnum;
|
||||
import cn.lili.common.security.enums.UserEnums;
|
||||
|
||||
/**
|
||||
* 缓存前缀
|
||||
*
|
||||
* @author pikachu
|
||||
* @version 4.1
|
||||
* @since 1.0
|
||||
* 2018/3/19
|
||||
*/
|
||||
public enum CachePrefix {
|
||||
|
||||
|
||||
/**
|
||||
* nonce
|
||||
*/
|
||||
NONCE,
|
||||
|
||||
/**
|
||||
* 在线人数
|
||||
*/
|
||||
ONLINE_NUM,
|
||||
|
||||
|
||||
/**
|
||||
* 会员分布数据
|
||||
*/
|
||||
MEMBER_DISTRIBUTION,
|
||||
|
||||
/**
|
||||
* 在线会员统计
|
||||
*/
|
||||
ONLINE_MEMBER,
|
||||
|
||||
/**
|
||||
* token 信息
|
||||
*/
|
||||
ACCESS_TOKEN,
|
||||
/**
|
||||
* token 信息
|
||||
*/
|
||||
REFRESH_TOKEN,
|
||||
/**
|
||||
* 联合登录响应
|
||||
*/
|
||||
CONNECT_RESULT,
|
||||
/**
|
||||
* 微信联合登陆 session key
|
||||
*/
|
||||
SESSION_KEY,
|
||||
/**
|
||||
* 权限
|
||||
*/
|
||||
PERMISSION_LIST,
|
||||
/**
|
||||
* 部门id列表
|
||||
*/
|
||||
DEPARTMENT_IDS,
|
||||
|
||||
/**
|
||||
* 用户错误登录限制
|
||||
*/
|
||||
LOGIN_TIME_LIMIT,
|
||||
/**
|
||||
* 系统设置
|
||||
*/
|
||||
SETTING,
|
||||
|
||||
/**
|
||||
* 验证码滑块源
|
||||
*/
|
||||
VERIFICATION,
|
||||
|
||||
/**
|
||||
* 验证码滑块源
|
||||
*/
|
||||
VERIFICATION_IMAGE,
|
||||
|
||||
/**
|
||||
* 快递平台
|
||||
*/
|
||||
EXPRESS,
|
||||
|
||||
/**
|
||||
* 图片验证码
|
||||
*/
|
||||
CAPTCHA,
|
||||
|
||||
/**
|
||||
* 商品
|
||||
*/
|
||||
GOODS,
|
||||
|
||||
/**
|
||||
* 商品SKU
|
||||
*/
|
||||
GOODS_SKU,
|
||||
|
||||
/**
|
||||
* 运费模板脚本
|
||||
*/
|
||||
SHIP_SCRIPT,
|
||||
|
||||
/**
|
||||
* 商品sku
|
||||
*/
|
||||
SKU,
|
||||
|
||||
/**
|
||||
* sku库存
|
||||
*/
|
||||
SKU_STOCK,
|
||||
|
||||
/**
|
||||
* 促销商品sku库存
|
||||
*/
|
||||
PROMOTION_GOODS_STOCK,
|
||||
|
||||
/**
|
||||
* 商品库存
|
||||
*/
|
||||
GOODS_STOCK,
|
||||
|
||||
/**
|
||||
* 商品分类 树状结构
|
||||
*/
|
||||
CATEGORY,
|
||||
/**
|
||||
* 商品分类 集合
|
||||
*/
|
||||
CATEGORY_ARRAY,
|
||||
/**
|
||||
* 浏览次数
|
||||
*/
|
||||
VISIT_COUNT,
|
||||
/**
|
||||
* 存储方案
|
||||
*/
|
||||
UPLOADER,
|
||||
/**
|
||||
* 地区
|
||||
*/
|
||||
REGION,
|
||||
|
||||
/**
|
||||
* 短信网关
|
||||
*/
|
||||
SPlATFORM,
|
||||
/**
|
||||
* 短信验证码前缀
|
||||
*/
|
||||
_CODE_PREFIX,
|
||||
/**
|
||||
* smtp
|
||||
*/
|
||||
SMTP,
|
||||
/**
|
||||
* 系统设置
|
||||
*/
|
||||
SETTINGS,
|
||||
/**
|
||||
* 电子面单
|
||||
*/
|
||||
WAYBILL,
|
||||
/**
|
||||
* 短信验证码
|
||||
*/
|
||||
SMS_CODE,
|
||||
/**
|
||||
* 邮箱验证码
|
||||
*/
|
||||
EMAIL_CODE,
|
||||
/**
|
||||
* 管理员角色权限对照表
|
||||
*/
|
||||
ADMIN_URL_ROLE,
|
||||
|
||||
/**
|
||||
* 店铺管理员角色权限对照表
|
||||
*/
|
||||
STORE_URL_ROLE,
|
||||
|
||||
/**
|
||||
* 手机验证标识
|
||||
*/
|
||||
MOBILE_VALIDATE,
|
||||
|
||||
/**
|
||||
* 邮箱验证标识
|
||||
*/
|
||||
EMAIL_VALIDATE,
|
||||
|
||||
/**
|
||||
* 店铺运费模版列表
|
||||
*/
|
||||
SHIP_TEMPLATE,
|
||||
|
||||
/**
|
||||
* 店铺中某个运费模版
|
||||
*/
|
||||
SHIP_TEMPLATE_ONE,
|
||||
|
||||
//================促销=================
|
||||
/**
|
||||
* 促销活动
|
||||
*/
|
||||
PROMOTION,
|
||||
/**
|
||||
* 促销活动
|
||||
*/
|
||||
PROMOTION_GOODS,
|
||||
|
||||
/*** 单品立减 */
|
||||
STORE_ID_MINUS,
|
||||
|
||||
/*** 第二件半价 */
|
||||
STORE_ID_HALF_PRICE,
|
||||
|
||||
/*** 满优惠 */
|
||||
STORE_ID_FULL_DISCOUNT,
|
||||
|
||||
/**
|
||||
* 秒杀活动活动缓存key前缀
|
||||
*/
|
||||
STORE_ID_SECKILL,
|
||||
|
||||
/**
|
||||
* 团购活动缓存key前缀
|
||||
*/
|
||||
STORE_ID_GROUP_BUY,
|
||||
|
||||
/**
|
||||
* 积分商品缓存key前缀
|
||||
*/
|
||||
STORE_ID_EXCHANGE,
|
||||
|
||||
|
||||
//================交易=================
|
||||
|
||||
/**
|
||||
* 购物车原始数据
|
||||
*/
|
||||
CART_ORIGIN_DATA_PREFIX,
|
||||
|
||||
/**
|
||||
* 立即购买原始数据
|
||||
*/
|
||||
BUY_NOW_ORIGIN_DATA_PREFIX,
|
||||
|
||||
/**
|
||||
* 交易原始数据
|
||||
*/
|
||||
TRADE_ORIGIN_DATA_PREFIX,
|
||||
|
||||
/**
|
||||
* 立即购买sku
|
||||
*/
|
||||
CART_SKU_PREFIX,
|
||||
|
||||
/**
|
||||
* 购物车视图
|
||||
*/
|
||||
CART_MEMBER_ID_PREFIX,
|
||||
|
||||
/**
|
||||
* 购物车,用户选择的促销信息
|
||||
*/
|
||||
CART_PROMOTION_PREFIX,
|
||||
|
||||
|
||||
/**
|
||||
* 交易_交易价格的前缀
|
||||
*/
|
||||
PRICE_SESSION_ID_PREFIX,
|
||||
|
||||
/**
|
||||
* 交易_交易单
|
||||
*/
|
||||
TRADE_SESSION_ID_PREFIX,
|
||||
|
||||
|
||||
/**
|
||||
* 结算参数
|
||||
*/
|
||||
CHECKOUT_PARAM_ID_PREFIX,
|
||||
|
||||
/**
|
||||
* 交易单号前缀
|
||||
*/
|
||||
TRADE_SN_CACHE_PREFIX,
|
||||
|
||||
/**
|
||||
* 订单编号前缀
|
||||
*/
|
||||
ORDER_SN_CACHE_PREFIX,
|
||||
/**
|
||||
* 订单编号标记
|
||||
*/
|
||||
ORDER_SN_SIGN_CACHE_PREFIX,
|
||||
/**
|
||||
* 订单编号前缀
|
||||
*/
|
||||
PAY_LOG_SN_CACHE_PREFIX,
|
||||
|
||||
/**
|
||||
* 合同编号
|
||||
*/
|
||||
CONTRACT_SN_CACHE_PREFIX,
|
||||
|
||||
|
||||
/**
|
||||
* 零钱
|
||||
*/
|
||||
SMALL_CHANGE_CACHE_PREFIX,
|
||||
|
||||
/**
|
||||
* 售后服务单号前缀
|
||||
*/
|
||||
AFTER_SALE_SERVICE_PREFIX,
|
||||
|
||||
/**
|
||||
* 交易
|
||||
*/
|
||||
TRADE,
|
||||
|
||||
/**
|
||||
* 站点导航栏
|
||||
*/
|
||||
SITE_NAVIGATION,
|
||||
|
||||
/**
|
||||
* 支付参数
|
||||
*/
|
||||
PAYMENT_CONFIG,
|
||||
|
||||
/**
|
||||
* 流程控制
|
||||
*/
|
||||
FLOW,
|
||||
|
||||
/**
|
||||
* 热门搜索
|
||||
*/
|
||||
HOT_WORD,
|
||||
|
||||
/**
|
||||
* 会员积分
|
||||
*/
|
||||
MEMBER_POINT,
|
||||
|
||||
/**
|
||||
* 会员积分
|
||||
*/
|
||||
POINT_ORDER,
|
||||
|
||||
|
||||
/**
|
||||
* 微博登录
|
||||
*/
|
||||
WEIBO_STATE,
|
||||
/**
|
||||
* 微博登录
|
||||
*/
|
||||
QQ_STATE,
|
||||
/**
|
||||
* 微博登录
|
||||
*/
|
||||
GITHUB_STATE,
|
||||
/**
|
||||
* 验证码key
|
||||
*/
|
||||
VERIFICATION_KEY,
|
||||
/**
|
||||
* 验证码验证结果
|
||||
*/
|
||||
VERIFICATION_RESULT,
|
||||
/**
|
||||
* 微信CGItoken
|
||||
*/
|
||||
WECHAT_CGI_ACCESS_TOKEN,
|
||||
/**
|
||||
* 微信JSApitoken
|
||||
*/
|
||||
WECHAT_JS_API_TOKEN,
|
||||
/**
|
||||
* 微信会话信息
|
||||
*/
|
||||
WECHAT_SESSION_PARAMS,
|
||||
/**
|
||||
* 第三方用户权限
|
||||
*/
|
||||
ALIPAY_CONFIG,
|
||||
/**
|
||||
* 微信支付配置
|
||||
*/
|
||||
WECHAT_PAY_CONFIG,
|
||||
/**
|
||||
* 微信支付平台证书配置
|
||||
*/
|
||||
WECHAT_PLAT_FORM_CERT,
|
||||
/**
|
||||
* 第三方用户权限
|
||||
*/
|
||||
CONNECT_AUTH,
|
||||
/**
|
||||
* 平台PageView 统计
|
||||
*/
|
||||
PV,
|
||||
/**
|
||||
* 平台UserView 统计
|
||||
*/
|
||||
UV,
|
||||
/**
|
||||
* 平台 商品PV 统计
|
||||
*/
|
||||
GOODS_PV,
|
||||
/**
|
||||
* 平台 商品UV 统计
|
||||
*/
|
||||
GOODS_UV,
|
||||
/**
|
||||
* 店铺PageView 统计
|
||||
*/
|
||||
STORE_PV,
|
||||
/**
|
||||
* 店铺UserView 统计
|
||||
*/
|
||||
STORE_UV,
|
||||
/**
|
||||
* 店铺 商品PV 统计
|
||||
*/
|
||||
STORE_GOODS_PV,
|
||||
/**
|
||||
* 店铺 商品UV 统计
|
||||
*/
|
||||
STORE_GOODS_UV,
|
||||
/**
|
||||
* 分销员
|
||||
*/
|
||||
DISTRIBUTION,
|
||||
|
||||
/**
|
||||
* 找回手机
|
||||
*/
|
||||
FIND_MOBILE,
|
||||
/**
|
||||
* 文章分类
|
||||
*/
|
||||
ARTICLE_CATEGORY,
|
||||
/**
|
||||
* 文章
|
||||
*/
|
||||
ARTICLE_CACHE,
|
||||
|
||||
/**
|
||||
* 初始化索引
|
||||
*/
|
||||
INIT_INDEX_PROCESS,
|
||||
|
||||
/**
|
||||
* 初始化索引标示
|
||||
*/
|
||||
INIT_INDEX_FLAG,
|
||||
|
||||
/**
|
||||
* 店铺分类
|
||||
*/
|
||||
STORE_CATEGORY,
|
||||
/**
|
||||
* 用户菜单
|
||||
*/
|
||||
MENU_USER_ID,
|
||||
/**
|
||||
* 用户菜单
|
||||
*/
|
||||
USER_MENU,
|
||||
/**
|
||||
* 订单暂时缓存
|
||||
*/
|
||||
ORDER,
|
||||
/**
|
||||
* 敏感词
|
||||
*/
|
||||
SENSITIVE;
|
||||
|
||||
|
||||
public static String removePrefix(String str) {
|
||||
return str.substring(str.lastIndexOf("}_") + 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用获取缓存key值
|
||||
*
|
||||
* @return 缓存key值
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return "{" + this.name() + "}_";
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用获取缓存key值
|
||||
*
|
||||
* @param typeEnum 促销枚举
|
||||
* @return 缓存key值
|
||||
*/
|
||||
public String getPrefix(PromotionTypeEnum typeEnum) {
|
||||
return "{" + this.name() + "_" + typeEnum.name() + "}_";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存key值 + 用户端
|
||||
* 例如:三端都有用户体系,需要分别登录,如果用户名一致,则redis中的权限可能会冲突出错
|
||||
*
|
||||
* @param user 角色
|
||||
* @return 缓存key值 + 用户端
|
||||
*/
|
||||
public String getPrefix(UserEnums user) {
|
||||
return "{" + this.name() + "_" + user.name() + "}_";
|
||||
}
|
||||
}
|
||||
40
framework/src/main/java/cn/lili/cache/config/redis/FastJsonRedisSerializer.java
vendored
Normal file
40
framework/src/main/java/cn/lili/cache/config/redis/FastJsonRedisSerializer.java
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package cn.lili.cache.config.redis;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 要实现对象的缓存,定义自己的序列化和反序列化器。使用阿里的fastjson来实现的比较多
|
||||
*
|
||||
* @author Bulbasaur
|
||||
*/
|
||||
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
private Class<T> clazz;
|
||||
|
||||
public FastJsonRedisSerializer(Class<T> clazz) {
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException {
|
||||
if (null == t) {
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException {
|
||||
if (null == bytes || bytes.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
return (T) JSON.parseObject(str, clazz);
|
||||
}
|
||||
}
|
||||
207
framework/src/main/java/cn/lili/cache/config/redis/RedisConfig.java
vendored
Normal file
207
framework/src/main/java/cn/lili/cache/config/redis/RedisConfig.java
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
package cn.lili.cache.config.redis;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.ClusterServersConfig;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SentinelServersConfig;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.interceptor.CacheErrorHandler;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义redis配置
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2021/3/20 09:37
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnClass(RedisOperations.class)
|
||||
@EnableConfigurationProperties(RedisProperties.class)
|
||||
public class RedisConfig extends CachingConfigurerSupport {
|
||||
|
||||
|
||||
@Value("${lili.cache.timeout:7200}")
|
||||
private Integer timeout;
|
||||
|
||||
@Autowired
|
||||
private RedisProperties redisProperties;
|
||||
|
||||
|
||||
/**
|
||||
* 当有多个管理器的时候,必须使用该注解在一个管理器上注释:表示该管理器为默认的管理器
|
||||
*
|
||||
* @param connectionFactory 链接工厂
|
||||
* @return 缓存
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||
//初始化一个RedisCacheWriter
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
//序列化方式2
|
||||
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
|
||||
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer);
|
||||
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
|
||||
//设置过期时间
|
||||
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(timeout));
|
||||
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
|
||||
|
||||
//设置白名单---非常重要********
|
||||
/*
|
||||
使用fastjson的时候:序列化时将class信息写入,反解析的时候,
|
||||
fastjson默认情况下会开启autoType的检查,相当于一个白名单检查,
|
||||
如果序列化信息中的类路径不在autoType中,
|
||||
反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
|
||||
可参考 https://blog.csdn.net/u012240455/article/details/80538540
|
||||
*/
|
||||
ParserConfig.getGlobalInstance().addAccept("cn.lili.");
|
||||
ParserConfig.getGlobalInstance().addAccept("cn.hutool.json.");
|
||||
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@Bean(name = "redisTemplate")
|
||||
@ConditionalOnMissingBean(name = "redisTemplate")
|
||||
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
//使用fastjson序列化
|
||||
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
|
||||
//value值的序列化采用fastJsonRedisSerializer
|
||||
template.setValueSerializer(fastJsonRedisSerializer);
|
||||
template.setHashValueSerializer(fastJsonRedisSerializer);
|
||||
//key的序列化采用StringRedisSerializer
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setConnectionFactory(lettuceConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public RedissonClient redisson() {
|
||||
Config config = new Config();
|
||||
|
||||
if (redisProperties.getSentinel() != null && !redisProperties.getSentinel().getNodes().isEmpty()) {
|
||||
// 哨兵模式
|
||||
SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
|
||||
sentinelServersConfig.setMasterName(redisProperties.getSentinel().getMaster());
|
||||
List<String> sentinelAddress = new ArrayList<>();
|
||||
for (String node : redisProperties.getCluster().getNodes()) {
|
||||
sentinelAddress.add("redis://" + node);
|
||||
}
|
||||
sentinelServersConfig.setSentinelAddresses(sentinelAddress);
|
||||
if (CharSequenceUtil.isNotEmpty(redisProperties.getSentinel().getPassword())) {
|
||||
sentinelServersConfig.setSentinelPassword(redisProperties.getSentinel().getPassword());
|
||||
}
|
||||
} else if (redisProperties.getCluster() != null && !redisProperties.getCluster().getNodes().isEmpty()) {
|
||||
// 集群模式
|
||||
ClusterServersConfig clusterServersConfig = config.useClusterServers();
|
||||
List<String> clusterNodes = new ArrayList<>();
|
||||
for (String node : redisProperties.getCluster().getNodes()) {
|
||||
clusterNodes.add("redis://" + node);
|
||||
}
|
||||
clusterServersConfig.setNodeAddresses(clusterNodes);
|
||||
if (CharSequenceUtil.isNotEmpty(redisProperties.getPassword())) {
|
||||
clusterServersConfig.setPassword(redisProperties.getPassword());
|
||||
}
|
||||
} else {
|
||||
SingleServerConfig singleServerConfig = config.useSingleServer();
|
||||
singleServerConfig.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort());
|
||||
if (CharSequenceUtil.isNotEmpty(redisProperties.getPassword())) {
|
||||
singleServerConfig.setPassword(redisProperties.getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
return Redisson.create(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义缓存key生成策略,默认将使用该策略
|
||||
*/
|
||||
@Bean
|
||||
@Override
|
||||
public KeyGenerator keyGenerator() {
|
||||
return (target, method, params) -> {
|
||||
Map<String, Object> container = new HashMap<>(3);
|
||||
Class<?> targetClassClass = target.getClass();
|
||||
//类地址
|
||||
container.put("class", targetClassClass.toGenericString());
|
||||
//方法名称
|
||||
container.put("methodName", method.getName());
|
||||
//包名称
|
||||
container.put("package", targetClassClass.getPackage());
|
||||
//参数列表
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
container.put(String.valueOf(i), params[i]);
|
||||
}
|
||||
//转为JSON字符串
|
||||
String jsonString = JSON.toJSONString(container);
|
||||
//做SHA256 Hash计算,得到一个SHA256摘要作为Key
|
||||
return DigestUtils.sha256Hex(jsonString);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public CacheErrorHandler errorHandler() {
|
||||
//异常处理,当Redis发生异常时,打印日志,但是程序正常走
|
||||
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
|
||||
return new CacheErrorHandler() {
|
||||
@Override
|
||||
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
|
||||
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
|
||||
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
|
||||
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCacheClearError(RuntimeException e, Cache cache) {
|
||||
log.error("Redis occur handleCacheClearError:", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
311
framework/src/main/java/cn/lili/cache/impl/RedisCache.java
vendored
Normal file
311
framework/src/main/java/cn/lili/cache/impl/RedisCache.java
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
package cn.lili.cache.impl;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* redis 缓存实现
|
||||
*
|
||||
* @author Chopepr
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedisCache implements Cache {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
public RedisCache() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
|
||||
return redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(Object key) {
|
||||
try {
|
||||
return redisTemplate.opsForValue().get(key).toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List multiGet(Collection keys) {
|
||||
return redisTemplate.opsForValue().multiGet(keys);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void multiSet(Map map) {
|
||||
redisTemplate.opsForValue().multiSet(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiDel(Collection keys) {
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object key, Object value) {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object key, Object value, Long exp) {
|
||||
put(key, value, exp, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object key, Object value, Long exp, TimeUnit timeUnit) {
|
||||
redisTemplate.opsForValue().set(key, value, exp, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean remove(Object key) {
|
||||
|
||||
return redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param key 模糊删除key
|
||||
*/
|
||||
@Override
|
||||
public void vagueDel(Object key) {
|
||||
Set<Object> keys = redisTemplate.keys(key + "*");
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
Set keys = redisTemplate.keys("*");
|
||||
redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putHash(Object key, Object hashKey, Object hashValue) {
|
||||
redisTemplate.opsForHash().put(key, hashKey, hashValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAllHash(Object key, Map map) {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHash(Object key, Object hashKey) {
|
||||
return redisTemplate.opsForHash().get(key, hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> getHash(Object key) {
|
||||
return this.redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(Object key) {
|
||||
return this.redisTemplate.opsForValue().get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取符合条件的key
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @return 模糊匹配key
|
||||
*/
|
||||
@Override
|
||||
public List<String> keys(String pattern) {
|
||||
List<String> keys = new ArrayList<>();
|
||||
this.scan(pattern, item -> {
|
||||
//符合条件的key
|
||||
String key = new String(item, StandardCharsets.UTF_8);
|
||||
keys.add(key);
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* scan 实现
|
||||
*
|
||||
* @param pattern 表达式
|
||||
* @param consumer 对迭代到的key进行操作
|
||||
*/
|
||||
private void scan(String pattern, Consumer<byte[]> consumer) {
|
||||
this.redisTemplate.execute((RedisConnection connection) -> {
|
||||
try (Cursor<byte[]> cursor =
|
||||
connection.scan(ScanOptions.scanOptions()
|
||||
.count(Long.MAX_VALUE)
|
||||
.match(pattern).build())) {
|
||||
cursor.forEachRemaining(consumer);
|
||||
return null;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("scan错误", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Long cumulative(Object key, Object value) {
|
||||
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
|
||||
//add 方法对应 PFADD 命令
|
||||
return operations.add(key, value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long counter(Object key) {
|
||||
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
|
||||
|
||||
//add 方法对应 PFADD 命令
|
||||
return operations.size(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List multiCounter(Collection keys) {
|
||||
if (keys == null) {
|
||||
return new ArrayList();
|
||||
}
|
||||
List<Long> result = new ArrayList<>();
|
||||
for (Object key : keys) {
|
||||
result.add(counter(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long mergeCounter(Object... key) {
|
||||
HyperLogLogOperations<Object, Object> operations = redisTemplate.opsForHyperLogLog();
|
||||
//计数器合并累加
|
||||
return operations.union(key[0], key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long incr(String key, long liveTime) {
|
||||
RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
|
||||
Long increment = entityIdCounter.getAndIncrement();
|
||||
//初始设置过期时间
|
||||
if (increment == 0 && liveTime > 0) {
|
||||
entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
return increment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long incr(String key) {
|
||||
RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
|
||||
return entityIdCounter.getAndIncrement();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Sorted Set记录keyword
|
||||
* zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员
|
||||
*
|
||||
* @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据
|
||||
* @param keyword 关键词
|
||||
*/
|
||||
@Override
|
||||
public void incrementScore(String sortedSetName, String keyword) {
|
||||
//指向key名为KEY的zset元素
|
||||
redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementScore(String sortedSetName, String keyword, Integer score) {
|
||||
redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* zrevrange命令, 查询Sorted Set中指定范围的值
|
||||
* 返回的有序集合中,score大的在前面
|
||||
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
|
||||
*
|
||||
* @param sortedSetName sortedSetName
|
||||
* @param start 查询范围开始位置
|
||||
* @param end 查询范围结束位置
|
||||
* @return 符合排序的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer start, Integer end) {
|
||||
return this.redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetName, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* zrevrange命令, 查询Sorted Set中指定范围的值
|
||||
* 返回的有序集合中,score大的在前面
|
||||
* zrevrange方法无需担心用于指定范围的start和end出现越界报错问题
|
||||
*
|
||||
* @param sortedSetName sortedSetName
|
||||
* @param count 获取数量
|
||||
* @return 符合排序的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<ZSetOperations.TypedTuple<Object>> reverseRangeWithScores(String sortedSetName, Integer count) {
|
||||
return this.redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetName, 0, count);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 向Zset里添加成员
|
||||
*
|
||||
* @param key key值
|
||||
* @param score 分数,通常用于排序
|
||||
* @param value 值
|
||||
* @return 增加状态
|
||||
*/
|
||||
@Override
|
||||
public boolean zAdd(String key, long score, String value) {
|
||||
return redisTemplate.opsForZSet().add(key, value, score);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 某key 下 某一分值区间的队列
|
||||
*
|
||||
* @param key 缓存key
|
||||
* @param from 开始时间
|
||||
* @param to 结束时间
|
||||
* @return 数据
|
||||
*/
|
||||
@Override
|
||||
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScore(String key, int from, long to) {
|
||||
Set<ZSetOperations.TypedTuple<Object>> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, from, to);
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Zset队列值
|
||||
*
|
||||
* @param key key值
|
||||
* @param value 删除的集合
|
||||
* @return 删除数量
|
||||
*/
|
||||
@Override
|
||||
public Long zRemove(String key, String... value) {
|
||||
return redisTemplate.opsForZSet().remove(key, value);
|
||||
}
|
||||
}
|
||||
64
framework/src/main/java/cn/lili/cache/limit/annotation/LimitPoint.java
vendored
Normal file
64
framework/src/main/java/cn/lili/cache/limit/annotation/LimitPoint.java
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package cn.lili.cache.limit.annotation;
|
||||
|
||||
|
||||
import cn.lili.cache.limit.enums.LimitTypeEnums;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
*
|
||||
* @author Chopper
|
||||
* @since 2018-02-05
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface LimitPoint {
|
||||
/**
|
||||
* 资源的名字 无实际意义,但是可以用于排除异常
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 资源的key
|
||||
* <p>
|
||||
* 如果下方 limitType 值为自定义,那么全局限流参数来自于此
|
||||
* 如果limitType 为ip,则限流条件 为 key+ip
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String key() default "";
|
||||
|
||||
/**
|
||||
* Key的prefix redis前缀,可选
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* 给定的时间段 单位秒
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int period() default 60;
|
||||
|
||||
/**
|
||||
* 最多的访问限制次数
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int limit() default 10;
|
||||
|
||||
/**
|
||||
* 类型 ip限制 还是自定义key值限制
|
||||
* 建议使用ip,自定义key属于全局限制,ip则是某节点设置,通常情况使用IP
|
||||
*
|
||||
* @return LimitType
|
||||
*/
|
||||
LimitTypeEnums limitType() default LimitTypeEnums.IP;
|
||||
}
|
||||
20
framework/src/main/java/cn/lili/cache/limit/enums/LimitTypeEnums.java
vendored
Normal file
20
framework/src/main/java/cn/lili/cache/limit/enums/LimitTypeEnums.java
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package cn.lili.cache.limit.enums;
|
||||
|
||||
|
||||
/**
|
||||
* redis 限流类型
|
||||
*
|
||||
* @author Chopper
|
||||
* @since 2018年2月02日 下午4:58:52
|
||||
*/
|
||||
|
||||
public enum LimitTypeEnums {
|
||||
/**
|
||||
* 自定义key(即全局限流)
|
||||
*/
|
||||
CUSTOMER,
|
||||
/**
|
||||
* 根据请求者IP(IP限流)
|
||||
*/
|
||||
IP
|
||||
}
|
||||
79
framework/src/main/java/cn/lili/cache/limit/interceptor/LimitInterceptor.java
vendored
Normal file
79
framework/src/main/java/cn/lili/cache/limit/interceptor/LimitInterceptor.java
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package cn.lili.cache.limit.interceptor;
|
||||
|
||||
import cn.lili.cache.limit.annotation.LimitPoint;
|
||||
import cn.lili.cache.limit.enums.LimitTypeEnums;
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.utils.IpUtils;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 流量拦截
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Aspect
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class LimitInterceptor {
|
||||
private RedisTemplate<String, Serializable> redisTemplate;
|
||||
|
||||
private DefaultRedisScript<Long> limitScript;
|
||||
|
||||
@Autowired
|
||||
public void setRedisTemplate(RedisTemplate redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setLimitScript(DefaultRedisScript<Long> limitScript) {
|
||||
this.limitScript = limitScript;
|
||||
}
|
||||
|
||||
@Before("@annotation(limitPointAnnotation)")
|
||||
public void interceptor(LimitPoint limitPointAnnotation) {
|
||||
LimitTypeEnums limitTypeEnums = limitPointAnnotation.limitType();
|
||||
String name = limitPointAnnotation.name();
|
||||
String key;
|
||||
int limitPeriod = limitPointAnnotation.period();
|
||||
int limitCount = limitPointAnnotation.limit();
|
||||
switch (limitTypeEnums) {
|
||||
case CUSTOMER:
|
||||
key = limitPointAnnotation.key();
|
||||
break;
|
||||
default:
|
||||
key = limitPointAnnotation.key() + IpUtils
|
||||
.getIpAddress(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
|
||||
}
|
||||
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitPointAnnotation.prefix(), key));
|
||||
try {
|
||||
Number count = redisTemplate.execute(limitScript, keys, limitCount, limitPeriod);
|
||||
log.info("限制请求{}, 当前请求{},缓存key{}", limitCount, count.intValue(), key);
|
||||
//如果缓存里没有值,或者他的值小于限制频率
|
||||
if (count.intValue() >= limitCount) {
|
||||
throw new ServiceException(ResultCode.LIMIT_ERROR);
|
||||
}
|
||||
}
|
||||
//如果从redis中执行都值判定为空,则这里跳过
|
||||
catch (NullPointerException e) {
|
||||
return;
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(ResultCode.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
42
framework/src/main/java/cn/lili/cache/script/LuaScript.java
vendored
Normal file
42
framework/src/main/java/cn/lili/cache/script/LuaScript.java
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package cn.lili.cache.script;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
|
||||
/**
|
||||
* redis 脚本
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* @since
|
||||
* 2020-06-16 10:40
|
||||
*/
|
||||
@Configuration
|
||||
public class LuaScript {
|
||||
|
||||
/**
|
||||
* 库存扣减脚本
|
||||
*/
|
||||
@Bean
|
||||
public DefaultRedisScript<Boolean> quantityScript() {
|
||||
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/quantity.lua")));
|
||||
redisScript.setResultType(Boolean.class);
|
||||
return redisScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流量限制脚本
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public DefaultRedisScript<Long> limitScript() {
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/limit.lua")));
|
||||
redisScript.setResultType(Long.class);
|
||||
return redisScript;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.lili.common.aop.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 演示站点注解
|
||||
* <p>
|
||||
* PS 此注解需要用户登录之后才可以使用
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:40 上午
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DemoSite {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.lili.common.aop.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 防止重复提交注解
|
||||
*
|
||||
* @author liushuai(liushuai711 @ gmail.com)
|
||||
* @version v4.0
|
||||
* @Description:
|
||||
* @since 2022/1/25 09:17
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface PreventDuplicateSubmissions {
|
||||
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
long expire() default 3;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.lili.common.aop.interceptor;
|
||||
|
||||
import cn.lili.common.aop.annotation.DemoSite;
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.properties.SystemSettingProperties;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 演示站点拦截
|
||||
* DemoInterceptor
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-05-12 17:55
|
||||
*/
|
||||
@Component
|
||||
@Aspect
|
||||
public class DemoInterceptor {
|
||||
|
||||
@Autowired
|
||||
private SystemSettingProperties systemSettingProperties;
|
||||
|
||||
@Before("@annotation(demoSite)")
|
||||
public void doAfter(DemoSite demoSite) {
|
||||
if (Boolean.TRUE.equals(systemSettingProperties.getIsDemoSite())) {
|
||||
throw new ServiceException(ResultCode.DEMO_SITE_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.lili.common.aop.interceptor;
|
||||
|
||||
/**
|
||||
* 防重复提交业务
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2022-01-25 09:20
|
||||
*/
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import cn.lili.common.aop.annotation.PreventDuplicateSubmissions;
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.security.context.UserContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PreventDuplicateSubmissionsInterceptor {
|
||||
|
||||
@Autowired
|
||||
private Cache<String> cache;
|
||||
|
||||
|
||||
@Before("@annotation(preventDuplicateSubmissions)")
|
||||
public void interceptor(PreventDuplicateSubmissions preventDuplicateSubmissions) {
|
||||
|
||||
try {
|
||||
Long count = cache.incr(getParams(), preventDuplicateSubmissions.expire());
|
||||
//如果超过1或者设置的参数,则表示重复提交了
|
||||
if (count.intValue() >= preventDuplicateSubmissions.expire()) {
|
||||
throw new ServiceException(ResultCode.LIMIT_ERROR);
|
||||
}
|
||||
}
|
||||
//如果参数为空,则表示用户未登录,直接略过,不做处理
|
||||
catch (NullPointerException e) {
|
||||
return;
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(ResultCode.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getParams() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
//请求地址
|
||||
return request.getRequestURI() + UserContext.getCurrentUser().getId() + UserContext.getCurrentUser().getUsername();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lili.common.context;
|
||||
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* request / response 获取工具
|
||||
*
|
||||
* @author paulG
|
||||
* @since 2020/10/16
|
||||
**/
|
||||
public class ThreadContextHolder {
|
||||
|
||||
public static HttpServletResponse getHttpResponse() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
assert servletRequestAttributes != null;
|
||||
return servletRequestAttributes.getResponse();
|
||||
}
|
||||
|
||||
public static HttpServletRequest getHttpRequest() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
assert servletRequestAttributes != null;
|
||||
return servletRequestAttributes.getRequest();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lili.common.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 客户端类型
|
||||
*
|
||||
* @author Chopper
|
||||
* @since 2020/12/8 9:46
|
||||
*/
|
||||
|
||||
public enum ClientTypeEnum {
|
||||
|
||||
/**
|
||||
* "移动端"
|
||||
*/
|
||||
H5("移动端"),
|
||||
/**
|
||||
* "PC端"
|
||||
*/
|
||||
PC("PC端"),
|
||||
/**
|
||||
* "小程序端"
|
||||
*/
|
||||
WECHAT_MP("小程序端"),
|
||||
/**
|
||||
* "移动应用端"
|
||||
*/
|
||||
APP("移动应用端"),
|
||||
/**
|
||||
* "未知"
|
||||
*/
|
||||
UNKNOWN("未知");
|
||||
|
||||
private final String clientName;
|
||||
|
||||
ClientTypeEnum(String des) {
|
||||
this.clientName = des;
|
||||
}
|
||||
|
||||
public String clientName() {
|
||||
return this.clientName;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return this.name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.lili.common.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 促销分类枚举
|
||||
*
|
||||
* @author Chopper
|
||||
* @since 2021/2/1 19:32
|
||||
*/
|
||||
public enum PromotionTypeEnum {
|
||||
/**
|
||||
* 促销枚举
|
||||
*/
|
||||
PINTUAN("拼团"),
|
||||
SECKILL("秒杀"),
|
||||
COUPON("优惠券"),
|
||||
PLATFORM_COUPON("平台优惠券"),
|
||||
FULL_DISCOUNT("满减"),
|
||||
POINTS_GOODS("积分商品"),
|
||||
KANJIA("砍价"),
|
||||
COUPON_ACTIVITY("优惠券活动");
|
||||
|
||||
/**
|
||||
* 有促销库存的活动类型
|
||||
*/
|
||||
static final PromotionTypeEnum[] haveStockPromotion = new PromotionTypeEnum[]{PINTUAN, SECKILL, KANJIA, POINTS_GOODS};
|
||||
|
||||
private final String description;
|
||||
|
||||
PromotionTypeEnum(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否拥有库存
|
||||
*/
|
||||
public static boolean haveStock(String promotionType) {
|
||||
for (PromotionTypeEnum promotionTypeEnum : haveStockPromotion) {
|
||||
if (promotionTypeEnum.name().equals(promotionType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
||||
488
framework/src/main/java/cn/lili/common/enums/ResultCode.java
Normal file
488
framework/src/main/java/cn/lili/common/enums/ResultCode.java
Normal file
@@ -0,0 +1,488 @@
|
||||
package cn.lili.common.enums;
|
||||
|
||||
/**
|
||||
* 返回状态码
|
||||
* 第一位 1:商品;2:用户;3:交易,4:促销,5:店铺,6:页面,7:设置,8:其他
|
||||
*
|
||||
* @author Chopper
|
||||
* @since 2020/4/8 1:36 下午
|
||||
*/
|
||||
public enum ResultCode {
|
||||
|
||||
/**
|
||||
* 成功状态码
|
||||
*/
|
||||
SUCCESS(200, "成功"),
|
||||
|
||||
/**
|
||||
* 失败返回码
|
||||
*/
|
||||
ERROR(400, "服务器繁忙,请稍后重试"),
|
||||
|
||||
/**
|
||||
* 失败返回码
|
||||
*/
|
||||
DEMO_SITE_EXCEPTION(4001, "演示站点禁止使用"),
|
||||
/**
|
||||
* 参数异常
|
||||
*/
|
||||
PARAMS_ERROR(4002, "参数异常"),
|
||||
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
WECHAT_CONNECT_NOT_EXIST(1001, "微信联合登录未配置"),
|
||||
VERIFICATION_EXIST(1002, "验证码服务异常"),
|
||||
LIMIT_ERROR(1003, "访问过于频繁,请稍后再试"),
|
||||
ILLEGAL_REQUEST_ERROR(1004, "非法请求,请重新刷新页面操作"),
|
||||
IMAGE_FILE_EXT_ERROR(1005, "不支持图片格式"),
|
||||
FILE_TYPE_NOT_SUPPORT(1010, "不支持上传的文件类型!"),
|
||||
PLATFORM_NOT_SUPPORTED_IM(1006, "平台未开启IM"),
|
||||
STORE_NOT_SUPPORTED_IM(1007, "店铺未开启IM"),
|
||||
/**
|
||||
* 分类
|
||||
*/
|
||||
CATEGORY_NOT_EXIST(10001, "商品分类不存在"),
|
||||
CATEGORY_NAME_IS_EXIST(10002, "该分类名称已存在"),
|
||||
CATEGORY_PARENT_NOT_EXIST(10003, "该分类名称已存在"),
|
||||
CATEGORY_BEYOND_THREE(10004, "最多为三级分类,添加失败"),
|
||||
CATEGORY_HAS_CHILDREN(10005, "此类别下存在子类别不能删除"),
|
||||
CATEGORY_HAS_GOODS(10006, "此类别下存在商品不能删除"),
|
||||
CATEGORY_SAVE_ERROR(10007, "此类别下存在商品不能删除"),
|
||||
CATEGORY_PARAMETER_NOT_EXIST(10012, "分类绑定参数组不存在"),
|
||||
CATEGORY_PARAMETER_SAVE_ERROR(10008, "分类绑定参数组添加失败"),
|
||||
CATEGORY_PARAMETER_UPDATE_ERROR(10009, "分类绑定参数组添加失败"),
|
||||
CATEGORY_DELETE_FLAG_ERROR(10010, "子类状态不能与父类不一致!"),
|
||||
CATEGORY_COMMISSION_RATE_ERROR(10011, "分类的佣金不正确!"),
|
||||
|
||||
/**
|
||||
* 商品
|
||||
*/
|
||||
GOODS_ERROR(11001, "商品异常,请稍后重试"),
|
||||
GOODS_NOT_EXIST(11001, "商品已下架"),
|
||||
GOODS_NAME_ERROR(11002, "商品名称不正确,名称应为2-50字符"),
|
||||
GOODS_UNDER_ERROR(11003, "商品下架失败"),
|
||||
GOODS_UPPER_ERROR(11004, "商品上架失败"),
|
||||
GOODS_AUTH_ERROR(11005, "商品审核失败"),
|
||||
POINT_GOODS_ERROR(11006, "积分商品业务异常,请稍后重试"),
|
||||
POINT_GOODS_NOT_EXIST(11020, "积分商品不存在"),
|
||||
POINT_GOODS_CATEGORY_EXIST(11021, "当前积分商品分类已存在"),
|
||||
GOODS_SKU_SN_ERROR(11007, "商品SKU货号不能为空"),
|
||||
GOODS_SKU_PRICE_ERROR(11008, "商品SKU价格不能小于等于0"),
|
||||
GOODS_SKU_COST_ERROR(11009, "商品SKU成本价不能小于等于0"),
|
||||
GOODS_SKU_WEIGHT_ERROR(11010, "商品重量不能为负数"),
|
||||
GOODS_SKU_QUANTITY_ERROR(11011, "商品库存数量不能为负数"),
|
||||
GOODS_SKU_QUANTITY_NOT_ENOUGH(11011, "商品库存不足"),
|
||||
MUST_HAVE_GOODS_SKU(11012, "规格必须要有一个!"),
|
||||
GOODS_PARAMS_ERROR(11013, "商品参数错误,刷新后重试"),
|
||||
PHYSICAL_GOODS_NEED_TEMP(11014, "实物商品需选择配送模板"),
|
||||
VIRTUAL_GOODS_NOT_NEED_TEMP(11015, "实物商品需选择配送模板"),
|
||||
GOODS_NOT_EXIST_STORE(11017, "当前用户无权操作此商品"),
|
||||
GOODS_TYPE_ERROR(11016, "需选择商品类型"),
|
||||
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
PARAMETER_SAVE_ERROR(12001, "参数添加失败"),
|
||||
PARAMETER_UPDATE_ERROR(12002, "参数编辑失败"),
|
||||
|
||||
/**
|
||||
* 规格
|
||||
*/
|
||||
SPEC_SAVE_ERROR(13001, "规格修改失败"),
|
||||
SPEC_UPDATE_ERROR(13002, "规格修改失败"),
|
||||
SPEC_DELETE_ERROR(13003, "分类已经绑定此规格,请先解除关联"),
|
||||
|
||||
/**
|
||||
* 品牌
|
||||
*/
|
||||
BRAND_SAVE_ERROR(14001, "品牌添加失败"),
|
||||
BRAND_UPDATE_ERROR(14002, "品牌修改失败"),
|
||||
BRAND_DISABLE_ERROR(14003, "品牌禁用失败"),
|
||||
BRAND_DELETE_ERROR(14004, "品牌删除失败"),
|
||||
BRAND_NAME_EXIST_ERROR(20002, "品牌名称重复!"),
|
||||
BRAND_USE_DISABLE_ERROR(20003, "分类已经绑定品牌,请先解除关联"),
|
||||
BRAND_BIND_GOODS_ERROR(20005, "品牌已经绑定商品,请先解除关联"),
|
||||
BRAND_NOT_EXIST(20004, "品牌不存在"),
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
USER_EDIT_SUCCESS(20001, "用户修改成功"),
|
||||
USER_NOT_EXIST(20002, "用户不存在"),
|
||||
USER_NOT_LOGIN(20003, "用户未登录"),
|
||||
USER_AUTH_EXPIRED(20004, "用户已退出,请重新登录"),
|
||||
USER_AUTHORITY_ERROR(20005, "权限不足"),
|
||||
USER_CONNECT_LOGIN_ERROR(20006, "未找到登录信息"),
|
||||
USER_EXIST(20008, "该用户名或手机号已被注册"),
|
||||
USER_PHONE_NOT_EXIST(20009, "手机号不存在"),
|
||||
USER_PASSWORD_ERROR(20010, "密码不正确"),
|
||||
USER_NOT_PHONE(20011, "非当前用户的手机号"),
|
||||
USER_CONNECT_ERROR(20012, "联合第三方登录,授权信息错误"),
|
||||
USER_RECEIPT_REPEAT_ERROR(20013, "会员发票信息重复"),
|
||||
USER_RECEIPT_NOT_EXIST(20014, "会员发票信息不存在"),
|
||||
USER_EDIT_ERROR(20015, "用户修改失败"),
|
||||
USER_OLD_PASSWORD_ERROR(20016, "旧密码不正确"),
|
||||
USER_COLLECTION_EXIST(20017, "无法重复收藏"),
|
||||
USER_GRADE_IS_DEFAULT(20018, "会员等级为默认会员等级"),
|
||||
USER_NOT_BINDING(20020, "未绑定用户"),
|
||||
USER_AUTO_REGISTER_ERROR(20021, "自动注册失败,请稍后重试"),
|
||||
USER_OVERDUE_CONNECT_ERROR(20022, "授权信息已过期,请重新授权/登录"),
|
||||
USER_CONNECT_BANDING_ERROR(20023, "当前联合登陆方式,已绑定其他账号,需进行解绑操作"),
|
||||
USER_CONNECT_NOT_EXIST_ERROR(20024, "暂无联合登陆信息,无法实现一键注册功能,请点击第三方登录进行授权"),
|
||||
USER_POINTS_ERROR(20024, "用户积分不足"),
|
||||
|
||||
/**
|
||||
* 权限
|
||||
*/
|
||||
PERMISSION_DEPARTMENT_ROLE_ERROR(21001, "角色已绑定部门,请逐个删除"),
|
||||
PERMISSION_USER_ROLE_ERROR(21002, "角色已绑定管理员,请逐个删除"),
|
||||
PERMISSION_MENU_ROLE_ERROR(21003, "菜单已绑定角色,请先删除或编辑角色"),
|
||||
PERMISSION_DEPARTMENT_DELETE_ERROR(21004, "部门已经绑定管理员,请先删除或编辑管理员"),
|
||||
PERMISSION_BEYOND_TEN(21005, "最多可以设置10个角色"),
|
||||
|
||||
/**
|
||||
* 分销
|
||||
*/
|
||||
DISTRIBUTION_CLOSE(22000, "分销功能关闭"),
|
||||
DISTRIBUTION_NOT_EXIST(22001, "分销员不存在"),
|
||||
DISTRIBUTION_IS_APPLY(22002, "分销员已申请,无需重复提交"),
|
||||
DISTRIBUTION_AUDIT_ERROR(22003, "审核分销员失败"),
|
||||
DISTRIBUTION_RETREAT_ERROR(22004, "分销员清退失败"),
|
||||
DISTRIBUTION_CASH_NOT_EXIST(22005, "分销员提现记录不存在"),
|
||||
DISTRIBUTION_GOODS_DOUBLE(22006, "不能重复添加分销商品"),
|
||||
|
||||
/**
|
||||
* 购物车
|
||||
*/
|
||||
CART_ERROR(30001, "读取结算页的购物车异常"),
|
||||
CART_NUM_ERROR(30010, "购买数量必须大于0"),
|
||||
CART_PINTUAN_NOT_EXIST_ERROR(30002, "拼团活动已关闭,请稍后重试"),
|
||||
CART_PINTUAN_LIMIT_ERROR(30003, "购买数量超过拼团活动限制数量"),
|
||||
SHIPPING_NOT_APPLY(30005, "购物商品不支持当前收货地址配送"),
|
||||
|
||||
/**
|
||||
* 订单
|
||||
*/
|
||||
ORDER_ERROR(31001, "创建订单异常,请稍后重试"),
|
||||
ORDER_NOT_EXIST(31002, "订单不存在"),
|
||||
ORDER_DELIVERED_ERROR(31003, "订单状态错误,无法进行确认收货"),
|
||||
ORDER_UPDATE_PRICE_ERROR(31004, "已支付的订单不能修改金额"),
|
||||
ORDER_LOGISTICS_ERROR(31005, "物流错误"),
|
||||
ORDER_DELIVER_ERROR(31006, "物流错误"),
|
||||
ORDER_NOT_USER(31007, "非当前会员的订单"),
|
||||
ORDER_TAKE_ERROR(31008, "当前订单无法核销"),
|
||||
MEMBER_ADDRESS_NOT_EXIST(31009, "订单无收货地址,请先配置收货地址"),
|
||||
ORDER_DELIVER_NUM_ERROR(31010, "没有待发货的订单"),
|
||||
ORDER_NOT_SUPPORT_DISTRIBUTION(31011, "购物车中包含不支持配送的商品,请重新选择收货地址,或者重新选择商品"),
|
||||
ORDER_CAN_NOT_CANCEL(31012, "当前订单状态不可取消"),
|
||||
ORDER_BATCH_DELIVER_ERROR(31013, "批量发货,文件读取失败"),
|
||||
ORDER_ITEM_NOT_EXIST(31014, "当前订单项不存在!"),
|
||||
POINT_NOT_ENOUGH(31015, "当前会员积分不足购买当前积分商品!"),
|
||||
|
||||
|
||||
/**
|
||||
* 支付
|
||||
*/
|
||||
PAY_UN_WANTED(32000, "当前订单不需要付款,返回订单列表等待系统订单出库即可"),
|
||||
PAY_SUCCESS(32001, "支付成功"),
|
||||
PAY_INCONSISTENT_ERROR(32002, "付款金额和应付金额不一致"),
|
||||
PAY_DOUBLE_ERROR(32003, "订单已支付,不能再次进行支付"),
|
||||
PAY_CASHIER_ERROR(32004, "收银台信息获取错误"),
|
||||
PAY_ERROR(32005, "支付业务异常,请稍后重试"),
|
||||
PAY_BAN(32006, "当前订单不需要付款,请返回订单列表重新操作"),
|
||||
PAY_PARTIAL_ERROR(32007, "该订单已部分支付,请前往订单中心进行支付"),
|
||||
PAY_NOT_SUPPORT(32008, "支付暂不支持"),
|
||||
PAY_CLIENT_TYPE_ERROR(32009, "错误的客户端"),
|
||||
PAY_POINT_ENOUGH(32010, "积分不足,不能兑换"),
|
||||
PAY_NOT_EXIST_ORDER(32011, "支付订单不存在"),
|
||||
CAN_NOT_RECHARGE_WALLET(32012, "不能使用余额进行充值"),
|
||||
|
||||
|
||||
/**
|
||||
* 售后
|
||||
*/
|
||||
AFTER_SALES_NOT_PAY_ERROR(33001, "当前订单未支付,不能申请售后"),
|
||||
AFTER_SALES_CANCEL_ERROR(33002, "当前售后单无法取消"),
|
||||
AFTER_SALES_BAN(33003, "订单状态不允许申请售后,请联系平台或商家"),
|
||||
AFTER_SALES_DOUBLE_ERROR(33004, "售后已审核,无法重复操作"),
|
||||
AFTER_SALES_LOGISTICS_ERROR(33005, "物流公司错误,请重新选择"),
|
||||
AFTER_STATUS_ERROR(33006, "售后状态错误,请刷新页面"),
|
||||
RETURN_MONEY_OFFLINE_BANK_ERROR(33007, "当账号类型为银行转账时,银行信息不能为空"),
|
||||
AFTER_SALES_PRICE_ERROR(33004, "申请退款金额错误"),
|
||||
AFTER_GOODS_NUMBER_ERROR(33008, "申请售后商品数量错误"),
|
||||
|
||||
/**
|
||||
* 投诉
|
||||
*/
|
||||
COMPLAINT_ORDER_ITEM_EMPTY_ERROR(33100, "订单不存在"),
|
||||
COMPLAINT_SKU_EMPTY_ERROR(33101, "商品已下架,如需投诉请联系平台客服"),
|
||||
COMPLAINT_ERROR(33102, "投诉异常,请稍后重试"),
|
||||
COMPLAINT_NOT_EXIT(33103, "当前投诉记录不存在"),
|
||||
COMPLAINT_ARBITRATION_RESULT_ERROR(33104, "结束订单投诉时,仲裁结果不能为空"),
|
||||
COMPLAINT_APPEAL_CONTENT_ERROR(33105, "商家申诉时,申诉内容不能为空"),
|
||||
COMPLAINT_CANCEL_ERROR(33106, "申诉已完成,不需要进行取消申诉操作"),
|
||||
|
||||
|
||||
/**
|
||||
* 余额
|
||||
*/
|
||||
WALLET_NOT_EXIT_ERROR(34000, "钱包不存在,请联系管理员"),
|
||||
WALLET_INSUFFICIENT(34001, "余额不足以支付订单,请充值!"),
|
||||
WALLET_WITHDRAWAL_INSUFFICIENT(34002, "可提现金额不足!"),
|
||||
WALLET_WITHDRAWAL_FROZEN_AMOUNT_INSUFFICIENT(34006, "冻结金额不足,无法处理提现申请请求!"),
|
||||
WALLET_ERROR_INSUFFICIENT(34003, "零钱提现失败!"),
|
||||
WALLET_REMARK_ERROR(34004, "请填写审核备注!"),
|
||||
WALLET_EXIT_ERROR(34000, "钱包已存在,无法重复创建"),
|
||||
WALLET_APPLY_ERROR(34005, "提现申请异常!"),
|
||||
|
||||
/**
|
||||
* 评价
|
||||
*/
|
||||
EVALUATION_DOUBLE_ERROR(35001, "无法重复提交评价"),
|
||||
|
||||
/**
|
||||
* 活动
|
||||
*/
|
||||
PROMOTION_GOODS_NOT_EXIT(40000, "当前促销商品不存在!"),
|
||||
PROMOTION_SAME_ACTIVE_EXIST(40001, "活动时间内已存在同类活动,请选择关闭、删除当前时段的活动"),
|
||||
PROMOTION_START_TIME_ERROR(40002, "活动起始时间不能小于当前时间"),
|
||||
PROMOTION_END_TIME_ERROR(40003, "活动结束时间不能小于当前时间"),
|
||||
PROMOTION_TIME_ERROR(40004, "活动起始时间必须大于结束时间"),
|
||||
PROMOTION_TIME_NOT_EXIST(40011, "活动起始时间和活动结束时间不能为空"),
|
||||
PROMOTION_SAME_ERROR(40005, "当前时间段已存在相同活动!"),
|
||||
PROMOTION_GOODS_ERROR(40006, "请选择要参与活动的商品"),
|
||||
PROMOTION_STATUS_END(40007, "当前活动已停止"),
|
||||
PROMOTION_UPDATE_ERROR(40008, "当前活动已开始/结束,无法编辑!"),
|
||||
PROMOTION_ACTIVITY_GOODS_ERROR(40009, "当前活动已经开始无法添加商品"),
|
||||
PROMOTION_ACTIVITY_ERROR(400010, "当前促销活动不存在"),
|
||||
PROMOTION_LOG_EXIST(40011, "活动已参加,已发重复参加"),
|
||||
|
||||
/**
|
||||
* 优惠券
|
||||
*/
|
||||
COUPON_LIMIT_ERROR(41000, "超出领取限制"),
|
||||
COUPON_EDIT_STATUS_SUCCESS(41001, "修改状态成功!"),
|
||||
COUPON_CANCELLATION_SUCCESS(41002, "会员优惠券作废成功"),
|
||||
COUPON_EXPIRED(41003, "优惠券已使用/已过期,不能使用"),
|
||||
COUPON_EDIT_STATUS_ERROR(41004, "优惠券修改状态失败!"),
|
||||
COUPON_RECEIVE_ERROR(41005, "当前优惠券已经被领取完了,下次要早点来哦"),
|
||||
COUPON_NUM_INSUFFICIENT_ERROR(41006, "优惠券剩余领取数量不足"),
|
||||
COUPON_NOT_EXIST(41007, "当前优惠券不存在"),
|
||||
COUPON_DO_NOT_RECEIVER(41030, "当前优惠券不允许主动领取"),
|
||||
COUPON_ACTIVITY_NOT_EXIST(410022, "当前优惠券活动不存在"),
|
||||
COUPON_SAVE_ERROR(41020, "保存优惠券失败"),
|
||||
COUPON_ACTIVITY_SAVE_ERROR(41023, "保存优惠券活动失败"),
|
||||
COUPON_DELETE_ERROR(41021, "删除优惠券失败"),
|
||||
COUPON_LIMIT_NUM_LESS_THAN_0(41008, "领取限制数量不能为负数"),
|
||||
COUPON_LIMIT_GREATER_THAN_PUBLISH(41009, "领取限制数量超出发行数量"),
|
||||
COUPON_DISCOUNT_ERROR(41010, "优惠券折扣必须小于10且大于0"),
|
||||
COUPON_SCOPE_TYPE_GOODS_ERROR(41011, "当前关联范围类型为指定商品时,商品列表不能为空"),
|
||||
COUPON_SCOPE_TYPE_CATEGORY_ERROR(41012, "当前关联范围类型为部分商品分类时,范围关联的id不能为空"),
|
||||
COUPON_SCOPE_TYPE_STORE_ERROR(41013, "当前关联范围类型为部分店铺分类时,范围关联的id不能为空"),
|
||||
COUPON_SCOPE_ERROR(41014, "指定商品范围关联id无效!"),
|
||||
COUPON_MEMBER_NOT_EXIST(41015, "没有当前会员优惠券"),
|
||||
COUPON_MEMBER_STATUS_ERROR(41016, "当前会员优惠券已过期/作废无法变更状态!"),
|
||||
|
||||
|
||||
/**
|
||||
* 拼团
|
||||
*/
|
||||
PINTUAN_MANUAL_OPEN_SUCCESS(42001, "手动开启拼团活动成功"),
|
||||
PINTUAN_MANUAL_CLOSE_SUCCESS(42002, "手动关闭拼团活动成功"),
|
||||
PINTUAN_ADD_SUCCESS(42003, "添加拼团活动成功"),
|
||||
PINTUAN_EDIT_SUCCESS(42004, "修改拼团活动成功"),
|
||||
PINTUAN_DELETE_SUCCESS(42005, "删除拼团活动成功"),
|
||||
PINTUAN_MANUAL_OPEN_ERROR(42006, "手动开启拼团活动失败"),
|
||||
PINTUAN_MANUAL_CLOSE_ERROR(42007, "手动关闭拼团活动失败"),
|
||||
PINTUAN_ADD_ERROR(42008, "添加拼团活动失败"),
|
||||
PINTUAN_EDIT_ERROR(42009, "修改拼团活动失败"),
|
||||
PINTUAN_EDIT_ERROR_ITS_OPEN(42019, "拼团活动已开启,无法修改拼团活动!"),
|
||||
PINTUAN_DELETE_ERROR(42010, "删除拼团活动失败"),
|
||||
PINTUAN_JOIN_ERROR(42011, "不能参与自己发起的拼团活动!"),
|
||||
PINTUAN_LIMIT_NUM_ERROR(42012, "购买数量超过拼团活动限制数量!"),
|
||||
PINTUAN_NOT_EXIST_ERROR(42013, "当前拼团活动不存在!"),
|
||||
PINTUAN_GOODS_NOT_EXIST_ERROR(42014, "当前拼团商品不存在!"),
|
||||
|
||||
/**
|
||||
* 满额活动
|
||||
*/
|
||||
FULL_DISCOUNT_EDIT_SUCCESS(43001, "修改满优惠活动成功"),
|
||||
FULL_DISCOUNT_EDIT_DELETE(43002, "删除满优惠活动成功"),
|
||||
FULL_DISCOUNT_MODIFY_ERROR(43003, "当前编辑的满优惠活动已经开始或者已经结束,无法修改"),
|
||||
FULL_DISCOUNT_NOT_EXIST_ERROR(43004, "当前要操作的满优惠活动不存在!"),
|
||||
FULL_DISCOUNT_WAY_ERROR(43005, "请选择一种优惠方式!"),
|
||||
FULL_DISCOUNT_GIFT_ERROR(43006, "请选择赠品!"),
|
||||
FULL_DISCOUNT_COUPON_TIME_ERROR(43007, "赠送的优惠券有效时间必须在活动时间之内"),
|
||||
FULL_DISCOUNT_MONEY_ERROR(43008, "请填写满减金额"),
|
||||
FULL_DISCOUNT_MONEY_GREATER_THAN_MINUS(43009, "满减金额不能大于优惠门槛"),
|
||||
FULL_RATE_NUM_ERROR(43010, "请填写打折数值"),
|
||||
|
||||
/**
|
||||
* 直播
|
||||
*/
|
||||
STODIO_GOODS_EXIST_ERROR(44001, "直播商品已存在"),
|
||||
COMMODITY_ERROR(44002, "添加直播商品失败"),
|
||||
|
||||
/**
|
||||
* 秒杀
|
||||
*/
|
||||
SECKILL_NOT_START_ERROR(45000, "今日没有限时抢购活动,请明天再来看看吧。"),
|
||||
SECKILL_NOT_EXIST_ERROR(45001, "当前参与的秒杀活动不存在!"),
|
||||
SECKILL_APPLY_NOT_EXIST_ERROR(45010, "当前参与的秒杀活动不存在!"),
|
||||
SECKILL_UPDATE_ERROR(45002, "当前秒杀活动活动已经开始,无法修改!"),
|
||||
SECKILL_PRICE_ERROR(45003, "活动价格不能大于商品原价"),
|
||||
SECKILL_TIME_ERROR(45004, "时刻参数异常"),
|
||||
SECKILL_DELETE_ERROR(45005, "该秒杀活动活动的状态不能删除"),
|
||||
SECKILL_OPEN_ERROR(45010, "该秒杀活动活动的状态不能删除"),
|
||||
SECKILL_CLOSE_ERROR(45006, "该秒杀活动活动的状态不能关闭"),
|
||||
|
||||
|
||||
/**
|
||||
* 优惠券活动
|
||||
*/
|
||||
COUPON_ACTIVITY_START_TIME_ERROR(46001, "活动时间小于当前时间,不能进行编辑删除操作"),
|
||||
COUPON_ACTIVITY_MEMBER_ERROR(46002, "指定精准发券则必须指定会员,会员不可以为空"),
|
||||
COUPON_ACTIVITY_ITEM_ERROR(46003, "优惠券活动必须指定优惠券,不能为空"),
|
||||
COUPON_ACTIVITY_ITEM_MUST_NUM_ERROR(46004, "优惠券活动最多指定10个优惠券"),
|
||||
COUPON_ACTIVITY_ITEM_NUM_ERROR(46005, "赠券数量必须大于0"),
|
||||
|
||||
/**
|
||||
* 其他促销
|
||||
*/
|
||||
MEMBER_SIGN_REPEAT(47001, "请勿重复签到"),
|
||||
POINT_GOODS_ACTIVE_STOCK_ERROR(47002, "活动库存数量不能高于商品库存"),
|
||||
POINT_GOODS_ACTIVE_STOCK_INSUFFICIENT(47003, "积分商品库存不足"),
|
||||
|
||||
/**
|
||||
* 砍价活动
|
||||
*/
|
||||
KANJIA_GOODS_ACTIVE_STOCK_ERROR(48001, "活动库存数量不能高于商品库存"),
|
||||
KANJIA_GOODS_ACTIVE_PRICE_ERROR(48002, "最低购买金额不能高于商品金额"),
|
||||
KANJIA_GOODS_ACTIVE_HIGHEST_PRICE_ERROR(48003, "最高砍价金额不能为0且不能超过商品金额"),
|
||||
KANJIA_GOODS_ACTIVE_LOWEST_PRICE_ERROR(48004, "最低砍价金额不能为0且不能超过商品金额"),
|
||||
KANJIA_GOODS_ACTIVE_HIGHEST_LOWEST_PRICE_ERROR(48005, "最低砍价金额不能高于最高砍价金额"),
|
||||
KANJIA_GOODS_ACTIVE_SETTLEMENT_PRICE_ERROR(48006, "结算金额不能高于商品金额"),
|
||||
KANJIA_GOODS_DELETE_ERROR(48007, "删除砍价商品异常"),
|
||||
KANJIA_GOODS_UPDATE_ERROR(48012, "更新砍价商品异常"),
|
||||
KANJIA_ACTIVITY_NOT_FOUND_ERROR(48008, "砍价记录不存在"),
|
||||
KANJIA_ACTIVITY_LOG_MEMBER_ERROR(48009, "当前会员已经帮砍"),
|
||||
KANJIA_ACTIVITY_MEMBER_ERROR(48010, "当前会员已经发起此砍价商品活动"),
|
||||
KANJIA_ACTIVITY_NOT_PASS_ERROR(48011, "当前砍价未满足条件,不能进行购买"),
|
||||
KANJIA_NUM_BUY_ERROR(48012, "砍价商品购买数量不正确"),
|
||||
/**
|
||||
* 店铺
|
||||
*/
|
||||
|
||||
STORE_NOT_EXIST(50001, "此店铺不存在"),
|
||||
STORE_NAME_EXIST_ERROR(50002, "店铺名称已存在!"),
|
||||
STORE_APPLY_DOUBLE_ERROR(50003, "已有店铺,无需重复申请!"),
|
||||
STORE_NOT_OPEN(50004, "该会员未开通店铺"),
|
||||
STORE_NOT_LOGIN_ERROR(50005, "未登录店铺"),
|
||||
STORE_CLOSE_ERROR(50006, "店铺关闭,请联系管理员"),
|
||||
FREIGHT_TEMPLATE_NOT_EXIST(50010, "当前模版不存在"),
|
||||
|
||||
/**
|
||||
* 结算单
|
||||
*/
|
||||
BILL_CHECK_ERROR(51001, "只有已出账结算单可以核对"),
|
||||
BILL_COMPLETE_ERROR(51002, "只有已审核结算单可以支付"),
|
||||
|
||||
/**
|
||||
* 文章
|
||||
*/
|
||||
ARTICLE_CATEGORY_NAME_EXIST(60001, "文章分类名称已存在"),
|
||||
ARTICLE_CATEGORY_PARENT_NOT_EXIST(60002, "文章分类父分类不存在"),
|
||||
ARTICLE_CATEGORY_BEYOND_TWO(60003, "最多为二级分类,操作失败"),
|
||||
ARTICLE_CATEGORY_DELETE_ERROR(60004, "该文章分类下存在子分类,不能删除"),
|
||||
ARTICLE_CATEGORY_HAS_ARTICLE(60005, "该文章分类下存在文章,不能删除"),
|
||||
ARTICLE_CATEGORY_NO_DELETION(60007, "默认文章分类不能进行删除"),
|
||||
ARTICLE_NO_DELETION(60008, "默认文章不能进行删除"),
|
||||
|
||||
|
||||
/**
|
||||
* 页面
|
||||
*/
|
||||
PAGE_NOT_EXIST(61001, "页面不存在"),
|
||||
PAGE_OPEN_DELETE_ERROR(61002, "当前页面为开启状态,无法删除"),
|
||||
PAGE_DELETE_ERROR(61003, "当前页面为唯一页面,无法删除"),
|
||||
PAGE_RELEASE_ERROR(61004, "页面已发布,无需重复提交"),
|
||||
|
||||
/**
|
||||
* 设置
|
||||
*/
|
||||
SETTING_NOT_TO_SET(70001, "该参数不需要设置"),
|
||||
ALIPAY_NOT_SETTING(70002, "支付宝支付未配置"),
|
||||
ALIPAY_EXCEPTION(70003, "支付宝支付错误,请稍后重试"),
|
||||
ALIPAY_PARAMS_EXCEPTION(70004, "支付宝参数异常"),
|
||||
LOGISTICS_NOT_SETTING(70005, "您还未配置快递查询"),
|
||||
ORDER_SETTING_ERROR(70006, "系统订单配置异常"),
|
||||
ALI_SMS_SETTING_ERROR(70007, "您还未配置阿里云短信"),
|
||||
SMS_SIGN_EXIST_ERROR(70008, "短信签名已存在"),
|
||||
|
||||
/**
|
||||
* 站内信
|
||||
*/
|
||||
NOTICE_NOT_EXIST(80001, "当前消息模板不存在"),
|
||||
NOTICE_ERROR(80002, "修改站内信异常,请稍后重试"),
|
||||
NOTICE_SEND_ERROR(80003, "发送站内信异常,请检查系统日志"),
|
||||
|
||||
|
||||
/**
|
||||
* OSS
|
||||
*/
|
||||
OSS_NOT_EXIST(80101, "OSS未配置"),
|
||||
OSS_EXCEPTION_ERROR(80102, "文件上传失败,请稍后重试"),
|
||||
OSS_DELETE_ERROR(80103, "图片删除失败"),
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
VERIFICATION_SEND_SUCCESS(80201, "短信验证码,发送成功"),
|
||||
VERIFICATION_ERROR(80202, "验证失败"),
|
||||
VERIFICATION_CODE_INVALID(80204, "验证码已失效,请重新校验"),
|
||||
VERIFICATION_SMS_CHECKED_ERROR(80210, "短信验证码错误,请重新校验"),
|
||||
|
||||
/**
|
||||
* 微信相关异常
|
||||
*/
|
||||
WECHAT_CONNECT_NOT_SETTING(80300, "微信联合登陆信息未配置"),
|
||||
WECHAT_PAYMENT_NOT_SETTING(80301, "微信支付信息未配置"),
|
||||
WECHAT_QRCODE_ERROR(80302, "微信二维码生成异常"),
|
||||
WECHAT_MP_MESSAGE_ERROR(80303, "微信小程序小消息订阅异常"),
|
||||
WECHAT_JSAPI_SIGN_ERROR(80304, "微信JsApi签名异常"),
|
||||
WECHAT_CERT_ERROR(80305, "证书获取失败"),
|
||||
WECHAT_MP_MESSAGE_TMPL_ERROR(80306, "未能获取到微信模版消息id"),
|
||||
WECHAT_ERROR(80307, "微信接口异常"),
|
||||
APP_VERSION_EXIST(80307, "APP版本已存在"),
|
||||
|
||||
/**
|
||||
* 其他
|
||||
*/
|
||||
CUSTOM_WORDS_EXIST_ERROR(90000, "当前自定义分词已存在!"),
|
||||
CUSTOM_WORDS_NOT_EXIST_ERROR(90001, "当前自定义分词不存在!"),
|
||||
CUSTOM_WORDS_SECRET_KEY_ERROR(90002, "秘钥验证失败!"),
|
||||
CONNECT_NOT_EXIST(90000, "登录方式不存在!"),
|
||||
ELASTICSEARCH_INDEX_INIT_ERROR(90003, "索引初始化失败!"),
|
||||
PURCHASE_ORDER_DEADLINE_ERROR(90004, "供求单,已超过报名截止时间"),
|
||||
INDEX_BUILDING(90005, "索引正在生成");
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
|
||||
ResultCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public String message() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
}
|
||||
134
framework/src/main/java/cn/lili/common/enums/ResultUtil.java
Normal file
134
framework/src/main/java/cn/lili/common/enums/ResultUtil.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package cn.lili.common.enums;
|
||||
|
||||
|
||||
import cn.lili.common.vo.ResultMessage;
|
||||
|
||||
/**
|
||||
* 返回结果工具类
|
||||
*
|
||||
* @author lili
|
||||
*/
|
||||
public class ResultUtil<T> {
|
||||
|
||||
/**
|
||||
* 抽象类,存放结果
|
||||
*/
|
||||
private final ResultMessage<T> resultMessage;
|
||||
/**
|
||||
* 正常响应
|
||||
*/
|
||||
private static final Integer SUCCESS = 200;
|
||||
|
||||
|
||||
/**
|
||||
* 构造话方法,给响应结果默认值
|
||||
*/
|
||||
public ResultUtil() {
|
||||
resultMessage = new ResultMessage<>();
|
||||
resultMessage.setSuccess(true);
|
||||
resultMessage.setMessage("success");
|
||||
resultMessage.setCode(SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据
|
||||
*
|
||||
* @param t 范型
|
||||
* @return 消息
|
||||
*/
|
||||
public ResultMessage<T> setData(T t) {
|
||||
this.resultMessage.setResult(t);
|
||||
return this.resultMessage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回成功消息
|
||||
*
|
||||
* @param resultCode 返回码
|
||||
* @return 返回成功消息
|
||||
*/
|
||||
public ResultMessage<T> setSuccessMsg(ResultCode resultCode) {
|
||||
this.resultMessage.setSuccess(true);
|
||||
this.resultMessage.setMessage(resultCode.message());
|
||||
this.resultMessage.setCode(resultCode.code());
|
||||
return this.resultMessage;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽象静态方法,返回结果集
|
||||
* @param t 范型
|
||||
* @param <T> 范型
|
||||
* @return 消息
|
||||
*/
|
||||
public static <T> ResultMessage<T> data(T t) {
|
||||
return new ResultUtil<T>().setData(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功
|
||||
*
|
||||
* @param resultCode 返回状态码
|
||||
* @return 消息
|
||||
*/
|
||||
public static <T> ResultMessage<T> success(ResultCode resultCode) {
|
||||
return new ResultUtil<T>().setSuccessMsg(resultCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功
|
||||
* @return 消息
|
||||
*/
|
||||
public static <T> ResultMessage<T> success() {
|
||||
return new ResultUtil<T>().setSuccessMsg(ResultCode.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回失败
|
||||
*
|
||||
* @param resultCode 返回状态码
|
||||
* @return 消息
|
||||
*/
|
||||
public static <T> ResultMessage<T> error(ResultCode resultCode) {
|
||||
return new ResultUtil<T>().setErrorMsg(resultCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回失败
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param msg 返回消息
|
||||
* @return 消息
|
||||
*/
|
||||
public static <T> ResultMessage<T> error(Integer code, String msg) {
|
||||
return new ResultUtil<T>().setErrorMsg(code, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器异常 追加状态码
|
||||
* @param resultCode 返回码
|
||||
* @return 消息
|
||||
*/
|
||||
public ResultMessage<T> setErrorMsg(ResultCode resultCode) {
|
||||
this.resultMessage.setSuccess(false);
|
||||
this.resultMessage.setMessage(resultCode.message());
|
||||
this.resultMessage.setCode(resultCode.code());
|
||||
return this.resultMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器异常 追加状态码
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param msg 返回消息
|
||||
* @return 消息
|
||||
*/
|
||||
public ResultMessage<T> setErrorMsg(Integer code, String msg) {
|
||||
this.resultMessage.setSuccess(false);
|
||||
this.resultMessage.setMessage(msg);
|
||||
this.resultMessage.setCode(code);
|
||||
return this.resultMessage;
|
||||
}
|
||||
|
||||
}
|
||||
25
framework/src/main/java/cn/lili/common/enums/SwitchEnum.java
Normal file
25
framework/src/main/java/cn/lili/common/enums/SwitchEnum.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package cn.lili.common.enums;
|
||||
|
||||
/**
|
||||
* 开关枚举
|
||||
* @author Chopper
|
||||
*/
|
||||
public enum SwitchEnum {
|
||||
|
||||
/**
|
||||
* 开关
|
||||
*/
|
||||
OPEN("开启"), CLOSE("关闭");
|
||||
|
||||
private String description;
|
||||
|
||||
SwitchEnum(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package cn.lili.common.exception;
|
||||
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.enums.ResultUtil;
|
||||
import cn.lili.common.vo.ResultMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 全局异常异常处理
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalControllerExceptionHandler {
|
||||
|
||||
/**
|
||||
* 如果超过长度,则前后段交互体验不佳,使用默认错误消息
|
||||
*/
|
||||
static Integer MAX_LENGTH = 200;
|
||||
|
||||
/**
|
||||
* 自定义异常
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(ServiceException.class)
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
|
||||
public ResultMessage<Object> handleServiceException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
|
||||
|
||||
|
||||
//如果是自定义异常,则获取异常,返回自定义错误消息
|
||||
if (e instanceof ServiceException) {
|
||||
ServiceException serviceException = ((ServiceException) e);
|
||||
ResultCode resultCode = serviceException.getResultCode();
|
||||
|
||||
Integer code = null;
|
||||
String message = null;
|
||||
|
||||
if (resultCode != null) {
|
||||
code = resultCode.code();
|
||||
message = resultCode.message();
|
||||
}
|
||||
//如果有扩展消息,则输出异常中,跟随补充异常
|
||||
if (!serviceException.getMsg().equals(ServiceException.DEFAULT_MESSAGE)) {
|
||||
message += ":" + serviceException.getMsg();
|
||||
}
|
||||
|
||||
log.error("全局异常[ServiceException]:{}-{}", serviceException.getResultCode().code(), serviceException.getResultCode().message(), e);
|
||||
return ResultUtil.error(code, message);
|
||||
|
||||
} else {
|
||||
|
||||
log.error("全局异常[ServiceException]:", e);
|
||||
}
|
||||
|
||||
//默认错误消息
|
||||
String errorMsg = "服务器异常,请稍后重试";
|
||||
if (e != null && e.getMessage() != null && e.getMessage().length() < MAX_LENGTH) {
|
||||
errorMsg = e.getMessage();
|
||||
}
|
||||
return ResultUtil.error(ResultCode.ERROR.code(), errorMsg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
|
||||
public ResultMessage<Object> runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
|
||||
|
||||
log.error("全局异常[RuntimeException]:", e);
|
||||
|
||||
return ResultUtil.error(ResultCode.ERROR);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 通用的接口映射异常处理方
|
||||
// */
|
||||
// @Override
|
||||
// protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
// if (ex instanceof MethodArgumentNotValidException) {
|
||||
// MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
|
||||
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg(exception.getBindingResult().getAllErrors().get(0).getDefaultMessage()), status);
|
||||
// }
|
||||
// if (ex instanceof MethodArgumentTypeMismatchException) {
|
||||
// MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
|
||||
// logger.error("参数转换失败,方法:" + exception.getParameter().getMethod().getName() + ",参数:" + exception.getName()
|
||||
// + ",信息:" + exception.getLocalizedMessage());
|
||||
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("参数转换失败"), status);
|
||||
// }
|
||||
// ex.printStackTrace();
|
||||
// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("未知异常,请联系管理员"), status);
|
||||
// }
|
||||
|
||||
/**
|
||||
* bean校验未通过异常
|
||||
*
|
||||
* @see javax.validation.Valid
|
||||
* @see org.springframework.validation.Validator
|
||||
* @see org.springframework.validation.DataBinder
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ResponseBody
|
||||
public ResultMessage<Object> validExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
|
||||
|
||||
BindException exception = (BindException) e;
|
||||
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
|
||||
for (FieldError error : fieldErrors) {
|
||||
return ResultUtil.error(ResultCode.PARAMS_ERROR.code(), error.getDefaultMessage());
|
||||
}
|
||||
return ResultUtil.error(ResultCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* bean校验未通过异常
|
||||
*
|
||||
* @see javax.validation.Valid
|
||||
* @see org.springframework.validation.Validator
|
||||
* @see org.springframework.validation.DataBinder
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ResponseBody
|
||||
public ResultMessage<Object> constraintViolationExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
|
||||
ConstraintViolationException exception = (ConstraintViolationException) e;
|
||||
return ResultUtil.error(ResultCode.PARAMS_ERROR.code(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.lili.common.exception;
|
||||
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 全局业务异常类
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 3447728300174142127L;
|
||||
|
||||
public static final String DEFAULT_MESSAGE = "网络错误,请稍后重试!";
|
||||
|
||||
/**
|
||||
* 异常消息
|
||||
*/
|
||||
private String msg = DEFAULT_MESSAGE;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private ResultCode resultCode;
|
||||
|
||||
public ServiceException(String msg) {
|
||||
this.resultCode = ResultCode.ERROR;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public ServiceException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ServiceException(ResultCode resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public ServiceException(ResultCode resultCode, String message) {
|
||||
this.resultCode = resultCode;
|
||||
this.msg = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* API地址配置
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.api")
|
||||
public class ApiProperties {
|
||||
|
||||
|
||||
/**
|
||||
* 买家api
|
||||
*/
|
||||
private String buyer;
|
||||
|
||||
/**
|
||||
* 管理端域名
|
||||
*/
|
||||
private String store;
|
||||
|
||||
/**
|
||||
* 管理端域名
|
||||
*/
|
||||
private String manager;
|
||||
|
||||
/**
|
||||
* 管理端域名
|
||||
*/
|
||||
private String common;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 域名配置
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.domain")
|
||||
public class DomainProperties {
|
||||
|
||||
/**
|
||||
* 买家PC端域名
|
||||
*/
|
||||
private String pc;
|
||||
|
||||
/**
|
||||
* 买家WAP端域名
|
||||
*/
|
||||
private String wap;
|
||||
|
||||
/**
|
||||
* Store域名
|
||||
*/
|
||||
private String store;
|
||||
|
||||
/**
|
||||
* 管理端域名
|
||||
*/
|
||||
private String admin;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 忽略授权设定
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ignored")
|
||||
public class IgnoredUrlsProperties {
|
||||
|
||||
private List<String> urls = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* token过期配置
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.jwt-setting")
|
||||
public class JWTTokenProperties {
|
||||
|
||||
|
||||
/**
|
||||
* token默认过期时间
|
||||
*/
|
||||
private long tokenExpireTime = 60;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author paulG
|
||||
* @since 2020/10/30
|
||||
**/
|
||||
@Component
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@ConfigurationProperties(prefix = "lili.data.rocketmq")
|
||||
public class RocketmqCustomProperties {
|
||||
|
||||
private String promotionTopic;
|
||||
|
||||
private String promotionGroup;
|
||||
|
||||
private String orderTopic;
|
||||
|
||||
private String orderGroup;
|
||||
|
||||
private String msgExtTopic;
|
||||
|
||||
private String msgExtGroup;
|
||||
|
||||
private String goodsTopic;
|
||||
|
||||
private String goodsGroup;
|
||||
|
||||
private String topicUser;
|
||||
|
||||
private String memberTopic;
|
||||
|
||||
private String memberGroup;
|
||||
|
||||
private String otherTopic;
|
||||
|
||||
private String otherGroup;
|
||||
|
||||
private String noticeTopic;
|
||||
|
||||
private String noticeGroup;
|
||||
|
||||
private String noticeSendTopic;
|
||||
|
||||
private String noticeSendGroup;
|
||||
|
||||
private String storeTopic;
|
||||
|
||||
private String storeGroup;
|
||||
|
||||
private String afterSaleTopic;
|
||||
|
||||
private String afterSaleGroup;
|
||||
|
||||
private String broadcastTopic;
|
||||
|
||||
private String broadcastGroup;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 短信模版设置
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.sms")
|
||||
public class SmsTemplateProperties {
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
private String lOGIN = "SMS_205755300";
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
private String REGISTER = "SMS_205755298";
|
||||
/**
|
||||
* 找回密码
|
||||
*/
|
||||
private String FIND_USER = "SMS_205755301";
|
||||
/**
|
||||
* 设置密码
|
||||
*/
|
||||
private String UPDATE_PASSWORD = "SMS_205755297";
|
||||
/**
|
||||
* 设置支付密码
|
||||
*/
|
||||
private String WALLET_PASSWORD = "SMS_205755297";
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 在线人数统计
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2021/2/21 10:19
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.statistics")
|
||||
public class StatisticsProperties {
|
||||
|
||||
/**
|
||||
* 在线人数统计 X 小时
|
||||
*/
|
||||
private Integer onlineMember = 48;
|
||||
|
||||
/**
|
||||
* 当前在线人数 刷新时间间隔
|
||||
*/
|
||||
private Integer currentOnlineUpdate = 600;
|
||||
|
||||
public Integer getOnlineMember() {
|
||||
if (onlineMember == null) {
|
||||
return 48;
|
||||
}
|
||||
return onlineMember;
|
||||
}
|
||||
|
||||
public Integer getCurrentOnlineUpdate() {
|
||||
if (currentOnlineUpdate == null) {
|
||||
return 600;
|
||||
}
|
||||
return currentOnlineUpdate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 系统设置
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.system")
|
||||
public class SystemSettingProperties {
|
||||
|
||||
|
||||
/**
|
||||
* 是否是演示站点
|
||||
*/
|
||||
private Boolean isDemoSite = false;
|
||||
|
||||
/**
|
||||
* 测试模式
|
||||
* 验证码短信为6个1
|
||||
*/
|
||||
private Boolean isTestModel = false;
|
||||
|
||||
/**
|
||||
* 脱敏级别:
|
||||
* 0:不做脱敏处理
|
||||
* 1:管理端用户手机号等信息脱敏
|
||||
* 2:商家端信息脱敏(为2时,表示管理端,商家端同时脱敏)
|
||||
* <p>
|
||||
* PS:
|
||||
*/
|
||||
private Integer sensitiveLevel = 0;
|
||||
|
||||
|
||||
public Boolean getDemoSite() {
|
||||
if (isDemoSite == null) {
|
||||
return false;
|
||||
}
|
||||
return isDemoSite;
|
||||
}
|
||||
|
||||
public Boolean getTestModel() {
|
||||
if (isTestModel == null) {
|
||||
return false;
|
||||
}
|
||||
return isTestModel;
|
||||
}
|
||||
|
||||
public Integer getSensitiveLevel() {
|
||||
if (sensitiveLevel == null) {
|
||||
return 0;
|
||||
}
|
||||
return sensitiveLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 线程配置
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.thread")
|
||||
public class ThreadProperties {
|
||||
|
||||
|
||||
/**
|
||||
* 核心线程数
|
||||
*/
|
||||
private Integer corePoolSize = 10;
|
||||
|
||||
/**
|
||||
* 最大线程数
|
||||
*/
|
||||
private Integer maxPoolSize = 50;
|
||||
|
||||
/**
|
||||
* 队列最大长度
|
||||
*/
|
||||
private Integer queueCapacity = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 允许超时关闭
|
||||
*/
|
||||
private Boolean allowCoreThreadTimeOut = false;
|
||||
|
||||
/**
|
||||
* 保持存活时间
|
||||
*/
|
||||
private Integer keepAliveSeconds = 60;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.lili.common.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 线程配置
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lili.verification-code")
|
||||
public class VerificationCodeProperties {
|
||||
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
* 包含滑块验证码有效时间, 以及验证通过之后,缓存中存储的验证结果有效时间
|
||||
*/
|
||||
private Long effectiveTime = 600L;
|
||||
|
||||
/**
|
||||
* 水印
|
||||
*/
|
||||
private String watermark = "";
|
||||
|
||||
/**
|
||||
* 干扰数量 最大数量
|
||||
*/
|
||||
private Integer interfereNum = 0;
|
||||
|
||||
/**
|
||||
* 容错像素
|
||||
*/
|
||||
private Integer faultTolerant = 3;
|
||||
|
||||
|
||||
public String getWatermark() {
|
||||
return watermark;
|
||||
}
|
||||
|
||||
public Integer getInterfereNum() {
|
||||
if (interfereNum > 2) {
|
||||
return 2;
|
||||
}
|
||||
return interfereNum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.lili.common.security;
|
||||
|
||||
import cn.lili.common.security.enums.UserEnums;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class AuthUser implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 582441893336003319L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String face;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 长期有效(用于手机app登录场景或者信任场景等)
|
||||
*/
|
||||
private Boolean longTerm = false;
|
||||
|
||||
/**
|
||||
* @see UserEnums
|
||||
* 角色
|
||||
*/
|
||||
private UserEnums role;
|
||||
|
||||
/**
|
||||
* 如果角色是商家,则存在此店铺id字段
|
||||
* storeId
|
||||
*/
|
||||
private String storeId;
|
||||
|
||||
/**
|
||||
* 如果角色是商家,则存在此店铺名称字段
|
||||
* storeName
|
||||
*/
|
||||
private String storeName;
|
||||
|
||||
/**
|
||||
* 是否是超级管理员
|
||||
*/
|
||||
private Boolean isSuper = false;
|
||||
|
||||
public AuthUser(String username, String id, String nickName, String face, UserEnums role) {
|
||||
this.username = username;
|
||||
this.face = face;
|
||||
this.id = id;
|
||||
this.role = role;
|
||||
this.nickName = nickName;
|
||||
}
|
||||
|
||||
public AuthUser(String username, String id, String face, UserEnums manager, String nickName, Boolean isSuper) {
|
||||
this.username = username;
|
||||
this.id = id;
|
||||
this.face = face;
|
||||
this.role = manager;
|
||||
this.isSuper = isSuper;
|
||||
this.nickName = nickName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.lili.common.security;
|
||||
|
||||
|
||||
import cn.lili.common.utils.ResponseUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 自定义 拒绝访问响应
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
|
||||
ResponseUtil.output(response, ResponseUtil.resultMap(false, 401, "抱歉,您没有访问权限"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lili.common.security;
|
||||
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.security.context.UserContext;
|
||||
import cn.lili.common.utils.BeanUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 全局统一判定是否可操作某属性
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-08-20 18:07
|
||||
*/
|
||||
public class OperationalJudgment {
|
||||
|
||||
/**
|
||||
* 需要判定的对象必须包含属性 memberId,storeId 代表判定的角色
|
||||
*
|
||||
* @param object 判定的对象
|
||||
* @param <T> 判定处理对象
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static <T> T judgment(T object) {
|
||||
return judgment(object, "memberId", "storeId");
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要判定的对象必须包含属性 memberId,storeId 代表判定的角色
|
||||
*
|
||||
* @param object 判定对象
|
||||
* @param buyerIdField 买家id
|
||||
* @param storeIdField 店铺id
|
||||
* @param <T> 范型
|
||||
* @return 返回判定本身,防止多次查询对象
|
||||
*/
|
||||
public static <T> T judgment(T object, String buyerIdField, String storeIdField) {
|
||||
AuthUser tokenUser = Objects.requireNonNull(UserContext.getCurrentUser());
|
||||
switch (tokenUser.getRole()) {
|
||||
case MANAGER:
|
||||
return object;
|
||||
case MEMBER:
|
||||
if (tokenUser.getId().equals(BeanUtil.getFieldValueByName(buyerIdField, object))) {
|
||||
return object;
|
||||
} else {
|
||||
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
|
||||
}
|
||||
case STORE:
|
||||
if (tokenUser.getStoreId().equals(BeanUtil.getFieldValueByName(storeIdField, object))) {
|
||||
return object;
|
||||
} else {
|
||||
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
|
||||
}
|
||||
default:
|
||||
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.lili.common.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* SecurityBean
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-11-14 15:03
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityBean {
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义跨域配置
|
||||
*
|
||||
* @return bean
|
||||
*/
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
config.setAllowedOriginPatterns(Collections.singletonList(CorsConfiguration.ALL));
|
||||
config.addAllowedHeader(CorsConfiguration.ALL);
|
||||
config.addAllowedMethod(CorsConfiguration.ALL);
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package cn.lili.common.security.context;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.security.AuthUser;
|
||||
import cn.lili.common.security.enums.SecurityEnum;
|
||||
import cn.lili.common.security.token.SecretKeyUtil;
|
||||
import com.google.gson.Gson;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 用户上下文
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2020/11/14 20:27
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
/**
|
||||
* 根据request获取用户信息
|
||||
*
|
||||
* @return 授权用户
|
||||
*/
|
||||
public static AuthUser getCurrentUser() {
|
||||
if (RequestContextHolder.getRequestAttributes() != null) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String accessToken = request.getHeader(SecurityEnum.HEADER_TOKEN.getValue());
|
||||
return getAuthUser(accessToken);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据request获取用户信息
|
||||
*
|
||||
* @return 授权用户
|
||||
*/
|
||||
public static String getUuid() {
|
||||
if (RequestContextHolder.getRequestAttributes() != null) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
return request.getHeader(SecurityEnum.UUID.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据jwt获取token重的用户信息
|
||||
*
|
||||
* @param cache 缓存
|
||||
* @param accessToken token
|
||||
* @return 授权用户
|
||||
*/
|
||||
public static AuthUser getAuthUser(Cache cache, String accessToken) {
|
||||
try {
|
||||
if (cache.keys("*" + accessToken).isEmpty()) {
|
||||
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
|
||||
}
|
||||
return getAuthUser(accessToken);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCurrentUserToken() {
|
||||
if (RequestContextHolder.getRequestAttributes() != null) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
return request.getHeader(SecurityEnum.HEADER_TOKEN.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据jwt获取token重的用户信息
|
||||
*
|
||||
* @param accessToken token
|
||||
* @return 授权用户
|
||||
*/
|
||||
public static AuthUser getAuthUser(String accessToken) {
|
||||
try {
|
||||
//获取token的信息
|
||||
Claims claims
|
||||
= Jwts.parser()
|
||||
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
|
||||
.parseClaimsJws(accessToken).getBody();
|
||||
//获取存储在claims中的用户信息
|
||||
String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
|
||||
return new Gson().fromJson(json, AuthUser.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.lili.common.security.enums;
|
||||
|
||||
/**
|
||||
* 权限枚举值
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2020/11/25 09:21
|
||||
*/
|
||||
|
||||
public enum PermissionEnum {
|
||||
|
||||
/**
|
||||
* 超级权限,查看权限
|
||||
*/
|
||||
SUPER, QUERY
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.lili.common.security.enums;
|
||||
|
||||
/**
|
||||
* 安全相关常量
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
public enum SecurityEnum {
|
||||
|
||||
/**
|
||||
* 存在与header中的token参数头 名
|
||||
*/
|
||||
HEADER_TOKEN("accessToken"), USER_CONTEXT("userContext"), JWT_SECRET("secret"), UUID("uuid");
|
||||
|
||||
String value;
|
||||
|
||||
SecurityEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.lili.common.security.enums;
|
||||
|
||||
/**
|
||||
* token角色类型
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* @since 2020/8/18 15:23
|
||||
*/
|
||||
public enum UserEnums {
|
||||
/**
|
||||
* 角色
|
||||
*/
|
||||
MEMBER("会员"),
|
||||
STORE("商家"),
|
||||
MANAGER("管理员"),
|
||||
SYSTEM("系统");
|
||||
private final String role;
|
||||
|
||||
UserEnums(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.lili.common.security.filter;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 防止XSS攻击过滤器
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-06-04 10:37
|
||||
*/
|
||||
@WebFilter
|
||||
@Component
|
||||
public class XssFilter implements Filter {
|
||||
FilterConfig filterConfig = null;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
this.filterConfig = filterConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
//对请求进行拦截,防xss处理
|
||||
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.filterConfig = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package cn.lili.common.security.filter;
|
||||
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.owasp.html.HtmlPolicyBuilder;
|
||||
import org.owasp.html.PolicyFactory;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 防止Xss
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-06-04 10:39
|
||||
*/
|
||||
@Slf4j
|
||||
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
//允许的标签
|
||||
private static final String[] allowedTags = {"h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"span", "strong",
|
||||
"img", "video", "source", "iframe", "code",
|
||||
"blockquote", "p", "div",
|
||||
"ul", "ol", "li",
|
||||
"table", "thead", "caption", "tbody", "tr", "th", "td", "br",
|
||||
"a"
|
||||
};
|
||||
|
||||
//需要转化的标签
|
||||
private static final String[] needTransformTags = {"article", "aside", "command", "datalist", "details", "figcaption", "figure",
|
||||
"footer", "header", "hgroup", "section", "summary"};
|
||||
|
||||
//带有超链接的标签
|
||||
private static final String[] linkTags = {"img", "video", "source", "a", "iframe", "p"};
|
||||
|
||||
//带有超链接的标签
|
||||
private static final String[] allowAttributes = {"style", "src", "href", "target", "width", "height"};
|
||||
|
||||
public XssHttpServletRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数组参数进行特殊字符过滤
|
||||
*/
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
String[] values = super.getParameterValues(name);
|
||||
if (values == null) {
|
||||
return new String[0];
|
||||
}
|
||||
int count = values.length;
|
||||
String[] encodedValues = new String[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
encodedValues[i] = filterXss(name, values[i]);
|
||||
}
|
||||
return encodedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对参数中特殊字符进行过滤
|
||||
*/
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String value = super.getParameter(name);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return filterXss(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取attribute,特殊字符过滤
|
||||
*/
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
Object value = super.getAttribute(name);
|
||||
if (value instanceof String) {
|
||||
value = filterXss(name, (String) value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对请求头部进行特殊字符过滤
|
||||
*/
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
String value = super.getHeader(name);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return filterXss(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
Map<String, String[]> parameterMap = super.getParameterMap();
|
||||
//因为super.getParameterMap()返回的是Map,所以我们需要定义Map的实现类对数据进行封装
|
||||
Map<String, String[]> params = new LinkedHashMap<>();
|
||||
//如果参数不为空
|
||||
if (parameterMap != null) {
|
||||
//对map进行遍历
|
||||
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
|
||||
//根据key获取value
|
||||
String[] values = entry.getValue();
|
||||
//遍历数组
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
String value = values[i];
|
||||
value = filterXss(entry.getKey(), value);
|
||||
//将转义后的数据放回数组中
|
||||
values[i] = value;
|
||||
}
|
||||
|
||||
//将转义后的数组put到linkMap当中
|
||||
params.put(entry.getKey(), values);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入流
|
||||
*
|
||||
* @return 过滤后的输入流
|
||||
* @throws IOException 异常信息
|
||||
*/
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
|
||||
BufferedReader bufferedReader = null;
|
||||
|
||||
InputStreamReader reader = null;
|
||||
|
||||
//获取输入流
|
||||
ServletInputStream in = null;
|
||||
try {
|
||||
in = super.getInputStream();
|
||||
//用于存储输入流
|
||||
StringBuilder body = new StringBuilder();
|
||||
reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
bufferedReader = new BufferedReader(reader);
|
||||
//按行读取输入流
|
||||
String line = bufferedReader.readLine();
|
||||
while (line != null) {
|
||||
//将获取到的第一行数据append到StringBuffer中
|
||||
body.append(line);
|
||||
//继续读取下一行流,直到line为空
|
||||
line = bufferedReader.readLine();
|
||||
}
|
||||
if (CharSequenceUtil.isNotEmpty(body) && Boolean.TRUE.equals(JSONUtil.isJsonObj(body.toString()))) {
|
||||
//将body转换为map
|
||||
Map<String, Object> map = JSONUtil.parseObj(body.toString());
|
||||
//创建空的map用于存储结果
|
||||
Map<String, Object> resultMap = new HashMap<>(map.size());
|
||||
//遍历数组
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
//如果map.get(key)获取到的是字符串就需要进行处理,如果不是直接存储resultMap
|
||||
if (map.get(entry.getKey()) instanceof String) {
|
||||
resultMap.put(entry.getKey(), filterXss(entry.getKey(), entry.getValue().toString()));
|
||||
} else {
|
||||
resultMap.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
//将resultMap转换为json字符串
|
||||
String resultStr = JSONUtil.toJsonStr(resultMap);
|
||||
//将json字符串转换为字节
|
||||
final ByteArrayInputStream resultBIS = new ByteArrayInputStream(resultStr.getBytes());
|
||||
|
||||
//实现接口
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return resultBIS.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//将json字符串转换为字节
|
||||
final ByteArrayInputStream bis = new ByteArrayInputStream(body.toString().getBytes());
|
||||
//实现接口
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return bis.read();
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
|
||||
log.error("get request inputStream error", e);
|
||||
return null;
|
||||
} finally {
|
||||
//关闭流
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String cleanXSS(String value) {
|
||||
if (value != null) {
|
||||
// 自定义策略
|
||||
PolicyFactory policy = new HtmlPolicyBuilder()
|
||||
.allowStandardUrlProtocols()
|
||||
//所有允许的标签
|
||||
.allowElements(allowedTags)
|
||||
//内容标签转化为div
|
||||
.allowElements((elementName, attributes) -> "div", needTransformTags)
|
||||
.allowAttributes(allowAttributes).onElements(linkTags)
|
||||
.allowStyling()
|
||||
.toFactory();
|
||||
// basic prepackaged policies for links, tables, integers, images, styles, blocks
|
||||
value = policy.sanitize(value);
|
||||
}
|
||||
return HtmlUtil.unescape(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤xss
|
||||
*
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
* @return 参数值
|
||||
*/
|
||||
private String filterXss(String name, String value) {
|
||||
return cleanXSS(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.lili.common.security.sensitive;
|
||||
|
||||
import cn.lili.common.security.sensitive.enums.SensitiveStrategy;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* 敏感注解
|
||||
*
|
||||
* @author liushuai(liushuai711 @ gmail.com)
|
||||
* @version v4.0
|
||||
* @Description:
|
||||
* @since 2021/9/10 16:45
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = SensitiveJsonSerializer.class)
|
||||
public @interface Sensitive {
|
||||
SensitiveStrategy strategy();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cn.lili.common.security.sensitive;
|
||||
|
||||
import cn.lili.common.properties.SystemSettingProperties;
|
||||
import cn.lili.common.security.AuthUser;
|
||||
import cn.lili.common.security.context.UserContext;
|
||||
import cn.lili.common.security.enums.UserEnums;
|
||||
import cn.lili.common.security.sensitive.enums.SensitiveStrategy;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 敏感信息序列化时 过滤
|
||||
*
|
||||
* @author liushuai(liushuai711 @ gmail.com)
|
||||
* @version v4.0
|
||||
* @Description:
|
||||
* @since 2021/9/10 16:46
|
||||
*/
|
||||
public class SensitiveJsonSerializer extends JsonSerializer<String>
|
||||
implements ContextualSerializer, ApplicationContextAware {
|
||||
private SensitiveStrategy strategy;
|
||||
|
||||
//系统配置
|
||||
private SystemSettingProperties systemSettingProperties;
|
||||
|
||||
@Override
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
// 字段序列化处理
|
||||
gen.writeString(strategy.desensitizer().apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||
|
||||
// 判定是否 需要脱敏处理
|
||||
if (desensitization()) {
|
||||
//获取敏感枚举
|
||||
Sensitive annotation = property.getAnnotation(Sensitive.class);
|
||||
//如果有敏感注解,则加入脱敏规则
|
||||
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
|
||||
this.strategy = annotation.strategy();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
return prov.findValueSerializer(property.getType(), property);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
systemSettingProperties = applicationContext.getBean(SystemSettingProperties.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要脱敏处理
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean desensitization() {
|
||||
|
||||
//当前用户
|
||||
AuthUser authUser = UserContext.getCurrentUser();
|
||||
//默认脱敏
|
||||
if (authUser == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//如果是店铺
|
||||
if (authUser.getRole().equals(UserEnums.STORE)) {
|
||||
//店铺需要进行脱敏,则脱敏处理
|
||||
return systemSettingProperties.getSensitiveLevel() == 2;
|
||||
}
|
||||
|
||||
|
||||
//如果是店铺
|
||||
if (authUser.getRole().equals(UserEnums.MANAGER)) {
|
||||
//店铺需要进行脱敏,则脱敏处理
|
||||
return systemSettingProperties.getSensitiveLevel() >= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.lili.common.security.sensitive.enums;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 敏感策略枚举
|
||||
*
|
||||
* @author liushuai(liushuai711 @ gmail.com)
|
||||
* @version v4.0
|
||||
* @Description:
|
||||
* @since 2021/9/10 16:46
|
||||
*/
|
||||
|
||||
public enum SensitiveStrategy {
|
||||
/**
|
||||
* Username sensitive strategy.
|
||||
*/
|
||||
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
|
||||
/**
|
||||
* Id card sensitive type.
|
||||
*/
|
||||
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
|
||||
/**
|
||||
* Phone sensitive type.
|
||||
*/
|
||||
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
|
||||
/**
|
||||
* Email sensitive type.
|
||||
*/
|
||||
EMAIL(s -> s.replaceAll("(^\\w)[^@]*(@.*$)", "$1****$2")),
|
||||
/**
|
||||
* Name sensitive type.
|
||||
*/
|
||||
NAME(s -> s.replaceAll("^(.{3}).+(.{3})$", "$1*****$2")),
|
||||
/**
|
||||
* Address sensitive type.
|
||||
*/
|
||||
ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
|
||||
|
||||
|
||||
private final Function<String, String> desensitizer;
|
||||
|
||||
SensitiveStrategy(Function<String, String> desensitizer) {
|
||||
this.desensitizer = desensitizer;
|
||||
}
|
||||
|
||||
public Function<String, String> desensitizer() {
|
||||
return desensitizer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.lili.common.security.token;
|
||||
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* SignWithUtil
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-11-18 17:30
|
||||
*/
|
||||
public class SecretKeyUtil {
|
||||
public static SecretKey generalKey() {
|
||||
//自定义
|
||||
byte[] encodedKey = Base64.decodeBase64("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=");
|
||||
SecretKey key = Keys.hmacShaKeyFor(encodedKey);
|
||||
return key;
|
||||
}
|
||||
|
||||
public static SecretKey generalKeyByDecoders() {
|
||||
return Keys.hmacShaKeyFor(Decoders.BASE64.decode("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.lili.common.security.token;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Token 实体类
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-11-13 10:02
|
||||
*/
|
||||
@Data
|
||||
public class Token {
|
||||
/**
|
||||
* 访问token
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package cn.lili.common.security.token;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import cn.lili.cache.CachePrefix;
|
||||
import cn.lili.common.enums.ResultCode;
|
||||
import cn.lili.common.exception.ServiceException;
|
||||
import cn.lili.common.security.AuthUser;
|
||||
import cn.lili.common.security.enums.SecurityEnum;
|
||||
import cn.lili.common.security.enums.UserEnums;
|
||||
import cn.lili.common.properties.JWTTokenProperties;
|
||||
import com.google.gson.Gson;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* TokenUtil
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-11-12 18:44
|
||||
*/
|
||||
@Component
|
||||
public class TokenUtil {
|
||||
@Autowired
|
||||
private JWTTokenProperties tokenProperties;
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
/**
|
||||
* 构建token
|
||||
*
|
||||
* @param username 主体
|
||||
* @param claim 私有声明
|
||||
* @param longTerm 长时间特殊token 如:移动端,微信小程序等
|
||||
* @param userEnums 用户枚举
|
||||
* @return TOKEN
|
||||
*/
|
||||
public Token createToken(String username, Object claim, boolean longTerm, UserEnums userEnums) {
|
||||
Token token = new Token();
|
||||
//访问token
|
||||
String accessToken = createToken(username, claim, tokenProperties.getTokenExpireTime());
|
||||
|
||||
cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1,
|
||||
tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
|
||||
//刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
|
||||
Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 2;
|
||||
String refreshToken = createToken(username, claim, expireTime);
|
||||
|
||||
cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expireTime, TimeUnit.MINUTES);
|
||||
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*
|
||||
* @param oldRefreshToken 刷新token
|
||||
* @param userEnums 用户枚举
|
||||
* @return token
|
||||
*/
|
||||
public Token refreshToken(String oldRefreshToken, UserEnums userEnums) {
|
||||
|
||||
Claims claims;
|
||||
try {
|
||||
claims = Jwts.parser()
|
||||
.setSigningKey(SecretKeyUtil.generalKeyByDecoders())
|
||||
.parseClaimsJws(oldRefreshToken).getBody();
|
||||
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
|
||||
//token 过期 认证失败等
|
||||
throw new ServiceException(ResultCode.USER_AUTH_EXPIRED);
|
||||
}
|
||||
|
||||
//获取存储在claims中的用户信息
|
||||
String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
|
||||
AuthUser authUser = new Gson().fromJson(json, AuthUser.class);
|
||||
|
||||
|
||||
String username = authUser.getUsername();
|
||||
//获取是否长期有效的token
|
||||
boolean longTerm = authUser.getLongTerm();
|
||||
|
||||
|
||||
//如果缓存中有刷新token &&
|
||||
if (cache.hasKey(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken)) {
|
||||
Token token = new Token();
|
||||
//访问token
|
||||
String accessToken = createToken(username, authUser, tokenProperties.getTokenExpireTime());
|
||||
cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
|
||||
|
||||
//如果是信任登录设备,则刷新token长度继续延长
|
||||
Long expirationTime = tokenProperties.getTokenExpireTime() * 2;
|
||||
if (longTerm) {
|
||||
expirationTime = 60 * 24 * 15L;
|
||||
}
|
||||
|
||||
//刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
|
||||
String refreshToken = createToken(username, authUser, expirationTime);
|
||||
|
||||
cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expirationTime, TimeUnit.MINUTES);
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
cache.remove(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken);
|
||||
return token;
|
||||
} else {
|
||||
throw new ServiceException(ResultCode.USER_AUTH_EXPIRED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
*
|
||||
* @param username 主体
|
||||
* @param claim 私有神明内容
|
||||
* @param expirationTime 过期时间(分钟)
|
||||
* @return token字符串
|
||||
*/
|
||||
private String createToken(String username, Object claim, Long expirationTime) {
|
||||
//JWT 生成
|
||||
return Jwts.builder()
|
||||
//jwt 私有声明
|
||||
.claim(SecurityEnum.USER_CONTEXT.getValue(), new Gson().toJson(claim))
|
||||
//JWT的主体
|
||||
.setSubject(username)
|
||||
//失效时间 当前时间+过期分钟
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000))
|
||||
//签名算法和密钥
|
||||
.signWith(SecretKeyUtil.generalKey())
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.lili.common.security.token.base;
|
||||
|
||||
import cn.lili.common.security.enums.UserEnums;
|
||||
import cn.lili.common.security.token.Token;
|
||||
|
||||
/**
|
||||
* AbstractToken
|
||||
* 抽象token,定义生成token类
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-11-13 10:13
|
||||
*/
|
||||
public abstract class AbstractTokenGenerate<T> {
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
*
|
||||
* @param user 用户名
|
||||
* @param longTerm 是否长时间有效
|
||||
* @return TOKEN对象
|
||||
*/
|
||||
public abstract Token createToken(T user, Boolean longTerm);
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*
|
||||
* @param refreshToken 刷新token
|
||||
* @return token
|
||||
*/
|
||||
public abstract Token refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 默认role
|
||||
*/
|
||||
public UserEnums role = UserEnums.MANAGER;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package cn.lili.common.sensitive;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
/**
|
||||
* 敏感词过滤器
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @version v1.0
|
||||
* @since v1.0
|
||||
* 2020-02-25 14:10:16
|
||||
*/
|
||||
@Slf4j
|
||||
public class SensitiveWordsFilter implements Serializable {
|
||||
|
||||
/**
|
||||
* 字符*
|
||||
*/
|
||||
public final static char WILDCARD_STAR = '*';
|
||||
|
||||
/**
|
||||
* 为2的n次方,考虑到敏感词大概在10k左右,
|
||||
* 这个数量应为词数的数倍,使得桶很稀疏
|
||||
* 提高不命中时hash指向null的概率,
|
||||
* 加快访问速度。
|
||||
*/
|
||||
static final int DEFAULT_INITIAL_CAPACITY = 131072;
|
||||
|
||||
/**
|
||||
* 类似HashMap的桶,比较稀疏。
|
||||
* 使用2个字符的hash定位。
|
||||
*/
|
||||
protected static SensitiveWordsNode[] nodes = new SensitiveWordsNode[0];
|
||||
|
||||
/**
|
||||
* 更新中的nodes,用于防止动态更新时,原有nodes被清空,导致无法正常写入过滤词
|
||||
*/
|
||||
protected static SensitiveWordsNode[] nodesUpdate;
|
||||
|
||||
|
||||
/**
|
||||
* 过滤铭感次
|
||||
*
|
||||
* @param sentence 过滤赐予
|
||||
* @return
|
||||
*/
|
||||
public static String filter(String sentence) {
|
||||
return filter(sentence, WILDCARD_STAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对句子进行敏感词过滤<br/>
|
||||
* 如果无敏感词返回输入的sentence对象,即可以用下面的方式判断是否有敏感词:<br/>
|
||||
*
|
||||
* @param sentence 句子
|
||||
* @param replace 敏感词的替换字符
|
||||
* @return 过滤后的句子
|
||||
*/
|
||||
public static String filter(String sentence, char replace) {
|
||||
//先转换为StringPointer
|
||||
StringPointer sp = new StringPointer(sentence + " ");
|
||||
|
||||
//标示是否替换
|
||||
boolean replaced = false;
|
||||
|
||||
//匹配的起始位置
|
||||
int i = 0;
|
||||
while (i < sp.length - 2) {
|
||||
/*
|
||||
* 移动到下一个匹配位置的步进:
|
||||
* 如果未匹配为1,如果匹配是匹配的词长度
|
||||
*/
|
||||
int step = 1;
|
||||
//计算此位置开始2个字符的hash
|
||||
int hash = sp.nextTwoCharHash(i);
|
||||
/*
|
||||
* 根据hash获取第一个节点,
|
||||
* 真正匹配的节点可能不是第一个,
|
||||
* 所以有后面的for循环。
|
||||
*/
|
||||
SensitiveWordsNode node = nodes[hash & (nodes.length - 1)];
|
||||
/*
|
||||
* 如果非敏感词,node基本为null。
|
||||
* 这一步大幅提升效率
|
||||
*/
|
||||
if (node != null) {
|
||||
/*
|
||||
* 如果能拿到第一个节点,
|
||||
* 才计算mix(mix相同表示2个字符相同)。
|
||||
* mix的意义和HashMap先hash再equals的equals部分类似。
|
||||
*/
|
||||
int mix = sp.nextTwoCharMix(i);
|
||||
/*
|
||||
* 循环所有的节点,如果非敏感词,
|
||||
* mix相同的概率非常低,提高效率
|
||||
*/
|
||||
outer:
|
||||
for (; node != null; node = node.next) {
|
||||
/*
|
||||
* 对于一个节点,先根据头2个字符判断是否属于这个节点。
|
||||
* 如果属于这个节点,看这个节点的词库是否命中。
|
||||
* 此代码块中访问次数已经很少,不是优化重点
|
||||
*/
|
||||
if (node.headTwoCharMix == mix) {
|
||||
/*
|
||||
* 查出比剩余sentence小的最大的词。
|
||||
* 例如剩余sentence为"色情电影哪家强?",
|
||||
* 这个节点含三个词从小到大为:"色情"、"色情电影"、"色情信息"。
|
||||
* 则从“色情电影”开始向前匹配
|
||||
*/
|
||||
NavigableSet<StringPointer> desSet = node.words.headSet(sp.substring(i), true);
|
||||
if (desSet != null) {
|
||||
for (StringPointer word : desSet.descendingSet()) {
|
||||
/*
|
||||
* 仍然需要再判断一次,例如"色情信息哪里有?",
|
||||
* 如果节点只包含"色情电影"一个词,
|
||||
* 仍然能够取到word为"色情电影",但是不该匹配。
|
||||
*/
|
||||
if (sp.nextStartsWith(i, word)) {
|
||||
//匹配成功,将匹配的部分,用replace制定的内容替代
|
||||
sp.fill(i, i + word.length, replace);
|
||||
//跳过已经替代的部分
|
||||
step = word.length;
|
||||
//标示有替换
|
||||
replaced = true;
|
||||
//跳出循环(然后是while循环的下一个位置)
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//移动到下一个匹配位置
|
||||
i += step;
|
||||
}
|
||||
|
||||
//如果没有替换,直接返回入参(节约String的构造copy)
|
||||
if (replaced) {
|
||||
String res = sp.toString();
|
||||
return res.substring(0, res.length() - 2);
|
||||
} else {
|
||||
return sentence;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化敏感词
|
||||
*/
|
||||
public static void init(List<String> words) {
|
||||
log.info("开始初始化敏感词");
|
||||
nodesUpdate = new SensitiveWordsNode[DEFAULT_INITIAL_CAPACITY];
|
||||
for (String word : words) {
|
||||
put(word);
|
||||
}
|
||||
nodes = nodesUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个敏感词,如果词的长度(trim后)小于2,则丢弃<br/>
|
||||
* 此方法(构建)并不是主要的性能优化点。
|
||||
*
|
||||
* @param word 敏感词
|
||||
* @return 操作结果
|
||||
*/
|
||||
public static boolean put(String word) {
|
||||
|
||||
//长度小于2的不加入
|
||||
if (word == null || word.trim().length() < 2) {
|
||||
return false;
|
||||
}
|
||||
//两个字符的不考虑
|
||||
if (word.length() == 2 && word.matches("\\w\\w")) {
|
||||
return false;
|
||||
}
|
||||
StringPointer sp = new StringPointer(word.trim());
|
||||
//计算头两个字符的hash
|
||||
int hash = sp.nextTwoCharHash(0);
|
||||
//计算头两个字符的mix表示(mix相同,两个字符相同)
|
||||
int mix = sp.nextTwoCharMix(0);
|
||||
//转为在hash桶中的位置
|
||||
int index = hash & (nodesUpdate.length - 1);
|
||||
|
||||
//从桶里拿第一个节点
|
||||
SensitiveWordsNode node = nodesUpdate[index];
|
||||
if (node == null) {
|
||||
//如果没有节点,则放进去一个
|
||||
node = new SensitiveWordsNode(mix);
|
||||
//并添加词
|
||||
node.words.add(sp);
|
||||
//放入桶里
|
||||
nodesUpdate[index] = node;
|
||||
} else {
|
||||
//如果已经有节点(1个或多个),找到正确的节点
|
||||
for (; node != null; node = node.next) {
|
||||
//匹配节点
|
||||
if (node.headTwoCharMix == mix) {
|
||||
node.words.add(sp);
|
||||
return true;
|
||||
}
|
||||
//如果匹配到最后仍然不成功,则追加一个节点
|
||||
if (node.next == null) {
|
||||
new SensitiveWordsNode(mix, node).words.add(sp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除敏感词
|
||||
*
|
||||
* @param word
|
||||
* @return
|
||||
*/
|
||||
public static void remove(String word) {
|
||||
|
||||
StringPointer sp = new StringPointer(word.trim());
|
||||
//计算头两个字符的hash
|
||||
int hash = sp.nextTwoCharHash(0);
|
||||
//计算头两个字符的mix表示(mix相同,两个字符相同)
|
||||
int mix = sp.nextTwoCharMix(0);
|
||||
//转为在hash桶中的位置
|
||||
int index = hash & (nodes.length - 1);
|
||||
SensitiveWordsNode node = nodes[index];
|
||||
|
||||
for (; node != null; node = node.next) {
|
||||
//匹配节点
|
||||
if (node.headTwoCharMix == mix) {
|
||||
node.words.remove(sp);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.lili.common.sensitive;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* @Description: 敏感词节点,每个节点包含了以相同的2个字符开头的所有词
|
||||
* @author Bulbasaur
|
||||
* @version v1.0
|
||||
* @since v1.0
|
||||
* 2020-02-25 14:10:16
|
||||
*/
|
||||
public class SensitiveWordsNode implements Serializable{
|
||||
|
||||
/**
|
||||
* 头两个字符的mix,mix相同,两个字符相同
|
||||
*/
|
||||
protected final int headTwoCharMix;
|
||||
|
||||
/**
|
||||
* 所有以这两个字符开头的词表
|
||||
*/
|
||||
protected final TreeSet<StringPointer> words = new TreeSet<StringPointer>();
|
||||
|
||||
/**
|
||||
* 下一个节点
|
||||
*/
|
||||
protected SensitiveWordsNode next;
|
||||
|
||||
public SensitiveWordsNode(int headTwoCharMix){
|
||||
this.headTwoCharMix = headTwoCharMix;
|
||||
}
|
||||
|
||||
public SensitiveWordsNode(int headTwoCharMix, SensitiveWordsNode parent){
|
||||
this.headTwoCharMix = headTwoCharMix;
|
||||
parent.next = this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package cn.lili.common.sensitive;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 字符指针
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2020-02-25 14:10:16
|
||||
*/
|
||||
public class StringPointer implements Serializable, CharSequence, Comparable<StringPointer> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected final char[] value;
|
||||
|
||||
protected final int offset;
|
||||
|
||||
protected final int length;
|
||||
|
||||
private int hash = 0;
|
||||
|
||||
public StringPointer(String str) {
|
||||
value = str.toCharArray();
|
||||
offset = 0;
|
||||
length = value.length;
|
||||
}
|
||||
|
||||
public StringPointer(char[] value, int offset, int length) {
|
||||
this.value = value;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算该位置后(包含)2个字符的hash值
|
||||
*
|
||||
* @param i 从 0 到 length - 2
|
||||
* @return 从 0 到 length - 2
|
||||
*/
|
||||
public int nextTwoCharHash(int i) {
|
||||
return 31 * value[offset + i] + value[offset + i + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算该位置后(包含)2个字符和为1个int型的值<br/>
|
||||
* int值相同表示2个字符相同
|
||||
*
|
||||
* @param i 从 0 到 length - 2
|
||||
* @return int值
|
||||
*/
|
||||
public int nextTwoCharMix(int i) {
|
||||
return (value[offset + i] << 16) | value[offset + i + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 该位置后(包含)的字符串,是否以某个词(word)开头
|
||||
*
|
||||
* @param i 从 0 到 length - 2
|
||||
* @param word 词
|
||||
* @return 是否?
|
||||
*/
|
||||
public boolean nextStartsWith(int i, StringPointer word) {
|
||||
//是否长度超出
|
||||
if (word.length > length - i) {
|
||||
return false;
|
||||
}
|
||||
//从尾开始判断
|
||||
for (int c = word.length - 1; c >= 0; c--) {
|
||||
if (value[offset + i + c] != word.value[word.offset + c]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充(替换)
|
||||
*
|
||||
* @param begin 从此位置开始(含)
|
||||
* @param end 到此位置结束(不含)
|
||||
* @param fillWith 以此字符填充(替换)
|
||||
*/
|
||||
public void fill(int begin, int end, char fillWith) {
|
||||
for (int i = begin; i < end; i++) {
|
||||
value[offset + i] = fillWith;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return value[offset + i];
|
||||
}
|
||||
|
||||
public StringPointer substring(int begin) {
|
||||
return new StringPointer(value, offset + begin, length - begin);
|
||||
}
|
||||
|
||||
public StringPointer substring(int begin, int end) {
|
||||
return new StringPointer(value, offset + begin, end - begin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return substring(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String(value, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = hash;
|
||||
if (h == 0 && length > 0) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
h = 31 * h + value[offset + i];
|
||||
}
|
||||
hash = h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object anObject) {
|
||||
if (this == anObject) {
|
||||
return true;
|
||||
}
|
||||
if (anObject instanceof StringPointer) {
|
||||
StringPointer that = (StringPointer) anObject;
|
||||
if (length == that.length) {
|
||||
char[] v1 = this.value;
|
||||
char[] v2 = that.value;
|
||||
for (int i = 0; i < this.length; i++) {
|
||||
if (v1[this.offset + i] != v2[that.offset + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(StringPointer that) {
|
||||
int len1 = this.length;
|
||||
int len2 = that.length;
|
||||
int lim = Math.min(len1, len2);
|
||||
char[] v1 = this.value;
|
||||
char[] v2 = that.value;
|
||||
|
||||
int k = 0;
|
||||
while (k < lim) {
|
||||
char c1 = v1[this.offset + k];
|
||||
char c2 = v2[that.offset + k];
|
||||
if (c1 != c2) {
|
||||
return c1 - c2;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
return len1 - len2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.lili.common.sensitive.init;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import cn.lili.cache.CachePrefix;
|
||||
import cn.lili.common.sensitive.SensitiveWordsFilter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 敏感词加载
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-11-23 12:08
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SensitiveWordsLoader implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private Cache<List<String>> cache;
|
||||
|
||||
/**
|
||||
* 程序启动时,获取最新的需要过滤的敏感词
|
||||
* <p>
|
||||
* 这里即便缓存中为空也没关系,定时任务会定时重新加载敏感词
|
||||
*
|
||||
* @param args 启动参数
|
||||
*/
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
List<String> sensitives = cache.get(CachePrefix.SENSITIVE.getPrefix());
|
||||
log.info("系统初始化敏感词");
|
||||
if (sensitives == null || sensitives.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SensitiveWordsFilter.init(sensitives);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.lili.common.sensitive.quartz;
|
||||
|
||||
import org.quartz.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 定时执行配置
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-11-23 16:30
|
||||
*/
|
||||
@Configuration
|
||||
public class QuartzConfig {
|
||||
|
||||
@Bean
|
||||
public JobDetail sensitiveQuartzDetail() {
|
||||
return JobBuilder.newJob(SensitiveQuartz.class).withIdentity("sensitiveQuartz").storeDurably().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Trigger sensitiveQuartzTrigger() {
|
||||
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
|
||||
.withIntervalInSeconds(3600)
|
||||
.repeatForever();
|
||||
return TriggerBuilder.newTrigger().forJob(sensitiveQuartzDetail())
|
||||
.withIdentity("sensitiveQuartz")
|
||||
.withSchedule(scheduleBuilder)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.lili.common.sensitive.quartz;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import cn.lili.cache.CachePrefix;
|
||||
import cn.lili.common.sensitive.SensitiveWordsFilter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 间隔更新敏感词
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-11-23 16:31
|
||||
*/
|
||||
@Slf4j
|
||||
public class SensitiveQuartz extends QuartzJobBean {
|
||||
|
||||
@Autowired
|
||||
private Cache<List<String>> cache;
|
||||
|
||||
/**
|
||||
* 定时更新敏感词信息
|
||||
*
|
||||
* @param jobExecutionContext
|
||||
*/
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext jobExecutionContext) {
|
||||
log.info("敏感词定时更新");
|
||||
List<String> sensitives = cache.get(CachePrefix.SENSITIVE.getPrefix());
|
||||
if (sensitives == null || sensitives.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SensitiveWordsFilter.init(sensitives);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package cn.lili.common.swagger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Chopper
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableSwagger2WebMvc
|
||||
public class Swagger2Config {
|
||||
|
||||
@Value("${swagger.title}")
|
||||
private String title;
|
||||
|
||||
@Value("${swagger.description}")
|
||||
private String description;
|
||||
|
||||
@Value("${swagger.version}")
|
||||
private String version;
|
||||
|
||||
@Value("${swagger.termsOfServiceUrl}")
|
||||
private String termsOfServiceUrl;
|
||||
|
||||
@Value("${swagger.contact.name}")
|
||||
private String name;
|
||||
|
||||
@Value("${swagger.contact.url}")
|
||||
private String url;
|
||||
|
||||
@Value("${swagger.contact.email}")
|
||||
private String email;
|
||||
|
||||
private List<ApiKey> securitySchemes() {
|
||||
List<ApiKey> apiKeys = new ArrayList<>();
|
||||
apiKeys.add(new ApiKey("Authorization", "accessToken", "header"));
|
||||
return apiKeys;
|
||||
}
|
||||
|
||||
private List<SecurityContext> securityContexts() {
|
||||
List<SecurityContext> securityContexts = new ArrayList<>();
|
||||
securityContexts.add(SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.forPaths(PathSelectors.regex("^(?!auth).*$")).build());
|
||||
return securityContexts;
|
||||
}
|
||||
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
List<SecurityReference> securityReferences = new ArrayList<>();
|
||||
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
|
||||
return securityReferences;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket goodsRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("商品")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
// .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.goods"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket memberRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("会员")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.member"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket promotionRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("促销")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.promotion"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket storeRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("店铺")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.store"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket tradeRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("交易")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.trade"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Docket settingRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("设置")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.setting"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket permissionRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("权限")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.permission"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket otherRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("其他")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.other"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket commonRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("通用")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.common"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
@Bean
|
||||
public Docket distributionRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("分销")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.distribution"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket statisticsRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("统计")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.statistics"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket paymentRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("支付")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.payment"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket passportRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.groupName("登录")
|
||||
.apiInfo(apiInfo()).select()
|
||||
//扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.passport"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title(title)
|
||||
.description(description)
|
||||
.termsOfServiceUrl(termsOfServiceUrl)
|
||||
.contact(new Contact(name, url, email))
|
||||
.version(version)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.lili.common.thread;
|
||||
|
||||
import cn.lili.common.properties.ThreadProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 多线程配置
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* @since
|
||||
* 2020-03-12 10:50 上午
|
||||
*/
|
||||
@Configuration
|
||||
public class ThreadConfig implements AsyncConfigurer {
|
||||
|
||||
|
||||
@Autowired
|
||||
private ThreadProperties threadProperties;
|
||||
|
||||
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心线程数,默认为5
|
||||
executor.setCorePoolSize(threadProperties.getCorePoolSize());
|
||||
// 最大线程数,默认为10
|
||||
executor.setMaxPoolSize(threadProperties.getMaxPoolSize());
|
||||
// 队列最大长度,一般需要设置值为足够大
|
||||
executor.setQueueCapacity(threadProperties.getQueueCapacity());
|
||||
// 线程池维护线程所允许的空闲时间,默认为60s
|
||||
executor.setKeepAliveSeconds(threadProperties.getKeepAliveSeconds());
|
||||
// 允许超时关闭
|
||||
executor.setAllowCoreThreadTimeOut(threadProperties.getAllowCoreThreadTimeOut());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Decoder;
|
||||
|
||||
/**
|
||||
* base64转为multipartFile工具类
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Slf4j
|
||||
public class Base64DecodeMultipartFile implements MultipartFile {
|
||||
|
||||
private final byte[] imgContent;
|
||||
private final String header;
|
||||
|
||||
public Base64DecodeMultipartFile(byte[] imgContent, String header) {
|
||||
this.imgContent = imgContent;
|
||||
this.header = header.split(";")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return header.split(":")[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return imgContent == null || imgContent.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return imgContent.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() throws IOException {
|
||||
return imgContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(imgContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(File dest) throws IOException, IllegalStateException {
|
||||
OutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(dest);
|
||||
stream.write(imgContent);
|
||||
} catch (IOException e) {
|
||||
log.error("transferTo错误", e);
|
||||
} finally {
|
||||
assert stream != null;
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static MultipartFile base64Convert(String base64) {
|
||||
|
||||
String[] baseStrs = base64.split(",");
|
||||
Decoder decoder = Base64.getDecoder();
|
||||
byte[] b = decoder.decode(baseStrs[1]);
|
||||
|
||||
for (int i = 0; i < b.length; ++i) {
|
||||
if (b[i] < 0) {
|
||||
b[i] += 256;
|
||||
}
|
||||
}
|
||||
return new Base64DecodeMultipartFile(b, baseStrs[0]);
|
||||
}
|
||||
|
||||
|
||||
public static InputStream base64ToInputStream(String base64) {
|
||||
ByteArrayInputStream stream = null;
|
||||
try {
|
||||
byte[] bytes = Base64.getDecoder().decode(base64);
|
||||
stream = new ByteArrayInputStream(bytes);
|
||||
} catch (Exception e) {
|
||||
log.error("base64ToInputStream错误", e);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static String inputStreamToStream(InputStream in) {
|
||||
byte[] data = null;
|
||||
//读取图片字节数组
|
||||
try {
|
||||
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
|
||||
byte[] buff = new byte[100];
|
||||
int rc = 0;
|
||||
while ((rc = in.read(buff, 0, 100)) > 0) {
|
||||
swapStream.write(buff, 0, rc);
|
||||
}
|
||||
data = swapStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
log.error("转码错误", e);
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
log.error("inputStreamToStream错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(data);
|
||||
}
|
||||
}
|
||||
|
||||
235
framework/src/main/java/cn/lili/common/utils/Base64Utils.java
Normal file
235
framework/src/main/java/cn/lili/common/utils/Base64Utils.java
Normal file
@@ -0,0 +1,235 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Base64编码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public class Base64Utils {
|
||||
|
||||
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
/**
|
||||
* 标准编码表
|
||||
*/
|
||||
private static final byte[] STANDARD_ENCODE_TABLE = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'
|
||||
};
|
||||
/**
|
||||
* URL安全的编码表,将 + 和 / 替换为 - 和 _
|
||||
*/
|
||||
private static final byte[] URL_SAFE_ENCODE_TABLE = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '-', '_'
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------- encode
|
||||
|
||||
/**
|
||||
* 编码为Base64,非URL安全的
|
||||
*
|
||||
* @param arr 被编码的数组
|
||||
* @param lineSep 在76个char之后是CRLF还是EOF
|
||||
* @return 编码后的bytes
|
||||
*/
|
||||
public static byte[] encode(byte[] arr, boolean lineSep) {
|
||||
return encode(arr, lineSep, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为Base64,URL安全的
|
||||
*
|
||||
* @param arr 被编码的数组
|
||||
* @param lineSep 在76个char之后是CRLF还是EOF
|
||||
* @return 编码后的bytes
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) {
|
||||
return encode(arr, lineSep, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(CharSequence source) {
|
||||
return encode(source, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static String encodeUrlSafe(CharSequence source) {
|
||||
return encodeUrlSafe(source, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(CharSequence source, Charset charset) {
|
||||
return encode(bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全的
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static String encodeUrlSafe(CharSequence source, Charset charset) {
|
||||
return encodeUrlSafe(bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return str(encode(source, false), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全的
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static String encodeUrlSafe(byte[] source) {
|
||||
return str(encodeUrlSafe(source, false), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为Base64<br>
|
||||
* 如果isMultiLine为<code>true</code>,则每76个字符一个换行符,否则在一行显示
|
||||
*
|
||||
* @param arr 被编码的数组
|
||||
* @param isMultiLine 在76个char之后是CRLF还是EOF
|
||||
* @param isUrlSafe 是否使用URL安全字符,一般为<code>false</code>
|
||||
* @return 编码后的bytes
|
||||
*/
|
||||
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
|
||||
if (null == arr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int len = arr.length;
|
||||
if (len == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int evenlen = (len / 3) * 3;
|
||||
int cnt = ((len - 1) / 3 + 1) << 2;
|
||||
int destlen = cnt + (isMultiLine ? (cnt - 1) / 76 << 1 : 0);
|
||||
byte[] dest = new byte[destlen];
|
||||
|
||||
byte[] encodeTable = isUrlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
|
||||
|
||||
for (int s = 0, d = 0, cc = 0; s < evenlen; ) {
|
||||
int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);
|
||||
|
||||
dest[d++] = encodeTable[(i >>> 18) & 0x3f];
|
||||
dest[d++] = encodeTable[(i >>> 12) & 0x3f];
|
||||
dest[d++] = encodeTable[(i >>> 6) & 0x3f];
|
||||
dest[d++] = encodeTable[i & 0x3f];
|
||||
|
||||
if (isMultiLine && ++cc == 19 && d < destlen - 2) {
|
||||
dest[d++] = '\r';
|
||||
dest[d++] = '\n';
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//剩余位数
|
||||
int left = len - evenlen;
|
||||
if (left > 0) {
|
||||
int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);
|
||||
|
||||
dest[destlen - 4] = encodeTable[i >> 12];
|
||||
dest[destlen - 3] = encodeTable[(i >>> 6) & 0x3f];
|
||||
|
||||
if (isUrlSafe) {
|
||||
//在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉。
|
||||
int urlSafeLen = destlen - 2;
|
||||
if (2 == left) {
|
||||
dest[destlen - 2] = encodeTable[i & 0x3f];
|
||||
urlSafeLen += 1;
|
||||
}
|
||||
byte[] urlSafeDest = new byte[urlSafeLen];
|
||||
System.arraycopy(dest, 0, urlSafeDest, 0, urlSafeLen);
|
||||
return urlSafeDest;
|
||||
} else {
|
||||
dest[destlen - 2] = (left == 2) ? encodeTable[i & 0x3f] : (byte) '=';
|
||||
dest[destlen - 1] = '=';
|
||||
}
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码字符串
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param charset 字符集,如果此字段为空,则解码的结果取决于平台
|
||||
* @return 编码后的字节码
|
||||
*/
|
||||
public static byte[] bytes(CharSequence str, Charset charset) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null == charset) {
|
||||
return str.toString().getBytes();
|
||||
}
|
||||
return str.toString().getBytes(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码字节码
|
||||
*
|
||||
* @param data 字符串
|
||||
* @param charset 字符集,如果此字段为空,则解码的结果取决于平台
|
||||
* @return 解码后的字符串
|
||||
*/
|
||||
public static String str(byte[] data, Charset charset) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null == charset) {
|
||||
return new String(data);
|
||||
}
|
||||
return new String(data, charset);
|
||||
}
|
||||
|
||||
}
|
||||
137
framework/src/main/java/cn/lili/common/utils/BeanUtil.java
Normal file
137
framework/src/main/java/cn/lili/common/utils/BeanUtil.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 对象属性复制
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
public class BeanUtil {
|
||||
|
||||
/**
|
||||
* 复制属性
|
||||
*
|
||||
* @param objectFrom 源自对象
|
||||
* @param objectTo 复制给对象
|
||||
*/
|
||||
public static void copyProperties(Object objectFrom, Object objectTo) {
|
||||
BeanUtils.copyProperties(objectFrom, objectTo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取属性名数组
|
||||
*
|
||||
* @param o 获取字段的对象
|
||||
* @return 返回各个字段
|
||||
*/
|
||||
public static String[] getFiledName(Object o) {
|
||||
Field[] fields = o.getClass().getDeclaredFields();
|
||||
Field[] superFields = o.getClass().getSuperclass().getDeclaredFields();
|
||||
String[] fieldNames = new String[fields.length + superFields.length];
|
||||
int index = 0;
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fieldNames[index] = fields[i].getName();
|
||||
index++;
|
||||
}
|
||||
for (int i = 0; i < superFields.length; i++) {
|
||||
if ("id".equals(superFields[i].getName())) {
|
||||
continue;
|
||||
}
|
||||
fieldNames[index] = superFields[i].getName();
|
||||
index++;
|
||||
}
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据属性名获取属性值
|
||||
*
|
||||
* @param fieldName 属性名
|
||||
* @param o 对象
|
||||
* @return 属性值
|
||||
*/
|
||||
public static Object getFieldValueByName(String fieldName, Object o) {
|
||||
try {
|
||||
String firstLetter = fieldName.substring(0, 1).toUpperCase();
|
||||
String getter = "get" + firstLetter + fieldName.substring(1);
|
||||
Method method = o.getClass().getMethod(getter, new Class[]{});
|
||||
Object value = method.invoke(o, new Object[]{});
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将对象转换为key value
|
||||
* A=a&B=b&C=c 格式
|
||||
*
|
||||
* @param object 对象
|
||||
* @return 格式化结果
|
||||
*/
|
||||
public static String formatKeyValuePair(Object object) {
|
||||
//准备接受的字符串
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
//获取对象字段
|
||||
String[] fieldNames = BeanUtil.getFiledName(object);
|
||||
//遍历所有属性
|
||||
for (int j = 0; j < fieldNames.length; j++) {
|
||||
//不是第一个并且不是最后一个,拼接&
|
||||
if (j != 0) {
|
||||
stringBuffer.append("&");
|
||||
}
|
||||
//获取属性的名字
|
||||
String key = fieldNames[j];
|
||||
//获取值
|
||||
Object value = BeanUtil.getFieldValueByName(key, object);
|
||||
assert value != null;
|
||||
stringBuffer.append(key).append("=").append(value.toString());
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* key value键值对 转换为 对象
|
||||
* A=a&B=b&C=c 格式 转换为对象
|
||||
*
|
||||
* @param str 对象字符串
|
||||
* @param t 范型
|
||||
* @param <T> 范型
|
||||
* @return 格式化结果
|
||||
*/
|
||||
public static <T> T formatKeyValuePair(String str, T t) {
|
||||
//填写对参数键值对
|
||||
String[] params = str.split("&");
|
||||
|
||||
//获取对象字段
|
||||
String[] fieldNames = BeanUtil.getFiledName(t);
|
||||
|
||||
try {
|
||||
//循环每个参数
|
||||
for (String param : params) {
|
||||
String[] keyValues = param.split("=");
|
||||
for (int i = 0; i < fieldNames.length; i++) {
|
||||
if (fieldNames[i].equals(keyValues[0])) {
|
||||
Field f = t.getClass().getDeclaredField(fieldNames[i]);
|
||||
f.setAccessible(true);
|
||||
//长度为2 才转换,否则不转
|
||||
if (keyValues.length == 2) {
|
||||
f.set(t, keyValues[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
}
|
||||
35
framework/src/main/java/cn/lili/common/utils/CommonUtil.java
Normal file
35
framework/src/main/java/cn/lili/common/utils/CommonUtil.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 通用工具
|
||||
* @author Chopper
|
||||
*/
|
||||
public class CommonUtil {
|
||||
|
||||
/**
|
||||
* 以UUID重命名
|
||||
* @param fileName 文件名称
|
||||
* @return 格式化名称
|
||||
*/
|
||||
public static String rename(String fileName) {
|
||||
String extName = fileName.substring(fileName.lastIndexOf("."));
|
||||
return UUID.randomUUID().toString().replace("-", "") + extName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 随机6位数生成
|
||||
*/
|
||||
public static String getRandomNum() {
|
||||
|
||||
Random random = new Random();
|
||||
int num = random.nextInt(999999);
|
||||
//不足六位前面补0
|
||||
String str = String.format("%06d", num);
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
||||
77
framework/src/main/java/cn/lili/common/utils/CookieUtil.java
Normal file
77
framework/src/main/java/cn/lili/common/utils/CookieUtil.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* CookieUtil
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-12-14 09:32
|
||||
*/
|
||||
@Slf4j
|
||||
public class CookieUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 新增cookie
|
||||
*
|
||||
* @param key key值
|
||||
* @param value 对应值
|
||||
* @param maxAge cookie 有效时间
|
||||
* @param response 响应
|
||||
*/
|
||||
public static void addCookie(String key, String value, Integer maxAge, HttpServletResponse response) {
|
||||
try {
|
||||
Cookie c = new Cookie(key, value);
|
||||
c.setMaxAge(maxAge);
|
||||
c.setPath("/");
|
||||
response.addCookie(c);
|
||||
} catch (Exception e) {
|
||||
log.error("新增cookie错误",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除cookie
|
||||
*
|
||||
* @param key key值
|
||||
* @param response 响应
|
||||
*/
|
||||
public static void delCookie(String key, HttpServletResponse response) {
|
||||
try {
|
||||
Cookie c = new Cookie(key, "");
|
||||
c.setMaxAge(0);
|
||||
response.addCookie(c);
|
||||
} catch (Exception e) {
|
||||
log.error("删除cookie错误",e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取cookie
|
||||
*
|
||||
* @param key key值
|
||||
* @param request 请求
|
||||
* @return cookie value
|
||||
*/
|
||||
public static String getCookie(String key, HttpServletRequest request) {
|
||||
try {
|
||||
if (request.getCookies() == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < request.getCookies().length; i++) {
|
||||
if (request.getCookies()[i].getName().equals(key)) {
|
||||
return request.getCookies()[i].getValue();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取cookie错误",e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
137
framework/src/main/java/cn/lili/common/utils/CurrencyUtil.java
Normal file
137
framework/src/main/java/cn/lili/common/utils/CurrencyUtil.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 金额计算工具
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:40 上午
|
||||
*/
|
||||
public final class CurrencyUtil {
|
||||
/**
|
||||
* 默认除法运算精度
|
||||
*/
|
||||
private static final int DEF_DIV_SCALE = 2;
|
||||
|
||||
/**
|
||||
* 这个类不能实例化
|
||||
*/
|
||||
private CurrencyUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供精确的加法运算。
|
||||
*
|
||||
* @return 累加之和
|
||||
*/
|
||||
public static Double add(double... params) {
|
||||
BigDecimal result = new BigDecimal("0");
|
||||
for (double param : params) {
|
||||
BigDecimal bigParam = BigDecimal.valueOf(param);
|
||||
result = result.add(bigParam).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
return result.doubleValue();
|
||||
}
|
||||
/**
|
||||
* 提供精确的减法运算。
|
||||
*
|
||||
* @return 第一个参数为被减数,其余数字为减数
|
||||
*/
|
||||
public static Double sub(double... params) {
|
||||
BigDecimal result = BigDecimal.valueOf(params[0]);
|
||||
params = Arrays.stream(params).skip(1).toArray();
|
||||
for (double param : params) {
|
||||
BigDecimal bigParam = BigDecimal.valueOf(param);
|
||||
result = result.subtract(bigParam).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
return result.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供精确的乘法运算。
|
||||
*
|
||||
* @param v1 被乘数
|
||||
* @param v2 乘数
|
||||
* @return 两个参数的积
|
||||
*/
|
||||
public static Double mul(double v1, double v2) {
|
||||
BigDecimal b1 = BigDecimal.valueOf(v1);
|
||||
BigDecimal b2 = BigDecimal.valueOf(v2);
|
||||
return b1.multiply(b2).setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供精确的乘法运算。
|
||||
*
|
||||
* @param v1 被乘数
|
||||
* @param v2 乘数
|
||||
* @param scale 表示表示需要精确到小数点以后几位。
|
||||
* @return 两个参数的积
|
||||
*/
|
||||
public static Double mul(double v1, double v2, int scale) {
|
||||
if (scale < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"The scale must be a positive integer or zero");
|
||||
}
|
||||
BigDecimal b1 = BigDecimal.valueOf(v1);
|
||||
BigDecimal b2 = BigDecimal.valueOf(v2);
|
||||
return b1.multiply(b2).setScale(scale, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后10位,以后的数字四舍五入。
|
||||
*
|
||||
* @param v1 被除数
|
||||
* @param v2 除数
|
||||
* @return 两个参数的商
|
||||
*/
|
||||
public static double div(double v1, double v2) {
|
||||
return div(v1, v2, DEF_DIV_SCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
|
||||
*
|
||||
* @param v1 被除数
|
||||
* @param v2 除数
|
||||
* @param scale 表示表示需要精确到小数点以后几位。
|
||||
* @return 两个参数的商
|
||||
*/
|
||||
public static double div(double v1, double v2, int scale) {
|
||||
if (scale < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"The scale must be a positive integer or zero");
|
||||
}
|
||||
//如果被除数等于0,则返回0
|
||||
if (v2 == 0) {
|
||||
return 0;
|
||||
}
|
||||
BigDecimal b1 = BigDecimal.valueOf(v1);
|
||||
BigDecimal b2 = BigDecimal.valueOf(v2);
|
||||
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额转分
|
||||
*
|
||||
* @param money 金额
|
||||
* @return 转换单位为分
|
||||
*/
|
||||
public static Integer fen(Double money) {
|
||||
double price = mul(money, 100);
|
||||
return (int) price;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额转分
|
||||
*
|
||||
* @param money 金额
|
||||
* @return double类型分
|
||||
*/
|
||||
public static double reversalFen(Double money) {
|
||||
return div(money, 100);
|
||||
}
|
||||
}
|
||||
398
framework/src/main/java/cn/lili/common/utils/DateUtil.java
Normal file
398
framework/src/main/java/cn/lili/common/utils/DateUtil.java
Normal file
@@ -0,0 +1,398 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 日期相关的操作
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String STANDARD_DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
public static final String STANDARD_DATE_NO_UNDERLINE_FORMAT = "yyyyMMdd";
|
||||
|
||||
public static final String FULL_DATE = "yyyyMMddHHmmss";
|
||||
|
||||
|
||||
/**
|
||||
* 当天的开始时间
|
||||
*
|
||||
* @return 今天开始时间
|
||||
*/
|
||||
public static Date startOfTodDayTime() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当天的开始时间
|
||||
*
|
||||
* @param date 时间
|
||||
* @return 根据传入的时间获取开始时间
|
||||
*/
|
||||
public static Date startOfTodDayTime(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当天的开始时间
|
||||
*
|
||||
* @return 今天开始时间
|
||||
*/
|
||||
public static long startOfTodDay() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
Date date = calendar.getTime();
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当天的结束时间
|
||||
*
|
||||
* @return 今天结束时间
|
||||
*/
|
||||
public static Date endOfDate() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 23);
|
||||
calendar.set(Calendar.MINUTE, 59);
|
||||
calendar.set(Calendar.SECOND, 59);
|
||||
calendar.set(Calendar.MILLISECOND, 999);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当天的结束时间
|
||||
*
|
||||
* @param date 传入日期
|
||||
* @return 获得传入日期当天结束时间
|
||||
*/
|
||||
public static Date endOfDate(Date date) {
|
||||
if (date == null) {
|
||||
date = new Date();
|
||||
}
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 23);
|
||||
calendar.set(Calendar.MINUTE, 59);
|
||||
calendar.set(Calendar.SECOND, 59);
|
||||
calendar.set(Calendar.MILLISECOND, 999);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 某天的年月日
|
||||
*
|
||||
* @param dayUntilNow 距今多少天以前
|
||||
* @return 年月日map key为 year month day
|
||||
*/
|
||||
public static Map<String, Object> getYearMonthAndDay(int dayUntilNow) {
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>(3);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
calendar.add(Calendar.DATE, -dayUntilNow);
|
||||
map.put("year", calendar.get(Calendar.YEAR));
|
||||
map.put("month", calendar.get(Calendar.MONTH) + 1);
|
||||
map.put("day", calendar.get(Calendar.DAY_OF_MONTH));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个字符串转换成日期格式
|
||||
*
|
||||
* @param date 字符串日期
|
||||
* @param pattern 日期格式
|
||||
* @return date
|
||||
*/
|
||||
public static Date toDate(String date, String pattern) {
|
||||
if ("".equals("" + date)) {
|
||||
return null;
|
||||
}
|
||||
if (pattern == null) {
|
||||
pattern = STANDARD_DATE_FORMAT;
|
||||
}
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH);
|
||||
Date newDate = new Date();
|
||||
try {
|
||||
newDate = sdf.parse(date);
|
||||
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上个月的开始结束时间
|
||||
*
|
||||
* @return 上个月的开始结束时间
|
||||
*/
|
||||
public static Long[] getLastMonth() {
|
||||
//取得系统当前时间
|
||||
Calendar cal = Calendar.getInstance();
|
||||
int year = cal.get(Calendar.YEAR);
|
||||
int month = cal.get(Calendar.MONTH) + 1;
|
||||
|
||||
//取得系统当前时间所在月第一天时间对象
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
|
||||
//日期减一,取得上月最后一天时间对象
|
||||
cal.add(Calendar.DAY_OF_MONTH, -1);
|
||||
|
||||
//输出上月最后一天日期
|
||||
int day = cal.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
String months = "";
|
||||
String days = "";
|
||||
|
||||
if (month > 1) {
|
||||
month--;
|
||||
} else {
|
||||
year--;
|
||||
month = 12;
|
||||
}
|
||||
if (String.valueOf(month).length() <= 1) {
|
||||
months = "0" + month;
|
||||
} else {
|
||||
months = String.valueOf(month);
|
||||
}
|
||||
if (String.valueOf(day).length() <= 1) {
|
||||
days = "0" + day;
|
||||
} else {
|
||||
days = String.valueOf(day);
|
||||
}
|
||||
String firstDay = "" + year + "-" + months + "-01";
|
||||
String lastDay = "" + year + "-" + months + "-" + days + " 23:59:59";
|
||||
|
||||
Long[] lastMonth = new Long[2];
|
||||
lastMonth[0] = DateUtil.getDateline(firstDay);
|
||||
lastMonth[1] = DateUtil.getDateline(lastDay, STANDARD_FORMAT);
|
||||
|
||||
return lastMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把日期转换成字符串型
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 字符串时间
|
||||
*/
|
||||
public static String toString(Date date) {
|
||||
return toString(date, STANDARD_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把日期转换成字符串型
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 字符串时间
|
||||
*/
|
||||
public static String toString(Long date) {
|
||||
return toString(date, STANDARD_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把日期转换成字符串型
|
||||
*
|
||||
* @param date 日期
|
||||
* @param pattern 类型
|
||||
* @return 字符串时间
|
||||
*/
|
||||
public static String toString(Date date, String pattern) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
if (pattern == null) {
|
||||
pattern = STANDARD_DATE_FORMAT;
|
||||
}
|
||||
String dateString = "";
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
|
||||
try {
|
||||
dateString = sdf.format(date);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return dateString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间戳转换成时间类型
|
||||
*
|
||||
* @param time 时间戳
|
||||
* @param pattern 格式
|
||||
* @return 字符串时间
|
||||
*/
|
||||
public static String toString(Long time, String pattern) {
|
||||
if (time > 0) {
|
||||
if (time.toString().length() == 10) {
|
||||
time = time * 1000;
|
||||
}
|
||||
Date date = new Date(time);
|
||||
return DateUtil.toString(date, pattern);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否在某个时间范围
|
||||
*
|
||||
* @param start 开始时间,以秒为单位的时间戳
|
||||
* @param end 结束时间,以秒为单位的时间戳
|
||||
* @return 是否在范围内
|
||||
*/
|
||||
public static boolean inRangeOf(long start, long end) {
|
||||
long now = getDateline();
|
||||
return start <= now && end >= now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的时间戳
|
||||
*
|
||||
* @param date 指定日期
|
||||
* @return 时间戳
|
||||
*/
|
||||
public static long getDateline(String date) {
|
||||
return Objects.requireNonNull(toDate(date, STANDARD_DATE_FORMAT)).getTime() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间的时间戳
|
||||
*
|
||||
* @return 时间戳
|
||||
*/
|
||||
public static long getDateline() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间格式化字符串
|
||||
*
|
||||
* @return 时间戳
|
||||
*/
|
||||
public static String getCurrentDateStr(String format) {
|
||||
return toString(new Date(), format);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间格式化字符串
|
||||
*
|
||||
* @return 格式化的时间
|
||||
*/
|
||||
public static String getCurrentDateStr() {
|
||||
return toString(new Date(), FULL_DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据日期格式及日期获取时间戳
|
||||
*
|
||||
* @param date 日期
|
||||
* @param pattern 日期格式
|
||||
* @return 时间戳
|
||||
*/
|
||||
public static long getDateline(String date, String pattern) {
|
||||
return Objects.requireNonNull(toDate(date, pattern)).getTime() / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取几个月之前的日期时间戳
|
||||
*
|
||||
* @param beforeMonth 几个月之前
|
||||
* @return 时间戳
|
||||
*/
|
||||
public static long getBeforeMonthDateline(int beforeMonth) {
|
||||
SimpleDateFormat format = new SimpleDateFormat(STANDARD_FORMAT);
|
||||
Calendar c = Calendar.getInstance();
|
||||
|
||||
//过去一月
|
||||
c.setTime(new Date());
|
||||
c.add(Calendar.MONTH, (0 - beforeMonth));
|
||||
Date m = c.getTime();
|
||||
String mon = format.format(m);
|
||||
return getDateline(mon, STANDARD_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前天的结束时间
|
||||
*
|
||||
* @return 当前天的结束时间
|
||||
*/
|
||||
public static Date getCurrentDayEndTime() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
cal.set(Calendar.DATE, cal.get(Calendar.DATE) + 1);
|
||||
cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) - 1);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延时时间(秒)
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @return 延时时间(秒)
|
||||
*/
|
||||
public static Integer getDelayTime(Long startTime) {
|
||||
int time = Math.toIntExact((startTime - System.currentTimeMillis()) / 1000);
|
||||
//如果时间为负数则改为一秒后执行
|
||||
if (time <= 0) {
|
||||
time = 1;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年某月开始时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月
|
||||
* @return 开始时间
|
||||
*/
|
||||
public static Date getBeginTime(int year, int month) {
|
||||
YearMonth yearMonth = YearMonth.of(year, month);
|
||||
LocalDate localDate = yearMonth.atDay(1);
|
||||
LocalDateTime startOfDay = localDate.atStartOfDay();
|
||||
ZonedDateTime zonedDateTime = startOfDay.atZone(ZoneId.of("Asia/Shanghai"));
|
||||
|
||||
return Date.from(zonedDateTime.toInstant());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年某月结束时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月
|
||||
* @return 结束时间
|
||||
*/
|
||||
public static Date getEndTime(int year, int month) {
|
||||
YearMonth yearMonth = YearMonth.of(year, month);
|
||||
LocalDate endOfMonth = yearMonth.atEndOfMonth();
|
||||
LocalDateTime localDateTime = endOfMonth.atTime(23, 59, 59, 999);
|
||||
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
|
||||
return Date.from(zonedDateTime.toInstant());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.NoHttpResponseException;
|
||||
import org.apache.http.client.HttpRequestRetryHandler;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.routing.HttpRoute;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* HttpClientUtils
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:40 上午
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpClientUtils {
|
||||
|
||||
/**
|
||||
* org.apache.http.impl.client.CloseableHttpClient
|
||||
*/
|
||||
private static CloseableHttpClient httpClient = null;
|
||||
|
||||
//这里就直接默认固定了,因为以下三个参数在新建的method中仍然可以重新配置并被覆盖.
|
||||
/**
|
||||
* ms毫秒,从池中获取链接超时时间
|
||||
*/
|
||||
static final int CONNECTION_REQUEST_TIMEOUT = 30000;
|
||||
/**
|
||||
* ms毫秒,建立链接超时时间
|
||||
*/
|
||||
static final int CONNECT_TIMEOUT = 60000;
|
||||
/**
|
||||
* ms毫秒,读取超时时间
|
||||
*/
|
||||
static final int SOCKET_TIMEOUT = 60000;
|
||||
|
||||
/**
|
||||
* 总配置,主要涉及是以下两个参数,如果要作调整没有用到properties会比较后麻烦,但鉴于一经粘贴,随处可用的特点,就不再做依赖性配置化处理了.
|
||||
* 而且这个参数同一家公司基本不会变动.
|
||||
* 最大总并发,很重要的参数
|
||||
*/
|
||||
static final int MAX_TOTAL = 500;
|
||||
/**
|
||||
* 每路并发,很重要的参数
|
||||
*/
|
||||
static final int MAX_PER_ROUTE = 100;
|
||||
|
||||
/**
|
||||
* 正常情况这里应该配成MAP或LIST
|
||||
* 细化配置参数,用来对每路参数做精细化处理,可以管控各ip的流量,比如默认配置请求baidu:80端口最大100个并发链接,
|
||||
* 每个细化配置之ip(不重要,在特殊场景很有用)
|
||||
*/
|
||||
static final String DETAIL_HOST_NAME = "http://www.baidu.com";
|
||||
|
||||
/**
|
||||
* 每个细化配置之port(不重要,在特殊场景很有用)
|
||||
*/
|
||||
static final int DETAIL_PORT = 80;
|
||||
/**
|
||||
* 每个细化配置之最大并发数(不重要,在特殊场景很有用)
|
||||
*/
|
||||
static final int DETAIL_MAX_PER_ROUTE = 100;
|
||||
|
||||
private synchronized static CloseableHttpClient getHttpClient() {
|
||||
if (null == httpClient) {
|
||||
httpClient = init();
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接池初始化 这里最重要的一点理解就是. 让CloseableHttpClient 一直活在池的世界里, 但是HttpPost却一直用完就消掉.
|
||||
* 这样可以让链接一直保持着.
|
||||
*/
|
||||
private static CloseableHttpClient init() {
|
||||
CloseableHttpClient newHotpoint;
|
||||
|
||||
//设置连接池
|
||||
ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
|
||||
LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
|
||||
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", plainsf).register("https", sslsf).build();
|
||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
|
||||
//将最大连接数增加
|
||||
cm.setMaxTotal(MAX_TOTAL);
|
||||
//将每个路由基础的连接增加
|
||||
cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);
|
||||
|
||||
//细化配置开始,其实这里用Map或List的for循环来配置每个链接,在特殊场景很有用.
|
||||
//将每个路由基础的连接做特殊化配置,一般用不着
|
||||
HttpHost httpHost = new HttpHost(DETAIL_HOST_NAME, DETAIL_PORT);
|
||||
//将目标主机的最大连接数增加
|
||||
cm.setMaxPerRoute(new HttpRoute(httpHost), DETAIL_MAX_PER_ROUTE);
|
||||
//细化配置结束
|
||||
|
||||
//请求重试处理
|
||||
HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
|
||||
if (executionCount >= 2) {//如果已经重试了2次,就放弃
|
||||
return false;
|
||||
}
|
||||
if (exception instanceof NoHttpResponseException) {//如果服务器丢掉了连接,那么就重试
|
||||
return true;
|
||||
}
|
||||
if (exception instanceof SSLHandshakeException) {//不要重试SSL握手异常
|
||||
return false;
|
||||
}
|
||||
if (exception instanceof InterruptedIOException) {//超时
|
||||
return false;
|
||||
}
|
||||
if (exception instanceof UnknownHostException) {//目标服务器不可达
|
||||
return false;
|
||||
}
|
||||
if (exception instanceof SSLException) {//SSL握手异常
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpClientContext clientContext = HttpClientContext.adapt(context);
|
||||
HttpRequest request = clientContext.getRequest();
|
||||
//如果请求是幂等的,就再次尝试
|
||||
return !(request instanceof HttpEntityEnclosingRequest);
|
||||
};
|
||||
|
||||
//配置请求的超时设置
|
||||
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
|
||||
newHotpoint = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).setRetryHandler(httpRequestRetryHandler).build();
|
||||
return newHotpoint;
|
||||
}
|
||||
|
||||
public static String doGet(String url, Map<String, String> param) {
|
||||
|
||||
//httpClient
|
||||
CloseableHttpClient httpClient = getHttpClient();
|
||||
|
||||
String resultString = "";
|
||||
CloseableHttpResponse response = null;
|
||||
try {
|
||||
//创建uri
|
||||
URIBuilder builder = new URIBuilder(url);
|
||||
if (param != null) {
|
||||
for (String key : param.keySet()) {
|
||||
builder.addParameter(key, param.get(key));
|
||||
}
|
||||
}
|
||||
URI uri = builder.build();
|
||||
|
||||
//创建http GET请求
|
||||
HttpGet httpGet = new HttpGet(uri);
|
||||
|
||||
//执行请求
|
||||
response = httpClient.execute(httpGet);
|
||||
//判断返回状态是否为200
|
||||
if (response.getStatusLine().getStatusCode() == 200) {
|
||||
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("get请求错误", e);
|
||||
} finally {
|
||||
try {
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
httpClient.close();
|
||||
} catch (IOException e) {
|
||||
log.error("Get错误", e);
|
||||
}
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
}
|
||||
368
framework/src/main/java/cn/lili/common/utils/HttpUtils.java
Normal file
368
framework/src/main/java/cn/lili/common/utils/HttpUtils.java
Normal file
@@ -0,0 +1,368 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.xkcoding.http.HttpUtil;
|
||||
import com.xkcoding.http.config.HttpConfig;
|
||||
import com.xkcoding.http.support.HttpHeader;
|
||||
import com.xkcoding.http.support.httpclient.HttpClientImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* HTTP 工具类
|
||||
* @author liushuai
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpUtils {
|
||||
|
||||
public HttpUtils(HttpConfig config) {
|
||||
HttpUtil.setConfig(config);
|
||||
HttpUtil.setHttp(new HttpClientImpl());
|
||||
}
|
||||
|
||||
public HttpUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @return 结果
|
||||
*/
|
||||
public String get(String url) {
|
||||
return HttpUtil.get(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @param params 参数
|
||||
* @param header 请求头
|
||||
* @param encode 是否需要 url encode
|
||||
* @return 结果
|
||||
*/
|
||||
public String get(String url, Map<String, String> params, HttpHeader header, boolean encode) {
|
||||
return HttpUtil.get(url, params, header, encode);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @return 结果
|
||||
*/
|
||||
public String post(String url) {
|
||||
return HttpUtil.post(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @param data JSON 参数
|
||||
* @return 结果
|
||||
*/
|
||||
public String post(String url, String data) {
|
||||
return HttpUtil.post(url, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @param data JSON 参数
|
||||
* @param header 请求头
|
||||
* @return 结果
|
||||
*/
|
||||
public String post(String url, String data, HttpHeader header) {
|
||||
return HttpUtil.post(url, data, header);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @param params form 参数
|
||||
* @param encode 是否需要 url encode
|
||||
* @return 结果
|
||||
*/
|
||||
public String post(String url, Map<String, String> params, boolean encode) {
|
||||
return HttpUtil.post(url, params, encode);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url URL
|
||||
* @param params form 参数
|
||||
* @param header 请求头
|
||||
* @param encode 是否需要 url encode
|
||||
* @return 结果
|
||||
*/
|
||||
public String post(String url, Map<String, String> params, HttpHeader header, boolean encode) {
|
||||
return HttpUtil.post(url, params, header, encode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 静态方法运行参数
|
||||
*/
|
||||
public static final int HTTP_CONN_TIMEOUT = 100000;
|
||||
public static final int HTTP_SOCKET_TIMEOUT = 100000;
|
||||
|
||||
/**
|
||||
* POST 静态方法请求
|
||||
*
|
||||
* @param reqUrl
|
||||
* @param parameters
|
||||
* @param encoding
|
||||
* @param connectTimeout
|
||||
* @param readTimeout
|
||||
* @return
|
||||
*/
|
||||
public static String doPost(String reqUrl, Map<String, String> parameters, String encoding, int connectTimeout,
|
||||
int readTimeout) {
|
||||
HttpURLConnection urlConn = null;
|
||||
try {
|
||||
urlConn = sendPost(reqUrl, parameters, encoding, connectTimeout, readTimeout);
|
||||
String responseContent = getContent(urlConn, encoding);
|
||||
return responseContent.trim();
|
||||
} finally {
|
||||
if (urlConn != null) {
|
||||
urlConn.disconnect();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* post携带json请求 静态方法
|
||||
*
|
||||
* @param reqUrl 请求地址
|
||||
* @param jsonParameters 参数
|
||||
* @return
|
||||
*/
|
||||
public static String doPostWithJson(String reqUrl, Map<String, String> jsonParameters) {
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
//创建连接
|
||||
URL url = new URL(reqUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
//设置请求方式
|
||||
connection.setRequestMethod("POST");
|
||||
//设置发送数据的格式
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.connect();
|
||||
//一定要用BufferedReader 来接收响应, 使用字节来接收响应的方法是接收不到内容的
|
||||
//utf-8编码
|
||||
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);
|
||||
out.append(JSONObject.toJSONString(jsonParameters));
|
||||
out.flush();
|
||||
out.close();
|
||||
//读取响应
|
||||
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
String res = "";
|
||||
while ((line = reader.readLine()) != null) {
|
||||
res += line;
|
||||
}
|
||||
reader.close();
|
||||
|
||||
return res;
|
||||
} catch (IOException e) {
|
||||
log.error("post请求错误", e);
|
||||
}
|
||||
//自定义错误信息
|
||||
return "error";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* post携带json请求 静态方法
|
||||
*
|
||||
* @param reqUrl 请求地址
|
||||
* @param object 对象
|
||||
* @return
|
||||
*/
|
||||
public static String doPostWithJson(String reqUrl, Object object) {
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
//创建连接
|
||||
URL url = new URL(reqUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
//设置请求方式
|
||||
connection.setRequestMethod("POST");
|
||||
//设置发送数据的格式
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.connect();
|
||||
//一定要用BufferedReader 来接收响应, 使用字节来接收响应的方法是接收不到内容的
|
||||
//utf-8编码
|
||||
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);
|
||||
out.append(JSONObject.toJSONString(object));
|
||||
out.flush();
|
||||
out.close();
|
||||
//读取响应
|
||||
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
String res = "";
|
||||
while ((line = reader.readLine()) != null) {
|
||||
res += line;
|
||||
}
|
||||
reader.close();
|
||||
|
||||
return res;
|
||||
} catch (IOException e) {
|
||||
log.error("post错误", e);
|
||||
}
|
||||
//自定义错误信息
|
||||
return "error";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送post请求
|
||||
*
|
||||
* @param reqUrl
|
||||
* @param parameters
|
||||
* @param encoding
|
||||
* @param connectTimeout
|
||||
* @param readTimeout
|
||||
* @return
|
||||
*/
|
||||
private static HttpURLConnection sendPost(String reqUrl,
|
||||
Map<String, String> parameters, String encoding, int connectTimeout, int readTimeout) {
|
||||
HttpURLConnection urlConn = null;
|
||||
try {
|
||||
String params = generatorParamString(parameters, encoding);
|
||||
URL url = new URL(reqUrl);
|
||||
urlConn = (HttpURLConnection) url.openConnection();
|
||||
urlConn.setRequestMethod("POST");
|
||||
//(单位:毫秒)jdk
|
||||
urlConn.setConnectTimeout(connectTimeout);
|
||||
//(单位:毫秒)jdk 1.5换成这个,读操作超时
|
||||
urlConn.setReadTimeout(readTimeout);
|
||||
urlConn.setDoOutput(true);
|
||||
//String按照字节处理是一个好方法
|
||||
byte[] b = params.getBytes(encoding);
|
||||
urlConn.getOutputStream().write(b, 0, b.length);
|
||||
urlConn.getOutputStream().flush();
|
||||
urlConn.getOutputStream().close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
return urlConn;
|
||||
}
|
||||
|
||||
/**
|
||||
* get 请求 静态方法
|
||||
*
|
||||
* @param urlConn
|
||||
* @param encoding
|
||||
* @return
|
||||
*/
|
||||
private static String getContent(HttpURLConnection urlConn, String encoding) {
|
||||
try {
|
||||
String responseContent = null;
|
||||
InputStream in = urlConn.getInputStream();
|
||||
BufferedReader rd = new BufferedReader(new InputStreamReader(in, encoding));
|
||||
String tempLine = rd.readLine();
|
||||
StringBuffer tempStr = new StringBuffer();
|
||||
String crlf = System.getProperty("line.separator");
|
||||
while (tempLine != null) {
|
||||
tempStr.append(tempLine);
|
||||
tempStr.append(crlf);
|
||||
tempLine = rd.readLine();
|
||||
}
|
||||
responseContent = tempStr.toString();
|
||||
rd.close();
|
||||
in.close();
|
||||
return responseContent;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get 请求 静态方法
|
||||
*
|
||||
* @param link
|
||||
* @param encoding
|
||||
* @return
|
||||
*/
|
||||
public static String doGet(String link, String encoding, int connectTimeout, int readTimeout) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(link);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setConnectTimeout(connectTimeout);
|
||||
conn.setReadTimeout(readTimeout);
|
||||
BufferedInputStream in = new BufferedInputStream(
|
||||
conn.getInputStream());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[1024];
|
||||
for (int i = 0; (i = in.read(buf)) > 0; ) {
|
||||
out.write(buf, 0, i);
|
||||
}
|
||||
out.flush();
|
||||
String s = out.toString(encoding);
|
||||
return s;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
conn = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将parameters中数据转换成用"&"链接的http请求参数形式
|
||||
*
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
private static String generatorParamString(Map<String, String> parameters, String encoding) {
|
||||
StringBuffer params = new StringBuffer();
|
||||
if (parameters != null) {
|
||||
for (Iterator<String> iter = parameters.keySet().iterator(); iter
|
||||
.hasNext(); ) {
|
||||
String name = iter.next();
|
||||
String value = parameters.get(name);
|
||||
params.append(name + "=");
|
||||
try {
|
||||
params.append(URLEncoder.encode(value, encoding));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
String message = String.format("'%s'='%s'", name, value);
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
if (iter.hasNext()) {
|
||||
params.append("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toString();
|
||||
}
|
||||
}
|
||||
78
framework/src/main/java/cn/lili/common/utils/IpHelper.java
Normal file
78
framework/src/main/java/cn/lili/common/utils/IpHelper.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* ip工具
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class IpHelper {
|
||||
|
||||
/**
|
||||
* qq lbs 地区查询key
|
||||
*/
|
||||
@Value("${lili.lbs.key}")
|
||||
private String key;
|
||||
/**
|
||||
* qq lbs 地区查询key
|
||||
*/
|
||||
@Value("${lili.lbs.sk}")
|
||||
private String sk;
|
||||
|
||||
private static final String API = "https://apis.map.qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 获取IP返回地理信息
|
||||
*
|
||||
* @param request 请求参数
|
||||
* @return 城市信息
|
||||
*/
|
||||
public String getIpCity(HttpServletRequest request) {
|
||||
|
||||
String url = "/ws/location/v1/ip?key=" + key + "&ip=" + IpUtils.getIpAddress(request);
|
||||
String sign = SecureUtil.md5(url + sk);
|
||||
url = API + url + "&sign=" + sign;
|
||||
String result = "未知";
|
||||
try {
|
||||
String json = HttpUtil.get(url, 3000);
|
||||
JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
|
||||
String status = jsonObject.get("status").getAsString();
|
||||
if ("0".equals(status)) {
|
||||
JsonObject address = jsonObject.get("result").getAsJsonObject().get("ad_info").getAsJsonObject();
|
||||
String nation = address.get("nation").getAsString();
|
||||
String province = address.get("province").getAsString();
|
||||
String city = address.get("city").getAsString();
|
||||
String district = address.get("district").getAsString();
|
||||
if (StrUtil.isNotBlank(nation) && StrUtil.isBlank(province)) {
|
||||
result = nation;
|
||||
} else {
|
||||
result = province;
|
||||
if (StrUtil.isNotBlank(city)) {
|
||||
result += " " + city;
|
||||
}
|
||||
if (StrUtil.isNotBlank(district)) {
|
||||
result += " " + district;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("获取IP地理信息失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
67
framework/src/main/java/cn/lili/common/utils/IpUtils.java
Normal file
67
framework/src/main/java/cn/lili/common/utils/IpUtils.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* IpUtils
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-12-08 15:32
|
||||
*/
|
||||
@Slf4j
|
||||
public class IpUtils {
|
||||
|
||||
/**
|
||||
* 获取本机IP
|
||||
*
|
||||
* @return ip
|
||||
*/
|
||||
public static String getLocalIp() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("获取本机IP错误",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*
|
||||
* @param request 请求
|
||||
* @return
|
||||
*/
|
||||
public static String getIpAddress(HttpServletRequest request) {
|
||||
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
||||
if (ip != null && ip.length() > 15) {
|
||||
if (ip.indexOf(",") > 0) {
|
||||
ip = ip.substring(0, ip.indexOf(","));
|
||||
}
|
||||
}
|
||||
if ("0:0:0:0:0:0:0:1".equals(ip)) {
|
||||
ip = "127.0.0.1";
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(IpUtils.getLocalIp());
|
||||
}
|
||||
}
|
||||
62
framework/src/main/java/cn/lili/common/utils/JasyptUtil.java
Normal file
62
framework/src/main/java/cn/lili/common/utils/JasyptUtil.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
|
||||
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 加密解密
|
||||
* @author Chopper
|
||||
*/
|
||||
public class JasyptUtil {
|
||||
|
||||
/**
|
||||
* Jasypt生成加密结果
|
||||
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
|
||||
* @param value 待加密值
|
||||
* @return 加密字符串
|
||||
*/
|
||||
public static String encryptPwd(String password, String value){
|
||||
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
|
||||
encryptor.setConfig(encryptor(password));
|
||||
return encryptor.encrypt(value);
|
||||
}
|
||||
/**
|
||||
* 解密
|
||||
* @param password 配置文件中设定的加密密码 jasypt.encryptor.password
|
||||
* @param value 待解密密文
|
||||
* @return 揭秘字符串
|
||||
*/
|
||||
public static String decryptPwd(String password, String value){
|
||||
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
|
||||
encryptor.setConfig(encryptor(password));
|
||||
encryptor.decrypt(value);
|
||||
return encryptor.decrypt(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密处理
|
||||
* @param password 密码
|
||||
* @return 加密字符串
|
||||
*/
|
||||
public static SimpleStringPBEConfig encryptor(String password){
|
||||
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
|
||||
config.setPassword(password);
|
||||
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
|
||||
config.setKeyObtentionIterations("1000");
|
||||
config.setPoolSize(1);
|
||||
config.setProviderName("SunJCE");
|
||||
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
|
||||
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
|
||||
config.setStringOutputType("base64");
|
||||
return config;
|
||||
}
|
||||
|
||||
public static void main(String[] args){
|
||||
|
||||
//加密 若修改了第一个参数加密password记得在配置文件同步修改
|
||||
System.out.println(encryptPwd("jasypt.encryptor.password","123456"));
|
||||
//解密
|
||||
System.out.println(decryptPwd("jasypt.encryptor.password","PYVnAYh+j5C3jkMV1d+myj6JzDaUk7pcfTWUaYsvQdEVkuvIVf7Y0mOU9XkffxT8"));
|
||||
}
|
||||
}
|
||||
57
framework/src/main/java/cn/lili/common/utils/ObjectUtil.java
Normal file
57
framework/src/main/java/cn/lili/common/utils/ObjectUtil.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.cglib.beans.BeanMap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 对象转换工具
|
||||
* @author Chopper
|
||||
*/
|
||||
public class ObjectUtil {
|
||||
|
||||
public static String mapToString(Map<String, String[]> paramMap){
|
||||
|
||||
if (paramMap == null) {
|
||||
return "";
|
||||
}
|
||||
Map<String, Object> params = new HashMap<>(16);
|
||||
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
|
||||
|
||||
String key = param.getKey();
|
||||
String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
|
||||
String obj = StrUtil.endWithIgnoreCase(param.getKey(), "password") ? "******" : paramValue;
|
||||
params.put(key,obj);
|
||||
}
|
||||
return new Gson().toJson(params);
|
||||
}
|
||||
|
||||
public static String mapToStringAll(Map<String, String[]> paramMap){
|
||||
|
||||
if (paramMap == null) {
|
||||
return "";
|
||||
}
|
||||
Map<String, Object> params = new HashMap<>(16);
|
||||
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
|
||||
|
||||
String key = param.getKey();
|
||||
String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : "");
|
||||
params.put(key, paramValue);
|
||||
}
|
||||
return new Gson().toJson(params);
|
||||
}
|
||||
|
||||
public static <T> Map<String, Object> beanToMap(T bean) {
|
||||
Map<String, Object> map = new HashMap<>(16);
|
||||
if (bean != null) {
|
||||
BeanMap beanMap = BeanMap.create(bean);
|
||||
for (Object key : beanMap.keySet()) {
|
||||
map.put(key+"", beanMap.get(key));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 用户名验证工具类
|
||||
* @author Chopper
|
||||
*/
|
||||
public class RegularUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private static final Pattern MOBILE = Pattern.compile("^1[3|4|5|8][0-9]\\d{8}$");
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private static final Pattern EMAIL = Pattern.compile("^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$");
|
||||
|
||||
public static boolean mobile(String v){
|
||||
|
||||
Matcher m = MOBILE.matcher(v);
|
||||
if(m.matches()){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean email(String v){
|
||||
|
||||
Matcher m = EMAIL.matcher(v);
|
||||
if(m.matches()){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
125
framework/src/main/java/cn/lili/common/utils/ResponseUtil.java
Normal file
125
framework/src/main/java/cn/lili/common/utils/ResponseUtil.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* response 输出响应工具
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Slf4j
|
||||
public class ResponseUtil {
|
||||
|
||||
static final String ENCODING = "UTF-8";
|
||||
static final String CONTENT_TYPE = "application/json;charset=UTF-8";
|
||||
|
||||
/**
|
||||
* 输出前端内容以及状态指定
|
||||
*
|
||||
* @param response
|
||||
* @param status
|
||||
* @param content
|
||||
*/
|
||||
public static void output(HttpServletResponse response, Integer status, String content) {
|
||||
ServletOutputStream servletOutputStream = null;
|
||||
try {
|
||||
response.setCharacterEncoding(ENCODING);
|
||||
response.setContentType(CONTENT_TYPE);
|
||||
response.setStatus(status);
|
||||
servletOutputStream = response.getOutputStream();
|
||||
servletOutputStream.write(content.getBytes());
|
||||
} catch (Exception e) {
|
||||
log.error("response output error:", e);
|
||||
} finally {
|
||||
if (servletOutputStream != null) {
|
||||
try {
|
||||
servletOutputStream.flush();
|
||||
servletOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("response output IO close error:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* response 输出JSON
|
||||
*
|
||||
* @param response
|
||||
* @param status response 状态
|
||||
* @param resultMap
|
||||
*/
|
||||
public static void output(HttpServletResponse response, Integer status, Map<String, Object> resultMap) {
|
||||
response.setStatus(status);
|
||||
output(response, resultMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* response 输出JSON
|
||||
*
|
||||
* @param response
|
||||
* @param resultMap
|
||||
*/
|
||||
public static void output(HttpServletResponse response, Map<String, Object> resultMap) {
|
||||
ServletOutputStream servletOutputStream = null;
|
||||
try {
|
||||
response.setCharacterEncoding(ENCODING);
|
||||
response.setContentType(CONTENT_TYPE);
|
||||
servletOutputStream = response.getOutputStream();
|
||||
servletOutputStream.write(new Gson().toJson(resultMap).getBytes());
|
||||
} catch (Exception e) {
|
||||
log.error("response output error:", e);
|
||||
} finally {
|
||||
if (servletOutputStream != null) {
|
||||
try {
|
||||
servletOutputStream.flush();
|
||||
servletOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("response output IO close error:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造响应
|
||||
*
|
||||
* @param flag
|
||||
* @param code
|
||||
* @param msg
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> resultMap(boolean flag, Integer code, String msg) {
|
||||
return resultMap(flag, code, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造响应
|
||||
*
|
||||
* @param flag
|
||||
* @param code
|
||||
* @param msg
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> resultMap(boolean flag, Integer code, String msg, Object data) {
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<String, Object>(16);
|
||||
resultMap.put("success", flag);
|
||||
resultMap.put("message", msg);
|
||||
resultMap.put("code", code);
|
||||
resultMap.put("timestamp", System.currentTimeMillis());
|
||||
if (data != null) {
|
||||
resultMap.put("result", data);
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
}
|
||||
55
framework/src/main/java/cn/lili/common/utils/SnowFlake.java
Normal file
55
framework/src/main/java/cn/lili/common/utils/SnowFlake.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 雪花分布式id获取
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Slf4j
|
||||
public class SnowFlake {
|
||||
|
||||
// /**
|
||||
// * 机器id
|
||||
// */
|
||||
// private static long workerId = 0L;
|
||||
// /**
|
||||
// * 机房id
|
||||
// */
|
||||
// public static long datacenterId = 0L;
|
||||
|
||||
private static Snowflake snowflake;
|
||||
|
||||
/**
|
||||
* 初始化配置
|
||||
*
|
||||
* @param workerId
|
||||
* @param datacenterId
|
||||
*/
|
||||
public static void initialize(long workerId, long datacenterId) {
|
||||
snowflake = IdUtil.getSnowflake(workerId, datacenterId);
|
||||
}
|
||||
|
||||
public static long getId() {
|
||||
return snowflake.nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字符,带有前缀
|
||||
*
|
||||
* @param prefix
|
||||
* @return
|
||||
*/
|
||||
public static String createStr(String prefix) {
|
||||
return prefix + DateUtil.toString(new Date(), "yyyyMMdd") + SnowFlake.getId();
|
||||
}
|
||||
|
||||
public static String getIdStr() {
|
||||
return snowflake.nextId() + "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import cn.lili.cache.Cache;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* SnowflakeInitiator
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2022-01-14 14:04
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SnowflakeInitiator {
|
||||
|
||||
/**
|
||||
* 缓存前缀
|
||||
*/
|
||||
private static final String KEY = "{Snowflake}";
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
/**
|
||||
* 尝试初始化
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Long num = cache.incr(KEY);
|
||||
long dataCenter = num / 32;
|
||||
long workedId = num % 32;
|
||||
//如果数据中心大于32,则抹除缓存,从头开始
|
||||
if (dataCenter >= 32) {
|
||||
cache.remove(KEY);
|
||||
num = cache.incr(KEY);
|
||||
dataCenter = num / 32;
|
||||
workedId = num % 32;
|
||||
}
|
||||
SnowFlake.initialize(workedId, dataCenter);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SnowFlake.initialize(0, 8);
|
||||
|
||||
System.out.println(SnowFlake.getId());
|
||||
}
|
||||
}
|
||||
81
framework/src/main/java/cn/lili/common/utils/SpelUtil.java
Normal file
81
framework/src/main/java/cn/lili/common/utils/SpelUtil.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* SpelUtil
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-01-11 10:45
|
||||
*/
|
||||
public class SpelUtil {
|
||||
|
||||
|
||||
/**
|
||||
* spel表达式解析器
|
||||
*/
|
||||
private static SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
/**
|
||||
* 参数名发现器
|
||||
*/
|
||||
private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
/**
|
||||
* 转换 jspl参数
|
||||
*
|
||||
* @param joinPoint
|
||||
* @param spel
|
||||
* @return
|
||||
*/
|
||||
public static String compileParams(JoinPoint joinPoint, String spel) { //Spel表达式解析日志信息
|
||||
//获得方法参数名数组
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod());
|
||||
if (parameterNames != null && parameterNames.length > 0) {
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
|
||||
//获取方法参数值
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
//替换spel里的变量值为实际值, 比如 #user --> user对象
|
||||
context.setVariable(parameterNames[i], args[i]);
|
||||
}
|
||||
return spelExpressionParser.parseExpression(spel).getValue(context).toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 jspl参数
|
||||
*
|
||||
* @param joinPoint
|
||||
* @param spel
|
||||
* @return
|
||||
*/
|
||||
public static String compileParams(JoinPoint joinPoint, Object rvt, String spel) { //Spel表达式解析日志信息
|
||||
//获得方法参数名数组
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod());
|
||||
if (parameterNames != null && parameterNames.length > 0) {
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
|
||||
//获取方法参数值
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
//替换spel里的变量值为实际值, 比如 #user --> user对象
|
||||
context.setVariable(parameterNames[i], args[i]);
|
||||
}
|
||||
context.setVariable("rvt", rvt);
|
||||
return spelExpressionParser.parseExpression(spel).getValue(context).toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Chopper
|
||||
*/
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
* 上下文对象实例
|
||||
*/
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
|
||||
applicationContext = appContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取applicationContext
|
||||
* @return spring applicationContext
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name获取 Bean.
|
||||
* @param name bean的名字
|
||||
* @return bean实例
|
||||
*/
|
||||
public static Object getBean(String name){
|
||||
return getApplicationContext().getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过class获取Bean.
|
||||
* @param clazz bean的类型
|
||||
* @param <T> bean的类型
|
||||
* @return bean实例
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clazz){
|
||||
return getApplicationContext().getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name,以及Clazz返回指定的Bean
|
||||
* @param name bean的名字
|
||||
* @param clazz bean的类型
|
||||
* @param <T> bean的类型
|
||||
* @return bean实例
|
||||
*/
|
||||
public static <T> T getBean(String name,Class<T> clazz){
|
||||
return getApplicationContext().getBean(name, clazz);
|
||||
}
|
||||
|
||||
}
|
||||
181
framework/src/main/java/cn/lili/common/utils/StringUtils.java
Normal file
181
framework/src/main/java/cn/lili/common/utils/StringUtils.java
Normal file
@@ -0,0 +1,181 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 字串工具类
|
||||
*
|
||||
* @author pikachu
|
||||
*/
|
||||
public class StringUtils extends StrUtil {
|
||||
|
||||
/**
|
||||
* MD5加密方法
|
||||
*
|
||||
* @param str String
|
||||
* @return String
|
||||
*/
|
||||
public static String md5(String str) {
|
||||
MessageDigest messageDigest = null;
|
||||
try {
|
||||
messageDigest = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
byte[] resultByte = messageDigest.digest(str.getBytes());
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (int i = 0; i < resultByte.length; ++i) {
|
||||
int v = 0xFF & resultByte[i];
|
||||
if (v < 16) {
|
||||
result.append("0");
|
||||
}
|
||||
result.append(Integer.toHexString(v));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机数
|
||||
*
|
||||
* @param n 随机次数
|
||||
* @return
|
||||
*/
|
||||
public static String getRandStr(int n) {
|
||||
Random random = new Random();
|
||||
String sRand = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
String rand = String.valueOf(random.nextInt(10));
|
||||
sRand += rand;
|
||||
}
|
||||
return sRand;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切个字符串,如果超出长度则切割
|
||||
*
|
||||
* @param var
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
public static String subStringLength(String var, Integer size) {
|
||||
if (var.length() > size) {
|
||||
return var.substring(0, (size - 4)).toString() + "...";
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转map
|
||||
*
|
||||
* @param obj
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Map<String, Object> objectToMap(Object obj) throws Exception {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> map = new HashMap<String, Object>(16);
|
||||
|
||||
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
|
||||
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
|
||||
for (PropertyDescriptor property : propertyDescriptors) {
|
||||
String key = property.getName();
|
||||
if (key.compareToIgnoreCase("class") == 0) {
|
||||
continue;
|
||||
}
|
||||
Method getter = property.getReadMethod();
|
||||
Object value = getter != null ? getter.invoke(obj) : null;
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 驼峰法转下划线
|
||||
*/
|
||||
public static String camel2Underline(String str) {
|
||||
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return "";
|
||||
}
|
||||
if (str.length() == 1) {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 1; i < str.length(); i++) {
|
||||
if (Character.isUpperCase(str.charAt(i))) {
|
||||
sb.append("_" + Character.toLowerCase(str.charAt(i)));
|
||||
} else {
|
||||
sb.append(str.charAt(i));
|
||||
}
|
||||
}
|
||||
return (str.charAt(0) + sb.toString()).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr};
|
||||
* 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise}
|
||||
*
|
||||
* @param str 给定的字符串
|
||||
* @param appendStr 需要追加的内容
|
||||
* @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容
|
||||
* @return 追加后的字符串
|
||||
*/
|
||||
public static String appendIfNotContain(String str, String appendStr, String otherwise) {
|
||||
if (isEmpty(str) || isEmpty(appendStr)) {
|
||||
return str;
|
||||
}
|
||||
if (str.contains(appendStr)) {
|
||||
return str.concat(otherwise);
|
||||
}
|
||||
return str.concat(appendStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤特殊字符串
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static String filterSpecialChart(String str) {
|
||||
String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
|
||||
Pattern p = Pattern.compile(regEx);
|
||||
Matcher m = p.matcher(str);
|
||||
return m.replaceAll("").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* double 转价格字符串
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String toFen(Double doubleValue) {
|
||||
String str = doubleValue.toString();
|
||||
|
||||
if (!str.contains(".")) {
|
||||
str = str + ".00";
|
||||
} else if (str.substring(str.indexOf(".")).length() == 2) {
|
||||
str = str + "0";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @author Chopper
|
||||
*/
|
||||
public class ThreadPoolUtil {
|
||||
|
||||
/**
|
||||
* 核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
|
||||
*/
|
||||
private static final int SIZE_CORE_POOL = 5;
|
||||
/**
|
||||
* 线程池维护线程的最大数量
|
||||
*/
|
||||
private static final int SIZE_MAX_POOL = 10;
|
||||
/**
|
||||
* 线程池维护线程所允许的空闲时间
|
||||
*/
|
||||
private static final long ALIVE_TIME = 2000;
|
||||
/**
|
||||
* 线程缓冲队列
|
||||
*/
|
||||
private static final BlockingQueue<Runnable> BQUEUE = new ArrayBlockingQueue<Runnable>(100);
|
||||
private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, BQUEUE, new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
/**
|
||||
* volatile禁止指令重排
|
||||
*/
|
||||
public static volatile ThreadPoolExecutor threadPool;
|
||||
|
||||
static {
|
||||
POOL.prestartAllCoreThreads();
|
||||
}
|
||||
|
||||
/**
|
||||
* 无返回值直接执行, 管他娘的
|
||||
*
|
||||
* @param runnable
|
||||
*/
|
||||
public static void execute(Runnable runnable) {
|
||||
getThreadPool().execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回值直接执行, 管他娘的
|
||||
*
|
||||
* @param callable
|
||||
*/
|
||||
public static <T> Future<T> submit(Callable<T> callable) {
|
||||
return getThreadPool().submit(callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* DCL获取线程池
|
||||
*
|
||||
* @return 线程池对象
|
||||
*/
|
||||
public static ThreadPoolExecutor getThreadPool() {
|
||||
if (threadPool != null) {
|
||||
return threadPool;
|
||||
}
|
||||
synchronized (ThreadPoolUtil.class) {
|
||||
if (threadPool == null) {
|
||||
threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
|
||||
}
|
||||
}
|
||||
return threadPool;
|
||||
}
|
||||
|
||||
public static ThreadPoolExecutor getPool() {
|
||||
return POOL;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(POOL.getPoolSize());
|
||||
}
|
||||
}
|
||||
102
framework/src/main/java/cn/lili/common/utils/UrlBuilder.java
Normal file
102
framework/src/main/java/cn/lili/common/utils/UrlBuilder.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import com.xkcoding.http.util.MapUtil;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 构造URL
|
||||
* </p>
|
||||
*
|
||||
* @author yangkai.shen (https://xkcoding.com)
|
||||
* @since 1.9.0
|
||||
*/
|
||||
@Setter
|
||||
public class UrlBuilder {
|
||||
|
||||
private final Map<String, String> params = new LinkedHashMap<>(7);
|
||||
private String baseUrl;
|
||||
|
||||
private UrlBuilder() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseUrl 基础路径
|
||||
* @return the new {@code UrlBuilder}
|
||||
*/
|
||||
public static UrlBuilder fromBaseUrl(String baseUrl) {
|
||||
UrlBuilder builder = new UrlBuilder();
|
||||
builder.setBaseUrl(baseUrl);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只读的参数Map
|
||||
*
|
||||
* @return unmodifiable Map
|
||||
* @since 1.15.0
|
||||
*/
|
||||
public Map<String, Object> getReadOnlyParams() {
|
||||
return Collections.unmodifiableMap(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数
|
||||
*
|
||||
* @param key 参数名称
|
||||
* @param value 参数值
|
||||
* @return this UrlBuilder
|
||||
*/
|
||||
public UrlBuilder queryParam(String key, Object value) {
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
throw new RuntimeException("参数名不能为空");
|
||||
}
|
||||
String valueAsString = (value != null ? value.toString() : null);
|
||||
this.params.put(key, valueAsString);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数
|
||||
*
|
||||
* @param value 参数值
|
||||
* @return this UrlBuilder
|
||||
*/
|
||||
public UrlBuilder pathAppend(String value) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new RuntimeException("参数不能为空");
|
||||
}
|
||||
this.setBaseUrl(this.baseUrl += value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造url
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
public String build() {
|
||||
return this.build(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造url
|
||||
*
|
||||
* @param encode 转码
|
||||
* @return url
|
||||
*/
|
||||
public String build(boolean encode) {
|
||||
if (MapUtil.isEmpty(this.params)) {
|
||||
return this.baseUrl;
|
||||
}
|
||||
String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&");
|
||||
String paramString = MapUtil.parseMapToString(this.params, encode);
|
||||
return baseUrl + paramString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
|
||||
import eu.bitwalker.useragentutils.DeviceType;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 客户端类型统计
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2021-02-26 10:53
|
||||
*/
|
||||
public class UserAgentUtils {
|
||||
|
||||
/**
|
||||
* 获取设备类型
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static DeviceType getDeviceType(HttpServletRequest request) {
|
||||
return getUserAgent(request).getOperatingSystem().getDeviceType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是PC
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static boolean isComputer(HttpServletRequest request) {
|
||||
return DeviceType.COMPUTER.equals(getDeviceType(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是手机
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static boolean isMobile(HttpServletRequest request) {
|
||||
return DeviceType.MOBILE.equals(getDeviceType(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是平板
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static boolean isTablet(HttpServletRequest request) {
|
||||
return DeviceType.TABLET.equals(getDeviceType(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是手机和平板
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static boolean isMobileOrTablet(HttpServletRequest request) {
|
||||
DeviceType deviceType = getDeviceType(request);
|
||||
return DeviceType.MOBILE.equals(deviceType) || DeviceType.TABLET.equals(deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户代理对象
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static UserAgent getUserAgent(HttpServletRequest request) {
|
||||
return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
65
framework/src/main/java/cn/lili/common/utils/UuidUtils.java
Normal file
65
framework/src/main/java/cn/lili/common/utils/UuidUtils.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package cn.lili.common.utils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 高性能的创建UUID的工具类,https://github.com/lets-mica/mica
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @since 1.9.3
|
||||
*/
|
||||
public class UuidUtils {
|
||||
|
||||
/**
|
||||
* All possible chars for representing a number as a String
|
||||
* copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/NumberUtil.java#L113
|
||||
*/
|
||||
private final static byte[] DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'a', 'b',
|
||||
'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F',
|
||||
'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R',
|
||||
'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z'
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成uuid,采用 jdk 9 的形式,优化性能
|
||||
* copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335
|
||||
* <p>
|
||||
* 关于mica uuid生成方式的压测结果,可以参考:https://github.com/lets-mica/mica-jmh/wiki/uuid
|
||||
*
|
||||
* @return UUID
|
||||
*/
|
||||
public static String getUUID() {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
long lsb = random.nextLong();
|
||||
long msb = random.nextLong();
|
||||
byte[] buf = new byte[32];
|
||||
formatUnsignedLong(lsb, buf, 20, 12);
|
||||
formatUnsignedLong(lsb >>> 48, buf, 16, 4);
|
||||
formatUnsignedLong(msb, buf, 12, 4);
|
||||
formatUnsignedLong(msb >>> 16, buf, 8, 4);
|
||||
formatUnsignedLong(msb >>> 32, buf, 0, 8);
|
||||
return new String(buf, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L348
|
||||
*/
|
||||
private static void formatUnsignedLong(long val, byte[] buf, int offset, int len) {
|
||||
int charPos = offset + len;
|
||||
int radix = 1 << 4;
|
||||
int mask = radix - 1;
|
||||
do {
|
||||
buf[--charPos] = DIGITS[((int) val) & mask];
|
||||
val >>>= 4;
|
||||
} while (charPos > offset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.lili.common.validation;
|
||||
|
||||
import cn.lili.common.validation.impl.EnumValuesValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 枚举值校验注解
|
||||
*
|
||||
* @author Bulbasaur
|
||||
*/
|
||||
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
@Constraint(validatedBy = {EnumValuesValidator.class})
|
||||
public @interface EnumValue {
|
||||
|
||||
String message() default "必须为指定值";
|
||||
|
||||
String[] strValues() default {};
|
||||
|
||||
int[] intValues() default {};
|
||||
|
||||
//分组
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
//负载
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
|
||||
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
@interface List {
|
||||
EnumValue[] value();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.lili.common.validation;
|
||||
|
||||
import cn.lili.common.validation.impl.MobileValidator;
|
||||
import cn.lili.common.validation.impl.PhoneValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 电话号码校验注解
|
||||
*
|
||||
* @author Bulbasaur
|
||||
*/
|
||||
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
@Constraint(validatedBy = {MobileValidator.class})
|
||||
public @interface Mobile {
|
||||
|
||||
String regexp() default "";
|
||||
|
||||
String message() default "电话号码格式不正确";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
32
framework/src/main/java/cn/lili/common/validation/Phone.java
Normal file
32
framework/src/main/java/cn/lili/common/validation/Phone.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package cn.lili.common.validation;
|
||||
|
||||
import cn.lili.common.validation.impl.PhoneValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 手机号码校验注解
|
||||
*
|
||||
* @author Bulbasaur
|
||||
*/
|
||||
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
@Constraint(validatedBy = {PhoneValidator.class})
|
||||
public @interface Phone {
|
||||
|
||||
String regexp() default "1[3|4|5|7|8]\\d{9}";
|
||||
|
||||
String message() default "手机号码格式不正确";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.lili.common.validation.impl;
|
||||
|
||||
import cn.lili.common.validation.EnumValue;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* 枚举之校验
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:41 上午
|
||||
*/
|
||||
public class EnumValuesValidator implements ConstraintValidator<EnumValue, Object> {
|
||||
|
||||
private String[] strValues;
|
||||
private int[] intValues;
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
|
||||
if (o instanceof String) {
|
||||
for (String s : strValues) {
|
||||
if (s.equals(o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (o instanceof Integer) {
|
||||
for (int s : intValues) {
|
||||
if (s == ((Integer) o).intValue()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(EnumValue constraintAnnotation) {
|
||||
strValues = constraintAnnotation.strValues();
|
||||
intValues = constraintAnnotation.intValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.lili.common.validation.impl;
|
||||
|
||||
import cn.lili.common.validation.Mobile;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 电话校验
|
||||
* 支持手机号+电话同时校验
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:41 上午
|
||||
*/
|
||||
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||
|
||||
private static final Pattern PHONE = Pattern.compile("^0?(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])[0-9]{8}$");
|
||||
private static final Pattern AREA_MOBILE = Pattern.compile("0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}");
|
||||
private static final Pattern MOBILE = Pattern.compile("^[1-9]{1}[0-9]{5,8}$");
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
Matcher m = null;
|
||||
// 验证手机号
|
||||
if (value.length() == 11) {
|
||||
m = PHONE.matcher(value);
|
||||
// 验证带区号的电话
|
||||
} else if (value.length() > 9) {
|
||||
m = AREA_MOBILE.matcher(value);
|
||||
//验证没有区号的电话
|
||||
} else {
|
||||
m = MOBILE.matcher(value);
|
||||
}
|
||||
return m.matches();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize(Mobile constraintAnnotation) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lili.common.validation.impl;
|
||||
|
||||
import cn.lili.common.validation.Phone;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 手机号校验
|
||||
*
|
||||
* @author Bulbasaur
|
||||
* @since 2021/7/9 1:42 上午
|
||||
*/
|
||||
public class PhoneValidator implements ConstraintValidator<Phone, String> {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("^0?(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])[0-9]{8}$");
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
Matcher m = pattern.matcher(value);
|
||||
return m.matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Phone constraintAnnotation) {
|
||||
|
||||
}
|
||||
}
|
||||
46
framework/src/main/java/cn/lili/common/vo/PageVO.java
Normal file
46
framework/src/main/java/cn/lili/common/vo/PageVO.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package cn.lili.common.vo;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.lili.common.utils.StringUtils;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 查询参数
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
public class PageVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty(value = "页号")
|
||||
private Integer pageNumber = 1;
|
||||
|
||||
@ApiModelProperty(value = "页面大小")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@ApiModelProperty(value = "排序字段")
|
||||
private String sort;
|
||||
|
||||
@ApiModelProperty(value = "排序方式 asc/desc")
|
||||
private String order;
|
||||
|
||||
@ApiModelProperty(value = "需要驼峰转换蛇形", notes = "一般不做处理,如果数据库中就是蛇形,则这块需要处理。")
|
||||
private Boolean notConvert;
|
||||
|
||||
public String getSort() {
|
||||
if (CharSequenceUtil.isNotEmpty(sort)) {
|
||||
if (notConvert == null || Boolean.FALSE.equals(notConvert)) {
|
||||
return StringUtils.camel2Underline(sort);
|
||||
} else {
|
||||
return sort;
|
||||
}
|
||||
}
|
||||
return sort;
|
||||
}
|
||||
|
||||
}
|
||||
42
framework/src/main/java/cn/lili/common/vo/ResultMessage.java
Normal file
42
framework/src/main/java/cn/lili/common/vo/ResultMessage.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package cn.lili.common.vo;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 前后端交互VO
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
public class ResultMessage<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 成功标志
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 返回代码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 结果对象
|
||||
*/
|
||||
private T result;
|
||||
}
|
||||
49
framework/src/main/java/cn/lili/common/vo/SearchVO.java
Normal file
49
framework/src/main/java/cn/lili/common/vo/SearchVO.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package cn.lili.common.vo;
|
||||
|
||||
import cn.lili.common.utils.DateUtil;
|
||||
import com.alipay.api.internal.util.StringUtils;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 日期搜索参数
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SearchVO implements Serializable {
|
||||
|
||||
@ApiModelProperty(value = "起始日期")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty(value = "结束日期")
|
||||
private String endDate;
|
||||
|
||||
public Date getConvertStartDate() {
|
||||
if (StringUtils.isEmpty(startDate)) {
|
||||
return null;
|
||||
}
|
||||
return DateUtil.toDate(startDate, DateUtil.STANDARD_DATE_FORMAT);
|
||||
}
|
||||
|
||||
public Date getConvertEndDate() {
|
||||
if (StringUtils.isEmpty(endDate)) {
|
||||
return null;
|
||||
}
|
||||
//结束时间等于结束日期+1天 -1秒,
|
||||
Date date = DateUtil.toDate(endDate, DateUtil.STANDARD_DATE_FORMAT);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH) + 1);
|
||||
calendar.set(Calendar.SECOND, -1);
|
||||
return calendar.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.lili.common.vo;
|
||||
|
||||
import cn.lili.common.utils.Base64DecodeMultipartFile;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 序列化的input stream
|
||||
*
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class SerializableStream {
|
||||
private String base64;
|
||||
|
||||
public SerializableStream(InputStream inputStream) {
|
||||
this.base64 = Base64DecodeMultipartFile.inputStreamToStream(inputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
package cn.lili.elasticsearch;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.lili.elasticsearch.config.ElasticsearchProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.client.HttpAsyncResponseConsumerFactory;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.client.indices.CreateIndexRequest;
|
||||
import org.elasticsearch.client.indices.CreateIndexResponse;
|
||||
import org.elasticsearch.client.indices.GetIndexRequest;
|
||||
import org.elasticsearch.client.indices.PutMappingRequest;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* @author paulG
|
||||
* @since 2020/10/14
|
||||
**/
|
||||
@Slf4j
|
||||
public abstract class BaseElasticsearchService {
|
||||
|
||||
protected static final RequestOptions COMMON_OPTIONS;
|
||||
|
||||
static {
|
||||
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
|
||||
|
||||
//默认缓冲限制为100MB,此处修改为30MB。
|
||||
builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024));
|
||||
COMMON_OPTIONS = builder.build();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("elasticsearchClient")
|
||||
protected RestHighLevelClient client;
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchProperties elasticsearchProperties;
|
||||
|
||||
/**
|
||||
* build DeleteIndexRequest
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @author fxbin
|
||||
*/
|
||||
private static DeleteIndexRequest buildDeleteIndexRequest(String index) {
|
||||
return new DeleteIndexRequest(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* build IndexRequest
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @param id request object id
|
||||
* @param object request object
|
||||
* @return {@link IndexRequest}
|
||||
* @author fxbin
|
||||
*/
|
||||
protected static IndexRequest buildIndexRequest(String index, String id, Object object) {
|
||||
return new IndexRequest(index).id(id).source(BeanUtil.beanToMap(object), XContentType.JSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* create elasticsearch index (asyc)
|
||||
*
|
||||
* @param index elasticsearch index
|
||||
* @author fxbin
|
||||
*/
|
||||
protected void createIndexRequest(String index) {
|
||||
try {
|
||||
CreateIndexRequest request = new CreateIndexRequest(index);
|
||||
//Settings for this index
|
||||
request.settings(Settings.builder()
|
||||
.put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards())
|
||||
.put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas())
|
||||
.put("index.mapping.total_fields.limit", 2000));
|
||||
|
||||
//创建索引
|
||||
CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS);
|
||||
createMapping(index);
|
||||
log.info(" whether all of the nodes have acknowledged the request : {}", createIndexResponse.isAcknowledged());
|
||||
log.info(" Indicates whether the requisite number of shard copies were started for each shard in the index before timing out :{}", createIndexResponse.isShardsAcknowledged());
|
||||
} catch (Exception e) {
|
||||
log.error("创建索引错误", e);
|
||||
throw new ElasticsearchException("创建索引 {" + index + "} 失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void createMapping(String index) throws Exception {
|
||||
String source =
|
||||
" {\n" +
|
||||
" \"properties\": {\n" +
|
||||
" \"_class\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"attrList\": {\n" +
|
||||
" \"type\": \"nested\",\n" +
|
||||
" \"properties\": {\n" +
|
||||
" \"name\": {\n" +
|
||||
" \"type\": \"keyword\"\n" +
|
||||
" },\n" +
|
||||
" \"type\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"value\": {\n" +
|
||||
" \"type\": \"keyword\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"brandId\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true,\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"brandName\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true,\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"brandUrl\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true,\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"buyCount\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"releaseTime\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true, \n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"categoryPath\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true,\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"categoryNamePath\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true,\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"commentNum\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"skuSource\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"goodsId\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"goodsName\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true, \n" +
|
||||
" \"analyzer\": \"ik_max_word\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"grade\": {\n" +
|
||||
" \"type\": \"float\"\n" +
|
||||
" },\n" +
|
||||
" \"highPraiseNum\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"id\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"intro\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"authFlag\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"marketEnable\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true, \n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"mobileIntro\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"point\": {\n" +
|
||||
" \"type\": \"long\"\n" +
|
||||
" },\n" +
|
||||
" \"price\": {\n" +
|
||||
" \"type\": \"float\"\n" +
|
||||
" },\n" +
|
||||
" \"salesModel\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"recommend\": {\n" +
|
||||
" \"type\": \"boolean\"\n" +
|
||||
" },\n" +
|
||||
" \"selfOperated\": {\n" +
|
||||
" \"type\": \"boolean\"\n" +
|
||||
" },\n" +
|
||||
" \"sellerId\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"sellerName\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true, \n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"shopCategoryPath\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fielddata\": true, \n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"sn\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"promotionMapJson\": {\n" +
|
||||
" \"type\": \"text\"\n" +
|
||||
" },\n" +
|
||||
" \"thumbnail\": {\n" +
|
||||
" \"type\": \"text\",\n" +
|
||||
" \"fields\": {\n" +
|
||||
" \"keyword\": {\n" +
|
||||
" \"type\": \"keyword\",\n" +
|
||||
" \"ignore_above\": 256\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n";
|
||||
|
||||
PutMappingRequest request = new PutMappingRequest(index)
|
||||
.source(source, XContentType.JSON);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AcknowledgedResponse> response = new AtomicReference<>();
|
||||
client.indices().putMappingAsync(
|
||||
request,
|
||||
RequestOptions.DEFAULT,
|
||||
new ActionListener<AcknowledgedResponse>() {
|
||||
@Override
|
||||
public void onResponse(AcknowledgedResponse r) {
|
||||
response.set(r);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
latch.await(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 判断某个index是否存在
|
||||
*
|
||||
* @param index index名
|
||||
* @return boolean
|
||||
* @author fanxb
|
||||
* @since 2019/7/24 14:57
|
||||
*/
|
||||
public boolean indexExist(String index) {
|
||||
try {
|
||||
GetIndexRequest request = new GetIndexRequest(index);
|
||||
request.local(false);
|
||||
request.humanReadable(true);
|
||||
request.includeDefaults(false);
|
||||
return client.indices().exists(request, RequestOptions.DEFAULT);
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("获取索引 {" + index + "} 是否存在失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete elasticsearch index
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @author fxbin
|
||||
*/
|
||||
protected void deleteIndexRequest(String index) {
|
||||
DeleteIndexRequest deleteIndexRequest = buildDeleteIndexRequest(index);
|
||||
try {
|
||||
client.indices().delete(deleteIndexRequest, COMMON_OPTIONS);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("删除索引 {" + index + "} 失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* exec updateRequest
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @param id Document id
|
||||
* @param object request object
|
||||
* @author fxbin
|
||||
*/
|
||||
protected void updateRequest(String index, String id, Object object) {
|
||||
try {
|
||||
UpdateRequest updateRequest = new UpdateRequest(index, id).doc(BeanUtil.beanToMap(object), XContentType.JSON);
|
||||
client.update(updateRequest, COMMON_OPTIONS);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("更新索引 {" + index + "} 数据 {" + object + "} 失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* exec deleteRequest
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @param id Document id
|
||||
* @author fxbin
|
||||
*/
|
||||
protected void deleteRequest(String index, String id) {
|
||||
try {
|
||||
DeleteRequest deleteRequest = new DeleteRequest(index, id);
|
||||
client.delete(deleteRequest, COMMON_OPTIONS);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("删除索引 {" + index + "} 数据id {" + id + "} 失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* search all
|
||||
*
|
||||
* @param index elasticsearch index name
|
||||
* @return {@link SearchResponse}
|
||||
* @author fxbin
|
||||
*/
|
||||
protected SearchResponse search(String index) {
|
||||
SearchRequest searchRequest = new SearchRequest(index);
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
|
||||
searchRequest.source(searchSourceBuilder);
|
||||
SearchResponse searchResponse = null;
|
||||
try {
|
||||
searchResponse = client.search(searchRequest, COMMON_OPTIONS);
|
||||
} catch (IOException e) {
|
||||
log.error("es 搜索错误", e);
|
||||
}
|
||||
return searchResponse;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
21
framework/src/main/java/cn/lili/elasticsearch/EsSuffix.java
Normal file
21
framework/src/main/java/cn/lili/elasticsearch/EsSuffix.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package cn.lili.elasticsearch;
|
||||
|
||||
/**
|
||||
* elasticsearch 索引后缀
|
||||
*
|
||||
* @author paulG
|
||||
* @since 2020/10/13
|
||||
**/
|
||||
public class EsSuffix {
|
||||
|
||||
/**
|
||||
* 商品索引后缀
|
||||
*/
|
||||
public static final String GOODS_INDEX_NAME = "goods";
|
||||
|
||||
/**
|
||||
* 日志索引后缀
|
||||
*/
|
||||
public static final String LOGS_INDEX_NAME = "logs";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package cn.lili.elasticsearch.config;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.conn.ConnectionKeepAliveStrategy;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.nio.reactor.IOReactorConfig;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* elasticsearch 配置
|
||||
*
|
||||
* @author paulG
|
||||
* @since 2020/10/13
|
||||
**/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchProperties elasticsearchProperties;
|
||||
|
||||
private RestHighLevelClient client;
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
RestClientBuilder restBuilder = RestClient
|
||||
.builder(this.getHttpHosts());
|
||||
restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
|
||||
httpClientBuilder
|
||||
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
|
||||
.setMaxConnPerRoute(10).
|
||||
setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build()));
|
||||
String username = elasticsearchProperties.getAccount().getUsername();
|
||||
String password = elasticsearchProperties.getAccount().getPassword();
|
||||
if (username != null && password != null) {
|
||||
final CredentialsProvider credential = new BasicCredentialsProvider();
|
||||
credential.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
|
||||
restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
|
||||
httpClientBuilder
|
||||
.setDefaultCredentialsProvider(credential)
|
||||
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
|
||||
.setMaxConnPerRoute(10)
|
||||
.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(Runtime.getRuntime().availableProcessors()).build()));
|
||||
}
|
||||
|
||||
restBuilder.setRequestConfigCallback(requestConfigBuilder ->
|
||||
requestConfigBuilder.setConnectTimeout(1000) //time until a connection with the server is established.
|
||||
.setSocketTimeout(12 * 1000) //time of inactivity to wait for packets[data] to receive.
|
||||
.setConnectionRequestTimeout(-1)); //time to fetch a connection from the connection pool 0 for infinite.
|
||||
|
||||
client = new RestHighLevelClient(restBuilder);
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean("elasticsearchRestTemplate")
|
||||
public ElasticsearchRestTemplate elasticsearchRestTemplate() {
|
||||
return new ElasticsearchRestTemplate(this.client);
|
||||
}
|
||||
|
||||
private HttpHost[] getHttpHosts() {
|
||||
List<String> clusterNodes = elasticsearchProperties.getClusterNodes();
|
||||
HttpHost[] httpHosts = new HttpHost[clusterNodes.size()];
|
||||
for (int i = 0; i < clusterNodes.size(); i++) {
|
||||
String[] node = clusterNodes.get(i).split(":");
|
||||
httpHosts[i] = new HttpHost(node[0], Convert.toInt(node[1]), elasticsearchProperties.getSchema());
|
||||
}
|
||||
return httpHosts;
|
||||
}
|
||||
|
||||
private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
|
||||
return (response, context) -> 2 * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* it gets called when bean instance is getting removed from the context if
|
||||
* scope is not a prototype
|
||||
* If there is a method named shutdown or close then spring container will try
|
||||
* to automatically configure them as callback methods when bean is being
|
||||
* destroyed
|
||||
*/
|
||||
@PreDestroy
|
||||
public void clientClose() {
|
||||
try {
|
||||
this.client.close();
|
||||
} catch (IOException e) {
|
||||
log.error("es clientClose错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.lili.elasticsearch.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author paulG
|
||||
* @since 2020/10/13
|
||||
**/
|
||||
@Data
|
||||
@Builder
|
||||
@Component("elasticsearchProperties")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ConfigurationProperties(prefix = "lili.data.elasticsearch")
|
||||
public class ElasticsearchProperties {
|
||||
|
||||
/**
|
||||
* 请求协议
|
||||
*/
|
||||
private String schema = "https";
|
||||
|
||||
/**
|
||||
* 集群名称
|
||||
*/
|
||||
private String clusterName = "elasticsearch";
|
||||
|
||||
/**
|
||||
* 集群节点
|
||||
*/
|
||||
@NotNull(message = "集群节点不允许为空")
|
||||
private List<String> clusterNodes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 索引前缀
|
||||
*/
|
||||
private String indexPrefix;
|
||||
|
||||
/**
|
||||
* 连接超时时间(毫秒)
|
||||
*/
|
||||
private Integer connectTimeout = 1000;
|
||||
|
||||
/**
|
||||
* socket 超时时间
|
||||
*/
|
||||
private Integer socketTimeout = 30000;
|
||||
|
||||
/**
|
||||
* 连接请求超时时间
|
||||
*/
|
||||
private Integer connectionRequestTimeout = 500;
|
||||
|
||||
/**
|
||||
* 每个路由的最大连接数量
|
||||
*/
|
||||
private Integer maxConnectPerRoute = 10;
|
||||
|
||||
/**
|
||||
* 最大连接总数量
|
||||
*/
|
||||
private Integer maxConnectTotal = 30;
|
||||
|
||||
/**
|
||||
* 索引配置信息
|
||||
*/
|
||||
private Index index = new Index();
|
||||
|
||||
/**
|
||||
* 认证账户
|
||||
*/
|
||||
private Account account = new Account();
|
||||
|
||||
/**
|
||||
* 索引配置信息
|
||||
*/
|
||||
@Data
|
||||
public static class Index {
|
||||
|
||||
/**
|
||||
* 分片数量
|
||||
*/
|
||||
private Integer numberOfShards = 3;
|
||||
|
||||
/**
|
||||
* 副本数量
|
||||
*/
|
||||
private Integer numberOfReplicas = 2;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证账户
|
||||
*/
|
||||
@Data
|
||||
public static class Account {
|
||||
|
||||
/**
|
||||
* 认证用户
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 认证密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package cn.lili.modules.connect.config;
|
||||
|
||||
import cn.lili.modules.connect.entity.dto.AuthCallback;
|
||||
import cn.lili.modules.connect.request.BaseAuthRequest;
|
||||
import com.xkcoding.http.config.HttpConfig;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JustAuth配置类
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @since 1.8
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuthConfig {
|
||||
|
||||
/**
|
||||
* 客户端id:对应各平台的appKey
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 客户端Secret:对应各平台的appSecret
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 登录成功后的回调地址
|
||||
*/
|
||||
private String redirectUri;
|
||||
|
||||
/**
|
||||
* 支付宝公钥:当选择支付宝登录时,该值可用
|
||||
* 对应“RSA2(SHA256)密钥”中的“支付宝公钥”
|
||||
*/
|
||||
private String alipayPublicKey;
|
||||
|
||||
/**
|
||||
* 是否需要申请unionid,目前只针对qq登录
|
||||
* 注:qq授权登录时,获取unionid需要单独发送邮件申请权限。如果个人开发者账号中申请了该权限,可以将该值置为true,在获取openId时就会同步获取unionId
|
||||
* 参考链接:http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D
|
||||
* <p>
|
||||
* 1.7.1版本新增参数
|
||||
*/
|
||||
private boolean unionId;
|
||||
|
||||
/**
|
||||
* Stack Overflow Key
|
||||
* <p>
|
||||
*
|
||||
* @since 1.9.0
|
||||
*/
|
||||
private String stackOverflowKey;
|
||||
|
||||
/**
|
||||
* 企业微信,授权方的网页应用ID
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* 使用 Coding 登录时,需要传该值。
|
||||
* <p>
|
||||
* 团队域名前缀,比如以“ https://justauth.coding.net/ ”为例,{@code codingGroupName} = justauth
|
||||
*
|
||||
* @since 1.15.5
|
||||
*/
|
||||
private String codingGroupName;
|
||||
|
||||
/**
|
||||
* 针对国外服务可以单独设置代理
|
||||
* HttpConfig config = new HttpConfig();
|
||||
* config.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 10080)));
|
||||
* config.setTimeout(15000);
|
||||
*
|
||||
* @since 1.15.5
|
||||
*/
|
||||
private HttpConfig httpConfig;
|
||||
|
||||
/**
|
||||
* 忽略校验 {@code state} 参数,默认不开启。当 {@code ignoreCheckState} 为 {@code true} 时,
|
||||
* {@link BaseAuthRequest#login(AuthCallback)}} 将不会校验 {@code state} 的合法性。
|
||||
* <p>
|
||||
* 使用场景:当且仅当使用自实现 {@code state} 校验逻辑时开启
|
||||
* <p>
|
||||
* 以下场景使用方案仅作参考:
|
||||
* 1. 授权、登录为同端,并且全部使用 JustAuth 实现时,该值建议设为 {@code false};
|
||||
* 2. 授权和登录为不同端实现时,比如前端页面拼装 {@code authorizeUrl},并且前端自行对{@code state}进行校验,
|
||||
* 后端只负责使用{@code code}获取用户信息时,该值建议设为 {@code true};
|
||||
*
|
||||
* <strong>如非特殊需要,不建议开启这个配置</strong>
|
||||
* <p>
|
||||
* 该方案主要为了解决以下类似场景的问题:
|
||||
*
|
||||
* @see <a href="https://github.com/justauth/JustAuth/issues/83">https://github.com/justauth/JustAuth/issues/83</a>
|
||||
* @since 1.15.6
|
||||
*/
|
||||
private boolean ignoreCheckState;
|
||||
|
||||
/**
|
||||
* 支持自定义授权平台的 scope 内容
|
||||
*
|
||||
* @since 1.15.7
|
||||
*/
|
||||
private List<String> scopes;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.lili.modules.connect.config;
|
||||
|
||||
|
||||
import cn.lili.modules.connect.entity.enums.AuthResponseStatus;
|
||||
import cn.lili.modules.connect.exception.AuthException;
|
||||
|
||||
/**
|
||||
* OAuth平台的API地址的统一接口
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2020/12/4 12:17
|
||||
*/
|
||||
public interface ConnectAuth {
|
||||
|
||||
/**
|
||||
* 授权的api
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
String authorize();
|
||||
|
||||
/**
|
||||
* 获取accessToken的api
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
String accessToken();
|
||||
|
||||
/**
|
||||
* 获取用户信息的api
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
String userInfo();
|
||||
|
||||
/**
|
||||
* 取消授权的api
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
default String revoke() {
|
||||
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新授权的api
|
||||
*
|
||||
* @return url
|
||||
*/
|
||||
default String refresh() {
|
||||
throw new AuthException(AuthResponseStatus.UNSUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Source的字符串名字
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
default String getName() {
|
||||
if (this instanceof Enum) {
|
||||
return String.valueOf(this);
|
||||
}
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package cn.lili.modules.connect.config;
|
||||
|
||||
|
||||
/**
|
||||
* 用户信息 枚举
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v4.0
|
||||
* @since 2020/12/4 14:10
|
||||
*/
|
||||
public enum ConnectAuthEnum implements ConnectAuth {
|
||||
|
||||
/**
|
||||
* 微信开放平台
|
||||
*/
|
||||
WECHAT {
|
||||
@Override
|
||||
public String authorize() {
|
||||
return "https://open.weixin.qq.com/connect/oauth2/authorize";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessToken() {
|
||||
return "https://api.weixin.qq.com/sns/oauth2/access_token";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userInfo() {
|
||||
return "https://api.weixin.qq.com/sns/userinfo";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 微信开放平台
|
||||
*/
|
||||
WECHAT_PC {
|
||||
@Override
|
||||
public String authorize() {
|
||||
return "https://open.weixin.qq.com/connect/qrconnect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessToken() {
|
||||
return "https://api.weixin.qq.com/sns/oauth2/access_token";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userInfo() {
|
||||
return "https://api.weixin.qq.com/sns/userinfo";
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* QQ
|
||||
*/
|
||||
QQ {
|
||||
@Override
|
||||
public String authorize() {
|
||||
return "https://graph.qq.com/oauth2.0/authorize";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessToken() {
|
||||
return "https://graph.qq.com/oauth2.0/token";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userInfo() {
|
||||
return "https://graph.qq.com/user/get_user_info";
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 支付宝
|
||||
*/
|
||||
ALIPAY {
|
||||
@Override
|
||||
public String authorize() {
|
||||
return "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessToken() {
|
||||
return "https://openapi.alipay.com/gateway.do";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userInfo() {
|
||||
return "https://openapi.alipay.com/gateway.do";
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 新浪微博
|
||||
*/
|
||||
WEIBO {
|
||||
@Override
|
||||
public String authorize() {
|
||||
return "https://api.weibo.com/oauth2/authorize";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessToken() {
|
||||
return "https://api.weibo.com/oauth2/access_token";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String userInfo() {
|
||||
return "https://api.weibo.com/2/users/show.json";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lili.modules.connect.entity;
|
||||
|
||||
import cn.lili.mybatis.BaseIdEntity;
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Chopper
|
||||
*/
|
||||
@Data
|
||||
@TableName("li_connect")
|
||||
@ApiModel(value = "联合登陆")
|
||||
@NoArgsConstructor
|
||||
public class Connect extends BaseIdEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@CreatedBy
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@ApiModelProperty(value = "创建者", hidden = true)
|
||||
private String createBy;
|
||||
|
||||
@CreatedDate
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@ApiModelProperty(value = "创建时间", hidden = true)
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty("用户id")
|
||||
private String userId;
|
||||
|
||||
@ApiModelProperty("联合登录id")
|
||||
private String unionId;
|
||||
|
||||
/**
|
||||
* @see cn.lili.modules.connect.entity.enums.ConnectEnum
|
||||
*/
|
||||
@ApiModelProperty(value = "联合登录类型")
|
||||
private String unionType;
|
||||
|
||||
|
||||
public Connect(String userId, String unionId, String unionType) {
|
||||
this.userId = userId;
|
||||
this.unionId = unionId;
|
||||
this.unionType = unionType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.lili.modules.connect.entity.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 授权回调时的参数类
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AuthCallback implements Serializable {
|
||||
|
||||
/**
|
||||
* 访问AuthorizeUrl后回调时带的参数code
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 访问AuthorizeUrl后回调时带的参数auth_code,该参数目前只使用于支付宝登录
|
||||
*/
|
||||
private String authCode;
|
||||
|
||||
/**
|
||||
* 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击
|
||||
*/
|
||||
private String state;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lili.modules.connect.entity.dto;
|
||||
|
||||
import cn.lili.modules.connect.entity.enums.AuthResponseStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* AuthResponse
|
||||
*
|
||||
* @author Chopper
|
||||
* @version v1.0
|
||||
* 2020-12-07 14:20
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuthResponse<T> implements Serializable {
|
||||
private static final long serialVersionUID = 7668539215757528636L;
|
||||
/**
|
||||
* 授权响应状态码
|
||||
*/
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 授权响应信息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 授权响应数据,当且仅当 code = 2000 时返回
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 是否请求成功
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean ok() {
|
||||
return this.code == AuthResponseStatus.SUCCESS.getCode();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user