R1 先行批:question-center 后端(SB 2.7.18,23 单测全绿)+ 建表脚本 + 仓库说明
build / build-push (push) Successful in 51s

This commit is contained in:
chenxing
2026-10-22 10:13:00 +08:00
parent 380df08271
commit 907063a0b6
30 changed files with 2693 additions and 2 deletions
@@ -0,0 +1,141 @@
package com.yunhai.question.controller.api;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.question.common.BizException;
import com.yunhai.question.entity.Questionnaire;
import com.yunhai.question.service.QuestionnaireService;
import com.yunhai.question.service.SchoolAdminGuard;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 问卷管理端(仅校管理员∪超管可建——FC 侧 create 守卫天然把关第一步,本侧登记步同样过守卫)。
*/
@RestController
@RequestMapping("/api/question/questionnaire")
@RequiredArgsConstructor
public class QuestionnaireController {
private final QuestionnaireService questionnaireService;
private final SchoolAdminGuard adminGuard;
public static class RegisterRequest {
@NotBlank(message = "formUuid 不能为空")
public String formUuid;
public String formCode;
@NotBlank(message = "问卷标题不能为空")
public String title;
public Boolean anonymous;
/** JSON 字符串:{description,cover,thanks} */
public String description;
}
public static class DescriptionRequest {
public String description;
}
/** 列表(本校全部未删问卷) */
@GetMapping("/list")
public List<Questionnaire> list(BoaUser user) {
Long schId = adminGuard.requireSchool(user);
return questionnaireService.list(schId);
}
/**
* 第三步绑定登记(幂等三步的收口;重复提交返回既有行)。
*/
@PostMapping
public Questionnaire register(BoaUser user, @Valid @RequestBody RegisterRequest body) {
Long schId = adminGuard.requireSchool(user);
boolean anonymous = Boolean.TRUE.equals(body.anonymous);
return questionnaireService.register(schId, body.formUuid.trim(), body.formCode,
body.title, anonymous, body.description);
}
@GetMapping("/{id}")
public Questionnaire detail(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
return questionnaireService.requireOwned(schId, id);
}
/** 问卷说明/封面/致谢(JSON 文本,仅问卷侧元数据) */
@PostMapping("/{id}/description")
public Questionnaire updateDescription(BoaUser user, @PathVariable Long id,
@RequestBody DescriptionRequest body) {
Long schId = adminGuard.requireSchool(user);
Questionnaire q = questionnaireService.requireOwned(schId, id);
q.setDescription(body == null ? null : body.description);
return questionnaireService.save(q);
}
/** 发布投放:fetchSchema 核验 FC 已发布→钉版+status=1 */
@PostMapping("/{id}/publish")
public Questionnaire publish(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
return questionnaireService.publish(schId, id);
}
/** 结束投放:status=2(FC 侧是否 disable 由前端另行确认调用 FC 端点) */
@PostMapping("/{id}/finish")
public Questionnaire finish(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
return questionnaireService.finish(schId, id);
}
/** 恢复收集 */
@PostMapping("/{id}/reopen")
public Questionnaire reopen(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
return questionnaireService.reopen(schId, id);
}
/** 软删问卷行(FC 表单不动) */
@PostMapping("/{id}/delete")
public Map<String, Object> softDelete(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
questionnaireService.softDelete(schId, id);
Map<String, Object> out = new LinkedHashMap<>();
out.put("deleted", true);
return out;
}
/** 编辑容器打开时刷新 FC name/code 快照并返回详情 */
@GetMapping("/{id}/refresh")
public Questionnaire refresh(BoaUser user, @PathVariable Long id) {
Long schId = adminGuard.requireSchool(user);
Questionnaire q = questionnaireService.requireOwned(schId, id);
return questionnaireService.save(questionnaireService.refreshFromFc(q));
}
/**
* 我的问卷交集:前端把 FC fillable(appCode=question) 的 code 列表 POST 过来,
* 服务端取 ∩ 本校收集中问卷。未登录教师/学生同样可达(无校管理员守卫)。
*/
@PostMapping("/my")
public List<Questionnaire> myFillable(BoaUser user, @RequestBody MyCodesRequest body) {
if (user == null || user.getId() == null) {
throw new BizException("未登录");
}
Integer schIdInt = user.getCurrentRole() != null ? user.getCurrentRole().getSchId() : null;
if (schIdInt == null) {
throw new BizException("当前会话无学校上下文");
}
return questionnaireService.myFillable(schIdInt.longValue(),
body == null ? null : body.codes);
}
public static class MyCodesRequest {
public List<String> codes;
}
}