整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          vue快速集成word在線編輯

          vue快速集成word在線編輯

          ord在線編輯

          查看一些文檔金格插件WebOffice2015、chrome瀏覽器插件、only-officeUEditorTinyMCECKEditorwangeditorcanvas-editor

          最后選擇了only-officecanvas-editor

          only-office非常功能強大,word、ppt、excel都支持在線編輯預覽,還支持協同,又有免費開源版。

          附上本地運行demo:

          一、安裝docker

          二、安裝并啟動 Onlyoffice 服務:

          docker run -i -t -d -p 8701:80 onlyoffice/documentserver:版本號

          如果是第 1 次執行這個命令,會先去下載 Onlyoffice,比較慢,約等待 3~10 分鐘,網絡暢通一點的會快一些。如果是已經安裝過則直接進行啟動。

          三、啟動內置服務

          先執行 docker ps 查看 Onlyoffice 容器 ID:

          # 注意這里要將 id 替換成自己的
          docker exec -it f2a3eb675ad1 /bin/bash

          然后執行 docker exec -it ID /bin/bash 進入容器,這里將獲取到的 ID 替換一下:

          # 啟動所有的內置服務
          supervisorctl restart all
          # 退出容器
          exit

          最后訪問 http://IP:8701/example 頁面(這里要注意,IP 不能是 localhost 和 127.0.0.1,一定要用真實 IP 來訪問)

          因為開發周期,后端又比較懶不想花時間去看文檔。這一方案被我放棄了

          最后選擇了canvas-editor

          canvas-editor

          為什么選它了,開發周期短,界面與word編輯器比較像,可以快速集成到vue,雖然作者沒有開箱即用版。

          在vue中主要實現方式就是采用開源項目代碼。

          在組件模塊,新建vue文件,html采用開源項目代碼,分3個部分,工具欄,側邊菜單,主要內容,底部工具,旁邊批注。通過import引入開源樣式,注意樣式沖突。在onMounted,采用開源main.ts window.onload代碼。

          <div class="menu" editor-component="menu">
          ...
          </div>
          <div class="catalog" editor-component="catalog">
          ...
          </div>
          <div id="canvasEditor" class="canvas-editor"></div>
          <div class="footer-canvas" editor-component="footer">
          ...
          </div>
          const instance=new Editor(
              document.querySelector('#canvasEditor'),
              {
                header: props.header,
                main: props.main,
                footer: props.footer
              },
              options
          );
          console.log('實例', instance);
          editorRef.value=instance;
          
          // 工具欄方法 例:
          // 2. | 撤銷 | 重做 | 格式刷 | 清除格式 |
          const undoDom=document.querySelector('.menu-item__undo');
              undoDom.title=`撤銷(${isApple ? '?' : 'Ctrl'}+Z)`;
              undoDom.onclick=function () {
              console.log('undo');
              instance.command.executeUndo();
          };
          
          <style lang="scss" scoped>
          #canvasEditor {
            display: flex;
            justify-content: center;
            background: #f2f4f7;
          }
          @import url(@/assets/css/dialog.css);
          @import url(@/assets/css/signature.css);
          </style>

          思路通過富文本編輯器實現在線編輯功能,通過插件提供的api獲取圖片base64和文本數據,通過接口保存至數據庫,通過列表數據復顯編輯,后端獲取到圖片數據轉pdf或者其他格式都可以。自己轉pdf則是通過html2canvas jspdf

          使用方式

          例:
          <canvas-editor
            v-if="!loadingInit"
            :header="canvasEditor.header"
            :main="canvasEditor.main"
            :footer="canvasEditor.footer"
            :options="canvasEditor.options"
            @getCanvasEditorData="getCanvasEditorData"
          />
          import CanvasEditor from '@/components/editor/index.vue';
          
          components: {
            CanvasEditor
          },
          
          canvasEditor: {
            main: [],
            options: [],
            header: [],
            footer: []
          },
          
          // getCanvasEditorData 事件
          
          主要獲取富文本 main內容 image的base64 options配置項 用于數據保存
          
          getCanvasEditorData: ({ value, imgage, options })=> {  
          },
          


          主要配置

              // 頁眉配置
              header: {
                type: Array,
                default: []
              },
              // 主要編輯內容
              main: {
                type: Array,
                default: []
              },
              // 頁腳信息
              footer: {
                type: Array,
                default: []
              },
              // 
              options: {
                type: Object,
                default: {}
              },
              // 批注 TODO
              commentList: {
                type: Array,
                default: []
              }

          完整配置

          interface IEditorOption {
            mode?: EditorMode // 編輯器模式:編輯、清潔(不顯示視覺輔助元素。如:分頁符)、只讀、表單(僅控件內可編輯)、打印(不顯示輔助元素、未書寫控件及前后括號)。默認:編輯
            defaultType?: string // 默認元素類型。默認:TEXT
            defaultColor?: string // 默認字體顏色。默認:#000000
            defaultFont?: string // 默認字體。默認:Microsoft YaHei
            defaultSize?: number // 默認字號。默認:16
            minSize?: number // 最小字號。默認:5
            maxSize?: number // 最大字號。默認:72
            defaultBasicRowMarginHeight?: number // 默認行高。默認:8
            defaultRowMargin?: number // 默認行間距。默認:1
            defaultTabWidth?: number // 默認tab寬度。默認:32
            width?: number // 紙張寬度。默認:794
            height?: number // 紙張高度。默認:1123
            scale?: number // 縮放比例。默認:1
            pageGap?: number // 紙張間隔。默認:20
            underlineColor?: string // 下劃線顏色。默認:#000000
            strikeoutColor?: string // 刪除線顏色。默認:#FF0000
            rangeColor?: string // 選區顏色。默認:#AECBFA
            rangeAlpha?: number // 選區透明度。默認:0.6
            rangeMinWidth?: number // 選區最小寬度。默認:5
            searchMatchColor?: string // 搜索高亮顏色。默認:#FFFF00
            searchNavigateMatchColor?: string // 搜索導航高亮顏色。默認:#AAD280
            searchMatchAlpha?: number // 搜索高亮透明度。默認:0.6
            highlightAlpha?: number // 高亮元素透明度。默認:0.6
            resizerColor?: string // 圖片尺寸器顏色。默認:#4182D9
            resizerSize?: number // 圖片尺寸器大小。默認:5
            marginIndicatorSize?: number // 頁邊距指示器長度。默認:35
            marginIndicatorColor?: string // 頁邊距指示器顏色。默認:#BABABA
            margins?: IMargin // 頁面邊距。默認:[100, 120, 100, 120]
            pageMode?: PageMode // 紙張模式:連頁、分頁。默認:分頁
            tdPadding?: IPadding // 單元格內邊距。默認:[0, 5, 5, 5]
            defaultTrMinHeight?: number // 默認表格行最小高度。默認:42
            defaultColMinWidth?: number // 默認表格列最小寬度(整體寬度足夠時應用,否則會按比例縮小)。默認:40
            defaultHyperlinkColor?: string // 默認超鏈接顏色。默認:#0000FF
            header?: IHeader // 頁眉信息。{top?:number; maxHeightRadio?:MaxHeightRatio;}
            footer?: IFooter // 頁腳信息。{bottom?:number; maxHeightRadio?:MaxHeightRatio;}
            pageNumber?: IPageNumber // 頁碼信息。{bottom:number; size:number; font:string; color:string; rowFlex:RowFlex; format:string; numberType:NumberType;}
            paperDirection?: PaperDirection // 紙張方向:縱向、橫向
            inactiveAlpha?: number // 正文內容失焦時透明度。默認值:0.6
            historyMaxRecordCount?: number // 歷史(撤銷重做)最大記錄次數。默認:100次
            printPixelRatio?: number // 打印像素比率(值越大越清晰,但尺寸越大)。默認:3
            maskMargin?: IMargin // 編輯器上的遮蓋邊距(如懸浮到編輯器上的菜單欄、底部工具欄)。默認:[0, 0, 0, 0]
            letterClass?: string[] // 排版支持的字母類。默認:a-zA-Z。內置可選擇的字母表類:LETTER_CLASS
            contextMenuDisableKeys?: string[] // 禁用的右鍵菜單。默認:[]
            scrollContainerSelector?: string // 滾動區域選擇器。默認:document
            wordBreak?: WordBreak // 單詞與標點斷行:BREAK_WORD首行不出現標點&單詞不拆分、BREAK_ALL按字符寬度撐滿后折行。默認:BREAK_WORD
            watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;}
            control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;}
            checkbox?: ICheckboxOption // 復選框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
            radio?: IRadioOption // 單選框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
            cursor?: ICursorOption // 光標樣式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;}
            title?: ITitleOption // 標題配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
            placeholder?: IPlaceholder // 編輯器空白占位文本
            group?: IGroup // 成組配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean}
            pageBreak?: IPageBreak // 分頁符配置。{font?:string; fontSize?:number; lineDash?:number[];}
            zone?: IZoneOption // 編輯器區域配置。{tipDisabled?:boolean;}
            background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。默認:{color: '#FFFFFF'}
            lineBreak?: ILineBreakOption // 換行符配置。{disabled?:boolean; color?:string; lineWidth?:number;}
            separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;}
          }

          頁眉配置

          interface IHeader {
            top?: number // 距離頁面頂部大小。默認:30
            maxHeightRadio?: MaxHeightRatio // 占頁面最大高度比。默認:HALF
            disabled?: boolean // 是否禁用
          }

          頁碼配置

          interface IPageNumber {
            bottom?: number // 距離頁面底部大小。默認:60
            size?: number // 字體大小。默認:12
            font?: string // 字體。默認:Microsoft YaHei
            color?: string // 字體顏色。默認:#000000
            rowFlex?: RowFlex // 行對齊方式。默認:CENTER
            format?: string // 頁碼格式。默認:{pageNo}。示例:第{pageNo}頁/共{pageCount}頁
            numberType?: NumberType // 數字類型。默認:ARABIC
            disabled?: boolean // 是否禁用
            startPageNo?: number // 起始頁碼。默認:1
            fromPageNo?: number // 從第幾頁開始出現頁碼。默認:0
          }

          水印配置

          interface IWatermark {
            data: string // 文本。
            color?: string // 顏色。默認:#AEB5C0
            opacity?: number // 透明度。默認:0.3
            size?: number // 字體大小。默認:200
            font?: string // 字體。默認:Microsoft YaHei
          }

          占位文本配置

          ploadBigFile/Index.cshtml

          
          
          @{
              ViewData["Title"]="Index";
          }
          
          <h1>Index</h1>
          
          <!--上傳視頻-->
          <div>
              <div>
                  <div>
                      <div>
                          <span class="">上傳視頻</span>
                      </div>
                      <input id="uploadvideofile" type="file" accept="video/mp4,video/quicktime" class="up-video" />
                  </div>
              </div>
              <div id="videoplayer" style="display: none"></div>
              <div id="videooutput"></div>
              <div>
                  <p style="color: #797979; margin-top: 5px; font-size: 12px;">視頻格式必須為: mp4或mov。視頻時長須在15秒以內,超出時長系統將自動截取前15秒內容。</p>
              </div>
          </div>
          
          <script src="~/Scripts/ajax.1.5.2.js"></script>
          <!--傳視頻相關-->
          <script type="text/javascript">
              $('#uploadvideofile').change(function() {
                  var file, videoURL, windowURL;
                  var filemaxsize=1024 * 1024 * 20; //200M
                  if (fileValid(this, filemaxsize, 'video')) {
                      file=this.files[0];
                      try {
                          var temp=ajax.upload_big(
                              "UpLoadBigFile/UploadVideo?opration=2222", //文件上傳地址
                              "#uploadvideofile", //input=file 選擇器
                              1024 * 1024, //切割文件大小
                              "*", //文件限制類型 mime類型
                              function(index) { //上傳成功事件
                                  //console.log("slice [ " + index + "] uploaded")
                              },
                              function(index, length) { //上傳進度事件
                                  //console.log(index + "/" + length);
                                  //var ratio=(index / length) * 100.00 | 0.00;
                                  //var progress=$(".progress > div");
                                  //progress.css("width", ratio + "%");
                                  //progress.attr("aria-valuenow", index);
                                  //progress.attr("aria-valuemax", length);
                                  //progress.children("span").text("" + ratio + "%");
                              },
                              function(index, length) { //超時處理事件
                                  console.log(index + "/" + length);
                              }
                          );
                      } catch (e) {
                          console.log(e.name + " " + e.message);
                      }
                      videoURL=null;
                      windowURL=window.URL || window.webkitURL;
                      videoURL=windowURL.createObjectURL(file);
                      $('#videoplayer').html('<video src="' + videoURL + '" controls="controls"></video>');
                      setTimeout(function() { createIMG(); }, 800);
                  }
              });
          
              //驗證上傳文件大小和類型
              /**
               *
               * param {this} value_ [獲取input對象,一般為this]
               * param {[number]} size_ [文件限制的大小,單位為kb]
               * param {[string]} type_ [文件類別]
               * param {[function]} callback [驗證通過的回調]
               */
              function fileValid(value_, size_, type_, callback) {
                  var file=value_.files[0];
                  var fileSize=(file.size / 1024).toFixed(0); //文件大小
                  var fileType=value_.value.substring(value_.value.lastIndexOf(".")); //文件類型
          
                  if (fileSize > size_) {
                      alert('視頻過大,請選擇小于200MB的視頻!');
                      return false;
                  }
                  switch (type_) {
                  case 'video':
                      if (!fileType.match(/.mp4|.mov/i)) {
                          alert('請上傳正確格式的視頻!');
                          return false;
                      }
                      break;
                  default:
                      alert('參數設置不正確!');
                      return false;
                      break;
                  }
                  return true;
              }
          
              var createIMG=function() {
                  var scale=0.25,
                      video=$('#videoplayer').find('video')[0],
                      canvas=document.createElement("canvas"),
                      canvasFill=canvas.getContext('2d');
                  canvas.width=video.videoWidth * scale;
                  canvas.height=video.videoHeight * scale;
                  canvasFill.drawImage(video, 0, 0, canvas.width, canvas.height);
                  var src=canvas.toDataURL("image/jpeg");
                  $('#videooutput').html('<img id="imgSmallView" src="' + src + '" alt="預覽圖" />');
              }
          </script>
          
          <script>
              (function () {
                  //$("#uploadvideofile").change(function () {
                  //    var temp=ajax.upload_big(
                  //        "AjaxHandlers/UploadVideoHandler.ashx", //文件上傳地址
                  //        "#uploadvideofile", //input=file 選擇器
                  //        1024 * 1024, //切割文件大小
                  //        "*", //文件限制類型 mime類型
                  //        function(index) { //上傳成功事件
                  //            //console.log("slice [ " + index + "] uploaded")
                  //        },
                  //        function(index, length) { //上傳進度事件
                  //            //console.log(index + "/" + length);
                  //            //var ratio=(index / length) * 100.00 | 0.00;
                  //            //var progress=$(".progress > div");
                  //            //progress.css("width", ratio + "%");
                  //            //progress.attr("aria-valuenow", index);
                  //            //progress.attr("aria-valuemax", length);
                  //            //progress.children("span").text("" + ratio + "%");
                  //        },
                  //        function(index, length) { //超時處理事件
                  //            console.log(index + "/" + length);
                  //        }
                  //    );
                  //    console.log(temp);
                  //});
              })();
          </script>
          

          UploadBigFileController.cs

          using Microsoft.AspNetCore.Http;
          using Microsoft.AspNetCore.Mvc;
          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Threading.Tasks;
          using VideoDemo.Common;
          using VideoDemo.Models;
          
          namespace VideoDemo.Controllers
          {
              public class UploadBigFileController : Controller
              {
                  public ActionResult Index()
                  {
                      return View();
                  }
          
                  [HttpPost]
                  //[ValidateAntiForgeryToken]
                  public int UploadVideo([FromForm] VideoFileModel filemodel, [FromQuery]string opration)
                  {
                      try
                      {
                          int slicenum=FileHelper.UploadVideo(filemodel);
                          return slicenum;
          
                          //return RedirectToAction(nameof(Index));
                      }
                      catch(Exception e)
                      {
                          Console.Write(e.ToString());
                          return -1;
                      }
                  }
          
          
                  public ActionResult Details(int id)
                  {
                      return View();
                  }
          
                  public ActionResult Create()
                  {
                      return View();
                  }
          
                  // GET: UpLoadBigFileController/Edit/5
                  public ActionResult Edit(int id)
                  {
                      return View();
                  }
          
                  // POST: UpLoadBigFileController/Edit/5
                  [HttpPost]
                  [ValidateAntiForgeryToken]
                  public ActionResult Edit(int id, IFormCollection collection)
                  {
                      try
                      {
                          return RedirectToAction(nameof(Index));
                      }
                      catch
                      {
                          return View();
                      }
                  }
          
               
              }
          }

          FileHelper.cs

          using Microsoft.AspNetCore.Http;
          using System;
          using System.Collections.Generic;
          using System.IO;
          using System.Linq;
          using System.Text.RegularExpressions;
          using System.Threading.Tasks;
          using VideoDemo.Models;
          using Xabe.FFmpeg;
          
          namespace VideoDemo.Common
          {
              public class FileHelper
              {
                  private static  string finalpath="";
          
                  public static int UploadVideo(VideoFileModel videoFile)
                  {
                      //前端傳輸是否為切割文件最后一個小文件
                      var isLast=videoFile.isLast;
                      //前端傳輸當前為第幾次切割小文件
                      var count=videoFile.Count;
                      //獲取前端處理過的傳輸文件名
                      string fileName=videoFile.Name;
                      //存儲接受到的切割文件
                      if (videoFile.Files.Length <=0)
                      {
                          return -1;
                      }
          
                      IFormFile formFile=videoFile.Files;
          
                      //處理文件名稱(去除.part*,還原真實文件名稱)
                      string newFileName=fileName.Substring(0, fileName.LastIndexOf('.'));
          
                      //臨時存儲文件夾路徑
                      string desPath=Directory.GetCurrentDirectory() + @"/uploads/slice/" + newFileName;
          
                      //判斷指定目錄是否存在臨時存儲文件夾,沒有就創建
                      if (!System.IO.Directory.Exists(desPath))
                      {
                          //不存在就創建目錄 
                          System.IO.Directory.CreateDirectory(desPath);
                      }
          
                      //存儲文件
                      using (var stream=new FileStream(desPath+"/"+ fileName, FileMode.Create))
                      {
                          formFile.CopyTo(stream);
                      }
                      //file.SaveAs("E:\\uploads\\slice\\" + newFileName + "\\" + fileName);
          
          
                      //判斷是否為最后一次切割文件傳輸
                      if (isLast=="true")
                      {
                          //判斷組合的文件是否存在
                          finalpath=Directory.GetCurrentDirectory() + @"/uploads/" + newFileName;
                          if (File.Exists(finalpath)) //如果文件存在
                          {
                              File.Delete(finalpath); //先刪除,否則新文件就不能創建
                          }
          
                          //創建空的文件流
                          FileStream FileOut=new FileStream(finalpath, FileMode.CreateNew, FileAccess.ReadWrite);
                          BinaryWriter bw=new BinaryWriter(FileOut);
                          //獲取臨時存儲目錄下的所有切割文件
                          string[] allFile=Directory.GetFiles(desPath);
                          //將文件進行排序拼接
                          allFile=allFile.OrderBy(s=> int.Parse(Regex.Match(s, @"\d+$").Value)).ToArray();
                          for (int i=0; i < allFile.Length; i++)
                          {
                              FileStream FileIn=new FileStream(allFile[i], FileMode.Open);
                              BinaryReader br=new BinaryReader(FileIn);
                              byte[] data=new byte[1048576]; //流讀取,緩存空間
                              int readLen=0; //每次實際讀取的字節大小
                              readLen=br.Read(data, 0, data.Length);
                              bw.Write(data, 0, readLen);
                              //關閉輸入流
                              FileIn.Close();
                          }
          
                          //關閉二進制寫入
                          bw.Close();
                          FileOut.Close();
                          ClipVideo();
                      }
          
                      return int.Parse(count) + 1;
                  }
          
                  public static async void ClipVideo()
                  {
                      /**
                       * 支持視頻格式:mpeg,mpg,avi,dat,mkv,rmvb,rm,mov.
                       *不支持:wmv
                       * **/
                      //FFmpeg.SetExecutablesPath("ffmpeg.exe");
                     //ffmpeg.exe的路徑,控制臺程序會在執行目錄(....FFmpeg測試\bin\Debug)下找此文件,
          
                      //視頻路徑
                      string finalname=Path.GetFileNameWithoutExtension(finalpath);
                      string videoFilePath=finalpath; //"d:\\01.avi";
                      finalpath=finalpath.Replace(finalname, finalname + "15S");
          
          
                   
                      //IMediaInfo videoFile=await FFmpeg.GetMediaInfo(videoFilePath);
                      //TimeSpan totaotp=videoFile.Duration;
                      //string totalTime=string.Format("{0:00}:{1:00}:{2:00}", (int)totaotp.TotalHours, totaotp.Minutes,totaotp.Seconds);
          
                      #region 自定義邏輯
          
                      //string sourceFileName=Path.GetFileName(upFile.get_FileName()); //取出上傳的視頻的文件名,進而取出該文件的擴展名
                      //string sourceFileName="02.avi";
                      //string flv_file=System.IO.Path.ChangeExtension("d:\\01.avi", ".flv");
                      //string Command=" -i \"" + FromName + "\" -y -ab 32 -ar 22050 -b 800000 -s  480*360 \"" + ExportName + "\""; //Flv格式  
          
                      //轉換視頻為flv
                      //ffmpeg -i F:\01.wmv -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flv
          
                      //視頻截圖,fileName視頻地址,imgFile圖片地址
                      //ffmpeg -i input.flv -y -f image2 -ss 10.11 -t 0.001 -s 240x180 catchimg.jpg;
                      //ImgstartInfo.Arguments="   -i   " + fileName + "  -y  -f  image2   -ss 2 -vframes 1  -s   " + FlvImgSize + "   " + flv_img;
          
                      //string Command=" -i \"test.wmv\" -y -ab 32 -ar 22050 -b 800000 -s 320*240 \"2.flv\"";
                      //string Command="E:\\FFmpeg\\ffmpeg.exe -i E:\\ClibDemo\\VideoPath\\admin\\a.wmv -y -ab 56 -ar 22050 -b 500 -r 15 -s 320*240 " ExportName;
          
                      //3.重新編碼進行剪切
                      //ffmpeg -ss [start] -t [duration] -i [in].mp4  -c:v libx264 -c:a aac -strict experimental -b:a 98k [out].mp4
                      //相對來說比較精確,可是還是不是特別精確
          
                      //string Command=" -ss 00:00:00 -t 00:00:15 -i  d:\\01.avi   d:\\output.avi"; // ffmpeg -ss 00:00:10 -t 00:01:22 -i 五月天-突然好想你.mp3  out.mp3  
                      string Command=" -ss 00:00:00 -t 00:00:15 -i  " + videoFilePath + "   " + finalpath;
                      string Command1=        " -i d:\\01.avi -vf \"drawtext=fontfile=simhei.ttf: text='南通極客如皋張HC':x=w-tw-10:y=10:fontsize=28:fontcolor=red:shadowy=2\" d:\\output1.avi";
                      string Command2=        " -i d:\\01.avi -vf \"drawtext=fontfile=simhei.ttf: text='南通極客QQ(827XXXXXX)':y=h-line_h-10:x=(w-mod(30*n\\,w+tw)):fontsize=34:fontcolor=yellow:shadowy=2\" d:\\output2.avi";
          
                      System.Diagnostics.Process p=new System.Diagnostics.Process();
                      //非控制臺程序必須寫完整路徑
                      p.StartInfo.FileName=AppContext.BaseDirectory + ("/ffmpeg.exe");
                      p.StartInfo.Arguments=Command;
                      //p.StartInfo.Arguments=Command1;
                      //Asp.net 獲取當前目錄
                      p.StartInfo.WorkingDirectory=AppContext.BaseDirectory; //HttpContext.Current.Request.MapPath("~/"); //Environment.CurrentDirectory;
                      p.StartInfo.UseShellExecute=false;
                      p.StartInfo.RedirectStandardInput=true;
                      p.StartInfo.RedirectStandardOutput=true;
                      p.StartInfo.RedirectStandardError=true;
                      p.StartInfo.CreateNoWindow=false;
                      //開始執行
                      p.Start();
                      p.BeginErrorReadLine();
                      p.WaitForExit();
                      p.Close();
                      p.Dispose();
          
                      #endregion
          
                      //Console.WriteLine("時間長度:{0}", totalTime);
                      //Console.WriteLine("高度:{0}", videoFile.Height);
                      //Console.WriteLine("寬度:{0}", videoFile.Width);
                      //Console.WriteLine("數據速率:{0}", videoFile.VideoBitRate);
                      //Console.WriteLine("數據格式:{0}", videoFile.VideoFormat);
                      //Console.WriteLine("比特率:{0}", videoFile.BitRate);
                      //Console.WriteLine("文件路徑:{0}", videoFile.Path);
                      Console.ReadKey();
                  }
          
              }
          }

          如圖:

          ainless Lab 是 Elasticsearch 7.13 引入的實驗性功能,是一個交互式代碼編輯器,可以實時測試和調試 Painless 腳本。

          本文展開解讀 Painless Lab 如何應用于企業級實戰開發中的腳本調試環節!

          1、Painless Lab 是什么?

          Painless Lab是一個交互式的測試版代碼編輯器,用于實時測試和調試Painless腳本。

          咱們可以通過打開主菜單,點擊開發工具,然后選擇 Painless Lab 來訪問它。

          如下圖所示,左側是:腳本輸入區域。右側由三部分組成:

          • Output:代表結果輸出,確切說是調試結果輸出。
          • Parameters:代表參數輸入。
          • Context:代表上下文,確切說是不同腳本類型選型與選擇。

          2、Painless Lab 能干什么?

          一句話:Painless Lab 可以實時測試和調試 Painless 腳本。

          Painless Lab 允許我們創建 Kibana 運行時字段(runtime fields)、處理重新索引的數據(reindex)、定義復雜的 Watcher 條件(付費功能),并在其他上下文中處理數據。

          下面的 Context 部分展開就是 Painless Lab 的核心功能區域。

          三種類型進一步展開:

          進一步再展開解讀。

          https://www.elastic.co/guide/en/elasticsearch/painless/8.11/painless-execute-api.html#_contexts

          上下文描述painless_test默認上下文,如果沒有指定其他上下文則使用此上下文。用于通用腳本測試,例如調試和驗證腳本邏輯。filter將腳本視為在腳本查詢中運行。用于過濾數據。score將腳本視為在 function_score 查詢中的 script_score 函數中運行。用于評分數據。

          2.1 painless_test 類型

          默認上下文,如果沒有指定其他上下文則使用此上下文。用于通用腳本測試,例如調試和驗證腳本邏輯。

          2.2 filter 類型

          將腳本視為在腳本查詢中運行。用于過濾數據。

          2.3 score 類型

          將腳本視為在 function_score 查詢中的 script_score 函數中運行。用于評分數據。

          我們逐一詳盡展開解讀,確保大家跟著過一遍,就能學得會!

          3、 Basic painless_test 基礎調試

          Basic 上下文允許我們獨立測試腳本邏輯,并將結果轉換為字符串輸出。

          樣例數據可以放到 params 中作為輸入。

          實戰舉例如下:

          對于 Ingest pipeline 腳本,參考官方示例,由于腳本是在 Ingest Pipeline 中處理數據的,并且沒有涉及到查詢過濾 filter 或評分 score,因此 Basic 上下文是最合適的選擇。

          https://www.elastic.co/guide/en/elasticsearch/reference/current/script-processor.html

          如上圖所示,我們在 parameters 中輸入如下數據:

          {
            "ctx": {
              "env": "小米-筆記本-電腦-雷軍"
            },
            "params": {
              "delimiter": "-",
              "position": 1
            }
          }
          

          我們在左側輸入如下的 painless script 腳本,如下所示:

          // 獲取 ctx 數據
          def ctx=params.ctx;
          
          // 獲取參數
          def delimiter=params.params.delimiter;
          def position=params.params.position;
          
          // 執行腳本邏輯
          String[] envSplit=ctx.env.splitOnToken(delimiter);
          ArrayList tags=new ArrayList();
          //tags.add(envSplit[position].trim());
          for (def tag : envSplit) {
             tags.add(tag.trim());
          }
          ctx.tags=tags;
          
          // 返回結果以供調試
          return ctx.tags;
          

          執行結果如下圖右側 Output 所示。

          上述腳本實現的核心功能就是:以分隔符截斷字符串,形成獨立字符串,插入到 tags 集合中。

          這樣調試過之后,再微調一下就可以應用到 ingest pipeline 中。

          4、filter 過濾調試

          區別于剛才的邏輯,這里需要我們先創建索引,然后基于我們構造的索引數據進行展開 filter 過濾檢索。

          POST /hockey/_doc/1
          {
            "first": "johnny",
            "last": "gaudreau",
            "goals": [9, 27, 1],
            "assists": [17, 46, 0],
            "gp": [26, 82, 1]
          }
          
          POST /hockey/_doc/2
          {
            "first": "john",
            "last": "doe",
            "goals": [2, 3, 4],
            "assists": [1, 2, 3],
            "gp": [4, 5, 6]
          }
          

          上述索引必須構建,否則會報錯如下圖所示。

          錯誤原因可能是:索引不存在或者Mapping 不存在。

          正確的執行步驟如下所示:

          結合上面三個步驟以及左側的腳本,主要驗證左側腳本正確與否。注意:返回值必須是 Bool 類型。執行結果如下:

          最終結合上述調試成功的腳本,整合到 script query 檢索語句中,就能得到滿足用戶預期的結果數據。

          POST /hockey/_search
          {
            "query": {
              "bool": {
                "filter": {
                  "script": {
                    "script": {
                      "lang": "painless",
                      "source": """
                      // 獲取 goals 字段的值
                      def goals=doc['goals'];
                      
                      // 初始化總和變量
                      int sum=0;
                      
                      // 遍歷 goals 數組并計算總和
                      for (int i=0; i < goals.size(); i++) {
                        sum +=goals.get(i);
                      }
                      
                      // 輸出調試信息
                      //Debug.explain(sum);
                      
                      // 返回 true 以匹配所有文檔,僅用于調試目的
                      return sum>10;
                      """
                    }
                  }
                }
              }
            }
          }
          

          5、評分 score 類型調試

          在 Elasticsearch 中,score 類型調試上下文用于在 function_score 查詢中的 script_score 函數中運行腳本。

          該方式允許用戶編寫腳本來動態計算文檔的評分,從而影響搜索結果的排序。


          5.1 真實企業場景再現

          假設我們有一個包含產品信息的索引 products,每個文檔包含以下字段:

          • 1.name: 產品名稱
          • 2.price: 產品價格
          • 3.rating: 產品評分

          我們希望根據價格和評分來動態計算每個產品的分數,具體規則如下:

          • 1.價格越低,分數越高
          • 2.評分越高,分數越高
          POST /products/_doc/1
          {
            "name": "Product A",
            "price": 100,
            "rating": 4.5
          }
          
          POST /products/_doc/2
          {
            "name": "Product B",
            "price": 200,
            "rating": 4.0
          }
          
          POST /products/_doc/3
          {
            "name": "Product C",
            "price": 150,
            "rating": 3.5
          }
          

          5.2 評分查詢腳本調試示例

          我們將編寫一個 function_score 查詢,使用 Painless 腳本來計算每個文檔的分數,并根據計算結果排序。

          核心邏輯:

          • 1、獲取字段值;
          • 2、腳本重新計算評分;
          • 3、返回自定義評分。

          在 Painless Lab 中,可以使用類似的腳本來調試和驗證評分邏輯:

          • 構造參數 Parameters 部分
          {
              "price": 100,
              "rating": 4.5
          }
          

          左側腳本部分

          // 獲取參數值
          long price=params.price;
          double rating=params.rating;
          
          // 檢查字段值是否存在
          if (price==0 || rating==0) {
              // 如果任一字段值為 0,則返回默認分數(例如 0)
              return 0.0;
          }
          
          // 自定義評分邏輯
          double score=(1 / (float)price) * rating;
          
          // 返回評分結果
          return score;
          

          執行結果如下所示:

          上述腳本通過使用 score 上下文中的 script_score 函數,可以根據自定義邏輯動態計算文檔的分數,從而影響搜索結果的排序。

          這在需要根據復雜規則排序搜索結果時非常有用。

          通過在 Painless Lab 中調試和驗證上述腳本,可以確保評分邏輯的正確性和有效性。

          進而,可以組合寫出如下的評分腳本檢索語句。

          POST /products/_search
          {
            "query": {
              "function_score": {
                "query": {
                  "match_all": {}
                },
                "script_score": {
                  "script": {
                    "source": """
                      long price=doc['price'].value;
                      double rating=doc['rating'].value;
                      
                      // 自定義評分邏輯
                      double score=(1 / (float)price) * rating;
                      return score;
                    """
                  }
                },
                "boost_mode": "replace"  // 使用腳本計算的分數替換原始分數
              }
            }
          }
          

          實現核心:根據自定義邏輯計算分數:score=(1 / price) * rating。價格越低,評分越高,分數越高。

          boost_mode: 設置為 replace,使用腳本計算的分數替換原始分數。

          6、小結

          Kibana Painless Lab 是 Elasticsearch 7.13 引入的實驗性功能,為開發者提供交互式代碼編輯器,用于實時測試和調試 Painless 腳本。

          通過 painless_test、filter 和 score 上下文三種測試方式,開發者可以創建和調試 Kibana 運行時字段、處理重新索引的數據、定義復雜的 Watcher 條件,并根據復雜規則動態計算文檔分數,提高腳本開發和優化效率。


          作者:銘毅天下

          來源-微信公眾號:銘毅天下Elasticsearch

          出處:https://mp.weixin.qq.com/s/x9FrVCyrmpvJsc7pNGBm2g


          主站蜘蛛池模板: 久久4k岛国高清一区二区| 亚洲中文字幕丝袜制服一区| 国产在线一区二区杨幂| 中文字幕一区二区三区永久| 曰韩人妻无码一区二区三区综合部 | 一区二区三区四区无限乱码 | 免费高清在线影片一区| 一区二区三区午夜视频| 国产午夜精品一区二区三区| 国产免费一区二区三区免费视频| 色一情一乱一伦一区二区三欧美| 亚洲福利电影一区二区?| 亚洲色大成网站www永久一区 | 亚洲AV美女一区二区三区| 国产成人高清视频一区二区| 午夜一区二区免费视频| 精品无码国产AV一区二区三区 | 日韩精品乱码AV一区二区| 一区二区免费视频| 奇米精品一区二区三区在线观看| 中文字幕无线码一区| 中文人妻无码一区二区三区| 国产午夜精品一区二区| 精品一区二区三区在线视频| 日韩AV无码一区二区三区不卡毛片| 国产精品无码不卡一区二区三区 | 免费一区二区三区在线视频| 日韩动漫av在线播放一区| 一区二区精品视频| 国产日韩AV免费无码一区二区| 免费萌白酱国产一区二区三区| 日本不卡一区二区三区| 日韩人妻一区二区三区蜜桃视频| 亚洲国产一区在线观看| 夜精品a一区二区三区| 精品国产一区二区三区AV性色| 嫩B人妻精品一区二区三区| 亚洲av无码一区二区三区天堂古代 | 日本精品视频一区二区三区| 日韩电影在线观看第一区| 国产亚洲福利精品一区|