Merge branch 'qa/2025-04-10' into 'master'
Qa/2025 04 10 See merge request !17
Showing
6 changed files
with
53 additions
and
13 deletions
| ... | @@ -83,9 +83,7 @@ public interface ConfigConstants { | ... | @@ -83,9 +83,7 @@ public interface ConfigConstants { |
| 83 | String MINI_PROGRAM_APP_SECRET = "${miniprogram.appSecret}"; | 83 | String MINI_PROGRAM_APP_SECRET = "${miniprogram.appSecret}"; |
| 84 | String MINI_PROGRAM_ORDER_REMINDER_TEMPLATE_ID = "${miniprogram.orderReminderTemplateId}"; | 84 | String MINI_PROGRAM_ORDER_REMINDER_TEMPLATE_ID = "${miniprogram.orderReminderTemplateId}"; |
| 85 | 85 | ||
| 86 | - // 公众号配置(用于订阅消息推送) | 86 | + // 公众号模板 ID(可选);appId/appSecret 见 WxOfficialAccountHttpClient 字段上的 @Value 默认值 |
| 87 | - String OFFICIAL_ACCOUNT_APP_ID = "${officialAccount.appId}"; | 87 | + String OFFICIAL_ACCOUNT_ORDER_REMINDER_TEMPLATE_ID = "${officialAccount.orderReminderTemplateId:}"; |
| 88 | - String OFFICIAL_ACCOUNT_APP_SECRET = "${officialAccount.appSecret}"; | ||
| 89 | - String OFFICIAL_ACCOUNT_ORDER_REMINDER_TEMPLATE_ID = "${officialAccount.orderReminderTemplateId}"; | ||
| 90 | 88 | ||
| 91 | } | 89 | } | ... | ... |
| ... | @@ -89,6 +89,10 @@ public class SettlementDTO { | ... | @@ -89,6 +89,10 @@ public class SettlementDTO { |
| 89 | public static class SettlementOrderDetail { | 89 | public static class SettlementOrderDetail { |
| 90 | private String mealTime; | 90 | private String mealTime; |
| 91 | private String code; | 91 | private String code; |
| 92 | + /** 支付完成时间,格式 yyyy-MM-dd HH:mm:ss,无则空 */ | ||
| 93 | + private String payTime; | ||
| 94 | + /** 下单人:院区端为操作员名称;住院小程序为客户姓名;阳光厅为客户手机号 */ | ||
| 95 | + private String orderer; | ||
| 92 | private String mealName; | 96 | private String mealName; |
| 93 | private String roomNo; | 97 | private String roomNo; |
| 94 | private String customerName; | 98 | private String customerName; | ... | ... |
| ... | @@ -2,12 +2,16 @@ package com.infoloop.tianting.service.client; | ... | @@ -2,12 +2,16 @@ package com.infoloop.tianting.service.client; |
| 2 | 2 | ||
| 3 | import com.infoloop.tianting.COperatorServiceProtoRpcGrpc; | 3 | import com.infoloop.tianting.COperatorServiceProtoRpcGrpc; |
| 4 | import com.infoloop.tianting.GetCOperatorByIdRpcRequest; | 4 | import com.infoloop.tianting.GetCOperatorByIdRpcRequest; |
| 5 | +import com.infoloop.tianting.GetCOperatorByIdsRpcRequest; | ||
| 5 | import com.infoloop.tianting.GetCOperatorByMobileOrEmailRpcRequest; | 6 | import com.infoloop.tianting.GetCOperatorByMobileOrEmailRpcRequest; |
| 6 | import com.infoloop.tianting.SingleCOperatorRpcResponse; | 7 | import com.infoloop.tianting.SingleCOperatorRpcResponse; |
| 7 | import lombok.RequiredArgsConstructor; | 8 | import lombok.RequiredArgsConstructor; |
| 8 | import org.springframework.beans.factory.annotation.Autowired; | 9 | import org.springframework.beans.factory.annotation.Autowired; |
| 9 | import org.springframework.stereotype.Service; | 10 | import org.springframework.stereotype.Service; |
| 10 | 11 | ||
| 12 | +import java.util.Collections; | ||
| 13 | +import java.util.List; | ||
| 14 | + | ||
| 11 | @Service | 15 | @Service |
| 12 | @RequiredArgsConstructor(onConstructor = @__(@Autowired)) | 16 | @RequiredArgsConstructor(onConstructor = @__(@Autowired)) |
| 13 | public class OperatorServiceRpcClient { | 17 | public class OperatorServiceRpcClient { |
| ... | @@ -26,5 +30,15 @@ public class OperatorServiceRpcClient { | ... | @@ -26,5 +30,15 @@ public class OperatorServiceRpcClient { |
| 26 | return cOperatorServiceProtoRpcBlockingStub.getCOperatorById(request).getResponse(); | 30 | return cOperatorServiceProtoRpcBlockingStub.getCOperatorById(request).getResponse(); |
| 27 | } | 31 | } |
| 28 | 32 | ||
| 33 | + public List<SingleCOperatorRpcResponse> getCOperatorsByIds(List<Integer> ids) { | ||
| 34 | + if (ids == null || ids.isEmpty()) { | ||
| 35 | + return Collections.emptyList(); | ||
| 36 | + } | ||
| 37 | + return cOperatorServiceProtoRpcBlockingStub.getCOperatorByIds(GetCOperatorByIdsRpcRequest.newBuilder() | ||
| 38 | + .addAllIds(ids) | ||
| 39 | + .setIncludeDeleted(false) | ||
| 40 | + .build()) | ||
| 41 | + .getResponsesList(); | ||
| 42 | + } | ||
| 29 | 43 | ||
| 30 | } | 44 | } | ... | ... |
| ... | @@ -720,7 +720,7 @@ public class OrderServiceRpcClient { | ... | @@ -720,7 +720,7 @@ public class OrderServiceRpcClient { |
| 720 | if (menuById.getResponse().getOrderRuleJson().getModeOfPayment() == ModeOfPaymentEnum.ONLINE) { | 720 | if (menuById.getResponse().getOrderRuleJson().getModeOfPayment() == ModeOfPaymentEnum.ONLINE) { |
| 721 | final var autoCancelParam = new AutoCancelParam(); | 721 | final var autoCancelParam = new AutoCancelParam(); |
| 722 | autoCancelParam.setOrderId(orderResponse.getId()); | 722 | autoCancelParam.setOrderId(orderResponse.getId()); |
| 723 | - delayedQueue.addToQueue(OrderAutoCancelDelayTask.AUTO_CANCEL_ORDER_DELAY_QUEUE, autoCancelParam, 10, TimeUnit.MINUTES); | 723 | + delayedQueue.addToQueue(OrderAutoCancelDelayTask.AUTO_CANCEL_ORDER_DELAY_QUEUE, autoCancelParam, 6, TimeUnit.HOURS); |
| 724 | } | 724 | } |
| 725 | } | 725 | } |
| 726 | return CreateOrderResponse.builder().id(orderResponse.getId()).isCreated(true).build(); | 726 | return CreateOrderResponse.builder().id(orderResponse.getId()).isCreated(true).build(); | ... | ... |
| ... | @@ -2,12 +2,13 @@ package com.infoloop.tianting.service.client; | ... | @@ -2,12 +2,13 @@ package com.infoloop.tianting.service.client; |
| 2 | 2 | ||
| 3 | import com.infoloop.tianting.model.dto.WxUserDto.SendSubscriptionMessageResponse; | 3 | import com.infoloop.tianting.model.dto.WxUserDto.SendSubscriptionMessageResponse; |
| 4 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto; | 4 | import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto; |
| 5 | +import org.springframework.lang.Nullable; | ||
| 5 | import com.infoloop.tianting.utils.JsonUtil; | 6 | import com.infoloop.tianting.utils.JsonUtil; |
| 6 | import lombok.RequiredArgsConstructor; | 7 | import lombok.RequiredArgsConstructor; |
| 7 | import lombok.extern.slf4j.Slf4j; | 8 | import lombok.extern.slf4j.Slf4j; |
| 8 | import org.apache.commons.lang3.StringUtils; | 9 | import org.apache.commons.lang3.StringUtils; |
| 9 | import org.springframework.beans.factory.annotation.Autowired; | 10 | import org.springframework.beans.factory.annotation.Autowired; |
| 10 | -import org.springframework.beans.factory.annotation.Value; | 11 | +import org.springframework.core.env.Environment; |
| 11 | import org.springframework.data.redis.core.StringRedisTemplate; | 12 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 12 | import org.springframework.http.HttpEntity; | 13 | import org.springframework.http.HttpEntity; |
| 13 | import org.springframework.http.HttpHeaders; | 14 | import org.springframework.http.HttpHeaders; |
| ... | @@ -16,13 +17,12 @@ import org.springframework.http.ResponseEntity; | ... | @@ -16,13 +17,12 @@ import org.springframework.http.ResponseEntity; |
| 16 | import org.springframework.stereotype.Service; | 17 | import org.springframework.stereotype.Service; |
| 17 | import org.springframework.web.client.RestTemplate; | 18 | import org.springframework.web.client.RestTemplate; |
| 18 | 19 | ||
| 20 | +import javax.annotation.PostConstruct; | ||
| 21 | + | ||
| 19 | import java.util.HashMap; | 22 | import java.util.HashMap; |
| 20 | import java.util.Map; | 23 | import java.util.Map; |
| 21 | import java.util.concurrent.TimeUnit; | 24 | import java.util.concurrent.TimeUnit; |
| 22 | 25 | ||
| 23 | -import static com.infoloop.tianting.constant.ConfigConstants.OFFICIAL_ACCOUNT_APP_ID; | ||
| 24 | -import static com.infoloop.tianting.constant.ConfigConstants.OFFICIAL_ACCOUNT_APP_SECRET; | ||
| 25 | - | ||
| 26 | /** | 26 | /** |
| 27 | * 微信公众号 HTTP 客户端 | 27 | * 微信公众号 HTTP 客户端 |
| 28 | * 用于发送公众号一次性订阅消息 | 28 | * 用于发送公众号一次性订阅消息 |
| ... | @@ -42,19 +42,36 @@ public class WxOfficialAccountHttpClient { | ... | @@ -42,19 +42,36 @@ public class WxOfficialAccountHttpClient { |
| 42 | 42 | ||
| 43 | private final RestTemplate restTemplate; | 43 | private final RestTemplate restTemplate; |
| 44 | private final StringRedisTemplate stringRedisTemplate; | 44 | private final StringRedisTemplate stringRedisTemplate; |
| 45 | + private final Environment environment; | ||
| 45 | 46 | ||
| 46 | - @Value(OFFICIAL_ACCOUNT_APP_ID) | 47 | + /** 由 {@link #bindOfficialAccountConfig()} 从 Environment 读取;缺失配置项时为空白,不触发占位符解析 */ |
| 47 | - private String appId; | 48 | + private String appId = ""; |
| 48 | 49 | ||
| 49 | - @Value(OFFICIAL_ACCOUNT_APP_SECRET) | 50 | + private String appSecret = ""; |
| 50 | - private String appSecret; | ||
| 51 | 51 | ||
| 52 | private final Object lock = new Object(); | 52 | private final Object lock = new Object(); |
| 53 | 53 | ||
| 54 | + @PostConstruct | ||
| 55 | + void bindOfficialAccountConfig() { | ||
| 56 | + this.appId = StringUtils.trimToEmpty(environment.getProperty("officialAccount.appId")); | ||
| 57 | + this.appSecret = StringUtils.trimToEmpty(environment.getProperty("officialAccount.appSecret")); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + private boolean isOfficialAccountConfigured() { | ||
| 61 | + return StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appSecret); | ||
| 62 | + } | ||
| 63 | + | ||
| 54 | /** | 64 | /** |
| 55 | * 获取公众号 access_token(带缓存) | 65 | * 获取公众号 access_token(带缓存) |
| 66 | + * | ||
| 67 | + * @return token;未配置 appId/appSecret 时返回 null(不请求微信接口) | ||
| 56 | */ | 68 | */ |
| 69 | + @Nullable | ||
| 57 | public String fetchStableAccessToken() { | 70 | public String fetchStableAccessToken() { |
| 71 | + if (!isOfficialAccountConfigured()) { | ||
| 72 | + log.warn("未配置 officialAccount.appId / officialAccount.appSecret,跳过获取公众号 access_token"); | ||
| 73 | + return null; | ||
| 74 | + } | ||
| 58 | log.info("Fetching Official Account access token..."); | 75 | log.info("Fetching Official Account access token..."); |
| 59 | String cachedToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_CACHE_KEY); | 76 | String cachedToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_CACHE_KEY); |
| 60 | if (StringUtils.isNotEmpty(cachedToken)) { | 77 | if (StringUtils.isNotEmpty(cachedToken)) { |
| ... | @@ -117,8 +134,15 @@ public class WxOfficialAccountHttpClient { | ... | @@ -117,8 +134,15 @@ public class WxOfficialAccountHttpClient { |
| 117 | Map<String, Object> data, | 134 | Map<String, Object> data, |
| 118 | String url, | 135 | String url, |
| 119 | Map<String, String> miniprogram) { | 136 | Map<String, String> miniprogram) { |
| 137 | + if (!isOfficialAccountConfigured()) { | ||
| 138 | + log.warn("未配置公众号,跳过发送订阅消息 openId={}", openId); | ||
| 139 | + return new SendSubscriptionMessageResponse(-1, "微信公众号未配置"); | ||
| 140 | + } | ||
| 120 | try { | 141 | try { |
| 121 | String accessToken = fetchStableAccessToken(); | 142 | String accessToken = fetchStableAccessToken(); |
| 143 | + if (accessToken == null) { | ||
| 144 | + return new SendSubscriptionMessageResponse(-1, "未获取到公众号 access_token"); | ||
| 145 | + } | ||
| 122 | HttpHeaders headers = new HttpHeaders(); | 146 | HttpHeaders headers = new HttpHeaders(); |
| 123 | headers.setContentType(MediaType.APPLICATION_JSON); | 147 | headers.setContentType(MediaType.APPLICATION_JSON); |
| 124 | 148 | ... | ... |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment