├── imgs └── file_not_find_err ├── .gitignore ├── model ├── city.go ├── VerifyModel.go ├── SubscribDate.go ├── customer.go └── customerProduct.go ├── zhimiaoyiyue ├── user_test.go ├── customerproduct_test.go ├── auth_test.go ├── order_test.go ├── custSubscribeDate_test.go ├── user.go ├── seckill.go ├── auth.go ├── verify_test.go ├── customerlist_test.go ├── customerlist.go ├── customerproduct.go ├── custSubscribeDate.go ├── verify.go ├── order.go └── engine.go ├── .idea ├── vcs.xml ├── other.xml ├── .gitignore ├── modules.xml ├── misc.xml └── inspectionProfiles │ └── Project_Default.xml ├── ip ├── ip_test.go ├── ip.go └── ip.txt ├── config ├── config_test.go ├── conf.yaml ├── config.go └── city.json ├── go.mod ├── utils ├── utils_test.go └── utils.go ├── zmyy_seckill.iml ├── limit └── limit.go ├── LICENSE ├── go.sum ├── main.go ├── consts └── consts.go ├── README.md ├── captcha.py └── fetcher └── fetcher.go /imgs/file_not_find_err: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.iml 3 | .idea 4 | *.xml 5 | *.txt 6 | -------------------------------------------------------------------------------- /model/city.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type City struct { 4 | Name string `json:"name"` 5 | Value string `json:"value"` 6 | Children []City `json:"children"` 7 | } 8 | -------------------------------------------------------------------------------- /zhimiaoyiyue/user_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import "testing" 4 | 5 | func TestGetUserInfo(t *testing.T) { 6 | e.Init() 7 | e.getUserInfo() 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ip/ip_test.go: -------------------------------------------------------------------------------- 1 | package ip 2 | 3 | import "testing" 4 | 5 | func TestFetchIp(t *testing.T) { 6 | ReadIpFile() 7 | } 8 | func TestProxyTest(t *testing.T) { 9 | ip := "59.56.74.51:9999" 10 | proxyTest(ip) 11 | } 12 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../../:\Users\Administrator\IdeaProjects\zmyy_seckill\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetConfig(t *testing.T) { 9 | var yaml RootConf 10 | conf, err := yaml.GetConf() 11 | if err != nil { 12 | t.Errorf("failed ") 13 | } 14 | fmt.Printf("%v \n", conf) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /model/VerifyModel.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type VerifyPicModel struct { 4 | Dragon string `json:"dragon"` 5 | Tiger string `json:"tiger"` 6 | } 7 | 8 | type VerifyResultModel struct { 9 | Guid string `json:"guid" default:""` 10 | Status int `json:"status"` 11 | Msg string `json:"msg"` 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module zmyy_seckill 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac 7 | github.com/sbinet/go-python v0.1.0 // indirect 8 | github.com/thedevsaddam/gojsonq/v2 v2.5.2 9 | gopkg.in/sourcemap.v1 v1.0.5 // indirect 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | -------------------------------------------------------------------------------- /zhimiaoyiyue/customerproduct_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestCustomerProduct(t *testing.T) { 9 | e := ZMYYEngine{} 10 | e.Init() 11 | product, err := e.GetCustomerProduct(1921) 12 | if err != nil { 13 | t.Errorf("err : %v", err) 14 | } 15 | fmt.Printf("%v", product) 16 | } 17 | -------------------------------------------------------------------------------- /zhimiaoyiyue/auth_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "zmyy_seckill/utils" 7 | ) 8 | 9 | func TestAuth(t *testing.T) { 10 | e := ZMYYEngine{} 11 | e.Init() 12 | e.AuthAndSetSessionID() 13 | } 14 | 15 | func TestParseSessionId(t *testing.T) { 16 | id := utils.ParseSessionId("ASP.NET_SessionId=jw1c3itgmqxoik0q3sazbyx5; path=/; HttpOnly; SameSite=Lax") 17 | fmt.Printf("%s", id) 18 | } 19 | -------------------------------------------------------------------------------- /zhimiaoyiyue/order_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func init() { 8 | e.Init() 9 | } 10 | 11 | func TestSaveOrder(t *testing.T) { 12 | path, _ := e.GetVerifyPic(dateDetail) 13 | guid, err := e.CaptchaVerify(path) 14 | _, err = e.SaveOrder(dateDetail, "2", guid) 15 | if err != nil { 16 | t.Errorf("%v", err) 17 | } 18 | } 19 | 20 | func TestZMYYEngine_GetOrderStatus(t *testing.T) { 21 | e.GetOrderStatus(dateDetail) 22 | } 23 | -------------------------------------------------------------------------------- /zhimiaoyiyue/custSubscribeDate_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestZMYYEngine_GetCustSubscribeDateAll(t *testing.T) { 9 | e := ZMYYEngine{} 10 | all := e.GetCustSubscribeDateAll(1921, 1, 202102) 11 | fmt.Printf("%v", all) 12 | } 13 | func TestZMYYEngine_GetCustSubscribeDateDetail(t *testing.T) { 14 | e := ZMYYEngine{} 15 | all, err := e.GetCustSubscribeDateDetail("2021-02-27", 2, 1921) 16 | if err != nil { 17 | t.Errorf("%v", err) 18 | } 19 | fmt.Printf("%v", all) 20 | } 21 | -------------------------------------------------------------------------------- /zhimiaoyiyue/user.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "zmyy_seckill/consts" 6 | "zmyy_seckill/fetcher" 7 | "zmyy_seckill/utils" 8 | ) 9 | 10 | func (e *ZMYYEngine) getUserInfo() { 11 | UserInfoURL := consts.UserInfoURL 12 | headers := make(map[string]string) 13 | headers["User-Agent"] = consts.UserAgent 14 | headers["Referer"] = consts.Refer 15 | headers["Cookie"] = e.Conf.Cookie 16 | zftsl := utils.GetZFTSL() 17 | headers["zftsl"] = zftsl 18 | contents, err := fetcher.FetchWithRatelimter(UserInfoURL, headers) 19 | if err != nil { 20 | return 21 | } 22 | fmt.Printf("%s", contents) 23 | } 24 | -------------------------------------------------------------------------------- /zhimiaoyiyue/seckill.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "zmyy_seckill/model" 7 | ) 8 | 9 | //一次完整的秒杀流程 10 | func (e *ZMYYEngine) SecKill(dateDetail model.DateDetail, productId string, wg sync.WaitGroup, 11 | ctx context.Context, stopGetDetail context.CancelFunc, 12 | stopSeckill context.CancelFunc, ip ...string) { 13 | defer wg.Done() 14 | for { 15 | select { 16 | case <-ctx.Done(): 17 | return 18 | default: 19 | ok, _ := e.Bingo(dateDetail, productId, ctx, ip...) 20 | if ok { 21 | stopGetDetail() 22 | stopSeckill() 23 | return 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /zhimiaoyiyue/auth.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "zmyy_seckill/consts" 6 | "zmyy_seckill/fetcher" 7 | "zmyy_seckill/utils" 8 | ) 9 | 10 | func (e *ZMYYEngine) AuthAndSetSessionID() error { 11 | headers := make(map[string]string) 12 | headers["User-Agent"] = consts.UserAgent 13 | headers["Referer"] = consts.Refer 14 | zftsl := utils.GetZFTSL() 15 | headers["zftsl"] = zftsl 16 | contents, err := fetcher.FetchWithRatelimter(consts.AuthUrl, headers) 17 | if err != nil { 18 | fmt.Printf("AuthAndSetSessionID err : %v \n", err) 19 | return err 20 | } 21 | fmt.Printf("%s \n", contents) 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /config/conf.yaml: -------------------------------------------------------------------------------- 1 | zhiyiyuemiao: 2 | # 【必填】省 3 | province: "xx省" 4 | # 【选填】市 5 | city: "xx市" 6 | # 区(可以不用填,不打算开发此功能) 7 | district: "" 8 | # 【必填】抢购疫苗的类型(必填),务必与小程序上的名称一致 9 | productName: "xxx" 10 | # 【必填】预约地点名称(必填),务必与小程序上的名称一致 11 | customerName: "xxx" 12 | # 【必填】预约时间 13 | subscribeTime: "2021-03-12 15:08:00" 14 | # 【必填】疫苗可预约的月份 15 | month: 202103 16 | # 【必填】生日 17 | birthday: "2000-12-09" 18 | # 【必填】电话 19 | tel: "xxxx" 20 | # 【必填】性别:1为男性 2为女性 21 | sex: 1 22 | # 【必填】姓名 23 | name: "xxxx" 24 | # 【必填】身份证号 25 | idcard: "xxxx" 26 | # 【必填】这个Cookie需要抓包获取 【有效期为5分钟或更久】 27 | cookie: "ASP.NET_SessionId=xxx" 28 | -------------------------------------------------------------------------------- /model/SubscribDate.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type SubscribeDate struct { 4 | Status int `json:"status"` 5 | Dates []Dates `json:"list"` 6 | } 7 | type Dates struct { 8 | Date string `json:"date"` 9 | Enable bool `json:"enable"` 10 | } 11 | 12 | type SubscribeDateDetail struct { 13 | Date string 14 | DateDetails []DateDetail `json:"list"` 15 | } 16 | type DateDetail struct { 17 | Date string 18 | CustomerName string `json:"customer"` 19 | CustomerId int `json:"customerid"` 20 | StartTime string `json:"StartTime"` 21 | EndTime string `json:"EndTime"` 22 | Mxid string `json:"mxid"` 23 | Qty int `json:"qty"` 24 | } 25 | -------------------------------------------------------------------------------- /model/customer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type CustomerList struct { 4 | Customers []Customer `json:"list"` 5 | //Status int `json:"status"` 6 | } 7 | type Customer struct { 8 | Id int `json:"id"` 9 | Cname string `json:"cname"` 10 | Addr string `json:"addr"` 11 | //SmallPic string `json:"-"` 12 | //Lat float64 `json:"-"` 13 | //Lng float64 `json:"-"` 14 | Tel string `json:"tel"` 15 | Addr2 string `json:"addr2"` 16 | Province int `json:"province"` 17 | City int `json:"city"` 18 | //County int `json:"county"` 19 | //Sort int `json:"-"` 20 | //Distance float64 `json:"-"` 21 | //Tags []string `json:"-"` 22 | } 23 | -------------------------------------------------------------------------------- /utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBase64ToPics(t *testing.T) { 9 | prefix := "2021-02-18:08:00:00-09:00:00-2" 10 | Base64ToPics(prefix) 11 | } 12 | func TestCallPythonScript(t *testing.T) { 13 | dragonPath := "C:\\Users\\Administrator\\IdeaProjects\\zmyy_seckill\\imgs\\2021-03-23-08_00_00-11_30_00-dragon.png" 14 | tigerPath := "C:\\Users\\Administrator\\IdeaProjects\\zmyy_seckill\\imgs\\2021-03-23-08_00_00-11_30_00-tiger.png" 15 | processPath := "C:\\Users\\Administrator\\IdeaProjects\\zmyy_seckill\\imgs\\2021-03-23-08_00_00-11_30_00-process.png" 16 | pythonScript, _ := CallPythonScript(tigerPath, dragonPath, processPath) 17 | fmt.Printf("%s", pythonScript) 18 | } 19 | func TestCallJsScript(t *testing.T) { 20 | zftsl := GetZFTSL() 21 | fmt.Printf("%v \n", zftsl) 22 | } 23 | -------------------------------------------------------------------------------- /zhimiaoyiyue/verify_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "zmyy_seckill/model" 7 | ) 8 | 9 | var e = ZMYYEngine{} 10 | var dateDetail = model.DateDetail{ 11 | Date: "2021-03-24", 12 | StartTime: "08:00", 13 | EndTime: "11:30", 14 | Mxid: "AAAAAJVjAACUYjQB", 15 | } 16 | 17 | func init() { 18 | e.Init() 19 | } 20 | 21 | func TestZMYYEngine_CaptchaVerify(t *testing.T) { 22 | 23 | path, err := e.GetVerifyPic(dateDetail) 24 | 25 | m, err := e.CaptchaVerify(path) 26 | if err != nil || m == "" { 27 | t.Errorf("err: %v\n", err) 28 | } 29 | fmt.Printf("guid: %v \n", m) 30 | } 31 | func TestZMYYEngine_GetVerifyPic(t *testing.T) { 32 | path, err := e.GetVerifyPic(dateDetail) 33 | if err != nil { 34 | t.Errorf("err : %v", err) 35 | return 36 | } 37 | fmt.Printf("%s", path) 38 | } 39 | -------------------------------------------------------------------------------- /zmyy_seckill.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | -------------------------------------------------------------------------------- /limit/limit.go: -------------------------------------------------------------------------------- 1 | package limit 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type LR struct { 9 | LRMap map[string]*LimitRate 10 | } 11 | 12 | func (lr *LR) SetRate(r int, second int) { 13 | for _, l := range lr.LRMap { 14 | l.SetRate(r, second) 15 | } 16 | } 17 | 18 | //LimitRate 限速 19 | type LimitRate struct { 20 | rate int 21 | interval time.Duration 22 | lastAction time.Time 23 | lock sync.Mutex 24 | } 25 | 26 | func (l *LimitRate) Limit() bool { 27 | result := false 28 | for { 29 | l.lock.Lock() 30 | //判断最后一次执行的时间与当前的时间间隔是否大于限速速率 31 | if time.Now().Sub(l.lastAction) > l.interval { 32 | l.lastAction = time.Now() 33 | result = true 34 | } 35 | l.lock.Unlock() 36 | if result { 37 | return result 38 | } 39 | time.Sleep(l.interval) 40 | } 41 | } 42 | 43 | //SetRate 设置Rate 44 | func (l *LimitRate) SetRate(r int, second int) { 45 | l.rate = r 46 | l.interval = time.Microsecond * time.Duration(second*100*1000/l.rate) 47 | } 48 | 49 | //GetRate 获取Rate 50 | func (l *LimitRate) GetRate() int { 51 | return l.rate 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sotowang 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 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU= 2 | github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= 3 | github.com/sbinet/go-python v0.1.0 h1:WlS8dGoxKMt9/c54U4XQuVhQt79p0uJUdzopuDR4QaI= 4 | github.com/sbinet/go-python v0.1.0/go.mod h1:Pq31TCdgxj39xSYY/VAfsWWrFphYSmx3jmPHtotzQNY= 5 | github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= 6 | github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= 10 | gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= 11 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 12 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | "zmyy_seckill/consts" 8 | "zmyy_seckill/ip" 9 | "zmyy_seckill/limit" 10 | "zmyy_seckill/zhimiaoyiyue" 11 | ) 12 | 13 | func main() { 14 | e := zhimiaoyiyue.ZMYYEngine{} 15 | e.Init() 16 | //获取可用代理ip,下行代码开启时则启用ip代理,默认使用本机的ip 17 | consts.ProxyIpArr = ip.ReadIpFile() 18 | customerId, productId := -1, -1 19 | lrmap := make(map[string]*limit.LimitRate, len(consts.ProxyIpArr)) 20 | for _, ip := range consts.ProxyIpArr { 21 | lr := &limit.LimitRate{} 22 | lrmap[ip] = lr 23 | } 24 | consts.LR.LRMap = lrmap 25 | //设置抢购请求速率,2s/次,下行代码开启时则开始限流 26 | consts.LR.SetRate(1, 20) 27 | for customerId == -1 || productId == -1 { 28 | if customerId == -1 { 29 | //获取指定地区接种地点的customerId 30 | customerId, _ = e.GetCustomerList() 31 | } 32 | if productId == -1 { 33 | //获取指定接种地点的productId 34 | productId, _ = e.GetCustomerProduct(customerId) 35 | } 36 | } 37 | loc, _ := time.LoadLocation("Local") 38 | timeLayout := "2006-01-02 15:04:05" 39 | subsTime, _ := time.ParseInLocation(timeLayout, e.Conf.SubscribeTime, loc) 40 | now := time.Now() 41 | timer := time.NewTimer(subsTime.Sub(now)) 42 | fmt.Printf("倒计时中,将在 %v 时运行程序...\n", subsTime) 43 | <-timer.C 44 | log.Printf("开始运行zmyy-seckill....\n") 45 | startTime := time.Now() 46 | e.Run(customerId, productId, startTime) 47 | } 48 | -------------------------------------------------------------------------------- /zhimiaoyiyue/customerlist_test.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "zmyy_seckill/model" 7 | "zmyy_seckill/utils" 8 | ) 9 | 10 | type CustomerList struct { 11 | E []Customer `json:"list"` 12 | Status int `json:"status"` 13 | } 14 | type Customer struct { 15 | Id int `json:"id"` 16 | Name string `json:"cname"` 17 | } 18 | 19 | func Test_transfer(t *testing.T) { 20 | s1 := ` 21 | { 22 | "list":[ 23 | { 24 | "id":1776, 25 | "cname":"西安市未央区张家堡社区卫生服务中心", 26 | "addr":"凤城七路八号", 27 | "SmallPic":"https://app.zhifeishengwu.com/img/none.png", 28 | "lat":34.33705700, 29 | "lng":108.94514500, 30 | "tel":"18991825131", 31 | "addr2":"凤城七路八号", 32 | "province":2375, 33 | "city":2376, 34 | "county":2377, 35 | "sort":1, 36 | "distance":907.52, 37 | "tags":[] 38 | } 39 | ], 40 | "status":200 41 | } 42 | ` 43 | b := []byte(s1) 44 | v, err := utils.Transfer2Model(b, model.CustomerList{}) 45 | m := v.(model.CustomerList) 46 | if err != nil { 47 | t.Errorf("failed, err : %v", err) 48 | } 49 | fmt.Printf("%v", m) 50 | } 51 | 52 | func Test_GetCustomerList(t *testing.T) { 53 | e := ZMYYEngine{} 54 | e.Init() 55 | list, err := e.GetCustomerList() 56 | if err != nil { 57 | t.Errorf("err : %v", err) 58 | } 59 | fmt.Printf("%v", list) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /zhimiaoyiyue/customerlist.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "zmyy_seckill/consts" 8 | "zmyy_seckill/fetcher" 9 | "zmyy_seckill/model" 10 | "zmyy_seckill/utils" 11 | ) 12 | 13 | //获取指定接种地ID 14 | func (e *ZMYYEngine) GetCustomerList(ip ...string) (int, error) { 15 | params := "[\"" + e.Conf.Province + "\",\"" + e.Conf.City + "\",\"" + e.Conf.District + "\"]" 16 | newUrl := consts.CustomerListUrl + "&city=" + utils.UrlEncode(params) + "&lat=31.23037&lng=121.4737" + "&id=0&cityCode=" + e.Conf.CityCode + "&product=0" 17 | headers := make(map[string]string) 18 | headers["User-Agent"] = consts.UserAgent 19 | headers["Referer"] = consts.Refer 20 | zftsl := utils.GetZFTSL() 21 | headers["zftsl"] = zftsl 22 | bytes, err2 := fetcher.FetchWithRatelimter(newUrl, headers, ip...) 23 | if err2 != nil { 24 | return -1, err2 25 | } 26 | customers := model.CustomerList{} 27 | err2 = utils.Transfer2CustomerListModel(bytes, &customers) 28 | if err2 != nil { 29 | return -1, err2 30 | } 31 | fmt.Printf("正在查找接种地点:\n") 32 | for k, v := range customers.Customers { 33 | log.Printf("第 %d个接种地:%s\n", k+1, v.Cname) 34 | if v.Cname == e.Conf.CustomerName { 35 | log.Printf("====选中第 %d个接种地:%s,其customerId为 %d====\n", k+1, v.Cname, v.Id) 36 | return v.Id, nil 37 | } 38 | } 39 | log.Printf("未找到指定接种地,请对比配置文件接种地是否正确!\n") 40 | return -1, errors.New("未找到指定接种地,请对比配置文件接种地是否正确!") 41 | } 42 | -------------------------------------------------------------------------------- /model/customerProduct.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type RootSource struct { 4 | Status int `json:"status"` 5 | StartDate string `json:"startDate"` 6 | EndDate string `json:"endDate"` 7 | Memo string `json:"-"` 8 | Tel string `json:"tel"` 9 | Addr string `json:"addr"` 10 | Cname string `json:"cname"` 11 | Lat float64 `json:"-"` 12 | Lng float64 `json:"-"` 13 | Distance float64 `json:"distance"` 14 | CustomerProducts []CustomerProduct `json:"list"` 15 | } 16 | type CustomerProduct struct { 17 | Id int `json:"id"` 18 | Text string `json:"text"` 19 | Price string `json:"price"` 20 | Descript string `json:"descript"` 21 | Warn string `json:"warn"` 22 | Tags []string `json:"tags"` 23 | QuestionnaireId int `json:"questionnaireId"` 24 | Remarks string `json:"remarks"` 25 | NumbersVaccine []Vaccine `json:"NumbersVaccine"` 26 | Date string `json:"date"` 27 | BtnLable string `json:"BtnLable"` 28 | Enable bool `json:"enable"` 29 | } 30 | type Payment struct { 31 | BigPic string `json:"big_pic"` 32 | IdcardLimit bool `json:"IdcardLimit"` 33 | Notice string `json:"notice"` 34 | } 35 | type Vaccine struct { 36 | Cname string `json:"cname"` 37 | Value int `json:"value"` 38 | } 39 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import "zmyy_seckill/limit" 4 | 5 | //var RequestLimitRate limit.LimitRate 6 | var ProxyIpArr []string 7 | var LR limit.LR 8 | 9 | const ( 10 | UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat" 11 | Refer = "https://servicewechat.com/wx2c7f0f3c30d99445/83/page-frame.html" 12 | Connection = "keep-alive" 13 | AcceptEncoding = "gzip, deflate, br" 14 | ContentType = "application/json" 15 | //Host = "https://106.13.187.42" 16 | Host = "https://cloud.cn2030.com" 17 | //Host = "https://183.230.139.228" 18 | //Host = "https://yun.cn2030.com" 19 | //某地区医院列表URL 20 | CustomerListUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=CustomerList" 21 | //授权URL 22 | AuthUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=auth&code=061H55000QOs8L1yHN100Ba0N43H550I" 23 | //某医院内HPV疫苗情况URL 24 | CustomerProductURL = Host + "/sc/wx/HandlerSubscribe.ashx?act=CustomerProduct" 25 | //预约用户信息 26 | UserInfoURL = Host + "/sc/wx/HandlerSubscribe.ashx?act=User" 27 | CustSubscribeDateUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=GetCustSubscribeDateAll" 28 | CaptchaVerifyUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=CaptchaVerify" 29 | GetCaptchaUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=GetCaptcha" 30 | SaveUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=Save20" 31 | //获取订单状态 32 | OrderStatusUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=GetOrderStatus" 33 | CustSubscribeDateDetailUrl = Host + "/sc/wx/HandlerSubscribe.ashx?act=GetCustSubscribeDateDetail" 34 | ) 35 | -------------------------------------------------------------------------------- /zhimiaoyiyue/customerproduct.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "zmyy_seckill/consts" 9 | "zmyy_seckill/fetcher" 10 | "zmyy_seckill/model" 11 | "zmyy_seckill/utils" 12 | ) 13 | 14 | //获取换购疫苗的productId 15 | func (e *ZMYYEngine) GetCustomerProduct(customerId int) (int, error) { 16 | url := consts.CustomerProductURL + "&id=" + strconv.Itoa(customerId) + "&lat=31.23037&lng=121.4737" 17 | headers := make(map[string]string) 18 | headers["User-Agent"] = consts.UserAgent 19 | headers["Referer"] = consts.Refer 20 | headers["cookie"] = e.Conf.Cookie 21 | headers["Connection"] = consts.Connection 22 | headers["Host"] = "cloud.cn2030.com" 23 | headers["content-type"] = "application/json" 24 | zftsl := utils.GetZFTSL() 25 | headers["zftsl"] = zftsl 26 | 27 | bytes, err := fetcher.FetchWithRatelimter(url, headers) 28 | if err != nil { 29 | //log.Printf("GetCustomerProduct() err : %v \n", err) 30 | return -1, err 31 | } 32 | customerProducts := model.RootSource{} 33 | err = utils.Transfer2CustomerProductListModel(bytes, &customerProducts) 34 | if err != nil { 35 | log.Printf("GetCustomerProduct() err: %v\n ", err) 36 | return -1, err 37 | } 38 | fmt.Printf("正在查找疫苗信息:\n") 39 | var selected model.CustomerProduct 40 | var contains bool 41 | for k, v := range customerProducts.CustomerProducts { 42 | fmt.Printf("第 %d 个疫苗:%s productId: %d\n", k+1, v.Text, v.Id) 43 | if v.Text == e.Conf.ProductName { 44 | selected = v 45 | contains = true 46 | } 47 | } 48 | if contains { 49 | fmt.Printf("====选中疫苗:%s ,其productId为 %d====\n", selected.Text, selected.Id) 50 | return selected.Id, nil 51 | } 52 | fmt.Printf("未找到指定疫苗,请对比配置文件疫苗信息是否正确!\n") 53 | return -1, errors.New("未找到指定疫苗,请对比配置文件疫苗信息是否正确!") 54 | 55 | } 56 | -------------------------------------------------------------------------------- /zhimiaoyiyue/custSubscribeDate.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | "zmyy_seckill/consts" 7 | "zmyy_seckill/fetcher" 8 | "zmyy_seckill/model" 9 | "zmyy_seckill/utils" 10 | ) 11 | 12 | /** 13 | /sc/wx/HandlerSubscribe.ashx?act=GetCustSubscribeDateAll&pid=2&id=1921&month=202102 14 | */ 15 | func (e *ZMYYEngine) GetCustSubscribeDateAll(customerId, productId, month int, ip ...string) *model.SubscribeDate { 16 | url := consts.CustSubscribeDateUrl + "&pid=" + strconv.Itoa(productId) + "&id=" + strconv.Itoa(customerId) + "&month=" + strconv.Itoa(month) 17 | headers := make(map[string]string) 18 | headers["User-Agent"] = consts.UserAgent 19 | headers["Referer"] = consts.Refer 20 | headers["cookie"] = e.Conf.Cookie 21 | headers["Connection"] = consts.Connection 22 | bytes, err := fetcher.Fetch(url, headers, ip...) 23 | if err != nil { 24 | log.Printf("GetCustSubscribeDateAll() err : %v \n", err) 25 | return nil 26 | } 27 | subsDates := model.SubscribeDate{} 28 | err = utils.Transfer2SubscribeDateModel(bytes, &subsDates) 29 | if err != nil { 30 | log.Printf("GetCustSubscribeDateAll() err: %v\n ", err) 31 | return nil 32 | } 33 | return &subsDates 34 | } 35 | func (e *ZMYYEngine) GetCustSubscribeDateDetail(date string, productId, customerId int, ip ...string) *model.SubscribeDateDetail { 36 | url := consts.CustSubscribeDateDetailUrl + "&pid=" + strconv.Itoa(productId) + "&id=" + strconv.Itoa(customerId) + "&scdate=" + date 37 | headers := make(map[string]string) 38 | headers["User-Agent"] = consts.UserAgent 39 | headers["Referer"] = consts.Refer 40 | headers["cookie"] = e.Conf.Cookie 41 | zftsl := utils.GetZFTSL() 42 | headers["zftsl"] = zftsl 43 | bytes, err := fetcher.FetchWithRatelimter(url, headers, ip...) 44 | if err != nil { 45 | return nil 46 | } 47 | dateDetails := &model.SubscribeDateDetail{} 48 | err = utils.Transfer2SubscribeDateDetailModel(bytes, dateDetails) 49 | if err != nil { 50 | return nil 51 | } 52 | dateDetails.Date = date 53 | return dateDetails 54 | } 55 | -------------------------------------------------------------------------------- /ip/ip.go: -------------------------------------------------------------------------------- 1 | package ip 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strings" 13 | "sync" 14 | "time" 15 | "zmyy_seckill/consts" 16 | "zmyy_seckill/utils" 17 | ) 18 | 19 | var ipCh = make(chan string, 100) 20 | 21 | /** 22 | 判断代理ip的有效性 23 | */ 24 | func proxyTest(ip string, wg *sync.WaitGroup) { 25 | defer wg.Done() 26 | headers := make(map[string]string) 27 | headers["User-Agent"] = consts.UserAgent 28 | headers["Referer"] = consts.Refer 29 | //testUrl := "https://cloud.cn2030.com" 30 | testUrl := "https://www.baidu.com" 31 | // 解析代理地址 32 | proxy, err := url.Parse(ip) 33 | //设置网络传输 34 | tr := &http.Transport{ 35 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 36 | Proxy: http.ProxyURL(proxy), 37 | MaxIdleConnsPerHost: 10, 38 | ResponseHeaderTimeout: time.Second * 3, 39 | } 40 | // 创建连接客户端 41 | client := &http.Client{ 42 | Transport: tr, 43 | Timeout: 5 * time.Second, 44 | } 45 | req, err := http.NewRequest("GET", testUrl, nil) 46 | for k, v := range headers { 47 | req.Header.Set(k, v) 48 | } 49 | resp, err := client.Do(req) 50 | if err != nil || resp.StatusCode != http.StatusOK { 51 | return 52 | } 53 | defer resp.Body.Close() 54 | fmt.Printf("有效ip : %v \n", ip) 55 | ipCh <- ip 56 | } 57 | func ReadIpFile() (ipArr []string) { 58 | log.Printf("========================正在验证IP=============================\n") 59 | //defer close(ipCh) 60 | path := utils.GetCurrentPath() + "/ip/ip.txt" 61 | ipFile, err := os.Open(path) 62 | if err != nil { 63 | log.Printf("未找到ip文件:%s \n", path) 64 | return 65 | } 66 | defer ipFile.Close() 67 | //按行读ip 68 | buf := bufio.NewReader(ipFile) 69 | ipWg := &sync.WaitGroup{} 70 | end := false 71 | for { 72 | ip, err := buf.ReadString('\n') 73 | ip = strings.TrimSpace(ip) 74 | if err != nil { 75 | if err == io.EOF { 76 | end = true 77 | break 78 | } else { 79 | return 80 | } 81 | } 82 | //ip验证 83 | ipWg.Add(1) 84 | go func(ip string) { 85 | proxyTest(ip, ipWg) 86 | }("http://" + ip) 87 | } 88 | ipWg.Wait() 89 | for !end { 90 | } 91 | close(ipCh) 92 | for ip := range ipCh { 93 | ipArr = append(ipArr, ip) 94 | } 95 | ipArr = append(ipArr, "") 96 | fmt.Printf("共找到 %d 个 可用 ip\n", len(ipArr)-1) 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /ip/ip.txt: -------------------------------------------------------------------------------- 1 | 8.133.191.41:80 2 | 51.79.173.167:8080 3 | 58.253.155.234:9999 4 | 158.255.215.50:16993 5 | 60.174.190.116:9999 6 | 27.43.188.238:9999 7 | 8.133.191.41:8081 8 | 58.253.158.80:9999 9 | 60.169.134.253:9999 10 | 60.169.133.140:9999 11 | 175.44.109.85:9999 12 | 171.35.150.103:9999 13 | 8.133.191.41:8080 14 | 49.89.67.36:9999 15 | 27.43.184.99:9999 16 | 117.94.143.63:9999 17 | 223.241.119.12:8888 18 | 27.43.191.242:9999 19 | 58.253.153.89:9999 20 | 60.167.135.78:8888 21 | 60.167.133.50:8888 22 | 60.168.206.138:8888 23 | 222.162.1.161:9999 24 | 58.253.159.5:9999 25 | 218.91.0.67:9999 26 | 27.43.190.58:9999 27 | 61.161.30.167:9999 28 | 27.43.190.30:9999 29 | 223.243.175.184:9999 30 | 58.253.152.47:9999 31 | 220.249.149.78:9999 32 | 27.43.190.33:9999 33 | 58.22.177.24:9999 34 | 60.168.80.208:8888 35 | 223.215.18.173:9999 36 | 180.119.93.22:9999 37 | 27.43.190.154:9999 38 | 59.33.52.56:9999 39 | 58.253.155.194:9999 40 | 42.238.83.76:9999 41 | 220.249.149.24:9999 42 | 27.43.191.190:9999 43 | 58.253.154.35:9999 44 | 58.253.151.5:9999 45 | 36.248.133.8:9999 46 | 60.168.206.110:8888 47 | 223.243.172.17:9999 48 | 140.238.228.223:3128 49 | 60.167.82.190:1133 50 | 58.255.199.138:9999 51 | 220.249.149.158:9999 52 | 60.168.206.243:8888 53 | 58.253.156.188:9999 54 | 27.43.184.58:9999 55 | 220.249.149.118:9999 56 | 27.43.185.151:9999 57 | 58.255.4.55:9999 58 | 49.86.59.49:9999 59 | 42.238.90.221:9999 60 | 60.174.191.186:9999 61 | 49.70.95.186:9999 62 | 150.255.129.42:9999 63 | 49.86.176.33:9999 64 | 58.253.159.135:9999 65 | 58.253.159.124:9999 66 | 49.70.33.210:9999 67 | 58.22.177.169:9999 68 | 58.255.206.60:9999 69 | 60.168.80.187:8888 70 | 60.19.239.194:9999 71 | 58.253.152.105:9999 72 | 59.33.54.200:9999 73 | 60.169.133.228:9999 74 | 60.169.134.127:9999 75 | 27.43.188.141:9999 76 | 58.253.156.15:9999 77 | 58.255.206.230:9999 78 | 58.253.156.51:9999 79 | 60.168.206.115:8888 80 | 60.168.81.175:1133 81 | 222.162.5.241:9999 82 | 58.253.158.79:9999 83 | 223.215.11.13:9999 84 | 60.169.134.123:9999 85 | 36.57.199.155:9999 86 | 49.86.181.210:9999 87 | 42.7.28.187:9999 88 | 58.253.153.229:9999 89 | 60.168.80.228:8888 90 | 220.249.149.69:9999 91 | 60.168.207.197:1133 92 | 60.167.82.114:8888 93 | 58.255.198.244:9999 94 | 58.255.5.128:9999 95 | 60.168.206.56:8888 96 | 58.253.152.167:9999 97 | 60.167.135.117:8888 98 | 60.168.206.169:8888 99 | 60.169.135.111:9999 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 知苗易约抢购小程序[由于知苗更新太快,本人已工作时间不足,维护跟不上,当前脚本已失效] 2 | 3 | 目前开发完成,暂未测试,因而暂不给(以后可能也不给)使用说明。本项目是作者本人写的第一个Go程序。只作学习交流使用。之初遇到不少的问题,多亏一些大佬点拨,不胜感激。【**如有帮助,请点个star**】 4 | 5 | 【2021-04-14】**v1.2.0** 增加IP轮询机制【暂时无法使用】,说明如下: 6 | 7 | * 无法使用原因:每个ip有自己的cookie,不可共享Cookie。 8 | 9 | * ip代理需要自行添加,文件在ip/ip.txt,ip需要支持https 10 | * 限速器以IP为单位,每个IP都将拥有一个线程进行预约 11 | * 验证码的获取、识别、订单提交仍以串行方式(加锁)运行,这是本项目最大的性能瓶颈 12 | 13 | 【2021-04-09】**v1.1.0** 增加IP代理 14 | 15 | 【2021-03-21】**v1.0.1** 删除用于获取zftsl参数的js文件 16 | 17 | 【2021-03-20】**v1.0.0** 验证码exe识别模块改为直接调用py脚本,以支持非windows用户使用 18 | 19 | 20 | 21 | * 问题进度 22 | * **【已解决】增加IP代理,详细说明如下** 23 | * IP使用的是免费的IP,所以速度不会很快,甚至有的IP会不可用 24 | * 默认已开启IP代理功能,默认使用本机IP,当本地IP发生error时切换代理IP 25 | * **【已解决】2021/2/22 -运行时出现403错误,经调试后发现小程序做出了相当大的更新导致的。原来请求的url都添加了一些动态的变化参数如key302和expire302** 26 | * 【问题详细说明】当程序请求原来的URL时,会返回403错误,但实际是由于302重定向引起的。即当第一次请求URL时,实际请求的资源是不存在的,小程序后端会重定向到一个新的URL,并赋予key302和expire302两个参数,而在zmyy程序中是无法被重定向的。 27 | * 【解决方案】若想拿到重定向的url,可以通过请求原url,并在响应报文resp的header中得到Location参数,即为重定向的url 28 | * **【已解决】获取验证码时,并未得到验证码图片,而是一大串文字** 29 | * 该大串响应报文是图片的Base64编码,可转换成图片 30 | * **【已解决】滑块验证码的x轴坐标可通过opencv来解决。附一个大佬的Python代码,亲测可用https://github.com/crazyxw/SlideCrack** 31 | * 在golang调用Python程序时,若该*.py文件中没有导入第三方依赖,可顺利被go调用。当导入三方依赖时,会报错,这种情况目前本人无法解决【已解决,见版本v1.0.0】。 32 | * 由于本人是Windows系统,故将python代码打包成了*.exe可执行程序进行调用,测试后可行 33 | * **【已解决】滑块验证码获取需要一个zftsl的参数,猜测与时间戳和sessionid有关,目前无法得到该值,故无法拿到滑块验证码文本。** 34 | * 逆向小程序后,拿到相关的js文件,找到了zftsl参数的来源。 35 | * 在使用golang调用js时,出现了与调用Python一样的情况,即不能有三方依赖。作者通过将几个js文件函数拼接到了一个文件中-app.js。go调用该js后可得到zftsl参数。测试通过。 36 | * **【已解决】在拿到滑块验证码的Base64编码时,发现该编码太长导致[]byte接收不完全,因而无法复原验证码图片。** 37 | * 通过将resp.body写入到本地文件中,再用json解析本地文件得到两个图片的base64编码 38 | * 需要注意的是,在多协程环境下,会有大量的文件读写操作,可能会造成文件的覆盖。未来需要对文件名(如 file+协程ID)进行区分,在验证码识别完成后可不对文件进行删除操作,直接覆盖对应文件名 39 | * **【已解决】在代码运行过程中出现503异常,Degug时程序却运行正常,猜测是请求过快导致被服务禁止** 40 | * 通过在关键请求前后增加sleep时间,降低访问频率 41 | * 增加LimitRate限流,在实际测试中发现,当请求速率设置有1次/s时,有小概率被限流的风险。当频率为1次/2s时,不会被限流。因此暂时设置速率为1次/2s 42 | * **【已解决】增加多线程以提高运行效率** 43 | * 增加多线程访问时,出现验证码识别失败的问题。猜测使用同一session并发请求获取验证码图片时,会导致小程序服务端前一次请求的验证码结果失效,即多线程并发请求和验证验证码会出现混乱状况。 44 | * 为解决上述问题,作者采用加锁的方式。从验证码获取到识别完成这一流程整合成一个步骤对其加锁,当流程中有任一操作失败时就释放锁,把请求资源让给其他线程执行。测试可行。 45 | * 开发进度 46 | * 开完发成。 47 | * 项目结构说明: 48 | * 用户信息在config/conf.yaml中手动配置 49 | * 项目build后会生成可执行文件zmyy_seckill.exe,使用时勿修改文件名。(因路径使用了正则匹配。) 50 | * 本人已尽可能减少使用难度,但conf.yaml中的Cookie因技术原因无法解决,故需要使用者本人获取 51 | * 推荐在电脑上打开小程序,并使用fiddler抓包即可 52 | 53 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/thedevsaddam/gojsonq/v2" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | "zmyy_seckill/utils" 9 | ) 10 | 11 | type RootConf struct { 12 | CustomerConf `yaml:"zhiyiyuemiao"` 13 | } 14 | 15 | type CustomerConf struct { 16 | Province string `yaml:province,omitempty default:""` 17 | City string `yaml:"city,omitempty" default:""` 18 | District string `yaml:"district,omitempty" default:""` 19 | ProductName string `yaml:"productName,omitempty" default:1` 20 | Birthday string `yaml:"birthday,omitempty" default:""` 21 | Tel string `yaml:"tel,omitempty" default:""` 22 | Sex int `yaml:"sex,omitempty" default:1` 23 | Name string `yaml:"name,omitempty" default:""` 24 | IdCard string `yaml:"idcard,omitempty" default:""` 25 | Cookie string `yaml:"cookie,omitempty" default:""` 26 | CityCode string `yaml:"-"` 27 | CustomerName string `yaml:"customerName,omitempty" default:""` 28 | Month int `yaml:"month,omitempty" default:202102` 29 | SubscribeTime string `yaml:"subscribeTime,omitempty" default:""` 30 | } 31 | 32 | func (c *RootConf) GetConf() (CustomerConf, error) { 33 | path := utils.GetCurrentPath() 34 | confPath := path + "/config/conf.yaml" 35 | yamlFile, err := ioutil.ReadFile(confPath) 36 | fmt.Printf("当前执行路径 : %s \n", path) 37 | if err != nil { 38 | fmt.Printf("yaml file get err #%v \n", err) 39 | return CustomerConf{}, err 40 | } 41 | var conf = RootConf{} 42 | err = yaml.Unmarshal(yamlFile, &conf) 43 | if err != nil { 44 | fmt.Printf("failed to unmarshal : %v\n", err) 45 | } 46 | getCityCode(path, &conf) 47 | return conf.CustomerConf, nil 48 | } 49 | func getCityCode(path string, conf *RootConf) string { 50 | cityJsonPath := path + "/config/city.json" 51 | jq := gojsonq.New().File(cityJsonPath).From("citycode") 52 | if conf.City != "" { 53 | r := jq.Select("children").Where("name", "=", conf.Province).Get().([]interface{})[0].(map[string]interface{})["children"].([]interface{}) 54 | for _, v := range r { 55 | vmap := v.(map[string]interface{}) 56 | if vmap["name"] == conf.City { 57 | conf.CityCode = vmap["value"].(string) + "00" 58 | break 59 | } 60 | } 61 | } else if conf.Province != "" { 62 | r := jq.Select("value").Where("name", "=", conf.Province).Get().([]interface{})[0].(map[string]interface{}) 63 | conf.CityCode = r["value"].(string) + "0000" 64 | } 65 | return "" 66 | } 67 | -------------------------------------------------------------------------------- /zhimiaoyiyue/verify.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | "zmyy_seckill/consts" 7 | "zmyy_seckill/fetcher" 8 | "zmyy_seckill/model" 9 | "zmyy_seckill/utils" 10 | ) 11 | 12 | /** 13 | 获取验证码图片 14 | */ 15 | func (e *ZMYYEngine) GetVerifyPic(dateDetail model.DateDetail, ip ...string) (path string, err error) { 16 | url := consts.GetCaptchaUrl 17 | headers := make(map[string]string) 18 | headers["User-Agent"] = consts.UserAgent 19 | headers["Referer"] = consts.Refer 20 | headers["Cookie"] = e.Conf.Cookie 21 | //headers["Connection"] = consts.Connection 22 | headers["content-type"] = consts.ContentType 23 | headers["Accept-Encoding"] = consts.AcceptEncoding 24 | zftsl := utils.GetZFTSL() 25 | headers["zftsl"] = zftsl 26 | prefix := dateDetail.Date + "-" + strings.Replace(dateDetail.StartTime, ":", "_", -1) + "-" + strings.Replace(dateDetail.EndTime, ":", "_", -1) 27 | err = fetcher.FetchCaptcha(url, headers, prefix, ip...) 28 | if err != nil { 29 | return "", err 30 | } 31 | path = utils.GetCurrentPath() + "/imgs/" 32 | //Base64转图片 33 | err = utils.Base64ToPics(path + prefix) 34 | if err != nil { 35 | return "", err 36 | } 37 | return path + prefix, nil 38 | } 39 | 40 | /** 41 | 滑块验证码验证 42 | */ 43 | func (e *ZMYYEngine) CaptchaVerify(prefix string, ip ...string) (guid string, err error) { 44 | tigerPath, dragonPath, processPath := prefix+"-tiger.png", prefix+"-dragon.png", prefix+"-process.png" 45 | //2.图片验证码识别 46 | x, err := utils.CallPythonScript(tigerPath, dragonPath, processPath) 47 | 48 | if err != nil { 49 | return "", err 50 | } 51 | url := consts.CaptchaVerifyUrl + "&token=&x=" + x + "&y=5" 52 | 53 | headers := make(map[string]string) 54 | headers["User-Agent"] = consts.UserAgent 55 | headers["Referer"] = consts.Refer 56 | headers["Cookie"] = e.Conf.Cookie 57 | //headers["Connection"] = consts.Connection 58 | headers["content-type"] = consts.ContentType 59 | headers["Accept-Encoding"] = consts.AcceptEncoding 60 | 61 | zftsl := utils.GetZFTSL() 62 | headers["zftsl"] = zftsl 63 | bytes, err := fetcher.FetchWithRatelimter(url, headers, ip...) 64 | if err != nil { 65 | return "", err 66 | } 67 | m := &model.VerifyResultModel{} 68 | err = utils.Transfer2VerifyResultModel(bytes, m) 69 | //删除验证码图片 70 | defer utils.DeleteFile(tigerPath, dragonPath, processPath, prefix) 71 | if err != nil || m.Status != 200 || m.Guid == "" { 72 | log.Printf("CaptchaVerify() 验证码%s验证失败 err:%v; %s\n", prefix, err, bytes) 73 | return "", err 74 | } 75 | return m.Guid, nil 76 | } 77 | -------------------------------------------------------------------------------- /captcha.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import cv2 4 | import sys 5 | class SlideCrack(object): 6 | def __init__(self, gap, bg, out): 7 | """ 8 | init code 9 | :param gap: 缺口图片 10 | :param bg: 背景图片 11 | :param out: 输出图片 12 | """ 13 | self.gap = gap 14 | self.bg = bg 15 | self.out = out 16 | 17 | @staticmethod 18 | def clear_white(img): 19 | # 清除图片的空白区域,这里主要清除滑块的空白 20 | img = cv2.imread(img) 21 | rows, cols, channel = img.shape 22 | min_x = 255 23 | min_y = 255 24 | max_x = 0 25 | max_y = 0 26 | for x in range(1, rows): 27 | for y in range(1, cols): 28 | t = set(img[x, y]) 29 | if len(t) >= 2: 30 | if x <= min_x: 31 | min_x = x 32 | elif x >= max_x: 33 | max_x = x 34 | 35 | if y <= min_y: 36 | min_y = y 37 | elif y >= max_y: 38 | max_y = y 39 | img1 = img[min_x:max_x, min_y: max_y] 40 | return img1 41 | 42 | def template_match(self, tpl, target): 43 | th, tw = tpl.shape[:2] 44 | result = cv2.matchTemplate(target, tpl, cv2.TM_CCOEFF_NORMED) 45 | # 寻找矩阵(一维数组当作向量,用Mat定义) 中最小值和最大值的位置 46 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) 47 | tl = max_loc 48 | br = (tl[0] + tw, tl[1] + th) 49 | # 绘制矩形边框,将匹配区域标注出来 50 | # target:目标图像 51 | # tl:矩形定点 52 | # br:矩形的宽高 53 | # (0,0,255):矩形边框颜色 54 | # 1:矩形边框大小 55 | cv2.rectangle(target, tl, br, (0, 0, 255), 2) 56 | cv2.imwrite(self.out, target) 57 | return tl[0] 58 | 59 | @staticmethod 60 | def image_edge_detection(img): 61 | edges = cv2.Canny(img, 100, 200) 62 | return edges 63 | 64 | def discern(self): 65 | img1 = self.clear_white(self.gap) 66 | img1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY) 67 | slide = self.image_edge_detection(img1) 68 | 69 | back = cv2.imread(self.bg, 0) 70 | back = self.image_edge_detection(back) 71 | 72 | slide_pic = cv2.cvtColor(slide, cv2.COLOR_GRAY2RGB) 73 | back_pic = cv2.cvtColor(back, cv2.COLOR_GRAY2RGB) 74 | x = self.template_match(slide_pic, back_pic) 75 | # 输出横坐标, 即 滑块在图片上的位置 76 | print(x) 77 | return x 78 | 79 | 80 | 81 | if __name__ == "__main__": 82 | args = sys.argv[1:] 83 | # 滑块图片 84 | tigerPath = args[0] 85 | # 背景图片 86 | dragonPath = args[1] 87 | 88 | # 处理结果图片,用红线标注 89 | processedPath = args[2] 90 | sc = SlideCrack(tigerPath, dragonPath, processedPath) 91 | sc.discern() 92 | -------------------------------------------------------------------------------- /zhimiaoyiyue/order.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "zmyy_seckill/consts" 11 | "zmyy_seckill/fetcher" 12 | "zmyy_seckill/model" 13 | "zmyy_seckill/utils" 14 | ) 15 | 16 | var orderLock sync.Mutex 17 | 18 | func (e *ZMYYEngine) SaveOrder(dateDetail model.DateDetail, productId string, guid string, ip ...string) (ok bool, err error) { 19 | ok = false 20 | url := consts.SaveUrl + "&birthday=" + e.Conf.Birthday + "&tel=" + e.Conf.Tel + "&sex=" + strconv.Itoa(e.Conf.Sex) + "&cname=" + utils.UrlEncode(e.Conf.Name) + "&doctype=1&idcard=" + e.Conf.IdCard + "&mxid=" + dateDetail.Mxid + "&date=" + dateDetail.Date + "&pid=" + productId + "&Ftime=1&guid=" + guid 21 | headers := make(map[string]string) 22 | headers["User-Agent"] = consts.UserAgent 23 | headers["Referer"] = consts.Refer 24 | headers["Cookie"] = e.Conf.Cookie 25 | headers["Connection"] = "keep-alive" 26 | zftsl := utils.GetZFTSL() 27 | headers["zftsl"] = zftsl 28 | bytes, err := fetcher.FetchWithRatelimter(url, headers, ip...) 29 | if err != nil { 30 | log.Printf("%v SaveOrder() err : %v \n", ip, err) 31 | return ok, err 32 | } 33 | //如果状态码为200,则订单提交成功 34 | res := string(bytes) 35 | if strings.Index(res, `"status":200`) == -1 { 36 | log.Printf("订单 %s: %s-%s 提交失败:%s \n", dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime, res) 37 | return ok, errors.New("订单提交失败:" + res) 38 | } 39 | log.Printf("%v 订单 %s: %s-%s 提交成功 \n", ip, dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime) 40 | //获取订单状态 41 | ok, _, err = e.GetOrderStatus(dateDetail, ip...) 42 | return ok, err 43 | } 44 | 45 | /** 46 | 获取订单状态 47 | */ 48 | func (e *ZMYYEngine) GetOrderStatus(dateDetail model.DateDetail, ip ...string) (bool, []byte, error) { 49 | url := consts.OrderStatusUrl 50 | headers := make(map[string]string) 51 | headers["Referer"] = consts.Refer 52 | zftsl := utils.GetZFTSL() 53 | headers["zftsl"] = zftsl 54 | headers["Cookie"] = e.Conf.Cookie 55 | resp, err := fetcher.FetchWithRatelimter(url, headers, ip...) 56 | if err != nil { 57 | return false, nil, err 58 | } 59 | //如果状态码为200,则订单提交成功 60 | res := string(resp) 61 | if strings.Index(res, `"status":200`) == -1 { 62 | log.Printf("%v 订单: %s: %s-%s 提交成功,但实际未生效:%s\n", ip, 63 | dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime, res) 64 | return false, resp, errors.New("订单实际未生效:" + res) 65 | } 66 | return true, resp, nil 67 | } 68 | 69 | /** 70 | 封装下单过程 71 | */ 72 | func (e *ZMYYEngine) Bingo(dateDetail model.DateDetail, productId string, ctx context.Context, ip ...string) (bool, error) { 73 | orderLock.Lock() 74 | select { 75 | case <-ctx.Done(): 76 | orderLock.Unlock() 77 | return false, nil 78 | default: 79 | path := "" 80 | var err error 81 | //1.获取验证码 82 | log.Printf("%v 正在获取验证码图片:%s %s-%s\n", ip, dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime) 83 | path, err = e.GetVerifyPic(dateDetail, ip...) 84 | if path == "" { 85 | orderLock.Unlock() 86 | return false, err 87 | } 88 | //2.识别验证码 89 | guid := "" 90 | log.Printf("%v 正在识别验证码图片:%s %s-%s\n", ip, 91 | dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime) 92 | guid, err = e.CaptchaVerify(path, ip...) 93 | if guid == "" { 94 | orderLock.Unlock() 95 | return false, err 96 | } 97 | //3.下单,重试2次 98 | log.Printf("%v 验证码图片:%s %s-%s 识别成功,尝试下单中...\n", ip, 99 | dateDetail.Date, dateDetail.StartTime, dateDetail.EndTime) 100 | ok := false 101 | try := 0 102 | for !ok && try < 2 { 103 | ok, err = e.SaveOrder(dateDetail, productId, guid, ip...) 104 | try++ 105 | } 106 | if ok { 107 | orderLock.Unlock() 108 | return true, nil 109 | } 110 | orderLock.Unlock() 111 | return false, err 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /fetcher/fetcher.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strconv" 13 | "time" 14 | "zmyy_seckill/consts" 15 | "zmyy_seckill/utils" 16 | ) 17 | 18 | //使用限流器请求url,限流速度根据IP进行区分 19 | func FetchWithRatelimter(zhimiaoUrl string, headers map[string]string, v ...string) ([]byte, error) { 20 | if v == nil { 21 | consts.LR.LRMap[""].Limit() 22 | } else { 23 | for _, ip := range v { 24 | consts.LR.LRMap[ip].Limit() 25 | } 26 | } 27 | return Fetch(zhimiaoUrl, headers, v...) 28 | } 29 | 30 | //常规请求url 31 | func Fetch(zhimiaoUrl string, headers map[string]string, v ...string) ([]byte, error) { 32 | //var proxy *url.URL 33 | var proxy func(*http.Request) (*url.URL, error) 34 | proxyIp := "" 35 | //目前v参数中为代理ip,若v长度>0,则需要使用代理 36 | for _, ip := range v { 37 | proxyIp = ip 38 | } 39 | if proxyIp != "" { 40 | proxyUrl, _ := url.Parse(proxyIp) 41 | proxy = http.ProxyURL(proxyUrl) 42 | } 43 | log.Printf("%v 正在发起请求.... url: %s\n", v, zhimiaoUrl) 44 | tr := &http.Transport{ 45 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 46 | Proxy: proxy, 47 | MaxIdleConnsPerHost: 10, 48 | ResponseHeaderTimeout: time.Second * 3, 49 | } 50 | client := &http.Client{ 51 | Transport: tr, 52 | Timeout: 5 * time.Second, 53 | } 54 | req, err := http.NewRequest("GET", zhimiaoUrl, nil) 55 | for k, v := range headers { 56 | req.Header.Set(k, v) 57 | } 58 | resp, err := client.Do(req) 59 | if err != nil { 60 | log.Printf("%s fetch err : %v \n", proxyIp, err) 61 | //utils.SetRandomIP() 62 | return nil, err 63 | } 64 | //切换IP 65 | defer resp.Body.Close() 66 | //如果有重定向错误,则重定向 67 | if resp.Request.Response != nil && resp.Request.Response.StatusCode == http.StatusFound { 68 | zhimiaoUrl = consts.Host + resp.Request.Response.Header.Get("Location") 69 | return Fetch(zhimiaoUrl, headers, v...) 70 | } 71 | if resp.StatusCode != http.StatusOK { 72 | log.Printf("%v fetch err : %v \n", v, resp.StatusCode) 73 | //切换IP 74 | //utils.SetRandomIP() 75 | return nil, fmt.Errorf("wrong status code: %d", resp.StatusCode) 76 | } 77 | contents, err := ioutil.ReadAll(resp.Body) 78 | if err != nil { 79 | log.Printf("%v fetch err : %v \n", v, err) 80 | //切换IP 81 | //utils.SetRandomIP() 82 | return nil, err 83 | } 84 | return contents, nil 85 | } 86 | func FetchCaptcha(captchaUrl string, headers map[string]string, prefix string, v ...string) error { 87 | if v == nil { 88 | consts.LR.LRMap[""].Limit() 89 | } else { 90 | for _, ip := range v { 91 | consts.LR.LRMap[ip].Limit() 92 | } 93 | } 94 | var proxy func(*http.Request) (*url.URL, error) 95 | proxyIp := "" 96 | //目前v参数中为代理ip,若v长度>0,则需要使用代理 97 | for _, ip := range v { 98 | proxyIp = ip 99 | } 100 | if proxyIp != "" { 101 | proxyUrl, _ := url.Parse(proxyIp) 102 | proxy = http.ProxyURL(proxyUrl) 103 | } 104 | log.Printf("%v 正在发起请求.... captchaUrl: %s\n", v, captchaUrl) 105 | tr := &http.Transport{ 106 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 107 | Proxy: proxy, 108 | MaxIdleConnsPerHost: 10, 109 | ResponseHeaderTimeout: time.Second * 3, 110 | } 111 | client := &http.Client{ 112 | Transport: tr, 113 | Timeout: 5 * time.Second, 114 | } 115 | req, err := http.NewRequest("GET", captchaUrl, nil) 116 | for k, v := range headers { 117 | req.Header.Set(k, v) 118 | } 119 | resp, err := client.Do(req) 120 | if err != nil { 121 | log.Printf("%v fetch err : %v \n", v, err) 122 | //切换IP 123 | //utils.SetRandomIP() 124 | return err 125 | } 126 | //如果有重定向错误,则重定向 127 | if resp.Request.Response != nil && resp.Request.Response.StatusCode == http.StatusFound { 128 | captchaUrl = consts.Host + resp.Request.Response.Header.Get("Location") 129 | return FetchCaptcha(captchaUrl, headers, prefix) 130 | } 131 | b, err := strconv.Atoi(resp.Header.Get("Content-Length")) 132 | if err != nil || resp.StatusCode != http.StatusOK || b < 100 { 133 | fmt.Printf("获取验证码图片失败,请求可能被禁止!code: %d\n", resp.StatusCode) 134 | log.Printf("%v fetch err : %v \n", v, err) 135 | //utils.SetRandomIP() 136 | return errors.New("获取验证码图片失败,请求可能被禁止!") 137 | } 138 | defer resp.Body.Close() 139 | path := utils.GetCurrentPath() 140 | path = path + "/imgs/" + prefix 141 | f, _ := os.Create(path) 142 | defer f.Close() 143 | 144 | buf := make([]byte, 1024*1024) 145 | for { 146 | n, _ := resp.Body.Read(buf) 147 | if n == 0 { 148 | break 149 | } 150 | f.Write(buf[:n]) 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "math/rand" 11 | "net/url" 12 | "os" 13 | "os/exec" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "time" 18 | "zmyy_seckill/consts" 19 | "zmyy_seckill/model" 20 | ) 21 | 22 | func Transfer2Model(jsonCont []byte, m interface{}) (interface{}, error) { 23 | switch m.(type) { 24 | case model.CustomerList: 25 | res := &model.CustomerList{} 26 | err := json.Unmarshal(jsonCont, res) 27 | if err != nil { 28 | fmt.Printf("Transfer2Model err:%v\n", err) 29 | return nil, err 30 | } 31 | return *res, nil 32 | } 33 | return nil, nil 34 | } 35 | func Transfer2CustomerListModel(jsonCont []byte, cumtomers *model.CustomerList) error { 36 | err := json.Unmarshal(jsonCont, &cumtomers) 37 | if err != nil { 38 | fmt.Printf("Transfer2CustomerListModel err:%v\n", err) 39 | return err 40 | } 41 | return nil 42 | } 43 | func Transfer2CustomerProductListModel(jsonCont []byte, m *model.RootSource) error { 44 | err := json.Unmarshal(jsonCont, &m) 45 | if err != nil { 46 | return err 47 | } 48 | return nil 49 | } 50 | 51 | func Transfer2SubscribeDateModel(jsonCont []byte, m *model.SubscribeDate) error { 52 | err := json.Unmarshal(jsonCont, &m) 53 | if err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | func Transfer2VerifyResultModel(jsonCont []byte, m *model.VerifyResultModel) error { 59 | err := json.Unmarshal(jsonCont, &m) 60 | if err != nil { 61 | return err 62 | } 63 | return nil 64 | } 65 | func Transfer2SubscribeDateDetailModel(jsonCont []byte, m *model.SubscribeDateDetail) error { 66 | err := json.Unmarshal(jsonCont, &m) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | //将Base64文件(../imgs/veryfiPics)转成图片 74 | func Base64ToPics(filePath string) error { 75 | data, err := ioutil.ReadFile(filePath) 76 | if err != nil { 77 | fmt.Printf("Base64ToPics() can not load file err : %v\n", err) 78 | return err 79 | } 80 | m := &model.VerifyPicModel{} 81 | err = json.Unmarshal(data, m) 82 | if err != nil { 83 | return err 84 | } 85 | d, _ := base64.StdEncoding.DecodeString(m.Dragon) 86 | t, _ := base64.StdEncoding.DecodeString(m.Tiger) 87 | fd, _ := os.OpenFile(filePath+"-dragon.png", os.O_RDWR|os.O_CREATE, os.ModePerm) 88 | ft, _ := os.OpenFile(filePath+"-tiger.png", os.O_RDWR|os.O_CREATE, os.ModePerm) 89 | defer fd.Close() 90 | defer ft.Close() 91 | _, err = fd.Write(d) 92 | if err != nil { 93 | fmt.Printf("Base64文件转图片失败! err : %v", err) 94 | return err 95 | } 96 | _, err = ft.Write(t) 97 | if err != nil { 98 | fmt.Printf("Base64文件转图片失败! err : %v", err) 99 | return err 100 | } 101 | return nil 102 | } 103 | 104 | /** 105 | 调用Python脚本破解验证码 106 | */ 107 | func CallPythonScript(tigerPath, dragonPath, procssPath string) (string, error) { 108 | path := GetCurrentPath() 109 | exePath := path + "/captcha.py" 110 | args := []string{exePath, tigerPath, dragonPath, procssPath} 111 | out, err := exec.Command("python", args...).Output() 112 | if err != nil { 113 | fmt.Printf("滑块验证码识别失败! 图片为: %s, err: %v\n", dragonPath, err.Error()) 114 | return "", err 115 | } 116 | str := strings.Replace(string(out), "\r", "", -1) 117 | str = strings.Replace(str, "\n", "", -1) 118 | //fmt.Printf("滑块坐标为: %s\n", str) 119 | return str, nil 120 | } 121 | func GetZFTSL() string { 122 | d := []byte("zfsw_" + strconv.FormatInt(time.Now().Unix()/10, 10)) 123 | m := md5.New() 124 | m.Write(d) 125 | return hex.EncodeToString(m.Sum(nil)) 126 | } 127 | 128 | func UrlEncode(orgUrl string) string { 129 | encodeUrl := url.QueryEscape(orgUrl) 130 | return encodeUrl 131 | } 132 | func ParseSessionId(s string) string { 133 | const sessionIdRe = `ASP.NET_SessionId=([^;]+)` 134 | compile := regexp.MustCompile(sessionIdRe) 135 | match := compile.FindSubmatch([]byte(s)) 136 | return string(match[1]) 137 | } 138 | func GetCurrentPath() string { 139 | dir, err := os.Getwd() 140 | if err != nil { 141 | fmt.Printf("Get current process path failed . err : %v \n", err) 142 | return "" 143 | } 144 | //dir = strings.Replace(dir, "\\", "/", -1) 145 | //const pathRe = `([0-9a-zA-z:]*[0-9a-zA-Z/]+/zmyy_seckill)` 146 | //compile := regexp.MustCompile(pathRe) 147 | //match := compile.FindSubmatch([]byte(dir)) 148 | //if len(match) > 1 { 149 | // dir = string(match[1]) 150 | //} 151 | return dir 152 | } 153 | 154 | func DeleteFile(path ...string) { 155 | for _, v := range path { 156 | err := os.Remove(v) 157 | if err != nil { 158 | fmt.Printf("删除文件%s失败:%v\n", v, err) 159 | } 160 | } 161 | } 162 | 163 | func SetRandomIP() string { 164 | selectedIp := "" 165 | if consts.ProxyIpArr == nil || len(consts.ProxyIpArr) == 0 { 166 | //如果IP池IP用尽,则使用本机IP 167 | return selectedIp 168 | } 169 | //随机从ip池中获取一个ip 170 | index := rand.Intn(len(consts.ProxyIpArr)) 171 | selectedIp = consts.ProxyIpArr[index] 172 | //删除该ip 173 | consts.ProxyIpArr = append(consts.ProxyIpArr[:index], consts.ProxyIpArr[index+1:]...) 174 | return selectedIp 175 | } 176 | -------------------------------------------------------------------------------- /zhimiaoyiyue/engine.go: -------------------------------------------------------------------------------- 1 | package zhimiaoyiyue 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "strconv" 10 | "sync" 11 | "time" 12 | "zmyy_seckill/config" 13 | "zmyy_seckill/consts" 14 | "zmyy_seckill/model" 15 | ) 16 | 17 | type ZMYYEngine struct { 18 | Conf config.CustomerConf 19 | } 20 | 21 | type SecKill interface { 22 | GetCustomerList() (*model.CustomerList, error) 23 | } 24 | 25 | var wg2 sync.WaitGroup 26 | var flag bool 27 | var detailLock sync.Mutex 28 | 29 | func (e *ZMYYEngine) Init() { 30 | var c config.RootConf 31 | conf, err2 := c.GetConf() 32 | if err2 != nil { 33 | panic(err2) 34 | } 35 | e.Conf = conf 36 | log.SetPrefix("【zmyy-seckill】") 37 | } 38 | 39 | /** 40 | 获取疫苗可预约日期 41 | */ 42 | func getAvailableDates(customerId, productId, month int, 43 | subscribeDateChan chan<- *model.SubscribeDate, e *ZMYYEngine, ctx context.Context, stop context.CancelFunc, ip ...string) { 44 | for { 45 | select { 46 | case <-ctx.Done(): 47 | return 48 | default: 49 | log.Printf("%v 正在获取dates\n", ip) 50 | subscribeDates := e.GetCustSubscribeDateAll(customerId, productId, month, ip...) 51 | if subscribeDates == nil || len(subscribeDates.Dates) == 0 { 52 | log.Printf("%v 未获取到可预约日期,尝试重新获取...\n", ip) 53 | } else { 54 | subscribeDateChan <- subscribeDates 55 | stop() 56 | } 57 | } 58 | 59 | } 60 | } 61 | func deleteSliceSafe(availableDates []model.Dates, v model.Dates, index int) []model.Dates { 62 | detailLock.Lock() 63 | if index < len(availableDates) && v == availableDates[index] { 64 | availableDates = append(availableDates[:index], availableDates[index+1:]...) 65 | } else { 66 | for i := 0; i < len(availableDates); i++ { 67 | if v == availableDates[i] { 68 | availableDates = append(availableDates[:i], availableDates[i+1:]...) 69 | break 70 | } 71 | } 72 | } 73 | detailLock.Unlock() 74 | return availableDates 75 | } 76 | 77 | /** 78 | 获取筛选后的日期的可预约时间段 79 | */ 80 | func getDateDetail(dateDetailCh chan model.DateDetail, 81 | productId int, customerId int, e *ZMYYEngine, 82 | ctx context.Context, stop context.CancelFunc, 83 | ip ...string) { 84 | for { 85 | select { 86 | case <-ctx.Done(): 87 | return 88 | default: 89 | //如果日期全部请求完毕,则返回 90 | if len(availableDates) == 0 { 91 | stop() 92 | return 93 | } 94 | //随机从availableDates切片中取一个日期进行请求,加锁防止其他线程删除切片元素 95 | detailLock.Lock() 96 | index := rand.Intn(len(availableDates)) 97 | v := availableDates[index] 98 | detailLock.Unlock() 99 | log.Printf("%v 尝试获取 %s 疫苗信息...\n", ip, v.Date) 100 | dateDetails := e.GetCustSubscribeDateDetail(v.Date, productId, customerId, ip...) 101 | if dateDetails == nil { 102 | log.Printf("%v 未找到 %s 的可预约时间,尝试查找下一个时间...\n", ip, v.Date) 103 | } else { 104 | //找到日期具体时间段后将日期从切片中删除,需要注意原子性问题,故加锁并二次确认当前位置是否还是该日期值 105 | availableDates = deleteSliceSafe(availableDates, v, index) 106 | //处理获取到的具体预约时间段,多线程情况下可能添加重复日期,先不管 107 | for _, v := range dateDetails.DateDetails { 108 | if v.Qty <= 0 { 109 | continue 110 | } 111 | v.Date = dateDetails.Date 112 | dateDetailCh <- v 113 | } 114 | } 115 | } 116 | } 117 | 118 | } 119 | 120 | var availableDates = make([]model.Dates, 0) 121 | 122 | func (e *ZMYYEngine) Run(customerId, productId int, startTime time.Time) { 123 | var subscribeDateCh = make(chan *model.SubscribeDate) 124 | var dateDetailCh = make(chan model.DateDetail, 50) 125 | //1. 使用多个ip多线程获取疫苗可预约的日期 126 | getDatesCtx, stopGetDates := context.WithCancel(context.Background()) 127 | for i := 0; i < len(consts.ProxyIpArr); i++ { 128 | go getAvailableDates(customerId, productId, e.Conf.Month, subscribeDateCh, 129 | e, getDatesCtx, stopGetDates, consts.ProxyIpArr[i]) 130 | } 131 | log.Println("正在等待获取日期...") 132 | subscribeDates := <-subscribeDateCh 133 | //2.将获取到的日期进行筛选,因为有的日期已经被约满了 134 | cnt := 0 135 | for _, v := range subscribeDates.Dates { 136 | if v.Enable == false { 137 | continue 138 | } 139 | cnt++ 140 | fmt.Printf("日期:%s \n", v.Date) 141 | availableDates = append(availableDates, v) 142 | } 143 | fmt.Printf("共找到 %d个 可预约的日期\n", cnt) 144 | log.Println("正在等待获取预约的时间段...") 145 | //3.获取筛选后的日期的可预约时间段 146 | getDetailCtx, stopGetDetail := context.WithCancel(context.Background()) 147 | for i := 0; i < len(consts.ProxyIpArr); i++ { 148 | go getDateDetail(dateDetailCh, productId, 149 | customerId, e, getDetailCtx, stopGetDetail, consts.ProxyIpArr[i]) 150 | } 151 | //4.SecKill 152 | var secKillWg sync.WaitGroup 153 | seckillCtx, stopSeckill := context.WithCancel(context.Background()) 154 | for { 155 | select { 156 | case <-seckillCtx.Done(): 157 | goto END 158 | case detail := <-dateDetailCh: 159 | for i := 0; i < len(consts.ProxyIpArr); i++ { 160 | secKillWg.Add(1) 161 | go func(detail model.DateDetail, ip string) { 162 | log.Printf("[%s]: 日期信息获取成功,尝试预约:%s %s-%s \n", ip, detail.Date, detail.StartTime, detail.EndTime) 163 | e.SecKill(detail, strconv.Itoa(productId), secKillWg, 164 | seckillCtx, stopGetDetail, stopSeckill, ip) 165 | }(detail, consts.ProxyIpArr[i]) 166 | } 167 | default: 168 | } 169 | } 170 | secKillWg.Wait() 171 | END: 172 | fmt.Printf("zmyy_seckill程序运行结束,共用时:%s, 按任意键退出...\n", time.Now().Sub(startTime)) 173 | b := make([]byte, 1) 174 | os.Stdin.Read(b) 175 | } 176 | -------------------------------------------------------------------------------- /config/city.json: -------------------------------------------------------------------------------- 1 | { 2 | "citycode": [ 3 | { 4 | "name": "直辖市", 5 | "value": "0", 6 | "children": [ 7 | { 8 | "name": "北京市", 9 | "value": "1100" 10 | }, 11 | { 12 | "name": "天津市", 13 | "value": "1200" 14 | }, 15 | { 16 | "name": "上海市", 17 | "value": "3100" 18 | }, 19 | { 20 | "name": "重庆市", 21 | "value": "5000" 22 | } 23 | ] 24 | }, 25 | { 26 | "name": "香港", 27 | "value": "1", 28 | "children": [ 29 | { 30 | "name": "香港", 31 | "value": "8101" 32 | } 33 | ] 34 | }, 35 | { 36 | "name": "河北省", 37 | "value": "13", 38 | "children": [ 39 | { 40 | "name": "石家庄市", 41 | "value": "1301" 42 | }, 43 | { 44 | "name": "唐山市", 45 | "value": "1302" 46 | }, 47 | { 48 | "name": "秦皇岛市", 49 | "value": "1303" 50 | }, 51 | { 52 | "name": "邯郸市", 53 | "value": "1304" 54 | }, 55 | { 56 | "name": "邢台市", 57 | "value": "1305" 58 | }, 59 | { 60 | "name": "保定市", 61 | "value": "1306" 62 | }, 63 | { 64 | "name": "张家口市", 65 | "value": "1307" 66 | }, 67 | { 68 | "name": "承德市", 69 | "value": "1308" 70 | }, 71 | { 72 | "name": "沧州市", 73 | "value": "1309" 74 | }, 75 | { 76 | "name": "廊坊市", 77 | "value": "1310" 78 | }, 79 | { 80 | "name": "衡水市", 81 | "value": "1311" 82 | }, 83 | { 84 | "name": "省直辖县级行政区划", 85 | "value": "1390" 86 | } 87 | ] 88 | }, 89 | { 90 | "name": "山西省", 91 | "value": "14", 92 | "children": [ 93 | { 94 | "name": "太原市", 95 | "value": "1401" 96 | }, 97 | { 98 | "name": "大同市", 99 | "value": "1402" 100 | }, 101 | { 102 | "name": "阳泉市", 103 | "value": "1403" 104 | }, 105 | { 106 | "name": "长治市", 107 | "value": "1404" 108 | }, 109 | { 110 | "name": "晋城市", 111 | "value": "1405" 112 | }, 113 | { 114 | "name": "朔州市", 115 | "value": "1406" 116 | }, 117 | { 118 | "name": "晋中市", 119 | "value": "1407" 120 | }, 121 | { 122 | "name": "运城市", 123 | "value": "1408" 124 | }, 125 | { 126 | "name": "忻州市", 127 | "value": "1409" 128 | }, 129 | { 130 | "name": "临汾市", 131 | "value": "1410" 132 | }, 133 | { 134 | "name": "吕梁市", 135 | "value": "1411" 136 | } 137 | ] 138 | }, 139 | { 140 | "name": "内蒙古自治区", 141 | "value": "15", 142 | "children": [ 143 | { 144 | "name": "呼和浩特市", 145 | "value": "1501" 146 | }, 147 | { 148 | "name": "包头市", 149 | "value": "1502" 150 | }, 151 | { 152 | "name": "乌海市", 153 | "value": "1503" 154 | }, 155 | { 156 | "name": "赤峰市", 157 | "value": "1504" 158 | }, 159 | { 160 | "name": "通辽市", 161 | "value": "1505" 162 | }, 163 | { 164 | "name": "鄂尔多斯市", 165 | "value": "1506" 166 | }, 167 | { 168 | "name": "呼伦贝尔市", 169 | "value": "1507" 170 | }, 171 | { 172 | "name": "巴彦淖尔市", 173 | "value": "1508" 174 | }, 175 | { 176 | "name": "乌兰察布市", 177 | "value": "1509" 178 | }, 179 | { 180 | "name": "兴安盟", 181 | "value": "1522" 182 | }, 183 | { 184 | "name": "锡林郭勒盟", 185 | "value": "1525" 186 | }, 187 | { 188 | "name": "阿拉善盟", 189 | "value": "1529" 190 | } 191 | ] 192 | }, 193 | { 194 | "name": "辽宁省", 195 | "value": "21", 196 | "children": [ 197 | { 198 | "name": "沈阳市", 199 | "value": "2101" 200 | }, 201 | { 202 | "name": "大连市", 203 | "value": "2102" 204 | }, 205 | { 206 | "name": "鞍山市", 207 | "value": "2103" 208 | }, 209 | { 210 | "name": "抚顺市", 211 | "value": "2104" 212 | }, 213 | { 214 | "name": "本溪市", 215 | "value": "2105" 216 | }, 217 | { 218 | "name": "丹东市", 219 | "value": "2106" 220 | }, 221 | { 222 | "name": "锦州市", 223 | "value": "2107" 224 | }, 225 | { 226 | "name": "营口市", 227 | "value": "2108" 228 | }, 229 | { 230 | "name": "阜新市", 231 | "value": "2109" 232 | }, 233 | { 234 | "name": "辽阳市", 235 | "value": "2110" 236 | }, 237 | { 238 | "name": "盘锦市", 239 | "value": "2111" 240 | }, 241 | { 242 | "name": "铁岭市", 243 | "value": "2112" 244 | }, 245 | { 246 | "name": "朝阳市", 247 | "value": "2113" 248 | }, 249 | { 250 | "name": "葫芦岛市", 251 | "value": "2114" 252 | } 253 | ] 254 | }, 255 | { 256 | "name": "吉林省", 257 | "value": "22", 258 | "children": [ 259 | { 260 | "name": "长春市", 261 | "value": "2201" 262 | }, 263 | { 264 | "name": "吉林市", 265 | "value": "2202" 266 | }, 267 | { 268 | "name": "四平市", 269 | "value": "2203" 270 | }, 271 | { 272 | "name": "辽源市", 273 | "value": "2204" 274 | }, 275 | { 276 | "name": "通化市", 277 | "value": "2205" 278 | }, 279 | { 280 | "name": "白山市", 281 | "value": "2206" 282 | }, 283 | { 284 | "name": "松原市", 285 | "value": "2207" 286 | }, 287 | { 288 | "name": "白城市", 289 | "value": "2208" 290 | }, 291 | { 292 | "name": "延边朝鲜族自治州", 293 | "value": "2224" 294 | } 295 | ] 296 | }, 297 | { 298 | "name": "黑龙江省", 299 | "value": "23", 300 | "children": [ 301 | { 302 | "name": "哈尔滨市", 303 | "value": "2301" 304 | }, 305 | { 306 | "name": "齐齐哈尔市", 307 | "value": "2302" 308 | }, 309 | { 310 | "name": "鸡西市", 311 | "value": "2303" 312 | }, 313 | { 314 | "name": "鹤岗市", 315 | "value": "2304" 316 | }, 317 | { 318 | "name": "双鸭山市", 319 | "value": "2305" 320 | }, 321 | { 322 | "name": "大庆市", 323 | "value": "2306" 324 | }, 325 | { 326 | "name": "伊春市", 327 | "value": "2307" 328 | }, 329 | { 330 | "name": "佳木斯市", 331 | "value": "2308" 332 | }, 333 | { 334 | "name": "七台河市", 335 | "value": "2309" 336 | }, 337 | { 338 | "name": "牡丹江市", 339 | "value": "2310" 340 | }, 341 | { 342 | "name": "黑河市", 343 | "value": "2311" 344 | }, 345 | { 346 | "name": "绥化市", 347 | "value": "2312" 348 | }, 349 | { 350 | "name": "大兴安岭地区", 351 | "value": "2327" 352 | } 353 | ] 354 | }, 355 | { 356 | "name": "江苏省", 357 | "value": "32", 358 | "children": [ 359 | { 360 | "name": "南京市", 361 | "value": "3201" 362 | }, 363 | { 364 | "name": "无锡市", 365 | "value": "3202" 366 | }, 367 | { 368 | "name": "徐州市", 369 | "value": "3203" 370 | }, 371 | { 372 | "name": "常州市", 373 | "value": "3204" 374 | }, 375 | { 376 | "name": "苏州市", 377 | "value": "3205" 378 | }, 379 | { 380 | "name": "南通市", 381 | "value": "3206" 382 | }, 383 | { 384 | "name": "连云港市", 385 | "value": "3207" 386 | }, 387 | { 388 | "name": "淮安市", 389 | "value": "3208" 390 | }, 391 | { 392 | "name": "盐城市", 393 | "value": "3209" 394 | }, 395 | { 396 | "name": "扬州市", 397 | "value": "3210" 398 | }, 399 | { 400 | "name": "镇江市", 401 | "value": "3211" 402 | }, 403 | { 404 | "name": "泰州市", 405 | "value": "3212" 406 | }, 407 | { 408 | "name": "宿迁市", 409 | "value": "3213" 410 | } 411 | ] 412 | }, 413 | { 414 | "name": "浙江省", 415 | "value": "33", 416 | "children": [ 417 | { 418 | "name": "杭州市", 419 | "value": "3301" 420 | }, 421 | { 422 | "name": "宁波市", 423 | "value": "3302" 424 | }, 425 | { 426 | "name": "温州市", 427 | "value": "3303" 428 | }, 429 | { 430 | "name": "嘉兴市", 431 | "value": "3304" 432 | }, 433 | { 434 | "name": "湖州市", 435 | "value": "3305" 436 | }, 437 | { 438 | "name": "绍兴市", 439 | "value": "3306" 440 | }, 441 | { 442 | "name": "金华市", 443 | "value": "3307" 444 | }, 445 | { 446 | "name": "衢州市", 447 | "value": "3308" 448 | }, 449 | { 450 | "name": "舟山市", 451 | "value": "3309" 452 | }, 453 | { 454 | "name": "台州市", 455 | "value": "3310" 456 | }, 457 | { 458 | "name": "丽水市", 459 | "value": "3311" 460 | } 461 | ] 462 | }, 463 | { 464 | "name": "安徽省", 465 | "value": "34", 466 | "children": [ 467 | { 468 | "name": "合肥市", 469 | "value": "3401" 470 | }, 471 | { 472 | "name": "芜湖市", 473 | "value": "3402" 474 | }, 475 | { 476 | "name": "蚌埠市", 477 | "value": "3403" 478 | }, 479 | { 480 | "name": "淮南市", 481 | "value": "3404" 482 | }, 483 | { 484 | "name": "马鞍山市", 485 | "value": "3405" 486 | }, 487 | { 488 | "name": "淮北市", 489 | "value": "3406" 490 | }, 491 | { 492 | "name": "铜陵市", 493 | "value": "3407" 494 | }, 495 | { 496 | "name": "安庆市", 497 | "value": "3408" 498 | }, 499 | { 500 | "name": "黄山市", 501 | "value": "3410" 502 | }, 503 | { 504 | "name": "滁州市", 505 | "value": "3411" 506 | }, 507 | { 508 | "name": "阜阳市", 509 | "value": "3412" 510 | }, 511 | { 512 | "name": "宿州市", 513 | "value": "3413" 514 | }, 515 | { 516 | "name": "六安市", 517 | "value": "3415" 518 | }, 519 | { 520 | "name": "亳州市", 521 | "value": "3416" 522 | }, 523 | { 524 | "name": "池州市", 525 | "value": "3417" 526 | }, 527 | { 528 | "name": "宣城市", 529 | "value": "3418" 530 | } 531 | ] 532 | }, 533 | { 534 | "name": "福建省", 535 | "value": "35", 536 | "children": [ 537 | { 538 | "name": "福州市", 539 | "value": "3501" 540 | }, 541 | { 542 | "name": "厦门市", 543 | "value": "3502" 544 | }, 545 | { 546 | "name": "莆田市", 547 | "value": "3503" 548 | }, 549 | { 550 | "name": "三明市", 551 | "value": "3504" 552 | }, 553 | { 554 | "name": "泉州市", 555 | "value": "3505" 556 | }, 557 | { 558 | "name": "漳州市", 559 | "value": "3506" 560 | }, 561 | { 562 | "name": "南平市", 563 | "value": "3507" 564 | }, 565 | { 566 | "name": "龙岩市", 567 | "value": "3508" 568 | }, 569 | { 570 | "name": "宁德市", 571 | "value": "3509" 572 | } 573 | ] 574 | }, 575 | { 576 | "name": "江西省", 577 | "value": "36", 578 | "children": [ 579 | { 580 | "name": "南昌市", 581 | "value": "3601" 582 | }, 583 | { 584 | "name": "景德镇市", 585 | "value": "3602" 586 | }, 587 | { 588 | "name": "萍乡市", 589 | "value": "3603" 590 | }, 591 | { 592 | "name": "九江市", 593 | "value": "3604" 594 | }, 595 | { 596 | "name": "新余市", 597 | "value": "3605" 598 | }, 599 | { 600 | "name": "鹰潭市", 601 | "value": "3606" 602 | }, 603 | { 604 | "name": "赣州市", 605 | "value": "3607" 606 | }, 607 | { 608 | "name": "吉安市", 609 | "value": "3608" 610 | }, 611 | { 612 | "name": "宜春市", 613 | "value": "3609" 614 | }, 615 | { 616 | "name": "抚州市", 617 | "value": "3610" 618 | }, 619 | { 620 | "name": "上饶市", 621 | "value": "3611" 622 | } 623 | ] 624 | }, 625 | { 626 | "name": "山东省", 627 | "value": "37", 628 | "children": [ 629 | { 630 | "name": "济南市", 631 | "value": "3701" 632 | }, 633 | { 634 | "name": "青岛市", 635 | "value": "3702" 636 | }, 637 | { 638 | "name": "淄博市", 639 | "value": "3703" 640 | }, 641 | { 642 | "name": "枣庄市", 643 | "value": "3704" 644 | }, 645 | { 646 | "name": "东营市", 647 | "value": "3705" 648 | }, 649 | { 650 | "name": "烟台市", 651 | "value": "3706" 652 | }, 653 | { 654 | "name": "潍坊市", 655 | "value": "3707" 656 | }, 657 | { 658 | "name": "济宁市", 659 | "value": "3708" 660 | }, 661 | { 662 | "name": "泰安市", 663 | "value": "3709" 664 | }, 665 | { 666 | "name": "威海市", 667 | "value": "3710" 668 | }, 669 | { 670 | "name": "日照市", 671 | "value": "3711" 672 | }, 673 | { 674 | "name": "临沂市", 675 | "value": "3713" 676 | }, 677 | { 678 | "name": "德州市", 679 | "value": "3714" 680 | }, 681 | { 682 | "name": "聊城市", 683 | "value": "3715" 684 | }, 685 | { 686 | "name": "滨州市", 687 | "value": "3716" 688 | }, 689 | { 690 | "name": "菏泽市", 691 | "value": "3717" 692 | } 693 | ] 694 | }, 695 | { 696 | "name": "河南省", 697 | "value": "41", 698 | "children": [ 699 | { 700 | "name": "郑州市", 701 | "value": "4101" 702 | }, 703 | { 704 | "name": "开封市", 705 | "value": "4102" 706 | }, 707 | { 708 | "name": "洛阳市", 709 | "value": "4103" 710 | }, 711 | { 712 | "name": "平顶山市", 713 | "value": "4104" 714 | }, 715 | { 716 | "name": "安阳市", 717 | "value": "4105" 718 | }, 719 | { 720 | "name": "鹤壁市", 721 | "value": "4106" 722 | }, 723 | { 724 | "name": "新乡市", 725 | "value": "4107" 726 | }, 727 | { 728 | "name": "焦作市", 729 | "value": "4108" 730 | }, 731 | { 732 | "name": "濮阳市", 733 | "value": "4109" 734 | }, 735 | { 736 | "name": "许昌市", 737 | "value": "4110" 738 | }, 739 | { 740 | "name": "漯河市", 741 | "value": "4111" 742 | }, 743 | { 744 | "name": "三门峡市", 745 | "value": "4112" 746 | }, 747 | { 748 | "name": "南阳市", 749 | "value": "4113" 750 | }, 751 | { 752 | "name": "商丘市", 753 | "value": "4114" 754 | }, 755 | { 756 | "name": "信阳市", 757 | "value": "4115" 758 | }, 759 | { 760 | "name": "周口市", 761 | "value": "4116" 762 | }, 763 | { 764 | "name": "驻马店市", 765 | "value": "4117" 766 | }, 767 | { 768 | "name": "济源市", 769 | "value": "4190" 770 | } 771 | ] 772 | }, 773 | { 774 | "name": "湖北省", 775 | "value": "42", 776 | "children": [ 777 | { 778 | "name": "武汉市", 779 | "value": "4201" 780 | }, 781 | { 782 | "name": "黄石市", 783 | "value": "4202" 784 | }, 785 | { 786 | "name": "十堰市", 787 | "value": "4203" 788 | }, 789 | { 790 | "name": "宜昌市", 791 | "value": "4205" 792 | }, 793 | { 794 | "name": "襄阳市", 795 | "value": "4206" 796 | }, 797 | { 798 | "name": "鄂州市", 799 | "value": "4207" 800 | }, 801 | { 802 | "name": "荆门市", 803 | "value": "4208" 804 | }, 805 | { 806 | "name": "孝感市", 807 | "value": "4209" 808 | }, 809 | { 810 | "name": "荆州市", 811 | "value": "4210" 812 | }, 813 | { 814 | "name": "黄冈市", 815 | "value": "4211" 816 | }, 817 | { 818 | "name": "咸宁市", 819 | "value": "4212" 820 | }, 821 | { 822 | "name": "随州市", 823 | "value": "4213" 824 | }, 825 | { 826 | "name": "恩施土家族苗族自治州", 827 | "value": "4228" 828 | }, 829 | { 830 | "name": "省直辖县级行政区划", 831 | "value": "4290" 832 | } 833 | ] 834 | }, 835 | { 836 | "name": "湖南省", 837 | "value": "43", 838 | "children": [ 839 | { 840 | "name": "长沙市", 841 | "value": "4301" 842 | }, 843 | { 844 | "name": "株洲市", 845 | "value": "4302" 846 | }, 847 | { 848 | "name": "湘潭市", 849 | "value": "4303" 850 | }, 851 | { 852 | "name": "衡阳市", 853 | "value": "4304" 854 | }, 855 | { 856 | "name": "邵阳市", 857 | "value": "4305" 858 | }, 859 | { 860 | "name": "岳阳市", 861 | "value": "4306" 862 | }, 863 | { 864 | "name": "常德市", 865 | "value": "4307" 866 | }, 867 | { 868 | "name": "张家界市", 869 | "value": "4308" 870 | }, 871 | { 872 | "name": "益阳市", 873 | "value": "4309" 874 | }, 875 | { 876 | "name": "郴州市", 877 | "value": "4310" 878 | }, 879 | { 880 | "name": "永州市", 881 | "value": "4311" 882 | }, 883 | { 884 | "name": "怀化市", 885 | "value": "4312" 886 | }, 887 | { 888 | "name": "娄底市", 889 | "value": "4313" 890 | }, 891 | { 892 | "name": "湘西土家族苗族自治州", 893 | "value": "4331" 894 | } 895 | ] 896 | }, 897 | { 898 | "name": "广东省", 899 | "value": "44", 900 | "children": [ 901 | { 902 | "name": "广州市", 903 | "value": "4401" 904 | }, 905 | { 906 | "name": "韶关市", 907 | "value": "4402" 908 | }, 909 | { 910 | "name": "深圳市", 911 | "value": "4403" 912 | }, 913 | { 914 | "name": "珠海市", 915 | "value": "4404" 916 | }, 917 | { 918 | "name": "汕头市", 919 | "value": "4405" 920 | }, 921 | { 922 | "name": "佛山市", 923 | "value": "4406" 924 | }, 925 | { 926 | "name": "江门市", 927 | "value": "4407" 928 | }, 929 | { 930 | "name": "湛江市", 931 | "value": "4408" 932 | }, 933 | { 934 | "name": "茂名市", 935 | "value": "4409" 936 | }, 937 | { 938 | "name": "肇庆市", 939 | "value": "4412" 940 | }, 941 | { 942 | "name": "惠州市", 943 | "value": "4413" 944 | }, 945 | { 946 | "name": "梅州市", 947 | "value": "4414" 948 | }, 949 | { 950 | "name": "汕尾市", 951 | "value": "4415" 952 | }, 953 | { 954 | "name": "河源市", 955 | "value": "4416" 956 | }, 957 | { 958 | "name": "阳江市", 959 | "value": "4417" 960 | }, 961 | { 962 | "name": "清远市", 963 | "value": "4418" 964 | }, 965 | { 966 | "name": "东莞市", 967 | "value": "4419" 968 | }, 969 | { 970 | "name": "中山市", 971 | "value": "4420" 972 | }, 973 | { 974 | "name": "潮州市", 975 | "value": "4451" 976 | }, 977 | { 978 | "name": "揭阳市", 979 | "value": "4452" 980 | }, 981 | { 982 | "name": "云浮市", 983 | "value": "4453" 984 | } 985 | ] 986 | }, 987 | { 988 | "name": "广西壮族自治区", 989 | "value": "45", 990 | "children": [ 991 | { 992 | "name": "南宁市", 993 | "value": "4501" 994 | }, 995 | { 996 | "name": "柳州市", 997 | "value": "4502" 998 | }, 999 | { 1000 | "name": "桂林市", 1001 | "value": "4503" 1002 | }, 1003 | { 1004 | "name": "梧州市", 1005 | "value": "4504" 1006 | }, 1007 | { 1008 | "name": "北海市", 1009 | "value": "4505" 1010 | }, 1011 | { 1012 | "name": "防城港市", 1013 | "value": "4506" 1014 | }, 1015 | { 1016 | "name": "钦州市", 1017 | "value": "4507" 1018 | }, 1019 | { 1020 | "name": "贵港市", 1021 | "value": "4508" 1022 | }, 1023 | { 1024 | "name": "玉林市", 1025 | "value": "4509" 1026 | }, 1027 | { 1028 | "name": "百色市", 1029 | "value": "4510" 1030 | }, 1031 | { 1032 | "name": "贺州市", 1033 | "value": "4511" 1034 | }, 1035 | { 1036 | "name": "河池市", 1037 | "value": "4512" 1038 | }, 1039 | { 1040 | "name": "来宾市", 1041 | "value": "4513" 1042 | }, 1043 | { 1044 | "name": "崇左市", 1045 | "value": "4514" 1046 | } 1047 | ] 1048 | }, 1049 | { 1050 | "name": "海南省", 1051 | "value": "46", 1052 | "children": [ 1053 | { 1054 | "name": "海口市", 1055 | "value": "4601" 1056 | }, 1057 | { 1058 | "name": "三亚市", 1059 | "value": "4602" 1060 | }, 1061 | { 1062 | "name": "三沙市", 1063 | "value": "4603" 1064 | }, 1065 | { 1066 | "name": "省直辖县级行政区划", 1067 | "value": "4690" 1068 | } 1069 | ] 1070 | }, 1071 | { 1072 | "name": "四川省", 1073 | "value": "51", 1074 | "children": [ 1075 | { 1076 | "name": "成都市", 1077 | "value": "5101" 1078 | }, 1079 | { 1080 | "name": "自贡市", 1081 | "value": "5103" 1082 | }, 1083 | { 1084 | "name": "攀枝花市", 1085 | "value": "5104" 1086 | }, 1087 | { 1088 | "name": "泸州市", 1089 | "value": "5105" 1090 | }, 1091 | { 1092 | "name": "德阳市", 1093 | "value": "5106" 1094 | }, 1095 | { 1096 | "name": "绵阳市", 1097 | "value": "5107" 1098 | }, 1099 | { 1100 | "name": "广元市", 1101 | "value": "5108" 1102 | }, 1103 | { 1104 | "name": "遂宁市", 1105 | "value": "5109" 1106 | }, 1107 | { 1108 | "name": "内江市", 1109 | "value": "5110" 1110 | }, 1111 | { 1112 | "name": "乐山市", 1113 | "value": "5111" 1114 | }, 1115 | { 1116 | "name": "南充市", 1117 | "value": "5113" 1118 | }, 1119 | { 1120 | "name": "眉山市", 1121 | "value": "5114" 1122 | }, 1123 | { 1124 | "name": "宜宾市", 1125 | "value": "5115" 1126 | }, 1127 | { 1128 | "name": "广安市", 1129 | "value": "5116" 1130 | }, 1131 | { 1132 | "name": "达州市", 1133 | "value": "5117" 1134 | }, 1135 | { 1136 | "name": "雅安市", 1137 | "value": "5118" 1138 | }, 1139 | { 1140 | "name": "巴中市", 1141 | "value": "5119" 1142 | }, 1143 | { 1144 | "name": "资阳市", 1145 | "value": "5120" 1146 | }, 1147 | { 1148 | "name": "阿坝藏族羌族自治州", 1149 | "value": "5132" 1150 | }, 1151 | { 1152 | "name": "甘孜藏族自治州", 1153 | "value": "5133" 1154 | }, 1155 | { 1156 | "name": "凉山彝族自治州", 1157 | "value": "5134" 1158 | } 1159 | ] 1160 | }, 1161 | { 1162 | "name": "贵州省", 1163 | "value": "52", 1164 | "children": [ 1165 | { 1166 | "name": "贵阳市", 1167 | "value": "5201" 1168 | }, 1169 | { 1170 | "name": "六盘水市", 1171 | "value": "5202" 1172 | }, 1173 | { 1174 | "name": "遵义市", 1175 | "value": "5203" 1176 | }, 1177 | { 1178 | "name": "安顺市", 1179 | "value": "5204" 1180 | }, 1181 | { 1182 | "name": "毕节市", 1183 | "value": "5205" 1184 | }, 1185 | { 1186 | "name": "铜仁市", 1187 | "value": "5206" 1188 | }, 1189 | { 1190 | "name": "黔西南布依族苗族自治州", 1191 | "value": "5223" 1192 | }, 1193 | { 1194 | "name": "黔东南苗族侗族自治州", 1195 | "value": "5226" 1196 | }, 1197 | { 1198 | "name": "黔南布依族苗族自治州", 1199 | "value": "5227" 1200 | } 1201 | ] 1202 | }, 1203 | { 1204 | "name": "云南省", 1205 | "value": "53", 1206 | "children": [ 1207 | { 1208 | "name": "昆明市", 1209 | "value": "5301" 1210 | }, 1211 | { 1212 | "name": "曲靖市", 1213 | "value": "5303" 1214 | }, 1215 | { 1216 | "name": "玉溪市", 1217 | "value": "5304" 1218 | }, 1219 | { 1220 | "name": "保山市", 1221 | "value": "5305" 1222 | }, 1223 | { 1224 | "name": "昭通市", 1225 | "value": "5306" 1226 | }, 1227 | { 1228 | "name": "丽江市", 1229 | "value": "5307" 1230 | }, 1231 | { 1232 | "name": "普洱市", 1233 | "value": "5308" 1234 | }, 1235 | { 1236 | "name": "临沧市", 1237 | "value": "5309" 1238 | }, 1239 | { 1240 | "name": "楚雄彝族自治州", 1241 | "value": "5323" 1242 | }, 1243 | { 1244 | "name": "红河哈尼族彝族自治州", 1245 | "value": "5325" 1246 | }, 1247 | { 1248 | "name": "文山壮族苗族自治州", 1249 | "value": "5326" 1250 | }, 1251 | { 1252 | "name": "西双版纳傣族自治州", 1253 | "value": "5328" 1254 | }, 1255 | { 1256 | "name": "大理白族自治州", 1257 | "value": "5329" 1258 | }, 1259 | { 1260 | "name": "德宏傣族景颇族自治州", 1261 | "value": "5331" 1262 | }, 1263 | { 1264 | "name": "怒江傈僳族自治州", 1265 | "value": "5333" 1266 | }, 1267 | { 1268 | "name": "迪庆藏族自治州", 1269 | "value": "5334" 1270 | } 1271 | ] 1272 | }, 1273 | { 1274 | "name": "西藏自治区", 1275 | "value": "54", 1276 | "children": [ 1277 | { 1278 | "name": "拉萨市", 1279 | "value": "5401" 1280 | }, 1281 | { 1282 | "name": "日喀则市", 1283 | "value": "5402" 1284 | }, 1285 | { 1286 | "name": "昌都市", 1287 | "value": "5403" 1288 | }, 1289 | { 1290 | "name": "林芝市", 1291 | "value": "5404" 1292 | }, 1293 | { 1294 | "name": "山南地区", 1295 | "value": "5422" 1296 | }, 1297 | { 1298 | "name": "那曲地区", 1299 | "value": "5424" 1300 | }, 1301 | { 1302 | "name": "阿里地区", 1303 | "value": "5425" 1304 | } 1305 | ] 1306 | }, 1307 | { 1308 | "name": "陕西省", 1309 | "value": "61", 1310 | "children": [ 1311 | { 1312 | "name": "西安市", 1313 | "value": "6101" 1314 | }, 1315 | { 1316 | "name": "铜川市", 1317 | "value": "6102" 1318 | }, 1319 | { 1320 | "name": "宝鸡市", 1321 | "value": "6103" 1322 | }, 1323 | { 1324 | "name": "咸阳市", 1325 | "value": "6104" 1326 | }, 1327 | { 1328 | "name": "渭南市", 1329 | "value": "6105" 1330 | }, 1331 | { 1332 | "name": "延安市", 1333 | "value": "6106" 1334 | }, 1335 | { 1336 | "name": "汉中市", 1337 | "value": "6107" 1338 | }, 1339 | { 1340 | "name": "榆林市", 1341 | "value": "6108" 1342 | }, 1343 | { 1344 | "name": "安康市", 1345 | "value": "6109" 1346 | }, 1347 | { 1348 | "name": "商洛市", 1349 | "value": "6110" 1350 | } 1351 | ] 1352 | }, 1353 | { 1354 | "name": "甘肃省", 1355 | "value": "62", 1356 | "children": [ 1357 | { 1358 | "name": "兰州市", 1359 | "value": "6201" 1360 | }, 1361 | { 1362 | "name": "嘉峪关市", 1363 | "value": "6202" 1364 | }, 1365 | { 1366 | "name": "金昌市", 1367 | "value": "6203" 1368 | }, 1369 | { 1370 | "name": "白银市", 1371 | "value": "6204" 1372 | }, 1373 | { 1374 | "name": "天水市", 1375 | "value": "6205" 1376 | }, 1377 | { 1378 | "name": "武威市", 1379 | "value": "6206" 1380 | }, 1381 | { 1382 | "name": "张掖市", 1383 | "value": "6207" 1384 | }, 1385 | { 1386 | "name": "平凉市", 1387 | "value": "6208" 1388 | }, 1389 | { 1390 | "name": "酒泉市", 1391 | "value": "6209" 1392 | }, 1393 | { 1394 | "name": "庆阳市", 1395 | "value": "6210" 1396 | }, 1397 | { 1398 | "name": "定西市", 1399 | "value": "6211" 1400 | }, 1401 | { 1402 | "name": "陇南市", 1403 | "value": "6212" 1404 | }, 1405 | { 1406 | "name": "临夏回族自治州", 1407 | "value": "6229" 1408 | }, 1409 | { 1410 | "name": "甘南藏族自治州", 1411 | "value": "6230" 1412 | } 1413 | ] 1414 | }, 1415 | { 1416 | "name": "青海省", 1417 | "value": "63", 1418 | "children": [ 1419 | { 1420 | "name": "西宁市", 1421 | "value": "6301" 1422 | }, 1423 | { 1424 | "name": "海东市", 1425 | "value": "6302" 1426 | }, 1427 | { 1428 | "name": "海北藏族自治州", 1429 | "value": "6322" 1430 | }, 1431 | { 1432 | "name": "黄南藏族自治州", 1433 | "value": "6323" 1434 | }, 1435 | { 1436 | "name": "海南藏族自治州", 1437 | "value": "6325" 1438 | }, 1439 | { 1440 | "name": "果洛藏族自治州", 1441 | "value": "6326" 1442 | }, 1443 | { 1444 | "name": "玉树藏族自治州", 1445 | "value": "6327" 1446 | }, 1447 | { 1448 | "name": "海西蒙古族藏族自治州", 1449 | "value": "6328" 1450 | } 1451 | ] 1452 | }, 1453 | { 1454 | "name": "宁夏回族自治区", 1455 | "value": "64", 1456 | "children": [ 1457 | { 1458 | "name": "银川市", 1459 | "value": "6401" 1460 | }, 1461 | { 1462 | "name": "石嘴山市", 1463 | "value": "6402" 1464 | }, 1465 | { 1466 | "name": "吴忠市", 1467 | "value": "6403" 1468 | }, 1469 | { 1470 | "name": "固原市", 1471 | "value": "6404" 1472 | }, 1473 | { 1474 | "name": "中卫市", 1475 | "value": "6405" 1476 | } 1477 | ] 1478 | }, 1479 | { 1480 | "name": "新疆维吾尔自治区", 1481 | "value": "65", 1482 | "children": [ 1483 | { 1484 | "name": "乌鲁木齐市", 1485 | "value": "6501" 1486 | }, 1487 | { 1488 | "name": "克拉玛依市", 1489 | "value": "6502" 1490 | }, 1491 | { 1492 | "name": "吐鲁番市", 1493 | "value": "6504" 1494 | }, 1495 | { 1496 | "name": "哈密地区", 1497 | "value": "6522" 1498 | }, 1499 | { 1500 | "name": "昌吉回族自治州", 1501 | "value": "6523" 1502 | }, 1503 | { 1504 | "name": "博尔塔拉蒙古自治州", 1505 | "value": "6527" 1506 | }, 1507 | { 1508 | "name": "巴音郭楞蒙古自治州", 1509 | "value": "6528" 1510 | }, 1511 | { 1512 | "name": "阿克苏地区", 1513 | "value": "6529" 1514 | }, 1515 | { 1516 | "name": "克孜勒苏柯尔克孜自治州", 1517 | "value": "6530" 1518 | }, 1519 | { 1520 | "name": "喀什地区", 1521 | "value": "6531" 1522 | }, 1523 | { 1524 | "name": "和田地区", 1525 | "value": "6532" 1526 | }, 1527 | { 1528 | "name": "伊犁哈萨克自治州", 1529 | "value": "6540" 1530 | }, 1531 | { 1532 | "name": "塔城地区", 1533 | "value": "6542" 1534 | }, 1535 | { 1536 | "name": "阿勒泰地区", 1537 | "value": "6543" 1538 | }, 1539 | { 1540 | "name": "自治区直辖县级行政区划", 1541 | "value": "6590" 1542 | } 1543 | ] 1544 | } 1545 | ] 1546 | } 1547 | --------------------------------------------------------------------------------