fix restemplate bug
This commit is contained in:
parent
2b15682b0a
commit
341342c8b9
@ -50,6 +50,18 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.logstash.logback</groupId>
|
||||
<artifactId>logstash-logback-encoder</artifactId>
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package com.suisung.mall.common.config;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
|
||||
import org.springframework.retry.backoff.FixedBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RestTemplate 企业级配置
|
||||
* 兼容 Spring Boot 2.3.0 版本
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
/**
|
||||
* 企业级 RestTemplate 配置
|
||||
* 1. 使用 OkHttp3 客户端(性能优化)
|
||||
* 2. 配置连接超时(5秒)和读取超时(10秒)
|
||||
* 3. 添加请求日志拦截器
|
||||
* 4. 兼容 Spring Boot 2.3.0 的 API
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
return builder
|
||||
.requestFactory(OkHttp3ClientHttpRequestFactory::new)
|
||||
.setConnectTimeout(Duration.ofSeconds(15)) // 连接超时5秒
|
||||
.setReadTimeout(Duration.ofSeconds(25)) // 读取超时10秒
|
||||
.additionalInterceptors((request, body, execution) -> {
|
||||
logRequest(request); // 请求日志记录
|
||||
return execution.execute(request, body);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试机制配置(兼容 Spring 2.3)
|
||||
* 1. 最多重试3次
|
||||
* 2. 每次间隔1秒
|
||||
*/
|
||||
@Bean
|
||||
public RetryTemplate retryTemplate() {
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000); // 1秒间隔
|
||||
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||
retryPolicy.setMaxAttempts(3); // 最大重试3次
|
||||
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setBackOffPolicy(backOffPolicy);
|
||||
template.setRetryPolicy(retryPolicy);
|
||||
return template;
|
||||
}
|
||||
|
||||
private void logRequest(HttpRequest request) {
|
||||
// 实现请求日志记录(需配合项目日志组件)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,4 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||
* Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||
*/
|
||||
// ... existing copyright notice ...
|
||||
|
||||
package com.suisung.mall.common.service.impl;
|
||||
|
||||
@ -26,15 +20,20 @@ import org.springframework.web.client.RestTemplate;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 调用 UniCloud 云函数 推送服务
|
||||
* 提供向多个客户端 ID 发送推送消息的功能
|
||||
* UniCloud 推送服务实现类
|
||||
* 提供向客户端发送推送消息的功能
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UniCloudPushServiceImpl implements UniCloudPushService {
|
||||
|
||||
private static final int MAX_CLIENT_IDS = 500;
|
||||
private static final String DEFAULT_TITLE = "小发同城:您有新的消息";
|
||||
private static final long DEFAULT_TTL = 3 * 24 * 3600 * 1000L; // 3天有效期
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private RestTemplate restTemplate;
|
||||
@ -45,132 +44,147 @@ public class UniCloudPushServiceImpl implements UniCloudPushService {
|
||||
/**
|
||||
* 向单个客户端发送推送消息
|
||||
*
|
||||
* @param clientId 客户端 ID
|
||||
* @param clientId 客户端唯一标识
|
||||
* @param title 推送标题
|
||||
* @param content 推送内容
|
||||
* @param payload 推送内容
|
||||
* @return
|
||||
* @param payload 附加数据(JSON格式)
|
||||
* @return Pair<是否成功, 结果描述>
|
||||
*/
|
||||
@Override
|
||||
public Pair<Boolean, String> sendPushMessage(String clientId, String title, String content, JSONObject payload) {
|
||||
if (StrUtil.isBlank(clientId)) {
|
||||
log.warn("[推送服务] 客户端ID不能为空");
|
||||
return Pair.of(false, "客户端ID不能为空");
|
||||
}
|
||||
return sendPushMessageBatch(Collections.singletonList(clientId), title, content, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向多个客户端ID批量发送推送消息
|
||||
* 批量发送推送消息
|
||||
*
|
||||
* @param clientIds 目标客户端 ID 列表 注意:超过500个,直接忽略 必填项
|
||||
* @param title 推送标题 可选项
|
||||
* @param content 推送内容 必填项
|
||||
* @param payload 附加数据(JSON对象) 可选项
|
||||
* @return 推送结果响应对象
|
||||
* @param clientIds 客户端ID列表(最多500个)
|
||||
* @param title 推送标题
|
||||
* @param content 推送内容
|
||||
* @param payload 附加数据(JSON格式)
|
||||
* @return Pair<是否成功, 结果描述>
|
||||
*/
|
||||
@Override
|
||||
public Pair<Boolean, String> sendPushMessageBatch(List<String> clientIds, String title, String content, JSONObject payload) {
|
||||
if (StrUtil.isBlank(pushMessageUrl) || CollUtil.isEmpty(clientIds) || StrUtil.isBlank(content)) {
|
||||
return Pair.of(false, "缺少必要参数");
|
||||
}
|
||||
|
||||
if (clientIds.size() > 500) {
|
||||
log.warn("批量推送消息时,CIDs 数量超过限制");
|
||||
return Pair.of(false, "推送客户端超出500限额");
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(title)) {
|
||||
title = "小发同城:您有新的消息";
|
||||
// === 参数校验 ===
|
||||
if (StrUtil.isBlank(pushMessageUrl)) {
|
||||
log.error("[推送服务] 推送URL未配置");
|
||||
return Pair.of(false, "推送服务未配置");
|
||||
}
|
||||
if (CollUtil.isEmpty(clientIds)) {
|
||||
log.warn("[推送服务] 客户端ID列表为空");
|
||||
return Pair.of(false, "客户端ID列表不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(content)) {
|
||||
log.warn("[推送服务] 推送内容为空");
|
||||
return Pair.of(false, "推送内容不能为空");
|
||||
}
|
||||
if (clientIds.size() > MAX_CLIENT_IDS) {
|
||||
log.warn("[推送服务] 客户端ID数量超过限制: {}", clientIds.size());
|
||||
return Pair.of(false, "客户端ID数量超过500限制");
|
||||
}
|
||||
|
||||
// === 业务处理 ===
|
||||
try {
|
||||
// 构建请求体
|
||||
JSONObject requestBody = buildPushRequest(clientIds, title, content, payload);
|
||||
log.debug("[推送服务] 开始处理推送请求, 客户端数量: {}", clientIds.size());
|
||||
|
||||
// 执行HTTP请求
|
||||
ResponseEntity<String> response = executeHttpRequest(pushMessageUrl, requestBody);
|
||||
// 1. 构建请求体
|
||||
JSONObject requestBody = buildPushRequest(
|
||||
clientIds,
|
||||
StrUtil.isBlank(title) ? DEFAULT_TITLE : title,
|
||||
content,
|
||||
payload
|
||||
);
|
||||
|
||||
// 处理响应结果
|
||||
return processResponse(response);
|
||||
// 2. 执行HTTP请求
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
pushMessageUrl,
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(requestBody.toString(), buildHeaders()),
|
||||
String.class
|
||||
);
|
||||
|
||||
// 3. 处理响应
|
||||
return processPushResponse(response);
|
||||
} catch (RestClientException e) {
|
||||
// 处理REST客户端异常
|
||||
log.error("推送请求发送失败:{}", e.getMessage(), e);
|
||||
return Pair.of(false, "推送请求发送失败");
|
||||
log.error("[推送服务] REST请求异常: {}", e.getMessage(), e);
|
||||
return Pair.of(false, "推送请求失败");
|
||||
} catch (Exception e) {
|
||||
// 处理其他异常
|
||||
log.error("推送处理过程发生异常:{}", e.getMessage(), e);
|
||||
return Pair.of(false, "推送处理过程发生异常");
|
||||
log.error("[推送服务] 处理异常: {}", e.getMessage(), e);
|
||||
return Pair.of(false, "推送处理异常");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求头
|
||||
*/
|
||||
private HttpHeaders buildHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建推送请求体
|
||||
*/
|
||||
private JSONObject buildPushRequest(List<String> clientIds, String title, String content, JSONObject payload) {
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("action", "push");
|
||||
requestBody.put("cidList", clientIds);
|
||||
JSONObject request = new JSONObject();
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("title", title);
|
||||
message.put("content", content);
|
||||
// 新增:客户端ID去重
|
||||
List<String> distinctClientIds = clientIds.stream().distinct().collect(Collectors.toList());
|
||||
if (distinctClientIds.size() != clientIds.size()) {
|
||||
log.warn("[推送服务] 客户端ID列表存在重复,已自动去重。原数量:{},去重后数量:{}",
|
||||
clientIds.size(), distinctClientIds.size());
|
||||
}
|
||||
|
||||
request.put("push_clientid", distinctClientIds)
|
||||
.put("title", title)
|
||||
.put("content", content);
|
||||
|
||||
if (payload != null) {
|
||||
message.put("payload", payload);
|
||||
request.put("payload", payload);
|
||||
}
|
||||
|
||||
requestBody.put("message", message);
|
||||
|
||||
JSONArray platform = new JSONArray();
|
||||
platform.add("web");
|
||||
platform.add("app-ios");
|
||||
platform.add("app-android");
|
||||
platform.add("mp-weixin");
|
||||
requestBody.put("platform", platform);
|
||||
// 设置平台和有效期
|
||||
JSONArray platforms = new JSONArray();
|
||||
platforms.add("web");
|
||||
platforms.add("app-ios");
|
||||
platforms.add("app-android");
|
||||
platforms.add("mp-weixin");
|
||||
request.put("platform", platforms)
|
||||
.put("settings", new JSONObject().set("ttl", DEFAULT_TTL));
|
||||
|
||||
|
||||
//消息有效期设置,单位毫秒,-1表示不设离线。默认是 2 小时,取值范围:-1 ~ 3 * 24 * 3600 * 1000(3天)之间
|
||||
requestBody.put("settings", new JSONObject().set("ttl", 3 * 24 * 3600 * 1000));
|
||||
|
||||
return requestBody;
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行HTTP POST请求
|
||||
* 处理推送响应
|
||||
*/
|
||||
private ResponseEntity<String> executeHttpRequest(String cloudFunctionUrl, JSONObject requestBody) {
|
||||
// 设置请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// 创建请求实体
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody.toString(), headers);
|
||||
|
||||
// 执行POST请求
|
||||
return restTemplate.exchange(
|
||||
cloudFunctionUrl,
|
||||
HttpMethod.POST,
|
||||
requestEntity,
|
||||
String.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP响应
|
||||
*/
|
||||
private Pair<Boolean, String> processResponse(ResponseEntity<String> response) {
|
||||
private Pair<Boolean, String> processPushResponse(ResponseEntity<String> response) {
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
return Pair.of(false, String.format("推送请求失败,状态码: %d,响应: %s",
|
||||
response.getStatusCodeValue(),
|
||||
response.getBody()));
|
||||
log.error("[推送服务] 请求失败, 状态码: {}", response.getStatusCodeValue());
|
||||
return Pair.of(false, "推送请求失败");
|
||||
}
|
||||
|
||||
String responseBody = response.getBody();
|
||||
if (responseBody == null || responseBody.trim().isEmpty()) {
|
||||
return Pair.of(false, "推送响应内容为空");
|
||||
String body = response.getBody();
|
||||
if (StrUtil.isBlank(body)) {
|
||||
log.warn("[推送服务] 响应体为空");
|
||||
return Pair.of(false, "推送响应为空");
|
||||
}
|
||||
|
||||
// 解析响应JSON
|
||||
JSONObject responseJson = JSONUtil.parseObj(responseBody);
|
||||
return Pair.of(true, String.format("推送请求成功,状态码: %d,响应: %s",
|
||||
responseJson.getInt("code"),
|
||||
responseJson.getJSONObject("data").toString()));
|
||||
try {
|
||||
JSONObject json = JSONUtil.parseObj(body);
|
||||
log.info("[推送服务] 推送成功, 响应: {}", json.toStringPretty());
|
||||
|
||||
return Pair.of(true, json.getJSONObject("data").toString());
|
||||
} catch (Exception e) {
|
||||
log.error("[推送服务] 响应解析异常: {}", e.getMessage(), e);
|
||||
return Pair.of(false, "响应解析失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,10 +9,12 @@
|
||||
package com.suisung.mall.shop.lakala.controller.mobile;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.suisung.mall.common.api.CommonResult;
|
||||
import com.suisung.mall.common.service.impl.BaseControllerImpl;
|
||||
import com.suisung.mall.shop.lakala.service.LakalaApiService;
|
||||
import com.suisung.mall.shop.library.service.LibraryProductService;
|
||||
import com.suisung.mall.shop.message.service.PushMessageService;
|
||||
import com.suisung.mall.shop.order.service.ShopOrderReturnService;
|
||||
import com.suisung.mall.shop.store.service.ShopStoreSameCityTransportBaseService;
|
||||
import io.swagger.annotations.Api;
|
||||
@ -26,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "拉卡拉相关接口 - 前端控制器")
|
||||
@RestController
|
||||
@ -44,6 +47,9 @@ public class LakalaController extends BaseControllerImpl {
|
||||
@Resource
|
||||
private ShopStoreSameCityTransportBaseService storeSameCityTransportBaseService;
|
||||
|
||||
@Resource
|
||||
private PushMessageService pushMessageService;
|
||||
|
||||
@ApiOperation(value = "测试案例", notes = "测试案例")
|
||||
@RequestMapping(value = "/testcase", method = RequestMethod.POST)
|
||||
public Object testcase(@RequestBody JSONObject paramsJSON) {
|
||||
@ -62,6 +68,19 @@ public class LakalaController extends BaseControllerImpl {
|
||||
//// tags.add("放心");
|
||||
// return libraryProductService.matchLibraryProducts(paramsJSON.getStr("barcode"), paramsJSON.getStr("productName"), tags);
|
||||
|
||||
// 测试推送消息
|
||||
// List<String> clientIds = JSONUtil.toList(paramsJSON.getJSONArray("clientIds"), String.class);
|
||||
// return pushMessageService.sendMessage(clientIds, paramsJSON.getStr("title"), paramsJSON.getStr("content"), paramsJSON.getJSONObject("payload"));
|
||||
|
||||
}
|
||||
|
||||
@ApiOperation(value = "批量发送推送消息 - 测试案例", notes = "批量发送推送消息 - 测试案例")
|
||||
@PostMapping(value = "/unipush/testcase")
|
||||
public Object pushMessageTestCase(@RequestBody JSONObject paramsJSON) {
|
||||
// 测试推送消息
|
||||
List<String> clientIds = JSONUtil.toList(paramsJSON.getJSONArray("clientIds"), String.class);
|
||||
return pushMessageService.sendMessage(clientIds, paramsJSON.getStr("title"), paramsJSON.getStr("content"), paramsJSON.getJSONObject("payload"));
|
||||
|
||||
}
|
||||
|
||||
@ApiOperation(value = "本地文件转base64", notes = "本地文件转base64")
|
||||
|
||||
@ -9,11 +9,34 @@
|
||||
package com.suisung.mall.shop.message.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import org.springframework.data.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface PushMessageService {
|
||||
|
||||
/**
|
||||
* 批量发送推送消息
|
||||
* === 功能说明 ===
|
||||
* 1. 异步批量发送推送消息
|
||||
* 2. 支持自定义标题、内容和附加数据
|
||||
* 3. 返回发送结果和错误信息
|
||||
* === 技术实现 ===
|
||||
* - 使用@Async实现异步发送
|
||||
* - 通过uniCloudPushService批量发送
|
||||
* - 记录详细日志便于问题追踪
|
||||
*
|
||||
* @param clientIds 客户端ID列表 (必填)
|
||||
* @param title 消息标题 (可选)
|
||||
* @param content 消息内容 (必填)
|
||||
* @param payload 附加数据 (可为空)
|
||||
* @return CompletableFuture<Pair < Boolean, String>>
|
||||
* - Boolean: 发送是否成功
|
||||
* - String: 成功为"", 失败为错误信息
|
||||
*/
|
||||
CompletableFuture<Pair<Boolean, String>> sendMessage(List<String> clientIds, String title, String content, JSONObject payload);
|
||||
|
||||
/**
|
||||
* 异步发送推送通知 商户签约电子合同
|
||||
*
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
package com.suisung.mall.shop.message.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.suisung.mall.common.feignService.AccountService;
|
||||
import com.suisung.mall.common.modules.account.AccountUserBindGeTui;
|
||||
@ -19,6 +20,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
@ -43,6 +45,59 @@ public class PushMessageServiceImpl implements PushMessageService {
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
/**
|
||||
* 批量发送推送消息
|
||||
* === 功能说明 ===
|
||||
* 1. 异步批量发送推送消息
|
||||
* 2. 支持自定义标题、内容和附加数据
|
||||
* 3. 返回发送结果和错误信息
|
||||
* === 技术实现 ===
|
||||
* - 使用@Async实现异步发送
|
||||
* - 通过uniCloudPushService批量发送
|
||||
* - 记录详细日志便于问题追踪
|
||||
*
|
||||
* @param clientIds 客户端ID列表 (必填)
|
||||
* @param title 消息标题 (可选)
|
||||
* @param content 消息内容 (必填)
|
||||
* @param payload 附加数据 (可为空)
|
||||
* @return CompletableFuture<Pair < Boolean, String>>
|
||||
* - Boolean: 发送是否成功
|
||||
* - String: 成功为"", 失败为错误信息
|
||||
*/
|
||||
@Async("asyncExecutor")
|
||||
@Override
|
||||
public CompletableFuture<Pair<Boolean, String>> sendMessage(List<String> clientIds, String title, String content, JSONObject payload) {
|
||||
// 参数校验
|
||||
if (CollectionUtils.isEmpty(clientIds) || StrUtil.isBlank(content)) {
|
||||
log.warn("推送消息参数无效 - clientIds:{}, title:{}, content:{}", clientIds, title, content);
|
||||
return CompletableFuture.completedFuture(Pair.of(false, "推送消息参数无效"));
|
||||
}
|
||||
|
||||
try {
|
||||
if (StrUtil.isBlank(title)) {
|
||||
title = "小发同城:您有新的消息";
|
||||
}
|
||||
|
||||
log.debug("开始发送推送消息 - clientIds:{}, title:{}", clientIds, title);
|
||||
Pair<Boolean, String> result = uniCloudPushService.sendPushMessageBatch(clientIds, title, content, payload);
|
||||
|
||||
if (!result.getFirst()) {
|
||||
log.error("推送消息失败 - clientIds:{}, error:{}", clientIds, result.getSecond());
|
||||
} else {
|
||||
log.debug("推送消息成功 - clientIds:{}", clientIds);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "推送消息系统异常";
|
||||
log.error("{} - clientIds:{}, title:{}, content:{}, payload:{}",
|
||||
errorMsg, clientIds, title, content, payload, e);
|
||||
return CompletableFuture.completedFuture(Pair.of(false, errorMsg));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 异步发送推送通知 商户签约电子合同
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user