Browse Source

拆分eta系统

# Conflicts:
#	services/task.go
#	services/wx_template_msg.go
tuoling805 1 year ago
parent
commit
ae7d7419ff
57 changed files with 5470 additions and 337 deletions
  1. 36 6
      go.mod
  2. 14 3
      go.sum
  3. 1 1
      main.go
  4. 167 0
      models/business_conf.go
  5. 7 6
      models/classify.go
  6. 51 31
      models/company/company.go
  7. 4 1
      models/company_contract/company_contract.go
  8. 13 0
      models/company_no_renewed_ascribe.go
  9. 2 2
      models/company_product.go
  10. 12 9
      models/company_product_update_log.go
  11. 2 1
      models/company_report_permission.go
  12. 71 0
      models/data_manage/activity_signup.go
  13. 185 0
      models/data_manage/activity_special_trip_bill.go
  14. 61 0
      models/data_manage/company_user_type.go
  15. 45 0
      models/data_manage/cygx_activity_special_points_company.go
  16. 208 12
      models/data_manage/trade_position_analysis.go
  17. 45 2
      models/db.go
  18. 128 0
      models/english_report.go
  19. 63 0
      models/english_report_email.go
  20. 327 0
      models/eta_business/eta_business.go
  21. 121 0
      models/eta_business/eta_business_contract.go
  22. 38 0
      models/eta_business/eta_business_remind_record.go
  23. 3 3
      models/eta_trial.go
  24. 177 3
      models/report.go
  25. 23 1
      models/report_view.go
  26. 29 0
      models/wx_template_msg.go
  27. 60 0
      models/xfyun.go
  28. 2 2
      services/activity.go
  29. 11 11
      services/alarm_msg/alarm_msg.go
  30. 20 0
      services/company/company_ascribe.go
  31. 20 3
      services/company_contract/company_contract.go
  32. 92 0
      services/company_product.go
  33. 2 1
      services/company_report_permission.go
  34. 193 0
      services/cygx/activity_points_set.go
  35. 311 0
      services/cygx/activity_special.go
  36. 40 0
      services/cygx/company_permission.go
  37. 28 7
      services/cygx_research_summary.go
  38. 24 20
      services/data/edb_info_notice.go
  39. 274 57
      services/data/trade_position_analysis.go
  40. 592 0
      services/data/trade_position_analysis_classify.go
  41. 151 0
      services/elastic.go
  42. 87 0
      services/english_report.go
  43. 40 0
      services/english_report_email.go
  44. 255 0
      services/eta_business.go
  45. 2 4
      services/eta_trial.go
  46. 24 18
      services/maycur/maycur.go
  47. 20 17
      services/oss.go
  48. 362 3
      services/report.go
  49. 64 3
      services/report_view.go
  50. 99 48
      services/task.go
  51. 318 0
      services/video.go
  52. 63 12
      services/wx_template_msg.go
  53. 114 0
      services/xfyun.go
  54. 60 20
      utils/common.go
  55. 100 14
      utils/config.go
  56. 22 16
      utils/constants.go
  57. 187 0
      utils/des3.go

+ 36 - 6
go.mod

@@ -1,21 +1,51 @@
 module hongze/hongze_task
 
-go 1.16
+go 1.18
 
 require (
 	github.com/PuerkitoBio/goquery v1.8.0
 	github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible
-	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 // indirect
-	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
 	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/satori/go.uuid v1.2.0 // indirect
+	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/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
+	golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
 	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/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
+	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
+	github.com/prometheus/client_golang v1.11.0 // indirect
+	github.com/prometheus/client_model v0.2.0 // indirect
+	github.com/prometheus/common v0.26.0 // indirect
+	github.com/prometheus/procfs v0.6.0 // indirect
+	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/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
+	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
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+)

+ 14 - 3
go.sum

@@ -99,9 +99,11 @@ 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=
+github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
 github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
@@ -155,8 +157,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 +168,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 +202,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 +213,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 +233,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 +272,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 +338,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=

+ 1 - 1
main.go

@@ -19,7 +19,7 @@ func main() {
 		web.BConfig.WebConfig.DirectoryIndex = true
 		web.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 	}
-	go services.Task()
+	go services.InitTask()
 	//services.TaskTest()
 	web.BConfig.RecoverFunc = Recover
 	web.Run()

+ 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
+}

+ 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 := ``

+ 51 - 31
models/company/company.go

@@ -9,10 +9,10 @@ import (
 )
 
 // TryOutToFormal 试用转正式
-func TryOutToFormal(companyId, productId, sellerId, companyContractId int, startDate, endDate, sellerName, productName string, packageType int) (err error) {
+func TryOutToFormal(companyId, productId, sellerId, companyContractId int, startDate, endDate, sellerName, productName string, packageType, raiPackageType int) (err error) {
 	o := orm.NewOrm()
-	tx,err:=o.Begin()
-	if err!=nil {
+	tx, err := o.Begin()
+	if err != nil {
 		return err
 	}
 	defer func() {
@@ -22,11 +22,22 @@ func TryOutToFormal(companyId, productId, sellerId, companyContractId int, start
 			tx.Commit()
 		}
 	}()
-	sql := `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,renewal_intention=0,is_suspend=0,is_formal=1,start_date=?,end_date=?,package_type=?,modify_time=NOW(),formal_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
-	_, err = tx.Raw(sql, startDate, endDate, packageType, companyId, productId).Exec()
-	if err != nil {
-		return
+
+	// 套餐类型为0时, 不更新套餐类型
+	sql := `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,renewal_intention=0,is_suspend=0,is_formal=1,start_date=?,end_date=?,package_type=?,rai_package_type=?,modify_time=NOW(),formal_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
+	if packageType == 0 {
+		sql = `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,renewal_intention=0,is_suspend=0,is_formal=1,start_date=?,end_date=?,rai_package_type=?,modify_time=NOW(),formal_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
+		_, err = tx.Raw(sql, startDate, endDate, raiPackageType, companyId, productId).Exec()
+		if err != nil {
+			return
+		}
+	} else {
+		_, err = tx.Raw(sql, startDate, endDate, packageType, raiPackageType, companyId, productId).Exec()
+		if err != nil {
+			return
+		}
 	}
+
 	sql = `UPDATE company SET type=1,last_updated_time=NOW() WHERE company_id=? `
 	_, err = tx.Raw(sql, companyId).Exec()
 	if err != nil {
@@ -63,9 +74,9 @@ func TryOutToFormal(companyId, productId, sellerId, companyContractId int, start
 	}
 	for _, pv := range contractPermission {
 		sql = `INSERT INTO company_report_permission(company_id, report_permission_id,created_time, last_updated_time,
-             chart_permission_id, start_date,end_date,product_id,product_name, modify_time,company_contract_id,status,is_upgrade) 
-			VALUES(?,?,NOW(),NOW(),?,?,?,?,?,NOW(),?,?,?) `
-		_, err = tx.Raw(sql, companyId, pv.ChartPermissionId, pv.ChartPermissionId, pv.StartDate, pv.EndDate, productId, productName, companyContractId, "正式",pv.IsUpgrade).Exec()
+             chart_permission_id, start_date,end_date,product_id,product_name, modify_time,company_contract_id,status,is_upgrade,expensive_yx) 
+			VALUES(?,?,NOW(),NOW(),?,?,?,?,?,NOW(),?,?,?,?) `
+		_, err = tx.Raw(sql, companyId, pv.ChartPermissionId, pv.ChartPermissionId, pv.StartDate, pv.EndDate, productId, productName, companyContractId, "正式", pv.IsUpgrade, pv.ExpensiveYx).Exec()
 		if err != nil {
 			return
 		}
@@ -73,11 +84,11 @@ func TryOutToFormal(companyId, productId, sellerId, companyContractId int, start
 	return
 }
 
-//服务更新
+// 服务更新
 func ApplyServiceUpdateOld(companyId, productId, sellerId, companyContractId int, startDate, endDate, sellerName, productName string) (err error) {
 	o := orm.NewOrm()
-	tx,err:=o.Begin()
-	if err!=nil {
+	tx, err := o.Begin()
+	if err != nil {
 		return err
 	}
 	defer func() {
@@ -138,10 +149,10 @@ func ApplyServiceUpdateOld(companyId, productId, sellerId, companyContractId int
 	return
 }
 
-func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, startDate, endDate, sellerName, productName string, packageType int) (err error) {
+func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, startDate, endDate, sellerName, productName string, packageType, raiPackageType int) (err error) {
 	o := orm.NewOrm()
-	tx,err:=o.Begin()
-	if err!=nil {
+	tx, err := o.Begin()
+	if err != nil {
 		return err
 	}
 	defer func() {
@@ -165,12 +176,12 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 
 	//产品服务的开始、结束日期(非产品权限)
 	updateStartDate := startDate
-	updateStartDateTime, err := time.Parse(utils.FormatDate, updateStartDate)
+	updateStartDateTime, err := time.ParseInLocation(utils.FormatDate, updateStartDate, time.Local)
 	if err != nil {
 		return
 	}
 	updateEndDate := endDate
-	updateEndDateTime, err := time.Parse(utils.FormatDate, updateEndDate)
+	updateEndDateTime, err := time.ParseInLocation(utils.FormatDate, updateEndDate, time.Local)
 	if err != nil {
 		return
 	}
@@ -192,7 +203,7 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 		nowCompanyReportPermissionMap[pv.ChartPermissionId] = pv
 
 		//校验原始数据中的开始日期是否小于合同内的开始日期,如果小于,那么变更为原先的合同开始日期
-		tmpStartDate, tmpErr := time.Parse(utils.FormatDate, pv.StartDate)
+		tmpStartDate, tmpErr := time.ParseInLocation(utils.FormatDate, pv.StartDate, time.Local)
 		if tmpErr != nil {
 			err = tmpErr
 			return
@@ -202,7 +213,7 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 		}
 
 		//校验原始数据中的结束日期是否大于合同内的结束日期,如果大于,那么变更为原先的合同结束日期
-		tmpEndDate, tmpErr := time.Parse(utils.FormatDate, pv.EndDate)
+		tmpEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, pv.EndDate, time.Local)
 		if tmpErr != nil {
 			err = tmpErr
 			return
@@ -219,10 +230,19 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 	//}
 
 	//更新客户产品信息
-	sql = `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,is_formal=1,is_suspend=0,start_date=?,end_date=?,package_type=?,modify_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
-	_, err = tx.Raw(sql, updateStartDateTime, updateEndDateTime, packageType, companyId, productId).Exec()
-	if err != nil {
-		return
+	if packageType == 0 {
+		// 套餐类型为0时, 不更新套餐类型
+		sql = `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,is_formal=1,is_suspend=0,start_date=?,end_date=?,rai_package_type=?,modify_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
+		_, err = tx.Raw(sql, updateStartDateTime, updateEndDateTime, raiPackageType, companyId, productId).Exec()
+		if err != nil {
+			return
+		}
+	} else {
+		sql = `UPDATE company_product SET status='正式',try_out_time=NULL,last_description_time=NULL,freeze_time=NULL,is_formal=1,is_suspend=0,start_date=?,end_date=?,package_type=?,rai_package_type=?,modify_time=NOW(),try_stage=1 WHERE company_id=? AND product_id=? `
+		_, err = tx.Raw(sql, updateStartDateTime, updateEndDateTime, packageType, raiPackageType, companyId, productId).Exec()
+		if err != nil {
+			return
+		}
 	}
 
 	contractPermission := make([]*company_contract.CompanyContractPermission, 0)
@@ -244,12 +264,12 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 			//如果 需要更新 字段 为false,那么再去校验时间
 			if needUpdate == false {
 				//如果当前存该权限,那么去校验是否需要修改
-				nowPermissionEndDateTime, tmpErr := time.Parse(utils.FormatDate, nowPermission.EndDate)
+				nowPermissionEndDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, nowPermission.EndDate, time.Local)
 				if tmpErr != nil {
 					err = tmpErr
 					return
 				}
-				contractPermissionEndDateTime, tmpErr := time.Parse(utils.FormatDate, pv.EndDate)
+				contractPermissionEndDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, pv.EndDate, time.Local)
 				if tmpErr != nil {
 					err = tmpErr
 					return
@@ -261,8 +281,8 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 
 			//如果 需要更新 字段 为 true,那么就去更新
 			if needUpdate {
-				sql = `update company_report_permission set end_date=?,last_updated_time=now(),modify_time=now(),company_contract_id=?,status=? where company_report_permission_id=? `
-				_, tmpErr := tx.Raw(sql, pv.EndDate, companyContractId, "正式", nowPermission.CompanyReportPermissionId).Exec()
+				sql = `update company_report_permission set end_date=?,last_updated_time=now(),modify_time=now(),company_contract_id=?,status=?,is_upgrade=?,expensive_yx=? where company_report_permission_id=? `
+				_, tmpErr := tx.Raw(sql, pv.EndDate, companyContractId, "正式", pv.IsUpgrade, pv.ExpensiveYx, nowPermission.CompanyReportPermissionId).Exec()
 				err = tmpErr
 				if err != nil {
 					return
@@ -271,9 +291,9 @@ func ApplyServiceUpdate(companyId, productId, sellerId, companyContractId int, s
 
 		} else {
 			sql = `INSERT INTO company_report_permission(company_id, report_permission_id,created_time, last_updated_time,
-             chart_permission_id, start_date,end_date,product_id,product_name, modify_time,company_contract_id,status) 
-			VALUES(?,?,NOW(),NOW(),?,?,?,?,?,NOW(),?,?) `
-			_, tmpErr := tx.Raw(sql, companyId, pv.ChartPermissionId, pv.ChartPermissionId, pv.StartDate, pv.EndDate, productId, productName, companyContractId, "正式").Exec()
+             chart_permission_id, start_date,end_date,product_id,product_name, modify_time,company_contract_id,status,is_upgrade,expensive_yx) 
+			VALUES(?,?,NOW(),NOW(),?,?,?,?,?,NOW(),?,?,?,?) `
+			_, tmpErr := tx.Raw(sql, companyId, pv.ChartPermissionId, pv.ChartPermissionId, pv.StartDate, pv.EndDate, productId, productName, companyContractId, "正式", pv.IsUpgrade, pv.ExpensiveYx).Exec()
 			err = tmpErr
 			if err != nil {
 				return

+ 4 - 1
models/company_contract/company_contract.go

@@ -23,9 +23,10 @@ type CompanyContract struct {
 	ModifyTime        time.Time `description:"合同修改时间"`
 	Status            int       `description:"状态"`
 	PackageType       int       `description:"套餐类型,0:无,1:大套餐,2:小套餐"`
+	RaiPackageType    int       `description:"权益套餐类型: 0-无; 1-70w大套餐; 2-45w大套餐"`
 }
 
-//通过开始日期获取当天开始的合同列表
+// 通过开始日期获取当天开始的合同列表
 func GetStartContractListByStartDate(startDate string) (total int64, list []*CompanyContract, err error) {
 	o := orm.NewOrm()
 	sql := "SELECT * FROM company_contract where start_date = ? AND status = 1 "
@@ -39,6 +40,8 @@ type CompanyContractPermission struct {
 	CompanyId         int       `description:"客户id"`
 	CompanyContractId int       `description:"合同id"`
 	ChartPermissionId int       `description:"权限id"`
+	IsUpgrade         int       `description:"是否升级,1是,0否"`
+	ExpensiveYx       int       `description:"权益研选: 0-3w; 1-5w"`
 	StartDate         string    `description:"开始日期"`
 	EndDate           string    `description:"结束日期"`
 	CreateTime        time.Time `description:"创建时间"`

+ 13 - 0
models/company_no_renewed_ascribe.go

@@ -0,0 +1,13 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// 删除数据
+func DeleteCompanNoRenewedAscribe(companyId , productId int) (err error) {
+	o := orm.NewOrm()
+	sql := ` DELETE FROM company_no_renewed_ascribe WHERE company_id = ? AND product_id = ? `
+	_, err = o.Raw(sql, companyId,productId).Exec()
+	return
+}

+ 2 - 2
models/company_product.go

@@ -250,11 +250,11 @@ func GetRemindCompany(sellerId int, endDate string) (items []*CompanyProduct, er
 	company a
 	INNER JOIN company_product AS b ON a.company_id = b.company_id 
 WHERE
-	b.seller_id = ? 
+	(b.seller_id = ? OR a.share_seller_id =?)
 	AND (
 		( b.contract_end_date = ? AND b.status = "正式" )  OR (b.end_date = ? AND b.status = "试用")
   ) ORDER BY b.status DESC `
-	_, err = o.Raw(sql, sellerId, endDate, endDate).QueryRows(&items)
+	_, err = o.Raw(sql, sellerId, sellerId, endDate, endDate).QueryRows(&items)
 	return
 }
 

+ 12 - 9
models/company_product_update_log.go

@@ -7,15 +7,18 @@ import (
 
 // CompanyProductUpdateLog 客户产品变更日志表
 type CompanyProductUpdateLog struct {
-	Id         int       `orm:"column(id);pk"`
-	CompanyId  int       `description:"客户id"`
-	ProductId  int       `description:"产品id"`
-	Status     string    `description:"变更后的状态"`
-	SellerId   int       `description:"销售id"`
-	SellerName string    `description:"销售名称"`
-	IsFormal   int       `description:"是否已经转正式,0是没有转正式,1是已经转过正式"`
-	Source     string    `description:"来源"`
-	CreateTime time.Time `description:"创建时间"`
+	Id          int       `orm:"column(id);pk"`
+	CompanyId   int       `description:"客户id"`
+	ProductId   int       `description:"产品id"`
+	Status      string    `description:"变更后的状态"`
+	SellerId    int       `description:"销售id"`
+	SellerName  string    `description:"销售名称"`
+	IsFormal    int       `description:"是否已经转正式,0是没有转正式,1是已经转过正式"`
+	Source      string    `description:"来源"`
+	StartDate   time.Time `description:"开始日期"`
+	EndDate     time.Time `description:"结束日期"`
+	RealEndDate time.Time `description:"实际结束日期"`
+	CreateTime  time.Time `description:"创建时间"`
 }
 
 // AddCompanyProductUpdateLog 新增客户产品变更日志

+ 2 - 1
models/company_report_permission.go

@@ -20,6 +20,7 @@ type CompanyReportPermission struct {
 	EndDate                   string    `description:"合同结束日期"`
 	ModifyTime                string    `description:"更新时间"`
 	IsUpgrade                 int       `description:"是否升级,1是,0否"`
+	ExpensiveYx               int       `description:"权益研选: 0-3w; 1-5w"`
 }
 
 // Update 更新客户产品权限
@@ -96,7 +97,7 @@ func CompanyReportPermissionClose(companyReportPermissionId int64, companyId, pr
 	return
 }
 
-//查询是否存在还未过期的产品权限
+// 查询是否存在还未过期的产品权限
 func GetCompanyReportPermissionCount(companyId, productId int) (count int, err error) {
 	o := orm.NewOrm()
 	startDate := time.Now().Format(utils.FormatDate)

+ 71 - 0
models/data_manage/activity_signup.go

@@ -0,0 +1,71 @@
+package data_manage
+
+import "github.com/beego/beego/v2/client/orm"
+
+type CompanyDetail struct {
+	CompanyId   int    `orm:"column(company_id);pk"`
+	CompanyName string `description:"客户名称"`
+	Status      string `description:"客户状态"`
+	SellerId    int    `description:"销售id"`
+	SellerName  string `description:"销售名称"`
+	Mobile      string `description:"销售手机号"`
+	ProductId   int    `description:"1,FICC,2权益"`
+}
+
+func GetCompanyDetailByIdGroup(companyId int) (item *CompanyDetail, err error) {
+	sql := ` SELECT a.company_id,a.company_name,b.status,b.seller_id,b.seller_name,c.mobile
+            FROM company AS a
+			INNER JOIN company_product AS b ON a.company_id=b.company_id
+			INNER JOIN company_report_permission AS p ON p.company_id = a.company_id
+			INNER JOIN chart_permission AS cp ON cp.chart_permission_id = p.chart_permission_id
+			LEFT JOIN admin AS c ON b.seller_id=c.admin_id
+			WHERE a.company_id=? AND b.product_id = 2
+			OR (a.company_id = ? AND  cp.permission_name = '策略' )
+			ORDER BY b.product_id DESC  LIMIT 0,1 `
+	o := orm.NewOrm()
+	err = o.Raw(sql, companyId, companyId).QueryRow(&item)
+	return
+}
+func GetCountCompanyDetailByIdGroup(companyId int) (count int, err error) {
+	sql := ` SELECT COUNT(1) AS count
+            FROM company AS a
+			INNER JOIN company_product AS b ON a.company_id=b.company_id
+			INNER JOIN company_report_permission AS p ON p.company_id = a.company_id
+			INNER JOIN chart_permission AS cp ON cp.chart_permission_id = p.chart_permission_id
+			LEFT JOIN admin AS c ON b.seller_id=c.admin_id
+			WHERE a.company_id=? AND b.product_id = 2    OR (a.company_id = ? AND  cp.permission_name = '策略' )   `
+	o := orm.NewOrm()
+	err = o.Raw(sql, companyId, companyId).QueryRow(&count)
+	return
+}
+
+func GetCompanyPermission(companyId int) (permission string, err error) {
+	sql := ` SELECT GROUP_CONCAT(DISTINCT b.remark  ORDER BY b.sort ASC SEPARATOR ',') AS permission
+			FROM company_report_permission AS a
+			INNER JOIN chart_permission AS b ON a.chart_permission_id=b.chart_permission_id
+			INNER JOIN company_product AS c ON a.company_id=c.company_id AND a.product_id=c.product_id
+			WHERE  a.company_id=?
+			AND c.is_suspend=0
+            AND b.cygx_auth=1 
+			AND c.status IN('正式','试用','永续')
+			AND a.status IN('正式','试用','永续') `
+	o := orm.NewOrm()
+	err = o.Raw(sql, companyId).QueryRow(&permission)
+	return
+}
+
+// 获取正式权限
+func GetCompanyPermissionByUserZhengShi(companyId int) (permission string, err error) {
+	sql := ` SELECT GROUP_CONCAT(DISTINCT b.remark  ORDER BY b.sort ASC  SEPARATOR ',') AS permission
+			FROM company_report_permission AS a
+			INNER JOIN chart_permission AS b ON a.chart_permission_id=b.chart_permission_id
+			INNER JOIN company_product AS c ON a.company_id=c.company_id AND a.product_id=c.product_id
+			WHERE  a.company_id=?
+			AND c.is_suspend=0
+            AND b.cygx_auth=1
+			AND c.status IN('正式')
+			AND a.status IN('正式') `
+	o := orm.NewOrm()
+	err = o.Raw(sql, companyId).QueryRow(&permission)
+	return
+}

+ 185 - 0
models/data_manage/activity_special_trip_bill.go

@@ -0,0 +1,185 @@
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CygxActivitySpecialTripBill struct {
+	Id                int       `orm:"column(id);pk"`
+	UserId            int       `description:"用户id,多个用,隔开"`
+	ActivityId        int       `description:"活动ID"`
+	CreateTime        time.Time `description:"创建时间"`
+	Mobile            string    `description:"手机号"`
+	Email             string    `description:"邮箱号"`
+	CompanyId         int       `description:"公司ID"`
+	CompanyName       string    `description:"公司名称"`
+	RealName          string    `description:"用户实际名称"`
+	AdminId           int       `description:"销售/管理员ID"`
+	Source            int       `description:"来源,1小程序,2后台添加, 3开发人员手动添加"`
+	BillDetailed      int       `description:"流水明细,判断是进账还是出账"`
+	DoType            int       `description:"操作方式,1报名,2取消报名"`
+	RegisterPlatform  int       `description:"来源 1小程序,2:网页"`
+	ChartPermissionId int       `description:"行业id"`
+	Way               int       `description:"1报名,取消报名。2到会取消到会 3转正或清零 4取消活动"`
+	Content           string    `description:"内容"`
+	Total             string    `description:"总和"`
+}
+
+type CygxActivitySpecialTripBillList struct {
+	Id                  int       `orm:"column(id);pk"`
+	UserId              int       `description:"用户id,多个用,隔开"`
+	ActivityId          int       `description:"活动ID"`
+	CreateTime          time.Time `description:"创建时间"`
+	Mobile              string    `description:"手机号"`
+	Email               string    `description:"邮箱号"`
+	CompanyId           int       `description:"公司ID"`
+	CompanyName         string    `description:"公司名称"`
+	RealName            string    `description:"用户实际名称"`
+	AdminId             int       `description:"销售/管理员ID"`
+	Source              int       `description:"来源,1小程序,2后台添加, 3开发人员手动添加"`
+	BillDetailed        int       `description:"流水明细,判断是进账还是出账"`
+	DoType              int       `description:"操作方式,1报名,2取消报名"`
+	RegisterPlatform    int       `description:"来源 1小程序,2:网页"`
+	ChartPermissionId   int       `description:"行业id"`
+	ChartPermissionName string    `description:"行业名称"`
+}
+
+// 添加
+func AddCygxActivitySpecialTripBill(item *CygxActivitySpecialTripBill) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(item)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func GetCygxActivitySpecialTripBill(condition string, pars []interface{}) (item []*CygxActivitySpecialTripBill, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT *
+			FROM
+			cygx_activity_special_trip_bill  
+			WHERE 1 = 1 ` + condition
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}
+
+func GetCygxActivitySpecialTripBillList(condition string, pars []interface{}) (item []*CygxActivitySpecialTripBillList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+			b.*,
+			c.chart_permission_name 
+		FROM
+			cygx_activity_special_trip_bill AS b
+			LEFT JOIN chart_permission AS c ON c.chart_permission_id = b.chart_permission_id 
+		WHERE
+			1 = 1` + condition
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}
+
+type AirborneCount struct {
+	Count             int
+	ChartPermissionId int ` description:"品种权限ID"`
+}
+
+// 获取空降的公司报名的记录
+func GetActivitySpecialTripAirborneListByActivitySpecial(condition string, pars []interface{}) (items []*AirborneCount, err error) {
+	sqlCount := ` SELECT chart_permission_id,COUNT(1) AS count
+		FROM
+			cygx_activity_special_meeting_detail AS t
+			INNER JOIN cygx_activity_special AS a ON a.activity_id = t.activity_id 
+		WHERE
+			 1= 1  	AND YEAR ( t.create_time )= YEAR (NOW()) ` + condition + `GROUP BY chart_permission_id`
+	o := orm.NewOrm()
+	_, err = o.Raw(sqlCount, pars).QueryRows(&items)
+	return
+}
+
+// 获取空降的公司报名的记录
+func GetActivitySpecialTripAirborneCountByActivitySpecial(condition string, pars []interface{}) (count int, err error) {
+	sqlCount := ` SELECT COUNT(1) AS count
+		FROM
+			cygx_activity_special_meeting_detail AS t
+			INNER JOIN cygx_activity_special AS a ON a.activity_id = t.activity_id 
+		WHERE
+			 1= 1  	AND YEAR ( t.create_time )= YEAR (NOW()) ` + condition
+	o := orm.NewOrm()
+	err = o.Raw(sqlCount, pars).QueryRow(&count)
+	return
+}
+
+type CygxActivitySpecialTripBillDetailList struct {
+	Id                  int       `orm:"column(id);pk"`
+	UserId              int       `description:"用户id,多个用,隔开"`
+	ActivityId          int       `description:"活动ID"`
+	ResearchTheme       string    `description:"调研主题"`
+	CreateTime          time.Time `description:"创建时间"`
+	Mobile              string    `description:"手机号"`
+	Email               string    `description:"邮箱号"`
+	CompanyId           int       `description:"公司ID"`
+	CompanyName         string    `description:"公司名称"`
+	RealName            string    `description:"用户实际名称"`
+	AdminId             int       `description:"销售/管理员ID"`
+	Source              int       `description:"来源,1小程序,2后台添加, 3开发人员手动添加"`
+	BillDetailed        int       `description:"流水明细,判断是进账还是出账"`
+	DoType              int       `description:"操作方式,1报名,2取消报名"`
+	RegisterPlatform    int       `description:"来源 1小程序,2:网页"`
+	ChartPermissionId   int       `description:"行业id"`
+	ChartPermissionName string    `description:"行业名称"`
+	Content             string    `description:"内容"`
+	Way                 int       `description:"1报名,取消报名。2到会取消到会 3转正或清零 4取消活动"`
+}
+
+func GetCygxActivitySpecialTripBillDetailList(condition string, pars []interface{}) (item []*CygxActivitySpecialTripBillDetailList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+			b.*,
+			a.research_theme,
+			c.chart_permission_name 
+		FROM
+			cygx_activity_special_trip_bill AS b
+			INNER JOIN chart_permission AS c ON c.chart_permission_id = b.chart_permission_id 
+			INNER JOIN cygx_activity_special AS a ON a.activity_id = b.activity_id
+		WHERE
+			1 = 1` + condition
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}
+
+type CygxActivitySpecialPointsBillRespItem struct {
+	Id                  int    `gorm:"column:id;primary_key;AUTO_INCREMENT"`
+	Content             string `gorm:"column:content" `                                 // 内容说明
+	Total               string `gorm:"column:points;default:0;NOT NULL" `               // 合计
+	CreateTime          string `gorm:"column:create_time" `                             // 创建时间
+	CompanyId           int    `gorm:"column:company_id;default:0" `                    // 公司ID
+	CompanyName         string `gorm:"column:company_name" `                            // 公司名称
+	RealName            string `gorm:"column:real_name"`                                // 用户实际名称
+	BillDetailed        int    `gorm:"column:bill_detailed;default:0;NOT NULL" json:""` // 流水明细,判断是进账还是出账
+	ActivityName        string `description:"活动标题"`
+	ChartPermissionId   int    `description:"行业id"`
+	ChartPermissionName string `description:"行业名称"`
+}
+
+type CygxActivitySpecialPointsBillResp struct {
+	List []*CygxActivitySpecialPointsBillRespItem
+}
+
+func GetCygxActivitySpecialTripBillDetailListAll(condition string, pars []interface{}) (item []*CygxActivitySpecialTripBillDetailList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+			b.*,
+			a.research_theme,
+			c.chart_permission_name 
+		FROM
+			cygx_activity_special_trip_bill AS b
+			LEFT JOIN chart_permission AS c ON c.chart_permission_id = b.chart_permission_id 
+			LEFT JOIN cygx_activity_special AS a ON a.activity_id = b.activity_id
+		WHERE
+			1 = 1` + condition
+
+	sql += ` ORDER BY b.create_time DESC`
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}

+ 61 - 0
models/data_manage/company_user_type.go

@@ -0,0 +1,61 @@
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CygxCompanyUserType struct {
+	Id             int `orm:"column(id);pk" description:"分析师id"`
+	CompanyId      int
+	CustomerTypeId int       `description:"用户身份ID"`
+	PackageType    int       `description:"1:70万 2:45w"`
+	CreateTime     time.Time `description:"创建时间"`
+	ModifyTime     time.Time `description:"创建时间"`
+}
+
+// 新增权益客户身份类型
+func AddCygxCompanyUserType(item *CygxCompanyUserType) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(item)
+	return
+}
+
+func GetCygxCompanyUserTypeByCompanyId(companyId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM cygx_company_user_type AS a WHERE a.company_id=?  `
+	err = o.Raw(sql, companyId).QueryRow(&count)
+	return
+}
+
+// CygxCompanyUserType 更新权益客户身份类型
+func UpdateCygxCompanyUserType(companyId, useType int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE cygx_company_user_type SET customer_type_id=? WHERE company_id=? `
+	_, err = o.Raw(sql, useType, companyId).Exec()
+	return
+}
+
+// 通过活动ID获取详情
+func GetCygxCompanyUserType(companyId int) (item *CygxCompanyUserType, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT *  FROM cygx_company_user_type AS a WHERE a.company_id=?  `
+	err = o.Raw(sql, companyId).QueryRow(&item)
+	return
+}
+
+// 获取大套餐客户列表
+func GetCygxCompanyUserTypeList() (items []*CygxCompanyUserType, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT *  FROM cygx_company_user_type AS a WHERE a.customer_type_id=2  `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// CygxCompanyUserType 更新权益客户身份类型
+func UpdateCygxCompanyUserTypeAndPackageType(companyId, useType, packageType int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE cygx_company_user_type SET customer_type_id=?, package_type = ? WHERE company_id=? `
+	_, err = o.Raw(sql, useType, packageType, companyId).Exec()
+	return
+}

+ 45 - 0
models/data_manage/cygx_activity_special_points_company.go

@@ -0,0 +1,45 @@
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CygxActivitySpecialInheritPointsCompany struct {
+	Id                  int       `orm:"column(id);pk"`
+	CompanyId           int       // 公司ID
+	CompanyName         string    // 公司名称
+	Points              int   // 公司剩余点数
+	CreateTime          time.Time // 创建时间
+	ModifyTime          time.Time // 更新时间
+	ChartPermissionId   int       // 品种ID
+	ChartPermissionName string    // 品种名称
+}
+
+func AddCygxActivitySpecialPointsCompany(item *CygxActivitySpecialInheritPointsCompany) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(item)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func AddCygxActivitySpecialInheritPointsCompanyMulti(items []*CygxActivitySpecialInheritPointsCompany) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(1, items)
+	return
+}
+
+func GetCygxActivitySpecialInheritPointsByCompanyId(companyId int) (list []*CygxActivitySpecialInheritPointsCompany, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM cygx_activity_special_inherit_points_company WHERE company_id = ?  `
+	_, err = o.Raw(sql,companyId).QueryRows(&list)
+	return
+}
+func DelCygxActivitySpecialInheritPointsByCompanyId(companyId int) (err error) {
+	o := orm.NewOrm()
+	sql := ` DELETE FROM cygx_activity_special_inherit_points_company WHERE company_id = ?  `
+	_, err = o.Raw(sql,companyId).Exec()
+	return
+}

+ 208 - 12
models/data_manage/trade_position_analysis.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"hongze/hongze_task/utils"
 	"time"
@@ -90,27 +91,41 @@ func InsertMultiTradePositionTop(exchange string, items []*TradePositionTop) (er
 	return
 }
 
-
 func GetTradePositionTopByExchangeDataTime(exchange string, startDate, endDate string) (list []*TradePositionTop, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "SELECT * FROM trade_position_"+exchange+"_top where data_time >= ? and data_time <= ? and deal_type in (1,2) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc"
+	sql := "SELECT * FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? and deal_type in (1,2) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc"
 	_, err = o.Raw(sql, startDate, endDate).QueryRows(&list)
 	return
 }
 
+func GetTradePositionTopByExchangeDataTimeByClassify(exchange string, startDate, endDate string, classifyNames, classifyTypes []string) (list []*TradePositionTop, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM trade_position_` + exchange + `_top where data_time >= ? and data_time <= ? and deal_type in (1,2) and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc`
+	_, err = o.Raw(sql, startDate, endDate, classifyNames, classifyTypes).QueryRows(&list)
+	return
+}
+
 func GetTradePositionTopCountByExchangeDataTime(exchange string, startDate, endDate string) (count int64, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "SELECT count(*) FROM trade_position_"+exchange+"_top where data_time >= ? and data_time <= ? and deal_type in (1,2) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc"
+	sql := "SELECT count(*) FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? and deal_type in (1,2) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc"
 	err = o.Raw(sql, startDate, endDate).QueryRow(&count)
 	return
 }
 
 func GetTradePositionTopByExchangeSourceType(exchange string, dataTime string, sourceType int) (list []*TradePositionTop, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "SELECT * FROM trade_position_"+exchange+"_top where data_time= ? and source_type = ? ORDER BY classify_name, classify_type, deal_type, deal_value desc"
+	sql := "SELECT * FROM trade_position_" + exchange + "_top where data_time= ? and source_type = ? ORDER BY classify_name, classify_type, deal_type, deal_value desc"
 	_, err = o.Raw(sql, dataTime, sourceType).QueryRows(&list)
 	return
 }
+
+func GetTradePositionTopByExchangeSourceTypeClassify(exchange string, dataTime string, sourceType int, classifyNames, classifyTypes []string) (list []*TradePositionTop, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM trade_position_` + exchange + `_top where data_time= ? and source_type = ? and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `) ORDER BY classify_name, classify_type, deal_type, deal_value desc`
+	_, err = o.Raw(sql, dataTime, sourceType, classifyNames, classifyTypes).QueryRows(&list)
+	return
+}
+
 type TradeTopClassify struct {
 	ClassifyName string //分类名称
 	ClassifyType string //分类名称下的类型
@@ -147,9 +162,15 @@ type UpdateDealValueChange struct {
 	ModifyTime time.Time //修改时间
 }
 
+type UpdateChangeVal struct {
+	Id         uint64
+	DealChange int
+	ModifyTime time.Time //修改时间
+}
+
 func MultiUpdatePositionTop(exchange string, updates []UpdateDealValueChange) (err error) {
 	o := orm.NewOrmUsingDB("data")
-	p, err := o.Raw("UPDATE trade_position_"+exchange+"_top SET deal_value=?, deal_change=?, source_type=?, modify_time=? WHERE id = ?").Prepare()
+	p, err := o.Raw("UPDATE trade_position_" + exchange + "_top SET deal_value=?, deal_change=?, source_type=?, modify_time=? WHERE id = ?").Prepare()
 	if err != nil {
 		return
 	}
@@ -167,29 +188,204 @@ func MultiUpdatePositionTop(exchange string, updates []UpdateDealValueChange) (e
 
 func DeletePositionTopByDataTime(exchange string, dataTime string, dealType int) (err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "delete from trade_position_"+exchange+"_Top WHERE data_time=? and deal_type=?"
+	sql := "delete from trade_position_" + exchange + "_top WHERE data_time=? and deal_type=?"
 	_, err = o.Raw(sql, dataTime, dealType).Exec()
 	return
 }
 
+func DeletePositionTopByDataTimeClassify(exchange string, dataTime string, dealType int, classifyNames, classifyTypes []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `delete from trade_position_` + exchange + `_top WHERE data_time=? and deal_type=? and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `)`
+	_, err = o.Raw(sql, dataTime, dealType, classifyNames, classifyTypes).Exec()
+	return
+}
+
 func GetTradePositionTopByExchangeDataTimeType(exchange string, dataTime string, dealType int) (list []TradePositionTop, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "select * from trade_position_"+exchange+"_Top WHERE data_time=? and deal_type=?"
-	_, err = o.Raw(sql,dataTime, dealType).QueryRows(&list)
+	sql := "select * from trade_position_" + exchange + "_top WHERE data_time=? and deal_type=?"
+	_, err = o.Raw(sql, dataTime, dealType).QueryRows(&list)
+	return
+}
+
+func GetTradePositionTopByExchangeDataTimeTypeClassify(exchange string, dataTime string, dealType int, classifyNames, classifyTypes []string) (list []TradePositionTop, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select * from trade_position_` + exchange + `_top WHERE data_time=? and deal_type=? and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `)`
+	_, err = o.Raw(sql, dataTime, dealType, classifyNames, classifyTypes).QueryRows(&list)
 	return
 }
 
 func MultiInsertTradeBaseDataToTop(exchange string, startDate, endDate string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	now := time.Now().Format(utils.FormatDateTime)
-	sql1 := `INSERT INTO trade_position_`+exchange+`_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
-	SELECT classify_name,classify_type,buy_short_name,buy_value,buy_change,data_time,1,0,rank,?,? FROM base_from_trade_`+exchange+`_index where rank <50 and buy_short_name !="" and data_time between ? and ?`
+	sql1 := `INSERT INTO trade_position_` + exchange + `_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
+	SELECT classify_name,classify_type,buy_short_name,buy_value,buy_change,data_time,1,0,rank,?,? FROM base_from_trade_` + exchange + `_index where rank <50 and buy_short_name !="" and data_time between ? and ?`
 	_, err = o.Raw(sql1, now, now, startDate, endDate).Exec()
 	if err != nil {
 		return
 	}
-	sql2 := `INSERT INTO trade_position_`+exchange+`_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
-SELECT classify_name,classify_type,sold_short_name,sold_value,sold_change,data_time,2,0,rank,?,? FROM base_from_trade_`+exchange+`_index where rank <50 and sold_short_name !="" and data_time between ? and ?`
+	sql2 := `INSERT INTO trade_position_` + exchange + `_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
+SELECT classify_name,classify_type,sold_short_name,sold_value,sold_change,data_time,2,0,rank,?,? FROM base_from_trade_` + exchange + `_index where rank <50 and sold_short_name !="" and data_time between ? and ?`
 	_, err = o.Raw(sql2, now, now, startDate, endDate).Exec()
 	return
 }
+
+func MultiInsertTradeBaseDataToTopByClassify(exchange string, startDate, endDate string, classifyNames, classifyTypes []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	now := time.Now().Format(utils.FormatDateTime)
+	sql1 := `INSERT INTO trade_position_` + exchange + `_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
+	SELECT classify_name,classify_type,buy_short_name,buy_value,buy_change,data_time,1,0,rank,?,? FROM base_from_trade_` + exchange + `_index where rank <50 and buy_short_name !="" and data_time between ? and ? and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `)`
+	_, err = o.Raw(sql1, now, now, startDate, endDate, classifyNames, classifyTypes).Exec()
+	if err != nil {
+		return
+	}
+	sql2 := `INSERT INTO trade_position_` + exchange + `_top(classify_name,classify_type,deal_short_name,deal_value,deal_change,data_time,deal_type,source_type,rank,create_time,modify_time)
+SELECT classify_name,classify_type,sold_short_name,sold_value,sold_change,data_time,2,0,rank,?,? FROM base_from_trade_` + exchange + `_index where rank <50 and sold_short_name !="" and data_time between ? and ? and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `)`
+	_, err = o.Raw(sql2, now, now, startDate, endDate, classifyNames, classifyTypes).Exec()
+	return
+}
+
+// GetTradePositionTopOriginDataTimes 获取榜单原始数据日期-正序
+func GetTradePositionTopOriginDataTimes(exchange string) (dates []string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT DISTINCT data_time FROM base_from_trade_%s_index ORDER BY data_time ASC`
+	sql = fmt.Sprintf(sql, exchange)
+	_, err = o.Raw(sql).QueryRows(&dates)
+	return
+}
+
+// BaseFromTradeClassify 交易所分类表
+type BaseFromTradeClassify struct {
+	Id           uint64    `orm:"column(id);pk"`
+	ClassifyName string    //分类名称
+	ClassifyType string    //分类名称下的类型
+	Exchange     string    //交易所
+	LatestDate   time.Time //数据最近的日期
+	CreateTime   time.Time //插入时间
+	ModifyTime   time.Time //修改时间
+}
+
+// GetAllBaseFromTradeClassify 获取所有的交易所分类列表
+func GetAllBaseFromTradeClassify() (list []*BaseFromTradeClassify, err error) {
+	sql := `SELECT * FROM base_from_trade_classify   `
+
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// Update 更新
+func (m *BaseFromTradeClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+// MultiAddBaseFromTradeClassify 批量插入交易所分类
+func MultiAddBaseFromTradeClassify(items []*BaseFromTradeClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+type TradeClassifyName struct {
+	ClassifyName string    //分类名称
+	ClassifyType string    //分类名称下的类型
+	DataTime     time.Time //数据最近的日期
+	ModifyTime   time.Time //数据最近的日期
+}
+
+// GetExchangeClassify 获取交易所分类列表
+func GetExchangeClassify(exchange string) (list []TradeClassifyName, err error) {
+	tableName := "trade_position_" + exchange + "_top"
+	orderStr := "classify_name DESC, classify_type asc"
+	if exchange == "zhengzhou" {
+		orderStr = "classify_name asc"
+	}
+	sql := `SELECT classify_name, classify_type,MAX(data_time) as data_time,MAX(modify_time) as modify_time FROM ` + tableName + ` GROUP BY classify_name, classify_type  `
+	sql += ` ORDER BY ` + orderStr
+
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// GetTradePositionTopCleanByExchangeDataTime 根据时间查询净多单和净空单的值
+func GetTradePositionTopCleanByExchangeDataTime(exchange string, startDate, endDate string) (list []*TradePositionTop, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? and deal_type in (3,4) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc"
+	_, err = o.Raw(sql, startDate, endDate).QueryRows(&list)
+	return
+}
+
+// GetTradePositionTopCleanByExchangeDataTimeClassify 根据时间查询净多单和净空单的值
+func GetTradePositionTopCleanByExchangeDataTimeClassify(exchange string, startDate, endDate string, classifyNames, classifyTypes []string) (list []*TradePositionTop, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM trade_position_` + exchange + `_top where data_time >= ? and data_time <= ? and deal_type in (3,4) and classify_name in (` + utils.GetOrmInReplace(len(classifyNames)) + `)  and classify_type in (` + utils.GetOrmInReplace(len(classifyTypes)) + `) ORDER BY classify_name, classify_type, deal_type, data_time, deal_value desc`
+	_, err = o.Raw(sql, startDate, endDate, classifyNames, classifyTypes).QueryRows(&list)
+	return
+}
+
+// MultiUpdatePositionTopChangeVal 批量更新榜单里变化量的值
+func MultiUpdatePositionTopChangeVal(exchange string, updates []UpdateChangeVal) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	p, err := o.Raw("UPDATE trade_position_" + exchange + "_top SET deal_change=?, modify_time=? WHERE id = ?").Prepare()
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = p.Close() // 别忘记关闭 statement
+	}()
+	for _, v := range updates {
+		_, err = p.Exec(v.DealChange, v.ModifyTime, v.Id)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func GetTradePositionOriginClassifyCountByExchangeDataTime(exchange string, startDate, endDate string) (count int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(DISTINCT classify_name, classify_type) FROM base_from_trade_` + exchange + `_index where rank <50 and (buy_short_name !="" || sold_short_name !="" ) and data_time >= ? and data_time <= ?`
+	err = o.Raw(sql, startDate, endDate).QueryRow(&count)
+	return
+}
+
+func GetTradePositionTopClassifyCountByExchangeDataTime(exchange string, startDate, endDate string) (count int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT COUNT(DISTINCT classify_name, classify_type)  FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? and deal_type in (1,2) "
+	err = o.Raw(sql, startDate, endDate).QueryRow(&count)
+	return
+}
+
+type TradePositionClassifyInfo struct {
+	ClassifyName string //分类名称
+	ClassifyType string //分类名称下的类型
+}
+
+func GetTradePositionOriginClassifyByExchangeDataTime(exchange string, startDate, endDate string) (list []TradePositionClassifyInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT DISTINCT classify_name, classify_type FROM base_from_trade_` + exchange + `_index where rank <50 and (buy_short_name !="" || sold_short_name !="" ) and data_time >= ? and data_time <= ?`
+	_, err = o.Raw(sql, startDate, endDate).QueryRows(&list)
+	return
+}
+
+func GetTradePositionTopClassifyByExchangeDataTime(exchange string, startDate, endDate string) (list []TradePositionClassifyInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT DISTINCT classify_name, classify_type  FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? and deal_type in (1,2) "
+	_, err = o.Raw(sql, startDate, endDate).QueryRows(&list)
+	return
+}
+
+// DeleteTradePositionTopAllByExchangeDataTime 删除计算数据
+func DeleteTradePositionTopAllByExchangeDataTime(exchange string, startDate, endDate string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "DELETE FROM trade_position_" + exchange + "_top where data_time >= ? and data_time <= ? "
+	_, err = o.Raw(sql, startDate, endDate).Exec()
+	return
+}

+ 45 - 2
models/db.go

@@ -6,6 +6,7 @@ import (
 	"hongze/hongze_task/models/company_contract"
 	"hongze/hongze_task/models/data_manage"
 	"hongze/hongze_task/models/data_manage/future_good"
+	"hongze/hongze_task/models/eta_business"
 	"hongze/hongze_task/models/roadshow"
 	"hongze/hongze_task/models/yb"
 	"hongze/hongze_task/utils"
@@ -58,6 +59,13 @@ func init() {
 	trial_datadb, _ := orm.GetDB("weekly_trial")
 	trial_datadb.SetConnMaxLifetime(10 * time.Minute)
 
+	_ = orm.RegisterDataBase("eta", "mysql", utils.MYSQL_URL_ETA)
+	orm.SetMaxIdleConns("eta", 50)
+	orm.SetMaxOpenConns("eta", 100)
+
+	etaDb, _ := orm.GetDB("eta")
+	etaDb.SetConnMaxLifetime(10 * time.Minute)
+
 	orm.Debug = true
 	orm.DebugLog = orm.NewLog(utils.Binlog)
 
@@ -94,6 +102,15 @@ func init() {
 
 	//注册持仓分析 数据表
 	initTradePositionTop()
+
+	// ETA商家数据表
+	initEtaBusiness()
+
+	//英文研报
+	initEnglishReport()
+
+	// ETA商家配置表
+	initEtaBusinessConf()
 }
 
 // initCompany 注册客户信息 数据表
@@ -109,7 +126,7 @@ func initCompany() {
 		new(CompanyEndDate),
 		new(CompanyProductUpdateLog), //客户产品状态变更表
 		new(CompanyViewStatistics),
-		new(CompanyRemindRecord),	// 客户提醒记录
+		new(CompanyRemindRecord), // 客户提醒记录
 	)
 }
 
@@ -182,6 +199,8 @@ func initCygx() {
 		new(data_manage.CygxMinutesSummaryLog),
 		new(data_manage.CygxArticle),
 		new(data_manage.CygxArticleType),
+		new(data_manage.CygxActivitySpecialTripBill),
+		new(data_manage.CygxActivitySpecialInheritPointsCompany),
 	)
 }
 
@@ -202,5 +221,29 @@ func initTradePositionTop() {
 		new(data_manage.TradePositionShanghaiTop),
 		new(data_manage.TradePositionIneTop),
 		new(data_manage.TradePositionCffexTop),
+		new(data_manage.BaseFromTradeClassify), // 交易所分类
+	)
+}
+
+// initEtaBusiness ETA商家数据表
+func initEtaBusiness() {
+	orm.RegisterModel(
+		new(eta_business.EtaBusiness),
+		new(eta_business.EtaBusinessContract),
+		new(eta_business.EtaBusinessRemindRecord),
 	)
-}
+}
+
+// initEtaBusinessConf ETA商家基本配置表
+func initEtaBusinessConf() {
+	orm.RegisterModel(
+		new(BusinessConf),
+	)
+}
+
+// initEnglishReport 英文研报
+func initEnglishReport() {
+	orm.RegisterModel(
+		new(EnglishReport),
+	)
+}

+ 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
+}

+ 63 - 0
models/english_report_email.go

@@ -0,0 +1,63 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"hongze/hongze_task/utils"
+	"time"
+)
+
+// EnglishReportEmail 英文研报-邮箱/客户联系人
+type EnglishReportEmail struct {
+	Id              int       `orm:"column(id);pk" description:"邮箱ID"`
+	CompanyId       int       `description:"客户ID"`
+	Name            string    `description:"联系人名称"`
+	Email           string    `description:"邮箱地址"`
+	Mobile          string    `description:"手机号"`
+	CountryCode     string    `description:"区号,86、852、886等"`
+	BusinessCardUrl string    `description:"名片"`
+	ViewTotal       int       `description:"累计点击量/阅读量"`
+	LastViewTime    time.Time `description:"最后阅读时间"`
+	IsDeleted       int       `description:"删除状态:0-正常;1-已删除"`
+	Enabled         int       `description:"邮箱状态:1:有效,0:禁用"`
+	AdminId         int       `description:"创建人ID"`
+	AdminName       string    `description:"创建人姓名"`
+	Status          int       `description:"1:正式,2:临时,3:终止"`
+	CompanyName     string    `description:"公司名称"`
+	CreateTime      time.Time `description:"创建时间"`
+	ModifyTime      time.Time `description:"更新时间"`
+	RegisterTime    time.Time `description:"注册时间"`
+}
+
+func (item *EnglishReportEmail) TableName() string {
+	return "english_report_email"
+}
+
+func (item *EnglishReportEmail) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.Id = int(id)
+	return
+}
+
+func (item *EnglishReportEmail) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+func GetEndEnglishReportEmailListByDate(endDate string) (items []*EnglishReportEmail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report_email WHERE register_time < '`+endDate+`' AND status = 2  `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func UpdateEnglishReportEmailTermination(disableIds []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report_email SET status = 3 WHERE id IN (` + utils.GetOrmInReplace(len(disableIds)) + `) `
+	_, err = o.Raw(sql, disableIds, ).Exec()
+	return
+}

+ 327 - 0
models/eta_business/eta_business.go

@@ -0,0 +1,327 @@
+package eta_business
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"hongze/hongze_task/utils"
+	"strings"
+	"time"
+)
+
+const (
+	EtaBusinessSigningStatusFirst = iota + 1
+	EtaBusinessSigningStatusContinue
+	EtaBusinessSigningStatusTerminate
+)
+
+type EtaBusiness struct {
+	EtaBusinessId    int       `orm:"column(eta_business_id);pk"`
+	BusinessName     string    `description:"商家名称"`
+	BusinessCode     string    `description:"商家编码"`
+	CreditCode       string    `description:"社会统一信用码"`
+	RegionType       string    `description:"所属区域:国内;海外"`
+	Province         string    `description:"省份"`
+	City             string    `description:"城市"`
+	Address          string    `description:"商家地址"`
+	SellerId         int       `description:"销售ID"`
+	SellerName       string    `description:"销售名称"`
+	Leader           string    `description:"决策人"`
+	IndustryId       int       `description:"行业ID"`
+	IndustryName     string    `description:"行业名称"`
+	CapitalScale     string    `description:"资金规模"`
+	ResearchTeamSize string    `description:"研究团队规模"`
+	UserMax          int       `description:"用户上限"`
+	SigningStatus    int       `description:"签约状态:1-首次签约;2-续约中;3-已终止"`
+	Enable           int       `description:"状态:0-禁用;1-启用"`
+	ContractId       int       `description:"当前合约ID"`
+	SigningTime      time.Time `description:"当前合约的签约时间"`
+	ExpiredTime      time.Time `description:"当前合约的到期时间"`
+	CreateTime       time.Time `description:"创建时间"`
+	ModifyTime       time.Time `description:"更新时间"`
+}
+
+func (m *EtaBusiness) TableName() string {
+	return "eta_business"
+}
+
+func (m *EtaBusiness) PrimaryId() string {
+	return EtaBusinessColumns.EtaBusinessId
+}
+
+var EtaBusinessColumns = struct {
+	EtaBusinessId    string
+	BusinessName     string
+	BusinessCode     string
+	CreditCode       string
+	RegionType       string
+	Province         string
+	City             string
+	Address          string
+	SellerId         string
+	SellerName       string
+	Leader           string
+	IndustryId       string
+	IndustryName     string
+	CapitalScale     string
+	ResearchTeamSize string
+	UserMax          string
+	SigningStatus    string
+	Enable           string
+	SigningTime      string
+	ExpiredTime      string
+	CreateTime       string
+	ModifyTime       string
+}{
+	EtaBusinessId:    "eta_business_id",
+	BusinessName:     "business_name",
+	BusinessCode:     "business_code",
+	CreditCode:       "credit_code",
+	RegionType:       "region_type",
+	Province:         "province",
+	City:             "city",
+	Address:          "address",
+	SellerId:         "seller_id",
+	SellerName:       "seller_name",
+	Leader:           "leader",
+	IndustryId:       "industry_id",
+	IndustryName:     "industry_name",
+	CapitalScale:     "capital_scale",
+	ResearchTeamSize: "research_team_size",
+	UserMax:          "user_max",
+	SigningStatus:    "signing_status",
+	Enable:           "enable",
+	SigningTime:      "signing_time",
+	ExpiredTime:      "expired_time",
+	CreateTime:       "create_time",
+	ModifyTime:       "modify_time",
+}
+
+func (m *EtaBusiness) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.EtaBusinessId = int(id)
+	return
+}
+
+func (m *EtaBusiness) CreateMulti(items []*EtaBusiness) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaBusiness) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaBusiness) UpdateMulti(items []*EtaBusiness, cols []string) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	for _, v := range items {
+		_, err = o.Update(v, cols...)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func (m *EtaBusiness) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.EtaBusinessId).Exec()
+	return
+}
+
+func (m *EtaBusiness) GetItemById(id int) (item *EtaBusiness, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *EtaBusiness) GetItemByCondition(condition string, pars []interface{}) (item *EtaBusiness, err error) {
+	o := orm.NewOrm()
+	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 *EtaBusiness) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	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 *EtaBusiness) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaBusiness, err error) {
+	o := orm.NewOrm()
+	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 *EtaBusiness) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*EtaBusiness, err error) {
+	o := orm.NewOrm()
+	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
+}
+
+// CreateEtaBusinessAndContract 新增商家和签约
+func CreateEtaBusinessAndContract(businessItem *EtaBusiness, contractItem *EtaBusinessContract) (err error) {
+	if businessItem == nil || contractItem == nil {
+		err = fmt.Errorf("item empty")
+		return
+	}
+
+	o := orm.NewOrm()
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 商家
+	businessId, e := tx.Insert(businessItem)
+	if e != nil {
+		err = fmt.Errorf("business insert err: %s", e.Error())
+		return
+	}
+	businessItem.EtaBusinessId = int(businessId)
+
+	// 签约
+	contractItem.EtaBusinessId = businessItem.EtaBusinessId
+	_, e = tx.Insert(contractItem)
+	if e != nil {
+		err = fmt.Errorf("contract insert err: %s", e.Error())
+	}
+	return
+}
+
+// EtaBusinessAddReq 新增商家请求体
+type EtaBusinessAddReq struct {
+	BusinessName     string `description:"商家名称"`
+	CreditCode       string `description:"社会统一信用码"`
+	RegionType       string `description:"所属区域:国内;海外"`
+	Province         string `description:"省份"`
+	City             string `description:"城市"`
+	SellerId         int    `description:"销售ID"`
+	SellerName       string `description:"销售名称"`
+	Leader           string `description:"决策人"`
+	IndustryId       int    `description:"行业ID"`
+	IndustryName     string `description:"行业名称"`
+	CapitalScale     string `description:"资金规模"`
+	ResearchTeamSize string `description:"研究团队规模"`
+	UserMax          int    `description:"用户上限"`
+	SigningTime      string `description:"签约时间"`
+	ExpiredTime      string `description:"到期时间"`
+	IsCheck          bool   `description:"是否只做校验而不实际新增(业务操作上基础信息和签约时间分成两个步骤了)"`
+}
+
+// EtaBusinessEditReq 编辑商家请求体
+type EtaBusinessEditReq struct {
+	EtaBusinessId    int    `description:"商家ID"`
+	Province         string `description:"省份"`
+	City             string `description:"城市"`
+	Leader           string `description:"决策人"`
+	IndustryId       int    `description:"行业ID"`
+	IndustryName     string `description:"行业名称"`
+	CapitalScale     string `description:"资金规模"`
+	ResearchTeamSize string `description:"研究团队规模"`
+	UserMax          int    `description:"用户上限"`
+}
+
+// EtaBusinessSigningReq 商家签约请求体
+type EtaBusinessSigningReq struct {
+	EtaBusinessId int    `description:"商家ID"`
+	SigningTime   string `description:"当前合约的签约时间"`
+	ExpiredTime   string `description:"当前合约的到期时间"`
+}
+
+// EtaBusinessEnableReq 禁启用商家请求体
+type EtaBusinessEnableReq struct {
+	EtaBusinessId int `description:"商家ID"`
+}
+
+// EtaBusinessMoveSellerReq 移动商家销售请求体
+type EtaBusinessMoveSellerReq struct {
+	EtaBusinessId int    `description:"商家ID"`
+	SellerId      int    `description:"销售ID"`
+	SellerName    string `description:"销售名称"`
+}
+
+// CreateEtaBusinessCode 生成ETA商家编码
+func CreateEtaBusinessCode() (code string, err error) {
+	var num int
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS num FROM eta_business WHERE create_time >= ? `
+	err = o.Raw(sql, time.Now().Format(utils.FormatDate)).QueryRow(&num)
+	if err != nil {
+		return
+	}
+	code = "E" + time.Now().Format("20060102") + fmt.Sprintf("%02d", num)
+	return
+}
+
+// EtaBusinessListResp 商家分页列表响应体
+type EtaBusinessListResp struct {
+	List   []*EtaBusinessItem `description:"商家列表数据"`
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// EtaBusinessItem ETA商家信息
+type EtaBusinessItem struct {
+	EtaBusinessId    int
+	BusinessName     string `description:"商家名称"`
+	BusinessCode     string `description:"商家编码"`
+	CreditCode       string `description:"社会统一信用码"`
+	RegionType       string `description:"所属区域:国内;海外"`
+	Address          string `description:"商家地址"`
+	SellerId         int    `description:"销售ID"`
+	SellerName       string `description:"销售名称"`
+	Leader           string `description:"决策人"`
+	IndustryId       int    `description:"行业ID"`
+	IndustryName     string `description:"行业名称"`
+	CapitalScale     string `description:"资金规模"`
+	ResearchTeamSize string `description:"研究团队规模"`
+	UserMax          int    `description:"用户上限"`
+	SigningStatus    int    `description:"签约状态:1-首次签约;2-续约中;3-已终止"`
+	Enable           int    `description:"状态:0-禁用;1-启用"`
+	SigningTime      string `description:"当前合约的签约时间"`
+	ExpiredTime      string `description:"当前合约的到期时间"`
+	CreateTime       string `description:"创建时间"`
+	ModifyTime       string `description:"更新时间"`
+}

+ 121 - 0
models/eta_business/eta_business_contract.go

@@ -0,0 +1,121 @@
+package eta_business
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// EtaBusinessContract ETA商家合同表
+type EtaBusinessContract struct {
+	EtaBusinessContractId int       `orm:"column(eta_business_contract_id);pk"`
+	EtaBusinessId         int       `description:"ETA商家ID"`
+	SigningTime           time.Time `description:"签约时间"`
+	ExpiredTime           time.Time `description:"到期时间"`
+	IsFirst               int       `description:"是否为首份签约"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"更新时间"`
+}
+
+func (m *EtaBusinessContract) TableName() string {
+	return "eta_business_contract"
+}
+
+func (m *EtaBusinessContract) PrimaryId() string {
+	return EtaBusinessContractColumns.EtaBusinessContractId
+}
+
+var EtaBusinessContractColumns = struct {
+	EtaBusinessContractId string
+	EtaBusinessId         string
+	SigningTime           string
+	ExpiredTime           string
+	CreateTime            string
+	ModifyTime            string
+}{
+	EtaBusinessContractId: "eta_business_contract_id",
+	EtaBusinessId:         "eta_business_id",
+	SigningTime:           "signing_time",
+	ExpiredTime:           "expired_time",
+	CreateTime:            "create_time",
+	ModifyTime:            "modify_time",
+}
+
+func (m *EtaBusinessContract) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.EtaBusinessContractId = int(id)
+	return
+}
+
+func (m *EtaBusinessContract) CreateMulti(items []*EtaBusinessContract) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *EtaBusinessContract) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *EtaBusinessContract) Del() (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.EtaBusinessContractId).Exec()
+	return
+}
+
+func (m *EtaBusinessContract) GetItemById(id int) (item *EtaBusinessContract, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *EtaBusinessContract) GetItemByCondition(condition string, pars []interface{}) (err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&m)
+	return
+}
+
+func (m *EtaBusinessContract) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	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 *EtaBusinessContract) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EtaBusinessContract, err error) {
+	o := orm.NewOrm()
+	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
+}
+
+// EtaBusinessContractItem 商家签约信息
+type EtaBusinessContractItem struct {
+	EtaBusinessContractId int
+	EtaBusinessId         int    `description:"ETA商家ID"`
+	SigningTime           string `description:"签约时间"`
+	ExpiredTime           string `description:"到期时间"`
+	ExpireDay             string `description:"到期天数"`
+	Using                 bool   `description:"是否当前合约"`
+}

+ 38 - 0
models/eta_business/eta_business_remind_record.go

@@ -0,0 +1,38 @@
+package eta_business
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// EtaBusinessRemindRecord 客户提醒记录
+type EtaBusinessRemindRecord struct {
+	CompanyRemindRecordId int    `orm:"column(eta_business_remind_record_id);pk"`
+	Type                  int    `description:"过期类型:1-1天;2-7天;3-15天;4-30天;5-60天"`
+	SellerId              int    `description:"销售id"`
+	SellerName            string `description:"销售名称"`
+	EtaBusinessId         int    `description:"ETA商家ID"`
+	BusinessName          string `description:"商家名称"`
+	EndDate               string `description:"到期日期"`
+	UniqueCode            string `description:"唯一code"`
+	CreateTime            time.Time
+}
+
+func (m *EtaBusinessRemindRecord) Create() (err error) {
+	o := orm.NewOrm()
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.CompanyRemindRecordId = int(id)
+	return
+}
+
+func (m *EtaBusinessRemindRecord) CreateMulti(items []*EtaBusinessRemindRecord) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(len(items), items)
+	return
+}

+ 3 - 3
models/eta_trial.go

@@ -40,9 +40,9 @@ func UpdateETATrialDisable(disableIds []int) (err error) {
 	return
 }
 
-func UpdateETATrialAdminDisable(mobiles string) (err error) {
+func UpdateETATrialAdminDisable(disableMobiles []string) (err error) {
 	o := orm.NewOrmUsingDB("weekly_trial")
-	sql := `UPDATE admin SET enabled = 0 WHERE mobile IN  (` + mobiles + `) `
-	_, err = o.Raw(sql).Exec()
+	sql := `UPDATE admin SET enabled = 0 WHERE mobile IN  (` + utils.GetOrmInReplace(len(disableMobiles)) + `) `
+	_, err = o.Raw(sql,disableMobiles).Exec()
 	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(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
+}

+ 23 - 1
models/report_view.go

@@ -80,7 +80,7 @@ func GetRddpReportViewersDetail(startTime, endTime string) (items []*ResearchRep
 			WHERE uvh.create_time >?
             AND uvh.create_time <=?
 			AND c.company_id NOT IN (1)
-			AND r.classify_name_first not in ("周报","大宗商品","数据点评","会议纪要","年报合集","需求报告")
+			AND r.classify_name_first not in ("晨报","周报","大宗商品","数据点评","会议纪要","年报合集","需求报告")
 			AND r.classify_id_second not in (113,114,127,134,141,149)
 			ORDER BY uvh.create_time DESC
 `
@@ -102,6 +102,28 @@ type RddpWeekReportViewersDetail struct {
 	ClassifyNameSecond string
 }
 
+func GetRddpDayReportViewersDetail(startTime, endTime string) (items []*RddpWeekReportViewersDetail, err error) {
+	sql := `
+			SELECT
+	u.real_name,c.company_name,uvh.create_time AS created_time,REPLACE ( SUBSTRING( r.create_time, 6, 5 ), '-', '' ) AS report_create_date,
+	r.title AS research_report_name,r.classify_name_second AS research_report_type ,rc.title chapter_title,rc.type_name as report_variety
+FROM
+	hongze_rddp.report_view_record AS uvh
+	INNER JOIN hongze_rddp.report AS r ON uvh.report_id = r.id
+	INNER JOIN hongze_rddp.report_chapter AS rc ON uvh.report_id = rc.report_id and uvh.report_chapter_id=rc.report_chapter_id
+	INNER JOIN wx_user u ON u.user_id = uvh.user_id
+	INNER JOIN company c ON c.company_id = u.company_id 
+			WHERE uvh.create_time >?
+            AND uvh.create_time <=?
+			AND c.company_id NOT IN (1)
+			AND r.classify_name_first = "晨报"
+			ORDER BY uvh.create_time DESC
+`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, startTime, endTime).QueryRows(&items)
+	return
+}
+
 func GetRddpWeekReportViewersDetail(startTime, endTime string) (items []*RddpWeekReportViewersDetail, err error) {
 	sql := `
 			SELECT

+ 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:"大小"`
+}

+ 2 - 2
services/activity.go

@@ -112,8 +112,8 @@ func sendWxMsg(activityInfo *yb.Activity, remindType string) (err error) {
 	}
 
 	first := `您有一场【` + activityInfo.ActivityTypeName + `】将在` + timeStr + `后开始`
-	keyword1 := activityInfo.ActivityName
-	keyword2 := "- -"
+	keyword1 := activityInfo.ActivityTypeName + "-" + activityInfo.ActivityName
+	keyword2 := "将在" + timeStr + "后开始"
 	//keyword3 := `2021-10-18 16:30-17:00 星期一`
 	keyword3 := formatActivityTimeStr(activityInfo.StartTime, activityInfo.EndTime)
 

+ 11 - 11
services/alarm_msg/alarm_msg.go

@@ -10,10 +10,10 @@ var (
 	AlarmMsgUrl = "http://127.0.0.1:8606/api/alarm/send"
 )
 
-//projectName-项目名称
-//runMode-运行模式
-//msgBody-消息内容
-//level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
+// projectName-项目名称
+// runMode-运行模式
+// msgBody-消息内容
+// level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
 func SendAlarmMsg(msgBody string, level int) {
 	params := make(map[string]interface{})
 	params["ProjectName"] = utils.APPNAME
@@ -25,15 +25,15 @@ func SendAlarmMsg(msgBody string, level int) {
 		utils.FileLog.Info("SendAlarmMsg json.Marshal Err:" + err.Error())
 		return
 	}
-	http.Post(AlarmMsgUrl, string(param))
+	http.Post(AlarmMsgUrl, string(param), "application/json")
 }
 
 // SendAlarmMsgWithEmail 指定邮箱通知
-//projectName-项目名称
-//runMode-运行模式
-//msgBody-消息内容
-//email-需要发送的邮箱
-//level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
+// projectName-项目名称
+// runMode-运行模式
+// msgBody-消息内容
+// email-需要发送的邮箱
+// level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
 func SendAlarmMsgWithEmail(msgBody, email string, level int) {
 	params := make(map[string]interface{})
 	params["ProjectName"] = utils.APPNAME
@@ -46,5 +46,5 @@ func SendAlarmMsgWithEmail(msgBody, email string, level int) {
 		utils.FileLog.Info("SendAlarmMsg json.Marshal Err:" + err.Error())
 		return
 	}
-	http.Post(AlarmMsgUrl, string(param))
+	http.Post(AlarmMsgUrl, string(param), "application/json")
 }

+ 20 - 0
services/company/company_ascribe.go

@@ -0,0 +1,20 @@
+package company
+
+import (
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
+)
+
+// 正式转试用,删除不续约归因内容
+func DeleteCompanNoRenewedAscribe(companyId ,productId int) () {
+	var err error
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprint("companyId:", companyId,"productId:", productId)
+			go alarm_msg.SendAlarmMsg("正式转试用,删除不续约归因内容失败:"+err.Error()+msg, 2)
+		}
+	}()
+	err = models.DeleteCompanNoRenewedAscribe(companyId,productId)
+	return
+}

+ 20 - 3
services/company_contract/company_contract.go

@@ -9,12 +9,13 @@ import (
 	"hongze/hongze_task/models/company_approval"
 	"hongze/hongze_task/models/company_contract"
 	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/services/cygx"
 	"hongze/hongze_task/utils"
 	"strings"
 	"time"
 )
 
-//合同处理
+// 合同处理
 func HandleCompanyContract(cont context.Context) (err error) {
 	defer func() {
 		if err != nil {
@@ -77,9 +78,15 @@ func HandleCompanyContract(cont context.Context) (err error) {
 		//申请类型:1:试用->正式,2:冻结—>试用,3:试用延期,4:原销售申请领取流失客户,5:正式客户申请服务更新
 		switch companyApproval.ApplyMethod {
 		case 1:
-			company.TryOutToFormal(v.CompanyId, v.ProductId, utils.AdminId, v.CompanyContractId, v.StartDate, v.EndDate, utils.RealName, v.ProductName, v.PackageType)
+			e := company.TryOutToFormal(v.CompanyId, v.ProductId, utils.AdminId, v.CompanyContractId, v.StartDate, v.EndDate, utils.RealName, v.ProductName, v.PackageType, v.RaiPackageType)
+			if e != nil {
+				errorList = append(errorList, fmt.Sprint("合同id:", v.CompanyContractId, ";TryOutToFormal Err: ", e.Error()))
+			}
 		case 5, 6:
-			company.ApplyServiceUpdate(v.CompanyId, v.ProductId, utils.AdminId, v.CompanyContractId, v.StartDate, v.EndDate, utils.RealName, v.ProductName, v.PackageType)
+			e := company.ApplyServiceUpdate(v.CompanyId, v.ProductId, utils.AdminId, v.CompanyContractId, v.StartDate, v.EndDate, utils.RealName, v.ProductName, v.PackageType, v.RaiPackageType)
+			if e != nil {
+				errorList = append(errorList, fmt.Sprint("合同id:", v.CompanyContractId, ";ApplyServiceUpdate Err: ", e.Error()))
+			}
 		default:
 			errorList = append(errorList, fmt.Sprint("合同id:", v.CompanyContractId, ";审批单类型异常"))
 			continue
@@ -101,6 +108,8 @@ func HandleCompanyContract(cont context.Context) (err error) {
 			case 6: //正式客户新增补充协议
 				updateSource = "add_agreement"
 			}
+			tmpStartDate, _ := time.ParseInLocation(utils.FormatDate, v.StartDate, time.Local)
+			tmpEndDate, _ := time.ParseInLocation(utils.FormatDate, v.EndDate, time.Local)
 			companyProductUpdateLog := &models.CompanyProductUpdateLog{
 				Id:         0,
 				CompanyId:  companyProduct.CompanyId,
@@ -110,10 +119,18 @@ func HandleCompanyContract(cont context.Context) (err error) {
 				SellerName: companyProduct.SellerName,
 				Source:     updateSource,
 				IsFormal:   companyProduct.IsFormal, //是否已经转正式,0是没有转正式,1是已经转过正式
+				StartDate:  tmpStartDate,
+				EndDate:    tmpEndDate,
 				CreateTime: time.Now(),
 			}
 			go models.AddCompanyProductUpdateLog(companyProductUpdateLog)
 		}
+
+		//研选审批通过的时候研选扣点更新
+		{
+			cygx.YanXuanCompanyApproval(v.CompanyId)
+			cygx.ActivitySpecialCompanyApproval(v.CompanyId)
+		}
 	}
 
 	//错误信息,批量返回错误信息

+ 92 - 0
services/company_product.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"fmt"
 	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/company"
+	"hongze/hongze_task/services/cygx"
 	"hongze/hongze_task/utils"
 	"strconv"
 	"strings"
@@ -120,6 +122,10 @@ func CompanyTryOut(cont context.Context) (err error) {
 		}
 		fmt.Println(k, v.CompanyId)
 
+		// 更新专项调研点数,需要在状态变更之前
+		if v.ProductId == 2 {
+			cygx.ActivitySpecialCompanyTryOutReduce(v.CompanyId)
+		}
 		//客户正式转试用
 		companyReportPermissionList, err := models.CompanyTryOut(v.CompanyId, v.ProductId)
 		if err != nil {
@@ -139,6 +145,16 @@ func CompanyTryOut(cont context.Context) (err error) {
 				_ = AddCompanyReportPermissionLog(v.CompanyReportPermissionId, v.CompanyId, v.ProductId, utils.AdminId, v.ProductName, utils.RealName, remark, operation, v.Status)
 			}
 		}
+
+		{
+			//正式转试用,删除不续约归因内容
+			company.DeleteCompanNoRenewedAscribe(v.CompanyId, v.ProductId)
+		}
+
+		//正式转试用定时任务更新研选扣点
+		{
+			cygx.YanXuanCompanyCompanyTryOut(v.CompanyId)
+		}
 	}
 	return
 }
@@ -261,6 +277,8 @@ func CompanyRemind(cont context.Context) (err error) {
 	sellers, err := models.GetSellersOpenId()
 	for k, v := range sellers {
 		fmt.Println(k, v.AdminId, v.Mobile)
+		CompanyRemind60Day(v)
+		time.Sleep(5 * time.Second)
 		CompanyRemind30Day(v)
 		time.Sleep(5 * time.Second)
 		CompanyRemind15Day(v)
@@ -273,6 +291,80 @@ func CompanyRemind(cont context.Context) (err error) {
 	return
 }
 
+// CompanyRemind60Day 60天后到期客户
+func CompanyRemind60Day(seller *models.Sellers) {
+	var err error
+	defer func() {
+		if err != nil {
+			go utils.SendEmail(utils.APPNAME+"失败提醒"+utils.RunMode, "到期提醒失败:CompanyRemind;Err"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+
+	companyRemindRecordList := make([]*models.CompanyRemindRecord, 0)
+	remindType := 5
+	uniqueCode := fmt.Sprint(seller.AdminId, time.Now().Format(utils.FormatDateUnSpace), remindType, utils.GetRandString(5))
+
+	//remindEndDate := "2020-12-31" //time.Now().AddDate(0, 0, 30).Format(utils.FormatDate)
+	remindEndDate := time.Now().AddDate(0, 0, 60).Format(utils.FormatDate)
+	companyItems, err := models.GetRemindCompany(seller.AdminId, remindEndDate)
+	if err != nil {
+		return
+	}
+	emailContents := "<div><p>您有【" + strconv.Itoa(len(companyItems)) + "】 客户将于60天后到期,请注意查看</p>"
+	emailContents += "<table border='1'><tr><td width='200'>60天后到期客户名称</td><td width='200'>到期日期</td><td width='200'>销售人员</td><td>客户类型</td></tr>"
+	var isSend bool
+	msgContent := ``
+	for _, v := range companyItems {
+		endTime := v.EndDate
+		if v.Status == "正式" {
+			endTime = v.ContractEndDate
+		}
+		emailContents += `<tr><td>` + v.CompanyName + `</td><td>` + endTime + `</td><td>` + seller.RealName + `</td><td>` + v.Status + `</td></tr>`
+		msgContent += `客户:` + v.CompanyName + ";状态:" + v.Status + "\n"
+		isSend = true
+
+		// 数据入库
+		companyRemindRecordList = append(companyRemindRecordList, &models.CompanyRemindRecord{
+			//CompanyRemindRecordId: 0,
+			Type:        remindType,
+			SellerId:    seller.AdminId,
+			SellerName:  seller.RealName,
+			CompanyId:   v.CompanyId,
+			CompanyName: v.CompanyName,
+			Status:      v.Status,
+			EndDate:     endTime,
+			UniqueCode:  uniqueCode,
+			CreateTime:  time.Now(),
+		})
+	}
+	emailContents += "</table></br>"
+
+	if isSend {
+		if seller.Email != "" {
+			utils.SendEmailByHongze("到期前60天提醒", emailContents, seller.Email, "", "")
+		}
+
+		if seller.OpenId != "" {
+			first := "您有【" + strconv.Itoa(len(companyItems)) + "】 客户将于60天后到期,请注意查看"
+			//keyword1 := "到期前30天提醒"
+			keyword1 := fmt.Sprintf(`【%d】客户到期前60天提醒,点击查看`, len(companyItems))
+			keyword2 := remindEndDate
+			remark := msgContent
+
+			openIdList := make([]*models.OpenIdList, 0)
+			openIdItem := new(models.OpenIdList)
+			openIdItem.OpenId = seller.OpenId
+			openIdList = append(openIdList, openIdItem)
+			SendWxMsgWithCompanyRemind(first, keyword1, keyword2, remark, uniqueCode, openIdList)
+		}
+	}
+
+	// 数据入库
+	if len(companyRemindRecordList) > 0 {
+		models.AddMultiCompanyRemindRecord(companyRemindRecordList)
+	}
+}
+
 // CompanyRemind30Day 30天后到期客户
 func CompanyRemind30Day(seller *models.Sellers) {
 	var err error

+ 2 - 1
services/company_report_permission.go

@@ -40,11 +40,12 @@ func CompanyReportPermissionTryOut(cont context.Context) (err error) {
 			productId = v.ProductId
 
 			//正式转试用用户产品权限
+			v.IsUpgrade = 0
 			v.Status = "试用"
 			v.StartDate = startDateTime.Format(utils.FormatDate)
 			v.EndDate = endDateTime.Format(utils.FormatDate)
 			v.ModifyTime = time.Now().Format(utils.FormatDate)
-			err = v.Update([]string{"Status", "StartDate", "EndDate", "ModifyTime"})
+			err = v.Update([]string{"IsUpgrade","Status", "StartDate", "EndDate", "ModifyTime"})
 			//err = models.CompanyReportPermissionTryOut(v.CompanyReportPermissionId, v.CompanyId, v.ProductId)
 			if err != nil {
 				utils.FileLog.Info("CompanyReportPermissionTryOut Err:%s" + err.Error())

+ 193 - 0
services/cygx/activity_points_set.go

@@ -0,0 +1,193 @@
+package cygx
+
+import (
+	"errors"
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/models/data_manage"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type YanXuanActivityPointsRedis struct {
+	UserId           int       `description:"用户ID"`
+	ComapnyId        int       `description:"公司ID"`
+	ActivityId       int       `description:"活动ID"`
+	PublishStatus    int       `description:"发布状态 1已发布,0未发布"`
+	SourceType       int       `description:"1:报名、 2:取消报名、3:活动编辑、4:活动发布,取消发布、5:活动到会、6 研选审批通过的时候研选扣点更新。"`
+	AdminId          int       `description:"管理员、销售ID"`
+	Source           int       `description:" 来源,1客户端,2后台添加, 3开发人员手动添加、4定时任务"`
+	RegisterPlatform int       `description:"来源 1小程序,2:网页"`
+	CreateTime       time.Time `description:"创建时间"`
+}
+
+//func init(){
+//	log := &YanXuanActivityPointsRedis{ComapnyId: 16, SourceType: 7, Source: 4, CreateTime: time.Now()}
+//	if utils.Re == nil {
+//		err := utils.Rc.LPush(utils.CYGX_YANXUAN_POINTS_KEY, log)
+//		if err != nil {
+//			fmt.Println("YanXuanActivityPointsRedis LPush Err:" + err.Error())
+//		}
+//	}
+//}
+
+// 6 研选审批通过的时候研选扣点更新
+func YanXuanCompanyApproval(comapnyId int) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+			msg := fmt.Sprint("comapnyId:", comapnyId)
+			go alarm_msg.SendAlarmMsg("研选审批通过的时候研选扣点更新,写入Redis队列消息失败:"+err.Error()+msg, 2)
+		}
+	}()
+	//SourceType int       `description:"1:报名、 2:取消报名、3:活动编辑、4:活动发布,取消发布、5:活动到会。6 研选审批通过的时候研选扣点更新。7,正式专试用定时任务更新"`
+	log := &YanXuanActivityPointsRedis{ComapnyId: comapnyId, SourceType: 6, Source: 2, CreateTime: time.Now()}
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CYGX_YANXUAN_POINTS_KEY, log)
+		if err != nil {
+			fmt.Println("YanXuanActivityPointsRedis LPush Err:" + err.Error())
+		}
+	}
+	return
+}
+
+
+// 7 正式专试用定时任务更新研选扣点
+func YanXuanCompanyCompanyTryOut(comapnyId int) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+			msg := fmt.Sprint("comapnyId:", comapnyId)
+			go alarm_msg.SendAlarmMsg("正式专试用定时任务更新研选扣点,写入Redis队列消息失败:"+err.Error()+msg, 2)
+		}
+	}()
+	//SourceType int       `description:"1:报名、 2:取消报名、3:活动编辑、4:活动发布,取消发布、5:活动到会。6 研选审批通过的时候研选扣点更新。7,正式专试用定时任务更新"`
+	log := &YanXuanActivityPointsRedis{ComapnyId: comapnyId, SourceType: 7, Source: 4, CreateTime: time.Now()}
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CYGX_YANXUAN_POINTS_KEY, log)
+		if err != nil {
+			fmt.Println("YanXuanActivityPointsRedis LPush Err:" + err.Error())
+		}
+	}
+	return
+}
+
+// 审批通过的时候专项调研次数更新
+func ActivitySpecialCompanyApproval(companyId int) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+			msg := fmt.Sprint("companyId:", companyId)
+			go alarm_msg.SendAlarmMsg("审批通过试用转正式时专项调研次数更新失败:"+err.Error()+msg, 2)
+		}
+	}()
+	comapny, e := models.GetCompanyById(companyId)
+	if e != nil {
+		err = errors.New("GetCompanyById err:" + e.Error())
+		return
+	}
+	// 获取继承点数
+	inheritList, e := data_manage.GetCygxActivitySpecialInheritPointsByCompanyId(companyId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = errors.New("GetCygxActivitySpecialInheritPointsByCompanyId, Err: " + e.Error())
+	}
+	chartNameMap := map[string]int{utils.YI_YAO_NAME: 0, utils.XIAO_FEI_NAME: 0, utils.KE_JI_NAME: 0, utils.ZHI_ZAO_NAME: 0}
+
+	userType, packageType, _, _, _ := GetUserType(companyId)
+	itemBill := new(data_manage.CygxActivitySpecialTripBill)
+	itemBill.CreateTime = time.Now()
+	itemBill.CompanyId = companyId
+	itemBill.CompanyName = comapny.CompanyName
+	itemBill.Source = 2
+	itemBill.DoType = 2
+	itemBill.Way = 3
+	if userType == 2{
+		packageTypeMap := map[int]int{1: 16, 2: 10}
+		totalTrip := packageTypeMap[packageType]
+		if len(inheritList) > 0 {
+			for _, v := range inheritList {
+				if v.ChartPermissionId == 0 {
+					itemBill.BillDetailed = totalTrip + v.Points
+				} else {
+					itemBill.BillDetailed = totalTrip
+				}
+			}
+		} else {
+			itemBill.BillDetailed = totalTrip
+		}
+
+		itemBill.Total = strconv.Itoa(itemBill.BillDetailed) + "次"
+		if totalTrip == 10 {
+			itemBill.Content = "45w大套餐转正"
+		} else {
+			itemBill.Content = "70w大套餐转正"
+		}
+	} else {
+		list, e := GetCompanyReportPermission(companyId, 2)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = errors.New("GetCompanyReportPermissionUpgrade, Err: " + e.Error())
+		}
+		if len(list) == 0 {
+			return
+		}
+		var chartPermissionIdSlice []string
+		mapChartName := make(map[string]int)
+		mapUpgradeId := make(map[int]int)
+		mapInheritChartName := make(map[string]int)
+		mapPermissionNameTrip := make(map[string]int)
+		//mapPermissionName := make(map[int]string)
+		for _, v := range list {
+			chartPermissionIdSlice = append(chartPermissionIdSlice, strconv.Itoa(v.ChartPermissionId))
+			//是升级套餐才有点数
+			if v.IsUpgrade == 1 {
+				mapUpgradeId[v.ChartPermissionId] = 1
+			}
+		}
+		chartList := make([]*models.ChartPermission, 0)
+		if len(chartPermissionIdSlice) > 0 {
+			chartList, e = models.GetChartPermissionByIds(chartPermissionIdSlice)
+			if e != nil {
+				err = errors.New("获取品种信息失败, Err:" + e.Error())
+				return
+			}
+		}
+		if len(chartList) == 0 {
+			return
+		}
+		for _, v := range chartList {
+			//如果是升级则加点
+			if _, ok := mapUpgradeId[v.ChartPermissionId]; ok {
+				mapChartName[v.PermissionName] = 5 + mapPermissionNameTrip[v.ChartPermissionName]
+			} else {
+				mapChartName[v.PermissionName] = mapPermissionNameTrip[v.ChartPermissionName]
+			}
+		}
+		// 通过继承获得的加点
+		for _, v := range inheritList {
+			mapInheritChartName[v.ChartPermissionName] = v.Points
+		}
+		for k, _ := range chartNameMap {
+			if _, ok := mapChartName[k]; ok {
+				if inherit, ok2 := mapInheritChartName[k]; ok2 {
+					mapChartName[k] += inherit
+				}
+			}
+		}
+		for k, v := range mapChartName {
+			if v > 0 {
+				itemBill.BillDetailed += v
+				itemBill.Total += k + strconv.Itoa(v) + "次+"
+			}
+		}
+		itemBill.Content = "行业升级套餐转正"
+		itemBill.Total = strings.TrimRight(itemBill.Total, "+")
+	}
+	err = data_manage.AddCygxActivitySpecialTripBill(itemBill)
+	if err != nil {
+		return
+	}
+	return
+}

+ 311 - 0
services/cygx/activity_special.go

@@ -0,0 +1,311 @@
+package cygx
+
+import (
+	"errors"
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/models/data_manage"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func ActivitySpecialCompanyTryOutReduce(comapnyId int) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+			go alarm_msg.SendAlarmMsg("正式转试用定时任务更新专项调研扣点失败:"+err.Error(), 2)
+		}
+	}()
+
+	comapny, e := models.GetCompanyById(comapnyId)
+	if e != nil {
+		err = errors.New("GetCompanyById err:" + e.Error())
+		return
+	}
+	userType, tripRemaining, mapChartName, err := GetChartPermissionSpecialSurplusByCompany(comapnyId)
+	if e != nil {
+		err = errors.New("GetChartPermissionSpecialSurplusByCompany err:" + e.Error())
+		return
+	}
+	//获取需要添加的流水信息
+	itemBill := new(data_manage.CygxActivitySpecialTripBill)
+	itemBill.CreateTime = time.Now()
+	itemBill.Source = 2
+	itemBill.DoType = 1
+	itemBill.Way = 3
+	itemBill.BillDetailed = -tripRemaining
+	itemBill.CompanyId = comapnyId
+	itemBill.CompanyName = comapny.CompanyName
+	itemBill.Content = "到期清零"
+	itemBill.Total = "0"
+	err = data_manage.AddCygxActivitySpecialTripBill(itemBill)
+	if err != nil {
+		return
+	}
+
+	// 删除公司之前的继承表
+	err = data_manage.DelCygxActivitySpecialInheritPointsByCompanyId(comapnyId)
+	if err != nil {
+		return
+	}
+	chartMap := map[string]int{utils.YI_YAO_NAME: utils.YI_YAO_ID, utils.XIAO_FEI_NAME: utils.XIAO_FEI_ID, utils.KE_JI_NAME: utils.KE_JI_ID, utils.ZHI_ZAO_NAME: utils.ZHI_ZAO_ID}
+	//添加继承点数表
+	inheritItems := make([]*data_manage.CygxActivitySpecialInheritPointsCompany, 0)
+	if userType == 2 {
+		inheritItem := new(data_manage.CygxActivitySpecialInheritPointsCompany)
+		inheritItem.CompanyId = comapnyId
+		inheritItem.CompanyName = comapny.CompanyName
+		inheritItem.CreateTime = time.Now()
+		inheritItem.ModifyTime = time.Now()
+		inheritItem.Points = tripRemaining
+		inheritItems = append(inheritItems, inheritItem)
+	} else {
+		for k, v := range mapChartName {
+			inheritItem := new(data_manage.CygxActivitySpecialInheritPointsCompany)
+			inheritItem.CompanyId = comapnyId
+			inheritItem.CompanyName = comapny.CompanyName
+			inheritItem.CreateTime = time.Now()
+			inheritItem.ModifyTime = time.Now()
+			inheritItem.Points = v
+			inheritItem.ChartPermissionName = k
+			if _, ok := chartMap[k]; ok {
+				inheritItem.ChartPermissionId = chartMap[k]
+			}
+			inheritItems = append(inheritItems, inheritItem)
+		}
+	}
+	if len(inheritItems) > 0 {
+		err = data_manage.AddCygxActivitySpecialInheritPointsCompanyMulti(inheritItems)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// 获取 用户类型   //1、永续客户 //2、大套餐客户(4个行业全开通的正式客户) //3、分行业套餐客户(开通对应行业的正式客户) //4、仅开通专家套餐的正式客户 //5、开通对应行业套餐或专家套餐的试用客户
+func GetUserType(companyId int) (userType, packageType int, permissionStrnew, companyStatus string, err error) {
+	var permissionStr, permissionZhengShiStr string
+	if companyId <= 1 {
+		userType = 0
+	} else {
+		total, errs := data_manage.GetCountCompanyDetailByIdGroup(companyId)
+		if errs != nil {
+			err = errs
+			return
+		}
+		if total == 0 {
+			userType = 0
+		} else {
+			companyDetail, errs := data_manage.GetCompanyDetailByIdGroup(companyId)
+			if errs != nil {
+				err = errs
+				return
+			}
+			companyStatus = companyDetail.Status
+			permissionStr, errs = data_manage.GetCompanyPermission(companyId)
+			if errs != nil {
+				err = errs
+				return
+			}
+			permissionStrnew = permissionStr
+			//大套餐客户,数据库添加标识,
+			companyUserTypeDetail, errs := data_manage.GetCygxCompanyUserType(companyId)
+			if errs != nil && errs.Error() != utils.ErrNoRow() {
+				err = errs
+				return
+			}
+			if companyUserTypeDetail != nil {
+				packageType = companyUserTypeDetail.PackageType
+				if companyUserTypeDetail.CustomerTypeId != 0 {
+					userType = companyUserTypeDetail.CustomerTypeId
+					return
+				}
+			}
+
+			permissionZhengShiStr, errs = data_manage.GetCompanyPermissionByUserZhengShi(companyId)
+			if errs != nil {
+				err = errs
+				return
+			}
+			//1、永续客户 //2、大套餐客户(4个行业全开通的正式客户) //3、分行业套餐客户(开通对应行业的正式客户) //4、仅开通专家套餐的正式客户 //5、开通对应行业套餐或专家套餐的试用客户、 10: 30W套餐客户
+			//大套餐客户定义:医药、消费、科技、智造、策略。5个行业中任意4个及以上是正式权限的,属于大套餐客户(医药、消费、科技、智造需要主客观都开)
+			if companyDetail.Status == "永续" {
+				userType = 1
+			} else if companyDetail.Status == "试用" {
+				userType = 5
+			} else if companyDetail.Status == "冻结" {
+				userType = 6
+			} else if companyDetail.Status == "流失" {
+				userType = 7
+			}
+			//大套餐客户定义:医药、消费、科技、智造、策略。5个行业中任意4个及以上是正式权限的,属于大套餐客户(医药、消费、科技、智造需要主客观都开)
+			if userType == 0 && companyDetail.Status == "正式" {
+				var permissionZhegnshiNum int
+				if strings.Count(permissionZhengShiStr, "医药") == 2 {
+					permissionZhegnshiNum++
+				}
+				if strings.Count(permissionZhengShiStr, "消费") == 2 {
+					permissionZhegnshiNum++
+				}
+				if strings.Count(permissionZhengShiStr, "科技") == 2 {
+					permissionZhegnshiNum++
+				}
+				if strings.Count(permissionZhengShiStr, "智造") == 2 {
+					permissionZhegnshiNum++
+				}
+				if strings.Count(permissionZhengShiStr, "策略") == 1 {
+					permissionZhegnshiNum++
+				}
+				if strings.Count(permissionZhengShiStr, "路演服务") == 1 {
+					permissionZhegnshiNum++
+				}
+				//if permissionZhegnshiNum == 6 {
+				//	userType = 2
+				//} else
+				//大套餐客户,数据库添加标识,条件大于等于四的都是 30W套餐客户
+				if permissionZhegnshiNum >= 4 {
+					userType = 10
+				} else {
+					userType = 3
+				}
+			}
+		}
+	}
+	permissionStrnew = permissionStr
+	return
+}
+
+// GetChartPermissionSpecialSurplusByCompany 获取公司专项调研次数-分品种
+func GetChartPermissionSpecialSurplusByCompany(companyId int) (userType int, tripRemaining int, mapChartName map[string]int, err error) {
+	companyDetail, e := data_manage.GetCompanyDetailByIdGroup(companyId)
+	if e != nil {
+		err = errors.New("GetCompanyDetailByIdGroup, Err: " + e.Error())
+	}
+	if companyDetail == nil {
+		return
+	}
+	if companyDetail.Status != "永续" && companyDetail.Status != "正式" {
+		return
+	}
+	//var specialSurplus string
+	//if companyDetail.Status == "永续" {
+	//	specialSurplus = "不限次数"
+	//}
+	//chartMap := map[int]string{utils.YI_YAO_ID:utils.YI_YAO_NAME, utils.XIAO_FEI_ID:utils.XIAO_FEI_NAME, utils.KE_JI_ID:utils.KE_JI_NAME, utils.ZHI_ZAO_ID:utils.ZHI_ZAO_NAME}
+	//chartNumMap := map[int]int{utils.YI_YAO_ID:0, utils.XIAO_FEI_ID:0, utils.KE_JI_ID:0, utils.ZHI_ZAO_ID:0}
+	//var packageType int
+
+	// 获取继承点数
+	inheritList, e := data_manage.GetCygxActivitySpecialInheritPointsByCompanyId(companyId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = errors.New("GetCygxActivitySpecialInheritPointsByCompanyId, Err: " + e.Error())
+	}
+
+	if companyDetail.Status == "正式" {
+		userType, _, _, _, _ = GetUserType(companyId)
+		var condition string
+		var pars []interface{}
+
+		condition += ` AND company_id = ? `
+		pars = append(pars, companyId)
+
+		//查询当年的数据
+		condition += ` AND b.create_time >= ?  `
+		pars = append(pars, time.Now().Format("2006")+"-01-01")
+		listTripBill, e := data_manage.GetCygxActivitySpecialTripBillList(condition, pars)
+		if e != nil {
+			err = errors.New("GetActivitySpecialTripCountByActivitySpecial, Err: " + e.Error())
+			return
+		}
+		if userType == 2 {
+			//condition += ` AND is_valid = 1 `
+			//TripBillNumMap := make(map[int]int)
+			//for _, v := range listTripBill {
+			//	TripBillNumMap[v.ChartPermissionId] += v.BillDetailed
+			//}
+			var TripBillNum int
+			for _, v := range listTripBill {
+				TripBillNum += v.BillDetailed
+			}
+			// CRM13.2 70w客户有16次专项调研, 45w有10次
+			//packageTypeMap := map[int]int{1: 16, 2: 10}
+			//totalTrip := packageTypeMap[packageType]
+
+			tripRemaining = TripBillNum
+			if tripRemaining < 0 {
+				tripRemaining = 0
+			}
+
+			//for k, _ := range chartNumMap {
+			//	tripRemaining := totalTrip - ariborneMap[k] + TripBillNumMap[k]
+			//	if tripRemaining < 0 {
+			//		tripRemaining = 0
+			//	}
+			//	specialSurplus += chartMap[k] + strconv.Itoa(tripRemaining) + "次,"
+			//}
+			//specialSurplus = strings.TrimRight(specialSurplus, ",")
+
+			//tripRemaining = 12 - tripTota
+		} else {
+			list, e := GetCompanyReportPermission(companyId, 2)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				err = errors.New("GetCompanyReportPermissionUpgrade, Err: " + e.Error())
+			}
+			if len(list) == 0 {
+				return
+			}
+			var chartPermissionIdSlice []string
+			mapChartName = make(map[string]int)
+			mapUpgradeId := make(map[int]int)
+			mapInheritChartName := make(map[string]int)
+			mapPermissionNameTrip := make(map[string]int)
+			//mapPermissionName := make(map[int]string)
+			for _, v := range list {
+				chartPermissionIdSlice = append(chartPermissionIdSlice, strconv.Itoa(v.ChartPermissionId))
+				//是升级套餐才有点数
+				if v.IsUpgrade == 1 {
+					mapUpgradeId[v.ChartPermissionId] = 1
+				}
+			}
+			chartList, e := models.GetChartPermissionByIds(chartPermissionIdSlice)
+			if e != nil {
+				err = errors.New("获取品种信息失败, Err:" + e.Error())
+				return
+			}
+			if len(chartList) == 0 {
+				return
+			}
+
+			//var TripBillNum int
+			for _, v := range listTripBill {
+				if v.ActivityId == 0 {
+					continue
+				}
+				mapPermissionNameTrip[v.ChartPermissionName] += v.BillDetailed
+			}
+
+			for _, v := range chartList {
+				//如果是升级则加点
+				if _, ok := mapUpgradeId[v.ChartPermissionId]; ok {
+					mapChartName[v.PermissionName] = 5 + mapPermissionNameTrip[v.ChartPermissionName]
+				} else {
+					mapChartName[v.PermissionName] = mapPermissionNameTrip[v.ChartPermissionName]
+				}
+			}
+			// 通过继承获得的加点
+			for _, v := range inheritList {
+				mapInheritChartName[v.ChartPermissionName] = v.Points
+			}
+			for _, v := range mapChartName {
+				if v > 0 {
+					tripRemaining += v
+				}
+			}
+		}
+	}
+	return
+}

+ 40 - 0
services/cygx/company_permission.go

@@ -0,0 +1,40 @@
+package cygx
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CompanyReportPermission struct {
+	CompanyReportPermissionId int64 `orm:"column(company_report_permission_id);pk"`
+	CompanyId                 int
+	ReportPermissionId        int
+	CreatedTime               time.Time
+	LastUpdatedTime           time.Time
+	ChartPermissionId         int
+	StartDate                 string    `description:"权限开始日期"`
+	EndDate                   string    `description:"权限结束日期"`
+	ProductId                 int       `description:"产品id"`
+	ProductName               string    `description:"产品名称"`
+	CompanyContractId         int       `description:"合同id"`
+	Status                    string    `description:"'正式','试用','关闭'"`
+	ModifyTime                time.Time `description:"修改时间"`
+	IsUpgrade                 int       `description:"是否升级,1是,0否"`
+	ExpensiveYx               int       `description:"权益研选: 0-3w; 1-5w"`
+}
+
+// GetCompanyReportPermission 获取权限类型
+func GetCompanyReportPermission(companyId, productId int) (items []*CompanyReportPermission, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+	re.*
+FROM
+	company_report_permission  as re 
+	INNER JOIN chart_permission as  c ON c.chart_permission_id = re.chart_permission_id
+WHERE
+	re.company_id = ? 
+	AND re.product_id = ? 
+	AND STATUS = '正式' `
+	_, err = o.Raw(sql, companyId, productId).QueryRows(&items)
+	return
+}

+ 28 - 7
services/cygx_research_summary.go

@@ -86,7 +86,7 @@ func CygxResearchSummary(cont context.Context) (err error) {
 
 		//消费
 		{
-			newCondition := condition + ` AND category_id_two IN(62,67,1008)`
+			newCondition := condition + ` AND category_id_two IN(67,1008)`
 			list, e := data_manage.GetReportArticleList(newCondition, pars)
 			if e != nil {
 				err = e
@@ -156,9 +156,14 @@ func CygxResearchSummary(cont context.Context) (err error) {
 				ChartPermissionId: CategoryChartPermissionIdMap[article.CategoryIdTwo],
 				CreateTime:        time.Now(),
 				Body:              head + company + annotation,
-				ReportLink:        "https://vmp.hzinsights.com/v2/articles/" + strconv.Itoa(article.ArticleId),
 				LinkArticleId:     article.LinkArticleId,
 			}
+			if utils.RunMode == "debug" {
+				logItem.ReportLink = "https://clpttest.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			} else {
+				logItem.ReportLink = "https://web.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			}
+
 			logItems = append(logItems, &logItem)
 		}
 		if len(logItems) > 0 {
@@ -264,9 +269,13 @@ func CygxResearchSummary(cont context.Context) (err error) {
 				ChartPermissionId: CategoryChartPermissionIdMap[article.CategoryIdTwo],
 				CreateTime:        time.Now(),
 				Body:              head + company + annotation,
-				ReportLink:        "https://vmp.hzinsights.com/v2/articles/" + strconv.Itoa(article.ArticleId),
 				LinkArticleId:     article.LinkArticleId,
 			}
+			if utils.RunMode == "debug" {
+				logItem.ReportLink = "https://clpttest.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			} else {
+				logItem.ReportLink = "https://web.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			}
 			logItems = append(logItems, &logItem)
 		}
 		if len(logItems) > 0 {
@@ -373,9 +382,13 @@ func CygxResearchSummary(cont context.Context) (err error) {
 				ChartPermissionId: CategoryChartPermissionIdMap[article.CategoryIdTwo],
 				CreateTime:        time.Now(),
 				Body:              head + company + annotation,
-				ReportLink:        "https://vmp.hzinsights.com/v2/articles/" + strconv.Itoa(article.ArticleId),
 				LinkArticleId:     article.LinkArticleId,
 			}
+			if utils.RunMode == "debug" {
+				logItem.ReportLink = "https://clpttest.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			} else {
+				logItem.ReportLink = "https://web.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			}
 			logItems = append(logItems, &logItem)
 		}
 		if len(logItems) > 0 {
@@ -620,7 +633,7 @@ func CygxLastWeekSummary(cont context.Context) (err error) {
 
 		//消费
 		{
-			newCondition := condition + ` AND category_id_two IN(32,58)`
+			newCondition := condition + ` AND category_id_two IN(32,58,62)`
 			list, e := data_manage.GetReportArticleList(newCondition, pars)
 			if e != nil {
 				err = e
@@ -690,9 +703,13 @@ func CygxLastWeekSummary(cont context.Context) (err error) {
 				ChartPermissionId: CategoryChartPermissionIdMap[article.CategoryIdTwo],
 				CreateTime:        time.Now(),
 				Body:              head + company + annotation,
-				ReportLink:        "https://vmp.hzinsights.com/v2/articles/" + strconv.Itoa(article.ArticleId),
 				LinkArticleId:     article.LinkArticleId,
 			}
+			if utils.RunMode == "debug" {
+				logItem.ReportLink = "https://clpttest.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			} else {
+				logItem.ReportLink = "https://web.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			}
 			logItems = append(logItems, &logItem)
 		}
 		if len(logItems) > 0 {
@@ -799,9 +816,13 @@ func CygxLastWeekSummary(cont context.Context) (err error) {
 				ChartPermissionId: CategoryChartPermissionIdMap[article.CategoryIdTwo],
 				CreateTime:        time.Now(),
 				Body:              head + company + annotation,
-				ReportLink:        "https://vmp.hzinsights.com/v2/articles/" + strconv.Itoa(article.ArticleId),
 				LinkArticleId:     article.LinkArticleId,
 			}
+			if utils.RunMode == "debug" {
+				logItem.ReportLink = "https://clpttest.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			} else {
+				logItem.ReportLink = "https://web.hzinsights.com/material/info/" + strconv.Itoa(article.ArticleId)
+			}
 			logItems = append(logItems, &logItem)
 		}
 		if len(logItems) > 0 {

+ 24 - 20
services/data/edb_info_notice.go

@@ -8,7 +8,7 @@ import (
 	"time"
 )
 
-//指标更新通知
+// 指标更新通知
 func RefreshNotice() (err error) {
 	defer func() {
 		if err != nil {
@@ -19,9 +19,13 @@ func RefreshNotice() (err error) {
 	var condition string
 	var pars []interface{}
 
-	endDate := time.Now().AddDate(0, -6, 0)
+	endDate := time.Now().AddDate(0, -2, 0)
 	condition += ` AND end_date > ? `
 	pars = append(pars, endDate)
+
+	condition += ` AND end_date < ? `
+	pars = append(pars, time.Now().Format(utils.FormatDate))
+
 	items, err := data_manage.GetEdbInfoByCondition(condition, pars, 1)
 	if err != nil {
 		return errors.New("GetEdbInfoByCondition:" + err.Error())
@@ -74,20 +78,20 @@ func RefreshNotice() (err error) {
     </thead>`
 	monthTableBody := `<tbody>`
 
-	allDiv := `<hr style="color: red">`
-	allDiv = `<div style="margin-bottom: 20px;min-width: 1000px;overflow-x: scroll;"><div>当前所有指标,按数据日期升序排列:</div>`
-	allTable := `<table border="1" style="border-collapse: collapse;">
-    <thead>
-    <tr>
-        <td>指标编码</td>
-        <td>指标名称</td>
-        <td>指标来源</td>
-		<td>频度</td>
-        <td>指标数据最后日期</td>
-        <td>指标数据更新日期</td>
-    </tr>
-    </thead>`
-	allTableBody := `<tbody>`
+	//allDiv := `<hr style="color: red">`
+	//allDiv = `<div style="margin-bottom: 20px;min-width: 1000px;overflow-x: scroll;"><div>当前所有指标,按数据日期升序排列:</div>`
+	//allTable := `<table border="1" style="border-collapse: collapse;">
+	//<thead>
+	//<tr>
+	//    <td>指标编码</td>
+	//    <td>指标名称</td>
+	//    <td>指标来源</td>
+	//	<td>频度</td>
+	//    <td>指标数据最后日期</td>
+	//    <td>指标数据更新日期</td>
+	//</tr>
+	//</thead>`
+	//allTableBody := `<tbody>`
 
 	dayDate := time.Now().AddDate(0, 0, -3)
 	weekDate := time.Now().AddDate(0, 0, -7)
@@ -107,7 +111,7 @@ func RefreshNotice() (err error) {
 			monthTableBody += getTableTr(v, 3)
 		}
 
-		allTableBody += getTableTr(v, 4)
+		//allTableBody += getTableTr(v, 4)
 	}
 	dayTable += dayTableBody
 	dayTable += ` </tbody></table></div>`
@@ -124,9 +128,9 @@ func RefreshNotice() (err error) {
 	noticeSendBody += monthDiv + monthTable
 	noticeSendBody += `<br/>`
 
-	allTable += allTableBody
-	allTable += ` </tbody></table></div>`
-	noticeSendBody += allDiv + allTable
+	//allTable += allTableBody
+	//allTable += ` </tbody></table></div>`
+	//noticeSendBody += allDiv + allTable
 
 	go utils.SendEmailByHongze("指标更新情况通知", noticeSendBody, utils.RefreshEdbInfoEmailSendToUsers, "", "")
 	//utils.SendEmailByHongze("指标更新情况通知", noticeSendBody, "317699326@qq.com", "", "")

+ 274 - 57
services/data/trade_position_analysis.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"hongze/hongze_task/models/data_manage"
+	"hongze/hongze_task/services/alarm_msg"
 	"hongze/hongze_task/utils"
 	"sort"
 	"strconv"
@@ -12,32 +13,101 @@ import (
 
 // InitPositionTask 统计今日交易所的持仓分析数据
 func InitPositionTask(cont context.Context) (err error) {
-	exchanges := []string{"zhengzhou","dalian","shanghai","cffex","ine"} //郑商所,大商所,上期所,中金所,上期能源
-	startDate := time.Now().Format(utils.FormatDate)
-	endDate := startDate
-	for _, v := range exchanges {
-		exchange := v
-		err = nil
-		fmt.Println("InitPositionTask:	启动:"+exchange)
-		utils.FileLog.Info("InitPositionTask:	启动:"+exchange)
-
-		fmt.Println("开始"+startDate+"结束"+endDate)
-		utils.FileLog.Info(fmt.Sprintf("InitTradePosition:开始:%s; 结束:%s", startDate, endDate))
-		tErr, errMsg := InitTradePosition(exchange, startDate, endDate)
-		if tErr != nil {
-			err = tErr
-			fmt.Println("InitTradePosition: 操作失败:"+errMsg+tErr.Error())
-			utils.FileLog.Info(fmt.Sprintf("InitTradePosition: 操作失败:%s:%s", errMsg, tErr.Error()))
-			continue
+	exchanges := []string{"zhengzhou", "dalian", "shanghai", "cffex", "ine"} //郑商所,大商所,上期所,中金所,上期能源
+	for i := 1; i >= 0; i-- {
+		startDate := time.Now().AddDate(0, 0, -i).Format(utils.FormatDate)
+		endDate := startDate
+		for _, v := range exchanges {
+			exchange := v
+			err = nil
+			fmt.Println("InitPositionTask:	启动:" + exchange)
+			utils.FileLog.Info("InitPositionTask:	启动:" + exchange)
+
+			fmt.Println("开始" + startDate + "结束" + endDate)
+			utils.FileLog.Info(fmt.Sprintf("InitTradePosition:开始:%s; 结束:%s", startDate, endDate))
+			tErr, errMsg := InitTradePosition(exchange, startDate, endDate)
+			if tErr != nil {
+				err = tErr
+				fmt.Println("InitTradePosition: 操作失败:" + errMsg + tErr.Error())
+				utils.FileLog.Info(fmt.Sprintf("InitTradePosition: 操作失败:%s:%s", errMsg, tErr.Error()))
+				continue
+			}
+
+			fmt.Println("InitTradePosition:" + exchange + "已完成")
+			utils.FileLog.Info("InitTradePosition:" + exchange + "已完成")
+		}
+	}
+
+	// 处理交易所的分类
+	{
+		allBaseFromTradeClassify, tmpErr := data_manage.GetAllBaseFromTradeClassify()
+		if tmpErr != nil {
+			utils.FileLog.Info(fmt.Sprintf("获取所有交易所分类失败,;err:%s", tmpErr.Error()))
+			return
+		}
+		tradeClassifyMap := make(map[string]*data_manage.BaseFromTradeClassify)
+		for _, v := range allBaseFromTradeClassify {
+			key := fmt.Sprintf("%s_%s_%s", v.Exchange, v.ClassifyName, v.ClassifyType)
+			tradeClassifyMap[key] = v
+		}
+
+		baseFromTradeClassifyList := make([]*data_manage.BaseFromTradeClassify, 0)
+		for _, v := range exchanges {
+			tradeClassifyNameList, tmpErr := data_manage.GetExchangeClassify(v)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取%s分类失败,;err:%s", v, tmpErr.Error()))
+				continue
+			}
+
+			for _, classify := range tradeClassifyNameList {
+				key := fmt.Sprintf("%s_%s_%s", v, classify.ClassifyName, classify.ClassifyType)
+
+				if tradeClassify, ok := tradeClassifyMap[key]; !ok {
+					baseFromTradeClassifyList = append(baseFromTradeClassifyList, &data_manage.BaseFromTradeClassify{
+						Id:           0,
+						ClassifyName: classify.ClassifyName,
+						ClassifyType: classify.ClassifyType,
+						Exchange:     v,
+						LatestDate:   classify.DataTime,
+						CreateTime:   time.Now(),
+						ModifyTime:   classify.ModifyTime,
+					})
+				} else {
+					if tradeClassify.LatestDate.Before(classify.DataTime) {
+						tradeClassify.LatestDate = classify.DataTime
+						tradeClassify.ModifyTime = classify.ModifyTime
+						tradeClassify.Update([]string{"LatestDate", "ModifyTime"})
+					}
+				}
+			}
 		}
 
-		fmt.Println("InitTradePosition:"+exchange+"已完成")
-		utils.FileLog.Info("InitTradePosition:"+exchange+"已完成")
+		lenAddList := len(baseFromTradeClassifyList)
+		if lenAddList > 0 {
+			baseAddNum := 500
+			num := lenAddList / baseAddNum
+			lastNum := lenAddList % baseAddNum
+			for i := 0; i <= num; i++ {
+				tmpNum := baseAddNum
+				if i == num && lastNum > 0 {
+					tmpNum = lastNum
+				}
+				data_manage.MultiAddBaseFromTradeClassify(baseFromTradeClassifyList[i*baseAddNum : (i*baseAddNum + tmpNum)])
+			}
+		}
 	}
+
 	return
 }
 
 func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("统计今日交易所的持仓分析数据失败, Exchange: %s, Err: %s, Msg: %s", exchange, err.Error(), errMsg)
+			alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
 	// 批量插入今日的初始值
 	num, err := data_manage.GetTradePositionTopCountByExchangeDataTime(exchange, startDate, endDate)
 	if err != nil {
@@ -45,8 +115,32 @@ func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg s
 		return
 	}
 	if num > 0 {
-		err = fmt.Errorf("数据已存在,无需处理")
-		return
+		//err = fmt.Errorf("数据已存在,无需处理")
+		//数据存在不同步的情况,有些合约会提早更新,有些合约会延迟更新
+		//判断合约数是否一致
+
+		originNum, tmpErr := data_manage.GetTradePositionOriginClassifyCountByExchangeDataTime(exchange, startDate, endDate)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "查询原始数据分类个数失败,GetTradePositionOriginClassifyCountByExchangeDataTime() Err: "
+			return
+		}
+		topNum, tmpErr := data_manage.GetTradePositionTopClassifyCountByExchangeDataTime(exchange, startDate, endDate)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "查询榜单数据分类个数失败,GetTradePositionTopClassifyCountByExchangeDataTime() Err: "
+			return
+		}
+		if originNum == topNum {
+			//err = fmt.Errorf("数据已存在,无需处理")
+			return
+		}
+		//如果合约数不一致,则删除今日数据
+		err = data_manage.DeleteTradePositionTopAllByExchangeDataTime(exchange, startDate, endDate)
+		if err != nil {
+			errMsg = "删除榜单数据失败,DeleteTradePositionTopAllByExchangeDataTime() Err: "
+			return
+		}
 	}
 	err = data_manage.MultiInsertTradeBaseDataToTop(exchange, startDate, endDate)
 	if err != nil {
@@ -59,10 +153,26 @@ func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg s
 		return
 	}
 	if len(originList) <= 0 {
+		// 忽略周末
+		w := time.Now().Weekday().String()
+		if w == "Saturday" || w == "Sunday" {
+			return
+		}
+		// 每天最后一个小时执行依旧无数据时, 才进行邮件提示
+		if time.Now().Hour() != 23 {
+			return
+		}
 		err = fmt.Errorf("原始数据没有值")
 		return
 	}
 
+	// 原始数据日期
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
+		return
+	}
+
 	now := time.Now()
 	dataTimeMap := make(map[string]*data_manage.TradePositionTop)
 	onlyEmptyMap := make(map[string]bool)
@@ -71,7 +181,7 @@ func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg s
 	topLastRankMap := make(map[string]int)
 	list := make([]*data_manage.TradePositionTop, 0)
 	for _, v := range originList {
-		tmp0, tmpErr := dealTradeOriginData(dataTimeMap, onlyEmptyMap, onlyEmptyNameMap, v, topLastMap, topLastRankMap, startDate, now)
+		tmp0, tmpErr := dealTradeOriginData(dataTimeMap, onlyEmptyMap, onlyEmptyNameMap, v, topLastMap, topLastRankMap, startDate, now, dates)
 		if tmpErr != nil {
 			err = tmpErr
 			errMsg = "处理原始数据失败 dealTradeOriginData() Err: "
@@ -153,7 +263,7 @@ func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg s
 		errMsg = "创建净多单,净空单数据失败,createAnalysisCleanTop() Err: "
 		return
 	}
-	// 特殊处理起始日期前一天的数据
+
 	err = DealYesterdayData(exchange, startDate)
 	if err != nil {
 		errMsg = "处理昨日数据失败,DealYesterdayData() Err: "
@@ -162,7 +272,7 @@ func InitTradePosition(exchange, startDate, endDate string) (err error, errMsg s
 	return
 }
 
-func dealTradeOriginData(dataTimeMap map[string]*data_manage.TradePositionTop, onlyEmptyMap map[string]bool, onlyEmptyNameMap map[string]*data_manage.TradePositionTop, currentItem *data_manage.TradePositionTop, topLastMap map[string]int, topLastRankMap map[string]int, startDate string, now time.Time) (tmp0 *data_manage.TradePositionTop, err error) {
+func dealTradeOriginData(dataTimeMap map[string]*data_manage.TradePositionTop, onlyEmptyMap map[string]bool, onlyEmptyNameMap map[string]*data_manage.TradePositionTop, currentItem *data_manage.TradePositionTop, topLastMap map[string]int, topLastRankMap map[string]int, startDate string, now time.Time, dates []string) (tmp0 *data_manage.TradePositionTop, err error) {
 	classifyName := currentItem.ClassifyName
 	classifyType := currentItem.ClassifyType
 	dealShortName := currentItem.DealShortName
@@ -179,11 +289,12 @@ func dealTradeOriginData(dataTimeMap map[string]*data_manage.TradePositionTop, o
 		topLastRankMap[classifyName+"_"+classifyType+"_"+dataTime+"_"+dealTypeStr] = currentItem.Rank
 	}
 	if dataTime > startDate {
-		tmpTimeStr, tErr := getYesterdayDate(dataTime)
-		if tErr != nil {
-			err = tErr
-			return
-		}
+		//tmpTimeStr, tErr := getYesterdayDate(dataTime)
+		//if tErr != nil {
+		//	err = tErr
+		//	return
+		//}
+		tmpTimeStr := getPrevTradeDataDate(dataTime, dates)
 		if tmpTimeStr < startDate {
 			return
 		}
@@ -191,7 +302,8 @@ func dealTradeOriginData(dataTimeMap map[string]*data_manage.TradePositionTop, o
 		if _, ok := dataTimeMap[classifyName+"_"+classifyType+"_"+dealTypeStr+"_"+dealShortName+"_"+tmpTimeStr]; !ok {
 			yesterdayVal := dealValue - dealChange
 			yesterdayChange := 0
-			beforeYesterday, _ := getYesterdayDate(tmpTimeStr)
+			//beforeYesterday, _ := getYesterdayDate(tmpTimeStr)
+			beforeYesterday := getPrevTradeDataDate(tmpTimeStr, dates)
 			beforeYesterdayItem, ok1 := dataTimeMap[classifyName+"_"+classifyType+"_"+dealTypeStr+"_"+dealShortName+"_"+beforeYesterday]
 			if ok1 {
 				yesterdayChange = yesterdayVal - beforeYesterdayItem.DealValue
@@ -216,7 +328,7 @@ func dealTradeOriginData(dataTimeMap map[string]*data_manage.TradePositionTop, o
 	return
 }
 
-// 更新昨日数据
+// DealYesterdayData 更新昨日数据
 func DealYesterdayData(exchange, startDate string) (err error) {
 	// 查询最早的日期
 	firstItem, err := data_manage.GetFirstBaseFromTradeIndexByDate(exchange)
@@ -227,15 +339,24 @@ func DealYesterdayData(exchange, startDate string) (err error) {
 		return
 	}
 
-	yesterdayStr, err := getYesterdayDate(startDate)
-	if err != nil {
-		return
-	}
-	//查找前日的值,并更新对应的更改
-	beforeYesterdayStr, err := getYesterdayDate(yesterdayStr)
-	if err != nil {
+	// 前一个交易日, 前两个交易日
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
 		return
 	}
+	yesterdayStr := getPrevTradeDataDate(startDate, dates)
+	beforeYesterdayStr := getPrevTradeDataDate(yesterdayStr, dates)
+
+	//yesterdayStr, err := getYesterdayDate(startDate)
+	//if err != nil {
+	//	return
+	//}
+	////查找前日的值,并更新对应的更改
+	//beforeYesterdayStr, err := getYesterdayDate(yesterdayStr)
+	//if err != nil {
+	//	return
+	//}
 	// 先查出T日最原始的数据
 	originList, err := data_manage.GetTradePositionTopByExchangeDataTime(exchange, startDate, startDate)
 	if err != nil {
@@ -345,6 +466,12 @@ func DealYesterdayData(exchange, startDate string) (err error) {
 		if err != nil {
 			return
 		}
+
+		//T-1日重新生成净多单和净空单的榜单后,需要更新T日净多单和净空单榜单里的变化量
+		err = updateAnalysisCleanTopChangeVal(exchange, startDate, yesterdayStr)
+		if err != nil {
+			return
+		}
 	}
 
 	return
@@ -352,17 +479,34 @@ func DealYesterdayData(exchange, startDate string) (err error) {
 
 // createAnalysisCleanTop 生成净多单,净空单榜单
 func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("createAnalysisCleanTop err: " + err.Error())
+		}
+	}()
+
 	topList := make([]*data_manage.TradePositionTop, 0)
 	now := time.Now()
 	var subDataList data_manage.TradePositionSubList
 
 	subChangeMap1 := make(map[string]int) //净多单map
 	subChangeMap2 := make(map[string]int) //净空单map
-	//查询所有差值数据,
-	yesterday, err := getYesterdayDate(startDate)
-	if err != nil {
+
+	// 2023-05-10 此处取前一个交易日, 不一定是昨日
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
 		return
 	}
+	yesterday := getPrevTradeDataDate(startDate, dates)
+
+	//查询所有差值数据,
+	//yesterday, err := getYesterdayDate(startDate)
+	//if err != nil {
+	//	return
+	//}
+
+	// 上一个交易日的净多单
 	yesterdayTopList1, tErr := data_manage.GetTradePositionTopByExchangeDataTimeType(exchange, yesterday, 3)
 	if tErr != nil {
 		err = tErr
@@ -375,6 +519,7 @@ func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
 		}
 	}
 
+	// 上一个交易日的净空单
 	yesterdayTopList2, tErr := data_manage.GetTradePositionTopByExchangeDataTimeType(exchange, yesterday, 4)
 	if tErr != nil {
 		err = tErr
@@ -387,6 +532,7 @@ func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
 		}
 	}
 
+	// 根据当日多单/空单数据, 生成净多单/净空单数据
 	originDataList, err := data_manage.GetTradePositionTopByExchangeDataTime(exchange, startDate, endDate)
 	if err != nil {
 		return
@@ -422,6 +568,8 @@ func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
 	if len(subDataList) > 0 {
 		sort.Sort(subDataList)
 	}
+
+	// 根据净多单/净空单数据, 比对上一个交易日的日期计算成交变化量, 并写入
 	var dealType int
 	rankMap := make(map[string]int)
 	for _, v := range subDataList {
@@ -444,12 +592,16 @@ func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
 				rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_4"]++
 			}
 		}
+
+		// 2023-05-10 目前看该方法的引用startDate和endDate其实是同一天, 所以前一个交易日直接用上面的yesterday
+		tmpTimeStr := yesterday
+
 		//和T-1日比较差值
-		var tmpTimeStr string
-		tmpTimeStr, err = getYesterdayDate(v.DataTime)
-		if err != nil {
-			return
-		}
+		//var tmpTimeStr string
+		//tmpTimeStr, err = getYesterdayDate(v.DataTime)
+		//if err != nil {
+		//	return
+		//}
 		yesterdayStr := v.ClassifyName + "_" + v.ClassifyType + "_" + tmpTimeStr + "_" + v.DealShortName
 		dealChange := 0
 		if dealType == 3 {
@@ -492,20 +644,85 @@ func createAnalysisCleanTop(exchange, startDate, endDate string) (err error) {
 	return
 }
 
-func getYesterdayDate(today string) (yesterday string, err error) {
-	i := 1
-	tmpTime, err := time.ParseInLocation(utils.FormatDate, today, time.Local)
+// getPrevTradeDataDate 获取指定日期上一个交易日日期
+func getPrevTradeDataDate(date string, dates []string) string {
+	pre := -1
+	for k, v := range dates {
+		n := k - 1
+		if v == date && n >= 0 {
+			pre = n
+			break
+		}
+	}
+	// 找不到就随便给个不存在日期
+	if pre == -1 {
+		return "1980-01-01"
+	}
+	return dates[pre]
+}
+
+// updateAnalysisCleanTopChangeVal T-1日重新生成净多单和净空单的榜单后,需要更新T日净多单和净空单榜单里的变化量
+func updateAnalysisCleanTopChangeVal(exchange, startDate, yesterday string) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("updateAnalysisCleanTopChangeVal err: " + err.Error())
+		}
+	}()
+
+	//查询T日的净多单和净空单榜单列表
+	//查询T-1日的净多单和净空单列表
+	//组装数据,计算T日与T-1日的变更值
+	//更新变更值
+	topList := make([]*data_manage.TradePositionTop, 0)      //T日和T+1日列表
+	todayTopList := make([]*data_manage.TradePositionTop, 0) //T日列表
+
+	yesterdayTopListMap := make(map[string]int) //净多单净空单持仓量map
+
+	// 查询T日和T-1日的净多单和净空单列表
+	topList, err = data_manage.GetTradePositionTopCleanByExchangeDataTime(exchange, yesterday, startDate)
 	if err != nil {
 		return
 	}
-	tmpTimeDate := tmpTime.AddDate(0, 0, -i)
-	weekStr := tmpTimeDate.Weekday().String()
-	if weekStr == "Sunday" {
-		i += 2
-	} else if weekStr == "Saturday" {
-		i += 1
+	if len(topList) == 0 {
+		return
+	}
+	for _, v := range topList {
+		if v.DataTime == startDate {
+			todayTopList = append(todayTopList, v)
+		} else if v.DataTime == yesterday {
+			nameStr := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName + "_" + strconv.Itoa(v.DealType)
+			yesterdayTopListMap[nameStr] = v.DealValue
+		}
+	}
+
+	if len(todayTopList) == 0 {
+		return
+	}
+	// 根据净多单/净空单数据, 比对上一个交易日的日期计算成交变化量, 并写入
+	now := time.Now()
+	updateList := make([]data_manage.UpdateChangeVal, 0)
+	for _, v := range todayTopList {
+		//T日值-T-1日值
+		yesterdayStr := v.ClassifyName + "_" + v.ClassifyType + "_" + yesterday + "_" + v.DealShortName + "_" + strconv.Itoa(v.DealType)
+		dealChange := 0
+		if c, ok := yesterdayTopListMap[yesterdayStr]; ok {
+			dealChange = v.DealValue - c
+		}
+		if dealChange != v.DealChange {
+			tmp := data_manage.UpdateChangeVal{
+				Id:         v.Id,
+				ModifyTime: now,
+				DealChange: dealChange,
+			}
+			updateList = append(updateList, tmp)
+		}
+	}
+
+	if len(updateList) > 0 {
+		err = data_manage.MultiUpdatePositionTopChangeVal(exchange, updateList)
+		if err != nil {
+			return
+		}
 	}
-	tmpTimeDate = tmpTime.AddDate(0, 0, -i)
-	yesterday = tmpTimeDate.Format(utils.FormatDate)
 	return
 }

+ 592 - 0
services/data/trade_position_analysis_classify.go

@@ -0,0 +1,592 @@
+package data
+
+import (
+	"fmt"
+	"hongze/hongze_task/models/data_manage"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"sort"
+	"strconv"
+	"time"
+)
+
+// FixPositionTask 补全缺失的合约, 注意和原先的定时任务区分开来
+func FixPositionTask() (err error) {
+	exchanges := []string{"zhengzhou", "dalian", "shanghai", "cffex", "ine"} //郑商所,大商所,上期所,中金所,上期能源
+	for i := 194; i > 1; i-- {
+		// 定时任务避开昨日和今日的数据,以免和原先的定时任务InitPositionTask冲突
+		startDate := time.Now().AddDate(0, 0, -i).Format(utils.FormatDate)
+		endDate := startDate
+		for _, v := range exchanges {
+			exchange := v
+			err = nil
+			fmt.Println("FixPositionTask:	启动:" + exchange)
+			utils.FileLog.Info("FixPositionTask:	启动:" + exchange)
+
+			fmt.Println("开始" + startDate + "结束" + endDate)
+			utils.FileLog.Info(fmt.Sprintf("FixPositionTask:开始:%s; 结束:%s", startDate, endDate))
+			tErr, errMsg := InitTradePositionClassify(exchange, startDate, endDate)
+			if tErr != nil {
+				err = tErr
+				fmt.Println("FixPositionTask: 操作失败:" + errMsg + tErr.Error())
+				utils.FileLog.Info(fmt.Sprintf("InitTradePosition: 操作失败:%s:%s", errMsg, tErr.Error()))
+				continue
+			}
+
+			fmt.Println("FixPositionTask:" + exchange + "已完成")
+			utils.FileLog.Info("FixPositionTask:" + exchange + "已完成")
+		}
+	}
+	return
+}
+
+// InitTradePositionClassify 持仓分析补全缺失的合约
+func InitTradePositionClassify(exchange, startDate, endDate string) (err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("持仓分析-补全缺失的品种数据 操作失败, Exchange: %s, Err: %s, Msg: %s", exchange, err.Error(), errMsg)
+			alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
+	//判断合约数与数据源是否一致
+	originClassifyList, tmpErr := data_manage.GetTradePositionOriginClassifyByExchangeDataTime(exchange, startDate, endDate)
+	if tmpErr != nil {
+		err = tmpErr
+		errMsg = "查询原始数据分类个数失败,GetTradePositionOriginClassifyCountByExchangeDataTime() Err: "
+		return
+	}
+	topClassifyList, tmpErr := data_manage.GetTradePositionTopClassifyByExchangeDataTime(exchange, startDate, endDate)
+	if tmpErr != nil {
+		err = tmpErr
+		errMsg = "查询榜单数据分类个数失败,GetTradePositionTopClassifyCountByExchangeDataTime() Err: "
+		return
+	}
+
+	if len(originClassifyList) == len(topClassifyList) {
+		//合约数目一致,无需补全
+		return
+	}
+	if len(originClassifyList) == 0 {
+		return
+	}
+	topClassifyMap := make(map[string][]data_manage.TradePositionClassifyInfo)
+	for _, v := range topClassifyList {
+		str := fmt.Sprintf("%s_%s", v.ClassifyName, v.ClassifyType)
+		topClassifyMap[str] = append(topClassifyMap[str], v)
+	}
+	classifyMap := make(map[string]struct{})
+	classifyNames := make([]string, 0)
+	classifyTypes := make([]string, 0)
+	for _, v := range originClassifyList {
+		str := fmt.Sprintf("%s_%s", v.ClassifyName, v.ClassifyType)
+		if _, ok := topClassifyMap[str]; !ok {
+			classifyTypes = append(classifyTypes, v.ClassifyType)
+			if _, ok1 := classifyMap[v.ClassifyName]; !ok1 {
+				classifyNames = append(classifyNames, v.ClassifyName)
+				classifyMap[v.ClassifyName] = struct{}{}
+			}
+		}
+	}
+
+	if len(classifyTypes) == 0 {
+		return
+	}
+
+	//批量导入缺失的合约
+	err = data_manage.MultiInsertTradeBaseDataToTopByClassify(exchange, startDate, endDate, classifyNames, classifyTypes)
+	if err != nil {
+		errMsg = "新增原始数据失败,MultiInsertTradeBaseDataToTop() Err: "
+		return
+	}
+	originList, err := data_manage.GetTradePositionTopByExchangeDataTimeByClassify(exchange, startDate, endDate, classifyNames, classifyTypes)
+	if err != nil {
+		errMsg = "查询原始数据失败, GetTradePositionTopByExchangeDataTime() Err: "
+		return
+	}
+	if len(originList) <= 0 {
+		// 忽略周末
+		w := time.Now().Weekday().String()
+		if w == "Saturday" || w == "Sunday" {
+			return
+		}
+		// 每天最后一个小时执行依旧无数据时, 才进行邮件提示
+		if time.Now().Hour() != 23 {
+			return
+		}
+		err = fmt.Errorf("原始数据没有值")
+		return
+	}
+
+	// 原始数据日期
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
+		return
+	}
+
+	now := time.Now()
+	dataTimeMap := make(map[string]*data_manage.TradePositionTop)
+	onlyEmptyMap := make(map[string]bool)
+	onlyEmptyNameMap := make(map[string]*data_manage.TradePositionTop)
+	topLastMap := make(map[string]int)
+	topLastRankMap := make(map[string]int)
+	list := make([]*data_manage.TradePositionTop, 0)
+	for _, v := range originList {
+		tmp0, tmpErr := dealTradeOriginData(dataTimeMap, onlyEmptyMap, onlyEmptyNameMap, v, topLastMap, topLastRankMap, startDate, now, dates)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "处理原始数据失败 dealTradeOriginData() Err: "
+			return
+		}
+		if tmp0 != nil {
+			list = append(list, tmp0)
+		}
+		if len(list) >= 1000 {
+			err = data_manage.InsertMultiTradePositionTop(exchange, list)
+			if err != nil {
+				errMsg = "批量新增昨日数据失败,InsertMultiTradePositionTop() Err: "
+				return
+			}
+			list = make([]*data_manage.TradePositionTop, 0)
+		}
+	}
+	if len(list) > 0 {
+		err = data_manage.InsertMultiTradePositionTop(exchange, list)
+		if err != nil {
+			errMsg = "批量新增昨日数据失败,InsertMultiTradePositionTop() Err: "
+			return
+		}
+		list = make([]*data_manage.TradePositionTop, 0)
+	}
+	// 处理某个期货公司只有买单没有卖单,或者只有卖单没有买单的情况
+	for k, v := range onlyEmptyNameMap {
+		_, ok1 := onlyEmptyMap[k+"_1"]
+		_, ok2 := onlyEmptyMap[k+"_2"]
+		var dealType int
+		if ok1 && !ok2 {
+			dealType = 2 //只有买单没有卖单
+		} else if !ok1 && ok2 {
+			dealType = 1 //只有卖单没有买单的情况
+		} else {
+			continue
+		}
+		if dealType > 0 {
+			str := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + strconv.Itoa(dealType)
+			dealValue := 0
+			if lastVal, ok := topLastMap[str]; ok {
+				dealValue = int(float64(lastVal)*0.7 + 0.5)
+			}
+
+			tmp := &data_manage.TradePositionTop{
+				ClassifyName:  v.ClassifyName,
+				ClassifyType:  v.ClassifyType,
+				DealShortName: v.DealShortName,
+				DataTime:      v.DataTime,
+				DealValue:     dealValue,
+				CreateTime:    now,
+				ModifyTime:    now,
+				DealType:      dealType,
+				SourceType:    2,
+			}
+			list = append(list, tmp)
+			if len(list) >= 1000 {
+				err = data_manage.InsertMultiTradePositionTop(exchange, list)
+				if err != nil {
+					errMsg = "批量新增前日数据失败,InsertMultiTradePositionTop() Err: "
+					return
+				}
+				list = make([]*data_manage.TradePositionTop, 0)
+			}
+		}
+	}
+
+	if len(list) > 0 {
+		err = data_manage.InsertMultiTradePositionTop(exchange, list)
+		if err != nil {
+			errMsg = "批量新增前日数据失败,InsertMultiTradePositionTop() Err: "
+			return
+		}
+	}
+
+	//生成净多单,净空单榜单
+	err = createAnalysisCleanTopClassify(exchange, startDate, endDate, classifyNames, classifyTypes)
+	if err != nil {
+		errMsg = "创建净多单,净空单数据失败,createAnalysisCleanTop() Err: "
+		return
+	}
+
+	err = DealYesterdayDataClassify(exchange, startDate, classifyNames, classifyTypes)
+	if err != nil {
+		errMsg = "处理昨日数据失败,DealYesterdayData() Err: "
+		return
+	}
+	return
+}
+
+// DealYesterdayDataClassify 更新部分合约的昨日数据
+func DealYesterdayDataClassify(exchange, startDate string, classifyNames, classifyTypes []string) (err error) {
+	// 查询最早的日期
+	firstItem, err := data_manage.GetFirstBaseFromTradeIndexByDate(exchange)
+	if err != nil {
+		return
+	}
+	if startDate == firstItem.DataTime { //如果当前是起始日,则无需统计修改前一天的数据
+		return
+	}
+
+	// 前一个交易日, 前两个交易日
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
+		return
+	}
+	yesterdayStr := getPrevTradeDataDate(startDate, dates)
+	beforeYesterdayStr := getPrevTradeDataDate(yesterdayStr, dates)
+
+	// 先查出T日最原始的数据
+	originList, err := data_manage.GetTradePositionTopByExchangeDataTimeByClassify(exchange, startDate, startDate, classifyNames, classifyTypes)
+	if err != nil {
+		return
+	}
+	originBuyMap := make(map[string]*data_manage.TradePositionTop)
+	originSoldMap := make(map[string]*data_manage.TradePositionTop)
+	for _, v := range originList {
+		if v.SourceType != 0 {
+			continue
+		}
+		str := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DealShortName
+		if v.DealType == 1 {
+			originBuyMap[str] = v
+		} else if v.DealType == 2 {
+			originSoldMap[str] = v
+		}
+	}
+
+	// 然后查询T-1中数据来源类型是2的数据
+	changeList, err := data_manage.GetTradePositionTopByExchangeSourceTypeClassify(exchange, yesterdayStr, 2, classifyNames, classifyTypes)
+	if err != nil {
+		return
+	}
+	if len(changeList) <= 0 {
+		//err = fmt.Errorf("前天的数据无需修改")
+		return
+	}
+	// 查询出前日的成交量
+	beforeYesterdayList, err := data_manage.GetTradePositionTopByExchangeDataTimeByClassify(exchange, beforeYesterdayStr, beforeYesterdayStr, classifyNames, classifyTypes)
+	if err != nil {
+		return
+	}
+	beforeYesterdayMap1 := make(map[string]int)
+	beforeYesterdayMap2 := make(map[string]int)
+	if len(beforeYesterdayList) > 0 {
+		for _, v := range beforeYesterdayList {
+			if v.SourceType == 2 {
+				continue
+			}
+			str := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DealShortName
+			if v.DealType == 1 {
+				beforeYesterdayMap1[str] = v.DealValue
+			} else if v.DealType == 2 {
+				beforeYesterdayMap2[str] = v.DealValue
+			}
+		}
+	}
+	// 根据原始数据中的值推算出最新的值
+	now := time.Now()
+	// 批量更新到分析表中,
+	var updateAnalysisData []data_manage.UpdateDealValueChange
+	for _, v := range changeList {
+		str := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DealShortName
+		dealValue := 0
+		dealChange := 0
+		if v.DealType == 1 {
+			if n, ok := originBuyMap[str]; ok {
+				dealValue = n.DealValue - n.DealChange
+				if beforeVal, ok1 := beforeYesterdayMap1[str]; ok1 {
+					dealChange = dealValue - beforeVal
+				}
+				tmp := data_manage.UpdateDealValueChange{
+					Id:         v.Id,
+					DealValue:  dealValue,
+					DealChange: dealChange,
+					SourceType: 1,
+					ModifyTime: now,
+				}
+				updateAnalysisData = append(updateAnalysisData, tmp)
+			}
+		} else if v.DealType == 2 {
+			if n, ok := originSoldMap[str]; ok {
+				dealValue = n.DealValue - n.DealChange
+				if beforeVal, ok1 := beforeYesterdayMap2[str]; ok1 {
+					dealChange = dealValue - beforeVal
+				}
+				tmp := data_manage.UpdateDealValueChange{
+					Id:         v.Id,
+					DealValue:  dealValue,
+					DealChange: dealChange,
+					SourceType: 1,
+					ModifyTime: now,
+				}
+				updateAnalysisData = append(updateAnalysisData, tmp)
+			}
+		}
+	}
+	if len(updateAnalysisData) > 0 {
+		err = data_manage.MultiUpdatePositionTop(exchange, updateAnalysisData)
+		if err != nil {
+			return
+		}
+		//删除T-1日净多单和净空单的榜单
+		err = data_manage.DeletePositionTopByDataTimeClassify(exchange, yesterdayStr, 3, classifyNames, classifyTypes)
+		if err != nil {
+			return
+		}
+
+		err = data_manage.DeletePositionTopByDataTimeClassify(exchange, yesterdayStr, 4, classifyNames, classifyTypes)
+		if err != nil {
+			return
+		}
+
+		//重新生成净多单和净空单的榜单
+		err = createAnalysisCleanTopClassify(exchange, yesterdayStr, yesterdayStr, classifyNames, classifyTypes)
+		if err != nil {
+			return
+		}
+
+		//T-1日重新生成净多单和净空单的榜单后,需要更新T日净多单和净空单榜单里的变化量
+		err = updateAnalysisCleanTopChangeValClassify(exchange, startDate, yesterdayStr, classifyNames, classifyTypes)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// createAnalysisCleanTopClassify 生成部分合约的净多单,净空单榜单
+func createAnalysisCleanTopClassify(exchange, startDate, endDate string, classifyNames, classifyTypes []string) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("createAnalysisCleanTop err: " + err.Error())
+		}
+	}()
+
+	topList := make([]*data_manage.TradePositionTop, 0)
+	now := time.Now()
+	var subDataList data_manage.TradePositionSubList
+
+	subChangeMap1 := make(map[string]int) //净多单map
+	subChangeMap2 := make(map[string]int) //净空单map
+
+	// 2023-05-10 此处取前一个交易日, 不一定是昨日
+	dates, e := data_manage.GetTradePositionTopOriginDataTimes(exchange)
+	if e != nil {
+		err = fmt.Errorf("GetTradePositionTopOriginDataTimes err: %s", e.Error())
+		return
+	}
+	yesterday := getPrevTradeDataDate(startDate, dates)
+
+	// 上一个交易日的净多单
+	yesterdayTopList1, tErr := data_manage.GetTradePositionTopByExchangeDataTimeTypeClassify(exchange, yesterday, 3, classifyNames, classifyTypes)
+	if tErr != nil {
+		err = tErr
+		return
+	}
+	if len(yesterdayTopList1) > 0 {
+		for _, v := range yesterdayTopList1 {
+			nameStr := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName
+			subChangeMap1[nameStr] = v.DealValue
+		}
+	}
+
+	// 上一个交易日的净空单
+	yesterdayTopList2, tErr := data_manage.GetTradePositionTopByExchangeDataTimeTypeClassify(exchange, yesterday, 4, classifyNames, classifyTypes)
+	if tErr != nil {
+		err = tErr
+		return
+	}
+	if len(yesterdayTopList2) > 0 {
+		for _, v := range yesterdayTopList2 {
+			nameStr := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName
+			subChangeMap2[nameStr] = v.DealValue
+		}
+	}
+
+	// 根据当日多单/空单数据, 生成净多单/净空单数据
+	originDataList, err := data_manage.GetTradePositionTopByExchangeDataTimeByClassify(exchange, startDate, endDate, classifyNames, classifyTypes)
+	if err != nil {
+		return
+	}
+	buyDataMap := make(map[string]int)
+	for _, v := range originDataList {
+		str := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName
+		if v.DealType == 1 {
+			buyDataMap[str] = v.DealValue
+		} else if v.DealType == 2 {
+			subValue := 0
+			dealType := 0
+			if buy, ok := buyDataMap[str]; ok {
+				subValue = buy - v.DealValue
+				if subValue >= 0 {
+					dealType = 3
+				} else {
+					subValue = -subValue
+					dealType = 4
+				}
+			}
+			tmp := &data_manage.TradePositionSub{
+				ClassifyName:  v.ClassifyName,
+				ClassifyType:  v.ClassifyType,
+				DataTime:      v.DataTime,
+				DealShortName: v.DealShortName,
+				SubValue:      subValue,
+				DealType:      dealType,
+			}
+			subDataList = append(subDataList, tmp)
+		}
+	}
+	if len(subDataList) > 0 {
+		sort.Sort(subDataList)
+	}
+
+	// 根据净多单/净空单数据, 比对上一个交易日的日期计算成交变化量, 并写入
+	var dealType int
+	rankMap := make(map[string]int)
+	for _, v := range subDataList {
+		subValue := v.SubValue
+		nameStr := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName
+		if v.DealType == 3 {
+			subChangeMap1[nameStr] = subValue
+			dealType = 3
+			if _, ok := rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_3"]; !ok {
+				rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_3"] = 1
+			} else {
+				rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_3"]++
+			}
+		} else if v.DealType == 4 {
+			subChangeMap2[nameStr] = subValue
+			dealType = 4
+			if _, ok := rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_4"]; !ok {
+				rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_4"] = 1
+			} else {
+				rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_4"]++
+			}
+		}
+
+		// 2023-05-10 目前看该方法的引用startDate和endDate其实是同一天, 所以前一个交易日直接用上面的yesterday
+		tmpTimeStr := yesterday
+
+		//和T-1日比较差值
+		//var tmpTimeStr string
+		//tmpTimeStr, err = getYesterdayDate(v.DataTime)
+		//if err != nil {
+		//	return
+		//}
+		yesterdayStr := v.ClassifyName + "_" + v.ClassifyType + "_" + tmpTimeStr + "_" + v.DealShortName
+		dealChange := 0
+		if dealType == 3 {
+			if c, ok := subChangeMap1[yesterdayStr]; ok {
+				dealChange = subValue - c
+			}
+		} else if dealType == 4 {
+			if c, ok := subChangeMap2[yesterdayStr]; ok {
+				dealChange = subValue - c
+			}
+		}
+		tmp := &data_manage.TradePositionTop{
+			ClassifyName:  v.ClassifyName,
+			ClassifyType:  v.ClassifyType,
+			DataTime:      v.DataTime,
+			CreateTime:    now,
+			ModifyTime:    now,
+			DealShortName: v.DealShortName,
+			DealValue:     subValue,
+			DealChange:    dealChange,
+			DealType:      dealType,
+			Rank:          rankMap[v.ClassifyName+"_"+v.ClassifyType+"_"+v.DataTime+"_"+strconv.Itoa(dealType)],
+		}
+		topList = append(topList, tmp)
+		if len(topList) >= 1000 {
+			err = data_manage.InsertMultiTradePositionTop(exchange, topList)
+			if err != nil {
+				return
+			}
+			topList = make([]*data_manage.TradePositionTop, 0)
+		}
+	}
+
+	if len(topList) >= 0 {
+		err = data_manage.InsertMultiTradePositionTop(exchange, topList)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// updateAnalysisCleanTopChangeValClassify T-1日重新生成净多单和净空单的榜单后,需要更新T日净多单和净空单榜单里的变化量
+func updateAnalysisCleanTopChangeValClassify(exchange, startDate, yesterday string, classifyNames, classifyTypes []string) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("updateAnalysisCleanTopChangeVal err: " + err.Error())
+		}
+	}()
+
+	//查询T日的净多单和净空单榜单列表
+	//查询T-1日的净多单和净空单列表
+	//组装数据,计算T日与T-1日的变更值
+	//更新变更值
+	topList := make([]*data_manage.TradePositionTop, 0)      //T日和T+1日列表
+	todayTopList := make([]*data_manage.TradePositionTop, 0) //T日列表
+
+	yesterdayTopListMap := make(map[string]int) //净多单净空单持仓量map
+
+	// 查询T日和T-1日的净多单和净空单列表
+	topList, err = data_manage.GetTradePositionTopCleanByExchangeDataTimeClassify(exchange, yesterday, startDate, classifyNames, classifyTypes)
+	if err != nil {
+		return
+	}
+	if len(topList) == 0 {
+		return
+	}
+	for _, v := range topList {
+		if v.DataTime == startDate {
+			todayTopList = append(todayTopList, v)
+		} else if v.DataTime == yesterday {
+			nameStr := v.ClassifyName + "_" + v.ClassifyType + "_" + v.DataTime + "_" + v.DealShortName + "_" + strconv.Itoa(v.DealType)
+			yesterdayTopListMap[nameStr] = v.DealValue
+		}
+	}
+
+	if len(todayTopList) == 0 {
+		return
+	}
+	// 根据净多单/净空单数据, 比对上一个交易日的日期计算成交变化量, 并写入
+	now := time.Now()
+	updateList := make([]data_manage.UpdateChangeVal, 0)
+	for _, v := range todayTopList {
+		//T日值-T-1日值
+		yesterdayStr := v.ClassifyName + "_" + v.ClassifyType + "_" + yesterday + "_" + v.DealShortName + "_" + strconv.Itoa(v.DealType)
+		dealChange := 0
+		if c, ok := yesterdayTopListMap[yesterdayStr]; ok {
+			dealChange = v.DealValue - c
+		}
+		if dealChange != v.DealChange {
+			tmp := data_manage.UpdateChangeVal{
+				Id:         v.Id,
+				ModifyTime: now,
+				DealChange: dealChange,
+			}
+			updateList = append(updateList, tmp)
+		}
+	}
+
+	if len(updateList) > 0 {
+		err = data_manage.MultiUpdatePositionTopChangeVal(exchange, updateList)
+		if err != nil {
+			return
+		}
+	}
+	return
+}

+ 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
+}

+ 87 - 0
services/english_report.go

@@ -0,0 +1,87 @@
+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.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
+}

+ 40 - 0
services/english_report_email.go

@@ -0,0 +1,40 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/utils"
+	"time"
+)
+
+//英文研报客户 临时->终止
+func EnglishReportEmailTermination(cont context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "英文研报客户 临时->终止 ErrMsg:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+
+	expiredTime := time.Now().AddDate(0, 0, -31).Format(utils.FormatDate)
+
+	items, err := models.GetEndEnglishReportEmailListByDate(expiredTime)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		fmt.Println("GetEndEnglishReportEmailListByDate Err:" + err.Error())
+		utils.FileLog.Info("GetEndEnglishReportEmailListByDate Err:%s" + err.Error())
+		return
+	}
+	disableIds := make([]int, 0)
+	for _, v := range items {
+		disableIds = append(disableIds, v.Id)
+	}
+
+	if len(disableIds) > 0 {
+		err = models.UpdateEnglishReportEmailTermination(disableIds)
+		if err != nil {
+			return err
+		}
+	}
+
+	return
+}

+ 255 - 0
services/eta_business.go

@@ -0,0 +1,255 @@
+package services
+
+import (
+	"context"
+	"fmt"
+	"hongze/hongze_task/models"
+	"hongze/hongze_task/models/eta_business"
+	"hongze/hongze_task/services/alarm_msg"
+	"hongze/hongze_task/utils"
+	"time"
+)
+
+// EtaBusinessExpiredRemind ETA商家到期提醒
+func EtaBusinessExpiredRemind(cont context.Context) (err error) {
+	// 频次: 1-1天; 2-7天; 3-15天; 4-30天; 5-60天
+	frequencyArr := []int{1, 2, 3, 4, 5}
+	for _, f := range frequencyArr {
+		_ = HandleEtaBusinessExpiredRemind(f)
+		time.Sleep(15 * time.Second)
+	}
+	return
+}
+
+// HandleEtaBusinessExpiredRemind ETA商家到期提醒
+func HandleEtaBusinessExpiredRemind(frequency int) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ETA商家到期提醒失败, frequency: %d, Err: %s", frequency, err.Error())
+			utils.FileLog.Info("%s", tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
+	// 频次对应天数
+	dayMap := map[int]int{1: 1, 2: 7, 3: 15, 4: 30, 5: 60}
+	days := dayMap[frequency]
+	if days <= 0 {
+		err = fmt.Errorf("提醒频次有误")
+		return
+	}
+
+	// 获取当前合约N后过期的ETA商家
+	monthLater := time.Now().Local().AddDate(0, 0, days)
+	expiredTime := monthLater.Format(utils.FormatDate)
+	businesses := make([]*eta_business.EtaBusiness, 0)
+	{
+		businessOb := new(eta_business.EtaBusiness)
+		cond := fmt.Sprintf(` AND %s = ?`, eta_business.EtaBusinessColumns.ExpiredTime)
+		pars := make([]interface{}, 0)
+		pars = append(pars, expiredTime)
+		list, e := businessOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取商家列表失败, Err: " + e.Error())
+			return
+		}
+		businesses = list
+	}
+	if len(businesses) == 0 {
+		return
+	}
+
+	// 获取销售
+	sellers, e := models.GetSellersOpenId()
+	if e != nil {
+		err = fmt.Errorf("获取销售信息失败, Err: " + e.Error())
+		return
+	}
+
+	// 以销售为单位
+	sellerBusinessMap := make(map[int][]*eta_business.EtaBusiness)
+	for _, v := range businesses {
+		if sellerBusinessMap[v.SellerId] == nil {
+			sellerBusinessMap[v.SellerId] = make([]*eta_business.EtaBusiness, 0)
+		}
+		sellerBusinessMap[v.SellerId] = append(sellerBusinessMap[v.SellerId], v)
+	}
+	sellerMap := make(map[int]*models.Sellers)
+	for _, s := range sellers {
+		sellerMap[s.AdminId] = s
+	}
+
+	// 推送邮件和公众号
+	remindRecords := make([]*eta_business.EtaBusinessRemindRecord, 0)
+	for k, v := range sellerBusinessMap {
+		seller := sellerMap[k]
+		if seller == nil {
+			continue
+		}
+		if seller.Email == "" && seller.OpenId == "" {
+			continue
+		}
+		if len(v) == 0 {
+			continue
+		}
+		uniqueCode := fmt.Sprint(seller.AdminId, time.Now().Format(utils.FormatDateUnSpace), frequency, utils.GetRandDigit(5))
+
+		contentRemark := ""
+		contentsEmail := fmt.Sprintf(`<div><p>您有【%d】ETA客户将于%d天后到期,请注意查看</p>`, len(v), days)
+		contentsEmail += fmt.Sprintf(`<table border='1'><tr><td width='200'>%d天后到期客户名称</td><td width='200'>到期日期</td><td width='200'>销售人员</td><td>客户类型</td></tr>`, days)
+		for _, bz := range v {
+			row := fmt.Sprintf(`<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>`, bz.BusinessName, bz.ExpiredTime.Format(utils.FormatDate), bz.SellerName, "ETA")
+			contentsEmail += row
+			contentRemark += fmt.Sprintf("【%s】", bz.BusinessName)
+
+			// 数据入库
+			remindRecords = append(remindRecords, &eta_business.EtaBusinessRemindRecord{
+				Type:          frequency,
+				SellerId:      seller.AdminId,
+				SellerName:    seller.RealName,
+				EtaBusinessId: bz.EtaBusinessId,
+				BusinessName:  bz.BusinessName,
+				EndDate:       bz.ExpiredTime.Format(utils.FormatDate),
+				UniqueCode:    uniqueCode,
+				CreateTime:    time.Now(),
+			})
+		}
+		contentsEmail += "</table></div>"
+
+		// 邮件
+		if seller.Email != "" {
+			msg := fmt.Sprintf("到期前%d天提醒", days)
+			utils.SendEmailByHongze(msg, contentsEmail, seller.Email, "", "")
+		}
+
+		// 公众号, first和remark已经无效了
+		if seller.OpenId != "" {
+			first := fmt.Sprintf(`您有【%d】 客户将于%d天后到期,请注意查看`, len(v), days)
+			keyword1 := fmt.Sprintf(`【%d】ETA到期前%d天提醒,点击查看`, len(v), days)
+			keyword2 := expiredTime
+			remark := contentRemark
+
+			openIdList := make([]*models.OpenIdList, 0)
+			openIdItem := new(models.OpenIdList)
+			openIdItem.OpenId = seller.OpenId
+			openIdList = append(openIdList, openIdItem)
+			_ = SendWxMsgWithEtaBusinessRemind(first, keyword1, keyword2, remark, uniqueCode, openIdList)
+		}
+
+		// 数据入库
+		if len(remindRecords) > 0 {
+			recordOb := new(eta_business.EtaBusinessRemindRecord)
+			if e = recordOb.CreateMulti(remindRecords); e != nil {
+				err = fmt.Errorf("批量新增ETA商家提醒记录失败, Err: %s", e.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+// EtaBusinessUpdateStatus 每日更新ETA商家签约状态
+func EtaBusinessUpdateStatus(cont context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			tips := "ETA商家签约状态更新: EtaBusinessUpdateStatus, Err: " + err.Error()
+			utils.FileLog.Info("%s", tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
+	// 获取所有商家
+	businesses := make([]*eta_business.EtaBusiness, 0)
+	{
+		ob := new(eta_business.EtaBusiness)
+		cond := ``
+		pars := make([]interface{}, 0)
+		list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取商家列表失败, Err: " + e.Error())
+			return
+		}
+		businesses = list
+	}
+	if len(businesses) == 0 {
+		return
+	}
+
+	// 获取所有商家签约
+	contracts := make([]*eta_business.EtaBusinessContract, 0)
+	{
+		ob := new(eta_business.EtaBusinessContract)
+		cond := ``
+		pars := make([]interface{}, 0)
+		list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取商家合同列表失败, Err: " + e.Error())
+			return
+		}
+		contracts = list
+	}
+	if len(contracts) == 0 {
+		return
+	}
+	businessContracts := make(map[int][]*eta_business.EtaBusinessContract) // 商家对应的所有签约
+	for _, c := range contracts {
+		if businessContracts[c.EtaBusinessId] == nil {
+			businessContracts[c.EtaBusinessId] = make([]*eta_business.EtaBusinessContract, 0)
+		}
+		businessContracts[c.EtaBusinessId] = append(businessContracts[c.EtaBusinessId], c)
+	}
+
+	// 遍历判断商家当前合约的时间, 更新签约状态
+	updateBusiness := make([]*eta_business.EtaBusiness, 0)
+	strToday := time.Now().Format(utils.FormatDate)
+	today, _ := time.ParseInLocation(utils.FormatDate, strToday, time.Local)
+	for _, b := range businesses {
+		cs := businessContracts[b.EtaBusinessId]
+		// 无签约-正常来讲不会出现这种情况
+		if cs == nil || (cs != nil && len(cs) == 0) {
+			if b.SigningStatus != eta_business.EtaBusinessSigningStatusTerminate {
+				b.SigningStatus = eta_business.EtaBusinessSigningStatusTerminate
+				b.ModifyTime = time.Now().Local()
+				updateBusiness = append(updateBusiness, b)
+			}
+			continue
+		}
+
+		// 有签约
+		using := false // 是否在任一存续期内
+		for _, c := range cs {
+			// 当前合约
+			if today.Equal(c.SigningTime) || today.Equal(c.ExpiredTime) || (today.After(c.SigningTime) && today.Before(c.ExpiredTime)) {
+				b.ContractId = c.EtaBusinessContractId
+				b.SigningTime = c.SigningTime
+				b.ExpiredTime = c.ExpiredTime
+				b.ModifyTime = time.Now().Local()
+				// 是否为首次签约
+				if c.IsFirst == 1 {
+					b.SigningStatus = eta_business.EtaBusinessSigningStatusFirst
+				} else {
+					b.SigningStatus = eta_business.EtaBusinessSigningStatusContinue
+				}
+				updateBusiness = append(updateBusiness, b)
+				using = true
+				break
+			}
+		}
+		// 不存在任一合同期内
+		if !using {
+			b.SigningStatus = eta_business.EtaBusinessSigningStatusTerminate
+			updateBusiness = append(updateBusiness, b)
+		}
+	}
+
+	// 更新签约状态
+	if len(updateBusiness) > 0 {
+		ob := new(eta_business.EtaBusiness)
+		cols := []string{"ContractId", "SigningStatus", "SigningTime", "ExpiredTime", "ModifyTime"}
+		if e := ob.UpdateMulti(updateBusiness, cols); e != nil {
+			err = fmt.Errorf("批量更新签约状态失败, Err: %s", e.Error())
+			return
+		}
+	}
+	return
+}

+ 2 - 4
services/eta_trial.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"hongze/hongze_task/models"
 	"hongze/hongze_task/utils"
-	"strings"
 	"time"
 )
 
@@ -29,7 +28,7 @@ func ETATrialDisabled(cont context.Context) (err error) {
 	disableMobiles := make([]string, 0)
 	for _, v := range items {
 		disableIds = append(disableIds, v.EtaTrialId)
-		disableMobiles = append(disableMobiles, "'" + v.Mobile + "'")
+		disableMobiles = append(disableMobiles, v.Mobile )
 	}
 
 	if len(disableIds) > 0 {
@@ -39,8 +38,7 @@ func ETATrialDisabled(cont context.Context) (err error) {
 		}
 	}
 	if len(disableMobiles) > 0 {
-		mobiles := strings.Join(disableMobiles, ",")
-		err = models.UpdateETATrialAdminDisable(mobiles)
+		err = models.UpdateETATrialAdminDisable(disableMobiles)
 		if err != nil {
 			return err
 		}

+ 24 - 18
services/maycur/maycur.go

@@ -2,9 +2,7 @@ package maycur
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
-	"hongze/hongze_task/models"
 	"hongze/hongze_task/services/alarm_msg"
 	"hongze/hongze_task/utils"
 	"io/ioutil"
@@ -13,7 +11,7 @@ import (
 )
 
 const (
-	SyncMaycurCompanyProfileUrl = "http://127.0.0.1:8602/adminapi/maycur/company_profile/daily_sync"
+	SyncMaycurCompanyProfileUrl = "http://127.0.0.1:8702/adminapi/maycur/company_profile/daily_sync"
 )
 
 // DailyUpdateCompanyProfile 每日更新客户档案-请求hongze_admin同步接口
@@ -21,7 +19,8 @@ func DailyUpdateCompanyProfile(cont context.Context) (err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Info("DailyUpdateCompanyProfile ErrMsg: %s", err.Error())
-			alarm_msg.SendAlarmMsg(fmt.Sprintf("每日更新客户档案失败, ErrMsg: %s", err.Error()), 3)
+			// 出错的话hongze_admin那边有报错, 这边只要定时请求了就行...
+			go alarm_msg.SendAlarmMsg(fmt.Sprintf("每日更新客户档案失败, ErrMsg: %s", err.Error()), 2)
 		}
 	}()
 
@@ -43,19 +42,26 @@ func DailyUpdateCompanyProfile(cont context.Context) (err error) {
 	defer func() {
 		_ = resp.Body.Close()
 	}()
-	b, e := ioutil.ReadAll(resp.Body)
-	if e != nil {
-		err = fmt.Errorf("resp body read err: %s", e.Error())
-		return
-	}
-	result := new(models.BaseResponse)
-	if e = json.Unmarshal(b, &result); e != nil {
-		err = fmt.Errorf("result unmarshal err: %s", e.Error())
-		return
-	}
-	if result.Ret != 200 {
-		err = fmt.Errorf("result: %s", string(b))
-		return err
-	}
+
+	//b, e := ioutil.ReadAll(resp.Body)
+	//if e != nil {
+	//	err = fmt.Errorf("resp body read err: %s", e.Error())
+	//	return
+	//}
+	//if len(b) == 0 {
+	//	err = fmt.Errorf("resp body is empty")
+	//	return
+	//}
+	//// 解密
+	//bo := utils.DesBase64Decrypt(b)
+	//result := new(models.BaseResponse)
+	//if e = json.Unmarshal(bo, &result); e != nil {
+	//	err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+	//	return
+	//}
+	//if result.Ret != 200 {
+	//	err = fmt.Errorf("result: %s", string(b))
+	//	return err
+	//}
 	return
 }

+ 20 - 17
services/oss.go

@@ -1,6 +1,7 @@
 package services
 
 import (
+	"errors"
 	"github.com/aliyun/aliyun-oss-go-sdk/oss"
 	"os"
 	"time"
@@ -26,49 +27,51 @@ func init() {
 }
 */
 
-//图片上传到阿里云
-func UploadAliyun(filename, filepath string) (string,error) {
+// 图片上传到阿里云
+func UploadAliyun(filename, filepath string) (string, error) {
 	client, err := oss.New(utils.Endpoint, utils.AccessKeyId, utils.AccessKeySecret)
 	if err != nil {
-		return "1",err
+		return "1", err
 	}
 	bucket, err := client.Bucket(utils.Bucketname)
 	if err != nil {
-		return "2",err
+		return "2", err
 	}
-	path := utils.Upload_dir + time.Now().Format("200601/20060102/")
+	path := utils.UploadDir + time.Now().Format("200601/20060102/")
 	path += filename
 	err = bucket.PutObjectFromFile(path, filepath)
 	if err != nil {
-		return "3",err
+		return "3", err
 	}
 	path = utils.Imghost + path
-	return path,err
+	return path, err
 }
 
-
-//音频上传到阿里云
-func UploadAudioAliyun(filename, filepath string) (string,error) {
+// 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
+		return "1", err
 	}
 	bucket, err := client.Bucket(utils.Bucketname)
 	if err != nil {
-		return "2",err
+		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
+		return "3", err
 	}
 	path = utils.Imghost + path
-	return path,err
+	return path, err
 }
 
-//视频上传到阿里云
-func UploadVideoAliyun(filename, filepath,savePath string) (error) {
+// 视频上传到阿里云
+func UploadVideoAliyun(filename, filepath, savePath string) error {
 	defer func() {
 		os.Remove(filepath)
 	}()
@@ -89,4 +92,4 @@ func UploadVideoAliyun(filename, filepath,savePath string) (error) {
 	//path = utils.Imghost + path
 	//return path,err
 	return err
-}
+}

+ 362 - 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,364 @@ 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.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
+}

+ 64 - 3
services/report_view.go

@@ -9,7 +9,7 @@ import (
 	"time"
 )
 
-//报告历史访问次数
+// 报告历史访问次数
 func ReportViewTimes() (err error) {
 	defer func() {
 		if err != nil {
@@ -86,7 +86,7 @@ func ReportViewTimes() (err error) {
 	return nil
 }
 
-//报告访问详情
+// 报告访问详情
 func ReportViewDetail() (err error) {
 	defer func() {
 		if err != nil {
@@ -136,7 +136,68 @@ func ReportViewDetail() (err error) {
 	file := xlsx.NewFile()
 	for _, v := range typeList {
 		fmt.Println(v.TypeName, v.TypeValue)
-		if v.TypeName == `周报` {
+		if v.TypeName == `晨报` {
+			sheet, err := file.AddSheet(v.TypeName + "研报阅读统计")
+			if err != nil {
+				return err
+			}
+			//标头
+			rowTitle := sheet.AddRow()
+			cellA := rowTitle.AddCell()
+			cellA.Value = "用户名称"
+			cellB := rowTitle.AddCell()
+			cellB.Value = "公司名称"
+			cellC := rowTitle.AddCell()
+			cellC.Value = "访问时间"
+			cellD := rowTitle.AddCell()
+			cellD.Value = "访问标题"
+			cellE := rowTitle.AddCell()
+			cellE.Value = "访问页面"
+			cellF := rowTitle.AddCell()
+			cellF.Value = "报告类型"
+
+			items, err := models.GetResearchReportViewersDetail(startTime, endTime, v.TypeValue)
+			if err != nil {
+				return err
+			}
+			for _, item := range items {
+				row := sheet.AddRow()
+				cellA := row.AddCell()
+				cellA.Value = item.RealName
+				cellB := row.AddCell()
+				cellB.Value = item.CompanyName
+				cellC := row.AddCell()
+				cellC.Value = item.CreatedTime
+				cellD := row.AddCell()
+				cellD.Value = item.ResearchReportName
+				cellE := row.AddCell()
+				cellE.Value = item.ReportVariety
+				cellF := row.AddCell()
+				cellF.Value = v.TypeName
+			}
+
+			//新晨报的数据
+			weekItems, err := models.GetRddpDayReportViewersDetail(startTime, endTime)
+			if err != nil {
+				return err
+			}
+			for _, item := range weekItems {
+				row := sheet.AddRow()
+				cellA := row.AddCell()
+				cellA.Value = item.RealName
+				cellB := row.AddCell()
+				cellB.Value = item.CompanyName
+				cellC := row.AddCell()
+				cellC.Value = item.CreatedTime
+				cellD := row.AddCell()
+				cellD.Value = item.ResearchReportName + "(" + item.ReportCreateDate + ")"
+				cellE := row.AddCell()
+				cellE.Value = item.ReportVariety
+				cellF := row.AddCell()
+				cellF.Value = v.TypeName
+			}
+			continue
+		} else if v.TypeName == `周报` {
 			sheet, err := file.AddSheet(v.TypeName + "研报阅读统计")
 			if err != nil {
 				return err

+ 99 - 48
services/task.go

@@ -6,19 +6,41 @@ import (
 	"fmt"
 	"github.com/beego/beego/v2/task"
 	"hongze/hongze_task/models"
+	"hongze/hongze_task/services/alarm_msg"
 	"hongze/hongze_task/services/company"
 	"hongze/hongze_task/services/company_contract"
 	"hongze/hongze_task/services/data"
-	"hongze/hongze_task/services/data/future_good"
 	"hongze/hongze_task/services/maycur"
 	"hongze/hongze_task/services/roadshow"
 	"hongze/hongze_task/utils"
+	"runtime"
 	"strconv"
 	"strings"
 	"sync"
 	"time"
 )
 
+func InitTask() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("进来了定时任务的异常处理")
+			stack := fmt.Sprintf("Handler crashed with error: %v", err) + "\n</br>"
+			for i := 1; ; i++ {
+				_, file, line, ok := runtime.Caller(i)
+				if !ok {
+					break
+				}
+				stack += fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+			}
+			//fmt.Println(stack)
+			go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05")+"\n"+stack, 3)
+		}
+	}()
+
+	// 实际开始定时任务
+	Task()
+}
+
 func Task() {
 	fmt.Println("task start")
 	//如果是生产环境,才需要走这些任务
@@ -58,8 +80,8 @@ func Task() {
 	task.AddTask("存量客户数据统计", stackCompanyStatistic)
 
 	// 定时往同花顺推送报告
-	sendWaitReport := task.NewTask("sendWaitReport", "0 */1 * * * * ", SendWaitReport)
-	task.AddTask("定时往同花顺推送报告", sendWaitReport)
+	//sendWaitReport := task.NewTask("sendWaitReport", "0 */1 * * * * ", SendWaitReport)
+	//task.AddTask("定时往同花顺推送报告", sendWaitReport)
 
 	// 研报电话会提醒
 	ybTelRemind := task.NewTask("YbTelRemind", "0 */1 * * * * ", YbTelRemind)
@@ -69,11 +91,11 @@ func Task() {
 	ybSalonRemind := task.NewTask("ybSalonRemind", "0 */1 * * * * ", YbSalonRemind)
 	task.AddTask("研报沙龙提醒", ybSalonRemind)
 
-	// 定时新增手工指标数据提醒
-	addEdbTask := task.NewTask("sendWaitReport", "0 30 15 * * * ", AddEdbTask)
-	task.AddTask("定时新增手工指标数据提醒", addEdbTask)
-	//每次服务启动都需要执行一次的
-	_ = AddEdbTask(nil)
+	//// 定时新增手工指标数据提醒
+	//addEdbTask := task.NewTask("sendWaitReport", "1 0 2 * * * ", AddEdbTask)
+	//task.AddTask("定时新增手工指标数据提醒", addEdbTask)
+	////每次服务启动都需要执行一次的
+	//_ = AddEdbTask(nil)
 
 	//每日用户阅读数据统计
 	statisticsUserView := task.NewTask("statisticsUserView", "0 5 2 * * *", StatisticsUserView)
@@ -114,14 +136,22 @@ func Task() {
 	syncSubStatus := task.NewTask("syncSubStatus", "0 0 2 * * *", SyncSubStatus)
 	task.AddTask("syncSubStatus", syncSubStatus)
 
-	//删除日志 report_save_log,ppt_v2_save_log,保留一个月的
-	deleteLog := task.NewTask("syncSubStatus", "0 0 2 2 * *", DeleteLog)
-	task.AddTask("deleteLog", deleteLog)
+	////删除日志 report_save_log,ppt_v2_save_log,保留一个月的
+	//deleteLog := task.NewTask("syncSubStatus", "0 0 2 2 * *", DeleteLog)
+	//task.AddTask("deleteLog", deleteLog)
 
 	// 定时发布研报语音播报
 	publishVoiceBroadcast := task.NewTask("publishVoiceBroadcast", "0 */1 * * * *", PublishVoiceBroadcast)
 	task.AddTask("定时发布研报语音播报", publishVoiceBroadcast)
 
+	//// 定时发布研报
+	//publishReport := task.NewTask("publishReport", "0 */1 * * * *", PublishReport)
+	//task.AddTask("定时发布研报", publishReport)
+	//
+	//// 定时发布英文研报
+	//publishEnglishReport := task.NewTask("publishEnglishReport", "0 */1 * * * *", PublishEnglishReport)
+	//task.AddTask("定时发布英文研报", publishEnglishReport)
+
 	// 定时生成本周研究汇总
 	cygxResearchSummary := task.NewTask("cygxResearchSummary", "0 0 15 * * 5", CygxResearchSummary)
 	task.AddTask("定时生成本周研究汇总", cygxResearchSummary)
@@ -140,6 +170,14 @@ func Task() {
 	eTATrialDisabled := task.NewTask("eTATrialDisabled", "0 15 2 * * *", ETATrialDisabled)
 	task.AddTask("ETA试用客户 启用->禁用", eTATrialDisabled)
 
+	//英文研报客户 临时->终止
+	englishReportEmailTermination := task.NewTask("englishReportEmailTermination", "0 30 2 * * *", EnglishReportEmailTermination)
+	task.AddTask("英文研报客户 临时->终止", englishReportEmailTermination)
+
+	// ETA商家签约状态更新
+	etaBusinessUpdateStatus := task.NewTask("etaBusinessUpdateStatus", "0 5 5 * * *", EtaBusinessUpdateStatus)
+	task.AddTask("etaBusinessUpdateStatus", etaBusinessUpdateStatus)
+
 	task.StartTask()
 
 	fmt.Println("task end")
@@ -179,66 +217,79 @@ func releaseTask() {
 	incrementCompany := task.NewTask("incrementCompany", "0 0 9 * * 1 ", IncrementCompany)
 	task.AddTask("上周增量客户列表", incrementCompany)
 
-	//刷新指标数据
-	refreshData := task.NewTask("refreshData", "0 1 0,19 * * *", RefreshData)
-	task.AddTask("refreshData", refreshData)
+	// 刷新指标数据
+	//refreshData := task.NewTask("refreshData", "0 1 0,19 * * *", RefreshData)
+	//task.AddTask("refreshData", refreshData)
+
+	// 刷新商品数据
+	//refreshFutureGoodData := task.NewTask("refreshFutureGoodData", "0 30 0,19 * * *", RefreshFutureGoodData)
+	//task.AddTask("refreshFutureGoodData", refreshFutureGoodData)
 
 	//刷新交易所指标数据
-	refreshTradeData := task.NewTask("refreshData", "0 1 4 * * *", RefreshTradeData)
-	task.AddTask("refreshTradeData", refreshTradeData)
+	//refreshTradeData := task.NewTask("refreshData", "0 1 4 * * *", RefreshTradeData)
+	//task.AddTask("refreshTradeData", refreshTradeData)
 
 	//刷新欧洲天然气指标数据
-	refreshEicData := task.NewTask("refreshData", "0 1 3,7 * * *", RefreshEicData)
-	task.AddTask("refreshEicData", refreshEicData)
+	//refreshEicData := task.NewTask("refreshData", "0 1 3,7 * * *", RefreshEicData)
+	//task.AddTask("refreshEicData", refreshEicData)
 
 	//刷新指标基础数据
 	//refreshBaseData := task.NewTask("refreshBaseData", "0 */30 * * * * ", RefreshBaseData)
 	//task.AddTask("refreshBaseData", refreshBaseData)
 
-	//同步弘则数据库中来自,钢联,隆众,有色,人工等基础数据--每隔五分钟,同步一次最新数据
-	syncBaseData := task.NewTask("syncBaseData", "0 */5 * * * * ", SyncBaseData)
-	task.AddTask("syncBaseData", syncBaseData)
-
-	syncBaseDataExt := task.NewTask("syncBaseDataExt", "0 */30 * * * * ", SyncBaseDataExt)
-	task.AddTask("syncBaseDataExt", syncBaseDataExt)
+	////同步弘则数据库中来自,钢联,隆众,有色,人工等基础数据--每隔五分钟,同步一次最新数据
+	//syncBaseData := task.NewTask("syncBaseData", "0 */5 * * * * ", SyncBaseData)
+	//task.AddTask("syncBaseData", syncBaseData)
+	//
+	//syncBaseDataExt := task.NewTask("syncBaseDataExt", "0 */30 * * * * ", SyncBaseDataExt)
+	//task.AddTask("syncBaseDataExt", syncBaseDataExt)
 
 	// 定时往同花顺同步微信群的截止日期(凌晨4点)
-	syncThsWxGroupEveryDay := task.NewTask("syncThsWxGroupEveryDay", "0 1 4 * * * ", SyncWxGroupEveryDay)
-	task.AddTask("定时往同花顺同步微信群的截止日期", syncThsWxGroupEveryDay)
+	//syncThsWxGroupEveryDay := task.NewTask("syncThsWxGroupEveryDay", "0 1 4 * * * ", SyncWxGroupEveryDay)
+	//task.AddTask("定时往同花顺同步微信群的截止日期", syncThsWxGroupEveryDay)
 
 	//检测数据服务器
-	checkDataServer := task.NewTask("checkDataServer", "0 */2 * * * * ", checkDataServer)
-	task.AddTask("checkDataServer", checkDataServer)
+	//checkDataServer := task.NewTask("checkDataServer", "0 */2 * * * * ", checkDataServer)
+	//task.AddTask("checkDataServer", checkDataServer)
 
 	//初始化指标更新状态
-	resetEdbInfoIsUpdate := task.NewTask("resetEdbInfoIsUpdate", "0 0 0 * * *", data.ResetEdbInfoIsUpdate)
-	task.AddTask("resetEdbInfoIsUpdate", resetEdbInfoIsUpdate)
+	//resetEdbInfoIsUpdate := task.NewTask("resetEdbInfoIsUpdate", "0 0 0 * * *", data.ResetEdbInfoIsUpdate)
+	//task.AddTask("resetEdbInfoIsUpdate", resetEdbInfoIsUpdate)
 
 	// 定时检测同花顺客群推送消息状态
-	checkThsReportList := task.NewTask("checkThsReportList", "0 */2 * * * * ", CheckThsReportList)
-	task.AddTask("checkThsReportList", checkThsReportList)
+	//checkThsReportList := task.NewTask("checkThsReportList", "0 */2 * * * * ", CheckThsReportList)
+	//task.AddTask("checkThsReportList", checkThsReportList)
 
-	// 定时统计交易所的持仓分析数据
-	InitPositionTask := task.NewTask("checkThsReportList", "0 30 16-20 * * *", data.InitPositionTask)
-	task.AddTask("checkThsReportList", InitPositionTask)
+	//// 定时统计交易所的持仓分析数据
+	//initPositionTask := task.NewTask("initPositionTask", "0 15,45 16-23 * * *", data.InitPositionTask)
+	//task.AddTask("initPositionTask", initPositionTask)
 
-	// 每日2:45更新每刻报销-客户档案
-	syncMaycurCompanyProfile := task.NewTask("syncMaycurCompanyProfile", "0 45 2 * * * ", maycur.DailyUpdateCompanyProfile)
+	// 每日4:01更新每刻报销-客户档案
+	syncMaycurCompanyProfile := task.NewTask("syncMaycurCompanyProfile", "0 1 4 * * * ", maycur.DailyUpdateCompanyProfile)
 	task.AddTask("每日更新每刻报销-客户档案", syncMaycurCompanyProfile)
-}
 
-func TaskTest() {
-	fmt.Println("The task is start")
-	//companyReportPermissionClose := task.NewTask("companyTryOut", "0 5 0 * * *", CompanyReportPermissionClose)
-	//companyReportPermissionClose := task.NewTask("companyReportPermissionClose", "0/30 * * * * *", CompanyReportPermissionClose)
-	//task.AddTask("用户产品权限试用-->关闭", companyReportPermissionClose)
-	//publishVoiceBroadcast := task.NewTask("publishVoiceBroadcast", "0 */1 * * * *", PublishVoiceBroadcast)
-	//task.AddTask("定时发布研报语音播报", publishVoiceBroadcast)
-
-	task.StartTask()
-	fmt.Println("The task is end")
+	// ETA商家到期提醒
+	etaBusinessRemind := task.NewTask("etaBusinessRemind", "0 20 8 * * *", EtaBusinessExpiredRemind)
+	task.AddTask("etaBusinessRemind", etaBusinessRemind)
 }
 
+//func TaskTest() {
+//	fmt.Println("The task is start")
+//
+//	//e, msg := data.InitTradePosition("shanghai", "2023-05-05", "2023-05-05")
+//	//if e != nil {
+//	//	fmt.Println(e.Error())
+//	//	fmt.Println(msg)
+//	//}
+//
+//	//var ctx context.Context
+//	//_ = EtaBusinessExpiredRemind(ctx)
+//	//_ = EtaBusinessUpdateStatus(ctx)
+//
+//	//task.StartTask()
+//	fmt.Println("The task is end")
+//}
+
 func SendEmail(cont context.Context) (err error) {
 	//报告历史访问次数
 	go ReportViewTimes()

+ 318 - 0
services/video.go

@@ -0,0 +1,318 @@
+package services
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"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) {
+	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
+}

+ 63 - 12
services/wx_template_msg.go

@@ -1,7 +1,6 @@
 package services
 
 import (
-	"bytes"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -71,6 +70,7 @@ func SendWxMsgWithFrequency(first, keyword1, keyword2, remark string, openIdList
 	return
 }
 
+<<<<<<< HEAD
 func WxSendTemplateMsg(sendUrl string, sendMap map[string]interface{}, items []*models.OpenIdList) (err error) {
 	for _, v := range items {
 		sendMap["touser"] = v.OpenId
@@ -108,6 +108,9 @@ func SendTemplateMsg(sendUrl string, data []byte) (err error) {
 }
 
 // SendWxMsgWithCompanyRemind 到期提醒模板消息
+=======
+// 到期提醒模板消息
+>>>>>>> master
 func SendWxMsgWithCompanyRemind(first, keyword1, keyword2, remark, code string, openIdList []*models.OpenIdList) (err error) {
 	var msg string
 	defer func() {
@@ -173,16 +176,8 @@ func SendWxMsgWithActivityAppointmentRemind(first, keyword1, keyword2, keyword3,
 		}
 	}()
 	utils.FileLog.Info("%s", "services SendMsg")
-	accessToken, err := models.GetWxAccessToken()
-	if err != nil {
-		msg = "GetWxAccessToken Err:" + err.Error()
-		return
-	}
-	if accessToken == "" {
-		msg = "accessToken is empty"
-		return
-	}
 
+<<<<<<< HEAD
 	// 获取研报小程序配置
 	ybConf, err := GetYbAppIdInfo()
 	if err != nil {
@@ -204,9 +199,24 @@ func SendWxMsgWithActivityAppointmentRemind(first, keyword1, keyword2, keyword3,
 
 	if wxAppPath != "" {
 		sendMap["miniprogram"] = map[string]interface{}{"appid": ybConf.AppId, "pagepath": wxAppPath}
+=======
+	openIdArr := make([]string, len(openIdList))
+	for i, v := range openIdList {
+		openIdArr[i] = v.OpenId
+>>>>>>> master
 	}
-	sendMap["data"] = sendData
-	WxSendTemplateMsg(sendUrl, sendMap, openIdList)
+	sendInfo := new(SendWxTemplate)
+	sendInfo.WxAppId = utils.WxAppId
+	sendInfo.First = first
+	sendInfo.Keyword1 = keyword1
+	sendInfo.Keyword2 = keyword2
+	sendInfo.RedirectUrl = wxAppPath
+	sendInfo.RedirectTarget = 1
+	sendInfo.TemplateId = utils.AppointmentRemindTemplateId
+	sendInfo.Resource = wxAppPath
+	sendInfo.OpenIdArr = openIdArr
+	sendInfo.Remark = remark
+	err = SendTemplateMsgV2(sendInfo)
 	return
 }
 
@@ -316,3 +326,44 @@ func SendYbVoiceBroadcastWxMsg(broadcastId int, sectionName, broadcastName strin
 	err = SendTemplateMsgV2(sendInfo)
 	return
 }
+
+// SendWxMsgWithEtaBusinessRemind ETA商家-到期提醒
+func SendWxMsgWithEtaBusinessRemind(first, keyword1, keyword2, remark, code string, openIdList []*models.OpenIdList) (err error) {
+	defer func() {
+		if err != nil {
+			tips := "ETA商家-推送到期提醒模板消息失败, Err: " + err.Error()
+			utils.FileLog.Info("%s", tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+			//go utils.SendEmail("发送模版消息失败"+"【"+utils.APPNAME+"】"+time.Now().Format("2006-01-02 15:04:05"), msg+";Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}()
+	utils.FileLog.Info("%s", "services SendMsg")
+
+	var wxAppPath string
+	if utils.RunMode == `debug` {
+		wxAppPath = `http://msgsendtest.hzinsights.com/ETAExpirationHint.html?code=` + code
+	} else {
+		wxAppPath = `https://msgsend.hzinsights.com/ETAExpirationHint.html?code=` + code
+	}
+	openIdArr := make([]string, len(openIdList))
+	for i, v := range openIdList {
+		openIdArr[i] = v.OpenId
+	}
+	if len(openIdArr) == 0 {
+		return
+	}
+
+	sendInfo := new(SendWxTemplate)
+	sendInfo.WxAppId = utils.AdminWxAppId
+	sendInfo.First = first
+	sendInfo.Productname = keyword1
+	sendInfo.Date = keyword2
+	sendInfo.RedirectUrl = wxAppPath
+	sendInfo.RedirectTarget = 0
+	sendInfo.TemplateId = utils.RemindTemplateId
+	sendInfo.Resource = wxAppPath
+	sendInfo.OpenIdArr = openIdArr
+	sendInfo.Remark = remark
+	err = SendTemplateMsgV2(sendInfo)
+	return
+}

+ 114 - 0
services/xfyun.go

@@ -0,0 +1,114 @@
+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"
+)
+
+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
+}

+ 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
+}

+ 100 - 14
utils/config.go

@@ -4,16 +4,22 @@ import (
 	"fmt"
 	beego "github.com/beego/beego/v2/adapter"
 	"github.com/beego/beego/v2/server/web"
+	"github.com/rdlucklib/rdluck_tools/cache"
 )
 
 var (
-	RunMode        string //运行模式
-	MYSQL_URL      string //数据库连接
-	MYSQL_URL_RDDP string //数据库连接
-	MYSQL_URL_EDB  string
-	MYSQL_URL_DATA string
-	MYSQL_URL_GL   string
-	MYSQL_URL_WEEKLY_TRIAL       string // ETA试用
+	RunMode                string //运行模式
+	MYSQL_URL              string //数据库连接
+	MYSQL_URL_RDDP         string //数据库连接
+	MYSQL_URL_EDB          string
+	MYSQL_URL_DATA         string
+	MYSQL_URL_GL           string
+	MYSQL_URL_WEEKLY_TRIAL string // ETA试用
+	MYSQL_URL_ETA          string
+
+	REDIS_CACHE string       //缓存地址
+	Rc          *cache.Cache //redis缓存
+	Re          error        //redis错误
 )
 
 var (
@@ -33,12 +39,14 @@ var (
 	WxYbAppId string //微信研报小程序
 
 	//内部员工公众号(弘则部门)
-	AdminWxAppId                    string
-	AdminWxAppSecret                string
+	AdminWxAppId     string
+	AdminWxAppSecret string
+
+	TemplateIdByProduct string //产品运行报告通知-模板ID
 )
 
-//oss配置
-var (
+// oss配置
+/*var (
 	Bucketname       string = "hongze"
 	Endpoint         string
 	Imghost          string = "https://hongze.oss-accelerate.aliyuncs.com/"
@@ -47,6 +55,29 @@ var (
 
 	AccessKeyId     string = "LTAIFMZYQhS2BTvW"
 	AccessKeySecret string = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
+)*/
+
+// 阿里云配置
+var (
+	Bucketname       string
+	Endpoint         string
+	Imghost          string
+	UploadDir        string
+	Upload_Audio_Dir string
+	AccessKeyId      string
+	AccessKeySecret  string
+)
+
+// ES配置
+var (
+	ES_URL      string // ES服务器地址
+	ES_USERNAME string // ES账号
+	ES_PASSWORD string // ES密码
+)
+
+var (
+	EsReportIndexName        string //研报ES索引
+	EsEnglishReportIndexName string //英文研报ES索引
 )
 
 var (
@@ -61,18 +92,23 @@ var (
 	EDB_LIB_URL string
 )
 
-//进门财经账号信息
+// 进门财经账号信息
 var (
 	COMEIN_URL      string
 	COMEIN_APPID    string
 	COMEIN_SECREKEY string
 )
 
-//模板消息推送
+// 模板消息推送
 var (
 	SendWxTemplateMsgUrl string
 )
 
+// 科大讯飞--语音合成
+var (
+	XfHostUrl string
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -106,6 +142,17 @@ func init() {
 	MYSQL_URL_DATA = config["mysql_url_data"]
 	MYSQL_URL_GL = config["mysql_url_gl"]
 	MYSQL_URL_WEEKLY_TRIAL = config["mysql_url_weekly_trial"]
+	MYSQL_URL_ETA = config["mysql_url_eta"]
+
+	REDIS_CACHE = config["beego_cache"]
+	if len(REDIS_CACHE) <= 0 {
+		panic("redis链接参数没有配置")
+	}
+	Rc, Re = cache.NewCache(REDIS_CACHE) //初始化缓存
+	if Re != nil {
+		fmt.Println(Re)
+		panic(Re)
+	}
 
 	//同花顺公钥
 	THS_PubKey = `-----BEGIN PUBLIC KEY-----
@@ -131,6 +178,8 @@ ZwIDAQAB
 		WxAppSecret = "26c586e7ccb3c575433f0f37797b3eeb"
 		TemplateId = "DGvLwidav-OZc07klLv5mxwxO63qHdWS5Cj-rTDvTQo"
 		RemindTemplateId = "rCvkgRatnK_3N9pnQEErUyjFkyVpyAGuYtv3HwY82K4"
+		AppointmentRemindTemplateId = `pdbn6Wr84Rr-Ek7zrYHcxh5RO5ROILHjbkj3em68954` //活动预约通知模板id
+
 		//同花顺正式地址
 		THS_SendUrl = `https://board.10jqka.com.cn/gateway/ps/syncNews`
 		THS_SyncWxGroupUrl = `https://board.10jqka.com.cn/gateway/ps/syncWechatGroupInfo`
@@ -151,7 +200,7 @@ ZwIDAQAB
 		WxAppSecret = "f4d52e34021eee262dce9682b31f8861"
 		TemplateId = "DGvLwidav-OZc07klLv5mxwxO63qHdWS5Cj-rTDvTQo"
 		RemindTemplateId = "rCvkgRatnK_3N9pnQEErUyjFkyVpyAGuYtv3HwY82K4"
-		AppointmentRemindTemplateId = `Y59n_AHg-RLCKaz293geW76KDHpGL1qOnE7eF_lxelY` //活动预约通知模板id
+		AppointmentRemindTemplateId = `U3su--7d6xsCDcP6Tya0N0wWpKn_uI0zO1cutRK52cc` //活动预约通知模板id
 
 		//同花顺测试地址
 		THS_SendUrl = `https://mtest.10jqka.com.cn/gateway/ps/syncNews`
@@ -166,6 +215,43 @@ 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"]
+	}
+
+	// 科大讯飞
+	{
+		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"]
+	}
+
 	// 进门财经开放api配置
 	ComeinOpenApiConfig()
 }

+ 22 - 16
utils/constants.go

@@ -2,6 +2,7 @@ package utils
 
 const (
 	Md5Key = "Ks@h64WJ#tcVgG8$&WlNfqvLAtMgpxWN"
+	DesKey = "6WpHp4vSvLVQK8SLioNZ7WMq" // 接口返回加密KEY
 )
 
 // 常量定义
@@ -12,6 +13,7 @@ const (
 	FormatDateTime        = "2006-01-02 15:04:05"     //完整时间格式
 	HlbFormatDateTime     = "2006-01-02_15:04:05.999" //完整时间格式
 	FormatDateTimeUnSpace = "20060102150405"          //完整时间格式
+	FormatDateTimeMinute  = "2006-01-02 15:04"        //时间格式只精确到分钟
 	PageSize15            = 15                        //列表页每页数据量
 	PageSize5             = 5
 	PageSize10            = 10
@@ -21,9 +23,9 @@ const (
 
 const (
 	APPNAME          = "弘则-task"
-	EmailSendToUsers = "317699326@qq.com;984198890@qq.com;512188925@qq.com"
+	EmailSendToUsers = "317699326@qq.com;984198890@qq.com;512188925@qq.com;1404741809@qq.com"
 	//RefreshEdbInfoEmailSendToUsers = "317699326@qq.com;984198890@qq.com;jhwang@hzinsights.com;lnyan@hzinsights.com;vwang@hzinsights.com"
-	RefreshEdbInfoEmailSendToUsers = "317699326@qq.com;984198890@qq.com;jhwang@hzinsights.com;lnyan@hzinsights.com"
+	RefreshEdbInfoEmailSendToUsers = "317699326@qq.com;984198890@qq.com"
 )
 
 // 手机号,电子邮箱正则
@@ -38,27 +40,19 @@ 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 = "544935339@qq.com;lnyan@hzinsights.com;pdzhao@hzinsights.com;glji@hzinsights.com;tshen@hzinsights.com"
+	EmailSendToHzUsers = "lnyan@hzinsights.com;pdzhao@hzinsights.com;glji@hzinsights.com;tshen@hzinsights.com"
 	//EmailSendToHzUsers = "317699326@qq.com"
 )
 
@@ -171,3 +165,15 @@ const (
 const (
 	TEMPLATE_MSG_YB_VOICE_BROADCAST = 20 //研报语音播报
 )
+
+const (
+	CYGX_YANXUAN_POINTS_KEY string = "CYGX_YANXUAN_POINTS_KEY" //查研观向研选活动扣点KEY
+	YI_YAO_NAME             string = "医药"
+	YI_YAO_ID               int    = 22
+	XIAO_FEI_NAME           string = "消费"
+	XIAO_FEI_ID             int    = 21
+	KE_JI_NAME              string = "科技"
+	KE_JI_ID                int    = 20
+	ZHI_ZAO_NAME            string = "智造"
+	ZHI_ZAO_ID              int    = 19
+)

+ 187 - 0
utils/des3.go

@@ -0,0 +1,187 @@
+// 加密工具类,用了3des和base64
+package utils
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/des"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"strings"
+)
+
+// des3 + base64 encrypt
+func DesBase64Encrypt(origData []byte) []byte {
+	result, err := TripleDesEncrypt(origData, []byte(DesKey))
+	if err != nil {
+		panic(any(err))
+	}
+	return []byte(base64.StdEncoding.EncodeToString(result))
+}
+
+func DesBase64Decrypt(crypted []byte) []byte {
+	result, _ := base64.StdEncoding.DecodeString(string(crypted))
+	remain := len(result) % 8
+	if remain > 0 {
+		mod := 8 - remain
+		for i := 0; i < mod; i++ {
+			result = append(result, 0)
+		}
+	}
+	origData, err := TripleDesDecrypt(result, []byte(DesKey))
+	if err != nil {
+		panic(any(err))
+	}
+	return origData
+}
+
+// 3DES加密
+func TripleDesEncrypt(origData, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	origData = PKCS5Padding(origData, block.BlockSize())
+	// origData = ZeroPadding(origData, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, key[:8])
+	crypted := make([]byte, len(origData))
+	blockMode.CryptBlocks(crypted, origData)
+	return crypted, nil
+}
+
+// 3DES解密
+func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockMode := cipher.NewCBCDecrypter(block, key[:8])
+	origData := make([]byte, len(crypted))
+	// origData := crypted
+	blockMode.CryptBlocks(origData, crypted)
+	origData = PKCS5UnPadding(origData)
+	// origData = ZeroUnPadding(origData)
+	return origData, nil
+}
+
+func ZeroPadding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{0}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func ZeroUnPadding(origData []byte) []byte {
+	length := len(origData)
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func PKCS5UnPadding(origData []byte) []byte {
+	length := len(origData)
+	// 去掉最后一个字节 unpadding 次
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+// DES加密
+func DesEncrypt(content string, key string) string {
+	contents := []byte(content)
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return ""
+	}
+	contents = PKCS5Padding(contents, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, keys)
+	crypted := make([]byte, len(contents))
+	blockMode.CryptBlocks(crypted, contents)
+	return byteToHexString(crypted)
+}
+
+func byteToHexString(bytes []byte) string {
+	str := ""
+	for i := 0; i < len(bytes); i++ {
+		sTemp := hex.EncodeToString([]byte{bytes[i]})
+		if len(sTemp) < 2 {
+			str += string(0)
+		}
+		str += strings.ToUpper(sTemp)
+	}
+	return str
+}
+
+// DES解密
+func DesDecrypt(content string, key string) string {
+	contentBytes, err := hex.DecodeString(content)
+	if err != nil {
+		return "字符串转换16进制数组失败" + err.Error()
+	}
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return "解密失败" + err.Error()
+	}
+	blockMode := cipher.NewCBCDecrypter(block, keys)
+	origData := contentBytes
+	blockMode.CryptBlocks(origData, contentBytes)
+	origData = ZeroUnPadding(origData)
+	return string(origData)
+}
+
+// DES ECB PKCK5Padding
+func EntryptDesECB(data, key []byte) (string, error) {
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return "", errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	data = PKCS5Padding(data, bs)
+	if len(data)%bs != 0 {
+		return "", errors.New("EntryptDesECB Need a multiple of the blocksize")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Encrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	return base64.StdEncoding.EncodeToString(out), nil
+}
+
+func DecryptDESECB(d string, key []byte) ([]byte, error) {
+	data, err := base64.StdEncoding.DecodeString(d)
+	if err != nil {
+		return nil, errors.New("decodebase64 " + err.Error())
+	}
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return nil, errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	if len(data)%bs != 0 {
+		return nil, errors.New("DecryptDES crypto/cipher: input not full blocks")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Decrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	out = PKCS5UnPadding(out)
+	return out, nil
+}