feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
63
backend/pkg/ctxcache/ctx_cache.go
Normal file
63
backend/pkg/ctxcache/ctx_cache.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 ctxcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ctxCacheKey struct{}
|
||||
|
||||
func Init(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, ctxCacheKey{}, new(sync.Map))
|
||||
}
|
||||
|
||||
func Get[T any](ctx context.Context, key any) (value T, ok bool) {
|
||||
var zero T
|
||||
|
||||
cacheMap, valid := ctx.Value(ctxCacheKey{}).(*sync.Map)
|
||||
if !valid {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
loadedValue, exists := cacheMap.Load(key)
|
||||
if !exists {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
if v, match := loadedValue.(T); match {
|
||||
return v, true
|
||||
}
|
||||
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func Store(ctx context.Context, key any, obj any) {
|
||||
if cacheMap, ok := ctx.Value(ctxCacheKey{}).(*sync.Map); ok {
|
||||
cacheMap.Store(key, obj)
|
||||
}
|
||||
}
|
||||
|
||||
func HasKey(ctx context.Context, key any) bool {
|
||||
if cacheMap, ok := ctx.Value(ctxCacheKey{}).(*sync.Map); ok {
|
||||
_, ok := cacheMap.Load(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
73
backend/pkg/ctxcache/ctx_cache_test.go
Normal file
73
backend/pkg/ctxcache/ctx_cache_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 ctxcache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestCtxCache(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// 测试没有 initCtxCacheData 场景
|
||||
Store(ctx, "test1", "1")
|
||||
_, ok := Get[string](ctx, "test1")
|
||||
g.Expect(ok).Should(BeFalse())
|
||||
|
||||
// 有 initCtxCacheData 场景
|
||||
ctx = Init(ctx)
|
||||
|
||||
_, ok = Get[string](ctx, "test")
|
||||
g.Expect(ok).Should(BeFalse())
|
||||
|
||||
Store(ctx, "key1", "abc")
|
||||
data1, ok := Get[string](ctx, "key1")
|
||||
|
||||
g.Expect(ok).Should(BeTrue())
|
||||
g.Expect(data1).Should(BeIdenticalTo("abc"))
|
||||
|
||||
type testKey struct{}
|
||||
Store(ctx, testKey{}, int64(1))
|
||||
data2, ok := Get[int64](ctx, testKey{})
|
||||
g.Expect(ok).Should(BeTrue())
|
||||
g.Expect(data2).Should(BeIdenticalTo(int64(1)))
|
||||
|
||||
type temp struct {
|
||||
a string
|
||||
b string
|
||||
c int64
|
||||
d []int64
|
||||
}
|
||||
|
||||
te := temp{
|
||||
a: "1",
|
||||
b: "2",
|
||||
c: 3,
|
||||
d: []int64{123, 1232, 232},
|
||||
}
|
||||
|
||||
Store(ctx, "temp", te)
|
||||
newT, ok := Get[temp](ctx, "temp")
|
||||
g.Expect(ok).Should(BeTrue())
|
||||
g.Expect(reflect.DeepEqual(te, newT)).Should(BeTrue())
|
||||
|
||||
}
|
||||
38
backend/pkg/errorx/code/register.go
Normal file
38
backend/pkg/errorx/code/register.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 code
|
||||
|
||||
import (
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx/internal"
|
||||
)
|
||||
|
||||
type RegisterOptionFn = internal.RegisterOption
|
||||
|
||||
// WithAffectStability 设置稳定性标识, true:会影响系统稳定性, 并体现在接口错误率中, false:不影响稳定性.
|
||||
func WithAffectStability(affectStability bool) RegisterOptionFn {
|
||||
return internal.WithAffectStability(affectStability)
|
||||
}
|
||||
|
||||
// Register 注册用户预定义的错误码信息, PSM服务对应的code_gen子module初始化时调用.
|
||||
func Register(code int32, msg string, opts ...RegisterOptionFn) {
|
||||
internal.Register(code, msg, opts...)
|
||||
}
|
||||
|
||||
// SetDefaultErrorCode 带有PSM信息染色的code替换默认code.
|
||||
func SetDefaultErrorCode(code int32) {
|
||||
internal.SetDefaultErrorCode(code)
|
||||
}
|
||||
90
backend/pkg/errorx/error.go
Normal file
90
backend/pkg/errorx/error.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 errorx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx/internal"
|
||||
)
|
||||
|
||||
// StatusError is an interface for error with status code, you can
|
||||
// create an error through New or WrapByCode and convert it back to
|
||||
// StatusError through FromStatusError to obtain information such as
|
||||
// error status code.
|
||||
type StatusError interface {
|
||||
error
|
||||
Code() int32
|
||||
Msg() string
|
||||
IsAffectStability() bool
|
||||
Extra() map[string]string
|
||||
}
|
||||
|
||||
// Option is used to configure an StatusError.
|
||||
type Option = internal.Option
|
||||
|
||||
func KV(k, v string) Option {
|
||||
return internal.Param(k, v)
|
||||
}
|
||||
|
||||
func KVf(k, v string, a ...any) Option {
|
||||
formatValue := fmt.Sprintf(v, a...)
|
||||
return internal.Param(k, formatValue)
|
||||
}
|
||||
|
||||
func Extra(k, v string) Option {
|
||||
return internal.Extra(k, v)
|
||||
}
|
||||
|
||||
// New get an error predefined in the configuration file by statusCode
|
||||
// with a stack trace at the point New is called.
|
||||
func New(code int32, options ...Option) error {
|
||||
return internal.NewByCode(code, options...)
|
||||
}
|
||||
|
||||
// WrapByCode returns an error annotating err with a stack trace
|
||||
// at the point WrapByCode is called, and the status code.
|
||||
func WrapByCode(err error, statusCode int32, options ...Option) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return internal.WrapByCode(err, statusCode, options...)
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is called, and the format specifier.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return internal.Wrapf(err, format, args...)
|
||||
}
|
||||
|
||||
func ErrorWithoutStack(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
errMsg := err.Error()
|
||||
index := strings.Index(errMsg, "stack=")
|
||||
if index != -1 {
|
||||
errMsg = errMsg[:index]
|
||||
}
|
||||
return errMsg
|
||||
}
|
||||
53
backend/pkg/errorx/error_test.go
Normal file
53
backend/pkg/errorx/error_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 errorx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx/code"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPermissionCode = int32(1000000)
|
||||
errPermissionMessage = "unauthorized access : {msg}"
|
||||
errPermissionAffectStability = false
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
code.Register(
|
||||
ErrPermissionCode,
|
||||
errPermissionMessage,
|
||||
code.WithAffectStability(errPermissionAffectStability),
|
||||
)
|
||||
|
||||
err := New(ErrPermissionCode, KV("msg", "test"))
|
||||
fmt.Println(err)
|
||||
fmt.Println(err.Error())
|
||||
fmt.Println(err)
|
||||
|
||||
var customErr StatusError
|
||||
b := errors.As(err, &customErr)
|
||||
assert.Equal(t, b, true)
|
||||
assert.Equal(t, customErr.Code(), ErrPermissionCode)
|
||||
assert.Equal(t, customErr.Msg(), strings.Replace(errPermissionMessage, "{msg}", "test", 1))
|
||||
}
|
||||
50
backend/pkg/errorx/internal/msg.go
Normal file
50
backend/pkg/errorx/internal/msg.go
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Unwrap() error {
|
||||
return w.cause
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string {
|
||||
return fmt.Sprintf("%s\ncause=%s", w.msg, w.cause.Error())
|
||||
}
|
||||
|
||||
func wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
return withStackTraceIfNotExists(wrapf(err, format, args...))
|
||||
}
|
||||
59
backend/pkg/errorx/internal/register.go
Normal file
59
backend/pkg/errorx/internal/register.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 internal
|
||||
|
||||
const (
|
||||
DefaultErrorMsg = "Service Internal Error"
|
||||
DefaultIsAffectStability = true
|
||||
)
|
||||
|
||||
var (
|
||||
ServiceInternalErrorCode int32 = 1
|
||||
CodeDefinitions = make(map[int32]*CodeDefinition)
|
||||
)
|
||||
|
||||
type CodeDefinition struct {
|
||||
Code int32
|
||||
Message string
|
||||
IsAffectStability bool
|
||||
}
|
||||
|
||||
type RegisterOption func(definition *CodeDefinition)
|
||||
|
||||
func WithAffectStability(affectStability bool) RegisterOption {
|
||||
return func(definition *CodeDefinition) {
|
||||
definition.IsAffectStability = affectStability
|
||||
}
|
||||
}
|
||||
|
||||
func Register(code int32, msg string, opts ...RegisterOption) {
|
||||
definition := &CodeDefinition{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
IsAffectStability: DefaultIsAffectStability,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(definition)
|
||||
}
|
||||
|
||||
CodeDefinitions[code] = definition
|
||||
}
|
||||
|
||||
func SetDefaultErrorCode(code int32) {
|
||||
ServiceInternalErrorCode = code
|
||||
}
|
||||
86
backend/pkg/errorx/internal/stack.go
Normal file
86
backend/pkg/errorx/internal/stack.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StackTracer interface {
|
||||
StackTrace() string
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
cause error
|
||||
stack string
|
||||
}
|
||||
|
||||
func (w *withStack) Unwrap() error {
|
||||
return w.cause
|
||||
}
|
||||
|
||||
func (w *withStack) StackTrace() string {
|
||||
return w.stack
|
||||
}
|
||||
|
||||
func (w *withStack) Error() string {
|
||||
return fmt.Sprintf("%s\nstack=%s", w.cause.Error(), w.stack)
|
||||
}
|
||||
|
||||
func stack() string {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(2, pcs[:])
|
||||
|
||||
b := strings.Builder{}
|
||||
for i := 0; i < n; i++ {
|
||||
fn := runtime.FuncForPC(pcs[i])
|
||||
|
||||
file, line := fn.FileLine(pcs[i])
|
||||
name := trimPathPrefix(fn.Name())
|
||||
b.WriteString(fmt.Sprintf("%s:%d %s\n", file, line, name))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func trimPathPrefix(s string) string {
|
||||
i := strings.LastIndex(s, "/")
|
||||
s = s[i+1:]
|
||||
i = strings.Index(s, ".")
|
||||
return s[i+1:]
|
||||
}
|
||||
|
||||
func withStackTraceIfNotExists(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip if stack has already exist
|
||||
var stackTracer StackTracer
|
||||
if errors.As(err, &stackTracer) {
|
||||
return err
|
||||
}
|
||||
|
||||
return &withStack{
|
||||
err,
|
||||
stack(),
|
||||
}
|
||||
}
|
||||
73
backend/pkg/errorx/internal/stack_test.go
Normal file
73
backend/pkg/errorx/internal/stack_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWithStack(t *testing.T) {
|
||||
err := withStackTraceIfNotExists(errors.New("test error"))
|
||||
output1 := fmt.Sprintf("%+v", err)
|
||||
assert.Contains(t, output1, "stack_test.go")
|
||||
assert.Contains(t, output1, "withStackTraceIfNotExists")
|
||||
t.Log(output1)
|
||||
}
|
||||
|
||||
func TestPrintStack(t *testing.T) {
|
||||
t.Run("New with stack", func(t *testing.T) {
|
||||
err := NewByCode(1)
|
||||
output1 := fmt.Sprintf("%v", err)
|
||||
assert.Contains(t, output1, "stack_test.go")
|
||||
assert.Contains(t, output1, "TestPrintStack")
|
||||
t.Log(output1)
|
||||
})
|
||||
|
||||
t.Run("New with stack and wrap with fmt.Errorf", func(t *testing.T) {
|
||||
err := NewByCode(1)
|
||||
err1 := fmt.Errorf("err=%w", err)
|
||||
output1 := fmt.Sprintf("%v", err1)
|
||||
assert.Contains(t, output1, "stack_test.go")
|
||||
assert.Contains(t, output1, "TestPrintStack")
|
||||
t.Log(output1)
|
||||
})
|
||||
|
||||
t.Run("wrapf with stack", func(t *testing.T) {
|
||||
err := errors.New("original error")
|
||||
err1 := Wrapf(err, "wrapped error")
|
||||
output1 := fmt.Sprintf("%v", err1)
|
||||
assert.Contains(t, output1, "stack_test.go")
|
||||
assert.Contains(t, output1, "TestPrintStack")
|
||||
t.Log(output1)
|
||||
})
|
||||
|
||||
t.Run("skip wrap with stack if stack has already exist", func(t *testing.T) {
|
||||
err := NewByCode(1)
|
||||
err1 := fmt.Errorf("err1=%w", err)
|
||||
err2 := withStackTraceIfNotExists(err1)
|
||||
_, ok := err2.(StackTracer)
|
||||
assert.False(t, ok)
|
||||
output1 := fmt.Sprintf("%v", err2)
|
||||
assert.Contains(t, output1, "stack_test.go")
|
||||
assert.Contains(t, output1, "TestPrintStack")
|
||||
t.Log(output1)
|
||||
})
|
||||
}
|
||||
195
backend/pkg/errorx/internal/status.go
Normal file
195
backend/pkg/errorx/internal/status.go
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StatusError interface {
|
||||
error
|
||||
Code() int32
|
||||
}
|
||||
|
||||
type statusError struct {
|
||||
statusCode int32
|
||||
message string
|
||||
|
||||
ext Extension
|
||||
}
|
||||
|
||||
type withStatus struct {
|
||||
status *statusError
|
||||
|
||||
stack string
|
||||
cause error
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
IsAffectStability bool
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
func (w *statusError) Code() int32 {
|
||||
return w.statusCode
|
||||
}
|
||||
|
||||
func (w *statusError) IsAffectStability() bool {
|
||||
return w.ext.IsAffectStability
|
||||
}
|
||||
|
||||
func (w *statusError) Msg() string {
|
||||
return w.message
|
||||
}
|
||||
|
||||
func (w *statusError) Error() string {
|
||||
return fmt.Sprintf("code=%d message=%s", w.statusCode, w.message)
|
||||
}
|
||||
|
||||
func (w *statusError) Extra() map[string]string {
|
||||
return w.ext.Extra
|
||||
}
|
||||
|
||||
// Unwrap supports go errors.Unwrap().
|
||||
func (w *withStatus) Unwrap() error {
|
||||
return w.cause
|
||||
}
|
||||
|
||||
// Is supports go errors.Is().
|
||||
func (w *withStatus) Is(target error) bool {
|
||||
var ws StatusError
|
||||
if errors.As(target, &ws) && w.status.Code() == ws.Code() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// As supports go errors.As().
|
||||
func (w *withStatus) As(target interface{}) bool {
|
||||
if errors.As(w.status, target) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *withStatus) StackTrace() string {
|
||||
return w.stack
|
||||
}
|
||||
|
||||
func (w *withStatus) Error() string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString(w.status.Error())
|
||||
|
||||
if w.cause != nil {
|
||||
b.WriteString("\n")
|
||||
b.WriteString(fmt.Sprintf("cause=%s", w.cause))
|
||||
}
|
||||
|
||||
if w.stack != "" {
|
||||
b.WriteString("\n")
|
||||
b.WriteString(fmt.Sprintf("stack=%s", w.stack))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type Option func(ws *withStatus)
|
||||
|
||||
func Param(k, v string) Option {
|
||||
return func(ws *withStatus) {
|
||||
if ws == nil || ws.status == nil {
|
||||
return
|
||||
}
|
||||
ws.status.message = strings.Replace(ws.status.message, fmt.Sprintf("{%s}", k), v, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func Extra(k, v string) Option {
|
||||
return func(ws *withStatus) {
|
||||
if ws == nil || ws.status == nil {
|
||||
return
|
||||
}
|
||||
if ws.status.ext.Extra == nil {
|
||||
ws.status.ext.Extra = make(map[string]string)
|
||||
}
|
||||
ws.status.ext.Extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func NewByCode(code int32, options ...Option) error {
|
||||
ws := &withStatus{
|
||||
status: getStatusByCode(code),
|
||||
cause: nil,
|
||||
stack: stack(),
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(ws)
|
||||
}
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
func WrapByCode(err error, code int32, options ...Option) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ws := &withStatus{
|
||||
status: getStatusByCode(code),
|
||||
cause: err,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(ws)
|
||||
}
|
||||
|
||||
// skip if stack has already exist
|
||||
var stackTracer StackTracer
|
||||
if errors.As(err, &stackTracer) {
|
||||
return ws
|
||||
}
|
||||
|
||||
ws.stack = stack()
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
func getStatusByCode(code int32) *statusError {
|
||||
codeDefinition, ok := CodeDefinitions[code]
|
||||
if ok {
|
||||
// predefined err code
|
||||
return &statusError{
|
||||
statusCode: code,
|
||||
message: codeDefinition.Message,
|
||||
ext: Extension{
|
||||
IsAffectStability: codeDefinition.IsAffectStability,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &statusError{
|
||||
statusCode: code,
|
||||
message: DefaultErrorMsg,
|
||||
ext: Extension{
|
||||
IsAffectStability: DefaultIsAffectStability,
|
||||
},
|
||||
}
|
||||
}
|
||||
39
backend/pkg/goutil/goutil.go
Normal file
39
backend/pkg/goutil/goutil.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 goutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
func Recovery(ctx context.Context) {
|
||||
e := recover()
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx == nil {
|
||||
ctx = context.Background() // nolint: byted_context_not_reinitialize -- false positive
|
||||
}
|
||||
|
||||
err := fmt.Errorf("%v", e)
|
||||
logs.CtxErrorf(ctx, fmt.Sprintf("[catch panic] err = %v \n stacktrace:\n%s", err, debug.Stack()))
|
||||
}
|
||||
44
backend/pkg/goutil/pyutil.go
Normal file
44
backend/pkg/goutil/pyutil.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 goutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
func GetPythonFilePath(fileName string) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logs.Warnf("[GetPythonFilePath] Failed to get current working directory: %v", err)
|
||||
return fileName
|
||||
}
|
||||
|
||||
return filepath.Join(cwd, fileName)
|
||||
}
|
||||
|
||||
func GetPython3Path() string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logs.Warnf("[GetPython3Path] Failed to get current working directory: %v", err)
|
||||
return ".venv/bin/python3"
|
||||
}
|
||||
|
||||
return filepath.Join(cwd, ".venv/bin/python3")
|
||||
}
|
||||
45
backend/pkg/hertzutil/domain/origin_host.go
Normal file
45
backend/pkg/hertzutil/domain/origin_host.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 domain
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderKeyOfOrigin = "Origin"
|
||||
HeaderKeyOfHost = "Host"
|
||||
)
|
||||
|
||||
func GetOriginHost(c *app.RequestContext) string {
|
||||
origin := c.Request.Header.Get(HeaderKeyOfOrigin)
|
||||
if origin != "" {
|
||||
u, err := url.Parse(origin)
|
||||
if err == nil {
|
||||
return u.Hostname()
|
||||
}
|
||||
}
|
||||
|
||||
host := c.Request.Header.Get(HeaderKeyOfHost)
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
|
||||
return string(c.Request.URI().Host())
|
||||
}
|
||||
50
backend/pkg/i18n/i18n.go
Normal file
50
backend/pkg/i18n/i18n.go
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Locale string
|
||||
|
||||
const (
|
||||
LocaleEN Locale = "en-US"
|
||||
LocaleZH Locale = "zh-CN"
|
||||
)
|
||||
|
||||
const key = "i18n.locale.key"
|
||||
|
||||
func SetLocale(ctx context.Context, locale string) context.Context {
|
||||
return context.WithValue(ctx, key, locale)
|
||||
}
|
||||
|
||||
func GetLocale(ctx context.Context) Locale {
|
||||
locale := ctx.Value(key)
|
||||
if locale == nil {
|
||||
return LocaleEN
|
||||
}
|
||||
|
||||
switch locale.(string) {
|
||||
case "en-US":
|
||||
return LocaleEN
|
||||
case "zh-CN":
|
||||
return LocaleZH
|
||||
default:
|
||||
return LocaleEN
|
||||
}
|
||||
}
|
||||
81
backend/pkg/jsoncache/jsoncache.go
Normal file
81
backend/pkg/jsoncache/jsoncache.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 jsoncache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type JsonCache[T any] struct {
|
||||
cache *redis.Client
|
||||
prefix string
|
||||
}
|
||||
|
||||
func New[T any](prefix string, cache *redis.Client) *JsonCache[T] {
|
||||
return &JsonCache[T]{
|
||||
prefix: prefix,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *JsonCache[T]) Save(ctx context.Context, k string, v *T) error {
|
||||
if v == nil {
|
||||
return fmt.Errorf("cannot save nil value for key: %s", k)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal failed for type %T: %w", *v, err)
|
||||
}
|
||||
|
||||
key := g.prefix + k
|
||||
if err := g.cache.Set(ctx, key, data, 0).Err(); err != nil {
|
||||
return fmt.Errorf("redis set failed for key %s: %w", k, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns default T if key not found
|
||||
func (g *JsonCache[T]) Get(ctx context.Context, k string) (*T, error) {
|
||||
key := g.prefix + k
|
||||
var obj T
|
||||
|
||||
data, err := g.cache.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get key %s: %w", k, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(data), &obj); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal json for key %s: %w", k, err)
|
||||
}
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
func (g *JsonCache[T]) Delete(ctx context.Context, k string) error {
|
||||
if err := g.cache.Del(ctx, k).Err(); err != nil {
|
||||
return fmt.Errorf("failed to delete key %s: %w", k, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
74
backend/pkg/lang/conv/to.go
Normal file
74
backend/pkg/lang/conv/to.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 conv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
)
|
||||
|
||||
// StrToInt64E returns strconv.ParseInt(v, 10, 64)
|
||||
func StrToInt64(v string) (int64, error) {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
// Int64ToStr returns strconv.FormatInt(v, 10) result
|
||||
func Int64ToStr(v int64) string {
|
||||
return strconv.FormatInt(v, 10)
|
||||
}
|
||||
|
||||
// StrToInt64 returns strconv.ParseInt(v, 10, 64)'s value.
|
||||
// if error occurs, returns defaultValue as result.
|
||||
func StrToInt64D(v string, defaultValue int64) int64 {
|
||||
toV, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return toV
|
||||
}
|
||||
|
||||
// DebugJsonToStr
|
||||
func DebugJsonToStr(v interface{}) string {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func BoolToInt(p bool) int {
|
||||
if p == true {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// BoolToIntPointer returns 1 or 0 as pointer
|
||||
func BoolToIntPointer(p *bool) *int {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if *p == true {
|
||||
return ptr.Of(int(1))
|
||||
}
|
||||
|
||||
return ptr.Of(int(0))
|
||||
}
|
||||
11
backend/pkg/lang/crypto/md5.go
Normal file
11
backend/pkg/lang/crypto/md5.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func MD5HexValue(input string) string {
|
||||
md5Hash := md5.Sum([]byte(input))
|
||||
return hex.EncodeToString(md5Hash[:])
|
||||
}
|
||||
34
backend/pkg/lang/maps/maps.go
Normal file
34
backend/pkg/lang/maps/maps.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 maps
|
||||
|
||||
func ToAnyValue[K comparable, V any](m map[K]V) map[K]any {
|
||||
n := make(map[K]any, len(m))
|
||||
for k, v := range m {
|
||||
n[k] = v
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func TransformKey[K1, K2 comparable, V any](m map[K1]V, f func(K1) K2) map[K2]V {
|
||||
n := make(map[K2]V, len(m))
|
||||
for k1, v := range m {
|
||||
n[f(k1)] = v
|
||||
}
|
||||
return n
|
||||
}
|
||||
36
backend/pkg/lang/ptr/ptr.go
Normal file
36
backend/pkg/lang/ptr/ptr.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 ptr
|
||||
|
||||
func Of[T any](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
func From[T any](p *T) T {
|
||||
if p != nil {
|
||||
return *p
|
||||
}
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
|
||||
func FromOrDefault[T any](p *T, def T) T {
|
||||
if p != nil {
|
||||
return *p
|
||||
}
|
||||
return def
|
||||
}
|
||||
42
backend/pkg/lang/sets/sets.go
Normal file
42
backend/pkg/lang/sets/sets.go
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 sets
|
||||
|
||||
type Set[T comparable] map[T]struct{}
|
||||
|
||||
func FromSlice[T comparable](s []T) Set[T] {
|
||||
set := make(Set[T], len(s))
|
||||
for _, elem := range s {
|
||||
cpElem := elem
|
||||
set[cpElem] = struct{}{}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func (s Set[T]) ToSlice() []T {
|
||||
sl := make([]T, 0, len(s))
|
||||
for elem := range s {
|
||||
cpElem := elem
|
||||
sl = append(sl, cpElem)
|
||||
}
|
||||
return sl
|
||||
}
|
||||
|
||||
func (s Set[T]) Contains(elem T) bool {
|
||||
_, ok := s[elem]
|
||||
return ok
|
||||
}
|
||||
29
backend/pkg/lang/signal/signal.go
Normal file
29
backend/pkg/lang/signal/signal.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 signal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func WaitExit() {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM)
|
||||
<-signals
|
||||
}
|
||||
125
backend/pkg/lang/slices/iter.go
Normal file
125
backend/pkg/lang/slices/iter.go
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 slices
|
||||
|
||||
func Transform[A, B any](src []A, fn func(A) B) []B {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst := make([]B, 0, len(src))
|
||||
for _, a := range src {
|
||||
dst = append(dst, fn(a))
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func TransformWithErrorCheck[A, B any](src []A, fn func(A) (B, error)) ([]B, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dst := make([]B, 0, len(src))
|
||||
for _, a := range src {
|
||||
item, err := fn(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dst = append(dst, item)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func GroupBy[A, K comparable, V any](src []A, fn func(A) (K, V)) map[K][]V {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := make(map[K][]V, len(src))
|
||||
for _, a := range src {
|
||||
k, v := fn(a)
|
||||
dst[k] = append(dst[k], v)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func Unique[T comparable](src []T) []T {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := make([]T, 0, len(src))
|
||||
m := make(map[T]struct{}, len(src))
|
||||
for _, s := range src {
|
||||
if _, ok := m[s]; ok {
|
||||
continue
|
||||
}
|
||||
dst = append(dst, s)
|
||||
m[s] = struct{}{}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func Fill[T any](val T, size int) []T {
|
||||
slice := make([]T, size)
|
||||
for i := 0; i < size; i++ {
|
||||
slice[i] = val
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func Chunks[T any](s []T, chunkSize int) [][]T {
|
||||
sliceLen := len(s)
|
||||
chunks := make([][]T, 0, sliceLen/chunkSize)
|
||||
|
||||
for start := 0; start < sliceLen; start += chunkSize {
|
||||
end := start + chunkSize
|
||||
if end > sliceLen {
|
||||
end = sliceLen
|
||||
}
|
||||
|
||||
chunks = append(chunks, s[start:end])
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
func ToMap[E any, K comparable, V any](src []E, fn func(e E) (K, V)) map[K]V {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst := make(map[K]V, len(src))
|
||||
for _, e := range src {
|
||||
k, v := fn(e)
|
||||
dst[k] = v
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func Reverse[T any](slice []T) []T {
|
||||
left := 0
|
||||
right := len(slice) - 1
|
||||
for left < right {
|
||||
slice[left], slice[right] = slice[right], slice[left]
|
||||
left++
|
||||
right--
|
||||
}
|
||||
return slice
|
||||
}
|
||||
33
backend/pkg/lang/sqlutil/sqlutil.go
Normal file
33
backend/pkg/lang/sqlutil/sqlutil.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 sqlutil
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
func DriverValue[T any](v T) driver.Valuer {
|
||||
return value[T]{val: v}
|
||||
}
|
||||
|
||||
type value[T any] struct {
|
||||
val T
|
||||
}
|
||||
|
||||
func (i value[T]) Value() (driver.Value, error) {
|
||||
return i.val, nil
|
||||
}
|
||||
24
backend/pkg/lang/ternary/ternary.go
Normal file
24
backend/pkg/lang/ternary/ternary.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 ternary
|
||||
|
||||
func IFElse[T any](ok bool, trueValue, falseValue T) T {
|
||||
if ok {
|
||||
return trueValue
|
||||
}
|
||||
return falseValue
|
||||
}
|
||||
292
backend/pkg/logs/default.go
Normal file
292
backend/pkg/logs/default.go
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var logger FullLogger = &defaultLogger{
|
||||
level: LevelInfo,
|
||||
stdlog: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),
|
||||
}
|
||||
|
||||
// SetOutput sets the output of default logs. By default, it is stderr.
|
||||
func SetOutput(w io.Writer) {
|
||||
logger.SetOutput(w)
|
||||
}
|
||||
|
||||
// SetLevel sets the level of logs below which logs will not be output.
|
||||
// The default log level is LevelTrace.
|
||||
// Note that this method is not concurrent-safe.
|
||||
func SetLevel(lv Level) {
|
||||
logger.SetLevel(lv)
|
||||
}
|
||||
|
||||
// DefaultLogger return the default logs for kitex.
|
||||
func DefaultLogger() FullLogger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// SetLogger sets the default logs.
|
||||
// Note that this method is not concurrent-safe and must not be called
|
||||
// after the use of DefaultLogger and global functions in this package.
|
||||
func SetLogger(v FullLogger) {
|
||||
logger = v
|
||||
}
|
||||
|
||||
// Fatal calls the default logs's Fatal method and then os.Exit(1).
|
||||
func Fatal(v ...interface{}) {
|
||||
logger.Fatal(v...)
|
||||
}
|
||||
|
||||
// Error calls the default logs's Error method.
|
||||
func Error(v ...interface{}) {
|
||||
logger.Error(v...)
|
||||
}
|
||||
|
||||
// Warn calls the default logs's Warn method.
|
||||
func Warn(v ...interface{}) {
|
||||
logger.Warn(v...)
|
||||
}
|
||||
|
||||
// Notice calls the default logs's Notice method.
|
||||
func Notice(v ...interface{}) {
|
||||
logger.Notice(v...)
|
||||
}
|
||||
|
||||
// Info calls the default logs's Info method.
|
||||
func Info(v ...interface{}) {
|
||||
logger.Info(v...)
|
||||
}
|
||||
|
||||
// Debug calls the default logs's Debug method.
|
||||
func Debug(v ...interface{}) {
|
||||
logger.Debug(v...)
|
||||
}
|
||||
|
||||
// Trace calls the default logs's Trace method.
|
||||
func Trace(v ...interface{}) {
|
||||
logger.Trace(v...)
|
||||
}
|
||||
|
||||
// Fatalf calls the default logs's Fatalf method and then os.Exit(1).
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(format, v...)
|
||||
}
|
||||
|
||||
// Errorf calls the default logs's Errorf method.
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
logger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
// Warnf calls the default logs's Warnf method.
|
||||
func Warnf(format string, v ...interface{}) {
|
||||
logger.Warnf(format, v...)
|
||||
}
|
||||
|
||||
// Noticef calls the default logs's Noticef method.
|
||||
func Noticef(format string, v ...interface{}) {
|
||||
logger.Noticef(format, v...)
|
||||
}
|
||||
|
||||
// Infof calls the default logs's Infof method.
|
||||
func Infof(format string, v ...interface{}) {
|
||||
logger.Infof(format, v...)
|
||||
}
|
||||
|
||||
// Debugf calls the default logs's Debugf method.
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
logger.Debugf(format, v...)
|
||||
}
|
||||
|
||||
// Tracef calls the default logs's Tracef method.
|
||||
func Tracef(format string, v ...interface{}) {
|
||||
logger.Tracef(format, v...)
|
||||
}
|
||||
|
||||
// CtxFatalf calls the default logs's CtxFatalf method and then os.Exit(1).
|
||||
func CtxFatalf(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxFatalf(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxErrorf calls the default logs's CtxErrorf method.
|
||||
func CtxErrorf(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxErrorf(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxWarnf calls the default logs's CtxWarnf method.
|
||||
func CtxWarnf(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxWarnf(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxNoticef calls the default logs's CtxNoticef method.
|
||||
func CtxNoticef(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxNoticef(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxInfof calls the default logs's CtxInfof method.
|
||||
func CtxInfof(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxInfof(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxDebugf calls the default logs's CtxDebugf method.
|
||||
func CtxDebugf(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxDebugf(ctx, format, v...)
|
||||
}
|
||||
|
||||
// CtxTracef calls the default logs's CtxTracef method.
|
||||
func CtxTracef(ctx context.Context, format string, v ...interface{}) {
|
||||
logger.CtxTracef(ctx, format, v...)
|
||||
}
|
||||
|
||||
type defaultLogger struct {
|
||||
stdlog *log.Logger
|
||||
level Level
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) SetOutput(w io.Writer) {
|
||||
ll.stdlog.SetOutput(w)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) SetLevel(lv Level) {
|
||||
ll.level = lv
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) logf(lv Level, format *string, v ...interface{}) {
|
||||
if ll.level > lv {
|
||||
return
|
||||
}
|
||||
msg := lv.toString()
|
||||
if format != nil {
|
||||
msg += fmt.Sprintf(*format, v...)
|
||||
} else {
|
||||
msg += fmt.Sprint(v...)
|
||||
}
|
||||
ll.stdlog.Output(4, msg)
|
||||
if lv == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) logfCtx(ctx context.Context, lv Level, format *string, v ...interface{}) {
|
||||
if ll.level > lv {
|
||||
return
|
||||
}
|
||||
msg := lv.toString()
|
||||
logID := ctx.Value("log-id")
|
||||
if logID != nil {
|
||||
msg += fmt.Sprintf("[log-id: %v] ", logID)
|
||||
}
|
||||
if format != nil {
|
||||
msg += fmt.Sprintf(*format, v...)
|
||||
} else {
|
||||
msg += fmt.Sprint(v...)
|
||||
}
|
||||
ll.stdlog.Output(4, msg)
|
||||
if lv == LevelFatal {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Fatal(v ...interface{}) {
|
||||
ll.logf(LevelFatal, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Error(v ...interface{}) {
|
||||
ll.logf(LevelError, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Warn(v ...interface{}) {
|
||||
ll.logf(LevelWarn, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Notice(v ...interface{}) {
|
||||
ll.logf(LevelNotice, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Info(v ...interface{}) {
|
||||
ll.logf(LevelInfo, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Debug(v ...interface{}) {
|
||||
ll.logf(LevelDebug, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Trace(v ...interface{}) {
|
||||
ll.logf(LevelTrace, nil, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
ll.logf(LevelFatal, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Errorf(format string, v ...interface{}) {
|
||||
ll.logf(LevelError, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Warnf(format string, v ...interface{}) {
|
||||
ll.logf(LevelWarn, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Noticef(format string, v ...interface{}) {
|
||||
ll.logf(LevelNotice, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Infof(format string, v ...interface{}) {
|
||||
ll.logf(LevelInfo, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Debugf(format string, v ...interface{}) {
|
||||
ll.logf(LevelDebug, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) Tracef(format string, v ...interface{}) {
|
||||
ll.logf(LevelTrace, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxFatalf(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelFatal, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxErrorf(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelError, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxWarnf(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelWarn, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxNoticef(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelNotice, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxInfof(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelInfo, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxDebugf(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelDebug, &format, v...)
|
||||
}
|
||||
|
||||
func (ll *defaultLogger) CtxTracef(ctx context.Context, format string, v ...interface{}) {
|
||||
ll.logfCtx(ctx, LevelTrace, &format, v...)
|
||||
}
|
||||
104
backend/pkg/logs/logger.go
Normal file
104
backend/pkg/logs/logger.go
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 logs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FormatLogger is a logs interface that output logs with a format.
|
||||
type FormatLogger interface {
|
||||
Tracef(format string, v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
Noticef(format string, v ...interface{})
|
||||
Warnf(format string, v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// Logger is a logs interface that provides logging function with levels.
|
||||
type Logger interface {
|
||||
Trace(v ...interface{})
|
||||
Debug(v ...interface{})
|
||||
Info(v ...interface{})
|
||||
Notice(v ...interface{})
|
||||
Warn(v ...interface{})
|
||||
Error(v ...interface{})
|
||||
Fatal(v ...interface{})
|
||||
}
|
||||
|
||||
// CtxLogger is a logs interface that accepts a context argument and output
|
||||
// logs with a format.
|
||||
type CtxLogger interface {
|
||||
CtxTracef(ctx context.Context, format string, v ...interface{})
|
||||
CtxDebugf(ctx context.Context, format string, v ...interface{})
|
||||
CtxInfof(ctx context.Context, format string, v ...interface{})
|
||||
CtxNoticef(ctx context.Context, format string, v ...interface{})
|
||||
CtxWarnf(ctx context.Context, format string, v ...interface{})
|
||||
CtxErrorf(ctx context.Context, format string, v ...interface{})
|
||||
CtxFatalf(ctx context.Context, format string, v ...interface{})
|
||||
}
|
||||
|
||||
// Control provides methods to config a logs.
|
||||
type Control interface {
|
||||
SetLevel(Level)
|
||||
SetOutput(io.Writer)
|
||||
}
|
||||
|
||||
// FullLogger is the combination of Logger, FormatLogger, CtxLogger and Control.
|
||||
type FullLogger interface {
|
||||
Logger
|
||||
FormatLogger
|
||||
CtxLogger
|
||||
Control
|
||||
}
|
||||
|
||||
// Level defines the priority of a log message.
|
||||
// When a logs is configured with a level, any log message with a lower
|
||||
// log level (smaller by integer comparison) will not be output.
|
||||
type Level int
|
||||
|
||||
// The levels of logs.
|
||||
const (
|
||||
LevelTrace Level = iota
|
||||
LevelDebug
|
||||
LevelInfo
|
||||
LevelNotice
|
||||
LevelWarn
|
||||
LevelError
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
var strs = []string{
|
||||
"[Trace] ",
|
||||
"[Debug] ",
|
||||
"[Info] ",
|
||||
"[Notice] ",
|
||||
"[Warn] ",
|
||||
"[Error] ",
|
||||
"[Fatal] ",
|
||||
}
|
||||
|
||||
func (lv Level) toString() string {
|
||||
if lv >= LevelTrace && lv <= LevelFatal {
|
||||
return strs[lv]
|
||||
}
|
||||
return fmt.Sprintf("[?%d] ", lv)
|
||||
}
|
||||
40
backend/pkg/safego/panic_err.go
Normal file
40
backend/pkg/safego/panic_err.go
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 safego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type panicErr struct {
|
||||
info any
|
||||
stack []byte
|
||||
}
|
||||
|
||||
func (p *panicErr) Error() string {
|
||||
return fmt.Sprintf("panic error: %v, \nstack: %s", p.info, string(p.stack))
|
||||
}
|
||||
|
||||
// NewPanicErr creates a new panic error.
|
||||
// panicErr is a wrapper of panic info and stack trace.
|
||||
// it implements the error interface, can print error message of info and stack trace.
|
||||
func NewPanicErr(info any, stack []byte) error {
|
||||
return &panicErr{
|
||||
info: info,
|
||||
stack: stack,
|
||||
}
|
||||
}
|
||||
31
backend/pkg/safego/safego.go
Normal file
31
backend/pkg/safego/safego.go
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 safego
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/goutil"
|
||||
)
|
||||
|
||||
func Go(ctx context.Context, fn func()) {
|
||||
go func() {
|
||||
defer goutil.Recovery(ctx)
|
||||
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
52
backend/pkg/sonic/sonic.go
Normal file
52
backend/pkg/sonic/sonic.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 sonic
|
||||
|
||||
import "github.com/bytedance/sonic"
|
||||
|
||||
var config = sonic.Config{
|
||||
UseInt64: true,
|
||||
}.Froze()
|
||||
|
||||
// Marshal returns the JSON encoding bytes of v.
|
||||
func Marshal(val interface{}) ([]byte, error) {
|
||||
return config.Marshal(val)
|
||||
}
|
||||
|
||||
// MarshalIndent is like Marshal but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
|
||||
return config.MarshalIndent(v, prefix, indent)
|
||||
}
|
||||
|
||||
// MarshalString returns the JSON encoding string of v.
|
||||
func MarshalString(val interface{}) (string, error) {
|
||||
return config.MarshalToString(val)
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||
// NOTICE: This API copies given buffer by default,
|
||||
// if you want to pass JSON more efficiently, use UnmarshalString instead.
|
||||
func Unmarshal(buf []byte, val interface{}) error {
|
||||
return config.Unmarshal(buf, val)
|
||||
}
|
||||
|
||||
// UnmarshalString is like Unmarshal, except buf is a string.
|
||||
func UnmarshalString(buf string, val interface{}) error {
|
||||
return config.UnmarshalFromString(buf, val)
|
||||
}
|
||||
81
backend/pkg/taskgroup/taskgroup.go
Normal file
81
backend/pkg/taskgroup/taskgroup.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 taskgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
type TaskGroup interface {
|
||||
Go(f func() error)
|
||||
Wait() error
|
||||
}
|
||||
|
||||
type taskGroup struct {
|
||||
errGroup *errgroup.Group
|
||||
ctx context.Context
|
||||
execAllTask atomic.Bool
|
||||
}
|
||||
|
||||
// NewTaskGroup if one task return error, the rest task will stop
|
||||
func NewTaskGroup(ctx context.Context, concurrentCount int) TaskGroup {
|
||||
t := &taskGroup{}
|
||||
t.errGroup, t.ctx = errgroup.WithContext(ctx)
|
||||
t.errGroup.SetLimit(concurrentCount)
|
||||
t.execAllTask.Store(false)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// NewUninterruptibleTaskGroup if one task return error, the rest task will continue
|
||||
func NewUninterruptibleTaskGroup(ctx context.Context, concurrentCount int) TaskGroup {
|
||||
t := &taskGroup{}
|
||||
t.errGroup, t.ctx = errgroup.WithContext(ctx)
|
||||
t.errGroup.SetLimit(concurrentCount)
|
||||
t.execAllTask.Store(true)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *taskGroup) Go(f func() error) {
|
||||
t.errGroup.Go(func() error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logs.CtxErrorf(t.ctx, "[TaskGroup] exec panic recover:%+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !t.execAllTask.Load() {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return t.ctx.Err()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return f()
|
||||
})
|
||||
}
|
||||
|
||||
func (t *taskGroup) Wait() error {
|
||||
return t.errGroup.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user