Przeglądaj źródła

perfect:000.000.008:优化生成图表中的状态更新逻辑

yang yi 1 miesiąc temu
rodzic
commit
ef6a777ece

+ 1 - 5
BI_front/src/common/api/chart/index.js

@@ -48,6 +48,7 @@ export function generateChartByAI(data) {
     formData.set('chartType',data.chartType);
     formData.set('analysisTarget',data.analysisTarget);
     formData.set('name',data.name);
+    formData.set('isAsynchronism',data.isAsynchronism);
     formData.append('file',data.file);
     return request.postForm(
         '/chart/generateChartByAI',
@@ -58,9 +59,4 @@ export function generateChartByAI(data) {
             }
         }
     );
-    // return request({
-    //     url: '/chart/generateChartByAI',
-    //     method: 'post',
-    //     data: data
-    // })
 }

+ 48 - 7
BI_front/src/views/chartManage.vue

@@ -37,20 +37,55 @@
       </el-form-item>
     </el-form>
 
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-popconfirm
+            :disabled="this.ids.length === 0"
+            confirm-button-text="确定"
+            cancel-button-text="取消"
+            :icon="InfoFilled"
+            icon-color="#626AEF"
+            title="确定删除多个分析的图表结果吗"
+            @confirm="handleDelete"
+            @cancel="cancelEvent"
+        >
+          <template #reference>
+            <el-button type="danger" :disabled="this.ids.length === 0">
+              <template #icon>
+                <el-icon><Remove /></el-icon>
+              </template>
+              <template #default>
+                删除
+              </template>
+            </el-button>
+          </template>
+        </el-popconfirm>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
     <el-table v-loading="loading" :data="chartList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="图表ID" align="center" prop="id" />
       <el-table-column label="图表名称" align="center" prop="name" />
       <el-table-column label="分析目标" align="center" prop="analysisTarget" />
       <el-table-column label="图标类型" align="center" prop="chartType" />
-      <!--<el-table-column label="生成的分析结论" align="center" prop="analysisConclusion" />-->
+      <el-table-column label="状态" align="center" prop="state" >
+        <template #default="scope">
+          <el-tag type="success" v-if="scope.row.state == '成功'">成功</el-tag>
+          <el-tag type="primary" v-if="scope.row.state == '生成中'">生成中</el-tag>
+          <el-tag type="info" v-if="scope.row.state == '等待中'">等待中</el-tag>
+          <el-tag type="warning" v-if="scope.row.state == '失败'">失败</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="创建用户ID" align="center" prop="userId" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template #default="scope">
           <el-button
+              v-if="scope.row.state == '成功'"
               type="primary"
               size="mini"
-              @click="handleUpdate(scope.row)"
+              @click="chartDetailHandler(scope.row)"
           >
             <template #icon>
               <el-icon><TrendCharts /></el-icon>
@@ -58,6 +93,7 @@
             详情
           </el-button>
           <el-popconfirm
+              v-if="scope.row.state == '失败' || scope.row.state == '成功'"
               confirm-button-text="确定"
               cancel-button-text="取消"
               :icon="InfoFilled"
@@ -93,7 +129,7 @@
     />
 
     <!-- 显示图表和结论详情的对话框 -->
-    <el-dialog v-model="open" width="60%" center append-to-body>
+    <el-dialog v-model="open" width="60%" @close="cancel" center append-to-body>
       <template #header>数据分析详情</template>
       <template #default>
         <el-row>
@@ -211,6 +247,9 @@ export default {
     // 取消按钮
     cancel() {
       this.open = false;
+      if (this.chart !== undefined) {
+        // this.chart.clear();
+      }
       this.reset();
     },
     // 表单重置
@@ -247,7 +286,7 @@ export default {
       this.multiple = !selection.length
     },
     /** 修改按钮操作 */
-    handleUpdate(row) {
+    chartDetailHandler(row) {
       this.reset();
       this.analysisConclusion = row.analysisConclusion;
       let generatedChartData = row.generatedChartData;
@@ -296,11 +335,13 @@ export default {
         if (this.chart === undefined){
           this.chart = echarts.init(this.$refs.chart);
         }
-        this.chart.clear();
+        this.chart.dispose();
+        this.chart = echarts.init(this.$refs.chart);
+        // this.chart.clear();
         setTimeout(()=>{
-          this.chart.setOption(this.option);
+          this.chart.setOption(this.option,true);
         },500)
-      },500);
+      },200);
     }
   }
 };

+ 102 - 21
BI_front/src/views/generateChart.vue

@@ -13,12 +13,21 @@
               <el-input v-model="formData.analysisTarget" type="textarea" placeholder="请输入分析目标;比如:分析网站的增长趋势"
                         :autosize="{minRows: 4, maxRows: 4}" :style="{width: '100%'}"></el-input>
             </el-form-item>
-            <el-form-item label="图表类型" prop="chartType">
-              <el-select v-model="formData.chartType" placeholder="请选择图表类型" clearable :style="{width: '100%'}">
-                <el-option v-for="(item, index) in chartTypeOptions" :key="index" :label="item.label"
-                           :value="item.value" :disabled="item.disabled"></el-option>
-              </el-select>
-            </el-form-item>
+            <el-row>
+              <el-col :span="12">
+                <el-form-item label="图表类型" prop="chartType">
+                  <el-select v-model="formData.chartType" placeholder="请选择图表类型" clearable :style="{width: '80%'}">
+                    <el-option v-for="(item, index) in chartTypeOptions" :key="index" :label="item.label"
+                               :value="item.value" :disabled="item.disabled"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="异步分析" prop="isAsynchronism">
+                  <el-switch v-model="formData.isAsynchronism" @change="console.log(formData.isAsynchronism)"/>
+                </el-form-item>
+              </el-col>
+            </el-row>
             <el-form-item label="上传原始数据" prop="file" required>
               <el-upload ref="file" :file-list="filefileList" :action="fileAction" :auto-upload="false" limit="1"
                          :before-upload="fileBeforeUpload" @change="changeFile" accept=".xls,.xlsx">
@@ -42,10 +51,15 @@
       <el-row>
         <el-col :span="24">
           <el-card>
-            <template #header>数据图表</template>
+            <template #header>
+              数据图表
+            </template>
             <template #default>
               <div ref="chart" style="height:50vh"/>
             </template>
+            <template #footer v-if="chartId">
+              <el-button  type="primary" @click.prevent="getChartResult">获取分析结果</el-button>
+            </template>
           </el-card>
         </el-col>
       </el-row>
@@ -65,7 +79,7 @@
 </template>
 
 <script>
-import {generateChartByAI} from "../common/api/chart/index.js";
+import {generateChartByAI, getChart} from "../common/api/chart/index.js";
 import {ElMessage} from "element-plus";
 import * as echarts from 'echarts';
 
@@ -80,6 +94,7 @@ export default {
         analysisTarget: undefined,
         chartType: undefined,
         file: null,
+        isAsynchronism:false
       },
       option:{
         xAxis: {
@@ -111,6 +126,8 @@ export default {
         }]
       },
       analysisConclusion:'',
+      chart:{},
+      chartId:undefined,
       rules: {
         name: [{
           required: true,
@@ -161,20 +178,44 @@ export default {
   },
   methods: {
     submitForm() {
+      console.log(this.formData)
       this.$refs['chartForm'].validate(valid => {
         if (!valid) return
-        generateChartByAI(this.formData).then(res => {
+        if (this.formData.isAsynchronism) {
+          generateChartByAI(this.formData).then(res =>{
+            if (res.code == 200){
+              console.log(res)
+              this.chartId = res.data;
+              ElMessage({
+                message: res.msg,
+                type: 'success',
+              });
+            }else{
+              ElMessage({
+                message: res.msg,
+                type: 'error',
+              });
+            }
+          }).catch(err=>{
+            ElMessage({
+              message: err,
+              type: 'error',
+            });
+          });
+        } else {
+          this.chartId = undefined;
+          generateChartByAI(this.formData).then(res => {
           if (res.code === 200) {
             this.analysisConclusion = res.data.analysisConclusion;
             //取出配置对象的代码
             let generatedChartData = res.data.generatedChartData;
             //将配置对象的代码转换为JSON对象,不是不是标准的JSON对象,需要使用eval函数进行转换
             // let optionJson = JSON.parse(option);
-            let optionJson = eval("(" +generatedChartData+ ")")
+            let optionJson = eval("(" + generatedChartData + ")")
             //返回格式不稳定,有可能会多封装一层option,需要判断一下
-            if (optionJson.option === undefined || optionJson.option === null){
+            if (optionJson.option === undefined || optionJson.option === null) {
               this.option = optionJson;
-            }else{
+            } else {
               this.option = optionJson.option;
             }
             this.chartRefresh();
@@ -182,7 +223,7 @@ export default {
               message: res.msg,
               type: 'success',
             })
-          }else {
+          } else {
             ElMessage({
               message: res.msg,
               type: 'error',
@@ -193,8 +234,9 @@ export default {
             message: err,
             type: 'error',
           });
-        })
-      })
+        });
+        }
+      });
     },
     changeFile(file){
       this.formData.file = file.raw;
@@ -213,13 +255,52 @@ export default {
       }
       return isRightSize && isAccept
     },
-    submitUpload() {
-      this.$refs['file'].submit()
-    },
     chartRefresh(){
-      let chart = echarts.init(this.$refs.chart);
-      chart.clear();
-      chart.setOption(this.option);
+      setTimeout(()=>{
+        this.chart = echarts.init(this.$refs.chart);
+        this.chart.clear();
+        setTimeout(()=>{
+          this.chart.setOption(this.option);
+        },300)
+      },200)
+    },
+    getChartResult(){
+      getChart(this.chartId).then(res =>{
+        if (res.code == 200 && res.data.state == '成功'){
+          this.analysisConclusion = res.data.analysisConclusion;
+          //取出配置对象的代码
+          let generatedChartData = res.data.generatedChartData;
+          //将配置对象的代码转换为JSON对象,不是不是标准的JSON对象,需要使用eval函数进行转换
+          let optionJson = eval("(" + generatedChartData + ")")
+          //返回格式不稳定,有可能会多封装一层option,需要判断一下
+          if (optionJson.option === undefined || optionJson.option === null) {
+            this.option = optionJson;
+            } else {
+              this.option = optionJson.option;
+              }
+            this.chartRefresh();
+            ElMessage({
+              message: '生成图表成功',
+              type: 'success',
+            });
+            this.chartId = undefined;
+        }else if (res.code == 200){
+          ElMessage({
+            message: res.data.state,
+            type: 'info',
+          });
+        }else {
+          ElMessage({
+            message: res.msg,
+            type: 'error',
+          });
+        }
+      }).catch(err =>{
+        ElMessage({
+          message: err,
+          type: 'error',
+        });
+      });
     }
   }
 }

+ 15 - 3
BI_front/src/views/myChart.vue

@@ -35,10 +35,19 @@
       <el-table-column label="图表名称" align="center" prop="name" />
       <el-table-column label="分析目标" align="center" prop="analysisTarget" />
       <el-table-column label="图标类型" align="center" prop="chartType" />
+      <el-table-column label="状态" align="center" prop="state" >
+        <template #default="scope">
+          <el-tag type="success" v-if="scope.row.state == '成功'">成功</el-tag>
+          <el-tag type="primary" v-if="scope.row.state == '生成中'">生成中</el-tag>
+          <el-tag type="info" v-if="scope.row.state == '等待中'">等待中</el-tag>
+          <el-tag type="warning" v-if="scope.row.state == '失败'">失败</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="创建用户ID" align="center" prop="userId" />
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template #default="scope">
           <el-button
+              v-if="scope.row.state == '成功'"
               type="primary"
               size="mini"
               @click="handleUpdate(scope.row)"
@@ -49,6 +58,7 @@
             详情
           </el-button>
           <el-popconfirm
+              v-if="scope.row.state == '失败' || scope.row.state == '成功'"
               confirm-button-text="确定"
               cancel-button-text="取消"
               :icon="InfoFilled"
@@ -287,11 +297,13 @@ export default {
         if (this.chart === undefined){
           this.chart = echarts.init(this.$refs.chart);
         }
-        this.chart.clear();
+        this.chart.dispose();
+        this.chart = echarts.init(this.$refs.chart);
+        // this.chart.clear();
         setTimeout(()=>{
-          this.chart.setOption(this.option);
+          this.chart.setOption(this.option,true);
         },500)
-      },500);
+      },200);
     }
   }
 };

+ 4 - 4
serve/src/main/java/space/anyi/BI/controller/ChartController.java

@@ -79,7 +79,7 @@ public class ChartController {
     @PostMapping("/generateChartByAI")
     @ResponseBody
     public ResponseResult generateChartByAI(ChartDTO chartDTO, MultipartFile file) throws IOException {
-        log.info("分析目标:{}图标名称:{}",chartDTO.getAnalysisTarget(),chartDTO.getName());
+        log.info("分析目标:{},图标名称:{},是否异步{}",chartDTO.getAnalysisTarget(),chartDTO.getName(),chartDTO.getIsAsynchronism());
         //限流判断
         if (!rRateLimiterHandler.accessAble("generateChartByAI_"+SecurityUtils.getUserId())){
             return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.RATE_LIMIT_ERROR);
@@ -95,9 +95,9 @@ public class ChartController {
         if (file.getSize()>1024*1024*2L) {
             return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.FILE_SIZE_ERROR);
         }
-        if (chartDTO.getAsynchronism()){
-            chartService.generateChartByAIAsyn(chartDTO,file);
-            return ResponseResult.okResult("任务提交成功,请稍后查看结果");
+        if (chartDTO.getIsAsynchronism()){
+            long id = chartService.generateChartByAIAsyn(chartDTO,file);
+            return ResponseResult.okResult("任务提交成功,请稍后查看结果",String.valueOf(id));
         }else {
             ChartVO vo = chartService.generateChartByAI(chartDTO,file);
             return ResponseResult.okResult(vo);

+ 4 - 0
serve/src/main/java/space/anyi/BI/entity/ResponseResult.java

@@ -50,6 +50,10 @@ public class ResponseResult<T> implements Serializable {
         ResponseResult result = new ResponseResult();
         return result.ok(code, null, msg);
     }
+    public static ResponseResult okResult(String msg,Object data) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(200, data, msg);
+    }
 
     public static ResponseResult okResult(Object data) {
         ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());

+ 2 - 2
serve/src/main/java/space/anyi/BI/entity/dto/ChartDTO.java

@@ -66,11 +66,11 @@ public class ChartDTO {
         this.chartType = chartType;
     }
 
-    public Boolean getAsynchronism() {
+    public Boolean getIsAsynchronism() {
         return isAsynchronism;
     }
 
-    public void setAsynchronism(Boolean asynchronism) {
+    public void setIsAsynchronism(Boolean asynchronism) {
         isAsynchronism = asynchronism;
     }
 }

+ 1 - 1
serve/src/main/java/space/anyi/BI/service/ChartService.java

@@ -16,7 +16,7 @@ public interface ChartService extends IService<Chart> {
 
     ChartVO generateChartByAI(ChartDTO chartDTO, MultipartFile file);
 
-    void generateChartByAIAsyn(ChartDTO chartDTO, MultipartFile file);
+    long generateChartByAIAsyn(ChartDTO chartDTO, MultipartFile file);
 
     PageVO getChartPage(Integer pageNum, Integer pageSize, String name, Long userId);
 }

+ 47 - 27
serve/src/main/java/space/anyi/BI/service/impl/ChartServiceImpl.java

@@ -49,38 +49,41 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
      * @author: 杨逸
      * @data:2024/12/07 17:43:22
      * @since 1.0.0
+     * @return
      */
     @Override
-    public void generateChartByAIAsyn(ChartDTO chartDTO, MultipartFile file) {
+    public long generateChartByAIAsyn(ChartDTO chartDTO, MultipartFile file) {
 
         Chart chart = BeanCopyUtil.copyBean(chartDTO, Chart.class);
         long chartId = IdUtil.getSnowflake(1, 1).nextId();
         chart.setId(chartId);
         chart.setUserId(SecurityUtils.getUserId());
         chart.setState("等待中");
+        //读数据
+        String csvData  = "";
+        try {
+            csvData = ExcelUtils.excel2csv(file.getInputStream());
+            log.info("上传的数据为:\n{}", csvData);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        if (csvData.length()>3000){
+            chart.setChartData("");
+            chart.setState("失败");
+            chart.setExecuteMessage("数据量过大,请上传小于3000行的数据");
+            save(chart);
+            throw new SystemException(500, "数据量过大,请上传小于3000行的数据");
+        }
+        chart.setChartData(csvData);
         save(chart);
         //使用线程池优化生成图表的逻辑
+        String finalCsvData = csvData;
         threadPoolExecutor.execute(()->{
-            //读数据
-            String csvData  = "";
-            try {
-                csvData = ExcelUtils.excel2csv(file.getInputStream());
-                log.info("上传的数据为:\n{}", csvData);
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-            if (csvData.length()>3000){
-                chart.setState("失败");
-                chart.setExecuteMessage("数据量过大,请上传小于3000行的数据");
-                updateById(chart);
-                throw new SystemException(500, "数据量过大,请上传小于3000行的数据");
-            }
-            chart.setChartData(csvData);
             chart.setState("生成中");
-            updateById(chart);
+           updateById(chart);
 
             StringBuilder message = new StringBuilder("原始数据:\n");
-            message.append(csvData);
+            message.append(finalCsvData);
             message.append("分析目标:\n");
             message.append(chartDTO.getAnalysisTarget());
             message.append("\n.使用").append(chartDTO.getChartType()).append("进行可视化分析.\n");
@@ -109,6 +112,7 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
             updateById(chart);
         });
 
+        return chartId;
     }
 
     @Override
@@ -135,6 +139,11 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
     public ChartVO generateChartByAI(ChartDTO chartDTO, MultipartFile file) {
         //读数据
         String csvData  = "";
+        long charId = IdUtil.getSnowflake(1, 1).nextId();
+        Chart chart = BeanCopyUtil.copyBean(chartDTO, Chart.class);
+        chart.setId(charId);
+        chart.setUserId(SecurityUtils.getUserId());
+        chart.setState("等待中");
         try {
             csvData = ExcelUtils.excel2csv(file.getInputStream());
             log.info("上传的数据为:\n{}", csvData);
@@ -142,37 +151,48 @@ public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
             e.printStackTrace();
         }
         if (csvData.length()>3000){
+            chart.setState("失败");
+            chart.setExecuteMessage("数据量过大,请上传小于3000行的数据");
+            chart.setChartData("");
+            save(chart);
             throw new SystemException(500, "数据量过大,请上传小于3000行的数据");
         }
+        chart.setChartData(csvData);
+        save(chart);
         StringBuilder message = new StringBuilder("原始数据:\n");
         message.append(csvData);
         message.append("分析目标:\n");
         message.append(chartDTO.getAnalysisTarget());
         message.append("\n.使用").append(chartDTO.getChartType()).append("进行可视化分析.\n");
+        chart.setState("生成中");
+        chart.setExecuteMessage("AI正在生成图表");
+        updateById(chart);
         //配置prompt向AI发送请求
         HttpRequestData requestData = AiUtil.createDefaultRequestData(message.toString());
         HttpResponseData responseData = AiUtil.doChat(requestData);
         //解析AI返回的数据
-        ChartVO chartVO = BeanCopyUtil.copyBean(chartDTO, ChartVO.class);
+        //ChartVO chartVO = BeanCopyUtil.copyBean(chartDTO, ChartVO.class);
         String content = responseData.getChoices().get(0).getMessage().getContent();
         log.info("AI返回的数据为:{}", content);
         int index = content.indexOf("```");
         int endIndex = content.lastIndexOf("```");
         if (index == -1 || endIndex == -1){
+            chart.setState("失败");
+            chart.setExecuteMessage("AI生成图表失败");
+            updateById(chart);
             throw new SystemException(500, "AI生成图表失败");
         }
         //数据可视化,Echarts的option代码
-        chartVO.setGeneratedChartData(content.substring(index+7, endIndex).trim());
+        chart.setGeneratedChartData(content.substring(index+7, endIndex).trim());
         index = endIndex;
         //分析结论
-        chartVO.setAnalysisConclusion(content.substring(index+3).trim());
+        chart.setAnalysisConclusion(content.substring(index+3).trim());
 
-        Chart chart = BeanCopyUtil.copyBean(chartVO, Chart.class);
-        chart.setUserId(SecurityUtils.getUserId());
-        chart.setChartData(csvData);
-        chart.setId(IdUtil.getSnowflake(1,1).nextId());
-        //保存到数据库
-        save(chart);
+        //更新数据库
+        chart.setState("成功");
+        chart.setExecuteMessage("图表生成成功");
+        updateById(chart);
+        ChartVO chartVO =  BeanCopyUtil.copyBean(chart, ChartVO.class);
         return chartVO;
     }
 }