package cz88ip import ( "encoding/json" "fmt" "io" "net/http" "os" "bet24.com/log" //"bet24.com/public" "context" "strings" "sync" "time" ) var mgr *ipmanager const APPCODE = "d85a9a4247954b5da7babcbaf29eefa2" func GetIpManager() *ipmanager { if mgr == nil { mgr = new(ipmanager) mgr.ctor() } return mgr } type ipmanager struct { iplist map[string]*IpInfo lock *sync.RWMutex isDirty bool } func (im *ipmanager) ctor() { im.lock = &sync.RWMutex{} im.iplist = make(map[string]*IpInfo) im.isDirty = false im.loadFromFile() im.flush() } func (im *ipmanager) flush() { time.AfterFunc(10*time.Minute, im.flush) if !im.isDirty { return } im.isDirty = false im.saveToFile() } func (im *ipmanager) loadFromFile() { data, err := os.ReadFile("conf/cz88ipdata.json") if err != nil { log.Release("ipmanager.loadFromFile read data failed conf/cz88ipdata.json %v", err) return } im.lock.Lock() err = json.Unmarshal([]byte(data), &im.iplist) im.lock.Unlock() if err != nil { log.Release("ipmanager.loadFromFile unmarshal failed conf/cz88ipdata.json %v", err) } } func (im *ipmanager) saveToFile() { im.lock.RLock() d, _ := json.Marshal(im.iplist) im.lock.RUnlock() err := os.WriteFile("conf/cz88ipdata.json", d, 0644) if err != nil { log.Release("ipmanager.saveToFile failed %v", err) } } // 获取ip前三位 func (im *ipmanager) getPrefixIp(ipAddress string) string { ips := strings.Split(ipAddress, ".") if len(ips) < 4 { return ipAddress } return fmt.Sprintf("%s.%s.%s", ips[0], ips[1], ips[2]) } func (im *ipmanager) getIpInfo(ipAddress string) *IpInfo { if ipAddress == "" { log.Release("ipmanager.getIpInfo invalid argument") return nil } ipPrefix := im.getPrefixIp(ipAddress) im.lock.RLock() ii, ok := im.iplist[ipPrefix] im.lock.RUnlock() if ok { return ii } // 找不到 return im.requestIpInfoWithTimeout(ipAddress, ipPrefix) } func (im *ipmanager) requestIpInfoWithTimeout(ipAddress, ipPrefix string) *IpInfo { channel_ipInfo := make(chan *IpInfo) go im.requestIPInfoAndSave(ipAddress, ipPrefix, channel_ipInfo) c, cancel := context.WithTimeout(context.Background(), 5*time.Second) select { case ipInfo := <-channel_ipInfo: cancel() return ipInfo case <-c.Done(): log.Release("requestIpInfoWithTimeout [%s]timeout", ipAddress) cancel() return nil } //log.Release("requestIpInfoWithTimeout [%s] should not be here", ipAddress) //return nil } func (im *ipmanager) requestIPInfoAndSave(ipAddress, ipPrefix string, ch chan<- *IpInfo) { newIpInfo := im.requestIPInfo(ipAddress) c, cancel := context.WithTimeout(context.Background(), 2*time.Second) select { case ch <- newIpInfo: cancel() break case <-c.Done(): log.Release("requestIPInfoAndSave channel post [%s]timeout", ipAddress) cancel() break } if newIpInfo == nil { return } im.lock.Lock() im.iplist[ipPrefix] = newIpInfo im.isDirty = true im.lock.Unlock() } func (im *ipmanager) requestIPInfo(ipAddress string) *IpInfo { //time.Sleep(5 * time.Second) if ipAddress == "" { log.Release("ipmanager.requestIPInfo[%s] invalid argument", ipAddress) return nil } url := fmt.Sprintf("http://cz88geoaliyun.cocobaloot.com/search/ip/geo?ip=%s&AppCode=%s", ipAddress, APPCODE) resp, err := http.Get(url) if err != nil { log.Release("ipmanager.requestIPInfo[%s] failed %v", ipAddress, err) } if resp == nil { log.Release("ipmanager.requestIPInfo[%s] failed resp == nil", ipAddress) return nil } defer resp.Body.Close() bs, err := io.ReadAll(resp.Body) if err != nil { log.Release("ipmanager.requestIPInfo[%s] read data failed %v", ipAddress, err) return nil } type retData struct { Ip string `json:"ip,omitempty"` Geocode string `json:"geocode,omitempty"` Country string `json:"country,omitempty"` CountryCode string `json:"countryCode,omitempty"` Province string `json:"province,omitempty"` City string `json:"city,omitempty"` Districts string `json:"districts,omitempty"` } var retJson struct { Data retData `json:"data,omitempty"` Success bool `json:"success,omitempty"` Message string `json:"message,omitempty"` Code int `json:"code,omitempty"` } err = json.Unmarshal(bs, &retJson) if err != nil { log.Release("ipmanager.requestIPInfo[%s] unmarshal failed %v", ipAddress, err) return nil } if !retJson.Success { log.Release("ipmanager.requestIPInfo[%s] return failed %d", ipAddress, retJson.Code) return nil } // 成功了 return &IpInfo{ Country: retJson.Data.Country, Region: fmt.Sprintf("%s%s%s", retJson.Data.Province, retJson.Data.City, retJson.Data.Districts), LastUpdate: time.Now().Unix(), } }