新增安全教育培训题库列表相关接口,批量上传、下载线下考试文件接口;优化,修改HSE管理相关代码逻辑

This commit is contained in:
lcj
2025-04-15 21:40:37 +08:00
parent 02d577e604
commit 20e778a1a7
31 changed files with 1247 additions and 79 deletions

View File

@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log; import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType; import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController; import org.dromara.common.web.core.BaseController;
@ -59,6 +60,7 @@ public class BusConstructionUserFileController extends BaseController {
*/ */
@SaCheckPermission("project:constructionUserFile:export") @SaCheckPermission("project:constructionUserFile:export")
@Log(title = "施工人员文件存储", businessType = BusinessType.EXPORT) @Log(title = "施工人员文件存储", businessType = BusinessType.EXPORT)
@RepeatSubmit()
@PostMapping("/exportFileTemplate") @PostMapping("/exportFileTemplate")
public void exportFileTemplate(ConstructionUserFileTemplateReq req, HttpServletResponse response) { public void exportFileTemplate(ConstructionUserFileTemplateReq req, HttpServletResponse response) {
busConstructionUserFileService.downloadFileTemplate(req, response); busConstructionUserFileService.downloadFileTemplate(req, response);
@ -69,6 +71,7 @@ public class BusConstructionUserFileController extends BaseController {
*/ */
@SaCheckPermission("project:constructionUserFile:edit") @SaCheckPermission("project:constructionUserFile:edit")
@Log(title = "施工人员文件存储", businessType = BusinessType.INSERT) @Log(title = "施工人员文件存储", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/upload/zip") @PostMapping("/upload/zip")
public R<Boolean> uploadFileZip(@RequestParam("file") MultipartFile file) { public R<Boolean> uploadFileZip(@RequestParam("file") MultipartFile file) {
Boolean result = busConstructionUserFileService.batchUploadFileByZip(file); Boolean result = busConstructionUserFileService.batchUploadFileByZip(file);

View File

@ -48,10 +48,10 @@ public interface IBusConstructionUserFileService extends IService<BusConstructio
/** /**
* 通过zip文件批量上传施工人员文件 * 通过zip文件批量上传施工人员文件
* *
* @param file zip文件 * @param multipartFile zip文件
* @return 是否上传成功 * @return 是否上传成功
*/ */
Boolean batchUploadFileByZip(MultipartFile file); Boolean batchUploadFileByZip(MultipartFile multipartFile);
/** /**
* 保存施工人员文件存储 * 保存施工人员文件存储

View File

@ -126,12 +126,14 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
.eq(StringUtils.isNotEmpty(req.getUserName()), BusAttendance::getUserName, req.getUserName()) .eq(StringUtils.isNotEmpty(req.getUserName()), BusAttendance::getUserName, req.getUserName())
.between(BusAttendance::getClockDate, startDate, endDate) .between(BusAttendance::getClockDate, startDate, endDate)
.orderByDesc(BusAttendance::getClockDate); .orderByDesc(BusAttendance::getClockDate);
// 获取黑名单用户列表
List<Long> blackList = constructionBlacklistService.queryIdListByProjectId(projectId);
// 构建查询用户相关信息查询条件 // 构建查询用户相关信息查询条件
List<Long> userIdList = constructionUserService.lambdaQuery() List<Long> userIdList = constructionUserService.lambdaQuery()
.eq(BusConstructionUser::getProjectId, projectId) .eq(BusConstructionUser::getProjectId, projectId)
.eq(req.getTeamId() != null, BusConstructionUser::getTeamId, req.getTeamId()) .eq(req.getTeamId() != null, BusConstructionUser::getTeamId, req.getTeamId())
.eq(req.getTypeOfWork() != null, BusConstructionUser::getTypeOfWork, req.getTypeOfWork()) .eq(req.getTypeOfWork() != null, BusConstructionUser::getTypeOfWork, req.getTypeOfWork())
.notIn(BusConstructionUser::getId, constructionBlacklistService.queryIdListByProjectId(projectId)) .notIn(CollUtil.isNotEmpty(blackList), BusConstructionUser::getId, blackList)
.list().stream().map(BusConstructionUser::getId).toList(); .list().stream().map(BusConstructionUser::getId).toList();
lqw.in(CollUtil.isNotEmpty(userIdList), BusAttendance::getUserId, userIdList); lqw.in(CollUtil.isNotEmpty(userIdList), BusAttendance::getUserId, userIdList);
Map<Date, List<BusAttendance>> dateListMap = this.list(lqw) Map<Date, List<BusAttendance>> dateListMap = this.list(lqw)

View File

@ -13,10 +13,19 @@ public interface DocumentSafetyMeetingConstant {
*/ */
String TOP_FOLDER_PREFIX = "/doc/safety/meeting/"; String TOP_FOLDER_PREFIX = "/doc/safety/meeting/";
/**
* 获取顶级目录前缀
*
* @param projectId 项目id
* @return 顶级目录前缀
*/
static String getTopFolderPrefix(Long projectId) { static String getTopFolderPrefix(Long projectId) {
return String.format("%s%s/", TOP_FOLDER_PREFIX, projectId); return String.format("%s%s/", TOP_FOLDER_PREFIX, projectId);
} }
/**
* 图片后缀列表
*/
List<String> PICTURE_SUFFIX_LIST = List.of("jpeg", "jpg", "png", "webp"); List<String> PICTURE_SUFFIX_LIST = List.of("jpeg", "jpg", "png", "webp");
} }

View File

@ -15,6 +15,7 @@ import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController; import org.dromara.common.web.core.BaseController;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerBatchDownloadFileReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerCreateReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerCreateReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerQueryReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerQueryReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerUpdateReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerUpdateReq;
@ -22,6 +23,7 @@ import org.dromara.safety.domain.vo.BusQuestionUserAnswerVo;
import org.dromara.safety.service.IBusQuestionUserAnswerService; import org.dromara.safety.service.IBusQuestionUserAnswerService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@ -59,6 +61,18 @@ public class BusQuestionUserAnswerController extends BaseController {
ExcelUtil.exportExcel(list, "用户试卷存储", BusQuestionUserAnswerVo.class, response); ExcelUtil.exportExcel(list, "用户试卷存储", BusQuestionUserAnswerVo.class, response);
} }
/**
* 批量下载用户试卷存储
*/
@SaCheckPermission("safety:questionUserAnswer:export")
@Log(title = "用户试卷存储", businessType = BusinessType.EXPORT)
@RepeatSubmit()
@PostMapping("/exportFile")
public void exportFile(QuestionUserAnswerBatchDownloadFileReq req,
HttpServletResponse response) {
busQuestionUserAnswerService.batchDownloadFile(req, response);
}
/** /**
* 获取用户试卷存储详细信息 * 获取用户试卷存储详细信息
* *
@ -82,6 +96,18 @@ public class BusQuestionUserAnswerController extends BaseController {
return R.ok(busQuestionUserAnswerService.insertByBo(req)); return R.ok(busQuestionUserAnswerService.insertByBo(req));
} }
/**
* 上传线下考试试卷存储
*/
@SaCheckPermission("safety:questionUserAnswer:add")
@Log(title = "用户试卷存储", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/upload/zip")
public R<Boolean> batchUploadFileByZip(@RequestParam("file") MultipartFile multipartFile,
Long projectId) {
return R.ok(busQuestionUserAnswerService.batchUploadFileByZip(multipartFile, projectId));
}
/** /**
* 修改用户试卷存储 * 修改用户试卷存储
*/ */

View File

@ -0,0 +1,108 @@
package org.dromara.safety.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryCreateReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryQueryReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionsCategoryVo;
import org.dromara.safety.service.IBusQuestionsCategoryService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 题库类别
*
* @author lcj
* @date 2025-04-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/safety/questionsCategory")
public class BusQuestionsCategoryController extends BaseController {
private final IBusQuestionsCategoryService busQuestionsCategoryService;
/**
* 查询题库类别列表
*/
@SaCheckPermission("safety:questionsCategory:list")
@GetMapping("/list")
public TableDataInfo<BusQuestionsCategoryVo> list(QuestionsCategoryQueryReq req, PageQuery pageQuery) {
return busQuestionsCategoryService.queryPageList(req, pageQuery);
}
/**
* 导出题库类别列表
*/
@SaCheckPermission("safety:questionsCategory:export")
@Log(title = "题库类别", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(QuestionsCategoryQueryReq req, HttpServletResponse response) {
List<BusQuestionsCategoryVo> list = busQuestionsCategoryService.queryList(req);
ExcelUtil.exportExcel(list, "题库类别", BusQuestionsCategoryVo.class, response);
}
/**
* 获取题库类别详细信息
*
* @param id 主键
*/
@SaCheckPermission("safety:questionsCategory:query")
@GetMapping("/{id}")
public R<BusQuestionsCategoryVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(busQuestionsCategoryService.queryById(id));
}
/**
* 新增题库类别
*/
@SaCheckPermission("safety:questionsCategory:add")
@Log(title = "题库类别", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Long> add(@Validated(AddGroup.class) @RequestBody QuestionsCategoryCreateReq req) {
return R.ok(busQuestionsCategoryService.insertByBo(req));
}
/**
* 修改题库类别
*/
@SaCheckPermission("safety:questionsCategory:edit")
@Log(title = "题库类别", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody QuestionsCategoryUpdateReq req) {
return toAjax(busQuestionsCategoryService.updateByBo(req));
}
/**
* 删除题库类别
*
* @param ids 主键串
*/
@SaCheckPermission("safety:questionsCategory:remove")
@Log(title = "题库类别", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(busQuestionsCategoryService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -1,6 +1,7 @@
package org.dromara.safety.domain; package org.dromara.safety.domain;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -37,7 +38,7 @@ public class BusQuestionBank extends BaseEntity {
/** /**
* 题目类别 * 题目类别
*/ */
private String categoryType; private Long categoryId;
/** /**
* 题目类型 * 题目类型
@ -72,6 +73,7 @@ public class BusQuestionBank extends BaseEntity {
/** /**
* 是否删除0正常 1删除 * 是否删除0正常 1删除
*/ */
@TableLogic
private Long isDelete; private Long isDelete;

View File

@ -1,6 +1,7 @@
package org.dromara.safety.domain; package org.dromara.safety.domain;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -54,6 +55,11 @@ public class BusQuestionUserAnswer extends BaseEntity {
*/ */
private Long score; private Long score;
/**
* 考试时间(时间戳/秒)
*/
private Long examTime;
/** /**
* 用时时间(时间戳/秒) * 用时时间(时间戳/秒)
*/ */
@ -69,6 +75,11 @@ public class BusQuestionUserAnswer extends BaseEntity {
*/ */
private String file; private String file;
/**
* 考试类型1线上考试 2线下考试
*/
private String examType;
/** /**
* 删除时间 * 删除时间
*/ */
@ -77,6 +88,7 @@ public class BusQuestionUserAnswer extends BaseEntity {
/** /**
* 是否删除0正常 1删除 * 是否删除0正常 1删除
*/ */
@TableLogic
private Long isDelete; private Long isDelete;

View File

@ -0,0 +1,50 @@
package org.dromara.safety.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 题库类别对象 bus_questions_category
*
* @author lcj
* @date 2025-04-15
*/
@Data
@TableName("bus_questions_category")
public class BusQuestionsCategory implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@TableId(value = "id")
private Long id;
/**
* 项目id
*/
private Long projectId;
/**
* 题库类别
*/
private String categoryName;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@ -0,0 +1,24 @@
package org.dromara.safety.domain.enums;
import lombok.Getter;
/**
* @author lcj
* @date 2025/4/15 14:56
*/
@Getter
public enum SafetyExamTypeEnum {
ONLINE("线上考试", "1"),
OFFLINE("线下考试", "2");
private final String text;
private final String value;
SafetyExamTypeEnum(String text, String value) {
this.text = text;
this.value = value;
}
}

View File

@ -24,7 +24,7 @@ public class QuestionBankCreateReq implements Serializable {
/** /**
* 题目类别 * 题目类别
*/ */
private String categoryType; private Long categoryId;
/** /**
* 题目类型 * 题目类型

View File

@ -29,7 +29,7 @@ public class QuestionBankQueryReq implements Serializable {
/** /**
* 题目类别 * 题目类别
*/ */
private String categoryType; private Long categoryId;
/** /**
* 题目类型 * 题目类型

View File

@ -24,7 +24,7 @@ public class QuestionBankUpdateReq implements Serializable {
/** /**
* 题目类别 * 题目类别
*/ */
private String categoryType; private Long categoryId;
/** /**
* 题目类型 * 题目类型

View File

@ -0,0 +1,28 @@
package org.dromara.safety.domain.req.questionscategory;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lcj
* @date 2025/4/15 9:39
*/
@Data
public class QuestionsCategoryCreateReq implements Serializable {
@Serial
private static final long serialVersionUID = -3517465472029929723L;
/**
* 项目id
*/
private Long projectId;
/**
* 题库类别
*/
private String categoryName;
}

View File

@ -0,0 +1,28 @@
package org.dromara.safety.domain.req.questionscategory;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lcj
* @date 2025/4/15 9:39
*/
@Data
public class QuestionsCategoryQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = 8543449238477998979L;
/**
* 项目id
*/
private Long projectId;
/**
* 题库类别
*/
private String categoryName;
}

View File

@ -0,0 +1,33 @@
package org.dromara.safety.domain.req.questionscategory;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lcj
* @date 2025/4/15 9:39
*/
@Data
public class QuestionsCategoryUpdateReq implements Serializable {
@Serial
private static final long serialVersionUID = -6588543841396112020L;
/**
* 主键id
*/
private Long id;
/**
* 项目id
*/
private Long projectId;
/**
* 题库类别
*/
private String categoryName;
}

View File

@ -0,0 +1,24 @@
package org.dromara.safety.domain.req.questionuseranswer;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author lcj
* @date 2025/4/15 16:35
*/
@Data
public class QuestionUserAnswerBatchDownloadFileReq implements Serializable {
@Serial
private static final long serialVersionUID = 5011807834768249135L;
/**
* 用户id列表
*/
private List<Long> userIdList;
}

View File

@ -4,7 +4,6 @@ import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
/** /**
* @author lcj * @author lcj
@ -16,43 +15,19 @@ public class QuestionUserAnswerQueryReq implements Serializable {
@Serial @Serial
private static final long serialVersionUID = -7056242109124074218L; private static final long serialVersionUID = -7056242109124074218L;
/**
* 主键id
*/
private Long id;
/** /**
* 项目id * 项目id
*/ */
private Long projectId; private Long projectId;
/** /**
* 用户id * 用户
*/ */
private Long userId; private String userName;
/** /**
* 题库id列表 * 班组id
*/ */
private List<Long> bankIdList; private Long teamId;
/**
* 答案列表
*/
private List<String> answerList;
/**
* 得分
*/
private Long score;
/**
* 用时时间(时间戳/秒)
*/
private Long takeTime;
/**
* 及格线/总分格式60,100
*/
private String pass;
} }

View File

@ -0,0 +1,47 @@
package org.dromara.safety.domain.req.questionuseranswer;
import lombok.Data;
/**
* @author lcj
* @date 2025/4/15 14:34
*/
@Data
public class QuestionUserAnswerUploadTemp {
/**
* 项目id
*/
private Long projectId;
/**
* 用户身份证
*/
private String userIdCard;
/**
* 用户姓名
*/
private String userName;
/**
* 满分
*/
private Long fullScore;
/**
* 得分
*/
private Long score;
/**
* 及格分数
*/
private Long passScore;
/**
* 文件地址
*/
private String file;
}

View File

@ -10,6 +10,7 @@ import org.dromara.safety.domain.BusQuestionBank;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import java.util.List; import java.util.List;
@ -40,11 +41,15 @@ public class BusQuestionBankVo implements Serializable {
private Long projectId; private Long projectId;
/** /**
* 题目类别 * 题目类别id
*/ */
@ExcelProperty(value = "题目类别", converter = ExcelDictConvert.class) private Long categoryId;
@ExcelDictFormat(dictType = "safety_question_category_type")
private String categoryType; /**
* 题目类别名称
*/
@ExcelProperty(value = "题目类别名称")
private String categoryName;
/** /**
* 题目类型 * 题目类型
@ -71,5 +76,9 @@ public class BusQuestionBankVo implements Serializable {
@ExcelProperty(value = "正确答案") @ExcelProperty(value = "正确答案")
private String correctAnswer; private String correctAnswer;
/**
* 创建时间
*/
private Date createTime;
} }

View File

@ -10,6 +10,7 @@ import org.dromara.safety.domain.BusQuestionUserAnswer;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import java.util.List; import java.util.List;
@ -30,13 +31,11 @@ public class BusQuestionUserAnswerVo implements Serializable {
/** /**
* 主键id * 主键id
*/ */
@ExcelProperty(value = "主键id")
private Long id; private Long id;
/** /**
* 项目id * 项目id
*/ */
@ExcelProperty(value = "项目id")
private Long projectId; private Long projectId;
/** /**
@ -45,6 +44,12 @@ public class BusQuestionUserAnswerVo implements Serializable {
@ExcelProperty(value = "用户id") @ExcelProperty(value = "用户id")
private Long userId; private Long userId;
/**
* 用户名称
*/
@ExcelProperty(value = "用户名称")
private String userName;
/** /**
* 题库id列表 * 题库id列表
*/ */
@ -63,11 +68,16 @@ public class BusQuestionUserAnswerVo implements Serializable {
@ExcelProperty(value = "得分") @ExcelProperty(value = "得分")
private Long score; private Long score;
/**
* 考试时间(时间戳/秒)
*/
@ExcelProperty(value = "考试时间")
private Long examTime;
/** /**
* 用时时间(时间戳/秒) * 用时时间(时间戳/秒)
*/ */
@ExcelProperty(value = "用时时间", converter = ExcelDictConvert.class) @ExcelProperty(value = "用时时间")
@ExcelDictFormat(readConverterExp = "时=间戳/秒")
private Long takeTime; private Long takeTime;
/** /**
@ -82,5 +92,22 @@ public class BusQuestionUserAnswerVo implements Serializable {
@ExcelProperty(value = "文件地址") @ExcelProperty(value = "文件地址")
private String file; private String file;
/**
* 文件地址列表
*/
private List<String> fileUrl;
/**
* 考试类型
*/
@ExcelProperty(value = "考试类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "user_exam_type")
private String examType;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date createTime;
} }

View File

@ -0,0 +1,39 @@
package org.dromara.safety.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.safety.domain.BusQuestionsCategory;
import java.io.Serial;
import java.io.Serializable;
/**
* 题库类别视图对象 bus_questions_category
*
* @author lcj
* @date 2025-04-15
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusQuestionsCategory.class)
public class BusQuestionsCategoryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@ExcelProperty(value = "主键id")
private Long id;
/**
* 题库类别
*/
@ExcelProperty(value = "题库类别")
private String categoryName;
}

View File

@ -36,8 +36,7 @@ public class BusSafetyInspectionVo implements Serializable {
/** /**
* 父id默认为0 * 父id默认为0
*/ */
@ExcelProperty(value = "父id", converter = ExcelDictConvert.class) @ExcelProperty(value = "父id")
@ExcelDictFormat(readConverterExp = "默=认为0")
private Long pid; private Long pid;
/** /**
@ -80,8 +79,7 @@ public class BusSafetyInspectionVo implements Serializable {
/** /**
* 整改人班组长id * 整改人班组长id
*/ */
@ExcelProperty(value = "整改人", converter = ExcelDictConvert.class) @ExcelProperty(value = "整改人")
@ExcelDictFormat(readConverterExp = "班=组长")
private Long correctorId; private Long correctorId;
/** /**

View File

@ -0,0 +1,15 @@
package org.dromara.safety.mapper;
import org.dromara.safety.domain.BusQuestionsCategory;
import org.dromara.safety.domain.vo.BusQuestionsCategoryVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 题库类别Mapper接口
*
* @author lcj
* @date 2025-04-15
*/
public interface BusQuestionsCategoryMapper extends BaseMapperPlus<BusQuestionsCategory, BusQuestionsCategoryVo> {
}

View File

@ -3,13 +3,16 @@ package org.dromara.safety.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.safety.domain.BusQuestionUserAnswer; import org.dromara.safety.domain.BusQuestionUserAnswer;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerBatchDownloadFileReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerCreateReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerCreateReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerQueryReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerQueryReq;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerUpdateReq; import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionUserAnswerVo; import org.dromara.safety.domain.vo.BusQuestionUserAnswerVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -55,6 +58,15 @@ public interface IBusQuestionUserAnswerService extends IService<BusQuestionUserA
*/ */
Long insertByBo(QuestionUserAnswerCreateReq req); Long insertByBo(QuestionUserAnswerCreateReq req);
/**
* 通过zip文件批量上传用户试卷存储
*
* @param multipartFile zip文件
* @param projectId 项目id
* @return 是否上传成功
*/
Boolean batchUploadFileByZip(MultipartFile multipartFile, Long projectId);
/** /**
* 修改用户试卷存储 * 修改用户试卷存储
* *
@ -95,4 +107,13 @@ public interface IBusQuestionUserAnswerService extends IService<BusQuestionUserA
* @return 用户试卷存储分页对象视图 * @return 用户试卷存储分页对象视图
*/ */
Page<BusQuestionUserAnswerVo> getVoPage(Page<BusQuestionUserAnswer> questionUserAnswerPage); Page<BusQuestionUserAnswerVo> getVoPage(Page<BusQuestionUserAnswer> questionUserAnswerPage);
/**
* 批量下载用户试卷存储文件
*
* @param req 用户试卷存储文件下载请求
* @param response 设置响应头和向客户端发送文件内容
*/
void batchDownloadFile(QuestionUserAnswerBatchDownloadFileReq req, HttpServletResponse response);
} }

View File

@ -0,0 +1,99 @@
package org.dromara.safety.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.safety.domain.BusQuestionsCategory;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryCreateReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryQueryReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionsCategoryVo;
import java.util.Collection;
import java.util.List;
/**
* 题库类别Service接口
*
* @author lcj
* @date 2025-04-15
*/
public interface IBusQuestionsCategoryService extends IService<BusQuestionsCategory> {
/**
* 查询题库类别
*
* @param id 主键
* @return 题库类别
*/
BusQuestionsCategoryVo queryById(Long id);
/**
* 分页查询题库类别列表
*
* @param req 查询条件
* @param pageQuery 分页参数
* @return 题库类别分页列表
*/
TableDataInfo<BusQuestionsCategoryVo> queryPageList(QuestionsCategoryQueryReq req, PageQuery pageQuery);
/**
* 查询符合条件的题库类别列表
*
* @param req 查询条件
* @return 题库类别列表
*/
List<BusQuestionsCategoryVo> queryList(QuestionsCategoryQueryReq req);
/**
* 新增题库类别
*
* @param req 题库类别
* @return 新增题库类别组件
*/
Long insertByBo(QuestionsCategoryCreateReq req);
/**
* 修改题库类别
*
* @param req 题库类别
* @return 是否修改成功
*/
Boolean updateByBo(QuestionsCategoryUpdateReq req);
/**
* 校验并批量删除题库类别信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取题库类别视图对象
*
* @param questionsCategory 题库类别对象
* @return 题库类别视图对象
*/
BusQuestionsCategoryVo getVo(BusQuestionsCategory questionsCategory);
/**
* 获取题库类别查询条件封装
*
* @param req 题库类别查询条件
* @return 题库类别查询条件封装
*/
LambdaQueryWrapper<BusQuestionsCategory> buildQueryWrapper(QuestionsCategoryQueryReq req);
/**
* 获取题库类别分页对象视图
*
* @param questionsCategoryPage 题库类别分页对象
* @return 题库类别分页对象视图
*/
Page<BusQuestionsCategoryVo> getVoPage(Page<BusQuestionsCategory> questionsCategoryPage);
}

View File

@ -18,18 +18,23 @@ import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.project.service.IBusProjectService; import org.dromara.project.service.IBusProjectService;
import org.dromara.safety.domain.BusQuestionBank; import org.dromara.safety.domain.BusQuestionBank;
import org.dromara.safety.domain.BusQuestionsCategory;
import org.dromara.safety.domain.req.questionbank.QuestionBankCreateReq; import org.dromara.safety.domain.req.questionbank.QuestionBankCreateReq;
import org.dromara.safety.domain.req.questionbank.QuestionBankQueryReq; import org.dromara.safety.domain.req.questionbank.QuestionBankQueryReq;
import org.dromara.safety.domain.req.questionbank.QuestionBankUpdateReq; import org.dromara.safety.domain.req.questionbank.QuestionBankUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionBankVo; import org.dromara.safety.domain.vo.BusQuestionBankVo;
import org.dromara.safety.mapper.BusQuestionBankMapper; import org.dromara.safety.mapper.BusQuestionBankMapper;
import org.dromara.safety.service.IBusQuestionBankService; import org.dromara.safety.service.IBusQuestionBankService;
import org.dromara.safety.service.IBusQuestionsCategoryService;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* 题库Service业务层处理 * 题库Service业务层处理
@ -44,6 +49,9 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
@Resource @Resource
private IBusProjectService projectService; private IBusProjectService projectService;
@Resource
private IBusQuestionsCategoryService questionsCategoryService;
/** /**
* 查询题库 * 查询题库
* *
@ -137,12 +145,12 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
*/ */
private void validEntityBeforeSave(BusQuestionBank entity, Boolean create) { private void validEntityBeforeSave(BusQuestionBank entity, Boolean create) {
// TODO 做一些数据校验,如唯一约束 // TODO 做一些数据校验,如唯一约束
String categoryType = entity.getCategoryType(); Long categoryId = entity.getCategoryId();
String questionType = entity.getQuestionType(); String questionType = entity.getQuestionType();
String questionContent = entity.getQuestionContent(); String questionContent = entity.getQuestionContent();
Long projectId = entity.getProjectId(); Long projectId = entity.getProjectId();
if (create) { if (create) {
if (StringUtils.isEmpty(categoryType)) { if (categoryId == null) {
throw new ServiceException("分类类型不能为空", HttpStatus.BAD_REQUEST); throw new ServiceException("分类类型不能为空", HttpStatus.BAD_REQUEST);
} }
if (StringUtils.isEmpty(questionType)) { if (StringUtils.isEmpty(questionType)) {
@ -158,6 +166,9 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
if (projectId != null && projectService.getById(projectId) == null) { if (projectId != null && projectService.getById(projectId) == null) {
throw new ServiceException("对应项目不存在", HttpStatus.NOT_FOUND); throw new ServiceException("对应项目不存在", HttpStatus.NOT_FOUND);
} }
if (categoryId != null && questionsCategoryService.getById(categoryId) == null) {
throw new ServiceException("对应题目分类类型不存在", HttpStatus.NOT_FOUND);
}
} }
/** /**
@ -194,6 +205,12 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
return questionBankVo; return questionBankVo;
} }
BeanUtils.copyProperties(questionBank, questionBankVo); BeanUtils.copyProperties(questionBank, questionBankVo);
// 获取题目分类名称
Long categoryId = questionBank.getCategoryId();
BusQuestionsCategory category = questionsCategoryService.getById(categoryId);
if (category != null) {
questionBankVo.setCategoryName(category.getCategoryName());
}
return questionBankVo; return questionBankVo;
} }
@ -211,7 +228,7 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
} }
Long id = req.getId(); Long id = req.getId();
Long projectId = req.getProjectId(); Long projectId = req.getProjectId();
String categoryType = req.getCategoryType(); Long categoryId = req.getCategoryId();
String questionType = req.getQuestionType(); String questionType = req.getQuestionType();
String questionContent = req.getQuestionContent(); String questionContent = req.getQuestionContent();
List<String> optionList = req.getOptionList(); List<String> optionList = req.getOptionList();
@ -228,7 +245,7 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
// 精准查询 // 精准查询
lqw.eq(ObjectUtils.isNotEmpty(id), BusQuestionBank::getId, id); lqw.eq(ObjectUtils.isNotEmpty(id), BusQuestionBank::getId, id);
lqw.eq(ObjectUtils.isNotEmpty(projectId), BusQuestionBank::getProjectId, projectId); lqw.eq(ObjectUtils.isNotEmpty(projectId), BusQuestionBank::getProjectId, projectId);
lqw.eq(StringUtils.isNotBlank(categoryType), BusQuestionBank::getCategoryType, categoryType); lqw.eq(ObjectUtils.isNotEmpty(categoryId), BusQuestionBank::getCategoryId, categoryId);
lqw.eq(StringUtils.isNotBlank(questionType), BusQuestionBank::getQuestionType, questionType); lqw.eq(StringUtils.isNotBlank(questionType), BusQuestionBank::getQuestionType, questionType);
return lqw; return lqw;
} }
@ -252,8 +269,26 @@ public class BusQuestionBankServiceImpl extends ServiceImpl<BusQuestionBankMappe
if (CollUtil.isEmpty(questionBankList)) { if (CollUtil.isEmpty(questionBankList)) {
return questionBankVoPage; return questionBankVoPage;
} }
// 关联查询题目分类信息
Set<Long> categoryIdList = questionBankList.stream().map(BusQuestionBank::getCategoryId)
.collect(Collectors.toSet());
Map<Long, List<BusQuestionsCategory>> questionCategoryMap = questionsCategoryService.listByIds(categoryIdList)
.stream().collect(Collectors.groupingBy(BusQuestionsCategory::getId));
// 对象列表 => 封装对象列表 // 对象列表 => 封装对象列表
List<BusQuestionBankVo> questionBankVoList = questionBankList.stream().map(this::getVo).toList(); List<BusQuestionBankVo> questionBankVoList = questionBankList.stream().map(questionBank -> {
BusQuestionBankVo questionBankVo = new BusQuestionBankVo();
BeanUtils.copyProperties(questionBank, questionBankVo);
// 获取题目分类名称
Long categoryId = questionBank.getCategoryId();
String categoryName = null;
if (questionCategoryMap.containsKey(categoryId)) {
BusQuestionsCategory category = questionCategoryMap.get(categoryId).get(0);
categoryName = category.getCategoryName();
}
questionBankVo.setCategoryName(categoryName);
// 返回封装对象
return questionBankVo;
}).toList();
questionBankVoPage.setRecords(questionBankVoList); questionBankVoPage.setRecords(questionBankVoList);
return questionBankVoPage; return questionBankVoPage;
} }

View File

@ -1,31 +1,55 @@
package org.dromara.safety.service.impl; package org.dromara.safety.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus; import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.project.domain.BusConstructionUser;
import org.dromara.project.domain.BusProjectTeamMember;
import org.dromara.project.service.IBusConstructionUserService;
import org.dromara.project.service.IBusProjectService; import org.dromara.project.service.IBusProjectService;
import org.dromara.project.service.IBusProjectTeamMemberService;
import org.dromara.safety.domain.BusQuestionUserAnswer; import org.dromara.safety.domain.BusQuestionUserAnswer;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerCreateReq; import org.dromara.safety.domain.enums.SafetyExamTypeEnum;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerQueryReq; import org.dromara.safety.domain.req.questionuseranswer.*;
import org.dromara.safety.domain.req.questionuseranswer.QuestionUserAnswerUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionUserAnswerVo; import org.dromara.safety.domain.vo.BusQuestionUserAnswerVo;
import org.dromara.safety.mapper.BusQuestionUserAnswerMapper; import org.dromara.safety.mapper.BusQuestionUserAnswerMapper;
import org.dromara.safety.service.IBusQuestionUserAnswerService; import org.dromara.safety.service.IBusQuestionUserAnswerService;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.service.ISysOssService;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection; import java.io.File;
import java.util.List; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/** /**
* 用户试卷存储Service业务层处理 * 用户试卷存储Service业务层处理
@ -33,6 +57,7 @@ import java.util.List;
* @author lcj * @author lcj
* @date 2025-03-24 * @date 2025-03-24
*/ */
@Slf4j
@Service @Service
public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUserAnswerMapper, BusQuestionUserAnswer> public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUserAnswerMapper, BusQuestionUserAnswer>
implements IBusQuestionUserAnswerService { implements IBusQuestionUserAnswerService {
@ -40,6 +65,15 @@ public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUse
@Resource @Resource
private IBusProjectService projectService; private IBusProjectService projectService;
@Resource
private ISysOssService ossService;
@Resource
private IBusConstructionUserService constructionUserService;
@Resource
private IBusProjectTeamMemberService projectTeamMemberService;
/** /**
* 查询用户试卷存储 * 查询用户试卷存储
* *
@ -112,6 +146,151 @@ public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUse
return questionUserAnswer.getId(); return questionUserAnswer.getId();
} }
/**
* 通过zip文件批量上传用户试卷存储
*
* @param multipartFile zip文件
* @return 是否上传成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchUploadFileByZip(MultipartFile multipartFile, Long projectId) {
// 1. 校验
String originalFilename = multipartFile.getOriginalFilename();
if (originalFilename == null) {
throw new ServiceException("文件名不存在", HttpStatus.BAD_REQUEST);
}
// 校验是否为压缩包zip格式
String suffix = FileUtil.getSuffix(originalFilename);
if (!suffix.equals("zip")) {
throw new ServiceException("请上传zip格式的文件", HttpStatus.BAD_REQUEST);
}
// 2. 临时存储文件路径
// 2.1 压缩包临时文件路径
File tempZipFile = null;
String randomStr = RandomUtil.randomString(16);
String tempZipFilePath = randomStr + "-" + originalFilename;
// 2.2 解压后的文件夹路径
File destDir = null;
String basePath = "unzip_path";
String destDirPath = String.format("%s/%s/%s/%s", basePath, DateUtils.getDate(), projectId, randomStr);
// 3. 构建临时存储对象
List<QuestionUserAnswerUploadTemp> tempList = new ArrayList<>();
try {
// 4. 创建临时文件
tempZipFile = File.createTempFile(tempZipFilePath, null);
multipartFile.transferTo(tempZipFile);
// 5. 解压 zip
destDir = new File(destDirPath);
ZipUtil.unzip(tempZipFile, destDir);
// 4. 遍历最外层文件夹
File[] userFolders = destDir.listFiles();
if (userFolders != null) {
for (File userFolder : userFolders) {
if (userFolder.isDirectory()) {
// 5. 获取文件名,格式为 姓名-身份证-满分-得分-及格分,例如 小明-500106200101011234-100-61-60
String userFolderName = userFolder.getName();
String[] userParts = userFolderName.split("-");
// 6. 继续遍历获取每个用户子文件夹里的文件
File[] docFolders = userFolder.listFiles();
String fileIdStr = null;
if (docFolders != null) {
// 6.1 遍历文件
List<Long> fileIdList = new ArrayList<>();
for (File docFolder : docFolders) {
if (docFolder.isFile()) {
// 6.2 上传文件
SysOssVo upload = ossService.upload(docFolder);
if (upload != null) {
fileIdList.add(upload.getOssId());
}
}
}
// 6.3 跳过空文件
if (fileIdList.isEmpty()) {
continue;
}
fileIdStr = fileIdList.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
}
// 7. 创建临时对象
QuestionUserAnswerUploadTemp temp = new QuestionUserAnswerUploadTemp();
temp.setProjectId(projectId);
temp.setUserName(userParts[0]);
temp.setUserIdCard(userParts[1]);
temp.setFullScore(Long.parseLong(userParts[2]));
temp.setScore(Long.parseLong(userParts[3]));
temp.setPassScore(Long.parseLong(userParts[4]));
temp.setFile(fileIdStr);
tempList.add(temp);
}
}
}
} catch (Exception e) {
throw new ServiceException("文件上传失败", HttpStatus.ERROR);
} finally {
if (tempZipFile != null) {
// 删除临时文件
boolean delete = tempZipFile.delete();
if (!delete) {
log.error("临时文件删除失败,路径:{}", tempZipFilePath);
}
}
if (destDir != null) {
Path dirPath = Paths.get(basePath);
try {
FileUtils.deleteDirectory(dirPath);
} catch (IOException e) {
log.error("解压文件删除失败,路径:{}", destDirPath, e);
}
}
}
// 8. 遍历临时对象,进行存储
if (CollUtil.isEmpty(tempList)) {
return true;
}
// 8.1 获取用户身份证列表
Set<String> idCardList = tempList.stream().map(QuestionUserAnswerUploadTemp::getUserIdCard)
.collect(Collectors.toSet());
// 8.2 根据用户身份证列表查询用户信息
Map<String, List<BusConstructionUser>> userIdMap = constructionUserService.lambdaQuery()
.in(BusConstructionUser::getSfzNumber, idCardList).list()
.stream().collect(Collectors.groupingBy(BusConstructionUser::getSfzNumber));
// 8.3 遍历临时对象,构造用户试卷存储对象
List<BusQuestionUserAnswer> questionUserAnswerList = tempList.stream().map(temp -> {
BusQuestionUserAnswer questionUserAnswer = new BusQuestionUserAnswer();
// 8.4 获取对应用户id
String userIdCard = temp.getUserIdCard();
Long userId = null;
if (userIdMap.containsKey(userIdCard)) {
BusConstructionUser constructionUser = userIdMap.get(userIdCard).get(0);
userId = constructionUser.getId();
}
// 8.5 判断用户是否存在
if (userId == null) {
String userName = temp.getUserName();
String message = String.format("用户[%s%s]不存在", userName, userIdCard);
throw new ServiceException(message, HttpStatus.NOT_FOUND);
}
questionUserAnswer.setUserId(userId);
// 8.6 设置其他属性
questionUserAnswer.setProjectId(temp.getProjectId());
questionUserAnswer.setScore(temp.getScore());
String pass = String.format("%s,%s", temp.getPassScore(), temp.getFullScore());
questionUserAnswer.setPass(pass);
questionUserAnswer.setFile(temp.getFile());
questionUserAnswer.setExamType(SafetyExamTypeEnum.OFFLINE.getValue());
return questionUserAnswer;
}).toList();
// 9. 保存
boolean result = this.saveBatch(questionUserAnswerList);
if (!result) {
throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
}
return true;
}
/** /**
* 修改用户试卷存储 * 修改用户试卷存储
* *
@ -190,14 +369,29 @@ public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUse
return questionUserAnswerVo; return questionUserAnswerVo;
} }
BeanUtils.copyProperties(questionUserAnswer, questionUserAnswerVo); BeanUtils.copyProperties(questionUserAnswer, questionUserAnswerVo);
// 获取用户试卷题库列表
String bankId = questionUserAnswer.getBankId(); String bankId = questionUserAnswer.getBankId();
if (StringUtils.isNotBlank(bankId)) { if (StringUtils.isNotBlank(bankId)) {
questionUserAnswerVo.setBankIdList(JSONUtil.toList(bankId, Long.class)); questionUserAnswerVo.setBankIdList(JSONUtil.toList(bankId, Long.class));
} }
// 获取用户试卷答案列表
String answer = questionUserAnswer.getAnswer(); String answer = questionUserAnswer.getAnswer();
if (StringUtils.isNotBlank(answer)) { if (StringUtils.isNotBlank(answer)) {
questionUserAnswerVo.setAnswerList(JSONUtil.toList(answer, String.class)); questionUserAnswerVo.setAnswerList(JSONUtil.toList(answer, String.class));
} }
// 获取用户姓名
Long userId = questionUserAnswer.getUserId();
if (userId != null) {
BusConstructionUser constructionUser = constructionUserService.getById(userId);
questionUserAnswerVo.setUserName(constructionUser.getUserName());
}
// 获取用户试卷文件地址
String file = questionUserAnswer.getFile();
if (StringUtils.isNotBlank(file)) {
List<Long> fileIdList = Arrays.stream(file.split(",")).map(Long::parseLong).toList();
List<String> fileUrlList = ossService.listByIds(fileIdList).stream().map(SysOssVo::getUrl).toList();
questionUserAnswerVo.setFileUrl(fileUrlList);
}
return questionUserAnswerVo; return questionUserAnswerVo;
} }
@ -213,33 +407,36 @@ public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUse
if (req == null) { if (req == null) {
return lqw; return lqw;
} }
Long id = req.getId();
Long projectId = req.getProjectId(); Long projectId = req.getProjectId();
Long userId = req.getUserId(); String userName = req.getUserName();
List<Long> bankIdList = req.getBankIdList(); Long teamId = req.getTeamId();
List<String> answerList = req.getAnswerList(); // 联表查询
Long score = req.getScore(); if (StringUtils.isNotBlank(userName)) {
Long takeTime = req.getTakeTime(); List<BusConstructionUser> constructionUserList = constructionUserService.lambdaQuery()
String pass = req.getPass(); .select(BusConstructionUser::getId)
// JSON 数组查询 .like(BusConstructionUser::getUserName, userName)
if (CollUtil.isNotEmpty(bankIdList)) { .list();
for (Long bankId : bankIdList) { if (CollUtil.isNotEmpty(constructionUserList)) {
lqw.like(BusQuestionUserAnswer::getBankId, "\"" + bankId + "\""); List<Long> userIdList = constructionUserList.stream().map(BusConstructionUser::getId).toList();
lqw.in(BusQuestionUserAnswer::getUserId, userIdList);
} else {
lqw.eq(BusQuestionUserAnswer::getUserId, null);
} }
} }
if (CollUtil.isNotEmpty(answerList)) { if (ObjectUtils.isNotEmpty(teamId)) {
for (String answer : answerList) { List<BusProjectTeamMember> projectTeamMemberList = projectTeamMemberService.lambdaQuery()
lqw.like(BusQuestionUserAnswer::getAnswer, "\"" + answer + "\""); .select(BusProjectTeamMember::getMemberId)
.eq(BusProjectTeamMember::getTeamId, teamId)
.list();
if (CollUtil.isNotEmpty(projectTeamMemberList)) {
List<Long> userIdList = projectTeamMemberList.stream().map(BusProjectTeamMember::getMemberId).toList();
lqw.in(BusQuestionUserAnswer::getUserId, userIdList);
} else {
lqw.eq(BusQuestionUserAnswer::getUserId, null);
} }
} }
// 模糊查询
lqw.like(StringUtils.isNotBlank(pass), BusQuestionUserAnswer::getPass, pass);
// 精准查询 // 精准查询
lqw.eq(ObjectUtils.isNotEmpty(id), BusQuestionUserAnswer::getId, id);
lqw.eq(ObjectUtils.isNotEmpty(projectId), BusQuestionUserAnswer::getProjectId, projectId); lqw.eq(ObjectUtils.isNotEmpty(projectId), BusQuestionUserAnswer::getProjectId, projectId);
lqw.eq(ObjectUtils.isNotEmpty(userId), BusQuestionUserAnswer::getUserId, userId);
lqw.eq(ObjectUtils.isNotEmpty(score), BusQuestionUserAnswer::getScore, score);
lqw.eq(ObjectUtils.isNotEmpty(takeTime), BusQuestionUserAnswer::getTakeTime, takeTime);
return lqw; return lqw;
} }
@ -262,10 +459,123 @@ public class BusQuestionUserAnswerServiceImpl extends ServiceImpl<BusQuestionUse
if (CollUtil.isEmpty(questionUserAnswerList)) { if (CollUtil.isEmpty(questionUserAnswerList)) {
return questionUserAnswerVoPage; return questionUserAnswerVoPage;
} }
// 获取用户名
Set<Long> userIdList = questionUserAnswerList.stream().map(BusQuestionUserAnswer::getUserId).collect(Collectors.toSet());
List<BusConstructionUser> constructionUserList = constructionUserService.lambdaQuery()
.select(BusConstructionUser::getId, BusConstructionUser::getUserName)
.in(BusConstructionUser::getId, userIdList)
.list();
Map<Long, String> userNameMap = constructionUserList.stream()
.collect(Collectors.toMap(BusConstructionUser::getId, BusConstructionUser::getUserName));
// 获取用户文件
Set<Long> fileIdList = questionUserAnswerList.stream().map(BusQuestionUserAnswer::getFile).filter(StringUtils::isNotBlank)
.flatMap(fileId -> Arrays.stream(fileId.split(",")).map(Long::parseLong)).collect(Collectors.toSet());
Map<Long, List<SysOssVo>> ossMap = ossService.listByIds(fileIdList)
.stream().collect(Collectors.groupingBy(SysOssVo::getOssId));
// 对象列表 => 封装对象列表 // 对象列表 => 封装对象列表
List<BusQuestionUserAnswerVo> questionUserAnswerVoList = questionUserAnswerList.stream().map(this::getVo).toList(); List<BusQuestionUserAnswerVo> questionUserAnswerVoList = questionUserAnswerList.stream().map(questionUserAnswer -> {
BusQuestionUserAnswerVo questionUserAnswerVo = new BusQuestionUserAnswerVo();
BeanUtils.copyProperties(questionUserAnswer, questionUserAnswerVo);
// 获取获取用户名
Long userId = questionUserAnswer.getUserId();
if (userId != null) {
questionUserAnswerVo.setUserName(userNameMap.get(userId));
}
// 获取用户文件
String file = questionUserAnswer.getFile();
if (StringUtils.isNotBlank(file)) {
List<Long> ossIdList = Arrays.stream(file.split(",")).map(Long::parseLong).toList();
List<String> fileUrlList = ossIdList.stream().map(ossId -> {
String url = "";
if (ossMap.containsKey(ossId)) {
url = ossMap.get(ossId).get(0).getUrl();
}
return url;
}).toList();
questionUserAnswerVo.setFileUrl(fileUrlList);
}
// 获取用户试卷题库列表
String bankId = questionUserAnswer.getBankId();
if (StringUtils.isNotBlank(bankId)) {
questionUserAnswerVo.setBankIdList(JSONUtil.toList(bankId, Long.class));
}
// 获取用户试卷答案列表
String answer = questionUserAnswer.getAnswer();
if (StringUtils.isNotBlank(answer)) {
questionUserAnswerVo.setAnswerList(JSONUtil.toList(answer, String.class));
}
return questionUserAnswerVo;
}).toList();
questionUserAnswerVoPage.setRecords(questionUserAnswerVoList); questionUserAnswerVoPage.setRecords(questionUserAnswerVoList);
return questionUserAnswerVoPage; return questionUserAnswerVoPage;
} }
/**
* 批量下载用户试卷存储文件
*
* @param req 用户试卷存储文件下载请求
* @param response 设置响应头和向客户端发送文件内容
*/
@Override
public void batchDownloadFile(QuestionUserAnswerBatchDownloadFileReq req, HttpServletResponse response) {
// 获取用户试卷存储文件
List<Long> userIdList = req.getUserIdList();
List<BusQuestionUserAnswer> questionUserAnswerList = lambdaQuery()
.in(BusQuestionUserAnswer::getUserId, userIdList)
.list();
if (CollUtil.isEmpty(questionUserAnswerList)) {
return;
}
// 查询对应施工人员信息
Map<Long, List<BusConstructionUser>> userMap = constructionUserService.lambdaQuery()
.select(BusConstructionUser::getId, BusConstructionUser::getUserName)
.in(BusConstructionUser::getId, userIdList).list()
.stream().collect(Collectors.groupingBy(BusConstructionUser::getId));
if (CollUtil.isEmpty(userMap)) {
return;
}
// 设置响应头
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
// 遍历所有用户文件
for (BusQuestionUserAnswer questionUserAnswer : questionUserAnswerList) {
String file = questionUserAnswer.getFile();
if (StringUtils.isBlank(file)) {
continue;
}
Long userId = questionUserAnswer.getUserId();
BusConstructionUser constructionUser = userMap.get(userId).get(0);
String userFolder = constructionUser.getUserName() + "-" + constructionUser.getId() + "/";
// 写入个人文件夹条目
zos.putNextEntry(new ZipEntry(userFolder));
zos.closeEntry();
List<Long> ossIdList = Arrays.stream(file.split(",")).map(Long::parseLong).toList();
List<SysOssVo> ossVoList = ossService.listByIds(ossIdList);
// 遍历用户文件
for (SysOssVo ossVo : ossVoList) {
// 获取文件输入流
OssClient storage = OssFactory.instance(ossVo.getService());
String path = ossVo.getUrl();
try (InputStream is = storage.getObjectContent(path)) {
// 从路径中提取文件名(可以根据业务规则调整)
String fileName = userFolder + "/" + ossVo.getOriginalName();
zos.putNextEntry(new ZipEntry(fileName));
byte[] buffer = new byte[4096];
int len;
while ((len = is.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
} catch (IOException e) {
// 针对单个文件处理异常,可以选择记录日志或终止处理
throw new OssException("处理文件[" + path + "]失败,错误信息: " + e.getMessage());
}
}
}
zos.finish();
} catch (IOException e) {
throw new OssException("生成ZIP文件失败错误信息: " + e.getMessage());
}
}
} }

View File

@ -0,0 +1,225 @@
package org.dromara.safety.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.project.service.IBusProjectService;
import org.dromara.safety.domain.BusQuestionsCategory;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryCreateReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryQueryReq;
import org.dromara.safety.domain.req.questionscategory.QuestionsCategoryUpdateReq;
import org.dromara.safety.domain.vo.BusQuestionsCategoryVo;
import org.dromara.safety.mapper.BusQuestionsCategoryMapper;
import org.dromara.safety.service.IBusQuestionsCategoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* 题库类别Service业务层处理
*
* @author lcj
* @date 2025-04-15
*/
@Service
public class BusQuestionsCategoryServiceImpl extends ServiceImpl<BusQuestionsCategoryMapper, BusQuestionsCategory>
implements IBusQuestionsCategoryService {
@Resource
private IBusProjectService projectService;
/**
* 查询题库类别
*
* @param id 主键
* @return 题库类别
*/
@Override
public BusQuestionsCategoryVo queryById(Long id) {
BusQuestionsCategory questionsCategory = this.getById(id);
if (questionsCategory == null) {
throw new ServiceException("题库类别信息不存在", HttpStatus.NOT_FOUND);
}
return this.getVo(questionsCategory);
}
/**
* 分页查询题库类别列表
*
* @param req 查询条件
* @param pageQuery 分页参数
* @return 题库类别分页列表
*/
@Override
public TableDataInfo<BusQuestionsCategoryVo> queryPageList(QuestionsCategoryQueryReq req, PageQuery pageQuery) {
Page<BusQuestionsCategory> result = this.page(pageQuery.build(), buildQueryWrapper(req));
return TableDataInfo.build(this.getVoPage(result));
}
/**
* 查询符合条件的题库类别列表
*
* @param req 查询条件
* @return 题库类别列表
*/
@Override
public List<BusQuestionsCategoryVo> queryList(QuestionsCategoryQueryReq req) {
LambdaQueryWrapper<BusQuestionsCategory> lqw = this.buildQueryWrapper(req);
return this.list(lqw).stream().map(this::getVo).toList();
}
/**
* 新增题库类别
*
* @param req 题库类别
* @return 新增题库类别主键
*/
@Override
public Long insertByBo(QuestionsCategoryCreateReq req) {
// 将实体类和 DTO 进行转换
BusQuestionsCategory questionsCategory = new BusQuestionsCategory();
BeanUtils.copyProperties(req, questionsCategory);
// 数据校验
validEntityBeforeSave(questionsCategory, true);
// 写入数据库
boolean save = this.save(questionsCategory);
if (!save) {
throw new ServiceException("新增题库类别失败,数据库异常", HttpStatus.ERROR);
}
// 返回新写入的数据
return questionsCategory.getId();
}
/**
* 修改题库类别
*
* @param req 题库类别
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(QuestionsCategoryUpdateReq req) {
// 将实体类和 DTO 进行转换
BusQuestionsCategory questionsCategory = new BusQuestionsCategory();
BeanUtils.copyProperties(req, questionsCategory);
// 数据校验
validEntityBeforeSave(questionsCategory, false);
// 判断是否存在
BusQuestionsCategory oldQuestionsCategory = this.getById(questionsCategory.getId());
if (oldQuestionsCategory == null) {
throw new ServiceException("修改题库类别失败,数据不存在", HttpStatus.NOT_FOUND);
}
// 操作数据库
return this.updateById(questionsCategory);
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(BusQuestionsCategory entity, Boolean create) {
// TODO 做一些数据校验,如唯一约束
Long projectId = entity.getProjectId();
if (create) {
if (projectId == null) {
throw new ServiceException("项目id不能为空", HttpStatus.BAD_REQUEST);
}
}
if (projectId != null && projectService.getById(projectId) == null) {
throw new ServiceException("对应项目不存在", HttpStatus.NOT_FOUND);
}
}
/**
* 校验并批量删除题库类别信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
Long userId = LoginHelper.getUserId();
List<BusQuestionsCategory> questionsCategoryList = this.listByIds(ids);
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
List<Long> projectId = questionsCategoryList.stream().map(BusQuestionsCategory::getProjectId).toList();
projectService.validAuth(projectId, userId);
}
return this.removeBatchByIds(ids);
}
/**
* 获取题库类别视图对象
*
* @param questionsCategory 题库类别对象
* @return 题库类别视图对象
*/
@Override
public BusQuestionsCategoryVo getVo(BusQuestionsCategory questionsCategory) {
// 对象转封装类
BusQuestionsCategoryVo questionsCategoryVo = new BusQuestionsCategoryVo();
if (questionsCategory == null) {
return questionsCategoryVo;
}
BeanUtils.copyProperties(questionsCategory, questionsCategoryVo);
return questionsCategoryVo;
}
/**
* 获取题库类别查询条件封装
*
* @param req 题库类别查询条件
* @return 题库类别查询条件封装
*/
@Override
public LambdaQueryWrapper<BusQuestionsCategory> buildQueryWrapper(QuestionsCategoryQueryReq req) {
LambdaQueryWrapper<BusQuestionsCategory> lqw = new LambdaQueryWrapper<>();
if (req == null) {
return lqw;
}
Long projectId = req.getProjectId();
String categoryName = req.getCategoryName();
// 模糊查询
lqw.like(StringUtils.isNotBlank(categoryName), BusQuestionsCategory::getCategoryName, categoryName);
// 精确查询
lqw.eq(ObjectUtils.isNotEmpty(projectId), BusQuestionsCategory::getProjectId, projectId);
return lqw;
}
/**
* 获取题库类别分页对象视图
*
* @param questionsCategoryPage 题库类别分页对象
* @return 题库类别分页对象视图
*/
@Override
public Page<BusQuestionsCategoryVo> getVoPage(Page<BusQuestionsCategory> questionsCategoryPage) {
// 获取分页数据
List<BusQuestionsCategory> questionsCategoryList = questionsCategoryPage.getRecords();
// 添加分页信息
Page<BusQuestionsCategoryVo> questionsCategoryVoPage = new Page<>(
questionsCategoryPage.getCurrent(),
questionsCategoryPage.getSize(),
questionsCategoryPage.getTotal()
);
if (CollUtil.isEmpty(questionsCategoryList)) {
return questionsCategoryVoPage;
}
// 对象列表 => 封装对象列表
List<BusQuestionsCategoryVo> questionsCategoryVoList = questionsCategoryList.stream().map(this::getVo).toList();
questionsCategoryVoPage.setRecords(questionsCategoryVoList);
return questionsCategoryVoPage;
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.safety.mapper.BusQuestionsCategoryMapper">
</mapper>

View File

@ -678,3 +678,15 @@ CREATE TABLE `bus_document_safety_meeting`
INDEX `idx_pid` (`pid` ASC) USING BTREE, INDEX `idx_pid` (`pid` ASC) USING BTREE,
INDEX `idx_project_id` (`project_id` ASC) USING BTREE INDEX `idx_project_id` (`project_id` ASC) USING BTREE
) comment = '安全会议纪要' COLLATE = utf8mb4_unicode_ci; ) comment = '安全会议纪要' COLLATE = utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `bus_questions_category`;
CREATE TABLE `bus_questions_category`
(
`id` bigint not null auto_increment comment '主键id',
`project_id` bigint not null comment '项目id',
`category_name` varchar(255) not null comment '题库类别',
`create_time` datetime default CURRENT_TIMESTAMP null comment '创建时间',
`update_time` datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_project_id` (`project_id` ASC) USING BTREE
) comment = '题库类别' COLLATE = utf8mb4_unicode_ci;