!4 Spring Cloud Alibaba 完成 system 和 infra 的迁移

Merge pull request !4 from 芋道源码/spring-cloud-alibaba
This commit is contained in:
芋道源码 2022-06-26 14:10:29 +00:00 committed by Gitee
commit ff055e5a2c
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
2462 changed files with 94569 additions and 25001 deletions

View File

@ -0,0 +1,25 @@
碰到问题,请在 <https://gitee.com/zhijiantianya/yudao-cloud/issues> 搜索是否存在相似的 issue。
不按照模板提交的 issue会被系统自动删除。
### 基本信息
- ruoyi-vue-pro 版本:
- 操作系统:
- 数据库:
### 你猜测可能的原因
(必填)我花费了 2-4 小时自查发现可能的原因是xxxxxx
### 复现步骤
第一步,
第二步,
第三步,
### 报错信息
带上必要的截图

34
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,34 @@
---
name: 问题反馈
about: 请详细描述,以便更高快的获得到解决
title: ''
labels: ''
assignees: ''
---
碰到问题,请在 <https://github.com/YunaiV/yudao-cloud/issues> 搜索是否存在相似的 issue。
不按照模板提交的 issue会被系统自动删除。
### 基本信息
- ruoyi-vue-pro 版本:
- 操作系统:
- 数据库:
### 你猜测可能的原因
(必填)我花费了 2-4 小时自查发现可能的原因是xxxxxx
### 复现步骤
第一步,
第二步,
第三步,
### 报错信息
带上必要的截图

30
.github/workflows/maven.yml vendored Normal file
View File

@ -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

View File

@ -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/> 地址。
----
# 前言

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -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());
}
}

View File

@ -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;
// }
}

View File

@ -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());
}
}

View File

@ -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>

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {};
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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=

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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 标签路由]");
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -1,2 +0,0 @@
dubboExceptionFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderExceptionFilter
dubboProviderRouterTagFilter=cn.iocoder.mall.dubbo.core.filter.DubboProviderRouterTagFilter

View File

@ -1 +0,0 @@
dubboConsumerRouterTagClusterInterceptor=cn.iocoder.mall.dubbo.core.cluster.interceptor.DubboConsumerRouterTagClusterInterceptor

View File

@ -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

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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 拦截器完成]");
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.security.admin.config.AdminSecurityAutoConfiguration

View File

@ -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>

View File

@ -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 拦截器完成]");
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.security.user.config.UserSecurityAutoConfiguration

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.sentry.config.CustomSentryAutoConfiguration

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -1,4 +0,0 @@
/**
* 无情的占位类
*/
package cn.iocoder.mall.swagger;

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.swagger.config.SwaggerAutoConfiguration

View File

@ -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>

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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());
});
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.system.errorcode.config.ErrorCodeAutoConfiguration

View File

@ -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>

View File

@ -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 前面
}
}

View File

@ -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";
}

View File

@ -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());
}
}

View File

@ -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));
}
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.web.config.CommonWebAutoConfiguration

View File

@ -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;
}
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.xxljob.config.XxlJobAutoConfiguration

View File

@ -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>

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.spring.boot.metrics.MetricsAutoConfiguration

View File

@ -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>

View File

@ -1,11 +0,0 @@
* setup
> 提供安装指南
* [搭建调试环境](https://gitee.com/zhijiantianya/onemall/docs/setup/quick-start.md)
* sql
> 提供 SQL 文件
* guides
> 提供入门指南
* TODO

View File

@ -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"
}
}

4
lombok.config Normal file
View File

@ -0,0 +1,4 @@
config.stopBubbling = true
lombok.tostring.callsuper=CALL
lombok.equalsandhashcode.callsuper=CALL
lombok.accessors.chain=true

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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