考勤设备模块

This commit is contained in:
furongxin 2024-06-05 09:28:30 +08:00
parent 0c7e9dc026
commit afa1689cb6
29 changed files with 574 additions and 80 deletions

View File

@ -81,6 +81,11 @@
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-websocket</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>

View File

@ -1,10 +1,18 @@
package cn.iocoder.yudao.module.system.api.equipment;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.equipment.dto.AttendanceUpdateDTO;
import cn.iocoder.yudao.module.system.api.equipment.dto.DistributeRecordDTO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine.AddUserToAttendanceMachineVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine.AttendanceMachinePasswordVO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.service.equipment.AttendanceMachineService;
import cn.iocoder.yudao.module.system.service.equipment.DistributeRecordService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@ -13,9 +21,37 @@ public class AttendanceMachineApiImpl implements AttendanceMachineApi{
@Resource
private AttendanceMachineService attendanceMachineService;
@Override
public void updateAttendanceMachineStatus(String deviceNo) {
@Resource
private DistributeRecordService distributeRecordService;
attendanceMachineService.updateAttendanceMachineStatus(deviceNo);
@Override
public void createDistributeRecord(List<DistributeRecordDTO> recordDTO) {
List<DistributeRecordDO> distributeRecordDO = BeanUtils.toBean(recordDTO, DistributeRecordDO.class);
distributeRecordService.createDistributeRecord(distributeRecordDO);
}
@Override
public void updateDistributeRecord(List<DistributeRecordDTO> recordDTO) {
distributeRecordService.updateDistributeRecord(BeanUtils.toBean(recordDTO, DistributeRecordDO.class));
}
@Override
public void updateAttendance(AttendanceUpdateDTO updateDTO) {
AddUserToAttendanceMachineVO updateVO = BeanUtils.toBean(updateDTO, AddUserToAttendanceMachineVO.class);
attendanceMachineService.addUserToAttendanceMachine(updateVO);
}
@Override
public void updateDevicePassword(String deviceNo, String oldPassword, String newPassword) {
AttendanceMachinePasswordVO passwordVO = new AttendanceMachinePasswordVO();
passwordVO.setDeviceNo(deviceNo);
passwordVO.setOldPassword(oldPassword);
passwordVO.setNewPassword(newPassword);
attendanceMachineService.updatePassword(passwordVO);
}
}

View File

@ -2,20 +2,34 @@ package cn.iocoder.yudao.module.system.controller.admin.equipment;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine.*;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtRespVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket.UpdatePasswordVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket.WebsocketBaseVO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.service.equipment.AttendanceMachineService;
import cn.iocoder.yudao.module.system.service.equipment.DistributeRecordService;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collections;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.REQUEST_FAILURE;
@Tag(name = "管理后台 - 考勤设备")
@ -27,6 +41,15 @@ public class AttendanceMachineController {
@Resource
private AttendanceMachineService attendanceMachineService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private WebSocketSenderApi webSocketSenderApi;
@Resource
private DistributeRecordService recordService;
@PostMapping("/create")
@Operation(summary = "创建考勤设备")
@PreAuthorize("@ss.hasPermission('system:attendance-machine:create')")
@ -48,8 +71,58 @@ public class AttendanceMachineController {
@PreAuthorize("@ss.hasPermission('system:attendance-machine:update')")
public CommonResult<Boolean> updateAttendanceMachine(@Valid @RequestBody AttendanceMachinePasswordVO updateReqVO) {
attendanceMachineService.updatePassword(updateReqVO);
return success(true);
// 将密码信息 储存在redis中
stringRedisTemplate.opsForValue().set(updateReqVO.getDeviceNo(), updateReqVO.getOldPassword() + "-" + updateReqVO.getNewPassword());
String requestId = IdWorker.get32UUID();
// 设备修改密码命令信息
UpdatePasswordVO passwordVO = new UpdatePasswordVO().setOld_password(updateReqVO.getOldPassword())
.setNew_password(updateReqVO.getNewPassword());
WebsocketBaseVO data = new WebsocketBaseVO()
.setFrom("")
.setTo(updateReqVO.getDeviceNo())
.setExtra(requestId + "_" + getLoginUserId())
.setData(passwordVO);
// 设置 设备下发记录
DistributeRecordDO recordDO = new DistributeRecordDO();
recordDO.setRequestId(requestId);
recordDO.setDeviceNo(updateReqVO.getDeviceNo());
recordDO.setUserId(getLoginUserId());
recordDO.setType(3);
recordDO.setResult("通讯失败");
// 更新下发记录信息
recordService.createDistributeRecord(Collections.singletonList(recordDO));
// 设备租户ID
TenantContextHolder.setTenantId(1L);
// 发送修改密码命令给设备
webSocketSenderApi.sendSN(updateReqVO.getDeviceNo(), "attendance-message-send", JsonUtils.toJsonString(data));
Long startTime = System.currentTimeMillis();
while (true) {
if (stringRedisTemplate.opsForValue().get(updateReqVO.getDeviceNo()) == null) {
return success(true);
}else if ("false".equals(stringRedisTemplate.opsForValue().get(updateReqVO.getDeviceNo()))) {
Integer code = Integer.valueOf(stringRedisTemplate.opsForValue().get(updateReqVO.getDeviceNo() + "msg").split("_")[0]);
String msg = stringRedisTemplate.opsForValue().get(updateReqVO.getDeviceNo() + "msg").split("_")[1];
// 清楚 redis缓存
stringRedisTemplate.delete(updateReqVO.getDeviceNo());
stringRedisTemplate.delete(updateReqVO.getDeviceNo() + "msg");
return error(code, msg);
}
Long currentTime = System.currentTimeMillis();
if (currentTime - startTime > 3000) {
throw exception(REQUEST_FAILURE);
}
}
}
@PutMapping("/update-user")

View File

@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
@ -14,12 +13,15 @@ import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.User
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtRespVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO;
import cn.iocoder.yudao.module.system.convert.equipment.AttendanceMachineConvert;
import cn.iocoder.yudao.module.system.convert.equipment.UsersExtConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.AttendanceMachineDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.UsersExtDO;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.equipment.AttendanceMachineService;
import cn.iocoder.yudao.module.system.service.equipment.DistributeRecordService;
import cn.iocoder.yudao.module.system.service.equipment.UsersExtService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -39,11 +41,15 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -62,6 +68,9 @@ public class UsersExtController {
@Resource
private AttendanceMachineService attendanceMachineService;
@Resource
private DistributeRecordService recordService;
@RequestMapping(value = "/create",
method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题)
@Operation(summary = "上传照片")
@ -102,9 +111,7 @@ public class UsersExtController {
// 获得部门信息Map
Map<Long, DeptDO> deptDOMap = deptService.getDeptMap(convertList(attendanceMachineDO, AttendanceMachineDO::getDeptId));
List<AttendanceMachineRespVO> respVOS = BeanUtils.toBean(attendanceMachineDO, AttendanceMachineRespVO.class);
return success(CollectionUtils.convertList(respVOS, respVO -> respVO.setDeptName(deptDOMap.get(respVO.getDeptId()).getName())));
return success(AttendanceMachineConvert.INSTANCE.convertList(attendanceMachineDO, deptDOMap));
}
@GetMapping("/page")
@ -119,8 +126,23 @@ public class UsersExtController {
//获得部门Map
Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(pageResult.getList(), UsersExtRespVO::getDeptId));
return success(new PageResult<>(UsersExtConvert.INSTANCE.convertList(pageResult.getList(), deptMap)
, pageResult.getTotal()));
// 获得 下发或修改状态信息
List<Integer> type = Arrays.asList(1,2);
List<DistributeRecordDO> recordDOs = recordService.getDistributeRecord(convertList(pageResult.getList(), UsersExtRespVO::getUserId), type);
Map<Long, DistributeRecordDO> recordDOMap = convertMap(recordDOs, DistributeRecordDO::getUserId);
List<UsersExtRespVO> respVOS = UsersExtConvert.INSTANCE.convertList(pageResult.getList(), deptMap, recordDOMap);
// 判断是否已下发
if (pageReqVO.getIsIssued() != null && pageReqVO.getIsIssued() == 0) {
respVOS = respVOS.stream().filter(data -> data.getDevices() == null || !new HashSet<>(data.getDevices()).containsAll(pageReqVO.getDeviceNos())).collect(Collectors.toList());
}else if (pageReqVO.getIsIssued() != null && pageReqVO.getIsIssued() == 1) {
respVOS = respVOS.stream().filter(data -> data.getDevices() != null && new HashSet<>(data.getDevices()).containsAll(pageReqVO.getDeviceNos())).collect(Collectors.toList());
}
return success(new PageResult<>(respVOS, pageResult.getTotal()));
}
return success(BeanUtils.toBean(pageResult, UsersExtRespVO.class));

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -11,13 +10,13 @@ import java.util.List;
@Data
public class AddUserToAttendanceMachineVO {
@Schema(description = "下发用户列表")
@NotNull(message = "下发用户不能为空")
private List<UsersExtRespVO> userInfo;
@Schema(description = "下发用户编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "下发用户编号不能为空")
private List<Long> userId;
@Schema(description = "设备号列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "设备号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "设备号不能为空")
private List<String> deviceNos;
private String deviceNo;
@Schema(description = "下发或删除 | 0下发 1删除")
private Integer method;

View File

@ -11,9 +11,9 @@ import javax.validation.constraints.NotNull;
@Data
public class AttendanceMachinePasswordVO {
@Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备ID不能为空")
private Long id;
@Schema(description = "设备", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备不能为空")
private String deviceNo;
@Schema(description = "旧密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotEmpty(message = "旧密码不能为空")

View File

@ -36,6 +36,6 @@ public class AttendanceMachineRespVO {
@ExcelProperty("设备名称")
private String deviceName;
@Schema(description = "是否在线 | 0否 1是")
@Schema(description = "是否在线 | 0离线 1在线")
private Integer isOnLine;
}

View File

@ -30,6 +30,15 @@ public class UsersExtPageReqVO extends PageParam {
@Schema(description = "是否录入人脸 | 0:未录入 1:已录入")
private Integer isEnter;
@Schema(description = "用户类型")
private Integer userType;
@Schema(description = "是否下发 | 0否 1是")
private Integer isIssued;
@Schema(description = "设备号集合")
private List<String> deviceNos;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt;
import cn.iocoder.yudao.module.system.controller.admin.worklog.vo.upload.UploadUserFile;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
@ -36,8 +35,14 @@ public class UsersExtRespVO {
@ExcelProperty("人脸图片")
private String faceImg;
@Schema(description = "绑定的考勤机设备号")
private String attendanceMachineNos;
@Schema(description = "绑定的考勤机设备号集合")
private List<String> attendanceMachineNos;
private List<String> devices;
@Schema(description = "下发结果")
private String result;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "修改人看图片命令 VO")
@Data
public class UpdateFaceImgVO {
@Schema(description = "指令")
private String cmd = "editUser";
@Schema(description = "用户id")
private String user_id;
@Schema(description = "修改类型")
private Integer edit_mode = 1;
@Schema(description = "人脸图片url")
private String face_template;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "修改设备密码 VO")
@Data
public class UpdatePasswordVO {
@Schema(description = "指令号,此处固定为 setPassword")
private String cmd = "setPassword";
@Schema(description = "设备当前密码")
private String old_password;
@Schema(description = "新密码")
private String new_password;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class WebsocketBaseVO {
@Schema(description = "固定为 to_device")
private String cmd = "to_device";
private String from;
private String extra;
@Schema(description = "目标设备号", requiredMode = Schema.RequiredMode.REQUIRED)
private String to;
@Schema(description = "具体指令中的数据格式")
private Object data;
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.system.convert.equipment;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine.AttendanceMachineRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.AttendanceMachineDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
@Mapper
public interface AttendanceMachineConvert {
AttendanceMachineConvert INSTANCE = Mappers.getMapper(AttendanceMachineConvert.class);
default List<AttendanceMachineRespVO> convertList(List<AttendanceMachineDO> list, Map<Long, DeptDO> deptMap) {
return CollectionUtils.convertList(list, data -> convert(data, deptMap.get(data.getDeptId())));
}
default AttendanceMachineRespVO convert(AttendanceMachineDO attendanceMachineDO, DeptDO deptDO) {
AttendanceMachineRespVO respVO = new AttendanceMachineRespVO();
if (attendanceMachineDO != null) {
respVO = BeanUtils.toBean(attendanceMachineDO, AttendanceMachineRespVO.class);
respVO.setIsOnLine(attendanceMachineDO.getStatus());
if (deptDO != null) {
respVO.setDeptName(deptDO.getName());
}
}
return respVO;
}
}

View File

@ -1,12 +1,13 @@
package cn.iocoder.yudao.module.system.convert.equipment;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@ -15,18 +16,32 @@ public interface UsersExtConvert {
UsersExtConvert INSTANCE = Mappers.getMapper(UsersExtConvert.class);
default List<UsersExtRespVO> convertList(List<UsersExtRespVO> list, Map<Long, DeptDO> deptMap) {
default List<UsersExtRespVO> convertList(List<UsersExtRespVO> list, Map<Long, DeptDO> deptMap, Map<Long, DistributeRecordDO> recordDOMap) {
return CollectionUtils.convertList(list, user -> convert(user, deptMap.get(user.getDeptId())));
return CollectionUtils.convertList(list, user -> convert(user, deptMap.get(user.getDeptId()), recordDOMap.get(user.getUserId())));
}
default UsersExtRespVO convert(UsersExtRespVO usersExtDO, DeptDO dept) {
default UsersExtRespVO convert(UsersExtRespVO usersExtDO, DeptDO dept, DistributeRecordDO recordDO) {
if (dept != null) {
usersExtDO.setDeptName(dept.getName());
}
usersExtDO.setFaceImg(usersExtDO.getFaceImg() + "?time=" + LocalDateTime.now().getSecond());
if (usersExtDO.getFaceImg() != null) {
usersExtDO.setFaceImg(usersExtDO.getFaceImg() + "?time=" + System.currentTimeMillis());
}
if (usersExtDO.getAttendanceMachineNos() != null) {
usersExtDO.setDevices(JsonUtils.parseArray(usersExtDO.getAttendanceMachineNos(), String.class));
}
if (recordDO != null) {
usersExtDO.setResult(recordDO.getResult());
usersExtDO.setCreateTime(recordDO.getCreateTime());
}
return usersExtDO;
}

View File

@ -24,6 +24,10 @@ public class DistributeRecordDO extends BaseDO {
*/
@TableId
private Long id;
/**
* 记录编号
*/
private String requestId;
/**
* 设备编号
*/
@ -34,9 +38,13 @@ public class DistributeRecordDO extends BaseDO {
private Long userId;
/**
* 记录类型
* 0删除 1下发
* 0删除 1下发 2修改
*/
private Integer type;
/**
* 错误码
*/
private Integer code;
/**
* 下发结果
*/

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.system.dal.dataobject.equipment;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.system.controller.admin.worklog.vo.upload.UploadUserFile;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -15,7 +14,7 @@ import java.util.List;
*
* @author 符溶馨
*/
@TableName("system_users_ext")
@TableName(value = "system_users_ext", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)

View File

@ -20,7 +20,6 @@ public interface AttendanceMachineMapper extends BaseMapperX<AttendanceMachineDO
IPage<AttendanceMachineRespVO> selectAttendancePage(@Param("page") IPage<AttendanceMachineRespVO> mpPage,
@Param("reqVO") AttendanceMachinePageReqVO pageReqVO);
// @Param("deviceNos")List<String> deviceNos);
AttendanceMachineRespVO selectAttendanceByAssetsNo(@Param("assetsNo") String assetsNo);

View File

@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.system.dal.mysql.equipment;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
* 考勤机下发记录 Mapper
@ -12,4 +16,5 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DistributeRecordMapper extends BaseMapperX<DistributeRecordDO> {
List<DistributeRecordDO> selectListByUserIdAndType(@Param("userId") List<Long> userId, @Param("type") Collection<Integer> type);
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.system.job.equipment;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.framework.websocket.core.session.WebSocketSessionManager;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.AttendanceMachineDO;
import cn.iocoder.yudao.module.system.service.equipment.AttendanceMachineService;
import com.github.yulichang.toolkit.SpringContentUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
@Slf4j
@EnableScheduling
public class AttendanceMachineJob {
@Resource
private AttendanceMachineService attendanceMachineService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Scheduled(initialDelay = 5000, fixedRate = 30000)
@TenantJob
public void updateAttendanceMachineStatus() {
// 获得所有在线的设备
List<AttendanceMachineDO> attendanceMachineDOS = attendanceMachineService.getListByStatus();
for (AttendanceMachineDO reqDO : attendanceMachineDOS) {
// redis数据不存在则为离线状态
if (stringRedisTemplate.opsForValue().get(reqDO.getDeviceNo()) == null) {
attendanceMachineService.updateAttendanceMachineStatus(reqDO.getDeviceNo(), 0, null);
//同步 删除对应sessionId
WebSocketSessionManager webSocketSessionManager = SpringContentUtils.getBean(WebSocketSessionManager.class);
webSocketSessionManager.removeSession(webSocketSessionManager.getSessionByDeviceNum(reqDO.getDeviceNo()));
}else {
attendanceMachineService.updateAttendanceMachineStatus(reqDO.getDeviceNo(), 1, stringRedisTemplate.opsForValue().get(reqDO.getDeviceNo()));
}
}
}
}

View File

@ -34,7 +34,7 @@ public interface AttendanceMachineService {
* 更新考勤设备 状态
* @param deviceNo 设备号
*/
void updateAttendanceMachineStatus(String deviceNo);
void updateAttendanceMachineStatus(String deviceNo, Integer status, String dateTime);
/**
* 考勤设备密码修改
@ -78,4 +78,10 @@ public interface AttendanceMachineService {
* @param addReqVO 下发信息
*/
void addUserToAttendanceMachine(AddUserToAttendanceMachineVO addReqVO);
/**
* 获得所有在线的设备
* @return 设备列表
*/
List<AttendanceMachineDO> getListByStatus();
}

View File

@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.User
import cn.iocoder.yudao.module.system.dal.dataobject.assets.AssetsTypeDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.AttendanceMachineDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.UsersExtDO;
import cn.iocoder.yudao.module.system.dal.mysql.equipment.AttendanceMachineMapper;
import cn.iocoder.yudao.module.system.service.assets.AssetsTypeService;
import cn.iocoder.yudao.module.system.service.assets.DeptAssetsInOutStockService;
@ -24,14 +24,15 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.ATTENDANCE_MACHINE_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.ATTENDANCE_PASSWORD_NOT_EQUAL;
@ -110,9 +111,15 @@ public class AttendanceMachineServiceImpl implements AttendanceMachineService {
}
@Override
public void updateAttendanceMachineStatus(String deviceNo) {
public void updateAttendanceMachineStatus(String deviceNo, Integer status, String dateTime) {
attendanceMachineMapper.update(new AttendanceMachineDO().setStatus(1).setRequestTime(LocalDateTime.now()),
AttendanceMachineDO updateDO = new AttendanceMachineDO();
updateDO.setStatus(status);
updateDO.setUpdater(getLoginUserId().toString());
if (status == 1) {
updateDO.setRequestTime(LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
attendanceMachineMapper.update(updateDO,
new LambdaUpdateWrapper<AttendanceMachineDO>().eq(AttendanceMachineDO::getDeviceNo, deviceNo));
}
@ -120,7 +127,7 @@ public class AttendanceMachineServiceImpl implements AttendanceMachineService {
public void updatePassword(AttendanceMachinePasswordVO updateReqVO) {
//校验 旧密码
AttendanceMachineDO attendanceMachineDO = attendanceMachineMapper.selectById(updateReqVO.getId());
AttendanceMachineDO attendanceMachineDO = attendanceMachineMapper.selectOne(AttendanceMachineDO::getDeviceNo, updateReqVO.getDeviceNo());
if (attendanceMachineDO == null) {
throw exception(ATTENDANCE_MACHINE_NOT_EXISTS);
@ -134,10 +141,9 @@ public class AttendanceMachineServiceImpl implements AttendanceMachineService {
//更新
attendanceMachineDO = new AttendanceMachineDO()
.setId(updateReqVO.getId())
.setPassword(passwordEncoder.encode(updateReqVO.getNewPassword()));
attendanceMachineMapper.updateById(attendanceMachineDO);
attendanceMachineMapper.update(attendanceMachineDO, new LambdaUpdateWrapper<AttendanceMachineDO>().eq(AttendanceMachineDO::getDeviceNo, updateReqVO.getDeviceNo()));
}
private void validateAttendanceMachineExists(Long id) {
@ -196,29 +202,39 @@ public class AttendanceMachineServiceImpl implements AttendanceMachineService {
@Override
public void addUserToAttendanceMachine(AddUserToAttendanceMachineVO addReqVO) {
List<DistributeRecordDO> recordDOS = new ArrayList<>();
//获得 用户已绑定设备信息
List<UsersExtDO> usersExtDO = usersExtService.getListByUserId(addReqVO.getUserId());
// 更新用户已下发设备列表
List<UsersExtRespVO> userInfo = addReqVO.getUserInfo();
if (addReqVO.getMethod() == 0) {
userInfo.forEach(data -> {
usersExtDO.forEach(data -> {
List<String> deviceNo = data.getAttendanceMachineNos();
// 添加不重复的设备号
deviceNo.addAll(addReqVO.getDeviceNos().stream().filter(var -> !deviceNo.contains(var)).collect(Collectors.toList()));
if (deviceNo == null || deviceNo.isEmpty()) {
deviceNo = new ArrayList<>();
deviceNo.add(addReqVO.getDeviceNo());
}else {
if (!deviceNo.contains(addReqVO.getDeviceNo())) {
deviceNo.add(addReqVO.getDeviceNo());
}
}
//设备 用户绑定设备
data.setAttendanceMachineNos(deviceNo);
});
}else if (addReqVO.getMethod() == 1) {
userInfo.forEach(data -> {
usersExtDO.forEach(data -> {
List<String> deviceNo = data.getAttendanceMachineNos();
// 添加设备号
deviceNo.removeAll(addReqVO.getDeviceNos());
deviceNo.remove(addReqVO.getDeviceNo());
//设备 用户绑定设备
data.setAttendanceMachineNos(deviceNo);
@ -226,9 +242,12 @@ public class AttendanceMachineServiceImpl implements AttendanceMachineService {
}
// 更新 用户信息
usersExtService.updateListUsersExt(userInfo);
usersExtService.updateListUsersExt(usersExtDO);
}
//同步 插入 下发记录
@Override
public List<AttendanceMachineDO> getListByStatus() {
return attendanceMachineMapper.selectList();
}
}

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.system.service.equipment;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 考勤机下发记录 Service 接口
@ -15,15 +17,20 @@ public interface DistributeRecordService {
* 创建考勤机下发记录
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createDistributeRecord(@Valid DistributeRecordDO createReqVO);
void createDistributeRecord(@Valid List<DistributeRecordDO> createReqVO);
/**
* 获得考勤机下发记录
* 更新 考勤机下发记录
* @param updateReqVO 更新信息
*/
void updateDistributeRecord(@Valid List<DistributeRecordDO> updateReqVO);
/**
* 获得最新的 下发或修改的考勤机下发记录
*
* @param id 编号
* @param userId 用户编号编号
* @return 考勤机下发记录
*/
DistributeRecordDO getDistributeRecord(Long id);
List<DistributeRecordDO> getDistributeRecord(List<Long> userId, Collection<Integer> type);
}

View File

@ -1,11 +1,14 @@
package cn.iocoder.yudao.module.system.service.equipment;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.equipment.DistributeRecordMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* 考勤机下发记录 Service 实现类
@ -20,17 +23,27 @@ public class DistributeRecordServiceImpl implements DistributeRecordService {
private DistributeRecordMapper distributeRecordMapper;
@Override
public Long createDistributeRecord(DistributeRecordDO createReqVO) {
public void createDistributeRecord(List<DistributeRecordDO> createReqVO) {
// 插入
distributeRecordMapper.insert(createReqVO);
// 返回
return createReqVO.getId();
distributeRecordMapper.insertBatch(createReqVO);
}
@Override
public DistributeRecordDO getDistributeRecord(Long id) {
return distributeRecordMapper.selectById(id);
public void updateDistributeRecord(List<DistributeRecordDO> updateReqVO) {
for (DistributeRecordDO updateDO : updateReqVO) {
distributeRecordMapper.update(updateDO,
new LambdaQueryWrapperX<DistributeRecordDO>()
.eq(DistributeRecordDO::getUserId, updateDO.getUserId())
.eq(DistributeRecordDO::getRequestId, updateDO.getRequestId()));
}
}
@Override
public List<DistributeRecordDO> getDistributeRecord(List<Long> userId, Collection<Integer> type) {
return distributeRecordMapper.selectListByUserIdAndType(userId, type);
}
}

View File

@ -40,7 +40,7 @@ public interface UsersExtService {
*
* @param updateDo 更新信息
*/
void updateListUsersExt(@Valid List<UsersExtRespVO> updateDo);
void updateListUsersExt(@Valid List<UsersExtDO> updateDo);
/**
* 删除用户人脸信息
@ -55,6 +55,13 @@ public interface UsersExtService {
*/
UsersExtDO getUsersExt(Long id);
/**
* 获得指定得用户信息列表
* @param userId 用户编号
* @return
*/
List<UsersExtDO> getListByUserId(List<Long> userId);
/**
* 获得用户信息拓展分页
*

View File

@ -3,21 +3,28 @@ package cn.iocoder.yudao.module.system.service.equipment;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UserExtImportVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtRespVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.userExt.UsersExtSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket.UpdateFaceImgVO;
import cn.iocoder.yudao.module.system.controller.admin.equipment.vo.websocket.WebsocketBaseVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO;
import cn.iocoder.yudao.module.system.dal.dataobject.equipment.UsersExtDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.equipment.UsersExtMapper;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -26,13 +33,11 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
@ -58,6 +63,12 @@ public class UsersExtServiceImpl implements UsersExtService {
@Resource
private AdminUserService userService;
@Resource
private WebSocketSenderApi webSocketSenderApi;
@Resource
private DistributeRecordService recordService;
@Override
public Long createUsersExt(UsersExtDO updateDO, MultipartFile file) throws IOException {
@ -96,6 +107,41 @@ public class UsersExtServiceImpl implements UsersExtService {
updateDO.setFaceImg(url);
usersExtMapper.updateById(usersExtDO);
}
// 判断用户是否已下发至考勤设备
if (usersExtDO.getAttendanceMachineNos() != null) {
String requestId = IdWorker.get32UUID();
WebsocketBaseVO baseVO = new WebsocketBaseVO()
.setFrom("")
.setExtra(requestId + "_" + getLoginUserId());
for (String deviceNo : usersExtDO.getAttendanceMachineNos()) {
// 设备指令信息
UpdateFaceImgVO faceImgVO = new UpdateFaceImgVO();
faceImgVO.setUser_id(String.valueOf(usersExtDO.getUserId()));
faceImgVO.setFace_template(url);
baseVO.setTo(deviceNo);
baseVO.setData(faceImgVO);
// 设置 设备下发记录
DistributeRecordDO recordDO = new DistributeRecordDO();
recordDO.setRequestId(requestId);
recordDO.setDeviceNo(deviceNo);
recordDO.setUserId(usersExtDO.getUserId());
recordDO.setType(2);
recordDO.setResult("通讯失败");
// 更新下发记录信息
recordService.createDistributeRecord(Collections.singletonList(recordDO));
// 设备租户ID
TenantContextHolder.setTenantId(1L);
// 发送指令至设备 更新设备上人脸图片
webSocketSenderApi.sendSN(deviceNo, "attendance-message-send", JsonUtils.toJsonString(baseVO));
}
}
}
// 返回
@ -113,12 +159,10 @@ public class UsersExtServiceImpl implements UsersExtService {
}
@Override
public void updateListUsersExt(List<UsersExtRespVO> updateDo) {
public void updateListUsersExt(List<UsersExtDO> updateDo) {
// 更新
List<UsersExtDO> update = BeanUtils.toBean(updateDo, UsersExtDO.class);
usersExtMapper.updateBatch(update);
usersExtMapper.updateBatch(updateDo);
}
@Override
@ -145,9 +189,16 @@ public class UsersExtServiceImpl implements UsersExtService {
@Override
public UsersExtDO getUsersExt(Long id) {
return usersExtMapper.selectById(id);
}
@Override
public List<UsersExtDO> getListByUserId(List<Long> userId) {
return usersExtMapper.selectList(UsersExtDO::getUserId, userId);
}
@Override
public PageResult<UsersExtRespVO> getUsersExtPage(UsersExtPageReqVO pageReqVO) {

View File

@ -135,6 +135,19 @@ yudao:
web:
admin-ui:
url: http://sys.znkjfw.com # Admin 管理后台 UI 的地址
websocket:
enable: true # websocket的开关
path: /infra/ws # 路径
sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
sender-rocketmq:
topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group
sender-rabbitmq:
exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange
queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue
sender-kafka:
topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic
consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group
swagger:
title: 管理后台
description: 提供管理员管理的所有功能

View File

@ -15,7 +15,8 @@
a.assets_no AS assetsNo,
b.device_no AS deviceNo,
c.dept_id AS deptId,
b.device_name AS deviceName
b.device_name AS deviceName,
b.status AS isOnLine
FROM
zc_assets a
LEFT JOIN kq_attendance_machine b ON a.assets_no = b.assets_no
@ -29,12 +30,9 @@
<if test="reqVO.deviceName != null and reqVO.deviceName != ''">
AND b.device_name LIKE CONCAT('%', #{reqVO.deviceName}, '%')
</if>
<!-- <if test="deviceNos != null and deviceNos.size() > 0">-->
<!-- AND b.device_no IN-->
<!-- <foreach collection="deviceNos" item="deviceNos" open="(" separator="," close=")">-->
<!-- #{deviceNos}-->
<!-- </foreach>-->
<!-- </if>-->
<if test="reqVO.isOnLine != null">
AND b.status = #{reqVO.isOnLine}
</if>
</select>
<select id="selectAttendanceByAssetsNo" resultType="cn.iocoder.yudao.module.system.controller.admin.equipment.vo.attendancemachine.AttendanceMachineRespVO">
@ -61,12 +59,24 @@
a.face_img AS faceImg,
c.create_time AS createTime
FROM
( system_users_ext a, kq_attendance_machine b )
LEFT JOIN kq_distribute_record c ON c.device_no = b.device_no AND c.user_id = a.user_id
LEFT JOIN system_users d ON a.user_id = d.id
( system_users_ext a, kq_attendance_machine b, system_users d )
LEFT JOIN (
SELECT
user_id,
max( create_time ) AS create_time
FROM
kq_distribute_record
WHERE
code = 0
AND ( type = 1 OR type = 2 )
AND device_no = #{reqVO.deviceNo}
GROUP BY
user_id
) c ON c.user_id = a.user_id
WHERE
a.attendance_machine_nos LIKE CONCAT( '%', b.device_no, '%' )
AND b.device_no = #{reqVO.deviceNo}
AND a.user_id = d.id
<if test="reqVO.userName != null and reqVO.userName != ''">
AND d.nickname LIKE CONCAT( '%', #{reqVO.userName}, '%' )
</if>

View File

@ -0,0 +1,41 @@
<?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="cn.iocoder.yudao.module.system.dal.mysql.equipment.DistributeRecordMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
<select id="selectListByUserIdAndType" resultType="cn.iocoder.yudao.module.system.dal.dataobject.equipment.DistributeRecordDO">
SELECT
a.user_id,
a.result,
b.create_time
FROM
kq_distribute_record a,
(
SELECT
user_id,
MAX( create_time ) AS create_time
FROM
kq_distribute_record
WHERE
type IN
<foreach collection="type" item="type" open="(" separator="," close=")">
#{type}
</foreach>
AND user_id IN
<foreach collection="userId" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
GROUP BY
user_id
) b
WHERE
a.user_id = b.user_id
AND a.create_time = b.create_time
</select>
</mapper>

View File

@ -45,12 +45,15 @@
</if>
<if test="reqVO.createTime != null and reqVO.createTime.length > 0">
<if test="reqVO.createTime[0] != null">
and b.create_time &gt;= #{reqVO.createTime[0]}
AND b.create_time &gt;= #{reqVO.createTime[0]}
</if>
<if test="reqVO.createTime[1] != null">
and b.create_time &lt;= #{reqVO.createTime[1]}
AND b.create_time &lt;= #{reqVO.createTime[1]}
</if>
</if>
<if test="reqVO.userType != null">
AND a.user_type = #{reqVO.userType}
</if>
</where>
</select>
</mapper>