├── _test ├── conf.yaml ├── todo_queue_test.go ├── proxy_test.go ├── stress_testing_test.go ├── conf_helper_test.go ├── slice_helper_test.go ├── scan_test.go ├── time_helper_test.go ├── req_test.go ├── base_test.go ├── map_helper_test.go ├── string_helper_test.go └── reg_test.go ├── _examples ├── cnlinfo │ ├── conf.yaml │ └── main.go ├── intercept │ ├── logo_16.ico │ └── main.go ├── tjbz │ ├── tjypflml.go │ └── tjyqhdmhcxhfdm.go ├── socket5 │ └── main.go ├── all_school │ └── main.go ├── search │ └── main.go ├── gkcx_eol_cn │ └── main.go ├── db_case │ └── main.go ├── mq │ └── main.go ├── qgyyzs │ └── main.go ├── upload_file │ └── main.go ├── baidu_tk │ └── main.go ├── ip_bczs_cn │ ├── go.mod │ └── main.go ├── get │ └── get.go ├── qihuo │ └── main.go ├── json │ └── main.go ├── weibo │ └── main.go ├── douyin │ └── main.go ├── websocket_client │ └── main.go └── baojia │ └── main.go ├── .gitignore ├── LICENSE ├── mail_helper.go ├── conf_helper.go ├── go.mod ├── id_helper.go ├── extract.go ├── stress_testing.go ├── logger.go ├── time_helper.go ├── scan.go ├── gathertool.go ├── job.go ├── out_helper.go ├── go.sum ├── data_structure.go ├── README.md ├── crypto_helper.go ├── string_helper.go └── conversion.go /_test/conf.yaml: -------------------------------------------------------------------------------- 1 | test: 2 | case: 123 -------------------------------------------------------------------------------- /_examples/cnlinfo/conf.yaml: -------------------------------------------------------------------------------- 1 | # 抓取结果输出文件 2 | out_file: "fenlei.json" 3 | # 请求延时 4 | req_delay_min : 1 5 | req_delay_max : 3 -------------------------------------------------------------------------------- /_examples/intercept/logo_16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mangenotwork/gathertool/HEAD/_examples/intercept/logo_16.ico -------------------------------------------------------------------------------- /_examples/tjbz/tjypflml.go: -------------------------------------------------------------------------------- 1 | /* 2 | 国家统计局-统计用产品分类目录 : http://www.stats.gov.cn/tjsj/tjbz/tjypflml/ 3 | */ 4 | 5 | package main 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | .vscode/ 4 | dist/ 5 | build/ 6 | vendor/ 7 | logs/ 8 | *.exe 9 | *.exe~ 10 | *.log 11 | *.db 12 | -------------------------------------------------------------------------------- /_examples/tjbz/tjyqhdmhcxhfdm.go: -------------------------------------------------------------------------------- 1 | /* 2 | 国家统计局-统计用区划和城乡划分代码 : http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/ 3 | */ 4 | 5 | package main 6 | 7 | -------------------------------------------------------------------------------- /_test/todo_queue_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTodoQueue(t *testing.T) { 8 | // TODO... 9 | } 10 | -------------------------------------------------------------------------------- /_examples/socket5/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | ) 6 | 7 | func main(){ 8 | gt.SockerProxy(":8111") 9 | } -------------------------------------------------------------------------------- /_test/proxy_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestSocketProxy(t *testing.T) { 9 | gt.SocketProxy("0.0.0.0:16666") 10 | } 11 | -------------------------------------------------------------------------------- /_examples/all_school/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | 抓取学校大全 5 | http://anshan.xuexiaodaquan.com/youeryuan/pn2.html 6 | 7 | 中学学校大全 8 | http://xuexiao.51sxue.com/slist/?o=&t=3&areaCodeS=&level=&sp=&score=&order=&areaS=%CB%C4%B4%A8%CA%A1&searchKey= 9 | 10 | */ 11 | -------------------------------------------------------------------------------- /_test/stress_testing_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestStressTesting(t *testing.T) { 9 | test := gt.NewTestUrl("https://baidu.com", "Get", 20, 10) 10 | test.Run() 11 | } 12 | 13 | // TODO 14 | func TestScanNewHostScanUrl(t *testing.T) { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /_test/conf_helper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | // go test -v -run=TestConf 9 | func TestConf(t *testing.T) { 10 | _ = gt.NewConf("./conf.yaml") 11 | t.Log(gt.Config.Get("test")) 12 | t.Log(gt.Config.GetString("test::case")) 13 | t.Log(gt.Config.GetAll()) 14 | } 15 | -------------------------------------------------------------------------------- /_test/slice_helper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestSliceHelper(t *testing.T) { 9 | t1 := []string{"1", "2", "3", "4", "4", "3"} 10 | t.Log(gt.SliceContains[string](t1, "1")) 11 | 12 | t.Log(gt.SliceDeduplicate[string](t1)) 13 | 14 | t2 := []int{1, 2, 3, 4} 15 | t.Log(gt.SliceDel[int](t2, 1)) 16 | 17 | t.Log(gt.SliceMax[int](t2)) 18 | 19 | t.Log(gt.SliceMin[int](t2)) 20 | 21 | t.Log(gt.SlicePop[int](t2)) 22 | 23 | t.Log(t1) 24 | t.Log(gt.SliceReverse[string](t1)) 25 | 26 | t.Log(gt.SliceShuffle[string](t1)) 27 | 28 | t.Log(gt.SliceCopy[string](t1)) 29 | } 30 | -------------------------------------------------------------------------------- /_examples/intercept/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | ) 7 | 8 | func main(){ 9 | ipt := >.Intercept{ 10 | Ip : "0.0.0.0:8111", 11 | HttpPackageFunc: func(pack *gt.HttpPackage){ 12 | // 查看 ContentType 13 | log.Println("ContentType = ", pack.ContentType) 14 | // 获取数据包数据为 http,json等 TXT格式的数据 15 | log.Println("Txt = ", pack.Html()) 16 | // 获取数据包为图片,将图片转为 base64 17 | log.Println("img base64 = ", pack.Img2Base64()) 18 | // 获取数据包为图片,存储图片 19 | log.Println(pack.SaveImage("")) 20 | }, 21 | } 22 | // 启动服务 23 | ipt.RunServer() 24 | } 25 | 26 | /* 27 | golang实现http&https代理服务器 28 | golang实现http代理服务器并解析包 29 | */ -------------------------------------------------------------------------------- /_examples/search/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 1. ip 扫描 3 | 2. DOS 攻击 4 | 3. DDOS 攻击 5 | 4. arp 6 | 5. ip的端口扫描 7 | 8 | */ 9 | package main 10 | 11 | import ( 12 | gt "github.com/mangenotwork/gathertool" 13 | "time" 14 | ) 15 | 16 | func main(){ 17 | //gt.SearchDomain("110.242.68.3") 18 | //gt.SearchPort("120.79.88.59", 5*time.Second) 19 | //gt.Ping("192.168.0.23") 20 | // 21 | //for i:=0; i<256; i++{ 22 | // gt.Ping(fmt.Sprintf("192.168.0.%d", i)) 23 | //} 24 | 25 | for { 26 | gt.Ping("120.79.88.59") 27 | time.Sleep(1*time.Second) 28 | } 29 | 30 | } 31 | 32 | 33 | 34 | /* 35 | 搜索引擎采集策略 36 | - 种子url 扩增扫描 37 | - ip 扫描到站点 38 | - 各大搜索引擎抓取 39 | 40 | 内容识别字段: title, description,

...

, keywords, site_name, property="og:..., 41 | */ 42 | -------------------------------------------------------------------------------- /_test/scan_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestApplicationTerminalOutClose(t *testing.T) { 9 | gt.ApplicationTerminalOutClose() 10 | } 11 | 12 | func TestNewHostScanUrl(t *testing.T) { 13 | scan := gt.NewHostScanUrl("http://127.0.0.1:8881/home", 3) 14 | gt.Info(scan.Run()) 15 | } 16 | 17 | func TestNewHostScanExtLinks(t *testing.T) { 18 | scan := gt.NewHostScanExtLinks("http://127.0.0.1:8881/home") 19 | gt.Info(scan.Run()) 20 | } 21 | 22 | func TestNewHostScanBadLink(t *testing.T) { 23 | scan := gt.NewHostScanBadLink("http://127.0.0.1:8881/home", 3) 24 | gt.Info(scan.Run()) 25 | } 26 | 27 | func TestNewHostPageSpeedCheck(t *testing.T) { 28 | scan := gt.NewHostPageSpeedCheck("http://127.0.0.1:8881/home", 3) 29 | gt.Info(scan.Run()) 30 | } 31 | -------------------------------------------------------------------------------- /_examples/gkcx_eol_cn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | getSchoolList() 10 | 11 | } 12 | 13 | func getSchoolList() { 14 | caseUrl := "https://api.eol.cn/gkcx/api/?access_token=&admissions=¢ral=&department=&dual_class=&f211=&f985=&is_doublehigh=&is_dual_class=&keyword=&nature=&page=1&province_id=&ranktype=&request_type=1&school_type=&signsafe=&size=20&sort=view_total&top_school_id=[]&type=&uri=apidata/api/gk/school/lists" 15 | //ctx := gt.PostJson(caseUrl, "")//.SetSucceedFunc(getSchoolListSuueed) 16 | //log.Println(ctx.String()) 17 | 18 | gt.PostJson(caseUrl, "", gt.SucceedFunc(getSchoolListSuueed)) 19 | 20 | } 21 | 22 | func getSchoolListSuueed(ctx *gt.Context) { 23 | //log.Println("ctx = ", ctx) 24 | log.Println(ctx.RespBodyString()) 25 | log.Println(ctx.CheckReqMd5()) 26 | log.Println(ctx.CheckMd5()) 27 | } 28 | -------------------------------------------------------------------------------- /_examples/db_case/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | ) 6 | 7 | var ( 8 | host192 = "192.168.0.192" 9 | port = 3306 10 | user = "root" 11 | password = "root123" 12 | spiderBase = "spider-base" 13 | SpiderBaseDB, _ = gt.NewMysql(host192, port, user, password, spiderBase) 14 | 15 | ) 16 | 17 | func main(){ 18 | //case1() 19 | 20 | case2() 21 | } 22 | 23 | func case1(){ 24 | data := map[string]interface{}{ 25 | "num":123, 26 | "txt":"aaaaa'aaaaa", 27 | "txt2":`bbbb"bbb'bbbbbb'bbb"bbbbb`, 28 | } 29 | err := SpiderBaseDB.InsertAt("test", data) 30 | if err != nil { 31 | panic(err) 32 | } 33 | } 34 | 35 | func case2(){ 36 | gd := gt.NewGDMap().Add("num", 123).Add("txt", "aaaaa'aaaaa") 37 | gd.Add("txt2", `bbbb"bbb'bbbbbb'bbb"bbbbb`) 38 | err := SpiderBaseDB.InsertAtGd("test2", gd) 39 | if err != nil { 40 | panic(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /_test/time_helper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestTimeHelperTest(t *testing.T) { 10 | t.Log(gt.Timestamp2Date(gt.Timestamp())) 11 | t.Log(gt.TimestampStr()) 12 | t.Log(gt.Timestamp2Date(gt.BeginDayUnix())) 13 | t.Log(gt.Timestamp2Date(gt.EndDayUnix())) 14 | t.Log(gt.Timestamp2Date(gt.MinuteAgo(10))) 15 | t.Log(gt.Timestamp2Date(gt.HourAgo(1))) 16 | t.Log(gt.Timestamp2Date(gt.DayAgo(2))) 17 | t.Log(gt.DayDiff("2023-11-23 00:00:00", "2023-12-23 00:00:00")) 18 | t.Log(gt.DayDiffAtUnix(gt.DayAgo(10), gt.Timestamp())) 19 | t.Log(gt.NowToEnd()) 20 | t.Log(gt.IsToday(gt.DayAgo(10))) 21 | t.Log(gt.IsTodayList(gt.DayAgo(10))) 22 | t.Log(gt.Timestamp2Week(gt.Timestamp())) 23 | t.Log(gt.Timestamp2WeekXinQi(gt.Timestamp())) 24 | t.Log(gt.LatestDate(10)) 25 | t.Log(gt.GetCurrentMonthRange()) 26 | t.Log(gt.GetCurrentWeekRange()) 27 | t.Log(gt.GetTodayRange()) 28 | } 29 | 30 | func TestTimeHelperTickerRunTest(t *testing.T) { 31 | gt.TickerRun(1*time.Second, true, func() { 32 | gt.Info("test...") 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ManGe(漫鸽) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_test/req_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestReq(t *testing.T) { 9 | ctx, err := gt.Request("https://www.doubao.com/", "get", []byte(""), "") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | t.Log(ctx.Html) 14 | 15 | ctx = gt.NewRequest("https://www.doubao.com/", "get", []byte(""), "") 16 | if ctx.Err != nil { 17 | t.Fatal(err) 18 | } 19 | t.Log(ctx.Html) 20 | 21 | t.Log(gt.IsUrl("https://www.doubao.com/")) 22 | t.Log(gt.UrlStr("www.doubao.com/")) 23 | 24 | gt.SetSleepMs(1, 3) 25 | 26 | ck := gt.NewCookie(map[string]string{ 27 | "case1": "1", 28 | "case2": "2", 29 | }) 30 | t.Log(ck.Delete("case1")) 31 | t.Log(ck.Get("case1")) 32 | 33 | gt.SearchDomain("180.97.248.210") 34 | 35 | gt.SearchPort("10.0.40.3") 36 | 37 | gt.ClosePingTerminalPrint() 38 | 39 | gt.GetCertificateInfo("https://www.doubao.com/") 40 | 41 | gt.SetStatusCodeSuccessEvent(203) 42 | gt.SetStatusCodeRetryEvent(401) 43 | gt.SetStatusCodeFailEvent(403) 44 | gt.SetAgent(1, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36") 45 | } 46 | -------------------------------------------------------------------------------- /_examples/mq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | ) 6 | 7 | func main() { 8 | 9 | NsqProducer() 10 | 11 | //NsqConsumer() 12 | 13 | } 14 | 15 | func NsqProducer() { 16 | mq := gt.NewNsq("127.0.0.1") 17 | topic := "test" 18 | data := []byte("data") 19 | mq.Producer(topic, data) 20 | } 21 | 22 | func NsqConsumer() { 23 | topic := "test" 24 | mq := gt.NewNsq("127.0.0.1") 25 | for { 26 | data := mq.Consumer(topic) 27 | gt.Info(string(data)) 28 | } 29 | 30 | } 31 | 32 | func RabbitProducer() { 33 | mq := gt.NewRabbit("amqp://admin:123456@127.0.0.1:5672") 34 | topic := "test" 35 | data := []byte("data") 36 | mq.Producer(topic, data) 37 | } 38 | 39 | func RabbitConsumer() { 40 | topic := "test" 41 | mq := gt.NewRabbit("amqp://admin:123456@127.0.0.1:5672") 42 | data := mq.Consumer(topic) 43 | gt.Info(string(data)) 44 | } 45 | 46 | func KafkaProducer() { 47 | mq := gt.NewKafka([]string{"192.168.4.12:9092"}) 48 | topic := "test" 49 | data := []byte("data") 50 | mq.Producer(topic, data) 51 | } 52 | 53 | func KafkaConsumer() { 54 | topic := "test" 55 | mq := gt.NewKafka([]string{"192.168.4.12:9092"}) 56 | data := mq.Consumer(topic) 57 | gt.Info(string(data)) 58 | } 59 | -------------------------------------------------------------------------------- /_examples/qgyyzs/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 环球医药网 爬虫 3 | http://data.qgyyzs.net 4 | 5 | 药智数据 6 | https://db.yaozh.com/instruct?p=1&pageSize=20 7 | */ 8 | package main 9 | 10 | import ( 11 | "github.com/PuerkitoBio/goquery" 12 | gt "github.com/mangenotwork/gathertool" 13 | "log" 14 | "net/http" 15 | "strings" 16 | ) 17 | 18 | func main() { 19 | //GcypList() 20 | 21 | //HospitalList() 22 | 23 | Instruct() 24 | } 25 | 26 | // GcypList 抓取国产药品列表 27 | func GcypList() { 28 | caseUrl := "http://data.qgyyzs.net/gcyp_1.aspx" 29 | ctx, _ := gt.Get(caseUrl) 30 | log.Println(ctx.RespBodyString()) 31 | } 32 | 33 | // HospitalList 抓取医院列表 34 | func HospitalList() { 35 | caseUrl := "http://data.qgyyzs.net/hospital_1.aspx" 36 | head := http.Header{} 37 | ctx, _ := gt.Get(caseUrl, head) 38 | htmlStr := gt.ConvertByte2String(ctx.RespBody, gt.GBK) 39 | //log.Println(htmlStr) 40 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr)) 41 | if err != nil { 42 | log.Println(err) 43 | return 44 | } 45 | result := dom.Find("div[class=content]") 46 | resultStr, _ := result.Html() 47 | for _, v := range gt.RegHtmlLi(resultStr) { 48 | log.Println(v) 49 | } 50 | } 51 | 52 | // Instruct 抓取药品说明书列表 53 | func Instruct() { 54 | caseUrl := "https://db.yaozh.com/instruct?p=1&pageSize=20" 55 | ctx, _ := gt.Get(caseUrl) 56 | log.Println(ctx.RespBodyString()) 57 | } 58 | -------------------------------------------------------------------------------- /mail_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 邮件的方法,应用场景有抓取完成通知. TODO 改为通知,新增webhook 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "gopkg.in/gomail.v2" 11 | ) 12 | 13 | type Mail struct { 14 | From string 15 | 16 | // 邮件授权码 17 | AuthCode string 18 | 19 | // QQ 邮箱: SMTP 服务器地址:smtp.qq.com(SSL协议端口:465/587, 非SSL协议端口:25) 20 | // 163 邮箱:SMTP 服务器地址:smtp.163.com(SSL协议端口:465/994,非SSL协议端口:25) 21 | Host string 22 | Port int 23 | C *gomail.Dialer 24 | Msg *gomail.Message 25 | } 26 | 27 | func NewMail(host, from, auth string, port int) *Mail { 28 | m := &Mail{ 29 | From: from, 30 | AuthCode: auth, 31 | Host: host, 32 | Port: port, 33 | } 34 | m.C = gomail.NewDialer( 35 | m.Host, 36 | m.Port, 37 | m.From, 38 | m.AuthCode, 39 | ) 40 | m.Msg = gomail.NewMessage() 41 | m.Msg.SetHeader("From", from) 42 | return m 43 | } 44 | 45 | func (m *Mail) Title(title string) *Mail { 46 | m.Msg.SetHeader("Subject", title) 47 | return m 48 | } 49 | 50 | func (m *Mail) HtmlBody(body string) *Mail { 51 | m.Msg.SetBody("text/html", body) 52 | return m 53 | } 54 | 55 | func (m *Mail) Send(to string) error { 56 | m.Msg.SetHeader("To", to) 57 | return m.C.DialAndSend(m.Msg) 58 | } 59 | 60 | func (m *Mail) SendMore(to []string) error { 61 | mgs := make([]*gomail.Message, 0) 62 | for _, v := range to { 63 | newMsg := m.Msg 64 | newMsg.SetHeader("To", v) 65 | mgs = append(mgs, newMsg) 66 | } 67 | return m.C.DialAndSend(mgs...) 68 | } 69 | -------------------------------------------------------------------------------- /_examples/upload_file/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | ) 7 | 8 | var fileLinks = map[string]string{ 9 | "https://jyzd.bfsu.edu.cn/uploadfile/bfsu/front/default/upload_file_35256.pdf": "/home/mange/Desktop/upload_file_1.pdf", 10 | "http://www.cuc.edu.cn/_upload/article/files/c8/44/741ea34245acbc7b8aa9b7660c0f/579130b8-3974-4c9d-99a3-19f4e4e095d1.pdf": "/home/mange/Desktop/upload_file_2.pdf", 11 | "https://job.buct.edu.cn/_upload/article/files/a0/b9/84fd06284b078e8051163a221546/2e302909-e81e-4aea-aa94-c5f37179cab0.pdf": "/home/mange/Desktop/upload_file_3.pdf", 12 | "http://kr.shanghai-jiuxin.com/file/2020/1031/774218be86d832f359637ab120eba52d.jpg": "/home/mange/Desktop/upload_file_1.jpg", 13 | "http://job.szu.edu.cn/UpLoadFile/InfoCenter/14247/File/20210119100014406.pdf": "/home/mange/Desktop/upload_file_4.pdf", 14 | } 15 | 16 | func main() { 17 | // 普通下载 18 | ctx, err := gt.Upload("https://jyzd.bfsu.edu.cn/uploadfile/bfsu/front/default/upload_file_35256.pdf", "/home/mange/Desktop/upload_file_3.pdf") 19 | log.Println(ctx.Resp.StatusCode, err) 20 | //for k,v := range fileLinks{ 21 | // gt.Upload(k,v) 22 | //} 23 | 24 | //// 并发下载 25 | //queue := gt.NewUploadQueue() 26 | //for k,v := range fileLinks{ 27 | // queue.Add(>.Task{ 28 | // Url: k, 29 | // SavePath: v, 30 | // Type: "upload", 31 | // }) 32 | //} 33 | //gt.StartJobGet(5, queue) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /_examples/baidu_tk/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 百度题库抓取 3 | */ 4 | 5 | package main 6 | 7 | func main() { 8 | //getZJ() 9 | } 10 | 11 | //// 1.获取每一章节对应的目录 12 | //func getZJ(){ 13 | // caseUrl := "https://tiku.baidu.com/tikupc/chapterlist/1bfd700abb68a98271fefa04-16-knowpoint" 14 | // c, err := gt.Get(caseUrl) 15 | // if err != nil { 16 | // panic(err) 17 | // } 18 | // 19 | // dom,err := goquery.NewDocumentFromReader(strings.NewReader(c.RespBodyString())) 20 | // if err != nil{ 21 | // log.Println(err) 22 | // return 23 | // } 24 | // 25 | // dom.Find("div[class=detail]").Each(func(i int, div *goquery.Selection){ 26 | // chapter := div.Find("div[class=detail-chapter]") 27 | // chapterHtml, _ := chapter.Html() 28 | // 29 | // // 章节名称 30 | // zjTitle := gt.RegHtmlH(chapterHtml, "3") 31 | // log.Println("章节名称 ==> ", zjTitle) 32 | // 33 | // chapter.Each(func(i int, div2 *goquery.Selection){ 34 | // kpoint := div2.Find("div[class=detail-kpoint-1]") 35 | // kpointHtml, _ := kpoint.Html() 36 | // 37 | // // 小节名称 38 | // xjTitle := gt.RegHtmlH(kpointHtml, "4") 39 | // log.Println("小节名称 ==> ", xjTitle) 40 | // 41 | // // 获取课程 42 | // kpoint.Each(func(i int, div3 *goquery.Selection){ 43 | // kpoint2 := div3.Find("div[class=detail-kpoint-2]") 44 | // kpoint2Html,_ := kpoint2.Html() 45 | // 46 | // //课程名称 47 | // kcTitle := gt.RegHtmlHTxt(kpoint2Html, "5") 48 | // log.Println("课程名称 ==> ", kcTitle) 49 | // //课程链接 50 | // kcLink := gt.RegHtmlHrefTxt(kpoint2Html) 51 | // if len(kcLink) > 0 { 52 | // link := "https://tiku.baidu.com"+kcLink[0] 53 | // log.Println("课程链接 ==> ", link) 54 | // } 55 | // log.Println("目录 ==> ", zjTitle, xjTitle, kcTitle) 56 | // log.Println("-------------------------------") 57 | // }) 58 | // }) 59 | // 60 | // }) 61 | //} 62 | -------------------------------------------------------------------------------- /_examples/ip_bczs_cn/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mangenotwork/gathertool/_examples/ip_bczs_cn 2 | 3 | go 1.17 4 | 5 | replace github.com/mangenotwork/gathertool => ../../ 6 | 7 | require ( 8 | github.com/PuerkitoBio/goquery v1.8.0 9 | github.com/mangenotwork/gathertool v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/cascadia v1.3.1 // indirect 14 | github.com/aws/aws-sdk-go v1.34.28 // indirect 15 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 16 | github.com/garyburd/redigo v1.6.4 // indirect 17 | github.com/go-sql-driver/mysql v1.7.1 // indirect 18 | github.com/go-stack/stack v1.8.0 // indirect 19 | github.com/golang/snappy v0.0.4 // indirect 20 | github.com/jmespath/go-jmespath v0.4.0 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/klauspost/compress v1.15.14 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 26 | github.com/pkg/errors v0.9.1 // indirect 27 | github.com/richardlehane/mscfb v1.0.4 // indirect 28 | github.com/richardlehane/msoleps v1.0.3 // indirect 29 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 30 | github.com/xdg-go/scram v1.1.2 // indirect 31 | github.com/xdg-go/stringprep v1.0.4 // indirect 32 | github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect 33 | github.com/xuri/excelize/v2 v2.7.0 // indirect 34 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 35 | go.mongodb.org/mongo-driver v1.5.2 // indirect 36 | golang.org/x/crypto v0.14.0 // indirect 37 | golang.org/x/net v0.17.0 // indirect 38 | golang.org/x/sync v0.1.0 // indirect 39 | golang.org/x/sys v0.13.0 // indirect 40 | golang.org/x/text v0.13.0 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /_test/base_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestPost(t *testing.T) { 9 | ctx, err := gt.Post("http://127.0.0.1/post/test", []byte("xx"), "application/json") 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | gt.Info(ctx.Json) 14 | } 15 | 16 | func TestPostForm(t *testing.T) { 17 | ctx, err := gt.PostForm("http://127.0.0.1/postFrom/test", map[string]string{"xx": "xx"}) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | gt.Info(ctx.RespBody) 22 | } 23 | 24 | func TestPostFile(t *testing.T) { 25 | ctx, err := gt.PostFile("http://127.0.0.1/postFile/test", "file", "./test.txt") 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | gt.Info(ctx.RespBody) 30 | } 31 | 32 | func TestPut(t *testing.T) { 33 | ctx, err := gt.Put("http://127.0.0.1/put/test", []byte("xx"), "application/json") 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | gt.Info(ctx.RespBody) 38 | } 39 | 40 | func TestDelete(t *testing.T) { 41 | ctx, err := gt.Delete("http://127.0.0.1/delete/test") 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | gt.Info(ctx.RespBody) 46 | } 47 | 48 | func TestOptions(t *testing.T) { 49 | ctx, err := gt.Options("http://127.0.0.1/options/test") 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | gt.Info(ctx.RespBody) 54 | } 55 | 56 | func TestHead(t *testing.T) { 57 | ctx, err := gt.Head("http://127.0.0.1/head/test") 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | gt.Info(ctx.RespBody) 62 | } 63 | 64 | func TestPatch(t *testing.T) { 65 | ctx, err := gt.Patch("http://127.0.0.1/patch/test") 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | gt.Info(ctx.RespBody) 70 | } 71 | 72 | func TestNewProxyIP(t *testing.T) { 73 | gt.NewProxyIP("127.0.0.1", 8888, "", "", false) 74 | } 75 | 76 | func TestNewProxyPool(t *testing.T) { 77 | gt.NewProxyPool() 78 | } 79 | 80 | func TestNewCookiePool(t *testing.T) { 81 | gt.NewCookiePool() 82 | } 83 | -------------------------------------------------------------------------------- /conf_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 配置文件 yaml TODO 扩展到所有能支持的配置文件 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "fmt" 11 | "os" 12 | "strings" 13 | 14 | "gopkg.in/yaml.v3" 15 | ) 16 | 17 | var Config *conf 18 | 19 | type conf struct { 20 | Path string 21 | Data map[string]any 22 | } 23 | 24 | // NewConf 读取配置,只支持yaml 25 | func NewConf(appConfigPath string) error { 26 | Config = &conf{ 27 | Path: appConfigPath, 28 | Data: make(map[string]any), 29 | } 30 | err := Config.Init() 31 | return err 32 | } 33 | 34 | func (c *conf) Init() error { 35 | if !FileExists(c.Path) { 36 | return fmt.Errorf("未找到配置文件: %v", c.Path) 37 | } 38 | Info("读取配置文件:", c.Path) 39 | //读取yaml文件到缓存中 40 | config, err := os.ReadFile(c.Path) 41 | if err != nil { 42 | ErrorF("读取配置文件 %v 失败", c.Path) 43 | return err 44 | } 45 | return yaml.Unmarshal(config, c.Data) 46 | } 47 | 48 | func (c *conf) GetInt(key string) int { 49 | if c.Data == nil { 50 | _ = c.Init() 51 | } 52 | return Any2Int(c.Data[key]) 53 | } 54 | 55 | func (c *conf) get(key string) (interface{}, bool) { 56 | var ( 57 | d interface{} 58 | ok bool 59 | ) 60 | keyList := strings.Split(key, "::") 61 | temp := make(map[string]interface{}) 62 | temp = c.Data 63 | for _, v := range keyList { 64 | d, ok = temp[v] 65 | if !ok { 66 | break 67 | } 68 | temp = Any2Map(d) 69 | } 70 | return d, ok 71 | } 72 | 73 | // Get 获取配置,多级使用::,例如:user::name 74 | func (c *conf) Get(key string) (any, bool) { 75 | if c.Data == nil { 76 | _ = c.Init() 77 | } 78 | return c.get(key) 79 | } 80 | 81 | // GetString 获取配置,配置不存在返回 "", false 82 | // ex: conf.GetString("user::name") 83 | func (c *conf) GetString(key string) (string, bool) { 84 | if c.Data == nil { 85 | _ = c.Init() 86 | } 87 | val, has := c.get(key) 88 | if !has { 89 | return "", has 90 | } 91 | return Any2String(val), has 92 | } 93 | 94 | func (c *conf) GetAll() map[string]any { 95 | return c.Data 96 | } 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mangenotwork/gathertool 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/Shopify/sarama v1.38.1 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/go-sql-driver/mysql v1.7.1 9 | github.com/nsqio/go-nsq v1.1.0 10 | github.com/streadway/amqp v1.0.0 11 | github.com/xuri/excelize/v2 v2.7.0 12 | golang.org/x/crypto v0.14.0 13 | golang.org/x/net v0.17.0 14 | golang.org/x/text v0.13.0 15 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df 16 | gopkg.in/yaml.v3 v3.0.1 17 | ) 18 | 19 | require ( 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/eapache/go-resiliency v1.3.0 // indirect 22 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect 23 | github.com/eapache/queue v1.1.0 // indirect 24 | github.com/golang/snappy v0.0.4 // indirect 25 | github.com/hashicorp/errwrap v1.0.0 // indirect 26 | github.com/hashicorp/go-multierror v1.1.1 // indirect 27 | github.com/hashicorp/go-uuid v1.0.3 // indirect 28 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 29 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 30 | github.com/jcmturner/gofork v1.7.6 // indirect 31 | github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect 32 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 33 | github.com/klauspost/compress v1.15.14 // indirect 34 | github.com/kr/text v0.1.0 // indirect 35 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 36 | github.com/pierrec/lz4/v4 v4.1.17 // indirect 37 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 38 | github.com/richardlehane/mscfb v1.0.4 // indirect 39 | github.com/richardlehane/msoleps v1.0.3 // indirect 40 | github.com/rogpeppe/go-internal v1.11.0 // indirect 41 | github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect 42 | github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect 43 | golang.org/x/sys v0.13.0 // indirect 44 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /_test/map_helper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | // go test -v -run=TestMapHelperOderMap 9 | func TestMapHelperOderMap(t *testing.T) { 10 | intMap := gt.NewOrderMap[int, int]() 11 | intMap.Add(1, 1) 12 | t.Log(intMap.Get(1)) 13 | intMap.Del(1) 14 | t.Log(intMap.Get(1)) 15 | intMap.Add(1, 1).Add(2, 2).Add(3, 3) 16 | intMap.RangeAt(func(id, k, v int) { 17 | t.Log(id, k, v) 18 | }) 19 | stringMap := gt.NewOrderMap[string, int]() 20 | stringMap.Add("a", 1) 21 | t.Log(stringMap.Get("a")) 22 | stringMap.Del("a") 23 | t.Log(stringMap.Get("a")) 24 | stringMap.Add("a", 1) 25 | stringMap.AddMap(map[string]int{"b": 2, "c": 3}) 26 | stringMap.RangeAt(func(id int, k string, v int) { 27 | t.Log(id, k, v) 28 | }) 29 | stringMap.Reverse() 30 | stringMap.RangeAt(func(id int, k string, v int) { 31 | t.Log(id, k, v) 32 | }) 33 | t.Log(stringMap.KeyList()) 34 | 35 | t.Log(stringMap.Json()) 36 | 37 | t.Log(stringMap.Move("a", 2)) 38 | stringMap.DebugPrint() 39 | t.Log(stringMap.Move("a", 3)) 40 | t.Log(stringMap.Insert("d", 4, 1)) 41 | stringMap.DebugPrint() 42 | 43 | t.Log("洗牌 ------> ") 44 | stringMap.Shuffle() 45 | stringMap.DebugPrint() 46 | 47 | t.Log(stringMap.GetAtPosition(2)) 48 | t.Log(stringMap.Pop()) 49 | stringMap.DebugPrint() 50 | t.Log(stringMap.BackPop()) 51 | stringMap.DebugPrint() 52 | 53 | } 54 | 55 | // go test -v -run=TestMapHelperSet 56 | func TestMapHelperSet(t *testing.T) { 57 | s := gt.NewSet() 58 | s.Add(1) 59 | s.Add(1) 60 | s.DebugPrint() 61 | s.Add(2) 62 | s.DebugPrint() 63 | t.Log(s.Has(3)) 64 | t.Log(s.Has(1)) 65 | s.Delete(1) 66 | s.DebugPrint() 67 | } 68 | 69 | // go test -v -run=TestMapHelperStack 70 | func TestMapHelperStack(t *testing.T) { 71 | s := gt.NewStack[string]() 72 | s.Push("a") 73 | s.Push("b") 74 | s.DebugPrint() 75 | t.Log(s.Pop()) 76 | s.DebugPrint() 77 | } 78 | 79 | // go test -v -run=TestMapHelper 80 | func TestMapHelper(t *testing.T) { 81 | map1 := map[int]string{1: "a", 2: "b"} 82 | map2 := gt.MapCopy(map1) 83 | t.Log(map2) 84 | t.Log(gt.MapMergeCopy(map1, map[int]string{3: "c", 2: "b"})) 85 | 86 | t.Log(gt.Map2Slice(map1)) 87 | 88 | t.Log(gt.Slice2Map([]any{"a", 1, "b", 2})) 89 | } 90 | -------------------------------------------------------------------------------- /_examples/get/get.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | gt "github.com/mangenotwork/gathertool" 9 | ) 10 | 11 | func main() { 12 | SimpleGet1() 13 | SimpleGet2() 14 | SimpleGet3() 15 | SimpleGet4() 16 | 17 | //Case() 18 | } 19 | 20 | // SimpleGet1 简单的get请求实例, 写法一: 方法做为请求函数的参数; 21 | func SimpleGet1() { 22 | // 创建请求 23 | ctx := gt.NewGet("http://192.168.3.1", 24 | //设置请求成功后的方法: 请求的数据 25 | gt.SucceedFunc(func(ctx *gt.Context) { 26 | log.Println(string(ctx.RespBody)) 27 | }), 28 | 29 | //设置请求失败后的方法: 打印失败信息 30 | gt.FailedFunc(func(ctx *gt.Context) { 31 | log.Println(ctx.Err) 32 | }), 33 | 34 | //设置重试前的方法(遇到403,502 等状态码会重试): 睡眠1s再重试 35 | gt.RetryFunc(func(ctx *gt.Context) { 36 | time.Sleep(1 * time.Second) 37 | }), 38 | ) 39 | // 执行创建的请求 40 | ctx.Do() 41 | 42 | } 43 | 44 | // SimpleGet2 最简单的get请求实例, 写法二: 上下文处理; 45 | func SimpleGet2() { 46 | // 创建请求 47 | ctx, err := gt.Get("http://192.168.3.1") 48 | // 打印请求结果与请求错误 49 | log.Println(ctx.RespBodyString(), err) 50 | } 51 | 52 | // SimpleGet3 简单的get请求实例, 写法三: 给请求设置方法; 53 | func SimpleGet3() { 54 | // 创建请求 55 | gt.NewGet("http://192.168.3.1").SetSucceedFunc(func(ctx *gt.Context) { 56 | //设置请求成功后的方法 57 | log.Println(string(ctx.RespBody)) 58 | }).SetFailedFunc(func(ctx *gt.Context) { 59 | //设置请求失败后的方法 60 | log.Println(ctx.Err) 61 | }).SetRetryFunc(func(*gt.Context) { 62 | //设置重试前的方法 63 | time.Sleep(1 * time.Second) 64 | }).Do() 65 | } 66 | 67 | // SimpleGet4 简单的get请求实例, 写法四: 外部函数为请求方法; 68 | func SimpleGet4() { 69 | _, _ = gt.Get("http://192.168.3.1", 70 | gt.SucceedFunc(succeed), 71 | gt.FailedFunc(fail), 72 | gt.RetryFunc(retry), 73 | ) 74 | } 75 | 76 | // 成功后的方法 77 | func succeed(ctx *gt.Context) { 78 | log.Println(string(ctx.RespBody)) 79 | //处理数据 80 | } 81 | 82 | // 设置需要重试状态码, 重试前的方法 83 | func retry(ctx *gt.Context) { 84 | ctx.Client = &http.Client{ 85 | Timeout: 1 * time.Second, 86 | } 87 | log.Println("休息1s") 88 | time.Sleep(1 * time.Second) 89 | } 90 | 91 | // 失败后的方法 92 | func fail(ctx *gt.Context) { 93 | log.Println("请求失败: ", ctx.Err) 94 | } 95 | 96 | func Case() { 97 | a := gt.Any2String("aasddas") 98 | log.Println(a) 99 | } 100 | -------------------------------------------------------------------------------- /_examples/cnlinfo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | ) 6 | 7 | // 行业信息网 : http://www.cnlinfo.net/ 8 | 9 | func init() { 10 | // 读取配置 11 | err := gt.NewConf("conf.yaml") 12 | if err != nil { 13 | panic(err) 14 | } 15 | } 16 | 17 | // Rse 抓取结果保存的数据结构 18 | type Rse struct { 19 | Broad string `json:"broad"` 20 | Fenlei []string `json:"fenlei"` 21 | } 22 | 23 | var ( 24 | rse = make([]*Rse, 0) 25 | ) 26 | 27 | // 获取行业分类 28 | func main() { 29 | caseUrl := "http://www.cnlinfo.net/allgongsifenlei/yiqiyibiao.htm" 30 | ctx, _ := gt.Get(caseUrl) 31 | hangyeList, err := gt.GetPointClassHTML(ctx.Html, "ul", "hangye-list") 32 | if err != nil { 33 | gt.Error("没有获取到 ul class = hangye-list") 34 | return 35 | } 36 | hangyeHtml := "" 37 | if len(hangyeList) > 0 { 38 | hangyeHtml = hangyeList[0] 39 | } 40 | // 获取并便利分类大类 41 | for _, li := range gt.RegHtmlA(hangyeHtml) { 42 | broad := gt.RegHtmlATxt(li)[0] 43 | href := gt.RegHtmlHrefTxt(li)[0] 44 | // 抓取小类 45 | getClassify2(broad, href) 46 | } 47 | // 保存并输出到json文件 48 | err = gt.OutJsonFile(rse, gt.Config.GetStr("out_file")) 49 | if err != nil { 50 | gt.Error(err) 51 | } 52 | } 53 | 54 | // 获取小类 55 | func getClassify2(broad, caseUrl string) { 56 | var ( 57 | notData = 0 58 | reqDelayMax = gt.Config.GetInt("req_delay_max") 59 | reqDelayMin = gt.Config.GetInt("req_delay_min") 60 | ) 61 | d := &Rse{ 62 | Broad: broad, 63 | Fenlei: make([]string, 0), 64 | } 65 | gt.Infof("抓取大类 %v : %v", broad, caseUrl) 66 | ctx, _ := gt.Get(caseUrl, gt.SetSleep(reqDelayMin, reqDelayMax)) 67 | hangyeContent, err := gt.GetPointClassHTML(ctx.Html, "ul", "hangye-fenlei-content") 68 | if err != nil { 69 | gt.Error("没有获取到 ul class = hangye-fenlei-content") 70 | notData++ 71 | } 72 | for _, ul := range hangyeContent { 73 | for _, a := range gt.RegHtmlATxt(ul) { 74 | d.Fenlei = append(d.Fenlei, a) 75 | } 76 | } 77 | // 第二种样式 78 | hangyeContent1, err := gt.GetPointClassHTML(ctx.Html, "ul", "fenlei_list1") 79 | if err != nil { 80 | gt.Error("没有获取到 ul class = fenlei_list1") 81 | notData++ 82 | } 83 | for _, ul := range hangyeContent1 { 84 | for _, a := range gt.RegHtmlATxt(ul) { 85 | d.Fenlei = append(d.Fenlei, a) 86 | } 87 | } 88 | rse = append(rse, d) 89 | if notData == 2 { 90 | panic("该页面没有数据") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /_examples/qihuo/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | url: http://futures.100ppi.com/qhb/day-2022-07-05.html 3 | 抓取期货数据 4 | */ 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | gt "github.com/mangenotwork/gathertool" 10 | "log" 11 | "strings" 12 | ) 13 | 14 | // 数据库对象 15 | var ( 16 | host192 = "192.168.0.192" 17 | port = 3306 18 | user = "root" 19 | password = "root123" 20 | dbName = "test" 21 | DB, _ = gt.NewMysql(host192, port, user, password, dbName) 22 | OutTable = "qihuo" 23 | ) 24 | 25 | func main() { 26 | date := "2022-07-05" 27 | caseUrl := "http://futures.100ppi.com/qhb/day-%s.html" 28 | ctx, _ := gt.Get(fmt.Sprintf(caseUrl, date)) 29 | //log.Println(ctx.Html) 30 | // 数据提取 31 | datas, _ := gt.GetPointHTML(ctx.Html, "div", "id", "domestic") 32 | Data(datas, date, "内期表", "备注:内期表=国内期货主力合约表") 33 | datas, _ = gt.GetPointHTML(ctx.Html, "div", "id", "overseas") 34 | Data(datas, date, "外期表", "备注:外期表=国外期货主力合约表") 35 | } 36 | 37 | func Data(datas []string, date, typeName, note string) { 38 | for _, data := range datas { 39 | table, _ := gt.GetPointHTML(data, "table", "id", "fdata") 40 | if len(table) > 0 { 41 | trList := gt.RegHtmlTr(table[0]) 42 | jys := "" 43 | for _, tr := range trList { 44 | td := gt.RegHtmlTd(tr) 45 | log.Println("td = ", td, len(td)) 46 | if len(td) == 1 { 47 | jys = gt.RegHtmlTdTxt(td[0])[0] 48 | continue 49 | } 50 | name := gt.RegHtmlTdTxt(td[0])[0] 51 | if strings.Index(name, "商品名称") != -1 { 52 | continue 53 | } 54 | zlhy := gt.RegHtmlTdTxt(td[1])[0] 55 | jsj := gt.RegHtmlTdTxt(td[2])[0] 56 | zd := gt.RegDelHtml(gt.RegHtmlTdTxt(td[3])[0]) 57 | cjj := gt.RegHtmlTdTxt(td[4])[0] 58 | ccl := gt.RegHtmlTdTxt(td[5])[0] 59 | dw := gt.RegHtmlTdTxt(td[6])[0] 60 | log.Println("日期 = ", date) 61 | log.Println("机构 = ", jys) 62 | log.Println("商品名称 = ", name) 63 | log.Println("主力合约 = ", zlhy) 64 | log.Println("结算价 = ", jsj) 65 | log.Println("涨跌 = ", zd) 66 | log.Println("成交量 = ", cjj) 67 | log.Println("持仓量 = ", ccl) 68 | log.Println("单位 = ", dw) 69 | gd := gt.NewGDMap().Add("date", date).Add("type_name", typeName).Add("note", note) 70 | gd.Add("jys", jys).Add("name", name).Add("zlhy", zlhy).Add("jsj", jsj) 71 | gd.Add("zd", zd).Add("cjj", cjj).Add("ccl", ccl).Add("dw", dw) 72 | err := DB.InsertAtGd(OutTable, gd) 73 | if err != nil { 74 | panic(err) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /_examples/json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | ) 7 | 8 | func main(){ 9 | txt := `{ 10 | "reason":"查询成功!", 11 | "result":{ 12 | "city":"苏州", 13 | "realtime":{ 14 | "temperature":"17", 15 | "humidity":"69", 16 | "info":"阴", 17 | "wid":"02", 18 | "direct":"东风", 19 | "power":"2级", 20 | "aqi":"30" 21 | }, 22 | "future":[ 23 | { 24 | "date":"2021-10-25", 25 | "temperature":"12\/21℃", 26 | "weather":"多云", 27 | "wid":{ 28 | "day":"01", 29 | "night":"01" 30 | }, 31 | "direct":"东风" 32 | }, 33 | { 34 | "date":"2021-10-26", 35 | "temperature":"13\/21℃", 36 | "weather":"多云", 37 | "wid":{ 38 | "day":"01", 39 | "night":"01" 40 | }, 41 | "direct":"东风转东北风" 42 | }, 43 | { 44 | "date":"2021-10-27", 45 | "temperature":"13\/22℃", 46 | "weather":"多云", 47 | "wid":{ 48 | "day":"01", 49 | "night":"01" 50 | }, 51 | "direct":"东北风" 52 | }, 53 | { 54 | "date":"2021-10-28", 55 | "temperature":"13\/21℃", 56 | "weather":"多云转晴", 57 | "wid":{ 58 | "day":"01", 59 | "night":"00" 60 | }, 61 | "direct":"东北风" 62 | }, 63 | { 64 | "date":"2021-10-29", 65 | "temperature":"14\/21℃", 66 | "weather":"多云转小雨", 67 | "wid":{ 68 | "day":"01", 69 | "night":"07" 70 | }, 71 | "direct":"东北风" 72 | } 73 | ] 74 | }, 75 | "error_code":0 76 | }` 77 | 78 | jx1 := "/result/future/[0]/date" 79 | jx2 := "/result/future/[0]" 80 | jx3 := "/result/future" 81 | 82 | log.Println(gt.JsonFind(txt, jx1)) 83 | log.Println(gt.JsonFind2Json(txt, jx2)) 84 | log.Println(gt.JsonFind2Json(txt, jx3)) 85 | log.Println(gt.JsonFind2Map(txt, jx2)) 86 | log.Println(gt.JsonFind2Arr(txt, jx3)) 87 | 88 | } 89 | 90 | -------------------------------------------------------------------------------- /_test/string_helper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestStringHelperTypeConversion(t *testing.T) { 9 | t.Log(gt.Any2String(1)) 10 | t.Log(gt.Any2String(2.22)) 11 | t.Log(gt.Any2String(true)) 12 | t.Log(gt.Any2String([]int{1, 2})) 13 | 14 | t.Log(gt.Json2Map("{\"a\":2,\"3\":4}")) 15 | t.Log(gt.Any2Map("{\"a\":2,\"3\":4}")) 16 | 17 | t.Log(gt.Any2Int("123")) 18 | t.Log(gt.Any2Int("123a")) 19 | t.Log(gt.Any2Int64("123")) 20 | t.Log(gt.Any2Arr("123,123")) 21 | t.Log(gt.Any2Float64("123.123")) 22 | 23 | t.Log(gt.Int2Hex(123)) 24 | t.Log(gt.Int642Hex(123)) 25 | t.Log(gt.Hex2Int("7b")) 26 | t.Log(gt.Hex2Int64("7b")) 27 | t.Log(gt.Str2Int("123")) 28 | t.Log(gt.Str2Int32("123")) 29 | t.Log(gt.Str2Float64("123.123")) 30 | t.Log(gt.Str2Float32("123.123")) 31 | t.Log(gt.Uint82Str([]uint8{1, 2, 3})) 32 | t.Log(gt.Str2Byte("abc")) 33 | t.Log(gt.Byte2Str([]byte("abc"))) 34 | t.Log(gt.Bool2Byte(true)) 35 | t.Log(gt.Byte2Bool([]byte("0"))) 36 | t.Log(gt.Int2Byte(123)) 37 | t.Log(gt.Byte2Int([]byte("1111"))) 38 | t.Log(gt.Int642Byte(123)) 39 | t.Log(gt.Byte2Int64(gt.Int642Byte(123))) 40 | t.Log(gt.Float322Byte(123.123)) 41 | t.Log(gt.Float322Uint32(123.123)) 42 | t.Log(gt.Byte2Float32(gt.Float322Byte(123.123))) 43 | t.Log(gt.Float642Byte(123.123)) 44 | t.Log(gt.Float642Uint64(123.123)) 45 | t.Log(gt.Byte2Float64(gt.Float642Byte(123.123))) 46 | t.Log(gt.Float642Uint64(123.123)) 47 | t.Log(gt.Byte2Float64(gt.Float642Byte(123.123))) 48 | type A struct { 49 | C string 50 | } 51 | a1 := &A{C: "1"} 52 | t.Log(gt.Struct2Map(a1, true)) 53 | 54 | t.Log(gt.CleaningStr(" sada\n\t\r ")) 55 | t.Log(gt.StrDeleteSpace(" sada ")) 56 | t1 := gt.EncodeByte("test1") 57 | t.Log(t1) 58 | t.Log(gt.DecodeByte(t1)) 59 | t.Log(gt.Byte2Bit([]byte("aaa"))) 60 | t.Log(gt.Bit2Byte(gt.Byte2Bit([]byte("aaa")))) 61 | 62 | a2 := &A{} 63 | t.Log(gt.DeepCopy[*A](a2, a1)) 64 | t.Log(a2) 65 | 66 | t.Log(gt.PanicToError(func() { 67 | panic(1) 68 | })) 69 | 70 | t.Log(gt.ByteToBinaryString(byte('a'))) 71 | t.Log(gt.UnicodeDec("aaa")) 72 | t.Log(gt.UnicodeDecByte([]byte("aaa"))) 73 | t.Log(gt.UnescapeUnicode(gt.UnicodeDecByte([]byte("aaa")))) 74 | t.Log(gt.Base64Encode("一二三")) 75 | t.Log(gt.Base64Decode(gt.Base64Encode("一二三"))) 76 | t.Log(gt.Base64UrlEncode("一二三")) 77 | t.Log(gt.Base64UrlDecode(gt.Base64UrlEncode("一二三"))) 78 | 79 | t2, _ := gt.ToUTF8("HZGB2312", "一二三") 80 | t.Log(t2) 81 | t.Log(gt.UTF8To("HZGB2312", t2)) 82 | 83 | t3, _ := gt.ToUTF16("HZGB2312", "一二三") 84 | t.Log(t3) 85 | t.Log(gt.UTF16To("HZGB2312", t3)) 86 | 87 | t4, _ := gt.ToBIG5("HZGB2312", "一二三") 88 | t.Log(t4) 89 | t.Log(gt.BIG5To("HZGB2312", t4)) 90 | 91 | t5, _ := gt.ToGDK("HZGB2312", "一二三") 92 | t.Log(t5) 93 | t.Log(gt.GDKTo("HZGB2312", t5)) 94 | 95 | t6, _ := gt.ToGB18030("HZGB2312", "一二三") 96 | t.Log(t6) 97 | t.Log(gt.GB18030To("HZGB2312", t6)) 98 | 99 | t7, _ := gt.ToGB2312("HZGB2312", "一二三") 100 | t.Log(t7) 101 | t.Log(gt.GB2312To("HZGB2312", t7)) 102 | 103 | t8, _ := gt.ToHZGB2312("HZGB2312", "一二三") 104 | t.Log(t8) 105 | t.Log(gt.HZGB2312To("HZGB2312", t8)) 106 | 107 | t.Log(gt.IF[string](true, "a", "b")) 108 | 109 | t.Log(gt.ReplaceAllToOne("aaacvccc", []string{"a", "c"}, "")) 110 | 111 | t.Log(gt.HumanFriendlyTraffic(12354646)) 112 | t.Log(gt.StrToSize("123415kb")) 113 | 114 | t.Log(gt.IP2Binary("127.0.0.1")) 115 | t.Log(gt.UInt32ToIP(12700001)) 116 | t.Log(gt.IP2Int64("127.0.0.1")) 117 | 118 | t.Log(gt.GzipCompress([]byte("abc"))) 119 | t.Log(gt.GzipDecompress(gt.GzipCompress([]byte("abc")))) 120 | t.Log(gt.ConvertStr2GBK("一二三")) 121 | t.Log(gt.ConvertGBK2Str("一二三")) 122 | t.Log(gt.ByteToGBK([]byte("一二三"))) 123 | t.Log(gt.ByteToUTF8([]byte("一二三"))) 124 | t.Log(gt.ID()) 125 | t.Log(gt.IDMd5()) 126 | 127 | } 128 | 129 | func TestStringHelper(t *testing.T) { 130 | t.Log(gt.MD5("asdsad")) 131 | } 132 | -------------------------------------------------------------------------------- /_examples/weibo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | 12 | //case1() 13 | 14 | //case2() 15 | 16 | //case3() 17 | 18 | case4() 19 | } 20 | 21 | func case1() { 22 | 23 | /* 24 | category 25 | 0 热门 26 | 1760 头条 27 | 99991 榜单 28 | 10011 高笑 29 | 7 社会 30 | 12 时尚 31 | 10018 电影 32 | 10007 美女 33 | 3 体育 34 | 10005 动漫 35 | */ 36 | 37 | url := "https://weibo.com/a/aj/transform/loadingmoreunlogin?ajwvr=6&category=0&page=2&lefnav=0&cursor=&__rnd=" + gt.TimestampStr() 38 | ctx, _ := gt.Get(url, gt.SucceedFunc(succed)) 39 | ctx.Req.AddCookie(&http.Cookie{Name: "SUBP", Value: "0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWENAjmKyIZz1AWjDi68mRw", HttpOnly: true}) 40 | ctx.Req.AddCookie(&http.Cookie{Name: "SUB", Value: "_2AkMXxWiSf8NxqwFRmPoWz2nlbop1zwvEieKhmZlJJRMxHRl-yT9jqlAItRB6PEVGfTP09XmsX_7CR2H1OUv6b-f-1bJl", HttpOnly: true}) 41 | ctx.Do() 42 | } 43 | 44 | func succed(ctx *gt.Context) { 45 | 46 | //html := gt.ConvertByte2String(ctx.RespBody, gt.GB2312) 47 | htmlBody, _ := gt.UnescapeUnicode(ctx.RespBody) 48 | html := string(htmlBody) 49 | html = strings.Replace(html, "\\r", "", -1) 50 | html = strings.Replace(html, "\\n", "", -1) 51 | html = strings.Replace(html, "\\", "", -1) 52 | 53 | } 54 | 55 | // cookie 持久化 56 | func case2() { 57 | // ========== 获取 tid 58 | //由 https://passport.weibo.com/js/visitor/mini_original.js?v=20161116 //main 执行入口 js代码 找到tid的方式 59 | // 也找到了 fpPostInterface: "visitor/genvisitor" 60 | // fpCollectInterface: "visitor/record" 61 | 62 | // cb 固定值 gen_callback 63 | //getTidUrl := "https://passport.weibo.com/visitor/genvisitor?cb=gen_callback&fp={\"os\":\"1\",\"browser\":\"Chrome70,0,3538,25\",\"fonts\":\"undefined\",\"screenInfo\":\"1920*1080*24\",\"plugins\":\"\"}" 64 | //ctx, _ := gt.Get(getTidUrl) 65 | //log.Println(string(ctx.RespBody)) 66 | 67 | // ============ 获取sub subp 68 | // F12观察到 https://passport.weibo.com/visitor/visitor?a=restore&cb=restore_back&from=weibo&_rand=0.8977533486179248 69 | // 获取的cookie 70 | // a 固定 incarnate 71 | // t 上一步得到的tid 72 | // w 上一步 new_tid = true 就是3 否则为2 73 | // cb 固定 cross_domain 74 | // from 固定 weibo 75 | 76 | getSubUrl := "https://passport.weibo.com/visitor/visitor?a=incarnate&t=h2b7xQtQwqk2cEjMgH/0AaWYvpijlgCCAs3qDzj2W58=&w=3&c&cb=restore_back&from=weibo" 77 | ctx, _ := gt.Get(getSubUrl) 78 | log.Println(ctx.Resp) 79 | log.Println(ctx.Resp.Cookies()) 80 | log.Println(string(ctx.RespBody)) 81 | 82 | //getSubUrl := "https://passport.weibo.com/visitor/visitor?a=restore&cb=restore_back&from=weibo&_rand=0.8977533486179248" 83 | //ctx,_ := gt.Get(getSubUrl) 84 | //ctx.Req.AddCookie(&http.Cookie{Name: "tid",Value: `HVyCvxI2gJHD6a6/hDOaWo3MNgwy9X8LwZFUG7o3vNA=__095`, HttpOnly: true}) 85 | ////ctx.Req.AddCookie(&http.Cookie{Name: "SRT",Value: "D.QqHBTrsR5QPiUcRtOeYoWr9NUPB3R39Qi-bYNdo35QWwMdbbN-YjTmntNbHi5mYNUCsuTZbgVdYC43MNAZSAMQHK549Q4qiaK4S1VFM6R4YbVP9GUqYYT3AqW-kmdA77%2AB.vAflW-P9Rc0lR-ykKDvnJqiQVbiRVPBtS%21r3J8sQVqbgVdWiMZ4siOzu4DbmKPWQU4PYU%21SiM4b9M-yMi%21VkR3mpIbPw", HttpOnly: true}) 86 | ////ctx.Req.AddCookie(&http.Cookie{Name: "SRF",Value: "1620470800", HttpOnly: true}) 87 | //ctx.Do() 88 | //log.Println(ctx.Resp) 89 | //log.Println(ctx.Resp.Cookies()) 90 | //log.Println(ctx.Resp.TransferEncoding) 91 | //log.Println(string(ctx.RespBody)) 92 | } 93 | 94 | func case3() { 95 | gt.MongoConn() 96 | } 97 | 98 | // 判断微博, 手机号是否注册 99 | func case4() { 100 | caseUrl := "https://weibo.com/signup/v5/formcheck?type=mobilesea&zone=0086&value=18483663083&from=&__rnd=" 101 | log.Println(caseUrl) 102 | ctx := gt.NewGet(caseUrl) 103 | ctx.AddHeader("referer", "https://weibo.com/signup/signup.php") 104 | ctx.AddHeader("accept-language", "zh-CN,zh;q=0.9") 105 | ctx.Do() 106 | log.Println(ctx.Resp.Status, gt.UnicodeDec(ctx.RespBodyString())) 107 | } 108 | -------------------------------------------------------------------------------- /id_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : id,有雪花id, uuid(todo) 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "crypto/md5" 11 | "encoding/hex" 12 | "fmt" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | // =================================================== 雪花Id 18 | 19 | type IdWorker struct { 20 | startTime int64 21 | workerIdBits uint 22 | datacenterIdBits uint 23 | maxWorkerId int64 24 | maxDatacenterId int64 25 | sequenceBits uint 26 | workerIdLeftShift uint 27 | datacenterIdLeftShift uint 28 | timestampLeftShift uint 29 | sequenceMask int64 30 | workerId int64 31 | datacenterId int64 32 | sequence int64 33 | lastTimestamp int64 34 | signMask int64 35 | idLock *sync.Mutex 36 | } 37 | 38 | func (idw *IdWorker) InitIdWorker(workerId, datacenterId int64) error { 39 | var baseValue int64 = -1 40 | idw.startTime = 1463834116272 41 | idw.workerIdBits = 5 42 | idw.datacenterIdBits = 5 43 | idw.maxWorkerId = baseValue ^ (baseValue << idw.workerIdBits) 44 | idw.maxDatacenterId = baseValue ^ (baseValue << idw.datacenterIdBits) 45 | idw.sequenceBits = 12 46 | idw.workerIdLeftShift = idw.sequenceBits 47 | idw.datacenterIdLeftShift = idw.workerIdBits + idw.workerIdLeftShift 48 | idw.timestampLeftShift = idw.datacenterIdBits + idw.datacenterIdLeftShift 49 | idw.sequenceMask = baseValue ^ (baseValue << idw.sequenceBits) 50 | idw.sequence = 0 51 | idw.lastTimestamp = -1 52 | idw.signMask = ^baseValue + 1 53 | idw.idLock = &sync.Mutex{} 54 | if idw.workerId < 0 || idw.workerId > idw.maxWorkerId { 55 | return fmt.Errorf("workerId[%v] is less than 0 or greater than maxWorkerId[%v]", 56 | workerId, datacenterId) 57 | } 58 | if idw.datacenterId < 0 || idw.datacenterId > idw.maxDatacenterId { 59 | return fmt.Errorf("datacenterId[%d] is less than 0 or greater than maxDatacenterId[%d]", 60 | workerId, datacenterId) 61 | } 62 | idw.workerId = workerId 63 | idw.datacenterId = datacenterId 64 | return nil 65 | } 66 | 67 | // NextId 返回一个唯一的 INT64 ID 68 | func (idw *IdWorker) NextId() (int64, error) { 69 | idw.idLock.Lock() 70 | timestamp := time.Now().UnixNano() 71 | if timestamp < idw.lastTimestamp { 72 | return -1, fmt.Errorf(fmt.Sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", 73 | idw.lastTimestamp-timestamp)) 74 | } 75 | if timestamp == idw.lastTimestamp { 76 | idw.sequence = (idw.sequence + 1) & idw.sequenceMask 77 | if idw.sequence == 0 { 78 | timestamp = idw.tilNextMillis() 79 | idw.sequence = 0 80 | } 81 | } else { 82 | idw.sequence = 0 83 | } 84 | idw.lastTimestamp = timestamp 85 | idw.idLock.Unlock() 86 | id := ((timestamp - idw.startTime) << idw.timestampLeftShift) | 87 | (idw.datacenterId << idw.datacenterIdLeftShift) | 88 | (idw.workerId << idw.workerIdLeftShift) | 89 | idw.sequence 90 | if id < 0 { 91 | id = -id 92 | } 93 | return id, nil 94 | } 95 | 96 | // tilNextMillis 97 | func (idw *IdWorker) tilNextMillis() int64 { 98 | timestamp := time.Now().UnixNano() 99 | if timestamp <= idw.lastTimestamp { 100 | timestamp = time.Now().UnixNano() / int64(time.Millisecond) 101 | } 102 | return timestamp 103 | } 104 | 105 | func ID64() (int64, error) { 106 | currWorker := &IdWorker{} 107 | err := currWorker.InitIdWorker(1000, 2) 108 | if err != nil { 109 | return 0, err 110 | } 111 | return currWorker.NextId() 112 | } 113 | 114 | func ID() int64 { 115 | id, _ := ID64() 116 | return id 117 | } 118 | 119 | func IDStr() string { 120 | currWorker := &IdWorker{} 121 | err := currWorker.InitIdWorker(1000, 2) 122 | if err != nil { 123 | return "" 124 | } 125 | id, err := currWorker.NextId() 126 | if err != nil { 127 | return "" 128 | } 129 | return Any2String(id) 130 | } 131 | 132 | func IDMd5() string { 133 | return Get16MD5Encode(IDStr()) 134 | } 135 | 136 | // MD5 MD5 137 | func MD5(str string) string { 138 | h := md5.New() 139 | h.Write([]byte(str)) 140 | return hex.EncodeToString(h.Sum(nil)) 141 | } 142 | -------------------------------------------------------------------------------- /_test/reg_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "testing" 6 | ) 7 | 8 | func TestReg(t *testing.T) { 9 | // 优先检查正则表达式是否报错,程序是否中断 10 | ctx, _ := gt.Get("https://golang.google.cn/") 11 | gt.RegFindAllTxt(`(?is:)`, ctx.Html) 12 | gt.RegHtmlA(ctx.Html) 13 | gt.RegHtmlTitle(ctx.Html) 14 | gt.RegHtmlKeyword(ctx.Html) 15 | gt.RegHtmlDescription(ctx.Html) 16 | gt.RegHtmlTr(ctx.Html) 17 | gt.RegHtmlInput(ctx.Html) 18 | gt.RegHtmlTd(ctx.Html) 19 | gt.RegHtmlP(ctx.Html) 20 | gt.RegHtmlSpan(ctx.Html) 21 | gt.RegHtmlSrc(ctx.Html) 22 | gt.RegHtmlHref(ctx.Html) 23 | gt.RegHtmlH1(ctx.Html) 24 | gt.RegHtmlH2(ctx.Html) 25 | gt.RegHtmlH3(ctx.Html) 26 | gt.RegHtmlH4(ctx.Html) 27 | gt.RegHtmlH5(ctx.Html) 28 | gt.RegHtmlH6(ctx.Html) 29 | gt.RegHtmlTbody(ctx.Html) 30 | gt.RegHtmlVideo(ctx.Html) 31 | gt.RegHtmlCanvas(ctx.Html) 32 | gt.RegHtmlCode(ctx.Html) 33 | gt.RegHtmlImg(ctx.Html) 34 | gt.RegHtmlUl(ctx.Html) 35 | gt.RegHtmlLi(ctx.Html) 36 | gt.RegHtmlMeta(ctx.Html) 37 | gt.RegHtmlSelect(ctx.Html) 38 | gt.RegHtmlTable(ctx.Html) 39 | gt.RegHtmlButton(ctx.Html) 40 | gt.RegHtmlTableOnly(ctx.Html) 41 | gt.RegHtmlDiv(ctx.Html) 42 | gt.RegHtmlOption(ctx.Html) 43 | 44 | // 取标签内容 45 | gt.RegHtmlATxt(ctx.Html) 46 | gt.RegHtmlTitleTxt(ctx.Html) 47 | gt.RegHtmlKeywordTxt(ctx.Html) 48 | gt.RegHtmlDescriptionTxt(ctx.Html) 49 | gt.RegHtmlTrTxt(ctx.Html) 50 | gt.RegHtmlTdTxt(ctx.Html) 51 | gt.RegHtmlPTxt(ctx.Html) 52 | gt.RegHtmlSpanTxt(ctx.Html) 53 | gt.RegHtmlSrcTxt(ctx.Html) 54 | gt.RegHtmlHrefTxt(ctx.Html) 55 | gt.RegHtmlH1Txt(ctx.Html) 56 | gt.RegHtmlH2Txt(ctx.Html) 57 | gt.RegHtmlH3Txt(ctx.Html) 58 | gt.RegHtmlH4Txt(ctx.Html) 59 | gt.RegHtmlH5Txt(ctx.Html) 60 | gt.RegHtmlH6Txt(ctx.Html) 61 | gt.RegHtmlCodeTxt(ctx.Html) 62 | gt.RegHtmlUlTxt(ctx.Html) 63 | gt.RegHtmlLiTxt(ctx.Html) 64 | gt.RegHtmlSelectTxt(ctx.Html) 65 | gt.RegHtmlTableTxt(ctx.Html) 66 | gt.RegHtmlButtonTxt(ctx.Html) 67 | gt.RegHtmlDivTxt(ctx.Html) 68 | gt.RegHtmlOptionTxt(ctx.Html) 69 | gt.RegValue(ctx.Html) 70 | 71 | // 删除 72 | gt.RegDelHtml(ctx.Html) 73 | gt.RegDelNumber(ctx.Html) 74 | 75 | // 是否含有 76 | gt.IsNumber(ctx.Html) 77 | gt.IsNumber2Len(ctx.Html, 2) 78 | gt.IsNumber2Heard(ctx.Html, 2) 79 | gt.IsFloat(ctx.Html) 80 | gt.IsFloat2Len(ctx.Html, 2) 81 | gt.IsEngAll(ctx.Html) 82 | gt.IsEngLen(ctx.Html, 2) 83 | gt.IsEngNumber(ctx.Html) 84 | gt.IsLeastNumber(ctx.Html, 2) 85 | gt.IsLeastCapital(ctx.Html, 2) 86 | gt.IsLeastLower(ctx.Html, 2) 87 | gt.IsLeastSpecial(ctx.Html, 2) 88 | gt.HaveNumber(ctx.Html) 89 | gt.HaveSpecial(ctx.Html) 90 | gt.IsEmail(ctx.Html) 91 | gt.IsDomain(ctx.Html) 92 | gt.IsURL(ctx.Html) 93 | gt.IsPhone(ctx.Html) 94 | gt.IsLandline(ctx.Html) 95 | gt.AccountRational(ctx.Html) 96 | gt.IsXMLFile(ctx.Html) 97 | gt.IsUUID3(ctx.Html) 98 | gt.IsUUID4(ctx.Html) 99 | gt.IsUUID5(ctx.Html) 100 | gt.IsRGB(ctx.Html) 101 | gt.IsFullWidth(ctx.Html) 102 | gt.IsHalfWidth(ctx.Html) 103 | gt.IsBase64(ctx.Html) 104 | gt.IsLatitude(ctx.Html) 105 | gt.IsLongitude(ctx.Html) 106 | gt.IsDNSName(ctx.Html) 107 | gt.IsIPv4(ctx.Html) 108 | gt.IsWindowsPath(ctx.Html) 109 | gt.IsUnixPath(ctx.Html) 110 | 111 | //常用正则 112 | gt.RegTime(ctx.Html) 113 | gt.RegLink(ctx.Html) 114 | gt.RegEmail(ctx.Html) 115 | gt.RegIPv4(ctx.Html) 116 | gt.RegIPv6(ctx.Html) 117 | gt.RegIP(ctx.Html) 118 | gt.RegMD5Hex(ctx.Html) 119 | gt.RegSHA1Hex(ctx.Html) 120 | gt.RegSHA256Hex(ctx.Html) 121 | gt.RegGUID(ctx.Html) 122 | gt.RegMACAddress(ctx.Html) 123 | gt.RegEmail2(ctx.Html) 124 | gt.RegUUID3(ctx.Html) 125 | gt.RegUUID4(ctx.Html) 126 | gt.RegUUID5(ctx.Html) 127 | gt.RegUUID(ctx.Html) 128 | gt.RegInt(ctx.Html) 129 | gt.RegFloat(ctx.Html) 130 | gt.RegRGBColor(ctx.Html) 131 | gt.RegFullWidth(ctx.Html) 132 | gt.RegHalfWidth(ctx.Html) 133 | gt.RegBase64(ctx.Html) 134 | gt.RegLatitude(ctx.Html) 135 | gt.RegLongitude(ctx.Html) 136 | gt.RegDNSName(ctx.Html) 137 | gt.RegFullURL(ctx.Html) 138 | gt.RegURLSchema(ctx.Html) 139 | gt.RegURLUsername(ctx.Html) 140 | gt.RegURLPath(ctx.Html) 141 | gt.RegURLPort(ctx.Html) 142 | gt.RegURLIP(ctx.Html) 143 | gt.RegURLSubdomain(ctx.Html) 144 | gt.RegWinPath(ctx.Html) 145 | gt.RegUnixPath(ctx.Html) 146 | } 147 | -------------------------------------------------------------------------------- /_examples/douyin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/PuerkitoBio/goquery" 6 | gt "github.com/mangenotwork/gathertool" 7 | "log" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | func main(){ 13 | caseUrl := "https://www.douyin.com/search/美女" 14 | ctx, _ := gt.Get(caseUrl) 15 | html := ctx.RespBodyString() 16 | log.Println("state = ", ctx.Resp.Status) 17 | log.Println("html = ", html) 18 | 19 | //https://www.douyin.com/aweme/v1/web/search/item/?device_platform=webapp&aid=6383&channel=channel_pc_web&search_channel=aweme_video_web&sort_type=0&publish_time=0&keyword=%E7%BE%8E%E5%A5%B3&search_source=normal_search&query_correct_type=1&is_filter_search=0&offset=0&count=24&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Linux+x86_64&browser_name=Mozilla&browser_version=5.0+(X11%3B+Linux+x86_64)+AppleWebKit%2F537.36+(KHTML,+like+Gecko)+Chrome%2F88.0.4324.182+Safari%2F537.36&browser_online=true&msToken=hYnIG63GJJkmsYDWKUjWsvEDcDPmZEuy8MYgsjXxSlU1ZN6MC8hm8GhE_9a3ZyzPYfjdiNH1NAyJ3f9kVKYoWZKhLA97yJEFpZE0iuAmTv-d_ANDzS_XIdM=&X-Bogus=DFSzswSO6iXANti9Sm4Fs7kkNL-I&_signature=_02B4Z6wo000012Jp3cwAAIDAlRxGJKBU6ztibdlAALnad5Pv.LnK808o1d1sNqHrn0TuRCRBKdT7SBouK177aJxXYha8PtRCONuke.0SGNVqrwEC0oaLm3b1csTU2JaAVmPgWXxliTxTbadT4d 20 | //https://www.douyin.com/aweme/v1/web/search/item/?device_platform=webapp&aid=6383&channel=channel_pc_web&search_channel=aweme_video_web&sort_type=0&publish_time=0&keyword=%E7%BE%8E%E5%A5%B3&search_source=normal_search&query_correct_type=1&is_filter_search=0&offset=0&count=24&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Linux+x86_64&browser_name=Mozilla&browser_version=5.0+(X11%3B+Linux+x86_64)+AppleWebKit%2F537.36+(KHTML,+like+Gecko)+Chrome%2F88.0.4324.182+Safari%2F537.36&browser_online=true&msToken=V87HYI3JZjqkHIzHUXeCX-ydzD-tg0a-3XnKe4ve8CKjU3aTgNooos1-YkuX4vqKdeh8M-AxIGQ7Se9ZYuaY9hMQ_DMOusY6fvd8-8m-s_PB18E3jWDonM4x&X-Bogus=DFSzswSO6EGANti9Sm4FddkkNL-N&_signature=_02B4Z6wo00001reRrigAAIDBQOQ1w1S1fya3laqAAMy9pcfZE.rxv6pEdxi2yA2oLpfeiTpV7qKmXzx8P8z6GWU76dRxCzblqXzukwCUP3ENdbAGBoU090l4J8MOqXBZgVDV19sE4LsODg5b07 21 | 22 | /* 23 | H = 6383 24 | , q = "douyin_website" 25 | , X = "" 26 | , W = "cbae2a42b075f1dfc39b9e7df764637c821e7bf2"; 27 | 28 | https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin/search/index.50db626a.js:formatted --> 11452 29 | 30 | 31 | */ 32 | 33 | 34 | } 35 | 36 | func main1(){ 37 | //ctx, err := gt.Upload("https://v26-web.douyinvod.com/1671afda348c88fb13af3d73bc727002/6155656b/video/tos/cn/tos-cn-ve-15/1b4fef278b124e659a69c648793dd278/?a=6383&br=2249&bt=2249&cd=0%7C0%7C0&ch=26&cr=0&cs=0&cv=1&dr=0&ds=4&er=&ft=~MeSY~88-o10DN7nh7TQPqeUfTusSYjVWy&l=2021093014201201021205308106205B1D&lr=all&mime_type=video_mp4&net=0&pl=0&qs=0&rc=amlybzk6ZnF2ODMzNGkzM0ApZjQ2M2g2OTw0Nzo7OzxmZmdrbS1scjRnaC9gLS1kLTBzczYyMmMzYGJiYGAwYzQ1YDU6Yw%3D%3D&vl=&vr=", 38 | // "/home/mange/Desktop/dy1.mp4") 39 | //log.Println(ctx.Resp.StatusCode, err) 40 | 41 | caseUrl := "https://www.douyin.com/video/7000282511469268264" 42 | ctx, _ := gt.Get(caseUrl) 43 | html := ctx.RespBodyString() 44 | log.Println("state = ", ctx.Resp.Status) 45 | log.Println("html = ", html) 46 | dom,err := goquery.NewDocumentFromReader(strings.NewReader(ctx.RespBodyString())) 47 | if err != nil{ 48 | log.Println(err) 49 | return 50 | } 51 | result := dom.Find("script[id=RENDER_DATA]") 52 | log.Println(result.Html()) 53 | res,_ := result.Html() 54 | unescape, _ := url.QueryUnescape(res) 55 | log.Println(unescape) 56 | 57 | m := make(map[string]interface{}) 58 | err = json.Unmarshal([]byte(unescape), &m) 59 | log.Println("json err = ", err) 60 | log.Println("m = ", m, "\n\n\n\n") 61 | 62 | c19 := m["C_19"].(map[string]interface{}) 63 | //log.Println("c19 = ", c19) 64 | 65 | aweme := c19["aweme"].(map[string]interface{}) 66 | //log.Println("aweme = ", aweme) 67 | 68 | detail := aweme["detail"].(map[string]interface{}) 69 | log.Println("detail = ", detail) 70 | 71 | awemeId := detail["awemeId"].(string) 72 | log.Println("awemeId = ", awemeId) 73 | 74 | awemeType := detail["awemeType"] 75 | log.Println("awemeType = ", awemeType) 76 | 77 | groupId := detail["groupId"] 78 | log.Println("groupId = ", groupId) 79 | 80 | authorInfo := detail["authorInfo"].(map[string]interface{}) 81 | log.Println("authorInfo = ", authorInfo) 82 | 83 | desc := detail["desc"] 84 | log.Println("desc = ", desc) 85 | 86 | authorUserId := detail["authorUserId"] 87 | log.Println("authorUserId = ", authorUserId) 88 | 89 | createTime := detail["createTime"] 90 | log.Println("createTime = ", createTime) 91 | 92 | video := detail["video"] 93 | log.Println("video = ", video) 94 | 95 | download := detail["download"].(map[string]interface{}) 96 | log.Println("download = ", download) 97 | 98 | url := download["url"].(string) 99 | ctx1, err := gt.Upload(url,"/home/mange/Desktop/"+awemeId+".mp4") 100 | log.Println(ctx1.Resp.StatusCode, err) 101 | 102 | } 103 | 104 | /* 105 | */ -------------------------------------------------------------------------------- /extract.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : HTML内容提取; json内容提取 TODO 测试 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "reflect" 14 | "strings" 15 | 16 | "golang.org/x/net/html" 17 | ) 18 | 19 | // GetPointHTML 获取指定位置的HTML, 用标签, 标签属性, 属性值来定位 20 | func GetPointHTML(htmlStr, label, attr, val string) ([]string, error) { 21 | rse := make([]string, 0) 22 | doc, err := html.Parse(strings.NewReader(htmlStr)) 23 | if err != nil { 24 | return rse, err 25 | } 26 | var f func(*html.Node) 27 | f = func(n *html.Node) { 28 | if n.Type == html.ElementNode { 29 | //log.Println("attr = ", n.Attr, n.Namespace, n.Data) 30 | if n.Data == label { 31 | if attr == "" && val == "" { 32 | rse = add(rse, n) 33 | } else { 34 | for _, a := range n.Attr { 35 | if a.Key == attr && a.Val == val { 36 | rse = add(rse, n) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | for c := n.FirstChild; c != nil; c = c.NextSibling { 43 | f(c) 44 | } 45 | } 46 | f(doc) 47 | return rse, nil 48 | } 49 | 50 | func add(rse []string, n *html.Node) []string { 51 | var buf bytes.Buffer 52 | err := html.Render(&buf, n) 53 | if err == nil { 54 | rse = append(rse, buf.String()) 55 | } 56 | return rse 57 | } 58 | 59 | // GetPointIDHTML 获取指定标签id属性的html 60 | func GetPointIDHTML(htmlStr, label, val string) ([]string, error) { 61 | return GetPointHTML(htmlStr, label, "id", val) 62 | } 63 | 64 | // GetPointClassHTML 获取指定标签class属性的html 65 | func GetPointClassHTML(htmlStr, label, val string) ([]string, error) { 66 | return GetPointHTML(htmlStr, label, "class", val) 67 | } 68 | 69 | // ==================================== json find 70 | 71 | // JsonFind 按路径寻找指定json值 72 | // 用法参考 ./_examples/json/main.go 73 | // @find : 寻找路径,与目录的url类似, 下面是一个例子: 74 | // json: {a:[{b:1},{b:2}]} 75 | // find=/a/[0] => {b:1} 76 | // find=a/[0]/b => 1 77 | func JsonFind(jsonStr, find string) (any, error) { 78 | if !IsJson(jsonStr) { 79 | return nil, fmt.Errorf("不是标准的Json格式") 80 | } 81 | jxList := strings.Split(find, "/") 82 | jxLen := len(jxList) 83 | var ( 84 | data = Any2Map(jsonStr) 85 | value any 86 | err error 87 | ) 88 | for i := 0; i < jxLen; i++ { 89 | l := len(jxList[i]) 90 | if l > 2 && string(jxList[i][0]) == "[" && string(jxList[i][l-1]) == "]" { 91 | numStr := jxList[i][1 : l-1] 92 | dataList := Any2Arr(value) 93 | value = dataList[Any2Int(numStr)] 94 | data, err = interface2Map(value) 95 | if err != nil { 96 | continue 97 | } 98 | } else { 99 | if IsHaveKey(data, jxList[i]) { 100 | value = data[jxList[i]] 101 | data, err = interface2Map(value) 102 | if err != nil { 103 | continue 104 | } 105 | } else { 106 | value = nil 107 | } 108 | } 109 | } 110 | return value, nil 111 | } 112 | 113 | // JsonFind2Json 寻找json,输出 json格式字符串 114 | func JsonFind2Json(jsonStr, find string) (string, error) { 115 | value, err := JsonFind(jsonStr, find) 116 | if err != nil { 117 | return "", err 118 | } 119 | return Any2Json(value) 120 | } 121 | 122 | // JsonFind2Map 寻找json,输出 map[string]any 123 | func JsonFind2Map(jsonStr, find string) (map[string]any, error) { 124 | value, err := JsonFind(jsonStr, find) 125 | if err != nil { 126 | return nil, err 127 | } 128 | return Any2Map(value), nil 129 | } 130 | 131 | // JsonFind2Arr 寻找json,输出 []any 132 | func JsonFind2Arr(jsonStr, find string) ([]any, error) { 133 | value, err := JsonFind(jsonStr, find) 134 | if err != nil { 135 | return nil, err 136 | } 137 | return Any2Arr(value), nil 138 | } 139 | 140 | // JsonFind2Str 寻找json,输出字符串 141 | func JsonFind2Str(jsonStr, find string) (string, error) { 142 | value, err := JsonFind(jsonStr, find) 143 | if err != nil { 144 | return "", err 145 | } 146 | return Any2String(value), nil 147 | } 148 | 149 | // JsonFind2Int 寻找json,输出int 150 | func JsonFind2Int(jsonStr, find string) (int, error) { 151 | value, err := JsonFind(jsonStr, find) 152 | if err != nil { 153 | return 0, err 154 | } 155 | return Any2Int(value), nil 156 | } 157 | 158 | // JsonFind2Int64 寻找json,输出int64 159 | func JsonFind2Int64(jsonStr, find string) (int64, error) { 160 | value, err := JsonFind(jsonStr, find) 161 | if err != nil { 162 | return 0, err 163 | } 164 | return Any2Int64(value), nil 165 | } 166 | 167 | // IsJson 是否是json格式 168 | func IsJson(str string) bool { 169 | var tempMap map[string]any 170 | err := json.Unmarshal([]byte(str), &tempMap) 171 | if err != nil { 172 | return false 173 | } 174 | return true 175 | } 176 | 177 | // IsHaveKey map[string]any 是否存在 输入的key 178 | func IsHaveKey[T comparable](data map[T]any, key T) bool { 179 | _, ok := data[key] 180 | return ok 181 | } 182 | 183 | // Any2Map any -> map[string]any 184 | func interface2Map(data any) (map[string]any, error) { 185 | if v, ok := data.(map[string]any); ok { 186 | return v, nil 187 | } 188 | if reflect.ValueOf(data).Kind() == reflect.String { 189 | return Json2Map(data.(string)) 190 | } 191 | return nil, fmt.Errorf("not map type") 192 | } 193 | -------------------------------------------------------------------------------- /stress_testing.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 接口压力测试, 并输出结果 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "log" 11 | "net" 12 | "net/http" 13 | "sync" 14 | "sync/atomic" 15 | "time" 16 | ) 17 | 18 | type stateCodeData struct { 19 | Code int 20 | ReqTime int64 21 | } 22 | 23 | // StressUrl 压力测试一个url 24 | type StressUrl struct { 25 | Url string 26 | Method string 27 | Sum int64 28 | Total int 29 | TQueue TodoQueue 30 | 31 | // 请求时间累加 32 | sumReqTime int64 33 | 34 | // 测试结果 35 | avgReqTime time.Duration 36 | 37 | // 接口传入的json 38 | JsonData string 39 | 40 | // 接口传入类型 41 | ContentType string 42 | 43 | // 是否重试 44 | isRetry bool 45 | 46 | stateCodeList []*stateCodeData 47 | stateCodeListMux *sync.Mutex 48 | } 49 | 50 | // NewTestUrl 实例化一个新的url压测 51 | func NewTestUrl(url, method string, sum int64, total int) *StressUrl { 52 | return &StressUrl{ 53 | Url: url, 54 | Method: method, 55 | Sum: sum, 56 | Total: total, 57 | TQueue: NewQueue(), 58 | sumReqTime: int64(0), 59 | isRetry: false, 60 | stateCodeList: make([]*stateCodeData, 0), 61 | stateCodeListMux: &sync.Mutex{}, 62 | } 63 | } 64 | 65 | // SetJson 设置json 66 | func (s *StressUrl) SetJson(str string) { 67 | s.JsonData = str 68 | } 69 | 70 | // OpenRetry 开启重试请求 71 | func (s *StressUrl) OpenRetry() { 72 | s.isRetry = true 73 | } 74 | 75 | // Run 运行压测 76 | func (s *StressUrl) Run(vs ...any) { 77 | //解析可变参 78 | var ( 79 | succeedFunc SucceedFunc 80 | n int64 81 | wg sync.WaitGroup 82 | reqTimeout ReqTimeOut 83 | reqTimeoutMs ReqTimeOutMs 84 | header http.Header 85 | ) 86 | for _, v := range vs { 87 | switch vv := v.(type) { 88 | // 使用方传入了 header 89 | case SucceedFunc: 90 | succeedFunc = vv 91 | case ReqTimeOut: 92 | reqTimeout = vv 93 | case ReqTimeOutMs: 94 | reqTimeoutMs = vv 95 | case http.Header: 96 | header = vv 97 | case *http.Header: 98 | header = *vv 99 | } 100 | } 101 | 102 | //初始化队列 103 | for n = 0; n < s.Sum; n++ { 104 | _ = s.TQueue.Add(&Task{Url: s.Url}) 105 | } 106 | log.Println("总执行次数: ", s.TQueue.Size()) 107 | var count int64 = 0 108 | for job := 0; job < s.Total; job++ { 109 | wg.Add(1) 110 | go func(i int) { 111 | defer wg.Done() 112 | for { 113 | var ( 114 | ctx = &Context{} 115 | ) 116 | if s.TQueue.IsEmpty() { 117 | break 118 | } 119 | task := s.TQueue.Poll() 120 | if task == nil { 121 | continue 122 | } 123 | // 定义适用于压力测试的client 124 | t := http.DefaultTransport.(*http.Transport).Clone() 125 | t.MaxIdleConns = s.Total * 2 126 | t.MaxIdleConnsPerHost = s.Total * 2 127 | t.DisableKeepAlives = true 128 | t.DialContext = (&net.Dialer{ 129 | Timeout: 3 * time.Second, 130 | KeepAlive: 3 * time.Second, 131 | }).DialContext 132 | t.IdleConnTimeout = 3 * time.Second 133 | t.ExpectContinueTimeout = 1 * time.Second 134 | client := http.Client{ 135 | Transport: t, 136 | Timeout: 1 * time.Second, 137 | } 138 | switch s.Method { 139 | case "get", "Get", "GET": 140 | ctx = NewGet(task.Url, client, succeedFunc, reqTimeout, reqTimeoutMs, header) 141 | case "post", "Post", "POST": 142 | ctx = NewPost(task.Url, []byte(s.JsonData), s.ContentType, client, succeedFunc, reqTimeout, reqTimeoutMs, header) 143 | default: 144 | log.Println("暂时不支持的 Method.") 145 | } 146 | if ctx == nil { 147 | continue 148 | } 149 | ctx.JobNumber = i 150 | if !s.isRetry { 151 | ctx.CloseRetry() 152 | } 153 | ctx.Do() 154 | 155 | atomic.AddInt64(&s.sumReqTime, int64(ctx.Ms)) 156 | atomic.AddInt64(&count, int64(1)) 157 | s.stateCodeListMux.Lock() 158 | if ctx.Resp != nil { 159 | s.stateCodeList = append(s.stateCodeList, &stateCodeData{ 160 | Code: ctx.Resp.StatusCode, 161 | ReqTime: int64(ctx.Ms), 162 | }) 163 | } else { 164 | //请求错误 165 | s.stateCodeList = append(s.stateCodeList, &stateCodeData{ 166 | Code: -1, 167 | ReqTime: int64(ctx.Ms), 168 | }) 169 | } 170 | s.stateCodeListMux.Unlock() 171 | } 172 | 173 | }(job) 174 | } 175 | wg.Wait() 176 | Info("执行次数 : ", count) 177 | 178 | var ( 179 | maxTime int64 = 0 180 | minTime int64 = 9999999999 181 | ) 182 | 183 | fb := make(map[int]int) 184 | for _, v := range s.stateCodeList { 185 | if v.ReqTime >= maxTime { 186 | maxTime = v.ReqTime 187 | } 188 | if v.ReqTime <= minTime { 189 | minTime = v.ReqTime 190 | } 191 | 192 | if _, ok := fb[v.Code]; ok { 193 | fb[v.Code]++ 194 | } else { 195 | fb[v.Code] = 1 196 | } 197 | //s.sumReqTime = s.sumReqTime + v.ReqTime 198 | } 199 | 200 | Info("状态码分布: ", fb) 201 | avg := float64(s.sumReqTime) / float64(s.Sum) 202 | avg = avg / (1000 * 1000) 203 | Info("平均用时: ", avg, "ms") 204 | Info("最高用时: ", float64(maxTime)/(1000*1000), "ms") 205 | Info("最低用时: ", float64(minTime)/(1000*1000), "ms") 206 | Info("执行完成!!!") 207 | } 208 | -------------------------------------------------------------------------------- /_examples/websocket_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gt "github.com/mangenotwork/gathertool" 5 | "log" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | type Token struct { 11 | Code int64 `json:"code"` 12 | Msg string `json:"msg"` 13 | Time int64 `json:"time"` 14 | B Body `json:"body"` 15 | } 16 | 17 | type Body struct { 18 | P Pager `json:"pager"` 19 | D Data `json:"data"` 20 | } 21 | 22 | type Pager struct { 23 | Page int64 `json:"page"` 24 | Count int64 `json:"count"` 25 | PageCount int64 `json:"page_count"` 26 | } 27 | 28 | type Data struct { 29 | Userid int64 `json:"userid"` 30 | Appid int64 `json:"appid"` 31 | TokenString string `json:"token_string"` 32 | Password string `json:"password"` 33 | } 34 | 35 | func main() { 36 | GetAllUser() 37 | } 38 | 39 | func main1() { 40 | idStr := "104" 41 | //tokenUrl := "https://test.api.ymzy.cn/kimi/logic/allow/init/user" 42 | //par := `{ 43 | // "nick_name":"test-` + idStr + `", 44 | // "user_id":104, 45 | // "password":"1", 46 | // "app_id":1 47 | // }` 48 | //ctx, _ := gt.PostJson(tokenUrl, par) 49 | // 50 | //m := Token{} 51 | // 52 | //err := json.Unmarshal(ctx.RespBody, &m) 53 | //if err != nil { 54 | // log.Println("Umarshal failed:", err) 55 | // return 56 | //} 57 | //log.Println("Token = ", m.B.D.TokenString) 58 | 59 | /* 60 | /v1/ws? 61 | user_id=304930&app_id=1&name=%E6%88%91%E8%BF%98%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88%E5%93%88 62 | &avatar=https://p2.ymzy.cn/ave/20220215/7ebc2087f6aba85c.jpg 63 | &token=bmJmIjoxNjI2MzMxNzYzLCJuaWNrbmFtZSI6IjEyMzAwMDAwMDIzIiwicHJvdl9pZCI6NTAsInByb3ZfbW9kZWwiOjIsIn 64 | &device_id=304930-e9f44268-b747-4c2e-971c-1c15a855a15a 65 | &source=1 66 | 67 | */ 68 | 69 | token := "bmJmIjoxNjI2MzMxNzYzLCJuaWNrbmFtZSI6IjEyMzAwMDAwMDIzIiwicHJvdl9pZCI6NTAsInByb3ZfbW9kZWwiOjIsIn" 70 | 71 | // 连接 72 | host := "wss://test.api.ymzy.cn" 73 | path := "/kimi/v1/ws?user_id=" + idStr + "&device_id=" + idStr + "&token=" + token + "&app_id=1&name=104&avatar=&source=1" 74 | wsc, err := gt.WsClient(host, path, false) 75 | log.Println(wsc, err) 76 | rand.Seed(time.Now().UnixNano()) 77 | 78 | //// 发消息 79 | //gt.PostForm("http://192.168.0.9:8001/auth/send", map[string]string{ 80 | // "conversation_type":"0", 81 | // "sender_type":"2", 82 | // "app_id":"1", 83 | // "from":idStr, 84 | // "from_device_id":"test_"+idStr, 85 | // "from_avatar": userAvatar, 86 | // "from_nickname": userNickName, 87 | // "to": "-1", 88 | // "content_type":"1", 89 | // "content":"你好我找一下客服", 90 | // 91 | //}) 92 | 93 | for { 94 | // CHANGE 95 | // QUESTION 96 | // ARTIFICIAL 97 | // commands := []string{"CHANGE", "QUESTION", "ARTIFICIAL"} 98 | // 99 | // command := `{ 100 | // "type":12, 101 | // "request_id":"", 102 | // "command": "`+commands[rand.Intn(len(commands)-1)]+`", 103 | // "device_id":"123123213123213213", 104 | // "app_id":1, 105 | // "user_id":104, 106 | // "receiver_id":1, 107 | // "seq":12321321321323 108 | //}` 109 | // 110 | // err = wsc.Send([]byte(command)) 111 | // log.Println(err) 112 | data := make([]byte, 100) 113 | err = wsc.Read(data) 114 | log.Println(err) 115 | log.Println("data = ", string(data)) 116 | time.Sleep(5 * time.Second) 117 | //time.Sleep(10*time.Microsecond) 118 | } 119 | 120 | wsc.Close() 121 | } 122 | 123 | var ( 124 | host197 = "192.168.0.197" 125 | port = 3306 126 | user = "root" 127 | password = "root123" 128 | gkzyUser = "gkzy-user" 129 | GkzyUserDB, _ = gt.NewMysql(host197, port, user, password, gkzyUser) 130 | ) 131 | 132 | func GetAllUser() { 133 | data, _ := GkzyUserDB.Select("select * from user limit 300, 100") 134 | for _, v := range data { 135 | log.Println(v["user_id"]) 136 | go Work(v["user_id"], v["avatar"], v["nick_name"]) 137 | } 138 | select {} 139 | } 140 | 141 | func Work(idStr, userAvatar, userNickName string) { 142 | token := "bmJmIjoxNjI2MzMxNzYzLCJuaWNrbmFtZSI6IjEyMzAwMDAwMDIzIiwicHJvdl9pZCI6NTAsInByb3ZfbW9kZWwiOjIsIn" 143 | 144 | // 连接 145 | host := "wss://test.api.ymzy.cn" 146 | path := "/kimi/v1/ws?user_id=" + idStr + "&device_id=" + idStr + "&token=" + token + "&app_id=1&name=104&avatar=&source=1" 147 | wsc, err := gt.WsClient(host, path, false) 148 | log.Println(wsc, err) 149 | rand.Seed(time.Now().UnixNano()) 150 | 151 | for { 152 | // 发消息 153 | header := gt.NewHeader(map[string]string{ 154 | "kimi-token": token, 155 | }) 156 | ctx, _ := gt.PostJson("https://test.api.ymzy.cn/kimi/logic/auth/send", `{ 157 | "conversation_type":0, 158 | "sender_type":2, 159 | "app_id":1, 160 | "from":`+idStr+`, 161 | "from_device_id":"test_`+idStr+`", 162 | "from_avatar": "`+userAvatar+`", 163 | "from_nickname": "`+userNickName+`", 164 | "to": -1, 165 | "content_type":1, 166 | "content":"[自动化压力测试] 你好我找一下客服" 167 | }`, header) 168 | 169 | log.Println(ctx.Json) 170 | 171 | data := make([]byte, 100) 172 | if wsc != nil { 173 | //err = wsc.Read(data) 174 | //log.Println(err) 175 | log.Println("data = ", string(data)) 176 | } 177 | 178 | time.Sleep(30 * time.Second) 179 | //time.Sleep(10*time.Microsecond) 180 | } 181 | 182 | wsc.Close() 183 | } 184 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : logger TODO 测试 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "os" 13 | "runtime" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | // LogClose 是否关闭日志 20 | var LogClose = true 21 | var std = newStd() 22 | 23 | // CloseLog 关闭日志 24 | func CloseLog() { 25 | LogClose = false 26 | } 27 | 28 | type logger struct { 29 | outFile bool 30 | outFileWriter *os.File 31 | } 32 | 33 | func newStd() *logger { 34 | return &logger{} 35 | } 36 | 37 | // SetLogFile 设置日志输出到的指定文件 38 | func SetLogFile(name string) { 39 | std.outFile = true 40 | std.outFileWriter, _ = os.OpenFile(name+time.Now().Format("-20060102")+".log", 41 | os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 42 | } 43 | 44 | // Level 日志等级 45 | type Level int 46 | 47 | var LevelMap = map[Level]string{ 48 | 1: "[Info] ", 49 | 2: "[Debug] ", 50 | 3: "[Warn] ", 51 | 4: "[Error] ", 52 | 5: "[HTTP/s]", 53 | } 54 | 55 | // Log 日志 56 | func (l *logger) Log(level Level, args string, times int) { 57 | var buffer bytes.Buffer 58 | buffer.WriteString(time.Now().Format(TimeMilliTemplate)) 59 | buffer.WriteString(LevelMap[level]) 60 | _, file, line, _ := runtime.Caller(times) 61 | fileList := strings.Split(file, "/") 62 | // 最多显示两级路径 63 | if len(fileList) > 3 { 64 | fileList = fileList[len(fileList)-3:] 65 | } 66 | buffer.WriteString(strings.Join(fileList, "/")) 67 | buffer.WriteString(":") 68 | buffer.WriteString(strconv.Itoa(line)) 69 | buffer.WriteString(" \t| ") 70 | buffer.WriteString(args) 71 | buffer.WriteString("\n") 72 | out := buffer.Bytes() 73 | if LogClose { 74 | _, _ = buffer.WriteTo(os.Stdout) 75 | } 76 | 77 | // 输出到文件或远程日志服务 78 | if l.outFile { 79 | _, _ = l.outFileWriter.Write(out) 80 | } 81 | } 82 | 83 | // Info 日志-信息 84 | func Info(args ...any) { 85 | std.Log(1, fmt.Sprint(args...), 2) 86 | } 87 | 88 | // InfoF 日志-信息 89 | func InfoF(format string, args ...any) { 90 | std.Log(1, fmt.Sprintf(format, args...), 2) 91 | } 92 | 93 | // InfoTimes 日志-信息, 指定日志代码位置的定位调用层级 94 | func InfoTimes(times int, args ...any) { 95 | std.Log(1, fmt.Sprint(args...), times) 96 | } 97 | 98 | // InfoFTimes 日志-信息, 指定日志代码位置的定位调用层级 99 | func InfoFTimes(times int, format string, args ...any) { 100 | std.Log(1, fmt.Sprintf(format, args...), times) 101 | } 102 | 103 | // Debug 日志-调试 104 | func Debug(args ...any) { 105 | std.Log(2, fmt.Sprint(args...), 2) 106 | } 107 | 108 | // DebugF 日志-调试 109 | func DebugF(format string, args ...any) { 110 | std.Log(2, fmt.Sprintf(format, args...), 2) 111 | } 112 | 113 | // DebugTimes 日志-调试, 指定日志代码位置的定位调用层级 114 | func DebugTimes(times int, args ...any) { 115 | std.Log(1, fmt.Sprint(args...), times) 116 | } 117 | 118 | // DebugFTimes 日志-调试, 指定日志代码位置的定位调用层级 119 | func DebugFTimes(format string, times int, args ...any) { 120 | std.Log(1, fmt.Sprintf(format, args...), times) 121 | } 122 | 123 | // Warn 日志-警告 124 | func Warn(args ...any) { 125 | std.Log(3, fmt.Sprint(args...), 2) 126 | } 127 | 128 | // WarnF 日志-警告 129 | func WarnF(format string, args ...any) { 130 | std.Log(3, fmt.Sprintf(format, args...), 2) 131 | } 132 | 133 | // WarnTimes 日志-警告, 指定日志代码位置的定位调用层级 134 | func WarnTimes(times int, args ...any) { 135 | std.Log(1, fmt.Sprint(args...), times) 136 | } 137 | 138 | // WarnFTimes 日志-警告, 指定日志代码位置的定位调用层级 139 | func WarnFTimes(format string, times int, args ...any) { 140 | std.Log(1, fmt.Sprintf(format, args...), times) 141 | } 142 | 143 | // Error 日志-错误 144 | func Error(args ...any) { 145 | std.Log(4, fmt.Sprint(args...), 2) 146 | } 147 | 148 | // ErrorF 日志-错误 149 | func ErrorF(format string, args ...any) { 150 | std.Log(4, fmt.Sprintf(format, args...), 2) 151 | } 152 | 153 | // ErrorTimes 日志-错误, 指定日志代码位置的定位调用层级 154 | func ErrorTimes(times int, args ...any) { 155 | std.Log(4, fmt.Sprint(args...), times) 156 | } 157 | 158 | // ErrorFTimes 日志-错误, 指定日志代码位置的定位调用层级 159 | func ErrorFTimes(format string, times int, args ...any) { 160 | std.Log(4, fmt.Sprintf(format, args...), times) 161 | } 162 | 163 | func Panic(args ...any) { 164 | panic(args) 165 | } 166 | 167 | // HTTPTimes 日志-信息, 指定日志代码位置的定位调用层级 168 | func HTTPTimes(times int, args ...any) { 169 | std.Log(5, fmt.Sprint(args...), times) 170 | } 171 | 172 | // Bar 终端显示的进度条 173 | type Bar struct { 174 | percent int64 //百分比 175 | cur int64 //当前进度位置 176 | total int64 //总进度 177 | rate string //进度条 178 | graph string //显示符号 179 | } 180 | 181 | func (bar *Bar) NewOption(start, total int64) { 182 | bar.cur = start 183 | bar.total = total 184 | if bar.graph == "" { 185 | bar.graph = "█" 186 | } 187 | bar.percent = bar.getPercent() 188 | for i := 0; i < int(bar.percent); i += 2 { 189 | bar.rate += bar.graph //初始化进度条位置 190 | } 191 | } 192 | 193 | func (bar *Bar) getPercent() int64 { 194 | return int64(float32(bar.cur) / float32(bar.total) * 100) 195 | } 196 | 197 | func (bar *Bar) NewOptionWithGraph(start, total int64, graph string) { 198 | bar.graph = graph 199 | bar.NewOption(start, total) 200 | } 201 | 202 | func (bar *Bar) Play(cur int64) { 203 | bar.cur = cur 204 | last := bar.percent 205 | bar.percent = bar.getPercent() 206 | if bar.percent != last && bar.percent%2 == 0 { 207 | bar.rate += bar.graph 208 | } 209 | fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total) 210 | } 211 | 212 | func (bar *Bar) Finish() { 213 | fmt.Println() 214 | } 215 | -------------------------------------------------------------------------------- /_examples/baojia/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | url : http://www.100ppi.com/mprice/mlist-1--1.html 3 | 抓取商品报价 4 | */ 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | gt "github.com/mangenotwork/gathertool" 10 | "log" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | // 数据库对象 16 | var ( 17 | host192 = "127.0.0.1" 18 | port = 3306 19 | user = "root" 20 | password = "123456" 21 | dbName = "sp_data" 22 | DB, _ = gt.NewMysql(host192, port, user, password, dbName) 23 | Host = "http://www.100ppi.com/mprice/" 24 | ) 25 | 26 | func main() { 27 | //GetMPrice() 28 | 29 | //RunGetMList() 30 | 31 | RunGetDetail() 32 | } 33 | 34 | // GetMPrice 抓取商品分类数据处理后存入数据库 35 | func GetMPrice() { 36 | OutTable := "table_fenlei" 37 | caseUrl := "https://www.100ppi.com/mprice/plist-1-1-1.html" 38 | ctx, _ := gt.Get(caseUrl) 39 | data, _ := gt.GetPointClassHTML(ctx.Html, "div", "p_list2") 40 | for _, v := range data { 41 | aDataList := gt.RegHtmlA(v) 42 | for _, aData := range aDataList { 43 | gt.Info(aData) 44 | href := "https://www.100ppi.com/mprice/" + gt.RegHtmlHrefTxt(aData)[0] 45 | fenlei := gt.RegHtmlATxt(aData)[0] 46 | fenlei = gt.RegDelHtml(fenlei) 47 | if strings.Index(href, "plist") == -1 { 48 | continue 49 | } 50 | gt.Info("href = ", href) 51 | gt.Info("fenlei = ", fenlei) 52 | gt.Info("数据提取完成,准备写入数据库") 53 | gd := gt.NewGDMap().Add("fenlei", fenlei).Add("href", href) 54 | err := DB.InsertAtGd(OutTable, gd) 55 | if err != nil { 56 | panic(err) 57 | } 58 | } 59 | } 60 | } 61 | 62 | // RunGetMList 从数据库中获取分类链接进行抓取商品列表 63 | func RunGetMList() { 64 | hrefList, err := DB.Select("select * from table_fenlei") 65 | if err != nil { 66 | log.Panic(err) 67 | } 68 | for _, hrefData := range hrefList { 69 | href := hrefData["href"] 70 | href = href[0:len(href)-6] + "%d.html" 71 | gt.Info(href) 72 | GetMList(href) 73 | } 74 | } 75 | 76 | // GetMList 抓取商品列表数据并处理后存储到数据库 77 | func GetMList(caseUrl string) { 78 | OutTable := "m_list" 79 | // 50页 80 | for i := 1; i < 11; i++ { 81 | time.Sleep(200 * time.Millisecond) 82 | // 请求 83 | ctx, _ := gt.Get(fmt.Sprintf(caseUrl, i)) 84 | // 数据提取 85 | table := gt.RegHtmlTable(ctx.Html) 86 | if len(table) > 0 { 87 | trList := gt.RegHtmlTr(table[0]) 88 | for _, tr := range trList { 89 | log.Println("tr = ", tr) 90 | tdList := gt.RegHtmlTd(tr) 91 | if len(tdList) == 0 { 92 | continue 93 | } 94 | spNameHtml := tdList[0] 95 | spName := gt.RegHtmlATxt(spNameHtml)[0] 96 | spUrl := Host + gt.RegHtmlHrefTxt(spNameHtml)[0] 97 | spNoteHtml := tdList[1] 98 | spNote := gt.CleaningStr(gt.RegHtmlDivTxt(spNoteHtml)[0]) 99 | pingpaiHtml := tdList[2] 100 | pingpai := gt.RegHtmlTdTxt(pingpaiHtml)[0] 101 | baojiaHtml := tdList[3] 102 | baojia := gt.CleaningStr(gt.RegHtmlTdTxt(baojiaHtml)[0]) 103 | baojiaTypeHtml := tdList[4] 104 | baojiaType := gt.CleaningStr(gt.RegHtmlTdTxt(baojiaTypeHtml)[0]) 105 | diziHtml := tdList[5] 106 | dizi := gt.CleaningStr(gt.RegHtmlTdTxt(diziHtml)[0]) 107 | shangjiaHtml := tdList[6] 108 | shangjia := "" 109 | shangjiaA := gt.RegHtmlATxt(shangjiaHtml) 110 | if len(shangjiaA) > 0 { 111 | shangjia = shangjiaA[0] 112 | } else { 113 | shangjia = gt.RegHtmlDivTxt(shangjiaHtml)[0] 114 | } 115 | shangjiaUrl := "" 116 | shangjiaUrlList := gt.RegHtmlHrefTxt(spNameHtml) 117 | if len(shangjiaUrlList) > 0 { 118 | shangjiaUrl = Host + shangjiaUrlList[0] 119 | } 120 | dateHtml := tdList[7] 121 | date := gt.RegHtmlTdTxt(dateHtml)[0] 122 | log.Println("商品名称 = ", spName) 123 | log.Println("商品详情地址 = ", spUrl) 124 | log.Println("规格 = ", spNote) 125 | log.Println("品牌/产地 = ", pingpai) 126 | log.Println("报价 = ", baojia) 127 | log.Println("报价类型 = ", baojiaType) 128 | log.Println("交货地 = ", dizi) 129 | log.Println("交易商 = ", shangjia) 130 | log.Println("交易商详情地址 = ", shangjiaUrl) 131 | log.Println("发布时间 = ", date) 132 | // 固定字段顺序Map, 写入mysql数据库 133 | gd := gt.NewGDMap().Add("sp_name", spName).Add("sp_url", spUrl).Add("sp_note", spNote) 134 | gd.Add("pingpai", pingpai).Add("baojia", baojia).Add("baojia_type", baojiaType) 135 | gd.Add("dizi", dizi).Add("shangjia", shangjia).Add("shangjia_url", shangjiaUrl) 136 | gd.Add("date", date).Add("req_md5", ctx.CheckMd5()) 137 | err := DB.InsertAtGd(OutTable, gd) 138 | if err != nil { 139 | panic(err) 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | // RunGetDetail 从数据库中获取商品详细链接进行抓取商品详情和商家信息 147 | func RunGetDetail() { 148 | caseList, err := DB.Select("select * from m_list") 149 | if err != nil { 150 | log.Panic(err) 151 | } 152 | for _, caseData := range caseList { 153 | GetDetail(caseData["sp_url"]) 154 | } 155 | } 156 | 157 | // GetDetail 抓取商品详细数据并处理后存储到数据库 158 | func GetDetail(caseUrl string) { 159 | //caseUrl := "http://www.100ppi.com/mprice/detail-4264843.html" 160 | time.Sleep(200 * time.Millisecond) 161 | ctx, _ := gt.Get(caseUrl) 162 | //gt.Info(ctx.Html) 163 | 164 | // 提取商品详情信息 165 | infoData, _ := gt.GetPointClassHTML(ctx.Html, "table", "st2-table tac") 166 | //gt.Info("infoData = ", infoData) 167 | 168 | // 提取商品描述 169 | noteData, _ := gt.GetPointClassHTML(ctx.Html, "table", "mb20 st2-table tac") 170 | //gt.Info("noteData = ", noteData[0]) 171 | 172 | // 提取报价走势图 173 | imgData, _ := gt.GetPointClassHTML(ctx.Html, "div", "vain") 174 | //gt.Info("imgData = ", imgData) 175 | 176 | if len(noteData) < 2 { 177 | return 178 | } 179 | 180 | // 商家信息 181 | shangJiaData := noteData[1] 182 | //gt.Info("shangJiaData = ", shangJiaData) 183 | 184 | // 存储商品详细信息 185 | gd1 := gt.NewGDMap().Add("info", infoData[0]).Add("note", noteData[0]).Add("img", imgData[0]) 186 | gd1.Add("shangjia", shangJiaData) 187 | err := DB.InsertAtGd("sp_detail", gd1) 188 | if err != nil { 189 | panic(err) 190 | } 191 | 192 | // 将商家信息单独提取出来存储数据 193 | trList := gt.RegHtmlTrTxt(shangJiaData) 194 | 195 | gs := GetSJData(trList[1]) 196 | gt.Info(gs) 197 | fzr := GetSJData(trList[2]) 198 | gt.Info(fzr) 199 | lx1 := GetSJData(trList[3]) 200 | gt.Info(lx1) 201 | lx2 := GetSJData(trList[4]) 202 | gt.Info(lx2) 203 | gd2 := gt.NewGDMap().Add("gs", gs).Add("fzr", fzr).Add("lx1", lx1).Add("lx2", lx2) 204 | err = DB.InsertAtGd("sj_info", gd2) 205 | if err != nil { 206 | panic(err) 207 | } 208 | 209 | } 210 | 211 | func GetSJData(data string) string { 212 | data = gt.RegDelHtml(data) 213 | data = strings.Replace(data, "\n", "", -1) 214 | data = strings.Replace(data, "\r", "", -1) 215 | data = strings.Replace(data, "\t", "", -1) 216 | data = strings.Replace(data, " ", "", -1) 217 | data = strings.Replace(data, "进入报价通", "", -1) 218 | data = strings.Replace(data, "联系报价人", "", -1) 219 | data = strings.Replace(data, "扫码分享", "", -1) 220 | return data 221 | } 222 | -------------------------------------------------------------------------------- /_examples/ip_bczs_cn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | gt "github.com/mangenotwork/gathertool" 11 | ) 12 | 13 | func main() { 14 | //main2() 15 | main1() 16 | } 17 | 18 | var ( 19 | // 全局声明抓取任务队列 20 | queue = gt.NewQueue() 21 | // 全局声明数据库客户端对象 22 | //host = "192.168.0.192" 23 | //port = 3306 24 | //user = "root" 25 | //password = "root123" 26 | //dbname = "test" 27 | //db,_ = gt.NewMysql(host, port, user, password, dbname) 28 | c, _ = gt.NewCSV("data.xls") 29 | ) 30 | 31 | func main1() { 32 | // 1.在页面 http://ip.bczs.net/country/CN 获取所有ip 33 | _, _ = gt.Get("http://ip.bczs.net/country/CN", gt.SucceedFunc(IPListSucceed)) 34 | 35 | // 2. 并发抓取详情数据, 20个并发 36 | gt.StartJobGet(10, queue, 37 | gt.SucceedFunc(GetIPSucceed), //请求成功后执行的方法 38 | gt.RetryFunc(GetIPRetry), //遇到 502,403 等状态码重试前执行的方法,一般为添加休眠时间或更换代理 39 | gt.FailedFunc(GetIPFailed), //请求失败后执行的方法 40 | ) 41 | //c.Close() 42 | 43 | select {} 44 | 45 | } 46 | 47 | // IPListSucceed 请求成功执行 48 | func IPListSucceed(ctx *gt.Context) { 49 | i := 1 50 | for _, tbody := range gt.RegHtmlTbody(ctx.RespBodyString()) { 51 | for _, tr := range gt.RegHtmlTr(tbody) { 52 | if i > 500 { 53 | goto End 54 | } 55 | td := gt.RegHtmlTdTxt(tr) 56 | log.Println(td) 57 | if len(td) < 3 { 58 | gt.Error("异常数据 : ", td) 59 | continue 60 | } 61 | startIp := gt.Any2String(gt.RegHtmlATxt(td[0])[0]) // IP起始 62 | endIP := td[1] // 结束 63 | number := td[2] // 数量 64 | // 创建队列 抓取详情信息 65 | queue.Add(>.Task{ 66 | Url: "http://ip.bczs.net/" + startIp, 67 | Data: map[string]interface{}{ 68 | "start_ip": startIp, 69 | "end_ip": endIP, 70 | "number": number, 71 | }, 72 | }) 73 | i++ 74 | } 75 | } 76 | End: 77 | } 78 | 79 | // GetIPSucceed 获取详情信息成功的处理 80 | func GetIPSucceed(cxt *gt.Context) { 81 | // 使用goquery库提取数据 82 | dom, err := goquery.NewDocumentFromReader(strings.NewReader(cxt.RespBodyString())) 83 | if err != nil { 84 | log.Println(err) 85 | return 86 | } 87 | result, err := dom.Find("div[id=result] .well").Html() 88 | if err != nil { 89 | log.Println(err) 90 | } 91 | // 固定顺序map 92 | gd := gt.NewGDMap().Add("start_ip", cxt.Task.GetDataStr("start_ip")) 93 | gd.Add("end_ip", cxt.Task.GetDataStr("end_ip")) 94 | gd.Add("number", cxt.Task.GetDataStr("number")).Add("result", result) 95 | log.Println("采集到的数据: ", []string{cxt.Task.GetDataStr("start_ip"), cxt.Task.GetDataStr("end_ip"), 96 | cxt.Task.GetDataStr("number"), result}) 97 | // 保存抓取数据 98 | //err = db.InsertAtGd("ip_result", gd) 99 | 100 | if err != nil { 101 | panic(err) 102 | } 103 | err = c.Add([]string{cxt.Task.GetDataStr("start_ip"), cxt.Task.GetDataStr("end_ip"), 104 | cxt.Task.GetDataStr("number"), result}) 105 | log.Println("写入csv失败: ", err) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | } 111 | 112 | // GetIPRetry 获取详情信息重试的处理 113 | func GetIPRetry(c *gt.Context) { 114 | //更换代理 115 | //c.SetProxy(uri) 116 | 117 | // or 118 | c.Client = &http.Client{ 119 | // 设置代理 120 | //Transport: &http.Transport{ 121 | // Proxy: http.ProxyURL(uri), 122 | //}, 123 | Timeout: 5 * time.Second, 124 | } 125 | 126 | log.Println("休息1s") 127 | time.Sleep(1 * time.Second) 128 | } 129 | 130 | // GetIPFailed 获取详情信息失败执行返还给队列 131 | func GetIPFailed(c *gt.Context) { 132 | queue.Add(c.Task) //请求失败归还到队列 133 | } 134 | 135 | // 第二种实现 136 | func main2() { 137 | // 连接数据库 138 | var ( 139 | host = "192.168.0.192" 140 | port = 3306 141 | user = "root" 142 | password = "root123" 143 | dbname = "test" 144 | ) 145 | db, err := gt.NewMysql(host, port, user, password, dbname) 146 | if err != nil { 147 | panic(err) 148 | } 149 | // 请求数据 150 | ctx, err := gt.Get("http://ip.bczs.net/country/CN") 151 | if err != nil { 152 | log.Println(err) 153 | return 154 | } 155 | // 提取数据并保存 156 | for _, tbody := range gt.RegHtmlTbody(ctx.Html) { 157 | for _, tr := range gt.RegHtmlTr(tbody) { 158 | td := gt.RegHtmlTdTxt(tr) 159 | if len(td) < 3 { 160 | gt.Error("异常数据 : ", td) 161 | continue 162 | } 163 | // 保存抓取数据 164 | err := db.InsertAt("ip", map[string]interface{}{ 165 | "start": td[0], 166 | "end": td[1], 167 | "num": td[2], 168 | }) 169 | if err != nil { 170 | panic(err) 171 | } 172 | } 173 | } 174 | } 175 | 176 | // --------------------------------------------------- ip属地实例 177 | // 思路, 将所有ip转10进制 ip >= 属地IP段启始ip && ip <= 属地IP段结束ip 或得地址 178 | 179 | // 数据库对象 180 | var ( 181 | host192 = "192.168.3.2" 182 | port = 3306 183 | user = "root" 184 | password = "admin" 185 | dbName = "niu_pp" 186 | DB, _ = gt.NewMysql(host192, port, user, password, dbName) 187 | Host = "http://www.100ppi.com/mprice/" 188 | ) 189 | 190 | func Run() { 191 | // 1.在页面 http://ip.bczs.net/country/CN 获取所有ip 192 | ctx, _ := gt.Get("http://ip.bczs.net/country/MO") 193 | for _, tbody := range gt.RegHtmlTbody(ctx.RespBodyString()) { 194 | for _, tr := range gt.RegHtmlTr(tbody) { 195 | td := gt.RegHtmlTdTxt(tr) 196 | log.Println(td) 197 | if len(td) < 3 { 198 | gt.Error("异常数据 : ", td) 199 | continue 200 | } 201 | startIp := gt.Any2String(gt.RegHtmlATxt(td[0])[0]) // IP起始 202 | GetIPInfo(startIp) 203 | time.Sleep(600 * time.Millisecond) 204 | } 205 | } 206 | } 207 | 208 | func NewTable() { 209 | fields := map[string]string{ 210 | "rse": "varchar(200)", 211 | "start": "varchar(50)", 212 | "start_int": "bigint", 213 | "end": "varchar(50)", 214 | "end_int": "bigint", 215 | "sd1": "varchar(50)", 216 | "sd2": "varchar(50)", 217 | "created": "int(11)", 218 | } 219 | err := DB.NewTable("ip_set", fields) 220 | log.Println(err) 221 | } 222 | 223 | func GetIPInfo(startIp string) { 224 | caseUrl := "http://ip.bczs.net/" + startIp 225 | log.Println("caseUrl = ", caseUrl) 226 | ctx, _ := gt.Get(caseUrl, gt.RetryFunc(GetIPRetry)) 227 | htmlData := ctx.Html 228 | //log.Println(htmlData) 229 | well, err := gt.GetPointClassHTML(htmlData, "div", "well") 230 | log.Println(well, err) 231 | 232 | data1 := gt.RegFindAll(`(?is:IP数据:.*?
)`, well[0]) 233 | log.Println(data1) 234 | rse := data1[0][0] 235 | rse = strings.Replace(rse, "IP数据:", "", -1) 236 | rse = strings.Replace(rse, "
", "", -1) 237 | log.Println("rse = ", rse) 238 | rseList := strings.Split(rse, " ") 239 | log.Println("rseList = ", rseList) 240 | ipArang := rseList[0] 241 | ipList := strings.Split(ipArang, "-") 242 | ipStart := ipList[0] 243 | ipStartInt := gt.IP2Int64(ipStart) 244 | ipEnd := ipList[1] 245 | ipEndInt := gt.IP2Int64(ipEnd) 246 | log.Println("ipStart = ", ipStart, ipStartInt) 247 | log.Println("ipEnd = ", ipEnd, ipEndInt) 248 | ipSD := rseList[1] 249 | ipSDList := strings.Split(ipSD, "\t") 250 | ipSD1, ipSD2 := "中国", "中国" 251 | if len(ipSDList) > 2 { 252 | ipSD1 = ipSDList[0] 253 | ipSD2 = ipSDList[1] 254 | } 255 | 256 | data := map[string]interface{}{ 257 | "rse": rse, 258 | "start": ipStart, 259 | "start_int": ipStartInt, 260 | "end": ipEnd, 261 | "end_int": ipEndInt, 262 | "sd1": ipSD1, 263 | "sd2": ipSD2, 264 | "created": time.Now().Unix(), 265 | } 266 | err = DB.InsertAt("ip_set", data) 267 | log.Println(err) 268 | } 269 | -------------------------------------------------------------------------------- /time_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 时间相关的操作 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "fmt" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | const TimeTemplate = "2006-01-02 15:04:05" 16 | const TimeMilliTemplate = "2006-01-02 15:04:05.000" 17 | const TimeTemplateYMD = "2006-01-02" 18 | const OneDay = 24 19 | const OneHours = 60 20 | const OneMinutes = 60 21 | const ZeroHMS = " 00:00:00" 22 | const TimeFormatList = "01-02 15:04" 23 | 24 | // Timestamp 获取时间戳 25 | func Timestamp() int64 { 26 | return time.Now().Unix() 27 | } 28 | 29 | func TimestampStr() string { 30 | return strconv.FormatInt(time.Now().Unix(), 10) 31 | } 32 | 33 | // BeginDayUnix 获取当天 0点 34 | func BeginDayUnix() int64 { 35 | timeStr := time.Now().Format(TimeTemplateYMD) 36 | t, _ := time.ParseInLocation(TimeTemplateYMD, timeStr, time.Local) 37 | return t.Unix() 38 | } 39 | 40 | // EndDayUnix 获取当天 24点 41 | func EndDayUnix() int64 { 42 | timeStr := time.Now().Format(TimeTemplateYMD) 43 | t, _ := time.ParseInLocation(TimeTemplateYMD, timeStr, time.Local) 44 | return t.Unix() + OneDay*OneHours*OneMinutes 45 | } 46 | 47 | // MinuteAgo 获取多少分钟前的时间戳 48 | func MinuteAgo(i int) int64 { 49 | return time.Now().Unix() - int64(i*OneMinutes) 50 | } 51 | 52 | // HourAgo 获取多少小时前的时间戳 53 | func HourAgo(i int) int64 { 54 | return time.Now().Unix() - int64(i*OneHours*OneMinutes) 55 | } 56 | 57 | // DayAgo 获取多少天前的时间戳 58 | func DayAgo(i int) int64 { 59 | return time.Now().Unix() - int64(i*OneHours*OneMinutes*OneDay) 60 | } 61 | 62 | // DayDiff 两个时间字符串的日期差, 返回的是天 63 | func DayDiff(beginDay string, endDay string) int { 64 | begin, _ := time.Parse(TimeTemplate, beginDay+ZeroHMS) 65 | end, _ := time.Parse(TimeTemplate, endDay+ZeroHMS) 66 | 67 | diff := end.Unix() - begin.Unix() 68 | return int(diff / (OneDay * OneHours * OneMinutes)) 69 | } 70 | 71 | func DayDiffAtUnix(s, e int64) int { 72 | diff := e - s 73 | return int(diff / (OneDay * OneHours * OneMinutes)) 74 | } 75 | 76 | // TickerRun 间隔运行 77 | // t: 间隔时间, runFirst: 间隔前或者后执行 f: 运行的方法 78 | func TickerRun(t time.Duration, runFirst bool, f func()) { 79 | if runFirst { 80 | f() 81 | } 82 | tick := time.NewTicker(t) 83 | for range tick.C { 84 | f() 85 | } 86 | } 87 | 88 | // Timestamp2Date timestamp -> "2006-01-02 15:04:05" 89 | func Timestamp2Date(timestamp int64) string { 90 | tm := time.Unix(timestamp, 0) 91 | return tm.Format(TimeTemplate) 92 | } 93 | 94 | // NowToEnd 计算当前时间到这天结束还有多久 95 | func NowToEnd() (int64, error) { 96 | now := time.Now() 97 | nextDay := now.Add(24 * time.Hour) 98 | loc, err := time.LoadLocation("Local") 99 | if err != nil { 100 | return -1, err 101 | } 102 | endTime := fmt.Sprintf("%d-%d-%d 00:00:00", nextDay.Year(), nextDay.Month(), nextDay.Day()) 103 | times, err := time.ParseInLocation(TimeTemplate, endTime, loc) 104 | if err != nil { 105 | return -1, err 106 | } 107 | distanceSec := times.Unix() - now.Unix() 108 | return distanceSec, nil 109 | } 110 | 111 | // IsToday 判断是否是今天 "2006-01-02 15:04:05" 112 | // timestamp 需要判断的时间 113 | func IsToday(timestamp int64) string { 114 | nowTime := time.Now() 115 | t := time.Unix(timestamp, 0) 116 | if t.Day() == nowTime.Day() && t.Month() == nowTime.Month() && t.Year() == nowTime.Year() { 117 | return "今天 " + t.Format("15:04:05") 118 | } 119 | return t.Format(TimeTemplate) 120 | } 121 | 122 | // IsTodayList 列表页的时间显示 "01-02 15:04" 123 | func IsTodayList(timestamp int64) string { 124 | nowTime := time.Now() 125 | t := time.Unix(timestamp, 0) 126 | if t.Day() == nowTime.Day() && t.Month() == nowTime.Month() && t.Year() == nowTime.Year() { 127 | return "今天 " + t.Format("15:04") 128 | } 129 | return t.Format(TimeFormatList) 130 | } 131 | 132 | // Timestamp2Week 获取timestamp是周几 133 | func Timestamp2Week(timestamp int64) string { 134 | tm := time.Unix(timestamp, 0) 135 | tm.Weekday() 136 | switch tm.Weekday() { 137 | case time.Sunday: 138 | return "周天" 139 | case time.Monday: 140 | return "周一" 141 | case time.Tuesday: 142 | return "周二" 143 | case time.Wednesday: 144 | return "周三" 145 | case time.Thursday: 146 | return "周四" 147 | case time.Friday: 148 | return "周五" 149 | case time.Saturday: 150 | return "周六" 151 | } 152 | return "周一" 153 | } 154 | 155 | // Timestamp2WeekXinQi 获取timestamp是星期几 156 | func Timestamp2WeekXinQi(timestamp int64) string { 157 | tm := time.Unix(timestamp, 0) 158 | tm.Weekday() 159 | switch tm.Weekday() { 160 | case time.Sunday: 161 | return "星期天" 162 | case time.Monday: 163 | return "星期一" 164 | case time.Tuesday: 165 | return "星期二" 166 | case time.Wednesday: 167 | return "星期三" 168 | case time.Thursday: 169 | return "星期四" 170 | case time.Friday: 171 | return "星期五" 172 | case time.Saturday: 173 | return "星期六" 174 | } 175 | return "星期一" 176 | } 177 | 178 | // LatestDate 返回最近好多天的日期 179 | func LatestDate(date int) []string { 180 | outList := make([]string, 0) 181 | now := time.Now() 182 | outList = append(outList, now.Format("2006-01-02")) 183 | nowInt := now.Unix() 184 | for i := 0; i < date; i++ { 185 | nowInt -= 86400 186 | outList = append(outList, time.Unix(nowInt, 0).Format("2006-01-02")) 187 | } 188 | return outList 189 | } 190 | 191 | // GetCurrentMonthRange 获取当前月的时间范围(月初1号0点 ~ 月末最后一刻) 192 | // 返回: 193 | // 194 | // firstDay:当月1号 00:00:00 195 | // lastDay:当月最后一天 23:59:59.999999999 196 | func GetCurrentMonthRange() (firstDay, lastDay time.Time) { 197 | now := time.Now() // 当前时间(当地时区) 198 | 199 | // 年、月不变,日设为1,时/分/秒/纳秒设为0 200 | firstDay = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) 201 | 202 | // 先获取下个月1号0点,再减1天得到当月最后一天0点,最后减1纳秒得到23:59:59.999999999 203 | nextMonthFirstDay := firstDay.AddDate(0, 1, 0) // 下个月1号0点 204 | lastDayStart := nextMonthFirstDay.AddDate(0, 0, -1) // 当月最后一天0点 205 | lastDay = lastDayStart.Add(24*time.Hour - time.Nanosecond) // 最后一天23:59:59.999999999 206 | 207 | return firstDay, lastDay 208 | } 209 | 210 | // GetCurrentWeekRange 获取当前周的时间范围(周一凌晨 ~ 周日23:59:59) 211 | // 返回: 212 | // 213 | // monday :本周一 00:00:00 214 | // sundayEnd:本周日 23:59:59.999999999 215 | func GetCurrentWeekRange() (monday, sundayEnd time.Time) { 216 | now := time.Now() // 当前时间(当地时区) 217 | 218 | // 计算当前是星期几(转换为:周一=1,周二=2,...,周日=7) 219 | currentWeekday := int(now.Weekday()) 220 | if currentWeekday == 0 { // 若为周日(0),转为7 221 | currentWeekday = 7 222 | } 223 | 224 | // 计算距离本周一的天数差 225 | daysToMonday := currentWeekday - 1 226 | 227 | // 计算本周一的凌晨(00:00:00) 228 | monday = now.AddDate(0, 0, -daysToMonday).Truncate(24 * time.Hour) 229 | 230 | // 计算本周日的23:59:59.999999999(当天最后一刻) 231 | sundayDate := monday.AddDate(0, 0, 6) // 周一加6天 = 周日(日期) 232 | sundayEnd = time.Date( 233 | sundayDate.Year(), 234 | sundayDate.Month(), 235 | sundayDate.Day(), 236 | 23, 59, 59, 999999999, // 时:分:秒:纳秒(当天最后一刻) 237 | now.Location(), // 保持与当前时间相同的时区 238 | ) 239 | 240 | return monday, sundayEnd 241 | } 242 | 243 | // GetTodayRange 按东八区(Asia/Shanghai)获取当天自然日范围(00:00:00 至 23:59:59.999999999) 244 | // 返回: 245 | // 246 | // start:当天00:00:00(东八区) 247 | // end:当天23:59:59.999999999(东八区) 248 | func GetTodayRange() (start, end time.Time) { 249 | // 指定时区为东八区(Asia/Shanghai) 250 | loc, _ := time.LoadLocation("Asia/Shanghai") 251 | now := time.Now().In(loc) 252 | 253 | // 计算东八当天凌晨(00:00:00) 254 | start = time.Date( 255 | now.Year(), 256 | now.Month(), 257 | now.Day(), 258 | 0, 0, 0, 0, 259 | loc, // 明确使用东八 260 | ) 261 | 262 | // 计算东八当天结束(23:59:59.999999999) 263 | end = time.Date( 264 | now.Year(), 265 | now.Month(), 266 | now.Day(), 267 | 23, 59, 59, 999999999, 268 | loc, // 明确使用东八 269 | ) 270 | 271 | return start, end 272 | } 273 | -------------------------------------------------------------------------------- /scan.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 一些扫描方法,一些website host方法 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "fmt" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | var ApplicationTerminalOut = true 16 | 17 | // ApplicationTerminalOutClose 关闭在终端打印的扫描信息 18 | func ApplicationTerminalOutClose() { 19 | ApplicationTerminalOut = false 20 | } 21 | 22 | type HostToolEr interface { 23 | Run() ([]string, int) 24 | } 25 | 26 | // HostScanUrl Host站点下 A标签 Url扫描, 从更目录开始扫描指定深度 get Url 应用函数 27 | type HostScanUrl struct { 28 | Host string 29 | Depth int // 页面深度 30 | UrlSet map[string]struct{} 31 | Count int 32 | MaxCount int64 33 | } 34 | 35 | // NewHostScanUrl 创建扫描站点 36 | func NewHostScanUrl(host string, depth int) *HostScanUrl { 37 | return &HostScanUrl{ 38 | Host: host, 39 | Depth: depth, 40 | UrlSet: make(map[string]struct{}), 41 | Count: 0, 42 | MaxCount: 0, 43 | } 44 | } 45 | 46 | // Run 执行扫描 47 | func (scan *HostScanUrl) Run() ([]string, int) { 48 | CloseLog() 49 | scan.do(scan.Host, 0) 50 | urls := make([]string, 0) 51 | for k := range scan.UrlSet { 52 | urls = append(urls, urlStr(k)) 53 | } 54 | if ApplicationTerminalOut { 55 | fmt.Printf("\n") 56 | } 57 | return urls, scan.Count 58 | } 59 | 60 | func (scan *HostScanUrl) do(caseUrl string, df int) { 61 | if len(caseUrl) < 1 { 62 | return 63 | } 64 | if df > scan.Depth { 65 | return 66 | } 67 | // 如果不是host下的域名 68 | if strings.Index(caseUrl, scan.Host) == -1 { 69 | if string(caseUrl[0]) == "/" { 70 | caseUrl = scan.Host + caseUrl 71 | goto G 72 | } 73 | return 74 | } 75 | G: 76 | if _, ok := scan.UrlSet[caseUrl]; ok { 77 | return 78 | } 79 | ctx, err := Get(caseUrl) 80 | if err != nil { 81 | Error(err) 82 | return 83 | } 84 | if ApplicationTerminalOut { 85 | fmt.Print(".") 86 | } 87 | df++ 88 | scan.UrlSet[caseUrl] = struct{}{} 89 | scan.Count++ 90 | a := RegHtmlA(ctx.Html) 91 | for _, v := range a { 92 | links := RegHtmlHrefTxt(v) 93 | if len(links) < 1 { 94 | continue 95 | } 96 | link := links[0] 97 | // 请求并验证 98 | scan.do(link, df) 99 | } 100 | } 101 | 102 | // HostScanExtLinks Host站点下的外链采集 应用函数 103 | type HostScanExtLinks struct { 104 | Host string 105 | } 106 | 107 | // NewHostScanExtLinks 创建站点链接采集,只支持get请求 108 | func NewHostScanExtLinks(host string) *HostScanExtLinks { 109 | return &HostScanExtLinks{ 110 | Host: host, 111 | } 112 | } 113 | 114 | func (scan *HostScanExtLinks) Run() ([]string, int) { 115 | var ( 116 | urlSet = make(map[string]struct{}) 117 | count int 118 | ) 119 | CloseLog() 120 | ctx, err := Get(scan.Host) 121 | if err != nil { 122 | Error(err) 123 | return []string{}, 0 124 | } 125 | a := RegHtmlA(ctx.Html) 126 | for _, v := range a { 127 | links := RegHtmlHrefTxt(v) 128 | if len(links) < 1 { 129 | continue 130 | } 131 | link := links[0] 132 | if strings.Index(link, scan.Host) == -1 && string(link[0]) != "/" && string(link[0]) != "#" { 133 | if _, ok := urlSet[link]; ok { 134 | continue 135 | } 136 | if UsefulUrl(link) { 137 | urlSet[link] = struct{}{} 138 | count++ 139 | } 140 | } 141 | } 142 | urls := make([]string, 0) 143 | for k := range urlSet { 144 | urls = append(urls, k) 145 | } 146 | return urls, count 147 | } 148 | 149 | // HostScanBadLink Host站点下 HTML Get Url 死链接扫描 应用函数 150 | type HostScanBadLink struct { 151 | Host string 152 | Depth int // 页面深度 153 | PageState map[string]int 154 | UrlSet map[string]struct{} // 检查页面重复 155 | } 156 | 157 | // NewHostScanBadLink 创建站点死链接扫描,只支持get请求 158 | func NewHostScanBadLink(host string, depth int) *HostScanBadLink { 159 | return &HostScanBadLink{ 160 | Host: host, 161 | Depth: depth, 162 | PageState: make(map[string]int), 163 | UrlSet: make(map[string]struct{}), 164 | } 165 | } 166 | 167 | func (scan *HostScanBadLink) Run() ([]string, int) { 168 | CloseLog() 169 | scan.do(scan.Host, 0) 170 | badUrls := make([]string, 0) 171 | for k, v := range scan.PageState { 172 | if v == 404 || v == 410 { 173 | badUrls = append(badUrls, k) 174 | } 175 | } 176 | if ApplicationTerminalOut { 177 | fmt.Printf("\n") 178 | } 179 | return badUrls, len(badUrls) 180 | } 181 | 182 | func (scan *HostScanBadLink) Result() map[string]int { 183 | res := make(map[string]int) 184 | for k, v := range scan.PageState { 185 | if v == 404 || v == 410 { 186 | res[k] = v 187 | } 188 | } 189 | return res 190 | } 191 | 192 | func (scan *HostScanBadLink) Report() map[string]int { 193 | return scan.PageState 194 | } 195 | 196 | func (scan *HostScanBadLink) do(caseUrl string, df int) { 197 | if df > scan.Depth { 198 | return 199 | } 200 | if len(caseUrl) < 1 { 201 | return 202 | } 203 | if string(caseUrl[0]) == "/" { 204 | caseUrl = scan.Host + caseUrl 205 | } 206 | if _, ok := scan.UrlSet[caseUrl]; ok { 207 | return 208 | } 209 | ctx, err := Get(caseUrl) 210 | if err != nil { 211 | ctx.StateCode = 404 212 | return 213 | } 214 | if ApplicationTerminalOut { 215 | fmt.Print(".") 216 | } 217 | df++ 218 | scan.UrlSet[caseUrl] = struct{}{} 219 | scan.PageState[caseUrl] = ctx.StateCode 220 | a := RegHtmlA(ctx.Html) 221 | for _, v := range a { 222 | links := RegHtmlHrefTxt(v) 223 | if len(links) < 1 { 224 | continue 225 | } 226 | link := links[0] 227 | // 请求并验证 228 | scan.do(link, df) 229 | } 230 | } 231 | 232 | // HostPageSpeedCheck Host站点下 HTML Get 测速 应用函数 233 | type HostPageSpeedCheck struct { 234 | Host string 235 | Depth int // 页面深度 236 | PageSpeed map[string]time.Duration 237 | UrlSet map[string]struct{} // 检查页面重复 238 | } 239 | 240 | // NewHostPageSpeedCheck 创建站点所有url测速,只支持get请求 241 | func NewHostPageSpeedCheck(host string, depth int) *HostPageSpeedCheck { 242 | return &HostPageSpeedCheck{ 243 | Host: host, 244 | Depth: depth, 245 | PageSpeed: make(map[string]time.Duration), 246 | UrlSet: make(map[string]struct{}), 247 | } 248 | } 249 | 250 | // Run int 单位 ms 251 | func (scan *HostPageSpeedCheck) Run() ([]string, int) { 252 | CloseLog() 253 | scan.do(scan.Host, 0) 254 | urls := make([]string, 0) 255 | for k, v := range scan.PageSpeed { 256 | urls = append(urls, fmt.Sprintf("%s:%v", k, v)) 257 | } 258 | if ApplicationTerminalOut { 259 | fmt.Printf("\n") 260 | } 261 | return urls, len(urls) 262 | } 263 | 264 | func (scan *HostPageSpeedCheck) Result() map[string]time.Duration { 265 | return scan.PageSpeed 266 | } 267 | 268 | func (scan *HostPageSpeedCheck) do(caseUrl string, df int) { 269 | if len(caseUrl) < 1 { 270 | return 271 | } 272 | if df > scan.Depth { 273 | return 274 | } 275 | if string(caseUrl[0]) == "/" { 276 | caseUrl = scan.Host + caseUrl 277 | } 278 | if _, ok := scan.UrlSet[caseUrl]; ok { 279 | return 280 | } 281 | if strings.Index(caseUrl, scan.Host) == -1 { 282 | return 283 | } 284 | ctx, err := Get(caseUrl) 285 | if err != nil { 286 | Error(err) 287 | return 288 | } 289 | if ApplicationTerminalOut { 290 | fmt.Print(".") 291 | } 292 | df++ 293 | scan.UrlSet[caseUrl] = struct{}{} 294 | scan.PageSpeed[caseUrl] = ctx.Ms 295 | a := RegHtmlA(ctx.Html) 296 | for _, v := range a { 297 | links := RegHtmlHrefTxt(v) 298 | if len(links) < 1 { 299 | continue 300 | } 301 | link := links[0] 302 | // 请求并验证 303 | scan.do(link, df) 304 | } 305 | } 306 | 307 | // AverageSpeed 平均时间 308 | func (scan *HostPageSpeedCheck) AverageSpeed() float64 { 309 | var ( 310 | n int64 = 0 311 | t int64 = 0 312 | ) 313 | for _, v := range scan.PageSpeed { 314 | n++ 315 | t += v.Milliseconds() 316 | } 317 | return float64(t) / float64(n) 318 | } 319 | 320 | // MaxSpeed 最高用时 321 | func (scan *HostPageSpeedCheck) MaxSpeed() int64 { 322 | var maxTmp int64 = 0 323 | for _, v := range scan.PageSpeed { 324 | if v.Milliseconds() > maxTmp { 325 | maxTmp = v.Milliseconds() 326 | } 327 | } 328 | return maxTmp 329 | } 330 | 331 | // MinSpeed 最低用时 332 | func (scan *HostPageSpeedCheck) MinSpeed() int64 { 333 | var minTmp int64 = 0 334 | for _, v := range scan.PageSpeed { 335 | if v.Milliseconds() < minTmp { 336 | minTmp = v.Milliseconds() 337 | } 338 | } 339 | return minTmp 340 | } 341 | 342 | // Report 报告 343 | func (scan *HostPageSpeedCheck) Report() map[string]string { 344 | report := make(map[string]string) 345 | report["avg"] = fmt.Sprintf("%f", scan.AverageSpeed()) 346 | report["max"] = fmt.Sprintf("%d", scan.MaxSpeed()) 347 | report["min"] = fmt.Sprintf("%d", scan.MinSpeed()) 348 | return report 349 | } 350 | -------------------------------------------------------------------------------- /gathertool.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 根据method提供对应的http请求方法,该库的请求方法会生成请求上下文,可以使用上下文的扩展功能来满足你当前的场景。 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "io" 13 | "mime/multipart" 14 | "net/http" 15 | "net/url" 16 | "os" 17 | "strings" 18 | ) 19 | 20 | // Get 执行GET请求,请求结果在 Context 对象里,请求错误会直接抛出 21 | // 22 | // caseUrl : 请求链接地址 23 | // 24 | // vs : 更多可选参数见 Req 25 | // 26 | // Example: 27 | // 28 | // ctx,err := Get("https://xxx.xxx") 29 | func Get(caseUrl string, vs ...any) (*Context, error) { 30 | ctx := NewGet(caseUrl, vs...) 31 | ctx.Do() 32 | return ctx, ctx.Err 33 | } 34 | 35 | // NewGet 定义一个GET请求的 Context ,但是不执行,执行需要调用 Context.Do 36 | // 37 | // caseUrl : 请求链接地址 38 | // 39 | // vs : 更多可选参数见 Req 40 | // 41 | // Example: 42 | // 43 | // NewGet("https://xxx.xxx").Do() 44 | func NewGet(caseUrl string, vs ...any) *Context { 45 | request, err := http.NewRequest(GET, urlStr(caseUrl), nil) 46 | if err != nil { 47 | Error(err) 48 | return &Context{ 49 | Err: reqErr(caseUrl, err), 50 | StateCode: RequestStateError, 51 | } 52 | } 53 | return Req(request, vs...) 54 | } 55 | 56 | // Post 执行POST请求,请求结果在 Context 对象里,请求错误会直接抛出 57 | // 58 | // caseUrl : 请求链接地址 59 | // 60 | // data : 请求的body 61 | // 62 | // contentType : 请求的content-type,如application/json; charset=UTF-8 63 | // 64 | // vs : 更多可选参数见 Req 65 | // 66 | // Example: 67 | // 68 | // ctx,err := Post("https://xxx.xxx", []byte("xx"), "application/json") 69 | func Post(caseUrl string, data []byte, contentType string, vs ...any) (*Context, error) { 70 | ctx := NewPost(caseUrl, data, contentType, vs...) 71 | ctx.Do() 72 | return ctx, ctx.Err 73 | } 74 | 75 | // NewPost 定义一个POST请求的 Context ,但是不执行,执行需要调用 Context.Do 76 | // 77 | // caseUrl : 请求链接地址 78 | // 79 | // data : 请求的body 80 | // 81 | // contentType : 请求的content-type,如application/json; charset=UTF-8 82 | // 83 | // vs : 更多可选参数见 Req 84 | // 85 | // Example: 86 | // 87 | // ctx := NewPost("https://xxx.xxx", []byte("xx"), "application/json") 88 | func NewPost(caseUrl string, data []byte, contentType string, vs ...any) *Context { 89 | request, err := http.NewRequest(POST, urlStr(caseUrl), bytes.NewBuffer(data)) 90 | if err != nil { 91 | Error(err) 92 | return &Context{ 93 | Err: reqErr(caseUrl, err), 94 | StateCode: RequestStateError, 95 | } 96 | } 97 | if contentType == "" { 98 | request.Header.Set("Content-Type", "application/json;") 99 | } else { 100 | request.Header.Set("Content-Type", contentType) 101 | } 102 | return Req(request, vs...) 103 | } 104 | 105 | // PostJson 执行Content-Type=json的POST请求,请求结果在 Context 对象里,请求错误会直接抛出 106 | // 107 | // caseUrl : 请求链接地址 108 | // 109 | // jsonStr : 请求body,应为json字符串 110 | // 111 | // vs : 更多可选参数见 Req 112 | // 113 | // Example: 114 | // 115 | // ctx,err := PostJson("https://xxx.xxx", `{"xx":"xx"}`) 116 | func PostJson(caseUrl string, jsonStr string, vs ...any) (*Context, error) { 117 | ctx := NewPost(caseUrl, []byte(jsonStr), "application/json; charset=UTF-8", vs...) 118 | ctx.Do() 119 | return ctx, ctx.Err 120 | } 121 | 122 | // PostForm 执行Content-Type=from-data的POST请求,请求结果在 Context 对象里,请求错误会直接抛出 123 | // 124 | // caseUrl : 请求链接地址 125 | // 126 | // data : 请求body,应为map[string]string 127 | // 128 | // vs : 更多可选参数见 Req 129 | // 130 | // Example: 131 | // 132 | // ctx,err := PostForm("https://xxx.xxx", map[string]string{"xx":"xx"}) 133 | func PostForm(caseUrl string, data map[string]string, vs ...any) (*Context, error) { 134 | postData := url.Values{} 135 | for k, v := range data { 136 | postData.Add(k, v) 137 | } 138 | request, err := http.NewRequest(POST, urlStr(caseUrl), strings.NewReader(postData.Encode())) 139 | if err != nil { 140 | Error(err) 141 | ctx := &Context{ 142 | Err: reqErr(caseUrl, err), 143 | StateCode: RequestStateError, 144 | } 145 | return ctx, ctx.Err 146 | } 147 | request.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") 148 | ctx := Req(request, vs...) 149 | ctx.Do() 150 | return ctx, ctx.Err 151 | } 152 | 153 | // PostFile 执行一个POST上传文件的请求,请求结果在 Context 对象里,请求错误会直接抛出 154 | // 155 | // caseUrl : 请求链接地址 156 | // 157 | // paramName : from-data的参数名 158 | // 159 | // filePath : 文件路径 160 | // 161 | // vs : 更多可选参数见 Req 162 | // 163 | // Example: 164 | // 165 | // ctx,err := PostFile("https://xxx.xxx","file","./test.txt") 166 | func PostFile(caseUrl, paramName, filePath string, vs ...any) (*Context, error) { 167 | f, err := os.Open(filePath) 168 | if err != nil { 169 | Error(err) 170 | ctx := &Context{ 171 | Err: reqErr(caseUrl, err), 172 | StateCode: RequestStateError, 173 | } 174 | return ctx, ctx.Err 175 | } 176 | defer func() { _ = f.Close() }() 177 | 178 | uploadBody := &bytes.Buffer{} 179 | writer := multipart.NewWriter(uploadBody) 180 | 181 | fWriter, err := writer.CreateFormFile("uploadFile", filePath) 182 | if err != nil { 183 | Info("copy file writer %v", err) 184 | } 185 | 186 | _, err = io.Copy(fWriter, f) 187 | if err != nil { 188 | Error(err) 189 | } 190 | 191 | fieldMap := map[string]string{ 192 | paramName: filePath, 193 | } 194 | 195 | for k, v := range fieldMap { 196 | _ = writer.WriteField(k, v) 197 | } 198 | 199 | err = writer.Close() 200 | if err != nil { 201 | Error(err) 202 | } 203 | 204 | request, err := http.NewRequest(POST, urlStr(caseUrl), uploadBody) 205 | if err != nil { 206 | Error(err) 207 | ctx := &Context{ 208 | Err: fmt.Errorf(caseUrl, " 请求错误, err: ", err.Error()), 209 | StateCode: RequestStateError, 210 | } 211 | return ctx, ctx.Err 212 | } 213 | 214 | request.Header.Add("Content-Type", writer.FormDataContentType()) 215 | ctx := Req(request, vs...) 216 | ctx.Do() 217 | return ctx, ctx.Err 218 | } 219 | 220 | // Put 执行一个PUT请求,请求结果在 Context 对象里,请求错误会直接抛出 221 | // 222 | // caseUrl : 请求链接地址 223 | // 224 | // data : 请求的body 225 | // 226 | // contentType : 请求的 content-type 227 | // 228 | // vs : 更多可选参数见 Req 229 | // 230 | // Example: 231 | // 232 | // ctx,err := Put("https://xxx.xxx",[]byte("xxx"),"") 233 | func Put(caseUrl string, data []byte, contentType string, vs ...any) (*Context, error) { 234 | ctx := NewPut(caseUrl, data, contentType, vs...) 235 | ctx.Do() 236 | return ctx, ctx.Err 237 | } 238 | 239 | // NewPut 定义一个PUT请求的 Context ,但是不执行,执行需要调用 Context.Do 240 | // 241 | // caseUrl : 请求链接地址 242 | // 243 | // data : 请求的body 244 | // 245 | // contentType : 请求的content-type,如application/json; charset=UTF-8 246 | // 247 | // vs : 更多可选参数见 Req 248 | // 249 | // Example: 250 | // 251 | // ctx := NewPut("https://xxx.xxx", []byte("xx"), "application/json") 252 | func NewPut(caseUrl string, data []byte, contentType string, vs ...any) *Context { 253 | request, err := http.NewRequest(PUT, urlStr(caseUrl), bytes.NewBuffer(data)) 254 | if err != nil { 255 | Error(err) 256 | return &Context{ 257 | Err: reqErr(caseUrl, err), 258 | StateCode: RequestStateError, 259 | } 260 | } 261 | request.Header.Set("Content-Type", contentType) 262 | return Req(request, vs...) 263 | } 264 | 265 | // Delete 执行一个DELETE请求,请求结果在 Context 对象里,请求错误会直接抛出 266 | func Delete(caseUrl string, vs ...any) (*Context, error) { 267 | ctx := NewDelete(caseUrl, vs...) 268 | ctx.Do() 269 | return ctx, ctx.Err 270 | } 271 | 272 | // NewDelete 定义一个DELETE请求的 Context ,但是不执行,执行需要调用 Context.Do 273 | func NewDelete(caseUrl string, vs ...any) *Context { 274 | request, err := http.NewRequest(DELETE, urlStr(caseUrl), nil) 275 | if err != nil { 276 | Error(err) 277 | return &Context{ 278 | Err: reqErr(caseUrl, err), 279 | StateCode: RequestStateError, 280 | } 281 | } 282 | return Req(request, vs...) 283 | } 284 | 285 | // Options 执行一个OPTIONS请求,请求结果在 Context 对象里,请求错误会直接抛出 286 | func Options(caseUrl string, vs ...any) (*Context, error) { 287 | ctx := NewOptions(caseUrl, vs...) 288 | ctx.Do() 289 | return ctx, ctx.Err 290 | } 291 | 292 | // NewOptions 定义一个OPTIONS请求的 Context ,但是不执行,执行需要调用 Context.Do 293 | func NewOptions(caseUrl string, vs ...any) *Context { 294 | request, err := http.NewRequest(OPTIONS, urlStr(caseUrl), nil) 295 | if err != nil { 296 | Error(err) 297 | return &Context{ 298 | Err: reqErr(caseUrl, err), 299 | StateCode: RequestStateError, 300 | } 301 | } 302 | return Req(request, vs...) 303 | } 304 | 305 | // Head 执行一个HEAD请求,请求结果在 Context 对象里,请求错误会直接抛出 306 | func Head(caseUrl string, vs ...any) (*Context, error) { 307 | request, err := http.NewRequest(HEAD, urlStr(caseUrl), nil) 308 | if err != nil { 309 | Error(err) 310 | return &Context{ 311 | Err: reqErr(caseUrl, err), 312 | StateCode: RequestStateError, 313 | }, err 314 | } 315 | ctx := Req(request, vs...) 316 | ctx.Do() 317 | return ctx, ctx.Err 318 | } 319 | 320 | // Patch 执行一个PATCH请求,请求结果在 Context 对象里,请求错误会直接抛出 321 | func Patch(caseUrl string, vs ...any) (*Context, error) { 322 | request, err := http.NewRequest(PATCH, urlStr(caseUrl), nil) 323 | if err != nil { 324 | Error(err) 325 | return &Context{ 326 | Err: reqErr(caseUrl, err), 327 | StateCode: RequestStateError, 328 | }, err 329 | } 330 | ctx := Req(request, vs...) 331 | ctx.Do() 332 | return ctx, ctx.Err 333 | } 334 | 335 | // Upload 执行GET下载请求,将下载的内容保存到指定路径终端会默认显示下载进度,并返回 Context 对象,请求错误会直接抛出 336 | // 337 | // caseUrl : 请求链接地址 338 | // 339 | // savePath : 下载内容保存路径 340 | // 341 | // vs : 更多可选参数见 Req 342 | // 343 | // Example: 344 | // 345 | // ctx := Upload("https://xxx.xxx", "./test.txt") 346 | func Upload(caseUrl, savePath string, vs ...any) (*Context, error) { 347 | ctx := NewGet(urlStr(caseUrl), vs) 348 | ctx.Upload(savePath) 349 | if ctx.Err != nil { 350 | return ctx, ctx.Err 351 | } 352 | return ctx, nil 353 | } 354 | -------------------------------------------------------------------------------- /job.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 并发工作任务 TODO 测试 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "errors" 11 | "net/http" 12 | "runtime" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | // TodoQueue 任务队列 18 | type TodoQueue interface { 19 | Add(task *Task) error // 向队列中添加元素 20 | Poll() *Task // 移除队列中最前面的元素 21 | Clear() bool // 清空队列 22 | Size() int // 获取队列的元素个数 23 | IsEmpty() bool // 判断队列是否为空 24 | Print() // 打印 25 | } 26 | 27 | // Queue 队列 28 | type Queue struct { 29 | mux *sync.Mutex 30 | list []*Task 31 | } 32 | 33 | // Task 任务对象 34 | type Task struct { 35 | Url string 36 | JsonParam string 37 | HeaderMap *http.Header 38 | Data map[string]any // 上下文传递的数据 39 | Urls []*ReqUrl // 多步骤使用 40 | Type string // "", "upload", "do" 41 | SavePath string 42 | SaveDir string 43 | FileName string 44 | once *sync.Once 45 | } 46 | 47 | // NewTask 新建任务 48 | func NewTask() *Task { 49 | return &Task{ 50 | Data: make(map[string]any), 51 | Urls: make([]*ReqUrl, 0), 52 | once: &sync.Once{}, 53 | HeaderMap: &http.Header{}, 54 | } 55 | } 56 | 57 | func (task *Task) GetDataStr(key string) string { 58 | v, ok := task.Data[key] 59 | if ok { 60 | if vStr, yes := v.(string); yes { 61 | return vStr 62 | } 63 | } 64 | return "" 65 | } 66 | 67 | func (task *Task) AddData(key string, value any) *Task { 68 | task.once.Do(func() { 69 | task.Data = make(map[string]any) 70 | }) 71 | task.Data[key] = value 72 | return task 73 | } 74 | 75 | func (task *Task) SetUrl(urlStr string) *Task { 76 | task.Url = urlStr 77 | return task 78 | } 79 | 80 | func (task *Task) SetJsonParam(jsonStr string) *Task { 81 | task.JsonParam = jsonStr 82 | return task 83 | } 84 | 85 | func CrawlerTask(url, jsonParam string, vs ...any) *Task { 86 | header := &http.Header{} 87 | 88 | for _, v := range vs { 89 | switch vv := v.(type) { 90 | case http.Header: 91 | for key, values := range vv { 92 | for _, value := range values { 93 | header.Add(key, value) 94 | } 95 | } 96 | case *http.Header: 97 | for key, values := range *vv { 98 | for _, value := range values { 99 | header.Add(key, value) 100 | } 101 | } 102 | } 103 | } 104 | 105 | return &Task{ 106 | Url: url, 107 | JsonParam: jsonParam, 108 | HeaderMap: header, 109 | Data: make(map[string]any), 110 | Type: "do", 111 | } 112 | } 113 | 114 | // ReqUrl 单个请求地址对象 115 | type ReqUrl struct { 116 | Url string 117 | Method string 118 | Params map[string]any 119 | } 120 | 121 | // NewQueue 新建一个队列 122 | func NewQueue() TodoQueue { 123 | list := make([]*Task, 0) 124 | return &Queue{list: list, mux: &sync.Mutex{}} 125 | } 126 | 127 | // Add 向队列中添加元素 128 | func (q *Queue) Add(task *Task) error { 129 | q.mux.Lock() 130 | defer q.mux.Unlock() 131 | q.list = append(q.list, task) 132 | return nil 133 | } 134 | 135 | // Poll 移除队列中最前面的额元素 136 | func (q *Queue) Poll() *Task { 137 | q.mux.Lock() 138 | defer q.mux.Unlock() 139 | if q.IsEmpty() { 140 | return nil 141 | } 142 | 143 | first := q.list[0] 144 | q.list = q.list[1:] 145 | return first 146 | } 147 | 148 | func (q *Queue) Clear() bool { 149 | if q.IsEmpty() { 150 | return false 151 | } 152 | for i := 0; i < q.Size(); i++ { 153 | q.list[i].Url = "" 154 | } 155 | q.list = nil 156 | return true 157 | } 158 | 159 | func (q *Queue) Size() int { 160 | return len(q.list) 161 | } 162 | 163 | func (q *Queue) IsEmpty() bool { 164 | if len(q.list) == 0 { 165 | return true 166 | } 167 | return false 168 | } 169 | 170 | func (q *Queue) Print() { 171 | Info(q.list) 172 | } 173 | 174 | // UploadQueue 下载队列 175 | type UploadQueue struct { 176 | mux *sync.Mutex 177 | list []*Task 178 | } 179 | 180 | // NewUploadQueue 新建一个下载队列 181 | func NewUploadQueue() TodoQueue { 182 | list := make([]*Task, 0) 183 | return &UploadQueue{list: list, mux: &sync.Mutex{}} 184 | } 185 | 186 | // Add 向队列中添加元素 187 | func (q *UploadQueue) Add(task *Task) error { 188 | if task.Url == "" { 189 | return errors.New("task url is null") 190 | } 191 | if task.SavePath == "" && (task.SaveDir == "" || task.FileName == "") { 192 | return errors.New("save path is null") 193 | } 194 | q.mux.Lock() 195 | defer q.mux.Unlock() 196 | q.list = append(q.list, task) 197 | return nil 198 | } 199 | 200 | // Poll 移除队列中最前面的额元素 201 | func (q *UploadQueue) Poll() *Task { 202 | q.mux.Lock() 203 | defer q.mux.Unlock() 204 | if q.IsEmpty() { 205 | return nil 206 | } 207 | 208 | first := q.list[0] 209 | q.list = q.list[1:] 210 | return first 211 | } 212 | 213 | func (q *UploadQueue) Clear() bool { 214 | if q.IsEmpty() { 215 | return false 216 | } 217 | for i := 0; i < q.Size(); i++ { 218 | q.list[i].Url = "" 219 | } 220 | q.list = nil 221 | return true 222 | } 223 | 224 | func (q *UploadQueue) Size() int { 225 | return len(q.list) 226 | } 227 | 228 | func (q *UploadQueue) IsEmpty() bool { 229 | if len(q.list) == 0 { 230 | return true 231 | } 232 | return false 233 | } 234 | 235 | func (q *UploadQueue) Print() { 236 | Info(q.list) 237 | } 238 | 239 | // TODO 使用双向链表实现队列 240 | 241 | // StartJob 开始运行并发 242 | func StartJob(jobNumber int, queue TodoQueue, f func(task *Task)) { 243 | var wg sync.WaitGroup 244 | for job := 0; job < jobNumber; job++ { 245 | wg.Add(1) 246 | go func(i int) { 247 | Info("启动第", i, "个任务") 248 | defer wg.Done() 249 | for { 250 | if queue.IsEmpty() { 251 | break 252 | } 253 | task := queue.Poll() 254 | Info("第", i, "个任务取的值: ", task) 255 | f(task) 256 | } 257 | Info("第", i, "个任务结束!!") 258 | }(job) 259 | } 260 | wg.Wait() 261 | Info("执行完成!!!") 262 | } 263 | 264 | // Err2Retry 设置遇到错误执行 Retry 事件 265 | type Err2Retry bool 266 | 267 | // StartJobGet 并发执行Get,直到队列任务为空 268 | // jobNumber 并发数, 269 | // queue 全局队列, 270 | func StartJobGet(jobNumber int, queue TodoQueue, vs ...any) { 271 | 272 | var ( 273 | client *http.Client 274 | succeed SucceedFunc 275 | retry RetryFunc 276 | failed FailedFunc 277 | err2Retry Err2Retry 278 | sleep Sleep 279 | ) 280 | 281 | runtime.GOMAXPROCS(runtime.NumCPU()) 282 | 283 | for _, v := range vs { 284 | switch vv := v.(type) { 285 | case *http.Client: 286 | client = vv 287 | case SucceedFunc: 288 | succeed = vv 289 | case FailedFunc: 290 | failed = vv 291 | case RetryFunc: 292 | retry = vv 293 | case Err2Retry: 294 | err2Retry = vv 295 | case Sleep: 296 | sleep = vv 297 | Info("设置 sleep = ", sleep) 298 | } 299 | } 300 | 301 | var wg sync.WaitGroup 302 | for job := 0; job < jobNumber; job++ { 303 | wg.Add(1) 304 | go func(i int) { 305 | Info("启动第", i, "个任务") 306 | defer wg.Done() 307 | for { 308 | if queue.IsEmpty() { 309 | break 310 | } 311 | task := queue.Poll() 312 | Info("第", i, "个任务取的值: ", task) 313 | ctx := NewGet(task.Url, task) 314 | if client != nil { 315 | ctx.Client = client 316 | } 317 | if succeed != nil { 318 | ctx.SetSucceedFunc(succeed) 319 | } 320 | if retry != nil { 321 | ctx.SetRetryFunc(retry) 322 | } 323 | if failed != nil { 324 | ctx.SetFailedFunc(failed) 325 | } 326 | if err2Retry { 327 | ctx.OpenErr2Retry() 328 | } 329 | if sleep != 0 { 330 | time.Sleep(time.Duration(sleep) * time.Second) 331 | } 332 | switch task.Type { 333 | case "", "do": 334 | ctx.Do() 335 | case "upload": 336 | if task.SavePath == "" { 337 | task.SavePath = task.SaveDir + task.FileName 338 | } 339 | ctx.Upload(task.SavePath) 340 | default: 341 | ctx.Do() 342 | } 343 | } 344 | Info("第", i, "个任务结束!!") 345 | }(job) 346 | } 347 | wg.Wait() 348 | Info("执行完成!!!") 349 | } 350 | 351 | // StartJobPost 开始运行并发Post 352 | func StartJobPost(jobNumber int, queue TodoQueue, vs ...any) { 353 | var ( 354 | client *http.Client 355 | succeed SucceedFunc 356 | retry RetryFunc 357 | failed FailedFunc 358 | err2Retry Err2Retry 359 | sleep Sleep 360 | ) 361 | 362 | for _, v := range vs { 363 | switch vv := v.(type) { 364 | case *http.Client: 365 | client = vv 366 | case SucceedFunc: 367 | succeed = vv 368 | case FailedFunc: 369 | failed = vv 370 | case RetryFunc: 371 | retry = vv 372 | case Err2Retry: 373 | err2Retry = vv 374 | case Sleep: 375 | sleep = vv 376 | Info("设置 sleep = ", sleep) 377 | } 378 | } 379 | 380 | var wg sync.WaitGroup 381 | for job := 0; job < jobNumber; job++ { 382 | wg.Add(1) 383 | go func(i int) { 384 | Info("启动第", i, "个任务") 385 | defer wg.Done() 386 | for { 387 | if queue.IsEmpty() { 388 | break 389 | } 390 | task := queue.Poll() 391 | Info("第", i, "个任务取的值: ", task, task.HeaderMap) 392 | ctx := NewPost(task.Url, []byte(task.JsonParam), "application/json;", task) 393 | if client != nil { 394 | ctx.Client = client 395 | } 396 | if succeed != nil { 397 | ctx.SetSucceedFunc(succeed) 398 | } 399 | if retry != nil { 400 | ctx.SetRetryFunc(retry) 401 | } 402 | if failed != nil { 403 | ctx.SetFailedFunc(failed) 404 | } 405 | if err2Retry { 406 | ctx.OpenErr2Retry() 407 | } 408 | if sleep != 0 { 409 | ctx.sleep = sleep 410 | } 411 | switch task.Type { 412 | case "", "do": 413 | ctx.Do() 414 | case "upload": 415 | if task.SavePath == "" { 416 | task.SavePath = task.SaveDir + task.FileName 417 | } 418 | ctx.Upload(task.SavePath) 419 | default: 420 | ctx.Do() 421 | } 422 | 423 | } 424 | Info("第", i, "个任务结束!!") 425 | }(job) 426 | } 427 | wg.Wait() 428 | Info("执行完成!!!") 429 | } 430 | 431 | // CPUMax 多核执行 432 | func CPUMax() { 433 | runtime.GOMAXPROCS(runtime.NumCPU()) 434 | } 435 | 436 | // TODO: 优化并重构 437 | -------------------------------------------------------------------------------- /out_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 输出数据到文本相关的操作; 其中含csv的相关方法, excel,文本操作等 TODO 测试 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "archive/tar" 11 | "archive/zip" 12 | "crypto/md5" 13 | "encoding/csv" 14 | "encoding/hex" 15 | "fmt" 16 | "io" 17 | "os" 18 | "path" 19 | "path/filepath" 20 | "runtime" 21 | "strings" 22 | ) 23 | 24 | // Csv Csv格式文件 25 | type Csv struct { 26 | FileName string 27 | W *csv.Writer 28 | R *csv.Reader 29 | } 30 | 31 | // NewCSV 新创建一个csv对象 32 | func NewCSV(fileName string) (*Csv, error) { 33 | f, err := os.Create(fileName) 34 | if err != nil { 35 | Error(err.Error()) 36 | return nil, err 37 | } 38 | _, _ = f.WriteString("\xEF\xBB\xBF") 39 | csvObj := &Csv{FileName: fileName} 40 | csvObj.W = csv.NewWriter(f) 41 | csvObj.R = csv.NewReader(f) 42 | return csvObj, nil 43 | } 44 | 45 | func (c *Csv) Close() { 46 | c.Close() 47 | } 48 | 49 | func (c *Csv) Add(data []string) error { 50 | err := c.W.Write(data) 51 | if err != nil { 52 | return err 53 | } 54 | c.W.Flush() 55 | return nil 56 | } 57 | 58 | func (c *Csv) ReadAll() ([][]string, error) { 59 | return c.R.ReadAll() 60 | } 61 | 62 | // ReadCsvFile csv file -> [][]string 行列 63 | func ReadCsvFile(filename string) [][]string { 64 | file, err := os.Open(filename) 65 | if err != nil { 66 | panic(err) 67 | } 68 | defer func() { 69 | _ = file.Close() 70 | }() 71 | reader := csv.NewReader(file) 72 | reader.Comma = ',' 73 | allRecords, err := reader.ReadAll() 74 | if err != nil { 75 | panic(err) 76 | } 77 | return allRecords 78 | } 79 | 80 | func FileExists(name string) bool { 81 | if _, err := os.Stat(name); err != nil { 82 | if os.IsNotExist(err) { 83 | return false 84 | } 85 | } 86 | return true 87 | } 88 | 89 | // OutJsonFile 将data输出到json文件 90 | func OutJsonFile(data any, fileName string) error { 91 | var ( 92 | f *os.File 93 | err error 94 | ) 95 | if Exists(fileName) { //如果文件存在 96 | f, err = os.OpenFile(fileName, os.O_APPEND, 0666) //打开文件 97 | } else { 98 | f, err = os.Create(fileName) //创建文件 99 | } 100 | if err != nil { 101 | return err 102 | } 103 | str, err := Any2Json(data) 104 | if err != nil { 105 | return err 106 | } 107 | _, err = io.WriteString(f, str) 108 | if err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | // GetAllFile 获取目录下的所有文件 115 | func GetAllFile(pathname string) ([]string, error) { 116 | s := make([]string, 0) 117 | rd, err := os.ReadDir(pathname) 118 | if err != nil { 119 | Error("read dir fail:", err) 120 | return s, err 121 | } 122 | for _, fi := range rd { 123 | if !fi.IsDir() { 124 | fullName := pathname + "/" + fi.Name() 125 | s = append(s, fullName) 126 | } 127 | } 128 | return s, nil 129 | } 130 | 131 | func subString(str string, start, end int) string { 132 | rs := []rune(str) 133 | length := len(rs) 134 | if start < 0 || start > length { 135 | Error("start is wrong") 136 | return "" 137 | } 138 | if end < start || end > length { 139 | Error("end is wrong") 140 | return "" 141 | } 142 | return string(rs[start:end]) 143 | } 144 | 145 | // DeCompressZIP zip解压文件 146 | func DeCompressZIP(zipFile, dest string) error { 147 | reader, err := zip.OpenReader(zipFile) 148 | if err != nil { 149 | return err 150 | } 151 | defer func() { 152 | _ = reader.Close() 153 | }() 154 | for _, file := range reader.File { 155 | rc, err := file.Open() 156 | if err != nil { 157 | return err 158 | } 159 | filename := dest + file.Name 160 | err = os.MkdirAll(subString(filename, 0, strings.LastIndex(filename, "/")), 0755) 161 | if err != nil { 162 | return err 163 | } 164 | w, err := os.Create(filename) 165 | if err != nil { 166 | return err 167 | } 168 | _, err = io.Copy(w, rc) 169 | if err != nil { 170 | return err 171 | } 172 | _ = w.Close() 173 | _ = rc.Close() 174 | } 175 | return nil 176 | } 177 | 178 | // DeCompressTAR tar 解压文件 179 | func DeCompressTAR(tarFile, dest string) error { 180 | file, err := os.Open(tarFile) 181 | if err != nil { 182 | Error(err) 183 | return err 184 | } 185 | defer func() { 186 | _ = file.Close() 187 | }() 188 | tr := tar.NewReader(file) 189 | for { 190 | hdr, err := tr.Next() 191 | if err == io.EOF { 192 | break 193 | } 194 | if err != nil { 195 | return err 196 | } 197 | filename := dest + hdr.Name 198 | err = os.MkdirAll(subString(filename, 0, strings.LastIndex(filename, "/")), 0755) 199 | if err != nil { 200 | return err 201 | } 202 | w, err := os.Create(filename) 203 | if err != nil { 204 | return err 205 | } 206 | _, err = io.Copy(w, tr) 207 | if err != nil { 208 | return err 209 | } 210 | _ = w.Close() 211 | } 212 | return nil 213 | } 214 | 215 | // DecompressionZipFile zip压缩文件 216 | func DecompressionZipFile(src, dest string) error { 217 | reader, err := zip.OpenReader(src) 218 | if err != nil { 219 | return err 220 | } 221 | defer func() { 222 | _ = reader.Close() 223 | }() 224 | for _, file := range reader.File { 225 | filePath := path.Join(dest, file.Name) 226 | if file.FileInfo().IsDir() { 227 | _ = os.MkdirAll(filePath, os.ModePerm) 228 | } else { 229 | if err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { 230 | return err 231 | } 232 | inFile, err := file.Open() 233 | if err != nil { 234 | return err 235 | } 236 | outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) 237 | if err != nil { 238 | return err 239 | } 240 | _, err = io.Copy(outFile, inFile) 241 | if err != nil { 242 | return err 243 | } 244 | _ = inFile.Close() 245 | _ = outFile.Close() 246 | } 247 | } 248 | return nil 249 | } 250 | 251 | // CompressFiles 压缩很多文件 252 | // files 文件数组,可以是不同dir下的文件或者文件夹 253 | // dest 压缩文件存放地址 254 | func CompressFiles(files []string, dest string) error { 255 | d, _ := os.Create(dest) 256 | defer func() { 257 | _ = d.Close() 258 | }() 259 | w := zip.NewWriter(d) 260 | defer func() { 261 | _ = w.Close() 262 | }() 263 | for _, file := range files { 264 | err := compressFiles(file, "", w) 265 | if err != nil { 266 | return err 267 | } 268 | } 269 | return nil 270 | } 271 | 272 | func compressFiles(filePath string, prefix string, zw *zip.Writer) error { 273 | file, err := os.Open(filePath) 274 | if err != nil { 275 | return err 276 | } 277 | info, err := file.Stat() 278 | if err != nil { 279 | return err 280 | } 281 | if info.IsDir() { 282 | prefix = prefix + "/" + info.Name() 283 | fileInfos, err := file.Readdir(-1) 284 | if err != nil { 285 | return err 286 | } 287 | for _, fi := range fileInfos { 288 | f := file.Name() + "/" + fi.Name() 289 | err = compressFiles(f, prefix, zw) 290 | if err != nil { 291 | return err 292 | } 293 | } 294 | } else { 295 | header, err := zip.FileInfoHeader(info) 296 | if err != nil { 297 | return err 298 | } 299 | header.Name = prefix + "/" + header.Name 300 | writer, err := zw.CreateHeader(header) 301 | if err != nil { 302 | return err 303 | } 304 | _, err = io.Copy(writer, file) 305 | _ = file.Close() 306 | if err != nil { 307 | return err 308 | } 309 | } 310 | return nil 311 | } 312 | 313 | // CompressDirZip 压缩目录 314 | func CompressDirZip(src, outFile string) error { 315 | _ = os.RemoveAll(outFile) 316 | zipFile, err := os.Create(outFile) 317 | if err != nil { 318 | return err 319 | } 320 | defer func() { 321 | _ = zipFile.Close() 322 | }() 323 | archive := zip.NewWriter(zipFile) 324 | defer func() { 325 | _ = archive.Close() 326 | }() 327 | return filepath.Walk(src, func(path string, info os.FileInfo, _ error) error { 328 | if path == src { 329 | return nil 330 | } 331 | header, _ := zip.FileInfoHeader(info) 332 | header.Name = strings.TrimPrefix(path, src+`/`) 333 | if info.IsDir() { 334 | header.Name += `/` 335 | } else { 336 | header.Method = zip.Deflate 337 | } 338 | writer, _ := archive.CreateHeader(header) 339 | if !info.IsDir() { 340 | file, _ := os.Open(path) 341 | defer func() { 342 | _ = file.Close() 343 | }() 344 | _, _ = io.Copy(writer, file) 345 | } 346 | return nil 347 | }) 348 | } 349 | 350 | func Exists(path string) bool { 351 | if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) { 352 | return true 353 | } 354 | return false 355 | } 356 | 357 | func IsDir(path string) bool { 358 | s, err := os.Stat(path) 359 | if err != nil { 360 | return false 361 | } 362 | return s.IsDir() 363 | } 364 | 365 | func IsFile(path string) bool { 366 | s, err := os.Stat(path) 367 | if err != nil { 368 | return false 369 | } 370 | return !s.IsDir() 371 | } 372 | 373 | // GetNowPath 获取当前运行路径 374 | func GetNowPath() string { 375 | pathData, err := os.Getwd() 376 | if err != nil { 377 | Error(err) 378 | } 379 | if runtime.GOOS == "windows" { 380 | return strings.Replace(pathData, "\\", "/", -1) 381 | } 382 | return pathData 383 | } 384 | 385 | // FileMd5sum 文件 Md5 386 | func FileMd5sum(fileName string) string { 387 | fin, err := os.OpenFile(fileName, os.O_RDONLY, 0644) 388 | if err != nil { 389 | Info(fileName, err) 390 | return "" 391 | } 392 | defer func() { 393 | _ = fin.Close() 394 | }() 395 | Buf, bufErr := os.ReadFile(fileName) 396 | if bufErr != nil { 397 | Info(fileName, bufErr) 398 | return "" 399 | } 400 | m := md5.Sum(Buf) 401 | return hex.EncodeToString(m[:16]) 402 | } 403 | 404 | // PathExists 目录不存在则创建 405 | func PathExists(path string) { 406 | _, err := os.Stat(path) 407 | if err != nil && os.IsNotExist(err) { 408 | _ = os.MkdirAll(path, 0777) 409 | } 410 | } 411 | 412 | // FileSizeFormat 字节的单位转换 保留两位小数 413 | func FileSizeFormat(fileSize int64) (size string) { 414 | if fileSize < 1024 { 415 | //return strconv.FormatInt(fileSize, 10) + "B" 416 | return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1)) 417 | } else if fileSize < (1024 * 1024) { 418 | return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024)) 419 | } else if fileSize < (1024 * 1024 * 1024) { 420 | return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024)) 421 | } else if fileSize < (1024 * 1024 * 1024 * 1024) { 422 | return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024)) 423 | } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { 424 | return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024)) 425 | } else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024) 426 | return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) 427 | } 428 | } 429 | 430 | // AbPathByCaller 获取当前执行文件绝对路径(go run) 431 | func AbPathByCaller() string { 432 | var abPath string 433 | _, filename, _, ok := runtime.Caller(0) 434 | if ok { 435 | abPath = path.Dir(filename) 436 | } 437 | return path.Join(abPath, "../../../") 438 | } 439 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= 2 | github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= 3 | github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 9 | github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= 10 | github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= 11 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM= 12 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= 13 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 14 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 15 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 16 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 17 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 18 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 19 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 20 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 21 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 22 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 23 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 24 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 25 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 26 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 27 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 28 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 29 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 30 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= 31 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 32 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= 33 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 34 | github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= 35 | github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= 36 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= 37 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 38 | github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8= 39 | github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0= 40 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= 41 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 42 | github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc= 43 | github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 44 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 45 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 46 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 49 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 50 | github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE= 51 | github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY= 52 | github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= 53 | github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= 57 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 58 | github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= 59 | github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= 60 | github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 61 | github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= 62 | github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 63 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 64 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 65 | github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= 66 | github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 68 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 69 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 70 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 71 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 72 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 73 | github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= 74 | github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= 75 | github.com/xuri/excelize/v2 v2.7.0 h1:Hri/czwyRCW6f6zrCDWXcXKshlq4xAZNpNOpdfnFhEw= 76 | github.com/xuri/excelize/v2 v2.7.0/go.mod h1:ebKlRoS+rGyLMyUx3ErBECXs/HNYqyj+PbkkKRK5vSI= 77 | github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= 78 | github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= 79 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 80 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 81 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 82 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 83 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 84 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 85 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 86 | golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= 87 | golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= 88 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 89 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 90 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 91 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 92 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 93 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 94 | golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= 95 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 96 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 97 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 98 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 101 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 109 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 111 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 112 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 113 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 114 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 115 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 116 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 117 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 118 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 119 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 120 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 121 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 122 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 123 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 124 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 125 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= 126 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 127 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 128 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 129 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= 130 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= 131 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 132 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 133 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 134 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 135 | -------------------------------------------------------------------------------- /data_structure.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 包含了常见数据结构的实用方法,有切片,map,set等等 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "errors" 12 | "fmt" 13 | "math/rand" 14 | "reflect" 15 | "sort" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | var randObj = rand.New(rand.NewSource(time.Now().UnixNano())) 21 | var TableIsNULL = fmt.Errorf("table is null") 22 | 23 | func SliceContains[V comparable](a []V, v V) bool { 24 | l := len(a) 25 | if l == 0 { 26 | return false 27 | } 28 | for i := 0; i < l; i++ { 29 | if a[i] == v { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func SliceDeduplicate[V comparable](a []V) []V { 37 | l := len(a) 38 | if l < 2 { 39 | return a 40 | } 41 | seen := make(map[V]struct{}) 42 | j := 0 43 | for i := 0; i < l; i++ { 44 | if _, ok := seen[a[i]]; ok { 45 | continue 46 | } 47 | seen[a[i]] = struct{}{} 48 | a[j] = a[i] 49 | j++ 50 | } 51 | return a[:j] 52 | } 53 | 54 | func SliceDel[V comparable](a []V, i int) []V { 55 | l := len(a) 56 | if l == 0 { 57 | return a 58 | } 59 | if i < 0 || i > l-1 { 60 | return a 61 | } 62 | return append(a[:i], a[i+1:]...) 63 | } 64 | 65 | type number interface { 66 | int | int8 | int16 | int32 | int64 | 67 | uint | uint8 | uint16 | uint32 | uint64 | 68 | float32 | float64 69 | } 70 | 71 | func SliceMax[V number](a []V) V { 72 | l := len(a) 73 | if l == 0 { 74 | var none V 75 | return none 76 | } 77 | maxV := a[0] 78 | for k := 1; k < l; k++ { 79 | if a[k] > maxV { 80 | maxV = a[k] 81 | } 82 | } 83 | return maxV 84 | } 85 | 86 | func SliceMin[V number](a []V) V { 87 | l := len(a) 88 | if l == 0 { 89 | return 0 90 | } 91 | minV := a[0] 92 | for k := 1; k < l; k++ { 93 | if a[k] < minV { 94 | minV = a[k] 95 | } 96 | } 97 | return minV 98 | } 99 | 100 | func SlicePop[V comparable](a []V) (V, []V) { 101 | if len(a) == 0 { 102 | var none V 103 | return none, a 104 | } 105 | return a[len(a)-1], a[:len(a)-1] 106 | } 107 | 108 | func SliceReverse[V comparable](a []V) []V { 109 | l := len(a) 110 | if l == 0 { 111 | return a 112 | } 113 | for s, e := 0, len(a)-1; s < e; { 114 | a[s], a[e] = a[e], a[s] 115 | s++ 116 | e-- 117 | } 118 | return a 119 | } 120 | 121 | func SliceShuffle[V comparable](a []V) []V { 122 | l := len(a) 123 | if l <= 1 { 124 | return a 125 | } 126 | randObj.Shuffle(l, func(i, j int) { 127 | a[i], a[j] = a[j], a[i] 128 | }) 129 | return a 130 | } 131 | 132 | func SliceCopy[V comparable](a []V) []V { 133 | return append(a[:0:0], a...) 134 | } 135 | 136 | // OrderMap 固定顺序map 137 | type OrderMap[K, V comparable] struct { 138 | mux sync.Mutex // TODO 优化 使用读写锁 139 | data map[K]V 140 | keyList []K // TODO 优化 使用链表 141 | size int 142 | } 143 | 144 | // NewOrderMap ues: NewOrderMap[K, V]() 145 | func NewOrderMap[K, V comparable]() *OrderMap[K, V] { 146 | obj := &OrderMap[K, V]{ 147 | mux: sync.Mutex{}, 148 | data: make(map[K]V), 149 | keyList: make([]K, 0), 150 | size: 0, 151 | } 152 | return obj 153 | } 154 | 155 | func (m *OrderMap[K, V]) Add(key K, value V) *OrderMap[K, V] { 156 | m.mux.Lock() 157 | defer m.mux.Unlock() 158 | if _, ok := m.data[key]; ok { 159 | m.data[key] = value 160 | return m 161 | } 162 | m.keyList = append(m.keyList, key) 163 | m.size++ 164 | m.data[key] = value 165 | return m 166 | } 167 | 168 | func (m *OrderMap[K, V]) Get(key K) V { 169 | m.mux.Lock() 170 | defer m.mux.Unlock() 171 | return m.data[key] 172 | } 173 | 174 | func (m *OrderMap[K, V]) Del(key K) *OrderMap[K, V] { 175 | m.mux.Lock() 176 | defer m.mux.Unlock() 177 | if _, ok := m.data[key]; ok { 178 | delete(m.data, key) 179 | for i := 0; i < m.size; i++ { 180 | if m.keyList[i] == key { 181 | m.keyList = append(m.keyList[:i], m.keyList[i+1:]...) 182 | m.size-- 183 | return m 184 | } 185 | } 186 | } 187 | return m 188 | } 189 | 190 | func (m *OrderMap[K, V]) Len() int { 191 | return m.size 192 | } 193 | 194 | func (m *OrderMap[K, V]) KeyList() []K { 195 | return m.keyList 196 | } 197 | 198 | func (m *OrderMap[K, V]) AddMap(data map[K]V) *OrderMap[K, V] { 199 | for k, v := range data { 200 | m.Add(k, v) 201 | } 202 | return m 203 | } 204 | 205 | func (m *OrderMap[K, V]) Range(f func(k K, v V)) *OrderMap[K, V] { 206 | for i := 0; i < m.size; i++ { 207 | f(m.keyList[i], m.data[m.keyList[i]]) 208 | } 209 | return m 210 | } 211 | 212 | // RangeAt Range 遍历map含顺序id 213 | func (m *OrderMap[K, V]) RangeAt(f func(id int, k K, v V)) *OrderMap[K, V] { 214 | for i := 0; i < m.size; i++ { 215 | f(i, m.keyList[i], m.data[m.keyList[i]]) 216 | } 217 | return m 218 | } 219 | 220 | // CheckValue 查看map是否存在指定的值 221 | func (m *OrderMap[K, V]) CheckValue(value V) bool { 222 | m.mux.Lock() 223 | defer m.mux.Unlock() 224 | for i := 0; i < m.size; i++ { 225 | if m.data[m.keyList[i]] == value { 226 | return true 227 | } 228 | } 229 | return false 230 | } 231 | 232 | // Reverse map反序 233 | func (m *OrderMap[K, V]) Reverse() *OrderMap[K, V] { 234 | for i, j := 0, len(m.keyList)-1; i < j; i, j = i+1, j-1 { 235 | m.keyList[i], m.keyList[j] = m.keyList[j], m.keyList[i] 236 | } 237 | return m 238 | } 239 | 240 | func (m *OrderMap[K, V]) Json() (string, error) { 241 | return Any2Json(m.data) 242 | } 243 | 244 | func (m *OrderMap[K, V]) DebugPrint() { 245 | m.RangeAt(func(id int, k K, v V) { 246 | DebugF("item:%d key:%v value:%v", id, k, v) 247 | }) 248 | } 249 | 250 | // Insert 插入值指定位置 251 | func (m *OrderMap[K, V]) Insert(k K, v V, position int) error { 252 | m.Add(k, v) 253 | return m.Move(k, position) 254 | } 255 | 256 | // Move 值移动指定位置操作 257 | func (m *OrderMap[K, V]) Move(k K, position int) error { 258 | if position >= m.size { 259 | return errors.New("position >= map len") 260 | } 261 | has := false 262 | for i := 0; i < m.size; i++ { 263 | if m.keyList[i] == k { 264 | m.keyList = append(m.keyList[0:i], m.keyList[i+1:]...) 265 | has = true 266 | break 267 | } 268 | } 269 | if has { 270 | m.keyList = append(m.keyList[:position+1], m.keyList[position:]...) 271 | m.keyList[position] = k 272 | } 273 | return nil 274 | } 275 | 276 | func (m *OrderMap[K, V]) GetAtPosition(position int) (K, V, error) { 277 | if position >= m.size { 278 | var ( 279 | k K 280 | v V 281 | ) 282 | return k, v, errors.New("position >= map len") 283 | } 284 | k := m.keyList[position] 285 | v := m.data[k] 286 | return k, v, nil 287 | } 288 | 289 | // Pop 首位读取并移除 290 | func (m *OrderMap[K, V]) Pop() (K, V, error) { 291 | if m.size < 1 { 292 | var ( 293 | k K 294 | v V 295 | ) 296 | return k, v, errors.New("map size is 0") 297 | } 298 | k := m.keyList[0] 299 | v := m.data[k] 300 | m.Del(k) 301 | return k, v, nil 302 | } 303 | 304 | // BackPop 末尾读取并移除 305 | func (m *OrderMap[K, V]) BackPop() (K, V, error) { 306 | if m.size < 1 { 307 | var ( 308 | k K 309 | v V 310 | ) 311 | return k, v, errors.New("map size is 0") 312 | } 313 | k := m.keyList[m.size-1] 314 | v := m.data[k] 315 | m.Del(k) 316 | return k, v, nil 317 | } 318 | 319 | // Shuffle 洗牌 320 | func (m *OrderMap[K, V]) Shuffle() { 321 | if m.size <= 1 { 322 | return 323 | } 324 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 325 | r.Shuffle(m.size, func(i, j int) { 326 | m.keyList[i], m.keyList[j] = m.keyList[j], m.keyList[i] 327 | }) 328 | } 329 | 330 | // SortDesc 根据k排序 desc 331 | func (m *OrderMap[K, V]) SortDesc() { 332 | sort.Slice(m.keyList, func(i, j int) bool { 333 | if i > j { 334 | return true 335 | } 336 | return false 337 | }) 338 | } 339 | 340 | // SortAsc 根据k排序 asc 341 | func (m *OrderMap[K, V]) SortAsc() { 342 | sort.Slice(m.keyList, func(i, j int) bool { 343 | if i < j { 344 | return true 345 | } 346 | return false 347 | }) 348 | } 349 | 350 | func (m *OrderMap[K, V]) CopyMap() map[K]V { 351 | temp := make(map[K]V, m.size) 352 | m.Range(func(k K, v V) { 353 | temp[k] = v 354 | }) 355 | return temp 356 | } 357 | 358 | // MysqlNewTable 给mysql提供创建新的固定map顺序为字段的表 359 | func (m *OrderMap[K, V]) MysqlNewTable(db Mysql, table string) error { 360 | var ( 361 | createSql bytes.Buffer 362 | line = m.Len() 363 | ) 364 | if table == "" { 365 | return TableIsNULL 366 | } 367 | if line < 1 { 368 | return fmt.Errorf("fiedls len is 0") 369 | } 370 | if db.DB == nil { 371 | _ = db.Conn() 372 | } 373 | createSql.WriteString("CREATE TABLE ") 374 | createSql.WriteString(table) 375 | createSql.WriteString(" ( temp_id int(11) NOT NULL AUTO_INCREMENT, ") 376 | m.Range(func(k K, v V) { 377 | createSql.WriteString(Any2String(k)) 378 | createSql.WriteString(" ") 379 | createSql.WriteString(dataType2Mysql(v)) 380 | createSql.WriteString(", ") 381 | }) 382 | createSql.WriteString("PRIMARY KEY (temp_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") 383 | _, err := db.DB.Exec(createSql.String()) 384 | if db.log { 385 | Info("[Sql] Exec : " + createSql.String()) 386 | if err != nil { 387 | Error("[Sql] Error : " + err.Error()) 388 | } 389 | } 390 | if db.allTN == nil { 391 | _ = db.allTableName() 392 | } 393 | db.allTN.add(table) 394 | return nil 395 | } 396 | 397 | // MysqlInsert 给mysql提供固定顺序map写入 398 | func (m *OrderMap[K, V]) MysqlInsert(db Mysql, table string) error { 399 | var ( 400 | line = m.Len() 401 | fieldDataMap = make(map[string]any) 402 | ) 403 | if table == "" { 404 | return TableIsNULL 405 | } 406 | if line < 1 { 407 | return fmt.Errorf("fiedls len is 0") 408 | } 409 | if db.DB == nil { 410 | _ = db.Conn() 411 | } 412 | if db.allTN == nil { 413 | _ = db.allTableName() 414 | } 415 | if !db.allTN.isHave(table) { 416 | err := m.MysqlNewTable(db, table) 417 | if err != nil { 418 | return err 419 | } 420 | } 421 | m.Range(func(k K, v V) { 422 | fieldDataMap[Any2String(k)] = v 423 | }) 424 | return db.insert(table, fieldDataMap) 425 | } 426 | 427 | type Set map[any]struct{} 428 | 429 | func NewSet() Set { 430 | return make(Set) 431 | } 432 | 433 | func (s Set) Has(key any) bool { 434 | _, ok := s[key] 435 | return ok 436 | } 437 | 438 | func (s Set) Add(key any) { 439 | s[key] = struct{}{} 440 | } 441 | 442 | func (s Set) Delete(key any) { 443 | delete(s, key) 444 | } 445 | 446 | func (s Set) DebugPrint() { 447 | for k := range s { 448 | Debug(k) 449 | } 450 | } 451 | 452 | type Stack[K comparable] struct { 453 | data map[int]K 454 | } 455 | 456 | func NewStack[K comparable]() *Stack[K] { 457 | return &Stack[K]{ 458 | data: make(map[int]K), 459 | } 460 | } 461 | 462 | func (s *Stack[K]) Push(data K) { 463 | s.data[len(s.data)] = data 464 | } 465 | 466 | func (s *Stack[K]) Pop() K { 467 | if len(s.data) < 1 { 468 | var k K 469 | return k 470 | } 471 | l := len(s.data) - 1 472 | k := s.data[l] 473 | delete(s.data, l) 474 | return k 475 | } 476 | 477 | func (s *Stack[K]) DebugPrint() { 478 | for i := 0; i < len(s.data); i++ { 479 | Debug(s.data) 480 | } 481 | } 482 | 483 | func MapCopy[K, V comparable](data map[K]V) (copy map[K]V) { 484 | copy = make(map[K]V, len(data)) 485 | for k, v := range data { 486 | copy[k] = v 487 | } 488 | return 489 | } 490 | 491 | func MapMergeCopy[K, V comparable](src ...map[K]V) (copy map[K]V) { 492 | copy = make(map[K]V) 493 | for _, m := range src { 494 | for k, v := range m { 495 | copy[k] = v 496 | } 497 | } 498 | return 499 | } 500 | 501 | // Map2Slice Eg: {"K1": "v1", "K2": "v2"} => ["K1", "v1", "K2", "v2"] 502 | func Map2Slice(data any) []any { 503 | var ( 504 | reflectValue = reflect.ValueOf(data) 505 | reflectKind = reflectValue.Kind() 506 | ) 507 | for reflectKind == reflect.Ptr { 508 | reflectValue = reflectValue.Elem() 509 | reflectKind = reflectValue.Kind() 510 | } 511 | switch reflectKind { 512 | case reflect.Map: 513 | array := make([]any, 0) 514 | for _, key := range reflectValue.MapKeys() { 515 | array = append(array, key.Interface()) 516 | array = append(array, reflectValue.MapIndex(key).Interface()) 517 | } 518 | return array 519 | default: 520 | return nil 521 | } 522 | } 523 | 524 | // Slice2Map ["K1", "v1", "K2", "v2"] => {"K1": "v1", "K2": "v2"} 525 | // ["K1", "v1", "K2"] => nil 526 | func Slice2Map(slice any) map[any]any { 527 | var ( 528 | reflectValue = reflect.ValueOf(slice) 529 | reflectKind = reflectValue.Kind() 530 | ) 531 | for reflectKind == reflect.Ptr { 532 | reflectValue = reflectValue.Elem() 533 | reflectKind = reflectValue.Kind() 534 | } 535 | switch reflectKind { 536 | case reflect.Slice, reflect.Array: 537 | length := reflectValue.Len() 538 | if length%2 != 0 { 539 | return nil 540 | } 541 | data := make(map[any]any) 542 | for i := 0; i < reflectValue.Len(); i += 2 { 543 | data[reflectValue.Index(i).Interface()] = reflectValue.Index(i + 1).Interface() 544 | } 545 | return data 546 | default: 547 | return nil 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | - gathertool是golang脚本化开发集成库,目的是提高爬虫,数据处理,接口测试,模拟请求等对应场景脚本程序开发的效率; 3 | - gathertool也是一款轻量级爬虫库,特色是抽象了请求事件,通俗点理解就是对请求过程状态进行事件处理。 4 | - gathertool也是接口测试&压力测试库,在接口测试脚本开发上有明显的效率优势, 5 | - gathertool还集成了对三方中间件的操作,DB操作等。 6 | 7 | --- 8 | 9 | ## 使用 10 | - go get github.com/mangenotwork/gathertool 11 | 12 | --- 13 | 14 | ## 介绍 15 | gathertool是一个高度封装工具库,包含了http/s的请求,Mysql数据库方法,数据类型处理方法,数据提取方法,websocket相关方法, 16 | TCP|UDP相关方法,NoSql相关方法,开发常用方法等; 可以用于爬虫程序,接口&压力测试程序,常见网络协议调试程序,数据提取与存储程序等; 17 | 18 | - gathertool的请求特点: 会在请求阶段执行各个事件如请求失败后的重试事件,请求前后的事件,请求成功事件等等, 可以根据请求状态码自定义这些事件; 19 | 20 | - gathertool还拥有很好的可扩展性, 适配传入任意自定义http请求对象, 能适配各种代理对象等等; 21 | 22 | - gathertool还拥有抓取数据存储功能, 存储到mysql;配合 https://github.com/mangenotwork/dbHelper 可存储到更多的数据存储上; 还有很多创新的地方文档会根据具体方法进行介绍; 23 | 24 | - gathertool还封装了消息队列接口,支持Nsq,Kafka,rabbitmq,redis等消息队列中间件 25 | 26 | ## 文档: 27 | [开发文档](https://github.com/mangenotwork/gathertool/blob/main/_doc/develop.md) 28 | 29 | [pkg.go.dev](https://pkg.go.dev/github.com/mangenotwork/gathertool) 30 | 31 | 32 | ## 开始使用 33 | > go get github.com/mangenotwork/gathertool 34 | 35 | 36 | #### 简单的get请求 37 | ```go 38 | import gt "github.com/mangenotwork/gathertool" 39 | 40 | func main(){ 41 | ctx, err := gt.Get("https://www.baidu.com") 42 | if err != nil { 43 | log.Println(err) 44 | } 45 | log.Println(ctx.RespBodyString) 46 | } 47 | ``` 48 | 49 | #### 含请求事件请求 50 | 51 | - StartFunc 开始事件,请求开始前执行的方法类型 52 | 53 | - SucceedFunc 成功事件,请求成功后执行的方法类型 54 | 55 | - FailedFunc 失败事件,请求失败执行的方法类型 56 | 57 | - RetryFunc 重试事件,重试请求前执行的方法类型,是否重试来自上次请求的状态码来确定,见StatusCodeMap; 58 | 59 | - EndFunc 结束事件,请求流程结束后执行的方法类型 60 | 61 | ```go 62 | import gt "github.com/mangenotwork/gathertool" 63 | 64 | func main(){ 65 | 66 | gt.NewGet(`http://192.168.0.1`).SetStartFunc(func(ctx *gt.Context){ 67 | log.Println("请求前: 添加代理等等操作") 68 | ctx.Client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)} 69 | } 70 | ).SetSucceedFunc(func(ctx *gt.Context){ 71 | log.Println("请求成功: 处理数据或存储等等") 72 | log.Println(ctx.RespBodyString()) 73 | } 74 | ).SetFailedFunc(func(ctx *gt.Context){ 75 | log.Println("请求失败: 记录失败等等") 76 | log.Println(ctx.Err) 77 | } 78 | ).SetRetryFunc(func(ctx *gt.Context){ 79 | log.Println("请求重试: 更换代理或添加等待时间等等") 80 | ctx.Client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)} 81 | } 82 | ).SetEndFunc(func(ctx *gt.Context){ 83 | log.Println("请求结束: 记录结束,处理数据等等") 84 | log.Println(ctx) 85 | } 86 | ).Do() 87 | 88 | } 89 | ``` 90 | 91 | #### 事件方法复用 92 | ```go 93 | func main(){ 94 | gt.NewGet(`http://192.168.0.1`).SetSucceedFunc(succeed).SetFailedFunc(failed).SetRetryFunc(retry).Do() 95 | gt.NewGet(`http://www.baidu.com`).SetSucceedFunc(baiduSucceed).SetFailedFunc(failed).SetRetryFunc(retry).Do() 96 | } 97 | // 请求成功: 处理数据或存储等等 98 | func succeed(ctx *gt.Context){ 99 | log.Println(ctx.RespBodyString()) 100 | } 101 | // 请求失败: 记录失败等等 102 | func failed(ctx *gt.Context){ 103 | log.Println(ctx.Err) 104 | } 105 | // 请求重试: 更换代理或添加等待时间等等 106 | func retry(ctx *gt.Context){ 107 | ctx.Client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)} 108 | } 109 | // 百度请求成功后 110 | func baiduSucceed(ctx *gt.Context){ 111 | log.Println(ctx.RespBodyString()) 112 | } 113 | ``` 114 | 115 | #### post请求 116 | ```go 117 | // FormData 118 | postData := gt.FormData{ 119 | "aa":"aa", 120 | } 121 | 122 | // header 123 | header := gt.NewHeader(map[string]string{ 124 | "Accept":"*/*", 125 | "X-MicrosoftAjax":"Delta=true", 126 | "Accept-Encoding":"gzip, deflate", 127 | "XMLHttpRequest":"XMLHttpRequest", 128 | "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", 129 | }) 130 | 131 | // cookie 132 | cookie := gt.Cookie{ 133 | "aa":"a" 134 | } 135 | 136 | // 随机休眠 2~6秒 137 | sleep := gt.SetSleep(2,6) 138 | c := gt.NewPostForm(caseUrl, postData, header, cookie, sleep) 139 | c.Do() 140 | html := c.RespBodyString() 141 | log.Print(html) 142 | 143 | ``` 144 | 145 | #### 数据存储到mysql 146 | ```go 147 | var ( 148 | host = "192.168.0.100" 149 | port = 3306 150 | user = "root" 151 | password = "root123" 152 | dbName = "dbName" 153 | db,_ = gt.NewMysql(host, port, user, password, dbName) 154 | ) 155 | 156 | //.... 执行抓取 157 | data1 := "data1" 158 | data2 := "data2" 159 | 160 | inputdata := map[string]interface{} { 161 | "data1" : data1, 162 | "data2" : data2, 163 | } 164 | 165 | tableName := "data" 166 | db.Spider2022DB.InsertAt(tableName, inputdata) 167 | ``` 168 | 169 | #### HTML数据提取 170 | ```go 171 | func main(){ 172 | date := "2022-07-05" 173 | caseUrl := "***" 174 | ctx, _ := gt.Get(fmt.Sprintf(caseUrl, date)) 175 | datas, _ := gt.GetPointHTML(ctx.Html, "div", "id", "domestic") 176 | Data(datas, date, "内期表", "备注:内期表=国内期货主力合约表") 177 | datas, _ = gt.GetPointHTML(ctx.Html, "div", "id", "overseas") 178 | Data(datas, date, "外期表", "备注:外期表=国外期货主力合约表") 179 | } 180 | 181 | func Data(datas []string, date, typeName, note string) { 182 | for _, data := range datas { 183 | table, _ := gt.GetPointHTML(data, "table", "id", "fdata") 184 | if len(table) > 0 { 185 | trList := gt.RegHtmlTr(table[0]) 186 | jys := "" 187 | for _, tr := range trList { 188 | td := gt.RegHtmlTd(tr) 189 | log.Println("td = ", td, len(td)) 190 | if len(td) == 1 { 191 | jys = gt.RegHtmlTdTxt(td[0])[0] 192 | continue 193 | } 194 | name := gt.RegHtmlTdTxt(td[0])[0] 195 | if strings.Index(name, "商品名称") != -1 { 196 | continue 197 | } 198 | zlhy := gt.RegHtmlTdTxt(td[1])[0] 199 | jsj := gt.RegHtmlTdTxt(td[2])[0] 200 | zd := gt.RegDelHtml(gt.RegHtmlTdTxt(td[3])[0]) 201 | cjj := gt.RegHtmlTdTxt(td[4])[0] 202 | ccl := gt.RegHtmlTdTxt(td[5])[0] 203 | dw := gt.RegHtmlTdTxt(td[6])[0] 204 | log.Println("日期 = ", date) 205 | log.Println("机构 = ", jys) 206 | log.Println("商品名称 = ", name) 207 | log.Println("主力合约 = ", zlhy) 208 | log.Println("结算价 = ", jsj) 209 | log.Println("涨跌 = ", zd) 210 | log.Println("成交量 = ", cjj) 211 | log.Println("持仓量 = ", ccl) 212 | log.Println("单位 = ", dw) 213 | } 214 | } 215 | } 216 | } 217 | ``` 218 | 219 | #### Json数据提取 220 | ```go 221 | func main(){ 222 | txt := `{ 223 | "reason":"查询成功!", 224 | "result":{ 225 | "city":"苏州", 226 | "realtime":{ 227 | "temperature":"17", 228 | "humidity":"69", 229 | "info":"阴", 230 | "wid":"02", 231 | "direct":"东风", 232 | "power":"2级", 233 | "aqi":"30" 234 | }, 235 | "future":[ 236 | { 237 | "date":"2021-10-25", 238 | "temperature":"12\/21℃", 239 | "weather":"多云", 240 | "wid":{ 241 | "day":"01", 242 | "night":"01" 243 | }, 244 | "direct":"东风" 245 | }, 246 | { 247 | "date":"2021-10-26", 248 | "temperature":"13\/21℃", 249 | "weather":"多云", 250 | "wid":{ 251 | "day":"01", 252 | "night":"01" 253 | }, 254 | "direct":"东风转东北风" 255 | }, 256 | { 257 | "date":"2021-10-27", 258 | "temperature":"13\/22℃", 259 | "weather":"多云", 260 | "wid":{ 261 | "day":"01", 262 | "night":"01" 263 | }, 264 | "direct":"东北风" 265 | } 266 | ] 267 | }, 268 | "error_code":0 269 | }` 270 | 271 | jx1 := "/result/future/[0]/date" 272 | jx2 := "/result/future/[0]" 273 | jx3 := "/result/future" 274 | 275 | log.Println(gt.JsonFind(txt, jx1)) 276 | log.Println(gt.JsonFind2Json(txt, jx2)) 277 | log.Println(gt.JsonFind2Json(txt, jx3)) 278 | log.Println(gt.JsonFind2Map(txt, jx2)) 279 | log.Println(gt.JsonFind2Arr(txt, jx3)) 280 | 281 | } 282 | ``` 283 | 284 | ...... 285 | 286 | ## 实例 287 | - [Get请求](https://github.com/mangenotwork/gathertool/tree/main/_examples/get) 288 | - [阳光高考招生章程抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/get_yggk) 289 | - [ip地址信息抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/ip_bczs_cn) 290 | - [压力测试](https://github.com/mangenotwork/gathertool/tree/main/_examples/stress_testing) 291 | - [文件下载](https://github.com/mangenotwork/gathertool/tree/main/_examples/upload_file) 292 | - [无登录微博抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/weibo) 293 | - [百度题库抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/baidu_tk) 294 | - [搭建http/s代理与抓包](https://github.com/mangenotwork/gathertool/tree/main/_examples/intercept) 295 | - [搭建socket5代理](https://github.com/mangenotwork/gathertool/tree/main/_examples/socket5) 296 | - [商品报价信息抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/baojia) 297 | - [期货信息抓取](https://github.com/mangenotwork/gathertool/tree/main/_examples/qihuo) 298 | - [行业信息网行业分类](https://github.com/mangenotwork/gathertool/tree/main/_examples/cnlinfo) 299 | 300 | ## JetBrains 开源证书支持 301 | 302 | `gathertool` 项目一直以来都是在 JetBrains 公司旗下的 `GoLand` 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。 303 | 304 | 305 | 306 | 307 | ## 使用注意&不足之处 308 | 1. Mysql相关方法封装使用的字符串拼接,如抓取无需关心Sql注入这类场景放心使用,生产环境使用会出现安全风险; 309 | 2. 变量类型转换使用反射,性能方面会差一些,追求性能请使用其他; 310 | 311 | 312 | ## 经验积累 313 | 从2022年v0.3.4版本到至今已经应用在我的实际工作中,给边边角角的工作带来了巨大的脚步编写效率,经过不断的积累和总结质量也在不断的提示;感兴趣的伙伴可以fork代码, 314 | 将你的宝贵经验提交进来。 315 | 316 | ## 配合使用 317 | - 数据存储与读取 https://github.com/mangenotwork/dbHelper 318 | - 图片处理 https://github.com/mangenotwork/imgHelper 319 | 320 | ## TODO 321 | - Redis连接方法改为连接池, 干掉redis队列 322 | - 关闭重试 323 | - 提供通过html定位加正则获取内容的方法 324 | - 引入请求工作与队列的功能,在广度爬取场景使用 325 | 326 | ## BUG 327 | - 固定map调用不了 .\main.go:124:16: undefined: gt.OrderMap 328 | - panic 329 | ```azure 330 | panic: runtime error: index out of range [1] with length 1 331 | 332 | goroutine 1 [running]: 333 | github.com/mangenotwork/gathertool.RegFindAllTxt({0xb042f8?, 0x1?}, {0xc00020c000, 0x3fe5}) 334 | D:/go/pkg/mod/github.com/mangenotwork/gathertool@v0.4.7/reg.go:32 +0x135 335 | ``` 336 | 337 | 338 | ## 三方引用 感谢这些开源项目 339 | - github.com/Shopify/sarama 340 | - github.com/dgrijalva/jwt-go 341 | - github.com/go-sql-driver/mysql 342 | - github.com/nsqio/go-nsq 343 | - github.com/streadway/amqp 344 | - github.com/xuri/excelize/v2 345 | - golang.org/x/crypto 346 | - golang.org/x/net 347 | - golang.org/x/text 348 | - gopkg.in/gomail.v2 349 | - gopkg.in/yaml.v3 350 | 351 | ## 里程碑 352 | 353 | #### v0.4.8 354 | ``` 355 | 1. 升级到go版本1.19并进行优化 356 | 2. 多类型重复的方法改为泛型,减轻代码量 357 | 3. 增加测试代码和整理实例 358 | 4. 优化项目文件结构,规范库的注释 359 | 5. 更新和优化文档 360 | 6. 修复 panic 361 | 7. 修复问题: 362 | - Any2Float64 不支持字符串转float64 363 | - 下载的函数名错误 Upload 执行GET下载请求 364 | - 缺少url提取文件名,文件后缀的函数 365 | - 类型转换缺少空值判断 366 | 8. 新增: 367 | - 正则提取增加 section, dl, dt, dd 标签 368 | - 去掉 gorm.io/gorm, mongo-driver, github.com/gomodule/redigo 369 | - html解析需要引入属性值包含关系 370 | - 下载资源需要打印下载url, 以及检查下载大小,如果是0则抛出错误 371 | ``` 372 | 373 | #### v0.4.7 374 | ``` 375 | 1. Redis连接方法改为连接池 376 | 2. 增加关闭重试方法 377 | 3. 增加Whois查询 378 | 4. 测试与优化 379 | ``` 380 | 381 | #### v0.4.4 ~ v0.4.6 382 | ``` 383 | 1. 移除 grpc相关方法 384 | 2. 新增DNS查询 385 | 3. 新增证书信息获取 386 | 4. 新增Url扫描 387 | 5. 新增邮件发送 388 | 6. 优化ICMP Ping的方法 389 | ``` 390 | 391 | #### v0.4.3 392 | ``` 393 | 1. 新增网站链接采集应用场景的方法 394 | 2. 修复v0.4.2的json导入bug 395 | 3. 修复redis封装方法的bug 396 | 4. 请求日志添加请求地址信息 397 | 5. 优化抓取实例 398 | ``` 399 | 400 | #### v0.4.2 401 | ``` 402 | 新增: 403 | 1. redis, nsq, rabbitmq, kafka 消息队列方法 404 | 2. 新增开发文档 405 | 3. 新增redis相关方法 406 | ``` 407 | 408 | #### v0.4.1 409 | ``` 410 | 新增: 411 | 1. 文件相关处理 412 | 2. 文件压缩解压 413 | 3. 新增抓取实列 _examples/cnlinfo 414 | ``` 415 | 416 | #### v0.2.1 ~ v0.3.9 417 | ``` 418 | 旧的迭代版本,不稳定,请使用 > v0.3.4的版本 419 | - 新增代理与抓包 420 | - 启动一个代理服务,并抓包对数据包进行处理 421 | - 新增socket5代理 422 | 423 | v0.3.6 424 | 新增: 425 | 1. func SimpleServer(addr string, g *grpc.Server) : grpc服务端 426 | 2. func NewTask() *Task : task的实例方法 427 | 3. func (task *Task) SetUrl(urlStr string) *Task & func (task *Task) SetJsonParam(jsonStr string) *Task 428 | 429 | 修复: 430 | 1. Context对象,Task为空的问题 431 | 432 | v0.3.7 433 | 新增: 434 | 1. 新增html解析,指定html内容提取 435 | 2. 新增抓取实例 436 | 3. 优化部分方法 437 | 438 | v0.3.9 439 | 新增: 440 | 1. 新增配置,支持yaml 441 | 2. 优化部分方法 442 | ``` 443 | 444 | 445 | 446 | -------------------------------------------------------------------------------- /crypto_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 加密解码相关封装方法 TODO 测试 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "crypto/aes" 12 | "crypto/cipher" 13 | "crypto/des" 14 | "crypto/hmac" 15 | "crypto/md5" 16 | "crypto/rand" 17 | "crypto/sha1" 18 | "crypto/sha256" 19 | "crypto/sha512" 20 | "encoding/base64" 21 | "errors" 22 | "fmt" 23 | "hash" 24 | "io" 25 | 26 | "github.com/dgrijalva/jwt-go" 27 | "golang.org/x/crypto/pbkdf2" 28 | ) 29 | 30 | const ( 31 | CBC = "CBC" 32 | ECB = "ECB" 33 | CFB = "CFB" 34 | CTR = "CTR" 35 | ) 36 | 37 | // AES AES interface 38 | type AES interface { 39 | Encrypt(str, key []byte) ([]byte, error) 40 | Decrypt(str, key []byte) ([]byte, error) 41 | } 42 | 43 | // DES DES interface 44 | type DES interface { 45 | Encrypt(str, key []byte) ([]byte, error) 46 | Decrypt(str, key []byte) ([]byte, error) 47 | } 48 | 49 | // NewAES : use NewAES(AES_CBC) 50 | func NewAES(typeName string, arg ...[]byte) AES { 51 | iv := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} 52 | if len(arg) != 0 { 53 | iv = arg[0] 54 | } 55 | switch typeName { 56 | case "cbc", "Cbc", CBC: 57 | return &cbcObj{ 58 | cryptoType: "aes", 59 | iv: iv, 60 | } 61 | case "ecb", "Ecb", ECB: 62 | return &ecbObj{ 63 | cryptoType: "aes", 64 | } 65 | case "cfb", "Cfb", CFB: 66 | return &cfbObj{ 67 | cryptoType: "aes", 68 | } 69 | case "ctr", "Ctr", CTR: 70 | return &ctrObj{ 71 | cryptoType: "aes", 72 | count: iv, 73 | } 74 | default: 75 | return &cbcObj{ 76 | iv: iv, 77 | } 78 | } 79 | } 80 | 81 | // NewDES : use NewDES(DES_CBC) 82 | func NewDES(typeName string, arg ...[]byte) DES { 83 | iv := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} 84 | if len(arg) != 0 { 85 | iv = arg[0] 86 | } 87 | switch typeName { 88 | case "cbc", "Cbc", CBC: 89 | return &cbcObj{ 90 | cryptoType: "des", 91 | iv: iv, 92 | } 93 | case "ecb", "Ecb", ECB: 94 | return &ecbObj{ 95 | cryptoType: "des", 96 | } 97 | case "cfb", "Cfb", CFB: 98 | return &cfbObj{ 99 | cryptoType: "des", 100 | } 101 | case "ctr", "Ctr", CTR: 102 | return &ctrObj{ 103 | count: iv, 104 | cryptoType: "des", 105 | } 106 | default: 107 | return &cbcObj{ 108 | iv: iv, 109 | cryptoType: "des", 110 | } 111 | } 112 | } 113 | 114 | // CBC : 密码分组链接模式(Cipher Block Chaining (CBC)) default 115 | type cbcObj struct { 116 | cryptoType string 117 | iv []byte 118 | } 119 | 120 | func (cbc *cbcObj) getBlock(key []byte) (block cipher.Block, err error) { 121 | if cbc.cryptoType == "aes" { 122 | block, err = aes.NewCipher(key) 123 | } 124 | if cbc.cryptoType == "des" { 125 | block, err = des.NewCipher(key) 126 | } 127 | return 128 | } 129 | 130 | // Encrypt AES CBC Encrypt 131 | func (cbc *cbcObj) Encrypt(str, key []byte) ([]byte, error) { 132 | block, err := cbc.getBlock(key) 133 | if err != nil { 134 | Error("[" + cbc.cryptoType + "-CBC] ERROR:" + err.Error()) 135 | return []byte(""), err 136 | } 137 | blockSize := block.BlockSize() 138 | originData := cbc.pkcs5Padding(str, blockSize) 139 | blockMode := cipher.NewCBCEncrypter(block, cbc.iv) 140 | cryptData := make([]byte, len(originData)) 141 | blockMode.CryptBlocks(cryptData, originData) 142 | P2E() 143 | return cryptData, nil 144 | } 145 | 146 | // Decrypt AES CBC Decrypt 147 | func (cbc *cbcObj) Decrypt(str, key []byte) ([]byte, error) { 148 | block, err := cbc.getBlock(key) 149 | if err != nil { 150 | Error("[" + cbc.cryptoType + "-CBC] ERROR:" + err.Error()) 151 | return []byte(""), err 152 | } 153 | blockMode := cipher.NewCBCDecrypter(block, cbc.iv) 154 | originStr := make([]byte, len(str)) 155 | blockMode.CryptBlocks(originStr, str) 156 | P2E() 157 | return cbc.pkcs5UnPadding(originStr), nil 158 | } 159 | 160 | func (cbc *cbcObj) pkcs5Padding(ciphertext []byte, blockSize int) []byte { 161 | padding := blockSize - len(ciphertext)%blockSize 162 | padText := bytes.Repeat([]byte{byte(padding)}, padding) 163 | return append(ciphertext, padText...) 164 | } 165 | 166 | func (cbc *cbcObj) pkcs5UnPadding(origData []byte) []byte { 167 | length := len(origData) 168 | unPadDing := int(origData[length-1]) 169 | return origData[:(length - unPadDing)] 170 | } 171 | 172 | // ECB : 电码本模式(Electronic Codebook Book (ECB)) 173 | type ecbObj struct { 174 | cryptoType string 175 | } 176 | 177 | func (ecb *ecbObj) getBlock(key []byte) (block cipher.Block, err error) { 178 | if ecb.cryptoType == "aes" { 179 | block, err = aes.NewCipher(ecb.generateKey(key)) 180 | } 181 | if ecb.cryptoType == "des" { 182 | block, err = des.NewCipher(key) 183 | } 184 | return 185 | } 186 | 187 | // Encrypt AES ECB Encrypt 188 | func (ecb *ecbObj) Encrypt(str, key []byte) ([]byte, error) { 189 | block, err := ecb.getBlock(key) 190 | if err != nil { 191 | Error("[" + ecb.cryptoType + "-ECB] ERROR:" + err.Error()) 192 | return []byte(""), err 193 | } 194 | blockSize := block.BlockSize() 195 | if ecb.cryptoType == "aes" { 196 | str = ecb.pkcs5PaddingAes(str, blockSize) 197 | } 198 | if ecb.cryptoType == "des" { 199 | str = ecb.pkcs5PaddingDes(str, blockSize) 200 | } 201 | 202 | //返回加密结果 203 | encryptData := make([]byte, len(str)) 204 | //存储每次加密的数据 205 | tmpData := make([]byte, blockSize) 206 | //分组分块加密 207 | for index := 0; index < len(str); index += blockSize { 208 | block.Encrypt(tmpData, str[index:index+blockSize]) 209 | copy(encryptData, tmpData) 210 | } 211 | P2E() 212 | return encryptData, nil 213 | } 214 | 215 | func (ecb *ecbObj) pkcs5PaddingDes(ciphertext []byte, blockSize int) []byte { 216 | padding := blockSize - len(ciphertext)%blockSize 217 | padText := bytes.Repeat([]byte{byte(padding)}, padding) 218 | return append(ciphertext, padText...) 219 | } 220 | 221 | func (ecb *ecbObj) pkcs5PaddingAes(ciphertext []byte, blockSize int) []byte { 222 | padding := blockSize - len(ciphertext)%blockSize 223 | if padding != 0 { 224 | ciphertext = append(ciphertext, bytes.Repeat([]byte{byte(0)}, padding)...) 225 | } 226 | return ciphertext 227 | } 228 | 229 | // Decrypt AES ECB Decrypt 230 | func (ecb *ecbObj) Decrypt(str, key []byte) ([]byte, error) { 231 | block, err := ecb.getBlock(key) 232 | if err != nil { 233 | Error("[" + ecb.cryptoType + "-ECB] ERROR:" + err.Error()) 234 | return []byte(""), err 235 | } 236 | blockSize := block.BlockSize() 237 | //返回加密结果 238 | decryptData := make([]byte, len(str)) 239 | //存储每次加密的数据 240 | tmpData := make([]byte, blockSize) 241 | 242 | //分组分块加密 243 | for index := 0; index < len(str); index += blockSize { 244 | block.Decrypt(tmpData, str[index:index+blockSize]) 245 | copy(decryptData, tmpData) 246 | } 247 | 248 | if ecb.cryptoType == "des" { 249 | return ecb.pkcs5UnPadding(decryptData), nil 250 | } 251 | P2E() 252 | return ecb.unPadding(decryptData), nil 253 | } 254 | 255 | func (ecb *ecbObj) generateKey(key []byte) (genKey []byte) { 256 | genKey = make([]byte, 16) 257 | copy(genKey, key) 258 | for i := 16; i < len(key); { 259 | for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { 260 | genKey[j] ^= key[i] 261 | } 262 | } 263 | return genKey 264 | } 265 | 266 | func (ecb *ecbObj) unPadding(src []byte) []byte { 267 | for i := len(src) - 1; ; i-- { 268 | if src[i] != 0 { 269 | return src[:i+1] 270 | } 271 | } 272 | } 273 | 274 | func (ecb *ecbObj) pkcs5UnPadding(origData []byte) []byte { 275 | length := len(origData) 276 | unPadding := int(origData[length-1]) 277 | return origData[:(length - unPadding)] 278 | } 279 | 280 | // 密码反馈模式(Cipher FeedBack (CFB)) 281 | type cfbObj struct { 282 | cryptoType string 283 | } 284 | 285 | func (cfb *cfbObj) getBlock(key []byte) (block cipher.Block, err error) { 286 | if cfb.cryptoType == "aes" { 287 | block, err = aes.NewCipher(key) 288 | } 289 | if cfb.cryptoType == "des" { 290 | block, err = des.NewCipher(key) 291 | } 292 | return 293 | } 294 | 295 | // Encrypt AES CFB Encrypt 296 | func (cfb *cfbObj) Encrypt(str, key []byte) ([]byte, error) { 297 | P2E() 298 | block, err := cfb.getBlock(key) 299 | if err != nil { 300 | Error("[" + cfb.cryptoType + "-CFB] ERROR:" + err.Error()) 301 | return nil, err 302 | } 303 | 304 | if cfb.cryptoType == "aes" { 305 | encrypted := make([]byte, aes.BlockSize+len(str)) 306 | iv := encrypted[:aes.BlockSize] 307 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 308 | return nil, err 309 | } 310 | stream := cipher.NewCFBEncrypter(block, iv) 311 | stream.XORKeyStream(encrypted[aes.BlockSize:], str) 312 | return encrypted, nil 313 | } 314 | 315 | if cfb.cryptoType == "des" { 316 | encrypted := make([]byte, des.BlockSize+len(str)) 317 | iv := encrypted[:des.BlockSize] 318 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 319 | return nil, err 320 | } 321 | stream := cipher.NewCFBEncrypter(block, iv) 322 | stream.XORKeyStream(encrypted[des.BlockSize:], str) 323 | return encrypted, nil 324 | } 325 | return nil, nil 326 | } 327 | 328 | // Decrypt AES CFB Decrypt 329 | func (cfb *cfbObj) Decrypt(str, key []byte) ([]byte, error) { 330 | P2E() 331 | block, err := cfb.getBlock(key) 332 | if err != nil { 333 | Error("[" + cfb.cryptoType + "-CFB] ERROR:" + err.Error()) 334 | return nil, err 335 | } 336 | 337 | iv := make([]byte, 0) 338 | if cfb.cryptoType == "aes" { 339 | if len(str) < aes.BlockSize { 340 | return nil, errors.New("ciphertext too short") 341 | } 342 | iv = str[:aes.BlockSize] 343 | str = str[aes.BlockSize:] 344 | } 345 | 346 | if cfb.cryptoType == "des" { 347 | if len(str) < des.BlockSize { 348 | return nil, errors.New("ciphertext too short") 349 | } 350 | iv = str[:des.BlockSize] 351 | str = str[des.BlockSize:] 352 | } 353 | 354 | stream := cipher.NewCFBDecrypter(block, iv) 355 | stream.XORKeyStream(str, str) 356 | return str, nil 357 | } 358 | 359 | // 计算器模式(Counter (CTR)) 360 | type ctrObj struct { 361 | count []byte //指定计数器,长度必须等于block的块尺寸 362 | cryptoType string 363 | } 364 | 365 | func (ctr *ctrObj) getBlock(key []byte) (block cipher.Block, err error) { 366 | if ctr.cryptoType == "aes" { 367 | block, err = aes.NewCipher(key) 368 | } 369 | if ctr.cryptoType == "des" { 370 | block, err = des.NewCipher(key) 371 | if len(ctr.count) > des.BlockSize { 372 | ctr.count = ctr.count[0:des.BlockSize] 373 | } 374 | } 375 | return 376 | } 377 | 378 | // Encrypt AES CTR Encrypt 379 | func (ctr *ctrObj) Encrypt(str, key []byte) ([]byte, error) { 380 | return ctr.crypto(str, key) 381 | } 382 | 383 | // Decrypt AES CTR Decrypt 384 | func (ctr *ctrObj) Decrypt(str, key []byte) ([]byte, error) { 385 | return ctr.crypto(str, key) 386 | } 387 | 388 | func (ctr *ctrObj) crypto(str, key []byte) ([]byte, error) { 389 | P2E() 390 | block, err := ctr.getBlock(key) 391 | if err != nil { 392 | Error("[AES-CTR] ERROR:" + err.Error()) 393 | return []byte(""), err 394 | } 395 | //指定分组模式 396 | blockMode := cipher.NewCTR(block, ctr.count) 397 | //执行加密、解密操作 398 | res := make([]byte, len(str)) 399 | blockMode.XORKeyStream(res, str) 400 | //返回明文或密文 401 | return res, nil 402 | } 403 | 404 | // 输出反馈模式(Output FeedBack (OFB)) 405 | type ofbObj struct{} 406 | 407 | func hmacFunc(h func() hash.Hash, str, key []byte) string { 408 | mac := hmac.New(h, key) 409 | mac.Write(str) 410 | res := base64.StdEncoding.EncodeToString(mac.Sum(nil)) 411 | return res 412 | } 413 | 414 | // HmacMD5 hmac md5 415 | func HmacMD5(str, key string) string { 416 | return hmacFunc(md5.New, []byte(str), []byte(key)) 417 | } 418 | 419 | // HmacSHA1 hmac sha1 420 | func HmacSHA1(str, key string) string { 421 | return hmacFunc(sha1.New, []byte(str), []byte(key)) 422 | } 423 | 424 | // HmacSHA256 hmac sha256 425 | func HmacSHA256(str, key string) string { 426 | return hmacFunc(sha256.New, []byte(str), []byte(key)) 427 | } 428 | 429 | // HmacSHA512 hmac sha512 430 | func HmacSHA512(str, key string) string { 431 | return hmacFunc(sha512.New, []byte(str), []byte(key)) 432 | } 433 | 434 | func pbkdf2Func(h func() hash.Hash, str, salt []byte, iterations, keySize int) []byte { 435 | return pbkdf2.Key(str, salt, iterations, keySize, h) 436 | } 437 | 438 | func PBKDF2(str, salt []byte, iterations, keySize int) []byte { 439 | return pbkdf2Func(sha256.New, str, salt, iterations, keySize) 440 | } 441 | 442 | // jwtEncrypt 443 | func jwtEncrypt(token *jwt.Token, data map[string]any, secret string) (string, error) { 444 | claims := make(jwt.MapClaims) 445 | for k, v := range data { 446 | claims[k] = v 447 | } 448 | token.Claims = claims 449 | return token.SignedString([]byte(secret)) 450 | } 451 | 452 | // JwtEncrypt jwt Encrypt 453 | func JwtEncrypt(data map[string]any, secret, method string) (string, error) { 454 | switch method { 455 | case "256": 456 | return jwtEncrypt(jwt.New(jwt.SigningMethodHS256), data, secret) 457 | case "384": 458 | return jwtEncrypt(jwt.New(jwt.SigningMethodHS384), data, secret) 459 | case "512": 460 | return jwtEncrypt(jwt.New(jwt.SigningMethodHS512), data, secret) 461 | } 462 | return "", fmt.Errorf("未知method; method= 256 or 384 or 512 ") 463 | } 464 | 465 | func JwtEncrypt256(data map[string]any, secret string) (string, error) { 466 | token := jwt.New(jwt.SigningMethodHS256) 467 | return jwtEncrypt(token, data, secret) 468 | } 469 | 470 | func JwtEncrypt384(data map[string]any, secret string) (string, error) { 471 | token := jwt.New(jwt.SigningMethodHS384) 472 | return jwtEncrypt(token, data, secret) 473 | } 474 | 475 | func JwtEncrypt512(data map[string]any, secret string) (string, error) { 476 | token := jwt.New(jwt.SigningMethodHS512) 477 | return jwtEncrypt(token, data, secret) 478 | } 479 | 480 | // JwtDecrypt jwt decrypt 481 | func JwtDecrypt(tokenString, secret string) (data map[string]any, err error) { 482 | data = make(map[string]any) 483 | var secretFunc = func() jwt.Keyfunc { //按照这样的规则解析 484 | return func(t *jwt.Token) (any, error) { 485 | return []byte(secret), nil 486 | } 487 | } 488 | token, err := jwt.Parse(tokenString, secretFunc()) 489 | if err != nil { 490 | err = fmt.Errorf("未知Token") 491 | return 492 | } 493 | claim, ok := token.Claims.(jwt.MapClaims) 494 | if !ok { 495 | return 496 | } 497 | if !token.Valid { 498 | // 令牌错误 499 | return 500 | } 501 | for k, v := range claim { 502 | data[k] = v 503 | } 504 | return 505 | } 506 | -------------------------------------------------------------------------------- /string_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 字符串,字符编码等等相关的操作方法 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "compress/gzip" 12 | "crypto/md5" 13 | "encoding/base64" 14 | "encoding/binary" 15 | "encoding/gob" 16 | "encoding/hex" 17 | "fmt" 18 | "golang.org/x/text/encoding" 19 | "golang.org/x/text/encoding/ianaindex" 20 | "golang.org/x/text/encoding/simplifiedchinese" 21 | "golang.org/x/text/transform" 22 | "io" 23 | "log" 24 | "strconv" 25 | "strings" 26 | "unicode/utf8" 27 | ) 28 | 29 | // CleaningStr 清理字符串前后空白,回车,换行符号 30 | func CleaningStr(str string) string { 31 | str = strings.Replace(str, "\n", "", -1) 32 | str = strings.Replace(str, "\r", "", -1) 33 | str = strings.Replace(str, "\\n", "", -1) 34 | str = strings.TrimSpace(str) 35 | str = StrDeleteSpace(str) 36 | return str 37 | } 38 | 39 | // StrDeleteSpace 删除字符串前后的空格 40 | func StrDeleteSpace(str string) string { 41 | strList := []byte(str) 42 | spaceCount, count := 0, len(strList) 43 | for i := 0; i <= len(strList)-1; i++ { 44 | if strList[i] == 32 { 45 | spaceCount++ 46 | } else { 47 | break 48 | } 49 | } 50 | 51 | strList = strList[spaceCount:] 52 | spaceCount, count = 0, len(strList) 53 | for i := count - 1; i >= 0; i-- { 54 | if strList[i] == 32 { 55 | spaceCount++ 56 | } else { 57 | break 58 | } 59 | } 60 | 61 | return string(strList[:count-spaceCount]) 62 | } 63 | 64 | // EncodeByte 任意类型编码为[]byte 65 | func EncodeByte(v any) []byte { 66 | switch value := v.(type) { 67 | case int, int8, int16, int32: 68 | return Int2Byte(value.(int)) 69 | case int64: 70 | return Int642Byte(value) 71 | case string: 72 | return Str2Byte(value) 73 | case bool: 74 | return Bool2Byte(value) 75 | case float32: 76 | return Float322Byte(value) 77 | case float64: 78 | return Float642Byte(value) 79 | } 80 | return []byte("") 81 | } 82 | 83 | // DecodeByte decode byte 84 | func DecodeByte(b []byte) ([]byte, error) { 85 | rse := make([]byte, 0) 86 | buf := bytes.NewBuffer(b) 87 | err := binary.Read(buf, binary.BigEndian, rse) 88 | return rse, err 89 | } 90 | 91 | func deepCopy[T any](dst, src T) error { 92 | var buf bytes.Buffer 93 | if err := gob.NewEncoder(&buf).Encode(src); err != nil { 94 | return err 95 | } 96 | return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst) 97 | } 98 | 99 | // DeepCopy 深copy 100 | func DeepCopy[T any](dst, src T) error { 101 | return deepCopy(dst, src) 102 | } 103 | 104 | // PanicToError panic -> error 105 | func PanicToError(fn func()) (err error) { 106 | defer func() { 107 | if r := recover(); r != nil { 108 | err = fmt.Errorf("panic error: %v", r) 109 | } 110 | }() 111 | fn() 112 | return 113 | } 114 | 115 | // P2E panic -> error 116 | func P2E() { 117 | defer func() { 118 | if r := recover(); r != nil { 119 | Error("Panic error: %v", r) 120 | } 121 | }() 122 | } 123 | 124 | // Charset 字符集类型 125 | type Charset string 126 | 127 | const ( 128 | UTF8 = Charset("UTF-8") 129 | GB18030 = Charset("GB18030") 130 | GBK = Charset("GBK") 131 | GB2312 = Charset("GB2312") 132 | ) 133 | 134 | // ConvertByte2String 编码转换 135 | func ConvertByte2String(byte []byte, charset Charset) string { 136 | var str string 137 | switch charset { 138 | case GB18030: 139 | var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte) 140 | str = string(decodeBytes) 141 | 142 | case GBK: 143 | var decodeBytes, _ = simplifiedchinese.GBK.NewDecoder().Bytes(byte) 144 | str = string(decodeBytes) 145 | 146 | case GB2312: 147 | var decodeBytes, _ = simplifiedchinese.HZGB2312.NewDecoder().Bytes(byte) 148 | str = string(decodeBytes) 149 | 150 | case UTF8: 151 | fallthrough 152 | 153 | default: 154 | str = string(byte) 155 | } 156 | 157 | return str 158 | } 159 | 160 | // UnicodeDec Unicode编码 161 | func UnicodeDec(raw string) string { 162 | str, err := strconv.Unquote(strings.Replace(strconv.Quote(raw), `\\u`, `\u`, -1)) 163 | if err != nil { 164 | return "" 165 | } 166 | return str 167 | } 168 | 169 | // UnicodeDecByte Unicode编码输出[]byte 170 | func UnicodeDecByte(raw []byte) []byte { 171 | rawStr := string(raw) 172 | return []byte(UnicodeDec(rawStr)) 173 | } 174 | 175 | // UnescapeUnicode Unicode 转码 176 | func UnescapeUnicode(raw []byte) ([]byte, error) { 177 | str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(raw)), `\\u`, `\u`, -1)) 178 | if err != nil { 179 | return nil, err 180 | } 181 | return []byte(str), nil 182 | } 183 | 184 | // Base64Encode base64 编码 185 | func Base64Encode(str string) string { 186 | return base64.StdEncoding.EncodeToString([]byte(str)) 187 | } 188 | 189 | // Base64Decode base64 解码 190 | func Base64Decode(str string) (string, error) { 191 | b, err := base64.StdEncoding.DecodeString(str) 192 | return string(b), err 193 | } 194 | 195 | // Base64UrlEncode base64 url 编码 196 | func Base64UrlEncode(str string) string { 197 | return base64.URLEncoding.EncodeToString([]byte(str)) 198 | } 199 | 200 | // Base64UrlDecode base64 url 解码 201 | func Base64UrlDecode(str string) (string, error) { 202 | b, err := base64.URLEncoding.DecodeString(str) 203 | return string(b), err 204 | } 205 | 206 | func convert(dstCharset string, srcCharset string, src string) (dst string, err error) { 207 | if dstCharset == srcCharset { 208 | return src, nil 209 | } 210 | dst = src 211 | 212 | if srcCharset != "UTF-8" { 213 | if e := getEncoding(srcCharset); e != nil { 214 | tmp, err := io.ReadAll( 215 | transform.NewReader(bytes.NewReader([]byte(src)), e.NewDecoder()), 216 | ) 217 | if err != nil { 218 | return "", err 219 | } 220 | src = string(tmp) 221 | } else { 222 | return dst, err 223 | } 224 | } 225 | 226 | if dstCharset != "UTF-8" { 227 | if e := getEncoding(dstCharset); e != nil { 228 | tmp, err := io.ReadAll( 229 | transform.NewReader(bytes.NewReader([]byte(src)), e.NewEncoder()), 230 | ) 231 | if err != nil { 232 | return "", err 233 | } 234 | dst = string(tmp) 235 | } else { 236 | return dst, err 237 | } 238 | } else { 239 | dst = src 240 | } 241 | return dst, nil 242 | } 243 | 244 | var ( 245 | // Alias for charsets. 246 | charsetAlias = map[string]string{ 247 | "HZGB2312": "HZ-GB-2312", 248 | "hzgb2312": "HZ-GB-2312", 249 | "GB2312": "HZ-GB-2312", 250 | "gb2312": "HZ-GB-2312", 251 | } 252 | ) 253 | 254 | func getEncoding(charset string) encoding.Encoding { 255 | if c, ok := charsetAlias[charset]; ok { 256 | charset = c 257 | } 258 | enc, err := ianaindex.MIB.Encoding(charset) 259 | if err != nil { 260 | log.Println(err) 261 | } 262 | return enc 263 | } 264 | 265 | // ToUTF8 to utf8 266 | func ToUTF8(srcCharset string, src string) (dst string, err error) { 267 | return convert("UTF-8", srcCharset, src) 268 | } 269 | 270 | // UTF8To utf8 to 271 | func UTF8To(dstCharset string, src string) (dst string, err error) { 272 | return convert(dstCharset, "UTF-8", src) 273 | } 274 | 275 | // ToUTF16 to utf16 276 | func ToUTF16(srcCharset string, src string) (dst string, err error) { 277 | return convert("UTF-16", srcCharset, src) 278 | } 279 | 280 | // UTF16To utf16 to 281 | func UTF16To(dstCharset string, src string) (dst string, err error) { 282 | return convert(dstCharset, "UTF-16", src) 283 | } 284 | 285 | // ToBIG5 to big5 286 | func ToBIG5(srcCharset string, src string) (dst string, err error) { 287 | return convert("big5", srcCharset, src) 288 | } 289 | 290 | // BIG5To big to 291 | func BIG5To(dstCharset string, src string) (dst string, err error) { 292 | return convert(dstCharset, "big5", src) 293 | } 294 | 295 | // ToGDK to gdk 296 | func ToGDK(srcCharset string, src string) (dst string, err error) { 297 | return convert("gbk", srcCharset, src) 298 | } 299 | 300 | // GDKTo gdk to 301 | func GDKTo(dstCharset string, src string) (dst string, err error) { 302 | return convert(dstCharset, "gbk", src) 303 | } 304 | 305 | // ToGB18030 to gb18030 306 | func ToGB18030(srcCharset string, src string) (dst string, err error) { 307 | return convert("gb18030", srcCharset, src) 308 | } 309 | 310 | // GB18030To gb18030 to 311 | func GB18030To(dstCharset string, src string) (dst string, err error) { 312 | return convert(dstCharset, "gb18030", src) 313 | } 314 | 315 | // ToGB2312 to gb2312 316 | func ToGB2312(srcCharset string, src string) (dst string, err error) { 317 | return convert("GB2312", srcCharset, src) 318 | } 319 | 320 | // GB2312To gb2312 to 321 | func GB2312To(dstCharset string, src string) (dst string, err error) { 322 | return convert(dstCharset, "GB2312", src) 323 | } 324 | 325 | // ToHZGB2312 to hzgb2312 326 | func ToHZGB2312(srcCharset string, src string) (dst string, err error) { 327 | return convert("HZGB2312", srcCharset, src) 328 | } 329 | 330 | // HZGB2312To hzgb2312 to 331 | func HZGB2312To(dstCharset string, src string) (dst string, err error) { 332 | return convert(dstCharset, "HZGB2312", src) 333 | } 334 | 335 | // IF 三元表达式 336 | func IF[T any](condition bool, a, b T) T { 337 | if condition { 338 | return a 339 | } 340 | return b 341 | } 342 | 343 | // ReplaceAllToOne 批量统一替换字符串 344 | func ReplaceAllToOne(str string, from []string, to string) string { 345 | arr := make([]string, len(from)*2) 346 | for i, s := range from { 347 | arr[i*2] = s 348 | arr[i*2+1] = to 349 | } 350 | r := strings.NewReplacer(arr...) 351 | return r.Replace(str) 352 | } 353 | 354 | // 字节换算 355 | const ( 356 | KiB = 1024 357 | MiB = KiB * 1024 358 | GiB = MiB * 1024 359 | TiB = GiB * 1024 360 | ) 361 | 362 | // HumanFriendlyTraffic 字节换算 363 | func HumanFriendlyTraffic(bytes uint64) string { 364 | if bytes <= KiB { 365 | return fmt.Sprintf("%d B", bytes) 366 | } 367 | if bytes <= MiB { 368 | return fmt.Sprintf("%.2f KiB", float32(bytes)/KiB) 369 | } 370 | if bytes <= GiB { 371 | return fmt.Sprintf("%.2f MiB", float32(bytes)/MiB) 372 | } 373 | if bytes <= TiB { 374 | return fmt.Sprintf("%.2f GiB", float32(bytes)/GiB) 375 | } 376 | return fmt.Sprintf("%.2f TiB", float32(bytes)/TiB) 377 | } 378 | 379 | // StrToSize 字节换算 380 | func StrToSize(sizeStr string) int64 { 381 | i := 0 382 | for ; i < len(sizeStr); i++ { 383 | if sizeStr[i] == '.' || (sizeStr[i] >= '0' && sizeStr[i] <= '9') { 384 | continue 385 | } else { 386 | break 387 | } 388 | } 389 | var ( 390 | unit = sizeStr[i:] 391 | number, _ = strconv.ParseFloat(sizeStr[:i], 64) 392 | ) 393 | if unit == "" { 394 | return int64(number) 395 | } 396 | switch strings.ToLower(unit) { 397 | case "b", "bytes": 398 | return int64(number) 399 | case "k", "kb", "ki", "kib", "kilobyte": 400 | return int64(number * 1024) 401 | case "m", "mb", "mi", "mib", "mebibyte": 402 | return int64(number * 1024 * 1024) 403 | case "g", "gb", "gi", "gib", "gigabyte": 404 | return int64(number * 1024 * 1024 * 1024) 405 | case "t", "tb", "ti", "tib", "terabyte": 406 | return int64(number * 1024 * 1024 * 1024 * 1024) 407 | case "p", "pb", "pi", "pib", "petabyte": 408 | return int64(number * 1024 * 1024 * 1024 * 1024 * 1024) 409 | case "e", "eb", "ei", "eib", "exabyte": 410 | return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 411 | case "z", "zb", "zi", "zib", "zettabyte": 412 | return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 413 | case "y", "yb", "yi", "yib", "yottabyte": 414 | return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 415 | case "bb", "brontobyte": 416 | return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) 417 | } 418 | return -1 419 | } 420 | 421 | // GzipCompress Gzip压缩 422 | func GzipCompress(src []byte) []byte { 423 | var in bytes.Buffer 424 | w := gzip.NewWriter(&in) 425 | _, _ = w.Write(src) 426 | _ = w.Close() 427 | return in.Bytes() 428 | } 429 | 430 | // GzipDecompress Gzip解压 431 | func GzipDecompress(src []byte) []byte { 432 | dst := make([]byte, 0) 433 | br := bytes.NewReader(src) 434 | gr, err := gzip.NewReader(br) 435 | if err != nil { 436 | return dst 437 | } 438 | defer func() { 439 | _ = gr.Close() 440 | }() 441 | tmp, err := io.ReadAll(gr) 442 | if err != nil { 443 | return dst 444 | } 445 | dst = tmp 446 | return dst 447 | } 448 | 449 | // ConvertStr2GBK 将utf-8编码的字符串转换为GBK编码 450 | func ConvertStr2GBK(str string) string { 451 | ret, err := simplifiedchinese.GBK.NewEncoder().String(str) 452 | if err != nil { 453 | ret = str 454 | } 455 | return ret 456 | } 457 | 458 | // ConvertGBK2Str 将GBK编码的字符串转换为utf-8编码 459 | func ConvertGBK2Str(gbkStr string) string { 460 | ret, err := simplifiedchinese.GBK.NewDecoder().String(gbkStr) 461 | if err != nil { 462 | ret = gbkStr 463 | } 464 | return ret 465 | } 466 | 467 | // ByteToGBK byte -> gbk byte 468 | func ByteToGBK(strBuf []byte) []byte { 469 | if IsUtf8(strBuf) { 470 | if GBKBuf, err := simplifiedchinese.GBK.NewEncoder().Bytes(strBuf); err == nil { 471 | if IsUtf8(GBKBuf) { 472 | return GBKBuf 473 | } 474 | } 475 | if GB18030Buf, err := simplifiedchinese.GB18030.NewEncoder().Bytes(strBuf); err == nil { 476 | if IsUtf8(GB18030Buf) { 477 | return GB18030Buf 478 | } 479 | } 480 | if HZGB2312Buf, err := simplifiedchinese.HZGB2312.NewEncoder().Bytes(strBuf); err == nil { 481 | if IsUtf8(HZGB2312Buf) { 482 | return HZGB2312Buf 483 | } 484 | } 485 | return strBuf 486 | } else { 487 | return strBuf 488 | } 489 | } 490 | 491 | // ByteToUTF8 byte -> utf8 byte 492 | func ByteToUTF8(strBuf []byte) []byte { 493 | if IsUtf8(strBuf) { 494 | return strBuf 495 | } else { 496 | if GBKBuf, err := simplifiedchinese.GBK.NewDecoder().Bytes(strBuf); err == nil { 497 | if IsUtf8(GBKBuf) { 498 | return GBKBuf 499 | } 500 | } 501 | if GB18030Buf, err := simplifiedchinese.GB18030.NewDecoder().Bytes(strBuf); err == nil { 502 | if IsUtf8(GB18030Buf) { 503 | return GB18030Buf 504 | } 505 | } 506 | if HZGB2312Buf, err := simplifiedchinese.HZGB2312.NewDecoder().Bytes(strBuf); err == nil { 507 | fmt.Println("3") 508 | if IsUtf8(HZGB2312Buf) { 509 | return HZGB2312Buf 510 | } 511 | } 512 | return strBuf 513 | } 514 | } 515 | 516 | // IsUtf8 是否是utf8编码 517 | func IsUtf8(buf []byte) bool { 518 | return utf8.Valid(buf) 519 | } 520 | 521 | // Get16MD5Encode 返回一个16位md5加密后的字符串 522 | func Get16MD5Encode(data string) string { 523 | return GetMD5Encode(data)[8:24] 524 | } 525 | 526 | // GetMD5Encode 获取Md5编码 527 | func GetMD5Encode(data string) string { 528 | h := md5.New() 529 | h.Write([]byte(data)) 530 | return hex.EncodeToString(h.Sum(nil)) 531 | } 532 | -------------------------------------------------------------------------------- /conversion.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Description : 数据类型转换 3 | * Author : ManGe 4 | * Mail : 2912882908@qq.com 5 | **/ 6 | 7 | package gathertool 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | "encoding/json" 13 | "fmt" 14 | "math" 15 | "net" 16 | "reflect" 17 | "strconv" 18 | "strings" 19 | "unsafe" 20 | ) 21 | 22 | // Json2Map 数据类型转换 json -> map 23 | func Json2Map(str string) (map[string]any, error) { 24 | var tempMap map[string]any 25 | err := json.Unmarshal([]byte(str), &tempMap) 26 | return tempMap, err 27 | } 28 | 29 | // Any2Map 数据类型转换 any -> map[string]any 30 | func Any2Map(data any) map[string]any { 31 | if v, ok := data.(map[string]any); ok { 32 | return v 33 | } 34 | 35 | if reflect.ValueOf(data).Kind() == reflect.String { 36 | dataMap, err := Json2Map(data.(string)) 37 | if err == nil { 38 | return dataMap 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | // Any2String 数据类型转换 any -> string 45 | func Any2String(i any) string { 46 | if i == nil { 47 | return "" 48 | } 49 | 50 | if reflect.ValueOf(i).Kind() == reflect.String { 51 | return i.(string) 52 | } 53 | 54 | var buf bytes.Buffer 55 | stringValue(reflect.ValueOf(i), 0, &buf) 56 | return buf.String() 57 | } 58 | 59 | func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) { 60 | for v.Kind() == reflect.Ptr { 61 | v = v.Elem() 62 | } 63 | switch v.Kind() { 64 | case reflect.Struct: 65 | buf.WriteString("{\n") 66 | for i := 0; i < v.Type().NumField(); i++ { 67 | ft := v.Type().Field(i) 68 | fv := v.Field(i) 69 | if ft.Name[0:1] == strings.ToLower(ft.Name[0:1]) { 70 | continue 71 | } 72 | if (fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Slice) && fv.IsNil() { 73 | continue 74 | } 75 | buf.WriteString(strings.Repeat(" ", indent+2)) 76 | buf.WriteString(ft.Name + ": ") 77 | if tag := ft.Tag.Get("sensitive"); tag == "true" { 78 | buf.WriteString("") 79 | } else { 80 | stringValue(fv, indent+2, buf) 81 | } 82 | buf.WriteString(",\n") 83 | } 84 | buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") 85 | 86 | case reflect.Slice: 87 | nl, id, id2 := "", "", "" 88 | if v.Len() > 3 { 89 | nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2) 90 | } 91 | buf.WriteString("[" + nl) 92 | for i := 0; i < v.Len(); i++ { 93 | buf.WriteString(id2) 94 | stringValue(v.Index(i), indent+2, buf) 95 | 96 | if i < v.Len()-1 { 97 | buf.WriteString("," + nl) 98 | } 99 | } 100 | buf.WriteString(nl + id + "]") 101 | 102 | case reflect.Map: 103 | buf.WriteString("{\n") 104 | for i, k := range v.MapKeys() { 105 | buf.WriteString(strings.Repeat(" ", indent+2)) 106 | buf.WriteString(k.String() + ": ") 107 | stringValue(v.MapIndex(k), indent+2, buf) 108 | if i < v.Len()-1 { 109 | buf.WriteString(",\n") 110 | } 111 | } 112 | buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") 113 | 114 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 115 | buf.WriteString(strconv.FormatInt(v.Int(), 10)) 116 | 117 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 118 | buf.WriteString(strconv.FormatUint(v.Uint(), 10)) 119 | 120 | case reflect.Float32, reflect.Float64: 121 | result := fmt.Sprintf("%f", v.Float()) 122 | // 去除result末尾的0 123 | for strings.HasSuffix(result, "0") { 124 | result = strings.TrimSuffix(result, "0") 125 | } 126 | if strings.HasSuffix(result, ".") { 127 | result = strings.TrimSuffix(result, ".") 128 | } 129 | buf.WriteString(result) 130 | 131 | default: 132 | format := "%v" 133 | switch v.Interface().(type) { 134 | case string: 135 | format = "%q" 136 | } 137 | _, _ = fmt.Fprintf(buf, format, v.Interface()) 138 | } 139 | } 140 | 141 | // Any2Int 数据类型转换 any -> int 142 | func Any2Int(data any) int { 143 | var t2 int 144 | switch data.(type) { 145 | case uint: 146 | t2 = int(data.(uint)) 147 | break 148 | case int8: 149 | t2 = int(data.(int8)) 150 | break 151 | case uint8: 152 | t2 = int(data.(uint8)) 153 | break 154 | case int16: 155 | t2 = int(data.(int16)) 156 | break 157 | case uint16: 158 | t2 = int(data.(uint16)) 159 | break 160 | case int32: 161 | t2 = int(data.(int32)) 162 | break 163 | case uint32: 164 | t2 = int(data.(uint32)) 165 | break 166 | case int64: 167 | t2 = int(data.(int64)) 168 | break 169 | case uint64: 170 | t2 = int(data.(uint64)) 171 | break 172 | case float32: 173 | t2 = int(data.(float32)) 174 | break 175 | case float64: 176 | t2 = int(data.(float64)) 177 | break 178 | case string: 179 | t2, _ = strconv.Atoi(data.(string)) 180 | break 181 | default: 182 | t2 = data.(int) 183 | break 184 | } 185 | return t2 186 | } 187 | 188 | // Any2Int64 数据类型转换 any -> int64 189 | func Any2Int64(data any) int64 { 190 | return int64(Any2Int(data)) 191 | } 192 | 193 | // Any2Arr 数据类型转换 any -> []any 194 | func Any2Arr(data any) []any { 195 | if v, ok := data.([]any); ok { 196 | return v 197 | } 198 | return nil 199 | } 200 | 201 | // Any2Float64 数据类型转换 any -> float64 202 | func Any2Float64(data any) float64 { 203 | if v, ok := data.(float64); ok { 204 | return v 205 | } 206 | if v, ok := data.(float32); ok { 207 | return float64(v) 208 | } 209 | return 0 210 | } 211 | 212 | // Any2Strings 数据类型转换 any -> []string 213 | func Any2Strings(data any) []string { 214 | listValue, ok := data.([]any) 215 | if !ok { 216 | return nil 217 | } 218 | keyStringValues := make([]string, len(listValue)) 219 | for i, arg := range listValue { 220 | keyStringValues[i] = arg.(string) 221 | } 222 | return keyStringValues 223 | } 224 | 225 | // Any2Json 数据类型转换 any -> json string 226 | func Any2Json(data any) (string, error) { 227 | jsonStr, err := json.Marshal(data) 228 | return string(jsonStr), err 229 | } 230 | 231 | // Int2Hex 数据类型转换 int -> hex 232 | func Int2Hex(i int64) string { 233 | return fmt.Sprintf("%x", i) 234 | } 235 | 236 | // Int642Hex 数据类型转换 int64 -> hex 237 | func Int642Hex(i int64) string { 238 | return fmt.Sprintf("%x", i) 239 | } 240 | 241 | // Hex2Int 数据类型转换 hex -> int 242 | func Hex2Int(s string) int { 243 | n, err := strconv.ParseUint(s, 16, 8) 244 | if err != nil { 245 | Error(err) 246 | return 0 247 | } 248 | 249 | n2 := uint8(n) 250 | return int(*(*int8)(unsafe.Pointer(&n2))) 251 | } 252 | 253 | // Hex2Int64 数据类型转换 hex -> int 254 | func Hex2Int64(s string) int64 { 255 | n, err := strconv.ParseUint(s, 16, 8) 256 | if err != nil { 257 | Error(err) 258 | return 0 259 | } 260 | 261 | n2 := uint8(n) 262 | return int64(*(*int8)(unsafe.Pointer(&n2))) 263 | } 264 | 265 | // Str2Int64 数据类型转换 string -> int64 266 | func Str2Int64(str string) int64 { 267 | i, err := strconv.ParseInt(str, 10, 64) 268 | if err != nil { 269 | return 0 270 | } 271 | return i 272 | } 273 | 274 | // Str2Int 数据类型转换 string -> int 275 | func Str2Int(str string) int { 276 | i, err := strconv.Atoi(str) 277 | if err != nil { 278 | return 0 279 | } 280 | return i 281 | } 282 | 283 | // Str2Int32 数据类型转换 string -> int32 284 | func Str2Int32(str string) int32 { 285 | i, err := strconv.Atoi(str) 286 | if err != nil { 287 | return 0 288 | } 289 | return int32(i) 290 | } 291 | 292 | // Str2Float64 数据类型转换 string -> float64 293 | func Str2Float64(str string) float64 { 294 | i, err := strconv.ParseFloat(str, 64) 295 | if err != nil { 296 | return 0 297 | } 298 | return i 299 | } 300 | 301 | // Str2Float32 数据类型转换 string -> float32 302 | func Str2Float32(str string) float32 { 303 | i, err := strconv.ParseFloat(str, 32) 304 | if err != nil { 305 | return 0 306 | } 307 | return float32(i) 308 | } 309 | 310 | // Uint82Str 数据类型转换 []uint8 -> string 311 | func Uint82Str(bs []uint8) string { 312 | ba := make([]byte, 0) 313 | for _, b := range bs { 314 | ba = append(ba, b) 315 | } 316 | return string(ba) 317 | } 318 | 319 | // Str2Byte 数据类型转换 string -> []byte 320 | func Str2Byte(s string) []byte { 321 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 322 | h := [3]uintptr{x[0], x[1], x[1]} 323 | return *(*[]byte)(unsafe.Pointer(&h)) 324 | } 325 | 326 | // Byte2Str 数据类型转换 []byte -> string 327 | func Byte2Str(b []byte) string { 328 | return *(*string)(unsafe.Pointer(&b)) 329 | } 330 | 331 | // Bool2Byte 数据类型转换 bool -> []byte 332 | func Bool2Byte(b bool) []byte { 333 | if b == true { 334 | return []byte{1} 335 | } 336 | return []byte{0} 337 | } 338 | 339 | // Byte2Bool 数据类型转换 []byte -> bool 340 | func Byte2Bool(b []byte) bool { 341 | if len(b) == 0 || bytes.Compare(b, make([]byte, len(b))) == 0 { 342 | return false 343 | } 344 | return true 345 | } 346 | 347 | // Int2Byte 数据类型转换 int -> []byte 348 | func Int2Byte(i int) []byte { 349 | b := make([]byte, 4) 350 | binary.LittleEndian.PutUint32(b, uint32(i)) 351 | return b 352 | } 353 | 354 | // Byte2Int 数据类型转换 []byte -> int 355 | func Byte2Int(b []byte) int { 356 | return int(binary.LittleEndian.Uint32(b)) 357 | } 358 | 359 | // Int642Byte 数据类型转换 int64 -> []byte 360 | func Int642Byte(i int64) []byte { 361 | b := make([]byte, 8) 362 | binary.LittleEndian.PutUint64(b, uint64(i)) 363 | return b 364 | } 365 | 366 | // Byte2Int64 数据类型转换 []byte -> int64 367 | func Byte2Int64(b []byte) int64 { 368 | return int64(binary.LittleEndian.Uint64(b)) 369 | } 370 | 371 | // Float322Byte 数据类型转换 float32 -> []byte 372 | func Float322Byte(f float32) []byte { 373 | b := make([]byte, 4) 374 | binary.LittleEndian.PutUint32(b, Float322Uint32(f)) 375 | return b 376 | } 377 | 378 | // Float322Uint32 数据类型转换 float32 -> uint32 379 | func Float322Uint32(f float32) uint32 { 380 | return math.Float32bits(f) 381 | } 382 | 383 | // Byte2Float32 数据类型转换 []byte -> float32 384 | func Byte2Float32(b []byte) float32 { 385 | return math.Float32frombits(binary.LittleEndian.Uint32(b)) 386 | } 387 | 388 | // Float642Byte 数据类型转换 float64 -> []byte 389 | func Float642Byte(f float64) []byte { 390 | b := make([]byte, 8) 391 | binary.LittleEndian.PutUint64(b, Float642Uint64(f)) 392 | return b 393 | } 394 | 395 | // Float642Uint64 数据类型转换 float64 -> uint64 396 | func Float642Uint64(f float64) uint64 { 397 | return math.Float64bits(f) 398 | } 399 | 400 | // Byte2Float64 数据类型转换 []byte -> float64 401 | func Byte2Float64(b []byte) float64 { 402 | return math.Float64frombits(binary.LittleEndian.Uint64(b)) 403 | } 404 | 405 | // Struct2Map 数据类型转换 Struct -> map 406 | // 参数说明: 407 | // - hasValue=true表示字段值不管是否存在都转换成map 408 | // - hasValue=false表示字段为空或者不为0则转换成map 409 | func Struct2Map(obj any, hasValue bool) (map[string]any, error) { 410 | mp := make(map[string]any) 411 | value := reflect.ValueOf(obj).Elem() 412 | typeOf := reflect.TypeOf(obj).Elem() 413 | for i := 0; i < value.NumField(); i++ { 414 | switch value.Field(i).Kind() { 415 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 416 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 417 | if hasValue { 418 | if value.Field(i).Int() != 0 { 419 | mp[typeOf.Field(i).Name] = value.Field(i).Int() 420 | } 421 | } else { 422 | mp[typeOf.Field(i).Name] = value.Field(i).Int() 423 | } 424 | 425 | case reflect.String: 426 | if hasValue { 427 | if len(value.Field(i).String()) != 0 { 428 | mp[typeOf.Field(i).Name] = value.Field(i).String() 429 | } 430 | } else { 431 | mp[typeOf.Field(i).Name] = value.Field(i).String() 432 | } 433 | 434 | case reflect.Float32, reflect.Float64: 435 | if hasValue { 436 | if len(value.Field(i).String()) != 0 { 437 | mp[typeOf.Field(i).Name] = value.Field(i).Float() 438 | } 439 | } else { 440 | mp[typeOf.Field(i).Name] = value.Field(i).Float() 441 | } 442 | 443 | case reflect.Bool: 444 | if hasValue { 445 | if len(value.Field(i).String()) != 0 { 446 | mp[typeOf.Field(i).Name] = value.Field(i).Bool() 447 | } 448 | } else { 449 | mp[typeOf.Field(i).Name] = value.Field(i).Bool() 450 | } 451 | 452 | default: 453 | return mp, fmt.Errorf("数据类型不匹配") 454 | } 455 | } 456 | return mp, nil 457 | } 458 | 459 | // Byte2Bit 数据类型转换 []byte -> []uint8 (bit) 460 | func Byte2Bit(b []byte) []uint8 { 461 | bits := make([]uint8, 0) 462 | for _, v := range b { 463 | bits = bits2Uint(bits, uint(v), 8) 464 | } 465 | return bits 466 | } 467 | 468 | func bits2Uint(bits []uint8, ui uint, l int) []uint8 { 469 | a := make([]uint8, l) 470 | for i := l - 1; i >= 0; i-- { 471 | a[i] = uint8(ui & 1) 472 | ui >>= 1 473 | } 474 | if bits != nil { 475 | return append(bits, a...) 476 | } 477 | return a 478 | } 479 | 480 | // Bit2Byte 数据类型转换 []uint8 -> []byte 481 | func Bit2Byte(b []uint8) []byte { 482 | if len(b)%8 != 0 { 483 | for i := 0; i < len(b)%8; i++ { 484 | b = append(b, 0) 485 | } 486 | } 487 | by := make([]byte, 0) 488 | for i := 0; i < len(b); i += 8 { 489 | by = append(b, byte(bitsToUint(b[i:i+8]))) 490 | } 491 | return by 492 | } 493 | 494 | func bitsToUint(bits []uint8) uint { 495 | v := uint(0) 496 | for _, i := range bits { 497 | v = v<<1 | uint(i) 498 | } 499 | return v 500 | } 501 | 502 | // ByteToBinaryString 数据类型转换 字节 -> 二进制字符串 503 | func ByteToBinaryString(data byte) (str string) { 504 | var a byte 505 | for i := 0; i < 8; i++ { 506 | a = data 507 | data <<= 1 508 | data >>= 1 509 | switch a { 510 | case data: 511 | str += "0" 512 | default: 513 | str += "1" 514 | } 515 | data <<= 1 516 | } 517 | return str 518 | } 519 | 520 | // IP2Binary 数据类型转换 IP str -> binary int64 521 | func IP2Binary(ip string) string { 522 | rse := IP2Int64(ip) 523 | return strconv.FormatInt(rse, 2) 524 | } 525 | 526 | // UInt32ToIP 数据类型转换 uint32 -> net.IP 527 | func UInt32ToIP(ip uint32) net.IP { 528 | var b [4]byte 529 | b[0] = byte(ip & 0xFF) 530 | b[1] = byte((ip >> 8) & 0xFF) 531 | b[2] = byte((ip >> 16) & 0xFF) 532 | b[3] = byte((ip >> 24) & 0xFF) 533 | return net.IPv4(b[3], b[2], b[1], b[0]) 534 | } 535 | 536 | // IP2Int64 数据类型转换 IP str -> int64 537 | func IP2Int64(ip string) int64 { 538 | address := net.ParseIP(ip) 539 | if address == nil { 540 | Error("ip地址不正确") 541 | return 0 542 | } 543 | bits := strings.Split(ip, ".") 544 | b0, b1, b2, b3 := 0, 0, 0, 0 545 | if len(bits) >= 1 { 546 | b0, _ = strconv.Atoi(bits[0]) 547 | } 548 | if len(bits) >= 2 { 549 | b1, _ = strconv.Atoi(bits[1]) 550 | } 551 | if len(bits) >= 3 { 552 | b2, _ = strconv.Atoi(bits[2]) 553 | } 554 | if len(bits) >= 4 { 555 | b3, _ = strconv.Atoi(bits[3]) 556 | } 557 | var sum int64 558 | sum += int64(b0) << 24 559 | sum += int64(b1) << 16 560 | sum += int64(b2) << 8 561 | sum += int64(b3) 562 | return sum 563 | } 564 | --------------------------------------------------------------------------------