浇水bug修改
This commit is contained in:
@@ -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("注销成功");
|
||||
}
|
||||
|
||||
|
||||
@@ -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("当前账号未绑定手机号或邮箱");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("注销账号失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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`表
|
||||
|
||||
@@ -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
|
||||
# 是否显示流程图顶部文字
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user