feat(tickets): 实现冻结水票自动释放功能

- 在 GltUserTicketMapper 中新增 releaseFrozenQty 方法用于释放冻结水票
- 新增 GltUserTicketReleaseMapper 处理释放记录的查询和状态更新
- 添加 H2 数据库依赖支持单元测试
- 创建 GltUserTicketAutoReleaseService 接口及其实现类
- 实现自动释放服务的核心逻辑包括加锁查询、数量更新和流水记录
- 新建定时任务 GltUserTicketAutoReleaseTask 定期执行释放操作
- 添加完整的单元测试覆盖正常释放、未到期和冻结不足等场景
- 创建测试专用数据库表结构文件
This commit is contained in:
2026-02-07 00:25:24 +08:00
parent 46de27611d
commit a20d1dd465
9 changed files with 569 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
package com.gxwebsoft.glt;
import com.gxwebsoft.glt.mapper.GltUserTicketLogMapper;
import com.gxwebsoft.glt.mapper.GltUserTicketMapper;
import com.gxwebsoft.glt.mapper.GltUserTicketReleaseMapper;
import com.gxwebsoft.glt.service.impl.GltUserTicketAutoReleaseServiceImpl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Import;
@SpringBootConfiguration
@EnableAutoConfiguration
@MapperScan(basePackageClasses = {
GltUserTicketMapper.class,
GltUserTicketReleaseMapper.class,
GltUserTicketLogMapper.class
})
@Import(GltUserTicketAutoReleaseServiceImpl.class)
public class AutoReleaseTestApplication {
}

View File

@@ -0,0 +1,209 @@
package com.gxwebsoft.glt.service;
import com.gxwebsoft.glt.AutoReleaseTestApplication;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.mapper.GltUserTicketLogMapper;
import com.gxwebsoft.glt.mapper.GltUserTicketMapper;
import com.gxwebsoft.glt.mapper.GltUserTicketReleaseMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(
classes = AutoReleaseTestApplication.class,
properties = {
"spring.datasource.url=jdbc:h2:mem:auto_release;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE",
"spring.datasource.driver-class-name=org.h2.Driver",
"spring.datasource.username=sa",
"spring.datasource.password=",
"spring.sql.init.mode=always",
"spring.sql.init.schema-locations=classpath:schema-auto-release-h2.sql",
"mybatis-plus.configuration.map-underscore-to-camel-case=true",
"mybatis-plus.global-config.db-config.logic-delete-value=1",
"mybatis-plus.global-config.db-config.logic-not-delete-value=0"
}
)
class GltUserTicketAutoReleaseServiceTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private GltUserTicketMapper userTicketMapper;
@Autowired
private GltUserTicketReleaseMapper releaseMapper;
@Autowired
private GltUserTicketLogMapper logMapper;
@Autowired
private GltUserTicketAutoReleaseService autoReleaseService;
@BeforeEach
void clean() {
jdbcTemplate.execute("DELETE FROM glt_user_ticket_log");
jdbcTemplate.execute("DELETE FROM glt_user_ticket_release");
jdbcTemplate.execute("DELETE FROM glt_user_ticket");
}
@Test
void releaseDue_success_updatesTicketReleaseAndWritesLog() {
LocalDateTime now = LocalDateTime.of(2026, 2, 6, 12, 0, 0);
GltUserTicket ticket = new GltUserTicket();
ticket.setTemplateId(1);
ticket.setGoodsId(1);
ticket.setOrderId(1);
ticket.setOrderNo("T-001");
ticket.setOrderGoodsId(1);
ticket.setTotalQty(10);
ticket.setAvailableQty(0);
ticket.setFrozenQty(10);
ticket.setUsedQty(0);
ticket.setReleasedQty(0);
ticket.setUserId(100);
ticket.setSortNumber(0);
ticket.setComments("test");
ticket.setStatus(0);
ticket.setDeleted(0);
ticket.setTenantId(10584);
ticket.setCreateTime(now);
ticket.setUpdateTime(now);
assertEquals(1, userTicketMapper.insert(ticket));
assertNotNull(ticket.getId());
GltUserTicketRelease rel = new GltUserTicketRelease();
rel.setUserTicketId(ticket.getId().longValue());
rel.setUserId(100);
rel.setPeriodNo(1);
rel.setReleaseQty(3);
rel.setReleaseTime(now.minusSeconds(1));
rel.setStatus(0);
rel.setDeleted(0);
rel.setTenantId(10584);
rel.setCreateTime(now);
rel.setUpdateTime(now);
assertEquals(1, releaseMapper.insert(rel));
assertNotNull(rel.getId());
int releasedCount = autoReleaseService.releaseDue(now, 100);
assertEquals(1, releasedCount);
GltUserTicket after = userTicketMapper.selectById(ticket.getId());
assertNotNull(after);
assertEquals(3, after.getAvailableQty());
assertEquals(7, after.getFrozenQty());
assertEquals(3, after.getReleasedQty());
GltUserTicketRelease relAfter = releaseMapper.selectById(rel.getId());
assertNotNull(relAfter);
assertEquals(1, relAfter.getStatus());
List<GltUserTicketLog> logs = logMapper.selectList(null);
assertEquals(1, logs.size());
assertEquals(ticket.getId(), logs.get(0).getUserTicketId());
assertEquals(3, logs.get(0).getChangeAvailable());
assertEquals(-3, logs.get(0).getChangeFrozen());
}
@Test
void releaseDue_notDue_noop() {
LocalDateTime now = LocalDateTime.of(2026, 2, 6, 12, 0, 0);
GltUserTicket ticket = new GltUserTicket();
ticket.setTotalQty(10);
ticket.setAvailableQty(0);
ticket.setFrozenQty(10);
ticket.setUsedQty(0);
ticket.setReleasedQty(0);
ticket.setUserId(100);
ticket.setStatus(0);
ticket.setDeleted(0);
ticket.setTenantId(10584);
ticket.setCreateTime(now);
ticket.setUpdateTime(now);
userTicketMapper.insert(ticket);
GltUserTicketRelease rel = new GltUserTicketRelease();
rel.setUserTicketId(ticket.getId().longValue());
rel.setUserId(100);
rel.setPeriodNo(1);
rel.setReleaseQty(3);
rel.setReleaseTime(now.plusDays(1));
rel.setStatus(0);
rel.setDeleted(0);
rel.setTenantId(10584);
rel.setCreateTime(now);
rel.setUpdateTime(now);
releaseMapper.insert(rel);
int releasedCount = autoReleaseService.releaseDue(now, 100);
assertEquals(0, releasedCount);
GltUserTicket after = userTicketMapper.selectById(ticket.getId());
assertNotNull(after);
assertEquals(0, after.getAvailableQty());
assertEquals(10, after.getFrozenQty());
GltUserTicketRelease relAfter = releaseMapper.selectById(rel.getId());
assertNotNull(relAfter);
assertEquals(0, relAfter.getStatus());
assertEquals(0, logMapper.selectCount(null));
}
@Test
void releaseDue_frozenInsufficient_marksFailed() {
LocalDateTime now = LocalDateTime.of(2026, 2, 6, 12, 0, 0);
GltUserTicket ticket = new GltUserTicket();
ticket.setTotalQty(2);
ticket.setAvailableQty(0);
ticket.setFrozenQty(2);
ticket.setUsedQty(0);
ticket.setReleasedQty(0);
ticket.setUserId(100);
ticket.setStatus(0);
ticket.setDeleted(0);
ticket.setTenantId(10584);
ticket.setCreateTime(now);
ticket.setUpdateTime(now);
userTicketMapper.insert(ticket);
GltUserTicketRelease rel = new GltUserTicketRelease();
rel.setUserTicketId(ticket.getId().longValue());
rel.setUserId(100);
rel.setPeriodNo(1);
rel.setReleaseQty(3);
rel.setReleaseTime(now.minusSeconds(1));
rel.setStatus(0);
rel.setDeleted(0);
rel.setTenantId(10584);
rel.setCreateTime(now);
rel.setUpdateTime(now);
releaseMapper.insert(rel);
int releasedCount = autoReleaseService.releaseDue(now, 100);
assertEquals(0, releasedCount);
GltUserTicket after = userTicketMapper.selectById(ticket.getId());
assertNotNull(after);
assertEquals(0, after.getAvailableQty());
assertEquals(2, after.getFrozenQty());
GltUserTicketRelease relAfter = releaseMapper.selectById(rel.getId());
assertNotNull(relAfter);
assertEquals(2, relAfter.getStatus());
assertEquals(0, logMapper.selectCount(null));
}
}

View File

@@ -0,0 +1,62 @@
DROP TABLE IF EXISTS glt_user_ticket_log;
DROP TABLE IF EXISTS glt_user_ticket_release;
DROP TABLE IF EXISTS glt_user_ticket;
CREATE TABLE glt_user_ticket (
id INT AUTO_INCREMENT PRIMARY KEY,
template_id INT,
goods_id INT,
order_id INT,
order_no VARCHAR(64),
order_goods_id INT,
total_qty INT,
available_qty INT,
frozen_qty INT,
used_qty INT,
released_qty INT,
user_id INT,
sort_number INT,
comments VARCHAR(255),
status INT,
deleted INT DEFAULT 0,
tenant_id INT,
create_time TIMESTAMP,
update_time TIMESTAMP
);
CREATE TABLE glt_user_ticket_release (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_ticket_id BIGINT,
user_id INT,
period_no INT,
release_qty INT,
release_time TIMESTAMP,
status INT,
deleted INT DEFAULT 0,
tenant_id INT,
create_time TIMESTAMP,
update_time TIMESTAMP
);
CREATE TABLE glt_user_ticket_log (
id INT AUTO_INCREMENT PRIMARY KEY,
user_ticket_id INT,
change_type INT,
change_available INT,
change_frozen INT,
change_used INT,
available_after INT,
frozen_after INT,
used_after INT,
order_id INT,
order_no VARCHAR(64),
user_id INT,
sort_number INT,
comments VARCHAR(255),
status INT,
deleted INT DEFAULT 0,
tenant_id INT,
create_time TIMESTAMP,
update_time TIMESTAMP
);