diff --git a/pom.xml b/pom.xml index 9dd70299..7328c53d 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ sop-index sop-spring-boot-starter + sop-registry @@ -119,15 +120,11 @@ - net.oschina.durcframework + io.gitee.durcframework fastmybatis-spring-boot-starter ${fastmybatis.version} - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus.version} - + io.springfox springfox-swagger2 diff --git a/sop-example/sop-story/pom.xml b/sop-example/sop-story/pom.xml index 9dcd2551..c57f7b2a 100644 --- a/sop-example/sop-story/pom.xml +++ b/sop-example/sop-story/pom.xml @@ -54,6 +54,16 @@ spring-boot-starter-test test + + + + org.apache.dubbo + dubbo-dependencies-zookeeper-curator5 + ${dubbo.version} + pom + provided + + org.projectlombok lombok diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/StoryService.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/StoryService.java index ed727055..556f885e 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/StoryService.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/StoryService.java @@ -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); } diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/impl/StoreyServiceImpl.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/impl/StoreyServiceImpl.java index b58edbeb..84c3923a 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/impl/StoreyServiceImpl.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/open/impl/StoreyServiceImpl.java @@ -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(); diff --git a/sop-example/sop-story/src/main/resources/application-dev.properties b/sop-example/sop-story/src/main/resources/application-dev.properties index 641cfe3d..ff692cf5 100644 --- a/sop-example/sop-story/src/main/resources/application-dev.properties +++ b/sop-example/sop-story/src/main/resources/application-dev.properties @@ -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 diff --git a/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/IsvService.java b/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/IsvService.java new file mode 100644 index 00000000..a75bf5a1 --- /dev/null +++ b/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/IsvService.java @@ -0,0 +1,8 @@ +package com.gitee.sop.index.api; + +/** + * @author 六如 + */ +public interface IsvService { + void refresh(String appId); +} diff --git a/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/RegisterDTO.java b/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/RegisterDTO.java index dbcb55d9..7cb03023 100644 --- a/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/RegisterDTO.java +++ b/sop-index/sop-index-api/src/main/java/com/gitee/sop/index/api/RegisterDTO.java @@ -25,8 +25,6 @@ public class RegisterDTO implements Serializable { private String paramTypeName; - private Integer isIgnoreValidate; - private Integer isPermission; private Integer isNeedToken; diff --git a/sop-index/sop-index-service/pom.xml b/sop-index/sop-index-service/pom.xml index ca2f91c0..4adf9f15 100644 --- a/sop-index/sop-index-service/pom.xml +++ b/sop-index/sop-index-service/pom.xml @@ -44,6 +44,19 @@ 0.2.1 + + io.gitee.durcframework + fastmybatis-spring-boot-starter + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-jdbc + commons-codec @@ -65,6 +78,24 @@ test + + + + + com.h2database + h2 + provided + + + + + org.apache.dubbo + dubbo-dependencies-zookeeper-curator5 + ${dubbo.version} + pom + provided + + org.projectlombok lombok diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiInfoDTO.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiInfoDTO.java index 39eac51b..7d091520 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiInfoDTO.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiInfoDTO.java @@ -31,6 +31,8 @@ public class ApiInfoDTO implements Serializable { private Integer isNeedToken; + private Integer status; + public String buildApiNameVersion() { return apiName + apiVersion; } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequest.java similarity index 98% rename from sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java rename to sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequest.java index 05ac2dc7..e3bf7a73 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequest.java @@ -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; diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequestContext.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequestContext.java new file mode 100644 index 00000000..104261b7 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiRequestContext.java @@ -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; + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiResponse.java similarity index 79% rename from sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java rename to sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiResponse.java index d43dc865..5a3955fd 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/ApiResponse.java @@ -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; + } + } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/RouteContext.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/RouteContext.java new file mode 100644 index 00000000..8cc20fc1 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/RouteContext.java @@ -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; + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/StatusEnum.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/StatusEnum.java new file mode 100644 index 00000000..75a03b20 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/common/StatusEnum.java @@ -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; + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/config/IndexConfig.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/config/IndexConfig.java index 0588ed9f..52c2b3a5 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/config/IndexConfig.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/config/IndexConfig.java @@ -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); + } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/IndexController.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/IndexController.java index a8cd8dbd..c8aa0a32 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/IndexController.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/controller/IndexController.java @@ -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); } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/entity/ApiInfo.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/entity/ApiInfo.java new file mode 100644 index 00000000..82674b4f --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/entity/ApiInfo.java @@ -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; + + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/mapper/ApiInfoMapper.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/mapper/ApiInfoMapper.java new file mode 100644 index 00000000..952ade8f --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dao/mapper/ApiInfoMapper.java @@ -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 { + + default ApiInfo getByNameVersion(String apiName, String apiVersion) { + return this.query() + .eq(ApiInfo::getApiName, apiName) + .eq(ApiInfo::getApiVersion, apiVersion) + .get(); + } + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dubbo/ApiRegisterServiceImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dubbo/ApiRegisterServiceImpl.java deleted file mode 100644 index 835398cf..00000000 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/dubbo/ApiRegisterServiceImpl.java +++ /dev/null @@ -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); - } -} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ApiException.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ApiException.java index 87c2c4cf..0a6c885c 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ApiException.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ApiException.java @@ -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); - } - return error; + public IError getError() { + return errorEnum.getErrorMeta().getError(locale, params); + } + + 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; } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ControllerGlobalExceptionHandler.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ControllerGlobalExceptionHandler.java new file mode 100644 index 00000000..600d2c39 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ControllerGlobalExceptionHandler.java @@ -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 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 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 fieldErrors = e.getFieldErrors(); + List msgList = new ArrayList<>(); + for (FieldError fieldError : fieldErrors) { + msgList.add(fieldError.getDefaultMessage()); + } + return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", msgList)); + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ExceptionExecutor.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ExceptionExecutor.java new file mode 100644 index 00000000..82bc68b0 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/ExceptionExecutor.java @@ -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); + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/SignException.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/SignException.java new file mode 100644 index 00000000..66d545c3 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/SignException.java @@ -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; + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/impl/ExceptionExecutorImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/impl/ExceptionExecutorImpl.java new file mode 100644 index 00000000..3833b176 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/exception/impl/ExceptionExecutorImpl.java @@ -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 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 findErrorMsg(String text) { + Pattern pattern = Pattern.compile("'([^']*)'"); + Matcher matcher = pattern.matcher(text); + Set msgList = new LinkedHashSet<>(); + while (matcher.find()) { + msgList.add(matcher.group(1)); + } + return msgList; + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorEnum.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorEnum.java index 00b44861..61823a0c 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorEnum.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorEnum.java @@ -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); diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorFactory.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorFactory.java index a80f4fd4..c78c444b 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorFactory.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorFactory.java @@ -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 baseNamesSet = new HashSet<>(); baseNamesSet.add(I18N_OPEN_ERROR); - if (!isvModules.isEmpty()) { + if (!CollectionUtils.isEmpty(isvModules)) { baseNamesSet.addAll(isvModules); } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorMeta.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorMeta.java index d86dd938..de53f2ab 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorMeta.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/message/ErrorMeta.java @@ -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() { diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ApiService.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ApiService.java index 272fa7b6..6338ba14 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ApiService.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ApiService.java @@ -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; }); } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java index 73ac1647..b0fede74 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java @@ -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 reference = new ReferenceConfig<>(); - reference.setGeneric("true"); + reference.setGeneric(TRUE); reference.setApplication(applicationConfig); reference.setInterface(interfaceName); reference.setTimeout(timeout); diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/RouteService.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/RouteService.java index 8172f433..41ddb655 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/RouteService.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/RouteService.java @@ -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( - apiInfo.getInterfaceClassName(), - apiInfo.getMethodName(), - new String[]{apiInfo.getParamTypeName()}, - new Object[]{buildInvokeParam(apiRequest, apiInfo)} - ); - } catch (Exception e) { - error = e; - log.error("请求服务出错, apiRequest={}, apiInfo={}", apiInfo, apiInfo, e); - return buildApiResponse(e); - } finally { - afterRoute(result, apiRequest, error); - } + private ApiResponse doRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfo) { + ApiRequest apiRequest = apiRequestContext.getApiRequest(); + Object result = genericServiceInvoker.invoke( + apiInfo.getInterfaceClassName(), + apiInfo.getMethodName(), + buildParamType(apiInfo), + buildInvokeParam(apiRequest, apiInfo) + ); 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 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 findErrorMsg(String text) { - Pattern pattern = Pattern.compile("'([^']*)'"); - Matcher matcher = pattern.matcher(text); - Set 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) { - - } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ValidateService.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ValidateService.java deleted file mode 100644 index c5a9ae4a..00000000 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/ValidateService.java +++ /dev/null @@ -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); - - } -} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/ApiRegisterServiceImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/ApiRegisterServiceImpl.java new file mode 100644 index 00000000..41b8578e --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/ApiRegisterServiceImpl.java @@ -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); + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/IsvServiceImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/IsvServiceImpl.java new file mode 100644 index 00000000..a2001feb --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/dubbo/IsvServiceImpl.java @@ -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); + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IpBlacklistManager.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IpBlacklistManager.java new file mode 100644 index 00000000..b34cc856 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IpBlacklistManager.java @@ -0,0 +1,12 @@ +package com.gitee.sop.index.service.manager; + +/** + * IP黑名单管理 + * + * @author 六如 + */ +public interface IpBlacklistManager { + + boolean contains(String ip); + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvApiPermissionManager.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvApiPermissionManager.java new file mode 100644 index 00000000..9832e949 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvApiPermissionManager.java @@ -0,0 +1,12 @@ +package com.gitee.sop.index.service.manager; + +/** + * isv接口授权管理 + * + * @author 六如 + */ +public interface IsvApiPermissionManager { + + boolean hasPermission(String appId, String apiNameVersion); + +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvManager.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvManager.java new file mode 100644 index 00000000..164900b7 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/IsvManager.java @@ -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); +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/dto/IsvDTO.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/dto/IsvDTO.java new file mode 100644 index 00000000..f8461443 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/dto/IsvDTO.java @@ -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; +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IpBlacklistManagerImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IpBlacklistManagerImpl.java new file mode 100644 index 00000000..e2e151ae --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IpBlacklistManagerImpl.java @@ -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; + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IsvApiPermissionManagerImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IsvApiPermissionManagerImpl.java new file mode 100644 index 00000000..98dae48b --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/IsvApiPermissionManagerImpl.java @@ -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; + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/LocalIsvManagerImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/LocalIsvManagerImpl.java new file mode 100644 index 00000000..a989179a --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/LocalIsvManagerImpl.java @@ -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 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() { + + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/RedisIsvManagerImpl.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/RedisIsvManagerImpl.java new file mode 100644 index 00000000..66597825 --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/manager/impl/RedisIsvManagerImpl.java @@ -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) { + + } +} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java index 3cd165db..9de2ea62 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java @@ -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); } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java index 2842d268..334e0241 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java @@ -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 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 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 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(ApiRequest param) { - String clientSign = param.getSign(); - try { - if (StringUtils.isEmpty(clientSign)) { - throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.takeNameVersion(), ParamNames.SIGN_NAME); - } -// 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); + 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()); + } + // 错误的sign + 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 + } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Signer.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Signer.java index 06f95192..79f65edc 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Signer.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Signer.java @@ -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); } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java deleted file mode 100644 index 8a7d00a6..00000000 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java +++ /dev/null @@ -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); -} diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Validator.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Validator.java index 47aac94c..8ab9ca27 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Validator.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/Validator.java @@ -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); } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java index 97aeba55..ad66c3fc 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java @@ -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 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 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 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 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 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 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 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); } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java index ab44e400..df40dc9f 100644 --- a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java @@ -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 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()); } } diff --git a/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/util/RequestUtil.java b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/util/RequestUtil.java new file mode 100644 index 00000000..fe4ae80f --- /dev/null +++ b/sop-index/sop-index-service/src/main/java/com/gitee/sop/index/util/RequestUtil.java @@ -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; + } +} diff --git a/sop-index/sop-index-service/src/main/resources/application-dev.properties b/sop-index/sop-index-service/src/main/resources/application-dev.properties index 171e376d..2b4ffeb8 100644 --- a/sop-index/sop-index-service/src/main/resources/application-dev.properties +++ b/sop-index/sop-index-service/src/main/resources/application-dev.properties @@ -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} diff --git a/sop-index/sop-index-service/src/main/resources/application.properties b/sop-index/sop-index-service/src/main/resources/application.properties index b92b78f3..d86b0ca5 100644 --- a/sop-index/sop-index-service/src/main/resources/application.properties +++ b/sop-index/sop-index-service/src/main/resources/application.properties @@ -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 ####### diff --git a/sop-index/sop-index-service/src/main/resources/schema.sql b/sop-index/sop-index-service/src/main/resources/schema.sql new file mode 100644 index 00000000..de994a50 --- /dev/null +++ b/sop-index/sop-index-service/src/main/resources/schema.sql @@ -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 +); diff --git a/sop-registry/pom.xml b/sop-registry/pom.xml new file mode 100644 index 00000000..18f3fb08 --- /dev/null +++ b/sop-registry/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + com.gitee.sop + sop-parent + 5.0.0-SNAPSHOT + + + sop-registry + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.dubbo + dubbo-dependencies-zookeeper-curator5 + ${dubbo.version} + pom + + + logback-classic + ch.qos.logback + + + logback-core + ch.qos.logback + + + log4j + log4j + + + slf4j-log4j12 + org.slf4j + + + + + + diff --git a/sop-registry/readme.md b/sop-registry/readme.md new file mode 100644 index 00000000..920f680f --- /dev/null +++ b/sop-registry/readme.md @@ -0,0 +1,4 @@ +内置简单的注册中心,基于zookeeper,用来开发演示使用. + +生产环境不可使用! +生产环境不可使用! diff --git a/sop-registry/src/main/java/com/gitee/sop/registry/EmbeddedZooKeeper.java b/sop-registry/src/main/java/com/gitee/sop/registry/EmbeddedZooKeeper.java new file mode 100644 index 00000000..562f3407 --- /dev/null +++ b/sop-registry/src/main/java/com/gitee/sop/registry/EmbeddedZooKeeper.java @@ -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 + *

+ * Helper class to start an embedded instance of standalone (non clustered) ZooKeeper. + *

+ * 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. + *

+ * 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 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; + } +} diff --git a/sop-registry/src/main/java/com/gitee/sop/registry/RegistryMain.java b/sop-registry/src/main/java/com/gitee/sop/registry/RegistryMain.java new file mode 100644 index 00000000..27c3fb02 --- /dev/null +++ b/sop-registry/src/main/java/com/gitee/sop/registry/RegistryMain.java @@ -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(); + } +} diff --git a/sop-spring-boot-starter/pom.xml b/sop-spring-boot-starter/pom.xml index 3dab4fa5..2b1cefea 100644 --- a/sop-spring-boot-starter/pom.xml +++ b/sop-spring-boot-starter/pom.xml @@ -21,12 +21,6 @@ 5.0.0-SNAPSHOT - - javax.validation - validation-api - 2.0.1.Final - - org.springframework.boot spring-boot-starter diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/OpenParam.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/OpenParam.java deleted file mode 100644 index 32a42a86..00000000 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/OpenParam.java +++ /dev/null @@ -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 { -} diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/IntId.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/IntId.java deleted file mode 100644 index 20021246..00000000 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/IntId.java +++ /dev/null @@ -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; -} diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/LongId.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/LongId.java deleted file mode 100644 index 5e9ea80d..00000000 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/LongId.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.gitee.sop.springboot.starter.request; - -import lombok.Data; - -/** - * @author 六如 - */ -@Data -public class LongId { - - private Long id; - -} diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/Open.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/annotation/Open.java similarity index 69% rename from sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/Open.java rename to sop-spring-boot-starter/src/main/java/com/gitee/sop/support/annotation/Open.java index 23f8c508..f4fe30b4 100644 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/annotation/Open.java +++ b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/annotation/Open.java @@ -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中进行修改 */ diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/config/SopAutoConfiguration.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/config/SopAutoConfiguration.java similarity index 63% rename from sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/config/SopAutoConfiguration.java rename to sop-spring-boot-starter/src/main/java/com/gitee/sop/support/config/SopAutoConfiguration.java index ff76c965..b9eb4942 100644 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/config/SopAutoConfiguration.java +++ b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/config/SopAutoConfiguration.java @@ -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 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> 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); - apiRegisterService.register(registerDTO); + try { + apiRegisterService.register(registerDTO); + } catch (Exception e) { + log.error("接口注册失败, registerDTO={}, method={}", registerDTO, method, e); + } } private Optional filterParameter(Parameter[] parameters) { if (ObjectUtils.isEmpty(parameters)) { return Optional.empty(); } - Optional first = Stream.of(parameters) - .filter(parameter -> AnnotationUtils.getAnnotation(parameter, OpenParam.class) != null) - .findFirst(); - return first.isPresent() ? first : Optional.of(parameters[0]); - } - - private Optional> 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) { diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/context/OpenContext.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/context/OpenContext.java new file mode 100644 index 00000000..fe2d6abf --- /dev/null +++ b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/context/OpenContext.java @@ -0,0 +1,10 @@ +package com.gitee.sop.support.context; + +/** + * @author 六如 + */ +public class OpenContext { + + + +} diff --git a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/OpenRequest.java b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/request/OpenRequest.java similarity index 88% rename from sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/OpenRequest.java rename to sop-spring-boot-starter/src/main/java/com/gitee/sop/support/request/OpenRequest.java index cb77ba2d..9b183ced 100644 --- a/sop-spring-boot-starter/src/main/java/com/gitee/sop/springboot/starter/request/OpenRequest.java +++ b/sop-spring-boot-starter/src/main/java/com/gitee/sop/support/request/OpenRequest.java @@ -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 implements Serializable { */ private String biz_content; - /** - * 将biz_content转成对象 - * - * @return 返回对象 - */ - public T toJavaObject() { - return JSON.parseObject(biz_content, clazz); - } - } diff --git a/sop-spring-boot-starter/src/main/resources/META-INF/spring.factories b/sop-spring-boot-starter/src/main/resources/META-INF/spring.factories index 90bb5b32..af89bfa7 100644 --- a/sop-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/sop-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,3 +1,3 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.gitee.sop.springboot.starter.config.SopAutoConfiguration +com.gitee.sop.support.config.SopAutoConfiguration diff --git a/sop-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sop-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 13c1a04f..1f8ef306 100644 --- a/sop-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/sop-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.gitee.sop.springboot.starter.config.SopAutoConfiguration +com.gitee.sop.support.config.SopAutoConfiguration diff --git a/sop.sql b/sop.sql index cffc83d2..d78f6fb2 100644 --- a/sop.sql +++ b/sop.sql @@ -5,21 +5,20 @@ USE `sop`; -- 5.0 CREATE TABLE `api_info` ( - `id` bigint(20) unsigned NOT NULL 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_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, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_apiname_version` (`api_name`, api_version) USING BTREE + `id` bigint(20) unsigned NOT NULL 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_class_name` varchar(128) NOT NULL DEFAULT '' COMMENT '参数class', + `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 ) ENGINE=InnoDB COMMENT='接口信息表';