├── 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 |
5 |
6 |
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 |
19 |
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 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
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 |
--------------------------------------------------------------------------------