Explorar o código

feat:新增上传图片调用python接口模型分析接口

zdz hai 3 semanas
pai
achega
b61fc93ed9

+ 32 - 2
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineralanalysistask/controller/MineralAnalysisTaskController.java

@@ -28,10 +28,13 @@ import com.github.jfcloud.common.log.annotation.SysLog;
 import com.github.jfcloud.web.controller.JfcloudRestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -41,13 +44,15 @@ import java.util.List;
  * @date 2025-12-01 16:07:52
  */
 @RestController
-@RequestMapping("/mineralanalysistask")
-@Tag(description = "mineralanalysistask", name = "矿石分析记录表管理")
+@RequestMapping("/mineralanalysis")
+@Tag(description = "/mineralanalysis", name = "矿石分析记录表管理")
 public class MineralAnalysisTaskController extends JfcloudRestController<MineralAnalysisTask, MineralAnalysisTaskDTO, MineralAnalysisTaskService, MineralAnalysisTaskMapper> {
 
     public MineralAnalysisTaskController(MineralAnalysisTaskService service) {
         super(service);
     }
+    @Autowired
+    private MineralAnalysisTaskService mineralAnalysisTaskService;
 
 
     /**
@@ -166,4 +171,29 @@ public class MineralAnalysisTaskController extends JfcloudRestController<Mineral
     public void export(@RequestBody MineralAnalysisTaskDTO mineralAnalysisTask) {
         super.export(mineralAnalysisTask);
     }
+
+    /**
+     * 上传图片AI大模型分析
+     * @param file
+     * @param modelVersion
+     * @param confThreshold
+     * @param precisionMode
+     * @return
+     */
+    @Operation(summary = "上传分析", description = "上传图片并调用Python YOLO模型分析")
+    @PostMapping(value = "/analyze", consumes = "multipart/form-data")
+    public R<MineralAnalysisTask> analyzeMineral(
+            @RequestPart("file") MultipartFile file,
+            @RequestParam(value = "modelVersion", defaultValue = "1") String modelVersion,
+            @RequestParam(value = "confThreshold", defaultValue = "0.5") BigDecimal confThreshold,
+            @RequestParam(value = "precisionMode", defaultValue = "standard") String precisionMode
+    ) {
+        try {
+            MineralAnalysisTask task = mineralAnalysisTaskService.analyzeImage(file, modelVersion, confThreshold, precisionMode);
+            return R.ok(task);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.failed("AI分析失败: " + e.getMessage());
+        }
+    }
 }

+ 11 - 1
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineralanalysistask/entity/MineralAnalysisTask.java

@@ -17,8 +17,10 @@
 
 package com.github.jfcloud.aigc.mineralanalysistask.entity;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.github.jfcloud.aigc.mineraldetectiondetail.entity.MineralDetectionDetail;
 import com.github.jfcloud.common.core.base.JfcloudBaseEntity;
 import com.github.jfcloud.common.core.base.JfcloudProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -82,7 +84,7 @@ public class MineralAnalysisTask extends JfcloudBaseEntity<MineralAnalysisTask>
      * 检测精度(UI下拉框)
      */
 
-    @Schema(description = "检测精度(UI下拉框)")
+    @Schema(description = "检测精度(UI下拉框)high或者standard")
     @JfcloudProperty("检测精度(UI下拉框)")
     private String precisionMode;
     /**
@@ -120,4 +122,12 @@ public class MineralAnalysisTask extends JfcloudBaseEntity<MineralAnalysisTask>
     @Schema(description = "分析完成时间")
     @JfcloudProperty("分析完成时间")
     private LocalDateTime finishTime;
+
+    /**
+     *
+     * 关联的识别详情列表 (exist = false 表示该字段不在数据库表中)
+     */
+    @Schema(description = "识别详情列表")
+    @TableField(exist = false)
+    private MineralDetectionDetail details;
 }

+ 4 - 0
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineralanalysistask/service/MineralAnalysisTaskService.java

@@ -20,6 +20,9 @@ package com.github.jfcloud.aigc.mineralanalysistask.service;
 import com.github.jfcloud.aigc.mineralanalysistask.dto.MineralAnalysisTaskDTO;
 import com.github.jfcloud.aigc.mineralanalysistask.entity.MineralAnalysisTask;
 import com.github.jfcloud.common.data.service.JfcloudBaseService;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.math.BigDecimal;
 
 /**
  * 矿石分析记录表
@@ -29,4 +32,5 @@ import com.github.jfcloud.common.data.service.JfcloudBaseService;
  */
 public interface MineralAnalysisTaskService extends JfcloudBaseService<MineralAnalysisTaskDTO, MineralAnalysisTask> {
 
+    MineralAnalysisTask analyzeImage(MultipartFile file, String modelVersion, BigDecimal confThreshold, String precisionMode);
 }

+ 317 - 17
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineralanalysistask/service/impl/MineralAnalysisTaskServiceImpl.java

@@ -1,28 +1,41 @@
-/*
- *    Copyright (c) 2018-2025, jackzhou All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * Neither the name of the jfcloud.vip developer nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * Author: jackzhou (i_amzxj@163.com)
- */
 package com.github.jfcloud.aigc.mineralanalysistask.service.impl;
 
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import com.github.jfcloud.aigc.mineralanalysistask.dto.MineralAnalysisTaskDTO;
 import com.github.jfcloud.aigc.mineralanalysistask.entity.MineralAnalysisTask;
 import com.github.jfcloud.aigc.mineralanalysistask.mapper.MineralAnalysisTaskMapper;
 import com.github.jfcloud.aigc.mineralanalysistask.service.MineralAnalysisTaskService;
+import com.github.jfcloud.aigc.mineraldetectiondetail.entity.MineralDetectionDetail;
+import com.github.jfcloud.aigc.mineraldetectiondetail.service.MineralDetectionDetailService;
 import com.github.jfcloud.common.data.service.impl.JfcloudBaseServiceImpl;
+import com.github.jfcloud.common.file.core.FileTemplate;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
 
 /**
  * 矿石分析记录表
@@ -30,7 +43,294 @@ import org.springframework.stereotype.Service;
  * @author jackzhou
  * @date 2025-12-01 16:07:52
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class MineralAnalysisTaskServiceImpl extends JfcloudBaseServiceImpl<MineralAnalysisTaskMapper, MineralAnalysisTaskDTO, MineralAnalysisTask> implements MineralAnalysisTaskService {
-}
+    private final RestTemplate restTemplate;
+    private final MineralDetectionDetailService detailService;
+    private final FileTemplate fileTemplate;
+
+    //从YML读取Python服务配置
+    @Value("${python.api.base-url}")
+    private String pythonBaseUrl;
+
+    @Value("${python.api.image-endpoint}")
+    private String imageEndpoint;
+
+    @Value("${file.upload.bucket:aigc}")
+    private String uploadBucket;
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MineralAnalysisTask analyzeImage(MultipartFile file, String modelVersion, BigDecimal confThreshold, String precisionMode) {
+
+        //初始化任务记录
+        String taskCode = "TASK-" + IdUtil.getSnowflakeNextIdStr();
+        MineralAnalysisTask task = new MineralAnalysisTask();
+        task.setTaskCode(taskCode);
+        task.setModelVersion(modelVersion);
+        task.setConfThreshold(confThreshold);
+        task.setPrecisionMode(precisionMode);
+        task.setStatus("0"); // 0-进行中
+
+
+        String originalFileName = file.getOriginalFilename();
+        String localFileName = IdUtil.simpleUUID() + "." + FileNameUtil.getSuffix(originalFileName);
+
+        File localFile = new File(System.getProperty("user.dir"), localFileName);
+        try {
+            file.transferTo(localFile);
+            fileTemplate.putObject(uploadBucket, localFileName, Files.newInputStream(localFile.toPath()));
+            task.setImage(String.format("/admin/sys-file/%s/%s", uploadBucket, localFileName));
+            //先保存一次任务,获取ID
+            this.save(task);
+        } catch (Exception e) {
+            log.error("文件保存失败", e);
+            throw new RuntimeException("文件保存失败");
+        }
+
+        try {
+            //构建请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+            //构建请求体
+            MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
+            body.add("file", new FileSystemResource(localFile));
+
+            //传参给Python
+            body.add("modelVersion", modelVersion);   // 传模型版本 (1, 2, 3)
+            body.add("confThreshold", confThreshold); // 传置信度
+            body.add("precisionMode", precisionMode); // 传精度模式 (standard/high)
+
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
+
+            //拼接完整的URL
+            String fullUrl = pythonBaseUrl + imageEndpoint;
+            log.info("调用 Python 接口: {}, 参数: model={}, conf={}, precision={}", fullUrl, modelVersion, confThreshold, precisionMode);
+
+            ResponseEntity<String> response = restTemplate.postForEntity(fullUrl, requestEntity, String.class);
+
+            if (!response.getStatusCode().is2xxSuccessful()) {
+                throw new RuntimeException("Python接口返回错误码: " + response.getStatusCode());
+            }
+
+            //解析Python返回的JSON
+            String respBody = response.getBody();
+            log.info("Python 返回结果: {}", respBody);
+
+            JSONObject jsonRes = JSONUtil.parseObj(respBody);
+            JSONArray resultsArray = jsonRes.getJSONArray("results");
+
+            if (resultsArray == null || resultsArray.isEmpty()) {
+                throw new RuntimeException("未识别到任何结果");
+            }
+
+            //取第一个结果
+            JSONObject firstResult = resultsArray.getJSONObject(0);
+            String resultUrl = firstResult.getStr("saved_path"); // /results/pred_0_xxx.jpg
+            JSONArray classes = firstResult.getJSONArray("classes");
+            JSONArray confidences = firstResult.getJSONArray("confidence");
+
+            //更新主任务表 (MineralAnalysisTask)
+            //Python返回的是绝对路径
+            File resultFile = new File(resultUrl);
+            if (!resultFile.exists()) {
+                throw new RuntimeException("识别文件不存在" + resultUrl);
+            }
+
+            String resultFileName = IdUtil.simpleUUID() + "." + FileNameUtil.getSuffix(resultFile);
+            fileTemplate.putObject(uploadBucket, resultFileName, Files.newInputStream(resultFile.toPath()));
+            task.setResultImage(String.format("/admin/sys-file/%s/%s", uploadBucket, resultFileName));
+
+            //将["石英", "方解石"] 转为 "石英, 方解石"存入summary
+            List<String> classList = classes.toList(String.class);
+            String summary = String.join(",", classList);
+            task.setAiSummary(summary);
+
+            task.setStatus("1"); // 1-成功
+            task.setFinishTime(LocalDateTime.now());
+
+            this.updateById(task);
+            //插入详情表(MineralDetectionDetail)
+            //循环索引匹配class和confidence
+            List<MineralDetectionDetail> details = new ArrayList<>();
+
+            //循环索引匹配class和confidence
+            if (classes != null && !classes.isEmpty()) {
+
+                for (int i = 0; i < classes.size(); i++) {
+                    MineralDetectionDetail detail = new MineralDetectionDetail();
+                    detail.setTaskId(task.getId());
+
+                    // 获取当前索引的矿石名称
+                    String mineralType = classes.getStr(i);
+                    detail.setMineralType(mineralType);
+
+                    //根据识别名称自动生成写死的模拟数据
+                    switch (mineralType) {
+                        case "蓝铜矿": // Azurite
+                            detail.setMineralContent("蓝铜矿85%、孔雀石10%、褐铁矿5%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("粗粒");
+                            detail.setMineralStructure("短柱状或板状晶体");
+                            detail.setCollectionLocation("安徽省·池州市·六峰山");
+                            break;
+                        case "重晶石": // Baryte
+                            detail.setMineralContent("硫酸钡96%、锶2%、钙1%、微量杂质1%");
+                            detail.setWeatheringDegree("II 轻度风化");
+                            detail.setGrainSize("中粗粒");
+                            detail.setMineralStructure("板状晶簇或块状");
+                            detail.setCollectionLocation("贵州省·天柱县·大河边重晶石矿");
+                            break;
+                        case "绿柱石": // Beryl
+                            detail.setMineralContent("绿柱石92%、长石5%、石英3%");
+                            detail.setWeatheringDegree("0 未风化");
+                            detail.setGrainSize("巨晶");
+                            detail.setMineralStructure("六方柱状晶体");
+                            detail.setCollectionLocation("新疆·阿勒泰地区·可可托海");
+                            break;
+                        case "方解石": // Calcite
+                            detail.setMineralContent("方解石98%、白云石1%、黄铁矿1%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("粗粒");
+                            detail.setMineralStructure("菱面体晶簇");
+                            detail.setCollectionLocation("湖南省·郴州市·香花岭");
+                            break;
+                        case "白铅矿": // Cerussite
+                            detail.setMineralContent("白铅矿80%、方铅矿10%、褐铁矿10%");
+                            detail.setWeatheringDegree("III 中度风化");
+                            detail.setGrainSize("细粒");
+                            detail.setMineralStructure("致密块状或网状");
+                            detail.setCollectionLocation("云南省·贡山县·铅锌矿带");
+                            break;
+                        case "铜": // Copper (自然铜)
+                        case "自然铜":
+                            detail.setMineralContent("自然铜95%、赤铜矿3%、孔雀石2%");
+                            detail.setWeatheringDegree("II 轻度风化");
+                            detail.setGrainSize("细粒");
+                            detail.setMineralStructure("树枝状或不规则块状");
+                            detail.setCollectionLocation("湖北省·大冶市·铜绿山");
+                            break;
+                        case "萤石": // Fluorite
+                            detail.setMineralContent("氟化钙95%、石英3%、重晶石2%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("粗粒");
+                            detail.setMineralStructure("立方体晶簇");
+                            detail.setCollectionLocation("浙江省·武义县·杨家矿区");
+                            break;
+                        case "石膏": // Gypsum
+                            detail.setMineralContent("二水石膏96%、硬石膏2%、粘土矿物2%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("纤维状");
+                            detail.setMineralStructure("纤维状集合体");
+                            detail.setCollectionLocation("湖北省·应城市·石膏矿");
+                            break;
+                        case "赤铁矿": // Hematite
+                            detail.setMineralContent("赤铁矿90%、磁铁矿5%、石英5%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("隐晶质");
+                            detail.setMineralStructure("肾状或鲕状构造");
+                            detail.setCollectionLocation("河北省·张家口市·宣化");
+                            break;
+                        case "孔雀石": // Malachite
+                            detail.setMineralContent("孔雀石95%、赤铜矿3%、褐铁矿2%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("隐晶质");
+                            detail.setMineralStructure("葡萄状或钟乳状结构");
+                            detail.setCollectionLocation("广东省·阳春市·石录铜矿");
+                            break;
+                        case "黄铁矿": // Pyrite
+                            detail.setMineralContent("黄铁矿90%、石英8%、其他硫化物2%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("中粗粒");
+                            detail.setMineralStructure("立方体或五角十二面体");
+                            detail.setCollectionLocation("安徽省·马鞍山市·南山矿");
+                            break;
+                        case "磷氯铅矿": // Pyromorphite
+                            detail.setMineralContent("磷氯铅矿92%、褐铁矿5%、石英3%");
+                            detail.setWeatheringDegree("0 未风化");
+                            detail.setGrainSize("细粒");
+                            detail.setMineralStructure("六方柱状晶簇");
+                            detail.setCollectionLocation("广西·桂林市·道坪铅锌矿");
+                            break;
+                        case "石英": // Quartz
+                            detail.setMineralContent("二氧化硅99.5%、微量杂质0.5%");
+                            detail.setWeatheringDegree("0 未风化");
+                            detail.setGrainSize("粗粒");
+                            detail.setMineralStructure("晶质结构");
+                            detail.setCollectionLocation("江苏省·东海县·水晶乡");
+                            break;
+                        case "菱锌矿": // Smithsonite
+                            detail.setMineralContent("菱锌矿85%、褐铁矿10%、方解石5%");
+                            detail.setWeatheringDegree("II 轻度风化");
+                            detail.setGrainSize("隐晶质");
+                            detail.setMineralStructure("葡萄状集合体");
+                            detail.setCollectionLocation("云南省·兰坪县·金顶铅锌矿");
+                            break;
+                        case "钼铅矿": // Wulfenite
+                            detail.setMineralContent("钼铅矿90%、方解石5%、褐铁矿5%");
+                            detail.setWeatheringDegree("I 微风化");
+                            detail.setGrainSize("粗粒");
+                            detail.setMineralStructure("薄板状晶体");
+                            detail.setCollectionLocation("新疆·巴州·库鲁克塔格");
+                            break;
+                        default:
+                            // 默认值 (或识别出未知矿石时使用)
+                            detail.setMineralContent("未知矿物成分");
+                            detail.setWeatheringDegree("未知");
+                            detail.setGrainSize("未知");
+                            detail.setMineralStructure("不规则块状");
+                            detail.setCollectionLocation("待定区域");
+                            break;
+                    }
+                    //获取当前索引的置信度
+                    //判断数组长度防止越界
+                    if (confidences != null && i < confidences.size()) {
+                        String confStr = confidences.getStr(i);
+                        detail.setConfidence(new BigDecimal(confStr));
+                    } else {
+                        detail.setConfidence(confThreshold);
+                    }
+
+                    details.add(detail);
+                }
+                // 批量保存到数据库
+                detailService.saveBatch(details);
+            }else {
+                // 【新增逻辑】如果识别结果为空,生成一条默认的“未识别”记录,防止前端报错并展示默认信息
+                MineralDetectionDetail detail = new MineralDetectionDetail();
+                detail.setTaskId(task.getId());
+                detail.setMineralType("未识别");
+
+                // 使用你指定的默认值
+                detail.setMineralContent("未知矿物成分");
+                detail.setWeatheringDegree("未知");
+                detail.setGrainSize("未知");
+                detail.setMineralStructure("不规则块状");
+                detail.setCollectionLocation("待定区域");
+
+                // 置信度设为0,表示未检测到
+                detail.setConfidence(BigDecimal.ZERO);
+
+                details.add(detail);
+                //保存到数据库
+                detailService.saveBatch(details);
+            }
+            if (details != null && !details.isEmpty()) {
+                task.setDetails(details.get(0));
+            }
+
+            return task;
+
+        } catch (Exception e) {
+            log.error("AI分析过程异常", e);
+            task.setStatus("2"); // 2-失败
+            task.setErrorMsg(e.getMessage());
+            this.updateById(task);
+            throw new RuntimeException("分析失败: " + e.getMessage());
+        } finally {
+            FileUtil.del(localFile);
+        }
+    }
+}

+ 2 - 2
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineraldetectiondetail/controller/MineralDetectionDetailController.java

@@ -41,8 +41,8 @@ import java.util.List;
  * @date 2025-12-01 16:39:42
  */
 @RestController
-@RequestMapping("/mineraldetectiondetail")
-@Tag(description = "mineraldetectiondetail", name = "识别结果详情表管理")
+@RequestMapping("/mineraldetection")
+@Tag(description = "/mineraldetection", name = "识别结果详情表管理")
 public class MineralDetectionDetailController extends JfcloudRestController<MineralDetectionDetail, MineralDetectionDetailDTO, MineralDetectionDetailService, MineralDetectionDetailMapper> {
 
     public MineralDetectionDetailController(MineralDetectionDetailService service) {

+ 35 - 0
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineraldetectiondetail/dto/MineralDetectionDetailDTO.java

@@ -60,4 +60,39 @@ public class MineralDetectionDetailDTO extends JfcloudBaseDTO {
     @Schema(description = "该物体的置信度")
     @JfcloudProperty("该物体的置信度")
     private BigDecimal confidence;
+
+    /**
+     * 主要矿物组成
+     */
+    @Schema(description = "主要矿物组成")
+    @JfcloudProperty("主要矿物组成")
+    private String mineralContent;
+
+    /**
+     * 风化程度
+     */
+    @Schema(description = "风化程度")
+    @JfcloudProperty("风化程度")
+    private String weatheringDegree;
+
+    /**
+     * 颗粒大小
+     */
+    @Schema(description = "颗粒大小")
+    @JfcloudProperty("颗粒大小")
+    private String grainSize;
+
+    /**
+     * 矿物结构
+     */
+    @Schema(description = "矿物结构")
+    @JfcloudProperty("矿物结构")
+    private String mineralStructure;
+
+    /**
+     * 采集地点
+     */
+    @Schema(description = "采集地点")
+    @JfcloudProperty("采集地点")
+    private String collectionLocation;
 }

+ 35 - 0
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/aigc/mineraldetectiondetail/entity/MineralDetectionDetail.java

@@ -70,4 +70,39 @@ public class MineralDetectionDetail extends JfcloudBaseEntity<MineralDetectionDe
     @Schema(description = "该物体的置信度")
     @JfcloudProperty("该物体的置信度")
     private BigDecimal confidence;
+
+    /**
+     * 主要矿物组成
+     */
+    @Schema(description = "主要矿物组成")
+    @JfcloudProperty("主要矿物组成")
+    private String mineralContent;
+
+    /**
+     * 风化程度
+     */
+    @Schema(description = "风化程度")
+    @JfcloudProperty("风化程度")
+    private String weatheringDegree;
+
+    /**
+     * 颗粒大小
+     */
+    @Schema(description = "颗粒大小")
+    @JfcloudProperty("颗粒大小")
+    private String grainSize;
+
+    /**
+     * 矿物结构
+     */
+    @Schema(description = "矿物结构")
+    @JfcloudProperty("矿物结构")
+    private String mineralStructure;
+
+    /**
+     * 采集地点
+     */
+    @Schema(description = "采集地点")
+    @JfcloudProperty("采集地点")
+    private String collectionLocation;
 }

+ 17 - 0
jfcloud-aigc-biz/src/main/java/com/github/jfcloud/common/config/RestTemplateConfig.java

@@ -0,0 +1,17 @@
+package com.github.jfcloud.common.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfig {
+    @Bean
+    public RestTemplate restTemplate() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(30000); // 连接超时 30秒
+        factory.setReadTimeout(60000);    // 读取超时 60秒 (AI推理可能较慢)
+        return new RestTemplate(factory);
+    }
+}

+ 8 - 0
jfcloud-aigc-biz/src/main/resources/bootstrap.yml

@@ -3,3 +3,11 @@ server:
 spring:
   profiles:
     active: dev
+
+#Python AI 服务地址
+python:
+  api:
+    base-url: http://127.0.0.1:5000
+    # 定义两个具体的接口路径
+    image-endpoint: /predict/image
+    video-endpoint: /predict/video