20260408:数据推送细节优化&新增LBS获取经纬度

This commit is contained in:
George 2026-04-08 22:00:15 +08:00
parent fd04f3c74c
commit 9a75a727c4
8 changed files with 324 additions and 83 deletions

View File

@ -272,8 +272,15 @@ public class ThingsModelPushHandler {
model = calculatedModelCache.get(identifier);
} else {
Map<String, ThingsModelValueItem> calculatedModelMap = itslCache.getDeviceCalculatedModelMap(gwSerialNumber);
calculatedModelCache.putAll(calculatedModelMap);
model = calculatedModelMap.get(identifier);
if (calculatedModelMap != null && !calculatedModelMap.isEmpty()) {
calculatedModelCache.putAll(calculatedModelMap);
}
Map<String, ThingsModelValueItem> map = itslCache.getCacheThMapByProductId(config.getProductId());
if (map != null && !map.isEmpty()) {
calculatedModelCache.putAll(map);
}
log.info("[Push] configId={} sn={} 网关物模型推送总物模型数 size={}", config.getId(), gwSerialNumber, calculatedModelCache.size());
model = calculatedModelCache.get(identifier);
}
if (model == null || model.getIsCalculated() == null ||
model.getIsCalculated() != 1 || StringUtils.isEmpty(model.getFormula())) {

View File

@ -75,7 +75,7 @@ public class ThingsModelPushController extends BaseController {
@Log(title = "推送配置状态", businessType = BusinessType.UPDATE)
@PutMapping("/status")
public AjaxResult changeStatus(@RequestBody ThingsModelPushConfig config) {
return toAjax(pushConfigService.updateConfig(config));
return toAjax(pushConfigService.updateStatus(config.getId(), config.getStatus()));
}
/** 删除 */

View File

@ -342,8 +342,8 @@ public class ModbusProtocol implements IProtocol {
simpleItem.setTs(nowDate);
simpleItem.setCalculated(true);
simpleItemList.add(simpleItem);
log.debug("=>添加纯计算物模型到上报列表,设备:[{}],标识符:[{}]", serialNumber, identifier);
}
log.debug("=>添加纯计算物模型到上报列表,设备:[{}],标识符:[{}]", serialNumber, calculatedModelMap.keySet());
} catch (Exception e) {
log.error("=>处理纯计算物模型时发生错误,设备:[{}],错误:{}", serialNumber, e.getMessage(), e);
}

View File

@ -94,30 +94,32 @@ public class TSLCacheImpl implements ITSLCache {
* @param productId
* @return <p>
* {
* "functions": [
* ],
* "events": [
* ],
* "properties": [
* {
* "datatype": {
* "max": 100,
* "min": 1,
* "step": 1,
* "type": "integer",
* "unit": "流明"
* },
* "id": "light",
* "isChart": 1,
* "isHistory": 1,
* "isMonitor": 1,
* "isReadonly": 1,
* "name": "光强",
* "order": 0,
* "regId": "light",
* "type": 1
* }
* ]
* "createTime": "2026-03-14 22:30:11",
* "datatype": {
* "arrayCount": 0,
* "arrayType": "",
* "falseText": "",
* "max": 100,
* "maxLength": 1024,
* "min": 0,
* "step": 1,
* "trueText": "",
* "type": "decimal",
* "unit": "V"
* },
* "formula": "%s-0.0812",
* "id": "Dev_Vol",
* "isCalculated": 0,
* "isChart": 1,
* "isHistory": 1,
* "isMonitor": 1,
* "isReadonly": 1,
* "isSharePerm": 0,
* "modelId": 1054,
* "name": "设备电压",
* "name_zh_CN": "设备电压",
* "order": 0,
* "type": 1
* }
* </p>
*/

View File

@ -25,6 +25,9 @@ public interface IThingsModelPushConfigService {
int updateConfig(ThingsModelPushConfig config);
/** 仅更新启用/停用状态,不影响物模型项和接收用户 */
int updateStatus(Long id, Integer status);
int deleteByIds(Long[] ids);
/** 更新最近推送时间 */

View File

@ -74,6 +74,9 @@ public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigS
update.setStatus(config.getStatus());
update.setUpdateBy(SecurityUtils.getUsername());
update.setUpdateTime(DateUtils.getNowDate());
if (config.getPushInterval() != null) {
update.setPushInterval(config.getPushInterval());
}
int rows = mapper.update(update);
mapper.deleteItemsByConfigId(config.getId());
mapper.deleteUsersByConfigId(config.getId());
@ -81,6 +84,16 @@ public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigS
return rows;
}
@Override
public int updateStatus(Long id, Integer status) {
ThingsModelPushConfig update = new ThingsModelPushConfig();
update.setId(id);
update.setStatus(status);
update.setUpdateBy(SecurityUtils.getUsername());
update.setUpdateTime(DateUtils.getNowDate());
return mapper.update(update);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteByIds(Long[] ids) {

View File

@ -19,7 +19,6 @@
<result property="status" column="status"/>
<result property="tenantId" column="tenant_id"/>
<result property="tenantName" column="tenant_name"/>
<result property="userId" column="config_owner_user_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
@ -54,7 +53,7 @@
<sql id="baseColumns">
c.id, c.config_name, c.product_id, c.device_id, c.serial_number,
c.template_id, c.push_interval, c.last_push_time, c.status,
c.tenant_id, c.tenant_name, c.user_id AS config_owner_user_id, c.create_by, c.create_time,
c.tenant_id, c.tenant_name, c.create_by, c.create_time,
c.update_by, c.update_time,p.product_name, p.device_type,
d.device_name,d.network_address AS device_address, nt.name AS template_name
</sql>
@ -67,15 +66,20 @@
</sql>
<select id="selectById" resultMap="ConfigResult">
SELECT <include refid="baseColumns"/>
SELECT
<include refid="baseColumns"/>
<include refid="baseJoins"/>
WHERE c.id = #{id} AND c.del_flag = '0'
WHERE
c.id = #{id}
AND c.del_flag = '0'
</select>
<select id="selectList" resultMap="ConfigResult">
SELECT <include refid="baseColumns"/>
SELECT
<include refid="baseColumns"/>
<include refid="baseJoins"/>
WHERE c.del_flag = '0'
WHERE
c.del_flag = '0'
<if test="configName != null and configName != ''">
AND c.config_name LIKE CONCAT('%', #{configName}, '%')
</if>
@ -91,114 +95,220 @@
<if test="tenantId != null">
AND c.tenant_id = #{tenantId}
</if>
<if test="userId != null">
AND c.user_id = #{userId}
</if>
ORDER BY c.create_time DESC
ORDER BY
c.create_time DESC
</select>
<select id="selectAllEnabled" resultMap="ConfigResult">
SELECT <include refid="baseColumns"/>
SELECT
<include refid="baseColumns"/>
<include refid="baseJoins"/>
WHERE c.del_flag = '0' AND c.status = 1 AND c.push_interval > 0
WHERE
c.del_flag = '0'
AND c.status = 1
AND c.push_interval > 0
</select>
<select id="selectEnabledByProductAndDevice" resultMap="ConfigResult">
SELECT <include refid="baseColumns"/>
SELECT
<include refid="baseColumns"/>
<include refid="baseJoins"/>
WHERE c.del_flag = '0' AND c.status = 1
AND c.device_id = #{deviceId}
WHERE
c.del_flag = '0'
AND c.status = 1
AND c.device_id = #{deviceId}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO iot_things_model_push_config
(config_name, product_id, device_id, serial_number, template_id,
push_interval, status,
tenant_id, tenant_name, user_id, create_by, create_time, del_flag)
INSERT INTO
iot_things_model_push_config (
config_name,
product_id,
device_id,
serial_number,
template_id,
push_interval,
status,
tenant_id,
tenant_name,
create_by,
create_time,
del_flag
)
VALUES
(#{configName}, #{productId}, #{deviceId}, #{serialNumber}, #{templateId},
#{pushInterval}, #{status},
#{tenantId}, #{tenantName}, #{userId}, #{createBy}, #{createTime}, '0')
(
#{configName},
#{productId},
#{deviceId},
#{serialNumber},
#{templateId},
#{pushInterval},
#{status},
#{tenantId},
#{tenantName},
#{createBy},
#{createTime},
'0'
)
</insert>
<update id="update">
UPDATE iot_things_model_push_config
<set>
<if test="configName != null">config_name = #{configName},</if>
<if test="productId != null">product_id = #{productId},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
<if test="serialNumber != null">serial_number = #{serialNumber},</if>
<if test="templateId != null">template_id = #{templateId},</if>
<if test="pushInterval != null">push_interval = #{pushInterval},</if>
<if test="status != null">status = #{status},</if>
<if test="configName != null">
config_name = #{configName},
</if>
<if test="productId != null">
product_id = #{productId},
</if>
<if test="deviceId != null">
device_id = #{deviceId},
</if>
<if test="serialNumber != null">
serial_number = #{serialNumber},
</if>
<if test="templateId != null">
template_id = #{templateId},
</if>
<if test="pushInterval != null">
push_interval = #{pushInterval},
</if>
<if test="status != null">
status = #{status},
</if>
update_by = #{updateBy},
update_time = #{updateTime}
</set>
WHERE id = #{id}
WHERE
id = #{id}
</update>
<update id="deleteById">
UPDATE iot_things_model_push_config SET del_flag = '2' WHERE id = #{id}
UPDATE iot_things_model_push_config
SET
del_flag = '2'
WHERE
id = #{id}
</update>
<update id="deleteByIds">
UPDATE iot_things_model_push_config SET del_flag = '2'
WHERE id IN
UPDATE iot_things_model_push_config
SET
del_flag = '2'
WHERE
id IN
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<update id="updateLastPushTime">
UPDATE iot_things_model_push_config SET last_push_time = #{lastPushTime} WHERE id = #{id}
UPDATE iot_things_model_push_config
SET
last_push_time = #{lastPushTime}
WHERE
id = #{id}
</update>
<!-- 物模型项 -->
<select id="selectItemsByConfigId" resultMap="ItemResult">
SELECT id, config_id, model_id, identifier, model_name, unit,
green_min, green_max, yellow_min, yellow_max, sort_order
FROM iot_push_model_item
WHERE config_id = #{configId}
ORDER BY sort_order
SELECT
id,
config_id,
model_id,
identifier,
model_name,
unit,
green_min,
green_max,
yellow_min,
yellow_max,
sort_order
FROM
iot_push_model_item
WHERE
config_id = #{configId}
ORDER BY
sort_order
</select>
<insert id="insertItems">
INSERT INTO iot_push_model_item
(config_id, model_id, identifier, model_name, unit,
green_min, green_max, yellow_min, yellow_max, sort_order)
INSERT INTO
iot_push_model_item (
config_id,
model_id,
identifier,
model_name,
unit,
green_min,
green_max,
yellow_min,
yellow_max,
sort_order
)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.configId}, #{item.modelId}, #{item.identifier}, #{item.modelName}, #{item.unit},
#{item.greenMin}, #{item.greenMax}, #{item.yellowMin}, #{item.yellowMax}, #{item.sortOrder})
(
#{item.configId},
#{item.modelId},
#{item.identifier},
#{item.modelName},
#{item.unit},
#{item.greenMin},
#{item.greenMax},
#{item.yellowMin},
#{item.yellowMax},
#{item.sortOrder}
)
</foreach>
</insert>
<delete id="deleteItemsByConfigId">
DELETE FROM iot_push_model_item WHERE config_id = #{configId}
DELETE FROM iot_push_model_item
WHERE
config_id = #{configId}
</delete>
<!-- 接收用户 -->
<select id="selectUsersByConfigId" resultMap="UserResult">
SELECT u.id, u.config_id, u.user_id, su.user_name
FROM iot_push_user u
LEFT JOIN sys_user su ON u.user_id = su.user_id
WHERE u.config_id = #{configId}
SELECT
u.id,
u.config_id,
u.user_id,
su.user_name
FROM
iot_push_user u
LEFT JOIN sys_user su ON u.user_id = su.user_id
WHERE
u.config_id = #{configId}
</select>
<select id="selectUserIdsByConfigId" resultType="java.lang.Long">
SELECT user_id FROM iot_push_user WHERE config_id = #{configId}
SELECT
user_id
FROM
iot_push_user
WHERE
config_id = #{configId}
</select>
<insert id="insertUsers">
INSERT INTO iot_push_user (config_id, user_id, user_name)
INSERT INTO
iot_push_user (config_id, user_id, user_name)
VALUES
<foreach collection="list" item="u" separator=",">
(#{u.configId}, #{u.userId}, #{u.userName})
(
#{u.configId},
#{u.userId},
#{u.userName}
)
</foreach>
</insert>
<delete id="deleteUsersByConfigId">
DELETE FROM iot_push_user WHERE config_id = #{configId}
DELETE FROM iot_push_user
WHERE
config_id = #{configId}
</delete>
</mapper>

View File

@ -102,6 +102,47 @@
</div>
</el-descriptions-item>
<!-- LBS 获取经纬度(AT+LBS,回复由后端解析写入 latitude/longitude) -->
<el-descriptions-item :labelStyle="statusColor">
<template slot="label">
<i class="el-icon-location-outline"></i>
LBS获取经纬度
</template>
<div class="cell-flex nowrap">
<el-input
v-model="lbsInput"
size="mini"
placeholder="纬度,经度(设备回复后自动刷新,可手动修改后回车保存)"
style="flex: 1; min-width: 160px;"
@keyup.enter.native="saveLbsCoords"
/>
<el-popover
v-model="lbsPopoverVisible"
placement="bottom"
width="360"
trigger="click"
popper-class="atcmd-popover"
>
<div class="atcmd-title">编辑下发指令,回车即可发送</div>
<el-input
v-model="lbsCmd"
size="mini"
placeholder="例如:AT+LBS\\r\\n 或 Hex:41 54 2b 4c 42 53 0d 0a (回车下发)"
@keyup.enter.native="handleEnterSend('lbs')"
/>
<el-button slot="reference" size="mini" type="default">编辑指令</el-button>
</el-popover>
<el-button
type="primary"
size="mini"
:disabled="deviceInfo.status != 3"
@click="sendAt('lbs')"
:loading="lbsLoading"
title="默认指令:AT+LBS\r\n(ASCII 转 Hex 为 41542B4C42530D0A)"
>从设备读取</el-button>
</div>
</el-descriptions-item>
<!-- 设备物模型-->
<el-descriptions-item v-for="(item, index) in deviceInfo.thingsModels" :key="index" :labelStyle="statusColor">
<template slot="label">
@ -612,6 +653,7 @@ export default {
if (newVal && newVal.deviceId != 0) {
this.deviceInfo = newVal;
this.updateDeviceStatus(this.deviceInfo);
this.syncLbsInputFromDevice();
this.$nextTick(function () {
this.MonitorChart();
});
@ -666,13 +708,17 @@ export default {
remoteCommand: {},
imeiCmd: 'AT+IMEI\\r\\n',
iccidCmd: 'AT+ICCID\\r\\n',
lbsCmd: 'AT+LBS\\r\\n',
lbsInput: '',
imeiLoading: false,
iccidLoading: false,
lbsLoading: false,
saveLoadingImei: false,
saveLoadingIccid: false,
imeiPopoverVisible: false, // 控制IMEI指令编辑弹窗
iccidPopoverVisible: false, // 控制ICCID指令编辑弹窗
lbsPopoverVisible: false,
};
},
mounted() {
@ -700,6 +746,8 @@ export default {
this.imeiPopoverVisible = false;
} else if (type === 'iccid') {
this.iccidPopoverVisible = false;
} else if (type === 'lbs') {
this.lbsPopoverVisible = false;
}
// 调用原有的 sendAt 方法下发指令
this.sendAt(type);
@ -708,21 +756,42 @@ export default {
if (!this.deviceInfo || !this.deviceInfo.deviceId) return;
const res = await getDevice(this.deviceInfo.deviceId);
if (res && res.code === 200 && res.data) {
// 只刷新这两个字段,避免影响运行态数据/使用 $set 确保响应式更新
// 只刷新这些字段,避免影响运行态数据/使用 $set 确保响应式更新
this.$set(this.deviceInfo, 'imei', res.data.imei);
this.$set(this.deviceInfo, 'iccid', res.data.iccid);
this.$set(this.deviceInfo, 'latitude', res.data.latitude);
this.$set(this.deviceInfo, 'longitude', res.data.longitude);
this.syncLbsInputFromDevice();
}
},
syncLbsInputFromDevice() {
const lat = this.deviceInfo.latitude;
const lng = this.deviceInfo.longitude;
if (lat != null && lat !== '' && lng != null && lng !== '') {
this.lbsInput = `${lat},${lng}`;
} else {
this.lbsInput = '';
}
},
async sendAt(type) {
try {
const cmd = type === 'imei' ? this.imeiCmd : this.iccidCmd;
const cmd =
type === 'imei'
? this.imeiCmd
: type === 'iccid'
? this.iccidCmd
: type === 'lbs'
? this.lbsCmd
: '';
if (!cmd) {
this.$message({ type: 'warning', message: '指令不能为空' });
return;
}
if (type === 'imei') this.imeiLoading = true;
if (type === 'iccid') this.iccidLoading = true;
if (type === 'lbs') this.lbsLoading = true;
// FUNCTION_GET 下发链路最终会走 ByteUtils.convertToHexByteArray(command)。
// 因此这里必须传“十六进制字节串”,而不是原始 ASCII。
@ -750,6 +819,7 @@ export default {
} finally {
this.imeiLoading = false;
this.iccidLoading = false;
this.lbsLoading = false;
}
},
@ -809,6 +879,42 @@ export default {
}
},
async saveLbsCoords() {
try {
if (!this.deviceInfo || !this.deviceInfo.deviceId) return;
const raw = String(this.lbsInput || '').trim();
if (!raw) {
this.$message({ type: 'warning', message: '请输入 纬度,经度' });
return;
}
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
if (parts.length !== 2) {
this.$message({ type: 'warning', message: '格式应为:纬度,经度' });
return;
}
const lat = parseFloat(parts[0]);
const lng = parseFloat(parts[1]);
if (Number.isNaN(lat) || Number.isNaN(lng)) {
this.$message({ type: 'warning', message: '纬度、经度必须为数字' });
return;
}
const res = await updateDevice({
deviceId: this.deviceInfo.deviceId,
serialNumber: this.deviceInfo.serialNumber,
latitude: lat,
longitude: lng,
});
if (res && res.code === 200) {
this.$message({ type: 'success', message: '保存成功' });
await this.refreshDeviceBaseInfo();
} else {
this.$message({ type: 'error', message: (res && res.msg) || '保存失败' });
}
} catch (e) {
this.$message({ type: 'error', message: e && e.message ? e.message : '保存失败' });
}
},
initResizeObserver() {
if (this.resizeObserver) return;
if (typeof window.ResizeObserver !== 'function') return;