Skip to content

Commit fd47051

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents e949afe + 2eb6f13 commit fd47051

File tree

12 files changed

+344
-6
lines changed

12 files changed

+344
-6
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ before_script:
3636
script:
3737
- composer analyse
3838
- composer test
39-
- composer start-test-server & sleep 5 && composer test-go
39+
- composer test-go

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# GoTask
22

3-
[![Build Status](https://travis-ci.org/Reasno/gotask.svg?branch=master)](https://travis-ci.org/Reasno/gotask)
3+
[![Build Status](https://travis-ci.org/hyperf/gotask.svg?branch=master)](https://travis-ci.org/hyperf/gotask)
44

55
GoTask通过[Swoole进程管理功能](https://wiki.swoole.com/#/process)启动Go进程作为Swoole主进程边车(Sidecar),利用[进程通讯](https://wiki.swoole.com/#/learn?id=%e4%bb%80%e4%b9%88%e6%98%afipc)将任务投递给边车处理并接收返回值。可以理解为Go版的Swoole TaskWorker。
66

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"require": {
2121
"php": ">=7.2",
2222
"ext-swoole": ">=4.4",
23+
"hyperf/command": "^1.1",
24+
"hyperf/devtool": "^1.1",
2325
"hyperf/pool": "^1.1",
2426
"hyperf/process": "^1.1",
2527
"spiral/goridge": "^2.3"
@@ -38,7 +40,7 @@
3840
"scripts": {
3941
"test": "go build -o app example/*.go && phpunit -c phpunit.xml --colors=always",
4042
"start-test-server": "php tests/TestServer.php",
41-
"test-go": "go test ./...",
43+
"test-go": "/bin/bash -c 'php tests/TestServer.php & sleep 5 && go test ./...'",
4244
"analyse": "phpstan analyse --memory-limit 300M -l 0 ./src",
4345
"cs-fix": "php-cs-fixer fix $1"
4446
},

example/sidecar.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ func (a *App) HelloError(name interface{}, r *interface{}) error {
4949
}
5050

5151
func main() {
52-
gotask.Register(new(App))
52+
if err := gotask.Register(new(App)); err != nil {
53+
log.Fatalln(err)
54+
}
5355
if err := gotask.Run(); err != nil {
5456
log.Fatalln(err)
5557
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.13
44

55
require (
66
github.com/fatih/pool v3.0.0+incompatible
7+
github.com/json-iterator/go v1.1.9
78
github.com/oklog/run v1.1.0
89
github.com/pkg/errors v0.9.1
910
github.com/spiral/goridge/v2 v2.3.1-0.20200327094950-280bd2861b57

pkg/gotask/common.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
package gotask
22

3-
import "strings"
3+
import (
4+
"reflect"
5+
"strings"
6+
)
7+
8+
type Function struct {
9+
Name string
10+
Raw bool
11+
ParamModifier string
12+
ResultModifier string
13+
}
14+
type Class struct {
15+
Name string
16+
Functions []Function
17+
}
418

519
func parseAddr(addr string) (string, string) {
620
var network string
@@ -11,3 +25,65 @@ func parseAddr(addr string) (string, string) {
1125
}
1226
return network, addr
1327
}
28+
29+
func reflectStruct(i interface{}) *Class {
30+
var val reflect.Type
31+
if reflect.TypeOf(i).Kind() != reflect.Ptr {
32+
val = reflect.PtrTo(reflect.TypeOf(i))
33+
} else {
34+
val = reflect.TypeOf(i)
35+
}
36+
functions := make([]Function, 0)
37+
for i := 0; i < val.NumMethod(); i++ {
38+
f := Function{
39+
Name: val.Method(i).Name,
40+
Raw: val.Method(i).Type.In(1) == reflect.TypeOf([]byte{}),
41+
ParamModifier: getModifier(val.Method(i).Type.In(1)),
42+
ResultModifier: getModifier(val.Method(i).Type.In(2).Elem()),
43+
}
44+
functions = append(functions, f)
45+
}
46+
return &Class{
47+
Name: val.Elem().Name(),
48+
Functions: functions,
49+
}
50+
}
51+
52+
func getModifier(t reflect.Type) string {
53+
if t == reflect.TypeOf([]byte{}) {
54+
return "string"
55+
}
56+
if t.Kind() == reflect.Int {
57+
return "int"
58+
}
59+
if t.Kind() == reflect.Float64 {
60+
return "float"
61+
}
62+
if t.Kind() == reflect.Float32 {
63+
return "float"
64+
}
65+
if t.Kind() == reflect.Bool {
66+
return "bool"
67+
}
68+
return ""
69+
}
70+
71+
// Reflect if an interface is either a struct or a pointer to a struct
72+
// and has the defined member field, if error is nil, the given
73+
// FieldName exists and is accessible with reflect.
74+
func property(i interface{}, fieldName string, fallback string) string {
75+
ValueIface := reflect.ValueOf(i)
76+
77+
// Check if the passed interface is a pointer
78+
if ValueIface.Type().Kind() != reflect.Ptr {
79+
// Create a new type of Iface's Type, so we have a pointer to work with
80+
ValueIface = reflect.New(reflect.TypeOf(i))
81+
}
82+
83+
// 'dereference' with Elem() and get the field by name
84+
Field := ValueIface.Elem().FieldByName(fieldName)
85+
if !Field.IsValid() || !(Field.Kind() == reflect.String) {
86+
return fallback
87+
}
88+
return Field.String()
89+
}

pkg/gotask/common_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package gotask
2+
3+
import (
4+
"testing"
5+
)
6+
7+
type Mock struct{}
8+
9+
func (m Mock) MockMethod(arg interface{}, r *interface{}) error {
10+
return nil
11+
}
12+
func (m Mock) MockMethodBytes(arg []byte, r *interface{}) error {
13+
return nil
14+
}
15+
func (m *Mock) Pointer(arg []byte, r *interface{}) error {
16+
return nil
17+
}
18+
func TestReflectStruct(t *testing.T) {
19+
m := Mock{}
20+
out := reflectStruct(m)
21+
if out.Name != "Mock" {
22+
t.Errorf("Name must be Mock, got %s", out.Name)
23+
}
24+
if out.Functions[0].Name != "MockMethod" {
25+
t.Errorf("Name must be MockMethod, got %s", out.Functions[0].Name)
26+
}
27+
if out.Functions[0].Raw != false {
28+
t.Errorf("Raw must be false, got %+v", out.Functions[0].Raw)
29+
}
30+
if out.Functions[1].Name != "MockMethodBytes" {
31+
t.Errorf("Name must be MockMethodBytes, got %s", out.Functions[1].Name)
32+
}
33+
if out.Functions[1].Raw != true {
34+
t.Errorf("Name must be true, got %+v", out.Functions[1].Raw)
35+
}
36+
if out.Functions[2].Name != "Pointer" {
37+
t.Errorf("Name must be MockMethodBytes, got %s", out.Functions[2].Name)
38+
}
39+
if out.Functions[2].Raw != true {
40+
t.Errorf("Name must be true, got %+v", out.Functions[2].Raw)
41+
}
42+
}

pkg/gotask/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var (
1010
standalone *bool
1111
listenOnPipe *bool
1212
go2phpAddress *string
13+
reflection *bool
1314
)
1415

1516
func init() {
@@ -18,5 +19,6 @@ func init() {
1819
address = flag.String("address", "127.0.0.1:6001", "must be a unix socket or tcp address:port like 127.0.0.1:6001")
1920
listenOnPipe = flag.Bool("listen-on-pipe", false, "listen on stdin/stdout pipe")
2021
go2phpAddress = flag.String("go2php-address", "127.0.0.1:6002", "must be a unix socket or tcp address:port like 127.0.0.1:6002")
22+
reflection = flag.Bool("reflect", false, "instead of running the service, provide a service definition to os.stdout using reflection")
2123
flag.Parse()
2224
}

pkg/gotask/generator.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package gotask
2+
3+
import (
4+
"bytes"
5+
"github.com/pkg/errors"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"text/template"
10+
"unicode"
11+
)
12+
13+
const phpBody = `<?php
14+
15+
declare(strict_types=1);
16+
{{ $ns := .Namespace -}}
17+
{{if $ns}}
18+
namespace {{ $ns }};
19+
{{end}}
20+
use Reasno\GoTask\GoTask;
21+
use Reasno\GoTask\GoTaskProxy;
22+
{{ $class := .Class.Name }}
23+
class {{ $class }} extends GoTaskProxy
24+
{
25+
{{- range $m := .Class.Functions}}
26+
/**
27+
* @param {{ $m.ParamModifier }} $payload
28+
* @return {{ $m.ResultModifier | returnAnnotation }}
29+
*/
30+
public function {{ $m.Name | lcfirst }}({{ $m.ParamModifier | postSpace }}$payload){{ $m.ResultModifier | returnTypeHint }}
31+
{
32+
return parent::call({{ methodName $class $m.Name }}, $payload, {{ $m.Raw | flag}});
33+
}
34+
{{end -}}
35+
}`
36+
37+
var tpl *template.Template
38+
39+
func init() {
40+
tpl = template.Must(template.New("phpBody").Funcs(template.FuncMap{
41+
"flag": func(raw *bool) string {
42+
if *raw {
43+
return "GoTask::PAYLOAD_RAW"
44+
}
45+
return "0"
46+
},
47+
"returnAnnotation": func(modifier *string) string {
48+
if *modifier != "" {
49+
return *modifier
50+
}
51+
return "mixed"
52+
},
53+
"returnTypeHint": func(modifier *string) string {
54+
if *modifier != "" {
55+
return " : " + *modifier
56+
}
57+
return ""
58+
},
59+
"postSpace": func(name *string) string {
60+
if *name == "" {
61+
return ""
62+
}
63+
return *name + " "
64+
},
65+
"lcfirst": func(name *string) string {
66+
for _, v := range *name {
67+
u := string(unicode.ToLower(v))
68+
return u + (*name)[len(u):]
69+
}
70+
return ""
71+
},
72+
"methodName": func(class string, method string) string {
73+
return "\"" + class + "." + method + "\""
74+
},
75+
}).Parse(phpBody))
76+
}
77+
78+
// generate php file body
79+
func body(namespace *string, class *Class) string {
80+
out := bytes.NewBuffer(nil)
81+
82+
data := struct {
83+
Namespace string
84+
Class Class
85+
}{
86+
Namespace: *namespace,
87+
Class: *class,
88+
}
89+
90+
err := tpl.Execute(out, data)
91+
if err != nil {
92+
panic(err)
93+
}
94+
95+
return out.String()
96+
}
97+
98+
func generatePHP(receiver interface{}) error {
99+
namespace := property(receiver, "Namespace", "App\\GoTask")
100+
class := reflectStruct(receiver)
101+
dirPath := property(receiver, "Path", "./../app/GoTask")
102+
err := os.MkdirAll(dirPath, os.FileMode(0755))
103+
if err != nil {
104+
return errors.Wrap(err, "cannot create dir for php files")
105+
}
106+
fullPath, err := filepath.Abs(filepath.Clean(dirPath) + "/" + class.Name + ".php")
107+
if err != nil {
108+
return errors.Wrap(err, "invalid file path")
109+
}
110+
out := body(&namespace, class)
111+
err = ioutil.WriteFile(fullPath, []byte(out), os.FileMode(0755))
112+
if err != nil {
113+
return errors.Wrap(err, "failed to generate PHP file")
114+
}
115+
return nil
116+
}

pkg/gotask/generator_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package gotask
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestGenerator(t *testing.T) {
8+
expected := `<?php
9+
10+
declare(strict_types=1);
11+
12+
namespace App\GoTask;
13+
14+
use Reasno\GoTask\GoTask;
15+
use Reasno\GoTask\GoTaskProxy;
16+
17+
class Worker extends GoTaskProxy
18+
{
19+
/**
20+
* @param string $payload
21+
* @return bool
22+
*/
23+
public function test(string $payload) : bool
24+
{
25+
return parent::call("Worker.Test", $payload, 0);
26+
}
27+
28+
/**
29+
* @param $payload
30+
* @return mixed
31+
*/
32+
public function debug($payload)
33+
{
34+
return parent::call("Worker.Debug", $payload, GoTask::PAYLOAD_RAW);
35+
}
36+
}`
37+
namespace := "App\\GoTask"
38+
class := Class{
39+
Name: "Worker",
40+
Functions: []Function{
41+
Function{
42+
Name: "Test",
43+
Raw: false,
44+
ParamModifier: "string",
45+
ResultModifier: "bool",
46+
},
47+
Function{
48+
Name: "Debug",
49+
Raw: true,
50+
ParamModifier: "",
51+
ResultModifier: "",
52+
},
53+
},
54+
}
55+
if body(&namespace, &class) != expected {
56+
t.Errorf("expecting %s, got %s", expected, body(&namespace, &class))
57+
}
58+
}

0 commit comments

Comments
 (0)