mirror of
https://github.com/RemainderTime/spring-boot-base-demo.git
synced 2026-06-09 11:39:58 +08:00
Merge branch 'master' into feature/admin-auth-satoken
# Conflicts: # src/main/java/cn/xf/basedemo/interceptor/TokenInterceptor.java # src/main/java/cn/xf/basedemo/service/impl/UserServiceImpl.java
This commit is contained in:
29
Dockerfile
29
Dockerfile
@@ -1,22 +1,17 @@
|
||||
# java8运行环境
|
||||
FROM openjdk:8-jdk-alpine
|
||||
# 作者名称
|
||||
MAINTAINER xiongfeng
|
||||
# 使用 JDK 17 轻量级运行环境
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
|
||||
# 作者信息
|
||||
LABEL maintainer="xiongfeng"
|
||||
|
||||
# 切换工作目录
|
||||
WORKDIR /root/java
|
||||
|
||||
#1. coding自动化部署
|
||||
#COPY target/*.jar app.jar
|
||||
## 暴露端口8080
|
||||
##EXPOSE 8080
|
||||
## 运行命令
|
||||
#ENTRYPOINT ["java","-Djava.security.egd=file:/dev/urandom","-Dfile.encoding=UTF-8","-Duser.timezone=Asia/Shanghai","-XX:MaxDirectMemorySize=1024m","-XX:MetaspaceSize=256m","-XX:MaxMetaspaceSize=512m","-XX:MaxRAMPercentage=80.0","-jar","app.jar"]
|
||||
# 将编译好的 jar 包复制到容器中,避免硬编码包名版本号
|
||||
COPY target/*.jar app.jar
|
||||
|
||||
#2. 手动部署项目到docker环境中
|
||||
# 添加demo-start-1.0.0.jar文件到docker环境内
|
||||
ADD xf-boot-base-1.0.1.jar /root/java/xf-boot-base-1.0.1.jar
|
||||
## 暴露端口8080
|
||||
#EXPOSE 8080
|
||||
## 运行命令
|
||||
ENTRYPOINT ["java", "-server", "-Xms512m", "-Xmx512m", "-jar", "/root/java/xf-boot-base-1.0.1.jar"]
|
||||
# 暴露端口 8089 (对齐 application.yml)
|
||||
EXPOSE 8089
|
||||
|
||||
# 运行命令,加入垃圾回收和内存优化参数,以及时区与字符编码设置
|
||||
ENTRYPOINT ["java", "-server", "-Xms512m", "-Xmx512m", "-Dfile.encoding=UTF-8", "-Duser.timezone=Asia/Shanghai", "-jar", "app.jar"]
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -91,6 +91,10 @@
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
<!--redis连接池需要依赖-->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
||||
@@ -5,8 +5,11 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public enum SystemStatus {
|
||||
|
||||
SUSSES(200, "请求成功"),
|
||||
UNAVAILABILITY(401, "token无效"),
|
||||
SUCCESS(200, "请求成功"),
|
||||
USER_INPUT_ERROR(400, "参数或用户输入错误"),
|
||||
UNAUTHORIZED(401, "token无效"),
|
||||
FORBIDDEN(403, "禁止访问"),
|
||||
TOO_FREQUENT_VISIT(429, "访问太频繁,请休息一会儿"),
|
||||
ERROR(500, "系统异常")
|
||||
;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.xf.basedemo.common.exception;
|
||||
|
||||
import cn.xf.basedemo.common.enums.SystemStatus;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -10,29 +11,29 @@ import lombok.Getter;
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException{
|
||||
private final ResponseCode code;
|
||||
private final SystemStatus status;
|
||||
|
||||
public BusinessException() {
|
||||
super(String.format("%s", ResponseCode.INTERNAL_ERROR.getMessage()));
|
||||
this.code = ResponseCode.INTERNAL_ERROR;
|
||||
super(String.format("%s", SystemStatus.ERROR.getErrorMessage()));
|
||||
this.status = SystemStatus.ERROR;
|
||||
}
|
||||
|
||||
public BusinessException(Throwable e) {
|
||||
super(e);
|
||||
this.code = ResponseCode.INTERNAL_ERROR;
|
||||
this.status = SystemStatus.ERROR;
|
||||
}
|
||||
|
||||
public BusinessException(String msg) {
|
||||
this(ResponseCode.INTERNAL_ERROR, msg);
|
||||
this(SystemStatus.ERROR, msg);
|
||||
}
|
||||
|
||||
public BusinessException(ResponseCode code) {
|
||||
super(String.format("%s", code.getMessage()));
|
||||
this.code = code;
|
||||
public BusinessException(SystemStatus status) {
|
||||
super(String.format("%s", status.getErrorMessage()));
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public BusinessException(ResponseCode code, String msg) {
|
||||
public BusinessException(SystemStatus status, String msg) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package cn.xf.basedemo.common.exception;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author: xiongfeng
|
||||
* @CreateTime: 2023-11-08 13:47
|
||||
* @Description: TODO
|
||||
* @Version: 1.0
|
||||
*/
|
||||
@Data
|
||||
public class GenericResponse<T> {
|
||||
private int code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String message;
|
||||
|
||||
public GenericResponse() {};
|
||||
|
||||
public GenericResponse(int code, T data) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public GenericResponse(int code, T data, String message) {
|
||||
this(code, data);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public GenericResponse(ResponseCode responseCode) {
|
||||
this.code = responseCode.getCode();
|
||||
this.data = null;
|
||||
this.message = responseCode.getMessage();
|
||||
}
|
||||
|
||||
public GenericResponse(ResponseCode responseCode, T data) {
|
||||
this(responseCode);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public GenericResponse(ResponseCode responseCode, T data, String message) {
|
||||
this(responseCode, data);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.xf.basedemo.common.exception;
|
||||
|
||||
import cn.xf.basedemo.common.enums.SystemStatus;
|
||||
import cn.xf.basedemo.common.model.RetObj;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
@@ -61,9 +63,9 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(LoginException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public GenericResponse<Void> handleLoginException(LoginException e, HttpServletRequest request) {
|
||||
log.warn("认证失败 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage());
|
||||
return new GenericResponse<>(e.getCode(), null, e.getMessage());
|
||||
public RetObj<Void> handleLoginException(LoginException e, HttpServletRequest request) {
|
||||
log.warn("认证失败 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getStatus().getCode(), e.getMessage());
|
||||
return new RetObj<>(e.getStatus().getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,9 +73,9 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST) // 或者使用 HttpStatus.OK,根据前端约定
|
||||
public GenericResponse<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
log.warn("业务异常 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage());
|
||||
return new GenericResponse<>(e.getCode(), null, e.getMessage());
|
||||
public RetObj<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
log.warn("业务异常 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getStatus().getCode(), e.getMessage());
|
||||
return new RetObj<>(e.getStatus().getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +83,7 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public GenericResponse<Void> handleValidationException(Exception e, HttpServletRequest request) {
|
||||
public RetObj<Void> handleValidationException(Exception e, HttpServletRequest request) {
|
||||
BindingResult bindingResult = null;
|
||||
if (e instanceof MethodArgumentNotValidException) {
|
||||
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
|
||||
@@ -97,7 +99,7 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
}
|
||||
log.warn("参数校验失败 [URL:{}]: {}", request.getRequestURI(), errorMsg);
|
||||
return new GenericResponse<>(ResponseCode.USER_INPUT_ERROR.getCode(), null, errorMsg);
|
||||
return new RetObj<>(SystemStatus.USER_INPUT_ERROR.getCode(), errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,9 +107,9 @@ public class GlobalExceptionHandler {
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public GenericResponse<Void> handleSystemException(Exception e, HttpServletRequest request) {
|
||||
public RetObj<Void> handleSystemException(Exception e, HttpServletRequest request) {
|
||||
// 生产级关键点:必须记录异常堆栈,否则无法排查 BUG
|
||||
log.error("系统发生未知异常 [URL:{}]", request.getRequestURI(), e);
|
||||
return new GenericResponse<>(500, null, "系统内部繁忙,请稍后再试");
|
||||
return new RetObj<>(SystemStatus.ERROR.getCode(), "系统内部繁忙,请稍后再试");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.xf.basedemo.common.exception;
|
||||
|
||||
import cn.xf.basedemo.common.enums.SystemStatus;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,30 +12,30 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class LoginException extends RuntimeException{
|
||||
|
||||
private final ResponseCode code;
|
||||
private final SystemStatus status;
|
||||
|
||||
public LoginException() {
|
||||
super(String.format("%s", ResponseCode.AUTHENTICATION_NEEDED.getMessage()));
|
||||
this.code = ResponseCode.AUTHENTICATION_NEEDED;
|
||||
super(String.format("%s", SystemStatus.UNAUTHORIZED.getErrorMessage()));
|
||||
this.status = SystemStatus.UNAUTHORIZED;
|
||||
}
|
||||
|
||||
public LoginException(Throwable e) {
|
||||
super(e);
|
||||
this.code = ResponseCode.AUTHENTICATION_NEEDED;
|
||||
this.status = SystemStatus.UNAUTHORIZED;
|
||||
}
|
||||
|
||||
public LoginException(String msg) {
|
||||
this(ResponseCode.AUTHENTICATION_NEEDED, msg);
|
||||
this(SystemStatus.UNAUTHORIZED, msg);
|
||||
}
|
||||
|
||||
public LoginException(ResponseCode code) {
|
||||
super(String.format("%s", code.getMessage()));
|
||||
this.code = code;
|
||||
public LoginException(SystemStatus status) {
|
||||
super(String.format("%s", status.getErrorMessage()));
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LoginException(ResponseCode code, String msg) {
|
||||
public LoginException(SystemStatus status, String msg) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,20 +46,20 @@ public class RetObj<T> {
|
||||
}
|
||||
|
||||
public static <T> RetObj<T> success() {
|
||||
return new RetObj(SystemStatus.SUSSES);
|
||||
return new RetObj<>(SystemStatus.SUCCESS);
|
||||
}
|
||||
|
||||
public static <T> RetObj<T> success(T data) {
|
||||
return new RetObj(SystemStatus.SUSSES, data);
|
||||
return new RetObj<>(SystemStatus.SUCCESS, data);
|
||||
}
|
||||
|
||||
|
||||
public static <T> RetObj<T> error(SystemStatus status) {
|
||||
return new RetObj(status);
|
||||
return new RetObj<>(status);
|
||||
}
|
||||
|
||||
public static <T> RetObj<T> error(String errorMsg) {
|
||||
return new RetObj(SystemStatus.ERROR.getCode(), errorMsg);
|
||||
return new RetObj<>(SystemStatus.ERROR.getCode(), errorMsg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class JwtTokenUtils {
|
||||
*/
|
||||
public static Integer getUserId(String token) {
|
||||
Map<String, Claim> claims = verifyToken(token);
|
||||
if (claims != null) {
|
||||
if (claims == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package cn.xf.basedemo.common.utils;
|
||||
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.*;
|
||||
@@ -11,6 +8,7 @@ import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -20,7 +18,7 @@ import java.util.Map;
|
||||
* @description: 加密工具类
|
||||
* @author: xiongfeng
|
||||
* @create: 2022-06-20 10:37
|
||||
**/
|
||||
* **/
|
||||
public class RSAUtils {
|
||||
|
||||
//算法类型
|
||||
@@ -49,10 +47,10 @@ public class RSAUtils {
|
||||
kpg.initialize(ENCRYPT_SIZE);
|
||||
KeyPair keyPair = kpg.generateKeyPair();
|
||||
PublicKey aPublic = keyPair.getPublic();
|
||||
String publicKey = Base64.encodeBase64URLSafeString(aPublic.getEncoded());
|
||||
String publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(aPublic.getEncoded());
|
||||
|
||||
PrivateKey aPrivate = keyPair.getPrivate();
|
||||
String privateKey = Base64.encodeBase64URLSafeString(aPrivate.getEncoded());
|
||||
String privateKey = Base64.getUrlEncoder().withoutPadding().encodeToString(aPrivate.getEncoded());
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
|
||||
@@ -73,7 +71,7 @@ public class RSAUtils {
|
||||
public static RSAPublicKey getPublicKey(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
// 通过X509编码的Key指令获得公钥对象
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64URLSafe(publicKeyStr));
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getUrlDecoder().decode(publicKeyStr));
|
||||
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
|
||||
return key;
|
||||
}
|
||||
@@ -89,7 +87,7 @@ public class RSAUtils {
|
||||
public static RSAPrivateKey getPrivateKey(String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
|
||||
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64URLSafe(privateKeyStr));
|
||||
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getUrlDecoder().decode(privateKeyStr));
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
|
||||
return privateKey;
|
||||
}
|
||||
@@ -109,8 +107,7 @@ public class RSAUtils {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
byte[] bytes = cipher.doFinal(data.getBytes());
|
||||
return Base64.encodeBase64URLSafeString(bytes);
|
||||
// return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
@@ -121,9 +118,7 @@ public class RSAUtils {
|
||||
Cipher cipher = Cipher.getInstance(RSA);
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
// byte[] bytes = cipher.doFinal(Base64.decodeBase64(data.getBytes(CHARSET)));
|
||||
// return new String(bytes);
|
||||
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data.getBytes(CHARSET), 0, data.getBytes(CHARSET).length), privateKey.getModulus().bitLength()), CHARSET);
|
||||
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getUrlDecoder().decode(data.getBytes(CHARSET)), privateKey.getModulus().bitLength()), CHARSET);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
|
||||
}
|
||||
@@ -137,11 +132,10 @@ public class RSAUtils {
|
||||
} else {
|
||||
maxBlock = keySize / 8 - 11;
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int offSet = 0;
|
||||
byte[] buff;
|
||||
int i = 0;
|
||||
try {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
byte[] buff;
|
||||
while (datas.length > offSet) {
|
||||
if (datas.length - offSet > maxBlock) {
|
||||
//可以调用以下的doFinal()方法完成加密或解密数据:
|
||||
@@ -153,12 +147,10 @@ public class RSAUtils {
|
||||
i++;
|
||||
offSet = i * maxBlock;
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
|
||||
}
|
||||
byte[] resultDatas = out.toByteArray();
|
||||
IOUtils.closeQuietly(out);
|
||||
return resultDatas;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -52,9 +52,9 @@ public class RequestHeaderUtil {
|
||||
public static String getToken(HttpServletRequest request) {
|
||||
//登录处理
|
||||
String token = request.getHeader("Authorization");
|
||||
if (StringUtils.isEmpty(token))
|
||||
if (!StringUtils.hasText(token))
|
||||
token = request.getParameter("token");
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
throw new LoginException("请先登录");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ package cn.xf.basedemo.common.utils;
|
||||
public class StringUtil {
|
||||
|
||||
/**
|
||||
* 驼峰命名法转下划线命名法
|
||||
* 驼峰命名法转短横线命名法(kebab-case)
|
||||
*
|
||||
* @param camelCase 驼峰命名法字符串
|
||||
* @return 下划线命名法字符串
|
||||
* @return 短横线命名法字符串
|
||||
*/
|
||||
public static String camelToKebabCase(String camelCase) {
|
||||
if (camelCase == null || camelCase.isEmpty()) {
|
||||
@@ -26,4 +26,22 @@ public class StringUtil {
|
||||
// 转换为小写
|
||||
return result.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法转下划线命名法(snake_case)
|
||||
*
|
||||
* @param camelCase 驼峰命名法字符串
|
||||
* @return 下划线命名法字符串
|
||||
*/
|
||||
public static String camelToSnakeCase(String camelCase) {
|
||||
if (camelCase == null || camelCase.isEmpty()) {
|
||||
return camelCase;
|
||||
}
|
||||
|
||||
// 使用正则表达式将大写字母前插入一个"_"
|
||||
String result = camelCase.replaceAll("([a-z])([A-Z])", "$1_$2");
|
||||
|
||||
// 转换为小写
|
||||
return result.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
50
src/main/java/cn/xf/basedemo/config/ThreadPoolConfig.java
Normal file
50
src/main/java/cn/xf/basedemo/config/ThreadPoolConfig.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package cn.xf.basedemo.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* @Description: 统一异步线程池配置类(防止默认线程池由于无界队列引发 OOM)
|
||||
* @Author: xiongfeng
|
||||
* @Date: 2026/5/28
|
||||
* @Version: 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Bean(name = "asyncServiceExecutor")
|
||||
public Executor asyncServiceExecutor() {
|
||||
log.info("初始化异步线程池 [asyncServiceExecutor] 开始...");
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 核心线程数:根据 CPU 核心数与业务性质配置(此处设为 10)
|
||||
executor.setCorePoolSize(10);
|
||||
// 最大线程数:高并发时能够支持的最大线程数
|
||||
executor.setMaxPoolSize(50);
|
||||
// 缓冲队列容量:限制任务队列大小,防止无界队列堆积导致 OOM
|
||||
executor.setQueueCapacity(1000);
|
||||
// 空闲线程存活时间(秒)
|
||||
executor.setKeepAliveSeconds(60);
|
||||
// 线程名称前缀
|
||||
executor.setThreadNamePrefix("async-executor-");
|
||||
|
||||
// 拒绝策略:当队列满且达到最大线程数时,由调用者线程直接执行任务(保证任务不丢失,同时降低提交速度)
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
|
||||
// 优雅停机配置
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
|
||||
executor.initialize();
|
||||
log.info("初始化异步线程池 [asyncServiceExecutor] 成功!");
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.xf.basedemo.model.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
@@ -17,6 +19,7 @@ import java.util.Date;
|
||||
@TableName(value = "xf_user")
|
||||
public class User {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -47,10 +48,12 @@ public class UserServiceImpl implements UserService {
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
@Override
|
||||
public RetObj login(LoginInfoRes res) {
|
||||
|
||||
if (Objects.isNull(res) || StringUtils.isEmpty(res.getEncryptedData())) {
|
||||
if (Objects.isNull(res) || !StringUtils.hasText(res.getEncryptedData())) {
|
||||
return null;
|
||||
}
|
||||
String loginJson = "";
|
||||
@@ -67,15 +70,14 @@ public class UserServiceImpl implements UserService {
|
||||
e.printStackTrace();
|
||||
return RetObj.error("账号或密码错误");
|
||||
}
|
||||
if (!StringUtils.isEmpty(loginInfo.check())) {
|
||||
if (StringUtils.hasText(loginInfo.check())) {
|
||||
return RetObj.error(loginInfo.check());
|
||||
}
|
||||
// 校验登录账号密码
|
||||
QueryWrapper queryWrapper = new QueryWrapper();
|
||||
//校验登录账号密码
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("account", loginInfo.getAccount());
|
||||
queryWrapper.eq("password", loginInfo.getPwd());
|
||||
User user = userMapper.selectOne(queryWrapper);
|
||||
if (Objects.isNull(user)) {
|
||||
if (Objects.isNull(user) || !passwordEncoder.matches(loginInfo.getPwd(), user.getPassword())) {
|
||||
return RetObj.error("账号或密码错误");
|
||||
}
|
||||
LoginUser loginUser = new LoginUser();
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 20MB
|
||||
max-request-size: 20MB
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
serialization:
|
||||
WRITE_DATES_AS_TIMESTAMPS: false
|
||||
FAIL_ON_EMPTY_BEANS: false
|
||||
datasource:
|
||||
dynamic:
|
||||
primary: master
|
||||
strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
|
||||
hikari:
|
||||
maximum-pool-size: 4
|
||||
minimum-idle: 4
|
||||
leak-detection-threshold: 0
|
||||
connection-init-sql: SELECT 1
|
||||
connection-test-query: SELECT 1
|
||||
datasource:
|
||||
master: #${SERVER_ADDRESS}
|
||||
url: jdbc:mysql://localhost:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username:
|
||||
password:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
slave:
|
||||
url: jdbc:mysql://localhost:3307/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username:
|
||||
password:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
data:
|
||||
redis:
|
||||
port: 6379
|
||||
host: localhost
|
||||
password:
|
||||
timeout: 5000
|
||||
lettuce: #参考博客 https://blog.csdn.net/weixin_43944305/article/details/124322595
|
||||
pool:
|
||||
maxActive: 5000
|
||||
maxIdle: 30
|
||||
minIdle: 5
|
||||
max-wait: 2000
|
||||
time-between-eviction-runs: 60s
|
||||
cluster:
|
||||
refresh:
|
||||
adaptive: true
|
||||
period: 60s
|
||||
|
||||
global:
|
||||
rsaPublicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC_F5UQC1QWsu3QsESQBz9M-GDA9Atm0qVSvwIsy568lyRLi-nq3VvvnmgrlL4yTbngFzyfb2Dn35cNCHsBvIaGuCY3_PpzPqMzVpxr2QlEkhEX9atnJQ1rWexS8QeZtPjpiIwoQrChTzXjD_sYUkDrqSykFplyivf0NSO2WqCBdwIDAQAB
|
||||
rsaPrivateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL8XlRALVBay7dCwRJAHP0z4YMD0C2bSpVK_AizLnryXJEuL6erdW--eaCuUvjJNueAXPJ9vYOfflw0IewG8hoa4Jjf8-nM-ozNWnGvZCUSSERf1q2clDWtZ7FLxB5m0-OmIjChCsKFPNeMP-xhSQOupLKQWmXKK9_Q1I7ZaoIF3AgMBAAECgYBxTUA61Ry0oL7U_86HP2TO9G4ZuhmQi9EucMaPXOPvmgYRLRIzCbDbMKc_P-BN3zwYnG57cgSZNz9OoPqeGvP_oVTnkoEpVkCSV-JP2p_DK09LdbDqszJXMrxAkPmWGUw8IRMcTJT1xJJcgzFE6T0CmTo-Vk47AnmqfJD4U6o74QJBAPRjVUJKZnrMSnSqKPDL2ThgTo8h7-KFxl_Z-g724lTOFiCmBpi6nCWAcuacFRrrYqxF-r9c4zdIyR7AvLROql8CQQDIK_kRF52dVtwShciZhyeUBLoi0nWV9F8mMGt60NTEER9zPEgPsv2aVn8h97KMWOwmd2Da4EPm25QxOuaKQC_pAkBczcfXp5co9KElkmR_pHl1jiTm97U3qSM-zPDHc_tYxvXiKgoBP4QCPbfkWMsu8MoEr4Jb3vMt0EcHlZtTQTgzAkAfmNla-lhV4sUgY1_T5EK6GbjsED6hag6u74u3ukkrnexR-10ApWdkumydBwV3I_464DM4uZfeVCDjWIHVpuYpAkEA6QLPztGD4V8Q1PqTEeSF3i68CKPM8vO1_mCH2JD7qsqDQcIKkczj5rTg7hlOKwB9V6gSw4CbnOF6moTooRD-cQ
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs # 自定义 API 文档路径
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html # 自定义 Swagger UI 路径
|
||||
enabled: true
|
||||
info:
|
||||
title: 文撩 API 文档
|
||||
description: 这是文撩平台的 API 文档
|
||||
version: v1.0
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: false
|
||||
auto-mapping-behavior: full
|
||||
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
|
||||
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
||||
global-config:
|
||||
# 逻辑删除配置
|
||||
db-config:
|
||||
update-strategy: IGNORED
|
||||
# 删除前
|
||||
logic-not-delete-value: 1
|
||||
# 删除后
|
||||
logic-delete-value: 0
|
||||
|
||||
# 参考文章 https://zhuanlan.zhihu.com/p/145359625
|
||||
management:
|
||||
health:
|
||||
elasticsearch: #禁用健康检查
|
||||
enabled: false
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "health"
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
@@ -1,96 +0,0 @@
|
||||
spring:
|
||||
datasource:
|
||||
dynamic:
|
||||
primary: master
|
||||
strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
|
||||
hikari:
|
||||
minimum-idle: 4
|
||||
maximum-pool-size: 4
|
||||
connection-init-sql: SELECT 1
|
||||
connection-test-query: SELECT 1
|
||||
datasource:
|
||||
master: #${SERVER_ADDRESS}
|
||||
url: jdbc:mysql://${SERVER_ADDRESS}:3306/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: ${MYSQL_NAME}
|
||||
password: ${MYSQL_PWD}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
slave:
|
||||
url: jdbc:mysql://${SERVER_ADDRESS}:3306/xf-boot-base?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: ${MYSQL_NAME}
|
||||
password: ${MYSQL_PWD}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
redis:
|
||||
port: 6379 #Redis服务器连接的端口
|
||||
host: ${SERVER_ADDRESS} # Redis服务器的地址
|
||||
password: ${REDIS_PWD} # Redis服务器连接密码(默认为空)
|
||||
timeout: 5000 # 连接超时时间(毫秒)
|
||||
lettuce: #参考博客 https://blog.csdn.net/weixin_43944305/article/details/124322595
|
||||
pool:
|
||||
maxActive: 5000 #最大连接数
|
||||
maxIdle: 30 #连接池最大空闲连接数.
|
||||
minIdle: 5 #连接池最小空闲连接数.
|
||||
max-wait: 2000 #从连接池中获取连接时的最大等待时间
|
||||
time-between-eviction-runs: 60s #空闲对象逐出器线程的运行间隔时间.空闲连接线程释放周期时间.
|
||||
cluster:
|
||||
refresh:
|
||||
adaptive: true #拓扑动态感应即客户端能够根据 redis cluster 集群的变化,动态改变客户端的节点情况,完成故障转移。
|
||||
period: 60s #刷新redis集群状态周期时间
|
||||
|
||||
global:
|
||||
rsaPublicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC_F5UQC1QWsu3QsESQBz9M-GDA9Atm0qVSvwIsy568lyRLi-nq3VvvnmgrlL4yTbngFzyfb2Dn35cNCHsBvIaGuCY3_PpzPqMzVpxr2QlEkhEX9atnJQ1rWexS8QeZtPjpiIwoQrChTzXjD_sYUkDrqSykFplyivf0NSO2WqCBdwIDAQAB
|
||||
rsaPrivateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL8XlRALVBay7dCwRJAHP0z4YMD0C2bSpVK_AizLnryXJEuL6erdW--eaCuUvjJNueAXPJ9vYOfflw0IewG8hoa4Jjf8-nM-ozNWnGvZCUSSERf1q2clDWtZ7FLxB5m0-OmIjChCsKFPNeMP-xhSQOupLKQWmXKK9_Q1I7ZaoIF3AgMBAAECgYBxTUA61Ry0oL7U_86HP2TO9G4ZuhmQi9EucMaPXOPvmgYRLRIzCbDbMKc_P-BN3zwYnG57cgSZNz9OoPqeGvP_oVTnkoEpVkCSV-JP2p_DK09LdbDqszJXMrxAkPmWGUw8IRMcTJT1xJJcgzFE6T0CmTo-Vk47AnmqfJD4U6o74QJBAPRjVUJKZnrMSnSqKPDL2ThgTo8h7-KFxl_Z-g724lTOFiCmBpi6nCWAcuacFRrrYqxF-r9c4zdIyR7AvLROql8CQQDIK_kRF52dVtwShciZhyeUBLoi0nWV9F8mMGt60NTEER9zPEgPsv2aVn8h97KMWOwmd2Da4EPm25QxOuaKQC_pAkBczcfXp5co9KElkmR_pHl1jiTm97U3qSM-zPDHc_tYxvXiKgoBP4QCPbfkWMsu8MoEr4Jb3vMt0EcHlZtTQTgzAkAfmNla-lhV4sUgY1_T5EK6GbjsED6hag6u74u3ukkrnexR-10ApWdkumydBwV3I_464DM4uZfeVCDjWIHVpuYpAkEA6QLPztGD4V8Q1PqTEeSF3i68CKPM8vO1_mCH2JD7qsqDQcIKkczj5rTg7hlOKwB9V6gSw4CbnOF6moTooRD-cQ
|
||||
|
||||
elasticsearch:
|
||||
host: localhost
|
||||
port: 9200
|
||||
username: elastic
|
||||
password: bz5oF*MGy8pKL_I=7KxY #window系统本地启动 es8.x 重置密码命令:.\elasticsearch-reset-password -u elastic
|
||||
|
||||
rocketmq:
|
||||
name-server: ${SERVER_ADDRESS}:9876
|
||||
producer:
|
||||
group: producer-group
|
||||
consumer:
|
||||
group: consumer-group
|
||||
enable-orderly: false
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs # 自定义 API 文档路径
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html # 自定义 Swagger UI 路径
|
||||
enabled: true
|
||||
info:
|
||||
title: 文撩 API 文档
|
||||
description: 这是文撩平台的 API 文档
|
||||
version: v1.0
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: false
|
||||
auto-mapping-behavior: full
|
||||
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
|
||||
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
||||
global-config:
|
||||
# 逻辑删除配置
|
||||
db-config:
|
||||
update-strategy: IGNORED
|
||||
# 删除前
|
||||
logic-not-delete-value: 1
|
||||
# 删除后
|
||||
logic-delete-value: 0
|
||||
|
||||
# 参考文章 https://zhuanlan.zhihu.com/p/145359625
|
||||
management:
|
||||
health:
|
||||
elasticsearch: #禁用健康检查
|
||||
enabled: false
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "health"
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
version: '1'
|
||||
version: '3.8'
|
||||
services:
|
||||
boot_docker_compose:
|
||||
image: remaindertime/boot_docker_compose:v1.0.0
|
||||
container_name: boot_docker_compose
|
||||
xf-boot-base:
|
||||
build:
|
||||
context: ../../../
|
||||
dockerfile: Dockerfile
|
||||
container_name: xf-boot-base
|
||||
environment:
|
||||
- SERVER_ADDRESS=120.48.109.209
|
||||
- MYSQL_NAME=root
|
||||
- MYSQL_PWD=123456
|
||||
- REDIS_PWD=123456
|
||||
ports:
|
||||
- 8090:8088
|
||||
- 8089:8089
|
||||
@@ -1,36 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
elasticsearch:
|
||||
image: elasticsearch:6.4.0
|
||||
container_name: elasticsearch
|
||||
environment:
|
||||
- "cluster.name=elasticsearch" #设置集群名称为elasticsearch
|
||||
- "discovery.type=single-node" #以单一节点模式启动
|
||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小
|
||||
volumes:
|
||||
- /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins #插件文件挂载
|
||||
- /mydata/elasticsearch/data:/usr/share/elasticsearch/data #数据文件挂载
|
||||
ports:
|
||||
- 9200:9200
|
||||
kibana:
|
||||
image: kibana:6.4.0
|
||||
container_name: kibana
|
||||
links:
|
||||
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
|
||||
depends_on:
|
||||
- elasticsearch #kibana在elasticsearch启动之后再启动
|
||||
environment:
|
||||
- "elasticsearch.hosts=http://es:9200" #设置访问elasticsearch的地址
|
||||
ports:
|
||||
- 5601:5601
|
||||
logstash:
|
||||
image: logstash:6.4.0
|
||||
container_name: logstash
|
||||
volumes:
|
||||
- /mydata/logstash/logstash-springboot.conf:/usr/share/logstash/pipeline/logstash.conf #挂载logstash的配置文件
|
||||
depends_on:
|
||||
- elasticsearch #kibana在elasticsearch启动之后再启动
|
||||
links:
|
||||
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
|
||||
ports:
|
||||
- 4560:4560
|
||||
@@ -1,14 +0,0 @@
|
||||
input {
|
||||
tcp {
|
||||
mode => "server"
|
||||
host => "0.0.0.0"
|
||||
port => 4560
|
||||
codec => json_lines
|
||||
}
|
||||
}
|
||||
output {
|
||||
elasticsearch {
|
||||
hosts => "es:9200"
|
||||
index => "springboot-logstash-%{+YYYY.MM.dd}"
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="false">
|
||||
<contextName>logback</contextName>
|
||||
|
||||
<property name="log_home" value="logs"/>
|
||||
<property name="log_pattern" value="[%date{yyyy-MM-dd HH:mm:ss.SSS}] %-5level %logger{96} - %msg%n"/>
|
||||
|
||||
<!--输出到控制台-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>info</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<Pattern>${log_pattern}</Pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!--输出到本地-->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>./${log_home}/base-%d{yyyy-MM-dd}-info.%i.log</FileNamePattern>
|
||||
<!-- 日志文件最大尺寸 -->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>3</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>info</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<!--输出到本地-->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<FileNamePattern>./${log_home}/base-%d{yyyy-MM-dd}-error.%i.log</FileNamePattern>
|
||||
<!-- 日志文件最大尺寸 -->
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>3</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>error</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<springProperty scope="logstash" name="logIps" source="logstash.ips"/>
|
||||
<springProperty scope="logstash" name="appName" source="logstash.appName"/>
|
||||
<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
|
||||
<!-- logstash tcp 服务器地址,可以配置多个地址 -->
|
||||
<destination>${logIps}</destination>
|
||||
<!-- 保持连接 -->
|
||||
<keepAliveDuration>5 minutes</keepAliveDuration>
|
||||
<!-- 连接超时等待时间 -->
|
||||
<connectionTimeout>5 seconds</connectionTimeout>
|
||||
<!-- 重连延迟,默认 30s -->
|
||||
<reconnectionDelay>30 second</reconnectionDelay>
|
||||
<!-- 等待策略,需要测试和监控CPU找到最佳配置 -->
|
||||
<!-- 文档: https://github.com/logfellow/logstash-logback-encoder#wait-strategy -->
|
||||
<waitStrategyType>sleeping</waitStrategyType>
|
||||
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
||||
<customFields>{"host":"${HOSTNAME}", "app_name":"${appName}","plantVersion":"1"}</customFields>
|
||||
</encoder>
|
||||
|
||||
</appender>
|
||||
|
||||
<springProfile name="dev">
|
||||
<root level="info">
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
<!-- <appender-ref ref="logstash"/>-->
|
||||
</root>
|
||||
</springProfile>
|
||||
<springProfile name="pre">
|
||||
<root level="info">
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
<!-- <appender-ref ref="logstash"/>-->
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE configuration>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
<!--应用名称-->
|
||||
<property name="APP_NAME" value="xf-boot-base"/>
|
||||
<!--日志文件保存路径-->
|
||||
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
|
||||
<contextName>${APP_NAME}</contextName>
|
||||
<!--每天记录日志到文件appender-->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!--输出到logstash的appender-->
|
||||
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
|
||||
<!--可以访问的logstash日志收集端口-->
|
||||
<destination>120.48.109.209:4560</destination>
|
||||
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>
|
||||
</appender>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="LOGSTASH"/>
|
||||
</root>
|
||||
</configuration>
|
||||
85
src/main/resources/logback-spring.xml
Normal file
85
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds">
|
||||
<contextName>xf-boot-base</contextName>
|
||||
|
||||
<property name="log_home" value="logs"/>
|
||||
<property name="log_pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log_pattern}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Info本地日志 -->
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log_home}/base-info.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log_home}/base-%d{yyyy-MM-dd}-info.%i.log</fileNamePattern>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
<totalSizeCap>10GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log_pattern}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- Error本地日志 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log_home}/base-error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${log_home}/base-%d{yyyy-MM-dd}-error.%i.log</fileNamePattern>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<maxHistory>30</maxHistory>
|
||||
<totalSizeCap>5GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log_pattern}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>ERROR</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- Logstash输出(动态读取配置) -->
|
||||
<springProperty scope="context" name="logIps" source="logstash.ips" defaultValue="127.0.0.1:4560"/>
|
||||
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="xf-boot-base"/>
|
||||
|
||||
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
|
||||
<destination>${logIps}</destination>
|
||||
<keepAliveDuration>5 minutes</keepAliveDuration>
|
||||
<connectionTimeout>5 seconds</connectionTimeout>
|
||||
<reconnectionDelay>30 seconds</reconnectionDelay>
|
||||
<waitStrategyType>sleeping</waitStrategyType>
|
||||
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
||||
<customFields>{"host":"${HOSTNAME}", "app_name":"${appName}"}</customFields>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 开发与本地环境 -->
|
||||
<springProfile name="dev,local">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 测试与预发布/生产环境 -->
|
||||
<springProfile name="pre,prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
<appender-ref ref="LOGSTASH"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user