package main
import (
. "app.yhyue.com/moapp/jybase/common"
. "app.yhyue.com/moapp/jybase/mongodb"
"context"
"flag"
"fmt"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"golang.org/x/net/html"
"net/rpc"
"regexp"
"strings"
"sync"
"time"
)
var (
TaskConfig *taskConfig
mgo *MongodbSim
mgoBid *MongodbSim
allSpaceReg = regexp.MustCompile(`([\s ])+`)
spaceReg = regexp.MustCompile(`\s+`)
brReg = regexp.MustCompile(`(
(\s+)?)+`)
)
type taskConfig struct {
LastTime int64
}
type BidDataItem struct {
Zbgs *BidDataObj //中标公示
Zcgs *BidDataObj //招采公示
Yxsjgs *BidDataObj //意向商机公示
}
type BidDataObj struct {
Winners *gset.Set
Purchasinglist *gset.Set
BidDataDetails *garray.SortedArray
}
type BidDataDetail struct {
ProjectName string //项目名称
ProjectCode string //项目编号
Area string //项目地区
Buyer string //招采单位
BudgetOriginal float64 //
Budget string //预算金额
Amount float64 //金额
Winner string //中标单位
BidamountOriginal float64 //
Bidamount string //中标金额
Detail string //建设内容
Bidendtime string //投标截止时间
PurchaseTime string //预计采购时间
Projectscope string //需求概况 projectscope
}
type Article struct {
Title string
Content string
}
func newBidDataObj() *BidDataObj {
return &BidDataObj{Winners: gset.New(true), Purchasinglist: gset.New(true), BidDataDetails: garray.NewSortedArray(func(a, b interface{}) int {
if a.(*BidDataDetail).Amount > b.(*BidDataDetail).Amount {
return -1
} else if a.(*BidDataDetail).Amount < b.(*BidDataDetail).Amount {
return 1
}
return 0
}, true)}
}
func init() {
ctx := gctx.New()
mgo = &MongodbSim{
MongodbAddr: g.Config().MustGet(ctx, "mongodb.main.address").String(),
Size: g.Config().MustGet(ctx, "mongodb.main.size").Int(),
DbName: g.Config().MustGet(ctx, "mongodb.main.dbName").String(),
UserName: g.Config().MustGet(ctx, "mongodb.main.userName").String(),
Password: g.Config().MustGet(ctx, "mongodb.main.password").String(),
}
mgo.InitPool()
mgoBid = &MongodbSim{
MongodbAddr: g.Config().MustGet(ctx, "mongodb.bidding.address").String(),
Size: g.Config().MustGet(ctx, "mongodb.bidding.size").Int(),
DbName: g.Config().MustGet(ctx, "mongodb.bidding.dbName").String(),
UserName: g.Config().MustGet(ctx, "mongodb.bidding.userName").String(),
Password: g.Config().MustGet(ctx, "mongodb.bidding.password").String(),
}
mgoBid.InitPool()
ReadConfig("./task.json", &TaskConfig)
}
func main() {
model := flag.Int("m", 0, "1:上传素材 2:非定时任务")
startTime := flag.Int64("s", 0, "开始时间")
endTime := flag.Int64("e", 0, "结束时间")
flag.Parse()
//log.Println(detailCapture(filterHtml(`一、项目编号:ZL24011540000(招标文件编号:ZL24011540000)
二、项目名称:西藏低温冷害、干旱、病虫害气象预警信息化平台
三、中标(成交)信息
供应商名称:重庆阿里九九科技有限公司
供应商地址:拉萨市北京西路回民巷2号
中标(成交)金额:61.8000000(万元)
四、主要标的信息
\t\t\t\t\t\t序号 | \t\t\t 供应商名称 | \t\t\t 服务名称 | \t\t\t 服务范围 | \t\t\t 服务要求 | \t\t\t 服务时间 | \t\t\t 服务标准 | \t\t
\t\t\t\t\t1 | \t\t\t 重庆阿里九九科技有限公司 | \t\t\t 详见响应文件 | \t\t\t 详见响应文件 | \t\t\t 详见响应文件 | \t\t\t 详见响应文件 | \t\t\t 详见响应文件 | \t\t
\t\t\t\t\t | \t\t\t | \t\t\t | \t\t\t | \t\t\t | \t\t\t | \t\t\t | \t\t
\t
五、评审专家(单一来源采购人员)名单:
程标、程伟伟、姚曌臻
六、代理服务收费标准及金额:
本项目代理费收费标准:按发改价格【2015】299号文相关规定向中标人收取
本项目代理费总金额:0.927000 万元(人民币)
七、公告期限
自本公告发布之日起1个工作日。
八、其它补充事宜
九、凡对本次公告内容提出询问,请按以下方式联系。
1.采购人信息
名 称:西藏自治区气候中心
地址:西藏自治区拉萨市城关区林廓北路1号
联系方式:张先生 0891-6330101
2.采购代理机构信息
名 称:西藏则立项目管理有限公司
地 址:拉萨市柳梧新区海亮天城商铺11-7号
联系方式:陈先生 17708911306
3.项目联系方式
项目联系人:陈先生
电 话: 17708911306`)))
//addDraft(0, 1736289976) //f_sourceinfo_2025Jslt_msx 江苏 西藏
//addDraft(1740731400, 1840731400) //f_sourceinfo_2025Sjcs 广东 上海
//return
if *model == 1 {
addMaterial()
} else if *model == 2 {
addDraft(*startTime, *endTime)
} else {
SimpleCrontab(false, g.Config().MustGet(gctx.New(), "timeTask").String(), func() {
et := time.Now().Unix()
addDraft(TaskConfig.LastTime, et)
TaskConfig.LastTime = et
WriteSysConfig("./task.json", *TaskConfig)
})
<-chan bool(nil)
}
}
func addMaterial() {
ctx := gctx.New()
var path = "./thumb.png"
result, err := g.Client().Post(ctx, "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token="+getAccessToken(ctx)+"&type=image", "media=@file:"+path)
if err != nil {
g.Log().Error(ctx, err)
return
}
defer result.Close()
g.Log().Info(ctx, "新增永久素材请求返回结果", result.ReadAllString())
}
// 新增草稿箱
func addDraft(startTime, endTime int64) {
ctx := gctx.New()
g.Log().Info(ctx, "开始新增草稿箱任务。。。")
sess := mgo.GetMgoConn()
defer mgo.DestoryMongoConn(sess)
it := sess.DB(g.Config().MustGet(ctx, "mongodb.main.dbName").String()).C(g.Config().MustGet(ctx, "mongodb.main.collection").String()).Find(map[string]interface{}{
"i_updatetime": map[string]interface{}{
"$gte": startTime,
"$lt": endTime,
},
"i_ckdata": map[string]interface{}{
"$gt": 0,
},
"v_baseinfo.informatized": "是",
}).Select(map[string]interface{}{"id": 1, "v_baseinfo": 1}).Iter()
all := gmap.New(true)
pool := make(chan bool, 5)
wait := &sync.WaitGroup{}
index := 0
areas := map[string]bool{}
for _, v := range g.Config().MustGet(ctx, "areas").Strings() {
areas[v] = true
}
for mt := make(map[string]interface{}); it.Next(&mt); {
pool <- true
wait.Add(1)
go func(mn map[string]interface{}) {
defer func() {
<-pool
wait.Done()
}()
v_baseinfo, _ := mn["v_baseinfo"].(map[string]interface{})
area, _ := v_baseinfo["area"].(string)
toptype, _ := v_baseinfo["toptype"].(string)
subtype, _ := v_baseinfo["subtype"].(string)
if !areas[area] || (toptype != "招标" && toptype != "采购意向" && subtype != "中标" && subtype != "成交") {
return
}
var bdi *BidDataItem
if all.Get(area) == nil {
bdi = &BidDataItem{Zbgs: newBidDataObj(), Zcgs: newBidDataObj(), Yxsjgs: newBidDataObj()}
all.Set(area, bdi)
} else {
bdi = all.Get(area).(*BidDataItem)
}
var bdo *BidDataObj
if toptype == "招标" {
bdo = bdi.Zcgs
} else if toptype == "结果" {
bdo = bdi.Zbgs
} else {
bdo = bdi.Yxsjgs
}
purchasinglist, _ := v_baseinfo["purchasinglist"].([]interface{})
bidendtime := gconv.Int64(v_baseinfo["bidendtime"])
procurementlist, _ := v_baseinfo["procurementlist"].([]interface{})
if id := gconv.String(mn["id"]); id != "" && (purchasinglist == nil || len(purchasinglist) == 0 || v_baseinfo["bidendtime"] == nil || procurementlist == nil || len(procurementlist) == 0) {
bidding, ok := mgoBid.FindById("bidding", id, `{"purchasinglist":1,"bidendtime":1,"procurementlist":1}`)
if ok && bidding != nil && len(*bidding) > 0 {
if purchasinglist == nil {
purchasinglist, _ = (*bidding)["purchasinglist"].([]interface{})
}
if bidendtime == 0 {
bidendtime = gconv.Int64((*bidding)["bidendtime"])
}
if procurementlist == nil {
procurementlist, _ = (*bidding)["procurementlist"].([]interface{})
}
}
}
//
if len(purchasinglist) > 0 {
firstOne, _ := purchasinglist[0].(map[string]interface{})
itemname := gconv.String(firstOne["itemname"])
if itemname != "" {
bdo.Purchasinglist.Add(itemname)
}
}
//
purchaseTime := ""
if len(procurementlist) > 0 {
firstOne, _ := procurementlist[0].(map[string]interface{})
purchaseTime = gconv.String(firstOne["expurasingtime"])
}
//
winner, _ := v_baseinfo["winner"].(string)
bdo.Winners.Add(winner)
//
detail := filterHtml(gconv.String(v_baseinfo["projectscope"]))
newDetail := detailCapture(detail)
if detail != newDetail {
newDetail += g.Config().MustGet(ctx, "template.captureAppend").String()
}
bdd := &BidDataDetail{
ProjectName: gconv.String(v_baseinfo["projectname"]),
ProjectCode: gconv.String(v_baseinfo["projectcode"]),
Area: area,
Buyer: gconv.String(v_baseinfo["buyer"]),
BudgetOriginal: gconv.Float64(v_baseinfo["budget"]),
Winner: winner,
BidamountOriginal: gconv.Float64(v_baseinfo["bidamount"]),
Detail: newDetail,
PurchaseTime: purchaseTime,
Projectscope: gconv.String(v_baseinfo["projectscope"]),
}
if bdd.BudgetOriginal > 0 {
bdd.Budget = gconv.String(RetainDecimal(gconv.Float64(v_baseinfo["budget"])/10000, 2)) + "万元"
}
if bdd.BidamountOriginal > 0 {
bdd.Bidamount = gconv.String(RetainDecimal(gconv.Float64(v_baseinfo["bidamount"])/10000, 2)) + "万元"
}
if v_baseinfo["bidamount"] != nil {
bdd.Amount = bdd.BidamountOriginal
} else {
bdd.Amount = bdd.BudgetOriginal
}
if bdd.ProjectName == "" {
bdd.ProjectName = gconv.String(v_baseinfo["title"])
}
if bidendtime > 0 {
bdd.Bidendtime = gtime.New(bidendtime).Layout(time.DateTime)
}
bdo.BidDataDetails.Add(bdd)
}(mt)
index++
if index%500 == 0 {
g.Log().Info(ctx, "加载清洗数据。。。", index)
}
mt = make(map[string]interface{})
}
wait.Wait()
g.Log().Info(ctx, "清洗数据加载结束。。。", index)
articles := []*Article{}
ymd := gtime.Now().Format("Ymd")
purchasingSize := g.Config().MustGet(ctx, "template.purchasingSize").Int()
winnerSize := g.Config().MustGet(ctx, "template.winnerSize").Int()
htmlBody := g.Config().MustGet(ctx, "template.htmlBody").String()
htmlTopImg := g.Config().MustGet(ctx, "template.htmlTopImg").String()
htmlProjectSpit := g.Config().MustGet(ctx, "template.htmlProjectSpit").String()
htmlProjectName := g.Config().MustGet(ctx, "template.htmlProjectName").String()
htmlProjectOther := g.Config().MustGet(ctx, "template.htmlProjectOther").String()
htmlBottom := g.Config().MustGet(ctx, "template.htmlBottom").String()
all.Iterator(func(k, v interface{}) bool {
kk := k.(string)
vv := v.(*BidDataItem)
//
if !vv.Zbgs.BidDataDetails.IsEmpty() {
vv.Zbgs.BidDataDetails.Sort()
content := fmt.Sprintf(g.Config().MustGet(ctx, "template.zbgs.content").String(), kk, garray.NewArrayFrom(vv.Zbgs.Purchasinglist.Pops(purchasingSize)).Join("、"), garray.NewArrayFrom(vv.Zbgs.Winners.Pops(winnerSize)).Join("、"))
content += htmlTopImg
for k, v := range vv.Zbgs.BidDataDetails.PopLefts(g.Config().MustGet(ctx, "template.zhongProjectSize").Int()) {
if k > 0 {
content += htmlProjectSpit
}
vv := v.(*BidDataDetail)
content += fmt.Sprintf(htmlProjectName, NumToChinese(k+1), vv.ProjectName)
if vv.Buyer != "" {
content += fmt.Sprintf(htmlProjectOther, "招采单位", vv.Buyer)
}
if vv.Winner != "" {
content += fmt.Sprintf(htmlProjectOther, "中标单位", vv.Winner)
}
if vv.Bidamount != "" {
content += fmt.Sprintf(htmlProjectOther, "中标金额", vv.Bidamount)
}
if vv.Detail != "" {
content += fmt.Sprintf(htmlProjectOther, "建设内容", vv.Detail)
}
}
articles = append(articles, &Article{
Title: fmt.Sprintf("%s_%s_中标标讯", ymd, kk),
Content: fmt.Sprintf(htmlBody, content+htmlBottom),
})
}
//
if !vv.Zcgs.BidDataDetails.IsEmpty() {
vv.Zcgs.BidDataDetails.Sort()
content := ""
var allBudget float64
for k, v := range vv.Zcgs.BidDataDetails.PopLefts(g.Config().MustGet(ctx, "template.zhaoProjectSize").Int()) {
if k > 0 {
content += htmlProjectSpit
}
vv := v.(*BidDataDetail)
allBudget += vv.BudgetOriginal
content += fmt.Sprintf(htmlProjectName, NumToChinese(k+1), vv.ProjectName)
if vv.ProjectCode != "" {
content += fmt.Sprintf(htmlProjectOther, "项目编号", vv.ProjectCode)
}
if vv.Buyer != "" {
content += fmt.Sprintf(htmlProjectOther, "采购单位", vv.Buyer)
}
if vv.Area != "" {
content += fmt.Sprintf(htmlProjectOther, "项目地区", vv.Area)
}
if vv.Budget != "" {
content += fmt.Sprintf(htmlProjectOther, "预算金额", vv.Budget)
}
if vv.Bidendtime != "" {
content += fmt.Sprintf(htmlProjectOther, "投标截止时间", vv.Bidendtime)
}
if vv.Detail != "" {
content += fmt.Sprintf(htmlProjectOther, "项目建设内容", vv.Detail)
}
}
content = fmt.Sprintf(g.Config().MustGet(ctx, "template.zcgs.content").String(), kk, garray.NewArrayFrom(vv.Zcgs.Purchasinglist.Pops(purchasingSize)).Join("、"), gconv.String(RetainDecimal(allBudget/10000, 2))+"万元") + htmlTopImg + content
articles = append(articles, &Article{
Title: fmt.Sprintf("%s_%s_招标标讯", ymd, kk),
Content: fmt.Sprintf(htmlBody, content+htmlBottom),
})
}
//
if !vv.Yxsjgs.BidDataDetails.IsEmpty() {
vv.Yxsjgs.BidDataDetails.Sort()
content := fmt.Sprintf(g.Config().MustGet(ctx, "template.yxsjgs.content").String(), kk)
content += htmlTopImg
for k, v := range vv.Yxsjgs.BidDataDetails.PopLefts(g.Config().MustGet(ctx, "template.caiProjectSize").Int()) {
if k > 0 {
content += htmlProjectSpit
}
vv := v.(*BidDataDetail)
content += fmt.Sprintf(htmlProjectName, NumToChinese(k+1), vv.ProjectName)
content += fmt.Sprintf(htmlProjectOther, "项目地区", vv.Area)
if vv.Buyer != "" {
content += fmt.Sprintf(htmlProjectOther, "采购单位", vv.Buyer)
}
if vv.Budget != "" {
content += fmt.Sprintf(htmlProjectOther, "预算金额", vv.Budget)
}
if vv.PurchaseTime != "" {
content += fmt.Sprintf(htmlProjectOther, "预计采购时间", vv.PurchaseTime)
}
if vv.Detail != "" {
content += fmt.Sprintf(htmlProjectOther, "需求概况", vv.Detail)
}
}
articles = append(articles, &Article{
Title: fmt.Sprintf("%s_%s_采购意向", ymd, kk),
Content: fmt.Sprintf(htmlBody, content+htmlBottom),
})
}
return true
})
//
if len(articles) > 0 {
list := g.List{}
for _, v := range articles {
list = append(list, g.Map{
"article_type": "news",
"title": v.Title,
//"author":AUTHOR,
//"digest":DIGEST,
"content": v.Content,
//"content_source_url":CONTENT_SOURCE_URL,
"thumb_media_id": g.Config().MustGet(ctx, "mediaId").String(),
//"need_open_comment":0,
//"only_fans_can_comment":0,
//"pic_crop_235_1":X1_Y1_X2_Y2,
//"pic_crop_1_1":X1_Y1_X2_Y2
})
}
g.Log().Info(ctx, "提交草稿数据", len(articles), "条", gconv.String(list))
result, err := g.Client().ContentType("application/json").Post(ctx, "https://api.weixin.qq.com/cgi-bin/draft/add?access_token="+getAccessToken(ctx), g.Map{"articles": list})
if err != nil {
g.Log().Error(ctx, "提交草稿出错", err)
return
}
defer result.Close()
g.Log().Info(ctx, "新增草稿箱请求返回结果", result.ReadAllString())
} else {
g.Log().Info(ctx, "没有需要提交草稿的数据")
}
g.Log().Info(ctx, "新增草稿箱任务结束。。。")
}
func getAccessToken(ctx context.Context) string {
code := g.Config().MustGet(ctx, "appid").String()
var repl string
client, err := rpc.DialHTTP("tcp", g.Config().MustGet(ctx, "wxTokenRpc").String())
if err != nil {
g.Log().Error(ctx, code, err)
return repl
}
defer client.Close()
err = client.Call("WxTokenRpc.GetAccessToken", code, &repl)
if err != nil {
g.Log().Error(ctx, code, err)
return repl
}
if repl == "" {
g.Log().Error(ctx, code, "未获取到accessToken")
} else {
g.Log().Info(ctx, code, "获取到accessToken", repl)
}
return repl
}
func filterHtml(text string) string {
doc, err := html.Parse(strings.NewReader(text))
if err != nil {
text = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(text, "")
} else {
var textBuilder strings.Builder
var traverse func(*html.Node)
traverse = func(n *html.Node) {
if n.Type == html.TextNode {
textBuilder.WriteString(n.Data)
} else if n.Type == html.ElementNode && n.Data == "br" {
textBuilder.WriteString("
")
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
traverse(doc)
text = textBuilder.String()
}
text = strings.ReplaceAll(text, `\t`, " ")
text = strings.ReplaceAll(text, `\n`, " ")
text = allSpaceReg.ReplaceAllString(text, " ")
text = spaceReg.ReplaceAllString(text, " ")
text = brReg.ReplaceAllString(text, "
")
text = strings.TrimSpace(text)
return text
}
func detailCapture(text string) string {
text = gstr.SubStrRune(text, 0, g.Config().MustGet(gctx.New(), "template.detailLen").Int())
text = strings.TrimSuffix(text, "