20260326:数据推送
This commit is contained in:
parent
d5cdfa9ba4
commit
b7e0984654
@ -0,0 +1,84 @@
|
||||
package com.fastbee.common.enums;
|
||||
|
||||
/**
|
||||
* 物模型数据等级枚举
|
||||
* 绿色=正常,黄色=告警,红色=警报
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
public enum DataLevelEnum {
|
||||
|
||||
GREEN(1, "✅ 正常", "#009900"),
|
||||
YELLOW(2, "⚠️ 告警", "#FF9900"),
|
||||
RED(3, "🔴 警报", "#FF0000");
|
||||
|
||||
private final int code;
|
||||
private final String label;
|
||||
/** 微信模板消息字体颜色 */
|
||||
private final String color;
|
||||
|
||||
DataLevelEnum(int code, String label, String color) {
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值和阈值配置计算数据等级
|
||||
*
|
||||
* @param value 物模型上报值(字符串)
|
||||
* @param greenMin 绿色下限(null=不限)
|
||||
* @param greenMax 绿色上限(null=不限)
|
||||
* @param yellowMin 黄色下限(null=不限)
|
||||
* @param yellowMax 黄色上限(null=不限)
|
||||
* @return 数据等级
|
||||
*/
|
||||
public static DataLevelEnum evaluate(String value,
|
||||
String greenMin, String greenMax,
|
||||
String yellowMin, String yellowMax) {
|
||||
if (value == null) {
|
||||
return RED;
|
||||
}
|
||||
// 优先尝试数值比较
|
||||
try {
|
||||
double v = Double.parseDouble(value.trim());
|
||||
if (inNumericRange(v, greenMin, greenMax)) {
|
||||
return GREEN;
|
||||
}
|
||||
if (inNumericRange(v, yellowMin, yellowMax)) {
|
||||
return YELLOW;
|
||||
}
|
||||
return RED;
|
||||
} catch (NumberFormatException e) {
|
||||
// 字符串等值比较
|
||||
if (value.equals(greenMin)) {
|
||||
return GREEN;
|
||||
}
|
||||
if (value.equals(yellowMin)) {
|
||||
return YELLOW;
|
||||
}
|
||||
return RED;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inNumericRange(double v, String min, String max) {
|
||||
if (min == null && max == null) {
|
||||
return false;
|
||||
}
|
||||
boolean lowerOk = (min == null) || v >= Double.parseDouble(min);
|
||||
boolean upperOk = (max == null) || v <= Double.parseDouble(max);
|
||||
return lowerOk && upperOk;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.fastbee.framework.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
@ -12,6 +13,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
* - 使用项目自定义的 scheduledExecutorService(已做 MDC 透传与异常打印)
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class SchedulingConfig implements SchedulingConfigurer {
|
||||
|
||||
@Resource(name = "scheduledExecutorService")
|
||||
|
||||
@ -0,0 +1,244 @@
|
||||
package com.fastbee.iot.data.push;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.notify.NotifySendResponse;
|
||||
import com.fastbee.common.core.notify.config.WeChatConfigParams;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.enums.DataLevelEnum;
|
||||
import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.wechat.WechatUtils;
|
||||
import com.fastbee.common.wechat.WeChatAppResult;
|
||||
import com.fastbee.iot.domain.PushModelItem;
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import com.fastbee.iot.model.ThingsModels.ValueItem;
|
||||
import com.fastbee.iot.service.IThingsModelPushConfigService;
|
||||
import com.fastbee.notify.domain.NotifyChannel;
|
||||
import com.fastbee.notify.core.wechat.vo.TemplateDataVo;
|
||||
import com.fastbee.notify.core.wechat.vo.WeChatPublicAccountPushVO;
|
||||
import com.fastbee.notify.service.INotifyChannelService;
|
||||
import com.fastbee.iot.service.ISocialUserService;
|
||||
import com.fastbee.iot.domain.SocialUser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 物模型数据推送核心处理器
|
||||
* 读取 Redis 中设备最新上报值,按配置的阈值评估数据等级,
|
||||
* 同等级数据合并为一条微信服务号模板消息推送。
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ThingsModelPushHandler {
|
||||
|
||||
private static final RestTemplate REST_TEMPLATE = new RestTemplate();
|
||||
|
||||
@Resource
|
||||
private IThingsModelPushConfigService pushConfigService;
|
||||
@Resource
|
||||
private INotifyChannelService notifyChannelService;
|
||||
@Resource
|
||||
private ISocialUserService socialUserService;
|
||||
@Resource
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 按推送配置ID执行推送(读取该配置涉及设备的最新 Redis 值)
|
||||
*
|
||||
* @param config 推送配置(含 items、userIds)
|
||||
*/
|
||||
public void doPush(ThingsModelPushConfig config) {
|
||||
if (CollectionUtils.isEmpty(config.getItems())) {
|
||||
log.warn("[Push] configId={} 没有配置物模型项,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取通知渠道(公众号)
|
||||
NotifyChannel channel = notifyChannelService.selectNotifyChannelById(config.getChannelId());
|
||||
if (channel == null) {
|
||||
log.warn("[Push] configId={} 渠道不存在 channelId={}", config.getId(), config.getChannelId());
|
||||
return;
|
||||
}
|
||||
WeChatConfigParams wxConfig = JSONObject.parseObject(channel.getConfigContent(), WeChatConfigParams.class);
|
||||
if (StringUtils.isEmpty(wxConfig.getAppId()) || StringUtils.isEmpty(wxConfig.getAppSecret())) {
|
||||
log.warn("[Push] configId={} 渠道微信配置不完整", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 access_token
|
||||
WeChatAppResult tokenResult = WechatUtils.getAccessToken(wxConfig.getAppId(), wxConfig.getAppSecret());
|
||||
if (tokenResult == null || StringUtils.isEmpty(tokenResult.getAccessToken())) {
|
||||
log.error("[Push] configId={} 获取微信 access_token 失败", config.getId());
|
||||
return;
|
||||
}
|
||||
String pushUrl = FastBeeConstant.URL.WX_PUBLIC_ACCOUNT_TEMPLATE_SEND_URL_PREFIX + tokenResult.getAccessToken();
|
||||
|
||||
// 确定要查询的设备编号列表
|
||||
List<String> serialNumbers = resolveSerialNumbers(config);
|
||||
if (CollectionUtils.isEmpty(serialNumbers)) {
|
||||
log.warn("[Push] configId={} 没有找到对应设备,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取接收用户 openId 列表
|
||||
List<Long> userIds = config.getUserIds();
|
||||
if (CollectionUtils.isEmpty(userIds)) {
|
||||
log.warn("[Push] configId={} 没有配置接收用户,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
List<SocialUser> socialUsers = socialUserService.listWechatPublicAccountOpenId(
|
||||
userIds.stream().map(Object::toString).collect(Collectors.toList()));
|
||||
Map<Long, String> openIdMap = socialUsers.stream()
|
||||
.collect(Collectors.toMap(SocialUser::getSysUserId, SocialUser::getOpenId, (a, b) -> b));
|
||||
|
||||
// 对每台设备推送
|
||||
for (String serialNumber : serialNumbers) {
|
||||
pushForDevice(config, serialNumber, pushUrl, openIdMap);
|
||||
}
|
||||
|
||||
// 更新最近推送时间
|
||||
pushConfigService.updateLastPushTime(config.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对单台设备执行推送
|
||||
*/
|
||||
private void pushForDevice(ThingsModelPushConfig config, String serialNumber,
|
||||
String pushUrl, Map<Long, String> openIdMap) {
|
||||
String redisKey = RedisKeyBuilder.buildTSLVCacheKey(config.getProductId(), serialNumber);
|
||||
|
||||
// 按等级分组收集显示文本
|
||||
Map<DataLevelEnum, List<String>> levelTextMap = new LinkedHashMap<>();
|
||||
for (DataLevelEnum level : DataLevelEnum.values()) {
|
||||
levelTextMap.put(level, new ArrayList<>());
|
||||
}
|
||||
|
||||
for (PushModelItem item : config.getItems()) {
|
||||
String cacheValue = redisCache.getCacheMapValue(redisKey, item.getIdentifier());
|
||||
if (StringUtils.isEmpty(cacheValue)) {
|
||||
continue;
|
||||
}
|
||||
String value;
|
||||
try {
|
||||
ValueItem vi = JSON.parseObject(cacheValue, ValueItem.class);
|
||||
value = vi.getValue();
|
||||
} catch (Exception e) {
|
||||
value = cacheValue;
|
||||
}
|
||||
DataLevelEnum level = DataLevelEnum.evaluate(
|
||||
value, item.getGreenMin(), item.getGreenMax(),
|
||||
item.getYellowMin(), item.getYellowMax());
|
||||
|
||||
String displayName = StringUtils.isNotEmpty(item.getModelName()) ? item.getModelName() : item.getIdentifier();
|
||||
String text = displayName + "=" + value + (StringUtils.isNotEmpty(item.getUnit()) ? item.getUnit() : "");
|
||||
levelTextMap.get(level).add(text);
|
||||
}
|
||||
|
||||
// 对每个非空等级发一条消息
|
||||
for (Map.Entry<DataLevelEnum, List<String>> entry : levelTextMap.entrySet()) {
|
||||
if (CollectionUtils.isEmpty(entry.getValue())) {
|
||||
continue;
|
||||
}
|
||||
DataLevelEnum level = entry.getKey();
|
||||
String dataText = String.join(";", entry.getValue());
|
||||
sendTemplateMsg(config, serialNumber, level, dataText, pushUrl, openIdMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送单条微信模板消息(带颜色标记)
|
||||
*/
|
||||
private void sendTemplateMsg(ThingsModelPushConfig config, String serialNumber,
|
||||
DataLevelEnum level, String dataText,
|
||||
String pushUrl, Map<Long, String> openIdMap) {
|
||||
// 微信模板格式(需在公众号后台与此对应):
|
||||
// {{first.DATA}}
|
||||
// 设备编号:{{keyword1.DATA}}
|
||||
// 数据等级:{{keyword2.DATA}}
|
||||
// 监测数据:{{keyword3.DATA}}
|
||||
// {{remark.DATA}}
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("first", new TemplateDataVo("设备数据监测通知", "#333333"));
|
||||
data.put("keyword1", new TemplateDataVo(serialNumber, "#333333"));
|
||||
data.put("keyword2", new TemplateDataVo(level.getLabel(), level.getColor()));
|
||||
data.put("keyword3", new TemplateDataVo(dataText, level.getColor()));
|
||||
data.put("remark", new TemplateDataVo("请及时关注设备状态", "#999999"));
|
||||
|
||||
for (Map.Entry<Long, String> userEntry : openIdMap.entrySet()) {
|
||||
String openId = userEntry.getValue();
|
||||
if (StringUtils.isEmpty(openId)) {
|
||||
continue;
|
||||
}
|
||||
WeChatPublicAccountPushVO vo = new WeChatPublicAccountPushVO();
|
||||
vo.setTouser(openId);
|
||||
vo.setTemplateId(config.getWxTemplateId());
|
||||
vo.setData(data);
|
||||
if (StringUtils.isNotEmpty(config.getRedirectUrl())) {
|
||||
vo.setUrl(config.getRedirectUrl());
|
||||
}
|
||||
try {
|
||||
NotifySendResponse resp = doPost(JSON.toJSONString(vo), pushUrl);
|
||||
log.info("[Push] configId={} sn={} userId={} level={} status={}",
|
||||
config.getId(), serialNumber, userEntry.getKey(), level.getLabel(), resp.getStatus());
|
||||
} catch (Exception e) {
|
||||
log.error("[Push] configId={} sn={} userId={} 推送异常: {}",
|
||||
config.getId(), serialNumber, userEntry.getKey(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NotifySendResponse doPost(String json, String url) {
|
||||
NotifySendResponse resp = new NotifySendResponse();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8"));
|
||||
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
|
||||
ResponseEntity<String> result = REST_TEMPLATE.postForEntity(url,
|
||||
new HttpEntity<>(json, headers), String.class);
|
||||
String body = result.getBody();
|
||||
resp.setResultContent(body);
|
||||
try {
|
||||
resp.setStatus("0".equals(JSONObject.parseObject(body).get("errcode").toString()) ? 1 : 0);
|
||||
} catch (Exception e) {
|
||||
resp.setStatus(0);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定推送涉及的设备编号列表
|
||||
* device_id=0 → 产品级,需查产品下所有设备
|
||||
* device_id>0 → 指定设备
|
||||
*/
|
||||
private List<String> resolveSerialNumbers(ThingsModelPushConfig config) {
|
||||
if (config.getDeviceId() != null && config.getDeviceId() > 0) {
|
||||
if (StringUtils.isNotEmpty(config.getSerialNumber())) {
|
||||
return Collections.singletonList(config.getSerialNumber().toUpperCase());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 产品级:从 Redis 中找该产品所有已有 TSLV 缓存的设备(通过前缀扫描)
|
||||
String pattern = FastBeeConstant.REDIS.DEVICE_PRE_KEY + config.getProductId() + "_*";
|
||||
Collection<String> keys = redisCache.keys(pattern);
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String prefix = FastBeeConstant.REDIS.DEVICE_PRE_KEY + config.getProductId() + "_";
|
||||
return keys.stream()
|
||||
.map(k -> k.substring(prefix.length()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.fastbee.iot.data.push;
|
||||
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import com.fastbee.iot.service.IThingsModelPushConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型数据定时推送调度器
|
||||
* 每分钟检查所有启用的推送配置,按照 push_interval 决定是否触发本次推送。
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ThingsModelPushJob {
|
||||
|
||||
@Resource
|
||||
private IThingsModelPushConfigService pushConfigService;
|
||||
@Resource
|
||||
private ThingsModelPushHandler pushHandler;
|
||||
|
||||
/**
|
||||
* 每分钟执行一次,检查并触发到期的推送任务
|
||||
*/
|
||||
@Scheduled(fixedDelay = 60_000)
|
||||
public void execute() {
|
||||
List<ThingsModelPushConfig> configs = pushConfigService.selectAllEnabled();
|
||||
if (CollectionUtils.isEmpty(configs)) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
for (ThingsModelPushConfig config : configs) {
|
||||
try {
|
||||
if (isDue(config, now)) {
|
||||
log.info("[PushJob] 触发推送 configId={} configName={}", config.getId(), config.getConfigName());
|
||||
pushHandler.doPush(config);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[PushJob] 推送异常 configId={}: {}", config.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断配置是否到达推送时间
|
||||
* push_interval=0 时不自动推送(仅手动)
|
||||
*/
|
||||
private boolean isDue(ThingsModelPushConfig config, long nowMs) {
|
||||
if (config.getPushInterval() == null || config.getPushInterval() <= 0) {
|
||||
return false;
|
||||
}
|
||||
Date lastPush = config.getLastPushTime();
|
||||
if (lastPush == null) {
|
||||
// 从未推送过,立即触发
|
||||
return true;
|
||||
}
|
||||
long intervalMs = (long) config.getPushInterval() * 60 * 1000;
|
||||
return (nowMs - lastPush.getTime()) >= intervalMs;
|
||||
}
|
||||
}
|
||||
@ -71,6 +71,7 @@ public class WeChatPushServiceImpl implements WeChatPushService {
|
||||
break;
|
||||
case WECHAT_PUBLIC_ACCOUNT:
|
||||
notifySendResponse = this.weChatPublicAccountSend(notifyVO);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,19 +1,27 @@
|
||||
package com.fastbee.notify.core.wechat.vo;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TemplateDataVo {
|
||||
|
||||
/*微信文档中要求的格式 "data": { "name01": {"value": "某某"},"thing01": {"value": "广州至北京"
|
||||
} ,"date01": {"value": "2018-01-01"}
|
||||
}*/
|
||||
/*微信文档中要求的格式 "data": { "name01": {"value": "某某","color":"#173177"},"thing01": {"value": "广州至北京"} }*/
|
||||
@JSONField(name = "value")
|
||||
private String value;
|
||||
|
||||
/** 字体颜色,格式 #RRGGBB,为 null 时不序列化(微信默认色) */
|
||||
@JSONField(name = "color")
|
||||
private String color;
|
||||
|
||||
public TemplateDataVo(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public TemplateDataVo(String value, String color) {
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
package com.fastbee.controller.push;
|
||||
|
||||
import com.fastbee.common.annotation.Log;
|
||||
import com.fastbee.common.core.controller.BaseController;
|
||||
import com.fastbee.common.core.domain.AjaxResult;
|
||||
import com.fastbee.common.core.page.TableDataInfo;
|
||||
import com.fastbee.common.enums.BusinessType;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.iot.data.push.ThingsModelPushHandler;
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import com.fastbee.iot.service.IThingsModelPushConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型数据推送配置 Controller
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Api(tags = "物模型数据推送配置")
|
||||
@RestController
|
||||
@RequestMapping("/iot/push/config")
|
||||
public class ThingsModelPushController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private IThingsModelPushConfigService pushConfigService;
|
||||
@Resource
|
||||
private ThingsModelPushHandler pushHandler;
|
||||
|
||||
/** 查询列表 */
|
||||
@ApiOperation("查询推送配置列表")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(ThingsModelPushConfig query) {
|
||||
startPage();
|
||||
List<ThingsModelPushConfig> list = pushConfigService.selectList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 获取详情 */
|
||||
@ApiOperation("获取推送配置详情")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:query')")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getById(@PathVariable Long id) {
|
||||
return AjaxResult.success(pushConfigService.selectById(id));
|
||||
}
|
||||
|
||||
/** 新增 */
|
||||
@ApiOperation("新增推送配置")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:add')")
|
||||
@Log(title = "推送配置", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody @Validated ThingsModelPushConfig config) {
|
||||
config.setCreateBy(SecurityUtils.getUsername());
|
||||
return toAjax(pushConfigService.insertConfig(config));
|
||||
}
|
||||
|
||||
/** 修改 */
|
||||
@ApiOperation("修改推送配置")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:edit')")
|
||||
@Log(title = "推送配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody ThingsModelPushConfig config) {
|
||||
config.setUpdateBy(SecurityUtils.getUsername());
|
||||
return toAjax(pushConfigService.updateConfig(config));
|
||||
}
|
||||
|
||||
/** 启用/停用 */
|
||||
@ApiOperation("修改推送配置状态")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:edit')")
|
||||
@Log(title = "推送配置状态", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/status")
|
||||
public AjaxResult changeStatus(@RequestBody ThingsModelPushConfig config) {
|
||||
ThingsModelPushConfig update = new ThingsModelPushConfig();
|
||||
update.setId(config.getId());
|
||||
update.setStatus(config.getStatus());
|
||||
update.setUpdateBy(SecurityUtils.getUsername());
|
||||
return toAjax(pushConfigService.updateConfig(update));
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
@ApiOperation("删除推送配置")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:remove')")
|
||||
@Log(title = "推送配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(pushConfigService.deleteByIds(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动立即推送最新数据
|
||||
*/
|
||||
@ApiOperation("手动立即推送最新设备数据")
|
||||
@PreAuthorize("@ss.hasPermi('iot:push:push')")
|
||||
@Log(title = "手动推送", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/manual/{id}")
|
||||
public AjaxResult manualPush(@PathVariable Long id) {
|
||||
ThingsModelPushConfig config = pushConfigService.selectById(id);
|
||||
if (config == null) {
|
||||
return AjaxResult.error("推送配置不存在");
|
||||
}
|
||||
if (config.getStatus() == null || config.getStatus() == 0) {
|
||||
return AjaxResult.error("推送配置已停用,请先启用");
|
||||
}
|
||||
pushHandler.doPush(config);
|
||||
return AjaxResult.success("推送已触发");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.fastbee.iot.domain;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 推送物模型项及等级阈值配置 iot_push_model_item
|
||||
*
|
||||
* @author sumuxi
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PushModelItem", description = "推送物模型项及等级阈值配置")
|
||||
public class PushModelItem {
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("推送配置ID")
|
||||
private Long configId;
|
||||
|
||||
@ApiModelProperty("物模型ID")
|
||||
private Long modelId;
|
||||
|
||||
@ApiModelProperty("物模型标识符")
|
||||
private String identifier;
|
||||
|
||||
@ApiModelProperty("物模型名称")
|
||||
private String modelName;
|
||||
|
||||
@ApiModelProperty("单位")
|
||||
private String unit;
|
||||
|
||||
@ApiModelProperty("绿色(正常)下限,null=不限")
|
||||
private String greenMin;
|
||||
|
||||
@ApiModelProperty("绿色(正常)上限,null=不限")
|
||||
private String greenMax;
|
||||
|
||||
@ApiModelProperty("黄色(告警)下限,null=不限")
|
||||
private String yellowMin;
|
||||
|
||||
@ApiModelProperty("黄色(告警)上限,null=不限")
|
||||
private String yellowMax;
|
||||
|
||||
@ApiModelProperty("排列顺序")
|
||||
private Integer sortOrder;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.fastbee.iot.domain;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 推送接收用户 iot_push_user
|
||||
*
|
||||
* @author sumuxi
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "PushUser", description = "推送接收用户")
|
||||
public class PushUser {
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("推送配置ID")
|
||||
private Long configId;
|
||||
|
||||
@ApiModelProperty("用户ID")
|
||||
private Long userId;
|
||||
|
||||
@ApiModelProperty("用户名")
|
||||
private String userName;
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package com.fastbee.iot.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fastbee.common.core.domain.BaseEntity;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型数据推送配置 iot_things_model_push_config
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel(value = "ThingsModelPushConfig", description = "物模型数据推送配置")
|
||||
public class ThingsModelPushConfig extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("配置名称")
|
||||
private String configName;
|
||||
|
||||
@ApiModelProperty("产品ID")
|
||||
private Long productId;
|
||||
|
||||
@ApiModelProperty("产品名称(非持久化字段)")
|
||||
private String productName;
|
||||
|
||||
@ApiModelProperty("设备ID,0=产品级")
|
||||
private Long deviceId;
|
||||
|
||||
@ApiModelProperty("设备编号")
|
||||
private String serialNumber;
|
||||
|
||||
@ApiModelProperty("通知渠道ID")
|
||||
private Long channelId;
|
||||
|
||||
@ApiModelProperty("通知渠道名称(非持久化字段)")
|
||||
private String channelName;
|
||||
|
||||
@ApiModelProperty("微信公众号模板ID")
|
||||
private String wxTemplateId;
|
||||
|
||||
@ApiModelProperty("消息点击跳转URL")
|
||||
private String redirectUrl;
|
||||
|
||||
@ApiModelProperty("推送间隔(分钟),0=仅手动推送")
|
||||
private Integer pushInterval;
|
||||
|
||||
@ApiModelProperty("最近推送时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastPushTime;
|
||||
|
||||
@ApiModelProperty("状态:0=停用 1=启用")
|
||||
private Integer status;
|
||||
|
||||
@ApiModelProperty("租户ID")
|
||||
private Long tenantId;
|
||||
|
||||
@ApiModelProperty("租户名称")
|
||||
private String tenantName;
|
||||
|
||||
/** 物模型项列表(非持久化,前端用) */
|
||||
private List<PushModelItem> items;
|
||||
|
||||
/** 接收用户列表(非持久化,前端用) */
|
||||
private List<Long> userIds;
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.fastbee.iot.mapper;
|
||||
|
||||
import com.fastbee.iot.domain.PushModelItem;
|
||||
import com.fastbee.iot.domain.PushUser;
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型数据推送配置 Mapper
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Repository
|
||||
public interface ThingsModelPushConfigMapper {
|
||||
|
||||
ThingsModelPushConfig selectById(Long id);
|
||||
|
||||
List<ThingsModelPushConfig> selectList(ThingsModelPushConfig query);
|
||||
|
||||
/** 查询所有启用的推送配置(含物模型项和接收用户) */
|
||||
List<ThingsModelPushConfig> selectAllEnabled();
|
||||
|
||||
/** 按产品ID+设备ID查询启用配置 */
|
||||
List<ThingsModelPushConfig> selectEnabledByProductAndDevice(@Param("productId") Long productId,
|
||||
@Param("deviceId") Long deviceId);
|
||||
|
||||
int insert(ThingsModelPushConfig config);
|
||||
|
||||
int update(ThingsModelPushConfig config);
|
||||
|
||||
int deleteById(Long id);
|
||||
|
||||
int deleteByIds(Long[] ids);
|
||||
|
||||
/** 更新最近推送时间 */
|
||||
int updateLastPushTime(@Param("id") Long id, @Param("lastPushTime") Date lastPushTime);
|
||||
|
||||
// ---- 物模型项 ----
|
||||
|
||||
List<PushModelItem> selectItemsByConfigId(Long configId);
|
||||
|
||||
int insertItems(List<PushModelItem> items);
|
||||
|
||||
int deleteItemsByConfigId(Long configId);
|
||||
|
||||
// ---- 接收用户 ----
|
||||
|
||||
List<PushUser> selectUsersByConfigId(Long configId);
|
||||
|
||||
int insertUsers(List<PushUser> users);
|
||||
|
||||
int deleteUsersByConfigId(Long configId);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.fastbee.iot.service;
|
||||
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 物模型数据推送配置 Service 接口
|
||||
*
|
||||
* @author sumuxi
|
||||
*/
|
||||
public interface IThingsModelPushConfigService {
|
||||
|
||||
ThingsModelPushConfig selectById(Long id);
|
||||
|
||||
List<ThingsModelPushConfig> selectList(ThingsModelPushConfig query);
|
||||
|
||||
/** 查询所有启用且设置了推送间隔的配置(用于定时任务) */
|
||||
List<ThingsModelPushConfig> selectAllEnabled();
|
||||
|
||||
/** 按产品/设备查询启用配置 */
|
||||
List<ThingsModelPushConfig> selectEnabledByProductAndDevice(Long productId, Long deviceId);
|
||||
|
||||
int insertConfig(ThingsModelPushConfig config);
|
||||
|
||||
int updateConfig(ThingsModelPushConfig config);
|
||||
|
||||
int deleteByIds(Long[] ids);
|
||||
|
||||
/** 更新最近推送时间 */
|
||||
void updateLastPushTime(Long id);
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
package com.fastbee.iot.service.impl;
|
||||
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.iot.domain.PushModelItem;
|
||||
import com.fastbee.iot.domain.PushUser;
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import com.fastbee.iot.mapper.ThingsModelPushConfigMapper;
|
||||
import com.fastbee.iot.service.IThingsModelPushConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 物模型数据推送配置 Service 实现
|
||||
*
|
||||
* @author fastbee
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ThingsModelPushConfigServiceImpl implements IThingsModelPushConfigService {
|
||||
|
||||
@Resource
|
||||
private ThingsModelPushConfigMapper mapper;
|
||||
|
||||
@Override
|
||||
public ThingsModelPushConfig selectById(Long id) {
|
||||
return mapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThingsModelPushConfig> selectList(ThingsModelPushConfig query) {
|
||||
return mapper.selectList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThingsModelPushConfig> selectAllEnabled() {
|
||||
return mapper.selectAllEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThingsModelPushConfig> selectEnabledByProductAndDevice(Long productId, Long deviceId) {
|
||||
return mapper.selectEnabledByProductAndDevice(productId, deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int insertConfig(ThingsModelPushConfig config) {
|
||||
config.setCreateTime(DateUtils.getNowDate());
|
||||
config.setStatus(config.getStatus() == null ? 1 : config.getStatus());
|
||||
config.setDeviceId(config.getDeviceId() == null ? 0L : config.getDeviceId());
|
||||
config.setPushInterval(config.getPushInterval() == null ? 60 : config.getPushInterval());
|
||||
int rows = mapper.insert(config);
|
||||
saveItemsAndUsers(config);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateConfig(ThingsModelPushConfig config) {
|
||||
config.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = mapper.update(config);
|
||||
mapper.deleteItemsByConfigId(config.getId());
|
||||
mapper.deleteUsersByConfigId(config.getId());
|
||||
saveItemsAndUsers(config);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteByIds(Long[] ids) {
|
||||
for (Long id : ids) {
|
||||
mapper.deleteItemsByConfigId(id);
|
||||
mapper.deleteUsersByConfigId(id);
|
||||
}
|
||||
return mapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLastPushTime(Long id) {
|
||||
mapper.updateLastPushTime(id, new Date());
|
||||
}
|
||||
|
||||
private void saveItemsAndUsers(ThingsModelPushConfig config) {
|
||||
if (CollectionUtils.isNotEmpty(config.getItems())) {
|
||||
List<PushModelItem> items = config.getItems();
|
||||
items.forEach(item -> {
|
||||
item.setConfigId(config.getId());
|
||||
if (item.getSortOrder() == null) {
|
||||
item.setSortOrder(0);
|
||||
}
|
||||
});
|
||||
mapper.insertItems(items);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(config.getUserIds())) {
|
||||
List<PushUser> users = config.getUserIds().stream().map(uid -> {
|
||||
PushUser u = new PushUser();
|
||||
u.setConfigId(config.getId());
|
||||
u.setUserId(uid);
|
||||
return u;
|
||||
}).collect(Collectors.toList());
|
||||
mapper.insertUsers(users);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.fastbee.iot.mapper.ThingsModelPushConfigMapper">
|
||||
|
||||
<resultMap id="ConfigResult" type="com.fastbee.iot.domain.ThingsModelPushConfig">
|
||||
<id property="id" column="id"/>
|
||||
<result property="configName" column="config_name"/>
|
||||
<result property="productId" column="product_id"/>
|
||||
<result property="productName" column="product_name"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="serialNumber" column="serial_number"/>
|
||||
<result property="channelId" column="channel_id"/>
|
||||
<result property="channelName" column="channel_name"/>
|
||||
<result property="wxTemplateId" column="wx_template_id"/>
|
||||
<result property="redirectUrl" column="redirect_url"/>
|
||||
<result property="pushInterval" column="push_interval"/>
|
||||
<result property="lastPushTime" column="last_push_time"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="tenantName" column="tenant_name"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<collection property="items" ofType="com.fastbee.iot.domain.PushModelItem"
|
||||
select="selectItemsByConfigId" column="id"/>
|
||||
<collection property="userIds" ofType="java.lang.Long"
|
||||
select="selectUserIdsByConfigId" column="id"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="ItemResult" type="com.fastbee.iot.domain.PushModelItem">
|
||||
<id property="id" column="id"/>
|
||||
<result property="configId" column="config_id"/>
|
||||
<result property="modelId" column="model_id"/>
|
||||
<result property="identifier" column="identifier"/>
|
||||
<result property="modelName" column="model_name"/>
|
||||
<result property="unit" column="unit"/>
|
||||
<result property="greenMin" column="green_min"/>
|
||||
<result property="greenMax" column="green_max"/>
|
||||
<result property="yellowMin" column="yellow_min"/>
|
||||
<result property="yellowMax" column="yellow_max"/>
|
||||
<result property="sortOrder" column="sort_order"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="UserResult" type="com.fastbee.iot.domain.PushUser">
|
||||
<id property="id" column="id"/>
|
||||
<result property="configId" column="config_id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="baseColumns">
|
||||
c.id, c.config_name, c.product_id, c.device_id, c.serial_number,
|
||||
c.channel_id, c.wx_template_id, c.redirect_url,
|
||||
c.push_interval, c.last_push_time, c.status,
|
||||
c.tenant_id, c.tenant_name,
|
||||
c.create_by, c.create_time, c.update_by, c.update_time,
|
||||
p.product_name, nc.name AS channel_name
|
||||
</sql>
|
||||
|
||||
<select id="selectById" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
WHERE c.id = #{id} AND c.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectList" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
WHERE c.del_flag = '0'
|
||||
<if test="configName != null and configName != ''">
|
||||
AND c.config_name LIKE CONCAT('%', #{configName}, '%')
|
||||
</if>
|
||||
<if test="productId != null">
|
||||
AND c.product_id = #{productId}
|
||||
</if>
|
||||
<if test="deviceId != null">
|
||||
AND c.device_id = #{deviceId}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND c.status = #{status}
|
||||
</if>
|
||||
<if test="tenantId != null">
|
||||
AND c.tenant_id = #{tenantId}
|
||||
</if>
|
||||
ORDER BY c.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectAllEnabled" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
WHERE c.del_flag = '0' AND c.status = 1 AND c.push_interval > 0
|
||||
</select>
|
||||
|
||||
<select id="selectEnabledByProductAndDevice" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
WHERE c.del_flag = '0' AND c.status = 1
|
||||
AND c.product_id = #{productId}
|
||||
AND (c.device_id = 0 OR 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, channel_id,
|
||||
wx_template_id, redirect_url, push_interval, status,
|
||||
tenant_id, tenant_name, create_by, create_time, del_flag)
|
||||
VALUES
|
||||
(#{configName}, #{productId}, #{deviceId}, #{serialNumber}, #{channelId},
|
||||
#{wxTemplateId}, #{redirectUrl}, #{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="channelId != null">channel_id = #{channelId},</if>
|
||||
<if test="wxTemplateId != null">wx_template_id = #{wxTemplateId},</if>
|
||||
<if test="redirectUrl != null">redirect_url = #{redirectUrl},</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}
|
||||
</update>
|
||||
|
||||
<update id="deleteById">
|
||||
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
|
||||
<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>
|
||||
|
||||
<!-- 物模型项 -->
|
||||
<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>
|
||||
|
||||
<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)
|
||||
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})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteItemsByConfigId">
|
||||
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>
|
||||
|
||||
<select id="selectUserIdsByConfigId" resultType="java.lang.Long">
|
||||
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)
|
||||
VALUES
|
||||
<foreach collection="list" item="u" separator=",">
|
||||
(#{u.configId}, #{u.userId}, #{u.userName})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteUsersByConfigId">
|
||||
DELETE FROM iot_push_user WHERE config_id = #{configId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
80
sql/iot_things_model_push.sql
Normal file
80
sql/iot_things_model_push.sql
Normal file
@ -0,0 +1,80 @@
|
||||
-- ============================================================
|
||||
-- 物模型数据推送配置 (微信服务号模板消息)
|
||||
-- 执行顺序:在主库 fastbee 中执行
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `iot_things_model_push_config` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`config_name` VARCHAR(128) NOT NULL COMMENT '配置名称',
|
||||
`product_id` BIGINT NOT NULL COMMENT '产品ID',
|
||||
`device_id` BIGINT DEFAULT 0 COMMENT '设备ID,0=产品级(产品下所有设备)',
|
||||
`serial_number` VARCHAR(64) DEFAULT NULL COMMENT '设备编号(device_id>0时有效)',
|
||||
`channel_id` BIGINT NOT NULL COMMENT '通知渠道ID (notify_channel.id,类型为 public_account)',
|
||||
`wx_template_id` VARCHAR(128) NOT NULL COMMENT '微信公众号模板ID(在微信公众平台配置)',
|
||||
`redirect_url` VARCHAR(512) DEFAULT NULL COMMENT '消息点击跳转URL',
|
||||
`push_interval` INT DEFAULT 60 COMMENT '推送间隔(分钟),0=仅手动推送',
|
||||
`last_push_time` DATETIME DEFAULT NULL COMMENT '最近一次推送时间',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0=停用 1=启用',
|
||||
`tenant_id` BIGINT DEFAULT NULL COMMENT '租户ID',
|
||||
`tenant_name` VARCHAR(64) DEFAULT NULL COMMENT '租户名称',
|
||||
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_product_id` (`product_id`),
|
||||
KEY `idx_device_id` (`device_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物模型数据推送配置';
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `iot_push_model_item` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`config_id` BIGINT NOT NULL COMMENT '推送配置ID',
|
||||
`model_id` BIGINT DEFAULT NULL COMMENT '物模型ID(iot_things_model.model_id)',
|
||||
`identifier` VARCHAR(128) NOT NULL COMMENT '物模型标识符',
|
||||
`model_name` VARCHAR(128) DEFAULT NULL COMMENT '物模型名称(冗余展示用)',
|
||||
`unit` VARCHAR(32) DEFAULT NULL COMMENT '单位(冗余展示用)',
|
||||
`green_min` VARCHAR(64) DEFAULT NULL COMMENT '绿色(正常)下限,NULL表示不限',
|
||||
`green_max` VARCHAR(64) DEFAULT NULL COMMENT '绿色(正常)上限,NULL表示不限',
|
||||
`yellow_min` VARCHAR(64) DEFAULT NULL COMMENT '黄色(告警)下限,NULL表示不限',
|
||||
`yellow_max` VARCHAR(64) DEFAULT NULL COMMENT '黄色(告警)上限,NULL表示不限',
|
||||
`sort_order` INT DEFAULT 0 COMMENT '排列顺序',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_config_id` (`config_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推送物模型项及等级阈值配置';
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `iot_push_user` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`config_id` BIGINT NOT NULL COMMENT '推送配置ID',
|
||||
`user_id` BIGINT NOT NULL COMMENT '系统用户ID(需已绑定微信公众号)',
|
||||
`user_name` VARCHAR(64) DEFAULT NULL COMMENT '用户名(冗余展示用)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_config_id` (`config_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推送接收用户';
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- sys_menu 菜单权限(挂在"规则引擎"父菜单 parent_id=3051 下)
|
||||
-- ============================================================
|
||||
|
||||
-- 主菜单:数据推送配置
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3430, '数据推送配置', 3051, 9, 'pushConfig', 'iot/push/index', NULL, 1, 0, 'C', '0', '0', 'iot:push:list', 'notification', 'admin', now(), 'admin', now(), '微信服务号物模型数据推送');
|
||||
|
||||
-- 按钮权限
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3431, '推送配置查询', 3430, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:query', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3432, '推送配置新增', 3430, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:add', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3433, '推送配置修改', 3430, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:edit', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3434, '推送配置删除', 3430, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:remove', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3435, '手动立即推送', 3430, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:push', '#', 'admin', now(), 'admin', now(), '');
|
||||
38
yack-web/src/api/iot/pushConfig.js
Normal file
38
yack-web/src/api/iot/pushConfig.js
Normal file
@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
const BASE = '/iot/push/config';
|
||||
|
||||
// 查询推送配置列表
|
||||
export function listPushConfig(query) {
|
||||
return request({ url: `${BASE}/list`, method: 'get', params: query });
|
||||
}
|
||||
|
||||
// 获取推送配置详情
|
||||
export function getPushConfig(id) {
|
||||
return request({ url: `${BASE}/${id}`, method: 'get' });
|
||||
}
|
||||
|
||||
// 新增推送配置
|
||||
export function addPushConfig(data) {
|
||||
return request({ url: BASE, method: 'post', data });
|
||||
}
|
||||
|
||||
// 修改推送配置
|
||||
export function updatePushConfig(data) {
|
||||
return request({ url: BASE, method: 'put', data });
|
||||
}
|
||||
|
||||
// 修改状态
|
||||
export function changePushConfigStatus(id, status) {
|
||||
return request({ url: `${BASE}/status`, method: 'put', data: { id, status } });
|
||||
}
|
||||
|
||||
// 删除推送配置
|
||||
export function delPushConfig(ids) {
|
||||
return request({ url: `${BASE}/${ids}`, method: 'delete' });
|
||||
}
|
||||
|
||||
// 手动立即推送最新数据
|
||||
export function manualPush(id) {
|
||||
return request({ url: `${BASE}/manual/${id}`, method: 'post' });
|
||||
}
|
||||
384
yack-web/src/views/iot/push/index.vue
Normal file
384
yack-web/src/views/iot/push/index.vue
Normal file
@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<div class="iot-push-config">
|
||||
<!-- 搜索栏 -->
|
||||
<el-card v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-form @submit.native.prevent :model="queryParams" ref="queryForm" :inline="true" size="mini" label-width="80px" style="margin-bottom: -18px">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<el-select v-model="queryParams.productId" placeholder="请选择产品" clearable filterable style="width:160px">
|
||||
<el-option v-for="p in productOptions" :key="p.productId" :label="p.productName" :value="p.productId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100px">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="float: right">
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<el-row :gutter="10" style="margin-bottom: 15px">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['iot:push:add']">新增</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="configList" border>
|
||||
<el-table-column label="配置名称" align="center" prop="configName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="产品" align="center" prop="productName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="通知渠道" align="center" prop="channelName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="推送间隔" align="center" width="110">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.pushInterval > 0">每 {{ scope.row.pushInterval }} 分钟</span>
|
||||
<el-tag v-else size="small" type="info">仅手动</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近推送" align="center" prop="lastPushTime" width="155">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.lastPushTime || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0"
|
||||
@change="handleStatusChange(scope.row)" v-hasPermi="['iot:push:edit']" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['iot:push:edit']">编辑</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-video-play" style="color:#67C23A"
|
||||
@click="handleManualPush(scope.row)" v-hasPermi="['iot:push:push']">立即推送</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#F56C6C"
|
||||
@click="handleDelete(scope.row)" v-hasPermi="['iot:push:remove']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="900px" append-to-body
|
||||
:close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="form.configName" placeholder="请输入配置名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<el-select v-model="form.productId" placeholder="请选择产品" filterable style="width:100%"
|
||||
@change="onProductChange">
|
||||
<el-option v-for="p in productOptions" :key="p.productId" :label="p.productName" :value="p.productId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="通知渠道" prop="channelId">
|
||||
<el-select v-model="form.channelId" placeholder="请选择公众号渠道" filterable style="width:100%">
|
||||
<el-option v-for="c in channelOptions" :key="c.id" :label="c.name" :value="c.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信模板ID" prop="wxTemplateId">
|
||||
<el-input v-model="form.wxTemplateId" placeholder="在微信公众号后台获取模板ID" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="推送间隔(分)" prop="pushInterval">
|
||||
<el-input-number v-model="form.pushInterval" :min="0" :max="1440" style="width:100%"
|
||||
placeholder="0=仅手动推送" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="跳转URL">
|
||||
<el-input v-model="form.redirectUrl" placeholder="消息点击跳转链接(选填)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 物模型选择与阈值配置 -->
|
||||
<el-divider content-position="left">监测物模型及数据等级阈值</el-divider>
|
||||
<div style="margin-bottom:10px;color:#606266;font-size:12px">
|
||||
绿色(✅ 正常):值在 [绿下限, 绿上限] 范围内;
|
||||
黄色(⚠️ 告警):值在 [黄下限, 黄上限] 范围内;
|
||||
红色(🔴 警报):其余情况。留空表示不限。
|
||||
</div>
|
||||
<el-table :data="form.items" size="mini" border>
|
||||
<el-table-column label="物模型" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.identifier" placeholder="选择物模型属性" filterable
|
||||
size="mini" @change="(v) => onModelItemChange(v, scope.row)">
|
||||
<el-option v-for="m in thingsModelOptions" :key="m.identifier"
|
||||
:label="m.modelName + ' (' + m.identifier + ')'" :value="m.identifier" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="70">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.unit" size="mini" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绿色下限" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.greenMin" size="mini" placeholder="不限" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绿色上限" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.greenMax" size="mini" placeholder="不限" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="黄色下限" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.yellowMin" size="mini" placeholder="不限" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="黄色上限" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.yellowMax" size="mini" placeholder="不限" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" icon="el-icon-delete" style="color:#F56C6C"
|
||||
size="mini" @click="removeModelItem(scope.$index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-button size="mini" icon="el-icon-plus" style="margin-top:8px" @click="addModelItem">添加物模型</el-button>
|
||||
|
||||
<!-- 接收用户 -->
|
||||
<el-divider content-position="left">接收用户(需已绑定微信公众号)</el-divider>
|
||||
<el-select v-model="form.userIds" multiple filterable placeholder="请选择接收用户"
|
||||
style="width:100%" size="small">
|
||||
<el-option v-for="u in userOptions" :key="u.userId" :value="u.userId"
|
||||
:label="u.nickName + ' (' + u.userName + ')'" />
|
||||
</el-select>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogOpen = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listPushConfig, getPushConfig, addPushConfig, updatePushConfig,
|
||||
changePushConfigStatus, delPushConfig, manualPush
|
||||
} from '@/api/iot/pushConfig';
|
||||
import { listProduct } from '@/api/iot/product';
|
||||
import { listModel } from '@/api/iot/model';
|
||||
import { listChannel } from '@/api/notify/channel';
|
||||
import { listUser } from '@/api/system/user';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
configName: '',
|
||||
productId: null,
|
||||
deviceId: 0,
|
||||
channelId: null,
|
||||
wxTemplateId: '',
|
||||
redirectUrl: '',
|
||||
pushInterval: 60,
|
||||
status: 1,
|
||||
items: [],
|
||||
userIds: []
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'PushConfig',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
configList: [],
|
||||
queryParams: { pageNum: 1, pageSize: 10, configName: '', productId: null, status: null },
|
||||
dialogOpen: false,
|
||||
dialogTitle: '',
|
||||
submitLoading: false,
|
||||
form: emptyForm(),
|
||||
rules: {
|
||||
configName: [{ required: true, message: '请输入配置名称', trigger: 'blur' }],
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
channelId: [{ required: true, message: '请选择通知渠道', trigger: 'change' }],
|
||||
wxTemplateId:[{ required: true, message: '请输入微信模板ID', trigger: 'blur' }],
|
||||
pushInterval:[{ required: true, message: '请填写推送间隔', trigger: 'blur' }]
|
||||
},
|
||||
productOptions: [],
|
||||
channelOptions: [],
|
||||
thingsModelOptions: [],
|
||||
userOptions: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadProductOptions();
|
||||
this.loadChannelOptions();
|
||||
this.loadUserOptions();
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listPushConfig(this.queryParams).then(res => {
|
||||
this.configList = res.rows;
|
||||
this.total = res.total;
|
||||
}).finally(() => { this.loading = false; });
|
||||
},
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm');
|
||||
this.queryParams = { pageNum: 1, pageSize: 10, configName: '', productId: null, status: null };
|
||||
this.getList();
|
||||
},
|
||||
loadProductOptions() {
|
||||
listProduct({ pageNum: 1, pageSize: 999 }).then(res => {
|
||||
this.productOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
loadChannelOptions() {
|
||||
// 只加载微信公众号渠道
|
||||
listChannel({ channelType: 'wechat', provider: 'public_account', pageNum: 1, pageSize: 100 }).then(res => {
|
||||
this.channelOptions = res.rows || [];
|
||||
}).catch(() => {
|
||||
listChannel({ pageNum: 1, pageSize: 100 }).then(res => {
|
||||
this.channelOptions = (res.rows || []).filter(c => c.provider === 'public_account');
|
||||
});
|
||||
});
|
||||
},
|
||||
loadUserOptions() {
|
||||
listUser({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.userOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
onProductChange(productId) {
|
||||
this.thingsModelOptions = [];
|
||||
this.form.items = [];
|
||||
if (!productId) return;
|
||||
listModel({ productId, type: 1, pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.thingsModelOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
onModelItemChange(identifier, row) {
|
||||
const model = this.thingsModelOptions.find(m => m.identifier === identifier);
|
||||
if (model) {
|
||||
row.modelId = model.modelId;
|
||||
row.modelName = model.modelName;
|
||||
row.unit = model.unit || '';
|
||||
}
|
||||
},
|
||||
addModelItem() {
|
||||
this.form.items.push({
|
||||
id: null, configId: null, modelId: null,
|
||||
identifier: '', modelName: '', unit: '',
|
||||
greenMin: null, greenMax: null,
|
||||
yellowMin: null, yellowMax: null,
|
||||
sortOrder: this.form.items.length
|
||||
});
|
||||
},
|
||||
removeModelItem(index) {
|
||||
this.form.items.splice(index, 1);
|
||||
},
|
||||
handleAdd() {
|
||||
this.form = emptyForm();
|
||||
this.thingsModelOptions = [];
|
||||
this.dialogTitle = '新增推送配置';
|
||||
this.dialogOpen = true;
|
||||
this.$nextTick(() => this.resetForm('form'));
|
||||
},
|
||||
handleUpdate(row) {
|
||||
getPushConfig(row.id).then(res => {
|
||||
const data = res.data;
|
||||
this.form = {
|
||||
...data,
|
||||
userIds: data.userIds || [],
|
||||
items: data.items || []
|
||||
};
|
||||
if (data.productId) {
|
||||
listModel({ productId: data.productId, type: 1, pageNum: 1, pageSize: 500 }).then(r => {
|
||||
this.thingsModelOptions = r.rows || [];
|
||||
});
|
||||
}
|
||||
this.dialogTitle = '编辑推送配置';
|
||||
this.dialogOpen = true;
|
||||
});
|
||||
},
|
||||
handleStatusChange(row) {
|
||||
const text = row.status === 1 ? '启用' : '停用';
|
||||
this.$confirm(`确认${text}【${row.configName}】?`, '提示', { type: 'warning' }).then(() => {
|
||||
changePushConfigStatus(row.id, row.status).then(() => {
|
||||
this.$message.success(`${text}成功`);
|
||||
});
|
||||
}).catch(() => { row.status = row.status === 1 ? 0 : 1; });
|
||||
},
|
||||
handleManualPush(row) {
|
||||
this.$confirm(`确认立即推送【${row.configName}】的最新设备数据?`, '手动推送', { type: 'info' }).then(() => {
|
||||
manualPush(row.id).then(res => {
|
||||
this.$message.success(res.msg || '推送已触发');
|
||||
this.getList();
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除推送配置【${row.configName}】?`, '警告', { type: 'warning' }).then(() => {
|
||||
delPushConfig(row.id).then(() => {
|
||||
this.$message.success('删除成功');
|
||||
this.getList();
|
||||
});
|
||||
});
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return;
|
||||
if (this.form.items.length === 0) {
|
||||
this.$message.warning('请至少添加一个监测物模型');
|
||||
return;
|
||||
}
|
||||
const hasEmptyIdentifier = this.form.items.some(i => !i.identifier);
|
||||
if (hasEmptyIdentifier) {
|
||||
this.$message.warning('存在未选择物模型的行,请完善');
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
const api = this.form.id ? updatePushConfig : addPushConfig;
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.id ? '修改成功' : '新增成功');
|
||||
this.dialogOpen = false;
|
||||
this.getList();
|
||||
}).finally(() => { this.submitLoading = false; });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.iot-push-config .el-divider {
|
||||
margin: 15px 0 10px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user