fix: 微信小程序接口对接修改,联调测试问题修复。

This commit is contained in:
tianyongbao
2026-01-15 11:35:13 +08:00
parent 15af17fb95
commit fe0f3e0432
23 changed files with 1797 additions and 179 deletions

View File

@@ -21,6 +21,7 @@ import com.intc.common.sms.config.properties.SmsProperties;
import com.intc.common.web.config.properties.CaptchaProperties;
import com.intc.common.web.enums.CaptchaType;
import com.intc.web.domain.vo.CaptchaVo;
import com.intc.fishery.domain.bo.ReqVerifySmsCode;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,8 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
@@ -105,6 +108,31 @@ public class CaptchaController {
return R.ok();
}
/**
* 验证短信验证码
*
* @param request 手机号和验证码数据
* @return 验证结果
*/
@PostMapping("/resource/sms/verify")
public R<Void> verifySmsCode(@Validated @RequestBody ReqVerifySmsCode request) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + request.getMobilePhone();
String cachedCode = RedisUtils.getCacheObject(key);
if (StringUtils.isBlank(cachedCode)) {
return R.fail("短信验证码已过期");
}
if (!cachedCode.equals(request.getSmsCode())) {
return R.fail("短信验证码错误");
}
// 验证成功后删除验证码(根据 C# 代码中的 true 参数)
RedisUtils.deleteObject(key);
return R.ok();
}
/**
* 邮箱验证码
*

View File

@@ -57,4 +57,14 @@ public class LoginVo {
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
}

View File

@@ -73,6 +73,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setUserId(loginUser.getUserId());
loginVo.setNickName(loginUser.getNickname());
loginVo.setUserName(loginUser.getUsername());
return loginVo;
}

View File

@@ -203,7 +203,12 @@ sms:
# 模板内容需包含 ${code} 参数
code-template-id: 'SMS_465720430' # TODO: 请填写您的阿里云短信模板CODE
# 设备类型到 ProductKey 的映射配置
device-type:
# 1-水质检测仪 ProductKey请填写实际的 ProductKey
water-quality-monitor: a15hA3oBPmB # TODO: 请替换为实际的水质检测仪 ProductKey
# 2-控制一体机 ProductKey请填写实际的 ProductKey
control-integrated: a1Xj9dagTIx # TODO: 请替换为实际的控制一体机 ProductKey
--- # 阿里云生活物联网平台(飞燕平台)配置
aliyun:
living-iot:
@@ -219,12 +224,6 @@ aliyun:
app-key: 334224397
# App Secret
app-secret: 70de3018ec39423e9ca1e1b6a6a84ad6
# 设备类型到 ProductKey 的映射配置
device-type:
# 1-水质检测仪 ProductKey请填写实际的 ProductKey
water-quality-monitor: a15hA3oBPmB # TODO: 请替换为实际的水质检测仪 ProductKey
# 2-控制一体机 ProductKey请填写实际的 ProductKey
control-integrated: a1Xj9dagTIx # TODO: 请替换为实际的控制一体机 ProductKey
# AMQP 服务端订阅配置(使用数据同步的 AppKey + AppSecret
amqp:
# 是否启用

View File

@@ -0,0 +1,62 @@
package com.intc.common.core.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 设备类型配置
*
* @author intc
*/
@Data
@Component
@ConfigurationProperties(prefix = "device-type")
public class DeviceTypeProperties {
/**
* 水质检测仪 ProductKey
*/
private String waterQualityMonitor;
/**
* 控制一体机 ProductKey
*/
private String controlIntegrated;
/**
* 根据 ProductKey 判断设备类型
* @param productKey ProductKey
* @return 设备类型: 1-水质检测仪, 2-控制一体机, null-未知类型
*/
public Integer getDeviceTypeByProductKey(String productKey) {
if (productKey == null) {
return null;
}
if (productKey.equals(waterQualityMonitor)) {
return 1;
} else if (productKey.equals(controlIntegrated)) {
return 2;
}
return null;
}
/**
* 根据设备类型获取 ProductKey
* @param deviceType 1-水质检测仪, 2-控制一体机
* @return ProductKey
*/
public String getProductKeyByType(Integer deviceType) {
if (deviceType == null) {
return null;
}
switch (deviceType) {
case 1:
return waterQualityMonitor;
case 2:
return controlIntegrated;
default:
return null;
}
}
}

View File

@@ -18,9 +18,27 @@ import com.intc.common.core.validate.EditGroup;
import com.intc.common.log.enums.BusinessType;
import com.intc.common.excel.utils.ExcelUtil;
import com.intc.fishery.domain.vo.AquUserVo;
import com.intc.fishery.domain.vo.UserChildVo;
import com.intc.fishery.domain.bo.AquUserBo;
import com.intc.fishery.domain.bo.ReqUpdateWarnPhone;
import com.intc.fishery.domain.bo.ReqChangeName;
import com.intc.fishery.domain.bo.ReqUpdatePhone;
import com.intc.fishery.domain.bo.ReqAddUserChild;
import com.intc.fishery.domain.bo.ReqId;
import com.intc.fishery.domain.AquUser;
import com.intc.fishery.domain.UserRelation;
import com.intc.fishery.service.IAquUserService;
import com.intc.fishery.mapper.AquUserMapper;
import com.intc.fishery.mapper.UserRelationMapper;
import com.intc.system.service.ISysUserService;
import com.intc.system.domain.bo.SysUserBo;
import com.intc.common.mybatis.core.page.TableDataInfo;
import com.intc.common.satoken.utils.LoginHelper;
import com.intc.common.core.utils.StringUtils;
import com.intc.common.json.utils.JsonUtils;
import com.intc.common.core.constant.GlobalConstants;
import com.intc.common.redis.utils.RedisUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
/**
* 养殖用户管理
@@ -35,6 +53,9 @@ import com.intc.common.mybatis.core.page.TableDataInfo;
public class AquUserController extends BaseController {
private final IAquUserService aquUserService;
private final AquUserMapper aquUserMapper;
private final UserRelationMapper userRelationMapper;
private final ISysUserService sysUserService;
/**
* 查询养殖用户管理列表
@@ -102,4 +123,341 @@ public class AquUserController extends BaseController {
@PathVariable Long[] ids) {
return toAjax(aquUserService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取报警电话列表
*
* @return 报警电话列表
*/
@GetMapping("/list_warn_phone")
public R<List<String>> getWarnPhone() {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询用户的报警电话 JSON字段
AquUserVo user = aquUserService.queryById(userId);
if (user == null) {
return R.fail("用户不存在");
}
String warnPhoneJson = user.getWarnPhoneJson();
if (StringUtils.isEmpty(warnPhoneJson)) {
return R.fail("不存在报警电话列表");
}
// 反序列化JSON为List<String>
try {
List<String> warnPhones = JsonUtils.parseArray(warnPhoneJson, String.class);
return R.ok(warnPhones);
} catch (Exception e) {
return R.fail("报警电话数据格式错误");
}
}
/**
* 获取子用户列表
*
* @return 子用户列表
*/
@GetMapping("/list_user_child")
public R<List<UserChildVo>> getListUserChild() {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询该用户的所有子账号
List<UserChildVo> list = userRelationMapper.selectChildUsersByParentId(userId);
return R.ok(list);
}
/**
* 获取父用户列表
*
* @return 父用户列表
*/
@GetMapping("/list_user_parent")
public R<List<UserChildVo>> getListUserParent() {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询该用户的所有父账号
List<UserChildVo> list = userRelationMapper.selectParentUsersByChildId(userId);
return R.ok(list);
}
/**
* 添加子用户
*
* @param request 子用户手机号
* @return 操作结果
*/
@PostMapping("/add_user_child")
public R<Void> addUserChild(@Validated @RequestBody ReqAddUserChild request) {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 根据手机号查询子用户
AquUser childUser = aquUserMapper.selectOne(
new LambdaQueryWrapper<AquUser>()
.eq(AquUser::getMobilePhone, request.getMobilePhone())
.select(AquUser::getId)
);
if (childUser == null) {
return R.fail("该手机号用户不存在");
}
Long childUserId = childUser.getId();
// 检查是否已存在该子账号关系
long count = userRelationMapper.selectCount(
new LambdaQueryWrapper<UserRelation>()
.eq(UserRelation::getParentUserId, userId)
.eq(UserRelation::getChildUserId, childUserId)
);
if (count > 0) {
return R.fail("子账号已添加,无需重复添加");
}
// 创建子账号关系
UserRelation relation = new UserRelation();
relation.setParentUserId(userId);
relation.setChildUserId(childUserId);
boolean success = userRelationMapper.insert(relation) > 0;
return toAjax(success);
}
/**
* 删除子用户
*
* @param request 关系ID
* @return 操作结果
*/
@DeleteMapping("/delete_user_child")
public R<Void> deleteUserChild(@Validated @RequestBody ReqId request) {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询子账号关系,验证权限
UserRelation userRelation = userRelationMapper.selectOne(
new LambdaQueryWrapper<UserRelation>()
.eq(UserRelation::getId, request.getId())
.select(UserRelation::getId, UserRelation::getParentUserId, UserRelation::getChildUserId)
);
if (userRelation == null) {
return R.fail("子账号关系不存在");
}
// 验证是否是当前用户的子账号
if (!userRelation.getParentUserId().equals(userId)) {
return R.fail("无权删除该子账号");
}
// 删除子账号关系
int count = userRelationMapper.deleteById(request.getId());
if (count == 0) {
return R.fail("删除失败");
}
return R.ok();
}
/**
* 更新报警电话列表
*
* @param request 报警电话列表
* @return 操作结果
*/
@PutMapping("/update_warn_phone")
public R<Void> updateWarnPhone(@Validated @RequestBody ReqUpdateWarnPhone request) {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询用户信息,获取当前用户的手机号
AquUserVo user = aquUserService.queryById(userId);
if (user == null) {
return R.fail("用户不存在");
}
String oldMobilePhone = user.getMobilePhone();
if (StringUtils.isEmpty(oldMobilePhone)) {
return R.fail("用户手机号不存在,无权操作");
}
// 验证报警电话列表是否包含用户自己的手机号
List<String> listPhone = request.getListPhone();
boolean containUser = false;
for (String phone : listPhone) {
if (phone.equals(oldMobilePhone)) {
containUser = true;
break;
}
}
if (!containUser) {
return R.fail("报警电话必须包含自己手机号");
}
// 序列化为JSON字符串
String warnPhoneJson = JsonUtils.toJsonString(listPhone);
// 更新数据库
AquUserBo updateBo = new AquUserBo();
updateBo.setId(userId);
updateBo.setWarnPhoneJson(warnPhoneJson);
boolean success = aquUserService.updateByBo(updateBo);
return toAjax(success);
}
/**
* 更新用户名
*
* @param request 更新用户名请求
* @return 操作结果
*/
@PutMapping("/update_user_name")
public R<Void> updateUserName(@Validated @RequestBody ReqChangeName request) {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 更新 AquUserBo 的用户名(长度验证已在 ReqChangeName 中处理)
AquUserBo updateBo = new AquUserBo();
updateBo.setId(userId);
updateBo.setUserName(request.getNewName());
boolean success = aquUserService.updateByBo(updateBo);
// 同时更新 SysUserBo 的昵称
if (success) {
SysUserBo sysUserBo = new SysUserBo();
sysUserBo.setUserId(userId);
sysUserBo.setNickName(request.getNewName());
sysUserService.updateUser(sysUserBo);
}
return toAjax(success);
}
/**
* 更新手机号
*
* @param request 更新手机号请求
* @return 操作结果
*/
@PutMapping("/update_mobile_phone")
public R<Void> updateMobilePhone(@Validated @RequestBody ReqUpdatePhone request) {
// 获取当前登录用户ID
Long userId = LoginHelper.getUserId();
if (userId == null || userId < 0) {
return R.fail("用户未登录");
}
// 查询用户信息
AquUserVo user = aquUserService.queryById(userId);
if (user == null) {
return R.fail("用户不存在");
}
String oldMobilePhone = user.getMobilePhone();
if (StringUtils.isEmpty(oldMobilePhone)) {
return R.fail("用户手机号不存在,无权操作");
}
// 验证旧手机号是否匹配
if (!request.getOldMobilePhone().equals(oldMobilePhone)) {
return R.fail("旧手机号不匹配");
}
// 如果新旧手机号相同,直接返回成功
if (request.getOldMobilePhone().equals(request.getNewMobilePhone())) {
return R.ok();
}
// 检查新手机号是否已被注册
long count = aquUserMapper.selectCount(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AquUser>()
.eq(AquUser::getMobilePhone, request.getNewMobilePhone())
);
if (count > 0) {
return R.fail("新手机号已被注册");
}
// 验证短信验证码
String codeKey = GlobalConstants.CAPTCHA_CODE_KEY + request.getNewMobilePhone();
String cachedCode = RedisUtils.getCacheObject(codeKey);
if (StringUtils.isBlank(cachedCode)) {
return R.fail("短信验证码已过期");
}
if (!cachedCode.equals(request.getSmsCode())) {
return R.fail("短信验证码错误");
}
// 处理报警电话列表:替换旧手机号为新手机号,并移除重复的新手机号
String warnPhoneJson = user.getWarnPhoneJson();
List<String> listWarnPhone = new java.util.ArrayList<>();
if (StringUtils.isNotEmpty(warnPhoneJson)) {
try {
listWarnPhone = JsonUtils.parseArray(warnPhoneJson, String.class);
if (listWarnPhone == null) {
listWarnPhone = new java.util.ArrayList<>();
}
} catch (Exception e) {
listWarnPhone = new java.util.ArrayList<>();
}
}
// 移除列表中所有的新手机号(避免重复)
listWarnPhone.removeIf(phone -> phone.equals(request.getNewMobilePhone()));
// 将旧手机号替换为新手机号
for (int i = 0; i < listWarnPhone.size(); i++) {
if (listWarnPhone.get(i).equals(oldMobilePhone)) {
listWarnPhone.set(i, request.getNewMobilePhone());
}
}
// 序列化报警电话列表
String newWarnPhoneJson = JsonUtils.toJsonString(listWarnPhone);
// 更新数据库:手机号和报警电话列表
AquUserBo updateBo = new AquUserBo();
updateBo.setId(userId);
updateBo.setMobilePhone(request.getNewMobilePhone());
updateBo.setWarnPhoneJson(newWarnPhoneJson);
boolean success = aquUserService.updateByBo(updateBo);
if (success) {
// 删除已使用的验证码
RedisUtils.deleteObject(codeKey);
// 也删除旧手机号的验证码(如果存在)
RedisUtils.deleteObject(GlobalConstants.CAPTCHA_CODE_KEY + request.getOldMobilePhone());
}
return toAjax(success);
}
}

View File

@@ -27,6 +27,7 @@ import com.intc.fishery.domain.vo.PublicDeviceSwitchSimpleVo;
import com.intc.fishery.domain.vo.PublicDeviceBaseData;
import com.intc.fishery.domain.vo.PublicDeviceLinkedCtrl;
import com.intc.fishery.domain.vo.PublicDeviceDeadInfo;
import com.intc.fishery.domain.vo.PublicDeviceTypeAndSerialNum;
import com.intc.fishery.domain.bo.ReqId;
import com.intc.fishery.domain.bo.ReqSetOxyTempWarnCall;
import com.intc.fishery.domain.bo.ReqSetOxyWarnValue;
@@ -46,6 +47,7 @@ import com.intc.fishery.mapper.PondMapper;
import com.intc.fishery.mapper.LinkedCtrlMapper;
import com.intc.fishery.mapper.DeviceCorrectRecordMapper;
import com.intc.fishery.constant.DefineDeviceWarnCode;
import com.intc.common.core.config.properties.DeviceTypeProperties;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.transaction.annotation.Transactional;
@@ -73,6 +75,7 @@ public class DeviceController extends BaseController {
private final PondMapper pondMapper;
private final LinkedCtrlMapper linkedCtrlMapper;
private final DeviceCorrectRecordMapper deviceCorrectRecordMapper;
private final DeviceTypeProperties deviceTypeProperties;
/**
* 查询设备管理列表
@@ -133,7 +136,7 @@ public class DeviceController extends BaseController {
*
* @param ids 主键串
*/
@SaCheckPermission("fishery:device:remove")
// @SaCheckPermission("fishery:device:remove")
@Log(title = "设备管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@@ -976,4 +979,60 @@ public class DeviceController extends BaseController {
return R.ok(deviceBaseData);
}
/**
* 检查设备二维码,解析设备类型和序列号
* 二维码格式pk=xxxxx&dn=xxxxx
*
* @param rootUserId 用户ID预留参数
* @param qrCode 设备二维码内容
* @return 设备类型和序列号
*/
@GetMapping("/check_device_qrcode")
public R<PublicDeviceTypeAndSerialNum> checkDeviceQrcode(
@RequestParam("rootUserId") Long rootUserId,
@RequestParam("qrCode") String qrCode) {
// 验证二维码不能为空
if (qrCode == null || qrCode.trim().isEmpty()) {
return R.fail("二维码格式错误");
}
// 解析二维码格式pk=xxxxx&dn=xxxxx
String[] parts = qrCode.trim().split("&");
if (parts.length != 2) {
return R.fail("二维码格式错误");
}
String pkPart = parts[0].trim();
String dnPart = parts[1].trim();
// 验证格式前缀
if (!pkPart.startsWith("pk=") || !dnPart.startsWith("dn=")) {
return R.fail("二维码格式错误");
}
// 提取实际值
String pk = pkPart.substring(3);
String dn = dnPart.substring(3);
// 验证长度
if (pk.length() < 10 || dn.length() < 10) {
return R.fail("二维码格式错误");
}
// 根据ProductKey判断设备类型
Integer deviceType = deviceTypeProperties.getDeviceTypeByProductKey(pk);
if (deviceType == null) {
return R.fail("二维码格式错误");
}
// 构建返回结果
PublicDeviceTypeAndSerialNum result = new PublicDeviceTypeAndSerialNum();
result.setDeviceType(deviceType);
result.setSerialNum(dn);
return R.ok(result);
}
}

View File

@@ -20,6 +20,7 @@ import com.intc.common.log.enums.BusinessType;
import com.intc.common.excel.utils.ExcelUtil;
import com.intc.fishery.domain.bo.PondBo;
import com.intc.fishery.domain.bo.ReqChangePond;
import com.intc.fishery.domain.bo.ReqTurnOpen;
import com.intc.fishery.service.IPondService;
import com.intc.common.mybatis.core.page.TableDataInfo;
import com.intc.common.satoken.utils.LoginHelper;
@@ -184,7 +185,23 @@ public class PondController extends BaseController {
@PathVariable Long pondId) {
PondDeviceListVo result = new PondDeviceListVo();
// 1. 查询塘口下的所有设备
// 1. 查询塘口信息
Pond pond = pondMapper.selectOne(
new LambdaQueryWrapper<Pond>()
.eq(Pond::getId, pondId)
.select(Pond::getId, Pond::getPondName, Pond::getKeepNightOpen)
);
if (pond == null) {
return R.fail("塘口不存在");
}
// 设置塘口信息
result.setPondId(pond.getId());
result.setPondName(pond.getPondName());
result.setKeepNightOpen(pond.getKeepNightOpen());
// 2. 查询塘口下的所有设备
List<Device> devices = deviceMapper.selectList(
new LambdaQueryWrapper<Device>()
.eq(Device::getPondId, pondId)
@@ -198,12 +215,12 @@ public class PondController extends BaseController {
return R.ok(result);
}
// 2. 收集所有设备ID,用于查询联动控制和故障码
// 3. 收集所有设备ID,用于查询联动控制和故障码
List<Long> deviceIds = devices.stream()
.map(Device::getId)
.collect(Collectors.toList());
// 3. 批量查询所有设备的联动控制
// 4. 批量查询所有设备的联动控制
List<LinkedCtrl> allLinkedCtrls = linkedCtrlMapper.selectList(
new LambdaQueryWrapper<LinkedCtrl>()
.in(LinkedCtrl::getDeviceId, deviceIds)
@@ -211,14 +228,14 @@ public class PondController extends BaseController {
java.util.Map<Long, List<LinkedCtrl>> linkedCtrlsByDevice = allLinkedCtrls.stream()
.collect(Collectors.groupingBy(LinkedCtrl::getDeviceId));
// 4. 批量查询塘口的所有开关
// 5. 批量查询塘口的所有开关
List<DeviceSwitch> allSwitches = deviceSwitchMapper.selectList(
new LambdaQueryWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getPondId, pondId)
.orderByAsc(DeviceSwitch::getIndex)
);
// 5. 收集开关ID,查询定时控制
// 6. 收集开关ID,查询定时控制
List<Long> switchIds = allSwitches.stream()
.map(DeviceSwitch::getId)
.collect(Collectors.toList());
@@ -232,11 +249,11 @@ public class PondController extends BaseController {
.collect(Collectors.groupingBy(TimingCtrl::getSwitchId));
}
// 6. 按设备ID分组开关
// 7. 按设备ID分组开关
java.util.Map<Long, List<DeviceSwitch>> switchesByDevice = allSwitches.stream()
.collect(Collectors.groupingBy(DeviceSwitch::getDeviceId));
// 7. 批量查询故障码
// 8. 批量查询故障码
Set<Long> controllerIds = devices.stream()
.filter(d -> d.getDeviceType() != null && d.getDeviceType() == 2)
.map(Device::getId)
@@ -249,7 +266,7 @@ public class PondController extends BaseController {
);
}
// 8. 处理探测器列表 (包含: deviceType=1 + deviceType=2且isOxygenUsed=1)
// 9. 处理探测器列表 (包含: deviceType=1 + deviceType=2且isOxygenUsed=1)
List<DeviceVo> detectorList = new ArrayList<>();
for (Device device : devices) {
// 水质检测仪 或 开启溶氧检测的测控一体机
@@ -283,7 +300,7 @@ public class PondController extends BaseController {
}
}
// 9. 处理控制器列表 (仅测控一体机 deviceType=2)
// 10. 处理控制器列表 (仅测控一体机 deviceType=2)
List<DeviceWithSwitchVo> controllerList = new ArrayList<>();
for (Device device : devices) {
if (device.getDeviceType() != null && device.getDeviceType() == 2) {
@@ -865,8 +882,8 @@ public class PondController extends BaseController {
// 如果塘口没有变化,直接返回
Long oldPondId = device.getPondId();
Long newPondId = (request.getPondId() != null && request.getPondId() > 0) ? request.getPondId() : null;
if ((oldPondId == null && newPondId == null) ||
if ((oldPondId == null && newPondId == null) ||
(oldPondId != null && oldPondId.equals(newPondId))) {
return R.ok();
}
@@ -944,4 +961,56 @@ public class PondController extends BaseController {
return R.ok();
}
/**
* 设置塘口夜间防误关开关
* 开启后,夜间时段(21:00-次日6:00)内不会因为故障而关闭设备
*
* @param request 请求对象包含塘口ID和开关状态
* @return 操作结果
*/
@PutMapping("/keep_night_open")
public R<Void> setKeepNightOpen(
@Validated @RequestBody ReqTurnOpen request) {
// 验证塘口ID
if (request.getId() == null || request.getId() <= 0) {
return R.fail("塘口ID不能为空");
}
// 查询塘口信息
Pond pond = pondMapper.selectOne(
new LambdaQueryWrapper<Pond>()
.eq(Pond::getId, request.getId())
.select(Pond::getId, Pond::getUserId, Pond::getPondName, Pond::getKeepNightOpen)
);
// 验证塘口存在性和权限
if (pond == null) {
return R.fail("塘口不存在或无权限访问");
}
// 如果开关状态没有变化,直接返回
if (pond.getKeepNightOpen() != null && pond.getKeepNightOpen().equals(request.getIsOpen())) {
return R.ok();
}
// 更新夜间防误关开关
boolean updated = pondMapper.update(null,
new LambdaUpdateWrapper<Pond>()
.eq(Pond::getId, request.getId())
.set(Pond::getKeepNightOpen, request.getIsOpen())
) > 0;
if (!updated) {
return R.fail("更新失败");
}
// TODO: 记录操作日志
// String op = request.getIsOpen() == 1 ? "开启" : "关闭";
// CacheData.AddMessageOpRecordUser(rootUserId, userId, "塘口操作",
// String.format("塘口(%s夜间防误关%s", pond.getPondName(), op));
return R.ok();
}
}

View File

@@ -17,9 +17,22 @@ import com.intc.common.core.validate.AddGroup;
import com.intc.common.core.validate.EditGroup;
import com.intc.common.log.enums.BusinessType;
import com.intc.fishery.domain.vo.TimingCtrlVo;
import com.intc.fishery.domain.vo.PublicTimeCtrl;
import com.intc.fishery.domain.bo.TimingCtrlBo;
import com.intc.fishery.domain.bo.ReqId;
import com.intc.fishery.domain.bo.ReqAddTimeCtrl;
import com.intc.fishery.service.ITimingCtrlService;
import com.intc.common.mybatis.core.page.TableDataInfo;
import com.intc.fishery.mapper.TimingCtrlMapper;
import com.intc.fishery.mapper.DeviceSwitchMapper;
import com.intc.fishery.mapper.DeviceMapper;
import com.intc.fishery.domain.TimingCtrl;
import com.intc.fishery.domain.DeviceSwitch;
import com.intc.fishery.domain.Device;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import java.util.Calendar;
import java.util.stream.Collectors;
import java.util.Date;
/**
* 开关定时控制
@@ -34,6 +47,9 @@ import com.intc.common.mybatis.core.page.TableDataInfo;
public class TimingCtrlController extends BaseController {
private final ITimingCtrlService timingCtrlService;
private final TimingCtrlMapper timingCtrlMapper;
private final DeviceSwitchMapper deviceSwitchMapper;
private final DeviceMapper deviceMapper;
/**
* 查询开关定时控制列表
@@ -101,4 +117,151 @@ public class TimingCtrlController extends BaseController {
@PathVariable Long[] ids) {
return toAjax(timingCtrlService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取指定开关的定时控制列表
*
* @param request 请求对象包含开关ID
* @return 定时控制列表
*/
@PutMapping("/list")
public R<List<PublicTimeCtrl>> getTimeCtrlList(
@Validated @RequestBody ReqId request) {
// 查询指定开关的所有定时控制,按创建时间降序排列
List<TimingCtrl> timingCtrls = timingCtrlMapper.selectList(
new LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getSwitchId, request.getId())
.orderByDesc(TimingCtrl::getCreateTime)
);
// 转换为VO列表
List<PublicTimeCtrl> list = timingCtrls.stream()
.map(tc -> {
PublicTimeCtrl vo = new PublicTimeCtrl();
vo.setId(tc.getId());
vo.setSwitchId(tc.getSwitchId());
vo.setOpenTime(tc.getOpenTime());
vo.setCloseTime(tc.getCloseTime());
vo.setLoopType(tc.getLoopType());
vo.setIsOpen(tc.getIsOpen());
return vo;
})
.collect(Collectors.toList());
return R.ok(list);
}
/**
* 添加定时控制
*
* @param request 请求对象
* @return 操作结果
*/
@PostMapping("/add")
public R<Void> addTimeCtrl(
@Validated @RequestBody ReqAddTimeCtrl request) {
// 验证开启和关闭时间不能相同
if (request.getOpenTime().equals(request.getCloseTime())) {
return R.fail("开启和关闭时间不能相同");
}
// 将时间转换为今天的具体时刻
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date dateToday = calendar.getTime();
// 设置开启时间
Calendar openCal = Calendar.getInstance();
openCal.setTime(dateToday);
Calendar reqOpenCal = Calendar.getInstance();
reqOpenCal.setTime(request.getOpenTime());
openCal.set(Calendar.HOUR_OF_DAY, reqOpenCal.get(Calendar.HOUR_OF_DAY));
openCal.set(Calendar.MINUTE, reqOpenCal.get(Calendar.MINUTE));
Date openTime = openCal.getTime();
// 设置关闭时间
Calendar closeCal = Calendar.getInstance();
closeCal.setTime(dateToday);
Calendar reqCloseCal = Calendar.getInstance();
reqCloseCal.setTime(request.getCloseTime());
closeCal.set(Calendar.HOUR_OF_DAY, reqCloseCal.get(Calendar.HOUR_OF_DAY));
closeCal.set(Calendar.MINUTE, reqCloseCal.get(Calendar.MINUTE));
Date closeTime = closeCal.getTime();
// 如果开启时间大于关闭时间则关闭时间加1天
if (openTime.after(closeTime)) {
closeCal.add(Calendar.DAY_OF_MONTH, 1);
closeTime = closeCal.getTime();
}
// 查询开关信息,包括关联的设备信息
DeviceSwitch deviceSwitch = deviceSwitchMapper.selectOne(
new LambdaQueryWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, request.getId())
.select(DeviceSwitch::getId, DeviceSwitch::getSwitchName,
DeviceSwitch::getPondId, DeviceSwitch::getDeviceId, DeviceSwitch::getIsOpen)
);
if (deviceSwitch == null) {
return R.fail("开关不存在");
}
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.eq(Device::getId, deviceSwitch.getDeviceId())
.select(Device::getId, Device::getUserId, Device::getDeviceName,
Device::getWarnCode, Device::getDeadTime)
);
if (device == null) {
return R.fail("开关不存在");
}
// 检查设备是否过期
if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) {
return R.fail("设备服务已过期");
}
// 检查开关是否绑定了塘口
if (deviceSwitch.getPondId() == null || deviceSwitch.getPondId() <= 0) {
return R.fail("开关未绑定塘口");
}
// 检查定时控制数量是否超限4个
long count = timingCtrlMapper.selectCount(
new LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getSwitchId, request.getId())
);
if (count >= 4) {
return R.fail("定时控制数量不得超过4个");
}
// 创建定时控制记录
TimingCtrl timingCtrl = new TimingCtrl();
timingCtrl.setSwitchId(request.getId());
timingCtrl.setOpenTime(openTime);
timingCtrl.setCloseTime(closeTime);
timingCtrl.setLoopType(request.getLoopType().longValue());
timingCtrl.setIsOpen(0L); // 默认未启用
// 插入数据库
int result = timingCtrlMapper.insert(timingCtrl);
if (result > 0) {
// TODO: 记录操作日志
// CacheData.AddMessageOpRecordUser(rootUserId, userId, "开关定时控制",
// String.format("%s(%s)新增定时控制。",
// device.getDeviceName(), deviceSwitch.getSwitchName()));
return R.ok();
} else {
return R.fail("添加失败");
}
}
}

View File

@@ -0,0 +1,48 @@
package com.intc.fishery.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 添加定时控制请求对象
*
* @author intc
* @date 2026-01-14
*/
@Data
public class ReqAddTimeCtrl implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 开关ID
*/
@NotNull(message = "开关ID不能为空")
private Long id;
/**
* 开启时间(只包含时分)
*/
@NotNull(message = "开启时间不能为空")
@JsonFormat(pattern = "HH:mm")
private Date openTime;
/**
* 关闭时间(只包含时分)
*/
@NotNull(message = "关闭时间不能为空")
@JsonFormat(pattern = "HH:mm")
private Date closeTime;
/**
* 循环类型
*/
@NotNull(message = "循环类型不能为空")
private Integer loopType;
}

View File

@@ -0,0 +1,32 @@
package com.intc.fishery.domain.bo;
import com.intc.common.core.constant.RegexConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 添加子用户请求对象
*
* @author intc
* @date 2026-01-14
*/
@Data
@Schema(description = "添加子用户请求对象")
public class ReqAddUserChild implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 子用户手机号
*/
@Schema(description = "子用户手机号")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式不正确")
private String mobilePhone;
}

View File

@@ -0,0 +1,47 @@
package com.intc.fishery.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import com.intc.common.core.constant.RegexConstants;
import java.io.Serial;
import java.io.Serializable;
/**
* 更新手机号请求对象
*
* @author intc
* @date 2026-01-14
*/
@Data
@Schema(description = "更新手机号请求对象")
public class ReqUpdatePhone implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 旧手机号
*/
@Schema(description = "旧手机号")
@NotBlank(message = "旧手机号不能为空")
@Pattern(regexp = RegexConstants.MOBILE, message = "旧手机号格式不正确")
private String oldMobilePhone;
/**
* 新手机号
*/
@Schema(description = "新手机号")
@NotBlank(message = "新手机号不能为空")
@Pattern(regexp = RegexConstants.MOBILE, message = "新手机号格式不正确")
private String newMobilePhone;
/**
* 短信验证码
*/
@Schema(description = "短信验证码")
@NotBlank(message = "短信验证码不能为空")
private String smsCode;
}

View File

@@ -0,0 +1,32 @@
package com.intc.fishery.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import com.intc.common.core.constant.RegexConstants;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 更新报警电话请求对象
*
* @author intc
* @date 2026-01-14
*/
@Data
@Schema(description = "更新报警电话请求对象")
public class ReqUpdateWarnPhone implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 报警电话列表
*/
@Schema(description = "报警电话列表")
@NotEmpty(message = "报警电话列表不能为空")
private List<@Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式不正确") String> listPhone;
}

View File

@@ -0,0 +1,39 @@
package com.intc.fishery.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import com.intc.common.core.constant.RegexConstants;
import java.io.Serial;
import java.io.Serializable;
/**
* 验证短信验证码请求对象
*
* @author intc
* @date 2026-01-14
*/
@Data
@Schema(description = "验证短信验证码请求对象")
public class ReqVerifySmsCode implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 手机号
*/
@Schema(description = "手机号")
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式不正确")
private String mobilePhone;
/**
* 短信验证码
*/
@Schema(description = "短信验证码")
@NotBlank(message = "短信验证码不能为空")
private String smsCode;
}

View File

@@ -18,6 +18,21 @@ public class PondDeviceListVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 塘口ID
*/
private Long pondId;
/**
* 塘口名称
*/
private String pondName;
/**
* 夜间防误关0-关闭1-开启)
*/
private Integer keepNightOpen;
/**
* 探测器列表(水质检测仪)
*/

View File

@@ -0,0 +1,29 @@
package com.intc.fishery.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 设备类型和序列号视图对象
*
* @author intc
* @date 2026-01-14
*/
@Data
public class PublicDeviceTypeAndSerialNum implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 设备类型1-水质检测仪2-控制一体机)
*/
private Integer deviceType;
/**
* 设备序列号
*/
private String serialNum;
}

View File

@@ -0,0 +1,53 @@
package com.intc.fishery.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 定时控制视图对象
*
* @author intc
* @date 2026-01-14
*/
@Data
public class PublicTimeCtrl implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Long id;
/**
* 开关ID
*/
private Long switchId;
/**
* 开启时间
*/
@JsonFormat(pattern = "HH:mm:ss")
private Date openTime;
/**
* 关闭时间
*/
@JsonFormat(pattern = "HH:mm:ss")
private Date closeTime;
/**
* 循环类型
*/
private Long loopType;
/**
* 是否启用
*/
private Long isOpen;
}

View File

@@ -0,0 +1,54 @@
package com.intc.fishery.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 子用户关系视图对象
*
* @author intc
* @date 2026-01-14
*/
@Data
@Schema(description = "子用户关系视图对象")
public class UserChildVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 关系表主键id
*/
@Schema(description = "关系表主键id")
private Long id;
/**
* 子账号用户id
*/
@Schema(description = "子账号用户id")
private Long userId;
/**
* 子账号用户名
*/
@Schema(description = "子账号用户名")
private String userName;
/**
* 子账号手机号
*/
@Schema(description = "子账号手机号")
private String mobilePhone;
/**
* 关系创建时间
*/
@Schema(description = "关系创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTime;
}

View File

@@ -2,7 +2,12 @@ package com.intc.fishery.mapper;
import com.intc.fishery.domain.UserRelation;
import com.intc.fishery.domain.vo.UserRelationVo;
import com.intc.fishery.domain.vo.UserChildVo;
import com.intc.common.mybatis.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 养殖用户子账号Mapper接口
@@ -12,4 +17,30 @@ import com.intc.common.mybatis.core.mapper.BaseMapperPlus;
*/
public interface UserRelationMapper extends BaseMapperPlus<UserRelation, UserRelationVo> {
/**
* 查询用户的子账号列表
*
* @param parentUserId 父级用户ID
* @return 子账号列表
*/
@Select("SELECT r.id, r.child_user_id as userId, u.user_name as userName, " +
"u.mobile_phone as mobilePhone, r.create_time as createdTime " +
"FROM aqu_user_relation r " +
"LEFT JOIN aqu_user u ON r.child_user_id = u.id " +
"WHERE r.parent_user_id = #{parentUserId}")
List<UserChildVo> selectChildUsersByParentId(@Param("parentUserId") Long parentUserId);
/**
* 查询用户的父账号列表
*
* @param childUserId 子用户ID
* @return 父账号列表
*/
@Select("SELECT r.id, r.parent_user_id as userId, u.user_name as userName, " +
"u.mobile_phone as mobilePhone, r.create_time as createdTime " +
"FROM aqu_user_relation r " +
"LEFT JOIN aqu_user u ON r.parent_user_id = u.id " +
"WHERE r.child_user_id = #{childUserId}")
List<UserChildVo> selectParentUsersByChildId(@Param("childUserId") Long childUserId);
}

View File

@@ -1,22 +1,31 @@
package com.intc.fishery.service.impl;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.intc.common.core.utils.MapstructUtils;
import com.intc.common.core.utils.StringUtils;
import com.intc.common.mybatis.core.page.PageQuery;
import com.intc.common.mybatis.core.page.TableDataInfo;
import com.intc.fishery.domain.AquUser;
import com.intc.fishery.domain.Device;
import com.intc.fishery.domain.DeviceCorrectRecord;
import com.intc.fishery.domain.DeviceSwitch;
import com.intc.fishery.domain.LinkedCtrl;
import com.intc.fishery.domain.Pond;
import com.intc.fishery.domain.TimingCtrl;
import com.intc.fishery.domain.bo.DeviceBo;
import com.intc.fishery.domain.vo.DeviceVo;
import com.intc.fishery.mapper.DeviceCorrectRecordMapper;
import com.intc.fishery.mapper.DeviceMapper;
import com.intc.fishery.mapper.DeviceSwitchMapper;
import com.intc.fishery.mapper.LinkedCtrlMapper;
import com.intc.fishery.mapper.TimingCtrlMapper;
import com.intc.fishery.service.IDeviceService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
@@ -36,6 +45,10 @@ import lombok.extern.slf4j.Slf4j;
public class DeviceServiceImpl implements IDeviceService {
private final DeviceMapper baseMapper;
private final DeviceCorrectRecordMapper deviceCorrectRecordMapper;
private final DeviceSwitchMapper deviceSwitchMapper;
private final LinkedCtrlMapper linkedCtrlMapper;
private final TimingCtrlMapper timingCtrlMapper;
/**
* 查询设备管理
@@ -206,16 +219,79 @@ public class DeviceServiceImpl implements IDeviceService {
/**
* 校验并批量删除设备管理信息
* 删除顺序:校准记录 -> 定时控制 -> 开关(先解除联动控制引用)-> 联动控制 -> 设备
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
// 1. 删除设备关联的校准记录
deviceCorrectRecordMapper.delete(
new LambdaQueryWrapper<DeviceCorrectRecord>()
.in(DeviceCorrectRecord::getDeviceId, ids)
);
// 2. 查询设备关联的开关
List<DeviceSwitch> switches = deviceSwitchMapper.selectList(
new LambdaQueryWrapper<DeviceSwitch>()
.in(DeviceSwitch::getDeviceId, ids)
);
if (!switches.isEmpty()) {
// 收集开关ID
List<Long> switchIds = switches.stream()
.map(DeviceSwitch::getId)
.collect(Collectors.toList());
// 3. 删除开关关联的定时控制
timingCtrlMapper.delete(
new LambdaQueryWrapper<TimingCtrl>()
.in(TimingCtrl::getSwitchId, switchIds)
);
// 收集开关关联的联动控制ID
List<Long> linkedCtrlIds = switches.stream()
.map(DeviceSwitch::getLinkedCtrlId)
.filter(id -> id != null && id > 0)
.distinct()
.collect(Collectors.toList());
// 4. 解除开关与联动控制的绑定
deviceSwitchMapper.update(null,
new LambdaUpdateWrapper<DeviceSwitch>()
.set(DeviceSwitch::getLinkedCtrlId, null)
.in(DeviceSwitch::getDeviceId, ids)
);
// 5. 删除联动控制(如果有)
if (!linkedCtrlIds.isEmpty()) {
linkedCtrlMapper.delete(
new LambdaQueryWrapper<LinkedCtrl>()
.in(LinkedCtrl::getId, linkedCtrlIds)
);
}
// 6. 删除开关
deviceSwitchMapper.delete(
new LambdaQueryWrapper<DeviceSwitch>()
.in(DeviceSwitch::getDeviceId, ids)
);
}
// 7. 删除设备本身关联的联动控制
linkedCtrlMapper.delete(
new LambdaQueryWrapper<LinkedCtrl>()
.in(LinkedCtrl::getDeviceId, ids)
);
// 8. 最后删除设备
return baseMapper.deleteByIds(ids) > 0;
}

View File

@@ -47,13 +47,6 @@ public class AliyunIotProperties {
*/
private String categoryKey;
/**
* 设备类型到产品Key的映射
* 1: 水质检测仪
* 2: 控制一体机
*/
private DeviceTypeMapping deviceType = new DeviceTypeMapping();
/**
* MQTT 配置(可选,用于设备直连)
*/
@@ -165,47 +158,4 @@ public class AliyunIotProperties {
private Integer reconnectDelay = 30000;
}
/**
* 设备类型映射配置
*/
@Data
public static class DeviceTypeMapping {
/**
* 水质检测仪 ProductKey
*/
private String waterQualityMonitor;
/**
* 控制一体机 ProductKey
*/
private String controlIntegrated;
/**
* 控制器 ProductKey (别名,指向 controlIntegrated)
* @return ProductKey
*/
public String getController() {
return controlIntegrated;
}
/**
* 根据设备类型获取 ProductKey
* @param deviceType 1-水质检测仪, 2-控制一体机
* @return ProductKey
*/
public String getProductKeyByType(Integer deviceType) {
if (deviceType == null) {
return null;
}
switch (deviceType) {
case 1:
return waterQualityMonitor;
case 2:
return controlIntegrated;
default:
return null;
}
}
}
}

View File

@@ -0,0 +1,105 @@
package com.intc.iot.constant;
/**
* 物联网平台设备属性名称常量
*
* @author intc
* @date 2026-01-14
*/
public class IOTPropertyName {
private IOTPropertyName() {
}
// ========== 水质检测仪属性 ==========
/**
* 物联网卡号
*/
public static final String ICCID = "ICCID";
/**
* 溶解氧
*/
public static final String DISSOLVED_OXYGEN = "dissolvedOxygen";
/**
* PH值
*/
public static final String PH = "PH";
/**
* 水温
*/
public static final String CURRENT_TEMPERATURE = "currentTemperature";
/**
* 饱和度
*/
public static final String DOSAT = "dosat";
/**
* 故障码
*/
public static final String ERROR_CODE = "errorCode";
/**
* 校准检测
*/
public static final String T_CORRECT = "Tcorrect";
/**
* 盐度
*/
public static final String SALINITY = "salinity";
/**
* 盐度设置
*/
public static final String SALINITY_SET = "salinitySet";
/**
* 参比值
*/
public static final String T_REFERENCE = "Treference";
/**
* 荧光值
*/
public static final String T_FLUORESCENCE = "Tfluorescence";
/**
* 相位差
*/
public static final String PHASE_DIFFERENCE = "Phasedifference";
/**
* 设备校准
*/
public static final String CORRECT = "correct";
// ========== 控制器属性 ==========
/**
* 控制器额定电压
*/
public static final String RATED_VOLTAGE = "rated_voltage";
/**
* 控制器开关定时控制(前缀)
*/
public static final String LOCAL_TIMER_SWITCH = "localTimer_switch";
/**
* 控制器开关(前缀)
*/
public static final String SWITCH_INDEX = "Switch";
/**
* 控制器开关额定电流(前缀)
*/
public static final String RATING_SWITCH_INDEX = "rating_switch";
/**
* 控制器传感器故障码
*/
public static final String SENSOR_ERROR_CODE = "sensorErrorCode";
}

View File

@@ -5,10 +5,21 @@ import com.intc.common.mybatis.core.page.PageQuery;
import com.intc.common.mybatis.core.page.TableDataInfo;
import com.intc.common.satoken.utils.LoginHelper;
import com.intc.common.web.core.BaseController;
import com.intc.common.core.config.properties.DeviceTypeProperties;
import com.intc.fishery.domain.Device;
import com.intc.fishery.domain.DeviceSwitch;
import com.intc.fishery.domain.TimingCtrl;
import com.intc.fishery.domain.Pond;
import com.intc.fishery.domain.LinkedCtrl;
import com.intc.fishery.domain.DeviceCorrectRecord;
import com.intc.fishery.domain.AquUser;
import com.intc.fishery.domain.bo.*;
import com.intc.fishery.domain.vo.DeviceSwitchVo;
import com.intc.fishery.mapper.DeviceMapper;
import com.intc.fishery.mapper.PondMapper;
import com.intc.iot.config.AliyunIotProperties;
import com.intc.fishery.mapper.DeviceSwitchMapper;
import com.intc.fishery.mapper.DeviceCorrectRecordMapper;
import com.intc.fishery.mapper.TimingCtrlMapper;
import com.intc.iot.domain.bo.AddDeviceControllerBo;
import com.intc.iot.domain.bo.AddDeviceDetectorBo;
import com.intc.iot.domain.bo.DeviceCalibrateBo;
@@ -27,12 +38,15 @@ import com.intc.iot.service.WarnCallNoticeService;
import com.intc.iot.utils.AliyunAmqpSignUtil;
import com.intc.iot.utils.ControllerHelper;
import com.intc.iot.service.IotCloudService;
import com.intc.iot.constant.IOTPropertyName;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
@@ -50,7 +64,7 @@ import java.util.Map;
@Tag(name = "生活物联网平台管理", description = "阿里云飞燕平台对接接口")
public class IotController extends BaseController {
private final AliyunIotProperties aliyunIotProperties;
private final DeviceTypeProperties deviceTypeProperties;
@Autowired(required = false)
private IotDeviceService iotDeviceService;
@@ -86,13 +100,13 @@ public class IotController extends BaseController {
private PondMapper pondMapper;
@Autowired(required = false)
private com.intc.fishery.mapper.DeviceSwitchMapper deviceSwitchMapper;
private DeviceSwitchMapper deviceSwitchMapper;
@Autowired(required = false)
private com.intc.fishery.mapper.DeviceCorrectRecordMapper deviceCorrectRecordMapper;
private DeviceCorrectRecordMapper deviceCorrectRecordMapper;
@Autowired(required = false)
private com.intc.fishery.mapper.TimingCtrlMapper timingCtrlMapper;
private TimingCtrlMapper timingCtrlMapper;
@Autowired(required = false)
private IotCloudService iotCloudService;
@@ -305,7 +319,7 @@ public class IotController extends BaseController {
}
// 根据设备类型获取 ProductKey
String productKey = aliyunIotProperties.getDeviceType().getProductKeyByType(devicetype);
String productKey = deviceTypeProperties.getProductKeyByType(devicetype);
if (productKey == null || productKey.isEmpty()) {
return R.fail("未配置该设备类型的 ProductKey请检查配置文件deviceType: " + devicetype + "");
}
@@ -722,9 +736,9 @@ public class IotController extends BaseController {
// 验证塘口是否存在且属于当前用户
if (bo.getPondId() != null && bo.getPondId() > 0 && pondMapper != null) {
long count = pondMapper.selectCount(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.intc.fishery.domain.Pond>()
.eq(com.intc.fishery.domain.Pond::getUserId, userId)
.eq(com.intc.fishery.domain.Pond::getId, bo.getPondId())
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Pond>()
.eq(Pond::getUserId, userId)
.eq(Pond::getId, bo.getPondId())
);
if (count == 0) {
return R.fail("塘口不存在或无权限访问");
@@ -742,7 +756,7 @@ public class IotController extends BaseController {
}
// 获取控制器的 ProductKey
String productKey = aliyunIotProperties.getDeviceType().getControlIntegrated();
String productKey = deviceTypeProperties.getControlIntegrated();
if (productKey == null || productKey.isEmpty()) {
return R.fail("未配置测控一体机的 ProductKey");
}
@@ -944,9 +958,9 @@ public class IotController extends BaseController {
device.setTempWarnCallNoDis(0);
// 创建开关列表
java.util.List<com.intc.fishery.domain.DeviceSwitch> switches = new java.util.ArrayList<>();
java.util.List<DeviceSwitch> switches = new java.util.ArrayList<>();
for (int i = 1; i <= 4; i++) {
com.intc.fishery.domain.DeviceSwitch deviceSwitch = new com.intc.fishery.domain.DeviceSwitch();
DeviceSwitch deviceSwitch = new DeviceSwitch();
deviceSwitch.setIndex(i);
deviceSwitch.setSwitchName(device.getDeviceName() + "_开关_" + i);
deviceSwitch.setConnectVoltageType(bo.getInputVoltage());
@@ -1089,7 +1103,7 @@ public class IotController extends BaseController {
}
// 保存开关信息
for (com.intc.fishery.domain.DeviceSwitch deviceSwitch : switches) {
for (DeviceSwitch deviceSwitch : switches) {
deviceSwitch.setDeviceId(device.getId());
deviceSwitchMapper.insert(deviceSwitch);
}
@@ -1119,7 +1133,7 @@ public class IotController extends BaseController {
/**
* 解析开关电压电流数据
*/
private void parseSwitchVoltCur(String json, com.intc.fishery.domain.DeviceSwitch deviceSwitch) {
private void parseSwitchVoltCur(String json, DeviceSwitch deviceSwitch) {
try {
// 清理JSON字符串
json = json.replace("\"{", "{").replace("}\"", "}").replace("\\", "");
@@ -1156,9 +1170,9 @@ public class IotController extends BaseController {
// 验证塘口是否存在且属于当前用户
if (bo.getPondId() != null && bo.getPondId() > 0 && pondMapper != null) {
long count = pondMapper.selectCount(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.intc.fishery.domain.Pond>()
.eq(com.intc.fishery.domain.Pond::getUserId, userId)
.eq(com.intc.fishery.domain.Pond::getId, bo.getPondId())
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Pond>()
.eq(Pond::getUserId, userId)
.eq(Pond::getId, bo.getPondId())
);
if (count == 0) {
return R.fail("塘口不存在或无权限访问");
@@ -1176,7 +1190,7 @@ public class IotController extends BaseController {
}
// 获取水质检测仪的 ProductKey
String productKey = aliyunIotProperties.getDeviceType().getWaterQualityMonitor();
String productKey = deviceTypeProperties.getWaterQualityMonitor();
if (productKey == null || productKey.isEmpty()) {
return R.fail("未配置水质检测仪的 ProductKey");
}
@@ -1478,7 +1492,7 @@ public class IotController extends BaseController {
// 创建校准记录
if (deviceCorrectRecordMapper != null) {
com.intc.fishery.domain.DeviceCorrectRecord correctRecord = new com.intc.fishery.domain.DeviceCorrectRecord();
DeviceCorrectRecord correctRecord = new DeviceCorrectRecord();
correctRecord.setDeviceId(device.getId());
correctRecord.setUserId(userId);
correctRecord.setSerialNum(device.getSerialNum());
@@ -1669,9 +1683,9 @@ public class IotController extends BaseController {
// 更新关联开关的接线方式
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getDeviceId, request.getId())
.set(com.intc.fishery.domain.DeviceSwitch::getConnectVoltageType, request.getVoltageType())
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getDeviceId, request.getId())
.set(DeviceSwitch::getConnectVoltageType, request.getVoltageType())
);
String voltageDesc = ControllerHelper.getVoltageDescription(request.getVoltageType());
@@ -1693,7 +1707,7 @@ public class IotController extends BaseController {
*/
@Operation(summary = "开关开启/关闭控制")
@PutMapping("/switch/turn_switch")
public R<Void> turnSwitch(@RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) {
public R<Void> turnSwitch(@RequestBody ReqTurnOpen request) {
try {
if (deviceSwitchMapper == null || deviceMapper == null) {
return R.fail("系统配置未完成");
@@ -1704,7 +1718,7 @@ public class IotController extends BaseController {
Integer targetStatus = request.getIsOpen();
// 查询开关信息
com.intc.fishery.domain.DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId);
DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId);
if (deviceSwitch == null) {
return R.fail("开关不存在");
}
@@ -1751,7 +1765,7 @@ public class IotController extends BaseController {
// 构造IoT属性
Map<String, Object> properties = new java.util.HashMap<>();
properties.put("switchIndex" + deviceSwitch.getIndex(), targetStatus);
properties.put(IOTPropertyName.SWITCH_INDEX + deviceSwitch.getIndex(), targetStatus);
// 调用物联网服务设置属性
boolean success = iotCloudService.setProperty(device.getIotId(), properties, true, 2);
@@ -1761,15 +1775,15 @@ public class IotController extends BaseController {
// 更新数据库中的开关状态和最后操作时间
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, targetStatus)
.set(com.intc.fishery.domain.DeviceSwitch::getLastTurnTime, new Date())
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getIsOpen, targetStatus)
.set(DeviceSwitch::getLastTurnTime, new Date())
);
// 记录操作日志
String operation = targetStatus == 1 ? "开启" : "关闭";
log.info("开关操作:{}({})的开关{}{}",
log.info("开关操作:{}({})的开关{}{}",
device.getDeviceName(), device.getSerialNum(), deviceSwitch.getSwitchName(), operation);
return R.ok();
@@ -1787,7 +1801,7 @@ public class IotController extends BaseController {
*/
@Operation(summary = "设置塘口所有开关状态")
@PutMapping("/switch/turn_pond_switch")
public R<Void> turnPondSwitch(@RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) {
public R<Void> turnPondSwitch(@RequestBody ReqTurnOpen request) {
try {
if (deviceSwitchMapper == null || deviceMapper == null) {
return R.fail("系统配置未完成");
@@ -1798,30 +1812,30 @@ public class IotController extends BaseController {
Integer targetStatus = request.getIsOpen();
// 查询塘口下所有开关信息(包括关联的设备信息)
com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch>()
.selectAll(com.intc.fishery.domain.DeviceSwitch.class)
com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch>()
.selectAll(DeviceSwitch.class)
.selectAs(Device::getUserId, "userId")
.selectAs(Device::getIotId, "iotId")
.selectAs(Device::getSerialNum, "serialNum")
.selectAs(Device::getDeviceName, "deviceName")
.selectAs(Device::getWarnCode, "warnCode")
.selectAs(Device::getDeadTime, "deadTime")
.leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId)
.eq(com.intc.fishery.domain.DeviceSwitch::getPondId, pondId);
.leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId)
.eq(DeviceSwitch::getPondId, pondId);
java.util.List<com.intc.fishery.domain.vo.DeviceSwitchVo> listSwitch =
deviceSwitchMapper.selectJoinList(com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper);
java.util.List<DeviceSwitchVo> listSwitch =
deviceSwitchMapper.selectJoinList(DeviceSwitchVo.class, wrapper);
if (listSwitch == null || listSwitch.isEmpty()) {
return R.fail("该塘口下没有开关");
}
// 按设备IotId分组并过滤不符合条件的开关
Map<String, java.util.List<com.intc.fishery.domain.vo.DeviceSwitchVo>> iotIdSwitchMap =
Map<String, java.util.List<DeviceSwitchVo>> iotIdSwitchMap =
new java.util.HashMap<>();
for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : listSwitch) {
for (DeviceSwitchVo switchVo : listSwitch) {
// 权限验证
if (switchVo.getUserId() == null || !switchVo.getUserId().equals(userId)) {
return R.fail("开关不存在或无权限访问");
@@ -1866,23 +1880,23 @@ public class IotController extends BaseController {
// 按设备批量设置开关状态
java.util.Set<Long> successSwitchIds = new java.util.HashSet<>();
java.util.List<com.intc.fishery.domain.vo.DeviceSwitchVo> successSwitches = new java.util.ArrayList<>();
java.util.List<DeviceSwitchVo> successSwitches = new java.util.ArrayList<>();
for (Map.Entry<String, java.util.List<com.intc.fishery.domain.vo.DeviceSwitchVo>> entry : iotIdSwitchMap.entrySet()) {
for (Map.Entry<String, java.util.List<DeviceSwitchVo>> entry : iotIdSwitchMap.entrySet()) {
String iotId = entry.getKey();
java.util.List<com.intc.fishery.domain.vo.DeviceSwitchVo> switches = entry.getValue();
java.util.List<DeviceSwitchVo> switches = entry.getValue();
// 构造该设备的所有开关属性
Map<String, Object> properties = new java.util.HashMap<>();
for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : switches) {
properties.put("switchIndex" + switchVo.getIndex(), targetStatus);
for (DeviceSwitchVo switchVo : switches) {
properties.put(IOTPropertyName.SWITCH_INDEX + switchVo.getIndex(), targetStatus);
}
// 调用物联网服务设置属性
boolean success = iotCloudService.setProperty(iotId, properties, false, 0);
if (success) {
// 记录成功的开关ID
for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : switches) {
for (DeviceSwitchVo switchVo : switches) {
successSwitchIds.add(switchVo.getId());
successSwitches.add(switchVo);
}
@@ -1892,15 +1906,15 @@ public class IotController extends BaseController {
// 批量更新数据库中成功设置的开关状态
if (!successSwitchIds.isEmpty()) {
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.in(com.intc.fishery.domain.DeviceSwitch::getId, successSwitchIds)
.set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, targetStatus)
.set(com.intc.fishery.domain.DeviceSwitch::getLastTurnTime, new Date())
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.in(DeviceSwitch::getId, successSwitchIds)
.set(DeviceSwitch::getIsOpen, targetStatus)
.set(DeviceSwitch::getLastTurnTime, new Date())
);
// 记录操作日志
String operation = targetStatus == 1 ? "开启" : "关闭";
for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : successSwitches) {
for (DeviceSwitchVo switchVo : successSwitches) {
log.info("开关操作:{}({})的开关{}{}",
switchVo.getDeviceName(), switchVo.getSerialNum(), switchVo.getSwitchName(), operation);
}
@@ -1921,7 +1935,7 @@ public class IotController extends BaseController {
*/
@Operation(summary = "设置开关额定电流")
@PutMapping("/switch/electric_set")
public R<Void> updateElectricSet(@RequestBody com.intc.fishery.domain.bo.ReqSwitchElectricSet request) {
public R<Void> updateElectricSet(@RequestBody ReqSwitchElectricSet request) {
try {
if (deviceSwitchMapper == null || deviceMapper == null) {
return R.fail("系统配置未完成");
@@ -1932,7 +1946,7 @@ public class IotController extends BaseController {
Double electric = request.getElectric();
// 查询开关信息
com.intc.fishery.domain.DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId);
DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId);
if (deviceSwitch == null) {
return R.fail("开关不存在");
}
@@ -1956,7 +1970,7 @@ public class IotController extends BaseController {
}
// 检查额定电流值是否已改变精度0.001
if (deviceSwitch.getRateElectricValue() != null
if (deviceSwitch.getRateElectricValue() != null
&& Math.abs(deviceSwitch.getRateElectricValue() - electric) < 0.001) {
return R.ok();
}
@@ -1983,9 +1997,9 @@ public class IotController extends BaseController {
// 更新数据库中的额定电流值
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getRateElectricValue, electric)
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getRateElectricValue, electric)
);
// 记录操作日志
@@ -2007,7 +2021,7 @@ public class IotController extends BaseController {
*/
@Operation(summary = "修改开关输出电压类型")
@PutMapping("/switch/update_voltage_type")
public R<Void> updateConnectVoltageType(@RequestBody com.intc.fishery.domain.bo.ReqSetVoltageType request) {
public R<Void> updateConnectVoltageType(@RequestBody ReqSetVoltageType request) {
try {
if (deviceSwitchMapper == null || deviceMapper == null) {
return R.fail("系统配置未完成");
@@ -2018,9 +2032,9 @@ public class IotController extends BaseController {
Integer voltageType = request.getVoltageType();
// 查询开关信息(包括关联的设备信息)
com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch>()
.selectAll(com.intc.fishery.domain.DeviceSwitch.class)
com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch>()
.selectAll(DeviceSwitch.class)
.selectAs(Device::getUserId, "userId")
.selectAs(Device::getIotId, "iotId")
.selectAs(Device::getSerialNum, "serialNum")
@@ -2029,11 +2043,11 @@ public class IotController extends BaseController {
.selectAs(Device::getInputVoltage, "inputVoltage")
.selectAs(Device::getWarnCode, "warnCode")
.selectAs(Device::getDeadTime, "deadTime")
.leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId)
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId);
.leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId)
.eq(DeviceSwitch::getId, switchId);
com.intc.fishery.domain.vo.DeviceSwitchVo switchVo =
deviceSwitchMapper.selectJoinOne(com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper);
DeviceSwitchVo switchVo =
deviceSwitchMapper.selectJoinOne(DeviceSwitchVo.class, wrapper);
if (switchVo == null) {
return R.fail("开关不存在");
@@ -2092,9 +2106,9 @@ public class IotController extends BaseController {
// 更新数据库中的电压类型
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getConnectVoltageType, voltageType)
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getConnectVoltageType, voltageType)
);
// 记录操作日志
@@ -2141,7 +2155,7 @@ public class IotController extends BaseController {
*/
@Operation(summary = "修改开关所属塘口")
@PutMapping("/switch/update_pond")
public R<Void> updateSwitchPond(@RequestBody com.intc.fishery.domain.bo.ReqChangePond request) {
public R<Void> updateSwitchPond(@RequestBody ReqChangePond request) {
try {
if (deviceSwitchMapper == null || pondMapper == null || deviceMapper == null) {
return R.fail("系统配置未完成");
@@ -2152,21 +2166,21 @@ public class IotController extends BaseController {
Long pondId = request.getPondId();
// 查询开关信息,包括关联的设备和塘口信息
com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<com.intc.fishery.domain.DeviceSwitch>()
.selectAll(com.intc.fishery.domain.DeviceSwitch.class)
com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch> wrapper =
new com.github.yulichang.wrapper.MPJLambdaWrapper<DeviceSwitch>()
.selectAll(DeviceSwitch.class)
.selectAs(Device::getDeviceName, "deviceName")
.selectAs(Device::getSerialNum, "serialNum")
.selectAs(Device::getIotId, "iotId")
.selectAs(Device::getWarnCode, "warnCode")
.selectAs(com.intc.fishery.domain.Pond::getPondName, "pondName")
.leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId)
.leftJoin(com.intc.fishery.domain.Pond.class, com.intc.fishery.domain.Pond::getId, com.intc.fishery.domain.DeviceSwitch::getPondId)
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId);
.selectAs(Pond::getPondName, "pondName")
.leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId)
.leftJoin(Pond.class, Pond::getId, DeviceSwitch::getPondId)
.eq(DeviceSwitch::getId, switchId);
DeviceSwitchVo vo = deviceSwitchMapper.selectJoinOne(
DeviceSwitchVo.class, wrapper);
com.intc.fishery.domain.vo.DeviceSwitchVo vo = deviceSwitchMapper.selectJoinOne(
com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper);
if (vo == null) {
return R.fail("开关不存在");
}
@@ -2186,30 +2200,30 @@ public class IotController extends BaseController {
if (device != null && com.intc.common.core.utils.StringUtils.isNotBlank(device.getIotId())) {
// 检查设备是否在线通过判断warnCode判断设备状态
boolean isOnline = device.getWarnCode() != null && device.getWarnCode() != 99;
if (isOnline && iotCloudService != null) {
try {
// 构造清空定时控制的属性
Map<String, Object> properties = new java.util.HashMap<>();
// 清空定时器localTimer_switch{Index}
properties.put("localTimer_switch" + vo.getIndex(), java.util.Collections.emptyList());
// 如果开关是打开状态,需要关闭它
if (vo.getIsOpen() != null && vo.getIsOpen() == 1) {
// 关闭开关:switchIndex{Index} = 0
properties.put("switchIndex" + vo.getIndex(), 0);
// 关闭开关:Switch{Index} = 0
properties.put(IOTPropertyName.SWITCH_INDEX + vo.getIndex(), 0);
// 更新数据库中的开关状态
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, 0)
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getIsOpen, 0)
);
}
// 调用物联网服务设置属性
boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 0);
if (success) {
log.info("成功通过物联网服务清空开关定时控制和关闭开关, switchId={}", switchId);
} else {
@@ -2220,53 +2234,53 @@ public class IotController extends BaseController {
}
}
}
// 删除该开关的所有定时控制记录
if (timingCtrlMapper != null) {
timingCtrlMapper.delete(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.intc.fishery.domain.TimingCtrl>()
.eq(com.intc.fishery.domain.TimingCtrl::getSwitchId, switchId)
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getSwitchId, switchId)
);
}
log.info("已清除开关ID={}的定时控制数据", switchId);
}
// 处理新塘口分配
if (pondId != null && pondId > 0) {
// 验证塘口是否存在
com.intc.fishery.domain.Pond pond = pondMapper.selectById(pondId);
Pond pond = pondMapper.selectById(pondId);
if (pond == null) {
return R.fail("塘口不存在");
}
// 更新开关的塘口ID并清空联动控制ID
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getPondId, pondId)
.set(com.intc.fishery.domain.DeviceSwitch::getLinkedCtrlId, null)
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getPondId, pondId)
.set(DeviceSwitch::getLinkedCtrlId, null)
);
// 记录操作日志
if (oldPondId != null) {
log.info("开关操作:{}({})的开关{},转移到塘口:{}",
log.info("开关操作:{}({})的开关{},转移到塘口:{}",
vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), pond.getPondName());
} else {
log.info("开关操作:{}({})的开关{},分配到塘口:{}",
log.info("开关操作:{}({})的开关{},分配到塘口:{}",
vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), pond.getPondName());
}
} else if (oldPondId != null) {
// 移除塘口分配
deviceSwitchMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<com.intc.fishery.domain.DeviceSwitch>()
.eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId)
.set(com.intc.fishery.domain.DeviceSwitch::getPondId, null)
.set(com.intc.fishery.domain.DeviceSwitch::getLinkedCtrlId, null)
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, switchId)
.set(DeviceSwitch::getPondId, null)
.set(DeviceSwitch::getLinkedCtrlId, null)
);
// 记录操作日志
log.info("开关操作:{}({})的开关{} 从 {} 移除",
log.info("开关操作:{}({})的开关{} 从 {} 移除",
vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), oldPondName);
}
@@ -2276,4 +2290,347 @@ public class IotController extends BaseController {
return R.fail("修改失败: " + e.getMessage());
}
}
/**
* 设置定时控制开关
*
* @param request 请求对象
* @return 操作结果
*/
@Operation(summary = "设置定时控制开关")
@PostMapping("/timingCtrl/update")
public R<Void> setTimeCtrlOpen(
@Validated @org.springframework.web.bind.annotation.RequestBody ReqTurnOpen request) {
try {
// 查询定时控制信息
TimingCtrl timingCtrl = timingCtrlMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getId, request.getId())
.select(TimingCtrl::getId,
TimingCtrl::getIsOpen,
TimingCtrl::getLoopType,
TimingCtrl::getSwitchId,
TimingCtrl::getOpenTime,
TimingCtrl::getCloseTime)
);
if (timingCtrl == null) {
return R.fail("定时控制不存在");
}
// 如果状态相同,无需更新
if (timingCtrl.getIsOpen().intValue() == request.getIsOpen()) {
return R.ok();
}
// 如果是启用且循环类型为1每天循环验证当前时间不在定时范围内
if (request.getIsOpen() == 1 && timingCtrl.getLoopType() != null && timingCtrl.getLoopType() == 1) {
Date now = new Date();
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTime(now);
int currentHour = calendar.get(java.util.Calendar.HOUR_OF_DAY);
int currentMinute = calendar.get(java.util.Calendar.MINUTE);
java.util.Calendar openCal = java.util.Calendar.getInstance();
openCal.setTime(timingCtrl.getOpenTime());
int openHour = openCal.get(java.util.Calendar.HOUR_OF_DAY);
int openMinute = openCal.get(java.util.Calendar.MINUTE);
java.util.Calendar closeCal = java.util.Calendar.getInstance();
closeCal.setTime(timingCtrl.getCloseTime());
int closeHour = closeCal.get(java.util.Calendar.HOUR_OF_DAY);
int closeMinute = closeCal.get(java.util.Calendar.MINUTE);
int currentTimeInMinutes = currentHour * 60 + currentMinute;
int openTimeInMinutes = openHour * 60 + openMinute;
int closeTimeInMinutes = closeHour * 60 + closeMinute;
if (currentTimeInMinutes >= openTimeInMinutes && currentTimeInMinutes <= closeTimeInMinutes) {
return R.fail("开启失败");
}
}
// 查询开关和设备信息
DeviceSwitch deviceSwitch = deviceSwitchMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, timingCtrl.getSwitchId())
.select(DeviceSwitch::getId,
DeviceSwitch::getDeviceId,
DeviceSwitch::getIndex,
DeviceSwitch::getSwitchName)
);
if (deviceSwitch == null) {
return R.fail("定时控制不存在");
}
// 查询设备信息
Device device = deviceMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
.eq(Device::getId, deviceSwitch.getDeviceId())
.select(Device::getId, Device::getUserId, Device::getDeviceName,
Device::getIotId, Device::getSerialNum, Device::getWarnCode, Device::getDeadTime)
);
if (device == null) {
return R.fail("定时控制不存在");
}
// 检查设备是否过期
if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) {
return R.fail("设备服务已过期");
}
// // 检查设备是否离线warnCode & 0x0080
// if (device.getWarnCode() != null && (device.getWarnCode() & 0x0080) != 0) {
// return R.fail("设备离线或关机");
// }
// 查询该开关的所有定时控制
java.util.List<TimingCtrl> allTimingCtrls = timingCtrlMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getSwitchId, timingCtrl.getSwitchId())
);
// 更新当前定时控制的状态
for (TimingCtrl tc : allTimingCtrls) {
if (tc.getId().equals(request.getId())) {
tc.setIsOpen(request.getIsOpen().longValue());
break;
}
}
// 调用设置属性方法
boolean success = setPropertyTimeCtrl(deviceSwitch, device, allTimingCtrls, request.getId());
if (!success) {
return R.fail("设置定时控制失败");
}
// 更新数据库
timingCtrlMapper.update(null,
new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<TimingCtrl>()
.eq(TimingCtrl::getId, request.getId())
.set(TimingCtrl::getIsOpen, request.getIsOpen().longValue())
.set(TimingCtrl::getOpenTime, timingCtrl.getOpenTime())
.set(TimingCtrl::getCloseTime, timingCtrl.getCloseTime())
);
// TODO: 记录操作日志
String operation = request.getIsOpen() == 1 ? "启用" : "停用";
log.info("开关定时控制:{}({}){}d定时控制。",
device.getDeviceName(), deviceSwitch.getSwitchName(), operation);
return R.ok();
} catch (Exception e) {
log.error("设置定时控制开关失败: {}", e.getMessage(), e);
return R.fail("设置失败: " + e.getMessage());
}
}
/**
* 设置设备定时控制属性
*
* @param deviceSwitch 开关信息
* @param device 设备信息
* @param allTimingCtrls 该开关的所有定时控制
* @param timeCtrlId 当前操作的定时控制ID
* @return 是否成功
*/
private boolean setPropertyTimeCtrl(
DeviceSwitch deviceSwitch,
Device device,
java.util.List<TimingCtrl> allTimingCtrls,
Long timeCtrlId) {
try {
java.util.List<Object> listTimingCtrl = new java.util.ArrayList<>();
Map<String, Object> properties = new java.util.HashMap<>();
Date dateToday = new Date();
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTime(dateToday);
calendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
calendar.set(java.util.Calendar.MINUTE, 0);
calendar.set(java.util.Calendar.SECOND, 0);
calendar.set(java.util.Calendar.MILLISECOND, 0);
Date todayStart = calendar.getTime();
Date now = new Date();
for (TimingCtrl timingCtrl : allTimingCtrls) {
Date onTime = timingCtrl.getOpenTime();
Date offTime = timingCtrl.getCloseTime();
// 如果是当前启用的定时控制,重新计算时间
if (timeCtrlId.equals(timingCtrl.getId()) && timingCtrl.getIsOpen() != null && timingCtrl.getIsOpen() == 1) {
java.util.Calendar onCal = java.util.Calendar.getInstance();
onCal.setTime(todayStart);
java.util.Calendar tempCal = java.util.Calendar.getInstance();
tempCal.setTime(onTime);
onCal.set(java.util.Calendar.HOUR_OF_DAY, tempCal.get(java.util.Calendar.HOUR_OF_DAY));
onCal.set(java.util.Calendar.MINUTE, tempCal.get(java.util.Calendar.MINUTE));
onTime = onCal.getTime();
java.util.Calendar offCal = java.util.Calendar.getInstance();
offCal.setTime(todayStart);
tempCal.setTime(offTime);
offCal.set(java.util.Calendar.HOUR_OF_DAY, tempCal.get(java.util.Calendar.HOUR_OF_DAY));
offCal.set(java.util.Calendar.MINUTE, tempCal.get(java.util.Calendar.MINUTE));
offTime = offCal.getTime();
// 如果开启时间小于当前时间加1天
if (onTime.before(now)) {
onCal.add(java.util.Calendar.DAY_OF_MONTH, 1);
onTime = onCal.getTime();
}
// 如果关闭时间小于当前时间加1天
if (offTime.before(now)) {
offCal.add(java.util.Calendar.DAY_OF_MONTH, 1);
offTime = offCal.getTime();
}
// 如果开启时间大于关闭时间关闭时间加1天
if (onTime.after(offTime)) {
offCal.add(java.util.Calendar.DAY_OF_MONTH, 1);
offTime = offCal.getTime();
}
// 如果当前时间在定时范围内,返回失败
if (!now.before(onTime) && !now.after(offTime)) {
return false;
}
// 更新定时控制的时间
timingCtrl.setOpenTime(onTime);
timingCtrl.setCloseTime(offTime);
}
// 转换为UTC Unix时间戳
long onUnixStamp = onTime.getTime() / 1000;
long offUnixStamp = offTime.getTime() / 1000;
// 构建定时控制对象
Map<String, Object> timeCtrl = new java.util.HashMap<>();
timeCtrl.put("timerMode", timingCtrl.getLoopType() != null ? timingCtrl.getLoopType() - 1 : 0);
timeCtrl.put("isValid", timingCtrl.getIsOpen() != null && timingCtrl.getIsOpen() == 1 ? 1 : 0);
timeCtrl.put("onTime", String.valueOf(onUnixStamp));
timeCtrl.put("offTime", String.valueOf(offUnixStamp));
listTimingCtrl.add(timeCtrl);
}
// 设置属性
properties.put(IOTPropertyName.LOCAL_TIMER_SWITCH + deviceSwitch.getIndex(), listTimingCtrl);
// 调用IoT服务设置属性0
return iotCloudService.setProperty(device.getIotId(), properties, false, 0);
} catch (Exception e) {
log.error("设置定时控制属性失败: {}", e.getMessage(), e);
return false;
}
}
/**
* 删除定时控制
*
* @param request 请求对象包含定时控制ID
* @return 操作结果
*/
@Operation(summary = "删除定时控制")
@DeleteMapping("/timingCtrl/delete")
@Transactional(rollbackFor = Exception.class)
public R<Void> deleteTimeCtrl(
@Validated @RequestBody ReqId request) {
try {
Long userId = LoginHelper.getUserId();
// 查询定时控制基本信息
TimingCtrl dbTimeCtrl = timingCtrlMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getId, request.getId())
.select(TimingCtrl::getId,
TimingCtrl::getIsOpen,
TimingCtrl::getSwitchId)
);
if (dbTimeCtrl == null) {
return R.fail("定时控制不存在");
}
// 查询开关信息及关联的设备信息
DeviceSwitch deviceSwitch = deviceSwitchMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getId, dbTimeCtrl.getSwitchId())
.select(DeviceSwitch::getId,
DeviceSwitch::getDeviceId,
DeviceSwitch::getIndex,
DeviceSwitch::getSwitchName)
);
if (deviceSwitch == null) {
return R.fail("定时控制不存在");
}
// 查询设备信息
Device device = deviceMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
.eq(Device::getId, deviceSwitch.getDeviceId())
.select(Device::getId, Device::getUserId, Device::getDeviceName,
Device::getIotId, Device::getSerialNum, Device::getWarnCode, Device::getDeadTime)
);
// 权限验证
if (device == null || device.getUserId() == null || !device.getUserId().equals(userId)) {
return R.fail("定时控制不存在");
}
// 检查设备是否过期
if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) {
return R.fail("设备服务已过期");
}
// // 检查设备是否离线warnCode & 0x0080
// if (device.getWarnCode() != null && (device.getWarnCode() & 0x0080) != 0) {
// return R.fail("设备离线或关机");
// }
// 查询该开关的所有定时控制
java.util.List<TimingCtrl> allTimingCtrls = timingCtrlMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TimingCtrl>()
.eq(TimingCtrl::getSwitchId, dbTimeCtrl.getSwitchId())
);
// 从列表中移除要删除的定时控制,并将其 isOpen 设置为 0
for (TimingCtrl tc : allTimingCtrls) {
if (tc.getId().equals(request.getId())) {
tc.setIsOpen(0L);
break;
}
}
// 调用设置属性方法,将更新后的定时控制列表同步到设备
boolean success = setPropertyTimeCtrl(deviceSwitch, device, allTimingCtrls, request.getId());
if (!success) {
return R.fail("设置定时控制失败");
}
// 从数据库中删除定时控制记录
int deleteCount = timingCtrlMapper.deleteById(request.getId());
if (deleteCount == 0) {
return R.fail("删除失败");
}
// 记录操作日志
log.info("开关定时控制:{}({})删除定时控制。",
device.getDeviceName(), deviceSwitch.getSwitchName());
return R.ok();
} catch (Exception e) {
log.error("删除定时控制失败: {}", e.getMessage(), e);
return R.fail("删除失败: " + e.getMessage());
}
}
}