| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- package com.tzld.rta.controller.rta;
- import com.tzld.rta.codec.RtaProtobufCodec;
- import com.tzld.rta.model.RtaRequestModel;
- import com.tzld.rta.model.RtaResponseModel;
- import com.tzld.rta.service.RtaService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.MediaType;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.*;
- import javax.servlet.http.HttpServletRequest;
- import java.io.ByteArrayOutputStream;
- import java.io.InputStream;
- /**
- * 腾讯RTA接入Controller
- * <p>
- * 协议要求:
- * - HTTP/1.1 长连接(Keep-Alive)
- * - POST 方法,Content-Type: application/x-protobuf
- * - 响应始终返回 HTTP 200(异常时也需 200)
- * - 超时时间: 60ms(含网络传输)
- * - 超时率/错误率需低于 2%
- * <p>
- * 接口地址: POST /rta/bid
- */
- @Slf4j
- @RestController
- @RequestMapping("/rta")
- public class RtaController {
- private static final String CONTENT_TYPE_PROTOBUF = "application/x-protobuf";
- @Autowired
- private RtaService rtaService;
- /**
- * RTA 竞价接口
- * 腾讯广告系统通过此接口实时查询设备是否参竞
- *
- * @param httpRequest HTTP请求
- * @return protobuf 格式响应
- */
- @PostMapping(
- value = "/bid",
- consumes = {CONTENT_TYPE_PROTOBUF, MediaType.APPLICATION_OCTET_STREAM_VALUE, MediaType.ALL_VALUE},
- produces = CONTENT_TYPE_PROTOBUF
- )
- public ResponseEntity<byte[]> bid(HttpServletRequest httpRequest) {
- long startTime = System.currentTimeMillis();
- try {
- // 1. 读取请求体
- byte[] requestBytes = readRequestBody(httpRequest);
- if (requestBytes == null || requestBytes.length == 0) {
- return buildEmptyResponse();
- }
- // 2. 解码 protobuf 请求
- RtaRequestModel request = RtaProtobufCodec.decodeRequest(requestBytes);
- // 3. 业务处理
- RtaResponseModel response = rtaService.process(request);
- // 4. 编码 protobuf 响应
- byte[] responseBytes = RtaProtobufCodec.encodeResponse(response);
- return ResponseEntity.ok()
- .header("Content-Type", CONTENT_TYPE_PROTOBUF)
- .header("Connection", "keep-alive")
- .header("Content-Length", String.valueOf(responseBytes.length))
- .body(responseBytes);
- } catch (Exception e) {
- log.error("[RTA] bid error, cost={}ms", System.currentTimeMillis() - startTime, e);
- // 出错时也要返回 200,返回空 protobuf(视为参竞,符合RTA规范)
- return buildEmptyResponse();
- }
- }
- /**
- * 读取请求体字节
- */
- private byte[] readRequestBody(HttpServletRequest request) throws Exception {
- try (InputStream is = request.getInputStream()) {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- byte[] chunk = new byte[4096];
- int n;
- while ((n = is.read(chunk)) != -1) {
- buffer.write(chunk, 0, n);
- }
- return buffer.toByteArray();
- }
- }
- /**
- * 构建空的成功响应(空protobuf body = code:0)
- */
- private ResponseEntity<byte[]> buildEmptyResponse() {
- try {
- byte[] emptyResponse = RtaProtobufCodec.encodeResponse(
- RtaResponseModel.builder().code(0).build()
- );
- return ResponseEntity.ok()
- .header("Content-Type", CONTENT_TYPE_PROTOBUF)
- .header("Connection", "keep-alive")
- .header("Content-Length", String.valueOf(emptyResponse.length))
- .body(emptyResponse);
- } catch (Exception ex) {
- // 兜底,返回纯空body
- return ResponseEntity.ok()
- .header("Content-Type", CONTENT_TYPE_PROTOBUF)
- .header("Connection", "keep-alive")
- .body(new byte[0]);
- }
- }
- }
|