Jiaqi Xia

feat: 微信点餐推送

...@@ -83,4 +83,9 @@ public interface ConfigConstants { ...@@ -83,4 +83,9 @@ 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 + // 公众号配置(用于订阅消息推送)
87 + String OFFICIAL_ACCOUNT_APP_ID = "${officialAccount.appId}";
88 + String OFFICIAL_ACCOUNT_APP_SECRET = "${officialAccount.appSecret}";
89 + String OFFICIAL_ACCOUNT_ORDER_REMINDER_TEMPLATE_ID = "${officialAccount.orderReminderTemplateId}";
90 +
86 } 91 }
......
1 +package com.infoloop.tianting.logic.task;
2 +
3 +import lombok.RequiredArgsConstructor;
4 +import lombok.extern.slf4j.Slf4j;
5 +import org.springframework.boot.CommandLineRunner;
6 +import org.springframework.stereotype.Component;
7 +
8 +import java.util.Arrays;
9 +
10 +/**
11 + * 订餐提醒任务命令行执行器
12 + * 通过命令行参数 --trigger-order-reminder 触发执行
13 + *
14 + * 使用方式:
15 + * java -jar app.jar --trigger-order-reminder
16 + * 或者
17 + * java -jar app.jar --spring.main.web-application-type=none --trigger-order-reminder
18 + */
19 +@Slf4j
20 +@Component
21 +@RequiredArgsConstructor
22 +public class OrderReminderTaskRunner implements CommandLineRunner {
23 +
24 + private final OrderReminderTask orderReminderTask;
25 +
26 + @Override
27 + public void run(String... args) {
28 + boolean shouldTrigger = Arrays.asList(args).contains("--trigger-order-reminder");
29 + if (shouldTrigger) {
30 + log.info("OrderReminderTaskRunner: Triggering order reminder task via command line");
31 + orderReminderTask.executeTask();
32 + log.info("OrderReminderTaskRunner: Task execution completed");
33 + }
34 + }
35 +}
...@@ -4,19 +4,24 @@ import com.infoloop.tianting.model.dto.WxUserDto.SendSubscriptionMessageResponse ...@@ -4,19 +4,24 @@ import com.infoloop.tianting.model.dto.WxUserDto.SendSubscriptionMessageResponse
4 4
5 /** 5 /**
6 * 订餐提醒服务接口 6 * 订餐提醒服务接口
7 - * 用于发送订餐周期开启前的提醒消息 7 + * 用于发送订餐周期开启前的提醒消息(小程序订阅消息)
8 */ 8 */
9 public interface OrderReminderService { 9 public interface OrderReminderService {
10 /** 10 /**
11 * 发送订餐提醒消息 11 * 发送订餐提醒消息
12 * 12 *
13 - * @param openId 用户 OpenID 13 + * 小程序模板字段(模板ID: LtCsW7ciF0edUwNwt3h5C_ZCIRzOeO7MnvozRl2CxlE):
14 - * @param page 点击跳转页面路径,例如:pages/order/index?cycleId=202501 14 + * - time4: 开始时间
15 - * @param reminderContent 提醒内容,例如:"下次订餐即将开启" 15 + * - time5: 结束时间
16 - * @param orderTime 订餐时间,例如:"周一 09:00 至 周三 12:00" 16 + * - thing1: 温馨提醒
17 - * @param tips 温馨提示,例如:"请点击卡片,准时进入小程序完成预订" 17 + *
18 + * @param openId 用户 OpenID(小程序的 openId)
19 + * @param page 点击跳转小程序页面路径
20 + * @param startTime 开始时间,例如:"周一 09:00"
21 + * @param endTime 结束时间,例如:"周四 18:00"
22 + * @param tips 温馨提醒,例如:"请点击卡片,准时进入小程序完成预订"
18 * @return 发送结果 23 * @return 发送结果
19 */ 24 */
20 SendSubscriptionMessageResponse sendOrderReminder(String openId, String page, 25 SendSubscriptionMessageResponse sendOrderReminder(String openId, String page,
21 - String reminderContent, String orderTime, String tips); 26 + String startTime, String endTime, String tips);
22 } 27 }
......
...@@ -12,8 +12,13 @@ import com.infoloop.tianting.mealorderservice.CreateMpAccountDinerRefRpcRequest; ...@@ -12,8 +12,13 @@ import com.infoloop.tianting.mealorderservice.CreateMpAccountDinerRefRpcRequest;
12 import com.infoloop.tianting.mealorderservice.CreateMpAccountDinerRefRpcResponse; 12 import com.infoloop.tianting.mealorderservice.CreateMpAccountDinerRefRpcResponse;
13 import com.infoloop.tianting.mealorderservice.CreateMpAccountRpcRequest; 13 import com.infoloop.tianting.mealorderservice.CreateMpAccountRpcRequest;
14 import com.infoloop.tianting.mealorderservice.CreateMpAccountRpcResponse; 14 import com.infoloop.tianting.mealorderservice.CreateMpAccountRpcResponse;
15 +import com.infoloop.tianting.mealorderservice.BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest;
15 import com.infoloop.tianting.mealorderservice.CreateOrderSubscriptionMessageRpcRequest; 16 import com.infoloop.tianting.mealorderservice.CreateOrderSubscriptionMessageRpcRequest;
16 import com.infoloop.tianting.mealorderservice.DeleteDinersByIdsRpcRequest; 17 import com.infoloop.tianting.mealorderservice.DeleteDinersByIdsRpcRequest;
18 +import com.infoloop.tianting.mealorderservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
19 +import com.infoloop.tianting.mealorderservice.OrderSubscriptionMessageRpcResponse;
20 +import com.infoloop.tianting.mealorderservice.PendingOrderSubscriptionMessageRpcResponse;
21 +import com.infoloop.tianting.mealorderservice.QueryPendingOrderSubscriptionMessagesRpcRequest;
17 import com.infoloop.tianting.mealorderservice.DeleteMpAccountDinerRefsByIdsRpcRequest; 22 import com.infoloop.tianting.mealorderservice.DeleteMpAccountDinerRefsByIdsRpcRequest;
18 import com.infoloop.tianting.mealorderservice.DinerCreation; 23 import com.infoloop.tianting.mealorderservice.DinerCreation;
19 import com.infoloop.tianting.mealorderservice.DinerModification; 24 import com.infoloop.tianting.mealorderservice.DinerModification;
...@@ -37,6 +42,7 @@ import com.infoloop.tianting.mealorderservice.QueryGradeClassesByConditionRpcReq ...@@ -37,6 +42,7 @@ import com.infoloop.tianting.mealorderservice.QueryGradeClassesByConditionRpcReq
37 import com.infoloop.tianting.mealorderservice.QueryLatestMealMenuRecordsByClientIdsRpcRequest; 42 import com.infoloop.tianting.mealorderservice.QueryLatestMealMenuRecordsByClientIdsRpcRequest;
38 import com.infoloop.tianting.mealorderservice.QueryMealOrdersByConditionRpcRequest; 43 import com.infoloop.tianting.mealorderservice.QueryMealOrdersByConditionRpcRequest;
39 import com.infoloop.tianting.mealorderservice.QueryMpAccountDinerRefsByConditionRpcRequest; 44 import com.infoloop.tianting.mealorderservice.QueryMpAccountDinerRefsByConditionRpcRequest;
45 +import com.infoloop.tianting.mealorderservice.QueryMpAccountsByConditionRpcRequest;
40 import com.infoloop.tianting.mealorderservice.QueryReserveMealOrdersByDinerIdsRpcRequest; 46 import com.infoloop.tianting.mealorderservice.QueryReserveMealOrdersByDinerIdsRpcRequest;
41 import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse; 47 import com.infoloop.tianting.mealorderservice.SingleDinerRpcResponse;
42 import com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse; 48 import com.infoloop.tianting.mealorderservice.SingleGradeClassRpcResponse;
...@@ -105,6 +111,19 @@ public class MealOrderServiceRpcClient { ...@@ -105,6 +111,19 @@ public class MealOrderServiceRpcClient {
105 .build()); 111 .build());
106 } 112 }
107 113
114 + /**
115 + * 查询所有活跃的小程序用户
116 + */
117 + public List<SingleMpAccountRpcResponse> queryAllActiveMpAccounts(int enterpriseId) {
118 + return mealOrderServiceRpcBlockingStub.queryMpAccountsByCondition(
119 + QueryMpAccountsByConditionRpcRequest.newBuilder()
120 + .setEnterpriseId(enterpriseId)
121 + .setShouldFilterStatus(false)
122 + .setIncludeDeleted(false)
123 + .build()
124 + ).getResponsesList();
125 + }
126 +
108 public List<SingleMpAccountDinerRefRpcResponse> queryMpAccountDinerRefsByCondition(int enterpriseId, List<Integer> mpAccountIds, List<Integer> dinerIds) { 127 public List<SingleMpAccountDinerRefRpcResponse> queryMpAccountDinerRefsByCondition(int enterpriseId, List<Integer> mpAccountIds, List<Integer> dinerIds) {
109 return mealOrderServiceRpcBlockingStub.queryMpAccountDinerRefsByCondition(QueryMpAccountDinerRefsByConditionRpcRequest.newBuilder() 128 return mealOrderServiceRpcBlockingStub.queryMpAccountDinerRefsByCondition(QueryMpAccountDinerRefsByConditionRpcRequest.newBuilder()
110 .setEnterpriseId(enterpriseId) 129 .setEnterpriseId(enterpriseId)
...@@ -328,4 +347,48 @@ public class MealOrderServiceRpcClient { ...@@ -328,4 +347,48 @@ public class MealOrderServiceRpcClient {
328 } 347 }
329 return mealOrderServiceRpcBlockingStub.createOrderSubscriptionMessage(req.build()).getId(); 348 return mealOrderServiceRpcBlockingStub.createOrderSubscriptionMessage(req.build()).getId();
330 } 349 }
350 +
351 + /**
352 + * 查询用户订阅消息历史(未删除的记录)
353 + */
354 + public List<OrderSubscriptionMessageRpcResponse> getUserOrderSubscriptionMessageHistory(
355 + int enterpriseId,
356 + String openId,
357 + @Nullable Long orderPeriodStartDate
358 + ) {
359 + final var reqBuilder = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
360 + .setEnterpriseId(enterpriseId)
361 + .setOpenId(openId);
362 + if (orderPeriodStartDate != null) {
363 + reqBuilder.setOrderPeriodStartDate(orderPeriodStartDate);
364 + }
365 + return mealOrderServiceRpcBlockingStub.getUserOrderSubscriptionMessageHistory(reqBuilder.build())
366 + .getResponseList();
367 + }
368 +
369 + /**
370 + * 批量删除订阅消息(逻辑删除,发送成功后调用)
371 + */
372 + public int batchDeleteOrderSubscriptionMessagesByIds(int enterpriseId, List<Long> ids) {
373 + if (CollectionUtils.isEmpty(ids)) {
374 + return 0;
375 + }
376 + return mealOrderServiceRpcBlockingStub.batchDeleteOrderSubscriptionMessagesByIds(
377 + BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest.newBuilder()
378 + .setEnterpriseId(enterpriseId)
379 + .addAllIds(ids)
380 + .build()
381 + ).getAffectedRows();
382 + }
383 +
384 + /**
385 + * 查询待发送的订阅消息(未删除的记录)
386 + */
387 + public List<PendingOrderSubscriptionMessageRpcResponse> queryPendingOrderSubscriptionMessages(int enterpriseId) {
388 + return mealOrderServiceRpcBlockingStub.queryPendingOrderSubscriptionMessages(
389 + QueryPendingOrderSubscriptionMessagesRpcRequest.newBuilder()
390 + .setEnterpriseId(enterpriseId)
391 + .build()
392 + ).getResponsesList();
393 + }
331 } 394 }
......
1 +package com.infoloop.tianting.service.client;
2 +
3 +import com.infoloop.tianting.model.dto.WxUserDto.SendSubscriptionMessageResponse;
4 +import com.infoloop.tianting.model.dto.WxUserDto.WxUserTokenDto;
5 +import com.infoloop.tianting.utils.JsonUtil;
6 +import lombok.RequiredArgsConstructor;
7 +import lombok.extern.slf4j.Slf4j;
8 +import org.apache.commons.lang3.StringUtils;
9 +import org.springframework.beans.factory.annotation.Autowired;
10 +import org.springframework.beans.factory.annotation.Value;
11 +import org.springframework.data.redis.core.StringRedisTemplate;
12 +import org.springframework.http.HttpEntity;
13 +import org.springframework.http.HttpHeaders;
14 +import org.springframework.http.MediaType;
15 +import org.springframework.http.ResponseEntity;
16 +import org.springframework.stereotype.Service;
17 +import org.springframework.web.client.RestTemplate;
18 +
19 +import java.util.HashMap;
20 +import java.util.Map;
21 +import java.util.concurrent.TimeUnit;
22 +
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 +/**
27 + * 微信公众号 HTTP 客户端
28 + * 用于发送公众号一次性订阅消息
29 + */
30 +@Slf4j
31 +@Service
32 +@RequiredArgsConstructor(onConstructor = @__(@Autowired))
33 +public class WxOfficialAccountHttpClient {
34 +
35 + /**
36 + * 公众号一次性订阅消息接口
37 + * 文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/One-time_subscription_info.html
38 + */
39 + private static final String SUBSCRIBE_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/subscribe?access_token={accessToken}";
40 +
41 + private static final String ACCESS_TOKEN_CACHE_KEY = "wx:official_account:access_token";
42 +
43 + private final RestTemplate restTemplate;
44 + private final StringRedisTemplate stringRedisTemplate;
45 +
46 + @Value(OFFICIAL_ACCOUNT_APP_ID)
47 + private String appId;
48 +
49 + @Value(OFFICIAL_ACCOUNT_APP_SECRET)
50 + private String appSecret;
51 +
52 + private final Object lock = new Object();
53 +
54 + /**
55 + * 获取公众号 access_token(带缓存)
56 + */
57 + public String fetchStableAccessToken() {
58 + log.info("Fetching Official Account access token...");
59 + String cachedToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_CACHE_KEY);
60 + if (StringUtils.isNotEmpty(cachedToken)) {
61 + log.info("Using cached Official Account access token");
62 + return cachedToken;
63 + }
64 +
65 + synchronized (lock) {
66 + cachedToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_CACHE_KEY);
67 + if (StringUtils.isNotEmpty(cachedToken)) {
68 + log.info("Using cached Official Account access token after lock");
69 + return cachedToken;
70 + }
71 +
72 + String url = "https://api.weixin.qq.com/cgi-bin/stable_token";
73 + HttpHeaders headers = new HttpHeaders();
74 + headers.setContentType(MediaType.APPLICATION_JSON);
75 + Map<String, String> requestBody = new HashMap<>();
76 + requestBody.put("grant_type", "client_credential");
77 + requestBody.put("appid", appId);
78 + requestBody.put("secret", appSecret);
79 + HttpEntity<Map<String, String>> request = new HttpEntity<>(requestBody, headers);
80 +
81 + ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
82 + if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
83 + log.error("Failed to fetch Official Account access token: {}", response);
84 + throw new IllegalArgumentException("获取公众号 access_token 失败");
85 + }
86 +
87 + WxUserTokenDto tokenDto = JsonUtil.readJsonAs(response.getBody(), WxUserTokenDto.class);
88 + log.info("Official Account access token received, expires_in: {}", tokenDto.getExpires_in());
89 +
90 + String accessToken = tokenDto.getAccess_token();
91 + // 缓存 token,提前 5 分钟过期
92 + long expireSeconds = tokenDto.getExpires_in() - 300;
93 + if (expireSeconds > 0) {
94 + stringRedisTemplate.opsForValue().set(ACCESS_TOKEN_CACHE_KEY, accessToken, expireSeconds, TimeUnit.SECONDS);
95 + }
96 + return accessToken;
97 + }
98 + }
99 +
100 + /**
101 + * 发送公众号一次性订阅消息
102 + *
103 + * @param openId 用户在公众号下的 OpenID
104 + * @param templateId 公众号订阅消息模板 ID
105 + * @param scene 订阅场景值(用户授权时传入的 scene)
106 + * @param title 消息标题(15字以内)
107 + * @param data 模板数据,格式为 Map<String, Map<String, String>>
108 + * @param url 点击跳转的 URL(可选)
109 + * @param miniprogram 跳转小程序配置(可选)
110 + * @return 发送结果
111 + */
112 + public SendSubscriptionMessageResponse sendSubscriptionMessage(
113 + String openId,
114 + String templateId,
115 + String scene,
116 + String title,
117 + Map<String, Object> data,
118 + String url,
119 + Map<String, String> miniprogram) {
120 + try {
121 + String accessToken = fetchStableAccessToken();
122 + HttpHeaders headers = new HttpHeaders();
123 + headers.setContentType(MediaType.APPLICATION_JSON);
124 +
125 + Map<String, Object> requestBody = new HashMap<>();
126 + requestBody.put("touser", openId);
127 + requestBody.put("template_id", templateId);
128 + requestBody.put("scene", scene);
129 + requestBody.put("title", title);
130 + requestBody.put("data", data);
131 +
132 + if (StringUtils.isNotEmpty(url)) {
133 + requestBody.put("url", url);
134 + }
135 + if (miniprogram != null && !miniprogram.isEmpty()) {
136 + requestBody.put("miniprogram", miniprogram);
137 + }
138 +
139 + HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
140 + ResponseEntity<SendSubscriptionMessageResponse> response = restTemplate.postForEntity(
141 + SUBSCRIBE_MESSAGE_URL, requestEntity, SendSubscriptionMessageResponse.class, accessToken);
142 +
143 + if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
144 + log.error("Failed to send Official Account subscription message: {}", response);
145 + throw new IllegalArgumentException("发送公众号订阅消息失败");
146 + }
147 +
148 + SendSubscriptionMessageResponse result = response.getBody();
149 + if (result.getErrcode() != null && result.getErrcode() != 0) {
150 + log.error("Official Account subscription message error: errcode={}, errmsg={}",
151 + result.getErrcode(), result.getErrmsg());
152 + } else {
153 + log.info("Official Account subscription message sent successfully to openId: {}", openId);
154 + }
155 +
156 + return result;
157 + } catch (Exception e) {
158 + log.error("Error sending Official Account subscription message to openId: {}", openId, e);
159 + throw new IllegalArgumentException("发送公众号订阅消息失败: " + e.getMessage());
160 + }
161 + }
162 +
163 + /**
164 + * 发送公众号一次性订阅消息(简化版,跳转到小程序)
165 + */
166 + public SendSubscriptionMessageResponse sendSubscriptionMessageToMiniProgram(
167 + String openId,
168 + String templateId,
169 + String scene,
170 + String title,
171 + Map<String, Object> data,
172 + String miniProgramAppId,
173 + String miniProgramPagePath) {
174 + Map<String, String> miniprogram = new HashMap<>();
175 + miniprogram.put("appid", miniProgramAppId);
176 + miniprogram.put("pagepath", miniProgramPagePath);
177 + return sendSubscriptionMessage(openId, templateId, scene, title, data, null, miniprogram);
178 + }
179 +}
...@@ -16,7 +16,16 @@ import static com.infoloop.tianting.constant.ConfigConstants.MINI_PROGRAM_ORDER_ ...@@ -16,7 +16,16 @@ import static com.infoloop.tianting.constant.ConfigConstants.MINI_PROGRAM_ORDER_
16 16
17 /** 17 /**
18 * 订餐提醒服务实现 18 * 订餐提醒服务实现
19 - * 将业务参数转换为微信订阅消息模板格式并发送 19 + * 使用小程序订阅消息接口发送提醒
20 + *
21 + * 小程序模板字段(模板ID: LtCsW7ciF0edUwNwt3h5C_ZCIRzOeO7MnvozRl2CxlE):
22 + * - time4: 开始时间
23 + * - time5: 结束时间
24 + * - thing1: 温馨提醒
25 + *
26 + * 注意:
27 + * 1. 使用的是小程序的 access_token 和 template_id
28 + * 2. openId 是用户在小程序下的 openId
20 */ 29 */
21 @Slf4j 30 @Slf4j
22 @Service 31 @Service
...@@ -31,36 +40,41 @@ public class OrderReminderServiceImpl implements OrderReminderService { ...@@ -31,36 +40,41 @@ public class OrderReminderServiceImpl implements OrderReminderService {
31 @Override 40 @Override
32 public SendSubscriptionMessageResponse sendOrderReminder(String openId, 41 public SendSubscriptionMessageResponse sendOrderReminder(String openId,
33 String page, 42 String page,
34 - String reminderContent, 43 + String startTime,
35 - String orderTime, 44 + String endTime,
36 String tips) { 45 String tips) {
37 - log.info("Sending order reminder to openId: {}, page: {}", openId, page); 46 + log.info("Sending order reminder via MiniProgram to openId: {}, page: {}, startTime: {}, endTime: {}, tips: {}",
47 + openId, page, startTime, endTime, tips);
38 48
39 - // 将业务参数转换为微信模板数据格式 49 + // 构建小程序订阅消息模板数据
40 - // 根据文档,模板字段为:thing1 (提醒内容), time2 (订餐时间), thing3 (温馨提示)
41 Map<String, Object> data = new HashMap<>(); 50 Map<String, Object> data = new HashMap<>();
42 51
43 - // thing1: 提醒内容 52 + // time4: 开始时间
44 - Map<String, String> thing1Value = new HashMap<>(); 53 + Map<String, String> time4Value = new HashMap<>();
45 - thing1Value.put("value", reminderContent); 54 + time4Value.put("value", startTime);
46 - data.put("thing1", thing1Value); 55 + data.put("time4", time4Value);
47 56
48 - // time2: 订餐时间 57 + // time5: 结束时间
49 - Map<String, String> time2Value = new HashMap<>(); 58 + Map<String, String> time5Value = new HashMap<>();
50 - time2Value.put("value", orderTime); 59 + time5Value.put("value", endTime);
51 - data.put("time2", time2Value); 60 + data.put("time5", time5Value);
52 61
53 - // thing3: 温馨提示 62 + // thing1: 温馨提醒
54 - Map<String, String> thing3Value = new HashMap<>(); 63 + Map<String, String> thing1Value = new HashMap<>();
55 - thing3Value.put("value", tips); 64 + thing1Value.put("value", tips);
56 - data.put("thing3", thing3Value); 65 + data.put("thing1", thing1Value);
57 66
58 try { 67 try {
68 + // 使用小程序订阅消息接口
59 SendSubscriptionMessageResponse response = wxMiniProgramService.sendSubscriptionMessage( 69 SendSubscriptionMessageResponse response = wxMiniProgramService.sendSubscriptionMessage(
60 - openId, templateId, page, data); 70 + openId,
71 + templateId,
72 + page,
73 + data
74 + );
61 75
62 if (response.getErrcode() != null && response.getErrcode() == 0) { 76 if (response.getErrcode() != null && response.getErrcode() == 0) {
63 - log.info("Order reminder sent successfully to openId: {}", openId); 77 + log.info("Order reminder sent successfully via MiniProgram to openId: {}", openId);
64 } else { 78 } else {
65 log.warn("Order reminder sent with error. openId: {}, errcode: {}, errmsg: {}", 79 log.warn("Order reminder sent with error. openId: {}, errcode: {}, errmsg: {}",
66 openId, response.getErrcode(), response.getErrmsg()); 80 openId, response.getErrcode(), response.getErrmsg());
...@@ -68,7 +82,7 @@ public class OrderReminderServiceImpl implements OrderReminderService { ...@@ -68,7 +82,7 @@ public class OrderReminderServiceImpl implements OrderReminderService {
68 82
69 return response; 83 return response;
70 } catch (Exception e) { 84 } catch (Exception e) {
71 - log.error("Failed to send order reminder to openId: {}", openId, e); 85 + log.error("Failed to send order reminder via MiniProgram to openId: {}", openId, e);
72 throw e; 86 throw e;
73 } 87 }
74 } 88 }
......
...@@ -69,6 +69,7 @@ service MealOrderServiceRpc { ...@@ -69,6 +69,7 @@ service MealOrderServiceRpc {
69 rpc GetUserOrderSubscriptionMessageHistory (GetUserOrderSubscriptionMessageHistoryRpcRequest) returns (GetUserOrderSubscriptionMessageHistoryRpcResponse) {} 69 rpc GetUserOrderSubscriptionMessageHistory (GetUserOrderSubscriptionMessageHistoryRpcRequest) returns (GetUserOrderSubscriptionMessageHistoryRpcResponse) {}
70 rpc BatchDeleteOrderSubscriptionMessagesByIds (BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse) {} 70 rpc BatchDeleteOrderSubscriptionMessagesByIds (BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
71 rpc BatchPhysicalDeleteOrderSubscriptionMessagesByIds (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse) {} 71 rpc BatchPhysicalDeleteOrderSubscriptionMessagesByIds (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
72 + rpc QueryPendingOrderSubscriptionMessages (QueryPendingOrderSubscriptionMessagesRpcRequest) returns (QueryPendingOrderSubscriptionMessagesRpcResponse) {}
72 } 73 }
73 74
74 enum MealOrderOrderMethodEnum { 75 enum MealOrderOrderMethodEnum {
...@@ -1013,3 +1014,24 @@ message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest { ...@@ -1013,3 +1014,24 @@ message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest {
1013 message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse { 1014 message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse {
1014 int32 affectedRows = 1; // 受影响的行数 1015 int32 affectedRows = 1; // 受影响的行数
1015 } 1016 }
1017 +
1018 +// 查询待发送的订阅消息(未删除的记录)
1019 +message QueryPendingOrderSubscriptionMessagesRpcRequest {
1020 + int32 enterpriseId = 1;
1021 +}
1022 +
1023 +message PendingOrderSubscriptionMessageRpcResponse {
1024 + int64 id = 1;
1025 + int32 enterpriseId = 2;
1026 + int32 dinerId = 3;
1027 + string openId = 4;
1028 + string templateId = 5;
1029 + int64 orderPeriodStartDate = 6;
1030 + string jumpPath = 7;
1031 + int64 orderPeriodEndDate = 8;
1032 + int64 createdAt = 9;
1033 +}
1034 +
1035 +message QueryPendingOrderSubscriptionMessagesRpcResponse {
1036 + repeated PendingOrderSubscriptionMessageRpcResponse responses = 1;
1037 +}
......