├── LICENSE ├── .gitignore ├── logo.png ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── go-build-test.yml │ ├── go-binary-release.yml │ └── golangci-lint.yml ├── tools ├── upload │ ├── req.json │ ├── resp.json │ ├── upload_test.go │ └── upload.go ├── download │ ├── urls.go │ ├── urls_test.go │ ├── download_test.go │ └── download.go └── regs │ ├── regs.go │ └── regs_test.go ├── main.go ├── go.mod ├── go.sum ├── Makefile ├── cmd ├── du.go ├── root.go ├── u.go └── d.go ├── README.md └── .golangci.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/tmp 2 | **/images 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarvanstack/markpic/HEAD/logo.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/upload/req.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | "C:\\Users\\dengjiawen\\Downloads\\yuque_diagram.jpg" 4 | ] 5 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Jarvan的博客 4 | url: https://jarvans.com 5 | about: 大厂程序员, 公众号 硬核的Jarvan 6 | -------------------------------------------------------------------------------- /tools/upload/resp.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "result": [ 4 | "https://markdown-1304103443.cos.ap-guangzhou.myqcloud.com/2022-02-04yuque_diagram.jpg" 5 | ] 6 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package main 6 | 7 | import "github.com/jarvanstack/markpic/cmd" 8 | 9 | func main() { 10 | cmd.Execute() 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jarvanstack/markpic 2 | 3 | go 1.18 4 | 5 | require github.com/spf13/cobra v1.6.1 6 | 7 | require ( 8 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 9 | github.com/spf13/pflag v1.0.5 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /tools/download/urls.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import "net/url" 4 | 5 | // TrimUrl 删除 URL 中的参数 6 | func TrimUrl(urlStr string) string { 7 | u, err := url.Parse(urlStr) 8 | if err != nil { 9 | panic(err) 10 | } 11 | return u.Scheme + "://" + u.Host + u.Path 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/go-build-test.yml: -------------------------------------------------------------------------------- 1 | name: Go package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up Go 13 | uses: actions/setup-go@v4 14 | with: 15 | go-version: '1.15' 16 | 17 | - name: Build 18 | run: go build -v ./... 19 | 20 | - name: Test 21 | run: go test -json > TestResults-${{ matrix.go-version }}.json 22 | - name: Upload Go test results 23 | uses: actions/upload-artifact@v3 24 | with: 25 | name: Go-results-${{ matrix.go-version }} 26 | path: TestResults-${{ matrix.go-version }}.json 27 | retention-days: 5 -------------------------------------------------------------------------------- /.github/workflows/go-binary-release.yml: -------------------------------------------------------------------------------- 1 | name: Auto Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - 21 | name: Set up Go 22 | uses: actions/setup-go@v2 23 | with: 24 | go-version: 1.17 25 | - 26 | name: Run GoReleaser 27 | uses: goreleaser/goreleaser-action@v2 28 | with: 29 | # either 'goreleaser' (default) or 'goreleaser-pro' 30 | distribution: goreleaser 31 | version: latest 32 | args: release --rm-dist 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 3 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 6 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 7 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 8 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 请求新功能 Request new features 3 | about: 提出你期待的功能特性 Come up with the features you expect 4 | --- 5 | 6 | ### 你在什么场景下需要该功能? In what scenarios do you need this function? 7 | 8 | 12 | 13 | ### 描述最优的解决方案 Describe the optimal solution 14 | 15 | 19 | 20 | ### 描述候选解决方案 Describe the candidate solution 21 | 22 | 26 | 27 | ### 其他信息 Other information 28 | 29 | -------------------------------------------------------------------------------- /tools/regs/regs.go: -------------------------------------------------------------------------------- 1 | package regs 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var imgUrlReg = regexp.MustCompile(`!\[.*\]\((.*)\)`) 9 | 10 | // GetRemoteImg 获取markdown中的远程图片链接 11 | func GetRemoteImg(text string) []string { 12 | sss := imgUrlReg.FindAllStringSubmatch(text, -1) 13 | 14 | var urls []string 15 | for _, ss := range sss { 16 | for _, s := range ss { 17 | if strings.HasPrefix(s, "![") { 18 | continue 19 | } 20 | if strings.HasPrefix(s, "http") { 21 | urls = append(urls, s) 22 | } 23 | } 24 | } 25 | 26 | return urls 27 | } 28 | 29 | // GetRemoteImg 获取markdown中的所有本地图片链接 30 | func GetLocalImg(text string) []string { 31 | sss := imgUrlReg.FindAllStringSubmatch(text, -1) 32 | 33 | var urls []string 34 | for _, ss := range sss { 35 | for _, s := range ss { 36 | if strings.HasPrefix(s, "![") { 37 | continue 38 | } 39 | if strings.HasPrefix(s, "http") { 40 | continue 41 | } 42 | urls = append(urls, s) 43 | } 44 | } 45 | 46 | return urls 47 | } 48 | -------------------------------------------------------------------------------- /tools/download/urls_test.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import "testing" 4 | 5 | func TestTrim(t *testing.T) { 6 | type args struct { 7 | urlStr string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | want string 13 | }{ 14 | { 15 | args: args{ 16 | urlStr: `https://cdn.nlark.com/yuque/0/2022/png/2397010/1668767432343-635ad8ea-c4ac-42bd-ae37-91d6bf00d541.png#averageHue=%2370673e&clientId=ue647dcef-b8ea-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1040&id=u0b01f546&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1040&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=134394&status=done&style=none&taskId=u71a2fad7-b512-44b5-85a2-3f682520dca&title=&width=1920`, 17 | }, 18 | want: "/80/v2-aaa43d382d9acc01cfe060b0228f58c4_720w.webp", 19 | }, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | if got := TrimUrl(tt.args.urlStr); got != tt.want { 24 | t.Errorf("Trim() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tools/upload/upload_test.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | /* 8 | curl --location --request POST 'http://127.0.0.1:36677/upload' --header 'Content-Type: application/json' --data-raw '{ 9 | "list": [ 10 | "/mnt/c/Users/dengjiawen/Downloads/ddd1.md" 11 | ] 12 | }' 13 | */ 14 | 15 | func TestUploaderImpl_Upload(t *testing.T) { 16 | u := NewUploader() 17 | type args struct { 18 | localPath string 19 | } 20 | tests := []struct { 21 | name string 22 | args args 23 | wantErr bool 24 | }{ 25 | { 26 | args: args{ 27 | localPath: `C:\c_code\markpic\images\1668760978594-47a93e74-e87e-49a3-946c-466ffd4f952d.jpeg`, 28 | }, 29 | wantErr: false, 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | got, err := u.Upload(tt.args.localPath) 35 | if (err != nil) != tt.wantErr { 36 | t.Errorf("UploaderImpl.Upload() error = %v, wantErr %v", err, tt.wantErr) 37 | return 38 | } 39 | t.Logf("UploaderImpl.Upload() = %v", got) 40 | }) 41 | } 42 | } 43 | 44 | func TestLocalIp(t *testing.T) { 45 | got := LocalIp() 46 | t.Logf("LocalIp() = %v", got) 47 | } 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # config 2 | bindir = ./bin 3 | releaseDir = ./release 4 | mainFile = ./main.go 5 | 6 | # var 7 | make_dir:=$(shell pwd) 8 | app_name:=$(shell basename $(make_dir)) 9 | version=$(shell git describe --tags --always) 10 | 11 | ## build: Builds the project 12 | .PHONY: build 13 | build: 14 | GOOS=linux GOARCH=amd64 go build -o $(bindir)/$(app_name).linux $(mainFile) 15 | GOOS=darwin GOARCH=amd64 go build -o $(bindir)/$(app_name).darwin $(mainFile) 16 | GOOS=windows GOARCH=amd64 go build -o $(bindir)/$(app_name).exe $(mainFile) 17 | 18 | ## release: Builds the project for release 19 | .PHONY: release 20 | release: clean build 21 | tar -zcvf $(releaseDir)/$(app_name)-linux-$(version).tar.gz $(bindir)/$(app_name).linux 22 | tar -zcvf $(releaseDir)/$(app_name)-darwin-$(version).tar.gz $(bindir)/$(app_name).darwin 23 | tar -zcvf $(releaseDir)/$(app_name)-exe-$(version).tar.gz $(bindir)/$(app_name).exe 24 | git tag -d $(version) 25 | ## clean: Cleans the project 26 | .PHONY: clean 27 | clean: 28 | rm -rf $(bindir)/* 29 | rm -rf $(releaseDir)/* 30 | rm -rf images/* 31 | go clean 32 | 33 | ## help: Show this help info. 34 | .PHONY: help 35 | help: Makefile 36 | @printf "\nUsage: make ...\n\nTargets:\n" 37 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' 38 | @echo "$$USAGE_OPTIONS -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 报告缺陷 Bug Report 3 | about: 报告缺陷以帮助我们改进 Report defects to help us improve 4 | --- 5 | 6 | ### 描述问题 Describe the problem 7 | 8 | 12 | 13 | ````````markdown 14 | 如果是解析渲染问题的话请在此处贴入 Markdown 原文 15 | If it is a problem of parsing and rendering, please post the original Markdown here 16 | ```````` 17 | 18 | ### 期待的结果 Expected result 19 | 20 | 24 | 25 | ### 截屏或录像 Screenshot or video 26 | 27 | 34 | 35 | ### 版本环境 Version environment 36 | 37 | * 版本 Version: 38 | * 操作系统 Operating system: 39 | * 浏览器(如果使用)Browser (if used): 40 | 41 | ### 其他信息 Other information 42 | 43 | -------------------------------------------------------------------------------- /cmd/du.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // duCmd represents the markpic command 13 | var duCmd = &cobra.Command{ 14 | Use: "du", 15 | Short: "先 d(下载) 再 u(上传)", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | from := cmd.Flag("from").Value.String() 18 | dir := cmd.Flag("dir").Value.String() 19 | 20 | err := du(from, dir) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | 26 | }, 27 | } 28 | 29 | func du(from, dir string) error { 30 | fmt.Println("[下载-上传]") 31 | 32 | toLocal := from + downloadFilePrefix 33 | err := d(from, toLocal, dir) 34 | if err != nil { 35 | fmt.Println(err) 36 | return err 37 | } 38 | 39 | fromLocal := toLocal 40 | toRemote := from + uploadFilePrefix 41 | err = u(fromLocal, toRemote) 42 | if err != nil { 43 | fmt.Println(err) 44 | return err 45 | } 46 | 47 | fmt.Println("[下载-上传完成]", fromLocal, toRemote) 48 | 49 | return nil 50 | } 51 | 52 | func init() { 53 | rootCmd.AddCommand(duCmd) 54 | 55 | // Here you will define your flags and configuration settings. 56 | 57 | // Cobra supports Persistent Flags which will work for this command 58 | // and all subcommands, e.g.: 59 | // duCmd.PersistentFlags().String("foo", "", "A help for foo") 60 | 61 | // Cobra supports local flags which will only run when this command 62 | // is called directly, e.g.: 63 | // duCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 64 | } 65 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | downloadFilePrefix = ".download.md" 16 | uploadFilePrefix = ".upload.md" 17 | ) 18 | 19 | var ( 20 | from string 21 | dir string 22 | ) 23 | 24 | // rootCmd represents the base command when called without any subcommands 25 | var rootCmd = &cobra.Command{ 26 | Use: "markpic [command]", 27 | Short: "一键将 markdown 中的所有图片下载到本地, 并通过 picgo 上传到图床", 28 | Args: cobra.MatchAll(cobra.ArbitraryArgs), // 任意参数 29 | Run: func(cmd *cobra.Command, args []string) { 30 | var from, dir string 31 | 32 | if len(args) > 0 { 33 | from = args[0] 34 | } else { 35 | from = cmd.Flag("from").Value.String() 36 | } 37 | 38 | if len(args) > 1 { 39 | dir = args[1] 40 | } else { 41 | dir = cmd.Flag("dir").Value.String() 42 | } 43 | 44 | err := du(from, dir) 45 | if err != nil { 46 | fmt.Println(err) 47 | return 48 | } 49 | }, 50 | } 51 | 52 | // Execute adds all child commands to the root command and sets flags appropriately. 53 | // This is called by main.main(). It only needs to happen once to the rootCmd. 54 | func Execute() { 55 | err := rootCmd.Execute() 56 | if err != nil { 57 | os.Exit(1) 58 | } 59 | } 60 | 61 | func init() { 62 | // Here you will define your flags and configuration settings. 63 | // Cobra supports persistent flags, which, if defined here, 64 | // will be global for your application. 65 | 66 | rootCmd.PersistentFlags().StringVarP(&from, "from", "f", "source.md", 67 | "需要处理的源文件") 68 | rootCmd.PersistentFlags().StringVarP(&dir, "dir", "d", "images/", 69 | "图片存放的目录") 70 | 71 | // Cobra also supports local flags, which will only run 72 | // when this action is called directly. 73 | // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 74 | 75 | } 76 | -------------------------------------------------------------------------------- /tools/download/download_test.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestDownLoadImpl_DownLoad(t *testing.T) { 9 | var dir string = "tmp/" 10 | 11 | // 删除临时文件 12 | os.RemoveAll(dir) 13 | 14 | d := NewDownLoader(dir) 15 | 16 | type args struct { 17 | url string 18 | } 19 | tests := []struct { 20 | name string 21 | args args 22 | want string 23 | wantErr bool 24 | }{ 25 | { 26 | args: args{ 27 | url: `https://pic1.zhimg.com/80/v2-aaa43d382d9acc01cfe060b0228f58c4_720w.webp`, 28 | }, 29 | want: dir + "v2-aaa43d382d9acc01cfe060b0228f58c4_720w.webp", 30 | wantErr: false, 31 | }, 32 | { 33 | args: args{ 34 | url: `https://cdn.nlark.com/yuque/0/2022/png/2397010/1668767432343-635ad8ea-c4ac-42bd-ae37-91d6bf00d541.png`, 35 | }, 36 | want: dir + "1668767432343-635ad8ea-c4ac-42bd-ae37-91d6bf00d541.png", 37 | wantErr: false, 38 | }, 39 | { 40 | args: args{ 41 | url: `https://cdn.nlark.com/yuque/__puml/2aa76dcc256f08335083bd039b5b4e49.svg#lake_card_v2=eyJ0eXBlIjoicHVtbCIsImNvZGUiOiJAc3RhcnR1bWxcblxufEEgU2VjdGlvbnxcbnN0YXJ0XG46c3RlcDE7XG58I0FudGlxdWVXaGl0ZXxCIFNlY3Rpb258XG46c3RlcDI7XG46c3RlcDM7XG58QSBTZWN0aW9ufFxuOnN0ZXA0O1xufEIgU2VjdGlvbnxcbjpzdGVwNTtcbnN0b3BcblxuQGVuZHVtbCIsInVybCI6Imh0dHBzOi8vY2RuLm5sYXJrLmNvbS95dXF1ZS9fX3B1bWwvMmFhNzZkY2MyNTZmMDgzMzUwODNiZDAzOWI1YjRlNDkuc3ZnIiwiaWQiOiJtanN4OCIsIm1hcmdpbiI6eyJ0b3AiOnRydWUsImJvdHRvbSI6dHJ1ZX0sImNhcmQiOiJkaWFncmFtIn0=`, 42 | }, 43 | want: dir + "2aa76dcc256f08335083bd039b5b4e49.svg", 44 | wantErr: false, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | got, err := d.DownLoad(tt.args.url) 50 | if (err != nil) != tt.wantErr { 51 | t.Errorf("DownLoadImpl.DownLoad() error = %v, wantErr %v", err, tt.wantErr) 52 | return 53 | } 54 | if got != tt.want { 55 | t.Errorf("DownLoadImpl.DownLoad() = %v, want %v", got, tt.want) 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tools/regs/regs_test.go: -------------------------------------------------------------------------------- 1 | package regs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestGetMarkdownImageUrls(t *testing.T) { 9 | type args struct { 10 | text string 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | want []string 16 | }{ 17 | { 18 | args: args{ 19 | text: `- 阅读: review 自己的文章, 就像代码一样, 你没有 review 的习惯, 但是这个习惯是至关重要的, 对比, 优化, 反馈才能进步 20 | - 分享: 关于一些知识, 比如说 no tech 思考方法, 可以分享给别人 21 | - 知识网络: 类似 notion 的知识网络, 也许有用的 22 | 23 | **主要是阅读** 24 | ![](https://cdn.nlark.com/yuque/__puml/2aa76dcc256f08335083bd039b5b4e49.svg#lake_card_v2=eyJ0eXBlIjoicHVtbCIsImNvZGUiOiJAc3RhcnR1bWxcblxufEEgU2VjdGlvbnxcbnN0YXJ0XG46c3RlcDE7XG58I0FudGlxdWVXaGl0ZXxCIFNlY3Rpb258XG46c3RlcDI7XG46c3RlcDM7XG58QSBTZWN0aW9ufFxuOnN0ZXA0O1xufEIgU2VjdGlvbnxcbjpzdGVwNTtcbnN0b3BcblxuQGVuZHVtbCIsInVybCI6Imh0dHBzOi8vY2RuLm5sYXJrLmNvbS95dXF1ZS9fX3B1bWwvMmFhNzZkY2MyNTZmMDgzMzUwODNiZDAzOWI1YjRlNDkuc3ZnIiwiaWQiOiJtanN4OCIsIm1hcmdpbiI6eyJ0b3AiOnRydWUsImJvdHRvbSI6dHJ1ZX0sImNhcmQiOiJkaWFncmFtIn0=)`, 25 | }, 26 | want: []string{ 27 | `https://cdn.nlark.com/yuque/__puml/2aa76dcc256f08335083bd039b5b4e49.svg#lake_card_v2=eyJ0eXBlIjoicHVtbCIsImNvZGUiOiJAc3RhcnR1bWxcblxufEEgU2VjdGlvbnxcbnN0YXJ0XG46c3RlcDE7XG58I0FudGlxdWVXaGl0ZXxCIFNlY3Rpb258XG46c3RlcDI7XG46c3RlcDM7XG58QSBTZWN0aW9ufFxuOnN0ZXA0O1xufEIgU2VjdGlvbnxcbjpzdGVwNTtcbnN0b3BcblxuQGVuZHVtbCIsInVybCI6Imh0dHBzOi8vY2RuLm5sYXJrLmNvbS95dXF1ZS9fX3B1bWwvMmFhNzZkY2MyNTZmMDgzMzUwODNiZDAzOWI1YjRlNDkuc3ZnIiwiaWQiOiJtanN4OCIsIm1hcmdpbiI6eyJ0b3AiOnRydWUsImJvdHRvbSI6dHJ1ZX0sImNhcmQiOiJkaWFncmFtIn0=`, 28 | }, 29 | }, 30 | { 31 | args: args{ 32 | text: ` 33 | ![](http://1.png) 34 | 发顺丰的 35 | ![](http://2.png) 36 | `, 37 | }, 38 | want: []string{ 39 | "http://1.png", 40 | "http://2.png", 41 | }, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | if got := GetRemoteImg(tt.args.text); !reflect.DeepEqual(got, tt.want) { 47 | t.Errorf("GetMarkdownImageUrls() = %v, want %v", got, tt.want) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | # pull-requests: read 13 | 14 | jobs: 15 | golangci: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21' 23 | cache: false 24 | - name: golangci-lint 25 | uses: golangci/golangci-lint-action@v3 26 | with: 27 | # Require: The version of golangci-lint to use. 28 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 29 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 30 | version: v1.54 31 | 32 | # Optional: working directory, useful for monorepos 33 | # working-directory: somedir 34 | 35 | # Optional: golangci-lint command line arguments. 36 | # 37 | # Note: By default, the `.golangci.yml` file should be at the root of the repository. 38 | # The location of the configuration file can be changed by using `--config=` 39 | # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 40 | 41 | # Optional: show only new issues if it's a pull request. The default value is `false`. 42 | # only-new-issues: true 43 | 44 | # Optional: if set to true, then all caching functionality will be completely disabled, 45 | # takes precedence over all other caching options. 46 | # skip-cache: true 47 | 48 | # Optional: if set to true, then the action won't cache or restore ~/go/pkg. 49 | # skip-pkg-cache: true 50 | 51 | # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. 52 | # skip-build-cache: true 53 | 54 | # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. 55 | # install-mode: "goinstall" -------------------------------------------------------------------------------- /cmd/u.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | "strings" 11 | 12 | "github.com/jarvanstack/markpic/tools/regs" 13 | "github.com/jarvanstack/markpic/tools/upload" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | // uCmd represents the u command 19 | var uCmd = &cobra.Command{ 20 | Use: "u", 21 | Short: "将 markdown 中的所有本地图片通过 picgo 上传到图床", 22 | Long: `将 markdown 中的所有本地图片通过 picgo 上传到图床. For example: 23 | 24 | markpic u --from README.md `, 25 | Run: func(cmd *cobra.Command, args []string) { 26 | from := cmd.Flag("from").Value.String() 27 | fmt.Println("[上传] ", from) 28 | 29 | to := from + uploadFilePrefix 30 | err := u(from, to) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | 36 | fmt.Println("[上传完成]", from, to) 37 | }, 38 | } 39 | 40 | func u(from, to string) error { 41 | // 输入 42 | formFile, err := os.Open(from) 43 | if err != nil { 44 | fmt.Println(err) 45 | return err 46 | } 47 | defer formFile.Close() 48 | fromBuf := bufio.NewReader(formFile) 49 | 50 | // 输出 51 | // 输出 52 | toFile, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY, 0666) 53 | if err != nil { 54 | fmt.Println(err) 55 | return err 56 | } 57 | defer toFile.Close() 58 | toBuf := bufio.NewWriter(toFile) 59 | 60 | // 下载器 61 | uploader := upload.NewUploader() 62 | 63 | isSkip := false 64 | 65 | // 读取 66 | for { 67 | line, err := fromBuf.ReadString('\n') 68 | if err != nil { 69 | break 70 | } 71 | 72 | if strings.HasPrefix(line, "```") { 73 | isSkip = !isSkip 74 | } 75 | 76 | if isSkip { 77 | _, _ = toBuf.WriteString(line) 78 | continue 79 | } 80 | 81 | // 获取 URL 82 | urls := regs.GetLocalImg(line) 83 | for _, url := range urls { 84 | // 下载 85 | newUrl, err := uploader.Upload(url) 86 | if err != nil { 87 | fmt.Println(err) 88 | return err 89 | } 90 | 91 | // 替换 92 | line = strings.ReplaceAll(line, url, newUrl) 93 | } 94 | _, err = toBuf.WriteString(line) 95 | if err != nil { 96 | fmt.Println(err) 97 | return err 98 | } 99 | } 100 | err = toBuf.Flush() 101 | 102 | return err 103 | } 104 | 105 | func init() { 106 | rootCmd.AddCommand(uCmd) 107 | 108 | // Here you will define your flags and configuration settings. 109 | 110 | // Cobra supports Persistent Flags which will work for this command 111 | // and all subcommands, e.g.: 112 | // uCmd.PersistentFlags().String("foo", "", "A help for foo") 113 | 114 | // Cobra supports local flags which will only run when this command 115 | // is called directly, e.g.: 116 | // uCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 117 | } 118 | -------------------------------------------------------------------------------- /cmd/d.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | "strings" 11 | 12 | "github.com/jarvanstack/markpic/tools/download" 13 | "github.com/jarvanstack/markpic/tools/regs" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | // dCmd represents the d command 19 | var dCmd = &cobra.Command{ 20 | Use: "d", 21 | Short: "将 markdown 中的图片下载到本地", 22 | Long: `将 markdown 中的图片下载到本地. 例如: 23 | 24 | markpic d --from README.md -dir tmp/`, 25 | Run: func(cmd *cobra.Command, args []string) { 26 | from := cmd.Flag("from").Value.String() 27 | dir := cmd.Flag("dir").Value.String() 28 | fmt.Println("[下载] ") 29 | 30 | to := from + downloadFilePrefix 31 | err := d(from, to, dir) 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | fmt.Println("[下载完成]", from, dir, to) 38 | }, 39 | } 40 | 41 | func d(from, to, dir string) error { 42 | // 输入 43 | formFile, err := os.Open(from) 44 | if err != nil { 45 | fmt.Println(err) 46 | return err 47 | } 48 | defer formFile.Close() 49 | fromBuf := bufio.NewReader(formFile) 50 | 51 | // 输出 52 | toFile, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY, 0666) 53 | if err != nil { 54 | fmt.Println(err) 55 | return err 56 | } 57 | defer toFile.Close() 58 | toBuf := bufio.NewWriter(toFile) 59 | 60 | // 下载器 61 | downloader := download.NewDownLoader(dir) 62 | 63 | isSkip := false 64 | 65 | // 读取 66 | for { 67 | line, err := fromBuf.ReadString('\n') 68 | if err != nil { 69 | break 70 | } 71 | 72 | if strings.HasPrefix(line, "```") { 73 | isSkip = !isSkip 74 | } 75 | 76 | if isSkip { 77 | _, _ = toBuf.WriteString(line) 78 | continue 79 | } 80 | 81 | // 获取 URL 82 | urls := regs.GetRemoteImg(line) 83 | if len(urls) > 0 { 84 | for _, url := range urls { 85 | // 下载 86 | newUrl, err := downloader.DownLoad(url) 87 | if err != nil { 88 | fmt.Println(err) 89 | return err 90 | } 91 | 92 | // 替换 93 | line = strings.ReplaceAll(line, url, newUrl) 94 | } 95 | } 96 | 97 | _, _ = toBuf.WriteString(line) 98 | } 99 | _ = toBuf.Flush() 100 | 101 | return err 102 | } 103 | 104 | func init() { 105 | rootCmd.AddCommand(dCmd) 106 | 107 | // Here you will define your flags and configuration settings. 108 | 109 | // Cobra supports Persistent Flags which will work for this command 110 | // and all subcommands, e.g.: 111 | // dCmd.PersistentFlags().String("foo", "", "A help for foo") 112 | 113 | // Cobra supports local flags which will only run when this command 114 | // is called directly, e.g.: 115 | // dCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | 6 | 7 | 8 |

9 |

10 | 11 | # markpic 12 | 13 | 一键将 markdown 中的所有图片下载到本地, 并通过 picgo 上传到图床 14 | 15 | 16 | ## 安装 17 | 18 | (1) 方法1 通过 go install 直接安装 19 | 20 | ```bash 21 | go install github.com/jarvanstack/markpic@latest 22 | ``` 23 | 24 | (2) 方法2 下载编译好的二进制文件并放到 PATH 中 25 | 26 | 下载地址: 27 | 28 | ## 使用 29 | 30 | ```bash 31 | # 一键将 markdown 中的所有图片下载到本地, 并通过 picgo 上传到图床 32 | markpic test.md 33 | ``` 34 | 35 | ```bash 36 | $ markpic --help 37 | 一键将 markdown 中的所有图片下载到本地, 并通过 picgo 上传到图床 38 | 39 | Usage: 40 | markpic [command] [flags] 41 | markpic [command] 42 | 43 | Available Commands: 44 | completion Generate the autocompletion script for the specified shell 45 | d 将 markdown 中的图片下载到本地 46 | du 先 d(下载) 再 u(上传) 47 | help Help about any command 48 | u 将 markdown 中的所有本地图片通过 picgo 上传到图床 49 | 50 | Flags: 51 | -d, --dir string 图片存放的目录 (default "images/") 52 | -f, --from string 需要处理的源文件 (default "source.md") 53 | -h, --help help for markpic 54 | 55 | Use "markpic [command] --help" for more information about a command. 56 | ``` 57 | 58 | ## 使用实例 59 | 60 | 我们在 windows 有一个 markdown 文件 test.md 61 | 62 | ```markdown 63 | 六边形架构 64 | 65 | ![](https://cdn.nlark.com/yuque/0/2022/png/2397010/1668752212281-41b93ae1-c4c0-4af4-befa-ceaf1f4efb05.png#averageHue=%23f9f9f8&clientId=u39f9e662-ddb8-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7149c748&margin=[object Object]&originHeight=279&originWidth=448&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u7e969d8b-9118-433a-86db-806375faecf&title=) 66 | 67 | ``` 68 | 69 | * 里面的网络图片因为防盗链机制, 无法再其他博客平台上同步 70 | * 即使在 typroa 中设置插入图片时候对网络位置的图片上传也无法上传 71 | 72 | ![无法上传](https://cdn.jarvans.com/blog/2023/2022-02-0420221119171918.png) 73 | 74 | * 所以我们需要将图片下载到本地, 然后再上传到图床 75 | * 但是这个操作如果图片比较多的话比较繁琐, 而且容易遗漏 76 | * 现在 **markpic 可以帮助我们一键将 markdown 中的所有图片下载到本地, 并通过 picgo 上传到图床** 77 | 78 | ```bash 79 | PS C:\c_code\markpic> markpic test.md 80 | [下载-上传] 81 | [下载-上传完成] .\test.md.download.md .\test.md.upload.md 82 | ``` 83 | 84 | test.md.download.md 内容如下 85 | 86 | ```markdown 87 | 六边形架构 88 | 89 | ![image.png](C:\c_code\markpic\images\) 90 | 91 | ``` 92 | 93 | test.md.upload.md 内容如下 94 | 95 | ```markdown 96 | 六边形架构 97 | 98 | ![image.png](https://myweb/1_1668752375501-1bb18730-73da-421a-8c35-de8d29029919.png.png) 99 | 100 | ``` 101 | 102 | 因为自己搭建的图床没有防盗链机制, 所以可以直接在其他平台同步 103 | 104 | ## 注意 105 | 106 | * 该程序调用 PicGo 客户端的接口上传图片, 使用前需要下载 PicGo 客户端并配置好图床, 保持 PicGo 客户端正常运行 107 | 108 | 109 | -------------------------------------------------------------------------------- /tools/upload/upload.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "path/filepath" 11 | "runtime" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | url = "http://localhost:36677/upload" 17 | ) 18 | 19 | var ( 20 | ErrPicgoUpload = errors.New("picgo 上传失败") 21 | ErrPicgoResultIsEmpty = errors.New("picgo 返回值为空") 22 | ) 23 | 24 | var ( 25 | ostype = runtime.GOOS 26 | ) 27 | 28 | type Uploader interface { 29 | // 路径处理 30 | 31 | // 上传, 返回远程绝对路径 32 | Upload(localPath string) (string, error) 33 | } 34 | 35 | type UploaderImpl struct { 36 | } 37 | 38 | type Request struct { 39 | List []string 40 | } 41 | 42 | type Response struct { 43 | Success bool 44 | Result []string 45 | } 46 | 47 | func NewUploader() Uploader { 48 | return &UploaderImpl{} 49 | } 50 | 51 | func (u *UploaderImpl) Upload(localPath string) (string, error) { 52 | // 绝对路径 53 | localPath, err := filepath.Abs(localPath) 54 | if err != nil { 55 | return "", err 56 | } 57 | 58 | // 统一 window 和 linux 的路径分隔符 59 | if ostype == "windows" { 60 | localPath = strings.ReplaceAll(localPath, `/`, `\\`) 61 | if !strings.HasPrefix(localPath, `\\`) { 62 | localPath = strings.ReplaceAll(localPath, `\`, `\\`) 63 | } 64 | } else { 65 | localPath = strings.ReplaceAll(localPath, `\\`, "/") 66 | } 67 | 68 | fmt.Println("[上传] 绝对路径: ", localPath) 69 | 70 | return upload(localPath) 71 | } 72 | 73 | // 获取 ipv4 地址 74 | func LocalIp() string { 75 | addrs, err := net.InterfaceAddrs() 76 | if err != nil { 77 | fmt.Println(err) 78 | } 79 | var ip string = "localhost" 80 | for _, address := range addrs { 81 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 82 | if ipnet.IP.To4() != nil { 83 | ip = ipnet.IP.String() 84 | } 85 | } 86 | } 87 | return ip 88 | } 89 | 90 | func upload(localPath string) (string, error) { 91 | 92 | // url := "http://" + LocalIp() + ":36677/upload" 93 | url := "http://127.0.0.1:36677/upload" 94 | method := "POST" 95 | 96 | data := fmt.Sprintf(`{"list":["%s"]}`, localPath) 97 | payload := strings.NewReader(data) 98 | 99 | client := &http.Client{} 100 | req, err := http.NewRequest(method, url, payload) 101 | 102 | if err != nil { 103 | fmt.Println(err) 104 | return "", err 105 | } 106 | req.Header.Add("User-Agent", "apifox/1.0.0 (https://www.apifox.cn)") 107 | req.Header.Add("Content-Type", "application/json") 108 | 109 | res, err := client.Do(req) 110 | if err != nil { 111 | fmt.Println(err) 112 | return "", err 113 | } 114 | defer res.Body.Close() 115 | 116 | body, err := ioutil.ReadAll(res.Body) 117 | if err != nil { 118 | fmt.Println(err) 119 | return "", err 120 | } 121 | 122 | var resp Response 123 | err = json.Unmarshal(body, &resp) 124 | if err != nil { 125 | fmt.Println(err) 126 | return "", err 127 | } 128 | 129 | if len(resp.Result) == 0 { 130 | return "", ErrPicgoResultIsEmpty 131 | } 132 | 133 | return resp.Result[0], nil 134 | } 135 | -------------------------------------------------------------------------------- /tools/download/download.go: -------------------------------------------------------------------------------- 1 | package download 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | "strconv" 12 | ) 13 | 14 | type DownLoader interface { 15 | // 下载, 返回本地绝对路径 16 | DownLoad(url string) (string, error) 17 | } 18 | 19 | type DownLoaderImpl struct { 20 | dir string // 保存的目录 21 | } 22 | 23 | func NewDownLoader(dir string) DownLoader { 24 | return &DownLoaderImpl{dir: dir} 25 | } 26 | 27 | func (d *DownLoaderImpl) DownLoad(urlStr string) (string, error) { 28 | // 修剪参数 29 | urlStr = TrimUrl(urlStr) 30 | 31 | // 获取文件路径 32 | filePath, err := d.GetFilePath(urlStr) 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | // 下载 38 | err = downloadFile(urlStr, filePath, func(length, downLen int64) { 39 | }) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | // 获取相对路径 45 | relPath, err := filepath.Rel("", filePath) 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | // 转换为斜杠 51 | relPath = filepath.ToSlash(relPath) 52 | 53 | // 返回相对路径 54 | return relPath, nil 55 | } 56 | 57 | func downloadFile(url string, localPath string, fb func(length, downLen int64)) error { 58 | var ( 59 | fsize int64 60 | buf = make([]byte, 32*1024) 61 | written int64 62 | ) 63 | tmpFilePath := localPath + ".download" 64 | //创建一个http client 65 | client := new(http.Client) 66 | //client.Timeout = time.Second * 60 //设置超时时间 67 | //get方法获取资源 68 | resp, err := client.Get(url) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | //读取服务器返回的文件大小 74 | fsize, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32) 75 | if err != nil { 76 | fmt.Println(err) 77 | } 78 | //创建文件 79 | file, err := os.Create(tmpFilePath) 80 | if err != nil { 81 | return err 82 | } 83 | defer file.Close() 84 | if resp.Body == nil { 85 | return errors.New("body is null") 86 | } 87 | defer resp.Body.Close() 88 | //下面是 io.copyBuffer() 的简化版本 89 | for { 90 | //读取bytes 91 | nr, er := resp.Body.Read(buf) 92 | if nr > 0 { 93 | //写入bytes 94 | nw, ew := file.Write(buf[0:nr]) 95 | //数据长度大于0 96 | if nw > 0 { 97 | written += int64(nw) 98 | } 99 | //写入出错 100 | if ew != nil { 101 | err = ew 102 | break 103 | } 104 | //读取是数据长度不等于写入的数据长度 105 | if nr != nw { 106 | err = io.ErrShortWrite 107 | break 108 | } 109 | } 110 | if er != nil { 111 | if er != io.EOF { 112 | err = er 113 | } 114 | break 115 | } 116 | //没有错误了快使用 callback 117 | fb(fsize, written) 118 | } 119 | if err == nil { 120 | file.Close() 121 | err = os.Rename(tmpFilePath, localPath) 122 | } 123 | return err 124 | } 125 | 126 | func (d *DownLoaderImpl) GetFilePath(url string) (string, error) { 127 | // 检查目录是否存在不存在就新建 128 | if _, err := os.Stat(d.dir); os.IsNotExist(err) { 129 | err = os.MkdirAll(d.dir, os.ModePerm) 130 | if err != nil { 131 | return "", err 132 | } 133 | } 134 | 135 | // 获取文件名 136 | fileName := path.Base(url) 137 | filePath := path.Join(d.dir, fileName) 138 | 139 | // 检查文件是否存在, 如果存在添加序号 140 | for i := 1; FileExist(filePath); i++ { 141 | newFileName := fmt.Sprintf("%d_", i) + fileName 142 | filePath = path.Join(d.dir, newFileName) 143 | } 144 | 145 | return filePath, nil 146 | } 147 | 148 | func FileExist(path string) bool { 149 | _, err := os.Lstat(path) 150 | return !os.IsNotExist(err) 151 | } 152 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Lingfei Kong . All rights reserved. 2 | # Use of this source code is governed by a MIT style 3 | # license that can be found in the LICENSE file. 4 | 5 | # This file contains all available configuration options 6 | # with their default values. 7 | 8 | # options for analysis running 9 | run: 10 | # default concurrency is a available CPU number 11 | concurrency: 4 12 | 13 | # timeout for analysis, e.g. 30s, 5m, default is 1m 14 | timeout: 5m 15 | 16 | # exit code when at least one issue was found, default is 1 17 | issues-exit-code: 1 18 | 19 | # include test files or not, default is true 20 | tests: true 21 | 22 | # list of build tags, all linters use it. Default is empty list. 23 | build-tags: 24 | - mytag 25 | 26 | # which dirs to skip: issues from them won't be reported; 27 | # can use regexp here: generated.*, regexp is applied on full path; 28 | # default value is empty list, but default dirs are skipped independently 29 | # from this option's value (see skip-dirs-use-default). 30 | # "/" will be replaced by current OS file path separator to properly work 31 | # on Windows. 32 | skip-dirs: 33 | - test # 测试目录 34 | - tools # 工具目录 35 | 36 | # default is true. Enables skipping of directories: 37 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 38 | skip-dirs-use-default: true 39 | 40 | # which files to skip: they will be analyzed, but issues from them 41 | # won't be reported. Default value is empty list, but there is 42 | # no need to include all autogenerated files, we confidently recognize 43 | # autogenerated files. If it's not please let us know. 44 | # "/" will be replaced by current OS file path separator to properly work 45 | # on Windows. 46 | skip-files: 47 | - ".*\\.my\\.go$" 48 | - _test.go 49 | 50 | # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": 51 | # If invoked with -mod=readonly, the go command is disallowed from the implicit 52 | # automatic updating of go.mod described above. Instead, it fails when any changes 53 | # to go.mod are needed. This setting is most useful to check that go.mod does 54 | # not need updates, such as in a continuous integration and testing system. 55 | # If invoked with -mod=vendor, the go command assumes that the vendor 56 | # directory holds the correct copies of dependencies and ignores 57 | # the dependency descriptions in go.mod. 58 | #modules-download-mode: release|readonly|vendor 59 | 60 | # Allow multiple parallel golangci-lint instances running. 61 | # If false (default) - golangci-lint acquires file lock on start. 62 | allow-parallel-runners: true 63 | 64 | 65 | # output configuration options 66 | output: 67 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 68 | format: colored-line-number 69 | 70 | # print lines of code with issue, default is true 71 | print-issued-lines: true 72 | 73 | # print linter name in the end of issue text, default is true 74 | print-linter-name: true 75 | 76 | # make issues output unique by line, default is true 77 | uniq-by-line: true 78 | 79 | # add a prefix to the output file references; default is no prefix 80 | path-prefix: "" 81 | 82 | # sorts results by: filepath, line and column 83 | sort-results: true 84 | 85 | # all available settings of specific linters 86 | linters-settings: 87 | gocyclo: 88 | # Minimal code complexity to report. 89 | # Default: 30 (but we recommend 10-20) 90 | min-complexity: 10 91 | linters: 92 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 93 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 94 | # enable-all: true 95 | disable-all: true 96 | enable: 97 | - typecheck # 类型检查 98 | - bodyclose # body是否关闭 99 | - durationcheck # duration相乘检查 100 | - errcheck # 错误检查 101 | - exportloopref # 循环指针检查 102 | # - gofmt # gofmt 103 | # - gofumpt # gofumpt 104 | # - goimports # 导包顺序检查 105 | # - gosec # go安全检查 106 | - gosimple # 简化代码检查 107 | - govet 108 | - ineffassign # 变量是否未被使用 109 | - makezero # make非0长度切片 110 | - nilerr 111 | #- prealloc # 切片预分配 112 | #- revive 冗余代码检查 113 | - staticcheck # 静态检查 114 | - unparam # 无用的参数 115 | - unused # 变量是否使用 116 | - errchkjson # json err是否处理 117 | - gocyclo # 圈复杂度检查 118 | fast: false 119 | 120 | issues: 121 | 122 | # Show only new issues created after git revision `REV` 123 | # new-from-rev: REV 124 | 125 | # Show only new issues created in git patch with set file path. 126 | #new-from-patch: path/to/patch/file 127 | 128 | # Fix found issues (if it's supported by the linter) 129 | fix: false 130 | 131 | 132 | --------------------------------------------------------------------------------