Compare commits
24 Commits
test
...
f7e0a938b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e0a938b1 | ||
|
|
5fc9fbfc6b | ||
|
|
0cacd97622 | ||
|
|
a896df1a8e | ||
|
|
c0b1fbcaaf | ||
|
|
991446c619 | ||
|
|
0f763243d8 | ||
|
|
69598f4a93 | ||
|
|
af01b7dc8c | ||
|
|
c43c04c58a | ||
|
|
c9bc699f9c | ||
|
|
e1820f4130 | ||
|
|
4c7f879faa | ||
|
|
297e70828d | ||
|
|
d2e47f6329 | ||
|
|
a83a363a76 | ||
|
|
ebd85a593b | ||
|
|
4b154c8c3a | ||
|
|
3600f89289 | ||
|
|
7e7ed5ed6d | ||
|
|
5a8344426b | ||
|
|
44b2336bd1 | ||
|
|
98f6ef52d8 | ||
|
|
20719de0de |
@@ -50,8 +50,11 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
|||||||
String tenantId = loginBody.getTenantId();
|
String tenantId = loginBody.getTenantId();
|
||||||
String email = loginBody.getEmail();
|
String email = loginBody.getEmail();
|
||||||
String emailCode = loginBody.getEmailCode();
|
String emailCode = loginBody.getEmailCode();
|
||||||
|
// 用于保存用户信息
|
||||||
|
SysUserVo[] userHolder = new SysUserVo[1];
|
||||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||||
SysUserVo user = loadUserByEmail(email);
|
SysUserVo user = loadUserByEmail(email);
|
||||||
|
userHolder[0] = user;
|
||||||
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
|
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
return loginService.buildLoginUser(user);
|
return loginService.buildLoginUser(user);
|
||||||
@@ -72,6 +75,10 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
|||||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
loginVo.setClientId(client.getClientId());
|
loginVo.setClientId(client.getClientId());
|
||||||
|
loginVo.setUserId(loginUser.getUserId());
|
||||||
|
loginVo.setUserName(loginUser.getUsername());
|
||||||
|
loginVo.setNickName(loginUser.getNickname());
|
||||||
|
loginVo.setPhonenumber(userHolder[0].getPhonenumber());
|
||||||
return loginVo;
|
return loginVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,11 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
|||||||
if (captchaEnabled) {
|
if (captchaEnabled) {
|
||||||
validateCaptcha(tenantId, username, code, uuid);
|
validateCaptcha(tenantId, username, code, uuid);
|
||||||
}
|
}
|
||||||
|
// 用于保存用户信息(包括手机号)
|
||||||
|
SysUserVo[] userHolder = new SysUserVo[1];
|
||||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||||
SysUserVo user = loadUserByUsername(username);
|
SysUserVo user = loadUserByUsername(username);
|
||||||
|
userHolder[0] = user; // 保存用户信息
|
||||||
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||||
return loginService.buildLoginUser(user);
|
return loginService.buildLoginUser(user);
|
||||||
@@ -84,6 +87,10 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
|||||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
loginVo.setClientId(client.getClientId());
|
loginVo.setClientId(client.getClientId());
|
||||||
|
loginVo.setUserId(loginUser.getUserId());
|
||||||
|
loginVo.setUserName(loginUser.getUsername());
|
||||||
|
loginVo.setNickName(loginUser.getNickname());
|
||||||
|
loginVo.setPhonenumber(userHolder[0].getPhonenumber());
|
||||||
return loginVo;
|
return loginVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,12 +71,16 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
|
|
||||||
|
// 短信登录用户:token 有效期1年,允许多设备同时在线(与微信小程序一致)
|
||||||
|
// - timeout: 1年 = 31536000秒
|
||||||
|
// - activeTimeout: -1 表示永不因不活跃而过期
|
||||||
|
// - isConcurrent: true 允许多设备同时在线
|
||||||
SaLoginParameter model = new SaLoginParameter();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDeviceType(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
model.setTimeout(31536000L); // 1年 = 365天 * 24小时 * 60分钟 * 60秒
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
model.setActiveTimeout(-1); // 永不因不活跃过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setIsConcurrent(true); // 允许多设备同时在线
|
||||||
model.setActiveTimeout(client.getActiveTimeout());
|
|
||||||
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.login(loginUser, model);
|
LoginHelper.login(loginUser, model);
|
||||||
|
|||||||
@@ -80,8 +80,11 @@ public class SocialAuthStrategy implements IAuthStrategy {
|
|||||||
} else {
|
} else {
|
||||||
social = list.get(0);
|
social = list.get(0);
|
||||||
}
|
}
|
||||||
|
// 用于保存用户信息
|
||||||
|
SysUserVo[] userHolder = new SysUserVo[1];
|
||||||
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
|
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
|
||||||
SysUserVo user = loadUser(social.getUserId());
|
SysUserVo user = loadUser(social.getUserId());
|
||||||
|
userHolder[0] = user;
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
return loginService.buildLoginUser(user);
|
return loginService.buildLoginUser(user);
|
||||||
});
|
});
|
||||||
@@ -101,6 +104,10 @@ public class SocialAuthStrategy implements IAuthStrategy {
|
|||||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||||
loginVo.setClientId(client.getClientId());
|
loginVo.setClientId(client.getClientId());
|
||||||
|
loginVo.setUserId(loginUser.getUserId());
|
||||||
|
loginVo.setUserName(loginUser.getUsername());
|
||||||
|
loginVo.setNickName(loginUser.getNickname());
|
||||||
|
loginVo.setPhonenumber(userHolder[0].getPhonenumber());
|
||||||
return loginVo;
|
return loginVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,12 +93,24 @@ public class WechatAuthStrategy implements IAuthStrategy {
|
|||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
|
|
||||||
// 4. 配置登录参数
|
// 4. 配置登录参数
|
||||||
|
// 微信小程序用户:token 有效期1年,允许多设备同时在线
|
||||||
|
// - timeout: 1年 = 31536000秒
|
||||||
|
// - activeTimeout: -1 表示永不因不活跃而过期(用户1年内随时可用)
|
||||||
|
// - isConcurrent: true 允许多设备同时在线
|
||||||
SaLoginParameter model = new SaLoginParameter();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDeviceType(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
model.setTimeout(client.getTimeout());
|
// 微信小程序:token有效期1年,永不因不活跃过期
|
||||||
model.setActiveTimeout(client.getActiveTimeout());
|
model.setTimeout(31536000L); // 1年 = 365天 * 24小时 * 60分钟 * 60秒
|
||||||
|
model.setActiveTimeout(-1); // 永不因不活跃过期
|
||||||
|
// isConcurrent: 是否允许同一账号多地同时登录
|
||||||
|
// true = 允许多设备同时在线(手机+平板)
|
||||||
|
// false = 同一设备类型只能一个在线,新登录踢掉旧的(换手机场景)
|
||||||
|
model.setIsConcurrent(true); // 允许多设备同时在线
|
||||||
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||||
|
|
||||||
|
log.info("微信登录token配置: clientId={}, timeout=1年, activeTimeout=永不过期, isConcurrent={}",
|
||||||
|
client.getClientId(), true);
|
||||||
|
|
||||||
// 5. 执行登录,生成 token
|
// 5. 执行登录,生成 token
|
||||||
LoginHelper.login(loginUser, model);
|
LoginHelper.login(loginUser, model);
|
||||||
|
|
||||||
@@ -124,15 +136,58 @@ public class WechatAuthStrategy implements IAuthStrategy {
|
|||||||
* @return 系统用户信息
|
* @return 系统用户信息
|
||||||
*/
|
*/
|
||||||
private SysUserVo loadUserByPhone(String phone, AquUser aquUser) {
|
private SysUserVo loadUserByPhone(String phone, AquUser aquUser) {
|
||||||
|
// 1. 优先按 AquUser ID 查询 SysUser,保证 ID 一致性
|
||||||
SysUserVo user = userMapper.selectVoOne(
|
SysUserVo user = userMapper.selectVoOne(
|
||||||
new LambdaQueryWrapper<SysUser>()
|
new LambdaQueryWrapper<SysUser>()
|
||||||
.eq(SysUser::getPhonenumber, phone)
|
.eq(SysUser::getUserId, aquUser.getId())
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果用户不存在,创建新用户
|
if (user != null) {
|
||||||
if (ObjectUtil.isNull(user)) {
|
log.info("通过AquUser ID找到系统用户: aquUserId={}, sysUserId={}, phone={}",
|
||||||
log.info("系统中不存在该手机号的用户,自动创建新用户: phone={}", phone);
|
aquUser.getId(), user.getUserId(), phone);
|
||||||
user = createNewUser(phone, aquUser);
|
// 检查手机号是否一致
|
||||||
|
if (!phone.equals(user.getPhonenumber())) {
|
||||||
|
log.warn("系统用户手机号与AquUser不一致,更新手机号: sysUserId={}, oldPhone={}, newPhone={}",
|
||||||
|
user.getUserId(), user.getPhonenumber(), phone);
|
||||||
|
// 更新手机号
|
||||||
|
SysUser updateUser = new SysUser();
|
||||||
|
updateUser.setUserId(user.getUserId());
|
||||||
|
updateUser.setPhonenumber(phone);
|
||||||
|
updateUser.setUserName(phone); // 用户名也更新为手机号
|
||||||
|
userMapper.updateById(updateUser);
|
||||||
|
// 重新查询获取更新后的数据
|
||||||
|
user = userMapper.selectVoOne(
|
||||||
|
new LambdaQueryWrapper<SysUser>()
|
||||||
|
.eq(SysUser::getUserId, aquUser.getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 2. 按手机号查询 SysUser
|
||||||
|
user = userMapper.selectVoOne(
|
||||||
|
new LambdaQueryWrapper<SysUser>()
|
||||||
|
.eq(SysUser::getPhonenumber, phone)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
// 手机号匹配但 ID 不一致,说明是历史数据,需要更新 ID 以保持一致
|
||||||
|
log.info("通过手机号找到系统用户,但ID不一致: sysUserId={}, aquUserId={}, phone={}",
|
||||||
|
user.getUserId(), aquUser.getId(), phone);
|
||||||
|
log.warn("将更新系统用户ID以与AquUser保持一致: oldSysUserId={}, newSysUserId={}",
|
||||||
|
user.getUserId(), aquUser.getId());
|
||||||
|
|
||||||
|
// 更新 SysUser 的 ID 以与 AquUser 一致
|
||||||
|
// 注意:需要先删除旧记录,再插入新记录(因为 ID 是主键)
|
||||||
|
Long oldUserId = user.getUserId();
|
||||||
|
userMapper.deleteById(oldUserId);
|
||||||
|
|
||||||
|
// 创建新的 SysUser 记录,使用 AquUser 的 ID
|
||||||
|
user = createNewUser(phone, aquUser);
|
||||||
|
log.info("已将系统用户ID从 {} 更新为 {},与AquUser保持一致", oldUserId, aquUser.getId());
|
||||||
|
} else {
|
||||||
|
// 3. 用户不存在,创建新用户
|
||||||
|
log.info("系统中不存在该手机号的用户,自动创建新用户: phone={}, aquUserId={}", phone, aquUser.getId());
|
||||||
|
user = createNewUser(phone, aquUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户状态
|
// 检查用户状态
|
||||||
|
|||||||
@@ -51,16 +51,16 @@ spring:
|
|||||||
# username: root
|
# username: root
|
||||||
# password: root
|
# password: root
|
||||||
driverClassName: org.postgresql.Driver
|
driverClassName: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://81.70.89.108:15432/fishery_dev?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
url: jdbc:postgresql://172.16.42.181:15432/fishery_prod?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
|
||||||
username: postgres
|
username: postgres
|
||||||
password: intc@123987
|
password: htYkSuRn7Gb*pbFn
|
||||||
# 从库数据源 - TDengine
|
# 从库数据源 - TDengine
|
||||||
taos:
|
taos:
|
||||||
lazy: false
|
lazy: false
|
||||||
type: ${spring.datasource.type}
|
type: ${spring.datasource.type}
|
||||||
driverClassName: com.taosdata.jdbc.rs.RestfulDriver
|
driverClassName: com.taosdata.jdbc.rs.RestfulDriver
|
||||||
# 不指定数据库名,在 SQL 中使用完整路径 fishery.table_name
|
# 不指定数据库名,在 SQL 中使用完整路径 fishery.table_name
|
||||||
url: jdbc:TAOS-RS://81.70.89.108:6041?timezone=Shanghai&charset=UTF-8&locale=en_US.UTF-8
|
url: jdbc:TAOS-RS://172.16.42.181:6041?timezone=Shanghai&charset=UTF-8&locale=en_US.UTF-8
|
||||||
username: root
|
username: root
|
||||||
password: intc@123456
|
password: intc@123456
|
||||||
hikari:
|
hikari:
|
||||||
@@ -106,7 +106,7 @@ spring:
|
|||||||
spring.data:
|
spring.data:
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
host: 81.70.89.108
|
host: 172.16.42.181
|
||||||
# 端口,默认为6379
|
# 端口,默认为6379
|
||||||
port: 26379
|
port: 26379
|
||||||
# 数据库索引
|
# 数据库索引
|
||||||
@@ -245,6 +245,8 @@ aliyun:
|
|||||||
call-success-suppress-hours: 2
|
call-success-suppress-hours: 2
|
||||||
# 设备离线告警延迟触发时间(分钟),设备离线超过此时间后才发送告警
|
# 设备离线告警延迟触发时间(分钟),设备离线超过此时间后才发送告警
|
||||||
offline-warn-delay-minutes: 6
|
offline-warn-delay-minutes: 6
|
||||||
|
# 是否启用报警电话通知(全局开关,关闭后所有类型的报警都不会触发电话通知)
|
||||||
|
call-notice-enabled: true
|
||||||
# AMQP 服务端订阅配置(使用数据同步的 AppKey + AppSecret)
|
# AMQP 服务端订阅配置(使用数据同步的 AppKey + AppSecret)
|
||||||
amqp:
|
amqp:
|
||||||
# 是否启用
|
# 是否启用
|
||||||
@@ -258,7 +260,7 @@ aliyun:
|
|||||||
# 消费组 ID(按照官方文档,应该与数据同步 AppKey 相同)
|
# 消费组 ID(按照官方文档,应该与数据同步 AppKey 相同)
|
||||||
consumer-group-id: 334224409
|
consumer-group-id: 334224409
|
||||||
# 客户端 ID(建议使用机器 UUID、MAC 地址等唯一标识)
|
# 客户端 ID(建议使用机器 UUID、MAC 地址等唯一标识)
|
||||||
client-id: fishery-backend-001
|
client-id: fishery-backend-prod
|
||||||
# 连接超时时间(毫秒)
|
# 连接超时时间(毫秒)
|
||||||
connection-timeout: 80000
|
connection-timeout: 80000
|
||||||
# 是否自动重连
|
# 是否自动重连
|
||||||
@@ -418,6 +420,6 @@ wx:
|
|||||||
# 支付回调通知配置
|
# 支付回调通知配置
|
||||||
pay-notify:
|
pay-notify:
|
||||||
# 支付回调通知URL(需根据实际域名配置)
|
# 支付回调通知URL(需根据实际域名配置)
|
||||||
notify-url: "https://api.yuceyun.cn/fishery-api/weixin/pay_notify"
|
notify-url: "https://api.yuceyun.cn/prod-api/weixin/pay_notify"
|
||||||
# 微信商户号(与上面wx.pay.mch-id保持一致)
|
# 微信商户号(与上面wx.pay.mch-id保持一致)
|
||||||
mch-id: "1671289865"
|
mch-id: "1671289865"
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ security:
|
|||||||
- /iot/test
|
- /iot/test
|
||||||
- /iot/device/**
|
- /iot/device/**
|
||||||
- /iot/amqp/**
|
- /iot/amqp/**
|
||||||
|
- /weixin/pay_notify
|
||||||
|
|
||||||
# 多租户配置
|
# 多租户配置
|
||||||
tenant:
|
tenant:
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ public class PondController extends BaseController {
|
|||||||
new LambdaQueryWrapper<Device>()
|
new LambdaQueryWrapper<Device>()
|
||||||
.eq(Device::getPondId, pondId)
|
.eq(Device::getPondId, pondId)
|
||||||
.orderByAsc(Device::getDeviceType)
|
.orderByAsc(Device::getDeviceType)
|
||||||
.orderByDesc(Device::getCreateTime)
|
.orderByAsc(Device::getCreateTime)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. 查询塘口下的所有开关(switch.pondId == pondId),包含开关未挂塘口的控制器也能被找到
|
// 3. 查询塘口下的所有开关(switch.pondId == pondId),包含开关未挂塘口的控制器也能被找到
|
||||||
@@ -601,12 +601,12 @@ public class PondController extends BaseController {
|
|||||||
com.intc.fishery.domain.bo.MessageWarnBo query = new com.intc.fishery.domain.bo.MessageWarnBo();
|
com.intc.fishery.domain.bo.MessageWarnBo query = new com.intc.fishery.domain.bo.MessageWarnBo();
|
||||||
query.setDeviceId(deviceId);
|
query.setDeviceId(deviceId);
|
||||||
List<com.intc.fishery.domain.vo.MessageWarnVo> messageWarns = messageWarnService.queryList(query);
|
List<com.intc.fishery.domain.vo.MessageWarnVo> messageWarns = messageWarnService.queryList(query);
|
||||||
|
|
||||||
if (!messageWarns.isEmpty()) {
|
if (!messageWarns.isEmpty()) {
|
||||||
List<Long> messageWarnIds = messageWarns.stream()
|
List<Long> messageWarnIds = messageWarns.stream()
|
||||||
.map(com.intc.fishery.domain.vo.MessageWarnVo::getId)
|
.map(com.intc.fishery.domain.vo.MessageWarnVo::getId)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 删除告警消息
|
// 删除告警消息
|
||||||
messageWarnService.deleteWithValidByIds(messageWarnIds, false);
|
messageWarnService.deleteWithValidByIds(messageWarnIds, false);
|
||||||
log.info("清除设备 {} 的 {} 条告警消息", deviceId, messageWarnIds.size());
|
log.info("清除设备 {} 的 {} 条告警消息", deviceId, messageWarnIds.size());
|
||||||
@@ -719,7 +719,7 @@ public class PondController extends BaseController {
|
|||||||
List<Pond> ponds = pondMapper.selectList(
|
List<Pond> ponds = pondMapper.selectList(
|
||||||
new LambdaQueryWrapper<Pond>()
|
new LambdaQueryWrapper<Pond>()
|
||||||
.eq(Pond::getUserId, rootUserId)
|
.eq(Pond::getUserId, rootUserId)
|
||||||
.orderByDesc(Pond::getCreateTime)
|
.orderByAsc(Pond::getCreateTime)
|
||||||
);
|
);
|
||||||
|
|
||||||
List<PublicPondMode1Vo> listData = new ArrayList<>();
|
List<PublicPondMode1Vo> listData = new ArrayList<>();
|
||||||
@@ -946,7 +946,7 @@ public class PondController extends BaseController {
|
|||||||
new LambdaQueryWrapper<Pond>()
|
new LambdaQueryWrapper<Pond>()
|
||||||
.eq(Pond::getUserId, rootUserId)
|
.eq(Pond::getUserId, rootUserId)
|
||||||
.select(Pond::getId, Pond::getPondName)
|
.select(Pond::getId, Pond::getPondName)
|
||||||
.orderByDesc(Pond::getCreateTime)
|
.orderByAsc(Pond::getCreateTime)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 转换为VO列表
|
// 转换为VO列表
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ public class AliyunIotProperties {
|
|||||||
*/
|
*/
|
||||||
private int saturabilityWarnHours = 8;
|
private int saturabilityWarnHours = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用报警电话通知
|
||||||
|
* 全局开关,关闭后所有类型的报警都不会触发电话通知
|
||||||
|
* 默认开启
|
||||||
|
*/
|
||||||
|
private boolean callNoticeEnabled = true;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class VmsMnsConfig {
|
public static class VmsMnsConfig {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -188,8 +188,12 @@ public class DeviceDataHandler {
|
|||||||
try {
|
try {
|
||||||
JSONObject data = new JSONObject(payload, JSONConfig.create().setIgnoreCase(false));
|
JSONObject data = new JSONObject(payload, JSONConfig.create().setIgnoreCase(false));
|
||||||
|
|
||||||
// 解析飞燕平台消息格式
|
// 解析飞燕平台消息格式(保持ignoreCase=false,避免子对象丢失大小写配置)
|
||||||
JSONObject params = data.getJSONObject("params");
|
JSONObject params = null;
|
||||||
|
Object paramsObj = data.get("params");
|
||||||
|
if (paramsObj != null) {
|
||||||
|
params = new JSONObject(paramsObj.toString(), JSONConfig.create().setIgnoreCase(false));
|
||||||
|
}
|
||||||
|
|
||||||
// 从Topic中解析产品Key和设备名称
|
// 从Topic中解析产品Key和设备名称
|
||||||
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/property/post
|
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/property/post
|
||||||
@@ -225,14 +229,14 @@ public class DeviceDataHandler {
|
|||||||
try {
|
try {
|
||||||
// 如果是控制器,需要区分处理
|
// 如果是控制器,需要区分处理
|
||||||
if (isController) {
|
if (isController) {
|
||||||
|
// 控制器故障码处理(独立于开关数据判断,确保只有故障码时也能处理)
|
||||||
|
if (params.containsKey("errorCode")) {
|
||||||
|
handleControllerErrorCode(deviceName, params);
|
||||||
|
}
|
||||||
// 判断数据类型(开关数据和传感器数据不会同时出现)
|
// 判断数据类型(开关数据和传感器数据不会同时出现)
|
||||||
if (hasSwitchData(params)) {
|
if (hasSwitchData(params)) {
|
||||||
// 开关数据:更新开关状态和电压电流
|
// 开关数据:更新开关状态和电压电流
|
||||||
handleSwitchData(deviceName, params);
|
handleSwitchData(deviceName, params);
|
||||||
// 控制器故障码处理(C# itemController.errorCode)
|
|
||||||
if (params.containsKey("errorCode")) {
|
|
||||||
handleControllerErrorCode(deviceName, params);
|
|
||||||
}
|
|
||||||
} else if (hasSensorData(params)) {
|
} else if (hasSensorData(params)) {
|
||||||
// 传感器数据:写入TDengine
|
// 传感器数据:写入TDengine
|
||||||
sensorData = convertToSensorData(productKey, deviceName, params);
|
sensorData = convertToSensorData(productKey, deviceName, params);
|
||||||
@@ -299,7 +303,12 @@ public class DeviceDataHandler {
|
|||||||
try {
|
try {
|
||||||
JSONObject data = new JSONObject(payload, JSONConfig.create().setIgnoreCase(false));
|
JSONObject data = new JSONObject(payload, JSONConfig.create().setIgnoreCase(false));
|
||||||
|
|
||||||
JSONObject params = data.getJSONObject("params");
|
// 保持ignoreCase=false,避免子对象丢失大小写配置
|
||||||
|
JSONObject params = null;
|
||||||
|
Object paramsObj = data.get("params");
|
||||||
|
if (paramsObj != null) {
|
||||||
|
params = new JSONObject(paramsObj.toString(), JSONConfig.create().setIgnoreCase(false));
|
||||||
|
}
|
||||||
|
|
||||||
// 从Topic中解析产品Key、设备名称和事件标识符
|
// 从Topic中解析产品Key、设备名称和事件标识符
|
||||||
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/{EventIdentifier}/post
|
// Topic格式: /sys/{ProductKey}/{DeviceName}/thing/event/{EventIdentifier}/post
|
||||||
@@ -570,7 +579,7 @@ public class DeviceDataHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 仅对开启了电话通知开关的告警触发电话通知(MessageWarn 由通知方法内部插入)
|
// 仅对开启了电话通知开关的告警触发电话通知(MessageWarn 由通知方法内部插入)
|
||||||
if (!callWarnList.isEmpty()) {
|
if (!callWarnList.isEmpty() && aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
triggerAlarmNotification(device, alarmMessage.toString(), sensorData, callWarnList);
|
triggerAlarmNotification(device, alarmMessage.toString(), sensorData, callWarnList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -903,6 +912,11 @@ public class DeviceDataHandler {
|
|||||||
* @param deviceName 设备名称(serialNum)
|
* @param deviceName 设备名称(serialNum)
|
||||||
*/
|
*/
|
||||||
public void triggerOfflineAlarm(String deviceName) {
|
public void triggerOfflineAlarm(String deviceName) {
|
||||||
|
// 全局电话通知开关检查
|
||||||
|
if (!aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
|
log.debug("[离线告警] 电话通知已全局关闭,跳过: {}", deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Device device = deviceMapper.selectOne(
|
Device device = deviceMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
@@ -1026,6 +1040,11 @@ public class DeviceDataHandler {
|
|||||||
* @param deviceName 设备名称(serialNum)
|
* @param deviceName 设备名称(serialNum)
|
||||||
*/
|
*/
|
||||||
public void triggerSaturaHighAlarm(String deviceName) {
|
public void triggerSaturaHighAlarm(String deviceName) {
|
||||||
|
// 全局电话通知开关检查
|
||||||
|
if (!aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
|
log.debug("[饱和度告警] 电话通知已全局关闭,跳过: {}", deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Device device = deviceMapper.selectOne(
|
Device device = deviceMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
@@ -1151,6 +1170,11 @@ public class DeviceDataHandler {
|
|||||||
* @param params 事件参数
|
* @param params 事件参数
|
||||||
*/
|
*/
|
||||||
private void triggerDeviceErrorAlarm(String deviceName, JSONObject params) {
|
private void triggerDeviceErrorAlarm(String deviceName, JSONObject params) {
|
||||||
|
// 全局电话通知开关检查
|
||||||
|
if (!aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
|
log.debug("[设备故障] 电话通知已全局关闭,跳过: {}", deviceName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Device device = deviceMapper.selectOne(
|
Device device = deviceMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
@@ -1158,76 +1182,76 @@ public class DeviceDataHandler {
|
|||||||
.last("LIMIT 1")
|
.last("LIMIT 1")
|
||||||
);
|
);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.debug("[设备故障] 设备不存在,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Long deviceId = device.getId();
|
Long deviceId = device.getId();
|
||||||
Long userId = device.getUserId();
|
Long userId = device.getUserId();
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u672a\u7ed1\u5b9a\u7528\u6237\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.debug("[设备故障] 设备未绑定用户,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.getPondId() == null) {
|
if (device.getPondId() == null) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u672a\u7ed1\u5b9a\u5858\u53e3\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.debug("[设备故障] 设备未绑定塘口,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pond pond = pondMapper.selectById(device.getPondId());
|
Pond pond = pondMapper.selectById(device.getPondId());
|
||||||
if (pond == null) {
|
if (pond == null) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u5858\u53e3\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.debug("[设备故障] 塘口不存在,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String pondName = pond.getPondName();
|
String pondName = pond.getPondName();
|
||||||
String displayName = device.getDeviceName() != null ? device.getDeviceName() : deviceName;
|
String displayName = device.getDeviceName() != null ? device.getDeviceName() : deviceName;
|
||||||
|
|
||||||
// \u89e3\u6790\u4e8b\u4ef6\u53c2\u6570\u83b7\u53d6\u6545\u969c\u7801
|
// 解析事件参数获取故障码
|
||||||
int errorCode = params != null && params.containsKey("errorCode")
|
int errorCode = params != null && params.containsKey("errorCode")
|
||||||
? params.getInt("errorCode", 0) : 0;
|
? params.getInt("errorCode", 0) : 0;
|
||||||
String errorDesc = DefineDeviceErrorCode.getErrorMessage(errorCode);
|
String errorDesc = DefineDeviceErrorCode.getErrorMessage(errorCode);
|
||||||
if (StrUtil.isBlank(errorDesc)) {
|
if (StrUtil.isBlank(errorDesc)) {
|
||||||
errorDesc = "\u8bbe\u5907\u6545\u969c(code=" + errorCode + ")";
|
errorDesc = "设备故障(code=" + errorCode + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
String warnMsg = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u53d1\u751f\u6545\u969c\uff1a%s",
|
String warnMsg = String.format("%s塘口中%s(%s)发生故障:%s",
|
||||||
pondName, displayName, deviceName, errorDesc);
|
pondName, displayName, deviceName, errorDesc);
|
||||||
log.error("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907: {}, \u6545\u969c\u4fe1\u606f: {}", deviceName, warnMsg);
|
log.error("[设备故障] 设备: {}, 故障信息: {}", deviceName, warnMsg);
|
||||||
|
|
||||||
// \u83b7\u53d6\u544a\u8b66\u624b\u673a\u53f7
|
// 获取告警手机号
|
||||||
AquUser aquUser = aquUserMapper.selectById(userId);
|
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||||
if (aquUser == null) {
|
if (aquUser == null) {
|
||||||
log.warn("[\u8bbe\u5907\u6545\u969c] \u7528\u6237\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.warn("[设备故障] 用户不存在,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
java.util.List<String> phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
java.util.List<String> phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
||||||
if (phoneList == null || phoneList.isEmpty()) {
|
if (phoneList == null || phoneList.isEmpty()) {
|
||||||
log.warn("[\u8bbe\u5907\u6545\u969c] \u7528\u6237\u672a\u914d\u7f6e\u544a\u8b66\u624b\u673a\u53f7\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.warn("[设备故障] 用户未配置告警手机号,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// \u53cc\u91cd\u6291\u5236
|
// 双重抑制
|
||||||
String errorTitle = "\u63a7\u5236\u5668\u6545\u969c";
|
String errorTitle = "控制器故障";
|
||||||
int maxBatchMinutesErr = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2;
|
int maxBatchMinutesErr = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2;
|
||||||
LocalDateTime activeBatchStartErr = LocalDateTime.now().minusMinutes(maxBatchMinutesErr);
|
LocalDateTime activeBatchStartErr = LocalDateTime.now().minusMinutes(maxBatchMinutesErr);
|
||||||
long activeCountErr = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, errorTitle, activeBatchStartErr);
|
long activeCountErr = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, errorTitle, activeBatchStartErr);
|
||||||
if (activeCountErr > 0) {
|
if (activeCountErr > 0) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u5f53\u524d\u6709\u6d3b\u8dc3\u901a\u77e5\u6279\u6b21\u5728\u8fdb\u884c\uff0c\u8df3\u8fc7: {}", deviceName);
|
log.debug("[设备故障] 当前有活跃通知批次在进行,跳过: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LocalDateTime suppressTimeErr = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours());
|
LocalDateTime suppressTimeErr = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours());
|
||||||
long recentCountErr = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, errorTitle, suppressTimeErr);
|
long recentCountErr = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, errorTitle, suppressTimeErr);
|
||||||
if (recentCountErr > 0) {
|
if (recentCountErr > 0) {
|
||||||
log.debug("[\u8bbe\u5907\u6545\u969c] {}h\u5185\u5df2\u6709\u56de\u6267\u6210\u529f\u7684\u901a\u77e5\uff0c\u8df3\u8fc7: {}", aliyunIotProperties.getCallSuccessSuppressHours(), deviceName);
|
log.debug("[设备故障] {}h内已有回执成功的通知,跳过: {}", aliyunIotProperties.getCallSuccessSuppressHours(), deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// \u5199\u5165\u544a\u8b66\u6d88\u606f
|
// 写入告警消息
|
||||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, errorTitle, WARN_TYPE_DISSOLVED_OXYGEN, warnMsg);
|
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, errorTitle, WARN_TYPE_DISSOLVED_OXYGEN, warnMsg);
|
||||||
messageWarnMapper.insert(warn);
|
messageWarnMapper.insert(warn);
|
||||||
|
|
||||||
// \u521b\u5efa\u7535\u8bdd\u901a\u77e5\u8bb0\u5f55
|
// 创建电话通知记录
|
||||||
LocalDateTime baseTime = LocalDateTime.now();
|
LocalDateTime baseTime = LocalDateTime.now();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
AquWarnCallNotice firstCallNotice = null;
|
AquWarnCallNotice firstCallNotice = null;
|
||||||
@@ -1266,11 +1290,11 @@ public class DeviceDataHandler {
|
|||||||
sendVoiceNotification(firstCallNotice, displayName, errorTitle);
|
sendVoiceNotification(firstCallNotice, displayName, errorTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("[\u8bbe\u5907\u6545\u969c] \u5df2\u89e6\u53d1\u901a\u77e5 - \u8bbe\u5907\uff1a{}, \u5858\u53e3\uff1a{}, \u624b\u673a\u53f7\u6570\uff1a{}",
|
log.info("[设备故障] 已触发通知 - 设备:{}, 塘口:{}, 手机号数:{}",
|
||||||
deviceName, pondName, phoneList.size());
|
deviceName, pondName, phoneList.size());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[\u8bbe\u5907\u6545\u969c] \u89e6\u53d1\u6545\u969c\u544a\u8b66\u5931\u8d25: {}", e.getMessage(), e);
|
log.error("[设备故障] 触发故障告警失败: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1500,8 +1524,10 @@ public class DeviceDataHandler {
|
|||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, title, WARN_TYPE_DISSOLVED_OXYGEN, message);
|
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, title, WARN_TYPE_DISSOLVED_OXYGEN, message);
|
||||||
messageWarnMapper.insert(warn);
|
messageWarnMapper.insert(warn);
|
||||||
// 创建电话通知记录
|
// 创建电话通知记录(全局开关控制)
|
||||||
createCallNotices(device, pondName, warn);
|
if (aliyunIotProperties.isCallNoticeEnabled()) {
|
||||||
|
createCallNotices(device, pondName, warn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1727,7 +1753,8 @@ public class DeviceDataHandler {
|
|||||||
try {
|
try {
|
||||||
Integer rawCode = params.getInt("sensorErrorCode");
|
Integer rawCode = params.getInt("sensorErrorCode");
|
||||||
if (rawCode == null) {
|
if (rawCode == null) {
|
||||||
log.warn("[探头错误码] 解析 sensorErrorCode 失败: {}", deviceName);
|
// 部分设备/数据包不携带此字段,属于正常情况
|
||||||
|
log.debug("[探头错误码] 设备上报数据不包含 sensorErrorCode 字段: {}", deviceName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int sensorErrorCode = rawCode;
|
int sensorErrorCode = rawCode;
|
||||||
@@ -1807,10 +1834,19 @@ public class DeviceDataHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除设备离线标志(收到属性上报时调用)
|
* 清除设备离线标志(收到属性上报时调用)
|
||||||
|
* 同时清除数据库 warnCode 位和 Redis 离线等待标记
|
||||||
*
|
*
|
||||||
* @param deviceName 设备名称(serialNum)
|
* @param deviceName 设备名称(serialNum)
|
||||||
*/
|
*/
|
||||||
private void clearDeviceOfflineFlag(String deviceName) {
|
private void clearDeviceOfflineFlag(String deviceName) {
|
||||||
|
// 1. 清除 Redis 离线等待标记(防止定时任务误触发告警)
|
||||||
|
String redisKey = DEVICE_OFFLINE_WAIT_KEY_PREFIX + deviceName;
|
||||||
|
boolean redisRemoved = RedisUtils.deleteObject(redisKey);
|
||||||
|
if (redisRemoved) {
|
||||||
|
log.info("[在线] 收到属性上报,已清除 Redis 离线等待标记: {}", deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 清除数据库 warnCode 离线标志位
|
||||||
try {
|
try {
|
||||||
Device device = deviceMapper.selectOne(
|
Device device = deviceMapper.selectOne(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||||
@@ -1827,7 +1863,7 @@ public class DeviceDataHandler {
|
|||||||
update.setId(device.getId());
|
update.setId(device.getId());
|
||||||
update.setWarnCode(device.getWarnCode() & ~DefineDeviceWarnCode.DeviceOffline);
|
update.setWarnCode(device.getWarnCode() & ~DefineDeviceWarnCode.DeviceOffline);
|
||||||
deviceMapper.updateById(update);
|
deviceMapper.updateById(update);
|
||||||
log.debug("[在线] 收到属性上报,已清除设备离线标志: {}", deviceName);
|
log.debug("[在线] 已清除设备数据库离线标志: {}", deviceName);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[在线] 清除设备离线标志失败[{}]: {}", deviceName, e.getMessage());
|
log.error("[在线] 清除设备离线标志失败[{}]: {}", deviceName, e.getMessage());
|
||||||
@@ -2069,7 +2105,6 @@ public class DeviceDataHandler {
|
|||||||
// 设备标识(使用 deviceName 作为 serialNum)
|
// 设备标识(使用 deviceName 作为 serialNum)
|
||||||
sensorData.setSerialNum(deviceName);
|
sensorData.setSerialNum(deviceName);
|
||||||
sensorData.setDeviceName(deviceName);
|
sensorData.setDeviceName(deviceName);
|
||||||
|
|
||||||
// 解析传感器数据(根据实际字段映射)
|
// 解析传感器数据(根据实际字段映射)
|
||||||
// 支持小写和驼峰命名两种格式
|
// 支持小写和驼峰命名两种格式
|
||||||
if (params.containsKey("dissolvedoxygen") || params.containsKey("dissolvedOxygen")) {
|
if (params.containsKey("dissolvedoxygen") || params.containsKey("dissolvedOxygen")) {
|
||||||
@@ -2077,22 +2112,28 @@ public class DeviceDataHandler {
|
|||||||
params.getDouble("dissolvedOxygen") : params.getDouble("dissolvedoxygen");
|
params.getDouble("dissolvedOxygen") : params.getDouble("dissolvedoxygen");
|
||||||
sensorData.setDissolvedOxygen(value);
|
sensorData.setDissolvedOxygen(value);
|
||||||
}
|
}
|
||||||
if (params.containsKey("temperature")) {
|
|
||||||
sensorData.setTemperature(params.getDouble("temperature"));
|
|
||||||
}
|
|
||||||
if (params.containsKey("currentTemperature")) {
|
if (params.containsKey("currentTemperature")) {
|
||||||
sensorData.setTemperature(params.getDouble("currentTemperature"));
|
sensorData.setTemperature(params.getDouble("currentTemperature"));
|
||||||
|
} else if (params.containsKey("temperature")) {
|
||||||
|
sensorData.setTemperature(params.getDouble("temperature"));
|
||||||
}
|
}
|
||||||
if (params.containsKey("saturability") || params.containsKey("dosat")) {
|
if (params.containsKey("saturability") || params.containsKey("dosat")) {
|
||||||
Double value = params.containsKey("dosat") ?
|
Double value = params.containsKey("dosat") ?
|
||||||
params.getDouble("dosat") : params.getDouble("saturability");
|
params.getDouble("dosat") : params.getDouble("saturability");
|
||||||
sensorData.setSaturability(value);
|
sensorData.setSaturability(value);
|
||||||
}
|
}
|
||||||
if (params.containsKey("ph")) {
|
if (params.containsKey("PH") || params.containsKey("ph") || params.containsKey("Ph")) {
|
||||||
sensorData.setPh(params.getDouble("ph"));
|
Double value = params.containsKey("PH") ? params.getDouble("PH")
|
||||||
|
: params.containsKey("Ph") ? params.getDouble("Ph")
|
||||||
|
: params.getDouble("ph");
|
||||||
|
sensorData.setPh(value);
|
||||||
}
|
}
|
||||||
if (params.containsKey("salinity")) {
|
// 通过遍历 keySet 做精确字符串比较,彻底避免 Hutool ignoreCase 下 containsKey/getDouble 误匹配 salinitySet
|
||||||
sensorData.setSalinity(params.getDouble("salinity"));
|
for (String k : params.keySet()) {
|
||||||
|
if ("salinity".equals(k)) {
|
||||||
|
sensorData.setSalinity(params.getDouble(k));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (params.containsKey("treference") || params.containsKey("Treference")) {
|
if (params.containsKey("treference") || params.containsKey("Treference")) {
|
||||||
Double value = params.containsKey("Treference") ?
|
Double value = params.containsKey("Treference") ?
|
||||||
@@ -2104,8 +2145,11 @@ public class DeviceDataHandler {
|
|||||||
params.getDouble("Tfluorescence") : params.getDouble("tfluorescence");
|
params.getDouble("Tfluorescence") : params.getDouble("tfluorescence");
|
||||||
sensorData.setTfluorescence(value);
|
sensorData.setTfluorescence(value);
|
||||||
}
|
}
|
||||||
if (params.containsKey("phasedifference")) {
|
if (params.containsKey("Phasedifference") || params.containsKey("phaseDifference") || params.containsKey("phasedifference")) {
|
||||||
sensorData.setPhaseDifference(params.getDouble("phasedifference"));
|
Double value = params.containsKey("Phasedifference") ? params.getDouble("Phasedifference")
|
||||||
|
: params.containsKey("phaseDifference") ? params.getDouble("phaseDifference")
|
||||||
|
: params.getDouble("phasedifference");
|
||||||
|
sensorData.setPhaseDifference(value);
|
||||||
}
|
}
|
||||||
if (params.containsKey("battery")) {
|
if (params.containsKey("battery")) {
|
||||||
sensorData.setBattery(params.getDouble("battery"));
|
sensorData.setBattery(params.getDouble("battery"));
|
||||||
|
|||||||
@@ -115,4 +115,16 @@ public interface AquWarnCallNoticeMapper extends BaseMapper<AquWarnCallNotice> {
|
|||||||
"INNER JOIN aqu_map_message_warn_call_notice m ON m.message_warn_id = w.id " +
|
"INNER JOIN aqu_map_message_warn_call_notice m ON m.message_warn_id = w.id " +
|
||||||
"WHERE m.call_notice_id = #{callNoticeId} LIMIT 1")
|
"WHERE m.call_notice_id = #{callNoticeId} LIMIT 1")
|
||||||
String selectWarnMessageByCallNoticeId(@Param("callNoticeId") Long callNoticeId);
|
String selectWarnMessageByCallNoticeId(@Param("callNoticeId") Long callNoticeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 callNoticeId 查询关联的第一条告警消息标题(title)
|
||||||
|
* 用于定时任务重发时作为 TTS warnMessage 参数,避免将完整消息内容传入导致 TTS 播报异常
|
||||||
|
*
|
||||||
|
* @param callNoticeId 通知记录ID
|
||||||
|
* @return 告警标题,不存在时返回 null
|
||||||
|
*/
|
||||||
|
@Select("SELECT w.title FROM aqu_message_warn w " +
|
||||||
|
"INNER JOIN aqu_map_message_warn_call_notice m ON m.message_warn_id = w.id " +
|
||||||
|
"WHERE m.call_notice_id = #{callNoticeId} LIMIT 1")
|
||||||
|
String selectWarnTitleByCallNoticeId(@Param("callNoticeId") Long callNoticeId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ public class AmqpMessageHandlerImpl implements AmqpMessageHandler {
|
|||||||
// 使用 DeviceDataHandler 处理数据(包括存储和报警)
|
// 使用 DeviceDataHandler 处理数据(包括存储和报警)
|
||||||
try {
|
try {
|
||||||
// 转换 items 格式:{"dissolvedOxygen": {"value": 7.82, "time": xxx}} -> {"dissolvedOxygen": 7.82}
|
// 转换 items 格式:{"dissolvedOxygen": {"value": 7.82, "time": xxx}} -> {"dissolvedOxygen": 7.82}
|
||||||
|
// 保留原始字段名不做任何映射,convertToSensorData 已兼容多种字段名格式
|
||||||
JSONObject params = new JSONObject(JSONConfig.create().setIgnoreCase(false));
|
JSONObject params = new JSONObject(JSONConfig.create().setIgnoreCase(false));
|
||||||
if (items != null) {
|
if (items != null) {
|
||||||
for (String key : items.keySet()) {
|
for (String key : items.keySet()) {
|
||||||
@@ -126,14 +127,11 @@ public class AmqpMessageHandlerImpl implements AmqpMessageHandler {
|
|||||||
JSONObject itemObj = (JSONObject) itemValue;
|
JSONObject itemObj = (JSONObject) itemValue;
|
||||||
// 提取 value 字段
|
// 提取 value 字段
|
||||||
if (itemObj.containsKey("value")) {
|
if (itemObj.containsKey("value")) {
|
||||||
// 字段名映射:currentTemperature -> temperature, dosat -> saturability
|
params.set(key, itemObj.get("value"));
|
||||||
String mappedKey = mapFieldName(key);
|
|
||||||
params.set(mappedKey, itemObj.get("value"));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果不是嵌套格式,直接使用
|
// 如果不是嵌套格式,直接使用
|
||||||
String mappedKey = mapFieldName(key);
|
params.set(key, itemValue);
|
||||||
params.set(mappedKey, itemValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,31 +148,6 @@ public class AmqpMessageHandlerImpl implements AmqpMessageHandler {
|
|||||||
log.error("使用 DeviceDataHandler 处理属性数据失败", e);
|
log.error("使用 DeviceDataHandler 处理属性数据失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段名映射:将阿里云字段名映射为应用字段名
|
|
||||||
*/
|
|
||||||
private String mapFieldName(String fieldName) {
|
|
||||||
switch (fieldName) {
|
|
||||||
case "currentTemperature":
|
|
||||||
return "temperature";
|
|
||||||
case "dosat":
|
|
||||||
return "saturability";
|
|
||||||
case "Treference":
|
|
||||||
return "treference";
|
|
||||||
case "Tfluorescence":
|
|
||||||
return "tfluorescence";
|
|
||||||
case "Tcorrect":
|
|
||||||
return "tcorrect";
|
|
||||||
case "Tsignal":
|
|
||||||
return "tsignal";
|
|
||||||
case "salinitySet":
|
|
||||||
return "salinity";
|
|
||||||
default:
|
|
||||||
// 其他字段名保持原始大小写,不做转换
|
|
||||||
return fieldName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理从 Topic 判断的属性消息(飞燕平台原始格式)
|
* 处理从 Topic 判断的属性消息(飞燕平台原始格式)
|
||||||
|
|||||||
@@ -56,9 +56,10 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 回执超时时间(分钟)
|
* 回执超时时间(分钟)
|
||||||
* VMS 回执延迟较长,改为 5 分钟,避免回执未到就被标记为超时
|
* VMS 回执延迟较长,设为 15 分钟,给 VMS 平台充足的回调缓冲时间
|
||||||
|
* 注意:超时判断基于 callTime(预计呼叫时间),实际呼叫发起后回调可能延迟数分钟到达
|
||||||
*/
|
*/
|
||||||
private static final int CALLBACK_TIMEOUT_MINUTES = 5;
|
private static final int CALLBACK_TIMEOUT_MINUTES = 15;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -104,7 +105,10 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (callNotice == null) {
|
if (callNotice == null) {
|
||||||
log.warn("[告警通知] 未找到对应的通知记录 - 呼叫ID: {}, outId: {}", callback.getCallId(), outId);
|
log.warn("[告警通知] 未找到对应的通知记录,标记为已处理跳过 - 呼叫ID: {}, outId: {}", callback.getCallId(), outId);
|
||||||
|
// 通知记录不存在(可能已被清除),直接标记回执为已处理,避免无限重复扫描
|
||||||
|
callback.setProcessed(1);
|
||||||
|
vmsCallbackMapper.updateById(callback);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +258,18 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
int sentCount = 0;
|
int sentCount = 0;
|
||||||
for (AquWarnCallNotice notice : pendingList) {
|
for (AquWarnCallNotice notice : pendingList) {
|
||||||
try {
|
try {
|
||||||
// 查询关联的告警消息内容,用于构建 TTS 参数
|
// 查询关联的告警标题(title),作为 TTS warnMessage 参数
|
||||||
String rawMessage = warnCallNoticeMapper.selectWarnMessageByCallNoticeId(notice.getId());
|
// 使用 title(如"设备离线"、"溶解氧")而非完整 message,避免长字符串被 TTS 逐字播报
|
||||||
String warnMessageShort = parseWarnMessageShort(rawMessage);
|
String warnTitle = warnCallNoticeMapper.selectWarnTitleByCallNoticeId(notice.getId());
|
||||||
|
String warnMessageShort = (warnTitle != null && !warnTitle.isEmpty()) ? warnTitle : "异常";
|
||||||
|
|
||||||
// 通过 deviceId 查询设备名称
|
// 通过 deviceId 查询设备名称
|
||||||
|
// 注意:不使用 serialNum 作为 fallback,避免数字序列号被 TTS 逐位播报
|
||||||
String deviceName = null;
|
String deviceName = null;
|
||||||
if (notice.getDeviceId() != null) {
|
if (notice.getDeviceId() != null) {
|
||||||
Device device = deviceMapper.selectById(notice.getDeviceId());
|
Device device = deviceMapper.selectById(notice.getDeviceId());
|
||||||
if (device != null) {
|
if (device != null && device.getDeviceName() != null && !device.getDeviceName().isEmpty()) {
|
||||||
deviceName = device.getDeviceName() != null ? device.getDeviceName() : device.getSerialNum();
|
deviceName = device.getDeviceName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user