├── .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 | architecture_diagram 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 | ![](https://gitee.com/gojuukaze/liteAuth/raw/master/shang.jpg) 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 | architecture_diagram 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 | ![](https://gitee.com/gojuukaze/liteAuth/raw/master/shang.jpg) 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 | --------------------------------------------------------------------------------