tuoling805 1 سال پیش
والد
کامیت
fd08e17366
19فایلهای تغییر یافته به همراه2237 افزوده شده و 9 حذف شده
  1. 13 4
      go.mod
  2. 38 5
      go.sum
  3. 167 0
      models/business_conf.go
  4. 245 0
      models/classify.go
  5. 128 0
      models/english_report.go
  6. 257 0
      models/report.go
  7. 29 0
      models/wx_template_msg.go
  8. 60 0
      models/xfyun.go
  9. 152 0
      services/elastic.go
  10. 87 0
      services/english_report.go
  11. 95 0
      services/oss.go
  12. 369 0
      services/report.go
  13. 36 0
      services/save_log.go
  14. 12 0
      services/task.go
  15. 318 0
      services/video.go
  16. 114 0
      services/xfyun.go
  17. 40 0
      utils/common.go
  18. 62 0
      utils/config.go
  19. 15 0
      utils/constants.go

+ 13 - 4
go.mod

@@ -3,21 +3,30 @@ module eta/eta_task
 go 1.19
 
 require (
+	github.com/PuerkitoBio/goquery v1.8.1
+	github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
 	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/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.2
 	github.com/shopspring/decimal v1.3.1
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
+	golang.org/x/net v0.7.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
 
 require (
+	github.com/andybalholm/cascadia v1.3.1 // indirect
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.1 // 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
@@ -26,10 +35,10 @@ require (
 	github.com/prometheus/common v0.26.0 // indirect
 	github.com/prometheus/procfs v0.6.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/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	google.golang.org/protobuf v1.26.0 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect

+ 38 - 5
go.sum

@@ -3,6 +3,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
+github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
@@ -14,6 +16,10 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
+github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
+github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 h1:o2oaBQGTzO+xNh12e7xWkphNe7H2DTiWv1ml9a2P9PQ=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
@@ -90,6 +96,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=
@@ -146,8 +153,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=
@@ -157,6 +164,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=
@@ -190,6 +198,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=
@@ -199,6 +209,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=
@@ -217,6 +229,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=
@@ -254,6 +268,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=
@@ -371,6 +387,7 @@ github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod
 github.com/yuin/goldmark v1.1.27/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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@@ -397,8 +414,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 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=
@@ -411,6 +429,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 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=
@@ -434,8 +453,10 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 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-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -448,6 +469,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 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=
@@ -474,17 +496,27 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
 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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/text v0.3.0/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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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=
@@ -504,6 +536,7 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 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=

+ 167 - 0
models/business_conf.go

@@ -0,0 +1,167 @@
+package models
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"html"
+	"strings"
+	"time"
+)
+
+const (
+	BusinessConfUseXf          = "UseXf"
+	BusinessConfXfAppid        = "XfAppid"
+	BusinessConfXfApiKey       = "XfApiKey"
+	BusinessConfXfApiSecret    = "XfApiSecret"
+	BusinessConfXfVcn          = "XfVcn"
+	BusinessConfEnPptCoverImgs = "EnPptCoverImgs"
+)
+
+// BusinessConf 商户配置表
+type BusinessConf struct {
+	Id         int    `orm:"column(id);pk"`
+	ConfKey    string `description:"配置Key"`
+	ConfVal    string `description:"配置值"`
+	ValType    int    `description:"1-字符串;2-数值;3-字符串数组;4-富文本;"`
+	Necessary  int    `description:"是否必填:0-否;1-是"`
+	Remark     string `description:"备注"`
+	CreateTime time.Time
+}
+
+func (m *BusinessConf) TableName() string {
+	return "business_conf"
+}
+
+func (m *BusinessConf) PrimaryId() string {
+	return "id"
+}
+
+func (m *BusinessConf) Create() (err error) {
+	o := orm.NewOrmUsingDB("eta")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *BusinessConf) CreateMulti(items []*BusinessConf) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("eta")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *BusinessConf) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("eta")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *BusinessConf) Del() (err error) {
+	o := orm.NewOrmUsingDB("eta")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *BusinessConf) GetItemById(id int) (item *BusinessConf, err error) {
+	o := orm.NewOrmUsingDB("eta")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *BusinessConf) GetItemByCondition(condition string, pars []interface{}) (item *BusinessConf, err error) {
+	o := orm.NewOrmUsingDB("eta")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *BusinessConf) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("eta")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *BusinessConf) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BusinessConf, err error) {
+	o := orm.NewOrmUsingDB("eta")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *BusinessConf) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BusinessConf, err error) {
+	o := orm.NewOrmUsingDB("eta")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetBusinessConf 获取商家配置
+func GetBusinessConf() (list map[string]string, err error) {
+	list = make(map[string]string)
+
+	var items []*BusinessConf
+	o := orm.NewOrmUsingDB("eta")
+	sql := `SELECT * FROM business_conf`
+	_, err = o.Raw(sql).QueryRows(&items)
+	if err != nil {
+		return
+	}
+
+	for _, v := range items {
+		if v.ValType == 4 {
+			list[v.ConfKey] = html.UnescapeString(v.ConfVal)
+			continue
+		}
+		list[v.ConfKey] = v.ConfVal
+	}
+	return
+}
+
+// BusinessConfUpdate 更新配置
+type BusinessConfUpdate struct {
+	ConfKey string
+	ConfVal string
+}
+
+// UpdateBusinessConfMulti 批量修改配置
+func UpdateBusinessConfMulti(items []BusinessConfUpdate) (err error) {
+	o := orm.NewOrmUsingDB("eta")
+	p, err := o.Raw("UPDATE business_conf SET conf_val = ? WHERE conf_key = ?").Prepare()
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = p.Close()
+	}()
+	for _, v := range items {
+		_, err = p.Exec(v.ConfVal, v.ConfKey)
+		if err != nil {
+			return
+		}
+	}
+	return
+}

+ 245 - 0
models/classify.go

@@ -0,0 +1,245 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type Classify struct {
+	Id             int       `orm:"column(id);pk"`
+	ClassifyName   string    `description:"分类名称"`
+	Sort           int       `json:"-"`
+	ParentId       int       `description:"父级分类id"`
+	CreateTime     time.Time `description:"创建时间"`
+	ModifyTime     time.Time `description:"修改时间"`
+	Abstract       string    `description:"栏目简介"`
+	Descript       string    `description:"分享描述"`
+	ReportAuthor   string    `description:"栏目作者"`
+	AuthorDescript string    `description:"作者简介"`
+	ColumnImgUrl   string    `description:"栏目配图"`
+	HeadImgUrl     string    `description:"头部banner"`
+	AvatarImgUrl   string    `description:"头像"`
+	ReportImgUrl   string    `description:"报告配图"`
+	HomeImgUrl     string    `description:"首页配图"`
+	ClassifyLabel  string    `description:"分类标签"`
+	IsMassSend     int       `description:"1:群发,0:非群发"`
+}
+
+type ClassifyAddReq struct {
+	ClassifyName   string `description:"分类名称"`
+	ParentId       int    `description:"父级分类id,没有父级分类传0"`
+	Abstract       string `description:"栏目简介"`
+	Descript       string `description:"分享描述"`
+	ReportAuthor   string `description:"栏目作者"`
+	AuthorDescript string `description:"作者简介"`
+	ColumnImgUrl   string `description:"栏目配图"`
+	ReportImgUrl   string `description:"报告配图"`
+	HeadImgUrl     string `description:"头部banner"`
+	AvatarImgUrl   string `description:"头像"`
+	HomeImgUrl     string `description:"首页配图"`
+	ClassifyLabel  string `description:"分类标签"`
+}
+
+func GetClassifyByName(classifyName string, parentId int) (item *Classify, err error) {
+	sql := `SELECT * FROM classify WHERE classify_name=? AND parent_id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sql, classifyName, parentId).QueryRow(&item)
+	return
+}
+
+func GetClassifyById(classifyId int) (item *Classify, err error) {
+	sql := `SELECT * FROM classify WHERE id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// 添加分类
+func AddClassify(item *Classify) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Insert(item)
+	return
+}
+
+func GetReportCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM report WHERE classify_id_second=? `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+func GetClassifySubCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) as num FROM classify AS a
+        INNER JOIN report AS b ON a.id=b.classify_id_second
+        WHERE a.parent_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+func GetClassifySubCountByParentId(classifyId int) (count int, err error) {
+	sqlCount := `
+	SELECT COUNT(1) as num FROM classify AS a
+	WHERE a.parent_id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sqlCount, classifyId).QueryRow(&count)
+	return
+}
+
+// 删除分类
+func DeleteClassify(classifyId int) (err error) {
+	sql := `DELETE FROM classify WHERE id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, classifyId).Exec()
+	if err != nil {
+		return
+	}
+	deleteImgSql := `DELETE FROM banner WHERE classify_id=? `
+	_, err = o.Raw(deleteImgSql, classifyId).Exec()
+	return
+}
+
+// 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 = ? `
+	_, err = o.Raw(sql, req.ClassifyName, req.Abstract, req.ParentId, req.Descript, req.ReportAuthor, req.AuthorDescript, req.ColumnImgUrl, req.HeadImgUrl, req.AvatarImgUrl, req.ReportImgUrl, req.HomeImgUrl, req.ClassifyLabel, req.ClassifyId).Exec()
+	return
+}
+
+//获取父级分类
+
+func ParentClassify() (items []*Classify, err error) {
+	sql := `SELECT * FROM classify WHERE parent_id=0 order by id desc `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// 根据id获取分类详情
+func FindByIdClassify(classifyId int) (item *Classify, err error) {
+	sql := `SELECT * FROM classify WHERE id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+type ClassifyList struct {
+	Id            int       `orm:"column(id);pk"`
+	ClassifyName  string    `description:"分类名称"`
+	Sort          int       `json:"-"`
+	ParentId      int       `description:"父级分类id"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+	Abstract      string    `description:"简介"`
+	Descript      string    `description:"描述"`
+	ClassifyLabel string    `description:"分类标签"`
+	Child         []*Classify
+}
+
+type ClassifyListResp struct {
+	List   []*ClassifyList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// 获取分类列表
+func GetClassifyList(startSize, pageSize int, keyWord, companyType string) (items []*ClassifyList, err error) {
+	sql := ``
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND id != 40 AND parent_id != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
+	}
+	if keyWord != "" {
+		sql = `SELECT * FROM (
+                   SELECT * FROM classify
+                   WHERE parent_id=0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%'
+                   UNION
+                   SELECT * FROM classify
+                   WHERE id IN(SELECT parent_id FROM classify
+                   WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%')
+                   )AS t
+                   ORDER BY create_time ASC
+                   LIMIT ?,? `
+	} else {
+		sql = `SELECT * FROM classify WHERE parent_id=0 ` + companyTypeSqlStr + ` ORDER BY create_time ASC LIMIT ?,? `
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetClassifyListCount(keyWord, companyType string) (count int, err error) {
+	sqlCount := ``
+
+	companyTypeSqlStr := ``
+	if companyType == "ficc" {
+		companyTypeSqlStr = " AND id != 40 AND parent_id != 40 "
+	} else if companyType == "权益" {
+		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
+	}
+	if keyWord != "" {
+		sqlCount = `SELECT  COUNT(1) AS count FROM (
+               SELECT * FROM classify
+               WHERE parent_id=0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%'
+               UNION
+               SELECT * FROM classify
+               WHERE id IN(SELECT parent_id FROM classify
+               WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%')
+               )AS t `
+
+	} else {
+		sqlCount = `SELECT COUNT(1) AS count FROM classify WHERE parent_id=0 ` + companyTypeSqlStr
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	err = o.Raw(sqlCount).QueryRow(&count)
+	return
+}
+
+type CheckDeleteClassifyReq struct {
+	ClassifyId int `description:"分类ID"`
+}
+
+type CheckDeleteClassifyResp struct {
+	Code int    `description:"编码:0:检测成功,可进行删除,1:分类不存在,2:该分类有关联报告,不允许删除,3:二级分类有关联报告,不允许删除,4:该分类下有关联分类,是否确认全部删除"`
+	Msg  string `description:"描述信息"`
+}
+
+type DeleteClassifyReq struct {
+	ClassifyId int `description:"分类ID"`
+}
+
+type EditClassifyReq struct {
+	ClassifyId     int    `description:"分类ID"`
+	ClassifyName   string `description:"分类名称"`
+	ParentId       int    `description:"父级分类id"`
+	Abstract       string `description:"栏目简介"`
+	Descript       string `description:"分享描述"`
+	ReportAuthor   string `description:"栏目作者"`
+	AuthorDescript string `description:"作者简介"`
+	ColumnImgUrl   string `description:"栏目配图"`
+	HeadImgUrl     string `description:"头部banner"`
+	AvatarImgUrl   string `description:"头像"`
+	ReportImgUrl   string `description:"报告配图"`
+	HomeImgUrl     string `description:"首页配图"`
+	ClassifyLabel  string `description:"分类标签"`
+}
+
+type FindByIdClassifyReq struct {
+	ClassifyId int `description:"分类ID"`
+}
+
+func GetClassifyChild(parentId int, keyWord string) (items []*Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ``
+	if keyWord != "" {
+		sql = `SELECT * FROM classify WHERE parent_id=? AND classify_name LIKE '%` + keyWord + `%' ORDER BY create_time ASC `
+	} else {
+		sql = `SELECT * FROM classify WHERE parent_id=? ORDER BY create_time ASC `
+	}
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}

+ 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(startTime, endTime, afterDate string) (list []*EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE state = 1 and pre_publish_time >= ? and pre_publish_time <= ? and modify_time >= ?`
+	_, err = o.Raw(sql, startTime, endTime, afterDate).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
+}

+ 257 - 0
models/report.go

@@ -0,0 +1,257 @@
+package models
+
+import (
+	"eta/eta_task/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type Report 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         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        time.Time `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:"内容前两个章节"`
+	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 *ReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+func GetReport() (items []*Report, err error) {
+	sql := `SELECT * FROM report ORDER BY id ASC `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func ModifyReportContentSub(id int, contentSub string) (err error) {
+	sql := `UPDATE report SET content_sub=? WHERE id=? `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, contentSub, id).Exec()
+	return
+}
+
+func GetReportLimit() (items []*Report, err error) {
+	sql := `SELECT * FROM report WHERE state=2 ORDER BY id DESC LIMIT 50 `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func EditReportContent(reportId int, content, contentSub string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE report SET content=?,content_sub=?,modify_time=NOW() WHERE id=? `
+	_, err = o.Raw(sql, content, contentSub, reportId).Exec()
+	return
+}
+
+// 删除报告日志记录-保留3个月
+func DeleteReportSaveLog() {
+	startDateTime := time.Now().AddDate(0, -3, 0).Format(utils.FormatDateTime)
+	fmt.Println(startDateTime)
+}
+
+func EditReportContentHtml(reportId int, content string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE report SET content=?,modify_time=NOW() WHERE id=? `
+	_, err = o.Raw(sql, content, reportId).Exec()
+	return
+}
+
+// GetPrePublishedReports 获取定时发布时间为当前时间的未发布的报告列表
+func GetPrePublishedReports(startTime, endTime, afterDate string) (list []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE state = 1 and pre_publish_time >= ? and pre_publish_time <=? and modify_time >= ?`
+	_, err = o.Raw(sql, startTime, endTime, afterDate).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

@@ -34,3 +34,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:"大小"`
+}

+ 152 - 0
services/elastic.go

@@ -0,0 +1,152 @@
+package services
+
+import (
+	"context"
+	"eta/eta_task/models"
+	"eta/eta_task/services/alarm_msg"
+	"eta/eta_task/utils"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"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
+}

+ 87 - 0
services/english_report.go

@@ -0,0 +1,87 @@
+package services
+
+import (
+	"context"
+	"eta/eta_task/models"
+	"eta/eta_task/services/alarm_msg"
+	"eta/eta_task/utils"
+	"fmt"
+	"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.FormatDateTimeMinute)
+	startTime := now + ":00"
+	endTime := now + ":59"
+	afterDate := time.Now().AddDate(0, -1, 0).Format(utils.FormatDate) //限制一下,只查询最近一个月的
+	list, err := models.GetPrePublishedEnglishReports(startTime, endTime, afterDate)
+	if err != 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 {
+			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
+}

+ 95 - 0
services/oss.go

@@ -0,0 +1,95 @@
+package services
+
+import (
+	"errors"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+	"os"
+	"time"
+
+	"eta/eta_task/utils"
+)
+
+/*
+上传demo
+func init() {
+	fmt.Println("start")
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName :=  randStr + ".jpg"
+	fmt.Println("fileName:",fileName)
+	fpath:="./1.png"
+	resourceUrl,err:=UploadAliyun(fileName,fpath)
+	if err!=nil {
+		fmt.Println("UploadAliyun Err:",err.Error())
+		return
+	}
+	fmt.Println("resourceUrl:",resourceUrl)
+	fmt.Println("end")
+}
+*/
+
+// 图片上传到阿里云
+func UploadAliyun(filename, filepath string) (string, error) {
+	client, err := oss.New(utils.Endpoint, utils.AccessKeyId, utils.AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(utils.Bucketname)
+	if err != nil {
+		return "2", err
+	}
+	path := utils.UploadDir + time.Now().Format("200601/20060102/")
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = utils.Imghost + path
+	return path, err
+}
+
+// UploadAudioAliyun 音频上传到阿里云
+func UploadAudioAliyun(filename, filepath string) (string, error) {
+	if utils.AccessKeyId == `` {
+		return "0", errors.New("阿里云信息未配置")
+	}
+	client, err := oss.New(utils.Endpoint, utils.AccessKeyId, utils.AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(utils.Bucketname)
+	if err != nil {
+		return "2", err
+	}
+	path := utils.Upload_Audio_Dir + time.Now().Format("200601/20060102/")
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = utils.Imghost + path
+	return path, err
+}
+
+// 视频上传到阿里云
+func UploadVideoAliyun(filename, filepath, savePath string) error {
+	defer func() {
+		os.Remove(filepath)
+	}()
+	client, err := oss.New(utils.Endpoint, utils.AccessKeyId, utils.AccessKeySecret)
+	if err != nil {
+		return err
+	}
+	bucket, err := client.Bucket(utils.Bucketname)
+	if err != nil {
+		return err
+	}
+	//path := utils.Upload_Audio_Dir + time.Now().Format("200601/20060102/")
+	//path += filename
+	err = bucket.PutObjectFromFile(savePath, filepath)
+	if err != nil {
+		return err
+	}
+	//path = utils.Imghost + path
+	//return path,err
+	return err
+}

+ 369 - 0
services/report.go

@@ -0,0 +1,369 @@
+package services
+
+import (
+	"errors"
+	"eta/eta_task/models"
+	"eta/eta_task/services/alarm_msg"
+	"eta/eta_task/utils"
+	"fmt"
+	"golang.org/x/net/context"
+	"html"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// 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.FormatDateTimeMinute)
+	startTime := now + ":00"
+	endTime := now + ":59"
+	afterDate := time.Now().AddDate(0, -1, 0).Format(utils.FormatDate) //限制一下,只查询最近一个月的
+	list, err := models.GetPrePublishedReports(startTime, endTime, afterDate)
+	if err != 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 {
+			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
+}

+ 36 - 0
services/save_log.go

@@ -0,0 +1,36 @@
+package services
+
+import (
+	"context"
+	"eta/eta_task/models"
+	"eta/eta_task/models/data_manage"
+	"eta/eta_task/services/alarm_msg"
+	"eta/eta_task/utils"
+	"time"
+)
+
+func DeleteLog(cont context.Context) (err error) {
+	date := time.Now().AddDate(0, -1, 0).Format(utils.FormatDate)
+	go func() {
+		err = data_manage.DeletePPTLogByDate(date)
+		if err != nil {
+			alarm_msg.SendAlarmMsg("DeletePPTLogByDate ErrMsg:"+err.Error(), 3)
+		}
+	}()
+
+	go func() {
+		err = data_manage.DeleteReportLogByDate(date)
+		if err != nil {
+			alarm_msg.SendAlarmMsg("DeleteReportLogByDate ErrMsg:"+err.Error(), 3)
+		}
+	}()
+
+	go func() {
+		err = models.DeleteTemplateRecordByDate(date)
+		if err != nil {
+			alarm_msg.SendAlarmMsg("DeleteTemplateRecordByDate ErrMsg:"+err.Error(), 3)
+		}
+	}()
+
+	return
+}

+ 12 - 0
services/task.go

@@ -62,6 +62,18 @@ func releaseTask() {
 	//检测数据服务器
 	checkDataServer := task.NewTask("checkDataServer", "0 */2 * * * * ", checkDataServer)
 	task.AddTask("checkDataServer", checkDataServer)
+
+	//删除日志 report_save_log,ppt_v2_save_log,保留一个月的
+	deleteLog := task.NewTask("syncSubStatus", "0 0 2 2 * *", DeleteLog)
+	task.AddTask("deleteLog", deleteLog)
+
+	// 定时发布研报
+	publishReport := task.NewTask("publishReport", "0 */1 * * * *", PublishReport)
+	task.AddTask("定时发布研报", publishReport)
+
+	// 定时发布英文研报
+	publishEnglishReport := task.NewTask("publishEnglishReport", "0 */1 * * * *", PublishEnglishReport)
+	task.AddTask("定时发布英文研报", publishEnglishReport)
 }
 
 func RefreshData(cont context.Context) (err error) {

+ 318 - 0
services/video.go

@@ -0,0 +1,318 @@
+package services
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"eta/eta_task/models"
+	"eta/eta_task/services/alarm_msg"
+	"eta/eta_task/utils"
+	"fmt"
+	"github.com/PuerkitoBio/goquery"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
+	"html"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"time"
+	"unicode"
+)
+
+func CreateVideo(report *models.Report) (err error) {
+	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)
+		}
+	}()
+
+	// 获取基础配置, 若未配置则直接返回
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("获取基础配置失败, Err: " + e.Error())
+		return
+	}
+	if conf[models.BusinessConfUseXf] != "true" {
+		return
+	}
+	if conf[models.BusinessConfXfAppid] == "" || conf[models.BusinessConfXfApiKey] == "" || conf[models.BusinessConfXfApiSecret] == "" || conf[models.BusinessConfXfVcn] == "" {
+		return
+	}
+	var xfReq XfParams
+	xfReq.XfAPPID = conf[models.BusinessConfXfAppid]
+	xfReq.XfAPIKey = conf[models.BusinessConfXfApiKey]
+	xfReq.XfAPISecret = conf[models.BusinessConfXfApiSecret]
+
+	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 = conf[models.BusinessConfXfAppid]
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = conf[models.BusinessConfXfVcn]
+	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, xfReq)
+		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) {
+	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
+	}
+
+	// 获取基础配置, 若未配置则直接返回
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("获取基础配置失败, Err: " + e.Error())
+		return
+	}
+	if conf[models.BusinessConfUseXf] != "true" {
+		return
+	}
+	if conf[models.BusinessConfXfAppid] == "" || conf[models.BusinessConfXfApiKey] == "" || conf[models.BusinessConfXfApiSecret] == "" || conf[models.BusinessConfXfVcn] == "" {
+		return
+	}
+	var xfReq XfParams
+	xfReq.XfAPPID = conf[models.BusinessConfXfAppid]
+	xfReq.XfAPIKey = conf[models.BusinessConfXfApiKey]
+	xfReq.XfAPISecret = conf[models.BusinessConfXfApiSecret]
+
+	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 = conf[models.BusinessConfXfAppid]
+	param.Business.Aue = "lame"
+	param.Business.Sfl = 1
+	param.Business.Auf = "audio/L16;rate=16000"
+	param.Business.Vcn = conf[models.BusinessConfXfVcn]
+	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, xfReq)
+		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
+}

+ 114 - 0
services/xfyun.go

@@ -0,0 +1,114 @@
+package services
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"eta/eta_task/models"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/crypt"
+	"net/url"
+	"strings"
+	"time"
+
+	"eta/eta_task/utils"
+	"github.com/gorilla/websocket"
+)
+
+type XfParams struct {
+	XfAPPID     string
+	XfAPIKey    string
+	XfAPISecret string
+}
+
+// 科大讯飞,语音合成
+func GetXfVideo(body []byte, savePath string, req XfParams) (err error) {
+	path, err := assembleAuthUrl(req)
+	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(req XfParams) (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(req.XfAPISecret))
+	//构建请求参数 此时不需要urlencoding
+	authUrl := fmt.Sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", req.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
+}

+ 40 - 0
utils/common.go

@@ -15,6 +15,7 @@ import (
 	"math/rand"
 	"net"
 	"os"
+	"os/exec"
 	"regexp"
 	"strconv"
 	"strings"
@@ -872,3 +873,42 @@ func GetOrmInReplace(num int) string {
 	}
 	return strings.Join(template, ",")
 }
+
+// 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
+}

+ 62 - 0
utils/config.go

@@ -63,6 +63,38 @@ var (
 	PbServerUrl   string //彭博 api 接口服务器地址
 )
 
+// ES配置
+var (
+	ES_URL      string // ES服务器地址
+	ES_USERNAME string // ES账号
+	ES_PASSWORD string // ES密码
+)
+
+var (
+	EsReportIndexName        string //研报ES索引
+	EsEnglishReportIndexName string //英文研报ES索引
+)
+
+// 科大讯飞--语音合成
+var (
+	XfHostUrl string
+)
+
+var (
+	TemplateIdByProduct string //产品运行报告通知-模板ID
+)
+
+// 阿里云配置
+var (
+	Bucketname       string
+	Endpoint         string
+	Imghost          string
+	UploadDir        string
+	Upload_Audio_Dir string
+	AccessKeyId      string
+	AccessKeySecret  string
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -126,7 +158,37 @@ func init() {
 
 	// 商家编码
 	BusinessCode = config["business_code"]
+	//服务检测
 	WindServerUrl = config["wind_server_url"]
 	LtServerUrl = config["lt_server_url"]
 	PbServerUrl = config["pb_server_url"]
+
+	// 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"]
+	}
+
+	// 科大讯飞
+	{
+		XfHostUrl = config["xf_host_url"]
+	}
+
+	// OSS相关
+	{
+		Endpoint = config["endpoint"]
+		Bucketname = config["bucket_name"]
+		Imghost = config["img_host"]
+		UploadDir = config["upload_dir"]
+		Upload_Audio_Dir = config["upload_audio_dir"]
+		AccessKeyId = config["access_key_id"]
+		AccessKeySecret = config["access_key_secret"]
+	}
 }

+ 15 - 0
utils/constants.go

@@ -7,6 +7,7 @@ const (
 	FormatDateUnSpace      = "20060102"                //日期格式
 	FormatDateTime         = "2006-01-02 15:04:05"     //完整时间格式
 	HlbFormatDateTime      = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeMinute   = "2006-01-02 15:04"        //时间格式只精确到分钟
 	FormatDateTimeUnSpace  = "20060102150405"          //完整时间格式
 	FormatMonthDateUnSpace = "200601"                  //年月格式
 	PageSize15             = 15                        //列表页每页数据量
@@ -100,3 +101,17 @@ const (
 const (
 	TEMPLATE_MSG_YB_VOICE_BROADCAST = 20 //研报语音播报
 )
+
+// 研报类型标识
+var (
+	REPORT_TYPE_DAY      = "day"
+	REPORT_TYPE_WEEK     = "week"
+	REPORT_TYPE_TWO_WEEK = "two_week"
+	REPORT_TYPE_MONTH    = "month"
+	REPORT_TYPE_OTHER    = "other"
+)
+
+// 模板消息推送类型
+const (
+	TEMPLATE_MSG_REPORT = iota + 1 //日度点评报告推送
+)