chen.yinxiang

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

......@@ -11,6 +11,7 @@ import brave.propagation.TraceContext;
import brave.sampler.Sampler;
import com.tianting.infoloop.service.grpc.MealOrderGrpcService;
import com.tianting.infoloop.service.grpc.OrderGrpcService;
import com.tianting.infoloop.service.grpc.OrderSubscriptionMessageGrpcService;
import io.grpc.Server;
import io.grpc.ServerInterceptor;
import io.grpc.ServerInterceptors;
......@@ -66,11 +67,13 @@ public class AppConfig implements ApplicationContextAware {
public Server serviceServer(@Value(SERVICE_PORT) final int port,
final ServerInterceptor serverInterceptor,
final OrderGrpcService orderGrpcService,
final MealOrderGrpcService mealOrderGrpcService) {
final MealOrderGrpcService mealOrderGrpcService,
final OrderSubscriptionMessageGrpcService orderSubscriptionMessageGrpcService) {
return NettyServerBuilder
.forPort(port)
.addService(ServerInterceptors.intercept(orderGrpcService, serverInterceptor))
.addService(ServerInterceptors.intercept(mealOrderGrpcService, serverInterceptor))
.addService(ServerInterceptors.intercept(orderSubscriptionMessageGrpcService, serverInterceptor))
.build();
}
}
......
package com.tianting.infoloop.mapper;
import com.tianting.infoloop.inject.MyBaseMapper;
import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OrderSubscriptionMessageMapper extends MyBaseMapper<OrderSubscriptionMessageDb> {
/**
* 批量物理删除(绕过逻辑删除)
*/
@Delete("<script>" +
"DELETE FROM order_subscription_message " +
"WHERE enterpriseId = #{enterpriseId} " +
"AND id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
int physicalDeleteByIds(@Param("ids") List<Long> ids, @Param("enterpriseId") int enterpriseId);
}
package com.tianting.infoloop.model.db;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* order subscription message DB
* @TableName order_subscription_message
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@TableName(value = "order_subscription_message")
public class OrderSubscriptionMessageDb extends Model<OrderSubscriptionMessageDb> {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 企业ID
*/
private Integer enterpriseId;
/**
* 订餐用户ID(dinerId)
*/
private Integer dinerId;
/**
* 微信用户OpenID
*/
private String openId;
/**
* 订阅消息模板ID
*/
private String templateId;
/**
* 跳转路径(小程序 path)
*/
private String jumpPath;
/**
* 关联的订餐周期开始时间(精确到秒)
*/
private LocalDateTime orderPeriodStartDate;
/**
* 关联的订餐周期结束时间(精确到秒)
*/
private LocalDateTime orderPeriodEndDate;
/**
* 授权时间(创建时间)
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
/**
* 授权状态,0-待使用(已授权但未发送),1-已使用(已发送,逻辑删除)
*/
@TableLogic
private Boolean isDeleted;
}
package com.tianting.infoloop.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
import java.util.List;
public interface OrderSubscriptionMessageDbService extends IService<OrderSubscriptionMessageDb> {
OrderSubscriptionMessageDb createOrderSubscriptionMessage(CreateOrderSubscriptionMessageRpcRequest request);
List<OrderSubscriptionMessageDb> getUserOrderSubscriptionMessageHistory(GetUserOrderSubscriptionMessageHistoryRpcRequest request);
/**
* 批量逻辑删除授权记录(表示已使用)
* @param ids 要删除的记录ID列表
* @param enterpriseId 企业ID
* @return 受影响的行数
*/
int batchDeleteByIds(java.util.List<Long> ids, int enterpriseId);
/**
* 批量物理删除授权记录
* @param ids 要删除的记录ID列表
* @param enterpriseId 企业ID
* @return 受影响的行数
*/
int batchPhysicalDeleteByIds(java.util.List<Long> ids, int enterpriseId);
}
package com.tianting.infoloop.service.grpc;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.infoloop.tianting.ordersubscriptionmessageservice.BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse;
import com.infoloop.tianting.ordersubscriptionmessageservice.BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse;
import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcResponse;
import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcResponse;
import com.infoloop.tianting.ordersubscriptionmessageservice.OrderSubscriptionMessageRpcResponse;
import com.infoloop.tianting.ordersubscriptionmessageservice.OrderSubscriptionMessageServiceRpcGrpc;
import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
import com.tianting.infoloop.service.OrderSubscriptionMessageDbService;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
import java.util.stream.Collectors;
import static com.tianting.infoloop.constants.ConfigConstants.DATA_SOURCE_MASTER;
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class OrderSubscriptionMessageGrpcService extends OrderSubscriptionMessageServiceRpcGrpc.OrderSubscriptionMessageServiceRpcImplBase {
private final OrderSubscriptionMessageDbService orderSubscriptionMessageDbService;
@Override
@DS(DATA_SOURCE_MASTER)
public void createOrderSubscriptionMessage(final CreateOrderSubscriptionMessageRpcRequest request,
final StreamObserver<CreateOrderSubscriptionMessageRpcResponse> responseObserver) {
final var builder = CreateOrderSubscriptionMessageRpcResponse.newBuilder();
try {
final var data = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(request);
builder.setId(data.getId());
} catch (final Exception e) {
log.error("createOrderSubscriptionMessage error;", e);
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
return;
}
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
@Override
@DS(DATA_SOURCE_MASTER)
public void getUserOrderSubscriptionMessageHistory(final GetUserOrderSubscriptionMessageHistoryRpcRequest request,
final StreamObserver<GetUserOrderSubscriptionMessageHistoryRpcResponse> responseObserver) {
final var builder = GetUserOrderSubscriptionMessageHistoryRpcResponse.newBuilder();
try {
final var list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(request);
builder.addAllResponse(list.stream()
.map(this::convertToRpcResponse)
.collect(Collectors.toList()));
} catch (final Exception e) {
log.error("getUserOrderSubscriptionMessageHistory error;", e);
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
return;
}
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
private OrderSubscriptionMessageRpcResponse convertToRpcResponse(OrderSubscriptionMessageDb db) {
OrderSubscriptionMessageRpcResponse.Builder builder = OrderSubscriptionMessageRpcResponse.newBuilder();
builder.setId(db.getId());
if (db.getJumpPath() != null) {
builder.setJumpPath(db.getJumpPath());
}
// 转换LocalDateTime为时间戳(毫秒),与项目中其他时间字段保持一致
if (db.getOrderPeriodStartDate() != null) {
long timestamp = db.getOrderPeriodStartDate().atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
builder.setOrderPeriodStartDate(timestamp);
}
if (db.getOrderPeriodEndDate() != null) {
long timestamp = db.getOrderPeriodEndDate().atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
builder.setOrderPeriodEndDate(timestamp);
}
// 转换LocalDateTime为时间戳(毫秒)
if (db.getCreatedAt() != null) {
long timestamp = db.getCreatedAt().atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
builder.setCreatedAt(timestamp);
}
builder.setIsDeleted(db.getIsDeleted() != null && db.getIsDeleted());
return builder.build();
}
@Override
@DS(DATA_SOURCE_MASTER)
public void batchDeleteOrderSubscriptionMessagesByIds(final BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest request,
final StreamObserver<BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse> responseObserver) {
final var builder = BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse.newBuilder();
try {
final int affectedRows = orderSubscriptionMessageDbService.batchDeleteByIds(
request.getIdsList(),
request.getEnterpriseId());
builder.setAffectedRows(affectedRows);
} catch (final Exception e) {
log.error("batchDeleteOrderSubscriptionMessagesByIds error;", e);
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
return;
}
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
@Override
@DS(DATA_SOURCE_MASTER)
public void batchPhysicalDeleteOrderSubscriptionMessagesByIds(
final BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest request,
final StreamObserver<BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse> responseObserver) {
final var builder = BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse.newBuilder();
try {
final int affectedRows = orderSubscriptionMessageDbService.batchPhysicalDeleteByIds(
request.getIdsList(),
request.getEnterpriseId());
builder.setAffectedRows(affectedRows);
} catch (final Exception e) {
log.error("batchPhysicalDeleteOrderSubscriptionMessagesByIds error;", e);
responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
return;
}
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
}
package com.tianting.infoloop.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
import com.tianting.infoloop.mapper.OrderSubscriptionMessageMapper;
import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
import com.tianting.infoloop.service.OrderSubscriptionMessageDbService;
import com.tianting.infoloop.utils.exception.ErrorCodeConstants;
import com.tianting.infoloop.utils.exception.ServiceExceptionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class OrderSubscriptionMessageDbServiceImpl extends ServiceImpl<OrderSubscriptionMessageMapper, OrderSubscriptionMessageDb> implements OrderSubscriptionMessageDbService {
@Override
public OrderSubscriptionMessageDb createOrderSubscriptionMessage(CreateOrderSubscriptionMessageRpcRequest request) {
OrderSubscriptionMessageDb orderSubscriptionMessageDb = new OrderSubscriptionMessageDb();
orderSubscriptionMessageDb.setEnterpriseId(request.getEnterpriseId());
if (request.getDinerId() != 0) {
orderSubscriptionMessageDb.setDinerId(request.getDinerId());
}
orderSubscriptionMessageDb.setOpenId(request.getOpenId());
orderSubscriptionMessageDb.setTemplateId(request.getTemplateId());
if (!request.getJumpPath().isEmpty()) {
orderSubscriptionMessageDb.setJumpPath(request.getJumpPath());
}
// 转换时间戳为LocalDateTime(精确到秒),与项目中其他时间字段保持一致
LocalDateTime orderPeriodStartDate = Instant.ofEpochMilli(request.getOrderPeriodStartDate())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
orderSubscriptionMessageDb.setOrderPeriodStartDate(orderPeriodStartDate);
if (request.getOrderPeriodEndDate() > 0) {
LocalDateTime orderPeriodEndDate = Instant.ofEpochMilli(request.getOrderPeriodEndDate())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
orderSubscriptionMessageDb.setOrderPeriodEndDate(orderPeriodEndDate);
}
boolean res = this.save(orderSubscriptionMessageDb);
if (!res) {
log.error("创建订阅消息授权记录失败, request: {}", request);
throw ServiceExceptionUtil.exception(ErrorCodeConstants.ORDER_GEN_FAIL);
}
return orderSubscriptionMessageDb;
}
@Override
public List<OrderSubscriptionMessageDb> getUserOrderSubscriptionMessageHistory(GetUserOrderSubscriptionMessageHistoryRpcRequest request) {
LambdaQueryWrapper<OrderSubscriptionMessageDb> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderSubscriptionMessageDb::getEnterpriseId, request.getEnterpriseId());
queryWrapper.eq(OrderSubscriptionMessageDb::getOpenId, request.getOpenId());
// 如果传入了 orderPeriodStartDate,查询大于等于该值的记录
if (request.getOrderPeriodStartDate() > 0) {
LocalDateTime orderPeriodStartDate = Instant.ofEpochMilli(request.getOrderPeriodStartDate())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
queryWrapper.ge(OrderSubscriptionMessageDb::getOrderPeriodStartDate, orderPeriodStartDate);
}
queryWrapper.orderByDesc(OrderSubscriptionMessageDb::getCreatedAt);
return this.list(queryWrapper);
}
@Override
public int batchDeleteByIds(java.util.List<Long> ids, int enterpriseId) {
if (ids == null || ids.isEmpty()) {
return 0;
}
LambdaQueryWrapper<OrderSubscriptionMessageDb> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderSubscriptionMessageDb::getEnterpriseId, enterpriseId);
queryWrapper.in(OrderSubscriptionMessageDb::getId, ids);
// 逻辑删除(使用 MyBatis-Plus 的 @TableLogic)
return this.remove(queryWrapper) ? ids.size() : 0;
}
@Override
public int batchPhysicalDeleteByIds(java.util.List<Long> ids, int enterpriseId) {
if (ids == null || ids.isEmpty()) {
return 0;
}
// 物理删除(使用 Mapper 中的 SQL,绕过逻辑删除)
return this.baseMapper.physicalDeleteByIds(ids, enterpriseId);
}
}
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.infoloop.tianting.ordersubscriptionmessageservice";
option java_outer_classname = "OrderSubscriptionMessageServiceProto";
option objc_class_prefix = "OP";
package com.infoloop.tianting.ordersubscriptionmessageservice;
service OrderSubscriptionMessageServiceRpc {
// 创建订阅消息授权
rpc CreateOrderSubscriptionMessage (CreateOrderSubscriptionMessageRpcRequest) returns (CreateOrderSubscriptionMessageRpcResponse) {}
// 查询用户授权历史
rpc GetUserOrderSubscriptionMessageHistory (GetUserOrderSubscriptionMessageHistoryRpcRequest) returns (GetUserOrderSubscriptionMessageHistoryRpcResponse) {}
// 批量逻辑删除授权记录(表示已使用)
rpc BatchDeleteOrderSubscriptionMessagesByIds (BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
// 批量物理删除授权记录
rpc BatchPhysicalDeleteOrderSubscriptionMessagesByIds (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest) returns (BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse) {}
}
// 创建订阅消息授权请求
message CreateOrderSubscriptionMessageRpcRequest {
int32 enterpriseId = 1;
int32 dinerId = 2;
string openId = 3;
string templateId = 4;
int64 orderPeriodStartDate = 5; // Unix 时间戳(毫秒),精确到秒
string jumpPath = 6; // 跳转路径(小程序 path),可选
int64 orderPeriodEndDate = 7; // Unix 时间戳(毫秒),精确到秒,可选
}
// 创建订阅消息授权响应
message CreateOrderSubscriptionMessageRpcResponse {
int64 id = 1;
}
// 查询用户授权历史请求
message GetUserOrderSubscriptionMessageHistoryRpcRequest {
int32 enterpriseId = 1;
string openId = 2;
int64 orderPeriodStartDate = 3; // Unix 时间戳(毫秒),精确到秒,可选,如果传入则查询大于等于该值的记录
}
// 订阅消息授权记录
message OrderSubscriptionMessageRpcResponse {
int64 id = 1;
int64 orderPeriodStartDate = 2; // Unix 时间戳(毫秒),精确到秒
int64 createdAt = 3; // Unix 时间戳(毫秒)
bool isDeleted = 4;
string jumpPath = 5; // 跳转路径(小程序 path),可选
int64 orderPeriodEndDate = 6; // Unix 时间戳(毫秒),精确到秒,可选
}
// 查询用户授权历史响应
message GetUserOrderSubscriptionMessageHistoryRpcResponse {
repeated OrderSubscriptionMessageRpcResponse response = 1;
}
// 批量逻辑删除授权记录请求
message BatchDeleteOrderSubscriptionMessagesByIdsRpcRequest {
int32 enterpriseId = 1;
repeated int64 ids = 2; // 要删除的记录ID列表
}
// 批量逻辑删除授权记录响应
message BatchDeleteOrderSubscriptionMessagesByIdsRpcResponse {
int32 affectedRows = 1; // 受影响的行数
}
// 批量物理删除授权记录请求
message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcRequest {
int32 enterpriseId = 1;
repeated int64 ids = 2; // 要删除的记录ID列表
}
// 批量物理删除授权记录响应
message BatchPhysicalDeleteOrderSubscriptionMessagesByIdsRpcResponse {
int32 affectedRows = 1; // 受影响的行数
}
package com.tianting.infoloop.service;
import com.infoloop.tianting.ordersubscriptionmessageservice.CreateOrderSubscriptionMessageRpcRequest;
import com.infoloop.tianting.ordersubscriptionmessageservice.GetUserOrderSubscriptionMessageHistoryRpcRequest;
import java.util.Arrays;
import java.util.List;
import com.tianting.infoloop.App;
import com.tianting.infoloop.model.db.OrderSubscriptionMessageDb;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ActiveProfiles("dev")
@SpringBootTest(classes = App.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class OrderSubscriptionMessageDbServiceTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private OrderSubscriptionMessageDbService orderSubscriptionMessageDbService;
@BeforeAll
void ensureTable() {
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS order_subscription_message ("
+ "id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',"
+ "enterpriseId INT NOT NULL COMMENT '企业ID',"
+ "dinerId INT DEFAULT NULL COMMENT '订餐用户ID(dinerId)',"
+ "openId VARCHAR(255) NOT NULL COMMENT '微信用户OpenID',"
+ "templateId VARCHAR(255) NOT NULL COMMENT '订阅消息模板ID',"
+ "jumpPath VARCHAR(255) DEFAULT NULL COMMENT '跳转路径(小程序 path)',"
+ "orderPeriodStartDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期开始时间(精确到秒)',"
+ "orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)',"
+ "createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
+ "updatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',"
+ "isDeleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除;0:未删除,1:已删除',"
+ "PRIMARY KEY (id),"
+ "KEY idx_enterprise_id (enterpriseId)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订餐订阅消息授权表';");
// 兼容本地已存在的旧表结构(修改字段类型为 DATETIME,精确到秒)
Integer startDateType = jdbcTemplate.queryForObject(
"SELECT COUNT(1) FROM information_schema.COLUMNS "
+ "WHERE table_schema = DATABASE() "
+ "AND table_name = 'order_subscription_message' "
+ "AND column_name = 'orderPeriodStartDate' "
+ "AND data_type IN ('date', 'bigint')",
Integer.class);
if (startDateType != null && startDateType > 0) {
// 如果字段是 bigint,需要先清空数据或转换数据,这里先清空(测试环境)
jdbcTemplate.update("UPDATE order_subscription_message SET orderPeriodStartDate = NULL WHERE orderPeriodStartDate IS NOT NULL");
jdbcTemplate.execute(
"ALTER TABLE order_subscription_message "
+ "MODIFY COLUMN orderPeriodStartDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期开始时间(精确到秒)'");
}
Integer endDateExists = jdbcTemplate.queryForObject(
"SELECT COUNT(1) FROM information_schema.COLUMNS "
+ "WHERE table_schema = DATABASE() "
+ "AND table_name = 'order_subscription_message' "
+ "AND column_name = 'orderPeriodEndDate'",
Integer.class);
if (endDateExists == null || endDateExists == 0) {
jdbcTemplate.execute(
"ALTER TABLE order_subscription_message "
+ "ADD COLUMN orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)' "
+ "AFTER orderPeriodStartDate");
} else {
Integer endDateType = jdbcTemplate.queryForObject(
"SELECT COUNT(1) FROM information_schema.COLUMNS "
+ "WHERE table_schema = DATABASE() "
+ "AND table_name = 'order_subscription_message' "
+ "AND column_name = 'orderPeriodEndDate' "
+ "AND data_type IN ('date', 'bigint')",
Integer.class);
if (endDateType != null && endDateType > 0) {
// 如果字段是 bigint,需要先清空数据或转换数据,这里先清空(测试环境)
jdbcTemplate.update("UPDATE order_subscription_message SET orderPeriodEndDate = NULL WHERE orderPeriodEndDate IS NOT NULL");
jdbcTemplate.execute(
"ALTER TABLE order_subscription_message "
+ "MODIFY COLUMN orderPeriodEndDate DATETIME DEFAULT NULL COMMENT '关联的订餐周期结束时间(精确到秒)'");
}
}
}
@Test
void shouldCreateAndQueryHistory() {
final String openId = "openId_test_junit_001";
// clean
jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
// create
CreateOrderSubscriptionMessageRpcRequest createReq = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_junit_001")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00 (毫秒时间戳,转换为 LocalDateTime)
.setOrderPeriodEndDate(1739030400000L) // 2025-02-09 00:00:00 (毫秒时间戳,转换为 LocalDateTime)
.build();
OrderSubscriptionMessageDb created = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq);
assertThat(created.getId()).isNotNull();
assertThat(created.getEnterpriseId()).isEqualTo(1);
assertThat(created.getDinerId()).isEqualTo(100);
assertThat(created.getJumpPath()).isEqualTo("/pages/order/index");
assertThat(created.getOrderPeriodEndDate()).isNotNull();
// query history
GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
.setEnterpriseId(1)
.setOpenId(openId)
.setOrderPeriodStartDate(0)
.build();
List<OrderSubscriptionMessageDb> list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq);
assertThat(list).isNotEmpty();
assertThat(list.get(0).getOpenId()).isEqualTo(openId);
assertThat(list.get(0).getJumpPath()).isEqualTo("/pages/order/index");
assertThat(list.get(0).getOrderPeriodEndDate()).isNotNull();
}
@Test
void shouldQueryByOrderPeriodStartDateWithGreaterEqual() {
final String openId = "openId_test_junit_date_query";
// clean
jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
// 创建多条不同开始时间的记录
// 记录1: 2025-02-03 00:00:00
CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_date_1")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00
.setOrderPeriodEndDate(1739030400000L) // 2025-02-09 00:00:00
.build();
OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
// 记录2: 2025-02-10 00:00:00 (晚于记录1)
CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_date_2")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1739116800000L) // 2025-02-10 00:00:00
.setOrderPeriodEndDate(1739635200000L) // 2025-02-16 00:00:00
.build();
OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
// 记录3: 2025-01-27 00:00:00 (早于记录1)
CreateOrderSubscriptionMessageRpcRequest createReq3 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_date_3")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1737907200000L) // 2025-01-27 00:00:00
.setOrderPeriodEndDate(1738425600000L) // 2025-02-02 00:00:00
.build();
OrderSubscriptionMessageDb created3 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq3);
// 记录4: 2025-02-03 12:00:00 (等于记录1的日期,但时间更晚)
CreateOrderSubscriptionMessageRpcRequest createReq4 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_date_4")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738555200000L) // 2025-02-03 12:00:00
.setOrderPeriodEndDate(1739073600000L) // 2025-02-09 12:00:00
.build();
OrderSubscriptionMessageDb created4 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq4);
// 测试1: 查询 orderPeriodStartDate >= 2025-02-03 00:00:00
// 应该返回: 记录1(等于), 记录2(大于), 记录4(大于)
// 不应该返回: 记录3(小于)
GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
.setEnterpriseId(1)
.setOpenId(openId)
.setOrderPeriodStartDate(1738512000000L) // 2025-02-03 00:00:00
.build();
List<OrderSubscriptionMessageDb> list = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq);
// 验证返回的记录数量:应该包含记录1、记录2、记录4,共3条
assertThat(list.size()).isEqualTo(3);
// 验证所有返回的记录的开始时间都 >= 2025-02-03 00:00:00
java.time.LocalDateTime threshold = java.time.Instant.ofEpochMilli(1738512000000L)
.atZone(java.time.ZoneId.systemDefault())
.toLocalDateTime();
for (OrderSubscriptionMessageDb db : list) {
assertThat(db.getOrderPeriodStartDate()).isNotNull();
assertThat(db.getOrderPeriodStartDate().isAfter(threshold) || db.getOrderPeriodStartDate().isEqual(threshold))
.as("开始时间应该 >= 2025-02-03 00:00:00,实际: %s", db.getOrderPeriodStartDate())
.isTrue();
}
// 验证包含记录1(等于)
assertThat(list.stream().anyMatch(db -> db.getId().equals(created1.getId()))).isTrue();
// 验证包含记录2(大于)
assertThat(list.stream().anyMatch(db -> db.getId().equals(created2.getId()))).isTrue();
// 验证包含记录4(大于,同一天但时间更晚)
assertThat(list.stream().anyMatch(db -> db.getId().equals(created4.getId()))).isTrue();
// 验证不包含记录3(小于)
assertThat(list.stream().anyMatch(db -> db.getId().equals(created3.getId()))).isFalse();
// 测试2: 查询 orderPeriodStartDate >= 2025-02-10 00:00:00
// 应该只返回: 记录2(等于)
GetUserOrderSubscriptionMessageHistoryRpcRequest historyReq2 = GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
.setEnterpriseId(1)
.setOpenId(openId)
.setOrderPeriodStartDate(1739116800000L) // 2025-02-10 00:00:00
.build();
List<OrderSubscriptionMessageDb> list2 = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(historyReq2);
// 验证只返回记录2
assertThat(list2.size()).isEqualTo(1);
assertThat(list2.get(0).getId()).isEqualTo(created2.getId());
}
@Test
void shouldBatchDeleteByIds() {
final String openId = "openId_test_junit_delete";
// clean
jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
// create multiple records
CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_junit_delete_1")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L)
.setOrderPeriodEndDate(1739030400000L)
.build();
CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_junit_delete_2")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L)
.setOrderPeriodEndDate(1739030400000L)
.build();
OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
List<Long> ids = Arrays.asList(created1.getId(), created2.getId());
// verify records exist
List<OrderSubscriptionMessageDb> beforeDelete = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(
GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
.setEnterpriseId(1)
.setOpenId(openId)
.setOrderPeriodStartDate(0)
.build());
assertThat(beforeDelete.size()).isGreaterThanOrEqualTo(2);
// batch delete (逻辑删除)
int affectedRows = orderSubscriptionMessageDbService.batchDeleteByIds(ids, 1);
assertThat(affectedRows).isEqualTo(2);
// verify deleted - 逻辑删除后查询不到
List<OrderSubscriptionMessageDb> afterDelete = orderSubscriptionMessageDbService.getUserOrderSubscriptionMessageHistory(
GetUserOrderSubscriptionMessageHistoryRpcRequest.newBuilder()
.setEnterpriseId(1)
.setOpenId(openId)
.setOrderPeriodStartDate(0)
.build());
assertThat(afterDelete.size()).isLessThan(beforeDelete.size());
// verify deleted - 但数据库里还存在(isDeleted=1)
Integer count = jdbcTemplate.queryForObject(
"SELECT COUNT(1) FROM order_subscription_message WHERE id IN (?,?) AND isDeleted=1",
Integer.class,
created1.getId(), created2.getId());
assertThat(count).isEqualTo(2);
}
@Test
void shouldBatchPhysicalDeleteByIds() {
final String openId = "openId_test_junit_physical_delete";
// clean
jdbcTemplate.update("DELETE FROM order_subscription_message WHERE enterpriseId=? AND openId=?", 1, openId);
// create multiple records
CreateOrderSubscriptionMessageRpcRequest createReq1 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_junit_physical_delete_1")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L)
.setOrderPeriodEndDate(1739030400000L)
.build();
CreateOrderSubscriptionMessageRpcRequest createReq2 = CreateOrderSubscriptionMessageRpcRequest.newBuilder()
.setEnterpriseId(1)
.setDinerId(100)
.setOpenId(openId)
.setTemplateId("template_test_junit_physical_delete_2")
.setJumpPath("/pages/order/index")
.setOrderPeriodStartDate(1738512000000L)
.setOrderPeriodEndDate(1739030400000L)
.build();
OrderSubscriptionMessageDb created1 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq1);
OrderSubscriptionMessageDb created2 = orderSubscriptionMessageDbService.createOrderSubscriptionMessage(createReq2);
List<Long> ids = Arrays.asList(created1.getId(), created2.getId());
// batch physical delete
int affectedRows = orderSubscriptionMessageDbService.batchPhysicalDeleteByIds(ids, 1);
assertThat(affectedRows).isEqualTo(2);
// verify deleted - 物理删除后数据库里也不存在
Integer count = jdbcTemplate.queryForObject(
"SELECT COUNT(1) FROM order_subscription_message WHERE id IN (?,?)",
Integer.class,
created1.getId(), created2.getId());
assertThat(count).isEqualTo(0);
}
}