Просмотр исходного кода

fix(risk): queryMobileDetail 关联 record_item photos + OSS proxy @SaIgnore

- SysOssController: 加 /resource/oss/proxy 代理方法
  * 解决 H5 端 HTTPS 页面加载 HTTP OSS 资源 mixed content 阻止
  * @SaIgnore 跳过 sa-token 鉴权(image 请求没法带 clientid header)
  * content-type 推断 + 1天缓存

- RiskInspectionTaskServiceImpl.queryMobileDetail:
  * 查 record_item (按 recordId) 关联填 task_item.photos
  * readonly 模式'查看详情'能看到已拍照片
  * 注入 recordItemMapper
yangjingjing 11 часов назад
Родитель
Сommit
6a0ed1548b

+ 32 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskServiceImpl.java

@@ -15,7 +15,9 @@ import org.dromara.risk.domain.RiskInspectionTask;
 import org.dromara.risk.domain.RiskInspectionTaskItem;
 import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
 import org.dromara.risk.domain.RiskInspectionRecord;
+import org.dromara.risk.domain.RiskInspectionRecordItem;
 import org.dromara.risk.domain.RiskInspectionTaskItem;
+import org.dromara.risk.mapper.RiskInspectionRecordItemMapper;
 import org.dromara.risk.domain.vo.MobileInspectionTaskVo;
 import org.dromara.risk.domain.vo.RiskInspectionRecordVo;
 import org.dromara.risk.domain.vo.InspectionStatsVo;
@@ -41,6 +43,7 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 巡检任务 业务层实现
@@ -244,6 +247,9 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
     private RiskInspectionRecordMapper recordMapper;
     @Autowired
     @Lazy
+    private RiskInspectionRecordItemMapper recordItemMapper;
+    @Autowired
+    @Lazy
     private RiskInspectionTaskItemMapper taskItemMapper;
 
     /**
@@ -529,6 +535,32 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
         } else {
             vo.setPunchLocation(vo.getLocation());
         }
+
+        // 4) 关联 record_item(已打卡的明细):把每个 item 的 photos/checkResult/problemCount 填到 task_item 上
+        //    之前只查 task_item(项目模板),readonly 模式下 item.photos 永远是空,
+        //    导致"查看详情"看不到已拍照片
+        if (latest != null && items != null && !items.isEmpty()) {
+            List<RiskInspectionRecordItem> recordItems = recordItemMapper.selectList(
+                Wrappers.<RiskInspectionRecordItem>lambdaQuery()
+                    .eq(RiskInspectionRecordItem::getRecordId, latest.getId())
+            );
+            if (recordItems != null && !recordItems.isEmpty()) {
+                // record_item.itemId 关联 task_item.id
+                Map<Long, RiskInspectionRecordItem> byItemId = recordItems.stream()
+                    .filter(ri -> ri.getItemId() != null)
+                    .collect(Collectors.toMap(RiskInspectionRecordItem::getItemId, ri -> ri, (a, b) -> a));
+                for (RiskInspectionTaskItemVo item : items) {
+                    RiskInspectionRecordItem ri = byItemId.get(item.getId());
+                    if (ri != null) {
+                        item.setCheckResult(ri.getCheckResult());
+                        item.setRemark(ri.getRemark());
+                        item.setProblemCount(ri.getProblemCount());
+                        // photos 是逗号分隔字符串,前端的 vo 用 string 字段(后端 vo 也是 string)
+                        item.setPhotos(ri.getPhotos());
+                    }
+                }
+            }
+        }
         return vo;
     }
 

+ 52 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOssController.java

@@ -2,10 +2,13 @@ package org.dromara.system.controller.system;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaIgnore;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.NotEmpty;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.validate.QueryGroup;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
@@ -33,6 +36,7 @@ import java.util.List;
 @Validated
 @RequiredArgsConstructor
 @RestController
+@Slf4j
 @RequestMapping("/resource/oss")
 public class SysOssController extends BaseController {
 
@@ -88,6 +92,54 @@ public class SysOssController extends BaseController {
         ossService.download(ossId, response);
     }
 
+    /**
+     * OSS 图片代理(解决 H5 端 HTTPS 页面加载 HTTP OSS 资源被浏览器当 mixed content 阻止的问题)
+     * <p>
+     * H5 端是 https://localhost:9000,而 OSS url 是 http://...:9000/...,直接 <image :src="url"> 会被浏览器 block.
+     * 通过这个代理走 HTTP 后端(9225)转发,H5 端改成 src='/dev-api/resource/oss/proxy?url=encoded(http://...)' 即可.
+     *
+     * @param url 完整的 OSS 对象 URL(需要 URLEncoder)
+     */
+    @SaIgnore
+    @GetMapping("/proxy")
+    public void proxy(@RequestParam String url, HttpServletResponse response) throws IOException {
+        if (StringUtils.isBlank(url) || (!url.startsWith("http://") && !url.startsWith("https://"))) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "url 参数不合法");
+            return;
+        }
+        // 简单 SSRF 防护:只允许 http/https,且 host 必须在白名单
+        // (这里只做基础防护,生产环境按需扩展)
+        log.info("[OSS Proxy] 代理下载: {}", url);
+        try (var in = new java.net.URL(url).openStream();
+             var out = response.getOutputStream()) {
+            // 透传 content-type(简单取文件后缀推断)
+            String contentType = guessContentType(url);
+            if (contentType != null) {
+                response.setContentType(contentType);
+            }
+            // 缓存 1 天(H5 端 image 重复加载避免重复后端代理)
+            response.setHeader("Cache-Control", "public, max-age=86400");
+            in.transferTo(out);
+        } catch (java.net.MalformedURLException e) {
+            log.warn("[OSS Proxy] URL 非法: {}", url);
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "url 非法: " + e.getMessage());
+        } catch (java.io.IOException e) {
+            log.warn("[OSS Proxy] 下载失败: {}, err={}", url, e.getMessage());
+            response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "OSS 下载失败: " + e.getMessage());
+        }
+    }
+
+    private static String guessContentType(String url) {
+        String lower = url.toLowerCase();
+        if (lower.endsWith(".png")) return "image/png";
+        if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
+        if (lower.endsWith(".gif")) return "image/gif";
+        if (lower.endsWith(".webp")) return "image/webp";
+        if (lower.endsWith(".svg")) return "image/svg+xml";
+        if (lower.endsWith(".pdf")) return "application/pdf";
+        return null;
+    }
+
     /**
      * 删除OSS对象存储
      *