-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d28c6a4
commit 6fdf3ad
Showing
10 changed files
with
327 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ jobs: | |
with: | ||
go-version: 1.20.0 | ||
|
||
|
||
- name: Build | ||
run: go build -v ./... | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,5 +14,9 @@ | |
|
||
run: | ||
go: '1.20' | ||
skip-dirs: | ||
- .idea | ||
issues: | ||
exclude-dirs: | ||
- .idea | ||
linters-settings: | ||
errcheck: | ||
ignore: '' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package spi | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"plugin" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// LoadService 加载 dir 下面的所有的实现了 T 接口的类型 | ||
// 举个例子来说,如果你有一个叫做 UserService 的接口 | ||
// 而后你将所有的实现都放到了 /ext/user_service 目录下 | ||
// 并且所有的实现,虽然在不同的包,但是都叫做 UserService | ||
// 那么我可以执行 LoadService("/ext/user_service", "UserService") | ||
// 加载到所有的实现 | ||
// LoadService 加载 dir 下面的所有的实现了 T 接口的类型 | ||
|
||
var ( | ||
ErrDirNotFound = errors.New("ekit: 目录不存在") | ||
ErrSymbolNameIsEmpty = errors.New("ekit: 结构体名不能为空") | ||
ErrOpenPluginFailed = errors.New("ekit: 打开插件失败") | ||
ErrSymbolNameNotFound = errors.New("ekit: 从插件中查找对象失败") | ||
ErrInvalidSo = errors.New("ekit: 插件非该接口类型") | ||
) | ||
|
||
func LoadService[T any](dir string, symName string) ([]T, error) { | ||
var services []T | ||
// 检查目录是否存在 | ||
if _, err := os.Stat(dir); os.IsNotExist(err) { | ||
return nil, fmt.Errorf("%w", ErrDirNotFound) | ||
} | ||
if symName == "" { | ||
return nil, fmt.Errorf("%w", ErrSymbolNameIsEmpty) | ||
} | ||
// 遍历目录下的所有 .so 文件 | ||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||
if !info.IsDir() && filepath.Ext(path) == ".so" { | ||
// 打开插件 | ||
p, err := plugin.Open(path) | ||
if err != nil { | ||
return fmt.Errorf("%w: %w", ErrOpenPluginFailed, err) | ||
} | ||
// 查找变量 | ||
sym, err := p.Lookup(symName) | ||
if err != nil { | ||
return fmt.Errorf("%w: %w", ErrSymbolNameNotFound, err) | ||
} | ||
|
||
// 尝试将符号断言为接口类型 | ||
service, ok := sym.(T) | ||
if !ok { | ||
return fmt.Errorf("%w", ErrInvalidSo) | ||
} | ||
// 收集服务 | ||
services = append(services, service) | ||
} | ||
return nil | ||
}) | ||
return services, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package spi | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/stretchr/testify/suite" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type LoadServiceSuite struct { | ||
suite.Suite | ||
} | ||
|
||
func (l *LoadServiceSuite) SetupTest() { | ||
t := l.T() | ||
wd, err := os.Getwd() | ||
require.NoError(t, err) | ||
cmd := exec.Command("go", "generate", "./...") | ||
cmd.Dir = filepath.Join(wd, "testdata") | ||
output, err := cmd.CombinedOutput() | ||
require.NoError(t, err, fmt.Sprintf("执行 go generate 失败: %v\n%s", err, output)) | ||
} | ||
|
||
func (l *LoadServiceSuite) Test_LoadService() { | ||
t := l.T() | ||
testcases := []struct { | ||
name string | ||
dir string | ||
svcName string | ||
want []string | ||
assertFunc assert.ErrorAssertionFunc | ||
}{ | ||
{ | ||
name: "有一个插件", | ||
dir: "./testdata/user_service", | ||
svcName: "UserSvc", | ||
want: []string{"Get"}, | ||
assertFunc: assert.NoError, | ||
}, | ||
{ | ||
name: "有两个插件", | ||
dir: "./testdata/user_service2", | ||
svcName: "UserSvc", | ||
want: []string{"A", "B"}, | ||
assertFunc: assert.NoError, | ||
}, | ||
{ | ||
name: "目录不存在", | ||
dir: "./notfound", | ||
assertFunc: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return assert.ErrorIs(t, err, ErrDirNotFound) | ||
}, | ||
}, | ||
{ | ||
name: "svcName为空", | ||
dir: "./testdata/user_service2", | ||
svcName: "", | ||
assertFunc: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return assert.ErrorIs(t, err, ErrSymbolNameIsEmpty) | ||
}, | ||
}, | ||
{ | ||
name: "svcName没找到", | ||
dir: "./testdata/user_service2", | ||
svcName: "notfound", | ||
assertFunc: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return assert.ErrorIs(t, err, ErrSymbolNameNotFound) | ||
}, | ||
}, | ||
{ | ||
name: "加载的对象未实现对应的抽象", | ||
dir: "./testdata/user_service3", | ||
svcName: "UserSvc", | ||
assertFunc: func(t assert.TestingT, err error, i ...interface{}) bool { | ||
return assert.ErrorIs(t, err, ErrInvalidSo) | ||
}, | ||
}, | ||
} | ||
for _, tc := range testcases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
list, err := LoadService[UserService](tc.dir, tc.svcName) | ||
tc.assertFunc(t, err) | ||
if err != nil { | ||
return | ||
} | ||
ans := make([]string, 0, len(list)) | ||
for _, svc := range list { | ||
ans = append(ans, svc.Get()) | ||
} | ||
assert.Equal(t, tc.want, ans) | ||
}) | ||
} | ||
} | ||
|
||
func TestLoadServiceSuite(t *testing.T) { | ||
suite.Run(t, new(LoadServiceSuite)) | ||
} | ||
|
||
type UserService interface { | ||
Get() string | ||
} | ||
|
||
func ExampleLoadService() { | ||
getters, err := LoadService[UserService]("./testdata/user_service", "UserSvc") | ||
fmt.Println(err) | ||
for _, getter := range getters { | ||
fmt.Println(getter.Get()) | ||
} | ||
// Output: | ||
// <nil> | ||
// Get | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
package main | ||
|
||
// 测试用 | ||
//go:generate go build -race --buildmode=plugin -o a.so ./a.go | ||
|
||
type UserService struct{} | ||
|
||
func (u UserService) Get() string { | ||
return "Get" | ||
} | ||
|
||
var UserSvc UserService |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:generate go build -race --buildmode=plugin -o ../a.so ./a.go | ||
|
||
package main | ||
|
||
// 测试用 | ||
|
||
type UserService struct{} | ||
|
||
// GetName returns the name of the service | ||
func (u UserService) Get() string { | ||
return "A" | ||
} | ||
|
||
// 导出对象 | ||
var UserSvc UserService |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:generate go build -race --buildmode=plugin -o ../b.so ./b.go | ||
package main | ||
|
||
// 测试用 | ||
|
||
type UserService struct{} | ||
|
||
// GetName returns the name of the service | ||
func (u UserService) Get() string { | ||
return "B" | ||
} | ||
|
||
// 导出对象 | ||
var UserSvc UserService |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright 2021 ecodeclub | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
// 测试用 | ||
|
||
//go:generate go build -race --buildmode=plugin -o a.so ./a.go | ||
|
||
type UserService struct{} | ||
|
||
func (u UserService) GetV1() string { | ||
return "Get" | ||
} | ||
|
||
var UserSvc UserService |