Compare commits

39 Commits

Author SHA1 Message Date
5f36c25550 内容(报警信息) 2025-09-20 14:35:23 +08:00
9058dcfcb1 设备告警 2025-09-20 11:45:11 +08:00
3e19cd9db2 设置图片路径 2025-09-19 09:43:52 +08:00
397f227bfb Merge branch 'dyf-device' into 6170 2025-09-18 15:26:04 +08:00
1b2bf542fa 个人中心修改密码2 2025-09-18 15:25:32 +08:00
7d4bc1f8ad Merge branch 'liwenlong-jingquan'
# Conflicts:
#	fys-admin/src/main/java/com/fuyuanshen/app/controller/AppAuthController.java
#	fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceBizService.java
#	fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceAlarmMapper.xml
#	fys-modules/fys-equipment/src/main/resources/mapper/equipment/DeviceMapper.xml
2025-09-18 12:03:43 +08:00
4d9038567f 根据提供的代码差异信息,以下是符合Angular commit规范的commit message:
```
feat(app): 添加获取app版本接口及视频处理控制器

新增功能:
- 在AppAuthController中添加了获取app版本信息的接口`/version`。- 新增AppVideoController用于处理视频上传和帧提取。

修改内容:
- 在AppAuthController中引入了ISysDictTypeService服务。
- 在DeviceBizService中更新了设备通信模式的判断逻辑。
- 修改了DeviceAlarmMapper.xml和DeviceMapper.xml中的SQL查询语句以支持更多通信模式。
- 更新了DeviceXinghanBizService中的人员信息登记逻辑,并添加了获取设备详细信息的方法。
- 在DeviceXinghanController中添加了获取设备详细信息的接口`/info/{id}`。
- 更新了MqttXinghanJson类中的字段命名。
- 在pom.xml中添加了javacv相关的依赖。

修复问题:
- 注释掉了AppSmsAuthStrategy中的登录检查逻辑。
```

这个commit message包含了类型(feat)、作用范围(app)以及简短的描述。同时在body部分详细说明了新增的功能、修改的内容以及修复的问题。
2025-09-18 11:40:12 +08:00
630c6a8a35 角色管理员 2025-09-18 11:25:40 +08:00
11e48acc81 个人中心修改密码 2025-09-18 08:54:29 +08:00
540a6fc1da 密码长度验证 2025-09-17 17:43:17 +08:00
aeea8f9072 登录优化 2025-09-17 10:56:25 +08:00
b463e97d28 配置文件mqtt端口修改 2025-09-16 17:35:29 +08:00
e1b147b6be Merge branch 'dyf-device' into 6170 2025-09-16 17:05:19 +08:00
23b48cfb06 设备告警 2025-09-16 17:04:45 +08:00
4106260e5f 在线状态修改优化4 2025-09-15 17:49:28 +08:00
07449e4a1e 查询围栏进出记录列表 2025-09-15 16:49:38 +08:00
b278a3087e 报警事项 2025-09-15 14:59:08 +08:00
dca5a6c48d 在线状态修改优化3 2025-09-15 09:43:15 +08:00
15b3348989 Merge branch 'dyf-device' into 6170 2025-09-15 09:41:56 +08:00
9fbb0aefcf 检查设备位置与围栏的关系 2025-09-15 09:41:10 +08:00
4004aa1090 在线状态修改优化2 2025-09-13 16:51:56 +08:00
39c8375f08 在线状态修改优化 2025-09-13 16:46:05 +08:00
64c81ac44e 在线状态修改2 2025-09-13 15:51:34 +08:00
41eb3d9a33 在线状态修改 2025-09-13 15:37:48 +08:00
8597dc5a9f 导出围栏进出记录列表 2025-09-12 14:50:42 +08:00
01a1a6e25b 查询围栏进出记录 2025-09-12 11:48:39 +08:00
1722f92328 电子围栏-位置检查 2025-09-12 09:04:47 +08:00
ee8840d657 Merge remote-tracking branch 'origin/6170' into 6170 2025-09-11 14:43:02 +08:00
9b84296419 Merge branch 'main' into dyf-device 2025-09-11 14:41:41 +08:00
dyf
d48c7b389e Merge pull request 'jingquan' (#10) from liwenlong/fys-Multi-tenant:jingquan into main
Reviewed-on: #10
2025-09-11 14:40:23 +08:00
ccdc25595d Merge remote-tracking branch 'origin/6170' into 6170 2025-09-11 14:38:06 +08:00
f1aad91421 修改绑定1 2025-09-11 14:37:40 +08:00
e2274bdf09 feat(web): 新增设备联调中心功能
- 新增设备联调中心相关控制器、服务、DTO和VO
- 实现设备列表查询、文件上传、操作视频添加、设备详情等功能
- 优化设备 logo 上传逻辑,支持批量上传
- 重构部分代码结构,提高可维护性
2025-09-11 11:07:58 +08:00
8a127b9d2d Merge branch 'dyf-device' into 6170 2025-09-11 10:56:29 +08:00
b8af6b511c 电子围栏 2025-09-11 10:54:56 +08:00
6bc1d5b20b 绑定设备 2025-09-10 09:56:39 +08:00
228e26df7f merge upstream 2025-09-09 17:23:23 +08:00
801b0267e6 设备分享3 2025-09-09 17:01:51 +08:00
d5a9a92f9f feat(device): 新增设备控制相关接口
- 人员信息登记- 发送紧急通知
-上传设备 logo 图片
-静电预警档位设置- 照明档位设置- SOS 档位设置- 静止报警状态设置
2025-09-04 11:50:18 +08:00
108 changed files with 4559 additions and 128 deletions

View File

@ -123,6 +123,17 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.7</version>
<scope>compile</scope>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->

View File

@ -4,22 +4,33 @@ import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppSmsLoginBody;
import com.fuyuanshen.app.service.AppLoginService;
import com.fuyuanshen.app.service.AppRegisterService;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.constant.SystemConstants;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody;
import com.fuyuanshen.common.core.domain.model.RegisterBody;
import com.fuyuanshen.common.core.domain.model.SmsLoginBody;
import com.fuyuanshen.common.core.enums.LoginType;
import com.fuyuanshen.common.core.utils.*;
import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.ratelimiter.annotation.RateLimiter;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.system.domain.bo.SysTenantBo;
import com.fuyuanshen.system.domain.vo.SysClientVo;
import com.fuyuanshen.system.domain.vo.SysDictDataVo;
import com.fuyuanshen.system.domain.vo.SysTenantVo;
import com.fuyuanshen.system.service.ISysClientService;
import com.fuyuanshen.system.service.ISysConfigService;
import com.fuyuanshen.system.service.ISysDictTypeService;
import com.fuyuanshen.system.service.ISysTenantService;
import com.fuyuanshen.web.domain.vo.LoginTenantVo;
import com.fuyuanshen.web.domain.vo.LoginVo;
@ -27,6 +38,7 @@ import com.fuyuanshen.web.domain.vo.TenantListVo;
import com.fuyuanshen.web.service.IAuthStrategy;
import com.fuyuanshen.web.service.SysRegisterService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
@ -36,8 +48,13 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_CODES_KEY;
/**
* APP认证
*
@ -51,10 +68,11 @@ import java.util.List;
public class AppAuthController {
private final AppLoginService loginService;
private final SysRegisterService registerService;
private final AppRegisterService registerService;
private final ISysConfigService configService;
private final ISysTenantService tenantService;
private final ISysClientService clientService;
private final ISysDictTypeService dictTypeService;
/**
@ -66,16 +84,34 @@ public class AppAuthController {
@PostMapping("/login")
public R<LoginVo> login(@RequestBody AppSmsLoginBody appSmsLoginBody) {
// SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(appSmsLoginBody);
SmsLoginBody loginBody = new SmsLoginBody();
loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber());
loginBody.setSmsCode(appSmsLoginBody.getSmsCode());
loginBody.setTenantId(appSmsLoginBody.getTenantId());
loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8");
loginBody.setGrantType("appSms");
String loginType = appSmsLoginBody.getLoginType();
String body = "";
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
String clientId = "";
String grantType = "";
String tenantId = appSmsLoginBody.getTenantId();
ValidatorUtils.validate(appSmsLoginBody);
if("2".equals(loginType)){
AppPasswordLoginBody loginBody = new AppPasswordLoginBody();
loginBody.setUsername(appSmsLoginBody.getPhonenumber());
loginBody.setPassword(appSmsLoginBody.getLoginPassword());
loginBody.setClientId("835b15335d389c9fcfdf99421fa8019b");
loginBody.setGrantType("appPassword");
clientId = loginBody.getClientId();
grantType = loginBody.getGrantType();
body = JsonUtils.toJsonString(loginBody);
}else{
SmsLoginBody loginBody = new SmsLoginBody();
loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber());
loginBody.setSmsCode(appSmsLoginBody.getSmsCode());
loginBody.setTenantId(appSmsLoginBody.getTenantId());
loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8");
loginBody.setGrantType("appSms");
clientId = loginBody.getClientId();
grantType = loginBody.getGrantType();
body = JsonUtils.toJsonString(loginBody);
}
SysClientVo client = clientService.queryByClientId(clientId);
// 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
@ -85,25 +121,14 @@ public class AppAuthController {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
// loginService.checkTenant(loginBody.getTenantId());
loginService.checkTenant(tenantId);
// 登录
String body = JsonUtils.toJsonString(loginBody);
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
return R.ok(loginVo);
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/**
* 用户注销
*/
@ -113,16 +138,68 @@ public class AppAuthController {
return R.ok("用户注销成功");
}
/**
* 用户注册
*/
@ApiEncrypt
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) {
if (!configService.selectRegisterEnabled(user.getTenantId())) {
return R.fail("当前系统没有开启注册功能!");
public R<Void> register(@Validated @RequestBody AppRegisterBody body) {
registerService.register(body);
return R.ok();
}
/**
* 找回密码
*/
@PostMapping("/forgetPassword")
public R<Void> forgetPassword(@Validated @RequestBody AppRegisterBody body) {
registerService.forgetPassword(body);
return R.ok();
}
/**
* 用户注册短信验证码
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/registerSmsCode")
public R<Void> registerSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.REGISTER_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
/**
* 找回密码短信验证码
*
* @param phonenumber 用户手机号
*/
@SaIgnore
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/forgetPasswordSmsCode")
public R<Void> forgetPasswordSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, map);
if (!smsResponse.isSuccess()) {
return R.fail(smsResponse.getData().toString());
}
registerService.register(user);
return R.ok();
}
@ -179,4 +256,26 @@ public class AppAuthController {
SmsResponse smsResponse = smsBlend.sendMessage("18656573389", "123");
}
/**
* 获取app版本
* @return
*/
@GetMapping("/version")
public R<List<SysDictDataVo>> getAppVersion() {
List<SysDictDataVo> list = dictTypeService.selectDictDataByType("app_version");
list.forEach(d -> {
String[] arr = d.getRemark().split("\\|");
d.setDictLabel(d.getDictLabel()); // ios/android
d.setDictValue(arr[0]); // 版本号
d.setRemark(arr[1]); // 下载地址
});
// 只保留方法体:筛选 label=ios 且版本号 ≥ 2.5.0 的列表
// List<SysDictDataVo> result = list.stream()
// .peek(d -> { String[] a = d.getRemark().split("\\|"); d.setDictValue(a[0]); d.setRemark(a[1]); })
// .filter(d -> "ios".equalsIgnoreCase(d.getDictLabel()))
// .filter(d -> VersionComparator.INSTANCE.compare(d.getDictValue(), "2.5.0") >= 0)
// .toList();
return R.ok(list);
}
}

View File

@ -0,0 +1,55 @@
package com.fuyuanshen.app.controller;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppUpdatePasswordBody;
import com.fuyuanshen.app.service.AppLoginService;
import com.fuyuanshen.app.service.AppRegisterService;
import com.fuyuanshen.common.core.domain.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* APP我的
*
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/userCenter")
public class AppUserCenterController {
private final AppLoginService loginService;
private final AppRegisterService registerService;
/**
* 用户注销
*/
@PostMapping("/cancelAccount")
public R<Void> cancelAccount() {
loginService.cancelAccount();
return R.ok("用户注销成功");
}
/**
* 修改密码
*/
@PostMapping("/updatePassword")
public R<Void> updatePassword(@Validated @RequestBody AppUpdatePasswordBody body) {
registerService.updatePassword(body);
return R.ok();
}
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
}

View File

@ -0,0 +1,249 @@
package com.fuyuanshen.app.controller;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.web.core.BaseController;
import lombok.RequiredArgsConstructor;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* APP 视频处理
* @date 2025-09-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/video")
public class AppVideoController extends BaseController {
// 可配置项:建议从 application.yml 中读取
private static final int MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10 MB
private static final int FRAME_RATE = 15; // 每秒抽15帧
private static final int DURATION = 2; // 抽2秒
private static final int TOTAL_FRAMES = FRAME_RATE * DURATION;
private static final int WIDTH = 160;
private static final int HEIGHT = 80;
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<List<String>> upload(@RequestParam("file") MultipartFile file) {
if (file == null || file.isEmpty()) {
return R.fail("上传文件不能为空");
}
if (!isVideo(file.getOriginalFilename())) {
return R.fail("只允许上传视频文件");
}
if (file.getSize() > MAX_VIDEO_SIZE) {
return R.fail("视频大小不能超过10MB");
}
File tempFile = null;
try {
// 创建临时文件保存上传的视频
tempFile = createTempVideoFile(file);
List<BufferedImage> frames = extractFramesFromVideo(tempFile);
if (frames.isEmpty()) {
return R.fail("无法提取任何帧");
}
// ✅ 新增:保存帧为图片
//saveFramesToLocal(frames, "extracted_frame");
byte[] binaryData = convertFramesToRGB565(frames);
// String base64Data = Base64.getEncoder().encodeToString(binaryData);
//
// return R.ok(base64Data);
// 构造响应头
// 将二进制数据转为 Hex 字符串
// 转换为 Hex 字符串列表
List<String> hexList = bytesToHexList(binaryData);
return R.ok(hexList);
} catch (Exception e) {
return R.fail("视频处理失败:" + e.getMessage());
} finally {
deleteTempFile(tempFile);
}
}
/**
* rgb565 转 hex
*/
private List<String> bytesToHexList(byte[] bytes) {
List<String> hexList = new ArrayList<>();
for (byte b : bytes) {
int value = b & 0xFF;
char high = HEX_ARRAY[value >>> 4];
char low = HEX_ARRAY[value & 0x0F];
hexList.add(String.valueOf(high) + low);
}
return hexList;
}
/**
* 创建临时文件并保存上传的视频
*/
private File createTempVideoFile(MultipartFile file) throws Exception {
File tempFile = Files.createTempFile("upload-", ".mp4").toFile();
file.transferTo(tempFile);
return tempFile;
}
/**
* 从视频中按时间均匀提取指定数量的帧
*/
private List<BufferedImage> extractFramesFromVideo(File videoFile) throws Exception {
List<BufferedImage> frames = new ArrayList<>();
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) {
grabber.start();
// 获取视频总帧数和帧率
long totalFramesInVideo = grabber.getLengthInFrames();
int fps = (int) Math.round(grabber.getFrameRate());
if (fps <= 0) fps = 30;
double durationSeconds = (double) totalFramesInVideo / fps;
if (durationSeconds < DURATION) {
throw new IllegalArgumentException("视频太短,至少需要 " + DURATION + "");
}
// 计算每帧之间的间隔(浮点以实现更精确跳转)
double frameInterval = (double) totalFramesInVideo / TOTAL_FRAMES;
for (int i = 0; i < TOTAL_FRAMES; i++) {
int targetFrameNumber = (int) Math.round(i * frameInterval);
// 避免设置无效帧号
if (targetFrameNumber >= totalFramesInVideo) {
throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber + " ");
}
grabber.setFrameNumber(targetFrameNumber);
Frame frame = grabber.grab();
if (frame != null && frame.image != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
frames.add(cropImage(bufferedImage, WIDTH, HEIGHT));
} else {
throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "");
}
}
grabber.stop();
}
return frames;
}
/**
* 将抽取的帧保存到本地,用于调试
*/
private void saveFramesToLocal(List<BufferedImage> frames, String prefix) {
// 指定输出目录
File outputDir = new File("output_frames");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
int index = 0;
for (BufferedImage frame : frames) {
try {
File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png");
ImageIO.write(frame, "png", outputImage);
System.out.println("保存帧图片成功: " + outputImage.getAbsolutePath());
} catch (Exception e) {
throw new IllegalArgumentException("保存帧图片失败 " + e);
}
}
}
/**
* 将所有帧转换为 RGB565 格式字节数组
*/
private byte[] convertFramesToRGB565(List<BufferedImage> frames) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
for (BufferedImage image : frames) {
byte[] rgb565Bytes = convertToRGB565(image);
byteArrayOutputStream.write(rgb565Bytes);
}
return byteArrayOutputStream.toByteArray();
}
/**
* 判断是否是支持的视频格式
*/
private boolean isVideo(String filename) {
String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
return Arrays.asList(".mp4", ".avi", ".mov", ".mkv").contains(ext);
}
/**
* 裁剪图像到目标尺寸
*/
private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) {
int w = Math.min(img.getWidth(), targetWidth);
int h = Math.min(img.getHeight(), targetHeight);
return img.getSubimage(0, 0, w, h);
}
/**
* 将 BufferedImage 转换为 RGB565 格式的字节数组
*/
private byte[] convertToRGB565(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
byte[] result = new byte[width * height * 2]; // RGB565: 2 bytes per pixel
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int r = ((rgb >> 16) & 0xFF) >> 3;
int g = ((rgb >> 8) & 0xFF) >> 2;
int b = (rgb & 0xFF) >> 3;
short pixel = (short) ((r << 11) | (g << 5) | b);
result[index++] = (byte) (pixel >> 8); // High byte first
result[index++] = (byte) pixel;
}
}
return result;
}
/**
* 删除临时文件
*/
private void deleteTempFile(File file) {
if (file != null && file.exists()) {
if (!file.delete()) {
throw new IllegalArgumentException("无法删除临时文件: " + file.getAbsolutePath());
}
}
}
}

View File

@ -5,6 +5,7 @@ import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
@ -29,6 +30,7 @@ public class AppDeviceXinghanController extends BaseController {
/**
* 人员信息登记
*/
@Log(title = "xinghan指令-人员信息登记")
@PostMapping(value = "/registerPersonInfo")
// @FunctionAccessAnnotation("registerPersonInfo")
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
@ -38,6 +40,7 @@ public class AppDeviceXinghanController extends BaseController {
/**
* 发送紧急通知
*/
@Log(title = "xinghan指令-发送紧急通知")
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
@ -47,6 +50,7 @@ public class AppDeviceXinghanController extends BaseController {
/**
* 上传设备logo图片
*/
@Log(title = "xinghan指令-上传设备logo图片")
@PostMapping("/uploadLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
@ -64,6 +68,7 @@ public class AppDeviceXinghanController extends BaseController {
* 静电预警档位
* 3,2,1,0,分别表示高档/中档/低挡/关闭
*/
@Log(title = "xinghan指令-静电预警档位")
@PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
@ -75,6 +80,7 @@ public class AppDeviceXinghanController extends BaseController {
* 照明档位
* 照明档位2,1,0,分别表示弱光/强光/关闭
*/
@Log(title = "xinghan指令-照明档位")
@PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
@ -86,6 +92,7 @@ public class AppDeviceXinghanController extends BaseController {
* SOS档位s
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
@Log(title = "xinghan指令-SOS档位s")
@PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
@ -97,6 +104,7 @@ public class AppDeviceXinghanController extends BaseController {
* 静止报警状态
* 静止报警状态0-未静止报警1-正在静止报警。
*/
@Log(title = "xinghan指令-静止报警状态")
@PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject

View File

@ -18,4 +18,9 @@ public class AppFileDto {
*/
private MultipartFile[] files;
/**
* 多设备id
*/
private Long[] deviceIds;
}

View File

@ -0,0 +1,30 @@
package com.fuyuanshen.app.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class AppRegisterBody {
/**
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 短信code
*/
private String smsCode;
/**
* 租户ID
*/
@NotBlank(message = "租户ID不能为空")
private String tenantId;
/**
* 登录密码
*/
private String password;
}

View File

@ -14,7 +14,6 @@ public class AppSmsLoginBody {
/**
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
/**
@ -23,4 +22,14 @@ public class AppSmsLoginBody {
@NotBlank(message = "租户ID不能为空")
private String tenantId;
/**
* 登录方式 1:手机验证码登录 2:密码登录
*/
@NotBlank(message = "登录方式不能为空")
private String loginType;
/**
* 登录密码
*/
private String loginPassword;
}

View File

@ -0,0 +1,12 @@
package com.fuyuanshen.app.model;
import lombok.Data;
@Data
public class AppUpdatePasswordBody {
/**
* 登录密码
*/
private String password;
}

View File

@ -0,0 +1,156 @@
package com.fuyuanshen.app.service;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.app.domain.AppUser;
import com.fuyuanshen.app.mapper.AppUserMapper;
import com.fuyuanshen.app.model.AppRegisterBody;
import com.fuyuanshen.app.model.AppUpdatePasswordBody;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.RegisterBody;
import com.fuyuanshen.common.core.enums.UserType;
import com.fuyuanshen.common.core.exception.user.CaptchaException;
import com.fuyuanshen.common.core.exception.user.CaptchaExpireException;
import com.fuyuanshen.common.core.exception.user.UserException;
import com.fuyuanshen.common.core.utils.MessageUtils;
import com.fuyuanshen.common.core.utils.ServletUtils;
import com.fuyuanshen.common.core.utils.SpringUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.log.event.LogininforEvent;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.common.web.config.properties.CaptchaProperties;
import com.fuyuanshen.system.domain.SysUser;
import com.fuyuanshen.system.domain.bo.SysUserBo;
import com.fuyuanshen.system.mapper.SysUserMapper;
import com.fuyuanshen.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* 注册校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class AppRegisterService {
private final IAppUserService userService;
private final AppUserMapper userMapper;
/**
* 注册
*/
public void register(AppRegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getPhonenumber();
String password = registerBody.getPassword();
boolean codeValidate = validateRegisterSmsCode(tenantId, username, registerBody.getSmsCode());
if (!codeValidate) {
throw new CaptchaException();
}
// 校验用户类型是否存在
String userType = UserType.APP_USER.getUserType();
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, username));
});
if (exist) {
throw new UserException("user.register.save.error", username);
}
AppUser appUser = new AppUser();
appUser.setPhonenumber(username);
appUser.setUserName(username);
appUser.setStatus("0");
appUser.setLoginDate(new Date());
appUser.setLoginIp(ServletUtils.getClientIP());
appUser.setTenantId(tenantId);
appUser.setPassword(password);
appUser.setUserType(userType);
userMapper.insert(appUser);
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 注册校验短信验证码
*/
private boolean validateRegisterSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.REGISTER_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
/**
* 忘记密码校验验证码
*/
private boolean validateForgetPasswordCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.FORGET_PASSWORD_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
public void forgetPassword(AppRegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getPhonenumber();
String password = registerBody.getPassword();
boolean codeValidate = validateForgetPasswordCode(tenantId, username, registerBody.getSmsCode());
if (!codeValidate) {
throw new CaptchaException();
}
boolean exist = TenantHelper.dynamic(tenantId, () -> {
return userMapper.exists(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getUserName, username));
});
if (!exist) {
throw new UserException("用户不存在");
}
UpdateWrapper<AppUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("user_name", username)
.set("password", password);
userMapper.update(updateWrapper);
}
public void updatePassword(AppUpdatePasswordBody body) {
String username = AppLoginHelper.getUsername();
String password = body.getPassword();
UpdateWrapper<AppUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("user_name", username)
.set("password", password);
userMapper.update(updateWrapper);
}
}

View File

@ -39,8 +39,8 @@ public class MqttXinghanJson {
/**
* 第七键值对, 静止报警状态0-未静止报警1-正在静止报警。
*/
@JsonProperty("staShakeBit")
public Integer sta_ShakeBit;
@JsonProperty("sta_ShakeBit")
public Integer staShakeBit;
/**
* 第八键值对, 4G信号强度0-32数值越大信号越强。
*/

View File

@ -3,6 +3,7 @@ package com.fuyuanshen.global.mqtt.config;
import cn.hutool.core.lang.UUID;
import com.fuyuanshen.global.mqtt.receiver.ReceiverMessageHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -55,4 +56,10 @@ public class MqttInboundConfiguration {
public MessageHandler messageHandler(){
return receiverMessageHandler;
}
// @Bean
// @ServiceActivator(inputChannel = "messageInboundChannel") // 确保通道名称正确
// public MessageHandler deviceAlarmMessageHandler() {
// return new DeviceAlrmMessageHandler();
// }
}

View File

@ -4,11 +4,14 @@ import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -48,14 +51,6 @@ public class RedisKeyExpirationListener implements MessageListener {
handleFunctionAccessExpired(element);
}
if(expiredKey.endsWith(DEVICE_ONLINE_STATUS_KEY_PREFIX)) {
// threadPoolTaskExecutor.execute(() -> {
// log.info("设备离线:{}", expiredKey);
// String element = expiredKey.substring(GlobalConstants.GLOBAL_REDIS_KEY.length() + DEVICE_KEY_PREFIX.length(), expiredKey.length() - DEVICE_ONLINE_STATUS_KEY_PREFIX.length());
// UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
// deviceUpdateWrapper.eq("device_imei", element);
// deviceUpdateWrapper.set("online_status", 0);
// deviceMapper.update(deviceUpdateWrapper);
// });
threadPoolTaskExecutor.execute(() -> {
log.info("设备离线:{}", expiredKey);
@ -69,10 +64,14 @@ public class RedisKeyExpirationListener implements MessageListener {
if (lockInfo != null) {
try {
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_imei", element);
deviceUpdateWrapper.set("online_status", 0);
deviceMapper.update(deviceUpdateWrapper);
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
if(StringUtils.isBlank(deviceOnlineStatusRedis)){
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_imei", element);
deviceUpdateWrapper.set("online_status", 0);
deviceMapper.update(deviceUpdateWrapper);
}
} finally {
//释放锁
lockTemplate.releaseLock(lockInfo);

View File

@ -49,12 +49,12 @@ public class ReceiverMessageHandler implements MessageHandler {
String[] subStr = receivedTopic.split("/");
String deviceImei = subStr[1];
if(StringUtils.isNotBlank(deviceImei)){
String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY;
RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24));
//在线状态
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62));
// String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
// String dedupKey = MqttMessageQueueConstants.MQTT_MESSAGE_DEDUP_KEY;
// RedisUtils.offerDeduplicated(queueKey,dedupKey,deviceImei, Duration.ofHours(24));
}
String state = payloadDict.getStr("state");

View File

@ -1,8 +1,18 @@
package com.fuyuanshen.global.mqtt.rule.bjq;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.core.utils.date.DurationUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.bo.DeviceAlarmBo;
import com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.IDeviceAlarmService;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
@ -13,9 +23,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
/**
@ -26,6 +38,10 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@Slf4j
public class BjqAlarmRule implements MqttMessageRule {
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
@Override
public String getCommandType() {
return LightingCommandTypeConstants.ALARM_MESSAGE;
@ -38,14 +54,21 @@ public class BjqAlarmRule implements MqttMessageRule {
Object[] convertArr = context.getConvertArr();
String convertValue = convertArr[1].toString();
if(StringUtils.isNotBlank(convertValue)){
if (StringUtils.isNotBlank(convertValue)) {
// 将设备状态信息存储到Redis中
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX;
String deviceRedisKey = GlobalConstants.GLOBAL_REDIS_KEY + DeviceRedisKeyConstants.DEVICE_KEY_PREFIX + context.getDeviceImei() + DEVICE_ALARM_KEY_PREFIX;
// 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, convertValue);
}
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
// 保存告警信息
String deviceImei = context.getDeviceImei();
// 设备告警状态 0:解除告警 1:报警产生
Byte state = (Byte) convertArr[1];
savaAlarm(deviceImei, state);
} catch (Exception e) {
log.error("处理告警命令时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
@ -53,4 +76,51 @@ public class BjqAlarmRule implements MqttMessageRule {
}
public void savaAlarm(String deviceImei, Byte state) {
DeviceAlarmVo deviceAlarmVo = deviceAlarmService.queryLatestByDeviceImei(deviceImei);
DeviceAlarmBo deviceAlarmBo = new DeviceAlarmBo();
// 解除告警
if (state == 0) {
if (deviceAlarmVo != null) {
if (deviceAlarmVo.getFinishTime() == null) {
BeanUtil.copyProperties(deviceAlarmVo, deviceAlarmBo);
deviceAlarmBo.setFinishTime(new Date());
String durationBetween = DurationUtils.getDurationBetween(deviceAlarmVo.getStartTime(), deviceAlarmBo.getFinishTime());
deviceAlarmBo.setDurationTime(durationBetween);
// 0已处理1未处理
deviceAlarmBo.setTreatmentState(0);
deviceAlarmService.updateByBo(deviceAlarmBo);
}
}
}
// 报警产生
if (state == 1) {
if (deviceAlarmVo == null || deviceAlarmVo.getFinishTime() != null) {
Device device = deviceService.selectDeviceByImei(deviceImei);
deviceAlarmBo.setDeviceId(device.getId());
deviceAlarmBo.setDeviceImei(deviceImei);
// 0-强制报警1-撞击闯入2-自动报警3-电子围栏告警
deviceAlarmBo.setDeviceAction(0);
deviceAlarmBo.setStartTime(new Date());
// 0已处理1未处理
deviceAlarmBo.setTreatmentState(1);
// LoginUser loginUser = LoginHelper.getLoginUser();
// deviceAlarmBo.setCreateBy(loginUser.getUserId());
deviceAlarmBo.setTenantId(device.getTenantId());
String location = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_LOCATION_KEY_PREFIX);
if (StringUtils.isNotBlank(location)) {
JSONObject jsonObject = JSONObject.parseObject(location);
deviceAlarmBo.setLocation(jsonObject.getString("address"));
}
deviceAlarmService.insertByBo(deviceAlarmBo);
}
}
}
}

View File

@ -54,7 +54,7 @@ public class XinghanBootLogoRule implements MqttMessageRule {
@Override
public void execute(MqttRuleContext ctx) {
final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
final String functionAccessKey = FUNCTION_ACCESS_KEY + "LOGO:" + ctx.getDeviceImei();
try {
MqttXinghanLogoJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), MqttXinghanLogoJson.class);

View File

@ -60,7 +60,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule {
@Override
public void execute(MqttRuleContext context) {
String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei();
String functionAccess = FUNCTION_ACCESS_KEY + "DATA:" + context.getDeviceImei();
try {
// Latitude, longitude
//主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间

View File

@ -48,7 +48,7 @@ public class XinghanSendAlarmMessageRule implements MqttMessageRule {
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
String functionAccess = FUNCTION_ACCESS_KEY + "ALARM:" + ctx.getDeviceImei();
try {
XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class);

View File

@ -47,7 +47,7 @@ public class XinghanSendMsgRule implements MqttMessageRule {
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
String functionAccess = FUNCTION_ACCESS_KEY + "MSG:" + ctx.getDeviceImei();
try {
XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class);

View File

@ -1,9 +1,13 @@
package com.fuyuanshen.global.queue;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
@ -15,6 +19,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@Service
@Slf4j
public class MqttMessageConsumer {
@ -27,7 +33,7 @@ public class MqttMessageConsumer {
private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10);
// 初始化方法,启动消息监听
// @PostConstruct
@PostConstruct
public void start() {
log.info("启动MQTT消息消费者...");
// 启动消息获取线程
@ -62,7 +68,7 @@ public class MqttMessageConsumer {
public void consumeMessages() {
String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
String threadName = Thread.currentThread().getName();
log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey);
// log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey);
try {
while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) {
@ -75,7 +81,7 @@ public class MqttMessageConsumer {
);
if (message != null) {
log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message);
// log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message);
// 将消息处理任务提交到处理线程池
messageProcessorPool.submit(() -> processMessage(message));
}
@ -92,15 +98,23 @@ public class MqttMessageConsumer {
String threadName = Thread.currentThread().getName();
try {
log.info("业务处理线程 {} 开始处理消息: {}", threadName, message);
// 实现具体的业务逻辑
// 例如更新数据库、发送通知等
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", message)
.set("online_status", 1);
deviceMapper.update(updateWrapper);
String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ message + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
String deviceOnlineStatusRedis = RedisUtils.getCacheObject(deviceOnlineStatusRedisKey);
if(StringUtils.isBlank(deviceOnlineStatusRedis)){
UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", message)
.set("online_status", 1);
deviceMapper.update(updateWrapper);
}
// QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("device_imei", message);
// queryWrapper.eq("online_status", 1);
// Long count = deviceMapper.selectCount(queryWrapper);
// if(count == 0){
//
// }
// 模拟业务处理耗时
Thread.sleep(200);
// Thread.sleep(200);
log.info("业务处理线程 {} 完成消息处理: {}", threadName, message);
} catch (Exception e) {

View File

@ -0,0 +1,160 @@
package com.fuyuanshen.web.controller.device;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.AppFileDto;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
import com.fuyuanshen.equipment.domain.vo.AppDeviceVo;
import com.fuyuanshen.equipment.domain.vo.WebDeviceVo;
import com.fuyuanshen.web.domain.Dto.DeviceDebugEditDto;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.web.service.device.DeviceBizService;
import com.fuyuanshen.web.service.device.DeviceDebugService;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 联调中心
*
* @author Lion Li
* @date 2025-08-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("api/device/debug")
public class DeviceDebugController extends BaseController {
private final DeviceBizService appDeviceService;
private final DeviceXinghanBizService deviceXinghanBizService;
private final DeviceDebugService deviceDebugService;
/**
* 查询设备列表
*/
@GetMapping("/list")
public TableDataInfo<WebDeviceVo> list(DeviceQueryCriteria bo, PageQuery pageQuery) {
return appDeviceService.queryWebDeviceList(bo, pageQuery);
}
/**
* 上传文件
*/
@Log(title = "批量上传文件")
@PostMapping("/addFile")
public R<Void> uploadFile(@Validated @ModelAttribute AppFileDto bo) throws IOException {
return toAjax(deviceDebugService.addFileHash(bo));
}
/**
* 操作视频添加
*/
@Log(title = "批量添加操作视频")
@PostMapping("/addVideo")
public R<Void> addOperationVideo(@RequestBody AppOperationVideoBo bo) {
return toAjax(deviceDebugService.addVideoList(bo));
}
/**
* 上传设备logo图片
*/
@Log(title = "批量上传设备logo图片")
@PostMapping("/addLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> uploadLogo670(@Validated @ModelAttribute DeviceDebugLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
deviceXinghanBizService.uploadDeviceLogoBatch(bo);
return R.ok();
}
/**
* 设备详情
*/
@Operation(summary = "设备详情")
@GetMapping(value = "/detail/{id}")
public R<DeviceInfoVo> getDeviceInfo(@PathVariable Long id) {
return R.ok(deviceDebugService.getDeviceInfo(id));
}
/**
* 修改设备联调信息
*/
@Log(title = "修改设备联调信息")
@PostMapping("/editDebug")
public R<Void> editDeviceDebug(@Validated @ModelAttribute DeviceDebugEditDto bo) throws Exception {
// 1. 基础参数必填校验
validateDeviceDebugEdit(bo);
// 修改上传设备说明
if (bo.getExplanationFiles() != null) {
AppFileDto appFileDto = new AppFileDto();
appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() });
appFileDto.setFileType(1L);
appFileDto.setFiles(bo.getExplanationFiles());
deviceDebugService.addFileHash(appFileDto);
}
// 修改上传设备参数
if (bo.getParameterFiles() != null) {
AppFileDto appFileDto = new AppFileDto();
appFileDto.setDeviceIds(new Long[]{ bo.getDeviceId() });
appFileDto.setFileType(2L);
appFileDto.setFiles(bo.getParameterFiles());
deviceDebugService.addFileHash(appFileDto);
}
// 修改操作视频
if (bo.getVideoUrl().isEmpty()) {
AppOperationVideoBo appOperationVideoBo = new AppOperationVideoBo();
appOperationVideoBo.setDeviceIds(new Long[]{ bo.getDeviceId() });
appOperationVideoBo.setVideoUrl(bo.getVideoUrl());
deviceDebugService.addVideoList(appOperationVideoBo);
}
// 修改设备logo 每个型号设备走不同协议无法共用同一个上传
// if(bo.getLogoFile() != null){
// MultipartFile file = bo.getLogoFile();
// if(file.getSize()>1024*1024*2){
// return R.warn("图片不能大于2M");
// }
// AppDeviceLogoUploadDto bo1 = new AppDeviceLogoUploadDto();
// bo1.setDeviceId(bo.getDeviceId());
// bo1.setDeviceImei(bo.getDeviceImei());
// bo1.setFile(file);
// deviceXinghanBizService.uploadDeviceLogo(bo1);
// }
return R.ok();
}
/* ------------------ 私有复用 ------------------ */
private void validateDeviceDebugEdit(DeviceDebugEditDto bo) {
if (bo.getDeviceId() == null || bo.getDeviceId() == 0L) {
throw new ServiceException("请选择设备");
}
// if (bo.getDeviceImei().isEmpty()) {
// throw new ServiceException("设备 IMEI 不能为空");
// }
}
}

View File

@ -67,7 +67,7 @@ public class DeviceShareController extends BaseController {
@RepeatSubmit()
@PostMapping("/permission")
public R<Void> permission(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) {
return toAjax(deviceShareService.deviceShare(bo));
return toAjax(deviceShareService.permission(bo));
}
/**

View File

@ -0,0 +1,121 @@
package com.fuyuanshen.web.controller.device;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation;
import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import com.fuyuanshen.web.service.device.DeviceXinghanBizService;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 设备控制类 HBY670
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/xinghan/device")
public class DeviceXinghanController extends BaseController {
private final DeviceXinghanBizService deviceXinghanBizService;
/**
* 获取设备详细信息
*
* @param id 主键
*/
@GetMapping("/{id}")
public R<DeviceXinghanDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceXinghanBizService.getInfo(id));
}
/**
* 人员信息登记
*/
@PostMapping(value = "/registerPersonInfo")
// @FunctionAccessAnnotation("registerPersonInfo")
public R<Void> registerPersonInfo(@Validated(AddGroup.class) @RequestBody AppPersonnelInfoBo bo) {
return toAjax(deviceXinghanBizService.registerPersonInfo(bo));
}
/**
* 发送紧急通知
*/
@PostMapping(value = "/sendAlarmMessage")
@FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10)
public R<Void> sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) {
return toAjax(deviceXinghanBizService.sendAlarmMessage(bo));
}
/**
* 上传设备logo图片
*/
@PostMapping("/uploadLogo")
@FunctionAccessAnnotation("uploadLogo")
public R<Void> upload(@Validated @ModelAttribute AppDeviceLogoUploadDto bo) {
MultipartFile file = bo.getFile();
if(file.getSize()>1024*1024*2){
return R.warn("图片不能大于2M");
}
deviceXinghanBizService.uploadDeviceLogo(bo);
return R.ok();
}
/**
* 静电预警档位
* 3,2,1,0,分别表示高档/中档/低挡/关闭
*/
@PostMapping("/DetectGradeSettings")
public R<Void> DetectGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upDetectGradeSettings(params);
return R.ok();
}
/**
* 照明档位
* 照明档位2,1,0,分别表示弱光/强光/关闭
*/
@PostMapping("/LightGradeSettings")
public R<Void> LightGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upLightGradeSettings(params);
return R.ok();
}
/**
* SOS档位
* SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
@PostMapping("/SOSGradeSettings")
public R<Void> SOSGradeSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upSOSGradeSettings(params);
return R.ok();
}
/**
* 静止报警状态
* 静止报警状态0-未静止报警1-正在静止报警。
*/
@PostMapping("/ShakeBitSettings")
public R<Void> ShakeBitSettings(@RequestBody DeviceInstructDto params) {
// params 转 JSONObject
deviceXinghanBizService.upShakeBitSettings(params);
return R.ok();
}
}

View File

@ -0,0 +1,110 @@
package com.fuyuanshen.web.controller.device.fence;
import java.util.List;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.excel.utils.ExcelUtil;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
/**
* 围栏进出记录
*
* @author Lion Li
* @date 2025-09-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/equipment/fenceAccessRecord")
public class DeviceFenceAccessRecordController extends BaseController {
private final IDeviceFenceAccessRecordService deviceFenceAccessRecordService;
/**
* 查询围栏进出记录列表
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:list")
@GetMapping("/list")
public TableDataInfo<DeviceFenceAccessRecordVo> list(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) {
return deviceFenceAccessRecordService.queryPageList(bo, pageQuery);
}
/**
* 导出围栏进出记录列表
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:export")
@Log(title = "导出围栏进出记录列表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceFenceAccessRecordBo bo, HttpServletResponse response) {
List<DeviceFenceAccessRecordVo> list = deviceFenceAccessRecordService.queryList(bo);
ExcelUtil.exportExcel(list, "围栏进出记录", DeviceFenceAccessRecordVo.class, response);
}
/**
* 获取围栏进出记录详细信息
*
* @param id 主键
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:query")
@GetMapping("/{id}")
public R<DeviceFenceAccessRecordVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceFenceAccessRecordService.queryById(id));
}
/**
* 新增围栏进出记录
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:add")
@Log(title = "围栏进出记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) {
return toAjax(deviceFenceAccessRecordService.insertByBo(bo));
}
/**
* 修改围栏进出记录
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:edit")
@Log(title = "围栏进出记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody DeviceFenceAccessRecordBo bo) {
return toAjax(deviceFenceAccessRecordService.updateByBo(bo));
}
/**
* 删除围栏进出记录
*
* @param ids 主键串
*/
@SaCheckPermission("fys-equipment:fenceAccessRecord:remove")
@Log(title = "围栏进出记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceFenceAccessRecordService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,126 @@
package com.fuyuanshen.web.controller.device.fence;
import java.util.List;
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
import com.fuyuanshen.equipment.service.IDeviceGeoFenceService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.excel.utils.ExcelUtil;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
/**
* 电子围栏
*
* @author Lion Li
* @date 2025-09-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/equipment/geoFence")
public class DeviceGeoFenceController extends BaseController {
private final IDeviceGeoFenceService deviceGeoFenceService;
/**
* 查询电子围栏列表
*/
@SaCheckPermission("fys-equipment:geoFence:list")
@GetMapping("/list")
public TableDataInfo<DeviceGeoFenceVo> list(DeviceGeoFenceBo bo, PageQuery pageQuery) {
return deviceGeoFenceService.queryPageList(bo, pageQuery);
}
/**
* 导出电子围栏列表
*/
@SaCheckPermission("fys-equipment:geoFence:export")
@Log(title = "导出电子围栏列表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceGeoFenceBo bo, HttpServletResponse response) {
List<DeviceGeoFenceVo> list = deviceGeoFenceService.queryList(bo);
ExcelUtil.exportExcel(list, "电子围栏", DeviceGeoFenceVo.class, response);
}
/**
* 获取电子围栏详细信息
*
* @param id 主键
*/
@SaCheckPermission("fys-equipment:geoFence:query")
@GetMapping("/{id}")
public R<DeviceGeoFenceVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceGeoFenceService.queryById(id));
}
/**
* 新增电子围栏
*/
@SaCheckPermission("fys-equipment:geoFence:add")
@Log(title = "电子围栏", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody DeviceGeoFenceBo bo) {
return toAjax(deviceGeoFenceService.insertByBo(bo));
}
/**
* 修改电子围栏
*/
@SaCheckPermission("fys-equipment:geoFence:edit")
@Log(title = "电子围栏", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody DeviceGeoFenceBo bo) {
return toAjax(deviceGeoFenceService.updateByBo(bo));
}
/**
* 删除电子围栏
*
* @param ids 主键串
*/
@SaCheckPermission("fys-equipment:geoFence:remove")
@Log(title = "电子围栏", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceGeoFenceService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 电子围栏-位置检查
*
* @param request
* @return
*/
@PostMapping("/check")
public ResponseEntity<FenceCheckResponse> checkPosition(
@Valid @RequestBody FenceCheckRequest request) {
FenceCheckResponse response = deviceGeoFenceService.checkPosition(request);
return ResponseEntity.ok(response);
}
}

View File

@ -0,0 +1,34 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
public class DeviceDebugEditDto {
/**
* 设备主键列表
*/
private Long deviceId;
/**
* 设备 IMEI
*/
//private String deviceImei;
/**
* 上传 logo 图片
*/
//private MultipartFile LogoFile; // 同一张图
/**
* 参数文件
*/
private MultipartFile[] parameterFiles;
/**
* 说明文件
*/
private MultipartFile[] explanationFiles;
/**
* 视频链接
*/
private String videoUrl;
}

View File

@ -0,0 +1,18 @@
package com.fuyuanshen.web.domain.Dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
public class DeviceDebugLogoUploadDto {
/**
* 设备主键列表
*/
private List<Long> deviceIds; // 设备主键列表
/**
* 上传 图片
*/
private MultipartFile file; // 同一张图
}

View File

@ -0,0 +1,27 @@
package com.fuyuanshen.web.domain.vo;
import com.fuyuanshen.app.domain.vo.AppBusinessFileVo;
import com.fuyuanshen.app.domain.vo.AppFileVo;
import com.fuyuanshen.app.domain.vo.AppOperationVideoVo;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.vo.AppDeviceVo;
import lombok.Data;
import java.util.List;
/**
* 设备信息 视图对象
*
* @author Michelle.Chung
*/
@Data
public class DeviceInfoVo {
/**
* 设备业务文件
*/
private List<AppFileVo> appBusinessFileVoList;
/**
* 设备操作视频
*/
private List<AppOperationVideoVo> appOperationVideoVoList;
}

View File

@ -0,0 +1,103 @@
package com.fuyuanshen.web.domain.vo;
import cn.idev.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import lombok.Data;
@Data
public class DeviceXinghanDetailVo {
/**
* 设备ID
*/
@ExcelProperty(value = "设备ID")
private Long deviceId;
/**
* 设备名称
*/
private String deviceName;
/**
* 设备IMEI
*/
private String deviceImei;
/**
* 设备MAC
*/
private String deviceMac;
/**
* 通讯方式 0:4G;1:蓝牙
*/
private Integer communicationMode;
/**
* 设备图片
*/
private String devicePic;
/**
* 设备类型
*/
private String typeName;
/**
* 蓝牙名称
*/
private String bluetoothName;
/**
* 设备状态
* 0 失效
* 1 正常
*/
private Integer deviceStatus;
/**
* 人员信息
*/
private AppPersonnelInfoVo personnelInfo;
/**
* 在线状态(0离线1在线)
*/
private Integer onlineStatus;
// 经度
private String longitude;
// 纬度
private String latitude;
// 逆解析地址
private String address;
/**
* 第一键值对静电预警档位3,2,1,0,分别表示高档/中档/低挡/关闭.
*/
private Integer staDetectGrade;
/**
* 第二键值对照明档位2,1,0,分别表示弱光/强光/关闭
*/
private Integer staLightGrade;
/**
* 第三键值对SOS档位2,1,0, 分别表示红蓝模式/爆闪模式/关闭
*/
public Integer staSOSGrade;
/**
* 第四键值对剩余照明时间0-5999单位分钟。
*/
public Integer staPowerTime;
/**
* 第五键值对剩余电量百分比0-100
*/
public Integer staPowerPercent;
/**
* 第六键值对, 近电预警级别, 0-无预警1-弱预警2-中预警3-强预警4-非常强预警。
*/
public Integer staDetectResult;
}

View File

@ -0,0 +1,85 @@
// package com.fuyuanshen.web.handler.mqtt;
//
// import cn.hutool.core.lang.Dict;
// import com.fuyuanshen.common.core.constant.GlobalConstants;
// import com.fuyuanshen.common.json.utils.JsonUtils;
// import com.fuyuanshen.common.redis.utils.RedisUtils;
// import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
// import com.fuyuanshen.global.mqtt.base.MqttRuleEngine;
// import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
// import lombok.extern.slf4j.Slf4j;
// import org.apache.commons.lang3.StringUtils;
// import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.messaging.Message;
// import org.springframework.messaging.MessageHandler;
// import org.springframework.messaging.MessageHeaders;
// import org.springframework.messaging.MessagingException;
// import org.springframework.stereotype.Service;
//
// import java.time.Duration;
// import java.util.Objects;
//
// import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
//
// /**
// * @author: 默苍璃
// * @date: 2025-09-1609:28
// */
// @Service
// @Slf4j
// public class DeviceAlrmMessageHandler implements MessageHandler {
//
// @Autowired
// private MqttRuleEngine ruleEngine;
//
// @Override
// public void handleMessage(Message<?> message) throws MessagingException {
//
// // 处理新类型的消息
// Object payload = message.getPayload();
// MessageHeaders headers = message.getHeaders();
// String receivedTopic = Objects.requireNonNull(headers.get("mqtt_receivedTopic")).toString();
//
// log.info("设备强制报警消息处理器 - MQTT payload= {} \n receivedTopic = {}", payload, receivedTopic);
//
// // 解析消息并执行相应逻辑
// Dict payloadDict = JsonUtils.parseMap(payload.toString());
// if (payloadDict != null) {
// // 根据主题或消息内容执行不同的处理逻辑
// processMessage(receivedTopic, payloadDict);
// }
// }
//
// private void processMessage(String topic, Dict payloadDict) {
// // 实现具体的业务逻辑
// // 可以根据不同的主题执行不同的操作
// if (topic.contains("newTopic")) {
// // 处理特定主题的消息
// handleNewTopicMessage(payloadDict);
// }
// }
//
// private void handleNewTopicMessage(Dict payloadDict) {
// // 实现新主题消息的具体处理逻辑
// String deviceImei = payloadDict.getStr("imei");
// if (StringUtils.isNotBlank(deviceImei)) {
// // 更新设备状态到Redis
// String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY +
// DEVICE_KEY_PREFIX + deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX;
// RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62));
//
// // 执行规则引擎
// MqttRuleContext context = new MqttRuleContext();
// context.setDeviceImei(deviceImei);
// context.setPayloadDict(payloadDict);
// // 设置命令类型
// context.setCommandType((byte) 0x02); // 根据实际需要设置命令类型
//
// boolean ruleExecuted = ruleEngine.executeRule(context);
// if (!ruleExecuted) {
// log.warn("未找到匹配的规则来处理新主题消息设备IMEI: {}", deviceImei);
// }
// }
// }
// }
//

View File

@ -212,6 +212,46 @@ public class DeviceShareService {
LambdaQueryWrapper<AppDeviceBindRecord> qw = new LambdaQueryWrapper<>();
qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId());
AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw);
if (bindRecord == null) {
throw new ServiceException("设备未绑定");
}
Long userId = bindRecord.getBindingUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();
lqw.eq(AppDeviceShare::getDeviceId, bo.getDeviceId());
lqw.eq(AppDeviceShare::getPhonenumber, bo.getPhonenumber());
Long count = appDeviceShareMapper.selectCount(lqw);
if (count > 0) {
UpdateWrapper<AppDeviceShare> uw = new UpdateWrapper<>();
uw.eq("device_id", bo.getDeviceId());
uw.eq("phonenumber", bo.getPhonenumber());
uw.set("permission", bo.getPermission());
uw.set("update_by", userId);
uw.set("update_time", new Date());
return appDeviceShareMapper.update(uw);
} else {
AppDeviceShare appDeviceShare = new AppDeviceShare();
appDeviceShare.setDeviceId(bo.getDeviceId());
appDeviceShare.setPhonenumber(bo.getPhonenumber());
appDeviceShare.setPermission(bo.getPermission());
appDeviceShare.setCreateBy(userId);
return appDeviceShareMapper.insert(appDeviceShare);
}
}
public int permission(AppDeviceShareBo bo) {
Device device = deviceMapper.selectById(bo.getDeviceId());
if (device == null) {
throw new ServiceException("设备不存在");
}
LambdaQueryWrapper<AppDeviceBindRecord> qw = new LambdaQueryWrapper<>();
qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId());
AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw);
if (bindRecord == null) {
throw new ServiceException("设备未绑定");
}
Long userId = bindRecord.getBindingUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();

View File

@ -36,6 +36,7 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.web.service.device.status.base.DeviceStatusRule;
import com.fuyuanshen.web.service.device.status.base.RealTimeStatusEngine;
import com.google.common.primitives.Ints;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -126,7 +127,7 @@ public class DeviceBizService {
List<WebDeviceVo> records = result.getRecords();
if(records != null && !records.isEmpty()){
records.forEach(item -> {
if(item.getCommunicationMode()!=null && item.getCommunicationMode() == 0){
if(item.getCommunicationMode()!=null && Ints.asList(0, 2).contains(item.getCommunicationMode())){
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ item.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
@ -181,12 +182,14 @@ public class DeviceBizService {
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId());
bindRecordQueryWrapper.eq("communication_mode", 0);
AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
if (appDeviceBindRecord != null) {
UpdateWrapper<AppDeviceBindRecord> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_id", device.getId())
.set("binding_status", BindingStatusEnum.BOUND.getCode())
.set("binding_user_id", userId)
.set("communication_mode", 0)
.set("update_time", new Date())
.set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
@ -195,6 +198,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(0);
bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord);
}
@ -224,6 +228,7 @@ public class DeviceBizService {
deviceUpdateWrapper.eq("device_id", device.getId())
.eq("binding_user_id", userId)
.set("binding_user_id", userId)
.set("communication_mode", 1)
.set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
} else {
@ -231,6 +236,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(1);
bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord);
}

View File

@ -0,0 +1,150 @@
package com.fuyuanshen.web.service.device;
import cn.hutool.core.collection.CollUtil;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.AppOperationVideo;
import com.fuyuanshen.app.domain.bo.AppBusinessFileBo;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.app.domain.dto.AppFileDto;
import com.fuyuanshen.app.service.IAppBusinessFileService;
import com.fuyuanshen.app.service.IAppOperationVideoService;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.service.ISysOssService;
import com.fuyuanshen.web.domain.vo.DeviceInfoVo;
import com.fuyuanshen.web.util.FileHashUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 设备调试服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceDebugService {
private final ISysOssService sysOssService;
private final IAppBusinessFileService appBusinessFileService;
private final IAppOperationVideoService appOperationVideoService;
private final DeviceService deviceService;
/**
* 文件上传并添加文件信息哈希去重
* @param bo
* @return
* @throws IOException
*/
@Transactional(rollbackFor = Exception.class)
public Boolean addFileHash(AppFileDto bo) throws IOException {
MultipartFile[] files = bo.getFiles();
if (files == null || files.length == 0) {
throw new ServiceException("请选择要上传的文件");
}
if (files.length > 5) {
throw new ServiceException("最多只能上传5个文件");
}
if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) {
throw new ServiceException("请选择你要上传的设备");
}
Map<String, Long> hash2OssId = new LinkedHashMap<>(files.length);
for (MultipartFile file : files) {
// 1. 计算文件哈希
String hash = FileHashUtil.hash(file);
// 2. 先根据 hash 查库(秒传)
SysOssVo exist = sysOssService.selectByHash(hash);
Long ossId;
if (exist != null) {
// 2.1 已存在,直接复用
ossId = exist.getOssId();
hash2OssId.putIfAbsent(hash, ossId);
} else {
// 2.2 不存在,真正上传
SysOssVo upload = sysOssService.upload(file);
if (upload == null) {
return false;
}
ossId = upload.getOssId();
hash2OssId.putIfAbsent(hash, ossId);
// 2.3 把 hash 写回记录(供下次去重)
sysOssService.updateHashById(ossId, hash);
}
}
// 4. 组装业务中间表
List<AppBusinessFile> bizList = new ArrayList<>(bo.getDeviceIds().length * hash2OssId.size());
Long userId = AppLoginHelper.getUserId();
for (Long deviceId : bo.getDeviceIds()) {
for (Long ossId : hash2OssId.values()) {
// 3. 关联业务表
AppBusinessFile appFile = new AppBusinessFile();
appFile.setFileId(ossId);
appFile.setBusinessId(deviceId);
appFile.setFileType(bo.getFileType());
appFile.setCreateBy(userId);
bizList.add(appFile);
}
}
if (CollUtil.isEmpty(bizList)) { // 空集合直接返回
throw new ServiceException("请选择要上传的文件");
}
return appBusinessFileService.insertBatch(bizList);
}
public Boolean addVideoList(AppOperationVideoBo bo){
if (bo.getVideoUrl().isEmpty()) {
throw new ServiceException("请输入视频地址");
}
if (bo.getDeviceIds() == null || bo.getDeviceIds().length == 0) {
throw new ServiceException("请选择你要上传的设备");
}
List<AppOperationVideo> bizList = new ArrayList<>(bo.getDeviceIds().length);
for (Long deviceId : bo.getDeviceIds()) {
AppOperationVideo appVideo = new AppOperationVideo();
appVideo.setVideoName(bo.getVideoName());
appVideo.setDeviceId(deviceId);
appVideo.setVideoUrl(bo.getVideoUrl());
bizList.add(appVideo);
}
if (CollUtil.isEmpty(bizList)) { // 空集合直接返回
throw new ServiceException("请选择要上传的视频");
}
return appOperationVideoService.insertBatch(bizList);
}
/**
* 设备详情
* @param deviceId
* @return
*/
public DeviceInfoVo getDeviceInfo(Long deviceId) {
if(deviceId == null || deviceId <= 0L) {
throw new ServiceException("请选择设备");
}
DeviceInfoVo vo = new DeviceInfoVo();
var device = deviceService.getById(deviceId);
AppBusinessFileBo fileBo = new AppBusinessFileBo();
fileBo.setBusinessId(deviceId);
AppOperationVideoBo videoBo = new AppOperationVideoBo();
videoBo.setDeviceId(deviceId);
vo.setAppBusinessFileVoList(appBusinessFileService.queryAppFileList(fileBo));
vo.setAppOperationVideoVoList(appOperationVideoService.queryList(videoBo));
return vo;
}
}

View File

@ -1,14 +1,22 @@
package com.fuyuanshen.web.service.device;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.app.domain.AppPersonnelInfo;
import com.fuyuanshen.app.domain.AppPersonnelInfoRecords;
import com.fuyuanshen.app.domain.bo.AppPersonnelInfoBo;
import com.fuyuanshen.app.domain.dto.AppDeviceLogoUploadDto;
import com.fuyuanshen.app.domain.dto.DeviceInstructDto;
import com.fuyuanshen.app.domain.vo.AppDeviceDetailVo;
import com.fuyuanshen.app.domain.vo.AppPersonnelInfoVo;
import com.fuyuanshen.app.mapper.AppPersonnelInfoMapper;
import com.fuyuanshen.app.mapper.AppPersonnelInfoRecordsMapper;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.exception.ServiceException;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
@ -19,21 +27,29 @@ import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.domain.DeviceType;
import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo;
import com.fuyuanshen.equipment.enums.LightModeEnum;
import com.fuyuanshen.equipment.mapper.DeviceLogMapper;
import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.global.mqtt.base.MqttXinghanJson;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.web.domain.Dto.DeviceDebugLogoUploadDto;
import com.fuyuanshen.web.domain.vo.DeviceXinghanDetailVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr;
@ -51,6 +67,9 @@ public class DeviceXinghanBizService {
private final DeviceTypeMapper deviceTypeMapper;
private final MqttGateway mqttGateway;
private final DeviceLogMapper deviceLogMapper;
private final AppPersonnelInfoRecordsMapper appPersonnelInfoRecordsMapper;
@Autowired
private ObjectMapper objectMapper;
/**
* 所有档位的描述表
@ -101,6 +120,71 @@ public class DeviceXinghanBizService {
sendCommand(dto, "ins_ShakeBit","强制报警");
}
public DeviceXinghanDetailVo getInfo(Long id) {
Device device = deviceMapper.selectById(id);
if (device == null) {
throw new RuntimeException("请先将设备入库!!!");
}
DeviceXinghanDetailVo vo = new DeviceXinghanDetailVo();
vo.setDeviceId(device.getId());
vo.setDeviceName(device.getDeviceName());
vo.setDevicePic(device.getDevicePic());
vo.setDeviceImei(device.getDeviceImei());
vo.setDeviceMac(device.getDeviceMac());
vo.setDeviceStatus(device.getDeviceStatus());
DeviceType deviceType = deviceTypeMapper.selectById(device.getDeviceType());
if (deviceType != null) {
vo.setCommunicationMode(Integer.valueOf(deviceType.getCommunicationMode()));
vo.setTypeName(deviceType.getTypeName());
}
vo.setBluetoothName(device.getBluetoothName());
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
.eq("device_id", device.getId());
AppPersonnelInfo appPersonnelInfo = appPersonnelInfoMapper.selectOne(qw);
if (appPersonnelInfo != null) {
AppPersonnelInfoVo personnelInfoVo = MapstructUtils.convert(appPersonnelInfo, AppPersonnelInfoVo.class);
vo.setPersonnelInfo(personnelInfoVo);
}
//设备在线状态
String onlineStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei()+ DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX);
if(StringUtils.isNotBlank(onlineStatus)){
vo.setOnlineStatus(1);
}else{
vo.setOnlineStatus(0);
}
String deviceStatus = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_STATUS_KEY_PREFIX);
// 获取设备上报的设备状态
if(StringUtils.isNotBlank(deviceStatus)){
try {
MqttXinghanJson deviceJson = objectMapper.readValue(deviceStatus, MqttXinghanJson.class);
vo.setStaLightGrade(deviceJson.getStaLightGrade());
vo.setStaPowerPercent(deviceJson.getStaPowerPercent());
vo.setStaSOSGrade(deviceJson.getStaSOSGrade());
vo.setStaDetectGrade(deviceJson.getStaDetectGrade());
vo.setStaPowerTime(deviceJson.getStaPowerTime());
} catch (Exception e) {
throw new IllegalArgumentException("设备状态缓存格式非法", e);
}
}else{
vo.setStaPowerPercent(0);
}
// 获取经度纬度
String locationKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DeviceRedisKeyConstants.DEVICE_LOCATION_KEY_PREFIX;
String locationInfo = RedisUtils.getCacheObject(locationKey);
if(StringUtils.isNotBlank(locationInfo)){
JSONObject jsonObject = JSONObject.parseObject(locationInfo);
vo.setLongitude(jsonObject.get("longitude").toString());
vo.setLatitude(jsonObject.get("latitude").toString());
vo.setAddress((String)jsonObject.get("address"));
}
return vo;
}
/**
* 上传设备logo
*/
@ -142,22 +226,84 @@ public class DeviceXinghanBizService {
}
}
/**
* 批量上传设备logo
*/
@Transactional(rollbackFor = Exception.class)
public void uploadDeviceLogoBatch(DeviceDebugLogoUploadDto batchDto) {
if (CollectionUtils.isEmpty(batchDto.getDeviceIds())) {
throw new ServiceException("设备列表为空");
}
// 1. 一次性把设备查出来N -> 1
QueryWrapper<Device> query = new QueryWrapper<>();
query.in("id", batchDto.getDeviceIds());
List<Device> devices = deviceMapper.selectList(query);
if (devices.size() != batchDto.getDeviceIds().size()) {
throw new ServiceException("部分设备不存在");
}
// 2. 图片只转换一次160*80 固定尺寸)
byte[] largeData;
try {
largeData = ImageToCArrayConverter.convertImageToCArray(
batchDto.getFile().getInputStream(), 160, 80, 25600);
} catch (IOException e) {
throw new ServiceException("图片解析失败");
}
int[] picArray = convertHexToDecimal(largeData);
// 3. 过滤离线设备 & 组装指令
List<Device> onlineDevices = devices.stream()
.filter(d -> !isDeviceOffline(d.getDeviceImei()))
.toList();
onlineDevices.forEach(d -> {
String redisKey = GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + d.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX;
// 如果 Redis 里已存在,直接跳过
if (RedisUtils.getCacheObject(redisKey) != null) {
return; // 跳过本次循环
}
RedisUtils.setCacheObject(
redisKey,
Arrays.toString(picArray),
Duration.ofSeconds(5 * 60L));
// 3.2 MQTT 下发
Map<String, Object> payload =
Collections.singletonMap("ins_PicTrans", Collections.singletonList(0));
String topic = MqttConstants.GLOBAL_PUB_KEY + d.getDeviceImei();
String json = JsonUtils.toJsonString(payload);
try {
mqttGateway.sendMsgToMqtt(topic, 1, json);
} catch (Exception e) {
log.error("上传开机画面失败, topic={}, payload={}", topic, json, e);
throw new ServiceException("上传LOGO失败" + e.getMessage());
}
recordDeviceLog(d.getId(), d.getDeviceName(), "上传开机画面", "上传开机画面", AppLoginHelper.getUserId());
});
}
/**
* 人员登记
* @param bo
*/
@Transactional(rollbackFor = Exception.class) // 1. 事务
public boolean registerPersonInfo(AppPersonnelInfoBo bo) {
Long deviceId = bo.getDeviceId();
Device deviceObj = deviceMapper.selectById(deviceId);
if (deviceObj == null) {
throw new RuntimeException("请先将设备入库!!!");
}
if (deviceObj.getDeviceImei() == null) {
return saveOrUpdatePersonnelInfo(bo, deviceId);
}
if (isDeviceOffline(deviceObj.getDeviceImei())) {
throw new ServiceException("设备已断开连接:" + deviceObj.getDeviceName());
}
QueryWrapper<AppPersonnelInfo> qw = new QueryWrapper<AppPersonnelInfo>()
.eq("device_id", deviceId);
List<AppPersonnelInfoVo> appPersonnelInfoVos = appPersonnelInfoMapper.selectVoList(qw);
List<String> list = new ArrayList<>();
list.add(bo.getName());
@ -181,18 +327,47 @@ public class DeviceXinghanBizService {
log.info("发送人员信息登记到设备消息=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY + deviceObj.getDeviceImei(), bo);
recordDeviceLog(deviceId, deviceObj.getDeviceName(), "人员信息登记", JSON.toJSONString(bo), AppLoginHelper.getUserId());
if (ObjectUtils.length(appPersonnelInfoVos) == 0) {
AppPersonnelInfo appPersonnelInfo = MapstructUtils.convert(bo, AppPersonnelInfo.class);
return appPersonnelInfoMapper.insertOrUpdate(appPersonnelInfo);
return saveOrUpdatePersonnelInfo(bo, deviceId);
}
/**
* 仅内部调用,已处于 @Transactional 内
*/
private Boolean saveOrUpdatePersonnelInfo(AppPersonnelInfoBo bo, Long deviceId) {
// 1. 先查主表有没有
AppPersonnelInfo main = appPersonnelInfoMapper.selectOne(
Wrappers.<AppPersonnelInfo>lambdaQuery()
.eq(AppPersonnelInfo::getDeviceId, deviceId));
if (main == null) {
/* ====== 新增场景 ====== */
main = MapstructUtils.convert(bo, AppPersonnelInfo.class);
main.setDeviceId(deviceId);
// 1.1 主表插入 -> 主键回写
appPersonnelInfoMapper.insert(main); // 执行后 main.getId() 一定有值
} else {
UpdateWrapper<AppPersonnelInfo> uw = new UpdateWrapper<>();
uw.eq("device_id", deviceId)
.set("name", bo.getName())
.set("position", bo.getPosition())
.set("unit_name", bo.getUnitName())
.set("code", bo.getCode());
return appPersonnelInfoMapper.update(null, uw) > 0;
/* ====== 更新场景 ====== */
// 1.2 把 bo 的新值刷到主表
appPersonnelInfoMapper.update(null,
Wrappers.<AppPersonnelInfo>update()
.eq("device_id", deviceId)
.set("name", bo.getName())
.set("position", bo.getPosition())
.set("unit_name", bo.getUnitName())
.set("code", bo.getCode())
.set("update_time", new Date()));
}
// 2. 无论新增/更新,都插入一条履历
AppPersonnelInfoRecords record = new AppPersonnelInfoRecords();
// 只拷业务字段不拷时间、id、操作人
BeanUtil.copyProperties(main, record, "id", "createTime", "updateTime", "createBy", "updateBy");
record.setId(null); // 让自增
record.setPersonnelId(main.getId()); // 关键:主键已回写,不会为 null
appPersonnelInfoRecordsMapper.insert(record);
return true;
}
/**
@ -207,6 +382,10 @@ public class DeviceXinghanBizService {
if (deviceIds == null || deviceIds.isEmpty()) {
throw new ServiceException("请选择设备");
}
if(!StringUtils.isNotEmpty(bo.getSendMsg()))
{
throw new ServiceException("请输入发送内容");
}
QueryWrapper<Device> queryWrapper = new QueryWrapper<>();
// 使用 in 语句根据id集合查询
queryWrapper.in("id", deviceIds);
@ -259,9 +438,11 @@ public class DeviceXinghanBizService {
*/
private void sendSingleAlarmMessage(Device device, String message) {
String deviceImei = device.getDeviceImei();
int msgLen = message.length() / 2;
StringBuilder msgBuilder = new StringBuilder(message);
msgBuilder.insert(msgLen, '|');
// 缓存告警消息到Redis
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, message, Duration.ofSeconds(5 * 60L));
RedisUtils.setCacheObject(GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + deviceImei + DEVICE_ALARM_MESSAGE_KEY_PREFIX, msgBuilder.toString(), Duration.ofSeconds(5 * 60L));
// 构建并发送MQTT消息
Map<String, Object> payload = Map.of("ins_BreakNews", Collections.singletonList(0));

View File

@ -0,0 +1,138 @@
package com.fuyuanshen.web.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fuyuanshen.app.domain.AppUser;
import com.fuyuanshen.app.domain.vo.AppUserVo;
import com.fuyuanshen.app.mapper.AppUserMapper;
import com.fuyuanshen.app.service.AppLoginService;
import com.fuyuanshen.common.core.constant.Constants;
import com.fuyuanshen.common.core.constant.GlobalConstants;
import com.fuyuanshen.common.core.constant.SystemConstants;
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
import com.fuyuanshen.common.core.domain.model.AppPasswordLoginBody;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.domain.model.PasswordLoginBody;
import com.fuyuanshen.common.core.enums.LoginType;
import com.fuyuanshen.common.core.enums.UserType;
import com.fuyuanshen.common.core.exception.user.CaptchaException;
import com.fuyuanshen.common.core.exception.user.CaptchaExpireException;
import com.fuyuanshen.common.core.exception.user.UserException;
import com.fuyuanshen.common.core.utils.MessageUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.core.utils.ValidatorUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
import com.fuyuanshen.common.satoken.utils.LoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.common.web.config.properties.CaptchaProperties;
import com.fuyuanshen.system.domain.SysUser;
import com.fuyuanshen.system.domain.vo.SysClientVo;
import com.fuyuanshen.system.domain.vo.SysUserVo;
import com.fuyuanshen.system.mapper.SysUserMapper;
import com.fuyuanshen.web.domain.vo.LoginVo;
import com.fuyuanshen.web.service.IAuthStrategy;
import com.fuyuanshen.web.service.SysLoginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* APP密码认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("appPassword" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class AppPasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final AppLoginService loginService;
private final AppUserMapper appUserMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
AppPasswordLoginBody loginBody = JsonUtils.parseObject(body, AppPasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
AppUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !password.equals(user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
AppLoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
private AppUserVo loadUserByUsername(String username) {
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
// if (ObjectUtil.isNull(user)) {
// log.info("登录用户:{} 不存在.", username);
// throw new UserException("user.not.exists", username);
// } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
// log.info("登录用户:{} 已被停用.", username);
// throw new UserException("user.blocked", username);
// }
// return user;
AppUserVo user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getPhonenumber, username)
.eq(AppUser::getUserType, UserType.APP_USER.getUserType()));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
}
}

View File

@ -54,7 +54,7 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
AppLoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode));
AppUserVo user = loadUserByPhonenumber(phonenumber);
if (ObjectUtil.isNull(user)) {
//新增Appuser

View File

@ -0,0 +1,28 @@
package com.fuyuanshen.web.util;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.HexFormat;
/**
* 文件哈希工具类
*/
public class FileHashUtil {
private static final String ALGORITHM = "SHA-256";
public static String hash(MultipartFile file) throws IOException {
MessageDigest digest = DigestUtils.getDigest(ALGORITHM);
try (InputStream in = file.getInputStream()) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
digest.update(buf, 0, len);
}
}
return HexFormat.of().formatHex(digest.digest());
}
}

View File

@ -303,6 +303,6 @@ mqtt:
password: #YtvpSfCNG
url: tcp://47.120.79.150:2883
subClientId: fys_subClient
subTopic: worker/location/#
subTopic: A/#
pubTopic: B/#
pubClientId: fys_pubClient

View File

@ -17,6 +17,16 @@ public interface GlobalConstants {
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 忘记密码验证码 redis key
*/
String FORGET_PASSWORD_CODE_KEY = GLOBAL_REDIS_KEY + "forget_password_codes:";
/**
* 注册验证码 redis key
*/
String REGISTER_CODE_KEY = GLOBAL_REDIS_KEY + "register_codes:";
/**
* 设备分享验证码 redis key
*/

View File

@ -0,0 +1,31 @@
package com.fuyuanshen.common.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AppPasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
// @Length(min = 5, max = 30, message = "{user.password.length.valid}")
private String password;
}

View File

@ -0,0 +1,108 @@
package com.fuyuanshen.common.core.utils.date;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 持续时间工具类
* 提供计算两个日期之间差值的方法,并以时分秒格式返回
*
* @author fys
*/
public class DurationUtils {
/**
* 计算两个日期之间的差值,返回时分秒格式的字符串
* 格式为 "X小时 Y分钟 Z秒"
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 时分秒格式的时间差字符串
*/
public static String getDurationBetween(Date startDate, Date endDate) {
if (startDate == null || endDate == null) {
return "0小时 0分钟 0秒";
}
long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime());
long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds);
}
/**
* 计算两个日期之间的差值,返回时分秒格式的字符串
* 仅显示非零单位
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 时分秒格式的时间差字符串,仅显示非零单位
*/
public static String getDurationBetweenPretty(Date startDate, Date endDate) {
if (startDate == null || endDate == null) {
return "0秒";
}
long diffInMillis = Math.abs(endDate.getTime() - startDate.getTime());
long hours = TimeUnit.MILLISECONDS.toHours(diffInMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
StringBuilder result = new StringBuilder();
if (hours > 0) {
result.append(hours).append("小时 ");
}
if (minutes > 0) {
result.append(minutes).append("分钟 ");
}
if (seconds > 0 || result.length() == 0) {
result.append(seconds).append("");
}
return result.toString().trim();
}
/**
* 计算指定毫秒数的持续时间,返回时分秒格式的字符串
*
* @param millis 毫秒数
* @return 时分秒格式的持续时间字符串
*/
public static String getDurationFromMillis(long millis) {
long absMillis = Math.abs(millis);
long hours = TimeUnit.MILLISECONDS.toHours(absMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60;
return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds);
}
/**
* 计算指定毫秒数的持续时间,返回时分秒格式的字符串
* 仅显示非零单位
*
* @param millis 毫秒数
* @return 时分秒格式的持续时间字符串,仅显示非零单位
*/
public static String getDurationFromMillisPretty(long millis) {
long absMillis = Math.abs(millis);
long hours = TimeUnit.MILLISECONDS.toHours(absMillis);
long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60;
StringBuilder result = new StringBuilder();
if (hours > 0) {
result.append(hours).append("小时 ");
}
if (minutes > 0) {
result.append(minutes).append("分钟 ");
}
if (seconds > 0 || result.length() == 0) {
result.append(seconds).append("");
}
return result.toString().trim();
}
}

View File

@ -50,5 +50,6 @@ public class AppDeviceBindRecord extends TenantEntity {
*/
private Date bindingTime;
private Integer communicationMode;
}

View File

@ -60,7 +60,7 @@ public class AppDeviceShareBo extends BaseEntity {
/**
* 分享时间
*/
private Date shareStartTime;
private Date shareEndTime;
private String shareStartTime;
private String shareEndTime;
}

View File

@ -45,5 +45,10 @@ public class AppOperationVideoBo extends BaseEntity {
*/
private String remark;
/**
* 多设备id
*/
private Long[] deviceIds;
}

View File

@ -109,4 +109,9 @@ public class AppDeviceShareVo implements Serializable {
* 告警状态(0解除告警1告警)
*/
private String alarmStatus;
/**
* 创建时间
*/
private String createTime;
}

View File

@ -19,6 +19,10 @@ public class AppFileVo {
* 文件名称
*/
private String fileName;
/**
* 文件类型(1:操作说明2:产品参数)
*/
private Long fileType;
/**
* 文件url
*/

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.vo.AppBusinessFileVo;
import com.fuyuanshen.app.domain.bo.AppBusinessFileBo;
import com.fuyuanshen.app.domain.vo.AppFileVo;
@ -50,6 +51,14 @@ public interface IAppBusinessFileService {
*/
Boolean insertByBo(AppBusinessFileBo bo);
/**
* 批量新增app业务文件
*
* @param bo 批量新增app业务文件
* @return 是否新增成功
*/
Boolean insertBatch(Collection<AppBusinessFile> bo);
/**
* 修改app业务文件
*

View File

@ -1,5 +1,7 @@
package com.fuyuanshen.app.service;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.app.domain.AppOperationVideo;
import com.fuyuanshen.app.domain.vo.AppOperationVideoVo;
import com.fuyuanshen.app.domain.bo.AppOperationVideoBo;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
@ -49,6 +51,14 @@ public interface IAppOperationVideoService {
*/
Boolean insertByBo(AppOperationVideoBo bo);
/**
* 批量新增操作视频
*
* @param bo 批量新增操作视频
* @return 是否新增成功
*/
Boolean insertBatch(Collection<AppOperationVideo> bo);
/**
* 修改操作视频
*

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fuyuanshen.app.domain.vo.AppFileVo;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
@ -100,6 +101,20 @@ public class AppBusinessFileServiceImpl implements IAppBusinessFileService {
return flag;
}
@Override
public Boolean insertBatch(Collection<AppBusinessFile> bo) {
// 1. 去重后的 businessId 集合
List<Long> businessIds = bo.stream()
.map(AppBusinessFile::getBusinessId)
.distinct()
.toList();
QueryWrapper<AppBusinessFile> queryWrapper = new QueryWrapper<>();
queryWrapper.in("business_id", businessIds);
queryWrapper.eq("file_type", bo.stream().findFirst().orElseThrow().getFileType());
baseMapper.delete(queryWrapper);
return baseMapper.insertBatch(bo);
}
/**
* 修改app业务文件
*

View File

@ -1,5 +1,7 @@
package com.fuyuanshen.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fuyuanshen.app.domain.AppBusinessFile;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
@ -97,6 +99,19 @@ public class AppOperationVideoServiceImpl implements IAppOperationVideoService {
return flag;
}
@Override
public Boolean insertBatch(Collection<AppOperationVideo> bo) {
// 1. 去重后的 businessId 集合
List<Long> deviceIds = bo.stream()
.map(AppOperationVideo::getDeviceId)
.distinct()
.toList();
QueryWrapper<AppOperationVideo> queryWrapper = new QueryWrapper<>();
queryWrapper.in("device_id", deviceIds);
baseMapper.delete(queryWrapper);
return baseMapper.insertBatch(bo);
}
/**
* 修改操作视频
*

View File

@ -5,7 +5,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<mapper namespace="com.fuyuanshen.app.mapper.AppBusinessFileMapper">
<select id="queryAppFileList" resultType="com.fuyuanshen.app.domain.vo.AppFileVo">
select a.id,a.business_id,a.file_id,b.file_name,b.url fileUrl from app_business_file a left join sys_oss b on a.file_id = b.oss_id
select a.id,a.business_id,a.file_id,a.file_type,b.file_name,b.url fileUrl from app_business_file a left join sys_oss b on a.file_id = b.oss_id
where 1=1
<if test="businessId != null">
and a.business_id = #{businessId}

View File

@ -72,9 +72,12 @@
and ad.device_id = #{bo.deviceId}
</if>
<if test="bo.shareUser != null">
and u.user_name = #{bo.shareUser}
and ad.phonenumber like concat('%',#{bo.shareUser},'%')
</if>
<if test="bo.shareStartTime != null and bo.hareEndTime != null">
<if test="bo.phonenumber != null">
and ad.phonenumber like concat('%',#{bo.phonenumber},'%')
</if>
<if test="bo.shareStartTime != null and bo.shareEndTime != null">
and ad.create_time between #{bo.shareStartTime} and #{bo.shareEndTime}
</if>
</where>

View File

@ -0,0 +1,105 @@
package com.fuyuanshen.equipment.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
import com.fuyuanshen.common.log.annotation.Log;
import com.fuyuanshen.common.web.core.BaseController;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.common.core.domain.R;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.log.enums.BusinessType;
import com.fuyuanshen.common.excel.utils.ExcelUtil;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
import com.fuyuanshen.equipment.service.IDeviceFenceStatusService;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
/**
* 设备进入围栏状态
*
* @author Lion Li
* @date 2025-09-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/equipment/fenceStatus")
public class DeviceFenceStatusController extends BaseController {
private final IDeviceFenceStatusService deviceFenceStatusService;
/**
* 查询设备进入围栏状态列表
*/
@SaCheckPermission("equipment:fenceStatus:list")
@GetMapping("/list")
public TableDataInfo<DeviceFenceStatusVo> list(DeviceFenceStatusBo bo, PageQuery pageQuery) {
return deviceFenceStatusService.queryPageList(bo, pageQuery);
}
/**
* 导出设备进入围栏状态列表
*/
@SaCheckPermission("equipment:fenceStatus:export")
@Log(title = "设备进入围栏状态", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DeviceFenceStatusBo bo, HttpServletResponse response) {
List<DeviceFenceStatusVo> list = deviceFenceStatusService.queryList(bo);
ExcelUtil.exportExcel(list, "设备进入围栏状态", DeviceFenceStatusVo.class, response);
}
/**
* 获取设备进入围栏状态详细信息
*
* @param id 主键
*/
@SaCheckPermission("equipment:fenceStatus:query")
@GetMapping("/{id}")
public R<DeviceFenceStatusVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(deviceFenceStatusService.queryById(id));
}
/**
* 新增设备进入围栏状态
*/
@SaCheckPermission("equipment:fenceStatus:add")
@Log(title = "设备进入围栏状态", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody DeviceFenceStatusBo bo) {
return toAjax(deviceFenceStatusService.insertByBo(bo));
}
/**
* 修改设备进入围栏状态
*/
@SaCheckPermission("equipment:fenceStatus:edit")
@Log(title = "设备进入围栏状态", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody DeviceFenceStatusBo bo) {
return toAjax(deviceFenceStatusService.updateByBo(bo));
}
/**
* 删除设备进入围栏状态
*
* @param ids 主键串
*/
@SaCheckPermission("equipment:fenceStatus:remove")
@Log(title = "设备进入围栏状态", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(deviceFenceStatusService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -2,6 +2,8 @@ package com.fuyuanshen.equipment.domain;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import io.github.linpeilie.annotations.AutoMapping;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -66,12 +68,12 @@ public class DeviceAlarm extends TenantEntity {
/**
* 经度
*/
private Long longitude;
private Double longitude;
/**
* 纬度
*/
private Long latitude;
private Double latitude;
/**
* 报警位置
@ -97,7 +99,15 @@ public class DeviceAlarm extends TenantEntity {
* 0已处理1未处理
* treatment_state
*/
private Long treatmentState;
private Integer treatmentState;
/**
* 设备IMEI
* device_imei
*/
@Schema(title = "设备IMEI")
@AutoMapping(target = "deviceImei")
private String deviceImei;
}

View File

@ -0,0 +1,72 @@
package com.fuyuanshen.equipment.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 围栏进出记录对象 device_fence_access_record
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("device_fence_access_record")
public class DeviceFenceAccessRecord extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "id")
private Long id;
/**
* 围栏ID
*/
private Long fenceId;
/**
* 设备标识
*/
private String deviceId;
/**
* 用户ID
*/
private Long userId;
/**
* 事件类型
*/
private Long eventType;
/**
* 纬度
*/
private Double latitude;
/**
* 经度
*/
private Double longitude;
/**
* 定位精度
*/
private Long accuracy;
/**
* 事件时间
*/
private Date eventTime;
}

View File

@ -0,0 +1,53 @@
package com.fuyuanshen.equipment.domain;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 设备进入围栏状态对象 device_fence_status
*
* @author Lion Li
* @date 2025-09-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("device_fence_status")
public class DeviceFenceStatus extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@TableId(value = "id")
private Long id;
/**
* 设备ID
*/
private String deviceId;
/**
* 围栏ID
*/
private Long fenceId;
/**
* 状态: 0-在围栏外, 1-在围栏内
*/
private Long status;
/**
* 上次检查时间
*/
private Date lastCheckTime;
}

View File

@ -0,0 +1,63 @@
package com.fuyuanshen.equipment.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
/**
* 电子围栏对象 device_geo_fence
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("device_geo_fence")
public class DeviceGeoFence extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 围栏唯一标识
*/
@TableId(value = "id")
private Long id;
/**
* 围栏名称
*/
private String name;
/**
* 围栏描述
*/
private String description;
/**
* 围栏区域类型, 0 POLYGON, 1 CIRCLE
*/
private Long areaType;
/**
* 围栏坐标数据
*/
private String coordinates;
/**
* 圆形围栏半径(米)
*/
private Long radius;
/**
* 是否激活
*/
private Long isActive;
}

View File

@ -59,8 +59,8 @@ public class DeviceType extends TenantEntity {
@Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI")
private String networkWay;
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙")
private String communicationMode;
@Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode;
/**
* 创建人名称

View File

@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.domain.bo;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import com.fuyuanshen.equipment.domain.DeviceAlarm;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
@ -19,7 +20,7 @@ import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = DeviceAlarm.class, reverseConvertGenerate = false)
public class DeviceAlarmBo extends BaseEntity {
public class DeviceAlarmBo extends TenantEntity {
/**
* ID
@ -35,6 +36,7 @@ public class DeviceAlarmBo extends BaseEntity {
/**
* 报警事项
* device_action
* 0-强制报警1-撞击闯入2-自动报警3-电子围栏告警
*/
private Integer deviceAction;
@ -61,7 +63,7 @@ public class DeviceAlarmBo extends BaseEntity {
private String dataSource;
/**
* 内容
* 内容(报警信息)
*/
private String content;
@ -99,7 +101,7 @@ public class DeviceAlarmBo extends BaseEntity {
/**
* 报警持续时间
*/
private Long durationTime;
private String durationTime;
/**
* 报警查询时间

View File

@ -6,6 +6,7 @@ import com.fuyuanshen.equipment.domain.DeviceChargeDischarge;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import cn.hutool.core.date.DateUtil;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
@ -44,13 +45,25 @@ public class DeviceChargeDischargeBo extends BaseEntity {
* 开始时间
*/
@NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startTime;
// 添加字符串类型的setter方法来处理前端传递的字符串日期
public void setStartTime(String startTime) {
this.startTime = DateUtil.parseDate(startTime);
}
/**
* 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endTime;
// 添加字符串类型的setter方法来处理前端传递的字符串日期
public void setEndTime(String endTime) {
this.endTime = DateUtil.parseDate(endTime);
}
/**
* 起始电量百分比(0-100)
*/

View File

@ -0,0 +1,93 @@
package com.fuyuanshen.equipment.domain.bo;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 围栏进出记录业务对象 device_fence_access_record
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = DeviceFenceAccessRecord.class, reverseConvertGenerate = false)
public class DeviceFenceAccessRecordBo extends BaseEntity {
/**
* 记录ID
*/
@NotNull(message = "记录ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 围栏ID
*/
@NotNull(message = "围栏ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long fenceId;
/**
* 设备标识
*/
@NotBlank(message = "设备标识不能为空", groups = { AddGroup.class, EditGroup.class })
private String deviceId;
/**
* 用户ID
*/
private Long userId;
/**
* 事件类型
*/
@NotNull(message = "事件类型不能为空", groups = { AddGroup.class, EditGroup.class })
private Long eventType;
/**
* 纬度
*/
@NotNull(message = "纬度不能为空", groups = { AddGroup.class, EditGroup.class })
private Double latitude;
/**
* 经度
*/
@NotNull(message = "经度不能为空", groups = { AddGroup.class, EditGroup.class })
private Double longitude;
/**
* 定位精度
*/
private Long accuracy;
/**
* 事件时间
*/
@NotNull(message = "事件时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date eventTime;
/**
* 开始时间
*/
private String beginTime;
/**
* 结束时间
*/
private String endTime;
/**
* 记录创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,56 @@
package com.fuyuanshen.equipment.domain.bo;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.equipment.domain.DeviceFenceStatus;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 设备进入围栏状态业务对象 device_fence_status
*
* @author Lion Li
* @date 2025-09-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = DeviceFenceStatus.class, reverseConvertGenerate = false)
public class DeviceFenceStatusBo extends BaseEntity {
/**
*
*/
@NotNull(message = "不能为空", groups = { EditGroup.class })
private Long id;
/**
* 设备ID
*/
@NotBlank(message = "设备ID不能为空", groups = { AddGroup.class, EditGroup.class })
private String deviceId;
/**
* 围栏ID
*/
@NotNull(message = "围栏ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long fenceId;
/**
* 状态: 0-在围栏外, 1-在围栏内
*/
@NotNull(message = "状态: 0-在围栏外, 1-在围栏内不能为空", groups = { AddGroup.class, EditGroup.class })
private Long status;
/**
* 上次检查时间
*/
@NotNull(message = "上次检查时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date lastCheckTime;
}

View File

@ -0,0 +1,75 @@
package com.fuyuanshen.equipment.domain.bo;
import com.fuyuanshen.common.core.validate.AddGroup;
import com.fuyuanshen.common.core.validate.EditGroup;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 电子围栏业务对象 device_geo_fence
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = DeviceGeoFence.class, reverseConvertGenerate = false)
public class DeviceGeoFenceBo extends BaseEntity {
/**
* 围栏唯一标识
*/
@NotNull(message = "围栏唯一标识不能为空", groups = { EditGroup.class })
private Long id;
/**
* 围栏名称
*/
@NotBlank(message = "围栏名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String name;
/**
* 围栏描述
*/
private String description;
/**
* 围栏区域类型, 0 POLYGON, 1 CIRCLE
*/
@NotNull(message = "围栏区域类型, 0 POLYGON, 1 CIRCLE不能为空", groups = { AddGroup.class, EditGroup.class })
private Long areaType;
/**
* 围栏坐标数据
*/
@NotBlank(message = "围栏坐标数据不能为空", groups = { AddGroup.class, EditGroup.class })
private String coordinates;
/**
* 圆形围栏半径(米)
*/
private Long radius;
/**
* 是否激活
*/
private Long isActive;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@ -23,7 +23,7 @@ public class AppDeviceBo {
private String deviceMac;
/**
* 通讯方式 0:4G; 1:蓝牙
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
@NotNull(message = "通讯方式不能为空", groups = { EditGroup.class })
private Integer communicationMode;

View File

@ -0,0 +1,63 @@
package com.fuyuanshen.equipment.domain.dto;
import lombok.Data;
import java.util.List;
/**
* 围栏位置检查响应结果
*
* @author: 默苍璃
* @date: 2025-09-1110:11
*/
@Data
public class FenceCheckResponse {
/**
* 设备ID
*/
private String deviceId;
/**
* 检查时间
*/
private Long checkTime;
/**
* 进入围栏列表
*/
private List<FenceInfo> enteredFences;
/**
* 离开围栏列表
*/
private List<FenceInfo> exitedFences;
/**
* 当前所在围栏列表
*/
private List<FenceInfo> currentFences;
/**
* 围栏信息
*/
@Data
public static class FenceInfo {
/**
* 围栏ID
*/
private Long fenceId;
/**
* 围栏名称
*/
private String fenceName;
/**
* 围栏类型
*/
private Integer fenceType;
}
}

View File

@ -21,13 +21,13 @@ public class DeviceTypeForm {
private Boolean isSupportBle;
@Schema(title = "定位方式", example = "0:无;1:GPS;2:基站;3:wifi;4:北斗")
private String locateMode;
private Integer locateMode;
@Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI")
private String networkWay;
private Integer networkWay;
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙")
private String communicationMode;
@Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode;
/**
* 型号字典用于APP页面跳转

View File

@ -59,7 +59,7 @@ public class APPDeviceQueryCriteria1 {
@Schema(name = "租户ID")
private Long tenantId;
@Schema(name = "通讯方式", example = "0:4G;1:蓝牙")
@Schema(name = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode;
}

View File

@ -82,9 +82,10 @@ public class DeviceQueryCriteria extends BaseEntity {
private String tenantId;
/**
* 通讯方式 0:4G;1:蓝牙
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
* communication_mode
*/
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode;
/* app绑定用户id */

View File

@ -0,0 +1,40 @@
package com.fuyuanshen.equipment.domain.query;
/**
* @author: 默苍璃
* @date: 2025-09-1110:10
*/
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 围栏位置检查请求参数
*/
@Data
public class FenceCheckRequest {
/**
* 设备ID
*/
@NotNull(message = "设备ID不能为空")
private String deviceId;
/**
* 纬度
*/
@NotNull(message = "纬度不能为空")
private Double latitude;
/**
* 经度
*/
@NotNull(message = "经度不能为空")
private Double longitude;
/**
* 定位精度(米)
*/
private Long accuracy;
}

View File

@ -49,7 +49,7 @@ public class AlarmInformationVo {
private Integer alarmManual = 0;
/**
* 电子围栏
*
*/
private Integer fenceElectronic = 0;

View File

@ -26,7 +26,7 @@ public class AppDeviceVo implements Serializable {
private String deviceMac;
/**
* 通讯方式 0:4G;1:蓝牙
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
private Integer communicationMode;

View File

@ -26,6 +26,11 @@ public class DataOverviewVo {
*/
private Integer bindingNew = 0;
/**
* 已绑定设备
*/
private Integer binding = 0;
/**
* 异常设备
*/

View File

@ -10,6 +10,7 @@ import com.fuyuanshen.common.excel.annotation.ExcelDictFormat;
import com.fuyuanshen.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.io.Serial;
@ -45,7 +46,7 @@ public class DeviceAlarmVo implements Serializable {
/**
* 报警事项
* 0-强制报警1-撞击闯入2-动报警3-电子围栏告警4-强制告警
* 0-强制报警1-撞击闯入2-动报警3-电子围栏告警
*/
@ExcelProperty(value = "报警事项")
private Integer deviceAction;
@ -134,4 +135,5 @@ public class DeviceAlarmVo implements Serializable {
@Schema(name = "设备图片")
private String devicePic;
}

View File

@ -0,0 +1,107 @@
package com.fuyuanshen.equipment.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.fuyuanshen.common.excel.annotation.ExcelDictFormat;
import com.fuyuanshen.common.excel.convert.ExcelDictConvert;
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 围栏进出记录视图对象 device_fence_access_record
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = DeviceFenceAccessRecord.class)
public class DeviceFenceAccessRecordVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
// @ExcelProperty(value = "记录ID")
private Long id;
/**
* 围栏ID
*/
// @ExcelProperty(value = "围栏ID")
private Long fenceId;
/**
* 围栏名称
*/
@ExcelProperty(value = "围栏名称")
private String fenceName;
/**
* 设备标识
*/
// @ExcelProperty(value = "设备标识")
private Long deviceId;
/**
* 设备名称
*/
@ExcelProperty(value = "设备名称")
private String deviceName;
/**
* 用户ID
*/
// @ExcelProperty(value = "用户ID")
private Long userId;
/**
* 事件类型
*/
@ExcelProperty(value = "事件类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "1=进入围栏,2=离开围栏")
private Long eventType;
/**
* 纬度
*/
@ExcelProperty(value = "纬度")
private Long latitude;
/**
* 经度
*/
@ExcelProperty(value = "经度")
private Long longitude;
/**
* 定位精度
*/
@ExcelProperty(value = "定位精度")
private Long accuracy;
/**
* 事件时间
*/
@ExcelProperty(value = "事件时间")
private Date eventTime;
/**
* 记录创建时间
*/
@ExcelProperty(value = "记录创建时间")
private Date createTime;
}

View File

@ -0,0 +1,64 @@
package com.fuyuanshen.equipment.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fuyuanshen.equipment.domain.DeviceFenceStatus;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.fuyuanshen.common.excel.annotation.ExcelDictFormat;
import com.fuyuanshen.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 设备进入围栏状态视图对象 device_fence_status
*
* @author Lion Li
* @date 2025-09-15
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = DeviceFenceStatus.class)
public class DeviceFenceStatusVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@ExcelProperty(value = "")
private Long id;
/**
* 设备ID
*/
@ExcelProperty(value = "设备ID")
private String deviceId;
/**
* 围栏ID
*/
@ExcelProperty(value = "围栏ID")
private Long fenceId;
/**
* 状态: 0-在围栏外, 1-在围栏内
*/
@ExcelProperty(value = "状态: 0-在围栏外, 1-在围栏内")
private Long status;
/**
* 上次检查时间
*/
@ExcelProperty(value = "上次检查时间")
private Date lastCheckTime;
}

View File

@ -0,0 +1,94 @@
package com.fuyuanshen.equipment.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.fuyuanshen.common.excel.annotation.ExcelDictFormat;
import com.fuyuanshen.common.excel.convert.ExcelDictConvert;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 电子围栏视图对象 device_geo_fence
*
* @author Lion Li
* @date 2025-09-11
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = DeviceGeoFence.class)
public class DeviceGeoFenceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 围栏唯一标识
*/
@ExcelProperty(value = "围栏唯一标识")
private Long id;
/**
* 围栏名称
*/
@ExcelProperty(value = "围栏名称")
private String name;
/**
* 围栏描述
*/
@ExcelProperty(value = "围栏描述")
private String description;
/**
* 围栏区域类型, 0 POLYGON, 1 CIRCLE
*/
@ExcelProperty(value = "围栏区域类型, 0 POLYGON, 1 CIRCLE")
private Integer areaType;
/**
* 围栏坐标数据
*/
@ExcelProperty(value = "围栏坐标数据")
private String coordinates;
/**
* 圆形围栏半径(米)
*/
@ExcelProperty(value = "圆形围栏半径(米)")
private Long radius;
/**
* 是否激活
*/
@ExcelProperty(value = "是否激活")
private Long isActive;
/**
* 创建人
*/
@ExcelProperty(value = "创建人")
private Long createBy;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date createTime;
/**
* 更新时间
*/
@ExcelProperty(value = "更新时间")
private Date updateTime;
}

View File

@ -27,7 +27,7 @@ public class WebDeviceVo implements Serializable {
private String deviceMac;
/**
* 通讯方式 0:4G;1:蓝牙
* 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/
private Integer communicationMode;

View File

@ -25,4 +25,13 @@ public interface DeviceAlarmMapper extends BaseMapperPlus<DeviceAlarm, DeviceAla
*/
Page<DeviceAlarmVo> selectVoPage( Page pageQuery,@Param("bo") DeviceAlarmBo bo);
/**
* 根据设备IMEI查询最新的一条告警数据
*
* @param deviceImei 设备IMEI
* @return 设备告警
*/
DeviceAlarmVo selectLatestByDeviceImei(@Param("deviceImei") String deviceImei);
}

View File

@ -0,0 +1,33 @@
package com.fuyuanshen.equipment.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 围栏进出记录Mapper接口
*
* @author Lion Li
* @date 2025-09-11
*/
public interface DeviceFenceAccessRecordMapper extends BaseMapperPlus<DeviceFenceAccessRecord, DeviceFenceAccessRecordVo> {
/**
* 分页查询围栏进出记录列表(包含围栏名称和设备名称)
*
* @param page 分页参数
* @param wrapper 查询条件
* @return 围栏进出记录分页列表
*/
Page<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(Page<DeviceFenceAccessRecord> page, @Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper);
List<DeviceFenceAccessRecordVo> selectVoPageWithFenceAndDeviceName(@Param(Constants.WRAPPER) Wrapper<DeviceFenceAccessRecord> wrapper);
}

View File

@ -0,0 +1,15 @@
package com.fuyuanshen.equipment.mapper;
import com.fuyuanshen.equipment.domain.DeviceFenceStatus;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 设备进入围栏状态Mapper接口
*
* @author Lion Li
* @date 2025-09-15
*/
public interface DeviceFenceStatusMapper extends BaseMapperPlus<DeviceFenceStatus, DeviceFenceStatusVo> {
}

View File

@ -0,0 +1,15 @@
package com.fuyuanshen.equipment.mapper;
import com.fuyuanshen.common.mybatis.core.mapper.BaseMapperPlus;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
/**
* 电子围栏Mapper接口
*
* @author Lion Li
* @date 2025-09-11
*/
public interface DeviceGeoFenceMapper extends BaseMapperPlus<DeviceGeoFence, DeviceGeoFenceVo> {
}

View File

@ -109,4 +109,12 @@ public interface DeviceMapper extends BaseMapper<Device> {
int getUsageDataForMonth(@Param("deviceId") Long deviceId,
@Param("year") int year,
@Param("month") int month);
}
/**
* 根据设备IMEI查询设备
*
* @param deviceImei 设备IMEI
* @return 设备信息
*/
Device selectDeviceByImei(@Param("deviceImei") String deviceImei);
}

View File

@ -144,4 +144,12 @@ public interface DeviceService extends IService<Device> {
* @return
*/
List<Map<String, Object>> getEquipmentUsageData(Long deviceTypeId, Integer range);
}
/**
* 根据设备IMEI查询设备
*
* @param deviceImei 设备IMEI
* @return 设备信息
*/
Device selectDeviceByImei(String deviceImei);
}

View File

@ -65,4 +65,14 @@ public interface IDeviceAlarmService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据设备IMEI查询最新的一条告警数据
*
* @param deviceImei 设备IMEI
* @return 设备告警
*/
DeviceAlarmVo queryLatestByDeviceImei(String deviceImei);
}

View File

@ -0,0 +1,68 @@
package com.fuyuanshen.equipment.service;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import java.util.Collection;
import java.util.List;
/**
* 围栏进出记录Service接口
*
* @author Lion Li
* @date 2025-09-11
*/
public interface IDeviceFenceAccessRecordService {
/**
* 查询围栏进出记录
*
* @param id 主键
* @return 围栏进出记录
*/
DeviceFenceAccessRecordVo queryById(Long id);
/**
* 分页查询围栏进出记录列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 围栏进出记录分页列表
*/
TableDataInfo<DeviceFenceAccessRecordVo> queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery);
/**
* 查询符合条件的围栏进出记录列表
*
* @param bo 查询条件
* @return 围栏进出记录列表
*/
List<DeviceFenceAccessRecordVo> queryList(DeviceFenceAccessRecordBo bo);
/**
* 新增围栏进出记录
*
* @param bo 围栏进出记录
* @return 是否新增成功
*/
Boolean insertByBo(DeviceFenceAccessRecordBo bo);
/**
* 修改围栏进出记录
*
* @param bo 围栏进出记录
* @return 是否修改成功
*/
Boolean updateByBo(DeviceFenceAccessRecordBo bo);
/**
* 校验并批量删除围栏进出记录信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,81 @@
package com.fuyuanshen.equipment.service;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 设备进入围栏状态Service接口
*
* @author Lion Li
* @date 2025-09-15
*/
public interface IDeviceFenceStatusService {
/**
* 查询设备进入围栏状态
*
* @param id 主键
* @return 设备进入围栏状态
*/
DeviceFenceStatusVo queryById(Long id);
/**
* 分页查询设备进入围栏状态列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 设备进入围栏状态分页列表
*/
TableDataInfo<DeviceFenceStatusVo> queryPageList(DeviceFenceStatusBo bo, PageQuery pageQuery);
/**
* 查询符合条件的设备进入围栏状态列表
*
* @param bo 查询条件
* @return 设备进入围栏状态列表
*/
List<DeviceFenceStatusVo> queryList(DeviceFenceStatusBo bo);
/**
* 新增设备进入围栏状态
*
* @param bo 设备进入围栏状态
* @return 是否新增成功
*/
Boolean insertByBo(DeviceFenceStatusBo bo);
/**
* 修改设备进入围栏状态
*
* @param bo 设备进入围栏状态
* @return 是否修改成功
*/
Boolean updateByBo(DeviceFenceStatusBo bo);
/**
* 校验并批量删除设备进入围栏状态信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 查询设备在特定围栏的最新状态
*
* @param deviceId 设备ID
* @param fenceId 围栏ID
* @return 最新状态记录如果不存在则返回null
*/
DeviceFenceStatusVo getLatestStatusByDeviceAndFence(String deviceId, Long fenceId);
}

View File

@ -0,0 +1,78 @@
package com.fuyuanshen.equipment.service;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
import java.util.Collection;
import java.util.List;
/**
* 电子围栏Service接口
*
* @author Lion Li
* @date 2025-09-11
*/
public interface IDeviceGeoFenceService {
/**
* 查询电子围栏
*
* @param id 主键
* @return 电子围栏
*/
DeviceGeoFenceVo queryById(Long id);
/**
* 分页查询电子围栏列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 电子围栏分页列表
*/
TableDataInfo<DeviceGeoFenceVo> queryPageList(DeviceGeoFenceBo bo, PageQuery pageQuery);
/**
* 查询符合条件的电子围栏列表
*
* @param bo 查询条件
* @return 电子围栏列表
*/
List<DeviceGeoFenceVo> queryList(DeviceGeoFenceBo bo);
/**
* 新增电子围栏
*
* @param bo 电子围栏
* @return 是否新增成功
*/
Boolean insertByBo(DeviceGeoFenceBo bo);
/**
* 修改电子围栏
*
* @param bo 电子围栏
* @return 是否修改成功
*/
Boolean updateByBo(DeviceGeoFenceBo bo);
/**
* 校验并批量删除电子围栏信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 检查设备位置与围栏的关系
*
* @param request 位置检查请求
* @return 位置检查结果
*/
FenceCheckResponse checkPosition(FenceCheckRequest request);
}

View File

@ -1,5 +1,6 @@
package com.fuyuanshen.equipment.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
@ -102,11 +103,13 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
*/
@Override
public Boolean insertByBo(DeviceAlarmBo bo) {
DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
DeviceAlarm deviceAlarm = new DeviceAlarm();
// DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class);
BeanUtil.copyProperties(bo, deviceAlarm);
validEntityBeforeSave(deviceAlarm);
boolean flag = baseMapper.insert(deviceAlarm) > 0;
if (flag) {
bo.setId(add.getId());
bo.setId(deviceAlarm.getId());
}
return flag;
}
@ -145,4 +148,17 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 根据设备IMEI查询最新的一条告警数据
*
* @param deviceImei 设备IMEI
* @return 设备告警
*/
@Override
public DeviceAlarmVo queryLatestByDeviceImei(String deviceImei) {
return baseMapper.selectLatestByDeviceImei(deviceImei);
}
}

View File

@ -0,0 +1,143 @@
package com.fuyuanshen.equipment.service.impl;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fuyuanshen.equipment.domain.DeviceFenceAccessRecord;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo;
import com.fuyuanshen.equipment.mapper.DeviceFenceAccessRecordMapper;
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 围栏进出记录Service业务层处理
*
* @author Lion Li
* @date 2025-09-11
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class DeviceFenceAccessRecordServiceImpl implements IDeviceFenceAccessRecordService {
private final DeviceFenceAccessRecordMapper baseMapper;
/**
* 查询围栏进出记录
*
* @param id 主键
* @return 围栏进出记录
*/
@Override
public DeviceFenceAccessRecordVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询围栏进出记录列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 围栏进出记录分页列表
*/
@Override
public TableDataInfo<DeviceFenceAccessRecordVo> queryPageList(DeviceFenceAccessRecordBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo);
Page<DeviceFenceAccessRecordVo> result = baseMapper.selectVoPageWithFenceAndDeviceName(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的围栏进出记录列表
*
* @param bo 查询条件
* @return 围栏进出记录列表
*/
@Override
public List<DeviceFenceAccessRecordVo> queryList(DeviceFenceAccessRecordBo bo) {
LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoPageWithFenceAndDeviceName(lqw);
}
private LambdaQueryWrapper<DeviceFenceAccessRecord> buildQueryWrapper(DeviceFenceAccessRecordBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<DeviceFenceAccessRecord> lqw = Wrappers.lambdaQuery();
// lqw.orderByAsc(DeviceFenceAccessRecord::getId);
lqw.eq(bo.getFenceId() != null, DeviceFenceAccessRecord::getFenceId, bo.getFenceId());
lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceFenceAccessRecord::getDeviceId, bo.getDeviceId());
lqw.eq(bo.getUserId() != null, DeviceFenceAccessRecord::getUserId, bo.getUserId());
lqw.eq(bo.getEventType() != null, DeviceFenceAccessRecord::getEventType, bo.getEventType());
lqw.eq(bo.getLatitude() != null, DeviceFenceAccessRecord::getLatitude, bo.getLatitude());
lqw.eq(bo.getLongitude() != null, DeviceFenceAccessRecord::getLongitude, bo.getLongitude());
lqw.eq(bo.getAccuracy() != null, DeviceFenceAccessRecord::getAccuracy, bo.getAccuracy());
lqw.eq(bo.getEventTime() != null, DeviceFenceAccessRecord::getEventTime, bo.getEventTime());
lqw.eq(bo.getCreateTime() != null, DeviceFenceAccessRecord::getCreateTime, bo.getCreateTime());
return lqw;
}
/**
* 新增围栏进出记录
*
* @param bo 围栏进出记录
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(DeviceFenceAccessRecordBo bo) {
DeviceFenceAccessRecord add = MapstructUtils.convert(bo, DeviceFenceAccessRecord.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改围栏进出记录
*
* @param bo 围栏进出记录
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(DeviceFenceAccessRecordBo bo) {
DeviceFenceAccessRecord update = MapstructUtils.convert(bo, DeviceFenceAccessRecord.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(DeviceFenceAccessRecord entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除围栏进出记录信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

View File

@ -0,0 +1,152 @@
package com.fuyuanshen.equipment.service.impl;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
import com.fuyuanshen.equipment.domain.DeviceFenceStatus;
import com.fuyuanshen.equipment.mapper.DeviceFenceStatusMapper;
import com.fuyuanshen.equipment.service.IDeviceFenceStatusService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 设备进入围栏状态Service业务层处理
*
* @author Lion Li
* @date 2025-09-15
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class DeviceFenceStatusServiceImpl implements IDeviceFenceStatusService {
private final DeviceFenceStatusMapper baseMapper;
/**
* 查询设备进入围栏状态
*
* @param id 主键
* @return 设备进入围栏状态
*/
@Override
public DeviceFenceStatusVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询设备进入围栏状态列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 设备进入围栏状态分页列表
*/
@Override
public TableDataInfo<DeviceFenceStatusVo> queryPageList(DeviceFenceStatusBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<DeviceFenceStatus> lqw = buildQueryWrapper(bo);
Page<DeviceFenceStatusVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的设备进入围栏状态列表
*
* @param bo 查询条件
* @return 设备进入围栏状态列表
*/
@Override
public List<DeviceFenceStatusVo> queryList(DeviceFenceStatusBo bo) {
LambdaQueryWrapper<DeviceFenceStatus> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<DeviceFenceStatus> buildQueryWrapper(DeviceFenceStatusBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<DeviceFenceStatus> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(DeviceFenceStatus::getId);
lqw.eq(StringUtils.isNotBlank(bo.getDeviceId()), DeviceFenceStatus::getDeviceId, bo.getDeviceId());
lqw.eq(bo.getFenceId() != null, DeviceFenceStatus::getFenceId, bo.getFenceId());
lqw.eq(bo.getStatus() != null, DeviceFenceStatus::getStatus, bo.getStatus());
lqw.eq(bo.getLastCheckTime() != null, DeviceFenceStatus::getLastCheckTime, bo.getLastCheckTime());
return lqw;
}
/**
* 新增设备进入围栏状态
*
* @param bo 设备进入围栏状态
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(DeviceFenceStatusBo bo) {
DeviceFenceStatus add = MapstructUtils.convert(bo, DeviceFenceStatus.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改设备进入围栏状态
*
* @param bo 设备进入围栏状态
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(DeviceFenceStatusBo bo) {
DeviceFenceStatus update = MapstructUtils.convert(bo, DeviceFenceStatus.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(DeviceFenceStatus entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除设备进入围栏状态信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 查询设备在特定围栏的最新状态
*/
@Override
public DeviceFenceStatusVo getLatestStatusByDeviceAndFence(String deviceId, Long fenceId) {
LambdaQueryWrapper<DeviceFenceStatus> lqw = Wrappers.lambdaQuery();
lqw.eq(DeviceFenceStatus::getDeviceId, deviceId);
lqw.eq(DeviceFenceStatus::getFenceId, fenceId);
lqw.orderByDesc(DeviceFenceStatus::getLastCheckTime);
lqw.last("LIMIT 1");
DeviceFenceStatus status = baseMapper.selectOne(lqw);
return MapstructUtils.convert(status, DeviceFenceStatusVo.class);
}
}

View File

@ -0,0 +1,266 @@
package com.fuyuanshen.equipment.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fuyuanshen.equipment.domain.DeviceGeoFence;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceAccessRecordBo;
import com.fuyuanshen.equipment.domain.bo.DeviceFenceStatusBo;
import com.fuyuanshen.equipment.domain.bo.DeviceGeoFenceBo;
import com.fuyuanshen.equipment.domain.dto.FenceCheckResponse;
import com.fuyuanshen.equipment.domain.query.FenceCheckRequest;
import com.fuyuanshen.equipment.domain.vo.DeviceFenceStatusVo;
import com.fuyuanshen.equipment.domain.vo.DeviceGeoFenceVo;
import com.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper;
import com.fuyuanshen.equipment.service.IDeviceFenceAccessRecordService;
import com.fuyuanshen.equipment.service.IDeviceFenceStatusService;
import com.fuyuanshen.equipment.service.IDeviceGeoFenceService;
import com.fuyuanshen.equipment.utils.map.GeoFenceChecker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 电子围栏Service业务层处理
*
* @author Lion Li
* @date 2025-09-11
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class DeviceGeoFenceServiceImpl implements IDeviceGeoFenceService {
private final DeviceGeoFenceMapper baseMapper;
private final IDeviceFenceStatusService fenceStatusService; // 添加此行
private final IDeviceFenceAccessRecordService fenceAccessRecordService; // 添加此行
/**
* 查询电子围栏
*
* @param id 主键
* @return 电子围栏
*/
@Override
public DeviceGeoFenceVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询电子围栏列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 电子围栏分页列表
*/
@Override
public TableDataInfo<DeviceGeoFenceVo> queryPageList(DeviceGeoFenceBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<DeviceGeoFence> lqw = buildQueryWrapper(bo);
Page<DeviceGeoFenceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的电子围栏列表
*
* @param bo 查询条件
* @return 电子围栏列表
*/
@Override
public List<DeviceGeoFenceVo> queryList(DeviceGeoFenceBo bo) {
LambdaQueryWrapper<DeviceGeoFence> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<DeviceGeoFence> buildQueryWrapper(DeviceGeoFenceBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<DeviceGeoFence> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(DeviceGeoFence::getId);
lqw.like(StringUtils.isNotBlank(bo.getName()), DeviceGeoFence::getName, bo.getName());
lqw.eq(StringUtils.isNotBlank(bo.getDescription()), DeviceGeoFence::getDescription, bo.getDescription());
lqw.eq(bo.getAreaType() != null, DeviceGeoFence::getAreaType, bo.getAreaType());
lqw.eq(StringUtils.isNotBlank(bo.getCoordinates()), DeviceGeoFence::getCoordinates, bo.getCoordinates());
lqw.eq(bo.getRadius() != null, DeviceGeoFence::getRadius, bo.getRadius());
lqw.eq(bo.getIsActive() != null, DeviceGeoFence::getIsActive, bo.getIsActive());
lqw.eq(bo.getCreateBy() != null, DeviceGeoFence::getCreateBy, bo.getCreateBy());
lqw.eq(bo.getCreateTime() != null, DeviceGeoFence::getCreateTime, bo.getCreateTime());
lqw.eq(bo.getUpdateTime() != null, DeviceGeoFence::getUpdateTime, bo.getUpdateTime());
return lqw;
}
/**
* 新增电子围栏
*
* @param bo 电子围栏
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(DeviceGeoFenceBo bo) {
DeviceGeoFence add = MapstructUtils.convert(bo, DeviceGeoFence.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改电子围栏
*
* @param bo 电子围栏
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(DeviceGeoFenceBo bo) {
DeviceGeoFence update = MapstructUtils.convert(bo, DeviceGeoFence.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(DeviceGeoFence entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除电子围栏信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 检查设备位置与围栏的关系
*
* @param request 位置检查请求
* @return 位置检查结果
*/
@Override
public FenceCheckResponse checkPosition(FenceCheckRequest request) {
// 1. 获取所有激活的围栏
DeviceGeoFenceBo bo = new DeviceGeoFenceBo();
bo.setIsActive(1L); // 假设1表示激活状态
List<DeviceGeoFenceVo> activeFences = queryList(bo);
// 2. 初始化响应对象
FenceCheckResponse response = new FenceCheckResponse();
response.setDeviceId(request.getDeviceId());
response.setCheckTime(System.currentTimeMillis());
response.setEnteredFences(new ArrayList<>());
response.setExitedFences(new ArrayList<>());
response.setCurrentFences(new ArrayList<>());
// 3. 遍历所有激活的围栏
for (DeviceGeoFenceVo fence : activeFences) {
// 解析围栏坐标
ObjectMapper objectMapper = new ObjectMapper();
List<GeoFenceChecker.Coordinate> coordinateList = new ArrayList<>();
try {
coordinateList = objectMapper.readValue(fence.getCoordinates(),
new TypeReference<List<GeoFenceChecker.Coordinate>>() {
});
} catch (Exception e) {
log.error("坐标数据解析失败: {}", e.getMessage());
continue; // 解析失败则跳过该围栏
}
// 检查设备是否在围栏内
boolean pointInFence = GeoFenceChecker.isPointInFence(
request.getLatitude(),
request.getLongitude(),
fence.getAreaType(),
coordinateList,
fence.getRadius());
// 创建围栏信息对象
FenceCheckResponse.FenceInfo fenceInfo = new FenceCheckResponse.FenceInfo();
fenceInfo.setFenceId(fence.getId());
fenceInfo.setFenceName(fence.getName());
fenceInfo.setFenceType(fence.getAreaType());
// // 查询设备在该围栏的历史状态
// DeviceFenceStatusBo statusQuery = new DeviceFenceStatusBo();
// statusQuery.setDeviceId(request.getDeviceId());
// statusQuery.setFenceId(fence.getId());
// List<DeviceFenceStatusVo> statusHistory = fenceStatusService.queryList(statusQuery);
//
// // 获取最新的状态记录
// DeviceFenceStatusVo latestStatus = statusHistory.stream()
// .max(Comparator.comparing(DeviceFenceStatusVo::getLastCheckTime))
// .orElse(null);
// 查询设备在该围栏的最新状态
DeviceFenceStatusVo latestStatus = fenceStatusService.getLatestStatusByDeviceAndFence(
request.getDeviceId(), fence.getId());
// 判断设备与围栏的关系变化 1=进入围栏,2=离开围栏
Long previousStatus = latestStatus != null ? latestStatus.getStatus() : 2L; // 默认在围栏外
Long currentStatus = pointInFence ? 1L : 2L; // 当前状态1-在围栏内2-在围栏外
// 如果状态发生变化,则记录
if (!previousStatus.equals(currentStatus)) {
// 创建新的状态记录
DeviceFenceStatusBo newStatus = new DeviceFenceStatusBo();
newStatus.setDeviceId(request.getDeviceId());
newStatus.setFenceId(fence.getId());
newStatus.setStatus(currentStatus);
newStatus.setLastCheckTime(new Date());
// 保存状态记录
fenceStatusService.insertByBo(newStatus);
// 添加围栏进出记录
DeviceFenceAccessRecordBo recordBo = new DeviceFenceAccessRecordBo();
recordBo.setDeviceId(request.getDeviceId());
recordBo.setFenceId(fence.getId());
recordBo.setLatitude(request.getLatitude());
recordBo.setLongitude(request.getLongitude());
recordBo.setEventTime(new Date());
// 1表示进入围栏2表示离开围栏
recordBo.setEventType(currentStatus);
fenceAccessRecordService.insertByBo(recordBo);
// 根据状态变化更新响应
if (currentStatus == 1L) {
// 设备进入围栏
response.getEnteredFences().add(fenceInfo);
} else {
// 设备离开围栏
response.getExitedFences().add(fenceInfo);
}
} else if (currentStatus == 1L) {
// 状态未变,但设备仍在围栏内
response.getCurrentFences().add(fenceInfo);
}
}
return response;
}
}

View File

@ -40,7 +40,9 @@ import com.fuyuanshen.equipment.service.DeviceAssignmentsService;
import com.fuyuanshen.equipment.service.DeviceService;
import com.fuyuanshen.equipment.service.DeviceTypeGrantsService;
import com.fuyuanshen.system.domain.vo.SysOssVo;
import com.fuyuanshen.system.domain.vo.SysRoleVo;
import com.fuyuanshen.system.service.ISysOssService;
import com.fuyuanshen.system.service.ISysRoleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -67,6 +69,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
public static final String USER_ID_SEPARATOR = ":";
private final ISysRoleService roleService;
@Value("${file.device.pic}")
private String filePath;
@Value("${file.device.ip}")
@ -109,6 +113,16 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
if (username.equals("admin")) {
criteria.setIsAdmin(true);
}
// 角色管理员
Long userId = LoginHelper.getUserId();
List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
if (CollectionUtil.isNotEmpty(roles)) {
for (SysRoleVo role : roles) {
if (role.getRoleKey().equals("admin")) {
criteria.setIsAdmin(true);
}
}
}
IPage<Device> devices = deviceMapper.findAll(criteria, page);
@ -238,7 +252,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
DeviceTypeQueryCriteria deviceTypeQueryCriteria = new DeviceTypeQueryCriteria();
deviceTypeQueryCriteria.setDeviceTypeId(deviceAssignments.getDeviceId());
deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId());
// 被授权的客户
// deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId());
List<DeviceType> deviceTypes = deviceTypeMapper.findAll(deviceTypeQueryCriteria);
if (deviceTypes.isEmpty()) {
throw new Exception("设备类型不存在!!!");
@ -252,9 +267,13 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
// 处理上传的图片
if (deviceForm.getFile() != null) {
SysOssVo upload = ossService.upload(deviceForm.getFile());
// 设置图片路径
deviceForm.setDevicePic(upload.getUrl());
SysOssVo oss = ossService.upload(deviceForm.getFile());
// 强制将HTTP替换为HTTPS
if (oss.getUrl() != null && oss.getUrl().startsWith("http://")) {
oss.setUrl(oss.getUrl().replaceFirst("^http://", "https://"));
}
deviceForm.setDevicePic(oss.getUrl());
}
// 更新字段
@ -650,7 +669,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
* 获取设备使用数据
*
* @param deviceTypeId 设备ID
* @param range 时间范围 1:半年 2:一年
* @param range 时间范围 1:半年 2:一年
* @return 每月使用数据列表
*/
@Override
@ -660,4 +679,9 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
}
@Override
public Device selectDeviceByImei(String deviceImei) {
return baseMapper.selectDeviceByImei(deviceImei);
}
}

View File

@ -19,6 +19,8 @@ import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeGrantsMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceTypeService;
import com.fuyuanshen.system.domain.vo.SysRoleVo;
import com.fuyuanshen.system.service.ISysRoleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -43,6 +45,8 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
private final DeviceTypeGrantsMapper deviceTypeGrantsMapper;
private final DeviceAssignmentsMapper deviceAssignmentsMapper;
private final ISysRoleService roleService;
/**
* 分页查询设备类型
@ -54,8 +58,23 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
@Override
public TableDataInfo<DeviceType> queryAll(DeviceTypeQueryCriteria criteria, Page<DeviceType> page) {
// 管理员
String username = LoginHelper.getUsername();
if (!username.equals("admin")) {
// String username = LoginHelper.getUsername();
// if (!username.equals("admin")) {
// criteria.setCustomerId(LoginHelper.getUserId());
// }
// 角色管理员
Long userId = LoginHelper.getUserId();
List<SysRoleVo> roles = roleService.selectRolesAuthByUserId(userId);
boolean isAdmin = false;
if (CollectionUtil.isNotEmpty(roles)) {
for (SysRoleVo role : roles) {
if (role.getRoleKey().contains("admin")) {
isAdmin = true;
break;
}
}
}
if (!isAdmin) {
criteria.setCustomerId(LoginHelper.getUserId());
}

View File

@ -0,0 +1,24 @@
package com.fuyuanshen.equipment.utils.map;
/**
* @author: 默苍璃
* @date: 2025-09-1110:27
*/
public class Coordinate {
private double lat;
private double lng;
// 构造函数
public Coordinate() {}
public Coordinate(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
// getter和setter方法
public double getLat() { return lat; }
public void setLat(double lat) { this.lat = lat; }
public double getLng() { return lng; }
public void setLng(double lng) { this.lng = lng; }
}

View File

@ -0,0 +1,135 @@
package com.fuyuanshen.equipment.utils.map;
import java.util.List;
/**
* @author: 默苍璃
* @date: 2025-09-1110:13
*/
public class GeoFenceChecker {
/**
* 点是否在围栏内
*
* @param pointLat 点的纬度
* @param pointLng 点的经度
* @param fenceType 围栏类型0 POLYGON 或 1 CIRCLE
* @param coordinates 围栏坐标点列表
* @param radius 圆形围栏半径仅对CIRCLE类型有效
* @return true表示在围栏内false表示在围栏外
*/
public static boolean isPointInFence(double pointLat, double pointLng,
Integer fenceType,
List<Coordinate> coordinates,
Long radius) {
if (fenceType == 0) {
return isPointInPolygon(pointLat, pointLng, coordinates);
} else if (fenceType == 1) {
if (coordinates == null || coordinates.isEmpty() || radius == null) {
return false;
}
// 圆形围栏只有一个坐标点(圆心)
Coordinate center = coordinates.get(0);
return isPointInCircle(pointLat, pointLng, center.lat, center.lng, radius);
}
return false;
}
/**
* 使用射线投射算法判断点是否在多边形内
*
* @param pointLat 点的纬度
* @param pointLng 点的经度
* @param polygon 多边形顶点坐标列表
* @return true表示在多边形内false表示在多边形外
*/
public static boolean isPointInPolygon(double pointLat, double pointLng,
List<Coordinate> polygon) {
if (polygon == null || polygon.size() < 3) {
return false;
}
int intersectCount = 0;
int vertexCount = polygon.size();
// 遍历多边形的每条边
for (int i = 0; i < vertexCount; i++) {
Coordinate p1 = polygon.get(i);
Coordinate p2 = polygon.get((i + 1) % vertexCount);
// 检查点是否在边的y范围内
if (p1.lat != p2.lat &&
(pointLat >= Math.min(p1.lat, p2.lat)) &&
(pointLat < Math.max(p1.lat, p2.lat))) {
// 计算边与从点发出的水平射线的交点经度
double intersectLng = (pointLat - p1.lat) * (p2.lng - p1.lng) / (p2.lat - p1.lat) + p1.lng;
// 如果交点在点的右侧则计数加1
if (pointLng < intersectLng) {
intersectCount++;
}
}
}
// 奇数个交点表示点在多边形内
return intersectCount % 2 == 1;
}
/**
* 判断点是否在圆形内
*
* @param pointLat 点的纬度
* @param pointLng 点的经度
* @param centerLat 圆心纬度
* @param centerLng 圆心经度
* @param radius 半径(米)
* @return true表示在圆内false表示在圆外
*/
public static boolean isPointInCircle(double pointLat, double pointLng,
double centerLat, double centerLng,
Long radius) {
double distance = calculateDistance(pointLat, pointLng, centerLat, centerLng);
return distance <= radius;
}
/**
* 计算两点间距离使用Haversine公式
*
* @param lat1 点1纬度
* @param lng1 点1经度
* @param lat2 点2纬度
* @param lng2 点2经度
* @return 距离(米)
*/
public static double calculateDistance(double lat1, double lng1, double lat2, double lng2) {
final double EARTH_RADIUS = 6371000; // 地球半径(米)
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c;
}
/**
* 坐标点类
*/
public static class Coordinate {
public double lat;
public double lng;
public Coordinate() {
}
public Coordinate(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
}
}

View File

@ -6,17 +6,21 @@
<!-- 查询设备告警列表 -->
<select id="selectVoPage" resultType="com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo">
select *, d.device_mac as deviceMac, d.device_imei as deviceImei,
select da.*, d.device_mac as deviceMac, d.device_imei as deviceImei,d.device_name as deviceName,
d.device_type as deviceType,
d.type_name as deviceTypeName, d.device_pic as devicePic
from device_alarm da
left join device d on da.device_id = d.id
left join device_type dt on dt.id = da.device_type
left join device_type dt on dt.id = d.device_type
<where>
<if test="bo.content != null">
and d.device_name like concat('%', #{bo.content}, '%') or dt.type_name like concat('%', #{bo.content}, '%')
</if>
<if test="bo.deviceName != null">
and da.device_name = #{bo.deviceName}
and d.device_name like concat('%', #{bo.deviceName}, '%')
</if>
<if test="bo.deviceType != null">
and da.device_type = #{bo.deviceType}
and dt.id = #{bo.deviceType}
</if>
<if test="bo.deviceAction != null">
and da.device_action = #{bo.deviceAction}
@ -30,4 +34,20 @@
</where>
</select>
<!-- 根据设备IMEI查询最新的一条告警数据 -->
<select id="selectLatestByDeviceImei" resultType="com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo">
select *, d.device_mac as deviceMac, d.device_imei as deviceImei,
d.type_name as deviceTypeName, d.device_pic as devicePic
from device_alarm da
left join device d on da.device_id = d.id
left join device_type dt on dt.id = da.device_type
<where>
da.device_imei = #{deviceImei}
</where>
order by da.create_time desc
limit 1
</select>
</mapper>

View File

@ -0,0 +1,29 @@
<?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.fuyuanshen.equipment.mapper.DeviceFenceAccessRecordMapper">
<!-- 修改后的分页查询围栏进出记录列表 -->
<select id="selectVoPageWithFenceAndDeviceName"
resultType="com.fuyuanshen.equipment.domain.vo.DeviceFenceAccessRecordVo">
SELECT r.id,
r.fence_id,
f.name AS fence_name,
r.device_id,
d.device_name,
r.user_id,
r.event_type,
r.latitude,
r.longitude,
r.accuracy,
r.event_time,
r.create_time
FROM device_fence_access_record r
LEFT JOIN device_geo_fence f ON r.fence_id = f.id
LEFT JOIN device d ON r.device_id = d.id
${ew.customSqlSegment}
ORDER BY r.id ASC
</select>
</mapper>

View File

@ -0,0 +1,7 @@
<?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.fuyuanshen.equipment.mapper.DeviceFenceStatusMapper">
</mapper>

View File

@ -0,0 +1,7 @@
<?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.fuyuanshen.equipment.mapper.DeviceGeoFenceMapper">
</mapper>

View File

@ -251,12 +251,13 @@
dt.pc_model_dictionary detailPageUrl,
ap.name personnelBy,
d.device_status,
d.online_status,
c.binding_time
from device d
inner join device_type dt on d.device_type = dt.id
inner join app_device_bind_record c on d.id = c.device_id
inner join app_device_bind_record c on d.id = c.device_id and c.communication_mode = 0
left join app_personnel_info ap on ap.device_id = d.id
where dt.communication_mode = 0
where dt.communication_mode in (0, 2)
<if test="criteria.deviceType != null">
and d.device_type = #{criteria.deviceType}
</if>
@ -325,6 +326,9 @@
(SELECT COUNT(1) FROM device WHERE DATE (create_time) = CURDATE() AND binding_status = 1 ) AS bindingNew, (
SELECT COUNT (1)
FROM device
WHERE binding_status = 1 ) AS binding, (
SELECT COUNT (1)
FROM device
WHERE online_status = 2) AS equipmentAbnormal
</select>
@ -347,13 +351,13 @@
<!-- 获取告警信息 -->
<select id="getAlarmInformation" resultType="com.fuyuanshen.equipment.domain.vo.AlarmInformationVo">
SELECT (SELECT COUNT(1) FROM device_alarm WHERE treatment_state = 0) AS alarmsTotal
SELECT (SELECT COUNT(1) FROM device_alarm) AS alarmsTotal
, (SELECT COUNT(1)
FROM device_alarm
WHERE treatment_state = 0) AS processingAlarm
WHERE treatment_state = 0) AS processingAlarm
, (SELECT COUNT(1)
FROM device_alarm
WHERE treatment_state = 0 AND
WHERE
DATE (create_time) = CURDATE()) AS alarmsTotalToday
, (
SELECT COUNT (1)
@ -419,4 +423,11 @@
AND MONTH (dl.create_time) = #{month}
</select>
<!-- 根据设备IMEI查询设备 -->
<select id="selectDeviceByImei" resultType="com.fuyuanshen.equipment.domain.Device">
SELECT *
FROM device
WHERE device_imei = #{deviceImei}
</select>
</mapper>

Some files were not shown because too many files have changed in this diff Show More