前置环境:
- 在微信小程序中嵌入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 ;
}