初始化提交

This commit is contained in:
2021-01-20 18:30:23 +08:00
commit 3eb965f380
208 changed files with 8103 additions and 0 deletions

102
common/web/pom.xml Normal file
View File

@ -0,0 +1,102 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>business.chaoran</groupId>
<artifactId>web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>web</name>
<description>Demo Web project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--spring web相关包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr1</version>
</dependency>
<!--mybatis plus依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!--Redis缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!--开发相关-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--测试框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>10.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,43 @@
package com.springboot.cloud.common.web.entity.form;
import com.springboot.cloud.common.web.entity.po.BasePo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@ApiModel
@Slf4j
@Data
public class BaseForm<T extends BasePo> {
/**
* 用户名
*/
private String username;
/**
* Form转化为Po进行后续业务处理
*
* @param clazz
* @return
*/
public T toPo(Class<T> clazz) {
T t = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(this, t);
return t;
}
/**
* Form转化为Po进行后续业务处理
*
* @param id
* @param clazz
* @return
*/
public T toPo(String id, Class<T> clazz) {
T t = BeanUtils.instantiateClass(clazz);
t.setId(id);
BeanUtils.copyProperties(this, t);
return t;
}
}

View File

@ -0,0 +1,44 @@
package com.springboot.cloud.common.web.entity.form;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.springboot.cloud.common.web.entity.param.BaseParam;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@ApiModel
@Slf4j
@Data
public class BaseQueryForm<P extends BaseParam> extends BaseForm {
/**
* 分页查询的参数,当前页数
*/
private long current = 1;
/**
* 分页查询的参数,当前页面每页显示的数量
*/
private long size = 10;
/**
* Form转化为Param
*
* @param clazz
* @return
*/
public P toParam(Class<P> clazz) {
P p = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(this, p);
return p;
}
/**
* 从form中获取page参数用于分页查询参数
*
* @return
*/
public Page getPage() {
return new Page(this.getCurrent(), this.getSize());
}
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.common.web.entity.param;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.Data;
import java.util.Date;
@Data
public class BaseParam<T extends BasePo> {
private Date createdTimeStart;
private Date createdTimeEnd;
public QueryWrapper<T> build() {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
queryWrapper.ge(null != this.createdTimeStart, "created_time", this.createdTimeStart)
.le(null != this.createdTimeEnd, "created_time", this.createdTimeEnd);
return queryWrapper;
}
}

View File

@ -0,0 +1,29 @@
package com.springboot.cloud.common.web.entity.po;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class BasePo implements Serializable {
public final static String DEFAULT_USERNAME = "system";
@TableId(type = IdType.ID_WORKER_STR)
private String id;
@TableField(fill = FieldFill.INSERT)
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private Date createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updatedTime;
}

View File

@ -0,0 +1,13 @@
package com.springboot.cloud.common.web.entity.vo;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class BaseVo<T extends BasePo> implements Serializable {
private String id;
}

View File

@ -0,0 +1,59 @@
package com.springboot.cloud.common.web.exception;
import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.cloud.common.core.exception.BaseException;
import com.springboot.cloud.common.core.exception.SystemErrorType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartException;
@Slf4j
public class DefaultGlobalExceptionHandlerAdvice {
@ExceptionHandler(value = {MissingServletRequestParameterException.class})
public Result missingServletRequestParameterException(MissingServletRequestParameterException ex) {
log.error("missing servlet request parameter exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.ARGUMENT_NOT_VALID);
}
@ExceptionHandler(value = {MultipartException.class})
public Result uploadFileLimitException(MultipartException ex) {
log.error("upload file size limit:{}", ex.getMessage());
return Result.fail(SystemErrorType.UPLOAD_FILE_SIZE_LIMIT);
}
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public Result serviceException(MethodArgumentNotValidException ex) {
log.error("service exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.ARGUMENT_NOT_VALID, ex.getBindingResult().getFieldError().getDefaultMessage());
}
@ExceptionHandler(value = {DuplicateKeyException.class})
public Result duplicateKeyException(DuplicateKeyException ex) {
log.error("primary key duplication exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.DUPLICATE_PRIMARY_KEY);
}
@ExceptionHandler(value = {BaseException.class})
public Result baseException(BaseException ex) {
log.error("base exception:{}", ex.getMessage());
return Result.fail(ex.getErrorType());
}
@ExceptionHandler(value = {Exception.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result exception() {
return Result.fail();
}
@ExceptionHandler(value = {Throwable.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result throwable() {
return Result.fail();
}
}

View File

@ -0,0 +1,36 @@
package com.springboot.cloud.common.web.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.springboot.cloud.common.core.util.UserContextHolder;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import java.time.ZonedDateTime;
import java.util.Date;
@Slf4j
public class PoMetaObjectHandler implements MetaObjectHandler {
/**
* 获取当前访问用户为空返回默认system
*
* @return
*/
private String getCurrentUsername() {
return StringUtils.defaultIfBlank(UserContextHolder.getInstance().getUsername(), BasePo.DEFAULT_USERNAME);
}
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName("createdBy", getCurrentUsername(), metaObject);
this.setInsertFieldValByName("createdTime", Date.from(ZonedDateTime.now().toInstant()), metaObject);
this.updateFill(metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName("updatedBy", getCurrentUsername(), metaObject);
this.setUpdateFieldValByName("updatedTime", Date.from(ZonedDateTime.now().toInstant()), metaObject);
}
}

View File

@ -0,0 +1,46 @@
package com.springboot.cloud.common.web.interceptor;
/*
**********************************************
* DATE PERSON REASON
* 2020-12-04 FXY Created
**********************************************
*/
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import java.util.Collection;
import java.util.Map;
@Slf4j
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
public FeignBasicAuthRequestInterceptor(){
log.info("========================加载自定义拦截器=========================");
}
//服务间调用头
private static final String SERVER_CLIENT_TOKEN="server-client-token";
//服务间调用秘钥
private static final String SERVER_CLIENT_SECRET="server";
//网关调用服务头
private static final String X_CLIENT_TOKEN="x-client-token";
@Override
public void apply(RequestTemplate requestTemplate) {
String url = requestTemplate.path();
Map<String, Collection<String>> headers = requestTemplate.headers();
if(!headers.containsKey(X_CLIENT_TOKEN)){
String method = requestTemplate.method();
String originStr = new StringBuilder().append(url).append(method).append(SERVER_CLIENT_TOKEN).append(SERVER_CLIENT_SECRET).toString();
// 加入自定义头信息
String md5Secret = DigestUtils.md5DigestAsHex(originStr.getBytes());
requestTemplate.header(SERVER_CLIENT_TOKEN, md5Secret);
}
}
}

View File

@ -0,0 +1,87 @@
package com.springboot.cloud.common.web.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.core.util.UserContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Optional;
/**
* 用户信息拦截器
*/
@Slf4j
public class UserInterceptor implements HandlerInterceptor {
/**
* 服务间调用token用户信息,格式为json
* {
* "user_name":"必须有"
* "自定义key:"value"
* }
*/
//获取相关授权信息请求头
private static final String X_CLIENT_TOKEN_USER = "x-client-token-user";
//网关调用服务请求头
private static final String X_CLIENT_TOKEN = "x-client-token";
//网关调用服务秘钥
private static final String X_CLIENT_SECRET = "gateway-web";
//服务间调用请求头
private static final String SERVER_CLIENT_TOKEN = "server-client-token";
//服务间调用秘钥
private static final String SERVER_CLIENT_SECRET = "server";
//当前用户登录公司
private static final String USER_CURRENT_COMPANY = "company";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//从网关获取并校验,通过校验就可信任x-client-token-user中的信息
boolean b = checkToken(request);
if (!b) {
//重定向到登录前端
response.sendRedirect("https://www.baidu.com");
return b;
}
//放入上下文数据
String userInfoString = StringUtils.defaultIfBlank(request.getHeader(X_CLIENT_TOKEN_USER), "{}");
Map context = new ObjectMapper().readValue(userInfoString, Map.class);
context.put("company", request.getHeader(USER_CURRENT_COMPANY));
UserContextHolder.getInstance().setContext(context);
return true;
}
private boolean checkToken(HttpServletRequest request) {
String method = request.getMethod();
String url = request.getServletPath();
Optional<String> serverToken = Optional.ofNullable(request.getHeader(SERVER_CLIENT_TOKEN));
Optional<String> clientToken = Optional.ofNullable(request.getHeader(X_CLIENT_TOKEN));
if (serverToken.isPresent()) {
//如果是服务间相互调用不需要加contextPath因为feign拦截器中加密是根据servletPath来加密的此处也是根据servletPath解密的
String originStr = new StringBuilder().append(url).append(method).append(SERVER_CLIENT_TOKEN).append(SERVER_CLIENT_SECRET).toString();
return DigestUtils.md5DigestAsHex(originStr.getBytes()).equals(serverToken.get());
} else if (clientToken.isPresent()) {
//如果是网关调用服务网关服务需要加上contextPath
String originStr = new StringBuilder().append(request.getContextPath().concat(url)).append(method).append(X_CLIENT_TOKEN).append(X_CLIENT_SECRET).toString();
return DigestUtils.md5DigestAsHex(originStr.getBytes()).equals(clientToken.get());
} else {
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
UserContextHolder.getInstance().clear();
}
}

View File

@ -0,0 +1,48 @@
package com.springboot.cloud.common.web.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
//对象的序列化
RedisSerializationContext.SerializationPair valueSerializationPair
= RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer());
//全局redis缓存过期时间
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
// .serializeKeysWith()
.serializeValuesWith(valueSerializationPair);
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory), redisCacheConfiguration);
}
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
return jackson2JsonRedisSerializer;
}
private ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
return objectMapper;
}
}

View File

@ -0,0 +1,12 @@
package com.springboot.cloud.common.web.exception;
import org.junit.Test;
public class DefaultGlobalExceptionHandlerAdviceTest {
@Test
public void testMethod() {
}
}

View File

@ -0,0 +1,27 @@
package com.springboot.cloud.common.web.interceptor;
import com.springboot.cloud.common.core.util.UserContextHolder;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class UserInterceptorTest {
@Test
public void preHandle_当未设置token_user_那么正常处理下一个handle() throws Exception {
UserInterceptor userInterceptor = new UserInterceptor();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
userInterceptor.preHandle(request, response, new Object());
}
@Test
public void preHandle_当设置token的username_那么username可以在线程中拿出来用() throws Exception {
UserInterceptor userInterceptor = new UserInterceptor();
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("x-client-token-user", "{\"user_name\":\"zhangsan\"}");
MockHttpServletResponse response = new MockHttpServletResponse();
userInterceptor.preHandle(request, response, new Object());
Assert.assertEquals(UserContextHolder.getInstance().getUsername(), "zhangsan");
}
}