将 onemall 老代码,统一到归档目录,后续不断迁移移除
This commit is contained in:
85
归档/common/common-framework/pom.xml
Normal file
85
归档/common/common-framework/pom.xml
Normal 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>
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.iocoder.common.framework.core;
|
||||
|
||||
/**
|
||||
* 可生成 Int 数组的接口
|
||||
*/
|
||||
public interface IntArrayValuable {
|
||||
|
||||
/**
|
||||
* @return int 数组
|
||||
*/
|
||||
int[] array();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
15
归档/common/mall-security-annotations/pom.xml
Normal file
15
归档/common/mall-security-annotations/pom.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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>mall-security-annotations</artifactId>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.security.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 要求用户认证(登陆)注解。通过将该注解添加到 Controller 上,会自动校验用户是否登陆。
|
||||
*
|
||||
* 默认请求下,用户访问的 API 接口,无需登陆。主要的考虑是,
|
||||
* 1. 需要用户登陆的接口,本身会获取在线用户的编号。如果不添加 @RequiresLogin 注解就会报错。
|
||||
* 2. 大多数情况下,用户的 API 接口无需登陆。
|
||||
*
|
||||
* ps:同样适用于管理员 Admin
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE ,因为没有场景
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequiresAuthenticate {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.iocoder.security.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 通过将该注解添加到 Controller 的方法上,声明无需进行登陆
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE ,因为没有场景
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequiresNone {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.security.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 参考 Shiro @RequiresPermissions 设计 http://shiro.apache.org/static/1.3.2/apidocs/org/apache/shiro/authz/annotation/RequiresPermissions.html
|
||||
*
|
||||
* 通过将该注解添加到 Controller 的方法上,进行授权鉴定
|
||||
*
|
||||
* ps:目前暂时只有管理员 Admin 使用到
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE ,因为没有场景
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequiresPermissions {
|
||||
|
||||
/**
|
||||
* 当有多个标识时,必须全部拥有权限,才可以操作
|
||||
*
|
||||
* @return 权限标识数组
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
}
|
||||
53
归档/common/mall-spring-boot-starter-dubbo/pom.xml
Normal file
53
归档/common/mall-spring-boot-starter-dubbo/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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>mall-spring-boot-starter-dubbo</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- 通用相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<!-- TODO 优化点:Spring Cloud Alibaba Dubbo 的示例 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.cloud</groupId>-->
|
||||
<!-- <artifactId>spring-cloud-starter-dubbo</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志相关 -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具相关 -->
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.iocoder.mall.dubbo.config;
|
||||
|
||||
import cn.iocoder.common.framework.util.OSUtils;
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dubbo 配置项的后置处理器,主要目的如下:
|
||||
*
|
||||
* 1. 生成 {@link #DUBBO_TAG_PROPERTIES_KEY} 配置项,可用于本地开发环境下的 dubbo.provider.tag 配置项
|
||||
*/
|
||||
public class DubboEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
/**
|
||||
* 默认配置项的 PropertySource 名字
|
||||
*/
|
||||
private static final String PROPERTY_SOURCE_NAME = "mallDubboProperties";
|
||||
|
||||
/**
|
||||
* Dubbo 路由标签属性 KEY
|
||||
*/
|
||||
private static final String DUBBO_TAG_PROPERTIES_KEY = "DUBBO_TAG";
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
// 需要修改的配置项
|
||||
Map<String, Object> modifyProperties = new HashMap<>();
|
||||
// 生成 DUBBO_TAG_PROPERTIES_KEY,使用 hostname
|
||||
String dubboTag = OSUtils.getHostName();
|
||||
if (!StringUtils.hasText(dubboTag)) {
|
||||
dubboTag = StringUtils.uuid(true); // 兜底,强行生成一个
|
||||
}
|
||||
modifyProperties.put(DUBBO_TAG_PROPERTIES_KEY, dubboTag);
|
||||
// 添加到 environment 中,排在最优,最低优先级
|
||||
addOrReplace(environment.getPropertySources(), modifyProperties);
|
||||
}
|
||||
|
||||
private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) {
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
// 情况一,如果存在 defaultProperties 的 PropertySource,则进行 key 的修改
|
||||
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
|
||||
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
|
||||
if (source instanceof MapPropertySource) {
|
||||
MapPropertySource target = (MapPropertySource) source;
|
||||
for (String key : map.keySet()) {
|
||||
target.getSource().put(key, map.get(key));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 情况二,不存在 defaultProperties 的 PropertySource,则直接添加到其中
|
||||
propertySources.addLast(new MapPropertySource(PROPERTY_SOURCE_NAME, map));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.mall.dubbo.config;
|
||||
|
||||
import cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class DubboWebAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(DubboWebAutoConfiguration.class);
|
||||
|
||||
// ========== 拦截器相关 ==========
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
try {
|
||||
// 设置为 -1000 的原因,保证在比较前面就处理该逻辑。例如说,认证拦截器;
|
||||
registry.addInterceptor(new DubboRouterTagWebInterceptor()).order(-1000);
|
||||
logger.info("[addInterceptors][加载 DubboRouterTagWebInterceptor 拦截器完成]");
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
logger.warn("[addInterceptors][无法获取 DubboRouterTagWebInterceptor 拦截器,无法使用基于 dubbo-tag 请求头进行 Dubbo 标签路由]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.mall.dubbo.core.cluster.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
|
||||
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
|
||||
/**
|
||||
* Consumer 方,在调用 Provider 时,将 {@link DubboRouterTagContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
|
||||
*
|
||||
* 完整逻辑说明,见 {@link DubboProviderRouterTagFilter}
|
||||
*
|
||||
* 注意,这里需要设置到 order = 1 的原因,是需要保证排在 ConsumerContextClusterInterceptor 之后
|
||||
*/
|
||||
@Activate(group = CommonConstants.CONSUMER, order = 1)
|
||||
public class DubboConsumerRouterTagClusterInterceptor implements ClusterInterceptor {
|
||||
|
||||
@Override
|
||||
public void before(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
|
||||
// 设置 Dubbo Tag 到 Dubbo 隐式传参
|
||||
String dubboTag = DubboRouterTagContextHolder.getTag();
|
||||
if (StringUtils.hasText(dubboTag)) {
|
||||
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
|
||||
// 清空 Dubbo Tag 的隐式传参
|
||||
invocation.setAttachment(CommonConstants.TAG_KEY, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.iocoder.mall.dubbo.core.filter;
|
||||
|
||||
import cn.iocoder.common.framework.exception.GlobalException;
|
||||
import cn.iocoder.common.framework.exception.ServiceException;
|
||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.*;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||
|
||||
@Activate(group = CommonConstants.PROVIDER) // TODO 优化点:设置下顺序
|
||||
public class DubboProviderExceptionFilter implements Filter, Filter.Listener {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(DubboProviderExceptionFilter.class);
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
|
||||
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
|
||||
try {
|
||||
// 1. 转换异常
|
||||
Throwable exception = appResponse.getException();
|
||||
// 1.1 参数校验异常
|
||||
if (exception instanceof ConstraintViolationException) {
|
||||
exception = this.constraintViolationExceptionHandler((ConstraintViolationException) exception);
|
||||
// 1. ServiceException 业务异常,因为不会有序列化问题,所以无需处理
|
||||
} else if (exception instanceof ServiceException) {
|
||||
// 1.3 其它异常,转换成 GlobalException 全局异常,避免可能存在的反序列化问题
|
||||
} else {
|
||||
exception = this.defaultExceptionHandler(exception, invocation);
|
||||
assert exception != null;
|
||||
}
|
||||
// 2. 根据不同的方法 schema 返回结果
|
||||
// 2.1 如果是 ServiceException 异常,并且返回参数类型是 CommonResult 的情况,则将转换成 CommonResult 返回
|
||||
if (isReturnCommonResult(invocation) && exception instanceof ServiceException) {
|
||||
appResponse.setException(null); // 一定要清空异常
|
||||
appResponse.setValue(CommonResult.error((ServiceException) exception));
|
||||
// 2.2 如果是 GlobalException 全局异常,则直接抛出
|
||||
} else {
|
||||
// TODO 优化点:尝试修改成 RpcException
|
||||
appResponse.setException(exception);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
|
||||
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
private boolean isReturnCommonResult(Invocation invocation) {
|
||||
if (!(invocation instanceof RpcInvocation)) {
|
||||
return false;
|
||||
}
|
||||
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
|
||||
Type[] returnTypes = rpcInvocation.getReturnTypes();
|
||||
if (returnTypes.length == 0) {
|
||||
return false;
|
||||
}
|
||||
Type returnType = returnTypes[0];
|
||||
if (!(returnType instanceof Class)) {
|
||||
return false;
|
||||
}
|
||||
Class returnClass = (Class) returnType;
|
||||
return returnClass == CommonResult.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Validator 校验不通过产生的异常
|
||||
*/
|
||||
private GlobalException constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
||||
logger.warn("[constraintViolationExceptionHandler]", ex);
|
||||
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
||||
return new GlobalException(BAD_REQUEST.getCode(),
|
||||
String.format("请求参数不正确:%s", constraintViolation.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统异常,兜底处理所有的一切
|
||||
*/
|
||||
private GlobalException defaultExceptionHandler(Throwable exception, Invocation invocation) {
|
||||
logger.error("[defaultExceptionHandler][service({}) method({}) params({}) 执行异常]",
|
||||
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), invocation.getArguments(), exception);
|
||||
// 如果已经是 GlobalException 全局异常,直接返回即可
|
||||
if (exception instanceof GlobalException) {
|
||||
return (GlobalException) exception;
|
||||
}
|
||||
return new GlobalException(INTERNAL_SERVER_ERROR)
|
||||
.setDetailMessage(this.buildDetailMessage(exception, invocation));
|
||||
}
|
||||
|
||||
private String buildDetailMessage(Throwable exception, Invocation invocation) {
|
||||
return String.format("Service(%s) Method(%s) 发生异常(%s)",
|
||||
invocation.getTargetServiceUniqueName(), invocation.getMethodName(), ExceptionUtil.getRootCauseMessage(exception));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.mall.dubbo.core.filter;
|
||||
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor;
|
||||
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.*;
|
||||
import org.apache.dubbo.rpc.cluster.router.tag.TagRouter;
|
||||
|
||||
/**
|
||||
* 基于 Dubbo 标签路由规则(http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html),实现如下功能:
|
||||
* 1. 本地开发调试时,在带有 Dubbo Tag 的情况下,优先调用指定 Tag 的服务提供者。这样,我们可以将本地启动的服务提供者打上相应的 Tag,即可优先调用本地;
|
||||
* 并且,前端在调用开发环境上的 Dubbo 服务时,因为不带有 Dubbo Tag,所以不会调用到后端开发本地启动的 Dubbo 服务提供者;
|
||||
* 2. TODO 优化点:蓝绿发布、灰度发布
|
||||
*
|
||||
* 实现逻辑为:
|
||||
* 1. 对于 Consumer 方,在调用 Provider 时,{@link DubboConsumerRouterTagClusterInterceptor} 会将 {@link DubboRouterTagContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
|
||||
* 同时,Dubbo 自带 {@link TagRouter},会根据该参数,会选择符合该 Tag 的 Provider。
|
||||
* 2. 对于 Provider 方,在通过 Dubbo 隐式传参获得到 Tag 时,会设置到 {@link DubboRouterTagContextHolder} 中。
|
||||
* 这样,在 Provider 作为 Consumer 角色时,调用其它 Provider 时,可以继续实现标签路由的功能。
|
||||
*/
|
||||
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
|
||||
public class DubboProviderRouterTagFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
// 从 Dubbo 隐式传参获得 Dubbo Tag
|
||||
String dubboTag = invocation.getAttachment(CommonConstants.TAG_KEY);
|
||||
boolean hasDubboTag = StringUtils.hasText(dubboTag);
|
||||
if (hasDubboTag) {
|
||||
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
|
||||
}
|
||||
// 继续调用
|
||||
try {
|
||||
return invoker.invoke(invocation);
|
||||
} finally {
|
||||
// 清理
|
||||
if (hasDubboTag) {
|
||||
DubboRouterTagContextHolder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.mall.dubbo.core.router;
|
||||
|
||||
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
|
||||
|
||||
/**
|
||||
* Dubbo 路由 Tag 的上下文
|
||||
*
|
||||
* @see DubboProviderRouterTagFilter
|
||||
* @see cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor
|
||||
*/
|
||||
public class DubboRouterTagContextHolder {
|
||||
|
||||
private static ThreadLocal<String> tagContext = new ThreadLocal<>();
|
||||
|
||||
public static void setTag(String tag) {
|
||||
tagContext.set(tag);
|
||||
}
|
||||
|
||||
public static String getTag() {
|
||||
return tagContext.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
tagContext.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.mall.dubbo.core.web;
|
||||
|
||||
import cn.iocoder.common.framework.util.OSUtils;
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor;
|
||||
import cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter;
|
||||
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Dubbo 路由标签的 Web 拦截器,将请求 Header 中的 {@link #HEADER_DUBBO_TAG} 设置到 {@link DubboRouterTagContextHolder} 中。
|
||||
*
|
||||
* @see DubboProviderRouterTagFilter
|
||||
* @see DubboConsumerRouterTagClusterInterceptor
|
||||
*/
|
||||
public class DubboRouterTagWebInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final String HEADER_DUBBO_TAG = "dubbo-tag";
|
||||
|
||||
private static final String HOST_NAME_VALUE = "${HOSTNAME}";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String tag = request.getHeader(HEADER_DUBBO_TAG);
|
||||
if (StringUtils.hasText(tag)) {
|
||||
// 特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做
|
||||
if (HOST_NAME_VALUE.equals(tag)) {
|
||||
tag = OSUtils.getHostName();
|
||||
}
|
||||
// 设置到 DubboRouterTagContextHolder 上下文
|
||||
DubboRouterTagContextHolder.setTag(tag);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
DubboRouterTagContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
dubboExceptionFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderExceptionFilter
|
||||
dubboProviderRouterTagFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter
|
||||
@@ -0,0 +1 @@
|
||||
dubboConsumerRouterTagClusterInterceptor=cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor
|
||||
@@ -0,0 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.dubbo.config.DubboWebAutoConfiguration
|
||||
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
cn.iocoder.mall.dubbo.config.DubboEnvironmentPostProcessor
|
||||
48
归档/common/mall-spring-boot-starter-security-admin/pom.xml
Normal file
48
归档/common/mall-spring-boot-starter-security-admin/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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>mall-spring-boot-starter-security-admin</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Mall 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-web</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-security-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.iocoder.mall.security.admin.config;
|
||||
|
||||
import cn.iocoder.mall.security.admin.core.interceptor.AdminDemoInterceptor;
|
||||
import cn.iocoder.mall.security.admin.core.interceptor.AdminSecurityInterceptor;
|
||||
import cn.iocoder.mall.web.config.CommonWebAutoConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureAfter(CommonWebAutoConfiguration.class) // 在 CommonWebAutoConfiguration 之后自动配置,保证过滤器的顺序
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@EnableConfigurationProperties(AdminSecurityProperties.class)
|
||||
public class AdminSecurityAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AdminSecurityProperties adminSecurityProperties() {
|
||||
return new AdminSecurityProperties();
|
||||
}
|
||||
|
||||
// ========== 拦截器相关 ==========
|
||||
|
||||
@Bean
|
||||
public AdminSecurityInterceptor adminSecurityInterceptor() {
|
||||
return new AdminSecurityInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AdminDemoInterceptor adminDemoInterceptor() {
|
||||
return new AdminDemoInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
AdminSecurityProperties properties = this.adminSecurityProperties();
|
||||
// AdminSecurityInterceptor 拦截器
|
||||
registry.addInterceptor(this.adminSecurityInterceptor())
|
||||
.excludePathPatterns(properties.getIgnorePaths())
|
||||
.excludePathPatterns(properties.getDefaultIgnorePaths());
|
||||
logger.info("[addInterceptors][加载 AdminSecurityInterceptor 拦截器完成]");
|
||||
// AdminDemoInterceptor 拦截器
|
||||
if (Boolean.TRUE.equals(properties.getDemo())) {
|
||||
registry.addInterceptor(this.adminDemoInterceptor())
|
||||
.excludePathPatterns(properties.getIgnorePaths())
|
||||
.excludePathPatterns(properties.getDefaultIgnorePaths());
|
||||
logger.info("[addInterceptors][加载 AdminDemoInterceptor 拦截器完成]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.mall.security.admin.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("mall.security.admin")
|
||||
public class AdminSecurityProperties {
|
||||
|
||||
private static final String[] DEFAULT_IGNORE_PATHS = new String[]{
|
||||
// Swagger 相关
|
||||
"/doc.html", "/swagger-resources", "/swagger-resources/**", "/webjars/**",
|
||||
// Actuator 相关
|
||||
};
|
||||
|
||||
/**
|
||||
* 演示模式 - 默认值(关闭)
|
||||
*/
|
||||
private static final Boolean DEFAULT_DEMO = false;
|
||||
|
||||
/**
|
||||
* 自定义忽略 Path
|
||||
*/
|
||||
private String[] ignorePaths = new String[0];
|
||||
/**
|
||||
* 默认忽略 Path
|
||||
*/
|
||||
private String[] defaultIgnorePaths = DEFAULT_IGNORE_PATHS;
|
||||
/**
|
||||
* 是否开启演示模式
|
||||
*/
|
||||
private Boolean demo = DEFAULT_DEMO;
|
||||
|
||||
public String[] getIgnorePaths() {
|
||||
return ignorePaths;
|
||||
}
|
||||
|
||||
public AdminSecurityProperties setIgnorePaths(String[] ignorePaths) {
|
||||
this.ignorePaths = ignorePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String[] getDefaultIgnorePaths() {
|
||||
return defaultIgnorePaths;
|
||||
}
|
||||
|
||||
public AdminSecurityProperties setDefaultIgnorePaths(String[] defaultIgnorePaths) {
|
||||
this.defaultIgnorePaths = defaultIgnorePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getDemo() {
|
||||
return demo;
|
||||
}
|
||||
|
||||
public AdminSecurityProperties setDemo(Boolean demo) {
|
||||
this.demo = demo;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.mall.security.admin.core.context;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* Admin Security 上下文
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminSecurityContext {
|
||||
|
||||
/**
|
||||
* 管理员编号
|
||||
*/
|
||||
private Integer adminId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.mall.security.admin.core.context;
|
||||
|
||||
/**
|
||||
* {@link AdminSecurityContext} Holder
|
||||
*
|
||||
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
|
||||
*/
|
||||
public class AdminSecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<AdminSecurityContext> SECURITY_CONTEXT = new ThreadLocal<>();
|
||||
|
||||
public static void setContext(AdminSecurityContext context) {
|
||||
SECURITY_CONTEXT.set(context);
|
||||
}
|
||||
|
||||
public static AdminSecurityContext getContext() {
|
||||
AdminSecurityContext ctx = SECURITY_CONTEXT.get();
|
||||
// 为空时,设置一个空的进去
|
||||
if (ctx == null) {
|
||||
ctx = new AdminSecurityContext();
|
||||
SECURITY_CONTEXT.set(ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
SECURITY_CONTEXT.remove();
|
||||
}
|
||||
|
||||
public static Integer getAdminId() {
|
||||
return getContext().getAdminId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.mall.security.admin.core.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContextHolder;
|
||||
import cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Admin 演示拦截器
|
||||
*
|
||||
* 这是个比较“奇怪”的拦截器,用于演示的管理员账号,禁止使用 POST 请求,从而实现即达到阉割版的演示的效果,又避免影响了数据
|
||||
*/
|
||||
public class AdminDemoInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 当 Admin 编号等于 1 时,约定为演示账号
|
||||
// TODO 芋艿,后续去优化
|
||||
if (Objects.equals(AdminSecurityContextHolder.getAdminId(), 1)
|
||||
&& request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
|
||||
throw ServiceExceptionUtil.exception(SystemErrorCodeConstants.PERMISSION_DEMO_PERMISSION_DENY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.iocoder.mall.security.admin.core.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.enums.UserTypeEnum;
|
||||
import cn.iocoder.common.framework.exception.GlobalException;
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContext;
|
||||
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContextHolder;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.OAuthFeign;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.permission.PermissionFeign;
|
||||
import cn.iocoder.mall.systemservice.rpc.permission.dto.PermissionCheckDTO;
|
||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
||||
import cn.iocoder.security.annotations.RequiresNone;
|
||||
import cn.iocoder.security.annotations.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||
import static cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants.OAUTH_USER_TYPE_ERROR;
|
||||
|
||||
public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OAuthFeign oAuthFeign;
|
||||
@Autowired
|
||||
private PermissionFeign permissionFeign;
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 获得访问令牌
|
||||
Integer adminId = this.obtainAdminId(request);
|
||||
// 校验认证
|
||||
this.checkAuthentication((HandlerMethod) handler, adminId);
|
||||
// 校验权限
|
||||
this.checkPermission((HandlerMethod) handler, adminId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Integer obtainAdminId(HttpServletRequest request) {
|
||||
String accessToken = HttpUtil.obtainAuthorization(request);
|
||||
Integer adminId = null;
|
||||
if (accessToken != null) {
|
||||
CommonResult<OAuth2AccessTokenRespDTO> checkAccessTokenResult = oAuthFeign.checkAccessToken(accessToken);
|
||||
checkAccessTokenResult.checkError();
|
||||
// 校验用户类型正确
|
||||
if (!UserTypeEnum.ADMIN.getValue().equals(checkAccessTokenResult.getData().getUserType())) {
|
||||
throw ServiceExceptionUtil.exception(OAUTH_USER_TYPE_ERROR);
|
||||
}
|
||||
// 获得用户编号
|
||||
adminId = checkAccessTokenResult.getData().getUserId();
|
||||
// 设置到 Request 中
|
||||
CommonWebUtil.setUserId(request, adminId);
|
||||
CommonWebUtil.setUserType(request, UserTypeEnum.ADMIN.getValue());
|
||||
// 设置到
|
||||
AdminSecurityContext adminSecurityContext = new AdminSecurityContext().setAdminId(adminId);
|
||||
AdminSecurityContextHolder.setContext(adminSecurityContext);
|
||||
}
|
||||
return adminId;
|
||||
}
|
||||
|
||||
private void checkAuthentication(HandlerMethod handlerMethod, Integer adminId) {
|
||||
boolean requiresAuthenticate = !handlerMethod.hasMethodAnnotation(RequiresNone.class); // 对于 ADMIN 来说,默认需登录
|
||||
if (requiresAuthenticate && adminId == null) {
|
||||
throw new GlobalException(UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPermission(HandlerMethod handlerMethod, Integer adminId) {
|
||||
RequiresPermissions requiresPermissions = handlerMethod.getMethodAnnotation(RequiresPermissions.class);
|
||||
if (requiresPermissions == null) {
|
||||
return;
|
||||
}
|
||||
String[] permissions = requiresPermissions.value();
|
||||
if (CollectionUtils.isEmpty(permissions)) {
|
||||
return;
|
||||
}
|
||||
// 权限验证
|
||||
permissionFeign.checkPermission(new PermissionCheckDTO().setAdminId(adminId).setPermissions(Arrays.asList(permissions)))
|
||||
.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 清空 SecurityContext
|
||||
AdminSecurityContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.security.admin.config.AdminSecurityAutoConfiguration
|
||||
47
归档/common/mall-spring-boot-starter-security-user/pom.xml
Normal file
47
归档/common/mall-spring-boot-starter-security-user/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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>mall-spring-boot-starter-security-user</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Mall 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-security-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.mall.security.user.config;
|
||||
|
||||
import cn.iocoder.mall.security.user.core.interceptor.UserSecurityInterceptor;
|
||||
import cn.iocoder.mall.web.config.CommonWebAutoConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureAfter(CommonWebAutoConfiguration.class) // 在 CommonWebAutoConfiguration 之后自动配置,保证过滤器的顺序
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@EnableConfigurationProperties(UserSecurityProperties.class)
|
||||
public class UserSecurityAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public UserSecurityProperties userSecurityProperties() {
|
||||
return new UserSecurityProperties();
|
||||
}
|
||||
|
||||
// ========== 拦截器相关 ==========
|
||||
|
||||
@Bean
|
||||
public UserSecurityInterceptor userSecurityInterceptor() {
|
||||
return new UserSecurityInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
UserSecurityProperties properties = this.userSecurityProperties();
|
||||
// UserSecurityInterceptor 拦截器
|
||||
registry.addInterceptor(this.userSecurityInterceptor())
|
||||
.excludePathPatterns(properties.getIgnorePaths())
|
||||
.excludePathPatterns(properties.getDefaultIgnorePaths());;
|
||||
logger.info("[addInterceptors][加载 UserSecurityInterceptor 拦截器完成]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.iocoder.mall.security.user.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("mall.security.user")
|
||||
public class UserSecurityProperties {
|
||||
|
||||
private static final String[] DEFAULT_IGNORE_PATHS = new String[]{
|
||||
// Swagger 相关
|
||||
"/doc.html", "/swagger-resources", "/swagger-resources/**", "/webjars/**",
|
||||
// Actuator 相关
|
||||
};
|
||||
|
||||
/**
|
||||
* 自定义忽略 Path
|
||||
*/
|
||||
private String[] ignorePaths = new String[0];
|
||||
/**
|
||||
* 默认忽略 Path
|
||||
*/
|
||||
private String[] defaultIgnorePaths = DEFAULT_IGNORE_PATHS;
|
||||
|
||||
public String[] getIgnorePaths() {
|
||||
return ignorePaths;
|
||||
}
|
||||
|
||||
public UserSecurityProperties setIgnorePaths(String[] ignorePaths) {
|
||||
this.ignorePaths = ignorePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String[] getDefaultIgnorePaths() {
|
||||
return defaultIgnorePaths;
|
||||
}
|
||||
|
||||
public UserSecurityProperties setDefaultIgnorePaths(String[] defaultIgnorePaths) {
|
||||
this.defaultIgnorePaths = defaultIgnorePaths;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.mall.security.user.core.context;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* User Security 上下文
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class UserSecurityContext {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.mall.security.user.core.context;
|
||||
|
||||
/**
|
||||
* {@link UserSecurityContext} Holder
|
||||
*
|
||||
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
|
||||
*/
|
||||
public class UserSecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<UserSecurityContext> SECURITY_CONTEXT = new ThreadLocal<UserSecurityContext>();
|
||||
|
||||
public static void setContext(UserSecurityContext context) {
|
||||
SECURITY_CONTEXT.set(context);
|
||||
}
|
||||
|
||||
public static UserSecurityContext getContext() {
|
||||
UserSecurityContext ctx = SECURITY_CONTEXT.get();
|
||||
// 为空时,设置一个空的进去
|
||||
if (ctx == null) {
|
||||
ctx = new UserSecurityContext();
|
||||
SECURITY_CONTEXT.set(ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static Integer getUserId() {
|
||||
UserSecurityContext ctx = SECURITY_CONTEXT.get();
|
||||
return ctx != null ? ctx.getUserId() : null;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
SECURITY_CONTEXT.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.mall.security.user.core.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.enums.UserTypeEnum;
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.security.user.core.context.UserSecurityContext;
|
||||
import cn.iocoder.mall.security.user.core.context.UserSecurityContextHolder;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.OAuthFeign;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
||||
import cn.iocoder.security.annotations.RequiresAuthenticate;
|
||||
import cn.iocoder.security.annotations.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||
import static cn.iocoder.mall.systemservice.enums.SystemErrorCodeConstants.OAUTH_USER_TYPE_ERROR;
|
||||
|
||||
public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Autowired
|
||||
private OAuthFeign oAuthFeign;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 获得访问令牌
|
||||
Integer userId = this.obtainUserId(request);
|
||||
// 校验认证
|
||||
this.checkAuthentication((HandlerMethod) handler, userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Integer obtainUserId(HttpServletRequest request) {
|
||||
String accessToken = HttpUtil.obtainAuthorization(request);
|
||||
Integer userId = null;
|
||||
if (accessToken != null) {
|
||||
CommonResult<OAuth2AccessTokenRespDTO> checkAccessTokenResult = oAuthFeign.checkAccessToken(accessToken);
|
||||
checkAccessTokenResult.checkError();
|
||||
// 校验用户类型正确
|
||||
if (!UserTypeEnum.USER.getValue().equals(checkAccessTokenResult.getData().getUserType())) {
|
||||
throw ServiceExceptionUtil.exception(OAUTH_USER_TYPE_ERROR);
|
||||
}
|
||||
// 获得用户编号
|
||||
userId = checkAccessTokenResult.getData().getUserId();
|
||||
// 设置到 Request 中
|
||||
CommonWebUtil.setUserId(request, userId);
|
||||
CommonWebUtil.setUserType(request, UserTypeEnum.USER.getValue());
|
||||
// 设置到
|
||||
UserSecurityContext userSecurityContext = new UserSecurityContext().setUserId(userId);
|
||||
UserSecurityContextHolder.setContext(userSecurityContext);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
private void checkAuthentication(HandlerMethod handlerMethod, Integer userId) {
|
||||
boolean requiresAuthenticate = false; // 对于 USER 来说,默认无需登录
|
||||
if (handlerMethod.hasMethodAnnotation(RequiresAuthenticate.class)
|
||||
|| handlerMethod.hasMethodAnnotation(RequiresPermissions.class)) { // 如果需要权限验证,也认为需要认证
|
||||
requiresAuthenticate = true;
|
||||
}
|
||||
if (requiresAuthenticate && userId == null) {
|
||||
throw ServiceExceptionUtil.exception(UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 清空 SecurityContext
|
||||
UserSecurityContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.security.user.config.UserSecurityAutoConfiguration
|
||||
25
归档/common/mall-spring-boot-starter-sentry/pom.xml
Normal file
25
归档/common/mall-spring-boot-starter-sentry/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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>mall-spring-boot-starter-sentry</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-logback</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.mall.sentry.config;
|
||||
|
||||
import cn.iocoder.mall.sentry.resolver.DoNothingExceptionResolver;
|
||||
import io.sentry.spring.SentryExceptionResolver;
|
||||
import io.sentry.spring.autoconfigure.SentryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
|
||||
/**
|
||||
* 自定义的 Sentry 自动配置类
|
||||
*
|
||||
* @author Hccake 2020/8/6
|
||||
* @version 1.0
|
||||
*/
|
||||
@ConditionalOnClass({HandlerExceptionResolver.class, SentryExceptionResolver.class})
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnProperty(name = "sentry.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class CustomSentryAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 用于覆盖原有的 SentryStarter 提供的 SentryExceptionResolver 操作
|
||||
* 解决使用 log appender 形式推送错误信息与全局异常捕获导致重复推送的情况
|
||||
*
|
||||
* @return DoNothingExceptionResolver
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnClass(SentryAutoConfiguration.class)
|
||||
@ConditionalOnMissingBean(SentryExceptionResolver.class)
|
||||
public SentryExceptionResolver doNothingExceptionResolver() {
|
||||
return new DoNothingExceptionResolver();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.mall.sentry.resolver;
|
||||
|
||||
import io.sentry.spring.SentryExceptionResolver;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 默认什么也不做的 SentryExceptionResolver
|
||||
*
|
||||
* @author Hccake 2020/8/6
|
||||
* @version 1.0
|
||||
*/
|
||||
public class DoNothingExceptionResolver extends SentryExceptionResolver {
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveException(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler,
|
||||
Exception ex) {
|
||||
// do nothing here
|
||||
|
||||
// null = run other HandlerExceptionResolvers to actually handle the exception
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.sentry.config.CustomSentryAutoConfiguration
|
||||
46
归档/common/mall-spring-boot-starter-system-error-code/pom.xml
Normal file
46
归档/common/mall-spring-boot-starter-system-error-code/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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>mall-spring-boot-starter-system-error-code</artifactId>
|
||||
<description>
|
||||
错误码 ErrorCode 的自动配置功能,提供如下功能:
|
||||
1. 远程读取:项目启动时,从 system-service 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提水可配置;
|
||||
2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-service 服务加载最新的 ErrorCode 错误码;
|
||||
3. 自动写入:项目启动时,将项目本地的错误码写到 system-service 服务中,方便管理员在管理后台编辑;
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Mall 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.mall.system.errorcode.config;
|
||||
|
||||
import cn.iocoder.mall.system.errorcode.core.ErrorCodeAutoGenerator;
|
||||
import cn.iocoder.mall.system.errorcode.core.ErrorCodeRemoteLoader;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ErrorCodeProperties.class)
|
||||
@EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
|
||||
public class ErrorCodeAutoConfiguration {
|
||||
|
||||
// @Bean
|
||||
// public ErrorCodeAutoGenerator errorCodeAutoGenerator(ErrorCodeProperties errorCodeProperties) {
|
||||
// return new ErrorCodeAutoGenerator(errorCodeProperties.getGroup())
|
||||
// .setErrorCodeConstantsClass(errorCodeProperties.getConstantsClass());
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public ErrorCodeRemoteLoader errorCodeRemoteLoader(ErrorCodeProperties errorCodeProperties) {
|
||||
// return new ErrorCodeRemoteLoader(errorCodeProperties.getGroup());
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.mall.system.errorcode.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ConfigurationProperties("mall.error-code")
|
||||
@Validated
|
||||
public class ErrorCodeProperties {
|
||||
|
||||
/**
|
||||
* 应用分组
|
||||
*/
|
||||
@NotNull(message = "应用分组不能为空,请设置 mall.error-code.group 配置项,推荐直接使用 spring. application.name 配置项")
|
||||
private String group;
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*/
|
||||
private String constantsClass;
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public ErrorCodeProperties setGroup(String group) {
|
||||
this.group = group;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getConstantsClass() {
|
||||
return constantsClass;
|
||||
}
|
||||
|
||||
public ErrorCodeProperties setConstantsClass(String constantsClass) {
|
||||
this.constantsClass = constantsClass;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package cn.iocoder.mall.system.errorcode.core;
|
||||
|
||||
import cn.iocoder.common.framework.exception.ErrorCode;
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.ErrorCodeFeign;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.dto.ErrorCodeAutoGenerateDTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ErrorCodeAutoGenerator {
|
||||
//
|
||||
// private Logger logger = LoggerFactory.getLogger(ErrorCodeAutoGenerator.class);
|
||||
//
|
||||
// /**
|
||||
// * 应用分组
|
||||
// */
|
||||
// private final String group;
|
||||
// /**
|
||||
// * 错误码枚举类
|
||||
// */
|
||||
// private String errorCodeConstantsClass;
|
||||
//
|
||||
//
|
||||
// @Autowired
|
||||
// private ErrorCodeFeign errorCodeFeign;
|
||||
// public ErrorCodeAutoGenerator(String group) {
|
||||
// this.group = group;
|
||||
// }
|
||||
//
|
||||
// public ErrorCodeAutoGenerator setErrorCodeConstantsClass(String errorCodeConstantsClass) {
|
||||
// this.errorCodeConstantsClass = errorCodeConstantsClass;
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// @EventListener(ApplicationReadyEvent.class)
|
||||
// @Async // 异步,保证项目的启动过程,毕竟非关键流程
|
||||
// public void execute() {
|
||||
// // 校验 errorCodeConstantsClass 参数
|
||||
// if (!StringUtils.hasText(errorCodeConstantsClass)) {
|
||||
// logger.info("[execute][未配置 mall.error-code.constants-class 配置项,不进行自动写入到 system-service 服务]");
|
||||
// return;
|
||||
// }
|
||||
// Class errorCodeConstantsClazz;
|
||||
// try {
|
||||
// errorCodeConstantsClazz = Class.forName(errorCodeConstantsClass);
|
||||
// } catch (ClassNotFoundException e) {
|
||||
// logger.error("[execute][配置的 ({}) 找不到对应的类]", errorCodeConstantsClass);
|
||||
// return;
|
||||
// }
|
||||
// // 写入 system-service 服务
|
||||
// logger.info("[execute][自动将 ({}) 类的错误码,准备写入到 system-service 服务]", errorCodeConstantsClass);
|
||||
// List<ErrorCodeAutoGenerateDTO> autoGenerateDTOs = new ArrayList<>();
|
||||
// Arrays.stream(errorCodeConstantsClazz.getFields()).forEach(field -> {
|
||||
// if (field.getType() != ErrorCode.class) {
|
||||
// return;
|
||||
// }
|
||||
// try {
|
||||
// // TODO 芋艿:校验是否重复了;
|
||||
// ErrorCode errorCode = (ErrorCode) field.get(errorCodeConstantsClazz);
|
||||
// autoGenerateDTOs.add(new ErrorCodeAutoGenerateDTO().setGroup(group)
|
||||
// .setCode(errorCode.getCode()).setMessage(errorCode.getMessage()));
|
||||
// } catch (IllegalAccessException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// });
|
||||
// CommonResult<Boolean> autoGenerateErrorCodesResult = errorCodeFeign.autoGenerateErrorCodes(autoGenerateDTOs);
|
||||
// if (autoGenerateErrorCodesResult.isSuccess()) {
|
||||
// logger.info("[execute][自动将 ({}) 类的错误码,成功写入到 system-service 服务]", errorCodeConstantsClass);
|
||||
// } else {
|
||||
// logger.error("[execute][自动将 ({}) 类的错误码,失败写入到 system-service 服务,原因为 ({}/{}/{})]", errorCodeConstantsClass,
|
||||
// autoGenerateErrorCodesResult.getCode(), autoGenerateErrorCodesResult.getMessage(), autoGenerateErrorCodesResult.getDetailMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.iocoder.mall.system.errorcode.core;
|
||||
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.ErrorCodeFeign;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.vo.ErrorCodeVO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class ErrorCodeRemoteLoader {
|
||||
//
|
||||
// private static final int REFRESH_ERROR_CODE_PERIOD = 60 * 1000;
|
||||
//
|
||||
// private Logger logger = LoggerFactory.getLogger(ErrorCodeRemoteLoader.class);
|
||||
//
|
||||
// /**
|
||||
// * 应用分组
|
||||
// */
|
||||
// private final String group;
|
||||
//
|
||||
// @Autowired
|
||||
// private ErrorCodeFeign errorCodeFeign;
|
||||
// private Date maxUpdateTime;
|
||||
//
|
||||
// public ErrorCodeRemoteLoader(String group) {
|
||||
// this.group = group;
|
||||
// }
|
||||
//
|
||||
// @EventListener(ApplicationReadyEvent.class)
|
||||
// public void loadErrorCodes() {
|
||||
// // 从 errorCodeFeign 全量加载 ErrorCode 错误码
|
||||
// CommonResult<List<ErrorCodeVO>> listErrorCodesResult = errorCodeFeign.listErrorCodes(group, null);
|
||||
// listErrorCodesResult.checkError();
|
||||
// logger.info("[loadErrorCodes][从 group({}) 全量加载到 {} 个 ErrorCode 错误码]", group, listErrorCodesResult.getData().size());
|
||||
// // 写入到 ServiceExceptionUtil 到
|
||||
// listErrorCodesResult.getData().forEach(errorCodeVO -> {
|
||||
// ServiceExceptionUtil.put(errorCodeVO.getCode(), errorCodeVO.getMessage());
|
||||
// // 记录下更新时间,方便增量更新
|
||||
// maxUpdateTime = DateUtil.max(maxUpdateTime, errorCodeVO.getUpdateTime());
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @Scheduled(fixedDelay = REFRESH_ERROR_CODE_PERIOD, initialDelay = REFRESH_ERROR_CODE_PERIOD)
|
||||
// public void refreshErrorCodes() {
|
||||
// // 从 errorCodeFeign 增量加载 ErrorCode 错误码
|
||||
// // TODO 优化点:假设删除错误码的配置,会存在问题;
|
||||
// CommonResult<List<ErrorCodeVO>> listErrorCodesResult = errorCodeFeign.listErrorCodes(group, maxUpdateTime);
|
||||
// listErrorCodesResult.checkError();
|
||||
// if (CollectionUtils.isEmpty(listErrorCodesResult.getData())) {
|
||||
// return;
|
||||
// }
|
||||
// logger.info("[refreshErrorCodes][从 group({}) 增量加载到 {} 个 ErrorCode 错误码]", group, listErrorCodesResult.getData().size());
|
||||
// // 写入到 ServiceExceptionUtil 到
|
||||
// listErrorCodesResult.getData().forEach(errorCodeVO -> {
|
||||
// ServiceExceptionUtil.put(errorCodeVO.getCode(), errorCodeVO.getMessage());
|
||||
// // 记录下更新时间,方便增量更新
|
||||
// maxUpdateTime = DateUtil.max(maxUpdateTime, errorCodeVO.getUpdateTime());
|
||||
// });
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.system.errorcode.config.ErrorCodeAutoConfiguration
|
||||
34
归档/common/mall-spring-boot-starter-xxl-job/pom.xml
Normal file
34
归档/common/mall-spring-boot-starter-xxl-job/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.iocoder.mall.xxljob.config;
|
||||
|
||||
import com.xxl.job.core.executor.XxlJobExecutor;
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* XXL-Job 自动配置类
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(XxlJobSpringExecutor.class)
|
||||
@ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
@EnableConfigurationProperties({XxlJobProperties.class})
|
||||
public class XxlJobAutoConfiguration {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(XxlJobAutoConfiguration.class);
|
||||
|
||||
private final XxlJobProperties properties;
|
||||
|
||||
public XxlJobAutoConfiguration(XxlJobProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public XxlJobExecutor xxlJobExecutor() {
|
||||
LOGGER.info("初始化 XXL-Job 执行器的配置");
|
||||
|
||||
// 参数校验
|
||||
XxlJobProperties.AdminProperties admin = this.properties.getAdmin();
|
||||
XxlJobProperties.ExecutorProperties executor = this.properties.getExecutor();
|
||||
Objects.requireNonNull(admin, "xxl job admin properties must not be null.");
|
||||
Objects.requireNonNull(executor, "xxl job executor properties must not be null.");
|
||||
|
||||
// 初始化执行器
|
||||
XxlJobExecutor xxlJobExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobExecutor.setIp(executor.getIp());
|
||||
xxlJobExecutor.setPort(executor.getPort());
|
||||
xxlJobExecutor.setAppname(executor.getAppName());
|
||||
xxlJobExecutor.setLogPath(executor.getLogPath());
|
||||
xxlJobExecutor.setLogRetentionDays(executor.getLogRetentionDays());
|
||||
xxlJobExecutor.setAdminAddresses(admin.getAddresses());
|
||||
xxlJobExecutor.setAccessToken(this.properties.getAccessToken());
|
||||
return xxlJobExecutor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package cn.iocoder.mall.xxljob.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* XXL-Job 配置类
|
||||
*/
|
||||
@ConfigurationProperties("xxl.job")
|
||||
public class XxlJobProperties {
|
||||
|
||||
/**
|
||||
* 是否开启,默认为 true 关闭
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 控制器配置
|
||||
*/
|
||||
private AdminProperties admin;
|
||||
/**
|
||||
* 执行器配置
|
||||
*/
|
||||
private ExecutorProperties executor;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
if (enabled != null) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
if (accessToken != null && accessToken.trim().length() > 0) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
public AdminProperties getAdmin() {
|
||||
return admin;
|
||||
}
|
||||
|
||||
public void setAdmin(AdminProperties admin) {
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
public ExecutorProperties getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void setExecutor(ExecutorProperties executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* XXL-Job 调度器配置类
|
||||
*/
|
||||
public static class AdminProperties {
|
||||
|
||||
/**
|
||||
* 调度器地址
|
||||
*/
|
||||
private String addresses;
|
||||
|
||||
public String getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(String addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdminProperties{" +
|
||||
"addresses='" + addresses + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* XXL-Job 执行器配置类
|
||||
*/
|
||||
public static class ExecutorProperties {
|
||||
|
||||
/**
|
||||
* 默认端口
|
||||
*
|
||||
* 这里使用 -1 表示随机
|
||||
*/
|
||||
private static final Integer PORT_DEFAULT = -1;
|
||||
|
||||
/**
|
||||
* 默认日志保留天数
|
||||
*
|
||||
* 默认为 -1,不清理,永久保留
|
||||
*/
|
||||
private static final Integer LOG_RETENTION_DAYS_DEFAULT = -1;
|
||||
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
private String appName;
|
||||
/**
|
||||
* 执行器的 IP
|
||||
*/
|
||||
private String ip;
|
||||
/**
|
||||
* 执行器的 Port
|
||||
*/
|
||||
private Integer port = PORT_DEFAULT;
|
||||
/**
|
||||
* 日志地址
|
||||
*/
|
||||
private String logPath;
|
||||
/**
|
||||
* 日志保留天数
|
||||
*/
|
||||
private Integer logRetentionDays = LOG_RETENTION_DAYS_DEFAULT;
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
public String getLogPath() {
|
||||
return logPath;
|
||||
}
|
||||
|
||||
public void setLogPath(String logPath) {
|
||||
this.logPath = logPath;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Integer getLogRetentionDays() {
|
||||
return logRetentionDays;
|
||||
}
|
||||
|
||||
public void setLogRetentionDays(Integer logRetentionDays) {
|
||||
this.logRetentionDays = logRetentionDays;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration
|
||||
55
归档/common/mall-spring-boot/pom.xml
Normal file
55
归档/common/mall-spring-boot/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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>mall-spring-boot</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Mall 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.mall.spring.boot.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass({MeterRegistryCustomizer.class})
|
||||
@ConditionalOnProperty(prefix = "management.metrics", value = "enable", matchIfMissing = true) // 允许使用 management.metrics.enable=false 禁用 Metrics
|
||||
public class MetricsAutoConfiguration {
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String applicationName;
|
||||
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("application", applicationName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.spring.boot.metrics.MetricsAutoConfiguration
|
||||
43
归档/common/pom.xml
Normal file
43
归档/common/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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>onemall</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>common-framework</module>
|
||||
<module>mall-spring-boot</module>
|
||||
<module>mall-spring-boot-starter-swagger</module>
|
||||
<module>mall-spring-boot-starter-web</module>
|
||||
<module>mall-security-annotations</module>
|
||||
<module>mall-spring-boot-starter-security-admin</module>
|
||||
<module>mall-spring-boot-starter-security-user</module>
|
||||
<module>mall-spring-boot-starter-sentry</module>
|
||||
<module>mall-spring-boot-starter-mybatis</module>
|
||||
<module>mall-spring-boot-starter-dubbo</module>
|
||||
<module>mall-spring-boot-starter-system-error-code</module>
|
||||
<module>mall-spring-boot-starter-rocketmq</module>
|
||||
<module>mall-spring-boot-starter-xxl-job</module>
|
||||
<module>mall-spring-boot-starter-redis</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-dependencies</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
11
归档/docs/README.md
Normal file
11
归档/docs/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
* setup
|
||||
> 提供安装指南
|
||||
* [搭建调试环境](https://gitee.com/zhijiantianya/onemall/docs/setup/quick-start.md)
|
||||
|
||||
* sql
|
||||
> 提供 SQL 文件
|
||||
|
||||
* guides
|
||||
> 提供入门指南
|
||||
* TODO
|
||||
34
归档/docs/guides/功能列表/功能列表-H5 商城.md
Normal file
34
归档/docs/guides/功能列表/功能列表-H5 商城.md
Normal file
@@ -0,0 +1,34 @@
|
||||
如下是 onemall 的功能列表。
|
||||
|
||||
* 当功能被完成时,会标记已完成。
|
||||
* 未完成的功能,欢迎一起来开发,特别是【待认领】的任务。
|
||||
|
||||
- [x] 首页
|
||||
- [x] 首页广告
|
||||
- [x] 商品推荐(手动)
|
||||
- 商品相关
|
||||
- [x] 分类列表
|
||||
- [x] 商品搜索
|
||||
- [x] 商品列表(基于分类)
|
||||
- [ ] 商品列表(基于促销活动)
|
||||
- [x] 商品详情
|
||||
- [ ] 商品收藏 @笑笑生
|
||||
- 订单相关
|
||||
- [x] 下单(直接购买)
|
||||
- [x] 下单(购物车购买)
|
||||
- [ ] 下单(拼团) @大太阳
|
||||
- [x] 订单列表
|
||||
- [x] 订单详情
|
||||
- [x] 支付
|
||||
- [ ] 退款
|
||||
- [x] 购物车
|
||||
- [x] 收获地址
|
||||
- 营销相关
|
||||
- [x] 优惠劵
|
||||
- [ ] 优惠码【待认领】
|
||||
- 用户相关
|
||||
- [x] 登陆
|
||||
- [x] 注册
|
||||
- [x] 个人信息
|
||||
- [ ] 手机改绑
|
||||
- [ ] 微信登陆 @To0R𓃰
|
||||
60
归档/docs/guides/功能列表/功能列表-管理后台.md
Normal file
60
归档/docs/guides/功能列表/功能列表-管理后台.md
Normal file
@@ -0,0 +1,60 @@
|
||||
如下是 onemall 的功能列表。
|
||||
|
||||
* 当功能被完成时,会标记已完成。
|
||||
* 未完成的功能,欢迎一起来开发,特别是【待认领】的任务。
|
||||
|
||||
- [ ] 概述 TODO【待认领】
|
||||
- [ ] 数据分析【待认领】
|
||||
- [ ] 商品分析 【@zhenxianyimeng】
|
||||
- [ ] 店铺资产【待认领】
|
||||
- [ ] 支付单 20% 【待认领】
|
||||
- [ ] 退款单 20% 【待认领】
|
||||
- TODO 需要补充
|
||||
- [ ] 店铺装修【迫切需要靠谱前端一起做】
|
||||
- [ ] H5 装修
|
||||
- [ ] 小程序装修
|
||||
- [ ] 自定义页面
|
||||
- [ ] 商品管理
|
||||
- [x] 发布商品
|
||||
- [x] 商品列表
|
||||
- [x] 展示类目
|
||||
- [ ] 品牌管理【开发中 @黑子】
|
||||
- [ ] 商品标签
|
||||
- [X] 商品规格页面
|
||||
- [ ] 订单管理
|
||||
- [x] 销售单
|
||||
- [x] 售后单
|
||||
- [ ] 订单评价【开发中 @wang171776704】
|
||||
- [ ] 会员管理
|
||||
- [ ] 会员资料 【开发中 @nengjie】
|
||||
- [ ] 会员等级
|
||||
- [ ] 会员积分
|
||||
- [ ] 用户标签
|
||||
- TODO 需要补充
|
||||
- [ ] 营销管理
|
||||
- [x] 首页广告
|
||||
- [x] 商品推荐
|
||||
- [x] 优惠劵
|
||||
- [ ] 优惠码【开发中 @native8623 2019-05-17】
|
||||
- [ ] 满减送 20% 【待认领】
|
||||
- [ ] 限制折扣 20% 【待认领】
|
||||
- [ ] 多人拼团【认领 @mijiu 2019-06-05】
|
||||
- [ ] 积分商城
|
||||
- [ ] 问卷调查
|
||||
- [ ] 幸运大转盘
|
||||
- [ ] 分销管理
|
||||
- [ ] 分销设置
|
||||
- [ ] 分销员管理
|
||||
- [ ] 提现管理
|
||||
- [ ] 系统管理
|
||||
- [x] 员工管理
|
||||
- [x] 角色管理 <!--【前端页面需要细化下】-->
|
||||
- [x] 权限管理 <!--【前端页面需要细化下】-->
|
||||
- [x] 部门管理 <!--【员工页面部门搜索需要优化】-->
|
||||
- [x] 数据字典
|
||||
- [x] 短信管理
|
||||
- [X] 短信模板
|
||||
- [ ] 短信发送日志【研发中 小范】
|
||||
- [ ] 员工操作日志
|
||||
- [ ] 访问日志【待认领】
|
||||
- [ ] 异常日志【待认领】
|
||||
190
归档/docs/setup/quick-start.md
Normal file
190
归档/docs/setup/quick-start.md
Normal file
@@ -0,0 +1,190 @@
|
||||
> 艿艿:本文暂时会写的比较简洁,如果有不懂的地方,请来[「交流群」](http://www.iocoder.cn/mall-user-group/?vip&gitee),艿艿来帮你解决。
|
||||
>
|
||||
> 交流群,我们提供了我们自己在使用的开发环境,搭建调试环境会更简便。
|
||||
|
||||
# 1. 概述
|
||||
|
||||
> 艿艿:本文暂时会写的比较简洁,如果有不懂的地方,请来[「交流群」](http://www.iocoder.cn/mall-user-group/?vip&gitee),艿艿来帮你解决。
|
||||
|
||||
本文,我们希望能带着胖友,快速搭建一个开发/调试环境。总的来说,我们需要安装如下东西:
|
||||
|
||||
* 后端
|
||||
* JDK 8+
|
||||
* Maven
|
||||
* IntelliJ IDEA
|
||||
|
||||
* 前端
|
||||
* NPM
|
||||
|
||||
# 2. 源码拉取
|
||||
|
||||
使用 IntelliJ IDEA 从 <https://gitee.com/zhijiantianya/onemall> 。拉取完成后,Maven 会下载依赖包,可能会花费一些时间,耐心等待下。
|
||||
|
||||
> 艿艿:也不要瞎等,咱继续顺着本文往下走。
|
||||
|
||||
# 3. MySQL
|
||||
|
||||
① 安装 MySQL 数据库
|
||||
|
||||
* Windows :参考 [《Windows 安装 MySQL》](https://juejin.im/post/5bdab0645188251e753c66f8)
|
||||
* Mac :参考 [《Mac 下安装与配置 MySQL》](https://www.jianshu.com/p/a8e4068a7a8a)
|
||||
|
||||
② 导入 SQL
|
||||
|
||||
将 [docs/sql](https://gitee.com/zhijiantianya/onemall/tree/master/docs/sql) 下的 SQL ,逐个导入到数据库中。
|
||||
|
||||
③ 修改项目中的 MySQL 配置
|
||||
|
||||
在 IDEA 中,搜索每个 `xxx-service-impl` 项目下的 `application.yaml` 文件,将数据库配置修改成连接你的。如下:
|
||||
|
||||
```YAML
|
||||
spring:
|
||||
# datasource
|
||||
datasource:
|
||||
url: jdbc:mysql://s1.iocoder.cn:3306/mall_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8 # 请修改成你本地的 MySQL url
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root # 请修改成你本地的 MySQL username
|
||||
password: zhuyang # 请修改成你本地的 MySQL password
|
||||
```
|
||||
|
||||
# 4. Zookeeper
|
||||
|
||||
① 安装 Zookeeper
|
||||
|
||||
* Windows :参考 [《Windows 下 ZooKeeper 的配置和启动步骤 —— 单机模式》](https://www.jianshu.com/p/66857cbccbd3)
|
||||
* Mac :参考 [《Zookeeper 安装及配置(Mac)》](https://www.jianshu.com/p/0ba61bf7149f)
|
||||
|
||||
② 修改项目中的 Zookeeper 配置
|
||||
|
||||
在 IDEA 中,搜索每个 `xxx-service-impl` 项目下的 `application.yaml` 文件,将 Zookeeper 配置修改成连接你的。如下:
|
||||
|
||||
```YAML
|
||||
# dubbo
|
||||
dubbo:
|
||||
application:
|
||||
name: product-service
|
||||
registry:
|
||||
address: zookeeper://127.0.0.1:2181 # 请修改成你本地的 Zookeeper url
|
||||
protocol:
|
||||
port: -1
|
||||
name: dubbo
|
||||
scan:
|
||||
base-packages: cn.iocoder.mall.product.service
|
||||
```
|
||||
|
||||
# 5. RocketMQ
|
||||
|
||||
① 安装 RocketMQ
|
||||
|
||||
* Windows :参考 [《RocketMQ 入门 —— 安装以及快速入门》](http://www.iocoder.cn/RocketMQ/start/install/?vip&gitee)
|
||||
* Mac :参考 [《RocketMQ 入门 —— 安装以及快速入门》](http://www.iocoder.cn/RocketMQ/start/install/?vip&gitee)
|
||||
|
||||
② 修改项目中的 RocketMQ 配置
|
||||
|
||||
在 IDEA 中,搜索每个 `xxx-service-impl` 项目下的 `application.yaml` 文件,将 RocketMQ 配置修改成连接你的。如下:
|
||||
|
||||
```YAML
|
||||
rocketmq:
|
||||
name-server: 127.0.0.1:9876 # 请修改成你本地的 RocketMQ url
|
||||
producer:
|
||||
group: product-producer-group
|
||||
```
|
||||
|
||||
# 6. XXL-Job
|
||||
|
||||
> 艿艿:这个中间件的安装,是可选项。如果不安装,只是定时任务无法执行。
|
||||
|
||||
TODO 未完成。建议先跳过。
|
||||
|
||||
① 安装 XXL-Job
|
||||
|
||||
参考 [《分布式任务调度平台 XXL-JOB》](http://www.xuxueli.com/xxl-job/#/) 官方文档。
|
||||
|
||||
② 修改项目中的 XXL-Job 配置
|
||||
|
||||
在 IDEA 中,搜索每个 `xxx-service-impl` 项目下的 `application-dev.yaml` 文件,将 XXL-Job 配置修改成连接你的。如下:
|
||||
|
||||
```YAML
|
||||
# xxl-job
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:18079/ # 请修改成你本地的 XXL-Job url
|
||||
executor:
|
||||
appname: pay-job-executor
|
||||
ip:
|
||||
port: 0
|
||||
logpath: /Users/yunai/logs/xxl-job/ # 请修改成你希望存放日志的目录
|
||||
logretentiondays: 1
|
||||
accessToken:
|
||||
```
|
||||
|
||||
③ 配置项目中的每个作业
|
||||
|
||||
TODO 芋艿,需要完善
|
||||
|
||||
# 7. Elasticsearch
|
||||
|
||||
① 安装 Elasticsearch
|
||||
|
||||
* Windows :参考 [《ElasticSearch 入门 第一篇:Windows 下安装ElasticSearch》](http://www.cnblogs.com/ljhdo/p/4887557.html)
|
||||
* Mac :参考 [《mac 安装 ElasticSearch 笔记》](https://www.jianshu.com/p/81b0b3a60c01)
|
||||
|
||||
因为需要中文分词,所以需要安装 [elasticsearch-analysis-ik](https://github.com/medcl/elasticsearch-analysis-ik) 插件。
|
||||
|
||||
② 修改项目中的 Elasticsearch 配置
|
||||
|
||||
在 IDEA 中,搜索`search-service-impl` 项目下的 `application.yaml` 文件,将 Elasticsearch 配置修改成连接你的。如下:
|
||||
|
||||
```YAML
|
||||
# es
|
||||
spring:
|
||||
data:
|
||||
elasticsearch:
|
||||
cluster-name: elasticsearch
|
||||
cluster-nodes: 180.167.213.26:9300 # 请修改成你本地的 Elasticsearch url
|
||||
repositories:
|
||||
enable: true
|
||||
```
|
||||
|
||||
# 8. 启动后端项目
|
||||
|
||||
在 IDEA 中,右键运行每个 `XXXApplication.java` 。例如说,`admin` 项目是 AdminApplication 。
|
||||
|
||||
是否启动成功,请查看 IDEA 输出的日志。
|
||||
|
||||
具体的启动顺序,是:
|
||||
|
||||
* SystemApplication
|
||||
* UserApplication
|
||||
* ProductApplication
|
||||
* PayApplication
|
||||
> 因为支付服务,涉及三方支付平台的配置。所以,需要艿艿后续提供简便的方案。TODO
|
||||
|
||||
* PromotionApplication
|
||||
* OrderApplication
|
||||
* SearchApplication
|
||||
|
||||
# 9. 启动前端项目
|
||||
|
||||
① 启动商城 H5 项目
|
||||
|
||||
在 `mobile-web` 项目下,执行 `npm start` 。
|
||||
|
||||
启动成功后,浏览器访问 <http://127.0.0.1:8000> 。
|
||||
|
||||
② 启动管理后台项目
|
||||
|
||||
在 `admin-web` 项目下,执行 `npm run start:no-mock` 。
|
||||
|
||||
启动成功后,浏览器访问 <http://127.0.0.1:8080> 。
|
||||
|
||||
# 10. 数据配置
|
||||
|
||||
TODO 芋艿
|
||||
|
||||
因为项目该配置完,是没有任何数据的。所以,需要操作对应的功能,添加数据。
|
||||
|
||||
# 233. 彩蛋
|
||||
|
||||
> 艿艿:本文暂时会写的比较简洁,如果有不懂的地方,请来[「交流群」](http://www.iocoder.cn/mall-user-group/?vip&gitee),艿艿来帮你解决。
|
||||
139
归档/docs/sql/old/mall_order.sql
Normal file
139
归档/docs/sql/old/mall_order.sql
Normal file
@@ -0,0 +1,139 @@
|
||||
-- ----------------------------
|
||||
-- Table structure for order_cancel
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_cancel`;
|
||||
CREATE TABLE `order_cancel` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`order_id` int(11) NOT NULL COMMENT '订单id',
|
||||
`order_no` varchar(50) NOT NULL COMMENT '订单编号',
|
||||
`reason` int(2) NOT NULL,
|
||||
`other_reason` varchar(100) DEFAULT NULL COMMENT '其他原因',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_exchange
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_exchange`;
|
||||
CREATE TABLE `order_exchange` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`order_id` int(11) NOT NULL,
|
||||
`order_no` varchar(50) NOT NULL,
|
||||
`sku_id` int(11) NOT NULL,
|
||||
`exchange_sku_id` int(11) NOT NULL COMMENT '换货商品id',
|
||||
`exchange_order_logistics_id` int(11) NOT NULL COMMENT '换货物流id',
|
||||
`receiver_order_logistics_id` int(11) NOT NULL COMMENT '收件地址',
|
||||
`order_reason_id` int(11) DEFAULT NULL COMMENT '换货原因',
|
||||
`reason` varchar(255) DEFAULT NULL COMMENT '换货原因 (其他的时候)',
|
||||
`payment_time` datetime DEFAULT NULL COMMENT '付款时间',
|
||||
`delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
|
||||
`receiver_time` datetime DEFAULT NULL COMMENT '收货时间',
|
||||
`closing_time` datetime DEFAULT NULL COMMENT '成交时间',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`delete` smallint(2) DEFAULT NULL COMMENT '删除状态',
|
||||
`order_type` int(2) DEFAULT NULL COMMENT '订单类型 0、为 Order 订单 1、为 OrderItem 订单',
|
||||
`status` int(2) DEFAULT NULL COMMENT '状态 申请换货、申请成功、申请失败、换货中、换货成功',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_logistics
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_logistics`;
|
||||
CREATE TABLE `order_logistics` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id自增长',
|
||||
`area_no` varchar(10) NOT NULL COMMENT '地区编号',
|
||||
`name` varchar(20) NOT NULL COMMENT '名称',
|
||||
`mobile` varchar(20) NOT NULL COMMENT '手机号',
|
||||
`address` varchar(255) NOT NULL COMMENT '详细地址',
|
||||
`logistics` int(2) NOT NULL COMMENT '物流商家',
|
||||
`logistics_no` varchar(20) NOT NULL COMMENT '物流单号',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of order_logistics
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `order_logistics` VALUES (24, '110101', 'Andy', '13302925934', '中二环,光电大厦11F 前台收', 1, '23123124123', '2019-04-11 22:50:31', NULL);
|
||||
INSERT INTO `order_logistics` VALUES (34, '110101', 'Andy', '13302925934', '中二环,光电大厦11F 前台收', 1, '314123123123', '2019-04-12 19:23:42', NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_logistics_detail
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_logistics_detail`;
|
||||
CREATE TABLE `order_logistics_detail` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id自增长',
|
||||
`order_logistics_id` int(11) NOT NULL COMMENT '物流编号',
|
||||
`logistics_time` datetime NOT NULL COMMENT '物流时间',
|
||||
`logistics_information` varchar(20) NOT NULL COMMENT '物流信息',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_recipient
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_recipient`;
|
||||
CREATE TABLE `order_recipient` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`order_id` int(11) NOT NULL COMMENT '订单id',
|
||||
`area_no` varchar(20) NOT NULL COMMENT '区域编号',
|
||||
`name` varchar(20) NOT NULL COMMENT '收件人名称',
|
||||
`mobile` varchar(20) NOT NULL COMMENT '手机号',
|
||||
`type` int(2) NOT NULL COMMENT '快递方式',
|
||||
`address` varchar(250) NOT NULL COMMENT '地址详细',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=189 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order_return
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `order_return`;
|
||||
CREATE TABLE `order_return` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id自增长',
|
||||
`service_number` varchar(50) NOT NULL COMMENT '服务号',
|
||||
`order_id` int(11) NOT NULL COMMENT '订单编号',
|
||||
`order_no` varchar(50) NOT NULL COMMENT '订单号',
|
||||
`order_logistics_id` int(11) DEFAULT NULL COMMENT '物流 id',
|
||||
`refund_price` int(11) NOT NULL COMMENT '退回金额',
|
||||
`reason` int(11) NOT NULL COMMENT '退货原因',
|
||||
`describe` varchar(255) DEFAULT NULL COMMENT '换货原因 (其他的时候)',
|
||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||
`approval_time` datetime DEFAULT NULL COMMENT '同意时间',
|
||||
`refuse_time` datetime DEFAULT NULL COMMENT '拒绝时间',
|
||||
`logistics_time` datetime DEFAULT NULL COMMENT '物流时间(填写物流单号时间)',
|
||||
`receiver_time` datetime DEFAULT NULL COMMENT '收货时间',
|
||||
`closing_time` datetime DEFAULT NULL COMMENT '成交时间',
|
||||
`service_type` int(2) DEFAULT NULL COMMENT ' 1、退货 2、退款',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`status` int(2) NOT NULL COMMENT '状态 申请换货、申请成功、申请失败、退货中、退货成功',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for undo_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `undo_log`;
|
||||
CREATE TABLE `undo_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`branch_id` bigint(20) NOT NULL,
|
||||
`xid` varchar(100) NOT NULL,
|
||||
`rollback_info` longblob NOT NULL,
|
||||
`log_status` int(11) NOT NULL,
|
||||
`log_created` datetime NOT NULL,
|
||||
`log_modified` datetime NOT NULL,
|
||||
`ext` varchar(100) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8;
|
||||
426
归档/mall-dependencies/pom.xml
Normal file
426
归档/mall-dependencies/pom.xml
Normal file
@@ -0,0 +1,426 @@
|
||||
<?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>onemall</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mall-dependencies</artifactId>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Maven Bom,定义 Onemall 项目的所有依赖的版本</description>
|
||||
|
||||
<!-- 属性 -->
|
||||
<properties>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<resource.delimiter>@</resource.delimiter>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>2.2.5.RELEASE</spring.boot.version>
|
||||
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
|
||||
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>
|
||||
<!-- Web 相关 -->
|
||||
<servlet.version>2.5</servlet.version>
|
||||
<knife4j.version>2.0.2</knife4j.version>
|
||||
<swagger.version>1.5.21</swagger.version>
|
||||
<springfox-swagger.version>2.9.2</springfox-swagger.version>
|
||||
<!-- DB 相关 -->
|
||||
<mysql-connector-java.version>5.1.46</mysql-connector-java.version>
|
||||
<druid.version>1.1.16</druid.version>
|
||||
<mybatis-spring-boot-starter.version>2.0.0</mybatis-spring-boot-starter.version>
|
||||
<mybatis.version>3.5.4</mybatis.version>
|
||||
<mybatis-plus.version>3.3.2</mybatis-plus.version>
|
||||
<spring-boot-starter-data-jest.version>3.2.5.RELEASE</spring-boot-starter-data-jest.version>
|
||||
<redisson.version>3.13.6</redisson.version>
|
||||
<!-- RPC 相关 -->
|
||||
<dubbo.version>2.7.7</dubbo.version>
|
||||
<!-- MQ 相关 -->
|
||||
<rocketmq-spring-boot-starter.version>2.1.1</rocketmq-spring-boot-starter.version>
|
||||
<!-- Job 相关 -->
|
||||
<xxl-job.version>2.2.0</xxl-job.version>
|
||||
<!-- Transaction 相关 -->
|
||||
<seata.version>1.1.0</seata.version>
|
||||
<!-- 云服务相关 -->
|
||||
<qiniu.version>7.2.18</qiniu.version>
|
||||
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
|
||||
<aliyun-java-sdk-core.version>4.1.0</aliyun-java-sdk-core.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>8.0.1</skywalking.version>
|
||||
<spring-boot-admin-starter-client.version>2.2.2</spring-boot-admin-starter-client.version>
|
||||
<sentry.version>1.7.30</sentry.version>
|
||||
<!-- 工具类相关 -->
|
||||
<fastjson.version>1.2.56</fastjson.version>
|
||||
<hibernate-validator.version>6.0.16.Final</hibernate-validator.version>
|
||||
<hutool.version>5.2.5</hutool.version>
|
||||
<guava.version>27.0.1-jre</guava.version>
|
||||
<org.projectlombok.version>1.16.14</org.projectlombok.version>
|
||||
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<!-- 依赖管理 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- 统一依赖管理 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||
<version>${spring.cloud.alibaba.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql-connector-java.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>${mybatis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>${mybatis-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-core</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 自动化配置 Spring Data Jest -->
|
||||
<groupId>com.github.vanroy</groupId>
|
||||
<artifactId>spring-boot-starter-data-jest</artifactId>
|
||||
<version>${spring-boot-starter-data-jest.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-mybatis</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-redis</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${servlet.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-web</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>${springfox-swagger.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-security-annotations</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-security-user</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-security-admin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-swagger</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
<!-- <version>2.7.8</version>-->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-dubbo</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency> <!-- TODO 需要思考下,归类到哪里 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-system-error-code</artifactId> <!-- 错误码 -->
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MQ 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<version>${rocketmq-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-rocketmq</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
<version>${xxl-job.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.nacos</groupId>-->
|
||||
<!-- <artifactId>nacos-client</artifactId>-->
|
||||
<!-- <version>1.3.1</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- Transaction 相关 -->
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
<version>${seata.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
<version>${spring-boot-admin-starter-client.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-logback</artifactId>
|
||||
<version>${sentry.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-spring-boot-starter</artifactId>
|
||||
<version>${sentry.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 相关 -->
|
||||
|
||||
<!--- 日志相关 -->
|
||||
|
||||
<!-- 云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId> <!-- 七牛文件服务器 -->
|
||||
<version>${qiniu.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.yunpian.sdk</groupId>
|
||||
<artifactId>yunpian-java-sdk</artifactId> <!-- 云片短信 -->
|
||||
<version>${yunpian-java-sdk.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-core</artifactId> <!-- 七牛短信 -->
|
||||
<version>${aliyun-java-sdk-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.fasterxml.jackson.core</groupId>-->
|
||||
<!-- <artifactId>jackson-annotations</artifactId>-->
|
||||
<!-- <version>${jackson.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.fasterxml.jackson.core</groupId>-->
|
||||
<!-- <artifactId>jackson-databind</artifactId>-->
|
||||
<!-- <version>${jackson.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<!-- hutool 工具类-->
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!-- 提供给 mapstruct 使用 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source> <!-- or higher, depending on your project -->
|
||||
<target>${java.version}</target> <!-- or higher, depending on your project -->
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- 打包 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<fork>true</fork>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
128
归档/management-web-app/pom.xml
Normal file
128
归档/management-web-app/pom.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<?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>onemall</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>management-web-app</artifactId>
|
||||
<description>管理后台,提供管理员管理的所有功能</description>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- onemall 基础 bom 文件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-dependencies</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-swagger</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-security-admin</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-dubbo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 用户服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>user-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 系统服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 商品服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>product-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 营销服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>promotion-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 支付服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>pay-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 和 Config 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<!-- 设置构建的 jar 包名 -->
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<!-- 使用 spring-boot-maven-plugin 插件打包 -->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.mall.managementweb;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients(basePackages = {"cn.iocoder.mall.productservice.rpc","cn.iocoder.mall.payservice.rpc"
|
||||
,"cn.iocoder.mall.promotion.api.rpc","cn.iocoder.mall.systemservice.rpc","cn.iocoder.mall.userservice.rpc"})
|
||||
public class ManagementWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ManagementWebApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.mall.managementweb.client.pay.transaction;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.PayTransactionFeign;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionPageReqDTO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionRespDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PayTransactionClient {
|
||||
|
||||
|
||||
@Autowired
|
||||
private PayTransactionFeign payTransactionFeign;
|
||||
public PageResult<PayTransactionRespDTO> pagePayTransaction(PayTransactionPageReqDTO pageReqDTO) {
|
||||
CommonResult<PageResult<PayTransactionRespDTO>> pagePayTransactionResult = payTransactionFeign.pagePayTransaction(pageReqDTO);
|
||||
pagePayTransactionResult.checkError();
|
||||
return pagePayTransactionResult.getData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
### /admin/page 成功
|
||||
GET http://127.0.0.1:18083/management-api/admin/page?pageNo=1&pageSize=10
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
### /admin/create 成功
|
||||
POST http://127.0.0.1:18083/management-api/admin/create
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
username=admin02&password=buzhidao&name=测试管理员&departmentId=1
|
||||
|
||||
### /admin/update 成功
|
||||
POST http://127.0.0.1:18083/management-api/admin/update
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
id=31&username=admin02&password=buzhidao&name=测试管理员&departmentId=1
|
||||
|
||||
### /admin/update-status 成功
|
||||
POST http://127.0.0.1:18083/management-api/admin/update-status
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
adminId=31&status=1
|
||||
|
||||
### /admin/update-status 失败,参数缺失
|
||||
POST http://127.0.0.1:18083/management-api/admin/update-status
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
adminId=31
|
||||
|
||||
### admin/update-status 失败,地址不存在
|
||||
GET http://127.0.0.1:18083/management-api/admin/update-status---
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
adminId=31&status=sss
|
||||
|
||||
###
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin;
|
||||
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.AdminCreateDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.AdminPageDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.AdminUpdateInfoDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.AdminUpdateStatusDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.vo.AdminPageItemVO;
|
||||
import cn.iocoder.mall.managementweb.manager.admin.AdminManager;
|
||||
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContextHolder;
|
||||
import cn.iocoder.security.annotations.RequiresPermissions;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.common.framework.vo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理员 API")
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
@Validated
|
||||
public class AdminController {
|
||||
|
||||
@Autowired
|
||||
private AdminManager adminManager;
|
||||
|
||||
@ApiOperation(value = "管理员分页")
|
||||
@GetMapping("/page")
|
||||
@RequiresPermissions("system:admin:page")
|
||||
public CommonResult<PageResult<AdminPageItemVO>> page(AdminPageDTO adminPageDTO) {
|
||||
return success(adminManager.pageAdmin(adminPageDTO));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "创建管理员")
|
||||
@PostMapping("/create")
|
||||
@RequiresPermissions("system:admin:create")
|
||||
public CommonResult<Integer> createAdmin(AdminCreateDTO createDTO, HttpServletRequest request) {
|
||||
return success(adminManager.createAdmin(createDTO, AdminSecurityContextHolder.getAdminId(), HttpUtil.getIp(request)));
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
@ApiOperation(value = "更新管理员")
|
||||
@RequiresPermissions("system:admin:update")
|
||||
public CommonResult<Boolean> updateAdmin(AdminUpdateInfoDTO updateInfoDTO) {
|
||||
adminManager.updateAdmin(updateInfoDTO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/update-status")
|
||||
@ApiOperation(value = "更新管理员状态")
|
||||
@RequiresPermissions("system:admin:update-status")
|
||||
public CommonResult<Boolean> updateAdminStatus(@Valid AdminUpdateStatusDTO updateStatusDTO) {
|
||||
adminManager.updateAdminStatus(updateStatusDTO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
### /department/create 成功
|
||||
POST http://127.0.0.1:18083/management-api/department/create
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
dubbo-tag: {{dubboTag}}
|
||||
|
||||
name=测试部门&pid=0&sort=0
|
||||
|
||||
### /department/update 成功
|
||||
POST http://127.0.0.1:18083/management-api/department/update
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
id=1&name=测试部门&pid=0&sort=0
|
||||
|
||||
### /resource/delete 成功
|
||||
POST http://127.0.0.1:18083/management-api/department/delete
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
id=1
|
||||
|
||||
### /department/get 成功
|
||||
GET http://127.0.0.1:18083/management-api/department/get?departmentId=1
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
### /department/list 成功
|
||||
GET http://127.0.0.1:18083/management-api/department/list?departmentIds=1,13
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
### /department/tree 成功
|
||||
GET http://127.0.0.1:18083/management-api/department/tree
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer yudaoyuanma
|
||||
|
||||
###
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.DepartmentCreateDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.dto.DepartmentUpdateDTO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.vo.DepartmentTreeNodeVO;
|
||||
import cn.iocoder.mall.managementweb.controller.admin.vo.DepartmentVO;
|
||||
import cn.iocoder.mall.managementweb.manager.admin.DepartmentManager;
|
||||
import cn.iocoder.security.annotations.RequiresPermissions;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.common.framework.vo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* 部门 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/department")
|
||||
@Api(tags = "部门")
|
||||
@Validated
|
||||
public class DepartmentController {
|
||||
|
||||
@Autowired
|
||||
private DepartmentManager departmentManager;
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation("创建部门")
|
||||
@RequiresPermissions("system:department:create")
|
||||
public CommonResult<Integer> createDepartment(@Valid DepartmentCreateDTO createDTO) {
|
||||
return success(departmentManager.createDepartment(createDTO));
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
@ApiOperation("更新部门")
|
||||
@RequiresPermissions("system:department:update")
|
||||
public CommonResult<Boolean> updateDepartment(@Valid DepartmentUpdateDTO updateDTO) {
|
||||
departmentManager.updateDepartment(updateDTO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@ApiOperation("删除部门")
|
||||
@ApiImplicitParam(name = "departmentId", value = "部门编号", required = true)
|
||||
@RequiresPermissions("system:department:delete")
|
||||
public CommonResult<Boolean> deleteDepartment(@RequestParam("departmentId") Integer departmentId) {
|
||||
departmentManager.deleteDepartment(departmentId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation("获得部门")
|
||||
@ApiImplicitParam(name = "departmentId", value = "部门编号", required = true)
|
||||
@RequiresPermissions("system:department:tree")
|
||||
public CommonResult<DepartmentVO> getDepartment(@RequestParam("departmentId") Integer departmentId) {
|
||||
return success(departmentManager.getDepartment(departmentId));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperation("获得部门列表")
|
||||
@ApiImplicitParam(name = "departmentIds", value = "部门编号列表", required = true)
|
||||
@RequiresPermissions("system:department:tree")
|
||||
public CommonResult<List<DepartmentVO>> listDepartments(@RequestParam("departmentIds") List<Integer> departmentIds) {
|
||||
return success(departmentManager.listDepartments(departmentIds));
|
||||
}
|
||||
|
||||
@GetMapping("/tree")
|
||||
@ApiOperation("获得部门树")
|
||||
@RequiresPermissions("system:department:tree")
|
||||
public CommonResult<List<DepartmentTreeNodeVO>> treeDepartment() {
|
||||
return success(departmentManager.treeDepartment());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@ApiModel("管理员创建 DTO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminCreateDTO {
|
||||
|
||||
@ApiModelProperty(value = "真实名字", required = true, example = "小王")
|
||||
@NotEmpty(message = "真实名字不能为空")
|
||||
@Length(max = 10, message = "真实名字长度最大为 10 位")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
@NotNull(message = "部门不能为空")
|
||||
private Integer departmentId;
|
||||
|
||||
@ApiModelProperty(value = "登陆账号", required = true, example = "15601691300")
|
||||
@NotEmpty(message = "登陆账号不能为空")
|
||||
@Length(min = 5, max = 16, message = "账号长度为 5-16 位")
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import cn.iocoder.common.framework.vo.PageParam;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@ApiModel("管理员分页查询 DTO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class AdminPageDTO extends PageParam {
|
||||
|
||||
@ApiModelProperty(value = "真实名字,模糊匹配", example = "小王")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "部门编号")
|
||||
private Integer departmentId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@ApiModel("管理员更新信息 DTO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminUpdateInfoDTO {
|
||||
|
||||
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
|
||||
@NotNull(message = "管理员编号不能为空")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "登陆账号", required = true, example = "15601691300")
|
||||
@NotEmpty(message = "登陆账号不能为空")
|
||||
@Length(min = 5, max = 16, message = "账号长度为 5-16 位")
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
private String password;
|
||||
|
||||
@ApiModelProperty(value = "真实名字", required = true, example = "小王")
|
||||
@NotEmpty(message = "真实名字不能为空")
|
||||
@Length(max = 10, message = "真实名字长度最大为 10 位")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
@NotNull(message = "部门不能为空")
|
||||
private Integer departmentId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import cn.iocoder.common.framework.enums.CommonStatusEnum;
|
||||
import cn.iocoder.common.framework.validator.InEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("管理员更新状态 DTO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminUpdateStatusDTO {
|
||||
|
||||
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
|
||||
@NotNull(message = "管理员编号不能为空")
|
||||
private Integer adminId;
|
||||
|
||||
@ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 CommonStatusEnum 枚举")
|
||||
@NotNull(message = "状态不能为空")
|
||||
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("部门创建 DTO")
|
||||
@Data
|
||||
public class DepartmentCreateDTO {
|
||||
|
||||
@ApiModelProperty(value = "部门名称", required = true, example = "技术部")
|
||||
@NotEmpty(message = "部门名称不能为空")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "排序字段", required = true, example = "1024")
|
||||
@NotNull(message = "排序字段不能为空")
|
||||
private Integer sort;
|
||||
@ApiModelProperty(value = "父级部门编号", required = true, example = "2048")
|
||||
@NotNull(message = "父级部门编号不能为空")
|
||||
private Integer pid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel("部门更新 DTO")
|
||||
@Data
|
||||
public class DepartmentUpdateDTO {
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
@NotNull(message = "部门编号不能为空")
|
||||
private Integer id;
|
||||
@ApiModelProperty(value = "部门名称", required = true, example = "技术部")
|
||||
@NotEmpty(message = "部门名称不能为空")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "排序字段", required = true, example = "1024")
|
||||
@NotNull(message = "排序字段不能为空")
|
||||
private Integer sort;
|
||||
@ApiModelProperty(value = "父级部门编号", required = true, example = "2048")
|
||||
@NotNull(message = "父级部门编号不能为空")
|
||||
private Integer pid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel(value = "分页时,管理员的信息 VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminPageItemVO {
|
||||
|
||||
@ApiModel("角色")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Role {
|
||||
|
||||
@ApiModelProperty(value = "角色编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "角色名", required = true, example = "码神")
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
@ApiModel("部门")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Department {
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "部门名称", required = true, example = "研发部")
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
@ApiModelProperty(value = "真实名字", required = true, example = "小王")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "创建时间", required = true, example = "时间戳格式")
|
||||
private Date createTime;
|
||||
@ApiModelProperty(value = "在职状态", required = true, example = "1", notes = "见 AdminStatusEnum 枚举")
|
||||
private Integer status;
|
||||
@ApiModelProperty(value = "登陆账号", required = true, example = "15601691300")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private List<Role> roles;
|
||||
/**
|
||||
* 所在部门
|
||||
*/
|
||||
private Department department;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ApiModel(value = "管理员 VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AdminVO {
|
||||
|
||||
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
@ApiModelProperty(value = "真实名字", required = true, example = "小王")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "创建时间", required = true, example = "时间戳格式")
|
||||
private Date createTime;
|
||||
@ApiModelProperty(value = "在职状态", required = true, example = "1", notes = "见 AdminStatusEnum 枚举")
|
||||
private Integer status;
|
||||
@ApiModelProperty(value = "登陆账号", required = true, example = "15601691300")
|
||||
private String username;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("部门树节点 VO")
|
||||
@Data
|
||||
public class DepartmentTreeNodeVO {
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
@ApiModelProperty(value = "部门名称", required = true, example = "技术部")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "排序字段", required = true, example = "1024")
|
||||
private Integer sort;
|
||||
@ApiModelProperty(value = "父级部门编号", required = true, example = "2048")
|
||||
private Integer pid;
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 子节点
|
||||
*/
|
||||
private List<DepartmentTreeNodeVO> children;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.mall.managementweb.controller.admin.vo;
|
||||
|
||||
import lombok.*;
|
||||
import io.swagger.annotations.*;
|
||||
import java.util.*;
|
||||
|
||||
@ApiModel("部门 VO")
|
||||
@Data
|
||||
public class DepartmentVO {
|
||||
|
||||
@ApiModelProperty(value = "部门编号", required = true, example = "1")
|
||||
private Integer id;
|
||||
@ApiModelProperty(value = "部门名称", required = true, example = "技术部")
|
||||
private String name;
|
||||
@ApiModelProperty(value = "排序字段", required = true, example = "1024")
|
||||
private Integer sort;
|
||||
@ApiModelProperty(value = "父级部门编号", required = true, example = "2048")
|
||||
private Integer pid;
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user