RtaController.java 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package com.tzld.rta.controller.rta;
  2. import com.tzld.rta.codec.RtaProtobufCodec;
  3. import com.tzld.rta.model.RtaRequestModel;
  4. import com.tzld.rta.model.RtaResponseModel;
  5. import com.tzld.rta.service.RtaService;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.http.ResponseEntity;
  10. import org.springframework.web.bind.annotation.*;
  11. import javax.servlet.http.HttpServletRequest;
  12. import java.io.ByteArrayOutputStream;
  13. import java.io.InputStream;
  14. /**
  15. * 腾讯RTA接入Controller
  16. * <p>
  17. * 协议要求:
  18. * - HTTP/1.1 长连接(Keep-Alive)
  19. * - POST 方法,Content-Type: application/x-protobuf
  20. * - 响应始终返回 HTTP 200(异常时也需 200)
  21. * - 超时时间: 60ms(含网络传输)
  22. * - 超时率/错误率需低于 2%
  23. * <p>
  24. * 接口地址: POST /rta/bid
  25. */
  26. @Slf4j
  27. @RestController
  28. @RequestMapping("/rta")
  29. public class RtaController {
  30. private static final String CONTENT_TYPE_PROTOBUF = "application/x-protobuf";
  31. @Autowired
  32. private RtaService rtaService;
  33. /**
  34. * RTA 竞价接口
  35. * 腾讯广告系统通过此接口实时查询设备是否参竞
  36. *
  37. * @param httpRequest HTTP请求
  38. * @return protobuf 格式响应
  39. */
  40. @PostMapping(
  41. value = "/bid",
  42. consumes = {CONTENT_TYPE_PROTOBUF, MediaType.APPLICATION_OCTET_STREAM_VALUE, MediaType.ALL_VALUE},
  43. produces = CONTENT_TYPE_PROTOBUF
  44. )
  45. public ResponseEntity<byte[]> bid(HttpServletRequest httpRequest) {
  46. long startTime = System.currentTimeMillis();
  47. try {
  48. // 1. 读取请求体
  49. byte[] requestBytes = readRequestBody(httpRequest);
  50. if (requestBytes == null || requestBytes.length == 0) {
  51. return buildEmptyResponse();
  52. }
  53. // 2. 解码 protobuf 请求
  54. RtaRequestModel request = RtaProtobufCodec.decodeRequest(requestBytes);
  55. // 3. 业务处理
  56. RtaResponseModel response = rtaService.process(request);
  57. // 4. 编码 protobuf 响应
  58. byte[] responseBytes = RtaProtobufCodec.encodeResponse(response);
  59. return ResponseEntity.ok()
  60. .header("Content-Type", CONTENT_TYPE_PROTOBUF)
  61. .header("Connection", "keep-alive")
  62. .header("Content-Length", String.valueOf(responseBytes.length))
  63. .body(responseBytes);
  64. } catch (Exception e) {
  65. log.error("[RTA] bid error, cost={}ms", System.currentTimeMillis() - startTime, e);
  66. // 出错时也要返回 200,返回空 protobuf(视为参竞,符合RTA规范)
  67. return buildEmptyResponse();
  68. }
  69. }
  70. /**
  71. * 读取请求体字节
  72. */
  73. private byte[] readRequestBody(HttpServletRequest request) throws Exception {
  74. try (InputStream is = request.getInputStream()) {
  75. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  76. byte[] chunk = new byte[4096];
  77. int n;
  78. while ((n = is.read(chunk)) != -1) {
  79. buffer.write(chunk, 0, n);
  80. }
  81. return buffer.toByteArray();
  82. }
  83. }
  84. /**
  85. * 构建空的成功响应(空protobuf body = code:0)
  86. */
  87. private ResponseEntity<byte[]> buildEmptyResponse() {
  88. try {
  89. byte[] emptyResponse = RtaProtobufCodec.encodeResponse(
  90. RtaResponseModel.builder().code(0).build()
  91. );
  92. return ResponseEntity.ok()
  93. .header("Content-Type", CONTENT_TYPE_PROTOBUF)
  94. .header("Connection", "keep-alive")
  95. .header("Content-Length", String.valueOf(emptyResponse.length))
  96. .body(emptyResponse);
  97. } catch (Exception ex) {
  98. // 兜底,返回纯空body
  99. return ResponseEntity.ok()
  100. .header("Content-Type", CONTENT_TYPE_PROTOBUF)
  101. .header("Connection", "keep-alive")
  102. .body(new byte[0]);
  103. }
  104. }
  105. }