├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── go.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _config.yml ├── go.mod ├── go.sum ├── internal ├── helper │ ├── http.go │ └── json.go ├── jpush │ ├── builder │ │ └── define.go │ ├── client.go │ ├── jpushconst.go │ └── report.go ├── jums │ ├── jums.go │ ├── message.go │ └── users.go ├── mi │ ├── api │ │ ├── channel.go │ │ ├── message.go │ │ ├── topic.go │ │ └── tracer.go │ ├── builder │ │ ├── builder.go │ │ ├── channel.go │ │ └── topic.go │ ├── domain.go │ └── miconst.go └── umeng │ └── builder │ └── define.go ├── jpush.go ├── mipush.go ├── mipush_test.go ├── revive.toml └── umeng.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | 4 | about: Create a report to help us improve 5 | 6 | title: '' 7 | 8 | labels: '' 9 | 10 | assignees: '' 11 | 12 | 13 | --- 14 | 15 | --- 16 | name: "Bug Report" 17 | 18 | about: Report something that's broken. Please ensure your kratos version is still supported. 19 | 20 | title: '' 21 | 22 | labels: bug 23 | 24 | assignees: '' 25 | 26 | --- 27 | 28 | #### What happened: 29 | 30 | #### What you expected to happen: 31 | 32 | #### How to reproduce it (as minimally and precisely as possible): 33 | 34 | #### Anything else we need to know?: 35 | 36 | #### Environment: 37 | - Go version (use `go version`): 38 | - OS (e.g: `cat /etc/os-release`): 39 | - Others: 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | 4 | about: Suggest an idea for this project 5 | 6 | title: '' 7 | 8 | labels: '' 9 | 10 | assignees: '' 11 | 12 | 13 | --- 14 | 15 | **Is your feature request related to a problem? Please describe.** 16 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 17 | 18 | **Describe the solution you'd like** 19 | A clear and concise description of what you want to happen. 20 | 21 | **Describe alternatives you've considered** 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context** 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "gomod" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '15 15 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go-Push-API-Action 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | - feature/** 9 | - fix/** 10 | 11 | pull_request: 12 | branches: 13 | - main 14 | - develop 15 | - feature/** 16 | - fix/** 17 | 18 | # Always force the use of Go modules 19 | env: 20 | GO111MODULE: on 21 | 22 | jobs: 23 | 24 | golangci: 25 | strategy: 26 | matrix: 27 | go-version: [1.18.x,1.19.x] 28 | name: golangci-lint 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/setup-go@v5 32 | - uses: actions/checkout@v4 33 | - name: golangci-lint 34 | uses: golangci/golangci-lint-action@v6 35 | with: 36 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 37 | version: latest 38 | 39 | Reviveci: 40 | strategy: 41 | matrix: 42 | go-version: [1.18.x,1.19.x] 43 | name: Run Revive Action 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/setup-go@v5 47 | - uses: actions/checkout@v4 48 | - name: Run Revive Action 49 | uses: morphy2k/revive-action@v2 50 | with: 51 | # Path to your Revive config within the repo (optional) 52 | config: revive.toml 53 | 54 | build: 55 | runs-on: ubuntu-latest 56 | 57 | # strategy set 58 | strategy: 59 | matrix: 60 | go: ["1.18", "1.19"] 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | 65 | - name: Set up Go 66 | uses: actions/setup-go@v5 67 | with: 68 | go-version: ${{ matrix.go }} 69 | 70 | - name: Build 71 | run: go build -v ./... 72 | 73 | - name: Test 74 | run: go test -v ./... 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | ### Go template 11 | # Binaries for programs and plugins 12 | *.exe 13 | *.exe~ 14 | *.dll 15 | *.so 16 | *.dylib 17 | 18 | # Test binary, built with `go test -c` 19 | *.test 20 | 21 | # Output of the go coverage tool, specifically when used with LiteIDE 22 | *.out 23 | 24 | # Dependency directories (remove the comment below to include it) 25 | # vendor/ 26 | logs/ -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-dirs: # 设置要忽略的目录 3 | - util 4 | - .*~ 5 | - api/swagger/docs 6 | skip-files: # 设置不需要检查的go源码文件,支持正则匹配 7 | - ".*.my.go$" 8 | - ".*.pb.go$" 9 | - _test.go 10 | 11 | linters: 12 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 13 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 14 | disable-all: true 15 | enable: 16 | - bodyclose 17 | - deadcode 18 | - depguard 19 | - dogsled 20 | - dupl 21 | - errcheck 22 | - funlen 23 | - goconst 24 | # - gocritic 25 | - gocyclo 26 | - gofmt 27 | - goimports 28 | - goprintffuncname 29 | # - gosimple 30 | - govet 31 | - ineffassign 32 | - misspell 33 | - nolintlint 34 | - revive 35 | - rowserrcheck 36 | - staticcheck 37 | - structcheck 38 | - stylecheck 39 | - typecheck 40 | - unconvert 41 | - unparam 42 | - unused 43 | - varcheck 44 | - whitespace 45 | 46 | issues: 47 | # Excluding configuration per-path, per-linter, per-text and per-source 48 | include: 49 | - EXC0002 # disable excluding of issues about comments from golint 50 | exclude-rules: 51 | - linters: 52 | - stylecheck 53 | text: "ST1000:" 54 | - path: _test\.go 55 | linters: 56 | - gomnd 57 | 58 | # https://github.com/go-critic/go-critic/issues/926 59 | - linters: 60 | - gocritic 61 | text: "unnecessaryDefer:" 62 | 63 | linters-settings: 64 | # errcheck: 65 | # check-type-assertions: true # 这里建议设置为true,如果确实不需要检查,可以写成`num, _ := strconv.Atoi(numStr)` 66 | # check-blank: false 67 | lll: 68 | line-length: 240 # 一行的长度 69 | godox: 70 | keywords: # 建议设置为BUG、FIXME、OPTIMIZE、HACK 71 | - BUG 72 | - FIXME 73 | - OPTIMIZE 74 | - HACK 75 | misspell: 76 | locale: US 77 | ignore-words: 78 | - cancelled 79 | goimports: 80 | local-prefixes: github.com/houseme/go-push-api 81 | godot: 82 | # Comments to be checked: `declarations`, `toplevel`, or `all`. 83 | # Default: declarations 84 | scope: toplevel 85 | exclude: 86 | # Exclude sentence fragments for lists. 87 | - '^[ ]*[-•]' 88 | # Exclude sentences prefixing a list. 89 | - ':$' 90 | # Check that each sentence ends with a period. 91 | # Default: true 92 | period: false 93 | # Check that each sentence starts with a capital letter. 94 | # Default: false 95 | capital: false 96 | revive: 97 | ignore-generated-header: true 98 | severity: warning 99 | rules: 100 | - name: atomic 101 | - name: line-length-limit 102 | severity: error 103 | arguments: [ 160 ] 104 | - name: unhandled-error 105 | arguments: [ "fmt.Printf", "myFunction" ] 106 | funlen: 107 | # Checks the number of lines in a function. 108 | # If lower than 0, disable the check. 109 | # Default: 60 110 | lines: 110 111 | # Checks the number of statements in a function. 112 | # If lower than 0, disable the check. 113 | # Default: 40 114 | statements: 60 115 | goconst: 116 | # Ignore test files. 117 | # Default: false 118 | ignore-tests: true 119 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### CONTRIBUTING 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present houseme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-Push-API 2 | 3 | ### MiPush、JiPush、UMeng API Server SDK for Golang. 4 | 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/houseme/go-push-api.svg)](https://pkg.go.dev/github.com/houseme/go-push-api) 6 | ![GitHub](https://img.shields.io/github/license/houseme/go-push-api) 7 | ![GitHub go.mod Go version (branch)](https://img.shields.io/github/go-mod/go-version/houseme/go-push-api/main) 8 | 9 | #### MiPush 10 | 11 | The Xiaomi message push service is a system-level channel on MIUI and is universal across the platform, which can 12 | provide developers with stable, reliable, and efficient push services. 13 | 14 | #### JiPush 15 | 16 | Aurora, push, authentication, SMS, magic chain RESTFul API Go SDK, including auroral push (JPush), auroral magic chain (JMLink), auroral SMS (JSMS), auroral authentication (JVerification) and other related open source projects. 17 | 18 | ## Installation 19 | 20 | `go get -u -v github.com/houseme/go-push-api@main` 21 | 22 | ## Quick Start 23 | ```go 24 | package main 25 | ``` 26 | 27 | ## Contributors 28 | 29 | You can contribute in one of three ways: 30 | 31 | 1. File bug reports using the [issue tracker](https://github.com/houseme/go-push-api/issues). 32 | 2. Answer questions or fix bugs on the [issue tracker](https://github.com/houseme/go-push-api/issues). 33 | 3. Contribute new features or update the wiki. 34 | 35 | 36 | ## License 37 | 38 | The Go-Push-API is open-sourced software licensed under the [MIT license](./LICENSE). 39 | 40 | 41 | ## Security 42 | 43 | [![OSCS Status](https://www.oscs1024.com/platform/badge/houseme/go-push-api.svg?size=large)](https://www.oscs1024.com/project/houseme/go-push-api?ref=badge_large) -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/houseme/go-push-api 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/buger/jsonparser v1.1.2-0.20210620171241-dc92d6932a12 7 | github.com/bytedance/sonic v1.11.9 8 | github.com/cloudwego/hertz v0.9.1 9 | github.com/gofrs/uuid v4.4.0+incompatible 10 | ) 11 | 12 | require ( 13 | github.com/andeya/ameda v1.5.3 // indirect 14 | github.com/andeya/goutil v1.0.1 // indirect 15 | github.com/bytedance/go-tagexpr/v2 v2.9.7 // indirect 16 | github.com/bytedance/gopkg v0.0.0-20230224073017-0b6876860a2f // indirect 17 | github.com/bytedance/sonic/loader v0.1.1 // indirect 18 | github.com/cloudwego/base64x v0.1.4 // indirect 19 | github.com/cloudwego/iasm v0.2.0 // indirect 20 | github.com/cloudwego/netpoll v0.6.0 // indirect 21 | github.com/fsnotify/fsnotify v1.6.0 // indirect 22 | github.com/golang/protobuf v1.5.2 // indirect 23 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 24 | github.com/nyaruka/phonenumbers v1.1.6 // indirect 25 | github.com/tidwall/gjson v1.14.4 // indirect 26 | github.com/tidwall/match v1.1.1 // indirect 27 | github.com/tidwall/pretty v1.2.1 // indirect 28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 29 | golang.org/x/arch v0.2.0 // indirect 30 | golang.org/x/sys v0.5.0 // indirect 31 | golang.org/x/text v0.7.0 // indirect 32 | google.golang.org/protobuf v1.33.0 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andeya/ameda v1.5.3 h1:SvqnhQPZwwabS8HQTRGfJwWPl2w9ZIPInHAw9aE1Wlk= 2 | github.com/andeya/ameda v1.5.3/go.mod h1:FQDHRe1I995v6GG+8aJ7UIUToEmbdTJn/U26NCPIgXQ= 3 | github.com/andeya/goutil v1.0.1 h1:eiYwVyAnnK0dXU5FJsNjExkJW4exUGn/xefPt3k4eXg= 4 | github.com/andeya/goutil v1.0.1/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= 5 | github.com/buger/jsonparser v1.1.2-0.20210620171241-dc92d6932a12 h1:nmnGdW9DwOvOul97rHFt7JPhUHm/8Srz5H9H5U199YI= 6 | github.com/buger/jsonparser v1.1.2-0.20210620171241-dc92d6932a12/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 7 | github.com/bytedance/go-tagexpr/v2 v2.9.2/go.mod h1:5qsx05dYOiUXOUgnQ7w3Oz8BYs2qtM/bJokdLb79wRM= 8 | github.com/bytedance/go-tagexpr/v2 v2.9.7 h1:y1b2Qv1fYFNpFEQ1jC3DCB2NZZNrKIpUYRiEj7TfUYE= 9 | github.com/bytedance/go-tagexpr/v2 v2.9.7/go.mod h1:SyfF2dfcKGKIfTL2trRu+LW4x2mH6ehuigpkvv9JtpY= 10 | github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= 11 | github.com/bytedance/gopkg v0.0.0-20230224073017-0b6876860a2f h1:VdPnKwoTE/nMbtjY5kpiQvbt0Ev7ceSiXwD/C77WZec= 12 | github.com/bytedance/gopkg v0.0.0-20230224073017-0b6876860a2f/go.mod h1:j0HolgUC8qp0ZPWZmsz5gOGcVxLtGt9ADpxxZnKoqek= 13 | github.com/bytedance/mockey v1.2.1 h1:g84ngI88hz1DR4wZTL3yOuqlEcq67MretBfQUdXwrmw= 14 | github.com/bytedance/mockey v1.2.1/go.mod h1:+Jm/fzWZAuhEDrPXVjDf/jLM2BlLXJkwk94zf2JZ3X4= 15 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 16 | github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 17 | github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= 18 | github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 19 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 20 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 21 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 22 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 23 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 24 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 25 | github.com/cloudwego/hertz v0.9.1 h1:+jK9A6MDNTUVy6q/zSOlhbnp1fFMiOaPIsq0jlOfjZE= 26 | github.com/cloudwego/hertz v0.9.1/go.mod h1:cs8dH6unM4oaJ5k9m6pqbgLBPqakGWMG0+cthsxitsg= 27 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 28 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 29 | github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= 30 | github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 35 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 36 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 37 | github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= 38 | github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 39 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 40 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 41 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 42 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 43 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 44 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 46 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 47 | github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= 48 | github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4= 49 | github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ= 50 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 51 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 52 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 53 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 54 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 55 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 56 | github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= 57 | github.com/nyaruka/phonenumbers v1.1.6 h1:DcueYq7QrOArAprAYNoQfDgp0KetO4LqtnBtQC6Wyes= 58 | github.com/nyaruka/phonenumbers v1.1.6/go.mod h1:yShPJHDSH3aTKzCbXyVxNpbl2kA+F+Ne5Pun/MvFRos= 59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 62 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 63 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 64 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 67 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 68 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 69 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 70 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 71 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 73 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 74 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 75 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 76 | github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 77 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 78 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 79 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 80 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 81 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 82 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 83 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 84 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 85 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 86 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 87 | golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 88 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 89 | golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= 90 | golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 91 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 92 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 93 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 94 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 95 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 96 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 97 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 98 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 99 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 102 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 103 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 112 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 114 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 115 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 116 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 117 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 118 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 119 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 120 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 121 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 122 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 123 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 124 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 125 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 126 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 127 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 128 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 129 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 130 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 131 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 132 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 133 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 134 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 135 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 136 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 137 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 138 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 139 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 140 | -------------------------------------------------------------------------------- /internal/helper/http.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/cloudwego/hertz/pkg/app/client" 10 | "github.com/cloudwego/hertz/pkg/protocol" 11 | "github.com/cloudwego/hertz/pkg/protocol/consts" 12 | 13 | "github.com/houseme/go-push-api/internal/mi" 14 | "github.com/houseme/go-push-api/internal/mi/builder" 15 | ) 16 | 17 | var ( 18 | hc *httpClient 19 | ) 20 | 21 | type httpClient struct { 22 | client *client.Client 23 | } 24 | 25 | // Client .http client 26 | func Client() (*httpClient, error) { 27 | c, err := client.NewClient( 28 | client.WithDialTimeout(5 * time.Second), 29 | ) 30 | if err != nil { 31 | return nil, err 32 | } 33 | hc = &httpClient{ 34 | client: c, 35 | } 36 | return hc, nil 37 | } 38 | 39 | // DoPost . 40 | func (c *httpClient) DoPost(ctx context.Context, builder *builder.Builder, params mi.Params) ([]byte, error) { 41 | return c.do(ctx, builder, params, consts.MethodPost) 42 | } 43 | 44 | // DoGet . 45 | func (c *httpClient) DoGet(ctx context.Context, builder *builder.Builder, params mi.Params) ([]byte, error) { 46 | return c.do(ctx, builder, params, consts.MethodGet) 47 | } 48 | 49 | // Do . 50 | func (c *httpClient) do(ctx context.Context, builder *builder.Builder, params mi.Params, method string) ([]byte, error) { 51 | form := c.messageToForm(ctx, builder) 52 | req := &protocol.Request{} 53 | res := &protocol.Response{} 54 | req.SetMethod(method) 55 | req.Header.SetContentTypeBytes([]byte("application/x-www-form-urlencoded;charset=UTF-8")) 56 | req.Header.Add("Authorization", "key="+params.AppSecret) 57 | req.SetRequestURI(c.requestURL(ctx, params.MiEnv, params.MiURL)) 58 | req.SetBodyString(form.Encode()) 59 | err := c.client.Do(context.Background(), req, res) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return res.Body(), nil 64 | } 65 | 66 | // requestURL 请求的url 67 | func (c *httpClient) requestURL(_ context.Context, env mi.RequestEnv, url string) string { 68 | var host = mi.OfficialHost 69 | if env == mi.SandBoxRequestEnv { 70 | host = mi.SandboxHost 71 | } 72 | return host + url 73 | } 74 | 75 | // messageToForm 消息转表单,小米推送接口使用form表单提交 76 | func (c *httpClient) messageToForm(_ context.Context, builder *builder.Builder) *url.Values { 77 | form := &url.Values{} 78 | form.Add("restricted_package_name", builder.RestrictedPackageName) 79 | form.Add("payload", builder.Payload) 80 | form.Add("title", builder.Title) 81 | form.Add("description", builder.Description) 82 | form.Add("notify_type", strconv.FormatInt(builder.NotifyType, 10)) 83 | form.Add("pass_through", strconv.FormatInt(builder.PassThrough, 10)) 84 | 85 | if builder.NotifyID > 0 { 86 | form.Add("notify_id", strconv.FormatInt(builder.NotifyID, 10)) 87 | } 88 | 89 | if builder.TimeToLive > 0 { 90 | form.Add("time_to_live", strconv.FormatInt(builder.TimeToLive, 10)) 91 | } 92 | 93 | if builder.TimeToSend > 0 { 94 | form.Add("time_to_send", strconv.FormatInt(builder.TimeToSend, 10)) 95 | } 96 | 97 | if builder.Extra != nil { 98 | for k, v := range builder.Extra { 99 | form.Add("extra."+k, v) 100 | } 101 | } 102 | 103 | return form 104 | } 105 | -------------------------------------------------------------------------------- /internal/helper/json.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/houseme/go-push-api/internal/mi" 7 | ) 8 | 9 | // ToJSON byte 转化为json 对象 10 | func ToJSON(res []byte) (*mi.Result, error) { 11 | var r *mi.Result 12 | err := json.Unmarshal(res, &r) 13 | if err != nil { 14 | return r, err 15 | } 16 | return r, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/jpush/builder/define.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // @Project: go-push-api 9 | // @Author: houseme 10 | // @Description: 11 | // @File: define 12 | // @Version: 1.0.0 13 | // @Date: 2021/6/28 01:12 14 | // @Package builder 15 | 16 | // SMS . 17 | type SMS struct { 18 | } 19 | 20 | // AudienceTarget . 21 | type AudienceTarget struct { 22 | AudienceType string `json:"audienceType"` 23 | Values []string `json:"values"` 24 | } 25 | 26 | // Builder . 27 | type Builder struct { 28 | Title string `json:"title"` 29 | MsgContent string `json:"msgContent"` 30 | ContentType string `json:"contentType"` 31 | Extras map[string]string `json:"extrasBuilder"` 32 | NumberExtrasBuilder map[string]int64 `json:"numberExtrasBuilder"` 33 | BooleanExtrasBuilder map[string]bool `json:"booleanExtrasBuilder"` 34 | JSONExtrasBuilder map[string]string `json:"jsonExtrasBuilder"` 35 | CustomData map[string]interface{} `json:"customData"` 36 | } 37 | 38 | // NamedValue . 39 | type NamedValue map[string]interface{} 40 | 41 | // TypePlatform . 42 | type TypePlatform string 43 | 44 | const ( 45 | // PlatformAndroid . 46 | PlatformAndroid TypePlatform = "android" 47 | // PlatformIOS . 48 | PlatformIOS TypePlatform = "ios" 49 | // PlatformWinPhone . 50 | PlatformWinPhone TypePlatform = "winphone" 51 | // ALL . 52 | ALL string = "all" 53 | // ALIAS . 54 | ALIAS string = "alias" 55 | // ALERT . 56 | ALERT string = "alert" 57 | // AUDIENCE . 58 | AUDIENCE string = "audience" 59 | // ApnsProduction is the apns production. 60 | ApnsProduction string = "apns_production" 61 | // BuilderID . 62 | BuilderID string = "builder_id" 63 | // ContentType . 64 | ContentType string = "content_type" 65 | // EXTRAS . 66 | EXTRAS string = "extras" 67 | // IosBadge . 68 | IosBadge string = "badge" 69 | // IosSound . 70 | IosSound string = "sound" 71 | // IosContentAvailable . 72 | IosContentAvailable string = "content-available" 73 | // MaxIosLength . 74 | MaxIosLength int = 220 75 | // MaxContentLength of the message. 76 | MaxContentLength int = 1200 77 | // MESSAGE . 78 | MESSAGE string = "message" 79 | // MsgContent . 80 | MsgContent string = "msg_content" 81 | // NOTIFICATION . 82 | NOTIFICATION string = "notification" 83 | // OverrideMsgID fileter the message by message id. 84 | OverrideMsgID string = "override_msg_id" 85 | // OPTIONS . 86 | OPTIONS string = "options" 87 | // PLATFORM . 88 | PLATFORM string = "platform" 89 | // SENDNO . 90 | SENDNO string = "sendno" 91 | // TAG . 92 | TAG string = "tag" 93 | // TagAnd . 94 | TagAnd string = "tag_and" 95 | // TITLE . 96 | TITLE string = "title" 97 | // TTL . 98 | TTL string = "time_to_live" 99 | // REGISTRATION . 100 | REGISTRATION string = "registration_id" 101 | // WpOpenPage . 102 | WpOpenPage string = "_open_page" 103 | ) 104 | 105 | // Platform . 106 | type Platform []TypePlatform 107 | 108 | // NewPlatform . 109 | func NewPlatform() *Platform { 110 | t := make(Platform, 0) 111 | return &t 112 | } 113 | 114 | // AddPlatform . 115 | func (p *Platform) AddPlatform(platform ...TypePlatform) *Platform { 116 | *p = append(*p, platform...) 117 | return p 118 | } 119 | 120 | // Value . 121 | func (p *Platform) Value() interface{} { 122 | if len(*p) == 0 { 123 | return ALL 124 | } 125 | 126 | return p 127 | } 128 | 129 | // Audience . 130 | type Audience map[string][]string 131 | 132 | // NewAudience . 133 | func NewAudience() *Audience { 134 | t := make(Audience) 135 | return &t 136 | } 137 | 138 | // AddCond . 139 | func (a *Audience) AddCond(key, value string) *Audience { 140 | if _, ok := (*a)[key]; !ok { 141 | (*a)[key] = make([]string, 0) 142 | } 143 | 144 | (*a)[key] = append((*a)[key], value) 145 | return a 146 | } 147 | 148 | // AddTag . 149 | func (a *Audience) AddTag(tag string) *Audience { 150 | return a.AddCond(TAG, tag) 151 | } 152 | 153 | // AddTadAnd . 154 | func (a *Audience) AddTadAnd(tagAnd string) *Audience { 155 | return a.AddCond(TagAnd, tagAnd) 156 | } 157 | 158 | // AddAlias . 159 | func (a *Audience) AddAlias(alias string) *Audience { 160 | return a.AddCond(ALIAS, alias) 161 | } 162 | 163 | // AddRegistrationID . 164 | func (a *Audience) AddRegistrationID(id string) *Audience { 165 | return a.AddCond(REGISTRATION, id) 166 | } 167 | 168 | // Value . 169 | func (a *Audience) Value() interface{} { 170 | if len(*a) == 0 { 171 | return ALL 172 | } 173 | 174 | return a 175 | } 176 | 177 | // Extras . 178 | type Extras NamedValue 179 | 180 | // NewExtras . 181 | func NewExtras() *Extras { 182 | e := make(Extras) 183 | return &e 184 | } 185 | 186 | // Add . 187 | func (e *Extras) Add(name string, value interface{}) *Extras { 188 | (*e)[name] = value 189 | return e 190 | } 191 | 192 | // Android . 193 | type Android NamedValue 194 | 195 | // NewAndroid . 196 | func NewAndroid(alert string) *Android { 197 | a := make(Android) 198 | a[ALERT] = alert 199 | return &a 200 | } 201 | 202 | // AddTitle . 203 | func (a *Android) AddTitle(title string) *Android { 204 | (*a)[TITLE] = title 205 | return a 206 | } 207 | 208 | // AddBuilderID . 209 | func (a *Android) AddBuilderID(bid int) *Android { 210 | (*a)[BuilderID] = bid 211 | return a 212 | } 213 | 214 | // AddExtras . 215 | func (a *Android) AddExtras(e *Extras) *Android { 216 | (*a)[EXTRAS] = e 217 | return a 218 | } 219 | 220 | // iOS . 221 | type iOS NamedValue 222 | 223 | // NewIOS . 224 | func NewIOS(alert string) *iOS { 225 | i := make(iOS) 226 | i[ALERT] = alert 227 | i[IosBadge] = 1 228 | i[IosSound] = "" 229 | return &i 230 | } 231 | 232 | // AddSound . 233 | func (i *iOS) AddSound(sd string) *iOS { 234 | (*i)[IosSound] = sd 235 | return i 236 | } 237 | 238 | // AddBadge . 239 | func (i *iOS) AddBadge(bd int) *iOS { 240 | (*i)[IosBadge] = bd 241 | return i 242 | } 243 | 244 | // AddContentAvailable . 245 | func (i *iOS) AddContentAvailable(b bool) *iOS { 246 | (*i)[IosContentAvailable] = b 247 | return i 248 | } 249 | 250 | // AddExtras . 251 | func (i *iOS) AddExtras(e *Extras) *iOS { 252 | (*i)[EXTRAS] = e 253 | return i 254 | } 255 | 256 | // WinPhone . 257 | type WinPhone NamedValue 258 | 259 | // NewWinPhone . 260 | func NewWinPhone(alert string) *WinPhone { 261 | w := make(WinPhone) 262 | w[ALERT] = alert 263 | return &w 264 | } 265 | 266 | // AddTitle . 267 | func (w *WinPhone) AddTitle(title string) *WinPhone { 268 | (*w)[TITLE] = title 269 | return w 270 | } 271 | 272 | // AddOpenPage . 273 | func (w *WinPhone) AddOpenPage(op int) *WinPhone { 274 | (*w)[WpOpenPage] = op 275 | return w 276 | } 277 | 278 | // AddExtras . 279 | func (w *WinPhone) AddExtras(e *Extras) *WinPhone { 280 | (*w)[EXTRAS] = e 281 | return w 282 | } 283 | 284 | // Notification . 285 | type Notification NamedValue 286 | 287 | // NewNotification . 288 | func NewNotification(defaultAlert string) *Notification { 289 | n := make(Notification) 290 | n[ALERT] = defaultAlert 291 | return &n 292 | } 293 | 294 | // AddAndroid . 295 | func (n *Notification) AddAndroid(android *Android) *Notification { 296 | (*n)[string(PlatformAndroid)] = android 297 | return n 298 | } 299 | 300 | // AddIOS . 301 | func (n *Notification) AddIOS(ios *iOS) *Notification { 302 | (*n)[string(PlatformIOS)] = ios 303 | return n 304 | } 305 | 306 | // AddWinPhone . 307 | func (n *Notification) AddWinPhone(wp *WinPhone) *Notification { 308 | (*n)[string(PlatformWinPhone)] = wp 309 | return n 310 | } 311 | 312 | // Message . 313 | type Message NamedValue 314 | 315 | // NewMessage . 316 | func NewMessage(msgContent string) *Message { 317 | m := make(Message) 318 | m[MsgContent] = msgContent 319 | return &m 320 | } 321 | 322 | // AddTitle . 323 | func (m *Message) AddTitle(t string) *Message { 324 | (*m)[TITLE] = t 325 | return m 326 | } 327 | 328 | // AddContentType . 329 | func (m *Message) AddContentType(ct string) *Message { 330 | (*m)[ContentType] = ct 331 | return m 332 | } 333 | 334 | // AddExtras . 335 | func (m *Message) AddExtras(e *Extras) *Message { 336 | (*m)[EXTRAS] = e 337 | return m 338 | } 339 | 340 | // Options . 341 | type Options NamedValue 342 | 343 | // NewOptions . 344 | func NewOptions() *Options { 345 | o := make(Options) 346 | return &o 347 | } 348 | 349 | // AddSendNo . 350 | func (o *Options) AddSendNo(sn int) *Options { 351 | (*o)[SENDNO] = sn 352 | return o 353 | } 354 | 355 | // AddTimeToLive . 356 | func (o *Options) AddTimeToLive(ttl int) *Options { 357 | (*o)[TTL] = ttl 358 | return o 359 | } 360 | 361 | // AddOverrideMsgID . 362 | func (o *Options) AddOverrideMsgID(omi int) *Options { 363 | (*o)[OverrideMsgID] = omi 364 | return o 365 | } 366 | 367 | // AddApnsProduction . 368 | func (o *Options) AddApnsProduction(ap bool) *Options { 369 | (*o)[ApnsProduction] = ap 370 | return o 371 | } 372 | 373 | // PushPayload . 374 | type PushPayload NamedValue 375 | 376 | // NewPushPayload . 377 | func NewPushPayload(p *Platform, a *Audience) *PushPayload { 378 | msg := make(PushPayload) 379 | msg[PLATFORM] = p.Value() 380 | msg[AUDIENCE] = a.Value() 381 | return &msg 382 | } 383 | 384 | // Build . 385 | func (p *PushPayload) Build(key string, comp interface{}) *PushPayload { 386 | (*p)[key] = comp 387 | return p 388 | } 389 | 390 | // AddNotification . 391 | func (p *PushPayload) AddNotification(n *Notification) *PushPayload { 392 | return p.Build(NOTIFICATION, n) 393 | } 394 | 395 | // AddMessage . 396 | func (p *PushPayload) AddMessage(m *Message) *PushPayload { 397 | return p.Build(MESSAGE, m) 398 | } 399 | 400 | // AddOptions . 401 | func (p *PushPayload) AddOptions(o *Options) *PushPayload { 402 | return p.Build(OPTIONS, o) 403 | } 404 | 405 | // ToJSONString . 406 | func (p *PushPayload) ToJSONString() (string, error) { 407 | for k, v := range *p { 408 | if v == nil { 409 | return "", fmt.Errorf("%s without a value", k) 410 | } 411 | } 412 | 413 | if (*p)[NOTIFICATION] == nil && (*p)[MESSAGE] == nil { 414 | return "", fmt.Errorf("without Notification/Message") 415 | } 416 | 417 | var length int 418 | if ntf := (*p)[NOTIFICATION]; ntf != nil { 419 | if n, _ := ntf.(*Notification); n != nil { 420 | if ios := (*n)[string(PlatformIOS)]; ios != nil { 421 | if b, e := json.Marshal(ios); e != nil || len(b) > MaxIosLength { 422 | return "", fmt.Errorf("invalidate ios notification") 423 | } 424 | } 425 | } 426 | 427 | b, e := json.Marshal(ntf) 428 | if e != nil { 429 | return "", e 430 | } 431 | length += len(b) 432 | } 433 | 434 | if msg := (*p)[MESSAGE]; msg != nil { 435 | b, e := json.Marshal(msg) 436 | if e != nil { 437 | return "", e 438 | } 439 | length += len(b) 440 | } 441 | 442 | if length > MaxContentLength { 443 | return "", fmt.Errorf("Notification/Message too large") 444 | } 445 | 446 | b, e := json.Marshal(p) 447 | if e != nil { 448 | return "", e 449 | } 450 | 451 | return string(b), e 452 | } 453 | -------------------------------------------------------------------------------- /internal/jpush/client.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/houseme/go-push-api/internal/jpush/builder" 12 | ) 13 | 14 | const ( 15 | // KeyLength . 16 | KeyLength = 24 17 | // ConnTimeout . 18 | ConnTimeout = 5 19 | // RwTimeout . 20 | RwTimeout = 30 21 | ) 22 | 23 | // Client . 24 | type Client struct { 25 | appKey string 26 | masterSecret string 27 | } 28 | 29 | // NewClient . 30 | func NewClient(appKey, masterSecret string) *Client { 31 | return &Client{appKey, masterSecret} 32 | } 33 | 34 | // Push . 35 | func (c *Client) Push(po *builder.PushPayload) (string, error) { 36 | if len(c.appKey) != KeyLength || len(c.masterSecret) != KeyLength { 37 | return "", fmt.Errorf("invalidate appkey/masterSecret") 38 | } 39 | 40 | poster, e := po.ToJSONString() 41 | if e != nil { 42 | return poster, e 43 | } 44 | 45 | body := strings.NewReader(poster) 46 | req, e := http.NewRequest("POST", "https://api.jpush.cn/v3/push", body) 47 | if e != nil { 48 | return "NewRequest", e 49 | } 50 | 51 | req.Header.Set("User-Agent", "JPush-API-GO-Client") 52 | req.Header.Set("Connection", "Keep-Alive") 53 | req.Header.Set("Accept-Charset", "UTF-8") 54 | req.Header.Set("Content-Type", "application/json") 55 | req.SetBasicAuth(c.appKey, c.masterSecret) 56 | 57 | client := http.Client{ 58 | Transport: &http.Transport{ 59 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 60 | c, err := net.DialTimeout(network, addr, time.Second*ConnTimeout) 61 | if err != nil { 62 | return nil, err 63 | } 64 | if err = c.SetDeadline(time.Now().Add(RwTimeout * time.Second)); err != nil { 65 | return nil, err 66 | } 67 | return c, nil 68 | }, 69 | }, 70 | } 71 | 72 | resp, e := client.Do(req) 73 | if e != nil { 74 | return resp.Status, e 75 | } 76 | defer func() { 77 | _ = resp.Body.Close() 78 | }() 79 | 80 | buf := make([]byte, 4096) 81 | nr, _ := resp.Body.Read(buf) 82 | 83 | if resp.StatusCode == http.StatusOK { 84 | return string(buf[:nr]), nil 85 | } 86 | 87 | return resp.Status, fmt.Errorf(string(buf[:nr])) 88 | } 89 | -------------------------------------------------------------------------------- /internal/jpush/jpushconst.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | // @Project: go-push-api 4 | // @Author: houseme 5 | // @Description: 6 | // @File: jpushconst 7 | // @Version: 1.0.0 8 | // @Date: 2021/6/28 01:27 9 | // @Package builder 10 | 11 | // AudienceType . 12 | type AudienceType string 13 | 14 | const ( 15 | // Tag . 16 | Tag = "tag" 17 | // TagAnd . 18 | TagAnd = "tag_and" 19 | // TagNot . 20 | TagNot = "tag_not" 21 | // ALIAS . 22 | ALIAS = "alias" 23 | // Segment . 24 | Segment = "segment" 25 | // Abtest . 26 | Abtest = "abtest" 27 | // RegistrationID . 28 | RegistrationID = "registration_id" 29 | // File . 30 | File = "file" 31 | ) 32 | -------------------------------------------------------------------------------- /internal/jpush/report.go: -------------------------------------------------------------------------------- 1 | package jpush 2 | 3 | // @Project: go-push-api 4 | // @Author: houseme 5 | // @Description: 6 | // @File: report 7 | // @Version: 1.0.0 8 | // @Date: 2021/6/29 11:12 9 | // @Package jpush 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "fmt" 15 | "net" 16 | "net/http" 17 | "time" 18 | ) 19 | 20 | // ReportClient . 21 | type ReportClient struct { 22 | appKey string 23 | masterSecret string 24 | } 25 | 26 | // ReportObject . 27 | type ReportObject struct { 28 | AndroidReceived int 29 | IosApnsSent int 30 | MsgID int 31 | } 32 | 33 | // NewReportClient . 34 | func NewReportClient(appKey, masterSecret string) *ReportClient { 35 | return &ReportClient{appKey, masterSecret} 36 | } 37 | 38 | // GetReport . 39 | func (c *ReportClient) GetReport(msgID ...int) (string, error) { 40 | if len(c.appKey) != KeyLength || len(c.masterSecret) != KeyLength { 41 | return "", fmt.Errorf("invalidate appkey/masterSecret") 42 | } 43 | 44 | b, e := json.Marshal(msgID) 45 | if e != nil { 46 | return "", e 47 | } 48 | 49 | reqEndpoint := "https://report.jpush.cn" 50 | reqURL := fmt.Sprintf("%s/v2/received?msg_ids=%s", reqEndpoint, string(b[1:len(b)-1])) 51 | req, e := http.NewRequest("GET", reqURL, nil) 52 | if e != nil { 53 | return "NewRequestReport", e 54 | } 55 | 56 | req.Header.Set("User-Agent", "JPush-API-GO-Client") 57 | req.Header.Set("Connection", "Keep-Alive") 58 | req.Header.Set("Accept-Charset", "UTF-8") 59 | req.Header.Set("Content-Type", "application/json") 60 | req.SetBasicAuth(c.appKey, c.masterSecret) 61 | 62 | client := http.Client{ 63 | Transport: &http.Transport{ 64 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 65 | c, err := net.DialTimeout(network, addr, time.Second*ConnTimeout) 66 | if err != nil { 67 | return nil, err 68 | } 69 | if err = c.SetDeadline(time.Now().Add(RwTimeout * time.Second)); err != nil { 70 | return nil, err 71 | } 72 | return c, nil 73 | }, 74 | }, 75 | } 76 | 77 | resp, e := client.Do(req) 78 | if e != nil { 79 | return resp.Status, e 80 | } 81 | defer resp.Body.Close() 82 | 83 | buf := make([]byte, 4096) 84 | nr, _ := resp.Body.Read(buf) 85 | 86 | if resp.StatusCode == http.StatusOK { 87 | return string(buf[:nr]), nil 88 | } 89 | 90 | return resp.Status, fmt.Errorf(string(buf[:nr])) 91 | } 92 | 93 | // GetReportObject . 94 | func (c *ReportClient) GetReportObject(msgIds ...int) ([]ReportObject, error) { 95 | report, err := c.GetReport(msgIds...) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | var res []ReportObject 101 | if e := json.Unmarshal([]byte(report), &res); e != nil { 102 | return nil, e 103 | } 104 | 105 | return res, nil 106 | } 107 | -------------------------------------------------------------------------------- /internal/jums/jums.go: -------------------------------------------------------------------------------- 1 | package jums 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/bytedance/sonic" 9 | "github.com/cloudwego/hertz/pkg/app/client" 10 | "github.com/cloudwego/hertz/pkg/protocol" 11 | "github.com/cloudwego/hertz/pkg/protocol/consts" 12 | ) 13 | 14 | // Jums 极光统一推送 15 | type Jums struct { 16 | config Config 17 | } 18 | 19 | // Config 配置 20 | type Config struct { 21 | Key string 22 | Secret string 23 | AccountKey string 24 | AccountSecret string 25 | } 26 | 27 | // New 极光统一推送 28 | func New(config Config) *Jums { 29 | return &Jums{ 30 | config: config, 31 | } 32 | } 33 | 34 | // Message 普通消息推送 35 | type Message struct { 36 | Key string 37 | Secret string 38 | Data map[string]interface{} 39 | } 40 | 41 | // Message 消息模式 42 | func (u *Jums) Message() *Message { 43 | return &Message{ 44 | Key: u.config.Key, 45 | Secret: u.config.Secret, 46 | Data: map[string]interface{}{}, 47 | } 48 | } 49 | 50 | // Users 用户模式 51 | type Users struct { 52 | Key string 53 | Secret string 54 | AccountKey string 55 | AccountSecret string 56 | Data map[string]interface{} 57 | UserID uint 58 | } 59 | 60 | // User 用户模式 61 | func (u *Jums) User() *Users { 62 | return &Users{ 63 | Key: u.config.Key, 64 | Secret: u.config.Secret, 65 | AccountKey: u.config.AccountKey, 66 | AccountSecret: u.config.AccountSecret, 67 | Data: map[string]interface{}{}, 68 | } 69 | } 70 | 71 | // UserDel 批量删除用户 72 | func (u *Jums) UserDel(userid ...uint) error { 73 | if len(userid) == 0 { 74 | return errors.New("删除用户为空") 75 | } 76 | return Request("v1/user/delete", u.config.AccountKey, u.config.AccountSecret, userid) 77 | } 78 | 79 | // Request 请求数据 80 | func Request(url string, key string, secret string, data interface{}) error { 81 | var ( 82 | jsonByte, err = sonic.Marshal(data) 83 | c *client.Client 84 | ) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | if c, err = client.NewClient( 90 | client.WithDialTimeout(5 * time.Second), 91 | ); err != nil { 92 | return err 93 | } 94 | req := &protocol.Request{} 95 | res := &protocol.Response{} 96 | req.SetMethod(consts.MethodPost) 97 | req.Header.SetContentTypeBytes([]byte("application/json")) 98 | req.URI().SetUsername(key) 99 | req.URI().SetPassword(secret) 100 | req.SetRequestURI("https://api.ums.jiguang.cn/" + url) 101 | req.SetBody(jsonByte) 102 | if err = c.Do(context.Background(), req, res); err != nil { 103 | return err 104 | } 105 | 106 | var api struct { 107 | Code int 108 | Message string 109 | } 110 | if err = sonic.Unmarshal(res.Body(), &api); err != nil { 111 | return errors.New("jums request failed") 112 | } 113 | 114 | if api.Code != 0 { 115 | return errors.New(api.Message) 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /internal/jums/message.go: -------------------------------------------------------------------------------- 1 | package jums 2 | 3 | import "github.com/gofrs/uuid" 4 | 5 | // ToData 发送消息对象 6 | type ToData map[string]interface{} 7 | 8 | // To 发送消息对象 9 | func (u *Message) To(datas ...*ToData) *Message { 10 | for _, datum := range datas { 11 | for k, v := range *datum { 12 | u.Data[k] = v 13 | } 14 | } 15 | return u 16 | } 17 | 18 | // MsgData 消息内容 19 | type MsgData map[string]interface{} 20 | 21 | // Content 发送消息内容 22 | func (u *Message) Content(datas ...*MsgData) *Message { 23 | for _, datum := range datas { 24 | for k, v := range *datum { 25 | u.Data[k] = v 26 | } 27 | } 28 | return u 29 | } 30 | 31 | // Send 发送消息内容 32 | func (u *Message) Send() error { 33 | return Request("v1/sent", u.Key, u.Secret, u.Data) 34 | } 35 | 36 | // ToUser 用户id 37 | func ToUser(aud []string) *ToData { 38 | return &ToData{ 39 | "aud_tag": aud, 40 | } 41 | } 42 | 43 | // ToTag 用户标签 44 | func ToTag(aud []string) *ToData { 45 | return &ToData{ 46 | "aud_userid": aud, 47 | } 48 | } 49 | 50 | // ToSegment 用户群 51 | func ToSegment(aud []string) *ToData { 52 | return &ToData{ 53 | "aud_segment": aud, 54 | } 55 | } 56 | 57 | // ToApp App 58 | func ToApp(instance string, aud []string) *ToData { 59 | return &ToData{ 60 | "aud_app": []map[string]interface{}{ 61 | { 62 | "instance": instance, 63 | "data": aud, 64 | }, 65 | }, 66 | } 67 | } 68 | 69 | // ToWechatMp 微信公众号 70 | func ToWechatMp(instance string, aud []string) *ToData { 71 | return &ToData{ 72 | "aud_wechatoa": []map[string]interface{}{ 73 | { 74 | "instance": instance, 75 | "data": aud, 76 | }, 77 | }, 78 | } 79 | } 80 | 81 | // ToWechatLite 微信小程序 82 | func ToWechatLite(instance string, aud []string) *ToData { 83 | return &ToData{ 84 | "aud_wechatmp": []map[string]interface{}{ 85 | { 86 | "instance": instance, 87 | "data": aud, 88 | }, 89 | }, 90 | } 91 | } 92 | 93 | // ToSms 短信 94 | func ToSms(instance string, aud []string) *ToData { 95 | return &ToData{ 96 | "aud_sms": []map[string]interface{}{ 97 | { 98 | "instance": instance, 99 | "data": aud, 100 | }, 101 | }, 102 | } 103 | } 104 | 105 | // ToEmail 邮件 106 | func ToEmail(instance string, aud []string) *ToData { 107 | return &ToData{ 108 | "aud_email": []map[string]interface{}{ 109 | { 110 | "instance": instance, 111 | "data": aud, 112 | }, 113 | }, 114 | } 115 | } 116 | 117 | // ToAlipayLife 支付宝生活号 118 | func ToAlipayLife(instance string, aud []string) *ToData { 119 | return &ToData{ 120 | "aud_alipay_life": []map[string]interface{}{ 121 | { 122 | "instance": instance, 123 | "data": aud, 124 | }, 125 | }, 126 | } 127 | } 128 | 129 | // ToDingTalkCC 钉钉工作 130 | func ToDingTalkCC(instance string, aud []string) *ToData { 131 | return &ToData{ 132 | "aud_dingtalk_cc": []map[string]interface{}{ 133 | { 134 | "instance": instance, 135 | "data": aud, 136 | }, 137 | }, 138 | } 139 | } 140 | 141 | // ToWechatWork 企业微信 142 | func ToWechatWork(instance string, aud []string) *ToData { 143 | return &ToData{ 144 | "aud_wechatwk": []map[string]interface{}{ 145 | { 146 | "instance": instance, 147 | "data": aud, 148 | }, 149 | }, 150 | } 151 | } 152 | 153 | // MsgApp app消息 154 | func MsgApp(msg string, url string) *MsgData { 155 | id, _ := uuid.NewV4() 156 | return &MsgData{ 157 | "msg_app": []map[string]interface{}{ 158 | { 159 | "cid": id.String(), 160 | "platform": "all", 161 | "notification": map[string]interface{}{ 162 | "android": map[string]interface{}{ 163 | "alert": msg, 164 | "extras": map[string]interface{}{ 165 | "url": url, 166 | }, 167 | }, 168 | "ios": map[string]interface{}{ 169 | "alert": msg, 170 | "extras": map[string]interface{}{ 171 | "url": url, 172 | }, 173 | }, 174 | "quick": map[string]interface{}{ 175 | "alert": msg, 176 | "extras": map[string]interface{}{ 177 | "url": url, 178 | }, 179 | }, 180 | }, 181 | }, 182 | }, 183 | } 184 | } 185 | 186 | // WechatMpData 微信公众号消息 187 | type WechatMpData struct { 188 | Value string `json:"value"` 189 | Color string `json:"color"` 190 | } 191 | 192 | // WechatMiniProgram 小程序参数消息 193 | type WechatMiniProgram struct { 194 | AppID string `json:"app_id"` 195 | PagePath string `json:"page_path"` 196 | } 197 | 198 | // MsgWechatMp 公众号消息 199 | func MsgWechatMp(types uint, tplID string, data map[string]WechatMpData, url string, minis ...WechatMiniProgram) *MsgData { 200 | var mini WechatMiniProgram 201 | if len(minis) > 0 { 202 | mini = minis[0] 203 | } 204 | return &MsgData{ 205 | "msg_wechatoa": []map[string]interface{}{ 206 | { 207 | "type": types, 208 | "template_id": tplID, 209 | "url": url, 210 | "miniprogram": mini, 211 | "data": data, 212 | }, 213 | }, 214 | } 215 | } 216 | 217 | // WechatLiteData 小程序消息 218 | type WechatLiteData struct { 219 | Value string `json:"value"` 220 | } 221 | 222 | // MsgWechatLite 小程序消息 223 | func MsgWechatLite(tplID string, data map[string]WechatLiteData, pages ...string) *MsgData { 224 | var page string 225 | if len(pages) > 0 { 226 | page = pages[0] 227 | } 228 | return &MsgData{ 229 | "msg_wechatmp": []map[string]interface{}{ 230 | { 231 | "template_id": tplID, 232 | "page": page, 233 | "miniprogram_state": "formal", 234 | "data": data, 235 | }, 236 | }, 237 | } 238 | } 239 | 240 | // MsgSms 短信消息 241 | func MsgSms(signID string, tempID int, params map[string]interface{}) *MsgData { 242 | return &MsgData{ 243 | "msg_sms": []map[string]interface{}{ 244 | { 245 | "sign_id": signID, 246 | "temp_id": tempID, 247 | "temp_para": params, 248 | }, 249 | }, 250 | } 251 | } 252 | 253 | // MsgEmail 邮件消息 254 | func MsgEmail(title string, content string, files ...[]string) *MsgData { 255 | var file []string 256 | if len(files) > 0 { 257 | file = files[0] 258 | } 259 | return &MsgData{ 260 | "msg_email": []map[string]interface{}{ 261 | { 262 | "subject": title, 263 | "text": content, 264 | "files": file, 265 | }, 266 | }, 267 | } 268 | } 269 | 270 | // AlipayData 支付宝生活号数据 271 | type AlipayData struct { 272 | Value string `json:"value"` 273 | Color string `json:"color"` 274 | } 275 | 276 | // MsgAlipayLife 阿里生活号 277 | func MsgAlipayLife(tempID int, headColor string, url string, desc string, data map[string]AlipayData) *MsgData { 278 | result := MsgData{ 279 | "template_id": tempID, 280 | "context": map[string]interface{}{ 281 | "head_color": headColor, 282 | "url": url, 283 | "action_name": desc, 284 | }, 285 | } 286 | for key, value := range data { 287 | result[key] = value 288 | } 289 | 290 | return &MsgData{ 291 | "msg_wechatwk": []map[string]interface{}{ 292 | result, 293 | }, 294 | } 295 | } 296 | 297 | // MsgDingTalkCC 钉钉通知 298 | func MsgDingTalkCC(content string) *MsgData { 299 | return &MsgData{ 300 | "msg_dingtalk_cc": []map[string]interface{}{ 301 | { 302 | "msg": map[string]interface{}{ 303 | "msgtype": "text", 304 | "text": content, 305 | }, 306 | }, 307 | }, 308 | } 309 | } 310 | 311 | type wechatWkType string 312 | 313 | const ( 314 | // WechatWkText 文本消息 315 | WechatWkText wechatWkType = "text" 316 | // WechatWkImage 图片消息 317 | WechatWkImage wechatWkType = "image" 318 | // WechatWkFile 链接消息 319 | WechatWkFile wechatWkType = "file" 320 | // WechatWkNews 图文消息 321 | WechatWkNews wechatWkType = "news" 322 | // WechatWkMpNews 图文消息 323 | WechatWkMpNews wechatWkType = "mpnews" 324 | ) 325 | 326 | // WechatWkConfigText 文本消息 327 | type WechatWkConfigText struct { 328 | Content string `json:"content"` 329 | } 330 | 331 | // WechatWkConfigImage 图片 332 | type WechatWkConfigImage struct { 333 | MediaID string `json:"media_id"` 334 | } 335 | 336 | // WechatWkConfigFile 图文消息 337 | type WechatWkConfigFile struct { 338 | MediaID string `json:"media_id"` 339 | } 340 | 341 | // WechatWkConfigNews 图文消息 342 | type WechatWkConfigNews struct { 343 | Title string `json:"title"` 344 | Description string `json:"description"` 345 | URL string `json:"url"` 346 | PicURL string `json:"picurl"` 347 | } 348 | 349 | // WechatWkConfigMpNews 图文消息 350 | type WechatWkConfigMpNews struct { 351 | ThumbMediaID string `json:"thumb_media_id"` 352 | Title string `json:"title"` 353 | Content string `json:"content"` 354 | Digest string `json:"digest"` 355 | Author string `json:"author"` 356 | ContentSourceURL string `json:"content_source_url"` 357 | } 358 | 359 | // MsgWechatWork 企业微信 360 | func MsgWechatWork(config interface{}) *MsgData { 361 | result := map[string]interface{}{ 362 | "enable_duplicate_check": 1, 363 | "duplicate_check_interval": 1800, 364 | } 365 | switch config.(type) { 366 | case WechatWkConfigText: 367 | data := config.(WechatWkConfigText) 368 | result["msgtype"] = "text" 369 | result["text"] = map[string]interface{}{ 370 | "content": data.Content, 371 | } 372 | case WechatWkConfigImage: 373 | data := config.(WechatWkConfigImage) 374 | result["msgtype"] = "image" 375 | result["image"] = map[string]interface{}{ 376 | "media_id": data.MediaID, 377 | } 378 | case WechatWkConfigFile: 379 | data := config.(WechatWkConfigFile) 380 | result["msgtype"] = "file" 381 | result["file"] = map[string]interface{}{ 382 | "media_id": data.MediaID, 383 | } 384 | case []WechatWkConfigNews: 385 | data := config.([]WechatWkConfigNews) 386 | result["msgtype"] = "news" 387 | result["news"] = map[string]interface{}{ 388 | "articles": data, 389 | } 390 | case []WechatWkConfigMpNews: 391 | data := config.([]WechatWkConfigNews) 392 | result["msgtype"] = "mpnews" 393 | result["mpnews"] = map[string]interface{}{ 394 | "articles": data, 395 | } 396 | } 397 | return &MsgData{ 398 | "msg_wechatwk": []map[string]interface{}{ 399 | result, 400 | }, 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /internal/jums/users.go: -------------------------------------------------------------------------------- 1 | package jums 2 | 3 | // SetID 设置用户id 4 | func (u *Users) SetID(userID uint) *Users { 5 | u.UserID = userID 6 | return u 7 | } 8 | 9 | // Add 添加数据 10 | func (u *Users) Add(types ...*userType) *Users { 11 | add := map[string]interface{}{} 12 | for _, data := range types { 13 | for k, v := range *data { 14 | add[k] = v 15 | } 16 | } 17 | u.Data["add"] = add 18 | return u 19 | } 20 | 21 | // Set 设置数据 22 | func (u *Users) Set(types ...*userType) *Users { 23 | set := map[string]interface{}{} 24 | for _, data := range types { 25 | for k, v := range *data { 26 | u.Data[k] = v 27 | } 28 | } 29 | u.Data["set"] = set 30 | return u 31 | } 32 | 33 | // Del 删除数据 34 | func (u *Users) Del(types ...*userType) *Users { 35 | del := map[string]interface{}{} 36 | for _, data := range types { 37 | for k, v := range *data { 38 | u.Data[k] = v 39 | } 40 | } 41 | u.Data["del"] = del 42 | return u 43 | } 44 | 45 | // Send 发送消息内容 46 | func (u *Users) Send() error { 47 | return Request("v1/sent/user/opt", u.Key, u.Secret, u.Data) 48 | } 49 | 50 | type userType map[string]interface{} 51 | 52 | // UserTag 渠道标签 53 | func UserTag(channelKey string, data ...string) *userType { 54 | return &userType{ 55 | "tag": map[string]interface{}{ 56 | channelKey: data, 57 | }, 58 | } 59 | } 60 | 61 | // UserPhone 渠道手机号 62 | func UserPhone(channelKey string, data ...string) *userType { 63 | return &userType{ 64 | "phone": map[string]interface{}{ 65 | channelKey: data, 66 | }, 67 | } 68 | } 69 | 70 | // UserEmail 渠道邮箱 71 | func UserEmail(channelKey string, data ...string) *userType { 72 | return &userType{ 73 | "email": map[string]interface{}{ 74 | channelKey: data, 75 | }, 76 | } 77 | } 78 | 79 | // UserApp app类型 80 | func UserApp(instance string, data ...string) *userType { 81 | return &userType{ 82 | "app": map[string]interface{}{ 83 | instance: data, 84 | }, 85 | } 86 | } 87 | 88 | // UserWechatMp 微信公众号 89 | func UserWechatMp(instance string, data string) *userType { 90 | return &userType{ 91 | "wechatoa": map[string]interface{}{ 92 | instance: data, 93 | }, 94 | } 95 | } 96 | 97 | // UserWechatLite 微信小程序 98 | func UserWechatLite(instance string, data string) *userType { 99 | return &userType{ 100 | "wechatmp": map[string]interface{}{ 101 | instance: data, 102 | }, 103 | } 104 | } 105 | 106 | // UserAlipayLife 支付宝生活号 107 | func UserAlipayLife(instance string, data string) *userType { 108 | return &userType{ 109 | "alipaylife": map[string]interface{}{ 110 | instance: data, 111 | }, 112 | } 113 | } 114 | 115 | // UserDingTalkCC 钉钉 116 | func UserDingTalkCC(data string) *userType { 117 | return &userType{ 118 | "dingtalkcc": data, 119 | } 120 | } 121 | 122 | // UserWechatWork 企业微星 123 | func UserWechatWork(data string) *userType { 124 | return &userType{ 125 | "wechatwk": data, 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /internal/mi/api/channel.go: -------------------------------------------------------------------------------- 1 | // ** 2 | // * @Project: go-push-api 3 | // * @Author: qun 4 | // * @Description: 5 | // * @File: channel 6 | // * @Version: 1.0.0 7 | // * @Date: 2020/1/31 12:13 8 | 9 | package api 10 | 11 | // Channel . 12 | var Channel = apiChannel{} 13 | 14 | type apiChannel struct { 15 | } 16 | -------------------------------------------------------------------------------- /internal/mi/api/message.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | "github.com/houseme/go-push-api/internal/helper" 14 | "github.com/houseme/go-push-api/internal/mi" 15 | "github.com/houseme/go-push-api/internal/mi/builder" 16 | ) 17 | 18 | // Message . 19 | var Message = apiMessage{} 20 | 21 | type apiMessage struct { 22 | } 23 | 24 | // SendMessage . 25 | func (a *apiMessage) SendMessage(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 26 | var ( 27 | buffer bytes.Buffer 28 | err error 29 | ) 30 | if params.MiEnv == mi.SandBoxRequestEnv { 31 | _, _ = buffer.WriteString(mi.SandboxHost) 32 | } else { 33 | _, _ = buffer.WriteString(mi.OfficialHost) 34 | } 35 | 36 | form := &url.Values{} 37 | form.Add("restricted_package_name", builder.RestrictedPackageName) 38 | form.Add("payload", builder.Payload) 39 | form.Add("title", builder.Title) 40 | form.Add("description", builder.Description) 41 | form.Add("notify_type", fmt.Sprintf("%d", builder.NotifyType)) 42 | form.Add("pass_through", fmt.Sprintf("%d", builder.PassThrough)) 43 | 44 | if builder.NotifyID > 0 { 45 | form.Add("notify_id", fmt.Sprintf("%d", builder.NotifyID)) 46 | } 47 | 48 | if builder.TimeToLive > 0 { 49 | form.Add("time_to_live", fmt.Sprintf("%d", builder.TimeToLive)) 50 | } 51 | 52 | if builder.TimeToSend > 0 { 53 | form.Add("time_to_send", fmt.Sprintf("%d", builder.TimeToSend)) 54 | } 55 | 56 | if builder.Extra != nil { 57 | for k, v := range builder.Extra { 58 | form.Add(fmt.Sprintf("extra.%s", k), v) 59 | } 60 | } 61 | header := make(http.Header) 62 | header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") 63 | header.Add("Authorization", "key=%s"+params.AppSecret) 64 | 65 | switch params.AccountType { 66 | case mi.RegIDAccountType: 67 | _, _ = buffer.WriteString(mi.MessageRegIDURL) 68 | if len(builder.RegistrationID) == 0 { 69 | return nil, errors.New("registration_id is required") 70 | } 71 | header.Add("registration_id", strings.Join(builder.RegistrationID, ",")) 72 | case mi.AliasAccountType: 73 | _, _ = buffer.WriteString(mi.MessageAliasURL) 74 | if len(builder.Alias) == 0 { 75 | return nil, errors.New("registration_id is required") 76 | } 77 | header.Add("registration_id", strings.Join(builder.Alias, ",")) 78 | case mi.UserAccountType: 79 | _, _ = buffer.WriteString(mi.MessageAccountURL) 80 | if len(builder.Alias) == 0 { 81 | return nil, errors.New("registration_id is required") 82 | } 83 | header.Add("user_account", strings.Join(builder.UserAccount, ",")) 84 | case mi.TopicAccountType: 85 | _, _ = buffer.WriteString(mi.MessageTopicURL) 86 | if len(builder.Alias) == 0 { 87 | return nil, errors.New("topic is required") 88 | } 89 | header.Add("topic", builder.Topic) 90 | default: 91 | _, _ = buffer.WriteString(mi.MessageAllURL) 92 | } 93 | 94 | req, err := http.NewRequest("POST", buffer.String(), strings.NewReader(form.Encode())) 95 | if err != nil { 96 | return nil, err 97 | } 98 | var client = &http.Client{ 99 | Timeout: 5 * time.Second, 100 | } 101 | 102 | res, err := client.Do(req) 103 | if err != nil { 104 | return nil, err 105 | } 106 | defer res.Body.Close() 107 | body, err := io.ReadAll(res.Body) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return helper.ToJSON(body) 112 | } 113 | 114 | // RevokeMessage revoke message 115 | func (a *apiMessage) RevokeMessage(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 116 | var buffer bytes.Buffer 117 | if params.MiEnv == mi.SandBoxRequestEnv { 118 | _, _ = buffer.WriteString(mi.SandboxHost) 119 | } else { 120 | _, _ = buffer.WriteString(mi.OfficialHost) 121 | } 122 | _, _ = buffer.WriteString(mi.MessageAliasRevokeURL) 123 | form := &url.Values{} 124 | form.Add("restricted_package_name", builder.RestrictedPackageName) 125 | form.Add("notify_id", fmt.Sprintf("%d", builder.NotifyID)) 126 | header := make(http.Header) 127 | header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") 128 | header.Add("Authorization", "key=%s"+params.AppSecret) 129 | req, err := http.NewRequest("POST", buffer.String(), strings.NewReader(form.Encode())) 130 | if err != nil { 131 | return nil, err 132 | } 133 | var client = &http.Client{ 134 | Timeout: 5 * time.Second, 135 | } 136 | res, err := client.Do(req) 137 | if err != nil { 138 | return nil, err 139 | } 140 | defer res.Body.Close() 141 | body, err := io.ReadAll(res.Body) 142 | if err != nil { 143 | return nil, err 144 | } 145 | return helper.ToJSON(body) 146 | } 147 | 148 | // DealTopic deal topic 149 | func (a *apiMessage) DealTopic(builder *builder.Builder, params mi.Params) error { 150 | return errors.New("not implement") 151 | } 152 | -------------------------------------------------------------------------------- /internal/mi/api/topic.go: -------------------------------------------------------------------------------- 1 | // * @Project: go-push-api 2 | // * @Author: qun 3 | // * @Description: 4 | // * @File: topic 5 | // * @Version: 1.0.0 6 | // * @Date: 2020/1/31 12:27 7 | 8 | package api 9 | 10 | import ( 11 | "context" 12 | 13 | "github.com/houseme/go-push-api/internal/helper" 14 | "github.com/houseme/go-push-api/internal/mi" 15 | "github.com/houseme/go-push-api/internal/mi/builder" 16 | ) 17 | 18 | // Topic . 19 | var Topic = apiTopic{ 20 | ctx: context.Background(), 21 | } 22 | 23 | type apiTopic struct { 24 | ctx context.Context 25 | } 26 | 27 | // SubscribeTopicByRegID 注册topic 28 | func (a *apiTopic) SubscribeTopicByRegID(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 29 | params.MiURL = mi.SubscribeURL 30 | hc, err := helper.Client() 31 | if err != nil { 32 | return nil, err 33 | } 34 | res, err := hc.DoPost(a.ctx, builder, params) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return helper.ToJSON(res) 39 | } 40 | 41 | // BatchSubscribeTopicByRegIds 批量注册主题 42 | func (a *apiTopic) BatchSubscribeTopicByRegIds(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 43 | params.MiURL = mi.SubscribeURL 44 | hc, err := helper.Client() 45 | if err != nil { 46 | return nil, err 47 | } 48 | res, err := hc.DoPost(a.ctx, builder, params) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return helper.ToJSON(res) 53 | } 54 | 55 | // SubscribeTopicByAlias 注册topic 56 | func (a *apiTopic) SubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 57 | params.MiURL = mi.SubscribeAliasURL 58 | hc, err := helper.Client() 59 | if err != nil { 60 | return nil, err 61 | } 62 | res, err := hc.DoPost(a.ctx, builder, params) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return helper.ToJSON(res) 67 | } 68 | 69 | // BatchSubscribeTopicByAlias 批量注册主题 70 | func (a *apiTopic) BatchSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 71 | params.MiURL = mi.SubscribeAliasURL 72 | hc, err := helper.Client() 73 | if err != nil { 74 | return nil, err 75 | } 76 | res, err := hc.DoPost(a.ctx, builder, params) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return helper.ToJSON(res) 81 | } 82 | 83 | // UnSubscribeTopicByRegID 通过regId取消注册topic 84 | func (a *apiTopic) UnSubscribeTopicByRegID(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 85 | params.MiURL = mi.UnsubscribeURL 86 | hc, err := helper.Client() 87 | if err != nil { 88 | return nil, err 89 | } 90 | res, err := hc.DoPost(a.ctx, builder, params) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return helper.ToJSON(res) 95 | } 96 | 97 | // BatchUnSubscribeTopicByRegIds 通过regId批量取消注册主题 98 | func (a *apiTopic) BatchUnSubscribeTopicByRegIds(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 99 | params.MiURL = mi.UnsubscribeURL 100 | hc, err := helper.Client() 101 | if err != nil { 102 | return nil, err 103 | } 104 | res, err := hc.DoPost(a.ctx, builder, params) 105 | if err != nil { 106 | return nil, err 107 | } 108 | return helper.ToJSON(res) 109 | } 110 | 111 | // UnSubscribeTopicByAlias 通过Alias取消注册topic 112 | func (a *apiTopic) UnSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 113 | params.MiURL = mi.UnsubscribeAliasURL 114 | hc, err := helper.Client() 115 | if err != nil { 116 | return nil, err 117 | } 118 | res, err := hc.DoPost(a.ctx, builder, params) 119 | if err != nil { 120 | return nil, err 121 | } 122 | return helper.ToJSON(res) 123 | } 124 | 125 | // BatchUnSubscribeTopicByAlias 通过Alias批量取消主题 126 | func (a *apiTopic) BatchUnSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 127 | params.MiURL = mi.UnsubscribeAliasURL 128 | hc, err := helper.Client() 129 | if err != nil { 130 | return nil, err 131 | } 132 | res, err := hc.DoPost(a.ctx, builder, params) 133 | if err != nil { 134 | return nil, err 135 | } 136 | return helper.ToJSON(res) 137 | } 138 | -------------------------------------------------------------------------------- /internal/mi/api/tracer.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/houseme/go-push-api/internal/mi" 5 | "github.com/houseme/go-push-api/internal/mi/builder" 6 | ) 7 | 8 | // Tracer . 9 | var Tracer = apiTracer{} 10 | 11 | type apiTracer struct { 12 | } 13 | 14 | // GetMessageStatus 通过RegId群推 15 | func (a *apiTracer) GetMessageStatus(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 16 | return nil, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/mi/builder/builder.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | // Builder . 4 | type Builder struct { 5 | RegistrationID []string `json:"registration_id"` 6 | Alias []string `json:"alias"` 7 | UserAccount []string `json:"user_account"` 8 | Topic string `json:"topic"` 9 | Topics []string `json:"topics"` 10 | TopicOp string `json:"topic_op"` 11 | Payload string `json:"payload,omitempty"` 12 | RestrictedPackageName string `json:"restricted_package_name,omitempty"` 13 | Title string `json:"title"` 14 | Description string `json:"description"` 15 | PassThrough int64 `json:"pass_through"` // 0 通知栏消息, 1 透传消息 16 | NotifyType int64 `json:"notify_type,omitempty"` // -1: DEFAULT_ALL 1: 使用默认提示音提示, 2: 使用默认震动提示, 4: 使用默认led灯光提示 17 | TimeToLive int64 `json:"time_to_live,omitempty"` 18 | TimeToSend int64 `json:"time_to_send,omitempty"` 19 | NotifyID int64 `json:"notify_id,omitempty"` 20 | Extra map[string]string `json:"extra,omitempty"` 21 | SoundURI string `json:"sound_uri"` 22 | Ticker string `json:"ticker"` 23 | NotifyForeground string `json:"notify_foreground"` 24 | NotifyEffect string `json:"notify_effect"` 25 | IntentURI string `json:"intent_uri"` 26 | FlowControl string `json:"flow_control"` 27 | AppVersionNotIn string `json:"app_version_not_in"` 28 | Badge string `json:"badge"` // ios使用 29 | Category string `json:"category"` // ios使用 30 | } 31 | -------------------------------------------------------------------------------- /internal/mi/builder/channel.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | // Channel . 4 | type Channel struct { 5 | ChannelID string `json:"channelId"` 6 | ChannelName string `json:"channelName"` 7 | NotifyType int32 `json:"notifyType"` 8 | ChannelDescription string `json:"channelDescription,omitempty"` 9 | SoundURI string `json:"soundUri,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /internal/mi/builder/topic.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | // Topic . 4 | type Topic struct { 5 | RegistrationID string `json:"registrationId,omitempty"` 6 | Aliases string `json:"aliases,omitempty"` 7 | UserAccount string `json:"userAccount,omitempty"` 8 | Topic string `json:"topic"` 9 | Topics string `json:"topics"` 10 | Category string `json:"category"` // ios使用 11 | RestrictedPackageName string `json:"restrictedPackageName,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /internal/mi/domain.go: -------------------------------------------------------------------------------- 1 | package mi 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Params . 8 | type Params struct { 9 | AppSecret string `json:"appSecret"` 10 | MiURL string `json:"miUrl"` 11 | MiEnv RequestEnv `json:"miEnv"` 12 | TimeOut time.Duration `json:"timeOut"` 13 | AccountType AccountType `json:"accountType"` 14 | } 15 | 16 | // Result 小米推送接口返回的结果,主体结构是一致的 17 | type Result struct { 18 | Code int64 `json:"code"` // 0表示成功,非0表示失败 19 | Result string `json:"result"` // "ok" 表示成功,"error" 表示失败 20 | Description string `json:"description,omitempty"` // 对发送消息失败原因的解释 21 | Info string `json:"info,omitempty"` // 详细信息 22 | Reason string `json:"reason,omitempty"` // 失败原因 23 | Data *Data `json:"data,omitempty"` // 本身就是一个json字符串 24 | } 25 | 26 | // Data . 27 | type Data struct { 28 | BadRegIds string `json:"badRegIds"` // 推送失败的cids 29 | ID string `json:"id"` // 消息的Id 30 | } 31 | -------------------------------------------------------------------------------- /internal/mi/miconst.go: -------------------------------------------------------------------------------- 1 | package mi 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Region . 8 | type Region string 9 | 10 | // RequestEnv . 11 | type RequestEnv string 12 | 13 | // ErrorCode . 14 | type ErrorCode int64 15 | 16 | // AccountType . 17 | type AccountType string 18 | 19 | // Kind . 20 | type Kind string 21 | 22 | const ( 23 | // SandboxHost . 24 | SandboxHost = "https://sandbox.xmpush.xiaomi.com" 25 | // OfficialHost . 26 | OfficialHost = "https://api.xmpush.xiaomi.com" 27 | // GlobalProductionHost . 28 | GlobalProductionHost = "https://api.xmpush.global.xiaomi.com" 29 | 30 | // MessageRegIDURL . 31 | MessageRegIDURL = "/v4/message/regid" 32 | // MessageAliasURL . 33 | MessageAliasURL = "/v3/message/alias" 34 | // MessageAllURL . 35 | MessageAllURL = "/v3/message/all" 36 | // MessageAccountURL . 37 | MessageAccountURL = "/v2/message/user_account" 38 | // MessageTopicURL . 39 | MessageTopicURL = "/v3/message/topic" 40 | // MessageTopicOpURL . 41 | MessageTopicOpURL = "/v3/message/multi_topic" 42 | 43 | // MultiRegIDURL . 44 | MultiRegIDURL = "/v2/multi_messages/regids" 45 | // MultiAliasURL . 46 | MultiAliasURL = "/v2/multi_messages/aliases" 47 | // MultiAccountURL . 48 | MultiAccountURL = "/v2/multi_messages/user_accounts" 49 | // StatsURL . 50 | StatsURL = "/v1/stats/message/counters" 51 | // MessageStatusURL . 52 | MessageStatusURL = "/v1/trace/message/status" 53 | // MessagesStatusURL . 54 | MessagesStatusURL = "/v1/trace/messages/status" 55 | // SubscribeURL . 56 | SubscribeURL = "/v2/topic/subscribe" 57 | // UnsubscribeURL . 58 | UnsubscribeURL = "/v2/topic/unsubscribe" 59 | // SubscribeAliasURL . 60 | SubscribeAliasURL = "/v2/topic/subscribe/alias" 61 | // UnsubscribeAliasURL . 62 | UnsubscribeAliasURL = "/v2/topic/unsubscribe/alias" 63 | // InvalidRegIdsURL . 64 | InvalidRegIdsURL = "https://feedback.xmpush.xiaomi.com/v1/feedback/fetch_invalid_regids" 65 | 66 | // RegIDAliasURL . 67 | RegIDAliasURL = "/v1/alias/all" 68 | // RegIDTopicURL . 69 | RegIDTopicURL = "/v1/topic/all" 70 | // ScheduleJobExistURL . 71 | ScheduleJobExistURL = "/v2/schedule_job/exist" 72 | // ScheduleJobDeleteURL . 73 | ScheduleJobDeleteURL = "/v2/schedule_job/delete" 74 | // GetRegIdsByUserAccountURL . 75 | GetRegIdsByUserAccountURL = "/v1/useraccount/get_regids_by_useraccount" 76 | // MediaUploadImageURL 上传大图API 77 | MediaUploadImageURL = "/media/upload/image" 78 | 79 | // ChannelAddURL 添加channel 80 | ChannelAddURL = "/v1/channel/add" 81 | 82 | // ChannelListURL channel list 83 | ChannelListURL = "/v1/channel/list" 84 | 85 | // ChannelDiscardURL 删除一个channel 86 | ChannelDiscardURL = "/v1/channel/discard" 87 | 88 | // GetRegionByRegIDURL 批量查询RegId的注册地区 89 | GetRegionByRegIDURL = "/v1/feedback/get_region_by_regid" 90 | 91 | // StopByIDURL 消息ID停止消息 92 | StopByIDURL = "/v1/message/switch/stop_by_id" 93 | 94 | // StopByJobKeyURL 消息jobkey停止消息 95 | StopByJobKeyURL = "/v1/message/switch/stop_by_jobkey" 96 | 97 | // MessageAliasRevokeURL 设备alias撤回消息 98 | MessageAliasRevokeURL = "/v1/message/alias/revoke" 99 | 100 | // MessageRegIDRevokeURL 设备regId撤回消息 101 | MessageRegIDRevokeURL = "/v1/message/regid/revoke" 102 | 103 | // MessageUserAccountRevokeURL 设备userAccount撤回消息 104 | MessageUserAccountRevokeURL = "/v1/message/user_account/revoke" 105 | 106 | // MessageTopicRevokeURL 撤回topic消息 107 | MessageTopicRevokeURL = "/v1/message/topic/revoke" 108 | 109 | // MessageMultiTopicRevokeURL 撤回多topic消息 110 | MessageMultiTopicRevokeURL = "/v1/message/multi_topic/revoke" 111 | 112 | // MediaUploadSmallIconQueryURL 查询小图上传状态 113 | MediaUploadSmallIconQueryURL = "/media/upload/smallIcon/query" 114 | // MediaUploadSmallIconURL 小图上传 115 | MediaUploadSmallIconURL = "/media/upload/smallIcon" 116 | 117 | // RegionChina .中国区 118 | RegionChina Region = "China" 119 | // RegionEurope .欧洲区 120 | RegionEurope Region = "Europe" 121 | // RegionRussia .俄罗斯区 122 | RegionRussia Region = "Russia" 123 | // RegionIndia .印度区 124 | RegionIndia Region = "India" 125 | // RegionOther .其他区 126 | RegionOther Region = "Other" 127 | 128 | // SandBoxRequestEnv 请求环境 生成还是测试 沙箱 or 正式 129 | SandBoxRequestEnv RequestEnv = "sandbox" 130 | // OfficialRequestEnv 请求环境 生成还是测试 沙箱 or 正式 131 | OfficialRequestEnv RequestEnv = "official" 132 | 133 | // DefaultTimeOut 请求超时时间 134 | DefaultTimeOut time.Duration = 10000000000 135 | 136 | // SuccessCode error code 成功 137 | SuccessCode ErrorCode = 0 138 | // FailCode error code 失败 139 | FailCode ErrorCode = 0 140 | 141 | // RegIDAccountType account type 发消息的账户类型 142 | RegIDAccountType AccountType = "regId" 143 | // AliasAccountType account type 发消息的账户类型 144 | AliasAccountType AccountType = "alias" 145 | TopicAccountType AccountType = "topic" 146 | MultiTopicAccountType AccountType = "multiTopic" 147 | AllAccountType AccountType = "all" 148 | UserAccountType AccountType = "userAccount" 149 | 150 | // ChannelKind 种类 分为 channel message topic tracer 渠道 消息 主题 跟踪 151 | ChannelKind Kind = "channel" 152 | // MessageKind 种类 分为 message topic 消息 主题 153 | MessageKind Kind = "message" 154 | // TopicKind 种类 分为 topic multiTopic 主题 多主题 155 | TopicKind Kind = "topic" 156 | // Tracer 种类 分为 tracer 渠道 157 | Tracer Kind = "tracer" 158 | ) 159 | -------------------------------------------------------------------------------- /internal/umeng/builder/define.go: -------------------------------------------------------------------------------- 1 | package builder 2 | -------------------------------------------------------------------------------- /jpush.go: -------------------------------------------------------------------------------- 1 | package push 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/houseme/go-push-api/internal/jums" 7 | ) 8 | 9 | // Jums 推送客户端 10 | type Jums struct { 11 | ctx context.Context 12 | client *jums.Jums 13 | } 14 | 15 | // SendSms 发送短信 16 | func (j *Jums) SendSms(ctx context.Context, tempID int, phone, code, signID string) error { 17 | var adu = []string{phone} 18 | return j.client.Message().To(jums.ToSms("sms", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 19 | } 20 | 21 | // NewClient 创建推送客户端 22 | func NewClient(ctx context.Context, key, secret string) *Jums { 23 | return &Jums{ 24 | ctx: ctx, 25 | client: jums.New(jums.Config{ 26 | Key: key, 27 | Secret: secret, 28 | }), 29 | } 30 | } 31 | 32 | // SendPush 发送推送 33 | func (j *Jums) SendPush(ctx context.Context, client *jums.Jums, title, content, url string, aud []string) error { 34 | return nil 35 | } 36 | 37 | // SendEmail 发送邮件 38 | func (j *Jums) SendEmail(ctx context.Context, tempID int, email, code, signID string) error { 39 | var adu = []string{email} 40 | return j.client.Message().To(jums.ToEmail("sms", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 41 | } 42 | 43 | // SendWechat 发送微信 44 | func (j *Jums) SendWechat(ctx context.Context, tempID int, openID, code, signID string) error { 45 | var adu = []string{openID} 46 | return j.client.Message().To(jums.ToWechatMp("wechat", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 47 | } 48 | 49 | // SendAPP 发送APP 50 | func (j *Jums) SendAPP(ctx context.Context, tempID int, appID, code, signID string) error { 51 | var adu = []string{appID} 52 | return j.client.Message().To(jums.ToApp("app", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 53 | } 54 | 55 | // SendWechatLite 发送微信小程序 56 | func (j *Jums) SendWechatLite(ctx context.Context, tempID int, appID, code, signID string) error { 57 | var adu = []string{appID} 58 | return j.client.Message().To(jums.ToWechatLite("wechatmp", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 59 | } 60 | 61 | // SendWechatWork 发送企业微信 62 | func (j *Jums) SendWechatWork(ctx context.Context, tempID int, appID, code, signID string) error { 63 | var adu = []string{appID} 64 | return j.client.Message().To(jums.ToWechatWork("wechatwk", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 65 | } 66 | 67 | // SendDingTalkCC 发送钉钉 68 | func (j *Jums) SendDingTalkCC(ctx context.Context, tempID int, appID, code, signID string) error { 69 | var adu = []string{appID} 70 | return j.client.Message().To(jums.ToDingTalkCC("dingtalkcc", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 71 | } 72 | 73 | // SendWechatMp 发送微信公众号 74 | func (j *Jums) SendWechatMp(ctx context.Context, tempID int, appID, code, signID string) error { 75 | var adu = []string{appID} 76 | return j.client.Message().To(jums.ToWechatMp("msg_wechatoa", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 77 | } 78 | 79 | // SendAlipayLife 发送支付宝生活号 80 | func (j *Jums) SendAlipayLife(ctx context.Context, tempID int, appID, code, signID string) error { 81 | var adu = []string{appID} 82 | return j.client.Message().To(jums.ToAlipayLife("alipaylife", adu)).Content(jums.MsgSms(signID, tempID, map[string]interface{}{"code": code})).Send() 83 | } 84 | -------------------------------------------------------------------------------- /mipush.go: -------------------------------------------------------------------------------- 1 | package push 2 | 3 | import ( 4 | "github.com/houseme/go-push-api/internal/mi" 5 | "github.com/houseme/go-push-api/internal/mi/api" 6 | "github.com/houseme/go-push-api/internal/mi/builder" 7 | ) 8 | 9 | // SendMessage 发送消息 10 | func SendMessage(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 11 | return api.Message.SendMessage(builder, params) 12 | } 13 | 14 | // RevokeMessage 消息退回 15 | func RevokeMessage(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 16 | return api.Message.RevokeMessage(builder, params) 17 | } 18 | 19 | // DealTopic 处理topic 20 | func DealTopic(builder *builder.Builder, params mi.Params) error { 21 | return api.Message.DealTopic(builder, params) 22 | } 23 | 24 | // SubscribeTopicByRegID 注册topic 25 | func SubscribeTopicByRegID(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 26 | return api.Topic.SubscribeTopicByRegID(builder, params) 27 | } 28 | 29 | // BatchSubscribeTopicByRegIds 批量注册主题 30 | func BatchSubscribeTopicByRegIds(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 31 | return api.Topic.BatchSubscribeTopicByRegIds(builder, params) 32 | } 33 | 34 | // SubscribeTopicByAlias 通过别名订阅topic 35 | func SubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 36 | return api.Topic.SubscribeTopicByAlias(builder, params) 37 | } 38 | 39 | // BatchSubscribeTopicByAlias 批量注册主题 40 | func BatchSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 41 | return api.Topic.BatchSubscribeTopicByAlias(builder, params) 42 | } 43 | 44 | // UnSubscribeTopicByRegID 通过regId取消注册topic 45 | func UnSubscribeTopicByRegID(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 46 | return api.Topic.UnSubscribeTopicByRegID(builder, params) 47 | } 48 | 49 | // UnSubscribeTopicByAlias 通过别名取消注册topic 50 | func UnSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 51 | return api.Topic.UnSubscribeTopicByAlias(builder, params) 52 | } 53 | 54 | // BatchUnSubscribeTopicByAlias 批量注册主题 55 | func BatchUnSubscribeTopicByAlias(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 56 | return api.Topic.BatchUnSubscribeTopicByAlias(builder, params) 57 | } 58 | 59 | // GetMessageStatus 获取消息状态 60 | func GetMessageStatus(builder *builder.Builder, params mi.Params) (*mi.Result, error) { 61 | return api.Tracer.GetMessageStatus(builder, params) 62 | } 63 | -------------------------------------------------------------------------------- /mipush_test.go: -------------------------------------------------------------------------------- 1 | package push 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | "github.com/buger/jsonparser" 11 | ) 12 | 13 | func TestSend(t *testing.T) { 14 | for _, v := range os.Environ() { 15 | // 输出系统所有环境变量的值 16 | fmt.Println(v) 17 | t.Log("os env", v) 18 | } 19 | 20 | } 21 | 22 | func TestSendMessage(t *testing.T) { 23 | timeStart := time.Now().UnixNano() 24 | fmt.Println("timeStart ", timeStart) 25 | 26 | for i := 0; i < 50; i++ { 27 | fileName := "MiPush" + strconv.Itoa(i%2) 28 | fmt.Println(fileName) 29 | } 30 | endTime := time.Now().UnixNano() 31 | fmt.Println("endTime ", endTime) 32 | fmt.Println("end - start time :", endTime-timeStart) 33 | fmt.Println(os.Getenv("MI_PUSH_LOGGING_MODE")) 34 | t.Log("timeStart ", timeStart) 35 | } 36 | 37 | func TestToken(t *testing.T) { 38 | data := []byte(`{ 39 | "person": { 40 | "name": { 41 | "first": "Leonid", 42 | "last": "Bugaev", 43 | "fullName": "Leonid Bugaev" 44 | }, 45 | "github": { 46 | "handle": "buger", 47 | "followers": 109 48 | }, 49 | "avatars": [ 50 | { 51 | "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", 52 | "type": "thumbnail" 53 | } 54 | ] 55 | }, 56 | "company": { 57 | "name": "Acme" 58 | } 59 | }`) 60 | 61 | // You can specify key path by providing arguments to Get function 62 | t.Log(jsonparser.Get(data, "person", "name", "fullName")) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /revive.toml: -------------------------------------------------------------------------------- 1 | ignoreGeneratedHeader = false 2 | severity = "warning" 3 | confidence = 0.8 4 | errorCode = 0 5 | warningCode = 0 6 | 7 | [rule.blank-imports] 8 | [rule.context-as-argument] 9 | [rule.context-keys-type] 10 | [rule.dot-imports] 11 | [rule.error-return] 12 | [rule.error-strings] 13 | [rule.error-naming] 14 | [rule.exported] 15 | [rule.if-return] 16 | [rule.increment-decrement] 17 | [rule.line-length-limit] 18 | severity = "error" 19 | arguments = [160] 20 | [rule.var-naming] 21 | arguments = [["ID", "URL", "IP", "HTTP", "JSON", "API", "UID","Http","Ip","Url","Id","Json","Api","Uid"], ["VM"]] 22 | [rule.var-declaration] 23 | [rule.package-comments] 24 | [rule.range] 25 | [rule.receiver-naming] 26 | [rule.time-naming] 27 | [rule.unexported-return] 28 | [rule.indent-error-flow] 29 | [rule.errorf] 30 | [rule.empty-block] 31 | [rule.superfluous-else] 32 | [rule.unused-parameter] 33 | [rule.unreachable-code] 34 | [rule.redefines-builtin-id] 35 | -------------------------------------------------------------------------------- /umeng.go: -------------------------------------------------------------------------------- 1 | package push 2 | --------------------------------------------------------------------------------