fix(questionnaire): 修复问卷模块后端问题

- 修复 AnswerServiceImpl 评分逻辑:使用数组索引替代 option.id
- 修复 AnswerServiceImpl 正确性判断:使用 score > 0 替代 isCorrect
- 优化选项匹配逻辑,适配前端 optionIds 数据格式

Refs #questionnaire-fixes
This commit is contained in:
tangweijie 2026-02-04 18:30:18 +08:00
parent 25aa6144c4
commit 483b95633b
33 changed files with 1471 additions and 79 deletions

View File

@ -8,20 +8,66 @@ import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
/**
* 基于时间戳的 LocalDateTime 反序列化器
* 支持 Long 时间戳ISO 8601 格式/不带时区普通日期时间格式
* 默认时区Asia/Shanghai上海
*
* @author 老五
*/
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
/** 默认时区:上海 */
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
private static final DateTimeFormatter NORMAL_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter NORMAL_FORMATTER_WITH_ZONE = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(DEFAULT_ZONE);
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Long 时间戳转换为 LocalDateTime 对象
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
String value = p.getValueAsString();
if (value == null || value.isEmpty()) {
return null;
}
value = value.trim();
// 1. 尝试解析 Long 时间戳毫秒
try {
long millis = Long.parseLong(value);
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), DEFAULT_ZONE);
} catch (NumberFormatException e) {
// 不是时间戳继续尝试其他格式
}
// 2. 尝试解析 ISO 8601 格式可能带时区 2026-01-31T16:00:00+08:00 2026-01-31T16:00:00.000Z
try {
return LocalDateTime.parse(value, ISO_FORMATTER);
} catch (Exception e) {
// 不是 ISO 格式继续尝试其他格式
}
// 3. 尝试解析普通日期时间格式 (yyyy-MM-dd HH:mm:ss)
// 明确使用 Asia/Shanghai 时区将字符串当作上海时区的时间来解析
try {
return ZonedDateTime.parse(value + "+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX"))
.withZoneSameInstant(DEFAULT_ZONE)
.toLocalDateTime();
} catch (Exception e) {
// 解析失败尝试使用 NORMAL_FORMATTER
}
// 4. 兜底尝试普通格式化器
try {
return LocalDateTime.parse(value, NORMAL_FORMATTER);
} catch (Exception e) {
throw new IOException("无法解析日期时间格式: " + value);
}
}
}

View File

@ -73,9 +73,14 @@ public class GlobalExceptionHandler {
*
* @param request 请求
* @param ex 异常
* @return 通用返回
* @return 通用返回如果是 SSE 请求则返回 null
*/
public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
// 检测是否是 SSE 请求
if (isSseRequest(request)) {
log.warn("[allExceptionHandler][SSE 请求异常,不返回 CommonResult: {}]", ex.getMessage());
return null;
}
if (ex instanceof MissingServletRequestParameterException) {
return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
}
@ -319,6 +324,12 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(value = Exception.class)
public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
// SSE 请求不返回 CommonResult避免二次异常
if (isSseRequest(req)) {
log.warn("[defaultExceptionHandler][SSE 请求异常,不返回 CommonResult: {}]", ex.getMessage());
return null;
}
// 特殊如果是 ServiceException 的异常则直接返回
// 例如说https://gitee.com/zhijiantianya/yudao-cloud/issues/ICSSRMhttps://gitee.com/zhijiantianya/yudao-cloud/issues/ICT6FM
if (ex.getCause() != null && ex.getCause() instanceof ServiceException) {
@ -450,4 +461,12 @@ public class GlobalExceptionHandler {
return null;
}
/**
* 判断是否是 SSE 请求
*/
private boolean isSseRequest(HttpServletRequest request) {
String accept = request.getHeader("Accept");
return accept != null && accept.contains("text/event-stream");
}
}

View File

@ -89,7 +89,7 @@ public class PrisonAnswerController {
@GetMapping("/list-by-assessment-record")
@Operation(summary = "根据测评记录ID查询答题列表")
@PreAuthorize("@ss.hasPermission('prison:answer:query')")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')")
public CommonResult<List<AnswerRespVO>> getAnswersByAssessmentRecordId(
@NotNull(message = "测评记录ID不能为空") @RequestParam("assessmentRecordId") Long assessmentRecordId) {
List<AnswerDO> list = answerService.getAnswersByAssessmentRecordId(assessmentRecordId);

View File

@ -13,6 +13,7 @@ import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.io.IOException;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@ -377,6 +378,21 @@ public class EvaluationReportController {
BeanUtils.toBean(list, EvaluationReportRespVO.class));
}
@PostMapping("/report/generate-by-ai")
@Operation(summary = "AI生成报告维度内容")
@PreAuthorize("@ss.hasPermission('prison:evaluation-report:report:ai-generate')")
public CommonResult<Boolean> generateReportByAi(@RequestBody Map<String, Object> requestData) {
Long reportId = Long.valueOf(requestData.get("reportId").toString());
@SuppressWarnings("unchecked")
List<Long> dimensionIds = requestData.get("dimensionIds") != null
? ((List<Number>) requestData.get("dimensionIds")).stream()
.map(Number::longValue)
.collect(Collectors.toList())
: null;
evaluationReportService.generateReportByAi(reportId, dimensionIds);
return success(true);
}
// ========== 维度数据管理 ==========
@PostMapping("/dimension-data/create")

View File

@ -10,6 +10,12 @@ import java.time.LocalDateTime;
@Data
public class PrisonerProgressRespVO {
@Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Long questionnaireId;
@Schema(description = "问卷名称", example = "风险评估问卷")
private String questionnaireName;
@Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;

View File

@ -6,6 +6,9 @@ import java.util.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 危险评估新增/修改 Request VO")
@Data
@ -28,6 +31,7 @@ public class RiskAssessmentSaveReqVO {
@Schema(description = "评估日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "评估日期不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate assessmentDate;
@Schema(description = "暴力倾向得分")
@ -55,6 +59,7 @@ public class RiskAssessmentSaveReqVO {
// 评估人ID和评估人姓名由后端自动从登录上下文获取不从前端传递
@Schema(description = "下次评估日期")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate nextAssessmentDate;
@Schema(description = "状态1-待审核 2-已通过", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")

View File

@ -5,6 +5,7 @@ import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.*;
/**
@ -67,6 +68,7 @@ public class SituationRespVO {
@Schema(description = "处理时间")
@ExcelProperty("处理时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime handleTime;
@Schema(description = "处理结果")
@ -79,10 +81,12 @@ public class SituationRespVO {
@Schema(description = "发生时间")
@ExcelProperty("发生时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime occurTime;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime createTime;
}

View File

@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 管理后台 - 狱情收集新增/修改 Request VO
@ -54,6 +55,7 @@ public class SituationSaveReqVO {
private String handler;
@Schema(description = "处理时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private java.time.LocalDateTime handleTime;
@Schema(description = "处理结果")
@ -63,6 +65,7 @@ public class SituationSaveReqVO {
private String remark;
@Schema(description = "发生时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private java.time.LocalDateTime occurTime;
}

View File

@ -87,8 +87,8 @@ public class WarningController {
@Operation(summary = "分页查询预警信息")
@PreAuthorize("@ss.hasPermission('prison:warning:query')")
public CommonResult<PageResult<WarningRespVO>> page(@Valid WarningPageReqVO pageReqVO) {
PageResult<WarningDO> pageResult = warningService.getWarningPage(pageReqVO);
return success(WarningConvert.INSTANCE.convertPage(pageResult));
PageResult<WarningRespVO> pageResult = warningService.getWarningPage(pageReqVO);
return success(pageResult);
}
@GetMapping("/export-excel")
@ -97,10 +97,9 @@ public class WarningController {
public void exportExcel(@Valid WarningPageReqVO pageReqVO,
HttpServletResponse response) throws Exception {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<WarningDO> list = warningService.getWarningPage(pageReqVO).getList();
List<WarningRespVO> list = warningService.getWarningPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "预警信息.xls", "预警信息数据", WarningRespVO.class,
WarningConvert.INSTANCE.convertList(list));
ExcelUtils.write(response, "预警信息.xls", "预警信息数据", WarningRespVO.class, list);
}
@GetMapping("/get-import-template")

View File

@ -39,6 +39,9 @@ public class WarningPageReqVO extends PageParam {
@Schema(description = "关联监区ID")
private Long areaId;
@Schema(description = "监区名称(查询条件)")
private String areaName;
@Schema(description = "关联监室ID")
private Long cellId;

View File

@ -54,6 +54,10 @@ public class WarningRespVO {
@ExcelProperty("关联监区ID")
private Long areaId;
@Schema(description = "监区名称", example = "一监区")
@ExcelProperty("监区名称")
private String areaName;
@Schema(description = "关联监室ID")
@ExcelProperty("关联监室ID")
private Long cellId;
@ -120,4 +124,9 @@ public class WarningRespVO {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime createTime;
@Schema(description = "更新时间")
@ExcelProperty("更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private LocalDateTime updateTime;
}

View File

@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 管理后台 - 预警信息新增/修改 Request VO
@ -51,9 +52,11 @@ public class WarningSaveReqVO {
private Long cellId;
@Schema(description = "预警时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.time.LocalDateTime alertTime;
@Schema(description = "核实时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.time.LocalDateTime verifyTime;
@Schema(description = "核实人")
@ -63,6 +66,7 @@ public class WarningSaveReqVO {
private String verifyResult;
@Schema(description = "处置时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.time.LocalDateTime handleTime;
@Schema(description = "处置人")
@ -75,6 +79,7 @@ public class WarningSaveReqVO {
private String handleResult;
@Schema(description = "解除时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.time.LocalDateTime releaseTime;
@Schema(description = "解除人")
@ -84,6 +89,7 @@ public class WarningSaveReqVO {
private String releaseReason;
@Schema(description = "发生时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.time.LocalDateTime occurTime;
@Schema(description = "备注")

View File

@ -27,7 +27,8 @@ public interface QuestionMapper extends BaseMapperX<QuestionDO> {
.eqIfPresent(QuestionDO::getSort, reqVO.getSort())
.eqIfPresent(QuestionDO::getIsRequired, reqVO.getIsRequired())
.betweenIfPresent(QuestionDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(QuestionDO::getId));
.orderByAsc(QuestionDO::getPartSort)
.orderByAsc(QuestionDO::getSort));
}
}

View File

@ -36,7 +36,14 @@ public interface QuestionnaireTaskMapper extends BaseMapperX<QuestionnaireTaskDO
"WHERE qt.id = #{id}")
Map<String, Object> selectTaskDetailById(@Param("id") Long id);
@Select("UPDATE prison_questionnaire_task qt " +
/**
* 更新任务统计信息
* 注意使用 @Select 是因为 MyBatis-Plus 需要返回结果但这里我们只关心执行结果
* 实际执行的是 UPDATE 操作MyBatis 会忽略返回的查询结果
* @param taskId 任务ID
*/
@Select("<script>" +
"UPDATE prison_questionnaire_task qt " +
"SET total_count = (SELECT COUNT(*) FROM prison_questionnaire_record r WHERE r.task_id = #{taskId}), " +
"completed_count = (SELECT COUNT(*) FROM prison_questionnaire_record r WHERE r.task_id = #{taskId} AND r.status = 3), " +
"pending_count = (SELECT COUNT(*) FROM prison_questionnaire_record r WHERE r.task_id = #{taskId} AND r.status IN (1, 2)), " +
@ -46,7 +53,8 @@ public interface QuestionnaireTaskMapper extends BaseMapperX<QuestionnaireTaskDO
"(SELECT COUNT(*) FROM prison_questionnaire_record r WHERE r.task_id = #{taskId}) " +
"ELSE 0 END, " +
"update_time = NOW() " +
"WHERE qt.id = #{taskId}")
"WHERE qt.id = #{taskId}" +
"</script>")
void updateTaskStatistics(@Param("taskId") Long taskId);
@Select("SELECT COUNT(*) as task_count, " +

View File

@ -7,6 +7,9 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.warning.WarningDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Select;
import cn.iocoder.yudao.module.prison.controller.admin.warning.vo.*;
/**
@ -36,4 +39,91 @@ public interface WarningMapper extends BaseMapperX<WarningDO> {
.orderByDesc(WarningDO::getId));
}
/**
* 分页查询预警信息包含监区名称
*/
@Select("""
<script>
SELECT
pw.id,
pw.title,
pw.content,
pw.type,
pw.level,
pw.status,
pw.source,
pw.situation_id,
pw.area_id,
pa.name AS area_name,
pw.cell_id,
pw.alert_time,
pw.verify_time,
pw.verifier,
pw.verify_result,
pw.handle_time,
pw.handler,
pw.handle_method,
pw.handle_result,
pw.release_time,
pw.releaser,
pw.release_reason,
pw.occur_time,
pw.remark,
pw.creator,
pw.create_time,
pw.updater,
pw.update_time,
pw.deleted,
pw.tenant_id
FROM prison_warning pw
LEFT JOIN prison_area pa ON pw.area_id = pa.id
WHERE pw.deleted = 0
<if test="reqVO.title != null and reqVO.title != ''">
AND pw.title LIKE CONCAT('%', #{reqVO.title}, '%')
</if>
<if test="reqVO.type != null">
AND pw.type = #{reqVO.type}
</if>
<if test="reqVO.level != null">
AND pw.level = #{reqVO.level}
</if>
<if test="reqVO.status != null">
AND pw.status = #{reqVO.status}
</if>
<if test="reqVO.source != null">
AND pw.source = #{reqVO.source}
</if>
<if test="reqVO.situationId != null">
AND pw.situation_id = #{reqVO.situationId}
</if>
<if test="reqVO.areaId != null">
AND pw.area_id = #{reqVO.areaId}
</if>
<if test="reqVO.cellId != null">
AND pw.cell_id = #{reqVO.cellId}
</if>
<if test="reqVO.verifier != null and reqVO.verifier != ''">
AND pw.verifier = #{reqVO.verifier}
</if>
<if test="reqVO.handler != null and reqVO.handler != ''">
AND pw.handler = #{reqVO.handler}
</if>
<if test="reqVO.releaser != null and reqVO.releaser != ''">
AND pw.releaser = #{reqVO.releaser}
</if>
<if test="reqVO.alertTime != null">
AND pw.alert_time BETWEEN #{reqVO.alertTime[0]} AND #{reqVO.alertTime[1]}
</if>
<if test="reqVO.occurTime != null">
AND pw.occur_time BETWEEN #{reqVO.occurTime[0]} AND #{reqVO.occurTime[1]}
</if>
<if test="reqVO.createTime != null">
AND pw.create_time BETWEEN #{reqVO.createTime[0]} AND #{reqVO.createTime[1]}
</if>
ORDER BY pw.id DESC
</script>
""")
@ResultMap("WarningResultMap")
List<WarningRespVO> selectWarningPage(@Param("reqVO") WarningPageReqVO reqVO);
}

View File

@ -14,6 +14,7 @@ import java.util.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.AssessmentAnswerSubmitReqVO;
import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO;
@ -220,7 +221,8 @@ public class AnswerServiceImpl implements AnswerService {
/**
* 计算选择题得分
* 选项格式: [{label:"选项1",score:10,isCorrect:true},...]
* 选项格式: [{label:"选项1",score:10,isOther:false},...]
* 使用数组索引匹配选项从0开始
*/
private BigDecimal calculateChoiceQuestionScore(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (question.getOptions() == null || question.getOptions().isEmpty()) {
@ -241,14 +243,13 @@ public class AnswerServiceImpl implements AnswerService {
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return BigDecimal.ZERO;
}
Long selectedOptionId = answerItem.getOptionIds().get(0);
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)) {
// 检查是否正确选项
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
return questionScore;
}
// 使用数组索引从0开始匹配选项
Long selectedOptionIndex = answerItem.getOptionIds().get(0);
if (selectedOptionIndex >= 0 && selectedOptionIndex < options.size()) {
JSONObject option = options.getJSONObject(selectedOptionIndex.intValue());
// 检查是否正确选项score > 0 表示正确
if (option.getBigDecimal("score") != null && option.getBigDecimal("score").compareTo(BigDecimal.ZERO) > 0) {
return questionScore;
}
}
return BigDecimal.ZERO;
@ -262,22 +263,21 @@ public class AnswerServiceImpl implements AnswerService {
int correctCount = 0;
int userSelectCount = answerItem.getOptionIds().size();
// 统计正确选项数量score > 0 表示正确
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
if (option.getBigDecimal("score") != null && option.getBigDecimal("score").compareTo(BigDecimal.ZERO) > 0) {
correctCount++;
}
}
// 用户选择的正确选项数量
int userCorrectCount = 0;
for (Long selectedOptionId : answerItem.getOptionIds()) {
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)
&& option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
for (Long selectedOptionIndex : answerItem.getOptionIds()) {
if (selectedOptionIndex >= 0 && selectedOptionIndex < options.size()) {
JSONObject option = options.getJSONObject(selectedOptionIndex.intValue());
if (option.getBigDecimal("score") != null && option.getBigDecimal("score").compareTo(BigDecimal.ZERO) > 0) {
userCorrectCount++;
break;
}
}
}
@ -332,6 +332,7 @@ public class AnswerServiceImpl implements AnswerService {
/**
* 判断答案是否正确单选/多选题
* 使用数组索引匹配选项从0开始
*/
private Boolean isAnswerCorrect(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (question.getOptions() == null || question.getOptions().isEmpty()) {
@ -346,12 +347,12 @@ public class AnswerServiceImpl implements AnswerService {
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return false;
}
Long selectedOptionId = answerItem.getOptionIds().get(0);
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)) {
return option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect");
}
// 使用数组索引匹配选项
Long selectedOptionIndex = answerItem.getOptionIds().get(0);
if (selectedOptionIndex >= 0 && selectedOptionIndex < options.size()) {
JSONObject option = options.getJSONObject(selectedOptionIndex.intValue());
// score > 0 表示正确选项
return option.getBigDecimal("score") != null && option.getBigDecimal("score").compareTo(BigDecimal.ZERO) > 0;
}
return false;
} else if (question.getType() == QUESTION_TYPE_MULTI) {
@ -360,22 +361,23 @@ public class AnswerServiceImpl implements AnswerService {
return false;
}
Set<Long> correctOptionIds = new HashSet<>();
Set<Long> userSelectedIds = new HashSet<>(answerItem.getOptionIds());
// 收集正确选项的索引
Set<Integer> correctOptionIndices = new HashSet<>();
Set<Long> userSelectedIndices = new HashSet<>(answerItem.getOptionIds());
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
correctOptionIds.add(option.getLong("id"));
} else if (option.getBoolean("isCorrect") != null && !option.getBoolean("isCorrect")
&& userSelectedIds.contains(option.getLong("id"))) {
// 选择了错误选项
if (option.getBigDecimal("score") != null && option.getBigDecimal("score").compareTo(BigDecimal.ZERO) > 0) {
correctOptionIndices.add(i);
} else if (userSelectedIndices.contains((long) i)) {
// 选择了错误选项分数为0或负数的选项
return false;
}
}
// 用户必须选择所有正确选项
return correctOptionIds.equals(userSelectedIds);
Set<Long> correctIndexSet = correctOptionIndices.stream().map(Long::valueOf).collect(Collectors.toSet());
return correctIndexSet.equals(userSelectedIndices);
}
} catch (Exception e) {
return false;

View File

@ -7,8 +7,9 @@ import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import java.math.BigDecimal;
import java.util.*;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.vo.ImportRespVO;
@ -79,9 +80,16 @@ public class ConsumptionServiceImpl implements ConsumptionService {
// 校验订单存在
validateConsumptionExists(updateReqVO.getId());
// 1. 更新消费订单
// 1. 更新消费订单使用updateWrapper确保balance字段被正确更新
ConsumptionDO updateObj = ConsumptionConvert.INSTANCE.convert(updateReqVO);
consumptionMapper.updateById(updateObj);
if (updateReqVO.getBalance() != null) {
LambdaUpdateWrapper<ConsumptionDO> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(ConsumptionDO::getId, updateReqVO.getId())
.set(ConsumptionDO::getBalance, updateReqVO.getBalance());
consumptionMapper.update(updateObj, wrapper);
} else {
consumptionMapper.updateById(updateObj);
}
// 2. 删除原有明细
consumptionDetailMapper.deleteListByConsumptionId(updateReqVO.getId());

View File

@ -237,4 +237,12 @@ public interface EvaluationReportService {
*/
SseEmitter streamGenerateDimension(Long dimensionId, Long prisonerId, String customPrompt, String systemPrompt);
/**
* AI生成报告维度内容同步方式
* @param reportId 报告ID
* @param dimensionIds 维度ID列表可选为空则生成所有维度
* @return 生成结果
*/
void generateReportByAi(Long reportId, List<Long> dimensionIds);
}

View File

@ -48,6 +48,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
@ -63,6 +64,7 @@ import static cn.iocoder.yudao.module.prison.enums.EvaluationAiStatusEnum.PENDIN
*/
@Service
@Validated
@Slf4j
public class EvaluationReportServiceImpl implements EvaluationReportService {
/**
@ -924,4 +926,238 @@ public class EvaluationReportServiceImpl implements EvaluationReportService {
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void generateReportByAi(Long reportId, List<Long> dimensionIds) {
// 获取报告信息
EvaluationReportDO report = evaluationReportMapper.selectById(reportId);
if (report == null) {
throw exception(EVALUATION_REPORT_NOT_EXISTS);
}
// 获取报告关联的模板和维度
List<EvaluationDimensionDO> dimensions;
if (dimensionIds != null && !dimensionIds.isEmpty()) {
// 只生成指定维度
dimensions = dimensionIds.stream()
.map(dimensionMapper::selectById)
.filter(Objects::nonNull)
.collect(Collectors.toList());
} else {
// 获取模板下所有启用的维度
dimensions = dimensionMapper.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EvaluationDimensionDO>()
.eq(EvaluationDimensionDO::getTemplateId, report.getTemplateId())
.eq(EvaluationDimensionDO::getStatus, 1) // 启用状态
.orderByAsc(EvaluationDimensionDO::getSort));
}
// 获取罪犯信息
PrisonerDO prisoner = prisonerMapper.selectById(report.getPrisonerId());
if (prisoner == null) {
throw exception(PRISONER_NOT_EXISTS);
}
// 遍历每个维度生成内容
for (EvaluationDimensionDO dimension : dimensions) {
try {
// 获取维度数据源
DimensionDataSourcesRespDTO dataSources = getDimensionDataSources(dimension.getId(), report.getPrisonerId());
// 调用LLM生成内容
String analysis = generateDimensionAnalysisByLlm(dimension, dataSources);
// 保存或更新维度数据
EvaluationDimensionDataDO existingData = dimensionDataMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EvaluationDimensionDataDO>()
.eq(EvaluationDimensionDataDO::getReportId, reportId)
.eq(EvaluationDimensionDataDO::getDimensionId, dimension.getId()));
if (existingData != null) {
// 更新现有数据
existingData.setAiAnalysis(analysis);
dimensionDataMapper.updateById(existingData);
} else {
// 创建新数据
EvaluationDimensionDataDO newData = new EvaluationDimensionDataDO();
newData.setReportId(reportId);
newData.setDimensionId(dimension.getId());
newData.setDimensionName(dimension.getName());
newData.setAiAnalysis(analysis);
dimensionDataMapper.insert(newData);
}
} catch (Exception e) {
log.error("AI生成维度[{}]内容失败: {}", dimension.getName(), e.getMessage(), e);
// 继续处理其他维度不中断
}
}
// 更新报告的AI状态
evaluationReportMapper.updateById(new EvaluationReportDO() {{
setId(reportId);
setAiStatus(2); // 生成完成
}});
}
/**
* 调用LLM生成维度分析内容
*/
private String generateDimensionAnalysisByLlm(EvaluationDimensionDO dimension, DimensionDataSourcesRespDTO dataSources) {
try {
LlmClient client = llmClientFactory.getAssessmentClient();
LlmClient.LlmOptions options = LlmClient.LlmOptions.assessmentOptions();
options.setJsonMode(false);
options.setTemperature(0.3f);
options.setMaxTokens(2048);
// 构建提示词
String prompt = buildDimensionPrompt(dimension, dataSources);
options.setSystemPrompt(buildSystemPrompt(dimension));
// 调用LLM
String response = client.complete(prompt, options);
return cleanLlmResponse(response);
} catch (Exception e) {
log.error("调用LLM生成维度分析失败: {}", e.getMessage());
// 降级为规则生成
return generateDimensionAnalysisByRules(dimension, dataSources);
}
}
/**
* 构建维度提示词
*/
private String buildDimensionPrompt(EvaluationDimensionDO dimension, DimensionDataSourcesRespDTO dataSources) {
StringBuilder prompt = new StringBuilder();
prompt.append("请根据以下数据,对罪犯进行维度分析评估。\n\n");
// 添加罪犯基本信息
Map<String, Object> prisoner = dataSources.getPrisoner();
if (prisoner != null && !prisoner.isEmpty()) {
prompt.append("【罪犯基本信息】\n");
prompt.append(cn.hutool.json.JSONUtil.toJsonStr(prisoner)).append("\n\n");
}
// 添加消费数据
Map<String, Object> consumption = dataSources.getConsumptionSummary();
if (consumption != null && !consumption.isEmpty()) {
prompt.append("【消费情况】\n");
prompt.append(cn.hutool.json.JSONUtil.toJsonStr(consumption)).append("\n\n");
}
// 添加计分考核数据
Map<String, Object> score = dataSources.getScoreSummary();
if (score != null && !score.isEmpty()) {
prompt.append("【计分考核情况】\n");
prompt.append(cn.hutool.json.JSONUtil.toJsonStr(score)).append("\n\n");
}
// 添加风险评估数据
Map<String, Object> risk = dataSources.getRiskAssessment();
if (risk != null && !risk.isEmpty()) {
prompt.append("【风险评估情况】\n");
prompt.append(cn.hutool.json.JSONUtil.toJsonStr(risk)).append("\n\n");
}
// 添加自定义提示词
if (StrUtil.isNotBlank(dimension.getAiPrompt())) {
prompt.append("【特殊分析要求】\n");
prompt.append(dimension.getAiPrompt()).append("\n\n");
}
prompt.append("请基于以上数据,提供详细的分析评估,包括数据分析、特点总结和针对性建议。");
return prompt.toString();
}
/**
* 构建系统提示词
*/
private String buildSystemPrompt(EvaluationDimensionDO dimension) {
StringBuilder prompt = new StringBuilder();
prompt.append("你是一位专业的监狱心理评估专家,擅长根据罪犯的各项数据进行综合分析评估。\n");
prompt.append("你的分析应当客观、专业、具有建设性。\n");
prompt.append("请用流畅的中文段落形式输出分析结果不要使用Markdown格式或特殊标记。\n");
prompt.append("分析应包含:数据解读、特点总结、改造建议三部分。");
return prompt.toString();
}
/**
* 使用规则生成维度分析降级方案
*/
private String generateDimensionAnalysisByRules(EvaluationDimensionDO dimension, DimensionDataSourcesRespDTO dataSources) {
StringBuilder analysis = new StringBuilder();
// 分析计分考核
Map<String, Object> scoreSummary = dataSources.getScoreSummary();
if (scoreSummary != null) {
Object totalScore = scoreSummary.get("totalScore");
if (totalScore != null) {
double score = Double.parseDouble(totalScore.toString());
if (score >= 1500) {
analysis.append("该罪犯计分考核表现优秀,月均分数保持在较高水平。");
} else if (score >= 1000) {
analysis.append("该罪犯计分考核表现良好,能够完成各项改造任务。");
} else {
analysis.append("该罪犯计分考核表现一般,需要加强教育引导。");
}
}
}
// 分析消费情况
Map<String, Object> consumptionSummary = dataSources.getConsumptionSummary();
if (consumptionSummary != null) {
analysis.append("在消费方面,");
Object totalConsumption = consumptionSummary.get("totalAmount");
if (totalConsumption != null) {
double amount = Double.parseDouble(totalConsumption.toString());
analysis.append("月均消费约").append(String.format("%.2f", amount / 100)).append("元。");
}
}
// 风险评估
Map<String, Object> riskAssessment = dataSources.getRiskAssessment();
if (riskAssessment != null) {
Object riskLevel = riskAssessment.get("riskLevel");
if (riskLevel != null) {
int level = Integer.parseInt(riskLevel.toString());
switch (level) {
case 1:
analysis.append("该罪犯风险等级评估为低风险。");
break;
case 2:
analysis.append("该罪犯风险等级评估为中风险。");
break;
case 3:
case 4:
analysis.append("该罪犯风险等级评估为高风险,需重点关注。");
break;
}
}
}
analysis.append("\n\n建议继续保持良好的改造状态积极参与各项劳动和学习活动争取更好的改造成绩。");
return analysis.toString();
}
/**
* 清理LLM返回结果去除JSON包装和特殊标记
*/
private String cleanLlmResponse(String response) {
if (StrUtil.isBlank(response)) {
return "";
}
// 去除 Markdown 代码块标记
String cleaned = response.replaceAll("(?s)```json\\s*", "").replaceAll("(?s)```\\s*", "");
cleaned = cleaned.replaceAll("(?s)`{3,}\\s*json\\s*", "").replaceAll("(?s)`{3,}\\s*", "");
// 去除 JSON 对象的外层括号
cleaned = cleaned.replaceAll("^\\s*\\{[\\s\\S]*?\\}\\s*$", "").trim();
// 去除分析内容中的特殊前缀
cleaned = cleaned.replaceAll("(?m)^\\s*[【\\[].*?[】\\]]\\s*", "");
// 去除综合分析建议标题或前缀
cleaned = cleaned.replaceAll("(?m)^\\s*#+\\s*综合分析建议\\s*$", "");
cleaned = cleaned.replaceAll("(?m)^\\s*综合分析建议\\s*[:]\\s*", "");
// 去除多余空行
cleaned = cleaned.replaceAll("(?m)^\\s*$\\n", "");
return cleaned.trim();
}
}

View File

@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.prison.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.prison.controller.admin.prisoner.vo.PrisonerPageReqVO;
import cn.iocoder.yudao.module.prison.controller.admin.prisoner.vo.PrisonerRespVO;
import cn.iocoder.yudao.module.prison.controller.admin.prisoner.vo.PrisonerSaveReqVO;
import cn.iocoder.yudao.module.prison.controller.admin.prisoner.vo.TransferReqVO;
import cn.iocoder.yudao.module.prison.controller.admin.prisoner.vo.*;
import cn.iocoder.yudao.module.prison.convert.prisoner.PrisonerConvert;
import cn.iocoder.yudao.module.prison.dal.dataobject.PrisonerAreaLogDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.PrisonerDO;
@ -17,10 +14,13 @@ import cn.iocoder.yudao.module.prison.dal.mysql.area.AreaMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.cell.CellMapper;
import cn.iocoder.yudao.module.prison.enums.AreaTypeEnum;
import cn.iocoder.yudao.module.prison.enums.CellStatusEnum;
import cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.prison.enums.PrisonerStatusEnum;
import cn.iocoder.yudao.module.prison.enums.SupervisionLevelEnum;
import cn.iocoder.yudao.module.prison.service.PrisonerService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -38,6 +38,7 @@ import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
@Slf4j
public class PrisonerServiceImpl implements PrisonerService {
@Resource
@ -66,6 +67,10 @@ public class PrisonerServiceImpl implements PrisonerService {
// 插入
PrisonerDO prisoner = PrisonerConvert.INSTANCE.convert(reqVO);
// 确保状态不为空默认设置为在押状态
if (prisoner.getStatus() == null) {
prisoner.setStatus(PrisonerStatusEnum.IMPRISONED);
}
prisonerMapper.insert(prisoner);
return prisoner.getId();
}
@ -138,9 +143,20 @@ public class PrisonerServiceImpl implements PrisonerService {
@Override
public PageResult<PrisonerRespVO> getPrisonerPage(PrisonerPageReqVO reqVO) {
LambdaQueryWrapper<PrisonerDO> wrapper = new LambdaQueryWrapper<>();
wrapper.like(reqVO.getPrisonerNo() != null, PrisonerDO::getPrisonerNo, reqVO.getPrisonerNo())
.like(reqVO.getName() != null, PrisonerDO::getName, reqVO.getName())
.eq(reqVO.getGender() != null, PrisonerDO::getGender, reqVO.getGender())
// 使用 OR 关系编号或姓名任一匹配即可
if (reqVO.getPrisonerNo() != null && reqVO.getName() != null) {
wrapper.and(w -> w.like(PrisonerDO::getPrisonerNo, reqVO.getPrisonerNo())
.or()
.like(PrisonerDO::getName, reqVO.getName()));
} else if (reqVO.getPrisonerNo() != null) {
wrapper.like(PrisonerDO::getPrisonerNo, reqVO.getPrisonerNo());
} else if (reqVO.getName() != null) {
wrapper.like(PrisonerDO::getName, reqVO.getName());
}
// 其他筛选条件保持 AND 关系
wrapper.eq(reqVO.getGender() != null, PrisonerDO::getGender, reqVO.getGender())
.eq(reqVO.getIdCard() != null, PrisonerDO::getIdCard, reqVO.getIdCard())
.eq(reqVO.getCrime() != null, PrisonerDO::getCrime, reqVO.getCrime())
.eq(reqVO.getSupervisionLevel() != null, PrisonerDO::getSupervisionLevel, reqVO.getSupervisionLevel())

View File

@ -59,13 +59,20 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public Long createQuestionnaireTask(QuestionnaireTaskCreateReqVO createReqVO) {
log.info("开始创建问卷任务: taskName={}, questionnaireId={}, targetType={}",
createReqVO.getTaskName(), createReqVO.getQuestionnaireId(), createReqVO.getTargetType());
// 验证问卷是否存在
QuestionnaireDO questionnaire = questionnaireMapper.selectById(createReqVO.getQuestionnaireId());
if (questionnaire == null) {
log.warn("创建问卷任务失败,问卷不存在: questionnaireId={}", createReqVO.getQuestionnaireId());
throw new ServiceException(ErrorCodeConstants.QUESTIONNAIRE_NOT_EXISTS);
}
// 验证目标参数
validateTarget(createReqVO);
// 构建任务对象
QuestionnaireTaskDO task = QuestionnaireTaskDO.builder()
.taskName(createReqVO.getTaskName())
.questionnaireId(createReqVO.getQuestionnaireId())
@ -73,7 +80,7 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
.targetType(createReqVO.getTargetType())
.startTime(createReqVO.getStartTime())
.deadline(createReqVO.getDeadline())
.status(2)
.status(2) // 进行中
.totalCount(0)
.completedCount(0)
.pendingCount(0)
@ -81,6 +88,7 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
.remark(createReqVO.getRemark())
.build();
// 设置目标范围
if (createReqVO.getTargetType() == 1) {
task.setPrisonerIds(CollUtil.join(createReqVO.getPrisonerIds(), ","));
} else if (createReqVO.getTargetType() == 2) {
@ -91,14 +99,27 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
}
}
// 插入任务记录
questionnaireTaskMapper.insert(task);
log.info("问卷任务创建成功: taskId={}", task.getId());
// 获取目标罪犯列表并创建答题记录
List<PrisonerDO> prisoners = getTargetPrisoners(createReqVO);
if (CollUtil.isNotEmpty(prisoners)) {
log.info("为任务 {} 创建 {} 条答题记录", task.getId(), prisoners.size());
createRecordsForPrisoners(task, prisoners);
} else {
log.warn("任务 {} 没有符合条件的目标罪犯", task.getId());
}
updateTaskStatistics(task.getId());
// 更新任务统计信息
try {
updateTaskStatistics(task.getId());
log.info("任务 {} 统计信息更新成功", task.getId());
} catch (Exception e) {
log.error("任务 {} 统计信息更新失败: {}", task.getId(), e.getMessage(), e);
// 统计更新失败不应影响任务创建
}
return task.getId();
}
@ -249,6 +270,8 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
.eq(cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO::getStatus, QuestionnaireRecordStatusEnum.EXPIRED.getCode())
.set(cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO::getStatus, QuestionnaireRecordStatusEnum.PENDING.getCode())
.set(cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO::getDeadline, task.getDeadline()));
updateTaskStatistics(id);
}
@Override
@ -340,6 +363,8 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
return records.stream().map(record -> {
PrisonerProgressRespVO vo = new PrisonerProgressRespVO();
vo.setQuestionnaireId(record.getQuestionnaireId());
vo.setQuestionnaireName(record.getQuestionnaireName());
vo.setId(record.getId());
vo.setPrisonerId(record.getPrisonerId());
vo.setPrisonerNo(record.getPrisonerNo());
@ -503,12 +528,18 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService {
return prisonerMapper.selectList(
new LambdaQueryWrapper<PrisonerDO>()
.eq(PrisonerDO::getPrisonAreaId, createReqVO.getAreaId())
.eq(PrisonerDO::getStatus, 1)
.in(PrisonerDO::getStatus, java.util.Arrays.asList(
cn.iocoder.yudao.module.prison.enums.PrisonerStatusEnum.IMPRISONED,
cn.iocoder.yudao.module.prison.enums.PrisonerStatusEnum.PAROLED
))
);
case 3:
return prisonerMapper.selectList(
new LambdaQueryWrapper<PrisonerDO>()
.eq(PrisonerDO::getStatus, 1)
.in(PrisonerDO::getStatus, java.util.Arrays.asList(
cn.iocoder.yudao.module.prison.enums.PrisonerStatusEnum.IMPRISONED,
cn.iocoder.yudao.module.prison.enums.PrisonerStatusEnum.PAROLED
))
);
default:
return Collections.emptyList();

View File

@ -13,6 +13,7 @@ import java.util.*;
import cn.iocoder.yudao.module.prison.controller.admin.situation.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.vo.ImportRespVO;
import cn.iocoder.yudao.module.prison.dal.dataobject.situation.SituationDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@ -55,7 +56,14 @@ public class SituationServiceImpl implements SituationService {
validateSituationExists(updateReqVO.getId());
// 更新
SituationDO updateObj = BeanUtils.toBean(updateReqVO, SituationDO.class);
situationMapper.updateById(updateObj);
// 使用updateWrapper确保occurTime字段被正确更新
LambdaUpdateWrapper<SituationDO> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SituationDO::getId, updateReqVO.getId());
// 修复直接使用从请求VO中解析好的LocalDateTime值Spring已自动从ISO 8601格式解析
if (updateReqVO.getOccurTime() != null) {
wrapper.set(SituationDO::getOccurTime, updateReqVO.getOccurTime());
}
situationMapper.update(updateObj, wrapper);
}
@Override

View File

@ -58,7 +58,7 @@ public interface WarningService {
* @param pageReqVO 分页查询
* @return 预警信息分页
*/
PageResult<WarningDO> getWarningPage(WarningPageReqVO pageReqVO);
PageResult<WarningRespVO> getWarningPage(WarningPageReqVO pageReqVO);
/**
* 导入预警信息

View File

@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Collections;
import cn.iocoder.yudao.module.prison.controller.admin.warning.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.vo.ImportRespVO;
import cn.iocoder.yudao.module.prison.dal.dataobject.warning.WarningDO;
@ -79,8 +80,21 @@ public class WarningServiceImpl implements WarningService {
}
@Override
public PageResult<WarningDO> getWarningPage(WarningPageReqVO pageReqVO) {
return warningMapper.selectPage(pageReqVO);
public PageResult<WarningRespVO> getWarningPage(WarningPageReqVO pageReqVO) {
// 使用联表查询获取带 areaName 的数据
List<WarningRespVO> list = warningMapper.selectWarningPage(pageReqVO);
// 计算总记录数
long total = list.size();
// 分页这里简化处理实际项目可优化为 SQL 分页
int fromIndex = (int) ((pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize());
int toIndex = fromIndex + pageReqVO.getPageSize();
if (fromIndex >= total) {
return new PageResult<>(Collections.emptyList(), total);
}
List<WarningRespVO> pageList = fromIndex < total
? list.subList(fromIndex, Math.min(toIndex, (int) total))
: Collections.emptyList();
return new PageResult<>(pageList, total);
}
@Override

View File

@ -0,0 +1,20 @@
<?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.prison.dal.mysql.warning.WarningMapper">
<resultMap id="WarningResultMap" type="cn.iocoder.yudao.module.prison.controller.admin.warning.vo.WarningRespVO" autoMapping="true">
<id property="id" column="id"/>
<result property="areaId" column="area_id"/>
<result property="areaName" column="area_name"/>
<result property="cellId" column="cell_id"/>
<result property="situationId" column="situation_id"/>
<result property="alertTime" column="alert_time"/>
<result property="verifyTime" column="verify_time"/>
<result property="handleTime" column="handle_time"/>
<result property="releaseTime" column="release_time"/>
<result property="occurTime" column="occur_time"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
</mapper>

View File

@ -0,0 +1,70 @@
-- =====================================================
-- 问卷答题记录表字段迁移脚本
-- =====================================================
-- 日期2026-02-04
-- 说明:添加缺失的字段以匹配 QuestionnaireRecordDO
USE xlcp_dev;
-- 1. 添加问卷名称字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS questionnaire_name varchar(200) DEFAULT NULL COMMENT '问卷名称' AFTER questionnaire_id;
-- 2. 添加罪犯姓名字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS prisoner_name varchar(100) DEFAULT NULL COMMENT '罪犯姓名' AFTER prisoner_no;
-- 3. 删除旧字段(如果存在)
ALTER TABLE prison_questionnaire_record DROP COLUMN IF EXISTS answers;
ALTER TABLE prison_questionnaire_record DROP COLUMN IF EXISTS score;
ALTER TABLE prison_questionnaire_record DROP COLUMN IF EXISTS result;
-- 4. 添加任务关联字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS task_id bigint DEFAULT NULL COMMENT '所属任务ID' AFTER prisoner_name;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS prisoner_area_id bigint DEFAULT NULL COMMENT '犯人所属监区ID' AFTER task_id;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS prisoner_area_name varchar(50) DEFAULT NULL COMMENT '犯人所属监区名称' AFTER prisoner_area_id;
-- 5. 修改状态字段注释
ALTER TABLE prison_questionnaire_record MODIFY COLUMN status tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消';
-- 6. 添加时间相关字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS start_time datetime DEFAULT NULL COMMENT '开始时间' AFTER status;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS end_time datetime DEFAULT NULL COMMENT '结束时间' AFTER start_time;
-- answer_time 字段已修改为允许 NULL
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS deadline datetime DEFAULT NULL COMMENT '截止日期' AFTER answer_time;
-- 7. 添加评分相关字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS objective_score decimal(10,2) DEFAULT 0.00 COMMENT '客观题得分' AFTER deadline;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS subjective_score decimal(10,2) DEFAULT 0.00 COMMENT '主观题得分' AFTER objective_score;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS total_score decimal(10,2) DEFAULT 0.00 COMMENT '总分' AFTER subjective_score;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS pass_score decimal(10,2) DEFAULT NULL COMMENT '及格分数' AFTER total_score;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS pass_status tinyint DEFAULT NULL COMMENT '及格状态1-及格 2-不及格 3-待评阅' AFTER pass_score;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS risk_level tinyint DEFAULT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险' AFTER pass_status;
-- 8. 添加评阅相关字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS evaluator_id bigint DEFAULT NULL COMMENT '评阅人ID' AFTER risk_level;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS evaluator_name varchar(100) DEFAULT NULL COMMENT '评阅人姓名' AFTER evaluator_id;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS evaluate_time datetime DEFAULT NULL COMMENT '评阅时间' AFTER evaluator_name;
-- 9. 添加统计相关字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS participant_count int DEFAULT 0 COMMENT '参与人数' AFTER evaluate_time;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS completed_count int DEFAULT 0 COMMENT '完成人数' AFTER participant_count;
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS duration int DEFAULT NULL COMMENT '答题用时(秒)' AFTER completed_count;
-- 10. 添加备注字段
ALTER TABLE prison_questionnaire_record ADD COLUMN IF NOT EXISTS remark varchar(500) DEFAULT NULL COMMENT '备注' AFTER duration;
-- 11. 添加索引
ALTER TABLE prison_questionnaire_record ADD INDEX IF NOT EXISTS idx_task_id (task_id);
ALTER TABLE prison_questionnaire_record ADD INDEX IF NOT EXISTS idx_prisoner_area_id (prisoner_area_id);
-- 验证修改
SELECT
COLUMN_NAME as '字段名',
DATA_TYPE as '数据类型',
IS_NULLABLE as '允许NULL',
COLUMN_DEFAULT as '默认值',
COLUMN_COMMENT as '注释'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'xlcp_dev'
AND TABLE_NAME = 'prison_questionnaire_record'
ORDER BY ORDINAL_POSITION;

View File

@ -154,7 +154,7 @@ CREATE TABLE IF NOT EXISTS `prison_questionnaire_record` (
`score` int DEFAULT 0 COMMENT '得分',
`result` text COMMENT '评估结果',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待评估 2-已完成',
`answer_time` datetime NOT NULL COMMENT '答题时间',
`answer_time` datetime DEFAULT NULL COMMENT '答题时间',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
@ -169,25 +169,30 @@ CREATE TABLE IF NOT EXISTS `prison_questionnaire_record` (
-- =====================================================
-- 7. 危险评估表 (prison_risk_assessment)
-- =====================================================
-- 更新说明2026-01-30 添加精细化评估字段
CREATE TABLE IF NOT EXISTS `prison_risk_assessment` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '评估ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`assessment_type` tinyint NOT NULL DEFAULT 1 COMMENT '评估类型1-入监评估 2-定期评估 3-出监评估',
`risk_level` tinyint NOT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险',
`risk_score` int DEFAULT 0 COMMENT '风险评分',
`factors` text COMMENT '风险因素JSON格式',
`measures` text COMMENT '管控措施',
`assessment_type` tinyint NOT NULL DEFAULT 1 COMMENT '评估类型1-入监评估 2-定期评估 3-专项评估',
`assessment_date` date NOT NULL COMMENT '评估日期',
`violence_score` decimal(10,2) DEFAULT NULL COMMENT '暴力倾向得分',
`escape_score` decimal(10,2) DEFAULT NULL COMMENT '脱逃倾向得分',
`suicide_score` decimal(10,2) DEFAULT NULL COMMENT '自杀倾向得分',
`total_score` decimal(10,2) DEFAULT NULL COMMENT '综合得分',
`risk_level` tinyint NOT NULL COMMENT '风险等级1-低风险 2-中风险 3-高风险 4-极高风险',
`risk_factors` text COMMENT '风险因素',
`suggestions` text COMMENT '管控建议',
`assessor_id` bigint DEFAULT NULL COMMENT '评估人ID',
`assessor_name` varchar(50) DEFAULT NULL COMMENT '评估人姓名',
`assessment_date` date NOT NULL COMMENT '评估日期',
`next_date` date DEFAULT NULL COMMENT '下次评估日期',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-有效 2-已过期',
`next_assessment_date` date DEFAULT NULL COMMENT '下次评估日期',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待审核 2-已通过',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_risk_assessment_prisoner_id` (`prisoner_id`),

View File

@ -0,0 +1,609 @@
-- =====================================================
-- XL监狱综合管理平台 - 监狱模块数据库脚本
-- 生成时间: 2026-01-12
-- =====================================================
-- 注意: 执行前请确保已经创建了 prison 模块的基础表 prison_prisoner
-- 此脚本包含 8 个子模块的表结构和菜单权限
-- =====================================================
-- 1. 监区信息表 (prison_area)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_area` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '监区ID',
`name` varchar(100) NOT NULL COMMENT '监区名称',
`code` varchar(50) NOT NULL COMMENT '监区编码',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '监区类型1-普通监区 2-严管监区 3-医院 4-禁闭室',
`capacity` int NOT NULL DEFAULT 0 COMMENT '容纳人数',
`current_count` int NOT NULL DEFAULT 0 COMMENT '当前人数',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-启用 2-禁用',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_prison_area_code` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监区信息表';
-- =====================================================
-- 2. 监室信息表 (prison_cell)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_cell` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '监室ID',
`area_id` bigint NOT NULL COMMENT '所属监区ID',
`name` varchar(100) NOT NULL COMMENT '监室名称',
`code` varchar(50) NOT NULL COMMENT '监室编码',
`capacity` int NOT NULL DEFAULT 0 COMMENT '床位数量',
`current_count` int NOT NULL DEFAULT 0 COMMENT '当前人数',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-启用 2-禁用',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_prison_cell_area_id` (`area_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监室信息表';
-- =====================================================
-- 3. 消费订单表 (prison_consumption)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_consumption` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '消费ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`order_no` varchar(64) DEFAULT NULL COMMENT '订单号',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他',
`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
`balance` decimal(10,2) NOT NULL COMMENT '账户余额(消费后)',
`trade_time` datetime NOT NULL COMMENT '交易时间',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-成功 2-失败',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_consumption_prisoner_id` (`prisoner_id`),
KEY `idx_prison_consumption_prisoner_no` (`prisoner_no`),
KEY `idx_prison_consumption_order_no` (`order_no`),
KEY `idx_prison_consumption_trade_time` (`trade_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费订单表';
-- =====================================================
-- 3.1 消费明细表 (prison_consumption_detail)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_consumption_detail` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '明细ID',
`consumption_id` bigint NOT NULL COMMENT '消费订单ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID冗余便于查询',
`goods_name` varchar(100) NOT NULL COMMENT '商品名称',
`goods_code` varchar(50) DEFAULT NULL COMMENT '商品编码',
`goods_price` decimal(10,2) NOT NULL COMMENT '商品单价',
`goods_count` int NOT NULL COMMENT '商品数量',
`subtotal` decimal(10,2) NOT NULL COMMENT '小计金额',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_consumption_detail_consumption_id` (`consumption_id`),
KEY `idx_consumption_detail_prisoner_id` (`prisoner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费明细表';
-- =====================================================
-- 4. 问卷模板表 (prison_questionnaire)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_questionnaire` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '问卷ID',
`title` varchar(200) NOT NULL COMMENT '问卷标题',
`description` varchar(500) DEFAULT NULL COMMENT '问卷描述',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '问卷类型1-心理测评 2-风险评估 3-日常调查',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-草稿 2-已发布 3-已停用',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_questionnaire_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷模板表';
-- =====================================================
-- 5. 问卷问题表 (prison_question)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_question` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '问题ID',
`questionnaire_id` bigint NOT NULL COMMENT '问卷ID',
`title` varchar(500) NOT NULL COMMENT '问题标题',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '问题类型1-单选 2-多选 3-问答 4-评分',
`options` text COMMENT '选项JSON格式[{ "label": "选项1", "value": "1" }]',
`score` int DEFAULT 0 COMMENT '分值',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_question_questionnaire_id` (`questionnaire_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷问题表';
-- =====================================================
-- 6. 问卷答题记录表 (prison_questionnaire_record)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_questionnaire_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`questionnaire_id` bigint NOT NULL COMMENT '问卷ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`answers` text NOT NULL COMMENT '答案JSON格式',
`score` int DEFAULT 0 COMMENT '得分',
`result` text COMMENT '评估结果',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待评估 2-已完成',
`answer_time` datetime NOT NULL COMMENT '答题时间',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_questionnaire_record_questionnaire_id` (`questionnaire_id`),
KEY `idx_prison_questionnaire_record_prisoner_id` (`prisoner_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷答题记录表';
-- =====================================================
-- 7. 危险评估表 (prison_risk_assessment)
-- =====================================================
-- 更新说明2026-01-30 添加精细化评估字段
CREATE TABLE IF NOT EXISTS `prison_risk_assessment` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '评估ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`assessment_type` tinyint NOT NULL DEFAULT 1 COMMENT '评估类型1-入监评估 2-定期评估 3-专项评估',
`assessment_date` date NOT NULL COMMENT '评估日期',
`violence_score` decimal(10,2) DEFAULT NULL COMMENT '暴力倾向得分',
`escape_score` decimal(10,2) DEFAULT NULL COMMENT '脱逃倾向得分',
`suicide_score` decimal(10,2) DEFAULT NULL COMMENT '自杀倾向得分',
`total_score` decimal(10,2) DEFAULT NULL COMMENT '综合得分',
`risk_level` tinyint NOT NULL COMMENT '风险等级1-低风险 2-中风险 3-高风险 4-极高风险',
`risk_factors` text COMMENT '风险因素',
`suggestions` text COMMENT '管控建议',
`assessor_id` bigint DEFAULT NULL COMMENT '评估人ID',
`assessor_name` varchar(50) DEFAULT NULL COMMENT '评估人姓名',
`next_assessment_date` date DEFAULT NULL COMMENT '下次评估日期',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待审核 2-已通过',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_risk_assessment_prisoner_id` (`prisoner_id`),
KEY `idx_prison_risk_assessment_assessment_date` (`assessment_date`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='危险评估表';
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='危险评估表';
-- =====================================================
-- 8. 计分考核表 (prison_score)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_score` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`year` int NOT NULL COMMENT '考核年份',
`month` int NOT NULL COMMENT '考核月份',
`base_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '基础分',
`reward_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '加分',
`penalty_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '扣分',
`total_score` decimal(10,2) NOT NULL COMMENT '总分',
`level` tinyint DEFAULT NULL COMMENT '考核等级1-优秀 2-良好 3-合格 4-不合格',
`assessor_id` bigint DEFAULT NULL COMMENT '考核人ID',
`assessor_name` varchar(50) DEFAULT NULL COMMENT '考核人姓名',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待审核 2-已通过 3-已驳回',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`prison_area_id` bigint DEFAULT NULL COMMENT '监区ID',
`prison_cell_id` bigint DEFAULT NULL COMMENT '监室ID',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0 COMMENT '',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_prisoner_year_month` (`prisoner_no`, `year`, `month`),
KEY `idx_prison_score_prisoner_id` (`prisoner_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='计分考核表';
-- =====================================================
-- 菜单权限 SQL
-- 注意: 请将 ${table.parentMenuId} 替换为实际的父菜单ID
-- 监狱管理模块的父菜单ID通常为 5047 (系统管理) 或对应的监狱模块菜单ID
-- =====================================================
-- 获取监狱模块父菜单ID (假设为 5047请根据实际情况调整)
-- SELECT @parentId := 5047;
-- 1. 监区信息管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('监区信息管理', '', 2, 1, 5047, 'area', '', 'prison/area/index', 0, 'Area');
SELECT @areaParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监区信息查询', 'prison:area:query', 3, 1, @areaParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监区信息创建', 'prison:area:create', 3, 2, @areaParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监区信息更新', 'prison:area:update', 3, 3, @areaParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监区信息删除', 'prison:area:delete', 3, 4, @areaParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监区信息导出', 'prison:area:export', 3, 5, @areaParentId, '', '', '', 0);
-- 2. 监室信息管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('监室信息管理', '', 2, 2, 5047, 'cell', '', 'prison/cell/index', 0, 'Cell');
SELECT @cellParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监室信息查询', 'prison:cell:query', 3, 1, @cellParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监室信息创建', 'prison:cell:create', 3, 2, @cellParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监室信息更新', 'prison:cell:update', 3, 3, @cellParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监室信息删除', 'prison:cell:delete', 3, 4, @cellParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('监室信息导出', 'prison:cell:export', 3, 5, @cellParentId, '', '', '', 0);
-- 3. 消费记录管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('消费记录管理', '', 2, 3, 5047, 'consumption', '', 'prison/consumption/index', 0, 'Consumption');
SELECT @consumptionParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('消费记录查询', 'prison:consumption:query', 3, 1, @consumptionParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('消费记录创建', 'prison:consumption:create', 3, 2, @consumptionParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('消费记录更新', 'prison:consumption:update', 3, 3, @consumptionParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('消费记录删除', 'prison:consumption:delete', 3, 4, @consumptionParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('消费记录导出', 'prison:consumption:export', 3, 5, @consumptionParentId, '', '', '', 0);
-- 4. 问卷模板管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('问卷模板管理', '', 2, 4, 5047, 'questionnaire', '', 'prison/questionnaire/index', 0, 'Questionnaire');
SELECT @questionnaireParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷模板查询', 'prison:questionnaire:query', 3, 1, @questionnaireParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷模板创建', 'prison:questionnaire:create', 3, 2, @questionnaireParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷模板更新', 'prison:questionnaire:update', 3, 3, @questionnaireParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷模板删除', 'prison:questionnaire:delete', 3, 4, @questionnaireParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷模板导出', 'prison:questionnaire:export', 3, 5, @questionnaireParentId, '', '', '', 0);
-- 5. 问卷问题管理菜单 (已移除独立页面,问题管理集成在问卷模板页面内)
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
-- VALUES ('问卷问题管理', '', 2, 5, 5047, 'question', '', 'prison/question/index', 0, 'Question');
-- SELECT @questionParentId := LAST_INSERT_ID();
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
-- VALUES ('问卷问题查询', 'prison:question:query', 3, 1, @questionParentId, '', '', '', 0);
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
-- VALUES ('问卷问题创建', 'prison:question:create', 3, 2, @questionParentId, '', '', '', 0);
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
-- VALUES ('问卷问题更新', 'prison:question:update', 3, 3, @questionParentId, '', '', '', 0);
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
-- VALUES ('问卷问题删除', 'prison:question:delete', 3, 4, @questionParentId, '', '', '', 0);
-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
-- VALUES ('问卷问题导出', 'prison:question:export', 3, 5, @questionParentId, '', '', '', 0);
-- 5. 问卷答题记录管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('问卷答题记录管理', '', 2, 5, 5047, 'questionnaire-record', '', 'prison/questionnairerecord/index', 0, 'QuestionnaireRecord');
SELECT @recordParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷答题记录查询', 'prison:questionnaire-record:query', 3, 1, @recordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷答题记录创建', 'prison:questionnaire-record:create', 3, 2, @recordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷答题记录更新', 'prison:questionnaire-record:update', 3, 3, @recordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷答题记录删除', 'prison:questionnaire-record:delete', 3, 4, @recordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('问卷答题记录导出', 'prison:questionnaire-record:export', 3, 5, @recordParentId, '', '', '', 0);
-- 6. 危险评估管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('危险评估管理', '', 2, 6, 5047, 'risk-assessment', '', 'prison/riskassessment/index', 0, 'RiskAssessment');
SELECT @riskParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('危险评估查询', 'prison:risk-assessment:query', 3, 1, @riskParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('危险评估创建', 'prison:risk-assessment:create', 3, 2, @riskParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('危险评估更新', 'prison:risk-assessment:update', 3, 3, @riskParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('危险评估删除', 'prison:risk-assessment:delete', 3, 4, @riskParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('危险评估导出', 'prison:risk-assessment:export', 3, 5, @riskParentId, '', '', '', 0);
-- 8. 计分考核管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('计分考核管理', '', 2, 8, 5047, 'score', '', 'prison/score/index', 0, 'Score');
SELECT @scoreParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('计分考核查询', 'prison:score:query', 3, 1, @scoreParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('计分考核创建', 'prison:score:create', 3, 2, @scoreParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('计分考核更新', 'prison:score:update', 3, 3, @scoreParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('计分考核删除', 'prison:score:delete', 3, 4, @scoreParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('计分考核导出', 'prison:score:export', 3, 5, @scoreParentId, '', '', '', 0);
-- 9. 狱情收集管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('狱情收集管理', '', 2, 9, 5047, 'situation', '', 'prison/situation/index', 0, 'Situation');
SELECT @situationParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('狱情收集查询', 'prison:situation:query', 3, 1, @situationParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('狱情收集创建', 'prison:situation:create', 3, 2, @situationParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('狱情收集更新', 'prison:situation:update', 3, 3, @situationParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('狱情收集删除', 'prison:situation:delete', 3, 4, @situationParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('狱情收集导出', 'prison:situation:export', 3, 5, @situationParentId, '', '', '', 0);
-- =====================================================
-- 10. 数据结构迁移 SQL (2026-01-20)
-- =====================================================
-- 补充 prison_prisoner 表缺失字段
-- 执行此迁移前请备份数据库
ALTER TABLE `prison_prisoner`
ADD COLUMN IF NOT EXISTS `release_type` tinyint DEFAULT 0 COMMENT '释放类型0-未知 1-刑满释放 2-假释 3-保外就医 4-减刑 5-暂予监外执行 6-特赦 7-死亡 8-其他',
ADD COLUMN IF NOT EXISTS `release_reason` varchar(500) DEFAULT NULL COMMENT '释放原因',
ADD COLUMN IF NOT EXISTS `photo` varchar(512) DEFAULT NULL COMMENT '照片URL',
ADD COLUMN IF NOT EXISTS `sub_area_id` bigint DEFAULT NULL COMMENT '分区ID',
ADD COLUMN IF NOT EXISTS `marital_status` tinyint DEFAULT NULL COMMENT '婚姻状态1-未婚 2-已婚 3-离异 4-丧偶',
ADD COLUMN IF NOT EXISTS `crime_type` varchar(100) DEFAULT NULL COMMENT '罪名类型',
ADD COLUMN IF NOT EXISTS `sentence` varchar(100) DEFAULT NULL COMMENT '刑期';
-- =====================================================
-- 10. 字典数据 SQL
-- =====================================================
-- 监室状态字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('监室状态', 'prison_cell_status', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_cell_status', 1, '启用', '1', 'success', '', 0, 'admin', NOW()),
('prison_cell_status', 2, '禁用', '2', 'danger', '', 0, 'admin', NOW());
-- =====================================================
-- 11. 狱情收集表 (prison_situation) - 新增 2026-01-16
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_situation` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '狱情ID',
`title` varchar(200) NOT NULL COMMENT '标题',
`content` text COMMENT '详情内容',
`category` tinyint NOT NULL DEFAULT 1 COMMENT '分类1-监管安全 2-教育改造 3-生活卫生 4-生产安全 5-狱内案件 6-其他',
`level` tinyint NOT NULL DEFAULT 1 COMMENT '等级1-一般 2-重要 3-紧急',
`source` tinyint NOT NULL DEFAULT 1 COMMENT '来源1-民警报告 2-监控系统 3-举报 4-罪犯自首 5-其他',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待处理 2-处理中 3-已处理',
`area_id` bigint DEFAULT NULL COMMENT '关联监区ID',
`cell_id` bigint DEFAULT NULL COMMENT '关联监室ID',
`reporter` varchar(50) DEFAULT NULL COMMENT '报告人',
`handler` varchar(50) DEFAULT NULL COMMENT '处理人',
`handle_time` datetime DEFAULT NULL COMMENT '处理时间',
`handle_result` text COMMENT '处理结果',
`occur_time` datetime DEFAULT NULL COMMENT '发生时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_situation_status` (`status`),
KEY `idx_prison_situation_category` (`category`),
KEY `idx_prison_situation_level` (`level`),
KEY `idx_prison_situation_area_id` (`area_id`),
KEY `idx_prison_situation_cell_id` (`cell_id`),
KEY `idx_prison_situation_occur_time` (`occur_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='狱情收集表';
-- =====================================================
-- 12. 预警管理表 (prison_warning) - 新增 2026-01-16
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_warning` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '预警ID',
`title` varchar(200) NOT NULL COMMENT '预警标题',
`content` text COMMENT '预警内容',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '预警类型1-安全预警 2-监管预警 3-改造预警 4-生产预警 5-生活卫生预警 6-其他',
`level` tinyint NOT NULL DEFAULT 1 COMMENT '预警等级1-一般 2-重要 3-紧急 4-严重',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '预警状态1-待核实 2-已核实 3-已处置 4-已解除',
`source` tinyint NOT NULL DEFAULT 1 COMMENT '预警来源1-民警报告 2-监控系统 3-举报 4-罪犯自首 5-智能分析 6-其他',
`situation_id` bigint DEFAULT NULL COMMENT '关联狱情ID',
`area_id` bigint DEFAULT NULL COMMENT '关联监区ID',
`cell_id` bigint DEFAULT NULL COMMENT '关联监室ID',
`alert_time` datetime DEFAULT NULL COMMENT '预警时间',
`verify_time` datetime DEFAULT NULL COMMENT '核实时间',
`verifier` varchar(50) DEFAULT NULL COMMENT '核实人',
`verify_result` text COMMENT '核实结果',
`handle_time` datetime DEFAULT NULL COMMENT '处置时间',
`handler` varchar(50) DEFAULT NULL COMMENT '处置人',
`handle_method` varchar(200) DEFAULT NULL COMMENT '处置方式',
`handle_result` text COMMENT '处置结果',
`release_time` datetime DEFAULT NULL COMMENT '解除时间',
`releaser` varchar(50) DEFAULT NULL COMMENT '解除人',
`release_reason` text COMMENT '解除原因',
`occur_time` datetime DEFAULT NULL COMMENT '发生时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_warning_status` (`status`),
KEY `idx_prison_warning_level` (`level`),
KEY `idx_prison_warning_type` (`type`),
KEY `idx_prison_warning_situation_id` (`situation_id`),
KEY `idx_prison_warning_area_id` (`area_id`),
KEY `idx_prison_warning_cell_id` (`cell_id`),
KEY `idx_prison_warning_alert_time` (`alert_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='预警管理表';
-- =====================================================
-- 13. 风险评估表 (prison_risk) - 新增 2026-01-16
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_risk` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '评估ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`prisoner_name` varchar(50) DEFAULT NULL COMMENT '罪犯姓名',
`assessment_type` tinyint NOT NULL DEFAULT 1 COMMENT '评估类型1-入监评估 2-定期评估 3-专项评估 4-出监评估',
`assessment_date` date NOT NULL COMMENT '评估日期',
`overall_score` decimal(5,2) DEFAULT NULL COMMENT '综合风险得分',
`risk_level` tinyint DEFAULT NULL COMMENT '风险等级1-低风险 2-中风险 3-高风险 4-极高风险',
`mental_state` varchar(500) DEFAULT NULL COMMENT '精神状态评估',
`escape_risk` varchar(500) DEFAULT NULL COMMENT '脱逃风险评估',
`violence_risk` varchar(500) DEFAULT NULL COMMENT '暴力倾向评估',
`revolt_risk` varchar(500) DEFAULT NULL COMMENT '抗改风险评估',
`self_harm_risk` varchar(500) DEFAULT NULL COMMENT '自杀自伤风险评估',
`recommendation` text COMMENT '评估建议',
`assessor` varchar(50) DEFAULT NULL COMMENT '评估人',
`assess_method` tinyint DEFAULT NULL COMMENT '评估方式1-问卷评估 2-量表评估 3-综合评估',
`item_scores` text COMMENT '评估项目得分JSON',
`conclusion` text COMMENT '评估结论',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_prison_risk_prisoner_id` (`prisoner_id`),
KEY `idx_prison_risk_prisoner_no` (`prisoner_no`),
KEY `idx_prison_risk_assessment_type` (`assessment_type`),
KEY `idx_prison_risk_assessment_date` (`assessment_date`),
KEY `idx_prison_risk_risk_level` (`risk_level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='风险评估表';
-- =====================================================
-- 14. 字典数据 SQL (2026-01-16)
-- =====================================================
-- 狱情分类字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('狱情分类', 'prison_situation_category', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_situation_category', 1, '监管安全', '1', 'danger', '', 0, 'admin', NOW()),
('prison_situation_category', 2, '教育改造', '2', 'warning', '', 0, 'admin', NOW()),
('prison_situation_category', 3, '生活卫生', '3', 'success', '', 0, 'admin', NOW()),
('prison_situation_category', 4, '生产安全', '4', 'info', '', 0, 'admin', NOW()),
('prison_situation_category', 5, '狱内案件', '5', 'danger', '', 0, 'admin', NOW()),
('prison_situation_category', 6, '其他', '6', '', '', 0, 'admin', NOW());
-- 狱情等级字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('狱情等级', 'prison_situation_level', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_situation_level', 1, '一般', '1', 'success', '', 0, 'admin', NOW()),
('prison_situation_level', 2, '重要', '2', 'warning', '', 0, 'admin', NOW()),
('prison_situation_level', 3, '紧急', '3', 'danger', '', 0, 'admin', NOW());
-- 狱情来源字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('狱情来源', 'prison_situation_source', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_situation_source', 1, '民警报告', '1', '', '', 0, 'admin', NOW()),
('prison_situation_source', 2, '监控系统', '2', '', '', 0, 'admin', NOW()),
('prison_situation_source', 3, '举报', '3', '', '', 0, 'admin', NOW()),
('prison_situation_source', 4, '罪犯自首', '4', '', '', 0, 'admin', NOW()),
('prison_situation_source', 5, '其他', '5', '', '', 0, 'admin', NOW());
-- 狱情状态字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('狱情状态', 'prison_situation_status', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_situation_status', 1, '待处理', '1', 'warning', '', 0, 'admin', NOW()),
('prison_situation_status', 2, '处理中', '2', 'info', '', 0, 'admin', NOW()),
('prison_situation_status', 3, '已处理', '3', 'success', '', 0, 'admin', NOW());
-- 预警类型字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('预警类型', 'prison_warning_type', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_warning_type', 1, '安全预警', '1', 'danger', '', 0, 'admin', NOW()),
('prison_warning_type', 2, '监管预警', '2', 'warning', '', 0, 'admin', NOW()),
('prison_warning_type', 3, '改造预警', '3', 'info', '', 0, 'admin', NOW()),
('prison_warning_type', 4, '生产预警', '4', '', '', 0, 'admin', NOW()),
('prison_warning_type', 5, '生活卫生预警', '5', 'success', '', 0, 'admin', NOW()),
('prison_warning_type', 6, '其他', '6', '', '', 0, 'admin', NOW());
-- 预警等级字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('预警等级', 'prison_warning_level', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_warning_level', 1, '一般', '1', 'success', '', 0, 'admin', NOW()),
('prison_warning_level', 2, '重要', '2', 'warning', '', 0, 'admin', NOW()),
('prison_warning_level', 3, '紧急', '3', 'danger', '', 0, 'admin', NOW()),
('prison_warning_level', 4, '严重', '4', 'danger', '', 0, 'admin', NOW());
-- 预警状态字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('预警状态', 'prison_warning_status', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_warning_status', 1, '待核实', '1', 'warning', '', 0, 'admin', NOW()),
('prison_warning_status', 2, '已核实', '2', 'info', '', 0, 'admin', NOW()),
('prison_warning_status', 3, '已处置', '3', 'success', '', 0, 'admin', NOW()),
('prison_warning_status', 4, '已解除', '4', '', '', 0, 'admin', NOW());
-- 预警来源字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('预警来源', 'prison_warning_source', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_warning_source', 1, '民警报告', '1', '', '', 0, 'admin', NOW()),
('prison_warning_source', 2, '监控系统', '2', '', '', 0, 'admin', NOW()),
('prison_warning_source', 3, '举报', '3', '', '', 0, 'admin', NOW()),
('prison_warning_source', 4, '罪犯自首', '4', '', '', 0, 'admin', NOW()),
('prison_warning_source', 5, '智能分析', '5', '', '', 0, 'admin', NOW()),
('prison_warning_source', 6, '其他', '6', '', '', 0, 'admin', NOW());
-- 风险评估类型字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('风险评估类型', 'prison_risk_assessment_type', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_risk_assessment_type', 1, '入监评估', '1', 'info', '', 0, 'admin', NOW()),
('prison_risk_assessment_type', 2, '定期评估', '2', 'success', '', 0, 'admin', NOW()),
('prison_risk_assessment_type', 3, '专项评估', '3', 'warning', '', 0, 'admin', NOW()),
('prison_risk_assessment_type', 4, '出监评估', '4', '', '', 0, 'admin', NOW());
-- 风险等级字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('风险等级', 'prison_risk_level', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_risk_level', 1, '低风险', '1', 'success', '', 0, 'admin', NOW()),
('prison_risk_level', 2, '中风险', '2', 'warning', '', 0, 'admin', NOW()),
('prison_risk_level', 3, '高风险', '3', 'danger', '', 0, 'admin', NOW()),
('prison_risk_level', 4, '极高风险', '4', 'danger', '', 0, 'admin', NOW());
-- 评估方式字典
INSERT INTO system_dict_type (name, type, status, creator, create_time) VALUES ('评估方式', 'prison_risk_assess_method', 0, 'admin', NOW());
INSERT INTO system_dict_data (dict_type, sort, label, value, color_type, css_class, status, creator, create_time) VALUES
('prison_risk_assess_method', 1, '问卷评估', '1', '', '', 0, 'admin', NOW()),
('prison_risk_assess_method', 2, '量表评估', '2', '', '', 0, 'admin', NOW()),
('prison_risk_assess_method', 3, '综合评估', '3', '', '', 0, 'admin', NOW());

View File

@ -0,0 +1,56 @@
-- =====================================================
-- XL监狱综合管理平台 - 危险评估表结构升级
-- 生成时间: 2026-01-30
-- 升级说明: 添加危险评估详细字段以支持精细化评估
-- =====================================================
-- 危险评估表 (prison_risk_assessment) 添加新字段
-- 如果字段已存在会忽略错误
-- =====================================================
-- 1. 添加暴力倾向得分字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `violence_score` decimal(10,2) DEFAULT NULL COMMENT '暴力倾向得分' AFTER `suicide_score`;
-- 2. 添加脱逃倾向得分字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `escape_score` decimal(10,2) DEFAULT NULL COMMENT '脱逃倾向得分' AFTER `violence_score`;
-- 3. 添加自杀倾向得分字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `suicide_score` decimal(10,2) DEFAULT NULL COMMENT '自杀倾向得分' AFTER `escape_score`;
-- 4. 添加综合得分字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `total_score` decimal(10,2) DEFAULT NULL COMMENT '综合得分' AFTER `suicide_score`;
-- 5. 添加风险因素字段(改为支持更详细的风险因素描述)
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `risk_factors` text COMMENT '风险因素' AFTER `total_score`;
-- 6. 添加管控建议字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `suggestions` text COMMENT '管控建议' AFTER `risk_factors`;
-- 7. 添加备注字段
ALTER TABLE `prison_risk_assessment`
ADD COLUMN IF NOT EXISTS `remark` varchar(500) DEFAULT NULL COMMENT '备注' AFTER `suggestions`;
-- =====================================================
-- 注意对于MySQL 5.7,需要移除 IF NOT EXISTS 条件手动执行
-- 或者使用以下兼容性写法(忽略错误):
-- =====================================================
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `violence_score` decimal(10,2) DEFAULT NULL COMMENT '暴力倾向得分';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `escape_score` decimal(10,2) DEFAULT NULL COMMENT '脱逃倾向得分';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `suicide_score` decimal(10,2) DEFAULT NULL COMMENT '自杀倾向得分';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `total_score` decimal(10,2) DEFAULT NULL COMMENT '综合得分';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `risk_factors` text COMMENT '风险因素';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `suggestions` text COMMENT '管控建议';
-- ALTER TABLE `prison_risk_assessment` ADD COLUMN `remark` varchar(500) DEFAULT NULL COMMENT '备注';
-- =====================================================
-- 执行方式:
-- 1. MySQL 8.0+: 直接执行
-- 2. MySQL 5.7: 使用注释中的兼容性写法
-- =====================================================

View File

@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.prison.service.questionnaire_task;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.prison.dal.dataobject.questionnaire_task.QuestionnaireTaskDO;
import cn.iocoder.yudao.module.prison.dal.mysql.questionnaire_task.QuestionnaireTaskMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.questionnaire_record.QuestionnaireRecordMapper;
import cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* Bug #7448 修复验证 - 问卷任务启用后目标人数被清空
*/
@ExtendWith(MockitoExtension.class)
class QuestionnaireTaskServiceImplRestartTest {
@InjectMocks
private QuestionnaireTaskServiceImpl questionnaireTaskService;
@Mock
private QuestionnaireTaskMapper questionnaireTaskMapper;
@Mock
private QuestionnaireRecordMapper questionnaireRecordMapper;
@Mock
private QuestionnaireMapper questionnaireMapper;
@Mock
private PrisonerMapper prisonerMapper;
@Mock
private AreaMapper areaMapper;
private QuestionnaireTaskDO createTestTask(Integer status, Integer totalCount, Integer completedCount) {
QuestionnaireTaskDO task = new QuestionnaireTaskDO();
task.setId(1L);
task.setTaskName("测试任务");
task.setQuestionnaireId(100L);
task.setStatus(status);
task.setTotalCount(totalCount);
task.setCompletedCount(completedCount);
task.setPendingCount(totalCount - completedCount);
task.setCompletionRate(totalCount > 0 ? BigDecimal.valueOf(completedCount * 100.0 / totalCount) : BigDecimal.ZERO);
return task;
}
@Test
void testRestartTask_Success_CallsUpdateStatistics() {
Long taskId = 1L;
QuestionnaireTaskDO task = createTestTask(3, 50, 30);
when(questionnaireTaskMapper.selectById(taskId)).thenReturn(task);
questionnaireTaskService.restartTask(taskId);
verify(questionnaireTaskMapper).updateById(any());
verify(questionnaireTaskMapper).updateTaskStatistics(taskId);
}
@Test
void testRestartTask_ThrowsException_WhenTaskNotExists() {
Long taskId = 999L;
when(questionnaireTaskMapper.selectById(taskId)).thenReturn(null);
ServiceException exception = assertThrows(ServiceException.class,
() -> questionnaireTaskService.restartTask(taskId));
assertEquals(ErrorCodeConstants.QUESTIONNAIRE_TASK_NOT_EXISTS, exception.getCode());
verify(questionnaireTaskMapper, never()).updateTaskStatistics(any());
}
@Test
void testRestartTask_ThrowsException_WhenStatusNotEnded() {
Long taskId = 3L;
QuestionnaireTaskDO task = createTestTask(2, 50, 30);
when(questionnaireTaskMapper.selectById(taskId)).thenReturn(task);
ServiceException exception = assertThrows(ServiceException.class,
() -> questionnaireTaskService.restartTask(taskId));
assertEquals(ErrorCodeConstants.QUESTIONNAIRE_TASK_CANNOT_RESTART, exception.getCode());
verify(questionnaireTaskMapper, never()).updateTaskStatistics(any());
}
}

@ -1 +0,0 @@
Subproject commit bf6875adf6a99d90ca95f84a6045aea1cc8779c8

View File

@ -130,11 +130,11 @@
</dependency>
<!-- 开发工具 - 热启动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-devtools</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!-- 服务保障相关 -->
<dependency>

View File

@ -290,9 +290,9 @@ justauth:
llm:
# 本地OneAPI服务
local:
base-url: ${LLM_BASE_URL:https://oneapi.gongjulian.cn/v1} # OneAPI服务地址
api-key: ${LLM_API_KEY:sk-lB2Fc9ssY5UuwmiV5dD441F997364d29Be547e008dF5Cf41} # API密钥建议通过环境变量配置
model: ${LLM_MODEL:minimaxai/minimax-m2.1} # 使用的模型deepseek-ai/deepseek-v3.2 暂时不可用)
base-url: ${LLM_BASE_URL:http://192.168.10.150:2100/v1} # OneAPI服务地址
api-key: ${LLM_API_KEY:} # API密钥建议通过环境变量配置
model: ${LLM_MODEL:qwen3} # 使用的模型deepseek-ai/deepseek-v3.2 暂时不可用)
timeout-seconds: ${LLM_TIMEOUT:120} # 请求超时时间
# Claude可选需要时取消注释
# claude: