|
@@ -1,6 +1,7 @@
|
|
|
package com.etotem.cfc.controller;
|
|
package com.etotem.cfc.controller;
|
|
|
|
|
|
|
|
import com.etotem.cfc.common.Result;
|
|
import com.etotem.cfc.common.Result;
|
|
|
|
|
+import com.etotem.cfc.common.AdminRoles;
|
|
|
import com.etotem.cfc.service.PurchaseService;
|
|
import com.etotem.cfc.service.PurchaseService;
|
|
|
import com.etotem.cfc.service.DanReportUploadService;
|
|
import com.etotem.cfc.service.DanReportUploadService;
|
|
|
import com.etotem.cfc.dto.HealthAnalysisResult;
|
|
import com.etotem.cfc.dto.HealthAnalysisResult;
|
|
@@ -29,6 +30,7 @@ import com.etotem.cfc.entity.User;
|
|
|
import com.etotem.cfc.mapper.HealthReportDraftMapper;
|
|
import com.etotem.cfc.mapper.HealthReportDraftMapper;
|
|
|
import com.etotem.cfc.mapper.FamilyMemberMapper;
|
|
import com.etotem.cfc.mapper.FamilyMemberMapper;
|
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
|
|
|
+import com.etotem.cfc.service.UserService;
|
|
|
import com.etotem.cfc.service.FamilyMemberNutritionProfileService;
|
|
import com.etotem.cfc.service.FamilyMemberNutritionProfileService;
|
|
|
import com.etotem.cfc.service.DimensionScoreService;
|
|
import com.etotem.cfc.service.DimensionScoreService;
|
|
|
import com.etotem.cfc.service.FoodRecommendService;
|
|
import com.etotem.cfc.service.FoodRecommendService;
|
|
@@ -171,6 +173,9 @@ public class HealthReportController {
|
|
|
@Resource
|
|
@Resource
|
|
|
private UserMapper userMapper;
|
|
private UserMapper userMapper;
|
|
|
|
|
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private UserService userService;
|
|
|
|
|
+
|
|
|
@Resource
|
|
@Resource
|
|
|
private ReportBlockService reportBlockService;
|
|
private ReportBlockService reportBlockService;
|
|
|
|
|
|
|
@@ -2106,7 +2111,9 @@ public class HealthReportController {
|
|
|
@PostMapping("/report/audit/confirm")
|
|
@PostMapping("/report/audit/confirm")
|
|
|
public Result<Map<String, Object>> auditConfirm(
|
|
public Result<Map<String, Object>> auditConfirm(
|
|
|
@RequestBody Map<String, Object> params,
|
|
@RequestBody Map<String, Object> params,
|
|
|
- @RequestAttribute("userId") Long auditorId) {
|
|
|
|
|
|
|
+ @RequestAttribute("userId") Long auditorId,
|
|
|
|
|
+ @RequestAttribute("role") String role,
|
|
|
|
|
+ @RequestAttribute("roles") List<String> roles) {
|
|
|
Long draftId = ParamUtils.getLong(params.get("draftId"));
|
|
Long draftId = ParamUtils.getLong(params.get("draftId"));
|
|
|
Long subjectId = ParamUtils.getLong(params.get("subjectId"));
|
|
Long subjectId = ParamUtils.getLong(params.get("subjectId"));
|
|
|
|
|
|
|
@@ -2115,7 +2122,9 @@ public class HealthReportController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- Map<String, Object> result = healthReportService.auditConfirm(draftId, subjectId, auditorId);
|
|
|
|
|
|
|
+ // B2-3:admin 判定——平台角色可确认任意草稿;非平台角色仅能确认自己创建的草稿
|
|
|
|
|
+ boolean isAdmin = "admin".equals(role) || AdminRoles.hasAnyPlatformRole(roles);
|
|
|
|
|
+ Map<String, Object> result = healthReportService.auditConfirm(draftId, subjectId, auditorId, isAdmin);
|
|
|
Long confirmedSubjectId = ParamUtils.getLong(result.get("subjectId"));
|
|
Long confirmedSubjectId = ParamUtils.getLong(result.get("subjectId"));
|
|
|
if (confirmedSubjectId != null) {
|
|
if (confirmedSubjectId != null) {
|
|
|
try {
|
|
try {
|
|
@@ -2675,6 +2684,23 @@ public class HealthReportController {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * B2-2 / B2-6:代传 familyId 绑定校验(发生在建草稿/写文件之前,越权不产生副作用)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>口径与 FamilyAccessInterceptor.getEffectiveFamilyIds 一致:平台角色(admin/operator/finance)
|
|
|
|
|
+ * 直接放行;否则 familyId 必位于调用者有效家庭集内(own family / teacher_family_ids /
|
|
|
|
|
+ * family_members 反查 / guide_family 绑定)。</p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true=允许代传该家庭;false=无权(controller 层返回 code=500 "无权为该家庭代传报告")
|
|
|
|
|
+ */
|
|
|
|
|
+ boolean canProxyForFamily(Long userId, String role, List<String> roles, Long familyId) {
|
|
|
|
|
+ if ("admin".equals(role) || AdminRoles.hasAnyPlatformRole(roles)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<Long> effective = userService.getEffectiveFamilyIds(userId);
|
|
|
|
|
+ return effective != null && effective.contains(familyId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 规划师/营养师代用户上传报告(预览模式,仅解析不入库)
|
|
* 规划师/营养师代用户上传报告(预览模式,仅解析不入库)
|
|
|
*
|
|
*
|
|
@@ -2683,8 +2709,17 @@ public class HealthReportController {
|
|
|
* <p>链路:storeFile → createDraft → updateNote(备注) → getFilePath → LangGraph 解析
|
|
* <p>链路:storeFile → createDraft → updateNote(备注) → getFilePath → LangGraph 解析
|
|
|
* (失败回退本地 Java 解析)→ 返回 draftId/payload/成员匹配/提取字段。</p>
|
|
* (失败回退本地 Java 解析)→ 返回 draftId/payload/成员匹配/提取字段。</p>
|
|
|
*
|
|
*
|
|
|
- * <p>权限编排说明:① 角色校验 isProxyUploader;② familyId 非空校验;
|
|
|
|
|
- * ③ 家庭访问拦截器兜底(代传绑定校验由 T29-B2 补充)。</p>
|
|
|
|
|
|
|
+ * <p><b>T29-B2-6 权限编排(三层校验,各层失败返回码):</b>
|
|
|
|
|
+ * <ol>
|
|
|
|
|
+ * <li>① 角色校验 {@link #isProxyUploader}:非 admin/teacher/approved 营养师/approved 规划师
|
|
|
|
|
+ * → <code>Result.error(code=500, "无权限代上传报告,仅超级管理员/规划师/营养师可操作")</code>;</li>
|
|
|
|
|
+ * <li>② familyId 绑定校验 {@link #canProxyForFamily}(B2-2,本方法内):
|
|
|
|
|
+ * 非平台角色且 familyId 不在有效家庭集内
|
|
|
|
|
+ * → <code>Result.error(code=500, "无权为该家庭代传报告")</code>(不建草稿、不写文件);</li>
|
|
|
|
|
+ * <li>③ 家庭访问拦截器兜底(FamilyAccessInterceptor,B2-4 enforcement):
|
|
|
|
|
+ * 越权 → <code>HTTP 403</code>,body <code>{"code":403,"message":"无权访问该家庭数据"}</code>。
|
|
|
|
|
+ * (拦截器在 controller 之前执行,因此②③不会同时触发;②是对拦截器未覆盖路径的纵深防御。)</li>
|
|
|
|
|
+ * </ol></p>
|
|
|
*/
|
|
*/
|
|
|
@Operation(summary = "代上传报告(预览)")
|
|
@Operation(summary = "代上传报告(预览)")
|
|
|
@PostMapping("/report/proxy-parse")
|
|
@PostMapping("/report/proxy-parse")
|
|
@@ -2712,6 +2747,12 @@ public class HealthReportController {
|
|
|
return Result.error("familyId不能为空");
|
|
return Result.error("familyId不能为空");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // B2-2 / B2-6:familyId 绑定校验(发生在建草稿/写文件之前,越权不产生副作用)。
|
|
|
|
|
+ // 拦截器(B2-4)未覆盖或未生效时,此处作纵深防御。
|
|
|
|
|
+ if (!canProxyForFamily(userId, role, roles, familyId)) {
|
|
|
|
|
+ return Result.error("无权为该家庭代传报告");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String contentType = file.getContentType();
|
|
String contentType = file.getContentType();
|
|
|
String originalFilename = file.getOriginalFilename();
|
|
String originalFilename = file.getOriginalFilename();
|
|
|
String lcName = originalFilename != null ? originalFilename.toLowerCase() : "";
|
|
String lcName = originalFilename != null ? originalFilename.toLowerCase() : "";
|
|
@@ -2844,8 +2885,10 @@ public class HealthReportController {
|
|
|
HealthReportDraft draft = healthReportDraftService.getDraftById(draftId);
|
|
HealthReportDraft draft = healthReportDraftService.getDraftById(draftId);
|
|
|
String proxyUploadNote = draft != null ? draft.getRemark() : null;
|
|
String proxyUploadNote = draft != null ? draft.getRemark() : null;
|
|
|
|
|
|
|
|
- // 代上传模式跳过草稿归属校验
|
|
|
|
|
- Map<String, Object> result = healthReportService.auditConfirm(draftId, subjectId, userId);
|
|
|
|
|
|
|
+ // B2-3:草稿归属校验下沉到 auditConfirm——仅创建者本人或平台角色可确认
|
|
|
|
|
+ // (原代传链路"跳过草稿归属校验"的口径已废止:代传草稿只能由代传人本人或 admin 确认)
|
|
|
|
|
+ boolean isAdmin = "admin".equals(role) || AdminRoles.hasAnyPlatformRole(roles);
|
|
|
|
|
+ Map<String, Object> result = healthReportService.auditConfirm(draftId, subjectId, userId, isAdmin);
|
|
|
Long reportId = (Long) result.get("reportId");
|
|
Long reportId = (Long) result.get("reportId");
|
|
|
|
|
|
|
|
// 维护"当前食材"推荐(有则更新,无则添加)
|
|
// 维护"当前食材"推荐(有则更新,无则添加)
|