update codec
parent
40ea785ec9
commit
17c7c7b139
|
|
@ -9,8 +9,9 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"git.ifooth.com/common/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"git.ifooth.com/common/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
// Package codec provides encoding and decoding utilities across various formats
|
||||
package codec
|
||||
|
||||
|
|
@ -28,9 +12,8 @@ func decodeTo(r *http.Request, val any) error {
|
|||
rt := reflect.TypeOf(val).Elem()
|
||||
rv := reflect.ValueOf(val).Elem()
|
||||
|
||||
// json 整个解析
|
||||
jsonCodec := NewJsonCodec(r)
|
||||
if err := jsonCodec.Decode(val); err != nil {
|
||||
fields, err := getStructFields(rt, rv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -41,38 +24,37 @@ func decodeTo(r *http.Request, val any) error {
|
|||
|
||||
pathCodec := NewPathCodec(r)
|
||||
queryCodec := NewQueryCodec(r)
|
||||
|
||||
headerCodec := NewHeaderCodec(r)
|
||||
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
field := rt.Field(i)
|
||||
|
||||
// 非导出需要跳过, 无法设置值
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
for _, f := range fields {
|
||||
switch f.tag.In {
|
||||
case pathOptName:
|
||||
if err := pathCodec.Decode(f.field, f.fv, f.tag); err != nil {
|
||||
return fmt.Errorf("field[%s] decode path: %w", f.field.Name, err)
|
||||
}
|
||||
case queryOptName:
|
||||
if err := queryCodec.Decode(f.field, f.fv, f.tag); err != nil {
|
||||
return fmt.Errorf("field[%s] decode query: %w", f.field.Name, err)
|
||||
}
|
||||
case formOptName:
|
||||
if err := formCodec.Decode(f.field, f.fv, f.tag); err != nil {
|
||||
return fmt.Errorf("field[%s] decode form: %w", f.field.Name, err)
|
||||
}
|
||||
case headerOptName:
|
||||
if err := headerCodec.Decode(f.field, f.fv, f.tag); err != nil {
|
||||
return fmt.Errorf("field[%s] decode header: %w", f.field.Name, err)
|
||||
}
|
||||
case "":
|
||||
return fmt.Errorf("field[%s] in option is required", f.field.Name)
|
||||
default:
|
||||
return fmt.Errorf("field[%s] in[%s] option not valid", f.field.Name, f.tag.In)
|
||||
}
|
||||
}
|
||||
|
||||
tagStr := field.Tag.Get(tagName)
|
||||
if tagStr == "" {
|
||||
continue
|
||||
}
|
||||
tag, err := parseTag(tagStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fv := rv.Field(i)
|
||||
if err := formCodec.Decode(field, fv, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := queryCodec.Decode(field, fv, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := headerCodec.Decode(field, fv, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pathCodec.Decode(field, fv, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
// json 整个解析
|
||||
jsonCodec := NewJsonCodec(r)
|
||||
if err := jsonCodec.Decode(val); err != nil {
|
||||
return fmt.Errorf("decode json: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -88,12 +70,53 @@ func Decode[T any](r *http.Request) (*T, error) {
|
|||
t := new(T)
|
||||
err := decodeTo(r, t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("codec decode: %w", err)
|
||||
return nil, fmt.Errorf("decode req: %w", err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
type structField struct {
|
||||
field reflect.StructField // 结构体字段
|
||||
fv reflect.Value // 字段的值
|
||||
tag *Tag // 字段解析后的req tag
|
||||
}
|
||||
|
||||
// getStructFields 获取字段列表, 校验json/req的唯一性
|
||||
func getStructFields(rt reflect.Type, rv reflect.Value) ([]structField, error) {
|
||||
fields := make([]structField, 0)
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
field := rt.Field(i)
|
||||
|
||||
// 非导出需要跳过, 无法设置值
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
reqTagStr := field.Tag.Get(tagName)
|
||||
if reqTagStr == "" {
|
||||
continue
|
||||
}
|
||||
tag, err := parseTag(reqTagStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field[%s] %w", field.Name, err)
|
||||
}
|
||||
// tag name为空或者-忽略
|
||||
if tag.Name == "" || tag.Name == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 可以重复
|
||||
// jsonTagName := util.GetTagName(field, "json")
|
||||
// if jsonTagName != "" && jsonTagName != "-" {
|
||||
// return nil, fmt.Errorf("field[%s] req and json tag are mutually exclusive", field.Name)
|
||||
// }
|
||||
|
||||
fields = append(fields, structField{field: field, fv: rv.Field(i), tag: tag})
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// getFieldValue 获取字段值
|
||||
func getFieldValue(field reflect.Type, tag *Tag, values []string) (reflect.Value, error) {
|
||||
// 指针类型
|
||||
|
|
|
|||
|
|
@ -34,10 +34,13 @@ func NewFormCodec(r *http.Request) (*formCodec, error) {
|
|||
isForm := false
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
||||
if (r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH") &&
|
||||
strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isForm = true
|
||||
}
|
||||
|
||||
|
|
@ -54,12 +57,7 @@ func (c *formCodec) Decode(field reflect.StructField, fv reflect.Value, tag *Tag
|
|||
return nil
|
||||
}
|
||||
|
||||
formTag, ok := tag.Option["form"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := c.values[formTag]
|
||||
v := c.values[tag.Name]
|
||||
if len(v) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,13 +34,8 @@ func NewHeaderCodec(r *http.Request) *headerCodec {
|
|||
|
||||
// Decode ...
|
||||
func (c *headerCodec) Decode(field reflect.StructField, fv reflect.Value, tag *Tag) error {
|
||||
headTag, ok := tag.Option["header"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// header统一格式
|
||||
key := http.CanonicalHeaderKey(headTag)
|
||||
key := http.CanonicalHeaderKey(tag.Name)
|
||||
v := c.values[key]
|
||||
if len(v) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
|
|
@ -33,8 +17,10 @@ type jsonCodec struct {
|
|||
func NewJsonCodec(r *http.Request) *jsonCodec {
|
||||
isJson := false
|
||||
|
||||
// 限制Method, 同ParseForm的一致
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if strings.HasPrefix(contentType, "application/json") {
|
||||
if (r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH") &&
|
||||
strings.HasPrefix(contentType, "application/json") {
|
||||
isJson = true
|
||||
}
|
||||
|
||||
|
|
@ -51,11 +37,15 @@ func (j *jsonCodec) Decode(val any) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// body等于空时,可能其他解析场景,直接正常返回
|
||||
// 如果需要判断是否有值,可通过指针处理
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("json body is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, val); err != nil {
|
||||
return fmt.Errorf("unmarshal json body: %s", err)
|
||||
return fmt.Errorf("unmarshal json body: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
|
|
@ -34,12 +18,7 @@ func NewPathCodec(r *http.Request) *pathCodec {
|
|||
|
||||
// Decode ...
|
||||
func (c *pathCodec) Decode(field reflect.StructField, fv reflect.Value, tag *Tag) error {
|
||||
pathTag, ok := tag.Option["path"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
pv := c.req.PathValue(pathTag)
|
||||
pv := c.req.PathValue(tag.Name)
|
||||
if pv == "" {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
|
|
@ -35,12 +19,7 @@ func NewQueryCodec(r *http.Request) *queryCodec {
|
|||
|
||||
// Decode ...
|
||||
func (c *queryCodec) Decode(field reflect.StructField, fv reflect.Value, tag *Tag) error {
|
||||
queryTag, ok := tag.Option["query"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := c.values[queryTag]
|
||||
v := c.values[tag.Name]
|
||||
if len(v) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
|
|
@ -25,11 +9,18 @@ const (
|
|||
// tagName 结构体tag名称
|
||||
// 格式参考 https://pkg.go.dev/encoding/json/v2#example-package-FormatFlags
|
||||
tagName = "req"
|
||||
inOptName = "in"
|
||||
queryOptName = "query"
|
||||
pathOptName = "path"
|
||||
formOptName = "form"
|
||||
headerOptName = "header"
|
||||
)
|
||||
|
||||
// Tag is a struct tag
|
||||
type Tag struct {
|
||||
Option map[string]string
|
||||
Name string // tag name
|
||||
In string // query/path参数中
|
||||
Option map[string]string // 自定义参数
|
||||
}
|
||||
|
||||
func parseTag(tagStr string) (*Tag, error) {
|
||||
|
|
@ -39,11 +30,14 @@ func parseTag(tagStr string) (*Tag, error) {
|
|||
}
|
||||
|
||||
parts := strings.Split(tagStr, ",")
|
||||
name := strings.TrimSpace(parts[0])
|
||||
|
||||
t := &Tag{
|
||||
Name: name,
|
||||
Option: map[string]string{},
|
||||
}
|
||||
|
||||
for _, part := range parts {
|
||||
for _, part := range parts[1:] {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
return nil, fmt.Errorf("tag option not valid")
|
||||
|
|
@ -55,8 +49,13 @@ func parseTag(tagStr string) (*Tag, error) {
|
|||
if len(opt) == 2 {
|
||||
val = opt[1]
|
||||
}
|
||||
|
||||
if key == inOptName {
|
||||
t.In = val
|
||||
} else {
|
||||
t.Option[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||
* Licensed under the MIT License (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||||
* 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.
|
||||
* We undertake not to change the open source license (MIT license) applicable
|
||||
* to the current version of the project delivered to anyone in the future.
|
||||
*/
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
|
|
@ -175,7 +159,7 @@ func (t *Time) New(opt map[string]string) Parser {
|
|||
}
|
||||
|
||||
func init() {
|
||||
// buildin parser
|
||||
// builtin parser
|
||||
RegisterParser[string](ParserFunc(StringParser))
|
||||
RegisterParser[bool](ParserFunc(BoolParser))
|
||||
RegisterParser[int](Int[int]{0})
|
||||
|
|
|
|||
|
|
@ -51,23 +51,23 @@ func Handle[Req, Resp any](fn UnaryFunc[Req, Resp]) func(w http.ResponseWriter,
|
|||
in, err := decodeReq[Req](r)
|
||||
if err != nil {
|
||||
slog.Error("handle decode request failed", "err", err)
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if err = validateReq(r.Context(), in); err != nil {
|
||||
slog.Error("validate req failed", "err", err)
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := fn(r.Context(), in)
|
||||
if err != nil {
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
return
|
||||
}
|
||||
_ = APIOK(out).Render(w, r)
|
||||
APIOK(out).Render(w, r)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
|
@ -98,14 +98,14 @@ func Stream[Req any](fn StreamFunc[Req]) func(w http.ResponseWriter, r *http.Req
|
|||
in, err := decodeReq[Req](r)
|
||||
if err != nil {
|
||||
slog.Error("handle decode stream request failed", "err", err)
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if err = validateReq(r.Context(), in); err != nil {
|
||||
slog.Error("validate stream req failed", "err", err)
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ func Stream[Req any](fn StreamFunc[Req]) func(w http.ResponseWriter, r *http.Req
|
|||
|
||||
err = fn(in, svr)
|
||||
if err != nil {
|
||||
_ = APIError(err).Render(w, r)
|
||||
APIError(err).Render(w, r)
|
||||
}
|
||||
}
|
||||
return f
|
||||
|
|
|
|||
|
|
@ -18,12 +18,13 @@ package rest
|
|||
|
||||
import (
|
||||
"encoding/json/v2"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Renderer interface for managing response payloads.
|
||||
type Renderer interface {
|
||||
Render(w http.ResponseWriter, r *http.Request) error
|
||||
Render(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// APIResponse response for api request
|
||||
|
|
@ -35,12 +36,15 @@ type APIResponse struct {
|
|||
}
|
||||
|
||||
// Render chi render interface implementation
|
||||
func (e *APIResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
func (e *APIResponse) Render(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.WriteHeader(e.HTTPCode)
|
||||
|
||||
return json.MarshalWrite(w, e)
|
||||
err := json.MarshalWrite(w, e)
|
||||
if err != nil {
|
||||
slog.Error("render resp failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// APIOK 正常返回
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.ifooth.com/common/pkg/config"
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
|
||||
"git.ifooth.com/common/pkg/config"
|
||||
)
|
||||
|
||||
// RedisSession :
|
||||
|
|
|
|||
|
|
@ -59,16 +59,16 @@ func NewHandler(mgr *TaskManager, host string, routePrefix string) http.Handler
|
|||
type Task struct {
|
||||
*itypes.Task
|
||||
Steps []*Step `json:"steps,omitempty"`
|
||||
ExecutionDuration time.Duration `json:"executionDuration" swaggertype:"string"`
|
||||
MaxExecutionDuration time.Duration `json:"maxExecutionDuration" swaggertype:"string"`
|
||||
ExecutionDuration string `json:"executionDuration" swaggertype:"string"`
|
||||
MaxExecutionDuration string `json:"maxExecutionDuration" swaggertype:"string"`
|
||||
DetailURL string `json:"detailURL,omitempty"`
|
||||
}
|
||||
|
||||
// Step bcs_task step with execution duration
|
||||
type Step struct {
|
||||
*itypes.Step
|
||||
ExecutionDuration time.Duration `json:"executionDuration" swaggertype:"string"`
|
||||
MaxExecutionDuration time.Duration `json:"maxExecutionDuration" swaggertype:"string"`
|
||||
ExecutionDuration string `json:"executionDuration,fmt" swaggertype:"string"`
|
||||
MaxExecutionDuration string `json:"maxExecutionDuration" swaggertype:"string"`
|
||||
}
|
||||
|
||||
// StepReq ...
|
||||
|
|
@ -320,16 +320,16 @@ func (s *service) Status(ctx context.Context, req *commonReq) (*Task, error) {
|
|||
steps := lo.Map(taskData.Steps, func(v *itypes.Step, _ int) *Step {
|
||||
return &Step{
|
||||
Step: v,
|
||||
ExecutionDuration: time.Duration(v.ExecutionTime) * time.Millisecond,
|
||||
MaxExecutionDuration: time.Duration(v.MaxExecutionSeconds) * time.Second,
|
||||
ExecutionDuration: (time.Duration(v.ExecutionTime) * time.Millisecond).String(),
|
||||
MaxExecutionDuration: (time.Duration(v.MaxExecutionSeconds) * time.Second).String(),
|
||||
}
|
||||
})
|
||||
|
||||
t := &Task{
|
||||
Task: taskData,
|
||||
Steps: steps,
|
||||
ExecutionDuration: time.Duration(taskData.ExecutionTime) * time.Millisecond,
|
||||
MaxExecutionDuration: time.Duration(taskData.MaxExecutionSeconds) * time.Second,
|
||||
ExecutionDuration: (time.Duration(taskData.ExecutionTime) * time.Millisecond).String(),
|
||||
MaxExecutionDuration: (time.Duration(taskData.MaxExecutionSeconds) * time.Second).String(),
|
||||
}
|
||||
|
||||
return t, nil
|
||||
|
|
@ -404,8 +404,8 @@ func (s *service) List(ctx context.Context, req *ListReq) (*rest.PaginationResp[
|
|||
items := lo.Map(result.Items, func(v *itypes.Task, _ int) Task {
|
||||
return Task{
|
||||
Task: v,
|
||||
ExecutionDuration: time.Duration(v.ExecutionTime) * time.Millisecond,
|
||||
MaxExecutionDuration: time.Duration(v.MaxExecutionSeconds) * time.Second,
|
||||
ExecutionDuration: (time.Duration(v.ExecutionTime) * time.Millisecond).String(),
|
||||
MaxExecutionDuration: (time.Duration(v.MaxExecutionSeconds) * time.Second).String(),
|
||||
DetailURL: fmt.Sprintf("%s?taskID=%s", detailURL, v.TaskID),
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// Package util provides common utility functions and helpers for various operations across the project.
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTagName extracts the first comma-separated value from a struct field's tag.
|
||||
// For example, given a tag `json:"name,omitempty"`, it returns "name".
|
||||
func GetTagName(field reflect.StructField, tag string) string {
|
||||
name := strings.SplitN(field.Tag.Get(tag), ",", 2)[0]
|
||||
return name
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ package validator
|
|||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/locales/en"
|
||||
"github.com/go-playground/locales/zh"
|
||||
|
|
@ -15,6 +14,7 @@ import (
|
|||
"github.com/samber/lo"
|
||||
|
||||
"git.ifooth.com/common/pkg/i18n"
|
||||
"git.ifooth.com/common/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -73,18 +73,24 @@ func Struct(ctx context.Context, s any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// tagNameFunc 优先从 json tag 获取名称
|
||||
func tagNameFunc(fld reflect.StructField) string {
|
||||
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
// readableTagName 返回可读的json/req校验字段名称, 唯一性由codec校验
|
||||
func readableTagName(field reflect.StructField) string {
|
||||
name := util.GetTagName(field, "json")
|
||||
if name != "" && name != "-" {
|
||||
return name
|
||||
}
|
||||
|
||||
name = util.GetTagName(field, "req")
|
||||
if name != "" && name != "-" {
|
||||
return name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
validate.RegisterTagNameFunc(tagNameFunc)
|
||||
validate.RegisterTagNameFunc(readableTagName)
|
||||
|
||||
// 默认使用英文
|
||||
en := en.New()
|
||||
|
|
|
|||
Loading…
Reference in New Issue