package common import ( "bytes" "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "git.listensoft.net/tool/jspkit/common/lxhttp" "git.listensoft.net/tool/jspkit/common/models" "git.listensoft.net/tool/jspkit/common/variable" "git.listensoft.net/tool/jspkit/common/watermark" "git.listensoft.net/tool/jspkit/logger" "git.listensoft.net/tool/jspkit/taxerr" "github.com/go-kratos/kratos/v2/log" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/utils" "github.com/tidwall/gjson" "io" "math/rand" "net/http" "net/url" "os" "reflect" "regexp" "strings" "time" ) type LvName string const ( CheckSuccess LvName = "申报成功" // 检查成功 ------ 检查完全通过 CheckFail LvName = "检查失败" // 检查任务失败 ----- 网页打开失败等 CheckOmit LvName = "有遗漏" // 检查有遗漏 ----- 未申报问题 CheckAbnormal LvName = "有异常" //检查有异常 ----- 税款问题 ) func GeneratePath(suffix string) string { ps := os.PathSeparator //检查路径 if !PathExists("captcha" + string(ps)) { _ = os.MkdirAll("captcha"+string(ps), os.ModePerm) } return "captcha" + string(ps) + GetTimestamp(13) + "." + suffix } func PathExists(path string) bool { _, err := os.Stat(path) if err == nil { return true } if os.IsNotExist(err) { return false } return false } // 返回一个32位md5加密后的字符串 func Md5(data string) string { h := md5.New() h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)) } /* 生成检查截图本地路径 lv 图片对应的错误等级 用上面定义好的 Omit>Abnormal taxNo 税号 period 账期 location 截图位置,例如申报清册、申报结果查询、税款缴纳、缴费缴纳查询 */ func GenerateCheckImagePath(lv LvName, taxNo, period, location string) string { var path string path = "./data/check/" + taxNo + "/" if !PathExists(path) { os.MkdirAll(path, os.ModePerm) } return fmt.Sprintf(`%s-%s-%s-新版_%s_%s.png`, path+taxNo, period, GetTimestamp(13), location, lv) } func PostSbjt(taxno, period, path string) string { var params = map[string]string{ "tax_no": taxno, "period": period, "type": "etax", //电子税务局 } str, err := PostFile("https://file.listensoft.net/api/v1/uploadsbjt", map[string]string{ "file": path, }, params) if err != nil { log.Warnf("【%s】文件上传失败", path) } return gjson.GetBytes(str, "path").String() } // 获取申报截图的path // taxNo 纳税人识别号 period 账期 taxType 任务名称 func GetSbjtPath(taxNo, period string, taxType variable.TaskName) string { var path string path = "./data/" + taxNo + "/" if !PathExists(path) { os.MkdirAll(path, os.ModePerm) } path = path + period + string(taxType) + "_" + GetTimestamp(13) + ".png" return path } // 判断字符串是否全是英文 func IsAllEnglish(s string) bool { // 检查字符串是否全部由ASCII字符组成 for _, c := range s { if c > 127 { return false } } return true } // CreateFormReader 将 map 转换成 header func CreateFormReader(data map[string]string) io.Reader { form := url.Values{} for k, v := range data { form.Add(k, v) } return strings.NewReader(form.Encode()) } func Unbracket(str string) string { str = strings.ReplaceAll(str, "(", "(") str = strings.ReplaceAll(str, ")", ")") str = strings.ReplaceAll(str, " ", "") return str } // 检查是否是手机号 func CheckMobile(mobile string) bool { r := regexp.MustCompile(`^1[0-9]{10}$`) return r.Match([]byte(mobile)) } func NewWebStuckTitle() *taxerr.SystemErr { return taxerr.NewSystemV3("电子税局页面卡顿", "系统将于30分钟后重试(可在\"通用设置\"关闭)") } // 查看设备是否在线 func CheckEqmLive(tel string) bool { client := lxhttp.NewHttpClient() defer client.CloseIdleConnections() bys, err := lxhttp.POSTJson(client, "https://keep.jsptax.com"+"/api/v1/telDeviceStatus", map[string]string{"mobiles": tel}, map[string]string{}) if err != nil { return false } if gjson.GetBytes(bys, `data.status`).Bool() { return true } return false } func GetData(key variable.SbKey, data string, target interface{}) bool { str := gjson.Get(data, string(key)).String() if err := MapToStruct(str, target); err != nil { logger.Info(err) return false } return true } // target 传指针 func MapToStruct(source interface{}, target interface{}) error { resByre := []byte("") var resByteErr error if reflect.TypeOf(source).String() == "string" { str := source.(string) resByre = []byte(str) } else { resByre, resByteErr = json.Marshal(source) if resByteErr != nil { return resByteErr } } err := json.Unmarshal(resByre, target) if err != nil { return err } return nil } // CancelLeftTasks 撤销其他的任务,避免一直发验证码导致超限,仅新版登录使用 func CancelLeftTasks(tel string) { c := lxhttp.NewHttpClient() lxhttp.POSTJson(c, variable.TaxTaskURL+"/api/v1/stopQueue", map[string]string{"tel": tel}, nil) } // GetTelTaskStatus 如果有任务正在进行 返回 true func GetTelTaskStatus(tel string) (bool, error) { c := lxhttp.NewHttpClient() bys, err := lxhttp.POSTJson(c, `https://task.listensoft.net/api/v1/other/telTaskDoi`, map[string]string{ "tel": tel, }, nil) if err != nil { return false, err } if gjson.GetBytes(bys, `data.res`).Bool() { return true, nil } else { return false, nil } } var rng = rand.New(rand.NewSource(time.Now().UnixMicro())) // GenTracks 函数生成移动轨迹。先做加速运动,再做减速运动。 // // 设滑块滑动的加速度为 a;当前速度为 v;初速度为 v0;位移为 x;所需时间为 t。 // 根据加速度物理公式,它们满足以下关系: // v = v0 + a * t // x = v0 * t + 0.5 * a * t * t // // 参考: // // https://python3webspider.cuiqingcai.com/8.2-ji-yan-hua-dong-yan-zheng-ma-shi-bie func GenTracks(distance float64) []float64 { var track = make([]float64, 0) // 移动轨迹 var current float64 = 0 // 当前位移 var mid float64 = distance * 4 / 5 // 减速阈值 var t float64 = 0.15 // 计算间隔 var v float64 = 0 // 初速度 var a1 = rng.Float64()*6 + 2 // 正加速度 [2,8) var a2 = -a1 - 2 // 负加速度 (-10,-4] for current < distance { // 加速度 var a float64 if current < mid { a = a1 } else { a = -a2 } // 初速度 v0 v0 := v // 当前速度 v = v0 + at v = v0 + a*t // 移动距离 x = v0t + 1/2 * a * t^2 x := v0*t + 1.0/2.0*a*t*t current += x track = append(track, x) } // 修正超标值 track = append(track, distance-current) return track } func Trim(str interface{}) string { return strings.TrimSpace(fmt.Sprintf("%s", str)) } // 生成随机数 func RandSeq(n int) string { var letters = []rune("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func SwitchCwbb(baseCwbb, targetCwbb variable.Kjze, basezcfz []models.KjZcfz, baseLrb []models.KjLrb) (zcfz []models.KjZcfz, lrb []models.KjLrb, err error) { uri := `https://daizhang.jsptax.com/api/public/kjzzConvert` payload := map[string]interface{}{ "oldKjzz": string(baseCwbb), "newKjzz": string(targetCwbb), "dataLrb": baseLrb, "dataZcfz": basezcfz, } data, _ := json.Marshal(payload) p := "./data/kjzzConvert" + GetTimestamp(13) + ".json" if !PathExists("pdf" + string(p)) { os.MkdirAll("pdf"+string(p), os.ModePerm) } logger.Info(p) _ = utils.OutputFile(p, data) req, err := http.NewRequest("POST", uri, bytes.NewReader(data)) if err != nil { fmt.Println(err) return } req.Header.Add("Content-Type", "application/json") client := &http.Client{} res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { fmt.Println(err) return } if gjson.GetBytes(body, "msg").String() != "success" { return nil, nil, taxerr.NewUserV3(gjson.GetBytes(body, "msg").String(), "请核实") } _ = json.Unmarshal([]byte(gjson.GetBytes(body, "data.lrb").String()), &lrb) _ = json.Unmarshal([]byte(gjson.GetBytes(body, "data.zcfz").String()), &zcfz) return } // 获取省份 func GetProvince(url string) string { if strings.Contains(url, "sichuan") { return "四川省" } else if strings.Contains(url, "beijing") { return "北京市" } else if strings.Contains(url, "tianjin") { return "天津市" } else if strings.Contains(url, "hebei") { return "河北省" } else if strings.Contains(url, "shanxi") { return "山西省" } else if strings.Contains(url, "neimenggu") { return "内蒙古自治区" } else if strings.Contains(url, "liaoning") { return "辽宁省" } else if strings.Contains(url, "dalian") { return "大连市" } else if strings.Contains(url, "jilin") { return "吉林省" } else if strings.Contains(url, "heilongjiang") { return "黑龙江省" } else if strings.Contains(url, "shanghai") { return "上海市" } else if strings.Contains(url, "jiangsu") { return "江苏省" } else if strings.Contains(url, "zhejiang") { return "浙江省" } else if strings.Contains(url, "ningbo") { return "宁波市" } else if strings.Contains(url, "anhui") { return "安徽省" } else if strings.Contains(url, "fujian") { return "福建省" } else if strings.Contains(url, "xiamen") { return "厦门市" } else if strings.Contains(url, "jiangxi") { return "江西省" } else if strings.Contains(url, "shandong") { return "山东省" } else if strings.Contains(url, "qingdao") { return "青岛市" } else if strings.Contains(url, "henan") { return "河南省" } else if strings.Contains(url, "hubei") { return "湖北省" } else if strings.Contains(url, "hunan") { return "湖南省" } else if strings.Contains(url, "guangdong") { return "广东省" } else if strings.Contains(url, "shenzhen") { return "深圳市" } else if strings.Contains(url, "guangxi") { return "广西壮族自治区" } else if strings.Contains(url, "hainan") { return "海南省" } else if strings.Contains(url, "chongqing") { return "重庆市" } else if strings.Contains(url, "guizhou") { return "贵州省" } else if strings.Contains(url, "yunnan") { return "云南省" } else if strings.Contains(url, "xizang") { return "西藏自治区" } else if strings.Contains(url, "shaanxi") { return "陕西省" } else if strings.Contains(url, "gansu") { return "甘肃省" } else if strings.Contains(url, "qinghai") { return "青海省" } else if strings.Contains(url, "ningxia") { return "宁夏回族自治区" } else if strings.Contains(url, "xinjiang") { return "新疆维吾尔自治区" } else if strings.Contains(url, "taiwan") { return "台湾省" } else { return "" } } // 输入文字 func InputX(p *rod.Page, xPath, inputVal string) { err := rod.Try(func() { if inputVal == "" { inputVal = "0" } _ = rod.Try(func() { elementX := MustElementX(p, xPath) disabled := elementX.MustAttribute(`disabled`) if disabled == nil { elementX.MustType(input.Backspace) elementX.MustSelectAllText().MustInput(inputVal) } }) }) if err != nil { logger.Info(err.Error() + "-" + xPath + "-" + inputVal) } } // 校验填写的对不对 func CheckInput(p *rod.Page, xPath, inputVal string, name string) (errMsg string) { if inputVal == "" { inputVal = "0" } val := GetInputValue(p, xPath) if StrToFloat(val) != StrToFloat(inputVal) { errMsg = fmt.Sprintf("%s不一致,税局:%s,系统:%s;", name, val, inputVal) } return } func SaveCookiesNoPhone(key string) error { var a interface{} _ = lxhttp.PostJson(variable.SessionKeepURL+"/api/v1/session/save", map[string]interface{}{ "sessionKey": key, "tsessionKey": key, }, &a) logger.Info("SaveSessionKey:", key) return nil } func SaveErrImgLv(lv LvName, p *rod.Page, info models.CompanyInfo, location string) string { checkPath := GenerateCheckImagePath(lv, info.TaxNo, info.Period, location) p.Timeout(ClickTimeOut).MustScreenshot(checkPath) _ = watermark.Add(checkPath) checkPath2 := PostSbjt(info.TaxNo, info.Period, checkPath) _ = os.Remove(checkPath) return checkPath2 } func CheckError(ctx context.Context, task *models.TaxTask, err error) { errStr := HandleError(ctx, err).Error() if strings.Contains(errStr, "网页") { //task.Result.Status = variable.TaxFail //task.Result.ErrLog = errStr task.Result.BusinessStatus = variable.TaxFail task.Result.BusinessLog = errStr } else { task.Result.BusinessStatus = variable.TaxFail task.Result.BusinessLog = errStr } } // 时间格式化 func TimeFormatM(str string, Type int) string { if str == "" { return "" } //1.str = 2035年6月23日 if Type == 1 { data, _ := time.Parse("2006年1月2日", str) return data.Format("2006-01-02") } return str } // 去空格 func DelSpace(str string) string { return strings.Replace(str, " ", "", -1) }