chen.yinxiang
Committed by Jiaqi Xia

农信小程序订阅消息记录数据库

...@@ -11,6 +11,7 @@ import brave.propagation.TraceContext; ...@@ -11,6 +11,7 @@ import brave.propagation.TraceContext;
11 import brave.sampler.Sampler; 11 import brave.sampler.Sampler;
12 import com.tianting.infoloop.service.grpc.MealOrderGrpcService; 12 import com.tianting.infoloop.service.grpc.MealOrderGrpcService;
13 import com.tianting.infoloop.service.grpc.OrderGrpcService; 13 import com.tianting.infoloop.service.grpc.OrderGrpcService;
14 +import com.tianting.infoloop.service.grpc.OrderSubscriptionMessageGrpcService;
14 import io.grpc.Server; 15 import io.grpc.Server;
15 import io.grpc.ServerInterceptor; 16 import io.grpc.ServerInterceptor;
16 import io.grpc.ServerInterceptors; 17 import io.grpc.ServerInterceptors;
...@@ -66,11 +67,13 @@ public class AppConfig implements ApplicationContextAware { ...@@ -66,11 +67,13 @@ public class AppConfig implements ApplicationContextAware {
66 public Server serviceServer(@Value(SERVICE_PORT) final int port, 67 public Server serviceServer(@Value(SERVICE_PORT) final int port,
67 final ServerInterceptor serverInterceptor, 68 final ServerInterceptor serverInterceptor,
68 final OrderGrpcService orderGrpcService, 69 final OrderGrpcService orderGrpcService,
69 - final MealOrderGrpcService mealOrderGrpcService) { 70 + final MealOrderGrpcService mealOrderGrpcService,
71 + final OrderSubscriptionMessageGrpcService orderSubscriptionMessageGrpcService) {
70 return NettyServerBuilder 72 return NettyServerBuilder
71 .forPort(port) 73 .forPort(port)
72 .addService(ServerInterceptors.intercept(orderGrpcService, serverInterceptor)) 74 .addService(ServerInterceptors.intercept(orderGrpcService, serverInterceptor))
73 .addService(ServerInterceptors.intercept(mealOrderGrpcService, serverInterceptor)) 75 .addService(ServerInterceptors.intercept(mealOrderGrpcService, serverInterceptor))
76 + .addService(ServerInterceptors.intercept(orderSubscriptionMessageGrpcService, serverInterceptor))
74 .build(); 77 .build();
75 } 78 }
76 } 79 }
......
1 +package com.tianting.infoloop.mapper;
2 +
3 +import com.tianting.infoloop.inject.MyBaseMapper;
4 +import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
5 +import org.apache.ibatis.annotations.Delete;
6 +import org.apache.ibatis.annotations.Param;
7 +
8 +import java.util.List;
9 +
10 +public interface OrderSubscriptionMessageMapper extends MyBaseMapper<OrderSubscriptionMessageDb> {
11 +
12 + /**
13 + * 批量物理删除(绕过逻辑删除)
14 + */
15 + @Delete("<script>" +
16 + "DELETE FROM order_subscription_message " +
17 + "WHERE enterpriseId = #{enterpriseId} " +
18 + "AND id IN " +
19 + "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
20 + "#{id}" +
21 + "</foreach>" +
22 + "</script>")
23 + int physicalDeleteByIds(@Param("ids") List<Long> ids, @Param("enterpriseId") int enterpriseId);
24 +}
25 +
1 +package com.tianting.infoloop.model.db;
2 +
3 +import com.baomidou.mybatisplus.annotation.FieldFill;
4 +import com.baomidou.mybatisplus.annotation.IdType;
5 +import com.baomidou.mybatisplus.annotation.TableField;
6 +import com.baomidou.mybatisplus.annotation.TableId;
7 +import com.baomidou.mybatisplus.annotation.TableLogic;
8 +import com.baomidou.mybatisplus.annotation.TableName;
9 +import com.baomidou.mybatisplus.extension.activerecord.Model;
10 +import lombok.Data;
11 +import lombok.EqualsAndHashCode;
12 +import lombok.experimental.Accessors;
13 +
14 +import java.time.LocalDateTime;
15 +
16 +/**
17 + * order subscription message DB
18 + * @TableName order_subscription_message
19 + */
20 +@Data
21 +@Accessors(chain = true)
22 +@EqualsAndHashCode(callSuper = true)
23 +@TableName(value = "order_subscription_message")
24 +public class OrderSubscriptionMessageDb extends Model<OrderSubscriptionMessageDb> {
25 + /**
26 + * 主键ID
27 + */
28 + @TableId(type = IdType.AUTO)
29 + private Long id;
30 + /**
31 + * 企业ID
32 + */
33 + private Integer enterpriseId;
34 + /**
35 + * 订餐用户ID(dinerId)
36 + */
37 + private Integer dinerId;
38 + /**
39 + * 微信用户OpenID
40 + */
41 + private String openId;
42 + /**
43 + * 订阅消息模板ID
44 + */
45 + private String templateId;
46 + /**
47 + * 跳转路径(小程序 path)
48 + */
49 + private String jumpPath;
50 + /**
51 + * 关联的订餐周期开始时间(精确到秒)
52 + */
53 + private LocalDateTime orderPeriodStartDate;
54 + /**
55 + * 关联的订餐周期结束时间(精确到秒)
56 + */
57 + private LocalDateTime orderPeriodEndDate;
58 + /**
59 + * 授权时间(创建时间)
60 + */
61 + @TableField(fill = FieldFill.INSERT)
62 + private LocalDateTime createdAt;
63 + /**
64 + * 更新时间
65 + */
66 + @TableField(fill = FieldFill.INSERT_UPDATE)
67 + private LocalDateTime updatedAt;
68 + /**
69 + * 授权状态,0-待使用(已授权但未发送),1-已使用(已发送,逻辑删除)
70 + */
71 + @TableLogic
72 + private Boolean isDeleted;
73 +}
74 +
1 +package com.tianting.infoloop.service;
2 +
3 +import com.baomidou.mybatisplus.extension.service.IService;
4 +import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
5 +import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
6 +import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
7 +
8 +import java.util.List;
9 +
10 +public interface OrderSubscriptionMessageDbService extends IService<OrderSubscriptionMessageDb> {
11 +
12 + OrderSubscriptionMessageDb createOrderSubscriptionMessage(CreateOrderSubscriptionMessageRpcRequest request);
13 +
14 + List<OrderSubscriptionMessageDb> getUserOrderSubscriptionMessageHistory(GetUserOrderSubscriptionMessageHistoryRpcRequest request);
15 +
16 + /**
17 + * 批量逻辑删除授权记录(表示已使用)
18 + * @param ids 要删除的记录ID列表
19 + * @param enterpriseId 企业ID
20 + * @return 受影响的行数
21 + */
22 + int batchDeleteByIds(java.util.List<Long> ids, int enterpriseId);
23 +
24 + /**
25 + * 批量物理删除授权记录
26 + * @param ids 要删除的记录ID列表
27 + * @param enterpriseId 企业ID
28 + * @return 受影响的行数
29 + */
30 + int batchPhysicalDeleteByIds(java.util.List<Long> ids, int enterpriseId);
31 +}
32 +
1 +package com.tianting.infoloop.service.grpc;
2 +
3 +import com.baomidou.dynamic.datasource.annotation.DS;
4 +import com.infoloop.tianting.ordersubscriptionmessageservice.BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest;
5 +import com.infoloop.tianting.ordersubscriptionmessageservice.BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse;
6 +import com.infoloop.tianting.ordersubscriptionmessageservice.BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest;
7 +import com.infoloop.tianting.ordersubscriptionmessageservice.BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse;
8 +import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
9 +import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcResponse;
10 +import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
11 +import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcResponse;
12 +import com.infoloop.tianting.ordersubscriptionmessageservice.OrderSubscriptionMessageRpcResponse;
13 +import com.infoloop.tianting.ordersubscriptionmessageservice.OrderSubscriptionMessageServiceRpcGrpc;
14 +import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
15 +import com.tianting.infoloop.service.OrderSubscriptionMessageDbService;
16 +import io.grpc.Status;
17 +import io.grpc.stub.StreamObserver;
18 +import lombok.RequiredArgsConstructor;
19 +import lombok.extern.slf4j.Slf4j;
20 +import org.springframework.beans.factory.annotation.Autowired;
21 +import org.springframework.stereotype.Service;
22 +
23 +import java.time.ZoneId;
24 +import java.util.stream.Collectors;
25 +
26 +import static com.tianting.infoloop.constants.ConfigConstants.DATA_SOURCE_MASTER;
27 +
28 +@Slf4j
29 +@Service
30 +@RequiredArgsConstructor(onConstructor_ = @Autowired)
31 +public class OrderSubscriptionMessageGrpcService extends OrderSubscriptionMessageServiceRpcGrpc.OrderSubscriptionMessageServiceRpcImplBase {
32 + private final OrderSubscriptionMessageDbService orderSubscriptionMessageDbService;
33 +
34 + @Override
35 + @DS(DATA_SOURCE_MASTER)
36 + public void createOrderSubscriptionMessage(final CreateOrderSubscriptionMessageRpcRequest request,
37 + final StreamObserver<CreateOrderSubscriptionMessageRpcResponse> responseObserver) {
38 + final var builder = CreateOrderSubscriptionMessageRpcResponse.newBuilder();
39 + try {
40 + final var data = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(request);
41 + builder.setId(data.getId());
42 + } catch (final Exception e) {
43 + log.error("createOrderSubscriptionMessage error;", e);
44 + responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
45 + return;
46 + }
47 + responseObserver.onNext(builder.build());
48 + responseObserver.onCompleted();
49 + }
50 +
51 + @Override
52 + @DS(DATA_SOURCE_MASTER)
53 + public void getUserOrderSubscriptionMessageHistory(final GetUserOrderSubscriptionMessageHistoryRpcRequest request,
54 + final StreamObserver<GetUserOrderSubscriptionMessageHistoryRpcResponse> responseObserver) {
55 + final var builder = GetUserOrderSubscriptionMessageHistoryRpcResponse.newBuilder();
56 + try {
57 + final var list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(request);
58 + builder.addAllResponse(list.stream()
59 + .map(this::convertToRpcResponse)
60 + .collect(Collectors.toList()));
61 + } catch (final Exception e) {
62 + log.error("getUserOrderSubscriptionMessageHistory error;", e);
63 + responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
64 + return;
65 + }
66 + responseObserver.onNext(builder.build());
67 + responseObserver.onCompleted();
68 + }
69 +
70 + private OrderSubscriptionMessageRpcResponse convertToRpcResponse(OrderSubscriptionMessageDb db) {
71 + OrderSubscriptionMessageRpcResponse.Builder builder = OrderSubscriptionMessageRpcResponse.newBuilder();
72 + builder.setId(db.getId());
73 +
74 + if (db.getJumpPath() != null) {
75 + builder.setJumpPath(db.getJumpPath());
76 + }
77 +
78 + // 转换LocalDateTime为时间戳(毫秒),与项目中其他时间字段保持一致
79 + if (db.getOrderPeriodStartDate() != null) {
80 + long timestamp = db.getOrderPeriodStartDate().atZone(ZoneId.systemDefault())
81 + .toInstant()
82 + .toEpochMilli();
83 + builder.setOrderPeriodStartDate(timestamp);
84 + }
85 +
86 + if (db.getOrderPeriodEndDate() != null) {
87 + long timestamp = db.getOrderPeriodEndDate().atZone(ZoneId.systemDefault())
88 + .toInstant()
89 + .toEpochMilli();
90 + builder.setOrderPeriodEndDate(timestamp);
91 + }
92 +
93 + // 转换LocalDateTime为时间戳(毫秒)
94 + if (db.getCreatedAt() != null) {
95 + long timestamp = db.getCreatedAt().atZone(ZoneId.systemDefault())
96 + .toInstant()
97 + .toEpochMilli();
98 + builder.setCreatedAt(timestamp);
99 + }
100 +
101 + builder.setIsDeleted(db.getIsDeleted() != null && db.getIsDeleted());
102 + return builder.build();
103 + }
104 +
105 + @Override
106 + @DS(DATA_SOURCE_MASTER)
107 + public void batchDeleteOrderSubscriptionMessagesByIds(final BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest request,
108 + final StreamObserver<BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse> responseObserver) {
109 + final var builder = BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse.newBuilder();
110 + try {
111 + final int affectedRows = orderSubscriptionMessageDbService.batchDeleteByIds(
112 + request.getIdsList(),
113 + request.getEnterpriseId());
114 + builder.setAffectedRows(affectedRows);
115 + } catch (final Exception e) {
116 + log.error("batchDeleteOrderSubscriptionMessagesByIds error;", e);
117 + responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
118 + return;
119 + }
120 + responseObserver.onNext(builder.build());
121 + responseObserver.onCompleted();
122 + }
123 +
124 + @Override
125 + @DS(DATA_SOURCE_MASTER)
126 + public void batchPhysicalDeleteOrderSubscriptionMessagesByIds(
127 + final BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest request,
128 + final StreamObserver<BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse> responseObserver) {
129 + final var builder = BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse.newBuilder();
130 + try {
131 + final int affectedRows = orderSubscriptionMessageDbService.batchPhysicalDeleteByIds(
132 + request.getIdsList(),
133 + request.getEnterpriseId());
134 + builder.setAffectedRows(affectedRows);
135 + } catch (final Exception e) {
136 + log.error("batchPhysicalDeleteOrderSubscriptionMessagesByIds error;", e);
137 + responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
138 + return;
139 + }
140 + responseObserver.onNext(builder.build());
141 + responseObserver.onCompleted();
142 + }
143 +}
144 +
1 +package com.tianting.infoloop.service.impl;
2 +
3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
5 +import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
6 +import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
7 +import com.tianting.infoloop.mapper.OrderSubscriptionMessageMapper;
8 +import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
9 +import com.tianting.infoloop.service.OrderSubscriptionMessageDbService;
10 +import com.tianting.infoloop.utils.exception.ErrorCodeConstants;
11 +import com.tianting.infoloop.utils.exception.ServiceExceptionUtil;
12 +import lombok.RequiredArgsConstructor;
13 +import lombok.extern.slf4j.Slf4j;
14 +import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.stereotype.Service;
16 +
17 +import java.time.Instant;
18 +import java.time.LocalDateTime;
19 +import java.time.ZoneId;
20 +import java.util.List;
21 +
22 +@Slf4j
23 +@Service
24 +@RequiredArgsConstructor(onConstructor_ = @Autowired)
25 +public class OrderSubscriptionMessageDbServiceImpl extends ServiceImpl<OrderSubscriptionMessageMapper, OrderSubscriptionMessageDb> implements OrderSubscriptionMessageDbService {
26 +
27 + @Override
28 + public OrderSubscriptionMessageDb createOrderSubscriptionMessage(CreateOrderSubscriptionMessageRpcRequest request) {
29 + OrderSubscriptionMessageDb orderSubscriptionMessageDb = new OrderSubscriptionMessageDb();
30 + orderSubscriptionMessageDb.setEnterpriseId(request.getEnterpriseId());
31 + if (request.getDinerId() != 0) {
32 + orderSubscriptionMessageDb.setDinerId(request.getDinerId());
33 + }
34 + orderSubscriptionMessageDb.setOpenId(request.getOpenId());
35 + orderSubscriptionMessageDb.setTemplateId(request.getTemplateId());
36 + if (!request.getJumpPath().isEmpty()) {
37 + orderSubscriptionMessageDb.setJumpPath(request.getJumpPath());
38 + }
39 +
40 + // 转换时间戳为LocalDateTime(精确到秒),与项目中其他时间字段保持一致
41 + LocalDateTime orderPeriodStartDate = Instant.ofEpochMilli(request.getOrderPeriodStartDate())
42 + .atZone(ZoneId.systemDefault())
43 + .toLocalDateTime();
44 + orderSubscriptionMessageDb.setOrderPeriodStartDate(orderPeriodStartDate);
45 +
46 + if (request.getOrderPeriodEndDate() > 0) {
47 + LocalDateTime orderPeriodEndDate = Instant.ofEpochMilli(request.getOrderPeriodEndDate())
48 + .atZone(ZoneId.systemDefault())
49 + .toLocalDateTime();
50 + orderSubscriptionMessageDb.setOrderPeriodEndDate(orderPeriodEndDate);
51 + }
52 +
53 + boolean res = this.save(orderSubscriptionMessageDb);
54 + if (!res) {
55 + log.error("创建订阅消息授权记录失败, request: {}", request);
56 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_GEN_FAIL);
57 + }
58 + return orderSubscriptionMessageDb;
59 + }
60 +
61 + @Override
62 + public List<OrderSubscriptionMessageDb> getUserOrderSubscriptionMessageHistory(GetUserOrderSubscriptionMessageHistoryRpcRequest request) {
63 + LambdaQueryWrapper<OrderSubscriptionMessageDb> queryWrapper = new LambdaQueryWrapper<>();
64 + queryWrapper.eq(OrderSubscriptionMessageDb::getEnterpriseId, request.getEnterpriseId());
65 + queryWrapper.eq(OrderSubscriptionMessageDb::getOpenId, request.getOpenId());
66 +
67 + // 如果传入了 orderPeriodStartDate,查询大于等于该值的记录
68 + if (request.getOrderPeriodStartDate() > 0) {
69 + LocalDateTime orderPeriodStartDate = Instant.ofEpochMilli(request.getOrderPeriodStartDate())
70 + .atZone(ZoneId.systemDefault())
71 + .toLocalDateTime();
72 + queryWrapper.ge(OrderSubscriptionMessageDb::getOrderPeriodStartDate, orderPeriodStartDate);
73 + }
74 +
75 + queryWrapper.orderByDesc(OrderSubscriptionMessageDb::getCreatedAt);
76 + return this.list(queryWrapper);
77 + }
78 +
79 + @Override
80 + public int batchDeleteByIds(java.util.List<Long> ids, int enterpriseId) {
81 + if (ids == null || ids.isEmpty()) {
82 + return 0;
83 + }
84 + LambdaQueryWrapper<OrderSubscriptionMessageDb> queryWrapper = new LambdaQueryWrapper<>();
85 + queryWrapper.eq(OrderSubscriptionMessageDb::getEnterpriseId, enterpriseId);
86 + queryWrapper.in(OrderSubscriptionMessageDb::getId, ids);
87 + // 逻辑删除(使用 MyBatis-Plus 的 @TableLogic)
88 + return this.remove(queryWrapper) ? ids.size() : 0;
89 + }
90 +
91 + @Override
92 + public int batchPhysicalDeleteByIds(java.util.List<Long> ids, int enterpriseId) {
93 + if (ids == null || ids.isEmpty()) {
94 + return 0;
95 + }
96 + // 物理删除(使用 Mapper 中的 SQL,绕过逻辑删除)
97 + return this.baseMapper.physicalDeleteByIds(ids, enterpriseId);
98 + }
99 +}
100 +
1 +syntax = "proto3";
2 +
3 +option java_multiple_files = true;
4 +option java_package = "com.infoloop.tianting.ordersubscriptionmessageservice";
5 +option java_outer_classname = "OrderSubscriptionMessageServiceProto";
6 +option objc_class_prefix = "OP";
7 +
8 +package com.infoloop.tianting.ordersubscriptionmessageservice;
9 +
10 +service OrderSubscriptionMessageServiceRpc {
11 + // 创建订阅消息授权
12 + rpc CreateOrderSubscriptionMessage (CreateOrderSubscriptionMessageRpcRequest) returns (CreateOrderSubscriptionMessageRpcResponse) {}
13 +
14 + // 查询用户授权历史
15 + rpc GetUserOrderSubscriptionMessageHistory (GetUserOrderSubscriptionMessageHistoryRpcRequest) returns (GetUserOrderSubscriptionMessageHistoryRpcResponse) {}
16 +
17 + // 批量逻辑删除授权记录(表示已使用)
18 + rpc BatchDeleteOrderSubscriptionMessagesByIds (BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
19 +
20 + // 批量物理删除授权记录
21 + rpc BatchPhysicalDeleteOrderSubscriptionMessagesByIds (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
22 +}
23 +
24 +// 创建订阅消息授权请求
25 +message CreateOrderSubscriptionMessageRpcRequest {
26 + int32 enterpriseId = 1;
27 + int32 dinerId = 2;
28 + string openId = 3;
29 + string templateId = 4;
30 + int64 orderPeriodStartDate = 5; // Unix 时间戳(毫秒),精确到秒
31 + string jumpPath = 6; // 跳转路径(小程序 path),可选
32 + int64 orderPeriodEndDate = 7; // Unix 时间戳(毫秒),精确到秒,可选
33 +}
34 +
35 +// 创建订阅消息授权响应
36 +message CreateOrderSubscriptionMessageRpcResponse {
37 + int64 id = 1;
38 +}
39 +
40 +// 查询用户授权历史请求
41 +message GetUserOrderSubscriptionMessageHistoryRpcRequest {
42 + int32 enterpriseId = 1;
43 + string openId = 2;
44 + int64 orderPeriodStartDate = 3; // Unix 时间戳(毫秒),精确到秒,可选,如果传入则查询大于等于该值的记录
45 +}
46 +
47 +// 订阅消息授权记录
48 +message OrderSubscriptionMessageRpcResponse {
49 + int64 id = 1;
50 + int64 orderPeriodStartDate = 2; // Unix 时间戳(毫秒),精确到秒
51 + int64 createdAt = 3; // Unix 时间戳(毫秒)
52 + bool isDeleted = 4;
53 + string jumpPath = 5; // 跳转路径(小程序 path),可选
54 + int64 orderPeriodEndDate = 6; // Unix 时间戳(毫秒),精确到秒,可选
55 +}
56 +
57 +// 查询用户授权历史响应
58 +message GetUserOrderSubscriptionMessageHistoryRpcResponse {
59 + repeated OrderSubscriptionMessageRpcResponse response = 1;
60 +}
61 +
62 +// 批量逻辑删除授权记录请求
63 +message BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest {
64 + int32 enterpriseId = 1;
65 + repeated int64 ids = 2; // 要删除的记录ID列表
66 +}
67 +
68 +// 批量逻辑删除授权记录响应
69 +message BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse {
70 + int32 affectedRows = 1; // 受影响的行数
71 +}
72 +
73 +// 批量物理删除授权记录请求
74 +message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest {
75 + int32 enterpriseId = 1;
76 + repeated int64 ids = 2; // 要删除的记录ID列表
77 +}
78 +
79 +// 批量物理删除授权记录响应
80 +message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse {
81 + int32 affectedRows = 1; // 受影响的行数
82 +}
83 +
1 +package com.tianting.infoloop.service;
2 +
3 +import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
4 +import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
5 +
6 +import java.util.Arrays;
7 +import java.util.List;
8 +import com.tianting.infoloop.App;
9 +import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
10 +import org.junit.jupiter.api.BeforeAll;
11 +import org.junit.jupiter.api.Test;
12 +import org.junit.jupiter.api.TestInstance;
13 +import org.springframework.beans.factory.annotation.Autowired;
14 +import org.springframework.boot.test.context.SpringBootTest;
15 +import org.springframework.jdbc.core.JdbcTemplate;
16 +import org.springframework.test.context.ActiveProfiles;
17 +
18 +import java.util.List;
19 +
20 +import static org.assertj.core.api.Assertions.assertThat;
21 +
22 +@ActiveProfiles("dev")
23 +@SpringBootTest(classes = App.class)
24 +@TestInstance(TestInstance.Lifecycle.PER_CLASS)
25 +class OrderSubscriptionMessageDbServiceTest {
26 +
27 + @Autowired
28 + private JdbcTemplate jdbcTemplate;
29 +
30 + @Autowired
31 + private OrderSubscriptionMessageDbService orderSubscriptionMessageDbService;
32 +
33 + @BeforeAll
34 + void ensureTable() {
35 + jdbcTemplate.execute(
36 + "CREATE TABLE IF NOT EXISTS order_subscription_message ("
37 + + "id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',"
38 + + "enterpriseId INT NOT NULL COMMENT '企业ID',"
39 + + "dinerId INT DEFAULT NULL COMMENT '订餐用户ID(dinerId)',"
40 + + "openId VARCHAR(255) NOT NULL COMMENT '微信用户OpenID',"
41 + + "templateId VARCHAR(255) NOT NULL COMMENT '订阅消息模板ID',"
42 + + "jumpPath VARCHAR(255) DEFAULT NULL COMMENT '跳转路径(小程序 path)',"
43 + + "orderPeriodStartDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期开始时间(精确到秒)',"
44 + + "orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)',"
45 + + "createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
46 + + "updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',"
47 + + "isDeleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除;0:未删除,1:已删除',"
48 + + "PRIMARY KEY (id),"
49 + + "KEY idx_enterprise_id (enterpriseId)"
50 + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订餐订阅消息授权表';");
51 +
52 + // 兼容本地已存在的旧表结构(修改字段类型为 DATETIME,精确到秒)
53 + Integer startDateType = jdbcTemplate.queryForObject(
54 + "SELECT COUNT(1) FROM information_schema.COLUMNS "
55 + + "WHERE table_schema = DATABASE() "
56 + + "AND table_name = 'order_subscription_message' "
57 + + "AND column_name = 'orderPeriodStartDate' "
58 + + "AND data_type IN ('date', 'bigint')",
59 + Integer.class);
60 + if (startDateType != null && startDateType > 0) {
61 + // 如果字段是 bigint,需要先清空数据或转换数据,这里先清空(测试环境)
62 + jdbcTemplate.update("UPDATE order_subscription_message SET orderPeriodStartDate = NULL WHERE orderPeriodStartDate IS NOT NULL");
63 + jdbcTemplate.execute(
64 + "ALTER TABLE order_subscription_message "
65 + + "MODIFY COLUMN orderPeriodStartDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期开始时间(精确到秒)'");
66 + }
67 +
68 + Integer endDateExists = jdbcTemplate.queryForObject(
69 + "SELECT COUNT(1) FROM information_schema.COLUMNS "
70 + + "WHERE table_schema = DATABASE() "
71 + + "AND table_name = 'order_subscription_message' "
72 + + "AND column_name = 'orderPeriodEndDate'",
73 + Integer.class);
74 + if (endDateExists == null || endDateExists == 0) {
75 + jdbcTemplate.execute(
76 + "ALTER TABLE order_subscription_message "
77 + + "ADD COLUMN orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)' "
78 + + "AFTER orderPeriodStartDate");
79 + } else {
80 + Integer endDateType = jdbcTemplate.queryForObject(
81 + "SELECT COUNT(1) FROM information_schema.COLUMNS "
82 + + "WHERE table_schema = DATABASE() "
83 + + "AND table_name = 'order_subscription_message' "
84 + + "AND column_name = 'orderPeriodEndDate' "
85 + + "AND data_type IN ('date', 'bigint')",
86 + Integer.class);
87 + if (endDateType != null && endDateType > 0) {
88 + // 如果字段是 bigint,需要先清空数据或转换数据,这里先清空(测试环境)
89 + jdbcTemplate.update("UPDATE order_subscription_message SET orderPeriodEndDate = NULL WHERE orderPeriodEndDate IS NOT NULL");
90 + jdbcTemplate.execute(
91 + "ALTER TABLE order_subscription_message "
92 + + "MODIFY COLUMN orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)'");
93 + }
94 + }
95 + }
96 +
97 + @Test
98 + void shouldCreateAndQueryHistory() {
99 + final String openId = "openId_test_junit_001";
100 +
101 + // clean
102 + jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
103 +
104 + // create
105 + CreateOrderSubscriptionMessageRpcRequest createReq = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
106 + .setEnterpriseId(1)
107 + .setDinerId(100)
108 + .setOpenId(openId)
109 + .setTemplateId("template_test_junit_001")
110 + .setJumpPath("/pages/order/index")
111 + .setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00 (毫秒时间戳,转换为 LocalDateTime)
112 + .setOrderPeriodEndDate(1739030400000L) // 2025-02-09 00:00:00 (毫秒时间戳,转换为 LocalDateTime)
113 + .build();
114 +
115 + OrderSubscriptionMessageDb created = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq);
116 + assertThat(created.getId()).isNotNull();
117 + assertThat(created.getEnterpriseId()).isEqualTo(1);
118 + assertThat(created.getDinerId()).isEqualTo(100);
119 + assertThat(created.getJumpPath()).isEqualTo("/pages/order/index");
120 + assertThat(created.getOrderPeriodEndDate()).isNotNull();
121 +
122 + // query history
123 + GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
124 + .setEnterpriseId(1)
125 + .setOpenId(openId)
126 + .setOrderPeriodStartDate(0)
127 + .build();
128 +
129 + List<OrderSubscriptionMessageDb> list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq);
130 + assertThat(list).isNotEmpty();
131 + assertThat(list.get(0).getOpenId()).isEqualTo(openId);
132 + assertThat(list.get(0).getJumpPath()).isEqualTo("/pages/order/index");
133 + assertThat(list.get(0).getOrderPeriodEndDate()).isNotNull();
134 + }
135 +
136 + @Test
137 + void shouldQueryByOrderPeriodStartDateWithGreaterEqual() {
138 + final String openId = "openId_test_junit_date_query";
139 +
140 + // clean
141 + jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
142 +
143 + // 创建多条不同开始时间的记录
144 + // 记录1: 2025-02-03 00:00:00
145 + CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
146 + .setEnterpriseId(1)
147 + .setDinerId(100)
148 + .setOpenId(openId)
149 + .setTemplateId("template_test_date_1")
150 + .setJumpPath("/pages/order/index")
151 + .setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00
152 + .setOrderPeriodEndDate(1739030400000L) // 2025-02-09 00:00:00
153 + .build();
154 + OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
155 +
156 + // 记录2: 2025-02-10 00:00:00 (晚于记录1)
157 + CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
158 + .setEnterpriseId(1)
159 + .setDinerId(100)
160 + .setOpenId(openId)
161 + .setTemplateId("template_test_date_2")
162 + .setJumpPath("/pages/order/index")
163 + .setOrderPeriodStartDate(1739116800000L) // 2025-02-10 00:00:00
164 + .setOrderPeriodEndDate(1739635200000L) // 2025-02-16 00:00:00
165 + .build();
166 + OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
167 +
168 + // 记录3: 2025-01-27 00:00:00 (早于记录1)
169 + CreateOrderSubscriptionMessageRpcRequest createReq3 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
170 + .setEnterpriseId(1)
171 + .setDinerId(100)
172 + .setOpenId(openId)
173 + .setTemplateId("template_test_date_3")
174 + .setJumpPath("/pages/order/index")
175 + .setOrderPeriodStartDate(1737907200000L) // 2025-01-27 00:00:00
176 + .setOrderPeriodEndDate(1738425600000L) // 2025-02-02 00:00:00
177 + .build();
178 + OrderSubscriptionMessageDb created3 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq3);
179 +
180 + // 记录4: 2025-02-03 12:00:00 (等于记录1的日期,但时间更晚)
181 + CreateOrderSubscriptionMessageRpcRequest createReq4 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
182 + .setEnterpriseId(1)
183 + .setDinerId(100)
184 + .setOpenId(openId)
185 + .setTemplateId("template_test_date_4")
186 + .setJumpPath("/pages/order/index")
187 + .setOrderPeriodStartDate(1738555200000L) // 2025-02-03 12:00:00
188 + .setOrderPeriodEndDate(1739073600000L) // 2025-02-09 12:00:00
189 + .build();
190 + OrderSubscriptionMessageDb created4 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq4);
191 +
192 + // 测试1: 查询 orderPeriodStartDate >= 2025-02-03 00:00:00
193 + // 应该返回: 记录1(等于), 记录2(大于), 记录4(大于)
194 + // 不应该返回: 记录3(小于)
195 + GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
196 + .setEnterpriseId(1)
197 + .setOpenId(openId)
198 + .setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00
199 + .build();
200 +
201 + List<OrderSubscriptionMessageDb> list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq);
202 +
203 + // 验证返回的记录数量:应该包含记录1、记录2、记录4,共3条
204 + assertThat(list.size()).isEqualTo(3);
205 +
206 + // 验证所有返回的记录的开始时间都 >= 2025-02-03 00:00:00
207 + java.time.LocalDateTime threshold = java.time.Instant.ofEpochMilli(1738512000000L)
208 + .atZone(java.time.ZoneId.systemDefault())
209 + .toLocalDateTime();
210 +
211 + for (OrderSubscriptionMessageDb db : list) {
212 + assertThat(db.getOrderPeriodStartDate()).isNotNull();
213 + assertThat(db.getOrderPeriodStartDate().isAfter(threshold) || db.getOrderPeriodStartDate().isEqual(threshold))
214 + .as("开始时间应该 >= 2025-02-03 00:00:00,实际: %s", db.getOrderPeriodStartDate())
215 + .isTrue();
216 + }
217 +
218 + // 验证包含记录1(等于)
219 + assertThat(list.stream().anyMatch(db -> db.getId().equals(created1.getId()))).isTrue();
220 +
221 + // 验证包含记录2(大于)
222 + assertThat(list.stream().anyMatch(db -> db.getId().equals(created2.getId()))).isTrue();
223 +
224 + // 验证包含记录4(大于,同一天但时间更晚)
225 + assertThat(list.stream().anyMatch(db -> db.getId().equals(created4.getId()))).isTrue();
226 +
227 + // 验证不包含记录3(小于)
228 + assertThat(list.stream().anyMatch(db -> db.getId().equals(created3.getId()))).isFalse();
229 +
230 + // 测试2: 查询 orderPeriodStartDate >= 2025-02-10 00:00:00
231 + // 应该只返回: 记录2(等于)
232 + GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq2 = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
233 + .setEnterpriseId(1)
234 + .setOpenId(openId)
235 + .setOrderPeriodStartDate(1739116800000L) // 2025-02-10 00:00:00
236 + .build();
237 +
238 + List<OrderSubscriptionMessageDb> list2 = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq2);
239 +
240 + // 验证只返回记录2
241 + assertThat(list2.size()).isEqualTo(1);
242 + assertThat(list2.get(0).getId()).isEqualTo(created2.getId());
243 + }
244 +
245 + @Test
246 + void shouldBatchDeleteByIds() {
247 + final String openId = "openId_test_junit_delete";
248 +
249 + // clean
250 + jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
251 +
252 + // create multiple records
253 + CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
254 + .setEnterpriseId(1)
255 + .setDinerId(100)
256 + .setOpenId(openId)
257 + .setTemplateId("template_test_junit_delete_1")
258 + .setJumpPath("/pages/order/index")
259 + .setOrderPeriodStartDate(1738512000000L)
260 + .setOrderPeriodEndDate(1739030400000L)
261 + .build();
262 +
263 + CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
264 + .setEnterpriseId(1)
265 + .setDinerId(100)
266 + .setOpenId(openId)
267 + .setTemplateId("template_test_junit_delete_2")
268 + .setJumpPath("/pages/order/index")
269 + .setOrderPeriodStartDate(1738512000000L)
270 + .setOrderPeriodEndDate(1739030400000L)
271 + .build();
272 +
273 + OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
274 + OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
275 + List<Long> ids = Arrays.asList(created1.getId(), created2.getId());
276 +
277 + // verify records exist
278 + List<OrderSubscriptionMessageDb> beforeDelete = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(
279 + GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
280 + .setEnterpriseId(1)
281 + .setOpenId(openId)
282 + .setOrderPeriodStartDate(0)
283 + .build());
284 + assertThat(beforeDelete.size()).isGreaterThanOrEqualTo(2);
285 +
286 + // batch delete (逻辑删除)
287 + int affectedRows = orderSubscriptionMessageDbService.batchDeleteByIds(ids, 1);
288 + assertThat(affectedRows).isEqualTo(2);
289 +
290 + // verify deleted - 逻辑删除后查询不到
291 + List<OrderSubscriptionMessageDb> afterDelete = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(
292 + GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
293 + .setEnterpriseId(1)
294 + .setOpenId(openId)
295 + .setOrderPeriodStartDate(0)
296 + .build());
297 + assertThat(afterDelete.size()).isLessThan(beforeDelete.size());
298 +
299 + // verify deleted - 但数据库里还存在(isDeleted=1)
300 + Integer count = jdbcTemplate.queryForObject(
301 + "SELECT COUNT(1) FROM order_subscription_message WHERE id IN (?,?) AND isDeleted=1",
302 + Integer.class,
303 + created1.getId(), created2.getId());
304 + assertThat(count).isEqualTo(2);
305 + }
306 +
307 + @Test
308 + void shouldBatchPhysicalDeleteByIds() {
309 + final String openId = "openId_test_junit_physical_delete";
310 +
311 + // clean
312 + jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
313 +
314 + // create multiple records
315 + CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
316 + .setEnterpriseId(1)
317 + .setDinerId(100)
318 + .setOpenId(openId)
319 + .setTemplateId("template_test_junit_physical_delete_1")
320 + .setJumpPath("/pages/order/index")
321 + .setOrderPeriodStartDate(1738512000000L)
322 + .setOrderPeriodEndDate(1739030400000L)
323 + .build();
324 +
325 + CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
326 + .setEnterpriseId(1)
327 + .setDinerId(100)
328 + .setOpenId(openId)
329 + .setTemplateId("template_test_junit_physical_delete_2")
330 + .setJumpPath("/pages/order/index")
331 + .setOrderPeriodStartDate(1738512000000L)
332 + .setOrderPeriodEndDate(1739030400000L)
333 + .build();
334 +
335 + OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
336 + OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
337 + List<Long> ids = Arrays.asList(created1.getId(), created2.getId());
338 +
339 + // batch physical delete
340 + int affectedRows = orderSubscriptionMessageDbService.batchPhysicalDeleteByIds(ids, 1);
341 + assertThat(affectedRows).isEqualTo(2);
342 +
343 + // verify deleted - 物理删除后数据库里也不存在
344 + Integer count = jdbcTemplate.queryForObject(
345 + "SELECT COUNT(1) FROM order_subscription_message WHERE id IN (?,?)",
346 + Integer.class,
347 + created1.getId(), created2.getId());
348 + assertThat(count).isEqualTo(0);
349 + }
350 +}
351 +