!4 Spring Cloud Alibaba 完成 system 和 infra 的迁移
Merge pull request !4 from 芋道源码/spring-cloud-alibaba
This commit is contained in:
commit
ff055e5a2c
|
@ -0,0 +1,25 @@
|
|||
碰到问题,请在 <https://gitee.com/zhijiantianya/yudao-cloud/issues> 搜索是否存在相似的 issue。
|
||||
|
||||
不按照模板提交的 issue,会被系统自动删除。
|
||||
|
||||
### 基本信息
|
||||
|
||||
- ruoyi-vue-pro 版本:
|
||||
- 操作系统:
|
||||
- 数据库:
|
||||
|
||||
### 你猜测可能的原因
|
||||
|
||||
(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
|
||||
|
||||
### 复现步骤
|
||||
|
||||
第一步,
|
||||
|
||||
第二步,
|
||||
|
||||
第三步,
|
||||
|
||||
### 报错信息
|
||||
|
||||
带上必要的截图
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
name: 问题反馈
|
||||
about: 请详细描述,以便更高快的获得到解决
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
碰到问题,请在 <https://github.com/YunaiV/yudao-cloud/issues> 搜索是否存在相似的 issue。
|
||||
|
||||
不按照模板提交的 issue,会被系统自动删除。
|
||||
|
||||
### 基本信息
|
||||
|
||||
- ruoyi-vue-pro 版本:
|
||||
- 操作系统:
|
||||
- 数据库:
|
||||
|
||||
### 你猜测可能的原因
|
||||
|
||||
(必填)我花费了 2-4 小时自查,发现可能的原因是:xxxxxx
|
||||
|
||||
### 复现步骤
|
||||
|
||||
第一步,
|
||||
|
||||
第二步,
|
||||
|
||||
第三步,
|
||||
|
||||
### 报错信息
|
||||
|
||||
带上必要的截图
|
|
@ -0,0 +1,30 @@
|
|||
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
# pull_request:
|
||||
# branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ '8', '11', '17' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK ${{ matrix.Java }}
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml -Dmaven.test.skip=true
|
22
README.md
22
README.md
|
@ -1,12 +1,18 @@
|
|||
[toc]
|
||||
以 [ruoyi-vue-pro](https://github.com/YunaiV/ruoyi-vue-pro) 为基础,实现的 Spring Cloud Alibaba 微服务架构。进度如下:
|
||||
|
||||
> 友情提示:近期在升级和优化该项目,建议先 Star 本项目。主要在做几个事情:
|
||||
>
|
||||
> * 1、微服务技术选型以 Spring Cloud Alibaba 为中心。
|
||||
> * 2、修改项目分层,并合并部分服务,简化整体服务的复杂性。
|
||||
> * 3、将管理后台从 React 重构到 Vue 框架。
|
||||
>
|
||||
> 交流群:[传送门](http://www.iocoder.cn/mall-user-group/?vip&gitee)
|
||||
* [x] `gateway` 网关服务
|
||||
* [x] `system` 系统服务
|
||||
* [x] `infra` 基础设施
|
||||
* [ ] `bpm` 工作流服务
|
||||
* [ ] `pay` 支付服务
|
||||
* [ ] `member` 会员服务
|
||||
* [ ] `product` 商品服务
|
||||
* [ ] `market` 营销服务
|
||||
* [ ] `trade` 交易服务
|
||||
|
||||
启动文档,可见 <https://cloud.iocoder.cn/quick-start/> 地址。
|
||||
|
||||
----
|
||||
|
||||
# 前言
|
||||
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
<?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>
|
|
@ -1,44 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
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));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,319 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package cn.iocoder.common.framework.util;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
|
||||
/**
|
||||
* 操作系统工具类
|
||||
*/
|
||||
public class OSUtils {
|
||||
|
||||
public static String getHostName() {
|
||||
return SystemUtil.getHostInfo().getName();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
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());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
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;
|
||||
// }
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
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());
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?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>
|
|
@ -1,18 +0,0 @@
|
|||
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 {
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package cn.iocoder.security.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 通过将该注解添加到 Controller 的方法上,声明无需进行登陆
|
||||
*/
|
||||
@Documented
|
||||
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE ,因为没有场景
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequiresNone {
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
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 {};
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?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-spring-boot-starter-cache</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>3.10.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
|
@ -1,79 +0,0 @@
|
|||
package cn.iocoder.mall.cache.config;
|
||||
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisSentinelPool;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
|
||||
@Component
|
||||
public class JedisClient {
|
||||
|
||||
@Resource
|
||||
private static JedisSentinelPool jedisSentinelPool;
|
||||
|
||||
public static String get(String key) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisSentinelPool.getResource();
|
||||
return jedis.get(key);
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean set(String key, String value) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisSentinelPool.getResource();
|
||||
String ret = jedis.set(key, value);
|
||||
return "ok".equalsIgnoreCase(ret);
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean set(String key, String value, int seconds) {
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisSentinelPool.getResource();
|
||||
String ret = jedis.set(key, value);
|
||||
jedis.expire(key, seconds);
|
||||
return "ok".equalsIgnoreCase(ret);
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean del(String key) {
|
||||
Long removedSize = 0L;
|
||||
Jedis jedis = null;
|
||||
try {
|
||||
jedis = jedisSentinelPool.getResource();
|
||||
removedSize = jedis.del(key);
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (jedis != null) {
|
||||
jedis.close();
|
||||
}
|
||||
}
|
||||
return removedSize > 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cn.iocoder.mall.cache.config;
|
||||
|
||||
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.ReadMode;
|
||||
import org.redisson.config.SentinelServersConfig;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Component
|
||||
public class RedissonClient {
|
||||
|
||||
@Value("${spring.redis.database}")
|
||||
private int database;
|
||||
|
||||
@Value("${spring.redis.sentinel.master}")
|
||||
private String master;
|
||||
|
||||
@Value("${spring.redis.sentinel.nodes}")
|
||||
private String nodes;
|
||||
|
||||
/**
|
||||
* 哨兵模式 redisson 客户端
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public org.redisson.api.RedissonClient redissonClient() {
|
||||
Config config = new Config();
|
||||
List<String> nodes = Arrays.asList(this.nodes.split(","));
|
||||
List<String> newNodes = new ArrayList(nodes.size());
|
||||
nodes .forEach((index) -> newNodes.add(
|
||||
index.startsWith("redis://") ? index : "redis://" + index));
|
||||
|
||||
SentinelServersConfig serverConfig = config.useSentinelServers()
|
||||
.addSentinelAddress(newNodes.toArray(new String[3]))
|
||||
.setMasterName(this.master)
|
||||
.setReadMode(ReadMode.SLAVE) ;
|
||||
|
||||
serverConfig.setDatabase(this.database);
|
||||
return Redisson.create(config);
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
package cn.iocoder.mall.cache.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.ReadMode;
|
||||
import org.redisson.config.SentinelServersConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.cache.interceptor.SimpleKeyGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class SpringDataRedisConfig extends CachingConfigurerSupport {
|
||||
|
||||
@Value("${spring.redis.database}")
|
||||
private int database;
|
||||
|
||||
@Value("${spring.redis.sentinel.master}")
|
||||
private String master;
|
||||
|
||||
@Value("${spring.redis.sentinel.nodes}")
|
||||
private String nodes;
|
||||
|
||||
private static RedisTemplate redisTemplate;
|
||||
|
||||
static {
|
||||
|
||||
}
|
||||
|
||||
public static String get(String key) {
|
||||
redisTemplate.opsForValue().get(key);
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean set(String key, String value) {
|
||||
redisTemplate.opsForValue().set(key,value);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean set(String key, String value, int seconds) {
|
||||
redisTemplate.opsForValue().set(key,value,seconds);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* json序列化
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisSerializer<Object> jackson2JsonRedisSerializer() {
|
||||
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
serializer.setObjectMapper(mapper);
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
//StringRedisTemplate的构造方法中默认设置了stringSerializer
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
//set key serializer
|
||||
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
|
||||
template.setKeySerializer(stringRedisSerializer);
|
||||
template.setHashKeySerializer(stringRedisSerializer);
|
||||
|
||||
|
||||
//set value serializer
|
||||
template.setDefaultSerializer(jackson2JsonRedisSerializer());
|
||||
|
||||
template.setConnectionFactory(lettuceConnectionFactory);
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
StringRedisTemplate template = new StringRedisTemplate();
|
||||
template.setConnectionFactory(lettuceConnectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public KeyGenerator keyGenerator() {
|
||||
return new SimpleKeyGenerator() {
|
||||
|
||||
@Override
|
||||
public Object generate(Object target, Method method, Object... params) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(target.getClass().getName());
|
||||
sb.append(".").append(method.getName());
|
||||
|
||||
StringBuilder paramsSb = new StringBuilder();
|
||||
for (Object param : params) {
|
||||
// 如果不指定,默认生成包含到键值中
|
||||
if (param != null) {
|
||||
paramsSb.append(param.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsSb.length() > 0) {
|
||||
sb.append("_").append(paramsSb);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer time, int timeType) {
|
||||
|
||||
Duration duration = Duration.ofMillis(time);
|
||||
if (timeType == 1){//时
|
||||
duration = Duration.ofHours(time);
|
||||
}else if (timeType == 2){//分
|
||||
duration = Duration.ofMinutes(time);
|
||||
}else if (timeType == 3){//秒
|
||||
duration = Duration.ofSeconds(time);
|
||||
}
|
||||
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
|
||||
redisCacheConfiguration = redisCacheConfiguration
|
||||
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())
|
||||
|
||||
)
|
||||
//超时时间
|
||||
.entryTtl(duration);
|
||||
|
||||
return redisCacheConfiguration;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
redis.pool.maxIdle=200
|
||||
redis.pool.minIdle=10
|
||||
#redis.pool.maxActive=600
|
||||
redis.pool.maxTotal=1024
|
||||
redis.pool.maxWaitMillis=3000
|
||||
redis.pool.minEvictableIdleTimeMillis=300000
|
||||
redis.pool.numTestsPerEvictionRun=1024
|
||||
redis.pool.timeBetweenEvictionRunsMillis=30000
|
||||
redis.pool.testOnBorrow=true
|
||||
redis.pool.testWhileIdle=true
|
||||
redis.pool.testOnReturn=true
|
||||
|
||||
#Redis 配置
|
||||
spring.redis.sentinel.master=
|
||||
spring.redis.sentinel.nodes=
|
||||
spring.redis.database=
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<?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>
|
|
@ -1,66 +0,0 @@
|
|||
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));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
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 标签路由]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
dubboExceptionFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderExceptionFilter
|
||||
dubboProviderRouterTagFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter
|
|
@ -1 +0,0 @@
|
|||
dubboConsumerRouterTagClusterInterceptor=cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor
|
|
@ -1,5 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.dubbo.config.DubboWebAutoConfiguration
|
||||
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
cn.iocoder.mall.dubbo.config.DubboEnvironmentPostProcessor
|
|
@ -1,47 +0,0 @@
|
|||
<?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-mybatis</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- 通用相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.config;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.injector.CustomSqlInject;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||
|
||||
/**
|
||||
* @author Hccake 2020/8/3
|
||||
* @version 1.0
|
||||
*/
|
||||
public class MybatisPlusAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 自定义方法扩展注入器
|
||||
* @return ISqlInjector CustomSqlInject
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ISqlInjector.class)
|
||||
public ISqlInjector sqlInjector(){
|
||||
return new CustomSqlInject();
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.dataobject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 基础实体对象
|
||||
*/
|
||||
public class BaseDO implements Serializable {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
/**
|
||||
* 最后更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BaseDO{" +
|
||||
"createTime=" + createTime +
|
||||
", updateTime=" + updateTime +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public BaseDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public BaseDO setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.dataobject;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
|
||||
/**
|
||||
* extends BaseDO 扩展 delete 操作
|
||||
*
|
||||
* @author Sin
|
||||
* @time 2019-03-22 22:03
|
||||
*/
|
||||
public class DeletableDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeletableDO{" +
|
||||
"deleted=" + deleted +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Integer getDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public DeletableDO setDeleted(Integer deleted) {
|
||||
this.deleted = deleted;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.enums;
|
||||
|
||||
/**
|
||||
* @author Hccake 2020/8/3
|
||||
* @version 1.0
|
||||
*/
|
||||
public enum CustomSqlMethodEnum {
|
||||
/**
|
||||
* 批量插入
|
||||
*/
|
||||
INSERT_BATCH("insertByBatch",
|
||||
"批量插入数据",
|
||||
"<script>\n"
|
||||
+ "INSERT INTO %s %s VALUES \n"
|
||||
+ "<foreach collection=\"collection\" item=\"item\" separator=\",\"> %s\n </foreach>\n"
|
||||
+ "</script>");
|
||||
|
||||
private final String method;
|
||||
private final String desc;
|
||||
private final String sql;
|
||||
|
||||
CustomSqlMethodEnum(String method, String desc, String sql) {
|
||||
this.method = method;
|
||||
this.desc = desc;
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.enums;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
|
||||
/**
|
||||
* {@link DeletableDO#getDeleted()} delete 状态
|
||||
*
|
||||
* @author Sin
|
||||
* @time 2019-03-22 21:15
|
||||
*/
|
||||
public enum DeletedStatusEnum {
|
||||
|
||||
DELETED_NO(0, "正常(未删除)"),
|
||||
DELETED_YES(1, "删除");
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private Integer value;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
DeletedStatusEnum(Integer value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.injector;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.injector.method.InsertByBatch;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义 Sql 注入器,继承 MybatisPlus 提供的默认注入器
|
||||
* @author Hccake 2020/8/3
|
||||
* @version 1.0
|
||||
*/
|
||||
public class CustomSqlInject extends DefaultSqlInjector {
|
||||
|
||||
@Override
|
||||
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
|
||||
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
|
||||
methodList.add(new InsertByBatch());
|
||||
return methodList;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.injector.method;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.enums.CustomSqlMethodEnum;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
|
||||
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
|
||||
import org.apache.ibatis.executor.keygen.KeyGenerator;
|
||||
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量插入
|
||||
* @author Hccake 2020/8/3
|
||||
* @version 1.0
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class InsertByBatch extends AbstractMethod {
|
||||
|
||||
private final static String ITEM = "item";
|
||||
|
||||
@Override
|
||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||
KeyGenerator keyGenerator = new NoKeyGenerator();
|
||||
|
||||
CustomSqlMethodEnum sqlMethod = CustomSqlMethodEnum.INSERT_BATCH;
|
||||
|
||||
// ==== 拼接 sql 模板 ==============
|
||||
StringBuilder columnScriptBuilder = new StringBuilder(LEFT_BRACKET);
|
||||
StringBuilder valuesScriptBuilder = new StringBuilder(LEFT_BRACKET);
|
||||
// 主键拼接
|
||||
if (tableInfo.havePK()) {
|
||||
columnScriptBuilder.append(tableInfo.getKeyColumn()).append(COMMA);
|
||||
valuesScriptBuilder.append(SqlScriptUtils.safeParam(ITEM + DOT + tableInfo.getKeyProperty())).append(COMMA);
|
||||
}
|
||||
// 普通字段拼接
|
||||
// PS:如有需要可在此实现插入字段过滤
|
||||
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
|
||||
for (TableFieldInfo fieldInfo : fieldList) {
|
||||
columnScriptBuilder.append(fieldInfo.getColumn()).append(COMMA);
|
||||
valuesScriptBuilder.append(SqlScriptUtils.safeParam(ITEM + DOT + fieldInfo.getProperty())).append(COMMA);
|
||||
}
|
||||
// 替换多余的逗号为括号
|
||||
columnScriptBuilder.setCharAt(columnScriptBuilder.length() - 1, ')');
|
||||
valuesScriptBuilder.setCharAt(valuesScriptBuilder.length() - 1, ')');
|
||||
// sql 模板占位符替换
|
||||
String columnScript = columnScriptBuilder.toString();
|
||||
String valuesScript = valuesScriptBuilder.toString();
|
||||
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
|
||||
|
||||
|
||||
// === mybatis 主键逻辑处理:主键生成策略,以及主键回填=======
|
||||
String keyColumn = null;
|
||||
String keyProperty = null;
|
||||
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
|
||||
if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
|
||||
if (tableInfo.getIdType() == IdType.AUTO) {
|
||||
/** 自增主键 */
|
||||
keyGenerator = new Jdbc3KeyGenerator();
|
||||
keyProperty = tableInfo.getKeyProperty();
|
||||
keyColumn = tableInfo.getKeyColumn();
|
||||
} else {
|
||||
if (null != tableInfo.getKeySequence()) {
|
||||
keyGenerator = TableInfoHelper.genKeyGenerator(sqlMethod.getMethod(), tableInfo, builderAssistant);
|
||||
keyProperty = tableInfo.getKeyProperty();
|
||||
keyColumn = tableInfo.getKeyColumn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模板写入
|
||||
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
|
||||
return this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Mapper 层基类
|
||||
* @author Hccake 2020/8/3
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface CommonMapper<T> extends BaseMapper<T> {
|
||||
|
||||
/**
|
||||
* 批量插入
|
||||
* @param collection 批量插入数据
|
||||
* @return ignore
|
||||
*/
|
||||
int insertByBatch(@Param("collection") Collection<T> collection);
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.type;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* TODO 芋艿
|
||||
*
|
||||
* 参考 https://www.cnblogs.com/waterystone/p/5547254.html
|
||||
*
|
||||
* 后续,补充下注释和测试类,以及文章。
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class JSONTypeHandler<T extends Object> extends BaseTypeHandler<T> {
|
||||
|
||||
private Class<T> clazz;
|
||||
|
||||
public JSONTypeHandler(Class<T> clazz) {
|
||||
if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
|
||||
ps.setString(i, this.toJson(parameter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return this.toObject(rs.getString(columnName), clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return this.toObject(rs.getString(columnIndex), clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return this.toObject(cs.getString(columnIndex), clazz);
|
||||
}
|
||||
|
||||
private String toJson(T object) {
|
||||
try {
|
||||
return JSON.toJSONString(object);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private T toObject(String content, Class<?> clazz) {
|
||||
if (content != null && !content.isEmpty()) {
|
||||
try {
|
||||
return (T) JSON.parseObject(content, clazz);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package cn.iocoder.mall.mybatis.core.util;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.vo.PageParam;
|
||||
import cn.iocoder.common.framework.vo.SortingField;
|
||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 分页工具里
|
||||
*
|
||||
* 目前主要用于 {@link Page} 的构建
|
||||
*/
|
||||
public class PageUtil {
|
||||
|
||||
public static <T> Page<T> build(PageParam pageParam) {
|
||||
return build(pageParam, null);
|
||||
}
|
||||
|
||||
public static <T> Page<T> build(PageParam pageParam, Collection<SortingField> sortingFields) {
|
||||
// 页码 + 数量
|
||||
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
|
||||
// 排序字段
|
||||
if (!CollectionUtils.isEmpty(sortingFields)) {
|
||||
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
|
||||
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?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-redis</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,71 +0,0 @@
|
|||
package cn.iocoder.mall.redis.core;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis Key 定义类
|
||||
*/
|
||||
public class RedisKeyDefine {
|
||||
|
||||
public enum KeyTypeEnum {
|
||||
|
||||
STRING,
|
||||
LIST,
|
||||
HASH,
|
||||
SET,
|
||||
ZSET,
|
||||
STREAM,
|
||||
PUBSUB;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 过期时间 - 永不过期
|
||||
*/
|
||||
public static final Duration TIMEOUT_FOREVER = null;
|
||||
|
||||
/**
|
||||
* Key 模板
|
||||
*/
|
||||
private final String keyTemplate;
|
||||
/**
|
||||
* Key 类型的枚举
|
||||
*/
|
||||
private final KeyTypeEnum keyType;
|
||||
/**
|
||||
* Value 类型
|
||||
*
|
||||
* 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型
|
||||
*/
|
||||
private final Class<?> valueType;
|
||||
/**
|
||||
* 过期时间
|
||||
*
|
||||
* 为空时,表示永不过期 {@link #TIMEOUT_FOREVER}
|
||||
*/
|
||||
private final Duration timeout;
|
||||
|
||||
public RedisKeyDefine(String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {
|
||||
this.keyTemplate = keyTemplate;
|
||||
this.keyType = keyType;
|
||||
this.valueType = valueType;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getKeyTemplate() {
|
||||
return keyTemplate;
|
||||
}
|
||||
|
||||
public KeyTypeEnum getKeyType() {
|
||||
return keyType;
|
||||
}
|
||||
|
||||
public Class<?> getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?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-rocketmq</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- MQ 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,48 +0,0 @@
|
|||
<?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>
|
|
@ -1,60 +0,0 @@
|
|||
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 拦截器完成]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
||||
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
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.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
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.OAuth2Rpc;
|
||||
import cn.iocoder.mall.systemservice.rpc.oauth.dto.OAuth2AccessTokenRespDTO;
|
||||
import cn.iocoder.mall.systemservice.rpc.permission.PermissionRpc;
|
||||
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.apache.dubbo.config.annotation.Reference;
|
||||
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 {
|
||||
|
||||
@Reference(version = "${dubbo.consumer.OAuth2Rpc.version}")
|
||||
private OAuth2Rpc oauth2Rpc;
|
||||
@Reference(version = "${dubbo.consumer.PermissionRpc.version}")
|
||||
private PermissionRpc permissionRpc;
|
||||
|
||||
@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 = oauth2Rpc.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;
|
||||
}
|
||||
// 权限验证
|
||||
permissionRpc.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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.security.admin.config.AdminSecurityAutoConfiguration
|
|
@ -1,47 +0,0 @@
|
|||
<?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>
|
|
@ -1,47 +0,0 @@
|
|||
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 拦截器完成]");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
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.OAuth2Rpc;
|
||||
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.apache.dubbo.config.annotation.Reference;
|
||||
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 {
|
||||
|
||||
@Reference(version = "${dubbo.consumer.OAuth2Rpc.version}")
|
||||
private OAuth2Rpc oauth2Rpc;
|
||||
|
||||
@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 = oauth2Rpc.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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.security.user.config.UserSecurityAutoConfiguration
|
|
@ -1,25 +0,0 @@
|
|||
<?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>
|
|
@ -1,39 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.sentry.config.CustomSentryAutoConfiguration
|
|
@ -1,57 +0,0 @@
|
|||
package cn.iocoder.mall.swagger.config;
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
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 springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
/**
|
||||
* 简单的 Swagger2 自动配置类
|
||||
*
|
||||
* 较为完善的,可以了解 https://mvnrepository.com/artifact/com.spring4all/spring-boot-starter-swagger
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@EnableKnife4j
|
||||
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
|
||||
@ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true) // 允许使用 swagger.enable=false 禁用 Swagger
|
||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||
public class SwaggerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SwaggerProperties swaggerProperties() {
|
||||
return new SwaggerProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
SwaggerProperties properties = swaggerProperties();
|
||||
// 创建 Docket 对象
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo(properties))
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo(SwaggerProperties properties) {
|
||||
return new ApiInfoBuilder()
|
||||
.title(properties.getTitle())
|
||||
.description(properties.getDescription())
|
||||
.version(properties.getVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package cn.iocoder.mall.swagger.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("swagger")
|
||||
public class SwaggerProperties {
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String version;
|
||||
private String basePackage;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public SwaggerProperties setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public SwaggerProperties setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public SwaggerProperties setVersion(String version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public SwaggerProperties setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 无情的占位类
|
||||
*/
|
||||
package cn.iocoder.mall.swagger;
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.swagger.config.SwaggerAutoConfiguration
|
|
@ -1,46 +0,0 @@
|
|||
<?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>
|
|
@ -1,26 +0,0 @@
|
|||
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());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
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.ErrorCodeRpc;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.dto.ErrorCodeAutoGenerateDTO;
|
||||
import org.apache.dubbo.config.annotation.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
|
||||
@Reference(version = "${dubbo.consumer.ErrorCodeRpc.version}")
|
||||
private ErrorCodeRpc errorCodeRpc;
|
||||
|
||||
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 = errorCodeRpc.autoGenerateErrorCodes(autoGenerateDTOs);
|
||||
if (autoGenerateErrorCodesResult.isSuccess()) {
|
||||
logger.info("[execute][自动将 ({}) 类的错误码,成功写入到 system-service 服务]", errorCodeConstantsClass);
|
||||
} else {
|
||||
logger.error("[execute][自动将 ({}) 类的错误码,失败写入到 system-service 服务,原因为 ({}/{}/{})]", errorCodeConstantsClass,
|
||||
autoGenerateErrorCodesResult.getCode(), autoGenerateErrorCodesResult.getMessage(), autoGenerateErrorCodesResult.getDetailMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package cn.iocoder.mall.system.errorcode.core;
|
||||
|
||||
import cn.iocoder.common.framework.util.CollectionUtils;
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.ErrorCodeRpc;
|
||||
import cn.iocoder.mall.systemservice.rpc.errorcode.vo.ErrorCodeVO;
|
||||
import org.apache.dubbo.config.annotation.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
|
||||
@Reference(version = "${dubbo.consumer.ErrorCodeRpc.version}")
|
||||
private ErrorCodeRpc errorCodeRpc;
|
||||
|
||||
private Date maxUpdateTime;
|
||||
|
||||
public ErrorCodeRemoteLoader(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void loadErrorCodes() {
|
||||
// 从 ErrorCodeRpc 全量加载 ErrorCode 错误码
|
||||
CommonResult<List<ErrorCodeVO>> listErrorCodesResult = errorCodeRpc.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() {
|
||||
// 从 ErrorCodeRpc 增量加载 ErrorCode 错误码
|
||||
// TODO 优化点:假设删除错误码的配置,会存在问题;
|
||||
CommonResult<List<ErrorCodeVO>> listErrorCodesResult = errorCodeRpc.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());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.system.errorcode.config.ErrorCodeAutoConfiguration
|
|
@ -1,50 +0,0 @@
|
|||
<?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-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Mall 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>system-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,96 +0,0 @@
|
|||
package cn.iocoder.mall.web.config;
|
||||
|
||||
import cn.iocoder.mall.web.core.handler.GlobalExceptionHandler;
|
||||
import cn.iocoder.mall.web.core.handler.GlobalResponseBodyHandler;
|
||||
import cn.iocoder.mall.web.core.interceptor.AccessLogInterceptor;
|
||||
import cn.iocoder.mall.web.core.servlet.CorsFilter;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
public class CommonWebAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
// ========== 全局处理器 ==========
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
|
||||
public GlobalResponseBodyHandler globalResponseBodyHandler() {
|
||||
return new GlobalResponseBodyHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
|
||||
public GlobalExceptionHandler globalExceptionHandler() {
|
||||
return new GlobalExceptionHandler();
|
||||
}
|
||||
|
||||
// ========== 拦截器相关 ==========
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"cn.iocoder.mall.systemservice.rpc.systemlog.SystemExceptionLogRpc", "org.apache.dubbo.config.annotation.Reference"})
|
||||
@ConditionalOnMissingBean(AccessLogInterceptor.class)
|
||||
public AccessLogInterceptor accessLogInterceptor() {
|
||||
return new AccessLogInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
try {
|
||||
registry.addInterceptor(this.accessLogInterceptor());
|
||||
logger.info("[addInterceptors][加载 AccessLogInterceptor 拦截器完成]");
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
logger.warn("[addInterceptors][无法获取 AccessLogInterceptor 拦截器,因此不启动 AccessLog 的记录]");
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 过滤器相关 ==========
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FilterRegistrationBean<CorsFilter> corsFilter() {
|
||||
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new CorsFilter());
|
||||
registrationBean.addUrlPatterns("/*");
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
// ========== MessageConverter 相关 ==========
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
// 创建 FastJsonHttpMessageConverter 对象
|
||||
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
|
||||
// 自定义 FastJson 配置
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setCharset(Charset.defaultCharset()); // 设置字符集
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect, // 剔除循环引用
|
||||
SerializerFeature.WriteNonStringKeyAsString); // 解决 Integer 作为 Key 时,转换为 String 类型,避免浏览器报错
|
||||
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
|
||||
// 设置支持的 MediaType
|
||||
fastJsonHttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
// 添加到 converters 中
|
||||
converters.add(0, fastJsonHttpMessageConverter); // 注意,添加到最开头,放在 MappingJackson2XmlHttpMessageConverter 前面
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package cn.iocoder.mall.web.core.constant;
|
||||
|
||||
public interface CommonMallConstants {
|
||||
|
||||
// 全局请求路径枚举类,用于定义不同用户类型的根请求路径
|
||||
/**
|
||||
* 根路径 - 用户
|
||||
*/
|
||||
@Deprecated
|
||||
String ROOT_PATH_USER = "/users";
|
||||
/**
|
||||
* 根路径 - 管理员
|
||||
*/
|
||||
@Deprecated
|
||||
String ROOT_PATH_ADMIN = "/admins";
|
||||
|
||||
// HTTP Request Attr
|
||||
/**
|
||||
* HTTP Request Attr - 用户编号
|
||||
*
|
||||
* 考虑到 mall-spring-boot-starter-web 不依赖 mall-spring-boot-starter-security,但是又希望拿到认证过的用户编号,
|
||||
* 因此通过 Request 的 Attribute 进行共享
|
||||
*/
|
||||
String REQUEST_ATTR_USER_ID_KEY = "mall_user_id";
|
||||
/**
|
||||
* HTTP Request Attr - 用户类型
|
||||
*
|
||||
* 作用同 {@link #REQUEST_ATTR_USER_ID_KEY}
|
||||
*/
|
||||
String REQUEST_ATTR_USER_TYPE_KEY = "mall_user_type";
|
||||
|
||||
/**
|
||||
* HTTP Request Attr - Controller 执行返回
|
||||
*
|
||||
* 通过该 Request 的 Attribute,获取到请求执行结果,从而在访问日志中,记录是否成功。
|
||||
*/
|
||||
String REQUEST_ATTR_COMMON_RESULT = "mall_common_result";
|
||||
/**
|
||||
* HTTP Request Attr - 访问开始时间
|
||||
*/
|
||||
String REQUEST_ATTR_ACCESS_START_TIME = "mall_access_start_time";
|
||||
|
||||
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
package cn.iocoder.mall.web.core.handler;
|
||||
|
||||
import cn.iocoder.common.framework.exception.GlobalException;
|
||||
import cn.iocoder.common.framework.exception.ServiceException;
|
||||
import cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.util.MallUtils;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.rpc.systemlog.SystemExceptionLogRpc;
|
||||
import cn.iocoder.mall.systemservice.rpc.systemlog.dto.SystemExceptionLogCreateDTO;
|
||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.dubbo.config.annotation.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.common.framework.exception.enums.GlobalErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
// TODO 芋艿,应该还有其它的异常,需要进行翻译
|
||||
|
||||
// /**
|
||||
// * 异常总数 Metrics
|
||||
// */
|
||||
// private static final Counter EXCEPTION_COUNTER = Metrics.counter("mall.exception.total");
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String applicationName;
|
||||
|
||||
// TODO 目前存在一个问题,如果未引入 system-rpc-api 依赖,GlobalExceptionHandler 会报类不存在。未来封装出 Repository 解决该问题
|
||||
@Reference(version = "${dubbo.consumer.SystemExceptionLogRpc.version}")
|
||||
private SystemExceptionLogRpc systemExceptionLogRpc;
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 请求参数缺失
|
||||
*
|
||||
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
|
||||
*/
|
||||
@ExceptionHandler(value = MissingServletRequestParameterException.class)
|
||||
public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
|
||||
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 请求参数类型错误
|
||||
*
|
||||
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public CommonResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
|
||||
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 参数校验不正确
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public CommonResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
|
||||
logger.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
|
||||
FieldError fieldError = ex.getBindingResult().getFieldError();
|
||||
assert fieldError != null; // 断言,避免告警
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public CommonResult bindExceptionHandler(BindException ex) {
|
||||
logger.warn("[handleBindException]", ex);
|
||||
FieldError fieldError = ex.getFieldError();
|
||||
assert fieldError != null; // 断言,避免告警
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Validator 校验不通过产生的异常
|
||||
*/
|
||||
@ExceptionHandler(value = ConstraintViolationException.class)
|
||||
public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
||||
logger.warn("[constraintViolationExceptionHandler]", ex);
|
||||
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 请求地址不存在
|
||||
*
|
||||
* 注意,它需要设置如下两个配置项:
|
||||
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
|
||||
* 2. spring.mvc.static-path-pattern 为 /statics/**
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public CommonResult noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
|
||||
logger.warn("[noHandlerFoundExceptionHandler]", ex);
|
||||
return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 SpringMVC 请求方法不正确
|
||||
*
|
||||
* 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public CommonResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
|
||||
logger.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
|
||||
return CommonResult.error(GlobalErrorCodeConstants.METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()))
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常 ServiceException
|
||||
*
|
||||
* 例如说,商品库存不足,用户手机号已存在。
|
||||
*/
|
||||
@ExceptionHandler(value = ServiceException.class)
|
||||
public CommonResult serviceExceptionHandler(ServiceException ex) {
|
||||
logger.info("[serviceExceptionHandler]", ex);
|
||||
return CommonResult.error(ex.getCode(), ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理全局异常 ServiceException
|
||||
*
|
||||
* 例如说,Dubbo 请求超时,调用的 Dubbo 服务系统异常
|
||||
*/
|
||||
@ExceptionHandler(value = GlobalException.class)
|
||||
public CommonResult globalExceptionHandler(HttpServletRequest req, GlobalException ex) {
|
||||
// 系统异常时,才打印异常日志
|
||||
if (INTERNAL_SERVER_ERROR.getCode().equals(ex.getCode())) {
|
||||
// 插入异常日志
|
||||
this.createExceptionLog(req, ex);
|
||||
// 普通全局异常,打印 info 日志即可
|
||||
} else {
|
||||
logger.info("[globalExceptionHandler]", ex);
|
||||
}
|
||||
// 返回 ERROR CommonResult
|
||||
return CommonResult.error(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
|
||||
*/
|
||||
@ExceptionHandler(value = ValidationException.class)
|
||||
public CommonResult validationException(ValidationException ex) {
|
||||
logger.warn("[constraintViolationExceptionHandler]", ex);
|
||||
// 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
|
||||
return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确")
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统异常,兜底处理所有的一切
|
||||
*/
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public CommonResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
|
||||
logger.error("[defaultExceptionHandler]", ex);
|
||||
// 插入异常日志
|
||||
this.createExceptionLog(req, ex);
|
||||
// 返回 ERROR CommonResult
|
||||
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage())
|
||||
.setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
|
||||
}
|
||||
|
||||
public void createExceptionLog(HttpServletRequest req, Throwable e) {
|
||||
// 插入异常日志
|
||||
SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO();
|
||||
try {
|
||||
// 增加异常计数 metrics TODO 暂时去掉
|
||||
// EXCEPTION_COUNTER.increment();
|
||||
// 初始化 exceptionLog
|
||||
initExceptionLog(exceptionLog, req, e);
|
||||
// 执行插入 exceptionLog
|
||||
createExceptionLog(exceptionLog);
|
||||
} catch (Throwable th) {
|
||||
logger.error("[createExceptionLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 优化点:后续可以增加事件
|
||||
@Async
|
||||
public void createExceptionLog(SystemExceptionLogCreateDTO exceptionLog) {
|
||||
try {
|
||||
systemExceptionLogRpc.createSystemExceptionLog(exceptionLog);
|
||||
} catch (Throwable th) {
|
||||
logger.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
||||
}
|
||||
}
|
||||
|
||||
private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) {
|
||||
// 设置账号编号
|
||||
exceptionLog.setUserId(CommonWebUtil.getUserId(request));
|
||||
exceptionLog.setUserType(CommonWebUtil.getUserType(request));
|
||||
// 设置异常字段
|
||||
exceptionLog.setExceptionName(e.getClass().getName());
|
||||
exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
|
||||
exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
|
||||
exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e));
|
||||
StackTraceElement[] stackTraceElements = e.getStackTrace();
|
||||
Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
|
||||
StackTraceElement stackTraceElement = stackTraceElements[0];
|
||||
exceptionLog.setExceptionClassName(stackTraceElement.getClassName());
|
||||
exceptionLog.setExceptionFileName(stackTraceElement.getFileName());
|
||||
exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName());
|
||||
exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
|
||||
// 设置其它字段
|
||||
exceptionLog.setTraceId(MallUtils.getTraceId())
|
||||
.setApplicationName(applicationName)
|
||||
.setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
||||
.setQueryString(HttpUtil.buildQueryString(request))
|
||||
.setMethod(request.getMethod())
|
||||
.setUserAgent(HttpUtil.getUserAgent(request))
|
||||
.setIp(HttpUtil.getIp(request))
|
||||
.setExceptionTime(new Date());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package cn.iocoder.mall.web.core.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.util.MallUtils;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.systemservice.rpc.systemlog.SystemAccessLogRpc;
|
||||
import cn.iocoder.mall.systemservice.rpc.systemlog.dto.SystemAccessLogCreateDTO;
|
||||
import cn.iocoder.mall.web.core.util.CommonWebUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.dubbo.config.annotation.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 访问日志拦截器
|
||||
*/
|
||||
public class AccessLogInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Reference(version = "${dubbo.consumer.SystemAccessLogRpc.version}")
|
||||
private SystemAccessLogRpc systemAccessLogRpc;
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String applicationName;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 记录当前时间
|
||||
CommonWebUtil.setAccessStartTime(request, new Date());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
SystemAccessLogCreateDTO accessLog = new SystemAccessLogCreateDTO();
|
||||
try {
|
||||
// 初始化 accessLog
|
||||
initAccessLog(accessLog, request);
|
||||
// 执行插入 accessLog
|
||||
addAccessLog(accessLog);
|
||||
// TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
|
||||
} catch (Throwable th) {
|
||||
logger.error("[afterCompletion][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
||||
}
|
||||
}
|
||||
|
||||
private void initAccessLog(SystemAccessLogCreateDTO accessLog, HttpServletRequest request) {
|
||||
// 设置账号编号
|
||||
accessLog.setUserId(CommonWebUtil.getUserId(request));
|
||||
accessLog.setUserType(CommonWebUtil.getUserType(request));
|
||||
// 设置访问结果
|
||||
CommonResult result = CommonWebUtil.getCommonResult(request);
|
||||
if (result != null) {
|
||||
accessLog.setErrorCode(result.getCode()).setErrorMessage(result.getMessage());
|
||||
} else {
|
||||
// 在访问非 onemall 系统提供的 API 时,会存在没有 CommonResult 的情况。例如说,Swagger 提供的接口
|
||||
accessLog.setErrorCode(0).setErrorMessage("");
|
||||
}
|
||||
// 设置其它字段
|
||||
accessLog.setTraceId(MallUtils.getTraceId())
|
||||
.setApplicationName(applicationName)
|
||||
.setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
||||
.setQueryString(HttpUtil.buildQueryString(request))
|
||||
.setMethod(request.getMethod())
|
||||
.setUserAgent(HttpUtil.getUserAgent(request))
|
||||
.setIp(HttpUtil.getIp(request))
|
||||
.setStartTime(CommonWebUtil.getAccessStartTime(request))
|
||||
.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime())); // 默认响应时间设为 0
|
||||
}
|
||||
|
||||
// TODO 优化点:后续可以增加事件
|
||||
@Async // 异步入库
|
||||
public void addAccessLog(SystemAccessLogCreateDTO accessLog) {
|
||||
try {
|
||||
systemAccessLogRpc.createSystemAccessLog(accessLog);
|
||||
} catch (Throwable th) {
|
||||
logger.error("[addAccessLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package cn.iocoder.mall.web.core.servlet;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Cors 过滤器
|
||||
*
|
||||
* 未来使用 {@link org.springframework.web.filter.CorsFilter} 替换
|
||||
*/
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) { }
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
resp.setHeader("Access-Control-Allow-Origin", "*");
|
||||
resp.setHeader("Access-Control-Allow-Methods", "*");
|
||||
resp.setHeader("Access-Control-Allow-Headers", "*");
|
||||
resp.setHeader("Access-Control-Max-Age", "1800");
|
||||
// For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake
|
||||
// 例如说,vue axios 请求时,会自带该逻辑的
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
if (req.getMethod().equals("OPTIONS")) {
|
||||
resp.setStatus(HttpServletResponse.SC_ACCEPTED);
|
||||
return;
|
||||
}
|
||||
// 如果是其它请求方法,则继续过滤器。
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package cn.iocoder.mall.web.core.util;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.web.core.constant.CommonMallConstants;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import java.util.Date;
|
||||
|
||||
public class CommonWebUtil {
|
||||
|
||||
public static Integer getUserId(ServletRequest request) {
|
||||
return (Integer) request.getAttribute(CommonMallConstants.REQUEST_ATTR_USER_ID_KEY);
|
||||
}
|
||||
|
||||
public static void setUserId(ServletRequest request, Integer userId) {
|
||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_USER_ID_KEY, userId);
|
||||
}
|
||||
|
||||
public static Integer getUserType(ServletRequest request) {
|
||||
return (Integer) request.getAttribute(CommonMallConstants.REQUEST_ATTR_USER_TYPE_KEY);
|
||||
}
|
||||
|
||||
public static void setUserType(ServletRequest request, Integer userType) {
|
||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_USER_TYPE_KEY, userType);
|
||||
}
|
||||
|
||||
public static CommonResult getCommonResult(ServletRequest request) {
|
||||
return (CommonResult) request.getAttribute(CommonMallConstants.REQUEST_ATTR_COMMON_RESULT);
|
||||
}
|
||||
|
||||
public static void setCommonResult(ServletRequest request, CommonResult result) {
|
||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_COMMON_RESULT, result);
|
||||
}
|
||||
|
||||
public static void setAccessStartTime(ServletRequest request, Date startTime) {
|
||||
request.setAttribute(CommonMallConstants.REQUEST_ATTR_ACCESS_START_TIME, startTime);
|
||||
}
|
||||
|
||||
public static Date getAccessStartTime(ServletRequest request) {
|
||||
return (Date) request.getAttribute(CommonMallConstants.REQUEST_ATTR_ACCESS_START_TIME);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.web.config.CommonWebAutoConfiguration
|
|
@ -1,172 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration
|
|
@ -1,55 +0,0 @@
|
|||
<?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>
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.mall.spring.boot.metrics.MetricsAutoConfiguration
|
|
@ -1,43 +0,0 @@
|
|||
<?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>
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
* setup
|
||||
> 提供安装指南
|
||||
* [搭建调试环境](https://gitee.com/zhijiantianya/onemall/docs/setup/quick-start.md)
|
||||
|
||||
* sql
|
||||
> 提供 SQL 文件
|
||||
|
||||
* guides
|
||||
> 提供入门指南
|
||||
* TODO
|
|
@ -1,13 +1,28 @@
|
|||
{
|
||||
"local": {
|
||||
"baseUrl": "http://127.0.0.1:18083/management-api/",
|
||||
"management-api-base-url": "http://127.0.0.1:18083/management-api/",
|
||||
"accessToken": "yudaoyuanma",
|
||||
"baseUrl": "http://127.0.0.1:48080/admin-api",
|
||||
"systemBaseUrl": "http://127.0.0.1:48081/admin-api",
|
||||
"infaBaseUrl": "http://127.0.0.1:48082/admin-api",
|
||||
|
||||
"user-api-base-url": "http://127.0.0.1:18082/user-api/",
|
||||
"shop-api-base-url": "http://127.0.0.1:18084/shop-api/",
|
||||
"user-access-token": "yunai",
|
||||
"token": "test1",
|
||||
"adminTenentId": "1",
|
||||
"tag": "${HOSTNAME}",
|
||||
|
||||
"dubboTag": "${HOSTNAME}"
|
||||
"appApi": "http://127.0.0.1:48080/app-api",
|
||||
"appToken": "test1",
|
||||
"appTenentId": "1"
|
||||
},
|
||||
"gateway": {
|
||||
"baseUrl": "http://127.0.0.1:48080/admin-api",
|
||||
"systemBaseUrl": "http://127.0.0.1:48080/admin-api",
|
||||
"infaBaseUrl": "http://127.0.0.1:48080/admin-api",
|
||||
|
||||
"token": "test1",
|
||||
"adminTenentId": "1",
|
||||
"tag": "${HOSTNAME}",
|
||||
|
||||
"appApi": "http://127.0.0.1:8888/app-api",
|
||||
"appToken": "test1",
|
||||
"appTenentId": "1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
config.stopBubbling = true
|
||||
lombok.tostring.callsuper=CALL
|
||||
lombok.equalsandhashcode.callsuper=CALL
|
||||
lombok.accessors.chain=true
|
|
@ -1,13 +0,0 @@
|
|||
package cn.iocoder.mall.managementweb;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ManagementWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ManagementWebApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
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.PayTransactionRpc;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionPageReqDTO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionRespDTO;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PayTransactionClient {
|
||||
|
||||
@DubboReference(version = "${dubbo.consumer.PayTransactionRpc.version}")
|
||||
private PayTransactionRpc payTransactionRpc;
|
||||
|
||||
public PageResult<PayTransactionRespDTO> pagePayTransaction(PayTransactionPageReqDTO pageReqDTO) {
|
||||
CommonResult<PageResult<PayTransactionRespDTO>> pagePayTransactionResult = payTransactionRpc.pagePayTransaction(pageReqDTO);
|
||||
pagePayTransactionResult.checkError();
|
||||
return pagePayTransactionResult.getData();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
### /admin/page 成功
|
||||
GET {{baseUrl}}/admin/page?pageNo=1&pageSize=10
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
### /admin/create 成功
|
||||
POST {{baseUrl}}/admin/create
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
username=admin02&password=buzhidao&name=测试管理员&departmentId=1
|
||||
|
||||
### /admin/update 成功
|
||||
POST {{baseUrl}}/admin/update
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
id=31&username=admin02&password=buzhidao&name=测试管理员&departmentId=1
|
||||
|
||||
### /admin/update-status 成功
|
||||
POST {{baseUrl}}/admin/update-status
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
adminId=31&status=1
|
||||
|
||||
### /admin/update-status 失败,参数缺失
|
||||
POST {{baseUrl}}/admin/update-status
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
adminId=31
|
||||
|
||||
### admin/update-status 失败,地址不存在
|
||||
GET {{baseUrl}}/admin/update-status---
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Bearer {{accessToken}}
|
||||
|
||||
adminId=31&status=sss
|
||||
|
||||
###
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue