35 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
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: dyf/fys-Multi-tenant#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
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
801b0267e6 设备分享3 2025-09-09 17:01:51 +08:00
74 changed files with 3286 additions and 103 deletions

View File

@ -4,15 +4,24 @@ import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; 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.model.AppSmsLoginBody;
import com.fuyuanshen.app.service.AppLoginService; 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.constant.SystemConstants;
import com.fuyuanshen.common.core.domain.R; 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.RegisterBody;
import com.fuyuanshen.common.core.domain.model.SmsLoginBody; 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.core.utils.*;
import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt; import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt;
import com.fuyuanshen.common.json.utils.JsonUtils; 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.satoken.utils.LoginHelper;
import com.fuyuanshen.common.tenant.helper.TenantHelper; import com.fuyuanshen.common.tenant.helper.TenantHelper;
import com.fuyuanshen.system.domain.bo.SysTenantBo; import com.fuyuanshen.system.domain.bo.SysTenantBo;
@ -29,6 +38,7 @@ import com.fuyuanshen.web.domain.vo.TenantListVo;
import com.fuyuanshen.web.service.IAuthStrategy; import com.fuyuanshen.web.service.IAuthStrategy;
import com.fuyuanshen.web.service.SysRegisterService; import com.fuyuanshen.web.service.SysRegisterService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.SmsBlend;
@ -38,9 +48,13 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.net.URL; import java.net.URL;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.fuyuanshen.common.core.constant.GlobalConstants.DEVICE_SHARE_CODES_KEY;
/** /**
* APP认证 * APP认证
* *
@ -54,7 +68,7 @@ import java.util.List;
public class AppAuthController { public class AppAuthController {
private final AppLoginService loginService; private final AppLoginService loginService;
private final SysRegisterService registerService; private final AppRegisterService registerService;
private final ISysConfigService configService; private final ISysConfigService configService;
private final ISysTenantService tenantService; private final ISysTenantService tenantService;
private final ISysClientService clientService; private final ISysClientService clientService;
@ -70,16 +84,34 @@ public class AppAuthController {
@PostMapping("/login") @PostMapping("/login")
public R<LoginVo> login(@RequestBody AppSmsLoginBody appSmsLoginBody) { public R<LoginVo> login(@RequestBody AppSmsLoginBody appSmsLoginBody) {
// SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class); // SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(appSmsLoginBody); String loginType = appSmsLoginBody.getLoginType();
SmsLoginBody loginBody = new SmsLoginBody(); String body = "";
loginBody.setPhonenumber(appSmsLoginBody.getPhonenumber());
loginBody.setSmsCode(appSmsLoginBody.getSmsCode());
loginBody.setTenantId(appSmsLoginBody.getTenantId());
loginBody.setClientId("ca839698e245d60aa2f0e59bd52b34f8");
loginBody.setGrantType("appSms");
// 授权类型和客户端id // 授权类型和客户端id
String clientId = loginBody.getClientId(); String clientId = "";
String grantType = loginBody.getGrantType(); 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); SysClientVo client = clientService.queryByClientId(clientId);
// 查询不到 client 或 client 内不包含 grantType // 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) { if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
@ -89,25 +121,14 @@ public class AppAuthController {
return R.fail(MessageUtils.message("auth.grant.type.blocked")); 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); LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
return R.ok(loginVo); return R.ok(loginVo);
} }
/**
* 退出登录
*/
@PostMapping("/logout")
public R<Void> logout() {
loginService.logout();
return R.ok("退出成功");
}
/** /**
* 用户注销 * 用户注销
*/ */
@ -117,16 +138,68 @@ public class AppAuthController {
return R.ok("用户注销成功"); return R.ok("用户注销成功");
} }
/** /**
* 用户注册 * 用户注册
*/ */
@ApiEncrypt
@PostMapping("/register") @PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) { public R<Void> register(@Validated @RequestBody AppRegisterBody body) {
if (!configService.selectRegisterEnabled(user.getTenantId())) { registerService.register(body);
return R.fail("当前系统没有开启注册功能!"); 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(); return R.ok();
} }

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,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 * 短信code
*/ */
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode; private String smsCode;
/** /**
@ -23,4 +22,14 @@ public class AppSmsLoginBody {
@NotBlank(message = "租户ID不能为空") @NotBlank(message = "租户ID不能为空")
private String tenantId; 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

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

View File

@ -49,12 +49,12 @@ public class ReceiverMessageHandler implements MessageHandler {
String[] subStr = receivedTopic.split("/"); String[] subStr = receivedTopic.split("/");
String deviceImei = subStr[1]; String deviceImei = subStr[1];
if(StringUtils.isNotBlank(deviceImei)){ 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 ; String deviceOnlineStatusRedisKey = GlobalConstants.GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX+ deviceImei + DeviceRedisKeyConstants.DEVICE_ONLINE_STATUS_KEY_PREFIX ;
RedisUtils.setCacheObject(deviceOnlineStatusRedisKey, "1", Duration.ofSeconds(62)); 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"); String state = payloadDict.getStr("state");

View File

@ -1,8 +1,18 @@
package com.fuyuanshen.global.mqtt.rule.bjq; 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.constant.GlobalConstants;
import com.fuyuanshen.common.core.domain.model.LoginUser;
import com.fuyuanshen.common.core.utils.StringUtils; 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.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.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext; import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
@ -13,9 +23,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY; 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.*; import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
/** /**
@ -26,6 +38,10 @@ import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*;
@Slf4j @Slf4j
public class BjqAlarmRule implements MqttMessageRule { public class BjqAlarmRule implements MqttMessageRule {
private final IDeviceAlarmService deviceAlarmService;
private final DeviceService deviceService;
@Override @Override
public String getCommandType() { public String getCommandType() {
return LightingCommandTypeConstants.ALARM_MESSAGE; return LightingCommandTypeConstants.ALARM_MESSAGE;
@ -38,14 +54,21 @@ public class BjqAlarmRule implements MqttMessageRule {
Object[] convertArr = context.getConvertArr(); Object[] convertArr = context.getConvertArr();
String convertValue = convertArr[1].toString(); String convertValue = convertArr[1].toString();
if(StringUtils.isNotBlank(convertValue)){ if (StringUtils.isNotBlank(convertValue)) {
// 将设备状态信息存储到Redis中 // 将设备状态信息存储到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 // 存储到Redis
RedisUtils.setCacheObject(deviceRedisKey, convertValue); RedisUtils.setCacheObject(deviceRedisKey, convertValue);
} }
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); 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) { } catch (Exception e) {
log.error("处理告警命令时出错", e); log.error("处理告警命令时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); 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

@ -1,9 +1,13 @@
package com.fuyuanshen.global.queue; package com.fuyuanshen.global.queue;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; 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.common.redis.utils.RedisUtils;
import com.fuyuanshen.equipment.domain.Device; import com.fuyuanshen.equipment.domain.Device;
import com.fuyuanshen.equipment.mapper.DeviceMapper; import com.fuyuanshen.equipment.mapper.DeviceMapper;
import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -15,6 +19,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
@Service @Service
@Slf4j @Slf4j
public class MqttMessageConsumer { public class MqttMessageConsumer {
@ -27,7 +33,7 @@ public class MqttMessageConsumer {
private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10); private ExecutorService messageProcessorPool = Executors.newFixedThreadPool(10);
// 初始化方法,启动消息监听 // 初始化方法,启动消息监听
// @PostConstruct @PostConstruct
public void start() { public void start() {
log.info("启动MQTT消息消费者..."); log.info("启动MQTT消息消费者...");
// 启动消息获取线程 // 启动消息获取线程
@ -62,7 +68,7 @@ public class MqttMessageConsumer {
public void consumeMessages() { public void consumeMessages() {
String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY; String queueKey = MqttMessageQueueConstants.MQTT_MESSAGE_QUEUE_KEY;
String threadName = Thread.currentThread().getName(); String threadName = Thread.currentThread().getName();
log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey); // log.info("消息消费者线程 {} 开始监听队列: {}", threadName, queueKey);
try { try {
while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) { while (!Thread.currentThread().isInterrupted() && !messageConsumerPool.isShutdown()) {
@ -75,7 +81,7 @@ public class MqttMessageConsumer {
); );
if (message != null) { if (message != null) {
log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message); // log.info("线程 {} 从队列中获取到消息,提交到处理线程池: {}", threadName, message);
// 将消息处理任务提交到处理线程池 // 将消息处理任务提交到处理线程池
messageProcessorPool.submit(() -> processMessage(message)); messageProcessorPool.submit(() -> processMessage(message));
} }
@ -92,15 +98,23 @@ public class MqttMessageConsumer {
String threadName = Thread.currentThread().getName(); String threadName = Thread.currentThread().getName();
try { try {
log.info("业务处理线程 {} 开始处理消息: {}", threadName, message); log.info("业务处理线程 {} 开始处理消息: {}", threadName, message);
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<Device> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("device_imei", message) updateWrapper.eq("device_imei", message)
.set("online_status", 1); .set("online_status", 1);
deviceMapper.update(updateWrapper); 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); log.info("业务处理线程 {} 完成消息处理: {}", threadName, message);
} catch (Exception e) { } catch (Exception e) {

View File

@ -67,7 +67,7 @@ public class DeviceShareController extends BaseController {
@RepeatSubmit() @RepeatSubmit()
@PostMapping("/permission") @PostMapping("/permission")
public R<Void> permission(@Validated(AddGroup.class) @RequestBody AppDeviceShareBo bo) { 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,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,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<>(); LambdaQueryWrapper<AppDeviceBindRecord> qw = new LambdaQueryWrapper<>();
qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId()); qw.eq(AppDeviceBindRecord::getDeviceId, bo.getDeviceId());
AppDeviceBindRecord bindRecord = appDeviceBindRecordMapper.selectOne(qw); 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(); Long userId = bindRecord.getBindingUserId();
LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<AppDeviceShare> lqw = new LambdaQueryWrapper<>();

View File

@ -182,12 +182,14 @@ public class DeviceBizService {
QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>(); QueryWrapper<AppDeviceBindRecord> bindRecordQueryWrapper = new QueryWrapper<>();
bindRecordQueryWrapper.eq("device_id", device.getId()); bindRecordQueryWrapper.eq("device_id", device.getId());
bindRecordQueryWrapper.eq("communication_mode", 0);
AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper); AppDeviceBindRecord appDeviceBindRecord = appDeviceBindRecordMapper.selectOne(bindRecordQueryWrapper);
if (appDeviceBindRecord != null) { if (appDeviceBindRecord != null) {
UpdateWrapper<AppDeviceBindRecord> deviceUpdateWrapper = new UpdateWrapper<>(); UpdateWrapper<AppDeviceBindRecord> deviceUpdateWrapper = new UpdateWrapper<>();
deviceUpdateWrapper.eq("device_id", device.getId()) deviceUpdateWrapper.eq("device_id", device.getId())
.set("binding_status", BindingStatusEnum.BOUND.getCode()) .set("binding_status", BindingStatusEnum.BOUND.getCode())
.set("binding_user_id", userId) .set("binding_user_id", userId)
.set("communication_mode", 0)
.set("update_time", new Date()) .set("update_time", new Date())
.set("binding_time", new Date()); .set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper); return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
@ -196,6 +198,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId()); bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId); bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date()); bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(0);
bindRecord.setCreateBy(userId); bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord); appDeviceBindRecordMapper.insert(bindRecord);
} }
@ -225,6 +228,7 @@ public class DeviceBizService {
deviceUpdateWrapper.eq("device_id", device.getId()) deviceUpdateWrapper.eq("device_id", device.getId())
.eq("binding_user_id", userId) .eq("binding_user_id", userId)
.set("binding_user_id", userId) .set("binding_user_id", userId)
.set("communication_mode", 1)
.set("binding_time", new Date()); .set("binding_time", new Date());
return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper); return appDeviceBindRecordMapper.update(null, deviceUpdateWrapper);
} else { } else {
@ -232,6 +236,7 @@ public class DeviceBizService {
bindRecord.setDeviceId(device.getId()); bindRecord.setDeviceId(device.getId());
bindRecord.setBindingUserId(userId); bindRecord.setBindingUserId(userId);
bindRecord.setBindingTime(new Date()); bindRecord.setBindingTime(new Date());
bindRecord.setCommunicationMode(1);
bindRecord.setCreateBy(userId); bindRecord.setCreateBy(userId);
appDeviceBindRecordMapper.insert(bindRecord); appDeviceBindRecordMapper.insert(bindRecord);
} }

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

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

View File

@ -17,6 +17,16 @@ public interface GlobalConstants {
*/ */
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:"; 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 * 设备分享验证码 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 Date bindingTime;
private Integer communicationMode;
} }

View File

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

View File

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

View File

@ -72,9 +72,12 @@
and ad.device_id = #{bo.deviceId} and ad.device_id = #{bo.deviceId}
</if> </if>
<if test="bo.shareUser != null"> <if test="bo.shareUser != null">
and u.user_name = #{bo.shareUser} and ad.phonenumber like concat('%',#{bo.shareUser},'%')
</if> </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} and ad.create_time between #{bo.shareStartTime} and #{bo.shareEndTime}
</if> </if>
</where> </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.fuyuanshen.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import io.github.linpeilie.annotations.AutoMapping;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; 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未处理 * 0已处理1未处理
* treatment_state * 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") @Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI")
private String networkWay; private String networkWay;
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙") @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private String communicationMode; 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.core.validate.EditGroup;
import com.fuyuanshen.common.mybatis.core.domain.BaseEntity; import com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import com.fuyuanshen.common.tenant.core.TenantEntity;
import com.fuyuanshen.equipment.domain.DeviceAlarm; import com.fuyuanshen.equipment.domain.DeviceAlarm;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -19,7 +20,7 @@ import java.util.Date;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@AutoMapper(target = DeviceAlarm.class, reverseConvertGenerate = false) @AutoMapper(target = DeviceAlarm.class, reverseConvertGenerate = false)
public class DeviceAlarmBo extends BaseEntity { public class DeviceAlarmBo extends TenantEntity {
/** /**
* ID * ID
@ -35,6 +36,7 @@ public class DeviceAlarmBo extends BaseEntity {
/** /**
* 报警事项 * 报警事项
* device_action * device_action
* 0-强制报警1-撞击闯入2-自动报警3-电子围栏告警
*/ */
private Integer deviceAction; private Integer deviceAction;
@ -61,7 +63,7 @@ public class DeviceAlarmBo extends BaseEntity {
private String dataSource; private String dataSource;
/** /**
* 内容 * 内容(报警信息)
*/ */
private String content; 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 com.fuyuanshen.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data; import lombok.Data;
import cn.hutool.core.date.DateUtil;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.Date; import java.util.Date;
@ -44,13 +45,25 @@ public class DeviceChargeDischargeBo extends BaseEntity {
* 开始时间 * 开始时间
*/ */
@NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class }) @NotNull(message = "开始时间不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startTime; private Date startTime;
// 添加字符串类型的setter方法来处理前端传递的字符串日期
public void setStartTime(String startTime) {
this.startTime = DateUtil.parseDate(startTime);
}
/** /**
* 结束时间 * 结束时间
*/ */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endTime; private Date endTime;
// 添加字符串类型的setter方法来处理前端传递的字符串日期
public void setEndTime(String endTime) {
this.endTime = DateUtil.parseDate(endTime);
}
/** /**
* 起始电量百分比(0-100) * 起始电量百分比(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; private String deviceMac;
/** /**
* 通讯方式 0:4G; 1:蓝牙 * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/ */
@NotNull(message = "通讯方式不能为空", groups = { EditGroup.class }) @NotNull(message = "通讯方式不能为空", groups = { EditGroup.class })
private Integer communicationMode; 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; private Boolean isSupportBle;
@Schema(title = "定位方式", example = "0:无;1:GPS;2:基站;3:wifi;4:北斗") @Schema(title = "定位方式", example = "0:无;1:GPS;2:基站;3:wifi;4:北斗")
private String locateMode; private Integer locateMode;
@Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI") @Schema(title = "联网方式", example = "0:无;1:4G;2:WIFI")
private String networkWay; private Integer networkWay;
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙") @Schema(title = "通讯方式", example = "通讯方式 0:4G;1:蓝牙,2 4G&蓝牙")
private String communicationMode; private Integer communicationMode;
/** /**
* 型号字典用于APP页面跳转 * 型号字典用于APP页面跳转

View File

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

View File

@ -82,9 +82,10 @@ public class DeviceQueryCriteria extends BaseEntity {
private String tenantId; private String tenantId;
/** /**
* 通讯方式 0:4G;1:蓝牙 * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
* communication_mode * communication_mode
*/ */
@Schema(title = "通讯方式", example = "0:4G;1:蓝牙,2 4G&蓝牙")
private Integer communicationMode; private Integer communicationMode;
/* app绑定用户id */ /* 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 alarmManual = 0;
/** /**
* 电子围栏 *
*/ */
private Integer fenceElectronic = 0; private Integer fenceElectronic = 0;

View File

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

View File

@ -26,6 +26,11 @@ public class DataOverviewVo {
*/ */
private Integer bindingNew = 0; 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 com.fuyuanshen.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.io.Serial; import java.io.Serial;
@ -45,7 +46,7 @@ public class DeviceAlarmVo implements Serializable {
/** /**
* 报警事项 * 报警事项
* 0-强制报警1-撞击闯入2-动报警3-电子围栏告警4-强制告警 * 0-强制报警1-撞击闯入2-动报警3-电子围栏告警
*/ */
@ExcelProperty(value = "报警事项") @ExcelProperty(value = "报警事项")
private Integer deviceAction; private Integer deviceAction;
@ -134,4 +135,5 @@ public class DeviceAlarmVo implements Serializable {
@Schema(name = "设备图片") @Schema(name = "设备图片")
private String devicePic; 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; private String deviceMac;
/** /**
* 通讯方式 0:4G;1:蓝牙 * 通讯方式 0:4G;1:蓝牙,2 4G&蓝牙
*/ */
private Integer communicationMode; 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); 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, int getUsageDataForMonth(@Param("deviceId") Long deviceId,
@Param("year") int year, @Param("year") int year,
@Param("month") int month); @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 * @return
*/ */
List<Map<String, Object>> getEquipmentUsageData(Long deviceTypeId, Integer range); 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 是否删除成功 * @return 是否删除成功
*/ */
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); 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; package com.fuyuanshen.equipment.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.fuyuanshen.common.core.utils.MapstructUtils; import com.fuyuanshen.common.core.utils.MapstructUtils;
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo; import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
import com.fuyuanshen.common.mybatis.core.page.PageQuery; import com.fuyuanshen.common.mybatis.core.page.PageQuery;
@ -102,11 +103,13 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
*/ */
@Override @Override
public Boolean insertByBo(DeviceAlarmBo bo) { public Boolean insertByBo(DeviceAlarmBo bo) {
DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class); DeviceAlarm deviceAlarm = new DeviceAlarm();
validEntityBeforeSave(add); // DeviceAlarm add = MapstructUtils.convert(bo, DeviceAlarm.class);
boolean flag = baseMapper.insert(add) > 0; BeanUtil.copyProperties(bo, deviceAlarm);
validEntityBeforeSave(deviceAlarm);
boolean flag = baseMapper.insert(deviceAlarm) > 0;
if (flag) { if (flag) {
bo.setId(add.getId()); bo.setId(deviceAlarm.getId());
} }
return flag; return flag;
} }
@ -145,4 +148,17 @@ public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
} }
return baseMapper.deleteByIds(ids) > 0; 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.DeviceService;
import com.fuyuanshen.equipment.service.DeviceTypeGrantsService; import com.fuyuanshen.equipment.service.DeviceTypeGrantsService;
import com.fuyuanshen.system.domain.vo.SysOssVo; 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.ISysOssService;
import com.fuyuanshen.system.service.ISysRoleService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; 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 = ":"; public static final String USER_ID_SEPARATOR = ":";
private final ISysRoleService roleService;
@Value("${file.device.pic}") @Value("${file.device.pic}")
private String filePath; private String filePath;
@Value("${file.device.ip}") @Value("${file.device.ip}")
@ -109,6 +113,16 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
if (username.equals("admin")) { if (username.equals("admin")) {
criteria.setIsAdmin(true); 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); IPage<Device> devices = deviceMapper.findAll(criteria, page);
@ -238,7 +252,8 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
DeviceTypeQueryCriteria deviceTypeQueryCriteria = new DeviceTypeQueryCriteria(); DeviceTypeQueryCriteria deviceTypeQueryCriteria = new DeviceTypeQueryCriteria();
deviceTypeQueryCriteria.setDeviceTypeId(deviceAssignments.getDeviceId()); deviceTypeQueryCriteria.setDeviceTypeId(deviceAssignments.getDeviceId());
deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId()); // 被授权的客户
// deviceTypeQueryCriteria.setCustomerId(LoginHelper.getUserId());
List<DeviceType> deviceTypes = deviceTypeMapper.findAll(deviceTypeQueryCriteria); List<DeviceType> deviceTypes = deviceTypeMapper.findAll(deviceTypeQueryCriteria);
if (deviceTypes.isEmpty()) { if (deviceTypes.isEmpty()) {
throw new Exception("设备类型不存在!!!"); throw new Exception("设备类型不存在!!!");
@ -252,9 +267,13 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
// 处理上传的图片 // 处理上传的图片
if (deviceForm.getFile() != null) { 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 deviceTypeId 设备ID
* @param range 时间范围 1:半年 2:一年 * @param range 时间范围 1:半年 2:一年
* @return 每月使用数据列表 * @return 每月使用数据列表
*/ */
@Override @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.DeviceTypeGrantsMapper;
import com.fuyuanshen.equipment.mapper.DeviceTypeMapper; import com.fuyuanshen.equipment.mapper.DeviceTypeMapper;
import com.fuyuanshen.equipment.service.DeviceTypeService; import com.fuyuanshen.equipment.service.DeviceTypeService;
import com.fuyuanshen.system.domain.vo.SysRoleVo;
import com.fuyuanshen.system.service.ISysRoleService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -43,6 +45,8 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
private final DeviceTypeGrantsMapper deviceTypeGrantsMapper; private final DeviceTypeGrantsMapper deviceTypeGrantsMapper;
private final DeviceAssignmentsMapper deviceAssignmentsMapper; private final DeviceAssignmentsMapper deviceAssignmentsMapper;
private final ISysRoleService roleService;
/** /**
* 分页查询设备类型 * 分页查询设备类型
@ -54,8 +58,23 @@ public class DeviceTypeServiceImpl extends ServiceImpl<DeviceTypeMapper, DeviceT
@Override @Override
public TableDataInfo<DeviceType> queryAll(DeviceTypeQueryCriteria criteria, Page<DeviceType> page) { public TableDataInfo<DeviceType> queryAll(DeviceTypeQueryCriteria criteria, Page<DeviceType> page) {
// 管理员 // 管理员
String username = LoginHelper.getUsername(); // String username = LoginHelper.getUsername();
if (!username.equals("admin")) { // 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()); 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 id="selectVoPage" resultType="com.fuyuanshen.equipment.domain.vo.DeviceAlarmVo">
select da.*, 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 d.type_name as deviceTypeName, d.device_pic as devicePic
from device_alarm da from device_alarm da
left join device d on da.device_id = d.id 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> <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"> <if test="bo.deviceName != null">
and da.device_name = #{bo.deviceName} and d.device_name like concat('%', #{bo.deviceName}, '%')
</if> </if>
<if test="bo.deviceType != null"> <if test="bo.deviceType != null">
and da.device_type = #{bo.deviceType} and dt.id = #{bo.deviceType}
</if> </if>
<if test="bo.deviceAction != null"> <if test="bo.deviceAction != null">
and da.device_action = #{bo.deviceAction} and da.device_action = #{bo.deviceAction}
@ -30,4 +34,20 @@
</where> </where>
</select> </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> </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, dt.pc_model_dictionary detailPageUrl,
ap.name personnelBy, ap.name personnelBy,
d.device_status, d.device_status,
d.online_status,
c.binding_time c.binding_time
from device d from device d
inner join device_type dt on d.device_type = dt.id 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 left join app_personnel_info ap on ap.device_id = d.id
where dt.communication_mode in(0,2) where dt.communication_mode in (0, 2)
<if test="criteria.deviceType != null"> <if test="criteria.deviceType != null">
and d.device_type = #{criteria.deviceType} and d.device_type = #{criteria.deviceType}
</if> </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 DATE (create_time) = CURDATE() AND binding_status = 1 ) AS bindingNew, (
SELECT COUNT (1) SELECT COUNT (1)
FROM device FROM device
WHERE binding_status = 1 ) AS binding, (
SELECT COUNT (1)
FROM device
WHERE online_status = 2) AS equipmentAbnormal WHERE online_status = 2) AS equipmentAbnormal
</select> </select>
@ -347,13 +351,13 @@
<!-- 获取告警信息 --> <!-- 获取告警信息 -->
<select id="getAlarmInformation" resultType="com.fuyuanshen.equipment.domain.vo.AlarmInformationVo"> <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) , (SELECT COUNT(1)
FROM device_alarm FROM device_alarm
WHERE treatment_state = 0) AS processingAlarm WHERE treatment_state = 0) AS processingAlarm
, (SELECT COUNT(1) , (SELECT COUNT(1)
FROM device_alarm FROM device_alarm
WHERE treatment_state = 0 AND WHERE
DATE (create_time) = CURDATE()) AS alarmsTotalToday DATE (create_time) = CURDATE()) AS alarmsTotalToday
, ( , (
SELECT COUNT (1) SELECT COUNT (1)
@ -419,4 +423,11 @@
AND MONTH (dl.create_time) = #{month} AND MONTH (dl.create_time) = #{month}
</select> </select>
<!-- 根据设备IMEI查询设备 -->
<select id="selectDeviceByImei" resultType="com.fuyuanshen.equipment.domain.Device">
SELECT *
FROM device
WHERE device_imei = #{deviceImei}
</select>
</mapper> </mapper>