wangchuanjin 2 anni fa
parent
commit
cde3a2721f
75 ha cambiato i file con 20125 aggiunte e 3890 eliminazioni
  1. 10 0
      common/src/app.yhyue.com/moapp/message/model/model.go
  2. 1 0
      common/src/common.go
  3. 603 0
      common/src/qfw/util/bidsearch/search.go
  4. 151 0
      common/src/qfw/util/dataexport/common.go
  5. 158 0
      common/src/qfw/util/dataexport/dataExportRecord.go
  6. 1118 0
      common/src/qfw/util/dataexport/dataexport.go
  7. 425 0
      common/src/qfw/util/dataexport/entdataexport.go
  8. 85 0
      common/src/qfw/util/fsw/filter.go
  9. 5 0
      common/src/qfw/util/fsw/fsw.go
  10. 11 0
      common/src/qfw/util/fsw/fsw_test.go
  11. 90 0
      common/src/qfw/util/fsw/fswex.go
  12. 8793 0
      common/src/qfw/util/fsw/mosaic_fsw.dict
  13. 51 0
      common/src/qfw/util/fsw/readdict.go
  14. 53 0
      common/src/qfw/util/jy/appToken.go
  15. 343 0
      common/src/qfw/util/jy/bigVipPower.go
  16. 69 0
      common/src/qfw/util/jy/checksendmsg.go
  17. 599 0
      common/src/qfw/util/jy/entnichepush.go
  18. 68 0
      common/src/qfw/util/jy/freeExperience.go
  19. 89 0
      common/src/qfw/util/jy/historypush.go
  20. 477 0
      common/src/qfw/util/jy/jy.go
  21. 243 0
      common/src/qfw/util/jy/memberpush.go
  22. 102 0
      common/src/qfw/util/jy/newpushmapping.go
  23. 64 0
      common/src/qfw/util/jy/nsq.go
  24. 178 0
      common/src/qfw/util/jy/payUser.go
  25. 27 0
      common/src/qfw/util/jy/payUser_test.go
  26. 76 0
      common/src/qfw/util/jy/pushmapping.go
  27. 1005 0
      common/src/qfw/util/jy/subscribepush.go
  28. 241 0
      common/src/qfw/util/jy/subvipPortrait.go
  29. 56 0
      common/src/qfw/util/jy/subvipPortrait_pack.go
  30. 101 0
      common/src/qfw/util/jy/switchService.go
  31. 67 0
      common/src/qfw/util/jy/userInfo.go
  32. 140 0
      common/src/qfw/util/jy/userMerge.go
  33. 358 0
      common/src/qfw/util/jy/userPhoneUtil.go
  34. 73 0
      common/src/qfw/util/jylog/jylog.go
  35. 55 0
      common/src/qfw/util/middleGround/doPost.go
  36. 0 149
      github.com/SKatiyar/qr/coding/gen.go
  37. 0 815
      github.com/SKatiyar/qr/coding/qr.go
  38. 0 133
      github.com/SKatiyar/qr/coding/qr_test.go
  39. 0 85
      github.com/SKatiyar/qr/gf256/blog_test.go
  40. 0 241
      github.com/SKatiyar/qr/gf256/gf256.go
  41. 0 194
      github.com/SKatiyar/qr/gf256/gf256_test.go
  42. 0 149
      github.com/SKatiyar/qr/libqrencode/qrencode.go
  43. 0 400
      github.com/SKatiyar/qr/png.go
  44. 0 56
      github.com/SKatiyar/qr/png_test.go
  45. 0 116
      github.com/SKatiyar/qr/qr.go
  46. 0 22
      github.com/cron/.gitignore
  47. 0 1
      github.com/cron/.travis.yml
  48. 0 21
      github.com/cron/LICENSE
  49. 0 2
      github.com/cron/README.md
  50. 0 27
      github.com/cron/constantdelay.go
  51. 0 54
      github.com/cron/constantdelay_test.go
  52. 0 227
      github.com/cron/cron.go
  53. 0 310
      github.com/cron/cron_test.go
  54. 0 129
      github.com/cron/doc.go
  55. 0 231
      github.com/cron/parser.go
  56. 0 117
      github.com/cron/parser_test.go
  57. 0 159
      github.com/cron/spec.go
  58. 0 249
      github.com/cron/spec_test.go
  59. 35 1
      go.mod
  60. 1062 2
      go.sum
  61. 78 0
      public/checkholiday.go
  62. 15 0
      public/checkwxbrowser.go
  63. 76 0
      public/convert.go
  64. 206 0
      public/dataexport.go
  65. 169 0
      public/db.go
  66. 335 0
      public/entdataexport.go
  67. 84 0
      public/extractarea.go
  68. 265 0
      public/forceshare.go
  69. 64 0
      public/front.go
  70. 155 0
      public/limitSearchText.go
  71. 279 0
      public/public.go
  72. 123 0
      public/recomKws.go
  73. 139 0
      public/rpccall.go
  74. 1002 0
      public/search.go
  75. 53 0
      public/structed.go

+ 10 - 0
common/src/app.yhyue.com/moapp/message/model/model.go

@@ -0,0 +1,10 @@
+package model
+
+//nsq队列对象
+type Message struct {
+	E_code   string                 //jyweb_article_open/jydocs_doc_open/jywx_subscribe_new/jywx_subscribe_old/jywx_unsubscribe/jyapp_bigmember_order/jyapp_bigmember_pay_success/jyapp_bigmember_pay_fail...
+	E_userId string                 //5ed6cii...
+	E_time   int64                  //1605223065
+	E_app    string                 //jywx_node1/jyweb_node2/jyapp_node1/jysubscribe
+	E_body   map[string]interface{} //选用字段,自定义map { 订单信息/关注信息/页面信息  }
+}

+ 1 - 0
common/src/common.go

@@ -0,0 +1 @@
+package main

+ 603 - 0
common/src/qfw/util/bidsearch/search.go

@@ -0,0 +1,603 @@
+package bidsearch
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	"app.yhyue.com/moapp/jypkg/public"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
+)
+
+//pc、微信、app 招标信息搜索
+
+const (
+	INDEX          = "bidding"
+	TYPE           = "bidding"
+	bidSearch_sort = `{"dataweight":-1,"publishtime":-1}`
+
+	//招标搜索分页--每页显示数量
+	SearchPageSize_APP = 50
+	SearchPageSize_WX  = 50
+	SearchPageSize_PC  = 50
+	//招标搜索分页--最大页数
+	SearchMaxPageNum_APP   = 10
+	SearchMaxPageNum_WX    = 10
+	SearchMaxPageNum_PC    = 10  //免费用户500条记录
+	SearchMaxPageNum_PAYED = 100 //付费用户5000条记录
+
+	SearchMaxPageCount_APP     = 500
+	SearchMaxPageCount_WX      = 500
+	SearchMaxPageCount_PC      = 500  //免费用户500条记录
+	SearchMaxPageCount_PAYED   = 5000 //付费用户5000条记录
+	SearchMaxPageCount_NOLOGIN = 5000 //未登录用户5000条记录
+
+	bidSearch_field_1 = `"_id","title","dataweight","publishtime","toptype","subtype","type","area","city","s_subscopeclass","bidamount","budget","buyerclass","filetext","spidercode","site"`
+	bidSearch_field   = bidSearch_field_1 + `,"bidopentime","winner","buyer","projectname","projectcode","projectinfo"`
+)
+
+// GetWxsearchlistData 移动端招标信息搜索
+func GetWxsearchlistData(keywords, scope, city, publishtime, subtype, industry, minprice, maxprice, winner, buyerclass, hasBuyerTel, hasWinnerTel, fileExists string, pageNum int, selectTypeArr []string, field, notkey string, searchTypeSwitch bool, pageSize int, Sysconfig map[string]interface{}) (list *[]map[string]interface{}, b_word, a_word, s_word string) {
+	var hightlightContent bool = false //是否高亮正文
+	for _, v := range selectTypeArr {
+		if v == "detail" {
+			hightlightContent = true
+			break
+		}
+	}
+	b_word, a_word, s_word = jy.InterceptSearchKW(keywords, util.IntAllDef(Sysconfig["keywordsLimit"], 35), len(industry) == 0)
+	if len(b_word) == 0 {
+		return list, b_word, a_word, s_word
+	}
+	var findfields string
+	if selectTypeArr == nil || len(selectTypeArr) == 0 {
+		findfields = `"title"`
+	} else {
+		findfields = fmt.Sprintf(`"%s"`, strings.Join(selectTypeArr, "\",\""))
+	}
+	qstr := GetSearchQuery(s_word, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, findfields, GetBidSearchQuery(scope, city, publishtime, subtype, winner, buyerclass), notkey, searchTypeSwitch, "")
+	if hightlightContent { //全文搜索
+		list = elastic.GetAllByNgram(INDEX, TYPE, qstr, `"detail"`, bidSearch_sort, field, (pageNum-1)*pageSize, pageSize, 100, true)
+	} else { //标题搜索
+		list = elastic.GetAllByNgram(INDEX, TYPE, qstr, ``, bidSearch_sort, field, (pageNum-1)*pageSize, pageSize, 100, false)
+	}
+	if list != nil {
+		public.BidListConvert(industry, list)
+		for _, v := range *list {
+			v["_id"] = EncodeArticleId2ByCheck(util.ObjToString(v["_id"]))
+		}
+	}
+	return list, b_word, a_word, s_word
+}
+
+// GetPcBidSearchData pc端招标信息搜索
+func GetPcBidSearchData(searchvalue, area, city, publishtime, subtype, industry, minprice, maxprice, winner, buyerclass, hasBuyerTel, hasWinnerTel, fileExists string, start int, isGetCount bool, selectTypeArr []string, field, notkey string, ispayed, searchTypeSwitch bool, bidField string, pageSize int, userId string) (count, totalPage int64, list *[]map[string]interface{}) {
+	var findfields string
+	var hightlightContent bool = false //是否高亮正文
+	for _, v := range selectTypeArr {
+		if v == "detail" {
+			hightlightContent = true
+			break
+		}
+	}
+	if selectTypeArr == nil || len(selectTypeArr) == 0 {
+		findfields = `"title"`
+	} else {
+		findfields = fmt.Sprintf(`"%s"`, strings.Join(selectTypeArr, "\",\""))
+	}
+	qstr := GetSearchQuery(searchvalue, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, findfields, GetBidSearchQuery(area, city, publishtime, subtype, winner, buyerclass), notkey, searchTypeSwitch, bidField)
+	if isGetCount && qstr != "" && start == 0 {
+		count = elastic.Count(INDEX, TYPE, qstr)
+	}
+	if !isGetCount || count > 0 || start > 0 {
+		var repl *[]map[string]interface{}
+		if hightlightContent {
+			repl = elastic.GetAllByNgram(INDEX, TYPE, qstr, `"detail"`, bidSearch_sort, field, start, pageSize, 115, true)
+		} else {
+			repl = elastic.GetAllByNgram(INDEX, TYPE, qstr, ``, bidSearch_sort, field, start, pageSize, 0, false)
+		}
+		if repl != nil && *repl != nil && len(*repl) > 0 {
+			public.BidListConvert(industry, repl)
+			list = repl
+		}
+	}
+	limitCount := util.If(ispayed, int64(SearchMaxPageCount_PAYED), int64(SearchMaxPageCount_PC)).(int64)
+	if userId == "" {
+		limitCount = SearchMaxPageCount_NOLOGIN
+	}
+	if count > limitCount {
+		count = limitCount
+	}
+	if count > 0 {
+		totalPage = (count + int64(pageSize) - 1) / int64(pageSize)
+	}
+	return
+}
+
+var topType = map[string]string{
+	"招标预告":   "预告",
+	"招标公告":   "招标",
+	"招标结果":   "结果",
+	"招标信用信息": "其它",
+}
+
+func GetBidSearchQuery(area, city, publishtime, subtype, winner, buyerclass string) string {
+	query_bool_should := `{"bool":{"should":[%s],"minimum_should_match": 1}}`
+	query := ``
+	if area != "" {
+		query += `{"terms":{"area":[`
+		for k, v := range strings.Split(area, ",") {
+			if k > 0 {
+				query += `,`
+			}
+			query += `"` + v + `"`
+		}
+		query += `]}}`
+	}
+	//
+	if city != "" {
+		if len(query) > 0 {
+			query += ","
+		}
+		query += `{"terms":{"city":[`
+		for k, v := range strings.Split(city, ",") {
+			if k > 0 {
+				query += `,`
+			}
+			query += `"` + v + `"`
+		}
+		query += `]}}`
+	}
+	if query != "" {
+		query = fmt.Sprintf(query_bool_should, query)
+	}
+	if publishtime != "" {
+		if len(query) > 0 {
+			query += ","
+		}
+		starttime, endtime := "", ""
+		now := time.Now()
+		if publishtime == "lately-7" { //最近7天
+			starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local).Unix())
+		} else if publishtime == "lately-30" { //最近30天
+			starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local).Unix())
+		} else if publishtime == "thisyear" { //最近一年
+			starttime = fmt.Sprint(time.Date(now.Year()-1, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+			endtime = fmt.Sprint(now.Unix())
+		} else if publishtime == "threeyear" { //最近三年
+			starttime = fmt.Sprint(time.Date(now.Year()-3, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+			endtime = fmt.Sprint(now.Unix())
+		} else if publishtime == "fiveyear" { //最近五年
+			starttime = fmt.Sprint(time.Date(now.Year()-5, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+			endtime = fmt.Sprint(now.Unix())
+		} else {
+			starttime = strings.Split(publishtime, "_")[0]
+			endtime = strings.Split(publishtime, "_")[1]
+			etTime := time.Now()
+			if endtime != "" {
+				et, _ := strconv.ParseInt(endtime, 0, 64)
+				etTime = time.Unix(et, 0)
+			}
+			endtime = fmt.Sprint(time.Date(etTime.Year(), etTime.Month(), etTime.Day()+1, 0, 0, 0, 0, time.Local).Unix())
+		}
+		query += `{"range":{"publishtime":{`
+		if starttime != "" {
+			query += `"gte":` + starttime
+		}
+		if starttime != "" && endtime != "" {
+			query += `,`
+		}
+		if endtime != "" {
+			query += `"lt":` + endtime
+		}
+		query += `}}}`
+	}
+	if subtype != "" {
+		if len(query) > 0 {
+			query += ","
+		}
+		var topTypes []string
+		var subTypes []string
+
+		for _, v := range strings.Split(subtype, ",") {
+			if v1, ok := topType[v]; ok {
+				topTypes = append(topTypes, fmt.Sprintf(`"%s"`, v1))
+			} else {
+				subTypes = append(subTypes, fmt.Sprintf(`"%s"`, v))
+			}
+		}
+		log.Println("信息类型搜索:", topTypes, subTypes)
+		if len(subTypes) > 0 && len(topTypes) > 0 {
+			query += fmt.Sprintf(`{"bool": {"should": [{"terms": {"subtype": [%s]}},{"terms": {"toptype": [%s]}}]}}`, strings.Join(subTypes, ","), strings.Join(topTypes, ","))
+		} else if len(subTypes) > 0 {
+			query += fmt.Sprintf(`{"terms":{"subtype":[%s]}}`, strings.Join(subTypes, ","))
+		} else if len(topTypes) > 0 {
+			query += fmt.Sprintf(`{"terms":{"toptype":[%s]}}`, strings.Join(topTypes, ","))
+		}
+	}
+	if winner != "" {
+		if len(query) > 0 {
+			query += ","
+		}
+		query += `{"terms":{"s_winner":[`
+		for k, v := range strings.Split(winner, ",") {
+			if k > 0 {
+				query += `,`
+			}
+			query += `"` + v + `"`
+		}
+		query += `]}}`
+	}
+	if buyerclass != "" {
+		if len(query) > 0 {
+			query += ","
+		}
+		query += `{"terms":{"buyerclass":[`
+		for k, v := range strings.Split(buyerclass, ",") {
+			if k > 0 {
+				query += `,`
+			}
+			query += `"` + v + `"`
+		}
+		query += `]}}`
+	}
+	return query
+}
+
+// 包含正文或 附件 不包含标题
+func DetailFileORTitle(findfields string) bool {
+	return (strings.Contains(findfields, `"detail"`) || strings.Contains(findfields, `"filetext"`)) && !strings.Contains(findfields, `"title"`)
+}
+
+// 包含标题和正文
+func DetailTitle(findfields string) bool {
+	return strings.Contains(findfields, `"detail"`) && strings.Contains(findfields, `"title"`)
+}
+func GetSearchQuery(keyword, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, findfields, mustquery, notkey string, searchTypeSwitch bool, bidField string) (qstr string) {
+	multi_match := `{"multi_match": {"query": "%s","type": "phrase", "fields": [%s]}}`
+	query := `{"query":{"bool":{"must":[%s],"must_not":[%s]}}}`
+	//query := `{"query": {"function_score": {"query": {"bool": {"must": [%s],"must_not": [%s]}},"field_value_factor": {"field": "dataweight","modifier": "ln1p","missing": 0}}}}`
+	query_bool_should := `{"bool":{"should":[%s],"minimum_should_match": 1}}`
+	query_bools_must := `{"bool":{"must":[{"range":{"bidamount":{%s}}}]}},{"bool":{"must":[{"range":{"budget":{%s}}}],"must_not":[{"range":{"bidamount":{"gte":-1}}}]}}`
+	query_bool_must := `{"bool":{"must":[{"terms":{"s_subscopeclass":[%s]}}]}}`
+	query_missing := `{"constant_score":{"filter":{"missing":{"field":"%s"}}}}`
+	query_bool_must_term := `{"bool": {"must": [{ "term": {"isValidFile": %d }}]}}`
+	query_bool_must_term_domain := `{"bool": {"must": [{ "term": {"bid_field": "%s" }}]}}` // 领域化数据类型
+	gte := `"gte": %s`
+	lte := `"lte": %s`
+	musts, must_not := []string{}, []string{}
+	if mustquery != "" {
+		musts = append(musts, mustquery)
+	}
+	//搜索范围是否只有附件
+	//搜索范围只选择附件,是否有附件条件无效;
+	var isFileSearch bool = findfields == `"filetext"`
+	if keyword != "" {
+		keyword_multi_match := fmt.Sprintf(multi_match, "%s", findfields)
+		shoulds := []string{}
+		for _, v := range strings.Split(keyword, "+") {
+			if elastic.ReplaceYH(v) == "" {
+				continue
+			}
+			if len([]rune(elastic.ReplaceYH(v))) == 1 {
+				//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+				if DetailFileORTitle(findfields) {
+					keyword_multi_match = fmt.Sprintf(multi_match, "%s", findfields+`,"title"`)
+				}
+			} else {
+				//标题+全文搜索 搜索类型开关打开 默认搜索全文;(全文包含标题)(单字排除)
+				if searchTypeSwitch && DetailTitle(findfields) {
+					if strings.Contains(findfields, `"title",`) {
+						findfields = strings.Replace(findfields, `"title",`, ``, -1)
+					} else if strings.Contains(findfields, `,"title"`) {
+						findfields = strings.Replace(findfields, `,"title"`, ``, -1)
+					}
+					keyword_multi_match = fmt.Sprintf(multi_match, "%s", findfields)
+				}
+			}
+			shoulds = append(shoulds, fmt.Sprintf(keyword_multi_match, elastic.ReplaceYH(v)))
+		}
+		musts = append(musts, fmt.Sprintf(elastic.NgramMust, strings.Join(shoulds, ",")))
+	}
+	if industry != "" {
+		industrys := strings.Split(industry, ",")
+		musts = append(musts, fmt.Sprintf(query_bool_must, `"`+strings.Join(industrys, `","`)+`"`))
+	}
+	if minprice != "" || maxprice != "" {
+		sq := ``
+		if minprice != "" {
+			min, _ := strconv.ParseFloat(minprice, 64)
+			minprice = fmt.Sprintf("%.0f", min*10000)
+			if minprice == "0" {
+				minprice = ""
+			}
+		}
+		if maxprice != "" {
+			max, _ := strconv.ParseFloat(maxprice, 64)
+			maxprice = fmt.Sprintf("%.0f", max*10000)
+			if maxprice == "0" {
+				maxprice = ""
+			}
+		}
+		if minprice != "" {
+			sq += fmt.Sprintf(gte, minprice)
+		}
+		if minprice != "" && maxprice != "" {
+			sq += `,`
+		}
+		if maxprice != "" {
+			sq += fmt.Sprintf(lte, maxprice)
+		}
+		if minprice != "" || maxprice != "" {
+			query_price := fmt.Sprintf(query_bool_should, fmt.Sprintf(query_bools_must, sq, sq))
+			musts = append(musts, query_price)
+		}
+	}
+	if hasBuyerTel != "" {
+		if hasBuyerTel == "y" {
+			must_not = append(must_not, fmt.Sprintf(query_missing, "buyertel"))
+		} else {
+			musts = append(musts, fmt.Sprintf(query_missing, "buyertel"))
+		}
+	}
+	if hasWinnerTel != "" {
+		if hasWinnerTel == "y" {
+			must_not = append(must_not, fmt.Sprintf(query_missing, "winnertel"))
+		} else {
+			musts = append(musts, fmt.Sprintf(query_missing, "winnertel"))
+		}
+	}
+	if notkey = strings.TrimSpace(notkey); notkey != "" {
+		notkey_multi_match := fmt.Sprintf(multi_match, "%s", findfields)
+		notkey_must_not := []string{}
+		for _, v := range strings.Split(notkey, " ") {
+			v = strings.TrimSpace(v)
+			if v == "" {
+				continue
+			}
+			if len([]rune(elastic.ReplaceYH(v))) == 1 {
+				//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+				if DetailFileORTitle(findfields) {
+					notkey_multi_match = fmt.Sprintf(multi_match, "%s", findfields+`,"title"`)
+				}
+			}
+			notkey_must_not = append(notkey_must_not, fmt.Sprintf(notkey_multi_match, elastic.ReplaceYH(v)))
+		}
+		must_not = append(must_not, fmt.Sprintf(query_bool_should, strings.Join(notkey_must_not, ",")))
+	}
+	if !isFileSearch && fileExists != "" {
+		if fileExists == "1" { //有附件
+			must_not = append(must_not, fmt.Sprintf(query_missing, "isValidFile"))
+			musts = append(musts, fmt.Sprintf(query_bool_must_term, 1))
+		} else if fileExists == "-1" { //无附件
+			musts = append(musts, fmt.Sprintf(query_missing, "isValidFile"))
+		}
+	}
+	// 如果是领域化数据则需要加标签
+	if bidField != "" {
+		musts = append(musts, fmt.Sprintf(query_bool_must_term_domain, bidField))
+	}
+	qstr = fmt.Sprintf(query, strings.Join(musts, ","), strings.Join(must_not, ","))
+	log.Println(qstr)
+	return
+}
+
+func PublicSearch(userId, selectType, publishtime string, bidSearchOldUserLimit int64, currentPage, pageSize int) (bool, string, []string, int, int) {
+	var start int
+	vipStatus := jy.GetVipState(public.Mysql, public.MQFW, userId)
+	isPayedUser := vipStatus.IsPayedUser()
+	queryItems := vipStatus.GetQueryItems(selectType, bidSearchOldUserLimit)
+
+	if publishtime == "" {
+		if isPayedUser {
+			publishtime = "fiveyear"
+		} else {
+			publishtime = "thisyear"
+		}
+	}
+
+	if isPayedUser {
+		//if currentPage > SearchMaxPageCount_PAYED {
+		//	currentPage = SearchMaxPageCount_PAYED
+		//}
+	} else {
+		//if currentPage > SearchMaxPageCount_PC {
+		//	currentPage = SearchMaxPageCount_PC
+		//}
+		//时间自定义选择默认是vip 大会员 等权限
+		if len(strings.Split(publishtime, "_")) == 2 || publishtime == "threeyear" || publishtime == "fiveyear" {
+			publishtime = ""
+		}
+	}
+	start = (currentPage - 1) * pageSize
+
+	return isPayedUser, publishtime, queryItems, currentPage, start
+}
+
+// 所有的再次分词查询 只查标题
+func IntegratedData(platform string, s_word, secondKWS, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, secondFlag, area, city, publishtime, subtype, buyerclass, notkey string, queryItems []string, list *[]map[string]interface{}, field string, searchTypeSwitch bool, bidField string, pageSize int) (string, string, string, string, *[]map[string]interface{}) {
+	var pcAjaxFlag string
+	var secRel *[]map[string]interface{} = list
+	if secondKWS = jy.HttpEs(s_word, "ik_smart", public.DbConf.Elasticsearch.Main.Address); secondKWS != "" {
+		findfields := `"title"`
+		qstr := GetSearchQuery(secondKWS, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, findfields, GetBidSearchQuery(area, city, publishtime, subtype, "", buyerclass), notkey, searchTypeSwitch, bidField)
+		secRel = elastic.GetAllByNgram(INDEX, TYPE, qstr, findfields, bidSearch_sort, field, 0, 2*pageSize, 0, false)
+		if secRel != nil && len(*secRel) > 0 {
+			public.BidListConvert(industry, secRel)
+			if platform == "app" || platform == "wx" {
+				for _, v := range *secRel {
+					v["_id"] = EncodeArticleId2ByCheck(util.ObjToString(v["_id"]))
+				}
+			}
+		}
+		if list != nil {
+			secRel = public.MapArrSortMerge(*list, *secRel, "_id", "publishtime")
+		}
+		secondFlag = "T"
+		pcAjaxFlag = "T"
+		if secondKWS != "" {
+			s_word += "+" + secondKWS
+		}
+	}
+	return s_word, pcAjaxFlag, secondFlag, secondKWS, secRel
+}
+
+func classify(stp, area, industry string, configData map[string]interface{}) (string, string, string) {
+	var areas, _ = configData["area"].(map[string]interface{})
+	var stypes, _ = configData["stype"].(map[string]interface{})
+	var industrys, _ = configData["industry"].(map[string]interface{})
+	var tpadd = ""
+	var areaadd = ""
+	var induadd = ""
+	if area != "" && area != "A" {
+		for k, v := range areas {
+			if area == v.(map[string]interface{})["NAME"] {
+				areaadd = k
+			}
+		}
+	}
+	//
+	if stp != "" {
+		for k, v := range stypes {
+			if stp == v.(map[string]interface{})["NAME"] {
+				tpadd = k
+			}
+		}
+	}
+	//
+	if industry != "" {
+		for k, v := range industrys {
+			if strings.Contains(util.ObjToString(v.(map[string]interface{})["NAME"]), industry) {
+				induadd = k
+			}
+		}
+	}
+	return tpadd, areaadd, induadd
+}
+
+// list != nil && len(*list) == bidsearch.SearchPageSize_APP && pageNum < util.If(isPayedUser, bidsearch.SearchMaxPageNum_PAYED, bidsearch.SearchMaxPageNum_APP).(int)
+// 数据格式化处理
+func LisetData(stype, pageNum int, list *[]map[string]interface{}, secondFlag string, configData map[string]interface{}, isPayedUser bool, pageSize int, userId string) ([]map[string]interface{}, int64, bool) {
+	var (
+		secondList  []map[string]interface{}
+		totalPage   int64
+		hasNextPage bool
+	)
+	if list != nil && len(*list) > 0 {
+		if stype != 3 && stype != 4 {
+			for _, v := range *list {
+				if v["_id"] != nil {
+					v["_id"] = EncodeArticleId2ByCheck(v["_id"].(string))
+				}
+				stp, ok := v["subtype"].(string)
+				if ok && stp == "" {
+					stp = v["toptype"].(string)
+				}
+				area, ok := v["area"].(string)
+				indtry := util.ObjToString(v["industry"])
+				v["stypeadd"], v["areaadd"], v["indadd"] = classify(stp, area, indtry, configData)
+				//正文匹配检索关键词
+				highlight, _ := v["highlight"].(map[string][]string)
+				detail := ""
+				for _, val := range highlight["detail"] {
+					detail += public.ClearHtml.ReplaceAllString(val, "")
+				}
+				v["detail"] = detail
+				v["href"] = EncodeArticleId2ByCheck(util.GetRandom(20))
+				if isValidFile, _ := v["isValidFile"].(bool); isValidFile {
+					delete(v, "filetext")
+					v["fileExists"] = true
+				}
+			}
+		}
+		if secondFlag != "" {
+			if len(*list) > pageSize {
+				secondList = (*list)[pageSize:]
+				if len(secondList) > pageSize {
+					secondList = secondList[:pageSize]
+				}
+				*list = (*list)[:pageSize]
+				totalPage = 2
+			} else {
+				totalPage = 1
+			}
+		}
+	}
+
+	if list != nil && len(*list) == pageSize {
+		hasNextPage = true
+	}
+	if isPayedUser && pageNum >= SearchMaxPageCount_PAYED {
+		hasNextPage = false
+	} else if !isPayedUser && pageNum >= SearchMaxPageCount_WX {
+		hasNextPage = false
+	}
+	if userId == "" && pageNum > SearchMaxPageCount_NOLOGIN {
+		hasNextPage = false
+	}
+	return secondList, totalPage, hasNextPage
+}
+
+func SearchData(platform string, request *http.Request, currentPage int, userId, secondKWS, s_word, area, city, publishtime, subtype, industry, minprice, maxprice, winner, buyerclass, hasBuyerTel, hasWinnerTel, fileExists string, start int, isGetCount bool, queryItems []string, field, notkey string, isPayedUser, searchTypeSwitch bool, bidField string, pageSize int, Sysconfig map[string]interface{}) (second, b_word, a_word, pcAjaxFlag, secondFlag string, count, totalPage int64, list *[]map[string]interface{}) {
+	//包含标题才能进行二次搜索
+	var secondSearch = strings.Contains(strings.Join(queryItems, ","), "title")
+	var searchvalue = s_word
+	number := util.If(platform == "app" || platform == "wx", 1, 0)
+	if platform == "app" || platform == "wx" {
+		list, b_word, a_word, s_word = GetWxsearchlistData(s_word, area, city, publishtime, subtype, industry, minprice, maxprice, winner, buyerclass, hasBuyerTel, hasWinnerTel, fileExists, start, queryItems, field, notkey, searchTypeSwitch, pageSize, Sysconfig)
+		if list != nil && len(*list) != 0 {
+			count = int64(len(*list))
+		}
+	} else {
+		count, totalPage, list = GetPcBidSearchData(s_word, area, city, publishtime, subtype, industry, minprice, maxprice, winner, buyerclass, hasBuyerTel, hasWinnerTel, fileExists, start, isGetCount, queryItems, field, notkey, isPayedUser, searchTypeSwitch, bidField, pageSize, userId)
+	}
+	if len([]rune(s_word)) > 3 && int(count) < pageSize && start == number && secondSearch {
+		var paramList = list
+		s_word, pcAjaxFlag, secondFlag, second, list = IntegratedData(platform, s_word, secondKWS, industry, minprice, maxprice, hasBuyerTel, hasWinnerTel, fileExists, secondFlag, area, city, publishtime, subtype, buyerclass, notkey, queryItems, paramList, field, searchTypeSwitch, bidField, pageSize)
+	}
+
+	listSize := 0
+	if list != nil {
+		listSize = len(*list)
+	}
+	public.SaveUserSearchLog(request, userId, -1, platform, "超级搜索", map[string]interface{}{
+		"search_word":        util.If(platform == "app" || platform == "wx", searchvalue, s_word),
+		"search_area":        area,
+		"search_city":        city,
+		"search_price":       []string{minprice, maxprice},
+		"search_publishtime": publishtime,
+		"search_type":        subtype,
+		"search_industry":    industry,
+		"pagenum":            currentPage,
+		"pagesize":           listSize,
+		"fileExists":         fileExists,
+		"bid_field":          util.If(bidField != "", bidField, nil),
+	})
+	return
+}
+
+func AddHistory(history, searchvalue string) []string {
+	arrs := strings.Split(history, ",")
+	//新增历史记录
+	if history == "" {
+		arrs = make([]string, 0)
+	}
+	for k, v := range arrs {
+		if v == strings.TrimSpace(searchvalue) {
+			arrs = append(arrs[:k], arrs[k+1:]...)
+			break
+		}
+	}
+	arrs = append(arrs, searchvalue)
+	if len(arrs) > 10 {
+		arrs = arrs[1:11]
+	}
+	return arrs
+}

+ 151 - 0
common/src/qfw/util/dataexport/common.go

@@ -0,0 +1,151 @@
+package dataexport
+
+import (
+	"fmt"
+	"log"
+	qutil "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"strings"
+)
+
+// GetLastExportPhoneAndMail 数据导出联想上次导出手机号和邮箱查询
+func GetLastExportPhoneAndMail(mysqlSess *mysql.Mysql, userId, entUserId string) (phone, email string) {
+	var searchSql []string
+	var searchValue []interface{}
+
+	if userId != "" {
+		//个人直接支付数据导出
+		searchSql = append(searchSql, `(SELECT user_phone AS phone,user_mail AS mail,UNIX_TIMESTAMP(create_time) AS exportDate FROM dataexport_order where user_mail  IS NOT NULL and user_phone is NOT NULL AND user_id =? AND product_type ='历史数据' ORDER BY create_time DESC LIMIT 1)`)
+		searchValue = append(searchValue, userId)
+
+		//每日限量包和个人线上数据包
+		searchSql = append(searchSql, `(SELECT phone,mail,date AS exportDate FROM datapacket_record WHERE  phone IS NOT NULL AND mail IS NOT NULL AND useid = ? ORDER BY id DESC LIMIT 1)`)
+		searchValue = append(searchValue, userId)
+
+	}
+
+	//企业数据导出查询
+	if entUserId != "" {
+		searchSql = append(searchSql, `(SELECT export_phone AS phone,export_mail AS mail,UNIX_TIMESTAMP(export_time) AS exportDate FROM entniche_export_log WHERE export_phone IS NOT NULL AND export_mail IS NOT NULL AND user_id =? ORDER BY id desc limit 1)`)
+		searchValue = append(searchValue, entUserId)
+	}
+
+	//log.Println(fmt.Sprintf(`SELECT phone,mail FROM ( %s ) AS allData ORDER BY exportDate DESC limit 1`, strings.Join(searchSql, ` UNION ALL `)))
+	res := mysqlSess.Query(fmt.Sprintf(`SELECT phone,mail FROM ( %s ) AS allData ORDER BY exportDate DESC limit 1`, strings.Join(searchSql, ` UNION ALL `)), searchValue...)
+	if res != nil && len(*res) > 0 {
+		phone, _ = (*res)[0]["phone"].(string)
+		email, _ = (*res)[0]["mail"].(string)
+	}
+	return
+}
+
+// GetDataExportMatchKey key 关键词 逗号分隔 或的关系进行关联  types 匹配类型  data数据
+// title 标题,detail 正文,filetext 附件,purchasing 标的物,projectname.pname 项目命,mbuyer 采购单位,mwinner 中标单位,magency 代理机构
+func GetDataExportMatchKey(scd *SieveCondition, list *[]map[string]interface{}) {
+	if list == nil || len(*list) == 0 {
+		return
+	}
+	if scd.Comeinfrom == "pushHistory" { //历史推送数据使用推送匹配关键词 if len(scd.PushKeyWords) == len(*list) {
+		if len(scd.SelectIds) == len(scd.PushKeyWords) {
+			for _, v := range *list {
+				for k, v1 := range scd.SelectIds {
+					if v1 == qutil.InterfaceToStr(v["_id"]) {
+						v["keyword"] = scd.PushKeyWords[k]
+					}
+				}
+				//v["keyword"] = scd.PushKeyWords[qutil.InterfaceToStr(v["_id"])]
+			}
+		} else {
+			log.Println("历史推送数据订阅词匹配失败++++", len(scd.SelectIds), len(scd.PushKeyWords), scd.Id)
+		}
+	} else if len(scd.Keyword) > 0 && scd.Keyword[0].Keyword != "" { //关键词二次匹配
+		types := scd.SelectType
+		var keys []string
+		//统计关键词
+		for _, vk := range scd.Keyword {
+			key := []string{}
+			if strings.Contains(vk.Keyword, " ") {
+				for _, v := range strings.Split(vk.Keyword, " ") {
+					if v != "" {
+						key = append(key, v)
+					}
+				}
+			} else {
+				key = append(key, vk.Keyword)
+			}
+			vk.Keyword = strings.Join(key, ",")
+			keys = append(keys, strings.Replace(vk.Keyword, "+", ",", -1))
+		}
+		key := strings.Join(keys, ",")
+		//二次匹配
+		for _, data := range *list {
+			keyWord := []string{}
+			if strings.Contains(types, "title") {
+				title := qutil.ObjToString(data["title"])
+				keyWord = KeyWordToDatas(title, key, keyWord)
+			}
+			if strings.Contains(types, "detail") {
+				detail := qutil.ObjToString(data["detail"])
+				keyWord = KeyWordToDatas(detail, key, keyWord)
+			}
+			if strings.Contains(types, "purchasing") {
+				purchasing := qutil.ObjToString(data["purchasing"])
+				keyWord = KeyWordToDatas(purchasing, key, keyWord)
+			}
+			if strings.Contains(types, "filetext") {
+				filetext := qutil.ObjToString(data["filetext"])
+				keyWord = KeyWordToDatas(filetext, key, keyWord)
+			}
+			if strings.Contains(types, "projectname.pname") {
+				projectname := qutil.ObjToString(data["projectname"])
+				keyWord = KeyWordToDatas(projectname, key, keyWord)
+			}
+			if strings.Contains(types, "mbuyer") {
+				buyer := qutil.ObjToString(data["buyer"])
+				keyWord = KeyWordToDatas(buyer, key, keyWord)
+			}
+			if strings.Contains(types, "mwinner") {
+				winner := qutil.ObjToString(data["s_winner"])
+				keyWord = KeyWordToDatas(winner, key, keyWord)
+			}
+			if strings.Contains(types, "magency") {
+				winner := qutil.ObjToString(data["agency"])
+				keyWord = KeyWordToDatas(winner, key, keyWord)
+			}
+			keyMap := map[string]bool{}
+			keyArr := []string{}
+			for _, key1 := range keyWord {
+				keyMap[key1] = true
+			}
+			for k, _ := range keyMap {
+				keyArr = append(keyArr, k)
+			}
+			data["keyword"] = strings.Join(keyArr, ",")
+		}
+	}
+}
+
+func KeyWordToDatas(item, key string, keyWord []string) []string {
+	for _, mk := range strings.Split(key, ",") {
+		if strings.Contains(mk, "&&") {
+			arr := strings.Split(mk, "&&")
+			for _, s := range arr {
+				if s != "" {
+					if strings.Contains(strings.ToUpper(item), strings.ToUpper(s)) {
+						keyWord = append(keyWord, mk)
+					}
+				}
+			}
+		} else {
+			if strings.Contains(strings.ToUpper(item), strings.ToUpper(mk)) {
+				keyWord = append(keyWord, mk)
+			}
+		}
+	}
+	return keyWord
+}
+
+// 获取关键词匹配字段
+func getKeywordFields() {
+
+}

+ 158 - 0
common/src/qfw/util/dataexport/dataExportRecord.go

@@ -0,0 +1,158 @@
+package dataexport
+
+import (
+	"encoding/json"
+	"fmt"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+	"strings"
+	"time"
+)
+
+//数据包导出记录
+type DataPacketRecord struct {
+	InfoIds []string //导出数据ids
+	Date    int64    //导出时间戳
+}
+
+//DataPacketManager 数据包
+//MasterUser 主账户id
+//RecordArr 缓存于redis一天,导出后清除、只插入不更新 key:dataPacketRecord_masterUser
+//RepeatMap 限定时间内已导出的信息id,去重时使用
+//Quota 根据大会员套餐内每日数据包额度
+//Used 已使用额度
+type DataPacketMsg struct {
+	masterId  string              //主账户id
+	quota     int                 //每日额度
+	used      int                 //已使用额度
+	recordArr []*DataPacketRecord //导出记录
+	repeatMap map[string]bool     //数据导出去重使用
+}
+
+const (
+	DataPacketCacheKey     = "dataPacketRecord_%s_%d"
+	DataPacketUsedNum      = "dataPacketUsed_%s_%d"
+	DataPacKetUpDateNumKey = "dataPacketUpdateIng_%s_%d"
+
+	redisCacheDb = "other"
+	cacheOneDay  = 60 * 60 * 24
+
+	recordTable = "datapacket_record"
+)
+
+//1企业导出 2大会员流量包 3个人
+const (
+	EntExport = iota
+	BigMemberDaily
+	PersonalExport
+)
+
+//GetDataPacketMsg 获取数据导出记录
+//masterId主账户id
+//Quota 限额
+//Db mysql数据库链接
+func GetDataPacketMsg(masterId string, quota int) *DataPacketMsg {
+	dpm := &DataPacketMsg{}
+
+	dpm.masterId = masterId
+	dpm.quota = quota
+
+	return dpm
+}
+
+func (dpm *DataPacketMsg) GetUsed(isNewest bool) int {
+	if isNewest || dpm.used == 0 {
+		dpm.used = redis.GetInt(redisCacheDb, fmt.Sprintf(DataPacketUsedNum, dpm.masterId, time.Now().Day()))
+	}
+	return dpm.used
+}
+
+func (dpm *DataPacketMsg) getDataPacketRecord(mysql *mysql.Mysql) (dpr []*DataPacketRecord) {
+	cacheKey := fmt.Sprintf(DataPacketCacheKey, dpm.masterId, time.Now().Day())
+	if bytes, err := redis.GetBytes(redisCacheDb, cacheKey); err == nil && bytes != nil {
+		if err := json.Unmarshal(*bytes, dpr); err == nil {
+			return
+		}
+	}
+	//查询数据库
+	dpr = []*DataPacketRecord{}
+	for _, val := range *mysql.Find("datapacket_record", map[string]interface{}{"master_id": dpm.masterId}, "date,infoids", "date", -1, -1) {
+		if idStr, _ := val["infoids"].(string); idStr != "" {
+			dpr = append(dpr, &DataPacketRecord{
+				InfoIds: strings.Split(idStr, ","),
+				Date:    val["date"].(int64),
+			})
+		}
+	}
+	go func() {
+		if bytes, err := json.Marshal(dpr); err == nil && bytes != nil {
+			_ = redis.PutBytes(redisCacheDb, cacheKey, &bytes, cacheOneDay)
+		}
+	}()
+	return
+}
+
+//数据导出去重
+func (dpm *DataPacketMsg) GetRepeatMap(db *mysql.Mysql) map[string]bool {
+	if dpm.repeatMap != nil {
+		return dpm.repeatMap
+	}
+	if dpm.recordArr == nil {
+		dpm.recordArr = dpm.getDataPacketRecord(db)
+	}
+	dpm.repeatMap = make(map[string]bool)
+	for _, arr := range dpm.recordArr {
+		for _, infoId := range arr.InfoIds {
+			dpm.repeatMap[infoId] = true
+		}
+	}
+	return dpm.repeatMap
+}
+
+//数据导出数量更新
+func (dpm *DataPacketMsg) AddNum(num int) (err error) {
+	//redis分布式锁,保证同一时刻只会有一个进程更改数量
+	runningKey := fmt.Sprintf(DataPacKetUpDateNumKey, dpm.masterId, time.Now().Day())
+	if redis.Incr(redisCacheDb, runningKey) == 1 {
+		defer redis.Del(redisCacheDb, runningKey) //执行完毕 清除锁
+
+		//查询当前使用
+		used := dpm.GetUsed(true)
+
+		//更新
+		used += num
+		if used > dpm.quota {
+			err = fmt.Errorf("余额不足")
+			return
+		}
+
+		numKey := fmt.Sprintf(DataPacketUsedNum, dpm.masterId, time.Now().Day())
+		if redis.Put(redisCacheDb, numKey, used, cacheOneDay) {
+			dpm.used = used
+		} else {
+			err = fmt.Errorf("余额扣除失败")
+		}
+		return
+	}
+	return fmt.Errorf("稍后重试")
+}
+
+func (dpm *DataPacketMsg) SaveRecord(db *mysql.Mysql, useId, selectId string, t int, idList []string, filePath, phone, email string) error {
+	if db.Insert(recordTable, map[string]interface{}{
+		"infoids":    strings.Join(idList, ","),
+		"type":       t,
+		"master_id":  dpm.masterId,
+		"useid":      useId,
+		"query_id":   selectId,
+		"date":       time.Now().Unix(),
+		"path":       filePath,
+		"deduct_num": len(idList),
+		"export_num": len(idList),
+		"phone":      phone,
+		"mail":       email,
+		"isSenior":   2,
+	}) == 0 {
+		return fmt.Errorf("数据导出记录保存出错 userId:%s,selectId:%s,类型:%d,导出数据:%v", useId, selectId, t, idList)
+	}
+	return nil
+}

+ 1118 - 0
common/src/qfw/util/dataexport/dataexport.go

@@ -0,0 +1,1118 @@
+package dataexport
+
+import (
+	//"config"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"math"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	qutil "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	mg "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
+)
+
+/*筛选条件--关键词*/
+type KeyWord struct {
+	Keyword  string   `json:"keyword"`  //关键词
+	Appended []string `json:"appended"` //附加词
+	Exclude  []string `json:"exclude"`  //排除词
+}
+
+/*筛选条件*/
+type SieveCondition struct {
+	Id               string    `json:"id"`
+	PublishTime      string    `json:"publishtime"`      //发布时间
+	Area             []string  `json:"area"`             //地区-省份
+	City             []string  `json:"city"`             //地区-城市
+	Region           []string  `json:"region"`           //地区-省份+城市
+	Industry         []string  `json:"industry"`         //行业
+	Keyword          []KeyWord `json:"keywords"`         //关键词
+	Buyer            []string  `json:"buyer"`            //招标单位(采购单位)
+	Buyerclass       []string  `json:"buyerclass"`       //采购单位类型
+	HasBuyerTel      string    `json:"hasBuyertel"`      //是否有采购单位电话
+	Winner           []string  `json:"winner"`           //中标单位
+	HasWinnerTel     string    `json:"hasWinnertel"`     //是否有中标单位电话
+	ComeInTime       int64     `json:"comeintime"`       //入库时间(秒)
+	OpenId           string    `json:"openid"`           //用户openid
+	MinPrice         string    `json:"minprice"`         //金额——最少
+	MaxPrice         string    `json:"maxprice"`         //金额——最多
+	SelectType       string    `json:"selectType"`       //筛选(正文 or 标题)
+	Subtype          string    `json:"subtype"`          //信息类型
+	SelectIds        []string  `json:"selectId"`         //选择信息导出
+	PushKeyWords     []string  `json:"pushKeyWords"`     //推送历史匹配词
+	Comeinfrom       string    `json:"comeinfrom"`       //查询来源
+	FileExists       string    `json:"fileExists"`       //是否有附件
+	SearchTypeSwitch bool      `json:"searchTypeSwitch"` //是否开启 正文 标题同时搜索只搜正文的开关
+	BidField         string    `json:"bid_field"`        // 领域化数据 0101-医疗行业
+}
+
+const (
+	INDEX          = "bidding"
+	TYPE           = "bidding"
+	bidSearch_sort = `{"publishtime":-1}`
+)
+
+var ClearOther = regexp.MustCompile("[\n\r\\s\u3000\u2003\u00a0]")
+var ClearHtml = regexp.MustCompile("<[^>]*>")
+var onceSearchCount = 500
+var ExportTable string = "export_search"
+var searchPool = make(chan bool, 8)
+
+var topType = map[string]string{
+	"招标预告":   "预告",
+	"招标公告":   "招标",
+	"招标结果":   "结果",
+	"招标信用信息": "其它",
+	"拟建项目":   "拟建",
+	"采购意向":   "采购意向",
+}
+
+// 包含正文或 附件 不包含标题
+func DetailFileORTitle(findfields string) bool {
+	return (strings.Contains(findfields, "detail") || strings.Contains(findfields, "filetext")) && !strings.Contains(findfields, "title")
+}
+
+// 包含正文包含标题
+func DetailANDTitle(findfields string) bool {
+	return strings.Contains(findfields, "detail") && strings.Contains(findfields, "title")
+}
+
+// 获取数据导出查询语句
+func getDataExportSql(scd *SieveCondition) string {
+	if len(scd.SelectIds) > 0 {
+		query := `{"query":{"bool":{"must":[%s]}}}`
+		doSearchSql := fmt.Sprintf(`{"terms":{"_id":[%s]}}`, `"`+strings.Join(scd.SelectIds, `","`)+`"`)
+		return fmt.Sprintf(query, doSearchSql)
+	}
+	multi_match := `{"multi_match": {"query": %s,"type": "phrase", "fields": [%s]}}`
+	query := `{"query":{"bool":{"must":[%s],"must_not":[%s],"should":[%s],"minimum_should_match": %d}}}`
+	//query := `{"query": {"function_score": {"query": {"bool": {"must": [%s],"must_not": [%s],"should": [%s],"minimum_should_match": %d}},"field_value_factor": {"field": "dataweight","modifier": "ln1p","missing": 0}}}}`
+	query_bool_should := `{"bool":{"should":[%s],"minimum_should_match": 1}}`
+	query_price := `{"bool":{"must":[{"range":{"bidamount":{%s}}}]}},{"bool":{"must":[{"range":{"budget":{%s}}}],"must_not":[{"range":{"bidamount":{"gte":-1}}}]}}`
+	query_bool_must := `{"terms":{"%s":[%s]}}`
+	query_bool_must_and := `{"bool":{"must":[%s]%s}}`
+	query_missing := `{"constant_score":{"filter":{"missing":{"field":"%s"}}}}`
+	query_bool_must_term := `{"bool": {"must": [{ "term": {"isValidFile": %d }}]}}`
+	query_bool_must_term_bidField := `{"bool": {"must": [{ "term": {"bid_field": "%s" }}]}}` // 领域化数据类型
+	gte := `"gte": %s`
+	lte := `"lte": %s`
+
+	bools := []string{}
+	musts := []string{fmt.Sprintf(`{"range":{"comeintime":{"lt":%d}}}`, scd.ComeInTime)}
+	must_not := []string{}
+	//省份
+	areaCity := []string{}
+	if len(scd.Area) > 0 {
+		areaquery := `{"terms":{"area":[`
+		for k, v := range scd.Area {
+			if k > 0 {
+				areaquery += `,`
+			}
+			areaquery += `"` + v + `"`
+		}
+		areaquery += `]}}`
+		areaCity = append(areaCity, areaquery)
+	}
+	//城市
+	if len(scd.City) > 0 {
+		areaquery := `{"terms":{"city":[`
+		for k, v := range scd.City {
+			if k > 0 {
+				areaquery += `,`
+			}
+			areaquery += `"` + v + `"`
+		}
+		areaquery += `]}}`
+		areaCity = append(areaCity, areaquery)
+	}
+	if len(areaCity) > 0 {
+		musts = append(musts, fmt.Sprintf(query_bool_should, strings.Join(areaCity, ",")))
+	}
+	//检索日期
+
+	starttime := ""
+	now := time.Unix(scd.ComeInTime, 0)
+	endtime := fmt.Sprintf("%d", now.Unix())
+	if scd.PublishTime == "lately-7" { //最近7天
+		starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local).Unix())
+	} else if scd.PublishTime == "lately-30" { //最近30天
+		starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local).Unix())
+	} else if scd.PublishTime == "thisyear" { //去年
+		starttime = fmt.Sprint(time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, time.Local).Unix())
+		endtime = fmt.Sprint(time.Date(now.Year()-1, 12, 31, 23, 59, 59, 0, time.Local).Unix())
+	} else if strings.Contains(scd.PublishTime, "_") { //设置检索日期
+		starttime = strings.Split(scd.PublishTime, "_")[0]
+		endTime_tmp := now
+		if etime := strings.Split(scd.PublishTime, "_")[1]; etime != "" {
+			etTime := time.Unix(qutil.Int64All(etime), 0)
+			endTime_tmp = time.Date(etTime.Year(), etTime.Month(), etTime.Day()+1, 0, 0, 0, 0, time.Local)
+		}
+		//结束时间必须小于筛选时间
+		if endTime_tmp.After(now) {
+			endTime_tmp = now
+		}
+		endtime = fmt.Sprintf("%d", endTime_tmp.Unix())
+	}
+	timequery := `{"range":{"publishtime":{`
+	if starttime != "" {
+		timequery += `"gte":` + starttime
+	}
+	if starttime != "" && endtime != "" {
+		timequery += `,`
+	}
+	if endtime != "" {
+		timequery += `"lt":` + endtime
+	}
+	timequery += `}}}`
+	musts = append(musts, timequery)
+
+	if scd.Subtype != "" {
+		var subquery string
+		var topTypes []string
+		var subTypes []string
+
+		for _, v := range strings.Split(scd.Subtype, ",") {
+			if v1, ok := topType[v]; ok {
+				topTypes = append(topTypes, fmt.Sprintf(`"%s"`, v1))
+			} else {
+				subTypes = append(subTypes, fmt.Sprintf(`"%s"`, v))
+			}
+		}
+		log.Println("信息类型搜索:", topTypes, subTypes)
+		if len(subTypes) > 0 && len(topTypes) > 0 {
+			subquery = fmt.Sprintf(`{"bool": {"should": [{"terms": {"subtype": [%s]}},{"terms": {"toptype": [%s]}}]}}`, strings.Join(subTypes, ","), strings.Join(topTypes, ","))
+		} else if len(subTypes) > 0 {
+			subquery = fmt.Sprintf(`{"terms":{"subtype":[%s]}}`, strings.Join(subTypes, ","))
+		} else if len(topTypes) > 0 {
+			subquery = fmt.Sprintf(`{"terms":{"toptype":[%s]}}`, strings.Join(topTypes, ","))
+		}
+		musts = append(musts, subquery)
+	}
+	if len(scd.Industry) > 0 {
+		musts = append(musts, fmt.Sprintf(query_bool_must, "s_subscopeclass", `"`+strings.Join(scd.Industry, `","`)+`"`))
+	}
+	if len(scd.Buyer) > 0 {
+		musts = append(musts, fmt.Sprintf(query_bool_must, "buyer", `"`+strings.Join(scd.Buyer, `","`)+`"`))
+	}
+	if len(scd.Buyerclass) > 0 {
+		musts = append(musts, fmt.Sprintf(query_bool_must, "buyerclass", `"`+strings.Join(scd.Buyerclass, `","`)+`"`))
+	}
+	if len(scd.Winner) > 0 {
+		musts = append(musts, fmt.Sprintf(query_bool_must, "s_winner", `"`+strings.Join(scd.Winner, `","`)+`"`))
+	}
+	_minPrice := ""
+	_maxPrice := ""
+	if scd.MinPrice != "" || scd.MaxPrice != "" {
+		sq := ``
+		if scd.MinPrice != "" {
+			min, _ := strconv.ParseFloat(scd.MinPrice, 64)
+			_minPrice = fmt.Sprintf("%.0f", min*10000)
+			if _minPrice == "0" {
+				_minPrice = ""
+			}
+		}
+		if scd.MaxPrice != "" {
+			max, _ := strconv.ParseFloat(scd.MaxPrice, 64)
+			_maxPrice = fmt.Sprintf("%.0f", max*10000)
+			if _maxPrice == "0" {
+				_maxPrice = ""
+			}
+		}
+		if _minPrice != "" {
+			sq += fmt.Sprintf(gte, _minPrice)
+		}
+		if _minPrice != "" && _maxPrice != "" {
+			sq += `,`
+		}
+		if _maxPrice != "" {
+			sq += fmt.Sprintf(lte, _maxPrice)
+		}
+		if _minPrice != "" || _maxPrice != "" {
+			query_price := fmt.Sprintf(query_bool_should, fmt.Sprintf(query_price, sq, sq))
+			musts = append(musts, query_price)
+		}
+	}
+	boolsNum := 0
+	selectType := scd.SelectType
+	//should
+	if len(scd.Keyword) > 0 {
+		boolsNum = 1
+		queryItem := ""
+		if selectType == "" {
+			queryItem = "title"
+		} else if selectType == "all" {
+			queryItem = "detail\", \"title"
+		} else {
+			if scd.SearchTypeSwitch && DetailANDTitle(selectType) {
+				if strings.Contains(selectType, "title,") {
+					selectType = strings.Replace(selectType, "title,", "", -1)
+				} else if strings.Contains(selectType, ",title") {
+					selectType = strings.Replace(selectType, ",title", "", -1)
+				}
+
+			}
+			queryItem = strings.ReplaceAll(selectType, ",", "\",\"")
+		}
+		multi_match_new := fmt.Sprintf(multi_match, "%s", "\""+queryItem+"\"")
+
+		if scd.Comeinfrom == "supersearchPage" {
+			var keywordArr []string
+			if strings.Contains(scd.Keyword[0].Keyword, "+") {
+				//keywordArr = strings.Split(scd.Keyword[0].Keyword, "+")
+			} else if strings.Contains(scd.Keyword[0].Keyword, " ") {
+				//keywordArr = strings.Split(scd.Keyword[0].Keyword, " ")
+			}
+			if len(keywordArr) > 1 {
+				KeyWordSearch := KeyWord{}
+				for _, v := range keywordArr {
+					KeyWordSearch.Appended = append(KeyWordSearch.Appended, v)
+				}
+				scd.Keyword = []KeyWord{KeyWordSearch}
+			}
+		}
+		for _, v := range scd.Keyword {
+			shoulds := []string{}
+			must_not := []string{}
+			if v.Keyword != "" {
+				if strings.Contains(v.Keyword, "+") {
+					for _, vk := range strings.Split(v.Keyword, "+") {
+						//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+						if scd.Comeinfrom == "supersearchPage" && DetailFileORTitle(selectType) && len([]rune(elastic.ReplaceYH(vk))) == 1 {
+							queryItem = strings.ReplaceAll(selectType+",title", ",", "\",\"")
+							shouldsKey := fmt.Sprintf(multi_match, "\""+vk+"\"", "\""+queryItem+"\"")
+							shoulds = append(shoulds, shouldsKey)
+						} else {
+							shoulds = append(shoulds, fmt.Sprintf(multi_match_new, "\""+vk+"\""))
+						}
+					}
+				} else if strings.Contains(v.Keyword, " ") {
+					for _, vk := range strings.Split(v.Keyword, " ") {
+						//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+						if scd.Comeinfrom == "supersearchPage" && DetailFileORTitle(selectType) && len([]rune(elastic.ReplaceYH(vk))) == 1 {
+							queryItem = strings.ReplaceAll(selectType+",title", ",", "\",\"")
+							shouldsKey := fmt.Sprintf(multi_match, "\""+vk+"\"", "\""+queryItem+"\"")
+							shoulds = append(shoulds, shouldsKey)
+						} else {
+							shoulds = append(shoulds, fmt.Sprintf(multi_match_new, "\""+vk+"\""))
+						}
+					}
+				} else {
+					//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+					if scd.Comeinfrom == "supersearchPage" && DetailFileORTitle(selectType) && len([]rune(elastic.ReplaceYH(v.Keyword))) == 1 {
+						queryItem = strings.ReplaceAll(selectType+",title", ",", "\",\"")
+						shouldsKey := fmt.Sprintf(multi_match, "\""+v.Keyword+"\"", "\""+queryItem+"\"")
+						shoulds = append(shoulds, shouldsKey)
+					} else {
+						shoulds = append(shoulds, fmt.Sprintf(multi_match_new, "\""+v.Keyword+"\""))
+					}
+				}
+			}
+
+			//附加词
+			for _, vv := range v.Appended {
+				shoulds = append(shoulds, fmt.Sprintf(multi_match_new, "\""+vv+"\""))
+			}
+
+			//排除词
+			for _, vv := range v.Exclude {
+				//单个字 搜索范围 有全文或者附件 无标题 例如:学 虚拟机 detail  搜索的时候加上标题
+				if scd.Comeinfrom == "supersearchPage" && DetailFileORTitle(selectType) && len([]rune(elastic.ReplaceYH(vv))) == 1 {
+					queryItem = strings.ReplaceAll(selectType+",title", ",", "\",\"")
+					shouldsKey := fmt.Sprintf(multi_match, "\""+vv+"\"", "\""+queryItem+"\"")
+					shoulds = append(shoulds, shouldsKey)
+				} else {
+					must_not = append(must_not, fmt.Sprintf(multi_match_new, "\""+vv+"\""))
+				}
+			}
+
+			//添加
+			if len(shoulds) > 0 {
+				notStr := ""
+				if len(must_not) > 0 {
+					notStr = fmt.Sprintf(`,"must_not":[%s]`, strings.Join(must_not, ","))
+				}
+				bools = append(bools, fmt.Sprintf(query_bool_must_and, strings.Join(shoulds, ","), notStr))
+			}
+		}
+	}
+	if scd.HasBuyerTel != "" {
+		if scd.HasBuyerTel == "y" {
+			must_not = append(must_not, fmt.Sprintf(query_missing, "buyertel"))
+		} else {
+			musts = append(musts, fmt.Sprintf(query_missing, "buyertel"))
+		}
+	}
+	if scd.HasWinnerTel != "" {
+		if scd.HasWinnerTel == "y" {
+			must_not = append(must_not, fmt.Sprintf(query_missing, "winnertel"))
+		} else {
+			musts = append(musts, fmt.Sprintf(query_missing, "winnertel"))
+		}
+	}
+
+	//搜索范围是否只有附件
+	//搜索范围只选择附件,是否有附件条件无效;
+	var isFileSearch = strings.ReplaceAll(selectType, ",", "\",\"") == "filetext"
+	if !isFileSearch && scd.FileExists != "" {
+		if scd.FileExists == "1" { //有附件
+			must_not = append(must_not, fmt.Sprintf(query_missing, "isValidFile"))
+			musts = append(musts, fmt.Sprintf(query_bool_must_term, 1))
+		} else if scd.FileExists == "-1" { //无附件
+			musts = append(musts, fmt.Sprintf(query_missing, "isValidFile"))
+		}
+	}
+	// 如果是领域化数据则需要加标签
+	if scd.BidField != "" {
+
+		musts = append(musts, fmt.Sprintf(query_bool_must_term_bidField, scd.BidField))
+
+	}
+	qstr := fmt.Sprintf(query, strings.Join(musts, ","), strings.Join(must_not, ","), strings.Join(bools, ","), boolsNum)
+	return qstr
+}
+
+// 获取数据导出筛选条件
+func GetSqlObjFromId(mongo mg.MongodbSim, _id string) *SieveCondition {
+	var (
+		query *map[string]interface{}
+		ok    bool
+	)
+	if query, ok = mongo.FindById(ExportTable, _id, nil); !ok {
+		return nil
+	}
+	searchTypeSwitch, _ := (*query)["searchTypeSwitch"].(bool)
+	return &SieveCondition{
+		Id:               _id,
+		Keyword:          getKeyWordArrFromDbResult((*query)["keywords"]),
+		Industry:         getStringArrFromDbResult((*query)["industry"]),
+		MinPrice:         qutil.ObjToString((*query)["minprice"]),
+		MaxPrice:         qutil.ObjToString((*query)["maxprice"]),
+		Subtype:          qutil.ObjToString((*query)["subtype"]),
+		Area:             getStringArrFromDbResult((*query)["area"]),
+		City:             getStringArrFromDbResult((*query)["city"]),
+		SelectType:       qutil.ObjToString((*query)["selectType"]),
+		PublishTime:      qutil.ObjToString((*query)["publishtime"]),
+		Buyer:            getStringArrFromDbResult((*query)["buyer"]),
+		Buyerclass:       getStringArrFromDbResult((*query)["buyerclass"]),
+		HasBuyerTel:      qutil.ObjToString((*query)["hasBuyertel"]),
+		Winner:           getStringArrFromDbResult((*query)["winner"]),
+		HasWinnerTel:     qutil.ObjToString((*query)["hasWinnertel"]),
+		ComeInTime:       qutil.Int64All((*query)["comeintime"]),
+		Comeinfrom:       qutil.ObjToString((*query)["comeinfrom"]),
+		SelectIds:        getStringArrFromDbResult((*query)["selectIds"]),
+		PushKeyWords:     getStringArrFromDbResult((*query)["pushKeyWords"]),
+		FileExists:       qutil.ObjToString((*query)["fileExists"]),
+		SearchTypeSwitch: searchTypeSwitch,
+		BidField:         qutil.ObjToString((*query)["bid_field"]), // 领域化数据
+	}
+}
+
+// 数据导出-查询结果数量
+func GetDataExportSearchCountByScdId(sim, bid mg.MongodbSim, biddingName, elasticAddress, id string) (count int) {
+	scd := GetSqlObjFromId(sim, id) //用户筛选条件
+	if scd.SelectIds != nil {
+		//部分数据可能已删除、不存在;此处需要统计返回实际数量
+		//return len(scd.SelectIds)
+		return int(GetDataExportSelectReallyCount(bid, biddingName, scd.SelectIds))
+	}
+	return GetDataExportSearchCountBySieveCondition(scd, elasticAddress)
+}
+
+func GetDataExportSearchCountBySieveCondition(scd *SieveCondition, elasticAddress string) (count int) {
+	qstr := getDataExportSql(scd)
+	if isNullSearch(scd) {
+		return -1 //程序端返回最大值
+	}
+	log.Printf("GetDataExportSearchCountUseId-%s-sql:%s\n", scd.Id, qstr)
+	count = int(elastic.Count(INDEX, TYPE, qstr))
+	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) {
+		if len(scd.Keyword) != 0 {
+			searchTextSize := 0
+			if len(scd.Keyword) > 0 {
+				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+			}
+			if searchTextSize > 3 && count < 50 {
+				if strings.Index(scd.SelectType, "title") > -1 {
+					var res *[]map[string]interface{}
+					if count > 0 {
+						res = doSearch(qstr, 0, count, "")
+					}
+					if secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", elasticAddress); secondKWS != "" {
+						scd.Keyword[0].Keyword = secondKWS
+						scd.SelectType = "title"
+						qstr = getDataExportSql(scd)
+						res2 := doSearch(qstr, 0, 100, "")
+						result := len(*delRepeatMapArr(res, res2))
+						if result > 100 {
+							result = 100
+						}
+						log.Printf("GetDataExportSearchCountUseId-%s-count:%d-分词-sql:%s\n", scd.Id, result, qstr)
+						return result
+					}
+				}
+			}
+			return
+		}
+	}
+	log.Printf("GetDataExportSearchCountUseId-%s-count:%d\n", scd.Id, count)
+	return
+}
+
+// 合并map数据,去重
+func delRepeatMapArr(res *[]map[string]interface{}, res2 *[]map[string]interface{}) *[]map[string]interface{} {
+	if res != nil {
+		for _, v := range *res {
+			for n, m := range *res2 {
+				if qutil.ObjToString(v["_id"]) == qutil.ObjToString(m["_id"]) {
+					*res2 = append((*res2)[0:n], (*res2)[n+1:]...)
+					break
+				}
+			}
+		}
+		*res = append(*res, *res2...)
+	} else {
+		res = res2
+	}
+	return res
+}
+
+// 查询条件是否为空
+func isNullSearch(scd *SieveCondition) (isNull bool) {
+	if scd.PublishTime == "" && len(scd.Area) == 0 && len(scd.Industry) == 0 && len(scd.Keyword) == 0 && len(scd.Buyer) == 0 && len(scd.Winner) == 0 && scd.MinPrice == "" && scd.MaxPrice == "" && scd.Subtype == "" && len(scd.City) == 0 {
+		isNull = true
+	}
+	return isNull
+}
+
+/*
+ * 数据导出 查询结果
+ * _id 数据库查询条件记录id
+ * dataType 1-普通字段 2-高级字段
+ * webdomain 三级页域名
+ * count 返回数量 (-1:预览数据查询)
+ */
+
+func GetDataExportSearchResultByScdId(sim, bid mg.MongodbSim, bidMgoDBName, elasticAddress, id, dataType string, checkCount int) (*[]map[string]interface{}, error) {
+	scd := GetSqlObjFromId(sim, id)
+	list, err := GetDataExportSearchResult(bid, bidMgoDBName, elasticAddress, scd, dataType, checkCount)
+	if list == nil || err != nil {
+		return nil, err
+	}
+	return list, nil
+}
+
+// GetDataExportIdArrByScdId 数据包去重获取导出信息id
+func GetDataExportIdArrByScdId(sim mg.MongodbSim, elasticAddress, id string, checkCount int) ([]string, error) {
+	scd := GetSqlObjFromId(sim, id)
+	return GetDataExportIds(elasticAddress, scd, checkCount)
+}
+
+// 收藏导出
+var contentfilterReg = regexp.MustCompile("<[^>]+>")
+
+// GetDataExportSelectReallyCount 查询实际可调导出数量
+func GetDataExportSelectReallyCount(bid mg.MongodbSim, biddingName string, ids []string) int64 {
+	sess := bid.GetMgoConn()
+	defer bid.DestoryMongoConn(sess)
+	if ids == nil || len(ids) == 0 {
+		return 0
+	}
+	var queryIds []interface{}
+	for _, idStr := range ids {
+		queryIds = append(queryIds, mg.StringTOBsonId(idStr))
+	}
+	lenNum := int64(len(ids))
+	num1, err1 := sess.DB(biddingName).C("bidding").Find(map[string]interface{}{"_id": map[string]interface{}{
+		"$in": queryIds,
+	}}).Count()
+	if err1 == nil {
+		if num1 == lenNum {
+			return lenNum
+		}
+		num2, err2 := sess.DB(biddingName).C("bidding_back").Find(map[string]interface{}{"_id": map[string]interface{}{
+			"$in": queryIds,
+		}}).Count()
+		if err2 == nil {
+			if num2+num1 == lenNum {
+				return lenNum
+			} else if num1+num2 > lenNum {
+				return lenNum
+			} else if num1+num2 < lenNum {
+				return num1 + num2
+			}
+		}
+	}
+	return -2
+}
+
+func GetDataExportSelectResult(bidding mg.MongodbSim, biddingName string, scd *SieveCondition, dataType string, checkCount int) (*[]map[string]interface{}, error) {
+	sess := bidding.GetMgoConn()
+	defer bidding.DestoryMongoConn(sess)
+	var queryIds []interface{}
+	for _, idStr := range scd.SelectIds {
+		queryIds = append(queryIds, mg.StringTOBsonId(idStr))
+	}
+
+	selectMap := map[string]interface{}{
+		"_id": 1, "title": 1, "detail": 1, "area": 1, "city": 1, "publishtime": 1, "projectname": 1, "buyer": 1, "s_winner": 1, "bidamount": 1, "subtype": 1, "toptype": 1, "filetext": 1, "purchasing": 1,
+	}
+	if dataType == "2" {
+		for _, key := range []string{"href", "projectcode", "buyerperson", "buyertel", "budget", "bidopentime", "agency", "projectscope", "winnerperson", "winnertel", "bidendtime"} {
+			selectMap[key] = 1
+		}
+	}
+	returnLsit := make([]map[string]interface{}, 0, len(queryIds))
+	iter := sess.DB(biddingName).C("bidding").Select(selectMap).Find(map[string]interface{}{"_id": map[string]interface{}{
+		"$in": queryIds,
+	}}).Iter()
+	for m := make(map[string]interface{}); iter.Next(&m); {
+		m["_id"] = mg.BsonIdToSId(m["_id"])
+		detail, _ := m["detail"].(string)
+		if detail != "" {
+			m["detail"] = contentfilterReg.ReplaceAllString(detail, "")
+		}
+		returnLsit = append(returnLsit, m)
+		m = make(map[string]interface{})
+	}
+	if len(returnLsit) == checkCount {
+		return &returnLsit, nil
+	}
+	iter_back := sess.DB(biddingName).C("bidding_back").Select(selectMap).Find(map[string]interface{}{"_id": map[string]interface{}{
+		"$in": queryIds,
+	}}).Iter()
+	for m := make(map[string]interface{}); iter_back.Next(&m); {
+		m["_id"] = mg.BsonIdToSId(m["_id"])
+		detail, _ := m["detail"].(string)
+		if detail != "" {
+			m["detail"] = contentfilterReg.ReplaceAllString(detail, "")
+		}
+		returnLsit = append(returnLsit, m)
+		m = make(map[string]interface{})
+	}
+	if len(returnLsit) == checkCount || checkCount == -1 {
+		return &returnLsit, nil
+	}
+	return nil, fmt.Errorf("选择数据导出异常 数据量期望%d条,实际查询%d条", checkCount, len(returnLsit))
+}
+
+func GetDataExportIds(elasticAddress string, scd *SieveCondition, checkCount int) ([]string, error) {
+	defer qutil.Catch()
+	if scd == nil {
+		return nil, errors.New("GetDataExportIds-未获取到查询信息")
+	}
+	if scd.SelectIds != nil {
+		return scd.SelectIds, nil
+	}
+	//获取查询语句
+	qstr := getDataExportSql(scd)
+	log.Printf("GetDataExportIds-%s-sql:%s\n", scd.Id, qstr)
+
+	//数据导出数据查询
+	res := doSearchByBatch(qstr, "1", checkCount, fmt.Sprintf("%s-%s", "GetDataExportSearchResult", scd.Id))
+	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) && len(scd.SelectIds) == 0 {
+		if len(scd.Keyword) != 0 {
+			num := len(res)
+			searchTextSize := 0
+			if len(scd.Keyword) > 0 {
+				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+			}
+			if searchTextSize > 3 && num < 50 {
+				if strings.Index(scd.SelectType, "title") > -1 {
+					if secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", elasticAddress); secondKWS != "" {
+						scd.Keyword[0].Keyword = secondKWS
+						scd.SelectType = "title"
+						qstr = getDataExportSql(scd)
+						log.Printf("GetDataExportIds-%s-分词查询-sql:%s\n", scd.Id, qstr)
+						res2 := doSearch(qstr, 0, 100, "")
+						res = *delRepeatMapArr(&res, res2)
+						if len(res) > 100 {
+							res = res[:100]
+						}
+					}
+				}
+			}
+		}
+	}
+	//获取信息id
+	idArr := make([]string, 0, 0)
+	for _, v := range res {
+		if id := qutil.ObjToString(v["_id"]); id != "" {
+			idArr = append(idArr, id)
+		}
+	}
+	if checkCount != len(idArr) {
+		return nil, fmt.Errorf("GetDataExportIds-%s-数据总量校验异常,期望:%d,实际:%d", scd.Id, checkCount, len(res))
+	}
+	return idArr, nil
+}
+
+// GetDataExportSearchResult 获取数据导出内容
+// entmg 高级字段包查询企业电话邮箱等字段
+// checkCount -1 预览500条
+func GetDataExportSearchResult(bid mg.MongodbSim, bidMgoDBName, elasticAddress string, scd *SieveCondition, dataType string, checkCount int) (*[]map[string]interface{}, error) {
+	defer qutil.Catch()
+	if scd == nil {
+		return nil, errors.New("GetDataExportSearchResult-未获取到查询信息")
+	}
+	if scd.SelectIds != nil {
+		idSelectDates, idSelectErr := GetDataExportSelectResult(bid, bidMgoDBName, scd, dataType, checkCount)
+		if idSelectErr != nil {
+			return nil, idSelectErr
+		}
+		GetDataExportMatchKey(scd, idSelectDates)
+		return idSelectDates, idSelectErr
+	}
+	selectType := scd.SelectType
+	//获取查询语句
+	qstr := getDataExportSql(scd)
+	log.Printf("GetDataExportSearchResult-%s-sql:%s\n", scd.Id, qstr)
+
+	//数据导出数据查询
+	res := doSearchByBatch(qstr, dataType, checkCount, fmt.Sprintf("%s-%s", "GetDataExportSearchResult", scd.Id))
+	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) && len(scd.SelectIds) == 0 {
+		if len(scd.Keyword) != 0 {
+			num := len(res)
+			searchTextSize := 0
+			if len(scd.Keyword) > 0 {
+				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+			}
+			if searchTextSize > 3 && num < 50 {
+				if strings.Index(scd.SelectType, "title") > -1 {
+					if secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", elasticAddress); secondKWS != "" {
+						scd.Keyword[0].Keyword = secondKWS
+						scd.SelectType = "title"
+						qstr = getDataExportSql(scd)
+						log.Printf("GetDataExportSearchResult-%s-分词查询-sql:%s\n", scd.Id, qstr)
+						res2 := doSearch(qstr, 0, 100, "")
+						res = *delRepeatMapArr(&res, res2)
+						if len(res) > 100 {
+							res = res[:100]
+						}
+					}
+				}
+			}
+		}
+	}
+	//校验数量
+	if checkCount != len(res) && checkCount != -1 {
+		return nil, fmt.Errorf("GetDataExportSearchResult-%s-数据总量校验异常,期望:%d,实际:%d", scd.Id, checkCount, len(res))
+	}
+	if selectType != scd.SelectType {
+		scd.SelectType = selectType
+	}
+	GetDataExportMatchKey(scd, &res)
+	return &res, nil
+}
+
+func doSearchByBatch(query, dataType string, searchCount int, flag string) (res []map[string]interface{}) {
+	if searchCount > onceSearchCount { //分批次查询
+		batchNum := qutil.IntAll(math.Ceil(float64(searchCount) / float64(onceSearchCount)))
+		var searchWaitGroup = &sync.WaitGroup{}
+		var lock sync.Mutex
+		for n := 0; n < batchNum; n++ {
+			searchWaitGroup.Add(1)
+			searchPool <- true
+			go func(start int) {
+				defer func() {
+					searchWaitGroup.Done()
+					<-searchPool
+				}()
+				checkNum, checkOk := onceSearchCount, false
+				if start == (batchNum - 1) {
+					if searchCount%onceSearchCount != 0 {
+						checkNum = searchCount % onceSearchCount
+					}
+				}
+
+				var tmp *[]map[string]interface{}
+				for i := 0; i < 3; i++ {
+					tmp = doSearch(query, start*onceSearchCount, onceSearchCount, dataType)
+					if tmp != nil && (len(*tmp) == checkNum) { //校验数据量是否够
+						checkOk = true
+						break
+					}
+				}
+				if tmp == nil {
+					log.Printf("%s-第%d页数据查询结果为空\n", flag, start+1)
+					return
+				}
+				if checkOk {
+					log.Printf("%s-第%d页数据加载完成,共%d条\n", flag, start+1, len(*tmp))
+				} else {
+					log.Printf("%s-第%d页数据加载异常,共%d条,预期%d条\n", flag, start+1, len(*tmp), checkNum)
+				}
+				lock.Lock()
+				res = append(res, *tmp...)
+				lock.Unlock()
+			}(n)
+		}
+		searchWaitGroup.Wait()
+		log.Printf("%s-分批次加载数据总量为%d\n", flag, len(res))
+	} else {
+		queryCount := qutil.If(searchCount == -1, onceSearchCount, searchCount).(int)
+		searchPool <- true
+		tmp := doSearch(query, 0, queryCount, dataType)
+		<-searchPool
+		if tmp == nil || len(*tmp) == 0 {
+			log.Printf("%s-一次性加载数据异常\n", flag)
+		} else {
+			res = *tmp
+			log.Printf("%s-一次性加载数据总量为%d\n", flag, len(res))
+		}
+	}
+	return
+}
+
+func FormatExportData(entmg mg.MongodbSim, data *[]map[string]interface{}, webdomain string, dataType string, encry ...bool) *[]map[string]interface{} {
+	//格式化输出
+	isEncry := false
+	if len(encry) > 0 {
+		isEncry = true
+	}
+	for _, v := range *data {
+		//有中标企业 且 高级字段查询
+		if dataType == "2" {
+			if qutil.Int64All(v["bidendtime"]) > 0 {
+				v["bidendtime"] = strings.Split(time.Unix(qutil.Int64All(v["bidendtime"]), 0).Format("2006-01-02 15:04:05"), " ")[0]
+			} else {
+				v["bidendtime"] = ""
+			}
+			//查询企业公示 法人 公司电话 公司邮箱地址
+			s_winner, ok := v["s_winner"].(string) //改为entidlistxx?
+			if ok && s_winner != "" {
+				if entData, ok := entmg.Find("winner_enterprise", map[string]interface{}{"company_name": s_winner}, nil, `{"company_name":1,"company_email":1,"legal_person":1,"company_phone":1}`, false, -1, -1); ok {
+					if entData != nil && *entData != nil && len(*entData) > 0 {
+						for _, ev := range *entData {
+							if v["s_winner"] == ev["company_name"] {
+								legal_person := ""
+								if ev["legal_person"] != nil {
+									legal_person = ev["legal_person"].(string)
+									if isEncry {
+										var xx = "*"
+										switch len([]rune(legal_person)) {
+										case 3:
+											xx = "**"
+										case 4:
+											xx = "***"
+										}
+										legal_person = string([]rune(legal_person)[:1]) + xx
+									}
+								}
+								company_phone := ""
+								if ev["company_phone"] != nil {
+									company_phone = ev["company_phone"].(string)
+									if isEncry {
+										if len([]rune(company_phone)) > 7 {
+											company_phone = company_phone[:7] + "****"
+										} else {
+											company_phone = "****"
+										}
+									}
+								}
+								company_email := ""
+								if ev["company_email"] != nil && ev["company_email"] != "无" {
+									company_email = ev["company_email"].(string)
+									if isEncry {
+										if len(strings.Split(company_email, "@")) > 1 {
+											company_email = "******" + "@" + strings.Split(company_email, "@")[1]
+										}
+									}
+								}
+								v["legal_person"] = legal_person
+								v["company_phone"] = company_phone
+								v["company_email"] = company_email
+							}
+						}
+					}
+				}
+			}
+		}
+		//====================字段补漏=========================
+		if v["toptype"] == "结果" && dataType == "2" && !(v["agency"] != nil && v["budget"] != nil && v["buyerperson"] != nil && v["buyertel"] != nil) {
+			r := elastic.Get("projectset", "projectset", fmt.Sprintf(`{"query":{"term":{"list.infoid":"%s"}},"_source": ["list"]}`, v["_id"]))
+			if len(*r) > 0 {
+				MsgList := (*r)[0]["list"]
+				if MsgList != nil {
+					list := qutil.ObjArrToMapArr(MsgList.([]interface{}))
+					for _, vv := range list {
+						if vv["subtype"] == "招标" {
+							if v["agency"] == nil && vv["agency"] != nil {
+								v["agency"] = vv["agency"]
+							}
+							if v["budget"] == nil && vv["budget"] != nil {
+								v["budget"] = vv["budget"]
+							}
+							if v["buyerperson"] == nil && vv["buyerperson"] != nil {
+								v["buyerperson"] = vv["buyerperson"]
+							}
+							if v["buyertel"] == nil && vv["buyertel"] != nil {
+								v["buyertel"] = vv["buyertel"]
+							}
+							break
+						}
+					}
+				}
+			}
+		}
+		if v["area"] == "A" {
+			v["area"] = "全国"
+		}
+		if v["bidamount"] != nil {
+			v["bidamount"] = formatFloat(qutil.Float64All(v["bidamount"]))
+		}
+		if v["budget"] != nil {
+			v["budget"] = formatFloat(qutil.Float64All(v["budget"]))
+		}
+		if v["publishtime"] != nil {
+			date := v["publishtime"]
+			v["publishtime"] = FormatDateWithObj(&date, Date_Short_Layout)
+		}
+		if v["bidopentime"] != nil {
+			date := v["bidopentime"]
+			v["bidopentime"] = FormatDateWithObj(&date, Date_Short_Layout)
+		}
+		if v["_id"] != nil {
+			encodeId := CommonEncodeArticle("content", v["_id"].(string))
+			v["url"] = webdomain + "/article/content/" + encodeId + ".html"
+			v["url_jump"] = webdomain + "/front/reloadTo/article/content/" + encodeId + ".html"
+		}
+		if v["currency"] == "" || v["currency"] == nil {
+			v["currency"] = "人民币"
+		}
+
+		if isEncry {
+			if v["projectscope"] != "" && v["projectscope"] != nil {
+				str := ClearHtml.ReplaceAllString(v["projectscope"].(string), "")
+				str = ClearOther.ReplaceAllString(str, "")
+				str = strings.Replace(str, " ", "", -1)
+				if len([]rune(str)) > 100 {
+					str = qutil.SubString(str, 0, 100) + "..."
+				}
+				v["projectscope"] = str
+			}
+			if v["detail"] != "" && v["detail"] != nil {
+				str := ClearHtml.ReplaceAllString(v["detail"].(string), "")
+				str = ClearOther.ReplaceAllString(str, "")
+				str = strings.Replace(str, " ", "", -1)
+				if len([]rune(str)) > 100 {
+					str = qutil.SubString(str, 0, 100) + "..."
+				}
+				v["detail"] = str
+			}
+			if v["title"] != "" && v["title"] != nil {
+				str := ClearHtml.ReplaceAllString(v["title"].(string), "")
+				str = ClearOther.ReplaceAllString(str, "")
+				str = strings.Replace(str, " ", "", -1)
+				if len([]rune(str)) > 100 {
+					str = qutil.SubString(str, 0, 100) + "..."
+				}
+				v["title"] = str
+			}
+
+		}
+
+		if v["subtype"] == nil && v["toptype"] != nil {
+			v["subtype"] = v["toptype"]
+		}
+	}
+	return data
+}
+
+// 保留到0.01分
+func formatFloat(value float64) string {
+	str := strings.TrimRight(fmt.Sprintf("%.7f", value*10000/100000000), "0")
+	if str[len(str)-1:] == "." {
+		return str[:len(str)-1]
+	}
+	return str
+}
+
+func doSearch(sql string, start, count int, dataType string) *[]map[string]interface{} {
+	if sql != "" {
+		//筛选字段
+		if dataType != "" {
+			dataexport_field := `"_id","title","detail","area","city","publishtime","projectname","buyer","s_winner","bidamount","subtype","toptype","filetext","purchasing"`
+			if dataType == "2" {
+				dataexport_field += `,"href","projectcode","buyerperson","buyertel","budget","bidopentime","agency","projectscope","winnerperson","winnertel","bidendtime"`
+			}
+			sql = sql[:len(sql)-1] + `,"_source":[` + dataexport_field + "]}"
+		}
+		//分页排序
+		sql = sql[:len(sql)-1] + `,"sort": {"dataweight": "desc","publishtime":"desc","id":"desc"},"from":` + strconv.Itoa(start) + `,"size":` + strconv.Itoa(count) + "}"
+	}
+	log.Println("doSearch", sql)
+	return elastic.Get(INDEX, TYPE, sql)
+}
+
+func getKeyWordArrFromDbResult(k interface{}) (arr []KeyWord) {
+	if k == nil {
+		return
+	}
+	kArr := k.([]interface{})
+	for _, v := range kArr {
+		kw := KeyWord{}
+		b, e := json.Marshal(v)
+		if e != nil {
+			log.Println(e.Error())
+		}
+		json.Unmarshal(b, &kw)
+		arr = append(arr, kw)
+	}
+	return
+}
+
+func getStringArrFromDbResult(c interface{}) (arr []string) {
+	if c != nil {
+		cArr := c.([]interface{})
+		arr = qutil.ObjArrToStringArr(cArr)
+	}
+	return
+}
+
+// 获取结果,空字段最少的数据
+func ScreenData(arr *[]map[string]interface{}, dataType string, resultNum int, kws []KeyWord) (res []map[string]interface{}) {
+	AllMap := map[int][]map[string]interface{}{}
+
+	NoKwsMap := map[int][]map[string]interface{}{}
+	lastNum := resultNum
+	for _, v := range *arr {
+		emptyNum := countOfTheEmpty(v, dataType)
+		if emptyNum == -1 {
+			continue
+		}
+		if len(kws) > 0 && kws[0].Keyword != "" {
+			var kwsFlag = true
+			for _, vk := range kws {
+				if strings.Contains(qutil.ObjToString(v["title"]), strings.Replace(vk.Keyword, "+", "", -1)) {
+					kwsFlag = false
+					continue
+				}
+			}
+			if kwsFlag {
+				if NoKwsMap[emptyNum] == nil {
+					NoKwsMap[emptyNum] = []map[string]interface{}{v}
+				} else {
+					NoKwsMap[emptyNum] = append(NoKwsMap[emptyNum], v)
+				}
+				continue
+			}
+		}
+		if AllMap[emptyNum] == nil {
+			AllMap[emptyNum] = []map[string]interface{}{v}
+			continue
+		}
+		AllMap[emptyNum] = append(AllMap[emptyNum], v)
+	}
+	//获取key
+	keys := []int{}
+	for k, _ := range AllMap {
+		keys = append(keys, k)
+	}
+	sort.Ints(keys)
+	//选取结果
+	for _, v := range keys {
+		if len(AllMap[v]) >= resultNum {
+			return append(res, AllMap[v][:resultNum]...)
+		} else {
+			resultNum = resultNum - len(AllMap[v])
+			tmp := append(res, AllMap[v][:len(AllMap[v])]...)
+			res = tmp
+		}
+	}
+	if len(res) < lastNum {
+		resultNum = lastNum - len(res)
+		//获取key
+		Nokeys := []int{}
+		for k, _ := range NoKwsMap {
+			Nokeys = append(Nokeys, k)
+		}
+		sort.Ints(Nokeys)
+		log.Println("没关键词的空字段数量", Nokeys)
+		//选取结果
+		for _, v := range Nokeys {
+			if len(NoKwsMap[v]) >= resultNum {
+				return append(res, NoKwsMap[v][:resultNum]...)
+			} else {
+				resultNum = resultNum - len(NoKwsMap[v])
+				tmp := append(res, NoKwsMap[v][:len(NoKwsMap[v])]...)
+				res = tmp
+			}
+		}
+	}
+	return res
+}
+func countOfTheEmpty(m map[string]interface{}, dataType string) int {
+	MsgType := m["subtype"]
+	//	if MsgType == "拟建" {
+	//		return -1
+	//	}
+	//计算空字段数量
+	var count int = 0
+	//高级字段包
+	if dataType == "2" {
+		if m["href"] == "" || m["href"] == nil {
+			count++
+		}
+		if m["projectcode"] == "" || m["projectcode"] == nil {
+			count++
+		}
+		if m["buyerperson"] == "" || m["buyerperson"] == nil {
+			count++
+		}
+		if m["buyertel"] == "" || m["buyertel"] == nil {
+			count++
+		}
+		if m["budget"] == "" || m["budget"] == nil {
+			count++
+		}
+		if m["bidopentime"] == "" || m["bidopentime"] == nil {
+			count++
+		}
+		if m["agency"] == "" || m["agency"] == nil {
+			count++
+		}
+		if m["projectscope"] == "" || m["projectscope"] == nil {
+			count++
+		}
+	}
+	if m["city"] == "" || m["city"] == nil {
+		count++
+	}
+	if m["publishtime"] == "" || m["publishtime"] == nil {
+		count++
+	}
+	if m["projectname"] == "" || m["projectname"] == nil {
+		count++
+	}
+	if m["buyer"] == "" || m["buyer"] == nil {
+		count++
+	}
+	if m["s_winner"] == "" || m["s_winner"] == nil {
+		if MsgType != "招标" {
+			count++
+		}
+	}
+	if m["bidamount"] == "" || m["bidamount"] == nil {
+		if MsgType != "招标" {
+			count++
+		}
+	}
+	if m["subtype"] == "" || m["subtype"] == nil {
+		count++
+	}
+	return count
+}

+ 425 - 0
common/src/qfw/util/dataexport/entdataexport.go

@@ -0,0 +1,425 @@
+package dataexport
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	"app.yhyue.com/moapp/jybase/mongodb"
+	mg "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+
+	"github.com/tealeg/xlsx"
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+// 作者:一组开发
+type Filters struct {
+	FilterId string
+}
+
+func GetEntDataExportCount(sim, bid mg.MongodbSim, bidMgoDBName, elasticAddress, _id string, entId, entUserId int, isFirst bool, url string, maxCount int) (count, newCount int, data *[]map[string]interface{}) {
+	defer util.Catch()
+	var (
+		searchsWaitGroup = &sync.WaitGroup{}
+	)
+	count = GetDataExportSearchCountByScdId(sim, bid, bidMgoDBName, elasticAddress, _id)
+	if count > maxCount || count == -1 {
+		count = maxCount
+	}
+	log.Println("count", count)
+	dataType := "2"
+	//数据导出数据查询
+	res, err := GetDataExportSearchResultByScdId(sim, bid, bidMgoDBName, elasticAddress, _id, dataType, count)
+	if err != nil {
+		log.Println("企业数据导出错误 ", err)
+		return 0, 0, nil
+	}
+	//  20210716 由原来的redis判重改为调用判重中台接口进行判重
+	m := map[string]bool{}
+	infoIdList := []string{}
+	insertFlag := "false"
+	if !isFirst {
+		insertFlag = "true"
+	}
+	for _, v := range *res {
+		if util.Int64All(v["bidendtime"]) > 0 {
+			v["bidendtime"] = strings.Split(time.Unix(util.Int64All(v["bidendtime"]), 0).Format("2006-01-02 15:04:05"), " ")[0]
+		} else {
+			v["bidendtime"] = ""
+		}
+		id := util.ObjToString(v["_id"])
+		if m[id] {
+			continue
+		}
+		m[id] = true
+		//  20210716  redis判重调整为调用判重中台接口  每一千个调用一次
+		infoIdList = append(infoIdList, id)
+		if len(infoIdList) > 1000 {
+			//	 调接口
+			rs, err5 := Post(url, map[string]string{
+				"personId": "0", // 没有使用这个参数
+				"infoId":   strings.Join(infoIdList, ","),
+				"entId":    fmt.Sprintf("%d", entId),
+				"isInsert": insertFlag,
+				"isEnt":    "true",
+			})
+			log.Println("响应结果:", rs)
+			if err5 != nil || util.IntAll(rs["code"]) != 0 {
+				log.Println("企业订阅数据导出接口判重失败", err5)
+				log.Println("企业订阅数据导出接口判重失败rs:", rs)
+				log.Println("企业订阅数据导出接口判重失败rs[code]:", rs["code"])
+				log.Println("企业订阅数据导出接口判重失败code是否为0", util.IntAll(rs["code"]) != 0)
+				log.Println("企业订阅数据导出接口判重失败", err5, "rs:", rs, " rs[code]:", rs["code"], " ", util.IntAll(rs["code"]), "code是否为0", util.IntAll(rs["code"]) != 0)
+			} else {
+				log.Println("企业订阅数据导出")
+				// 置空
+				infoIdList = []string{}
+				// 本次数据累计
+				returnData := rs["data"].(map[string]interface{})
+				log.Println(newCount, "加之前")
+				newCount += util.IntAll(returnData["newCount"])
+				//newCount += int(returnData["newCount"].(float64))
+				log.Println(newCount, "加之后")
+			}
+
+		}
+		if !isFirst {
+			delete(v, "_id")
+			v["entid"] = entId
+			v["userid"] = entUserId
+			v["infoid"] = id
+			v["createtime"] = time.Now().Unix()
+		}
+	}
+	if len(infoIdList) > 0 {
+		rs, err5 := Post(url, map[string]string{
+			"personId": "0", // 没有使用这个参数
+			"infoId":   strings.Join(infoIdList, ","),
+			"entId":    fmt.Sprintf("%d", entId),
+			"isInsert": insertFlag,
+			"isEnt":    "true",
+		})
+		log.Println(rs)
+		if err5 != nil || util.IntAll(rs["code"]) != 0 {
+			log.Println("企业订阅数据导出接口判重失败", err5)
+			log.Println("企业订阅数据导出接口判重失败rs:", rs)
+			log.Println("企业订阅数据导出接口判重失败rs[code]:", rs["code"])
+			log.Println("企业订阅数据导出接口判重失败code是否为0", util.IntAll(rs["code"]) != 0)
+			log.Println("企业订阅数据导出接口判重失败", err5, "rs:", rs, " rs[code]:", rs["code"], " ", util.IntAll(rs["code"]), "code是否为0", util.IntAll(rs["code"]) != 0)
+		} else {
+			log.Println("企业订阅数据导出")
+			// 置空
+			infoIdList = []string{}
+			// 本次数据累计
+			returnData := rs["data"].(map[string]interface{})
+			log.Println(newCount, "加之前")
+			newCount += util.IntAll(returnData["newCount"])
+			//newCount += int(returnData["newCount"].(float64))
+			log.Println(newCount, "加之后")
+
+		}
+	}
+	searchsWaitGroup.Wait()
+	log.Println("企业数据导出--数据遍历完成")
+	//newCount = len(newCountPool)
+	log.Println("new", newCount)
+	data = res
+	return
+}
+
+func FormatExportDatas(Mgo_Ent mongodb.MongodbSim, data *[]map[string]interface{}, webdomain string, dataType string, entId int) *[]map[string]interface{} {
+	//格式化输出
+	var (
+		entexportPool      = make(chan bool, 20)
+		entexportWaitGroup = &sync.WaitGroup{}
+	)
+	log.Println("补充信息开始")
+	for _, v := range *data {
+		entexportWaitGroup.Add(1)
+		entexportPool <- true
+		go func(v map[string]interface{}) {
+			defer func() {
+				entexportWaitGroup.Done()
+				<-entexportPool
+			}()
+			//有中标企业 且 高级字段查询
+			if dataType == "2" {
+				//查询企业公示 法人 公司电话 公司邮箱地址
+				s_winner := strings.Split(util.ObjToString(v["s_winner"]), ",")[0]
+				if entData, ok := Mgo_Ent.Find("winner_enterprise", bson.M{"company_name": s_winner}, nil,
+					`{"company_name":1,"company_email":1,"legal_person":1,"company_phone":1}`, false, -1, -1); ok {
+					if entData != nil && *entData != nil && len(*entData) > 0 {
+						for _, ev := range *entData {
+							if v["s_winner"] == ev["company_name"] {
+								legal_person := ""
+								if ev["legal_person"] != nil {
+									legal_person = ev["legal_person"].(string)
+								}
+								company_phone := ""
+								if ev["company_phone"] != nil {
+									company_phone = ev["company_phone"].(string)
+								}
+								company_email := ""
+								if ev["company_email"] != nil && ev["company_email"] != "无" {
+									company_email = ev["company_email"].(string)
+								}
+								v["legal_person"] = legal_person
+								v["company_phone"] = company_phone
+								v["company_email"] = company_email
+							}
+						}
+					}
+				}
+			}
+			//====================字段补漏=========================
+			if v["toptype"] == "结果" && dataType == "2" && !(v["agency"] != nil && v["budget"] != nil && v["buyerperson"] != nil && v["buyertel"] != nil) {
+				r := elastic.Get("projectset", "projectset", fmt.Sprintf(`{"query":{"term":{"list.infoid":"%s"}},"_source": ["list"]}`, v["_id"]))
+				if len(*r) > 0 {
+					MsgList := (*r)[0]["list"]
+					if MsgList != nil {
+						list := util.ObjArrToMapArr(MsgList.([]interface{}))
+						for _, vv := range list {
+							if vv["subtype"] == "招标" {
+								if v["agency"] == nil && vv["agency"] != nil {
+									v["agency"] = vv["agency"]
+								}
+								if v["budget"] == nil && vv["budget"] != nil {
+									v["budget"] = vv["budget"]
+								}
+								if v["buyerperson"] == nil && vv["buyerperson"] != nil {
+									v["buyerperson"] = vv["buyerperson"]
+								}
+								if v["buyertel"] == nil && vv["buyertel"] != nil {
+									v["buyertel"] = vv["buyertel"]
+								}
+								break
+							}
+						}
+					}
+				}
+			}
+			if v["area"] == "A" {
+				v["area"] = "全国"
+			}
+			if v["publishtime"] != nil {
+				date := v["publishtime"]
+				v["publishtime"] = FormatDateWithObj(&date, Date_Short_Layout)
+			}
+			if v["bidopentime"] != nil {
+				date := v["bidopentime"]
+				v["bidopentime"] = FormatDateWithObj(&date, Date_Short_Layout)
+			}
+			if v["currency"] == "" || v["currency"] == nil {
+				v["currency"] = "人民币"
+			}
+			if v["subtype"] == nil && v["toptype"] != nil {
+				v["subtype"] = v["toptype"]
+			}
+			if v["detail"] != "" && v["detail"] != nil {
+				str := ClearHtml.ReplaceAllString(v["detail"].(string), "")
+				str = ClearOther.ReplaceAllString(str, "")
+				str = strings.Replace(str, " ", "", -1)
+				v["detail"] = str
+			}
+			if v["infoid"] != nil {
+				v["url"] = webdomain + "/article/content/" + CommonEncodeArticle("content", v["infoid"].(string)) + ".html"
+			}
+		}(v)
+	}
+	entexportWaitGroup.Wait()
+	log.Println("补充信息结束")
+	return data
+}
+
+func Post(url string, form map[string]string) (data map[string]interface{}, err error) {
+	str := ""
+	for k, v := range form {
+		str += "&" + k + "=" + v
+	}
+	//log.Println(str)
+	res, err1 := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(str))
+	log.Println(res)
+	if err1 != nil {
+		log.Println("post err:", err1.Error())
+		return nil, err1
+
+	} else if res.Body != nil {
+		defer res.Body.Close()
+		bs, _ := ioutil.ReadAll(res.Body)
+		err2 := json.Unmarshal(bs, &data)
+		if err2 != nil {
+			return nil, err2
+		}
+
+	}
+	return data, nil
+}
+
+// 生成xlsx
+func GetXlsx(mMap []map[string]interface{}, entId, entUserId int, filePath string) string {
+	xf, err := xlsx.OpenFile("./web/staticres/fields.xlsx")
+	if err != nil {
+		log.Println("fields file not foud", err.Error())
+	}
+	sh := xf.Sheets[1]
+	for _, v := range mMap {
+		row := sh.AddRow()
+		row.AddCell().SetValue(v["keyword"])
+		row.AddCell().SetValue(v["area"])
+		row.AddCell().SetValue(v["city"])
+		row.AddCell().SetValue(v["title"])
+		row.AddCell().SetValue(v["subtype"])
+		row.AddCell().SetValue(v["detail"])
+		if v["publishtime"] != nil {
+			row.AddCell().SetValue(v["publishtime"])
+		} else {
+			row.AddCell()
+		}
+		row.AddCell().SetValue(v["href"])
+		row.AddCell().SetValue(v["url"])
+		row.AddCell().SetValue(v["projectname"])
+		row.AddCell().SetValue(v["projectcode"])
+		row.AddCell().SetValue(v["projectscope"])
+		if v["budget"] != nil {
+			row.AddCell().SetFloat(util.Float64All(v["budget"]))
+		} else {
+			row.AddCell()
+		}
+		if v["bidamount"] != nil {
+			row.AddCell().SetFloat(util.Float64All(v["bidamount"]))
+		} else {
+			row.AddCell()
+		}
+		if v["bidopentime"] != nil {
+			row.AddCell().SetValue(v["bidopentime"])
+		} else {
+			row.AddCell()
+		}
+		if v["bidendtime"] != nil {
+			row.AddCell().SetValue(v["bidendtime"])
+		} else {
+			row.AddCell()
+		}
+		row.AddCell().SetValue(v["buyer"])
+		row.AddCell().SetValue(v["buyerperson"])
+		row.AddCell().SetValue(v["buyertel"])
+		row.AddCell().SetValue(v["agency"])
+		row.AddCell().SetValue(v["s_winner"])
+		row.AddCell().SetValue(v["winnerperson"])
+		row.AddCell().SetValue(v["winnertel"])
+		row.AddCell().SetValue(v["legal_person"])
+		row.AddCell().SetValue(v["company_phone"])
+		row.AddCell().SetValue(v["company_email"])
+	}
+	xf.Sheets = xf.Sheets[1:2]
+	xf.Sheets[0].Name = "数据导出"
+	//生文件
+	t := strconv.FormatInt(time.Now().Unix(), 10)
+	entIds := strconv.Itoa(entId)
+	entUserIds := strconv.Itoa(entUserId)
+
+	dir := filePath + "/entsearchexport/" + entIds + "_" + entUserIds + "_" + t + "/"
+	if b, _ := PathExists(dir); !b {
+		err1 := os.MkdirAll(dir, os.ModePerm)
+		if err1 != nil {
+			log.Println("mkdir err", dir)
+		}
+	}
+	fname := entIds + "_" + entUserIds + "_" + "entdataexport.xlsx"
+
+	download_url := "/entsearchexport/" + entIds + "_" + entUserIds + "_" + t + "/" + fname
+	err = xf.Save(dir + fname)
+	if err != nil {
+		log.Println("xls error", fname)
+		return ""
+	}
+	return download_url
+}
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+func SaveExportLog(mysqlSess *mysql.Mysql, entId, entUserId, count, newCount, remain_nums, export_nums int, xlsxUrl, types, filterStr string, exportPhone, exportEmail string) {
+	query := map[string]interface{}{
+		"id":     entUserId,
+		"ent_id": entId,
+	}
+	set := map[string]interface{}{
+		"remain_nums": remain_nums - newCount,
+		"export_nums": export_nums + newCount,
+	}
+	ok := mysqlSess.Update("entniche_export_limit", map[string]interface{}{"ent_id": entId, "user_id": entUserId}, set)
+	if !ok {
+		log.Println("修改导出条数失败", query, remain_nums, newCount)
+	}
+	userData := mysqlSess.FindOne("entniche_user", query, "name,phone", "")
+	if userData != nil {
+		name := util.ObjToString((*userData)["name"])
+		phone := util.ObjToString((*userData)["phone"])
+		now := time.Now()
+		mysqlSess.Insert("entniche_export_log", map[string]interface{}{
+			"user_name":    name,
+			"export_time":  FormatDate(&now, Date_Full_Layout),
+			"data_source":  "2",
+			"export_num":   count,
+			"deduct_num":   newCount,
+			"download_url": xlsxUrl,
+			"ent_id":       entId,
+			"phone":        phone,
+			"user_id":      entUserId,
+			"filter":       filterStr,
+			"export_phone": exportPhone,
+			"export_mail":  exportEmail,
+		})
+	}
+}
+
+func DeductNum(m *mysql.Mysql, qyfw mongodb.MongodbSim, entId, newCount int) {
+	query := map[string]interface{}{
+		"id": entId,
+	}
+	userData := m.FindOne("entniche_info", query, "name,phone", "")
+	if userData != nil {
+		name := util.ObjToString((*userData)["name"])
+		phone := util.ObjToString((*userData)["phone"])
+		qyfw.Update("user", map[string]interface{}{"phone": phone, "username": name}, map[string]interface{}{
+			"$inc": map[string]interface{}{
+				"plan.current": -newCount,
+			},
+		}, false, false)
+	}
+}
+func GetCurrentCount(m *mysql.Mysql, qyfw mongodb.MongodbSim, entId int) int {
+	count := 0
+	userData := m.FindOne("entniche_info", map[string]interface{}{"id": entId}, "name,phone", "")
+	if userData == nil {
+		return count
+	}
+	current, ok := qyfw.FindOne("user", map[string]interface{}{"phone": util.ObjToString((*userData)["phone"]), "username": util.ObjToString((*userData)["name"])})
+	if current == nil || !ok {
+		return count
+	}
+	plan, _ := (*current)["plan"].(map[string]interface{})
+	count = util.IntAll(plan["current"])
+	return count
+}

+ 85 - 0
common/src/qfw/util/fsw/filter.go

@@ -0,0 +1,85 @@
+package fsw
+
+import (
+	"strings"
+)
+
+const (
+	REPLTXT = "略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略略"
+)
+
+//过滤,验证是否存在敏感词
+func Match(txt string) bool {
+	for i := 0; i < len(txt); i++ {
+		nowMap := &link
+		length := 0   // 匹配标识数默认为0
+		flag := false // 敏感词结束标识位:用于敏感词只有1位的情况
+		for j := i; j < len(txt); j++ {
+			word := txt[j : j+1]
+			nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+			if nowMap != nil {
+				length = length + 1
+				tag, _ := (*nowMap)["YN"].(string)
+				if "Y" == tag {
+					flag = true
+					return true
+				}
+			} else {
+				break
+			}
+		}
+
+		if length < 2 || !flag {
+			length = 0
+		}
+		if length > 0 {
+			//log.Println(txt[i : i+length])
+			i = i + length - 1
+		}
+	}
+	return false
+}
+
+//替换
+func Repl(atxt ...string) string {
+	txt := atxt[0]
+	repl := ""
+	if len(atxt) == 2 {
+		repl = atxt[1]
+	}
+	keywords := []string{}
+	for i := 0; i < len(txt); i++ {
+		nowMap := &link
+		length := 0   // 匹配标识数默认为0
+		flag := false // 敏感词结束标识位:用于敏感词只有1位的情况
+		for j := i; j < len(txt); j++ {
+			word := txt[j : j+1]
+			nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+			if nowMap != nil {
+				length = length + 1
+				tag, _ := (*nowMap)["YN"].(string)
+				if "Y" == tag {
+					flag = true
+				}
+			} else {
+				break
+			}
+		}
+
+		if length < 2 || !flag {
+			length = 0
+		}
+		if length > 0 {
+			keywords = append(keywords, txt[i:i+length])
+			i = i + length - 1
+		}
+	}
+	for _, keyword := range keywords {
+		if len(atxt) == 1 {
+			txt = strings.Replace(txt, keyword, REPLTXT[:len(keyword)], -1)
+		} else {
+			txt = strings.Replace(txt, keyword, repl, -1)
+		}
+	}
+	return txt
+}

+ 5 - 0
common/src/qfw/util/fsw/fsw.go

@@ -0,0 +1,5 @@
+//敏感词过滤,使用DFA算法+可扩展的敏感词库
+package fsw
+
+//在调用具体方法前,请先调用初始化词库的方法
+//整个项目中仅需要调用一次

File diff suppressed because it is too large
+ 11 - 0
common/src/qfw/util/fsw/fsw_test.go


+ 90 - 0
common/src/qfw/util/fsw/fswex.go

@@ -0,0 +1,90 @@
+package fsw
+
+//敏感词过滤,扩展,类似查字典
+type Fsw struct {
+	IsStart bool
+	IsEnd   bool
+	Char    string          //字符
+	Link    map[string]*Fsw //下级对象
+}
+
+//
+type FswScan struct {
+	FswDictionary map[string]*Fsw //
+}
+
+//
+func NewFswScan() *FswScan {
+	return &FswScan{
+		FswDictionary: make(map[string]*Fsw),
+	}
+}
+
+//追加
+func (fsw *FswScan) AddWord(word string) {
+	var cfsw *Fsw
+	rs := []rune(word)
+	for i, wv := range rs {
+		kc := string(wv)
+		if i == 0 { //初始化
+			if v, ok := fsw.FswDictionary[kc]; ok {
+				cfsw = v
+			} else {
+				cfsw = &Fsw{
+					Char:    kc,
+					IsStart: true,
+					Link:    make(map[string]*Fsw),
+				}
+				fsw.FswDictionary[kc] = cfsw
+			}
+			continue
+		}
+		//查询
+		if v, ok := (*cfsw).Link[kc]; ok {
+			cfsw = v
+		} else {
+			tmp := &Fsw{
+				Char:    kc,
+				IsStart: true,
+				Link:    make(map[string]*Fsw),
+			}
+			(*cfsw).Link[kc] = tmp
+			cfsw = tmp
+		}
+		//判断结束
+	}
+	(*cfsw).IsEnd = true
+}
+
+//
+func (fsw *FswScan) AddWords(words []string) {
+	for _, v := range words {
+		fsw.AddWord(v)
+	}
+}
+
+/*按字典查找
+匹配模式:最大匹配,最小匹配,
+目前是最小匹配
+*/
+func (fsw *FswScan) Filter(src string) (string, bool) {
+	rs := []rune(src)
+	for i := 0; i < len(rs); i++ {
+		char := string(rs[i])
+		if f, ok := fsw.FswDictionary[char]; ok { //某1个字匹配上了
+			for j := i + 1; j < len(rs); j++ {
+				char = string(rs[j])
+				if v, ok := f.Link[char]; ok {
+					f = v
+					if v.IsEnd { //找到了
+						return string(rs[i : j+1]), true
+					}
+				} else {
+					break
+				}
+			}
+		}
+
+	}
+	return "", false
+}

+ 8793 - 0
common/src/qfw/util/fsw/mosaic_fsw.dict

@@ -0,0 +1,8793 @@
+北京
+北京市
+东城区
+西城区
+朝阳区
+丰台区
+石景山区
+海淀区
+门头沟区
+房山区
+通州区
+顺义区
+昌平区
+大兴区
+怀柔区
+平谷区
+密云区
+延庆区
+天津
+天津市
+和平区
+河东区
+河西区
+南开区
+河北区
+红桥区
+东丽区
+西青区
+津南区
+北辰区
+武清区
+宝坻区
+滨海新区
+宁河区
+静海区
+蓟州区
+河北
+石家庄市
+长安区
+桥西区
+新华区
+井陉矿区
+裕华区
+藁城区
+鹿泉区
+栾城区
+井陉县
+正定县
+行唐县
+灵寿县
+高邑县
+深泽县
+赞皇县
+无极县
+平山县
+元氏县
+赵县
+石家庄高新技术产业开发区
+石家庄循环化工园区
+辛集市
+晋州市
+新乐市
+唐山市
+路南区
+路北区
+古冶区
+开平区
+丰南区
+丰润区
+曹妃甸区
+滦南县
+乐亭县
+迁西县
+玉田县
+河北唐山芦台经济开发区
+唐山市汉沽管理区
+唐山高新技术产业开发区
+河北唐山海港经济开发区
+遵化市
+迁安市
+滦州市
+秦皇岛市
+海港区
+山海关区
+北戴河区
+抚宁区
+青龙满族自治县
+昌黎县
+卢龙县
+秦皇岛市经济技术开发区
+北戴河新区
+邯郸市
+邯山区
+丛台区
+复兴区
+峰峰矿区
+肥乡区
+永年区
+临漳县
+成安县
+大名县
+涉县
+磁县
+邱县
+鸡泽县
+广平县
+馆陶县
+魏县
+曲周县
+邯郸经济技术开发区
+邯郸冀南新区
+武安市
+邢台市
+襄都区
+信都区
+任泽区
+南和区
+临城县
+内丘县
+柏乡县
+隆尧县
+宁晋县
+巨鹿县
+新河县
+广宗县
+平乡县
+威县
+清河县
+临西县
+河北邢台经济开发区
+南宫市
+沙河市
+保定市
+竞秀区
+莲池区
+满城区
+清苑区
+徐水区
+涞水县
+阜平县
+定兴县
+唐县
+高阳县
+容城县
+涞源县
+望都县
+安新县
+易县
+曲阳县
+蠡县
+顺平县
+博野县
+雄县
+保定高新技术产业开发区
+保定白沟新城
+涿州市
+定州市
+安国市
+高碑店市
+张家口市
+桥东区
+宣化区
+下花园区
+万全区
+崇礼区
+张北县
+康保县
+沽源县
+尚义县
+蔚县
+阳原县
+怀安县
+怀来县
+涿鹿县
+赤城县
+张家口经济开发区
+张家口市察北管理区
+张家口市塞北管理区
+承德市
+双桥区
+双滦区
+鹰手营子矿区
+承德县
+兴隆县
+滦平县
+隆化县
+丰宁满族自治县
+宽城满族自治县
+围场满族蒙古族自治县
+承德高新技术产业开发区
+平泉市
+沧州市
+运河区
+沧县
+青县
+东光县
+海兴县
+盐山县
+肃宁县
+南皮县
+吴桥县
+献县
+孟村回族自治县
+河北沧州经济开发区
+沧州高新技术产业开发区
+沧州渤海新区
+泊头市
+任丘市
+黄骅市
+河间市
+廊坊市
+安次区
+广阳区
+固安县
+永清县
+香河县
+大城县
+文安县
+大厂回族自治县
+廊坊经济技术开发区
+霸州市
+三河市
+衡水市
+桃城区
+冀州区
+枣强县
+武邑县
+武强县
+饶阳县
+安平县
+故城县
+景县
+阜城县
+河北衡水高新技术产业开发区
+衡水滨湖新区
+深州市
+山西
+太原市
+小店区
+迎泽区
+杏花岭区
+尖草坪区
+万柏林区
+晋源区
+清徐县
+阳曲县
+娄烦县
+山西转型综合改革示范区
+古交市
+大同市
+新荣区
+平城区
+云冈区
+云州区
+阳高县
+天镇县
+广灵县
+灵丘县
+浑源县
+左云县
+山西大同经济开发区
+阳泉市
+城区
+矿区
+郊区
+平定县
+盂县
+长治市
+潞州区
+上党区
+屯留区
+潞城区
+襄垣县
+平顺县
+黎城县
+壶关县
+长子县
+武乡县
+沁县
+沁源县
+山西长治高新技术产业园区
+晋城市
+沁水县
+阳城县
+陵川县
+泽州县
+高平市
+朔州市
+朔城区
+平鲁区
+山阴县
+应县
+右玉县
+山西朔州经济开发区
+怀仁市
+晋中市
+榆次区
+太谷区
+榆社县
+左权县
+和顺县
+昔阳县
+寿阳县
+祁县
+平遥县
+灵石县
+介休市
+运城市
+盐湖区
+临猗县
+万荣县
+闻喜县
+稷山县
+新绛县
+绛县
+垣曲县
+夏县
+平陆县
+芮城县
+永济市
+河津市
+忻州市
+忻府区
+定襄县
+五台县
+代县
+繁峙县
+宁武县
+静乐县
+神池县
+五寨县
+岢岚县
+河曲县
+保德县
+偏关县
+五台山风景名胜区
+原平市
+临汾市
+尧都区
+曲沃县
+翼城县
+襄汾县
+洪洞县
+古县
+安泽县
+浮山县
+吉县
+乡宁县
+大宁县
+隰县
+永和县
+蒲县
+汾西县
+侯马市
+霍州市
+吕梁市
+离石区
+文水县
+交城县
+兴县
+临县
+柳林县
+石楼县
+岚县
+方山县
+中阳县
+交口县
+孝义市
+汾阳市
+内蒙古
+呼和浩特市
+新城区
+回民区
+玉泉区
+赛罕区
+土默特左旗
+托克托县
+和林格尔县
+清水河县
+武川县
+呼和浩特经济技术开发区
+包头市
+东河区
+昆都仑区
+青山区
+石拐区
+白云鄂博矿区
+九原区
+土默特右旗
+固阳县
+达尔罕茂明安联合旗
+包头稀土高新技术产业开发区
+乌海市
+海勃湾区
+海南区
+乌达区
+赤峰市
+红山区
+元宝山区
+松山区
+阿鲁科尔沁旗
+巴林左旗
+巴林右旗
+林西县
+克什克腾旗
+翁牛特旗
+喀喇沁旗
+宁城县
+敖汉旗
+通辽市
+科尔沁区
+科尔沁左翼中旗
+科尔沁左翼后旗
+开鲁县
+库伦旗
+奈曼旗
+扎鲁特旗
+通辽经济技术开发区
+霍林郭勒市
+鄂尔多斯市
+东胜区
+康巴什区
+达拉特旗
+准格尔旗
+鄂托克前旗
+鄂托克旗
+杭锦旗
+乌审旗
+伊金霍洛旗
+呼伦贝尔市
+海拉尔区
+扎赉诺尔区
+阿荣旗
+莫力达瓦达斡尔族自治旗
+鄂伦春自治旗
+鄂温克族自治旗
+陈巴尔虎旗
+新巴尔虎左旗
+新巴尔虎右旗
+满洲里市
+牙克石市
+扎兰屯市
+额尔古纳市
+根河市
+巴彦淖尔市
+临河区
+五原县
+磴口县
+乌拉特前旗
+乌拉特中旗
+乌拉特后旗
+杭锦后旗
+乌兰察布市
+集宁区
+卓资县
+化德县
+商都县
+兴和县
+凉城县
+察哈尔右翼前旗
+察哈尔右翼中旗
+察哈尔右翼后旗
+四子王旗
+丰镇市
+兴安盟
+乌兰浩特市
+阿尔山市
+科尔沁右翼前旗
+科尔沁右翼中旗
+扎赉特旗
+突泉县
+锡林郭勒盟
+二连浩特市
+锡林浩特市
+阿巴嘎旗
+苏尼特左旗
+苏尼特右旗
+东乌珠穆沁旗
+西乌珠穆沁旗
+太仆寺旗
+镶黄旗
+正镶白旗
+正蓝旗
+多伦县
+乌拉盖管委会
+阿拉善盟
+阿拉善左旗
+阿拉善右旗
+额济纳旗
+内蒙古阿拉善经济开发区
+辽宁
+沈阳市
+沈河区
+大东区
+皇姑区
+铁西区
+苏家屯区
+浑南区
+沈北新区
+于洪区
+辽中区
+康平县
+法库县
+新民市
+大连市
+中山区
+西岗区
+沙河口区
+甘井子区
+旅顺口区
+金州区
+普兰店区
+长海县
+瓦房店市
+庄河市
+鞍山市
+铁东区
+立山区
+千山区
+台安县
+岫岩满族自治县
+海城市
+抚顺市
+新抚区
+东洲区
+望花区
+顺城区
+抚顺县
+新宾满族自治县
+清原满族自治县
+本溪市
+平山区
+溪湖区
+明山区
+南芬区
+本溪满族自治县
+桓仁满族自治县
+丹东市
+元宝区
+振兴区
+振安区
+宽甸满族自治县
+东港市
+凤城市
+锦州市
+古塔区
+凌河区
+太和区
+黑山县
+义县
+凌海市
+北镇市
+营口市
+站前区
+西市区
+鲅鱼圈区
+老边区
+盖州市
+大石桥市
+阜新市
+海州区
+新邱区
+太平区
+清河门区
+细河区
+阜新蒙古族自治县
+彰武县
+辽阳市
+白塔区
+文圣区
+宏伟区
+弓长岭区
+太子河区
+辽阳县
+灯塔市
+盘锦市
+双台子区
+兴隆台区
+大洼区
+盘山县
+铁岭市
+银州区
+清河区
+铁岭县
+西丰县
+昌图县
+调兵山市
+开原市
+朝阳市
+双塔区
+龙城区
+朝阳县
+建平县
+喀喇沁左翼蒙古族自治县
+北票市
+凌源市
+葫芦岛市
+连山区
+龙港区
+南票区
+绥中县
+建昌县
+兴城市
+吉林
+长春市
+南关区
+宽城区
+二道区
+绿园区
+双阳区
+九台区
+农安县
+长春经济技术开发区
+长春净月高新技术产业开发区
+长春高新技术产业开发区
+长春汽车经济技术开发区
+榆树市
+德惠市
+公主岭市
+吉林市
+昌邑区
+龙潭区
+船营区
+丰满区
+永吉县
+吉林经济开发区
+吉林高新技术产业开发区
+吉林中国新加坡食品区
+蛟河市
+桦甸市
+舒兰市
+磐石市
+四平市
+梨树县
+伊通满族自治县
+双辽市
+辽源市
+龙山区
+西安区
+东丰县
+东辽县
+通化市
+东昌区
+二道江区
+通化县
+辉南县
+柳河县
+梅河口市
+集安市
+白山市
+浑江区
+江源区
+抚松县
+靖宇县
+长白朝鲜族自治县
+临江市
+松原市
+宁江区
+前郭尔罗斯蒙古族自治县
+长岭县
+乾安县
+吉林松原经济开发区
+扶余市
+白城市
+洮北区
+镇赉县
+通榆县
+吉林白城经济开发区
+洮南市
+大安市
+延边朝鲜族自治州
+延吉市
+图们市
+敦化市
+珲春市
+龙井市
+和龙市
+汪清县
+安图县
+黑龙江
+哈尔滨市
+道里区
+南岗区
+道外区
+平房区
+松北区
+香坊区
+呼兰区
+阿城区
+双城区
+依兰县
+方正县
+宾县
+巴彦县
+木兰县
+通河县
+延寿县
+尚志市
+五常市
+齐齐哈尔市
+龙沙区
+建华区
+铁锋区
+昂昂溪区
+富拉尔基区
+碾子山区
+梅里斯达斡尔族区
+龙江县
+依安县
+泰来县
+甘南县
+富裕县
+克山县
+克东县
+拜泉县
+讷河市
+鸡西市
+鸡冠区
+恒山区
+滴道区
+梨树区
+城子河区
+麻山区
+鸡东县
+虎林市
+密山市
+鹤岗市
+向阳区
+工农区
+南山区
+兴安区
+东山区
+兴山区
+萝北县
+绥滨县
+双鸭山市
+尖山区
+岭东区
+四方台区
+宝山区
+集贤县
+友谊县
+宝清县
+饶河县
+大庆市
+萨尔图区
+龙凤区
+让胡路区
+红岗区
+大同区
+肇州县
+肇源县
+林甸县
+杜尔伯特蒙古族自治县
+大庆高新技术产业开发区
+伊春市
+伊美区
+乌翠区
+友好区
+嘉荫县
+汤旺县
+丰林县
+大箐山县
+南岔县
+金林区
+铁力市
+佳木斯市
+前进区
+东风区
+桦南县
+桦川县
+汤原县
+同江市
+富锦市
+抚远市
+七台河市
+新兴区
+桃山区
+茄子河区
+勃利县
+牡丹江市
+东安区
+阳明区
+爱民区
+林口县
+牡丹江经济技术开发区
+绥芬河市
+海林市
+宁安市
+穆棱市
+东宁市
+黑河市
+爱辉区
+逊克县
+孙吴县
+北安市
+五大连池市
+嫩江市
+绥化市
+北林区
+望奎县
+兰西县
+青冈县
+庆安县
+明水县
+绥棱县
+安达市
+肇东市
+海伦市
+大兴安岭地区
+漠河市
+呼玛县
+塔河县
+加格达奇区
+松岭区
+新林区
+呼中区
+上海
+上海市
+黄浦区
+徐汇区
+长宁区
+静安区
+普陀区
+虹口区
+杨浦区
+闵行区
+嘉定区
+浦东新区
+金山区
+松江区
+青浦区
+奉贤区
+崇明区
+江苏
+南京市
+玄武区
+秦淮区
+建邺区
+鼓楼区
+浦口区
+栖霞区
+雨花台区
+江宁区
+六合区
+溧水区
+高淳区
+无锡市
+锡山区
+惠山区
+滨湖区
+梁溪区
+新吴区
+江阴市
+宜兴市
+徐州市
+云龙区
+贾汪区
+泉山区
+铜山区
+丰县
+沛县
+睢宁县
+徐州经济技术开发区
+新沂市
+邳州市
+常州市
+天宁区
+钟楼区
+新北区
+武进区
+金坛区
+溧阳市
+苏州市
+虎丘区
+吴中区
+相城区
+姑苏区
+吴江区
+苏州工业园区
+常熟市
+张家港市
+昆山市
+太仓市
+南通市
+崇川区
+港闸区
+如东县
+南通经济技术开发区
+启东市
+如皋市
+海门市
+海安市
+连云港市
+连云区
+赣榆区
+东海县
+灌云县
+灌南县
+连云港经济技术开发区
+连云港高新技术产业开发区
+淮安市
+淮安区
+淮阴区
+清江浦区
+洪泽区
+涟水县
+盱眙县
+金湖县
+淮安经济技术开发区
+盐城市
+亭湖区
+盐都区
+大丰区
+响水县
+滨海县
+阜宁县
+射阳县
+建湖县
+盐城经济技术开发区
+东台市
+扬州市
+广陵区
+邗江区
+江都区
+宝应县
+扬州经济技术开发区
+仪征市
+高邮市
+镇江市
+京口区
+润州区
+丹徒区
+镇江新区
+丹阳市
+扬中市
+句容市
+泰州市
+海陵区
+高港区
+姜堰区
+泰州医药高新技术产业开发区
+兴化市
+靖江市
+泰兴市
+宿迁市
+宿城区
+宿豫区
+沭阳县
+泗阳县
+泗洪县
+宿迁经济技术开发区
+浙江
+杭州市
+上城区
+下城区
+江干区
+拱墅区
+西湖区
+滨江区
+萧山区
+余杭区
+富阳区
+临安区
+桐庐县
+淳安县
+建德市
+宁波市
+海曙区
+江北区
+北仑区
+镇海区
+鄞州区
+奉化区
+象山县
+宁海县
+余姚市
+慈溪市
+温州市
+鹿城区
+龙湾区
+瓯海区
+洞头区
+永嘉县
+平阳县
+苍南县
+文成县
+泰顺县
+温州经济技术开发区
+瑞安市
+乐清市
+龙港市
+嘉兴市
+南湖区
+秀洲区
+嘉善县
+海盐县
+海宁市
+平湖市
+桐乡市
+湖州市
+吴兴区
+南浔区
+德清县
+长兴县
+安吉县
+绍兴市
+越城区
+柯桥区
+上虞区
+新昌县
+诸暨市
+嵊州市
+金华市
+婺城区
+金东区
+武义县
+浦江县
+磐安县
+兰溪市
+义乌市
+东阳市
+永康市
+衢州市
+柯城区
+衢江区
+常山县
+开化县
+龙游县
+江山市
+舟山市
+定海区
+岱山县
+嵊泗县
+台州市
+椒江区
+黄岩区
+路桥区
+三门县
+天台县
+仙居县
+温岭市
+临海市
+玉环市
+丽水市
+莲都区
+青田县
+缙云县
+遂昌县
+松阳县
+云和县
+庆元县
+景宁畲族自治县
+龙泉市
+安徽
+合肥市
+瑶海区
+庐阳区
+蜀山区
+包河区
+长丰县
+肥东县
+肥西县
+庐江县
+合肥高新技术产业开发区
+合肥经济技术开发区
+合肥新站高新技术产业开发区
+巢湖市
+芜湖市
+镜湖区
+弋江区
+鸠江区
+三山区
+芜湖县
+繁昌县
+南陵县
+芜湖经济技术开发区
+安徽芜湖长江大桥经济开发区
+无为市
+蚌埠市
+龙子湖区
+蚌山区
+禹会区
+淮上区
+怀远县
+五河县
+固镇县
+蚌埠市高新技术开发区
+蚌埠市经济开发区
+淮南市
+大通区
+田家庵区
+谢家集区
+八公山区
+潘集区
+凤台县
+寿县
+马鞍山市
+花山区
+雨山区
+博望区
+当涂县
+含山县
+和县
+淮北市
+杜集区
+相山区
+烈山区
+濉溪县
+铜陵市
+铜官区
+义安区
+枞阳县
+安庆市
+迎江区
+大观区
+宜秀区
+怀宁县
+太湖县
+宿松县
+望江县
+岳西县
+安徽安庆经济开发区
+桐城市
+潜山市
+黄山市
+屯溪区
+黄山区
+徽州区
+歙县
+休宁县
+黟县
+祁门县
+滁州市
+琅琊区
+南谯区
+来安县
+全椒县
+定远县
+凤阳县
+苏滁现代产业园
+滁州经济技术开发区
+天长市
+明光市
+阜阳市
+颍州区
+颍东区
+颍泉区
+临泉县
+太和县
+阜南县
+颍上县
+阜阳合肥现代产业园区
+阜阳经济技术开发区
+界首市
+宿州市
+埇桥区
+砀山县
+萧县
+灵璧县
+泗县
+宿州马鞍山现代产业园区
+宿州经济技术开发区
+六安市
+金安区
+裕安区
+叶集区
+霍邱县
+舒城县
+金寨县
+霍山县
+亳州市
+谯城区
+涡阳县
+蒙城县
+利辛县
+池州市
+贵池区
+东至县
+石台县
+青阳县
+宣城市
+宣州区
+郎溪县
+泾县
+绩溪县
+旌德县
+宣城市经济开发区
+宁国市
+广德市
+福建
+福州市
+台江区
+仓山区
+马尾区
+晋安区
+长乐区
+闽侯县
+连江县
+罗源县
+闽清县
+永泰县
+平潭县
+福清市
+厦门市
+思明区
+海沧区
+湖里区
+集美区
+同安区
+翔安区
+莆田市
+城厢区
+涵江区
+荔城区
+秀屿区
+仙游县
+三明市
+梅列区
+三元区
+明溪县
+清流县
+宁化县
+大田县
+尤溪县
+沙县
+将乐县
+泰宁县
+建宁县
+永安市
+泉州市
+鲤城区
+丰泽区
+洛江区
+泉港区
+惠安县
+安溪县
+永春县
+德化县
+金门县
+石狮市
+晋江市
+南安市
+漳州市
+芗城区
+龙文区
+云霄县
+漳浦县
+诏安县
+长泰县
+东山县
+南靖县
+平和县
+华安县
+龙海市
+南平市
+延平区
+建阳区
+顺昌县
+浦城县
+光泽县
+松溪县
+政和县
+邵武市
+武夷山市
+建瓯市
+龙岩市
+新罗区
+永定区
+长汀县
+上杭县
+武平县
+连城县
+漳平市
+宁德市
+蕉城区
+霞浦县
+古田县
+屏南县
+寿宁县
+周宁县
+柘荣县
+福安市
+福鼎市
+江西
+南昌市
+东湖区
+青云谱区
+青山湖区
+新建区
+红谷滩区
+南昌县
+安义县
+进贤县
+景德镇市
+昌江区
+珠山区
+浮梁县
+乐平市
+萍乡市
+安源区
+湘东区
+莲花县
+上栗县
+芦溪县
+九江市
+濂溪区
+浔阳区
+柴桑区
+武宁县
+修水县
+永修县
+德安县
+都昌县
+湖口县
+彭泽县
+瑞昌市
+共青城市
+庐山市
+新余市
+渝水区
+分宜县
+鹰潭市
+月湖区
+余江区
+贵溪市
+赣州市
+章贡区
+南康区
+赣县区
+信丰县
+大余县
+上犹县
+崇义县
+安远县
+定南县
+全南县
+宁都县
+于都县
+兴国县
+会昌县
+寻乌县
+石城县
+瑞金市
+龙南市
+吉安市
+吉州区
+青原区
+吉安县
+吉水县
+峡江县
+新干县
+永丰县
+泰和县
+遂川县
+万安县
+安福县
+永新县
+井冈山市
+宜春市
+袁州区
+奉新县
+万载县
+上高县
+宜丰县
+靖安县
+铜鼓县
+丰城市
+樟树市
+高安市
+抚州市
+临川区
+东乡区
+南城县
+黎川县
+南丰县
+崇仁县
+乐安县
+宜黄县
+金溪县
+资溪县
+广昌县
+上饶市
+信州区
+广丰区
+广信区
+玉山县
+铅山县
+横峰县
+弋阳县
+余干县
+鄱阳县
+万年县
+婺源县
+德兴市
+山东
+济南市
+历下区
+市中区
+槐荫区
+天桥区
+历城区
+长清区
+章丘区
+济阳区
+莱芜区
+钢城区
+平阴县
+商河县
+济南高新技术产业开发区
+青岛市
+市南区
+市北区
+黄岛区
+崂山区
+李沧区
+城阳区
+即墨区
+青岛高新技术产业开发区
+胶州市
+平度市
+莱西市
+淄博市
+淄川区
+张店区
+博山区
+临淄区
+周村区
+桓台县
+高青县
+沂源县
+枣庄市
+薛城区
+峄城区
+台儿庄区
+山亭区
+滕州市
+东营市
+东营区
+河口区
+垦利区
+利津县
+广饶县
+东营经济技术开发区
+东营港经济开发区
+烟台市
+芝罘区
+福山区
+牟平区
+莱山区
+蓬莱区
+烟台高新技术产业开发区
+烟台经济技术开发区
+龙口市
+莱阳市
+莱州市
+招远市
+栖霞市
+海阳市
+潍坊市
+潍城区
+寒亭区
+坊子区
+奎文区
+临朐县
+昌乐县
+潍坊滨海经济技术开发区
+青州市
+诸城市
+寿光市
+安丘市
+高密市
+昌邑市
+济宁市
+任城区
+兖州区
+微山县
+鱼台县
+金乡县
+嘉祥县
+汶上县
+泗水县
+梁山县
+济宁高新技术产业开发区
+曲阜市
+邹城市
+泰安市
+泰山区
+岱岳区
+宁阳县
+东平县
+新泰市
+肥城市
+威海市
+环翠区
+文登区
+威海火炬高技术产业开发区
+威海经济技术开发区
+威海临港经济技术开发区
+荣成市
+乳山市
+日照市
+东港区
+岚山区
+五莲县
+莒县
+日照经济技术开发区
+临沂市
+兰山区
+罗庄区
+沂南县
+郯城县
+沂水县
+兰陵县
+费县
+平邑县
+莒南县
+蒙阴县
+临沭县
+临沂高新技术产业开发区
+德州市
+德城区
+陵城区
+宁津县
+庆云县
+临邑县
+齐河县
+平原县
+夏津县
+武城县
+德州经济技术开发区
+德州运河经济开发区
+乐陵市
+禹城市
+聊城市
+东昌府区
+茌平区
+阳谷县
+莘县
+东阿县
+冠县
+高唐县
+临清市
+滨州市
+滨城区
+沾化区
+惠民县
+阳信县
+无棣县
+博兴县
+邹平市
+菏泽市
+牡丹区
+定陶区
+曹县
+单县
+成武县
+巨野县
+郓城县
+鄄城县
+东明县
+菏泽经济技术开发区
+菏泽高新技术开发区
+河南
+郑州市
+中原区
+二七区
+管城回族区
+金水区
+上街区
+惠济区
+中牟县
+郑州经济技术开发区
+郑州高新技术产业开发区
+郑州航空港经济综合实验区
+巩义市
+荥阳市
+新密市
+新郑市
+登封市
+开封市
+龙亭区
+顺河回族区
+禹王台区
+祥符区
+杞县
+通许县
+尉氏县
+兰考县
+洛阳市
+老城区
+西工区
+瀍河回族区
+涧西区
+吉利区
+洛龙区
+孟津县
+新安县
+栾川县
+嵩县
+汝阳县
+宜阳县
+洛宁县
+伊川县
+洛阳高新技术产业开发区
+偃师市
+平顶山市
+卫东区
+石龙区
+湛河区
+宝丰县
+叶县
+鲁山县
+郏县
+平顶山高新技术产业开发区
+平顶山市城乡一体化示范区
+舞钢市
+汝州市
+安阳市
+文峰区
+北关区
+殷都区
+龙安区
+安阳县
+汤阴县
+滑县
+内黄县
+安阳高新技术产业开发区
+林州市
+鹤壁市
+鹤山区
+山城区
+淇滨区
+浚县
+淇县
+鹤壁经济技术开发区
+新乡市
+红旗区
+卫滨区
+凤泉区
+牧野区
+新乡县
+获嘉县
+原阳县
+延津县
+封丘县
+新乡高新技术产业开发区
+新乡经济技术开发区
+新乡市平原城乡一体化示范区
+卫辉市
+辉县市
+长垣市
+焦作市
+解放区
+中站区
+马村区
+山阳区
+修武县
+博爱县
+武陟县
+温县
+焦作城乡一体化示范区
+沁阳市
+孟州市
+濮阳市
+华龙区
+清丰县
+南乐县
+范县
+台前县
+濮阳县
+河南濮阳工业园区
+濮阳经济技术开发区
+许昌市
+魏都区
+建安区
+鄢陵县
+襄城县
+许昌经济技术开发区
+禹州市
+长葛市
+漯河市
+源汇区
+郾城区
+召陵区
+舞阳县
+临颍县
+漯河经济技术开发区
+三门峡市
+湖滨区
+陕州区
+渑池县
+卢氏县
+河南三门峡经济开发区
+义马市
+灵宝市
+南阳市
+宛城区
+卧龙区
+南召县
+方城县
+西峡县
+镇平县
+内乡县
+淅川县
+社旗县
+唐河县
+新野县
+桐柏县
+南阳高新技术产业开发区
+南阳市城乡一体化示范区
+邓州市
+商丘市
+梁园区
+睢阳区
+民权县
+睢县
+宁陵县
+柘城县
+虞城县
+夏邑县
+豫东综合物流产业聚集区
+河南商丘经济开发区
+永城市
+信阳市
+浉河区
+平桥区
+罗山县
+光山县
+新县
+商城县
+固始县
+潢川县
+淮滨县
+息县
+信阳高新技术产业开发区
+周口市
+川汇区
+淮阳区
+扶沟县
+西华县
+商水县
+沈丘县
+郸城县
+太康县
+鹿邑县
+河南周口经济开发区
+项城市
+驻马店市
+驿城区
+西平县
+上蔡县
+平舆县
+正阳县
+确山县
+泌阳县
+汝南县
+遂平县
+新蔡县
+河南驻马店经济开发区
+济源市
+湖北
+武汉市
+江岸区
+江汉区
+硚口区
+汉阳区
+武昌区
+洪山区
+东西湖区
+汉南区
+蔡甸区
+江夏区
+黄陂区
+新洲区
+黄石市
+黄石港区
+西塞山区
+下陆区
+铁山区
+阳新县
+大冶市
+十堰市
+茅箭区
+张湾区
+郧阳区
+郧西县
+竹山县
+竹溪县
+房县
+丹江口市
+宜昌市
+西陵区
+伍家岗区
+点军区
+猇亭区
+夷陵区
+远安县
+兴山县
+秭归县
+长阳土家族自治县
+五峰土家族自治县
+宜都市
+当阳市
+枝江市
+襄阳市
+襄城区
+樊城区
+襄州区
+南漳县
+谷城县
+保康县
+老河口市
+枣阳市
+宜城市
+鄂州市
+梁子湖区
+华容区
+鄂城区
+荆门市
+东宝区
+掇刀区
+沙洋县
+钟祥市
+京山市
+孝感市
+孝南区
+孝昌县
+大悟县
+云梦县
+应城市
+安陆市
+汉川市
+荆州市
+沙市区
+荆州区
+公安县
+监利县
+江陵县
+荆州经济技术开发区
+石首市
+洪湖市
+松滋市
+黄冈市
+黄州区
+团风县
+红安县
+罗田县
+英山县
+浠水县
+蕲春县
+黄梅县
+龙感湖管理区
+麻城市
+武穴市
+咸宁市
+咸安区
+嘉鱼县
+通城县
+崇阳县
+通山县
+赤壁市
+随州市
+曾都区
+随县
+广水市
+恩施土家族苗族自治州
+恩施市
+利川市
+建始县
+巴东县
+宣恩县
+咸丰县
+来凤县
+鹤峰县
+仙桃市
+潜江市
+天门市
+神农架林区
+湖南
+长沙市
+芙蓉区
+天心区
+岳麓区
+开福区
+雨花区
+望城区
+长沙县
+浏阳市
+宁乡市
+株洲市
+荷塘区
+芦淞区
+石峰区
+天元区
+渌口区
+攸县
+茶陵县
+炎陵县
+云龙示范区
+醴陵市
+湘潭市
+雨湖区
+岳塘区
+湘潭县
+湖南湘潭高新技术产业园区
+湘潭昭山示范区
+湘潭九华示范区
+湘乡市
+韶山市
+衡阳市
+珠晖区
+雁峰区
+石鼓区
+蒸湘区
+南岳区
+衡阳县
+衡南县
+衡山县
+衡东县
+祁东县
+衡阳综合保税区
+湖南衡阳高新技术产业园区
+湖南衡阳松木经济开发区
+耒阳市
+常宁市
+邵阳市
+双清区
+大祥区
+北塔区
+新邵县
+邵阳县
+隆回县
+洞口县
+绥宁县
+新宁县
+城步苗族自治县
+武冈市
+邵东市
+岳阳市
+岳阳楼区
+云溪区
+君山区
+岳阳县
+华容县
+湘阴县
+平江县
+岳阳市屈原管理区
+汨罗市
+临湘市
+常德市
+武陵区
+鼎城区
+安乡县
+汉寿县
+澧县
+临澧县
+桃源县
+石门县
+常德市西洞庭管理区
+津市市
+张家界市
+武陵源区
+慈利县
+桑植县
+益阳市
+资阳区
+赫山区
+南县
+桃江县
+安化县
+益阳市大通湖管理区
+湖南益阳高新技术产业园区
+沅江市
+郴州市
+北湖区
+苏仙区
+桂阳县
+宜章县
+永兴县
+嘉禾县
+临武县
+汝城县
+桂东县
+安仁县
+资兴市
+永州市
+零陵区
+冷水滩区
+祁阳县
+东安县
+双牌县
+道县
+江永县
+宁远县
+蓝山县
+新田县
+江华瑶族自治县
+永州经济技术开发区
+永州市金洞管理区
+永州市回龙圩管理区
+怀化市
+鹤城区
+中方县
+沅陵县
+辰溪县
+溆浦县
+会同县
+麻阳苗族自治县
+新晃侗族自治县
+芷江侗族自治县
+靖州苗族侗族自治县
+通道侗族自治县
+怀化市洪江管理区
+洪江市
+娄底市
+娄星区
+双峰县
+新化县
+冷水江市
+涟源市
+湘西土家族苗族自治州
+吉首市
+泸溪县
+凤凰县
+花垣县
+保靖县
+古丈县
+永顺县
+龙山县
+广东
+广州市
+荔湾区
+越秀区
+海珠区
+天河区
+白云区
+黄埔区
+番禺区
+花都区
+南沙区
+从化区
+增城区
+韶关市
+武江区
+浈江区
+曲江区
+始兴县
+仁化县
+翁源县
+乳源瑶族自治县
+新丰县
+乐昌市
+南雄市
+深圳市
+罗湖区
+福田区
+宝安区
+龙岗区
+盐田区
+龙华区
+坪山区
+光明区
+珠海市
+香洲区
+斗门区
+金湾区
+汕头市
+龙湖区
+金平区
+濠江区
+潮阳区
+潮南区
+澄海区
+南澳县
+佛山市
+禅城区
+南海区
+顺德区
+三水区
+高明区
+江门市
+蓬江区
+江海区
+新会区
+台山市
+开平市
+鹤山市
+恩平市
+湛江市
+赤坎区
+霞山区
+坡头区
+麻章区
+遂溪县
+徐闻县
+廉江市
+雷州市
+吴川市
+茂名市
+茂南区
+电白区
+高州市
+化州市
+信宜市
+肇庆市
+端州区
+鼎湖区
+高要区
+广宁县
+怀集县
+封开县
+德庆县
+四会市
+惠州市
+惠城区
+惠阳区
+博罗县
+惠东县
+龙门县
+梅州市
+梅江区
+梅县区
+大埔县
+丰顺县
+五华县
+平远县
+蕉岭县
+兴宁市
+汕尾市
+海丰县
+陆河县
+陆丰市
+河源市
+源城区
+紫金县
+龙川县
+连平县
+和平县
+东源县
+阳江市
+江城区
+阳东区
+阳西县
+阳春市
+清远市
+清城区
+清新区
+佛冈县
+阳山县
+连山壮族瑶族自治县
+连南瑶族自治县
+英德市
+连州市
+东莞市
+中山市
+潮州市
+湘桥区
+潮安区
+饶平县
+揭阳市
+榕城区
+揭东区
+揭西县
+惠来县
+普宁市
+云浮市
+云城区
+云安区
+新兴县
+郁南县
+罗定市
+广西
+南宁市
+兴宁区
+青秀区
+江南区
+西乡塘区
+良庆区
+邕宁区
+武鸣区
+隆安县
+马山县
+上林县
+宾阳县
+横县
+柳州市
+城中区
+鱼峰区
+柳南区
+柳北区
+柳江区
+柳城县
+鹿寨县
+融安县
+融水苗族自治县
+三江侗族自治县
+桂林市
+秀峰区
+叠彩区
+象山区
+七星区
+雁山区
+临桂区
+阳朔县
+灵川县
+全州县
+兴安县
+永福县
+灌阳县
+龙胜各族自治县
+资源县
+平乐县
+恭城瑶族自治县
+荔浦市
+梧州市
+万秀区
+长洲区
+龙圩区
+苍梧县
+藤县
+蒙山县
+岑溪市
+北海市
+海城区
+银海区
+铁山港区
+合浦县
+防城港市
+港口区
+防城区
+上思县
+东兴市
+钦州市
+钦南区
+钦北区
+灵山县
+浦北县
+贵港市
+港北区
+港南区
+覃塘区
+平南县
+桂平市
+玉林市
+玉州区
+福绵区
+容县
+陆川县
+博白县
+兴业县
+北流市
+百色市
+右江区
+田阳区
+田东县
+德保县
+那坡县
+凌云县
+乐业县
+田林县
+西林县
+隆林各族自治县
+靖西市
+平果市
+贺州市
+八步区
+平桂区
+昭平县
+钟山县
+富川瑶族自治县
+河池市
+金城江区
+宜州区
+南丹县
+天峨县
+凤山县
+东兰县
+罗城仫佬族自治县
+环江毛南族自治县
+巴马瑶族自治县
+都安瑶族自治县
+大化瑶族自治县
+来宾市
+兴宾区
+忻城县
+象州县
+武宣县
+金秀瑶族自治县
+合山市
+崇左市
+江州区
+扶绥县
+宁明县
+龙州县
+大新县
+天等县
+凭祥市
+海南
+海口市
+秀英区
+琼山区
+美兰区
+三亚市
+海棠区
+吉阳区
+天涯区
+崖州区
+三沙市
+西沙群岛
+南沙群岛
+中沙群岛的岛礁及其海域
+儋州市
+五指山市
+琼海市
+文昌市
+万宁市
+东方市
+定安县
+屯昌县
+澄迈县
+临高县
+白沙黎族自治县
+昌江黎族自治县
+乐东黎族自治县
+陵水黎族自治县
+保亭黎族苗族自治县
+琼中黎族苗族自治县
+重庆
+重庆市
+万州区
+涪陵区
+渝中区
+大渡口区
+沙坪坝区
+九龙坡区
+南岸区
+北碚区
+綦江区
+大足区
+渝北区
+巴南区
+黔江区
+长寿区
+江津区
+合川区
+永川区
+南川区
+璧山区
+铜梁区
+潼南区
+荣昌区
+开州区
+梁平区
+武隆区
+城口县
+丰都县
+垫江县
+忠县
+云阳县
+奉节县
+巫山县
+巫溪县
+石柱土家族自治县
+秀山土家族苗族自治县
+酉阳土家族苗族自治县
+彭水苗族土家族自治县
+四川
+成都市
+锦江区
+青羊区
+金牛区
+武侯区
+成华区
+龙泉驿区
+青白江区
+新都区
+温江区
+双流区
+郫都区
+新津区
+金堂县
+大邑县
+蒲江县
+都江堰市
+彭州市
+邛崃市
+崇州市
+简阳市
+自贡市
+自流井区
+贡井区
+大安区
+沿滩区
+荣县
+富顺县
+攀枝花市
+东区
+西区
+仁和区
+米易县
+盐边县
+泸州市
+江阳区
+纳溪区
+龙马潭区
+泸县
+合江县
+叙永县
+古蔺县
+德阳市
+旌阳区
+罗江区
+中江县
+广汉市
+什邡市
+绵竹市
+绵阳市
+涪城区
+游仙区
+安州区
+三台县
+盐亭县
+梓潼县
+北川羌族自治县
+平武县
+江油市
+广元市
+利州区
+昭化区
+朝天区
+旺苍县
+青川县
+剑阁县
+苍溪县
+遂宁市
+船山区
+安居区
+蓬溪县
+大英县
+射洪市
+内江市
+东兴区
+威远县
+资中县
+内江经济开发区
+隆昌市
+乐山市
+沙湾区
+五通桥区
+金口河区
+犍为县
+井研县
+夹江县
+沐川县
+峨边彝族自治县
+马边彝族自治县
+峨眉山市
+南充市
+顺庆区
+高坪区
+嘉陵区
+南部县
+营山县
+蓬安县
+仪陇县
+西充县
+阆中市
+眉山市
+东坡区
+彭山区
+仁寿县
+洪雅县
+丹棱县
+青神县
+宜宾市
+翠屏区
+南溪区
+叙州区
+江安县
+长宁县
+高县
+珙县
+筠连县
+兴文县
+屏山县
+广安市
+广安区
+前锋区
+岳池县
+武胜县
+邻水县
+华蓥市
+达州市
+通川区
+达川区
+宣汉县
+开江县
+大竹县
+渠县
+达州经济开发区
+万源市
+雅安市
+雨城区
+名山区
+荥经县
+汉源县
+石棉县
+天全县
+芦山县
+宝兴县
+巴中市
+巴州区
+恩阳区
+通江县
+南江县
+平昌县
+巴中经济开发区
+资阳市
+雁江区
+安岳县
+乐至县
+阿坝藏族羌族自治州
+马尔康市
+汶川县
+理县
+茂县
+松潘县
+九寨沟县
+金川县
+小金县
+黑水县
+壤塘县
+阿坝县
+若尔盖县
+红原县
+甘孜藏族自治州
+康定市
+泸定县
+丹巴县
+九龙县
+雅江县
+道孚县
+炉霍县
+甘孜县
+新龙县
+德格县
+白玉县
+石渠县
+色达县
+理塘县
+巴塘县
+乡城县
+稻城县
+得荣县
+凉山彝族自治州
+西昌市
+木里藏族自治县
+盐源县
+德昌县
+会理县
+会东县
+宁南县
+普格县
+布拖县
+金阳县
+昭觉县
+喜德县
+冕宁县
+越西县
+甘洛县
+美姑县
+雷波县
+贵州
+贵阳市
+南明区
+云岩区
+花溪区
+乌当区
+观山湖区
+开阳县
+息烽县
+修文县
+清镇市
+六盘水市
+钟山区
+六枝特区
+水城县
+盘州市
+遵义市
+红花岗区
+汇川区
+播州区
+桐梓县
+绥阳县
+正安县
+道真仡佬族苗族自治县
+务川仡佬族苗族自治县
+凤冈县
+湄潭县
+余庆县
+习水县
+赤水市
+仁怀市
+安顺市
+西秀区
+平坝区
+普定县
+镇宁布依族苗族自治县
+关岭布依族苗族自治县
+紫云苗族布依族自治县
+毕节市
+七星关区
+大方县
+黔西县
+金沙县
+织金县
+纳雍县
+威宁彝族回族苗族自治县
+赫章县
+铜仁市
+碧江区
+万山区
+江口县
+玉屏侗族自治县
+石阡县
+思南县
+印江土家族苗族自治县
+德江县
+沿河土家族自治县
+松桃苗族自治县
+黔西南布依族苗族自治州
+兴义市
+兴仁市
+普安县
+晴隆县
+贞丰县
+望谟县
+册亨县
+安龙县
+黔东南苗族侗族自治州
+凯里市
+黄平县
+施秉县
+三穗县
+镇远县
+岑巩县
+天柱县
+锦屏县
+剑河县
+台江县
+黎平县
+榕江县
+从江县
+雷山县
+麻江县
+丹寨县
+黔南布依族苗族自治州
+都匀市
+福泉市
+荔波县
+贵定县
+瓮安县
+独山县
+平塘县
+罗甸县
+长顺县
+龙里县
+惠水县
+三都水族自治县
+云南
+昆明市
+五华区
+盘龙区
+官渡区
+西山区
+东川区
+呈贡区
+晋宁区
+富民县
+宜良县
+石林彝族自治县
+嵩明县
+禄劝彝族苗族自治县
+寻甸回族彝族自治县
+安宁市
+曲靖市
+麒麟区
+沾益区
+马龙区
+陆良县
+师宗县
+罗平县
+富源县
+会泽县
+宣威市
+玉溪市
+红塔区
+江川区
+通海县
+华宁县
+易门县
+峨山彝族自治县
+新平彝族傣族自治县
+元江哈尼族彝族傣族自治县
+澄江市
+保山市
+隆阳区
+施甸县
+龙陵县
+昌宁县
+腾冲市
+昭通市
+昭阳区
+鲁甸县
+巧家县
+盐津县
+大关县
+永善县
+绥江县
+镇雄县
+彝良县
+威信县
+水富市
+丽江市
+古城区
+玉龙纳西族自治县
+永胜县
+华坪县
+宁蒗彝族自治县
+普洱市
+思茅区
+宁洱哈尼族彝族自治县
+墨江哈尼族自治县
+景东彝族自治县
+景谷傣族彝族自治县
+镇沅彝族哈尼族拉祜族自治县
+江城哈尼族彝族自治县
+孟连傣族拉祜族佤族自治县
+澜沧拉祜族自治县
+西盟佤族自治县
+临沧市
+临翔区
+凤庆县
+云县
+永德县
+镇康县
+双江拉祜族佤族布朗族傣族自治县
+耿马傣族佤族自治县
+沧源佤族自治县
+楚雄彝族自治州
+楚雄市
+双柏县
+牟定县
+南华县
+姚安县
+大姚县
+永仁县
+元谋县
+武定县
+禄丰县
+红河哈尼族彝族自治州
+个旧市
+开远市
+蒙自市
+弥勒市
+屏边苗族自治县
+建水县
+石屏县
+泸西县
+元阳县
+红河县
+金平苗族瑶族傣族自治县
+绿春县
+河口瑶族自治县
+文山壮族苗族自治州
+文山市
+砚山县
+西畴县
+麻栗坡县
+马关县
+丘北县
+广南县
+富宁县
+西双版纳傣族自治州
+景洪市
+勐海县
+勐腊县
+大理白族自治州
+大理市
+漾濞彝族自治县
+祥云县
+宾川县
+弥渡县
+南涧彝族自治县
+巍山彝族回族自治县
+永平县
+云龙县
+洱源县
+剑川县
+鹤庆县
+德宏傣族景颇族自治州
+瑞丽市
+芒市
+梁河县
+盈江县
+陇川县
+怒江傈僳族自治州
+泸水市
+福贡县
+贡山独龙族怒族自治县
+兰坪白族普米族自治县
+迪庆藏族自治州
+香格里拉市
+德钦县
+维西傈僳族自治县
+西藏
+拉萨市
+城关区
+堆龙德庆区
+达孜区
+林周县
+当雄县
+尼木县
+曲水县
+墨竹工卡县
+格尔木藏青工业园区
+拉萨经济技术开发区
+西藏文化旅游创意园区
+达孜工业园区
+日喀则市
+桑珠孜区
+南木林县
+江孜县
+定日县
+萨迦县
+拉孜县
+昂仁县
+谢通门县
+白朗县
+仁布县
+康马县
+定结县
+仲巴县
+亚东县
+吉隆县
+聂拉木县
+萨嘎县
+岗巴县
+昌都市
+卡若区
+江达县
+贡觉县
+类乌齐县
+丁青县
+察雅县
+八宿县
+左贡县
+芒康县
+洛隆县
+边坝县
+林芝市
+巴宜区
+工布江达县
+米林县
+墨脱县
+波密县
+察隅县
+朗县
+山南市
+乃东区
+扎囊县
+贡嘎县
+桑日县
+琼结县
+曲松县
+措美县
+洛扎县
+加查县
+隆子县
+错那县
+浪卡子县
+那曲市
+色尼区
+嘉黎县
+比如县
+聂荣县
+安多县
+申扎县
+索县
+班戈县
+巴青县
+尼玛县
+双湖县
+阿里地区
+普兰县
+札达县
+噶尔县
+日土县
+革吉县
+改则县
+措勤县
+陕西
+西安市
+碑林区
+莲湖区
+灞桥区
+未央区
+雁塔区
+阎良区
+临潼区
+高陵区
+鄠邑区
+蓝田县
+周至县
+铜川市
+王益区
+印台区
+耀州区
+宜君县
+宝鸡市
+渭滨区
+金台区
+陈仓区
+凤翔县
+岐山县
+扶风县
+眉县
+陇县
+千阳县
+麟游县
+凤县
+太白县
+咸阳市
+秦都区
+杨陵区
+渭城区
+三原县
+泾阳县
+乾县
+礼泉县
+永寿县
+长武县
+旬邑县
+淳化县
+武功县
+兴平市
+彬州市
+渭南市
+临渭区
+华州区
+潼关县
+大荔县
+合阳县
+澄城县
+蒲城县
+白水县
+富平县
+韩城市
+华阴市
+延安市
+宝塔区
+安塞区
+延长县
+延川县
+志丹县
+吴起县
+甘泉县
+富县
+洛川县
+宜川县
+黄龙县
+黄陵县
+子长市
+汉中市
+汉台区
+南郑区
+城固县
+洋县
+西乡县
+勉县
+宁强县
+略阳县
+镇巴县
+留坝县
+佛坪县
+榆林市
+榆阳区
+横山区
+府谷县
+靖边县
+定边县
+绥德县
+米脂县
+佳县
+吴堡县
+清涧县
+子洲县
+神木市
+安康市
+汉滨区
+汉阴县
+石泉县
+宁陕县
+紫阳县
+岚皋县
+平利县
+镇坪县
+旬阳县
+白河县
+商洛市
+商州区
+洛南县
+丹凤县
+商南县
+山阳县
+镇安县
+柞水县
+甘肃
+兰州市
+七里河区
+西固区
+安宁区
+红古区
+永登县
+皋兰县
+榆中县
+兰州新区
+嘉峪关市
+金昌市
+金川区
+永昌县
+白银市
+白银区
+平川区
+靖远县
+会宁县
+景泰县
+天水市
+秦州区
+麦积区
+清水县
+秦安县
+甘谷县
+武山县
+张家川回族自治县
+武威市
+凉州区
+民勤县
+古浪县
+天祝藏族自治县
+张掖市
+甘州区
+肃南裕固族自治县
+民乐县
+临泽县
+高台县
+山丹县
+平凉市
+崆峒区
+泾川县
+灵台县
+崇信县
+庄浪县
+静宁县
+华亭市
+酒泉市
+肃州区
+金塔县
+瓜州县
+肃北蒙古族自治县
+阿克塞哈萨克族自治县
+玉门市
+敦煌市
+庆阳市
+西峰区
+庆城县
+环县
+华池县
+合水县
+正宁县
+宁县
+镇原县
+定西市
+安定区
+通渭县
+陇西县
+渭源县
+临洮县
+漳县
+岷县
+陇南市
+武都区
+成县
+文县
+宕昌县
+康县
+西和县
+礼县
+徽县
+两当县
+临夏回族自治州
+临夏市
+临夏县
+康乐县
+永靖县
+广河县
+和政县
+东乡族自治县
+积石山保安族东乡族撒拉族自治县
+甘南藏族自治州
+合作市
+临潭县
+卓尼县
+舟曲县
+迭部县
+玛曲县
+碌曲县
+夏河县
+青海
+西宁市
+城东区
+城西区
+城北区
+湟中区
+大通回族土族自治县
+湟源县
+海东市
+乐都区
+平安区
+民和回族土族自治县
+互助土族自治县
+化隆回族自治县
+循化撒拉族自治县
+海北藏族自治州
+门源回族自治县
+祁连县
+海晏县
+刚察县
+黄南藏族自治州
+同仁县
+尖扎县
+泽库县
+河南蒙古族自治县
+海南藏族自治州
+共和县
+同德县
+贵德县
+兴海县
+贵南县
+果洛藏族自治州
+玛沁县
+班玛县
+甘德县
+达日县
+久治县
+玛多县
+玉树藏族自治州
+玉树市
+杂多县
+称多县
+治多县
+囊谦县
+曲麻莱县
+海西蒙古族藏族自治州
+格尔木市
+德令哈市
+茫崖市
+乌兰县
+都兰县
+天峻县
+大柴旦行政委员会
+宁夏
+银川市
+兴庆区
+西夏区
+金凤区
+永宁县
+贺兰县
+灵武市
+石嘴山市
+大武口区
+惠农区
+平罗县
+吴忠市
+利通区
+红寺堡区
+盐池县
+同心县
+青铜峡市
+固原市
+原州区
+西吉县
+隆德县
+泾源县
+彭阳县
+中卫市
+沙坡头区
+中宁县
+海原县
+新疆
+乌鲁木齐市
+天山区
+沙依巴克区
+新市区
+水磨沟区
+头屯河区
+达坂城区
+米东区
+乌鲁木齐县
+克拉玛依市
+独山子区
+克拉玛依区
+白碱滩区
+乌尔禾区
+吐鲁番市
+高昌区
+鄯善县
+托克逊县
+哈密市
+伊州区
+巴里坤哈萨克自治县
+伊吾县
+昌吉回族自治州
+昌吉市
+阜康市
+呼图壁县
+玛纳斯县
+奇台县
+吉木萨尔县
+木垒哈萨克自治县
+博尔塔拉蒙古自治州
+博乐市
+阿拉山口市
+精河县
+温泉县
+巴音郭楞蒙古自治州
+库尔勒市
+轮台县
+尉犁县
+若羌县
+且末县
+焉耆回族自治县
+和静县
+和硕县
+博湖县
+库尔勒经济技术开发区
+阿克苏地区
+阿克苏市
+库车市
+温宿县
+沙雅县
+新和县
+拜城县
+乌什县
+阿瓦提县
+柯坪县
+克孜勒苏柯尔克孜自治州
+阿图什市
+阿克陶县
+阿合奇县
+乌恰县
+喀什地区
+喀什市
+疏附县
+疏勒县
+英吉沙县
+泽普县
+莎车县
+叶城县
+麦盖提县
+岳普湖县
+伽师县
+巴楚县
+塔什库尔干塔吉克自治县
+和田地区
+和田市
+和田县
+墨玉县
+皮山县
+洛浦县
+策勒县
+于田县
+民丰县
+伊犁哈萨克自治州
+伊宁市
+奎屯市
+霍尔果斯市
+伊宁县
+察布查尔锡伯自治县
+霍城县
+巩留县
+新源县
+昭苏县
+特克斯县
+尼勒克县
+塔城地区
+塔城市
+乌苏市
+额敏县
+沙湾县
+托里县
+裕民县
+和布克赛尔蒙古自治县
+阿勒泰地区
+阿勒泰市
+布尔津县
+富蕴县
+福海县
+哈巴河县
+青河县
+吉木乃县
+石河子市
+阿拉尔市
+图木舒克市
+五家渠市
+北屯市
+铁门关市
+双河市
+可克达拉市
+昆玉市
+胡杨河市
+应急指挥中心
+国家森林草原防灭火指挥部
+国家森林防火指挥部
+国家防汛抗旱总指挥部
+防汛抗旱司
+抗震救灾指挥部
+国家减灾委员会
+武装警察部队森林部队
+武警森林部队
+武警森林指挥部
+煤矿安全监察局
+森林消防局
+应急管理部
+应急救援大队
+风险监测和综合减灾司
+救援协调和预案管理局
+火灾防治管理司
+地震和地质灾害救援司
+危险化学品安全监督管理司
+安全生产基础司
+安全生产执法局
+安全生产综合协调司
+救灾和物资保障司
+国际合作和救援司
+国家安全生产应急救援中心
+地震预测研究所
+应急通信保障大队
+应急车辆勤务大队
+矿山救援中心
+防灾科技学院
+防震减灾科技交流培训中心
+防震减灾科普教育基地
+森林草原防火保障中心
+草原防火指挥部
+草原防火办公室
+森林草原防火办
+森林草原防火预警监测中心
+草原防火指挥中心
+森林草原防火站
+森林草原防扑火专业队
+森林草原防火办办公室
+森林草原防火中标办公室
+森林草原防火指挥办公室
+森林草原防火司
+城市应急联动中心
+消防总队
+消防支队
+消防大队
+消防办公室
+消防局
+消防队
+消防救援局
+消防中队
+减灾救灾应急指挥中心
+救灾减灾中心
+减灾救灾中心
+救灾物资储备站
+救助安置中心
+社会救助安置中心
+救灾物资仓储中心
+减灾备灾中心
+救灾物资储备中心
+救助站
+救灾服务中心
+森林草原防火办公室
+森林防火预警
+地质灾害防治中心
+抗旱
+防洪排涝管理站
+排涝站
+地震应急搜救中心
+地震局
+地震台
+地震灾害防御中心
+地震监测中心
+地震勘测研究中心
+地震
+防震办公室
+防震减灾局
+防震减灾办公室
+安全生产监督管理总局
+安全生产监督管理局
+安监局
+煤矿安全生产
+安全生产监督管理
+安全生产宣传教育中心
+安全生产应急救援指挥中心
+煤炭生产安全管理局
+安全生产监督
+安全监督管理局
+安全生产监管局
+安全生产监督执法支队
+安全生产执法监察总队
+安全生产执法总队
+安全监察执法大队
+安全生产监察执法支队
+应急办
+应急管理办公室
+应急管理厅
+应急管理局
+移民管理局
+外国人管理司
+移民事务服务中心
+移民局
+公民出入境管理司
+边防检查管理司
+出入境管理信息技术研究所
+移民事务国际合作司
+经济犯罪侦查大队
+社会组织管理局
+社会组织执法监督局
+社会组织服务中心
+社会救助司
+社会救助中心
+社会救助事业局
+社会救助管理服务中心
+社会救助工作管理局
+社会救助福利服务中心
+社会救助管理中心
+社会救助办公室
+社会救助服务中心
+社会救助事业中心
+社会救助家庭经济状况核对中心
+社会救助家庭经济状况信息核对中心
+社会救助管理办公室
+社会救助处
+社会救助帮扶中心
+社会救助办
+社区治理司
+区划地名司
+区划地名办公室
+区划地名管理办公室
+区划地名管理处
+区划地名服务中心
+社会事务司
+生活无着人员救助管理处
+婚姻收养管理处
+养老服务司
+儿童福利司
+儿童福利指导中心
+中国儿童福利和收养中心
+儿童福利中心
+慈善事业促进和社会工作司
+慈善事业促进办公室
+慈善事业发展中心
+慈善事业和志愿服务促进中心
+慈善事业发展服务中心
+慈善事业服务指导中心
+彩票发行中心
+社会福利有奖募捐彩票发行中心
+社会工作司
+社会捐助处
+社会捐助工作中心
+社会捐助接收站
+社会捐助事务中心
+社会捐助事务管理中心
+社会捐助物资管理服务中心
+社会捐助接收工作站
+社会捐助接收管理服务中心
+社会捐助接受工作站
+社会捐助中心
+社会捐助和慈善事务管理中心
+儿童救助中心
+未成年人留守儿童保护处
+未成年人留守儿童社会保护中心
+婚姻登记管理处
+婚姻服务中心
+婚姻登记管理中心
+婚姻管理所
+涉外婚姻收养登记服务中心
+海峡两岸婚姻家庭服务中心
+婚姻收养登记中心
+婚姻登记管理所
+婚姻登记管理服务中心
+婚姻事务所
+婚姻家庭研究所
+婚姻中心
+婚姻登记所
+婚姻家庭纠纷调解委员会
+婚姻登记办公室
+城乡最低生活保障服务中心
+法制调研局
+法制督察局
+监狱管理局
+社区矫正管理局
+社区矫正工作管理局
+社区矫正中心
+戒毒康复矫正服务中心
+司法矫正中心
+社区矫正服务中心
+社区矫正指挥中心
+社区矫正管理支队
+社区矫正支队
+矫正监管指挥中心
+社区矫正执法大队
+社区矫正局
+社区矫正管理教育中心
+社区矫正管理中心
+矫正帮教管理服务中心
+社区矫正工作办公室
+矫正帮教中心
+司法矫正局
+行政复议局
+行政复议事务中心
+人民参与和促进法治局
+人民参与和促进法治处
+公共法律服务中心
+公共法律服务管理局
+公共法律服务协调指挥中心
+律师工作局
+法律职业资格管理局
+法制宣传中心
+燕城监狱
+法律援助中心
+法律援助处
+直属煤矿管理局
+预防犯罪研究所
+法制日报社
+法律出版社有限公司
+中国法制出版社
+全国律师协会
+中国公证协会
+中国监狱工作协会
+中国司法行政戒毒工作协会
+中央司法警官学院
+司法行政学院
+司法考试中心
+法律援助基金会
+司法协助交流中心
+司法鉴定科学研究院
+中国法律服务香港公司
+中国法律服务澳门公司
+全国人民调解员协会
+关税政策研究中心
+国有金融资本运营评价中心
+世界银行贷款项目评估中心
+财政票据监管中心
+中国财政科学研究院
+中国财政杂志社
+中国财经报社
+预算评审中心
+会计资格评价中心
+政府债务管理中心
+会计准则委员会
+注册会计师协会
+中国资产评估协会
+国家会计学院
+中国财税博物馆
+国际财经中心
+中国财政学会
+中国会计学会
+全国预算与会计研究会
+中国农村财经研究会
+中国珠算心算协会
+世界珠算心算联合会
+中国国债协会
+中国财经出版传媒集团
+政府采购监管处
+政府采购监管办公室
+政府采购监管支队
+财政政务后勤服务中心
+公务员测评中心
+公务员考试测评中心
+公务员培训中心
+就业促进司
+就业促进工作办公室
+人力资源规划处
+人力资源和社会保障所
+人力资源和社会保障事务服务中心
+人力资源开发培训中心
+公共行政与人力资源研究所
+人力资源鉴定考试院
+人力资源配置中心
+人力资源职业能力建设中心
+人力资源考试院
+人力资源考试测评服务中心
+人力资源考试和鉴定中心
+人力资源管理服务中心
+人力资源管理局
+人力资源管理协会
+人力资源管理中心
+人力资源社会保障能力建设中心
+人力资源社会保障综合培训指导中心
+人力资源社会保障管理服务中心
+人力资源社会保障稽核监督局
+人力资源社会保障电话咨询服务中心
+人力资源社会保障服务所
+人力资源社会保障所
+人力资源社会保障培训指导中心
+人力资源服务管理办公室
+人力资源服务管理中心
+人力资源开发管理服务中心
+人力资源开发管理中心
+人力资源开发服务管理办公室
+人力资源开发服务中心
+人力资源开发办公室
+人力资源开发交流服务中心
+人力资源培训考试中心
+人力资源培训指导中心
+人力资源培训中心
+人力资源和社会保障资金监督检查所
+人力资源和社会保障综合执法大队
+人力资源和社会保障研究所
+人力资源和社会保障监察支队
+人力资源和社会保障监察大队
+人力资源和社会保障电话咨询中心
+人力资源和社会保障服务所
+人力资源和社会保障教育中心
+人力资源和社会保障宣传教育科研中心
+人力资源和社会保障宣传教育中心
+人力资源和社会保障宣传中心
+人力资源和社会保障培训考试中心
+人力资源和社会保障培训教育指导中心
+人力资源和社会保障培训中心
+人力资源和社会保障咨询服务中心
+人力资源和社会保障劳动监察支队
+人力资源和社会保障公共服务与技术保障中心
+人力资源和社会保障信访举报中心
+人力资源和社会保障信息化建设办公室
+人力资源和社会保障事务中心
+人力资源和就业事务中心
+人力资源产业园管理办公室
+人力资源交流服务中心
+人力资源流动管理司
+职业能力建设司
+职业能力建设指导中心
+职业能力建设中心
+事业单位人事管理处
+事业单位人事管理司
+事业单位人事管理科
+农民工工作司
+农民工工作领导小组
+农民工工作联席会议办公室
+专业技术人员管理司
+专业技术人员管理处
+专业技术人员管理服务中心
+劳动关系协调中心
+劳动关系司
+工资福利司
+工资福利中心
+养老保险司
+失业保险司
+工伤保险司
+农村社会保险司
+社会保险基金监管局
+劳动人事争议调解仲裁院
+调解仲裁管理司
+劳动监察局
+劳动监察支队
+劳动监察处
+劳动监察中队
+劳动监察执法队
+国家表彰奖励办公室
+社会保险事业管理中心
+养老保险管理中心
+就业培训技术指导中心
+职业技能鉴定中心
+中国人事科学研究院
+劳动和社会保障科学研究院
+全国人才流动中心
+中国人事报刊社
+中国劳动保障报社
+人力资源和社会保障出版集团
+社会保险费征缴管理中心
+社会保险基金管理局
+社会保险基金管理中心
+社会养老保险基金管理中心
+社会保障基金稽核中心
+社会保险局
+社会保险基金结算稽核局
+社会保险基金结算中心
+社会保险基金经办中心
+社会保险基金管理结算中心
+社会保险基金管理大鹏分局
+社会保险基金管理办公室
+社会保险基金征缴管理中心
+社会保险基金征缴处
+社会保险基金征缴和信息中心
+社会保险基金征缴信息管理中心
+社会保险基金征缴中心
+社会保险基金征收稽查局
+社会保险基金中心
+新型农村养老保险基金管理中心
+工伤保险基金管理中心
+就业基金监管中心
+失业保险基金管理办公室
+失业保险基金管理中心
+养老保险基金管理服务中心
+养老保险基金管理中心
+就业服务中心
+存量房交易价格评估工作领导小组办公室
+建筑工程安全生产与质量监督总站
+住房改革委员会
+住房改革与发展司
+住房保障司
+住房保障建设中心
+住房保障工作领导小组
+住房保障服务站
+住房保障和房地产中心
+住房保障与房屋征收局
+住房保障办
+住房保障处
+住房保障和房地产管理局
+住房保障和房地产管理中心
+住房保障和城乡建设服务中心
+住房保障和城乡建设管理局
+住房保障和建设中心
+住房保障发展中心
+房地产市场监管司
+房地产管理站
+房地产管理监察支队
+房地产管理交易处
+房地产管理事业局
+房地产监理所
+房地产监察支队
+房地产监察大队
+房地产登记所
+房地产登记中心
+房地产档案室
+房地产执法监察大队
+房地产开发事务中心
+房地产市场监察大队
+房地产市场产权监理所
+房地产勘察测绘所
+房地产信息中心
+房地产估价所
+房地产产权监理所
+房地产产权监理处
+房地产产权登记所
+房地产产权产籍监理处
+房地产交易资金监管中心
+房地产交易管理事务中心
+房地产交易监理所
+房地产交易监理处
+房地产交易登记所
+房地产交易事务中心
+房地产交易与权属登记中心
+房地产交易与房屋权属登记发证处
+房地产交易与房屋权属登记发证中心
+房地产事务中心
+房地产事业发展中心
+建筑市场监管司
+建筑市场监督管理处
+建筑市场管理站
+城市建设司
+村镇建设司
+工程质量安全监管司
+工程质量安全监管中心
+建筑节能与科技司
+住房公积金监管司
+住房公积金
+城市管理监督局
+建筑行业劳动保险基金统筹管理办公室
+住宅维修基金管理中心
+路灯管理所
+园林绿化科研中心
+高速公路管理局
+邮政局
+交通运行监测调度中心
+运输监督管理司
+运输监督管理办公室
+运输监管处
+客运监管处
+货运监管处
+民用航空局
+航空安全办公室
+飞行标准司
+航空器适航审定中心
+航空器适航审定司
+全国民航公会
+中国民用航空
+中国民航大学
+中国民航飞行学院
+中国民航管理干部学院
+中国民航科学技术研究院
+中国民航报社出版社
+中国民航
+首都机场
+民航专业工程质量监督总站
+民航博物馆
+水运局
+运输服务司
+海上搜救中心
+救助打捞局
+中国船级社
+中国交通通信信息中心
+公路科学研究所
+公路科学研究院
+水运科学研究所
+水运科学研究院
+天津水运工程科学研究所
+天津水运工程科学研究院
+大连海事大学
+中国交通书画协会
+中国公路建设行业协会
+中国船东协会
+中国港口协会
+中国水运建设行业协会
+中国船舶代理及无船承运人协会
+中国道路运输协会
+中国交通建设监理协会
+中国交通会计学会
+中国交通教育研究会
+郑和研究会
+海峡两岸航运交流协会
+渔船检验处
+渔船检验局
+水利安全生产监管站
+水资源保护局
+水利水保局
+水源保护办公室
+地下水管理处
+地下水管理监测局
+地下水管理监测站
+节约用水办公室
+节约用水管理中心
+节约用水管理办公室
+节约用水事务管理中心
+节水管理处
+节水管理办公室
+水利工程建设司
+水利工程建设管理局
+水利工程建设办公室
+水利工程建设管理站
+水利工程建设管理中心
+水利工程建设项目管理处
+水利工程建设中心
+水利工程建设质量与安全监督中心
+水利工程建设质量与安全中心
+水利工程建设管护中心
+水利工程建设工作站
+水利工程建设运维中心
+水利工程建设工作队
+水利工程建设所
+水利工程建设发展中心
+水利工程建设管理服务中心
+水利工程建设技术审核中心
+水利工程建设质量与安全监督中心站
+水库管理处
+水库管理服务中心
+水库管理养护所
+河道堤防建设管理局
+河流堤防管理总段
+堤防管理段
+堤防管理总段
+堤防管护中心
+水闸管理署
+堤防所
+堤防建设管护中心
+堤防工程管理总段
+堤防工程服务保障中心
+堤防局
+堤防养护所
+堤防事务所
+水闸水电管养所
+河湖管理司
+河湖管理处
+河湖管理总站
+河湖管理事务中心
+河湖长制工作处
+河湖长制办公室
+河湖长制工作科
+河湖长制事务中心
+河湖长制调度指挥中心
+河湖长制工作领导小组办公室
+水域岸线管理处
+河道采砂管理局
+河道采砂管理站
+河道采砂管理处
+河道采砂服务中心
+河道采砂整治办
+水土保持司
+水土保持监测站
+水土保持局
+水土保持预防监督检测站
+水土保持预防监督检查站
+水土保持试验推广站
+水土保持研究所
+水土保持监管站
+水土保持监督检查站
+水土保持监督执法站
+水土保持监督局
+水土保持监察站
+水土保持生态环境监督检查站
+水土保持生态环境建设中心
+水土保持治理监督局
+水土保持服务中心
+水土保持指导站
+水土保持技术推广站
+水土保持技术咨询研究中心
+水土保持所
+水土保持总站
+水土保持工作队
+水土保持工作中心
+水土保持实验站
+水土保持委员会
+水土保持和移民工作中心
+水土保持和水利规划室
+水土保持勘测规划研究所
+水土保持中心
+农水技术和水土保持监测指导站
+农村水利管理处
+农村水利水电司
+农村水利水电建设服务中心
+农村水利管理服务总站
+农村水利科技推广站
+农村水利科技发展中心
+农村水利水电服务中心
+农村水利建设指导工作站
+农村水利中心
+农村水利与河湖管理服务处
+农业农村水利项目管理服务中心
+农村水电管理处
+灌溉节水处
+农村供水处
+农村供水管理服务站
+农村供水管理总站
+农村供水服务站
+农村供水排水站
+农村供水总站
+农村供水工程管理服务站
+农村供水工程所
+农村供水安全中心
+农村供水中心
+农村水能开发处
+水旱灾害防御司
+水旱灾害防御调度指挥中心
+水旱灾害防御站
+水旱灾害防御物资储备中心
+水旱灾害防御服务中心
+水旱灾害防御处
+水旱灾害防御事务中心
+水旱灾害防御中心
+水资源监测处
+水质监测处
+地下水监测处
+水质监测分中心
+水质监测中心
+水资源监测预警中心
+水资源监测局
+水环境监测分中心
+水环境监测中心
+水资源监测规划所
+地下水资源监测控制中心
+地下水监测中心
+三峡工程管理司
+南水北调工程管理司
+调水管理司
+调水管理中心
+调水管理局
+调水管理所
+水资源调度中心
+水资源调度管理局
+水资源调度处
+调水规划处
+调水工程处
+太湖流域管理局
+水利水电科学研究院
+中国水利报社
+中国水利水电出版社
+中国灌溉排水发展中心
+南水北调工程设计管理中心
+南水北调中线干线工程建设管理局
+南水北调东线总公司
+中国水利学会
+南京水利科学研究院
+国际小水电中心
+中国水利博物馆
+农田灌溉实验中心
+农业农村办公室
+乡村镇兴办
+乡村振兴战略领导小组办公室
+乡村振兴建设办公室
+动物卫生监督所
+动物卫生与流行病学中心
+农畜产品质量安全检验检测监督管理站
+农畜产品安全检验检测监督管理站
+海洋经济运行监测与评估中心
+农业农村部
+农业农村局
+农业农村厅
+乡村产业发展司
+农村社会事业促进司
+农村合作经济指导服务中心
+农村合作经济指导司
+农村合作经济经营中心
+农村合作经济经营服务中心
+农村合作经济经营指导总站
+农村合作经济管理指导中心
+农村合作经济管理局
+农村合作经济服务中心
+农村合作经济经营管理局
+农村合作经济经营管理处
+农业转基因生物安全管理处
+农业转基因生物安全管理办公室
+农业农村委员会
+农产品质量监督管理局
+农产品质量监督检验测试中心
+农产品质量监督检验检测站
+农产品质量监测站
+农产品质量监测所
+农产品质量安全综合质检站
+农产品质量安全管理服务中心
+农产品质量安全管理局
+农产品质量安全监管站
+农产品质量安全监管局
+农产品质量安全监督管理总站
+农产品质量安全监督检验监测站
+农产品质量安全监督检验测试站
+农产品质量安全监督检测信息站
+农产品质量安全监测站
+农产品质量安全检验监测站
+农产品质量安全检验测试中心
+农产品质量安全办公室
+农产品质量与农业环境保护站
+种植业管理司
+种植业管理总站
+农情信息处
+农产品质量安全监管处
+经济作物试验推广站
+经济作物管理站
+经济作物管理服务中心
+经济作物管理局
+经济作物站
+经济作物推广站
+经济作物推广中心
+经济作物指导中心
+经济作物技术推广总站
+经济作物技术推广中心
+经济作物技术指导站
+经济作物总站
+经济作物工作站
+经济作物工作指导站
+经济作物局
+特种经济作物总站
+农业经济作物推广站
+植保植检处
+国际植物保护公约履约办公室
+植保植检管理站
+植保植检工作站
+植保植检服务中心
+植保植检总站
+植保植检局
+植保植检所
+植保植检和种子土肥环保管理站
+植保植检土肥中心
+农药管理处
+农药检定所
+生物农药工程研究中心
+农药技术推广服务中心
+农药检定管理总站
+农药管理检定所
+生物农药工程技术研究中心
+农药总站
+农药检定站
+农药检定监测总站
+农药检定管理所
+农药风险监测中心
+农药管理站
+农药检定管理站
+农药管理检定站
+农药监督管理所
+农药技术推广总站
+农药综合执法大队
+农药委员会
+肥料与节水处
+土襄肥料和节水农业工作站
+肥料工作总站
+肥料管理局
+肥料管理站
+耕地肥料总站
+耕地质量保护与肥料工作站
+耕地质量保护与肥料管理局
+耕地质量与肥料工作站
+耕地质量与肥料管理总站
+农业节水与土壤肥料管理总站
+土壤肥料测试中心
+土壤肥料工作总站
+土壤肥料管理站
+土壤肥料和节水农业工作站
+土壤肥料技术推广站
+土壤肥料技术指导站
+土壤肥料技术总站
+土壤肥料调查测试中心
+土壤肥料与资源环境工作站
+土壤肥料与资源环境站
+土壤肥料专业站
+土壤肥料总站
+土攘肥料工作站
+畜牧兽医局
+饲料饲草处
+畜禽废弃物利用处
+畜禽粪污资源化利用办公室
+畜禽粪污资源化利用集中处理中心
+渔业渔政管理局
+渔船渔港处
+远洋渔业处
+渔政处
+农垦局
+农垦总局
+热带作物处
+种业管理司
+农作物种业中心
+农作物种业处
+畜禽种业处
+农业机械化管理司
+农业机械化管理处
+农业机械化管理办公室
+农业机械化管理中心
+农业机械化管理服务站
+农田建设管理司
+高标准农田建设管理办公室
+耕地质量与肥料工作总站
+耕地质量与肥料管理局
+耕地质量建设保护总站
+长江流域渔政监督管理办公室
+长渔委
+珠渔委
+农业科学院
+水产科学研究院
+热带农业科学院
+全国农业展览馆
+中国农业博物馆
+中国农业电影电视中心
+农民日报社
+农业出版社
+农村读物出版社
+农村杂志社
+农业干部教育培训中心
+农科院
+水产技术推广总站
+水产学会
+农业科教基金会
+联盟农业技术中心
+农业行业分会
+国际商会农业行业分会
+中国饲料工业协会
+农垦经济发展中心
+农业广播电视学校
+农民体育协会
+全国农业技术推广服务中心
+国际食品法典农药残留委员会秘书处
+全国畜牧总站
+蜜蜂研究所
+饲料研究所
+农产品加工研究所
+生物技术研究所
+农业质量标准与检测技术研究所
+中国农业科学技术出版社
+农学会
+渔业渔政办公室
+商贸流通运行监测中心
+市场体系建设司
+消费促进司
+对外贸易司
+外贸司
+服贸司
+服务贸易司
+产业安全与进出口管制局
+世界贸易组织事务中心
+外贸发展局
+投资促进事务局
+国际经济合作事务局
+国际贸易经济合作研究院
+商务出版社
+国际经济技术交流中心
+配额许可证事务局
+国际商报社
+中国对外贸易中心
+国际电子商务中心
+流通产业促进中心
+中国服务外包研究中心
+中国国际进口博览局
+中国医药保健品进出口商会
+中国五矿化工进出口商会
+中国食品土畜进出口商会
+中国藏毯协会
+中国欧洲经济技术合作协会
+中国国际货运代理协会
+中国国际跨国公司促进会
+中国国际工程咨询协会
+中国外商投资企业协会
+中俄机电商会
+国际烟花协会
+中国国际民间组织合作促进会
+海峡两岸经贸交流协会
+中国国际贸易学会
+中国对外经济贸易会计学会
+中国国际经济合作学会
+中国对外经济贸易统计学会
+中国世界贸易组织研究会
+中国会展经济研究会
+华侨茶业发展研究基金会
+中华茶人联谊会
+全国银行间同业拆借中心
+中国金融出版社
+金融时报社
+中国印钞造币总公司
+中国金币总公司
+中国金融电子化公司
+中国金融培训中心
+中国钱币博物馆
+货币政策司
+宏观审慎管理局
+金融市场司
+金融稳定局
+征信管理局
+反洗钱局
+反洗钱中心
+金融消费权益保护协会
+金融消费权益保护局
+审计博物馆
+中国审计报社
+中国时代经济出版社
+原中国审计出版社
+审计宣传中心
+中国审计学会
+国有资产监督管理委员会
+国资委
+国资局
+国有资产管理办公室
+国有资产管理局
+国资办
+国有资产监督管理局
+国有资金资产监督管理局
+国有资产监督管理办公室
+国企绩效评价中心
+出入境检验检疫局
+进出口食品安全局
+口岸监管司
+口岸委员会
+打击走私综合治理办公室
+社会保险费司
+非税收入司
+纳税服务司
+纳税服务局
+纳税服务中心
+电子税务管理中心
+税收科学研究所
+税收宣传中心
+税务干部进修学院
+中国税务报社
+税务出版社
+广播电视总局
+电视剧司
+传媒机构管理司
+网络视听节目管理司
+媒体融合发展司
+电视剧管理处
+传媒机构管理处
+网络视听节目管理处
+电视剧管理局
+传媒机构管理局
+网络视听节目管理局
+媒体融合发展处
+媒体融合发展局
+广电分局
+乌兰夫基金会办公室
+电影频道节目中心
+青少年体育俱乐部
+体育经济司
+体育彩票管理中心
+体育彩票中心
+体育彩票管理分中心
+体育彩票发行中心
+体育彩票销售管理中心
+体育彩票管理发行中心
+体彩中心
+体育彩票肇庆管理分中心
+体育彩票彩发行中心
+体育彩票销售中心
+体育彩票工作站
+体育彩票经营管理中心
+健身气功管理中心
+奥林匹克体育中心
+体育信息中心
+体育基金管理中心
+青岛航海运动学校
+运动医学研究所
+对外体育交流中心
+体育报业总社
+湛江潜水运动学校
+反兴奋剂中心
+社会体育指导中心
+安阳航空运动学校
+北京体育大学
+体育器材装备中心
+体育文化发展中心
+国民经济核算司
+统计司
+服务业调查中心
+普查中心
+国际统计信息中心
+统计教育培训中心
+中国国际统计培训中心
+统计科学研究所
+统计资料管理中心
+
+统计信息服务中心
+中国经济景气监测中心
+中国信息报社
+中国统计师事务所
+中国统计出版社
+国管局
+机关事务管理局
+行政事务管理局
+机关住房服务中心
+机关事务服务中心
+机关后勤服务中心
+机关大院服务中心
+干部疗养院
+机关物业管理中心
+机关车队
+机关后勤管理处
+行政事务管理中心
+机关服务管理中心
+机关后勤管理
+人民大会堂管理局
+人民大会常务委员会
+人民代表大会
+归国华侨联合会
+中国民主同盟
+无党派人士
+中国共产主义青年团
+共青团
+中华全国总工会
+中华全国妇女联合会
+全国妇联
+中华全国青年联合会
+全国青联
+中国科学技术协会
+中华全国台湾同胞联谊会
+全国台联
+人民政协报
+中国文史出版社
+中国政协杂志社
+中国政协文史馆
+中国人民政协理论研究会
+中国宗教界和平委员会
+法院
+军队离退干部
+军队离退干休所
+军队离退休管理所
+军队离退休服务管理中心
+军队离退休服务中心
+军队离退休人员
+军队离休退休
+转业军官管理服务中心
+军队离休干部
+军队干部退休所
+军队干部离休退休
+军队干休所
+军队转业
+自主择业军队干部管理中心
+自主择业转业干部管理服务中心
+自主择业转业军官管理办公室
+自主择业军转干部管理服务中心
+自主择业军转干部服务中心
+军官培训与自主择业管理办公室
+军休服务管理司
+拥军优属拥政爱民领导小组办公室
+拥军优属拥政爱民工作领导小组
+拥军优属拥政爱民工作领导组
+东部战区
+西部战区
+南部战区
+北部战区
+中部战区
+陆军
+海军
+火箭军
+国防部
+国防工业出版社
+中国国防报
+中国军网
+人武部
+民兵训练基地
+民兵装备修理所
+民兵综合训练基地
+民兵武器装备仓库
+民兵军事训练基地
+医疗保障局
+医保局
+医疗保障事务服务中心
+农村合作医疗管理中心
+农村合作医疗管理办公室
+农村合作医疗办公室
+农村合作医疗服务中心
+农村合作医疗监督管理局
+农村合作医疗监督管理办公室农村合作医疗经办中心
+农村合作医疗管理服务中心
+农村合作医疗工作领导小组
+农村合作医疗工作办公室
+农村合作医疗管理局
+医疗保障管理局
+医疗保障基金结算中心
+医疗保障基金管理中心
+医疗保障基金监督检查所
+医疗保障基金中心
+医疗保险基金结算中心
+医疗保险基金经办中心
+医疗保险基金管理结算中心
+医疗保险基金管理服务中心
+医疗保险基金管理局
+医疗保险基金管理处
+医疗保险基金管理中心
+学校
+大学
+中学
+小学
+幼儿园
+师专
+中心校
+高中
+总校
+分校
+校区
+电大
+实验高中
+职校
+聋校
+体校
+中专
+职专
+一中
+联校
+教育后勤办公室
+二中
+军校
+学院
+气象中心
+中央气象台
+空间天气监测预警中心
+气象信息中心
+公共气象服务中心
+国家预警信息发布中心
+气象科学研究院
+气象干部培训学院
+气象宣传与科普中心
+气象报社
+气象出版社
+气象学会
+中国华云气象科技集团公司
+华风气象传媒集团有限责任公司
+证监局
+证监会
+证券监督管理委员会
+公共资源交易
+采购中心
+采购监督管理办公室
+采购办
+采购管理办公室
+公共资源交易服务中心
+公共资源管理交易中心
+公共资源交易所
+招标中心
+招标采购管理中心
+政府采购和出让中心
+建设工程交易中心
+招投标管理中心
+产权交易所
+商品交易所
+政府采购部
+城市管理
+救助管理站
+城市综合管理局
+城市监察管理局
+城市综合管理办公室
+综合执法局
+城市综合管理中心
+救助服务站
+行政执法局
+公园建设管理中心
+综合管理和执法局
+城市建设管理处
+城市综合管理事务中心
+城市网格化综合管理中心
+城镇管理局
+城市综合管理总站
+城乡管理执法局
+综合行政执法支队
+城市管理办公室
+智慧城市运行管理中心
+城区管理办公室
+城市执法管理局
+执法局
+城市监督管理中心
+城市运行监测中心
+环卫所
+市政维护处
+路灯管理处
+市政管理处
+市政服务中心
+市容管理局
+垃圾综合处理场
+市政工程管理处
+城环局
+环境卫生管理局
+亮灯监管中心
+市政管理局
+市政建设管理处
+市政园林工程处
+市政工程处
+市政公用局
+广场管理处
+市政设施管理处
+市政园林管理局
+市政设施管理局
+市政管理所
+公用事业管理中心
+市政设施维护处
+市政配套服务中心
+环境卫生管理所
+市容环卫局
+市政绿化处
+市政工程建设处
+市政工程管理所
+环境卫生管理处
+环卫大队
+城市道路养护管理中心
+公用事业局
+市政工程建设中心
+环境卫生清洁中心
+环卫中心
+城市综合管理处
+公用事业办公室
+市政热线服务中心
+绿化维护队
+地下管线管理
+环卫处
+公用事业管理
+爱国卫生运动委员会
+市政维护应急中心
+社会事业服务中心
+市政园林水利管护中心
+市政园林配套中心
+城区水系污染综合治理工程指挥部
+市政工程局
+档案资料保管部
+档案资料利用部
+历史档案馆
+中国档案报社
+档案科学技术研究所
+档案干部教育中心
+档案杂志社
+档案学会
+档案登记备份中心
+档案修志馆
+地方志编修委员会
+妇幼联合会
+性病艾滋病防治协会
+中国营养学会
+科学技术学会
+肝炎防治基金会
+公共卫生管理协会
+烟草学会
+职业安全健康协会
+反邪教协会
+全国学生联合会
+侨商投资企业协会
+全国学联
+中国汽车工业协会
+中国钢铁工业协会
+中国互联网协会
+矿业协会
+书法协会
+老龄协会
+纺织协会
+兰花协会
+林学会
+水利学会
+水利工程学会
+归侨侨眷联合会
+基金会
+慈善协会
+慈善总会
+红十字扶贫开发服务中心
+中远渔业推广示范中心
+中国道路交通安全协会
+中国造纸学会
+中国轻工业联合会
+中国质量协会
+中国计算机行业协会
+中国统计学会
+中国经济体制改革研究会
+中国纺织工业联合会
+中国粮油学会
+中国硅酸盐学会
+中国矿业联合会
+中国知识产权研究会
+中国电石工业协会
+中国电工技术学会
+中国生态文明研究与促进会
+中国环境保护产业协会
+中国物流与采购联合会
+中国煤炭工业协会
+中国渔业互保协会
+中国水泥协会
+中国水利工程协会
+中国水产学会
+中国民族卫生协会
+中国机械工程学会
+中国机械工业联合会
+中国服装设计师协会
+中国有色金属工业协会
+中国支付清算协会
+中国报关协会
+中国扶贫开发协会
+中国建筑材料联合会
+中国康复辅助器具协会
+中国工程咨询协会
+中国密码学会
+中国安全生产协会
+中国天然橡胶协会
+中国地质学会
+中国土地估价师与土地登记代理人协会
+中国国际经济交流中心
+中国发明协会
+中国卫生监督协会
+中国医学装备协会
+中国出版协会
+中国冶金教育学会
+中国农垦经贸流通协会
+中国农业生产资料流通协会
+中国企业国有产权交易机构协会
+中国人才研究会
+中华护理学会
+中华全国专利代理人协会
+世界中医药学会联合会
+中国青少年宫协会
+中国银行间市场交易商协会
+中国银行业协会
+中国证券业协会
+中国航空运输协会
+中国绿化基金会
+中国红十字基金会
+中国神经科学学会
+中国电机工程学会
+中国注册会计师协会
+中国木材与木制品流通协会
+中国期货业协会
+中国服装协会
+中国文学艺术基金会
+中国招标投标协会
+中国抗癌协会
+中国投资协会
+中国建筑学会
+中国孔庙保护协会
+中国土木工程学会
+中国可持续发展研究会
+中国医药教育协会
+中国兵工学会
+中国公路学会
+中国保险行业协会
+中国人工智能学会
+中国中药协会
+残疾人福利基金会
+爱德基金会
+残疾人就业服务指导中心
+政务公开办公室
+政务热线服务中心
+政务服务网运管中心
+政务服务热线办公室
+政务服务大厅
+政务服务代办中心
+行政服务管理局
+行政服务管理办公室
+行政服务工作管理办公室
+行政服务局
+行政服务审批中心
+行政服务南坪中心
+行政服务办公室
+行政服务便民中心
+行政服务与审批管理局
+宗教
+民族事务委员会
+民族事务局
+国家民委
+民宗委
+检察院
+检察分院
+检察厅
+法律政策研究室
+国家检察官学院
+检察日报社
+中国检察出版社
+检察理论研究所
+检察技术信息研究中心
+党员群众服务中心
+党委办
+党政办
+省委办
+市委办
+县委办
+机要局
+党史研究室
+党校
+区委办
+农工部
+机构编制委员会
+党员电化教育中心
+中共中央台湾工作办公室
+市委当代党员杂志社
+地方志
+地委办公室
+新农村建设项目办公室
+社会治安综合治理委员会
+市委农工委
+市委编办
+党政专用通信局
+保密局
+省委信访局
+市委信访局
+县委信访办
+国家公务员局
+国家公务员培训指导中心
+公务员培训管理中心
+组织部
+公务员录用考试中心
+老干部活动中心
+老干部局
+老干部活动管理服务中心
+老干部服务中心
+老干部休养所
+宣传部
+新闻中心
+文明办
+新闻信息中心
+报社
+精神文明建设委员会
+对外宣传办公室
+宣传文体服务中心
+宣教局
+精神文明建设办公室
+精神文明建设指导委员会
+互联网舆情中心
+纪委
+监察局
+纪律检查委员会
+纪检监察室
+纪律检察委员会
+廉政教育中心
+廉政教育基地
+党纪教育中心
+纪检监察审计办公室
+监察委员会
+纪律委员会
+纪检委
+监委
+监察部
+监察厅
+信访室
+统战部
+统一战线工作部
+国务院办公厅
+中央密码工作领导小组办公室
+中央保密委员会办公室
+国际农发基金扶贫项目领导小组办公室
+金融工作办公室
+金融办
+金融工作局
+金融服务中心
+金融服务办公室
+金融事业发展中心
+商务经济合作和外事局
+市长公开电话受理中心
+糖业发展局
+发展和改革委
+国民经济综合司
+经济运行调节局
+经济研究院
+固定资产投资司
+体制改革综合司
+外资司
+地区经济司
+京津冀协同发展领导小组办公室
+地区振兴司
+区域开放司
+农村经济司
+基础设施发展司
+资源节约和环境保护司
+经济贸易司
+财政金融和信用建设司
+价格司
+经济与国防协调发展司
+宏观经济研究院
+国家信息中心
+电子政务外网管理中心
+基建物业管理中心
+经济与国防协调发展研究中心
+国家投资项目评审中心
+价格监测中心
+价格监测处
+价格成本调查中心
+节能中心
+国家地理空间信息中心
+国家公共信用信息中心
+一带一路建设促进中心
+创新驱动发展中心
+中国经济导报社
+中国改革报社
+中国计划出版社
+中国城市轨道交通协会
+国际经济交流中心
+中国产业发展促进会
+中国交通运输协会
+中国施工企业管理协会
+中国市场出版社
+宏观经济管理编辑部
+中国经贸导刊杂志社
+粮食和物资储备局
+物资储备司
+物资储备局
+能源储备司
+能源储备局
+安全仓储与科技司
+价格认证办公室
+价格认证中心
+价格认定局
+价格认定中心
+价格成本调查队
+价格管理中心
+价格成本调查监审分局
+价格研究所
+价格认证局
+价格政策研究中心
+价格监测与成本监审局
+价格监测分析中心
+价格监测与认证中心
+价格成本监审局
+价格监测分局
+价格成本监测所
+价格监测和成本调查监审局
+价格采集分析中心
+价格举报受理督察办公室
+价格监督检查局
+价格监督检查分局
+价格成本调查监审局
+价格监督稽查支队
+价格鉴定监测管理局
+价格监督监查局
+价格监督检察局
+价格监督检查和反不正当竞争局
+价格协会
+价格调节基金办公室
+价格调节基金领导小组办公室
+价格调节基金管理办公室
+价格调节基金征收所
+价格基金管理办公室
+储备物资管理局
+教材局
+国家教材委员会
+教材编译中心
+基础教育司
+基础教育科
+职业教育与成人教育司
+高等教育司
+教育督导局
+教育督导委员会
+民族教育司
+教师工作司
+体育卫生与艺术教育司
+体卫艺司
+思想政治工作司
+社会科学司
+科技司
+科学技术司
+学位委员会办公室
+学位管理与研究生教育司
+语言文字应用管理司
+语言文字信息管理司
+教科文全委会
+国家教育行政学院
+教育科学研究院
+孔子学院
+语言文字应用研究所
+国家开放大学
+中央电化教育馆
+中国教育电视台
+课程教材研究所
+国家留学基金管理委员会
+中国教育报刊社
+全国学生资助管理中心
+全国高等学校学生信息咨询与就业指导中心
+中国教育出版传媒集团
+人民教育出版社
+高等教育出版社
+语文出版社
+中国教育国际交流协会
+教育学会
+中国高等教育学会
+中国教师发展基金会
+中国教育发展基金会
+语言文字工作委员会
+国家语委
+国家语委语言文字规范标准审定会员会
+教育体育司
+教育发展战略学会
+中华孔子学会
+教育基金管理委员会
+教育基金征集工作领导小组办公室
+教育基金办公室
+教育基金会
+教育发展基金会
+教师奖励基金会
+科学技术部
+高新技术司
+农村科技发展中心
+农村科技服务中心
+农村科技开发中心
+农村科技司
+社会发展科技司
+外国专家服务司
+外国专家服务中心
+引进国外智力办公室
+引进国外智力管理司
+国家科学技术奖励工作办公室
+科学技术发展战略研究院
+中国农村技术开发中心
+中国生物技术发展中心
+中国21世纪议程管理中心
+国家遥感中心
+国家科技基础条件平台中心
+中国国际核聚变能源计划执行中心
+国家科技风险开发事业中心
+中国国际人才交流中心
+中国国际人才交流基金会
+国家自然科学基金委员会
+科技日报社
+外国专家局
+自然科学基金委员会
+中德科学基金研究交流中心
+自然科学基金会
+科技型中小企业技术创新基金管理中心
+科技型中小企业创新基金管理中心
+基础与应用基础研究基金委员会
+烟草专卖局
+产业政策与法规司
+中小企业局
+节能与综合利用司
+原材料工业司
+装备工业一司
+装备工业处
+国家重大技术装备办公室
+装备工业二司
+消费品工业司
+军民融合发展委员会
+电子信息司
+电子信息产品检验研究院
+电子信息产品检验所电子信息产品监督检验院
+电子信息产品检验院
+电子信息产业研究院
+电子信息技术应用促进中心
+信息技术发展司
+信息技术发展中心
+信息通信发展司
+网络安全管理局
+无线电管理局
+无线电办公室
+国家航天局
+中国信息通信研究院
+人民邮电报社
+中国通信学会
+电子信息产业发展局
+电子信息产业发展研究院
+国家工业信息安全发展研究中心
+中国电子技术标准化研究院
+中国电子产品可靠性与环境试验研究所
+中国电子学会
+国际经济技术合作中心
+中国国际贸易促进委员会电子信息行业分会
+中小企业发展促进中心
+中国机电设备招标中心
+通信清算中心
+网络安全产业发展中心
+应急通信保障中心
+中国工业互联网研究院
+中国工信出版传媒集团
+工业文化发展中心
+威海电子信息技术综合研究中心
+中小企业国际合作协会
+电子通信行业职业技能鉴定指导中心
+国防科工局
+自然资源局
+自然资源部
+自然资源厅
+自然资源调查监测司
+自然资源确权登记局
+自然资源所有者权益司
+自然资源开发利用司
+国土空间规划局
+国土空间规划司
+国土空间用途管制司
+国土空间生态修复司
+耕地保护监督司
+地质勘查管理司
+矿业权管理司
+矿产资源保护监督司
+海洋战略规划与经济司
+海域海岛管理司
+海洋预警监测司
+国土测绘司
+地理信息管理司
+国家自然资源总督察办公室
+海洋权益司
+国家自然资源督察
+自然资源经济研究院
+国土资源规划所
+国土资源产业经济所
+地质出版社
+中国大地出版社
+自然资源报社
+中国地质博物馆
+国土整治中心
+土地科技创新中心
+中央地质勘查基金管理中心
+测绘发展研究中心
+地图技术审查中心
+中国测绘科学研究院
+国家基础地理信息中心
+国家测绘档案资料馆
+大地测量部
+天地图工作部
+地理国情监测部
+全球地理信息资源部
+地理信息分析部
+国土卫星遥感应用中心
+测绘产品质量检验测试中心
+中国地图出版集团
+国家海洋技术中心
+海洋技术战略研究与规划室
+海域与海岛技术室
+海域室
+海洋可再生能源开发利用技术室
+海洋能管理中心
+海洋测量传感器技术研究室
+近海海洋环境观测与监测技术研究室
+深远海及海底探测技术研究室
+海洋遥感与测绘技术研究室
+海洋环境保障技术研究室
+国家海洋环境预报中心
+海啸预警中心
+海洋灾害预警报室
+海啸预警室
+海洋环境预报室
+海洋气象预报室
+极地环境研究预报室
+海洋气候预测室
+中国极地研究中心
+国家海洋标准计量中心
+国家卫星海洋应用中心
+海洋减灾中心
+海洋咨询中心
+国家深海基地管理中心
+海岛研究中心
+第一海洋研究所
+第二海洋研究所
+第三海洋研究所
+第四海洋研究所
+天津海水淡化与综合利用研究所
+海洋发展战略研究所
+国家南极考察训练基地
+海洋出版社
+中国海洋报社
+城乡规划管理中心
+生态保护基金办公室
+生态保护基金会
+规划和国土资源管理委员会
+规划和自然资源委员会
+生态环境部
+生态环境保护督察办公室
+生态环境政策处
+自然生态保护司
+生态保护红线监管处
+自然保护地监管处
+生物多样性保护处
+生物安全管理处
+水生态环境司
+重点流域保护修复协调与监督处
+流域处
+地表水生态环境质量管理处
+黑臭水体治理协调与监督办公室
+重点工程水质保障处
+海洋生态环境司
+海洋生态保护与环境质量管理处
+海域综合治理监督协调处
+海洋污染防治监管
+大气环境司
+大气环境质量管理处
+大气环境协调办公室
+重污染天气应对处
+应对气候变化司
+土壤生态环境司
+地下水生态环境处
+农村生态环境处
+污染地块生态环境处
+固体废物与化学品司
+固体废物处
+化学品处
+核设施安全监管司
+核安全协调处
+国家安全协调处
+辐射监测与应急处
+核安全设备处
+核电安全监管司
+核电一处
+核电二处
+核电三处
+反应堆处
+辐射源安全监管司
+核燃料与运输处
+放射性废物管理处
+核技术利用处
+电磁辐射与矿冶处
+环境影响评价与排放管理司
+规划环评处
+区域与规划环境影响评价处
+环境影响评价与固定污染源排污许可一处
+环评与排污许可一处
+环境影响评价与固定污染源排污许可二处
+环评与排污许可二处
+资源开发与基础设施环境影响评价处
+生态环评处
+生态环境监测司
+环境质量监测与评估处
+生态与污染源监测处
+生态监测处
+生态环境执法局
+生态环境公约处
+核安全国际合作处
+核与辐射安全监督站
+生态环境监督管理局
+中国环境科学研究院
+中国环境监测总站
+中日友好环境保护中心
+环境发展中心
+环境与经济政策研究中心
+中国环境报社
+中国环境出版集团
+核与辐射安全中心
+环境科学研究所
+环境规划院
+环境工程评估中心
+卫星环境应用中心
+环境遥感部
+航空遥感部
+环评与规划遥感部
+国家气候战略中心
+海洋环境监测中心
+生态环境监管技术部
+环境技术交流中心
+环境保护总局
+中国环境科学学会
+中华环境保护基金会
+中国环境文化促进会
+中国环境新闻工作者协会
+中国老龄协会
+全国老龄工作委员会
+卫生健康委
+卫健委
+公立医院改革处
+疾病预防控制局
+传染病防控处
+免疫规划处
+免疫规划中心
+艾滋病防控处
+防治艾滋病工作办公室
+艾滋病防控中心
+防治艾滋病工作委员会
+防治艾滋病局
+艾滋病防治协会
+艾滋病防治中心
+艾滋病防治工作委员会
+艾滋病救治中心
+艾滋病关爱中心
+艾滋病防治局
+艾滋病防治办公室
+艾滋病预防控制中心
+艾滋病防治工作领导小组办公室
+艾滋病防治服务中心
+艾滋病工作委员会
+艾滋病防治工作办公室
+防治艾滋病工程委员会办公室
+艾滋病临床治疗中心
+防治艾滋病办公室
+结核病防控处
+寄生虫病与地方病防控处
+寄生虫病预防控制所
+慢性病与营养管理处
+精神卫生处
+环境健康处
+慢性病前瞻性研究项目管理办公室
+慢性病前瞻性研究项目办公室
+医政医管局
+医疗资源处
+医疗机构处
+医疗管理处
+医疗质量与评价处
+医疗安全与血液处
+护理与康复处
+公共卫生医疗管理处
+基层卫生健康司
+基本公共卫生处
+家庭医生处
+卫生应急办公室
+公共卫生事件应急指挥中心
+医学教育处
+公共卫生监督一处
+公共卫生监督二处
+公共卫生监督一所
+公共卫生监督二所
+公共卫生事件应急处理中心
+公共卫生健康指导中心
+公共卫生紧急救援指挥中心
+公共卫生医学中心
+公共卫生管理办公室
+公共卫生间管理所
+公共卫生应急指挥中心
+公共卫生应急处理中心
+公共卫生综合服务大楼指挥部
+公共卫生和医疗保障中心
+公共卫生突发事件处置中心
+公共卫生事件应急中心
+公共卫生项目管理办公室
+公共卫生指挥中心
+国家级公共卫生示范中心
+传染病防治监督处
+医疗监督处
+药物政策与基本药物制度司
+药物政策与基本药物制度处
+药物政策处
+药品目录管理处
+药品供应保障协调处
+药品使用管理处
+食品安全标准与监测评估司
+食品安全标准管理处
+食品安全风险监测与评估处
+食品营养处
+老龄健康司
+老龄工作委员会
+老龄委
+老龄工作办公室
+老龄司
+老龄科学研究中心
+老龄办
+老龄事业中心
+老龄退管工作委员会办公室
+老龄问题研究中心
+老龄工作委员办公室
+老龄事业服务中心
+老龄服务中心
+老龄人活动中心管理所
+老龄人工作委员会办公室
+老龄健康发展中心
+老龄化工作委员会办公室
+老龄工作委员会办公室
+老龄工作服务中心
+健康服务处
+医养结合处
+妇幼健康司
+妇幼健康服务司
+妇幼保建院
+妇幼保键院
+妇幼院
+妇幼健康服务中心
+妇幼和生殖保健中心
+妇幼服务中心
+妇幼儿童医疗保健中心
+妇幼生殖中心
+妇幼中心
+妇幼保建站
+妇幼健康咨询中心
+计生妇幼综合服务站
+妇幼医学研究所
+妇女卫生处
+儿童卫生处
+出生缺陷防治处
+人口监测与家庭发展司
+家庭发展指导处
+健康促进处
+国家人口计生委药具管理中心
+国家人口计生委药具发展中心
+中国医学科学院
+疾病预防控制中心
+医药研究所
+疾病预防控中心
+疾病预防控制保健中心
+疾病预防保健中心
+疾病预防疫控中心
+疾病预防控制所
+疾病预防控㓡中心
+疾病预防控制分中心
+疾病预防控制西部中心
+疾病预防控制站
+疾病预防控制检验中心
+疾病预防控制与保健中心
+疾病预防控制南部分中心
+疾病预防监控中心
+疾病预防检控中心
+疾病预防防控中心
+健康传播中心
+医学实验动物中心
+卫生标准处
+公共卫生中心
+公共卫生服务站
+卫生应急中心
+卫生应急调度指挥中心
+卫生应急救援指挥中心
+卫生应急处理中心
+卫生应急指挥处置中心
+卫生应急调度中心
+卫生应急指挥中心
+传染病管理处
+传染病预防控制处
+结核病预防控制中心
+公共卫生管理处
+控烟办公室
+传染病预防控制所
+病毒病预防控制所
+性病艾滋病预防控制中心
+慢性非传染性疾病预防控制中心
+营养与健康所
+环境与健康相关产品安全所
+职业卫生与中毒控制所
+职业卫生所
+辐射防护与核安全医学所
+农村改水技术指导中心
+麻风病控制中心
+老年保健中心
+精神卫生中心
+儿少/学校卫生中心
+鼠疫布氏菌病预防控制基地
+结核病防治临床中心
+性病控制中心
+预防医学研究所
+预防医学研究中心
+预防医学研究院
+卫生信息学会
+性病麻风病防治技术指导中心卫生健康监督中心
+医疗卫生监督处
+公共卫生监督处
+妇幼健康监督处
+职业与放射卫生监督处
+药具管理中心
+食品安全风险评估中心
+食品安全标准研究中心
+国民营养行动中心
+健康教育中心
+爱国卫生发展中心
+医管中心
+医疗管理服务指导中心
+医学考试中心
+国家癌症中心
+国家心血管病中心
+人口文化中心
+人口文化发展中心
+流动人口服务中心
+人民卫生出版社
+人卫社
+健康报社
+人口出版社预防医学会
+计划生育协会
+医学会
+人口基金会
+人口协会
+人口学会
+老龄产业发展协会
+居民健康管理中心
+医共体区域检验中心
+医共体管理中心
+医共体影像中心
+医共体总院
+医学基金会
+医学发展基金会
+疾病应急救助基金管理中心
+农民合作医疗基金管理委员会办公室
+农民合作医疗保险基金管理中心
+银保监
+银行机构检查局
+普惠金融部
+非银检查局
+保险消费者权益保护局
+政策性银行监管部
+大型银行部
+股份制银行部
+城市银行部
+农村银行部
+财险部
+人身险部
+信托部
+市场监督管理总局
+市场监管总局
+市场监督管理厅
+知识产权局
+药品监督管理局
+中国食品药品检定研究院
+医疗器械标准管理中心
+中国药品检验总所
+国家药典委员会
+药品审评中心
+食品药品审核查验中心
+医疗器械技术审评中心
+食品药品监管数据中心
+执业药师资格认证中心
+中国食品药品国际交流中心
+南方医药经济研究所
+中国药学会
+中国健康传媒集团
+登记注册局
+信用监督管理局
+信用监督管理司
+信用监督管理处
+打击传销领导小组办公室
+打击传销工作领导小组办公室
+网络交易监督管理司
+网络交易监督管理局
+网络交易监督管理处
+广告监督管理司
+广告监督管理局
+广告监督管理处
+质量发展局
+质量发展研究中心
+产品质量安全监督管理司
+产品质量安全监督管理局
+产品质量安全监督管理处
+食品安全协调司
+食品安全协调局
+食品安全协调处
+食品安全投诉举报受理中心
+食品安全委员会办公室
+食品安全工作办公室
+食品安全监督所
+食品安全审评认证中心
+食品安全监督协调办公室
+食品安全委员会
+食品安全管理办公室
+食品安全协调委员会办公室
+食品安全监督中心
+食品安全协调委员会
+食品安全卫生应急技术指挥中心
+国家食品安全检测科研中心
+食品安全应急指挥中心
+食品安全工作领导组办公室
+食品安全监管指挥中心
+食品生产安全监督管理司
+食品生产监督所
+食品生产安全监督管理局
+食品生产安全监督管理处
+食品经营安全监督管理司
+食品经营安全监督管理局
+食品经营安全监督管理处
+特种设备安全监察局
+特种设备检验院
+特种设备检测院
+特种设备安全技术检查中心
+特种设备技术检查所
+特种设备应急处置中心
+特种设备应急处置指挥中心
+特种设备技术检查中心
+特种设备检测技术中心
+特种设备监督检验院
+特种设备检验研究
+特种设备检验研究中心
+特种设备事故调查处置中心
+特种设备监察中心
+特种设备安全技术培训中心
+特种设备安全节能技术研发中心
+特种设备监督检查中心
+特种设备安全检验检测实验中心
+计量司
+计量所
+质量计量监督监测所
+质量计量监督所
+计量质量服务中心
+标准计量质检院
+计量质量检测中心
+计量与合格评定监督管理处
+计量检定站
+计量科学研究院
+计量科学研究所
+计量监督检定所
+计量监督所
+计量测试所
+计量测试院
+计量测试检定所
+计量测试检定研究所
+计量测试技术研究院
+计量测试技术研究所
+计量测试技术研究中心
+计量检测技术中心
+计量检定监督局
+计量检定监测院
+计量检定测试所
+计量检定测评所
+计量检定二所
+计量局
+计量仪器仪表试验所
+标准计量管理所
+标准计量情报研究所
+标准计量情报所
+国家计量测试中心
+国家大容量第二计量站
+四川重大技术装备几何量计量站
+标准技术管理司
+标准技术研究院
+标准技术管理处
+标准技术审评中心
+标准创新管理司
+认证监督管理司
+认证认可监督管理委员会
+中国认证认可协会
+认可与检验检测监督管理司
+计量院
+称重公正计量中心
+中国纤维质量监测中心
+食品审评中心
+国家中药品种保护审评委员会
+中国检验检疫科学研究院
+中国合格评定国家认可中心
+中国网络安全审查技术与认证中心
+全国组织机构统一社会信用代码数据服务中心
+中国物品编码中心
+认证认可技术研究中心
+认证认可技术研究所
+中国市场监管报社
+中国质量报刊社
+中国消费者报社
+中消报
+中国工商出版社
+中国质量标准出版传媒有限公司
+中国市场监督管理学会
+中国计量测试学会
+中国检验检测学会
+中国个体劳动者协会
+中国质量万里行促进会
+中国品牌建设促进会
+中国消费品质量安全促进会
+中国出入境检验检疫协会
+中国质量检验协会
+中国计量协会
+中国标准化协会
+中国标协
+中国防伪行业协会
+中国设备监理协会
+中国特种设备安全与节能促进会
+特种设备行业协会
+特种设备协会
+计量测试学会
+市场安全监管局
+文化和旅游部
+艺术司
+非物质文化遗产司
+文化市场综合执法监督局
+文化市场执法监督处
+旅游市场执法监督处
+国家图书馆
+故宫博物院
+中国国家博物馆
+中央文化和旅游管理干部学院
+中国文化传媒集团有限公司
+国家京剧院
+中国国家话剧院
+中国歌剧舞剧院
+中国东方演艺集团有限公司
+中国交响乐团
+中国儿童艺术剧院
+中央歌剧院
+中央芭蕾舞团
+中央民族乐团
+中国煤矿文工团
+中国美术馆
+中国国家画院
+中国对外文化集团有限公司
+中国数字文化集团有限公司
+中国动漫集团有限公司
+中外文化交流中心
+中国艺术科技研究所
+国家艺术基金管理中心
+中国旅游出版社有限公司
+中国旅游研究院
+梅兰芳纪念馆
+中国文化报
+中国旅游报
+中国美术报
+音乐生活报
+文化艺术出版社
+中国旅游出版社
+故宫出版社
+公安部
+公安厅
+公安局
+公安处
+森林公安
+铁路公安
+民航公安
+交通警察
+看守所
+公安边防支队
+交通综合行政执法支队
+公安边防大队
+交警总队
+交警队
+交警支队
+交警大队
+交巡警支队
+海警局
+交通行政执法总队
+边防总队
+海警
+公安分局
+公安特警
+边防检查站
+边防检查总站
+边防委员会
+边海防委员会
+刑事侦查局
+警卫局
+公安科技研究所
+公安民警培训服务中心
+社会综合治理
+综合治理委员会
+拘留所
+维护稳定办公室
+禁毒委员会
+机动车驾驶培训协会
+社会管理综合治理工作中心
+出入境管理局
+出入境管理支队
+民政部
+民政厅
+民政局
+福利彩票
+殡葬管理所
+彩票发行管理中心
+殡仪馆
+儿童福利院
+社会福利院
+公墓
+陵园
+革命公墓
+民政和民族宗教事务局
+民政办公室
+敬老院
+福利院
+殡葬服务所
+老年福利中心
+社会救助事务中心
+社会福利中心
+社会工作联络中心
+福彩发行工作管理站
+光荣院
+疗养院
+养老公馆
+社区教育中心
+民政事务中心
+民政事务局
+民政事业服务中心
+民政福利事业
+民政所
+民政和民宗局
+民政信息中心
+民政分局
+社会福利服务中心
+儿童救助保护中心
+慈善会
+司法部
+司法厅
+司法局
+司法所
+法制教育所
+司法处
+司法警察训练总队
+司法鉴定所
+财政部
+财政厅
+财政局
+财税局
+财政所
+财税所
+财政委员会
+财政干部培训中心
+财政数据信息计算中心
+财政信息中心
+财政投资评审中心
+行政事业单位财务结算中心
+非税收入管理局
+财政信息化建设管理处
+财政实物管理中心
+会计培训中心
+财政收费票据监管中心
+国库支付局
+财会人员继续教育中心
+财政结算中心
+专项资金监管中心
+财政管理局
+非税收入征收管理局
+国库支付中心
+国库集中支付核算中心
+财政管理中心
+农村财务管理局
+农村经济财务管理局
+国库集中支付局
+财政收支管理服务中心
+财政财务管理局
+政府资金结算中心
+财务结算管理中心
+财务结算中心
+财政监督检查所
+会计服务中心
+财政分局
+财政监督局
+农村综合改革工作领导小组办公室
+人力资源和社会保障部
+人力资源社会保障部
+社会保障事务局
+公务员局
+人事局
+人力资源和社会保障厅
+人力和社会保障局
+社保局
+就业局
+人社局
+劳动就业办公室
+社会事业管理局
+社会保障厅
+社会保险事业局
+社会保障局
+劳动保障就业服务中心
+社会保障信息中心
+社会事务局
+社会保险事业管理局
+劳动监察大队
+社会保障管理中心
+人才服务中心
+人事考试中心
+人事考试院
+劳动保障监察支队
+人事考试管理办公室
+人力资源社会保障信息化建设办公室
+人才交流服务中心
+人力资源公共服务中心
+人才交流中心
+劳动保障信息中心
+失业保险处
+社会保险服务中心
+人事考试办公室
+人力资源开发局
+人力资源考试中心
+人力和社会资源保障局
+社会事业发展局
+社会保障卡服务中心
+人力资源和社会保障数据管理中心
+社会事务管理局
+社会事业局
+劳动人事争议仲裁院
+劳动保护教育中心
+创业服务中心
+公共实训管理服务中心
+人力资源培训教育中心
+住房和城乡建设部
+住房城乡建设部
+住房和城乡建设厅
+住建局
+建交局
+公共建筑建设管理处
+住房和城乡建筑局
+城乡建设办公室
+建设厅
+住房和城乡建设局
+住房和城乡建设委员会
+城乡建设委员会
+住房保障和房产交易中心
+廉租住房管理所
+住房保障署
+城市建设管理局
+建委
+建设局
+城乡建设管理所
+公共工程建设中心
+建筑管理站
+工程项目代建局
+建筑工程管理处
+经济适用住房建设开发中心
+公共租赁房管理局
+住房管理中心
+住房保障服务局
+城建局
+新城建设管理局
+城乡建设服务中心
+城建环保服务中心
+城区建设管理处
+城乡建设和管理服务中心
+房产和住房保障局
+城镇建设管理处
+工程建设管理局
+住房和城市更新局
+建筑管理处
+政府投资工程建设管理中心
+建筑业管理局
+建设信息中心
+城市建设监察大队
+住房制度改革委员会
+住房和城乡建设管理局
+住房保障工作局
+住房保障工作分局
+住房发展中心
+住房保障管理所
+住房制度改革领导小组
+城乡建设管理局
+住宅建设发展中心
+工程建设项目管理中心
+住房保障局
+住宅发展中心
+建设管理局
+城建监察支队
+城市建设管理监察队
+住房和建设委员会
+城市建设服务中心
+住房和城市建设委员会
+建筑工务局
+村镇建设管理服务所
+住房和城乡建设执法监察总队
+建设工程质量监督检测站
+住房和建设管理部
+建筑工程安全质量监督总站
+建筑业教育中心
+建筑工程检测站
+建筑质量安全监督站
+住房和城乡局
+住房与村镇建设监察大队
+建筑工程管理局
+住房城乡建设执法稽查支队
+房产事业局
+房产交易监理所
+房地产交易登记中心
+房产管理局
+房管局
+房地产登记发证中心
+房产所
+房产局
+房屋登记中心
+物业供热管理办公室
+物业管理有限公司
+公积金服务中心
+房产测绘中心
+城建监察大队
+住房和城乡建议局
+建筑规划设计院
+建筑设计研究院
+交通部
+交通厅
+交通局
+铁路局
+民航总局
+航道局
+航道所
+交通运输局
+交通运输部
+交通管理所
+交通办公室
+港务局
+交通安全委员会
+交通委员会
+交通管理局
+交通建设发展中心
+航务管理处
+公路管理所
+渔港监督处
+空管局
+公共交通管理处
+道桥管理处
+养路总段
+民用航空总局
+运输管理处
+交通设施建设处
+公路管理局
+公路处
+公路养护管理
+航标处
+公路收费管理所
+道路管理所
+海事处
+公路建设管理局
+港口管理局
+道路运输管理
+航道处
+出租汽车管理处
+公路局
+公路分局
+车辆段
+隧道局
+轨道集团
+路政管理局
+公路管养中心
+高速公路收费中心
+路政总队
+港航管理处
+交通科学研究所
+民航管理中心
+铁路信息技术中心
+路政执法总队
+城市干道工程建设办公室
+交通运输厅
+路政局
+航运管理站
+道路管理站
+高速公路信息监控中心
+交通运行监测与应急调度中心
+交通建设工程质量监督局
+航运服务中心
+公路客运总站
+交通建设项目招投标评审中心
+车渡管理站
+航道管理
+海事服务中心
+交通信息通信中心
+港航管理局
+交通建设工程质量安全监督局
+公路收费站
+船务管理局
+航空局
+交通运输站
+航运救捞站
+交通管理站
+路网建设
+交通运输委员会
+船级社
+路政执法监察支队
+港航管理所
+民用机场
+公路段
+公路养护中心
+交通信息中心
+广铁集团
+航道工程局
+交通工程检测中心
+交通运输执法所
+交通工程管理站
+交通工程质量监督检测站
+交通运输执法总队
+交通工程建设指挥部
+交通运行监测中心
+交通建设质量安全监督局
+公路改造领导小组
+公路服务中心
+公路养护处
+公路养护所
+公路事业发展中心
+公路养护应急中心
+公路桥隧维护中心
+铁路监督管理局
+长途客运总站
+航道工程处
+航道养护中心
+民航局
+道路桥梁管理中心
+道路交通管理事务中心
+道路养护中心
+路政管理所
+道路建设管理中心
+水路建设养护中心
+道路硬化工程
+路桥建设管理处
+桥梁隧道养护中心
+桥梁维修管理处
+河道堤防管护中心
+公路养护队
+交通工程质量监督管理站
+交通运政稽查支队
+交通运输监察支队
+交通运输安全督查中心
+交通运输工作部
+交通运输安全事务中心
+交通工程管理所
+交通安全管理中心
+交通运输执法监察总队
+交通运输执法监察支队
+交通路桥养护中心
+公路养护工程有限公司
+河道管护服务总站
+交通工程质量安全监管站
+桥梁管理处
+公路桥梁养护处
+海事局
+交通发展研究院
+铁道部
+水利部
+水利厅
+水利站
+水利局
+水利物资站
+水利总站
+河务局
+水利委员会
+水务局
+防汛
+水务管理局
+南水北调工程建设监管中心
+水文管理站
+运河管理处
+水利服务中心
+水管站
+水利工作队
+水文水资源勘测局
+水利建设中心
+水利工程管理处
+水利管理局
+水利管理站
+水资源中心
+水资源服务中心
+水土保持预防监督局
+水利工作站
+自来水厂
+水务设施管理所
+水库管理中心
+供水站
+水利管理处
+水利建设站
+水利工程建设管理处
+供水处
+水业集团
+水库工程管理所
+数字水利中心
+水土保持站
+南水北调干线管理处
+水利水电管理中心
+水资源勘测分局
+水利宣传中心
+水务管理站
+水利信息中心
+水务集团
+水资源局
+水利信息管理中心
+供水管理处
+水政管理所
+水文水质监测站
+水质检测中心
+引排水调度中心
+水利管理中心
+水利水电工程建设服务中心
+污水处理厂
+水利电力管理所
+水利工程养护维修中心
+水利综合经营管理中心
+排水事业管理局
+污水治理工程筹建处
+水库管理局
+水文局
+河道管理处
+水政监察支队
+供水办公室
+供水有限公司
+南水北调工程建设委员会
+水资源管理中心
+水利电力局
+水库建设管理局
+水利所
+调水工程建设管理办公室
+水利水电建设站
+排水设施管理处
+运河治理中心
+水利管理署
+河道管理事务中心
+河道管理所
+水文总局
+灌溉管理处
+灌溉管理局
+水库工程管理局
+水务工程建设管理中心
+河道管理局
+水务中心
+自来水公司
+水利管理所
+河道管理站
+河务管理处
+水库管理所
+河长制办公室
+水库管理办公室
+涧河管理处
+供水调度监测中心
+水务管理所
+水资源管理所
+灌溉排水发展中心
+移民开发局
+水政监察大队
+移民工作局
+移民办公室
+水资源监察支队
+水文水资源勘测总站
+机电排灌总站
+移民工作管理局
+农村饮水管理总站
+排灌工程管理处
+河湖管理保护中心
+水务站
+移民办
+移民指挥部
+河湖管理所
+南水北调管理局
+水利科技推广中心
+水政执法监察大队
+水利开发有限公司
+水利发展建设总公司
+水利工程有限公司
+水土保持事业局
+水利水电管理所
+水库移民
+水务信息管理中心
+水利水务信息调度指挥中心
+水利水务站
+水务服务中心
+水文站
+水利水电局
+水政监察总队
+水利事务服务与行政执法中心
+灌溉总渠管理处
+水务水电局
+水利水电队
+农业部
+农业厅
+农业局
+农牧林局
+农业综合开发局
+农业委员会
+绿色食品发展中心
+农业资源开发局
+农林发展局
+畜牧发展中心
+农业工作办公室
+农村发展局
+农村经济局
+渔政局
+渔业执法局
+农林局
+农村经济经营管理局
+种植业管理局
+草原站
+渔业局
+渔港监督支队
+畜牧工作站
+水产局
+种子工作站
+农牧业科技服务中心
+农村经济管理局
+农业科技博览园
+植保总站
+植物检疫站
+种子管理站
+渔港监督局
+农牧厅
+植检站
+农村经济经营管理站
+农业畜牧局
+农发办
+经济作物技术推广站
+农牧局
+畜牧研究所
+农作物研究所
+农科所
+畜牧机械化研究所
+畜产品质监站
+农村经营服务站
+油料研究所
+粮食局
+农牧业厅
+农林水局
+农业开发办
+农业执法总队
+农业行政综合执法大队
+农牧业市场信息中心
+土壤肥料工作站
+农产品质量安全检验监测中心
+畜牧业管理局
+海洋与渔业厅
+苗木繁育中心
+畜牧局
+畜禽屠宰管理所
+植物保护总站
+动物疫病预防控制中心
+农业开发管理办公室
+兽医科学研究所
+动物疫病预防与控制中心
+植物保护检疫站
+植保站
+畜牧总站
+兽药饲料监察所
+畜牧兽医站
+动物卫生监督
+饲料兽药检测站
+农业信息中心
+种子管理局
+渔政总队
+农业和农村工作局
+农业综合改革工作
+农产品质量安全检测中心
+农业综合开发项目建设中心
+农牧业局
+渔政监督
+农村工作局
+农村工作办公室
+农业工程技术研究中心
+农业执法大队
+农产品质量安全检测站
+农业科学研究所
+寄生虫病防治研究所
+农林水牧局
+水产养殖病害防治监测
+畜牧办公室
+农业综合开发办
+农村综合改革
+畜牧技术推广站
+热作产业局
+农业综合技术推广服务中心
+农业服务中心
+农村牧区工作部
+兽医药品监察所
+农业展览馆
+农业对外合作交流中心
+渔政渔港监督管理处
+农村能源管理办公室
+水稻研究所
+商务部
+商务厅
+经贸局
+商务局
+商业局
+贸易促进局
+贸易促进委员会
+招商融资促进局
+投资促进局
+贸促会
+商贸流通管理办公室
+贸易促进会
+商务委员会
+商务委
+商务会展促进服务中心
+商务合作局
+商务办公室
+电子商务服务中心
+商务发展工作领导小组办公室
+投资促进中心
+招商局
+会展促进中心
+商务综合行政执法大队
+电子商务孵化中心
+经济技术合作局
+供销社
+供销联社
+供销合作社
+招商合作局
+经济贸易局
+国际博览事务局
+招商促进局
+企业服务局
+会议展览事务局
+招商和项目推进局
+供销合作总社
+对外贸易中心
+经济合作局
+供销合作联合社
+商务和经济信息委员会
+招商服务局
+供销合作联社
+招商服务中心
+商务综合执法大队
+人民银行
+外汇交易中心
+票据交换中心
+人行
+反洗钱监测分析中心
+国家外汇管理局
+审计署
+审计厅
+审计局
+审计服务中心
+公共投资审计中心
+审计办公室
+审计监督局
+审计监督中心
+城投
+城市投资
+建设投资公司
+建投
+建投建设工程集团
+海关
+海防与口岸局
+口岸办公室
+口岸管理局
+口岸服务中心
+口岸事务局
+电子口岸数据中心
+口岸办
+口岸管理服务中心
+口岸工作办公室
+缉私局
+税务总局
+税务局
+国税局
+地税局
+办税服务中心
+税务干部培训中心
+税务信息中心
+地方税务局
+地税分局
+国税分局
+国家税务局
+地方税务服务中心
+税务分局
+税务杂志社
+版权局
+广播局
+出版局
+广电局
+广播转播台
+广播电视局
+广播电视发射监测技术中心
+广播影视局
+文广服务中心
+文化广电出版体育厅
+广播电视中心
+出版发行事业局
+广播电视监测站
+新华书店
+广播影视服务中心
+出版集团
+新闻出版局
+广电总局
+新闻出版广播影视技术推广中心
+新闻出版研究院
+广电新闻出版
+运动管理中心
+体育科学研究所
+体育馆
+体育场馆管理中心
+体育工作办公室
+网球中心
+冬季项目管理中心
+冰上训练基地
+滑雪场
+体育场
+体育工作大队
+体育协会
+体育委员会
+体育总会
+体育发展委员会
+乒乓球通州训练基
+冬残奥会组织委员会
+统计局
+经济调查队
+统计调查局
+人大
+政治协商会议
+政协
+民盟
+九三学社
+国民党革命委员会
+民革
+民主建国会
+民主促进会
+农工民主党
+致公党
+台湾民主自治同盟
+中华全国工商业联合会
+工商联合会
+气象局
+气象服务中心
+海洋预报台
+气象科技服务中心
+气象台
+气象科学研究所
+气象公共服务中心
+气候中心
+人工影响天气中心
+气候观象台
+人工影响天气办公室
+招标办
+招标投标中心
+招标办公室
+招标管理中心
+招标管理办公室
+招标采购管理局
+招标投标交易管理中心
+招标投标办公室
+招标采购事务管理所
+招标投标服务中心
+招标采购交易中心
+政府采购交易中心
+招标服务中心
+招标投标监督管理局
+招标投标事务中心
+招投标交易办公室
+招投标交易中心
+公共资源综合交易管理办公室
+公共资源综合交易服务中心
+公共资源综合交易中心
+政府采购服务中心
+政府采购分中心
+政府采购服务部
+政府采购管理中心
+政府采购促进中心
+政府采购中心
+政府采购管理工作办公室
+政府采购控制办公室
+徽采商城
+政府采购工作领导小组办公室
+档案局
+档案馆
+档案缩微技术中心
+档案史志局
+档案资料馆
+档案管理中心
+档案中心
+档案管理服务中心
+档案管理处
+工会
+残联
+儿童少年基金会
+残疾人联合会
+文学艺术界联合会
+红十字会
+妇女联合会
+妇联
+妇女儿童服务中心
+妇女儿童活动中心
+商会
+芭蕾舞团
+交响乐团
+摄影家协会
+天主教爱国会
+宋庆龄基金会
+工商业联合会
+华侨联合会
+业主委员会
+经济合作社
+经济联合社
+纪律检查工作委员会
+纪律检查委员
+纪律与监督工作部
+纪律审查管理中心
+纪律与工作监督部
+纪律检查委
+纪律教育中心
+纪律审查服务中心
+党风廉政建设教育中心
+党风廉政建设教育基地
+党风廉政警示教育基地
+党风廉政建设办公室
+党风廉政中心
+中央巡视工作领导小组
+案件监督管理室
+纪检监察案件管理中心
+纪检监察宣传教育基地
+纪检监察宣教基地
+纪检监察机关
+纪检监察教育中心
+纪检监察宣教培训中心
+纪检监察宣教中心
+纪检监察办公室
+纪检监察干部培训中心
+纪检监察宣传教育中心
+纪检监察审计中心
+纪检监察审计室
+纪检监察保障中心
+纪检监察案管中心
+纪检监察办案管理中心
+纪检监察事务服务中心
+纪检监察信息中心
+纪检监察信息技术保障中心
+纪检监察留置专业陪护中队
+纪检监察办案中心
+纪检监察警示教育基地
+纪检监察集中办案中心
+纪检监察工作协调中心
+纪检监察工委
+纪检监察办案基地管理中心
+纪检监察工作委员会
+纪检监察杂志社
+纪检监察报社
+中国方正出版社
+中国纪检监察学院
+纪检监察干部监督室
+政府办
+政府值班室
+政府法制办
+政府信息办
+信访办
+政府督查室
+政府电子政务办
+信访局
+街道办事处
+密码管理局
+法制办
+政策研究室
+政研室
+史志办
+参事室
+台办
+侨务办
+电子政务办
+电子政务管理办公室
+政务网络中心
+人民政府
+管理委员会
+林场
+居民委员会
+综合实验区
+村民委员会
+开发区管委会
+高新区管委会
+新区管委会
+科技园管委会
+工业园区
+扶贫开发领导小组
+专业合作社
+社会建设工作办公室
+电子政务内网
+电子政务管理中心
+扶贫开发办公室
+国家保密科技测评中心
+保密技术服务中心
+振兴发展工作办公室
+外事台侨办公室
+扶贫办公室
+民防局
+牧场
+地方志办公室
+信访维稳
+管委会办公室
+新农办
+新农村建设工作领导小组
+南水北调办公室
+新农村建设办公室
+外事侨务局
+民防办公室
+扶贫开发局
+扶贫办
+接待办公室
+商务中心区建设指挥部办公室
+信息化推进中心
+省直属机关
+中央和国家机关涉密载体销毁中心
+版权保护中心
+国家版权局
+知识产权服务中心
+知识产权中心
+街道办
+开发区综合办公室
+居民办事处
+外事侨务工作服务中心
+外事办公室
+大数据发展局
+信访服务中心
+发改委
+发展改革委
+发展和改革局
+发展与改革委员会
+发改局
+发展和改革委员会
+发展改革局
+储备粮管理中心
+粮食管理所
+物价局
+粮食中心库
+经济信息中心
+省信息中心
+信用中心
+农村能源办公室
+粮食行业协会
+粮油卫生检验监测中心
+粮库
+经济社会发展局
+发改和经济局
+发改经信局
+发改和改革局
+发改工促局
+粮油质量监测所
+粮油管理所
+价格检查局
+粮油质量监督检测站
+发展改革和经济委员会
+能源局
+教育体育局
+教育局
+教育部
+教体局
+教育体育处
+教育卫体局
+教育厅
+电化教育仪器站
+教科文卫局
+教育委员会
+文教局
+职教中心
+电化教育站
+人事考试局
+教育辅导站
+特教中心
+学区教育办公室
+电教馆
+电教装备馆
+招生考试委员会办公室
+教育办
+教育质量监测中心
+教育技术与装备中心
+考试管理中心
+教科局
+教育技术中心
+教育技术装备处
+现代教育技术中心
+教学研究室
+电教教仪中心
+科教中心
+教委
+教育教学培训研究中心
+招生考试院
+教育信息网络中心
+教育招生和考试中心
+招生考试办公室
+师资培训站
+教育事务指导中心
+科技艺术教育中心
+教育装备与保障中心
+电教站
+教育条件装备站
+教体文广局
+特殊教育中心
+教育研究与服务中心
+教育事务受理中心
+教育项目执行办公室
+民办教育服务中心
+电教仪器站
+教仪工作站
+教育考试院
+电教教仪站
+教育服务中心
+教育发展中心
+教育发展投资中心
+教育活动中心
+教育教学研究院
+教育研究院
+教师研修中心
+教育委会员
+职业教育中心
+成人教育培训中心
+科技教仪中心
+教育科学研究所
+教育测评与研究中心
+教育考试中心
+教育人才服务中心
+教学仪器站
+教师培训中心
+电教中心
+科技部
+科技厅
+科技局
+信息办
+科学技术局
+科技信息局
+经科局
+科学研究院
+科学技术馆
+科学技术协会
+科技馆
+科学技术情报研究所
+科学技术信息研究所
+科学技术研究院
+科技创新委员会
+科学技术厅
+科技创新局
+科技协作中心
+科学馆
+科技创新服务中心
+科学技术委员会
+科技和经济委员会
+科协
+科技促进局
+科学技术宣传馆
+社会科学界联合会
+中国工程院
+科学技术院
+科普宣传教育中心
+科技资源统筹中心
+生产力促进中心
+科技成果管理办公室
+科技研发服务中心
+科技信息中心
+科技发展战略研究所
+科技信息研究院
+科技信息管理局
+能源与核技术应用研究院
+科技信息研究所
+地理研究所
+科技产权局
+科技创新与经济发展局
+科技成果转化服务中心
+科技促进中心
+科技情报研究所
+大数据研究院
+社会发展研究所
+工业和信息化部
+工信部
+工信委
+信息化委员会
+工信局
+工业经济和信息化研究院
+信息安全管理中心
+网信办
+工业和信息化厅
+工信厅
+工业和信息化局
+工业经济技术服务管理中心
+科技和信息化局
+大数据运行管理局
+有色金属研究院
+信息化办公室
+智慧城市办公室
+工业和信息化委员会
+经信局
+经信委
+无线电管理办公室
+专用通信局
+信息化工作办公室
+经济和信息化局
+应急通信局
+无线电监测站
+互联网络信息中心
+国家计算机病毒应急处理中心
+互联网行业管理服务中心
+经济和信息化委员会
+信息产业促进中心
+信息产业办公室
+大数据发展管理局
+大数据管理服务局
+大数据发展委员会
+数据服务管理局
+数据管理局
+大数据发展服务局
+信息网络中心
+信息技术服务中心
+工业技术服务中心
+工业经济运行局
+经济发展局
+工业和科技信息化局
+国家无线电监测中心
+信息化和无线电管理局
+信息化局
+网络信息统筹局
+工业发展委员会
+经济与信息化委员会
+信息技术安全研究中心
+节能监察中心
+节能环保中心
+信息安全认证中心
+工业信息和科学技术局
+工业信息产业局
+经济信息化委员会
+信息化管理中心
+工业信息化委员会
+盐务管理局
+工业和信息局
+通信管理局
+中小企业服务中心
+国防科技工业办公室
+卫星装备研究所
+电子研究所
+信息化建设和管理办公室
+经济与信息委员会
+国防科技工业局
+工业和信息化研究院
+环境保护部
+环境保护厅
+环境保护局
+环保部
+环保厅
+环保局
+环境监控
+环境信息中心
+环境委员会
+环保产业发展中心
+环境应急与事故调查中心
+环境监察
+环境保护办公室
+辐射环境监督管理站
+辐射环境监督站
+环境宣教信息中心
+环境科学技术研究所
+固体废物管理中心
+环境保护管理站
+环境保护合作中心
+洱海流域保护局
+污水处理公司
+垃圾处理场
+环境保护宣传教育中心
+环境卫生事业局
+环境保护信息中心
+环卫局
+污染管理办公室
+环境监测保护站
+环境资源保护中心
+环境保护科技信息中心
+环保安监站
+环境保护科学研究所
+环保产业促进中心
+环境宣传教育中心
+环境保护管理局
+环境卫生管护中心
+环境绿化工程指挥部
+环境综合治理办公室
+环境保护综合执法支队
+环境监测应急中心
+环境污染防治管理中心
+垃圾处理厂
+环璄保护局
+环境保护委员会办公室
+生态环境厅
+生态环境和自然资源管理局
+环境治理监督大队
+环境保护督查中心
+环保能源工作站
+环境保护监测管理站
+环境保护分局
+环境保护检测站
+环境卫生监察大队
+环境保护工作站
+污水处理站
+生态环境监测监督管理局
+生态环境局
+生活垃圾处理厂
+环境保护委员会
+流行病学办公室
+银监会
+银行业监督管理委员会
+银监分局
+银监局
+价格监督检查与反垄断局
+价格监督检查与反垄断所
+价格监督稽查与反垄断分局
+价格监督检查与反垄断分局
+价格监督与反垄断局
+文化部
+文化厅
+文化局
+文体旅局
+文物管理所
+文化和新闻出版厅
+图书馆
+文保所
+文物管理处
+文体广电站
+文物局
+文广局
+文化站
+文化委员会
+文化馆
+收藏馆
+文物馆
+文化宫
+文体办
+文化市场管理所
+文物考古研究院
+黄帝文化园区建设协调办公室
+文化产业园建设办公室
+文体局
+青少年活动中心
+文物信息咨询中心
+文化产业发展服务中心
+艺术中心
+艺术研究院
+美术家协会
+博物馆
+文物保护管理中心
+美术馆
+青少年宫
+文物保管所
+文化广播新闻局
+文化广电影视局
+非物质文化遗产保护
+民族文化中心
+文物事业管理局
+剧院
+艺术馆
+民族歌舞传习中心
+博物院
+考古研究院
+文物保护管理
+文物管理中心
+文物管理局
+纪念馆
+文化市场行政执法支队
+中国画院
+歌舞团
+中国艺术研究院
+体育宫
+文化广播影视管理局
+民俗文化中心
+电影资料馆
+考古研究所
+广播影视中心
+文化体育办
+艺术团
+文化艺术研究院
+中央民族歌舞团
+剧团
+文广新局
+文化市场综合执法大队
+文化行政综合执法大队
+文化广播影视监测中心
+文化遗产研究院
+文化体育服务所
+文化博览局
+文联
+文物管理站
+影视局
+计算中心
+科技合作研究促进中心
+科学技术情报所
+科技创新发展中心
+科学技术信息研究院
+科学技术情报研究院
+自然资源科学院
+国土资源和房地产信息中心
+国土资源部
+国土资源所
+国土资源分局
+国土资源厅
+国土资源纠纷调处中心
+煤炭工业管理局
+国土分局
+国家海洋信息中心
+国土资源局
+海洋管理事务中心
+测绘局
+土地局
+土地储备分中心
+地籍管理所
+土地资源局
+国土资源管理分局
+国家海洋局
+海洋研究院
+海洋与渔业行政执法处
+海洋港口发展委员会
+海洋与渔业技术中心
+征地拆迁办公室
+海洋与渔业监督监察总队
+国土资源执法监察支队
+地质调查院
+地质调查中心
+地质局
+地矿局
+国土资源出让服务中心
+测绘技术中心
+煤炭工业局
+国土勘测规划研究院
+土地调查规划院
+国土资源执法监察大队
+规土局
+矿产研究所
+国土整治局
+中国海监
+测绘科技研究所
+土地储备供应中心
+国家测绘地理信息局
+国土资源调查监测院
+基础测绘信息中心
+土地督察
+测绘院
+测绘工程院
+国土资源档案信息中心
+国土环境资源局
+测绘研究所
+海洋科学与技术国家实验室
+国土资源和房屋局
+国土局
+国土资源委员会
+土地发展中心
+国土资源和规划委员会
+地质矿产勘查开发局
+国土资源管理局
+地质勘察院
+矿务局
+矿产资源管理中心
+矿产资源管理局
+国土资源信息中心
+国土资源规划与评审中心
+征地办公室
+土地开发整理服务中心
+土地整理中心
+土地登记交易中心
+不动产登记局
+规划和土地管理局
+土地开发中心
+国土资源技术中心
+国土资源和房屋管理局
+地质测试研究中心
+地质调查研究院
+地理信息工程院
+矿产资源研究开发协会
+地质勘查
+勘探队
+海洋勘测设计院
+地质物测队
+地质研究
+国土资源档案馆
+国土开发整治管理局
+国土资源管理所
+海洋局
+海洋环境与资源监测中心
+土地开发征用事务所
+地理信息中心
+测绘地理信息局
+土地登记信息交易中心
+土地开发整理建设管理局
+征地拆迁工作办公室
+征收改造指挥部
+地质环境监测院
+不动产登记中心
+遥感中心
+土地勘测规划院
+极地研究中心
+土地综合整治服务中心
+规划建设土地局
+地质矿产局
+不动产登记事务中心
+海事测绘中心
+旅游局
+旅游发展委员会
+景区建设办公室
+旅游培训中心
+旅游服务业发展局
+景区管理中心
+旅游发展有限公司
+风景名胜区管理局
+景区管理处
+旅游委员会
+旅游监察大队
+窟区管理处
+龙门石窟世界文化遗产园区建设发展局
+旅游发展局
+旅游开发中心
+旅游宣传推广中心
+旅游管理局
+动物园
+旅游信息中心
+旅游工作委员会
+招生办公室
+招生考试中心
+教育考试服务中心
+招生委员会办公室
+成人教育中心
+节能与应对气候变化中心
+应对气候变化研究中心
+应对气候变化办公室
+节能减排监察支队
+节能减排监察总队
+农业面源污染
+生态环境分局
+生态环境智能管控中心
+生态环境综合执法支队
+饮用水源保护管理办公室
+生态环境技术审查中心
+劳动和社会保障监察支队
+人力资源市场管理服务中心
+人才引进交流服务中心
+人力资源服务中心
+人才服务管理办公室
+人力资源和社保保障局
+人才管理服务处
+人力资源管理办公室
+人才开发交流服务中心
+人才管理服务办公室
+社保核算中心
+社会事务服务中心
+流域管理处
+流域管理局
+水务和湖泊局
+湖泊管理局
+水务事务服务中心
+食品药品监督管理总局
+食品药品监督管理局
+食药局
+食安局
+药监局
+食品药品监督所
+药品稽查总队
+食品药品安全信息与监控中心
+食品药品执法大队
+药品不良反应监测中心
+药械管理中心
+药品检验检测院
+食品药品认证审评中心
+安全生产技术支撑中心
+安全生产科技宣教中心
+安全生产考试考核中心
+安全生产技术研究中心
+安全生产执法大队
+安全生产重大危险源与煤矿瓦斯监控中心
+安全生产技术检测检验中心
+安全生产监察执法大队
+安全生产执法支队
+安全生产举报投诉中心
+安全生产应急指挥中心
+安全生产科学技术中心
+安全生产行政执法支队
+安全生产应急服务中心
+安全生产培训协会
+应急管理和安全生产综合行政执法队
+安全生产救援服务中心
+安全生产培训服务中心
+安全生产技术服务中心
+安全生产应急救援支队
+安全生产科学研究所
+安全生产应急救援队
+安全生产科技服务中心
+安全生产信息调度和应急指挥中心
+安全生产科学技术研究中心
+安全生产科技教育中心
+安全生产教育中心
+安全生产考试中心
+安全生产考务中心
+安全生产技术中心
+安全生产检测检验中心
+安全生产监察执法总队
+安全生产培训中心
+安全生产技术培训中心
+安全生产资格考试中心
+安全生产信息调度中心
+安全生产科学技术服务中心
+安全生产宣教中心
+安全生产救护大队
+安全生产服务中心
+安全生产救援救护中心
+安全生产培训教育中心
+安全生产监控中心
+安全生产应急管理中心
+安全生产应急救援大队
+安全生产培训考试考核中心
+安全生产协会
+煤炭安全生产管理局
+安全生产管理监督局
+安全生产管理局
+应急管理事务服务中心
+医疗保险服务管理局
+医疗保险管理中心
+医保中心
+规划建设委员会
+城乡统筹发展局
+规划展览馆
+城建工程管理处
+城建工程管理局
+规划管理办公室
+城乡管理局
+规划信息技术中心
+规划局
+规划设计研究院
+规划勘测院
+规划和城市综合管理局
+规划委员会
+规划处
+规划委
+住房和规划建设管理局
+规划管理局
+城市规划信息中心
+规划展示馆
+农业和畜牧业局
+兽药监察所
+土壤肥料技术站
+土壤肥料研究所
+土壤肥料站
+病虫防治检疫站
+有害生物防治检疫总站
+农牧机械管理局
+水产养殖场
+森林病虫病防治检疫站
+饲料草种监督检验站
+农产品质量监督检验测试分中心
+农产品质量安全监督检验测试中心
+农产品质量安全监督管理办公室
+农畜产品质量安全中心
+农林技术推广中心
+花苗木服务中心
+农林业行政执法大队
+畜牧发展局
+牧草饲料工作站
+白蚁防治管理处
+畜牧兽医管理中心
+畜牧兽医发展中心
+畜禽繁育改良站
+农畜产品质量安全监督站
+畜禽品种改良站
+森林病虫害防治工作站
+花苗木管理所
+动物疫病防治控制中心
+植物保护检疫局
+动植物保护管理总站
+花卉苗木培育中心
+畜牧兽医所
+农业综合执法督察总队
+农业综合执法队
+农业行政综合执法总队
+农畜产品质量安全监督管理中心
+林牧业局
+土肥站
+兽医局
+农林综合行政执法总队
+镇政府
+办事处
+乡政府
+区政府
+教育发展服务中心
+教育信息中心
+教育和体育局
+教育信息化中心
+教育数据信息中心
+教育和科学技术局
+教育和科技局
+教育科技体育局
+教育装备技术中心
+土地交易中心
+土地综合开发中心
+教育矫治局
+监狱
+劳动教养管理局
+劳动教养管理所
+教育矫治所
+戒毒管理局
+戒毒所
+未成年犯管教所
+戒毒管理所
+戒毒康复所
+少年犯管教所
+中国冶金地质总局
+武警
+军区
+军分区
+解放军
+武装警察
+人民防空办公室
+军队离退休干部
+军用供应站
+预备役
+军队离休退休干部
+军粮供应站
+团部
+部队
+边防支队
+军委
+武装部
+军用饮食供应站
+军队粮油供应站
+空军
+军供站
+军粮供应管理中心
+退役军人事务厅
+退役军人事务局
+退役军人事务部
+医院
+卫生院
+医疗集团
+精神病院
+社区卫生服务中心
+门诊部
+康复中心
+精神病防治院
+精神病治疗所
+皮肤性病防治院
+职业病防治院
+妇女儿童医疗中心
+麻风病防治中心
+医疗中心
+临床检验中心
+公共卫生临床中心
+医疗急救中心
+牙病防治所
+医疗救护站
+临床技术研发中心
+肝病研究所
+血液中心
+心血管病中心
+慢性病防治院
+皮肤病防治院
+伤骨科研究所
+口腔病防治院
+中毒抢救治疗中心
+急救中心
+传染病院
+聋儿康复教育中心
+健康教育所
+残疾人康复教育中心
+保育院
+数字医学工程研究院
+保健服务中心
+医药科学研究所
+精神医学中心
+社区健康服务管理中心
+口腔病防治所
+内分泌与代谢病研究所
+肿瘤防治院
+精神病康复院
+医疗急救服务中心
+中医骨伤科研究所
+医疗机构服务中心
+精神康复院
+急救医疗站
+紧急医疗救援中心
+耳鼻咽喉科研究所
+医疗救治中心
+老年病医疗研究中心
+眼科研究所
+卫生分院
+卫生所
+体检康复保健中心
+消毒供应中心
+老年医学中心
+市政工程公司
+市政建设集团
+市政基础设施建设有限公司
+市政公用有限公司
+市政建设开发有限公司
+市政工程维修公司
+市政管网有限公司
+城市绿化队
+环卫清运中心
+市政工程安全质量监督站
+市政工程服务中心
+市政工程养管中心
+市政排水事务中心
+环卫站
+市政服务所
+市政建设所
+市政设施养护所
+市政工程站
+城市道路绿化处
+市容局
+环境卫生监管处
+市容市貌局
+环境卫生发展中心
+市政路灯园林管理所
+政务中心
+行政服务中心
+行政审批服务中心
+民生服务中心
+政务服务管理局
+数据资源管理局
+综合服务中心
+社区服务中心
+行政服务大厅
+政务服务管理办公室
+项目推进服务中心
+政务服务中心
+公共事务中心
+政府服务中心
+便民服务中心
+中央商务区服务中心
+行政审批管理办公室
+行政审批服务局
+行政审批中心
+行政审批局
+政务服务保障中心
+行政审批管理处
+81890求助服务中心
+行政服务管理中心
+办事服务中心
+便民专线服务中心
+行政服务保障中心
+政务大厅管理中心
+行政服务站
+政务大厅管理服务中心
+政务服务局
+政务服务办公室
+政务服务监督管理局
+政务服务监督办公室
+市民服务中心
+新闻网管理中心
+农村经济委员会
+粮油信息中心
+发展和改革委局
+粮油管理处
+粮油储备中心
+粮食中心储备库
+粮食储备库
+中储粮
+储备粮
+国家粮食储备
+粮食储备直属库
+农业经济局
+国家粮食库
+国检珠宝培训中心
+珠宝研究所
+职卫中心
+职业安全卫生研究中心
+职业安全健康监督检测检验中心
+职业安全监督管理站
+职业健康司
+职业健康监督所
+职业病管理处
+保监会
+保险监督管理委员会
+社会保障基金理事会
+工商行政管理总局
+工商行政管理局
+工商总局
+工商局
+市场监管局
+市场和质量监督管理委员会
+市场监督管理局
+市场建设服务中心
+工商质监管理局
+市场和质量监督管理局
+市场发展局
+市场监管稽查大队
+消费者协会
+市场服务中心
+工商和质量监督管理局
+工商质量监督管理局
+工商管理局
+市场和质量监督稽查总队
+工商管理和质量监督局
+消费者权益保护委员会
+消费者权益保护局
+市场监管委员会
+市场监督检验所
+消费者投诉举报中心
+保护消费者权益委员会秘书处
+市场质量监督管理局
+工商行政管理服务中心
+市监局
+交通轨道建设办公室
+机动车辆安全检验站
+大数据局
+数据资源局
+工业园区管理委员会
+工业园区管委会
+营商环境建设监督局
+重点项目服务中心
+桂城投资发展公司
+国家质检总局
+特种机电设备检测研究院
+旅游事业局
+中国国家铁路集团
+机务段
+供电段
+动车段
+广铁
+煤炭地质总局
+地质工程勘察院
+地质实验研究所
+勘察测绘研究院
+国土规建局
+海洋环境预报中心
+矿产资源管理所
+旅游开发有限公司
+旅游发展股份有限公司
+旅游质量监督管所
+旅游开发有限责任公司
+旅游监察支队
+交通投资集团
+交投
+交通投资建设集团
+港航发展中心
+道路运输发展中心
+公路发展中心
+交通运输工程质量监测鉴定中心
+停车管理事务中心
+不动产中心
+街道拆迁办公室
+海洋环境监测与预报中心
+开垦整理中心
+征收储备局
+资源服务中心
+复垦中心
+统征整理中心
+资源管理事务中心
+勘测规划所
+资源监察大队
+规划建设执法监察队
+资源规划研究院
+储备事务中心
+渔政站
+渔政管理局
+渔政渔监站
+渔政渔港监督管理局
+渔政船检港监管理局
+农业科学推广研究院
+水产技术指导站
+畜牧良种场
+国际农发基金贷款项目管理办公室
+海洋和渔业厅
+海洋和渔业局
+不动产交易所
+地质测量队
+地质矿产勘查院
+地质勘察技术院
+海洋环境监测预报中心
+不动产交易登记中心
+拆迁安置工作指挥部
+征地拆迁管理处
+耕地质量管理处
+城乡规划局
+城乡规划司
+城乡规划管理局
+城乡规划
+城乡规划建设委员会
+草原监理站
+草原工作站
+草原资源监测管理站
+森林和草原资源监测站
+草原监督管理局
+林业局
+林业厅
+园林局
+园林绿化局
+园林绿化管理处
+园林绿化管理局
+林业管理站
+森林资源保护中心
+森林资源监测中心
+森林公园
+林业委员会
+林业技术推广中心
+林业管理局
+园林管理处
+园林办
+林业工作站
+园林管理局
+园林绿化科研所
+林业工作总站
+生态公益林管理中心
+森林工业总局
+林业科学研究所
+森林病虫害防治检疫站
+林业总场
+林木种苗总站
+林业站
+绿化管理所
+林业实验局
+园林绿地所
+园林绿化所
+园林处
+果树研究所
+林业科技推广站
+绿化管理事务中心
+林业管理处
+林政执法大队
+林业种苗管理站
+林业和城乡绿化局
+园林绿化事业局
+公园管理局
+绿化管理署
+林业技术推广服务中心
+林业技术推广站
+林业服务中心
+林业科技示范中心
+林业信息中心
+果业局
+园林所
+植物园
+林果科技服务中心
+林业工作中心站
+林木种质资源中心
+林业调查规划院
+林业建设服务中心
+绿化管理指导站
+民政办
+养老服务中心
+社会福利管理服务中心
+婚姻登记服务中心
+社会救助局
+养老服务指导中心
+社会救助福利中心
+婚姻登记中心
+婚姻登记处
+民政服务中心
+社会救助管理局
+社会福利总院
+婚姻收养登记管理中心
+防灾减灾局
+防灾减灾中心
+灾害监测预警中心
+海洋预报减灾中心
+政法委
+政法委员会
+卫生和计划生育委
+卫计委
+卫生局
+计生委
+计生局
+卫生信息中心
+卫生计生宣传中心
+卫生监督局
+血站
+卫生和计划生育局
+急救指挥中心
+医疗服务管理评价中心
+计划生育服务站
+卫生厅
+卫计局
+计划生育服务中心
+肿瘤研究所
+中医药管理局
+中药研究所
+医疗器械质量监督检验院
+药物依赖防治研究所
+计划生育局
+疾控中心
+计划生育监督所
+医疗器械检测中心
+中医文献馆
+卫生和计划生育监督执法支队
+卫生计生监督所
+卫生服务中心
+临床病理诊断中心
+卫生事业服务中心
+公共卫生救治中心
+保健站
+劳动卫生职业病研究院
+急救指挥调度中心
+急救站
+卫生计生综合监督执法局
+妇幼保健
+公立医疗机构管理中心
+卫生和计划生育信息中心
+精神卫生防治中心
+卫生干部教育培训中心
+医疗紧急救援中心
+卫生计生监督执法局
+计划生育技术服务站
+卫生监督所
+优生优育公共服务中心
+卫生计生委统计信息中心
+医药研究院
+残疾人康复服务中心
+医疗卫生专业服务中心
+计划生育技术指导站
+急救医疗指挥中心
+公共卫生医疗救治中心
+防治站
+防疫站
+健康促进中心
+卫生计生信息中心
+卫生计生委
+血防站
+医疗器械检验所
+人口宣传教育中心
+人口和计划生育宣传教育中心
+卫生宣传教育中心
+计划生育宣传教育中心
+医学交流中心
+计划生育研究中心
+卫生和计划局
+卫生健康委员会
+卫生计生执法支队
+残疾人安养院
+妇女儿童保健中心
+地方病防治办公室
+医学科学研究所
+健康体检中心
+计划生育宣传技术指导站
+计划生育办公室
+计划生育药具管理站
+医疗器械检测所
+医疗器械检验研究中心
+医疗器械检验检测所
+医疗救援指挥中心
+残疾人康复研究中心
+中医药发展服务中心
+职业病防治研究院
+计划生育研究所
+计划生育委员会
+卫生和计划委员会
+卫生和计划生局
+中药研究院
+血吸虫病防治领导小组办公室
+防治艾滋病工作委员会办公室
+妇幼计生中心
+残疾人康复指导中心
+血吸虫病预防站
+卫生检疫站
+残疾人康复管理中心
+皮肤病防治管理站
+优生优育宣传中心
+残疾人康复服务指导中心
+血吸虫病防治办公室
+妇幼儿童保健中心
+血吸虫病预防控制站
+残疾人康复托养中心
+卫生与健康委员会
+计生协会
+卫生和计生委员会
+卫生计生综合监督处
+卫生和计划生育监督执法总队
+中医药发展局
+地方病预防控制所
+卫生与生育计划局
+卫生计生综合行政执法队
+卫生计生综合监督行政执法支队
+计划生育药品器械管理站
+卫生健康局
+公共卫生与计划生育管理所
+公共卫生研究院
+派出所
+人力资源事务服务中心
+就业人才局
+人力资源和社会保障服务中心
+社会事务管理中心
+土地价格监测中心
+土地价格评估所
+国土价格管理处
+土地价格与征地事务中心
+国土资源交易事务中心
+国土资源交易和登记服务中心
+国土资源交易事务所
+国土资源交易评审中心
+国土资源交易拍卖中心
+国土资源交易管理办公室
+国土资源交易局
+土地矿业权储备交易中心
+质量监督检验检疫总局
+质量技术监督局
+质监局
+组织机构代码管理中心
+质量检测
+设备监督检验中心
+标准化研究院
+检验检测中心
+产品质量监督检验院
+锅炉压力容器监督检验院
+计量检定所
+质量监督检测研究院
+质量技术监督检验测试中心
+入境边防检查站
+技术监督局
+质量监督检验研究院
+质量技术监督
+产品检验检测
+质量检验检测
+质量监督检验技术研究院
+特种设备检测研究院
+特种设备检验检测院
+质量认证中心
+检疫检验局
+物品编码中心
+检验检测研究院
+质量监督检验检测院
+计量检定测试中心
+计量监督检测院
+质量计量监测所
+花鼓戏传承保护中心
+文化交流服务中心
+艺术保护传承中心
+文化市场行政执法大队
+柳子戏艺术保护传承中心
+文化市场综合执法支队
+文化事业发展局
+湘剧保护传承中心
+文化和旅游厅
+文物研究所
+林草局
+林业和草原局
+国家公园管理局
+生态保护修复司
+绿化委员会办公室
+森林资源管理司
+森林资源管理总站
+草原管理司
+湿地管理司
+国际湿地公约履约办公室
+荒漠化防治司
+野生动植物保护司
+濒危物种进出口管理办公室
+自然保护地管理司
+国有林场和种苗管理司
+森林公安局
+林业和草原改革发展司
+林业和草原科学研究所
+草原总站
+草原综合专业队
+林业草原规划院
+林业和草原调查规划院
+林业和草原发展服务中心
+林业和草原项目服务中心
+林业和草原服务中心
+林业和草原技术推广中心
+草原生态综合执法大队
+草原饲料工作站
+草原生态保护补助奖励工作领导小组办公室
+草原监理总站
+草原有害生物监测预警中心
+草原监督所
+林业与草原局
+森林和草原局
+环境保护和草原局
+草原监理工作总站
+草原监理中心
+林业和草原自然资源事务中心
+草原工作总站
+草原确权承包工作领导小组办公室
+草原沙化治理工程领导小组办公室
+草原饲料监督管理站
+草原管理所
+草原工件站
+草原监理检测站
+草原监理监测站
+草原管理站
+草原监理所
+草原技术推广总站
+草原监理工作站
+草原综合治理项目建设办公室
+草原勘察规划院
+湿地草原管理处
+草原监督管理站
+草原基本建设综合专业队
+草原管理总站
+草原饲料管理站
+草原技术推广站
+林业草原保护中心
+林业草原局
+林业和草原司
+林业和草原管理局
+森林草原指挥部
+野生动植物保护办公室
+野生动植物保护处
+野生动植物保护管理处
+野生动植物保护管理局
+野生动植物保护管理所
+野生动植物保护管理中心
+野生动植物保护和自然保护区管理中心
+野生动植物保护救护站
+野生动植物保护协会
+野生动植物保护研究中心
+野生动植物保护中心
+农牧林水局
+林业和草原基金管理总站
+林业基金管理站
+林业建设基金征收管理站
+林业基金管理总站
+林业基金管理办公室
+林业基金站
+林业基金征收管理站
+林业和草原重点工程基金总站
+中国林业科学研究院
+中国绿色时报社
+中国林业出版社
+国际竹藤中心
+南京森林警察学院
+中国大熊猫保护研究中心
+地质调查局
+环境地质水文地质部
+中国地质科学院
+地质科技创新中心
+广州海洋地质调查局
+青岛海洋地质研究所
+中国自然资源航空物探遥感中心
+中国地质环境监测院
+自然资源实物地质资料中心
+中国地质图书馆
+国家地质实验测试中心
+北京探矿工程研究所
+地质环境监测总站
+自然资源和规划局
+自然资源标林业和湿地事务服务中心
+自然资源林业局
+自然资源林业和草原局
+自然资源林业和湿地事务服务中心
+自然资源项目中心
+自然资源陕西省卫星应用技术中心
+自然资源遥感院
+自然资源遥感技术管理中心
+自然资源资料馆
+自然资源资料档案馆
+自然资源资产统计核算中心
+自然资源资产管理局
+自然资源资产管理办公室
+自然资源调查院
+自然资源调查评价研究院
+自然资源调查设计测绘中心
+自然资源调查规划院
+自然资源调查规划站
+自然资源调查规划研究中心
+自然资源调查规划中心
+自然资源调查监测院
+自然资源调查监测信息中心
+自然资源调查监测中心
+自然资源调查登记中心
+自然资源调查测绘中心
+自然资源调查勘测院
+自然资源调查利用中心
+自然资源调查中心
+自然资源调查与规划中心
+自然资源调查与登记中心
+自然资源调处服务中心
+自然资源规划院
+自然资源规划编制研究中心
+自然资源规划编制服务中心
+自然资源规划研究中心
+自然资源规划测绘信息院
+自然资源规划服务中心
+自然资源规划执法支队
+自然资源规划建设局
+自然资源规划展示和档案馆
+自然资源规划局
+自然资源规划和测绘中心
+自然资源规划勘测院
+自然资源规划分局
+自然资源规划事务服务中心
+自然资源规划中心
+自然资源行政执法队
+自然资源行政执法支队
+自然资源行政执法大队
+自然资源行业职业技能鉴定指导中心
+自然资源自动化中心
+自然资源耀州分局
+自然资源网上交易中心
+自然资源综合调查监测院
+自然资源综合调查中心
+自然资源综合行政执法队
+自然资源综合行政执法西青支队
+自然资源综合行政执法支队
+自然资源综合行政执法总队
+自然资源综合行政执法局
+自然资源综合行政执法大队
+自然资源综合服务中心
+自然资源综合整治技术服务中心
+自然资源综合整治中心
+自然资源综合执法队
+自然资源综合执法支队
+自然资源综合执法大队
+自然资源综合事务技术保障中心
+自然资源管理行政执法支队
+自然资源管理服务中心
+自然资源管理所
+自然资源管理局
+自然资源管理委员会
+自然资源管理和规划局
+自然资源管理中心
+自然资源科学研究院
+自然资源确权登记服务中心
+自然资源确权登记交易服务中心
+自然资源确权登记事务中心
+自然资源确权登记中心
+自然资源研究规划中心
+自然资源省局
+自然资源监测院
+自然资源监测省级试点
+自然资源监测和生态修复中心
+自然资源监测信息中心
+自然资源监测中心
+自然资源监察支队
+自然资源的规划局
+自然资源登记管理中心
+自然资源登记中心
+自然资源电子政务中心
+自然资源生态环境和建设管理局
+自然资源生态修复整治中心
+自然资源生态修复和海洋管理服务中心
+自然资源生态修复中心
+自然资源环境监测站
+自然资源湿地生态保护中心
+自然资源测绘队
+自然资源测绘设计中心
+自然资源测绘数据中心
+自然资源测绘成果管理中心
+自然资源测绘地理信息规划院
+自然资源测绘中心
+自然资源测绘与监测院
+自然资源检测和生态修复中心
+自然资源档案馆
+自然资源档案博物馆
+自然资源档案中心
+自然资源权籍调查服务中心
+自然资源权益调查监测院
+自然资源权益与储备保障中心
+自然资源服务所
+自然资源服务与行政执法中心
+自然资源智能监管指挥中心
+自然资源整治储备中心
+自然资源整治修复中心
+自然资源整治中心
+自然资源数据资料管理中心
+自然资源数据信息中心
+自然资源数据中心
+自然资源改革发展研究中心
+自然资源收购储备中心
+自然资源收储与开发中心
+自然资源技术服务中心
+自然资源技术信息中心
+自然资源技术中心
+自然资源执法监察支队
+自然资源执法监察总队
+自然资源执法监察局
+自然资源执法监察大队
+自然资源执法支队
+自然资源执法大队
+自然资源所
+自然资源所有者权益事务中心
+自然资源成果质量检验中心
+自然资源征收中心
+自然资源开发服务中心
+自然资源开发整理中心
+自然资源应急中心
+自然资源市场管理服务中心
+自然资源巡查督察中心
+自然资源宣教中心
+自然资源宣传教育中心
+自然资源宣传中心
+自然资源安全调度中心
+自然资源委员会怀柔分局
+自然资源大数据中心
+自然资源坪山管理局
+自然资源地理规划信息中心
+自然资源国土空间规划服务中心
+自然资源和规建局
+自然资源和规化局
+自然资源和规刬局
+自然资源和规划
+自然资源和规划林业综合行政执法支队
+自然资源和规划高新技术产业园区分局
+自然资源和规划高新分局
+自然资源和规划行政执法支队
+自然资源和规划蜀冈瘦西湖风景名胜区分局
+自然资源和规划综合服务中心
+自然资源和规划综合执法支队
+自然资源和规划综合执法大队
+自然资源和规划管理局
+自然资源和规划管理中心
+自然资源和规划研究中心
+自然资源和规划监察支队
+自然资源和规划用地服务中心
+自然资源和规划档案馆
+自然资源和规划服务中心
+自然资源和规划技术中心
+自然资源和规划执法监察支队
+自然资源和规划执法监察大队
+自然资源和规划所
+自然资源和规划征拨事务中心
+自然资源和规划建设局
+自然资源和规划姜堰分局
+自然资源和规划大数据中心
+自然资源和规划司
+自然资源和规划厅
+自然资源和规划勘测事务中心
+自然资源和规划分局
+自然资源和规划信息中心
+自然资源和规划住建局
+自然资源和规划事务服务中心
+自然资源和规划中心
+自然资源和行政执法队
+自然资源和生态环境处
+自然资源和生态环境保护服务中心
+自然资源和生态保护执法大队
+自然资源和水务局
+自然资源和建设管理局
+自然资源和建设局
+自然资源和城乡建设局
+自然资源和地理空间基础信息库项目办公室
+自然资源和信息中心
+自然资源和不动产评估发展研究中心
+自然资源和不动产确权登记中心
+自然资源和不动产权属档案中心
+自然资源司
+自然资源发展中心
+自然资源及规划局
+自然资源卫星应用技术中心
+自然资源博物馆
+自然资源勘测调查院
+自然资源勘测设计院
+自然资源勘测规研究划院
+自然资源勘测规划院
+自然资源勘测规划设计院
+自然资源勘测规划研究院
+自然资源勘测技术咨询服务中心
+自然资源勘测中心
+自然资源利用规划院
+自然资源利用服务中心
+自然资源利用事务中心
+自然资源分局
+自然资源储备整理信息中心
+自然资源储备整理中心
+自然资源储备开发中心
+自然资源储备利用中心
+自然资源储备交易事务中心
+自然资源储备中心
+自然资源偹整理信息中心
+自然资源修复事务中心
+自然资源修复中心
+自然资源信息管理中心
+自然资源信息档案中心
+自然资源信息服务中心
+自然资源信息中心
+自然资源保标护与行政执法中心
+自然资源保招护与行政执法中心
+自然资源保护行政执法支队
+自然资源保护管理中心
+自然资源保护检查中心
+自然资源保护开发利用中心
+自然资源保护工作站
+自然资源保护处
+自然资源保护和发展中心
+自然资源保护事务服务中心
+自然资源保护中心
+自然资源保护与行政执法中心
+自然资源保护与发展中心
+自然资源保护与利用中心
+自然资源产品质量检验中心
+自然资源交易服务中心
+自然资源交易和建设用地事务中心
+自然资源交易事务中心
+自然资源交易中心
+自然资源事物服务中心
+自然资源事务管理中心
+自然资源事务服务中
+自然资源事务服务中心
+自然资源事务中心
+自然资源事业发展中心
+自然资源与规划档案馆
+自然资源与规划技术中心
+自然资源与规划建设管理局
+自然资源与规划建设局
+自然资源与规划局
+自然资源与规划厅
+自然资源与规划信息中心
+自然资源与行政执法中心
+自然资源与科技信息研究所
+自然资源与生态环境局
+自然资源与生态保护综合执法局
+自然资源与生态保护综合执法大队
+自然资源与房屋征收服务中心
+自然资源与建设规划局
+自然资源与建设管理局
+土地综合整治局
+自然资源所有者权益处
+国土空间利用服务中心
+国土空间生态修复中心
+国土空间整治与生态修复中心
+耕地保护处
+耕地质量保护监测中心
+矿业权交易中心
+土地与矿业权公开交易管理办公室
+土地和矿业权收购储备交易中心
+矿产资源监督检测中心
+矿产资源开发保护中心
+矿产资源保护中心
+地理信息管理处
+地理信息管理中心
+自然资源督察办公室
+基础测绘设施技术保障中心
+国土空间规划编制研究中心
+国土空间规划研究中心
+国土空间规划技术研究中心
+国土资源中心所
+应急中心
+指挥部
+指挥中心
+作战训练处
+特种灾害救援处
+信息通信处
+机关事务
+机关行政事务
+军休
+教育后勤管理处
+环境综合整治指挥部
+市政工程建设
+市政建设
+行政服务
+办公室
+委员会
+办公厅
+对外联络部
+信息中心
+首饰管理中心
+质量监督检验中心
+首饰行业协会
+标准化技术委员会
+防火指挥部
+综合行政执法大队
+执法总队
+服务局
+公共服务中心
+信息市场
+资源局
+资源开发管理办公室
+公积金管理
+兽医研究所
+水产业发展服务中心
+兽医中心
+兽医总站
+服务中心
+人防指挥保障中心
+科学发展研究院
+管理中心
+数据中心
+信息管理中心
+后勤保障中心
+执法督察总队
+监督局
+执法监督大队
+监督行政执法大队
+监督执法大队
+综合监督所
+管理站
+监督分所
+监督中心
+服务所
+管理服务中心
+保健院
+妇保院
+妇幼站
+妇幼卫生保健所
+质量计量监督
+特种设备
+纤维
+质量计量
+产品质量
+质量监督
+研究院
+检测所
+检验所
+检测中心
+测试所
+检验局
+研究所
+检定中心
+检验中心
+防洪工程
+防洪治理工程
+排涝工程
+堤防工程
+防洪排涝工程
+山洪灾害防治
+除险加固工程
+堤防
+防洪堤
+劳动保障
+执法大队
+执法支队
+监察大队
+监察支队
+交通建设项目
+交通项目
+交通工程
+铁路建设
+交通工程建设
+救护中心
+保护管理站
+保护与检疫站
+保护站
+疫病预防控中心
+检疫站
+救护研究中心
+管理办公室
+工作站
+疾病控制与诊断中心
+国土整治
+土地储备项目
+土地整治
+测试院
+监测中心
+检验站
+湿地公园
+湿地保护
+古城保护
+景区
+旅游区
+建设管理处
+管理局
+安全生产
+安全质量
+管理协会
+监察总队
+监管管理局
+应急救援中心
+监督管理局
+管理服务站
+纠纷调处中心
+管理处
+管理指导中心
+交通行政
+交通综合
+交通运输
+行政执法支队
+质量安全监管办公室
+执法监察支队
+质量安全管理办公室
+行政执法大队
+种质资源中心
+机械试验鉴定站
+机械安全监理所
+机械研究院
+质量安全监管中心
+经营管理工作站
+机械安全监督管理所
+质量安全检验检测站
+发展服务中心
+质量安全中心
+执法监察大队
+行政执法总队
+品种资源研究所
+综合执法大队
+质量安全监督所
+栽培技术指导站
+技术推广站
+质量检验测试中心
+科学技术推广站
+质量安全监督检验检测站
+质量安全监督管理中心
+机械管理处
+质量安全管理总站
+科学技术普及站
+技术推广所
+技术管理中心
+监测总站
+技术推广中心
+机械中心
+发展局
+农村厅
+综合办公室
+质量安全监督检测站
+安全检测中心
+研究中心
+机械管理站
+电子政务
+国有土地
+稀土工业
+交通调度
+高速公路
+交通信息
+指挥总中心
+网管中心
+监控中心
+应急处置中心
+检验研究院
+检定研究院
+评估中心
+旅游发展
+旅游事务
+信息管理办公室
+预算稽核中心
+审核中心
+农业技术推广
+畜牧兽医技术
+农业科学
+热带作物
+畜牧兽医
+种植业
+电子商务
+征收管理局
+征收管理分局
+离休干部
+机关干部
+休养所
+培训中心
+服务台
+试验站
+技术中心
+装备中心
+探测中心
+共产主义青年团
+省委
+市委
+县委
+团校
+共产党
+开发区
+商务中心区
+开发建设指挥部
+电化教育
+教学仪器
+教育技术
+教育装备
+管理所
+装备站
+设备站
+装备室
+发展研究中心
+塑料工业
+工业技术
+信息技术
+治霾工作
+防沙治沙
+生态保护修复项目
+艺术
+文化
+戏剧
+交流中心
+国土
+地质
+矿产
+调查局
+勘查局
+规划院
+食品药品
+食品
+食品质量
+食品安全
+稽查支队
+安全监控中心
+检测研究院
+安全检测技术研究院
+监管局
+检验检测院
+检验检测与认证中心
+监管综合行政执法队
+稽查局
+稽查队
+监管所
+监督检验院
+稽查大队
+稽查服务中心
+监督管理大队
+监督管理司
+监督管理处
+抽检监测司
+抽检监测局
+抽检监测处
+经营
+管理
+公司
+客运交通
+农村合作经济
+管理总站
+管理指导站
+土地储备
+土地收购
+土地收储
+土地整改
+土地征用
+土地开发
+土地利用
+土地复垦
+土地整理
+国土资源
+储备中心
+整治中心
+交易中心
+整改中心
+整理中心
+整理服务中心
+发展中心
+开发中心
+旅游产业
+公路
+养护工程处
+公路事务中心
+养护工程部
+养护段
+建设
+扶贫
+开发办公室
+国土资源执法监察
+局
+队
+交通
+路政
+监督站
+质量监督所
+管理大队
+柑桔科学
+蔬菜
+不动产登记
+中心
+旅游
+管委会
+监督管理所
+发展促进局
+综合执法支队
+渔政渔船
+检验监督
+白蚁
+防治研究所
+防治所
+土地
+耕地质量
+监测保护中心
+资源监察支队
+资源事务管理所
+资源勘测队
+勘测所
+收储中心
+工程测绘队
+渔政
+渔港
+渔业
+管理监督处
+技术推广总站
+船检港监站
+储备开发
+收储开发
+行政执法监察大队
+征费稽查站
+动态监测中心
+储备局
+调查办公室
+科技中心
+整备中心
+储备办公室
+管理事务所
+消防救援
+总队
+支队
+大队
+市公安
+区公安
+县公安
+分局
+社会保障卡
+城市建设
+棚户区改造
+住房改造
+旅游小镇工程
+整体搬迁改造
+旧城改造
+街区保护开发
+征迁改造
+村镇建设
+重点工程
+城乡一体化
+片区搬迁工作设
+采煤沉陷区综合治理办公室
+保障性安居工程
+城镇化项目
+危房改造
+城镇建设
+新城建设
+城中村改造
+老街建设
+度假区建设
+城市建设工程
+小区应急改造
+综合开发建设
+项目部
+事务中心
+交通发展集团
+交通一卡通
+机场
+地铁
+轨道交通
+交通产业发展
+地下铁道
+高速发展
+交通控股
+泵闸
+堤闸
+水资源
+水闸
+河道
+引水工程
+河流治理项目
+饮水安全工程项目
+移民扶持项目
+水利工程
+水保工程
+饮水安全
+饮改水工程
+南水北调工程
+饮水扶贫工程
+节水改造工程
+水土保持工程
+河道综合治理工程
+水电工程
+节水配套改造工程
+黑臭水体治理工程
+河流治理
+河治理工程
+水库工程
+水土保持
+灌区
+安全饮水工程
+水源工程
+饮用水工程
+饮水工程
+节水灌溉
+干渠维修工程
+地下水
+灌溉工程
+农田水利
+河堤
+水利枢纽
+水务工程
+水库建设
+泵站
+水利项目
+五水共治
+淮河流域
+水库
+水电
+水务
+供水工程
+水文
+水利
+水土
+建设处
+建设站
+监测站
+管理署
+建设指挥部
+建设运营中心
+勘测局
+规划设计院
+移民工程建设处
+服务站
+勘测设计院
+工作局
+科学研究所
+工作总站
+规划队
+监测分站
+农业机械
+农机
+农业技术
+水产技术
+农业科技
+鉴定站
+推广总站
+推广站
+推广服务站
+推广中心
+机关
+后勤发展中心
+运行保障中心
+市容
+市政
+环境事务所
+监察中队
+养护队
+设施监管处
+建设服务所
+养护处
+设施监管中心
+设施维护所
+环境绿化维护中心
+工程质量监督站
+新闻
+宣传中心
+农垦
+粮食
+粮油
+品质检验所
+战略储备库
+市场监督稽查队
+管理储备中心
+监察执法队
+食品站
+残疾人教育
+语言文字培训
+师资培训
+教育研究
+教育科研
+测试中心
+国家超级计算
+自然保护区
+管护局
+管护分局
+环保
+环境保护
+环境
+市场监督
+文物
+活动中心
+保护研究所
+保护研究院
+保护中心
+综合执法队
+传播有限公司
+发展有限公司
+护林防火
+森林防火
+事业局
+养护中心
+养护事务中心
+建设工程
+建筑工程
+民用航空
+航务
+客运
+治理非法超限超载
+水利水电技术
+供水
+排水
+水务工程建设
+水产
+农业综合开发
+牧区综合改革
+农业发展
+基本农田建设
+高标准农田提升工程
+供热
+热力
+公用工程
+照明
+亮化
+路灯
+公用事业
+环境卫生
+公共工程
+公共卫生服务
+市政养护
+城市隧道
+区
+市
+县
+建设兵团
+省
+信息化中心
+网络中心
+网络管理中心
+大数据中心
+信息服务中心
+信息化促进中心
+信息化建设促进中心
+信息化服务中心
+信息化管理办公室
+信息资源管理中心
+信息资源中心
+网络管理服务中心
+信息化建设办公室
+网络信息中心
+数字化服务中心
+信息化建设中心
+粮食生产能力规划项目
+教育工作联席会议
+教育体育管理
+教育管理
+勤工俭学管理
+社会
+大数据
+发展办公室
+渣土
+污水
+结核病
+地方病
+疾病
+眼病
+慢性病
+传染病
+皮肤病
+血吸虫病
+职业病
+疾控
+防治院
+控制中心
+防治中心
+预防中心
+治疗所
+收治中心
+预防院
+公园
+园林
+绿化
+公益林
+国有林
+经济林
+绿化管护
+森林
+林业
+林木
+护林
+绿化服务中心
+绿化养护大队
+生态研究所
+规划设计大队
+种苗站
+资源管理局
+种苗管理站
+监测规划院
+技术指导站
+种苗管理总站
+资源管护站
+资源收储中心
+育种研究中心
+资源管理站
+科技推广总站
+有害生物防治检疫局

+ 51 - 0
common/src/qfw/util/fsw/readdict.go

@@ -0,0 +1,51 @@
+package fsw
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+)
+
+//敏感词库
+var link map[string]interface{}
+
+//读取字段
+func ReadFswDict(config ...string) {
+	var fi *os.File
+	if len(config) == 0 {
+		fi1, err := os.Open("./mosaic_fsw.dict")
+		log.Println("err:", err)
+		fi = fi1
+	} else {
+		fi, _ = os.Open(config[0])
+	}
+	defer fi.Close()
+	bs, _ := ioutil.ReadAll(fi)
+	fswwords := strings.Split(string(bs), "\r\n")
+	if len(fswwords) == 1 {
+		fswwords = strings.Split(string(bs), "\n")
+	}
+	//要组装关键字
+	link = make(map[string]interface{})
+	var nowMap *map[string]interface{}
+	for _, key := range fswwords {
+		nowMap = &link
+		for i := 0; i < len(key); i++ {
+			kc := key[i : i+1]
+			if v, ok := (*nowMap)[kc]; ok {
+				nowMap, _ = v.(*map[string]interface{})
+			} else {
+				newMap := map[string]interface{}{}
+				newMap["YN"] = "N"
+				(*nowMap)[kc] = &newMap
+				nowMap = &newMap
+			}
+
+			if i == len(key)-1 {
+				(*nowMap)["YN"] = "Y"
+			}
+
+		}
+	}
+}

+ 53 - 0
common/src/qfw/util/jy/appToken.go

@@ -0,0 +1,53 @@
+package jy
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	qutil "app.yhyue.com/moapp/jybase/common"
+	"strings"
+)
+
+//用户信息
+type WxUserInfo struct {
+	City         string `json:"city"`
+	Country      string `json:"country"`
+	CreateTime   int64  `json:"createtime"`
+	HeadImageUrl string `json:"headimgurl"`
+	Nickname     string `json:"nickname"`
+	OpenId       string `json:"openid"`
+	Province     string `json:"province"`
+	Sex          int    `json:"sex"`
+	Sign         string `json:"sign"`
+	UnionId      string `json:"unionid"`
+}
+
+func GetMsgFromWxSign(sign string) (wxinfo *WxUserInfo, err error) {
+	wxinfo = &WxUserInfo{}
+	if sign == "" {
+		err = fmt.Errorf("获取信息异常,参数为空")
+		return
+	}
+	b, decodeErr := base64.StdEncoding.DecodeString(strings.Replace(sign, " ", "+", -1))
+	if decodeErr != nil {
+		err = fmt.Errorf("获取信息异常,解密失败")
+		return
+	}
+	UnmarshalErr := json.Unmarshal(b, &wxinfo)
+	if UnmarshalErr != nil {
+		err = fmt.Errorf("获取信息异常,格式异常")
+		return
+	}
+	//做比对,防篡改
+	checkSign := qutil.GetMd5String(fmt.Sprintf("city=%s&country=%s&createtime=%d&headimgurl=%s&nickname=%s&openid=%s&province=%s&sex=%d&unionid=%s",
+		wxinfo.City, wxinfo.Country, wxinfo.CreateTime, wxinfo.HeadImageUrl, wxinfo.Nickname, wxinfo.OpenId, wxinfo.Province, wxinfo.Sex, wxinfo.UnionId))
+	if wxinfo.Sign != checkSign {
+		err = fmt.Errorf("获取信息异常,校验错误")
+		return
+	}
+	if wxinfo.OpenId == "" {
+		err = fmt.Errorf("获取信息异常,openid为空")
+		return
+	}
+	return
+}

+ 343 - 0
common/src/qfw/util/jy/bigVipPower.go

@@ -0,0 +1,343 @@
+package jy
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"math/rand"
+	"strings"
+	"time"
+
+	qutil "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+)
+
+// 大会员状态redis缓存
+type BigVipBaseMsg struct {
+	Status          int                    `json:"status"`          //大会员状态
+	Used            bool                   `json:"used"`            //是否首次使用大会员
+	PowerMap        map[int]bool           `json:"p_map"`           //权限列表
+	ProNum          int                    `json:"p_num"`           //可关注项目数量
+	EntNum          int                    `json:"e_num"`           //可关注企业数量(取企业情报监控和企业中标动态中最大的值)
+	DailyNum        int                    `json:"d_num"`           //数据包导出数量
+	Pid             string                 `json:"pid"`             //若为子账号此处为父节点userid
+	Uid             string                 `json:"uid"`             //用户id
+	HasTrial        bool                   `json:"has_trial"`       //是否试用过
+	Customers       int                    `json:"customers"`       //可关注客户数量
+	VipStatus       int                    `json:"vip_status"`      //超级订阅状态
+	Vip_BuySet      BuySet                 `json:"vip_buyset"`      //超级订阅套餐内容
+	EntnicheStatus  int                    `json:"entniche_status"` //商机管理状态
+	IsUpgrade       bool                   `json:"isUpgrade"`       //是否是免费用户订阅升级用户 默认true
+	Registedate     int64                  `json:"registedate"`     //用户注册时间
+	EntName         string                 `json:"entname"`         //企业名称
+	EntIsNew        bool                   `json:"entIsNew"`        //是否是新版商机管理用户
+	IsEntService    bool                   `json:"isEntService"`    //是否有商机管理服务
+	PrivateGD       bool                   `json:"PrivateGD"`       //广东移动DICT 用户
+	Email           string                 `json:"email"`
+	EntInfo         map[int]*EntInfoStruct `json:"entInfo"`         //企业信息
+	MemberPowerType int64                  `json:"memberPowerType"` //大会员权益类型 0无 1个人 2企业
+	VipPowerType    int64                  `json:"vipPowerType"`    //超级订阅权益类型 0无 1个人 2企业
+	BaseUserId      int                    `json:"base_user_id"`    //用户base_user_id
+}
+
+type EntInfoStruct struct {
+	IsNew     bool   `json:"isNew"`     //是否是新版商机管理
+	IsPower   bool   `json:"isPower"`   //是否分配了商机管理权限
+	Status    int    `json:"status"`    //是否购买
+	Name      string `json:"name"`      //企业名称
+	IsService bool   `json:"isService"` //企业商机管理服务
+	RoleId    int    `json:"roleId"`    //角色默认0:员工; 1:系统(企业)管理员;2:部门管理员
+}
+
+// 超级订阅购买内容
+type BuySet struct {
+	Upgrade         int `json:"upgrade"`         //是否是升级版;1是 其他不是
+	AreaCount       int `json:"areacount"`       //省份数量
+	BuyerclassCount int `json:"buyerclasscount"` //行业数
+}
+
+var FrontService, BackService map[string][]int
+
+const (
+	IsNewFreeTimeCell       = 1637830020
+	BigmemberServiceTable   = "bigmember_service"
+	BigmemberUserPowerTable = "bigmember_service_user"
+
+	PowerCacheDb              = "other"
+	PowerCacheKey             = "bigmember_power_3_%s"
+	OneDay                    = 60 * 60 * 24
+	UserUpdateAreaKey         = "free_area_num_%s_%s"
+	BaseAreaNum               = 1
+	VipFileUploadNumKey       = "vip_file_num_%s_%s"  //超级订阅附件本月一下载次数 %s:userid ; %s:当前月份-fmt.Sprint(time.Now().Month())
+	FilePackNumKey            = "file_pack_num_%s_%s" //附件下载包本月  剩余次数  %s:userid   %s 当前月份-fmt.Sprint(time.Now().Month())
+	BaseInfoCacheDb           = "newother"
+	IsGetUserBaseInfoRedisKey = "baseinfo_%s"
+	RedisMenuKeyPC            = "jy_workdesktopmenu_10000_PC_%s"  //剑鱼appid:10000
+	RedisMenuKeyWX            = "jy_workdesktopmenu_10000_WX_%s"  //剑鱼appid:10000
+	RedisMenuKeyAPP           = "jy_workdesktopmenu_10000_APP_%s" //剑鱼appid:10000
+	UserPowerRedisKey         = "jy_userpowerredis_10000_%d_%s"   //工作桌面 用户功能缓存(类似bigmember_power_3_%s)
+)
+
+// 初始化大会员权益
+func InitBigVipService(mysql *mysql.Mysql) {
+	serviceList := mysql.Find(BigmemberServiceTable, nil, "id,s_url_front,s_url_back", "id", -1, -1)
+	if serviceList == nil || len(*serviceList) == 0 {
+		panic(fmt.Sprintf("大会员初始权限失败,请检查mysql链接是否正常、查看%s表是否正常", BigmemberServiceTable))
+	}
+	FrontService, BackService = make(map[string][]int), make(map[string][]int)
+	for _, one := range *serviceList {
+		power := qutil.ObjToMap(one)
+		if power == nil {
+			continue
+		}
+		serviceId := qutil.IntAll((*power)["id"])
+		if urlFronts := qutil.ObjToString((*power)["s_url_front"]); urlFronts != "" {
+			for _, urlFront := range strings.Split(urlFronts, "&") {
+				FrontService[urlFront] = append(FrontService[urlFront], serviceId)
+			}
+		}
+
+		if urlBacks := qutil.ObjToString((*power)["s_url_back"]); urlBacks != "" {
+			for _, urlBack := range strings.Split(urlBacks, "&") {
+				BackService[urlBack] = append(BackService[urlBack], serviceId)
+			}
+		}
+	}
+	log.Printf("init BigVipService status \nFrontService:%+v\nBackService:%+v\n", FrontService, BackService)
+}
+
+// 当大会员状态改变时清除此状态
+func ClearBigVipUserPower(userId string) bool {
+	cacheKey := fmt.Sprintf(PowerCacheKey, userId)
+	baseInfoCacheKey := fmt.Sprintf(IsGetUserBaseInfoRedisKey, userId)
+	redisMenuKeyPC := fmt.Sprintf(RedisMenuKeyPC, userId)
+	redisMenuKeyWX := fmt.Sprintf(RedisMenuKeyWX, userId)
+	redisMenuKeyAPP := fmt.Sprintf(RedisMenuKeyAPP, userId)
+	userPowerRedisKey := fmt.Sprintf(UserPowerRedisKey, time.Now().Day(), userId)
+	return redis.Del(BaseInfoCacheDb, cacheKey) && redis.Del(BaseInfoCacheDb, baseInfoCacheKey) && redis.Del(BaseInfoCacheDb, redisMenuKeyPC) && redis.Del(BaseInfoCacheDb, redisMenuKeyWX) && redis.Del(BaseInfoCacheDb, redisMenuKeyAPP) && redis.Del(BaseInfoCacheDb, userPowerRedisKey)
+}
+
+// 获取商机管理个人基本信息
+func GetEntnicheState(userId string, mysql *mysql.Mysql, mg MongodbSim) *BigVipBaseMsg {
+	userPower := BigVipBaseMsg{}
+	userPower.EntnicheStatus = 0
+	//手机号
+	data, ok := mg.FindById("user", userId, `{"s_phone":1,"s_m_phone":1,i_member_status":1,"i_member_give":1,"s_member_mainid":1,"i_member_sub_status":1,"i_member_trial":1,"i_vip_status":1,"o_vipjy":1,"o_jy":1,"l_registedate":1}`)
+	if ok && *data != nil && len(*data) > 0 {
+		//查询是否是商机管理付费用户
+		phone, _ := qutil.If((*data)["s_phone"] != nil, (*data)["s_phone"], (*data)["s_m_phone"]).(string)
+		if phone != "" {
+			if count := mysql.CountBySql(`SELECT count(1) FROM entniche_user u LEFT JOIN entniche_info i ON u.ent_id=i.id WHERE u.phone=? and u.power=1 and i.status=1`, phone); count > 0 {
+				userPower.EntnicheStatus = 1
+			}
+		}
+	}
+	return &userPower
+}
+
+// 获取大会员个人基本信息
+func GetBigVipUserBaseMsg(userId string, mysql *mysql.Mysql, mg MongodbSim) *BigVipBaseMsg {
+	userPower := BigVipBaseMsg{}
+	userPower.PowerMap = make(map[int]bool)
+	if userId == "" {
+		return &userPower
+	}
+	userPower.IsUpgrade = false
+	userPower.Uid = userId
+	cacheKey := fmt.Sprintf(PowerCacheKey, userId)
+
+	if bytes, err := redis.GetBytes(BaseInfoCacheDb, cacheKey); err == nil && bytes != nil {
+		if err := json.Unmarshal(*bytes, &userPower); err == nil {
+			return &userPower
+		}
+	}
+	//大会员状态
+	data, ok := mg.FindById("user", userId, `{"s_phone":1,"s_m_phone":1,i_member_status":1,"i_member_give":1,"s_member_mainid":1,"i_member_sub_status":1,"i_member_trial":1,"i_vip_status":1,"o_vipjy":1,"o_jy":1,"l_registedate":1,"s_myemail":1,"base_user_id":1}`)
+	if ok && *data != nil && len(*data) > 0 {
+		userPower.Email = qutil.ObjToString((*data)["s_myemail"])
+		userPower.BaseUserId = qutil.IntAll((*data)["base_user_id"])
+		userPower.Registedate = qutil.Int64All((*data)["l_registedate"])
+		userPower.Status = qutil.IntAllDef((*data)["i_member_status"], 0)
+		//子账号被启用
+		i_member_sub_status := qutil.IntAllDef((*data)["i_member_sub_status"], 0)
+		if (*data)["s_member_mainid"] != nil && qutil.ObjToString((*data)["s_member_mainid"]) != "" && i_member_sub_status > 0 {
+			userPower.Pid = qutil.ObjToString((*data)["s_member_mainid"])
+		}
+		if (userPower.Pid != "" && qutil.IntAllDef((*data)["i_member_sub_status"], 0) == 1) || mg.Count("member", map[string]interface{}{"userid": userId}) > 0 {
+			userPower.Used = true
+		}
+		if (*data)["i_member_trial"] != nil {
+			userPower.HasTrial = true
+		}
+		if vipStatus := qutil.IntAll((*data)["i_vip_status"]); vipStatus > 0 {
+			userPower.VipStatus = vipStatus
+			if o_vipjy := qutil.ObjToMap((*data)["o_vipjy"]); o_vipjy != nil {
+				if o_buyset := qutil.ObjToMap((*o_vipjy)["o_buyset"]); o_buyset != nil {
+					userPower.Vip_BuySet = BuySet{
+						Upgrade:         qutil.IntAll((*o_buyset)["upgrade"]),
+						AreaCount:       qutil.IntAll((*o_buyset)["areacount"]),
+						BuyerclassCount: qutil.IntAll((*o_buyset)["buyerclasscount"]),
+					}
+				}
+			}
+			userPower.VipPowerType = 1
+		}
+		if userPower.Status > 0 {
+			userPower.MemberPowerType = 1
+		}
+		//免费用户画像和附件下载权限
+		//if userPower.Status <= 0 && userPower.VipStatus <= 0 {
+		o_jy := qutil.ObjToMap((*data)["o_jy"])
+		//"i_newfree":    1, //新免费用户=>新订阅设置页面 20211122
+		//IsNewFreeTimeCell dev3.6.4版本之前发了个紧急版本处理老用户订阅问题,i_newfree字段必须用户选择地区才能生成,不能作为判断是否是新用户得唯一标识,在此版本添加了常量:IsNewFreeTimeCell作为判断标准;--ws
+		if qutil.IntAll((*o_jy)["i_newfree"]) > 0 || IsNewFreeTimeCell < userPower.Registedate {
+			userPower.IsUpgrade = true
+		}
+		//}
+		//查询是否是商机管理付费用户
+		//userPower.EntnicheStatus = 0
+		phone, _ := qutil.If((*data)["s_phone"] != nil, (*data)["s_phone"], (*data)["s_m_phone"]).(string)
+		if phone != "" {
+			res := mysql.SelectBySql(`SELECT i. STATUS AS status, i.isNew, i.power_source, r.role_id, u.power, i.name,i.id FROM (entniche_user u LEFT JOIN entniche_user_role r ON r.user_id = u.id ) LEFT JOIN entniche_info i ON u.ent_id = i.id WHERE u.phone = ? ORDER BY i. STATUS DESC, i.auth_status DESC`, phone)
+			if res != nil && len(*res) > 0 {
+				userPower.EntInfo = map[int]*EntInfoStruct{}
+				//商机管理
+				userPower.EntName = qutil.ObjToString((*res)[0]["name"])
+				//已购买企业未过期-商机管理用户
+				for _, v := range *res {
+					if qutil.IntAll(v["id"]) == 0 {
+						continue
+					}
+					entId := qutil.IntAll(v["id"])
+					userPower.EntInfo[entId] = &EntInfoStruct{
+						IsNew:     qutil.IntAll(v["isNew"]) > 0,
+						IsPower:   qutil.IntAll(v["power"]) > 0,
+						Status:    qutil.IntAll(v["status"]),
+						Name:      qutil.ObjToString(v["name"]),
+						IsService: qutil.IntAll(v["power_source"]) > 0,
+						RoleId:    qutil.IntAll(v["role_id"]),
+					}
+					// 判断是否是新商机管理
+					if qutil.IntAll(v["status"]) == 1 && qutil.IntAll(v["power"]) == 1 {
+						userPower.EntnicheStatus = 1
+						if qutil.IntAll(v["isNew"]) == 1 {
+							userPower.EntIsNew = true
+							// if userPower.EntIsNew && userPower.IsEntService {
+							// 	break
+							// }
+						}
+					}
+					// 判断是否是商机管理服务 (在超级订阅或者大会员、医械通(暂无)的基础上才会有的)
+					// if qutil.IntAll(v["power_source"]) == 1 {
+					// 	userPower.IsEntService = true
+					// 	break
+					// }
+
+				}
+			}
+			// 如果是商机管理服务用户,则不能是旧版商机管理及新版商机管理
+			// if userPower.IsEntService {
+			// 	userPower.EntnicheStatus = 0 // 商机管理状态
+			// 	userPower.EntIsNew = false   // 新版商机管理
+			// }
+			//if count := mysql.CountBySql(`SELECT count(1) FROM entniche_user u LEFT JOIN entniche_info i ON u.ent_id=i.id WHERE u.phone=? and u.power=1 and i.status=1`, phone); count > 0 {
+			//	userPower.EntnicheStatus = 1
+			//}
+			//广东移动DICT 用户
+			userPower.PrivateGD = mysql.CountBySql(`select count(1) from privatedata where phone = ?`, phone) > 0
+
+			//用于判断是否含有企业主体的超级订阅、大会员服务
+			epowerData := mysql.SelectBySql(`SELECT c.product_type FROM entniche_user a INNER JOIN entniche_power b 
+		 									INNER JOIN entniche_wait_empower c 
+		 									ON a.id =b.ent_user_id  AND c.id = b.wait_empower_id WHERE a.phone =? AND c.end_time >?`, phone, time.Now().Format(Date_Full_Layout))
+			if epowerData != nil && len(*epowerData) > 0 {
+				for _, v := range *epowerData {
+					product_type := qutil.ObjToString(v["product_type"])
+					if strings.Contains(product_type, "VIP订阅") {
+						userPower.MemberPowerType = 2
+					} else if strings.Contains(product_type, "大会员") {
+						userPower.MemberPowerType = 2
+					}
+				}
+			}
+		}
+	}
+
+	//子账号查询父节点权限
+	queryId := qutil.If(userPower.Pid == "", userId, userPower.Pid).(string)
+	//用户购买的服务
+	serviceList := mysql.Find(BigmemberUserPowerTable, map[string]interface{}{"s_userid": queryId, "i_status": 0}, "DISTINCT(s_serviceid),i_frequency", "", -1, -1)
+	if serviceList != nil && len(*serviceList) != 0 {
+		pCount, eCount, dailyNum, customers := 0, 0, 0, 10
+		for _, item := range *serviceList {
+			serviceid := qutil.IntAll(item["s_serviceid"])
+			userPower.PowerMap[serviceid] = true
+			if serviceid == 14 { //项目数量
+				pCount = qutil.IntAll(item["i_frequency"])
+			} else if serviceid == 4 || serviceid == 12 || serviceid == 13 { //企业情报监控 企业中标动态
+				tEcount := qutil.IntAll(item["i_frequency"])
+				if tEcount > eCount {
+					eCount = tEcount
+				}
+			} else if serviceid == 17 || serviceid == 18 { //每日数据包
+				dailyNum = qutil.IntAll(item["i_frequency"])
+			} else if serviceid == 7 { //潜在客户 关注客户
+				customers = qutil.IntAll(item["i_frequency"])
+			}
+		}
+		userPower.EntNum = eCount
+		userPower.ProNum = pCount
+		userPower.DailyNum = dailyNum
+		userPower.Customers = customers
+	}
+
+	//存储缓存
+	go func() {
+		if bytes, err := json.Marshal(userPower); err == nil && bytes != nil {
+			oneDayMore := OneDay + rand.Intn(60*60)
+			_ = redis.PutBytes(BaseInfoCacheDb, cacheKey, &bytes, oneDayMore)
+		}
+	}()
+	return &userPower
+}
+
+// 权限判断
+func (this *BigVipBaseMsg) CheckBigVipFrontPower(reqFlag string) (pass bool) {
+	if reqFlag == "ent_portrait" || reqFlag == "unit_portrayal" { //画像页面无权限控制
+		return true
+	}
+	if this.Vip_BuySet.Upgrade == 1 {
+		if reqFlag == "ent_portrait" || reqFlag == "svip" {
+			return true
+		}
+	}
+	return this.checkPower(reqFlag, FrontService)
+}
+
+func (this *BigVipBaseMsg) CheckBigVipBackPower(reqFlag string) (pass bool) {
+	return this.checkPower(reqFlag, BackService)
+}
+
+func (this *BigVipBaseMsg) checkPower(reqFlag string, servicesPower map[string][]int) bool {
+	powers, ok := servicesPower[reqFlag]
+	if !ok {
+		return false
+	}
+	for _, p := range powers {
+		if this.PowerMap[p] {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (this *BigVipBaseMsg) GetUseId() string {
+	if this.Pid != "" {
+		return this.Pid
+	}
+	return this.Uid
+}

+ 69 - 0
common/src/qfw/util/jy/checksendmsg.go

@@ -0,0 +1,69 @@
+package jy
+
+import (
+	"fmt"
+	"log"
+	"net/url"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	"app.yhyue.com/moapp/jybase/redis"
+
+	//"strconv"
+	"strings"
+	"time"
+)
+
+var se *SimpleEncrypt = &SimpleEncrypt{Key: "topnet2015topnet2015"}
+var ActivityEndCode, ActivityStartCode int64
+var AC = &AES_CBC{
+	Key: "mGlAgnIBB8bx2nch",
+	Iv:  "1389461544135476",
+}
+
+//发短信,验证token
+func CheckSendMsg(token string) string {
+	if token == "" {
+		return ""
+	}
+	log.Println("短信解析前token", token)
+	key := fmt.Sprintf("smstoken_%s", token)
+	ok, e := redis.Exists("other", key)
+	if e != nil {
+		log.Println("redis中token error", e)
+		return ""
+	}
+	if ok {
+		log.Println("redis中token已存在", token)
+		return ""
+	}
+	token, e = url.QueryUnescape(token)
+	if e != nil {
+		log.Println("短信token QueryUnescape error", e)
+	}
+	v, err := AC.Decrypt(token)
+	if err != nil {
+		log.Println("短信token Decrypt error", err)
+		return ""
+	}
+	log.Println("短信解析后token", v)
+	vs := strings.Split(v, "_")
+	if len(vs) != 3 {
+		log.Println("短信token error", vs)
+		return ""
+	}
+	now := time.Now()
+	if !strings.HasPrefix(vs[1], FormatDate(&now, Date_yyyyMMdd)) {
+		log.Println("短信token date错误", vs)
+		return ""
+	}
+	if vs[2] != util.GetMd5String(fmt.Sprintf("%s&%s", vs[0], vs[1])) {
+		log.Println("短信token sing错误", vs)
+		return ""
+	}
+	if vs[0] != "" {
+		redis.Put("other", key, 1, 86400)
+	}
+	return vs[0]
+}

+ 599 - 0
common/src/qfw/util/jy/entnichepush.go

@@ -0,0 +1,599 @@
+package jy
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"math"
+	"net/http"
+	"strings"
+	"time"
+
+	. "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	mg "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+var EntnichePush = &entnichePush{}
+
+type entnichePush struct {
+}
+
+//从pushcache_2_a中取
+func (e *entnichePush) GetTodayCache(entId, userId int) (*SubPush, error) {
+	pc_a, err := redis.GetNewBytes("pushcache_2_b", e.todayKey(entId, userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *SubPush
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+//往pushcache_2_a中放
+func (e *entnichePush) PutTodayCache(entId, userId int, pc_a *SubPush) {
+	redis.Put("pushcache_2_b", e.todayKey(entId, userId), pc_a, threeDay)
+}
+
+//获取redis key
+func (e *entnichePush) todayKey(entId, userId int) string {
+	return fmt.Sprintf("entnichepush_%d_%d", entId, userId)
+}
+
+func (e *entnichePush) Datas(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, pageNum int, selectTime, area string) (hasNextPage bool, result []*SubPushList) {
+	if pageNum < 1 {
+		pageNum = 1
+	}
+	now := NowFormat(Date_Short_Layout)
+	start := (pageNum - 1) * pageSize
+	end := start + pageSize
+	if now == selectTime && area == "" {
+		subPush, err := e.GetTodayCache(entId, userId)
+		if err != nil {
+			log.Println(userId, "GetTodayCache Error", err)
+		}
+		if err != nil || subPush == nil || subPush.Date != now || len(subPush.Datas) == 0 {
+			list := e.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, pageNum, pageSize, selectTime, area, false)
+			subPush = &SubPush{
+				Date:  now,
+				Datas: list,
+			}
+			e.PutTodayCache(entId, userId, subPush)
+		}
+		length := len(subPush.Datas)
+		if end > length {
+			end = length
+		}
+		if start < length {
+			result = subPush.Datas[start:end]
+		}
+	} else if selectTime == "" && area == "" && pageNum <= 5 {
+		allCache, err := e.GetAllCache(entId, userId)
+		if err != nil {
+			log.Println(userId, "GetAllCache Error", err)
+		}
+		if err != nil || allCache == nil || len(allCache) == 0 {
+			allCache = e.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, 1, AllSubPushCacheSize, selectTime, area, true)
+			e.PutAllCache(entId, userId, allCache)
+		}
+		length := len(allCache)
+		if end > length {
+			end = length
+		}
+		if start < length {
+			result = allCache[start:end]
+		}
+	} else {
+		result = e.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, pageNum, pageSize, selectTime, area, true)
+	}
+	if result == nil {
+		result = []*SubPushList{}
+	}
+	hasNextPage = len(result) >= pageSize
+	return
+}
+func (e *entnichePush) getDatasFromMysql(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, pageNum, myPageSize int, selectTime, area string, isLimit bool) (result []*SubPushList) {
+	findSQL := "select id,date,infoid,isvisit,matchkeys,type,1 as isvip from pushentniche where entid=" + fmt.Sprint(entId) + " and userid=" + fmt.Sprint(userId)
+	findStr := ""
+	if selectTime != "" {
+		startTime := selectTime + " 00:00:00"
+		endTime := selectTime + " 23:59:59"
+		st, _ := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
+		et, _ := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
+		findStr += " and date < " + fmt.Sprint(et.Unix()) + " and date >= " + fmt.Sprint(st.Unix())
+	}
+	if area != "" {
+		findStr += " and area in ("
+		var _area = ""
+		for _, v := range strings.Split(area, ",") {
+			if v == "全部" {
+				continue
+			}
+			if _area != "" {
+				_area += ","
+			}
+			_area += fmt.Sprint(PushMapping.Area[v])
+		}
+		findStr += _area + ")"
+	}
+	start := (pageNum - 1) * myPageSize
+	findStr += " order by id desc"
+	if isLimit {
+		findStr += " limit " + fmt.Sprint(start) + "," + fmt.Sprint(myPageSize)
+	}
+	findSQL = findSQL + findStr
+	//log.Println("findsql:", findSQL)
+	list := PushMysql.SelectBySql(findSQL)
+	if len(*list) > 0 {
+		pushCas := e.GetJyPushs(*list)
+		result = NewSubscribePush().GetInfoByIds(Mgo_bidding, bidding, bidding_back, pushCas)
+	} else {
+		result = []*SubPushList{}
+	}
+	return
+}
+
+//获取历史推送
+func (e *entnichePush) GetJyPushs(datas []map[string]interface{}) (pushCas []*PushCa) {
+	pushCas = []*PushCa{}
+	for _, v := range datas {
+		keys := []string{}
+		if matchkeys := ObjToString(v["matchkeys"]); matchkeys != "" {
+			keys = strings.Split(matchkeys, " ")
+		}
+		pushCas = append(pushCas, &PushCa{
+			Date:   Int64All(v["date"]),
+			InfoId: ObjToString(v["infoid"]),
+			Visit:  IntAll(v["isvisit"]),
+			Index:  Int64All(v["id"]),
+			Keys:   keys,
+			Type:   IntAll(v["type"]),
+			Isvip:  IntAll(v["isvip"]),
+		})
+	}
+	return
+}
+
+func (e *entnichePush) Visit(PushMysql *mysql.Mysql, entId, userId, id int) {
+	if id <= 0 {
+		return
+	}
+	PushMysql.UpdateOrDeleteBySql("update pushentniche set isvisit=1 where userid=? and id=?", userId, id)
+	todaySubPush, err := e.GetTodayCache(entId, userId)
+	if err == nil && todaySubPush != nil {
+		for _, v := range todaySubPush.Datas {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		e.PutTodayCache(entId, userId, todaySubPush)
+	}
+	//
+	allSubPush, err := e.GetAllCache(entId, userId)
+	if err == nil && allSubPush != nil {
+		for _, v := range allSubPush {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		e.PutAllCache(entId, userId, allSubPush)
+	}
+}
+
+//查看全部列表缓存
+func (e *entnichePush) allKey(entId, userId int) string {
+	return fmt.Sprintf("all_entnichepush_%d_%d", entId, userId)
+}
+
+func (e *entnichePush) PutAllCache(entId, userId int, datas []*SubPushList) {
+	redis.Put("pushcache_2_a", e.allKey(entId, userId), datas, threeDay)
+}
+
+func (e *entnichePush) GetAllCache(entId, userId int) ([]*SubPushList, error) {
+	return e.GetCache("pushcache_2_a", e.allKey(entId, userId))
+}
+
+func (e *entnichePush) GetCache(code, key string) ([]*SubPushList, error) {
+	pc_a, err := redis.GetNewBytes(code, key)
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p []*SubPushList
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+func (e *entnichePush) NewDatas(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, pageNum int, selectTime, area, buyerclass string, isEnt bool, url string, pageSize int) (hasNextPage bool, result []map[string]interface{}, allCount int) {
+	if pageNum < 1 {
+		pageNum = 1
+	}
+	result, allCount = e.newGetDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, pageNum, pageSize, selectTime, area, buyerclass, true, isEnt, url)
+	if result == nil {
+		result = []map[string]interface{}{}
+	}
+	hasNextPage = len(result) >= pageSize
+	return
+}
+
+func (e *entnichePush) NewExportDatas(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, selectTime, area, city, buyerclass string, isEnt bool, maxCount int, url string) (result []map[string]interface{}, allCount, secondCount int) {
+	if selectTime == "" && area == "" {
+		result, allCount, secondCount = e.newGetExportDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, 1, pageSizes, selectTime, area, city, buyerclass, false, isEnt, maxCount, url)
+	} else {
+		result, allCount, secondCount = e.newGetExportDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, entId, userId, 1, pageSizes, selectTime, area, city, buyerclass, false, isEnt, maxCount, url)
+	}
+	if result == nil {
+		result = []map[string]interface{}{}
+	}
+	return
+}
+
+func (e *entnichePush) newGetDatasFromMysql(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, pageNum, myPageSize int, selectTime, area, buyerclass string, isLimit bool, isEnt bool, url string) (result []map[string]interface{}, count int) {
+	findSQL := ""
+	if isEnt {
+		findSQL = "select id,date,infoid,buyerclass,isvisit,matchkeys,type,1 as isvip from pushentniche where entid=" + fmt.Sprint(entId)
+	} else {
+		findSQL = "select id,date,infoid,buyerclass,isvisit,matchkeys,type,1 as isvip from pushentniche where entid=" + fmt.Sprint(entId) + " and userid=" + fmt.Sprint(userId)
+	}
+	findStr := ""
+	if selectTime != "" {
+		times := strings.Split(selectTime, "_")
+		startTime := times[0][0:10]
+		endTime := times[1][0:10]
+		findStr += " and a.date < " + endTime + " and a.date >= " + startTime
+	}
+	if area != "" {
+		var _area = ""
+		for _, v := range strings.Split(area, ",") {
+			if v == "全部" {
+				continue
+			}
+			if _area != "" {
+				_area += ","
+			}
+			_area += fmt.Sprint(PushMapping.City[v])
+		}
+		if strings.Contains(_area, ",") {
+			findStr += " and city in (" + _area + ")"
+		} else if len(strings.Split(_area, ",")) == 1 {
+			_area += ",9999" //多个in操作 比直接=查询更快
+			findStr += " and city in (" + _area + ")"
+		}
+	}
+	if buyerclass != "" {
+		findStr += " and buyerclass in ("
+		var _buyerclass = ""
+		for _, v := range strings.Split(buyerclass, ",") {
+			if v == "全部" {
+				continue
+			}
+			if _buyerclass != "" {
+				_buyerclass += ","
+			}
+			_buyerclass += fmt.Sprint(PushMapping.Buyerclass[v])
+		}
+		findStr += _buyerclass + ")"
+	}
+	start := (pageNum - 1) * myPageSize
+	findStr += " order by id desc"
+	if isLimit {
+		findSQLs := findSQL + findStr
+		lists := PushMysql.SelectBySql(findSQLs)
+		count = len(*lists)
+		findStr += " limit " + fmt.Sprint(start) + "," + fmt.Sprint(myPageSize)
+	}
+	findSQL = findSQL + findStr
+	log.Println("findsql:", findSQL)
+	list := PushMysql.SelectBySql(findSQL)
+	counts := len(*list)
+	if counts > 0 {
+		result, _ = e.GetExportInfoByIds(Mgo_bidding, bidding, bidding_back, *list, false, entId, url)
+	}
+	if counts == 0 || *list == nil || list == nil {
+		result = []map[string]interface{}{}
+	}
+	return
+}
+
+func (e *entnichePush) newGetExportDatasFromMysql(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, entId, userId int, pageNum, myPageSize int, selectTime, area, city, buyerclass string, isLimit bool, isEnt bool, maxCount int, url string) (result []map[string]interface{}, counts, secondCount int) {
+	findSQL := ""
+	countSQL := ""
+	userStr := " "
+	querys := []string{}
+	querys = append(querys, " 1=1 ")
+	if isEnt {
+		//querys = []string{fmt.Sprintf("a.entid='%s'", fmt.Sprint(entId))}
+		userStr = fmt.Sprintf("   a.entid='%s'", fmt.Sprint(entId))
+	} else {
+		querys = []string{fmt.Sprintf("a.userid='%s'", fmt.Sprint(userId))}
+		//querys = []string{fmt.Sprintf("a.userid='%s'", fmt.Sprint(userId))}
+		userStr = fmt.Sprintf("  a.userid='%s'", fmt.Sprint(userId))
+	}
+	if selectTime != "" {
+		times := strings.Split(selectTime, "_")
+		startTime := times[0][0:10]
+		endTime := times[1][0:10]
+		//findStr += " and date < " + endTime + " and date >= " + startTime
+		querys = append(querys, fmt.Sprintf("a.date>=%s and a.date<=%s", startTime, endTime))
+	}
+	if area != "" || city != "" {
+		var sqlAreaCity = ""
+		city := []string{}
+		for _, v := range strings.Split(area, ",") {
+			if NewPushMapping.City[v] != "" {
+				city = append(city, fmt.Sprint(NewPushMapping.City[v]))
+			} else {
+				city = append(city, "-1")
+			}
+		}
+		if len(city) == 1 {
+			city = append(city, "9999")
+		}
+		if len(city) > 0 {
+			sqlAreaCity = fmt.Sprintf(" b.city_code in (%s)", strings.Join(city, ","))
+		}
+		//区域
+		var sqlArea = ""
+		areaArr := []string{}
+		for _, v := range strings.Split(area, ",") {
+			if NewPushMapping.Area[v] != "" {
+				areaArr = append(areaArr, fmt.Sprint(NewPushMapping.Area[v]))
+			} else {
+				areaArr = append(areaArr, "-1")
+			}
+		}
+		if len(areaArr) == 1 {
+			areaArr = append(areaArr, "9999")
+		}
+		if len(areaArr) > 0 {
+			sqlArea = fmt.Sprintf(" b.area_code in (%s)", strings.Join(areaArr, ","))
+		}
+		if sqlAreaCity != "" && sqlArea != "" {
+			sqlAreaCity = "( " + sqlAreaCity + " or " + sqlArea + " )"
+		} else if sqlAreaCity == "" && sqlArea != "" {
+			sqlAreaCity = sqlArea
+		}
+		if sqlAreaCity != "" {
+			querys = append(querys, sqlAreaCity)
+		}
+	}
+	if buyerclass != "" {
+		buyerclassArr := []string{}
+		for _, v := range strings.Split(buyerclass, ",") {
+			if NewPushMapping.Buyerclass[v] != "" {
+				buyerclassArr = append(buyerclassArr, fmt.Sprint(NewPushMapping.Buyerclass[v]))
+			}
+		}
+		if len(buyerclassArr) == 1 {
+			buyerclassArr = append(buyerclassArr, "9999")
+		}
+		if len(buyerclassArr) > 0 {
+			querys = append(querys, fmt.Sprintf(" b.buyerclass_code in (%s)", strings.Join(buyerclassArr, ",")))
+		}
+	}
+	searchSql := fmt.Sprintf(" from %s  a INNER  JOIN %s b ON  %s and  a.infoid = b.infoid   where  %s"+
+		" order by a.id desc", PushEntniche, Baseinfo, userStr, strings.Join(querys, " and "))
+	countSQL = fmt.Sprintf("select count(a.id)" + searchSql)
+	count := PushMysql.CountBySql(countSQL)
+	log.Println(countSQL)
+	counts = int(count)
+	findSQL = "select a.id,a.infoid,a.matchkeys,a.isvisit"
+	findSQL += searchSql
+	list := &[]map[string]interface{}{}
+	log.Println("counts ", counts)
+	if counts > 0 {
+		//超过最大限额  2020-11-5
+		if counts > maxCount {
+			findSQL += " limit 0," + fmt.Sprint(maxCount)
+			log.Println("findSQL ", findSQL)
+			list = PushMysql.SelectBySql(findSQL)
+			counts = maxCount
+		} else {
+			log.Println("findSQL ", findSQL)
+			list = PushMysql.SelectBySql(findSQL)
+		}
+		result, secondCount = e.GetExportInfoByIds(Mgo_bidding, bidding, bidding_back, *list, true, entId, url)
+	}
+	if counts == 0 || len(*list) == 0 || list == nil {
+		result = []map[string]interface{}{}
+	}
+	return
+}
+
+//根据id取内容
+func (e *entnichePush) GetExportInfoByIds(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, pushCas []map[string]interface{}, isInfoId bool, entId int, url string) ([]map[string]interface{}, int) {
+	array := []map[string]interface{}{}
+	//var newsDatalen = make(chan bool, 1000000)
+	newCount := 0            // 新导出数量
+	infoIdList := []string{} // 临时infoid数组
+	//log.Println(pushCas,"啦啦啦")
+	if len(pushCas) == 0 {
+		return array, 0
+	}
+	m := map[string]bool{}
+	ids := []string{}
+	// 20210716  判重由redis 改为请求判重中台接口  每一千个请求一次
+	for _, v := range pushCas {
+		infoid := ObjToString(v["infoid"])
+		if m[infoid] {
+			continue
+		}
+		m[infoid] = true
+		ids = append(ids, infoid)
+		// 20210716  判重由redis 改为请求判重中台接口  每一千个请求一次
+		if len(infoIdList) > 1000 {
+			//	 调接口
+			rs, err5 := Post(url, map[string]string{
+				"personId": "0", // 这个参数没有用
+				"infoId":   strings.Join(infoIdList, ","),
+				"entId":    fmt.Sprintf("%d", entId),
+				"isInsert": "false", // 是否插入数据
+				"isEnt":    "true",  // 是否根据企业id判重
+			})
+			if err5 != nil || IntAll(rs["code"]) != 0 {
+				log.Println("企业订阅数据导出接口判重失败", err5)
+			} else {
+				log.Println("企业订阅数据导出", rs)
+				// 置空
+				infoIdList = []string{}
+				// 本次数据累计
+				returnData := rs["data"].(map[string]interface{})
+				//log.Println(newCount,"加之前121")
+				newCount += int(returnData["newCount"].(float64))
+				//log.Println(newCount,"加之后121")
+			}
+
+		}
+		infoIdList = append(infoIdList, infoid)
+	}
+	//
+	if len(infoIdList) > 0 {
+		log.Println(entId)
+		rs, err5 := Post(url, map[string]string{
+			"personId": "0",
+			"infoId":   strings.Join(infoIdList, ","),
+			"entId":    fmt.Sprintf("%d", entId),
+			"isInsert": "false",
+			"isEnt":    "true",
+		})
+		if err5 != nil || IntAll(rs["code"]) != 0 {
+			log.Println("判重失败===", err5)
+		} else {
+			// 置空
+			infoIdList = []string{}
+			// 本次数据累计
+			returnData := rs["data"].(map[string]interface{})
+			newCount += int(returnData["newCount"].(float64))
+		}
+
+	}
+	infos := map[string]map[string]interface{}{}
+	//elasticsearch
+	if len(ids) > 0 {
+		pagecount := math.Ceil(float64(float32(len(ids)) / float32(100)))
+		idscount := 0
+		if len(ids) < 100 {
+			idscount = len(ids)
+		} else {
+			idscount = len(ids) % 100
+		}
+		log.Println("pagecount", pagecount)
+		list := []map[string]interface{}{}
+		for i := 0; i < int(pagecount); i++ {
+			idss := []string{}
+			if i == int(pagecount)-1 {
+				idss = ids[100*i : 100*i+idscount]
+			} else {
+				idss = ids[100*i : 100*i+100]
+			}
+			dataArr := elastic.Get("bidding", "bidding", fmt.Sprintf(querys, strings.Join(idss, `","`)))
+			list = append(list, *dataArr...)
+		}
+		if list != nil {
+			for _, v := range list {
+				_id := ObjToString(v["_id"])
+				infos[_id] = v
+			}
+		}
+	}
+	//mongodb bidding
+	mgo_ids := []primitive.ObjectID{}
+	for _, v := range ids {
+		if infos[v] == nil {
+			_id, _ := primitive.ObjectIDFromHex(v)
+			mgo_ids = append(mgo_ids, _id)
+		}
+	}
+	if len(mgo_ids) > 0 {
+		list, ok := Mgo_bidding.Find(bidding, map[string]interface{}{"_id": map[string]interface{}{"$in": mgo_ids}}, nil, nil, false, -1, -1)
+		if ok && *list != nil {
+			for _, v := range *list {
+				_id := mg.BsonIdToSId(v["_id"])
+				v["_id"] = _id
+				infos[_id] = v
+			}
+		}
+	}
+	//mongodb bidding_back
+	mgo_back_ids := []primitive.ObjectID{}
+	for _, v := range mgo_ids {
+		if infos[mg.BsonIdToSId(v)] == nil {
+			mgo_back_ids = append(mgo_back_ids, v)
+		}
+	}
+	if len(mgo_back_ids) > 0 {
+		list, ok := Mgo_bidding.Find(bidding_back, map[string]interface{}{"_id": map[string]interface{}{"$in": mgo_back_ids}}, nil, nil, false, -1, -1)
+		if ok && *list != nil {
+			for _, v := range *list {
+				_id := mg.BsonIdToSId(v["_id"])
+				v["_id"] = _id
+				infos[_id] = v
+			}
+		}
+	}
+	//
+	for _, v := range pushCas {
+		infoid := ObjToString(v["infoid"])
+		// 20210820  商机管理订阅导出发布时间字段缺失修改
+		info := map[string]interface{}{}
+		if infos[infoid] != nil {
+			for info_k, info_v := range infos[infoid] {
+				info[info_k] = info_v
+			}
+		}
+		info["_id"] = EncodeArticleId2ByCheck(infoid)
+		if isInfoId {
+			info["infoid"] = infoid
+		}
+		// info["date"] = Int64All(v["date"])
+		info["ca_isvisit"] = IntAll(v["isvisit"])
+		info["ca_index"] = Int64All(v["id"])
+		info["matchkeys"] = ObjToString(v["matchkeys"])
+		info = InfoFormats(info, v)
+		array = append(array, info)
+	}
+	log.Println("newsDatalen", newCount)
+	log.Println(len(array))
+	return array, newCount
+}
+func Post(url string, form map[string]string) (data map[string]interface{}, err error) {
+	str := ""
+	for k, v := range form {
+		str += "&" + k + "=" + v
+	}
+	//log.Println(str)
+	res, err1 := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(str))
+	log.Println(res)
+	if err1 != nil {
+		log.Println("post err:", err1.Error())
+		return nil, err1
+
+	} else if res.Body != nil {
+		defer res.Body.Close()
+		bs, _ := ioutil.ReadAll(res.Body)
+		err2 := json.Unmarshal(bs, &data)
+		if err2 != nil {
+			return nil, err2
+		}
+
+	}
+	return data, nil
+}

+ 68 - 0
common/src/qfw/util/jy/freeExperience.go

@@ -0,0 +1,68 @@
+package jy
+
+import (
+	"fmt"
+	"log"
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/redis"
+	"time"
+)
+
+const (
+	PowerCacheEntPortKey   = "free_ent_portrait_%s"
+	PowerCacheBuyerPortKey = "free_buyer_portrait_%s"
+	PowerCacheFileKey      = "free_article_attach_%s"
+	PowerCachePortraitKey  = "free_portrait_%s_%s"
+)
+
+//免费用户体验会员功能权限
+//免费用户在企业画像/采购单位画像/附件下载留资 留资成功后用户才有功能使用次数
+func FreeExperience(userId string) (int, int, int) {
+	return redis.GetInt(PowerCacheDb, fmt.Sprintf(PowerCacheEntPortKey, userId)),
+		redis.GetInt(PowerCacheDb, fmt.Sprintf(PowerCacheBuyerPortKey, userId)),
+		redis.GetInt(PowerCacheDb, fmt.Sprintf(PowerCacheFileKey, userId))
+}
+
+func Portraitexperience(userId, queryEnt string, isWinner bool) (eOk bool) {
+	i := redis.GetInt(PowerCacheDb, fmt.Sprintf("free_%s_portrait_%s", util.If(isWinner, "ent", "buyer").(string), userId))
+	if i >= 1 {
+		eOk = PortraitExperienceSet(userId, queryEnt, util.If(isWinner, "W", "B").(string))
+	} else if i == -1 {
+		eOk = PortraitExperienceUsing(userId, queryEnt)
+	}
+	return
+}
+
+//画像体验是否到期
+func PortraitExperienceUsing(userId, name string) bool {
+	b, err := redis.Exists(PowerCacheDb, fmt.Sprintf(PowerCachePortraitKey, userId, name))
+	if err != nil {
+		log.Println("查询redis 异常...")
+	}
+	return b
+}
+
+// 免费用户权限设置
+func PortraitExperienceSet(userId, name, p string) bool {
+	//企业画像
+	b := false
+	timeRemaining := int(RemainingTimes())
+	switch p {
+	case "W":
+		if redis.GetInt(PowerCacheDb, fmt.Sprintf(PowerCacheEntPortKey, userId)) > 0 {
+			b = redis.PutCKV(PowerCacheDb, fmt.Sprintf(PowerCacheEntPortKey, userId), -1) && redis.Put(PowerCacheDb, fmt.Sprintf(PowerCachePortraitKey, userId, name), name, timeRemaining)
+		}
+	case "B":
+		if redis.GetInt(PowerCacheDb, fmt.Sprintf(PowerCacheBuyerPortKey, userId)) > 0 {
+			b = redis.PutCKV(PowerCacheDb, fmt.Sprintf(PowerCacheBuyerPortKey, userId), -1) && redis.Put(PowerCacheDb, fmt.Sprintf(PowerCachePortraitKey, userId, name), name, timeRemaining)
+		}
+	}
+	return b
+}
+
+//当前剩余时间
+func RemainingTimes() int64 {
+	now := time.Now().Unix()
+	lastTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 59, time.Now().Location()).Unix()
+	return lastTime - now
+}

+ 89 - 0
common/src/qfw/util/jy/historypush.go

@@ -0,0 +1,89 @@
+package jy
+
+import (
+	"encoding/json"
+	"fmt"
+	"app.yhyue.com/moapp/jybase/redis"
+)
+
+/*A缓存中存这个结构体,B缓存不存
+ *B缓存初始化的时候,根据A缓存中改结构体type和count属性,判断查询半年内还是半年前年还是半年内+半年前的数据,减少查询次数
+ *type==1,如果count==0,说明A缓存中正好500条数据,B缓存可以直接查半年前的数据
+ *如果count>0,说明半年内的数据在A缓存中已经有了count条,B缓存取的时候要从count后面开始取
+ *type==2,如果count==0,说明A缓存中正好500条数据,B缓存没有可以查询的数据
+ *如果count>0,说明半年前的数据在A缓存中已经有了count条,B缓存取的时候要从count后面开始取
+ *type==3,如果count==0,说明半年内+半年前的数据正好500条,B缓存没有可以查询的数据
+ *如果count>0,说明半年前的数据在A缓存中已经有了count条,B缓存取的时候要从count后面开始取
+ */
+type PushSub struct {
+	Type  int //数据构成:1 半年内 2 半年前 3 半年内+半年前
+	Count int
+	Infos []map[string]interface{} //该缓存的数据
+}
+
+var HistoryPush = &historyPush{}
+
+type historyPush struct{}
+
+//从pushcache_2_a中取
+func (h *historyPush) GetPushCache_A(userId string) (*PushSub, error) {
+	pc_a, err := redis.GetNewBytes("pushcache_2_a", h.redisKey(userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *PushSub
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+//往pushcache_2_a中放
+func (h *historyPush) PutPushCache_A(userId string, pc_a *PushSub) {
+	if pc_a.Infos == nil || len(pc_a.Infos) == 0 {
+		return
+	}
+	redis.Put("pushcache_2_a", h.redisKey(userId), pc_a, threeDay)
+}
+
+//从pushcache_2_b中取
+func (h *historyPush) GetPushCache_B(userId string) ([]map[string]interface{}, error) {
+	pc_b, err := redis.GetNewBytes("pushcache_2_b", h.redisKey(userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_b == nil {
+		return nil, nil
+	}
+	var l []map[string]interface{}
+	if err := json.Unmarshal(*pc_b, &l); err != nil {
+		return nil, err
+	}
+	return l, nil
+}
+
+//往pushcache_2_b中放
+func (h *historyPush) PutPushCache_B(userId string, pc_b []map[string]interface{}) {
+	if pc_b == nil || len(pc_b) == 0 {
+		return
+	}
+	redis.Put("pushcache_2_b", h.redisKey(userId), pc_b, halfOfDay)
+}
+
+//获取redis key
+func (h *historyPush) redisKey(userId string) string {
+	return fmt.Sprintf("push_%s", userId)
+}
+
+func (h *historyPush) ClearPushCache(userId string) {
+	redis.Del("pushcache_2_a", h.redisKey(userId))
+	redis.Del("pushcache_2_b", h.redisKey(userId))
+}
+
+//历史推送记录中单条信息格式化
+func (h *historyPush) InfoFormat(p *PushCa, oldInfo *map[string]interface{}) *SubPushList {
+	return NewSubscribePush().InfoFormat(p, oldInfo)
+}

+ 477 - 0
common/src/qfw/util/jy/jy.go

@@ -0,0 +1,477 @@
+package jy
+
+import (
+	"crypto/rand"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"math/big"
+	"net/http"
+	"net/url"
+	"regexp"
+	"sort"
+	"strings"
+	"time"
+	"unicode"
+
+	. "app.yhyue.com/moapp/jybase/date"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mail"
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/redis"
+	qrpc "app.yhyue.com/moapp/jybase/rpc"
+	"app.yhyue.com/moapp/jybase/sms"
+
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+)
+
+//获取用户合并以前,合并以后的openid
+func GetOldOpenid(s_m_openid, a_m_openid, s_phone string, mergeorder interface{}) string {
+	a_mergeorder, _ := mergeorder.([]interface{})
+	openid := ""
+	if len(a_mergeorder) > 0 {
+		first, _ := a_mergeorder[0].(string)
+		if first == "s_m_openid" {
+			openid = s_m_openid
+		} else if first == "a_m_openid" {
+			openid = a_m_openid
+		} else if first == "s_phone" {
+			openid = s_phone
+		}
+	} else {
+		if s_m_openid != "" {
+			openid = s_m_openid
+		} else if a_m_openid != "" {
+			openid = a_m_openid
+		} else if s_phone != "" {
+			openid = s_phone
+		}
+	}
+	return openid
+}
+
+var MatchSpace = regexp.MustCompile("\\s+")
+var filterReg_3 = regexp.MustCompile("(项目|公告|公示)$")
+var filterReg_2 = regexp.MustCompile("^[)\\)>》】\\]}}〕,,;;::'\"“”。.\\??、/+=\\_—*&……\\^%$¥@!!`~·(\\(<《【\\[{{〔]+$")
+var filterReg_1 = regexp.MustCompile("^([0-9]{1,3}|[零一二三四五六七八九十]{1,2}|联系人?|电话|地址|编号|采购|政府采购|成交|更正|招标|中标|变更|结果)$")
+var filterReg = regexp.MustCompile("^[的人号时元万公告项目地址电话邮编日期联系招标中结果成交项目项目采购采购项目政府采购公告更正公告]+$")
+var PhoneReg = regexp.MustCompile("^[1][3-9][0-9]{9}$")
+var EmailPattern = regexp.MustCompile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$")
+
+func FilteKey(k string) string {
+	k = strings.TrimSpace(k)
+	k = filterReg_3.ReplaceAllString(k, "")
+	k = filterReg_2.ReplaceAllString(k, "")
+	k = filterReg_1.ReplaceAllString(k, "")
+	k = filterReg.ReplaceAllString(k, "")
+	return k
+}
+
+//超过20个字,截断
+//返回截取后的字符串和截取掉中的前3个字
+func InterceptSearchKW(word string, keywordsLimit int, isFilter bool) (b_word, a_word, s_word string) {
+	if isFilter {
+		word = FilteKey(word)
+	}
+	word = MatchSpace.ReplaceAllString(strings.TrimSpace(word), " ")
+	words := []rune(word)
+	if len(words) > keywordsLimit {
+		b_word = string(words[:keywordsLimit])
+		b_word = strings.TrimSpace(b_word)
+		if len(words) > keywordsLimit+3 {
+			a_word = string(words[keywordsLimit : keywordsLimit+3])
+		} else {
+			a_word = string(words[keywordsLimit:])
+		}
+	} else {
+		b_word = word
+	}
+	a_word = strings.TrimSpace(a_word)
+	s_word = MatchSpace.ReplaceAllString(b_word, "+")
+	return
+}
+
+func HttpEs(ques, analyzer, esAddress string) (res string) {
+	var addrs []string
+	surl := ""
+	for _, s := range strings.Split(esAddress, ",") {
+		addrs = append(addrs, s)
+	}
+	i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(addrs)))) //随机
+	surl = addrs[int(i.Int64())] + "/bidding/_analyze"
+	URL, _ := url.Parse(surl)
+	Q := URL.Query()
+	Q.Add("text", ques)
+	Q.Add("analyzer", analyzer)
+	URL.RawQuery = Q.Encode()
+	resp, err := http.Get(URL.String())
+	if err != nil {
+		log.Println("es连接失败 err1:", err)
+		resp, err = getesResp(ques, analyzer, addrs)
+		if err != nil {
+			return
+		}
+	}
+	result, err := ioutil.ReadAll(resp.Body)
+	if err == nil {
+		defer resp.Body.Close()
+		var resmap map[string]interface{}
+		json.Unmarshal(result, &resmap)
+		if resmap != nil && resmap["tokens"] != nil {
+			tokens := util.ObjArrToMapArr(resmap["tokens"].([]interface{}))
+			for _, v := range tokens {
+				token := util.ObjToString(v["token"])
+				if len([]rune(token)) == 1 && unicode.IsLetter([]rune(token)[0]) {
+					continue
+				}
+				if res != "" {
+					res += "+"
+				}
+				res += token
+			}
+		}
+	}
+	return
+}
+
+//
+func getesResp(ques, analyzer string, addrs []string) (resp *http.Response, err error) {
+	for _, v := range addrs {
+		surl := v + "/bidding/_analyze"
+		URL, _ := url.Parse(surl)
+		Q := URL.Query()
+		Q.Add("text", ques)
+		Q.Add("analyzer", analyzer)
+		URL.RawQuery = Q.Encode()
+		resp, err = http.Get(URL.String())
+		if err == nil {
+			break
+		}
+	}
+	return resp, err
+}
+
+//发送邮箱验证码
+func SendMailIdentCode(to, code string, auth []*mail.GmailAuth) bool {
+	html := fmt.Sprintf(`<div>
+		<div>
+			%s,您好!
+		</div>
+		<div style="padding: 20px 70px 10px 70px;">
+			<p>您正在进行绑定邮箱地址验证,请在邮件验证码输入框输入下方验证码:</p>
+			<span style="font-weight: bold;font-size: x-large;">%s</span>
+			<p>请勿向任何人泄露您收到的验证码。</p>
+			<p>如果您没有使用剑鱼标讯,请忽略此邮件。</p>
+			<p>此为系统邮件,请勿回复。</p>
+			<p>如有疑问,请联系客服 400-108-6670。</p>
+		</div>
+		<div>
+			<p>此致</p>
+			<p>剑鱼标讯</p>
+		</div>
+	</div>`, to, code)
+
+	for k, v := range auth {
+		if mail.GSendMail("剑鱼标讯", to, "", "", "剑鱼标讯邮箱校验", html, "", "", v) {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件成功", v.User))
+			return true
+		}
+		if k < len(auth)-1 {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件失败!3s后使用其他邮箱尝试", v.User))
+		} else {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件失败!", v.User))
+		}
+		time.Sleep(time.Second * 3)
+	}
+
+	return false
+}
+
+//
+func SendSMS(address, mobile string, params ...string) {
+	sms.SendSms(address, "01", mobile, params...)
+}
+
+//发送验证码
+//增加sessionKey字段 更换手机号防止绕过身份校验
+func SendPhoneIdentCode(address, phone string, session *httpsession.Session, sessionKey ...string) bool {
+	sessionKeyFlag := defaultPhoneFlag
+	if len(sessionKey) > 0 && sessionKey[0] != "" {
+		sessionKeyFlag = sessionKey[0]
+	}
+
+	lastSentTime := util.Int64All(session.Get(fmt.Sprintf("%sTime", sessionKeyFlag)))
+	//60秒之内不允许重复发
+	if lastSentTime > 0 && time.Now().Unix()-lastSentTime <= 60 {
+		return false
+	}
+	s_ranNum := util.GetRandom(6) //生成随机数
+	//s_ranNum = "111111"
+	session.Set(fmt.Sprintf("%sValue", sessionKeyFlag), s_ranNum)
+	session.Set(fmt.Sprintf("%sKey", sessionKeyFlag), phone)
+	session.Set(fmt.Sprintf("%sTime", sessionKeyFlag), time.Now().Unix())
+	//发送短信
+	log.Println("短信验证码", phone, s_ranNum)
+	SendSMS(address, phone, s_ranNum)
+	return true
+}
+
+const defaultPhoneFlag = "identCode"
+
+//短信验证码校验
+func CheckPhoneIdent(session *httpsession.Session, code string, sessionKey ...string) string {
+	sessionKeyFlag := defaultPhoneFlag
+	if len(sessionKey) > 0 && sessionKey[0] != "" {
+		sessionKeyFlag = sessionKey[0]
+	}
+	identCodeValue, _ := session.Get(fmt.Sprintf("%sValue", sessionKeyFlag)).(string)
+	if identCodeValue != "" && identCodeValue == code {
+		identCodeKey, _ := session.Get(fmt.Sprintf("%sKey", sessionKeyFlag)).(string)
+		ClearPhoneIdentSession(session)
+		return identCodeKey
+	}
+	return ""
+}
+
+//删除短信验证码有关的session
+func ClearPhoneIdentSession(session *httpsession.Session, sessionKey ...string) {
+	sessionKeyFlag := defaultPhoneFlag
+	if len(sessionKey) > 0 && sessionKey[0] != "" {
+		sessionKeyFlag = sessionKey[0]
+	}
+	session.Del(fmt.Sprintf("%sValue", sessionKeyFlag))
+	session.Del(fmt.Sprintf("%sKey", sessionKeyFlag))
+	session.Del(fmt.Sprintf("%sTime", sessionKeyFlag))
+}
+
+//邮箱校验
+func IsEmail(value string) bool {
+	return EmailPattern.MatchString(value)
+}
+
+//手机号校验
+func IsPhone(phone string) bool {
+	return PhoneReg.MatchString(phone)
+}
+
+//获取信息行业
+func Getindustrys(industryname string, mongodb MongodbSim) (industry map[string][]string, sortArray []string) {
+	industry = map[string][]string{}
+	sortArray = []string{}
+	classdata, ok1 := mongodb.FindOneByField("rc_task", `{"s_name":"`+industryname+`"}`, `{"s_class":1}`)
+	if ok1 && len(*classdata) > 0 {
+		classid := ""
+		if util.ObjToString((*classdata)["s_class"]) != "" {
+			classids := strings.Split(util.ObjToString((*classdata)["s_class"]), ",")
+			for k, v := range classids {
+				if k > 0 {
+					classid += `,`
+				}
+				classid += `"` + v + `"`
+			}
+		}
+		industryData, ok := mongodb.Find("rc_rule", `{"s_pid":{"$ne":""},"s_classid":{"$in":[`+classid+`]}}`, `{"i_order":1}`, `{"_id":1,"s_name":1}`, false, -1, -1)
+		if ok && industryData != nil && len(*industryData) > 0 {
+			for _, v := range *industryData {
+				fatFlag := 0
+				industryname, _ := v["s_name"].(string)
+				if strings.Contains(industryname, "_") {
+					fat := strings.Split(industryname, "_")[0]
+					child := strings.Split(industryname, "_")[1]
+					if len(industry[fat]) == 0 {
+						sortArray = append(sortArray, fat)
+					}
+					for _, fv := range industry[fat] {
+						if fv == strings.Trim(child, " ") {
+							fatFlag = 1
+							break
+						}
+					}
+					if fatFlag != 1 {
+						industry[fat] = append(industry[fat], child)
+					}
+				}
+			}
+		}
+	}
+	return industry, sortArray
+}
+
+//对应月份
+//
+func GetMonth(mon string) int {
+	month := map[string]int{
+		"January":   1,
+		"February":  2,
+		"March":     3,
+		"April":     4,
+		"May":       5,
+		"June":      6,
+		"July":      7,
+		"August":    8,
+		"September": 9,
+		"October":   10,
+		"November":  11,
+		"December":  12,
+	}
+	return month[mon]
+}
+
+//value unlimited 并发限制登陆用户
+func LoginRedisKey(userid string) string {
+	return fmt.Sprintf("unlimited_%s", userid)
+}
+
+//获取loginSess
+func GetLoginSess(userid string) []string {
+	key := LoginRedisKey(userid)
+	if data, ok := redis.Get("other", key).([]interface{}); ok {
+		return util.ObjArrToStringArr(data)
+	}
+	return []string{}
+}
+
+//判断是否在内
+func IsInLoginSess(key string, arr []string) bool {
+	for _, v := range arr {
+		if key == v {
+			return true
+		}
+	}
+	return false
+}
+
+type TtlMap struct {
+	key   string //sessionid
+	value int    //ttl
+}
+
+type AppLoginPush struct {
+	JgPushId    string `json:"jpushId"`
+	OtherPushId string `json:"opushId"`
+	PhoneType   string `json:"phoneType"`
+}
+
+//更新存储用户sessionid的队列 【sessionid ttl小于三天的清除】  key:sessionid
+func PutLoginSess(mongodb MongodbSim, apppushRpc, key, userid string, limit, max int) bool {
+	value := GetLoginSess(userid)
+	if !IsInLoginSess(key, value) {
+		value = append(value, key)
+	}
+	ttlmap := []TtlMap{}
+	if len(value) >= int(limit*max/100) {
+		new_value := []string{}
+		//如果已存的session大于限制 则清除三天后到期的session
+		for _, vv := range value {
+			ttl := redis.GetTTL("session", vv)
+			if ttl <= 86400*1 && ttl != -1 {
+				// if ttl <= 600 && ttl != -1 {
+				if ok := redis.Del("session", vv); ok {
+					LoginOutPush(mongodb, userid, vv, apppushRpc)
+					log.Println(fmt.Sprintf("%s用户的sessionid 到期时间还有%v 小于三天 清除 %s", userid, ttl, vv))
+				}
+				continue
+			}
+			new_value = append(new_value, vv)
+			ttlmap = append(ttlmap, TtlMap{vv, int(ttl)})
+		}
+		value = new_value
+	}
+	//清除并发最大限制的某个sessionid
+	if len(value) > max {
+		//排序 获取马上过期的session 清除
+		sort.Slice(ttlmap, func(i, j int) bool {
+			return ttlmap[i].value < ttlmap[j].value
+		})
+		if len(ttlmap) > 0 {
+			//清除sessionid
+			delId := ttlmap[0].key
+			redis.Del("session", delId)
+			value = removeArr(value, delId)
+			LoginOutPush(mongodb, userid, delId, apppushRpc)
+			log.Println(fmt.Sprintf("%s用户的sessionid 超过最大限制 清除 %s ,到期时间%v", userid, delId, ttlmap[0].value))
+			for _, v := range ttlmap {
+				log.Println(v.key, "===", v.value)
+			}
+		}
+	}
+	return redis.Put("other", LoginRedisKey(userid), value, -1)
+}
+
+//清除数组中的s
+func removeArr(arr []string, s string) []string {
+	result := []string{}
+	for _, v := range arr {
+		if v == s {
+			continue
+		}
+		result = append(result, v)
+	}
+	return result
+}
+
+//
+func LoginOutPush(mongodb MongodbSim, userid, sessid, apppushRpc string) {
+	rediskey := fmt.Sprintf("app_%s", sessid)
+	r := redis.Get("other", rediskey)
+	if r == nil {
+		return
+	}
+	arr, err := json.Marshal(r)
+	if err != nil {
+		log.Println("json Marshal err:", err)
+	}
+	apppush := AppLoginPush{}
+	// 反序列化
+	err2 := json.Unmarshal(arr, &apppush)
+	if err2 != nil {
+		log.Println("json Unmarshal err:", err)
+	}
+	if apppush.JgPushId == "" {
+		return
+	}
+	mongodb.UpdateById("user", userid, map[string]interface{}{
+		"$unset":    map[string]interface{}{"s_jpushid": "", "s_opushid": ""},
+		"$addToSet": map[string]interface{}{"a_jpushid": apppush.JgPushId},
+	})
+	//
+	log.Println("多账号登陆:", userid, apppush.PhoneType, apppush.JgPushId, apppush.OtherPushId, "踢人下线")
+	ok := qrpc.AppPush(apppushRpc, map[string]interface{}{
+		"type":        "signOut",
+		"descript":    "您的账号在其他设备登录,如有需要请重新登录。",
+		"jgPushId":    apppush.JgPushId,
+		"otherPushId": apppush.OtherPushId,
+		"phoneType":   apppush.PhoneType,
+		"userId":      userid,
+	})
+	log.Println("ok:", ok)
+}
+
+//session数组清除某个value[针对多账号同时在线的方法、退出登录时调用]
+func DelUnlimitSessionId(sessid, userid string) []string {
+	sessArr := GetLoginSess(userid)
+	newArr := []string{}
+	for _, v := range sessArr {
+		if v == sessid {
+			continue
+		}
+		newArr = append(newArr, v)
+	}
+	return newArr
+}
+
+//获取当天结束时间 单位秒
+func GetExpire() int {
+	t, _ := time.ParseInLocation(Date_Short_Layout, time.Now().AddDate(0, 0, 1).Format(Date_Short_Layout), time.Local)
+	t2, _ := time.ParseInLocation(Date_Full_Layout, time.Now().Format(Date_Full_Layout), time.Local)
+	return int(t.Unix() - t2.Unix())
+}
+
+func TimeProcessing(hour interface{}, duration int) time.Time {
+	todayZero, _ := time.ParseInLocation(Date_Full_Layout, fmt.Sprint(hour), time.Local)
+	mm, _ := time.ParseDuration(fmt.Sprint(duration) + "m")
+	t := todayZero.Add(mm)
+	return t
+}

+ 243 - 0
common/src/qfw/util/jy/memberpush.go

@@ -0,0 +1,243 @@
+package jy
+
+/*
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	mg "mongodb"
+	. util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+	"strings"
+	"time"
+)
+
+var MemberPush = &memberPush{}
+
+type memberPush struct {
+}
+
+//从pushcache_2_a中取
+func (m *memberPush) GetTodayCache(userId string) (*SubPush, error) {
+	pc_a, err := redis.GetNewBytes("pushcache_2_b", m.todayKey(userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *SubPush
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+//往pushcache_2_a中放
+func (m *memberPush) PutTodayCache(userId string, pc_a *SubPush) {
+	redis.Put("pushcache_2_b", m.todayKey(userId), pc_a, threeDay)
+}
+
+//获取redis key
+func (m *memberPush) todayKey(userId string) string {
+	return fmt.Sprintf("memberpush_%s", userId)
+}
+func (m *memberPush) Datas(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, userId string, pageNum int, selectTime, area string, pageSizePC int, startTime, endTime string) (hasNextPage bool, result []*SubPushList, count int64) {
+	if pageNum < 1 {
+		pageNum = 1
+	}
+	if pageSizePC == 0 || pageSizePC > 100 {
+		pageSizePC = pageSize
+	}
+	now := NowFormat(Date_Short_Layout)
+	start := (pageNum - 1) * pageSizePC
+	end := start + pageSizePC
+	if now == selectTime && area == "" {
+		subPush, err := m.GetTodayCache(userId)
+		if err != nil {
+			log.Println(userId, "GetTodayCache Error", err)
+		}
+		if err != nil || subPush == nil || subPush.Date != now || len(subPush.Datas) == 0 {
+			listSearch, countSearch := m.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, userId, pageNum, pageSizePC, selectTime, area, false, startTime, endTime)
+			subPush = &SubPush{
+				Date:  now,
+				Datas: listSearch,
+				Count: countSearch,
+			}
+			m.PutTodayCache(userId, subPush)
+		}
+		count = subPush.Count
+		if end > int(count) {
+			end = int(count)
+		}
+		if start < int(count) {
+			result = subPush.Datas[start:end]
+		}
+	} else if selectTime == "" && area == "" && pageNum <= 5 {
+		allCache, err := m.GetAllCache(userId)
+		if err != nil {
+			log.Println(userId, "GetAllCache Error", err)
+		}
+		if err != nil || allCache == nil {
+			listSearch, countSearch := m.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, userId, 1, AllSubPushCacheSize, selectTime, area, true, startTime, endTime)
+			allCache = &SubPush{
+				Date:  now,
+				Datas: listSearch,
+				Count: countSearch,
+			}
+			m.PutAllCache(userId, allCache)
+		}
+		count = allCache.Count
+		if end > int(count) {
+			end = int(count)
+		}
+		if start < int(count) {
+			result = allCache.Datas[start:end]
+		}
+	} else {
+		result, count = m.getDatasFromMysql(Mgo_bidding, bidding, bidding_back, PushMysql, userId, pageNum, pageSizePC, selectTime, area, true, startTime, endTime)
+	}
+	if result == nil {
+		result = []*SubPushList{}
+	}
+	hasNextPage = len(result) >= pageSizePC
+	return
+}
+func (m *memberPush) getDatasFromMysql(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, PushMysql *mysql.Mysql, userId string, pageNum, myPageSize int, selectTime, area string, isLimit bool, startTime, endTime string) (result []*SubPushList, count int64) {
+	findSQL := ` %s where userid="` + userId + `" `
+	findStr := ""
+	if selectTime != "" {
+		startTime := selectTime + " 00:00:00"
+		endTime := selectTime + " 23:59:59"
+		st, _ := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
+		et, _ := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
+		findStr += " and date < " + fmt.Sprint(et.Unix()) + " and date >= " + fmt.Sprint(st.Unix())
+	} else if startTime != "" && endTime != "" {
+		startTime = startTime + " 00:00:00"
+		endTime = endTime + " 23:59:59"
+		st, _ := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
+		et, _ := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
+		findStr += " and date < " + fmt.Sprint(et.Unix()) + " and date >= " + fmt.Sprint(st.Unix())
+	}
+	if area != "" {
+		var _area, _city = "", ""
+		for _, v := range strings.Split(area, ",") {
+			if v == "全部" {
+				continue
+			}
+			//省份
+			if province, ok := PushMapping.Area[v]; ok {
+				if _area != "" {
+					_area += ","
+				}
+				_area += fmt.Sprint(province)
+			}
+			//城市
+			if city, ok := PushMapping.City[v]; ok {
+				if _city != "" {
+					_city += ","
+				}
+				_city += fmt.Sprint(city)
+			}
+		}
+
+		if _area != "" && _city != "" {
+			findStr += fmt.Sprintf(" and ( area in ( %s ) or city in ( %s ) )", _area, _city)
+		} else if _area != "" && _city == "" { //仅查询省份
+			findStr += fmt.Sprintf(" and area in ( %s ) ", _area)
+		} else if _area == "" && _city != "" { //仅查询城市
+			findStr += fmt.Sprintf(" and  city in ( %s ) ", _city)
+		}
+	}
+	start := (pageNum - 1) * myPageSize
+	count = PushMysql.CountBySql(fmt.Sprintf(findSQL+findStr, "select count(id) from pushmember"))
+
+	findStr += " order by id desc"
+	if isLimit {
+		findStr += " limit " + fmt.Sprint(start) + "," + fmt.Sprint(myPageSize)
+	}
+	findSQL = fmt.Sprintf(findSQL+findStr, `select id,date,infoid,isvisit,matchkeys,type from pushmember`)
+	log.Println("findsql:", findSQL)
+	list := PushMysql.SelectBySql(findSQL)
+	if len(*list) > 0 {
+		pushCas := m.GetJyPushs(*list)
+		result = SubscribePush.GetInfoByIds(Mgo_bidding, bidding, bidding_back, pushCas)
+	} else {
+		result = []*SubPushList{}
+	}
+	return
+}
+
+//获取历史推送
+func (m *memberPush) GetJyPushs(datas []map[string]interface{}) (pushCas []*PushCa) {
+	pushCas = []*PushCa{}
+	for _, v := range datas {
+		keys := []string{}
+		if matchkeys := ObjToString(v["matchkeys"]); matchkeys != "" {
+			keys = strings.Split(matchkeys, " ")
+		}
+		pushCas = append(pushCas, &PushCa{
+			Date:   Int64All(v["date"]),
+			InfoId: ObjToString(v["infoid"]),
+			Visit:  IntAll(v["isvisit"]),
+			Index:  Int64All(v["id"]),
+			Keys:   keys,
+			Type:   IntAll(v["type"]),
+		})
+	}
+	return
+}
+
+func (m *memberPush) Visit(PushMysql *mysql.Mysql, userId string, id int) {
+	if id <= 0 {
+		return
+	}
+	PushMysql.UpdateOrDeleteBySql("update pushmember set isvisit=1 where userid=? and id=?", userId, id)
+	todaySubPush, err := m.GetTodayCache(userId)
+	if err == nil && todaySubPush != nil {
+		for _, v := range todaySubPush.Datas {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		m.PutTodayCache(userId, todaySubPush)
+	}
+	//
+	allSubPush, err := m.GetAllCache(userId)
+	if err == nil && allSubPush != nil {
+		for _, v := range allSubPush.Datas {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		m.PutAllCache(userId, allSubPush)
+	}
+}
+
+//查看全部列表缓存
+func (m *memberPush) allKey(userId string) string {
+	return fmt.Sprintf("all_memberpush_new_%s", userId)
+}
+
+func (m *memberPush) PutAllCache(userId string, datas *SubPush) {
+	redis.Put("pushcache_2_a", m.allKey(userId), datas, threeDay)
+}
+
+func (m *memberPush) GetAllCache(userId string) (*SubPush, error) {
+	pc_a, err := redis.GetNewBytes("pushcache_2_b", m.allKey(userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *SubPush
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+*/

+ 102 - 0
common/src/qfw/util/jy/newpushmapping.go

@@ -0,0 +1,102 @@
+package jy
+
+import (
+	"fmt"
+	"log"
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+)
+
+var NewPushMapping = &newpushMapping{}
+
+const (
+	CODEAREA          = "code_area"
+	CODEBIDSCOPE      = "code_bidscope"
+	CODEBIDTOPSUBTYPE = "code_bidtopsubtype"
+	CODEBUYERCLASS    = "code_buyerclass"
+	Baseinfo          = "global_common_data.dws_f_bid_baseinfo"
+	BidTags           = "global_common_data.dws_f_bid_tags"
+	PushEntniche      = "pushentniche"
+)
+
+type newpushMapping struct {
+	Area          map[string]string
+	City          map[string]string
+	Toptype       map[string]string
+	Subtype       map[string]string
+	Buyerclass    map[string]string
+	Subscopeclass map[string]string
+}
+
+func (p *newpushMapping) Init(Mysql *mysql.Mysql) {
+	//信息类型
+	infotype := Mysql.SelectBySql(fmt.Sprintf("select level,code,name from %s where level=2", CODEBIDTOPSUBTYPE))
+	Subtype := map[string]string{}
+	if infotype != nil && len(*infotype) > 0 {
+		for _, v := range *infotype {
+			code := util.ObjToString(v["code"])
+			name := util.ObjToString(v["name"])
+			Subtype[name] = code
+		}
+		if len(Subtype) == 0 {
+			log.Fatalln("PushMapping Subtype Init Error")
+		}
+		p.Subtype = Subtype
+	}
+	//采购单位行业
+	Buyerclass := map[string]string{}
+	buyerclass := Mysql.SelectBySql(fmt.Sprintf("select code,name from %s where level=1", CODEBUYERCLASS))
+	if buyerclass != nil && len(*buyerclass) > 0 {
+		for _, v := range *buyerclass {
+			code := util.ObjToString(v["code"])
+			name := util.ObjToString(v["name"])
+			Buyerclass[name] = code
+		}
+
+		if len(Buyerclass) == 0 {
+			log.Fatalln("PushMapping Buyerclass Init Error")
+		}
+		p.Buyerclass = Buyerclass
+	}
+
+	//公告类型处理
+	Subscopeclass := map[string]string{}
+	subscopeclass := Mysql.SelectBySql(fmt.Sprintf("select a.code, CONCAT(b.name,'_',a.name) as name  from %s a LEFT JOIN %s b on a.pcode=b.code where  a.level=2", CODEBIDSCOPE, CODEBIDSCOPE))
+	if subscopeclass != nil && len(*subscopeclass) > 0 {
+		for _, v := range *subscopeclass {
+			code := util.ObjToString(v["code"])
+			name := util.ObjToString(v["name"])
+			Subscopeclass[name] = code
+		}
+
+		if len(Subscopeclass) == 0 {
+			log.Fatalln("PushMapping subscopeclass Init Error")
+		}
+		p.Subscopeclass = Subscopeclass
+	}
+	//省份处理
+	Area := map[string]string{}
+	City := map[string]string{}
+	province := Mysql.SelectBySql(fmt.Sprintf("select code,area,city from %s where (district is null or district='')", CODEAREA))
+	if province != nil && len(*province) > 0 {
+		for _, v := range *province {
+			code := util.ObjToString(v["code"])
+			city := util.ObjToString(v["city"])
+			area := util.ObjToString(v["area"])
+			if city == "" {
+				Area[area] = code
+			} else {
+				City[city] = code
+			}
+		}
+		if len(Area) == 0 {
+			log.Fatalln("PushMapping Area Init Error")
+		}
+		if len(City) == 0 {
+			log.Fatalln("PushMapping City Init Error")
+		}
+		p.Area = Area
+		p.City = City
+	}
+
+}

+ 64 - 0
common/src/qfw/util/jy/nsq.go

@@ -0,0 +1,64 @@
+package jy
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	"time"
+
+	"app.yhyue.com/moapp/message/model"
+	"github.com/nsqio/go-nsq"
+)
+
+const (
+	Jyweb_article_open     = "jyweb_article_open"     //打开招投标信息三级页
+	Jydocs_doc_open        = "jydocs_doc_open"        //打开文库三级页
+	Jyapp_wx_register      = "jyapp_wx_register"      //app微信注册
+	Jyapp_phone_register   = "jyapp_phone_register"   //app手机号注册
+	Jypc_phone_register    = "jypc_phone_register"    //pc端手机号注册
+	Jywx_subscribe_new     = "jywx_subscribe_new"     //微信新用户关注
+	Jywx_subscribe_invite  = "jywx_subscribe_invite"  //已邀请并产生了新用户
+	Jywx_subscribe_invited = "jywx_subscribe_invited" //被邀请产生新用户
+
+	Jywx_node1  = "jywx_node1"  //微信端
+	Jyweb_node2 = "jyweb_node2" //pc端
+	Jyapp_node1 = "jyapp_node1" //app端
+	Jysubscrib  = "jysubscribe" //支付
+)
+
+//e_code 模块
+//e_app 终端
+func Publish(mg MongodbSim, ip, topicName, e_code, e_userid, e_app string) error {
+	if topicName == "" {
+		return errors.New("未找到topicName,请检查tye参数是否正确")
+	}
+	cfg := nsq.NewConfig()
+	producer, err := nsq.NewProducer(ip, cfg)
+	if err != nil {
+		fmt.Println("err1---", err)
+	}
+	msg := &model.Message{
+		E_code:   e_code,
+		E_userId: e_userid,
+		E_time:   time.Now().Unix(), //1605223065
+		E_app:    e_app,
+	}
+	b, _ := json.Marshal(msg)
+	err = producer.Publish(topicName, b)
+	if err != nil {
+		fmt.Println("errr2:", err)
+	}
+	producer.Stop()
+	saveLog(mg, topicName, *msg)
+	return err
+}
+
+func saveLog(mg MongodbSim, topicName string, msg model.Message) {
+	mg.Save("nsq_logs", map[string]interface{}{
+		"body":       msg,
+		"topicName":  topicName,
+		"createtime": time.Now().Unix(),
+		"type":       "producer",
+	})
+}

+ 178 - 0
common/src/qfw/util/jy/payUser.go

@@ -0,0 +1,178 @@
+package jy
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"strings"
+
+	"app.yhyue.com/moapp/jybase/mongodb"
+
+	qu "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+)
+
+//是否是付费用户
+type VipState struct {
+	VipState     int   //超级订阅状态(1普通 2升级版)
+	BigMember    int   //大会员状态
+	EntMember    int   //商机管理用户状态
+	registerData int64 //注册时间
+}
+
+func GetVipState(mysql *mysql.Mysql, mg mongodb.MongodbSim, userId string) (vs *VipState) {
+	vs = &VipState{}
+	if userId == "" {
+		return
+	}
+	phone := ""
+	data, ok := mg.FindById("user", userId, `"i_member_status":1,"i_vip_status":1,"s_m_phone":1,"s_phone":1,"o_vipjy":1,"l_registedate":1`)
+	if data != nil && len(*data) > 0 && ok {
+		i_vip_status := qu.IntAll((*data)["i_vip_status"])
+		if i_vip_status > 1 {
+			vs.VipState = 1
+			ovipjy, _ := (*data)["o_vipjy"].(map[string]interface{})
+			if ovipjy["o_buyset"] != nil {
+				o_buyset := ovipjy["o_buyset"].(map[string]interface{})
+				if o_buyset["upgrade"] != nil {
+					vs.VipState = 2
+				}
+			}
+		}
+		if i_member_status := qu.IntAllDef((*data)["i_member_status"], 0); i_member_status > 0 {
+			vs.BigMember = i_member_status
+		}
+		if s_phone, _ := (*data)["s_phone"].(string); s_phone != "" {
+			phone = s_phone
+		} else if s_m_phone, _ := (*data)["s_m_phone"].(string); s_m_phone != "" {
+			phone = s_m_phone
+		}
+		if phone != "" {
+			if mysql.CountBySql(`select count(1) from entniche_user where phone = ? and power =1`, phone) > 0 {
+				vs.EntMember = 1
+			}
+		}
+		vs.registerData, _ = ((*data)["l_registedate"]).(int64)
+	}
+	return
+}
+
+//是否是付费账户
+func (vs *VipState) IsPayedUser() bool {
+	return vs.VipState > 0 || vs.BigMember > 0 || vs.EntMember > 0
+}
+
+//免费 标题(title)  正文(content) 老用户【中标企业(winner)】
+//付费用户 全部(all)、标题(title)  正文(content)  会员: 采购单位(buyer) 中标企业(winner) 招标代理机构(agency) 附件(file)
+//项目名称projectname和标的物purchasing(ppa)
+func (vs *VipState) GetQueryItems(selectType string, limitOldTime int64) (items []string) {
+	if vs.IsPayedUser() {
+		for _, t := range strings.Split(selectType, ",") {
+			if t == "content" {
+				items = append(items, "detail")
+			} else if t == "buyer" {
+				items = append(items, "mbuyer")
+			} else if t == "winner" {
+				items = append(items, "mwinner")
+			} else if t == "agency" {
+				items = append(items, "magency")
+			} else if t == "title" {
+				items = append(items, "title")
+			} else if t == "ppa" {
+				items = append(items, []string{"purchasing", "projectname.pname"}...)
+			} else if t == "file" { //dev4.7.8 标讯优化:搜索范围附件-》全部用户可用
+				items = append(items, "filetext")
+			}
+		}
+		return
+	}
+	isOldUser := vs.registerData != 0 && vs.registerData < limitOldTime
+	for _, t := range strings.Split(selectType, ",") {
+		if t == "winner" {
+			if isOldUser {
+				items = append(items, "mwinner")
+			}
+		} else if t == "title" {
+			items = append(items, "title")
+		} else if t == "content" {
+			items = append(items, "detail")
+		} else if t == "file" { //dev4.7.8 标讯优化:搜索范围附件-》全部用户可用
+			items = append(items, "filetext")
+		}
+	}
+	return
+}
+
+// HasBidFieldPower 获取用户是否有领域化数据权限
+func HasBidFieldPower(url string, userId string, functionCode string) bool {
+	// 是否开通过权益
+	header := map[string]string{
+		"newUserId": userId,
+	}
+	byJson, err := PostByJson(url, []byte(""), header)
+	if err != nil || byJson == nil {
+		log.Println("查询用户权益", url, userId, functionCode, err)
+		return false
+	}
+	if byJson != nil {
+		powerList := byJson["data"].([]interface{})
+		for i := 0; i < len(powerList); i++ {
+			if powerList[i] == functionCode {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func PostByJson(reqUrl string, data []byte, header map[string]string) (rs map[string]interface{}, err error) {
+	reqData := bytes.NewBuffer(data)
+	log.Println("请求参数", reqData)
+	method := "POST"
+	client := &http.Client{}
+	req, err2 := http.NewRequest(method, reqUrl, reqData)
+	if err2 != nil {
+		log.Println(err2)
+	}
+	req.Header.Add("Content-Type", "application/json")
+	if header != nil && len(header) > 0 {
+		for k, v := range header {
+			req.Header.Add(k, v)
+
+		}
+	}
+	log.Printf("请求url:%v", reqUrl)
+	log.Printf("请求header:%v", header)
+	res, err3 := client.Do(req)
+	if err3 != nil {
+		log.Println(err3)
+	}
+	log.Printf("err:%v,结果:%v", err3, res)
+	defer func(Body io.ReadCloser) {
+		err6 := Body.Close()
+		if err6 != nil {
+			log.Println("ReadCloser Err", err6)
+		}
+	}(res.Body)
+
+	if err3 != nil {
+		log.Println("请求失败:", err3)
+		return map[string]interface{}{}, errors.New("请求失败")
+	}
+	body, err4 := ioutil.ReadAll(res.Body)
+	if err4 != nil {
+		log.Println("读取响应数据信息失败", err4)
+		return map[string]interface{}{}, errors.New("读取响应信息失败")
+	}
+	err5 := json.Unmarshal(body, &rs)
+	if err5 != nil {
+		log.Println("反序列化数据失败", err5)
+		return map[string]interface{}{}, errors.New("反序列化数据失败")
+	}
+	log.Println("结果:", rs)
+	return rs, nil
+}

+ 27 - 0
common/src/qfw/util/jy/payUser_test.go

@@ -0,0 +1,27 @@
+package jy
+
+import "testing"
+
+func TestHasBidFieldPower(t *testing.T) {
+	type args struct {
+		url          string
+		userId       string
+		functionCode string
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			"获取用户是否有领域化权益", args{"http://192.168.3.206:1006/resourceCenter/haspowers", "69946", "lyh_yl_ylbxss"}, true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := HasBidFieldPower(tt.args.url, tt.args.userId, tt.args.functionCode); got != tt.want {
+				t.Errorf("HasBidFieldPower() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}

+ 76 - 0
common/src/qfw/util/jy/pushmapping.go

@@ -0,0 +1,76 @@
+package jy
+
+import (
+	"log"
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+)
+
+var PushMapping = &pushMapping{}
+
+type pushMapping struct {
+	Area          map[string]int
+	City          map[string]int
+	Toptype       map[string]int
+	Subtype       map[string]int
+	Buyerclass    map[string]int
+	Subscopeclass map[string]int
+}
+
+func (p *pushMapping) Init(Mysql *mysql.Mysql) {
+	infotype := Mysql.SelectBySql("select id,type,name from infotype")
+	p.Toptype = map[string]int{}
+	p.Subtype = map[string]int{}
+	p.Buyerclass = map[string]int{}
+	p.Subscopeclass = map[string]int{}
+	if infotype != nil && len(*infotype) > 0 {
+		for _, v := range *infotype {
+			id := util.IntAll(v["id"])
+			tp := util.IntAll(v["type"])
+			name := util.ObjToString(v["name"])
+			if tp == 1 {
+				p.Toptype[name] = id
+			} else if tp == 2 {
+				p.Subtype[name] = id
+			} else if tp == 3 {
+				p.Buyerclass[name] = id
+			} else if tp == 4 {
+				p.Subscopeclass[name] = id
+			}
+		}
+		if len(p.Toptype) == 0 {
+			log.Fatalln("PushMapping Toptype Init Error")
+		}
+		if len(p.Subtype) == 0 {
+			log.Fatalln("PushMapping Subtype Init Error")
+		}
+		if len(p.Buyerclass) == 0 {
+			log.Fatalln("PushMapping Buyerclass Init Error")
+		}
+		if len(p.Subscopeclass) == 0 {
+			log.Fatalln("PushMapping Subscopeclass Init Error")
+		}
+	}
+	//
+	p.Area = map[string]int{}
+	p.City = map[string]int{}
+	province := Mysql.SelectBySql("select id,level,name from province")
+	if province != nil && len(*province) > 0 {
+		for _, v := range *province {
+			id := util.IntAll(v["id"])
+			level := util.IntAll(v["level"])
+			name := util.ObjToString(v["name"])
+			if level == 1 {
+				p.Area[name] = id
+			} else if level == 2 {
+				p.City[name] = id
+			}
+		}
+		if len(p.Area) == 0 {
+			log.Fatalln("PushMapping Area Init Error")
+		}
+		if len(p.City) == 0 {
+			log.Fatalln("PushMapping City Init Error")
+		}
+	}
+}

+ 1005 - 0
common/src/qfw/util/jy/subscribepush.go

@@ -0,0 +1,1005 @@
+package jy
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	. "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/date"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	mg "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const (
+	threeDay  = 172800
+	oneDay    = 86400
+	halfOfDay = 43200
+)
+
+type SubPushList struct {
+	Id            string      `json:"_id"`
+	Title         string      `json:"title"`
+	Area          string      `json:"area"`
+	BuyerClass    string      `json:"buyerClass"`
+	Type          string      `json:"type"`
+	Subtype       string      `json:"subtype"`
+	Industry      string      `json:"industry"`
+	PublishTime   int64       `json:"publishTime"`
+	Ca_index      int64       `json:"ca_index"`
+	Ca_date       int64       `json:"ca_date"`
+	Ca_isvisit    int         `json:"ca_isvisit"`
+	Ca_isvip      int         `json:"ca_isvip"`
+	Ca_type       int         `json:"ca_type"`
+	Ca_fileExists bool        `json:"ca_fileExists"`
+	MatchKeys     []string    `json:"matchKeys"`
+	Budget        interface{} `json:"budget"`
+	BidAmount     interface{} `json:"bidAmount"`
+	Collection    int         `json:"collection"`
+	Buyer         string      `json:"buyer"`
+	ProjectName   string      `json:"projectName"`
+	Winner        string      `json:"winner"`
+	BidOpenTime   int64       `json:"bidOpenTime"`
+	Spidercode    string      `json:"spidercode"`
+	Site          string      `json:"site"`
+}
+
+const (
+	pageSize            = 100
+	pageSizes           = 10
+	AllSubPushCacheSize = 250
+	query               = `{"query":{"terms":{"_id":["%s"]}},"_source":["_id","area", "publishtime", "s_subscopeclass", "subtype", "title", "toptype", "type", "buyerclass","bidamount","budget","projectname","buyer","bidopentime","s_winner","spidercode","site"],"from":0,"size":%d}`
+	mongodb_fields      = `{"_id":1,"area":1,"publishtime":1,"s_subscopeclass":1,"subtype":1,"title":1,"toptype":1,"type":1, "buyerclass":1,"budget":1,"bidamount":1,"s_winner":1,"bidopentime":1,"buyer":1,"projectname":1,"spidercode":1,"site":1}`
+	querys              = `{"query":{"terms":{"_id":["%s"]}},"_source":["_id","title","detail","area","city","publishtime","projectname","buyer","buyerclass","s_winner","bidamount","subtype","toptype","href","projectcode","buyerperson","buyertel","budget","bidopentime","agency","projectscope","winnerperson","winnertel","spidercode","site"]}`
+
+	MemberFlag     = "m"
+	SubVipFlag     = "v"
+	EntnicheFlag   = "s"
+	SubFreeFlag    = ""
+	SubNewFreeFlag = "f"
+)
+
+var aboutDbMsg map[string]*AboutDbMsg = map[string]*AboutDbMsg{
+	SubNewFreeFlag: &AboutDbMsg{"pushsubscribe", "subpush"},
+	SubFreeFlag:    &AboutDbMsg{"pushsubscribe", "subpush"},
+	SubVipFlag:     &AboutDbMsg{"pushsubscribe", "subpush"},
+	MemberFlag:     &AboutDbMsg{"pushmember", "memberpush"},
+	EntnicheFlag:   &AboutDbMsg{"pushentniche", "entnichepush"},
+}
+
+type AboutDbMsg struct {
+	MysqlTable   string
+	RedisKeyFlag string
+}
+
+type SubPush struct {
+	Date  string
+	Datas []*SubPushList
+	Count int64
+}
+
+type PushCa struct {
+	Date       int64
+	InfoId     string
+	Visit      int
+	Index      int64
+	Keys       []string
+	Type       int
+	Isvip      int
+	FileExists bool
+}
+
+//查询参数
+type SubPushQueryParam struct {
+	Mgo_bidding   mg.MongodbSim //
+	Bidding       string        //
+	Bidding_back  string        //
+	PushMysql     *mysql.Mysql  //
+	UserId        string        //用户id
+	PageNum       int           //页面
+	PageSize      int           //每页数量
+	SelectTime    string        //时间
+	Area          string        //区域
+	City          string        //城市
+	Buyerclass    string        //采购单位行业
+	Subtype       string        //信息类型 二级分类
+	Subscopeclass string        //信息行业
+	Key           string        //订阅词
+	Export        bool          //导出
+	EntId         int           //企业id
+	Price         string        //价格
+	FileExists    string        //是否有附件;默认全部;1:有附件;-1:无附件
+	SelectInfoIds []string      //选择信息id
+}
+
+//spqp.SelectTime == "all"  满足PC端超级订阅和免费用户 第一次进入订阅列表无数据 默认匹配部分数据的问题
+func (spqp *SubPushQueryParam) IsEmpty() bool {
+	return (spqp.SelectTime == "" || spqp.SelectTime == "all") && spqp.Area == "" && spqp.City == "" && spqp.Buyerclass == "" && spqp.Subscopeclass == "" && spqp.Subtype == "" && spqp.Key == "" && spqp.Price == "" && spqp.FileExists == ""
+}
+
+type subscribePush struct {
+	ModuleFlag string
+}
+
+func NewSubscribePush(module ...string) *subscribePush {
+	m := ""
+	if len(module) > 0 {
+		m = module[0]
+	}
+	return &subscribePush{m}
+}
+
+//从pushcache_2_a中取
+func (h *subscribePush) GetTodayCache(userId string) (*SubPush, error) {
+	pc_a, err := redis.GetNewBytes("pushcache_2_b", h.todayKey(userId))
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *SubPush
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+//往pushcache_2_a中放
+func (h *subscribePush) PutTodayCache(userId string, pc_a *SubPush) {
+	redis.Put("pushcache_2_b", h.todayKey(userId), pc_a, oneDay)
+}
+
+//获取redis key
+func (s *subscribePush) todayKey(userId string) string {
+	return fmt.Sprintf("%s_%s", aboutDbMsg[s.ModuleFlag].RedisKeyFlag, userId)
+}
+func (s *subscribePush) allKey(userId string) string {
+	return fmt.Sprintf("all_%s_%s", aboutDbMsg[s.ModuleFlag].RedisKeyFlag, userId)
+}
+
+//历史推送记录中单条信息格式化
+func (s *subscribePush) InfoFormat(p *PushCa, info *map[string]interface{}) *SubPushList {
+	area := ObjToString((*info)["area"])
+	if area == "A" {
+		area = "全国"
+	}
+	industry := ObjToString((*info)["s_subscopeclass"])
+	scs := strings.Split(industry, ",")
+	if len(scs) > 0 {
+		industry = scs[0]
+		if industry != "" {
+			iss := strings.Split(industry, "_")
+			if len(iss) > 0 {
+				industry = iss[0]
+			}
+		}
+	}
+	infotype := ObjToString((*info)["subtype"])
+	if infotype == "" {
+		infotype = ObjToString((*info)["toptype"])
+	}
+	if infotype == "" {
+		infotype = ObjToString((*info)["type"])
+		if infotype == "tender" {
+			infotype = "招标"
+		} else if infotype == "bid" {
+			infotype = "中标"
+		}
+	}
+	_id := p.InfoId
+	if _id == "" {
+		_id = ObjToString((*info)["_id"])
+	}
+	return &SubPushList{
+		Id:            EncodeArticleId2ByCheck(_id),
+		Title:         ObjToString((*info)["title"]),
+		Area:          area,
+		BuyerClass:    ObjToString((*info)["buyerclass"]),
+		Type:          infotype,
+		Subtype:       infotype,
+		Industry:      industry,
+		PublishTime:   Int64All((*info)["publishtime"]),
+		Ca_index:      p.Index,
+		Ca_date:       p.Date,
+		Ca_isvisit:    p.Visit,
+		Ca_isvip:      p.Isvip,
+		Ca_type:       p.Type,
+		Ca_fileExists: p.FileExists,
+		MatchKeys:     p.Keys,
+		Budget:        (*info)["budget"],
+		BidAmount:     (*info)["bidamount"],
+		Buyer:         ObjToString((*info)["buyer"]),
+		ProjectName:   ObjToString((*info)["projectname"]),
+		Winner:        ObjToString((*info)["s_winner"]),
+		BidOpenTime:   Int64All((*info)["bidopentime"]),
+		Site:          ObjToString((*info)["site"]),
+		Spidercode:    ObjToString((*info)["spidercode"]),
+	}
+}
+
+func (s *subscribePush) Datas(spqp *SubPushQueryParam) (hasNextPage bool, total int64, result []*SubPushList) {
+	log.Println(spqp.UserId, s.ModuleFlag, "subscribePush query param:", "SelectTime:", spqp.SelectTime, "Area:", spqp.Area, "City:", spqp.City, "Subtype:", spqp.Subtype, "Subscopeclass:", spqp.Subscopeclass, "Buyerclass:", spqp.Buyerclass, "Key:", spqp.Key, "PageNum:", spqp.PageNum, "Price:", spqp.Price, "FileExists:", spqp.FileExists)
+	if spqp.UserId == "" {
+		return
+	}
+	if spqp.PageNum < 1 {
+		spqp.PageNum = 1
+	}
+	if spqp.PageSize < 1 || spqp.PageSize > pageSize {
+		if !spqp.Export {
+			spqp.PageSize = pageSize
+		} else { //数据导出查询20000条限制
+			if spqp.PageSize < 1 || spqp.PageSize > 20000 {
+				spqp.PageSize = 20000
+			}
+		}
+	}
+	starttime, endtime := int64(0), int64(0)
+	st, et := "", ""
+	now := time.Now()
+	if spqp.SelectTime == "today" { //今天
+		starttime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
+	} else if spqp.SelectTime == "yesterday" { //昨天
+		starttime = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, time.Local).Unix()
+		endtime = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
+	} else if spqp.SelectTime == "lately-7" { //最近7天
+		starttime = time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local).Unix()
+	} else if spqp.SelectTime == "lately-30" { //最近30天
+		starttime = time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local).Unix()
+	} else if spqp.SelectTime == "lastyear" { //去年
+		starttime = time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, time.Local).Unix()
+		endtime = time.Date(now.Year()-1, 12, 31, 23, 59, 59, 0, time.Local).Unix()
+	} else if len(strings.Split(spqp.SelectTime, "_")) == 2 {
+		st = strings.Split(spqp.SelectTime, "_")[0]
+		starttime, _ = strconv.ParseInt(st, 0, 64)
+		et = strings.Split(spqp.SelectTime, "_")[1]
+		endtime, _ = strconv.ParseInt(et, 0, 64)
+		if endtime > 0 {
+			etTime := time.Unix(endtime, 0)
+			endtime = time.Date(etTime.Year(), etTime.Month(), etTime.Day(), 23, 59, 59, 0, time.Local).Unix()
+		}
+	}
+	nowFormat := NowFormat(Date_Short_Layout)
+	start := (spqp.PageNum - 1) * spqp.PageSize
+	end := start + spqp.PageSize
+	//时间是今天,没有别的过滤条件
+	if nowFormat == FormatDateByInt64(&starttime, Date_Short_Layout) && spqp.Area == "" && spqp.City == "" && spqp.Buyerclass == "" && spqp.Subscopeclass == "" && spqp.Subtype == "" && spqp.Key == "" && spqp.Price == "" && spqp.FileExists == "" {
+		subPush, err := s.GetTodayCache(spqp.UserId)
+		if err != nil {
+			log.Println(spqp.UserId, "GetTodayCache Error", err)
+		}
+		if err != nil || subPush == nil || subPush.Date != nowFormat || len(subPush.Datas) == 0 {
+			list, countSearch := s.getDatasFromMysql(spqp, starttime, endtime, spqp.PageSize, false)
+			subPush = &SubPush{
+				Date:  nowFormat,
+				Datas: list,
+				Count: countSearch,
+			}
+			s.PutTodayCache(spqp.UserId, subPush)
+		}
+		length := len(subPush.Datas)
+		if end > length {
+			end = length
+		}
+		if start < length {
+			result = subPush.Datas[start:end]
+		}
+		total = int64(length)
+	} else if spqp.IsEmpty() && (spqp.PageNum-1)*spqp.PageSize <= 250 && len(spqp.SelectInfoIds) == 0 { //全部,没有过滤条件 之前缓存5页*50条=250
+		allCache, err := s.GetAllCache(spqp.UserId)
+		if err != nil {
+			log.Println(spqp.UserId, "GetAllCache Error", err)
+		}
+		if err != nil || allCache == nil || allCache.Date != nowFormat || len(allCache.Datas) == 0 {
+			spqp.PageNum = 1
+			list, countSearch := s.getDatasFromMysql(spqp, starttime, endtime, AllSubPushCacheSize, true)
+			allCache = &SubPush{
+				Date:  nowFormat,
+				Datas: list,
+				Count: countSearch,
+			}
+			s.PutAllCache(spqp.UserId, allCache)
+		}
+		length := len(allCache.Datas)
+		if end > length {
+			end = length
+		}
+		if start < length {
+			result = allCache.Datas[start:end]
+		}
+		total = allCache.Count
+	} else {
+		result, total = s.getDatasFromMysql(spqp, starttime, endtime, spqp.PageSize, true)
+	}
+	if result == nil {
+		result = []*SubPushList{}
+	}
+	hasNextPage = len(result) >= spqp.PageSize
+	return
+}
+func (s *subscribePush) getDatasFromMysql(spqp *SubPushQueryParam, starttime, endtime int64, size int, isLimit bool) (result []*SubPushList, count int64) {
+	querys := []string{fmt.Sprintf("userid='%s'", spqp.UserId)}
+	if len(spqp.SelectInfoIds) != 0 {
+		querys = append(querys, fmt.Sprintf("infoid in ('%s')", strings.Join(spqp.SelectInfoIds, "','")))
+	} else {
+		//时间
+		if starttime > 0 && endtime > 0 {
+			querys = append(querys, fmt.Sprintf("date>=%d and date<=%d", starttime, endtime))
+		} else if starttime > 0 && endtime == 0 {
+			querys = append(querys, fmt.Sprintf("date>=%d", starttime))
+		} else if starttime == 0 && endtime > 0 {
+			querys = append(querys, fmt.Sprintf("date<=%d", endtime))
+		}
+		if spqp.Area != "" || spqp.City != "" {
+			var sqlAreaCity = ""
+			//城市
+			city := []string{}
+			for _, v := range strings.Split(spqp.City, ",") {
+				if PushMapping.City[v] > 0 {
+					city = append(city, fmt.Sprint(PushMapping.City[v]))
+				} else {
+					city = append(city, "-1")
+				}
+			}
+			if len(city) == 1 {
+				city = append(city, "9999")
+			}
+			if len(city) > 0 {
+				sqlAreaCity = fmt.Sprintf("city in (%s)", strings.Join(city, ","))
+			}
+			//区域
+			var sqlArea = ""
+			area := []string{}
+			for _, v := range strings.Split(spqp.Area, ",") {
+				if PushMapping.Area[v] > 0 {
+					area = append(area, fmt.Sprint(PushMapping.Area[v]))
+				} else {
+					area = append(area, "-1")
+				}
+			}
+			if len(area) == 1 {
+				area = append(area, "9999")
+			}
+			if len(area) > 0 {
+				sqlArea = fmt.Sprintf("area in (%s)", strings.Join(area, ","))
+			}
+			if sqlAreaCity != "" && sqlArea != "" {
+				sqlAreaCity = "( " + sqlAreaCity + " or " + sqlArea + " )"
+			} else if sqlAreaCity == "" && sqlArea != "" {
+				sqlAreaCity = sqlArea
+			}
+			if sqlAreaCity != "" {
+				querys = append(querys, sqlAreaCity)
+			}
+		}
+		//采购单位行业
+		if spqp.Buyerclass != "" {
+			buyerclass := []string{}
+			for _, v := range strings.Split(spqp.Buyerclass, ",") {
+				buyerclass = append(buyerclass, fmt.Sprint(PushMapping.Buyerclass[v]))
+			}
+			if len(buyerclass) == 1 {
+				buyerclass = append(buyerclass, "9999")
+			}
+			if len(buyerclass) > 0 {
+				querys = append(querys, fmt.Sprintf("buyerclass in (%s)", strings.Join(buyerclass, ",")))
+			}
+		}
+		//信息类型
+		if spqp.Subtype != "" {
+			subtype := []string{}
+			for _, v := range strings.Split(spqp.Subtype, ",") {
+				subtype = append(subtype, fmt.Sprint(PushMapping.Subtype[v]))
+			}
+			if len(subtype) == 1 {
+				subtype = append(subtype, "9999")
+			}
+			if len(subtype) > 0 {
+				querys = append(querys, fmt.Sprintf("subtype in (%s)", strings.Join(subtype, ",")))
+			}
+		}
+		//信息行业
+		if spqp.Subscopeclass != "" {
+			find_in_set := []string{}
+			for _, v := range strings.Split(spqp.Subscopeclass, ",") {
+				find_in_set = append(find_in_set, fmt.Sprintf("find_in_set('%d',subscopeclass)", PushMapping.Subscopeclass[v]))
+			}
+			if len(find_in_set) == 1 {
+				querys = append(querys, find_in_set[0])
+			} else if len(find_in_set) > 1 {
+				querys = append(querys, fmt.Sprintf("(%s)", strings.Join(find_in_set, " or ")))
+			}
+		}
+		//关键词
+		if spqp.Key != "" {
+			find_in_set := []string{}
+			for _, v := range strings.Split(spqp.Key, ",") {
+				find_in_set = append(find_in_set, fmt.Sprintf("find_in_set('%s',replace(replace(matchkeys,'+',','),' ',','))", v))
+			}
+			if len(find_in_set) == 1 {
+				querys = append(querys, find_in_set[0])
+			} else if len(find_in_set) > 1 {
+				querys = append(querys, fmt.Sprintf("(%s)", strings.Join(find_in_set, " or ")))
+			}
+		}
+		//价格- 预算和中标金额
+		if spqp.Price != "" && strings.Contains(spqp.Price, "-") {
+			minPriceStr, maxPriceStr := strings.Split(spqp.Price, "-")[0], strings.Split(spqp.Price, "-")[1]
+			minPrice := Int64All(Float64All(minPriceStr) * 10000) //换成元
+			maxPrice := Int64All(Float64All(maxPriceStr) * 10000) //换成元
+			if minPriceStr != "" && maxPriceStr != "" {
+				querys = append(querys, fmt.Sprintf("((bidamount>=%d and bidamount<=%d) or (budget>=%d and budget<=%d and bidamount is null))", minPrice, maxPrice, minPrice, maxPrice))
+			} else if minPriceStr != "" {
+				querys = append(querys, fmt.Sprintf("(bidamount>=%d  or (budget>=%d and bidamount is null))", minPrice, minPrice))
+			} else if maxPriceStr != "" {
+				querys = append(querys, fmt.Sprintf("( bidamount<=%d or (budget<=%d and bidamount is null))", maxPrice, maxPrice))
+			}
+		}
+		//附件
+		if spqp.FileExists != "" {
+			if spqp.FileExists == "1" {
+				querys = append(querys, fmt.Sprintf("attachment_count is not null"))
+			} else if spqp.FileExists == "-1" {
+				querys = append(querys, fmt.Sprintf("attachment_count is null"))
+			}
+		}
+	}
+	searchSql := fmt.Sprintf(" from %s where %s order by id desc", aboutDbMsg[s.ModuleFlag].MysqlTable, strings.Join(querys, " and "))
+	log.Println("searchSql", searchSql)
+	//查询总数
+	count = spqp.PushMysql.CountBySql(fmt.Sprintf("select count(id)" + searchSql))
+	findSql := "select id,date,infoid,isvisit,matchkeys,type,attachment_count"
+	if s.ModuleFlag != MemberFlag {
+		if s.ModuleFlag == EntnicheFlag {
+
+		} else {
+			findSql += ",isvip"
+		}
+	}
+	findSql += searchSql
+	if isLimit {
+		findSql += fmt.Sprintf(" limit %d,%d", (spqp.PageNum-1)*size, size)
+	}
+	log.Println(spqp.UserId, "subscribePush query sql:", findSql)
+	list := spqp.PushMysql.SelectBySql(findSql)
+	if list != nil && len(*list) > 0 {
+		pushCas := s.GetJyPushs(*list)
+		if spqp.Export { //数据导出,仅需要信息id
+			result = s.GetOnlyExportInfo(pushCas)
+		} else {
+			result = s.GetInfoByIds(spqp.Mgo_bidding, spqp.Bidding, spqp.Bidding_back, pushCas)
+		}
+	} else {
+		result = []*SubPushList{}
+	}
+	return
+}
+
+// GetOnlyInfoId 获取信息id
+func (s *subscribePush) GetOnlyExportInfo(pushCas []*PushCa) []*SubPushList {
+	array := make([]*SubPushList, len(pushCas))
+	for k, v := range pushCas {
+		array[k] = &SubPushList{Id: EncodeArticleId2ByCheck(v.InfoId), MatchKeys: v.Keys}
+	}
+	return array
+}
+
+//获取历史推送
+func (s *subscribePush) GetJyPushs(datas []map[string]interface{}) (pushCas []*PushCa) {
+	pushCas = []*PushCa{}
+	for _, v := range datas {
+		keys := []string{}
+		if matchkeys := ObjToString(v["matchkeys"]); matchkeys != "" {
+			keys = strings.Split(matchkeys, " ")
+		}
+		pushCas = append(pushCas, &PushCa{
+			Date:       Int64All(v["date"]),
+			InfoId:     ObjToString(v["infoid"]),
+			Visit:      IntAll(v["isvisit"]),
+			Index:      Int64All(v["id"]),
+			Keys:       keys,
+			Type:       IntAll(v["type"]),
+			Isvip:      IntAll(v["isvip"]),
+			FileExists: IntAll(v["attachment_count"]) > 0,
+		})
+	}
+	return
+}
+
+//根据id取内容
+func (s *subscribePush) GetInfoByIds(Mgo_bidding mg.MongodbSim, bidding, bidding_back string, pushCas []*PushCa) []*SubPushList {
+	array := make([]*SubPushList, len(pushCas))
+	if len(pushCas) == 0 {
+		return array
+	}
+	m := map[string]bool{}
+	ids := []string{}
+	for _, v := range pushCas {
+		if m[v.InfoId] {
+			continue
+		}
+		m[v.InfoId] = true
+		ids = append(ids, v.InfoId)
+	}
+	infos := map[string]map[string]interface{}{}
+	//redis
+	es_ids := ids
+	for _, v := range ids {
+		info_i := redis.Get("pushcache_1", fmt.Sprintf("info_%s", v))
+		if info_i != nil {
+			info_m, _ := info_i.(map[string]interface{})
+			info_m["_id"] = v
+			infos[v] = info_m
+		} else {
+			es_ids = append(es_ids, v)
+		}
+	}
+	//elasticsearch
+	if len(es_ids) > 0 {
+		list := elastic.Get("bidding", "bidding", fmt.Sprintf(query, strings.Join(es_ids, `","`), len(es_ids)))
+		if list != nil {
+			for _, v := range *list {
+				_id := ObjToString(v["_id"])
+				infos[_id] = v
+			}
+		}
+	}
+	//mongodb bidding
+	mgo_ids := []primitive.ObjectID{}
+	for _, v := range ids {
+		if infos[v] == nil {
+			_id, _ := primitive.ObjectIDFromHex(v)
+			mgo_ids = append(mgo_ids, _id)
+		}
+	}
+	if len(mgo_ids) > 0 {
+		list, ok := Mgo_bidding.Find(bidding, map[string]interface{}{"_id": map[string]interface{}{"$in": mgo_ids}}, nil, mongodb_fields, false, -1, -1)
+		if ok && *list != nil {
+			for _, v := range *list {
+				_id := mg.BsonIdToSId(v["_id"])
+				v["_id"] = _id
+				infos[_id] = v
+			}
+		}
+	}
+	//mongodb bidding_back
+	mgo_back_ids := []primitive.ObjectID{}
+	for _, v := range mgo_ids {
+		if infos[mg.BsonIdToSId(v)] == nil {
+			mgo_back_ids = append(mgo_back_ids, v)
+		}
+	}
+	if len(mgo_back_ids) > 0 {
+		list, ok := Mgo_bidding.Find(bidding_back, map[string]interface{}{"_id": map[string]interface{}{"$in": mgo_back_ids}}, nil, mongodb_fields, false, -1, -1)
+		if ok && *list != nil {
+			for _, v := range *list {
+				_id := mg.BsonIdToSId(v["_id"])
+				v["_id"] = _id
+				infos[_id] = v
+			}
+		}
+	}
+	//
+	for k, v := range pushCas {
+		info := infos[v.InfoId]
+		if info == nil {
+			info = map[string]interface{}{}
+		}
+		array[k] = s.InfoFormat(v, &info)
+	}
+	return array
+}
+
+//保存最近7天的数据到历史记录
+func (s *subscribePush) MakeHistoryDatas(MQFW mg.MongodbSim, PushMysql *mysql.Mysql, userId string, PushView func(userid, allquery, field string, pageNum, pageSize int) (keys []interface{}, list *[]map[string]interface{})) (bool, []*SubPushList) {
+	log.Println("匹配最近7天数据", userId)
+	field := `"_id","title","publishtime","toptype","subtype","type","area","city","s_subscopeclass","buyerclass","budget","bidamount","filetext","spidercode","site"`
+	allquery := `{"range":{"publishtime":{"gt":%s}}}`
+	allquery = fmt.Sprintf(allquery, fmt.Sprint(time.Now().AddDate(0, 0, -7).Unix()))
+	//allquery = ``
+	keys, list := PushView(userId, allquery, field, 1, 50)
+	if list == nil || len(*list) == 0 {
+		return true, nil
+	}
+	matchkeys := []string{}
+	for _, v := range keys {
+		matchkeys = append(matchkeys, strings.Join(ObjArrToStringArr(v.([]interface{})), "+"))
+	}
+	publishTitle := map[string]bool{}
+	now := time.Now().Unix()
+	var array []*SubPushList
+	for i := len(*list) - 1; i >= 0; i-- {
+		v := (*list)[i]
+		var myInsert = make(map[string]interface{})
+		title := strings.Replace(v["title"].(string), "\n", "", -1)
+		area_check := ObjToString(v["area"])
+		if area_check == "A" {
+			area_check = "全国"
+		}
+		if publishTitle[area_check+title] {
+			continue
+		} else {
+			publishTitle[area_check+title] = true
+		}
+		myInsert["userid"] = userId
+		_id := ObjToString(v["_id"])
+		myInsert["infoid"] = _id
+		redisKey := fmt.Sprintf("pushinfo_%s_%s", userId, _id)
+		if isExists, _ := redis.Exists("pushcache_2_a", redisKey); isExists {
+			continue
+		}
+		myInsert["date"] = now
+		myInsert["matchkeys"] = strings.Join(matchkeys, " ")
+		if area_check != "" {
+			if area_check == "全国" {
+				myInsert["area"] = 0
+			} else if area_mapping, ok := PushMapping.Area[area_check]; ok {
+				myInsert["area"] = area_mapping
+			}
+		}
+		if city_check := ObjToString(v["city"]); city_check != "" {
+			if city_mapping, ok := PushMapping.City[city_check]; ok {
+				myInsert["city"] = city_mapping
+			}
+		}
+		if subtype := ObjToString(v["subtype"]); subtype != "" {
+			if subtype_mapping, ok := PushMapping.Subtype[subtype]; ok {
+				myInsert["subtype"] = subtype_mapping
+			}
+		}
+		if toptype := ObjToString(v["toptype"]); toptype != "" {
+			if toptype_mapping, ok := PushMapping.Toptype[toptype]; ok {
+				myInsert["toptype"] = toptype_mapping
+			}
+		}
+		if buyerclass := ObjToString(v["buyerclass"]); buyerclass != "" {
+			if buyerclass_mapping, ok := PushMapping.Buyerclass[buyerclass]; ok {
+				myInsert["buyerclass"] = buyerclass_mapping
+			}
+		}
+		//附件 :检索库 只有附件字段,无法识别附件数量 暂定为1;为识别有附件
+		//v["filetext"]filetext := ObjToString(v["filetext"]); filetext != ""
+		if v["filetext"] != nil {
+			myInsert["attachment_count"] = 1
+		}
+		//中标金额
+		if v["bidamount"] != nil {
+			myInsert["bidamount"] = Int64All(v["bidamount"])
+		}
+		//预算
+		if v["budget"] != nil {
+			myInsert["budget"] = Int64All(v["budget"])
+		}
+		if s_subscopeclass := ObjToString(v["s_subscopeclass"]); s_subscopeclass != "" {
+			subscopeclass := []string{}
+			for _, v := range strings.Split(s_subscopeclass, ",") {
+				if subscopeclass_mapping, ok := PushMapping.Subscopeclass[v]; ok {
+					subscopeclass = append(subscopeclass, fmt.Sprint(subscopeclass_mapping))
+				}
+			}
+			if len(subscopeclass) > 0 {
+				myInsert["subscopeclass"] = strings.Join(subscopeclass, ",")
+			}
+		}
+		id := PushMysql.Insert("pushsubscribe", myInsert)
+		if id > 0 {
+			redis.Put("pushcache_2_a", redisKey, 1, 86400)
+		} else {
+			continue
+		}
+		array = append(array, s.InfoFormat(&PushCa{
+			InfoId:     _id,
+			Date:       now,
+			Index:      id,
+			Keys:       matchkeys,
+			FileExists: v["filetext"] != nil,
+		}, &v))
+	}
+	var resultList []*SubPushList
+	for i := len(array) - 1; i >= 0; i-- {
+		resultList = append(resultList, array[i])
+	}
+	return true, resultList
+}
+
+//获取用户信息
+func (s *subscribePush) UserInfo(MQFW mg.MongodbSim, userId string) (*map[string]interface{}, int64) {
+	user, ok := MQFW.FindById("user", userId, `{"s_m_openid":1,"a_m_openid":1,"s_phone":1,"a_mergeorder":1,"o_jy":1,"l_firstpushtime":1,"i_vip_status":1,"l_vip_endtime":1,"o_vipjy":1,"i_member_status":1,"o_member_jy":1}`)
+	if !ok || user == nil {
+		return nil, 0
+	}
+	return user, Int64All((*user)["l_firstpushtime"])
+}
+func (s *subscribePush) Visit(PushMysql *mysql.Mysql, userId string, id int) {
+	if userId == "" {
+		return
+	}
+	PushMysql.UpdateOrDeleteBySql(fmt.Sprintf("update %s set isvisit=1 where userid=? and id=?", aboutDbMsg[s.ModuleFlag].MysqlTable), userId, id)
+	todaySubPush, err := s.GetTodayCache(userId)
+	if err == nil && todaySubPush != nil {
+		for _, v := range todaySubPush.Datas {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		s.PutTodayCache(userId, todaySubPush)
+	}
+	//
+	allSubPush, err := s.GetAllCache(userId)
+	if err == nil && allSubPush != nil {
+		for _, v := range allSubPush.Datas {
+			if v.Ca_index == Int64All(id) {
+				v.Ca_isvisit = 1
+				break
+			}
+		}
+		s.PutAllCache(userId, allSubPush)
+	}
+}
+
+//查看全部列表缓存
+func (s *subscribePush) PutAllCache(userId string, datas *SubPush) {
+	redis.Put("pushcache_2_a", s.allKey(userId), datas, oneDay)
+}
+
+func (s *subscribePush) GetAllCache(userId string) (*SubPush, error) {
+	return s.GetCache("pushcache_2_a", s.allKey(userId))
+}
+
+func (s *subscribePush) GetCache(code, key string) (*SubPush, error) {
+	pc_a, err := redis.GetNewBytes(code, key)
+	if err != nil {
+		return nil, err
+	}
+	if pc_a == nil {
+		return nil, nil
+	}
+	var p *SubPush
+	if err := json.Unmarshal(*pc_a, &p); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
+
+//是否收藏
+func (s *subscribePush) MakeCollection(userId string, m *mysql.Mysql, list []*SubPushList, fileSignBool bool) {
+	if list == nil || len(list) == 0 {
+		return
+	}
+	param := []interface{}{userId}
+	wh := []string{}
+	for _, v := range list {
+		array := DecodeArticleId2ByCheck(v.Id)
+		if len(array) == 1 && array[0] != "" {
+			param = append(param, array[0])
+			wh = append(wh, "?")
+		}
+	}
+	if len(wh) > 0 {
+		result := m.SelectBySql(`select bid from bdcollection where userid=? and bid in (`+strings.Join(wh, ",")+`)`, param...)
+		bid_map := map[string]bool{}
+		if result != nil {
+			for _, v := range *result {
+				bid_map[EncodeArticleId2ByCheck(ObjToString(v["bid"]))] = true
+			}
+		}
+		for _, v := range list {
+			if !fileSignBool {
+				v.Ca_fileExists = false
+			}
+			if bid_map[v.Id] {
+				v.Collection = 1
+			}
+		}
+	}
+}
+
+//仅移动端首页使用,历史推送7天信息
+func (s *subscribePush) sevenDayKey(userId string) string {
+	return fmt.Sprintf("7day_subpush_%s", userId)
+}
+
+func (s *subscribePush) PutSevenDayCache(userId string, datas []*SubPushList) {
+	redis.Put("pushcache_2_a", s.sevenDayKey(userId), SubPush{Datas: datas}, 7*24*60*60)
+}
+
+//从pushcache_2_a中取
+func (s *subscribePush) GetSevenDayCache(userId string) ([]*SubPushList, error) {
+	allPush, _ := s.GetCache("pushcache_2_a", s.sevenDayKey(userId))
+	if allPush != nil && allPush.Datas != nil && len(allPush.Datas) > 0 {
+		return allPush.Datas, nil
+	}
+	return nil, nil
+}
+
+//历史推送记录中单条信息格式化
+func InfoFormats(info map[string]interface{}, tmp map[string]interface{}) map[string]interface{} {
+	area := ObjToString(info["area"])
+	if area == "A" {
+		area = "全国"
+	}
+	industry := ObjToString(info["s_subscopeclass"])
+	scs := strings.Split(industry, ",")
+	if len(scs) > 0 {
+		industry = scs[0]
+		if industry != "" {
+			iss := strings.Split(industry, "_")
+			if len(iss) > 0 {
+				industry = iss[0]
+			}
+		}
+	}
+	infotype := ObjToString(info["subtype"])
+	if infotype == "" {
+		infotype = ObjToString(info["toptype"])
+	}
+	if infotype == "" {
+		infotype = ObjToString(info["type"])
+		if infotype == "tender" {
+			infotype = "招标"
+		} else if infotype == "bid" {
+			infotype = "中标"
+		}
+	}
+	info["type"] = infotype
+	return info
+}
+
+//保存最近7天的数据到历史记录--新
+/*已选条件--关键词*/
+type ViewKeyWord struct {
+	Keyword  []string `json:"key"`       //关键词
+	Appended []string `json:"appendkey"` //附加词
+	Exclude  []string `json:"notkey"`    //排除词
+	MatchWay int      `json:"matchway"`  //匹配模式
+}
+
+func (s *subscribePush) MakeHistoryDatasNew(PushMysql *mysql.Mysql, userId string, entId int, DefaultPushList func(userId, moduleFlag string, entId int) (keyword []byte, list *[]map[string]interface{}, deptId int)) (bool, []*SubPushList, int) {
+	log.Println(s.ModuleFlag, "匹配最近7天数据", userId, entId)
+	//
+	t1 := time.Now()
+	kw, list, deptId := DefaultPushList(userId, s.ModuleFlag, entId)
+	if list == nil || len(*list) == 0 {
+		return false, nil, 0
+	}
+	var isNext = false
+	log.Println("es查询耗时:", time.Since(t1))
+	var resultList []*SubPushList
+	if list != nil && len(*list) > 0 {
+		keyword := []ViewKeyWord{}
+		if err := json.Unmarshal(kw, &keyword); err != nil {
+			return false, nil, 0
+		}
+		//超过50条先处理50条 返回前50条
+		listOne := *list
+		if len(*list) > pageSize {
+			listOne = (*list)[:pageSize]
+		}
+		resultList = s.listManager(PushMysql, listOne, keyword, (len(listOne)+pageSize)/pageSize, entId, deptId, userId)
+		if len(*list) > pageSize {
+			isNext = true
+			listOther := (*list)[pageSize:]
+			go s.listManager(PushMysql, listOther, keyword, (len(listOther)+pageSize)/pageSize, entId, deptId, userId)
+		}
+	}
+	log.Println("第一批推送信息处理耗时:", time.Since(t1).Seconds(), len(resultList))
+	return isNext, resultList, len(*list)
+}
+
+//
+func (s *subscribePush) listManager(PushMysql *mysql.Mysql, list []map[string]interface{}, keyword []ViewKeyWord, ccount, entId, deptId int, userId string) (resultList []*SubPushList) {
+	t2 := time.Now()
+	now := time.Now()
+	var (
+		wg = &sync.WaitGroup{}
+		wc = make(chan bool, ccount)
+	)
+	for _, v := range list {
+		wg.Add(1)
+		wc <- true
+		go func(v map[string]interface{}) {
+			defer func() {
+				wg.Done()
+				<-wc
+			}()
+			redisKey := fmt.Sprintf("pushinfo_%s_%s", userId, ObjToString(v["_id"]))
+			title := strings.Replace(ObjToString(v["title"]), "\n", "", -1)
+			var myInsert = make(map[string]interface{})
+			myInsert["userid"] = userId
+			myInsert["infoid"] = ObjToString(v["_id"])
+			myInsert["date"] = now.Unix()
+			matchkeys := getKeys(title, keyword)
+			myInsert["matchkeys"] = strings.Join(matchkeys, " ")
+			if v["area"] != nil {
+				myInsert["area"] = If(ObjToString(v["area"]) == "A", 0, If(PushMapping.Area[ObjToString(v["area"])] > 0, PushMapping.Area[ObjToString(v["area"])], 0).(int)).(int)
+			}
+			if v["city"] != nil {
+				myInsert["city"] = If(PushMapping.City[ObjToString(v["city"])] > 0, PushMapping.City[ObjToString(v["city"])], 0).(int)
+			}
+			if v["subtype"] != nil {
+				myInsert["subtype"] = If(PushMapping.Subtype[ObjToString(v["subtype"])] > 0, PushMapping.Subtype[ObjToString(v["subtype"])], 0).(int)
+			}
+			if v["toptype"] != nil {
+				myInsert["toptype"] = If(PushMapping.Toptype[ObjToString(v["toptype"])] > 0, PushMapping.Toptype[ObjToString(v["toptype"])], 0).(int)
+			}
+			if v["buyerclass"] != nil {
+				myInsert["buyerclass"] = If(PushMapping.Buyerclass[ObjToString(v["buyerclass"])] > 0, PushMapping.Buyerclass[ObjToString(v["buyerclass"])], 0).(int)
+			}
+			if v["isValidFile"] != nil {
+				isValidFile, _ := v["isValidFile"].(bool)
+				myInsert["attachment_count"] = If(isValidFile, 1, 0).(int) //附件 :检索库 只有附件字段,无法识别附件数量 暂定为1;为识别有附件
+			}
+			if v["bidamount"] != nil {
+				myInsert["bidamount"] = Int64All(v["bidamount"])
+			}
+			if v["budget"] != nil {
+				myInsert["budget"] = Int64All(v["budget"])
+			}
+			if s_subscopeclass := ObjToString(v["s_subscopeclass"]); s_subscopeclass != "" {
+				subscopeclass := []string{}
+				for _, v := range strings.Split(s_subscopeclass, ",") {
+					if subscopeclass_mapping, ok := PushMapping.Subscopeclass[v]; ok {
+						subscopeclass = append(subscopeclass, fmt.Sprint(subscopeclass_mapping))
+					}
+				}
+				if len(subscopeclass) > 0 {
+					myInsert["subscopeclass"] = strings.Join(subscopeclass, ",")
+				}
+			}
+			//商机管理用户
+			if s.ModuleFlag == "s" {
+				myInsert["userid"] = userId
+				myInsert["entid"] = entId
+				myInsert["deptid"] = deptId
+			}
+			//保存推送表
+			id := PushMysql.Insert(aboutDbMsg[s.ModuleFlag].MysqlTable, myInsert)
+			if id > 0 {
+				redis.Put("pushcache_2_a", redisKey, 1, 86400)
+				resultList = append(resultList, s.InfoFormat(&PushCa{
+					InfoId:     ObjToString(v["_id"]),
+					Date:       now.Unix(),
+					Index:      id,
+					Keys:       matchkeys,
+					FileExists: v["filetext"] != nil,
+				}, &v))
+			}
+		}(v)
+	}
+	wg.Wait()
+	log.Println("数据处理耗时:", time.Since(t2))
+	return
+}
+
+//获取匹配得关键词
+func getKeys(title string, keywords []ViewKeyWord) (str []string) {
+	if len(keywords) > 0 {
+	L:
+		for _, v := range keywords {
+			for _, vk := range v.Keyword {
+				if strings.Contains(title, vk) {
+					str = append(str, v.Keyword...)
+					break L
+				}
+			}
+			for _, va := range v.Appended {
+				if strings.Contains(title, va) {
+					str = append(str, v.Appended...)
+					break L
+				}
+			}
+		}
+	}
+	return
+}

+ 241 - 0
common/src/qfw/util/jy/subvipPortrait.go

@@ -0,0 +1,241 @@
+package jy
+
+import (
+	"fmt"
+	"log"
+	qutil "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+	"time"
+)
+
+//超级订阅画像管理
+//1.剩余次数,及是否浏览过
+//2.次数扣除
+//3.记录查询
+
+//SubVipPortrait 超级订阅画像浏览次数
+type SubVipPortrait struct {
+	*BigVipBaseMsg
+	Mysql            *mysql.Mysql
+	SearchValue      string //查询内容 企业名称or 企业id
+	IsWinnerPortrait bool   //采购单位画像 or 企业画像
+}
+
+const (
+	PortraitRecordTable       = "portrait_record"
+	PortraitWinnerRecordTimes = "subVip_WinnerPortraitRecordTimes_%s_%s"
+	PortraitBuyerRecordTimes  = "subVip_BuyerPortraitRecordTimes_%s_%s"
+	UsePortraitWinnerVisited  = "subVip_WinnerPortraitVisited_%s_%s_%s"
+	UsePortraitBuyerVisited   = "subVip_BuyerPortraitVisited_%s_%s_%s"
+	OneAreaTimes_Winner       = 50
+	OneAreaTimes_Buyer        = 5
+	CacheDateForMat           = "200601"
+	buyerPackName             = "采购单位画像包"
+)
+
+//NewSubVipPortrait 获取超级订阅画像浏览次数结构体
+func (this *BigVipBaseMsg) NewSubVipPortrait(mysql *mysql.Mysql, searchValue string, isWinnerPortrait bool) *SubVipPortrait {
+	return &SubVipPortrait{
+		this,
+		mysql, searchValue, isWinnerPortrait,
+	}
+}
+
+//getVipSurplus 获取超级订阅套餐总次数
+func (this *SubVipPortrait) getVipComboTimes() int {
+	var fullCount int
+	num := qutil.If(this.IsWinnerPortrait, OneAreaTimes_Winner, OneAreaTimes_Buyer).(int)
+	if this.Vip_BuySet.AreaCount == -1 {
+		fullCount = 16 * num //全国全国行业
+	} else {
+		fullCount = this.Vip_BuySet.AreaCount * num
+	}
+	return fullCount
+}
+
+func (this *SubVipPortrait) getUsageKey() string {
+	queryDate := time.Now()
+	if this.IsWinnerPortrait {
+		return fmt.Sprintf(PortraitWinnerRecordTimes, this.Uid, queryDate.Format(CacheDateForMat))
+	}
+	return fmt.Sprintf(PortraitBuyerRecordTimes, this.Uid, queryDate.Format(CacheDateForMat))
+}
+
+//getVipSurplus 获取超级订阅套餐使用次数
+func (this *SubVipPortrait) getUsageValue() (usageValue int) {
+	usageKey := this.getUsageKey()
+	usageValue = redis.GetInt(PowerCacheDb, usageKey)
+	if usageValue == 0 && this.Mysql != nil { //当缓存中无值时查询mysql中使用量
+		queryDate := time.Now()
+		usageValue = int(this.Mysql.CountBySql(fmt.Sprintf("select count(id) from %s where user_id =? and p_type =? and year(create_time)=? and month(create_time)=?",
+			PortraitRecordTable), this.Uid, qutil.If(this.IsWinnerPortrait, 0, 1).(int), int(queryDate.Year()), int(queryDate.Month())))
+		//仅当redis没有值时存储,防止覆盖
+		if ex, err := redis.Exists(PowerCacheDb, usageKey); !ex && err == nil {
+			redis.Put(PowerCacheDb, usageKey, usageValue, 24*60*60*31)
+			log.Printf("%s获取使用次数缓存%d\n", this.GetUseId(), usageValue)
+		}
+	}
+	return
+}
+
+//surplus 使用超级订阅赠送流量包(同一个企业只会进入一次扣除)
+func (this *SubVipPortrait) surplus(hasPack bool, vipSurplus int) error {
+	surErr, usePack := func() (err error, usePack bool) {
+		payOk := false
+		//优先套餐内扣除
+		if vipSurplus > 0 {
+			usageKey := this.getUsageKey()
+			if nowValue := redis.Incr(PowerCacheDb, usageKey); int(nowValue) > this.getVipComboTimes() {
+				value := redis.Decrby(PowerCacheDb, usageKey, 1)
+				log.Printf("%s %v超额扣除还原 %d->%d\n", this.GetUseId(), this.IsWinnerPortrait, nowValue, value)
+				err = fmt.Errorf("套餐内扣除异常")
+			} else {
+				payOk = true
+			}
+		}
+		if !payOk && hasPack { //流量包扣除
+			if err = this.consumeBuyerPortrait(this.SearchValue); err == nil {
+				usePack = true
+			}
+		}
+		if !(payOk || usePack) {
+			err = fmt.Errorf("扣除异常")
+		}
+		return
+	}()
+	if surErr == nil {
+		// 0 企业画像套餐内扣除  10 企业画像流量包扣除(暂无)
+		// 1 采购单位套餐内扣除  11 采购单位画像包扣除
+		pType := qutil.If(this.IsWinnerPortrait, 0, 1).(int)
+		if usePack {
+			pType += 10
+		}
+		this.Mysql.Insert(PortraitRecordTable, map[string]interface{}{
+			"user_id":     this.Uid,
+			"ent_id":      this.SearchValue,
+			"p_type":      pType,
+			"create_time": time.Now().Format("2006-01-02 15:04:05"),
+		})
+	}
+	log.Printf("用户%s 画像%s 画像包:%v 扣除结果%v  \n", this.GetUseId(), this.SearchValue, usePack, surErr)
+	return surErr
+}
+
+//isVisitEnt 查询当月是否查看过次画像
+func (this *SubVipPortrait) isVisitEnt() bool {
+	if this.SearchValue == "" || this.Mysql == nil {
+		return false
+	}
+	//本月有浏览记录
+	usePortraitVisited := this.getPortraitVisitedRedisKey()
+	if exists, err := redis.Exists(PowerCacheDb, usePortraitVisited); err == nil && exists {
+		return true
+	}
+	now := time.Now()
+
+	if this.Mysql.CountBySql(fmt.Sprintf("select count(id) from %s where user_id =? and ent_id =? and year(create_time)=? and month(create_time)=?",
+		PortraitRecordTable), this.Uid, this.SearchValue, now.Year(), int(now.Month())) > 0 {
+		redis.Incr(PowerCacheDb, usePortraitVisited)
+		addTryCacheKeyTimeOut(usePortraitVisited, 60*60*5)
+		return true
+	}
+	return false
+}
+
+//GetPortraitVisitedRedisKey 画像是否浏览过key
+func (this *SubVipPortrait) getPortraitVisitedRedisKey() string {
+	queryDate := time.Now()
+	if this.IsWinnerPortrait {
+		return fmt.Sprintf(UsePortraitWinnerVisited, this.Uid, queryDate.Format(CacheDateForMat), this.SearchValue)
+	}
+	return fmt.Sprintf(UsePortraitBuyerVisited, this.Uid, queryDate.Format(CacheDateForMat), this.SearchValue)
+}
+
+//addTryCacheKeyTimeOut 多次尝试redisKey增加过期
+func addTryCacheKeyTimeOut(key string, expire int) {
+	for i := 0; i < 3; i++ {
+		if redis.SetExpire(PowerCacheDb, key, expire) == nil {
+			return
+		}
+	}
+}
+
+//AccountStatus 剩余次数,及是否浏览过
+func (this *SubVipPortrait) AccountStatus() (vipTotal, vipSurplus, packSurplus int, visited bool) {
+	//超级订阅套餐余额
+	vipTotal = this.getVipComboTimes()
+	vipSurplus = this.getVipComboTimes() - this.getUsageValue()
+	if vipSurplus <= 0 {
+		vipSurplus = 0
+	}
+
+	//采购单位流量包查询
+	if !this.IsWinnerPortrait {
+		var err error
+		if packSurplus, err = this.getBuyerPortraitCount(); err != nil {
+			log.Printf("%s 查询采购单位流量包异常 %v\n", this.GetUseId(), err)
+		}
+	}
+
+	//是否查看过此画像
+	if this.Mysql != nil && this.SearchValue != "" {
+		visited = this.isVisitEnt()
+	}
+	return
+}
+
+//SubVipPortraitTimesCheck 次数扣除
+func (this *SubVipPortrait) SubVipPortraitTimesCheck() error {
+	if this.VipStatus <= 0 || this.SearchValue == "" {
+		return fmt.Errorf("非法请求")
+	}
+
+	_, vipSurplus, packSurplus, visited := this.AccountStatus()
+	log.Printf("用户%s 画像%s 套餐余额%d 流量包余额%d  是否浏览过%v \n", this.GetUseId(), this.SearchValue, vipSurplus, packSurplus, visited)
+	if visited { //浏览过直接返回
+		return nil
+	}
+
+	//校验本月次数是否使用完
+	if (this.IsWinnerPortrait && vipSurplus <= 0) || (!this.IsWinnerPortrait && vipSurplus+packSurplus <= 0) {
+		return fmt.Errorf("本月次数已使用完")
+	}
+
+	//并发控制
+	usePortraitVisited := this.getPortraitVisitedRedisKey()
+	if redis.Incr(PowerCacheDb, usePortraitVisited) == 1 {
+		if err := this.surplus(!this.IsWinnerPortrait && packSurplus > 0, vipSurplus); err != nil {
+			redis.Del(PowerCacheDb, usePortraitVisited)
+			return err
+		}
+		addTryCacheKeyTimeOut(usePortraitVisited, 60*60*5)
+	}
+	return nil
+}
+
+//GetPortraitRecord 查询浏览记录
+// 0 企业画像套餐内扣除  10 企业画像流量包扣除(暂无)
+// 1 采购单位套餐内扣除  11 采购单位画像包扣除
+func (this *SubVipPortrait) GetPortraitRecord(queryDate time.Time, pageNum, pageSize int, needCount bool) (ids []string, total int) {
+	total = -1
+	pType := qutil.If(this.IsWinnerPortrait, "0,10", "1,11").(string)
+	if needCount { //查询数量
+		total = int(this.Mysql.CountBySql(fmt.Sprintf("select count(id) from %s where user_id =? and p_type in ( %s ) and year(create_time)=? and month(create_time)=?",
+			PortraitRecordTable, pType), this.Uid, queryDate.Year(), int(queryDate.Month())))
+		if total <= 0 {
+			return
+		}
+	}
+	res := this.Mysql.Query(fmt.Sprintf("select ent_id from %s where user_id =? and p_type in ( %s ) and year(create_time)=? and month(create_time)=? order by create_time desc limit ?,?",
+		PortraitRecordTable, pType), this.Uid, queryDate.Year(), int(queryDate.Month()), pageNum*pageSize, pageSize)
+	if res == nil {
+		return
+	}
+	for _, d := range *res {
+		if id, ok := d["ent_id"].(string); ok && id != "" {
+			ids = append(ids, id)
+		}
+	}
+	return
+}

+ 56 - 0
common/src/qfw/util/jy/subvipPortrait_pack.go

@@ -0,0 +1,56 @@
+package jy
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/url"
+
+	qutil "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/middleGround"
+)
+
+//画像包-采购单位
+
+//GetBuyerPortraitCount 采购单位画像流量包查询
+func (this *SubVipPortrait) getBuyerPortraitCount() (int, error) {
+	param := url.Values{
+		"accountId":    []string{this.GetUseId()},
+		"resourceType": []string{buyerPackName},
+	}
+	res, err := middleGround.CommonPost(middleGround.JyApiConfig.ApiList.FindBalance, param)
+	if err != nil {
+		return 0, err
+	}
+	if list, ok := res["data"].([]interface{}); ok && len(list) > 0 {
+		for _, pack := range list {
+			if pMap, ok1 := pack.(map[string]interface{}); ok1 && qutil.ObjToString(pMap["name"]) == buyerPackName {
+				return qutil.IntAll(pMap["number"]), nil
+			}
+		}
+	}
+	return 0, nil
+}
+
+// ConsumeBuyerPortrait 消耗采购单位流量包
+func (this *SubVipPortrait) consumeBuyerPortrait(searchValue string) error {
+	remarks := map[string]interface{}{
+		"searchValue": searchValue,
+	}
+	remarkBytes, _ := json.Marshal(remarks)
+	resMap, err := middleGround.CommonPost(middleGround.JyApiConfig.ApiList.UseBalance, url.Values{
+		"accountId":        []string{this.GetUseId()},      //账户标识
+		"name":             []string{buyerPackName},        //资源名称
+		"resourceType":     []string{buyerPackName},        //资源类型
+		"number":           []string{fmt.Sprintf("%d", 1)}, //扣除数量
+		"userId":           []string{this.GetUseId()},      //用户标识
+		"remarks":          []string{string(remarkBytes)},  //备注 查询名称、使用时间
+		"duplicateRemoval": []string{fmt.Sprintf("%d", 0)}, //是否去重0不去1去重
+	})
+	if err != nil {
+		return err
+	}
+	if qutil.IntAll(resMap["code"]) != 1 {
+		return fmt.Errorf("采购单位画像包扣除失败")
+	}
+	return nil
+}

+ 101 - 0
common/src/qfw/util/jy/switchService.go

@@ -0,0 +1,101 @@
+package jy
+
+import (
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+)
+
+var SwitchService = &switchService{
+	SessionKey: "vSwitch",
+	Member:     "m",
+	Vip:        "v",
+	Free:       "",
+	Entniche:   "s",
+}
+
+//切换超级订阅、大会员服务
+type switchService struct {
+	SessionKey string
+	Member     string
+	Vip        string
+	Free       string
+	Entniche   string
+}
+
+/* 获取当前所选的服务类型
+ * @param session
+ * @param mongodb
+ * @return string 服务类型
+ */
+func (s *switchService) Get(session *httpsession.Session, m MongodbSim) string {
+	userId, _ := session.Get("userId").(string)
+	v, _ := session.Get(s.SessionKey).(string)
+	u, ok := m.FindById("user", userId, `{"i_member_status":1,"i_vip_status":1}`)
+	if ok && u != nil {
+		if i_member_status := util.IntAll((*u)["i_member_status"]); v == s.Member && i_member_status > 0 {
+			return s.Member
+		} else if i_vip_status := util.IntAll((*u)["i_vip_status"]); v == s.Vip && i_vip_status > 0 {
+			return s.Vip
+		} else if i_member_status > 0 {
+			session.Set(s.SessionKey, s.Member)
+			return s.Member
+		} else if i_vip_status > 0 {
+			session.Set(s.SessionKey, s.Vip)
+			return s.Vip
+		}
+	}
+	session.Del(s.SessionKey)
+	return s.Free
+}
+
+func (s *switchService) GetEntniche(session *httpsession.Session, m MongodbSim, sql *mysql.Mysql) string {
+	sessMap := session.GetMultiple("userId", s.SessionKey, "entId", "entUserId")
+	userId, _ := sessMap["userId"].(string)
+	v, _ := sessMap[s.SessionKey].(string)
+	u, ok := m.FindById("user", userId, `{"i_member_status":1,"i_vip_status":1}`)
+	entniche := false
+	if entId, entUserId := util.IntAll(sessMap["entId"]), util.IntAll(sessMap["entUserId"]); entId > 0 && entUserId > 0 {
+		//当前企业是否购买商机管理
+		if sql.CountBySql(`SELECT count(1) as count FROM entniche_info a inner join entniche_user b on (a.id=? and a.status=1 and ifnull(a.power_source,0)=0 and b.id=? and b.power=1)`, entId, entUserId) > 0 {
+			entniche = true
+		}
+	}
+	if ok && u != nil {
+		if i_member_status := util.IntAll((*u)["i_member_status"]); v == s.Member && i_member_status > 0 {
+			return s.Member
+		} else if v == s.Entniche && entniche {
+			return s.Entniche
+		} else if i_vip_status := util.IntAll((*u)["i_vip_status"]); v == s.Vip && i_vip_status > 0 {
+			return s.Vip
+		} else if i_member_status > 0 {
+			session.Set(s.SessionKey, s.Member)
+			return s.Member
+		} else if entniche {
+			session.Set(s.SessionKey, s.Entniche)
+			return s.Entniche
+		} else if i_vip_status > 0 {
+			session.Set(s.SessionKey, s.Vip)
+			return s.Vip
+		}
+	}
+	session.Del(s.SessionKey)
+	return s.Free
+}
+
+//所选服务是否是大会员
+func (s *switchService) IsMember(session *httpsession.Session, m MongodbSim, sql *mysql.Mysql) bool {
+	return s.GetEntniche(session, m, sql) == s.Member
+}
+
+//所选服务是否是超级订阅
+func (s *switchService) IsVip(session *httpsession.Session, m MongodbSim) bool {
+	return s.Get(session, m) == s.Vip
+}
+
+//所选服务是否是免费
+func (s *switchService) IsFree(session *httpsession.Session, m MongodbSim) bool {
+	return s.Get(session, m) == s.Free
+}

+ 67 - 0
common/src/qfw/util/jy/userInfo.go

@@ -0,0 +1,67 @@
+/**
+@author:王山
+@descript:获取用户昵称
+@date:2022-02-24
+**/
+package jy
+
+import (
+	"fmt"
+	"log"
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/redis"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func GetUserName(redisName, prefix string) (name string) {
+	name = fmt.Sprintf(prefix, getNameMiddle()+strings.ToUpper(util.GetComplexRandom(3, 1, 3)))
+	if ok, err := redis.Exists(redisName, name); ok && err == nil {
+		name = GetUserName(redisName, prefix)
+	} else {
+		if !redis.Put(redisName, name, true, 24*60*60) {
+			log.Println("nickname err")
+		}
+	}
+	return
+}
+
+//年月日
+func getNameMiddle() string {
+	y := strconv.Itoa(time.Now().Year())
+	m := getMonth()
+	d := strconv.Itoa(time.Now().Day())
+	return string([]byte(y)[3:]) + m + d
+}
+
+//月
+func getMonth() string {
+	switch time.Now().Month() {
+	case time.January:
+		return "1"
+	case time.February:
+		return "2"
+	case time.March:
+		return "3"
+	case time.April:
+		return "4"
+	case time.May:
+		return "5"
+	case time.June:
+		return "6"
+	case time.July:
+		return "7"
+	case time.August:
+		return "8"
+	case time.September:
+		return "9"
+	case time.October:
+		return "A"
+	case time.November:
+		return "B"
+	case time.December:
+		return "C"
+	}
+	return "*"
+}

+ 140 - 0
common/src/qfw/util/jy/userMerge.go

@@ -0,0 +1,140 @@
+package jy
+
+import (
+	"fmt"
+	"strings"
+
+	qutil "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+//dev3.7 账号合并
+type UserMerge struct {
+	mysqlDb *mysql.Mysql
+	mg      MongodbSim
+	sess    *httpsession.Session
+}
+
+func CreateUserMerge(mg MongodbSim, msql *mysql.Mysql, sess *httpsession.Session) *UserMerge {
+	return &UserMerge{
+		mysqlDb: msql,
+		mg:      mg,
+		sess:    sess,
+	}
+}
+
+var (
+	MergeEncrypt = SimpleEncrypt{Key: "jy_merge_user"}
+)
+
+//查询合并
+//wId 微信用户id
+//pId 手机用户id
+//当wId和pId都为空时 即不需要合并
+func (this *UserMerge) MergeQuery() (wId, pId, bPhone string, err error) {
+	userId, _ := this.sess.Get("userId").(string)
+	if userId == "" {
+		err = fmt.Errorf("未获取到用户身份")
+		return
+	}
+	tud, _ := this.mg.FindById("user", userId, `{"s_m_phone":1,"s_phone":1,"s_unionid":1,"s_m_openid":1,"s_nickname":1}`)
+	if tud == nil || len(*tud) == 0 {
+		err = fmt.Errorf("查询用户身份异常")
+		return
+	}
+	s_m_phone, s_phone := qutil.ObjToString((*tud)["s_m_phone"]), qutil.ObjToString((*tud)["s_phone"])
+	if s_phone != "" {
+		bPhone = s_phone
+	} else {
+		bPhone = s_m_phone
+	}
+	//查询是否有对应微信需要合并
+	if unionid := qutil.ObjToString((*tud)["s_unionid"]); unionid != "" && len(unionid) > 11 {
+		mud, _ := this.mg.FindOneByField("user", bson.M{"i_appid": 2, "s_unionid": unionid, "_id": bson.M{"$ne": StringTOBsonId(userId)}}, `{"_id":1,"s_nickname":1,"s_phone":1}`)
+		if mud != nil && len(*mud) > 0 {
+			if qutil.ObjToString((*tud)["s_phone"]) == "" {
+				wId = userId
+				pId = BsonIdToSId((*mud)["_id"])
+				bPhone = qutil.ObjToString((*mud)["s_phone"])
+			} else {
+				pId = userId
+				wId = BsonIdToSId((*mud)["_id"])
+			}
+			return
+		}
+	}
+	//查询是否有对应手机号需要合并
+	if s_phone == s_m_phone { //已合并
+		return
+	}
+	if s_m_phone != "" { //当前用户为微信用户;查询对应的手机号用户
+		mud, _ := this.mg.FindOneByField("user", bson.M{"i_appid": 2, "s_phone": s_m_phone, "_id": bson.M{"$ne": StringTOBsonId(userId)}}, `{"_id":1}`)
+		if mud != nil && len(*mud) > 0 {
+			wId = BsonIdToSId((*tud)["_id"])
+			pId = BsonIdToSId((*mud)["_id"])
+			return
+		}
+	} else if s_phone != "" { //当前用户为手机号用户;查询对应的微信用户
+		mud, _ := this.mg.FindOneByField("user", bson.M{"i_appid": 2, "s_m_phone": s_phone, "_id": bson.M{"$ne": StringTOBsonId(userId)}}, `{"_id":1,"s_nickname":1}`)
+		if mud != nil && len(*mud) > 0 {
+			wId = BsonIdToSId((*mud)["_id"])
+			pId = BsonIdToSId((*tud)["_id"])
+			return
+		}
+	}
+	return
+}
+
+//更新session
+func (this *UserMerge) FlushSession(userData *map[string]interface{}, userId string) {
+	if userData == nil {
+		userData, _ = this.mg.FindById("user", userId, `{"_id":1,"i_shareknow":1,"s_m_openid":1,"s_nickname":1,"s_headimage":1,"s_headimageurl":1,"s_phone":1,"s_m_phone":1,"s_jpushid":1,"s_opushid":1,"s_appponetype":1,"s_appversion":1,"base_user_id":1}`)
+	}
+	sessionVal := make(map[string]interface{})
+	sessionVal["user"] = *userData
+	if (*userData)["i_shareknow"] != nil {
+		sessionVal["shareknow"] = (*userData)["i_shareknow"]
+	}
+	if userId == "" {
+		userId = BsonIdToSId((*userData)["_id"])
+	}
+	ClearBigVipUserPower(userId) //大会员状态刷新
+	sessionVal["userId"] = userId
+	nickName, _ := (*userData)["s_nickname"].(string)
+	phone, _ := (*userData)["s_phone"].(string)
+	if nickName == "" {
+		if phone != "" && len(phone) > 3 {
+			nickName = string(phone[0:3]) + "****" + string(phone[len(phone)-4:])
+		}
+	}
+	sessionVal["s_nickname"] = nickName
+	sessionVal["nickname"] = nickName
+	avatar, _ := (*userData)["s_headimageurl"].(string)
+	if avatar == "" {
+		avatar, _ = (*userData)["s_headimage"].(string)
+	}
+	sessionVal["s_avatar"] = strings.Replace(avatar, "http://", "https://", 1)
+	sessionVal["s_m_openid"], _ = (*userData)["s_m_openid"].(string)
+	sessionVal["openid"] = sessionVal["s_m_openid"]
+	//app端
+	sessionVal["s_phone"] = phone
+	if phone == "" {
+		phone, _ = (*userData)["s_m_phone"].(string)
+	}
+	sessionVal["phone"] = phone
+	sessionVal["s_headimageurl"] = strings.Replace(avatar, "http://", "https://", 1)
+	sessionVal["s_jpushid"], _ = (*userData)["s_jpushid"].(string)
+	sessionVal["s_opushid"], _ = (*userData)["s_opushid"].(string)
+	sessionVal["s_appponetype"], _ = (*userData)["s_appponetype"].(string)
+	sessionVal["s_appversion"], _ = (*userData)["s_appversion"].(string)
+	sessionVal["app_name"] = nickName
+	if base_userid := qutil.IntAllDef((*userData)["base_user_id"], 0); base_userid != 0 {
+		sessionVal["base_user_id"] = base_userid
+	}
+	this.sess.SetMultiple(sessionVal)
+}

+ 358 - 0
common/src/qfw/util/jy/userPhoneUtil.go

@@ -0,0 +1,358 @@
+package jy
+
+import (
+	"fmt"
+	"time"
+
+	qutil "app.yhyue.com/moapp/jybase/common"
+	mg "app.yhyue.com/moapp/jybase/mongodb"
+)
+
+type PhoneUtil struct {
+	UserMgoDB mg.MongodbSim
+}
+
+func NewPhoneUtil(db mg.MongodbSim) *PhoneUtil {
+	return &PhoneUtil{db}
+}
+
+func (this *PhoneUtil) Log(userId, operation, value string) {
+	go this.UserMgoDB.Save("user_relation_log", map[string]interface{}{
+		"s_userId":      userId,
+		"s_operation":   operation,
+		"s_updateValue": value,
+		"l_createtime":  time.Now().Unix(),
+	})
+}
+
+// 获取微信关联字段
+func (this *PhoneUtil) getWxRelationMap(userId string) *map[string]interface{} {
+	rData, _ := this.UserMgoDB.FindById("user", userId, `{"s_unionid":1,"s_name":1,"s_nickname":1,"s_headimageurl":1}`)
+	if rData == nil || len(*rData) == 0 {
+		return nil
+	}
+	delete(*rData, "_id")
+	return rData
+}
+
+// 获取手机号
+func (this *PhoneUtil) getUserPhone(userId string) (phone string) {
+	rData, _ := this.UserMgoDB.FindById("user", userId, `{"s_phone":1}`)
+	if rData != nil && len(*rData) > 0 {
+		phone, _ = (*rData)["s_phone"].(string)
+	}
+	return
+}
+
+// 绑定微信
+func (this *PhoneUtil) bindWeixin(phoneUserId string, wxRelationMap *map[string]interface{}, opera string) error {
+	if !this.UserMgoDB.UpdateById("user", phoneUserId, map[string]interface{}{
+		"$set": wxRelationMap,
+	}) {
+		return fmt.Errorf("建立Phone绑定异常")
+	}
+	//存日志
+	this.Log(phoneUserId, fmt.Sprintf("%s_bindWeixin", opera), qutil.ObjToString((*wxRelationMap)["s_unionid"]))
+	return nil
+}
+
+// 解绑微信
+func (this *PhoneUtil) unBindWeixin(phoneUserId, opera string) error {
+	//查询解绑微信unionid
+	logMap := this.getWxRelationMap(phoneUserId)
+	if !this.UserMgoDB.UpdateById("user", phoneUserId, map[string]interface{}{
+		"$unset": map[string]interface{}{"s_name": 1, "s_nickname": 1, "i_sex": 1, "s_country": 1, "s_province": 1, "s_city": 1, "s_headimageurl": 1, "s_m_openid": 1, "a_m_openid": 1, "i_ispush": 1, "s_unionid": 1, "i_applystatus": 1},
+	}) {
+		return fmt.Errorf("解除微信关联异常")
+	}
+	//存日志
+	logUnionid := ""
+	if logMap != nil && len(*logMap) > 0 {
+		logUnionid, _ = (*logMap)["s_unionid"].(string)
+	}
+	this.Log(phoneUserId, fmt.Sprintf("%s_unBindWeixin", opera), logUnionid)
+	return nil
+}
+
+// 绑定手机号
+func (this *PhoneUtil) bindPhone(weixinUserId, phone, email, opera string) error {
+	data := map[string]interface{}{
+		"s_m_phone":       phone,
+		"l_bindphonetime": time.Now().Unix(),
+	}
+	if EmailPattern.MatchString(email) {
+		data["s_email"] = email
+	}
+	if !this.UserMgoDB.UpdateById("user", weixinUserId, map[string]interface{}{
+		"$set": data,
+	}) {
+		return fmt.Errorf("建立微信绑定异常")
+	}
+	//存日志
+	this.Log(weixinUserId, fmt.Sprintf("%s_bindPhone", opera), phone)
+	return nil
+}
+
+// 解除手机号关联
+func (this *PhoneUtil) unBindPhone(weixinUserId, opera string) error {
+	logPhone := this.getUserPhone(weixinUserId)
+	if !this.UserMgoDB.UpdateById("user", weixinUserId, map[string]interface{}{
+		"$unset": map[string]interface{}{"s_m_phone": 0},
+	}) {
+		return fmt.Errorf("解除手机关联异常")
+	}
+	//存日志
+	this.Log(weixinUserId, fmt.Sprintf("%s_unBindPhone", opera), logPhone)
+	return nil
+}
+
+//=============================================
+
+// 建立双向关联关系
+func (this *PhoneUtil) buildRelation(wxUserId, phoneUserId, phone string, wxRelationMap *map[string]interface{}, opera string) error {
+	//建立双向关联关系
+	if wxRelationMap == nil {
+		wxRelationMap = this.getWxRelationMap(wxUserId)
+	}
+	if phone == "" {
+		phone = this.getUserPhone(phoneUserId)
+	}
+	if wxRelationMap == nil || phone == "" {
+		return fmt.Errorf("获取绑定关系异常")
+	}
+	if err := this.bindWeixin(phoneUserId, wxRelationMap, opera); err != nil {
+		return err
+	}
+	if err := this.bindPhone(wxUserId, phone, qutil.InterfaceToStr((*wxRelationMap)["s_email"]), opera); err != nil {
+		return err
+	}
+	return nil
+}
+
+// 解除双向绑定
+func (this *PhoneUtil) RelieveRelation(weixinId, phoneId, opera string) error {
+	if err := this.unBindPhone(weixinId, opera); err != nil {
+		return err
+	}
+	if err := this.unBindWeixin(phoneId, opera); err != nil {
+		return err
+	}
+	return nil
+}
+
+// 查询是否存在双向关联关系
+func (this *PhoneUtil) QueryRelation(phone string) (wId, pId string, err error) {
+	relationUser, _ := this.UserMgoDB.Find("user", map[string]interface{}{
+		"i_appid": 2,
+		"$or": []map[string]interface{}{
+			map[string]interface{}{"s_phone": phone},
+			map[string]interface{}{"s_m_phone": phone},
+		},
+	}, nil, `{"s_phone":1}`, false, -1, -1)
+	if relationUser == nil || len(*relationUser) == 0 {
+		return "", "", fmt.Errorf("未查询到用户信息")
+	}
+	if len(*relationUser) > 2 {
+		return "", "", fmt.Errorf("查询异常")
+	}
+	if len(*relationUser) == 2 {
+		for _, m := range *relationUser {
+			if m["s_phone"] != nil {
+				pId = mg.BsonIdToSId((m)["_id"])
+			} else {
+				wId = mg.BsonIdToSId((m)["_id"])
+			}
+		}
+		if pId == "" || wId == "" {
+			return "", "", fmt.Errorf("获取用户异常")
+		}
+	}
+	return
+}
+
+//=============================================
+
+/*
+微信账号绑定手机号判断是否存在
+param  unionId:当前微信的s_unionid
+
+	phone:要绑定的手机号
+
+return exists:是否存在
+
+	relationPhoneId:需要建立关联关系的手机号用户id
+
+rule 1.不存在手机号,2.存在手机号用户,但此手机号未绑定微信 3.存在手机号用户,或绑定微信和当前账户绑定微信一致
+*/
+func (this *PhoneUtil) BindPhoneIsOccupy(unionId, phone string) (exists bool, relationPhoneId string) {
+	//已经有微信账户绑定过
+	if this.UserMgoDB.Count("user", map[string]interface{}{"i_appid": 2, "s_m_phone": phone, "s_phone": map[string]interface{}{"$exists": 0}}) > 0 {
+		return true, ""
+	}
+	relationUser, _ := this.UserMgoDB.Find("user", map[string]interface{}{
+		"i_appid": 2,
+		"s_phone": phone,
+	}, nil, `{"s_unionid":1}`, false, -1, -1)
+	//不存在手机号用户
+	if relationUser == nil || len(*relationUser) == 0 {
+		return false, ""
+	}
+	for _, userData := range *relationUser {
+		unionidTmp, _ := userData["s_unionid"].(string)
+		//没绑定微信或绑定微信和当前账户绑定微信一致
+		if unionidTmp == "" || unionidTmp == unionId {
+			relationPhoneId = mg.BsonIdToSId(userData["_id"])
+		} else {
+			return true, ""
+		}
+	}
+	//但此手机号未绑定微信
+	return false, relationPhoneId
+}
+
+/*
+绑定手机号
+param wxUserId:绑定手机号的微信
+
+		  relationPhoneId:存在关联的手机号账户
+	      phone:绑定的手机号
+	      wxRelationMap:相关联的微信信息
+*/
+func (this *PhoneUtil) BindPhone(wxUserId, relationPhoneId, phone, email string, wxRelationMap *map[string]interface{}) error {
+	if relationPhoneId == "" {
+		//单独绑定手机号
+		return this.bindPhone(wxUserId, phone, email, "BindPhone")
+	} else {
+		//绑定及建立双向关联
+		return this.buildRelation(wxUserId, relationPhoneId, phone, wxRelationMap, "BindPhone")
+	}
+}
+
+/*
+更改手机号
+param phone要更改的手机号
+
+	needMerge:当前账户是否需要合并
+
+return exists:是否存在
+
+	relationPhoneId:需要建立关联关系的手机号用户id
+
+rule:在合并状态的微信账户,可以更换存在但未绑定微信的手机号;其他情况只能更换新手机号
+*/
+func (this *PhoneUtil) ChangePhoneIsOccupy(phone string, isWxAndNeedMerge bool) (exists bool, relationPhoneId string) {
+	if isWxAndNeedMerge {
+		relationUser, _ := this.UserMgoDB.Find("user", map[string]interface{}{
+			"i_appid": 2,
+			"$or": []map[string]interface{}{
+				map[string]interface{}{ //存在手机号未绑定微信的用户
+					"s_unionid": map[string]interface{}{"$exists": 1},
+					"s_phone":   phone,
+				},
+				map[string]interface{}{ //手机号已绑定
+					"s_m_phone": phone,
+				},
+			},
+		}, nil, `{"s_unionid":1,"s_phone":1}`, false, -1, -1)
+		if relationUser == nil || len(*relationUser) == 0 {
+			return false, ""
+		}
+		for _, userData := range *relationUser {
+			if userData["s_unionid"] == nil && userData["s_phone"] != nil {
+				return false, mg.BsonIdToSId(userData["_id"])
+			}
+		}
+		return true, ""
+	} else {
+		if this.UserMgoDB.Count("user", map[string]interface{}{
+			"i_appid": 2,
+			"$or": []map[string]interface{}{
+				map[string]interface{}{ //手机号用户
+					"s_m_phone": phone,
+				},
+				map[string]interface{}{ //手机号已被绑定的用户
+					"s_phone": phone,
+				},
+			},
+		}) > 0 {
+			return true, ""
+		}
+	}
+	return false, ""
+}
+
+/*
+手机号账号绑定微信判断微信是否存在
+param   phone:当前手机号
+
+	unionId:要绑定的微信s_unionid
+
+return exists:是否存在
+
+	relationWeixinId:需要建立关联关系的微信用户id
+
+rule  存在对应的微信 1.未绑定手机号 2.绑定手机号为当前账户手机号
+*/
+func (this *PhoneUtil) BindWeixinIsOccupy(phone, unionId string) (exists bool, relationWeixinId string) {
+	if wxBindNum := this.UserMgoDB.Count("user", map[string]interface{}{"i_appid": 2, "s_unionid": unionId}); wxBindNum > 0 {
+		relationUser, _ := this.UserMgoDB.FindOneByField("user", map[string]interface{}{
+			"i_appid":   2,
+			"s_unionid": unionId,
+			"s_phone":   map[string]interface{}{"$exists": 0},
+			"$or": []map[string]interface{}{
+				map[string]interface{}{
+					"s_m_phone": map[string]interface{}{"$exists": 0},
+				},
+				map[string]interface{}{
+					"s_m_phone": phone,
+				},
+			},
+		}, `{"_id":1}`)
+		if relationUser != nil && len(*relationUser) > 0 {
+			if relation := mg.BsonIdToSId((*relationUser)["_id"]); relation != "" {
+				return false, mg.BsonIdToSId((*relationUser)["_id"])
+			}
+		}
+		return true, ""
+	}
+	//全新微信账户
+	return false, ""
+}
+
+/*
+手机号账号绑定微信
+param   phoneUserId:绑定微信的手机账户
+
+	        relationWeixinId:存在相关联的微信信息
+	        phone:当前手机账号手机号
+			wxRelationMap:相关联的微信信息
+*/
+func (this *PhoneUtil) BindWeixin(phoneUserId, relationWeixinId, phone string, wxRelationMap *map[string]interface{}, opear ...string) error {
+	logFlag := "BindWeixin"
+	if len(opear) == 1 {
+		logFlag = opear[0]
+	}
+	if relationWeixinId == "" {
+		return this.bindWeixin(phoneUserId, wxRelationMap, logFlag)
+	} else {
+		return this.buildRelation(relationWeixinId, phoneUserId, phone, wxRelationMap, logFlag)
+	}
+}
+
+/*
+手机号解绑微信
+param   phoneUserId:绑定微信的手机账户
+
+	relationWeixinId:存在相关联的微信信息
+*/
+func (this *PhoneUtil) UnBindWeixin(phoneUserId, relationWeixinId string, opear ...string) error {
+	logFlag := "UnBindWeixin"
+	if len(opear) == 1 {
+		logFlag = opear[0]
+	}
+	if relationWeixinId == "" {
+		return this.unBindWeixin(phoneUserId, logFlag)
+	} else {
+		return this.RelieveRelation(relationWeixinId, phoneUserId, logFlag)
+	}
+}

+ 73 - 0
common/src/qfw/util/jylog/jylog.go

@@ -0,0 +1,73 @@
+package jylog
+
+/**
+日志文件自动切换,默认保留15天内日志
+**/
+
+import (
+	"log"
+	"os"
+	"path/filepath"
+	"regexp"
+	"time"
+
+	"app.yhyue.com/moapp/jybase/go-xweb/xweb"
+	"github.com/robfig/cron"
+)
+
+//日志格式
+var fileReg = regexp.MustCompile("^(\\d{4}_[0-9_]{14})\\.log$")
+
+//当前日志文件句柄
+var LogFile *os.File
+
+//时间格式
+var FMT = "2006_01_02_15_04_05"
+
+//日志目录
+var LogPath = "./jylog"
+
+func init() {
+	os.Mkdir(LogPath, os.ModePerm)
+	//默认保留15天内的日志,-1为永久保留
+	initLog(15)
+}
+
+func initLog(saveDay int) {
+	go logfile()
+	task := cron.New()
+	task.Start()
+	task.AddFunc("0 0 0 * * ?", func() {
+		go logfile()
+		time.Sleep(50 * time.Second)
+		if saveDay > 0 {
+			filepath.Walk(LogPath, func(path string, info os.FileInfo, err error) error {
+				str := fileReg.FindStringSubmatch(info.Name())
+				if len(str) == 2 {
+					t, er := time.ParseInLocation(FMT, str[1], time.Local)
+					if er == nil {
+						if (time.Now().Unix()-t.Unix())/86400 > int64(saveDay) {
+							log.Println("delete log file:", path, os.Remove(path))
+						}
+					}
+				}
+				return nil
+			})
+		}
+	})
+}
+
+//创建并切换输出文件
+func logfile() {
+	now := time.Now().Format(FMT)
+	file, _ := os.Create(LogPath + "/" + now + ".log")
+	log.SetOutput(file)
+	xweb.RootApp().Logger.SetOutput(file)
+	go func(file *os.File) {
+		time.Sleep(5 * time.Second)
+		if LogFile != nil {
+			LogFile.Close()
+		}
+		LogFile = file
+	}(file)
+}

+ 55 - 0
common/src/qfw/util/middleGround/doPost.go

@@ -0,0 +1,55 @@
+package middleGround
+
+//中台接口请求
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	qutil "app.yhyue.com/moapp/jybase/common"
+)
+
+var JyApiConfig *ApiConfig
+
+func init() {
+	//初始化中台请求接口
+	//qutil.ReadConfig("./baseApi.json", &JyApiConfig)
+}
+
+//ApiConfig 中台交互配置
+type ApiConfig struct {
+	AppId   string `json:"appId"`
+	ApiList struct {
+		BuyBalance    string `json:"buyBalance"`    //购买资源
+		UseBalance    string `json:"useBalance"`    //使用资源
+		FindBalance   string `json:"findBalance"`   //查询账户余额接口
+		FindRecord    string `json:"findRecord"`    //查询流水账接口
+		FindPreview   string `json:"findPreview"`   //数据导出去重查询
+		EntDedupUrl   string `json:"entdedupUrl"`   //企业去重
+		UpdateVipTime string `json:"updateVipTime"` //更新超级订阅到期时间接口
+	} `json:"apiList"`
+}
+
+// CommonPost 中台api接口公共请求方法
+func CommonPost(path string, param url.Values) (map[string]interface{}, error) {
+	if path == "" {
+		return nil, fmt.Errorf("请求地址异常")
+	}
+	param["appId"] = []string{JyApiConfig.AppId}
+	res, err := http.PostForm(path, param)
+	if err != nil {
+		return nil, err
+	}
+	defer res.Body.Close()
+	bs, _ := ioutil.ReadAll(res.Body)
+	resMap := map[string]interface{}{}
+	err = json.Unmarshal(bs, &resMap)
+	if err != nil {
+		return nil, fmt.Errorf("%s 请求中台返回内容异常 %s", path, string(bs))
+	}
+	if qutil.IntAll(resMap["code"]) != 1 {
+		return nil, fmt.Errorf("%s 请求中台请求出错 %v", path, resMap["message"])
+	}
+	return resMap, nil
+}

+ 0 - 149
github.com/SKatiyar/qr/coding/gen.go

@@ -1,149 +0,0 @@
-// +build ignore
-
-package main
-
-import "fmt"
-
-// tables from qrencode-3.1.1/qrspec.c
-
-var capacity = [41]struct {
-	width     int
-	words     int
-	remainder int
-	ec        [4]int
-}{
-	{0, 0, 0, [4]int{0, 0, 0, 0}},
-	{21, 26, 0, [4]int{7, 10, 13, 17}}, // 1
-	{25, 44, 7, [4]int{10, 16, 22, 28}},
-	{29, 70, 7, [4]int{15, 26, 36, 44}},
-	{33, 100, 7, [4]int{20, 36, 52, 64}},
-	{37, 134, 7, [4]int{26, 48, 72, 88}}, // 5
-	{41, 172, 7, [4]int{36, 64, 96, 112}},
-	{45, 196, 0, [4]int{40, 72, 108, 130}},
-	{49, 242, 0, [4]int{48, 88, 132, 156}},
-	{53, 292, 0, [4]int{60, 110, 160, 192}},
-	{57, 346, 0, [4]int{72, 130, 192, 224}}, //10
-	{61, 404, 0, [4]int{80, 150, 224, 264}},
-	{65, 466, 0, [4]int{96, 176, 260, 308}},
-	{69, 532, 0, [4]int{104, 198, 288, 352}},
-	{73, 581, 3, [4]int{120, 216, 320, 384}},
-	{77, 655, 3, [4]int{132, 240, 360, 432}}, //15
-	{81, 733, 3, [4]int{144, 280, 408, 480}},
-	{85, 815, 3, [4]int{168, 308, 448, 532}},
-	{89, 901, 3, [4]int{180, 338, 504, 588}},
-	{93, 991, 3, [4]int{196, 364, 546, 650}},
-	{97, 1085, 3, [4]int{224, 416, 600, 700}}, //20
-	{101, 1156, 4, [4]int{224, 442, 644, 750}},
-	{105, 1258, 4, [4]int{252, 476, 690, 816}},
-	{109, 1364, 4, [4]int{270, 504, 750, 900}},
-	{113, 1474, 4, [4]int{300, 560, 810, 960}},
-	{117, 1588, 4, [4]int{312, 588, 870, 1050}}, //25
-	{121, 1706, 4, [4]int{336, 644, 952, 1110}},
-	{125, 1828, 4, [4]int{360, 700, 1020, 1200}},
-	{129, 1921, 3, [4]int{390, 728, 1050, 1260}},
-	{133, 2051, 3, [4]int{420, 784, 1140, 1350}},
-	{137, 2185, 3, [4]int{450, 812, 1200, 1440}}, //30
-	{141, 2323, 3, [4]int{480, 868, 1290, 1530}},
-	{145, 2465, 3, [4]int{510, 924, 1350, 1620}},
-	{149, 2611, 3, [4]int{540, 980, 1440, 1710}},
-	{153, 2761, 3, [4]int{570, 1036, 1530, 1800}},
-	{157, 2876, 0, [4]int{570, 1064, 1590, 1890}}, //35
-	{161, 3034, 0, [4]int{600, 1120, 1680, 1980}},
-	{165, 3196, 0, [4]int{630, 1204, 1770, 2100}},
-	{169, 3362, 0, [4]int{660, 1260, 1860, 2220}},
-	{173, 3532, 0, [4]int{720, 1316, 1950, 2310}},
-	{177, 3706, 0, [4]int{750, 1372, 2040, 2430}}, //40
-}
-
-var eccTable = [41][4][2]int{
-	{{0, 0}, {0, 0}, {0, 0}, {0, 0}},
-	{{1, 0}, {1, 0}, {1, 0}, {1, 0}}, // 1
-	{{1, 0}, {1, 0}, {1, 0}, {1, 0}},
-	{{1, 0}, {1, 0}, {2, 0}, {2, 0}},
-	{{1, 0}, {2, 0}, {2, 0}, {4, 0}},
-	{{1, 0}, {2, 0}, {2, 2}, {2, 2}}, // 5
-	{{2, 0}, {4, 0}, {4, 0}, {4, 0}},
-	{{2, 0}, {4, 0}, {2, 4}, {4, 1}},
-	{{2, 0}, {2, 2}, {4, 2}, {4, 2}},
-	{{2, 0}, {3, 2}, {4, 4}, {4, 4}},
-	{{2, 2}, {4, 1}, {6, 2}, {6, 2}}, //10
-	{{4, 0}, {1, 4}, {4, 4}, {3, 8}},
-	{{2, 2}, {6, 2}, {4, 6}, {7, 4}},
-	{{4, 0}, {8, 1}, {8, 4}, {12, 4}},
-	{{3, 1}, {4, 5}, {11, 5}, {11, 5}},
-	{{5, 1}, {5, 5}, {5, 7}, {11, 7}}, //15
-	{{5, 1}, {7, 3}, {15, 2}, {3, 13}},
-	{{1, 5}, {10, 1}, {1, 15}, {2, 17}},
-	{{5, 1}, {9, 4}, {17, 1}, {2, 19}},
-	{{3, 4}, {3, 11}, {17, 4}, {9, 16}},
-	{{3, 5}, {3, 13}, {15, 5}, {15, 10}}, //20
-	{{4, 4}, {17, 0}, {17, 6}, {19, 6}},
-	{{2, 7}, {17, 0}, {7, 16}, {34, 0}},
-	{{4, 5}, {4, 14}, {11, 14}, {16, 14}},
-	{{6, 4}, {6, 14}, {11, 16}, {30, 2}},
-	{{8, 4}, {8, 13}, {7, 22}, {22, 13}}, //25
-	{{10, 2}, {19, 4}, {28, 6}, {33, 4}},
-	{{8, 4}, {22, 3}, {8, 26}, {12, 28}},
-	{{3, 10}, {3, 23}, {4, 31}, {11, 31}},
-	{{7, 7}, {21, 7}, {1, 37}, {19, 26}},
-	{{5, 10}, {19, 10}, {15, 25}, {23, 25}}, //30
-	{{13, 3}, {2, 29}, {42, 1}, {23, 28}},
-	{{17, 0}, {10, 23}, {10, 35}, {19, 35}},
-	{{17, 1}, {14, 21}, {29, 19}, {11, 46}},
-	{{13, 6}, {14, 23}, {44, 7}, {59, 1}},
-	{{12, 7}, {12, 26}, {39, 14}, {22, 41}}, //35
-	{{6, 14}, {6, 34}, {46, 10}, {2, 64}},
-	{{17, 4}, {29, 14}, {49, 10}, {24, 46}},
-	{{4, 18}, {13, 32}, {48, 14}, {42, 32}},
-	{{20, 4}, {40, 7}, {43, 22}, {10, 67}},
-	{{19, 6}, {18, 31}, {34, 34}, {20, 61}}, //40
-}
-
-var align = [41][2]int{
-	{0, 0},
-	{0, 0}, {18, 0}, {22, 0}, {26, 0}, {30, 0}, // 1- 5
-	{34, 0}, {22, 38}, {24, 42}, {26, 46}, {28, 50}, // 6-10
-	{30, 54}, {32, 58}, {34, 62}, {26, 46}, {26, 48}, //11-15
-	{26, 50}, {30, 54}, {30, 56}, {30, 58}, {34, 62}, //16-20
-	{28, 50}, {26, 50}, {30, 54}, {28, 54}, {32, 58}, //21-25
-	{30, 58}, {34, 62}, {26, 50}, {30, 54}, {26, 52}, //26-30
-	{30, 56}, {34, 60}, {30, 58}, {34, 62}, {30, 54}, //31-35
-	{24, 50}, {28, 54}, {32, 58}, {26, 54}, {30, 58}, //35-40
-}
-
-var versionPattern = [41]int{
-	0,
-	0, 0, 0, 0, 0, 0,
-	0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
-	0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
-	0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
-	0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
-	0x27541, 0x28c69,
-}
-
-func main() {
-	fmt.Printf("\t{},\n")
-	for i := 1; i <= 40; i++ {
-		apos := align[i][0] - 2
-		if apos < 0 {
-			apos = 100
-		}
-		astride := align[i][1] - align[i][0]
-		if astride < 1 {
-			astride = 100
-		}
-		fmt.Printf("\t{%v, %v, %v, %#x, [4]level{{%v, %v}, {%v, %v}, {%v, %v}, {%v, %v}}},  // %v\n",
-			apos, astride, capacity[i].words,
-			versionPattern[i],
-			eccTable[i][0][0]+eccTable[i][0][1],
-			float64(capacity[i].ec[0])/float64(eccTable[i][0][0]+eccTable[i][0][1]),
-			eccTable[i][1][0]+eccTable[i][1][1],
-			float64(capacity[i].ec[1])/float64(eccTable[i][1][0]+eccTable[i][1][1]),
-			eccTable[i][2][0]+eccTable[i][2][1],
-			float64(capacity[i].ec[2])/float64(eccTable[i][2][0]+eccTable[i][2][1]),
-			eccTable[i][3][0]+eccTable[i][3][1],
-			float64(capacity[i].ec[3])/float64(eccTable[i][3][0]+eccTable[i][3][1]),
-			i,
-		)
-	}
-}

+ 0 - 815
github.com/SKatiyar/qr/coding/qr.go

@@ -1,815 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package coding implements low-level QR coding details.
-package coding
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-
-	"github.com/SKatiyar/qr/gf256"
-)
-
-// Field is the field for QR error correction.
-var Field = gf256.NewField(0x11d, 2)
-
-// A Version represents a QR version.
-// The version specifies the size of the QR code:
-// a QR code with version v has 4v+17 pixels on a side.
-// Versions number from 1 to 40: the larger the version,
-// the more information the code can store.
-type Version int
-
-const MinVersion = 1
-const MaxVersion = 40
-
-func (v Version) String() string {
-	return strconv.Itoa(int(v))
-}
-
-func (v Version) sizeClass() int {
-	if v <= 9 {
-		return 0
-	}
-	if v <= 26 {
-		return 1
-	}
-	return 2
-}
-
-// DataBytes returns the number of data bytes that can be
-// stored in a QR code with the given version and level.
-func (v Version) DataBytes(l Level) int {
-	vt := &vtab[v]
-	lev := &vt.level[l]
-	return vt.bytes - lev.nblock*lev.check
-}
-
-// Encoding implements a QR data encoding scheme.
-// The implementations--Numeric, Alphanumeric, and String--specify
-// the character set and the mapping from UTF-8 to code bits.
-// The more restrictive the mode, the fewer code bits are needed.
-type Encoding interface {
-	Check() error
-	Bits(v Version) int
-	Encode(b *Bits, v Version)
-}
-
-type Bits struct {
-	b    []byte
-	nbit int
-}
-
-func (b *Bits) Reset() {
-	b.b = b.b[:0]
-	b.nbit = 0
-}
-
-func (b *Bits) Bits() int {
-	return b.nbit
-}
-
-func (b *Bits) Bytes() []byte {
-	if b.nbit%8 != 0 {
-		panic("fractional byte")
-	}
-	return b.b
-}
-
-func (b *Bits) Append(p []byte) {
-	if b.nbit%8 != 0 {
-		panic("fractional byte")
-	}
-	b.b = append(b.b, p...)
-	b.nbit += 8 * len(p)
-}
-
-func (b *Bits) Write(v uint, nbit int) {
-	for nbit > 0 {
-		n := nbit
-		if n > 8 {
-			n = 8
-		}
-		if b.nbit%8 == 0 {
-			b.b = append(b.b, 0)
-		} else {
-			m := -b.nbit & 7
-			if n > m {
-				n = m
-			}
-		}
-		b.nbit += n
-		sh := uint(nbit - n)
-		b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7))
-		v -= v >> sh << sh
-		nbit -= n
-	}
-}
-
-// Num is the encoding for numeric data.
-// The only valid characters are the decimal digits 0 through 9.
-type Num string
-
-func (s Num) String() string {
-	return fmt.Sprintf("Num(%#q)", string(s))
-}
-
-func (s Num) Check() error {
-	for _, c := range s {
-		if c < '0' || '9' < c {
-			return fmt.Errorf("non-numeric string %#q", string(s))
-		}
-	}
-	return nil
-}
-
-var numLen = [3]int{10, 12, 14}
-
-func (s Num) Bits(v Version) int {
-	return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3
-}
-
-func (s Num) Encode(b *Bits, v Version) {
-	b.Write(1, 4)
-	b.Write(uint(len(s)), numLen[v.sizeClass()])
-	var i int
-	for i = 0; i+3 <= len(s); i += 3 {
-		w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0')
-		b.Write(w, 10)
-	}
-	switch len(s) - i {
-	case 1:
-		w := uint(s[i] - '0')
-		b.Write(w, 4)
-	case 2:
-		w := uint(s[i]-'0')*10 + uint(s[i+1]-'0')
-		b.Write(w, 7)
-	}
-}
-
-// Alpha is the encoding for alphanumeric data.
-// The valid characters are 0-9A-Z$%*+-./: and space.
-type Alpha string
-
-const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
-
-func (s Alpha) String() string {
-	return fmt.Sprintf("Alpha(%#q)", string(s))
-}
-
-func (s Alpha) Check() error {
-	for _, c := range s {
-		if strings.IndexRune(alphabet, c) < 0 {
-			return fmt.Errorf("non-alphanumeric string %#q", string(s))
-		}
-	}
-	return nil
-}
-
-var alphaLen = [3]int{9, 11, 13}
-
-func (s Alpha) Bits(v Version) int {
-	return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2
-}
-
-func (s Alpha) Encode(b *Bits, v Version) {
-	b.Write(2, 4)
-	b.Write(uint(len(s)), alphaLen[v.sizeClass()])
-	var i int
-	for i = 0; i+2 <= len(s); i += 2 {
-		w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 +
-			uint(strings.IndexRune(alphabet, rune(s[i+1])))
-		b.Write(w, 11)
-	}
-
-	if i < len(s) {
-		w := uint(strings.IndexRune(alphabet, rune(s[i])))
-		b.Write(w, 6)
-	}
-}
-
-// String is the encoding for 8-bit data.  All bytes are valid.
-type String string
-
-func (s String) String() string {
-	return fmt.Sprintf("String(%#q)", string(s))
-}
-
-func (s String) Check() error {
-	return nil
-}
-
-var stringLen = [3]int{8, 16, 16}
-
-func (s String) Bits(v Version) int {
-	return 4 + stringLen[v.sizeClass()] + 8*len(s)
-}
-
-func (s String) Encode(b *Bits, v Version) {
-	b.Write(4, 4)
-	b.Write(uint(len(s)), stringLen[v.sizeClass()])
-	for i := 0; i < len(s); i++ {
-		b.Write(uint(s[i]), 8)
-	}
-}
-
-// A Pixel describes a single pixel in a QR code.
-type Pixel uint32
-
-const (
-	Black Pixel = 1 << iota
-	Invert
-)
-
-func (p Pixel) Offset() uint {
-	return uint(p >> 6)
-}
-
-func OffsetPixel(o uint) Pixel {
-	return Pixel(o << 6)
-}
-
-func (r PixelRole) Pixel() Pixel {
-	return Pixel(r << 2)
-}
-
-func (p Pixel) Role() PixelRole {
-	return PixelRole(p>>2) & 15
-}
-
-func (p Pixel) String() string {
-	s := p.Role().String()
-	if p&Black != 0 {
-		s += "+black"
-	}
-	if p&Invert != 0 {
-		s += "+invert"
-	}
-	s += "+" + strconv.FormatUint(uint64(p.Offset()), 10)
-	return s
-}
-
-// A PixelRole describes the role of a QR pixel.
-type PixelRole uint32
-
-const (
-	_         PixelRole = iota
-	Position            // position squares (large)
-	Alignment           // alignment squares (small)
-	Timing              // timing strip between position squares
-	Format              // format metadata
-	PVersion            // version pattern
-	Unused              // unused pixel
-	Data                // data bit
-	Check               // error correction check bit
-	Extra
-)
-
-var roles = []string{
-	"",
-	"position",
-	"alignment",
-	"timing",
-	"format",
-	"pversion",
-	"unused",
-	"data",
-	"check",
-	"extra",
-}
-
-func (r PixelRole) String() string {
-	if Position <= r && r <= Check {
-		return roles[r]
-	}
-	return strconv.Itoa(int(r))
-}
-
-// A Level represents a QR error correction level.
-// From least to most tolerant of errors, they are L, M, Q, H.
-type Level int
-
-const (
-	L Level = iota
-	M
-	Q
-	H
-)
-
-func (l Level) String() string {
-	if L <= l && l <= H {
-		return "LMQH"[l : l+1]
-	}
-	return strconv.Itoa(int(l))
-}
-
-// A Code is a square pixel grid.
-type Code struct {
-	Bitmap []byte // 1 is black, 0 is white
-	Size   int    // number of pixels on a side
-	Stride int    // number of bytes per row
-}
-
-func (c *Code) Black(x, y int) bool {
-	return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
-		c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
-}
-
-// A Mask describes a mask that is applied to the QR
-// code to avoid QR artifacts being interpreted as
-// alignment and timing patterns (such as the squares
-// in the corners).  Valid masks are integers from 0 to 7.
-type Mask int
-
-// http://www.swetake.com/qr/qr5_en.html
-var mfunc = []func(int, int) bool{
-	func(i, j int) bool { return (i+j)%2 == 0 },
-	func(i, j int) bool { return i%2 == 0 },
-	func(i, j int) bool { return j%3 == 0 },
-	func(i, j int) bool { return (i+j)%3 == 0 },
-	func(i, j int) bool { return (i/2+j/3)%2 == 0 },
-	func(i, j int) bool { return i*j%2+i*j%3 == 0 },
-	func(i, j int) bool { return (i*j%2+i*j%3)%2 == 0 },
-	func(i, j int) bool { return (i*j%3+(i+j)%2)%2 == 0 },
-}
-
-func (m Mask) Invert(y, x int) bool {
-	if m < 0 {
-		return false
-	}
-	return mfunc[m](y, x)
-}
-
-// A Plan describes how to construct a QR code
-// with a specific version, level, and mask.
-type Plan struct {
-	Version Version
-	Level   Level
-	Mask    Mask
-
-	DataBytes  int // number of data bytes
-	CheckBytes int // number of error correcting (checksum) bytes
-	Blocks     int // number of data blocks
-
-	Pixel [][]Pixel // pixel map
-}
-
-// NewPlan returns a Plan for a QR code with the given
-// version, level, and mask.
-func NewPlan(version Version, level Level, mask Mask) (*Plan, error) {
-	p, err := vplan(version)
-	if err != nil {
-		return nil, err
-	}
-	if err := fplan(level, mask, p); err != nil {
-		return nil, err
-	}
-	if err := lplan(version, level, p); err != nil {
-		return nil, err
-	}
-	if err := mplan(mask, p); err != nil {
-		return nil, err
-	}
-	return p, nil
-}
-
-func (b *Bits) Pad(n int) {
-	if n < 0 {
-		panic("qr: invalid pad size")
-	}
-	if n <= 4 {
-		b.Write(0, n)
-	} else {
-		b.Write(0, 4)
-		n -= 4
-		n -= -b.Bits() & 7
-		b.Write(0, -b.Bits()&7)
-		pad := n / 8
-		for i := 0; i < pad; i += 2 {
-			b.Write(0xec, 8)
-			if i+1 >= pad {
-				break
-			}
-			b.Write(0x11, 8)
-		}
-	}
-}
-
-func (b *Bits) AddCheckBytes(v Version, l Level) {
-	nd := v.DataBytes(l)
-	if b.nbit < nd*8 {
-		b.Pad(nd*8 - b.nbit)
-	}
-	if b.nbit != nd*8 {
-		panic("qr: too much data")
-	}
-
-	dat := b.Bytes()
-	vt := &vtab[v]
-	lev := &vt.level[l]
-	db := nd / lev.nblock
-	extra := nd % lev.nblock
-	chk := make([]byte, lev.check)
-	rs := gf256.NewRSEncoder(Field, lev.check)
-	for i := 0; i < lev.nblock; i++ {
-		if i == lev.nblock-extra {
-			db++
-		}
-		rs.ECC(dat[:db], chk)
-		b.Append(chk)
-		dat = dat[db:]
-	}
-
-	if len(b.Bytes()) != vt.bytes {
-		panic("qr: internal error")
-	}
-}
-
-func (p *Plan) Encode(text ...Encoding) (*Code, error) {
-	var b Bits
-	for _, t := range text {
-		if err := t.Check(); err != nil {
-			return nil, err
-		}
-		t.Encode(&b, p.Version)
-	}
-	if b.Bits() > p.DataBytes*8 {
-		return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8)
-	}
-	b.AddCheckBytes(p.Version, p.Level)
-	bytes := b.Bytes()
-
-	// Now we have the checksum bytes and the data bytes.
-	// Construct the actual code.
-	c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7}
-	c.Bitmap = make([]byte, c.Stride*c.Size)
-	crow := c.Bitmap
-	for _, row := range p.Pixel {
-		for x, pix := range row {
-			switch pix.Role() {
-			case Data, Check:
-				o := pix.Offset()
-				if bytes[o/8]&(1<<uint(7-o&7)) != 0 {
-					pix ^= Black
-				}
-			}
-			if pix&Black != 0 {
-				crow[x/8] |= 1 << uint(7-x&7)
-			}
-		}
-		crow = crow[c.Stride:]
-	}
-	return c, nil
-}
-
-// A version describes metadata associated with a version.
-type version struct {
-	apos    int
-	astride int
-	bytes   int
-	pattern int
-	level   [4]level
-}
-
-type level struct {
-	nblock int
-	check  int
-}
-
-var vtab = []version{
-	{},
-	{100, 100, 26, 0x0, [4]level{{1, 7}, {1, 10}, {1, 13}, {1, 17}}},          // 1
-	{16, 100, 44, 0x0, [4]level{{1, 10}, {1, 16}, {1, 22}, {1, 28}}},          // 2
-	{20, 100, 70, 0x0, [4]level{{1, 15}, {1, 26}, {2, 18}, {2, 22}}},          // 3
-	{24, 100, 100, 0x0, [4]level{{1, 20}, {2, 18}, {2, 26}, {4, 16}}},         // 4
-	{28, 100, 134, 0x0, [4]level{{1, 26}, {2, 24}, {4, 18}, {4, 22}}},         // 5
-	{32, 100, 172, 0x0, [4]level{{2, 18}, {4, 16}, {4, 24}, {4, 28}}},         // 6
-	{20, 16, 196, 0x7c94, [4]level{{2, 20}, {4, 18}, {6, 18}, {5, 26}}},       // 7
-	{22, 18, 242, 0x85bc, [4]level{{2, 24}, {4, 22}, {6, 22}, {6, 26}}},       // 8
-	{24, 20, 292, 0x9a99, [4]level{{2, 30}, {5, 22}, {8, 20}, {8, 24}}},       // 9
-	{26, 22, 346, 0xa4d3, [4]level{{4, 18}, {5, 26}, {8, 24}, {8, 28}}},       // 10
-	{28, 24, 404, 0xbbf6, [4]level{{4, 20}, {5, 30}, {8, 28}, {11, 24}}},      // 11
-	{30, 26, 466, 0xc762, [4]level{{4, 24}, {8, 22}, {10, 26}, {11, 28}}},     // 12
-	{32, 28, 532, 0xd847, [4]level{{4, 26}, {9, 22}, {12, 24}, {16, 22}}},     // 13
-	{24, 20, 581, 0xe60d, [4]level{{4, 30}, {9, 24}, {16, 20}, {16, 24}}},     // 14
-	{24, 22, 655, 0xf928, [4]level{{6, 22}, {10, 24}, {12, 30}, {18, 24}}},    // 15
-	{24, 24, 733, 0x10b78, [4]level{{6, 24}, {10, 28}, {17, 24}, {16, 30}}},   // 16
-	{28, 24, 815, 0x1145d, [4]level{{6, 28}, {11, 28}, {16, 28}, {19, 28}}},   // 17
-	{28, 26, 901, 0x12a17, [4]level{{6, 30}, {13, 26}, {18, 28}, {21, 28}}},   // 18
-	{28, 28, 991, 0x13532, [4]level{{7, 28}, {14, 26}, {21, 26}, {25, 26}}},   // 19
-	{32, 28, 1085, 0x149a6, [4]level{{8, 28}, {16, 26}, {20, 30}, {25, 28}}},  // 20
-	{26, 22, 1156, 0x15683, [4]level{{8, 28}, {17, 26}, {23, 28}, {25, 30}}},  // 21
-	{24, 24, 1258, 0x168c9, [4]level{{9, 28}, {17, 28}, {23, 30}, {34, 24}}},  // 22
-	{28, 24, 1364, 0x177ec, [4]level{{9, 30}, {18, 28}, {25, 30}, {30, 30}}},  // 23
-	{26, 26, 1474, 0x18ec4, [4]level{{10, 30}, {20, 28}, {27, 30}, {32, 30}}}, // 24
-	{30, 26, 1588, 0x191e1, [4]level{{12, 26}, {21, 28}, {29, 30}, {35, 30}}}, // 25
-	{28, 28, 1706, 0x1afab, [4]level{{12, 28}, {23, 28}, {34, 28}, {37, 30}}}, // 26
-	{32, 28, 1828, 0x1b08e, [4]level{{12, 30}, {25, 28}, {34, 30}, {40, 30}}}, // 27
-	{24, 24, 1921, 0x1cc1a, [4]level{{13, 30}, {26, 28}, {35, 30}, {42, 30}}}, // 28
-	{28, 24, 2051, 0x1d33f, [4]level{{14, 30}, {28, 28}, {38, 30}, {45, 30}}}, // 29
-	{24, 26, 2185, 0x1ed75, [4]level{{15, 30}, {29, 28}, {40, 30}, {48, 30}}}, // 30
-	{28, 26, 2323, 0x1f250, [4]level{{16, 30}, {31, 28}, {43, 30}, {51, 30}}}, // 31
-	{32, 26, 2465, 0x209d5, [4]level{{17, 30}, {33, 28}, {45, 30}, {54, 30}}}, // 32
-	{28, 28, 2611, 0x216f0, [4]level{{18, 30}, {35, 28}, {48, 30}, {57, 30}}}, // 33
-	{32, 28, 2761, 0x228ba, [4]level{{19, 30}, {37, 28}, {51, 30}, {60, 30}}}, // 34
-	{28, 24, 2876, 0x2379f, [4]level{{19, 30}, {38, 28}, {53, 30}, {63, 30}}}, // 35
-	{22, 26, 3034, 0x24b0b, [4]level{{20, 30}, {40, 28}, {56, 30}, {66, 30}}}, // 36
-	{26, 26, 3196, 0x2542e, [4]level{{21, 30}, {43, 28}, {59, 30}, {70, 30}}}, // 37
-	{30, 26, 3362, 0x26a64, [4]level{{22, 30}, {45, 28}, {62, 30}, {74, 30}}}, // 38
-	{24, 28, 3532, 0x27541, [4]level{{24, 30}, {47, 28}, {65, 30}, {77, 30}}}, // 39
-	{28, 28, 3706, 0x28c69, [4]level{{25, 30}, {49, 28}, {68, 30}, {81, 30}}}, // 40
-}
-
-func grid(siz int) [][]Pixel {
-	m := make([][]Pixel, siz)
-	pix := make([]Pixel, siz*siz)
-	for i := range m {
-		m[i], pix = pix[:siz], pix[siz:]
-	}
-	return m
-}
-
-// vplan creates a Plan for the given version.
-func vplan(v Version) (*Plan, error) {
-	p := &Plan{Version: v}
-	if v < 1 || v > 40 {
-		return nil, fmt.Errorf("invalid QR version %d", int(v))
-	}
-	siz := 17 + int(v)*4
-	m := grid(siz)
-	p.Pixel = m
-
-	// Timing markers (overwritten by boxes).
-	const ti = 6 // timing is in row/column 6 (counting from 0)
-	for i := range m {
-		p := Timing.Pixel()
-		if i&1 == 0 {
-			p |= Black
-		}
-		m[i][ti] = p
-		m[ti][i] = p
-	}
-
-	// Position boxes.
-	posBox(m, 0, 0)
-	posBox(m, siz-7, 0)
-	posBox(m, 0, siz-7)
-
-	// Alignment boxes.
-	info := &vtab[v]
-	for x := 4; x+5 < siz; {
-		for y := 4; y+5 < siz; {
-			// don't overwrite timing markers
-			if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) {
-			} else {
-				alignBox(m, x, y)
-			}
-			if y == 4 {
-				y = info.apos
-			} else {
-				y += info.astride
-			}
-		}
-		if x == 4 {
-			x = info.apos
-		} else {
-			x += info.astride
-		}
-	}
-
-	// Version pattern.
-	pat := vtab[v].pattern
-	if pat != 0 {
-		v := pat
-		for x := 0; x < 6; x++ {
-			for y := 0; y < 3; y++ {
-				p := PVersion.Pixel()
-				if v&1 != 0 {
-					p |= Black
-				}
-				m[siz-11+y][x] = p
-				m[x][siz-11+y] = p
-				v >>= 1
-			}
-		}
-	}
-
-	// One lonely black pixel
-	m[siz-8][8] = Unused.Pixel() | Black
-
-	return p, nil
-}
-
-// fplan adds the format pixels
-func fplan(l Level, m Mask, p *Plan) error {
-	// Format pixels.
-	fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10
-	fb |= uint32(m) << 10   // mask
-	const formatPoly = 0x537
-	rem := fb
-	for i := 14; i >= 10; i-- {
-		if rem&(1<<uint(i)) != 0 {
-			rem ^= formatPoly << uint(i-10)
-		}
-	}
-	fb |= rem
-	invert := uint32(0x5412)
-	siz := len(p.Pixel)
-	for i := uint(0); i < 15; i++ {
-		pix := Format.Pixel() + OffsetPixel(i)
-		if (fb>>i)&1 == 1 {
-			pix |= Black
-		}
-		if (invert>>i)&1 == 1 {
-			pix ^= Invert | Black
-		}
-		// top left
-		switch {
-		case i < 6:
-			p.Pixel[i][8] = pix
-		case i < 8:
-			p.Pixel[i+1][8] = pix
-		case i < 9:
-			p.Pixel[8][7] = pix
-		default:
-			p.Pixel[8][14-i] = pix
-		}
-		// bottom right
-		switch {
-		case i < 8:
-			p.Pixel[8][siz-1-int(i)] = pix
-		default:
-			p.Pixel[siz-1-int(14-i)][8] = pix
-		}
-	}
-	return nil
-}
-
-// lplan edits a version-only Plan to add information
-// about the error correction levels.
-func lplan(v Version, l Level, p *Plan) error {
-	p.Level = l
-
-	nblock := vtab[v].level[l].nblock
-	ne := vtab[v].level[l].check
-	nde := (vtab[v].bytes - ne*nblock) / nblock
-	extra := (vtab[v].bytes - ne*nblock) % nblock
-	dataBits := (nde*nblock + extra) * 8
-	checkBits := ne * nblock * 8
-
-	p.DataBytes = vtab[v].bytes - ne*nblock
-	p.CheckBytes = ne * nblock
-	p.Blocks = nblock
-
-	// Make data + checksum pixels.
-	data := make([]Pixel, dataBits)
-	for i := range data {
-		data[i] = Data.Pixel() | OffsetPixel(uint(i))
-	}
-	check := make([]Pixel, checkBits)
-	for i := range check {
-		check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits))
-	}
-
-	// Split into blocks.
-	dataList := make([][]Pixel, nblock)
-	checkList := make([][]Pixel, nblock)
-	for i := 0; i < nblock; i++ {
-		// The last few blocks have an extra data byte (8 pixels).
-		nd := nde
-		if i >= nblock-extra {
-			nd++
-		}
-		dataList[i], data = data[0:nd*8], data[nd*8:]
-		checkList[i], check = check[0:ne*8], check[ne*8:]
-	}
-	if len(data) != 0 || len(check) != 0 {
-		panic("data/check math")
-	}
-
-	// Build up bit sequence, taking first byte of each block,
-	// then second byte, and so on.  Then checksums.
-	bits := make([]Pixel, dataBits+checkBits)
-	dst := bits
-	for i := 0; i < nde+1; i++ {
-		for _, b := range dataList {
-			if i*8 < len(b) {
-				copy(dst, b[i*8:(i+1)*8])
-				dst = dst[8:]
-			}
-		}
-	}
-	for i := 0; i < ne; i++ {
-		for _, b := range checkList {
-			if i*8 < len(b) {
-				copy(dst, b[i*8:(i+1)*8])
-				dst = dst[8:]
-			}
-		}
-	}
-	if len(dst) != 0 {
-		panic("dst math")
-	}
-
-	// Sweep up pair of columns,
-	// then down, assigning to right then left pixel.
-	// Repeat.
-	// See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm
-	siz := len(p.Pixel)
-	rem := make([]Pixel, 7)
-	for i := range rem {
-		rem[i] = Extra.Pixel()
-	}
-	src := append(bits, rem...)
-	for x := siz; x > 0; {
-		for y := siz - 1; y >= 0; y-- {
-			if p.Pixel[y][x-1].Role() == 0 {
-				p.Pixel[y][x-1], src = src[0], src[1:]
-			}
-			if p.Pixel[y][x-2].Role() == 0 {
-				p.Pixel[y][x-2], src = src[0], src[1:]
-			}
-		}
-		x -= 2
-		if x == 7 { // vertical timing strip
-			x--
-		}
-		for y := 0; y < siz; y++ {
-			if p.Pixel[y][x-1].Role() == 0 {
-				p.Pixel[y][x-1], src = src[0], src[1:]
-			}
-			if p.Pixel[y][x-2].Role() == 0 {
-				p.Pixel[y][x-2], src = src[0], src[1:]
-			}
-		}
-		x -= 2
-	}
-	return nil
-}
-
-// mplan edits a version+level-only Plan to add the mask.
-func mplan(m Mask, p *Plan) error {
-	p.Mask = m
-	for y, row := range p.Pixel {
-		for x, pix := range row {
-			if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) {
-				row[x] ^= Black | Invert
-			}
-		}
-	}
-	return nil
-}
-
-// posBox draws a position (large) box at upper left x, y.
-func posBox(m [][]Pixel, x, y int) {
-	pos := Position.Pixel()
-	// box
-	for dy := 0; dy < 7; dy++ {
-		for dx := 0; dx < 7; dx++ {
-			p := pos
-			if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 {
-				p |= Black
-			}
-			m[y+dy][x+dx] = p
-		}
-	}
-	// white border
-	for dy := -1; dy < 8; dy++ {
-		if 0 <= y+dy && y+dy < len(m) {
-			if x > 0 {
-				m[y+dy][x-1] = pos
-			}
-			if x+7 < len(m) {
-				m[y+dy][x+7] = pos
-			}
-		}
-	}
-	for dx := -1; dx < 8; dx++ {
-		if 0 <= x+dx && x+dx < len(m) {
-			if y > 0 {
-				m[y-1][x+dx] = pos
-			}
-			if y+7 < len(m) {
-				m[y+7][x+dx] = pos
-			}
-		}
-	}
-}
-
-// alignBox draw an alignment (small) box at upper left x, y.
-func alignBox(m [][]Pixel, x, y int) {
-	// box
-	align := Alignment.Pixel()
-	for dy := 0; dy < 5; dy++ {
-		for dx := 0; dx < 5; dx++ {
-			p := align
-			if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 {
-				p |= Black
-			}
-			m[y+dy][x+dx] = p
-		}
-	}
-}

+ 0 - 133
github.com/SKatiyar/qr/coding/qr_test.go

@@ -1,133 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package coding
-
-import (
-	"bytes"
-	"testing"
-
-	"github.com/SKatiyar/qr/gf256"
-	"github.com/SKatiyar/qr/libqrencode"
-)
-
-func test(t *testing.T, v Version, l Level, text ...Encoding) bool {
-	s := ""
-	ty := libqrencode.EightBit
-	switch x := text[0].(type) {
-	case String:
-		s = string(x)
-	case Alpha:
-		s = string(x)
-		ty = libqrencode.Alphanumeric
-	case Num:
-		s = string(x)
-		ty = libqrencode.Numeric
-	}
-	key, err := libqrencode.Encode(libqrencode.Version(v), libqrencode.Level(l), ty, s)
-	if err != nil {
-		t.Errorf("libqrencode.Encode(%v, %v, %d, %#q): %v", v, l, ty, s, err)
-		return false
-	}
-	mask := (^key.Pixel[8][2]&1)<<2 | (key.Pixel[8][3]&1)<<1 | (^key.Pixel[8][4] & 1)
-	p, err := NewPlan(v, l, Mask(mask))
-	if err != nil {
-		t.Errorf("NewPlan(%v, L, %d): %v", v, err, mask)
-		return false
-	}
-	if len(p.Pixel) != len(key.Pixel) {
-		t.Errorf("%v: NewPlan uses %dx%d, libqrencode uses %dx%d", v, len(p.Pixel), len(p.Pixel), len(key.Pixel), len(key.Pixel))
-		return false
-	}
-	c, err := p.Encode(text...)
-	if err != nil {
-		t.Errorf("Encode: %v", err)
-		return false
-	}
-	badpix := 0
-Pixel:
-	for y, prow := range p.Pixel {
-		for x, pix := range prow {
-			pix &^= Black
-			if c.Black(x, y) {
-				pix |= Black
-			}
-
-			keypix := key.Pixel[y][x]
-			want := Pixel(0)
-			switch {
-			case keypix&libqrencode.Finder != 0:
-				want = Position.Pixel()
-			case keypix&libqrencode.Alignment != 0:
-				want = Alignment.Pixel()
-			case keypix&libqrencode.Timing != 0:
-				want = Timing.Pixel()
-			case keypix&libqrencode.Format != 0:
-				want = Format.Pixel()
-				want |= OffsetPixel(pix.Offset()) // sic
-				want |= pix & Invert
-			case keypix&libqrencode.PVersion != 0:
-				want = PVersion.Pixel()
-			case keypix&libqrencode.DataECC != 0:
-				if pix.Role() == Check || pix.Role() == Extra {
-					want = pix.Role().Pixel()
-				} else {
-					want = Data.Pixel()
-				}
-				want |= OffsetPixel(pix.Offset())
-				want |= pix & Invert
-			default:
-				want = Unused.Pixel()
-			}
-			if keypix&libqrencode.Black != 0 {
-				want |= Black
-			}
-			if pix != want {
-				t.Errorf("%v/%v: Pixel[%d][%d] = %v, want %v %#x", v, mask, y, x, pix, want, keypix)
-				if badpix++; badpix >= 100 {
-					t.Errorf("stopping after %d bad pixels", badpix)
-					break Pixel
-				}
-			}
-		}
-	}
-	return badpix == 0
-}
-
-var input = []Encoding{
-	String("hello"),
-	Num("1"),
-	Num("12"),
-	Num("123"),
-	Alpha("AB"),
-	Alpha("ABC"),
-}
-
-func TestVersion(t *testing.T) {
-	badvers := 0
-Version:
-	for v := Version(1); v <= 40; v++ {
-		for l := L; l <= H; l++ {
-			for _, in := range input {
-				if !test(t, v, l, in) {
-					if badvers++; badvers >= 10 {
-						t.Errorf("stopping after %d bad versions", badvers)
-						break Version
-					}
-				}
-			}
-		}
-	}
-}
-
-func TestEncode(t *testing.T) {
-	data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
-	check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
-	rs := gf256.NewRSEncoder(Field, len(check))
-	out := make([]byte, len(check))
-	rs.ECC(data, out)
-	if !bytes.Equal(out, check) {
-		t.Errorf("have %x want %x", out, check)
-	}
-}

+ 0 - 85
github.com/SKatiyar/qr/gf256/blog_test.go

@@ -1,85 +0,0 @@
-// Copyright 2012 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file contains a straightforward implementation of
-// Reed-Solomon encoding, along with a benchmark.
-// It goes with http://research.swtch.com/field.
-//
-// For an optimized implementation, see gf256.go.
-
-package gf256
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-)
-
-// BlogECC writes to check the error correcting code bytes
-// for data using the given Reed-Solomon parameters.
-func BlogECC(rs *RSEncoder, m []byte, check []byte) {
-	if len(check) < rs.c {
-		panic("gf256: invalid check byte length")
-	}
-	if rs.c == 0 {
-		return
-	}
-
-	// The check bytes are the remainder after dividing
-	// data padded with c zeros by the generator polynomial.
-
-	// p = data padded with c zeros.
-	var p []byte
-	n := len(m) + rs.c
-	if len(rs.p) >= n {
-		p = rs.p
-	} else {
-		p = make([]byte, n)
-	}
-	copy(p, m)
-	for i := len(m); i < len(p); i++ {
-		p[i] = 0
-	}
-
-	gen := rs.gen
-
-	// Divide p by gen, leaving the remainder in p[len(data):].
-	// p[0] is the most significant term in p, and
-	// gen[0] is the most significant term in the generator.
-	for i := 0; i < len(m); i++ {
-		k := f.Mul(p[i], f.Inv(gen[0])) // k = pi / g0
-		// p -= k·g
-		for j, g := range gen {
-			p[i+j] = f.Add(p[i+j], f.Mul(k, g))
-		}
-	}
-
-	copy(check, p[len(m):])
-	rs.p = p
-}
-
-func BenchmarkBlogECC(b *testing.B) {
-	data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
-	check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
-	out := make([]byte, len(check))
-	rs := NewRSEncoder(f, len(check))
-	for i := 0; i < b.N; i++ {
-		BlogECC(rs, data, out)
-	}
-	b.SetBytes(int64(len(data)))
-	if !bytes.Equal(out, check) {
-		fmt.Printf("have %#v want %#v\n", out, check)
-	}
-}
-
-func TestBlogECC(t *testing.T) {
-	data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
-	check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
-	out := make([]byte, len(check))
-	rs := NewRSEncoder(f, len(check))
-	BlogECC(rs, data, out)
-	if !bytes.Equal(out, check) {
-		t.Errorf("have %x want %x", out, check)
-	}
-}

+ 0 - 241
github.com/SKatiyar/qr/gf256/gf256.go

@@ -1,241 +0,0 @@
-// Copyright 2010 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package gf256 implements arithmetic over the Galois Field GF(256).
-package gf256
-
-import "strconv"
-
-// A Field represents an instance of GF(256) defined by a specific polynomial.
-type Field struct {
-	log [256]byte // log[0] is unused
-	exp [510]byte
-}
-
-// NewField returns a new field corresponding to the polynomial poly
-// and generator α.  The Reed-Solomon encoding in QR codes uses
-// polynomial 0x11d with generator 2.
-//
-// The choice of generator α only affects the Exp and Log operations.
-func NewField(poly, α int) *Field {
-	if poly < 0x100 || poly >= 0x200 || reducible(poly) {
-		panic("gf256: invalid polynomial: " + strconv.Itoa(poly))
-	}
-
-	var f Field
-	x := 1
-	for i := 0; i < 255; i++ {
-		if x == 1 && i != 0 {
-			panic("gf256: invalid generator " + strconv.Itoa(α) +
-				" for polynomial " + strconv.Itoa(poly))
-		}
-		f.exp[i] = byte(x)
-		f.exp[i+255] = byte(x)
-		f.log[x] = byte(i)
-		x = mul(x, α, poly)
-	}
-	f.log[0] = 255
-	for i := 0; i < 255; i++ {
-		if f.log[f.exp[i]] != byte(i) {
-			panic("bad log")
-		}
-		if f.log[f.exp[i+255]] != byte(i) {
-			panic("bad log")
-		}
-	}
-	for i := 1; i < 256; i++ {
-		if f.exp[f.log[i]] != byte(i) {
-			panic("bad log")
-		}
-	}
-
-	return &f
-}
-
-// nbit returns the number of significant in p.
-func nbit(p int) uint {
-	n := uint(0)
-	for ; p > 0; p >>= 1 {
-		n++
-	}
-	return n
-}
-
-// polyDiv divides the polynomial p by q and returns the remainder.
-func polyDiv(p, q int) int {
-	np := nbit(p)
-	nq := nbit(q)
-	for ; np >= nq; np-- {
-		if p&(1<<(np-1)) != 0 {
-			p ^= q << (np - nq)
-		}
-	}
-	return p
-}
-
-// mul returns the product x*y mod poly, a GF(256) multiplication.
-func mul(x, y, poly int) int {
-	z := 0
-	for x > 0 {
-		if x&1 != 0 {
-			z ^= y
-		}
-		x >>= 1
-		y <<= 1
-		if y&0x100 != 0 {
-			y ^= poly
-		}
-	}
-	return z
-}
-
-// reducible reports whether p is reducible.
-func reducible(p int) bool {
-	// Multiplying n-bit * n-bit produces (2n-1)-bit,
-	// so if p is reducible, one of its factors must be
-	// of np/2+1 bits or fewer.
-	np := nbit(p)
-	for q := 2; q < 1<<(np/2+1); q++ {
-		if polyDiv(p, q) == 0 {
-			return true
-		}
-	}
-	return false
-}
-
-// Add returns the sum of x and y in the field.
-func (f *Field) Add(x, y byte) byte {
-	return x ^ y
-}
-
-// Exp returns the base-α exponential of e in the field.
-// If e < 0, Exp returns 0.
-func (f *Field) Exp(e int) byte {
-	if e < 0 {
-		return 0
-	}
-	return f.exp[e%255]
-}
-
-// Log returns the base-α logarithm of x in the field.
-// If x == 0, Log returns -1.
-func (f *Field) Log(x byte) int {
-	if x == 0 {
-		return -1
-	}
-	return int(f.log[x])
-}
-
-// Inv returns the multiplicative inverse of x in the field.
-// If x == 0, Inv returns 0.
-func (f *Field) Inv(x byte) byte {
-	if x == 0 {
-		return 0
-	}
-	return f.exp[255-f.log[x]]
-}
-
-// Mul returns the product of x and y in the field.
-func (f *Field) Mul(x, y byte) byte {
-	if x == 0 || y == 0 {
-		return 0
-	}
-	return f.exp[int(f.log[x])+int(f.log[y])]
-}
-
-// An RSEncoder implements Reed-Solomon encoding
-// over a given field using a given number of error correction bytes.
-type RSEncoder struct {
-	f    *Field
-	c    int
-	gen  []byte
-	lgen []byte
-	p    []byte
-}
-
-func (f *Field) gen(e int) (gen, lgen []byte) {
-	// p = 1
-	p := make([]byte, e+1)
-	p[e] = 1
-
-	for i := 0; i < e; i++ {
-		// p *= (x + Exp(i))
-		// p[j] = p[j]*Exp(i) + p[j+1].
-		c := f.Exp(i)
-		for j := 0; j < e; j++ {
-			p[j] = f.Mul(p[j], c) ^ p[j+1]
-		}
-		p[e] = f.Mul(p[e], c)
-	}
-
-	// lp = log p.
-	lp := make([]byte, e+1)
-	for i, c := range p {
-		if c == 0 {
-			lp[i] = 255
-		} else {
-			lp[i] = byte(f.Log(c))
-		}
-	}
-
-	return p, lp
-}
-
-// NewRSEncoder returns a new Reed-Solomon encoder
-// over the given field and number of error correction bytes.
-func NewRSEncoder(f *Field, c int) *RSEncoder {
-	gen, lgen := f.gen(c)
-	return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen}
-}
-
-// ECC writes to check the error correcting code bytes
-// for data using the given Reed-Solomon parameters.
-func (rs *RSEncoder) ECC(data []byte, check []byte) {
-	if len(check) < rs.c {
-		panic("gf256: invalid check byte length")
-	}
-	if rs.c == 0 {
-		return
-	}
-
-	// The check bytes are the remainder after dividing
-	// data padded with c zeros by the generator polynomial.
-
-	// p = data padded with c zeros.
-	var p []byte
-	n := len(data) + rs.c
-	if len(rs.p) >= n {
-		p = rs.p
-	} else {
-		p = make([]byte, n)
-	}
-	copy(p, data)
-	for i := len(data); i < len(p); i++ {
-		p[i] = 0
-	}
-
-	// Divide p by gen, leaving the remainder in p[len(data):].
-	// p[0] is the most significant term in p, and
-	// gen[0] is the most significant term in the generator,
-	// which is always 1.
-	// To avoid repeated work, we store various values as
-	// lv, not v, where lv = log[v].
-	f := rs.f
-	lgen := rs.lgen[1:]
-	for i := 0; i < len(data); i++ {
-		c := p[i]
-		if c == 0 {
-			continue
-		}
-		q := p[i+1:]
-		exp := f.exp[f.log[c]:]
-		for j, lg := range lgen {
-			if lg != 255 { // lgen uses 255 for log 0
-				q[j] ^= exp[lg]
-			}
-		}
-	}
-	copy(check, p[len(data):])
-	rs.p = p
-}

+ 0 - 194
github.com/SKatiyar/qr/gf256/gf256_test.go

@@ -1,194 +0,0 @@
-// Copyright 2010 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package gf256
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-)
-
-var f = NewField(0x11d, 2) // x^8 + x^4 + x^3 + x^2 + 1
-
-func TestBasic(t *testing.T) {
-	if f.Exp(0) != 1 || f.Exp(1) != 2 || f.Exp(255) != 1 {
-		panic("bad Exp")
-	}
-}
-
-func TestECC(t *testing.T) {
-	data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
-	check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
-	out := make([]byte, len(check))
-	rs := NewRSEncoder(f, len(check))
-	rs.ECC(data, out)
-	if !bytes.Equal(out, check) {
-		t.Errorf("have %x want %x", out, check)
-	}
-}
-
-func TestLinear(t *testing.T) {
-	d1 := []byte{0x00, 0x00}
-	c1 := []byte{0x00, 0x00}
-	out := make([]byte, len(c1))
-	rs := NewRSEncoder(f, len(c1))
-	if rs.ECC(d1, out); !bytes.Equal(out, c1) {
-		t.Errorf("ECBytes(%x, %d) = %x, want 0", d1, len(c1), out)
-	}
-	d2 := []byte{0x00, 0x01}
-	c2 := make([]byte, 2)
-	rs.ECC(d2, c2)
-	d3 := []byte{0x00, 0x02}
-	c3 := make([]byte, 2)
-	rs.ECC(d3, c3)
-	cx := make([]byte, 2)
-	for i := range cx {
-		cx[i] = c2[i] ^ c3[i]
-	}
-	d4 := []byte{0x00, 0x03}
-	c4 := make([]byte, 2)
-	rs.ECC(d4, c4)
-	if !bytes.Equal(cx, c4) {
-		t.Errorf("ECBytes(%x, 2) = %x\nECBytes(%x, 2) = %x\nxor = %x\nECBytes(%x, 2) = %x",
-			d2, c2, d3, c3, cx, d4, c4)
-	}
-}
-
-func TestGaussJordan(t *testing.T) {
-	rs := NewRSEncoder(f, 2)
-	m := make([][]byte, 16)
-	for i := range m {
-		m[i] = make([]byte, 4)
-		m[i][i/8] = 1 << uint(i%8)
-		rs.ECC(m[i][:2], m[i][2:])
-	}
-	if false {
-		fmt.Printf("---\n")
-		for _, row := range m {
-			fmt.Printf("%x\n", row)
-		}
-	}
-	b := []uint{0, 1, 2, 3, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27}
-	for i := 0; i < 16; i++ {
-		bi := b[i]
-		if m[i][bi/8]&(1<<(7-bi%8)) == 0 {
-			for j := i + 1; ; j++ {
-				if j >= len(m) {
-					t.Errorf("lost track for %d", bi)
-					break
-				}
-				if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
-					m[i], m[j] = m[j], m[i]
-					break
-				}
-			}
-		}
-		for j := i + 1; j < len(m); j++ {
-			if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
-				for k := range m[j] {
-					m[j][k] ^= m[i][k]
-				}
-			}
-		}
-	}
-	if false {
-		fmt.Printf("---\n")
-		for _, row := range m {
-			fmt.Printf("%x\n", row)
-		}
-	}
-	for i := 15; i >= 0; i-- {
-		bi := b[i]
-		for j := i - 1; j >= 0; j-- {
-			if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
-				for k := range m[j] {
-					m[j][k] ^= m[i][k]
-				}
-			}
-		}
-	}
-	if false {
-		fmt.Printf("---\n")
-		for _, row := range m {
-			fmt.Printf("%x", row)
-			out := make([]byte, 2)
-			if rs.ECC(row[:2], out); !bytes.Equal(out, row[2:]) {
-				fmt.Printf(" - want %x", out)
-			}
-			fmt.Printf("\n")
-		}
-	}
-}
-
-func BenchmarkECC(b *testing.B) {
-	data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
-	check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
-	out := make([]byte, len(check))
-	rs := NewRSEncoder(f, len(check))
-	for i := 0; i < b.N; i++ {
-		rs.ECC(data, out)
-	}
-	b.SetBytes(int64(len(data)))
-	if !bytes.Equal(out, check) {
-		fmt.Printf("have %#v want %#v\n", out, check)
-	}
-}
-
-func TestGen(t *testing.T) {
-	for i := 0; i < 256; i++ {
-		_, lg := f.gen(i)
-		if lg[0] != 0 {
-			t.Errorf("#%d: %x", i, lg)
-		}
-	}
-}
-
-func TestReducible(t *testing.T) {
-	var count = []int{1, 2, 3, 6, 9, 18, 30, 56, 99, 186} // oeis.org/A1037
-	for i, want := range count {
-		n := 0
-		for p := 1 << uint(i+2); p < 1<<uint(i+3); p++ {
-			if !reducible(p) {
-				n++
-			}
-		}
-		if n != want {
-			t.Errorf("#reducible(%d-bit) = %d, want %d", i+2, n, want)
-		}
-	}
-}
-
-func TestExhaustive(t *testing.T) {
-	for poly := 0x100; poly < 0x200; poly++ {
-		if reducible(poly) {
-			continue
-		}
-		α := 2
-		for !generates(α, poly) {
-			α++
-		}
-		f := NewField(poly, α)
-		for p := 0; p < 256; p++ {
-			for q := 0; q < 256; q++ {
-				fm := int(f.Mul(byte(p), byte(q)))
-				pm := mul(p, q, poly)
-				if fm != pm {
-					t.Errorf("NewField(%#x).Mul(%#x, %#x) = %#x, want %#x", poly, p, q, fm, pm)
-				}
-			}
-		}
-	}
-}
-
-func generates(α, poly int) bool {
-	x := α
-	for i := 0; i < 254; i++ {
-		if x == 1 {
-			return false
-		}
-		x = mul(x, α, poly)
-	}
-	return true
-}

+ 0 - 149
github.com/SKatiyar/qr/libqrencode/qrencode.go

@@ -1,149 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package libqrencode wraps the C libqrencode library.
-// The qr package (in this package's parent directory)
-// does not use any C wrapping.  This code is here only
-// for use during that package's tests.
-package libqrencode
-
-/*
-#cgo LDFLAGS: -lqrencode
-#include <qrencode.h>
-*/
-import "C"
-
-import (
-	"fmt"
-	"image"
-	"image/color"
-	"unsafe"
-)
-
-type Version int
-
-type Mode int
-
-const (
-	Numeric      Mode = C.QR_MODE_NUM
-	Alphanumeric Mode = C.QR_MODE_AN
-	EightBit     Mode = C.QR_MODE_8
-)
-
-type Level int
-
-const (
-	L Level = C.QR_ECLEVEL_L
-	M Level = C.QR_ECLEVEL_M
-	Q Level = C.QR_ECLEVEL_Q
-	H Level = C.QR_ECLEVEL_H
-)
-
-type Pixel int
-
-const (
-	Black Pixel = 1 << iota
-	DataECC
-	Format
-	PVersion
-	Timing
-	Alignment
-	Finder
-	NonData
-)
-
-type Code struct {
-	Version int
-	Width   int
-	Pixel   [][]Pixel
-	Scale   int
-}
-
-func (*Code) ColorModel() color.Model {
-	return color.RGBAModel
-}
-
-func (c *Code) Bounds() image.Rectangle {
-	d := (c.Width + 8) * c.Scale
-	return image.Rect(0, 0, d, d)
-}
-
-var (
-	white  color.Color = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
-	black  color.Color = color.RGBA{0x00, 0x00, 0x00, 0xFF}
-	blue   color.Color = color.RGBA{0x00, 0x00, 0x80, 0xFF}
-	red    color.Color = color.RGBA{0xFF, 0x40, 0x40, 0xFF}
-	yellow color.Color = color.RGBA{0xFF, 0xFF, 0x00, 0xFF}
-	gray   color.Color = color.RGBA{0x80, 0x80, 0x80, 0xFF}
-	green  color.Color = color.RGBA{0x22, 0x8B, 0x22, 0xFF}
-)
-
-func (c *Code) At(x, y int) color.Color {
-	x = x/c.Scale - 4
-	y = y/c.Scale - 4
-	if 0 <= x && x < c.Width && 0 <= y && y < c.Width {
-		switch p := c.Pixel[y][x]; {
-		case p&Black == 0:
-			// nothing
-		case p&DataECC != 0:
-			return black
-		case p&Format != 0:
-			return blue
-		case p&PVersion != 0:
-			return red
-		case p&Timing != 0:
-			return yellow
-		case p&Alignment != 0:
-			return gray
-		case p&Finder != 0:
-			return green
-		}
-	}
-	return white
-}
-
-type Chunk struct {
-	Mode Mode
-	Text string
-}
-
-func Encode(version Version, level Level, mode Mode, text string) (*Code, error) {
-	return EncodeChunk(version, level, Chunk{mode, text})
-}
-
-func EncodeChunk(version Version, level Level, chunk ...Chunk) (*Code, error) {
-	qi, err := C.QRinput_new2(C.int(version), C.QRecLevel(level))
-	if qi == nil {
-		return nil, fmt.Errorf("QRinput_new2: %v", err)
-	}
-	defer C.QRinput_free(qi)
-	for _, ch := range chunk {
-		data := []byte(ch.Text)
-		n, err := C.QRinput_append(qi, C.QRencodeMode(ch.Mode), C.int(len(data)), (*C.uchar)(&data[0]))
-		if n < 0 {
-			return nil, fmt.Errorf("QRinput_append %q: %v", data, err)
-		}
-	}
-
-	qc, err := C.QRcode_encodeInput(qi)
-	if qc == nil {
-		return nil, fmt.Errorf("QRinput_encodeInput: %v", err)
-	}
-
-	c := &Code{
-		Version: int(qc.version),
-		Width:   int(qc.width),
-		Scale:   16,
-	}
-	pix := make([]Pixel, c.Width*c.Width)
-	cdat := (*[1000 * 1000]byte)(unsafe.Pointer(qc.data))[:len(pix)]
-	for i := range pix {
-		pix[i] = Pixel(cdat[i])
-	}
-	c.Pixel = make([][]Pixel, c.Width)
-	for i := range c.Pixel {
-		c.Pixel[i] = pix[i*c.Width : (i+1)*c.Width]
-	}
-	return c, nil
-}

+ 0 - 400
github.com/SKatiyar/qr/png.go

@@ -1,400 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package qr
-
-// PNG writer for QR codes.
-
-import (
-	"bytes"
-	"encoding/binary"
-	"hash"
-	"hash/crc32"
-)
-
-// PNG returns a PNG image displaying the code.
-//
-// PNG uses a custom encoder tailored to QR codes.
-// Its compressed size is about 2x away from optimal,
-// but it runs about 20x faster than calling png.Encode
-// on c.Image().
-func (c *Code) PNG() []byte {
-	var p pngWriter
-	return p.encode(c)
-}
-
-type pngWriter struct {
-	tmp   [16]byte
-	wctmp [4]byte
-	buf   bytes.Buffer
-	zlib  bitWriter
-	crc   hash.Hash32
-}
-
-var pngHeader = []byte("\x89PNG\r\n\x1a\n")
-
-func (w *pngWriter) encode(c *Code) []byte {
-	scale := c.Scale
-	siz := c.Size
-
-	w.buf.Reset()
-
-	// Header
-	w.buf.Write(pngHeader)
-
-	// Header block
-	binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale))
-	binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale))
-	w.tmp[8] = 1 // 1-bit
-	w.tmp[9] = 0 // gray
-	w.tmp[10] = 0
-	w.tmp[11] = 0
-	w.tmp[12] = 0
-	w.writeChunk("IHDR", w.tmp[:13])
-
-	// Comment
-	w.writeChunk("tEXt", comment)
-
-	// Data
-	w.zlib.writeCode(c)
-	w.writeChunk("IDAT", w.zlib.bytes.Bytes())
-
-	// End
-	w.writeChunk("IEND", nil)
-
-	return w.buf.Bytes()
-}
-
-var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/")
-
-func (w *pngWriter) writeChunk(name string, data []byte) {
-	if w.crc == nil {
-		w.crc = crc32.NewIEEE()
-	}
-	binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data)))
-	w.buf.Write(w.wctmp[0:4])
-	w.crc.Reset()
-	copy(w.wctmp[0:4], name)
-	w.buf.Write(w.wctmp[0:4])
-	w.crc.Write(w.wctmp[0:4])
-	w.buf.Write(data)
-	w.crc.Write(data)
-	crc := w.crc.Sum32()
-	binary.BigEndian.PutUint32(w.wctmp[0:4], crc)
-	w.buf.Write(w.wctmp[0:4])
-}
-
-func (b *bitWriter) writeCode(c *Code) {
-	const ftNone = 0
-
-	b.adler32.Reset()
-	b.bytes.Reset()
-	b.nbit = 0
-
-	scale := c.Scale
-	siz := c.Size
-
-	// zlib header
-	b.tmp[0] = 0x78
-	b.tmp[1] = 0
-	b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31)
-	b.bytes.Write(b.tmp[0:2])
-
-	// Start flate block.
-	b.writeBits(1, 1, false) // final block
-	b.writeBits(1, 2, false) // compressed, fixed Huffman tables
-
-	// White border.
-	// First row.
-	b.byte(ftNone)
-	n := (scale*(siz+8) + 7) / 8
-	b.byte(255)
-	b.repeat(n-1, 1)
-	// 4*scale rows total.
-	b.repeat((4*scale-1)*(1+n), 1+n)
-
-	for i := 0; i < 4*scale; i++ {
-		b.adler32.WriteNByte(ftNone, 1)
-		b.adler32.WriteNByte(255, n)
-	}
-
-	row := make([]byte, 1+n)
-	for y := 0; y < siz; y++ {
-		row[0] = ftNone
-		j := 1
-		var z uint8
-		nz := 0
-		for x := -4; x < siz+4; x++ {
-			// Raw data.
-			for i := 0; i < scale; i++ {
-				z <<= 1
-				if !c.Black(x, y) {
-					z |= 1
-				}
-				if nz++; nz == 8 {
-					row[j] = z
-					j++
-					nz = 0
-				}
-			}
-		}
-		if j < len(row) {
-			row[j] = z
-		}
-		for _, z := range row {
-			b.byte(z)
-		}
-
-		// Scale-1 copies.
-		b.repeat((scale-1)*(1+n), 1+n)
-
-		b.adler32.WriteN(row, scale)
-	}
-
-	// White border.
-	// First row.
-	b.byte(ftNone)
-	b.byte(255)
-	b.repeat(n-1, 1)
-	// 4*scale rows total.
-	b.repeat((4*scale-1)*(1+n), 1+n)
-
-	for i := 0; i < 4*scale; i++ {
-		b.adler32.WriteNByte(ftNone, 1)
-		b.adler32.WriteNByte(255, n)
-	}
-
-	// End of block.
-	b.hcode(256)
-	b.flushBits()
-
-	// adler32
-	binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32())
-	b.bytes.Write(b.tmp[0:4])
-}
-
-// A bitWriter is a write buffer for bit-oriented data like deflate.
-type bitWriter struct {
-	bytes bytes.Buffer
-	bit   uint32
-	nbit  uint
-
-	tmp     [4]byte
-	adler32 adigest
-}
-
-func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) {
-	// reverse, for huffman codes
-	if rev {
-		br := uint32(0)
-		for i := uint(0); i < nbit; i++ {
-			br |= ((bit >> i) & 1) << (nbit - 1 - i)
-		}
-		bit = br
-	}
-	b.bit |= bit << b.nbit
-	b.nbit += nbit
-	for b.nbit >= 8 {
-		b.bytes.WriteByte(byte(b.bit))
-		b.bit >>= 8
-		b.nbit -= 8
-	}
-}
-
-func (b *bitWriter) flushBits() {
-	if b.nbit > 0 {
-		b.bytes.WriteByte(byte(b.bit))
-		b.nbit = 0
-		b.bit = 0
-	}
-}
-
-func (b *bitWriter) hcode(v int) {
-	/*
-	   Lit Value    Bits        Codes
-	   ---------    ----        -----
-	     0 - 143     8          00110000 through
-	                            10111111
-	   144 - 255     9          110010000 through
-	                            111111111
-	   256 - 279     7          0000000 through
-	                            0010111
-	   280 - 287     8          11000000 through
-	                            11000111
-	*/
-	switch {
-	case v <= 143:
-		b.writeBits(uint32(v)+0x30, 8, true)
-	case v <= 255:
-		b.writeBits(uint32(v-144)+0x190, 9, true)
-	case v <= 279:
-		b.writeBits(uint32(v-256)+0, 7, true)
-	case v <= 287:
-		b.writeBits(uint32(v-280)+0xc0, 8, true)
-	default:
-		panic("invalid hcode")
-	}
-}
-
-func (b *bitWriter) byte(x byte) {
-	b.hcode(int(x))
-}
-
-func (b *bitWriter) codex(c int, val int, nx uint) {
-	b.hcode(c + val>>nx)
-	b.writeBits(uint32(val)&(1<<nx-1), nx, false)
-}
-
-func (b *bitWriter) repeat(n, d int) {
-	for ; n >= 258+3; n -= 258 {
-		b.repeat1(258, d)
-	}
-	if n > 258 {
-		// 258 < n < 258+3
-		b.repeat1(10, d)
-		b.repeat1(n-10, d)
-		return
-	}
-	if n < 3 {
-		panic("invalid flate repeat")
-	}
-	b.repeat1(n, d)
-}
-
-func (b *bitWriter) repeat1(n, d int) {
-	/*
-	        Extra               Extra               Extra
-	   Code Bits Length(s) Code Bits Lengths   Code Bits Length(s)
-	   ---- ---- ------     ---- ---- -------   ---- ---- -------
-	    257   0     3       267   1   15,16     277   4   67-82
-	    258   0     4       268   1   17,18     278   4   83-98
-	    259   0     5       269   2   19-22     279   4   99-114
-	    260   0     6       270   2   23-26     280   4  115-130
-	    261   0     7       271   2   27-30     281   5  131-162
-	    262   0     8       272   2   31-34     282   5  163-194
-	    263   0     9       273   3   35-42     283   5  195-226
-	    264   0    10       274   3   43-50     284   5  227-257
-	    265   1  11,12      275   3   51-58     285   0    258
-	    266   1  13,14      276   3   59-66
-	*/
-	switch {
-	case n <= 10:
-		b.codex(257, n-3, 0)
-	case n <= 18:
-		b.codex(265, n-11, 1)
-	case n <= 34:
-		b.codex(269, n-19, 2)
-	case n <= 66:
-		b.codex(273, n-35, 3)
-	case n <= 130:
-		b.codex(277, n-67, 4)
-	case n <= 257:
-		b.codex(281, n-131, 5)
-	case n == 258:
-		b.hcode(285)
-	default:
-		panic("invalid repeat length")
-	}
-
-	/*
-	        Extra           Extra               Extra
-	   Code Bits Dist  Code Bits   Dist     Code Bits Distance
-	   ---- ---- ----  ---- ----  ------    ---- ---- --------
-	     0   0    1     10   4     33-48    20    9   1025-1536
-	     1   0    2     11   4     49-64    21    9   1537-2048
-	     2   0    3     12   5     65-96    22   10   2049-3072
-	     3   0    4     13   5     97-128   23   10   3073-4096
-	     4   1   5,6    14   6    129-192   24   11   4097-6144
-	     5   1   7,8    15   6    193-256   25   11   6145-8192
-	     6   2   9-12   16   7    257-384   26   12  8193-12288
-	     7   2  13-16   17   7    385-512   27   12 12289-16384
-	     8   3  17-24   18   8    513-768   28   13 16385-24576
-	     9   3  25-32   19   8   769-1024   29   13 24577-32768
-	*/
-	if d <= 4 {
-		b.writeBits(uint32(d-1), 5, true)
-	} else if d <= 32768 {
-		nbit := uint(16)
-		for d <= 1<<(nbit-1) {
-			nbit--
-		}
-		v := uint32(d - 1)
-		v &^= 1 << (nbit - 1)      // top bit is implicit
-		code := uint32(2*nbit - 2) // second bit is low bit of code
-		code |= v >> (nbit - 2)
-		v &^= 1 << (nbit - 2)
-		b.writeBits(code, 5, true)
-		// rest of bits follow
-		b.writeBits(uint32(v), nbit-2, false)
-	} else {
-		panic("invalid repeat distance")
-	}
-}
-
-func (b *bitWriter) run(v byte, n int) {
-	if n == 0 {
-		return
-	}
-	b.byte(v)
-	if n-1 < 3 {
-		for i := 0; i < n-1; i++ {
-			b.byte(v)
-		}
-	} else {
-		b.repeat(n-1, 1)
-	}
-}
-
-type adigest struct {
-	a, b uint32
-}
-
-func (d *adigest) Reset() { d.a, d.b = 1, 0 }
-
-const amod = 65521
-
-func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) {
-	// TODO(rsc): 6g doesn't do magic multiplies for b %= amod,
-	// only for b = b%amod.
-
-	// invariant: a, b < amod
-	if pi == 0 {
-		b += uint32(n%amod) * a
-		b = b % amod
-		return a, b
-	}
-
-	// n times:
-	//	a += pi
-	//	b += a
-	// is same as
-	//	b += n*a + n*(n+1)/2*pi
-	//	a += n*pi
-	m := uint32(n)
-	b += (m % amod) * a
-	b = b % amod
-	b += (m * (m + 1) / 2) % amod * uint32(pi)
-	b = b % amod
-	a += (m % amod) * uint32(pi)
-	a = a % amod
-	return a, b
-}
-
-func afinish(a, b uint32) uint32 {
-	return b<<16 | a
-}
-
-func (d *adigest) WriteN(p []byte, n int) {
-	for i := 0; i < n; i++ {
-		for _, pi := range p {
-			d.a, d.b = aupdate(d.a, d.b, pi, 1)
-		}
-	}
-}
-
-func (d *adigest) WriteNByte(pi byte, n int) {
-	d.a, d.b = aupdate(d.a, d.b, pi, n)
-}
-
-func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) }

+ 0 - 56
github.com/SKatiyar/qr/png_test.go

@@ -1,56 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package qr
-
-import (
-	"bytes"
-	"image/png"
-	"io/ioutil"
-	"os"
-	"testing"
-)
-
-func Test2File(t *testing.T) {
-	r, _ := Encode("http://www.baidu.com", L)
-	fi, _ := os.OpenFile("C:\\Users\\admin\\Desktop\\2.png", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0x666)
-	fi.Write(r.PNG())
-}
-
-func TestPNG(t *testing.T) {
-	c, err := Encode("http://www.baidu.com", H)
-	if err != nil {
-		t.Fatal(err)
-	}
-	pngdat := c.PNG()
-	if true {
-		ioutil.WriteFile("x.png", pngdat, 0666)
-	}
-
-}
-
-func BenchmarkPNG(b *testing.B) {
-	c, err := Encode("0123456789012345678901234567890123456789", L)
-	if err != nil {
-		panic(err)
-	}
-	var bytes []byte
-	for i := 0; i < b.N; i++ {
-		bytes = c.PNG()
-	}
-	b.SetBytes(int64(len(bytes)))
-}
-
-func BenchmarkImagePNG(b *testing.B) {
-	c, err := Encode("0123456789012345678901234567890123456789", L)
-	if err != nil {
-		panic(err)
-	}
-	var buf bytes.Buffer
-	for i := 0; i < b.N; i++ {
-		buf.Reset()
-		png.Encode(&buf, c.Image())
-	}
-	b.SetBytes(int64(buf.Len()))
-}

+ 0 - 116
github.com/SKatiyar/qr/qr.go

@@ -1,116 +0,0 @@
-// Copyright 2011 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/*
-Package qr encodes QR codes.
-*/
-package qr
-
-import (
-	"errors"
-	"image"
-	"image/color"
-
-	"github.com/SKatiyar/qr/coding"
-)
-
-// A Level denotes a QR error correction level.
-// From least to most tolerant of errors, they are L, M, Q, H.
-type Level int
-
-const (
-	L Level = iota // 20% redundant
-	M              // 38% redundant
-	Q              // 55% redundant
-	H              // 65% redundant
-)
-
-// Encode returns an encoding of text at the given error correction level.
-func Encode(text string, level Level) (*Code, error) {
-	// Pick data encoding, smallest first.
-	// We could split the string and use different encodings
-	// but that seems like overkill for now.
-	var enc coding.Encoding
-	switch {
-	case coding.Num(text).Check() == nil:
-		enc = coding.Num(text)
-	case coding.Alpha(text).Check() == nil:
-		enc = coding.Alpha(text)
-	default:
-		enc = coding.String(text)
-	}
-
-	// Pick size.
-	l := coding.Level(level)
-	var v coding.Version
-	for v = coding.MinVersion; ; v++ {
-		if v > coding.MaxVersion {
-			return nil, errors.New("text too long to encode as QR")
-		}
-		if enc.Bits(v) <= v.DataBytes(l)*8 {
-			break
-		}
-	}
-
-	// Build and execute plan.
-	p, err := coding.NewPlan(v, l, 0)
-	if err != nil {
-		return nil, err
-	}
-	cc, err := p.Encode(enc)
-	if err != nil {
-		return nil, err
-	}
-
-	// TODO: Pick appropriate mask.
-
-	return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil
-}
-
-// A Code is a square pixel grid.
-// It implements image.Image and direct PNG encoding.
-type Code struct {
-	Bitmap []byte // 1 is black, 0 is white
-	Size   int    // number of pixels on a side
-	Stride int    // number of bytes per row
-	Scale  int    // number of image pixels per QR pixel
-}
-
-// Black returns true if the pixel at (x,y) is black.
-func (c *Code) Black(x, y int) bool {
-	return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
-		c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
-}
-
-// Image returns an Image displaying the code.
-func (c *Code) Image() image.Image {
-	return &codeImage{c}
-
-}
-
-// codeImage implements image.Image
-type codeImage struct {
-	*Code
-}
-
-var (
-	whiteColor color.Color = color.Gray{0xFF}
-	blackColor color.Color = color.Gray{0x00}
-)
-
-func (c *codeImage) Bounds() image.Rectangle {
-	d := (c.Size + 8) * c.Scale
-	return image.Rect(0, 0, d, d)
-}
-
-func (c *codeImage) At(x, y int) color.Color {
-	if c.Black(x, y) {
-		return blackColor
-	}
-	return whiteColor
-}
-
-func (c *codeImage) ColorModel() color.Model {
-	return color.GrayModel
-}

+ 0 - 22
github.com/cron/.gitignore

@@ -1,22 +0,0 @@
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe

+ 0 - 1
github.com/cron/.travis.yml

@@ -1 +0,0 @@
-language: go

+ 0 - 21
github.com/cron/LICENSE

@@ -1,21 +0,0 @@
-Copyright (C) 2012 Rob Figueiredo
-All Rights Reserved.
-
-MIT LICENSE
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 0 - 2
github.com/cron/README.md

@@ -1,2 +0,0 @@
-[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) 
-[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron)

+ 0 - 27
github.com/cron/constantdelay.go

@@ -1,27 +0,0 @@
-package cron
-
-import "time"
-
-// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
-// It does not support jobs more frequent than once a second.
-type ConstantDelaySchedule struct {
-	Delay time.Duration
-}
-
-// Every returns a crontab Schedule that activates once every duration.
-// Delays of less than a second are not supported (will round up to 1 second).
-// Any fields less than a Second are truncated.
-func Every(duration time.Duration) ConstantDelaySchedule {
-	if duration < time.Second {
-		duration = time.Second
-	}
-	return ConstantDelaySchedule{
-		Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
-	}
-}
-
-// Next returns the next time this should be run.
-// This rounds so that the next activation time will be on the second.
-func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
-	return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
-}

+ 0 - 54
github.com/cron/constantdelay_test.go

@@ -1,54 +0,0 @@
-package cron
-
-import (
-	"testing"
-	"time"
-)
-
-func TestConstantDelayNext(t *testing.T) {
-	tests := []struct {
-		time     string
-		delay    time.Duration
-		expected string
-	}{
-		// Simple cases
-		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
-		{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
-
-		// Wrap around hours
-		{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
-
-		// Wrap around days
-		{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
-		{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
-		{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
-
-		// Wrap around months
-		{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
-
-		// Wrap around minute, hour, day, month, and year
-		{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
-
-		// Round to nearest second on the delay
-		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-
-		// Round up to 1 second if the duration is less.
-		{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
-
-		// Round to nearest second when calculating the next time.
-		{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
-
-		// Round to nearest second for both.
-		{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-	}
-
-	for _, c := range tests {
-		actual := Every(c.delay).Next(getTime(c.time))
-		expected := getTime(c.expected)
-		if actual != expected {
-			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
-		}
-	}
-}

+ 0 - 227
github.com/cron/cron.go

@@ -1,227 +0,0 @@
-// This library implements a cron spec parser and runner.  See the README for
-// more details.
-package cron
-
-import (
-	"log"
-	"runtime"
-	"sort"
-	"time"
-)
-
-// Cron keeps track of any number of entries, invoking the associated func as
-// specified by the schedule. It may be started, stopped, and the entries may
-// be inspected while running.
-type Cron struct {
-	entries  []*Entry
-	stop     chan struct{}
-	add      chan *Entry
-	snapshot chan []*Entry
-	running  bool
-	ErrorLog *log.Logger
-}
-
-// Job is an interface for submitted cron jobs.
-type Job interface {
-	Run()
-}
-
-// The Schedule describes a job's duty cycle.
-type Schedule interface {
-	// Return the next activation time, later than the given time.
-	// Next is invoked initially, and then each time the job is run.
-	Next(time.Time) time.Time
-}
-
-// Entry consists of a schedule and the func to execute on that schedule.
-type Entry struct {
-	// The schedule on which this job should be run.
-	Schedule Schedule
-
-	// The next time the job will run. This is the zero time if Cron has not been
-	// started or this entry's schedule is unsatisfiable
-	Next time.Time
-
-	// The last time this job was run. This is the zero time if the job has never
-	// been run.
-	Prev time.Time
-
-	// The Job to run.
-	Job Job
-}
-
-// byTime is a wrapper for sorting the entry array by time
-// (with zero time at the end).
-type byTime []*Entry
-
-func (s byTime) Len() int      { return len(s) }
-func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s byTime) Less(i, j int) bool {
-	// Two zero times should return false.
-	// Otherwise, zero is "greater" than any other time.
-	// (To sort it at the end of the list.)
-	if s[i].Next.IsZero() {
-		return false
-	}
-	if s[j].Next.IsZero() {
-		return true
-	}
-	return s[i].Next.Before(s[j].Next)
-}
-
-// New returns a new Cron job runner.
-func New() *Cron {
-	return &Cron{
-		entries:  nil,
-		add:      make(chan *Entry),
-		stop:     make(chan struct{}),
-		snapshot: make(chan []*Entry),
-		running:  false,
-		ErrorLog: nil,
-	}
-}
-
-// A wrapper that turns a func() into a cron.Job
-type FuncJob func()
-
-func (f FuncJob) Run() { f() }
-
-// AddFunc adds a func to the Cron to be run on the given schedule.
-func (c *Cron) AddFunc(spec string, cmd func()) error {
-	return c.AddJob(spec, FuncJob(cmd))
-}
-
-// AddJob adds a Job to the Cron to be run on the given schedule.
-func (c *Cron) AddJob(spec string, cmd Job) error {
-	schedule, err := Parse(spec)
-	if err != nil {
-		return err
-	}
-	c.Schedule(schedule, cmd)
-	return nil
-}
-
-// Schedule adds a Job to the Cron to be run on the given schedule.
-func (c *Cron) Schedule(schedule Schedule, cmd Job) {
-	entry := &Entry{
-		Schedule: schedule,
-		Job:      cmd,
-	}
-	if !c.running {
-		c.entries = append(c.entries, entry)
-		return
-	}
-
-	c.add <- entry
-}
-
-// Entries returns a snapshot of the cron entries.
-func (c *Cron) Entries() []*Entry {
-	if c.running {
-		c.snapshot <- nil
-		x := <-c.snapshot
-		return x
-	}
-	return c.entrySnapshot()
-}
-
-// Start the cron scheduler in its own go-routine.
-func (c *Cron) Start() {
-	c.running = true
-	go c.run()
-}
-
-func (c *Cron) runWithRecovery(j Job) {
-	defer func() {
-		if r := recover(); r != nil {
-			const size = 64 << 10
-			buf := make([]byte, size)
-			buf = buf[:runtime.Stack(buf, false)]
-			c.logf("cron: panic running job: %v\n%s", r, buf)
-		}
-	}()
-	j.Run()
-}
-
-// Run the scheduler.. this is private just due to the need to synchronize
-// access to the 'running' state variable.
-func (c *Cron) run() {
-	// Figure out the next activation times for each entry.
-	now := time.Now().Local()
-	for _, entry := range c.entries {
-		entry.Next = entry.Schedule.Next(now)
-	}
-
-	for {
-		// Determine the next entry to run.
-		sort.Sort(byTime(c.entries))
-
-		var effective time.Time
-		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
-			// If there are no entries yet, just sleep - it still handles new entries
-			// and stop requests.
-			effective = now.AddDate(10, 0, 0)
-		} else {
-			effective = c.entries[0].Next
-		}
-
-		select {
-		case now = <-time.After(effective.Sub(now)):
-			// Run every entry whose next time was this effective time.
-			for _, e := range c.entries {
-				if e.Next != effective {
-					break
-				}
-				go c.runWithRecovery(e.Job)
-				e.Prev = e.Next
-				e.Next = e.Schedule.Next(now)
-			}
-			continue
-
-		case newEntry := <-c.add:
-			c.entries = append(c.entries, newEntry)
-			newEntry.Next = newEntry.Schedule.Next(time.Now().Local())
-
-		case <-c.snapshot:
-			c.snapshot <- c.entrySnapshot()
-
-		case <-c.stop:
-			return
-		}
-
-		// 'now' should be updated after newEntry and snapshot cases.
-		now = time.Now().Local()
-	}
-}
-
-// Logs an error to stderr or to the configured error log
-func (c *Cron) logf(format string, args ...interface{}) {
-	if c.ErrorLog != nil {
-		c.ErrorLog.Printf(format, args...)
-	} else {
-		log.Printf(format, args...)
-	}
-}
-
-// Stop stops the cron scheduler if it is running; otherwise it does nothing.
-func (c *Cron) Stop() {
-	if !c.running {
-		return
-	}
-	c.stop <- struct{}{}
-	c.running = false
-}
-
-// entrySnapshot returns a copy of the current cron entry list.
-func (c *Cron) entrySnapshot() []*Entry {
-	entries := []*Entry{}
-	for _, e := range c.entries {
-		entries = append(entries, &Entry{
-			Schedule: e.Schedule,
-			Next:     e.Next,
-			Prev:     e.Prev,
-			Job:      e.Job,
-		})
-	}
-	return entries
-}

+ 0 - 310
github.com/cron/cron_test.go

@@ -1,310 +0,0 @@
-package cron
-
-import (
-	"fmt"
-	"sync"
-	"testing"
-	"time"
-)
-
-// Many tests schedule a job for every second, and then wait at most a second
-// for it to run.  This amount is just slightly larger than 1 second to
-// compensate for a few milliseconds of runtime.
-const ONE_SECOND = 1*time.Second + 10*time.Millisecond
-
-func TestFuncPanicRecovery(t *testing.T) {
-	cron := New()
-	cron.Start()
-	defer cron.Stop()
-	cron.AddFunc("* * * * * ?", func() { panic("YOLO") })
-
-	select {
-	case <-time.After(ONE_SECOND):
-		return
-	}
-}
-
-type DummyJob struct{}
-
-func (d DummyJob) Run() {
-	panic("YOLO")
-}
-
-func TestJobPanicRecovery(t *testing.T) {
-	var job DummyJob
-
-	cron := New()
-	cron.Start()
-	defer cron.Stop()
-	cron.AddJob("* * * * * ?", job)
-
-	select {
-	case <-time.After(ONE_SECOND):
-		return
-	}
-}
-
-// Start and stop cron with no entries.
-func TestNoEntries(t *testing.T) {
-	cron := New()
-	cron.Start()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-stop(cron):
-	}
-}
-
-// Start, stop, then add an entry. Verify entry doesn't run.
-func TestStopCausesJobsToNotRun(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.Start()
-	cron.Stop()
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-
-	select {
-	case <-time.After(ONE_SECOND):
-		// No job ran!
-	case <-wait(wg):
-		t.FailNow()
-	}
-}
-
-// Add a job, start cron, expect it runs.
-func TestAddBeforeRunning(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddFunc("* * * * * ?", func() { wg.Done(); fmt.Println("ss") })
-	cron.Start()
-	defer cron.Stop()
-
-	// Give cron 2 seconds to run our job (which is always activated).
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Start cron, add a job, expect it runs.
-func TestAddWhileRunning(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.Start()
-	defer cron.Stop()
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test for #34. Adding a job after calling start results in multiple job invocations
-func TestAddWhileRunningWithDelay(t *testing.T) {
-	cron := New()
-	cron.Start()
-	defer cron.Stop()
-	time.Sleep(5 * time.Second)
-	var calls = 0
-	cron.AddFunc("* * * * * *", func() { calls += 1 })
-
-	<-time.After(ONE_SECOND)
-	if calls != 1 {
-		fmt.Printf("called %d times, expected 1\n", calls)
-		t.Fail()
-	}
-}
-
-// Test timing with Entries.
-func TestSnapshotEntries(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddFunc("@every 2s", func() { wg.Done() })
-	cron.Start()
-	defer cron.Stop()
-
-	// Cron should fire in 2 seconds. After 1 second, call Entries.
-	select {
-	case <-time.After(ONE_SECOND):
-		cron.Entries()
-	}
-
-	// Even though Entries was called, the cron should fire at the 2 second mark.
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-
-}
-
-// Test that the entries are correctly sorted.
-// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
-// that the immediate entry runs immediately.
-// Also: Test that multiple jobs run in the same instant.
-func TestMultipleEntries(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("0 0 0 1 1 ?", func() {})
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-	cron.AddFunc("0 0 0 31 12 ?", func() {})
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test running the same job twice.
-func TestRunningJobTwice(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("0 0 0 1 1 ?", func() {})
-	cron.AddFunc("0 0 0 31 12 ?", func() {})
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(2 * ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-func TestRunningMultipleSchedules(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("0 0 0 1 1 ?", func() {})
-	cron.AddFunc("0 0 0 31 12 ?", func() {})
-	cron.AddFunc("* * * * * ?", func() { wg.Done() })
-	cron.Schedule(Every(time.Minute), FuncJob(func() {}))
-	cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
-	cron.Schedule(Every(time.Hour), FuncJob(func() {}))
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(2 * ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test that the cron is run in the local time zone (as opposed to UTC).
-func TestLocalTimezone(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	now := time.Now().Local()
-	spec := fmt.Sprintf("%d %d %d %d %d ?",
-		now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
-
-	cron := New()
-	cron.AddFunc(spec, func() { wg.Done() })
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test that calling stop before start silently returns without
-// blocking the stop channel.
-func TestStopWithoutStart(t *testing.T) {
-	cron := New()
-	cron.Stop()
-}
-
-type testJob struct {
-	wg   *sync.WaitGroup
-	name string
-}
-
-func (t testJob) Run() {
-	t.wg.Done()
-}
-
-// Simple test using Runnables.
-func TestJob(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
-	cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
-	cron.AddJob("* * * * * ?", testJob{wg, "job2"})
-	cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
-	cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
-	cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-
-	// Ensure the entries are in the right order.
-	expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
-
-	var actuals []string
-	for _, entry := range cron.Entries() {
-		actuals = append(actuals, entry.Job.(testJob).name)
-	}
-
-	for i, expected := range expecteds {
-		if actuals[i] != expected {
-			t.Errorf("Jobs not in the right order.  (expected) %s != %s (actual)", expecteds, actuals)
-			t.FailNow()
-		}
-	}
-}
-
-func wait(wg *sync.WaitGroup) chan bool {
-	ch := make(chan bool)
-	go func() {
-		wg.Wait()
-		ch <- true
-	}()
-	return ch
-}
-
-func stop(cron *Cron) chan bool {
-	ch := make(chan bool)
-	go func() {
-		cron.Stop()
-		ch <- true
-	}()
-	return ch
-}

+ 0 - 129
github.com/cron/doc.go

@@ -1,129 +0,0 @@
-/*
-Package cron implements a cron spec parser and job runner.
-
-Usage
-
-Callers may register Funcs to be invoked on a given schedule.  Cron will run
-them in their own goroutines.
-
-	c := cron.New()
-	c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
-	c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
-	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
-	c.Start()
-	..
-	// Funcs are invoked in their own goroutine, asynchronously.
-	...
-	// Funcs may also be added to a running Cron
-	c.AddFunc("@daily", func() { fmt.Println("Every day") })
-	..
-	// Inspect the cron job entries' next and previous run times.
-	inspect(c.Entries())
-	..
-	c.Stop()  // Stop the scheduler (does not stop any jobs already running).
-
-CRON Expression Format
-
-A cron expression represents a set of times, using 6 space-separated fields.
-
-	Field name   | Mandatory? | Allowed values  | Allowed special characters
-	----------   | ---------- | --------------  | --------------------------
-	Seconds      | Yes        | 0-59            | * / , -
-	Minutes      | Yes        | 0-59            | * / , -
-	Hours        | Yes        | 0-23            | * / , -
-	Day of month | Yes        | 1-31            | * / , - ?
-	Month        | Yes        | 1-12 or JAN-DEC | * / , -
-	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?
-
-Note: Month and Day-of-week field values are case insensitive.  "SUN", "Sun",
-and "sun" are equally accepted.
-
-Special Characters
-
-Asterisk ( * )
-
-The asterisk indicates that the cron expression will match for all values of the
-field; e.g., using an asterisk in the 5th field (month) would indicate every
-month.
-
-Slash ( / )
-
-Slashes are used to describe increments of ranges. For example 3-59/15 in the
-1st field (minutes) would indicate the 3rd minute of the hour and every 15
-minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
-that is, an increment over the largest possible range of the field.  The form
-"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
-increment until the end of that specific range.  It does not wrap around.
-
-Comma ( , )
-
-Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
-the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
-
-Hyphen ( - )
-
-Hyphens are used to define ranges. For example, 9-17 would indicate every
-hour between 9am and 5pm inclusive.
-
-Question mark ( ? )
-
-Question mark may be used instead of '*' for leaving either day-of-month or
-day-of-week blank.
-
-Predefined schedules
-
-You may use one of several pre-defined schedules in place of a cron expression.
-
-	Entry                  | Description                                | Equivalent To
-	-----                  | -----------                                | -------------
-	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *
-	@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *
-	@weekly                | Run once a week, midnight on Sunday        | 0 0 0 * * 0
-	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *
-	@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *
-
-Intervals
-
-You may also schedule a job to execute at fixed intervals.  This is supported by
-formatting the cron spec like this:
-
-    @every <duration>
-
-where "duration" is a string accepted by time.ParseDuration
-(http://golang.org/pkg/time/#ParseDuration).
-
-For example, "@every 1h30m10s" would indicate a schedule that activates every
-1 hour, 30 minutes, 10 seconds.
-
-Note: The interval does not take the job runtime into account.  For example,
-if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
-it will have only 2 minutes of idle time between each run.
-
-Time zones
-
-All interpretation and scheduling is done in the machine's local time zone (as
-provided by the Go time package (http://www.golang.org/pkg/time).
-
-Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
-not be run!
-
-Thread safety
-
-Since the Cron service runs concurrently with the calling code, some amount of
-care must be taken to ensure proper synchronization.
-
-All cron methods are designed to be correctly synchronized as long as the caller
-ensures that invocations have a clear happens-before ordering between them.
-
-Implementation
-
-Cron entries are stored in an array, sorted by their next activation time.  Cron
-sleeps until the next job is due to be run.
-
-Upon waking:
- - it runs each entry that is active on that second
- - it calculates the next run times for the jobs that were run
- - it re-sorts the array of entries by next activation time.
- - it goes to sleep until the soonest job.
-*/
-package cron

+ 0 - 231
github.com/cron/parser.go

@@ -1,231 +0,0 @@
-package cron
-
-import (
-	"fmt"
-	"log"
-	"math"
-	"strconv"
-	"strings"
-	"time"
-)
-
-// Parse returns a new crontab schedule representing the given spec.
-// It returns a descriptive error if the spec is not valid.
-//
-// It accepts
-//   - Full crontab specs, e.g. "* * * * * ?"
-//   - Descriptors, e.g. "@midnight", "@every 1h30m"
-func Parse(spec string) (_ Schedule, err error) {
-	// Convert panics into errors
-	defer func() {
-		if recovered := recover(); recovered != nil {
-			err = fmt.Errorf("%v", recovered)
-		}
-	}()
-
-	if spec[0] == '@' {
-		return parseDescriptor(spec), nil
-	}
-
-	// Split on whitespace.  We require 5 or 6 fields.
-	// (second) (minute) (hour) (day of month) (month) (day of week, optional)
-	fields := strings.Fields(spec)
-	if len(fields) != 5 && len(fields) != 6 {
-		log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
-	}
-
-	// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
-	if len(fields) == 5 {
-		fields = append(fields, "*")
-	}
-
-	schedule := &SpecSchedule{
-		Second: getField(fields[0], seconds),
-		Minute: getField(fields[1], minutes),
-		Hour:   getField(fields[2], hours),
-		Dom:    getField(fields[3], dom),
-		Month:  getField(fields[4], months),
-		Dow:    getField(fields[5], dow),
-	}
-
-	return schedule, nil
-}
-
-// getField returns an Int with the bits set representing all of the times that
-// the field represents.  A "field" is a comma-separated list of "ranges".
-func getField(field string, r bounds) uint64 {
-	// list = range {"," range}
-	var bits uint64
-	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
-	for _, expr := range ranges {
-		bits |= getRange(expr, r)
-	}
-	return bits
-}
-
-// getRange returns the bits indicated by the given expression:
-//   number | number "-" number [ "/" number ]
-func getRange(expr string, r bounds) uint64 {
-
-	var (
-		start, end, step uint
-		rangeAndStep     = strings.Split(expr, "/")
-		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
-		singleDigit      = len(lowAndHigh) == 1
-	)
-
-	var extra_star uint64
-	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
-		start = r.min
-		end = r.max
-		extra_star = starBit
-	} else {
-		start = parseIntOrName(lowAndHigh[0], r.names)
-		switch len(lowAndHigh) {
-		case 1:
-			end = start
-		case 2:
-			end = parseIntOrName(lowAndHigh[1], r.names)
-		default:
-			log.Panicf("Too many hyphens: %s", expr)
-		}
-	}
-
-	switch len(rangeAndStep) {
-	case 1:
-		step = 1
-	case 2:
-		step = mustParseInt(rangeAndStep[1])
-
-		// Special handling: "N/step" means "N-max/step".
-		if singleDigit {
-			end = r.max
-		}
-	default:
-		log.Panicf("Too many slashes: %s", expr)
-	}
-
-	if start < r.min {
-		log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
-	}
-	if end > r.max {
-		log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
-	}
-	if start > end {
-		log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
-	}
-
-	return getBits(start, end, step) | extra_star
-}
-
-// parseIntOrName returns the (possibly-named) integer contained in expr.
-func parseIntOrName(expr string, names map[string]uint) uint {
-	if names != nil {
-		if namedInt, ok := names[strings.ToLower(expr)]; ok {
-			return namedInt
-		}
-	}
-	return mustParseInt(expr)
-}
-
-// mustParseInt parses the given expression as an int or panics.
-func mustParseInt(expr string) uint {
-	num, err := strconv.Atoi(expr)
-	if err != nil {
-		log.Panicf("Failed to parse int from %s: %s", expr, err)
-	}
-	if num < 0 {
-		log.Panicf("Negative number (%d) not allowed: %s", num, expr)
-	}
-
-	return uint(num)
-}
-
-// getBits sets all bits in the range [min, max], modulo the given step size.
-func getBits(min, max, step uint) uint64 {
-	var bits uint64
-
-	// If step is 1, use shifts.
-	if step == 1 {
-		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
-	}
-
-	// Else, use a simple loop.
-	for i := min; i <= max; i += step {
-		bits |= 1 << i
-	}
-	return bits
-}
-
-// all returns all bits within the given bounds.  (plus the star bit)
-func all(r bounds) uint64 {
-	return getBits(r.min, r.max, 1) | starBit
-}
-
-// parseDescriptor returns a pre-defined schedule for the expression, or panics
-// if none matches.
-func parseDescriptor(spec string) Schedule {
-	switch spec {
-	case "@yearly", "@annually":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    1 << dom.min,
-			Month:  1 << months.min,
-			Dow:    all(dow),
-		}
-
-	case "@monthly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    1 << dom.min,
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-
-	case "@weekly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    1 << dow.min,
-		}
-
-	case "@daily", "@midnight":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-
-	case "@hourly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   all(hours),
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-	}
-
-	const every = "@every "
-	if strings.HasPrefix(spec, every) {
-		duration, err := time.ParseDuration(spec[len(every):])
-		if err != nil {
-			log.Panicf("Failed to parse duration %s: %s", spec, err)
-		}
-		return Every(duration)
-	}
-
-	log.Panicf("Unrecognized descriptor: %s", spec)
-	return nil
-}

+ 0 - 117
github.com/cron/parser_test.go

@@ -1,117 +0,0 @@
-package cron
-
-import (
-	"reflect"
-	"testing"
-	"time"
-)
-
-func TestRange(t *testing.T) {
-	ranges := []struct {
-		expr     string
-		min, max uint
-		expected uint64
-	}{
-		{"5", 0, 7, 1 << 5},
-		{"0", 0, 7, 1 << 0},
-		{"7", 0, 7, 1 << 7},
-
-		{"5-5", 0, 7, 1 << 5},
-		{"5-6", 0, 7, 1<<5 | 1<<6},
-		{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7},
-
-		{"5-6/2", 0, 7, 1 << 5},
-		{"5-7/2", 0, 7, 1<<5 | 1<<7},
-		{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7},
-
-		{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit},
-		{"*/2", 1, 3, 1<<1 | 1<<3 | starBit},
-	}
-
-	for _, c := range ranges {
-		actual := getRange(c.expr, bounds{c.min, c.max, nil})
-		if actual != c.expected {
-			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
-		}
-	}
-}
-
-func TestField(t *testing.T) {
-	fields := []struct {
-		expr     string
-		min, max uint
-		expected uint64
-	}{
-		{"5", 1, 7, 1 << 5},
-		{"5,6", 1, 7, 1<<5 | 1<<6},
-		{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
-		{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
-	}
-
-	for _, c := range fields {
-		actual := getField(c.expr, bounds{c.min, c.max, nil})
-		if actual != c.expected {
-			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
-		}
-	}
-}
-
-func TestBits(t *testing.T) {
-	allBits := []struct {
-		r        bounds
-		expected uint64
-	}{
-		{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
-		{hours, 0xffffff},            // 0-23: 24 ones
-		{dom, 0xfffffffe},            // 1-31: 31 ones, 1 zero
-		{months, 0x1ffe},             // 1-12: 12 ones, 1 zero
-		{dow, 0x7f},                  // 0-6: 7 ones
-	}
-
-	for _, c := range allBits {
-		actual := all(c.r) // all() adds the starBit, so compensate for that..
-		if c.expected|starBit != actual {
-			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
-				c.r.min, c.r.max, 1, c.expected|starBit, actual)
-		}
-	}
-
-	bits := []struct {
-		min, max, step uint
-		expected       uint64
-	}{
-
-		{0, 0, 1, 0x1},
-		{1, 1, 1, 0x2},
-		{1, 5, 2, 0x2a}, // 101010
-		{1, 4, 2, 0xa},  // 1010
-	}
-
-	for _, c := range bits {
-		actual := getBits(c.min, c.max, c.step)
-		if c.expected != actual {
-			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
-				c.min, c.max, c.step, c.expected, actual)
-		}
-	}
-}
-
-func TestSpecSchedule(t *testing.T) {
-	entries := []struct {
-		expr     string
-		expected Schedule
-	}{
-		{"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}},
-		{"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}},
-	}
-
-	for _, c := range entries {
-		actual, err := Parse(c.expr)
-		if err != nil {
-			t.Error(err)
-		}
-		if !reflect.DeepEqual(actual, c.expected) {
-			t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
-		}
-	}
-}

+ 0 - 159
github.com/cron/spec.go

@@ -1,159 +0,0 @@
-package cron
-
-import "time"
-
-// SpecSchedule specifies a duty cycle (to the second granularity), based on a
-// traditional crontab specification. It is computed initially and stored as bit sets.
-type SpecSchedule struct {
-	Second, Minute, Hour, Dom, Month, Dow uint64
-}
-
-// bounds provides a range of acceptable values (plus a map of name to value).
-type bounds struct {
-	min, max uint
-	names    map[string]uint
-}
-
-// The bounds for each field.
-var (
-	seconds = bounds{0, 59, nil}
-	minutes = bounds{0, 59, nil}
-	hours   = bounds{0, 23, nil}
-	dom     = bounds{1, 31, nil}
-	months  = bounds{1, 12, map[string]uint{
-		"jan": 1,
-		"feb": 2,
-		"mar": 3,
-		"apr": 4,
-		"may": 5,
-		"jun": 6,
-		"jul": 7,
-		"aug": 8,
-		"sep": 9,
-		"oct": 10,
-		"nov": 11,
-		"dec": 12,
-	}}
-	dow = bounds{0, 6, map[string]uint{
-		"sun": 0,
-		"mon": 1,
-		"tue": 2,
-		"wed": 3,
-		"thu": 4,
-		"fri": 5,
-		"sat": 6,
-	}}
-)
-
-const (
-	// Set the top bit if a star was included in the expression.
-	starBit = 1 << 63
-)
-
-// Next returns the next time this schedule is activated, greater than the given
-// time.  If no time can be found to satisfy the schedule, return the zero time.
-func (s *SpecSchedule) Next(t time.Time) time.Time {
-	// General approach:
-	// For Month, Day, Hour, Minute, Second:
-	// Check if the time value matches.  If yes, continue to the next field.
-	// If the field doesn't match the schedule, then increment the field until it matches.
-	// While incrementing the field, a wrap-around brings it back to the beginning
-	// of the field list (since it is necessary to re-verify previous field
-	// values)
-
-	// Start at the earliest possible time (the upcoming second).
-	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
-
-	// This flag indicates whether a field has been incremented.
-	added := false
-
-	// If no time is found within five years, return zero.
-	yearLimit := t.Year() + 5
-
-WRAP:
-	if t.Year() > yearLimit {
-		return time.Time{}
-	}
-
-	// Find the first applicable month.
-	// If it's this month, then do nothing.
-	for 1<<uint(t.Month())&s.Month == 0 {
-		// If we have to add a month, reset the other parts to 0.
-		if !added {
-			added = true
-			// Otherwise, set the date at the beginning (since the current time is irrelevant).
-			t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
-		}
-		t = t.AddDate(0, 1, 0)
-
-		// Wrapped around.
-		if t.Month() == time.January {
-			goto WRAP
-		}
-	}
-
-	// Now get a day in that month.
-	for !dayMatches(s, t) {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
-		}
-		t = t.AddDate(0, 0, 1)
-
-		if t.Day() == 1 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Hour())&s.Hour == 0 {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
-		}
-		t = t.Add(1 * time.Hour)
-
-		if t.Hour() == 0 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Minute())&s.Minute == 0 {
-		if !added {
-			added = true
-			t = t.Truncate(time.Minute)
-		}
-		t = t.Add(1 * time.Minute)
-
-		if t.Minute() == 0 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Second())&s.Second == 0 {
-		if !added {
-			added = true
-			t = t.Truncate(time.Second)
-		}
-		t = t.Add(1 * time.Second)
-
-		if t.Second() == 0 {
-			goto WRAP
-		}
-	}
-
-	return t
-}
-
-// dayMatches returns true if the schedule's day-of-week and day-of-month
-// restrictions are satisfied by the given time.
-func dayMatches(s *SpecSchedule, t time.Time) bool {
-	var (
-		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
-		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
-	)
-
-	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
-		return domMatch && dowMatch
-	}
-	return domMatch || dowMatch
-}

+ 0 - 249
github.com/cron/spec_test.go

@@ -1,249 +0,0 @@
-package cron
-
-import (
-	"testing"
-	"time"
-)
-
-func TestActivation(t *testing.T) {
-	tests := []struct {
-		time, spec string
-		expected   bool
-	}{
-		// Every fifteen minutes.
-		{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true},
-		{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true},
-		{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false},
-
-		// Every fifteen minutes, starting at 5 minutes.
-		{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true},
-		{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true},
-		{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true},
-
-		// Named months
-		{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true},
-		{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false},
-
-		// Everything set.
-		{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true},
-		{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true},
-		{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false},
-		{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false},
-
-		// Predefined schedules
-		{"Mon Jul 9 15:00 2012", "@hourly", true},
-		{"Mon Jul 9 15:04 2012", "@hourly", false},
-		{"Mon Jul 9 15:00 2012", "@daily", false},
-		{"Mon Jul 9 00:00 2012", "@daily", true},
-		{"Mon Jul 9 00:00 2012", "@weekly", false},
-		{"Sun Jul 8 00:00 2012", "@weekly", true},
-		{"Sun Jul 8 01:00 2012", "@weekly", false},
-		{"Sun Jul 8 00:00 2012", "@monthly", false},
-		{"Sun Jul 1 00:00 2012", "@monthly", true},
-
-		// Test interaction of DOW and DOM.
-		// If both are specified, then only one needs to match.
-		{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true},
-		{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true},
-		{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true},
-
-		// However, if one has a star, then both need to match.
-		{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false},
-		{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false},
-		{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false},
-		{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true},
-		{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
-	}
-
-	for _, test := range tests {
-		sched, err := Parse(test.spec)
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
-		expected := getTime(test.time)
-		if test.expected && expected != actual || !test.expected && expected == actual {
-			t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
-				test.spec, test.time, expected, actual)
-		}
-	}
-}
-
-func TestNext(t *testing.T) {
-	runs := []struct {
-		time, spec string
-		expected   string
-	}{
-		// Simple cases
-		{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-
-		// Wrap around hours
-		{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
-
-		// Wrap around days
-		{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
-		{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
-
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
-
-		// Wrap around months
-		{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
-
-		// Wrap around years
-		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
-
-		// Wrap around minute, hour, day, month, and year
-		{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
-
-		// Leap year
-		{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
-
-		// Daylight savings time 2am EST (-5) -> 3am EDT (-4)
-		{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
-
-		// hourly job
-		{"2012-03-11T00:00:00-0500", "0 0 * * * ?", "2012-03-11T01:00:00-0500"},
-		{"2012-03-11T01:00:00-0500", "0 0 * * * ?", "2012-03-11T03:00:00-0400"},
-		{"2012-03-11T03:00:00-0400", "0 0 * * * ?", "2012-03-11T04:00:00-0400"},
-		{"2012-03-11T04:00:00-0400", "0 0 * * * ?", "2012-03-11T05:00:00-0400"},
-
-		// 1am nightly job
-		{"2012-03-11T00:00:00-0500", "0 0 1 * * ?", "2012-03-11T01:00:00-0500"},
-		{"2012-03-11T01:00:00-0500", "0 0 1 * * ?", "2012-03-12T01:00:00-0400"},
-
-		// 2am nightly job (skipped)
-		{"2012-03-11T00:00:00-0500", "0 0 2 * * ?", "2012-03-12T02:00:00-0400"},
-
-		// Daylight savings time 2am EDT (-4) => 1am EST (-5)
-		{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
-		{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
-
-		// hourly job
-		{"2012-11-04T00:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0400"},
-		{"2012-11-04T01:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0500"},
-		{"2012-11-04T01:00:00-0500", "0 0 * * * ?", "2012-11-04T02:00:00-0500"},
-
-		// 1am nightly job (runs twice)
-		{"2012-11-04T00:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
-		{"2012-11-04T01:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
-		{"2012-11-04T01:00:00-0500", "0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
-
-		// 2am nightly job
-		{"2012-11-04T00:00:00-0400", "0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
-		{"2012-11-04T02:00:00-0500", "0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
-
-		// 3am nightly job
-		{"2012-11-04T00:00:00-0400", "0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
-		{"2012-11-04T03:00:00-0500", "0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
-
-		// Unsatisfiable
-		{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
-		{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
-	}
-
-	for _, c := range runs {
-		sched, err := Parse(c.spec)
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		actual := sched.Next(getTime(c.time))
-		expected := getTime(c.expected)
-		if !actual.Equal(expected) {
-			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
-		}
-	}
-}
-
-func TestErrors(t *testing.T) {
-	invalidSpecs := []string{
-		"xyz",
-		"60 0 * * *",
-		"0 60 * * *",
-		"0 0 * * XYZ",
-	}
-	for _, spec := range invalidSpecs {
-		_, err := Parse(spec)
-		if err == nil {
-			t.Error("expected an error parsing: ", spec)
-		}
-	}
-}
-
-func getTime(value string) time.Time {
-	if value == "" {
-		return time.Time{}
-	}
-	t, err := time.Parse("Mon Jan 2 15:04 2006", value)
-	if err != nil {
-		t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
-		if err != nil {
-			t, err = time.Parse("2006-01-02T15:04:05-0700", value)
-			if err != nil {
-				panic(err)
-			}
-			// Daylight savings time tests require location
-			if ny, err := time.LoadLocation("America/New_York"); err == nil {
-				t = t.In(ny)
-			}
-		}
-	}
-
-	return t
-}
-
-func TestNextWithTz(t *testing.T) {
-	runs := []struct {
-		time, spec string
-		expected   string
-	}{
-		// Failing tests
-		{"2016-01-03T13:09:03+0530", "0 14 14 * * *", "2016-01-03T14:14:00+0530"},
-		{"2016-01-03T04:09:03+0530", "0 14 14 * * ?", "2016-01-03T14:14:00+0530"},
-
-		// Passing tests
-		{"2016-01-03T14:09:03+0530", "0 14 14 * * *", "2016-01-03T14:14:00+0530"},
-		{"2016-01-03T14:00:00+0530", "0 14 14 * * ?", "2016-01-03T14:14:00+0530"},
-	}
-	for _, c := range runs {
-		sched, err := Parse(c.spec)
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		actual := sched.Next(getTimeTZ(c.time))
-		expected := getTimeTZ(c.expected)
-		if !actual.Equal(expected) {
-			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
-		}
-	}
-}
-
-func getTimeTZ(value string) time.Time {
-	if value == "" {
-		return time.Time{}
-	}
-	t, err := time.Parse("Mon Jan 2 15:04 2006", value)
-	if err != nil {
-		t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
-		if err != nil {
-			t, err = time.Parse("2006-01-02T15:04:05-0700", value)
-			if err != nil {
-				panic(err)
-			}
-		}
-	}
-
-	return t
-}

+ 35 - 1
go.mod

@@ -2,4 +2,38 @@ module app.yhyue.com/moapp/jypkg
 
 go 1.18
 
-require github.com/SKatiyar/qr v0.0.0-20151201054752-25b6bdf44e67
+require (
+	app.yhyue.com/moapp/jybase v0.0.0-20221229054056-eb168b11d8e2
+	app.yhyue.com/moapp/message v0.0.0-20221202072401-d825fc65512c
+	github.com/nsqio/go-nsq v1.1.0
+	github.com/robfig/cron v1.2.0
+	github.com/tealeg/xlsx v1.0.5
+	github.com/thinxer/go-word2vec v0.0.0-20150917053916-5c19ec7379ed
+	go.mongodb.org/mongo-driver v1.11.1
+)
+
+require (
+	app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d // indirect
+	github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f // indirect
+	github.com/garyburd/redigo v1.6.2 // indirect
+	github.com/go-sql-driver/mysql v1.6.0 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/howeyc/fsnotify v0.9.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.1 // indirect
+	github.com/klauspost/compress v1.13.6 // indirect
+	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
+	github.com/olivere/elastic v6.2.37+incompatible // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+	github.com/xdg-go/scram v1.1.1 // indirect
+	github.com/xdg-go/stringprep v1.0.3 // indirect
+	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
+	github.com/ziutek/blas v0.0.0-20190227122918-da4ca23e90bb // indirect
+	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
+	golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gorm.io/driver/mysql v1.0.5 // indirect
+	gorm.io/gorm v1.21.3 // indirect
+)

+ 1062 - 2
go.sum

@@ -1,2 +1,1062 @@
-github.com/SKatiyar/qr v0.0.0-20151201054752-25b6bdf44e67 h1:x98/gnpyNAiuvg/gX3KgdfKxnZj3t9pIl3BCVJg3qwg=
-github.com/SKatiyar/qr v0.0.0-20151201054752-25b6bdf44e67/go.mod h1:g1VZ0nbzBvfsWw22gNVOxWxJxpBR+CBiFNK2n2ogeUo=
+app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d h1:WPsYuuptAd3UEgN+jPzpnsDe/OvcshDUUtOTZPYGSJ8=
+app.yhyue.com/moapp/esv1 v0.0.0-20220414031211-3da4123e648d/go.mod h1:91/lSD/hS+ckMVP3WdidRzDhC60lLMdyce9QHy0cSMA=
+app.yhyue.com/moapp/jyPoints v1.1.1/go.mod h1:SvP8p5L3jGrejHiH2LXfgCg/NPlFiKBC5Yd0gsI12FU=
+app.yhyue.com/moapp/jybase v0.0.0-20220427020729-974c1a148186/go.mod h1:qNRA0sHuYqcLoYoP8irpaWnW9YsXixe6obBIkwaXpD0=
+app.yhyue.com/moapp/jybase v0.0.0-20221229054056-eb168b11d8e2 h1:mt+Mlb89ykA3sMWk/BAhadH31lyLOrYwQ2Mufd+R4zM=
+app.yhyue.com/moapp/jybase v0.0.0-20221229054056-eb168b11d8e2/go.mod h1:efAeRPDpJ13JuNODuqtfLlKQSQgCbnUcwGPzhFU5krY=
+app.yhyue.com/moapp/message v0.0.0-20221202072401-d825fc65512c h1:CrcvbsXZ4aQkNikBi7FUUQZNnY8hKsNo2LLe/SqeXs8=
+app.yhyue.com/moapp/message v0.0.0-20221202072401-d825fc65512c/go.mod h1:b0zZHev3gmJao1Fo+2Z2KPVjsuLOJVvVxf+kCnu9WkA=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
+github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
+github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
+github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
+github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
+github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
+github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
+github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
+github.com/antlr/antlr4 v0.0.0-20210319025552-3590d4d5e18c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
+github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coscms/tagfast v0.0.0-20150925144250-2b69b2496250/go.mod h1:zX8vynptAghuV/KG8BOZlDeo4DsTKWfBQ154RWlkay0=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
+github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
+github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/donnie4w/go-logger v0.0.0-20170827050443-4740c51383f4/go.mod h1:L7S4x0R7vv3xoOhGuyAJyCO2MYzWOpccM4Isn8jIUgY=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
+github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
+github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
+github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
+github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogf/gf/v2 v2.0.6/go.mod h1:8uYzw7qNzuq8vrhVlWke1b1925FFqOJIgmyYW1sr/0M=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
+github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY=
+github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
+github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
+github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
+github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/longbridgeapp/sqlparser v0.3.1/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
+github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U=
+github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
+github.com/olivere/elastic/v7 v7.0.22/go.mod h1:VDexNy9NjmtAkrjNoI7tImv7FR4tf5zUA3ickqu5Pc8=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
+github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
+github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
+github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
+github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/tal-tech/go-zero v1.1.5/go.mod h1:LbN0C7/rbl2+LUWTSUYx5leXmgedeMWjt1jc3/8/zFA=
+github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
+github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
+github.com/thinxer/go-word2vec v0.0.0-20150917053916-5c19ec7379ed h1:1+oKuPuDQ4AbN1WRMFxl9WQClH80GuZ81X/4FsOshjI=
+github.com/thinxer/go-word2vec v0.0.0-20150917053916-5c19ec7379ed/go.mod h1:WE5pZgSp3RwicfhHQmOJOexA0n4AKTzBqmnSu7R8Nbk=
+github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
+github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
+github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
+github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zeromicro/go-zero v1.3.5/go.mod h1:wh4o794b7Ul3W0k35Pw9nc3iB4O0OpaQTMQz/PJc1bc=
+github.com/ziutek/blas v0.0.0-20190227122918-da4ca23e90bb h1:uWiILQloLUVdtPYr1ZZo2zqtlpzo4G8vUpglo/Fs2H8=
+github.com/ziutek/blas v0.0.0-20190227122918-da4ca23e90bb/go.mod h1:J3xKssoVdrwZ2E29fIox/EKxOZWimS7AZ4fOTCFkOLo=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM=
+go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
+go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
+go.mongodb.org/mongo-driver v1.5.0/go.mod h1:boiGPFqyBs5R0R5qf2ErokGRekMfwn+MqKaUyHs7wy0=
+go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
+go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
+go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
+go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
+go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
+go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
+go.opentelemetry.io/otel/exporters/jaeger v1.8.0/go.mod h1:GbWg+ng88rDtx+id26C34QLqw2erqJeAjsCx9AFeHfE=
+go.opentelemetry.io/otel/exporters/zipkin v1.8.0/go.mod h1:0uYAyCuGT67MFV9Z/Mmx93wGuugHw0FbxMc74fs3LNo=
+go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM=
+go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
+go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c=
+go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
+go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
+go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
+go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
+go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
+go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
+golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
+gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
+gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
+gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME=
+gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.22.9/go.mod h1:rcjO/FPOuvc3x7nQWx29UcDrFJMx82RxDob71ntNH4A=
+k8s.io/apimachinery v0.22.9/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
+k8s.io/client-go v0.22.9/go.mod h1:IoH7exYnoH/zgvHOuVxh2c4yJepcCBt72FzCTisOc4k=
+k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
+k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
+k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20220706174534-f6158b442e7c/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
+xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=

+ 78 - 0
public/checkholiday.go

@@ -0,0 +1,78 @@
+package public
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"sync"
+	"time"
+
+	util "app.yhyue.com/moapp/jybase/common"
+)
+
+type Holidy struct {
+	isHolidy bool
+	date     string
+}
+
+const (
+	URL = "http://apis.baidu.com/xiaogg/holiday/holiday?d=%s"
+	KEY = "188da6713cec8bf6287ee14e4ddcee26"
+)
+
+var Now = Holidy{}
+var Lock = &sync.Mutex{}
+
+func CheckNowHoliday() bool {
+	_now := time.Now().Format("20060102")
+	if Now.date == _now {
+		return Now.isHolidy
+	} else {
+		return GetNowHoliday(_now)
+	}
+}
+
+func GetNowHoliday(_now string) bool {
+	b1, b2 := GetHolidy(_now)
+	if b2 {
+		Lock.Lock()
+		Now.date = _now
+		Now.isHolidy = b1
+		Lock.Unlock()
+	}
+	return b1
+}
+
+func GetHolidy(yyyymmdd string) (bool, bool) {
+	m, b := GetUrl(yyyymmdd)
+	if b {
+		if m == "1" || m == "2" {
+			return true, true
+		}
+	}
+	return false, b
+}
+
+func GetUrl(date string) (m string, res bool) {
+	defer util.Catch()
+	req, err := http.NewRequest("GET", fmt.Sprintf(URL, date), nil)
+	if err != nil {
+		return
+	}
+	req.Header.Add("apikey", KEY)
+	resp, err := http.DefaultClient.Do(req)
+	defer resp.Body.Close()
+	if err == nil {
+		bs, err := ioutil.ReadAll(resp.Body)
+		if len(bs) == 1 {
+			m = string(bs)
+		}
+		if err != nil {
+			log.Println("err:", err)
+		} else {
+			res = true
+		}
+	}
+	return
+}

+ 15 - 0
public/checkwxbrowser.go

@@ -0,0 +1,15 @@
+package public
+
+import (
+	"net/http"
+	"strings"
+)
+
+//判断是否是微信访问
+func CheckWxBrowser(Request *http.Request) bool {
+	if strings.Index(Request.UserAgent(), "MicroMessenger") > -1 || strings.Index(Request.UserAgent(), "Wechat") > -1 {
+		return true
+	} else {
+		return false
+	}
+}

+ 76 - 0
public/convert.go

@@ -0,0 +1,76 @@
+package public
+
+import (
+	"encoding/gob"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+type Convert struct {
+	lock      *sync.RWMutex
+	lockun    *sync.RWMutex
+	mapping   map[rune]rune
+	unmapping map[rune]rune
+}
+
+//
+func NewConvert() *Convert {
+	return &Convert{
+		lock:      new(sync.RWMutex),
+		lockun:    new(sync.RWMutex),
+		mapping:   make(map[rune]rune),
+		unmapping: make(map[rune]rune),
+	}
+}
+
+//支持动态切换mapping文件
+func (c *Convert) LoadMapping(mappingfilepath string) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	c.lockun.Lock()
+	defer c.lockun.Unlock()
+	fi, err := os.Open(mappingfilepath)
+	if err != nil {
+		return err
+	}
+	mapping := make(map[string]string)
+	err = gob.NewDecoder(fi).Decode(&mapping)
+	if err != nil {
+		return err
+	}
+	fi.Close()
+	for k, v := range mapping {
+		key_r, _ := strconv.ParseInt(k[3:], 16, 32)
+		value_r, _ := strconv.ParseInt(v[3:], 16, 32)
+		c.mapping[rune(value_r)] = rune(key_r)
+		c.unmapping[rune(key_r)] = rune(value_r)
+	}
+	return nil
+}
+
+//转换
+func (c *Convert) DoConvert(src string) string {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	return strings.Map(func(r rune) rune {
+		if v, ok := c.mapping[r]; ok {
+			return v
+		} else {
+			return r
+		}
+	}, src)
+}
+
+func (c *Convert) UnConvert(src string) string {
+	c.lockun.RLock()
+	defer c.lockun.RUnlock()
+	return strings.Map(func(r rune) rune {
+		if v, ok := c.unmapping[r]; ok {
+			return v
+		} else {
+			return r
+		}
+	}, src)
+}

+ 206 - 0
public/dataexport.go

@@ -0,0 +1,206 @@
+package public
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"sync"
+	"time"
+
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/dataexport"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mail"
+)
+
+var ExConf *DataexportConfig
+
+//价格配置文件
+type DataexportConfig struct {
+	MsgMaxCount int  `json:"msgMaxCount"`
+	Font        Font `json:"font"`
+}
+
+type Font struct {
+	Enabled                 bool   `json:"enabled"`
+	ConvertVersionDefault   string `json:"convertVersionDefault"`
+	UnConvertVersionDefault string `json:"unConvertVersionDefault"`
+	MappingFileExt          string `json:"mappingFileExt"`
+	MappingFilePath         string `json:"mappingFilePath"`
+	RedisPool               string `json:"redisPool"`
+	RedisKey                string `json:"redisKey"`
+}
+
+func init() {
+	util.ReadConfig("./dataexport.json", &ExConf)
+}
+
+//发送邮箱验证码
+func SendMailIdentCode(to, code string, auth []*mail.GmailAuth) bool {
+	html := fmt.Sprintf(`<div>
+		<div>
+			%s,您好!
+		</div>
+		<div style="padding: 20px 70px 10px 70px;">
+			<p>您正在进行导出邮箱地址验证,请在邮件验证码输入框输入下方验证码:</p>
+			<span style="font-weight: bold;font-size: x-large;">%s</span>
+			<p>请勿向任何人泄露您收到的验证码。</p>
+			<p>如果您没有使用剑鱼标讯,请忽略此邮件。</p>
+			<p>此为系统邮件,请勿回复。</p>
+			<p>如有疑问,请联系客服 400-108-6670。</p>
+		</div>
+		<div>
+			<p>此致</p>
+			<p>剑鱼标讯</p>
+		</div>
+	</div>`, to, code)
+
+	for k, v := range auth {
+		if mail.GSendMail("剑鱼标讯", to, "", "", "剑鱼标讯邮箱校验", html, "", "", v) {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件成功", v.User))
+			return true
+		}
+		if k < len(auth)-1 {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件失败!3s后使用其他邮箱尝试", v.User))
+		} else {
+			log.Println(to, fmt.Sprintf("使用%s发送邮件失败!", v.User))
+		}
+		time.Sleep(time.Second * 3)
+	}
+
+	return false
+}
+
+var finaceLock *sync.Mutex = &sync.Mutex{}
+
+func GetWaitPayToken(orderid int64, order_money int, ordercode, payway, userid string) string {
+	return util.GetMd5String(fmt.Sprintf("%d_%d_%s_%s_%s", orderid, order_money, ordercode, payway, userid))
+}
+
+/*
+	获取-筛选条件-金额
+*/
+func GetPriceDes_SieveCondition(minPrice, maxPrice string) string {
+	des := ""
+	unit := "万元"
+	if minPrice != "" && maxPrice != "" {
+		des = minPrice + unit + "-" + maxPrice + unit
+	} else if minPrice != "" {
+		des = "大于" + minPrice + unit
+	} else if maxPrice != "" {
+		des = "小于" + maxPrice + unit
+	}
+	return des
+}
+
+//招标数据导出筛选
+type BidSearchExport struct {
+	Keywords     string //搜索词
+	Publishtime  string //发布时间
+	Area         string //地区
+	Subtype      string //信息类型
+	Minprice     string //最低价格
+	Maxprice     string //最高价格
+	Industry     string //选中的行业
+	SelectType   string //标题 or 全文
+	Winner       string //中标单位
+	Buyerclass   string //采购单位行业
+	Hasbuyertel  string //是否有采购电话
+	Haswinnertel string //是否有中标电话
+	SelectIds    string //选择单条信息id
+	Notkey       string //排除词
+	FileExists   string //是否有附件
+	City         string //城市
+	BidField     string // 领域化数据: 0101 医疗
+}
+
+func (this *BidSearchExport) PassBidSearchExport(Sysconfig map[string]interface{}) (returnData map[string]interface{}) {
+	defer util.Catch()
+	areaSave, industrySave, citySave := []string{}, []string{}, []string{}
+	winnerSave, buyerclassSave := []string{}, []string{}
+	publishtimeSave := this.Publishtime
+	if len(this.Area) > 0 {
+		areaSave = strings.Split(this.Area, ",")
+	}
+	if this.City != "" {
+		citySave = strings.Split(this.City, ",")
+	}
+	if len(this.Industry) > 0 {
+		industrySave = strings.Split(this.Industry, ",")
+	}
+	if len(this.Buyerclass) > 0 {
+		buyerclassSave = strings.Split(this.Buyerclass, ",")
+	}
+
+	if len(this.Winner) > 0 {
+		winnerSave = strings.Split(this.Winner, ",")
+	}
+
+	KeyWordSave := []dataexport.KeyWord{}
+	if len(this.Keywords) > 0 {
+		_, _, keywords := jy.InterceptSearchKW(this.Keywords, util.IntAllDef(Sysconfig["keywordsLimit"], 35), len(this.Industry) == 0)
+		keywords = strings.Replace(keywords, "+", " ", -1)
+		excludes := []string{}
+		if this.Notkey != "" && len(strings.Split(this.Notkey, " ")) > 0 {
+			excludes = strings.Split(this.Notkey, " ")
+		}
+		KeyWordSave = append(KeyWordSave, dataexport.KeyWord{Keyword: keywords, Exclude: excludes})
+	}
+
+	//时间
+	now := time.Now()
+	if this.Publishtime == "lately-7" { //最近7天
+		starttime := fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local).Unix())
+		publishtimeSave = fmt.Sprintf("%s_%d", starttime, now.Unix())
+	} else if this.Publishtime == "lately-30" { //最近30天
+		starttime := fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local).Unix())
+		publishtimeSave = fmt.Sprintf("%s_%d", starttime, now.Unix())
+	} else if this.Publishtime == "thisyear" { //去年
+		starttime := fmt.Sprint(time.Date(now.Year()-1, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+		endtime := fmt.Sprint(now.Unix())
+		publishtimeSave = fmt.Sprintf("%s_%s", starttime, endtime)
+	} else if this.Publishtime == "threeyear" { //近三年
+		starttime := fmt.Sprint(time.Date(now.Year()-3, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+		endtime := fmt.Sprint(now.Unix())
+		publishtimeSave = fmt.Sprintf("%s_%s", starttime, endtime)
+	} else if this.Publishtime == "fiveyear" { //近五年
+		starttime := fmt.Sprint(time.Date(now.Year()-5, now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.Local).Unix())
+		endtime := fmt.Sprint(now.Unix())
+		publishtimeSave = fmt.Sprintf("%s_%s", starttime, endtime)
+	}
+	returnData = map[string]interface{}{
+		"keywords":     KeyWordSave,
+		"publishtime":  publishtimeSave,
+		"area":         areaSave,
+		"city":         citySave,
+		"subtype":      this.Subtype,
+		"minprice":     this.Minprice,
+		"maxprice":     this.Maxprice,
+		"industry":     industrySave,
+		"selectType":   this.SelectType,
+		"buyerclass":   buyerclassSave,
+		"winner":       winnerSave,
+		"hasBuyertel":  this.Hasbuyertel,
+		"hasWinnertel": this.Haswinnertel,
+		"fileExists":   this.FileExists,
+		"comeintime":   now.Unix(),
+		"bid_field":    this.BidField,
+	}
+	//选择信息id
+	if this.SelectIds != "" {
+		ids := []string{}
+		for _, encodeId := range strings.Split(this.SelectIds, ",") {
+			if tmp := CommonDecodeArticle("content", encodeId); len(tmp) > 0 {
+				if id := tmp[0]; id != "" {
+					ids = append(ids, id)
+				}
+			}
+		}
+		if len(ids) > 0 {
+			returnData["selectIds"] = ids
+		}
+	}
+	return returnData
+}

+ 169 - 0
public/db.go

@@ -0,0 +1,169 @@
+package public
+
+import (
+	"log"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	m "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+)
+
+var (
+	DbConf      *dbConf
+	Mysql       *mysql.Mysql
+	BaseMysql   *mysql.Mysql
+	PushMysql   *mysql.Mysql
+	BaseService *mysql.Mysql
+	MQFW        m.MongodbSim
+	Mgo_Ent     m.MongodbSim
+	Mgo_Log     m.MongodbSim
+	Mgo_Bidding m.MongodbSim
+	//Mgo_Qyfw    m.MongodbSim
+)
+
+type dbConf struct {
+	Mongodb struct {
+		Main    *mgoConf
+		Log     *mgoConf
+		Ent     *mgoConf
+		Qyfw    *mgoConf
+		Bidding *mgoConf
+		Seo     *mgoConf
+	}
+	Elasticsearch struct {
+		Main *esConf
+	}
+	Redis struct {
+		Main  *redisConf
+		Login *redisConf
+	}
+	Mysql struct {
+		Main *mysqlConf
+		Push *mysqlConf
+		Base *mysqlConf
+	}
+}
+type mgoConf struct {
+	Address         string
+	Size            int
+	DbName          string
+	ReplSet         string
+	UserName        string
+	Password        string
+	Collection      string
+	Collection_back string
+}
+
+type esConf struct {
+	Address string
+	Size    int
+}
+type redisConf struct {
+	Address string
+}
+type mysqlConf struct {
+	DbName       string
+	Address      string
+	UserName     string
+	PassWord     string
+	MaxOpenConns int
+	MaxIdleConns int
+}
+
+func init() {
+	util.ReadConfig("./db.json", &DbConf)
+	if DbConf != nil {
+		if DbConf.Elasticsearch.Main != nil {
+			elastic.InitElasticSize(DbConf.Elasticsearch.Main.Address, DbConf.Elasticsearch.Main.Size)
+			log.Println("初始化 elasticsearch")
+		}
+		//初始化redis
+		if DbConf.Redis.Main != nil {
+			log.Println("初始化 redis")
+			redis.InitRedisBySize(DbConf.Redis.Main.Address, 100, 30, 300)
+		}
+		//
+		if DbConf.Mongodb.Main != nil {
+			log.Println("初始化 mongodb main")
+			MQFW = m.MongodbSim{
+				MongodbAddr: DbConf.Mongodb.Main.Address,
+				Size:        DbConf.Mongodb.Main.Size,
+				DbName:      DbConf.Mongodb.Main.DbName,
+				ReplSet:     DbConf.Mongodb.Main.ReplSet,
+			}
+			MQFW.InitPool()
+		}
+		if DbConf.Mongodb.Bidding != nil {
+			log.Println("初始化 mongodb bidding")
+			Mgo_Bidding = m.MongodbSim{
+				MongodbAddr: DbConf.Mongodb.Bidding.Address,
+				Size:        DbConf.Mongodb.Bidding.Size,
+				DbName:      DbConf.Mongodb.Bidding.DbName,
+				ReplSet:     DbConf.Mongodb.Bidding.ReplSet,
+				UserName:    DbConf.Mongodb.Bidding.UserName,
+				Password:    DbConf.Mongodb.Bidding.Password,
+			}
+			Mgo_Bidding.InitPool()
+		}
+		if DbConf.Mongodb.Ent != nil {
+			log.Println("初始化 mongodb ent")
+			Mgo_Ent = m.MongodbSim{
+				MongodbAddr: DbConf.Mongodb.Ent.Address,
+				Size:        DbConf.Mongodb.Ent.Size,
+				DbName:      DbConf.Mongodb.Ent.DbName,
+				ReplSet:     DbConf.Mongodb.Ent.ReplSet,
+				UserName:    DbConf.Mongodb.Ent.UserName,
+				Password:    DbConf.Mongodb.Ent.Password,
+			}
+			Mgo_Ent.InitPool()
+		}
+		if DbConf.Mongodb.Log != nil {
+			log.Println("初始化 mongodb log")
+			Mgo_Log = m.MongodbSim{
+				MongodbAddr: DbConf.Mongodb.Log.Address,
+				Size:        DbConf.Mongodb.Log.Size,
+				DbName:      DbConf.Mongodb.Log.DbName,
+				ReplSet:     DbConf.Mongodb.Log.ReplSet,
+				UserName:    DbConf.Mongodb.Log.UserName,
+				Password:    DbConf.Mongodb.Log.Password,
+			}
+			Mgo_Log.InitPool()
+		}
+		//if DbConf.Mongodb.Qyfw != nil {
+		//	log.Println("初始化 mongodb ent")
+		//	Mgo_Qyfw = m.MongodbSim{
+		//		MongodbAddr: DbConf.Mongodb.Qyfw.Address,
+		//		Size:        DbConf.Mongodb.Qyfw.Size,
+		//		DbName:      DbConf.Mongodb.Qyfw.DbName,
+		//	}
+		//	Mgo_Qyfw.InitPool()
+		//}
+		if DbConf.Mysql.Main != nil {
+			log.Println("初始化 mysql")
+			Mysql = &mysql.Mysql{
+				Address:      DbConf.Mysql.Main.Address,
+				UserName:     DbConf.Mysql.Main.UserName,
+				PassWord:     DbConf.Mysql.Main.PassWord,
+				DBName:       DbConf.Mysql.Main.DbName,
+				MaxOpenConns: DbConf.Mysql.Main.MaxOpenConns,
+				MaxIdleConns: DbConf.Mysql.Main.MaxIdleConns,
+			}
+			Mysql.Init()
+		}
+		//
+		if DbConf.Mysql.Base != nil {
+			log.Println("初始化 BaseMysql")
+			BaseMysql = &mysql.Mysql{
+				Address:      DbConf.Mysql.Base.Address,
+				UserName:     DbConf.Mysql.Base.UserName,
+				PassWord:     DbConf.Mysql.Base.PassWord,
+				DBName:       DbConf.Mysql.Base.DbName,
+				MaxOpenConns: DbConf.Mysql.Base.MaxOpenConns,
+				MaxIdleConns: DbConf.Mysql.Base.MaxIdleConns,
+			}
+			BaseMysql.Init()
+		}
+	}
+}

+ 335 - 0
public/entdataexport.go

@@ -0,0 +1,335 @@
+package public
+
+//此处逻辑移至支付程序
+//func GetEntDataExportCount(_id string, entId, entUserId, limitNum, current int, isFirst bool, webdomain string, url string) (count, newCount int, data *[]map[string]interface{}) {
+//	defer util.Catch()
+//	var (
+//		searchsWaitGroup = &sync.WaitGroup{}
+//		//searchsPool      = make(chan bool, 20)
+//		// res              = &[]map[string]interface{}{}
+//		//newCountPool = make(chan bool, 20000)
+//	)
+//	//count = GetDataExportSearchCountUseId(_id)
+//	count = dataexport.GetDataExportSearchCountByScdId(MQFW, DbConf.Elasticsearch.Main.Address, _id)
+//	log.Println("count", count)
+//	if count > ExConf.MsgMaxCount || count == -1 {
+//		count = ExConf.MsgMaxCount
+//	}
+//	dataType := "2"
+//	//数据导出数据查询
+//
+//	res, err := dataexport.GetDataExportSearchResultByScdId(MQFW, Mgo_Bidding, DbConf.Mongodb.Bidding.DbName, DbConf.Elasticsearch.Main.Address, _id, dataType, count)
+//	if err != nil {
+//		log.Println("企业数据导出错误 ", err)
+//		return 0, 0, nil
+//	}
+//	//  20210716 由原来的redis判重改为调用判重中台接口进行判重
+//	m := map[string]bool{}
+//	infoIdList := []string{}
+//	insertFlag := "false"
+//	if !isFirst {
+//		insertFlag = "true"
+//	}
+//	for _, v := range *res {
+//		id := util.ObjToString(v["_id"])
+//		if m[id] {
+//			continue
+//		}
+//		m[id] = true
+//		//  20210716  redis判重调整为调用判重中台接口  每一千个调用一次
+//		infoIdList = append(infoIdList, id)
+//		if len(infoIdList) > 1000 {
+//			//	 调接口
+//			rs, err5 := Post(url, map[string]string{
+//				"personId": "0", // 没有使用这个参数
+//				"infoId":   strings.Join(infoIdList, ","),
+//				"entId":    fmt.Sprintf("%d", entId),
+//				"isInsert": insertFlag,
+//				"isEnt":    "true",
+//			})
+//			log.Println("响应结果:",rs)
+//			if err5 != nil|| util.IntAll(rs["code"])!=0 {
+//				log.Println("企业订阅数据导出接口判重失败", err5)
+//				log.Println("企业订阅数据导出接口判重失败rs:",rs)
+//				log.Println("企业订阅数据导出接口判重失败rs[code]:",rs["code"])
+//				log.Println("企业订阅数据导出接口判重失败code是否为0",util.IntAll(rs["code"])!=0)
+//				log.Println("企业订阅数据导出接口判重失败", err5,"rs:",rs," rs[code]:",rs["code"]," ",util.IntAll(rs["code"]),"code是否为0",util.IntAll(rs["code"])!=0)
+//			} else {
+//				log.Println("企业订阅数据导出")
+//				// 置空
+//				infoIdList = []string{}
+//				// 本次数据累计
+//				returnData := rs["data"].(map[string]interface{})
+//				log.Println(newCount, "加之前")
+//				newCount += int(returnData["newCount"].(float64))
+//				log.Println(newCount, "加之后")
+//			}
+//
+//		}
+//		if !isFirst {
+//			delete(v, "_id")
+//			v["entid"] = entId
+//			v["userid"] = entUserId
+//			v["infoid"] = id
+//			v["createtime"] = time.Now().Unix()
+//		}
+//	}
+//	if len(infoIdList) > 0 {
+//		rs, err5 := Post(url, map[string]string{
+//			"personId": "0", // 没有使用这个参数
+//			"infoId":   strings.Join(infoIdList, ","),
+//			"entId":    fmt.Sprintf("%d", entId),
+//			"isInsert": insertFlag,
+//			"isEnt":    "true",
+//		})
+//		log.Println(rs)
+//		if err5 != nil|| util.IntAll(rs["code"])!=0{
+//			log.Println("企业订阅数据导出接口判重失败", err5)
+//			log.Println("企业订阅数据导出接口判重失败rs:",rs)
+//			log.Println("企业订阅数据导出接口判重失败rs[code]:",rs["code"])
+//			log.Println("企业订阅数据导出接口判重失败code是否为0",util.IntAll(rs["code"])!=0)
+//			log.Println("企业订阅数据导出接口判重失败", err5,"rs:",rs," rs[code]:",rs["code"]," ",util.IntAll(rs["code"]),"code是否为0",util.IntAll(rs["code"])!=0)
+//		} else {
+//			log.Println("企业订阅数据导出")
+//			// 置空
+//			infoIdList = []string{}
+//			// 本次数据累计
+//			returnData := rs["data"].(map[string]interface{})
+//			log.Println(newCount, "加之前")
+//			newCount += int(returnData["newCount"].(float64))
+//			log.Println(newCount, "加之后")
+//
+//		}
+//	}
+//	searchsWaitGroup.Wait()
+//	log.Println("企业数据导出--数据遍历完成")
+//	//newCount = len(newCountPool)
+//	log.Println("new", newCount)
+//	data = res
+//	return
+//}
+//
+//func FormatExportDatas(data *[]map[string]interface{}, webdomain string, dataType string, entId int) *[]map[string]interface{} {
+//	//格式化输出
+//	var (
+//		entexportPool      = make(chan bool, 20)
+//		entexportWaitGroup = &sync.WaitGroup{}
+//	)
+//	log.Println("补充信息开始")
+//	for _, v := range *data {
+//		entexportWaitGroup.Add(1)
+//		entexportPool <- true
+//		go func(v map[string]interface{}) {
+//			defer func() {
+//				entexportWaitGroup.Done()
+//				<-entexportPool
+//			}()
+//			//有中标企业 且 高级字段查询
+//			if dataType == "2" {
+//				//查询企业公示 法人 公司电话 公司邮箱地址
+//				s_winner := strings.Split(util.ObjToString(v["s_winner"]), ",")[0]
+//				if entData, ok := Mgo_Ent.Find("winner_enterprise", bson.M{"company_name": s_winner}, nil,
+//					`{"company_name":1,"company_email":1,"legal_person":1,"company_phone":1}`, false, -1, -1); ok {
+//					if entData != nil && *entData != nil && len(*entData) > 0 {
+//						for _, ev := range *entData {
+//							if v["s_winner"] == ev["company_name"] {
+//								legal_person := ""
+//								if ev["legal_person"] != nil {
+//									legal_person = ev["legal_person"].(string)
+//								}
+//								company_phone := ""
+//								if ev["company_phone"] != nil {
+//									company_phone = ev["company_phone"].(string)
+//								}
+//								company_email := ""
+//								if ev["company_email"] != nil && ev["company_email"] != "无" {
+//									company_email = ev["company_email"].(string)
+//								}
+//								v["legal_person"] = legal_person
+//								v["company_phone"] = company_phone
+//								v["company_email"] = company_email
+//							}
+//						}
+//					}
+//				}
+//			}
+//			//====================字段补漏=========================
+//			if v["toptype"] == "结果" && dataType == "2" && !(v["agency"] != nil && v["budget"] != nil && v["buyerperson"] != nil && v["buyertel"] != nil) {
+//				r := elastic.Get("projectset", "projectset", fmt.Sprintf(`{"query":{"term":{"list.infoid":"%s"}},"_source": ["list"]}`, v["_id"]))
+//				if len(*r) > 0 {
+//					MsgList := (*r)[0]["list"]
+//					if MsgList != nil {
+//						list := util.ObjArrToMapArr(MsgList.([]interface{}))
+//						for _, vv := range list {
+//							if vv["subtype"] == "招标" {
+//								if v["agency"] == nil && vv["agency"] != nil {
+//									v["agency"] = vv["agency"]
+//								}
+//								if v["budget"] == nil && vv["budget"] != nil {
+//									v["budget"] = vv["budget"]
+//								}
+//								if v["buyerperson"] == nil && vv["buyerperson"] != nil {
+//									v["buyerperson"] = vv["buyerperson"]
+//								}
+//								if v["buyertel"] == nil && vv["buyertel"] != nil {
+//									v["buyertel"] = vv["buyertel"]
+//								}
+//								break
+//							}
+//						}
+//					}
+//				}
+//			}
+//			if v["area"] == "A" {
+//				v["area"] = "全国"
+//			}
+//			if v["publishtime"] != nil {
+//				date := v["publishtime"]
+//				v["publishtime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//			}
+//			if v["bidopentime"] != nil {
+//				date := v["bidopentime"]
+//				v["bidopentime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//			}
+//			if v["currency"] == "" || v["currency"] == nil {
+//				v["currency"] = "人民币"
+//			}
+//			if v["subtype"] == nil && v["toptype"] != nil {
+//				v["subtype"] = v["toptype"]
+//			}
+//			if v["detail"] != "" && v["detail"] != nil {
+//				str := ClearHtml.ReplaceAllString(v["detail"].(string), "")
+//				str = ClearOther.ReplaceAllString(str, "")
+//				str = strings.Replace(str, " ", "", -1)
+//				v["detail"] = str
+//			}
+//			if v["_id"] != nil {
+//				v["url"] = webdomain + "/article/content/" + util.CommonEncodeArticle("content", v["_id"].(string)) + ".html"
+//			}
+//		}(v)
+//	}
+//	entexportWaitGroup.Wait()
+//	log.Println("补充信息结束")
+//	return data
+//}
+
+//func GetDataExportSearchResult(id string, dataType string, checkCount int) (*[]map[string]interface{}, error) {
+//	defer util.Catch()
+//	var (
+//		onceSearchCount = 500
+//		searchPool      = make(chan bool, 20)
+//		res             []map[string]interface{}
+//	)
+//	//获取查询语句
+//	scd := dataexport.GetSqlObjFromId(MQFW, id)
+//	if scd == nil {
+//		return nil, errors.New("GetDataExportSearchResult-获取查询条件")
+//	}
+//	qstr := getDataExportSql(scd)
+//	log.Printf("GetDataExportSearchResult-%s-sql:%s\n", scd.Id, qstr)
+//	//数据导出数据查询
+//	if checkCount > onceSearchCount { //分批次查询
+//		batchNum := util.IntAll(math.Ceil(float64(checkCount) / float64(onceSearchCount)))
+//		var searchWaitGroup = &sync.WaitGroup{}
+//		var lock sync.Mutex
+//		for n := 0; n < batchNum; n++ {
+//			searchWaitGroup.Add(1)
+//			searchPool <- true
+//			go func(start int) {
+//				defer func() {
+//					searchWaitGroup.Done()
+//					<-searchPool
+//				}()
+//				checkNum, checkOk := onceSearchCount, false
+//				if start == (batchNum - 1) {
+//					if checkCount%onceSearchCount != 0 {
+//						checkNum = checkCount % onceSearchCount
+//					}
+//				}
+//
+//				var tmp *[]map[string]interface{}
+//				for i := 0; i < 3; i++ {
+//					tmp = doSearch(qstr, start*onceSearchCount, onceSearchCount, dataType)
+//					if tmp != nil && (len(*tmp) == checkNum) { //校验数据量是否够
+//						checkOk = true
+//						break
+//					}
+//				}
+//				if tmp == nil {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据查询结果为空\n", scd.Id, start+1)
+//					return
+//				}
+//				if checkOk {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据加载完成,共%d条\n", scd.Id, start+1, len(*tmp))
+//				} else {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据加载异常,共%d条,预期%d条\n", scd.Id, start+1, len(*tmp), checkNum)
+//				}
+//				lock.Lock()
+//				res = append(res, *tmp...)
+//				lock.Unlock()
+//			}(n)
+//		}
+//		searchWaitGroup.Wait()
+//		log.Printf("GetDataExportSearchResult-%s-分批次加载数据总量为%d\n", scd.Id, len(res))
+//	} else {
+//		tmp := doSearch(qstr, 0, checkCount, dataType)
+//		if tmp == nil || len(*tmp) == 0 {
+//			log.Printf("GetDataExportSearchResult-%s-一次性加载数据异常\n", scd.Id)
+//		} else {
+//			res = *tmp
+//			log.Printf("GetDataExportSearchResult-%s-一次性加载数据总量为%d\n", scd.Id, len(res))
+//		}
+//	}
+//	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+//	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) {
+//		if len(scd.Keyword) != 0 {
+//			num := len(res)
+//			searchTextSize := 0
+//			if len(scd.Keyword) > 0 {
+//				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+//			}
+//			if searchTextSize > 3 && num < 50 {
+//				secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", DbConf.Elasticsearch.Main.Address)
+//				scd.Keyword[0].Keyword = secondKWS
+//				scd.SelectType = "title"
+//				qstr = getDataExportSql(scd)
+//				log.Printf("GetDataExportSearchResult-%s-分词查询-sql:%s\n", scd.Id, qstr)
+//				res2 := doSearch(qstr, 0, 100, "")
+//				res = *delRepeatMapArr(&res, res2)
+//				if len(res) > 100 {
+//					res = res[:100]
+//				}
+//			}
+//		}
+//	}
+//	//校验数量
+//	if checkCount != len(res) {
+//		return nil, fmt.Errorf("GetDataExportSearchResult-%s-数据总量校验异常,期望:%d,实际:%d", scd.Id, checkCount, len(res))
+//		//发邮件
+//	}
+//	return &res, nil
+//}
+
+//func Post(url string, form map[string]string) (data map[string]interface{}, err error) {
+//	str := ""
+//	for k, v := range form {
+//		str += "&" + k + "=" + v
+//	}
+//	//log.Println(str)
+//	res, err1 := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(str))
+//	log.Println(res)
+//	if err1 != nil {
+//		log.Println("post err:", err1.Error())
+//		return nil, err1
+//
+//	} else if res.Body != nil {
+//		defer res.Body.Close()
+//		bs, _ := ioutil.ReadAll(res.Body)
+//		err2 := json.Unmarshal(bs, &data)
+//		if err2 != nil {
+//			return nil, err2
+//		}
+//
+//	}
+//	return data, nil
+//}

+ 84 - 0
public/extractarea.go

@@ -0,0 +1,84 @@
+package public
+
+import (
+	"fmt"
+
+	util "app.yhyue.com/moapp/jybase/common"
+)
+
+var CityConfig map[string]interface{}
+var AreaGet DFA //敏感词
+
+func init() {
+	util.ReadConfig("./city.json", &CityConfig)
+	city, _ := CityConfig["city"].([]interface{})
+	citys := []string{}
+	for _, v := range city {
+		citys = append(citys, fmt.Sprint(v))
+	}
+	AreaGet = DFA{}
+	AreaGet.AddWord(citys...)
+}
+
+type DFA struct {
+	Link map[string]interface{}
+}
+
+func DealString(title string) bool {
+	return AreaGet.CheckSensitiveWord(title)
+}
+
+func (d *DFA) AddWordAll(haskey bool, keys ...string) {
+	if d.Link == nil {
+		d.Link = make(map[string]interface{})
+	}
+	for _, key := range keys {
+		nowMap := &d.Link
+		for i := 0; i < len(key); i++ {
+			kc := key[i : i+1]
+			if v, ok := (*nowMap)[kc]; ok {
+				nowMap, _ = v.(*map[string]interface{})
+			} else {
+				newMap := map[string]interface{}{}
+				newMap["YN"] = "0"
+				(*nowMap)[kc] = &newMap
+				nowMap = &newMap
+			}
+			if i == len(key)-1 {
+				(*nowMap)["YN"] = "1"
+			}
+		}
+	}
+}
+
+func (d *DFA) AddWord(keys ...string) {
+	d.AddWordAll(true, keys...)
+}
+
+//适合一次查找
+func (d *DFA) CheckSensitiveWord(src string) bool {
+	pos := 0
+	nowMap := &d.Link
+	res := false
+	for i := 0; i < len(src); i++ {
+		word := src[i : i+1]
+		nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+		if nowMap != nil { // 存在,则判断是否为最后一个
+			if pos == 0 {
+				pos = i
+			}
+			if "1" == util.ObjToString((*nowMap)["YN"]) { // 如果为最后一个匹配规则,结束循环,返回匹配标识数
+				res = true
+				pos = 0
+				break
+			}
+		} else {
+			nowMap = &d.Link
+			if pos > 0 {
+				i = pos
+				pos = 0
+			}
+		}
+	}
+	return res
+}

+ 265 - 0
public/forceshare.go

@@ -0,0 +1,265 @@
+package public
+
+import (
+	//"log"
+	"time"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/redis"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const (
+	ShareType_detail = 1 //分享类型 1-详情页
+	ShareType_push   = 2 //2-推送列表
+	ShareType_lab    = 3 //3-实验室
+
+	shareProperty_passive = 1 // 1-被动分享
+	shareProperty_active  = 2 // 2-主动分享
+
+	prefix_shareTimes = "shareTimes_" //redis 分享次数上限前缀
+
+	redisPoolCode = "other"
+)
+
+/**
+判断是否需要强制分享
+
+return - true-需要强制分享 false-不需强制分享
+*/
+func CheckNeedShared(stl1, stl2, stlt1, stlt2 int, Sysconfig map[string]interface{}) bool {
+	reFlag := false
+	var shareTimeLine int
+	var shareTimeLineTime int
+	shareIntervalDays := util.IntAll((Sysconfig["share"].(map[string]interface{}))["shareIntervalDays"])
+	if stl1 != 0 && stlt1 != 0 && stl2 != 0 && stlt2 != 0 {
+		if stl1 == shareProperty_passive && stl2 == shareProperty_passive {
+			if stlt1-stlt2 > 0 {
+				shareTimeLineTime = stlt1
+			} else {
+				shareTimeLineTime = stlt2
+			}
+		} else if stl1 == shareProperty_passive {
+			shareTimeLineTime = stlt1
+		} else if stl2 == shareProperty_passive {
+			shareTimeLineTime = stlt2
+		} else {
+			reFlag = true
+		}
+		if shareTimeLineTime != 0 {
+			interval := GetIntervalDayFromLastToNow(shareTimeLineTime)
+			if interval >= shareIntervalDays {
+				reFlag = true
+			}
+		}
+	} else if stl1 != 0 && stlt1 != 0 {
+		shareTimeLine = stl1
+		if shareTimeLine == shareProperty_passive {
+			shareTimeLineTime = stlt1
+			interval := GetIntervalDayFromLastToNow(shareTimeLineTime)
+			if interval >= shareIntervalDays {
+				reFlag = true
+			}
+		} else {
+			reFlag = true
+		}
+	} else if stl2 != 0 && stlt2 != 0 {
+		shareTimeLine = stl2
+		if shareTimeLine == shareProperty_passive {
+			shareTimeLineTime = stlt2
+			interval := GetIntervalDayFromLastToNow(shareTimeLineTime)
+			if interval >= shareIntervalDays {
+				reFlag = true
+			}
+		} else {
+			reFlag = true
+		}
+	} else {
+		reFlag = true
+	}
+	return reFlag
+}
+
+//当前时间距上个时间的间隔天数
+func GetIntervalDayFromLastToNow(lasttime int) int {
+	return (int(time.Now().Unix()) - lasttime) / (60 * 60 * 24)
+}
+
+/**
+判断用户是否需要强制分享
+
+userId - 用户userId
+shareType - 分享类型 1-详情页 2-推送列表 3-实验室
+return - true-需强制分享 false-不需强制分享
+*/
+func CheckUserNeedForceShare(userId string, shareType int, Sysconfig map[string]interface{}) bool {
+	defer util.Catch()
+	weekday := time.Now().Local().Weekday().String()
+	if weekday == "Sunday" || weekday == "Saturday" {
+		return false
+	}
+	needForceShareFlag := false
+	shareConfigMap := Sysconfig["share"].(map[string]interface{})
+	//强制分享功能是否启用 true-启用 false-不启用
+	if shareConfigMap["forceShareEnabled"].(bool) {
+		userInfo, ok := MQFW.FindById("user", userId, nil)
+		if ok && userInfo != nil {
+			isNewUserFlag := false
+			if shareType == ShareType_detail {
+				//是否为新用户-详情页分享
+				if (*userInfo)["l_registedate"] != nil {
+					regDate := (*userInfo)["l_registedate"].(int64)
+					onlineDate := shareConfigMap["onlineDate"].(string)
+					regDaysForNewUser := util.IntAll(shareConfigMap["regDaysForNewUser"])
+					onlineTime, _ := time.ParseInLocation("2006-01-02 15:04:05", onlineDate, time.Local)
+					onlineTimeI := onlineTime.Unix()
+					interval := GetIntervalDayFromLastToNow(int(regDate))
+					//log.Println("----------", openid, "userRegisterDays:", interval, regDate, onlineTimeI, "----------")
+					if regDate-onlineTimeI > 0 && interval >= regDaysForNewUser {
+						isNewUserFlag = true
+					} else {
+						isNewUserFlag = false
+					}
+				}
+			}
+			//log.Println("----------", openid, "isNewUserFlag:", isNewUserFlag, "----------")
+			// 1.详情页-只新用户,需强制分享(分享次数未达上限 && 未分享)
+			// 2.推送消息、实验室-不区分新老用户,均需强制分享(分享次数未达上限 && 未分享)
+			if (shareType == ShareType_detail && isNewUserFlag) || shareType == ShareType_push || shareType == ShareType_lab {
+				//分享次数是否超过上限
+				hour, _, _ := time.Now().Clock()
+				startHour := util.IntAll(shareConfigMap["startHour"])
+				endHour := util.IntAll(shareConfigMap["endHour"]) - 1
+				shareTimesUpperLimitR := util.IntAll(shareConfigMap["shareTimesUpperLimitR"]) / (endHour - startHour + 1)
+				shareTimesUpperLimitIrr := util.IntAll(shareConfigMap["shareTimesUpperLimitIrr"])
+				reachedUpperLimitFlag := false
+				if hour >= startHour && hour <= endHour {
+					shareTimes := redis.GetInt(redisPoolCode, prefix_shareTimes+time.Now().Format("2006010215"))
+					//log.Println("----------", openid, "redis-shareTimes_", time.Now().Format("2006010215"), shareTimes, "----------")
+					if shareTimes >= shareTimesUpperLimitR {
+						reachedUpperLimitFlag = true
+					}
+				} else {
+					shareTimes := redis.GetInt(redisPoolCode, prefix_shareTimes+time.Now().Format("20060102"))
+					//log.Println("----------", openid, "redis-shareTimes_", time.Now().Format("20060102"), shareTimes, "----------")
+					if shareTimes >= shareTimesUpperLimitIrr {
+						reachedUpperLimitFlag = true
+					}
+				}
+				//log.Println("----------", openid, "reachedUpperLimitFlag:", reachedUpperLimitFlag, "----------")
+				//分享次数未超上限,判断是否已分享
+				if !reachedUpperLimitFlag {
+					//是否已分享
+					sharedFlag := false
+					detailSTL := util.IntAll((*userInfo)["i_detailsharetimeline"]) //公告详情页分享标志
+					pushSTL := util.IntAll((*userInfo)["i_pushsharetimeline"])     //推送列表分享标志
+					labSTL := util.IntAll((*userInfo)["i_jylabsharetimeline"])     //实验室分享标志
+					var shareTimeLine interface{}
+					if shareType == ShareType_detail {
+						shareTimeLine = detailSTL
+					} else if shareType == ShareType_push {
+						shareTimeLine = pushSTL
+					} else if shareType == ShareType_lab {
+						shareTimeLine = labSTL
+					}
+					if shareTimeLine != nil && (util.IntAll(shareTimeLine) == shareProperty_passive || util.IntAll(shareTimeLine) == shareProperty_active) {
+						sharedFlag = true
+					}
+					//log.Println("----------", openid, "sharedFlag:", sharedFlag, "----------")
+					//未分享时,判断是否需强制分享
+					if !sharedFlag {
+						detailSTLT := util.IntAll((*userInfo)["i_detailsharetimelinetime"])
+						pushSTLT := util.IntAll((*userInfo)["i_pushsharetimelinetime"])
+						labSTLT := util.IntAll((*userInfo)["i_jylabsharetimelinetime"])
+						if shareType == ShareType_detail {
+							needForceShareFlag = CheckNeedShared(pushSTL, labSTL, pushSTLT, labSTLT, Sysconfig)
+						} else if shareType == ShareType_push {
+							needForceShareFlag = CheckNeedShared(detailSTL, labSTL, detailSTLT, labSTLT, Sysconfig)
+						} else if shareType == ShareType_lab {
+							needForceShareFlag = CheckNeedShared(detailSTL, pushSTL, detailSTLT, pushSTLT, Sysconfig)
+						}
+					}
+				}
+			}
+		}
+	}
+	//log.Println("----------", openid, "needForceShareFlag:", needForceShareFlag, "----------")
+	//log.Println("----------", "判断用户是否需要强制分享end", openid, "----------")
+	return needForceShareFlag
+}
+
+/**
+成功分享后 更改分享相关信息
+
+shareType - 分享类型 1-详情页 2-推送列表 3-实验室
+shareProperty - 分享性质 1-被动分享 2-主动分享
+*/
+func UpdateShareStatus(userId, platform string, shareType, shareProperty, ispcforceshare int64, isRepair bool, Sysconfig map[string]interface{}) {
+	defer util.Catch()
+	if userId != "" && shareType != 0 && shareProperty != 0 {
+		_id, _ := primitive.ObjectIDFromHex(userId)
+		queryM := map[string]interface{}{
+			"_id": _id,
+		}
+		setM := map[string]interface{}{}
+		now := time.Now()
+		tp := ""
+		field := ""
+		if shareType == ShareType_detail {
+			tp = "detail"
+			field = "i_detailsharetimeline"
+			setM["i_detailsharetimeline"] = shareProperty
+			setM["i_detailsharetimelinetime"] = now.Unix()
+			if ispcforceshare == 1 {
+				redis.Put(redisPoolCode, "pcbiddetail_shareTimeline_"+userId, 1, 60)
+			}
+		} else if shareType == ShareType_push {
+			tp = "push"
+			field = "i_pushsharetimeline"
+			setM["i_pushsharetimeline"] = shareProperty
+			setM["i_pushsharetimelinetime"] = now.Unix()
+		} else if shareType == ShareType_lab {
+			tp = "lab"
+			field = "i_jylabsharetimeline"
+			setM["i_jylabsharetimeline"] = shareProperty
+			setM["i_jylabsharetimelinetime"] = now.Unix()
+		}
+		flag := true
+		if isRepair {
+			if MQFW.Count("user", map[string]interface{}{
+				"_id": _id,
+				field: map[string]interface{}{"$exists": 0},
+			}) == 0 {
+				flag = false
+			}
+		}
+		if flag {
+			if shareProperty == shareProperty_active {
+				queryM[field] = map[string]interface{}{"$exists": 0}
+			}
+			MQFW.Update("user", queryM, map[string]interface{}{
+				"$set": setM,
+			}, false, false)
+			logM := map[string]interface{}{
+				"s_userid":     userId,
+				"i_status":     shareProperty,
+				"l_createtime": now.Unix(),
+				"s_type":       tp,
+				"s_platform":   platform,
+			}
+			if isRepair {
+				logM["s_other"] = "repair"
+			}
+			MQFW.Save("share_log", logM)
+			hour, _, _ := now.Clock()
+			shareConfigMap := Sysconfig["share"].(map[string]interface{})
+			startHour := util.IntAll(shareConfigMap["startHour"])
+			endHour := util.IntAll(shareConfigMap["endHour"]) - 1
+			if hour >= startHour && hour <= endHour {
+				redis.Incr(redisPoolCode, prefix_shareTimes+now.Format("2006010215"))
+			} else {
+				redis.Incr(redisPoolCode, prefix_shareTimes+now.Format("20060102"))
+			}
+		}
+	}
+}

+ 64 - 0
public/front.go

@@ -0,0 +1,64 @@
+package public
+
+import (
+	"log"
+
+	"app.yhyue.com/moapp/jybase/redis"
+)
+
+const (
+	PC  = "pc"
+	WX  = "wx"
+	APP = "app"
+)
+
+/**
+混淆字体解析
+source : pc,wx,app
+*/
+func GetFontUnConvertStr(fontVersion, content, source string) string {
+	if ExConf.Font.Enabled && content != "" {
+		c := NewConvert()
+		err := c.LoadMapping("./" + ExConf.Font.MappingFilePath + fontVersion + "_" + source + ExConf.Font.MappingFileExt)
+		if err != nil {
+			log.Println("LoadMappingOfFont_Error", err)
+			return content
+		}
+		return c.UnConvert(content)
+	} else {
+		return content
+	}
+}
+
+/**
+字体混淆
+加载mapping文件出错时,返回原文本
+*/
+func GetFontConvertStr(fontVersion, content, source string) string {
+	if ExConf.Font.Enabled && content != "" {
+		c := NewConvert()
+		err := c.LoadMapping(ExConf.Font.MappingFilePath + fontVersion + "_" + source + ExConf.Font.MappingFileExt)
+		if err != nil {
+			log.Println("LoadMappingOfFont_Error", err)
+			return content
+		}
+		return c.DoConvert(content)
+	} else {
+		return content
+	}
+}
+
+/**
+获取所使用的字体库
+*/
+func GetFontVersion() string {
+	fontVersion := ExConf.Font.UnConvertVersionDefault
+	if ExConf.Font.Enabled {
+		fontVersion = ExConf.Font.ConvertVersionDefault
+		redisFontVersion := redis.Get(ExConf.Font.RedisPool, ExConf.Font.RedisKey)
+		if redisFontVersion != nil {
+			fontVersion = redisFontVersion.(string)
+		}
+	}
+	return fontVersion
+}

+ 155 - 0
public/limitSearchText.go

@@ -0,0 +1,155 @@
+package public
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/redis"
+
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
+)
+
+var Lst *LimitSearchText
+
+type LimitSearchText struct {
+	TimeOut      int
+	Count        int
+	UserIds      []string
+	Flag         bool
+	TotalPage    int
+	Msg          string
+	Percentage   int
+	LimitKey     string
+	LimitKeyUser string
+	NoLogin      int
+}
+
+func IsSearchLimit(searchItems []string) bool {
+	for _, searchItem := range searchItems {
+		if searchItem == "detail" || searchItem == "filetext" {
+			return true
+		}
+	}
+	return false
+}
+
+//
+func InitLimitSearchText(flag bool, Sysconfig map[string]interface{}) {
+	limitSearchConfig, _ := Sysconfig["limitSearchText"].(map[string]interface{})
+	Lst = &LimitSearchText{
+		TimeOut:      util.IntAllDef(limitSearchConfig["timeout"], 60),
+		UserIds:      util.ObjArrToStringArr(limitSearchConfig["userIds"].([]interface{})),
+		Count:        util.IntAllDef(limitSearchConfig["count"], 3),
+		TotalPage:    util.IntAllDef(limitSearchConfig["totalPage"], 3),
+		Flag:         limitSearchConfig["flag"].(bool),
+		Msg:          limitSearchConfig["msg"].(string),
+		Percentage:   util.IntAllDef(limitSearchConfig["percentage"], 80),
+		LimitKey:     fmt.Sprintf(util.ObjToString(limitSearchConfig["limitKey"]), "jy_limitSearchText"),
+		LimitKeyUser: fmt.Sprintf(util.ObjToString(limitSearchConfig["limitKey"]), "%s"),
+		NoLogin:      util.IntAllDef(limitSearchConfig["noLogin"], 3),
+	}
+	if !Lst.Flag {
+		return
+	}
+	if flag {
+		Lst.Init()
+	}
+}
+func (l *LimitSearchText) Init() {
+	l.Clear()
+	for i := 0; i < Lst.Count; i++ {
+		redis.RPUSH("other", l.LimitKey, 1)
+	}
+}
+func (l *LimitSearchText) Clear() {
+	redis.Del("other", l.LimitKey)
+}
+
+//限制正文查询
+//return 1 正常
+//return -1 抱歉!由于系统繁忙暂时无法进行搜索,请1分钟后再试!
+//return -2 抱歉!由于系统繁忙暂时无法进行搜索,请稍后再试!
+func (l *LimitSearchText) IsLimited(r *http.Request, w http.ResponseWriter, s *httpsession.Session, isPayedUser bool) int {
+	if !l.Flag {
+		return 1
+	}
+	var llen = int(redis.LLEN("other", l.LimitKey))
+	if l.TimeOut > 0 {
+		limitFlag, isNew := l.getFlag(r, w, s)
+		if isNew {
+			if llen <= l.Count/2 {
+				timeLimit, _ := redis.Exists("other", fmt.Sprintf(l.LimitKeyUser, limitFlag))
+				if timeLimit {
+					return -1
+				}
+			}
+		}
+		redis.Put("other", fmt.Sprintf(l.LimitKeyUser, limitFlag), 1, l.TimeOut)
+	}
+	if l.Count == -2 {
+		return 1
+	} else if l.Count == -1 {
+		return -2
+	}
+	if s.Get("userId") == nil { //未登录用户,2个并发数限制
+		if l.Count-l.NoLogin <= 0 || llen <= l.Count-l.NoLogin || llen == 1 {
+			return -2
+		}
+	} else if !isPayedUser { //非VIP&大会员 用户 使用并发的80%(默认)通道|| 保留一条通道给非普通用户使用
+		if llen <= l.Count*l.Percentage/100 || llen == 1 {
+			return -2
+		}
+	}
+	//
+	pollLimit := redis.LPOP("other", l.LimitKey)
+	if util.IntAll(pollLimit) <= 0 {
+		return -2
+	}
+	return 1
+}
+func (l *LimitSearchText) Limit() {
+	if !l.Flag {
+		return
+	}
+	if int(redis.LLEN("other", l.LimitKey)) < l.Count {
+		redis.RPUSH("other", l.LimitKey, 1)
+	}
+}
+func (l *LimitSearchText) getFlag(r *http.Request, w http.ResponseWriter, s *httpsession.Session) (string, bool) {
+	limitFlag, _ := s.Get("userId").(string)
+	if limitFlag == "" {
+		c, _ := r.Cookie("limitSearchTextFlag")
+		if c != nil {
+			limitFlag = c.Value
+			if limitFlag == "" {
+				limitFlag, _ = s.Get("limitSearchTextFlag").(string)
+			}
+		}
+	}
+	if limitFlag != "" {
+		return limitFlag, true
+	}
+	limitFlag = util.GetLetterRandom(5) + fmt.Sprint(time.Now().UnixNano())
+	s.Set("limitSearchTextFlag", limitFlag)
+	c := &http.Cookie{
+		Name:     "limitSearchTextFlag",
+		Value:    limitFlag,
+		Path:     "/",
+		HttpOnly: false,
+		MaxAge:   2592000, //一个月
+	}
+	http.SetCookie(w, c)
+	return limitFlag, false
+}
+
+//
+func (l *LimitSearchText) IsCanLogin(userId string) bool {
+	for _, v := range l.UserIds {
+		if v == userId {
+			return true
+		}
+	}
+	return false
+}

+ 279 - 0
public/public.go

@@ -0,0 +1,279 @@
+package public
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+	"time"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	. "app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	. "app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/redis"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
+)
+
+var ClearHtml = regexp.MustCompile("<[^>]*>")
+var ClearOther = regexp.MustCompile("[\n\r\\s\u3000\u2003\u00a0]")
+
+var filterReg_3 = regexp.MustCompile("(项目|公告|公示)$")
+var filterReg_2 = regexp.MustCompile("^[)\\)>》】\\]}}〕,,;;::'\"“”。.\\??、/+=\\_—*&……\\^%$¥@!!`~·(\\(<《【\\[{{〔]+$")
+var filterReg_1 = regexp.MustCompile("^([0-9]{1,3}|[零一二三四五六七八九十]{1,2}|联系人?|电话|地址|编号|采购|政府采购|成交|更正|招标|中标|变更|结果)$")
+var filterReg = regexp.MustCompile("^[的人号时元万公告项目地址电话邮编日期联系招标中结果成交项目项目采购采购项目政府采购公告更正公告]+$")
+var MgoBiddingFields = `{"_id":1,"projectname":1,"projectcode":1,"title":1,"s_winner":1,"buyertel":1,"bidstatus":1,"site":1,"bidamount":1,"toptype":1,"winneraddr":1,"winner":1,"agency":1,"buyer":1,"detail":1,"city":1,"subtype":1,"buyerclass":1,"href":1,"comeintime":1,"winnertel":1,"area":1,"publishtime":1,"buyeraddr":1,"agencytel":1,"budget":1,"entidlist":1,"buyerperson":1,"winnerperson":1,"agencyaddr":1,"recommended_service":1,"competehref":1,"owner":1,"total_investment":1,"projectaddr":1,"projectperiod":1,"approvedept":1,"approvecontent":1,"approvecode":1,"approvenumber":1,"approvetime":1,"approvestatus":1,"project_scale":1}`
+
+const (
+	INDEX = "bidding"
+	TYPE  = "bidding"
+)
+
+func FilteKey(k string) string {
+	k = strings.TrimSpace(k)
+	k = filterReg_3.ReplaceAllString(k, "")
+	k = filterReg_2.ReplaceAllString(k, "")
+	k = filterReg_1.ReplaceAllString(k, "")
+	k = filterReg.ReplaceAllString(k, "")
+	return k
+}
+
+/*
+ * 结果列表转换,目前只换行行业字段
+ * 所有的招标搜索都要调用此方法,列表中有展示行业的也可以用
+ * industry 搜索条件中的行业,默认为空
+ */
+func BidListConvert(industry string, list *[]map[string]interface{}) {
+	if list == nil {
+		return
+	}
+	commonSubstring := func(v string) (value string) {
+		bcs := strings.Split(v, "_")
+		if len(bcs) == 1 {
+			value = bcs[0]
+		} else if len(bcs) == 2 {
+			value = bcs[0]
+			if strings.TrimSpace(value) == "" {
+				value = bcs[0]
+			}
+		}
+		return
+	}
+	for _, v := range *list {
+		budget, _ := v["budget"].(float64)
+		bidamount, _ := v["bidamount"].(float64)
+		if budget == 0 || strings.TrimSpace(fmt.Sprint(v["budget"])) == "" {
+			delete(v, "budget")
+		}
+		if bidamount == 0 || strings.TrimSpace(fmt.Sprint(v["bidamount"])) == "" {
+			delete(v, "bidamount")
+		}
+		value := ""
+		subscopeclass, _ := v["s_subscopeclass"].(string)
+		subscopeclass = strings.Trim(subscopeclass, ",")
+		bct := strings.Split(subscopeclass, ",")
+		if bct == nil || len(bct) == 0 {
+			continue
+		}
+		//搜索条件中没有行业的话,取查询结果中第一个行业
+		if industry == "" {
+			value = commonSubstring(bct[0])
+		} else { //搜索条件中有行业的话,取行业中和搜索条件相对应的第一个
+			industrys := strings.Split(industry, ",")
+		L:
+			for _, bc := range bct {
+				for _, is := range industrys {
+					if bc == is {
+						value = commonSubstring(bc)
+						break L
+					}
+				}
+			}
+		}
+		if strings.TrimSpace(value) == "" {
+			continue
+		}
+		v["industry"] = value
+	}
+}
+
+// a_mergeorder 根据这三个字段产生的顺序,决定用哪个当作openid
+func GetOldOpenId(userId string) (*map[string]interface{}, string) {
+	user, ok := MQFW.FindById("user", userId, `{"s_m_openid":1,"a_m_openid":1,"s_phone":1,"a_mergeorder":1,"o_jy":1,"l_firstpushtime":1}`)
+	if !ok || user == nil {
+		return nil, ""
+	}
+	s_m_openid, _ := (*user)["s_m_openid"].(string)
+	a_m_openid, _ := (*user)["a_m_openid"].(string)
+	s_phone, _ := (*user)["s_phone"].(string)
+	openid := jy.GetOldOpenid(s_m_openid, a_m_openid, s_phone, (*user)["a_mergeorder"])
+	return user, openid
+}
+
+func PushViewDatas(userid, allquery, field string, pageNum, pageSize int) (keys []interface{}, list *[]map[string]interface{}) {
+	keys, list = PushView(userid, allquery, field, pageNum, pageSize)
+	if list != nil {
+		for _, v := range *list {
+			v["_id"] = EncodeArticleId2ByCheck(util.ObjToString(v["_id"]))
+		}
+	}
+	return
+}
+
+func PushView(userid, allquery, field string, pageNum, pageSize int) (keys []interface{}, list *[]map[string]interface{}) {
+	if userid == "" {
+		return
+	}
+	tmp, ok := MQFW.FindById("user", userid, `{"_id":1,"o_jy":1}`)
+	if !ok || tmp == nil || len(*tmp) == 0 {
+		return
+	}
+	o_jy := (*tmp)["o_jy"].(map[string]interface{})
+	a_key, _ := o_jy["a_key"].([]interface{})
+	if len(a_key) == 0 {
+		return
+	}
+	for _, v := range a_key {
+		keyMap, _ := v.(map[string]interface{})
+		key, _ := keyMap["key"].([]interface{})
+		keys = append(keys, key)
+	}
+	var allkeys []elastic.KeyConfig //用户配置
+	_bs, err := json.Marshal(a_key)
+	if err == nil {
+		json.Unmarshal(_bs, &allkeys)
+	}
+	list = elastic.GetResForJY(INDEX, TYPE, allkeys, allquery, `"title"`, `{"publishtime":"desc"}`, field, (pageNum-1)*pageSize, pageSize)
+	return
+}
+
+// 查询获取最多订阅词
+func GetHotkeys() []interface{} {
+	sess := MQFW.GetMgoConn()
+	defer MQFW.DestoryMongoConn(sess)
+	var res []map[string]interface{}
+	var keys []interface{}
+	sess.DB("qfw").C("user").Pipe([]map[string]interface{}{
+		map[string]interface{}{"$match": map[string]interface{}{"o_jy.a_key": map[string]interface{}{"$exists": 1}}},
+		map[string]interface{}{"$project": map[string]interface{}{"o_jy.a_key.key": 1}},
+		map[string]interface{}{"$unwind": "$o_jy.a_key"},
+		map[string]interface{}{"$project": map[string]interface{}{"key": "$o_jy.a_key.key"}},
+		map[string]interface{}{"$unwind": "$key"},
+		map[string]interface{}{"$group": map[string]interface{}{"_id": "$key", "sum": map[string]interface{}{"$sum": 1}}},
+		map[string]interface{}{"$sort": map[string]interface{}{"sum": -1}},
+		map[string]interface{}{"$limit": 6},
+	}).All(&res)
+	for _, v := range res {
+		keys = append(keys, v["_id"])
+	}
+	return keys
+}
+func GetWinnerNewestDatas(winner string) *[]map[string]interface{} {
+	if winner == "" {
+		return nil
+	}
+	//判断用户是否关注
+	fields := `"_id","projectname","bidamount","title","publishtime","subtype","toptype","href"`
+	data := elastic.GetPage("bidding", "bidding", `{"TERM_s_winner":"`+winner+`"}`, `{"publishtime":-1}`, fields, 0, 100)
+	if *data != nil {
+		for _, v := range *data {
+			v["_id"] = EncodeArticleId2ByCheck(BsonIdToSId(v["_id"]))
+			v["bidamount"], _ = v["bidamount"].(float64)
+		}
+	}
+	return data
+}
+
+// 删除redis相关数据
+func DelRelRedis(userid interface{}, relationinfo interface{}) {
+	defer util.Catch()
+	uid, _ := userid.(string)
+	if uid == "" || relationinfo == nil {
+		return
+	}
+	array, _ := relationinfo.([]interface{})
+	for _, v := range util.ObjArrToMapArr(array) {
+		sid, _ := v["s_id"].(string)
+		redis.Del("push", "push_"+uid+"_"+sid)
+	}
+}
+
+// 合并两次检索结果(根据filed从大到小)
+// sortFiled 排序字段
+// Mergefiled 判重字段
+func MapArrSortMerge(arr1, arr2 []map[string]interface{}, mergeFiled, sortFiled string) *[]map[string]interface{} {
+	for _, v := range arr1 {
+		for n, m := range arr2 {
+			if util.ObjToString(v["_id"]) == util.ObjToString(m["_id"]) {
+				arr2 = append((arr2)[0:n], (arr2)[n+1:]...)
+				break
+			}
+		}
+	}
+	arr1 = append(arr1, arr2...)
+	return &arr1
+	// var tmp []map[string]interface{}
+	// i, j := 0, 0
+	// var idMap = map[string]bool{}
+	// MaxLenArr1, MaxLenArr2 := len(arr1), len(arr2)
+	// for i < MaxLenArr1 || j < MaxLenArr2 {
+	// 	var mergeTmp interface{}
+	// 	var sign map[string]interface{}
+
+	// 	if len(tmp) > 0 {
+	// 		mergeTmp = tmp[len(tmp)-1][mergeFiled]
+	// 	}
+
+	// 	if i <= MaxLenArr1-1 {
+	// 		sign = arr1[i]
+	// 	}
+	// 	if j <= MaxLenArr2-1 {
+	// 		if util.Int64All(sign[sortFiled]) > util.Int64All(arr2[j][sortFiled]) {
+	// 			i++
+	// 		} else {
+	// 			sign = arr2[j]
+	// 			j++
+	// 		}
+	// 	} else {
+	// 		i++
+	// 	}
+	// 	if mergeTmp != sign[mergeFiled] {
+	// 		if !idMap[util.ObjToString(sign[mergeFiled])] {
+	// 			idMap[util.ObjToString(sign[mergeFiled])] = true
+	// 			tmp = append(tmp, sign)
+	// 		}
+	// 	}
+	// }
+	// return &tmp
+}
+
+// 保存用户搜索日志
+func SaveUserSearchLog(request *http.Request, userid string, count int64, platform, source string, condition map[string]interface{}) {
+	go func() {
+		data := map[string]interface{}{
+			"ip":         util.GetIp(request),
+			"count":      count,
+			"s_userid":   userid,
+			"platform":   platform,
+			"source":     source,
+			"createtime": time.Now().Unix(),
+			"userAgent":  request.Header.Get("User-Agent"),
+		}
+		for _, v := range []string{"pagenum", "pagesize", "search_word", "search_area", "search_publishtime", "search_industry", "search_price", "search_type", "bid_field"} {
+			if vv := condition[v]; vv != nil {
+				data[v] = vv
+			}
+		}
+		Mgo_Log.Save("jy_search_log", data)
+	}()
+}
+func isPhone(value string) bool {
+	var phonePattern = regexp.MustCompile("^[1][3-9][0-9]{9}$")
+	return phonePattern.MatchString(value)
+}
+
+func isEmail(value string) bool {
+	var emailPattern = regexp.MustCompile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$")
+	return emailPattern.MatchString(value)
+}

+ 123 - 0
public/recomKws.go

@@ -0,0 +1,123 @@
+package public
+
+import (
+	"math/rand"
+	"regexp"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	. "app.yhyue.com/moapp/jybase/sort"
+	. "github.com/thinxer/go-word2vec"
+)
+
+var RecomKws = &recomKws{
+	reqPool: make(chan bool, 5),
+}
+
+func init() {
+	RecomKws.model, _ = Load("./zb.bin")
+}
+
+type recomKws struct {
+	reqPool chan bool
+	model   *Model
+}
+
+func (rk *recomKws) GetRecomKws(value string, count int, recommendThreshold float32) []*map[string]interface{} {
+	rk.reqPool <- true
+	defer func() {
+		<-rk.reqPool
+	}()
+	recomPool := make(chan bool, 5)
+	wait := &sync.WaitGroup{}
+	lock := &sync.Mutex{}
+	maxCount := count * 3
+	if maxCount > 100 {
+		maxCount = 100
+	}
+	keys := strings.Split(value, " ")
+	wordMap := map[string]bool{}
+	randomNum := rk.generateRandomNumber(maxCount, count)
+	keyMap := map[string]bool{}
+	allKeyMap := map[string]bool{}
+	for _, key := range keys {
+		for _, v := range strings.Split(key, "+") {
+			v = strings.TrimSpace(v)
+			if v == "" {
+				continue
+			}
+			allKeyMap[v] = true
+		}
+	}
+	sl := &ComSortList{
+		SortKeys: []*ComSortKey{
+			&ComSortKey{
+				Keys:  []string{"sim"},
+				Order: -1,
+				Type:  "float",
+			},
+		},
+		List: []*map[string]interface{}{},
+	}
+	for _, key := range keys {
+		key = strings.TrimSpace(key)
+		if keyMap[key] {
+			continue
+		}
+		keyMap[key] = true
+		recomPool <- true
+		wait.Add(1)
+		go func(v string) {
+			defer func() {
+				<-recomPool
+				wait.Done()
+			}()
+			//获取随机数
+			pw, _ := rk.model.MostSimilar(strings.Split(v, "+"), []string{}, maxCount)
+			for k, p := range pw {
+				p.Word = strings.TrimSpace(p.Word)
+				if sim := p.Sim; sim < recommendThreshold {
+					continue
+				}
+				if p.Word == "" || !randomNum[k] || allKeyMap[p.Word] || len([]rune(p.Word)) == 1 {
+					continue
+				}
+				if strings.HasSuffix(p.Word, "路") || DealString(p.Word) {
+					continue
+				}
+				hzRegexp := regexp.MustCompile("[^A-Za-z0-9\u4e00-\u9fa5]")
+				if hzRegexp.MatchString(p.Word) {
+					continue //过滤乱码
+				}
+				lock.Lock()
+				if !wordMap[p.Word] {
+					sl.List = append(sl.List, &map[string]interface{}{
+						"sim":  p.Sim,
+						"word": p.Word,
+					})
+				}
+				wordMap[p.Word] = true
+				lock.Unlock()
+			}
+		}(key)
+	}
+	wait.Wait()
+	sort.Sort(sl)
+	if len(sl.List) > 100 {
+		return sl.List[:100]
+	}
+	return sl.List
+}
+
+//随机数查询
+func (rk *recomKws) generateRandomNumber(max int, count int) map[int]bool {
+	nums := map[int]bool{}
+	//随机数生成器,加入时间戳保证每次生成的随机数不一样
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	for len(nums) < count {
+		nums[r.Intn(max)] = true
+	}
+	return nums
+}

+ 139 - 0
public/rpccall.go

@@ -0,0 +1,139 @@
+package public
+
+import (
+	"encoding/json"
+	"log"
+	"net/rpc"
+
+	util "app.yhyue.com/moapp/jybase/common"
+	qrpc "app.yhyue.com/moapp/jybase/rpc"
+)
+
+var rpcserver string
+var followPushRpcServer string
+
+func InitRpcCall(Sysconfig map[string]interface{}) {
+	rpcserver, _ = Sysconfig["weixinrpc"].(string)
+	followPushRpcServer, _ = Sysconfig["followPushRpc"].(string)
+}
+
+//发送管理员模板消息
+func SendBidOpenMsg(p *qrpc.NotifyMsg) bool {
+	ok, _ := qrpc.WxPush(rpcserver, "WeiXinRpc.SendBidOpenMsg", p)
+	return ok
+}
+
+//发送数据报告模板消息
+func SendDataReportMsg(p *qrpc.NotifyMsg) bool {
+	ok, _ := qrpc.WxPush(rpcserver, "WeiXinRpc.SendDataReportMsg", p)
+	return ok
+}
+
+//发送保函申请消息
+func SendLOGApplyMsg(p *qrpc.NotifyMsg) bool {
+	ok, _ := qrpc.WxPush(rpcserver, "WeiXinRpc.SendLOGApplyMsg", p)
+	return ok
+}
+
+//发送年终活动消息
+func SendACTIVEApplyMsg(p *qrpc.NotifyMsg) bool {
+	ok, _ := qrpc.WxPush(rpcserver, "WeiXinRpc.SendACTIVEApplyMsg", p)
+	return ok
+}
+
+//项目更新推送
+func FollowPush(p *qrpc.FollowPush) (repls []*map[string]interface{}, err error) {
+	util.Try(func() {
+		client, e := rpc.DialHTTP("tcp", followPushRpcServer)
+		defer client.Close()
+		if e != nil {
+			err = e
+			log.Println(err.Error())
+			return
+		}
+		var repl []byte
+		err = client.Call("FollowPushRpc.FollowPush", p, &repl)
+		if err == nil && repl != nil && len(repl) > 0 {
+			var mp []*map[string]interface{}
+			json.Unmarshal(repl, &mp)
+			repls = mp
+		}
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+	return
+}
+
+//返回项目更新
+func FollowPushBack(p *qrpc.FollowPush) (err error) {
+	util.Try(func() {
+		client, e := rpc.DialHTTP("tcp", followPushRpcServer)
+		defer client.Close()
+		if e != nil {
+			err = e
+			log.Println(err.Error())
+			return
+		}
+		var repl []byte
+		err = client.Call("FollowPushRpc.FollowPush", p, &repl)
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+	return
+}
+
+//分享二维码图片
+func GetShareQR(url uint32) string {
+	var ret string
+	util.Try(func() {
+		client, err := rpc.DialHTTP("tcp", rpcserver)
+		defer client.Close()
+		if err != nil {
+			log.Println(err.Error())
+			return
+		}
+		err = client.Call("WeiXinRpc.GetShareQR", url, &ret)
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+	return ret
+}
+
+//分享二维码图片
+func GetShareQRStr(url string) string {
+	var ret string
+	util.Try(func() {
+		client, err := rpc.DialHTTP("tcp", rpcserver)
+		defer client.Close()
+		if err != nil {
+			log.Println(err.Error())
+			return
+		}
+		err = client.Call("WeiXinRpc.GetShareQRStr", url, &ret)
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+	return ret
+}
+
+//获取未关注用户unionid
+func GetUnionid(openid string) string {
+	var unionid string
+	util.Try(func() {
+		client, err := rpc.DialHTTP("tcp", rpcserver)
+		defer client.Close()
+		if err != nil {
+			log.Println(err.Error())
+			return
+		}
+		err = client.Call("WeiXinRpc.GetUnionid", openid, &unionid)
+		if err != nil {
+			log.Println(err.Error())
+		}
+	}, func(e interface{}) {})
+	return unionid
+}

+ 1002 - 0
public/search.go

@@ -0,0 +1,1002 @@
+package public
+
+//
+//import (
+//	"encoding/json"
+//	"errors"
+//	"fmt"
+//	"log"
+//	"math"
+//	util "app.yhyue.com/moapp/jybase/common"
+//	elastic "app.yhyue.com/moapp/jybase/esv1"
+//	"qfw/util/jy"
+//	"app.yhyue.com/moapp/jybase/redis"
+//	"sort"
+//	"strconv"
+//	"strings"
+//	"sync"
+//	"time"
+//
+//	"go.mongodb.org/mongo-driver/bson"
+//)
+//
+///*筛选条件--关键词*/
+//type KeyWord struct {
+//	Keyword  string   `json:"keyword"`  //关键词
+//	Appended []string `json:"appended"` //附加词
+//	Exclude  []string `json:"exclude"`  //排除词
+//}
+//
+///*筛选条件*/
+//type SieveCondition struct {
+//	Id          string    `json:"id"`
+//	PublishTime string    `json:"publishtime"` //发布时间
+//	Area        []string  `json:"area"`        //地区-省份
+//	City        []string  `json:"city"`        //地区-城市
+//	Region      []string  `json:"region"`      //地区-省份+城市
+//	Industry    []string  `json:"industry"`    //行业
+//	Keyword     []KeyWord `json:"keywords"`    //关键词
+//	Buyer       []string  `json:"buyer"`       //招标单位(采购单位)
+//	Buyerclass  []string  `json:"buyerclass"`  //采购单位类型
+//	Winner      []string  `json:"winner"`      //中标单位
+//	ComeInTime  int64     `json:"comeintime"`  //入库时间(秒)
+//	OpenId      string    `json:"openid"`      //用户openid
+//	MinPrice    string    `json:"minprice"`    //金额——最少
+//	MaxPrice    string    `json:"maxprice"`    //金额——最多
+//	SelectType  string    `json:"selectType"`  //筛选(正文 or 标题)
+//	Subtype     string    `json:"subtype"`     //信息类型
+//	Comeinfrom  string    `json:"comeinfrom"`  //查询来源
+//}
+//
+//const (
+//	INDEX          = "bidding"
+//	TYPE           = "bidding"
+//	bidSearch_sort = `{"publishtime":-1}`
+//)
+//
+//var ExportTable string = "export_search"
+//
+////获取数据导出查询语句
+//func getDataExportSql(scd *SieveCondition) string {
+//	multi_match := `{"multi_match": {"query": %s,"type": "phrase", "fields": [%s]}}`
+//	query := `{"query":{"bool":{"must":[%s],"should":[%s],"minimum_should_match": %d}}}`
+//	query_bool_should := `{"bool":{"should":[%s],"minimum_should_match": 1}}`
+//	query_price := `{"bool":{"must":[{"range":{"bidamount":{%s}}}]}},{"bool":{"must":[{"range":{"budget":{%s}}}],"must_not":[{"range":{"bidamount":{"gte":-1}}}]}}`
+//	query_bool_must := `{"terms":{"%s":[%s]}}`
+//	query_bool_must_and := `{"bool":{"must":[%s]%s}}`
+//	gte := `"gte": %s`
+//	lte := `"lte": %s`
+//
+//	bools := []string{}
+//	musts := []string{fmt.Sprintf(`{"range":{"comeintime":{"lt":%d}}}`, scd.ComeInTime)}
+//	//省份
+//	areaCity := []string{}
+//	if len(scd.Area) > 0 {
+//		areaquery := `{"terms":{"area":[`
+//		for k, v := range scd.Area {
+//			if k > 0 {
+//				areaquery += `,`
+//			}
+//			areaquery += `"` + v + `"`
+//		}
+//		areaquery += `]}}`
+//		areaCity = append(areaCity, areaquery)
+//	}
+//	//城市
+//	if len(scd.City) > 0 {
+//		areaquery := `{"terms":{"city":[`
+//		for k, v := range scd.City {
+//			if k > 0 {
+//				areaquery += `,`
+//			}
+//			areaquery += `"` + v + `"`
+//		}
+//		areaquery += `]}}`
+//		areaCity = append(areaCity, areaquery)
+//	}
+//	if len(areaCity) > 0 {
+//		musts = append(musts, fmt.Sprintf(query_bool_should, strings.Join(areaCity, ",")))
+//	}
+//	//检索日期
+//
+//	starttime := ""
+//	now := time.Unix(scd.ComeInTime, 0)
+//	endtime := fmt.Sprintf("%d", now.Unix())
+//	if scd.PublishTime == "lately-7" { //最近7天
+//		starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-7, 0, 0, 0, 0, time.Local).Unix())
+//	} else if scd.PublishTime == "lately-30" { //最近30天
+//		starttime = fmt.Sprint(time.Date(now.Year(), now.Month(), now.Day()-30, 0, 0, 0, 0, time.Local).Unix())
+//	} else if scd.PublishTime == "thisyear" { //去年
+//		starttime = fmt.Sprint(time.Date(now.Year()-1, 1, 1, 0, 0, 0, 0, time.Local).Unix())
+//		endtime = fmt.Sprint(time.Date(now.Year()-1, 12, 31, 23, 59, 59, 0, time.Local).Unix())
+//	} else if strings.Contains(scd.PublishTime, "_") { //设置检索日期
+//		starttime = strings.Split(scd.PublishTime, "_")[0]
+//		endTime_tmp := now
+//		if etime := strings.Split(scd.PublishTime, "_")[1]; etime != "" {
+//			etTime := time.Unix(util.Int64All(etime), 0)
+//			endTime_tmp = time.Date(etTime.Year(), etTime.Month(), etTime.Day()+1, 0, 0, 0, 0, time.Local)
+//		}
+//		//结束时间必须小于筛选时间
+//		if endTime_tmp.After(now) {
+//			endTime_tmp = now
+//		}
+//		endtime = fmt.Sprintf("%d", endTime_tmp.Unix())
+//	}
+//	timequery := `{"range":{"publishtime":{`
+//	if starttime != "" {
+//		timequery += `"gte":` + starttime
+//	}
+//	if starttime != "" && endtime != "" {
+//		timequery += `,`
+//	}
+//	if endtime != "" {
+//		timequery += `"lt":` + endtime
+//	}
+//	timequery += `}}}`
+//	musts = append(musts, timequery)
+//
+//	if scd.Subtype != "" {
+//		subquery := `{"terms":{"subtype":[`
+//		for k, v := range strings.Split(scd.Subtype, ",") {
+//			if k > 0 {
+//				subquery += `,`
+//			}
+//			subquery += `"` + v + `"`
+//		}
+//		subquery += `]}}`
+//		musts = append(musts, subquery)
+//	}
+//	if len(scd.Industry) > 0 {
+//		musts = append(musts, fmt.Sprintf(query_bool_must, "s_subscopeclass", `"`+strings.Join(scd.Industry, `","`)+`"`))
+//	}
+//	if len(scd.Buyer) > 0 {
+//		musts = append(musts, fmt.Sprintf(query_bool_must, "buyer", `"`+strings.Join(scd.Buyer, `","`)+`"`))
+//	}
+//	if len(scd.Buyerclass) > 0 {
+//		musts = append(musts, fmt.Sprintf(query_bool_must, "buyerclass", `"`+strings.Join(scd.Buyerclass, `","`)+`"`))
+//	}
+//	if len(scd.Winner) > 0 {
+//		musts = append(musts, fmt.Sprintf(query_bool_must, "s_winner", `"`+strings.Join(scd.Winner, `","`)+`"`))
+//	}
+//	_minPrice := ""
+//	_maxPrice := ""
+//	if scd.MinPrice != "" || scd.MaxPrice != "" {
+//		sq := ``
+//		if scd.MinPrice != "" {
+//			min, _ := strconv.ParseFloat(scd.MinPrice, 64)
+//			_minPrice = fmt.Sprintf("%.0f", min*10000)
+//			if _minPrice == "0" {
+//				_minPrice = ""
+//			}
+//		}
+//		if scd.MaxPrice != "" {
+//			max, _ := strconv.ParseFloat(scd.MaxPrice, 64)
+//			_maxPrice = fmt.Sprintf("%.0f", max*10000)
+//			if _maxPrice == "0" {
+//				_maxPrice = ""
+//			}
+//		}
+//		if _minPrice != "" {
+//			sq += fmt.Sprintf(gte, _minPrice)
+//		}
+//		if _minPrice != "" && _maxPrice != "" {
+//			sq += `,`
+//		}
+//		if _maxPrice != "" {
+//			sq += fmt.Sprintf(lte, _maxPrice)
+//		}
+//		if _minPrice != "" || _maxPrice != "" {
+//			query_price := fmt.Sprintf(query_bool_should, fmt.Sprintf(query_price, sq, sq))
+//			musts = append(musts, query_price)
+//		}
+//	}
+//	boolsNum := 0
+//	//should
+//	if len(scd.Keyword) > 0 {
+//		boolsNum = 1
+//		if scd.SelectType == "" || scd.SelectType == "all" {
+//			scd.SelectType = "detail\", \"title"
+//		}
+//		multi_match = fmt.Sprintf(multi_match, "%s", "\""+scd.SelectType+"\"")
+//
+//		if scd.Comeinfrom == "supersearchPage" {
+//			var keywordArr []string
+//			if strings.Contains(scd.Keyword[0].Keyword, "+") {
+//				keywordArr = strings.Split(scd.Keyword[0].Keyword, "+")
+//			} else if strings.Contains(scd.Keyword[0].Keyword, " ") {
+//				keywordArr = strings.Split(scd.Keyword[0].Keyword, " ")
+//			}
+//			if len(keywordArr) > 1 {
+//				KeyWordSearch := KeyWord{}
+//				for _, v := range keywordArr {
+//					KeyWordSearch.Appended = append(KeyWordSearch.Appended, v)
+//				}
+//				scd.Keyword = []KeyWord{KeyWordSearch}
+//			}
+//		}
+//
+//		for _, v := range scd.Keyword {
+//			shoulds := []string{}
+//			must_not := []string{}
+//			//附加词
+//			if v.Keyword != "" {
+//				shoulds = append(shoulds, fmt.Sprintf(multi_match, "\""+v.Keyword+"\""))
+//			}
+//
+//			for _, vv := range v.Appended {
+//				shoulds = append(shoulds, fmt.Sprintf(multi_match, "\""+vv+"\""))
+//			}
+//
+//			//排除词
+//			for _, vv := range v.Exclude {
+//				must_not = append(must_not, fmt.Sprintf(multi_match, "\""+vv+"\""))
+//			}
+//
+//			//添加
+//			if len(shoulds) > 0 {
+//				notStr := ""
+//				if len(must_not) > 0 {
+//					notStr = fmt.Sprintf(`,"must_not":[%s]`, strings.Join(must_not, ","))
+//				}
+//				bools = append(bools, fmt.Sprintf(query_bool_must_and, strings.Join(shoulds, ","), notStr))
+//			}
+//		}
+//	}
+//	qstr := fmt.Sprintf(query, strings.Join(musts, ","), strings.Join(bools, ","), boolsNum)
+//	return qstr
+//}
+//
+//func getSqlObjFromId(_id string) *SieveCondition {
+//	var (
+//		query *map[string]interface{}
+//		ok    bool
+//	)
+//	if query, ok = MQFW.FindById(ExportTable, _id, nil); !ok {
+//		return nil
+//	}
+//	return &SieveCondition{
+//		Id:          _id,
+//		Keyword:     getKeyWordArrFromDbResult((*query)["keywords"]),
+//		Industry:    getStringArrFromDbResult((*query)["industry"]),
+//		MinPrice:    util.ObjToString((*query)["minprice"]),
+//		MaxPrice:    util.ObjToString((*query)["maxprice"]),
+//		Subtype:     util.ObjToString((*query)["subtype"]),
+//		Area:        getStringArrFromDbResult((*query)["area"]),
+//		City:        getStringArrFromDbResult((*query)["city"]),
+//		SelectType:  util.ObjToString((*query)["selectType"]),
+//		PublishTime: util.ObjToString((*query)["publishtime"]),
+//		Buyer:       getStringArrFromDbResult((*query)["buyer"]),
+//		Buyerclass:  getStringArrFromDbResult((*query)["buyerclass"]),
+//		Winner:      getStringArrFromDbResult((*query)["winner"]),
+//		ComeInTime:  util.Int64All((*query)["comeintime"]),
+//		Comeinfrom:  util.ObjToString((*query)["comeinfrom"]),
+//	}
+//}
+//
+////数据导出-查询结果数量
+//func GetDataExportSearchCountUseId(_id string) (count int) {
+//	scd := getSqlObjFromId(_id)
+//	qstr := getDataExportSql(scd)
+//	log.Printf("GetDataExportSearchCountUseId-%s-count:%d-sql:%s\n", _id, count, qstr)
+//	if isNullSearch(scd) {
+//		return ExConf.MsgMaxCount
+//	}
+//	count = int(elastic.Count(INDEX, TYPE, qstr))
+//	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+//	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) {
+//		if len(scd.Keyword) != 0 {
+//			searchTextSize := 0
+//			if len(scd.Keyword) > 0 {
+//				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+//			}
+//			if searchTextSize > 3 && count < 50 {
+//				var res *[]map[string]interface{}
+//				if count > 0 {
+//					res = doSearch(qstr, 0, count, "")
+//				}
+//				secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", DbConf.Elasticsearch.Main.Address)
+//				scd.Keyword[0].Keyword = secondKWS
+//				scd.SelectType = "title"
+//				qstr = getDataExportSql(scd)
+//				res2 := doSearch(qstr, 0, 100, "")
+//				result := len(*delRepeatMapArr(res, res2))
+//				if result > 100 {
+//					result = 100
+//				}
+//				log.Printf("GetDataExportSearchCountUseId-%s-count:%d-分词-sql:%s\n", _id, result, qstr)
+//				return result
+//			}
+//			return
+//		}
+//	}
+//	log.Printf("GetDataExportSearchCountUseId-%s-count:%d\n", _id, count)
+//	return
+//}
+//
+////合并map数据,去重
+//func delRepeatMapArr(res *[]map[string]interface{}, res2 *[]map[string]interface{}) *[]map[string]interface{} {
+//	if res != nil {
+//		for _, v := range *res {
+//			for n, m := range *res2 {
+//				if util.ObjToString(v["_id"]) == util.ObjToString(m["_id"]) {
+//					*res2 = append((*res2)[0:n], (*res2)[n+1:]...)
+//					break
+//				}
+//			}
+//		}
+//		*res = append(*res, *res2...)
+//	} else {
+//		res = res2
+//	}
+//	return res
+//}
+//
+////查询条件是否为空
+//func isNullSearch(scd *SieveCondition) (isNull bool) {
+//	if scd.PublishTime == "" && len(scd.Area) == 0 && len(scd.Industry) == 0 && len(scd.Keyword) == 0 && len(scd.Buyer) == 0 && len(scd.Winner) == 0 && scd.MinPrice == "" && scd.MaxPrice == "" && scd.Subtype == "" && len(scd.City) == 0 {
+//		isNull = true
+//	}
+//	return isNull
+//}
+//
+///*
+// * 数据导出 查询结果
+// * _id 数据库查询条件记录id
+// * dataType 1-普通字段 2-高级字段
+// * webdomain 三级页域名
+// * count 返回数量 (-1:预览数据查询)
+// */
+//var EntTable = "winner_enterprise"
+//
+//func GetDataExportSearchResultUseId(_id, dataType string, count int) (*[]map[string]interface{}, []KeyWord) {
+//	defer util.Catch()
+//	var res []map[string]interface{}
+//	var kws []KeyWord
+//	scd := getSqlObjFromId(_id)
+//	//获取查询语句
+//	qstr := getDataExportSql(scd)
+//	log.Printf("GetDataExportSearchResultUseId-%s-sql:%s\n", scd.Id, qstr)
+//	kws = scd.Keyword
+//	//数据预览数据查询
+//	if scd.Comeinfrom == "supersearchPage" && len(scd.Keyword) == 0 && len(scd.Industry) == 0 {
+//		//空查询
+//		obj := redis.Get("other", "export_news")
+//		if obj != nil {
+//			res = util.ObjArrToMapArr(obj.([]interface{}))
+//		} else {
+//			res = *doSearch(qstr, 0, 500, "2")
+//			if len(res) > 0 {
+//				redis.Put("other", "export_news", res, 7200)
+//			}
+//		}
+//		return &res, kws
+//	} else if scd.Comeinfrom == "supersearchPage" {
+//		//超级搜索非空查询
+//		count = int(elastic.Count(INDEX, TYPE, qstr))
+//		searchTextSize := 0
+//		if len(scd.Keyword) > 0 {
+//			searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+//		}
+//		if searchTextSize > 3 && count < 50 {
+//			var res *[]map[string]interface{}
+//			if count > 0 {
+//				res = doSearch(qstr, 0, count, "")
+//			}
+//			secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", DbConf.Elasticsearch.Main.Address)
+//			scd.Keyword[0].Keyword = secondKWS
+//			scd.SelectType = "title"
+//			qstr = getDataExportSql(scd)
+//			log.Printf("GetDataExportSearchResultUseId-%s-分词-sql:%s\n", scd.Id, qstr)
+//			res2 := doSearch(qstr, 0, 100, "")
+//			if len(*res2) > 100 {
+//				res2_temp := (*res2)[:100]
+//				res2 = &res2_temp
+//			}
+//			return delRepeatMapArr(res, res2), kws
+//		}
+//	}
+//	//非空查询
+//	res = *doSearch(qstr, 0, 500, dataType)
+//	return &res, kws
+//}
+//
+//func FormatExportData(data *[]map[string]interface{}, webdomain string, dataType string, EntArr []string) *[]map[string]interface{} {
+//	//格式化输出
+//	for _, v := range *data {
+//		//有中标企业 且 高级字段查询
+//		if len(EntArr) > 0 && dataType == "2" {
+//			//查询企业公示 法人 公司电话 公司邮箱地址
+//			query := bson.M{"company_name": bson.M{"$in": EntArr}} //
+//			if entData, ok := Mgo_Ent.Find(EntTable, query, nil, `{"company_name":1,"company_email":1,"legal_person":1,"company_phone":1}`, false, -1, -1); ok {
+//				if entData != nil && *entData != nil && len(*entData) > 0 {
+//					for _, ev := range *entData {
+//						if v["s_winner"] == ev["company_name"] {
+//							legal_person := ""
+//							if ev["legal_person"] != nil && ev["legal_person"].(string) != "" {
+//								legal_person = ev["legal_person"].(string)
+//								var xx = "*"
+//								switch len([]rune(legal_person)) {
+//								case 3:
+//									xx = "**"
+//								case 4:
+//									xx = "***"
+//								}
+//								legal_person = string([]rune(legal_person)[:1]) + xx
+//							}
+//							company_phone := ""
+//							if ev["company_phone"] != nil && ev["company_phone"].(string) != "" {
+//								company_phone = ev["company_phone"].(string)
+//								if len([]rune(company_phone)) > 7 {
+//									company_phone = company_phone[:7] + "****"
+//								} else {
+//									company_phone = "****"
+//								}
+//							}
+//							company_email := ""
+//							if ev["company_email"] != nil && ev["company_email"].(string) != "无" {
+//								company_email = ev["company_email"].(string)
+//								if len(strings.Split(company_email, "@")) > 1 {
+//									company_email = "******" + "@" + strings.Split(company_email, "@")[1]
+//								}
+//							}
+//							v["legal_person"] = legal_person
+//							v["company_phone"] = company_phone
+//							v["company_email"] = company_email
+//						}
+//					}
+//				}
+//			}
+//		}
+//		//====================字段补漏=========================
+//		if v["toptype"] == "结果" && dataType == "2" && !(v["agency"] != nil && v["budget"] != nil && v["buyerperson"] != nil && v["buyertel"] != nil) {
+//			r := elastic.Get("projectset", "projectset", fmt.Sprintf(`{"query":{"term":{"list.infoid":"%s"}},"_source": ["list"]}`, v["_id"]))
+//			if len(*r) > 0 {
+//				MsgList := (*r)[0]["list"]
+//				if MsgList != nil {
+//					list := util.ObjArrToMapArr(MsgList.([]interface{}))
+//					for _, vv := range list {
+//						if vv["subtype"] == "招标" {
+//							if v["agency"] == nil && vv["agency"] != nil {
+//								v["agency"] = vv["agency"]
+//							}
+//							if v["budget"] == nil && vv["budget"] != nil {
+//								v["budget"] = vv["budget"]
+//							}
+//							if v["buyerperson"] == nil && vv["buyerperson"] != nil {
+//								v["buyerperson"] = vv["buyerperson"]
+//							}
+//							if v["buyertel"] == nil && vv["buyertel"] != nil {
+//								v["buyertel"] = vv["buyertel"]
+//							}
+//							break
+//						}
+//					}
+//				}
+//			}
+//		}
+//		if v["area"] == "A" {
+//			v["area"] = "全国"
+//		}
+//		if v["bidamount"] != nil {
+//			v["bidamount"] = formatFloat(util.Float64All(v["bidamount"]))
+//		}
+//		if v["budget"] != nil {
+//			v["budget"] = formatFloat(util.Float64All(v["budget"]))
+//		}
+//		if v["publishtime"] != nil {
+//			date := v["publishtime"]
+//			v["publishtime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//		}
+//		if v["bidopentime"] != nil {
+//			date := v["bidopentime"]
+//			v["bidopentime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//		}
+//		if v["_id"] != nil {
+//			v["url"] = webdomain + "/article/content/" + util.CommonEncodeArticle("content", v["_id"].(string)) + ".html"
+//		}
+//		if v["currency"] == "" || v["currency"] == nil {
+//			v["currency"] = "人民币"
+//		}
+//		if v["projectscope"] != "" && v["projectscope"] != nil {
+//			str := ClearHtml.ReplaceAllString(v["projectscope"].(string), "")
+//			str = ClearOther.ReplaceAllString(str, "")
+//			str = strings.Replace(str, " ", "", -1)
+//			if len([]rune(str)) > 100 {
+//				str = util.SubString(str, 0, 100) + "..."
+//			}
+//			v["projectscope"] = str
+//		}
+//		if v["detail"] != "" && v["detail"] != nil {
+//			str := ClearHtml.ReplaceAllString(v["detail"].(string), "")
+//			str = ClearOther.ReplaceAllString(str, "")
+//			str = strings.Replace(str, " ", "", -1)
+//			if len([]rune(str)) > 100 {
+//				str = util.SubString(str, 0, 100) + "..."
+//			}
+//			v["detail"] = str
+//		}
+//		if v["title"] != "" && v["title"] != nil {
+//			str := ClearHtml.ReplaceAllString(v["title"].(string), "")
+//			str = ClearOther.ReplaceAllString(str, "")
+//			str = strings.Replace(str, " ", "", -1)
+//			if len([]rune(str)) > 100 {
+//				str = util.SubString(str, 0, 100) + "..."
+//			}
+//			v["title"] = str
+//		}
+//		if v["subtype"] == nil && v["toptype"] != nil {
+//			v["subtype"] = v["toptype"]
+//		}
+//	}
+//	return data
+//}
+//
+////保留到0.01分
+//func formatFloat(value float64) string {
+//	str := strings.TrimRight(fmt.Sprintf("%.7f", value*10000/100000000), "0")
+//	if str[len(str)-1:] == "." {
+//		return str[:len(str)-1]
+//	}
+//	return str
+//}
+//
+//func FormatExportDatas(data *[]map[string]interface{}, webdomain string, dataType string, entId int) *[]map[string]interface{} {
+//	//格式化输出
+//	var (
+//		entexportPool      = make(chan bool, 20)
+//		entexportWaitGroup = &sync.WaitGroup{}
+//		// newsDatalen        = make(chan bool, 20000)
+//		// datalock           = sync.RWMutex{}
+//	)
+//	log.Println("补充信息开始")
+//	for _, v := range *data {
+//		entexportWaitGroup.Add(1)
+//		entexportPool <- true
+//		go func(v map[string]interface{}) {
+//			defer func() {
+//				entexportWaitGroup.Done()
+//				<-entexportPool
+//				// datalock.Unlock()
+//			}()
+//			// datalock.Lock()
+//			//二次验证
+//			// id := util.ObjToString(v["_id"])
+//			// isExist, err := redis.Exists("other", "entexportdata_"+id+"_"+fmt.Sprintln(entId))
+//			// if err != nil {
+//			// 	log.Println("企业搜索数据导出redis判重失败")
+//			// } else if !isExist {
+//			// 	newsDatalen <- true
+//			// }
+//			//
+//			//有中标企业 且 高级字段查询
+//			if dataType == "2" {
+//				//查询企业公示 法人 公司电话 公司邮箱地址
+//				// query := bson.M{"company_name": bson.M{"$in": EntArr}} //
+//				s_winner := strings.Split(util.ObjToString(v["s_winner"]), ",")[0]
+//				query := bson.M{"company_name": s_winner} //
+//				if entData, ok := Mgo_Ent.Find(EntTable, query, nil, `{"company_name":1,"company_email":1,"legal_person":1,"company_phone":1}`, false, -1, -1); ok {
+//					if entData != nil && *entData != nil && len(*entData) > 0 {
+//						for _, ev := range *entData {
+//							if v["s_winner"] == ev["company_name"] {
+//								legal_person := ""
+//								if ev["legal_person"] != nil {
+//									legal_person = ev["legal_person"].(string)
+//								}
+//								company_phone := ""
+//								if ev["company_phone"] != nil {
+//									company_phone = ev["company_phone"].(string)
+//								}
+//								company_email := ""
+//								if ev["company_email"] != nil && ev["company_email"] != "无" {
+//									company_email = ev["company_email"].(string)
+//								}
+//								v["legal_person"] = legal_person
+//								v["company_phone"] = company_phone
+//								v["company_email"] = company_email
+//							}
+//						}
+//					}
+//				}
+//			}
+//			//====================字段补漏=========================
+//			if v["toptype"] == "结果" && dataType == "2" && !(v["agency"] != nil && v["budget"] != nil && v["buyerperson"] != nil && v["buyertel"] != nil) {
+//				r := elastic.Get("projectset", "projectset", fmt.Sprintf(`{"query":{"term":{"list.infoid":"%s"}},"_source": ["list"]}`, v["_id"]))
+//				if len(*r) > 0 {
+//					MsgList := (*r)[0]["list"]
+//					if MsgList != nil {
+//						list := util.ObjArrToMapArr(MsgList.([]interface{}))
+//						for _, vv := range list {
+//							if vv["subtype"] == "招标" {
+//								if v["agency"] == nil && vv["agency"] != nil {
+//									v["agency"] = vv["agency"]
+//								}
+//								if v["budget"] == nil && vv["budget"] != nil {
+//									v["budget"] = vv["budget"]
+//								}
+//								if v["buyerperson"] == nil && vv["buyerperson"] != nil {
+//									v["buyerperson"] = vv["buyerperson"]
+//								}
+//								if v["buyertel"] == nil && vv["buyertel"] != nil {
+//									v["buyertel"] = vv["buyertel"]
+//								}
+//								break
+//							}
+//						}
+//					}
+//				}
+//			}
+//			if v["area"] == "A" {
+//				v["area"] = "全国"
+//			}
+//			// if v["bidamount"] != nil {
+//			// 	v["bidamount"] = fmt.Sprintf("%g", util.Float64All(v["bidamount"]))
+//			// }
+//			// if v["budget"] != nil {
+//			// 	v["budget"] = fmt.Sprintf("%g", util.Float64All(v["budget"]))
+//			// }
+//			if v["publishtime"] != nil {
+//				date := v["publishtime"]
+//				v["publishtime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//			}
+//			if v["bidopentime"] != nil {
+//				date := v["bidopentime"]
+//				v["bidopentime"] = util.FormatDateWithObj(&date, util.Date_Short_Layout)
+//			}
+//			if v["currency"] == "" || v["currency"] == nil {
+//				v["currency"] = "人民币"
+//			}
+//			if v["subtype"] == nil && v["toptype"] != nil {
+//				v["subtype"] = v["toptype"]
+//			}
+//			if v["detail"] != "" && v["detail"] != nil {
+//				str := ClearHtml.ReplaceAllString(v["detail"].(string), "")
+//				str = ClearOther.ReplaceAllString(str, "")
+//				str = strings.Replace(str, " ", "", -1)
+//				v["detail"] = str
+//			}
+//			if v["_id"] != nil {
+//				v["url"] = webdomain + "/article/content/" + util.CommonEncodeArticle("content", v["_id"].(string)) + ".html"
+//			}
+//		}(v)
+//	}
+//	entexportWaitGroup.Wait()
+//	log.Println("补充信息结束")
+//	return data
+//}
+//
+//func doSearch(sql string, start, count int, dataType string) *[]map[string]interface{} {
+//	if sql != "" {
+//		//筛选字段
+//		if dataType != "" {
+//			dataexport_field := `"_id","title","detail","area","city","publishtime","projectname","buyer","s_winner","bidamount","subtype","toptype"`
+//			if dataType == "2" {
+//				dataexport_field += `,"href","projectcode","buyerperson","buyertel","budget","bidopentime","agency","projectscope","winnerperson","winnertel"`
+//			}
+//			sql = sql[:len(sql)-1] + `,"_source":[` + dataexport_field + "]}"
+//		}
+//		//分页排序
+//		sql = sql[:len(sql)-1] + `,"sort": {"publishtime":"desc"},"from":` + strconv.Itoa(start) + `,"size":` + strconv.Itoa(count) + "}"
+//	}
+//	return elastic.Get(INDEX, TYPE, sql)
+//}
+//
+//func getKeyWordArrFromDbResult(k interface{}) (arr []KeyWord) {
+//	if k == nil {
+//		return
+//	}
+//	kArr := k.([]interface{})
+//	for _, v := range kArr {
+//		kw := KeyWord{}
+//		b, e := json.Marshal(v)
+//		if e != nil {
+//			log.Println(e.Error())
+//		}
+//		json.Unmarshal(b, &kw)
+//		arr = append(arr, kw)
+//	}
+//	return
+//}
+//
+//func getStringArrFromDbResult(c interface{}) (arr []string) {
+//	if c != nil {
+//		cArr := c.([]interface{})
+//		arr = util.ObjArrToStringArr(cArr)
+//	}
+//	return
+//}
+//
+////获取结果,空字段最少的数据
+//func ScreenData(arr *[]map[string]interface{}, dataType string, resultNum int, kws []KeyWord) (res []map[string]interface{}) {
+//	AllMap := map[int][]map[string]interface{}{}
+//
+//	NoKwsMap := map[int][]map[string]interface{}{}
+//	lastNum := resultNum
+//	for _, v := range *arr {
+//		emptyNum := countOfTheEmpty(v, dataType)
+//		if emptyNum == -1 {
+//			continue
+//		}
+//		if len(kws) > 0 && kws[0].Keyword != "" {
+//			var kwsFlag = true
+//			for _, vk := range kws {
+//				if strings.Contains(util.ObjToString(v["title"]), strings.Replace(vk.Keyword, "+", "", -1)) {
+//					kwsFlag = false
+//					continue
+//				}
+//			}
+//			if kwsFlag {
+//				if NoKwsMap[emptyNum] == nil {
+//					NoKwsMap[emptyNum] = []map[string]interface{}{v}
+//				} else {
+//					NoKwsMap[emptyNum] = append(NoKwsMap[emptyNum], v)
+//				}
+//				continue
+//			}
+//		}
+//		if AllMap[emptyNum] == nil {
+//			AllMap[emptyNum] = []map[string]interface{}{v}
+//			continue
+//		}
+//		AllMap[emptyNum] = append(AllMap[emptyNum], v)
+//	}
+//	//获取key
+//	keys := []int{}
+//	for k, _ := range AllMap {
+//		keys = append(keys, k)
+//	}
+//	sort.Ints(keys)
+//	log.Println("空字段数量", keys)
+//	//选取结果
+//	for _, v := range keys {
+//		if len(AllMap[v]) >= resultNum {
+//			return append(res, AllMap[v][:resultNum]...)
+//		} else {
+//			resultNum = resultNum - len(AllMap[v])
+//			tmp := append(res, AllMap[v][:len(AllMap[v])]...)
+//			res = tmp
+//		}
+//	}
+//	if len(res) < lastNum {
+//		resultNum = lastNum - len(res)
+//		//获取key
+//		Nokeys := []int{}
+//		for k, _ := range NoKwsMap {
+//			Nokeys = append(Nokeys, k)
+//		}
+//		sort.Ints(Nokeys)
+//		log.Println("没关键词的空字段数量", Nokeys)
+//		//选取结果
+//		for _, v := range Nokeys {
+//			if len(NoKwsMap[v]) >= resultNum {
+//				return append(res, NoKwsMap[v][:resultNum]...)
+//			} else {
+//				resultNum = resultNum - len(NoKwsMap[v])
+//				tmp := append(res, NoKwsMap[v][:len(NoKwsMap[v])]...)
+//				res = tmp
+//			}
+//		}
+//	}
+//	return res
+//}
+//func countOfTheEmpty(m map[string]interface{}, dataType string) int {
+//	MsgType := m["subtype"]
+//	//	if MsgType == "拟建" {
+//	//		return -1
+//	//	}
+//	//计算空字段数量
+//	var count int = 0
+//	//高级字段包
+//	if dataType == "2" {
+//		if m["href"] == "" || m["href"] == nil {
+//			count++
+//		}
+//		if m["projectcode"] == "" || m["projectcode"] == nil {
+//			count++
+//		}
+//		if m["buyerperson"] == "" || m["buyerperson"] == nil {
+//			count++
+//		}
+//		if m["buyertel"] == "" || m["buyertel"] == nil {
+//			count++
+//		}
+//		if m["budget"] == "" || m["budget"] == nil {
+//			count++
+//		}
+//		if m["bidopentime"] == "" || m["bidopentime"] == nil {
+//			count++
+//		}
+//		if m["agency"] == "" || m["agency"] == nil {
+//			count++
+//		}
+//		if m["projectscope"] == "" || m["projectscope"] == nil {
+//			count++
+//		}
+//	}
+//	if m["city"] == "" || m["city"] == nil {
+//		count++
+//	}
+//	if m["publishtime"] == "" || m["publishtime"] == nil {
+//		count++
+//	}
+//	if m["projectname"] == "" || m["projectname"] == nil {
+//		count++
+//	}
+//	if m["buyer"] == "" || m["buyer"] == nil {
+//		count++
+//	}
+//	if m["s_winner"] == "" || m["s_winner"] == nil {
+//		if MsgType != "招标" {
+//			count++
+//		}
+//	}
+//	if m["bidamount"] == "" || m["bidamount"] == nil {
+//		if MsgType != "招标" {
+//			count++
+//		}
+//	}
+//	if m["subtype"] == "" || m["subtype"] == nil {
+//		count++
+//	}
+//	return count
+//}
+//
+//func GetEntDataExportCount(_id string, entId, entUserId, limitNum, current int, isFirst bool) (count, newCount int, data *[]map[string]interface{}) {
+//	defer util.Catch()
+//	var (
+//		searchsWaitGroup = &sync.WaitGroup{}
+//		searchsPool      = make(chan bool, 20)
+//		// res              = &[]map[string]interface{}{}
+//		newCountPool = make(chan bool, 20000)
+//	)
+//	count = GetDataExportSearchCountUseId(_id)
+//	log.Println("count", count)
+//	if count > ExConf.MsgMaxCount {
+//		count = ExConf.MsgMaxCount
+//	}
+//	dataType := "2"
+//	//数据导出数据查询
+//
+//	res, err := GetDataExportSearchResult(_id, dataType, count)
+//	if err != nil {
+//		log.Println("企业数据导出错误 ", err)
+//		return 0, 0, nil
+//	}
+//	// secondCount := 0
+//
+//	// isOK := true
+//	// if secondCount > current {
+//	// 	isOK = false
+//	// }
+//	for _, v := range *res {
+//		searchsWaitGroup.Add(1)
+//		searchsPool <- true
+//		id := util.ObjToString(v["_id"])
+//		go func(id string) {
+//			defer func() {
+//				searchsWaitGroup.Done()
+//				<-searchsPool
+//			}()
+//			isExist, err := redis.Exists("other", "entexportdata_"+id+"_"+fmt.Sprintln(entId))
+//			if err != nil {
+//				log.Println("企业搜索数据导出redis判重失败")
+//			} else if isExist {
+//				log.Println("数据重复,id ", id, "entid ", entId, "userid ", entUserId)
+//				return
+//			}
+//			newCountPool <- true
+//			if !isFirst {
+//				redis.Put("other", "entexportdata_"+id+"_"+fmt.Sprintln(entId), 1, -1)
+//			}
+//		}(id)
+//		if !isFirst {
+//			delete(v, "_id")
+//			v["entid"] = entId
+//			v["userid"] = entUserId
+//			v["infoid"] = id
+//			v["createtime"] = time.Now().Unix()
+//		}
+//	}
+//	searchsWaitGroup.Wait()
+//	log.Println("企业数据导出--数据遍历完成")
+//	newCount = len(newCountPool)
+//	log.Println("new", newCount)
+//	data = res
+//	return
+//}
+//
+//func GetDataExportSearchResult(id string, dataType string, checkCount int) (*[]map[string]interface{}, error) {
+//	defer util.Catch()
+//	var (
+//		onceSearchCount = 500
+//		searchPool      = make(chan bool, 20)
+//		res             []map[string]interface{}
+//	)
+//	//获取查询语句
+//	scd := getSqlObjFromId(id)
+//	if scd == nil {
+//		return nil, errors.New("GetDataExportSearchResult-获取查询条件")
+//	}
+//	qstr := getDataExportSql(scd)
+//	log.Printf("GetDataExportSearchResult-%s-sql:%s\n", scd.Id, qstr)
+//	//数据导出数据查询
+//	if checkCount > onceSearchCount { //分批次查询
+//		batchNum := util.IntAll(math.Ceil(float64(checkCount) / float64(onceSearchCount)))
+//		var searchWaitGroup = &sync.WaitGroup{}
+//		var lock sync.Mutex
+//		for n := 0; n < batchNum; n++ {
+//			searchWaitGroup.Add(1)
+//			searchPool <- true
+//			go func(start int) {
+//				defer func() {
+//					searchWaitGroup.Done()
+//					<-searchPool
+//				}()
+//				checkNum, checkOk := onceSearchCount, false
+//				if start == (batchNum - 1) {
+//					if checkCount%onceSearchCount != 0 {
+//						checkNum = checkCount % onceSearchCount
+//					}
+//				}
+//
+//				var tmp *[]map[string]interface{}
+//				for i := 0; i < 3; i++ {
+//					tmp = doSearch(qstr, start*onceSearchCount, onceSearchCount, dataType)
+//					if tmp != nil && (len(*tmp) == checkNum) { //校验数据量是否够
+//						checkOk = true
+//						break
+//					}
+//				}
+//				if tmp == nil {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据查询结果为空\n", scd.Id, start+1)
+//					return
+//				}
+//				if checkOk {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据加载完成,共%d条\n", scd.Id, start+1, len(*tmp))
+//				} else {
+//					log.Printf("GetDataExportSearchResult-%s-第%d页数据加载异常,共%d条,预期%d条\n", scd.Id, start+1, len(*tmp), checkNum)
+//				}
+//				lock.Lock()
+//				res = append(res, *tmp...)
+//				lock.Unlock()
+//			}(n)
+//		}
+//		searchWaitGroup.Wait()
+//		log.Printf("GetDataExportSearchResult-%s-分批次加载数据总量为%d\n", scd.Id, len(res))
+//	} else {
+//		tmp := doSearch(qstr, 0, checkCount, dataType)
+//		if tmp == nil || len(*tmp) == 0 {
+//			log.Printf("GetDataExportSearchResult-%s-一次性加载数据异常\n", scd.Id)
+//		} else {
+//			res = *tmp
+//			log.Printf("GetDataExportSearchResult-%s-一次性加载数据总量为%d\n", scd.Id, len(res))
+//		}
+//	}
+//	//超级搜索一致的检索(防止数据导出和超级搜索数据量不一致)
+//	if scd.Comeinfrom == "supersearchPage" && (len(scd.Keyword) != 0 || len(scd.Industry) != 0) {
+//		if len(scd.Keyword) != 0 {
+//			num := len(res)
+//			searchTextSize := 0
+//			if len(scd.Keyword) > 0 {
+//				searchTextSize = len([]rune(scd.Keyword[0].Keyword))
+//			}
+//			if searchTextSize > 3 && num < 50 {
+//				secondKWS := jy.HttpEs(scd.Keyword[0].Keyword, "ik_smart", DbConf.Elasticsearch.Main.Address)
+//				scd.Keyword[0].Keyword = secondKWS
+//				scd.SelectType = "title"
+//				qstr = getDataExportSql(scd)
+//				log.Printf("GetDataExportSearchResult-%s-分词查询-sql:%s\n", scd.Id, qstr)
+//				res2 := doSearch(qstr, 0, 100, "")
+//				res = *delRepeatMapArr(&res, res2)
+//				if len(res) > 100 {
+//					res = res[:100]
+//				}
+//			}
+//		}
+//	}
+//	//校验数量
+//	if checkCount != len(res) {
+//		return nil, fmt.Errorf("GetDataExportSearchResult-%s-数据总量校验异常,期望:%d,实际:%d", scd.Id, checkCount, len(res))
+//		//发邮件
+//	}
+//	return &res, nil
+//}

+ 53 - 0
public/structed.go

@@ -0,0 +1,53 @@
+package public
+
+import (
+	"fmt"
+	"log"
+	"time"
+
+	"app.yhyue.com/moapp/jybase/mail"
+)
+
+func SendStructedDataByEmail(email string, auth []*mail.GmailAuth, Sysconfig, Seoconfig map[string]interface{}) bool {
+	var uploadAddress = Sysconfig["webdomain"].(string) + Seoconfig["structedUploadA"].(string)
+	var imagesAddress = Sysconfig["webdomain"].(string) + "/structuredata/mobile/image/emaillogo.png"
+	var feedbackAddress = "mailto:bd@topnet.net.cn"
+
+	if isEmail(email) {
+		html := fmt.Sprintf(`<div style="width:100%%;display: flex;justify-content: center;">
+		<div class="Email" id="Email" style="max-width:100%%;min-height: 540px;display: flex;flex-direction: column;align-items: center;">
+        <div class="emailmain" style="border: 1px solid #ccc;max-width:100%%;height: 319px;border-top: 8px solid #2CB7CA;border-radius: 8px;background: #fff;margin: 0 auto; margin-top: 107px;padding: 0px 40px 20px;">
+            <div class="emailmain_top" style="height: 88px;max-width:100%%;border-bottom: 1px solid #F7F7F7;display: flex;justify-content: center;align-items: center;">
+                <img src="%s" alt="" style="width: 120px;height: 32px;">
+            </div>
+            <div class="emailmain_bottom" style="display: flex;flex-direction: column;align-items: center;">
+                <p class="emailmain_text" style="margin-top: 32px;width: 100%%;min-height: 72px;font-size: 16px;line-height: 24px;color: #1D1D1D;">
+                    尊敬的剑鱼标讯用户:<br> 您好,感谢您使用剑鱼标讯的结构化招标数据产品,免费样例已发送到您的邮箱,请查收!如有问题,可拨打<span style="color: #0987FF;cursor: pointer;">400-108-6670</span>,客服人员将诚挚为您服务。
+                </p>
+                <div class="download" style="margin-top: 32px; max-width:100%%; height: 30px;display: flex;justify-content: center;align-items: center;">
+                    <a href="%s" class="download_btn" style="width: 116px;height: 30px;display: flex;text-decoration: none;justify-content: center;align-items: center;font-size: 14px;line-height: 24px;color: #fff;background: #2CB7CA;border-radius: 4px;">下载免费样例 </a>
+                </div>
+            </div>
+        </div>
+        <p class="havapro" style="font-size: 14px;height: 24px;color: #686868;margin-top: 32px;">
+            如有问题请<a href="%s" class="click_here"  id="click_here" style="color: #0987FF;cursor: pointer;">点击此处</a>,进行意见反馈
+        </p>
+    </div></div>`, imagesAddress, uploadAddress, feedbackAddress)
+
+		for k, v := range auth {
+			if mail.GSendMail("剑鱼标讯", email, "", "", "【剑鱼标讯】结构化招标数据", html, "", "", v) {
+				log.Println(email, fmt.Sprintf("使用%s发送邮件成功", v.User))
+				return true
+			}
+			if k < len(auth)-1 {
+				log.Println(email, fmt.Sprintf("使用%s发送邮件失败!3s后使用其他邮箱尝试", v.User))
+			} else {
+				log.Println(email, fmt.Sprintf("使用%s发送邮件失败!", v.User))
+			}
+			time.Sleep(time.Second * 3)
+		}
+	} else {
+		log.Println(fmt.Sprintf("%s 邮件格式有误", email))
+	}
+	return false
+}

Some files were not shown because too many files changed in this diff