fix(train): 学员详情回填报到状态并优化人脸设备超时与报到接口提示
This commit is contained in:
1 parent
f3041e7718
commit
fe988f32f0
5 files changed
+91
-6
No files matched your search
+28
-1
@@ -4,12 +4,15 @@ import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -19,6 +22,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -69,13 +73,36 @@ public class TrainFaceApiController {
|
||||
@Value("${iot.face.face_api_key:}")
|
||||
private String apiKey;
|
||||
|
||||
/** 设备调用连接超时(毫秒):设备离线时快速失败,避免阻塞报到机请求 */
|
||||
@Value("${face.connect-timeout:3000}")
|
||||
private int connectTimeoutMs;
|
||||
|
||||
/** 设备调用读取超时(毫秒) */
|
||||
@Value("${face.read-timeout:3000}")
|
||||
private int readTimeoutMs;
|
||||
|
||||
@Autowired
|
||||
private ITrainStudentService studentService;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TrainFaceApiController.class);
|
||||
|
||||
/**
|
||||
* 初始化带超时的 RestTemplate。
|
||||
* 默认 RestTemplate 不设超时,设备不可达时会一直等待(实测约 21 秒由系统层超时才失败),
|
||||
* 报到机属现场交互设备,必须快速失败并把失败信息回传给调用方。
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initRestTemplate()
|
||||
{
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(Duration.ofMillis(connectTimeoutMs));
|
||||
factory.setReadTimeout(Duration.ofMillis(readTimeoutMs));
|
||||
this.restTemplate = new RestTemplate(factory);
|
||||
log.info("人脸设备调用超时配置 connectTimeout={}ms readTimeout={}ms", connectTimeoutMs, readTimeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.3 POST /employees 新增员工人脸
|
||||
* 按 employee_id(即学员 sId)查询 train_student,取头像转 Base64 data URL 后调用设备 /employees。
|
||||
|
||||
+28
-2
@@ -693,7 +693,6 @@ public class TrainRegisterApiController {
|
||||
|
||||
String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
|
||||
String fileName = UUID.randomUUID().toString().replace("-", "") + ".png";
|
||||
System.out.println("fileName = " + fileName);
|
||||
File dir = new File(RuoYiConfig.getProfile() + File.separator + "upload" + File.separator + datePath);
|
||||
if (!dir.exists() && !dir.mkdirs())
|
||||
{
|
||||
@@ -708,9 +707,9 @@ public class TrainRegisterApiController {
|
||||
{
|
||||
return AjaxResult.error("图片写入失败:" + ex.getMessage());
|
||||
}
|
||||
log.debug("上传学员人脸 sId={} 落盘文件={}", sId, target.getAbsolutePath());
|
||||
|
||||
String avatarPath = Constants.RESOURCE_PREFIX + "/upload/" + datePath + "/" + fileName;
|
||||
System.out.println("avatarPath = " + avatarPath);
|
||||
// 更新头像路径
|
||||
TrainStudent student = new TrainStudent();
|
||||
student.setsId(sId);
|
||||
@@ -727,6 +726,8 @@ public class TrainRegisterApiController {
|
||||
int rows = trainStudentMapper.updateTrainStudent(student);
|
||||
if (rows <= 0)
|
||||
{
|
||||
// 学员不存在或未更新成功时不保留已落盘图片,避免产生无引用的孤儿文件
|
||||
deleteUploadedFile(target);
|
||||
return AjaxResult.error("学员不存在或更新失败");
|
||||
}
|
||||
|
||||
@@ -957,6 +958,7 @@ public class TrainRegisterApiController {
|
||||
{
|
||||
return AjaxResult.error("图片写入失败:" + ex.getMessage());
|
||||
}
|
||||
log.debug("上传教师人脸 teacherId={} 落盘文件={}", teacherId, target.getAbsolutePath());
|
||||
|
||||
String avatarPath = Constants.RESOURCE_PREFIX + "/upload/" + datePath + "/" + fileName;
|
||||
// 更新教师头像路径(教师表主键为 t_id)
|
||||
@@ -975,6 +977,8 @@ public class TrainRegisterApiController {
|
||||
int rows = teacherService.updateTrainTeacher(teacher);
|
||||
if (rows <= 0)
|
||||
{
|
||||
// 教师不存在或未更新成功时不保留已落盘图片,避免产生无引用的孤儿文件
|
||||
deleteUploadedFile(target);
|
||||
return AjaxResult.error("教师不存在或更新失败");
|
||||
}
|
||||
|
||||
@@ -1106,6 +1110,28 @@ public class TrainRegisterApiController {
|
||||
return uploadStudentFace(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除已落盘的上传文件。用于人脸上传后的业务校验失败场景,
|
||||
* 避免在磁盘留下没有任何数据引用的孤儿图片。
|
||||
*
|
||||
* @param file 待删除的文件,可为 null
|
||||
*/
|
||||
private void deleteUploadedFile(File file)
|
||||
{
|
||||
if (file == null || !file.exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Files.deleteIfExists(file.toPath());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn("删除孤儿上传文件失败 file={}", file.getAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 base64(可带 data URI 前缀)解码并转码为 PNG 字节。
|
||||
*/
|
||||
|
||||
@@ -99,6 +99,9 @@ face:
|
||||
#api-base-url: http://192.168.1.230:9000
|
||||
# 是否实际调用人脸识别设备:false=返回模拟响应,true=真实调用
|
||||
enabled: true
|
||||
# 设备调用超时(毫秒):设备离线时快速失败,避免报到机请求被长时间阻塞
|
||||
connect-timeout: 3000
|
||||
read-timeout: 3000
|
||||
|
||||
# 培训与门禁业务配置
|
||||
train:
|
||||
@@ -186,8 +189,8 @@ iot:
|
||||
#- http://192.168.1.230:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
|
||||
# 调用超时(毫秒)
|
||||
timeout: 10000
|
||||
# lock_endpoint: http://192.168.1.203:8080
|
||||
lock_endpoint: http://192.168.1.202:8085
|
||||
lock_endpoint: http://192.168.1.203:8080
|
||||
#lock_endpoint: http://192.168.1.202:8085
|
||||
api_key: RiviwlSWGDVjY7ktsRsn718R6oDNmrQy
|
||||
avatar_profile: http://192.168.1.106:8096
|
||||
face_api_key: To_eIlokUB6vf6HPSFugljLaAZh38AOI3al5xwb8rFU
|
||||
|
||||
+29
@@ -27,6 +27,7 @@ import com.ruoyi.train.domain.TrainStudent;
|
||||
import com.ruoyi.train.domain.dto.StudentCreateDTO;
|
||||
import com.ruoyi.train.domain.dto.StudentUpdateDTO;
|
||||
import com.ruoyi.train.domain.query.StudentQuery;
|
||||
import com.ruoyi.train.domain.vo.EnrollmentItemVO;
|
||||
import com.ruoyi.train.domain.vo.StudentDetailVO;
|
||||
import com.ruoyi.train.domain.vo.StudentExportVO;
|
||||
import com.ruoyi.train.domain.vo.StudentItemVO;
|
||||
@@ -118,9 +119,37 @@ public class TrainStudentServiceImpl implements ITrainStudentService
|
||||
detail.setRemark(student.getRemark());
|
||||
detail.setCreateBy(student.getCreateBy());
|
||||
detail.setCreateTime(student.getCreateTime());
|
||||
fillEnrollmentStatus(detail);
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用参训档案回填报到状态。
|
||||
*
|
||||
* <p>报到状态只存在于 train_enrollment,学员主档 train_student 不保存该字段。
|
||||
* 报到机上传人脸(/api/register/v1/student/face)更新的是 train_enrollment,
|
||||
* 若详情接口不回填,就会出现“报到状态已变更、但学员详情仍显示空”的口径不一致。</p>
|
||||
*
|
||||
* <p>取值口径与报到流程保持一致:按 create_time 降序取第一条参训记录。</p>
|
||||
*
|
||||
* @param detail 学员详情对象
|
||||
*/
|
||||
private void fillEnrollmentStatus(StudentItemVO detail)
|
||||
{
|
||||
if (trainEnrollmentMapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<EnrollmentItemVO> enrollments = trainEnrollmentMapper.selectEnrollmentsByStudentId(detail.getsId());
|
||||
if (enrollments == null || enrollments.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
EnrollmentItemVO latest = enrollments.get(0);
|
||||
detail.setEnrollmentStatus(latest.getEnrollmentStatus());
|
||||
detail.setJoinTime(latest.getJoinTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String genPersonNo()
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
<el-alert
|
||||
title="报到状态取自参训记录表 train_enrollment 的 enrollment_status 字段(0 待报到 / 1 已报到 / 2 请假 / 3 放弃 / 4 已完成);「已报到」由报到确认流程产生,人工仅可标记请假、放弃与恢复参训。"
|
||||
title="报到状态包含(0 待报到 / 1 已报到 / 2 请假 / 3 放弃 / 4 已完成);「已报到」由报到确认流程产生,人工仅可标记请假、放弃与恢复参训。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
|
||||
Reference in new issue
Block a user