Initial commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?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>oneone-common</artifactId>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oneone.cloud</groupId>
|
||||
<artifactId>oneone-upms-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<!--配置文件处理器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--undertow容器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 -->
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId> <!-- 接口文档 -->
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
<version>9.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RefreshScope
|
||||
@Data
|
||||
public class Configs {
|
||||
@Value("${innerToken:xx}")
|
||||
private String innerToken;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import static com.oneone.common.constant.GlobalConstants.FEIGN_TOKEN;
|
||||
import static com.oneone.common.constant.GlobalConstants.USER_INFO;
|
||||
|
||||
|
||||
/**
|
||||
* Feign相关配置类
|
||||
*
|
||||
* @author oneone
|
||||
* @since 2021-08-06 9:47
|
||||
*/
|
||||
|
||||
//@ComponentScan("com.oneone.common.ruoyi.api")
|
||||
//@EnableFeignClients(clients = {RemoteAuthService.class
|
||||
//})
|
||||
//@EnableFeignClients("com.oneone.common.ruoyi.api")
|
||||
@Configuration
|
||||
public class FeignConfig {
|
||||
@Resource
|
||||
Configs configs;
|
||||
|
||||
/**
|
||||
* 让DispatcherServlet向子线程传递RequestContext
|
||||
*
|
||||
* @param servlet servlet
|
||||
* @return 注册bean
|
||||
*/
|
||||
@Bean
|
||||
public ServletRegistrationBean<DispatcherServlet> dispatcherRegistration(DispatcherServlet servlet) {
|
||||
servlet.setThreadContextInheritable(true);
|
||||
return new ServletRegistrationBean<>(servlet, "/**" );
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆写拦截器,在feign发送请求前取出原来的header并转发
|
||||
*
|
||||
* @return 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor() {
|
||||
return (template) -> {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (servletRequestAttributes == null) {
|
||||
return;
|
||||
}
|
||||
HttpServletRequest request = servletRequestAttributes.getRequest();
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
// 标注feign请求头
|
||||
template.header(FEIGN_TOKEN, configs.getInnerToken());
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
if (headerNames != null) {
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String name = headerNames.nextElement();
|
||||
String values = request.getHeader(name);
|
||||
if (checkEssential(name)) {
|
||||
template.header(name, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
List<String> headers = Arrays.asList(USER_INFO);
|
||||
|
||||
/**
|
||||
* 请求头必要传递参数过滤,只携带必要请求头信息
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private boolean checkEssential(String name) {
|
||||
|
||||
return headers.stream().anyMatch(e -> e.equalsIgnoreCase(name));
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class HttpClientConfig {
|
||||
|
||||
@Bean
|
||||
public HttpClient httpClient() {
|
||||
return HttpClientBuilder.create().build();
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Metrics 配置类
|
||||
*
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({MeterRegistryCustomizer.class})
|
||||
@ConditionalOnProperty(prefix = "oneone.metrics", value = "enable", matchIfMissing = true) // 允许使用 oneone.metrics.enable=false 禁用 Metrics
|
||||
public class MetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(
|
||||
@Value("${spring.application.name}") String applicationName) {
|
||||
return registry -> registry.config().commonTags("application", applicationName);
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import com.oneone.common.web.security.service.SecurityService;
|
||||
import com.oneone.common.web.security.service.SecurityServiceImpl;
|
||||
import com.oneone.upms.api.permission.PermissionApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class SecurityConfiguration {
|
||||
|
||||
|
||||
@Bean("securityService")
|
||||
public SecurityService securityService(PermissionApi permissionApi) {
|
||||
return new SecurityServiceImpl(permissionApi);
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2022-04-14 21:00
|
||||
*/
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Bean("asyncExecutor")
|
||||
public ThreadPoolTaskExecutor asyncExecutor(){
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
//创建核心线程数量
|
||||
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
|
||||
//最大核心线程数
|
||||
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
|
||||
//队列长度(超过会创建工作线程)
|
||||
executor.setQueueCapacity(10240);
|
||||
//线程名称前缀,用于监控识别
|
||||
executor.setThreadNamePrefix("async-task-");
|
||||
//设置线程保持活跃的时间(默认:60)
|
||||
executor.setKeepAliveSeconds(60);
|
||||
|
||||
//设置任务拒绝策略
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import com.oneone.common.web.trace.TraceFilter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
|
||||
/**
|
||||
* Tracer 配置类
|
||||
*
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "oneone.tracer", value = "enable", matchIfMissing = true)
|
||||
public class TracerAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 创建 TraceFilter 过滤器,响应 header 设置 traceId
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<TraceFilter> traceFilter() {
|
||||
FilterRegistrationBean<TraceFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new TraceFilter());
|
||||
registrationBean.setOrder(1);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.oneone.common.web.interceptor.AuthInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@EnableConfigurationProperties(WebProperties.class)
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Resource
|
||||
private AuthInterceptor authInterceptor;
|
||||
@Resource
|
||||
private WebProperties webProperties;
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
|
||||
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
||||
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
|
||||
// 后台Long值传递给前端精度丢失问题(JS最大精度整数是Math.pow(2,53))
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
// simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
|
||||
// simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
|
||||
objectMapper.registerModule(simpleModule);
|
||||
|
||||
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
|
||||
converters.add(1, jackson2HttpMessageConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(this.authInterceptor).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**" , "/webjars/**" , "/v2/api-docs" , "/doc.html/**");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurePathMatch(configurer, webProperties.getAdminApi());
|
||||
configurePathMatch(configurer, webProperties.getAppApi());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API 前缀,仅仅匹配 controller 包下的
|
||||
*
|
||||
* @param configurer 配置
|
||||
* @param api API 配置
|
||||
*/
|
||||
private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
|
||||
AntPathMatcher antPathMatcher = new AntPathMatcher(".");
|
||||
configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)
|
||||
&& antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(4);
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.oneone.common.web.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ConfigurationProperties(prefix = "oneone.web")
|
||||
@Validated
|
||||
@Data
|
||||
public class WebProperties {
|
||||
|
||||
@NotNull(message = "APP API 不能为空")
|
||||
private Api appApi = new Api("/app-api", "**.controller.app.**");
|
||||
@NotNull(message = "Admin API 不能为空")
|
||||
private Api adminApi = new Api("/admin-api", "**.controller.admin.**");
|
||||
|
||||
@NotNull(message = "Admin UI 不能为空")
|
||||
private Ui adminUi;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Valid
|
||||
public static class Api {
|
||||
|
||||
/**
|
||||
* API 前缀,实现所有 Controller 提供的 RESTFul API 的统一前缀
|
||||
*
|
||||
*
|
||||
* 意义:通过该前缀,避免 Swagger、Actuator 意外通过 Nginx 暴露出来给外部,带来安全性问题
|
||||
* 这样,Nginx 只需要配置转发到 /api/* 的所有接口即可。
|
||||
*
|
||||
*/
|
||||
@NotEmpty(message = "API 前缀不能为空")
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* Controller 所在包的 Ant 路径规则
|
||||
*
|
||||
* 主要目的是,给该 Controller 设置指定的 {@link #prefix}
|
||||
*/
|
||||
@NotEmpty(message = "Controller 所在包不能为空")
|
||||
private String controller;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Valid
|
||||
public static class Ui {
|
||||
|
||||
/**
|
||||
* 访问地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.oneone.common.web.exception;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import com.oneone.common.result.Result;
|
||||
import com.oneone.common.result.ResultCode;
|
||||
import feign.FeignException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
import java.sql.SQLSyntaxErrorException;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 全局系统异常处理
|
||||
* 调整异常处理的HTTP状态码,丰富异常处理类型
|
||||
*
|
||||
**/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
/**
|
||||
* 表单绑定到 java bean 出错时抛出 BindException 异常
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(BindException.class)
|
||||
public <T> Result<T> processException(BindException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
JSONObject msg = new JSONObject();
|
||||
e.getAllErrors().forEach(error -> {
|
||||
if (error instanceof FieldError) {
|
||||
FieldError fieldError = (FieldError) error;
|
||||
msg.set(fieldError.getField(),
|
||||
fieldError.getDefaultMessage());
|
||||
} else {
|
||||
msg.set(error.getObjectName(),
|
||||
error.getDefaultMessage());
|
||||
}
|
||||
});
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg.toString());
|
||||
}
|
||||
/**
|
||||
* 普通参数(非 java bean)校验出错时抛出 ConstraintViolationException 异常
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public <T> Result<T> processException(ConstraintViolationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
JSONObject msg = new JSONObject();
|
||||
e.getConstraintViolations().forEach(constraintViolation -> {
|
||||
String template = constraintViolation.getMessage();
|
||||
String path = constraintViolation.getPropertyPath().toString();
|
||||
msg.set(path, template);
|
||||
});
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg.toString());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
public <T> Result<T> processException(ValidationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_ERROR, "参数校验失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* NoHandlerFoundException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public <T> Result<T> processException(NoHandlerFoundException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* MissingServletRequestParameterException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public <T> Result<T> processException(MissingServletRequestParameterException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_IS_NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* MethodArgumentTypeMismatchException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_ERROR, "类型错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* ServletException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(ServletException.class)
|
||||
public <T> Result<T> processException(ServletException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(JsonProcessingException.class)
|
||||
public <T> Result<T> handleJsonProcessingException(JsonProcessingException e) {
|
||||
log.error("Json转换异常,异常原因:{}", e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpMessageNotReadableException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public <T> Result<T> processException(HttpMessageNotReadableException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorMessage = "请求体不可为空";
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
errorMessage = convertMessage(cause);
|
||||
}
|
||||
return Result.failed(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeMismatchException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
public <T> Result<T> processException(TypeMismatchException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(SQLSyntaxErrorException.class)
|
||||
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorMsg = e.getMessage();
|
||||
if (StrUtil.isNotBlank(errorMsg) && errorMsg.contains("denied to user")) {
|
||||
return Result.failed("数据库用户无操作权限,建议本地搭建数据库环境");
|
||||
} else {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(CompletionException.class)
|
||||
public <T> Result<T> processException(CompletionException e) {
|
||||
if (e.getMessage().startsWith("feign.FeignException")) {
|
||||
return Result.failed("微服务调用异常");
|
||||
}
|
||||
return handleException(e);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(FeignException.BadRequest.class)
|
||||
public <T> Result<T> processException(FeignException.BadRequest e) {
|
||||
log.error("微服务feign调用异常:", e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public <T> Result<T> handleBizException(BusinessException e) {
|
||||
log.error("业务异常,异常原因:{}", e.getMessage(), e);
|
||||
if (e.getResultCode() != null) {
|
||||
return Result.failed(e.getResultCode());
|
||||
}
|
||||
if (e.getCode() != null) {
|
||||
return Result.failed(e.getCode(), e.getMessage());
|
||||
}
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(Exception.class)
|
||||
public <T> Result<T> handleException(Exception e) {
|
||||
log.error("==>系统异常{}", e.getMessage(), e);
|
||||
return Result.failed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 传参类型错误时,用于消息转换
|
||||
*
|
||||
* @param throwable 异常
|
||||
* @return 错误信息
|
||||
*/
|
||||
private String convertMessage(Throwable throwable) {
|
||||
String error = throwable.toString();
|
||||
String regulation = "\\[\"(.*?)\"]+";
|
||||
Pattern pattern = Pattern.compile(regulation);
|
||||
Matcher matcher = pattern.matcher(error);
|
||||
String group = "";
|
||||
if (matcher.find()) {
|
||||
String matchString = matcher.group();
|
||||
matchString = matchString
|
||||
.replace("[", "")
|
||||
.replace("]", "");
|
||||
matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
|
||||
group += matchString;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.oneone.common.web.interceptor;
|
||||
|
||||
import com.oneone.common.base.LocalUser;
|
||||
import com.oneone.common.constant.SecurityConstants;
|
||||
import com.oneone.common.util.JsonUtils;
|
||||
import com.oneone.common.web.util.UserContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import static com.oneone.common.constant.GlobalConstants.USER_INFO;
|
||||
|
||||
/**
|
||||
* @author mice
|
||||
* @version 1.0
|
||||
* @date 2021/11/26 23:26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String user_info = request.getHeader(USER_INFO);
|
||||
if (StringUtils.isNotEmpty(user_info)){
|
||||
user_info = URLDecoder.decode(user_info, "UTF-8");
|
||||
LocalUser user = JsonUtils.strToClass(user_info,LocalUser.class);
|
||||
UserContext.setUser(user);
|
||||
}else{
|
||||
String sysUserId = request.getHeader(SecurityConstants.DETAILS_USER_ID);
|
||||
if(StringUtils.isNotEmpty(sysUserId)){
|
||||
LocalUser user=new LocalUser();
|
||||
user.setUserId(new Long(sysUserId));
|
||||
UserContext.setUser(user);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
|
||||
UserContext.clear();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.oneone.common.web.listener;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @description: 优雅停机 提前从nacos主动下线服务
|
||||
* @author: liubo
|
||||
* @create: 2023/04/23 10:00
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NacosShutdownListener implements ApplicationListener<ContextClosedEvent>, PriorityOrdered {
|
||||
|
||||
@Resource
|
||||
private AbstractAutoServiceRegistration autoServiceRegistration;
|
||||
|
||||
private AtomicInteger contextClosedEventCount = new AtomicInteger();
|
||||
|
||||
@Value("${nacos-discovery.per-shutdown-time:35}")
|
||||
private int waitTime;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
|
||||
if (contextClosedEventCount.incrementAndGet() > 1) {
|
||||
log.info("Frequent request!");
|
||||
return;
|
||||
}
|
||||
log.info("Nacos initiative DOWN Start!");
|
||||
try {
|
||||
autoServiceRegistration.stop();
|
||||
//responseEntity = serviceRegistryEndpoint.setStatus("DOWN");
|
||||
} catch (Exception e) {
|
||||
log.error("Nacos initiative DOWN ServiceRegistryEndpoint setStatus error:{}", e);
|
||||
}
|
||||
log.info("Nacos initiative DOWN End!");
|
||||
|
||||
log.info("Start sleep {}S for cache!", waitTime);
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("End sleep {}S for cache!", waitTime, e);
|
||||
}
|
||||
log.info("End sleep {}S for cache!", waitTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.oneone.common.web.security.annotation;
|
||||
|
||||
/**
|
||||
* 权限注解的验证模式
|
||||
*
|
||||
* @author ruoyi
|
||||
*
|
||||
*/
|
||||
public enum Logical
|
||||
{
|
||||
/**
|
||||
* 必须具有所有的元素
|
||||
*/
|
||||
AND,
|
||||
|
||||
/**
|
||||
* 只需具有其中一个元素
|
||||
*/
|
||||
OR
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.oneone.common.web.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 登录认证:只有登录之后才能进入该方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface RequiresLogin {
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.oneone.common.web.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限认证:必须具有指定权限才能进入该方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface RequiresPermissions {
|
||||
/**
|
||||
* 需要校验的权限码
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* 验证模式:AND | OR,默认AND
|
||||
*/
|
||||
Logical logical() default Logical.AND;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.oneone.common.web.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 角色认证:必须具有指定角色标识才能进入该方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface RequiresRoles
|
||||
{
|
||||
/**
|
||||
* 需要校验的角色标识
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* 验证逻辑:AND | OR,默认AND
|
||||
*/
|
||||
Logical logical() default Logical.AND;
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.oneone.common.web.security.aspect;
|
||||
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import com.oneone.common.web.config.Configs;
|
||||
import com.oneone.common.web.security.annotation.Logical;
|
||||
import com.oneone.common.web.security.annotation.RequiresLogin;
|
||||
import com.oneone.common.web.security.annotation.RequiresPermissions;
|
||||
import com.oneone.common.web.security.annotation.RequiresRoles;
|
||||
import com.oneone.common.web.security.service.SecurityService;
|
||||
import com.oneone.common.web.util.UserContext;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 基于 Spring Aop 的注解鉴权
|
||||
*
|
||||
* @author kong
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class PreAuthorizeAspect {
|
||||
|
||||
@Resource
|
||||
private SecurityService securityService;
|
||||
|
||||
@Resource
|
||||
Configs configs;
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*/
|
||||
public PreAuthorizeAspect() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义AOP签名 (切入所有使用鉴权注解的方法)
|
||||
*/
|
||||
public static final String POINTCUT_SIGN = " @annotation(com.oneone.common.web.security.annotation.RequiresLogin) || "
|
||||
+ "@annotation(com.oneone.common.web.security.annotation.RequiresPermissions) || "
|
||||
+ "@annotation(com.oneone.common.web.security.annotation.RequiresRoles)";
|
||||
|
||||
/**
|
||||
* 声明AOP签名
|
||||
*/
|
||||
@Pointcut(POINTCUT_SIGN)
|
||||
public void pointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕切入
|
||||
*
|
||||
* @param joinPoint 切面对象
|
||||
* @return 底层方法执行后的返回值
|
||||
* @throws Throwable 底层方法抛出的异常
|
||||
*/
|
||||
@Around("pointcut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// 注解鉴权
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
checkMethodAnnotation(signature.getMethod());
|
||||
try {
|
||||
// 执行原有逻辑
|
||||
Object obj = joinPoint.proceed();
|
||||
return obj;
|
||||
} catch (Throwable e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一个Method对象进行注解检查
|
||||
*/
|
||||
public void checkMethodAnnotation(Method method) {
|
||||
// 校验 @RequiresLogin 注解
|
||||
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
|
||||
if (requiresLogin != null) {
|
||||
if (UserContext.getUserIdIfPresentDefault().equals(UserContext.DEFAULT_VALUE)){
|
||||
throw new BusinessException("权限校验失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasAuth = false;
|
||||
// 校验 @RequiresRoles 注解
|
||||
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
|
||||
if (requiresRoles != null) {
|
||||
if (requiresRoles.logical() == Logical.AND) {
|
||||
hasAuth = securityService.hasRole(requiresRoles.value()[0]);
|
||||
}else {
|
||||
hasAuth = securityService.hasAnyRoles(requiresRoles.value());
|
||||
}
|
||||
if (!hasAuth){
|
||||
throw new BusinessException("权限校验失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 校验 @RequiresPermissions 注解
|
||||
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
|
||||
if (requiresPermissions != null) {
|
||||
if (requiresPermissions.logical() == Logical.AND) {
|
||||
hasAuth = securityService.hasPermission(requiresPermissions.value()[0]);
|
||||
}else {
|
||||
hasAuth = securityService.hasAnyPermissions(requiresPermissions.value());
|
||||
}
|
||||
if (!hasAuth){
|
||||
throw new BusinessException("权限校验失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.oneone.common.web.security.service;
|
||||
|
||||
/**
|
||||
* Security 框架 Service 接口,定义权限相关的校验操作
|
||||
*
|
||||
*/
|
||||
public interface SecurityService {
|
||||
|
||||
/**
|
||||
* 判断是否有权限
|
||||
*
|
||||
* @param permission 权限
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* 判断是否有权限,任一一个即可
|
||||
*
|
||||
* @param permissions 权限
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasAnyPermissions(String... permissions);
|
||||
|
||||
/**
|
||||
* 判断是否有角色
|
||||
*
|
||||
* 注意,角色使用的是 SysRoleDO 的 code 标识
|
||||
*
|
||||
* @param role 角色
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasRole(String role);
|
||||
|
||||
/**
|
||||
* 判断是否有角色,任一一个即可
|
||||
*
|
||||
* @param roles 角色数组
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasAnyRoles(String... roles);
|
||||
|
||||
/**
|
||||
* 判断是否有授权
|
||||
*
|
||||
* @param scope 授权
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasScope(String scope);
|
||||
|
||||
/**
|
||||
* 判断是否有授权范围,任一一个即可
|
||||
*
|
||||
* @param scope 授权范围数组
|
||||
* @return 是否
|
||||
*/
|
||||
boolean hasAnyScopes(String... scope);
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.oneone.common.web.security.service;
|
||||
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.oneone.common.base.KeyValue;
|
||||
import com.oneone.common.util.CacheUtils;
|
||||
import com.oneone.common.web.util.UserContext;
|
||||
import com.oneone.upms.api.permission.PermissionApi;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认的 {@link SecurityService} 实现类
|
||||
*
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class SecurityServiceImpl implements SecurityService {
|
||||
|
||||
private final PermissionApi permissionApi;
|
||||
|
||||
/**
|
||||
* 针对 {@link #hasAnyRoles(String...)} 的缓存
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyRolesCache = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean load(KeyValue<Long, List<String>> key) {
|
||||
return permissionApi.hasAnyRoles(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 针对 {@link #hasAnyPermissions(String...)} 的缓存
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyPermissionsCache = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean load(KeyValue<Long, List<String>> key) {
|
||||
return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return hasAnyPermissions(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyPermissions(String... permissions) {
|
||||
return hasAnyPermissionsCache.get(new KeyValue<>(UserContext.getUserId(), Arrays.asList(permissions)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(String role) {
|
||||
return hasAnyRoles(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyRoles(String... roles) {
|
||||
return hasAnyRolesCache.get(new KeyValue<>(UserContext.getUserId(), Arrays.asList(roles)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(String scope) {
|
||||
return hasAnyScopes(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnyScopes(String... scope) {
|
||||
// TODO 暂未设计
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package com.oneone.common.web.swagger.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.media.StringSchema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springdoc.core.*;
|
||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||
import org.springdoc.core.providers.JavadocProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Swagger 自动配置类,基于 OpenAPI + Springdoc 实现。
|
||||
*
|
||||
* 友情提示:
|
||||
* 1. Springdoc 文档地址:<a href="https://github.com/springdoc/springdoc-openapi">仓库</a>
|
||||
* 2. Swagger 规范,于 2015 更名为 OpenAPI 规范,本质是一个东西
|
||||
*
|
||||
*
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({OpenAPI.class})
|
||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||
@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
|
||||
public class SwaggerAutoConfiguration {
|
||||
|
||||
// ========== 全局 OpenAPI 配置 ==========
|
||||
|
||||
@Bean
|
||||
public OpenAPI createApi(SwaggerProperties properties) {
|
||||
OpenAPI openAPI = new OpenAPI()
|
||||
.info(buildInfo(properties));
|
||||
return openAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 摘要信息
|
||||
*/
|
||||
private Info buildInfo(SwaggerProperties properties) {
|
||||
return new Info()
|
||||
.title(properties.getTitle())
|
||||
.description(properties.getDescription())
|
||||
.version(properties.getVersion())
|
||||
.contact(new Contact().name(properties.getAuthor()).url(properties.getUrl()).email(properties.getEmail()))
|
||||
.license(new License().name(properties.getLicense()).url(properties.getLicenseUrl()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 所有模块的 API 分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi allGroupedOpenApi() {
|
||||
return buildGroupedOpenApi("all", "");
|
||||
}
|
||||
|
||||
public static GroupedOpenApi buildGroupedOpenApi(String group, String path) {
|
||||
return GroupedOpenApi.builder()
|
||||
.group(group)
|
||||
.pathsToMatch("/admin-api/" + path + "/**", "/app-api/" + path + "/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.oneone.common.web.swagger.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
/**
|
||||
* Swagger 配置属性
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("oneone.swagger")
|
||||
@Data
|
||||
public class SwaggerProperties {
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@NotEmpty(message = "标题不能为空")
|
||||
private String title;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@NotEmpty(message = "描述不能为空")
|
||||
private String description;
|
||||
/**
|
||||
* 作者
|
||||
*/
|
||||
@NotEmpty(message = "作者不能为空")
|
||||
private String author;
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
@NotEmpty(message = "版本不能为空")
|
||||
private String version;
|
||||
/**
|
||||
* url
|
||||
*/
|
||||
@NotEmpty(message = "扫描的 package 不能为空")
|
||||
private String url;
|
||||
/**
|
||||
* email
|
||||
*/
|
||||
@NotEmpty(message = "扫描的 email 不能为空")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* license
|
||||
*/
|
||||
@NotEmpty(message = "扫描的 license 不能为空")
|
||||
private String license;
|
||||
|
||||
/**
|
||||
* license-url
|
||||
*/
|
||||
@NotEmpty(message = "扫描的 license-url 不能为空")
|
||||
private String licenseUrl;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.oneone.common.web.trace;
|
||||
|
||||
import com.oneone.common.util.TracerUtils;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Trace 过滤器,打印 traceId 到 header 中返回
|
||||
*
|
||||
*/
|
||||
public class TraceFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* Header 名 - 链路追踪编号
|
||||
*/
|
||||
private static final String HEADER_NAME_TRACE_ID = "trace-id";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
// 设置响应 traceId
|
||||
response.addHeader(HEADER_NAME_TRACE_ID, TracerUtils.getTraceId());
|
||||
// 继续过滤
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import cn.hutool.core.net.URLEncodeUtil;
|
||||
import com.oneone.common.util.JsonUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.entity.mime.content.FileBody;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* http请求工具类
|
||||
*/
|
||||
@Component
|
||||
public class HttpClientUtil {
|
||||
|
||||
private static final String EMPTY_STR = "";
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
@Resource
|
||||
private HttpClient httpClient;
|
||||
|
||||
|
||||
/**
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
public String doGet(String url) {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
return getResult(httpGet);
|
||||
}
|
||||
|
||||
public String doGet(String url, Map<String, Object> params) throws URISyntaxException {
|
||||
URIBuilder ub = new URIBuilder();
|
||||
ub.setPath(url);
|
||||
|
||||
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
|
||||
ub.setParameters(pairs);
|
||||
|
||||
HttpGet httpGet = new HttpGet(ub.build());
|
||||
return getResult(httpGet);
|
||||
}
|
||||
|
||||
public String doGet(String url, Map<String, Object> headers, Map<String, Object> params)
|
||||
throws URISyntaxException {
|
||||
URIBuilder ub = new URIBuilder();
|
||||
ub.setPath(url);
|
||||
|
||||
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
|
||||
ub.setParameters(pairs);
|
||||
|
||||
HttpGet httpGet = new HttpGet(ub.build());
|
||||
for (Map.Entry<String, Object> param : headers.entrySet()) {
|
||||
httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));
|
||||
}
|
||||
return getResult(httpGet);
|
||||
}
|
||||
|
||||
public String doPost(String url) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
public String doPost(String url, Map<String, Object> params) throws UnsupportedEncodingException {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
public String doPost(String url, Object param) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
//设置数据为utf-8编码
|
||||
StringEntity entity = new StringEntity(JsonUtils.toJSONString(param), "utf-8");
|
||||
//设置请求编码
|
||||
entity.setContentEncoding("utf-8");
|
||||
//设置请求类型
|
||||
entity.setContentType("application/json");
|
||||
httpPost.setEntity(entity);
|
||||
//请求并得到结果
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
public String doPost(String url, Map<String, Object> headers, Object obj) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
for (Map.Entry<String, Object> param : headers.entrySet()) {
|
||||
httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));
|
||||
}
|
||||
//设置数据为utf-8编码
|
||||
StringEntity entity = new StringEntity(JsonUtils.toJSONString(obj), "utf-8");
|
||||
//设置请求编码
|
||||
entity.setContentEncoding("utf-8");
|
||||
//设置请求类型
|
||||
entity.setContentType("application/json");
|
||||
httpPost.setEntity(entity);
|
||||
//请求并得到结果
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
public String doPost(String url, MultipartFile file) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
// 创建 MultipartEntityBuilder 并添加文件部分
|
||||
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
||||
try {
|
||||
builder.addBinaryBody("upload", file.getInputStream(), ContentType.APPLICATION_JSON, URLEncodeUtil.encode(file.getOriginalFilename()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// 构建 HttpEntity
|
||||
HttpEntity entity = builder.build();
|
||||
httpPost.setEntity(entity);
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
|
||||
public HttpResponse doPostResp(String url, Map<String, Object> headers, Object obj) {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
if (!CollectionUtils.isEmpty(headers)) {
|
||||
for (Map.Entry<String, Object> param : headers.entrySet()) {
|
||||
httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
//设置数据为utf-8编码
|
||||
StringEntity entity = new StringEntity(JsonUtils.toJSONString(obj), "utf-8");
|
||||
//设置请求编码
|
||||
entity.setContentEncoding("utf-8");
|
||||
//设置请求类型
|
||||
entity.setContentType("application/json");
|
||||
httpPost.setEntity(entity);
|
||||
//请求并得到结果
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
response = httpClient.execute(httpPost);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public String doPost(String url, Map<String, Object> headers, Map<String, Object> params)
|
||||
throws UnsupportedEncodingException {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
|
||||
if (!CollectionUtils.isEmpty(headers)) {
|
||||
for (Map.Entry<String, Object> param : headers.entrySet()) {
|
||||
httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
|
||||
|
||||
return getResult(httpPost);
|
||||
}
|
||||
|
||||
private ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {
|
||||
ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();
|
||||
for (Map.Entry<String, Object> param : params.entrySet()) {
|
||||
pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Http请求
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private String getResult(HttpRequestBase request) {
|
||||
try {
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity != null) {
|
||||
String result = EntityUtils.toString(entity, UTF_8);
|
||||
return result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return EMPTY_STR;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import com.oneone.common.base.Area;
|
||||
import com.oneone.common.util.AreaUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class IPUtils {
|
||||
private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
|
||||
private static final String IP_UTILS_FLAG = ",";
|
||||
private static final String UNKNOWN = "unknown";
|
||||
private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
|
||||
private static final String LOCALHOST_IP1 = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* 获取IP地址
|
||||
* <p>
|
||||
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
|
||||
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
|
||||
*/
|
||||
public static String getIpAddrByServlet() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String ip = null;
|
||||
try {
|
||||
//以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
|
||||
ip = request.getHeader("X-Original-Forwarded-For");
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
//获取nginx等代理的ip
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("x-forwarded-for");
|
||||
}
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
//兼容k8s集群获取ip
|
||||
if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
|
||||
//根据网卡取本机配置的IP
|
||||
InetAddress iNet = null;
|
||||
try {
|
||||
iNet = InetAddress.getLocalHost();
|
||||
} catch (UnknownHostException e) {
|
||||
logger.error("getClientIp error: {}", e);
|
||||
}
|
||||
ip = iNet.getHostAddress();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("IPUtils ERROR ", e);
|
||||
}
|
||||
//使用代理,则获取第一个IP地址
|
||||
if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
|
||||
ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
public static String getIpAddress(ServerHttpRequest request) {
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
String ip = headers.getFirst("x-forwarded-for");
|
||||
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
|
||||
if (ip.indexOf(",") != -1) {
|
||||
ip = ip.split(",")[0];
|
||||
}
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = headers.getFirst("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = headers.getFirst("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = headers.getFirst("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = headers.getFirst("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddress().getAddress().getHostAddress();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
public static String getLocalIpv4() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
InetAddress ip = null;
|
||||
while (allNetInterfaces.hasMoreElements()) {
|
||||
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
|
||||
//排除虚拟接口和没有启动运行的接口
|
||||
if (netInterface.isVirtual() || !netInterface.isUp()) {
|
||||
continue;
|
||||
} else {
|
||||
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
ip = addresses.nextElement();
|
||||
if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() && !ip.getHostAddress().contains(":")) {
|
||||
return ip.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Set<String> getLocalIpAddress() {
|
||||
Set<String> ipList = new HashSet<>();
|
||||
try {
|
||||
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
InetAddress ip = null;
|
||||
while (allNetInterfaces.hasMoreElements()) {
|
||||
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
|
||||
//排除虚拟接口和没有启动运行的接口
|
||||
if (netInterface.isVirtual() || !netInterface.isUp()) {
|
||||
continue;
|
||||
} else {
|
||||
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
ip = addresses.nextElement();
|
||||
if (ip != null && (ip instanceof Inet4Address || ip instanceof Inet6Address)) {
|
||||
ipList.add(ip.getHostAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ipList;
|
||||
}
|
||||
|
||||
public static String getUserAgent() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
String ua = request.getHeader("User-Agent");
|
||||
return ua != null ? ua : "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.oneone.common.constant.SecurityConstants;
|
||||
import com.oneone.common.result.ResultCode;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static JSONObject getJwtPayload() {
|
||||
JSONObject jsonObject = null;
|
||||
String payload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(SecurityConstants.JWT_PAYLOAD_KEY);
|
||||
if (StrUtil.isNotBlank(payload)) {
|
||||
jsonObject = JSONUtil.parseObj(URLDecoder.decode(payload, StandardCharsets.UTF_8.name()));
|
||||
}else {
|
||||
throw new BusinessException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* spring 工具类
|
||||
*/
|
||||
@Component
|
||||
public class SpringUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
private static String host;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
if (this.applicationContext == null) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取applicationContext
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name获取 Bean.
|
||||
*/
|
||||
public static Object getBean(String name) {
|
||||
return getApplicationContext().getBean(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过class获取Bean.
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return getApplicationContext().getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name,以及Clazz返回指定的Bean
|
||||
*/
|
||||
public static <T> T getBean(String name, Class<T> clazz) {
|
||||
return getApplicationContext().getBean(name, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解的所有bean
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Object> getBeansWithAnnotation(Class clazz) {
|
||||
return getApplicationContext().getBeansWithAnnotation(clazz);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getBeansOfType(Class clazz) {
|
||||
return getApplicationContext().getBeansOfType(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
public static void publishEvent(Object event) {
|
||||
getApplicationContext().publishEvent(event);
|
||||
}
|
||||
|
||||
|
||||
public static String getHost() {
|
||||
if (StringUtils.hasLength(host)) {
|
||||
return host;
|
||||
}
|
||||
InetUtils inetUtils = SpringUtil.getBean(InetUtils.class);
|
||||
inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
|
||||
String port = applicationContext.getEnvironment().getProperty("server.port");
|
||||
host = "http://" + inetUtils.findFirstNonLoopbackHostInfo().getIpAddress() + ":" + port;
|
||||
return host;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import com.oneone.common.base.LocalUser;
|
||||
import com.oneone.common.exception.BusinessException;
|
||||
import com.oneone.common.result.ResultCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author oneone
|
||||
* @date 2022/2/12 20:14
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
private static final ThreadLocal<LocalUser> THREAD_LOCAL = new TransmittableThreadLocal<>();
|
||||
|
||||
public static final Long DEFAULT_VALUE = -1L;
|
||||
|
||||
/**
|
||||
* 获取当前登录管理员的ID
|
||||
*
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
LocalUser localUser = THREAD_LOCAL.get();
|
||||
if (localUser == null){
|
||||
throw new BusinessException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
return localUser.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录管理员的ID 未登录返回默认值
|
||||
*
|
||||
*/
|
||||
public static Long getUserIdIfPresentDefault() {
|
||||
LocalUser localUser = THREAD_LOCAL.get();
|
||||
if (localUser == null){
|
||||
return DEFAULT_VALUE;
|
||||
}
|
||||
return localUser.getUserId();
|
||||
}
|
||||
|
||||
public static Long getUserIdIfPresentNull() {
|
||||
LocalUser localUser = THREAD_LOCAL.get();
|
||||
if (localUser == null){
|
||||
return null;
|
||||
}
|
||||
return localUser.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录管理员的ID
|
||||
*
|
||||
*/
|
||||
public static Long getMemberId() {
|
||||
LocalUser localUser = THREAD_LOCAL.get();
|
||||
if (localUser == null){
|
||||
throw new BusinessException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
return localUser.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录管理员的ID 未登录返回默认值
|
||||
*
|
||||
*/
|
||||
public static Long getMemberIdIfPresentDefault() {
|
||||
LocalUser localUser = THREAD_LOCAL.get();
|
||||
if (localUser == null){
|
||||
return DEFAULT_VALUE;
|
||||
}
|
||||
return localUser.getUserId();
|
||||
}
|
||||
|
||||
public static void setUser(LocalUser user){
|
||||
THREAD_LOCAL.set(user);
|
||||
}
|
||||
|
||||
public static LocalUser getUser(){
|
||||
return THREAD_LOCAL.get();
|
||||
}
|
||||
|
||||
public static void clear(){
|
||||
THREAD_LOCAL.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.oneone.common.constant.SecurityConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*
|
||||
* @author oneone
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
@Slf4j
|
||||
public class UserUtils {
|
||||
|
||||
/**
|
||||
* 解析JWT获取用户ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
Long userId = null;
|
||||
JSONObject jwtPayload = JwtUtils.getJwtPayload();
|
||||
if (jwtPayload != null) {
|
||||
userId = jwtPayload.getLong("userId");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static Long getMemberId() {
|
||||
Long memberId = null;
|
||||
JSONObject jwtPayload = JwtUtils.getJwtPayload();
|
||||
if (jwtPayload != null) {
|
||||
memberId = jwtPayload.getLong("memberId");
|
||||
}
|
||||
return memberId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JWT获取用户ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Long getDeptId() {
|
||||
Long id = JwtUtils.getJwtPayload().getLong("deptId");
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JWT获取获取用户名
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getUsername() {
|
||||
String username = JwtUtils.getJwtPayload().getStr(SecurityConstants.USER_NAME_KEY);
|
||||
return username;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JWT获取用户角色列表
|
||||
*
|
||||
* @return 角色列表
|
||||
*/
|
||||
public static List<String> getRoles() {
|
||||
List<String> roles;
|
||||
JSONObject payload = JwtUtils.getJwtPayload();
|
||||
if (payload.containsKey(SecurityConstants.JWT_AUTHORITIES_KEY)) {
|
||||
roles = payload.getJSONArray(SecurityConstants.JWT_AUTHORITIES_KEY).toList(String.class);
|
||||
} else {
|
||||
roles = Collections.emptyList();
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否「超级管理员」
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean isRoot() {
|
||||
List<String> roles = getRoles();
|
||||
return CollectionUtil.isNotEmpty(roles) && roles.contains("ROOT");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.oneone.common.web.util;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.oneone.common.util.Func;
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class WebUtil extends org.springframework.web.util.WebUtils{
|
||||
|
||||
public static final String USER_AGENT_HEADER = "user-agent";
|
||||
|
||||
public static final String UN_KNOWN = "unknown";
|
||||
|
||||
public static HttpServletRequest getRequest() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
return (requestAttributes == null) ? null : ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取 request 中 json 字符串的内容
|
||||
*
|
||||
* @param request request
|
||||
* @return 字符串内容
|
||||
*/
|
||||
public static String getRequestParamString(HttpServletRequest request) {
|
||||
try {
|
||||
return getRequestStr(request);
|
||||
} catch (Exception ex) {
|
||||
return StringPool.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 请求内容
|
||||
*
|
||||
* @param request request
|
||||
* @return String
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public static String getRequestStr(HttpServletRequest request) throws IOException {
|
||||
String queryString = request.getQueryString();
|
||||
if (Func.isEmpty(queryString)) {
|
||||
return new String(queryString.getBytes(Charsets.ISO_8859_1), Charsets.UTF_8).replaceAll("&", "&").replaceAll("%22", "\"");
|
||||
}
|
||||
return getRequestStr(request, getRequestBytes(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 请求的 byte[] 数组
|
||||
*
|
||||
* @param request request
|
||||
* @return byte[]
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public static byte[] getRequestBytes(HttpServletRequest request) throws IOException {
|
||||
int contentLength = request.getContentLength();
|
||||
if (contentLength < 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] buffer = new byte[contentLength];
|
||||
for (int i = 0; i < contentLength; ) {
|
||||
|
||||
int readlen = request.getInputStream().read(buffer, i, contentLength - i);
|
||||
if (readlen == -1) {
|
||||
break;
|
||||
}
|
||||
i += readlen;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 请求内容
|
||||
*
|
||||
* @param request request
|
||||
* @param buffer buffer
|
||||
* @return String
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public static String getRequestStr(HttpServletRequest request, byte[] buffer) throws IOException {
|
||||
String charEncoding = request.getCharacterEncoding();
|
||||
if (charEncoding == null) {
|
||||
charEncoding = StringPool.UTF_8;
|
||||
}
|
||||
String str = new String(buffer, charEncoding).trim();
|
||||
if (Func.isEmpty(str)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Enumeration<String> parameterNames = request.getParameterNames();
|
||||
while (parameterNames.hasMoreElements()) {
|
||||
String key = parameterNames.nextElement();
|
||||
String value = request.getParameter(key);
|
||||
Func.appendBuilder(sb, key, "=", value, "&");
|
||||
}
|
||||
str = Func.removeSuffix(sb.toString(), "&");
|
||||
}
|
||||
return str.replaceAll("&", "&");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ip
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @return {String}
|
||||
*/
|
||||
@Nullable
|
||||
public static String getIP(HttpServletRequest request) {
|
||||
Assert.notNull(request, "HttpServletRequest is null");
|
||||
String ip = request.getHeader("X-Requested-For");
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (Func.isEmpty(ip) || UN_KNOWN.equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return Func.isEmpty(ip) ? null : ip.split(",")[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url路径
|
||||
*
|
||||
* @param uriStr 路径
|
||||
* @return url路径
|
||||
*/
|
||||
public static String getPath(String uriStr) {
|
||||
URI uri;
|
||||
|
||||
try {
|
||||
uri = new URI(uriStr);
|
||||
} catch (URISyntaxException var3) {
|
||||
throw new RuntimeException(var3);
|
||||
}
|
||||
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.oneone.common.web.config.WebMvcConfig,\
|
||||
com.oneone.common.web.config.HttpClientConfig,\
|
||||
com.oneone.common.web.config.Configs,\
|
||||
com.oneone.common.web.security.aspect.PreAuthorizeAspect,\
|
||||
com.oneone.common.web.config.FeignConfig,\
|
||||
com.oneone.common.web.exception.GlobalExceptionHandler,\
|
||||
com.oneone.common.web.interceptor.AuthInterceptor,\
|
||||
com.oneone.common.web.util.SpringUtil,\
|
||||
com.oneone.common.web.util.HttpClientUtil,\
|
||||
com.oneone.common.web.config.ThreadPoolConfig
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
com.oneone.common.web.swagger.config.SwaggerAutoConfiguration
|
||||
com.oneone.common.web.config.SecurityConfiguration
|
||||
com.oneone.common.web.config.MetricsAutoConfiguration
|
||||
com.oneone.common.web.config.TracerAutoConfiguration
|
||||
Reference in New Issue
Block a user