自动化操作极速全景下载大师初探

项目背景:针对迁移全景图片到新的网站,又丢失了原图的情况,开发了这个脚本程序,依托全景下载大师自动下载全景图片并合成全景图。这个脚本的作用是减少了手动的操作,可以放置到云电脑自动运行。

下载全景软件前需要先导出所有需要下载的全景图片的表格,表格必须包含项目id和下载链接两列。

然后编写ahk脚本,读取表格的数据,先创建以项目id为名称的文件夹,再把下载链接复制到全景下载大师,推荐开通会员,可以下载高清模式。右键选择高清模式下载,下载的过程中可以通过shareX截取下载软件窗口图片,再通过tesseract ocr识别图片的文本,图片包含完成相关信息的时候就读取下一行表格,这样就实现了自动化处理全景图片下载。

参考脚本

; 1. 启动全景下载大师
Run "D:\App\PanoDownloaderMaster\pdm.exe",,, &pdm_pid
Sleep 5000 ; 休眠5s
WinWaitActive("提示") ;弹窗标题
ControlClick("Button2") ;
Sleep(500)
Loop {
    aTitle := WinGetTitle("A")
    if InStr(aTitle, "专业版")
        break
    Sleep(500)
}
; 等待窗口创建
if WinWait(aTitle, , 3)
{
    ; OutputDebug 'process id debug ' pdm_pid
    ; MsgBox 'process id ' pdm_pid
    active_id := WinGetID(aTitle) ; 窗口id 不要关 否则获取不到
    ; MsgBox 'window id ' active_id
    ; 2. 打开Excel 文件
    excel := ComObject("Excel.Application")
    workbook := excel.Workbooks.Open("D:\work\dy\xxx.xlsx") ; https://learn.microsoft.com/zh-tw/office/vba/api/excel.workbooks.open
    ; 强制关闭受保护视图(如果触发)
    try {
        if (workbook.ProtectedViewWindow.Visible) {
            workbook.ProtectedViewWindow.Visible := false
        }
    }
    sheet := workbook.Sheets(1)
    row := 2
    baseDir := "D:\work\dy\pic"
    ; 3. 循环操作(复用已启动的软件窗口)
    Loop {
        dirName := sheet.Cells(row, 1).Value
        url := sheet.Cells(row, 7).Value
        if (dirName = "" || url = ""){
            break
        }else{
            ; MsgBox 'dirName' dirName
            ; MsgBox 'url' url
            if WinExist(aTitle){
                WinActivate ; 使用由 WinExist 找到的窗口
            }
            else{
                MsgBox "全景下载大师窗口未激活,请手动打开!"
                if WinWaitActive(aTitle, , 3)
                    MsgBox "全景下载大师窗口已激活!"
            }
            WinActivate(aTitle)
            WinWaitActive(aTitle)
            Sleep(200)
            CoordMode("Mouse", "Window") ; 鼠标坐标以窗口为参考
            MouseClick("left", 246, 146) ; 246, 146 是窗口内的相对坐标,用 Window Spy 工具获取
            Sleep(200)
            Send("{VK15}") ; 尝试切换到英文输入法(部分输入法支持)
            Sleep(200)
            Send("^a")      ; 全选
            Sleep(100)
            Send("{Del}")   ; 删除
            Sleep(100)
            A_Clipboard := url
            Sleep(100)
            Send("^v")      ; 粘贴内容
            Sleep(100)
            Send("{Enter}") ; 回车确认
            targetDir := baseDir "\" dirName
            if !DirExist(targetDir)
                DirCreate(targetDir)
            Sleep(10000) ;等 10s 加载
            WinGetPos(,, &winW, &winH, aTitle) ; 获取窗口宽高
            x := winW // 2                  ; 水平居中
            y := winH - 200                 ; 底部往上移200像素
            CoordMode("Mouse", "Window")
            MouseMove(x, y)
            Sleep(1000)
            MouseClick("right")             ; 右键点击
            Sleep(1000)
            Send("{Down}")                  ; 选中第一个选项
            Sleep(1000)
            Send("{Enter}")                 ; 确认
            Sleep(1000)
            WinWaitActive("选择文件夹")
            Sleep(500)
            Send("^a")
            Sleep(100)
            Send("{Del}")
            Sleep(100)
            A_Clipboard := targetDir
            Send("^v")
            Sleep(300)
            Send("{Enter}") ; 确认输入
            Sleep(1000)
            ControlClick("Button1","选择文件夹",,,2) ; 点击选择文件夹按钮
            Sleep(5000)
            WinClose("pic - 文件资源管理器")
            Sleep(3000)
            isWait := 0
            Loop {
                now := FormatTime(, "yyyy-MM")
                imgDir := "C:\Users\huawe\Documents\ShareX\Screenshots\" SubStr(now, 1, 4) "-" SubStr(now, 6, 2)
                FileDelete imgDir "\*.png"
                FileDelete imgDir "\*.jpg"
                FileDelete imgDir "\*.txt"
                Sleep(1000)
                WinActivate(aTitle)
                Sleep(1000)
                RunWait('D:\App\ShareX\ShareX.exe -ActiveWindow',,"Hide")
                Sleep(10000) ; 等待截图保存
                latestFile := ""
                Loop Files , imgDir "\*.jpg"
                    latestFile := imgDir "\" A_LoopFileName
                if latestFile = "" {
                    MsgBox("未找到截图文件!")
                    isWait := 1
                    break
                }else{
                    ; OCR 识别
                    txtFile := RegExReplace(latestFile, "\.(jpg|png)$", "")
                    RunWait('tesseract "' latestFile '" "' txtFile '" -l chi_sim',, "Hide")
                    ocrText := FileRead(txtFile ".txt","UTF-8")
                    MsgBox (ocrText)
                    if InStr(ocrText, "还原出的全景图保存在") {
                        break
                    }else{
                        if InStr(ocrText,"全部处理完成"){
                            break
                        }else{
                            if InStr(ocrText,"空闲"){
                                break
                            }else{
                                Sleep(60000) ;一分钟截图一次
                            }
                        }
                    }
                }
            }
            if(isWait = 1){
                Sleep(1200000)
            }
        }
        ; 后续操作...
        row++
    }
}
else
{
    MsgBox "WinWait timed out."
    ExitApp
}

需要的软件

一次接口nginx502的排查经历

接口是一个生成全景作品的接口,接口会经历下载、切图、上传三个步骤。接口大部分时间运行是正常的,图片数量过多时会Nginx502。

排查经历

首先了解502是什么类型的错误,502 错误表明 Nginx 作为代理服务器时,无法从上游服务PHP-FPM获取有效响应。

先排查nginx配置的超时时间

是否超时时间过短导致的服务断开。nginx超时相关配置为/www/server/nginx/conf/proxy.conf

proxy_connect_timeout 60;# 连接上游超时
proxy_read_timeout 300;# 读取响应超时
proxy_send_timeout 120;# 发送请求超时

调大连接上游超时和读取响应超时,程序在多图时依然502。

考虑调整php.ini配置

看到接口设置了set_time_limit(0);和ini配置max_execution_time一样只影响脚本本身执行的时间,已经设置了不超时,可是程序还是提前退出了。

考虑调整php-fpm配置

/www/server/php/73/etc/php-fpm.conf

其中有一个配置是

request_terminate_timeout = 300s #整个PHP进程生存时间

进程超时优先大于脚本执行超时,如下图示:

所以会出现set_time_limit(0);不生效

临时解决

调大上述参数

终极解决

切图任务不要放在控制器处理,改用消息队列。

小程序web-view和原生程序交互

交互的前提是安装jssdk

npm i weixin-js-sdk

官方手册:开放能力 / web-view

实现跳转到原生程序的某个页面
wx.miniProgram.navigateTo({url: '/pages/xxx/xxx?from=xx&id=xxx&phone=xxx'});//可以携带参数,此外可以支持redirectTo等详见手册
onLoad(options) {}//小程序对应页面拿到参数
实现小程序分享web-view页面
//H5
wx.miniProgram.postMessage({
    data: {
        type: "share",// 标识分享操作
        title: xxx,// 动态标题
        path: "/pages/xxx/xxx?isofficial=1&pageurl=" + pageurl // 目标路径
    }
});
//Mini
<web-view id="webView1" src="{{pageurl}}" bindmessage="handleMessage">
handleMessage(e) {
    const msgData = e.detail.data[e.detail.data.length - 1]; // 取最新一次消息
    if (msgData.type === "share") {
        this.setData({
            shareTitle: msgData.title,    // 更新分享标题
            sharePath: msgData.path       // 更新分享路径
        });
    }
}
onShareAppMessage() {
    return {
        title: this.data.shareTitle,
        path: this.data.sharePath       // 携带动态路径
    };
}
//此时小程序分享的卡片打开就是这个web-view内的H5页面