瀏覽代碼

研报自动发布

xyxie 1 年之前
父節點
當前提交
000fbb2c53
共有 17 個文件被更改,包括 1548 次插入87 次删除
  1. 7 2
      go.mod
  2. 13 3
      go.sum
  3. 7 6
      models/classify.go
  4. 128 0
      models/english_report.go
  5. 177 3
      models/report.go
  6. 29 0
      models/wx_template_msg.go
  7. 60 0
      models/xfyun.go
  8. 151 0
      services/elastic.go
  9. 85 0
      services/english_report.go
  10. 360 3
      services/report.go
  11. 8 0
      services/task.go
  12. 289 0
      services/video.go
  13. 0 37
      services/wx_template_msg.go
  14. 108 0
      services/xfyun.go
  15. 60 20
      utils/common.go
  16. 61 0
      utils/config.go
  17. 5 13
      utils/constants.go

+ 7 - 2
go.mod

@@ -8,11 +8,15 @@ require (
 	github.com/beego/beego/v2 v2.0.2
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-sql-driver/mysql v1.6.0
-	github.com/rdlucklib/rdluck_tools v1.0.2
+	github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c
+	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
+	github.com/olivere/elastic/v7 v7.0.32
+	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	github.com/tealeg/xlsx v1.0.5
 	github.com/wenzhenxi/gorsa v0.0.0-20210524035706-528c7050d703
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
+	golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
 
@@ -25,6 +29,8 @@ require (
 	github.com/garyburd/redigo v1.6.3 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
@@ -35,7 +41,6 @@ require (
 	github.com/satori/go.uuid v1.2.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
-	golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect
 	golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
 	golang.org/x/text v0.3.6 // indirect
 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect

+ 13 - 3
go.sum

@@ -98,6 +98,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -155,8 +156,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 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=
@@ -166,6 +167,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 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=
@@ -199,6 +201,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 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=
@@ -208,6 +212,8 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 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/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53 h1:+8X3HMX8A2QhvNg3dImiQTCiVUt6BQXz1mW+/DrWI+k=
+github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53/go.mod h1:E61jD6q4yJ6Cu9uDGRAfiENM1G5TVZhOog0Y3+GgTpQ=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 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=
@@ -226,6 +232,8 @@ github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -263,6 +271,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
+github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
 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.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -327,8 +337,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
 github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rdlucklib/rdluck_tools v1.0.2 h1:Xf1khfttpAh4D1jtMVE5OxLXhFUaDnvG74vZH7FSZQY=
-github.com/rdlucklib/rdluck_tools v1.0.2/go.mod h1:9Onw9o4w19C8KE5lxb8GyxgRBbZweRVkQSc79v38EaA=
+github.com/rdlucklib/rdluck_tools v1.0.3 h1:iOtK2QPlPQ6CL6c1htCk5VnFCHzyG6DCfJtunrMswK0=
+github.com/rdlucklib/rdluck_tools v1.0.3/go.mod h1:9Onw9o4w19C8KE5lxb8GyxgRBbZweRVkQSc79v38EaA=
 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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

+ 7 - 6
models/classify.go

@@ -23,6 +23,7 @@ type Classify struct {
 	ReportImgUrl   string    `description:"报告配图"`
 	HomeImgUrl     string    `description:"首页配图"`
 	ClassifyLabel  string    `description:"分类标签"`
+	IsMassSend     int       `description:"1:群发,0:非群发"`
 }
 
 type ClassifyAddReq struct {
@@ -54,7 +55,7 @@ func GetClassifyById(classifyId int) (item *Classify, err error) {
 	return
 }
 
-//添加分类
+// 添加分类
 func AddClassify(item *Classify) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Insert(item)
@@ -86,7 +87,7 @@ func GetClassifySubCountByParentId(classifyId int) (count int, err error) {
 	return
 }
 
-//删除分类
+// 删除分类
 func DeleteClassify(classifyId int) (err error) {
 	sql := `DELETE FROM classify WHERE id=? `
 	o := orm.NewOrmUsingDB("rddp")
@@ -99,8 +100,8 @@ func DeleteClassify(classifyId int) (err error) {
 	return
 }
 
-//classifyName, abstract, descript string, parentId, classifyId int
-//修改分类
+// classifyName, abstract, descript string, parentId, classifyId int
+// 修改分类
 func EditClassify(req *EditClassifyReq) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `UPDATE classify SET classify_name = ?,abstract=?, parent_id= ?,descript=?,report_author=?,author_descript=?,column_img_url=?,head_img_url=?,avatar_img_url=?,report_img_url=?,home_img_url=?,classify_label=?, modify_time= NOW() WHERE id = ? `
@@ -117,7 +118,7 @@ func ParentClassify() (items []*Classify, err error) {
 	return
 }
 
-//根据id获取分类详情
+// 根据id获取分类详情
 func FindByIdClassify(classifyId int) (item *Classify, err error) {
 	sql := `SELECT * FROM classify WHERE id=? `
 	o := orm.NewOrmUsingDB("rddp")
@@ -143,7 +144,7 @@ type ClassifyListResp struct {
 	Paging *paging.PagingItem `description:"分页数据"`
 }
 
-//获取分类列表
+// 获取分类列表
 func GetClassifyList(startSize, pageSize int, keyWord, companyType string) (items []*ClassifyList, err error) {
 	sql := ``
 	companyTypeSqlStr := ``

+ 128 - 0
models/english_report.go

@@ -0,0 +1,128 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EnglishReport struct {
+	Id                 int    `orm:"column(id)" description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	PrePublishTime     string `description:"预发布时间"`
+	Stage              int    `description:"期数"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	VideoSize          string `description:"音频文件大小,单位M"`
+	ContentSub         string `description:"内容前两个章节"`
+	ReportCode         string `description:"报告唯一编码"`
+	Pv                 int    `description:"Pv"`
+	PvEmail            int    `description:"邮箱PV"`
+	EmailState         int    `description:"群发邮件状态: 0-未发送; 1-已发送"`
+	Overview           string `description:"英文概述部分"`
+	KeyTakeaways       string `description:"关键点"`
+	FromReportId       int    `description:"继承的报告ID(英文策略报告ID)"`
+	AdminId            int    `description:"创建者账号"`
+	AdminRealName      string `description:"创建者姓名"`
+}
+
+type EnglishReportDetail struct {
+	Id                 int    `orm:"column(id)" description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	Stage              int    `description:"期数"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	ReportCode         string `description:"报告唯一编码"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	ContentSub         string `description:"内容前两个章节"`
+	Pv                 int    `description:"Pv"`
+	Overview           string `description:"英文概述部分"`
+	FromReportId       int    `description:"继承的报告ID(英文策略报告ID)"`
+	KeyTakeaways       string `description:"关键点"`
+}
+
+type ElasticEnglishReportDetail struct {
+	Id                 string `description:"报告id或者线上路演Id"`
+	ReportId           int    `description:"报告id"`
+	VideoId            int    `description:"线上路演Id"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	StageStr           string `description:"报告期数"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	PublishState       int    `description:"状态:1:未发布,2:已发布"`
+	BodyContent        string `description:"内容"`
+	ContentSub         string `description:"前两段内容"`
+	CreateTime         string `description:"创建时间"`
+	PublishTime        string `description:"发布时间"`
+	ReportCode         string `description:"报告唯一编码"`
+	Overview           string `description:"英文概述部分"`
+}
+
+// GetPrePublishedEnglishReports 获取定时发布时间为当前时间的未发布的英文报告列表
+func GetPrePublishedEnglishReports(prePublishTime string) (list []*EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE state = 1 and pre_publish_time = ?`
+	_, err = o.Raw(sql, prePublishTime).QueryRows(&list)
+	return
+}
+
+// PublishEnglishReportById 发布报告
+func PublishEnglishReportById(reportId int, publishTime string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report SET state=2, publish_time=?, modify_time=NOW() WHERE id = ? `
+	_, err = o.Raw(sql, publishTime, reportId).Exec()
+	return
+}
+
+func GetEnglishReportById(reportId int) (item *EnglishReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// GetReportByReportId 主键获取报告
+func GetReportByReportId(reportId int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id = ?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// Update 更新
+func (item *EnglishReport) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}

+ 177 - 3
models/report.go

@@ -2,8 +2,8 @@ package models
 
 import (
 	"fmt"
-	"hongze/hongze_task/utils"
 	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_task/utils"
 	"time"
 )
 
@@ -32,9 +32,10 @@ type Report struct {
 	HasChapter         int       `description:"是否有章节 0-否 1-是"`
 	ChapterType        string    `description:"章节类型 day-晨报 week-周报"`
 	OldReportId        int       `description:"research_report表ID(后续一两个版本过渡需要,之后可移除)"`
+	PreMsgSend         int       `description:"定时发布成功后是否立即推送模版消息:0否,1是"`
 }
 
-func GetReportById(reportId int) (item *Report, err error) {
+func GetReportById(reportId int) (item *ReportDetail, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM report WHERE id=?`
 	err = o.Raw(sql, reportId).QueryRow(&item)
@@ -69,7 +70,7 @@ func EditReportContent(reportId int, content, contentSub string) (err error) {
 	return
 }
 
-//删除报告日志记录-保留3个月
+// 删除报告日志记录-保留3个月
 func DeleteReportSaveLog() {
 	startDateTime := time.Now().AddDate(0, -3, 0).Format(utils.FormatDateTime)
 	fmt.Println(startDateTime)
@@ -81,3 +82,176 @@ func EditReportContentHtml(reportId int, content string) (err error) {
 	_, err = o.Raw(sql, content, reportId).Exec()
 	return
 }
+
+// GetPrePublishedReports 获取定时发布时间为当前时间的未发布的报告列表
+func GetPrePublishedReports(prePublishTime string) (list []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE state = 1 and pre_publish_time = ?`
+	_, err = o.Raw(sql, prePublishTime).QueryRows(&list)
+	return
+}
+
+// PublishReportById 发布报告
+func PublishReportById(reportId int, publishTime time.Time) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET state = 2, publish_time = ?, modify_time = NOW() WHERE id = ? `
+	_, err = o.Raw(sql, publishTime, reportId).Exec()
+	return
+}
+
+type ReportDetail struct {
+	Id                 int    `orm:"column(id)" description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	Stage              int    `description:"期数"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	ContentSub         string `description:"内容前两个章节"`
+	ThsMsgIsSend       int    `description:"客户群消息是否已发送,0:否,1:是"`
+	HasChapter         int    `description:"是否有章节 0-否 1-是"`
+	ChapterType        string `description:"章节类型 day-晨报 week-周报"`
+	OldReportId        int    `description:"research_report表ID(后续一两个版本过渡需要,之后可移除)"`
+	PreMsgSend         int    `description:"定时发布成功后是否立即推送模版消息:0否,1是"`
+}
+
+func ModifyReportVideo(reportId int, videoUrl, videoName, videoSize string, playSeconds float64) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET video_url=?,video_name=?,video_play_seconds=?,video_size=? WHERE id=? `
+	_, err = o.Raw(sql, videoUrl, videoName, playSeconds, videoSize, reportId).Exec()
+	return
+}
+
+// ReportChapter 报告章节
+type ReportChapter struct {
+	ReportChapterId   int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId          int       `description:"报告ID"`
+	ReportType        string    `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst   int       `description:"一级分类id"`
+	ClassifyNameFirst string    `description:"一级分类名称"`
+	TypeId            int       `description:"品种ID"`
+	TypeName          string    `description:"品种名称"`
+	Title             string    `description:"标题"`
+	Abstract          string    `description:"摘要"`
+	AddType           int       `description:"新增方式:1:新增报告,2:继承报告"`
+	Author            string    `description:"作者"`
+	Content           string    `description:"内容"`
+	ContentSub        string    `description:"内容前两个章节"`
+	Stage             int       `description:"期数"`
+	Trend             string    `description:"趋势观点"`
+	Sort              int       `description:"排序: 数值越小越靠前"`
+	IsEdit            int       `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState      int       `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime       time.Time `description:"发布时间"`
+	VideoUrl          string    `description:"音频文件URL"`
+	VideoName         string    `description:"音频文件名称"`
+	VideoPlaySeconds  string    `description:"音频播放时长"`
+	VideoSize         string    `description:"音频文件大小,单位M"`
+	VideoKind         int       `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime        string    `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+	OriginalVideoUrl  string    `description:"原始音频文件URL"`
+}
+
+// GetPublishedChapterListByReportId 根据ReportId获取已发布章节列表
+func GetPublishedChapterListByReportId(reportId int) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_id = ? AND publish_state = 2 ORDER BY sort ASC`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+type ReportChapterTypePermission struct {
+	Id                    int       `orm:"column(id);pk" description:"主键ID"`
+	ReportChapterTypeId   int       `description:"报告章节类型ID"`
+	ReportChapterTypeName string    `description:"章节名称"`
+	ChartPermissionId     int       `description:"大分类ID"`
+	PermissionName        string    `description:"权限名称"`
+	ResearchType          string    `description:"研报类型"`
+	CreatedTime           time.Time `description:"创建时间"`
+}
+
+// GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
+func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType string) (list []*ReportChapterTypePermission, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ? ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, typeId, researchType).QueryRows(&list)
+	return
+}
+
+type ElasticReportDetail struct {
+	ReportId           int    `description:"报告ID"`
+	ReportChapterId    int    `description:"报告章节ID"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	BodyContent        string `description:"内容"`
+	PublishTime        string `description:"发布时间"`
+	PublishState       int    `description:"发布状态 1-未发布 2-已发布"`
+	Author             string `description:"作者"`
+	ClassifyIdFirst    int    `description:"一级分类ID"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类ID"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Categories         string `description:"关联的品种名称(包括品种别名)"`
+	StageStr           string `description:"报告期数"`
+}
+
+type ChartPermissionMappingIdName struct {
+	PermissionId   int
+	PermissionName string
+}
+
+func GetChartPermissionNameFromMappingByKeyword(keyword string, source string) (list []*ChartPermissionMappingIdName, err error) {
+	o := orm.NewOrm()
+	sql := " SELECT b.chart_permission_id AS permission_id,b.permission_name FROM chart_permission_search_key_word_mapping AS a INNER JOIN chart_permission AS b ON a.chart_permission_id = b.chart_permission_id WHERE a.`from` = ? AND a.key_word = ? "
+	_, err = o.Raw(sql, source, keyword).QueryRows(&list)
+
+	return
+}
+
+func UpdateReportPublishTime(reportId int, videoNameDate string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql1 := ` UPDATE report SET publish_time = NOW() WHERE id = ?  `
+	_, err = o.Raw(sql1, reportId).Exec()
+	if err != nil {
+		return
+	}
+	//修改音频标题
+	sql2 := ` UPDATE report SET video_name=CONCAT(SUBSTRING_INDEX(video_name,"(",1),"` + videoNameDate + `") WHERE id = ? and (video_name !="" and video_name is not null)`
+	_, err = o.Raw(sql2, reportId).Exec()
+	return
+}
+
+func UpdateReportChapterPublishTime(reportId int, videoNameDate string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql1 := ` UPDATE report_chapter SET publish_time = NOW() WHERE report_id = ? `
+	_, err = o.Raw(sql1, reportId).Exec()
+	if err != nil {
+		return
+	}
+	//修改音频标题
+	sql2 := ` UPDATE report_chapter SET video_name=CONCAT(SUBSTRING_INDEX(video_name,"(",1),"` + videoNameDate + `") WHERE report_id = ? and (video_name !="" and video_name is not null)`
+	_, err = o.Raw(sql2, reportId).Exec()
+	return
+}
+
+func ModifyReportMsgIsSend(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET msg_is_send = 1, msg_send_time=NOW()  WHERE id = ? and msg_is_send=0`
+	_, err = o.Raw(sql, reportId).Exec()
+	return
+}

+ 29 - 0
models/wx_template_msg.go

@@ -55,3 +55,32 @@ func GetOpenIdListV2() (items []*OpenIdList, err error) {
 	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
 	return
 }
+
+func GetOpenIdArr() (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+          INNER JOIN company AS c ON c.company_id = wu.company_id 
+          INNER JOIN company_product AS d ON c.company_id=d.company_id
+		INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+          WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+         ORDER BY FIELD(c.company_id, 16) desc, ur.user_record_id asc`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string, err error) {
+	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
+			INNER JOIN company AS c ON c.company_id = wu.company_id 
+			INNER JOIN company_product AS d ON c.company_id=d.company_id
+			INNER JOIN user_record  AS ur ON wu.user_id=ur.user_id
+			INNER JOIN company_report_permission AS e ON d.company_id=e.company_id
+			INNER JOIN chart_permission AS f ON e.chart_permission_id=f.chart_permission_id
+			INNER JOIN chart_permission_search_key_word_mapping AS g ON f.chart_permission_id=g.chart_permission_id
+			WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续') AND  e.status IN('正式','试用','永续') 
+			AND g.from='rddp'
+			AND g.key_word=?
+			ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, classifyNameSecond).QueryRows(&items)
+	return
+}

+ 60 - 0
models/xfyun.go

@@ -0,0 +1,60 @@
+package models
+
+type XfSendParam struct {
+	Common struct {
+		AppId string `json:"app_id"`
+	} `json:"common"`
+	Business struct {
+		Aue    string `json:"aue"`
+		Sfl    int    `json:"sfl"`
+		Auf    string `json:"auf"`
+		Vcn    string `json:"vcn"`
+		Speed  int    `json:"speed"`
+		Volume int    `json:"volume"`
+		Pitch  int    `json:"pitch"`
+		Bgs    int    `json:"bgs"`
+		Tte    string `json:"tte"`
+		Reg    string `json:"reg"`
+		Rdn    string `json:"rdn"`
+	} `json:"business"`
+	Data struct {
+		Text   string `json:"text"`
+		Status int    `json:"status"`
+	} `json:"data"`
+}
+
+type XfReciveResult struct {
+	Code    int
+	Message string
+	Sid     string
+	Data    *struct {
+		Audio  string `json:"audio"`
+		Ced    string `json:"ced"`
+		Status int    `json:"status"`
+	} `json:"data"`
+}
+
+type AudioReq struct {
+	ReportId string
+}
+
+type EdbdataImportReq struct {
+	FullPath  string
+	SysUserId string
+}
+
+type EdbdataExportExcelReq struct {
+	StartDate  string
+	EndDate    string
+	Frequency  string
+	ClassifyId int
+	KeyWord    string
+	Mobile     string
+}
+
+// 内容转音频后的结构体
+type VideoInfo struct {
+	VideoUrl         string  `description:"链接"`
+	VideoPlaySeconds float64 `description:"时长"`
+	VideoSize        string  `description:"大小"`
+}

+ 151 - 0
services/elastic.go

@@ -0,0 +1,151 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"strings"
+)
+
+func NewClient() (client *elastic.Client, err error) {
+	client, err = elastic.NewClient(
+		elastic.SetURL(utils.ES_URL),
+		elastic.SetBasicAuth(utils.ES_USERNAME, utils.ES_PASSWORD),
+		elastic.SetSniff(false))
+	return
+}
+
+// EsAddOrEditReport 新增编辑es报告
+func EsAddOrEditReport(indexName, docId string, item *models.ElasticReportDetail) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("EsAddOrEditReport Err:", err.Error())
+		}
+	}()
+	client, err := NewClient()
+	if err != nil {
+		return
+	}
+	// docId为报告ID+章节ID
+	searchById, err := client.Get().Index(indexName).Id(docId).Do(context.Background())
+	if err != nil && !strings.Contains(err.Error(), "404") {
+		fmt.Println("Get Err" + err.Error())
+		return
+	}
+	if searchById != nil && searchById.Found {
+		resp, err := client.Update().Index(indexName).Id(docId).Doc(map[string]interface{}{
+			"ReportId":           item.ReportId,
+			"ReportChapterId":    item.ReportChapterId,
+			"Title":              item.Title,
+			"Abstract":           item.Abstract,
+			"BodyContent":        item.BodyContent,
+			"PublishTime":        item.PublishTime,
+			"PublishState":       item.PublishState,
+			"Author":             item.Author,
+			"ClassifyIdFirst":    item.ClassifyIdFirst,
+			"ClassifyNameFirst":  item.ClassifyNameFirst,
+			"ClassifyIdSecond":   item.ClassifyIdSecond,
+			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"Categories":         item.Categories,
+			"StageStr":           item.StageStr,
+		}).Do(context.Background())
+		if err != nil {
+			return err
+		}
+		//fmt.Println(resp.Status, resp.Result)
+		if resp.Status == 0 {
+			fmt.Println("修改成功" + docId)
+			err = nil
+		} else {
+			fmt.Println("EditData", resp.Status, resp.Result)
+		}
+	} else {
+		resp, err := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
+		if err != nil {
+			fmt.Println("新增失败:", err.Error())
+			return err
+		}
+		if resp.Status == 0 && resp.Result == "created" {
+			fmt.Println("新增成功" + docId)
+			return nil
+		} else {
+			fmt.Println("AddData", resp.Status, resp.Result)
+		}
+	}
+	return
+}
+
+// EsAddOrEditEnglishReport 新增编辑es英文报告
+func EsAddOrEditEnglishReport(indexName, docId string, item *models.ElasticEnglishReportDetail) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("EsAddOrEditEnglishReport Err:", err.Error())
+			go alarm_msg.SendAlarmMsg("新增编辑es英文报告 EsAddOrEditEnglishReport,Err:"+err.Error(), 3)
+		}
+	}()
+	client, err := NewClient()
+	if err != nil {
+		return
+	}
+	// docId为报告ID
+	searchById, err := client.Get().Index(indexName).Id(docId).Do(context.Background())
+	if err != nil {
+		if strings.Contains(err.Error(), "404") {
+			err = nil
+		} else {
+			fmt.Println("Get Err" + err.Error())
+			return
+		}
+	}
+	if searchById != nil && searchById.Found {
+		resp, e := client.Update().Index(indexName).Id(docId).Doc(map[string]interface{}{
+			"Id":                 item.Id,
+			"ReportId":           item.ReportId,
+			"VideoId":            item.VideoId,
+			"Title":              item.Title,
+			"Abstract":           item.Abstract,
+			"BodyContent":        item.BodyContent,
+			"PublishTime":        item.PublishTime,
+			"PublishState":       item.PublishState,
+			"Author":             item.Author,
+			"ClassifyIdFirst":    item.ClassifyIdFirst,
+			"ClassifyNameFirst":  item.ClassifyNameFirst,
+			"ClassifyIdSecond":   item.ClassifyIdSecond,
+			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"CreateTime":         item.CreateTime,
+			"Overview":           item.Overview,
+			"ReportCode":         item.ReportCode,
+			"Frequency":          item.Frequency,
+			"StageStr":           item.StageStr,
+			"ContentSub":         item.ContentSub,
+		}).Do(context.Background())
+		if e != nil {
+			err = e
+			return
+		}
+		//fmt.Println(resp.Status, resp.Result)
+		if resp.Status == 0 {
+			fmt.Println("修改成功" + docId)
+			err = nil
+		} else {
+			fmt.Println("EditData", resp.Status, resp.Result)
+		}
+	} else {
+		resp, e := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
+		if e != nil {
+			err = e
+			fmt.Println("新增失败:", err.Error())
+			return
+		}
+		if resp.Status == 0 && resp.Result == "created" {
+			fmt.Println("新增成功" + docId)
+			return
+		} else {
+			fmt.Println("AddData", resp.Status, resp.Result)
+		}
+	}
+	return
+}

+ 85 - 0
services/english_report.go

@@ -0,0 +1,85 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"html"
+	"strconv"
+	"time"
+)
+
+// PublishEnglishReport 定时发布英文研报-每分钟
+func PublishEnglishReport(cont context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("PublishEnglishReport-定时发布英文研报失败, ErrMsg:\n"+err.Error(), 3)
+		}
+	}()
+	// 获取所有未发布的语音播报
+	now := time.Now().Format(utils.FormatDateTime)
+	list, e := models.GetPrePublishedEnglishReports(now)
+	if e != nil {
+		return
+	}
+	listLen := len(list)
+	if listLen == 0 {
+		return
+	}
+	// 比对时间(分钟),时间相等则发布并推送
+	for i := 0; i < listLen; i++ {
+		item := list[i]
+		publishTime := time.Now().Format(utils.FormatDateTime)
+		if item.PublishTime != "" {
+			// 发布时间固定为首次发布时间
+			publishTime = item.PublishTime
+		}
+		if err = models.PublishEnglishReportById(item.Id, publishTime); err != nil {
+			err = e
+			return
+		}
+		go func() {
+			_ = UpdateEnglishReportEs(item.Id, 2)
+		}()
+	}
+	return
+}
+
+// UpdateEnglishReportEs 更新英文报告/章节Es
+func UpdateEnglishReportEs(reportId int, publishState int) (err error) {
+	if reportId <= 0 {
+		return
+	}
+	reportInfo, err := models.GetEnglishReportById(reportId)
+	if err != nil {
+		return
+	}
+	// 新增报告ES
+	esReport := &models.ElasticEnglishReportDetail{
+		Id:                 strconv.Itoa(reportInfo.Id),
+		ReportId:           reportInfo.Id,
+		Title:              reportInfo.Title,
+		Abstract:           reportInfo.Abstract,
+		BodyContent:        utils.TrimHtml(html.UnescapeString(reportInfo.Content)),
+		PublishTime:        reportInfo.PublishTime,
+		CreateTime:         reportInfo.CreateTime,
+		ReportCode:         reportInfo.ReportCode,
+		PublishState:       publishState,
+		Author:             reportInfo.Author,
+		Frequency:          reportInfo.Frequency,
+		ClassifyIdFirst:    reportInfo.ClassifyIdFirst,
+		ClassifyNameFirst:  reportInfo.ClassifyNameFirst,
+		ClassifyIdSecond:   reportInfo.ClassifyIdSecond,
+		ClassifyNameSecond: reportInfo.ClassifyNameSecond,
+		StageStr:           strconv.Itoa(reportInfo.Stage),
+		Overview:           utils.TrimHtml(html.UnescapeString(reportInfo.Overview)),
+		ContentSub:         utils.TrimHtml(html.UnescapeString(reportInfo.ContentSub)),
+	}
+	docId := fmt.Sprintf("%d", reportInfo.Id)
+	if err = EsAddOrEditEnglishReport(utils.EsEnglishReportIndexName, docId, esReport); err != nil {
+		return
+	}
+	return
+}

+ 360 - 3
services/report.go

@@ -1,12 +1,16 @@
 package services
 
 import (
+	"errors"
 	"fmt"
 	"github.com/PuerkitoBio/goquery"
+	"golang.org/x/net/context"
 	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
 	"hongze/hongze_task/utils"
 	"html"
 	"os"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -66,7 +70,7 @@ func FixReportCount() {
 		contentHtml := html.UnescapeString(content)
 		doc, err := goquery.NewDocumentFromReader(strings.NewReader(contentHtml))
 		if err != nil {
-			fmt.Println("line 70:"+err.Error())
+			fmt.Println("line 70:" + err.Error())
 			return
 		}
 		isModify := false
@@ -106,9 +110,362 @@ func FixReportCount() {
 	}
 }
 
-func DeleteReportSaveLog()(err error) {
+func DeleteReportSaveLog() (err error) {
 	fmt.Println("start")
 	models.DeleteReportSaveLog()
 	fmt.Println("end")
 	return
-}
+}
+
+// UpdateReportEs 更新报告/章节Es
+func UpdateReportEs(reportId int, publishState int) (err error) {
+	if reportId <= 0 {
+		return
+	}
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		return
+	}
+	categories := ""
+	if reportInfo.HasChapter == 1 {
+		// 晨周报
+		chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
+		if tmpErr != nil {
+			return
+		}
+		if len(chapterList) > 0 {
+			for i := 0; i < len(chapterList); i++ {
+				// 章节对应的品种
+				permissionList, tmpErr := models.GetChapterTypePermissionByTypeIdAndResearchType(chapterList[i].TypeId, chapterList[i].ReportType)
+				if tmpErr != nil {
+					return
+				}
+				categoryArr := make([]string, 0)
+				if len(permissionList) > 0 {
+					for ii := 0; ii < len(permissionList); ii++ {
+						categoryArr = append(categoryArr, permissionList[ii].PermissionName)
+					}
+				}
+				aliasArr, _ := addCategoryAliasToArr(categoryArr)
+				chapterCategories := strings.Join(aliasArr, ",")
+
+				esChapter := &models.ElasticReportDetail{
+					ReportId:           chapterList[i].ReportId,
+					ReportChapterId:    chapterList[i].ReportChapterId,
+					Title:              chapterList[i].Title,
+					Abstract:           chapterList[i].Abstract,
+					BodyContent:        utils.TrimHtml(html.UnescapeString(chapterList[i].Content)),
+					PublishTime:        chapterList[i].PublishTime.Format(utils.FormatDateTime),
+					PublishState:       chapterList[i].PublishState,
+					Author:             chapterList[i].Author,
+					ClassifyIdFirst:    chapterList[i].ClassifyIdFirst,
+					ClassifyNameFirst:  chapterList[i].ClassifyNameFirst,
+					ClassifyIdSecond:   0,
+					ClassifyNameSecond: "",
+					Categories:         chapterCategories,
+					StageStr:           strconv.Itoa(chapterList[i].Stage),
+				}
+				chapterDocId := fmt.Sprintf("%d-%d", reportInfo.Id, chapterList[i].ReportChapterId)
+				if err = EsAddOrEditReport(utils.EsReportIndexName, chapterDocId, esChapter); err != nil {
+					return
+				}
+			}
+		}
+	} else {
+		permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword(reportInfo.ClassifyNameSecond, "rddp")
+		if tmpErr != nil {
+			return
+		}
+		categoryArr := make([]string, 0)
+		for i := 0; i < len(permissionList); i++ {
+			categoryArr = append(categoryArr, permissionList[i].PermissionName)
+		}
+		aliasArr, _ := addCategoryAliasToArr(categoryArr)
+		categories = strings.Join(aliasArr, ",")
+	}
+
+	// 新增报告ES
+	esReport := &models.ElasticReportDetail{
+		ReportId:           reportInfo.Id,
+		ReportChapterId:    0,
+		Title:              reportInfo.Title,
+		Abstract:           reportInfo.Abstract,
+		BodyContent:        utils.TrimHtml(html.UnescapeString(reportInfo.Content)),
+		PublishTime:        reportInfo.PublishTime.Format(utils.FormatDateTime),
+		PublishState:       publishState,
+		Author:             reportInfo.Author,
+		ClassifyIdFirst:    reportInfo.ClassifyIdFirst,
+		ClassifyNameFirst:  reportInfo.ClassifyNameFirst,
+		ClassifyIdSecond:   reportInfo.ClassifyIdSecond,
+		ClassifyNameSecond: reportInfo.ClassifyNameSecond,
+		Categories:         categories,
+		StageStr:           strconv.Itoa(reportInfo.Stage),
+	}
+	docId := fmt.Sprintf("%d-%d", reportInfo.Id, 0)
+	if err = EsAddOrEditReport(utils.EsReportIndexName, docId, esReport); err != nil {
+		return
+	}
+
+	return
+}
+
+// addCategoryAliasToArr 品种别名
+func addCategoryAliasToArr(categoryArr []string) (aliasArr []string, err error) {
+	aliasArr = categoryArr
+	if len(categoryArr) > 0 {
+		for i := 0; i < len(categoryArr); i++ {
+			if strings.Contains(categoryArr[i], "沥青") {
+				aliasArr = append(aliasArr, "BU")
+			}
+			if strings.Contains(categoryArr[i], "MEG") {
+				aliasArr = append(aliasArr, "EG", "乙二醇")
+			}
+			if strings.Contains(categoryArr[i], "聚酯") {
+				aliasArr = append(aliasArr, "长丝", "短纤", "瓶片")
+			}
+			if strings.Contains(categoryArr[i], "纯苯+苯乙烯") {
+				aliasArr = append(aliasArr, "EB")
+			}
+			if strings.Contains(categoryArr[i], "聚乙烯") {
+				aliasArr = append(aliasArr, "PP", "PE")
+			}
+			if strings.Contains(categoryArr[i], "玻璃纯碱") {
+				aliasArr = append(aliasArr, "玻璃", "纯碱", "FG", "SA")
+			}
+			if strings.Contains(categoryArr[i], "甲醇") {
+				aliasArr = append(aliasArr, "甲醇", "MA")
+			}
+			if strings.Contains(categoryArr[i], "橡胶") {
+				aliasArr = append(aliasArr, "橡胶", "RU")
+			}
+		}
+	}
+	return
+}
+
+// PublishReport 定时发布研报-每秒
+func PublishReport(cont context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("PublishReport-定时发布研报失败, ErrMsg:\n"+err.Error(), 3)
+		}
+	}()
+
+	now := time.Now().Format(utils.FormatDateTime)
+	list, e := models.GetPrePublishedReports(now)
+	if e != nil {
+		return
+	}
+	listLen := len(list)
+	if listLen == 0 {
+		return
+	}
+	// 比对时间(分钟),时间相等则发布并推送
+	for i := 0; i < listLen; i++ {
+		item := list[i]
+		var publishTime time.Time
+		if item.MsgIsSend == 1 && !item.PublishTime.IsZero() { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+			publishTime = item.PublishTime
+		} else {
+			publishTime = time.Now()
+		}
+		if item.HasChapter == 1 && (item.ChapterType == utils.REPORT_TYPE_DAY || item.ChapterType == utils.REPORT_TYPE_WEEK) {
+			continue
+		}
+		if err = models.PublishReportById(item.Id, publishTime); err != nil {
+			err = e
+			return
+		}
+
+		go func() {
+			// 生成音频
+			if item.VideoUrl == "" {
+				_ = CreateVideo(item)
+			}
+			//// 推送找钢网
+			//if utils.RunMode == "release" && (report.ClassifyNameSecond == "知白守黑日评" || report.ClassifyNameSecond == "股债日评") {
+			//	_ = services.ZhaoGangSend(report)
+			//}
+			// 更新报告Es
+			_ = UpdateReportEs(item.Id, 2)
+
+			// 判断是否未发送模版消息,并且配置了立即推送模版消息的报告需要推送
+			if item.MsgIsSend == 0 && item.PreMsgSend == 1 {
+				_ = ReportSendTemplateMsg(item.Id)
+			}
+		}()
+
+	}
+	return
+}
+
+// PublishReportTest 定时发布研报-每秒
+/*func PublishReportTest() (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err.Error())
+		}
+	}()
+	item, err := models.GetReportById(3331)
+	if err != nil {
+		return
+	}
+	// 判断是否未发送模版消息,并且配置了立即推送模版消息的报告需要推送
+	if item.MsgIsSend == 0 && item.PreMsgSend == 1 {
+		err = ReportSendTemplateMsg(item.Id)
+		if err != nil {
+			return
+		}
+	}
+	return
+}*/
+
+func ReportSendTemplateMsg(reportId int) (err error) {
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf("ReportSendTemplateMsg, 发送报告模版消息失败, ReportId:%s, Err:%s", reportId, err.Error())
+			utils.FileLog.Error(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+
+	report, err := models.GetReportById(reportId)
+	if err != nil {
+		err = errors.New("查询报告失败 Err:" + err.Error())
+		return
+	}
+	if report.MsgIsSend == 1 {
+		err = errors.New("模板消息已推送,请勿重复操作")
+		return
+	}
+
+	videoNameDate := `(` + time.Now().Format("0102") + `)`
+	err = models.UpdateReportPublishTime(reportId, videoNameDate)
+	if err != nil {
+		err = errors.New("修改发布时间失败,Err:" + err.Error())
+		return
+	}
+	if report.HasChapter > 0 {
+		err = models.UpdateReportChapterPublishTime(reportId, videoNameDate)
+		if err != nil {
+			err = errors.New("修改发布时间失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	err = sendMiniProgramReportWxMsg(report)
+	if err != nil {
+		err = errors.New("发送失败,Err:" + err.Error())
+		return
+	}
+	err = models.ModifyReportMsgIsSend(reportId)
+	if err != nil {
+		err = errors.New("发送失败,Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// sendMiniProgramReportWxMsg 推送报告微信模板消息-小程序链接
+func sendMiniProgramReportWxMsg(report *models.ReportDetail) (err error) {
+	reportId := report.Id
+	var msg string
+	reportIdStr := strconv.Itoa(reportId)
+	defer func() {
+		if err != nil {
+			fmt.Println("msg:", msg)
+			utils.FileLog.Error(fmt.Sprintf("SendMiniProgramReportWxMsg, 发送报告模版消息失败, ReportId:%s, Err:%s", reportIdStr, err.Error()))
+			go alarm_msg.SendAlarmMsg("SendMiniProgramReportWxMsg发送报告模版消息失败;"+"ReportId:"+reportIdStr+",Err:"+err.Error()+";msg:"+msg, 3)
+			//go utils.SendEmail("SendMiniProgramReportWxMsg发送报告模版消息失败"+"【"+utils.APPNAME+"】"+"【"+utils.RunMode+"】"+time.Now().Format("2006-01-02 15:04:05"), "ReportId:"+reportIdStr+";"+msg+";Err:"+err.Error(), toUser)
+		}
+	}()
+	utils.FileLog.Info("%s", "services SendMsg")
+
+	if report == nil {
+		utils.FileLog.Info("报告信息不存在")
+		return
+	}
+
+	var openIdArr []string
+	if report.ClassifyIdSecond <= 0 {
+		openIdArr, err = models.GetOpenIdArr()
+		if err != nil {
+			msg = "get GetOpenIdArr err:" + err.Error()
+			return
+		}
+	} else {
+		classify, err := models.GetClassifyById(report.ClassifyIdSecond)
+		if err != nil {
+			msg = "获取报告分类失败 err:" + err.Error()
+			return err
+		}
+		if classify.IsMassSend == 1 {
+			openIdArr, err = models.GetOpenIdArr()
+			if err != nil {
+				msg = "get GetOpenIdArr err:" + err.Error()
+				return err
+			}
+		} else {
+			openIdArr, err = models.GetOpenIdArrByClassifyNameSecond(report.ClassifyNameSecond)
+			if err != nil {
+				msg = "GetOpenIdArrByClassifyNameSecond err:" + err.Error()
+				return err
+			}
+		}
+	}
+
+	title := fmt.Sprintf("弘则%s", report.ClassifyNameFirst)
+	if CheckTwoWeekOrMonthReport(report.ClassifyIdFirst, report.ClassifyNameFirst) {
+		title = fmt.Sprintf("弘则%s", report.ClassifyNameSecond)
+	}
+	//redirectUrl := utils.TemplateRedirectUrl + strconv.Itoa(reportId)
+	first := fmt.Sprintf("Hi,最新一期%s已上线,欢迎查看", report.ClassifyNameFirst)
+	keyword1 := title
+	keyword2 := report.Title
+	keyword3 := report.PublishTime
+	keyword4 := report.Abstract
+
+	var wxAppPath string
+	if report.ChapterType == utils.REPORT_TYPE_WEEK {
+		wxAppPath = fmt.Sprintf("pages-report/chapterList?reportId=%s", reportIdStr)
+	} else {
+		wxAppPath = fmt.Sprintf("pages-report/reportDetail?reportId=%s", reportIdStr)
+	}
+
+	sendInfo := new(SendWxTemplate)
+	sendInfo.First = first
+	sendInfo.Keyword1 = keyword1
+	sendInfo.Keyword2 = keyword2
+	sendInfo.Keyword3 = keyword3
+	sendInfo.Keyword4 = keyword4
+	sendInfo.TemplateId = utils.TemplateIdByProduct
+	sendInfo.RedirectUrl = wxAppPath
+	sendInfo.Resource = wxAppPath
+	sendInfo.SendType = utils.TEMPLATE_MSG_REPORT
+	sendInfo.OpenIdArr = openIdArr
+	sendInfo.RedirectTarget = 1
+	err = SendTemplateMsgV2(sendInfo)
+
+	return
+}
+
+// CheckTwoWeekOrMonthReport 校验推送报告是否为双周报或者月报
+func CheckTwoWeekOrMonthReport(classifyId int, classifyName string) (ok bool) {
+	if utils.RunMode == "debug" {
+		miniStrArr := []string{
+			"双周报", "月报",
+		}
+		if utils.InArrayByStr(miniStrArr, classifyName) {
+			ok = true
+		}
+	} else {
+		// 此处生产环境用ID主要是担心分类改了名字...
+		IdArr := []int{
+			96, 112,
+		}
+		if utils.InArrayByInt(IdArr, classifyId) {
+			ok = true
+		}
+	}
+	return
+}

+ 8 - 0
services/task.go

@@ -145,6 +145,14 @@ func Task() {
 	publishVoiceBroadcast := task.NewTask("publishVoiceBroadcast", "0 */1 * * * *", PublishVoiceBroadcast)
 	task.AddTask("定时发布研报语音播报", publishVoiceBroadcast)
 
+	// 定时发布研报
+	publishReport := task.NewTask("publishReport", "* * * * * *", PublishReport)
+	task.AddTask("定时发布研报", publishReport)
+
+	// 定时发布英文研报
+	publishEnglishReport := task.NewTask("publishEnglishReport", "* * * * * *", PublishEnglishReport)
+	task.AddTask("定时发布英文研报", publishEnglishReport)
+
 	// 定时生成本周研究汇总
 	cygxResearchSummary := task.NewTask("cygxResearchSummary", "0 0 15 * * 5", CygxResearchSummary)
 	task.AddTask("定时生成本周研究汇总", cygxResearchSummary)

+ 289 - 0
services/video.go

@@ -0,0 +1,289 @@
+package services
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"github.com/PuerkitoBio/goquery"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"html"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"time"
+	"unicode"
+)
+
+func CreateVideo(report *models.Report) (err error) {
+	if utils.XfAPPID == `` {
+		// 如果科大讯飞未配置,那么就直接返回,不生成音频了
+		return
+	}
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("CreateVideo Err:%s", err.Error())
+			go alarm_msg.SendAlarmMsg("CreateVideo, Err:"+err.Error(), 3)
+			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+	ct, err := time.Parse(utils.FormatDateTime, report.CreateTime)
+	createTime := ct.Format("0102")
+	videoName := report.Title + "(" + createTime + ")"
+	content := html.UnescapeString(report.Content)
+	content = strings.Replace(content, "Powered", "", -1)
+	content = strings.Replace(content, "by", "", -1)
+	content = strings.Replace(content, "Froala", "", -1)
+	content = strings.Replace(content, "Editor", "", -1)
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
+	if err != nil {
+		return
+	}
+
+	param := new(models.XfSendParam)
+	param.Common.AppId = utils.XfAPPID
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = utils.XfVcn
+	param.Business.Speed = 50
+	param.Business.Volume = 100
+	param.Business.Pitch = 50
+	param.Business.Bgs = 0
+	param.Business.Tte = "UTF8"
+	param.Business.Reg = "2"
+	param.Business.Rdn = "0"
+	param.Data.Status = 2
+	videoContent := doc.Text()
+
+	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
+	savePath := "./" + saveName
+	//if utils.FileIsExist(savePath) {
+	//	os.Remove(savePath)
+	//}
+	contentArr := GetChineseCount(videoContent)
+	for _, v := range contentArr {
+		newText := v
+		param.Data.Text = base64.StdEncoding.EncodeToString([]byte(newText))
+		result, err := json.Marshal(param)
+		if err != nil {
+			return err
+		}
+		err = GetXfVideo(result, savePath)
+		if err != nil {
+			err = errors.New("GetXfVideo Err:" + err.Error())
+			utils.FileLog.Error("GetXfVideo err", err.Error())
+			return err
+		}
+		time.Sleep(5 * time.Second)
+	}
+	uploadUrl, err := UploadAudioAliyun(saveName, savePath)
+	if err != nil {
+		err = errors.New("UploadAudioAliyun Err:" + err.Error())
+		return
+	}
+
+	fileBody, err := ioutil.ReadFile(savePath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	playSeconds, err := mp3duration.Calculate(savePath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(savePath)
+		if err != nil {
+			err = errors.New("GetVideoPlaySeconds Err:" + err.Error())
+			return
+		}
+	}
+
+	if playSeconds > 0 {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}
+	err = models.ModifyReportVideo(report.Id, uploadUrl, videoName, sizeStr, playSeconds)
+	return
+}
+
+func GetChineseCount(str1 string) []string {
+	fontArr := make([]string, 0)
+	str := ""
+	count := 0
+	for _, char := range str1 {
+		str += string(char)
+		if unicode.Is(unicode.Han, char) {
+			count++
+			if count >= 1700 {
+				fontArr = append(fontArr, str)
+				str = ""
+				count = 0
+			}
+		}
+	}
+	fontArr = append(fontArr, str)
+	return fontArr
+}
+
+// BoxHeader 信息头
+type BoxHeader struct {
+	Size       uint32
+	FourccType [4]byte
+	Size64     uint64
+}
+
+// GetMP4Duration 获取视频时长,以秒计
+func GetMP4Duration(reader io.ReaderAt) (lengthOfTime uint32, err error) {
+	var info = make([]byte, 0x10)
+	var boxHeader BoxHeader
+	var offset int64 = 0
+	// 获取moov结构偏移
+	for {
+		_, err = reader.ReadAt(info, offset)
+		if err != nil {
+			return
+		}
+		boxHeader = getHeaderBoxInfo(info)
+		fourccType := getFourccType(boxHeader)
+		if fourccType == "moov" {
+			break
+		}
+		// 有一部分mp4 mdat尺寸过大需要特殊处理
+		if fourccType == "mdat" {
+			if boxHeader.Size == 1 {
+				offset += int64(boxHeader.Size64)
+				continue
+			}
+		}
+		offset += int64(boxHeader.Size)
+	}
+	// 获取moov结构开头一部分
+	moovStartBytes := make([]byte, 0x100)
+	_, err = reader.ReadAt(moovStartBytes, offset)
+	if err != nil {
+		return
+	}
+	// 定义timeScale与Duration偏移
+	timeScaleOffset := 0x1C
+	durationOffest := 0x20
+	timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
+	Duration := binary.BigEndian.Uint32(moovStartBytes[durationOffest : durationOffest+4])
+	lengthOfTime = Duration / timeScale
+	return
+}
+
+// getHeaderBoxInfo 获取头信息
+func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
+	buf := bytes.NewBuffer(data)
+	binary.Read(buf, binary.BigEndian, &boxHeader)
+	return
+}
+
+// getFourccType 获取信息头类型
+func getFourccType(boxHeader BoxHeader) (fourccType string) {
+	fourccType = string(boxHeader.FourccType[:])
+	return
+}
+
+// CreateReportVideo 生成报告video
+func CreateReportVideo(reportTitle, reportContent, reportTime string) (uploadUrl, videoName, sizeStr string, playSeconds float64, err error) {
+	if utils.XfAPPID == `` {
+		// 如果科大讯飞未配置,那么就直接返回,不生成音频了
+		return
+	}
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("CreateReportVideo Err:%s", err.Error())
+			go alarm_msg.SendAlarmMsg("CreateReportVideo, reportTitle:"+reportTitle+", Err:"+err.Error(), 3)
+			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "CreateReportVideo, reportTitle:" + reportTitle +", Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+	if reportContent == "" {
+		return
+	}
+	ct, err := time.Parse(utils.FormatDateTime, reportTime)
+	if err != nil {
+		return
+	}
+	createTime := ct.Format("0102")
+	videoName = reportTitle + "(" + createTime + ")"
+	content := html.UnescapeString(reportContent)
+	content = strings.Replace(content, "Powered", "", -1)
+	content = strings.Replace(content, "by", "", -1)
+	content = strings.Replace(content, "Froala", "", -1)
+	content = strings.Replace(content, "Editor", "", -1)
+	doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
+	if err != nil {
+		return
+	}
+
+	param := new(models.XfSendParam)
+	param.Common.AppId = utils.XfAPPID
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = utils.XfVcn
+	param.Business.Speed = 50
+	param.Business.Volume = 100
+	param.Business.Pitch = 50
+	param.Business.Bgs = 0
+	param.Business.Tte = "UTF8"
+	param.Business.Reg = "2"
+	param.Business.Rdn = "0"
+	param.Data.Status = 2
+	videoContent := doc.Text()
+
+	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
+	savePath := "./" + saveName
+	//if utils.FileIsExist(savePath) {
+	//	os.Remove(savePath)
+	//}
+	contentArr := GetChineseCount(videoContent)
+	for _, v := range contentArr {
+		newText := v
+		param.Data.Text = base64.StdEncoding.EncodeToString([]byte(newText))
+		result, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		err = GetXfVideo(result, savePath)
+		if err != nil {
+			err = errors.New("GetXfVideo Err:" + err.Error())
+			utils.FileLog.Error("GetXfVideo err", err.Error())
+			return
+		}
+		time.Sleep(5 * time.Second)
+	}
+	uploadUrl, err = UploadAudioAliyun(saveName, savePath)
+	if err != nil {
+		err = errors.New("UploadAudioAliyun Err:" + err.Error())
+		return
+	}
+
+	fileBody, err := ioutil.ReadFile(savePath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr = utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	playSeconds, err = mp3duration.Calculate(savePath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(savePath)
+		if err != nil {
+			err = errors.New("GetVideoPlaySeconds Err:" + err.Error())
+			return
+		}
+	}
+
+	if playSeconds > 0 {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}
+
+	return
+}

+ 0 - 37
services/wx_template_msg.go

@@ -1,7 +1,6 @@
 package services
 
 import (
-	"bytes"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -71,42 +70,6 @@ func SendWxMsgWithFrequency(first, keyword1, keyword2, remark string, openIdList
 	return
 }
 
-func WxSendTemplateMsg(sendUrl string, sendMap map[string]interface{}, items []*models.OpenIdList) (err error) {
-	for _, v := range items {
-		sendMap["touser"] = v.OpenId
-		data, err := json.Marshal(sendMap)
-		if err != nil {
-			fmt.Println("SendTemplateMsgOne Marshal Err:", err.Error())
-			return err
-		}
-		utils.FileLog.Info(fmt.Sprintf("One SendData:%s", string(data)))
-		err = SendTemplateMsg(sendUrl, data)
-		if err != nil {
-			fmt.Println("send err:", err.Error())
-			return err
-		}
-	}
-	return
-}
-
-func SendTemplateMsg(sendUrl string, data []byte) (err error) {
-	client := http.Client{}
-	resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(data))
-	if err != nil {
-		return
-	}
-	defer resp.Body.Close()
-
-	body, _ := ioutil.ReadAll(resp.Body)
-	utils.FileLog.Info(fmt.Sprintf("SendResult:%s", string(body)))
-	var templateResponse models.SendTemplateResponse
-	err = json.Unmarshal(body, &templateResponse)
-	if err != nil {
-		return err
-	}
-	return
-}
-
 // 到期提醒模板消息
 func SendWxMsgWithCompanyRemind(first, keyword1, keyword2, remark, code string, openIdList []*models.OpenIdList) (err error) {
 	var msg string

+ 108 - 0
services/xfyun.go

@@ -0,0 +1,108 @@
+package services
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/crypt"
+	"hongze/hongze_task/models"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/gorilla/websocket"
+	"hongze/hongze_task/utils"
+)
+
+// 科大讯飞,语音合成
+func GetXfVideo(body []byte, savePath string) (err error) {
+	path, err := assembleAuthUrl()
+	if err != nil {
+		return
+	}
+	conn, _, err := websocket.DefaultDialer.Dial(path, nil)
+	if err != nil {
+		return
+	}
+	defer conn.Close()
+
+	err = conn.WriteMessage(websocket.TextMessage, body)
+	if err != nil {
+		return
+	}
+
+	for {
+		_, message, err := conn.ReadMessage()
+		if err != nil {
+			fmt.Println("ReadMessage Err:" + err.Error())
+			return err
+		}
+		item := new(models.XfReciveResult)
+		err = json.Unmarshal(message, &item)
+		if err != nil {
+			fmt.Println("json.Unmarshal Err:" + err.Error())
+			return err
+		}
+		if item.Code != 0 {
+			goto readWebSocketFail
+		}
+		if item.Code == 0 && item.Data != nil {
+			if item.Data.Status == 1 {
+				audio := item.Data.Audio
+				err = utils.SaveBase64ToFileBySeek(audio, savePath)
+				if err != nil {
+					fmt.Println("文件保存失败", err.Error())
+					goto readWebSocketFail
+				}
+			} else {
+				audio := item.Data.Audio
+				err = utils.SaveBase64ToFileBySeek(audio, savePath)
+				if err != nil {
+					fmt.Println("文件保存失败", err.Error())
+					goto webSocketClose
+					//return
+				}
+				fmt.Println("goto close")
+				goto webSocketClose
+			}
+		}
+	}
+readWebSocketFail:
+	conn.Close()
+	fmt.Println("goto readWebSocketFail")
+webSocketClose:
+	conn.Close()
+	fmt.Println("goto webSocketClose")
+	return nil
+}
+
+// @hosturl :  like  wss://iat-api.xfyun.cn/v2/iat
+// @apikey : apiKey
+// @apiSecret : apiSecret
+func assembleAuthUrl() (callUrl string, err error) {
+	ul, err := url.Parse(utils.XfHostUrl)
+	if err != nil {
+		return
+	}
+	//签名时间
+	date := time.Now().UTC().Format(time.RFC1123)
+	//参与签名的字段 host ,date, request-line
+	signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"}
+	//拼接签名字符串
+	sign := strings.Join(signString, "\n")
+	fmt.Println("sign:", sign)
+	//签名结果
+	sha := crypt.HmacSha256EncryptToBase64([]byte(sign), []byte(utils.XfAPISecret))
+	//构建请求参数 此时不需要urlencoding
+	authUrl := fmt.Sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", utils.XfAPIKey,
+		"hmac-sha256", "host date request-line", sha)
+	//将请求参数使用base64编码
+	authorization := base64.StdEncoding.EncodeToString([]byte(authUrl))
+	v := url.Values{}
+	v.Add("host", ul.Host)
+	v.Add("date", date)
+	v.Add("authorization", authorization)
+	//将编码后的字符串url encode后添加到url后面
+	callUrl = utils.XfHostUrl + "?" + v.Encode()
+	return
+}

+ 60 - 20
utils/common.go

@@ -14,13 +14,14 @@ import (
 	"math/rand"
 	"net"
 	"os"
+	"os/exec"
 	"regexp"
 	"strconv"
 	"strings"
 	"time"
 )
 
-//随机数种子
+// 随机数种子
 var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
 
 func GetRandString(size int) string {
@@ -57,13 +58,13 @@ func StringsToJSON(str string) string {
 	return jsons
 }
 
-//序列化
+// 序列化
 func ToString(v interface{}) string {
 	data, _ := json.Marshal(v)
 	return string(data)
 }
 
-//md5加密
+// md5加密
 func MD5(data string) string {
 	m := md5.Sum([]byte(data))
 	return hex.EncodeToString(m[:])
@@ -98,7 +99,7 @@ func GetToday(format string) string {
 	return today
 }
 
-//获取今天剩余秒数
+// 获取今天剩余秒数
 func GetTodayLastSecond() time.Duration {
 	today := GetToday(FormatDate) + " 23:59:59"
 	end, _ := time.ParseInLocation(FormatDateTime, today, time.Local)
@@ -120,7 +121,7 @@ func GetBrithDate(idcard string) string {
 	return GetToday(FormatDate)
 }
 
-//处理性别
+// 处理性别
 func WhichSexByIdcard(idcard string) string {
 	var sexs = [2]string{"女", "男"}
 	length := len(idcard)
@@ -134,7 +135,7 @@ func WhichSexByIdcard(idcard string) string {
 	return "男"
 }
 
-//截取小数点后几位
+// 截取小数点后几位
 func SubFloatToString(f float64, m int) string {
 	n := strconv.FormatFloat(f, 'f', -1, 64)
 	if n == "" {
@@ -153,14 +154,14 @@ func SubFloatToString(f float64, m int) string {
 	return newn[0] + "." + newn[1][:m]
 }
 
-//截取小数点后几位
+// 截取小数点后几位
 func SubFloatToFloat(f float64, m int) float64 {
 	newn := SubFloatToString(f, m)
 	newf, _ := strconv.ParseFloat(newn, 64)
 	return newf
 }
 
-//获取相差时间-年
+// 获取相差时间-年
 func GetYearDiffer(start_time, end_time string) int {
 	t1, _ := time.ParseInLocation("2006-01-02", start_time, time.Local)
 	t2, _ := time.ParseInLocation("2006-01-02", end_time, time.Local)
@@ -171,7 +172,7 @@ func GetYearDiffer(start_time, end_time string) int {
 	return age
 }
 
-//获取相差时间-秒
+// 获取相差时间-秒
 func GetSecondDifferByTime(start_time, end_time time.Time) int64 {
 	diff := end_time.Unix() - start_time.Unix()
 	return diff
@@ -198,7 +199,7 @@ func StrListToString(strList []string) (str string) {
 	return ""
 }
 
-//Token
+// Token
 func GetToken() string {
 	randStr := GetRandString(64)
 	token := MD5(randStr + Md5Key)
@@ -206,30 +207,30 @@ func GetToken() string {
 	return strings.ToUpper(token + GetRandString(tokenLen))
 }
 
-//数据没有记录
+// 数据没有记录
 func ErrNoRow() string {
 	return "<QuerySeter> no row found"
 }
 
-//校验邮箱格式
+// 校验邮箱格式
 func ValidateEmailFormatat(email string) bool {
 	reg := regexp.MustCompile(RegularEmail)
 	return reg.MatchString(email)
 }
 
-//验证是否是手机号
+// 验证是否是手机号
 func ValidateMobileFormatat(mobileNum string) bool {
 	reg := regexp.MustCompile(RegularMobile)
 	return reg.MatchString(mobileNum)
 }
 
-//判断文件是否存在
+// 判断文件是否存在
 func FileIsExist(filePath string) bool {
 	_, err := os.Stat(filePath)
 	return err == nil || os.IsExist(err)
 }
 
-//获取图片扩展名
+// 获取图片扩展名
 func GetImgExt(file string) (ext string, err error) {
 	var headerByte []byte
 	headerByte = make([]byte, 8)
@@ -272,7 +273,7 @@ func GetImgExt(file string) (ext string, err error) {
 	return ext, nil
 }
 
-//保存图片
+// 保存图片
 func SaveImage(path string, img image.Image) (err error) {
 	//需要保持的文件
 	imgfile, err := os.Create(path)
@@ -282,7 +283,7 @@ func SaveImage(path string, img image.Image) (err error) {
 	return err
 }
 
-//保存base64数据为文件
+// 保存base64数据为文件
 func SaveBase64ToFile(content, path string) error {
 	data, err := base64.StdEncoding.DecodeString(content)
 	if err != nil {
@@ -412,7 +413,7 @@ func GetWilsonScore(p, n float64) float64 {
 	return toFixed(((p+1.9208)/(p+n)-1.96*math.Sqrt(p*n/(p+n)+0.9604)/(p+n))/(1+3.8416/(p+n)), 2)
 }
 
-//将中文数字转化成数字,比如 第三百四十五章,返回第345章 不支持一亿及以上
+// 将中文数字转化成数字,比如 第三百四十五章,返回第345章 不支持一亿及以上
 func ChangeWordsToNum(str string) (numStr string) {
 	words := ([]rune)(str)
 	num := 0
@@ -775,7 +776,7 @@ func GetLocalIP() (ip string, err error) {
 	return
 }
 
-//富文本字段过滤处理
+// 富文本字段过滤处理
 func GetRichText(content string) (contentSub string) {
 	contentSub = strings.Replace(content, "<p data-f-id=\"pbf\" style=\"text-align: center; font-size: 14px; margin-top: 30px; opacity: 0.65; font-family: sans-serif;\">Powered by <a href=\"https://www.froala.com/wysiwyg-editor?pb=1\" title=\"Froala Editor\">Froala Editor</a></p>", "", -1)
 	return
@@ -803,4 +804,43 @@ func GetDaysBetween2Date(format, date1Str, date2Str string) (int, error) {
 	}
 	//计算相差天数
 	return int(date1.Sub(date2).Hours() / 24), nil
-}
+}
+
+// SubFloatToFloatStr 截取小数点后几位
+func SubFloatToFloatStr(f float64, m int) string {
+	newn := SubFloatToString(f, m)
+	return newn
+}
+
+func GetVideoPlaySeconds(videoPath string) (playSeconds float64, err error) {
+	cmd := `ffmpeg -i ` + videoPath + `  2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//`
+	out, err := exec.Command("bash", "-c", cmd).Output()
+	if err != nil {
+		return
+	}
+	outTimes := string(out)
+	fmt.Println("outTimes:", outTimes)
+	if outTimes != "" {
+		timeArr := strings.Split(outTimes, ":")
+		h := timeArr[0]
+		m := timeArr[1]
+		s := timeArr[2]
+		hInt, err := strconv.Atoi(h)
+		if err != nil {
+			return playSeconds, err
+		}
+
+		mInt, err := strconv.Atoi(m)
+		if err != nil {
+			return playSeconds, err
+		}
+		s = strings.Trim(s, " ")
+		s = strings.Trim(s, "\n")
+		sInt, err := strconv.ParseFloat(s, 64)
+		if err != nil {
+			return playSeconds, err
+		}
+		playSeconds = float64(hInt)*3600 + float64(mInt)*60 + float64(sInt)
+	}
+	return
+}

+ 61 - 0
utils/config.go

@@ -40,6 +40,8 @@ var (
 	//内部员工公众号(弘则部门)
 	AdminWxAppId     string
 	AdminWxAppSecret string
+
+	TemplateIdByProduct string //产品运行报告通知-模板ID
 )
 
 // oss配置
@@ -54,6 +56,18 @@ var (
 	AccessKeySecret string = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
 )
 
+// ES配置
+var (
+	ES_URL      string // ES服务器地址
+	ES_USERNAME string // ES账号
+	ES_PASSWORD string // ES密码
+)
+
+var (
+	EsReportIndexName        string //研报ES索引
+	EsEnglishReportIndexName string //英文研报ES索引
+)
+
 var (
 	THS_SendUrl        string //同花顺地址url
 	THS_SyncWxGroupUrl string //同花顺同步微信群url
@@ -78,6 +92,21 @@ var (
 	SendWxTemplateMsgUrl string
 )
 
+// 科大讯飞--语音合成
+var (
+	XfSTATUS_FIRST_FRAME    = 0 //第一帧标识
+	XfSTATUS_CONTINUE_FRAME = 1 //中间帧标识
+	XfSTATUS_LAST_FRAME     = 2 //最后一帧标识
+	XfHost                  = "tts-api.xfyun.cn"
+	XfMaxFontSize           = 8000
+	XfAPPID                 string
+	XfAPIKey                string
+	XfAPISecret             string
+	XfHostUrl               string
+	XfOrigin                string
+	XfVcn                   string //发言人
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -183,6 +212,38 @@ ZwIDAQAB
 		YbCommonTemplateId = "CB7bOl7f3viMG4s1uhRo7WM0Jbx3WvodKuIZ8A_z8fM"
 	}
 
+	// 微信模版消息
+	{
+		TemplateIdByProduct = config["template_id_by_product"]
+	}
+	// oss
+	{
+		Endpoint = config["endpoint"]
+	}
+	// ES配置
+	{
+		ES_URL = config["es_url"]
+		ES_USERNAME = config["es_username"]
+		ES_PASSWORD = config["es_password"]
+	}
+
+	// ES 索引
+	{
+		EsReportIndexName = config["es_report_index_name"]
+		EsEnglishReportIndexName = config["es_english_report_index_name"]
+	}
+
+	// 科大讯飞
+	{
+
+		XfAPPID = config["xf_appid"]
+		XfAPIKey = config["xf_api_key"]
+		XfAPISecret = config["xf_api_secret"]
+		XfHostUrl = config["xf_host_url"]
+		XfOrigin = config["xf_origin"]
+		XfVcn = config["xf_vcn"]
+	}
+
 	// 进门财经开放api配置
 	ComeinOpenApiConfig()
 }

+ 5 - 13
utils/constants.go

@@ -39,25 +39,17 @@ const (
 	LOGIN_CODE               //登录
 )
 
+// 模板消息推送类型
+const (
+	TEMPLATE_MSG_REPORT = iota + 1 //日度点评报告推送
+)
+
 // 聚合短信
 var (
 	TplId    = "65692"
 	JhAppKey = "4c8504c49dd335e99cfd7b6a3a9e2415"
 )
 
-// 科大讯飞--语音合成
-const (
-	XfSTATUS_FIRST_FRAME    = 0 //第一帧标识
-	XfSTATUS_CONTINUE_FRAME = 1 //中间帧标识
-	XfSTATUS_LAST_FRAME     = 2 //最后一帧标识
-	XfAPPID                 = "5ed70e9d"
-	XfAPIKey                = "d580509ca262e9586fb65a7064d5ce77"
-	XfAPISecret             = "a085720dc55850c720fa5576335f847a"
-	XfHostUrl               = "wss://tts-api.xfyun.cn/v2/tts"
-	XfOrigin                = "http://tts-api.xfyun.cn/"
-	XfHost                  = "tts-api.xfyun.cn"
-)
-
 const (
 	EmailSendToHzUsers = "lnyan@hzinsights.com;pdzhao@hzinsights.com;glji@hzinsights.com;tshen@hzinsights.com"
 	//EmailSendToHzUsers = "317699326@qq.com"