将 onemall 老代码,统一到归档目录,后续不断迁移移除

This commit is contained in:
YunaiV
2022-06-16 09:06:44 +08:00
parent 64c478a45b
commit 71930d492e
1095 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-framework</artifactId>
<dependencies>
<!-- Web 相关 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<optional>true</optional>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试相关 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<!-- 工具相关 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package cn.iocoder.common.framework.core;
/**
* 可生成 Int 数组的接口
*/
public interface IntArrayValuable {
/**
* @return int 数组
*/
int[] array();
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.common.framework.enums;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 通用状态枚举
*/
public enum CommonStatusEnum implements IntArrayValuable {
ENABLE(1, "开启"),
DISABLE(2, "关闭");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getValue).toArray();
/**
* 状态值
*/
private final Integer value;
/**
* 状态名
*/
private final String name;
CommonStatusEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.common.framework.enums;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 全局用户类型枚举
*/
public enum UserTypeEnum implements IntArrayValuable {
USER(1, "用户"),
ADMIN(2, "管理员");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(UserTypeEnum::getValue).toArray();
/**
* 类型
*/
private final Integer value;
/**
* 类型名
*/
private final String name;
UserTypeEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.common.framework.exception;
import cn.iocoder.common.framework.exception.enums.ServiceErrorCodeRange;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999],参见 {@link GlobalException}
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
*
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
*/
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String message;
public ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,71 @@
package cn.iocoder.common.framework.exception;
import cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.common.framework.vo.CommonResult;
/**
* 全局异常 Exception
*/
public class GlobalException extends RuntimeException {
/**
* 全局错误码
*
* @see GlobalErrorCodeConstants
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public GlobalException() {
}
public GlobalException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public GlobalException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getDetailMessage() {
return detailMessage;
}
public GlobalException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
public GlobalException setCode(Integer code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public GlobalException setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,71 @@
package cn.iocoder.common.framework.exception;
import cn.iocoder.common.framework.exception.enums.ServiceErrorCodeRange;
import cn.iocoder.common.framework.vo.CommonResult;
/**
* 业务逻辑异常 Exception
*/
public final class ServiceException extends RuntimeException {
/**
* 业务错误码
*
* @see ServiceErrorCodeRange
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException() {
}
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public ServiceException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getDetailMessage() {
return detailMessage;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
public ServiceException setCode(Integer code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.common.framework.exception.enums;
import cn.iocoder.common.framework.exception.ErrorCode;
/**
* 全局错误码枚举
* 0-999 系统异常编码保留
*
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*/
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(0, "成功");
// ========== 客户端错误段 ==========
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
// ========== 服务端错误段 ==========
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
static boolean isMatch(Integer code) {
return code != null
&& code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
}
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.common.framework.exception.enums;
/**
* 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
*
* 一共 10 位,分成四段
*
* 第一段1 位,类型
* 1 - 业务级别异常
* x - 预留
* 第二段3 位,系统类型
* 001 - 用户系统
* 002 - 商品系统
* 003 - 订单系统
* 004 - 支付系统
* 005 - 优惠劵系统
* ... - ...
* 第三段3 位,模块
* 不限制规则。
* 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
* 001 - OAuth2 模块
* 002 - User 模块
* 003 - MobileCode 模块
* 第四段3 位,错误码
* 不限制规则。
* 一般建议,每个模块自增。
*
* @author Sin
* @time 2019-03-23 11:28
*/
public class ServiceErrorCodeRange {
// order 错误码区间 [1-000-001-000 ~ 1-000-002-000]
// user 错误码区间 [1-001-000-000 ~ 1-002-000-000)
// system-service 服务 => 错误码区间 [1-002-000-000 ~ 1-003-000-000)
// product 错误码区间 [1-003-000-000 ~ 1-004-000-000)
// pay 错误码区间 [1-004-000-000 ~ 1-005-000-000)
// cart 错误码区间 [1-005-000-000 ~ 1-006-000-000)
// promotion 错误码区间 [1-006-000-000 ~ 1-007-000-000)
}

View File

@@ -0,0 +1,122 @@
package cn.iocoder.common.framework.exception.util;
import cn.iocoder.common.framework.exception.ErrorCode;
import cn.iocoder.common.framework.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* {@link ServiceException} 工具类
*
* 目的在于,格式化异常信息提示。
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
*
* 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式:
*
* 1. 异常提示信息写在枚举类中例如说cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration
* 2. 异常提示信息,写在 .properties 等等配置文件
* 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新
* 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新
*/
public class ServiceExceptionUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExceptionUtil.class);
/**
* 错误码提示模板
*/
private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
public static void putAll(Map<Integer, String> messages) {
ServiceExceptionUtil.MESSAGES.putAll(messages);
}
public static void put(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.put(code, message);
}
public static void delete(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.remove(code, message);
}
// ========== 和 ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
return exception0(errorCode.getCode(), messagePattern);
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
return exception0(errorCode.getCode(), messagePattern, params);
}
/**
* 创建指定编号的 ServiceException 的异常
*
* @param code 编号
* @return 异常
*/
public static ServiceException exception(Integer code) {
return exception0(code, MESSAGES.get(code));
}
/**
* 创建指定编号的 ServiceException 的异常
*
* @param code 编号
* @param params 消息提示的占位符对应的参数
* @return 异常
*/
public static ServiceException exception(Integer code, Object... params) {
return exception0(code, MESSAGES.get(code), params);
}
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
String message = doFormat(code, messagePattern, params);
return new ServiceException(code, message);
}
// ========== 格式化方法 ==========
/**
* 将错误编号对应的消息使用 params 进行格式化。
*
* @param code 错误编号
* @param messagePattern 消息模版
* @param params 参数
* @return 格式化后的提示
*/
private static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
int l;
for (l = 0; l < params.length; l++) {
j = messagePattern.indexOf("{}", i);
if (j == -1) {
LOGGER.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
if (i == 0) {
return messagePattern;
} else {
sbuf.append(messagePattern.substring(i, messagePattern.length()));
return sbuf.toString();
}
} else {
sbuf.append(messagePattern.substring(i, j));
sbuf.append(params[l]);
i = j + 2;
}
}
if (messagePattern.indexOf("{}", i) != -1) {
LOGGER.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
}
sbuf.append(messagePattern.substring(i, messagePattern.length()));
return sbuf.toString();
}
}

View File

@@ -0,0 +1,60 @@
package cn.iocoder.common.framework.util;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CollectionUtils {
public static boolean isEmpty(Collection collection) {
return collection == null || collection.isEmpty();
}
public static boolean isEmpty(Object[] arrays) {
return arrays == null || arrays.length == 0;
}
public static <T> Set<T> asSet(T... objs) {
return new HashSet<>(Arrays.asList(objs));
}
public static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
return from.stream().map(func).collect(Collectors.toList());
}
public static <T, U> Set<U> convertSet(List<T> from, Function<T, U> func) {
return from.stream().map(func).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
}
public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
}
public static <T, K> Map<K, List<T>> convertMultiMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc,
Collectors.mapping(t -> t, Collectors.toList())));
}
public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc,
Collectors.mapping(valueFunc, Collectors.toList())));
}
// 暂时没想好名字,先以 2 结尾噶
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
}
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
}
public static <T> T getFirst(List<T> from) {
return !isEmpty(from) ? from.get(0) : null;
}
}

View File

@@ -0,0 +1,146 @@
package cn.iocoder.common.framework.util;
import org.springframework.util.Assert;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateUtil {
/**
* 计算当期时间相差的日期
*
* @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
* @param amount 相差的数值
* @return 计算后的日志
*/
public static Date addDate(int field, int amount) {
return addDate(null, field, amount);
}
/**
* 计算当期时间相差的日期
*
* @param date 设置时间
* @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
* @param amount 相差的数值
* @return 计算后的日志
*/
public static Date addDate(Date date, int field, int amount) {
if (amount == 0) {
return date;
}
Calendar c = Calendar.getInstance();
if (date != null) {
c.setTime(date);
}
c.add(field, amount);
return c.getTime();
}
/**
* @param date 时间。若为空,则返回空串
* @param pattern 时间格式化
* @return 格式化后的时间字符串.
*/
public static String format(Date date, String pattern) {
if (date == null) {
return "";
}
// TODO 芋艿,后面改成缓存
return new SimpleDateFormat(pattern).format(date);
}
/**
* 获取指定天结束时间
*
* @param date 日期
* @return 获得该日期的开始
*/
public static Date getDayBegin(Date date) {
if (date == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, 0, 0, 0, 0);
return calendar.getTime();
}
/**
* 获取当天开始时间
*
* @return 获得该日期的开始
*/
public static Date getDayBegin() {
return getDayBegin(new Date());
}
/**
* 获取指定天结束时间
*
* @param date 日期
* @return 获得该日期的结束
*/
public static Date getDayEnd(Date date) {
if (date == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, 23, 59, 59, 999);
return calendar.getTime();
}
/**
* 获取当天结束时间
*
* @return 获得该日期的开始
*/
public static Date getDayEnd() {
return getDayEnd(new Date());
}
/**
* 设置Calendar的小时、分钟、秒、毫秒
*
* @param calendar 日历
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @param milliSecond 毫秒
*/
public static void setCalender(Calendar calendar, int hour, int minute, int second, int milliSecond) {
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, milliSecond);
}
/**
* 判断当前时间,是否在该时间范围内
*
* @param beginTime 开始时间
* @param endTime 结束时间
* @return 是否在
*/
public static boolean isBetween(Date beginTime, Date endTime) {
Assert.notNull(beginTime, "开始时间不能为空");
Assert.notNull(endTime, "结束时间不能为空");
Date now = new Date();
return beginTime.getTime() <= now.getTime()
&& now.getTime() <= endTime.getTime();
}
public static Date max(Date a, Date b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.compareTo(b) > 0 ? a : b;
}
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.common.framework.util;
import cn.hutool.crypto.digest.BCrypt;
/**
* 加解密工具类
*/
public class DigestUtils {
public static String genBcryptSalt() {
return BCrypt.gensalt();
}
public static String bcrypt(String key, String salt) {
return BCrypt.hashpw(key, salt);
}
// TODO 稍后移到单元测试
public static void main(String[] args) {
String salt = genBcryptSalt();
String password = "buzhidao";
System.out.println(salt);
System.out.println(bcrypt(password, salt));
}
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.common.framework.util;
import org.apache.commons.lang3.exception.ExceptionUtils;
public class ExceptionUtil {
public static String getMessage(Throwable th) {
return ExceptionUtils.getMessage(th);
}
public static String getRootCauseMessage(Throwable th) {
return ExceptionUtils.getRootCauseMessage(th);
}
public static String getStackTrace(Throwable th) {
return ExceptionUtils.getStackTrace(th);
}
}

View File

@@ -0,0 +1,319 @@
package cn.iocoder.common.framework.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
public class HttpUtil {
private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
/**
* Standard Servlet 2.3+ spec request attributes for include URI and paths.
* <p>If included via a RequestDispatcher, the current resource will see the
* originating request. Its own URI and paths are exposed as request attributes.
*/
public static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
public static final String INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path";
// public static final String INCLUDE_SERVLET_PATH_ATTRIBUTE = "javax.servlet.include.servlet_path";
// public static final String INCLUDE_PATH_INFO_ATTRIBUTE = "javax.servlet.include.path_info";
// public static final String INCLUDE_QUERY_STRING_ATTRIBUTE = "javax.servlet.include.query_string";
//
// /**
// * Standard Servlet 2.4+ spec request attributes for forward URI and paths.
// * <p>If forwarded to via a RequestDispatcher, the current resource will see its
// * own URI and paths. The originating URI and paths are exposed as request attributes.
// */
// public static final String FORWARD_REQUEST_URI_ATTRIBUTE = "javax.servlet.forward.request_uri";
// public static final String FORWARD_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.forward.context_path";
// public static final String FORWARD_SERVLET_PATH_ATTRIBUTE = "javax.servlet.forward.servlet_path";
// public static final String FORWARD_PATH_INFO_ATTRIBUTE = "javax.servlet.forward.path_info";
// public static final String FORWARD_QUERY_STRING_ATTRIBUTE = "javax.servlet.forward.query_string";
/**
* Default character encoding to use when <code>request.getCharacterEncoding</code>
* returns <code>null</code>, according to the Servlet spec.
*
* @see javax.servlet.ServletRequest#getCharacterEncoding
*/
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
public static String obtainAuthorization(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (!StringUtils.hasText(authorization)) {
return null;
}
int index = authorization.indexOf("Bearer ");
if (index == -1) { // 未找到
return null;
}
return authorization.substring(index + 7).trim();
}
public static String getIp(HttpServletRequest request) {
// 基于 X-Forwarded-For 获得
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值第一个 ip 才是真实 ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
// 基于 X-Real-IP 获得
ip = request.getHeader("X-Real-IP");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
// 默认方式
return request.getRemoteAddr();
}
/**
* @param request 请求
* @return ua
*/
public static String getUserAgent(HttpServletRequest request) {
String ua = request.getHeader("User-Agent");
return ua != null ? ua : "";
}
/**
* 根据request拼接queryString
*
* @return queryString
*/
@SuppressWarnings("unchecked")
public static String buildQueryString(HttpServletRequest request) {
Enumeration<String> es = request.getParameterNames();
if (!es.hasMoreElements()) {
return "";
}
String parameterName, parameterValue;
StringBuilder params = new StringBuilder();
while (es.hasMoreElements()) {
parameterName = es.nextElement();
parameterValue = request.getParameter(parameterName);
params.append(parameterName).append("=").append(parameterValue).append("&");
}
return params.deleteCharAt(params.length() - 1).toString();
}
/**
* Return the path within the web application for the given request.
* Detects include request URL if called within a RequestDispatcher include.
* <p/>
* For example, for a request to URL
* <p/>
* <code>http://www.somehost.com/myapp/my/url.jsp</code>,
* <p/>
* for an application deployed to <code>/mayapp</code> (the application's context path), this method would return
* <p/>
* <code>/my/url.jsp</code>.
*
* 该方法,是从 Shiro 源码中扣出来。add by 芋艿
*
* @param request current HTTP request
* @return the path within the web application
*/
public static String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
// Normal case: URI contains context path.
String path = requestUri.substring(contextPath.length());
return (StringUtils.hasText(path) ? path : "/");
} else {
// Special case: rather unusual.
return requestUri;
}
}
/**
* Return the request URI for the given request, detecting an include request
* URL if called within a RequestDispatcher include.
* <p>As the value returned by <code>request.getRequestURI()</code> is <i>not</i>
* decoded by the servlet container, this method will decode it.
* <p>The URI that the web container resolves <i>should</i> be correct, but some
* containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
* in the URI. This method cuts off such incorrect appendices.
*
* @param request current HTTP request
* @return the request URI
*/
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
uri = request.getRequestURI();
}
return normalize(decodeAndCleanUriString(request, uri));
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
* Tomcat trunk, r939305
*
* @param path Relative path to be normalized
* @return normalized path
*/
public static String normalize(String path) {
return normalize(path, true);
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
* Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
* Tomcat trunk, r939305
*
* @param path Relative path to be normalized
* @param replaceBackSlash Should '\\' be replaced with '/'
* @return normalized path
*/
private static String normalize(String path, boolean replaceBackSlash) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (normalized.equals("/."))
return "/";
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
/**
* Decode the supplied URI string and strips any extraneous portion after a ';'.
*
* @param request the incoming HttpServletRequest
* @param uri the application's URI string
* @return the supplied URI string stripped of any extraneous portion after a ';'.
*/
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = decodeRequestString(request, uri);
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
/**
* Return the context path for the given request, detecting an include request
* URL if called within a RequestDispatcher include.
* <p>As the value returned by <code>request.getContextPath()</code> is <i>not</i>
* decoded by the servlet container, this method will decode it.
*
* @param request current HTTP request
* @return the context path
*/
public static String getContextPath(HttpServletRequest request) {
String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
if (contextPath == null) {
contextPath = request.getContextPath();
}
if ("/".equals(contextPath)) {
// Invalid case, but happens for includes on Jetty: silently adapt it.
contextPath = "";
}
return decodeRequestString(request, contextPath);
}
/**
* Decode the given source string with a URLDecoder. The encoding will be taken
* from the request, falling back to the default "ISO-8859-1".
* <p>The default implementation uses <code>URLDecoder.decode(input, enc)</code>.
*
* @param request current HTTP request
* @param source the String to decode
* @return the decoded String
* @see #DEFAULT_CHARACTER_ENCODING
* @see javax.servlet.ServletRequest#getCharacterEncoding
* @see java.net.URLDecoder#decode(String, String)
* @see java.net.URLDecoder#decode(String)
*/
@SuppressWarnings({"deprecation"})
public static String decodeRequestString(HttpServletRequest request, String source) {
String enc = determineEncoding(request);
try {
return URLDecoder.decode(source, enc);
} catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
"': falling back to platform default encoding; exception message: " + ex.getMessage());
}
return URLDecoder.decode(source);
}
}
/**
* Determine the encoding for the given request.
* Can be overridden in subclasses.
* <p>The default implementation checks the request's
* {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
* <code>null</code>, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
*
* @param request current HTTP request
* @return the encoding for the request (never <code>null</code>)
* @see javax.servlet.ServletRequest#getCharacterEncoding()
*/
protected static String determineEncoding(HttpServletRequest request) {
String enc = request.getCharacterEncoding();
if (enc == null) {
enc = DEFAULT_CHARACTER_ENCODING;
}
return enc;
}
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.common.framework.util;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import java.util.UUID;
public class MallUtils {
/**
* 获得链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等,结合在一起,从而进行排错。
*
* 默认情况下,我们使用 Apache SkyWalking 的 traceId 作为链路追踪编号。当然,可能会存在并未引入 Skywalking 的情况,此时使用 UUID 。
*
* @return 链路追踪编号
*/
public static String getTraceId() {
// 通过 SkyWalking 获取链路编号
try {
String traceId = TraceContext.traceId();
if (StringUtils.hasText(traceId)) {
return traceId;
}
} catch (Throwable ignore) {}
// TODO 芋艿 多次调用会问题
return UUID.randomUUID().toString();
}
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.common.framework.util;
import java.util.Random;
public class MathUtil {
/**
* 随机对象
*/
private static final Random RANDOM = new Random(); // TODO 后续优化
/**
* 随机[min, max]范围内的数字
*
* @param min 随机开始
* @param max 随机结束
* @return 数字
*/
public static int random(int min, int max) {
if (min == max) {
return min;
}
if (min > max) {
int temp = min;
min = max;
max = temp;
}
// 随即开始
int diff = max - min + 1;
return RANDOM.nextInt(diff) + min;
}
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.common.framework.util;
import cn.hutool.system.SystemUtil;
/**
* 操作系统工具类
*/
public class OSUtils {
public static String getHostName() {
return SystemUtil.getHostInfo().getName();
}
}

View File

@@ -0,0 +1,39 @@
package cn.iocoder.common.framework.util;
import cn.hutool.core.lang.UUID;
import java.util.*;
public class StringUtils {
public static boolean hasText(String str) {
return org.springframework.util.StringUtils.hasText(str);
}
public static String join(Collection<?> coll, String delim) {
return org.springframework.util.StringUtils.collectionToDelimitedString(coll, delim);
}
public static List<String> split(String toSplit, String delim) {
String[] stringArray = org.springframework.util.StringUtils.tokenizeToStringArray(toSplit, delim);
return Arrays.asList(stringArray);
}
public static List<Integer> splitToInt(String toSplit, String delim) {
String[] stringArray = org.springframework.util.StringUtils.tokenizeToStringArray(toSplit, delim);
List<Integer> array = new ArrayList<>(stringArray.length);
for (String string : stringArray) {
array.add(Integer.valueOf(string));
}
return array;
}
public static String substring(String str, int start) {
return org.apache.commons.lang3.StringUtils.substring(str, start);
}
public static String uuid(boolean isSimple) {
return UUID.fastUUID().toString(isSimple);
}
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.common.framework.util;
import java.util.regex.Pattern;
/**
* 校验工具类
*/
public class ValidationUtil {
private static Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
public static boolean isMobile(String mobile) {
if (mobile == null || mobile.length() != 11) {
return false;
}
// TODO 芋艿,后面完善手机校验
return true;
}
public static boolean isURL(String url) {
return StringUtils.hasText(url)
&& PATTERN_URL.matcher(url).matches();
}
public static void main(String[] args) {
System.out.println(isURL("http://www.iocoder.cn"));
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.core.IntArrayValuable;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = InEnumValidator.class
)
public @interface InEnum {
/**
* @return 实现 EnumValuable 接口的
*/
Class<? extends IntArrayValuable> value();
String message() default "必须在指定范围 {value}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,43 @@
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.core.IntArrayValuable;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class InEnumValidator implements ConstraintValidator<InEnum, Integer> {
private List<Integer> values;
@Override
public void initialize(InEnum annotation) {
IntArrayValuable[] values = annotation.value().getEnumConstants();
if (values.length == 0) {
this.values = Collections.emptyList();
} else {
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 为空时,默认不校验,即认为通过
if (value == null) {
return true;
}
// 校验通过
if (values.contains(value)) {
return true;
}
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
.replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句
return false;
}
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.common.framework.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER,
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = MobileValidator.class
)
public @interface Mobile {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.common.framework.validator;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.common.framework.util.ValidationUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<Mobile, String> {
@Override
public void initialize(Mobile annotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果手机号为空,默认不校验,即校验通过
if (!StringUtils.hasText(value)) {
return true;
}
// 校验手机
return ValidationUtil.isMobile(value);
}
}

View File

@@ -0,0 +1,154 @@
package cn.iocoder.common.framework.vo;
import cn.iocoder.common.framework.exception.ErrorCode;
import cn.iocoder.common.framework.exception.GlobalException;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants;
import com.alibaba.fastjson.annotation.JSONField;
import org.springframework.util.Assert;
import java.io.Serializable;
/**
* 通用返回
*
* @param <T> 数据泛型
*/
public final class CommonResult<T> implements Serializable {
/**
* 错误码
*
* @see ErrorCode#getCode()
*/
private Integer code;
/**
* 返回数据
*/
private T data;
/**
* 错误提示,用户可阅读
*
* @see ErrorCode#getMessage() ()
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
/**
* 将传入的 result 对象,转换成另外一个泛型结果的对象
*
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {
return error(result.getCode(), result.getMessage(), result.detailMessage);
}
public static <T> CommonResult<T> error(Integer code, String message) {
return error(code, message, null);
}
public static <T> CommonResult<T> error(Integer code, String message, String detailMessage) {
Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.message = message;
result.detailMessage = detailMessage;
return result;
}
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.data = data;
result.message = "";
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getDetailMessage() {
return detailMessage;
}
public CommonResult<T> setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
@JSONField(serialize = false) // 避免序列化
public boolean isSuccess() {
return GlobalErrorCodeConstants.SUCCESS.getCode().equals(code);
}
@JSONField(serialize = false) // 避免序列化
public boolean isError() {
return !isSuccess();
}
@Override
public String toString() {
return "CommonResult{" +
"code=" + code +
", data=" + data +
", message='" + message + '\'' +
", detailMessage='" + detailMessage + '\'' +
'}';
}
// ========= 和 Exception 异常体系集成 =========
/**
* 判断是否有异常。如果有,则抛出 {@link GlobalException} 或 {@link ServiceException} 异常
*/
public void checkError() throws GlobalException, ServiceException {
if (isSuccess()) {
return;
}
// 全局异常
if (GlobalErrorCodeConstants.isMatch(code)) {
throw new GlobalException(code, message).setDetailMessage(detailMessage);
}
// 业务异常
throw new ServiceException(code, message).setDetailMessage(detailMessage);
}
public static <T> CommonResult<T> error(ServiceException serviceException) {
return error(serviceException.getCode(), serviceException.getMessage(),
serviceException.getDetailMessage());
}
public static <T> CommonResult<T> error(GlobalException globalException) {
return error(globalException.getCode(), globalException.getMessage(),
globalException.getDetailMessage());
}
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.common.framework.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("分页参数")
public class PageParam implements Serializable {
@ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为 1")
private Integer pageNo;
@ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
@NotNull(message = "每页条数不能为空")
@Range(min = 1, max = 100, message = "条数范围为 [1, 100]")
private Integer pageSize;
public Integer getPageNo() {
return pageNo;
}
public PageParam setPageNo(Integer pageNo) {
this.pageNo = pageNo;
return this;
}
public Integer getPageSize() {
return pageSize;
}
public PageParam setPageSize(Integer pageSize) {
this.pageSize = pageSize;
return this;
}
// public final int getOffset() {
// return (pageNo - 1) * pageSize;
// }
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.common.framework.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.util.List;
@ApiModel("分页结果")
public final class PageResult<T> implements Serializable {
@ApiModelProperty(value = "数据", required = true)
private List<T> list;
@ApiModelProperty(value = "总量", required = true)
private Long total;
public List<T> getList() {
return list;
}
public PageResult<T> setList(List<T> list) {
this.list = list;
return this;
}
public Long getTotal() {
return total;
}
public PageResult<T> setTotal(Long total) {
this.total = total;
return this;
}
}

View File

@@ -0,0 +1,56 @@
package cn.iocoder.common.framework.vo;
import java.io.Serializable;
/**
* 排序字段 DTO
*
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
*/
public class SortingField implements Serializable {
/**
* 顺序 - 升序
*/
public static final String ORDER_ASC = "asc";
/**
* 顺序 - 降序
*/
public static final String ORDER_DESC = "desc";
/**
* 字段
*/
private String field;
/**
* 顺序
*/
private String order;
// 空构造方法,解决反序列化
public SortingField() {
}
public SortingField(String field, String order) {
this.field = field;
this.order = order;
}
public String getField() {
return field;
}
public SortingField setField(String field) {
this.field = field;
return this;
}
public String getOrder() {
return order;
}
public SortingField setOrder(String order) {
this.order = order;
return this;
}
}

View File

@@ -0,0 +1,61 @@
package cn.iocoder.common.framework.util;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.Date;
import java.util.GregorianCalendar;
public class DateUtilTest {
@Test
@Ignore // 暂时忽略测试不通过add by 芋艿
public void testAddDate() {
Assert.assertNull(DateUtil.addDate(0, 0));
Assert.assertEquals(new Date(1_778_410_800_000L), DateUtil.addDate(
new Date(1_515_585_600_000L), 2, 100));
}
@Test
@Ignore // 暂时忽略测试不通过add by 芋艿
public void testFormat() {
Assert.assertEquals("", DateUtil.format(null, null));
Assert.assertEquals("2018-01-10:12:00:00", DateUtil.format(
new Date(1_515_585_600_000L), "yyyy-MM-dd:HH:mm:ss"));
}
@Test
@Ignore // 暂时忽略测试不通过add by 芋艿
public void testGetDayBegin() {
Assert.assertNull(DateUtil.getDayBegin(null));
Assert.assertEquals(new Date(1_515_542_400_000L),
DateUtil.getDayBegin(new Date(1_515_585_600_000L)));
}
@Test
@Ignore // 暂时忽略测试不通过add by 芋艿
public void testGetDayEnd() {
Assert.assertNull(DateUtil.getDayEnd(null));
Assert.assertEquals(new Date(1_515_628_799_999L), DateUtil.getDayEnd(
new Date(1_515_585_600_000L)));
}
@Test
public void testIsBetween() {
Assert.assertTrue(DateUtil.isBetween(DateUtil.getDayBegin(),
DateUtil.getDayEnd()));
Assert.assertFalse(DateUtil.isBetween(
DateUtil.getDayBegin(new Date(0L)),
DateUtil.getDayEnd(new Date(100_000L))));
}
@Test
public void testSetCalender() {
final GregorianCalendar calendar = new GregorianCalendar();
DateUtil.setCalender(calendar, 2, 30, 50, 0);
Assert.assertEquals(2, calendar.getTime().getHours());
Assert.assertEquals(30, calendar.getTime().getMinutes());
Assert.assertEquals(50, calendar.getTime().getSeconds());
}
}