feat: 店铺密码检测任务(后台发起→客户端领取→紫鸟真实登录判定→回传结果)

- 新增 biz_shop_credential_check 表(V99)与 ShopCredentialCheck 全链路
- Controller /api/admin/shop-credential-checks(create/poll/report/latest,X-Internal-Token 鉴权)
- ShopManageService 列表附带 latestCheck(每店铺最近一次检测结果)
- 客户端通过 poll 领取(60s 轮询、原子置 RUNNING),报告 SUCCESS/FAILED/OTP_REQUIRED/NO_NEED_LOGIN/ERROR
This commit is contained in:
2026-08-31 17:02:49 +08:00
parent 26f4bb3f25
commit 7f4737c800
12 changed files with 667 additions and 1 deletions
@@ -0,0 +1,147 @@
package com.nanri.aiimage.modules.shopkey.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.modules.shopkey.mapper.ShopCredentialCheckMapper;
import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper;
import com.nanri.aiimage.modules.shopkey.model.dto.ShopCredentialCheckReportRequest;
import com.nanri.aiimage.modules.shopkey.model.entity.ShopCredentialCheckEntity;
import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageEntity;
import com.nanri.aiimage.modules.shopkey.model.vo.ShopCredentialCheckClaimVo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ShopCredentialCheckServiceTest {
@Mock
private ShopCredentialCheckMapper shopCredentialCheckMapper;
@Mock
private ShopManageMapper shopManageMapper;
@InjectMocks
private ShopCredentialCheckService service;
@BeforeEach
void initTableInfo() {
// MyBatis-Plus Lambda 缓存依赖 TableInfo,单测环境需手动初始化(对应实体)
initTable(ShopCredentialCheckEntity.class);
initTable(ShopManageEntity.class);
}
private void initTable(Class<?> entityClass) {
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
TableInfoHelper.initTableInfo(assistant, entityClass);
}
@Test
void createReusesActiveTaskForSameShop() {
ShopManageEntity shop = new ShopManageEntity();
shop.setId(7L);
shop.setShopName("美国站-主营");
when(shopManageMapper.selectOne(any())).thenReturn(shop);
ShopCredentialCheckEntity active = new ShopCredentialCheckEntity();
active.setId(3L);
active.setShopId(7L);
active.setShopName("美国站-主营");
active.setStatus(ShopCredentialCheckService.STATUS_RUNNING);
when(shopCredentialCheckMapper.selectOne(any())).thenReturn(active);
var vo = service.create("美国站-主营");
assertEquals(3L, vo.getId());
assertEquals("RUNNING", vo.getStatus());
verify(shopCredentialCheckMapper, never()).insert(any(ShopCredentialCheckEntity.class));
}
@Test
void createRejectsUnknownShop() {
when(shopManageMapper.selectOne(any())).thenReturn(null);
BusinessException ex = assertThrows(BusinessException.class, () -> service.create("不存在店铺"));
assertEquals("后台店铺管理中未找到店铺:不存在店铺,请先添加店铺信息", ex.getMessage());
}
@Test
void claimSetsRunningAndReturnsZnUsername() {
ShopCredentialCheckEntity pending = new ShopCredentialCheckEntity();
pending.setId(9L);
pending.setShopName("店铺-测试");
when(shopCredentialCheckMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(java.util.List.of());
when(shopCredentialCheckMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(pending);
when(shopCredentialCheckMapper.update(any(), any(LambdaUpdateWrapper.class))).thenReturn(1);
ShopManageEntity shop = new ShopManageEntity();
shop.setZnUsername("zn-user-1");
when(shopManageMapper.selectOne(any())).thenReturn(shop);
ShopCredentialCheckClaimVo vo = service.claimForClient("PC-01");
assertNotNull(vo);
assertEquals(9L, vo.getId());
assertEquals("店铺-测试", vo.getShopName());
assertEquals("zn-user-1", vo.getZnUsername());
verify(shopCredentialCheckMapper, times(1)).update(any(), any(LambdaUpdateWrapper.class));
}
@Test
void claimReturnsNullWhenNothingPending() {
when(shopCredentialCheckMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(java.util.List.of());
when(shopCredentialCheckMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
assertNull(service.claimForClient("PC-01"));
verify(shopCredentialCheckMapper, never()).update(any(), any(LambdaUpdateWrapper.class));
}
@Test
void reportIgnoresStaleStatus() {
ShopCredentialCheckEntity finished = new ShopCredentialCheckEntity();
finished.setId(5L);
finished.setStatus(ShopCredentialCheckService.STATUS_SUCCESS);
when(shopCredentialCheckMapper.selectById(5L)).thenReturn(finished);
ShopCredentialCheckReportRequest request = new ShopCredentialCheckReportRequest();
request.setStatus(ShopCredentialCheckService.STATUS_FAILED);
request.setDetail("密码错误");
service.report(5L, request);
verify(shopCredentialCheckMapper, never()).updateById(any(ShopCredentialCheckEntity.class));
}
@Test
void reportRecordsFailureDetail() {
ShopCredentialCheckEntity running = new ShopCredentialCheckEntity();
running.setId(6L);
running.setStatus(ShopCredentialCheckService.STATUS_RUNNING);
when(shopCredentialCheckMapper.selectById(6L)).thenReturn(running);
ShopCredentialCheckReportRequest request = new ShopCredentialCheckReportRequest();
request.setStatus(ShopCredentialCheckService.STATUS_FAILED);
request.setDetail("账号或密码错误");
request.setClientHost("PC-02");
service.report(6L, request);
verify(shopCredentialCheckMapper, times(1)).updateById(running);
assertEquals(ShopCredentialCheckService.STATUS_FAILED, running.getStatus());
assertEquals("账号或密码错误", running.getDetail());
assertEquals("PC-02", running.getClientHost());
assertNotNull(running.getCheckFinishedAt());
}
}