feat: user improve
CI / changes (push) Successful in 1m31s
CI / ad (push) Successful in 32s
CI / admin (push) Successful in 26s
CI / analysis (push) Successful in 28s
CI / bff (push) Successful in 23s
CI / chore (push) Successful in 24s
CI / equipment (push) Successful in 21s
CI / express (push) Successful in 21s
CI / factory (push) Successful in 18s
CI / message (push) Successful in 18s
CI / order (push) Successful in 21s
CI / payment (push) Successful in 20s
CI / percutaneous (push) Successful in 19s
CI / product (push) Successful in 21s
CI / record (push) Successful in 21s
CI / sale (push) Successful in 20s
CI / store (push) Successful in 25s
CI / support (push) Successful in 21s
CI / task (push) Successful in 20s
CI / user (push) Successful in 22s
CI / water (push) Successful in 20s
CI / wecom (push) Successful in 21s
CI / ws (push) Successful in 8s

This commit is contained in:
zzw
2026-10-27 13:01:24 +08:00
parent 70543fbcb0
commit b77a1f1b95
20 changed files with 1110 additions and 171 deletions
@@ -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...)
}
@@ -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
}
@@ -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
}
+19 -13
View File
@@ -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 {
@@ -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
@@ -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
}
@@ -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
@@ -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)
}
+11
View File
@@ -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"`
}
@@ -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
+1 -1
View File
@@ -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"`