diff --git a/Dockerfile b/Dockerfile index 777dc69..57c16ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,8 @@ WORKDIR /go/src/miniapp COPY --from=0 /go/src/miniapp/server ./ COPY --from=0 /go/src/miniapp/resource ./resource/ -COPY --from=0 /go/src/miniapp/config.yaml ./ +COPY --from=0 /go/src/miniapp/docker/config.docker.yaml ./docker/ +COPY --from=0 /go/src/miniapp/docker ./ EXPOSE 8888 -ENTRYPOINT ./server -c config.yaml +ENTRYPOINT ./server -c docker/config.docker.yaml diff --git a/api/v1/app/user.go b/api/v1/app/user.go index 46c662f..0351759 100644 --- a/api/v1/app/user.go +++ b/api/v1/app/user.go @@ -1,8 +1,11 @@ package app import ( + "fmt" "git.echol.cn/loser/logger/log" "github.com/gin-gonic/gin" + "github.com/medivhzhan/weapp/v3" + msg "github.com/medivhzhan/weapp/v3/subscribemessage" "miniapp/api" "miniapp/model/app" "miniapp/model/app/request" @@ -86,6 +89,13 @@ func (u *UserApi) UpdateUser(ctx *gin.Context) { if p.Phone != "" { loginUser.Phone = p.Phone } + if p.SurgeryTime != "" { + //t, _ := time.Parse("2006-01-02", p.SurgeryTime) + //format := t.UTC().Format("2006-01-02") + loginUser.SurgeryTime = p.SurgeryTime + } + loginUser.IsSurgery = p.IsSurgery + loginUser.HospitalId = p.HospitalId // 修改数据 if err := userService.UpdateUserInfo(&loginUser); err != nil { log.Errorf("修改用户信息失败:%s", err.Error()) @@ -137,3 +147,46 @@ func (u *UserApi) GetInfo(ctx *gin.Context) { } r.OkWithData(userInfo, ctx) } + +// SendMedicineRemind 发送用药提醒消息 +func (u *UserApi) SendMedicineRemind(ctx *gin.Context) { + var req utils.SendRequest + if err := ctx.ShouldBind(&req); err != nil { + r.FailWithMessage("参数错误: "+err.Error(), ctx) + return + } + + //app-id: wxaaf66dbb5c3983b3 + //app-secret: 0abba24dbff43febba1e551651f693b4 + //template-id: PgxoZOOSDgBcmIGd_EVLDnYUmL3eu6NQTAZCsHQeuWY + + sdk := weapp.NewClient("wxaaf66dbb5c3983b3", "0abba24dbff43febba1e551651f693b4") + + msgData := msg.SendRequest{ + ToUser: "o9Fq_6_cYKvOWnyUM3McC11hWsTI", + TemplateID: "PgxoZOOSDgBcmIGd_EVLDnYUmL3eu6NQTAZCsHQeuWY", + Page: "page/index/todo", + MiniprogramState: msg.MiniprogramStateTrial, + Data: msg.SendData{ + "thing1": msg.SendValue{Value: "眼药水"}, + "time2": msg.SendValue{Value: "20:00"}, + "short_thing17": msg.SendValue{Value: "一天4次"}, + "time15": msg.SendValue{Value: "2024-03-20 20:00"}, + }, + } + + send, err := sdk.NewSubscribeMessage().Send(&msgData) + if err != nil { + return + } + + err = send.GetResponseError() + if err != nil { + fmt.Printf("微信返回错误: %#v", err) + return + } + + fmt.Printf("返回结果: %#v", send) + + r.Ok(ctx) +} diff --git a/config.docker.yaml b/config.docker.yaml deleted file mode 100644 index c24676c..0000000 --- a/config.docker.yaml +++ /dev/null @@ -1,203 +0,0 @@ -# miniapp Global Configuration - -# jwt configuration -jwt: - signing-key: qmPlus - expires-time: 7d - buffer-time: 1d - issuer: qmPlus -# zap logger configuration -zap: - level: info - format: console - prefix: "[miniapp]" - director: log - show-line: true - encode-level: LowercaseColorLevelEncoder - stacktrace-key: stacktrace - log-in-console: true - -# redis configuration -redis: - db: 0 - addr: 177.7.0.14:6379 - password: "" - -# email configuration -email: - to: xxx@qq.com - port: 465 - from: xxx@163.com - host: smtp.163.com - is-ssl: true - secret: xxx - nickname: test - -# system configuration -system: - env: public # Change to "develop" to skip authentication for development mode - addr: 8888 - db-type: mysql - oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server.exe.exe/utils/upload/upload.go 中 NewOss函数配置 - use-redis: false # 使用redis - use-multipoint: false - # IP限制次数 一个小时15000次 - iplimit-count: 15000 - # IP限制一个小时 - iplimit-time: 3600 - -# captcha configuration -captcha: - key-long: 6 - img-width: 240 - img-height: 80 - open-captcha: 0 # 0代表一直开启,大于0代表限制次数 - open-captcha-timeout: 3600 # open-captcha大于0时才生效 - -# mysql connect configuration -# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) -mysql: - path: "" - port: "" - config: "" - db-name: "" - username: "" - password: "" - max-idle-conns: 10 - max-open-conns: 100 - log-mode: "" - log-zap: false - -# pgsql connect configuration -# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) -pgsql: - path: "" - port: "" - config: "" - db-name: "" - username: "" - password: "" - max-idle-conns: 10 - max-open-conns: 100 - log-mode: "" - log-zap: false - -db-list: - - disable: true # 是否禁用 - type: "" # 数据库的类型,目前支持mysql、pgsql - alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一 - path: "" - port: "" - config: "" - db-name: "" - username: "" - password: "" - max-idle-conns: 10 - max-open-conns: 100 - log-mode: "" - log-zap: false - - -# local configuration -local: - path: uploads/file - store-path: uploads/file - -# autocode configuration -autocode: - transfer-restart: true - # root 自动适配项目根目录 - # 请不要手动配置,他会在项目加载的时候识别出根路径 - root: "" - server: /server.exe.exe - server-plug: /plugin/%s - server-api: /api/v1/%s - server-initialize: /initialize - server-model: /model/%s - server-request: /model/%s/request/ - server-router: /router/%s - server-service: /service/%s - web: /web/src - web-api: /api - web-form: /view - web-table: /view - -# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址) -qiniu: - zone: ZoneHuaDong - bucket: "" - img-path: "" - use-https: false - access-key: "" - secret-key: "" - use-cdn-domains: false - -# aliyun oss configuration -aliyun-oss: - endpoint: yourEndpoint - access-key-id: yourAccessKeyId - access-key-secret: yourAccessKeySecret - bucket-name: yourBucketName - bucket-url: yourBucketUrl - base-path: yourBasePath - -# tencent cos configuration -tencent-cos: - bucket: xxxxx-10005608 - region: ap-shanghai - secret-id: your-secret-id - secret-key: your-secret-key - base-url: https://gin.vue.admin - path-prefix: miniapp - -# aws s3 configuration (minio compatible) -aws-s3: - bucket: xxxxx-10005608 - region: ap-shanghai - endpoint: "" - s3-force-path-style: false - disable-ssl: false - secret-id: your-secret-id - secret-key: your-secret-key - base-url: https://gin.vue.admin - path-prefix: miniapp - -# huawei obs configuration -hua-wei-obs: - path: you-path - bucket: you-bucket - endpoint: you-endpoint - access-key: you-access-key - secret-key: you-secret-key - -# excel configuration -excel: - dir: ./resource/excel/ - -# timer task db clear table -Timer: - start: true - spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3 - detail: - - tableName: sys_operation_records - compareField: created_at - interval: 2160h - - tableName: jwt_blacklists - compareField: created_at - interval: 168h - -# 跨域配置 -# 需要配合 server.exe.exe/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用 -cors: - mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝 - whitelist: - - allow-origin: example1.com - allow-headers: content-type - allow-methods: GET, POST - expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type - allow-credentials: true # 布尔值 - - allow-origin: example2.com - allow-headers: content-type - allow-methods: GET, POST - expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type - allow-credentials: true # 布尔值 \ No newline at end of file diff --git a/config/wechat.go b/config/wechat.go index a11f242..2d7851e 100644 --- a/config/wechat.go +++ b/config/wechat.go @@ -1,12 +1,12 @@ package config +// MiniApp 微信小程序配置 type MiniApp struct { - AppId string `mapstructure:"app-id" yaml:"app-id"` - AppSecret string `mapstructure:"app-secret" yaml:"app-secret"` + AppId string `mapstructure:"app-id" yaml:"app-id"` + AppSecret string `mapstructure:"app-secret" yaml:"app-secret"` + TemplateID string `mapstructure:"template-id" yaml:"template-id"` } -// 微信小程序配置 - // 微信支付配置 type wechatPayConfig struct { MchId string `mapstructure:"mchId" yaml:"mchId"` // 商户号 diff --git a/config.yaml b/config2.yaml similarity index 95% rename from config.yaml rename to config2.yaml index 4f9f8ae..510c40c 100644 --- a/config.yaml +++ b/config2.yaml @@ -79,12 +79,12 @@ mini-app: app-secret: 0abba24dbff43febba1e551651f693b4 mysql: prefix: "" - port: "3307" + port: "3306" config: charset=utf8mb4&parseTime=True&loc=Local - db-name: mini_app - username: root + db-name: jmyl + username: jmyl password: Jmyl123123 - path: 47.116.50.126 + path: 101.35.50.11 engine: "" log-mode: error max-idle-conns: 10 @@ -92,8 +92,8 @@ mysql: singular: false log-zap: false redis: - addr: 47.116.50.126:6378 - password: "Jmyl123123" + addr: 101.35.50.11:6379 + password: "loser7659" db: 0 system: env: public diff --git a/docker/config.docker.yaml b/docker/config.docker.yaml new file mode 100644 index 0000000..afd3c30 --- /dev/null +++ b/docker/config.docker.yaml @@ -0,0 +1,132 @@ +aliyun-oss: + endpoint: oss-cn-chengdu.aliyuncs.com + access-key-id: LTAI5tFHes6HBWJFUjuPwHso + access-key-secret: qXuWtEJvYEQvj9yhkmLYfRxHShheYa + bucket-name: jmyl-app + bucket-url: https://jmyl-app.oss-cn-chengdu.aliyuncs.com + base-path: miniapp +autocode: + server-model: /model/%s + server-router: /router/%s + server: /server.exe.exe + server-api: /api/v1/%s + server-plug: /plugin/%s + server-initialize: /initialize + root: C:\Users\Administrator\Desktop + web-table: /view + web: /web/src + server-service: /service/%s + server-request: /model/%s/request/ + web-api: /api + web-form: /view + transfer-restart: true +captcha: + key-long: 4 + img-width: 240 + img-height: 80 + open-captcha: 0 + open-captcha-timeout: 3600 +cors: + mode: strict-whitelist + whitelist: + - allow-origin: example1.com + allow-methods: POST, GET + allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + allow-credentials: true + - allow-origin: example2.com + allow-methods: GET, POST + allow-headers: content-type + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + allow-credentials: true +db-list: + - type: "" + alias-name: "" + prefix: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + path: "" + engine: "" + log-mode: "" + max-idle-conns: 10 + max-open-conns: 100 + singular: false + log-zap: false + disable: true +email: + to: xxx@qq.com + from: xxx@163.com + host: smtp.163.com + secret: xxx + nickname: test + port: 465 + is-ssl: true +excel: + dir: ./resource/excel/ +jwt: + signing-key: f2b1b2af-c8f1-43cf-88e4-40b0a64b5487 + expires-time: 7d + buffer-time: 1d + issuer: qmPlus +local: + path: uploads/file + store-path: uploads/file +mini-app: + app-id: wxaaf66dbb5c3983b3 + app-secret: 0abba24dbff43febba1e551651f693b4 +mysql: + prefix: "" + port: "3307" + config: charset=utf8mb4&parseTime=True&loc=Local + db-name: mini_app + username: root + password: Jmyl123123 + path: 47.113.103.195 + engine: "" + log-mode: error + max-idle-conns: 10 + max-open-conns: 100 + singular: false + log-zap: false +redis: + addr: 127.0.0.1:6379 + password: "Jmyl123123" + db: 0 +system: + env: public + db-type: mysql + oss-type: aliyun-oss + router-prefix: "" + addr: 8888 + iplimit-count: 15000 + iplimit-time: 3600 + use-multipoint: false + use-redis: true +timer: + spec: '@daily' + detail: + - tableName: sys_operation_records + compareField: created_at + interval: 2160h + - tableName: jwt_blacklists + compareField: created_at + interval: 168h + start: true + with_seconds: false +zap: + level: info + prefix: '[miniapp]' + format: console + director: log + encode-level: LowercaseColorLevelEncoder + stacktrace-key: stacktrace + max-age: 0 + show-line: true + log-in-console: true + + + + diff --git a/go.mod b/go.mod index 4026b06..06d5e1e 100644 --- a/go.mod +++ b/go.mod @@ -15,13 +15,14 @@ require ( github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 github.com/gin-gonic/gin v1.9.1 github.com/glebarez/sqlite v1.8.0 + github.com/go-co-op/gocron v1.13.0 github.com/go-oauth2/oauth2/v4 v4.5.2 github.com/go-oauth2/redis/v4 v4.1.1 github.com/go-redis/redis/v8 v8.11.4 github.com/go-sql-driver/mysql v1.7.1 github.com/gofrs/uuid/v5 v5.0.0 github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84 @@ -37,7 +38,7 @@ require ( github.com/shirou/gopsutil/v3 v3.23.6 github.com/songzhibin97/gkit v1.2.11 github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.2 diff --git a/go.sum b/go.sum index d91acc2..81e64f4 100644 --- a/go.sum +++ b/go.sum @@ -137,6 +137,8 @@ github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0 github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc= github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8= +github.com/go-co-op/gocron v1.13.0 h1:BjkuNImPy5NuIPEifhWItFG7pYyr27cyjS6BN9w/D4c= +github.com/go-co-op/gocron v1.13.0/go.mod h1:GD5EIEly1YNW+LovFVx5dzbYVcIc8544K99D8UVRpGo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -264,8 +266,9 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -467,8 +470,8 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -480,8 +483,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= diff --git a/initialize/router.go b/initialize/router.go index 983511e..2620dc3 100644 --- a/initialize/router.go +++ b/initialize/router.go @@ -1,11 +1,10 @@ package initialize import ( - swaggerFiles "github.com/swaggo/files" - "net/http" - "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" + "net/http" "miniapp/global" "miniapp/middleware" @@ -31,7 +30,7 @@ func Routers() *gin.Engine { Router.StaticFS(global.GVA_CONFIG.Local.StorePath, http.Dir(global.GVA_CONFIG.Local.StorePath)) // 为用户头像和文件提供静态地址 // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.exe.exe.exe.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件") // 跨域,如需跨域可以打开下面的注释 - // Router.Use(middleware.Cors()) // 直接放行全部跨域请求 + Router.Use(middleware.Cors()) // 直接放行全部跨域请求 // Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求 //global.GVA_LOG.Info("use middleware cors") diff --git a/main.go b/main.go index cf00999..49b1563 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "go.uber.org/zap" "miniapp/client" "miniapp/oauth2" + "miniapp/task" "miniapp/core" "miniapp/global" @@ -41,5 +42,6 @@ func main() { client.InitRedisClient() oauth2.InitOAuth2Server() // 初始化OAuth2服务 initialize.InitKp() // 初始化关键词处理器 + task.InitTask() // 初始化定时任务 core.RunWindowsServer() } diff --git a/model/app/request/user.go b/model/app/request/user.go index 87b40b1..6a1eabf 100644 --- a/model/app/request/user.go +++ b/model/app/request/user.go @@ -21,9 +21,12 @@ type GetUserList struct { // ChangeUserInfo 修改普通用户信息 type ChangeUserInfo struct { - Nickname string `json:"nickname" form:"nickname"` // 昵称 - Avatar string `json:"avatar" form:"avatar"` // 头像 - Phone string `json:"phone" form:"phone"` // 手机号 + Nickname string `json:"nickname" form:"nickname"` // 昵称 + Avatar string `json:"avatar" form:"avatar"` // 头像 + Phone string `json:"phone" form:"phone"` // 手机号 + IsSurgery int `json:"isSurgery" gorm:"default:0;comment:是否已经手术 0未手术 1已手术"` + HospitalId int `json:"hospitalId" gorm:"comment:手术医院"` + SurgeryTime string `json:"surgery_time" gorm:"comment:手术时间"` } // ChangePassword 修改密码 diff --git a/model/app/response/user.go b/model/app/response/user.go index db46508..e91a3d2 100644 --- a/model/app/response/user.go +++ b/model/app/response/user.go @@ -34,7 +34,7 @@ type UserVO struct { IsSurgery int `json:"isSurgery" gorm:"default:0;comment:是否已经手术 0未手术 1已手术"` IsInfo int `json:"isInfo" gorm:"default:0;comment:是否已经填写信息 0未填写 1已填写"` HospitalId int `json:"hospital_id" gorm:"comment:手术医院"` - SurgeryTime time.Time `json:"surgery_time" gorm:"comment:手术时间"` + SurgeryTime string `json:"surgery_time" gorm:"comment:手术时间"` Todos []common.UserTodo `json:"todos" gorm:"comment:用户待办事项"` Notes []common.Notes `json:"notes" gorm:"comment:用户须知"` } diff --git a/model/app/user.go b/model/app/user.go index 63e1b68..cce1e59 100644 --- a/model/app/user.go +++ b/model/app/user.go @@ -17,7 +17,7 @@ type User struct { IsSurgery int `json:"isSurgery" gorm:"default:0;comment:是否已经手术 0未手术 1已手术"` IsInfo int `json:"isInfo" gorm:"default:0;comment:是否已经填写信息 0未填写 1已填写"` HospitalId int `json:"hospital_id" gorm:"comment:手术医院"` - SurgeryTime time.Time `json:"surgery_time" gorm:"comment:手术时间"` + SurgeryTime string `json:"surgery_time" gorm:"comment:手术时间"` LastLoginAt *time.Time `json:"last_login_at" gorm:"comment:'最后登录时间'"` } diff --git a/model/common/request/notes.go b/model/common/request/notes.go index 5f1329f..c3f9fa2 100644 --- a/model/common/request/notes.go +++ b/model/common/request/notes.go @@ -4,3 +4,10 @@ type GetUserNotes struct { UserId string `json:"userId" form:"userId" binding:"required"` IsFinish string `json:"isFinish" form:"isFinish" binding:"required"` } + +type TodoTask struct { + OpenId string `json:"openId" form:"openId"` + Context string `json:"context" form:"context"` + Frequency string `json:"frequency" form:"frequency"` + RemindTime string `json:"remindTime" form:"remindTime"` +} diff --git a/model/common/todos.go b/model/common/todos.go index 353b84a..d3e1d55 100644 --- a/model/common/todos.go +++ b/model/common/todos.go @@ -5,7 +5,11 @@ import "miniapp/global" type Todos struct { // 任务列表 global.GVA_MODEL - Content string `json:"content" form:"content" gorm:"comment:任务内容;"` + Content string `json:"content" form:"content" gorm:"comment:任务内容;"` + RemindTime string `json:"remindTime" form:"remindTime" gorm:"comment:提醒时间;"` + RemindPeriod int `json:"remindPeriod" form:"remindPeriod" gorm:"comment:0术前,1术后;"` + Frequency string `json:"frequency" form:"frequency" gorm:"comment:用药频率;"` + RemindDay string `json:"remindDay" form:"remindDay" gorm:"comment:提醒日期;"` } func (t Todos) TableName() string { diff --git a/model/common/user_todo.go b/model/common/user_todo.go index 2663a2b..1615d11 100644 --- a/model/common/user_todo.go +++ b/model/common/user_todo.go @@ -4,9 +4,13 @@ import "miniapp/global" type UserTodo struct { global.GVA_MODEL - UserId int `json:"userId" form:"userId" gorm:"comment:用户id;"` - Content string `json:"content" form:"content" gorm:"comment:任务内容;"` - IsFinish int `json:"isFinish" form:"isFinish" gorm:"comment:是否完成;"` + UserId int `json:"userId" form:"userId" gorm:"comment:用户id;"` + Content string `json:"content" form:"content" gorm:"comment:任务内容;"` + IsFinish int `json:"isFinish" form:"isFinish" gorm:"comment:是否完成;"` + RemindTime string `json:"remindTime" form:"remindTime" gorm:"comment:提醒时间;"` + RemindPeriod int `json:"remindPeriod" form:"remindPeriod" gorm:"comment:术前,术后;"` + Frequency string `json:"frequency" form:"frequency" gorm:"comment:用药频率;"` + RemindDay string `json:"remindDay" form:"remindDay" gorm:"comment:提醒日期;"` } func (u UserTodo) TableName() string { diff --git a/model/system/sys_operation_record.go b/model/system/sys_operation_record.go index 314ef9c..1a3f23d 100644 --- a/model/system/sys_operation_record.go +++ b/model/system/sys_operation_record.go @@ -15,7 +15,7 @@ type SysOperationRecord struct { Path string `json:"path" form:"path" gorm:"column:path;comment:请求路径"` // 请求路径 Status int `json:"status" form:"status" gorm:"column:status;comment:请求状态"` // 请求状态 Latency time.Duration `json:"latency" form:"latency" gorm:"column:latency;comment:延迟" swaggertype:"string"` // 延迟 - Agent string `json:"agent" form:"agent" gorm:"column:agent;comment:代理"` // 代理 + Agent string `json:"agent" form:"agent" gorm:"column:agent; type:longtext; comment:代理"` // 代理 ErrorMessage string `json:"error_message" form:"error_message" gorm:"column:error_message;comment:错误信息"` // 错误信息 Body string `json:"body" form:"body" gorm:"type:text;column:body;comment:请求Body"` // 请求Body Resp string `json:"resp" form:"resp" gorm:"type:text;column:resp;comment:响应Body"` // 响应Body diff --git a/oauth2/handle/oauth2.go b/oauth2/handle/oauth2.go index 9844039..a49e1c5 100644 --- a/oauth2/handle/oauth2.go +++ b/oauth2/handle/oauth2.go @@ -116,6 +116,9 @@ func ExtensionFields(ti oauth2.TokenInfo) (fieldsValue map[string]any) { fieldsValue["IsSurgery"] = userInfo.IsSurgery fieldsValue["HospitalId"] = userInfo.HospitalId fieldsValue["IsInfo"] = userInfo.IsInfo + fieldsValue["SurgeryTime"] = userInfo.SurgeryTime + fieldsValue["HospitalId"] = userInfo.HospitalId + fieldsValue["IsSurgery"] = userInfo.IsSurgery return } diff --git a/router/app/user.go b/router/app/user.go index 2b197ab..a2c6f61 100644 --- a/router/app/user.go +++ b/router/app/user.go @@ -25,6 +25,7 @@ func (s *LoginRouter) InitUserRouter(Router *gin.RouterGroup) { userRouter.GET("todo", todoApi.GetUserTodos) userRouter.PUT("todo", todoApi.UpdateTodo) + userRouter.POST("msg", baseApi.SendMedicineRemind) } } diff --git a/service/app/user.go b/service/app/user.go index 96071c3..9c1b5e4 100644 --- a/service/app/user.go +++ b/service/app/user.go @@ -10,6 +10,7 @@ import ( "miniapp/model/app/request" "miniapp/model/app/response" "miniapp/model/common" + "strings" "time" ) @@ -49,7 +50,53 @@ func (UserService) CheckUnionIdIsExist(unionId, openId string) bool { // UpdateUserInfo 更新普通用户信息 func (UserService) UpdateUserInfo(e *app.User) (err error) { - return global.GVA_DB.Updates(&e).Error + tUser := app.User{} + err = global.GVA_DB.Where("id = ?", e.ID).First(&tUser).Error + + err = global.GVA_DB.Updates(&e).Error + if err != nil { + global.GVA_LOG.Error("更新用户信息失败", zap.Error(err)) + return + } + + if e.SurgeryTime != "" { + if tUser.HospitalId != e.HospitalId { + + err = global.GVA_DB.Delete(&common.UserTodo{}, "user_id = ?", e.ID).Error + if err != nil { + global.GVA_LOG.Error("清除用户Todo列表失败", zap.Error(err)) + return + } + var hospital common.Hospital + err = global.GVA_DB.Where("id = ?", e.HospitalId).Preload("Todos").First(&hospital).Error + if err != nil { + global.GVA_LOG.Error("获取医院信息失败", zap.Error(err)) + return + } + + if len(hospital.Todos) != 0 { + var userTodos []common.UserTodo + for _, todo := range hospital.Todos { + for _, s := range strings.Split(todo.RemindTime, ",") { + userTodos = append(userTodos, common.UserTodo{ + Content: todo.Content, + UserId: int(e.ID), + IsFinish: 0, + RemindTime: s, + RemindPeriod: todo.RemindPeriod, + }) + } + } + err = global.GVA_DB.Create(&userTodos).Error + if err != nil { + global.GVA_LOG.Error("创建用户Todo列表失败", zap.Error(err)) + return + } + } + } + } + + return err } // GetAverageUserList 查询普通用户列表 @@ -126,11 +173,15 @@ func (UserService) UpdateUserHospital(r *request.ChangeUserHospital) (err error) if len(hospital.Todos) != 0 { var userTodos []common.UserTodo for _, todo := range hospital.Todos { - userTodos = append(userTodos, common.UserTodo{ - Content: todo.Content, - UserId: r.UserId, - IsFinish: 0, - }) + for _, s := range strings.Split(todo.RemindTime, ",") { + userTodos = append(userTodos, common.UserTodo{ + Content: todo.Content, + UserId: r.UserId, + IsFinish: 0, + RemindTime: s, + RemindPeriod: todo.RemindPeriod, + }) + } } err = global.GVA_DB.Create(&userTodos).Error if err != nil { @@ -139,9 +190,9 @@ func (UserService) UpdateUserHospital(r *request.ChangeUserHospital) (err error) } } - t, _ := time.Parse("2006-01-02", r.SurgeryTime) + //t, _ := time.Parse("2006-01-02", r.SurgeryTime) - err = global.GVA_DB.Table("t_user").Where("id = ?", r.UserId).Updates(app.User{IsInfo: 1, HospitalId: r.HospitalId, SurgeryTime: t}).Error + err = global.GVA_DB.Table("t_user").Where("id = ?", r.UserId).Updates(app.User{IsInfo: 1, HospitalId: r.HospitalId, SurgeryTime: r.SurgeryTime}).Error if err != nil { global.GVA_LOG.Error("更新用户信息失败", zap.Error(err)) return diff --git a/service/system/hospital.go b/service/system/hospital.go index 3aee325..90c028a 100644 --- a/service/system/hospital.go +++ b/service/system/hospital.go @@ -23,12 +23,48 @@ func (h HospitalService) CreateHospital(hospital *common.Hospital) (err error) { // UpdateHospital 更新医院 func (h HospitalService) UpdateHospital(hospital *common.Hospital) (err error) { - return global.GVA_DB.Updates(&hospital).Error + for i := range hospital.Notes { + if hospital.Notes[i].NotesTime == "手术前" { + hospital.Notes[i].NotestimeNum = 1 + } else if hospital.Notes[i].NotesTime == "手术中" { + hospital.Notes[i].NotestimeNum = 2 + } else { + hospital.Notes[i].NotestimeNum = 3 + } + } + err = global.GVA_DB.Model(&common.Notes{}).Save(&hospital.Notes).Error + if err != nil { + global.GVA_LOG.Error("更新医院注意事项失败", zap.Error(err)) + return + } + err = global.GVA_DB.Model(&hospital).Association("Notes").Replace(&hospital.Notes) + if err != nil { + global.GVA_LOG.Error("更新医院注意事项失败", zap.Error(err)) + return + } + + err = global.GVA_DB.Model(&common.Todos{}).Save(&hospital.Todos).Error + if err != nil { + global.GVA_LOG.Error("更新医院Todo失败", zap.Error(err)) + return + } + err = global.GVA_DB.Model(&hospital).Association("Todos").Replace(&hospital.Todos) + if err != nil { + global.GVA_LOG.Error("更新医院Todo关联失败", zap.Error(err)) + return + } + + return global.GVA_DB.Save(&hospital).Error } // DeleteHospital 删除医院 func (h HospitalService) DeleteHospital(hospital *common.Hospital) (err error) { - return global.GVA_DB.Select("Notes").Delete(&hospital).Error + err = global.GVA_DB.Model(&hospital).Association("Notes").Clear() + err = global.GVA_DB.Model(&hospital).Association("Todos").Clear() + if err != nil { + return err + } + return global.GVA_DB.Delete(&hospital).Error } // GetHospitalById 根据id获取医院 diff --git a/task/task.go b/task/task.go new file mode 100644 index 0000000..ab49bce --- /dev/null +++ b/task/task.go @@ -0,0 +1,18 @@ +package task + +import ( + "github.com/go-co-op/gocron" + "time" +) + +var WxTask *gocron.Scheduler + +func InitTask() { + WxTask = gocron.NewScheduler(time.Local) + + // 每天凌晨1点执行 + _, _ = WxTask.Every(1).Day().At("01:00").Do(CheckUserSurgeryDate) // 检查用户是否已到手术日期 + + // 开启定时任务 + WxTask.StartAsync() +} diff --git a/task/user.go b/task/user.go new file mode 100644 index 0000000..20a53ca --- /dev/null +++ b/task/user.go @@ -0,0 +1,19 @@ +package task + +import ( + "miniapp/global" + "miniapp/model/app" + "time" +) + +// CheckUserSurgeryDate 检查用户是否已到手术日期 +func CheckUserSurgeryDate() { + var users []app.User + global.GVA_DB.Model(&app.User{}).Find(&users) + for _, user := range users { + parse, _ := time.Parse("2006-01-02", user.SurgeryTime) + if time.Now().Sub(parse).Hours()/24 == 0 { + global.GVA_DB.Model(&app.User{}).Where("id = ?", user.ID).Updates(app.User{IsSurgery: 1}) + } + } +} diff --git a/task/wxMsg.go b/task/wxMsg.go new file mode 100644 index 0000000..3921172 --- /dev/null +++ b/task/wxMsg.go @@ -0,0 +1,83 @@ +package task + +import ( + "fmt" + "github.com/medivhzhan/weapp/v3" + msg "github.com/medivhzhan/weapp/v3/subscribemessage" + "go.uber.org/zap" + "miniapp/global" + "miniapp/model/app" + "miniapp/model/common" + "strconv" + "strings" + "time" +) + +func SendMsg(userId int) { + var user app.User + err := global.GVA_DB.Model(&user).Where("id = ?", userId).Find(&user).Error + if err != nil { + global.GVA_LOG.Error("获取用户信息失败:%s", zap.Error(err)) + return + } + + // 获取用户待办列表 + var userTodo []common.UserTodo + if user.IsSurgery == 0 { + err = global.GVA_DB.Model(&userTodo).Where("user_id = ? and is_finish = ? and remind_period = 0", userId, 0).Find(&userTodo).Error + if err != nil { + global.GVA_LOG.Error("获取用户待办列表:%s", zap.Error(err)) + return + } + } else { + err = global.GVA_DB.Model(&userTodo).Where("user_id = ? and is_finish = ? and remind_period = 1", userId, 0).Find(&userTodo).Error + if err != nil { + global.GVA_LOG.Error("获取用户待办列表:%s", zap.Error(err)) + return + } + } + + // 根据用户手术信息 发送提醒消息 + parse, _ := time.Parse("2006-01-02", user.SurgeryTime) + if time.Now().Sub(parse).Hours()/24 <= -3 { + for _, todo := range userTodo { + jbo, _ := WxTask.Every(1).Day().At(todo.RemindTime).Do(MiniappSendMsg, *user.WechatOpenId, todo.Content, todo.RemindPeriod, todo.RemindTime) // 检查用户待办事项 + s := strings.Split("一天3次", "")[2] + // 将s转为int类型 + atoi, _ := strconv.Atoi(s) + if jbo.RunCount() == atoi { + WxTask.Remove(jbo) + } + } + } +} + +func MiniappSendMsg(openId string, context string, frequency string, remindTime string) { + sdk := weapp.NewClient(global.GVA_CONFIG.MiniApp.AppId, global.GVA_CONFIG.MiniApp.AppSecret) + + msgData := msg.SendRequest{ + ToUser: openId, + TemplateID: "PgxoZOOSDgBcmIGd_EVLDnYUmL3eu6NQTAZCsHQeuWY", + Page: "/page/index/todo", + MiniprogramState: msg.MiniprogramStateTrial, + Data: msg.SendData{ + "thing1": msg.SendValue{Value: context}, + "time2": msg.SendValue{Value: remindTime}, + "short_thing17": msg.SendValue{Value: frequency}, + "time15": msg.SendValue{Value: time.DateTime}, + }, + } + + send, err := sdk.NewSubscribeMessage().Send(&msgData) + if err != nil { + return + } + + err = send.GetResponseError() + if err != nil { + fmt.Printf("微信返回错误: %#v", err) + return + } + + fmt.Printf("返回结果: %#v", send) +} diff --git a/utils/wx_msg.go b/utils/wx_msg.go new file mode 100644 index 0000000..83a7b67 --- /dev/null +++ b/utils/wx_msg.go @@ -0,0 +1,190 @@ +package utils + +import ( + "crypto/sha1" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "miniapp/global" + "net/http" + "net/url" + "sort" + "strings" + "sync" + "time" +) + +//type wxMsg struct{} +// +//func WxMsgUtils() *wxMsg { +// return &wxMsg{} +//} + +type Wechat struct { + appID string + secret string + templateID string + accessToken *AccessToken + sync.Mutex +} + +type OpenIDResponse struct { + Openid string `json:"openid"` + SessionKey string `json:"session_key"` + Unionid string `json:"unionid"` + WxErr +} + +type CheckTokenRequest struct { + Signature string `json:"signature"` + Timestamp string `json:"timestamp"` + Nonce string `json:"nonce"` + Echostr string `json:"echostr"` +} + +type WxErr struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +type AccessToken struct { + AccessToken string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` + WxErr +} + +type SendRequest struct { + Touser string `json:"touser"` + Page string `json:"page"` + Data Message `json:"data"` + EmphasisKeyword string `json:"emphasis_keyword"` +} + +type Message map[string]interface{} + +func (w *Wechat) SendMsg(req SendRequest) (err error) { + token, err := w.GetAccessToken() + if err != nil { + return + } + api, err := TokenAPI("https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send", token) + if err != nil { + return + } + for key := range req.Data { + req.Data[key] = Message{"value": req.Data[key]} + } + body := map[string]interface{}{ + "touser": "o9Fq_6_cYKvOWnyUM3McC11hWsTI", + "template_id": global.GVA_CONFIG.MiniApp.TemplateID, + "page": "/pages/index/todo", + "data": req.Data, + "emphasis_keyword": req.EmphasisKeyword, + } + + b, err := json.Marshal(body) + if err != nil { + return err + } + res, err := http.Post(api, "application/json", strings.NewReader(string(b))) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != 200 { + err = errors.New("WECHAT_SERVER_ERROR") + return err + } + + var resp WxErr + if err = json.NewDecoder(res.Body).Decode(&resp); err != nil { + return err + } + if resp.Errcode != 0 { + return errors.New(resp.Errmsg) + } + return nil + +} + +func (w *Wechat) GetAccessToken() (token string, err error) { + w.Lock() + defer w.Unlock() + if w.accessToken == nil || w.accessToken.ExpiresIn < time.Now().Unix() { + for i := 0; i < 3; i++ { + err = w.getAccessToken() + if err == nil { + break + } + time.Sleep(time.Second) + } + if err != nil { + return + } + } + token = w.accessToken.AccessToken + return +} + +func (w *Wechat) CheckSignature(req CheckTokenRequest) (err error) { + if sig := w.sortSha1(req.Timestamp, req.Nonce, req.Echostr); sig != req.Signature { + err = errors.New("check signature failed.") + return + } + return +} + +func TokenAPI(api, token string) (string, error) { + u, err := url.Parse(api) + if err != nil { + return "", err + } + query := u.Query() + query.Set("access_token", token) + u.RawQuery = query.Encode() + + return u.String(), nil +} + +func (w *Wechat) getAccessToken() (err error) { + urls, err := url.Parse("https://api.weixin.qq.com/cgi-bin/token") + if err != nil { + return + } + query := urls.Query() + query.Set("appid", w.appID) + query.Set("secret", w.secret) + query.Set("grant_type", "client_credential") + + urls.RawQuery = query.Encode() + + res, err := http.Get(urls.String()) + if err != nil { + return + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return errors.New("wechat internal server error.") + } + + var token AccessToken + if err = json.NewDecoder(res.Body).Decode(&token); err != nil { + return + } + + if token.Errcode != 0 { + return errors.New(token.Errmsg) + } + w.accessToken.AccessToken = token.AccessToken + w.accessToken.ExpiresIn = token.ExpiresIn + return +} + +func (w *Wechat) sortSha1(s ...string) string { + sort.Strings(s) + h := sha1.New() + h.Write([]byte(strings.Join(s, ""))) + return fmt.Sprintf("%x", h.Sum(nil)) +}