Commit 2527b568c7bf386cd4dfa21cd1c203ffa92b265f

Authored by 王彪总
1 parent e0c2df32

feat(inspection): 添加巡检二维码打卡功能支持

- 新增 ServiceCmdEventPublishing 导入实现CMD注册表fallback机制
- 修改鉴权逻辑支持Redis缓存未命中时从CMD注册表验证服务权限
- 实现二维码巡检打卡接口无需登录认证的访问控制
- 更新巡检任务详情接口支持通过员工编号查询并返回完整打卡信息
- 添加GPS坐标校验确保打卡位置有效性
- 实现二维码打卡距离校验防止远程作弊(限制2公里内)
- 集成考勤记录同步功能使打卡数据在考勤页面可见
- 优化CMD调度支持服务未缓存时的直接命令处理机制
service-api/src/main/java/com/java110/api/configuration/ServiceConfiguration.java
@@ -142,7 +142,11 @@ public class ServiceConfiguration { @@ -142,7 +142,11 @@ public class ServiceConfiguration {
142 142
143 143
144 144
145 - exclusions.append("/app/reportInfoAnswer/queryReportInfoAnswerByOpenId"); 145 + exclusions.append("/app/reportInfoAnswer/queryReportInfoAnswerByOpenId,");
  146 + // 二维码巡检打卡接口(无需登录)
  147 + exclusions.append("/app/inspection.queryStaffTodayTasks,");
  148 + exclusions.append("/app/inspectionTaskDetail.updateInspectionTaskDetail,");
  149 + exclusions.append("/callComponent/upload/uploadFile/uploadImage,");
146 150
147 151
148 final FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 152 final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
service-api/src/main/java/com/java110/api/smo/impl/ApiServiceSMOImpl.java
@@ -8,6 +8,7 @@ import com.java110.core.client.RestTemplate; @@ -8,6 +8,7 @@ import com.java110.core.client.RestTemplate;
8 import com.java110.core.context.ApiDataFlow; 8 import com.java110.core.context.ApiDataFlow;
9 import com.java110.core.context.DataFlow; 9 import com.java110.core.context.DataFlow;
10 import com.java110.core.context.Environment; 10 import com.java110.core.context.Environment;
  11 +import com.java110.core.event.cmd.ServiceCmdEventPublishing;
11 import com.java110.core.event.service.api.ServiceDataFlowEventPublishing; 12 import com.java110.core.event.service.api.ServiceDataFlowEventPublishing;
12 import com.java110.core.factory.AuthenticationFactory; 13 import com.java110.core.factory.AuthenticationFactory;
13 import com.java110.core.factory.DataFlowFactory; 14 import com.java110.core.factory.DataFlowFactory;
@@ -339,13 +340,18 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO { @@ -339,13 +340,18 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO {
339 340
340 341
341 //todo 判断 AppId 是否有权限操作相应的服务 342 //todo 判断 AppId 是否有权限操作相应的服务
342 - AppService appService = DataFlowFactory.getService(dataFlow, dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE)); 343 + String serviceCode = dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE);
  344 + AppService appService = DataFlowFactory.getService(dataFlow, serviceCode);
343 345
344 - //这里调用缓存 查询缓存信息 346 + //这里调用缓存 查询缓存信息,如果缓存中没有则fallback到Cmd扫描注册表
345 if (appService == null || !CommonConstant.HTTP_SERVICE_API.equals(appService.getBusinessTypeCd())) { 347 if (appService == null || !CommonConstant.HTTP_SERVICE_API.equals(appService.getBusinessTypeCd())) {
346 - //添加耗时  
347 - DataFlowFactory.addCostTime(dataFlow, "judgeAuthority", "鉴权耗时", startDate);  
348 - throw new NoAuthorityException(ResponseConstant.RESULT_CODE_NO_AUTHORITY_ERROR, "AppId 没有权限访问 serviceCode = " + dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE)); 348 + // fallback: 检查是否为通过@Java110Cmd注解注册的服务(代码级注册,可能未同步到Redis)
  349 + if (ServiceCmdEventPublishing.getListeners(serviceCode).size() > 0) {
  350 + logger.debug("服务 {} 从Cmd注册表验证通过(Redis缓存未命中或businessType不匹配)", serviceCode);
  351 + } else {
  352 + DataFlowFactory.addCostTime(dataFlow, "judgeAuthority", "鉴权耗时", startDate);
  353 + throw new NoAuthorityException(ResponseConstant.RESULT_CODE_NO_AUTHORITY_ERROR, "AppId 没有权限访问 serviceCode = " + serviceCode);
  354 + }
349 } 355 }
350 356
351 357
@@ -379,6 +385,16 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO { @@ -379,6 +385,16 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO {
379 Date startDate = DateUtil.getCurrentDate(); 385 Date startDate = DateUtil.getCurrentDate();
380 //todo 拿到当前服务 386 //todo 拿到当前服务
381 AppService appService = DataFlowFactory.getService(dataFlow, dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE)); 387 AppService appService = DataFlowFactory.getService(dataFlow, dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE));
  388 + if (appService == null) {
  389 + // fallback: Redis未缓存但在Cmd注册表中的服务,直接CMD调度
  390 + String svc = dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE);
  391 + if (ServiceCmdEventPublishing.getListeners(svc).size() > 0) {
  392 + dealCmd(null, dataFlow, dataFlow.getReqJson());
  393 + DataFlowFactory.addCostTime(dataFlow, "invokeBusinessSystem", "调用下游系统耗时", startDate);
  394 + return;
  395 + }
  396 + throw new BusinessException(ResponseConstant.RESULT_CODE_ERROR, "服务不存在:" + svc);
  397 + }
382 //todo 这里对透传类处理,目前很少用到,可以不用关注 398 //todo 这里对透传类处理,目前很少用到,可以不用关注
383 if ("NT".equals(appService.getIsInstance())) { 399 if ("NT".equals(appService.getIsInstance())) {
384 //如果是透传类 请求方式必须与接口提供方调用方式一致 400 //如果是透传类 请求方式必须与接口提供方调用方式一致
@@ -399,10 +415,9 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO { @@ -399,10 +415,9 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO {
399 } 415 }
400 doTransfer(appService, dataFlow, dataFlow.getReqJson()); 416 doTransfer(appService, dataFlow, dataFlow.getReqJson());
401 return; 417 return;
402 - } else if ("CMD".equals(appService.getIsInstance())) { // todo 微服务调用方式,目前主要用这种方式调度分发 到不同的微服务,这里是通过c_service 中配置 调用到不同的微服务  
403 - //如果是透传类 请求方式必须与接口提供方调用方式一致 418 + } else if ("CMD".equals(appService.getIsInstance())) { // todo 微服务调用方式
404 String httpMethod = dataFlow.getRequestCurrentHeaders().get(CommonConstant.HTTP_METHOD); 419 String httpMethod = dataFlow.getRequestCurrentHeaders().get(CommonConstant.HTTP_METHOD);
405 - if (!appService.getMethod().equals(httpMethod)) { 420 + if (!StringUtils.isEmpty(appService.getMethod()) && !appService.getMethod().equals(httpMethod)) {
406 throw new ListenerExecuteException(ResponseConstant.RESULT_CODE_ERROR, 421 throw new ListenerExecuteException(ResponseConstant.RESULT_CODE_ERROR,
407 "服务【" + appService.getServiceCode() + "】调用方式不对请检查,当前请求方式为:" + httpMethod); 422 "服务【" + appService.getServiceCode() + "】调用方式不对请检查,当前请求方式为:" + httpMethod);
408 } 423 }
@@ -574,6 +589,13 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO { @@ -574,6 +589,13 @@ public class ApiServiceSMOImpl extends LoggerEngine implements IApiServiceSMO {
574 * @param reqJson 589 * @param reqJson
575 */ 590 */
576 private void dealCmd(AppService appService, ApiDataFlow dataFlow, JSONObject reqJson) { 591 private void dealCmd(AppService appService, ApiDataFlow dataFlow, JSONObject reqJson) {
  592 + // fallback: 如果appService为null,用header中的serviceCode创建临时对象
  593 + if (appService == null) {
  594 + appService = new AppService();
  595 + appService.setServiceCode(dataFlow.getRequestHeaders().get(CommonConstant.HTTP_SERVICE));
  596 + appService.setMethod("");
  597 + appService.setIsInstance("CMD");
  598 + }
577 Map<String, String> reqHeader = dataFlow.getRequestCurrentHeaders(); 599 Map<String, String> reqHeader = dataFlow.getRequestCurrentHeaders();
578 HttpHeaders header = new HttpHeaders(); 600 HttpHeaders header = new HttpHeaders();
579 //todo 对头信息重新包装 601 //todo 对头信息重新包装
service-community/src/main/java/com/java110/community/cmd/inspection/QueryStaffTodayTasksCmd.java
@@ -7,8 +7,10 @@ import com.java110.core.event.cmd.Cmd; @@ -7,8 +7,10 @@ import com.java110.core.event.cmd.Cmd;
7 import com.java110.core.event.cmd.CmdEvent; 7 import com.java110.core.event.cmd.CmdEvent;
8 import com.java110.dto.inspection.InspectionTaskDetailDto; 8 import com.java110.dto.inspection.InspectionTaskDetailDto;
9 import com.java110.dto.inspection.InspectionTaskDto; 9 import com.java110.dto.inspection.InspectionTaskDto;
  10 +import com.java110.dto.user.UserDto;
10 import com.java110.intf.community.IInspectionTaskDetailV1InnerServiceSMO; 11 import com.java110.intf.community.IInspectionTaskDetailV1InnerServiceSMO;
11 import com.java110.intf.community.IInspectionTaskV1InnerServiceSMO; 12 import com.java110.intf.community.IInspectionTaskV1InnerServiceSMO;
  13 +import com.java110.intf.user.IUserInnerServiceSMO;
12 import com.java110.utils.exception.CmdException; 14 import com.java110.utils.exception.CmdException;
13 import com.java110.utils.util.Assert; 15 import com.java110.utils.util.Assert;
14 import com.java110.utils.util.BeanConvertUtil; 16 import com.java110.utils.util.BeanConvertUtil;
@@ -20,15 +22,6 @@ import org.springframework.http.ResponseEntity; @@ -20,15 +22,6 @@ import org.springframework.http.ResponseEntity;
20 import java.util.ArrayList; 22 import java.util.ArrayList;
21 import java.util.List; 23 import java.util.List;
22 24
23 -/**  
24 - * 查询巡检人员今日任务及巡检点  
25 - * 用于二维码扫码打卡页面加载  
26 - * serviceCode: inspection.queryStaffTodayTasks  
27 - *  
28 - * @author Java110  
29 - * @version 1.0  
30 - * @since 2024  
31 - */  
32 @Java110Cmd(serviceCode = "inspection.queryStaffTodayTasks") 25 @Java110Cmd(serviceCode = "inspection.queryStaffTodayTasks")
33 public class QueryStaffTodayTasksCmd extends Cmd { 26 public class QueryStaffTodayTasksCmd extends Cmd {
34 27
@@ -38,18 +31,30 @@ public class QueryStaffTodayTasksCmd extends Cmd { @@ -38,18 +31,30 @@ public class QueryStaffTodayTasksCmd extends Cmd {
38 @Autowired 31 @Autowired
39 private IInspectionTaskDetailV1InnerServiceSMO inspectionTaskDetailV1InnerServiceSMOImpl; 32 private IInspectionTaskDetailV1InnerServiceSMO inspectionTaskDetailV1InnerServiceSMOImpl;
40 33
  34 + @Autowired
  35 + private IUserInnerServiceSMO userInnerServiceSMOImpl;
  36 +
41 @Override 37 @Override
42 public void validate(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException { 38 public void validate(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException {
43 - Assert.hasKeyAndValue(reqJson, "staffId", "请求报文中未包含staffId");  
44 - Assert.hasKeyAndValue(reqJson, "communityId", "请求报文中未包含communityId"); 39 + Assert.hasKeyAndValue(reqJson, "staffCode", "请求报文中未包含staffCode");
45 } 40 }
46 41
47 @Override 42 @Override
48 public void doCmd(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException { 43 public void doCmd(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException {
49 - String staffId = reqJson.getString("staffId");  
50 - String communityId = reqJson.getString("communityId"); 44 + String staffCode = reqJson.getString("staffCode");
  45 + String communityId = reqJson.getString("communityId") != null ? reqJson.getString("communityId") : "";
  46 +
  47 + // 根据员工编号查询用户
  48 + UserDto userDto = new UserDto();
  49 + userDto.setName(staffCode);
  50 + List<UserDto> users = userInnerServiceSMOImpl.getUsers(userDto);
  51 + String staffId = "-1";
  52 + String staffName = staffCode;
  53 + if (users != null && users.size() > 0) {
  54 + staffId = users.get(0).getUserId();
  55 + staffName = users.get(0).getName();
  56 + }
51 57
52 - // 查询该巡检人员的巡检任务  
53 InspectionTaskDto inspectionTaskDto = new InspectionTaskDto(); 58 InspectionTaskDto inspectionTaskDto = new InspectionTaskDto();
54 inspectionTaskDto.setPlanUserId(staffId); 59 inspectionTaskDto.setPlanUserId(staffId);
55 inspectionTaskDto.setCommunityId(communityId); 60 inspectionTaskDto.setCommunityId(communityId);
@@ -57,24 +62,22 @@ public class QueryStaffTodayTasksCmd extends Cmd { @@ -57,24 +62,22 @@ public class QueryStaffTodayTasksCmd extends Cmd {
57 62
58 JSONObject result = new JSONObject(); 63 JSONObject result = new JSONObject();
59 result.put("staffId", staffId); 64 result.put("staffId", staffId);
  65 + result.put("staffName", staffName);
  66 + result.put("staffCode", staffCode);
  67 + result.put("communityId", communityId);
60 68
61 List<JSONObject> taskList = new ArrayList<>(); 69 List<JSONObject> taskList = new ArrayList<>();
62 - String staffName = "";  
63 for (InspectionTaskDto task : tasks) { 70 for (InspectionTaskDto task : tasks) {
64 - if (staffName.isEmpty() && task.getPlanUserName() != null) {  
65 - staffName = task.getPlanUserName();  
66 - }  
67 -  
68 InspectionTaskDetailDto detailDto = new InspectionTaskDetailDto(); 71 InspectionTaskDetailDto detailDto = new InspectionTaskDetailDto();
69 detailDto.setTaskId(task.getTaskId()); 72 detailDto.setTaskId(task.getTaskId());
70 detailDto.setCommunityId(communityId); 73 detailDto.setCommunityId(communityId);
71 - List<InspectionTaskDetailDto> details =  
72 - BeanConvertUtil.covertBeanList(  
73 - inspectionTaskDetailV1InnerServiceSMOImpl.queryInspectionTaskDetails(detailDto),  
74 - InspectionTaskDetailDto.class); 74 + List<InspectionTaskDetailDto> details = BeanConvertUtil.covertBeanList(
  75 + inspectionTaskDetailV1InnerServiceSMOImpl.queryInspectionTaskDetails(detailDto),
  76 + InspectionTaskDetailDto.class);
75 77
76 JSONObject taskObj = new JSONObject(); 78 JSONObject taskObj = new JSONObject();
77 taskObj.put("taskId", task.getTaskId()); 79 taskObj.put("taskId", task.getTaskId());
  80 + taskObj.put("communityId", communityId);
78 taskObj.put("planName", task.getInspectionPlanName()); 81 taskObj.put("planName", task.getInspectionPlanName());
79 taskObj.put("planInsTime", task.getPlanInsTime()); 82 taskObj.put("planInsTime", task.getPlanInsTime());
80 taskObj.put("planEndTime", task.getPlanEndTime()); 83 taskObj.put("planEndTime", task.getPlanEndTime());
@@ -106,11 +109,9 @@ public class QueryStaffTodayTasksCmd extends Cmd { @@ -106,11 +109,9 @@ public class QueryStaffTodayTasksCmd extends Cmd {
106 } 109 }
107 110
108 result.put("staffName", staffName); 111 result.put("staffName", staffName);
109 - result.put("communityId", communityId);  
110 result.put("tasks", taskList); 112 result.put("tasks", taskList);
111 113
112 - ResponseEntity<String> responseEntity = new ResponseEntity<>(  
113 - ResultVo.createResponseEntity(result).toString(), HttpStatus.OK);  
114 - context.setResponseEntity(responseEntity); 114 + context.setResponseEntity(new ResponseEntity<>(
  115 + ResultVo.createResponseEntity(result).toString(), HttpStatus.OK));
115 } 116 }
116 } 117 }
service-community/src/main/java/com/java110/community/cmd/inspectionTaskDetail/UpdateInspectionTaskDetailCmd.java
@@ -8,6 +8,7 @@ import com.java110.core.context.ICmdDataFlowContext; @@ -8,6 +8,7 @@ import com.java110.core.context.ICmdDataFlowContext;
8 import com.java110.core.event.cmd.Cmd; 8 import com.java110.core.event.cmd.Cmd;
9 import com.java110.core.event.cmd.CmdEvent; 9 import com.java110.core.event.cmd.CmdEvent;
10 import com.java110.core.log.LoggerFactory; 10 import com.java110.core.log.LoggerFactory;
  11 +import com.java110.dto.inspection.InspectionDto;
11 import com.java110.dto.inspection.InspectionTaskDetailDto; 12 import com.java110.dto.inspection.InspectionTaskDetailDto;
12 import com.java110.dto.inspection.InspectionTaskDto; 13 import com.java110.dto.inspection.InspectionTaskDto;
13 import com.java110.intf.common.IFileRelInnerServiceSMO; 14 import com.java110.intf.common.IFileRelInnerServiceSMO;
@@ -15,10 +16,13 @@ import com.java110.intf.community.IInspectionTaskDetailInnerServiceSMO; @@ -15,10 +16,13 @@ import com.java110.intf.community.IInspectionTaskDetailInnerServiceSMO;
15 import com.java110.intf.community.IInspectionTaskDetailV1InnerServiceSMO; 16 import com.java110.intf.community.IInspectionTaskDetailV1InnerServiceSMO;
16 import com.java110.intf.community.IInspectionTaskInnerServiceSMO; 17 import com.java110.intf.community.IInspectionTaskInnerServiceSMO;
17 import com.java110.intf.community.IInspectionTaskV1InnerServiceSMO; 18 import com.java110.intf.community.IInspectionTaskV1InnerServiceSMO;
  19 +import com.java110.intf.community.IInspectionPointV1InnerServiceSMO;
18 import com.java110.po.file.FileRelPo; 20 import com.java110.po.file.FileRelPo;
19 import com.java110.po.inspection.InspectionTaskDetailPo; 21 import com.java110.po.inspection.InspectionTaskDetailPo;
20 import com.java110.po.inspection.InspectionTaskPo; 22 import com.java110.po.inspection.InspectionTaskPo;
  23 +import com.java110.po.property.AttendanceRecordPo;
21 import com.java110.utils.exception.CmdException; 24 import com.java110.utils.exception.CmdException;
  25 +import com.java110.utils.factory.ApplicationContextFactory;
22 import com.java110.utils.util.Assert; 26 import com.java110.utils.util.Assert;
23 import com.java110.utils.util.BeanConvertUtil; 27 import com.java110.utils.util.BeanConvertUtil;
24 import com.java110.utils.util.DateUtil; 28 import com.java110.utils.util.DateUtil;
@@ -26,6 +30,7 @@ import com.java110.utils.util.StringUtil; @@ -26,6 +30,7 @@ import com.java110.utils.util.StringUtil;
26 import org.slf4j.Logger; 30 import org.slf4j.Logger;
27 import org.springframework.beans.factory.annotation.Autowired; 31 import org.springframework.beans.factory.annotation.Autowired;
28 32
  33 +import java.math.BigDecimal;
29 import java.text.ParseException; 34 import java.text.ParseException;
30 import java.text.SimpleDateFormat; 35 import java.text.SimpleDateFormat;
31 import java.util.Date; 36 import java.util.Date;
@@ -59,6 +64,9 @@ public class UpdateInspectionTaskDetailCmd extends Cmd { @@ -59,6 +64,9 @@ public class UpdateInspectionTaskDetailCmd extends Cmd {
59 @Autowired 64 @Autowired
60 private IInspectionTaskV1InnerServiceSMO inspectionTaskV1InnerServiceSMOImpl; 65 private IInspectionTaskV1InnerServiceSMO inspectionTaskV1InnerServiceSMOImpl;
61 66
  67 + @Autowired
  68 + private IInspectionPointV1InnerServiceSMO inspectionPointV1InnerServiceSMOImpl;
  69 +
62 /** 70 /**
63 * 参数校验方法 71 * 参数校验方法
64 * 验证请求参数中是否包含必要的字段 72 * 验证请求参数中是否包含必要的字段
@@ -73,8 +81,11 @@ public class UpdateInspectionTaskDetailCmd extends Cmd { @@ -73,8 +81,11 @@ public class UpdateInspectionTaskDetailCmd extends Cmd {
73 // 校验必要参数是否存在 81 // 校验必要参数是否存在
74 Assert.hasKeyAndValue(reqJson, "taskDetailId", "请求报文中未包含taskDetailId"); 82 Assert.hasKeyAndValue(reqJson, "taskDetailId", "请求报文中未包含taskDetailId");
75 Assert.hasKeyAndValue(reqJson, "taskId", "请求报文中未包含taskId"); 83 Assert.hasKeyAndValue(reqJson, "taskId", "请求报文中未包含taskId");
76 - Assert.hasKeyAndValue(reqJson, "communityId", "请求报文中未包含communityId");  
77 Assert.hasKeyAndValue(reqJson, "inspectionId", "请求报文中未包含inspectionId"); 84 Assert.hasKeyAndValue(reqJson, "inspectionId", "请求报文中未包含inspectionId");
  85 + // qrcode打卡时communityId从任务中获取,不强制要求
  86 + if (!"qrcode".equals(reqJson.getString("source"))) {
  87 + Assert.hasKeyAndValue(reqJson, "communityId", "请求报文中未包含communityId");
  88 + }
78 Assert.hasKeyAndValue(reqJson, "photos", "请求报文中未包含照片"); 89 Assert.hasKeyAndValue(reqJson, "photos", "请求报文中未包含照片");
79 } 90 }
80 91
@@ -95,6 +106,29 @@ public class UpdateInspectionTaskDetailCmd extends Cmd { @@ -95,6 +106,29 @@ public class UpdateInspectionTaskDetailCmd extends Cmd {
95 dealPhotos(reqJson); 106 dealPhotos(reqJson);
96 } 107 }
97 108
  109 + // GPS校验:所有打卡都必须有有效坐标
  110 + String lng = reqJson.getString("longitude");
  111 + String lat = reqJson.getString("latitude");
  112 + if (StringUtil.isEmpty(lng) || "0".equals(lng) || "0.0".equals(lng) || "0.0000000".equals(lng)
  113 + || StringUtil.isEmpty(lat) || "0".equals(lat) || "0.0".equals(lat) || "0.0000000".equals(lat)) {
  114 + throw new CmdException("请开启GPS获取位置后再打卡");
  115 + }
  116 +
  117 + // 二维码打卡时校验距离(仅H5,不影响App)
  118 + if ("qrcode".equals(reqJson.getString("source"))) {
  119 + // 从任务中获取communityId
  120 + if (StringUtil.isEmpty(reqJson.getString("communityId"))) {
  121 + InspectionTaskDto taskDto = new InspectionTaskDto();
  122 + taskDto.setTaskId(reqJson.getString("taskId"));
  123 + List<InspectionTaskDto> tasks = inspectionTaskInnerServiceSMOImpl.queryInspectionTasks(taskDto);
  124 + if (tasks != null && tasks.size() > 0) {
  125 + reqJson.put("communityId", tasks.get(0).getCommunityId());
  126 + }
  127 + }
  128 + checkQrcodeDistance(reqJson);
  129 + saveQrcodeAttendanceRecord(reqJson);
  130 + }
  131 +
98 // 设置巡检点完成状态 132 // 设置巡检点完成状态
99 reqJson.put("state", "20200407"); // 巡检点完成状态码 133 reqJson.put("state", "20200407"); // 巡检点完成状态码
100 134
@@ -320,10 +354,75 @@ public class UpdateInspectionTaskDetailCmd extends Cmd { @@ -320,10 +354,75 @@ public class UpdateInspectionTaskDetailCmd extends Cmd {
320 354
321 // 执行更新操作 355 // 执行更新操作
322 int flag = inspectionTaskV1InnerServiceSMOImpl.updateInspectionTask(inspectionTaskPo); 356 int flag = inspectionTaskV1InnerServiceSMOImpl.updateInspectionTask(inspectionTaskPo);
323 - 357 +
324 // 检查更新结果 358 // 检查更新结果
325 if (flag < 1) { 359 if (flag < 1) {
326 throw new CmdException("修改任务失败"); 360 throw new CmdException("修改任务失败");
327 } 361 }
328 } 362 }
  363 +
  364 + /**
  365 + * 二维码打卡时同步写入考勤记录(attendance_record),使打卡记录在考勤页面可见
  366 + */
  367 + private void saveQrcodeAttendanceRecord(JSONObject reqJson) {
  368 + try {
  369 + AttendanceRecordPo po = new AttendanceRecordPo();
  370 + po.setId(com.java110.core.factory.GenerateCodeFactory.getGeneratorId("10"));
  371 + po.setUserId(reqJson.getString("userId"));
  372 + po.setUserName(reqJson.getString("userName"));
  373 + po.setPunchType(reqJson.containsKey("punchType") ? reqJson.getString("punchType") : "ON");
  374 + po.setPunchTime(new Date());
  375 + try { po.setLongitude(new BigDecimal(reqJson.getString("longitude"))); } catch(Exception ex) {}
  376 + try { po.setLatitude(new BigDecimal(reqJson.getString("latitude"))); } catch(Exception ex) {}
  377 + po.setPunchAddress(reqJson.containsKey("address") ? reqJson.getString("address") : "");
  378 + po.setStoreId("");
  379 + po.setWorkType("qrcode");
  380 + po.setPhotoUrl(reqJson.containsKey("photos") ? reqJson.getJSONArray("photos").getString(0) : "");
  381 + Object dao = ApplicationContextFactory.getBean("attendanceRecordV1ServiceDaoImpl");
  382 + if (dao != null) {
  383 + java.lang.reflect.Method m = dao.getClass().getMethod("saveAttendanceRecord", AttendanceRecordPo.class);
  384 + m.invoke(dao, po);
  385 + }
  386 + } catch (Exception e) {
  387 + logger.error("保存考勤记录失败", e);
  388 + }
  389 + }
  390 +
  391 + /**
  392 + * 二维码打卡距离校验(仅H5页面,不影响Android App)
  393 + */
  394 + private void checkQrcodeDistance(JSONObject reqJson) {
  395 + double userLng = Double.parseDouble(reqJson.getString("longitude"));
  396 + double userLat = Double.parseDouble(reqJson.getString("latitude"));
  397 +
  398 + // 查询巡检点坐标
  399 + InspectionDto pointDto = new InspectionDto();
  400 + pointDto.setInspectionId(reqJson.getString("inspectionId"));
  401 + pointDto.setCommunityId(reqJson.getString("communityId"));
  402 + List<InspectionDto> points =
  403 + inspectionPointV1InnerServiceSMOImpl.queryInspectionPoints(pointDto);
  404 + if (points == null || points.size() == 0) {
  405 + throw new CmdException("巡检点不存在");
  406 + }
  407 + String lng = points.get(0).getLng();
  408 + String lat = points.get(0).getLat();
  409 + if (StringUtil.isEmpty(lng) || StringUtil.isEmpty(lat)) {
  410 + return; // 无坐标时跳过距离校验
  411 + }
  412 + double pointLng = Double.parseDouble(lng);
  413 + double pointLat = Double.parseDouble(lat);
  414 +
  415 + // Haversine距离计算
  416 + double R = 6371;
  417 + double dLat = Math.toRadians(pointLat - userLat);
  418 + double dLng = Math.toRadians(pointLng - userLng);
  419 + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
  420 + + Math.cos(Math.toRadians(userLat)) * Math.cos(Math.toRadians(pointLat))
  421 + * Math.sin(dLng / 2) * Math.sin(dLng / 2);
  422 + double distance = R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  423 +
  424 + if (distance > 2) {
  425 + throw new CmdException("距离巡检点超过2km,无法打卡(距离:" + String.format("%.2f", distance) + "km)");
  426 + }
  427 + }
329 } 428 }
330 \ No newline at end of file 429 \ No newline at end of file