ai之对接电信ds后端服务,通过nginx代理转发https为http,对外请求,保持到达第三方后请求头不变

前置环境:

  • 在微信小程序中嵌入H5页面(智能客服),需要让h5页面在https的域名服务器上。即通过 nginx 部署成web服务,还得配置域名和端口443访问。
  • 电信的第三方deepseek服务 ,只接收http请求,暂未支持https请求。
  • 所以我们要使用https的话 就需要在智文网关xxx:31170前面 自行加一个网关进行处理(https -> http)。

下面配置nginx 代理,及调试。主要麻烦在了我方与第三方签名验证不通过上 (postJsonWithAuth)。

1. 构建请求头的方法

1.1

public class SignUtils 工具类

复制代码
//    public static final String PARAM_HEADER_DATE = "Date";
    public static final String PARAM_HEADER_X_DATE = "x-date";

    private static final List<String> SIGNED_HEADERS = new ArrayList<>();
    private static final String algorithm = "HmacSHA256";
    private static final String hmacAlgorithm = "hmac-sha256";

    static {
        SIGNED_HEADERS.add("x-tenantid");
        SIGNED_HEADERS.add("x-userid");
        SIGNED_HEADERS.add("x-source");
    }
1.2
复制代码
    private static Map<String, String> buildHeaders() {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("x-userid", RequestParam.getUserId());
        headers.put("x-tenantid", RequestParam.getTenantId());
        headers.put("x-source", RequestParam.getSource());
        LocalDateTime utcTime = LocalDateTime.now(ZoneOffset.UTC);
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH);
        // 格式化时间并输出
        String formattedUtcTime = utcTime.format(formatter);
        headers.put(SignUtils.PARAM_HEADER_X_DATE, formattedUtcTime);
        return headers;
    }
1.3

/**

API 常量类

**/

public class ApiConstants

复制代码
    // 对话接口: 智文-SSE对话接口
    public static final String CHAT_OPENAPI = "/ais/bot/openapi/dcc/sseDialog";
    // 智文-同步对话接口
    public static final String CHAT_OPENAPI_SYNC = "/ais/bot/openapi/dcc/dialog";
    // 数科官网本地https 转发
    public static final String API_DIANXIN_PROXY = "/apidx";
    public static final String CHAT_OPENAPI_SYNC_PROXY = API_DIANXIN_PROXY + CHAT_OPENAPI_SYNC;

主要问题就忽略了 我们签名时,参数uri 加了"/api" 前缀,通过nginx 代理转发后,过滤了 /api 前缀,到达第三方后,对方用的原始 uri验签,所以总是不通过。

过程中 在ds这台服务器上装了 tcpdump 抓包工具,比对本地debug(不代理)成功的报文,和访问https生产服务器 nginx代理失败的报文

复制代码
    /**
     * @param api 第三方接口uri
     * @param reqBody post请求体参数
     */
    public static String postJsonWithAuth(String api, String reqBody) throws Exception {
//        String url = RequestParam.getHost() + api;
        // 通过nginx 代理转发时,加上前缀 
        String url = RequestParam.getHost() + ApiConstants.API_DIANXIN_PROXY + api;
        long startTime = System.currentTimeMillis();

        try {
            Map<String, String> headers = buildHeaders();

            // 签名时,与第三方保持一致,使用原始api不变 ,不使用代理后的api
            String sign = SignUtils.generateAuth(RequestParam.getAccessKey(), RequestParam.getSecretKey(),
                    "POST", api, headers, new HashMap<>(), reqBody);
            headers.put("Authorization", sign);
            // 请求开始监控
            log.info(">>>> HTTP请求开始 [URL: {},API: {}]", url, api);
            log.info(">>>> HTTP请求头: {}", headers);
            log.info(">>>> HTTP请求参数 [Body: {}]", reqBody);

            RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), reqBody);
            Request request = new Request.Builder()
                    .url(url)
                    .headers(Headers.of(headers))
                    .post(requestBody)
                    .build();

            try (Response response = httpClient.newCall(request).execute()) {
                String responseBody = response.body().string();

                log.info("<<<< 原始响应结果:[状态码: {}, 内容: {}]", response.code(), response);
                if (response.isSuccessful()) {
                    return responseBody;
                } else {
                    log.error("详细错误响应: {}", responseBody);
                    throw new IOException("HTTP请求失败: " + response.code() + " - " + response.message());
                }
            }
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - startTime;
            log.error("<<<< 请求异常 [耗时: {} ms, URL: {}, 错误: {}]", cost, url, e.getMessage(), e);
            throw e;
        } finally {
            long totalCost = System.currentTimeMillis() - startTime;
            log.info("==== 请求结束 [总耗时: {} ms] ====", totalCost);
        }
    }

测试方法:

复制代码
@Test(timeOut = 60000)
    public void testSSEChat() throws Exception {

        String question="石家庄有什么好玩的";
//        String agentCode = queryAgentCode();
        String agentCode = "agent1048486107377569792";
        String messageId = UUID.randomUUID().toString();
        String sessionId = UUID.randomUUID().toString();
        String userId = RequestParam.getUserId();


        MessageRequest messageRequest = new MessageRequest();
        messageRequest.setMessageId(messageId);
        messageRequest.setSessionId(sessionId);
        messageRequest.setMsgType("TEXT");
        messageRequest.setUserId(userId);

        messageRequest.setContent(question);
        messageRequest.setQuery(question);
        messageRequest.setAgentCode(agentCode);
        messageRequest.setTest(1);
        messageRequest.setChatType("chat");
        messageRequest.setRequestTime(System.currentTimeMillis());
        messageRequest.setEntry("default");
        // 添加extraData参数
        Map<String, Object> extraData = new HashMap<>();
        Map<String, Object> filterMap = new HashMap<>();
        filterMap.put("knowledgeFilters", Collections.singletonList(
                Collections.singletonMap("knowledgeBaseCode", "1050656046175358976")
        ));
        extraData.put("filter", JsonUtil.toJSONString(filterMap));
        extraData.put("range", "all");
        messageRequest.setExtraData(extraData);

        CountDownLatch latch = new CountDownLatch(5);
        AtomicBoolean received = new AtomicBoolean(false);


        String resp = OkHttpUtils.postJsonWithAuth(ApiConstants.CHAT_OPENAPI_SYNC, JsonUtil.toJSONString(messageRequest));
        log.info("同步结果: {}", resp);
        
        Thread.sleep(10000);   
    }

下面是Nginx配置:

bash 复制代码
# 添加详细日志记录
log_format proxy_debug '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent" '
                      'Authorization: "$http_authorization" '
                      'x-source: "$http_x_source" '
                      'x-userid: "$http_x_userid" '
                      'x-tenantid: "$http_x_tenantid" '
                      'x-date: "$http_x_date" '
                      'content-type: "$http_content_type" '
                      'cookie_header: "$http_cookie" '
                      'host_header: "$host"';

server {
        listen       443 ssl;
        charset   utf-8;
        server_name stdai.sjzwltszkj.com ;
        ssl_certificate      /usr/local/nginx/conf/cert/cert.pem;
        ssl_certificate_key  /usr/local/nginx/conf/cert/cert.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        # location / {
        location ~* \.txt$ {
           root /data/std/authentication;
        }


        location /ai/ {
            alias /www/wwwroot/static.ltkj.com/std_applet/dist/build/h5/ ;
            #root /www/wwwroot/static.ltkj.com/std_applet/h5/dist/;
            try_files $uri $uri/ /ai/index.html;
            index  index.html index.htm;

        }
        location /static/ {
            alias /www/wwwroot/static.ltkj.com/std_applet/dist/build/h5/static/;
            expires 1y;
            add_header Cache-Control "public";
        }

		# API代理 关键配置
	    location /apidx/ {
	        # 1. 保持原始HTTP版本和连接行为
	        proxy_http_version 1.1;
	        proxy_set_header Connection "";
	
	        # 2. 保持原始Host头
	        proxy_set_header Host $proxy_host;
	        proxy_set_header x-forwarded-host $host;
	
	        # 传递所有头并保持原始顺序
	        proxy_pass_request_headers on;
	         # 3. 禁用不必要的头传递
	        proxy_pass_header Server;
	        proxy_pass_header Date;
	        proxy_hide_header 'Access-Control-Allow-Origin';
	        proxy_hide_header 'Access-Control-Allow-Methods';
	        proxy_hide_header 'Access-Control-Allow-Headers';
	
	        # 4. 精确传递鉴权相关头
	        proxy_set_header Authorization $http_authorization;
	        proxy_set_header x-source $http_x_source;
	        proxy_set_header x-userid $http_x_userid;
	        proxy_set_header x-tenantid $http_x_tenantid;
	        proxy_set_header x-date $http_x_date;
	
	        # 5. 代理到后端服务器 https -> http. 过滤掉/api/ 后保持原始请求路径
	        proxy_pass http://222.223.xxx.xxx:xxx70/;
	
	        # 超时设置
	        proxy_connect_timeout 60s;
	        proxy_send_timeout 60s;
	        proxy_read_timeout 60s;
	
	        # 禁用缓冲
	        proxy_buffering off;
	
	        # CORS配置
	        add_header 'Access-Control-Allow-Origin' 'https://stdai.sjzwltszkj.com' always;
	        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
	        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With, X-CSRF-Token, x-source, x-userid, x-tenantid, x-date' always;
	        add_header 'Access-Control-Allow-Credentials' 'true' always;
	        add_header 'Access-Control-Expose-Headers' 'Authorization' always;
	
	        # 处理OPTIONS预检请求
	        if ($request_method = 'OPTIONS') {
	            add_header 'Access-Control-Allow-Origin' 'https://stdai.sjzwltszkj.com' always;
	            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
	            add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-CSRF-Token,x-source,x-userid,x-tenantid,x-date' always;
	            add_header 'Access-Control-Max-Age' 1728000 always;
	            add_header 'Content-Type' 'text/plain; charset=utf-8' always;
	 add_header 'Content-Type' 'text/plain; charset=utf-8' always;
	            add_header 'Content-Length' 0 always;
	            return 204;
	        }
	
	    }
	
	
	
	
	    error_page   500 502 503 504  /50x.html;
	    location = /50x.html {
	        root   html;
	    }
	    access_log /usr/local/nginx/logs/access.log proxy_debug ;

}
相关推荐
华无丽言2 分钟前
如何简单实现发版不影响客户使用?nginx负载
linux·nginx
2501_916007478 分钟前
iOS 性能测试工具全流程:主流工具实战对比与适用场景
websocket·tcp/ip·http·网络安全·https·udp
teeeeeeemo38 分钟前
http和https的区别
开发语言·网络·笔记·网络协议·http·https
带刺的坐椅1 小时前
Java MCP 实战:构建跨进程与远程的工具服务
java·ai·solon·mcp
小付爱coding1 小时前
SpringAIAlibaba正式版发布!
ai
产品经理独孤虾12 小时前
人工智能大模型如何助力电商产品经理打造高效的商品工业属性画像
人工智能·机器学习·ai·大模型·产品经理·商品画像·商品工业属性
海豚调度18 小时前
Linux 基金会报告解读:开源 AI 重塑经济格局,有人失业,有人涨薪!
大数据·人工智能·ai·开源
令狐少侠201118 小时前
ai之RAG本地知识库--基于OCR和文本解析器的新一代RAG引擎:RAGFlow 认识和源码剖析
人工智能·ai
2501_915918411 天前
接口漏洞怎么抓?Fiddler 中文版 + Postman + Wireshark 实战指南
websocket·网络协议·tcp/ip·http·网络安全·https·udp