diff --git a/constants/field_status.go b/constants/field_status.go deleted file mode 100644 index fdf96379..00000000 --- a/constants/field_status.go +++ /dev/null @@ -1,5 +0,0 @@ -package constants - -// 普通表中status状态 -const STATUS_ON = 1 -const STATUS_OFF = 0 diff --git a/constants/image_cropping.go b/constants/image_cropping.go new file mode 100644 index 00000000..e8bf5d57 --- /dev/null +++ b/constants/image_cropping.go @@ -0,0 +1,4 @@ +package constants + +// 裁剪尺寸阶梯 +var IMAGE_CROPPING_STEP_SIZE = []int{200, 400, 600, 800} diff --git a/model/gmodel/fs_product_design_logic.go b/model/gmodel/fs_product_design_logic.go index 23c0aa69..29022eb8 100755 --- a/model/gmodel/fs_product_design_logic.go +++ b/model/gmodel/fs_product_design_logic.go @@ -31,3 +31,6 @@ func (d *FsProductDesignModel) GetAllByIdsWithoutStatus(ctx context.Context, ids func (d *FsProductDesignModel) Create(ctx context.Context, data *FsProductDesign) error { return d.db.WithContext(ctx).Model(&FsProductDesign{}).Create(&data).Error } +func (d *FsProductDesignModel) UpdateBySn(ctx context.Context, sn string, data *FsProductDesign) error { + return d.db.WithContext(ctx).Model(&FsProductDesign{}).Where("`sn` = ?", sn).Updates(&data).Error +} diff --git a/server/product/internal/handler/routes.go b/server/product/internal/handler/routes.go index 023a5800..d644038e 100644 --- a/server/product/internal/handler/routes.go +++ b/server/product/internal/handler/routes.go @@ -42,6 +42,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/product/info", Handler: GetProductInfoHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/product/save-design", + Handler: SaveDesignHandler(serverCtx), + }, }, ) } diff --git a/server/product/internal/handler/savedesignhandler.go b/server/product/internal/handler/savedesignhandler.go new file mode 100644 index 00000000..9c69607a --- /dev/null +++ b/server/product/internal/handler/savedesignhandler.go @@ -0,0 +1,78 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest/httpx" + + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "fusenapi/server/product/internal/logic" + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" +) + +func SaveDesignHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var ( + // 定义错误变量 + err error + // 定义用户信息变量 + userinfo *auth.UserInfo + ) + // 解析JWT token,并对空用户进行判断 + claims, err := svcCtx.ParseJwtToken(r) + // 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 401, // 返回401状态码,表示未授权 + Message: "unauthorized", // 返回未授权信息 + }) + logx.Info("unauthorized:", err.Error()) // 记录错误日志 + return + } + + if claims != nil { + // 从token中获取对应的用户信息 + userinfo, err = auth.GetUserInfoFormMapClaims(claims) + // 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 401, + Message: "unauthorized", + }) + logx.Info("unauthorized:", err.Error()) + return + } + } else { + // 如果claims为nil,则认为用户身份为白板用户 + userinfo = &auth.UserInfo{UserId: 0, GuestId: 0} + } + + var req types.SaveDesignReq + // 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据 + if err := httpx.Parse(r, &req); err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 510, + Message: "parameter error", + }) + logx.Info(err) + return + } + // 创建一个业务逻辑层实例 + l := logic.NewSaveDesignLogic(r.Context(), svcCtx) + resp := l.SaveDesign(&req, userinfo) + // 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应; + if resp != nil { + httpx.OkJsonCtx(r.Context(), w, resp) + } else { + err := errors.New("server logic is error, resp must not be nil") + httpx.ErrorCtx(r.Context(), w, err) + logx.Error(err) + } + } +} diff --git a/server/product/internal/logic/getproductinfologic.go b/server/product/internal/logic/getproductinfologic.go index f289cf84..e7069c29 100644 --- a/server/product/internal/logic/getproductinfologic.go +++ b/server/product/internal/logic/getproductinfologic.go @@ -1,19 +1,9 @@ package logic import ( - "encoding/json" - "errors" - "fmt" - "fusenapi/constants" + "context" "fusenapi/utils/auth" "fusenapi/utils/basic" - "fusenapi/utils/format" - "fusenapi/utils/image" - "gorm.io/gorm" - "strconv" - "strings" - - "context" "fusenapi/server/product/internal/svc" "fusenapi/server/product/internal/types" @@ -37,7 +27,7 @@ func NewGetProductInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Ge func (l *GetProductInfoLogic) GetProductInfo(req *types.GetProductInfoReq, userinfo *auth.UserInfo) (resp *basic.Response) { //获取产品信息 - productInfo, err := l.svcCtx.AllModels.FsProduct.FindOneBySn(l.ctx, req.Pid) + /*productInfo, err := l.svcCtx.AllModels.FsProduct.FindOneBySn(l.ctx, req.Pid) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "the product is not exists") @@ -261,7 +251,8 @@ func (l *GetProductInfoLogic) GetProductInfo(req *types.GetProductInfoReq, useri temBytes, _ := json.Marshal(allModel3dList[key]) _ = json.Unmarshal(temBytes, &thisInfo) } - } + }*/ + //************************************************** /* //循环处理组装模板信息 foreach ($templates as $temp) { diff --git a/server/product/internal/logic/savedesignlogic.go b/server/product/internal/logic/savedesignlogic.go new file mode 100644 index 00000000..38f2f8d1 --- /dev/null +++ b/server/product/internal/logic/savedesignlogic.go @@ -0,0 +1,168 @@ +package logic + +import ( + "encoding/json" + "errors" + "fmt" + "fusenapi/constants" + "fusenapi/model/gmodel" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + "fusenapi/utils/encryption_decryption" + "fusenapi/utils/id_generator" + "github.com/google/uuid" + "github.com/nfnt/resize" + "gorm.io/gorm" + "image" + "image/gif" + "image/jpeg" + "image/png" + "net/http" + "os" + "path" + "time" + + "context" + + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type SaveDesignLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewSaveDesignLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveDesignLogic { + return &SaveDesignLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SaveDesignLogic) SaveDesign(req *types.SaveDesignReq, userinfo *auth.UserInfo) (resp *basic.Response) { + if userinfo.GetIdType() != auth.IDTYPE_User { + return resp.SetStatusWithMessage(basic.CodeUnAuth, "please login first") + } + //查询是否是加密的(不太合理) + encryptWebsetting, err := l.svcCtx.AllModels.FsWebSet.FindValueByKey(l.ctx, "is_encrypt") + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "web setting is_encrypt is not exists") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get web setting") + } + var postInfo types.SaveDesignReqRealStruct + //不加密 + if encryptWebsetting.Value == nil || *encryptWebsetting.Value == "0" { + if err = json.Unmarshal([]byte(req.Data), &postInfo); err != nil { + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse json data,format may be invalid") + } + } else { //加密的 + //解密数据 + desData, err := encryption_decryption.CBCDecrypt(req.Data) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "failed to decryption data") + } + if err = json.Unmarshal([]byte(desData), &postInfo); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse json data") + } + } + infoBytes, _ := json.Marshal(postInfo.Data) + info := string(infoBytes) + now := time.Now() + logoColorBytes, _ := json.Marshal(postInfo.Data.Logo.Colors) + logoColor := string(logoColorBytes) + saveData := gmodel.FsProductDesign{ + UserId: &userinfo.UserId, + ProductId: &postInfo.ProductId, + TemplateId: &postInfo.TemplateId, + SizeId: &postInfo.SizeId, + OptionalId: &postInfo.OptionalId, + Cover: &postInfo.Cover, + Info: &info, + Utime: &now, + LogoColor: &logoColor, + PageGuid: &postInfo.PageGuid, + } + switch postInfo.Sn { + case "": //新增 + status := int64(1) + postInfo.Sn, err = id_generator.GenSnowFlakeId() + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to gen sn ") + } + saveData.Status = &status + saveData.Sn = &postInfo.Sn + err = l.svcCtx.AllModels.FsProductDesign.Create(l.ctx, &saveData) + default: //更新 + err = l.svcCtx.AllModels.FsProductDesign.UpdateBySn(l.ctx, postInfo.Sn, &saveData) + } + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to save design") + } + if postInfo.Cover == "" { + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.SaveDesignRsp{Sn: postInfo.Sn}) + } + //生成各个尺寸缩略图 + if err = l.CreateStepThumbnailImage(l.ctx, postInfo.Cover); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to create step thumbnail image ") + } + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.SaveDesignRsp{Sn: postInfo.Sn}) +} + +// 创建阶梯缩略图 +func (l *SaveDesignLogic) CreateStepThumbnailImage(ctx context.Context, coverImage string) error { + httpRsp, err := http.Get(coverImage) + if err != nil { + return err + } + defer httpRsp.Body.Close() + coverImg, _, err := image.Decode(httpRsp.Body) + if err != nil { + return err + } + coverImgOrgWith := coverImg.Bounds().Dx() + coverImgOrgHeight := coverImg.Bounds().Dy() + fileExt := path.Ext(coverImage) + for _, size := range constants.IMAGE_CROPPING_STEP_SIZE { + //尺寸大于原图 + if size > coverImgOrgWith { + continue + } + //缩放比例按照宽度来设定 + scale := size / coverImgOrgWith + height := scale * coverImgOrgHeight + tmpImage := resize.Resize(uint(size), uint(height), coverImg, resize.Lanczos3) + fileName := fmt.Sprintf("%s_%d_%s", uuid.New().String(), size, fileExt) + targetFile, err := os.Create(fileName) + if err != nil { + return err + } + defer targetFile.Close() + switch fileExt { + case ".png": + err = png.Encode(targetFile, tmpImage) + case ".jpg", ".jpeg": + err = jpeg.Encode(targetFile, tmpImage, &jpeg.Options{Quality: 100}) + case ".gif": + err = gif.Encode(targetFile, tmpImage, nil) + default: + err = errors.New("unSupport image format") + } + if err != nil { + return err + } + } + return nil +} diff --git a/server/product/internal/types/types.go b/server/product/internal/types/types.go index 4d0e53ee..de674721 100644 --- a/server/product/internal/types/types.go +++ b/server/product/internal/types/types.go @@ -151,6 +151,75 @@ type MaterialItem struct { Title string `json:"title"` } +type SaveDesignReq struct { + Data string `json:"data"` //加密信息 +} + +type SaveDesignRsp struct { + Sn string `json:"sn"` +} + +type SaveDesignReqRealStruct struct { + ProductId int64 `json:"product_id"` + SizeId int64 `json:"size_id"` + OptionalId int64 `json:"optional_id"` + TemplateId int64 `json:"template_id"` + Sn string `json:"sn"` + Data DesignData `json:"data"` + Cover string `json:"cover"` + PageGuid string `json:"pageGuid"` +} + +type DesignData struct { + MainColor ColorFill `json:"MainColor"` + SecondaryColor ColorFill `json:"SecondaryColor"` + Logo DesignLogo `json:"Logo"` + Slogan DesignSlogan `json:"Slogan"` + QRcode DesignQRcode `json:"QRcode"` + Website DesignWebsite `json:"Website"` + Phone DesignPhone `json:"Phone"` + Address DesignAddress `json:"Address"` +} + +type DesignAddress struct { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} + +type DesignPhone struct { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} + +type DesignWebsite struct { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} + +type DesignQRcode struct { + Text string `json:"text"` + SvgPath string `json:"svgPath"` + IfShow bool `json:"ifShow"` +} + +type DesignSlogan struct { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} + +type DesignLogo struct { + Material string `json:"material"` + MaterialName string `json:"materialName"` + MaterialTime string `json:"materialTime"` + Fill string `json:"fill"` + FillName string `json:"fill_name"` + Colors []string `json:"colors"` +} + +type ColorFill struct { + Fill string `json:"fill"` +} + type Request struct { } diff --git a/server_api/product.api b/server_api/product.api index 324a8cb8..96189201 100644 --- a/server_api/product.api +++ b/server_api/product.api @@ -28,6 +28,9 @@ service product { //获取产品信息 @handler GetProductInfoHandler get /product/info(GetProductInfoReq) returns (response); + //保存设计信息 + @handler SaveDesignHandler + post /product/save-design(SaveDesignReq) returns (response); } //获取产品列表 @@ -162,4 +165,65 @@ type SizeTitle { type MaterialItem { Id int64 `json:"id"` Title string `json:"title"` +} +//保存设计信息 +type SaveDesignReq { + Data string `json:"data"` //加密信息 +} +type SaveDesignRsp { + Sn string `json:"sn"` +} +//保存设计信息(解密结构体) +type SaveDesignReqRealStruct { + ProductId int64 `json:"product_id"` + SizeId int64 `json:"size_id"` + OptionalId int64 `json:"optional_id"` + TemplateId int64 `json:"template_id"` + Sn string `json:"sn"` + Data DesignData `json:"data"` + Cover string `json:"cover"` + PageGuid string `json:"pageGuid"` +} + +type DesignData { + MainColor ColorFill `json:"MainColor"` + SecondaryColor ColorFill `json:"SecondaryColor"` + Logo DesignLogo `json:"Logo"` + Slogan DesignSlogan `json:"Slogan"` + QRcode DesignQRcode `json:"QRcode"` + Website DesignWebsite `json:"Website"` + Phone DesignPhone `json:"Phone"` + Address DesignAddress `json:"Address"` +} +type DesignAddress { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} +type DesignPhone { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} +type DesignWebsite { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} +type DesignQRcode { + Text string `json:"text"` + SvgPath string `json:"svgPath"` + IfShow bool `json:"ifShow"` +} +type DesignSlogan { + Text string `json:"text"` + IfShow bool `json:"ifShow"` +} +type DesignLogo { + Material string `json:"material"` + MaterialName string `json:"materialName"` + MaterialTime string `json:"materialTime"` + Fill string `json:"fill"` + FillName string `json:"fill_name"` + Colors []string `json:"colors"` +} +type ColorFill { + Fill string `json:"fill"` } \ No newline at end of file