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

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

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;
}