0. 前言
最近春节放假回家时,感觉之前实现用路由器实现的移动扫描功能界面有点太难看了.......因此重新覆写了代码,在此做一总结。
1. 之前的相关文章
基于Sane成功解决路由器改OpenWrt打印扫描服务器的手机移动端(IOS、Android)扫描功能实现问题
【2024.1.2更新】windows10环境下vscode配置go开发环境及热加载Air框架设置记录
实现openwrt打印扫描服务器移动端扫描的配置服务文件
TP-LINK-TL-WR703N(原装)制作打印服务器过程记录整理
2. SANE协议简介
为了让新读者更快了解本篇文章的作用及意义,这里引用一下博主FDWMin对SANE协议的简要介绍:
Linux下通用扫描仪API——SANE( Scanner Access Now Easy)
SANE( Scanner Access Now Easy),是一个应用程序编程接口(API),它提供给任何光栅图像扫描仪硬件标准化的访问(平板扫描仪,手持式扫描仪,视频和静止相机,图像采集卡等。 )。该api是公共领域,它的讨论和发展,对所有人开放。目前的源代码是UNIX(包括GNU / Linux)的和GNU通用公共许可证(下可用SANE API可用于专有应用程序和后端为好)。
SANE是一种通用扫描仪接口。这样的通用接口的价值在于,它允许写入每个图像采集装置,而不是为每个设备和应用的一个驱动器只有一个驱动器。所以,如果你有三个应用程序和四个设备,传统上你不得不写12次不同的程序(3*4种驱动程序); 有了 SANE,这个数字减少到7(3+4种驱动程序)。当然,储蓄获得的越来越多的驱动程序和/或应用程序被添加更大。
不仅SANE减少开发时间和代码重复,这也引发了在哪些应用程序可以工作的水平。这样,就会使这在以前是闻所未闻的,在UNIX世界的应用。虽然SANE主要是针对UNIX环境中,该标准已被精心设计,使之可以实现在几乎任何硬件或操作系统的API。
虽然SANE是“Scanner Access Now Easy”的首字母缩写的希望当然是SANE在某种意义上确实是明智的,这将是很容易实现的API,同时适应今天的扫描仪硬件和应用程序所需的所有功能。具体而言,SANE应足够宽,以适应装置,例如扫描仪,数码相机和摄像机,以及如图像文件的过滤器的虚拟设备。
关于SANE更详细的介绍和使用建议直接参考SANE官网:
SANE - Scanner Access Now Easy
简介中的重点是SANE支持Unix系统,Unix系统在我的认知中与嵌入式系统相近(非该专业,个人观点),这意味着SANE可以在路由器这样的小型设备中运行。
3. 代码重构
之前扫描服务器的移动端实现是通过对github上gyscos编写的RemoteScanViewer项目进行修改后实现的。
修改后实现的移动扫描端界面:
尽管项目能够正常运行,但是界面确实有点太难看了,而且HTML前端采用的CDN 是外网链接,如果采用本地引入js文件会莫名其妙报错,导致页面没有办法显示,虽然后面采用国内BootCDN替换了原来的CDN链接,但这并不能解决网络问题,意味着每次运行服务必须处于联网状态。
BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务
除此之外, 该项目对移动端的支持也存在问题,使用移动端进行访问时,会出现页面组件混乱的现象。
鉴于此,我决定对该项目进行重写。
3.1 代码结构(或者说技术栈,不知道这么说对不对)
前端——由于不打算采用Node.js(因为这意味着又得在OpenWrt上装包),所以没法使用Vue,尝试过在原生HTML上通过本地文件引入Vue.js,可以使用部分Vue方法,但UI组件库使用时有问题,遂放弃,采用原生HTML。
后端——采用Golang编写,具体Web框架采用gin框架,相比gframe的庞大繁杂,gin简单上手快,更符合项目需求。
通信——采用本地文件引入的方式引入axios.js,使用axios进行请求和响应。
3.2 部分代码展示及解释
执行扫描的主要函数
// 执行SANE扫描cmd命令并返回实时扫描进度 func ScanProgress(device string, dpi string, mode string, fileName string) (float64, error) { ScanEnd = "0" //声明TIF_DEST变量 TIF_DEST := DEST_DIR + "/" + fileName + ".tiff" //声明PDF_DEST变量 PDF_DEST := DEST_DIR + "/" + fileName + ".pdf" //声明TBN_DEST变量 TBN_DEST := DEST_DIR + "/thumb/" + fileName + ".png" cmdName := "scanimage" cmdArgs := []string{"--progress", "--device-name", device, "--resolution", dpi, "--mode", mode, "--format", "tiff", "--output-file", TIF_DEST} // --device-name设备名称 --resolution分辨率 --mode扫描模式 --progress命令用于显示扫描的进度 // 打印即将执行的命令 fmt.Printf("将要执行的命令:%s %s\n", cmdName, strings.Join(cmdArgs, " ")) ScanProgressValue = 60.00 cmd := exec.Command(cmdName, cmdArgs...) var stderr bytes.Buffer cmd.Stderr = &stderr stdout, err := cmd.StdoutPipe() if err != nil { fmt.Printf("执行cmd.StdoutPipe()出错:%s\n", err) fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) return 0, err } if err := cmd.Start(); err != nil { fmt.Printf("执行cmd.Start()出错:%s\n", err) fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) return 0, err } go func() { reader := bufio.NewReader(stdout) for { line, err := reader.ReadString('\n') fmt.Printf("当前行内容:%s\n", line) // 添加调试输出 if err != nil || err == io.EOF { break } if strings.Contains(line, "Progress:") { fmt.Printf("发现进度信息:%s\n", line) // 添加调试输出 progressStr := strings.Split(line, ":")[1] // 去除百分号 progressStr = strings.TrimSpace(progressStr[:len(progressStr)-1]) progress, err := strconv.ParseFloat(progressStr, 64) if err != nil { fmt.Println("解析进度信息时出现错误:", err) // 添加调试输出 // 如果解析出错,可以尝试打印 progressStr 看看原始内容 continue } fmt.Printf("当前 ScanProgressValue:%f\n", progress) // 将进度保存到全局变量 ScanProgressValue = progress } } }() if err := cmd.Wait(); err != nil { fmt.Printf("执行cmd.Wait()出错:%s\n", err) fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) return 0, err } ScanProgressValue = 100.00 fmt.Printf("tiff格式转换...") //将获得的tiff转换 //转为pdf command1 := "convert" args1 := []string{TIF_DEST, PDF_DEST} cmd1 := exec.Command(command1, args1...) err1 := cmd1.Run() if err1 != nil { fmt.Printf("执行tiff转pdf出错: %s", err1) fmt.Println("尝试使用golang转换pdf...") // 读取 TIF 文件 tifData, err := os.ReadFile(TIF_DEST) if err != nil { fmt.Println("Error reading TIF file:", err) } // 解码 TIF 数据 img, err := tiff.Decode(bytes.NewReader(tifData)) if err != nil { fmt.Println("Error decoding TIF image:", err) } // 获取 TIF 图像的宽度和高度 bounds := img.Bounds() imageWidth := float64(bounds.Dx()) imageHeight := float64(bounds.Dy()) // 创建 PDF 文件 pdf := gofpdf.New("P", "mm", "A4", "") pdf.AddPage() // 将 TIF 图像转换为 PDF 图像 imageIndex := pdf.RegisterImageOptions(TIF_DEST, gofpdf.ImageOptions{ImageType: "TIFF", ReadDpi: true}) if imageIndex == nil { fmt.Println("Error registering image for PDF:", pdf.Error()) } pdf.ImageOptions(TIF_DEST, 0, 0, imageWidth, imageHeight, false, gofpdf.ImageOptions{ImageType: "TIFF"}, 0, "") // 保存 PDF 文件 err = pdf.OutputFileAndClose(PDF_DEST) if err != nil { fmt.Println("Error saving PDF file:", err) } fmt.Println("Conversion from TIF to PDF successful!") } //转为png command2 := "convert" args2 := []string{TIF_DEST, TBN_DEST} cmd2 := exec.Command(command2, args2...) err2 := cmd2.Run() if err2 != nil { fmt.Printf("执行tiff转png出错: %s\n", err2) fmt.Println("尝试使用golang转png...") // 读取 TIF 文件 tifData, err := os.ReadFile(TIF_DEST) if err != nil { fmt.Println("Error reading TIF file:", err) } // 解码 TIF 数据 img, err := tiff.Decode(bytes.NewReader(tifData)) if err != nil { fmt.Println("Error decoding TIF image:", err) } // 创建输出 PNG 文件 outfile, err := os.Create(TBN_DEST) if err != nil { fmt.Println("Error creating PNG file:", err) } defer outfile.Close() // 将图像以 PNG 格式写入文件 if err := png.Encode(outfile, img); err != nil { fmt.Println("Error encoding PNG image:", err) } fmt.Println("Conversion from TIF to PNG successful!") } fmt.Printf("tiff转换完成") ScanEnd = "1" return 0, nil }
这里采用了和之前gyscos那个项目的同样方法,即采用SANE的命令行工具scanimage,通过Go执行scanimage命令,实现扫描功能的控制。
scanimage - flexible command-line-frontend including support for pnm and tiff output (included in sane-backends).
对于Go,其实我们还有另一种方法来实现SANE的扫描功能,即go-sane包:
go-sane包封装了针对go语言的SANE协议实现函数,但是这个包有一个缺陷,其采用了cgo:
这意味着我们不能直接通过go的跨平台编译功能,要通过其他方法才能实现跨平台编译:
含有CGO代码的项目如何实现跨平台编译
这过于麻烦了,因此我们采用scanimage命令行工具实现扫描功能即可。
3.3 编译工具
编译采用的是liteide这款IDE:
IDE的界面:
界面比较简朴,但是它最显眼的地方是编译环境的设置非常方便:
注意跨平台编译时cgo要关掉,因此CGO_ENABLED等于0
3.4 前端组件库
组件丰富度还行,够用了(当然后面还是发现了一点Bug…),最主要是它声称支持移动端显示。
在使用它的导航菜单组件时,我发现在PC端时显示正常:
变成移动端时出现问题:
可以看到移动端显示时,并菜单项目并没有按竖向排布,同时右边的三角后里面的内容并没有显示出来…难绷
但是除了这个小问题之外没有再遇到更多的问题,因此还行。
4. 最终效果
4.1 首页
空白是因为里面的东西还没搞,我计划做成一个打印和扫描一体的一个项目,还没搞完。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/101552.html