feat(tickets): 实现冻结水票自动释放功能
- 在 GltUserTicketMapper 中新增 releaseFrozenQty 方法用于释放冻结水票 - 新增 GltUserTicketReleaseMapper 处理释放记录的查询和状态更新 - 添加 H2 数据库依赖支持单元测试 - 创建 GltUserTicketAutoReleaseService 接口及其实现类 - 实现自动释放服务的核心逻辑包括加锁查询、数量更新和流水记录 - 新建定时任务 GltUserTicketAutoReleaseTask 定期执行释放操作 - 添加完整的单元测试覆盖正常释放、未到期和冻结不足等场景 - 创建测试专用数据库表结构文件
This commit is contained in:
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
62
src/test/resources/schema-auto-release-h2.sql
Normal file
62
src/test/resources/schema-auto-release-h2.sql
Normal 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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user