This commit is contained in:
六如
2024-09-10 00:20:43 +08:00
parent 9a213743d7
commit 5e8a5d7133
67 changed files with 1458 additions and 592 deletions

View File

@@ -27,6 +27,7 @@
<!-- <module>sop-website</module>-->
<module>sop-index</module>
<module>sop-spring-boot-starter</module>
<module>sop-registry</module>
</modules>
<properties>
@@ -119,15 +120,11 @@
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
<groupId>io.gitee.durcframework</groupId>
<artifactId>fastmybatis-spring-boot-starter</artifactId>
<version>${fastmybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>

View File

@@ -54,6 +54,16 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 仅在开发中使用 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -2,14 +2,17 @@ package com.gitee.sop.storyweb.open;
import com.gitee.sop.storyweb.open.req.StorySaveDTO;
import com.gitee.sop.storyweb.open.resp.StoryResponse;
import com.gitee.sop.support.annotation.Open;
/**
* @author 六如
*/
public interface StoryService {
@Open("story.save")
Integer save(StorySaveDTO storySaveDTO);
@Open("story.get")
StoryResponse getById(Integer id);
}

View File

@@ -1,6 +1,6 @@
package com.gitee.sop.storyweb.open.impl;
import com.gitee.sop.springboot.starter.annotation.Open;
import com.gitee.sop.support.annotation.Open;
import com.gitee.sop.storyweb.open.StoryService;
import com.gitee.sop.storyweb.open.req.StorySaveDTO;
import com.gitee.sop.storyweb.open.resp.StoryResponse;
@@ -17,14 +17,12 @@ import javax.validation.constraints.NotNull;
@DubboService(validation = "true")
public class StoreyServiceImpl implements StoryService {
@Open("story.save")
@Override
public Integer save(StorySaveDTO storySaveDTO) {
System.out.println("save storySaveDTO:" + storySaveDTO);
return 1;
}
@Open("story.get")
@Override
public StoryResponse getById(@NotNull(message = "id必填") Integer id) {
StoryResponse storyResponse = new StoryResponse();

View File

@@ -1,9 +1,7 @@
server.port=8082
spring.application.name=story-service
# nacos
nacos.host=127.0.0.1:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
dubbo.application.qos-enable=false
dubbo.registry.address=nacos://${nacos.host}
dubbo.registry.address=zookeeper://localhost:2181

View File

@@ -0,0 +1,8 @@
package com.gitee.sop.index.api;
/**
* @author 六如
*/
public interface IsvService {
void refresh(String appId);
}

View File

@@ -25,8 +25,6 @@ public class RegisterDTO implements Serializable {
private String paramTypeName;
private Integer isIgnoreValidate;
private Integer isPermission;
private Integer isNeedToken;

View File

@@ -44,6 +44,19 @@
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>io.gitee.durcframework</groupId>
<artifactId>fastmybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -65,6 +78,24 @@
<scope>test</scope>
</dependency>
<!-- provided -->
<!-- 仅在开发中使用 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>provided</scope>
</dependency>
<!-- 仅在开发中使用 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -31,6 +31,8 @@ public class ApiInfoDTO implements Serializable {
private Integer isNeedToken;
private Integer status;
public String buildApiNameVersion() {
return apiName + apiVersion;
}

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.index.controller.param;
package com.gitee.sop.index.common;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.index.common;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.Locale;
/**
* @author 六如
*/
@Builder
@Getter
@Setter
public class ApiRequestContext {
private ApiRequest apiRequest;
private Locale locale;
private String ip;
}

View File

@@ -1,4 +1,4 @@
package com.gitee.sop.index.controller.param;
package com.gitee.sop.index.common;
import com.gitee.sop.index.exception.ApiException;
import com.gitee.sop.index.message.ErrorEnum;
@@ -93,10 +93,9 @@ public class ApiResponse {
}
public static ApiResponse error(ErrorEnum errorEnum) {
public static ApiResponse error(ApiException e) {
ApiResponse apiResponse = new ApiResponse();
ErrorMeta errorMeta = errorEnum.getErrorMeta();
IError error = errorMeta.getError(Locale.SIMPLIFIED_CHINESE);
IError error = e.getError();
apiResponse.setCode(error.getCode());
apiResponse.setMsg(error.getMsg());
apiResponse.setSub_code(error.getSub_code());
@@ -104,10 +103,10 @@ public class ApiResponse {
return apiResponse;
}
public static ApiResponse error(ErrorEnum errorEnum, String subMsg) {
public static ApiResponse error(ErrorEnum errorEnum, Locale locale, String subMsg) {
ApiResponse apiResponse = new ApiResponse();
ErrorMeta errorMeta = errorEnum.getErrorMeta();
IError error = errorMeta.getError(Locale.SIMPLIFIED_CHINESE);
IError error = errorMeta.getError(locale);
apiResponse.setCode(error.getCode());
apiResponse.setMsg(error.getMsg());
apiResponse.setSub_code(error.getSub_code());
@@ -115,4 +114,15 @@ public class ApiResponse {
return apiResponse;
}
public static ApiResponse error(ErrorEnum errorEnum, Locale locale) {
ApiResponse apiResponse = new ApiResponse();
ErrorMeta errorMeta = errorEnum.getErrorMeta();
IError error = errorMeta.getError(locale);
apiResponse.setCode(error.getCode());
apiResponse.setMsg(error.getMsg());
apiResponse.setSub_code(error.getSub_code());
apiResponse.setSub_msg(error.getSub_msg());
return apiResponse;
}
}

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.index.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 六如
*/
@Getter
@AllArgsConstructor
public class RouteContext {
private ApiRequestContext apiRequestContext;
private ApiInfoDTO apiInfo;
}

View File

@@ -0,0 +1,17 @@
package com.gitee.sop.index.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 六如
*/
@AllArgsConstructor
@Getter
public enum StatusEnum {
ENABLE(1),
DISABLE(0);
private final int value;
}

View File

@@ -1,16 +1,22 @@
package com.gitee.sop.index.config;
import com.gitee.sop.index.message.ErrorFactory;
import com.gitee.sop.index.service.manager.impl.LocalApiCacheManagerImpl;
import com.gitee.sop.index.service.manager.impl.LocalSecretManagerImpl;
import com.gitee.sop.index.service.manager.impl.LocalIsvManagerImpl;
import com.gitee.sop.index.service.manager.impl.RedisApiCacheManagerImpl;
import com.gitee.sop.index.service.manager.impl.RedisIsvManagerImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author 六如
*/
@Configuration
@Slf4j
public class IndexConfig {
@Bean
@@ -26,9 +32,20 @@ public class IndexConfig {
}
@Bean
@ConditionalOnProperty(value = "manager.secret", havingValue = "local", matchIfMissing = true)
public LocalSecretManagerImpl localSecretManager() {
return new LocalSecretManagerImpl();
@ConditionalOnProperty(value = "manager.isv", havingValue = "local", matchIfMissing = true)
public LocalIsvManagerImpl localIsvManager() {
return new LocalIsvManagerImpl();
}
@Bean
@ConditionalOnProperty(value = "manager.isv", havingValue = "redis")
public RedisIsvManagerImpl redisIsvManager() {
return new RedisIsvManagerImpl();
}
@PostConstruct
public void init() {
ErrorFactory.initMessageSource(null);
}
}

View File

@@ -1,8 +1,10 @@
package com.gitee.sop.index.controller;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.controller.param.ApiResponse;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ApiResponse;
import com.gitee.sop.index.service.RouteService;
import com.gitee.sop.index.util.RequestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@@ -10,6 +12,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* 开放平台入口
*
@@ -23,7 +27,7 @@ public class IndexController {
@GetMapping("/")
public String home() {
return "api";
return "Open Platform";
}
/**
@@ -32,8 +36,14 @@ public class IndexController {
* @return 返回响应内容
*/
@PostMapping("api")
public ApiResponse index(@Validated @RequestBody ApiRequest request) {
return routeService.route(request);
public ApiResponse index(@RequestBody ApiRequest apiRequest, HttpServletRequest request) {
String ip = RequestUtil.getIP(request);
ApiRequestContext apiRequestContext = ApiRequestContext.builder()
.apiRequest(apiRequest)
.locale(request.getLocale())
.ip(ip)
.build();
return routeService.route(apiRequestContext);
}
}

View File

@@ -0,0 +1,80 @@
package com.gitee.sop.index.dao.entity;
import java.time.LocalDateTime;
import java.util.Date;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
import com.gitee.fastmybatis.annotation.Table;
import lombok.Data;
/**
* 表名api_info
* 备注:接口信息表
*
* @author tanghc
*/
@Table(name = "api_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
@Data
public class ApiInfo {
private Long id;
/**
* 应用名称
*/
private String application;
/**
* 接口名称
*/
private String apiName;
/**
* 版本号
*/
private String apiVersion;
/**
* 接口class
*/
private String interfaceClassName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数名称
*/
private String paramName;
/**
* 参数类型名称
*/
private String paramTypeName;
/**
* 接口是否需要授权访问
*/
private Integer isPermission;
/**
* 是否需要appAuthToken
*/
private Integer isNeedToken;
/**
* 状态,1-启用,0-禁用
*/
private Integer apiStatus;
private LocalDateTime addTime;
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,19 @@
package com.gitee.sop.index.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.index.dao.entity.ApiInfo;
/**
* @author tanghc
*/
public interface ApiInfoMapper extends BaseMapper<ApiInfo> {
default ApiInfo getByNameVersion(String apiName, String apiVersion) {
return this.query()
.eq(ApiInfo::getApiName, apiName)
.eq(ApiInfo::getApiVersion, apiVersion)
.get();
}
}

View File

@@ -1,29 +0,0 @@
package com.gitee.sop.index.dubbo;
import com.gitee.sop.index.api.ApiRegisterService;
import com.gitee.sop.index.api.RegisterDTO;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.service.manager.ApiCacheManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author 六如
*/
@Slf4j
@DubboService
public class ApiRegisterServiceImpl implements ApiRegisterService {
@Autowired
private ApiCacheManager apiCache;
@Override
public void register(RegisterDTO registerDTO) {
log.info("收到接口注册, registerDTO={}", registerDTO);
ApiInfoDTO apiInfoDTO = new ApiInfoDTO();
BeanUtils.copyProperties(registerDTO, apiInfoDTO);
apiCache.save(apiInfoDTO);
}
}

View File

@@ -1,8 +1,7 @@
package com.gitee.sop.index.exception;
import com.gitee.sop.index.message.ErrorFactory;
import com.gitee.sop.index.message.ErrorMeta;
import com.gitee.sop.index.message.ErrorEnum;
import com.gitee.sop.index.message.IError;
import java.util.Locale;
@@ -13,33 +12,36 @@ import java.util.Locale;
public class ApiException extends RuntimeException {
private static final long serialVersionUID = 8278005515613227643L;
private transient IError error;
private final transient Locale locale;
private transient ErrorMeta errorMeta;
private transient Object[] params;
private final transient ErrorEnum errorEnum;
private final transient Object[] params;
public ApiException(ErrorMeta errorMeta, Object... params) {
this.errorMeta = errorMeta;
public ApiException(ErrorEnum errorEnum, Locale locale, Object... params) {
this.errorEnum = errorEnum;
this.params = params;
this.locale = locale;
}
public ApiException(Throwable cause, ErrorMeta errorMeta, Object... params) {
public ApiException(Throwable cause, ErrorEnum errorEnum, Locale locale, Object... params) {
super(cause);
this.errorMeta = errorMeta;
this.errorEnum = errorEnum;
this.params = params;
this.locale = locale;
}
public IError getError(Locale locale) {
if (error == null) {
error = ErrorFactory.getError(this.errorMeta, locale, params);
public IError getError() {
return errorEnum.getErrorMeta().getError(locale, params);
}
return error;
public ErrorEnum getErrorEnum() {
return errorEnum;
}
@Override
public String getMessage() {
String message = super.getMessage();
return message == null ? errorMeta.toString() : message;
return message == null ? errorEnum.getErrorMeta().toString() : message;
}
}

View File

@@ -0,0 +1,77 @@
package com.gitee.sop.index.exception;
import com.gitee.sop.index.common.ApiResponse;
import com.gitee.sop.index.message.ErrorEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class ControllerGlobalExceptionHandler {
// 未知异常
@ExceptionHandler(value = Exception.class)
public ApiResponse globalExceptionHandler(Exception e, HttpServletRequest request) {
log.error("系统出错", e);
return ApiResponse.error(ErrorEnum.ISP_UNKNOWN_ERROR, request.getLocale());
}
/**
* 处理jsr303的字段校验异常也可以自定义注解校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse handleValidationExceptions(
MethodArgumentNotValidException ex, HttpServletRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", errors.values()));
}
/**
* 验证异常
*
* @param e e
*/
@ExceptionHandler(ValidationException.class)
public ApiResponse validationException(ValidationException e, HttpServletRequest request) {
List<String> msgList = new ArrayList<>();
for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException) e).getConstraintViolations()) {
msgList.add(constraintViolation.getMessage());
}
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", msgList));
}
/**
* 参数绑定异常GET请求参数校验
*
* @param e e
*/
@ExceptionHandler(BindException.class)
public ApiResponse bandException(BindException e, HttpServletRequest request) {
List<FieldError> fieldErrors = e.getFieldErrors();
List<String> msgList = new ArrayList<>();
for (FieldError fieldError : fieldErrors) {
msgList.add(fieldError.getDefaultMessage());
}
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", msgList));
}
}

View File

@@ -0,0 +1,13 @@
package com.gitee.sop.index.exception;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ApiResponse;
/**
* @author 六如
*/
public interface ExceptionExecutor {
ApiResponse executeException(ApiRequestContext apiRequestContext, Exception e);
}

View File

@@ -0,0 +1,25 @@
package com.gitee.sop.index.exception;
import com.gitee.sop.index.message.ErrorEnum;
import lombok.Getter;
/**
* @author 六如
*/
@Getter
public class SignException extends Exception {
private static final long serialVersionUID = 4049365045207852768L;
public SignException(ErrorEnum errorEnum) {
this.errorEnum = errorEnum;
}
public SignException(ErrorEnum errorEnum, Throwable cause) {
super(cause);
this.errorEnum = errorEnum;
}
private final ErrorEnum errorEnum;
}

View File

@@ -0,0 +1,50 @@
package com.gitee.sop.index.exception.impl;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ApiResponse;
import com.gitee.sop.index.exception.ApiException;
import com.gitee.sop.index.exception.ExceptionExecutor;
import com.gitee.sop.index.message.ErrorEnum;
import org.apache.dubbo.rpc.service.GenericException;
import org.springframework.stereotype.Service;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 六如
*/
@Service
public class ExceptionExecutorImpl implements ExceptionExecutor {
private static final String CONSTRAINT_VIOLATION_EXCEPTION = "ConstraintViolationException";
@Override
public ApiResponse executeException(ApiRequestContext apiRequestContext, Exception e) {
if (e instanceof GenericException) {
GenericException genericException = (GenericException) e;
String exceptionClass = genericException.getExceptionClass();
if (exceptionClass.contains(CONSTRAINT_VIOLATION_EXCEPTION)) {
String exceptionMessage = genericException.getExceptionMessage();
// 参数校验:Failed to validate service: com.gitee.sop.storyweb.open.StoryService, method: save, cause: [ConstraintViolationImpl{interpolatedMessage='故事名称必填', propertyPath=storyName, rootBeanClass=class com.gitee.sop.storyweb.open.req.StorySaveDTO, messageTemplate='故事名称必填'}]
Set<String> msgs = findErrorMsg(exceptionMessage);
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, apiRequestContext.getLocale(), String.join(",", msgs));
}
} else if (e instanceof ApiException) {
return ApiResponse.error(((ApiException) e));
}
return ApiResponse.error(ErrorEnum.ISP_SERVICE_UNKNOWN_ERROR, apiRequestContext.getLocale());
}
private static Set<String> findErrorMsg(String text) {
Pattern pattern = Pattern.compile("'([^']*)'");
Matcher matcher = pattern.matcher(text);
Set<String> msgList = new LinkedHashSet<>();
while (matcher.find()) {
msgList.add(matcher.group(1));
}
return msgList;
}
}

View File

@@ -109,7 +109,7 @@ public enum ErrorEnum {
ISV_IP_FORBIDDEN(Codes.CODE_ISV_PERM, "isv.ip-forbidden"),
;
private ErrorMeta errorMeta;
private final ErrorMeta errorMeta;
ErrorEnum(String code, String subCode) {
this.errorMeta = new ErrorMeta("open.error_", code, subCode);

View File

@@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Arrays;
@@ -48,7 +49,7 @@ public class ErrorFactory {
HashSet<String> baseNamesSet = new HashSet<>();
baseNamesSet.add(I18N_OPEN_ERROR);
if (!isvModules.isEmpty()) {
if (!CollectionUtils.isEmpty(isvModules)) {
baseNamesSet.addAll(isvModules);
}

View File

@@ -1,6 +1,5 @@
package com.gitee.sop.index.message;
import com.gitee.sop.index.exception.ApiException;
import lombok.Getter;
import java.util.Locale;
@@ -23,25 +22,10 @@ public class ErrorMeta {
this.subCode = subCode;
}
public IError getError(Locale locale) {
return ErrorFactory.getError(this, locale);
public IError getError(Locale locale, Object... params) {
return ErrorFactory.getError(this, locale, params);
}
/**
* 返回网关exception
*
* @param params 参数
* @return 返回exception
*/
public ApiException getException(Object... params) {
if (params != null && params.length == 1) {
Object param = params[0];
if (param instanceof Throwable) {
return new ApiException((Throwable) param, this);
}
}
return new ApiException(this, params);
}
@Override
public String toString() {

View File

@@ -1,23 +1,33 @@
package com.gitee.sop.index.service;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.dao.entity.ApiInfo;
import com.gitee.sop.index.dao.mapper.ApiInfoMapper;
import com.gitee.sop.index.service.manager.ApiCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author 六如
*/
@Service
public class ApiService {
@Autowired
@Resource
private ApiCacheManager apiCacheManager;
@Resource
private ApiInfoMapper apiInfoMapper;
public ApiInfoDTO getApi(String apiName, String apiVersion) {
return apiCacheManager.getOrElse(apiName, apiVersion, () -> {
ApiInfoDTO record = new ApiInfoDTO();
ApiInfo apiInfo = apiInfoMapper.getByNameVersion(apiName, apiVersion);
if (apiInfo != null) {
BeanUtils.copyProperties(apiInfo, record);
}
return record;
});
}

View File

@@ -15,11 +15,15 @@ import org.springframework.stereotype.Service;
*/
@Service
public class GenericServiceInvoker implements InitializingBean {
private static final String TRUE = "true";
private ApplicationConfig applicationConfig;
@Value("${nacos.host:localhost:8848}")
private String nacosHost;
@Value("${register.address:${register.type}://${register.host}")
private String registerAddress;
@Value("${spring.application.name}")
private String appName;
@@ -36,13 +40,13 @@ public class GenericServiceInvoker implements InitializingBean {
private RegistryConfig buildRegistryConfig(String nacosHost) {
RegistryConfig config = new RegistryConfig();
config.setAddress("nacos://" + nacosHost);
config.setAddress(registerAddress);
return config;
}
public Object invoke(String interfaceName, String method, String[] parameterTypes, Object[] params) {
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setGeneric("true");
reference.setGeneric(TRUE);
reference.setApplication(applicationConfig);
reference.setInterface(interfaceName);
reference.setTimeout(timeout);

View File

@@ -3,22 +3,19 @@ package com.gitee.sop.index.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.controller.param.ApiResponse;
import com.gitee.sop.index.message.ErrorEnum;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ApiResponse;
import com.gitee.sop.index.exception.ExceptionExecutor;
import com.gitee.sop.index.service.validate.Validator;
import com.gitee.sop.index.util.ClassUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.ClassUtils;
import org.apache.dubbo.rpc.service.GenericException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
/**
* 接口路由
@@ -29,94 +26,66 @@ import java.util.regex.Pattern;
@Slf4j
public class RouteService {
private static final String CONSTRAINT_VIOLATION_EXCEPTION = "ConstraintViolationException";
@Autowired
@Resource
private Validator validator;
@Autowired
@Resource
private GenericServiceInvoker genericServiceInvoker;
@Autowired
private ApiService apiService;
@Resource
private ExceptionExecutor exceptionExecutor;
public ApiResponse route(ApiRequest apiRequest) {
// 校验
validator.validate(apiRequest);
return doRoute(apiRequest);
public ApiResponse route(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
log.info("收到客户端请求, ip={}, apiRequest={}", apiRequestContext.getIp(), apiRequest);
try {
// 接口校验
ApiInfoDTO apiInfoDTO = validator.validate(apiRequestContext);
return doRoute(apiRequestContext, apiInfoDTO);
} catch (Exception e) {
log.error("接口请求报错, , ip={}, apiRequest={}", apiRequestContext.getIp(), apiRequest, e);
return exceptionExecutor.executeException(apiRequestContext, e);
}
}
private ApiResponse doRoute(ApiRequest apiRequest) {
ApiInfoDTO apiInfo = apiService.getApi(apiRequest.getMethod(), apiRequest.getVersion());
Object result = null;
Exception error = null;
beforeRoute(apiRequest, apiInfo);
try {
result = genericServiceInvoker.invoke(
private ApiResponse doRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfo) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
Object result = genericServiceInvoker.invoke(
apiInfo.getInterfaceClassName(),
apiInfo.getMethodName(),
new String[]{apiInfo.getParamTypeName()},
new Object[]{buildInvokeParam(apiRequest, apiInfo)}
buildParamType(apiInfo),
buildInvokeParam(apiRequest, apiInfo)
);
} catch (Exception e) {
error = e;
log.error("请求服务出错, apiRequest={}, apiInfo={}", apiInfo, apiInfo, e);
return buildApiResponse(e);
} finally {
afterRoute(result, apiRequest, error);
}
return ApiResponse.success(result);
}
private ApiResponse buildApiResponse(Exception e) {
if (e instanceof GenericException) {
GenericException genericException = (GenericException) e;
String exceptionClass = genericException.getExceptionClass();
if (exceptionClass.contains(CONSTRAINT_VIOLATION_EXCEPTION)) {
String exceptionMessage = genericException.getExceptionMessage();
// 参数校验:Failed to validate service: com.gitee.sop.storyweb.open.StoryService, method: save, cause: [ConstraintViolationImpl{interpolatedMessage='故事名称必填', propertyPath=storyName, rootBeanClass=class com.gitee.sop.storyweb.open.req.StorySaveDTO, messageTemplate='故事名称必填'}]
Set<String> msgs = findErrorMsg(exceptionMessage);
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, String.join(",", msgs));
private String[] buildParamType(ApiInfoDTO apiInfo) {
String paramTypeName = apiInfo.getParamTypeName();
if (StringUtils.hasText(paramTypeName)) {
return new String[]{apiInfo.getParamTypeName()};
} else {
return new String[0];
}
}
return ApiResponse.error(ErrorEnum.ISP_SERVICE_UNKNOWN_ERROR);
}
private Set<String> findErrorMsg(String text) {
Pattern pattern = Pattern.compile("'([^']*)'");
Matcher matcher = pattern.matcher(text);
Set<String> msgList = new LinkedHashSet<>();
while (matcher.find()) {
msgList.add(matcher.group(1));
}
return msgList;
}
private Object buildInvokeParam(ApiRequest apiRequest, ApiInfoDTO apiInfo) {
private Object[] buildInvokeParam(ApiRequest apiRequest, ApiInfoDTO apiInfo) {
if (ObjectUtils.isEmpty(apiInfo.getParamTypeName())) {
return null;
return new Object[0];
}
String bizContent = apiRequest.getBiz_content();
JSONObject jsonObject = JSON.parseObject(bizContent);
if (ClassUtil.isPrimitive(apiInfo.getParamTypeName())) {
try {
return jsonObject.getObject(apiInfo.getParamName(), ClassUtils.forName(apiInfo.getParamTypeName()));
Object value = jsonObject.getObject(apiInfo.getParamName(), ClassUtils.forName(apiInfo.getParamTypeName()));
return new Object[]{value};
} catch (ClassNotFoundException e) {
log.error("找不到参数class, paramTypeName={}, apiRequest={}, apiInfo={}",
apiInfo.getParamTypeName(), apiRequest, apiInfo, e);
throw new RuntimeException(e);
}
} else {
return jsonObject;
return new Object[]{jsonObject};
}
}
private void beforeRoute(ApiRequest apiRequest, ApiInfoDTO apiInfo) {
log.info("收到接口请求, apiRequest={}", apiRequest);
}
private void afterRoute(Object result, ApiRequest apiRequest, Exception e) {
}
}

View File

@@ -1,19 +0,0 @@
package com.gitee.sop.index.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.gitee.sop.index.controller.param.ApiRequest;
import org.springframework.stereotype.Service;
/**
* @author 六如
*/
@Service
public class ValidateService {
public void validate(ApiRequest apiRequest) {
String jsonString = JSON.toJSONString(apiRequest);
JSONObject jsonObject = JSON.parseObject(jsonString);
}
}

View File

@@ -0,0 +1,52 @@
package com.gitee.sop.index.service.dubbo;
import com.gitee.sop.index.api.ApiRegisterService;
import com.gitee.sop.index.api.RegisterDTO;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.common.StatusEnum;
import com.gitee.sop.index.dao.entity.ApiInfo;
import com.gitee.sop.index.dao.mapper.ApiInfoMapper;
import com.gitee.sop.index.service.manager.ApiCacheManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author 六如
*/
@Slf4j
@DubboService
public class ApiRegisterServiceImpl implements ApiRegisterService {
@Resource
private ApiCacheManager apiCacheManager;
@Resource
private ApiInfoMapper apiInfoMapper;
@Override
public void register(RegisterDTO registerDTO) {
log.info("收到接口注册, registerDTO={}", registerDTO);
ApiInfoDTO apiInfoDTO = new ApiInfoDTO();
BeanUtils.copyProperties(registerDTO, apiInfoDTO);
ApiInfo apiInfo = apiInfoMapper.getByNameVersion(apiInfoDTO.getApiName(), apiInfoDTO.getApiVersion());
LocalDateTime now = LocalDateTime.now();
if (apiInfo == null) {
apiInfo = new ApiInfo();
apiInfo.setApiStatus(StatusEnum.ENABLE.getValue());
apiInfo.setAddTime(now);
}
apiInfo.setUpdateTime(now);
BeanUtils.copyProperties(apiInfoDTO, apiInfo);
// 保存到数据库
apiInfoMapper.saveOrUpdate(apiInfo);
// 保存到缓存
apiCacheManager.save(apiInfoDTO);
}
}

View File

@@ -0,0 +1,23 @@
package com.gitee.sop.index.service.dubbo;
import com.gitee.sop.index.api.IsvService;
import com.gitee.sop.index.service.manager.IsvManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
/**
* @author 六如
*/
@DubboService
@Slf4j
public class IsvServiceImpl implements IsvService {
private IsvManager isvManager;
@Override
public void refresh(String appId) {
log.info("refresh Isv, appId={}", appId);
// 刷新isv信息
isvManager.reload(appId);
}
}

View File

@@ -0,0 +1,12 @@
package com.gitee.sop.index.service.manager;
/**
* IP黑名单管理
*
* @author 六如
*/
public interface IpBlacklistManager {
boolean contains(String ip);
}

View File

@@ -0,0 +1,12 @@
package com.gitee.sop.index.service.manager;
/**
* isv接口授权管理
*
* @author 六如
*/
public interface IsvApiPermissionManager {
boolean hasPermission(String appId, String apiNameVersion);
}

View File

@@ -0,0 +1,13 @@
package com.gitee.sop.index.service.manager;
import com.gitee.sop.index.service.manager.dto.IsvDTO;
/**
* @author 六如
*/
public interface IsvManager {
IsvDTO getIsv(String appId);
void reload(String appId);
}

View File

@@ -0,0 +1,18 @@
package com.gitee.sop.index.service.manager.dto;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class IsvDTO {
private String appId;
private String publicKey;
private String privateKeyPlatform;
private Integer status;
}

View File

@@ -0,0 +1,15 @@
package com.gitee.sop.index.service.manager.impl;
import com.gitee.sop.index.service.manager.IpBlacklistManager;
import org.springframework.stereotype.Service;
/**
* @author 六如
*/
@Service
public class IpBlacklistManagerImpl implements IpBlacklistManager {
@Override
public boolean contains(String ip) {
return false;
}
}

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.index.service.manager.impl;
import com.gitee.sop.index.service.manager.IsvApiPermissionManager;
import org.springframework.stereotype.Service;
/**
* @author 六如
*/
@Service
public class IsvApiPermissionManagerImpl implements IsvApiPermissionManager {
@Override
public boolean hasPermission(String appId, String apiNameVersion) {
return false;
}
}

View File

@@ -0,0 +1,31 @@
package com.gitee.sop.index.service.manager.impl;
import com.gitee.sop.index.service.manager.IsvManager;
import com.gitee.sop.index.service.manager.dto.IsvDTO;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
* @author 六如
*/
public class LocalIsvManagerImpl implements IsvManager {
private static final Map<String, IsvDTO> ISV_MAP = new HashMap<>();
@Override
public IsvDTO getIsv(String appId) {
return ISV_MAP.get(appId);
}
@Override
public void reload(String appId) {
}
@PostConstruct
public void init() {
}
}

View File

@@ -0,0 +1,20 @@
package com.gitee.sop.index.service.manager.impl;
import com.gitee.sop.index.service.manager.IsvManager;
import com.gitee.sop.index.service.manager.dto.IsvDTO;
/**
* @author 六如
*/
public class RedisIsvManagerImpl implements IsvManager {
@Override
public IsvDTO getIsv(String appId) {
return null;
}
@Override
public void reload(String appId) {
}
}

View File

@@ -1,6 +1,8 @@
package com.gitee.sop.index.service.validate;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.exception.ApiException;
import com.gitee.sop.index.message.ErrorEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.StringUtils;
@@ -14,19 +16,20 @@ public abstract class AbstractSigner implements Signer {
/**
* 构建服务端签名串
*
* @param params 接口参数
* @param apiRequestContext 接口参数
* @param secret 秘钥
* @return 返回服务端签名串
*/
protected abstract String buildServerSign(ApiRequest params, String secret);
protected abstract String buildServerSign(ApiRequestContext apiRequestContext, String secret);
@Override
public boolean checkSign(ApiRequest apiRequest, String secret) {
public boolean checkSign(ApiRequestContext apiRequestContext, String publicKey) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String clientSign = apiRequest.getSign();
if (StringUtils.isBlank(clientSign)) {
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException();
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, apiRequestContext.getLocale());
}
String serverSign = buildServerSign(apiRequest, secret);
String serverSign = buildServerSign(apiRequestContext, publicKey);
return clientSign.equals(serverSign);
}

View File

@@ -1,18 +1,25 @@
package com.gitee.sop.index.service.validate;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ParamNames;
import com.gitee.sop.index.config.ApiConfig;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.exception.ApiException;
import com.gitee.sop.index.message.ErrorEnum;
import com.gitee.sop.index.service.manager.ApiCacheManager;
import com.gitee.sop.index.service.manager.SecretManager;
import com.gitee.sop.index.service.ApiService;
import com.gitee.sop.index.service.manager.IpBlacklistManager;
import com.gitee.sop.index.service.manager.IsvApiPermissionManager;
import com.gitee.sop.index.service.manager.IsvManager;
import com.gitee.sop.index.service.manager.dto.IsvDTO;
import com.gitee.sop.index.service.validate.alipay.AlipaySigner;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile;
@@ -21,6 +28,7 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* 负责校验,校验工作都在这里
@@ -28,7 +36,7 @@ import java.util.List;
* @author tanghc
*/
@Slf4j
@Getter
@RequiredArgsConstructor
@Service
public class ApiValidator implements Validator {
@@ -37,18 +45,6 @@ public class ApiValidator implements Validator {
private static List<String> FORMAT_LIST = Arrays.asList("json", "xml");
// @Autowired
// private IsvManager isvManager;
//
// @Autowired
// private IsvRoutePermissionManager isvRoutePermissionManager;
//
// @Autowired
// private IPBlacklistManager ipBlacklistManager;
//
// @Autowired
// private RouteConfigManager routeConfigManager;
private final Signer signer = new AlipaySigner();
/**
@@ -57,137 +53,114 @@ public class ApiValidator implements Validator {
@Value("${upload.max-file-size:${spring.servlet.multipart.max-file-size:10MB}}")
private String maxFileSize;
@Autowired
private ApiConfig apiConfig;
private final ApiConfig apiConfig;
@Autowired
private ApiCacheManager apiCacheManager;
private ApiService apiService;
@Autowired
private SecretManager secretManager;
private IpBlacklistManager ipBlacklistManager;
@Autowired
private IsvApiPermissionManager isvApiPermissionManager;
@Autowired
private IsvManager isvManager;
@Override
public void validate(ApiRequest param) {
// checkIP(param);
// TargetRoute targetRoute = checkEnable(param);
// initFields(targetRoute, param);
// ApiConfig apiConfig = ApiContext.getApiConfig();
// checkAppKey(param);
// if (apiConfig.isIgnoreValidate()
// || BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) {
// if (log.isDebugEnabled()) {
// log.debug("忽略签名校验, name:{}, version:{}", param.fetchName(), param.fetchVersion());
// }
// } else {
// checkSign(param);
// }
checkSign(param);
checkTimeout(param);
checkFormat(param);
// checkUploadFile(param);
// checkPermission(param);
// checkToken(param);
public ApiInfoDTO validate(ApiRequestContext apiRequestContext) {
// 校验字段完整性
checkField(apiRequestContext);
// 检查isv
IsvDTO isvDTO = checkIsv(apiRequestContext);
// 校验签名
checkSign(apiRequestContext, isvDTO);
// 检查是否超时
checkTimeout(apiRequestContext);
// 检查格式化
checkFormat(apiRequestContext);
// IP能否访问
checkIP(apiRequestContext);
ApiRequest apiRequest = apiRequestContext.getApiRequest();
ApiInfoDTO apiInfo = apiService.getApi(apiRequest.getMethod(), apiRequest.getVersion());
// 检查接口信息
checkApiInfo(apiRequestContext, apiInfo);
// 检查上传文件
checkUploadFile(apiRequestContext);
// 检查token
checkToken(apiRequestContext);
return apiInfo;
}
public void checkApiInfo(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO) {
// 检查路由是否存在
if (apiInfoDTO == null) {
throw new ApiException(ErrorEnum.ISV_INVALID_METHOD, apiRequestContext.getLocale());
}
// 检查路由是否启用
if (!BooleanUtils.toBoolean(apiInfoDTO.getStatus())) {
throw new ApiException(ErrorEnum.ISP_API_DISABLED, apiRequestContext.getLocale());
}
// 校验是否需要授权访问
boolean needCheckPermission = BooleanUtils.toBoolean(apiInfoDTO.getIsPermission());
if (needCheckPermission) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String appKey = apiRequest.getApp_id();
String nameVersion = apiRequest.takeNameVersion();
boolean hasPermission = isvApiPermissionManager.hasPermission(appKey, nameVersion);
if (!hasPermission) {
throw new ApiException(ErrorEnum.ISV_ROUTE_NO_PERMISSIONS, apiRequestContext.getLocale());
}
}
}
public void checkField(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
Locale locale = apiRequestContext.getLocale();
if (ObjectUtils.isEmpty(apiRequest.getApp_id())) {
throw new ApiException(ErrorEnum.ISV_MISSING_APP_ID, locale);
}
}
/**
* 是否在IP黑名单中
*
* @param param 接口参数
* @param apiRequestContext 接口参数
*/
// protected void checkIP(ApiRequest param) {
// String ip = param.fetchIp();
// if (ipBlacklistManager.contains(ip)) {
// throw ErrorEnum.ISV_IP_FORBIDDEN.getErrorMeta().getException();
// }
// }
protected void checkIP(ApiRequestContext apiRequestContext) {
String ip = apiRequestContext.getIp();
if (ipBlacklistManager.contains(ip)) {
throw new ApiException(ErrorEnum.ISV_IP_FORBIDDEN, apiRequestContext.getLocale());
}
}
/**
* 检测能否访问
*
* @param param 接口参数
*/
// protected TargetRoute checkEnable(ApiRequest param) {
// String name = param.getMethod();
// if (name == null) {
// throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
// }
// String version = param.fetchVersion();
// if (version == null) {
// throw ErrorEnum.ISV_MISSING_VERSION.getErrorMeta().getException();
// }
// String routeId = param.takeApiName();
// // 检查路由是否存在
// TargetRoute targetRoute = RouteRepositoryContext.getTargetRoute(routeId);
// if (targetRoute == null) {
// throw ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
// }
// // 检查路由是否启用
// RouteConfig routeConfig = routeConfigManager.get(routeId);
// if (!routeConfig.enable()) {
// throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
// }
// return targetRoute;
// }
// private void initFields(TargetRoute targetRoute, ApiRequest apiParam) {
// apiParam.setServiceId(targetRoute.getServiceDefinition().getServiceId());
// boolean mergeResult;
// Boolean defaultSetting = ApiContext.getApiConfig().getMergeResult();
// if (defaultSetting != null) {
// mergeResult = defaultSetting;
// } else {
// RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
// mergeResult = routeDefinition == null || BooleanUtils.toBoolean(routeDefinition.getMergeResult());
// }
// apiParam.setMergeResult(mergeResult);
// }
/**
* 校验上传文件内容
*
* @param param
* @param apiRequestContext apiRequestContext
*/
// protected void checkUploadFile(ApiRequest param) {
// UploadContext uploadContext = param.fetchUploadContext();
// if (uploadContext != null) {
// try {
// List<MultipartFile> files = uploadContext.getAllFile();
// for (MultipartFile file : files) {
// checkSingleFileSize(file);
// checkFileMd5(param, file);
// }
// } catch (IOException e) {
// log.error("验证上传文件MD5错误", e);
// throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
// }
// }
// }
// private void checkFileMd5(ApiRequest param, MultipartFile file) throws IOException {
// // 客户端传来的文件md5
// String clientMd5 = param.getString(file.getName());
// if (clientMd5 != null) {
// String fileMd5 = DigestUtils.md5Hex(file.getBytes());
// if (!clientMd5.equals(fileMd5)) {
// throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
// }
// }
// }
protected void checkUploadFile(ApiRequestContext apiRequestContext) {
// todo:校验上传文件内容
}
/**
* 校验单个文件大小
*
* @param file 文件
*/
private void checkSingleFileSize(MultipartFile file) {
private void checkSingleFileSize(ApiRequestContext apiRequestContext, MultipartFile file) {
long fileSize = file.getSize();
if (fileSize > DataSize.parse(maxFileSize).toBytes()) {
throw ErrorEnum.ISV_INVALID_FILE_SIZE.getErrorMeta().getException(file.getName(), maxFileSize);
throw new ApiException(ErrorEnum.ISV_INVALID_FILE_SIZE, apiRequestContext.getLocale(), fileSize, maxFileSize);
}
}
protected void checkTimeout(ApiRequest param) {
protected void checkTimeout(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
int timeoutSeconds = apiConfig.getTimeoutSeconds();
// 如果设置为0表示不校验
if (timeoutSeconds == 0) {
@@ -196,103 +169,71 @@ public class ApiValidator implements Validator {
if (timeoutSeconds < 0) {
throw new IllegalArgumentException("服务端timeoutSeconds设置错误");
}
String requestTime = param.getTimestamp();
String requestTime = apiRequest.getTimestamp();
try {
Date requestDate = new SimpleDateFormat(ParamNames.TIMESTAMP_PATTERN).parse(requestTime);
long requestMilliseconds = requestDate.getTime();
if (System.currentTimeMillis() - requestMilliseconds > timeoutSeconds * MILLISECOND_OF_ONE_SECOND) {
throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException();
throw new ApiException(ErrorEnum.ISV_INVALID_TIMESTAMP, apiRequestContext.getLocale());
}
} catch (ParseException e) {
throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException(param.takeNameVersion());
throw new ApiException(ErrorEnum.ISV_INVALID_TIMESTAMP, apiRequestContext.getLocale(), apiRequest.takeNameVersion());
}
}
// protected void checkAppKey(ApiRequest param) {
// if (StringUtils.isEmpty(param.fetchAppKey())) {
// throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
// }
// Isv isv = isvManager.getIsv(param.fetchAppKey());
// // 没有用户
// if (isv == null) {
// throw ErrorEnum.ISV_INVALID_APP_ID.getErrorMeta().getException();
// }
// // 禁止访问
// if (isv.getStatus() == null || isv.getStatus() == STATUS_FORBIDDEN) {
// throw ErrorEnum.ISV_ACCESS_FORBIDDEN.getErrorMeta().getException();
// }
// }
protected void checkSign(ApiRequest param) {
String clientSign = param.getSign();
try {
if (StringUtils.isEmpty(clientSign)) {
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.takeNameVersion(), ParamNames.SIGN_NAME);
protected IsvDTO checkIsv(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
IsvDTO isv = isvManager.getIsv(apiRequest.getApp_id());
// 没有用户
if (isv == null) {
throw new ApiException(ErrorEnum.ISV_INVALID_APP_ID, apiRequestContext.getLocale());
}
// 禁止访问
if (isv.getStatus() == null || isv.getStatus() == STATUS_FORBIDDEN) {
throw new ApiException(ErrorEnum.ISV_ACCESS_FORBIDDEN, apiRequestContext.getLocale());
}
return isv;
}
protected void checkSign(ApiRequestContext apiRequestContext, IsvDTO isv) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String clientSign = apiRequest.getSign();
if (ObjectUtils.isEmpty(clientSign)) {
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, apiRequestContext.getLocale(),
apiRequest.takeNameVersion(), ParamNames.SIGN_NAME);
}
// ISV上传的公钥
String publicKey = isv.getPublicKey();
if (ObjectUtils.isEmpty(publicKey)) {
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG, apiRequestContext.getLocale(),
apiRequest.takeNameVersion());
}
// ApiConfig apiConfig = ApiContext.getApiConfig();
// // 根据appId获取秘钥
// Isv isvInfo = isvManager.getIsv(param.fetchAppKey());
// String secret = isvInfo.getSecretInfo();
String secret = secretManager.getIsvPublicKey(param.getApp_id());
// if (StringUtils.isEmpty(secret)) {
// throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
// }
// 错误的sign
if (!signer.checkSign(param, secret)) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.takeNameVersion());
}
} finally {
// 校验过程中会移除sign这里需要重新设置进去
param.setSign(clientSign);
if (!signer.checkSign(apiRequestContext, publicKey)) {
throw new ApiException(ErrorEnum.ISV_INVALID_SIGNATURE, apiRequestContext.getLocale(),
apiRequest.takeNameVersion());
}
}
protected void checkFormat(ApiRequest param) {
String format = param.getFormat();
protected void checkFormat(ApiRequestContext apiRequestContext) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
String format = apiRequest.getFormat();
boolean contains = FORMAT_LIST.contains(format.toLowerCase());
if (!contains) {
throw ErrorEnum.ISV_INVALID_FORMAT.getErrorMeta().getException(param.takeNameVersion(), format);
throw new ApiException(ErrorEnum.ISV_INVALID_FORMAT, apiRequestContext.getLocale(),
apiRequest.takeNameVersion(), format);
}
}
/**
* 校验访问权限
*
* @param apiParam 参数
*/
// protected void checkPermission(ApiRequest apiParam) {
// String routeId = apiParam.takeApiName();
// TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
// RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
// boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
// if (needCheckPermission) {
// String appKey = apiParam.fetchAppKey();
// boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
// if (!hasPermission) {
// throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException();
// }
// }
// }
/**
* 校验token
*
* @param apiParam 参数
* @param apiRequestContext 参数
*/
// protected void checkToken(ApiRequest apiParam) {
// String routeId = apiParam.takeApiName();
// TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
// RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
// boolean needToken = BooleanUtils.toBoolean(routeDefinition.getNeedToken());
// if (needToken) {
// TokenValidator tokenValidator = ApiConfig.getInstance().getTokenValidator();
// boolean rightToken = tokenValidator.validateToken(apiParam);
// if (!rightToken) {
// throw ErrorEnum.AOP_INVALID_APP_AUTH_TOKEN.getErrorMeta().getException();
// }
// }
// }
protected void checkToken(ApiRequestContext apiRequestContext) {
// todo:校验token
}
}

View File

@@ -1,20 +1,21 @@
package com.gitee.sop.index.service.validate;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
/**
* 负责签名校验
* @author tanghc
*
* @author tanghc
*/
public interface Signer {
/**
* 签名校验
* @param apiRequest 参数
* @param secret 秘钥
*
* @param apiRequestContext 参数
* @param publicKey 公钥
* @return true签名正确
*/
boolean checkSign(ApiRequest apiRequest, String secret);
boolean checkSign(ApiRequestContext apiRequestContext, String publicKey);
}

View File

@@ -1,12 +0,0 @@
package com.gitee.sop.index.service.validate;
import com.gitee.sop.index.controller.param.ApiRequest;
/**
* @author tanghc
*/
@FunctionalInterface
public interface TokenValidator {
boolean validateToken(ApiRequest apiRequest);
}

View File

@@ -1,18 +1,20 @@
package com.gitee.sop.index.service.validate;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.common.ApiInfoDTO;
import com.gitee.sop.index.common.ApiRequestContext;
/**
* 校验接口
*
* @author tanghc
*
*/
public interface Validator {
/**
* 接口验证
* @param apiRequest 接口参数
*
* @param apiRequestContext 请求内容
* @return 校验通过返回路由信息
*/
void validate(ApiRequest apiRequest);
ApiInfoDTO validate(ApiRequestContext apiRequestContext);
}

View File

@@ -5,6 +5,8 @@
package com.gitee.sop.index.service.validate.alipay;
import com.gitee.sop.index.common.ParamNames;
import com.gitee.sop.index.exception.SignException;
import com.gitee.sop.index.message.ErrorEnum;
import com.gitee.sop.index.service.validate.SignConfig;
import org.apache.commons.codec.binary.Base64;
@@ -71,17 +73,14 @@ public class AlipaySignature {
* @return
*/
public static String rsaSign(String content, String publicKey, String charset,
String signType) {
String signType) throws SignException {
if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
return rsaSign(content, publicKey, charset);
} else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
return rsa256Sign(content, publicKey, charset);
} else {
throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE_TYPE);
}
}
@@ -95,7 +94,7 @@ public class AlipaySignature {
* @return
*/
public static String rsa256Sign(String content, String privateKey,
String charset) {
String charset) throws SignException {
try {
PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
@@ -116,8 +115,7 @@ public class AlipaySignature {
return new String(Base64.encodeBase64(signed));
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}
@@ -131,7 +129,7 @@ public class AlipaySignature {
* @return
*/
public static String rsaSign(String content, String publicKey,
String charset) {
String charset) throws SignException {
try {
PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(publicKey.getBytes()));
@@ -151,16 +149,14 @@ public class AlipaySignature {
return new String(Base64.encodeBase64(signed));
} catch (InvalidKeySpecException ie) {
throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(ie);
// throw new AlipayApiException("RSA私钥格式不正确请检查是否正确配置了PKCS8格式的私钥", ie);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE_TYPE, ie);
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}
public static String rsaSign(Map<String, Object> params, String publicKey,
String charset, String signType) {
String charset, String signType) throws SignException {
String signContent = getSignContent(params);
return rsaSign(signContent, publicKey, charset, signType);
@@ -224,15 +220,14 @@ public class AlipaySignature {
}
public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
String charset) {
String sign = params.get("sign");
String charset) throws SignException {
String sign = params.get(ParamNames.SIGN_NAME);
String content = getSignCheckContentV1(params);
return rsaCheckContent(content, sign, publicKey, charset);
}
public static boolean rsaCheckV1(Map<String, String> params, String publicKey,
String charset, String signType) {
String charset, String signType) throws SignException {
String sign = params.get("sign");
String content = getSignCheckContentV1(params);
@@ -240,15 +235,15 @@ public class AlipaySignature {
}
public static boolean rsaCheckV2(Map<String, String> params, String publicKey,
String charset) {
String sign = params.get("sign");
String charset) throws SignException {
String sign = params.get(ParamNames.SIGN_NAME);
String content = getSignCheckContentV2(params);
return rsaCheckContent(content, sign, publicKey, charset);
}
public static boolean rsaCheckV2(Map<String, ?> params, String publicKey,
String charset, String signType) {
String charset, String signType) throws SignException {
String sign = String.valueOf(params.get("sign"));
String content = getSignCheckContentV2(params);
@@ -256,7 +251,7 @@ public class AlipaySignature {
}
public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
String signType) {
String signType) throws SignException {
if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
@@ -267,14 +262,13 @@ public class AlipaySignature {
return rsa256CheckContent(content, sign, publicKey, charset);
} else {
throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE_TYPE);
}
}
public static boolean rsa256CheckContent(String content, String sign, String publicKey,
String charset) {
String charset) throws SignException {
try {
PublicKey pubKey = getPublicKeyFromX509("RSA",
new ByteArrayInputStream(publicKey.getBytes()));
@@ -292,14 +286,12 @@ public class AlipaySignature {
return signature.verify(Base64.decodeBase64(sign.getBytes()));
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException(
// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}
public static boolean rsaCheckContent(String content, String sign, String publicKey,
String charset) {
String charset) throws SignException {
try {
PublicKey pubKey = getPublicKeyFromX509("RSA",
new ByteArrayInputStream(publicKey.getBytes()));
@@ -317,9 +309,7 @@ public class AlipaySignature {
return signature.verify(Base64.decodeBase64(sign.getBytes()));
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException(
// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}
@@ -359,13 +349,12 @@ public class AlipaySignature {
*/
public static String checkSignAndDecrypt(Map<String, String> params, String alipayPublicKey,
String cusPrivateKey, boolean isCheckSign,
boolean isDecrypt) {
String charset = params.get("charset");
String bizContent = params.get("biz_content");
boolean isDecrypt) throws SignException {
String charset = params.get(ParamNames.CHARSET_NAME);
String bizContent = params.get(ParamNames.BIZ_CONTENT_NAME);
if (isCheckSign) {
if (!rsaCheckV2(params, alipayPublicKey, charset)) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE);
}
}
@@ -398,13 +387,12 @@ public class AlipaySignature {
*/
public static String checkSignAndDecrypt(Map<String, String> params, String alipayPublicKey,
String cusPrivateKey, boolean isCheckSign,
boolean isDecrypt, String signType) {
boolean isDecrypt, String signType) throws SignException {
String charset = params.get("charset");
String bizContent = params.get("biz_content");
if (isCheckSign) {
if (!rsaCheckV2(params, alipayPublicKey, charset, signType)) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE);
}
}
@@ -438,7 +426,7 @@ public class AlipaySignature {
*/
public static String encryptAndSign(String bizContent, String alipayPublicKey,
String cusPrivateKey, String charset, boolean isEncrypt,
boolean isSign) {
boolean isSign) throws SignException {
StringBuilder sb = new StringBuilder();
if (StringUtils.isEmpty(charset)) {
charset = AlipayConstants.CHARSET_GBK;
@@ -491,7 +479,7 @@ public class AlipaySignature {
*/
public static String encryptAndSign(String bizContent, String alipayPublicKey,
String cusPrivateKey, String charset, boolean isEncrypt,
boolean isSign, String signType) {
boolean isSign, String signType) throws SignException {
StringBuilder sb = new StringBuilder();
if (StringUtils.isEmpty(charset)) {
charset = AlipayConstants.CHARSET_GBK;
@@ -534,7 +522,7 @@ public class AlipaySignature {
* @return 密文内容
*/
public static String rsaEncrypt(String content, String publicKey,
String charset) {
String charset) throws SignException {
try {
PublicKey pubKey = getPublicKeyFromX509(AlipayConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(publicKey.getBytes()));
@@ -564,9 +552,7 @@ public class AlipaySignature {
return StringUtils.isEmpty(charset) ? new String(encryptedData)
: new String(encryptedData, charset);
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException("EncryptContent = " + content + ",charset = " + charset,
// e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}
@@ -579,7 +565,7 @@ public class AlipaySignature {
* @return 明文内容
*/
public static String rsaDecrypt(String content, String publicKey,
String charset) {
String charset) throws SignException {
try {
PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(publicKey.getBytes()));
@@ -610,8 +596,7 @@ public class AlipaySignature {
return StringUtils.isEmpty(charset) ? new String(decryptedData)
: new String(decryptedData, charset);
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
// throw new AlipayApiException("EncodeContent = " + content + ",charset = " + charset, e);
throw new SignException(ErrorEnum.ISV_INVALID_SIGNATURE, e);
}
}

View File

@@ -1,9 +1,13 @@
package com.gitee.sop.index.service.validate.alipay;
import com.gitee.sop.index.common.ApiRequest;
import com.gitee.sop.index.common.ApiRequestContext;
import com.gitee.sop.index.common.ParamNames;
import com.gitee.sop.index.controller.param.ApiRequest;
import com.gitee.sop.index.exception.ApiException;
import com.gitee.sop.index.exception.SignException;
import com.gitee.sop.index.message.ErrorEnum;
import com.gitee.sop.index.message.ErrorMeta;
import com.gitee.sop.index.service.validate.Signer;
import lombok.extern.slf4j.Slf4j;
@@ -20,23 +24,29 @@ import java.util.Map;
public class AlipaySigner implements Signer {
@Override
public boolean checkSign(ApiRequest apiRequest, String secret) {
public boolean checkSign(ApiRequestContext apiRequestContext, String publicKey) {
ApiRequest apiRequest = apiRequestContext.getApiRequest();
// 服务端存的是公钥
String publicKey = secret;
String charset = apiRequest.getCharset();
String signType = apiRequest.getSign_type();
if (signType == null) {
throw ErrorEnum.ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE.getErrorMeta().getException();
throw new ApiException(ErrorEnum.ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE, apiRequestContext.getLocale());
}
if (charset == null) {
throw ErrorEnum.ISV_INVALID_CHARSET.getErrorMeta().getException();
throw new ApiException(ErrorEnum.ISV_INVALID_CHARSET, apiRequestContext.getLocale());
}
Map<String, String> params = buildParams(apiRequest);
try {
return AlipaySignature.rsaCheckV2(params, publicKey, charset, signType);
} catch (SignException e) {
ErrorEnum errorEnum = e.getErrorEnum();
ErrorMeta errorMeta = errorEnum.getErrorMeta();
log.error("验签错误, code={}, subCode={}, apiRequest={}",
errorMeta.getCode(), errorMeta.getSubCode(), apiRequest, e);
throw new ApiException(errorEnum, apiRequestContext.getLocale());
} catch (Exception e) {
log.error("RSA2解密异常, params={}", params, e);
return false;
log.error("验签未知错误, apiRequest={}", apiRequest, e);
throw new ApiException(ErrorEnum.ISV_INVALID_SIGNATURE, apiRequestContext.getLocale());
}
}

View File

@@ -0,0 +1,53 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.gitee.sop.index.util;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class RequestUtil {
private static final String IP_UNKNOWN = "unknown";
private static final String IP_LOCAL = "127.0.0.1";
private static final int IP_LEN = 15;
/**
* 获取客户端IP
*
* @param request request
* @return 返回ip
*/
public static String getIP(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (IP_LOCAL.equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (UnknownHostException e) {
// ignore
}
}
}
// 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > IP_LEN) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
}

View File

@@ -1,5 +1,7 @@
nacos.host=127.0.0.1:8848
dubbo.registry.address=zookeeper://localhost:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
dubbo.registry.address=nacos://${nacos.host}
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.sql.init.schema-locations=classpath:schema.sql
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}

View File

@@ -1,3 +1,28 @@
spring.profiles.active=dev
spring.application.name=sop-index
server.port=8081
####### dubbo config #######
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
# ### register config see:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/overview/
# ------
# nacos://localhost:8848 Cluster config:nacos://localhost:8848?backup=localshot:8846,localshot:8847
# zookeeper://localhost:2181 Cluster config:zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181
# redis://localhost:6379 Cluster config:redis://10.20.153.10:6379?backup=10.20.153.11:6379,10.20.153.12:6379
# ------
dubbo.registry.address=nacos://localhost:8848
####### dubbo config end #######
####### mysql config #######
mysql.host=127.0.0.1:3306
mysql.username=root
mysql.password=root
mysql.db=sop
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/${mysql.db}?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
####### mysql config end #######

View File

@@ -0,0 +1,16 @@
CREATE TABLE `api_info`
(
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`application` varchar(64) NOT NULL DEFAULT '' COMMENT '应用名称',
`api_name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名称',
`api_version` varchar(16) NOT NULL DEFAULT '1.0' COMMENT '版本号',
`interface_class_name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口class',
`method_name` varchar(128) NOT NULL DEFAULT '' COMMENT '方法名称',
`param_name` varchar(128) NOT NULL DEFAULT '' COMMENT '参数名称',
`param_type_name` varchar(128) NOT NULL DEFAULT '' COMMENT '参数类型名称',
is_permission tinyint(4) NOT NULL DEFAULT '0' COMMENT '接口是否需要授权访问',
is_need_Token tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要appAuthToken',
api_status tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1-启用,0-禁用',
`add_time` datetime NOT NULL DEFAULT null,
`update_time` datetime NOT NULL DEFAULT null
);

52
sop-registry/pom.xml Normal file
View File

@@ -0,0 +1,52 @@
<?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>
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
</parent>
<artifactId>sop-registry</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>logback-core</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

4
sop-registry/readme.md Normal file
View File

@@ -0,0 +1,4 @@
内置简单的注册中心,基于zookeeper,用来开发演示使用.
生产环境不可使用!
生产环境不可使用!

View File

@@ -0,0 +1,309 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitee.sop.registry;
import org.apache.zookeeper.server.ServerConfig;
import org.apache.zookeeper.server.ZooKeeperServerMain;
import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.ErrorHandler;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* from: https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java
* <p>
* Helper class to start an embedded instance of standalone (non clustered) ZooKeeper.
* <p>
* NOTE: at least an external standalone server (if not an ensemble) are recommended, even for
* {@link org.springframework.xd.dirt.server.singlenode.SingleNodeApplication}
*
* @author Patrick Peralta
* @author Mark Fisher
* @author David Turanski
*/
public class EmbeddedZooKeeper implements SmartLifecycle {
private static final Random RANDOM = new Random();
/**
* Logger.
*/
private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class);
/**
* ZooKeeper client port. This will be determined dynamically upon startup.
*/
private final int clientPort;
/**
* Whether to auto-start. Default is true.
*/
private boolean autoStartup = true;
/**
* Lifecycle phase. Default is 0.
*/
private int phase = 0;
/**
* Thread for running the ZooKeeper server.
*/
private volatile Thread zkServerThread;
/**
* ZooKeeper server.
*/
private volatile ZooKeeperServerMain zkServer;
/**
* {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread.
*/
private ErrorHandler errorHandler;
private boolean daemon = true;
/**
* Construct an EmbeddedZooKeeper with a random port.
*/
public EmbeddedZooKeeper() {
clientPort = findRandomPort(30000, 65535);
}
/**
* Construct an EmbeddedZooKeeper with the provided port.
*
* @param clientPort port for ZooKeeper server to bind to
*/
public EmbeddedZooKeeper(int clientPort, boolean daemon) {
this.clientPort = clientPort;
this.daemon = daemon;
}
/**
* Returns the port that clients should use to connect to this embedded server.
*
* @return dynamically determined client port
*/
public int getClientPort() {
return this.clientPort;
}
/**
* Specify whether to start automatically. Default is true.
*
* @param autoStartup whether to start automatically
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}
/**
* Specify the lifecycle phase for the embedded server.
*
* @param phase the lifecycle phase
*/
public void setPhase(int phase) {
this.phase = phase;
}
/**
* {@inheritDoc}
*/
@Override
public int getPhase() {
return this.phase;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isRunning() {
return (zkServerThread != null);
}
/**
* Start the ZooKeeper server in a background thread.
* <p>
* Register an error handler via {@link #setErrorHandler} in order to handle
* any exceptions thrown during startup or execution.
*/
@Override
public synchronized void start() {
if (zkServerThread == null) {
zkServerThread = new Thread(new ServerRunnable(), "ZooKeeper Server Starter");
zkServerThread.setDaemon(daemon);
zkServerThread.start();
}
}
/**
* Shutdown the ZooKeeper server.
*/
@Override
public synchronized void stop() {
if (zkServerThread != null) {
// The shutdown method is protected...thus this hack to invoke it.
// This will log an exception on shutdown; see
// https://issues.apache.org/jira/browse/ZOOKEEPER-1873 for details.
try {
Method shutdown = ZooKeeperServerMain.class.getDeclaredMethod("shutdown");
shutdown.setAccessible(true);
shutdown.invoke(zkServer);
} catch (Exception e) {
throw new RuntimeException(e);
}
// It is expected that the thread will exit after
// the server is shutdown; this will block until
// the shutdown is complete.
try {
zkServerThread.join(5000);
zkServerThread = null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("Interrupted while waiting for embedded ZooKeeper to exit");
// abandoning zk thread
zkServerThread = null;
}
}
}
/**
* Stop the server if running and invoke the callback when complete.
*/
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
/**
* Provide an {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. If none
* is provided, only error-level logging will occur.
*
* @param errorHandler the {@link ErrorHandler} to be invoked
*/
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Runnable implementation that starts the ZooKeeper server.
*/
private class ServerRunnable implements Runnable {
@Override
public void run() {
try {
Properties properties = new Properties();
File file = new File(System.getProperty("java.io.tmpdir")
+ File.separator + UUID.randomUUID());
file.deleteOnExit();
properties.setProperty("dataDir", file.getAbsolutePath());
properties.setProperty("clientPort", String.valueOf(clientPort));
QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig();
quorumPeerConfig.parseProperties(properties);
zkServer = new ZooKeeperServerMain();
ServerConfig configuration = new ServerConfig();
configuration.readFrom(quorumPeerConfig);
System.setProperty("zookeeper.admin.enableServer", "false");
zkServer.runFromConfig(configuration);
} catch (Exception e) {
if (errorHandler != null) {
errorHandler.handleError(e);
} else {
logger.error("Exception running embedded ZooKeeper", e);
}
}
}
}
/**
* Workaround for SocketUtils.findRandomPort() deprecation.
*
* @param min min port
* @param max max port
* @return a random generated available port
*/
private static int findRandomPort(int min, int max) {
if (min < 1024) {
throw new IllegalArgumentException("Max port shouldn't be less than 1024.");
}
if (max > 65535) {
throw new IllegalArgumentException("Max port shouldn't be greater than 65535.");
}
if (min > max) {
throw new IllegalArgumentException("Min port shouldn't be greater than max port.");
}
int port = 0;
int counter = 0;
// Workaround for legacy JDK doesn't support Random.nextInt(min, max).
List<Integer> randomInts = RANDOM.ints(min, max + 1)
.limit(max - min)
.mapToObj(Integer::valueOf)
.collect(Collectors.toList());
do {
if (counter > max - min) {
throw new IllegalStateException("Unable to find a port between " + min + "-" + max);
}
port = randomInts.get(counter);
counter++;
} while (isPortInUse(port));
return port;
}
private static boolean isPortInUse(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return false;
} catch (IOException e) {
// continue
}
return true;
}
}

View File

@@ -0,0 +1,12 @@
package com.gitee.sop.registry;
/**
* @author 六如
*/
public class RegistryMain {
public static void main(String[] args) {
int zkPort = 2181;
System.out.println("启动内置zookeeper注册中心(仅在开发环境下使用),port=" + zkPort);
new EmbeddedZooKeeper(zkPort, false).start();
}
}

View File

@@ -21,12 +21,6 @@
<version>5.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>

View File

@@ -1,16 +0,0 @@
package com.gitee.sop.springboot.starter.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 六如
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpenParam {
}

View File

@@ -1,14 +0,0 @@
package com.gitee.sop.springboot.starter.request;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author 六如
*/
@Data
public class IntId {
@NotNull(message = "id不能为空")
private Integer id;
}

View File

@@ -1,13 +0,0 @@
package com.gitee.sop.springboot.starter.request;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class LongId {
private Long id;
}

View File

@@ -1,7 +1,8 @@
package com.gitee.sop.springboot.starter.annotation;
package com.gitee.sop.support.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -9,6 +10,7 @@ import java.lang.annotation.Target;
/**
* @author tanghc
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@@ -24,16 +26,6 @@ public @interface Open {
*/
String version() default "1.0";
/**
* 忽略验证业务参数除外
*/
boolean ignoreValidate() default false;
/**
* 告诉网关是否对结果进行合并默认合并设置为false客户端将直接收到微服务端的结果
*/
boolean mergeResult() default true;
/**
* 指定接口是否需要授权才能访问可在admin中进行修改
*/

View File

@@ -1,9 +1,8 @@
package com.gitee.sop.springboot.starter.config;
package com.gitee.sop.support.config;
import com.gitee.sop.index.api.ApiRegisterService;
import com.gitee.sop.index.api.RegisterDTO;
import com.gitee.sop.springboot.starter.annotation.Open;
import com.gitee.sop.springboot.starter.annotation.OpenParam;
import com.gitee.sop.support.annotation.Open;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
@@ -20,7 +19,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
@@ -45,13 +43,23 @@ public class SopAutoConfiguration implements InitializingBean {
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(DubboService.class);
for (Object serviceObj : beanMap.values()) {
Class<?> objClass = serviceObj.getClass();
ReflectionUtils.doWithMethods(objClass, method -> {
regApi(appName, objClass, method);
Class<?> interfaceClass = findInterfaceClass(serviceObj);
ReflectionUtils.doWithMethods(interfaceClass, method -> {
regApi(appName, interfaceClass, method);
});
}
}
private void regApi(String appName, Class<?> clazz, Method method) {
private Class<?> findInterfaceClass(Object serviceObj) {
Class<?> objClass = serviceObj.getClass();
Class<?>[] interfaces = objClass.getInterfaces();
if (interfaces.length == 0) {
throw new RuntimeException("Dubbo接口必须要实现接口, class=" + objClass.getName());
}
return interfaces[0];
}
private void regApi(String appName, Class<?> interfaceClass, Method method) {
Open open = AnnotationUtils.getAnnotation(method, Open.class);
if (open == null) {
return;
@@ -64,46 +72,25 @@ public class SopAutoConfiguration implements InitializingBean {
registerDTO.setApplication(appName);
registerDTO.setApiName(open.value());
registerDTO.setApiVersion(open.version());
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length == 0) {
throw new RuntimeException("Dubbo接口必须要实现接口, class=" + clazz.getName());
}
Optional<Class<?>> interfaceClassOpt = filterInterfaceClass(method, interfaces);
if (!interfaceClassOpt.isPresent()) {
throw new RuntimeException("找不到dubbo接口, clazz=" + clazz.getName());
}
registerDTO.setInterfaceClassName(interfaceClassOpt.get().getName());
registerDTO.setInterfaceClassName(interfaceClass.getName());
registerDTO.setMethodName(method.getName());
registerDTO.setParamName(paramName);
registerDTO.setParamTypeName(paramTypeName);
registerDTO.setIsIgnoreValidate(parseBoolean(open.ignoreValidate()));
registerDTO.setIsPermission(parseBoolean(open.permission()));
registerDTO.setIsNeedToken(parseBoolean(open.needToken()));
log.info("注册开放接口, apiInfo={}", registerDTO);
try {
apiRegisterService.register(registerDTO);
} catch (Exception e) {
log.error("接口注册失败, registerDTO={}, method={}", registerDTO, method, e);
}
}
private Optional<Parameter> filterParameter(Parameter[] parameters) {
if (ObjectUtils.isEmpty(parameters)) {
return Optional.empty();
}
Optional<Parameter> first = Stream.of(parameters)
.filter(parameter -> AnnotationUtils.getAnnotation(parameter, OpenParam.class) != null)
.findFirst();
return first.isPresent() ? first : Optional.of(parameters[0]);
}
private Optional<Class<?>> filterInterfaceClass(Method method, Class<?>[] interfaces) {
return Stream.of(interfaces)
.filter(inter -> {
for (Method interMethod : inter.getMethods()) {
if (interMethod.getName().equals(method.getName())) {
return true;
}
}
return false;
})
.findFirst();
return Optional.of(parameters[0]);
}
private int parseBoolean(boolean b) {

View File

@@ -0,0 +1,10 @@
package com.gitee.sop.support.context;
/**
* @author 六如
*/
public class OpenContext {
}

View File

@@ -1,6 +1,5 @@
package com.gitee.sop.springboot.starter.request;
package com.gitee.sop.support.request;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import org.springframework.core.GenericTypeResolver;
@@ -90,13 +89,4 @@ public class OpenRequest<T> implements Serializable {
*/
private String biz_content;
/**
* 将biz_content转成对象
*
* @return 返回对象
*/
public T toJavaObject() {
return JSON.parseObject(biz_content, clazz);
}
}

View File

@@ -1,3 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gitee.sop.springboot.starter.config.SopAutoConfiguration
com.gitee.sop.support.config.SopAutoConfiguration

View File

@@ -1 +1 @@
com.gitee.sop.springboot.starter.config.SopAutoConfiguration
com.gitee.sop.support.config.SopAutoConfiguration

13
sop.sql
View File

@@ -12,14 +12,13 @@ CREATE TABLE `api_info` (
`interface_class_name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口class',
`method_name` varchar(128) NOT NULL DEFAULT '' COMMENT '方法名称',
`param_class_name` varchar(128) NOT NULL DEFAULT '' COMMENT '参数class',
is_ignore_validate tinyint(4) NOT NULL DEFAULT '0' COMMENT '忽略验证',
is_permission tinyint(4) NOT NULL DEFAULT '0' COMMENT '接口是否需要授权访问',
is_need_Token tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要appAuthToken',
status tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1-启用,0-禁用',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`is_permission` tinyint(4) NOT NULL DEFAULT '0' COMMENT '接口是否需要授权访问',
`is_need_Token` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要appAuthToken',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,1-启用,0-禁用',
`add_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_apiname_version` (`api_name`, api_version) USING BTREE
UNIQUE KEY `uk_apiname_version` (`api_name`,`api_version`) USING BTREE
) ENGINE=InnoDB COMMENT='接口信息表';