考勤机

This commit is contained in:
lcj
2025-10-15 23:57:32 +08:00
parent 9f1da9e6c0
commit 6808057111
22 changed files with 1189 additions and 14 deletions

View File

@ -324,9 +324,9 @@ dxf2GeoJson:
file-name: main.exe file-name: main.exe
ys7: ys7:
app-key: 3acf9f1a43dc4209841e0893003db0a2 app-key: 3acf9f1a43dc4209841e0893003db0a2
app-secret: 4bbf3e9394f55d3af6e3af27b2d3db36 app-secret: 09e29c70ae1161fbc3ce2030fc09ba2e
job: job:
capture-enabled: false # 控制是否启用萤石抓拍任务 capture-enabled: true # 控制是否启用萤石抓拍任务
# 斯巴达算法 # 斯巴达算法
sparta: sparta:
url: http://119.3.204.120:8040 url: http://119.3.204.120:8040

View File

@ -355,7 +355,7 @@ public class MatMaterialsInventoryServiceImpl extends ServiceImpl<MatMaterialsIn
lqw.eq(ObjectUtils.isNotEmpty(number), MatMaterialsInventory::getNumber, number); lqw.eq(ObjectUtils.isNotEmpty(number), MatMaterialsInventory::getNumber, number);
lqw.eq(ObjectUtils.isNotEmpty(outPut), MatMaterialsInventory::getOutPut, outPut); lqw.eq(ObjectUtils.isNotEmpty(outPut), MatMaterialsInventory::getOutPut, outPut);
// 排序 // 排序
lqw.orderByDesc(MatMaterialsInventory::getCreateTime); lqw.orderByAsc(MatMaterialsInventory::getCreateTime);
return lqw; return lqw;
} }

View File

@ -23,7 +23,7 @@ public class DeviceMessageSender {
* @param face 人脸模板地址HTTP链接 * @param face 人脸模板地址HTTP链接
* @return 异常信息无异常则返回null * @return 异常信息无异常则返回null
*/ */
public static Exception sendPersonnelInformation(String sn, String userId, String name, String face) { public Boolean sendPersonnelInformation(String sn, String userId, String name, String face) {
try { try {
// 生成UUID // 生成UUID
String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits(); String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits();
@ -45,10 +45,10 @@ public class DeviceMessageSender {
// 发送请求并等待响应(忽略响应结果,只关注是否成功) // 发送请求并等待响应(忽略响应结果,只关注是否成功)
DeviceWebSocketServer.sendRequestAndWaitResponse(sn, sUuid, people); DeviceWebSocketServer.sendRequestAndWaitResponse(sn, sUuid, people);
return null; return true;
} catch (Exception e) { } catch (Exception e) {
log.error("下发人员信息失败SN: {}, UserID: {}", sn, userId, e); log.error("下发人员信息失败SN: {}, UserID: {}", sn, userId, e);
return e; return false;
} }
} }
@ -59,7 +59,7 @@ public class DeviceMessageSender {
* @return 响应结果(包含人员信息) * @return 响应结果(包含人员信息)
* @throws Exception 发送或接收过程中的异常 * @throws Exception 发送或接收过程中的异常
*/ */
public static KqjEntity.CommonResponse getAllUsers(String sn) throws Exception { public KqjEntity.CommonResponse getAllUsers(String sn) throws Exception {
// 生成UUID // 生成UUID
String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits(); String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits();
@ -87,7 +87,7 @@ public class DeviceMessageSender {
* @return 响应结果 * @return 响应结果
* @throws Exception 发送或接收过程中的异常 * @throws Exception 发送或接收过程中的异常
*/ */
public static KqjEntity.CommonResponse deleteUser(String sn, String userId) throws Exception { public KqjEntity.CommonResponse deleteUser(String sn, String userId) throws Exception {
// 生成UUID // 生成UUID
String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits(); String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits();
@ -116,7 +116,7 @@ public class DeviceMessageSender {
* @return 响应结果 * @return 响应结果
* @throws Exception 发送或接收过程中的异常 * @throws Exception 发送或接收过程中的异常
*/ */
public static KqjEntity.CommonResponse batchDeleteUsers(String sn, String[] userIds) throws Exception { public KqjEntity.CommonResponse batchDeleteUsers(String sn, String[] userIds) throws Exception {
// 生成UUID // 生成UUID
String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits(); String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits();
@ -144,7 +144,7 @@ public class DeviceMessageSender {
* @return 响应结果 * @return 响应结果
* @throws Exception 发送或接收过程中的异常 * @throws Exception 发送或接收过程中的异常
*/ */
public static KqjEntity.CommonResponse deleteAllUsers(String sn) throws Exception { public KqjEntity.CommonResponse deleteAllUsers(String sn) throws Exception {
// 生成UUID // 生成UUID
String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits(); String sUuid = DeviceWebSocketServer.generateUUIDWithSixRandomDigits();

View File

@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.websocket.*; import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint; import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.project.service.IBusAttendanceMachineService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
@ -17,7 +19,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* WebSocket服务端设备连接管理、消息处理 * WebSocket服务端设备连接管理、消息处理
@ -28,6 +29,8 @@ import java.util.concurrent.atomic.AtomicReference;
@Log4j2 @Log4j2
public class DeviceWebSocketServer { public class DeviceWebSocketServer {
private final static IBusAttendanceMachineService attendanceMachineService = SpringUtils.getBean(IBusAttendanceMachineService.class);
// ------------------------------ 常量定义 ------------------------------ // ------------------------------ 常量定义 ------------------------------
public static final String DECLARE = "declare"; // 设备注册消息 public static final String DECLARE = "declare"; // 设备注册消息
public static final String PING = "ping"; // 心跳消息 public static final String PING = "ping"; // 心跳消息
@ -36,7 +39,7 @@ public class DeviceWebSocketServer {
// ------------------------------ 全局静态存储 ------------------------------ // ------------------------------ 全局静态存储 ------------------------------
// JSON序列化工具单例 // JSON序列化工具单例
private static final ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);; private static final ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 设备连接池key=设备SNvalue=设备信息含Session // 设备连接池key=设备SNvalue=设备信息含Session
private static final Map<String, KqjEntity.DeviceInfo> connectedDevices = new ConcurrentHashMap<>(); private static final Map<String, KqjEntity.DeviceInfo> connectedDevices = new ConcurrentHashMap<>();
// 响应通道key=UUIDvalue=响应结果容器 // 响应通道key=UUIDvalue=响应结果容器
@ -212,7 +215,6 @@ public class DeviceWebSocketServer {
// registerFuture.complete(true); // 标记注册成功 // registerFuture.complete(true); // 标记注册成功
// log.info("设备注册成功SN: {}IP: {}:{}会话ID: {}", sn, ip, port, session.getId()); // log.info("设备注册成功SN: {}IP: {}:{}会话ID: {}", sn, ip, port, session.getId());
// } // }
private void handleRegisterStage(String message, String cmd) throws Exception { private void handleRegisterStage(String message, String cmd) throws Exception {
// 非DECLARE消息直接拒绝 // 非DECLARE消息直接拒绝
if (!DECLARE.equals(cmd)) { if (!DECLARE.equals(cmd)) {
@ -382,6 +384,11 @@ public class DeviceWebSocketServer {
// TODO: 替换为真实的业务服务调用如Spring Bean注入后调用 // TODO: 替换为真实的业务服务调用如Spring Bean注入后调用
log.info("【业务服务】设备注册SN: {}", sn); log.info("【业务服务】设备注册SN: {}", sn);
// 示例BusAttendanceMachineService.register(sn); // 示例BusAttendanceMachineService.register(sn);
if (attendanceMachineService != null) {
attendanceMachineService.insertBySn(sn);
} else {
log.error("IBusAttendanceMachineService 为空,无法进行设备注册");
}
} catch (Exception e) { } catch (Exception e) {
log.error("【业务服务】设备注册失败SN: {}", sn, e); log.error("【业务服务】设备注册失败SN: {}", sn, e);
} }
@ -395,6 +402,11 @@ public class DeviceWebSocketServer {
// TODO: 替换为真实的业务服务调用 // TODO: 替换为真实的业务服务调用
log.info("【业务服务】更新设备状态SN: {},状态: {}", sn, status); log.info("【业务服务】更新设备状态SN: {},状态: {}", sn, status);
// 示例BusAttendanceMachineService.changeStatus(sn, status); // 示例BusAttendanceMachineService.changeStatus(sn, status);
if (attendanceMachineService != null) {
attendanceMachineService.changeStatus(sn, status);
} else {
log.error("IBusAttendanceMachineService 为空,无法更新设备状态");
}
} catch (Exception e) { } catch (Exception e) {
log.error("【业务服务】更新设备状态失败SN: {}", sn, e); log.error("【业务服务】更新设备状态失败SN: {}", sn, e);
} }

View File

@ -0,0 +1,94 @@
package org.dromara.project.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineQueryReq;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineUpdateReq;
import org.dromara.project.domain.vo.BusAttendanceMachineVo;
import org.dromara.project.service.IBusAttendanceMachineService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 考勤机
*
* @author lilemy
* @date 2025-10-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/project/attendanceMachine")
public class BusAttendanceMachineController extends BaseController {
private final IBusAttendanceMachineService busAttendanceMachineService;
/**
* 查询考勤机列表
*/
@SaCheckPermission("project:attendanceMachine:list")
@GetMapping("/list")
public TableDataInfo<BusAttendanceMachineVo> list(BusAttendanceMachineQueryReq req, PageQuery pageQuery) {
return busAttendanceMachineService.queryPageList(req, pageQuery);
}
/**
* 导出考勤机列表
*/
@SaCheckPermission("project:attendanceMachine:export")
@Log(title = "考勤机", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(BusAttendanceMachineQueryReq req, HttpServletResponse response) {
List<BusAttendanceMachineVo> list = busAttendanceMachineService.queryList(req);
ExcelUtil.exportExcel(list, "考勤机", BusAttendanceMachineVo.class, response);
}
/**
* 获取考勤机详细信息
*
* @param id 主键
*/
@SaCheckPermission("project:attendanceMachine:query")
@GetMapping("/{id}")
public R<BusAttendanceMachineVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(busAttendanceMachineService.queryById(id));
}
/**
* 修改考勤机
*/
@SaCheckPermission("project:attendanceMachine:edit")
@Log(title = "考勤机", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated @RequestBody BusAttendanceMachineUpdateReq req) {
return toAjax(busAttendanceMachineService.updateByBo(req));
}
/**
* 删除考勤机
*
* @param ids 主键串
*/
@SaCheckPermission("project:attendanceMachine:remove")
@Log(title = "考勤机", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(busAttendanceMachineService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,65 @@
package org.dromara.project.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineSendInfoUserReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserQueryReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserRemoveReq;
import org.dromara.project.domain.vo.BusAttendanceMachineUserVo;
import org.dromara.project.service.IBusAttendanceMachineUserService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 考勤机用户
*
* @author lilemy
* @date 2025-10-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/project/attendanceMachineUser")
public class BusAttendanceMachineUserController extends BaseController {
private final IBusAttendanceMachineUserService busAttendanceMachineUserService;
/**
* 查询考勤机用户列表
*/
@SaCheckPermission("project:attendanceMachineUser:list")
@GetMapping("/list")
public R<List<BusAttendanceMachineUserVo>> list(BusAttendanceMachineUserQueryReq req) {
return R.ok(busAttendanceMachineUserService.queryList(req));
}
/**
* 下发用户到考勤机中
*/
@SaCheckPermission("project:attendanceMachineUser:add")
@Log(title = "考勤机用户", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/send")
public R<String> sendPersonInfo(@Validated @RequestBody BusAttendanceMachineSendInfoUserReq req) {
return R.ok(busAttendanceMachineUserService.sendPersonInfo(req));
}
/**
* 删除考勤机用户
*/
@SaCheckPermission("project:attendanceMachineUser:remove")
@Log(title = "考勤机用户", businessType = BusinessType.DELETE)
@RepeatSubmit()
@DeleteMapping()
public R<Void> remove(@Validated @RequestBody BusAttendanceMachineUserRemoveReq req) {
return toAjax(busAttendanceMachineUserService.remove(req));
}
}

View File

@ -0,0 +1,65 @@
package org.dromara.project.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 考勤机对象 bus_attendance_machine
*
* @author lilemy
* @date 2025-10-15
*/
@Data
@TableName("bus_attendance_machine")
public class BusAttendanceMachine implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 项目ID
*/
private Long projectId;
/**
* 设备sn
*/
private String sn;
/**
* 是否在线0-no 1-yes
*/
private String status;
/**
* 班组id多个逗号分隔
*/
private String teams;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@ -0,0 +1,56 @@
package org.dromara.project.domain;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 考勤机用户对象 bus_attendance_machine_user
*
* @author lilemy
* @date 2025-10-15
*/
@Data
public class BusAttendanceMachineUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Long id;
/**
* 考勤机主键ID
*/
private Long machineId;
/**
* 班组ID
*/
private Long teamId;
/**
* 用户ID
*/
private Long userId;
/**
* 标识(是否下发)
*/
private Integer identifying;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@ -0,0 +1,37 @@
package org.dromara.project.domain.dto.attendancemachine;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-10-15 16:18
*/
@Data
public class BusAttendanceMachineQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = 6296359812022907365L;
/**
* 项目ID
*/
private Long projectId;
/**
* 设备sn
*/
private String sn;
/**
* 是否在线0-no 1-yes
*/
private String status;
/**
* 班组id多个逗号分隔
*/
private String teams;
}

View File

@ -0,0 +1,40 @@
package org.dromara.project.domain.dto.attendancemachine;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-10-15 16:15
*/
@Data
public class BusAttendanceMachineUpdateReq implements Serializable {
@Serial
private static final long serialVersionUID = 4764015594443520162L;
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空")
private Long id;
/**
* 项目ID
*/
@NotNull(message = "项目ID不能为空")
private Long projectId;
/**
* 班组id多个逗号分隔
*/
private String teams;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,32 @@
package org.dromara.project.domain.dto.attendancemachineuser;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author lilemy
* @date 2025-10-15 17:43
*/
@Data
public class BusAttendanceMachineSendInfoUserReq implements Serializable {
@Serial
private static final long serialVersionUID = -6517799483848691652L;
/**
* 考勤机主键ID
*/
@NotNull(message = "考勤机不能为空")
private Long machineId;
/**
* 用户ID列表
*/
@NotNull(message = "用户列表不能为空")
private List<Long> userIds;
}

View File

@ -0,0 +1,40 @@
package org.dromara.project.domain.dto.attendancemachineuser;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-10-15 19:22
*/
@Data
public class BusAttendanceMachineUserQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = -1629819732452840391L;
/**
* 考勤机主键ID
*/
@NotNull(message = "考勤机不能为空")
private Long machineId;
/**
* 班组ID
*/
@NotNull(message = "班组不能为空")
private Long teamId;
/**
* 用户名称
*/
private String userName;
/**
* 标识(是否下发)
*/
private Integer identifying;
}

View File

@ -0,0 +1,31 @@
package org.dromara.project.domain.dto.attendancemachineuser;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author lilemy
* @date 2025-10-15 19:56
*/
@Data
public class BusAttendanceMachineUserRemoveReq implements Serializable {
@Serial
private static final long serialVersionUID = -8880884070612972760L;
/**
* 考勤机主键ID
*/
@NotNull(message = "考勤机不能为空")
private Long machineId;
/**
* 用户ID列表
*/
@NotNull(message = "用户列表不能为空")
private List<Long> userIds;
}

View File

@ -0,0 +1,46 @@
package org.dromara.project.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 考勤机用户视图对象 bus_attendance_machine_user
*
* @author lilemy
* @date 2025-10-15
*/
@Data
public class BusAttendanceMachineUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 考勤机主键ID
*/
private Long machineId;
/**
* 班组ID
*/
private Long teamId;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名称
*/
private String userName;
/**
* 标识(是否下发)
*/
private Integer identifying;
}

View File

@ -0,0 +1,87 @@
package org.dromara.project.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.project.domain.BusAttendanceMachine;
import org.dromara.project.domain.vo.projectteam.BusProjectTeamVo;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 考勤机视图对象 bus_attendance_machine
*
* @author lilemy
* @date 2025-10-15
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusAttendanceMachine.class)
public class BusAttendanceMachineVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 项目ID
*/
@ExcelProperty(value = "项目ID")
private Long projectId;
/**
* 项目名称
*/
@Translation(type = TransConstant.PROJECT_ID_TO_NAME, mapper = "projectId")
private String projectName;
/**
* 设备sn
*/
@ExcelProperty(value = "设备sn")
private String sn;
/**
* 是否在线0-no 1-yes
*/
@ExcelProperty(value = "是否在线", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=-no,1=-yes")
private String status;
/**
* 班组id多个逗号分隔
*/
@ExcelProperty(value = "班组id多个逗号分隔")
private String teams;
/**
* 班组列表
*/
private List<BusProjectTeamVo> teamsList;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,15 @@
package org.dromara.project.mapper;
import org.dromara.project.domain.BusAttendanceMachine;
import org.dromara.project.domain.vo.BusAttendanceMachineVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 考勤机Mapper接口
*
* @author lilemy
* @date 2025-10-15
*/
public interface BusAttendanceMachineMapper extends BaseMapperPlus<BusAttendanceMachine, BusAttendanceMachineVo> {
}

View File

@ -0,0 +1,78 @@
package org.dromara.project.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.project.domain.BusAttendanceMachine;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineQueryReq;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineUpdateReq;
import org.dromara.project.domain.vo.BusAttendanceMachineVo;
import java.util.Collection;
import java.util.List;
/**
* 考勤机Service接口
*
* @author lilemy
* @date 2025-10-15
*/
public interface IBusAttendanceMachineService extends IService<BusAttendanceMachine> {
/**
* 查询考勤机
*
* @param id 主键
* @return 考勤机
*/
BusAttendanceMachineVo queryById(Long id);
/**
* 分页查询考勤机列表
*
* @param req 查询条件
* @param pageQuery 分页参数
* @return 考勤机分页列表
*/
TableDataInfo<BusAttendanceMachineVo> queryPageList(BusAttendanceMachineQueryReq req, PageQuery pageQuery);
/**
* 查询符合条件的考勤机列表
*
* @param req 查询条件
* @return 考勤机列表
*/
List<BusAttendanceMachineVo> queryList(BusAttendanceMachineQueryReq req);
/**
* 根据sn新增考勤机
*
* @param sn 考勤机sn
*/
void insertBySn(String sn);
/**
* 修改考勤机
*
* @param req 考勤机
* @return 是否修改成功
*/
Boolean updateByBo(BusAttendanceMachineUpdateReq req);
/**
* 修改考勤机状态
*
* @param sn 考勤机sn
* @param status 考勤机状态
*/
void changeStatus(String sn, String status);
/**
* 校验并批量删除考勤机信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,41 @@
package org.dromara.project.service;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineSendInfoUserReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserQueryReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserRemoveReq;
import org.dromara.project.domain.vo.BusAttendanceMachineUserVo;
import java.util.List;
/**
* 考勤机用户Service接口
*
* @author lilemy
* @date 2025-10-15
*/
public interface IBusAttendanceMachineUserService {
/**
* 查询符合条件的考勤机用户列表
*
* @param req 查询条件
* @return 考勤机用户列表
*/
List<BusAttendanceMachineUserVo> queryList(BusAttendanceMachineUserQueryReq req);
/**
* 下发用户到考勤机中
*
* @param req 下发用户参数
* @return 下发结果
*/
String sendPersonInfo(BusAttendanceMachineSendInfoUserReq req);
/**
* 删除考勤机用户
*
* @param req 删除参数
* @return 删除结果
*/
Boolean remove(BusAttendanceMachineUserRemoveReq req);
}

View File

@ -0,0 +1,266 @@
package org.dromara.project.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mobileAttendanceMachine.DeviceMessageSender;
import org.dromara.project.domain.BusAttendanceMachine;
import org.dromara.project.domain.BusProject;
import org.dromara.project.domain.BusProjectTeam;
import org.dromara.project.domain.BusProjectTeamMember;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineQueryReq;
import org.dromara.project.domain.dto.attendancemachine.BusAttendanceMachineUpdateReq;
import org.dromara.project.domain.vo.BusAttendanceMachineVo;
import org.dromara.project.domain.vo.projectteam.BusProjectTeamVo;
import org.dromara.project.mapper.BusAttendanceMachineMapper;
import org.dromara.project.service.IBusAttendanceMachineService;
import org.dromara.project.service.IBusProjectService;
import org.dromara.project.service.IBusProjectTeamMemberService;
import org.dromara.project.service.IBusProjectTeamService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 考勤机Service业务层处理
*
* @author lilemy
* @date 2025-10-15
*/
@RequiredArgsConstructor
@Service
public class BusAttendanceMachineServiceImpl extends ServiceImpl<BusAttendanceMachineMapper, BusAttendanceMachine>
implements IBusAttendanceMachineService {
@Resource
private IBusProjectService projectService;
@Resource
private IBusProjectTeamService projectTeamService;
@Resource
private IBusProjectTeamMemberService projectTeamMemberService;
@Resource
private DeviceMessageSender deviceMessageSender;
/**
* 查询考勤机
*
* @param id 主键
* @return 考勤机
*/
@Override
public BusAttendanceMachineVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 分页查询考勤机列表
*
* @param req 查询条件
* @param pageQuery 分页参数
* @return 考勤机分页列表
*/
@Override
public TableDataInfo<BusAttendanceMachineVo> queryPageList(BusAttendanceMachineQueryReq req, PageQuery pageQuery) {
LambdaQueryWrapper<BusAttendanceMachine> lqw = buildQueryWrapper(req);
Page<BusAttendanceMachineVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
List<BusAttendanceMachineVo> records = result.getRecords();
List<Long> teamIds = records.stream()
.map(BusAttendanceMachineVo::getTeams)
.filter(StringUtils::isNotBlank)
.flatMap(team -> Arrays.stream(team.split(",")) // flatMap 展开内部 stream
.map(Long::parseLong))
.toList();
if (CollUtil.isEmpty(teamIds)) {
return TableDataInfo.build(result);
}
List<BusProjectTeam> projectTeams = projectTeamService.listByIds(teamIds);
Map<Long, String> teamMap = projectTeams.stream()
.collect(Collectors.toMap(BusProjectTeam::getId, BusProjectTeam::getTeamName));
records.forEach(machine -> {
String teams = machine.getTeams();
if (StringUtils.isNotBlank(teams)) {
List<Long> t = Arrays.stream(teams.split(",")).map(Long::parseLong).toList();
List<BusProjectTeamVo> teamVoList = t.stream().map(teamId -> {
BusProjectTeamVo vo = new BusProjectTeamVo();
vo.setId(teamId);
vo.setTeamName(teamMap.getOrDefault(teamId, "未命名"));
return vo;
}).toList();
machine.setTeamsList(teamVoList);
}
});
result.setRecords(records);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的考勤机列表
*
* @param req 查询条件
* @return 考勤机列表
*/
@Override
public List<BusAttendanceMachineVo> queryList(BusAttendanceMachineQueryReq req) {
LambdaQueryWrapper<BusAttendanceMachine> lqw = buildQueryWrapper(req);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<BusAttendanceMachine> buildQueryWrapper(BusAttendanceMachineQueryReq req) {
LambdaQueryWrapper<BusAttendanceMachine> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(BusAttendanceMachine::getId);
lqw.eq(req.getProjectId() != null, BusAttendanceMachine::getProjectId, req.getProjectId());
lqw.isNull(req.getProjectId() == null, BusAttendanceMachine::getProjectId);
lqw.eq(StringUtils.isNotBlank(req.getSn()), BusAttendanceMachine::getSn, req.getSn());
lqw.eq(StringUtils.isNotBlank(req.getStatus()), BusAttendanceMachine::getStatus, req.getStatus());
lqw.like(StringUtils.isNotBlank(req.getTeams()), BusAttendanceMachine::getTeams, req.getTeams());
return lqw;
}
/**
* 根据sn新增考勤机
*
* @param sn 考勤机sn
*/
@Override
public void insertBySn(String sn) {
BusAttendanceMachine machine = new BusAttendanceMachine();
// 判断考勤机是否存在
BusAttendanceMachine oldMachine = this.lambdaQuery()
.eq(BusAttendanceMachine::getSn, sn)
.one();
if (oldMachine != null) {
machine.setId(oldMachine.getId());
}
machine.setSn(sn);
machine.setStatus("1");
this.saveOrUpdate(machine);
}
/**
* 修改考勤机
*
* @param req 考勤机
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(BusAttendanceMachineUpdateReq req) {
Long id = req.getId();
BusAttendanceMachine oldMachine = this.getById(id);
if (oldMachine == null) {
throw new ServiceException("考勤机不存在", HttpStatus.NOT_FOUND);
}
BusAttendanceMachine machine = new BusAttendanceMachine();
BeanUtils.copyProperties(req, machine);
// 判断班组是否属于所选项目
Long projectId = req.getProjectId();
BusProject project = projectService.getById(projectId);
if (project == null) {
throw new ServiceException("所选项目不存在", HttpStatus.NOT_FOUND);
}
String teams = req.getTeams();
if (StringUtils.isBlank(teams) && StringUtils.isBlank(oldMachine.getTeams())) {
throw new ServiceException("请选择班组", HttpStatus.BAD_REQUEST);
}
if (StringUtils.isNotBlank(teams)) {
List<Long> teamIds = Arrays.stream(teams.split(","))
.map(Long::parseLong)
.distinct()
.toList();
List<BusProjectTeam> projectTeams = projectTeamService.listByIds(teamIds);
if (projectTeams.size() != teamIds.size()) {
throw new ServiceException("所选班组不存在", HttpStatus.NOT_FOUND);
}
String oldTeams = oldMachine.getTeams();
if (StringUtils.isNotBlank(oldTeams)) {
List<Long> oldTeamIds = Arrays.stream(oldTeams.split(","))
.map(Long::parseLong)
.distinct()
.toList();
// 获取新增的班组ID
List<Long> added = teamIds.stream()
.filter(t -> !oldTeamIds.contains(t))
.toList();
// 获取删除的班组ID
List<Long> deleted = oldTeamIds.stream()
.filter(t -> !teamIds.contains(t))
.toList();
// 删除的班组不为空时,删除考勤机中对应的人数
if (CollUtil.isNotEmpty(deleted)) {
// 获取待删除的班组的所有用户id
List<Long> userIds = projectTeamMemberService.lambdaQuery()
.select(BusProjectTeamMember::getMemberId)
.in(BusProjectTeamMember::getTeamId, deleted)
.list()
.stream().map(BusProjectTeamMember::getMemberId).toList();
if (CollUtil.isNotEmpty(userIds)) {
String[] userIdArray = userIds.stream()
.map(String::valueOf) // 将 Long 转成 String
.toArray(String[]::new);
try {
deviceMessageSender.batchDeleteUsers(machine.getSn(), userIdArray);
} catch (Exception e) {
throw new ServiceException("删除考勤机用户失败", HttpStatus.ERROR);
}
}
}
}
projectTeams.forEach(team -> {
if (!team.getProjectId().equals(projectId)) {
throw new ServiceException(team.getTeamName() + "不属于所选项目", HttpStatus.BAD_REQUEST);
}
});
}
return this.updateById(machine);
}
/**
* 修改考勤机状态
*
* @param sn 考勤机sn
* @param status 考勤机状态
*/
@Override
public void changeStatus(String sn, String status) {
BusAttendanceMachine machine = this.lambdaQuery()
.eq(BusAttendanceMachine::getSn, sn)
.one();
if (machine == null) {
machine = new BusAttendanceMachine();
machine.setSn(sn);
}
machine.setStatus(status);
this.saveOrUpdate(machine);
}
/**
* 校验并批量删除考勤机信息
*
* @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,163 @@
package org.dromara.project.service.impl;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.contractor.domain.SubConstructionUser;
import org.dromara.contractor.service.ISubConstructionUserService;
import org.dromara.mobileAttendanceMachine.DeviceMessageSender;
import org.dromara.mobileAttendanceMachine.KqjEntity;
import org.dromara.project.domain.BusAttendanceMachine;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineSendInfoUserReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserQueryReq;
import org.dromara.project.domain.dto.attendancemachineuser.BusAttendanceMachineUserRemoveReq;
import org.dromara.project.domain.dto.projectteammember.BusProjectTeamMemberQueryReq;
import org.dromara.project.domain.vo.BusAttendanceMachineUserVo;
import org.dromara.project.domain.vo.projectteammember.BusProjectTeamMemberVo;
import org.dromara.project.service.IBusAttendanceMachineService;
import org.dromara.project.service.IBusAttendanceMachineUserService;
import org.dromara.project.service.IBusProjectTeamMemberService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 考勤机用户Service业务层处理
*
* @author lilemy
* @date 2025-10-15
*/
@RequiredArgsConstructor
@Service
public class BusAttendanceMachineUserServiceImpl implements IBusAttendanceMachineUserService {
private final IBusAttendanceMachineService attendanceMachineService;
private final DeviceMessageSender deviceMessageSender;
private final ISubConstructionUserService constructionUserService;
private final IBusProjectTeamMemberService projectTeamMemberService;
/**
* 查询符合条件的考勤机用户列表
*
* @param req 查询条件
* @return 考勤机用户列表
*/
@Override
public List<BusAttendanceMachineUserVo> queryList(BusAttendanceMachineUserQueryReq req) {
BusProjectTeamMemberQueryReq query = new BusProjectTeamMemberQueryReq();
query.setTeamId(req.getTeamId());
query.setMemberName(req.getUserName());
List<BusProjectTeamMemberVo> teamMemberList = projectTeamMemberService.queryList(query);
if (CollUtil.isEmpty(teamMemberList)) {
return CollUtil.newArrayList();
}
BusAttendanceMachine machine = attendanceMachineService.getById(req.getMachineId());
if (machine == null) {
throw new ServiceException("考勤机不存在", HttpStatus.NOT_FOUND);
}
// 判断选择班组和考勤机是否一致
if (StringUtils.isBlank(machine.getTeams())) {
throw new ServiceException("考勤机未绑定班组", HttpStatus.NOT_FOUND);
}
if (!Arrays.stream(machine.getTeams().split(","))
.map(Long::parseLong)
.collect(Collectors.toSet()).contains(req.getTeamId())) {
throw new ServiceException("所选班组与考勤机不匹配", HttpStatus.NOT_FOUND);
}
// 获取考勤机里的用户
Set<Long> userIdList = new HashSet<>();
try {
KqjEntity.CommonResponse response = deviceMessageSender.getAllUsers(machine.getSn());
int code = response.getData().getCode();
if (code == 0 || code == 200) {
String[] userIds = response.getData().getUserIds();
userIdList = Arrays.stream(userIds).map(Long::parseLong).collect(Collectors.toSet());
}
} catch (Exception e) {
throw new ServiceException("获取考勤机用户失败", HttpStatus.ERROR);
}
Set<Long> finalUserIdList = userIdList;
return teamMemberList.stream().map(member -> {
BusAttendanceMachineUserVo vo = new BusAttendanceMachineUserVo();
vo.setMachineId(req.getMachineId());
vo.setTeamId(req.getTeamId());
vo.setUserId(member.getMemberId());
vo.setUserName(member.getMemberName());
if (CollUtil.isEmpty(finalUserIdList)) {
vo.setIdentifying(0);
} else if (finalUserIdList.contains(member.getMemberId())) {
vo.setIdentifying(1);
} else {
vo.setIdentifying(0);
}
return vo;
}).toList();
}
/**
* 下发用户到考勤机中
*
* @param req 下发用户参数
* @return 下发结果
*/
@Override
public String sendPersonInfo(BusAttendanceMachineSendInfoUserReq req) {
Long machineId = req.getMachineId();
List<Long> userIds = req.getUserIds();
BusAttendanceMachine machine = attendanceMachineService.getById(machineId);
if (machine == null) {
throw new ServiceException("考勤机不存在", HttpStatus.NOT_FOUND);
}
List<SubConstructionUser> userList = constructionUserService.lambdaQuery()
.in(SubConstructionUser::getSysUserId, userIds)
.list();
// 返回数据
StringBuilder sb = new StringBuilder();
sb.append("用户:[");
int count = 0;
for (SubConstructionUser user : userList) {
Boolean result = deviceMessageSender.sendPersonnelInformation(machine.getSn(),
user.getSysUserId().toString(), user.getUserName(), user.getFacePic());
if (!result) {
sb.append(user.getUserName()).append(" ");
count++;
}
}
sb.append("] 下发失败");
return count == 0 ? "下发成功" : sb.toString();
}
/**
* 删除考勤机用户
*
* @param req 删除参数
* @return 删除结果
*/
@Override
public Boolean remove(BusAttendanceMachineUserRemoveReq req) {
Long machineId = req.getMachineId();
List<Long> userIds = req.getUserIds();
BusAttendanceMachine machine = attendanceMachineService.getById(machineId);
if (machine == null) {
throw new ServiceException("考勤机不存在", HttpStatus.NOT_FOUND);
}
String[] userIdArray = userIds.stream()
.map(String::valueOf) // 将 Long 转成 String
.toArray(String[]::new);
try {
KqjEntity.CommonResponse response = deviceMessageSender.batchDeleteUsers(machine.getSn(), userIdArray);
return response.getData().getCode() == 0 || response.getData().getCode() == 200;
} catch (Exception e) {
throw new ServiceException("删除考勤机用户失败", HttpStatus.ERROR);
}
}
}

View File

@ -273,7 +273,7 @@ public class SysUserController extends BaseController {
@Log(title = "用户管理", businessType = BusinessType.UPDATE) @Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd") @PutMapping("/resetPwd")
public R<Void> resetPwd(@RequestBody SysUserBo user) { public R<Void> resetPwd(@RequestBody SysUserBo user) {
userService.checkUserAllowed(user.getUserId()); // userService.checkUserAllowed(user.getUserId());
userService.checkUserDataScope(user.getUserId()); userService.checkUserDataScope(user.getUserId());
user.setPassword(BCrypt.hashpw(user.getPassword())); user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword())); return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword()));

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="org.dromara.project.mapper.BusAttendanceMachineMapper">
</mapper>