package com.mydreamjob.assessment; import com.mydreamjob.assessment.dto.AssessmentAttemptResponse; import com.mydreamjob.assessment.dto.AssessmentHistoryItem; import com.mydreamjob.assessment.dto.AssessmentQuestionResponse; import com.mydreamjob.assessment.dto.AssessmentResultResponse; import com.mydreamjob.assessment.dto.AssessmentTypeResponse; import com.mydreamjob.assessment.dto.CreateAssessmentAttemptRequest; import com.mydreamjob.assessment.dto.SaveAssessmentAnswersRequest; import com.mydreamjob.audit.AuditContext; import com.mydreamjob.common.api.ApiResponse; import com.mydreamjob.common.api.PageResponse; import com.mydreamjob.common.logging.RequestIdAccessor; import com.mydreamjob.security.CurrentUserAccessor; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; import java.util.List; /** * RIASEC 职业兴趣测评接口(V34/V35)。 * *

身份仅取自 JWT,不接受 userId 参数。类型与题目对任意已登录用户开放; * 答卷创建/详情/保存/提交/结果/历史仅 STUDENT 且仅限本人。COMPANY/ADMIN 操作答卷返回 403。

*/ @Tag(name = "RIASEC 职业兴趣测评", description = "测评类型、题目、答卷、保存答案、提交与结果查询") @RestController @RequiredArgsConstructor public class AssessmentController { private final AssessmentCatalogService catalogService; private final AssessmentAttemptService attemptService; private final AssessmentResultService resultService; private final CurrentUserAccessor currentUserAccessor; private final RequestIdAccessor requestIdAccessor; @Operation(summary = "测评类型列表", description = "返回全部 ACTIVE 测评定义。任意已登录用户可访问;未登录 401。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录") }) @GetMapping("/assessments/types") public ApiResponse> listTypes(HttpServletRequest request) { return ApiResponse.ok(catalogService.listTypes(), requestIdAccessor.get(request)); } @Operation(summary = "题目列表", description = "返回指定测评的 ACTIVE 题目,隐藏维度/reverseScored/source/版本等内部字段。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "测评类型不存在(9701)") }) @GetMapping("/assessments/{type}/questions") public ApiResponse> listQuestions( @PathVariable String type, HttpServletRequest request) { return ApiResponse.ok(catalogService.listQuestions(type), requestIdAccessor.get(request)); } @Operation(summary = "创建答卷", description = "照六维各抽 5 题共 30 道并持久化,刷新不重抽。幂等键重放返回原答卷;" + "同键不同定义 409/9709;已有活跃答卷 409/9708。仅 STUDENT。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非 STUDENT(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "测评类型不存在(9701)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "活跃答卷/幂等冲突(9708/9709)") }) @PostMapping("/assessments/attempts") public ApiResponse createAttempt( @Valid @RequestBody CreateAssessmentAttemptRequest req, HttpServletRequest request) { return ApiResponse.ok(attemptService.create(currentUserAccessor.requireUserId(), req, AuditContext.from(request)), requestIdAccessor.get(request)); } @Operation(summary = "答卷详情", description = "返回本次抽中的 30 道题(固定顺序)、已保存答案、进度与当前状态。仅本人可访问;非 STUDENT 403。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非本人或非 STUDENT(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "答卷不存在(9702)") }) @GetMapping("/assessments/attempts/{attemptId}") public ApiResponse getAttempt( @PathVariable Long attemptId, HttpServletRequest request) { return ApiResponse.ok(attemptService.get(currentUserAccessor.requireUserId(), attemptId), requestIdAccessor.get(request)); } @Operation(summary = "保存答案", description = "单题或批量保存,重复保存 upsert;value 1-5;题目必须属于本答卷;" + "版本冲突 409/9707;首次保存后状态置为 IN_PROGRESS。仅本人。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非本人或非 STUDENT(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "答卷不存在(9702)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "非法状态/并发(9703/9707)") }) @PatchMapping("/assessments/attempts/{attemptId}/answers") public ApiResponse saveAnswers( @PathVariable Long attemptId, @Valid @RequestBody SaveAssessmentAnswersRequest req, HttpServletRequest request) { return ApiResponse.ok(attemptService.saveAnswers(currentUserAccessor.requireUserId(), attemptId, req, AuditContext.from(request)), requestIdAccessor.get(request)); } @Operation(summary = "提交答卷", description = "30 题全答且每维各 5 题后方可提交(否则 400/9706);提交后生成结果并写审计;" + "重复提交返回原结果,不重复生成与审计。仅本人。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非本人或非 STUDENT(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "答卷不存在(9702)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "非法状态(9703)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "未完成(9706)") }) @PostMapping("/assessments/attempts/{attemptId}/submit") public ApiResponse submitAttempt( @PathVariable Long attemptId, HttpServletRequest request) { return ApiResponse.ok(attemptService.submit(currentUserAccessor.requireUserId(), attemptId, AuditContext.from(request)), requestIdAccessor.get(request)); } @Operation(summary = "查看结果", description = "答卷 COMPLETED 后返回聚合分、类型排名、探索方向。仅本人可访问。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非本人或非 STUDENT(2002)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "答卷不存在(9702)"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "答卷未完成(9703)") }) @GetMapping("/assessments/attempts/{attemptId}/result") public ApiResponse getResult( @PathVariable Long attemptId, HttpServletRequest request) { return ApiResponse.ok(resultService.get(currentUserAccessor.requireUserId(), attemptId), requestIdAccessor.get(request)); } @Operation(summary = "我的测评历史", description = "分页返回当前学生的全部答卷与结果概要。仅 STUDENT 本人可访问。") @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "OK"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "未登录"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "非 STUDENT(2002)") }) @GetMapping("/assessments/me") public ApiResponse> listMine( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, HttpServletRequest request) { Page result = attemptService.listMine(currentUserAccessor.requireUserId(), page, size); return ApiResponse.ok(PageResponse.of( result.getContent(), result.getTotalElements(), result.getNumber(), result.getSize()), requestIdAccessor.get(request)); } }