├── .gitignore
├── .idea
├── .gitignore
├── YTask.iml
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.ZH.md
├── README.md
├── architecture_diagram.png
├── docs
├── .gitignore
├── Makefile
├── QuickStart.rst
├── _static
│ └── architecture_diagram.png
├── abortTask.rst
├── backend.rst
├── broker.rst
├── client.rst
├── conf.py
├── delay.rst
├── error.rst
├── expire.rst
├── html_to_rst.py
├── index.rst
├── log.rst
├── make.bat
├── requirements.txt
├── retry.rst
├── server.rst
├── upgrade.rst
└── workflow.rst
├── drives
├── README.md
├── memcache
│ ├── README.md
│ ├── backend.go
│ ├── go.mod
│ ├── go.sum
│ ├── memcache.go
│ └── test
│ │ ├── README.md
│ │ └── mencacheBackend_test.go
├── mongo
│ ├── README.md
│ ├── backend.go
│ ├── go.mod
│ ├── go.sum
│ ├── mongo.go
│ └── test
│ │ ├── README.md
│ │ └── mongoBackend_test.go
├── mongo2
│ ├── README.md
│ ├── backend.go
│ ├── go.mod
│ ├── go.sum
│ ├── mongo.go
│ └── test
│ │ ├── README.md
│ │ └── mongoBackend_test.go
├── rabbitmq
│ ├── README.md
│ ├── broker.go
│ ├── go.mod
│ ├── go.sum
│ ├── rabbitmq.go
│ └── test
│ │ ├── README.md
│ │ ├── rabbitmqBroker_test.go
│ │ └── rabbitmqPool_test.go
├── redis
│ ├── README.md
│ ├── bakcend.go
│ ├── broker.go
│ ├── go.mod
│ ├── go.sum
│ ├── redis.go
│ └── test
│ │ ├── README.md
│ │ ├── redisBackend_test.go
│ │ └── redisBroker_test.go
└── rocketmq
│ └── README.md
├── example
├── README.md
├── send
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── server
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── workers
│ └── workers.go
├── go.mod
├── go.sum
├── go.work
├── go.work.sum
└── v3
├── backends
├── backend.go
└── local.go
├── brokers
├── broker.go
└── local.go
├── config
└── config.go
├── consts
└── consts.go
├── drive
├── err.go
├── local.go
├── lock.go
└── slice.go
├── go.mod
├── go.sum
├── log
└── log.go
├── message
├── msg.go
└── result.go
├── server
├── client.go
├── delayServer.go
├── delayServerGoRoutine.go
├── inlineServer.go
├── inlineServerGoroutine.go
├── multiServer.go
├── serverUtils.go
├── sortQueue.go
├── taskCtl.go
└── worker.go
├── test
├── README.md
├── abort_test.go
├── connPool_test.go
├── localBackend_test.go
├── localBroker_test.go
├── reflect_test.go
├── sortQueue_test.go
├── v2_test.go
├── workflow_test.go
├── ytask_2_test.go
├── ytask_callback_test.go
├── ytask_delayServer_test.go
├── ytask_multiServer_2_test.go
├── ytask_multiServer_test.go
└── ytask_test.go
├── util
├── common.go
├── pool.go
├── reflect.go
└── yjson
│ └── yjson.go
├── yerrors
└── errors.go
└── ytask.go
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/goland
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=goland
4 |
5 | ### GoLand ###
6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
8 |
9 | # User-specific stuff
10 | .idea/**/workspace.xml
11 | .idea/**/tasks.xml
12 | .idea/**/usage.statistics.xml
13 | .idea/**/dictionaries
14 | .idea/**/shelf
15 |
16 | # AWS User-specific
17 | .idea/**/aws.xml
18 |
19 | # Generated files
20 | .idea/**/contentModel.xml
21 |
22 | # Sensitive or high-churn files
23 | .idea/**/dataSources/
24 | .idea/**/dataSources.ids
25 | .idea/**/dataSources.local.xml
26 | .idea/**/sqlDataSources.xml
27 | .idea/**/dynamic.xml
28 | .idea/**/uiDesigner.xml
29 | .idea/**/dbnavigator.xml
30 |
31 | # Gradle
32 | .idea/**/gradle.xml
33 | .idea/**/libraries
34 |
35 | # Gradle and Maven with auto-import
36 | # When using Gradle or Maven with auto-import, you should exclude module files,
37 | # since they will be recreated, and may cause churn. Uncomment if using
38 | # auto-import.
39 | # .idea/artifacts
40 | # .idea/compiler.xml
41 | # .idea/jarRepositories.xml
42 | # .idea/modules.xml
43 | # .idea/*.iml
44 | # .idea/modules
45 | # *.iml
46 | # *.ipr
47 |
48 | # CMake
49 | cmake-build-*/
50 |
51 | # Mongo Explorer plugin
52 | .idea/**/mongoSettings.xml
53 |
54 | # File-based project format
55 | *.iws
56 |
57 | # IntelliJ
58 | out/
59 |
60 | # mpeltonen/sbt-idea plugin
61 | .idea_modules/
62 |
63 | # JIRA plugin
64 | atlassian-ide-plugin.xml
65 |
66 | # Cursive Clojure plugin
67 | .idea/replstate.xml
68 |
69 | # SonarLint plugin
70 | .idea/sonarlint/
71 |
72 | # Crashlytics plugin (for Android Studio and IntelliJ)
73 | com_crashlytics_export_strings.xml
74 | crashlytics.properties
75 | crashlytics-build.properties
76 | fabric.properties
77 |
78 | # Editor-based Rest Client
79 | .idea/httpRequests
80 |
81 | # Android studio 3.1+ serialized cache file
82 | .idea/caches/build_file_checksums.ser
83 |
84 | ### GoLand Patch ###
85 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
86 |
87 | # *.iml
88 | # modules.xml
89 | # .idea/misc.xml
90 | # *.ipr
91 |
92 | # Sonarlint plugin
93 | # https://plugins.jetbrains.com/plugin/7973-sonarlint
94 | .idea/**/sonarlint/
95 |
96 | # SonarQube Plugin
97 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
98 | .idea/**/sonarIssues.xml
99 |
100 | # Markdown Navigator plugin
101 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
102 | .idea/**/markdown-navigator.xml
103 | .idea/**/markdown-navigator-enh.xml
104 | .idea/**/markdown-navigator/
105 |
106 | # Cache file creation bug
107 | # See https://youtrack.jetbrains.com/issue/JBR-2257
108 | .idea/$CACHE_FILE$
109 |
110 | # CodeStream plugin
111 | # https://plugins.jetbrains.com/plugin/12206-codestream
112 | .idea/codestream.xml
113 |
114 | # Azure Toolkit for IntelliJ plugin
115 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
116 | .idea/**/azureSettings.xml
117 |
118 | # End of https://www.toptal.com/developers/gitignore/api/goland
119 |
120 | # Binaries for programs and plugins
121 | *.exe
122 | *.exe~
123 | *.dll
124 | *.so
125 | *.dylib
126 |
127 | # Test binary, build with `go test -c`
128 | *.test
129 |
130 | # Output of the go coverage tool, specifically when used with LiteIDE
131 | *.out
132 |
133 | core/main.go
134 | .DS_Store
135 |
136 | go.work
137 | go.work.sum
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/YTask.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.ZH.md:
--------------------------------------------------------------------------------
1 | YTask
2 | -----------
3 |
4 | YTask is an asynchronous task queue for handling distributed jobs in golang
5 | golang异步任务/队列 框架
6 |
7 | * [中文文档](https://doc.ikaze.cn/YTask) (中文文档更加全面,优先阅读中文文档)
8 | * [En Doc](https://github.com/gojuukaze/YTask/wiki)
9 | * [Github](https://github.com/gojuukaze/YTask)
10 | * [Brokers And Backends](./drives)
11 | * [V2 Doc](https://doc.ikaze.cn/YTaskV2)
12 |
13 | # install
14 | ```bash
15 | # install core
16 | go get -u github.com/gojuukaze/YTask/v3
17 |
18 | #install broker and backend
19 | go get -u github.com/gojuukaze/YTask/drives/redis/v3
20 | go get -u github.com/gojuukaze/YTask/drives/rabbitmq/v3
21 | go get -u github.com/gojuukaze/YTask/drives/mongo2/v3
22 | go get -u github.com/gojuukaze/YTask/drives/memcache/v3
23 |
24 | ```
25 |
26 | # architecture diagram
27 |
28 |
29 |
30 |
31 | # Quick Start
32 |
33 | ## server demo
34 |
35 | ```go
36 | package main
37 |
38 | import (
39 | "context"
40 | "os"
41 | "os/signal"
42 | "syscall"
43 | "github.com/gojuukaze/YTask/drives/redis/v3"
44 | "github.com/gojuukaze/YTask/v3"
45 | )
46 |
47 | // 定义两个任务,任务参数、返回值支持所有能被序列化为json的类型
48 |
49 | func add(a int, b int) int {
50 | return a + b
51 | }
52 |
53 | type User struct {
54 | Id int
55 | Name string
56 | }
57 |
58 | func appendUser(user User, ids []int, names []string) []User {
59 | var r = make([]User, 0)
60 | r = append(r, user)
61 | for i := range ids {
62 | r = append(r, User{ids[i], names[i]})
63 | }
64 | return r
65 | }
66 |
67 | func main() {
68 | // RedisBroker最后一个参数是连接池大小, 若不需要 任务流 功能可以设为0(为0时使用默认值,server端默认为3)
69 | // 否则根据需要设置,最大不要超过并发任务数
70 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 3)
71 |
72 | // RedisBackend最后一个参数是连接池大小,对于server端 如果<=0 会使用默认值,
73 | // 默认值是 min(10, numWorkers)
74 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 0)
75 |
76 | ser := ytask.Server.NewServer(
77 | ytask.Config.Broker(&broker),
78 | ytask.Config.Backend(&backend), // 可不设置
79 | ytask.Config.Debug(true),
80 | ytask.Config.StatusExpires(60*5),
81 | ytask.Config.ResultExpires(60*5),
82 | )
83 |
84 | // 注册任务
85 | ser.Add("group1", "add", add)
86 | ser.Add("group1", "append_user", appendUser)
87 |
88 | // 运行server,并发数3
89 | ser.Run("group1", 3)
90 |
91 | quit := make(chan os.Signal, 1)
92 |
93 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
94 | <-quit
95 | ser.Shutdown(context.Background())
96 |
97 | }
98 |
99 | ```
100 |
101 | ## client demo
102 |
103 | ```go
104 | package main
105 |
106 | import (
107 | "fmt"
108 | "github.com/gojuukaze/YTask/drives/redis/v3"
109 | "github.com/gojuukaze/YTask/v3"
110 | "github.com/gojuukaze/YTask/v3/server"
111 | "time"
112 | )
113 |
114 | type User struct {
115 | Id int
116 | Name string
117 | }
118 |
119 | var client server.Client
120 |
121 | func main() {
122 | // 对于client端你需要设置连接池大小
123 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 5)
124 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 5)
125 |
126 | ser := ytask.Server.NewServer(
127 | ytask.Config.Broker(&broker),
128 | ytask.Config.Backend(&backend),
129 | ytask.Config.Debug(true),
130 | ytask.Config.StatusExpires(60*5),
131 | ytask.Config.ResultExpires(60*5),
132 | )
133 |
134 | client = ser.GetClient()
135 |
136 | // 提交任务
137 | taskId, _ := client.Send("group1", "add", 123, 44)
138 | // 获取结果
139 | result, _ := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
140 |
141 | if result.IsSuccess() {
142 | // 有多种方法获取返回值,具体可以参考: https://doc.ikaze.cn/YTask/client.html#id4
143 | sum, _ := result.GetInt64(0)
144 | // or
145 | var sum2 int
146 | result.Get(0, &sum2)
147 |
148 | fmt.Println("add(123,44) =", int(sum))
149 | }
150 |
151 | // 提交结构体,slice等
152 | taskId, _ = client.Send("group1", "append_user", User{1, "aa"}, []int{322, 11}, []string{"bb", "cc"})
153 | result, _ = client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
154 | var users []User
155 | result.Get(0, &users)
156 | fmt.Println(users)
157 |
158 | }
159 |
160 |
161 | ```
162 |
163 | # Example
164 |
165 | Also take a look at [example](https://github.com/gojuukaze/YTask/tree/master/example) directory.
166 |
167 | ```bash
168 | cd example/server
169 | go run main.go
170 |
171 | cd example/send
172 | go run send/main.go
173 | ```
174 |
175 | 捐赠 / Sponsor
176 | ================
177 |
178 | 开源不易,如果你觉得对你有帮助,求打赏个一块两块的
179 |
180 | 
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | YTask
2 | -----------
3 |
4 | YTask is an asynchronous task queue for handling distributed jobs in golang
5 | golang异步任务/队列 框架
6 |
7 | * [中文文档](https://doc.ikaze.cn/YTask) (Chinese documents are more detailed, give priority to reading Chinese documents)
8 | * [En Doc](https://github.com/gojuukaze/YTask/wiki)
9 | * [Github](https://github.com/gojuukaze/YTask)
10 | * [Brokers And Backends](./drives)
11 | * [V2 Doc](https://doc.ikaze.cn/YTaskV2)
12 |
13 |
14 | # install
15 | ```bash
16 | # install core
17 | go get -u github.com/gojuukaze/YTask/v3
18 |
19 | #install broker and backend
20 | go get -u github.com/gojuukaze/YTask/drives/redis/v3
21 | go get -u github.com/gojuukaze/YTask/drives/rabbitmq/v3
22 | go get -u github.com/gojuukaze/YTask/drives/mongo2/v3
23 | go get -u github.com/gojuukaze/YTask/drives/memcache/v3
24 |
25 | ```
26 |
27 | # architecture diagram
28 |
29 |
30 |
31 |
32 | # Quick Start
33 |
34 | ## server demo
35 |
36 | ```go
37 | package main
38 |
39 | import (
40 | "context"
41 | "os"
42 | "os/signal"
43 | "syscall"
44 | "github.com/gojuukaze/YTask/drives/redis/v3"
45 | "github.com/gojuukaze/YTask/v3"
46 | )
47 |
48 | // Define two tasks.
49 | // Task parameters and return values support all types that can be serialized to json
50 |
51 | func add(a int, b int) int {
52 | return a + b
53 | }
54 |
55 | type User struct {
56 | Id int
57 | Name string
58 | }
59 |
60 | func appendUser(user User, ids []int, names []string) []User {
61 | var r = make([]User, 0)
62 | r = append(r, user)
63 | for i := range ids {
64 | r = append(r, User{ids[i], names[i]})
65 | }
66 | return r
67 | }
68 |
69 | func main() {
70 | // The last parameter of RedisBroker is the connection pool size. If you don't need workflow, you can set it to 0 (the default value is used when it is 0, and the default value on the server side is 3)
71 | // Otherwise, set it as needed, the maximum should not exceed the number of concurrent tasks
72 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 3)
73 |
74 | // The last parameter of RedisBackend is the connection pool size. For the server side, if <=0, the default value will be used.
75 | // the default value is min(10, numWorkers)
76 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 0)
77 |
78 | ser := ytask.Server.NewServer(
79 | ytask.Config.Broker(&broker),
80 | ytask.Config.Backend(&backend), // 可不设置
81 | ytask.Config.Debug(true),
82 | ytask.Config.StatusExpires(60*5),
83 | ytask.Config.ResultExpires(60*5),
84 | )
85 |
86 | // register task
87 | ser.Add("group1", "add", add)
88 | ser.Add("group1", "append_user", appendUser)
89 |
90 | // Run the server, the number of concurrency is 3
91 | ser.Run("group1", 3)
92 |
93 | quit := make(chan os.Signal, 1)
94 |
95 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
96 | <-quit
97 | ser.Shutdown(context.Background())
98 |
99 | }
100 |
101 | ```
102 |
103 | ## client demo
104 |
105 | ```go
106 | package main
107 |
108 | import (
109 | "fmt"
110 | "github.com/gojuukaze/YTask/drives/redis/v3"
111 | "github.com/gojuukaze/YTask/v3"
112 | "github.com/gojuukaze/YTask/v3/server"
113 | "time"
114 | )
115 |
116 | type User struct {
117 | Id int
118 | Name string
119 | }
120 |
121 | var client server.Client
122 |
123 | func main() {
124 | // For the client side you need to set the connection pool size
125 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 5)
126 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 5)
127 |
128 | ser := ytask.Server.NewServer(
129 | ytask.Config.Broker(&broker),
130 | ytask.Config.Backend(&backend),
131 | ytask.Config.Debug(true),
132 | ytask.Config.StatusExpires(60*5),
133 | ytask.Config.ResultExpires(60*5),
134 | )
135 |
136 | client = ser.GetClient()
137 |
138 | // Submit a task
139 | taskId, _ := client.Send("group1", "add", 123, 44)
140 | // get results
141 | result, _ := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
142 |
143 | if result.IsSuccess() {
144 | // There are multiple ways to get the return value, please refer to the documentation for details
145 | sum, _ := result.GetInt64(0)
146 | // or
147 | var sum2 int
148 | result.Get(0, &sum2)
149 |
150 | fmt.Println("add(123,44) =", int(sum))
151 | }
152 |
153 | // Submit structs, slices, etc.
154 | taskId, _ = client.Send("group1", "append_user", User{1, "aa"}, []int{322, 11}, []string{"bb", "cc"})
155 | result, _ = client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
156 | var users []User
157 | result.Get(0, &users)
158 | fmt.Println(users)
159 |
160 | }
161 |
162 |
163 | ```
164 |
165 | # Example
166 |
167 | Also take a look at [example](https://github.com/gojuukaze/YTask/tree/master/example) directory.
168 |
169 | ```bash
170 | cd example/server
171 | go run main.go
172 |
173 | cd example/send
174 | go run send/main.go
175 | ```
176 |
177 | 捐赠 / Sponsor
178 | ================
179 |
180 | 开源不易,如果你觉得对你有帮助,求打赏个一块两块的
181 |
182 | 
183 |
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/architecture_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojuukaze/YTask/1524b236b6029a3f5e000c45cb8249d057f26138/architecture_diagram.png
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _build
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/QuickStart.rst:
--------------------------------------------------------------------------------
1 | 快速开始
2 | ==========
3 |
4 | 这里给出一个简单的server端,client端代码,更多的样例可以参考 `example `_
5 |
6 |
7 | server
8 | ----------
9 |
10 | .. code:: go
11 |
12 | package main
13 |
14 | import (
15 | "context"
16 | "github.com/gojuukaze/YTask/drives/redis/v3"
17 | "github.com/gojuukaze/YTask/v3"
18 | "os"
19 | "os/signal"
20 | "syscall"
21 | )
22 |
23 | // 定义两个任务,任务参数、返回值支持所有能被序列化为json的类型
24 |
25 | func add(a int, b int) int {
26 | return a + b
27 | }
28 |
29 | type User struct {
30 | Id int
31 | Name string
32 | }
33 |
34 | func appendUser(user User, ids []int, names []string) []User {
35 | var r = make([]User, 0)
36 | r = append(r, user)
37 | for i := range ids {
38 | r = append(r, User{ids[i], names[i]})
39 | }
40 | return r
41 | }
42 |
43 | func main() {
44 | // RedisBroker最后一个参数是连接池大小, 若不需要 任务流 功能可以设为0(为0时使用默认值,server端默认为3)
45 | // 否则根据需要设置,最大不要超过并发任务数
46 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 3)
47 |
48 | // RedisBackend最后一个参数是连接池大小,对于server端 如果<=0 会使用默认值,
49 | // 默认值是 min(10, numWorkers)
50 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 0)
51 |
52 | ser := ytask.Server.NewServer(
53 | ytask.Config.Broker(&broker),
54 | ytask.Config.Backend(&backend), // 可不设置
55 | ytask.Config.Debug(true),
56 | ytask.Config.StatusExpires(60*5),
57 | ytask.Config.ResultExpires(60*5),
58 | )
59 |
60 | // 注册任务
61 | ser.Add("group1", "add", add)
62 | ser.Add("group1", "append_user", appendUser)
63 |
64 | // 运行server,并发数3
65 | ser.Run("group1", 3)
66 |
67 | quit := make(chan os.Signal, 1)
68 |
69 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
70 | <-quit
71 | ser.Shutdown(context.Background())
72 |
73 | }
74 |
75 |
76 | client
77 | ----------
78 |
79 | .. code:: go
80 |
81 | package main
82 |
83 | import (
84 | "fmt"
85 | "github.com/gojuukaze/YTask/drives/redis/v3"
86 | "github.com/gojuukaze/YTask/v3"
87 | "github.com/gojuukaze/YTask/v3/server"
88 | "time"
89 | )
90 |
91 | type User struct {
92 | Id int
93 | Name string
94 | }
95 |
96 | var client server.Client
97 |
98 | func main() {
99 | // 对于client端你需要设置连接池大小
100 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 5)
101 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 5)
102 |
103 | ser := ytask.Server.NewServer(
104 | ytask.Config.Broker(&broker),
105 | ytask.Config.Backend(&backend),
106 | ytask.Config.Debug(true),
107 | ytask.Config.StatusExpires(60*5),
108 | ytask.Config.ResultExpires(60*5),
109 | )
110 |
111 | client = ser.GetClient()
112 |
113 | // 提交任务
114 | taskId, _ := client.Send("group1", "add", 123, 44)
115 | // 获取结果
116 | result, _ := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
117 |
118 | if result.IsSuccess() {
119 | // 有多种方法获取返回值,具体可以参考: https://doc.ikaze.cn/YTask/client.html#id4
120 | sum, _ := result.GetInt64(0)
121 | // or
122 | var sum2 int
123 | result.Get(0, &sum2)
124 |
125 | fmt.Println("add(123,44) =", int(sum))
126 | }
127 |
128 | // 提交结构体,slice等
129 | taskId, _ = client.Send("group1", "append_user", User{1, "aa"}, []int{322, 11}, []string{"bb", "cc"})
130 | result, _ = client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
131 | var users []User
132 | result.Get(0, &users)
133 | fmt.Println(users)
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/docs/_static/architecture_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojuukaze/YTask/1524b236b6029a3f5e000c45cb8249d057f26138/docs/_static/architecture_diagram.png
--------------------------------------------------------------------------------
/docs/abortTask.rst:
--------------------------------------------------------------------------------
1 | 中止任务
2 | ===========
3 |
4 | 你可以中止任务以及工作流,**注意!!使用中止任务必须配置backend!!!**
5 |
6 | 中止未运行任务
7 | ------------------------
8 |
9 | 通过 ``AbortTask()`` 设置中止任务标记。
10 |
11 | 第二个参数是中止标记的过期时间,若任务太多建议设置长一点,或设为-1后续手动清理。
12 | (对于mongo这里设置过期时间是无效的,只能在NewBackend时设置)
13 |
14 | .. code:: go
15 |
16 | client:= ser.GetClient()
17 |
18 | client.AbortTask(id, 10)
19 |
20 | 中止运行中的任务
21 | ------------------------
22 |
23 | 对于运行中的任务,你同样需要调用 ``AbortTask()`` 设置中止标记。然后在任务函数中手动检测,并中止。
24 |
25 | .. code:: go
26 |
27 | func sendSMS(ctl *server.TaskCtl, userId, msg string) {
28 |
29 | phone := getUserPhone(userId)
30 |
31 | if f, _ := ctl.IsAbort(); f {
32 | ctl.Abort("手动中止")
33 | // 别忘了return,否则会继续执行下去
34 | return
35 | }
36 |
37 | Send(phone, msg)
38 | }
39 |
40 | ``IsAbort()`` 返回两个参数:``isAbort, err`` ,如果从backend获取数据出错 isAbort会设为false ,此时你要根据需求判断是否再次检测。
41 |
--------------------------------------------------------------------------------
/docs/backend.rst:
--------------------------------------------------------------------------------
1 | Backend
2 | ===========
3 | Backend用于保存任务结果
4 |
5 | Redis
6 | --------------
7 | https://github.com/gojuukaze/YTask/tree/master/drives/redis
8 |
9 | .. code:: go
10 |
11 | import "github.com/gojuukaze/YTask/drives/redis/v3"
12 |
13 | // 127.0.0.1 : host
14 | // 6379 : port
15 | // "" : password
16 | // 0 : db
17 | // 10 : 连接池大小.
18 | // 对于server端,如果为0,则值为min(10, numWorkers)
19 | // 对于client端, 你需要根据情况自行设置连接池
20 | redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 10)
21 |
22 | MemCache
23 | ---------------------
24 | https://github.com/gojuukaze/YTask/tree/master/drives/memcache
25 |
26 | .. code:: go
27 |
28 | import "github.com/gojuukaze/YTask/drives/memcache/v3"
29 |
30 | // []string{"127.0.0.1:11211"} : ["host:port"]
31 | // 10 : 连接池大小.
32 | // 对于server端,如果为0,则值为min(10, numWorkers)
33 | // 对于client端, 你需要根据情况自行设置连接池
34 |
35 | b := memcache.NewMemCacheBackend([]string{"127.0.0.1:11211"}, 10)
36 |
37 | Mongo
38 | --------------
39 |
40 | https://github.com/gojuukaze/YTask/tree/master/drives/mongo2
41 |
42 | 如果你是从v2版升级,且希望保留旧数据,则使用 `mongo `__ ,否则使用mongo2
43 |
44 | ---
45 |
46 | **注意1:** MongoBackend 无需设置连接池。
47 |
48 | **注意2:** 若需设置过期时间,只能在 ``NewMongoBackend()`` 函数中设置,且设置后后续无法修改。
49 | 要修改的话只能手动修改MongoDB对应表的索引(修改ExpireAfterSeconds)
50 |
51 | .. code:: go
52 |
53 | import "github.com/gojuukaze/YTask/drives/mongo2/v3"
54 |
55 | // 127.0.0.1 : host
56 | // 27017 : port
57 | // "" : username
58 | // "" : password
59 | // "test": db
60 | // "test": collection
61 | // 20: 过期时间,单位秒
62 |
63 | backend := mongo2.NewMongoBackend("127.0.0.1", "27017", "", "", "test", "test", 20)
64 |
65 | 自定义backend
66 | ----------------
67 |
68 | 你可以自定义backend。同broker一样,调用\ ``Activate()``\ 时再建立连接。 具体注意事项参照 :ref:`自定义Broker`
69 |
70 | .. code:: go
71 |
72 | type BackendInterface interface {
73 | SetResult(result message.Result, exTime int) error
74 | GetResult(key string) (message.Result, error)
75 | // Activate connection
76 | Activate()
77 | SetPoolSize(int)
78 | GetPoolSize() int
79 | Clone() BackendInterface
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/docs/broker.rst:
--------------------------------------------------------------------------------
1 | Broker
2 | ==========
3 |
4 | | YTask使用broker与任务队列通信,发送或接收任务。
5 | | 支持的broker有:
6 |
7 | Redis
8 | --------------
9 |
10 | https://github.com/gojuukaze/YTask/tree/master/drives/redis
11 |
12 | .. code:: go
13 |
14 | import "github.com/gojuukaze/YTask/drives/redis/v3"
15 |
16 | // 127.0.0.1 : host
17 | // 6379 : port
18 | // "" : password
19 | // 0 : db
20 | // 10 : client连接池大小. (<=0默认为3)
21 | // 对于client端, 你需要根据情况自行设置连接池
22 | redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 10)
23 |
24 | RabbitMq
25 | -----------------
26 | https://github.com/gojuukaze/YTask/tree/master/drives/rabbitmq
27 |
28 | .. code:: go
29 |
30 | import "github.com/gojuukaze/YTask/drives/rabbitmq/v3"
31 | // 127.0.0.1 : host
32 | // 5672 : port
33 | // guest : username
34 | // guest : password
35 |
36 | rabbitmq.NewRabbitMqBroker("127.0.0.1", "5672", "guest", "guest", "")
37 |
38 | RocketMq (不再支持)
39 | -------------------------------------
40 |
41 | v3 不再支持,具体见:https://github.com/gojuukaze/YTask/tree/master/drives/rocketmq
42 |
43 | .. _custom:
44 |
45 | 自定义broker
46 | --------------
47 |
48 | 你可以自定义broker。需要注意,因为系统中会调用\ ``SetPoolSize``\ 设置连接池,所以初始化broker时不要建立连接,调用\ ``Activate()``\ 时再建立。
49 |
50 | 如果你的broker不支持连接池,那可以不用管Activate,SetPoolSize,GetPoolSize三个方法,直接返回空就行。
51 |
52 | 获取任务时,应不断循环获取,而不是阻塞在这,若队列为空,则返回 ``ErrTypeEmptyQuery`` 错误。
53 |
54 | .. code:: go
55 |
56 | type BrokerInterface interface {
57 | // 获取任务
58 | Next(queryName string) (message.Message, error)
59 | // 发送任务
60 | Send(queryName string, msg message.Message) error
61 | // 把任务插到队头
62 | // - 如果你的broker不支持,也没有优先队列这样的替代方案,则可以复用Send(这样做会影响延时任务的处理时间)。
63 | LSend(queryName string, msg message.Message) error
64 | // 建立连接
65 | Activate()
66 | SetPoolSize(int)
67 | GetPoolSize()int
68 | // 用当前broker的配置生成个新的broker
69 | Clone() BrokerInterface
70 | }
71 |
72 | .. admonition:: 注意
73 |
74 | 强烈建议你使用连接池,v3自带了一个连接池在 ``util/pool.go`` 使用样例见 ``test/connPool_test.go``
75 |
76 | 如果确定不用连接池,需要在Send,Next时建立链接,用完后销毁。 **(注意不要复用链接,否则会造成多个协程争抢!!)**
--------------------------------------------------------------------------------
/docs/client.rst:
--------------------------------------------------------------------------------
1 | 客户端
2 | =========
3 |
4 | 获取连接
5 | ----------
6 |
7 | 获取连接前一样需要初始化Server,然后调用\ ``GetClient()``\ 。\ ``NewServer``\ 的参数可以和服务端不同,但建议使用相同的参数配置
8 |
9 | .. code:: go
10 |
11 | import "github.com/gojuukaze/YTask/v3"
12 |
13 | ser := ytask.Server.NewServer(
14 | ytask.Config.Broker(&broker),
15 | ytask.Config.Backend(&backend),
16 | ...
17 | )
18 |
19 | client = ser.GetClient()
20 |
21 | 发送信息
22 | ----------
23 |
24 | | 使用\ ``Send``\ 发送任务信息,函数前两个参数为组名、任务,后面的参数是任务函数的参数。函数第一个返回值为任务id,可以用来获取任务结果。
25 | | 发送消息时可以使用\ ``SetTaskCtl()``\ 配置该次任务的重试次数等
26 |
27 | .. code:: go
28 |
29 | // group1 : 组名
30 | // add : 任务名
31 | // 12,33 ... : 任务参数
32 | // return :
33 | // - taskId : taskId
34 | // - err : error
35 | taskId,err:=client.Send("group1","add",12,33)
36 |
37 | // set retry count
38 | taskId,err=client.SetTaskCtl(client.RetryCount, 5).Send("group1","add",12,33)
39 |
40 | // set delay time
41 | taskId,err=client.SetTaskCtl(client.RunAfter, 2*time.Second).Send("group1","add",12,33)
42 |
43 | // set expire time
44 | taskId,err=client.SetTaskCtl(client.ExpireTime,time.Now().Add(4*time.Second)).Send("group1","add",12,33)
45 |
46 | 获取结果
47 | ----------
48 | 可通过 ``GetResult()`` , ``GetResult2()`` 获取结果
49 |
50 | * ``GetResult()`` : 只有任务结束才返回(任务失败、完成都是结束)
51 | * ``GetResult2()`` : backend中有记录就返回(一般来说任务开始执行就会有),这个通常用于获取任务流进度
52 |
53 | ---
54 |
55 | | \ ``GetResult()``\ , \ ``GetResult2()``\ 的第2个参数为超时时间,第3个参数为重新获取时间。
56 | | 获取结果后可调用\ ``GetXX()``\ ,\ ``Get()``\ ,\ ``Gets()``\ 获取任务函数的返回结果。
57 |
58 | .. code:: go
59 |
60 | // taskId :
61 | // 3*time.Second : timeout
62 | // 300*time.Millisecond : sleep time
63 | result, _ := client.GetResult(taskId, 3*time.Second, 300*time.Millisecond)
64 |
65 | if result.IsSuccess(){
66 | // get worker func return
67 | a,err:=result.GetInt64(0)
68 | b,err:=result.GetBool(1)
69 |
70 | // or
71 | var a int
72 | var b bool
73 | err:=result.Get(0, &a)
74 | err:=result.Get(1, &b)
75 |
76 | // or
77 | var a int
78 | var b bool
79 | err:=result.Gets(&a, &b)
80 | }
81 |
82 |
83 |
84 | ..
85 |
86 | | **重要!!**
87 | | YTask虽然提供获取结果功能,但不要过渡依赖。
88 | | 如果backend出错导致无法保存结果,YTask不会再次重试。因为对任务状态、结果的保存与运行任务的goroutine是同一个,不断重试会导致worker被占用。
89 | YTask优先保障任务运行,而不是结果保存。
90 | | 如果你特别需要任务结果,推荐你在任务函数中自行保存。
91 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 |
17 |
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = 'YTask'
21 | copyright = '2021, gojuukaze'
22 | author = 'gojuukaze'
23 |
24 | # The full version, including alpha/beta/rc tags
25 | release = 'v3.0.0'
26 |
27 |
28 | # -- General configuration ---------------------------------------------------
29 |
30 | # Add any Sphinx extension module names here, as strings. They can be
31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32 | # ones.
33 | extensions = [
34 | 'sphinx_rtd_theme'
35 | ]
36 |
37 | # Add any paths that contain templates here, relative to this directory.
38 | templates_path = ['_templates']
39 |
40 | # The language for content autogenerated by Sphinx. Refer to documentation
41 | # for a list of supported languages.
42 | #
43 | # This is also used if you do content translation via gettext catalogs.
44 | # Usually you set "language" from the command line for these cases.
45 | language = 'zh_CN'
46 |
47 | # List of patterns, relative to source directory, that match files and
48 | # directories to ignore when looking for source files.
49 | # This pattern also affects html_static_path and html_extra_path.
50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
51 |
52 |
53 | # -- Options for HTML output -------------------------------------------------
54 |
55 | # The theme to use for HTML and HTML Help pages. See the documentation for
56 | # a list of builtin themes.
57 | #
58 | html_theme = 'sphinx_rtd_theme'
59 |
60 | # Add any paths that contain custom static files (such as style sheets) here,
61 | # relative to this directory. They are copied after the builtin static files,
62 | # so a file named "default.css" will overwrite the builtin "default.css".
63 | html_static_path = ['_static']
--------------------------------------------------------------------------------
/docs/delay.rst:
--------------------------------------------------------------------------------
1 | 延时任务
2 | ============
3 | YTask并不能保证任务准时执行,
4 | 当你发现大量的延时任务到执行时间却没执行时,可尝试调大本地队列(v2.4+支持),调大YTask的任务并发数,或者部署更多的server端
5 |
6 |
7 | 开启延时任务
8 | --------------
9 |
10 | 目前有两种方法开启延时任务:
11 |
12 | 1. 运行server时,把enableDelayServer设为true
13 |
14 | .. code:: go
15 |
16 | ser := ytask.Server.NewServer(...)
17 |
18 | ser.Run("group", 10, true)
19 |
20 | 2. 通过config配置 (v2.4+)
21 |
22 | .. code:: go
23 |
24 | config:=config.NewConfig(
25 | config.EnableDelayServer(true),
26 | config.delayServerQueueSize(50), // 本地队列大小
27 | )
28 |
29 | 提交延时任务
30 | --------------
31 |
32 | * RunAfter
33 |
34 | .. code:: go
35 |
36 | client.SetTaskCtl(client.RunAfter, 1*time.Second).Send("group2", "add_sub", 123, 44)
37 |
38 | * RunAt
39 |
40 | .. code:: go
41 |
42 | runTime := time.Now().Add(1 * time.Second)
43 | client.SetTaskCtl(client.RunAt, runTime).Send("group2", "add_sub", 123, 44)
44 |
45 | 延时任务执行流程
46 | ------------------
47 |
48 | 首选说明两个概念:``inlineServer`` :运行任务的server; ``delayServer`` : 获取延时任务的server。
49 |
50 | 1. 提交延时任务时YTask会在把任务提交到一个延时任务队列中(下面成为远程队列)。
51 |
52 | 2. delayServer会从远程队列中获取任务到本地队列并排序,
53 | 插入本地队列时,若本地队列已满,则会把离执行时间最远的任务出队,并重新插入远程队列的队尾。
54 |
55 | 3. delayServer定时取出本地队列中到执行时间的任务,并提交给inlineServer执行。
56 | 也就是说延时任务与非延时任务公用一组并发的worker。
57 |
58 | 4. 当服务关闭时delayServer会把本地队列中的任务插入远程队列中(默认插到队头,如果broker不支持会插到队尾)
59 |
--------------------------------------------------------------------------------
/docs/error.rst:
--------------------------------------------------------------------------------
1 | Error
2 | ==========
3 |
4 | 内置的错误类型
5 | -------------------
6 |
7 | .. code:: go
8 |
9 | const (
10 | ErrTypeEmptyQueue = 1 // 队列为空, broker获取任务时用到
11 | ErrTypeUnsupportedType = 2 // 不支持此参数类型
12 | ErrTypeOutOfRange = 3 // 暂时没用
13 | ErrTypeNilResult = 4 // 任务结果为空
14 | ErrTypeTimeOut = 5 // broker,backend超时
15 | ErrTypeServerStop = 6 // 服务已停止
16 | ErrTypeSendMsg = 7 // 通过broker发送消息失败,目前工作流中发送下一个任务时会用到
17 | ErrTypeNilBackend = 8
18 | ErrTypeAbortTask = 9
19 | )
20 |
21 |
22 | 比较错误
23 | -------------
24 |
25 | .. code:: go
26 |
27 | import "github.com/gojuukaze/YTask/v3/yerrors"
28 | yerrors.IsEqual(err, yerrors.ErrTypeNilResult)
29 |
--------------------------------------------------------------------------------
/docs/expire.rst:
--------------------------------------------------------------------------------
1 | 任务过期时间
2 | ==============
3 |
4 | 目前只支持在client端设置过期时间,若任务发生重试且重试开始的时间超过了过期时间,任务会直接终止
5 |
6 | .. code:: go
7 |
8 | taskId, err :=client.SetTaskCtl(client.ExpireTime,time.Now().Add(4*time.Second)).Send("group1","add",12,33)
9 |
10 | result, _ := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
11 |
12 | if result.Status == message.ResultStatus.Expired{
13 | // do ...
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/docs/html_to_rst.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 |
3 | # html粘贴到这
4 | html = ''' '''
5 |
6 | soup = BeautifulSoup(html)
7 |
8 |
9 | def get_len(s):
10 | m = 0
11 | for c in s:
12 | if c.isascii():
13 | m += 1
14 | else:
15 | m += 2
16 | return m
17 |
18 |
19 | f = True
20 | line_max = []
21 | for r in soup.select('tr'):
22 | for i, td in enumerate(r.select('td')):
23 | m = get_len(td.text)
24 | if not m%2==0:
25 | m+=1
26 |
27 | if f:
28 | line_max.append(m)
29 | else:
30 | line_max[i] = max(m, line_max[i])
31 | f=False
32 | sss='+'
33 | for i in line_max:
34 | sss+='-'*(i+2)
35 | sss+='+'
36 |
37 |
38 | with open('table.log','w')as f:
39 | for r in soup.select('tr'):
40 | f.write(sss+'\n')
41 | s='|'
42 | for i, td in enumerate(r.select('td')):
43 | t=' '*(line_max[i]-get_len(td.text))
44 | s+=f' {td.text}{t} |'
45 | f.write(s+'\n')
46 | f.write(sss+'\n')
47 |
48 | print('表格已输出到table.log')
49 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. YTask documentation master file, created by
2 | sphinx-quickstart on Fri Jul 9 14:15:44 2021.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to YTask's documentation!
7 | =================================
8 |
9 | | YTask is an asynchronous task queue for handling distributed jobs in
10 | golang
11 | | golang异步任务/队列 框架
12 |
13 | - `中文文档 `__
14 | (中文文档更加全面,优先阅读中文文档)
15 | - `En Doc `__
16 | - `Github `__
17 | - `V2 Doc `__
18 |
19 | 安装
20 | -----
21 |
22 | .. code:: shell
23 |
24 | # 安装核心代码
25 | go get -u github.com/gojuukaze/YTask/v3
26 |
27 | # 安装broker, backend
28 | go get -u github.com/gojuukaze/YTask/drives/redis/v3
29 | go get -u github.com/gojuukaze/YTask/drives/rabbitmq/v3
30 | go get -u github.com/gojuukaze/YTask/drives/mongo2/v3
31 | go get -u github.com/gojuukaze/YTask/drives/memcache/v3
32 |
33 |
34 | 特点
35 | -----
36 |
37 | - 简单无侵入
38 | - 方便扩展broker,backend
39 | - 支持所有能被序列化为json的类型(如:int,float,数组,结构体,复杂的结构体嵌套等)
40 | - 支持任务重试,延时任务
41 |
42 | 架构图
43 | -------
44 |
45 | .. image:: _static/architecture_diagram.png
46 |
47 | .. toctree::
48 | :maxdepth: 2
49 | :caption: 使用指南
50 | :hidden:
51 |
52 | QuickStart
53 | server
54 | client
55 | broker
56 | backend
57 | retry
58 | delay
59 | expire
60 | workflow
61 | abortTask
62 | log
63 | error
64 | upgrade
65 |
66 | Indices and tables
67 | ==================
68 |
69 | * :ref:`genindex`
70 | * :ref:`modindex`
71 | * :ref:`search`
72 |
--------------------------------------------------------------------------------
/docs/log.rst:
--------------------------------------------------------------------------------
1 | Log
2 | =======
3 |
4 | log接口
5 | -----------------------------------------
6 |
7 | 注意: v2.5+ 改用接口形式,只要实现 log.LoggerInterface 接口即可,默认已经实现一个基于 logrus 的 logger
8 |
9 | .. code:: go
10 |
11 | logger := ytask.Logger.NewYTaskLogger()
12 |
13 | Server := ytask.Server.NewServer(
14 | ...
15 | ytask.Config.Logger(logger), // 可以不设置 logger
16 | ...
17 | )
18 |
19 |
20 |
21 | v2.4 以前版本使用方法
22 | ---------------------------------
23 |
24 | YTask使用logrus打印日志
25 |
26 |
27 | 输出日志到文件
28 | ^^^^^^^^^^^^^^^^^^
29 |
30 | .. code:: go
31 |
32 | import (
33 | "github.com/gojuukaze/YTask/v3/log"
34 | "github.com/gojuukaze/go-watch-file")
35 |
36 | // write to file
37 | file,err:=watchFile.OpenWatchFile("xx.log")
38 | if err != nil {
39 | panic(err)
40 | }
41 | log.YTaskLog.SetOutput(file)
42 |
43 | - `go-watch-file `__
44 | :一个专为日志系统编写的读写文件库,会自动监听文件的变化,文件被删除时自动创建新文件。
45 |
46 | 设置level
47 | ^^^^^^^^^^^^^^^^^^^^^
48 |
49 | .. code:: go
50 |
51 | // set level
52 | log.YTaskLog.SetLevel(logrus.InfoLevel)
53 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx==3.4.3
2 | sphinx-rtd-theme==0.5.1
3 | Pillow==9.0.1
--------------------------------------------------------------------------------
/docs/retry.rst:
--------------------------------------------------------------------------------
1 | 任务重试
2 | ===========
3 |
4 | 触发重试
5 | ----------
6 |
7 | 有两种方法可以触发2重试
8 |
9 | - 使用 panic
10 |
11 | .. code:: go
12 |
13 |
14 | func add(a, b int){
15 | panic("xx")
16 | }
17 |
18 | - 使用 TaskCtl
19 |
20 | .. code:: go
21 |
22 |
23 | func add(ctl *server.TaskCtl,a, b int){
24 | ctl.Retry(errors.New("xx"))
25 | // 别忘了return,否则会继续执行下去
26 | return
27 | }
28 |
29 | 设置重试次数
30 | --------------
31 |
32 | 默认的重试次数是3次,目前只支持在client端设置
33 |
34 | - in client
35 |
36 | .. code:: go
37 |
38 | client.SetTaskCtl(client.RetryCount, 5).Send("group1", "retry", 123, 44)
39 |
40 | 禁用重试
41 | ---------
42 |
43 | - 在server端针对某个任务禁用
44 |
45 | .. code:: go
46 |
47 | func add(ctl *controller.TaskCtl,a, b int){
48 | ctl.SetRetryCount(0)
49 | return
50 | }
51 |
52 | - 在client端对此次任务禁用
53 |
54 | .. code:: go
55 |
56 | client.SetTaskCtl(client.RetryCount, 0).Send("group1", "retry", 123, 44)
57 |
--------------------------------------------------------------------------------
/docs/upgrade.rst:
--------------------------------------------------------------------------------
1 | 升级说明
2 | =============
3 |
4 | 从v2升级到v3
5 | -----------------
6 |
7 | 你需要注意以下改动
8 |
9 | * v3版本需要独立安装broker, backend
10 |
11 | * TaskCtl结构体的位置迁移到了server包中,若需要获取RetryCount,则使用 ``GetRetryCount()``
12 |
13 | .. code:: go
14 |
15 | import "github.com/gojuukaze/YTask/v3/server"
16 |
17 | func add(ctl *server.TaskCtl, a, b int) int {
18 |
19 | if ctl.GetRetryCount()==1 {
20 | return 0
21 | }
22 | return a +b
23 | }
24 |
25 | * v3版本修改了队列名中之前拼错的单词,及msg结构体。升级v3后你可以保持之前的server运行一段时间再关闭。
26 | 或者使用 ``UseV2Name()`` 兼容
27 |
28 | .. code:: go
29 |
30 | import "github.com/gojuukaze/YTask/v3"
31 |
32 | ytask.UseV2Name()
33 |
34 | ser:=ytask.NewServer(...)
35 |
36 | * 若你之前使用MongoBackend又不想丢失之前保存的任务结果,则使用 ``mongo`` 包,否则建议使用 ``mongo2`` 包
37 |
38 | * v3移除了RocketMq支持,具体说明见:https://github.com/gojuukaze/YTask/tree/master/drives/rocketmq
39 |
--------------------------------------------------------------------------------
/docs/workflow.rst:
--------------------------------------------------------------------------------
1 | 工作流
2 | ===========
3 |
4 | **注意!!使用工作流必须配置backend!!!** ,另外若backend出错会导致工作流终止。
5 |
6 | 开启工作流
7 | ---------------
8 |
9 | 通过 ``Workflow()`` 开启工作流,然后通过 ``Send()`` 提交子任务。
10 |
11 | 使用 ``Send()`` 时,只有第一个任务才需要任务参数,后续任务的参数默认为前一个任务的返回值。
12 |
13 | .. code:: go
14 |
15 | ser := ytask.Server.NewServer(...)
16 | client:= ser.GetClient()
17 |
18 |
19 | tId, _ := client.Workflow().
20 | Send("group1", "add", 123, 44).
21 | Send("group1", "add").
22 | Done()
23 |
24 | 工作流同样支持通过 ``SetTaskCtl()`` 设置任务参数。( 设置延时任务时,只支持 ``RunAfter`` )
25 |
26 | 对于下面的样例,第二个任务只有ExpireTime,不会继承第一个任务的设置
27 |
28 | .. code:: go
29 |
30 | tId, _ := client.Workflow().
31 | SetTaskCtl(client.RunAfter, 2*time.Second).
32 | Send("group1", "add", 123, 44).
33 | SetTaskCtl(client.ExpireTime, time.Now()).
34 | Send("group1", "add").
35 | Done()
36 |
37 |
38 | 获取工作流结果
39 | -----------------
40 |
41 | 你可以使用 ``GetResult()`` 获取最后一个任务的返回值。
42 |
43 | 也可以使用 ``GetResult2()`` 获取任务流的运行进度。
44 |
45 | .. code:: go
46 |
47 | result, err := client.GetResult2(id, time.Second*2, time.Millisecond*300)
48 |
49 | // 说明任务还未开始
50 | if yerrors.IsEqual(err, yerrors.ErrTypeTimeOut) {
51 | // ...
52 | }
53 |
54 | // status: waiting , running , success , failure , expired , abort
55 | for name, status:= range result.Workflow{
56 | fmt.Println(name, status)
57 | }
58 |
--------------------------------------------------------------------------------
/drives/README.md:
--------------------------------------------------------------------------------
1 | # drives
2 |
3 |
4 | ## Brokers
5 |
6 | * [Redis](./redis)
7 | * [RabbitMQ](./rabbitmq)
8 | * [RocketMQ](./rocketmq) (不再支持)
9 |
10 | ## Backends
11 |
12 |
13 |
14 | * [Redis](./redis)
15 | * [MongoDB](./mongo2) (优先使用mongo2包)
16 | * [MemCache](./memcache)
--------------------------------------------------------------------------------
/drives/memcache/README.md:
--------------------------------------------------------------------------------
1 | # MemCache
2 |
3 |
4 | ## Installation
5 |
6 | ```shell
7 | go get -u github.com/gojuukaze/YTask/drives/memcache/v3
8 | ```
9 |
10 | ## Backend
11 |
12 | ```go
13 | package main
14 |
15 | import (
16 | "github.com/gojuukaze/YTask/drives/memcache/v3"
17 | )
18 |
19 | func main() {
20 | backend := memcache.NewMemCacheBackend([]string{"127.0.0.1:11211"}, 10)
21 | // ...
22 | }
23 | ```
24 |
--------------------------------------------------------------------------------
/drives/memcache/backend.go:
--------------------------------------------------------------------------------
1 | package memcache
2 |
3 | import (
4 | "github.com/bradfitz/gomemcache/memcache"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/util/yjson"
8 | "github.com/gojuukaze/YTask/v3/yerrors"
9 | )
10 |
11 | type MemCacheBackend struct {
12 | client *Client
13 | server []string
14 | poolSize int
15 | }
16 |
17 | func NewMemCacheBackend(server []string, poolSize int) MemCacheBackend {
18 | return MemCacheBackend{
19 | server: server,
20 | poolSize: poolSize,
21 | }
22 | }
23 |
24 | func (r *MemCacheBackend) Activate() {
25 | r.client = NewMemCacheClient(r.server, r.poolSize)
26 | }
27 |
28 | func (r *MemCacheBackend) SetPoolSize(n int) {
29 | r.poolSize = n
30 | }
31 |
32 | func (r *MemCacheBackend) GetPoolSize() int {
33 | return r.poolSize
34 | }
35 |
36 | func (r *MemCacheBackend) SetResult(result message.Result, exTime int) error {
37 | b, err := yjson.YJson.Marshal(result)
38 |
39 | if err != nil {
40 | return err
41 | }
42 | err = r.client.Set(result.GetBackendKey(), b, exTime)
43 | return err
44 | }
45 |
46 | func (r *MemCacheBackend) GetResult(key string) (message.Result, error) {
47 | var result message.Result
48 |
49 | b, err := r.client.Get(key)
50 | if err != nil {
51 | if err == memcache.ErrCacheMiss {
52 | return result, yerrors.ErrNilResult{}
53 | }
54 | return result, err
55 | }
56 |
57 | err = yjson.YJson.Unmarshal(b, &result)
58 | return result, err
59 | }
60 |
61 | func (r MemCacheBackend) Clone() backends.BackendInterface {
62 | return &MemCacheBackend{
63 | server: r.server,
64 | poolSize: r.poolSize,
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/drives/memcache/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/drives/memcache/v3
2 |
3 | go 1.18
4 |
5 | require github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
6 |
--------------------------------------------------------------------------------
/drives/memcache/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
2 | github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
3 |
--------------------------------------------------------------------------------
/drives/memcache/memcache.go:
--------------------------------------------------------------------------------
1 | package memcache
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/bradfitz/gomemcache/memcache"
7 | )
8 |
9 | type Client struct {
10 | Server []string
11 | PoolSize int
12 | memCacheConn *memcache.Client
13 | }
14 |
15 | // NewMemCacheClient
16 | // - server: ["host:port"]
17 | func NewMemCacheClient(server []string, poolSize int) *Client {
18 | client := Client{server, poolSize, nil}
19 | client.Dail()
20 | err := client.Ping()
21 | if err != nil {
22 | panic("YTask: connect memCached error : " + err.Error())
23 | }
24 |
25 | return &client
26 | }
27 |
28 | func (c *Client) Dail() {
29 | c.memCacheConn = memcache.New(c.Server...)
30 | c.memCacheConn.MaxIdleConns = c.PoolSize
31 | // 为什么默认的Timeout是100millisecond??这么短就没必要用连接池了。所以这里改长一点。
32 | // 写这个注释是因为不熟悉memcached,不知道设这么短是否有特殊的原因,后人看到有问题的话请反馈
33 | c.memCacheConn.Timeout = time.Second * 10
34 | }
35 |
36 | func (c *Client) Check() error {
37 | err := c.Ping()
38 | if err != nil && err.Error() == "EOF" {
39 | c.Dail()
40 | return nil
41 | }
42 | return err
43 | }
44 |
45 | func (c *Client) Get(key string) ([]byte, error) {
46 | err := c.Check()
47 | if err != nil {
48 | return nil, err
49 | }
50 | item, err := c.memCacheConn.Get(key)
51 | if err != nil {
52 | return nil, err
53 | }
54 | return item.Value, nil
55 | }
56 |
57 | func (c *Client) Set(key string, value []byte, exTime int) error {
58 | err := c.Check()
59 | if err != nil {
60 | return err
61 | }
62 | err = c.memCacheConn.Set(&memcache.Item{Key: key, Value: value, Expiration: int32(exTime)})
63 | if err != nil {
64 | return err
65 | }
66 | return nil
67 | }
68 |
69 | func (c *Client) Flush() error {
70 | return c.memCacheConn.FlushAll()
71 | }
72 |
73 | func (c *Client) Ping() error {
74 | return c.memCacheConn.Ping()
75 | }
76 |
77 | func (c *Client) Close() {
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/drives/memcache/test/README.md:
--------------------------------------------------------------------------------
1 |
2 | ```shell
3 | docker run --name ytask-memcache -d -p 11211:11211 memcached
4 | cd drives/memcache
5 | go test -v test/*.go
6 |
7 | docker stop ytask-memcache
8 | docker rm ytask-memcache
9 |
10 | ```
--------------------------------------------------------------------------------
/drives/memcache/test/mencacheBackend_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/memcache/v3"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "os"
9 | "os/signal"
10 | "syscall"
11 | "testing"
12 | "time"
13 | )
14 |
15 | func TestMemcachedBackend(t *testing.T) {
16 | b := memcache.NewMemCacheBackend([]string{"127.0.0.1:11211"}, 1)
17 | result := message.NewResult("xx123")
18 | result.FuncReturn = nil
19 | b.Activate()
20 | err := b.SetResult(result, 2)
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 |
25 | r2, err := b.GetResult(result.GetBackendKey())
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
30 | t.Fatalf("%v != %v", r2, result)
31 | }
32 |
33 | time.Sleep(2 * time.Second)
34 |
35 | _, err = b.GetResult(result.GetBackendKey())
36 | if !yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
37 | t.Fatal("err != ErrNilResult")
38 |
39 | }
40 |
41 | }
42 |
43 | func TestMemcachedBackend2(t *testing.T) {
44 | b := memcache.NewMemCacheBackend([]string{"127.0.0.1:11211"}, 1)
45 | result := message.NewResult("xx123")
46 | result.FuncReturn = nil
47 | b.Activate()
48 | err := b.SetResult(result, 100)
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 |
53 | fmt.Printf("重启memcache后通过 \" kill -CONT %d \" 继续运行测试\n", os.Getpid())
54 | quit := make(chan os.Signal, 1)
55 | signal.Notify(quit, syscall.SIGCONT)
56 | <-quit
57 |
58 | err = b.SetResult(result, 100)
59 | if err != nil {
60 | t.Fatal(err)
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/drives/mongo/README.md:
--------------------------------------------------------------------------------
1 | # MongoDB
2 |
3 | 此版本已过时,建议使用`mongo2`版本
4 |
5 | 如果是从YTask v2 版本迁移过来的,可继续使用此版本。
6 |
7 |
8 | ## Installation
9 |
10 | ```shell
11 | go get -u github.com/gojuukaze/YTask/drives/mongo/v3
12 | ```
13 |
--------------------------------------------------------------------------------
/drives/mongo/backend.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/backends"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/util/yjson"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "go.mongodb.org/mongo-driver/mongo"
9 | )
10 |
11 | type Backend struct {
12 | client MongoClient
13 | host string
14 | port string
15 | user string
16 | password string
17 | db string
18 | collection string
19 | //poolSize int
20 | }
21 |
22 | func NewMongoBackend(host, port, user, password, db, collection string) Backend {
23 | return Backend{
24 | host: host,
25 | port: port,
26 | user: user,
27 | password: password,
28 | db: db,
29 | collection: collection,
30 | //poolSize: 0,
31 | }
32 | }
33 |
34 | func (r *Backend) Activate() {
35 | client := NewMongoClient(r.host, r.port, r.user, r.password, r.db, r.collection)
36 | r.client = client
37 | }
38 |
39 | func (r *Backend) SetPoolSize(n int) {
40 | //r.poolSize = n
41 | }
42 |
43 | func (r *Backend) GetPoolSize() int {
44 | //return r.poolSize
45 | return 0
46 | }
47 |
48 | func (r *Backend) SetResult(result message.Result, exTime int) error {
49 |
50 | b, err := yjson.YJson.Marshal(result)
51 |
52 | if err != nil {
53 | return err
54 | }
55 |
56 | err = r.client.Set(result.GetBackendKey(), b, exTime)
57 |
58 | return err
59 | }
60 |
61 | func (r *Backend) GetResult(key string) (message.Result, error) {
62 | var result message.Result
63 |
64 | b, err := r.client.Get(key)
65 | if err != nil {
66 | if err == mongo.ErrNoDocuments {
67 | return result, yerrors.ErrNilResult{}
68 | }
69 | return result, err
70 | }
71 |
72 | err = yjson.YJson.Unmarshal([]byte(b), &result)
73 |
74 | return result, err
75 | }
76 |
77 | func (r Backend) Clone() backends.BackendInterface {
78 | return &Backend{
79 | host: r.host,
80 | port: r.port,
81 | password: r.password,
82 | db: r.db,
83 | collection: r.collection,
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/drives/mongo/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/drives/mongo/v3
2 |
3 | go 1.18
4 |
5 | require go.mongodb.org/mongo-driver v1.9.1
6 |
7 | require (
8 | github.com/go-stack/stack v1.8.0 // indirect
9 | github.com/golang/snappy v0.0.1 // indirect
10 | github.com/klauspost/compress v1.13.6 // indirect
11 | github.com/pkg/errors v0.9.1 // indirect
12 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
13 | github.com/xdg-go/scram v1.0.2 // indirect
14 | github.com/xdg-go/stringprep v1.0.2 // indirect
15 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
16 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect
17 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
18 | golang.org/x/text v0.3.5 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/drives/mongo/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
5 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
6 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
7 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
8 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
9 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
10 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
11 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
14 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
15 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
16 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
17 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
21 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
22 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
23 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
24 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
25 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
26 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
27 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
28 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
29 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
30 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
31 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
32 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
33 | go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
34 | go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
36 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
37 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
38 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
39 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
40 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
41 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
42 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
44 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
47 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
48 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
50 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
51 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
52 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
54 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
55 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
57 |
--------------------------------------------------------------------------------
/drives/mongo/mongo.go:
--------------------------------------------------------------------------------
1 | // Package mongo
2 | // Deprecated !! Use github.com/gojuukaze/YTask/drives/mongo/v32
3 | package mongo
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "go.mongodb.org/mongo-driver/x/bsonx"
10 | "time"
11 |
12 | "go.mongodb.org/mongo-driver/bson"
13 | "go.mongodb.org/mongo-driver/mongo"
14 | "go.mongodb.org/mongo-driver/mongo/options"
15 | )
16 |
17 | func init() {
18 | log.YTaskLog.WithField("server", "MongoInit").
19 | Warning("drives/mongo is deprecated, replace it with drives/mongo2")
20 |
21 | }
22 |
23 | type Result struct {
24 | Uuid string `json:"uuid" bson:"uuid"`
25 | Res string `json:"res" bson:"res"`
26 | CreateTime time.Time `json:"create_time" bson:"create_time"`
27 | }
28 |
29 | type MongoClient struct {
30 | mongoConn *mongo.Client
31 | mongoCollection *mongo.Collection
32 | hasSetExTime bool
33 | }
34 |
35 | func NewMongoClient(host, port, user, password, db, collection string) MongoClient {
36 | var clientOptions *options.ClientOptions
37 | if user != "" {
38 | clientOptions = options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s@%s:%s", user, password, host, port))
39 | } else {
40 | clientOptions = options.Client().ApplyURI(fmt.Sprintf("mongodb://%s:%s", host, port))
41 | }
42 |
43 | ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
44 | //client, err := mongo.Connect(context.TODO(), clientOptions)
45 | client, err := mongo.Connect(ctx, clientOptions)
46 | if err != nil {
47 | panic("YTask: connect mongo error : " + err.Error())
48 | }
49 |
50 | //err = client.Ping(context.TODO(), nil)
51 | err = client.Ping(ctx, nil)
52 | if err != nil {
53 | panic("YTask: connect mongo error : " + err.Error())
54 | }
55 |
56 | coll := client.Database(db).Collection(collection)
57 |
58 | return MongoClient{
59 | mongoConn: client,
60 | mongoCollection: coll,
61 | }
62 | }
63 |
64 | // =======================
65 | // high api
66 | // =======================
67 | func (c *MongoClient) Get(key string) (string, error) {
68 | var res Result
69 | filter := bson.D{{"uuid", key}}
70 | ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
71 | //err := c.mongoCollection.FindOne(context.TODO(), filter).Decode(&res)
72 | err := c.mongoCollection.FindOne(ctx, filter).Decode(&res)
73 | if err != nil {
74 | return "", err
75 | }
76 | return res.Res, nil
77 | }
78 |
79 | func (c *MongoClient) Set(key string, value interface{}, exTime int) error {
80 | ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
81 |
82 | if !c.hasSetExTime {
83 | _, _ = c.mongoCollection.Indexes().DropOne(ctx, "create_time_ttl_index")
84 | if exTime != -1 {
85 | option := options.Index()
86 | option.SetName("create_time_ttl_index")
87 | option.SetExpireAfterSeconds(int32(exTime))
88 | option.SetBackground(true)
89 |
90 | _, _ = c.mongoCollection.Indexes().CreateOne(ctx, mongo.IndexModel{
91 | Keys: bsonx.Doc{{"create_time", bsonx.Int32(1)}},
92 | Options: option,
93 | })
94 | }
95 | c.hasSetExTime = true
96 | }
97 |
98 | res := Result{key, string(value.([]byte)), time.Now()}
99 | _, err := c.Get(key)
100 | if err == mongo.ErrNoDocuments {
101 | //_, err := c.mongoCollection.InsertOne(context.TODO(), res)
102 | _, err := c.mongoCollection.InsertOne(ctx, res)
103 | return err
104 | } else {
105 | filter := bson.D{{"uuid", key}}
106 |
107 | //update := bson.D{
108 | // {"$set", bson.D{
109 | // {"uuid", key},
110 | // {"res", string(value.([]byte))},
111 | // }},
112 | //}
113 | //
114 | //_, err := c.mongoCollection.UpdateOne(context.TODO(), filter, update)
115 |
116 | // or
117 | //_, err := c.mongoCollection.ReplaceOne(context.TODO(), filter, res)
118 | _, err := c.mongoCollection.ReplaceOne(ctx, filter, res)
119 | return err
120 | }
121 | }
122 |
123 | func (c *MongoClient) Ping() error {
124 | ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
125 | //return c.mongoConn.Ping(context.TODO(), nil)
126 | return c.mongoConn.Ping(ctx, nil)
127 | }
128 |
129 | func (c *MongoClient) Close() {
130 | ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
131 | //_ = c.mongoConn.Disconnect(context.TODO())
132 | _ = c.mongoConn.Disconnect(ctx)
133 | }
134 |
--------------------------------------------------------------------------------
/drives/mongo/test/README.md:
--------------------------------------------------------------------------------
1 |
2 | ```shell
3 | docker run --name ytask-mongo -d -p 27017:27017 mongo
4 | cd drives/mongo
5 | go test -v test/*.go
6 |
7 | docker stop ytask-mongo
8 | docker rm ytask-mongo
9 |
10 | ```
--------------------------------------------------------------------------------
/drives/mongo/test/mongoBackend_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/mongo/v3"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "testing"
8 | )
9 |
10 | func TestMongoBackend(t *testing.T) {
11 | b := mongo.NewMongoBackend("127.0.0.1", "27017", "", "", "task", "task")
12 | result := message.NewResult("xx123")
13 | result.FuncReturn = nil
14 | b.Activate()
15 | err := b.SetResult(result, 2)
16 | if err != nil {
17 | t.Fatal(err)
18 | }
19 |
20 | r2, err := b.GetResult(result.GetBackendKey())
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
25 | t.Fatalf("%v != %v", r2, result)
26 | }
27 |
28 | // mongo不支持过期时间
29 |
30 | //time.Sleep(2 * time.Second)
31 | //
32 | //_, err = b.GetResult(result.GetBackendKey())
33 | //if !yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
34 | // t.Fatal("err != ErrNilResult")
35 | //
36 | //}
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/drives/mongo2/README.md:
--------------------------------------------------------------------------------
1 | # MongoDB
2 |
3 | **【注意】使用MongoDB时,若需设置过期时间,只能在 ``NewMongoBackend()`` 函数中设置**
4 |
5 | ## Installation
6 |
7 | ```shell
8 | go get -u github.com/gojuukaze/YTask/drives/mongo2/v3
9 | ```
10 |
11 | ## Backend
12 |
13 | ```go
14 | package main
15 |
16 | import (
17 | "github.com/gojuukaze/YTask/drives/mongo2/v3"
18 | )
19 |
20 | func main() {
21 | backend := mongo2.NewMongoBackend("127.0.0.1", "27017", "", "", "test", "test", 200)
22 | // ...
23 | }
24 | ```
25 |
--------------------------------------------------------------------------------
/drives/mongo2/backend.go:
--------------------------------------------------------------------------------
1 | package mongo2
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/backends"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/util/yjson"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "go.mongodb.org/mongo-driver/mongo"
9 | )
10 |
11 | type Backend struct {
12 | client *Client
13 | host string
14 | port string
15 | user string
16 | password string
17 | db string
18 | collection string
19 | exTime int
20 | }
21 |
22 | // NewMongoBackend
23 | // - exTime: Expiration time in seconds. <=0: no expiration.
24 | func NewMongoBackend(host, port, user, password, db, collection string, exTime int) Backend {
25 | return Backend{
26 | host: host,
27 | port: port,
28 | user: user,
29 | password: password,
30 | db: db,
31 | collection: collection,
32 | exTime: exTime,
33 | }
34 | }
35 |
36 | func (r *Backend) Activate() {
37 | r.client = NewMongoClient(r.host, r.port, r.user, r.password, r.db, r.collection, r.exTime)
38 |
39 | }
40 |
41 | func (r *Backend) SetPoolSize(n int) {
42 | }
43 |
44 | func (r *Backend) GetPoolSize() int {
45 | return 0
46 | }
47 |
48 | func (r *Backend) SetResult(result message.Result, exTime int) error {
49 |
50 | b, err := yjson.YJson.Marshal(result)
51 |
52 | if err != nil {
53 | return err
54 | }
55 |
56 | err = r.client.Set(result.GetBackendKey(), b)
57 |
58 | return err
59 | }
60 |
61 | func (r *Backend) GetResult(key string) (message.Result, error) {
62 | var result message.Result
63 |
64 | res, err := r.client.Get(key)
65 | if err != nil {
66 | if err == mongo.ErrNoDocuments {
67 | return result, yerrors.ErrNilResult{}
68 | }
69 | return result, err
70 | }
71 |
72 | err = yjson.YJson.Unmarshal(res.Data, &result)
73 |
74 | return result, err
75 | }
76 |
77 | func (r Backend) Clone() backends.BackendInterface {
78 | return &Backend{
79 | host: r.host,
80 | port: r.port,
81 | password: r.password,
82 | db: r.db,
83 | collection: r.collection,
84 | exTime: r.exTime,
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/drives/mongo2/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/drives/mongo2/v3
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gojuukaze/YTask/v3 v3.0.1
7 | go.mongodb.org/mongo-driver v1.9.1
8 | )
9 |
10 | require (
11 | github.com/go-stack/stack v1.8.0 // indirect
12 | github.com/golang/snappy v0.0.1 // indirect
13 | github.com/google/uuid v1.3.0 // indirect
14 | github.com/json-iterator/go v1.1.12 // indirect
15 | github.com/klauspost/compress v1.13.6 // indirect
16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
17 | github.com/modern-go/reflect2 v1.0.2 // indirect
18 | github.com/pkg/errors v0.9.1 // indirect
19 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
20 | github.com/xdg-go/scram v1.0.2 // indirect
21 | github.com/xdg-go/stringprep v1.0.2 // indirect
22 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
23 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect
24 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
25 | golang.org/x/text v0.3.8 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/drives/mongo2/mongo.go:
--------------------------------------------------------------------------------
1 | package mongo2
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "go.mongodb.org/mongo-driver/bson"
7 | "go.mongodb.org/mongo-driver/mongo/readpref"
8 | "time"
9 |
10 | "go.mongodb.org/mongo-driver/mongo"
11 | "go.mongodb.org/mongo-driver/mongo/options"
12 | )
13 |
14 | type Result struct {
15 | Id string `json:"_id" bson:"_id"`
16 | Data []byte `json:"data" bson:"data"`
17 | CreateTime time.Time `json:"create_time" bson:"create_time"`
18 | }
19 |
20 | type Client struct {
21 | Uri string
22 | DB string
23 | Collection string
24 | Expires int
25 | cli *mongo.Client
26 | }
27 |
28 | /*
29 | NewMongoClient
30 | 经测试mongo-driver会自动断线重连,且自带连接池,但貌似设置连接池大小没有作用,因此就不需要poolSize选项了
31 | */
32 | func NewMongoClient(host, port, user, password, db, collection string, expires int) *Client {
33 | var uri string
34 | if user != "" {
35 | uri = fmt.Sprintf("mongodb://%s:%s@%s:%s", user, password, host, port)
36 | } else {
37 | uri = fmt.Sprintf("mongodb://%s:%s", host, port)
38 | }
39 | client := Client{uri, db, collection, expires, nil}
40 | err := client.Init()
41 | if err != nil {
42 | panic("YTask: init mongo error : " + err.Error())
43 | }
44 | return &client
45 | }
46 |
47 | // =======================
48 | // high api
49 | // =======================
50 | func (c *Client) Get(key string) (Result, error) {
51 | var result Result
52 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
53 | defer cancel()
54 |
55 | col := c.GetCollection()
56 | err := col.FindOne(ctx, bson.D{{"_id", key}}).Decode(&result)
57 | // 由于mongo不是立即清理过期数据,所以这里需要判断是否过期
58 | if err == nil && c.Expires > 0 && result.CreateTime.Add(time.Duration(c.Expires)*time.Second).Before(time.Now()) {
59 | err = mongo.ErrNoDocuments
60 | }
61 |
62 | return result, err
63 |
64 | }
65 |
66 | func (c *Client) Set(key string, value []byte) error {
67 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
68 | defer cancel()
69 | col := c.GetCollection()
70 |
71 | // 这里有个问题,如果doc在find之后过期,则ReplaceOne会报错。不过一般不用担心,key没那么容易重复
72 | filter := bson.D{{"_id", key}}
73 | err := col.FindOne(ctx, filter).Err()
74 | if err == mongo.ErrNoDocuments {
75 | _, err = col.InsertOne(ctx, Result{key, value, time.Now()})
76 | return err
77 |
78 | } else if err == nil {
79 | _, err = col.ReplaceOne(ctx, filter, Result{key, value, time.Now()})
80 | return err
81 | }
82 |
83 | return err
84 | }
85 |
86 | func (c *Client) GetClient(ctx context.Context) (*mongo.Client, error) {
87 |
88 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(c.Uri))
89 | return client, err
90 | }
91 |
92 | func (c *Client) GetCollection() *mongo.Collection {
93 | return c.cli.Database(c.DB).Collection(c.Collection)
94 | }
95 |
96 | func (c *Client) Init() error {
97 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
98 | defer cancel()
99 | var err error
100 | c.cli, err = c.GetClient(ctx)
101 | if err != nil {
102 | return err
103 | }
104 |
105 | err = c.cli.Ping(ctx, readpref.Primary())
106 | if err != nil {
107 | return err
108 | }
109 |
110 | col := c.GetCollection()
111 |
112 | if c.Expires > 0 {
113 | err = c.InitIndex(ctx, col.Indexes())
114 | if err != nil {
115 | return err
116 | }
117 | }
118 |
119 | return err
120 | }
121 |
122 | func (c *Client) InitIndex(ctx context.Context, index mongo.IndexView) error {
123 |
124 | cur, err := index.List(ctx)
125 | if err != nil {
126 | return err
127 | }
128 | // 索引为空则创建
129 | if !cur.Next(ctx) {
130 | indexOps := options.Index()
131 | indexOps.SetExpireAfterSeconds(int32(c.Expires))
132 |
133 | _, err = index.CreateOne(ctx, mongo.IndexModel{
134 | Keys: bson.D{{"create_time", 1}},
135 | Options: indexOps,
136 | })
137 | return err
138 | }
139 | return nil
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/drives/mongo2/test/README.md:
--------------------------------------------------------------------------------
1 |
2 | ```shell
3 | docker run --name ytask-mongo -d -p 27017:27017 mongo
4 | cd drives/mongo2
5 | go test -v test/*.go
6 |
7 | docker stop ytask-mongo
8 | docker rm ytask-mongo
9 |
10 | ```
--------------------------------------------------------------------------------
/drives/mongo2/test/mongoBackend_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/mongo2/v3"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "os"
9 | "os/signal"
10 | "syscall"
11 | "testing"
12 | "time"
13 | )
14 |
15 | func TestMongoBackend(t *testing.T) {
16 | b := mongo2.NewMongoBackend("127.0.0.1", "27017", "", "", "test", "test", 2)
17 | result := message.NewResult("xx123")
18 | result.FuncReturn = nil
19 | b.Activate()
20 | err := b.SetResult(result, 0)
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 |
25 | r2, err := b.GetResult(result.GetBackendKey())
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
30 | t.Fatalf("%v != %v", r2, result)
31 | }
32 |
33 | time.Sleep(3 * time.Second)
34 |
35 | _, err = b.GetResult(result.GetBackendKey())
36 | if !yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
37 | t.Fatal("err != ErrNilResult")
38 | }
39 |
40 | }
41 |
42 | func TestMongoBackend2(t *testing.T) {
43 | // 测试断线重连
44 | b := mongo2.NewMongoBackend("127.0.0.1", "27017", "", "", "test", "test", 0)
45 | result := message.NewResult("xx123")
46 | result.FuncReturn = nil
47 | b.Activate()
48 | err := b.SetResult(result, 0)
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 |
53 | fmt.Printf("重启mongo后通过 \" kill -CONT %d \" 继续运行测试\n", os.Getpid())
54 | quit := make(chan os.Signal, 1)
55 | signal.Notify(quit, syscall.SIGCONT)
56 | <-quit
57 |
58 | r2, err := b.GetResult(result.GetBackendKey())
59 | if err != nil {
60 | t.Fatal(err)
61 | }
62 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
63 | t.Fatalf("%v != %v", r2, result)
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/drives/rabbitmq/README.md:
--------------------------------------------------------------------------------
1 | # RabbitMQ
2 |
3 | ## Installation
4 |
5 | ```shell
6 | go get -u github.com/gojuukaze/YTask/drives/rabbitmq/v3
7 | ```
8 |
9 | ## Broker
10 |
11 | ```go
12 | package main
13 |
14 | import (
15 | "github.com/gojuukaze/YTask/drives/rabbitmq/v3"
16 | )
17 |
18 | func main() {
19 | broker := rabbitmq.NewRabbitMqBroker("127.0.0.1", "5672", "guest", "guest", "", 2)
20 | // ...
21 | }
22 | ```
23 |
24 |
--------------------------------------------------------------------------------
/drives/rabbitmq/broker.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/brokers"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/util/yjson"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | )
9 |
10 | type Broker struct {
11 | client *Client
12 | host string
13 | port string
14 | user string
15 | password string
16 | vhost string
17 | poolSize int
18 | }
19 |
20 | func NewRabbitMqBroker(host, port, user, password, vhost string, poolSize int) Broker {
21 | return Broker{
22 | host: host,
23 | port: port,
24 | password: password,
25 | user: user,
26 | vhost: vhost,
27 | poolSize: poolSize,
28 | }
29 | }
30 |
31 | func (r *Broker) Activate() {
32 | r.client = NewRabbitMqClient(r.host, r.port, r.user, r.password, r.vhost, r.poolSize)
33 | }
34 |
35 | func (r *Broker) SetPoolSize(n int) {
36 | r.poolSize = n
37 | }
38 | func (r *Broker) GetPoolSize() int {
39 | return r.poolSize
40 | }
41 |
42 | func (r *Broker) Next(queueName string) (message.Message, error) {
43 | var msg message.Message
44 |
45 | b, err := r.client.Get(queueName)
46 | if err != nil {
47 | if err == AMQPNil {
48 | err = yerrors.ErrEmptyQueue{}
49 | }
50 | return msg, err
51 | }
52 |
53 | err = yjson.YJson.Unmarshal(b, &msg)
54 | return msg, err
55 | }
56 |
57 | func (r *Broker) Send(queueName string, msg message.Message) error {
58 | b, err := yjson.YJson.Marshal(msg)
59 |
60 | if err != nil {
61 | return err
62 | }
63 | err = r.client.Publish(queueName, b, 0)
64 | return err
65 | }
66 |
67 | func (r *Broker) LSend(queueName string, msg message.Message) error {
68 | b, err := yjson.YJson.Marshal(msg)
69 |
70 | if err != nil {
71 | return err
72 | }
73 | err = r.client.Publish(queueName, b, 5)
74 | return err
75 | }
76 |
77 | func (r Broker) Clone() brokers.BrokerInterface {
78 |
79 | return &Broker{
80 | host: r.host,
81 | port: r.port,
82 | password: r.password,
83 | user: r.user,
84 | vhost: r.vhost,
85 | poolSize: r.poolSize,
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/drives/rabbitmq/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/drives/rabbitmq/v3
2 |
3 | go 1.18
4 |
5 | require github.com/rabbitmq/amqp091-go v1.3.4
6 |
--------------------------------------------------------------------------------
/drives/rabbitmq/go.sum:
--------------------------------------------------------------------------------
1 | github.com/rabbitmq/amqp091-go v1.3.4 h1:tXuIslN1nhDqs2t6Jrz3BAoqvt4qIZzxvdbdcxWtHYU=
2 | github.com/rabbitmq/amqp091-go v1.3.4/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
3 |
--------------------------------------------------------------------------------
/drives/rabbitmq/rabbitmq.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | amqp "github.com/rabbitmq/amqp091-go"
8 | "math/rand"
9 | "sync"
10 | "time"
11 | )
12 |
13 | var (
14 | AMQPNil = errors.New("rabbitMq get nil")
15 | ErrNoIdleChannel = errors.New("rabbitMq no idle channel")
16 | GetChanTimeout = 60 * time.Second
17 | GetChanSleepTime = 100 * time.Millisecond
18 | )
19 |
20 | type Client struct {
21 | uri string
22 | declareQueue map[string]struct{}
23 | lock sync.Mutex
24 | Conn *amqp.Connection
25 | IdleChan map[string]*amqp.Channel
26 | NumOpen int
27 | MaxChannel int
28 | }
29 |
30 | func NewRabbitMqClient(host, port, user, password, vhost string, maxChannel int) *Client {
31 |
32 | c := Client{
33 | uri: fmt.Sprintf("amqp://%s:%s@%s:%s/%s", user, password, host, port, vhost),
34 | declareQueue: make(map[string]struct{}),
35 | IdleChan: make(map[string]*amqp.Channel),
36 | MaxChannel: maxChannel,
37 | }
38 | var err error
39 | c.Conn, err = amqp.Dial(c.uri)
40 | if err != nil {
41 | panic("YTask: connect rabbitMq error : " + err.Error())
42 | }
43 | return &c
44 |
45 | }
46 |
47 | func (c *Client) GetChannel() (*amqp.Channel, error) {
48 |
49 | timeout := time.Now().Add(GetChanTimeout)
50 | for {
51 | channel, err := c.getChannel()
52 | if err == nil {
53 | return channel, nil
54 | } else if err != ErrNoIdleChannel {
55 | return nil, err
56 | }
57 | if time.Now().After(timeout) {
58 | return nil, err
59 | }
60 | time.Sleep(GetChanSleepTime)
61 | }
62 |
63 | }
64 |
65 | func (c *Client) getChannel() (*amqp.Channel, error) {
66 | c.lock.Lock()
67 | defer c.lock.Unlock()
68 | for id, channel := range c.IdleChan {
69 | isBad := channel.IsClosed()
70 | delete(c.IdleChan, id)
71 | if isBad {
72 | c.closeChan(id, channel)
73 | } else {
74 | return channel, nil
75 | }
76 | }
77 | if c.NumOpen == c.MaxChannel {
78 | return nil, ErrNoIdleChannel
79 | }
80 | // 创建新channel
81 | c.NumOpen++
82 | if c.Conn.IsClosed() {
83 | var err error
84 | c.Conn, err = amqp.Dial(c.uri)
85 | if err != nil {
86 | return nil, err
87 | }
88 | }
89 | channel, err := c.Conn.Channel()
90 | return channel, err
91 | }
92 |
93 | func (c *Client) closeChan(id string, channel *amqp.Channel) {
94 | if id != "" {
95 | delete(c.IdleChan, id)
96 | }
97 | c.NumOpen--
98 | channel.Close()
99 | }
100 |
101 | func (c *Client) PutChannel(channel *amqp.Channel, isBad bool) {
102 | c.lock.Lock()
103 | defer c.lock.Unlock()
104 | if isBad {
105 | c.closeChan("", channel)
106 | return
107 | }
108 | // 这个id其实没啥用,只是为了配合map使用,所以每次随机生成
109 | id := fmt.Sprintf("%v-%v", rand.Intn(10), time.Now().UnixMilli())
110 | c.IdleChan[id] = channel
111 |
112 | }
113 |
114 | // 创建队列,如果队列已经存在,则忽略
115 | func (c *Client) QueueDeclare(queueName string, channel *amqp.Channel) error {
116 |
117 | _, ok := c.declareQueue[queueName]
118 | if ok {
119 | return nil
120 | }
121 | if c.Conn.IsClosed() {
122 | var err error
123 | c.Conn, err = amqp.Dial(c.uri)
124 | if err != nil {
125 | return err
126 | }
127 | }
128 |
129 | _, err := channel.QueueDeclare(queueName, true, false, false, false, amqp.Table{"x-max-priority": 9})
130 | return err
131 | }
132 |
133 | func (c *Client) Get(queueName string) ([]byte, error) {
134 | channel, err := c.GetChannel()
135 | // 因为获取channel时会判断一次isBad,因此放回时不再管isBad
136 | defer c.PutChannel(channel, false)
137 | if err != nil {
138 | return nil, err
139 | }
140 |
141 | if err = c.QueueDeclare(queueName, channel); err != nil {
142 | return nil, err
143 | }
144 | ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
145 | for {
146 | msg, ok, err := channel.Get(queueName, true)
147 | if err != nil {
148 | return nil, err
149 | }
150 | if ok {
151 | return msg.Body, nil
152 | }
153 |
154 | select {
155 | case <-ctx.Done():
156 | return nil, AMQPNil
157 | case <-time.After(time.Millisecond * 300):
158 | }
159 | }
160 |
161 | }
162 |
163 | func (c *Client) Publish(queueName string, value []byte, Priority uint8) error {
164 | channel, err := c.GetChannel()
165 | defer c.PutChannel(channel, false)
166 | if err != nil {
167 | return err
168 | }
169 | if err = c.QueueDeclare(queueName, channel); err != nil {
170 | return err
171 | }
172 | err = channel.Publish("", queueName, false, false, amqp.Publishing{
173 | ContentType: "text/plain",
174 | Body: value,
175 | Priority: Priority,
176 | })
177 | return err
178 | }
179 |
--------------------------------------------------------------------------------
/drives/rabbitmq/test/README.md:
--------------------------------------------------------------------------------
1 |
2 | ```shell
3 | docker run --name ytask-rabbit -d -p 5672:5672 rabbitmq
4 | cd drives/rabbitmq
5 | # rabbitmq启动有点慢,这里要等一下
6 | go test -v test/*.go
7 |
8 | docker stop ytask-rabbit
9 | docker rm ytask-rabbit
10 |
11 | ```
--------------------------------------------------------------------------------
/drives/rabbitmq/test/rabbitmqBroker_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/rabbitmq/v3"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "testing"
9 | )
10 |
11 | func TestRabbitmqBroker(t *testing.T) {
12 | broker := rabbitmq.NewRabbitMqBroker("127.0.0.1", "5672", "guest", "guest", "", 2)
13 | broker.Activate()
14 | msg := message.NewMessage(message.NewMsgArgs())
15 | msg2 := message.NewMessage(message.NewMsgArgs())
16 |
17 | _, err := broker.Next("test_amqp")
18 | if !yerrors.IsEqual(err, yerrors.ErrTypeEmptyQueue) {
19 | t.Fatal(err)
20 | }
21 |
22 | err = broker.Send("test_amqp", msg)
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | err = broker.Send("test_amqp", msg2)
27 | if err != nil {
28 | t.Fatal(err)
29 | }
30 |
31 | m, err := broker.Next("test_amqp")
32 | if err != nil {
33 | t.Fatal(err)
34 | }
35 | if fmt.Sprintf("%v", m) != fmt.Sprintf("%v", msg) {
36 | t.Fatalf("%v != %v", m, msg)
37 | }
38 |
39 | m2, err := broker.Next("test_amqp")
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | if fmt.Sprintf("%v", m2) != fmt.Sprintf("%v", msg2) {
44 | t.Fatalf("%v != %v", m2, msg2)
45 |
46 | }
47 | }
48 |
49 | func TestRabbitmqBrokerLSend(t *testing.T) {
50 | broker := rabbitmq.NewRabbitMqBroker("127.0.0.1", "5672", "guest", "guest", "", 2)
51 | broker.Activate()
52 | msg := message.NewMessage(message.NewMsgArgs())
53 | msg.Id = "1"
54 | msg2 := message.NewMessage(message.NewMsgArgs())
55 | msg2.Id = "2"
56 | err := broker.Send("test_amqp", msg)
57 | if err != nil {
58 | t.Fatal(err)
59 | }
60 | err = broker.LSend("test_amqp", msg2)
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 |
65 | m, err := broker.Next("test_amqp")
66 | if err != nil {
67 | t.Fatal(err)
68 | }
69 | if m.Id != msg2.Id {
70 | t.Fatalf("%v != %v", m, msg2)
71 | }
72 |
73 | m2, err := broker.Next("test_amqp")
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 | if m2.Id != msg.Id {
78 | t.Fatalf("%v != %v", m2, msg)
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/drives/rabbitmq/test/rabbitmqPool_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/rabbitmq/v3"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 | "testing"
10 | "time"
11 | )
12 |
13 | func putMsg() {
14 |
15 | }
16 | func TestRabbitmqPool(t *testing.T) {
17 | c := rabbitmq.NewRabbitMqClient("127.0.0.1", "5672", "guest", "guest", "", 2)
18 | channel0, _ := c.GetChannel()
19 | c.QueueDeclare("test_amqp", channel0)
20 | c.PutChannel(channel0, false)
21 |
22 | channel1, _ := c.GetChannel()
23 |
24 | // channel 0 与 1 应该是同一个
25 | if channel1 != channel0 {
26 | t.Fatal("channel1 != channel")
27 | }
28 | channel2, err := c.GetChannel()
29 | if err != nil {
30 | t.Fatal(err)
31 | }
32 |
33 | // channel 2 与 1 不是同一个
34 | if channel1 == channel2 {
35 | t.Fatal("channel1 == channel")
36 | }
37 | // 临时修改超时时间
38 | rabbitmq.GetChanTimeout = 5 * time.Second
39 | defer func() {
40 | rabbitmq.GetChanTimeout = 60 * time.Second
41 | }()
42 | // 这里应该是超时
43 | _, err = c.GetChannel()
44 | if err != rabbitmq.ErrNoIdleChannel {
45 | t.Fatal(err)
46 | }
47 | }
48 |
49 | func TestRabbitmqPoolBadChan(t *testing.T) {
50 | /*
51 | 测试Rabbitmq重启后能否创建链接
52 | */
53 | c := rabbitmq.NewRabbitMqClient("127.0.0.1", "5672", "guest", "guest", "", 2)
54 | channel0, _ := c.GetChannel()
55 |
56 | // test中无法通过Scanf()获取输入, 因此通过信号传递信息
57 | fmt.Printf("重启Rabbitmq后通过 \" kill -CONT %d \" 继续运行测试\n", os.Getpid())
58 | quit := make(chan os.Signal, 1)
59 | signal.Notify(quit, syscall.SIGCONT)
60 | <-quit
61 |
62 | c.PutChannel(channel0, false)
63 |
64 | channel1, _ := c.GetChannel()
65 |
66 | // 因为链接断了,所以channel0和channel1应该是不同的
67 | if channel0 == channel1 {
68 | t.Fatal("channel0 == channel1")
69 | }
70 | // 这里应该是成功的
71 | err := c.QueueDeclare("test_amqp22", channel1)
72 | if err != nil {
73 | t.Fatal(err)
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/drives/redis/README.md:
--------------------------------------------------------------------------------
1 | # Redis
2 |
3 | ## Installation
4 |
5 | ```shell
6 | go get -u github.com/gojuukaze/YTask/drives/redis/v3
7 | ```
8 |
9 | ## Broker
10 |
11 | ```go
12 | package main
13 |
14 | import (
15 | "github.com/gojuukaze/YTask/drives/redis/v3"
16 | )
17 |
18 | func main() {
19 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 3)
20 | // ...
21 | }
22 | ```
23 |
24 |
25 | ## Backend
26 |
27 | ```go
28 | package main
29 |
30 | import (
31 | "github.com/gojuukaze/YTask/drives/redis/v3"
32 | )
33 |
34 | func main() {
35 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 10)
36 | // ...
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/drives/redis/bakcend.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "github.com/go-redis/redis/v8"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/util/yjson"
8 | "github.com/gojuukaze/YTask/v3/yerrors"
9 | "time"
10 | )
11 |
12 | type Backend struct {
13 | client *Client
14 | host string
15 | port string
16 | password string
17 | db int
18 | poolSize int
19 | }
20 |
21 | // NewRedisBackend
22 | // - poolSize: Maximum number of idle connections in the pool. If poolSize<=0 use default value.
23 | // default value is min(10, numWorkers) at SERVER
24 | // default value is 10 at CLIENT
25 | //
26 | func NewRedisBackend(host string, port string, password string, db int, poolSize int) Backend {
27 | return Backend{
28 | host: host,
29 | port: port,
30 | password: password,
31 | db: db,
32 | poolSize: poolSize,
33 | }
34 | }
35 |
36 | func (r *Backend) Activate() {
37 | client := NewRedisClient(r.host, r.port, r.password, r.db, r.poolSize)
38 | r.client = &client
39 | }
40 |
41 | func (r *Backend) SetPoolSize(n int) {
42 | r.poolSize = n
43 | }
44 | func (r *Backend) GetPoolSize() int {
45 | return r.poolSize
46 | }
47 | func (r *Backend) SetResult(result message.Result, exTime int) error {
48 | b, err := yjson.YJson.Marshal(result)
49 |
50 | if err != nil {
51 | return err
52 | }
53 | err = r.client.Set(result.GetBackendKey(), b, time.Duration(exTime)*time.Second)
54 | return err
55 | }
56 | func (r *Backend) GetResult(key string) (message.Result, error) {
57 | var result message.Result
58 |
59 | b, err := r.client.Get(key).Bytes()
60 | if err != nil {
61 | if err == redis.Nil {
62 | return result, yerrors.ErrNilResult{}
63 | }
64 | return result, err
65 | }
66 |
67 | err = yjson.YJson.Unmarshal(b, &result)
68 | return result, err
69 | }
70 |
71 | func (r Backend) Clone() backends.BackendInterface {
72 | return &Backend{
73 | host: r.host,
74 | port: r.port,
75 | password: r.password,
76 | db: r.db,
77 | poolSize: r.poolSize,
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/drives/redis/broker.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "github.com/go-redis/redis/v8"
5 | "github.com/gojuukaze/YTask/v3/brokers"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/util/yjson"
8 | "github.com/gojuukaze/YTask/v3/yerrors"
9 | "time"
10 | )
11 |
12 | type Broker struct {
13 | client *Client
14 | host string
15 | port string
16 | password string
17 | db int
18 | poolSize int
19 | }
20 |
21 | // NewRedisBroker
22 | // - poolSize: Maximum number of idle connections in client pool.
23 | // If clientPoolSize<=0, clientPoolSize=10
24 | func NewRedisBroker(host string, port string, password string, db int, poolSize int) Broker {
25 | return Broker{
26 | host: host,
27 | port: port,
28 | password: password,
29 | db: db,
30 | poolSize: poolSize,
31 | }
32 | }
33 |
34 | func (r *Broker) Activate() {
35 | client := NewRedisClient(r.host, r.port, r.password, r.db, r.poolSize)
36 | r.client = &client
37 | }
38 |
39 | func (r *Broker) SetPoolSize(n int) {
40 | r.poolSize = n
41 | }
42 | func (r *Broker) GetPoolSize() int {
43 | return r.poolSize
44 | }
45 |
46 | func (r *Broker) Next(queueName string) (message.Message, error) {
47 | var msg message.Message
48 | values, err := r.client.BLPop(queueName, 2*time.Second).Result()
49 | if err != nil {
50 | if err == redis.Nil {
51 | return msg, yerrors.ErrEmptyQueue{}
52 | }
53 | return msg, err
54 | }
55 |
56 | err = yjson.YJson.UnmarshalFromString(values[1], &msg)
57 | return msg, err
58 | }
59 |
60 | func (r *Broker) Send(queueName string, msg message.Message) error {
61 | b, err := yjson.YJson.Marshal(msg)
62 |
63 | if err != nil {
64 | return err
65 | }
66 | err = r.client.RPush(queueName, b)
67 | return err
68 | }
69 |
70 | func (r *Broker) LSend(queueName string, msg message.Message) error {
71 | b, err := yjson.YJson.Marshal(msg)
72 |
73 | if err != nil {
74 | return err
75 | }
76 | err = r.client.LPush(queueName, b)
77 | return err
78 | }
79 |
80 | func (r Broker) Clone() brokers.BrokerInterface {
81 |
82 | return &Broker{
83 | host: r.host,
84 | port: r.port,
85 | password: r.password,
86 | db: r.db,
87 | poolSize: r.poolSize,
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/drives/redis/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/drives/redis/v3
2 |
3 | go 1.18
4 |
5 |
6 |
7 | require (
8 | github.com/go-redis/redis/v8 v8.11.5
9 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
10 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
11 | )
12 |
--------------------------------------------------------------------------------
/drives/redis/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
4 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
5 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
6 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
7 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
8 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
9 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
10 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
11 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
12 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
13 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
14 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
15 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
16 |
--------------------------------------------------------------------------------
/drives/redis/redis.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "time"
7 | )
8 |
9 | type Client struct {
10 | redisPool *redis.Client
11 | }
12 |
13 | func NewRedisClient(host string, port string, password string, db int, poolSize int) Client {
14 | client := Client{
15 | redisPool: redis.NewClient(&redis.Options{
16 | Addr: host + ":" + port,
17 | Password: password,
18 | DB: db,
19 | PoolSize: poolSize,
20 | PoolTimeout: 60 * time.Second,
21 | }),
22 | }
23 | err := client.Ping()
24 | if err != nil {
25 | panic("YTask: connect redisBroker error : " + err.Error())
26 | }
27 | return client
28 |
29 | }
30 |
31 | // =======================
32 | // high api
33 | // =======================
34 | func (c *Client) Exists(key string) (bool, error) {
35 | r, err := c.redisPool.Exists(context.Background(), key).Result()
36 | return r == 1, err
37 | }
38 | func (c *Client) Get(key string) *redis.StringCmd {
39 |
40 | return c.redisPool.Get(context.Background(), key)
41 | }
42 |
43 | func (c *Client) Set(key string, value interface{}, exTime time.Duration) error {
44 |
45 | if exTime <= 0 {
46 | exTime = 0
47 | }
48 | return c.redisPool.Set(context.Background(), key, value, exTime).Err()
49 |
50 | }
51 |
52 | func (c *Client) RPush(key string, value interface{}) error {
53 | return c.redisPool.RPush(context.Background(), key, value).Err()
54 | }
55 |
56 | func (c *Client) LPush(key string, value interface{}) error {
57 | return c.redisPool.LPush(context.Background(), key, value).Err()
58 | }
59 |
60 | func (c *Client) BLPop(key string, timeout time.Duration) *redis.StringSliceCmd {
61 |
62 | return c.redisPool.BLPop(context.Background(), timeout, key)
63 | }
64 |
65 | func (c *Client) Do(args ...interface{}) *redis.Cmd {
66 | var ctx = context.Background()
67 |
68 | return c.redisPool.Do(ctx, args)
69 | }
70 |
71 | func (c *Client) Flush() error {
72 |
73 | return c.redisPool.FlushDB(context.Background()).Err()
74 | }
75 |
76 | func (c *Client) Ping() error {
77 |
78 | return c.redisPool.Ping(context.Background()).Err()
79 | }
80 |
81 | func (c *Client) Close() {
82 | c.redisPool.Close()
83 | }
84 |
--------------------------------------------------------------------------------
/drives/redis/test/README.md:
--------------------------------------------------------------------------------
1 |
2 | ```shell
3 | docker run --name ytask-redis -d -p 6379:6379 redis
4 | cd drives/redis
5 | go test -v test/*.go
6 |
7 | docker stop ytask-redis
8 | docker rm ytask-redis
9 |
10 | ```
--------------------------------------------------------------------------------
/drives/redis/test/redisBackend_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/redis/v3"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestRedisBackend(t *testing.T) {
13 | b := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 1)
14 | result := message.NewResult("xx123")
15 | result.FuncReturn = nil
16 | b.Activate()
17 | err := b.SetResult(result, 2)
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | r2, err := b.GetResult(result.GetBackendKey())
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
27 | t.Fatalf("%v != %v", r2, result)
28 | }
29 |
30 | time.Sleep(2 * time.Second)
31 |
32 | _, err = b.GetResult(result.GetBackendKey())
33 | if !yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
34 | t.Fatal("err != ErrNilResult")
35 |
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/drives/redis/test/redisBroker_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/redis/v3"
6 | "github.com/gojuukaze/YTask/v3/brokers"
7 | "github.com/gojuukaze/YTask/v3/message"
8 | "os"
9 | "os/signal"
10 | "syscall"
11 | "testing"
12 | )
13 |
14 | func TestRedisBroker(t *testing.T) {
15 | b := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 1)
16 | var broker brokers.BrokerInterface = &b
17 | broker.Activate()
18 | msg := message.NewMessage(message.NewMsgArgs())
19 | msg2 := message.NewMessage(message.NewMsgArgs())
20 |
21 | err := broker.Send("test_redis", msg)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 | err = broker.Send("test_redis", msg2)
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 |
30 | m, err := broker.Next("test_redis")
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 | if fmt.Sprintf("%v", m) != fmt.Sprintf("%v", msg) {
35 | t.Fatalf("%v != %v", m, msg)
36 | }
37 |
38 | m2, err := broker.Next("test_redis")
39 | if err != nil {
40 | t.Fatal(err)
41 | }
42 | if fmt.Sprintf("%v", m2) != fmt.Sprintf("%v", msg2) {
43 | t.Fatalf("%v != %v", m2, msg2)
44 |
45 | }
46 | }
47 |
48 | func TestRedisBrokerLSend(t *testing.T) {
49 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 1)
50 | broker.Activate()
51 | msg := message.NewMessage(message.NewMsgArgs())
52 | msg.Id = "1"
53 | msg2 := message.NewMessage(message.NewMsgArgs())
54 | msg2.Id = "2"
55 | err := broker.Send("test_redis", msg)
56 | if err != nil {
57 | t.Fatal(err)
58 | }
59 | err = broker.LSend("test_redis", msg2)
60 | if err != nil {
61 | t.Fatal(err)
62 | }
63 |
64 | fmt.Printf("重启Redis后通过 \" kill -CONT %d \" 继续运行测试\n", os.Getpid())
65 | quit := make(chan os.Signal, 1)
66 | signal.Notify(quit, syscall.SIGCONT)
67 | <-quit
68 |
69 | m, err := broker.Next("test_redis")
70 | if err != nil {
71 | t.Fatal(err)
72 | }
73 | if m.Id != msg2.Id {
74 | t.Fatalf("%v != %v", m, msg2)
75 | }
76 |
77 | m2, err := broker.Next("test_redis")
78 | if err != nil {
79 | t.Fatal(err)
80 | }
81 | if m2.Id != msg.Id {
82 | t.Fatalf("%v != %v", m2, msg)
83 |
84 | }
85 | }
86 |
87 | func TestRedisBroker2(t *testing.T) {
88 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 1)
89 | broker.Activate()
90 | msg := message.NewMessage(message.NewMsgArgs())
91 | msg.Id = "1"
92 | msg2 := message.NewMessage(message.NewMsgArgs())
93 | msg2.Id = "2"
94 | err := broker.Send("test_redis", msg)
95 | if err != nil {
96 | t.Fatal(err)
97 | }
98 | err = broker.LSend("test_redis", msg2)
99 | if err != nil {
100 | t.Fatal(err)
101 | }
102 |
103 | m, err := broker.Next("test_redis")
104 | if err != nil {
105 | t.Fatal(err)
106 | }
107 | if m.Id != msg2.Id {
108 | t.Fatalf("%v != %v", m, msg2)
109 | }
110 |
111 | m2, err := broker.Next("test_redis")
112 | if err != nil {
113 | t.Fatal(err)
114 | }
115 | if m2.Id != msg.Id {
116 | t.Fatalf("%v != %v", m2, msg)
117 |
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/drives/rocketmq/README.md:
--------------------------------------------------------------------------------
1 | # 不再支持!!
2 |
3 | `rocketmq-client` 的设计与 `ytask` 的设计是不兼容的。 具体是 `broker.Next()` 的设计。
4 | v2版的这部分代码其实是有问题的,因此v3不再支持。
5 |
6 | ## ytask
7 |
8 | `ytask` 的 `broker.Next()` 设计为:每次获取一条消息并返回,这样可以让 `ytask` 主动控制获取消息的频率(即有woker空闲时才获取消息)。
9 |
10 | 当然这牺牲了处理速度,且如果没有连接池或者保持链接,每次调用 `broker.Next()` 都会建立一个连接。 但这样做能让整个逻辑变得简单。另外这点开销也是能接受的。
11 |
12 | ## rocketmq-client
13 |
14 | `rocketmq-client` 是典型的 "生产-消费" 模型,通过 `Consumer.Subscribe()` 订阅消息,有新消息就立即消费。
15 |
16 | 这导致无法实现 `ytask` 的 `broker.Next()` 。
17 |
18 | 如果你读过v2版的代码,会发现v2其实用了chan。启动时先创建一个无缓冲的chan,消费者获取消息后提交到chan,每次调用 `broker.Next()` 时从chan中获取一条消息并返回。
19 |
20 | 这样做会导致服务关闭时要做而外的清理工作。且为了防止多个消费者同时有消息等待提交到chan,`ReadQueueNums`被设置为了1,这导致只能部署一个服务。
21 |
22 | ---
23 |
24 | 综上所述,不再支持`rocketmq`。
25 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | ```bash
4 | cd example/server
5 | go run main.go
6 |
7 | cd example/send
8 | go run main.go
9 | ```
10 |
--------------------------------------------------------------------------------
/example/send/go.mod:
--------------------------------------------------------------------------------
1 | module send
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f
7 | github.com/gojuukaze/YTask/v3 v3.0.1
8 | )
9 |
10 | require (
11 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
13 | github.com/go-redis/redis/v8 v8.11.5 // indirect
14 | github.com/google/uuid v1.3.0 // indirect
15 | github.com/json-iterator/go v1.1.12 // indirect
16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
17 | github.com/modern-go/reflect2 v1.0.2 // indirect
18 | github.com/sirupsen/logrus v1.9.0 // indirect
19 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
20 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/example/send/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
7 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
8 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
9 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
10 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
11 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f h1:Hv2Ehi+c5fxNWO3oi8233J4xQ0Yp9IZpYDUjttMEAC4=
12 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f/go.mod h1:5yhrp6Zeg1nZv/FQ8d3VpffjY3NaaZ7MNmlzGS43qLY=
13 | github.com/gojuukaze/YTask/v3 v3.0.1 h1:+UvAQN+5Wy9ag1eXBjzml/Hb5uoiksIKMZnrjCN+Uzw=
14 | github.com/gojuukaze/YTask/v3 v3.0.1/go.mod h1:FXA8STSHAk8sEm2ppUSMEZTaIJyeijUtAjKElKqz/gY=
15 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
16 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
17 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
18 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
19 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
21 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
22 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
23 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
24 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
25 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
26 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
29 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
30 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
33 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
34 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
35 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
36 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
37 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
39 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
44 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 |
--------------------------------------------------------------------------------
/example/send/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/drives/redis/v3"
6 | "github.com/gojuukaze/YTask/v3"
7 | "github.com/gojuukaze/YTask/v3/server"
8 | "time"
9 | )
10 |
11 | type User struct {
12 | Id int
13 | Name string
14 | }
15 |
16 | var client server.Client
17 |
18 | func main() {
19 | // poolSize: Maximum number of idle connections in client pool.
20 | // If clientPoolSize<=0, clientPoolSize=10
21 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 5)
22 | // poolSize: Maximum number of idle connections in the pool. If poolSize<=0 use default value
23 | // default value is 10 at client
24 | // ---------------
25 | // 对于client端,如果poolSize<=0,poolSize会设为10
26 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 5)
27 |
28 | ser := ytask.Server.NewServer(
29 | ytask.Config.Broker(&broker),
30 | ytask.Config.Backend(&backend),
31 | ytask.Config.Debug(true),
32 | ytask.Config.StatusExpires(60*5),
33 | ytask.Config.ResultExpires(60*5),
34 | )
35 |
36 | client = ser.GetClient()
37 |
38 | fmt.Println("Send and get result\n---")
39 | sendAndGet()
40 | fmt.Println("\nRetry\n---")
41 | retry()
42 | fmt.Println("\nDelay\n---")
43 | delay()
44 |
45 | fmt.Println("\nWorkflow\n---")
46 | workflow()
47 |
48 | }
49 | func taskAdd() {
50 | taskId, err := client.Send("group1", "add", 123, 44)
51 | _ = err
52 | result, err := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
53 | _ = err
54 |
55 | if result.IsSuccess() {
56 | sum, err := result.GetInt64(0)
57 | if err != nil {
58 | fmt.Println(err)
59 | }
60 | fmt.Println("add(123,44) =", int(sum))
61 | } else {
62 | fmt.Println("result failure")
63 | }
64 | }
65 |
66 | func taskAddSub() {
67 | taskId, _ := client.Send("group2", "add_sub", 123, 44)
68 | result, _ := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
69 |
70 | if result.IsSuccess() {
71 | sum, _ := result.GetInt64(0)
72 | sub, _ := result.GetInt64(1)
73 |
74 | // or
75 | var sum2, sub2 int
76 | result.Get(0, &sum2)
77 | result.Get(1, &sub2)
78 |
79 | // or
80 | var sum3, sub3 int
81 | result.Gets(&sum3, &sub3)
82 |
83 | fmt.Println("add_sub(123,44) =", sum, sub)
84 | }
85 | }
86 |
87 | func taskAppendUser() {
88 | taskId, err := client.Send("group1", "add_user", User{1, "aa"}, []int{322, 11}, []string{"bb", "cc"})
89 | _ = err
90 | result, err := client.GetResult(taskId, 2*time.Second, 300*time.Millisecond)
91 | _ = err
92 |
93 | if result.IsSuccess() {
94 | var users []User
95 | err := result.Get(0, &users)
96 | if err != nil {
97 | fmt.Println(err)
98 | }
99 | fmt.Println(`add_user({1,"aa"}, [322,11], ["bb","cc"]) =`, users)
100 | }
101 | }
102 | func sendAndGet() {
103 | taskAdd()
104 | taskAddSub()
105 | taskAppendUser()
106 | }
107 |
108 | func retry() {
109 |
110 | // set retry count, default is 3
111 | tId, _ := client.SetTaskCtl(client.RetryCount, 5).Send("group1", "retry", 123, 44)
112 | result, _ := client.GetResult(tId, 3*time.Second, 300*time.Millisecond)
113 | fmt.Println("retry times =", result.RetryCount)
114 |
115 | // do not retry
116 | tId, _ = client.SetTaskCtl(client.RetryCount, 0).Send("group1", "retry", 123, 44)
117 | result, _ = client.GetResult(tId, 3*time.Second, 300*time.Millisecond)
118 | fmt.Println("retry times =", result.RetryCount)
119 |
120 | }
121 |
122 | func delay() {
123 | // RunAfter
124 | tId, _ := client.SetTaskCtl(client.RunAfter, 1*time.Second).Send("group2", "add_sub", 123, 44)
125 | result, _ := client.GetResult(tId, 3*time.Second, 300*time.Millisecond)
126 | var sum, sub int
127 | result.Gets(&sum, &sub)
128 | fmt.Println("add_sub(123,44) =", sum, sub)
129 |
130 | // RunAt
131 | runTime := time.Now().Add(1 * time.Second)
132 | tId, _ = client.SetTaskCtl(client.RunAt, runTime).Send("group2", "add_sub", 123, 44)
133 | result, _ = client.GetResult(tId, 3*time.Second, 300*time.Millisecond)
134 | result.Gets(&sum, &sub)
135 | fmt.Println("add_sub(123,44) =", sum, sub)
136 | }
137 |
138 | func workflow() {
139 |
140 | tId, _ := client.Workflow().
141 | Send("group2", "add_sub", 123, 44).
142 | Send("group1", "add").
143 | Done()
144 | result, _ := client.GetResult(tId, 3*time.Second, 300*time.Millisecond)
145 | a, _ := result.GetInt64(0)
146 | fmt.Println("(123+44)+(123-44)=", a)
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/example/server/go.mod:
--------------------------------------------------------------------------------
1 | module server
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f
7 | github.com/gojuukaze/YTask/v3 v3.0.1
8 | )
9 |
10 | require (
11 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
13 | github.com/go-redis/redis/v8 v8.11.5 // indirect
14 | github.com/google/uuid v1.3.0 // indirect
15 | github.com/json-iterator/go v1.1.12 // indirect
16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
17 | github.com/modern-go/reflect2 v1.0.2 // indirect
18 | github.com/sirupsen/logrus v1.9.0 // indirect
19 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
20 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/example/server/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
7 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
8 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
9 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
10 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
11 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f h1:Hv2Ehi+c5fxNWO3oi8233J4xQ0Yp9IZpYDUjttMEAC4=
12 | github.com/gojuukaze/YTask/drives/redis/v3 v3.0.0-20220829075720-6ea13eaf3d2f/go.mod h1:5yhrp6Zeg1nZv/FQ8d3VpffjY3NaaZ7MNmlzGS43qLY=
13 | github.com/gojuukaze/YTask/v3 v3.0.1 h1:+UvAQN+5Wy9ag1eXBjzml/Hb5uoiksIKMZnrjCN+Uzw=
14 | github.com/gojuukaze/YTask/v3 v3.0.1/go.mod h1:FXA8STSHAk8sEm2ppUSMEZTaIJyeijUtAjKElKqz/gY=
15 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
16 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
17 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
18 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
19 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
21 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
22 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
23 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
24 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
25 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
26 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
29 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
30 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
33 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
34 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
35 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
36 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
37 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
39 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
43 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
44 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 |
--------------------------------------------------------------------------------
/example/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/drives/redis/v3"
6 | "github.com/gojuukaze/YTask/v3"
7 | "os"
8 | "os/signal"
9 | "server/workers"
10 | "syscall"
11 | )
12 |
13 | func main() {
14 | //
15 | // PoolSize : server端, 如果brokerPoolSize<=0时默认为3;
16 | // 如果需要频繁使用工作流,则可适当调大此项,最大不要超过 并发任务数+1
17 | broker := redis.NewRedisBroker("127.0.0.1", "6379", "", 0, 0)
18 | // poolSize: Maximum number of idle connections in the pool. If poolSize<=0 use default value
19 | // default value is min(10, numWorkers) at server
20 | // -------------
21 | // 如果poolSize<=0 会使用默认值,对于server端backendPoolSize的默认值是 min(10, numWorkers)
22 | backend := redis.NewRedisBackend("127.0.0.1", "6379", "", 0, 0)
23 |
24 | ser := ytask.Server.NewServer(
25 | ytask.Config.Broker(&broker),
26 | ytask.Config.Backend(&backend),
27 | ytask.Config.Debug(true),
28 | ytask.Config.StatusExpires(60*5),
29 | ytask.Config.ResultExpires(60*5),
30 | )
31 |
32 | ser.Add("group1", "add", workers.Add)
33 | ser.Add("group1", "retry", workers.Retry)
34 | ser.Add("group1", "add_user", workers.AppendUser)
35 |
36 | ser.Add("group2", "add_sub", workers.AddSub)
37 |
38 | ser.Run("group1", 3)
39 | ser.Run("group2", 3, true)
40 |
41 | quit := make(chan os.Signal, 1)
42 |
43 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
44 | <-quit
45 | ser.Shutdown(context.Background())
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/example/server/workers/workers.go:
--------------------------------------------------------------------------------
1 | package workers
2 |
3 | import (
4 | "errors"
5 | "github.com/gojuukaze/YTask/v3/server"
6 | )
7 |
8 | type User struct {
9 | Id int
10 | Name string
11 | }
12 |
13 | func Add(a int, b int) int {
14 |
15 | return a + b
16 | }
17 |
18 | func AddSub(ctl *server.TaskCtl, a int, b int) (int, int) {
19 | // do not retry
20 | ctl.SetRetryCount(0)
21 |
22 | return a + b, a - b
23 | }
24 |
25 | func Retry(ctl *server.TaskCtl, a int, b int) (int, int) {
26 | if ctl.GetRetryCount()%2 == 0 {
27 | // use ctl.Retry
28 | ctl.Retry(errors.New("ctl.Retry"))
29 | } else {
30 | // or use panic
31 | panic("panic retry")
32 | }
33 |
34 | return a + b, a - b
35 | }
36 |
37 | func AppendUser(user User, ids []int, names []string) []User {
38 | var r = make([]User, 0)
39 | r = append(r, user)
40 | for i := range ids {
41 | r = append(r, User{
42 | Id: ids[i],
43 | Name: names[i],
44 | })
45 | }
46 | return r
47 | }
48 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask
2 |
3 | go 1.18
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojuukaze/YTask/1524b236b6029a3f5e000c45cb8249d057f26138/go.sum
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.18
2 |
3 | use (
4 | ./drives/rabbitmq
5 | ./drives/memcache
6 | ./drives/mongo2
7 | ./drives/mongo
8 | ./drives/redis
9 | ./v3
10 | ./example/server
11 | ./example/send
12 | )
13 |
--------------------------------------------------------------------------------
/go.work.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
2 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
6 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
8 |
--------------------------------------------------------------------------------
/v3/backends/backend.go:
--------------------------------------------------------------------------------
1 | package backends
2 |
3 | import "github.com/gojuukaze/YTask/v3/message"
4 |
5 | type BackendInterface interface {
6 | SetResult(result message.Result, exTime int) error
7 | GetResult(key string) (message.Result, error)
8 | // 如果使用连接池,调用Activate后才真正建立连接
9 | Activate()
10 | SetPoolSize(int)
11 | GetPoolSize() int
12 | Clone() BackendInterface
13 | }
14 |
--------------------------------------------------------------------------------
/v3/backends/local.go:
--------------------------------------------------------------------------------
1 | package backends
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/drive"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/util/yjson"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | )
9 |
10 | type LocalBackend struct {
11 | client drive.LocalDrive
12 | }
13 |
14 | func NewLocalBackend() LocalBackend {
15 | return LocalBackend{}
16 | }
17 |
18 | func (l *LocalBackend) Activate() {
19 | l.client = drive.NewLocalDrive(false)
20 | }
21 |
22 | func (l *LocalBackend) SetResult(result message.Result, exTime int) error {
23 | b, err := yjson.YJson.Marshal(result)
24 |
25 | if err != nil {
26 | return err
27 | }
28 | err = l.client.Set(result.GetBackendKey(), b, exTime)
29 | return err
30 | }
31 |
32 | func (l *LocalBackend) GetResult(key string) (message.Result, error) {
33 | var result message.Result
34 |
35 | b, err := l.client.Get(key)
36 | if err != nil {
37 | if err == drive.NilResultError {
38 | return result, yerrors.ErrNilResult{}
39 | }
40 | return result, err
41 | }
42 |
43 | err = yjson.YJson.Unmarshal(b, &result)
44 | return result, err
45 | }
46 |
47 | func (l *LocalBackend) SetPoolSize(i int) {
48 |
49 | }
50 |
51 | func (l LocalBackend) GetPoolSize() int {
52 | return 0
53 | }
54 |
55 | func (l *LocalBackend) Clone() BackendInterface {
56 | return &LocalBackend{}
57 | }
58 |
--------------------------------------------------------------------------------
/v3/brokers/broker.go:
--------------------------------------------------------------------------------
1 | package brokers
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/message"
5 | )
6 |
7 | type BrokerInterface interface {
8 | Next(queueName string) (message.Message, error)
9 | Send(queueName string, msg message.Message) error
10 | LSend(queueName string, msg message.Message) error
11 | // 如果使用连接池,调用Activate后才真正建立连接
12 | Activate()
13 | SetPoolSize(int)
14 | GetPoolSize() int
15 | Clone() BrokerInterface
16 | }
17 |
--------------------------------------------------------------------------------
/v3/brokers/local.go:
--------------------------------------------------------------------------------
1 | package brokers
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/drive"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/util/yjson"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | )
9 |
10 | // LocalBroker
11 | // !!!只用于本地测试!!!
12 | // !!! Only for local test !!!
13 | type LocalBroker struct {
14 | client drive.LocalDrive
15 | }
16 |
17 | func NewLocalBroker() LocalBroker {
18 | return LocalBroker{}
19 | }
20 | func (l *LocalBroker) Activate() {
21 | l.client = drive.NewLocalDrive(true)
22 | }
23 | func (l *LocalBroker) Next(queueName string) (message.Message, error) {
24 | var msg message.Message
25 | b, err := l.client.LPop(queueName)
26 | if err != nil {
27 | if err == drive.EmptyQueueError {
28 | return msg, yerrors.ErrEmptyQueue{}
29 | }
30 | return msg, err
31 | }
32 | err = yjson.YJson.Unmarshal(b, &msg)
33 | return msg, err
34 | }
35 |
36 | func (l *LocalBroker) Send(queueName string, msg message.Message) error {
37 | b, err := yjson.YJson.Marshal(msg)
38 |
39 | if err != nil {
40 | return err
41 | }
42 | err = l.client.RPush(queueName, b)
43 | return err
44 | }
45 |
46 | func (l *LocalBroker) LSend(queueName string, msg message.Message) error {
47 | b, err := yjson.YJson.Marshal(msg)
48 |
49 | if err != nil {
50 | return err
51 | }
52 | err = l.client.LPush(queueName, b)
53 | return err
54 | }
55 |
56 | func (l *LocalBroker) SetPoolSize(i int) {
57 |
58 | }
59 |
60 | func (l *LocalBroker) GetPoolSize() int {
61 | return 0
62 | }
63 |
64 | func (l *LocalBroker) Clone() BrokerInterface {
65 | return &LocalBroker{}
66 | }
67 |
--------------------------------------------------------------------------------
/v3/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/backends"
5 | "github.com/gojuukaze/YTask/v3/brokers"
6 | "github.com/gojuukaze/YTask/v3/log"
7 | )
8 |
9 | type Config struct {
10 | // require: true
11 | Broker brokers.BrokerInterface
12 |
13 | // require: false
14 | Backend backends.BackendInterface
15 |
16 | // require: false
17 | Logger log.LoggerInterface
18 |
19 | // require: false
20 | // default:false
21 | Debug bool
22 |
23 | // require: false
24 | // default: 1 day
25 | // task status expires in ex seconds, -1:forever
26 | StatusExpires int
27 |
28 | // require: false
29 | // default: 1 day
30 | // task result expires in ex seconds, -1:forever
31 | ResultExpires int
32 |
33 | EnableDelayServer bool
34 | DelayServerQueueSize int
35 | }
36 |
37 | func (c Config) Clone() Config {
38 | newC := Config{
39 | Broker: c.Broker.Clone(),
40 | Backend: nil,
41 | Logger: c.Logger.Clone(),
42 | Debug: c.Debug,
43 | StatusExpires: c.StatusExpires,
44 | ResultExpires: c.ResultExpires,
45 | EnableDelayServer: c.EnableDelayServer,
46 | DelayServerQueueSize: c.DelayServerQueueSize,
47 | }
48 | if c.Backend != nil {
49 | newC.Backend = c.Backend.Clone()
50 | }
51 | return newC
52 |
53 | }
54 |
55 | type Opt struct {
56 | }
57 | type SetConfigFunc func(*Config)
58 |
59 | func NewConfig(setConfigFunc ...SetConfigFunc) Config {
60 | var config = Config{
61 | StatusExpires: 60 * 60 * 24,
62 | ResultExpires: 60 * 60 * 24,
63 | DelayServerQueueSize: 20,
64 | Logger: log.NewYTaskLogger(log.YTaskLog),
65 | }
66 | for _, f := range setConfigFunc {
67 | f(&config)
68 | }
69 | return config
70 | }
71 | func Broker(b brokers.BrokerInterface) SetConfigFunc {
72 | return func(config *Config) {
73 | config.Broker = b
74 | }
75 | }
76 |
77 | func Backend(b backends.BackendInterface) SetConfigFunc {
78 | return func(config *Config) {
79 | config.Backend = b
80 | }
81 | }
82 |
83 | func Logger(l log.LoggerInterface) SetConfigFunc {
84 | return func(config *Config) {
85 | config.Logger = l
86 | }
87 | }
88 |
89 | func DelayServerQueueSize(size int) SetConfigFunc {
90 | return func(config *Config) {
91 | config.DelayServerQueueSize = size
92 | }
93 | }
94 |
95 | func EnableDelayServer(enable bool) SetConfigFunc {
96 | return func(config *Config) {
97 | config.EnableDelayServer = enable
98 | }
99 | }
100 |
101 | func Debug(debug bool) SetConfigFunc {
102 | return func(config *Config) {
103 | config.Debug = debug
104 | }
105 | }
106 |
107 | func StatusExpires(ex int) SetConfigFunc {
108 | return func(config *Config) {
109 | config.StatusExpires = ex
110 | }
111 | }
112 |
113 | func ResultExpires(ex int) SetConfigFunc {
114 | return func(config *Config) {
115 | config.ResultExpires = ex
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/v3/consts/consts.go:
--------------------------------------------------------------------------------
1 | package consts
2 |
3 | var UserV2Name = false
4 |
--------------------------------------------------------------------------------
/v3/drive/err.go:
--------------------------------------------------------------------------------
1 | package drive
2 |
3 | import "errors"
4 |
5 | var NilResultError = errors.New("nil result")
6 | var EmptyQueueError = errors.New("empty queue")
7 |
--------------------------------------------------------------------------------
/v3/drive/local.go:
--------------------------------------------------------------------------------
1 | package drive
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "os"
7 | "path/filepath"
8 | "time"
9 | )
10 |
11 | type brokerItem struct {
12 | Msg [][]byte `json:"msg"`
13 | }
14 | type brokerStruct map[string]brokerItem
15 | type backendItem struct {
16 | Data []byte `json:"data"`
17 | ExTime time.Time `json:"ex_time"`
18 | }
19 | type backendStruct map[string]backendItem
20 |
21 | type LocalDrive struct {
22 | brokerLock UnsafeFileLock
23 | backendLock UnsafeFileLock
24 | brokerPath string
25 | backendPath string
26 | isBroker bool
27 | }
28 |
29 | func NewLocalDrive(isBroker bool) LocalDrive {
30 | var d = LocalDrive{}
31 | if isBroker {
32 | d.isBroker = true
33 | d.brokerLock = NewFileLock("ytask_local_broker.lock")
34 | d.brokerPath = filepath.Join(os.TempDir(), "ytask_local_broker.json")
35 | } else {
36 | d.backendLock = NewFileLock("ytask_local_backend.lock")
37 | d.backendPath = filepath.Join(os.TempDir(), "ytask_local_backend.json")
38 | }
39 |
40 | d.Init()
41 | return d
42 | }
43 |
44 | func (d LocalDrive) Init() {
45 | if d.isBroker {
46 | d.brokerLock.Init()
47 | os.WriteFile(d.brokerPath, []byte("{}"), os.FileMode(0600))
48 |
49 | } else {
50 | d.backendLock.Init()
51 | os.WriteFile(d.backendPath, []byte("{}"), os.FileMode(0600))
52 | }
53 | }
54 | func (d LocalDrive) Close() {
55 | d.brokerLock.Unlock()
56 | d.backendLock.Unlock()
57 | os.Remove(d.brokerPath)
58 | os.Remove(d.backendPath)
59 | }
60 |
61 | func (d LocalDrive) getBrokerData() brokerStruct {
62 | var data brokerStruct
63 | b, _ := os.ReadFile(d.brokerPath)
64 | json.Unmarshal(b, &data)
65 | return data
66 | }
67 | func (d LocalDrive) setBrokerData(data brokerStruct) {
68 | b, _ := json.Marshal(data)
69 | os.WriteFile(d.brokerPath, b, os.FileMode(0600))
70 | }
71 |
72 | func (d LocalDrive) getBackendData() backendStruct {
73 | var data backendStruct
74 | b, _ := os.ReadFile(d.backendPath)
75 | json.Unmarshal(b, &data)
76 | return data
77 | }
78 |
79 | func (d LocalDrive) setBackendData(data backendStruct) {
80 | b, _ := json.Marshal(data)
81 | os.WriteFile(d.backendPath, b, os.FileMode(0600))
82 | }
83 |
84 | func (d LocalDrive) Set(key string, value []byte, exTime int) error {
85 | err := d.backendLock.Lock()
86 | if err != nil {
87 | return err
88 | }
89 | defer d.backendLock.Unlock()
90 | var t = time.Time{}
91 | if exTime > 0 {
92 | t = time.Now().Add(time.Duration(exTime) * time.Second)
93 | }
94 | data := d.getBackendData()
95 | data[key] = backendItem{value, t}
96 | d.setBackendData(data)
97 | return nil
98 | }
99 |
100 | func (d LocalDrive) Get(key string) ([]byte, error) {
101 | err := d.backendLock.Lock()
102 | if err != nil {
103 | return nil, err
104 | }
105 | defer d.backendLock.Unlock()
106 | data := d.getBackendData()
107 | r, ok := data[key]
108 | if !ok {
109 | return nil, NilResultError
110 | }
111 | if !r.ExTime.IsZero() && r.ExTime.Before(time.Now()) {
112 | return nil, NilResultError
113 | }
114 | return r.Data, nil
115 | }
116 |
117 | func (d LocalDrive) push(queueName string, value []byte, isRight bool) error {
118 | err := d.brokerLock.Lock()
119 | if err != nil {
120 | return err
121 | }
122 | defer d.brokerLock.Unlock()
123 | data := d.getBrokerData()
124 | item, _ := data[queueName]
125 | if isRight {
126 | item.Msg = rPush(item.Msg, value)
127 | } else {
128 | item.Msg = lPush(item.Msg, value)
129 | }
130 | data[queueName] = item
131 | d.setBrokerData(data)
132 | return nil
133 | }
134 |
135 | func (d LocalDrive) RPush(queueName string, value []byte) error {
136 | return d.push(queueName, value, true)
137 |
138 | }
139 |
140 | func (d LocalDrive) LPush(queueName string, value []byte) error {
141 | return d.push(queueName, value, false)
142 | }
143 | func (d LocalDrive) lPop(queueName string) ([]byte, error) {
144 | err := d.brokerLock.Lock()
145 | if err != nil {
146 | return nil, err
147 | }
148 | defer d.brokerLock.Unlock()
149 | data := d.getBrokerData()
150 | item, ok := data[queueName]
151 | if !ok {
152 | return nil, EmptyQueueError
153 | }
154 | b, msg := lPop(item.Msg)
155 | if b == nil {
156 | return nil, EmptyQueueError
157 | }
158 | item.Msg = msg
159 | data[queueName] = item
160 | d.setBrokerData(data)
161 | return b, nil
162 |
163 | }
164 |
165 | func (d LocalDrive) LPop(queueName string) ([]byte, error) {
166 | // 由于会有多个协程执行这个操作,这里超时时间短一点,尽快让出锁
167 | ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*300)
168 | for {
169 | b, err := d.lPop(queueName)
170 | if err == nil {
171 | return b, nil
172 | }
173 | select {
174 | case <-ctx.Done():
175 | return nil, EmptyQueueError
176 | case <-time.After(time.Millisecond * 100):
177 | }
178 | }
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/v3/drive/lock.go:
--------------------------------------------------------------------------------
1 | package drive
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "os"
7 | "path/filepath"
8 | "time"
9 | )
10 |
11 | type UnsafeFileLock struct {
12 | filePath string
13 | }
14 |
15 | func NewFileLock(s string) UnsafeFileLock {
16 | return UnsafeFileLock{filepath.Join(os.TempDir(), s)}
17 | }
18 | func (l UnsafeFileLock) Init() {
19 | os.Remove(l.filePath)
20 | }
21 | func (l UnsafeFileLock) Lock() error {
22 | ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*400)
23 | for {
24 | f, err := os.OpenFile(l.filePath, os.O_CREATE|os.O_EXCL, os.FileMode(0600))
25 | if err == nil {
26 | f.Close()
27 | return nil
28 | }
29 | select {
30 | case <-ctx.Done():
31 | return errors.New("lock timeout")
32 | case <-time.After(100 * time.Millisecond):
33 | }
34 | }
35 | }
36 |
37 | func (l UnsafeFileLock) Unlock() {
38 | os.Remove(l.filePath)
39 | }
40 |
--------------------------------------------------------------------------------
/v3/drive/slice.go:
--------------------------------------------------------------------------------
1 | package drive
2 |
3 | func rPush(l [][]byte, v []byte) [][]byte {
4 | return append(l, v)
5 | }
6 | func lPop(l [][]byte) ([]byte, [][]byte) {
7 | if len(l) == 0 {
8 | return nil, l
9 | }
10 | return l[0], l[1:]
11 | }
12 |
13 | func lPush(l [][]byte, v []byte) [][]byte {
14 | var n = make([][]byte, len(l)+1)
15 | copy(n[1:], l)
16 | n[0] = v
17 | return n
18 | }
19 |
--------------------------------------------------------------------------------
/v3/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gojuukaze/YTask/v3
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/google/uuid v1.3.0
7 | github.com/json-iterator/go v1.1.12
8 | github.com/sirupsen/logrus v1.9.0
9 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
10 | )
11 |
12 | require (
13 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
14 | github.com/modern-go/reflect2 v1.0.2 // indirect
15 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/v3/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
5 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
6 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
7 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
8 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
9 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
10 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
11 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
12 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
16 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
18 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
19 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
20 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
21 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
22 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
23 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
24 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
28 |
--------------------------------------------------------------------------------
/v3/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 | "github.com/sirupsen/logrus"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | var YTaskLog *logrus.Logger
11 |
12 | type YTaskHook struct {
13 | }
14 |
15 | func (hook YTaskHook) Levels() []logrus.Level {
16 | return logrus.AllLevels
17 | }
18 |
19 | func (hook YTaskHook) Fire(entry *logrus.Entry) error {
20 | serverName, ok := entry.Data["server"]
21 | s := ""
22 | if ok {
23 | s = fmt.Sprintf("server[%s", serverName)
24 |
25 | }
26 |
27 | goroutineName, ok := entry.Data["goroutine"]
28 | if ok {
29 | goroutineName = fmt.Sprintf("|%s", goroutineName)
30 |
31 | }
32 | if !ok {
33 | goroutineName = ""
34 | }
35 | delete(entry.Data, "goroutine")
36 | delete(entry.Data, "server")
37 |
38 | entry.Message = fmt.Sprintf("%s%s]: %s", s, goroutineName, entry.Message)
39 | return nil
40 | }
41 |
42 | // 记录行号的hook
43 | type LineNumHook struct {
44 | }
45 |
46 | func (hook LineNumHook) Levels() []logrus.Level {
47 | return logrus.AllLevels
48 | }
49 |
50 | func (hook LineNumHook) Fire(entry *logrus.Entry) error {
51 | pc, file, line, ok := runtime.Caller(7)
52 | if ok {
53 | i := strings.Index(file, "YTask")
54 | if i == -1 {
55 | return nil
56 | }
57 | entry.Data["file"] = fmt.Sprintf("%s:%d", file[i:], line)
58 |
59 | fu := runtime.FuncForPC(pc - 1)
60 | name := fu.Name()
61 | i = strings.LastIndex(name, "/")
62 | if i == -1 {
63 | return nil
64 | }
65 | entry.Data["func"] = name[i+1:]
66 |
67 | }
68 | return nil
69 | }
70 |
71 | func init() {
72 |
73 | YTaskLog = logrus.New()
74 | YTaskLog.SetFormatter(&logrus.TextFormatter{
75 | TimestampFormat: "2006-01-02 15:04:05",
76 | FullTimestamp: true,
77 | })
78 |
79 | YTaskLog.SetLevel(logrus.InfoLevel)
80 | YTaskLog.AddHook(&LineNumHook{})
81 | YTaskLog.AddHook(&YTaskHook{})
82 |
83 | }
84 |
85 | type LoggerInterface interface {
86 | Debug(string)
87 | DebugWithField(string, string, interface{})
88 | Info(string)
89 | InfoWithField(string, string, interface{})
90 | Warn(string)
91 | WarnWithField(string, string, interface{})
92 | Error(string)
93 | ErrorWithField(string, string, interface{})
94 | Fatal(string)
95 | FatalWithField(string, string, interface{})
96 | Panic(string)
97 | PanicWithField(string, string, interface{})
98 | SetLevel(string)
99 | Clone() LoggerInterface
100 | }
101 |
102 | type YTaskLogger struct {
103 | logger *logrus.Logger
104 | }
105 |
106 | func NewYTaskLogger(logger *logrus.Logger) *YTaskLogger {
107 | return &YTaskLogger{
108 | logger: logger,
109 | }
110 | }
111 |
112 | func (yl *YTaskLogger) Debug(msg string) {
113 | yl.logger.Debug(msg)
114 | }
115 |
116 | func (yl *YTaskLogger) DebugWithField(msg string, key string, val interface{}) {
117 | yl.logger.WithField(key, val).Debug(msg)
118 | }
119 |
120 | func (yl *YTaskLogger) Info(msg string) {
121 | yl.logger.Info(msg)
122 | }
123 |
124 | func (yl *YTaskLogger) InfoWithField(msg string, key string, val interface{}) {
125 | yl.logger.WithField(key, val).Info(msg)
126 | }
127 |
128 | func (yl *YTaskLogger) Warn(msg string) {
129 | yl.logger.Warn(msg)
130 | }
131 |
132 | func (yl *YTaskLogger) WarnWithField(msg string, key string, val interface{}) {
133 | yl.logger.WithField(key, val).Warn(msg)
134 | }
135 |
136 | func (yl *YTaskLogger) Error(msg string) {
137 | yl.logger.Error(msg)
138 | }
139 |
140 | func (yl *YTaskLogger) ErrorWithField(msg string, key string, val interface{}) {
141 | yl.logger.WithField(key, val).Error(msg)
142 | }
143 |
144 | func (yl *YTaskLogger) Fatal(msg string) {
145 | yl.logger.Fatal(msg)
146 | }
147 |
148 | func (yl *YTaskLogger) FatalWithField(msg string, key string, val interface{}) {
149 | yl.logger.WithField(key, val).Fatal(msg)
150 | }
151 |
152 | func (yl *YTaskLogger) Panic(msg string) {
153 | yl.logger.Panic(msg)
154 | }
155 |
156 | func (yl *YTaskLogger) PanicWithField(msg string, key string, val interface{}) {
157 | yl.logger.WithField(key, val).Panic(msg)
158 | }
159 |
160 | func (yl *YTaskLogger) SetLevel(level string) {
161 | switch level {
162 | case "debug":
163 | yl.logger.SetLevel(logrus.DebugLevel)
164 | case "info":
165 | yl.logger.SetLevel(logrus.InfoLevel)
166 | case "warn":
167 | yl.logger.SetLevel(logrus.WarnLevel)
168 | case "error":
169 | yl.logger.SetLevel(logrus.ErrorLevel)
170 | case "fatal":
171 | yl.logger.SetLevel(logrus.FatalLevel)
172 | case "panic":
173 | yl.logger.SetLevel(logrus.PanicLevel)
174 | default:
175 | yl.logger.SetLevel(logrus.InfoLevel)
176 | }
177 | }
178 |
179 | func (yl *YTaskLogger) Clone() LoggerInterface {
180 | return yl
181 | }
182 |
--------------------------------------------------------------------------------
/v3/message/msg.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/util"
5 | "github.com/google/uuid"
6 | "time"
7 | )
8 |
9 | type Message struct {
10 | Id string `json:"id"`
11 | WorkerName string `json:"worker_name"`
12 | FuncArgs []string `json:"func_args"`
13 | MsgArgs MessageArgs `v2JsonName:"TaskCtl"` // 为了方便client端send时通过SetTaskCtl修改相关参数
14 | // 因此单独放把这些参数放一个结构体里
15 |
16 | }
17 |
18 | type MessageArgs struct {
19 | RetryCount int
20 | RunTime time.Time
21 | ExpireTime time.Time
22 | Workflow []MessageWorkflowArgs `json:"workflow"`
23 | }
24 |
25 | type MessageWorkflowArgs struct {
26 | GroupName string
27 | WorkerName string
28 | RetryCount int
29 | RunAfter time.Duration
30 | ExpireTime time.Time
31 | }
32 |
33 | func NewMsgArgs() MessageArgs {
34 | return MessageArgs{RetryCount: 3}
35 | }
36 | func NewMessage(msgArgs MessageArgs) Message {
37 | id := uuid.New().String()
38 | return Message{
39 | Id: id,
40 | MsgArgs: msgArgs,
41 | }
42 | }
43 |
44 | func (m *Message) SetArgs(args ...interface{}) error {
45 | r, err := util.GoVarsToYJsonSlice(args...)
46 | if err != nil {
47 | return err
48 | }
49 | m.FuncArgs = r
50 | return nil
51 | }
52 |
53 | func (m Message) IsRunTime() bool {
54 | n := time.Now().Unix()
55 | return n >= m.MsgArgs.GetRunTime().Unix()
56 | }
57 |
58 | func (m Message) RunTimeAfter(t time.Time) bool {
59 | return m.MsgArgs.GetRunTime().Unix() > t.Unix()
60 | }
61 |
62 | func (m Message) RunTimeAfterOrEqual(t time.Time) bool {
63 | return m.MsgArgs.GetRunTime().Unix() >= t.Unix()
64 | }
65 |
66 | func (m Message) RunTimeBefore(t time.Time) bool {
67 | return m.MsgArgs.GetRunTime().Unix() < t.Unix()
68 | }
69 |
70 | func (m Message) RunTimeBeforeOrEqual(t time.Time) bool {
71 | return m.MsgArgs.GetRunTime().Unix() <= t.Unix()
72 | }
73 |
74 | func (m Message) RunTimeEqual(t time.Time) bool {
75 | return m.MsgArgs.GetRunTime().Unix() == t.Unix()
76 | }
77 |
78 | func (m MessageArgs) IsDelayMessage() bool {
79 | return !m.RunTime.IsZero()
80 | }
81 |
82 | func (t *MessageArgs) AppendWorkflow(work MessageWorkflowArgs) {
83 | t.Workflow = append(t.Workflow, work)
84 | }
85 |
86 | func (t MessageArgs) GetRunTime() time.Time {
87 | return t.RunTime
88 | }
89 |
--------------------------------------------------------------------------------
/v3/message/result.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/util/yjson"
6 | )
7 |
8 | type resultStatusChoice struct {
9 | Sent int
10 | FirstRunning int
11 | WaitingRetry int
12 | Running int
13 | Success int
14 | Failure int
15 | Expired int
16 | Abort int // 手动中止任务
17 | }
18 |
19 | var ResultStatus = resultStatusChoice{
20 | Sent: 0,
21 | FirstRunning: 1,
22 | WaitingRetry: 2,
23 | Running: 3,
24 | Success: 4,
25 | Failure: 5,
26 | Expired: 6,
27 | Abort: 7, // 手动中止任务
28 | }
29 |
30 | type workflowStatusChoice struct {
31 | Waiting string
32 | Running string
33 | Success string
34 | Failure string
35 | Expired string
36 | Abort string
37 | }
38 |
39 | var WorkflowStatus = workflowStatusChoice{
40 | Waiting: "waiting",
41 | Running: "running",
42 | Success: "success",
43 | Failure: "failure",
44 | Expired: "expired",
45 | Abort: "abort", // 手动中止任务
46 | }
47 |
48 | var StatusToWorkflowStatus = map[int]string{
49 | ResultStatus.Sent: WorkflowStatus.Waiting,
50 | ResultStatus.FirstRunning: WorkflowStatus.Running,
51 | ResultStatus.WaitingRetry: WorkflowStatus.Running,
52 | ResultStatus.Running: WorkflowStatus.Running,
53 | ResultStatus.Success: WorkflowStatus.Success,
54 | ResultStatus.Failure: WorkflowStatus.Failure,
55 | ResultStatus.Expired: WorkflowStatus.Expired,
56 | ResultStatus.Abort: WorkflowStatus.Abort,
57 | }
58 |
59 | type Result struct {
60 | Id string `json:"id"`
61 | Status int `json:"status"` // 0:sent , 1:first running , 2: waiting to retry , 3: running , 4: success , 5: Failure
62 | FuncReturn []string `json:"func_return"`
63 | RetryCount int `json:"retry_count"`
64 | Workflow [][2]string `json:"workflow"` // [["workName","status"],] ; status: waiting , running , success , failure , expired , abort
65 | Err string `json:"err"`
66 | }
67 |
68 | func NewResult(id string) Result {
69 | return Result{
70 | Id: id,
71 | }
72 | }
73 | func (r Result) GetBackendKey() string {
74 | return "YTask:Backend:" + r.Id
75 | }
76 |
77 | func (r *Result) SetStatusRunning() {
78 | if r.Status == ResultStatus.Sent {
79 | r.Status = ResultStatus.FirstRunning
80 | } else {
81 | r.Status = ResultStatus.Running
82 | r.RetryCount += 1
83 | }
84 | }
85 |
86 | func (r Result) Get(index int, v interface{}) error {
87 |
88 | err := yjson.YJson.UnmarshalFromString(r.FuncReturn[index], v)
89 | return err
90 | }
91 |
92 | func (r Result) Gets(args ...interface{}) error {
93 | for i, v := range args {
94 | err := yjson.YJson.UnmarshalFromString(r.FuncReturn[i], v)
95 | if err != nil {
96 | return err
97 | }
98 | }
99 | return nil
100 | }
101 |
102 | // 过时: 此方法只能用于v1.0.0,高版本中,如果值为int64,uint64类型,会导致获取的值不对
103 | // Deprecated: only can use in v1.0.0
104 | func (r Result) GetInterface(index int) (interface{}, error) {
105 |
106 | var result interface{}
107 |
108 | err := yjson.YJson.Unmarshal([]byte(r.FuncReturn[index]), &result)
109 | return result, err
110 | }
111 | func (r Result) GetInt64(index int) (int64, error) {
112 | var v int64
113 | err := r.Get(index, &v)
114 | return v, err
115 | }
116 |
117 | func (r Result) GetUint64(index int) (uint64, error) {
118 | var v uint64
119 | err := r.Get(index, &v)
120 | return v, err
121 | }
122 | func (r Result) GetFloat64(index int) (float64, error) {
123 | var v float64
124 | err := r.Get(index, &v)
125 | return v, err
126 | }
127 |
128 | func (r Result) GetBool(index int) (bool, error) {
129 | var v bool
130 | err := r.Get(index, &v)
131 | return v, err
132 | }
133 |
134 | func (r Result) GetString(index int) (string, error) {
135 | var v string
136 | err := r.Get(index, &v)
137 | return v, err
138 | }
139 |
140 | func (r Result) IsSuccess() bool {
141 | return r.Status == ResultStatus.Success
142 | }
143 |
144 | func (r Result) IsFailure() bool {
145 | if r.Status == ResultStatus.Failure || r.Status == ResultStatus.Expired || r.Status == ResultStatus.Abort {
146 | return true
147 | }
148 | return false
149 | }
150 |
151 | func (r Result) IsFinish() bool {
152 | if r.Status == ResultStatus.Success || r.IsFailure() {
153 | return true
154 | }
155 | return false
156 | }
157 |
158 | // 结束任务标志
159 | func GetAbortKey(id string) string {
160 | return fmt.Sprintf("Abort:%s", id)
161 |
162 | }
163 |
164 | func NewAbortResult(id string) Result {
165 | return Result{
166 | Id: GetAbortKey(id),
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/v3/server/delayServer.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 |
6 | "fmt"
7 | "github.com/gojuukaze/YTask/v3/config"
8 | "github.com/gojuukaze/YTask/v3/message"
9 | "sync"
10 | )
11 |
12 | type DelayServer struct {
13 | sync.Map
14 | ServerUtils
15 | delayGroupName string
16 |
17 | // 延时任务的本地队列,用于在本地排序
18 | queue SortQueue
19 |
20 | // 到处理时间的任务先放入readyMsgChan暂存,然后在放入inlineServerMsgChan
21 | readyMsgChan chan message.Message
22 | // inlineServer中的chan
23 | inlineServerMsgChan chan message.Message
24 |
25 | // stop chan
26 | safeStopChan chan struct{}
27 | getDelayMsgStopChan chan struct{}
28 | getReadyMsgStopChan chan struct{}
29 | sendReadyMsgStopChan chan struct{}
30 | }
31 |
32 | func NewDelayServer(groupName string, c config.Config, msgChan chan message.Message) DelayServer {
33 | ds := DelayServer{
34 | ServerUtils: newServerUtils(c.Broker, nil, c.Logger, 0, 0),
35 | queue: NewSortQueue(c.DelayServerQueueSize),
36 | readyMsgChan: make(chan message.Message, 5),
37 | inlineServerMsgChan: msgChan,
38 | safeStopChan: make(chan struct{}),
39 | getDelayMsgStopChan: make(chan struct{}),
40 | getReadyMsgStopChan: make(chan struct{}),
41 | sendReadyMsgStopChan: make(chan struct{}),
42 | }
43 | ds.delayGroupName = ds.GetDelayGroupName(groupName)
44 | return ds
45 | }
46 |
47 | func (s *DelayServer) IsStop() bool {
48 | _, ok := s.Load("isStop")
49 | return ok
50 | }
51 |
52 | func (s *DelayServer) SetStop() {
53 | s.Store("isStop", struct{}{})
54 |
55 | }
56 |
57 | func (s *DelayServer) IsRunning() bool {
58 | _, ok := s.Load("isRunning")
59 | return ok
60 | }
61 |
62 | func (s *DelayServer) SetRunning() {
63 | s.Store("isRunning", struct{}{})
64 |
65 | }
66 |
67 | func (s *DelayServer) Run() {
68 | if s.IsRunning() {
69 | panic("DelayServer " + s.delayGroupName + " is running")
70 | }
71 |
72 | s.Store("isRunning", struct{}{})
73 | // 这里应该一个就够了,不知道为啥之前设为了11 =。=
74 | s.SetBrokerPoolSize(1)
75 | s.BrokerActivate()
76 |
77 | //log.YTaskLog.WithField("server", s.delayGroupName).Infof("Start delayServer[%s] ", s.delayGroupName)
78 | s.logger.InfoWithField(fmt.Sprintf("Start delayServer[%s] ", s.delayGroupName), "server", s.delayGroupName)
79 |
80 | go s.GetDelayMsgGoroutine()
81 | go s.GetReadyMsgGoroutine()
82 | go s.SendReadyMsgGoroutine()
83 |
84 | }
85 |
86 | func (s *DelayServer) safeStop() {
87 | //log.YTaskLog.WithField("server", s.delayGroupName).Info("waiting for incomplete goroutine ")
88 | s.logger.InfoWithField("waiting for incomplete goroutine ", "server", s.delayGroupName)
89 |
90 | s.SetStop()
91 | close(s.readyMsgChan)
92 |
93 | <-s.getDelayMsgStopChan
94 | <-s.getReadyMsgStopChan
95 | // 必须要等前两个结束才能执行这个
96 | s.LSendQueue()
97 | <-s.sendReadyMsgStopChan
98 |
99 | }
100 |
101 | func (s *DelayServer) Shutdown(ctx context.Context) error {
102 |
103 | go func() {
104 | s.safeStop()
105 | s.safeStopChan <- struct{}{}
106 | }()
107 |
108 | select {
109 | case <-s.safeStopChan:
110 | case <-ctx.Done():
111 | return ctx.Err()
112 | }
113 |
114 | //log.YTaskLog.WithField("server", s.delayGroupName).Info("Shutdown!")
115 | s.logger.InfoWithField("Shutdown!", "server", s.delayGroupName)
116 | return nil
117 | }
118 |
119 | func (s *DelayServer) LSendQueue() {
120 | for i := s.queue.len - 1; i >= 0; i-- {
121 | msg := s.queue.Get(i)
122 | err := s.LSendMsg(s.delayGroupName, msg)
123 | if err != nil {
124 | //log.YTaskLog.WithField("server", s.delayGroupName).Error("SendQueue msg error: ", err, " [msg=", msg, "]")
125 | s.logger.ErrorWithField(fmt.Sprint("SendQueue msg error: ", err, " [msg=", msg, "]"), "server", s.delayGroupName)
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/v3/server/inlineServer.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gojuukaze/YTask/v3/config"
7 | "github.com/gojuukaze/YTask/v3/message"
8 | "github.com/gojuukaze/YTask/v3/util"
9 | "reflect"
10 | "sync"
11 | )
12 |
13 | type InlineServer struct {
14 | sync.Map
15 | ServerUtils
16 |
17 | groupName string
18 | workerMap map[string]WorkerInterface // [workerName]worker
19 |
20 | workerReadyChan chan struct{}
21 | msgChan chan message.Message
22 |
23 | getMessageGoroutineStopChan chan struct{}
24 | workerGoroutineStopChan chan struct{}
25 |
26 | safeStopChan chan struct{}
27 | }
28 |
29 | func NewInlineServer(groupName string, c config.Config) InlineServer {
30 |
31 | wm := make(map[string]WorkerInterface)
32 | if c.Debug {
33 | //log.YTaskLog.SetLevel(logrus.DebugLevel)
34 | c.Logger.SetLevel("debug")
35 | }
36 |
37 | return InlineServer{
38 | groupName: groupName,
39 | workerMap: wm,
40 | ServerUtils: newServerUtils(c.Broker, c.Backend, c.Logger, c.StatusExpires, c.ResultExpires),
41 | safeStopChan: make(chan struct{}),
42 | getMessageGoroutineStopChan: make(chan struct{}),
43 | workerGoroutineStopChan: make(chan struct{}),
44 | }
45 | }
46 |
47 | func (t *InlineServer) IsRunning() bool {
48 | _, ok := t.Load("isRunning")
49 | return ok
50 | }
51 |
52 | func (t *InlineServer) SetRunning() {
53 | t.Store("isRunning", struct{}{})
54 |
55 | }
56 |
57 | func (t *InlineServer) IsStop() bool {
58 | _, ok := t.Load("isStop")
59 | return ok
60 | }
61 |
62 | func (t *InlineServer) SetStop() {
63 | t.Store("isStop", struct{}{})
64 |
65 | }
66 |
67 | func (t *InlineServer) MakeWorkerReady() {
68 | defer func() {
69 | recover()
70 | }()
71 | t.workerReadyChan <- struct{}{}
72 | }
73 |
74 | func (t *InlineServer) Run(numWorkers int) {
75 |
76 | if t.IsRunning() {
77 | panic("inlineServer " + t.groupName + " is running")
78 | }
79 | t.SetRunning()
80 |
81 | // 初始化Broker, Backend
82 | if t.GetBrokerPoolSize() <= 0 {
83 | t.SetBrokerPoolSize(3)
84 | } else {
85 | t.SetBrokerPoolSize(t.GetBrokerPoolSize())
86 | }
87 | t.BrokerActivate()
88 |
89 | if t.backend != nil {
90 | if t.GetBackendPoolSize() <= 0 {
91 | t.SetBackendPoolSize(util.Min(10, numWorkers))
92 | }
93 | t.BackendActivate()
94 | }
95 |
96 | //log.YTaskLog.WithField("server", t.groupName).Infof("Start server[%s] numWorkers=%d", t.groupName, numWorkers)
97 | t.logger.InfoWithField(fmt.Sprintf("Start server[%s] numWorkers=%d", t.groupName, numWorkers), "server", t.groupName)
98 |
99 | //log.YTaskLog.WithField("server", t.groupName).Info("group workers:")
100 | t.logger.InfoWithField("group workers:", "server", t.groupName)
101 |
102 | for name := range t.workerMap {
103 | //log.YTaskLog.WithField("server", t.groupName).Info(" - " + name)
104 | t.logger.InfoWithField(" - "+name, "server", t.groupName)
105 | }
106 |
107 | // 初始化chan,运行协程
108 | t.workerReadyChan = make(chan struct{}, numWorkers)
109 | t.msgChan = make(chan message.Message, numWorkers)
110 |
111 | t.getMessageGoroutineStopChan = make(chan struct{}, 1)
112 | go t.GetNextMessageGoroutine()
113 |
114 | t.workerGoroutineStopChan = make(chan struct{}, 1)
115 | go t.WorkerGoroutine()
116 |
117 | for i := 0; i < numWorkers; i++ {
118 | t.MakeWorkerReady()
119 | }
120 |
121 | }
122 |
123 | func (t *InlineServer) safeStop() {
124 | //log.YTaskLog.WithField("server", t.groupName).Info("waiting for incomplete tasks ")
125 | t.logger.InfoWithField("waiting for incomplete tasks ", "server", t.groupName)
126 |
127 | // stop get message goroutine
128 | close(t.workerReadyChan)
129 | t.SetStop()
130 | <-t.getMessageGoroutineStopChan
131 |
132 | // stop worker goroutine
133 | close(t.msgChan)
134 | <-t.workerGoroutineStopChan
135 |
136 | }
137 |
138 | func (t *InlineServer) Shutdown(ctx context.Context) error {
139 |
140 | go func() {
141 | t.safeStop()
142 | close(t.safeStopChan)
143 |
144 | }()
145 |
146 | select {
147 | case <-t.safeStopChan:
148 | case <-ctx.Done():
149 | return ctx.Err()
150 | }
151 |
152 | //log.YTaskLog.WithField("server", t.groupName).Info("Shutdown!")
153 | t.logger.InfoWithField("Shutdown!", "server", t.groupName)
154 | return nil
155 | }
156 |
157 | // Add worker to group
158 | // w : worker func
159 | // callbackFunc : callbackFunc func
160 | func (t *InlineServer) Add(workerName string, w interface{}, callbackFunc ...interface{}) {
161 |
162 | var cFunc interface{} = nil
163 |
164 | cType := "func"
165 | if len(callbackFunc) > 0 {
166 | cFunc = callbackFunc[0]
167 | cType = reflect.TypeOf(cFunc).Kind().String()
168 | }
169 |
170 | wType := reflect.TypeOf(w).Kind().String()
171 | if wType == "func" && cType == "func" {
172 | funcWorker := &FuncWorker{
173 | Name: workerName,
174 | Func: w,
175 | CallbackFunc: cFunc,
176 | Logger: t.logger,
177 | }
178 | t.workerMap[workerName] = funcWorker
179 | } else {
180 | panic("worker and callbackFunc must be func")
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/v3/server/multiServer.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/v3/config"
6 | "golang.org/x/sync/errgroup"
7 | )
8 |
9 | type Server struct {
10 | ServerMap map[string]*InlineServer // groupName:server
11 | DelayServerMap map[string]*DelayServer // groupName:server
12 |
13 | config config.Config
14 | }
15 |
16 | func NewServer(c config.Config) Server {
17 |
18 | if c.Debug {
19 | //log.YTaskLog.SetLevel(logrus.DebugLevel)
20 | c.Logger.SetLevel("debug")
21 | }
22 | return Server{
23 | ServerMap: make(map[string]*InlineServer),
24 | DelayServerMap: make(map[string]*DelayServer),
25 | config: c,
26 | }
27 | }
28 |
29 | // Add worker to group
30 | // w : worker func
31 | // callbackFunc:callbackFunc
32 | func (t *Server) Add(groupName string, workerName string, w interface{}, callbackFunc ...interface{}) {
33 | server := t.getOrCreateInlineServer(groupName)
34 | server.Add(workerName, w, callbackFunc...)
35 |
36 | }
37 |
38 | func (t *Server) getOrCreateInlineServer(groupName string) *InlineServer {
39 | server, ok := t.ServerMap[groupName]
40 | if ok {
41 | return server
42 | } else {
43 | newServer := NewInlineServer(groupName, t.config.Clone())
44 | t.ServerMap[groupName] = &newServer
45 | return t.ServerMap[groupName]
46 | }
47 |
48 | }
49 |
50 | func (t *Server) getOrCreateDelayServer(groupName string) *DelayServer {
51 | ds, ok := t.DelayServerMap[groupName]
52 | if ok {
53 | return ds
54 | } else {
55 | is := t.ServerMap[groupName]
56 | ds := NewDelayServer(groupName, t.config.Clone(), is.msgChan)
57 | t.DelayServerMap[groupName] = &ds
58 | return t.DelayServerMap[groupName]
59 | }
60 |
61 | }
62 |
63 | func (t *Server) Run(groupName string, numWorkers int, enableDelayServer ...bool) {
64 | server, ok := t.ServerMap[groupName]
65 | if !ok {
66 | panic("YTask: not found group: " + groupName)
67 | }
68 | server.Run(numWorkers)
69 | if (t.config.EnableDelayServer && t.config.DelayServerQueueSize > 0) ||
70 | (len(enableDelayServer) > 0 && enableDelayServer[0]) {
71 | ds := t.getOrCreateDelayServer(groupName)
72 | ds.Run()
73 | }
74 | }
75 |
76 | func (t *Server) GetClient() Client {
77 |
78 | return NewClient(t.config.Clone())
79 | }
80 |
81 | func (t *Server) Shutdown(ctx context.Context) error {
82 |
83 | var eg = errgroup.Group{}
84 | for _, s := range t.ServerMap {
85 | s := s
86 | if s.IsRunning() {
87 | eg.Go(func() error {
88 | return s.Shutdown(ctx)
89 | })
90 | }
91 | }
92 |
93 | for _, s := range t.DelayServerMap {
94 | s := s
95 | if s.IsRunning() {
96 | eg.Go(func() error {
97 | return s.Shutdown(ctx)
98 | })
99 | }
100 | }
101 |
102 | return eg.Wait()
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/v3/server/serverUtils.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/backends"
5 | "github.com/gojuukaze/YTask/v3/brokers"
6 | "github.com/gojuukaze/YTask/v3/consts"
7 | "github.com/gojuukaze/YTask/v3/log"
8 | "github.com/gojuukaze/YTask/v3/message"
9 | "github.com/gojuukaze/YTask/v3/yerrors"
10 | "time"
11 | )
12 |
13 | // serverUtils用于把 delayServer,inlineServer,client 用到的方法抽离出来
14 | type ServerUtils struct {
15 | broker brokers.BrokerInterface
16 | backend backends.BackendInterface
17 | logger log.LoggerInterface
18 |
19 | // config
20 | statusExpires int // second, -1:forever
21 | resultExpires int // second, -1:forever
22 | }
23 |
24 | func newServerUtils(broker brokers.BrokerInterface, backend backends.BackendInterface, logger log.LoggerInterface, statusExpires int, resultExpires int) ServerUtils {
25 | return ServerUtils{broker: broker, backend: backend, logger: logger, statusExpires: statusExpires, resultExpires: resultExpires}
26 | }
27 |
28 | func (b ServerUtils) GetQueueName(groupName string) string {
29 | if consts.UserV2Name {
30 | return "YTask:Query:" + groupName
31 | } else {
32 | return "YTask:Queue:" + groupName
33 | }
34 | }
35 |
36 | func (b ServerUtils) GetDelayGroupName(groupName string) string {
37 | return "Delay:" + groupName
38 | }
39 |
40 | func (b *ServerUtils) GetBrokerPoolSize() int {
41 | return b.broker.GetPoolSize()
42 | }
43 |
44 | func (b *ServerUtils) SetBrokerPoolSize(num int) {
45 | b.broker.SetPoolSize(num)
46 | }
47 |
48 | func (b *ServerUtils) BrokerActivate() {
49 | b.broker.Activate()
50 | }
51 |
52 | func (b *ServerUtils) Next(groupName string) (message.Message, error) {
53 | return b.broker.Next(b.GetQueueName(groupName))
54 | }
55 |
56 | // send msg to Queue
57 | // t.Send("groupName", "workerName" , 1,"hi",1.2)
58 | //
59 | func (b *ServerUtils) Send(groupName string, workerName string, msgArgs message.MessageArgs, args ...interface{}) (string, error) {
60 | var msg = message.NewMessage(msgArgs)
61 | msg.WorkerName = workerName
62 | err := msg.SetArgs(args...)
63 | if err != nil {
64 | return "", err
65 | }
66 | return msg.Id, b.SendMsg(groupName, msg)
67 |
68 | }
69 |
70 | func (b *ServerUtils) SendMsg(groupName string, msg message.Message) error {
71 | var err error
72 | for i := 0; i < 3; i++ {
73 | err = b.broker.Send(b.GetQueueName(groupName), msg)
74 | if err == nil {
75 | break
76 | }
77 | time.Sleep(1 * time.Second)
78 | }
79 | return err
80 |
81 | }
82 |
83 | func (b *ServerUtils) LSendMsg(groupName string, msg message.Message) error {
84 |
85 | var err error
86 | for i := 0; i < 3; i++ {
87 | err = b.broker.LSend(b.GetQueueName(groupName), msg)
88 | if err == nil {
89 | break
90 | }
91 | }
92 | return err
93 |
94 | }
95 |
96 | func (b *ServerUtils) GetBackendPoolSize() int {
97 | if b.backend == nil {
98 | return 0
99 | }
100 | return b.backend.GetPoolSize()
101 | }
102 |
103 | func (b *ServerUtils) SetBackendPoolSize(num int) {
104 | if b.backend == nil {
105 | return
106 | }
107 | b.backend.SetPoolSize(num)
108 | }
109 |
110 | func (b *ServerUtils) BackendActivate() {
111 | if b.backend == nil {
112 | return
113 | }
114 | b.backend.Activate()
115 | }
116 |
117 | func (b *ServerUtils) SetResult(result message.Result) error {
118 | if b.backend == nil {
119 | return nil
120 | }
121 | var exTime int
122 | if result.IsFinish() {
123 | exTime = b.resultExpires
124 | } else {
125 | exTime = b.statusExpires
126 | }
127 | if exTime == 0 {
128 | return nil
129 | }
130 | return b.backend.SetResult(result, exTime)
131 | }
132 |
133 | func (b *ServerUtils) GetResult(id string) (message.Result, error) {
134 | if b.backend == nil {
135 | return message.Result{}, yerrors.ErrNilResult{}
136 | }
137 | result := message.NewResult(id)
138 | return b.backend.GetResult(result.GetBackendKey())
139 | }
140 |
141 | // - exTime : 过期时间,秒
142 | func (b *ServerUtils) AbortTask(id string, exTime int) error {
143 | if b.backend == nil {
144 | return yerrors.ErrNilBackend{}
145 | }
146 | return b.backend.SetResult(message.NewAbortResult(id), exTime)
147 | }
148 |
149 | func (b *ServerUtils) IsAbort(id string) (bool, error) {
150 | if b.backend == nil {
151 | return false, yerrors.ErrNilBackend{}
152 | }
153 | _, err := b.backend.GetResult(message.NewAbortResult(id).GetBackendKey())
154 | if err == nil {
155 | return true, err
156 | }
157 | if yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
158 | return false, nil
159 | }
160 | return false, err
161 | }
162 |
--------------------------------------------------------------------------------
/v3/server/sortQueue.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "sync"
7 | "time"
8 | )
9 |
10 | type SortQueue struct {
11 | sync.Mutex
12 |
13 | Queue []message.Message
14 | len int
15 | MaxLen int
16 | }
17 |
18 | func NewSortQueue(maxLen int) SortQueue {
19 | return SortQueue{
20 | Queue: make([]message.Message, maxLen+1),
21 | MaxLen: maxLen,
22 | }
23 | }
24 |
25 | func (s *SortQueue) IsFull() bool {
26 | return s.len == s.MaxLen
27 | }
28 |
29 | func (s *SortQueue) Insert(msg message.Message) *message.Message {
30 | s.Lock()
31 | defer s.Unlock()
32 | if s.len == 0 {
33 | s.Queue[0] = msg
34 |
35 | } else {
36 | t := msg.MsgArgs.GetRunTime()
37 | if s.Queue[s.len-1].RunTimeBeforeOrEqual(t) {
38 | s.Queue[s.len] = msg
39 |
40 | } else {
41 | index := s.binarySearch(0, s.len-1, t)
42 | copy(s.Queue[index+1:], s.Queue[index:])
43 | s.Queue[index] = msg
44 | }
45 |
46 | }
47 |
48 | if s.len == s.MaxLen {
49 | return &s.Queue[s.len]
50 | } else {
51 | s.len++
52 | return nil
53 | }
54 |
55 | }
56 |
57 | func (s *SortQueue) binarySearch(leftIndex int, rightIndex int, t time.Time) int {
58 |
59 | if leftIndex > rightIndex {
60 | return leftIndex
61 | }
62 | middleIndex := (leftIndex + rightIndex) / 2
63 |
64 | if s.Queue[middleIndex].RunTimeAfter(t) {
65 | return s.binarySearch(leftIndex, middleIndex-1, t)
66 | } else if s.Queue[middleIndex].RunTimeBefore(t) {
67 | return s.binarySearch(middleIndex+1, rightIndex, t)
68 | } else {
69 | return middleIndex + 1
70 | }
71 | }
72 |
73 | func (s *SortQueue) Pop() *message.Message {
74 | s.Lock()
75 | defer s.Unlock()
76 | if s.len == 0 {
77 | return nil
78 | }
79 | if s.Queue[0].IsRunTime() {
80 | msg := s.Queue[0]
81 | copy(s.Queue[:], s.Queue[1:])
82 | s.len--
83 | return &msg
84 | }
85 | return nil
86 | }
87 |
88 | func (s *SortQueue) Get(i int) message.Message {
89 | return s.Queue[i]
90 | }
91 |
92 | func (s *SortQueue) print() {
93 | for i := 0; i < s.len; i++ {
94 | fmt.Print(s.Queue[i].WorkerName, ",")
95 | }
96 | fmt.Println()
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/v3/server/taskCtl.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "errors"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/yerrors"
7 | "time"
8 | )
9 |
10 | /*
11 | Message 与 TaskMessage 是差不多的(TaskCtl中的参数有细微差别),之所以又写了个TaskMessage是为了解决循环引用问题
12 |
13 | 因为任务函数中可以使用TaskCtl控制一些东西,但TaskCtl会用到broker,backend等,broker,backend中又用到了message这样就造成了循环引用
14 | (这里必须fuck一下go的循环引用!!!)
15 |
16 |
17 | - Message用于client端send时,只保存任务参数;
18 | - server端获取Message后会转为TaskMessage
19 |
20 | Message中为了便于区分,task_ctl改名为MsgArgs
21 | */
22 |
23 | type TaskMessage struct {
24 | Id string `json:"id"`
25 | WorkerName string `json:"worker_name"`
26 | FuncArgs []string `json:"func_args"` //yjson string slice
27 | Ctl TaskCtl `json:"task_ctl"`
28 | }
29 | type TaskCtlWorkflowArgs struct {
30 | GroupName string
31 | WorkerName string
32 | RetryCount int
33 | RunAfter time.Duration
34 | ExpireTime time.Time
35 | }
36 |
37 | type TaskCtl struct {
38 | message.Message
39 | err error
40 | su *ServerUtils
41 | }
42 |
43 | func NewTaskCtl(msg message.Message) TaskCtl {
44 | return TaskCtl{Message: msg}
45 | }
46 |
47 | func (t *TaskCtl) GetTaskId() string {
48 | return t.Id
49 | }
50 |
51 | func (t *TaskCtl) Retry(err error) {
52 | t.err = err
53 | }
54 |
55 | func (t *TaskCtl) GetRetryCount() int {
56 | return t.MsgArgs.RetryCount
57 | }
58 | func (t *TaskCtl) SetRetryCount(c int) {
59 | t.MsgArgs.RetryCount = c
60 | }
61 |
62 | func (t TaskCtl) CanRetry() bool {
63 | return t.MsgArgs.RetryCount > 0
64 | }
65 |
66 | func (t TaskCtl) GetError() error {
67 | return t.err
68 | }
69 |
70 | func (t *TaskCtl) SetError(err error) {
71 | t.err = err
72 | }
73 |
74 | func (t *TaskCtl) SetRunTime(_t time.Time) {
75 | t.MsgArgs.RunTime = _t
76 | }
77 |
78 | func (t *TaskCtl) GetRunTime() time.Time {
79 | return t.MsgArgs.RunTime
80 | }
81 |
82 | func (t *TaskCtl) IsZeroRunTime() bool {
83 | return t.MsgArgs.RunTime.IsZero()
84 | }
85 |
86 | func (t *TaskCtl) SetExpireTime(_t time.Time) {
87 | t.MsgArgs.ExpireTime = _t
88 | }
89 |
90 | func (t *TaskCtl) IsExpired() bool {
91 | return !t.MsgArgs.ExpireTime.IsZero() && time.Now().After(t.MsgArgs.ExpireTime)
92 | }
93 |
94 | func (t *TaskCtl) Abort(msg string) {
95 | t.err = yerrors.ErrAbortTask{msg}
96 | t.MsgArgs.RetryCount = 0
97 | }
98 |
99 | func (t *TaskCtl) IsAbort() (bool, error) {
100 | if t.su == nil {
101 | return false, errors.New("IsAbort() can only be called on the server side")
102 | }
103 | return t.su.IsAbort(t.GetTaskId())
104 | }
105 |
106 | func (t *TaskCtl) SetServerUtil(su *ServerUtils) {
107 | t.su = su
108 | }
109 |
--------------------------------------------------------------------------------
/v3/server/worker.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/gojuukaze/YTask/v3/log"
7 | "github.com/gojuukaze/YTask/v3/message"
8 | "github.com/gojuukaze/YTask/v3/util"
9 | "reflect"
10 | )
11 |
12 | type WorkerInterface interface {
13 | Run(ctl *TaskCtl, funcArgs []string, result *message.Result) error
14 | WorkerName() string
15 | After(ctl *TaskCtl, funcArgs []string, result *message.Result) error
16 | }
17 |
18 | type FuncWorker struct {
19 | Func interface{} // 执行的函数
20 | CallbackFunc interface{} // 回调函数
21 | Name string
22 | Logger log.LoggerInterface
23 | }
24 |
25 | func (f *FuncWorker) Run(ctl *TaskCtl, funcArgs []string, result *message.Result) error {
26 | return runFunc(f.Func, ctl, funcArgs, result, false, f.Logger)
27 | }
28 |
29 | func (f *FuncWorker) After(ctl *TaskCtl, funcArgs []string, result *message.Result) error {
30 | if f.CallbackFunc != nil {
31 | return runFunc(f.CallbackFunc, ctl, funcArgs, result, true, f.Logger)
32 |
33 | }
34 | return nil
35 | }
36 |
37 | func (f *FuncWorker) WorkerName() string {
38 | return f.Name
39 | }
40 |
41 | // isCallBack: 是否是回调函数
42 | func runFunc(f interface{}, ctl *TaskCtl, funcArgs []string, result *message.Result, isCallBack bool, logger log.LoggerInterface) (err error) {
43 | defer func() {
44 | e := recover()
45 | if e != nil {
46 | t, ok := e.(error)
47 | if ok {
48 | err = t
49 | } else {
50 | err = errors.New(fmt.Sprintf("%v", e))
51 | }
52 | if !isCallBack {
53 | result.Status = message.ResultStatus.Failure
54 | }
55 |
56 | }
57 | }()
58 | funcValue := reflect.ValueOf(f)
59 | funcType := reflect.TypeOf(f)
60 | var inStart = 0
61 | var inValue []reflect.Value
62 | if funcType.NumIn() > 0 && funcType.In(0) == reflect.TypeOf(&TaskCtl{}) {
63 | inStart = 1
64 | }
65 |
66 | inValue, err = util.GetCallInArgs(funcValue, funcArgs, inStart)
67 | if err != nil {
68 | return
69 | }
70 | if inStart == 1 {
71 | inValue = append(inValue, reflect.Value{})
72 | copy(inValue[1:], inValue)
73 | inValue[0] = reflect.ValueOf(ctl)
74 | }
75 |
76 | if isCallBack {
77 | inValue[len(inValue)-1] = reflect.ValueOf(result)
78 |
79 | }
80 |
81 | funcOut := funcValue.Call(inValue)
82 |
83 | if isCallBack {
84 | return
85 | }
86 | err = ctl.GetError()
87 | if err == nil {
88 | result.Status = message.ResultStatus.Success
89 | if len(funcOut) > 0 {
90 | re, err2 := util.GoValuesToYJsonSlice(funcOut)
91 | if err2 != nil {
92 | //log.YTaskLog.Error(err2)
93 | logger.Error(err2.Error())
94 | } else {
95 | result.FuncReturn = re
96 | }
97 | }
98 | }
99 |
100 | return
101 | }
102 |
--------------------------------------------------------------------------------
/v3/test/README.md:
--------------------------------------------------------------------------------
1 | # test
2 |
3 | ## run test
4 |
5 | ```shell
6 | go test -v test/*.go
7 | ```
8 |
9 | ## 编写说明
10 |
11 | 使用``LocalBroker``与``LocalBackend`` 进行测试时要注意:
12 |
13 | * 只能执行一次 ``Active()`` ,因为Active操作会清空数据
14 | * ``ser.GetClient()`` 应该在 ``ser.Run()`` 之前调用,同样也只能执行一次GetClient操作
15 | * 由于LocalBroker执行Next, Send时会争抢锁,有时候测试不通过可能是因为一直没抢到锁超时了,可以把此项测试移到redis中,或修改超时时间
16 | * GetResult时,timeout建议设长点,sleepTime建议为100毫秒或更短
--------------------------------------------------------------------------------
/v3/test/abort_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/brokers"
7 | "github.com/gojuukaze/YTask/v3/config"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "github.com/gojuukaze/YTask/v3/message"
10 | "github.com/gojuukaze/YTask/v3/server"
11 | "io/ioutil"
12 | "testing"
13 | "time"
14 | )
15 |
16 | func abortW1(a int, b int) int {
17 | return a + b
18 | }
19 |
20 | func abortW2(ctl *server.TaskCtl, a int) int {
21 | time.Sleep(3 * time.Second)
22 |
23 | f, _ := ctl.IsAbort()
24 | if f {
25 | ctl.Abort("手动中止")
26 | return 0
27 | }
28 | return a * a
29 | }
30 |
31 | func TestAbort(t *testing.T) {
32 | b := brokers.NewLocalBroker()
33 | b2 := backends.NewLocalBackend()
34 | log.YTaskLog.Out = ioutil.Discard
35 |
36 | ser := server.NewServer(
37 | config.NewConfig(
38 | config.Broker(&b),
39 | config.Backend(&b2),
40 | config.ResultExpires(100),
41 | config.Debug(true),
42 | ),
43 | )
44 |
45 | ser.Add("test_g", "abortW1", abortW1)
46 | ser.Add("test_g", "abortW2", abortW2)
47 |
48 | client := ser.GetClient()
49 | ser.Run("test_g", 2, true)
50 |
51 | testAbort1(client, t)
52 | testAbort2(client, t)
53 | ser.Shutdown(context.TODO())
54 |
55 | }
56 |
57 | func testAbort1(client server.Client, t *testing.T) {
58 | id, _ := client.SetTaskCtl(client.RunAfter, 1*time.Second).
59 | Send("test_g", "abortW1", 1, 2)
60 | client.AbortTask(id, 10)
61 | result, _ := client.GetResult(id, time.Second*2, time.Millisecond*300)
62 |
63 | if !result.IsFailure() || result.Status != message.ResultStatus.Abort {
64 | t.Fatalf("!result.IsFailure()")
65 | }
66 | }
67 |
68 | func testAbort2(client server.Client, t *testing.T) {
69 | id, _ := client.Send("test_g", "abortW2", 1, 2)
70 | time.Sleep(1 * time.Second)
71 | client.AbortTask(id, 10)
72 |
73 | time.Sleep(1 * time.Second)
74 | result, _ := client.GetResult(id, time.Second*3, time.Millisecond*100)
75 |
76 | if !result.IsFailure() || result.Status != message.ResultStatus.Abort {
77 | t.Fatalf("!result.IsFailure()")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/v3/test/connPool_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/util"
5 | "testing"
6 | "time"
7 | )
8 |
9 | type testConn struct {
10 | id string
11 | user string
12 | password string
13 | conn *struct{}
14 | }
15 |
16 | func (t *testConn) Dail(timeout int) error {
17 | t.conn = &struct{}{}
18 | return nil
19 | }
20 | func (t *testConn) Close() {
21 | // close
22 | }
23 | func (t *testConn) GetId() string {
24 | return t.id
25 | }
26 |
27 | func (t *testConn) SetId(id string) {
28 | t.id = id
29 | }
30 |
31 | // Clone 复制配置(host,pass等),然后返回指针(注意,必须返回【指针】!!!)
32 | func (t *testConn) Clone() interface{} {
33 | return &testConn{
34 | user: t.user,
35 | password: t.password,
36 | }
37 | }
38 |
39 | func (t *testConn) IsActive() bool {
40 | // ping ...
41 | return true
42 | }
43 |
44 | func TestConnPool(t *testing.T) {
45 | conf := util.NewConnPoolConfig()
46 | conf.IdleTimeout = 3
47 | conf.GetConnTimeout = 1
48 | conf.Size = 2
49 | pool := util.NewConnPool(&testConn{}, conf)
50 |
51 | // test1
52 | c1, err := pool.Get()
53 | if err != nil {
54 | t.Fatal("pool get err", err)
55 | }
56 | c2, err := pool.Get()
57 | if err != nil {
58 | t.Fatal("pool get err", err)
59 | }
60 | _, err = pool.Get()
61 | // 连接池为2,所以这里应该timeout
62 | if err != util.ErrConnPoolGetTimeout {
63 | t.Fatal("pool does not timeout ", err)
64 | }
65 |
66 | pool.Put(c1, false)
67 | pool.Put(c2, false)
68 | // 此时应该能顺利放回
69 | if len(pool.IdleConn) != 2 {
70 | t.Fatal("len(pool.IdleConn)!=2")
71 | }
72 | time.Sleep(3 * time.Second)
73 | // test2
74 | c3, err := pool.Get()
75 | // 原来的c1,c2应该过期了,c3应该是新的
76 | if c1 == c3 || c2 == c3 {
77 | t.Fatal("c1==c3 || c2==c3")
78 | }
79 | pool.Put(c3, true)
80 |
81 | // 因为c1,c2超时, c3设为bad,此时应该没链接
82 | if len(pool.IdleConn) > 0 {
83 | t.Fatal("len(pool.IdleConn)>0")
84 | }
85 |
86 | // test3
87 | c1, err = pool.Get()
88 | pool.Put(c1, false)
89 | c2, err = pool.Get()
90 | pool.Put(c1, false)
91 | // 拿出又放回,此时应该是同一个链接
92 | if c1 != c2 {
93 | t.Fatal("c1!=c2")
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/v3/test/localBackend_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/yerrors"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestRedisBackend(t *testing.T) {
13 | b := backends.NewLocalBackend()
14 | result := message.NewResult("xx123")
15 | result.FuncReturn = nil
16 | b.Activate()
17 | err := b.SetResult(result, 2)
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | r2, err := b.GetResult(result.GetBackendKey())
23 | if err != nil {
24 | t.Fatal(err)
25 | }
26 | if fmt.Sprintf("%v", r2) != fmt.Sprintf("%v", result) {
27 | t.Fatalf("%v != %v", r2, result)
28 | }
29 |
30 | time.Sleep(2 * time.Second)
31 |
32 | _, err = b.GetResult(result.GetBackendKey())
33 | if !yerrors.IsEqual(err, yerrors.ErrTypeNilResult) {
34 | t.Fatal("err != ErrNilResult")
35 |
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/v3/test/localBroker_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/brokers"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "testing"
8 | )
9 |
10 | func TestRedisBroker(t *testing.T) {
11 | broker := brokers.NewLocalBroker()
12 | broker.Activate()
13 | msg := message.NewMessage(message.NewMsgArgs())
14 | msg2 := message.NewMessage(message.NewMsgArgs())
15 |
16 | err := broker.Send("test_redis", msg)
17 | if err != nil {
18 | t.Fatal(err)
19 | }
20 | err = broker.Send("test_redis", msg2)
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 |
25 | m, err := broker.Next("test_redis")
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | if fmt.Sprintf("%v", m) != fmt.Sprintf("%v", msg) {
30 | t.Fatalf("%v != %v", m, msg)
31 | }
32 |
33 | m2, err := broker.Next("test_redis")
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 | if fmt.Sprintf("%v", m2) != fmt.Sprintf("%v", msg2) {
38 | t.Fatalf("%v != %v", m2, msg2)
39 |
40 | }
41 | }
42 |
43 | func TestRedisBrokerLSend(t *testing.T) {
44 | broker := brokers.NewLocalBroker()
45 | broker.Activate()
46 | msg := message.NewMessage(message.NewMsgArgs())
47 | msg.Id = "1"
48 | msg2 := message.NewMessage(message.NewMsgArgs())
49 | msg2.Id = "2"
50 | err := broker.Send("test_redis", msg)
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 | err = broker.LSend("test_redis", msg2)
55 | if err != nil {
56 | t.Fatal(err)
57 | }
58 |
59 | m, err := broker.Next("test_redis")
60 | if err != nil {
61 | t.Fatal(err)
62 | }
63 | if m.Id != msg2.Id {
64 | t.Fatalf("%v != %v", m, msg2)
65 | }
66 |
67 | m2, err := broker.Next("test_redis")
68 | if err != nil {
69 | t.Fatal(err)
70 | }
71 | if m2.Id != msg.Id {
72 | t.Fatalf("%v != %v", m2, msg)
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/v3/test/reflect_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/server"
7 | "github.com/gojuukaze/YTask/v3/util"
8 | "github.com/gojuukaze/YTask/v3/util/yjson"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | type s1 struct {
14 | A int
15 | B int64
16 | C uint64
17 | D float64
18 | F []int64
19 | G []float64
20 | }
21 | type s2 struct {
22 | A int
23 | B string
24 | }
25 | type s3 struct {
26 | S2 s2
27 | C bool
28 | D float64
29 | }
30 |
31 | var (
32 | a int = 1
33 | b int64 = 259933429192721385
34 | c uint = 3
35 | d uint64 = 4
36 | e float32 = 5.5
37 | f float64 = 133.7976931348623
38 | g bool = true
39 | h string = "TestJsonArgs"
40 | i []int = []int{123, 4456, 56756, 234, 123, 4, 5, 6, 7, 812, 123, 345, 756, 678, 7686, 7, 2, 23, 4}
41 | j []int64 = []int64{259933429192721385, 219933429192721385, 4}
42 | k []uint = []uint{44, 56546, 2311, 567}
43 | l []uint64 = []uint64{18446744073709551615, 18446744073709551600, 184467440737095516}
44 | m []float64 = []float64{445535.3321, 133.7976931348623}
45 | n []string = []string{"", "YTask", "is", "good", "!!", " "}
46 | o = s1{A: 12344, B: 4444444444444, C: 123789, D: 123.444456, G: []float64{677.4, 345.78221}}
47 | p = s3{S2: s2{345, "ggggggg"}, C: true, D: 344}
48 | )
49 |
50 | func TestGoVarToYJson(t *testing.T) {
51 |
52 | jsonSlice, _ := util.GoVarsToYJsonSlice(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
53 | var base = []string{fmt.Sprintf("%+v", a),
54 | fmt.Sprintf("%+v", b),
55 | fmt.Sprintf("%+v", c),
56 | fmt.Sprintf("%+v", d),
57 | fmt.Sprintf("%+v", e),
58 | fmt.Sprintf("%+v", f),
59 | fmt.Sprintf("%+v", g),
60 | fmt.Sprintf(`"%+v"`, h),
61 | "[123,4456,56756,234,123,4,5,6,7,812,123,345,756,678,7686,7,2,23,4]",
62 | "[259933429192721385,219933429192721385,4]",
63 | "[44,56546,2311,567]",
64 | "[18446744073709551615,18446744073709551600,184467440737095516]",
65 | "[445535.3321,133.7976931348623]",
66 | `["","YTask","is","good","!!"," "]`,
67 | `{"A":12344,"B":4444444444444,"C":123789,"D":123.444456,"F":null,"G":[677.4,345.78221]}`,
68 | `{"S2":{"A":345,"B":"ggggggg"},"C":true,"D":344}`}
69 | for i, v := range base {
70 | if v != jsonSlice[i] {
71 | t.Fatalf("%+v != %+v", v, jsonSlice[i])
72 | }
73 | }
74 |
75 | }
76 |
77 | func TestGetCallInArgs(t *testing.T) {
78 | testFunc := func(int, int64, uint, uint64, float32, float64, bool, string, []int, []int64, []uint64, []string, s1, s3) {
79 | }
80 |
81 | jsonSlice, _ := util.GoVarsToYJsonSlice(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
82 | inValues, _ := util.GetCallInArgs(reflect.ValueOf(testFunc), jsonSlice, 0)
83 | var base = []reflect.Value{
84 | reflect.ValueOf(a),
85 | reflect.ValueOf(b),
86 | reflect.ValueOf(c),
87 | reflect.ValueOf(d),
88 | reflect.ValueOf(e),
89 | reflect.ValueOf(f),
90 | reflect.ValueOf(g),
91 | reflect.ValueOf(h),
92 | reflect.ValueOf(i),
93 | reflect.ValueOf(j),
94 | reflect.ValueOf(k),
95 | reflect.ValueOf(l),
96 | reflect.ValueOf(m),
97 | reflect.ValueOf(n),
98 | reflect.ValueOf(o),
99 | reflect.ValueOf(p),
100 | }
101 | for i, v := range inValues {
102 | if reflect.DeepEqual(b, base[i]) {
103 | t.Fatalf("%v!=%v", v, base[i])
104 | }
105 | }
106 |
107 | }
108 |
109 | //
110 | func TestGetCallInArgs2(t *testing.T) {
111 | testFunc := func() {}
112 |
113 | inValues, err := util.GetCallInArgs(reflect.ValueOf(testFunc), nil, 0)
114 | if err != nil {
115 | t.Fatal(err)
116 | }
117 | if len(inValues) != 0 {
118 | t.Fatal("len(inValues)!=0")
119 | }
120 |
121 | }
122 |
123 | func swapMessageArgs_TaskCtl(old interface{}) interface{} {
124 | oldB, _ := yjson.YJson.Marshal(old)
125 | switch old.(type) {
126 | case message.MessageArgs:
127 | var newObj = server.TaskCtl{}
128 | yjson.YJson.Unmarshal(oldB, &newObj)
129 | return newObj
130 | case server.TaskCtl:
131 | var newObj = message.MessageArgs{}
132 | yjson.YJson.Unmarshal(oldB, &newObj)
133 | return newObj
134 | }
135 | return nil
136 | }
137 | func TestRunFunc(t *testing.T) {
138 | fun := func(aa, bb int) (int, int, int64, uint, uint64, float32, float64, bool, string, []int64, []uint64, []float64, []string, s1, s3) {
139 | return aa + bb, a, b, c, d, e, f, g, h, j, l, m, n, o, p
140 | }
141 |
142 | w := &server.FuncWorker{
143 | Func: fun,
144 | }
145 | s, _ := util.GoVarsToYJsonSlice(12, 33)
146 | msg := message.NewMessage(message.NewMsgArgs())
147 | msg.FuncArgs = s
148 | result := message.Result{}
149 | ctl := server.NewTaskCtl(msg)
150 |
151 | err := w.Run(&ctl, msg.FuncArgs, &result)
152 | if err != nil {
153 | t.Fatal(err)
154 | }
155 | var base = []interface{}{int(45), a, b, c, d, e, f, g, h, j, l, m, n, o, p}
156 | var (
157 | raa int
158 | ra int
159 | rb int64
160 | rc uint
161 | rd uint64
162 | re float32
163 | rf float64
164 | rg bool
165 | rh string
166 | rj []int64
167 | rl []uint64
168 | rm []float64
169 | rn []string
170 | ro s1
171 | rp s3
172 | )
173 |
174 | var returnV = []interface{}{&raa, &ra, &rb, &rc, &rd, &re, &rf, &rg, &rh, &rj, &rl, &rm, &rn, &ro, &rp}
175 | for i, v := range base {
176 | temp := returnV[i]
177 | err := result.Get(i, temp)
178 | if err != nil {
179 | t.Fatal(err)
180 | }
181 | var realV = reflect.ValueOf(temp).Elem()
182 | if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", realV) {
183 | t.Fatalf("%v != %v", v, realV)
184 | }
185 | }
186 |
187 | err = result.Gets(&raa, &ra, &rb, &rc, &rd, &re, &rf, &rg, &rh, &rj, &rl, &rm, &rn, &ro, &rp)
188 | if err != nil {
189 | t.Fatal(err)
190 | }
191 | returnV = []interface{}{raa, ra, rb, rc, rd, re, rf, rg, rh, rj, rl, rm, rn, ro, rp}
192 | for i, v := range base {
193 | if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", returnV[i]) {
194 | t.Fatalf("%v != %v", v, returnV[i])
195 | }
196 | }
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/v3/test/sortQueue_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "github.com/gojuukaze/YTask/v3/message"
6 | "github.com/gojuukaze/YTask/v3/server"
7 | "math/rand"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func newMsg(t time.Time) message.Message {
13 | c := message.NewMsgArgs()
14 | c.RunTime = t
15 | return message.NewMessage(c)
16 | }
17 |
18 | func TestSortQ(t *testing.T) {
19 | testSortQ(t)
20 |
21 | }
22 | func testSortQ(t *testing.T) {
23 | q := server.NewSortQueue(20)
24 |
25 | for i := 0; i < 20; i++ {
26 | t := time.Now().Add(time.Duration(rand.Intn(600)) * time.Second)
27 | q.Insert(newMsg(t))
28 | }
29 | // 排序是否正确
30 | for i := 0; i < 20; i += 2 {
31 | if !q.Queue[i].RunTimeBefore(q.Queue[i+1].MsgArgs.GetRunTime()) && !q.Queue[i].RunTimeEqual(q.Queue[i+1].MsgArgs.GetRunTime()) {
32 | fmt.Println(q.Queue)
33 | t.Fatal("排序错误")
34 | }
35 | }
36 |
37 | // 插入一个较前的任务时,最后一个任务会出队
38 | temp := q.Queue[19]
39 | pop := q.Insert(newMsg(q.Queue[3].MsgArgs.GetRunTime()))
40 | if !temp.RunTimeEqual(pop.MsgArgs.GetRunTime()) {
41 | t.Fatal("temp!=pop", temp, pop)
42 |
43 | }
44 |
45 | // 插入的任务比最后一个运行时间还旧时,任务不会插入
46 | temp = newMsg(q.Queue[19].MsgArgs.GetRunTime().Add(2000))
47 | pop = q.Insert(temp)
48 | if !temp.RunTimeEqual(pop.MsgArgs.GetRunTime()) {
49 | t.Fatal("temp!=pop", temp, pop)
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/v3/test/v2_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3"
5 | "github.com/gojuukaze/YTask/v3/consts"
6 | "github.com/gojuukaze/YTask/v3/message"
7 | "github.com/gojuukaze/YTask/v3/server"
8 | "github.com/gojuukaze/YTask/v3/util/yjson"
9 | "github.com/json-iterator/go"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | // 测试v2兼容
15 | func TestV2(t *testing.T) {
16 |
17 | s, _ := yjson.YJson.MarshalToString(message.NewMessage(message.NewMsgArgs()))
18 | // v3版本,msg序列化后不应包含TaskCtl
19 | i := strings.Index(s, "TaskCtl")
20 | if i != -1 {
21 | t.Fatalf("%s", s)
22 | }
23 | // 测试v3版本,队列名
24 | su := server.ServerUtils{}
25 | s = su.GetQueueName("a")
26 | i = strings.Index(s, "Query")
27 | if i != -1 {
28 | t.Fatalf("%s", s)
29 | }
30 |
31 | // 改为v2版本名字
32 | ytask.UseV2Name()
33 | // 测试完后改回去,否则运行多个测试时会影响后面测试
34 | defer func() {
35 | consts.UserV2Name = false
36 | yjson.YJson = jsoniter.Config{
37 | EscapeHTML: true,
38 | ValidateJsonRawMessage: true,
39 | TagKey: "yjson",
40 | }.Froze()
41 | }()
42 | s, _ = yjson.YJson.MarshalToString(message.NewMessage(message.NewMsgArgs()))
43 | i = strings.Index(s, "TaskCtl")
44 | if i == -1 {
45 | t.Fatalf("%s", s)
46 | }
47 |
48 | s = su.GetQueueName("a")
49 | i = strings.Index(s, "Query")
50 | if i == -1 {
51 | t.Fatalf("%s", s)
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/v3/test/workflow_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/brokers"
7 | "github.com/gojuukaze/YTask/v3/config"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "github.com/gojuukaze/YTask/v3/message"
10 | "github.com/gojuukaze/YTask/v3/server"
11 | "io/ioutil"
12 | "testing"
13 | "time"
14 | )
15 |
16 | func workflow1(a int, b int) int {
17 | return a + b
18 | }
19 |
20 | func workflow2(a int) int {
21 |
22 | return a * a
23 | }
24 |
25 | func TestWorkflow(t *testing.T) {
26 | b := brokers.NewLocalBroker()
27 | b2 := backends.NewLocalBackend()
28 | log.YTaskLog.Out = ioutil.Discard
29 |
30 | ser := server.NewServer(
31 | config.NewConfig(
32 | config.Broker(&b),
33 | config.Backend(&b2),
34 | config.ResultExpires(100),
35 | config.Debug(true),
36 | ),
37 | )
38 |
39 | ser.Add("test_g", "workflow1", workflow1)
40 | ser.Add("test_g", "workflow2", workflow2)
41 |
42 | client := ser.GetClient()
43 | ser.Run("test_g", 2, true)
44 |
45 | testWorkflow1(client, t)
46 | testWorkflow2(client, t)
47 | testWorkflow3(client, t)
48 | ser.Shutdown(context.TODO())
49 |
50 | }
51 |
52 | func testWorkflow1(client server.Client, t *testing.T) {
53 | id, _ := client.Workflow().
54 | Send("test_g", "workflow1", 1, 2).
55 | Send("test_g", "workflow2").
56 | Done()
57 |
58 | result, err := client.GetResult(id, time.Second*2, time.Millisecond*300)
59 |
60 | a, _ := result.GetInt64(0)
61 | if a != 9 {
62 | t.Fatalf("a is %d , !=3 ; err=%s", a, err)
63 | }
64 | }
65 |
66 | // 测试延时任务执行
67 | func testWorkflow2(client server.Client, t *testing.T) {
68 | id, _ := client.Workflow().
69 | SetTaskCtl(client.RunAfter, 3*time.Second).
70 | Send("test_g", "workflow1", 1, 2).
71 | SetTaskCtl(client.RunAfter, 3*time.Second).
72 | Send("test_g", "workflow2").
73 | Done()
74 |
75 | result, _ := client.GetResult(id, time.Second*2, time.Millisecond*300)
76 | if result.IsFinish() {
77 | t.Fatalf("result.IsFinish()")
78 | }
79 | time.Sleep(1 * time.Second)
80 | result, _ = client.GetResult2(id, time.Second*2, time.Millisecond*300)
81 | if result.Workflow[0][1] != "success" || result.Workflow[1][1] != "waiting" {
82 | t.Fatalf("WorkflowStatus error %v", result.Workflow)
83 | }
84 | result, _ = client.GetResult(id, time.Second*5, time.Millisecond*300)
85 |
86 | a, _ := result.GetInt64(0)
87 | if a != 9 {
88 | t.Fatalf("a is %d , !=3", a)
89 | }
90 | }
91 |
92 | // 测试任务过期
93 | func testWorkflow3(client server.Client, t *testing.T) {
94 | id, _ := client.Workflow().
95 | SetTaskCtl(client.RunAfter, 2*time.Second).
96 | SetTaskCtl(client.ExpireTime, time.Now()).
97 | Send("test_g", "workflow1", 1, 2).
98 | Send("test_g", "workflow2").
99 | Done()
100 |
101 | result, _ := client.GetResult(id, time.Second*4, time.Millisecond*300)
102 | if result.Status != message.ResultStatus.Expired ||
103 | result.Workflow[0][1] != message.WorkflowStatus.Expired {
104 | t.Fatalf("result.Status error %v", result)
105 | }
106 |
107 | id, _ = client.Workflow().
108 | Send("test_g", "workflow1", 1, 2).
109 | SetTaskCtl(client.RunAfter, 2*time.Second).
110 | SetTaskCtl(client.ExpireTime, time.Now()).
111 | Send("test_g", "workflow2").
112 | Done()
113 |
114 | result, _ = client.GetResult(id, time.Second*5, time.Millisecond*300)
115 | if result.Status != message.ResultStatus.Expired ||
116 | result.Workflow[1][1] != message.WorkflowStatus.Expired {
117 | t.Fatalf("result.Status error %v", result)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/v3/test/ytask_2_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/brokers"
7 | "github.com/gojuukaze/YTask/v3/config"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "github.com/gojuukaze/YTask/v3/message"
10 | "github.com/gojuukaze/YTask/v3/server"
11 | "io/ioutil"
12 | "testing"
13 | "time"
14 | )
15 |
16 | func workerTestStatusExpires() int {
17 | time.Sleep(2 * time.Second)
18 | return 233
19 | }
20 |
21 | func workerTestResultExpires() int {
22 | time.Sleep(2 * time.Second)
23 |
24 | return 233
25 | }
26 |
27 | func TestStatusExpires(t *testing.T) {
28 | b := brokers.NewLocalBroker()
29 | b2 := backends.NewLocalBackend()
30 |
31 | ser := server.NewServer(
32 | config.NewConfig(
33 | config.Broker(&b),
34 | config.Backend(&b2),
35 | config.Debug(false),
36 | config.StatusExpires(0),
37 | ),
38 | )
39 | log.YTaskLog.Out = ioutil.Discard
40 |
41 | ser.Add("test_g2", "workerTestStatusExpires", workerTestStatusExpires)
42 | ser.Run("test_g2", 1)
43 | client := ser.GetClient()
44 | id, err := client.Send("test_g2", "workerTestStatusExpires")
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 | _, err = client.GetStatus(id, 1*time.Second, 300*time.Millisecond)
49 | if err == nil {
50 | t.Fatal("err==nill")
51 | }
52 |
53 | result, _ := client.GetResult(id, 3*time.Second, 300*time.Millisecond)
54 | if !result.IsSuccess() {
55 | t.Fatal("!result.IsSuccess()")
56 |
57 | }
58 | a, _ := result.GetInt64(0)
59 |
60 | if int(a) != 233 {
61 | t.Fatal("int(a)!=233")
62 | }
63 | ser.Shutdown(context.TODO())
64 |
65 | }
66 |
67 | func TestResultExpires(t *testing.T) {
68 | b := brokers.NewLocalBroker()
69 | b2 := backends.NewLocalBackend()
70 |
71 | ser := server.NewServer(
72 | config.NewConfig(
73 | config.Broker(&b),
74 | config.Backend(&b2),
75 | config.Debug(false),
76 | config.ResultExpires(0),
77 | ),
78 | )
79 | log.YTaskLog.Out = ioutil.Discard
80 |
81 | ser.Add("test_g2", "workerTestResultExpires", workerTestResultExpires)
82 | ser.Run("test_g2", 1)
83 |
84 | client := ser.GetClient()
85 | id, err := client.Send("test_g2", "workerTestResultExpires")
86 | if err != nil {
87 | t.Fatal(err)
88 | }
89 | status, err := client.GetStatus(id, 1*time.Second, 100*time.Millisecond)
90 | if err != nil {
91 | t.Fatal(err)
92 | }
93 | if status != message.ResultStatus.FirstRunning {
94 | t.Fatal("r1.Status!=message.ResultStatus.FirstRunning", status)
95 |
96 | }
97 |
98 | _, err = client.GetResult(id, 3*time.Second, 300*time.Millisecond)
99 | if err == nil {
100 | t.Fatal("err==nil")
101 |
102 | }
103 | ser.Shutdown(context.TODO())
104 | }
105 |
106 | func TestWorkerExpires(t *testing.T) {
107 | // 测试任务过期
108 | b := brokers.NewLocalBroker()
109 | b2 := backends.NewLocalBackend()
110 |
111 | ser := server.NewServer(
112 | config.NewConfig(
113 | config.Broker(&b),
114 | config.Backend(&b2),
115 | config.Debug(false),
116 | ),
117 | )
118 | log.YTaskLog.Out = ioutil.Discard
119 |
120 | ser.Add("test_we", "w1", workerTestResultExpires)
121 | ser.Run("test_we", 1)
122 |
123 | client := ser.GetClient()
124 | client.Send("test_we", "w1")
125 |
126 | // 这个任务能执行
127 | id, _ := client.SetTaskCtl(client.ExpireTime, time.Now().Add(4*time.Second)).Send("test_we", "w1")
128 | // 这个任务应该过期
129 | id2, _ := client.SetTaskCtl(client.ExpireTime, time.Now().Add(2*time.Second)).Send("test_we", "w1")
130 |
131 | result, _ := client.GetResult(id, 6*time.Second, 300*time.Millisecond)
132 | if !result.IsSuccess() {
133 | t.Fatal("!result.IsSuccess()")
134 |
135 | }
136 | result, _ = client.GetResult(id2, 2*time.Second, 300*time.Millisecond)
137 | if result.IsSuccess() || result.Status != message.ResultStatus.Expired {
138 | t.Fatal("任务状态错误")
139 |
140 | }
141 | ser.Shutdown(context.TODO())
142 | }
143 |
--------------------------------------------------------------------------------
/v3/test/ytask_callback_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "github.com/gojuukaze/YTask/v3/backends"
6 | "github.com/gojuukaze/YTask/v3/brokers"
7 | "github.com/gojuukaze/YTask/v3/config"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "github.com/gojuukaze/YTask/v3/message"
10 | "github.com/gojuukaze/YTask/v3/server"
11 | "io/ioutil"
12 | "testing"
13 | "time"
14 | )
15 |
16 | var workerTestCallbackChan chan workerTestCallbackResult
17 |
18 | type workerTestCallbackResult struct {
19 | a int
20 | s string
21 | result *message.Result
22 | }
23 |
24 | func workerTestCallback(a int, s string) int {
25 |
26 | a = a * 6
27 | return 233
28 | }
29 |
30 | func workerTestCallback2(a int, s string) int {
31 | panic("err")
32 | return 233
33 | }
34 |
35 | func callbackTestCallback(a int, s string, result *message.Result) {
36 |
37 | workerTestCallbackChan <- workerTestCallbackResult{a, s, result}
38 |
39 | }
40 |
41 | func callbackTestCallback2(a int, s string, result *message.Result) {
42 | panic("err")
43 | }
44 |
45 | func TestCallback(t *testing.T) {
46 | workerTestCallbackChan = make(chan workerTestCallbackResult, 2)
47 | b := brokers.NewLocalBroker()
48 | b2 := backends.NewLocalBackend()
49 |
50 | ser := server.NewServer(
51 | config.NewConfig(
52 | config.Broker(&b),
53 | config.Backend(&b2),
54 | config.Debug(true),
55 | ),
56 | )
57 | log.YTaskLog.Out = ioutil.Discard
58 |
59 | ser.Add("test_callback", "workerTestCallback", workerTestCallback, callbackTestCallback)
60 | ser.Add("test_callback", "workerTestCallback2", workerTestCallback2, callbackTestCallback)
61 | ser.Add("test_callback", "workerTestCallback3", workerTestCallback, callbackTestCallback2)
62 |
63 | ser.Run("test_callback", 1)
64 | client := ser.GetClient()
65 | testCallback_1(t, client)
66 | testCallback_2(t, client)
67 | testCallback_3(t, client)
68 |
69 | ser.Shutdown(context.TODO())
70 |
71 | }
72 |
73 | func testCallback_1(t *testing.T, client server.Client) {
74 | _, _ = client.Send("test_callback", "workerTestCallback", 1, "s")
75 |
76 | r := <-workerTestCallbackChan
77 | // worker函数中修改参数不会改变callback中的参数
78 | if r.a != 1 || r.s != "s" {
79 | t.Fatal("r.a!=1 || r.s!=\"s\"")
80 | }
81 | // 验证callback中的result
82 | if r.result.IsSuccess() != true {
83 | t.Fatal("IsSuccess!=true")
84 |
85 | }
86 | r0, _ := r.result.GetInt64(0)
87 | if r0 != 233 {
88 | t.Fatal("r0!=233")
89 | }
90 | }
91 |
92 | func testCallback_2(t *testing.T, client server.Client) {
93 | // worker错误,callback中能获取错误
94 | _, _ = client.Send("test_callback", "workerTestCallback2", 1, "s")
95 |
96 | r := <-workerTestCallbackChan
97 |
98 | if r.result.IsSuccess() != false {
99 | t.Fatal("IsSuccess!=false")
100 | }
101 |
102 | }
103 |
104 | func testCallback_3(t *testing.T, client server.Client) {
105 | // callback错误不会影响结果获取
106 | id, _ := client.Send("test_callback", "workerTestCallback3", 1, "s")
107 | r, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
108 |
109 | if r.IsSuccess() != true {
110 | t.Fatal("IsSuccess!=true")
111 |
112 | }
113 | r0, _ := r.GetInt64(0)
114 | if r0 != 233 {
115 | t.Fatal("r0!=233")
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/v3/test/ytask_multiServer_2_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | //
4 | // cd core
5 | // go test -v -count=1 test/*
6 | //
7 |
8 | import (
9 | "context"
10 | "github.com/gojuukaze/YTask/v3/backends"
11 | "github.com/gojuukaze/YTask/v3/brokers"
12 | "github.com/gojuukaze/YTask/v3/config"
13 | "github.com/gojuukaze/YTask/v3/log"
14 | "github.com/gojuukaze/YTask/v3/server"
15 | "github.com/gojuukaze/YTask/v3/yerrors"
16 | "io/ioutil"
17 | "testing"
18 | "time"
19 | )
20 |
21 | func delayWorker1() int {
22 | return 123
23 | }
24 |
25 | func TestMultit2(t *testing.T) {
26 | b := brokers.NewLocalBroker()
27 | b2 := backends.NewLocalBackend()
28 |
29 | ser := server.NewServer(
30 | config.NewConfig(
31 | config.Broker(&b),
32 | config.Backend(&b2),
33 | config.Debug(true),
34 | config.EnableDelayServer(true),
35 | ),
36 | )
37 | log.YTaskLog.Out = ioutil.Discard
38 |
39 | ser.Add("TestMulti2Group", "delayWorker1", delayWorker1)
40 |
41 | client := ser.GetClient()
42 |
43 | ser.Run("TestMulti2Group", 2)
44 |
45 | testMulti2_1(t, client)
46 | testMulti2_2(t, client)
47 | testMulti2_3(t, client)
48 |
49 | ser.Shutdown(context.TODO())
50 |
51 | }
52 |
53 | func testMulti2_1(t *testing.T, client server.Client) {
54 | // 测试两种任务能正常执行
55 |
56 | id, _ := client.Send("TestMulti2Group", "delayWorker1")
57 | id2, _ := client.SetTaskCtl(client.RunAfter, 100*time.Millisecond).Send("TestMulti2Group", "delayWorker1")
58 |
59 | _, err := client.GetStatus(id, 1*time.Second, 100*time.Millisecond)
60 | if err != nil {
61 | t.Fatal("err=", err)
62 | }
63 |
64 | _, err = client.GetResult(id2, 2*time.Second, 100*time.Millisecond)
65 | if err != nil {
66 | t.Fatal("err=", err)
67 | }
68 |
69 | }
70 |
71 | func testMulti2_2(t *testing.T, client server.Client) {
72 | // 测试延时任务是否延时执行
73 |
74 | client.Send("TestMulti2Group", "delayWorker1")
75 |
76 | id2, _ := client.SetTaskCtl(client.RunAfter, 2*time.Second).Send("TestMulti2Group", "delayWorker1")
77 |
78 | _, err := client.GetResult(id2, 1*time.Second, 300*time.Millisecond)
79 | if !yerrors.IsEqual(err, yerrors.ErrTypeTimeOut) {
80 | t.Fatal("err!=yerrors.ErrTypeTimeOut")
81 | }
82 | time.Sleep(1 * time.Second)
83 |
84 | _, err = client.GetResult(id2, 1*time.Second, 300*time.Millisecond)
85 | if err != nil {
86 | t.Fatal("err=", err)
87 | }
88 |
89 | }
90 |
91 | func testMulti2_3(t *testing.T, client server.Client) {
92 | // 测试多个延时任务的执行顺序
93 |
94 | id2, _ := client.SetTaskCtl(client.RunAfter, 3*time.Second).Send("TestMulti2Group", "delayWorker1")
95 |
96 | id, _ := client.SetTaskCtl(client.RunAfter, 100*time.Millisecond).Send("TestMulti2Group", "delayWorker1")
97 |
98 | _, err := client.GetResult(id, 500*time.Millisecond, 100*time.Millisecond)
99 | if err != nil {
100 | t.Fatal("err=", err)
101 | }
102 | _, err = client.GetResult(id2, 1*time.Second, 100*time.Millisecond)
103 | if !yerrors.IsEqual(err, yerrors.ErrTypeTimeOut) {
104 | t.Fatal("err!=yerrors.ErrTypeTimeOut ", err)
105 | }
106 |
107 | time.Sleep(2 * time.Second)
108 |
109 | _, err = client.GetResult(id2, 1*time.Second, 300*time.Millisecond)
110 | if err != nil {
111 | t.Fatal("err=", err)
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/v3/test/ytask_multiServer_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | //
4 | // cd core
5 | // go test -v -count=1 test/*
6 | //
7 |
8 | import (
9 | "context"
10 | "github.com/gojuukaze/YTask/v3/backends"
11 | "github.com/gojuukaze/YTask/v3/brokers"
12 | "github.com/gojuukaze/YTask/v3/config"
13 | "github.com/gojuukaze/YTask/v3/log"
14 | "github.com/gojuukaze/YTask/v3/server"
15 | "github.com/gojuukaze/YTask/v3/yerrors"
16 | "io/ioutil"
17 | "testing"
18 | "time"
19 | )
20 |
21 | func multiWorker1() int {
22 | time.Sleep(2 * time.Second)
23 | return 123
24 | }
25 |
26 | func multiWorker2() int {
27 | return 123
28 | }
29 |
30 | func TestMulti(t *testing.T) {
31 | b := brokers.NewLocalBroker()
32 | b2 := backends.NewLocalBackend()
33 |
34 | ser := server.NewServer(
35 | config.NewConfig(
36 | config.Broker(&b),
37 | config.Backend(&b2),
38 | config.Debug(true),
39 | ),
40 | )
41 | log.YTaskLog.Out = ioutil.Discard
42 |
43 | ser.Add("test_group1", "multiWorker1", multiWorker1)
44 | ser.Add("test_group2", "multiWorker2", multiWorker2)
45 |
46 | ser.Run("test_group1", 1)
47 | ser.Run("test_group2", 1)
48 |
49 | client := ser.GetClient()
50 |
51 | testMulti1(t, client)
52 | testMulti2(t, client)
53 |
54 | ser.Shutdown(context.TODO())
55 |
56 | }
57 |
58 | func testMulti1(t *testing.T, client server.Client) {
59 |
60 | client.Send("test_group1", "multiWorker1")
61 | id, _ := client.Send("test_group1", "multiWorker1")
62 |
63 | // 连续发两次,因为并发是1,此时第二次应该还没运行
64 | _, err := client.GetStatus(id, 1*time.Second, 300*time.Millisecond)
65 | if !yerrors.IsEqual(err, yerrors.ErrTypeTimeOut) {
66 | t.Fatal("err!=yerrors.ErrTypeTimeOut")
67 | }
68 |
69 | _, err = client.GetResult(id, 4*time.Second, 300*time.Millisecond)
70 | if err != nil {
71 | t.Fatal("err!=nil")
72 | }
73 |
74 | }
75 |
76 | func testMulti2(t *testing.T, client server.Client) {
77 |
78 | client.Send("test_group1", "multiWorker1")
79 | id, _ := client.Send("test_group2", "multiWorker2")
80 |
81 | result, err := client.GetResult(id, 1*time.Second, 300*time.Millisecond)
82 | if err != nil {
83 | t.Fatal("err!=nil")
84 | }
85 | r, _ := result.GetInt64(0)
86 | if r != 123 {
87 | t.Fatal("r!=123")
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/v3/test/ytask_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | //
4 | // cd core
5 | // go test -v -count=1 test/*
6 | //
7 |
8 | import (
9 | "context"
10 | "errors"
11 | "fmt"
12 | "github.com/gojuukaze/YTask/v3/backends"
13 | "github.com/gojuukaze/YTask/v3/brokers"
14 | "github.com/gojuukaze/YTask/v3/config"
15 | "github.com/gojuukaze/YTask/v3/log"
16 | "github.com/gojuukaze/YTask/v3/server"
17 | "io/ioutil"
18 | "testing"
19 | "time"
20 | )
21 |
22 | type User struct {
23 | Id int
24 | Name string
25 | }
26 |
27 | func worker1() {
28 | }
29 |
30 | func worker2(a int, b float32, c uint64, d bool) (float32, uint64, bool) {
31 | return float32(a) + b, c, d
32 | }
33 |
34 | func worker3(user User, ids []int, names []string) []User {
35 | var r = make([]User, 0)
36 | r = append(r, user)
37 | for i := range ids {
38 | r = append(r, User{
39 | Id: ids[i],
40 | Name: names[i],
41 | })
42 | }
43 | return r
44 | }
45 | func workerTestRetry1() {
46 | panic("test retry")
47 | }
48 |
49 | func workerTestRetry2(ctl *server.TaskCtl, a int) int {
50 | if ctl.GetRetryCount() == 3 {
51 | panic("test retry")
52 | } else if ctl.GetRetryCount() == 2 {
53 | ctl.Retry(errors.New("test retry 2"))
54 | return 0
55 | }
56 | return a + ctl.GetRetryCount()
57 | }
58 | func TestYTask1(t *testing.T) {
59 | b := brokers.NewLocalBroker()
60 | b2 := backends.NewLocalBackend()
61 |
62 | ser := server.NewServer(
63 | config.NewConfig(
64 | config.Broker(&b),
65 | config.Backend(&b2),
66 | config.Debug(true),
67 | ),
68 | )
69 | log.YTaskLog.Out = ioutil.Discard
70 |
71 | ser2 := ser
72 | ser.Add("test_g", "worker1", worker1)
73 | ser.Add("test_g", "worker2", worker2)
74 | ser.Add("test_g", "worker3", worker3)
75 |
76 | ser.Add("test_g", "workerTestRetry1", workerTestRetry1)
77 | ser.Add("test_g", "workerTestRetry2", workerTestRetry2)
78 |
79 | ser.Run("test_g", 3)
80 | testWorker1(ser2, t)
81 | testWorker2(ser2, t)
82 | testWorker3(ser2, t)
83 |
84 | testRetry1(ser2, t)
85 | testRetry2(ser2, t)
86 |
87 | ser.Shutdown(context.TODO())
88 |
89 | }
90 |
91 | func testWorker1(ser server.Server, t *testing.T) {
92 | client := ser.GetClient()
93 |
94 | id, err := client.Send("test_g", "worker1")
95 | if err != nil {
96 | t.Fatal(err)
97 | }
98 | result, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
99 |
100 | if !result.IsSuccess() {
101 | t.Fatal("result is not success")
102 | }
103 |
104 | }
105 |
106 | func testWorker2(ser server.Server, t *testing.T) {
107 | client := ser.GetClient()
108 | var (
109 | a int = 12
110 | b float32 = 22.1
111 | c uint64 = 18446744073709551610
112 | d = true
113 | )
114 | id, err := client.Send("test_g", "worker2", a, b, c, d)
115 | if err != nil {
116 | t.Fatal(err)
117 | }
118 | result, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
119 |
120 | if !result.IsSuccess() {
121 | t.Fatal("result is not success")
122 | }
123 |
124 | r1, _ := result.GetFloat64(0)
125 | if float32(a)+b != float32(r1) {
126 | t.Fatalf("%v != %v", float32(a)+b, float32(r1))
127 | }
128 |
129 | r2, _ := result.GetUint64(1)
130 | if c != r2 {
131 | t.Fatalf("%v != %v", c, r2)
132 | }
133 |
134 | r3, _ := result.GetBool(2)
135 | if d != r3 {
136 | t.Fatalf("%v != %v", c, r3)
137 | }
138 | }
139 |
140 | func testWorker3(ser server.Server, t *testing.T) {
141 | client := ser.GetClient()
142 |
143 | id, err := client.Send("test_g", "worker3",
144 | User{
145 | Id: 1,
146 | Name: "a",
147 | },
148 | []int{233, 44},
149 | []string{"bb", "cc"})
150 | if err != nil {
151 | t.Fatal(err)
152 | }
153 | result, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
154 |
155 | if !result.IsSuccess() {
156 | t.Fatal("result is not success")
157 | }
158 | var base = []User{{1, "a"}, {233, "bb"}, {44, "cc"}}
159 | var r []User
160 | err = result.Get(0, &r)
161 |
162 | if err != nil {
163 | t.Fatal(err)
164 | }
165 | if fmt.Sprint(base) != fmt.Sprint(r) {
166 | t.Fatalf("%v !=%v", base, r)
167 | }
168 | }
169 |
170 | func testRetry1(ser server.Server, t *testing.T) {
171 | client := ser.GetClient()
172 |
173 | id, err := client.SetTaskCtl(client.RetryCount, 5).Send("test_g", "workerTestRetry1")
174 | if err != nil {
175 | t.Fatal(err)
176 | }
177 | result, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
178 |
179 | if result.IsSuccess() {
180 | t.Fatal("result is success")
181 | }
182 |
183 | if result.RetryCount != 5 {
184 | t.Fatal("result.RetryCount!=5")
185 |
186 | }
187 |
188 | }
189 |
190 | func testRetry2(ser server.Server, t *testing.T) {
191 | client := ser.GetClient()
192 |
193 | id, err := client.Send("test_g", "workerTestRetry2", 6)
194 | if err != nil {
195 | t.Fatal(err)
196 | }
197 | result, _ := client.GetResult(id, 2*time.Second, 300*time.Millisecond)
198 |
199 | if !result.IsSuccess() {
200 | t.Fatal("result is not success")
201 | }
202 | r1, _ := result.GetInt64(0)
203 | if int(r1) != 6+1 {
204 | t.Fatal("int(r1)!=6+1")
205 |
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------
/v3/util/common.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/md5"
5 | "crypto/sha1"
6 | "encoding/hex"
7 | )
8 |
9 | func GetStrMd5(s string) string {
10 | h := md5.New()
11 | h.Write([]byte(s))
12 | md5Data := h.Sum([]byte(""))
13 | return hex.EncodeToString(md5Data)
14 | }
15 |
16 | func GetStrSha1(data string) string {
17 | sha1 := sha1.New()
18 | sha1.Write([]byte(data))
19 | return hex.EncodeToString(sha1.Sum([]byte("")))
20 | }
21 |
22 | func Min(a, b int) int {
23 | if a < b {
24 | return a
25 | }
26 | return b
27 | }
28 |
29 | func Max(a, b int) int {
30 | if a > b {
31 | return a
32 | }
33 | return b
34 | }
35 |
--------------------------------------------------------------------------------
/v3/util/pool.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "sync"
8 | "time"
9 | )
10 |
11 | var (
12 | ErrConnPoolGetTimeout = errors.New("ErrConnPoolGetTimeout")
13 | ErrConnPoolClosed = errors.New("ErrConnPoolClosed")
14 | ErrNoIdleConn = errors.New("NoIdleConn")
15 |
16 | IdleTimeout = 60
17 | GetConnTimeout = 60
18 | GetConnSleepTime = 100 * time.Millisecond
19 | ReDailTimes = 2
20 | )
21 |
22 | type ConnInterface interface {
23 | Dail(timeout int) error
24 | Close()
25 | GetId() string
26 | // SetId 注意,这个id要存下来。这个id其实是上次使用的时间戳,用于判断是否过期。格式是: {random_int}-{time}
27 | SetId(id string)
28 | // Clone 复制配置(host,pass等),然后返回指针(注意,必须返回【指针】!!!)
29 | Clone() interface{}
30 | // IsActive 判断链接是否存活,通常是调用ping。
31 | // 这个操作会浪费一点时间,若觉得不必要或是没有ping方法 也可以直接返回true
32 | // 如果直接返回true,使用pool.Get() 返回的链接时注意判断错误,因为链接可能因各种情况关闭。
33 | // 注意!!就算链接已经关闭,仍然需要调用pool.Put() (此时isBad为true),然后再调用pool.Get()重新获取链接
34 | IsActive() bool
35 | }
36 |
37 | type ConnPoolConfig struct {
38 | Size int
39 | IdleTimeout int // 链接超时时间. Second
40 | GetConnTimeout int
41 | GetConnSleepTime time.Duration
42 | ReDailTimes int // Dail报错的重试次数
43 | }
44 |
45 | type ConnPool struct {
46 | Conf ConnPoolConfig
47 | Conn ConnInterface
48 | Mu sync.Mutex
49 | IdleConn map[string]interface{} // 用slice会频繁修改,chan比较适合跨协程场景,因此用map
50 | Locked bool
51 | NumOpen int
52 | IsClose bool
53 | }
54 |
55 | func NewConnPoolConfig() ConnPoolConfig {
56 | return ConnPoolConfig{
57 | Size: 10,
58 | IdleTimeout: IdleTimeout,
59 | GetConnTimeout: GetConnTimeout,
60 | GetConnSleepTime: GetConnSleepTime,
61 | ReDailTimes: ReDailTimes,
62 | }
63 | }
64 | func NewConnPool(conn ConnInterface, conf ConnPoolConfig) ConnPool {
65 | return ConnPool{
66 | Conf: conf,
67 | Conn: conn,
68 | IdleConn: make(map[string]interface{}),
69 | NumOpen: 0,
70 | Locked: false,
71 | }
72 | }
73 | func (p *ConnPool) Lock() {
74 | p.Mu.Lock()
75 | p.Locked = true
76 | }
77 | func (p *ConnPool) UnLock() {
78 | p.Locked = false
79 | p.Mu.Unlock()
80 | }
81 |
82 | func (p *ConnPool) Get() (interface{}, error) {
83 | if p.IsClose {
84 | return nil, ErrConnPoolClosed
85 | }
86 | ctx, _ := context.WithTimeout(context.Background(), time.Duration(p.Conf.GetConnTimeout)*time.Second)
87 | for {
88 | conn, err := p.get()
89 | if err == nil {
90 | return conn, err
91 | } else if err != ErrNoIdleConn {
92 | return nil, err
93 | }
94 | select {
95 | case <-ctx.Done():
96 | return nil, ErrConnPoolGetTimeout
97 | case <-time.After(p.Conf.GetConnSleepTime):
98 |
99 | }
100 | }
101 | }
102 |
103 | func (p *ConnPool) get() (interface{}, error) {
104 | p.Lock()
105 | defer p.UnLock()
106 |
107 | for id, conn := range p.IdleConn {
108 | isBad := p.IsConnTimeout(conn) || !conn.(ConnInterface).IsActive()
109 | delete(p.IdleConn, id)
110 | if !isBad {
111 | p.UpdateConnTime(conn)
112 | return conn, nil
113 | } else {
114 | p.CloseConn(conn)
115 | }
116 | }
117 | // 走到这说明要创建新的链接
118 | if p.NumOpen == p.Conf.Size {
119 | return nil, ErrNoIdleConn
120 | }
121 | return p.NewConn()
122 |
123 | }
124 |
125 | func (p *ConnPool) IsConnTimeout(conn interface{}) bool {
126 | _, t := p.DecodeId(conn.(ConnInterface).GetId())
127 | // +1提前一秒过期
128 | return time.Now().UnixMilli()+1000 > t+int64(p.Conf.IdleTimeout)*1000
129 |
130 | }
131 |
132 | func (p *ConnPool) NewConn() (interface{}, error) {
133 | if !p.Locked {
134 | panic("Need to lock before use NewConn")
135 | }
136 | newConn := p.Conn.Clone()
137 | var err error
138 | for num := p.Conf.ReDailTimes; num > 0; num-- {
139 | // Timeout + 1 防止在时间临界点时pool.Get()获取的链接无法使用
140 | err = newConn.(ConnInterface).Dail(p.Conf.IdleTimeout + 1)
141 | if err == nil {
142 | break
143 | }
144 | time.Sleep(p.Conf.GetConnSleepTime)
145 | }
146 | if err != nil {
147 | return nil, err
148 | }
149 | newConn.(ConnInterface).SetId(p.GenId(-1))
150 | p.NumOpen++
151 | return newConn, err
152 |
153 | }
154 |
155 | func (p *ConnPool) GenId(randomNum int) string {
156 | if !p.Locked {
157 | panic("Need to lock before use GenId")
158 | }
159 | if randomNum == -1 {
160 | randomNum = p.NumOpen
161 | }
162 | return fmt.Sprintf("%v-%v", randomNum, time.Now().UnixMilli())
163 |
164 | }
165 |
166 | func (p *ConnPool) DecodeId(id string) (n int, t int64) {
167 | fmt.Sscanf(id, "%d-%d", &n, &t)
168 | return
169 | }
170 |
171 | func (p *ConnPool) CloseConn(conn interface{}) {
172 | if !p.Locked {
173 | panic("Need to lock before use GenId")
174 | }
175 | delete(p.IdleConn, conn.(ConnInterface).GetId())
176 | p.NumOpen--
177 | conn.(ConnInterface).Close()
178 | }
179 |
180 | func (p *ConnPool) UpdateConnTime(conn interface{}) {
181 | n, _ := p.DecodeId(conn.(ConnInterface).GetId())
182 | newId := p.GenId(n)
183 | conn.(ConnInterface).SetId(newId)
184 | }
185 |
186 | // Put
187 | // isBadConn: 用户Get获取的链接时,如果链接已关闭或者因其他原因不能在用,则isBadConn为true
188 | func (p *ConnPool) Put(conn interface{}, isBadConn bool) {
189 | p.Lock()
190 | defer p.UnLock()
191 | if isBadConn || p.IsConnTimeout(conn) {
192 | p.CloseConn(conn)
193 | } else {
194 | p.UpdateConnTime(conn)
195 | p.IdleConn[conn.(ConnInterface).GetId()] = conn
196 | }
197 |
198 | }
199 |
200 | func (p *ConnPool) Close() {
201 | p.Lock()
202 | defer p.UnLock()
203 | for _, conn := range p.IdleConn {
204 | p.CloseConn(conn)
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/v3/util/reflect.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/util/yjson"
5 | "reflect"
6 | )
7 |
8 | func GoVarToYJson(v interface{}) (string, error) {
9 | b, err := yjson.YJson.Marshal(v)
10 | return string(b), err
11 | }
12 |
13 | func GoVarsToYJsonSlice(args ...interface{}) ([]string, error) {
14 | var r = make([]string, len(args))
15 | for i, v := range args {
16 | yJsonStr, err := GoVarToYJson(v)
17 | if err != nil {
18 | return r, err
19 | }
20 | r[i] = yJsonStr
21 | }
22 | return r, nil
23 | }
24 |
25 | func GoValuesToYJsonSlice(values []reflect.Value) ([]string, error) {
26 | var r = make([]string, len(values))
27 | for i, v := range values {
28 | s, err := GoVarToYJson(v.Interface())
29 | if err != nil {
30 | return r, err
31 | }
32 | r[i] = s
33 |
34 | }
35 | return r, nil
36 | }
37 |
38 | func GetCallInArgs(funcValue reflect.Value, funcArgs []string, inStart int) ([]reflect.Value, error) {
39 |
40 | var inArgs = make([]reflect.Value, funcValue.Type().NumIn()-inStart)
41 | for i := inStart; i < funcValue.Type().NumIn(); i++ {
42 | if i-inStart >= len(funcArgs) {
43 | break
44 | }
45 | inType := funcValue.Type().In(i)
46 | inValue := reflect.New(inType)
47 | // yjson to go value
48 | err := yjson.YJson.Unmarshal([]byte(funcArgs[i-inStart]), inValue.Interface())
49 |
50 | if err != nil {
51 | return inArgs, err
52 | }
53 | inArgs[i-inStart] = inValue.Elem()
54 | }
55 | return inArgs, nil
56 | }
57 |
--------------------------------------------------------------------------------
/v3/util/yjson/yjson.go:
--------------------------------------------------------------------------------
1 | package yjson
2 |
3 | import jsoniter "github.com/json-iterator/go"
4 |
5 | var YJson = jsoniter.Config{
6 | EscapeHTML: true,
7 | ValidateJsonRawMessage: true,
8 | TagKey: "yjson",
9 | }.Froze()
10 |
11 | func SetDebug() {
12 | YJson = jsoniter.Config{
13 | EscapeHTML: true,
14 | ValidateJsonRawMessage: true,
15 | TagKey: "yjson",
16 | SortMapKeys: true,
17 | }.Froze()
18 | }
19 |
--------------------------------------------------------------------------------
/v3/yerrors/errors.go:
--------------------------------------------------------------------------------
1 | package yerrors
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | const (
8 | ErrTypeEmptyQuery = 1 // 这个之前拼错了,为了兼容保留下来
9 | ErrTypeEmptyQueue = 1 // 队列为空, broker获取任务时用到
10 | ErrTypeUnsupportedType = 2 // 不支持此参数类型
11 | ErrTypeOutOfRange = 3 // 暂时没用
12 | ErrTypeNilResult = 4 // 任务结果为空
13 | ErrTypeTimeOut = 5 // broker,backend超时
14 | ErrTypeServerStop = 6 // 服务已停止
15 |
16 | ErrTypeSendMsg = 7 // 通过broker发送消息失败,目前工作流发送下一个任务时会用到
17 | ErrTypeNilBackend = 8
18 |
19 | ErrTypeAbortTask = 9
20 | )
21 |
22 | func IsEqual(err error, errType int) bool {
23 | yerr, ok := err.(YTaskError)
24 | if !ok {
25 | return ok
26 | }
27 | if yerr.Type() == errType {
28 | return true
29 | }
30 | return false
31 | }
32 |
33 | type YTaskError interface {
34 | Error() string
35 | Type() int
36 | }
37 |
38 | type ErrEmptyQueue struct {
39 | }
40 |
41 | func (e ErrEmptyQueue) Error() string {
42 | return "YTask: empty queue"
43 | }
44 |
45 | func (e ErrEmptyQueue) Type() int {
46 | return ErrTypeEmptyQueue
47 | }
48 |
49 | type ErrUnsupportedType struct {
50 | T string
51 | }
52 |
53 | func (e ErrUnsupportedType) Error() string {
54 | return fmt.Sprintf("YTask: UnsupportedType: %s", e.T)
55 | }
56 |
57 | func (e ErrUnsupportedType) Type() int {
58 | return ErrTypeUnsupportedType
59 | }
60 |
61 | type ErrOutOfRange struct {
62 | }
63 |
64 | func (e ErrOutOfRange) Error() string {
65 | return "YTask: index out of range"
66 | }
67 |
68 | func (e ErrOutOfRange) Type() int {
69 | return ErrTypeOutOfRange
70 | }
71 |
72 | type ErrNilResult struct {
73 | }
74 |
75 | func (e ErrNilResult) Error() string {
76 | return "YTask: nil result"
77 | }
78 |
79 | func (e ErrNilResult) Type() int {
80 | return ErrTypeNilResult
81 | }
82 |
83 | type ErrTimeOut struct {
84 | }
85 |
86 | func (e ErrTimeOut) Error() string {
87 | return "YTask: timeout"
88 | }
89 |
90 | func (e ErrTimeOut) Type() int {
91 | return ErrTypeTimeOut
92 | }
93 |
94 | type ErrServerStop struct {
95 | }
96 |
97 | func (e ErrServerStop) Error() string {
98 | return "YTask: server stop"
99 | }
100 |
101 | func (e ErrServerStop) Type() int {
102 | return ErrTypeServerStop
103 | }
104 |
105 | type ErrSendMsg struct {
106 | Msg string
107 | }
108 |
109 | func (e ErrSendMsg) Error() string {
110 | return fmt.Sprintf("YTask: send msg error [%s]", e.Msg)
111 | }
112 |
113 | func (e ErrSendMsg) Type() int {
114 | return ErrTypeSendMsg
115 | }
116 |
117 | type ErrNilBackend struct {
118 | }
119 |
120 | func (e ErrNilBackend) Error() string {
121 | return "YTask: Nil Backend"
122 | }
123 |
124 | func (e ErrNilBackend) Type() int {
125 | return ErrTypeNilBackend
126 | }
127 |
128 | type ErrAbortTask struct {
129 | Msg string
130 | }
131 |
132 | func (e ErrAbortTask) Error() string {
133 | return fmt.Sprintf("YTask: abort task [%s]", e.Msg)
134 | }
135 |
136 | func (e ErrAbortTask) Type() int {
137 | return ErrTypeAbortTask
138 | }
139 |
--------------------------------------------------------------------------------
/v3/ytask.go:
--------------------------------------------------------------------------------
1 | package ytask
2 |
3 | import (
4 | "github.com/gojuukaze/YTask/v3/backends"
5 | "github.com/gojuukaze/YTask/v3/brokers"
6 | "github.com/gojuukaze/YTask/v3/config"
7 | "github.com/gojuukaze/YTask/v3/consts"
8 | "github.com/gojuukaze/YTask/v3/log"
9 | "github.com/gojuukaze/YTask/v3/server"
10 | "github.com/gojuukaze/YTask/v3/util/yjson"
11 | "github.com/json-iterator/go"
12 | )
13 |
14 | var (
15 | Server = iServer{}
16 | Logger = iLogger{}
17 | Config = iConfig{}
18 | )
19 |
20 | type iServer struct {
21 | }
22 |
23 | func (i iServer) NewServer(setConfigFunc ...config.SetConfigFunc) server.Server {
24 | c := config.NewConfig(setConfigFunc...)
25 | return server.NewServer(c)
26 | }
27 |
28 | type iConfig struct {
29 | }
30 |
31 | func (i iConfig) Broker(b brokers.BrokerInterface) config.SetConfigFunc {
32 | return config.Broker(b)
33 | }
34 |
35 | func (i iConfig) Backend(b backends.BackendInterface) config.SetConfigFunc {
36 | return config.Backend(b)
37 | }
38 | func (i iConfig) Debug(debug bool) config.SetConfigFunc {
39 | return config.Debug(debug)
40 | }
41 |
42 | func (i iConfig) EnableDelayServer(enable bool) config.SetConfigFunc {
43 | return config.EnableDelayServer(enable)
44 | }
45 |
46 | func (i iConfig) DelayServerQueueSize(size int) config.SetConfigFunc {
47 | return config.DelayServerQueueSize(size)
48 | }
49 |
50 | // default: 1 day
51 | // task status expires in ex seconds, -1:forever,
52 | func (i iConfig) StatusExpires(ex int) config.SetConfigFunc {
53 | return config.StatusExpires(ex)
54 | }
55 |
56 | // default: 1day
57 | // task result expires in ex seconds, -1:forever,
58 | func (i iConfig) ResultExpires(ex int) config.SetConfigFunc {
59 | return config.ResultExpires(ex)
60 | }
61 |
62 | type iLogger struct {
63 | }
64 |
65 | func (i iLogger) NewYTaskLogger() log.LoggerInterface {
66 | return log.NewYTaskLogger(log.YTaskLog)
67 | }
68 |
69 | func UseV2Name() {
70 | consts.UserV2Name = true
71 | yjson.YJson = jsoniter.Config{
72 | EscapeHTML: true,
73 | ValidateJsonRawMessage: true,
74 | TagKey: "v2JsonName",
75 | }.Froze()
76 | }
77 |
--------------------------------------------------------------------------------