diff --git a/bff/internal/request/user.go b/bff/internal/request/user.go index f4a498c..483b255 100644 --- a/bff/internal/request/user.go +++ b/bff/internal/request/user.go @@ -43,7 +43,8 @@ const ( var callbackPrefixes = []string{ "/api/v3/express/", - "/api/v1/oss/callback", + "/api/v2/analysis/oss/callback", + "/api/v2/analysis/py/callback", "/api/v3/other1/", "/api/v3/other2/", } diff --git a/go.mod b/go.mod index b852c63..e7c3bd2 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/disintegration/imaging v1.6.2 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect @@ -141,6 +142,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect + golang.org/x/image v0.38.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.21.0 // indirect diff --git a/go.sum b/go.sum index 6f8a2eb..0a9193e 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -426,6 +428,7 @@ golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsi golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/pkg/oss/oss.go b/pkg/oss/oss.go index 4201770..8bcceef 100644 --- a/pkg/oss/oss.go +++ b/pkg/oss/oss.go @@ -36,6 +36,7 @@ type UploadInput struct { Filename string Content []byte ContentType string + Dir string // 可选,覆盖配置 oss.dir } func LoadConfig() Config { @@ -84,7 +85,11 @@ func Upload(in UploadInput) (*Result, error) { return nil, fmt.Errorf("file too large, max %d bytes", MaxUploadBytes) } - objectKey := buildObjectKey(cfg.Dir, in.Filename) + dir := cfg.Dir + if strings.TrimSpace(in.Dir) != "" { + dir = in.Dir + } + objectKey := buildObjectKey(dir, in.Filename) endpoint := normalizeEndpoint(cfg.Endpoint, cfg.BucketName) client, err := alioss.New(endpoint, cfg.AccessKeyId, cfg.AccessKeySecret) diff --git a/pkg/utils/time.go b/pkg/utils/time.go index abab91f..9a2ae63 100644 --- a/pkg/utils/time.go +++ b/pkg/utils/time.go @@ -228,11 +228,15 @@ func ParseCustomTime(s string) (CustomTime, error) { return ct, nil } -// ParseDateOnly 解析 YYYY-MM-DD;空串返回零值(写入 DB 为 NULL) +// ParseDateOnly 解析 YYYY-MM-DD;兼容带时间的字符串,只取日期部分;空串返回零值(写入 DB 为 NULL) func ParseDateOnly(s string) (CustomTime, error) { - if s == "" { + s = strings.TrimSpace(s) + if s == "" || isZeroDate(s) { return CustomTime{}, nil } + if len(s) >= len(time.DateOnly) { + s = s[:len(time.DateOnly)] + } t, err := time.ParseInLocation(time.DateOnly, s, AsiaShanghai) if err != nil { return CustomTime{}, fmt.Errorf("invalid date format: %s", s) diff --git a/rpc/analysis/analysis.pb b/rpc/analysis/analysis.pb index 3d9d667..7a82565 100644 Binary files a/rpc/analysis/analysis.pb and b/rpc/analysis/analysis.pb differ diff --git a/rpc/analysis/analysis.proto b/rpc/analysis/analysis.proto index 05915dc..6acbdca 100644 --- a/rpc/analysis/analysis.proto +++ b/rpc/analysis/analysis.proto @@ -290,7 +290,14 @@ service AnalysisDevice { rpc OssCallback(OssCallbackReq) returns (OssCallbackResp) { option (google.api.http) = { - post: "/api/v1/oss/callback" + post: "/api/v2/analysis/oss/callback" + body: "*" + }; + } + + rpc PyCallback(PyCallbackReq) returns (Response) { + option (google.api.http) = { + post: "/api/v2/analysis/py/callback" body: "*" }; } @@ -426,6 +433,10 @@ message OssCallbackResp { string Status = 1 [json_name = "Status"]; } +message PyCallbackReq { + string task_id = 1; // python 回传的是 sub_task_id +} + message UserLogDeleteReq { repeated int64 ids = 1; } diff --git a/rpc/analysis/pb/analysis.pb.go b/rpc/analysis/pb/analysis.pb.go index 3c6e37a..e9d8c90 100644 --- a/rpc/analysis/pb/analysis.pb.go +++ b/rpc/analysis/pb/analysis.pb.go @@ -1438,6 +1438,50 @@ func (x *OssCallbackResp) GetStatus() string { return "" } +type PyCallbackReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` // python 回传的是 sub_task_id + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PyCallbackReq) Reset() { + *x = PyCallbackReq{} + mi := &file_analysis_analysis_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PyCallbackReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PyCallbackReq) ProtoMessage() {} + +func (x *PyCallbackReq) ProtoReflect() protoreflect.Message { + mi := &file_analysis_analysis_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PyCallbackReq.ProtoReflect.Descriptor instead. +func (*PyCallbackReq) Descriptor() ([]byte, []int) { + return file_analysis_analysis_proto_rawDescGZIP(), []int{28} +} + +func (x *PyCallbackReq) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + type UserLogDeleteReq struct { state protoimpl.MessageState `protogen:"open.v1"` Ids []int64 `protobuf:"varint,1,rep,packed,name=ids,proto3" json:"ids,omitempty"` @@ -1447,7 +1491,7 @@ type UserLogDeleteReq struct { func (x *UserLogDeleteReq) Reset() { *x = UserLogDeleteReq{} - mi := &file_analysis_analysis_proto_msgTypes[28] + mi := &file_analysis_analysis_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1459,7 +1503,7 @@ func (x *UserLogDeleteReq) String() string { func (*UserLogDeleteReq) ProtoMessage() {} func (x *UserLogDeleteReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[28] + mi := &file_analysis_analysis_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1472,7 +1516,7 @@ func (x *UserLogDeleteReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UserLogDeleteReq.ProtoReflect.Descriptor instead. func (*UserLogDeleteReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{28} + return file_analysis_analysis_proto_rawDescGZIP(), []int{29} } func (x *UserLogDeleteReq) GetIds() []int64 { @@ -1490,7 +1534,7 @@ type UserStatusReq struct { func (x *UserStatusReq) Reset() { *x = UserStatusReq{} - mi := &file_analysis_analysis_proto_msgTypes[29] + mi := &file_analysis_analysis_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1502,7 +1546,7 @@ func (x *UserStatusReq) String() string { func (*UserStatusReq) ProtoMessage() {} func (x *UserStatusReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[29] + mi := &file_analysis_analysis_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1515,7 +1559,7 @@ func (x *UserStatusReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UserStatusReq.ProtoReflect.Descriptor instead. func (*UserStatusReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{29} + return file_analysis_analysis_proto_rawDescGZIP(), []int{30} } type EmptyReq struct { @@ -1526,7 +1570,7 @@ type EmptyReq struct { func (x *EmptyReq) Reset() { *x = EmptyReq{} - mi := &file_analysis_analysis_proto_msgTypes[30] + mi := &file_analysis_analysis_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1538,7 +1582,7 @@ func (x *EmptyReq) String() string { func (*EmptyReq) ProtoMessage() {} func (x *EmptyReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[30] + mi := &file_analysis_analysis_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1551,7 +1595,7 @@ func (x *EmptyReq) ProtoReflect() protoreflect.Message { // Deprecated: Use EmptyReq.ProtoReflect.Descriptor instead. func (*EmptyReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{30} + return file_analysis_analysis_proto_rawDescGZIP(), []int{31} } type LogReq struct { @@ -1565,7 +1609,7 @@ type LogReq struct { func (x *LogReq) Reset() { *x = LogReq{} - mi := &file_analysis_analysis_proto_msgTypes[31] + mi := &file_analysis_analysis_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1577,7 +1621,7 @@ func (x *LogReq) String() string { func (*LogReq) ProtoMessage() {} func (x *LogReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[31] + mi := &file_analysis_analysis_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1590,7 +1634,7 @@ func (x *LogReq) ProtoReflect() protoreflect.Message { // Deprecated: Use LogReq.ProtoReflect.Descriptor instead. func (*LogReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{31} + return file_analysis_analysis_proto_rawDescGZIP(), []int{32} } func (x *LogReq) GetUserId() int64 { @@ -1624,7 +1668,7 @@ type OverviewReq struct { func (x *OverviewReq) Reset() { *x = OverviewReq{} - mi := &file_analysis_analysis_proto_msgTypes[32] + mi := &file_analysis_analysis_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1636,7 +1680,7 @@ func (x *OverviewReq) String() string { func (*OverviewReq) ProtoMessage() {} func (x *OverviewReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[32] + mi := &file_analysis_analysis_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1649,7 +1693,7 @@ func (x *OverviewReq) ProtoReflect() protoreflect.Message { // Deprecated: Use OverviewReq.ProtoReflect.Descriptor instead. func (*OverviewReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{32} + return file_analysis_analysis_proto_rawDescGZIP(), []int{33} } func (x *OverviewReq) GetUserId() int64 { @@ -1675,7 +1719,7 @@ type UserReq struct { func (x *UserReq) Reset() { *x = UserReq{} - mi := &file_analysis_analysis_proto_msgTypes[33] + mi := &file_analysis_analysis_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1687,7 +1731,7 @@ func (x *UserReq) String() string { func (*UserReq) ProtoMessage() {} func (x *UserReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[33] + mi := &file_analysis_analysis_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1700,7 +1744,7 @@ func (x *UserReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UserReq.ProtoReflect.Descriptor instead. func (*UserReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{33} + return file_analysis_analysis_proto_rawDescGZIP(), []int{34} } func (x *UserReq) GetUserId() int64 { @@ -1719,7 +1763,7 @@ type UserDetailReq struct { func (x *UserDetailReq) Reset() { *x = UserDetailReq{} - mi := &file_analysis_analysis_proto_msgTypes[34] + mi := &file_analysis_analysis_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1731,7 +1775,7 @@ func (x *UserDetailReq) String() string { func (*UserDetailReq) ProtoMessage() {} func (x *UserDetailReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[34] + mi := &file_analysis_analysis_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1744,7 +1788,7 @@ func (x *UserDetailReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UserDetailReq.ProtoReflect.Descriptor instead. func (*UserDetailReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{34} + return file_analysis_analysis_proto_rawDescGZIP(), []int{35} } func (x *UserDetailReq) GetId() int64 { @@ -1763,7 +1807,7 @@ type TaskIdReq struct { func (x *TaskIdReq) Reset() { *x = TaskIdReq{} - mi := &file_analysis_analysis_proto_msgTypes[35] + mi := &file_analysis_analysis_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1775,7 +1819,7 @@ func (x *TaskIdReq) String() string { func (*TaskIdReq) ProtoMessage() {} func (x *TaskIdReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[35] + mi := &file_analysis_analysis_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1788,7 +1832,7 @@ func (x *TaskIdReq) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskIdReq.ProtoReflect.Descriptor instead. func (*TaskIdReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{35} + return file_analysis_analysis_proto_rawDescGZIP(), []int{36} } func (x *TaskIdReq) GetTaskId() string { @@ -1807,7 +1851,7 @@ type QrcodeShareReq struct { func (x *QrcodeShareReq) Reset() { *x = QrcodeShareReq{} - mi := &file_analysis_analysis_proto_msgTypes[36] + mi := &file_analysis_analysis_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1819,7 +1863,7 @@ func (x *QrcodeShareReq) String() string { func (*QrcodeShareReq) ProtoMessage() {} func (x *QrcodeShareReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[36] + mi := &file_analysis_analysis_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1832,7 +1876,7 @@ func (x *QrcodeShareReq) ProtoReflect() protoreflect.Message { // Deprecated: Use QrcodeShareReq.ProtoReflect.Descriptor instead. func (*QrcodeShareReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{36} + return file_analysis_analysis_proto_rawDescGZIP(), []int{37} } func (x *QrcodeShareReq) GetIds() []int64 { @@ -1851,7 +1895,7 @@ type GraphDetailReq struct { func (x *GraphDetailReq) Reset() { *x = GraphDetailReq{} - mi := &file_analysis_analysis_proto_msgTypes[37] + mi := &file_analysis_analysis_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1863,7 +1907,7 @@ func (x *GraphDetailReq) String() string { func (*GraphDetailReq) ProtoMessage() {} func (x *GraphDetailReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[37] + mi := &file_analysis_analysis_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1876,7 +1920,7 @@ func (x *GraphDetailReq) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphDetailReq.ProtoReflect.Descriptor instead. func (*GraphDetailReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{37} + return file_analysis_analysis_proto_rawDescGZIP(), []int{38} } func (x *GraphDetailReq) GetType() int32 { @@ -1896,7 +1940,7 @@ type FaceppMapBoxesReq struct { func (x *FaceppMapBoxesReq) Reset() { *x = FaceppMapBoxesReq{} - mi := &file_analysis_analysis_proto_msgTypes[38] + mi := &file_analysis_analysis_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1908,7 +1952,7 @@ func (x *FaceppMapBoxesReq) String() string { func (*FaceppMapBoxesReq) ProtoMessage() {} func (x *FaceppMapBoxesReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[38] + mi := &file_analysis_analysis_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1921,7 +1965,7 @@ func (x *FaceppMapBoxesReq) ProtoReflect() protoreflect.Message { // Deprecated: Use FaceppMapBoxesReq.ProtoReflect.Descriptor instead. func (*FaceppMapBoxesReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{38} + return file_analysis_analysis_proto_rawDescGZIP(), []int{39} } func (x *FaceppMapBoxesReq) GetId() int64 { @@ -1947,7 +1991,7 @@ type IdReq struct { func (x *IdReq) Reset() { *x = IdReq{} - mi := &file_analysis_analysis_proto_msgTypes[39] + mi := &file_analysis_analysis_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1959,7 +2003,7 @@ func (x *IdReq) String() string { func (*IdReq) ProtoMessage() {} func (x *IdReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[39] + mi := &file_analysis_analysis_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1972,7 +2016,7 @@ func (x *IdReq) ProtoReflect() protoreflect.Message { // Deprecated: Use IdReq.ProtoReflect.Descriptor instead. func (*IdReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{39} + return file_analysis_analysis_proto_rawDescGZIP(), []int{40} } func (x *IdReq) GetId() int64 { @@ -1993,7 +2037,7 @@ type ImagesDateReq struct { func (x *ImagesDateReq) Reset() { *x = ImagesDateReq{} - mi := &file_analysis_analysis_proto_msgTypes[40] + mi := &file_analysis_analysis_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2005,7 +2049,7 @@ func (x *ImagesDateReq) String() string { func (*ImagesDateReq) ProtoMessage() {} func (x *ImagesDateReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[40] + mi := &file_analysis_analysis_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2018,7 +2062,7 @@ func (x *ImagesDateReq) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagesDateReq.ProtoReflect.Descriptor instead. func (*ImagesDateReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{40} + return file_analysis_analysis_proto_rawDescGZIP(), []int{41} } func (x *ImagesDateReq) GetUserId() int64 { @@ -2055,7 +2099,7 @@ type ImagesListReq struct { func (x *ImagesListReq) Reset() { *x = ImagesListReq{} - mi := &file_analysis_analysis_proto_msgTypes[41] + mi := &file_analysis_analysis_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2067,7 +2111,7 @@ func (x *ImagesListReq) String() string { func (*ImagesListReq) ProtoMessage() {} func (x *ImagesListReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[41] + mi := &file_analysis_analysis_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2080,7 +2124,7 @@ func (x *ImagesListReq) ProtoReflect() protoreflect.Message { // Deprecated: Use ImagesListReq.ProtoReflect.Descriptor instead. func (*ImagesListReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{41} + return file_analysis_analysis_proto_rawDescGZIP(), []int{42} } func (x *ImagesListReq) GetUserId() int64 { @@ -2128,7 +2172,7 @@ type DeviceUpdateReq struct { func (x *DeviceUpdateReq) Reset() { *x = DeviceUpdateReq{} - mi := &file_analysis_analysis_proto_msgTypes[42] + mi := &file_analysis_analysis_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2140,7 +2184,7 @@ func (x *DeviceUpdateReq) String() string { func (*DeviceUpdateReq) ProtoMessage() {} func (x *DeviceUpdateReq) ProtoReflect() protoreflect.Message { - mi := &file_analysis_analysis_proto_msgTypes[42] + mi := &file_analysis_analysis_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2153,7 +2197,7 @@ func (x *DeviceUpdateReq) ProtoReflect() protoreflect.Message { // Deprecated: Use DeviceUpdateReq.ProtoReflect.Descriptor instead. func (*DeviceUpdateReq) Descriptor() ([]byte, []int) { - return file_analysis_analysis_proto_rawDescGZIP(), []int{42} + return file_analysis_analysis_proto_rawDescGZIP(), []int{43} } func (x *DeviceUpdateReq) GetId() int64 { @@ -2256,7 +2300,9 @@ const file_analysis_analysis_proto_rawDesc = "" + "\bmimeType\x18\x05 \x01(\tR\bmimeType\x12\x17\n" + "\atask_id\x18\x06 \x01(\tR\x06taskId\")\n" + "\x0fOssCallbackResp\x12\x16\n" + - "\x06Status\x18\x01 \x01(\tR\x06Status\"$\n" + + "\x06Status\x18\x01 \x01(\tR\x06Status\"(\n" + + "\rPyCallbackReq\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\"$\n" + "\x10UserLogDeleteReq\x12\x10\n" + "\x03ids\x18\x01 \x03(\x03R\x03ids\"\x0f\n" + "\rUserStatusReq\"\n" + @@ -2351,11 +2397,13 @@ const file_analysis_analysis_proto_rawDesc = "" + "\tStoreInfo\x12\x16.analysis.StoreInfoReq\x1a\x12.analysis.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/v2/analysis/store/info\x12g\n" + "\rStoreUserList\x12\x1a.analysis.StoreUserListReq\x1a\x12.analysis.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/v2/analysis/store/list\x12r\n" + "\x0fStoreUserUpdate\x12\x1c.analysis.StoreUserUpdateReq\x1a\x12.analysis.Response\"-\x82\xd3\xe4\x93\x02':\x01*\"\"/api/v2/analysis/store/user/update\x12l\n" + - "\rStoreUserInfo\x12\x1a.analysis.StoreUserInfoReq\x1a\x12.analysis.Response\"+\x82\xd3\xe4\x93\x02%:\x01*\" /api/v2/analysis/store/user/info2\xb8\x02\n" + + "\rStoreUserInfo\x12\x1a.analysis.StoreUserInfoReq\x1a\x12.analysis.Response\"+\x82\xd3\xe4\x93\x02%:\x01*\" /api/v2/analysis/store/user/info2\xa5\x03\n" + "\x0eAnalysisDevice\x12^\n" + "\x06Policy\x12\x19.analysis.DevicePolicyReq\x1a\x12.analysis.Response\"%\x82\xd3\xe4\x93\x02\x1f:\x01*\"\x1a/device/v1/analysis/policy\x12a\n" + - "\aDisplay\x12\x1a.analysis.DeviceDisplayReq\x1a\x12.analysis.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/device/v1/analysis/display\x12c\n" + - "\vOssCallback\x12\x18.analysis.OssCallbackReq\x1a\x19.analysis.OssCallbackResp\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/oss/callbackB\x1cZ\x1alone-services/rpc/analysisb\x06proto3" + "\aDisplay\x12\x1a.analysis.DeviceDisplayReq\x1a\x12.analysis.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/device/v1/analysis/display\x12l\n" + + "\vOssCallback\x12\x18.analysis.OssCallbackReq\x1a\x19.analysis.OssCallbackResp\"(\x82\xd3\xe4\x93\x02\":\x01*\"\x1d/api/v2/analysis/oss/callback\x12b\n" + + "\n" + + "PyCallback\x12\x17.analysis.PyCallbackReq\x1a\x12.analysis.Response\"'\x82\xd3\xe4\x93\x02!:\x01*\"\x1c/api/v2/analysis/py/callbackB\x1cZ\x1alone-services/rpc/analysisb\x06proto3" var ( file_analysis_analysis_proto_rawDescOnce sync.Once @@ -2369,7 +2417,7 @@ func file_analysis_analysis_proto_rawDescGZIP() []byte { return file_analysis_analysis_proto_rawDescData } -var file_analysis_analysis_proto_msgTypes = make([]protoimpl.MessageInfo, 43) +var file_analysis_analysis_proto_msgTypes = make([]protoimpl.MessageInfo, 44) var file_analysis_analysis_proto_goTypes = []any{ (*Response)(nil), // 0: analysis.Response (*InternalCreateUserReq)(nil), // 1: analysis.InternalCreateUserReq @@ -2399,21 +2447,22 @@ var file_analysis_analysis_proto_goTypes = []any{ (*DeviceDisplayReq)(nil), // 25: analysis.DeviceDisplayReq (*OssCallbackReq)(nil), // 26: analysis.OssCallbackReq (*OssCallbackResp)(nil), // 27: analysis.OssCallbackResp - (*UserLogDeleteReq)(nil), // 28: analysis.UserLogDeleteReq - (*UserStatusReq)(nil), // 29: analysis.UserStatusReq - (*EmptyReq)(nil), // 30: analysis.EmptyReq - (*LogReq)(nil), // 31: analysis.LogReq - (*OverviewReq)(nil), // 32: analysis.OverviewReq - (*UserReq)(nil), // 33: analysis.UserReq - (*UserDetailReq)(nil), // 34: analysis.UserDetailReq - (*TaskIdReq)(nil), // 35: analysis.TaskIdReq - (*QrcodeShareReq)(nil), // 36: analysis.QrcodeShareReq - (*GraphDetailReq)(nil), // 37: analysis.GraphDetailReq - (*FaceppMapBoxesReq)(nil), // 38: analysis.FaceppMapBoxesReq - (*IdReq)(nil), // 39: analysis.IdReq - (*ImagesDateReq)(nil), // 40: analysis.ImagesDateReq - (*ImagesListReq)(nil), // 41: analysis.ImagesListReq - (*DeviceUpdateReq)(nil), // 42: analysis.DeviceUpdateReq + (*PyCallbackReq)(nil), // 28: analysis.PyCallbackReq + (*UserLogDeleteReq)(nil), // 29: analysis.UserLogDeleteReq + (*UserStatusReq)(nil), // 30: analysis.UserStatusReq + (*EmptyReq)(nil), // 31: analysis.EmptyReq + (*LogReq)(nil), // 32: analysis.LogReq + (*OverviewReq)(nil), // 33: analysis.OverviewReq + (*UserReq)(nil), // 34: analysis.UserReq + (*UserDetailReq)(nil), // 35: analysis.UserDetailReq + (*TaskIdReq)(nil), // 36: analysis.TaskIdReq + (*QrcodeShareReq)(nil), // 37: analysis.QrcodeShareReq + (*GraphDetailReq)(nil), // 38: analysis.GraphDetailReq + (*FaceppMapBoxesReq)(nil), // 39: analysis.FaceppMapBoxesReq + (*IdReq)(nil), // 40: analysis.IdReq + (*ImagesDateReq)(nil), // 41: analysis.ImagesDateReq + (*ImagesListReq)(nil), // 42: analysis.ImagesListReq + (*DeviceUpdateReq)(nil), // 43: analysis.DeviceUpdateReq } var file_analysis_analysis_proto_depIdxs = []int32{ 1, // 0: analysis.Analysis.InternalCreateUser:input_type -> analysis.InternalCreateUserReq @@ -2421,23 +2470,23 @@ var file_analysis_analysis_proto_depIdxs = []int32{ 7, // 2: analysis.Analysis.AgreementInfo:input_type -> analysis.AgreementInfoReq 9, // 3: analysis.Analysis.Switch:input_type -> analysis.SwitchReq 10, // 4: analysis.Analysis.QrcodeInfo:input_type -> analysis.QrcodeInfoReq - 31, // 5: analysis.Analysis.Log:input_type -> analysis.LogReq - 32, // 6: analysis.Analysis.Overview:input_type -> analysis.OverviewReq - 33, // 7: analysis.Analysis.User:input_type -> analysis.UserReq - 34, // 8: analysis.Analysis.UserDetail:input_type -> analysis.UserDetailReq - 35, // 9: analysis.Analysis.UserFacepp:input_type -> analysis.TaskIdReq - 30, // 10: analysis.Analysis.UserMode:input_type -> analysis.EmptyReq - 31, // 11: analysis.Analysis.UserLog:input_type -> analysis.LogReq - 30, // 12: analysis.Analysis.Banners:input_type -> analysis.EmptyReq - 36, // 13: analysis.Analysis.QrcodeShare:input_type -> analysis.QrcodeShareReq - 37, // 14: analysis.Analysis.GraphDetail:input_type -> analysis.GraphDetailReq - 35, // 15: analysis.Analysis.FaceppMaps:input_type -> analysis.TaskIdReq - 38, // 16: analysis.Analysis.FaceppMapBoxes:input_type -> analysis.FaceppMapBoxesReq - 39, // 17: analysis.Analysis.FaceppMapBoxDelete:input_type -> analysis.IdReq - 40, // 18: analysis.Analysis.ImagesDate:input_type -> analysis.ImagesDateReq - 41, // 19: analysis.Analysis.ImagesList:input_type -> analysis.ImagesListReq - 35, // 20: analysis.Analysis.ModeImage:input_type -> analysis.TaskIdReq - 42, // 21: analysis.Analysis.DeviceUpdate:input_type -> analysis.DeviceUpdateReq + 32, // 5: analysis.Analysis.Log:input_type -> analysis.LogReq + 33, // 6: analysis.Analysis.Overview:input_type -> analysis.OverviewReq + 34, // 7: analysis.Analysis.User:input_type -> analysis.UserReq + 35, // 8: analysis.Analysis.UserDetail:input_type -> analysis.UserDetailReq + 36, // 9: analysis.Analysis.UserFacepp:input_type -> analysis.TaskIdReq + 31, // 10: analysis.Analysis.UserMode:input_type -> analysis.EmptyReq + 32, // 11: analysis.Analysis.UserLog:input_type -> analysis.LogReq + 31, // 12: analysis.Analysis.Banners:input_type -> analysis.EmptyReq + 37, // 13: analysis.Analysis.QrcodeShare:input_type -> analysis.QrcodeShareReq + 38, // 14: analysis.Analysis.GraphDetail:input_type -> analysis.GraphDetailReq + 36, // 15: analysis.Analysis.FaceppMaps:input_type -> analysis.TaskIdReq + 39, // 16: analysis.Analysis.FaceppMapBoxes:input_type -> analysis.FaceppMapBoxesReq + 40, // 17: analysis.Analysis.FaceppMapBoxDelete:input_type -> analysis.IdReq + 41, // 18: analysis.Analysis.ImagesDate:input_type -> analysis.ImagesDateReq + 42, // 19: analysis.Analysis.ImagesList:input_type -> analysis.ImagesListReq + 36, // 20: analysis.Analysis.ModeImage:input_type -> analysis.TaskIdReq + 43, // 21: analysis.Analysis.DeviceUpdate:input_type -> analysis.DeviceUpdateReq 6, // 22: analysis.AnalysisUser.FaceBind:input_type -> analysis.FaceBindReq 5, // 23: analysis.AnalysisUser.UserImprove:input_type -> analysis.UserImproveReq 8, // 24: analysis.AnalysisUser.UserDelete:input_type -> analysis.UserDeleteReq @@ -2447,8 +2496,8 @@ var file_analysis_analysis_proto_depIdxs = []int32{ 21, // 28: analysis.AnalysisUser.UserAuthUpdate:input_type -> analysis.UserAuthUpdateReq 22, // 29: analysis.AnalysisUser.DeviceBind:input_type -> analysis.DeviceBindReq 23, // 30: analysis.AnalysisUser.DeviceInfo:input_type -> analysis.DeviceInfoReq - 28, // 31: analysis.AnalysisUser.UserLogDelete:input_type -> analysis.UserLogDeleteReq - 29, // 32: analysis.AnalysisUser.UserStatus:input_type -> analysis.UserStatusReq + 29, // 31: analysis.AnalysisUser.UserLogDelete:input_type -> analysis.UserLogDeleteReq + 30, // 32: analysis.AnalysisUser.UserStatus:input_type -> analysis.UserStatusReq 11, // 33: analysis.AnalysisStore.StoreAuth:input_type -> analysis.StoreAuthReq 12, // 34: analysis.AnalysisStore.StoreAuthQrcode:input_type -> analysis.StoreAuthQrcodeReq 13, // 35: analysis.AnalysisStore.StoreInfo:input_type -> analysis.StoreInfoReq @@ -2458,50 +2507,52 @@ var file_analysis_analysis_proto_depIdxs = []int32{ 24, // 39: analysis.AnalysisDevice.Policy:input_type -> analysis.DevicePolicyReq 25, // 40: analysis.AnalysisDevice.Display:input_type -> analysis.DeviceDisplayReq 26, // 41: analysis.AnalysisDevice.OssCallback:input_type -> analysis.OssCallbackReq - 2, // 42: analysis.Analysis.InternalCreateUser:output_type -> analysis.InternalCreateUserData - 4, // 43: analysis.Analysis.InternalSetRegistered:output_type -> analysis.InternalSetRegisteredData - 0, // 44: analysis.Analysis.AgreementInfo:output_type -> analysis.Response - 0, // 45: analysis.Analysis.Switch:output_type -> analysis.Response - 0, // 46: analysis.Analysis.QrcodeInfo:output_type -> analysis.Response - 0, // 47: analysis.Analysis.Log:output_type -> analysis.Response - 0, // 48: analysis.Analysis.Overview:output_type -> analysis.Response - 0, // 49: analysis.Analysis.User:output_type -> analysis.Response - 0, // 50: analysis.Analysis.UserDetail:output_type -> analysis.Response - 0, // 51: analysis.Analysis.UserFacepp:output_type -> analysis.Response - 0, // 52: analysis.Analysis.UserMode:output_type -> analysis.Response - 0, // 53: analysis.Analysis.UserLog:output_type -> analysis.Response - 0, // 54: analysis.Analysis.Banners:output_type -> analysis.Response - 0, // 55: analysis.Analysis.QrcodeShare:output_type -> analysis.Response - 0, // 56: analysis.Analysis.GraphDetail:output_type -> analysis.Response - 0, // 57: analysis.Analysis.FaceppMaps:output_type -> analysis.Response - 0, // 58: analysis.Analysis.FaceppMapBoxes:output_type -> analysis.Response - 0, // 59: analysis.Analysis.FaceppMapBoxDelete:output_type -> analysis.Response - 0, // 60: analysis.Analysis.ImagesDate:output_type -> analysis.Response - 0, // 61: analysis.Analysis.ImagesList:output_type -> analysis.Response - 0, // 62: analysis.Analysis.ModeImage:output_type -> analysis.Response - 0, // 63: analysis.Analysis.DeviceUpdate:output_type -> analysis.Response - 0, // 64: analysis.AnalysisUser.FaceBind:output_type -> analysis.Response - 0, // 65: analysis.AnalysisUser.UserImprove:output_type -> analysis.Response - 0, // 66: analysis.AnalysisUser.UserDelete:output_type -> analysis.Response - 0, // 67: analysis.AnalysisUser.UserAuth:output_type -> analysis.Response - 0, // 68: analysis.AnalysisUser.UserAuthList:output_type -> analysis.Response - 0, // 69: analysis.AnalysisUser.UserAuthDelete:output_type -> analysis.Response - 0, // 70: analysis.AnalysisUser.UserAuthUpdate:output_type -> analysis.Response - 0, // 71: analysis.AnalysisUser.DeviceBind:output_type -> analysis.Response - 0, // 72: analysis.AnalysisUser.DeviceInfo:output_type -> analysis.Response - 0, // 73: analysis.AnalysisUser.UserLogDelete:output_type -> analysis.Response - 0, // 74: analysis.AnalysisUser.UserStatus:output_type -> analysis.Response - 0, // 75: analysis.AnalysisStore.StoreAuth:output_type -> analysis.Response - 0, // 76: analysis.AnalysisStore.StoreAuthQrcode:output_type -> analysis.Response - 0, // 77: analysis.AnalysisStore.StoreInfo:output_type -> analysis.Response - 0, // 78: analysis.AnalysisStore.StoreUserList:output_type -> analysis.Response - 0, // 79: analysis.AnalysisStore.StoreUserUpdate:output_type -> analysis.Response - 0, // 80: analysis.AnalysisStore.StoreUserInfo:output_type -> analysis.Response - 0, // 81: analysis.AnalysisDevice.Policy:output_type -> analysis.Response - 0, // 82: analysis.AnalysisDevice.Display:output_type -> analysis.Response - 27, // 83: analysis.AnalysisDevice.OssCallback:output_type -> analysis.OssCallbackResp - 42, // [42:84] is the sub-list for method output_type - 0, // [0:42] is the sub-list for method input_type + 28, // 42: analysis.AnalysisDevice.PyCallback:input_type -> analysis.PyCallbackReq + 2, // 43: analysis.Analysis.InternalCreateUser:output_type -> analysis.InternalCreateUserData + 4, // 44: analysis.Analysis.InternalSetRegistered:output_type -> analysis.InternalSetRegisteredData + 0, // 45: analysis.Analysis.AgreementInfo:output_type -> analysis.Response + 0, // 46: analysis.Analysis.Switch:output_type -> analysis.Response + 0, // 47: analysis.Analysis.QrcodeInfo:output_type -> analysis.Response + 0, // 48: analysis.Analysis.Log:output_type -> analysis.Response + 0, // 49: analysis.Analysis.Overview:output_type -> analysis.Response + 0, // 50: analysis.Analysis.User:output_type -> analysis.Response + 0, // 51: analysis.Analysis.UserDetail:output_type -> analysis.Response + 0, // 52: analysis.Analysis.UserFacepp:output_type -> analysis.Response + 0, // 53: analysis.Analysis.UserMode:output_type -> analysis.Response + 0, // 54: analysis.Analysis.UserLog:output_type -> analysis.Response + 0, // 55: analysis.Analysis.Banners:output_type -> analysis.Response + 0, // 56: analysis.Analysis.QrcodeShare:output_type -> analysis.Response + 0, // 57: analysis.Analysis.GraphDetail:output_type -> analysis.Response + 0, // 58: analysis.Analysis.FaceppMaps:output_type -> analysis.Response + 0, // 59: analysis.Analysis.FaceppMapBoxes:output_type -> analysis.Response + 0, // 60: analysis.Analysis.FaceppMapBoxDelete:output_type -> analysis.Response + 0, // 61: analysis.Analysis.ImagesDate:output_type -> analysis.Response + 0, // 62: analysis.Analysis.ImagesList:output_type -> analysis.Response + 0, // 63: analysis.Analysis.ModeImage:output_type -> analysis.Response + 0, // 64: analysis.Analysis.DeviceUpdate:output_type -> analysis.Response + 0, // 65: analysis.AnalysisUser.FaceBind:output_type -> analysis.Response + 0, // 66: analysis.AnalysisUser.UserImprove:output_type -> analysis.Response + 0, // 67: analysis.AnalysisUser.UserDelete:output_type -> analysis.Response + 0, // 68: analysis.AnalysisUser.UserAuth:output_type -> analysis.Response + 0, // 69: analysis.AnalysisUser.UserAuthList:output_type -> analysis.Response + 0, // 70: analysis.AnalysisUser.UserAuthDelete:output_type -> analysis.Response + 0, // 71: analysis.AnalysisUser.UserAuthUpdate:output_type -> analysis.Response + 0, // 72: analysis.AnalysisUser.DeviceBind:output_type -> analysis.Response + 0, // 73: analysis.AnalysisUser.DeviceInfo:output_type -> analysis.Response + 0, // 74: analysis.AnalysisUser.UserLogDelete:output_type -> analysis.Response + 0, // 75: analysis.AnalysisUser.UserStatus:output_type -> analysis.Response + 0, // 76: analysis.AnalysisStore.StoreAuth:output_type -> analysis.Response + 0, // 77: analysis.AnalysisStore.StoreAuthQrcode:output_type -> analysis.Response + 0, // 78: analysis.AnalysisStore.StoreInfo:output_type -> analysis.Response + 0, // 79: analysis.AnalysisStore.StoreUserList:output_type -> analysis.Response + 0, // 80: analysis.AnalysisStore.StoreUserUpdate:output_type -> analysis.Response + 0, // 81: analysis.AnalysisStore.StoreUserInfo:output_type -> analysis.Response + 0, // 82: analysis.AnalysisDevice.Policy:output_type -> analysis.Response + 0, // 83: analysis.AnalysisDevice.Display:output_type -> analysis.Response + 27, // 84: analysis.AnalysisDevice.OssCallback:output_type -> analysis.OssCallbackResp + 0, // 85: analysis.AnalysisDevice.PyCallback:output_type -> analysis.Response + 43, // [43:86] is the sub-list for method output_type + 0, // [0:43] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -2519,7 +2570,7 @@ func file_analysis_analysis_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_analysis_analysis_proto_rawDesc), len(file_analysis_analysis_proto_rawDesc)), NumEnums: 0, - NumMessages: 43, + NumMessages: 44, NumExtensions: 0, NumServices: 4, }, diff --git a/rpc/analysis/pb/analysis_grpc.pb.go b/rpc/analysis/pb/analysis_grpc.pb.go index 0651463..ea19e1a 100644 --- a/rpc/analysis/pb/analysis_grpc.pb.go +++ b/rpc/analysis/pb/analysis_grpc.pb.go @@ -1696,6 +1696,7 @@ const ( AnalysisDevice_Policy_FullMethodName = "/analysis.AnalysisDevice/Policy" AnalysisDevice_Display_FullMethodName = "/analysis.AnalysisDevice/Display" AnalysisDevice_OssCallback_FullMethodName = "/analysis.AnalysisDevice/OssCallback" + AnalysisDevice_PyCallback_FullMethodName = "/analysis.AnalysisDevice/PyCallback" ) // AnalysisDeviceClient is the client API for AnalysisDevice service. @@ -1705,6 +1706,7 @@ type AnalysisDeviceClient interface { Policy(ctx context.Context, in *DevicePolicyReq, opts ...grpc.CallOption) (*Response, error) Display(ctx context.Context, in *DeviceDisplayReq, opts ...grpc.CallOption) (*Response, error) OssCallback(ctx context.Context, in *OssCallbackReq, opts ...grpc.CallOption) (*OssCallbackResp, error) + PyCallback(ctx context.Context, in *PyCallbackReq, opts ...grpc.CallOption) (*Response, error) } type analysisDeviceClient struct { @@ -1745,6 +1747,16 @@ func (c *analysisDeviceClient) OssCallback(ctx context.Context, in *OssCallbackR return out, nil } +func (c *analysisDeviceClient) PyCallback(ctx context.Context, in *PyCallbackReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, AnalysisDevice_PyCallback_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // AnalysisDeviceServer is the server API for AnalysisDevice service. // All implementations must embed UnimplementedAnalysisDeviceServer // for forward compatibility. @@ -1752,6 +1764,7 @@ type AnalysisDeviceServer interface { Policy(context.Context, *DevicePolicyReq) (*Response, error) Display(context.Context, *DeviceDisplayReq) (*Response, error) OssCallback(context.Context, *OssCallbackReq) (*OssCallbackResp, error) + PyCallback(context.Context, *PyCallbackReq) (*Response, error) mustEmbedUnimplementedAnalysisDeviceServer() } @@ -1771,6 +1784,9 @@ func (UnimplementedAnalysisDeviceServer) Display(context.Context, *DeviceDisplay func (UnimplementedAnalysisDeviceServer) OssCallback(context.Context, *OssCallbackReq) (*OssCallbackResp, error) { return nil, status.Error(codes.Unimplemented, "method OssCallback not implemented") } +func (UnimplementedAnalysisDeviceServer) PyCallback(context.Context, *PyCallbackReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method PyCallback not implemented") +} func (UnimplementedAnalysisDeviceServer) mustEmbedUnimplementedAnalysisDeviceServer() {} func (UnimplementedAnalysisDeviceServer) testEmbeddedByValue() {} @@ -1846,6 +1862,24 @@ func _AnalysisDevice_OssCallback_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _AnalysisDevice_PyCallback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PyCallbackReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AnalysisDeviceServer).PyCallback(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AnalysisDevice_PyCallback_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AnalysisDeviceServer).PyCallback(ctx, req.(*PyCallbackReq)) + } + return interceptor(ctx, in, info, handler) +} + // AnalysisDevice_ServiceDesc is the grpc.ServiceDesc for AnalysisDevice service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1865,6 +1899,10 @@ var AnalysisDevice_ServiceDesc = grpc.ServiceDesc{ MethodName: "OssCallback", Handler: _AnalysisDevice_OssCallback_Handler, }, + { + MethodName: "PyCallback", + Handler: _AnalysisDevice_PyCallback_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "analysis/analysis.proto", diff --git a/services/analysis/client/analysisdevice/analysisDevice.go b/services/analysis/client/analysisdevice/analysisDevice.go index 3e660ef..65b57a8 100644 --- a/services/analysis/client/analysisdevice/analysisDevice.go +++ b/services/analysis/client/analysisdevice/analysisDevice.go @@ -18,12 +18,14 @@ type ( DevicePolicyReq = analysis.DevicePolicyReq OssCallbackReq = analysis.OssCallbackReq OssCallbackResp = analysis.OssCallbackResp + PyCallbackReq = analysis.PyCallbackReq Response = analysis.Response AnalysisDevice interface { Policy(ctx context.Context, in *DevicePolicyReq, opts ...grpc.CallOption) (*Response, error) Display(ctx context.Context, in *DeviceDisplayReq, opts ...grpc.CallOption) (*Response, error) OssCallback(ctx context.Context, in *OssCallbackReq, opts ...grpc.CallOption) (*OssCallbackResp, error) + PyCallback(ctx context.Context, in *PyCallbackReq, opts ...grpc.CallOption) (*Response, error) } defaultAnalysisDevice struct { @@ -51,3 +53,8 @@ func (m *defaultAnalysisDevice) OssCallback(ctx context.Context, in *OssCallback client := analysis.NewAnalysisDeviceClient(m.cli.Conn()) return client.OssCallback(ctx, in, opts...) } + +func (m *defaultAnalysisDevice) PyCallback(ctx context.Context, in *PyCallbackReq, opts ...grpc.CallOption) (*Response, error) { + client := analysis.NewAnalysisDeviceClient(m.cli.Conn()) + return client.PyCallback(ctx, in, opts...) +} diff --git a/services/analysis/internal/callback/faceback.go b/services/analysis/internal/callback/faceback.go new file mode 100644 index 0000000..80ff4c5 --- /dev/null +++ b/services/analysis/internal/callback/faceback.go @@ -0,0 +1,734 @@ +package callback + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/rand" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "lone-services/pkg/modelbase" + pkgoss "lone-services/pkg/oss" + "lone-services/pkg/utils" + "lone-services/pkg/wechat/qrcode" + analysisconfig "lone-services/services/analysis/internal/config" + "lone-services/services/analysis/internal/dao" + "lone-services/services/analysis/internal/model" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ImageRule struct { + Scene string + Position string +} + +type FrontItem struct { + Name string `json:"name"` + Path string `json:"path"` +} + +type AnalysisSummary struct { + HealthScore string `json:"health_score"` + NormalArea string `json:"normal_area"` + MildAbnormality string `json:"mild_abnormality"` + ModerateAbnormality string `json:"moderate_abnormality"` + SevereAbnormality string `json:"severe_abnormality"` + PredictedAge string `json:"predicted_age"` +} + +// ProcessPyCallback 对应旧项目 POST /v1/analysis/callback +// 入参 task_id 实际为 python 侧的 sub_task_id。 +func ProcessPyCallback(subTaskId string) error { + sub, err := GetSubTask(subTaskId) + if err != nil { + return err + } + if err = FinishSubTask(subTaskId); err != nil { + return err + } + + subCopy := *sub + go func(s dao.ImageAnalysisSub) { + defer func() { + if r := recover(); r != nil { + logx.Errorf("py callback async panic: %v", r) + } + }() + if ossErr := UploadSubImagesToOSS(&s); ossErr != nil { + logx.Errorf("upload sub images to oss failed: %v", ossErr) + } + if finishErr := TryFinishTask(s.TaskId); finishErr != nil { + logx.Errorf("try finish task failed: %v", finishErr) + } + }(subCopy) + + return nil +} + +func GetSubTask(subTaskId string) (*dao.ImageAnalysisSub, error) { + var data dao.ImageAnalysisSub + err := model.ImageAnalysisSubModel{}.Init().GetOne(modelbase.Params{ + Eq: map[string]string{"sub_task_id": subTaskId}, + }, &data) + if err != nil { + return nil, err + } + if data.Id < utils.NumberOne { + return nil, fmt.Errorf("sub task not found: %s", subTaskId) + } + return &data, nil +} + +func FinishSubTask(subTaskId string) error { + _, err := model.ImageAnalysisSubModel{}.Init().Edit(modelbase.Params{ + Eq: map[string]string{"sub_task_id": subTaskId}, + }, map[string]interface{}{ + "status": dao.AnalysisStatusDone, + "process_end_ts": time.Now().UnixMilli(), + "updated_at": utils.Now(), + }) + return err +} + +func UploadSubImagesToOSS(sub *dao.ImageAnalysisSub) error { + if sub == nil || sub.SubTaskId == utils.StringEmpty { + return fmt.Errorf("sub task empty") + } + + imageDir := sub.ImageDir + if imageDir == utils.StringEmpty { + imageDir = subImageDir(sub.FilePath) + } + if imageDir == utils.StringEmpty { + return fmt.Errorf("sub image dir empty") + } + + scene, originalPaths, err := detectOriginalLighting(imageDir) + if err != nil { + return err + } + + needResult := sceneHasResult(scene) + originalsReady := sub.LeftPath != utils.StringEmpty && sub.MidPath != utils.StringEmpty && sub.RightPath != utils.StringEmpty + resultsReady := !needResult || (sub.LeftResultPath != utils.StringEmpty && sub.MidResultPath != utils.StringEmpty && sub.RightResultPath != utils.StringEmpty) + if originalsReady && resultsReady { + return nil + } + + update := map[string]interface{}{ + "scene": scene, + "updated_at": utils.Now(), + } + + if !originalsReady { + leftURL, midURL, rightURL, upErr := uploadPositionImages(sub.TaskId, originalPaths, true) + if upErr != nil { + return fmt.Errorf("upload original images: %w", upErr) + } + update["left_path"] = leftURL + update["mid_path"] = midURL + update["right_path"] = rightURL + sub.LeftPath = leftURL + sub.MidPath = midURL + sub.RightPath = rightURL + } + + if needResult && !resultsReady { + analysis, aErr := GetAnalysis(sub.TaskId) + if aErr != nil { + return aErr + } + if analysis.FolderPath == utils.StringEmpty { + return fmt.Errorf("analysis folder_path empty task_id=%s", sub.TaskId) + } + resultPaths, pErr := resultImagePaths(analysis.FolderPath, scene) + if pErr != nil { + return pErr + } + leftURL, midURL, rightURL, upErr := uploadPositionImages(sub.TaskId, resultPaths, false) + if upErr != nil { + return fmt.Errorf("upload result images: %w", upErr) + } + update["left_result_path"] = leftURL + update["mid_result_path"] = midURL + update["right_result_path"] = rightURL + } + + _, err = model.ImageAnalysisSubModel{}.Init().Edit(modelbase.Params{ + Eq: map[string]string{"sub_task_id": sub.SubTaskId}, + }, update) + return err +} + +func sceneHasResult(scene string) bool { + switch scene { + case "uv405", "cross", "parallel", "uv365": + return true + default: + return false + } +} + +func detectOriginalLighting(imageDir string) (string, map[string]string, error) { + entries, err := os.ReadDir(imageDir) + if err != nil { + return utils.StringEmpty, nil, err + } + + scene := utils.StringEmpty + paths := map[string]string{} + for _, entry := range entries { + if entry.IsDir() { + continue + } + rule, ok := parseImageRule(entry.Name()) + if !ok || !strings.HasPrefix(rule.Scene, "original_") { + continue + } + light := strings.TrimPrefix(rule.Scene, "original_") + if scene == utils.StringEmpty { + scene = light + } + if light != scene { + continue + } + paths[rule.Position] = filepath.Join(imageDir, entry.Name()) + } + if scene == utils.StringEmpty || paths["left"] == "" || paths["mid"] == "" || paths["right"] == "" { + return utils.StringEmpty, nil, fmt.Errorf("no complete original lighting in %s", imageDir) + } + return scene, paths, nil +} + +func resultImagePaths(folderPath, scene string) (map[string]string, error) { + var dir string + var names map[string]string + switch scene { + case "uv405": + dir = "output/yolo" + names = map[string]string{ + "left": "yolo_uv405_left.jpg", + "mid": "yolo_uv405_mid.jpg", + "right": "yolo_uv405_right.jpg", + } + case "cross": + dir = "output/pigment" + names = map[string]string{ + "left": "pigment_cross_left.jpg", + "mid": "pigment_cross_mid.jpg", + "right": "pigment_cross_right.jpg", + } + case "parallel": + dir = "output/sensitivity" + names = map[string]string{ + "left": "sensitivity_parallel_left.jpg", + "mid": "sensitivity_parallel_mid.jpg", + "right": "sensitivity_parallel_right.jpg", + } + case "uv365": + dir = "output/redzone" + names = map[string]string{ + "left": "red_uv365_left.jpg", + "mid": "red_uv365_mid.jpg", + "right": "red_uv365_right.jpg", + } + default: + return nil, fmt.Errorf("scene %s has no result images", scene) + } + + paths := map[string]string{} + for pos, name := range names { + full := filepath.Join(folderPath, dir, name) + if _, err := os.Stat(full); err != nil { + return nil, fmt.Errorf("result image missing: %s", full) + } + paths[pos] = full + } + return paths, nil +} + +func uploadPositionImages(taskId string, paths map[string]string, rotate bool) (string, string, string, error) { + leftURL, err := uploadImageToOSS(taskId, paths["left"], rotate) + if err != nil { + return utils.StringEmpty, utils.StringEmpty, utils.StringEmpty, fmt.Errorf("left: %w", err) + } + midURL, err := uploadImageToOSS(taskId, paths["mid"], rotate) + if err != nil { + return utils.StringEmpty, utils.StringEmpty, utils.StringEmpty, fmt.Errorf("mid: %w", err) + } + rightURL, err := uploadImageToOSS(taskId, paths["right"], rotate) + if err != nil { + return utils.StringEmpty, utils.StringEmpty, utils.StringEmpty, fmt.Errorf("right: %w", err) + } + return leftURL, midURL, rightURL, nil +} + +func subImageDir(filePath string) string { + if filePath == utils.StringEmpty { + return utils.StringEmpty + } + if info, err := os.Stat(filePath); err == nil && info.IsDir() { + return filePath + } + ext := filepath.Ext(filePath) + if strings.EqualFold(ext, ".zip") { + return strings.TrimSuffix(filePath, ext) + } + return filepath.Dir(filePath) +} + +func uploadImageToOSS(taskId, localPath string, rotate bool) (string, error) { + _ = taskId + data, err := os.ReadFile(localPath) + if err != nil { + return utils.StringEmpty, err + } + if rotate { + data, err = Rotate90Bytes(data, filepath.Ext(localPath)) + if err != nil { + return utils.StringEmpty, fmt.Errorf("rotate image: %w", err) + } + } + + ext := strings.ToLower(filepath.Ext(localPath)) + if ext == utils.StringEmpty { + ext = ".jpg" + } + fileName := fmt.Sprintf("%d%d%s", time.Now().UnixNano(), rand.Intn(10000), ext) + contentType := "image/jpeg" + if ext == ".png" { + contentType = "image/png" + } + + imageDir := utils.GetConfigString("oss.imageDir") + res, err := pkgoss.Upload(pkgoss.UploadInput{ + Filename: fileName, + Content: data, + ContentType: contentType, + Dir: imageDir, + }) + if err != nil { + return utils.StringEmpty, err + } + return res.URL, nil +} + +func ListFinishedSubs(taskId string) ([]dao.ImageAnalysisSub, error) { + var list []dao.ImageAnalysisSub + err := model.ImageAnalysisSubModel{}.Init().Items(modelbase.Params{ + Eq: map[string]string{ + "task_id": taskId, + "status": strconv.Itoa(int(dao.AnalysisStatusDone)), + }, + Order: "id asc", + }, &list) + return list, err +} + +func CountFinishedTask(taskId string) (int64, error) { + return model.ImageAnalysisSubModel{}.Init().CountNumber(modelbase.Params{ + Eq: map[string]string{ + "task_id": taskId, + "status": strconv.Itoa(int(dao.AnalysisStatusDone)), + }, + }) +} + +// TryFinishTask 六组图都已上传 OSS 后做最终落库。可重复调用,已完成则直接返回。 +func TryFinishTask(taskId string) error { + count, err := CountFinishedTask(taskId) + if err != nil || count < 6 { + return err + } + + subs, err := ListFinishedSubs(taskId) + if err != nil { + return err + } + if len(subs) < 6 { + return nil + } + for _, sub := range subs { + if sub.LeftPath == utils.StringEmpty || sub.MidPath == utils.StringEmpty || sub.RightPath == utils.StringEmpty { + return nil + } + if sceneHasResult(sub.Scene) && (sub.LeftResultPath == utils.StringEmpty || sub.MidResultPath == utils.StringEmpty || sub.RightResultPath == utils.StringEmpty) { + return nil + } + } + + analysis, err := GetAnalysis(taskId) + if err != nil { + return err + } + if analysis.Status == dao.AnalysisStatusDone { + return nil + } + + summary, err := ParseSummary(analysis.FolderPath) + if err != nil { + logx.Errorf("read summary failed: %v", err) + } + + analysis.Cover = UploadCoverToOSS(taskId, analysis.Cover) + if err = FinishAnalysis(taskId, analysis); err != nil { + return err + } + userImage, err := FinishUserImage(analysis, summary) + if err != nil { + return err + } + if qrErr := EnsureReportQrcode(taskId, userImage.Id); qrErr != nil { + logx.Errorf("ensure report qrcode failed: %v", qrErr) + } + return nil +} + +func EnsureReportQrcode(taskId string, imageId int64) error { + if taskId == utils.StringEmpty { + return fmt.Errorf("task_id empty") + } + if imageId <= 0 { + return fmt.Errorf("image id invalid") + } + + analysis, err := GetAnalysis(taskId) + if err != nil { + return err + } + if analysis.Qrcode != utils.StringEmpty { + return nil + } + + wxCfg, err := analysisconfig.LoadWechatConfig() + if err != nil { + return err + } + img, err := wxCfg.NewQrcodeClient().CreateWxaCode(context.Background(), &qrcode.WxaCodeRequest{ + Path: qrcode.BuildPath(wxCfg.ReportPage, map[string]string{ + "type": strconv.Itoa(utils.NumberOne), + "ids": strconv.FormatInt(imageId, utils.NumberTen), + }), + Width: wxCfg.Width, + EnvVersion: wxCfg.EnvVersion, + AutoColor: false, + LineColor: &qrcode.LineColor{R: 255, G: 255, B: 255}, + IsHyaline: true, + }) + if err != nil { + return fmt.Errorf("fetch mini qrcode: %w", err) + } + + imageDir := utils.GetConfigString("oss.imageDir") + fileName := fmt.Sprintf("%d%d.png", time.Now().UnixNano(), rand.Intn(10000)) + res, err := pkgoss.Upload(pkgoss.UploadInput{ + Filename: fileName, + Content: img, + ContentType: "image/png", + Dir: imageDir, + }) + if err != nil { + return err + } + + affected, err := model.ImageAnalysisModel{}.Init().Edit(modelbase.Params{ + Eq: map[string]string{ + "task_id": taskId, + "qrcode": "", + }, + }, map[string]interface{}{ + "qrcode": res.URL, + "updated_at": utils.Now(), + }) + if err != nil { + return fmt.Errorf("save qrcode: %w", err) + } + if affected == 0 { + logx.Infof("report qrcode already set by concurrent callback task_id=%s", taskId) + return nil + } + logx.Infof("report qrcode saved task_id=%s image_id=%d qrcode=%s", taskId, imageId, res.URL) + return nil +} + +func GetAnalysis(taskId string) (*dao.ImageAnalysis, error) { + var task dao.ImageAnalysis + err := model.ImageAnalysisModel{}.Init().GetOne(modelbase.Params{ + Eq: map[string]string{"task_id": taskId}, + }, &task) + if err != nil { + return nil, err + } + if task.Id < utils.NumberOne { + return nil, fmt.Errorf("analysis not found: %s", taskId) + } + return &task, nil +} + +func ParseSummary(folderPath string) (*AnalysisSummary, error) { + txtPath := path.Join(folderPath, "output/texts", "analysis_report.txt") + data, err := os.ReadFile(txtPath) + if err != nil { + return nil, err + } + + result := &AnalysisSummary{} + items := strings.Split(string(data), ";;") + for _, item := range items { + item = strings.TrimSpace(item) + if item == utils.StringEmpty { + continue + } + parts := strings.Split(item, "::") + if len(parts) != utils.NumberTwo { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + switch key { + case "Health Score": + result.HealthScore = value + case "Normal area": + result.NormalArea = value + case "Mild abnormality": + result.MildAbnormality = value + case "Moderate abnormality": + result.ModerateAbnormality = value + case "Severe abnormality": + result.SevereAbnormality = value + case "Predicted Age": + result.PredictedAge = value + } + } + return result, nil +} + +func FinishAnalysis(taskID string, analysis *dao.ImageAnalysis) error { + now := utils.Now() + _, err := model.ImageAnalysisModel{}.Init().Edit(modelbase.Params{ + Eq: map[string]string{"task_id": taskID}, + }, map[string]interface{}{ + "status": dao.AnalysisStatusDone, + "cover": analysis.Cover, + "updated_at": now, + }) + if err != nil { + return err + } + + wLog := modelbase.Params{ + Eq: map[string]string{ + "user_id": strconv.FormatInt(analysis.UserId, utils.NumberTen), + }, + } + var logRow dao.UserImageLog + err = model.UserImageLogModel{}.Init().GetOne(wLog, &logRow) + if err != nil { + return err + } + + if logRow.Id < utils.NumberOne { + logModel := model.UserImageLogModel{}.Init() + return logModel.Create(&dao.UserImageLog{ + UserId: analysis.UserId, + DetectionNumber: utils.NumberOne, + Cover: analysis.Cover, + DetectionTime: now, + Status: uint8(utils.StatusOk), + }) + } + + newNumber := logRow.DetectionNumber + utils.NumberOne + updateData := map[string]interface{}{ + "cover": analysis.Cover, + "detection_time": now, + "detection_number": newNumber, + } + if newNumber == utils.NumberTwo { + updateData["status"] = utils.NumberTwo + } + _, err = model.UserImageLogModel{}.Init().Edit(wLog, updateData) + return err +} + +func FinishUserImage(analysis *dao.ImageAnalysis, summaryStruct *AnalysisSummary) (*dao.Image, error) { + subs, err := ListFinishedSubs(analysis.TaskId) + if err != nil { + return nil, err + } + if len(subs) == 0 { + return nil, errors.New("no finished sub tasks") + } + + userImage, err := BuildUserImageFromSubs(analysis, summaryStruct, subs) + if err != nil { + return nil, err + } + imgModel := model.ImageModel{}.Init() + if err = imgModel.Create(userImage); err != nil { + return nil, err + } + return userImage, nil +} + +func BuildUserImageFromSubs(analysis *dao.ImageAnalysis, summaryStruct *AnalysisSummary, subs []dao.ImageAnalysisSub) (*dao.Image, error) { + summaryBytes, err := json.Marshal(summaryStruct) + if err != nil { + return nil, err + } + + images := make(map[string]map[string]string) + var front []FrontItem + + for _, sub := range subs { + if sub.Scene == utils.StringEmpty { + continue + } + if sub.LeftPath == utils.StringEmpty || sub.MidPath == utils.StringEmpty || sub.RightPath == utils.StringEmpty { + continue + } + originalScene := "original_" + sub.Scene + images[originalScene] = map[string]string{ + "left": sub.LeftPath, + "mid": sub.MidPath, + "right": sub.RightPath, + } + front = append(front, FrontItem{ + Name: originalScene, + Path: sub.MidPath, + }) + + if sub.LeftResultPath == utils.StringEmpty || sub.MidResultPath == utils.StringEmpty || sub.RightResultPath == utils.StringEmpty { + continue + } + images[sub.Scene] = map[string]string{ + "left": sub.LeftResultPath, + "mid": sub.MidResultPath, + "right": sub.RightResultPath, + } + front = append(front, FrontItem{ + Name: sub.Scene, + Path: sub.MidResultPath, + }) + } + + if len(images) == 0 { + return nil, errors.New("no oss images on finished sub tasks") + } + + result := map[string]interface{}{ + "images": images, + "front": front, + } + imagesBytes, err := json.Marshal(result) + if err != nil { + return nil, err + } + + return &dao.Image{ + UserId: analysis.UserId, + TaskId: analysis.TaskId, + Images: string(imagesBytes), + Contents: string(summaryBytes), + Cover: analysis.Cover, + Status: int8(utils.StatusOk), + CreateTime: utils.Now(), + }, nil +} + +func UploadCoverToOSS(taskId, localPath string) string { + if localPath == utils.StringEmpty { + return utils.StringEmpty + } + if strings.HasPrefix(localPath, "http://") || strings.HasPrefix(localPath, "https://") { + return localPath + } + + candidates := []string{localPath} + if !filepath.IsAbs(localPath) { + candidates = append(candidates, filepath.Join(utils.GetConfigString("file.upload_path"), localPath)) + } + + for _, fullPath := range candidates { + if _, err := os.Stat(fullPath); err != nil { + continue + } + url, err := uploadImageToOSS(taskId, fullPath, true) + if err != nil { + logx.Errorf("upload cover to oss failed: %v", err) + return localPath + } + return url + } + + logx.Errorf("upload cover skipped, file not found: %s", localPath) + return localPath +} + +func parseImageRule(fileName string) (ImageRule, bool) { + switch fileName { + case "yolo_uv405_left.jpg": + return ImageRule{"uv405", "left"}, true + case "yolo_uv405_mid.jpg": + return ImageRule{"uv405", "mid"}, true + case "yolo_uv405_right.jpg": + return ImageRule{"uv405", "right"}, true + case "pigment_cross_left.jpg": + return ImageRule{"cross", "left"}, true + case "pigment_cross_mid.jpg": + return ImageRule{"cross", "mid"}, true + case "pigment_cross_right.jpg": + return ImageRule{"cross", "right"}, true + case "sensitivity_cross_left.jpg": + return ImageRule{"parallel", "left"}, true + case "sensitivity_cross_mid.jpg": + return ImageRule{"parallel", "mid"}, true + case "sensitivity_cross_right.jpg": + return ImageRule{"parallel", "right"}, true + case "red_uv365_left.jpg": + return ImageRule{"uv365", "left"}, true + case "red_uv365_mid.jpg": + return ImageRule{"uv365", "mid"}, true + case "red_uv365_right.jpg": + return ImageRule{"uv365", "right"}, true + } + + if strings.HasPrefix(fileName, "parallel_") { + return buildOriginalRule(fileName, "original_parallel") + } + if strings.HasPrefix(fileName, "cross_") { + return buildOriginalRule(fileName, "original_cross") + } + if strings.HasPrefix(fileName, "visible_") { + return buildOriginalRule(fileName, "original_visible") + } + if strings.HasPrefix(fileName, "uv365_") { + return buildOriginalRule(fileName, "original_uv365") + } + if strings.HasPrefix(fileName, "uv385_") { + return buildOriginalRule(fileName, "original_uv385") + } + if strings.HasPrefix(fileName, "uv405_") { + return buildOriginalRule(fileName, "original_uv405") + } + return ImageRule{}, false +} + +func buildOriginalRule(fileName string, scene string) (ImageRule, bool) { + switch { + case strings.HasSuffix(fileName, "_left.jpg"): + return ImageRule{Scene: scene, Position: "left"}, true + case strings.HasSuffix(fileName, "_mid.jpg"): + return ImageRule{Scene: scene, Position: "mid"}, true + case strings.HasSuffix(fileName, "_right.jpg"): + return ImageRule{Scene: scene, Position: "right"}, true + } + return ImageRule{}, false +} diff --git a/services/analysis/internal/callback/image.go b/services/analysis/internal/callback/image.go new file mode 100644 index 0000000..620775e --- /dev/null +++ b/services/analysis/internal/callback/image.go @@ -0,0 +1,27 @@ +package callback + +import ( + "bytes" + "fmt" + "strings" + + "github.com/disintegration/imaging" +) + +func Rotate90Bytes(data []byte, ext string) ([]byte, error) { + img, err := imaging.Decode(bytes.NewReader(data)) + if err != nil { + return nil, err + } + img = imaging.Rotate90(img) + + format := imaging.JPEG + if strings.EqualFold(ext, ".png") { + format = imaging.PNG + } + var buf bytes.Buffer + if err = imaging.Encode(&buf, img, format); err != nil { + return nil, fmt.Errorf("encode image: %w", err) + } + return buf.Bytes(), nil +} diff --git a/services/analysis/internal/dao/analysis_sub.go b/services/analysis/internal/dao/analysis_sub.go index 9b32b26..c90c267 100644 --- a/services/analysis/internal/dao/analysis_sub.go +++ b/services/analysis/internal/dao/analysis_sub.go @@ -3,19 +3,25 @@ package dao import "lone-services/pkg/utils" type ImageAnalysisSub struct { - Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - TaskId string `gorm:"column:task_id" json:"task_id"` - SubTaskId string `gorm:"column:sub_task_id" json:"sub_task_id"` - Status int8 `gorm:"column:status" json:"status"` - FilePath string `gorm:"column:file_path" json:"file_path"` - ImageDir string `gorm:"column:image_dir" json:"image_dir"` - Scene string `gorm:"column:scene" json:"scene"` - UploadStartTs int64 `gorm:"column:upload_start_ts" json:"upload_start_ts"` - UploadEndTs int64 `gorm:"column:upload_end_ts" json:"upload_end_ts"` - ProcessStartTs int64 `gorm:"column:process_start_ts" json:"process_start_ts"` - ProcessEndTs int64 `gorm:"column:process_end_ts" json:"process_end_ts"` - CreatedAt utils.CustomTime `gorm:"column:created_at" json:"created_at"` - UpdatedAt utils.CustomTime `gorm:"column:updated_at" json:"updated_at"` + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + TaskId string `gorm:"column:task_id" json:"task_id"` + SubTaskId string `gorm:"column:sub_task_id" json:"sub_task_id"` + Status int8 `gorm:"column:status" json:"status"` + FilePath string `gorm:"column:file_path" json:"file_path"` + ImageDir string `gorm:"column:image_dir" json:"image_dir"` + Scene string `gorm:"column:scene" json:"scene"` + LeftPath string `gorm:"column:left_path" json:"left_path"` + MidPath string `gorm:"column:mid_path" json:"mid_path"` + RightPath string `gorm:"column:right_path" json:"right_path"` + LeftResultPath string `gorm:"column:left_result_path" json:"left_result_path"` + MidResultPath string `gorm:"column:mid_result_path" json:"mid_result_path"` + RightResultPath string `gorm:"column:right_result_path" json:"right_result_path"` + UploadStartTs int64 `gorm:"column:upload_start_ts" json:"upload_start_ts"` + UploadEndTs int64 `gorm:"column:upload_end_ts" json:"upload_end_ts"` + ProcessStartTs int64 `gorm:"column:process_start_ts" json:"process_start_ts"` + ProcessEndTs int64 `gorm:"column:process_end_ts" json:"process_end_ts"` + CreatedAt utils.CustomTime `gorm:"column:created_at" json:"created_at"` + UpdatedAt utils.CustomTime `gorm:"column:updated_at" json:"updated_at"` } type HandleZipResource struct { diff --git a/services/analysis/internal/logic/analysisdevice/ossCallbackLogic.go b/services/analysis/internal/logic/analysisdevice/ossCallbackLogic.go index 1ada39d..e3aa190 100644 --- a/services/analysis/internal/logic/analysisdevice/ossCallbackLogic.go +++ b/services/analysis/internal/logic/analysisdevice/ossCallbackLogic.go @@ -15,7 +15,7 @@ import ( "google.golang.org/grpc/status" ) -const ossCallbackPath = "/api/v1/oss/callback" +const ossCallbackPath = "/api/v2/analysis/oss/callback" type OssCallbackLogic struct { ctx context.Context diff --git a/services/analysis/internal/logic/analysisdevice/pyCallbackLogic.go b/services/analysis/internal/logic/analysisdevice/pyCallbackLogic.go new file mode 100644 index 0000000..0628265 --- /dev/null +++ b/services/analysis/internal/logic/analysisdevice/pyCallbackLogic.go @@ -0,0 +1,42 @@ +package analysisdevicelogic + +import ( + "context" + + "lone-services/pkg/utils" + "lone-services/pkg/validate" + analysis "lone-services/rpc/analysis/pb" + "lone-services/services/analysis/internal/callback" + "lone-services/services/analysis/internal/logic/common" + "lone-services/services/analysis/internal/svc" + "lone-services/services/analysis/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type PyCallbackLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewPyCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PyCallbackLogic { + return &PyCallbackLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *PyCallbackLogic) PyCallback(in *analysis.PyCallbackReq) (*analysis.Response, error) { + var req validator.PyCallbackValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return common.OutResponse(utils.ErrorParams, msg), nil + } + + if err := callback.ProcessPyCallback(req.TaskId); err != nil { + l.Errorf("py callback failed: %v", err) + return common.FailResponse(utils.Fail), nil + } + return common.OkResponse(nil), nil +} diff --git a/services/analysis/internal/logic/analysisuser/userImproveLogic.go b/services/analysis/internal/logic/analysisuser/userImproveLogic.go index 3439bb8..c37909b 100644 --- a/services/analysis/internal/logic/analysisuser/userImproveLogic.go +++ b/services/analysis/internal/logic/analysisuser/userImproveLogic.go @@ -106,33 +106,24 @@ func (l *UserImproveLogic) updatePlatformUser(loginUser utils.UserInfo, bizId in return utils.ErrorParams } - name := req.Name - if name == utils.StringEmpty { - name = info.Name - } - avatar := req.Avatar - if avatar == utils.StringEmpty { - avatar = info.Avatar - } - gender := uint32(req.Gender) - if gender == utils.NumberZero { - gender = info.Gender - } - birthday := req.Birthday - if birthday == utils.StringEmpty { - birthday = info.Birthday - } - updateReq := &userpb.InternalUpdateReq{ BizId: bizId, SubjectType: "user", Mobile: mobile, - Name: name, - Avatar: avatar, - Gender: gender, - Birthday: birthday, ClientCode: []string{loginUser.ClientCode}, } + if req.Name != utils.StringEmpty { + updateReq.Name = req.Name + } + if req.Avatar != utils.StringEmpty { + updateReq.Avatar = req.Avatar + } + if req.Gender > utils.NumberZero { + updateReq.Gender = uint32(req.Gender) + } + if req.Birthday != utils.StringEmpty { + updateReq.Birthday = req.Birthday + } if req.Password != utils.StringEmpty { updateReq.CredentialType = []string{"password"} updateReq.Secret = req.Password diff --git a/services/analysis/internal/server/analysisdevice/analysisDeviceServer.go b/services/analysis/internal/server/analysisdevice/analysisDeviceServer.go index 7107c24..573fae1 100644 --- a/services/analysis/internal/server/analysisdevice/analysisDeviceServer.go +++ b/services/analysis/internal/server/analysisdevice/analysisDeviceServer.go @@ -37,3 +37,8 @@ func (s *AnalysisDeviceServer) OssCallback(ctx context.Context, in *analysis.Oss l := analysisdevicelogic.NewOssCallbackLogic(ctx, s.svcCtx) return l.OssCallback(in) } + +func (s *AnalysisDeviceServer) PyCallback(ctx context.Context, in *analysis.PyCallbackReq) (*analysis.Response, error) { + l := analysisdevicelogic.NewPyCallbackLogic(ctx, s.svcCtx) + return l.PyCallback(in) +} diff --git a/services/analysis/validator/user.go b/services/analysis/validator/user.go index 1d59785..c1e0964 100644 --- a/services/analysis/validator/user.go +++ b/services/analysis/validator/user.go @@ -147,6 +147,17 @@ func (p DeviceDisplayValidator) GetMessage() validate.ValidatorMessages { } } +type PyCallbackValidator struct { + TaskId string `validate:"required,max=64"` +} + +func (p PyCallbackValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "TaskId.required": "task_id 必传", + "TaskId.max": "task_id 过长", + } +} + type StoreInfoValidator struct { Id int64 `validate:"required,gt=0"` } diff --git a/services/user/internal/logic/internalUpdateLogic.go b/services/user/internal/logic/internalUpdateLogic.go index 1f42a86..19859b5 100644 --- a/services/user/internal/logic/internalUpdateLogic.go +++ b/services/user/internal/logic/internalUpdateLogic.go @@ -122,7 +122,8 @@ func (l *InternalUpdateLogic) InternalUpdate(in *user.InternalUpdateReq) (*user. if bErr != nil { return nil, status.Error(codes.InvalidArgument, "生日格式不正确") } - updates["birthday"] = birthday + // 用 YYYY-MM-DD 字符串写入,避免 Edit 的 JSON 往返把 CustomTime 编成带 00:00:00 的 datetime + updates["birthday"] = birthday.DateString() } oldMobile := row.Mobile diff --git a/services/user/validator/user.go b/services/user/validator/user.go index f069126..891738d 100644 --- a/services/user/validator/user.go +++ b/services/user/validator/user.go @@ -289,7 +289,7 @@ type InternalUpdateValidator struct { Name string `validate:"omitempty,max=64"` Avatar string `validate:"omitempty,max=255"` Gender uint32 `validate:"omitempty,oneof=0 1 2 3"` - Birthday string `validate:"omitempty,max=12"` + Birthday string `validate:"omitempty,max=32"` ClientCode []string `validate:"omitempty,dive,required,max=32"` CredentialType []string `validate:"omitempty"` Identifier string `validate:"omitempty,max=128"`