浇水bug修改

This commit is contained in:
yuhaiming
2026-07-13 14:22:11 +08:00
parent 1e1f1042fe
commit 86e511f418
50 changed files with 4976 additions and 280 deletions

View File

@@ -190,8 +190,9 @@ public class AuthController {
@DeleteMapping("/account")
public R<Void> cancelAccount(@Validated @RequestBody AccountCancelBody body) {
StpUtil.checkLogin();
Long userId = LoginHelper.getUserId();
accountCancellationService.cancelCurrentAccount(body.getCode());
StpUtil.logout();
StpUtil.logout(userId);
return R.ok("注销成功");
}

View File

@@ -110,6 +110,7 @@ public class CaptchaController {
/**
* 注销账号验证码
*/
@RateLimiter(key = "#{T(org.dromara.common.satoken.utils.LoginHelper).getUserId()}", time = 60, count = 1)
@GetMapping("/resource/account/cancel/code")
public R<Void> accountCancelCode() {
StpUtil.checkLogin();
@@ -129,7 +130,7 @@ public class CaptchaController {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
emailCodeImpl(user.getEmail(), username);
SpringUtils.getAopProxy(this).emailCodeImpl(user.getEmail(), username);
return R.ok("操作成功");
}
return R.fail("当前账号未绑定手机号或邮箱");

View File

@@ -0,0 +1,36 @@
package org.dromara.web.service;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.springframework.stereotype.Service;
/**
* 注销账号验证码服务
*/
@RequiredArgsConstructor
@Service
public class AccountCancelCodeService {
public void validate(String username, String code) {
String cacheCode = RedisUtils.getCacheObject(buildKey(username));
if (StringUtils.isBlank(cacheCode)) {
throw new CaptchaExpireException();
}
if (!StringUtils.equals(cacheCode, code)) {
throw new UserException("验证码无效");
}
}
public void delete(String username) {
RedisUtils.deleteObject(buildKey(username));
}
private String buildKey(String username) {
return GlobalConstants.CAPTCHA_CODE_KEY + username;
}
}

View File

@@ -0,0 +1,156 @@
package org.dromara.web.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.app.domain.*;
import org.dromara.app.mapper.*;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysSocial;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.mapper.SysSocialMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.mapper.SysUserPostMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
@RequiredArgsConstructor
@Service
public class AccountCancellationService {
private final SysUserMapper sysUserMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final SysUserPostMapper sysUserPostMapper;
private final SysSocialMapper sysSocialMapper;
private final AppDeviceMapper appDeviceMapper;
private final AppScheduleMapper appScheduleMapper;
private final AppScheduleDetailMapper appScheduleDetailMapper;
private final AppSchedulingDeviceMapper appSchedulingDeviceMapper;
private final AppWateringLogMapper appWateringLogMapper;
private final AccountCancelCodeService accountCancelCodeService;
private final IDeviceCommandService deviceCommandService;
@Transactional(rollbackFor = Exception.class)
public void cancelCurrentAccount(String code) {
Long userId = LoginHelper.getUserId();
if (userId == null) {
throw new ServiceException("用户未登录");
}
if (LoginHelper.isSuperAdmin(userId)) {
throw new ServiceException("超级管理员账号不允许注销");
}
String username = getCurrentUsername();
accountCancelCodeService.validate(username, code);
List<String> deviceNos = queryUserDeviceNos(userId);
List<Long> scheduleIds = queryUserScheduleIds(userId);
sendInitDeviceCommands(deviceNos);
deleteAppScheduleData(userId, scheduleIds);
deleteAppDeviceData(userId, deviceNos);
deleteSystemUserData(userId);
accountCancelCodeService.delete(username);
}
private String getCurrentUsername() {
String username = LoginHelper.getUsername();
if (StringUtils.isBlank(username)) {
throw new ServiceException("用户未登录");
}
return username;
}
private List<String> queryUserDeviceNos(Long userId) {
return appDeviceMapper.selectList(
Wrappers.<AppDevice>lambdaQuery()
.select(AppDevice::getDeviceNo)
.eq(AppDevice::getUserId, userId)
).stream()
.map(AppDevice::getDeviceNo)
.filter(StringUtils::isNotBlank)
.distinct()
.toList();
}
private List<Long> queryUserScheduleIds(Long userId) {
return appScheduleMapper.selectList(
Wrappers.<AppSchedule>lambdaQuery()
.select(AppSchedule::getId)
.eq(AppSchedule::getUserId, userId)
).stream()
.map(AppSchedule::getId)
.filter(Objects::nonNull)
.distinct()
.toList();
}
private void sendInitDeviceCommands(List<String> deviceNos) {
for (String deviceNo : deviceNos) {
deviceCommandService.sendInitDeviceCommand(deviceNo);
}
}
private void deleteAppScheduleData(Long userId, List<Long> scheduleIds) {
if (!scheduleIds.isEmpty()) {
appSchedulingDeviceMapper.delete(
Wrappers.<AppSchedulingDevice>lambdaQuery()
.in(AppSchedulingDevice::getScheduleId, scheduleIds)
);
appScheduleDetailMapper.delete(
Wrappers.<AppScheduleDetail>lambdaQuery()
.in(AppScheduleDetail::getScheduleId, scheduleIds)
);
}
appScheduleMapper.delete(
Wrappers.<AppSchedule>lambdaQuery()
.eq(AppSchedule::getUserId, userId)
);
}
private void deleteAppDeviceData(Long userId, List<String> deviceNos) {
if (!deviceNos.isEmpty()) {
appSchedulingDeviceMapper.delete(
Wrappers.<AppSchedulingDevice>lambdaQuery()
.in(AppSchedulingDevice::getDeviceNo, deviceNos)
);
appWateringLogMapper.delete(
Wrappers.<AppWateringLog>lambdaQuery()
.in(AppWateringLog::getDeviceNo, deviceNos)
);
}
appWateringLogMapper.delete(
Wrappers.<AppWateringLog>lambdaQuery()
.eq(AppWateringLog::getUserId, userId)
);
appDeviceMapper.delete(
Wrappers.<AppDevice>lambdaQuery()
.eq(AppDevice::getUserId, userId)
);
}
private void deleteSystemUserData(Long userId) {
sysSocialMapper.delete(
Wrappers.<SysSocial>lambdaQuery()
.eq(SysSocial::getUserId, userId)
);
sysUserRoleMapper.delete(
Wrappers.<SysUserRole>lambdaQuery()
.eq(SysUserRole::getUserId, userId)
);
sysUserPostMapper.delete(
Wrappers.<SysUserPost>lambdaQuery()
.eq(SysUserPost::getUserId, userId)
);
int rows = sysUserMapper.deleteById(userId);
if (rows < 1) {
throw new ServiceException("注销账号失败");
}
}
}

View File

@@ -4,7 +4,7 @@ spring.servlet.multipart.location: /water/server/temp
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
@@ -16,7 +16,7 @@ spring.boot.admin.client:
--- # snail-job 配置
snail-job:
enabled: true
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "water_group"
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表

View File

@@ -1,7 +1,7 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8081
port: 8082
servlet:
# 应用的访问路径
context-path: /
@@ -26,9 +26,9 @@ mqtt:
broker-url: tcp://47.97.217.123
username: admin
password: 61e6062129e9
client-id: water-server-${server.port}
client-id: water-server-1${server.port}
qos: 1
keep-alive: 60
keep-alive: 300
connection-timeout: 30
max-inflight: 5000
clean-session: false
@@ -55,9 +55,10 @@ mqtt:
ack-key-prefix: "mqtt:command:ack:"
pending-set-key: "mqtt:command:pending:ids"
device-status-cache-prefix: "mqtt:device:status:"
device-status-cache-ttl-seconds: 300
device-status-cache-ttl-seconds: 900
offline-check:
enabled: true
ttl-compat-enabled: true
interval-ms: 90000
topics:
# 订阅/下发主题第一段为设备 MAC小写、去分隔符上行业务侧由 DeviceIdentityResolver 解析为设备编号
@@ -65,6 +66,7 @@ mqtt:
- /+/publish/finish/schedule #排程任务完成上报
- /+/publish/register #设备注册
- /+/publish/status #设备在线/离线状态online/offline离线配合设备 LWT
- /+/publish/power #电量
- /+/publish/ack #应答
- /+/publish/start #开始浇水上报
@@ -236,7 +238,7 @@ api-decrypt:
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
enabled: false
info:
# 标题
title: '标题water-Vue-Plus多租户管理系统_接口文档'
@@ -306,7 +308,7 @@ websocket:
--- # warm-flow工作流配置
warm-flow:
# 是否开启工作流默认true
enabled: true
enabled: false
# 是否开启设计器ui
ui: true
# 是否显示流程图顶部文字

View File

@@ -0,0 +1,65 @@
package org.dromara.web.config;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("dev")
class MqttCommandAckConfigUnitTest {
@Test
void commandAckMaxRetryCountIsThree() throws Exception {
Integer maxRetryCount = null;
Yaml yaml = new Yaml();
try (InputStream inputStream = new ClassPathResource("application.yml").getInputStream()) {
for (Object document : yaml.loadAll(inputStream)) {
if (!(document instanceof Map<?, ?> root)) {
continue;
}
Object mqtt = root.get("mqtt");
if (!(mqtt instanceof Map<?, ?> mqttConfig)) {
continue;
}
Object commandAck = mqttConfig.get("command-ack");
if (!(commandAck instanceof Map<?, ?> commandAckConfig)) {
continue;
}
Object value = commandAckConfig.get("max-retry-count");
if (value instanceof Number number) {
maxRetryCount = number.intValue();
}
}
}
assertThat(maxRetryCount).isEqualTo(3);
}
@Test
void offlineCheckTtlCompatibilityIsEnabled() throws Exception {
Boolean ttlCompatEnabled = null;
Yaml yaml = new Yaml();
try (InputStream inputStream = new ClassPathResource("application.yml").getInputStream()) {
for (Object document : yaml.loadAll(inputStream)) {
if (!(document instanceof Map<?, ?> root)) {
continue;
}
Object mqtt = root.get("mqtt");
if (!(mqtt instanceof Map<?, ?> mqttConfig)) {
continue;
}
Object offlineCheck = mqttConfig.get("offline-check");
if (offlineCheck instanceof Map<?, ?> offlineCheckConfig) {
ttlCompatEnabled = (Boolean) offlineCheckConfig.get("ttl-compat-enabled");
}
}
}
assertThat(ttlCompatEnabled).isTrue();
}
}

View File

@@ -0,0 +1,55 @@
package org.dromara.web.controller;
import cn.dev33.satoken.stp.StpUtil;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.AccountCancelBody;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.web.service.AccountCancellationService;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@Tag("dev")
class AuthControllerUnitTest {
@Mock
private AccountCancellationService accountCancellationService;
@Test
void cancelAccount_checksLoginCancelsAccountAndLogsOut() {
AuthController controller = new AuthController(
null,
null,
null,
null,
null,
null,
null,
null,
null,
accountCancellationService
);
try (MockedStatic<StpUtil> stpUtil = mockStatic(StpUtil.class);
MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
AccountCancelBody body = new AccountCancelBody();
body.setCode("123456");
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
R<Void> result = controller.cancelAccount(body);
assertThat(result.getCode()).isEqualTo(200);
verify(accountCancellationService).cancelCurrentAccount("123456");
stpUtil.verify(StpUtil::checkLogin);
stpUtil.verify(() -> StpUtil.logout(100L));
}
}
}

View File

@@ -0,0 +1,119 @@
package org.dromara.web.controller;
import cn.dev33.satoken.stp.StpUtil;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysUserService;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@Tag("dev")
class CaptchaControllerUnitTest {
@Mock
private MailProperties mailProperties;
@Mock
private ISysUserService userService;
@Test
void accountCancelCode_sendsSmsCodeWhenCurrentUserHasPhone() {
CaptchaController controller = spy(new CaptchaController(null, mailProperties, userService));
SysUserVo user = new SysUserVo();
user.setPhonenumber("13305376054");
user.setEmail("demo@example.com");
when(userService.selectUserById(100L)).thenReturn(user);
doReturn(R.ok("操作成功")).when(controller).sendSmsCode("13305376054", "zhangsan");
try (MockedStatic<StpUtil> stpUtil = mockStatic(StpUtil.class);
MockedStatic<SpringUtils> springUtils = mockStatic(SpringUtils.class);
MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
springUtils.when(() -> SpringUtils.getAopProxy(controller)).thenReturn(controller);
R<Void> result = controller.accountCancelCode();
assertThat(result.getCode()).isEqualTo(200);
stpUtil.verify(StpUtil::checkLogin);
verify(controller).sendSmsCode("13305376054", "zhangsan");
verify(controller, never()).emailCodeImpl("demo@example.com");
}
}
@Test
void accountCancelCode_sendsEmailCodeWhenCurrentUserHasNoPhone() {
CaptchaController controller = spy(new CaptchaController(null, mailProperties, userService));
SysUserVo user = new SysUserVo();
user.setEmail("demo@example.com");
when(userService.selectUserById(100L)).thenReturn(user);
when(mailProperties.getEnabled()).thenReturn(true);
doNothing().when(controller).emailCodeImpl("demo@example.com", "zhangsan");
try (MockedStatic<StpUtil> stpUtil = mockStatic(StpUtil.class);
MockedStatic<SpringUtils> springUtils = mockStatic(SpringUtils.class);
MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
springUtils.when(() -> SpringUtils.getAopProxy(controller)).thenReturn(controller);
R<Void> result = controller.accountCancelCode();
assertThat(result.getCode()).isEqualTo(200);
stpUtil.verify(StpUtil::checkLogin);
springUtils.verify(() -> SpringUtils.getAopProxy(controller));
verify(controller).emailCodeImpl("demo@example.com", "zhangsan");
verify(controller, never()).sendSmsCode("demo@example.com", "zhangsan");
}
}
@Test
void accountCancelCode_hasPerUserRateLimiter() throws NoSuchMethodException {
Method method = CaptchaController.class.getDeclaredMethod("accountCancelCode");
RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
assertThat(rateLimiter).isNotNull();
assertThat(rateLimiter.time()).isEqualTo(60);
assertThat(rateLimiter.count()).isEqualTo(1);
assertThat(rateLimiter.key())
.isEqualTo("#{T(org.dromara.common.satoken.utils.LoginHelper).getUserId()}");
}
@Test
void accountCancelCode_failsWhenCurrentUserHasNoPhoneOrEmail() {
CaptchaController controller = spy(new CaptchaController(null, mailProperties, userService));
SysUserVo user = new SysUserVo();
when(userService.selectUserById(100L)).thenReturn(user);
try (MockedStatic<StpUtil> stpUtil = mockStatic(StpUtil.class);
MockedStatic<SpringUtils> springUtils = mockStatic(SpringUtils.class);
MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
springUtils.when(() -> SpringUtils.getAopProxy(controller)).thenReturn(controller);
R<Void> result = controller.accountCancelCode();
assertThat(result.getCode()).isEqualTo(500);
assertThat(result.getMsg()).isEqualTo("当前账号未绑定手机号或邮箱");
stpUtil.verify(StpUtil::checkLogin);
verify(controller, never()).sendSmsCode("13305376054", "zhangsan");
verify(controller, never()).emailCodeImpl("demo@example.com", "zhangsan");
}
}
}

View File

@@ -0,0 +1,215 @@
package org.dromara.web.service;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.dromara.app.domain.*;
import org.dromara.app.mapper.*;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysSocial;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.mapper.SysSocialMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.mapper.SysUserPostMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@Tag("dev")
class AccountCancellationServiceUnitTest {
@Mock
private SysUserMapper sysUserMapper;
@Mock
private SysUserRoleMapper sysUserRoleMapper;
@Mock
private SysUserPostMapper sysUserPostMapper;
@Mock
private SysSocialMapper sysSocialMapper;
@Mock
private AppDeviceMapper appDeviceMapper;
@Mock
private AppScheduleMapper appScheduleMapper;
@Mock
private AppScheduleDetailMapper appScheduleDetailMapper;
@Mock
private AppSchedulingDeviceMapper appSchedulingDeviceMapper;
@Mock
private AppWateringLogMapper appWateringLogMapper;
@Mock
private AccountCancelCodeService accountCancelCodeService;
@Mock
private IDeviceCommandService deviceCommandService;
@BeforeAll
static void initTableInfo() {
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
initTableInfo(assistant, AppDevice.class);
initTableInfo(assistant, AppSchedule.class);
initTableInfo(assistant, AppScheduleDetail.class);
initTableInfo(assistant, AppSchedulingDevice.class);
initTableInfo(assistant, AppWateringLog.class);
initTableInfo(assistant, SysSocial.class);
initTableInfo(assistant, SysUserRole.class);
initTableInfo(assistant, SysUserPost.class);
}
private static void initTableInfo(MapperBuilderAssistant assistant, Class<?> entityClass) {
TableInfoHelper.remove(entityClass);
TableInfoHelper.initTableInfo(assistant, entityClass);
}
@Test
void cancelCurrentAccount_deletesCurrentUserAndRelatedData() {
AccountCancellationService service = newService();
AppDevice device = new AppDevice();
device.setDeviceNo("D01");
AppSchedule schedule = new AppSchedule();
schedule.setId(10L);
when(appDeviceMapper.selectList(any(Wrapper.class))).thenReturn(List.of(device));
when(appScheduleMapper.selectList(any(Wrapper.class))).thenReturn(List.of(schedule));
when(sysUserMapper.deleteById(100L)).thenReturn(1);
try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);
service.cancelCurrentAccount("123456");
verify(accountCancelCodeService).validate("zhangsan", "123456");
verify(deviceCommandService).sendInitDeviceCommand("D01");
verify(appSchedulingDeviceMapper, atLeastOnce()).delete(any(Wrapper.class));
verify(appScheduleDetailMapper).delete(any(Wrapper.class));
verify(appScheduleMapper).delete(any(Wrapper.class));
verify(appWateringLogMapper, atLeastOnce()).delete(any(Wrapper.class));
verify(appDeviceMapper).delete(any(Wrapper.class));
verify(sysSocialMapper).delete(any(Wrapper.class));
verify(sysUserRoleMapper).delete(any(Wrapper.class));
verify(sysUserPostMapper).delete(any(Wrapper.class));
verify(sysUserMapper).deleteById(100L);
verify(accountCancelCodeService).delete("zhangsan");
}
}
@Test
void cancelCurrentAccount_rejectsSuperAdmin() {
AccountCancellationService service = newService();
try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(1L);
loginHelper.when(() -> LoginHelper.isSuperAdmin(1L)).thenReturn(true);
assertThatThrownBy(() -> service.cancelCurrentAccount("123456"))
.isInstanceOf(ServiceException.class)
.hasMessageContaining("超级管理员");
verifyNoInteractions(
sysUserMapper,
sysUserRoleMapper,
sysUserPostMapper,
sysSocialMapper,
appDeviceMapper,
appScheduleMapper,
appScheduleDetailMapper,
appSchedulingDeviceMapper,
appWateringLogMapper,
accountCancelCodeService
);
}
}
@Test
void cancelCurrentAccount_rejectsInvalidCodeWithoutDeletingData() {
AccountCancellationService service = newService();
try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);
doThrow(new UserException("验证码无效"))
.when(accountCancelCodeService).validate("zhangsan", "654321");
assertThatThrownBy(() -> service.cancelCurrentAccount("654321"))
.isInstanceOf(UserException.class);
verifyNoInteractions(
sysUserMapper,
sysUserRoleMapper,
sysUserPostMapper,
sysSocialMapper,
appDeviceMapper,
appScheduleMapper,
appScheduleDetailMapper,
appSchedulingDeviceMapper,
appWateringLogMapper
);
verify(accountCancelCodeService).validate("zhangsan", "654321");
verify(accountCancelCodeService, never()).delete("zhangsan");
}
}
@Test
void cancelCurrentAccount_rejectsExpiredCodeWithoutDeletingData() {
AccountCancellationService service = newService();
try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);
doThrow(new CaptchaExpireException())
.when(accountCancelCodeService).validate("zhangsan", "123456");
assertThatThrownBy(() -> service.cancelCurrentAccount("123456"))
.isInstanceOf(CaptchaExpireException.class);
verifyNoInteractions(
sysUserMapper,
sysUserRoleMapper,
sysUserPostMapper,
sysSocialMapper,
appDeviceMapper,
appScheduleMapper,
appScheduleDetailMapper,
appSchedulingDeviceMapper,
appWateringLogMapper
);
verify(accountCancelCodeService).validate("zhangsan", "123456");
verify(accountCancelCodeService, never()).delete("zhangsan");
}
}
private AccountCancellationService newService() {
return new AccountCancellationService(
sysUserMapper,
sysUserRoleMapper,
sysUserPostMapper,
sysSocialMapper,
appDeviceMapper,
appScheduleMapper,
appScheduleDetailMapper,
appSchedulingDeviceMapper,
appWateringLogMapper,
accountCancelCodeService,
deviceCommandService
);
}
}