浇水bug修改
This commit is contained in:
@@ -20,7 +20,6 @@ INSERT INTO sys_menu VALUES ('11654', '流程变量查询', '11621', '2', '#', '
|
|||||||
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, sysdate(), null, null, '');
|
INSERT INTO sys_menu VALUES ('11655', '流程变量修改', '11621', '3', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:variable', '#', 103, 1, sysdate(), null, null, '');
|
||||||
|
|
||||||
-- Device binding and device_no primary key migration.
|
-- Device binding and device_no primary key migration.
|
||||||
ALTER TABLE app_device ADD COLUMN bind_token_hash varchar(255) NULL COMMENT '设备绑定令牌哈希' AFTER device_no;
|
|
||||||
ALTER TABLE app_device ADD COLUMN qrcode varchar(1000) NULL COMMENT '设备二维码' AFTER device_img;
|
ALTER TABLE app_device ADD COLUMN qrcode varchar(1000) NULL COMMENT '设备二维码' AFTER device_img;
|
||||||
ALTER TABLE app_watering_log ADD COLUMN remark varchar(500) NULL COMMENT '备注' AFTER trigger_type;
|
ALTER TABLE app_watering_log ADD COLUMN remark varchar(500) NULL COMMENT '备注' AFTER trigger_type;
|
||||||
ALTER TABLE app_scheduling_device ADD COLUMN device_no varchar(64) NULL COMMENT '设备编号' AFTER schedule_id;
|
ALTER TABLE app_scheduling_device ADD COLUMN device_no varchar(64) NULL COMMENT '设备编号' AFTER schedule_id;
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import me.zhyd.oauth.request.AuthRequest;
|
|||||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||||
import org.dromara.common.core.constant.SystemConstants;
|
import org.dromara.common.core.constant.SystemConstants;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
import org.dromara.common.core.domain.model.ForgotLoginBody;
|
import org.dromara.common.core.domain.model.*;
|
||||||
import org.dromara.common.core.domain.model.LoginBody;
|
|
||||||
import org.dromara.common.core.domain.model.RegisterBody;
|
|
||||||
import org.dromara.common.core.domain.model.SocialLoginBody;
|
|
||||||
import org.dromara.common.core.exception.user.UserException;
|
import org.dromara.common.core.exception.user.UserException;
|
||||||
import org.dromara.common.core.utils.*;
|
import org.dromara.common.core.utils.*;
|
||||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||||
@@ -42,10 +39,7 @@ import org.dromara.system.service.ISysTenantService;
|
|||||||
import org.dromara.web.domain.vo.LoginTenantVo;
|
import org.dromara.web.domain.vo.LoginTenantVo;
|
||||||
import org.dromara.web.domain.vo.LoginVo;
|
import org.dromara.web.domain.vo.LoginVo;
|
||||||
import org.dromara.web.domain.vo.TenantListVo;
|
import org.dromara.web.domain.vo.TenantListVo;
|
||||||
import org.dromara.web.service.ForgotPasswordService;
|
import org.dromara.web.service.*;
|
||||||
import org.dromara.web.service.IAuthStrategy;
|
|
||||||
import org.dromara.web.service.SysLoginService;
|
|
||||||
import org.dromara.web.service.SysRegisterService;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -79,6 +73,7 @@ public class AuthController {
|
|||||||
private final ISysClientService clientService;
|
private final ISysClientService clientService;
|
||||||
private final ScheduledExecutorService scheduledExecutorService;
|
private final ScheduledExecutorService scheduledExecutorService;
|
||||||
private final ForgotPasswordService forgotPasswordService;
|
private final ForgotPasswordService forgotPasswordService;
|
||||||
|
private final AccountCancellationService accountCancellationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录方法
|
* 登录方法
|
||||||
@@ -189,6 +184,17 @@ public class AuthController {
|
|||||||
return R.ok("退出成功");
|
return R.ok("退出成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销当前账号
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/account")
|
||||||
|
public R<Void> cancelAccount(@Validated @RequestBody AccountCancelBody body) {
|
||||||
|
StpUtil.checkLogin();
|
||||||
|
accountCancellationService.cancelCurrentAccount(body.getCode());
|
||||||
|
StpUtil.logout();
|
||||||
|
return R.ok("注销成功");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户注册
|
* 用户注册
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.dromara.web.controller;
|
package org.dromara.web.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.captcha.generator.CodeGenerator;
|
import cn.hutool.captcha.generator.CodeGenerator;
|
||||||
import cn.hutool.captcha.generator.MathGenerator;
|
import cn.hutool.captcha.generator.MathGenerator;
|
||||||
import cn.hutool.captcha.generator.RandomGenerator;
|
import cn.hutool.captcha.generator.RandomGenerator;
|
||||||
@@ -21,11 +22,14 @@ import org.dromara.common.mail.utils.MailUtils;
|
|||||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
||||||
import org.dromara.common.ratelimiter.enums.LimitType;
|
import org.dromara.common.ratelimiter.enums.LimitType;
|
||||||
import org.dromara.common.redis.utils.RedisUtils;
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
import org.dromara.common.web.core.WaveAndCircleCaptcha;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||||
|
import org.dromara.common.web.core.WaveAndCircleCaptcha;
|
||||||
import org.dromara.sms4j.api.SmsBlend;
|
import org.dromara.sms4j.api.SmsBlend;
|
||||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||||
|
import org.dromara.system.domain.vo.SysUserVo;
|
||||||
|
import org.dromara.system.service.ISysUserService;
|
||||||
import org.dromara.web.domain.vo.CaptchaVo;
|
import org.dromara.web.domain.vo.CaptchaVo;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
@@ -52,6 +56,7 @@ public class CaptchaController {
|
|||||||
|
|
||||||
private final CaptchaProperties captchaProperties;
|
private final CaptchaProperties captchaProperties;
|
||||||
private final MailProperties mailProperties;
|
private final MailProperties mailProperties;
|
||||||
|
private final ISysUserService userService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,20 +71,20 @@ public class CaptchaController {
|
|||||||
String code = RandomUtil.randomNumbers(6);
|
String code = RandomUtil.randomNumbers(6);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
|
|
||||||
// if (Validator.isMobile(username)){
|
if (Validator.isMobile(username)){
|
||||||
// // 验证码模板id 自行处理 (查数据库或写死均可)
|
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||||
// String templateId = "SMS_333877107";
|
String templateId = "SMS_333877107";
|
||||||
// LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||||
// map.put("code", code);
|
map.put("code", code);
|
||||||
// SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||||
// SmsResponse smsResponse = smsBlend.sendMessage(username, templateId, map);
|
SmsResponse smsResponse = smsBlend.sendMessage(username, templateId, map);
|
||||||
// if (!smsResponse.isSuccess()) {
|
if (!smsResponse.isSuccess()) {
|
||||||
// log.error("验证码短信发送异常 => {}", smsResponse);
|
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||||
// return R.fail(smsResponse.getData().toString());
|
return R.fail(smsResponse.getData().toString());
|
||||||
// }
|
}
|
||||||
// }else {
|
}else {
|
||||||
// emailCodeImpl(username);
|
emailCodeImpl(username);
|
||||||
// }
|
}
|
||||||
|
|
||||||
return R.ok(code);
|
return R.ok(code);
|
||||||
}
|
}
|
||||||
@@ -95,7 +100,49 @@ public class CaptchaController {
|
|||||||
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
||||||
@GetMapping("/resource/sms/code")
|
@GetMapping("/resource/sms/code")
|
||||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
||||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
R<Void> result = sendSmsCode(phonenumber, phonenumber);
|
||||||
|
if (R.isError(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销账号验证码
|
||||||
|
*/
|
||||||
|
@GetMapping("/resource/account/cancel/code")
|
||||||
|
public R<Void> accountCancelCode() {
|
||||||
|
StpUtil.checkLogin();
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
String username = LoginHelper.getUsername();
|
||||||
|
if (userId == null || StringUtils.isBlank(username)) {
|
||||||
|
return R.fail("用户未登录");
|
||||||
|
}
|
||||||
|
SysUserVo user = userService.selectUserById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return R.fail("用户不存在");
|
||||||
|
}
|
||||||
|
if (Validator.isMobile(user.getPhonenumber())) {
|
||||||
|
return sendSmsCode(user.getPhonenumber(), username);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(user.getEmail())) {
|
||||||
|
if (!mailProperties.getEnabled()) {
|
||||||
|
return R.fail("当前系统没有开启邮箱功能!");
|
||||||
|
}
|
||||||
|
emailCodeImpl(user.getEmail(), username);
|
||||||
|
return R.ok("操作成功");
|
||||||
|
}
|
||||||
|
return R.fail("当前账号未绑定手机号或邮箱");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信验证码
|
||||||
|
*
|
||||||
|
* @param phonenumber 接收手机号
|
||||||
|
* @param cacheKey 验证码缓存标识
|
||||||
|
*/
|
||||||
|
public R<Void> sendSmsCode(String phonenumber, String cacheKey) {
|
||||||
|
String key = GlobalConstants.CAPTCHA_CODE_KEY + cacheKey;
|
||||||
String code = RandomUtil.randomNumbers(6);
|
String code = RandomUtil.randomNumbers(6);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||||
@@ -131,7 +178,15 @@ public class CaptchaController {
|
|||||||
*/
|
*/
|
||||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||||
public void emailCodeImpl(String email) {
|
public void emailCodeImpl(String email) {
|
||||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
emailCodeImpl(email, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱验证码
|
||||||
|
*/
|
||||||
|
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||||
|
public void emailCodeImpl(String email, String cacheKey) {
|
||||||
|
String key = GlobalConstants.CAPTCHA_CODE_KEY + cacheKey;
|
||||||
String code = RandomUtil.randomNumbers(6);
|
String code = RandomUtil.randomNumbers(6);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.dromara.web.service;
|
|||||||
import cn.hutool.core.lang.Validator;
|
import cn.hutool.core.lang.Validator;
|
||||||
import cn.hutool.crypto.digest.BCrypt;
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.Data;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.constant.Constants;
|
import org.dromara.common.core.constant.Constants;
|
||||||
import org.dromara.common.core.constant.GlobalConstants;
|
import org.dromara.common.core.constant.GlobalConstants;
|
||||||
@@ -67,7 +66,7 @@ public class SysRegisterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SysUserBo sysUser = new SysUserBo();
|
SysUserBo sysUser = new SysUserBo();
|
||||||
// sysUser.setUserName(username);
|
sysUser.setUserName(username);
|
||||||
|
|
||||||
|
|
||||||
sysUser.setNickName("用户"+new Date().getTime());
|
sysUser.setNickName("用户"+new Date().getTime());
|
||||||
@@ -76,6 +75,7 @@ public class SysRegisterService {
|
|||||||
boolean exist;
|
boolean exist;
|
||||||
if(Validator.isEmail(username)){
|
if(Validator.isEmail(username)){
|
||||||
sysUser.setEmail(username);
|
sysUser.setEmail(username);
|
||||||
|
|
||||||
exist = TenantHelper.dynamic(tenantId, () -> {
|
exist = TenantHelper.dynamic(tenantId, () -> {
|
||||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||||
.eq(SysUser::getEmail, sysUser.getEmail()));
|
.eq(SysUser::getEmail, sysUser.getEmail()));
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
|
//loginUser.setUsername(phonenumber);
|
||||||
SaLoginParameter model = new SaLoginParameter();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDeviceType(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
|
|||||||
@@ -96,13 +96,13 @@ spring:
|
|||||||
spring.data:
|
spring.data:
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
host: localhost
|
host: 47.97.217.123
|
||||||
# 端口,默认为6379
|
# 端口,默认为6379
|
||||||
port: 6379
|
port: 6379
|
||||||
# 数据库索引
|
# 数据库索引
|
||||||
database: 0
|
database: 0
|
||||||
# redis 密码必须配置
|
# redis 密码必须配置
|
||||||
#password:
|
password: waterx123
|
||||||
# 连接超时时间
|
# 连接超时时间
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
# 是否开启ssl
|
# 是否开启ssl
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ server:
|
|||||||
--- # mqtt配置信息
|
--- # mqtt配置信息
|
||||||
mqtt:
|
mqtt:
|
||||||
enabled: ${MQTT_ENABLED:true}
|
enabled: ${MQTT_ENABLED:true}
|
||||||
broker-url: ssl://service.reinkun.com:8883
|
#broker-url: ssl://www.mmipco.cn:8883
|
||||||
|
broker-url: tcp://47.97.217.123
|
||||||
username: admin
|
username: admin
|
||||||
password: yukun_mqtt
|
password: 61e6062129e9
|
||||||
client-id: water-server-${server.port}
|
client-id: water-server-${server.port}
|
||||||
qos: 1
|
qos: 1
|
||||||
keep-alive: 60
|
keep-alive: 60
|
||||||
@@ -46,7 +47,7 @@ mqtt:
|
|||||||
command-ack:
|
command-ack:
|
||||||
enabled: true
|
enabled: true
|
||||||
max-retry-count: 3
|
max-retry-count: 3
|
||||||
retry-interval-ms: 5000
|
retry-interval-ms: 30000
|
||||||
scan-interval-ms: 5000
|
scan-interval-ms: 5000
|
||||||
pending-ttl-seconds: 86400
|
pending-ttl-seconds: 86400
|
||||||
ack-ttl-seconds: 86400
|
ack-ttl-seconds: 86400
|
||||||
@@ -66,6 +67,7 @@ mqtt:
|
|||||||
- /+/publish/register #设备注册
|
- /+/publish/register #设备注册
|
||||||
- /+/publish/power #电量
|
- /+/publish/power #电量
|
||||||
- /+/publish/ack #应答
|
- /+/publish/ack #应答
|
||||||
|
- /+/publish/start #开始浇水上报
|
||||||
- /+/publish/finish/key #按键浇水完成
|
- /+/publish/finish/key #按键浇水完成
|
||||||
- /+/publish/error #硬件故障
|
- /+/publish/error #硬件故障
|
||||||
# - /+/subscriber/cmd #下发命令 0关闭浇水 1开始浇水
|
# - /+/subscriber/cmd #下发命令 0关闭浇水 1开始浇水
|
||||||
@@ -123,9 +125,9 @@ spring:
|
|||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
# 单个文件大小
|
# 单个文件大小
|
||||||
max-file-size: 10MB
|
max-file-size: 100MB
|
||||||
# 设置总上传的文件大小
|
# 设置总上传的文件大小
|
||||||
max-request-size: 20MB
|
max-request-size: 100MB
|
||||||
mvc:
|
mvc:
|
||||||
# 设置静态资源路径 防止所有请求都去查静态资源
|
# 设置静态资源路径 防止所有请求都去查静态资源
|
||||||
static-path-pattern: /static/**
|
static-path-pattern: /static/**
|
||||||
|
|||||||
@@ -50,6 +50,24 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
|||||||
withCommandLock(commandId, () -> deletePending(commandId));
|
withCommandLock(commandId, () -> deletePending(commandId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean resolveMissingCommandId(DeviceCommandAck ack, List<DeviceCommand> pendingCommands) {
|
||||||
|
if (ack == null || StringUtils.isNotBlank(ack.getCommandId())) {
|
||||||
|
return ack != null;
|
||||||
|
}
|
||||||
|
if (pendingCommands == null || pendingCommands.size() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DeviceCommand command = pendingCommands.get(0);
|
||||||
|
if (command == null || StringUtils.isBlank(command.getCommandId())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ack.setCommandId(command.getCommandId());
|
||||||
|
if (StringUtils.isBlank(ack.getStatus())) {
|
||||||
|
ack.setStatus("1");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAck(String deviceNo, String payload) {
|
public void handleAck(String deviceNo, String payload) {
|
||||||
if (StringUtils.isBlank(payload)) {
|
if (StringUtils.isBlank(payload)) {
|
||||||
@@ -70,11 +88,19 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
|||||||
log.warn("[MQTT] ACK JSON 格式错误 设备编号={} 消息体={}", deviceNo, payload, e);
|
log.warn("[MQTT] ACK JSON 格式错误 设备编号={} 消息体={}", deviceNo, payload, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ack == null || ack.getCommandId() == null) {
|
if (ack == null) {
|
||||||
log.warn("[MQTT] ACK 缺少命令编号 设备编号={} 消息体={}", deviceNo, payload);
|
log.warn("[MQTT] ACK 缺少命令编号 设备编号={} 消息体={}", deviceNo, payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ack.setDeviceNo(deviceNo);
|
ack.setDeviceNo(deviceNo);
|
||||||
|
if (StringUtils.isBlank(ack.getCommandId())) {
|
||||||
|
List<DeviceCommand> pendingCommands = findPendingCommandsByDeviceNo(deviceNo);
|
||||||
|
if (!resolveMissingCommandId(ack, pendingCommands)) {
|
||||||
|
log.warn("[MQTT] ACK 缺少命令编号且无法唯一匹配待确认命令 设备编号={} 待确认数量={} 消息体={}",
|
||||||
|
deviceNo, pendingCommands.size(), payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
boolean ackSaved = withCommandLock(ack.getCommandId(), () -> {
|
boolean ackSaved = withCommandLock(ack.getCommandId(), () -> {
|
||||||
deletePending(ack.getCommandId());
|
deletePending(ack.getCommandId());
|
||||||
RedisUtils.setCacheObject(ackKey(ack.getCommandId()), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds()));
|
RedisUtils.setCacheObject(ackKey(ack.getCommandId()), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds()));
|
||||||
@@ -84,7 +110,7 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshDeviceOnline(deviceNo);
|
refreshDeviceOnline(deviceNo);
|
||||||
log.info("[MQTT] 收到命令确认 设备编号={} 命令编号={} 状态={}", deviceNo, ack.getCommandId(), ack.getStatus());
|
log.info("[MQTT] 收到命令确认 设备编号={} 命令编号={} message={}", deviceNo, ack.getCommandId(), ack.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlainAck(String deviceNo, String payload) {
|
private void handlePlainAck(String deviceNo, String payload) {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ public class AppController extends BaseController {
|
|||||||
private final IAppWateringLogService appWateringLogService;
|
private final IAppWateringLogService appWateringLogService;
|
||||||
private final IDeviceCommandService deviceCommandService;
|
private final IDeviceCommandService deviceCommandService;
|
||||||
private final ISysOssService ossService;
|
private final ISysOssService ossService;
|
||||||
|
private final IAppVersionService appVersionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*查询设备
|
*查询设备
|
||||||
@@ -288,21 +290,12 @@ public class AppController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> buildScheduleBindPayload(Long scheduleId, String deviceNo) {
|
private Map<String, Object> buildScheduleBindPayload(Long scheduleId, String deviceNo) {
|
||||||
// AppScheduleVo schedule = getOwnedSchedule(scheduleId);
|
|
||||||
List<AppScheduleDetailVo> details = appScheduleDetailService.queryByScheduleIdByStatus(scheduleId);
|
List<AppScheduleDetailVo> details = appScheduleDetailService.queryByScheduleIdByStatus(scheduleId);
|
||||||
|
|
||||||
// Map<String, Object> schedulePayload = new HashMap<>();
|
|
||||||
// schedulePayload.put("id", schedule.getId());
|
|
||||||
// schedulePayload.put("name", schedule.getName());
|
|
||||||
// schedulePayload.put("status", schedule.getStatus());
|
|
||||||
|
|
||||||
Map<String, Object> payload = new HashMap<>();
|
Map<String, Object> payload = new HashMap<>();
|
||||||
// payload.put("cmd", -1);
|
|
||||||
payload.put("deviceNo", deviceNo);
|
payload.put("deviceNo", deviceNo);
|
||||||
// payload.put("schedule", schedulePayload);
|
|
||||||
payload.put("details", toScheduleDetailPayload(details));
|
payload.put("details", toScheduleDetailPayload(details));
|
||||||
return payload;
|
return payload;
|
||||||
// return toScheduleDetailPayload(details);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Map<String, Object>> toScheduleDetailPayload(List<AppScheduleDetailVo> details) {
|
private List<Map<String, Object>> toScheduleDetailPayload(List<AppScheduleDetailVo> details) {
|
||||||
@@ -317,13 +310,16 @@ public class AppController extends BaseController {
|
|||||||
timeSlots.add(timeSlot);
|
timeSlots.add(timeSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> item = new HashMap<>();
|
if (detail.getStatus().equals("1")){
|
||||||
item.put("id", detail.getId());
|
Map<String, Object> item = new HashMap<>();
|
||||||
item.put("weekday", detail.getWeekday());
|
// item.put("id", detail.getId());
|
||||||
item.put("timeData", timeSlots);
|
item.put("weekday", detail.getWeekday());
|
||||||
item.put("triggerType", detail.getTriggerType());
|
item.put("timeData", timeSlots);
|
||||||
item.put("status", detail.getStatus());
|
item.put("triggerType", detail.getTriggerType());
|
||||||
detailPayload.add(item);
|
item.put("status", detail.getStatus());
|
||||||
|
detailPayload.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return detailPayload;
|
return detailPayload;
|
||||||
}
|
}
|
||||||
@@ -470,7 +466,7 @@ public class AppController extends BaseController {
|
|||||||
Boolean updated = appScheduleService.updateStatusByBo(bo);
|
Boolean updated = appScheduleService.updateStatusByBo(bo);
|
||||||
if (updated) {
|
if (updated) {
|
||||||
// 向所有绑定设备下发排程状态变更
|
// 向所有绑定设备下发排程状态变更
|
||||||
notifyBoundDevicesScheduleUpdate(bo.getId());
|
notifyBoundDevicesScheduleStatusChange(bo.getId(), bo.getStatus());
|
||||||
}
|
}
|
||||||
return toAjax(updated);
|
return toAjax(updated);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -560,6 +556,39 @@ public class AppController extends BaseController {
|
|||||||
return appWateringLogVoTableDataInfo;
|
return appWateringLogVoTableDataInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备最新进行中的浇水记录
|
||||||
|
*/
|
||||||
|
@GetMapping("/latestWaterLog")
|
||||||
|
public R<Map<String, Object>> latestWaterLog(@RequestParam String deviceNo) {
|
||||||
|
try {
|
||||||
|
assertDeviceOwned(deviceNo);
|
||||||
|
|
||||||
|
AppWateringLogBo bo = new AppWateringLogBo();
|
||||||
|
bo.setUserId(LoginHelper.getUserId());
|
||||||
|
bo.setDeviceNo(deviceNo);
|
||||||
|
bo.setStatus("1");
|
||||||
|
List<AppWateringLogVo> logs = appWateringLogService.queryList(bo);
|
||||||
|
if (logs == null || logs.isEmpty()) {
|
||||||
|
return R.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppWateringLogVo latest = logs.stream()
|
||||||
|
.max(Comparator.comparing(AppWateringLogVo::getId, Comparator.nullsFirst(Long::compareTo)))
|
||||||
|
.orElse(null);
|
||||||
|
if (latest == null) {
|
||||||
|
return R.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("startTime", formatSecondTime(latest.getStartTime()));
|
||||||
|
result.put("endTime", formatSecondTime(latest.getEndTime()));
|
||||||
|
return R.ok(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除浇水记录
|
* 删除浇水记录
|
||||||
@@ -666,6 +695,41 @@ public class AppController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查APP版本更新
|
||||||
|
*/
|
||||||
|
@GetMapping("/checkVersion")
|
||||||
|
public R<AppVersionCheckVo> checkVersion(@RequestParam(required = false, defaultValue = "android") String platform,
|
||||||
|
@RequestParam(required = false) String currentVersion) {
|
||||||
|
try {
|
||||||
|
if (StringUtils.isBlank(currentVersion)) {
|
||||||
|
throw new ServiceException("当前版本不能为空");
|
||||||
|
}
|
||||||
|
String normalizedPlatform = StringUtils.lowerCase(StringUtils.trim(platform));
|
||||||
|
if (!StringUtils.equalsAny(normalizedPlatform, "android", "ios")) {
|
||||||
|
throw new ServiceException("平台类型仅支持 android 或 ios");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppVersionVo version = appVersionService.queryLatestByPlatform(normalizedPlatform);
|
||||||
|
if (version == null || StringUtils.isBlank(version.getLatestVersion())) {
|
||||||
|
throw new ServiceException("版本配置不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppVersionCheckVo result = new AppVersionCheckVo();
|
||||||
|
result.setPlatform(normalizedPlatform);
|
||||||
|
result.setCurrentVersion(currentVersion);
|
||||||
|
result.setLatestVersion(version.getLatestVersion());
|
||||||
|
result.setVersionCode(version.getVersionCode());
|
||||||
|
result.setUpdateAvailable(!StringUtils.equals(currentVersion, version.getLatestVersion()));
|
||||||
|
result.setForceUpdate(StringUtils.equals(version.getForceUpdate(), "1"));
|
||||||
|
result.setDownloadUrl(version.getDownloadUrl());
|
||||||
|
result.setReleaseNotes(version.getReleaseNotes());
|
||||||
|
return R.ok(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改用户
|
* 修改用户
|
||||||
*/
|
*/
|
||||||
@@ -763,6 +827,10 @@ public class AppController extends BaseController {
|
|||||||
return message.equals(i18nMessage) ? message : i18nMessage;
|
return message.equals(i18nMessage) ? message : i18nMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatSecondTime(Date time) {
|
||||||
|
return time == null ? null : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
|
||||||
|
}
|
||||||
|
|
||||||
private AppDeviceVo getOwnedDevice(String deviceNo) {
|
private AppDeviceVo getOwnedDevice(String deviceNo) {
|
||||||
if (StringUtils.isBlank(deviceNo)) {
|
if (StringUtils.isBlank(deviceNo)) {
|
||||||
throw new ServiceException("app.device.no.not.blank");
|
throw new ServiceException("app.device.no.not.blank");
|
||||||
@@ -833,6 +901,25 @@ public class AppController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按排程开关状态下发不同主题:关闭走取消主题,开启重新下发排程。
|
||||||
|
*/
|
||||||
|
private void notifyBoundDevicesScheduleStatusChange(Long scheduleId, String status) {
|
||||||
|
List<String> deviceNos = findBoundDeviceNos(scheduleId);
|
||||||
|
for (String deviceNo : deviceNos) {
|
||||||
|
try {
|
||||||
|
if ("0".equals(status)) {
|
||||||
|
deviceCommandService.sendScheduleCanceledCommand(deviceNo, scheduleId);
|
||||||
|
} else {
|
||||||
|
deviceCommandService.sendScheduleBindCommand(deviceNo, buildScheduleBindPayload(scheduleId, deviceNo));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[排程通知] 下发排程状态变更失败 排程ID={} 状态={} 设备编号={} 原因={}",
|
||||||
|
scheduleId, status, deviceNo, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向排程关联的所有设备下发排程解绑通知
|
* 向排程关联的所有设备下发排程解绑通知
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ public class AppDevice extends BaseEntity {
|
|||||||
@TableId(value = "device_no")
|
@TableId(value = "device_no")
|
||||||
private String deviceNo;
|
private String deviceNo;
|
||||||
|
|
||||||
private String bindTokenHash;
|
|
||||||
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
private String deviceInitName;
|
||||||
|
|
||||||
private String deviceImg;
|
private String deviceImg;
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public class AppWateringLog extends BaseEntity {
|
|||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
|
private String status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ public class AppDeviceBo extends BaseEntity {
|
|||||||
@NotBlank(message = "设备编号不能为空", groups = { EditGroup.class })
|
@NotBlank(message = "设备编号不能为空", groups = { EditGroup.class })
|
||||||
private String deviceNo;
|
private String deviceNo;
|
||||||
|
|
||||||
private String bindToken;
|
|
||||||
|
|
||||||
private String bindTokenHash;
|
|
||||||
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
@@ -29,6 +26,7 @@ public class AppDeviceBo extends BaseEntity {
|
|||||||
* 设备名称
|
* 设备名称
|
||||||
*/
|
*/
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
private String deviceInitName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备图片
|
* 设备图片
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class AppWateringLogBo extends BaseEntity {
|
|||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
|
private String status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class AppDeviceVo implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "设备名称")
|
@ExcelProperty(value = "设备名称")
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
private String deviceInitName;
|
||||||
|
|
||||||
@ExcelProperty(value = "设备图片")
|
@ExcelProperty(value = "设备图片")
|
||||||
private String deviceImg;
|
private String deviceImg;
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public class AppWateringLogVo implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "备注")
|
@ExcelProperty(value = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
private String status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.dromara.app.service.IAppDeviceService;
|
|||||||
import org.dromara.app.service.IDeviceCommandPublisher;
|
import org.dromara.app.service.IDeviceCommandPublisher;
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
import org.dromara.common.redis.utils.RedisUtils;
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.redisson.api.RLock;
|
import org.redisson.api.RLock;
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@@ -92,6 +93,7 @@ DeviceRegisterHandler implements MqttTopicHandler {
|
|||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
device.setDeviceName(valueAsString(dto.get("deviceName")));
|
device.setDeviceName(valueAsString(dto.get("deviceName")));
|
||||||
|
device.setDeviceInitName(valueAsString(dto.get("deviceName")));
|
||||||
device.setPowerLevel(valueAsString(dto.get("powerLevel")));
|
device.setPowerLevel(valueAsString(dto.get("powerLevel")));
|
||||||
device.setDeviceEm(valueAsString(dto.get("deviceEm")));
|
device.setDeviceEm(valueAsString(dto.get("deviceEm")));
|
||||||
device.setDeviceSn(valueAsString(dto.get("deviceSn")));
|
device.setDeviceSn(valueAsString(dto.get("deviceSn")));
|
||||||
@@ -143,7 +145,7 @@ DeviceRegisterHandler implements MqttTopicHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String firstNotBlank(Object first, Object second) {
|
private @Nullable String firstNotBlank(Object first, Object second) {
|
||||||
String firstText = valueAsString(first);
|
String firstText = valueAsString(first);
|
||||||
if (firstText != null && !firstText.isBlank()) {
|
if (firstText != null && !firstText.isBlank()) {
|
||||||
return firstText;
|
return firstText;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.app.domain.AppSchedulingDevice;
|
import org.dromara.app.domain.AppSchedulingDevice;
|
||||||
|
import org.dromara.app.domain.bo.AppDeviceBo;
|
||||||
import org.dromara.app.domain.bo.AppWateringLogBo;
|
import org.dromara.app.domain.bo.AppWateringLogBo;
|
||||||
import org.dromara.app.domain.vo.AppDeviceVo;
|
import org.dromara.app.domain.vo.AppDeviceVo;
|
||||||
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
|
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
|
||||||
@@ -53,11 +54,8 @@ public class KeyFinishHandler implements MqttTopicHandler {
|
|||||||
}
|
}
|
||||||
Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
|
Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
|
||||||
AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
|
AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
|
||||||
if ("0".equals(logBo.getTriggerType())) {
|
appWateringLogService.confirmScheduleLog(logBo);
|
||||||
appWateringLogService.confirmScheduleLog(logBo);
|
updateDeviceWorkStatusIdle(deviceNo);
|
||||||
} else {
|
|
||||||
appWateringLogService.insertByBo(logBo);
|
|
||||||
}
|
|
||||||
log.info("[MQTT] 按键浇水完成上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
|
log.info("[MQTT] 按键浇水完成上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[MQTT] 按键浇水完成上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
|
log.error("[MQTT] 按键浇水完成上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
|
||||||
@@ -89,6 +87,7 @@ public class KeyFinishHandler implements MqttTopicHandler {
|
|||||||
logBo.setStartTime(startTime);
|
logBo.setStartTime(startTime);
|
||||||
logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
|
logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
|
||||||
logBo.setTriggerType("1");
|
logBo.setTriggerType("1");
|
||||||
|
logBo.setStatus("0");
|
||||||
if ("schedule".equals(valueAsString(dto.get("triggerON")))){
|
if ("schedule".equals(valueAsString(dto.get("triggerON")))){
|
||||||
logBo.setTriggerType("0");
|
logBo.setTriggerType("0");
|
||||||
logBo.setScheduleId(queryScheduleId(topicDeviceNo));
|
logBo.setScheduleId(queryScheduleId(topicDeviceNo));
|
||||||
@@ -99,6 +98,13 @@ public class KeyFinishHandler implements MqttTopicHandler {
|
|||||||
return logBo;
|
return logBo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateDeviceWorkStatusIdle(String deviceNo) {
|
||||||
|
AppDeviceBo deviceBo = new AppDeviceBo();
|
||||||
|
deviceBo.setDeviceNo(deviceNo);
|
||||||
|
deviceBo.setWorkStatus("0");
|
||||||
|
appDeviceService.updateByBo(deviceBo);
|
||||||
|
}
|
||||||
|
|
||||||
private Long queryUserId(String deviceNo) {
|
private Long queryUserId(String deviceNo) {
|
||||||
AppDeviceVo device = appDeviceService.queryById(deviceNo);
|
AppDeviceVo device = appDeviceService.queryById(deviceNo);
|
||||||
return device == null ? null : device.getUserId();
|
return device == null ? null : device.getUserId();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.app.domain.AppSchedulingDevice;
|
import org.dromara.app.domain.AppSchedulingDevice;
|
||||||
|
import org.dromara.app.domain.bo.AppDeviceBo;
|
||||||
import org.dromara.app.domain.bo.AppWateringLogBo;
|
import org.dromara.app.domain.bo.AppWateringLogBo;
|
||||||
import org.dromara.app.domain.vo.AppDeviceVo;
|
import org.dromara.app.domain.vo.AppDeviceVo;
|
||||||
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
|
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
|
||||||
@@ -54,6 +55,7 @@ public class ScheduleFinishHandler implements MqttTopicHandler {
|
|||||||
Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
|
Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
|
||||||
AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
|
AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
|
||||||
appWateringLogService.confirmScheduleLog(logBo);
|
appWateringLogService.confirmScheduleLog(logBo);
|
||||||
|
updateDeviceWorkStatusIdle(deviceNo);
|
||||||
log.info("[MQTT] 浇水排程上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
|
log.info("[MQTT] 浇水排程上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[MQTT] 浇水排程上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
|
log.error("[MQTT] 浇水排程上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
|
||||||
@@ -86,6 +88,7 @@ public class ScheduleFinishHandler implements MqttTopicHandler {
|
|||||||
logBo.setStartTime(startTime);
|
logBo.setStartTime(startTime);
|
||||||
logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
|
logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
|
||||||
logBo.setTriggerType("0");
|
logBo.setTriggerType("0");
|
||||||
|
logBo.setStatus("0");
|
||||||
if ("mqtt on".equals(valueAsString(dto.get("triggerON")))){
|
if ("mqtt on".equals(valueAsString(dto.get("triggerON")))){
|
||||||
logBo.setTriggerType("1");
|
logBo.setTriggerType("1");
|
||||||
}
|
}
|
||||||
@@ -95,6 +98,13 @@ public class ScheduleFinishHandler implements MqttTopicHandler {
|
|||||||
return logBo;
|
return logBo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateDeviceWorkStatusIdle(String deviceNo) {
|
||||||
|
AppDeviceBo deviceBo = new AppDeviceBo();
|
||||||
|
deviceBo.setDeviceNo(deviceNo);
|
||||||
|
deviceBo.setWorkStatus("0");
|
||||||
|
appDeviceService.updateByBo(deviceBo);
|
||||||
|
}
|
||||||
|
|
||||||
private Long queryUserId(String deviceNo) {
|
private Long queryUserId(String deviceNo) {
|
||||||
AppDeviceVo device = appDeviceService.queryById(deviceNo);
|
AppDeviceVo device = appDeviceService.queryById(deviceNo);
|
||||||
return device == null ? null : device.getUserId();
|
return device == null ? null : device.getUserId();
|
||||||
@@ -128,6 +138,16 @@ public class ScheduleFinishHandler implements MqttTopicHandler {
|
|||||||
// Try the next supported format.
|
// Try the next supported format.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||||
|
for (String pattern : new String[]{"yyyy-MM-dd H:mm:ss", "yyyy-MM-dd H:mm"}) {
|
||||||
|
try {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(pattern);
|
||||||
|
format.setLenient(false);
|
||||||
|
return format.parse(today + " " + startTime);
|
||||||
|
} catch (ParseException ignored) {
|
||||||
|
// Try the next supported format.
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new IllegalArgumentException("Unsupported startTime format: " + startTime);
|
throw new IllegalArgumentException("Unsupported startTime format: " + startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package org.dromara.app.handler;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.app.domain.AppSchedulingDevice;
|
||||||
|
import org.dromara.app.domain.bo.AppDeviceBo;
|
||||||
|
import org.dromara.app.domain.bo.AppWateringLogBo;
|
||||||
|
import org.dromara.app.domain.vo.AppDeviceVo;
|
||||||
|
import org.dromara.app.domain.vo.AppWateringLogVo;
|
||||||
|
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
|
||||||
|
import org.dromara.app.mqtt.MqttTopicHandler;
|
||||||
|
import org.dromara.app.service.IAppDeviceService;
|
||||||
|
import org.dromara.app.service.IAppWateringLogService;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始浇水上报处理器,匹配 /{deviceNo}/publish/startWater。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StartWaterHandler implements MqttTopicHandler {
|
||||||
|
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/start$");
|
||||||
|
|
||||||
|
private final IAppWateringLogService appWateringLogService;
|
||||||
|
private final IAppDeviceService appDeviceService;
|
||||||
|
private final AppSchedulingDeviceMapper schedulingDeviceMapper;
|
||||||
|
private final DeviceIdentityResolver deviceIdentityResolver;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pattern topicPattern() {
|
||||||
|
return PATTERN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(String deviceIdentity, String payload) {
|
||||||
|
String deviceNo = null;
|
||||||
|
try {
|
||||||
|
deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity);
|
||||||
|
if (deviceNo == null) {
|
||||||
|
log.warn("[MQTT] 开始浇水上报未找到设备 时间={} 设备标识={} 消息体={}",
|
||||||
|
HandlerLogTime.now(), deviceIdentity, payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> dto = JsonUtils.parseObject(payload, Map.class);
|
||||||
|
AppWateringLogBo logBo = buildWateringLog(deviceNo, dto);
|
||||||
|
if (hasDuplicateRunningLog(logBo)) {
|
||||||
|
updateDeviceWorkStatusWorking(deviceNo);
|
||||||
|
log.info("[MQTT] 开始浇水上报已存在进行中记录,跳过保存 时间={} 设备编号={} 开始时间={} 浇水方式={}",
|
||||||
|
HandlerLogTime.now(), deviceNo, logBo.getStartTime(), logBo.getTriggerType());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appWateringLogService.insertByBo(logBo);
|
||||||
|
updateDeviceWorkStatusWorking(deviceNo);
|
||||||
|
log.info("[MQTT] 开始浇水上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MQTT] 开始浇水上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}",
|
||||||
|
HandlerLogTime.now(), deviceIdentity, deviceNo, payload, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDeviceWorkStatusWorking(String deviceNo) {
|
||||||
|
AppDeviceBo deviceBo = new AppDeviceBo();
|
||||||
|
deviceBo.setDeviceNo(deviceNo);
|
||||||
|
deviceBo.setWorkStatus("1");
|
||||||
|
appDeviceService.updateByBo(deviceBo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppWateringLogBo buildWateringLog(String topicDeviceNo, Map<String, Object> dto) {
|
||||||
|
String payloadDeviceNo = valueAsString(dto.get("deviceNo"));
|
||||||
|
if (StringUtils.isNotBlank(payloadDeviceNo) && !topicDeviceNo.equals(payloadDeviceNo)) {
|
||||||
|
log.warn("[MQTT] 开始浇水上报设备编号不一致 时间={} 主题设备编号={} 消息体设备编号={}",
|
||||||
|
HandlerLogTime.now(), topicDeviceNo, payloadDeviceNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
String triggerType = resolveTriggerType(dto);
|
||||||
|
Date startTime = parseStartTime(valueAsString(dto.get("startTime")));
|
||||||
|
Integer durationMin = valueAsInteger(dto.get("durationMin"));
|
||||||
|
|
||||||
|
AppWateringLogBo logBo = new AppWateringLogBo();
|
||||||
|
logBo.setDeviceNo(topicDeviceNo);
|
||||||
|
logBo.setCommandId(valueAsString(dto.get("commandId")));
|
||||||
|
logBo.setUserId(queryUserId(topicDeviceNo));
|
||||||
|
logBo.setScheduleId("0".equals(triggerType) ? queryScheduleId(topicDeviceNo) : 0L);
|
||||||
|
logBo.setStartTime(startTime);
|
||||||
|
logBo.setEndTime(resolveEndTime(startTime, durationMin));
|
||||||
|
logBo.setDurationMin(durationMin == null ? null : String.valueOf(durationMin));
|
||||||
|
logBo.setTriggerType(triggerType);
|
||||||
|
logBo.setStatus("1");
|
||||||
|
logBo.setCreateTime(new Date());
|
||||||
|
logBo.setRemark("0".equals(triggerType) ? "设备上报排程开始浇水" : "设备上报手动开始浇水");
|
||||||
|
return logBo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDuplicateRunningLog(AppWateringLogBo logBo) {
|
||||||
|
AppWateringLogBo query = new AppWateringLogBo();
|
||||||
|
query.setDeviceNo(logBo.getDeviceNo());
|
||||||
|
query.setScheduleId(logBo.getScheduleId());
|
||||||
|
query.setTriggerType(logBo.getTriggerType());
|
||||||
|
List<AppWateringLogVo> exists = appWateringLogService.queryList(query);
|
||||||
|
if (exists == null || exists.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return exists.stream().anyMatch(item -> {
|
||||||
|
boolean sameCommand = StringUtils.isNotBlank(logBo.getCommandId())
|
||||||
|
&& Objects.equals(item.getCommandId(), logBo.getCommandId());
|
||||||
|
boolean sameWateringWindow = sameMinute(item.getStartTime(), logBo.getStartTime());
|
||||||
|
boolean running = "1".equals(item.getStatus())
|
||||||
|
|| (StringUtils.isBlank(item.getStatus()) && item.getEndTime() == null);
|
||||||
|
return Objects.equals(item.getDeviceNo(), logBo.getDeviceNo())
|
||||||
|
&& scheduleIdMatches(item.getScheduleId(), logBo)
|
||||||
|
&& Objects.equals(item.getTriggerType(), logBo.getTriggerType())
|
||||||
|
&& running
|
||||||
|
&& (sameCommand || sameWateringWindow);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scheduleIdMatches(Long existsScheduleId, AppWateringLogBo logBo) {
|
||||||
|
if ("1".equals(logBo.getTriggerType())) {
|
||||||
|
return existsScheduleId == null || existsScheduleId == 0L;
|
||||||
|
}
|
||||||
|
return Objects.equals(existsScheduleId, logBo.getScheduleId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameMinute(Date existsStartTime, Date reportStartTime) {
|
||||||
|
if (existsStartTime == null || reportStartTime == null) {
|
||||||
|
return Objects.equals(existsStartTime, reportStartTime);
|
||||||
|
}
|
||||||
|
Calendar existsCalendar = Calendar.getInstance();
|
||||||
|
existsCalendar.setTime(existsStartTime);
|
||||||
|
Calendar reportCalendar = Calendar.getInstance();
|
||||||
|
reportCalendar.setTime(reportStartTime);
|
||||||
|
return existsCalendar.get(Calendar.YEAR) == reportCalendar.get(Calendar.YEAR)
|
||||||
|
&& existsCalendar.get(Calendar.DAY_OF_YEAR) == reportCalendar.get(Calendar.DAY_OF_YEAR)
|
||||||
|
&& existsCalendar.get(Calendar.HOUR_OF_DAY) == reportCalendar.get(Calendar.HOUR_OF_DAY)
|
||||||
|
&& existsCalendar.get(Calendar.MINUTE) == reportCalendar.get(Calendar.MINUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date resolveEndTime(Date startTime, Integer durationMin) {
|
||||||
|
if (startTime == null || durationMin == null || durationMin <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(startTime.getTime() + durationMin * 60000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTriggerType(Map<String, Object> dto) {
|
||||||
|
String triggerType = valueAsString(dto.get("triggerType"));
|
||||||
|
if ("0".equals(triggerType) || "1".equals(triggerType)) {
|
||||||
|
return triggerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
String trigger = valueAsString(dto.get("triggerON"));
|
||||||
|
if (StringUtils.isBlank(trigger)) {
|
||||||
|
trigger = valueAsString(dto.get("wateringType"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(trigger)) {
|
||||||
|
trigger = valueAsString(dto.get("type"));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(trigger)) {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = trigger.trim().toLowerCase(Locale.ROOT);
|
||||||
|
if ("schedule".equals(normalized) || "0".equals(normalized)) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long queryUserId(String deviceNo) {
|
||||||
|
AppDeviceVo device = appDeviceService.queryById(deviceNo);
|
||||||
|
return device == null ? null : device.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long queryScheduleId(String deviceNo) {
|
||||||
|
List<AppSchedulingDevice> schedulingDevices = schedulingDeviceMapper.selectList(
|
||||||
|
Wrappers.<AppSchedulingDevice>lambdaQuery()
|
||||||
|
.eq(AppSchedulingDevice::getDeviceNo, deviceNo)
|
||||||
|
);
|
||||||
|
if (schedulingDevices == null || schedulingDevices.isEmpty()) {
|
||||||
|
log.warn("[MQTT] 开始浇水上报未找到设备绑定的排程 时间={} 设备编号={}", HandlerLogTime.now(), deviceNo);
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
if (schedulingDevices.size() > 1) {
|
||||||
|
log.warn("[MQTT] 开始浇水上报匹配到多个排程 时间={} 设备编号={} 数量={}", HandlerLogTime.now(), deviceNo, schedulingDevices.size());
|
||||||
|
}
|
||||||
|
return schedulingDevices.get(0).getScheduleId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date parseStartTime(String startTime) {
|
||||||
|
if (StringUtils.isBlank(startTime)) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"}) {
|
||||||
|
try {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(pattern);
|
||||||
|
format.setLenient(false);
|
||||||
|
return format.parse(startTime);
|
||||||
|
} catch (ParseException ignored) {
|
||||||
|
// Try the next supported format.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd H:mm");
|
||||||
|
format.setLenient(false);
|
||||||
|
return format.parse(today + " " + startTime);
|
||||||
|
} catch (ParseException ignored) {
|
||||||
|
// Fall through and report the unsupported format.
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unsupported startTime format: " + startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String valueAsString(Object value) {
|
||||||
|
return value == null ? null : String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer valueAsInteger(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof Number number) {
|
||||||
|
return number.intValue();
|
||||||
|
}
|
||||||
|
String text = String.valueOf(value);
|
||||||
|
return StringUtils.isBlank(text) ? null : Integer.valueOf(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
d.device_no,
|
d.device_no,
|
||||||
d.user_id,
|
d.user_id,
|
||||||
d.device_name,
|
d.device_name,
|
||||||
|
d.device_init_name,
|
||||||
d.device_img,
|
d.device_img,
|
||||||
d.qrcode,
|
d.qrcode,
|
||||||
d.status,
|
d.status,
|
||||||
@@ -53,6 +54,9 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
<if test="bo.deviceName != null and bo.deviceName != ''">
|
<if test="bo.deviceName != null and bo.deviceName != ''">
|
||||||
and d.device_name like concat('%', #{bo.deviceName}, '%')
|
and d.device_name like concat('%', #{bo.deviceName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="bo.deviceInitName != null and bo.deviceInitName != ''">
|
||||||
|
and d.device_init_name = #{bo.deviceInitName}
|
||||||
|
</if>
|
||||||
<if test="bo.deviceImg != null and bo.deviceImg != ''">
|
<if test="bo.deviceImg != null and bo.deviceImg != ''">
|
||||||
and d.device_img = #{bo.deviceImg}
|
and d.device_img = #{bo.deviceImg}
|
||||||
</if>
|
</if>
|
||||||
@@ -101,6 +105,7 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
d.device_no,
|
d.device_no,
|
||||||
d.user_id,
|
d.user_id,
|
||||||
d.device_name,
|
d.device_name,
|
||||||
|
d.device_init_name,
|
||||||
d.device_img,
|
d.device_img,
|
||||||
d.qrcode,
|
d.qrcode,
|
||||||
d.status,
|
d.status,
|
||||||
@@ -127,6 +132,9 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
<if test="bo.deviceName != null and bo.deviceName != ''">
|
<if test="bo.deviceName != null and bo.deviceName != ''">
|
||||||
and d.device_name like concat('%', #{bo.deviceName}, '%')
|
and d.device_name like concat('%', #{bo.deviceName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="bo.deviceInitName != null and bo.deviceInitName != ''">
|
||||||
|
and d.device_init_name = #{bo.deviceInitName}
|
||||||
|
</if>
|
||||||
<if test="bo.deviceImg != null and bo.deviceImg != ''">
|
<if test="bo.deviceImg != null and bo.deviceImg != ''">
|
||||||
and d.device_img = #{bo.deviceImg}
|
and d.device_img = #{bo.deviceImg}
|
||||||
</if>
|
</if>
|
||||||
@@ -174,7 +182,6 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
update app_device
|
update app_device
|
||||||
<set>
|
<set>
|
||||||
user_id = #{userId},
|
user_id = #{userId},
|
||||||
bind_token_hash = null,
|
|
||||||
<if test="deviceName != null and deviceName != ''">
|
<if test="deviceName != null and deviceName != ''">
|
||||||
device_name = #{deviceName},
|
device_name = #{deviceName},
|
||||||
</if>
|
</if>
|
||||||
@@ -220,6 +227,13 @@ public interface AppDeviceMapper extends BaseMapperPlus<AppDevice, AppDeviceVo>
|
|||||||
""")
|
""")
|
||||||
AppDevice selectByMac(@Param("macAddress") String macAddress);
|
AppDevice selectByMac(@Param("macAddress") String macAddress);
|
||||||
|
|
||||||
|
@Select("""
|
||||||
|
select *
|
||||||
|
from app_device
|
||||||
|
where device_init_name = #{deviceInitName}
|
||||||
|
""")
|
||||||
|
AppDevice selectByDeviceInitName(@Param("deviceInitName") String deviceInitName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仪表盘统计 — 单条 SQL 条件聚合,一次查询返回全部 8 个指标。
|
* 仪表盘统计 — 单条 SQL 条件聚合,一次查询返回全部 8 个指标。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ public interface IDeviceCommandService {
|
|||||||
*/
|
*/
|
||||||
String sendScheduleUnbindCommand(String deviceNo, Long scheduleId);
|
String sendScheduleUnbindCommand(String deviceNo, Long scheduleId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下发排程取消命令,通知设备暂停指定排程。
|
||||||
|
*
|
||||||
|
* @param deviceNo 设备编号
|
||||||
|
* @param scheduleId 排程ID
|
||||||
|
* @return commandId
|
||||||
|
*/
|
||||||
|
String sendScheduleCanceledCommand(String deviceNo, Long scheduleId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下发设备初始化指令。用于解绑设备,允许设备未绑定用户时下发。
|
* 下发设备初始化指令。用于解绑设备,允许设备未绑定用户时下发。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -37,13 +37,26 @@ import org.springframework.context.annotation.Lazy;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
public class AppDeviceServiceImpl implements IAppDeviceService {
|
public class AppDeviceServiceImpl implements IAppDeviceService {
|
||||||
|
|
||||||
|
private static final int QR_CODE_SIZE = 300;
|
||||||
|
private static final int QR_CODE_LABEL_HEIGHT = 20;
|
||||||
|
private static final int QR_CODE_LABEL_PADDING = 16;
|
||||||
|
private static final int QR_CODE_LABEL_FONT_SIZE = 20;
|
||||||
|
private static final int QR_CODE_LABEL_OFFSET_Y = -10;
|
||||||
|
|
||||||
private final AppDeviceMapper baseMapper;
|
private final AppDeviceMapper baseMapper;
|
||||||
private final AppSchedulingDeviceMapper schedulingDeviceMapper;
|
private final AppSchedulingDeviceMapper schedulingDeviceMapper;
|
||||||
private final AppWateringLogMapper wateringLogMapper;
|
private final AppWateringLogMapper wateringLogMapper;
|
||||||
@@ -150,43 +163,53 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
return baseMapper.updateById(device) > 0;
|
return baseMapper.updateById(device) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
static byte[] renderQrCodeImage(String content, String deviceNo) {
|
||||||
public Map<String, Object> bindDeviceStatus(AppDeviceBo bo) {
|
try {
|
||||||
Map<String, Object> params = new HashMap<>();
|
byte[] qrCodeBytes = QrCodeUtil.generatePng(content, QR_CODE_SIZE, QR_CODE_SIZE);
|
||||||
params.put("bindDeviceStatus", 200);
|
BufferedImage qrCodeImage = ImageIO.read(new ByteArrayInputStream(qrCodeBytes));
|
||||||
params.put("bindDeviceStatusName", "");
|
BufferedImage image = new BufferedImage(
|
||||||
if (bo == null) {
|
QR_CODE_SIZE,
|
||||||
params.put("bindDeviceStatus", 300);
|
QR_CODE_SIZE + QR_CODE_LABEL_HEIGHT,
|
||||||
params.put("bindDeviceStatusName", "设备信息不能为空");
|
BufferedImage.TYPE_INT_RGB
|
||||||
return params;
|
);
|
||||||
|
|
||||||
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
|
||||||
|
graphics.drawImage(qrCodeImage, 0, 0, null);
|
||||||
|
drawCenteredDeviceNo(graphics, deviceNo);
|
||||||
|
} finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(image, "png", outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ServiceException("设备二维码生成失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawCenteredDeviceNo(Graphics2D graphics, String deviceNo) {
|
||||||
|
String text = StringUtils.blankToDefault(deviceNo, "");
|
||||||
|
graphics.setColor(Color.BLACK);
|
||||||
|
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
Font font = new Font(Font.SANS_SERIF, Font.BOLD, QR_CODE_LABEL_FONT_SIZE);
|
||||||
|
graphics.setFont(font);
|
||||||
|
|
||||||
|
FontMetrics metrics = graphics.getFontMetrics();
|
||||||
|
int maxTextWidth = QR_CODE_SIZE - QR_CODE_LABEL_PADDING * 2;
|
||||||
|
while (metrics.stringWidth(text) > maxTextWidth && font.getSize() > 12) {
|
||||||
|
font = font.deriveFont((float) font.getSize() - 1);
|
||||||
|
graphics.setFont(font);
|
||||||
|
metrics = graphics.getFontMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(bo.getMacAddress())) {
|
int x = (QR_CODE_SIZE - metrics.stringWidth(text)) / 2;
|
||||||
params.put("bindDeviceStatus", 301);
|
int y = QR_CODE_SIZE + (QR_CODE_LABEL_HEIGHT - metrics.getHeight()) / 2 + metrics.getAscent()
|
||||||
params.put("bindDeviceStatusName", "MAC地址不能为空");
|
+ QR_CODE_LABEL_OFFSET_Y;
|
||||||
return params;
|
graphics.drawString(text, Math.max(QR_CODE_LABEL_PADDING, x), y);
|
||||||
}
|
|
||||||
params.put("bindDevice", null);
|
|
||||||
Long userId = LoginHelper.getUserId();
|
|
||||||
AppDevice exists = baseMapper.selectByMac(bo.getMacAddress());
|
|
||||||
if (exists == null) {
|
|
||||||
params.put("bindDeviceStatus", 302);
|
|
||||||
params.put("bindDeviceStatusName", "设备未上线注册,请先完成配网");
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
params.put("bindDevice", exists);
|
|
||||||
if (exists.getUserId() != null && !exists.getUserId().equals(userId)) {
|
|
||||||
params.put("bindDeviceStatus", 303);
|
|
||||||
params.put("bindDeviceStatusName", "设备已被其他用户绑定");
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
if (exists.getUserId() == null ) {
|
|
||||||
params.put("bindDeviceStatus", 304);
|
|
||||||
params.put("bindDeviceStatusName", "设备未绑定,请先绑定用户");
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -254,6 +277,70 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> bindDeviceStatus(AppDeviceBo bo) {
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("bindDeviceStatus", 200);
|
||||||
|
params.put("bindDeviceStatusName", "");
|
||||||
|
if (bo == null) {
|
||||||
|
params.put("bindDeviceStatus", 300);
|
||||||
|
params.put("bindDeviceStatusName", "设备信息不能为空");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isAllBlank(bo.getDeviceNo(), bo.getMacAddress(), bo.getDeviceInitName())) {
|
||||||
|
params.put("bindDeviceStatus", 301);
|
||||||
|
params.put("bindDeviceStatusName", "设备编号、MAC地址或设备初始名称不能为空");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
params.put("bindDevice", null);
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
AppDevice exists = findDeviceForBindStatus(bo);
|
||||||
|
if (exists == null) {
|
||||||
|
params.put("bindDeviceStatus", 302);
|
||||||
|
params.put("bindDeviceStatusName", "设备未入库注册,请先进行入库");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
params.put("bindDevice", exists);
|
||||||
|
if (exists.getUserId() != null && !exists.getUserId().equals(userId)) {
|
||||||
|
params.put("bindDeviceStatus", 303);
|
||||||
|
params.put("bindDeviceStatusName", "设备已被其他用户绑定");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
if (exists.getUserId() == null ) {
|
||||||
|
params.put("bindDeviceStatus", 304);
|
||||||
|
params.put("bindDeviceStatusName", "设备未绑定,请先绑定用户");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
if (exists.getUserId() == null && exists.getWorkStatus().equals("2") && exists.getStatus().equals("0") ) {
|
||||||
|
params.put("bindDeviceStatus", 305);
|
||||||
|
params.put("bindDeviceStatusName", "设备未配网,请先去配置网络");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppDevice findDeviceForBindStatus(AppDeviceBo bo) {
|
||||||
|
AppDevice exists = null;
|
||||||
|
if (StringUtils.isNotBlank(bo.getDeviceNo())) {
|
||||||
|
exists = baseMapper.selectById(bo.getDeviceNo());
|
||||||
|
if (exists != null) {
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(bo.getMacAddress())) {
|
||||||
|
exists = baseMapper.selectByMac(bo.getMacAddress());
|
||||||
|
if (exists != null) {
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(bo.getDeviceInitName())) {
|
||||||
|
return baseMapper.selectByDeviceInitName(bo.getDeviceInitName());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean generateQrCode(Collection<String> deviceNos) {
|
public Boolean generateQrCode(Collection<String> deviceNos) {
|
||||||
@@ -266,7 +353,7 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (AppDevice device : devices) {
|
for (AppDevice device : devices) {
|
||||||
byte[] imageBytes = QrCodeUtil.generatePng(buildQrCodeContent(device), 300, 300);
|
byte[] imageBytes = renderQrCodeImage(buildQrCodeContent(device), device.getDeviceNo());
|
||||||
UploadResult uploadResult = OssFactory.instance().uploadSuffix(imageBytes, ".png", "image/png");
|
UploadResult uploadResult = OssFactory.instance().uploadSuffix(imageBytes, ".png", "image/png");
|
||||||
int rows = baseMapper.update(null, new LambdaUpdateWrapper<AppDevice>()
|
int rows = baseMapper.update(null, new LambdaUpdateWrapper<AppDevice>()
|
||||||
.set(AppDevice::getQrcode, uploadResult.getUrl())
|
.set(AppDevice::getQrcode, uploadResult.getUrl())
|
||||||
@@ -286,7 +373,7 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
content.put("deviceNo", device.getDeviceNo());
|
content.put("deviceNo", device.getDeviceNo());
|
||||||
content.put("mac", device.getMacAddress());
|
content.put("mac", device.getMacAddress());
|
||||||
content.put("transport", "ble");
|
content.put("transport", "ble");
|
||||||
content.put("name", device.getDeviceName());
|
content.put("name", device.getDeviceInitName());
|
||||||
return JsonUtils.toJsonString(content);
|
return JsonUtils.toJsonString(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,24 +421,27 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
throw new ServiceException("设备编号不能为空");
|
throw new ServiceException("设备编号不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long userId = LoginHelper.getUserId();
|
boolean platformAdmin = LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin();
|
||||||
if (userId == null) {
|
Long userId = platformAdmin ? null : LoginHelper.getUserId();
|
||||||
throw new ServiceException("用户未登录");
|
if (!platformAdmin) {
|
||||||
}
|
if (userId == null) {
|
||||||
|
throw new ServiceException("用户未登录");
|
||||||
|
}
|
||||||
|
|
||||||
Long ownedCount = baseMapper.selectCount(
|
Long ownedCount = baseMapper.selectCount(
|
||||||
Wrappers.<AppDevice>lambdaQuery()
|
Wrappers.<AppDevice>lambdaQuery()
|
||||||
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
||||||
.eq(AppDevice::getUserId, userId)
|
.eq(AppDevice::getUserId, userId)
|
||||||
);
|
);
|
||||||
if (ownedCount == null || ownedCount.intValue() != distinctDeviceNos.size()) {
|
if (ownedCount == null || ownedCount.intValue() != distinctDeviceNos.size()) {
|
||||||
throw new ServiceException("设备不存在或无权操作");
|
throw new ServiceException("设备不存在或无权操作");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean flag = baseMapper.delete(
|
boolean flag = baseMapper.delete(
|
||||||
Wrappers.<AppDevice>lambdaUpdate()
|
Wrappers.<AppDevice>lambdaUpdate()
|
||||||
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
||||||
.eq(AppDevice::getUserId, userId)
|
.eq(!platformAdmin, AppDevice::getUserId, userId)
|
||||||
) > 0;
|
) > 0;
|
||||||
if (flag) {
|
if (flag) {
|
||||||
schedulingDeviceMapper.delete(
|
schedulingDeviceMapper.delete(
|
||||||
@@ -399,7 +489,6 @@ public class AppDeviceServiceImpl implements IAppDeviceService {
|
|||||||
.set(AppDevice::getWorkStatus, "0")
|
.set(AppDevice::getWorkStatus, "0")
|
||||||
.set(AppDevice::getWifiName, null)
|
.set(AppDevice::getWifiName, null)
|
||||||
.set(AppDevice::getWifiPassword, null)
|
.set(AppDevice::getWifiPassword, null)
|
||||||
.set(AppDevice::getBindTokenHash, null)
|
|
||||||
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
.in(AppDevice::getDeviceNo, distinctDeviceNos)
|
||||||
.eq(AppDevice::getUserId, userId)
|
.eq(AppDevice::getUserId, userId)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
|||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 浇水记录Service业务层处理
|
* 浇水记录Service业务层处理
|
||||||
@@ -123,6 +120,7 @@ public class AppWateringLogServiceImpl implements IAppWateringLogService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bo.setTriggerType("0");
|
bo.setTriggerType("0");
|
||||||
|
bo.setStatus("0");
|
||||||
if (StringUtils.isBlank(bo.getRemark())) {
|
if (StringUtils.isBlank(bo.getRemark())) {
|
||||||
bo.setRemark("设备离线,按排程自动补记");
|
bo.setRemark("设备离线,按排程自动补记");
|
||||||
}
|
}
|
||||||
@@ -145,6 +143,18 @@ public class AppWateringLogServiceImpl implements IAppWateringLogService {
|
|||||||
.last("limit 1")
|
.last("limit 1")
|
||||||
);
|
);
|
||||||
if (estimatedLog == null) {
|
if (estimatedLog == null) {
|
||||||
|
AppWateringLog runningLog = findMatchingRunningScheduleLog(bo);
|
||||||
|
if (runningLog != null) {
|
||||||
|
AppWateringLog update = MapstructUtils.convert(bo, AppWateringLog.class);
|
||||||
|
update.setId(runningLog.getId());
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
AppWateringLog finishedLog = findMatchingFinishedLog(bo);
|
||||||
|
if (finishedLog != null) {
|
||||||
|
AppWateringLog update = MapstructUtils.convert(bo, AppWateringLog.class);
|
||||||
|
update.setId(finishedLog.getId());
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
if (existsScheduleLog(bo.getDeviceNo(), bo.getScheduleId(), bo.getStartTime())) {
|
if (existsScheduleLog(bo.getDeviceNo(), bo.getScheduleId(), bo.getStartTime())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -157,6 +167,54 @@ public class AppWateringLogServiceImpl implements IAppWateringLogService {
|
|||||||
return baseMapper.updateById(update) > 0;
|
return baseMapper.updateById(update) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AppWateringLog findMatchingRunningScheduleLog(AppWateringLogBo bo) {
|
||||||
|
if (bo.getStartTime() == null || StringUtils.isBlank(bo.getDurationMin()) || bo.getEndTime() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return baseMapper.selectOne(
|
||||||
|
Wrappers.<AppWateringLog>lambdaQuery()
|
||||||
|
.eq(AppWateringLog::getDeviceNo, bo.getDeviceNo())
|
||||||
|
.between(AppWateringLog::getStartTime, minuteStart(bo.getStartTime()), minuteEnd(bo.getStartTime()))
|
||||||
|
.eq(StringUtils.isNotBlank(bo.getTriggerType()), AppWateringLog::getTriggerType, bo.getTriggerType())
|
||||||
|
// .between(AppWateringLog::getEndTime, minuteStart(bo.getEndTime()), minuteEnd(bo.getEndTime()))
|
||||||
|
.eq(AppWateringLog::getStatus, "1")
|
||||||
|
.last("limit 1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppWateringLog findMatchingFinishedLog(AppWateringLogBo bo) {
|
||||||
|
if (bo.getStartTime() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return baseMapper.selectOne(
|
||||||
|
Wrappers.<AppWateringLog>lambdaQuery()
|
||||||
|
.eq(AppWateringLog::getDeviceNo, bo.getDeviceNo())
|
||||||
|
.between(AppWateringLog::getStartTime, minuteStart(bo.getStartTime()), minuteEnd(bo.getStartTime()))
|
||||||
|
.eq(StringUtils.isNotBlank(bo.getTriggerType()), AppWateringLog::getTriggerType, bo.getTriggerType())
|
||||||
|
.eq(AppWateringLog::getStatus, "0")
|
||||||
|
.orderByDesc(AppWateringLog::getEndTime)
|
||||||
|
.orderByDesc(AppWateringLog::getCreateTime)
|
||||||
|
.orderByDesc(AppWateringLog::getId)
|
||||||
|
.last("limit 1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date minuteStart(Date time) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(time);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
return calendar.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date minuteEnd(Date time) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(time);
|
||||||
|
calendar.set(Calendar.SECOND, 59);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 999);
|
||||||
|
return calendar.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改浇水记录
|
* 修改浇水记录
|
||||||
*
|
*
|
||||||
@@ -225,30 +283,30 @@ public class AppWateringLogServiceImpl implements IAppWateringLogService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AppWateringLog> activeLogs = baseMapper.selectList(
|
AppWateringLog activeLog = baseMapper.selectOne(
|
||||||
Wrappers.<AppWateringLog>lambdaQuery()
|
Wrappers.<AppWateringLog>lambdaQuery()
|
||||||
.eq(AppWateringLog::getDeviceNo, deviceNo)
|
.eq(AppWateringLog::getDeviceNo, deviceNo)
|
||||||
.isNull(AppWateringLog::getEndTime)
|
.eq(AppWateringLog::getStatus, "1")
|
||||||
|
.orderByDesc(AppWateringLog::getStartTime)
|
||||||
|
.orderByDesc(AppWateringLog::getCreateTime)
|
||||||
|
.orderByDesc(AppWateringLog::getId)
|
||||||
|
.last("limit 1")
|
||||||
);
|
);
|
||||||
if (activeLogs == null || activeLogs.isEmpty()) {
|
if (activeLog == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = 0;
|
LambdaUpdateWrapper<AppWateringLog> updateWrapper = Wrappers.lambdaUpdate();
|
||||||
for (AppWateringLog activeLog : activeLogs) {
|
updateWrapper.eq(AppWateringLog::getId, activeLog.getId())
|
||||||
LambdaUpdateWrapper<AppWateringLog> updateWrapper = Wrappers.lambdaUpdate();
|
.set(AppWateringLog::getEndTime, endTime)
|
||||||
updateWrapper.eq(AppWateringLog::getId, activeLog.getId())
|
.set(AppWateringLog::getStatus, "0")
|
||||||
.isNull(AppWateringLog::getEndTime)
|
.set(StringUtils.isNotBlank(remark), AppWateringLog::getRemark, remark);
|
||||||
.set(AppWateringLog::getEndTime, endTime)
|
if (activeLog.getStartTime() != null) {
|
||||||
.set(StringUtils.isNotBlank(remark), AppWateringLog::getRemark, remark);
|
long durationMillis = endTime.getTime() - activeLog.getStartTime().getTime();
|
||||||
if (activeLog.getStartTime() != null) {
|
long durationMin = Math.max(durationMillis, 0L) / 60000L;
|
||||||
long durationMillis = endTime.getTime() - activeLog.getStartTime().getTime();
|
updateWrapper.set(AppWateringLog::getDurationMin, String.valueOf(durationMin));
|
||||||
long durationMin = Math.max(durationMillis, 0L) / 60000L;
|
|
||||||
updateWrapper.set(AppWateringLog::getDurationMin, String.valueOf(durationMin));
|
|
||||||
}
|
|
||||||
updated += baseMapper.update(null, updateWrapper);
|
|
||||||
}
|
}
|
||||||
return updated;
|
return baseMapper.update(null, updateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -69,9 +69,10 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
command.setDeviceNo(deviceNo);
|
command.setDeviceNo(deviceNo);
|
||||||
command.setDeviceMac(requireDeviceMac(deviceNo));
|
command.setDeviceMac(requireDeviceMac(deviceNo));
|
||||||
command.setCommandType("switchDevice");
|
command.setCommandType("switchDevice");
|
||||||
|
String commandStartTime = normalizeCommandStartTime(startTime);
|
||||||
command.getPayload().put("deviceNo", deviceNo);
|
command.getPayload().put("deviceNo", deviceNo);
|
||||||
command.getPayload().put("cmd", workStatus);
|
command.getPayload().put("cmd", workStatus);
|
||||||
command.getPayload().put("startTime", startTime);
|
command.getPayload().put("startTime", commandStartTime);
|
||||||
if (durationMin != null) {
|
if (durationMin != null) {
|
||||||
command.getPayload().put("durationMin", durationMin);
|
command.getPayload().put("durationMin", durationMin);
|
||||||
}
|
}
|
||||||
@@ -82,12 +83,12 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
|
|
||||||
Long userId = LoginHelper.getUserId();
|
Long userId = LoginHelper.getUserId();
|
||||||
if ("1".equals(workStatus)) {
|
if ("1".equals(workStatus)) {
|
||||||
AppWateringLogBo logBo = buildManualStartLog(deviceNo, userId, commandId, startTime, durationMin);
|
AppWateringLogBo logBo = buildManualStartLog(deviceNo, userId, commandId, commandStartTime, durationMin);
|
||||||
wateringLogService.insertByBo(logBo);
|
wateringLogService.insertByBo(logBo);
|
||||||
} else {
|
} else {
|
||||||
boolean updated = wateringLogService.finishManualLog(deviceNo, userId, new Date(), "手动停止浇水");
|
int updated = wateringLogService.finishRunningLogsByDevice(deviceNo, new Date(), "手动停止浇水");
|
||||||
if (!updated) {
|
if (updated <= 0) {
|
||||||
log.warn("[命令] 未找到进行中的手动浇水记录 设备编号={} 用户ID={} 停止命令编号={}",
|
log.warn("[命令] 未找到进行中的浇水记录 设备编号={} 用户ID={} 停止命令编号={}",
|
||||||
deviceNo, userId, commandId);
|
deviceNo, userId, commandId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,6 +169,24 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
return sendCommand(command);
|
return sendCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sendScheduleCanceledCommand(String deviceNo, Long scheduleId) {
|
||||||
|
AppDeviceVo device = requireCommandDevice(deviceNo);
|
||||||
|
if (StringUtils.isBlank(device.getMacAddress())) {
|
||||||
|
throw new ServiceException("设备未登记 MAC,无法下发排程取消命令:" + deviceNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceCommand command = new DeviceCommand();
|
||||||
|
command.setDeviceNo(deviceNo);
|
||||||
|
command.setDeviceMac(device.getMacAddress());
|
||||||
|
command.setCommandType("cancelSchedule");
|
||||||
|
command.setTopic(buildScheduleCanceledTopic(deviceNo));
|
||||||
|
command.getPayload().put("deviceNo", deviceNo);
|
||||||
|
command.getPayload().put("scheduleId", scheduleId);
|
||||||
|
command.getPayload().put("canceled", true);
|
||||||
|
return sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String sendInitDeviceCommand(String deviceNo) {
|
public String sendInitDeviceCommand(String deviceNo) {
|
||||||
AppDeviceVo device = deviceService.queryById(deviceNo);
|
AppDeviceVo device = deviceService.queryById(deviceNo);
|
||||||
@@ -237,6 +256,10 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
return "/" + deviceNo.trim().toLowerCase(Locale.ROOT) + "/subscriber/schedule";
|
return "/" + deviceNo.trim().toLowerCase(Locale.ROOT) + "/subscriber/schedule";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildScheduleCanceledTopic(String deviceNo) {
|
||||||
|
return "/" + deviceNo.trim().toLowerCase(Locale.ROOT) + "/schedule/canceled";
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSwitchStatus(String workStatus) {
|
private void assertSwitchStatus(String workStatus) {
|
||||||
if (!"0".equals(workStatus) && !"1".equals(workStatus)) {
|
if (!"0".equals(workStatus) && !"1".equals(workStatus)) {
|
||||||
throw new ServiceException("设备工作状态只能为0或1");
|
throw new ServiceException("设备工作状态只能为0或1");
|
||||||
@@ -256,6 +279,7 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
String startTime,
|
String startTime,
|
||||||
Integer durationMin
|
Integer durationMin
|
||||||
) {
|
) {
|
||||||
|
Date starttime = parseStartTime(valueAsString(startTime));
|
||||||
AppWateringLogBo logBo = new AppWateringLogBo();
|
AppWateringLogBo logBo = new AppWateringLogBo();
|
||||||
logBo.setDeviceNo(deviceNo);
|
logBo.setDeviceNo(deviceNo);
|
||||||
logBo.setCommandId(commandId);
|
logBo.setCommandId(commandId);
|
||||||
@@ -265,6 +289,8 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
logBo.setTriggerType("1"); // 手动
|
logBo.setTriggerType("1"); // 手动
|
||||||
logBo.setRemark("手动开始浇水");
|
logBo.setRemark("手动开始浇水");
|
||||||
logBo.setCreateTime(new Date());
|
logBo.setCreateTime(new Date());
|
||||||
|
logBo.setStatus("1");
|
||||||
|
logBo.setEndTime(resolveEndTime(starttime, durationMin));
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(startTime)) {
|
if (StringUtils.isNotEmpty(startTime)) {
|
||||||
logBo.setStartTime(parseStartTime(startTime));
|
logBo.setStartTime(parseStartTime(startTime));
|
||||||
@@ -274,7 +300,43 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
return logBo;
|
return logBo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String valueAsString(Object value) {
|
||||||
|
return value == null ? null : String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeCommandStartTime(String startTime) {
|
||||||
|
if (StringUtils.isBlank(startTime)) {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
if (startTime.matches("^\\d{2}:\\d{2}$")) {
|
||||||
|
return startTime + ":00";
|
||||||
|
}
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date resolveEndTime(Date startTime, Integer durationMin) {
|
||||||
|
if (startTime == null || durationMin == null || durationMin <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(startTime.getTime() + durationMin * 60000L);
|
||||||
|
}
|
||||||
private Date parseStartTime(String startTime) {
|
private Date parseStartTime(String startTime) {
|
||||||
|
if (startTime.matches("^\\d{2}:\\d{2}:\\d{2}$")) {
|
||||||
|
try {
|
||||||
|
Date parsedTime = new SimpleDateFormat("HH:mm:ss").parse(startTime);
|
||||||
|
Calendar parsed = Calendar.getInstance();
|
||||||
|
parsed.setTime(parsedTime);
|
||||||
|
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
today.set(Calendar.HOUR_OF_DAY, parsed.get(Calendar.HOUR_OF_DAY));
|
||||||
|
today.set(Calendar.MINUTE, parsed.get(Calendar.MINUTE));
|
||||||
|
today.set(Calendar.SECOND, parsed.get(Calendar.SECOND));
|
||||||
|
today.set(Calendar.MILLISECOND, 0);
|
||||||
|
return today.getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new ServiceException("开始时间格式错误,期望 HH:mm、HH:mm:ss 或 yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (startTime.matches("^\\d{2}:\\d{2}$")) {
|
if (startTime.matches("^\\d{2}:\\d{2}$")) {
|
||||||
try {
|
try {
|
||||||
Date parsedTime = new SimpleDateFormat("HH:mm").parse(startTime);
|
Date parsedTime = new SimpleDateFormat("HH:mm").parse(startTime);
|
||||||
@@ -288,13 +350,13 @@ public class DeviceCommandServiceImpl implements IDeviceCommandService {
|
|||||||
today.set(Calendar.MILLISECOND, 0);
|
today.set(Calendar.MILLISECOND, 0);
|
||||||
return today.getTime();
|
return today.getTime();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new ServiceException("开始时间格式错误,期望 HH:mm 或 yyyy-MM-dd HH:mm:ss");
|
throw new ServiceException("开始时间格式错误,期望 HH:mm、HH:mm:ss 或 yyyy-MM-dd HH:mm:ss");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startTime);
|
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startTime);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new ServiceException("开始时间格式错误,期望 HH:mm 或 yyyy-MM-dd HH:mm:ss");
|
throw new ServiceException("开始时间格式错误,期望 HH:mm、HH:mm:ss 或 yyyy-MM-dd HH:mm:ss");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class SysUser extends TenantEntity {
|
|||||||
/**
|
/**
|
||||||
* 用户头像
|
* 用户头像
|
||||||
*/
|
*/
|
||||||
private Long avatar;
|
private String avatar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ public class SysUserBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String sex;
|
private String sex;
|
||||||
|
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package org.dromara.system.domain.vo;
|
package org.dromara.system.domain.vo;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import lombok.Data;
|
||||||
import org.dromara.common.sensitive.annotation.Sensitive;
|
import org.dromara.common.sensitive.annotation.Sensitive;
|
||||||
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
||||||
import org.dromara.common.translation.annotation.Translation;
|
import org.dromara.common.translation.annotation.Translation;
|
||||||
import org.dromara.common.translation.constant.TransConstant;
|
import org.dromara.common.translation.constant.TransConstant;
|
||||||
import org.dromara.system.domain.SysUser;
|
import org.dromara.system.domain.SysUser;
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -78,8 +76,8 @@ public class SysUserVo implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 头像地址
|
* 头像地址
|
||||||
*/
|
*/
|
||||||
@Translation(type = TransConstant.OSS_ID_TO_URL)
|
//@Translation(type = TransConstant.OSS_ID_TO_URL)
|
||||||
private Long avatar;
|
private String avatar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
|
|||||||
Reference in New Issue
Block a user