Skip to content

Commit

Permalink
Merge pull request #55 from xezzon/feature/issue-54
Browse files Browse the repository at this point in the history
开放平台转发请求 Close #54
  • Loading branch information
xezzon authored Dec 12, 2024
2 parents fe3efd7 + 62bab7c commit b55b345
Show file tree
Hide file tree
Showing 23 changed files with 529 additions and 293 deletions.
Original file line number Diff line number Diff line change
@@ -1,43 +1,132 @@
package io.github.xezzon.zeroweb.call;

import static com.google.auth.http.AuthHttpConstants.BEARER;
import static org.springframework.web.bind.annotation.RequestMethod.*;

import com.auth0.jwt.JWT;
import io.github.xezzon.zeroweb.ZerowebOpenConstant;
import jakarta.servlet.http.HttpServletResponse;
import io.github.xezzon.zeroweb.auth.JwtFilter;
import io.github.xezzon.zeroweb.subscription.domain.Subscription;
import io.github.xezzon.zeroweb.subscription.service.ISubscriptionService4Call;
import io.github.xezzon.zeroweb.third_party_app.service.IThirdPartyAppService4Call;
import jakarta.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;

/**
* 订阅服务调用记录
* @author xezzon
*/
@RestController
@RequestMapping("/subscription-call")
@RequestMapping("/call")
public class SubscriptionCallController {

private final SubscriptionCallService subscriptionCallService;
private final ISubscriptionService4Call subscriptionService;
private final IThirdPartyAppService4Call thirdPartyAppService;

public SubscriptionCallController(SubscriptionCallService subscriptionCallService) {
this.subscriptionCallService = subscriptionCallService;
public SubscriptionCallController(
ISubscriptionService4Call subscriptionService,
IThirdPartyAppService4Call thirdPartyAppService
) {
this.subscriptionService = subscriptionService;
this.thirdPartyAppService = thirdPartyAppService;
}

/**
* 转发 GET 请求
*/
@GetMapping(value = "/{openapiCode}")
public ResponseEntity<byte[]> forwardForSafe(
@PathVariable String openapiCode,
@RequestBody(required = false) byte[] body,
@RequestHeader(ZerowebOpenConstant.ACCESS_KEY_HEADER) String accessKey,
@RequestHeader(ZerowebOpenConstant.TIMESTAMP_HEADER) Instant timestamp,
@RequestHeader(ZerowebOpenConstant.SIGNATURE_HEADER) String signature,
@RequestHeader HttpHeaders headers,
HttpServletRequest request
) {
Map<String, String[]> parameterMap = request.getParameterMap();
return forward(openapiCode, body, accessKey, timestamp, signature, headers, parameterMap);
}

@PostMapping("/validate")
public void validateCall(
@RequestBody byte[] body,
@RequestParam("path") String path,
/**
* 转发非 GET 请求
*/
@RequestMapping(value = "/{openapiCode}", method = {POST, PUT, DELETE, PATCH})
public ResponseEntity<byte[]> forwardForUnsafe(
@PathVariable String openapiCode,
@RequestBody(required = false) byte[] body,
@RequestHeader(ZerowebOpenConstant.ACCESS_KEY_HEADER) String accessKey,
@RequestHeader(ZerowebOpenConstant.TIMESTAMP_HEADER) Instant timestamp,
@RequestHeader(ZerowebOpenConstant.SIGNATURE_HEADER) String signature,
HttpServletResponse response
@RequestHeader HttpHeaders headers,
HttpServletRequest request
) {
Map<String, String[]> parameterMap = request.getParameterMap();
return forward(openapiCode, body, accessKey, timestamp, signature, headers, parameterMap);
}

/**
* 转发请求
* @param openapiCode 对外路径
* @param body 请求体
* @param originalHeaders 请求头
* @param accessKey AccessKey(请求头)
* @param timestamp 时间戳(请求头)
* @param signature 签名(请求头)
* @param parameterMap 原始请求参数
* @return 响应体
*/
private ResponseEntity<byte[]> forward(
String openapiCode, byte[] body, String accessKey, Instant timestamp, String signature,
HttpHeaders originalHeaders, Map<String, String[]> parameterMap
) {
/* 所有校验通过后发放令牌 */
String jwt = subscriptionCallService.signJwt(accessKey, path, body, signature, timestamp);
response.setHeader(HttpHeaders.AUTHORIZATION, BEARER + " " + jwt);
/* 签发JWT */
String jwt = thirdPartyAppService.signJwt(accessKey, body, signature, timestamp);
/* 获取相应的后端地址 */
String appId = JWT.decode(jwt).getSubject();
Subscription subscription = subscriptionService.getSubscription(appId, openapiCode);
/* 转发请求 */
originalHeaders.remove(ZerowebOpenConstant.TIMESTAMP_HEADER.toLowerCase());
originalHeaders.remove(ZerowebOpenConstant.SIGNATURE_HEADER.toLowerCase());
originalHeaders.remove(JwtFilter.PUBLIC_KEY_HEADER.toLowerCase());
return RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (req, resp) -> {
})
.build()
// 请求方法由对外接口定义
.method(HttpMethod.valueOf(subscription.getOpenapi().getHttpMethod().getCode()))
// 请求路径由目标地址定义
.uri(subscription.getOpenapi().getDestination(), uri -> {
// 请求参数与路径参数都由原始请求的请求参数提供
parameterMap.forEach(uri::queryParam);
Map<String, String> pathVariableMap = parameterMap.entrySet().parallelStream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> String.join(",", entry.getValue())
));
return uri.build(pathVariableMap);
})
// 请求头由原始请求的请求头提供,但需要移除签名和时间戳
.headers(headers -> headers.addAll(originalHeaders))
// 认证头由本系统签发的JWT提供
.header(HttpHeaders.AUTHORIZATION, BEARER + " " + jwt)
// 请求体由原始请求的请求体提供
.body(body)
.retrieve()
.toEntity(byte[].class);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ protected Page<Openapi> pageList(ODataQueryOption odata) {
protected void modifyOpenapi(Openapi openapi) {
this.checkRepeat(openapi);
Openapi entity = openapiDAO.get().getReferenceById(openapi.getId());
if (entity.isPublished()) {
if (entity.isPublished()
&& openapi.getCode() != null
&& !Objects.equals(entity.getCode(), openapi.getCode())
) {
// 已发布的接口不能修改编码(即对外的路径)
throw new PublishedOpenapiCannotBeModifyException();
}
openapiDAO.partialUpdate(openapi);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@
import io.github.xezzon.tao.trait.From;
import io.github.xezzon.tao.trait.Into;
import io.github.xezzon.zeroweb.common.validator.Alphanumeric;
import lombok.Getter;
import lombok.Setter;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
* @param code 接口编码
* @param destination 后端地址
* @param httpMethod 请求接口的HTTP方法
* @author xezzon
*/
@Getter
@Setter
public class AddOpenapiReq implements Into<Openapi> {

/**
* 接口编码
*/
@Alphanumeric
private String code;
public record AddOpenapiReq(
@Alphanumeric String code,
String destination,
HttpMethod httpMethod
) implements Into<Openapi> {

@Override
public Openapi into() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.github.xezzon.zeroweb.openapi.domain;

import io.github.xezzon.tao.dict.IDict;

/**
* 开放平台允许使用的HTTP方法
*/
public enum HttpMethod implements IDict {

GET,
POST,
PUT,
DELETE,
PATCH,
;

@Override
public String getTag() {
return this.getClass().getSimpleName();
}

@Override
public String getCode() {
return this.name();
}

@Override
public String getLabel() {
return this.name();
}

@Override
public int getOrdinal() {
return this.ordinal();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
/**
* @param id 对外接口标识
* @param code 接口编码
* @param destination 后端地址
* @param httpMethod 请求接口的HTTP方法
* @author xezzon
*/
public record ModifyOpenapiReq(
String id,
@Alphanumeric
String code
@Alphanumeric String code,
String destination,
HttpMethod httpMethod
) implements Into<Openapi> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ public class Openapi implements IEntity<String> {
String id;
/**
* 接口编码
* 即第三方接口调用的路径
*/
@Column(name = CODE_COLUMN, nullable = false, unique = true)
String code;
/**
* 后端地址
* 即该接口应该转发到的后端地址
*/
@Column(name = "destination", nullable = false, length = 2048)
String destination;
/**
* 请求接口的HTTP方法
*/
@Column(name = "http_method", nullable = false, length = 16)
@Enumerated(EnumType.STRING)
HttpMethod httpMethod;
/**
* 接口状态
*/
Expand Down
Loading

0 comments on commit b55b345

Please sign in to comment.