Merge branch 'develop' of https://gitee.com/fusenpack/fusenapi into feature/auth

This commit is contained in:
eson 2023-08-08 14:18:45 +08:00
commit 8bea89754b
115 changed files with 3189 additions and 504 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
*.dll
*.so
*.dylib
bin/
# Test binary, built with `go test -c`
*.test

View File

@ -126,7 +126,8 @@ type Day int64
// 订单取消时间
const (
CANCLE_ORDER_EXPIRE Day = 48 * 3600
CANCLE_ORDER_EXPIRE Day = 48 * 3600
CANCLE_ORDER_EXPIRE_DAY Day = 2 // 2天
)
// 订单时间配置

View File

@ -9,9 +9,3 @@ const (
//渲染结果数据队列
RABBIT_MQ_RENDER_RESULT_DATA RABBIT_MQ = "RABBIT_MQ_RENDER_RESULT_DATA"
)
// 队列列表
var MQ_QUEUE_ARR = []RABBIT_MQ{
RABBIT_MQ_ASSEMBLE_RENDER_DATA,
RABBIT_MQ_RENDER_RESULT_DATA,
}

203
constants/render.go Normal file
View File

@ -0,0 +1,203 @@
package constants
// 渲染要用到的面片模板
const RENDER_FACE_SLICE_TEMPLATE_JSON = `[
{
"id": "",
"tag": "MainColor",
"title": "",
"type": "color",
"text": "",
"fill": "{{MainColorFill}}",
"fontSize": 20,
"fontFamily": "Aqum2SmallCaps3",
"ifBr": false,
"ifShow": true,
"ifGroup": false,
"maxNum": 50,
"rotation": 0,
"lineHeight": 1,
"align": "center",
"verticalAlign": "middle",
"material": "",
"materialTime": "",
"materialName": "",
"QRcodeType": "",
"width": 1024,
"height": 1024,
"proportion": 60,
"x": 0,
"y": 0,
"opacity": 1,
"optionalColor": [
{
"color": "#000000",
"name": "Black",
"default": true
}
],
"zIndex": 1,
"svgPath": "",
"follow": {
"fill": "",
"ifShow": "",
"content": ""
},
"group": [],
"cameraStand": {
"x": 0,
"y": 0,
"z": 0
}
},
{
"id": "",
"tag": "SecondaryColor",
"title": "贴图3",
"type": "color",
"text": "",
"fill": "{{SecondaryColorFill}}",
"fontSize": 20,
"fontFamily": "Aqum2SmallCaps3",
"ifBr": false,
"ifShow": true,
"ifGroup": false,
"maxNum": 50,
"rotation": 0,
"lineHeight": 1,
"align": "center",
"verticalAlign": "middle",
"material": "",
"materialTime": "",
"materialName": "",
"QRcodeType": "",
"width": 1024,
"height": 1024,
"proportion": 60,
"x": 0,
"y": 0,
"opacity": 1,
"optionalColor": [
{
"color": "#000000",
"name": "Black",
"default": true
}
],
"zIndex": 2,
"svgPath": "",
"follow": {
"fill": "",
"ifShow": "",
"content": ""
},
"group": [],
"cameraStand": {
"x": 0,
"y": 0,
"z": 0
}
},
{
"id": "569d7981-25c3-3c03-0e7e-800c14800362",
"tag": "Slogan",
"title": "贴图4",
"type": "text",
"text": "",
"fill": "",
"fontSize": 13,
"fontFamily": "MontserratBold3",
"ifBr": false,
"ifShow": true,
"ifGroup": false,
"maxNum": 50,
"rotation": 0,
"lineHeight": 1,
"align": "center",
"verticalAlign": "middle",
"material": "",
"materialTime": "",
"materialName": "",
"QRcodeType": "",
"width": 309.9999999999993,
"height": 11.999265664648076,
"proportion": 60,
"x": 97.0015259021898,
"y": 575.300725990631,
"opacity": 1,
"optionalColor": [
{
"color": "#000000",
"name": "Black",
"default": true
}
],
"zIndex": 3,
"svgPath": "",
"follow": {
"fill": "38c09538-937d-510c-bf32-bfb1ce90cafa",
"ifShow": "",
"content": ""
},
"group": [],
"cameraStand": {
"x": 0,
"y": 0,
"z": 45
}
},
{
"id": "c466e27c-d48f-db86-b85f-3c4c51114046",
"tag": "Logo",
"title": "贴图8",
"type": "image",
"text": "",
"fill": "#0082ca",
"fontSize": 20,
"fontFamily": "Aqum2SmallCaps3",
"ifBr": false,
"ifShow": true,
"ifGroup": false,
"maxNum": 50,
"rotation": 0,
"lineHeight": 1,
"align": "center",
"verticalAlign": "middle",
"material": "{{LogoMaterial}}",
"materialTime": "",
"materialName": "",
"QRcodeType": "",
"width": 282.9999999999999,
"height": 95.99999999999933,
"proportion": 60,
"x": 110.99999999999982,
"y": 438.7192999999991,
"opacity": 1,
"optionalColor": [
{
"color": "#000000",
"name": "Black",
"default": false
},
{
"name": "MainColor",
"color": "#0082ca",
"default": true
}
],
"zIndex": 7,
"svgPath": "",
"follow": {
"fill": "",
"ifShow": "",
"content": ""
},
"group": [],
"cameraStand": {
"x": 0,
"y": 0,
"z": 0
}
}
]
`

View File

@ -8,6 +8,8 @@ const (
WEBSOCKET_UNAUTH = "WEBSOCKET_UNAUTH"
//ws连接成功
WEBSOCKET_CONNECT_SUCCESS = "WEBSOCKET_CONNECT_SUCCESS"
//渲染前数据组装
WEBSOCKET_RENDER_IMAGE_ASSEMBLE = "WEBSOCKET_RENDER_IMAGE_ASSEMBLE"
//图片渲染
WEBSOCKET_RENDER_IMAGE = "WEBSOCKET_RENDER_IMAGE"
//数据格式错误

15
fs_package_docker_image.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
name=${1%%\\*}
#进入对应服务目录
cd server/$name
#构建二进制文件
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ./bin/api-$name-srv ./$name.go
#删除之前旧的镜像
docker rmi -f api-$name-srv:latest
docker rmi -f registry.cn-hangzhou.aliyuncs.com/fusen-test/fusen_docker_hub:latest
#打包docker镜像
docker build -t api-$name-srv:latest .
#打tag(测试环境正式把命名空间fusentest改成fusen)
docker tag api-$name-srv:latest registry.cn-hangzhou.aliyuncs.com/fusen-test/$name:latest
#推送到阿里云镜像库(测试环境正式把命名空间fusentest改成fusen)
docker push registry.cn-hangzhou.aliyuncs.com/fusen-test/$name:latest

View File

@ -1,6 +1,7 @@
Name: {{.serviceName}}
Host: {{.host}}
Port: {{.port}}
Timeout: 15000 #服务超时时间(毫秒)
SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest
SourceRabbitMq: amqp://rabbit001:rabbit001129@110.41.19.98:5672
Auth:

View File

@ -1,12 +1,15 @@
package initalize
import (
"context"
"crypto/tls"
"errors"
"fusenapi/constants"
"fusenapi/utils/mq_consumer_factory"
"github.com/streadway/amqp"
"github.com/zeromicro/go-zero/core/logx"
"log"
"sync"
)
type RabbitMqHandle struct {
@ -18,6 +21,12 @@ type queueItem struct {
queue amqp.Queue
}
// 队列列表
var mqQueueArr = []constants.RABBIT_MQ{
constants.RABBIT_MQ_ASSEMBLE_RENDER_DATA,
constants.RABBIT_MQ_RENDER_RESULT_DATA,
}
// 存储连接
var mapMq = make(map[constants.RABBIT_MQ]queueItem)
@ -35,7 +44,7 @@ func InitRabbitMq(url string, config *tls.Config) *RabbitMqHandle {
log.Fatalf("Failed to open a channel: %v", err)
}
//声明队列
for _, queueName := range constants.MQ_QUEUE_ARR {
for _, queueName := range mqQueueArr {
q, err := ch.QueueDeclare(
string(queueName), // 队列名
true, // 是否持久化
@ -75,11 +84,17 @@ func (h *RabbitMqHandle) SendMsg(queueName constants.RABBIT_MQ, message []byte)
}
// 消费消息
func (h *RabbitMqHandle) Consume(queueName constants.RABBIT_MQ, handleFunc func(data []byte) error) error {
func (h *RabbitMqHandle) Consume(ctx context.Context, queueName constants.RABBIT_MQ, handle mq_consumer_factory.MqHandle) {
object, ok := mapMq[queueName]
if !ok {
return errors.New("unknown queue")
panic("unknown queue")
}
go func() {
select {
case <-ctx.Done():
panic("err ctx deadline")
}
}()
msgs, err := object.ch.Consume(
object.queue.Name, // 队列名
object.queue.Name, // 消费者名,如果为空,则是随机生成一个
@ -94,18 +109,21 @@ func (h *RabbitMqHandle) Consume(queueName constants.RABBIT_MQ, handleFunc func(
}
//允许20的并发
limit := make(chan struct{}, 20)
wait := sync.WaitGroup{}
defer close(limit)
// 消费消息
for msg := range msgs {
limit <- struct{}{}
wait.Add(1)
go func(m amqp.Delivery) {
if err := recover(); err != nil {
logx.Error(err)
}
defer func() {
<-limit
wait.Done()
}()
if err = handleFunc(m.Body); err != nil {
if err = handle.Run(ctx, m.Body); err != nil {
logx.Error("failed to deal with MQ message:", string(m.Body))
return
}
@ -116,5 +134,5 @@ func (h *RabbitMqHandle) Consume(queueName constants.RABBIT_MQ, handleFunc func(
}
}(msg)
}
return nil
wait.Wait()
}

View File

@ -92,5 +92,5 @@ func (c *FsCartModel) DeleteCartsByIds(ctx context.Context, ids []int64) ( err e
if len(ids) == 0 {
return
}
return c.db.WithContext(ctx).Model(&FsCart{}).Where("`id` in (?)", ids).Delete(&FsCart{}).Error
return c.db.Table(c.name).WithContext(ctx).Model(&FsCart{}).Where("`id` in (?)", ids).Update("status", 0).Error
}

View File

@ -1,3 +1,9 @@
package gmodel
import "context"
// TODO: 使用model的属性做你想做的
func (r *FsCloudRenderLogModel) Create(ctx context.Context, data *FsCloudRenderLog) error {
return r.db.WithContext(ctx).Model(&FsCloudRenderLog{}).Create(data).Error
}

View File

@ -6,7 +6,7 @@ import (
"reflect"
"time"
"fusenapi/utils/handler"
"fusenapi/utils/handlers"
"gorm.io/gorm"
)
@ -60,7 +60,7 @@ func (o *FsOrderModel) FindPageListByPage(ctx context.Context, rowBuilder *gorm.
var resp []*FsOrderRel
// 过滤
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap))
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
// 排序
@ -70,11 +70,11 @@ func (o *FsOrderModel) FindPageListByPage(ctx context.Context, rowBuilder *gorm.
for i := 0; i < s.NumField(); i++ {
fieldsMap[s.Field(i).Tag.Get("json")] = struct{}{}
}
rowBuilder = rowBuilder.Scopes(handler.OrderCheck(orderBy, fieldsMap))
rowBuilder = rowBuilder.Scopes(handlers.OrderCheck(orderBy, fieldsMap))
}
// 分页
rowBuilder = rowBuilder.Scopes(handler.Paginate(page, pageSize))
rowBuilder = rowBuilder.Scopes(handlers.Paginate(page, pageSize))
// 结果
result := rowBuilder.Debug().WithContext(ctx).Find(&resp)
@ -125,7 +125,7 @@ func (m *FsOrderModel) FindCount(ctx context.Context, countBuilder *gorm.DB, fil
// 过滤
if filterMap != nil {
countBuilder = countBuilder.Scopes(handler.FilterData(filterMap))
countBuilder = countBuilder.Scopes(handlers.FilterData(filterMap))
}
result := countBuilder.WithContext(ctx).Limit(1).Count(&count)
@ -140,7 +140,7 @@ func (m *FsOrderModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB,
var resp FsOrderRel
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap))
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp)

View File

@ -1,2 +1,15 @@
package gmodel
// TODO: 使用model的属性做你想做的
import "context"
// TODO: 使用model的属性做你想做的
func (p *FsPayEventModel) CreateOrUpdate(ctx context.Context, req *FsPayEvent) (resp *FsPayEvent, err error) {
rowBuilder := p.db.Table(p.name).WithContext(ctx)
if req.Id > 0 {
err = rowBuilder.Save(req).Error
} else {
err = rowBuilder.Create(req).Error
}
return req, err
}

View File

@ -2,7 +2,8 @@ package gmodel
import (
"context"
"fusenapi/utils/handler"
"fusenapi/utils/handlers"
"reflect"
"gorm.io/gorm"
)
@ -54,7 +55,7 @@ func (m *FsPayModel) FindCount(ctx context.Context, countBuilder *gorm.DB, filte
// 过滤
if filterMap != nil {
countBuilder = countBuilder.Scopes(handler.FilterData(filterMap))
countBuilder = countBuilder.Scopes(handlers.FilterData(filterMap))
}
result := countBuilder.WithContext(ctx).Limit(1).Count(&count)
@ -69,7 +70,7 @@ func (m *FsPayModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, fi
var resp FsPay
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap))
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp)
@ -80,6 +81,31 @@ func (m *FsPayModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, fi
}
}
func (m *FsPayModel) FindAll(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string, orderBy string) ([]*FsPay, error) {
var resp []*FsPay
// 过滤
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
// 排序
if orderBy != "" {
var fieldsMap = make(map[string]struct{})
s := reflect.TypeOf(&FsOrder{}).Elem() //通过反射获取type定义
for i := 0; i < s.NumField(); i++ {
fieldsMap[s.Field(i).Tag.Get("json")] = struct{}{}
}
rowBuilder = rowBuilder.Scopes(handlers.OrderCheck(orderBy, fieldsMap))
}
result := rowBuilder.WithContext(ctx).Find(&resp)
if result.Error != nil {
return nil, result.Error
} else {
return resp, nil
}
}
// 事务
func (m *FsPayModel) Trans(ctx context.Context, fn func(ctx context.Context, connGorm *gorm.DB) error) error {
tx := m.db.Table(m.name).WithContext(ctx).Begin()

View File

@ -93,3 +93,11 @@ func (d *FsProductModel3dModel) GetGroupPartListByProductIds(ctx context.Context
Group("product_id").Find(&resp).Error
return resp, err
}
func (d *FsProductModel3dModel) FindOneJoinSize(ctx context.Context, productId int64) (resp FsProductModel3d, err error) {
err = d.db.WithContext(ctx).Table(d.name+"as m").Joins("left join fs_product_size as s on m.size_id = s.id").
Select("m.*").
Where("m.product_id = ?", productId).
Where("(s.status= ? and m.tag = ?)", 1, 1).
Order("s.sort ASC").Take(&resp).Error
return resp, err
}

View File

@ -22,6 +22,8 @@ type FsProductTemplateV2 struct {
Ctime *int64 `gorm:"default:0;" json:"ctime"` // 添加时间
Tag *string `gorm:"default:'';" json:"tag"` // 标签(用户自填)
IsDel *int64 `gorm:"default:0;" json:"is_del"` // 是否删除 1删除
GroupOptions *string `gorm:"default:'';" json:"group_options"` // 颜色分组
Version *int64 `gorm:"default:0;" json:"version"` //
}
type FsProductTemplateV2Model struct {
db *gorm.DB

View File

@ -106,3 +106,16 @@ func (t *FsProductTemplateV2Model) GetProductTemplateListByParams(ctx context.Co
err = db.Find(&resp).Error
return resp, err
}
// 获取第一个尺寸下的模板
func (t *FsProductTemplateV2Model) FindOneByProductIdTagIdWithSizeTable(ctx context.Context, productId int64, tagId string) (resp *FsProductTemplateV2, err error) {
err = t.db.WithContext(ctx).Table(t.name+" as t").
Joins("left join fs_product_size as s on t.product_id = s.product_id").
Select("t.*").
Where("t.product_id = ? and t.tag = ? ", productId, tagId).
Where("t.status = ? and t.is_del = ?", 1, 0).
Where("s.status = ?", 1).
Order("s.sort ASC").
Take(&resp).Error
return resp, err
}

View File

@ -2,6 +2,7 @@ package gmodel
import (
"context"
"fusenapi/utils/handlers"
"gorm.io/gorm"
)
@ -24,3 +25,43 @@ func (m *FsRefundReasonModel) Update(ctx context.Context, obj *FsRefundReason) e
func (m *FsRefundReasonModel) UpdateByRefundReasonId(ctx context.Context, obj *FsRefundReason) error {
return m.db.WithContext(ctx).Model(obj).Where("`refund_reason_id` = ?", obj.RefundReasonId).Updates(obj).Error
}
func (m *FsRefundReasonModel) CreateOrUpdate(ctx context.Context, req *FsRefundReason) (resp *FsRefundReason, err error) {
rowBuilder := m.db.Table(m.name).WithContext(ctx)
if req.Id > 0 {
err = rowBuilder.Save(req).Error
} else {
err = rowBuilder.Create(req).Error
}
return req, err
}
func (m *FsRefundReasonModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string) (*FsRefundReason, error) {
var resp FsRefundReason
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp)
if result.Error != nil {
return nil, result.Error
} else {
return &resp, nil
}
}
func (m *FsRefundReasonModel) RowSelectBuilder(selectData []string) *gorm.DB {
var rowBuilder = m.db.Table(m.name)
if selectData != nil {
rowBuilder = rowBuilder.Select(selectData)
} else {
rowBuilder = rowBuilder.Select("*")
}
return rowBuilder
}
func (m *FsRefundReasonModel) TableName() string {
return m.name
}

View File

@ -16,6 +16,8 @@ type FsResource struct {
UploadedAt *time.Time `gorm:"index;default:'0000-00-00 00:00:00';" json:"uploaded_at"` // 上传时间
Metadata *string `gorm:"default:'';" json:"metadata"` // 元数据,json格式,存储图像分率
MetaKey1 *string `gorm:"index;default:'';" json:"meta_key1"` // 需要关键信息查询的自定义属性1,可以动态增加
ApiType *int64 `gorm:"default:1;" json:"api_type"` // 调用类型1=对外2=对内
BucketName *string `gorm:"default:'';" json:"bucket_name"` // 存储桶名
}
type FsResourceModel struct {
db *gorm.DB

View File

@ -1 +1,84 @@
package gmodel
import (
"context"
"errors"
"fusenapi/utils/handlers"
"gorm.io/gorm"
)
// TODO: 使用model的属性做你想做的
func (p *FsResourceModel) FindOneById(ctx context.Context, resourceId string) (*FsResource, error) {
var resp FsResource
result := p.db.Table(p.name).WithContext(ctx).Where("resource_id =?", resourceId).Take(&resp)
if result.Error != nil {
// 检查 ErrRecordNotFound 错误
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, result.Error
}
}
return &resp, nil
}
func (p *FsResourceModel) Create(ctx context.Context, req *FsResource) (resp *FsResource, err error) {
err = p.db.Table(p.name).WithContext(ctx).Create(req).Error
return req, err
}
func (p *FsResourceModel) Update(ctx context.Context, req *FsResource) (resp *FsResource, err error) {
err = p.db.Table(p.name).WithContext(ctx).Where("resource_id =?", req.ResourceId).Save(req).Error
return req, err
}
func (m *FsResourceModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string) (*FsResource, error) {
var resp FsResource
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp)
if result.Error != nil {
return nil, result.Error
} else {
return &resp, nil
}
}
func (m *FsResourceModel) RowSelectBuilder(selectData []string) *gorm.DB {
var rowBuilder = m.db.Table(m.name)
if selectData != nil {
rowBuilder = rowBuilder.Select(selectData)
} else {
rowBuilder = rowBuilder.Select("*")
}
return rowBuilder
}
// 事务
func (m *FsResourceModel) Trans(ctx context.Context, fn func(ctx context.Context, connGorm *gorm.DB) error) error {
tx := m.db.Table(m.name).WithContext(ctx).Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
if err := fn(ctx, tx); err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
func (m *FsResourceModel) TableName() string {
return m.name
}

View File

@ -18,6 +18,7 @@ type FsTags struct {
Description *string `gorm:"default:'';" json:"description"` // 介绍 Seo
RecommendProduct *string `gorm:"default:'';" json:"recommend_product"` //
RecommendProductSort *string `gorm:"default:'';" json:"recommend_product_sort"` //
Category *int64 `gorm:"default:1;" json:"category"` // 分类1前台用的 2后台用的
}
type FsTagsModel struct {
db *gorm.DB

View File

@ -36,6 +36,7 @@ type GetAllTagByParamsReq struct {
OrderBy string
LevelPrefixLeftLike string //右模糊
WithChild bool //是否包含子层级
Category int64
}
func (t *FsTagsModel) GetAllTagByParams(ctx context.Context, req GetAllTagByParamsReq) (resp []FsTags, err error) {
@ -46,6 +47,9 @@ func (t *FsTagsModel) GetAllTagByParams(ctx context.Context, req GetAllTagByPara
if req.Status != nil {
db = db.Where("`status` = ?", *req.Status)
}
if req.Category != 0 {
db = db.Where("`category` = ?", req.Category)
}
if req.LevelPrefixLeftLike != "" {
//查询子集
if req.WithChild {

View File

@ -0,0 +1,25 @@
package gmodel
import (
"gorm.io/gorm"
)
// fs_user_material 用户素材表
type FsUserMaterial struct {
Id int64 `gorm:"primary_key;default:0;auto_increment;" json:"id"` // 用户 ID
Module *string `gorm:"default:'';" json:"module"` // 所属模块:logo
UserId *int64 `gorm:"index;default:0;" json:"user_id"` // 用户 ID
GuestId *int64 `gorm:"index;default:0;" json:"guest_id"` // 游客 ID
ResourceId *string `gorm:"default:'';" json:"resource_id"` // 资源ID
ResourceUrl *string `gorm:"default:'';" json:"resource_url"` // 资源 URL
Metadata *string `gorm:"default:'';" json:"metadata"` // 元数据,json格式,存储图像分率
CreateAt *int64 `gorm:"default:0;" json:"create_at"` // 上传时间
}
type FsUserMaterialModel struct {
db *gorm.DB
name string
}
func NewFsUserMaterialModel(db *gorm.DB) *FsUserMaterialModel {
return &FsUserMaterialModel{db: db, name: "fs_user_material"}
}

View File

@ -0,0 +1,72 @@
package gmodel
import (
"context"
"fusenapi/utils/handlers"
"reflect"
"gorm.io/gorm"
)
// TODO: 使用model的属性做你想做的
func (p *FsUserMaterialModel) CreateOrUpdate(ctx context.Context, req *FsUserMaterial) (resp *FsUserMaterial, err error) {
rowBuilder := p.db.Table(p.name).WithContext(ctx)
if req.Id > 0 {
err = rowBuilder.Save(req).Error
} else {
err = rowBuilder.Create(req).Error
}
return req, err
}
func (m *FsUserMaterialModel) FindAll(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string, orderBy string) ([]*FsUserMaterial, error) {
var resp []*FsUserMaterial
// 过滤
if filterMap != nil {
rowBuilder = rowBuilder.Scopes(handlers.FilterData(filterMap))
}
// 排序
if orderBy != "" {
var fieldsMap = make(map[string]struct{})
s := reflect.TypeOf(&FsUserMaterial{}).Elem() //通过反射获取type定义
for i := 0; i < s.NumField(); i++ {
fieldsMap[s.Field(i).Tag.Get("json")] = struct{}{}
}
rowBuilder = rowBuilder.Scopes(handlers.OrderCheck(orderBy, fieldsMap))
}
result := rowBuilder.WithContext(ctx).Find(&resp)
if result.Error != nil {
return nil, result.Error
} else {
return resp, nil
}
}
func (m *FsUserMaterialModel) RowSelectBuilder(selectData []string) *gorm.DB {
var rowBuilder = m.db.Table(m.name)
if selectData != nil {
rowBuilder = rowBuilder.Select(selectData)
} else {
rowBuilder = rowBuilder.Select("*")
}
return rowBuilder
}
// 获取最新记录
func (m *FsUserMaterialModel) FindLatestOne(ctx context.Context, userId int64, guestId int64) (resp FsUserMaterial, err error) {
if userId == 0 && guestId == 0 {
return FsUserMaterial{}, nil
}
db := m.db.WithContext(ctx).Model(&FsUserMaterial{}).Order("id DESC")
if userId != 0 {
db = db.Where("`user_id` = ?", userId)
} else {
db = db.Where("`guest_id` = ?", guestId)
}
err = db.Take(&resp).Error
return resp, err
}

View File

@ -89,6 +89,7 @@ type AllModelsGen struct {
FsTrade *FsTradeModel // fs_trade
FsUser *FsUserModel // fs_user 用户表
FsUserDesign *FsUserDesignModel // fs_user_design 废弃表
FsUserMaterial *FsUserMaterialModel // fs_user_material 用户素材表
FsUserStock *FsUserStockModel // fs_user_stock 用户云仓库存
FsWebSet *FsWebSetModel // fs_web_set 网站配置表
@ -181,6 +182,7 @@ func NewAllModels(gdb *gorm.DB) *AllModelsGen {
FsTrade: NewFsTradeModel(gdb),
FsUser: NewFsUserModel(gdb),
FsUserDesign: NewFsUserDesignModel(gdb),
FsUserMaterial: NewFsUserMaterialModel(gdb),
FsUserStock: NewFsUserStockModel(gdb),
FsWebSet: NewFsWebSetModel(gdb),
}

6
server/auth/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-assistant-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-assistant-srv"]

6
server/backend/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-backend-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-backend-srv"]

6
server/canteen/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-canteen-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-canteen-srv"]

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-data-transfer-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-data-transfer-srv"]

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-home-user-auth-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-home-user-auth-srv"]

View File

@ -12,3 +12,10 @@ Auth:
Stripe:
SK: "sk_test_51IisojHygnIJZeghPVSBhkwySfcyDV4SoAduIxu3J7bvSJ9cZMD96LY1LO6SpdbYquLJX5oKvgEBB67KT9pecfCy00iEC4pp9y"
PayConfig:
Stripe:
Key: "sk_test_51IisojHygnIJZeghPVSBhkwySfcyDV4SoAduIxu3J7bvSJ9cZMD96LY1LO6SpdbYquLJX5oKvgEBB67KT9pecfCy00iEC4pp9y"
EndpointSecret: "whsec_f5f9a121d43af3789db7459352f08cf523eb9e0fbf3381f91ba6c97c324c174d"
SuccessURL: "http://www.baidu.com"
CancelURL: "http://www.baidu.com"

View File

@ -17,4 +17,13 @@ type Config struct {
Stripe struct {
SK string
}
PayConfig struct {
Stripe struct {
EndpointSecret string
Key string
CancelURL string
SuccessURL string
}
}
}

View File

@ -72,6 +72,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/api/user/order-cancel",
Handler: UserOrderCancelHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/api/user/logo-list",
Handler: UserLogoListHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/api/user/one-more-order",
Handler: UserAgainOrderHandler(serverCtx),
},
},
)
}

View File

@ -6,27 +6,27 @@ import (
"fusenapi/utils/basic"
"fusenapi/server/websocket/internal/logic"
"fusenapi/server/websocket/internal/svc"
"fusenapi/server/websocket/internal/types"
"fusenapi/server/home-user-auth/internal/logic"
"fusenapi/server/home-user-auth/internal/svc"
"fusenapi/server/home-user-auth/internal/types"
)
func ThirdPartyLoginNotifyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func UserAgainOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ThirdPartyLoginNotifyReq
var req types.UserAgainOrderReq
userinfo, err := basic.RequestParse(w, r, svcCtx.SharedState, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewThirdPartyLoginNotifyLogic(r.Context(), svcCtx)
l := logic.NewUserAgainOrderLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.ThirdPartyLoginNotify(&req, userinfo)
resp := l.UserAgainOrder(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/home-user-auth/internal/logic"
"fusenapi/server/home-user-auth/internal/svc"
"fusenapi/server/home-user-auth/internal/types"
)
func UserLogoListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UserLogoListReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUserLogoListLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UserLogoList(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,144 @@
package logic
import (
"errors"
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"time"
"context"
"fusenapi/server/home-user-auth/internal/svc"
"fusenapi/server/home-user-auth/internal/types"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
)
type UserAgainOrderLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUserAgainOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserAgainOrderLogic {
return &UserAgainOrderLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UserAgainOrderLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UserAgainOrderLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UserAgainOrderLogic) UserAgainOrder(req *types.UserAgainOrderReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
if userinfo == nil || userinfo.UserId == 0 {
return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order not found")
}
// 查询订单数据
orderModel := gmodel.NewFsOrderModel(l.svcCtx.MysqlConn)
orderDetailTemplateModel := gmodel.NewFsOrderDetailTemplateModel(l.svcCtx.MysqlConn)
fsOrderDetailModel := gmodel.NewFsOrderDetailModel(l.svcCtx.MysqlConn)
fsProductDesignModel := gmodel.NewFsProductDesignModel(l.svcCtx.MysqlConn)
rsbOrder := orderModel.RowSelectBuilder(nil)
rsbOrder = rsbOrder.Where("sn =?", req.Sn).Preload("FsOrderDetails")
rsbOrder = rsbOrder.Preload("FsOrderDetails", func(dbPreload *gorm.DB) *gorm.DB {
return dbPreload.Table(fsOrderDetailModel.TableName()).Preload("FsOrderDetailTemplateInfo", func(dbPreload *gorm.DB) *gorm.DB {
return dbPreload.Table(orderDetailTemplateModel.TableName()).Preload("FsProductDesignInfo", func(dbPreload *gorm.DB) *gorm.DB {
return dbPreload.Table(fsProductDesignModel.TableName())
})
})
})
fsOrderRelInfo, err := orderModel.FindOneByQuery(l.ctx, rsbOrder, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order not found")
}
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get order info")
}
if len(fsOrderRelInfo.FsOrderDetails) > 0 {
for _, fsOrderDetail := range fsOrderRelInfo.FsOrderDetails {
var isCheck int64 = 1
productDesignInfo := fsOrderDetail.FsOrderDetailTemplateInfo.FsProductDesignInfo
if productDesignInfo.Id != 0 {
// 查找是否有此材质、产品、大小id的阶梯价格
productPriceModel := gmodel.NewFsProductPriceModel(l.svcCtx.MysqlConn)
priceStatus := int64(1)
priceReq := gmodel.FindOneProductPriceByParamsReq{
ProductId: productDesignInfo.ProductId,
MaterialId: productDesignInfo.MaterialId,
SizeId: productDesignInfo.SizeId,
Status: &priceStatus,
}
productPriceInfo, err := productPriceModel.FindOneProductPriceByParams(l.ctx, priceReq)
if err == nil && productPriceInfo.Id != 0 && *productPriceInfo.EachBoxNum > 0 {
// 买的数量和每箱数量取余为0 且 份数大于等于最小购买数量才算满足条件
if *fsOrderDetail.BuyNum%*productPriceInfo.EachBoxNum == 0 && int64(float64(*fsOrderDetail.BuyNum)/float64(*productPriceInfo.EachBoxNum)) >= *productPriceInfo.MinBuyNum {
// 查询购物车
cartModel := gmodel.NewFsCartModel(l.svcCtx.MysqlConn)
cartStatus := int64(1)
cartReq := gmodel.FindOneCartByParamsReq{
UserId: &userinfo.UserId,
ProductId: productDesignInfo.ProductId,
TemplateId: productDesignInfo.TemplateId,
PriceId: &productPriceInfo.Id,
DesignId: &productDesignInfo.Id,
MaterialId: productDesignInfo.MaterialId,
Status: &cartStatus,
}
cartInfo, err := cartModel.FindOneCartByParams(l.ctx, cartReq)
if err == nil && (err != nil && errors.Is(err, gorm.ErrRecordNotFound)) {
now := time.Now().Unix()
nowTime := time.Now()
data := gmodel.FsCart{
UserId: &userinfo.UserId,
ProductId: productPriceInfo.ProductId,
TemplateId: productDesignInfo.TemplateId,
PriceId: &productPriceInfo.Id,
MaterialId: productDesignInfo.MaterialId,
SizeId: productDesignInfo.SizeId,
BuyNum: fsOrderDetail.BuyNum,
Cover: productDesignInfo.Cover,
DesignId: &productDesignInfo.Id,
Ctime: &now,
Status: &cartStatus,
OptionalId: productDesignInfo.OptionalId,
IsCheck: &isCheck,
TsTime: &nowTime,
}
if cartInfo == nil {
err = cartModel.Create(l.ctx, data)
} else {
err = cartModel.Update(l.ctx, cartInfo.Id, data)
}
if err != nil {
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to add to cart")
}
}
}
}
}
}
}
return resp.SetStatus(basic.CodeOK)
}

View File

@ -0,0 +1,71 @@
package logic
import (
"errors"
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"context"
"fusenapi/server/home-user-auth/internal/svc"
"fusenapi/server/home-user-auth/internal/types"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
)
type UserLogoListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUserLogoListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLogoListLogic {
return &UserLogoListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UserLogoListLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UserLogoListLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UserLogoListLogic) UserLogoList(req *types.UserLogoListReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
// 定义用户ID
var userId int64
var guestId int64
// 检查用户是否是游客
if userinfo.IsGuest() {
// 如果是使用游客ID和游客键名格式
guestId = userinfo.GuestId
} else {
// 否则使用用户ID和用户键名格式
userId = userinfo.UserId
}
userMaterialModel := gmodel.NewFsUserMaterialModel(l.svcCtx.MysqlConn)
userMaterialRSB := userMaterialModel.RowSelectBuilder(nil).
Where("module = ?", "logo").Where("user_id = ?", userId).Where("guest_id = ?", guestId).Order("id desc")
list, err := userMaterialModel.FindAll(l.ctx, userMaterialRSB, nil, "")
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "data not found")
}
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get data list")
}
return resp.SetStatus(basic.CodeOK, map[string]interface{}{
"list": list,
})
}

View File

@ -6,6 +6,8 @@ import (
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"fusenapi/utils/handlers"
"time"
"context"
@ -50,11 +52,76 @@ func (l *UserOrderCancelLogic) UserOrderCancel(req *types.UserOrderCancelReq, us
}
// 判断订单状态
if *orderInfo.Status == int64(constants.STATUS_NEW_NOT_PAY) {
} else {
var notCancelStatusMap = make(map[int64]struct{}, 3)
notCancelStatusMap[int64(constants.STATUS_NEW_NOT_PAY)] = struct{}{}
notCancelStatusMap[int64(constants.STATUS_NEW_PART_PAY)] = struct{}{}
notCancelStatusMap[int64(constants.STATUS_NEW_PAY_COMPLETED)] = struct{}{}
_, ok := notCancelStatusMap[int64(*orderInfo.Status)]
if !ok {
return resp.SetStatusWithMessage(basic.CodeOrderNotCancelledErr, "the order status not cancle")
}
var cancelTime int64 = time.Now().Unix() - (*orderInfo.Ctime + int64(constants.CANCLE_ORDER_EXPIRE))
// 第一次支付成功后48小时后不能进行取消操作
if *orderInfo.IsPayCompleted == 1 && cancelTime > 0 {
return resp.SetStatusWithMessage(basic.CodeOrderNotCancelledErr, "The current order cannot be cancelled")
}
// 修改订单--取消状态和取消原因
*orderInfo.Status = int64(constants.STATUS_NEW_CANCEL)
*orderInfo.IsCancel = 1
orderInfo.RefundReasonId = &req.RefundReasonId
orderInfo.RefundReason = &req.RefundReason
var nowTime = time.Now().Unix()
var payList []handlers.PayInfo
// 事务处理
err = orderModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) (err error) {
// 修改订单信息
orderModelTS := gmodel.NewFsOrderModel(connGorm)
err = orderModelTS.Update(ctx, orderInfo)
if err != nil {
return err
}
// 新增退款记录
var isRefund int64 = 0
refundReasonModelTS := gmodel.NewFsRefundReasonModel(connGorm)
refundReasonModelTS.CreateOrUpdate(ctx, &gmodel.FsRefundReason{
IsRefund: &isRefund,
RefundReasonId: &req.RefundReasonId,
RefundReason: &req.RefundReason,
OrderId: &orderInfo.Id,
CreatedAt: &nowTime,
})
// 退款申请
// 退款申请--查询支付信息
fsPayModelTS := gmodel.NewFsPayModel(connGorm)
rbFsPay := fsPayModelTS.RowSelectBuilder(nil).Where("order_number = ?", orderInfo.Sn).Where("pay_status =?", constants.PAYSTATUS_SUCCESS).Where("is_refund =?", 0)
payInfoList, err := fsPayModelTS.FindAll(ctx, rbFsPay, nil, "")
if err != nil {
return err
}
for _, payInfo := range payInfoList {
var key string
if *payInfo.PaymentMethod == int64(constants.PAYMETHOD_STRIPE) {
key = l.svcCtx.Config.PayConfig.Stripe.Key
}
payList = append(payList, handlers.PayInfo{
TradeNo: *payInfo.TradeNo,
PaymentMethod: *payInfo.PaymentMethod,
Key: key,
})
}
return nil
})
// 退款申请--调取第三方接口发起退款
handlers.PayRefundHandler(&handlers.PayRefundHandlerReq{
PayInfoList: payList,
})
if err != nil {
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeOrderCancelledNotOk, "the order cancle failed")
}
return resp.SetStatus(basic.CodeOK)
}

View File

@ -5,6 +5,19 @@ import (
"fusenapi/utils/basic"
)
type UserAgainOrderReq struct {
Sn string `form:"sn"` // 订单编号
}
type UserAgainOrderRes struct {
}
type UserLogoListReq struct {
}
type UserLogoListRes struct {
}
type UserOrderDeleteReq struct {
ID int64 `form:"id"` //订单id
}

6
server/inventory/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-inventory-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-inventory-srv"]

View File

@ -1,7 +1,6 @@
Name: inventory
Host: localhost
Port: 9905
ReplicaId: 30
SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest
Auth:
AccessSecret: fusen2023

View File

@ -18,7 +18,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Handler: TakeHandler(serverCtx),
},
{
Method: http.MethodGet,
Method: http.MethodPost,
Path: "/api/inventory/list",
Handler: GetCloudListHandler(serverCtx),
},

View File

@ -16,9 +16,9 @@ type TakeForm struct {
}
type GetCloudListReq struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Size int64 `form:"size"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Size int64 `json:"size"`
}
type GetCloudListRsp struct {

View File

@ -0,0 +1,12 @@
package main
import (
"testing"
)
// var configFile = flag.String("f", "etc/home-user-auth.yaml", "the config file")
func TestMain(t *testing.T) {
// log.Println(model.RawFieldNames[FsCanteenType]())
main()
}

6
server/map-library/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-map-library-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-map-library-srv"]

6
server/orders/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-order-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-order-srv"]

6
server/pay/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-pay-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-pay-srv"]

View File

@ -1,8 +1,6 @@
Name: pay
Host: 0.0.0.0
Port: 9915
ReplicaId: 45
Timeout: 15000
SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest
Auth:
AccessSecret: fusen2023

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/pay/internal/logic"
"fusenapi/server/pay/internal/svc"
"fusenapi/server/pay/internal/types"
)
func OrderRefundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OrderRefundReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewOrderRefundLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.OrderRefund(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -17,6 +17,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/api/pay/payment-intent",
Handler: OrderPaymentIntentHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/pay/refund",
Handler: OrderRefundHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/pay/stripe-webhook",

View File

@ -30,6 +30,15 @@ func StripeWebhookHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// return
// }
IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" {
IPAddress = r.Header.Get("X-Forwarded-For")
}
if IPAddress == "" {
IPAddress = r.RemoteAddr
}
req.RemoteAddr = IPAddress
req.Payload = payload
req.StripeSignature = r.Header.Get("Stripe-Signature")

View File

@ -0,0 +1,43 @@
package logic
import (
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"context"
"fusenapi/server/pay/internal/svc"
"fusenapi/server/pay/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type OrderRefundLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewOrderRefundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrderRefundLogic {
return &OrderRefundLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *OrderRefundLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *OrderRefundLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *OrderRefundLogic) OrderRefund(req *types.OrderRefundReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
return resp.SetStatus(basic.CodeOK)
}

View File

@ -64,6 +64,21 @@ func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "Webhook signature verification failed")
}
// 新增支付回调事件日志
var payMethod = int64(constants.PAYMETHOD_STRIPE)
var nowTime = time.Now().Unix()
var eventData = string(event.Data.Raw)
var fsPayEvent = &gmodel.FsPayEvent{
PayMethod: &payMethod,
EventId: &event.ID,
EventType: &event.Type,
EventData: &eventData,
EventCreated: &event.Created,
Ip: &req.RemoteAddr,
CreatedAt: &nowTime,
}
l.HandlePayEventCreate(fsPayEvent)
// Unmarshal the event data into an appropriate struct depending on its Type
switch event.Type {
case "charge.succeeded":
@ -91,12 +106,13 @@ func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo
var paymentIntent stripe.PaymentIntent
err := json.Unmarshal(event.Data.Raw, &paymentIntent)
if err != nil {
logx.Error(err)
logx.Errorf("err%+vdesc%s", err, "pay notify Unmarshal fail event.Type payment_intent.succeeded")
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type payment_intent.succeeded")
}
err = l.handlePaymentIntentSucceeded(&paymentIntent)
err = l.HandlePaymentIntentSucceeded(&paymentIntent)
if err != nil {
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type Unhandled")
logx.Errorf("err%+vdesc%s", err, "pay notify handle payment_intent.succeeded")
return resp.SetStatusWithMessage(basic.CodePaybackNotOk, "pay notify handle payment_intent.succeeded")
}
case "payment_method.attached":
var paymentMethod stripe.PaymentMethod
@ -105,6 +121,19 @@ func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type payment_method.attached")
}
case "charge.refunded":
var chargeRefunded stripe.Charge
err := json.Unmarshal(event.Data.Raw, &chargeRefunded)
if err != nil {
logx.Errorf("err%+vdesc%s", err, "pay notify Unmarshal fail event.Type charge.refunded")
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type charge.refunded")
}
err = l.HandleChargeRefunded(&chargeRefunded)
if err != nil {
logx.Errorf("err%+vdesc%s", err, "pay notify handle charge.refunded")
return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify handle charge.refunded")
}
// ... handle other event types
default:
logx.Error("Unhandled event")
@ -114,6 +143,72 @@ func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo
return resp.SetStatus(basic.CodeOK)
}
// 回调事件日志
func (l *StripeWebhookLogic) HandlePayEventCreate(fsPayEvent *gmodel.FsPayEvent) error {
_, err := gmodel.NewFsPayEventModel(l.svcCtx.MysqlConn).CreateOrUpdate(l.ctx, fsPayEvent)
return err
}
// 退款成功
func (l *StripeWebhookLogic) HandleChargeRefunded(chargeRefunded *stripe.Charge) (err error) {
// 退款成功
if chargeRefunded.Status == "succeeded" {
orderModel := gmodel.NewFsOrderModel(l.svcCtx.MysqlConn)
err = orderModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) (err error) {
// 查询支付记录
payModelT := gmodel.NewFsPayModel(connGorm)
payModelTRSB := payModelT.RowSelectBuilder(nil)
payModelTRSB1 := payModelTRSB.Where("trade_no = ?", chargeRefunded.PaymentIntent.ID).Where("pay_status = ?", constants.PAYSTATUS_SUCCESS).Where("is_refund = ?", 0)
payInfo, err := payModelT.FindOneByQuery(l.ctx, payModelTRSB1, nil)
if err != nil {
return err
}
// 更新支付记录
*payInfo.IsRefund = 1
_, err = payModelT.CreateOrUpdate(ctx, payInfo)
if err != nil {
return err
}
// 获取是否还有未退款的数据
payModelTRSB2 := payModelTRSB.Where("order_number = ?", payInfo.OrderNumber).Where("pay_status = ?", constants.PAYSTATUS_SUCCESS).Where("is_refund = ?", 0)
count, err := payModelT.FindCount(l.ctx, payModelTRSB2, nil)
if count == 0 {
// 退款完成更新订单状态
orderModelT := gmodel.NewFsOrderModel(connGorm)
orderModelTRSB := orderModelT.RowSelectBuilder(nil).Where("sn =?", payInfo.OrderNumber)
orderInfoRel, err := orderModelT.FindOneByQuery(ctx, orderModelTRSB, nil)
if err != nil {
return err
}
var isRefunded int64 = 1
var isRefunding int64 = 1
var orderStatus int64 = int64(constants.STATUS_NEW_REFUNDED)
var orderInfo = &gmodel.FsOrder{}
orderInfo.Id = orderInfoRel.Id
orderInfo.IsRefunded = &isRefunded
orderInfo.IsRefunding = &isRefunding
orderInfo.Status = &orderStatus
orderModelT.Update(ctx, orderInfo)
// 记录退款原因
refundReasonModelT := gmodel.NewFsRefundReasonModel(connGorm)
refundReasonModelTRSB := refundReasonModelT.RowSelectBuilder(nil).Where("order_id =?", orderInfoRel.Id)
refundReasonInfo, err := refundReasonModelT.FindOneByQuery(ctx, refundReasonModelTRSB, nil)
if err != nil {
return err
}
*refundReasonInfo.IsRefund = 1
_, err = refundReasonModelT.CreateOrUpdate(ctx, refundReasonInfo)
if err != nil {
return err
}
}
return err
})
}
return err
}
// session完成
// func (l *StripeWebhookLogic) handlePaymentSessionCompleted(sessionId string, tradeNo string) (err error) {
// // 查询支付记录
@ -136,8 +231,8 @@ func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo
// return err
// }
// 成功的付款
func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.PaymentIntent) error {
// 付款成功
func (l *StripeWebhookLogic) HandlePaymentIntentSucceeded(paymentIntent *stripe.PaymentIntent) error {
orderSn, ok := paymentIntent.Metadata["order_sn"]
if !ok || orderSn == "" {
return errors.New("order_sn not found")
@ -146,14 +241,12 @@ func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.
// 查询支付记录
payModel := gmodel.NewFsPayModel(l.svcCtx.MysqlConn)
rsbPay := payModel.RowSelectBuilder(nil)
rsbPay = rsbPay.Where("order_number = ?", orderSn)
rsbPay = rsbPay.Where("order_number = ?", orderSn).Where("pay_status = ?", constants.PAYSTATUS_UNSUCCESS)
payInfo, err := payModel.FindOneByQuery(l.ctx, rsbPay, nil)
if err != nil {
return err
}
if *payInfo.PayStatus == 1 {
return errors.New("pay status 1")
}
//订单信息
orderDetailTemplateModel := gmodel.NewFsOrderDetailTemplateModel(l.svcCtx.MysqlConn)
orderModel := gmodel.NewFsOrderModel(l.svcCtx.MysqlConn)
@ -209,6 +302,7 @@ func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.
*payInfo.PayTime = nowTime
*payInfo.CardNo = card
*payInfo.Brand = brand
*payInfo.TradeNo = paymentIntent.ID
_, err = payModelT.CreateOrUpdate(ctx, payInfo)
if err != nil {
return err
@ -244,12 +338,12 @@ func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.
// 支付记录是尾款
if *payInfo.PayStage == int64(constants.PAYSTAGE_REMAINING) {
if *orderInfo.Status < int64(constants.STATUS_NEW_PAY_COMPLETED) {
if *fsOrderRelInfo.Status < int64(constants.STATUS_NEW_PAY_COMPLETED) {
orderStatus = int64(constants.STATUS_NEW_PAY_COMPLETED)
}
orderIsPayCompleted = 1
orderInfo.IsPayCompleted = &orderIsPayCompleted
orderPayedAmount = *orderInfo.PayedAmount + paymentIntent.Amount
orderPayedAmount = *fsOrderRelInfo.PayedAmount + paymentIntent.Amount
}
// 更新订单信息

View File

@ -5,6 +5,12 @@ import (
"fusenapi/utils/basic"
)
type OrderRefundReq struct {
}
type OrderRefundRes struct {
}
type OrderPaymentIntentReq struct {
Sn string `form:"sn"` //订单编号
DeliveryMethod int64 `form:"delivery_method"` //发货方式
@ -20,6 +26,7 @@ type OrderPaymentIntentRes struct {
type StripeWebhookReq struct {
Payload []byte `json:"base_byte_slice,optional"`
StripeSignature string `json:"Stripe-Signature"`
RemoteAddr string `json:"remote_addr"`
}
type Request struct {

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-product-model-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-product-model-srv"]

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-product-template-tag-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-product-template-tag-srv"]

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-product-template-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-product-template-srv"]

6
server/product/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-product-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-product-srv"]

View File

@ -50,6 +50,7 @@ func (l *GetTagProductListLogic) GetTagProductList(req *types.GetTagProductListR
Status: &tStatus,
OrderBy: "`sort` DESC",
WithChild: true, //需要子集
Category: 1, //前台网站用的
}
//传入分类id
if req.Cid > 0 {
@ -62,6 +63,9 @@ func (l *GetTagProductListLogic) GetTagProductList(req *types.GetTagProductListR
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get tag info")
}
if *tagData.Category != 1 {
return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "invalid tag")
}
tReq.LevelPrefixLeftLike = *tagData.LevelPrefix
}
tagList, err := l.svcCtx.AllModels.FsTags.GetAllTagByParams(l.ctx, tReq)
@ -220,7 +224,7 @@ func (l *GetTagProductListLogic) dealWithTagMenuData(req dealWithTagMenuDataReq)
*req.MinLevel = lenLevel
}
tagTem := types.TagItem{
TagProductList: nil,
TagProductList: []interface{}{},
TypeName: *tagInfo.Title,
TypeId: tagInfo.Id,
Icon: *tagInfo.Icon,

6
server/render/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-render-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-render-srv"]

View File

@ -0,0 +1,238 @@
package consumer
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"fusenapi/model/gmodel"
"fusenapi/utils/websocket_data"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
"io/ioutil"
"net/http"
"time"
)
// 这里请求的py接口返回数据
type pythonApiRsp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data []struct {
Tid int64 `json:"tid"`
Imgurl string `json:"imgurl"`
Costtime int64 `json:"costtime"`
} `json:"data"`
}
// 消费渲染需要组装的数据
type MqConsumerRenderAssemble struct {
}
func (m *MqConsumerRenderAssemble) Run(ctx context.Context, data []byte) error {
logx.Info("收到需要组装的消息:", string(data))
var parseInfo websocket_data.AssembleRenderData
if err := json.Unmarshal(data, &parseInfo); err != nil {
logx.Error("MqConsumerRenderAssemble数据格式错误:", err)
return nil //不返回错误就删除消息
}
val := ctx.Value("allmodels")
if val == nil {
return errors.New("allmodels is nil")
}
allmodels, ok := val.(*gmodel.AllModelsGen)
if !ok {
return errors.New("allmodels is nil!!")
}
timeSearchBegin := time.Now().UnixMilli()
//获取模板
templateInfo, err := allmodels.FsProductTemplateV2.FindOneByProductIdTagIdWithSizeTable(ctx, parseInfo.RenderData.ProductId, fmt.Sprintf("%d", parseInfo.RenderData.TemplateTagId))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
logx.Error("template info is not found")
return nil
}
logx.Error("failed to get template info:", err)
return err
}
renderLogTime := time.Now().UnixMilli() - timeSearchBegin
now := time.Now().Unix()
title := "1-组装模板数据"
//云渲染日志
err = allmodels.FsCloudRenderLog.Create(ctx, &gmodel.FsCloudRenderLog{
UserId: &parseInfo.RenderData.UserId,
Title: &title,
Time: &renderLogTime,
Tag: &parseInfo.RenderId,
Ctime: &now,
})
if err != nil {
logx.Error(err)
}
pyapiBeginTime := time.Now().UnixMilli()
//这里curl post请求数据。获取处理好的贴图数据用于贴model的贴图
pythonPostData := map[string]interface{}{
"tids": templateInfo.Id,
"data": parseInfo.RenderData.Data,
}
pyPostBytes, _ := json.Marshal(pythonPostData)
url := "http://110.41.19.98:8867/imgRender"
pyRsp, err := http.Post(url, "application/json;charset=UTF-8", bytes.NewReader(pyPostBytes))
if err != nil {
logx.Error("request python render api err:", err)
return err
}
defer pyRsp.Body.Close()
pyRspBytes, err := ioutil.ReadAll(pyRsp.Body)
if err != nil {
logx.Error("failed to read python api rsp body,err=", err)
return err
}
var rspInfo pythonApiRsp
if err = json.Unmarshal(pyRspBytes, &rspInfo); err != nil {
logx.Error("failed to unmarshal python api rsp:", err)
return err
}
if rspInfo.Code != 200 {
logx.Error("python api 接口请求错误:", rspInfo.Msg)
return err
}
if len(rspInfo.Data) == 0 {
logx.Error("python api 接口没有数据:")
return err
}
//云渲染日志
title = "2-请求->接收python合成刀版图接口"
now = time.Now().Unix()
pyRequestTime := time.Now().UnixMilli() - pyapiBeginTime
err = allmodels.FsCloudRenderLog.Create(ctx, &gmodel.FsCloudRenderLog{
UserId: &parseInfo.RenderData.UserId,
Title: &title,
Time: &pyRequestTime,
Tag: &parseInfo.RenderId,
Ctime: &now,
})
if err != nil {
logx.Error(err)
}
incTime := int64(0)
mapCurlData := make(map[int64]int)
for k, v := range rspInfo.Data {
mapCurlData[v.Tid] = k
incTime += v.Costtime
}
//云渲染日志
title = "3-python合成刀版图"
now = time.Now().Unix()
postData := string(pyPostBytes)
pyRspStr := string(pyRspBytes)
err = allmodels.FsCloudRenderLog.Create(ctx, &gmodel.FsCloudRenderLog{
UserId: &parseInfo.RenderData.UserId,
PostUrl: &url,
PostData: &postData,
Result: &pyRspStr,
Title: &title,
Time: &incTime,
Tag: &parseInfo.RenderId,
Ctime: &now,
})
if err != nil {
logx.Error(err)
}
//获取渲染设置信息
//element, err := allmodels.FsProductTemplateElement
/*
$element = ProductTemplateElement::find()
->andFilterWhere(['in', 'product_template_id', $mids])
->asArray()
->all();
$element = array_column($element, null, 'product_template_id');
$elementTitles = array_column($element, 'title');
$result = [];
$time_pinjie_begin = $render->getMillisecond();
foreach ($templates as $key => $val) {
if(!isset($element[$val['model_id']]) || !isset($imageData[$val['id']])){
continue;
}
//数据拼装
$item = [];
$item['light'] = $element[$val['model_id']]['light'];
$item['refletion'] = $element[$val['model_id']]['refletion'] == '' ? -1 : (int)$element[$val['model_id']]['refletion'];
$item['scale'] = $element[$val['model_id']]['scale'];
$item['sku_id'] = $val['product_id'];
$item['tid'] = $element[$val['model_id']]['title'];
$item['rotation'] = $element[$val['model_id']]['rotation'];
$item['filePath'] = '';//todo 文件路径,针对千人千面
//组装data数据
$tempData = [];
//获取材质模式对应关系
$mode = $element[$val['model_id']]['mode'] ? json_decode($element[$val['model_id']]['mode'], true) : [];
// $base_img = (new ImageService())->base64EncodeImageNoHeader(\Yii::$app->params['baseurl'].$imageData[$val['id']]['imgurl']);
$base_img = \Yii::$app->params['h5Url'].'/storage'.$imageData[$val['id']]['imgurl'];
//判断是否包含base数据 即对应建模那边的model
if($element[$val['model_id']]['base']){
$tempData[] = [
'name' => 'model',
'data' => '0,'.$base_img.','.$element[$val['model_id']]['base'],
'type' => 'other',
'layer' => '0',
'is_update' => 1,
'mode' => $mode['model'],
];
}
if($element[$val['model_id']]['shadow']){
$tempData[] = [
'name' => 'shadow',
'data' => $element[$val['model_id']]['shadow'],
'type' => 'other',
'layer' => '0',
'is_update' => 0,
'mode' => $mode['shadow'],
];
}
if($element[$val['model_id']]['model_p']){
$tempData[] = [
'name' => 'model_P',
'data' => '0,'.$element[$val['model_id']]['model_p'],
'type' => 'other',
'layer' => '0',
'is_update' => 0,
'mode' => $mode['model_P'],
];
}
$item['data'] = $tempData;
$result[] = $item;
}
$log = new CloudRenderLog();
$log->title = '接收到python刀版图 -> 3-组装MQ渲染任务队列';
$log->time = $render->getMillisecond() - $time_pinjie_begin;
$log->user_id = $user_id;
$log->post_data = '';
$log->post_url = '';
$log->result = $res;
$log->tag = $inputData['id'];
$log->ctime = time();
$log->save(false);
}
$sendData = [
'id' => $inputData['id'],
'order_id' => 0,
'user_id' => \Yii::$app->user->id,
'sku_ids' => $inputData['sku_ids'],
'tids' => $elementTitles,
'data' => $result,
'is_thousand_face' => 0,
'folder' => '',//todo 千人千面需要使用
];
return $sendData;*/
return nil
}

View File

@ -1,9 +1,9 @@
Name: render
Host: localhost
Port: 8888
ReplicaId: 55
Host: 0.0.0.0
Port: 9919
SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest
Auth:
AccessSecret: fusen2023
AccessExpire: 2592000
RefreshAfter: 1592000
RefreshAfter: 1592000
SourceRabbitMq: amqp://rabbit001:rabbit001129@110.41.19.98:5672

View File

@ -8,7 +8,8 @@ import (
type Config struct {
rest.RestConf
SourceMysql string
Auth types.Auth
ReplicaId uint64
SourceMysql string
Auth types.Auth
ReplicaId uint64
SourceRabbitMq string
}

View File

@ -11,22 +11,22 @@ import (
"fusenapi/server/render/internal/types"
)
func ToUnityHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func GetFaceSliceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RequestToUnity
var req types.Request
userinfo, err := basic.RequestParse(w, r, svcCtx.SharedState, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewToUnityLogic(r.Context(), svcCtx)
l := logic.NewGetFaceSliceLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.ToUnity(&req, userinfo)
resp := l.GetFaceSlice(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)

View File

@ -11,22 +11,22 @@ import (
"fusenapi/server/render/internal/types"
)
func ReadImagesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func RenderNotifyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RequestReadImages
var req types.RenderNotifyReq
userinfo, err := basic.RequestParse(w, r, svcCtx.SharedState, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewReadImagesLogic(r.Context(), svcCtx)
l := logic.NewRenderNotifyLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.ReadImages(&req, userinfo)
resp := l.RenderNotify(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)

View File

@ -13,14 +13,14 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/api/render/to-unity",
Handler: ToUnityHandler(serverCtx),
Method: http.MethodPost,
Path: "/api/render/render_notify",
Handler: RenderNotifyHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/api/render/read-images",
Handler: ReadImagesHandler(serverCtx),
Method: http.MethodPost,
Path: "/api/render/get_face_slice",
Handler: GetFaceSliceHandler(serverCtx),
},
},
)

View File

@ -0,0 +1,69 @@
package logic
import (
"encoding/json"
"errors"
"fusenapi/constants"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"gorm.io/gorm"
"strings"
"context"
"fusenapi/server/render/internal/svc"
"fusenapi/server/render/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetFaceSliceLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetFaceSliceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFaceSliceLogic {
return &GetFaceSliceLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *GetFaceSliceLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *GetFaceSliceLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *GetFaceSliceLogic) GetFaceSlice(req *types.Request, userinfo *auth.UserInfo) (resp *basic.Response) {
if !userinfo.IsUser() && !userinfo.IsGuest() {
return resp.SetStatusWithMessage(basic.CodeUnAuth, "please login or access cookie")
}
//获取用户素材信息
materialInfo, err := l.svcCtx.AllModels.FsUserMaterial.FindLatestOne(l.ctx, userinfo.UserId, userinfo.GuestId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "user material info is not exists")
}
return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get user material info")
}
if materialInfo.Metadata == nil || *materialInfo.Metadata == "" {
return resp.SetStatusWithMessage(basic.CodeServiceErr, "user material info`Metadata is empty")
}
var info map[string]interface{}
if err = json.Unmarshal([]byte(*materialInfo.Metadata), &info); err != nil {
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeJsonErr, "invalid json format of metadata")
}
str := strings.ReplaceAll(constants.RENDER_FACE_SLICE_TEMPLATE_JSON, "{{MainColorFill}}", info["main_color_fill"].(string))
str = strings.ReplaceAll(str, "{{SecondaryColorFill}}", info["secondary_color_fill"].(string))
str = strings.ReplaceAll(str, "{{LogoMaterial}}", info["logo_material"].(string))
var rspInfo interface{}
_ = json.Unmarshal([]byte(str), &rspInfo)
return resp.SetStatusWithMessage(basic.CodeOK, "success", rspInfo)
}

View File

@ -1,42 +0,0 @@
package logic
import (
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"context"
"fusenapi/server/render/internal/svc"
"fusenapi/server/render/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ReadImagesLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewReadImagesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReadImagesLogic {
return &ReadImagesLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *ReadImagesLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向
// func (l *ReadImagesLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// }
func (l *ReadImagesLogic) ReadImages(req *types.RequestReadImages, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
return resp.SetStatus(basic.CodeOK)
}

View File

@ -1,14 +1,15 @@
package logic
import (
"fusenapi/constants"
"fusenapi/utils/basic"
"time"
"context"
"encoding/json"
"fusenapi/constants"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"fusenapi/utils/websocket_data"
"fusenapi/server/websocket/internal/svc"
"fusenapi/server/websocket/internal/types"
"fusenapi/server/render/internal/svc"
"fusenapi/server/render/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
@ -36,50 +37,36 @@ func NewRenderNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Rend
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *RenderNotifyLogic) RenderNotify(req *types.RenderNotifyReq) (resp *basic.Response) {
if time.Now().Unix()-120 > req.Time /*|| req.Time > time.Now().Unix() */ {
func (l *RenderNotifyLogic) RenderNotify(req *types.RenderNotifyReq, userinfo *auth.UserInfo) (resp *basic.Response) {
/*if time.Now().Unix()-120 > req.Time || req.Time > time.Now().Unix() {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param time")
}*/
if req.Info.TaskId == "" {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param task_id")
}
if req.Info.Image == "" {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param image")
}
/* if req.Sign == "" {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param sign")
}*/
//验证签名 sha256
/*notifyByte, _ := json.Marshal(req.Info)
h := sha256.New()
h.Write([]byte(fmt.Sprintf(constants.RENDER_NOTIFY_SIGN_KEY, string(notifyByte), req.Time)))
signHex := h.Sum(nil)
sign := hex.EncodeToString(signHex)
//fmt.Println(sign)
if req.Sign != sign {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid sign")
}*/
//遍历websocket链接把数据传进去
mapConnPool.Range(func(key, value any) bool {
//断言连接
ws, ok := value.(wsConnectItem)
if !ok {
return true
}
//关闭标识
if ws.isClose {
return true
}
renderKey := ws.getRenderImageMapKey(req.Info.ProductId, req.Info.TemplateTagId, req.Info.LogoId, req.Info.AlgorithmVersion)
//查询有无该渲染任务
_, ok = ws.renderProperty.renderImageTask[renderKey]
if !ok {
return true
}
b := ws.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, types.RenderImageRspMsg{
ProductId: req.Info.ProductId,
TemplateTagId: req.Info.TemplateTagId,
Image: req.Info.Image,
})
//删除对应的需要渲染的图片map
ws.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{
Option: 0, //0删除 1添加
Key: renderKey,
}
//发送数据到out chan
ws.sendToOutChan(b)
return true
})
data := websocket_data.RenderImageNotify{
TaskId: req.Info.TaskId,
Image: req.Info.Image,
}
d, _ := json.Marshal(data)
if err := l.svcCtx.RabbitMq.SendMsg(constants.RABBIT_MQ_RENDER_RESULT_DATA, d); err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeServiceErr, "failed to send data")
}
return resp.SetStatus(basic.CodeOK)
}

View File

@ -1,42 +0,0 @@
package logic
import (
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"context"
"fusenapi/server/render/internal/svc"
"fusenapi/server/render/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ToUnityLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewToUnityLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToUnityLogic {
return &ToUnityLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *ToUnityLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向
// func (l *ToUnityLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// }
func (l *ToUnityLogic) ToUnity(req *types.RequestToUnity, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
return resp.SetStatus(basic.CodeOK)
}

View File

@ -21,6 +21,7 @@ type ServiceContext struct {
MysqlConn *gorm.DB
AllModels *gmodel.AllModelsGen
RabbitMq *initalize.RabbitMqHandle
}
func NewServiceContext(c config.Config) *ServiceContext {
@ -32,6 +33,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
MysqlConn: conn,
SharedState: StateServer,
AllModels: gmodel.NewAllModels(initalize.InitMysql(c.SourceMysql)),
RabbitMq: initalize.InitRabbitMq(c.SourceRabbitMq, nil),
}
}

View File

@ -11,6 +11,17 @@ type RequestToUnity struct {
type RequestReadImages struct {
}
type RenderNotifyReq struct {
Sign string `json:"sign"`
Time int64 `json:"time"`
Info NotifyInfo `json:"info"`
}
type NotifyInfo struct {
TaskId string `json:"task_id"` //任务id
Image string `json:"image"`
}
type Request struct {
}

View File

@ -1,8 +1,11 @@
package main
import (
"context"
"flag"
"fmt"
"fusenapi/constants"
"fusenapi/server/render/consumer"
"net/http"
"time"
@ -29,8 +32,13 @@ func main() {
defer server.Stop()
ctx := svc.NewServiceContext(c)
//消费渲染前组装数据队列
ctx1 := context.Background()
ctx2, cancel := context.WithCancel(ctx1)
ctx2 = context.WithValue(ctx2, "allmodels", ctx.AllModels)
defer cancel()
go ctx.RabbitMq.Consume(ctx2, constants.RABBIT_MQ_ASSEMBLE_RENDER_DATA, &consumer.MqConsumerRenderAssemble{})
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-shopping-cart-confirmation-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-shopping-cart-confirmation-srv"]

6
server/upload/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-upload-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-upload-srv"]

View File

@ -13,4 +13,7 @@ AWS:
Credentials:
AccessKeyID: AKIAZB2JKUXDPNRP4YT2
Secret: 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
Token:
Token:
BLMService:
ImageProcess:
Url: "http://110.41.19.98:8868/removebg"

View File

@ -21,4 +21,9 @@ type Config struct {
}
}
}
BLMService struct {
ImageProcess struct {
Url string
}
}
}

View File

@ -32,6 +32,31 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/api/upload/qrcode",
Handler: UploadQrcodeHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/upload/upload-files-backend",
Handler: UploadFilesBackendHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/upload/upload-files-frontend",
Handler: UploadFilesFrontendHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/upload/upload-callback",
Handler: UploadCallbackHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/upload/up-logo",
Handler: UploadLogoHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/upload/upload-file-base",
Handler: UploadFileBaseHandler(serverCtx),
},
},
)
}

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/upload/internal/logic"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
)
func UploadCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadCallbackReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUploadCallbackLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UploadCallback(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/upload/internal/logic"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
)
func UploadFileBaseHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadFileBaseReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUploadFileBaseLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UploadFileBase(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/upload/internal/logic"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
)
func UploadFilesBackendHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadFilesReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUploadFilesBackendLogic(r, svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UploadFilesBackend(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/upload/internal/logic"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
)
func UploadFilesFrontendHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadFilesReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUploadFilesFrontendLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UploadFilesFrontend(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,35 @@
package handler
import (
"net/http"
"reflect"
"fusenapi/utils/basic"
"fusenapi/server/upload/internal/logic"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
)
func UploadLogoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadLogoReq
userinfo, err := basic.RequestParse(w, r, svcCtx, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewUploadLogoLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.UploadLogo(&req, userinfo)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -0,0 +1,98 @@
package logic
import (
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"context"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/gorm"
)
type UploadCallbackLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadCallbackLogic {
return &UploadCallbackLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UploadCallbackLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UploadCallbackLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UploadCallbackLogic) UploadCallback(req *types.UploadCallbackReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
var userId int64
var guestId int64
// 检查用户是否是游客
if userinfo.IsGuest() {
// 如果是使用游客ID和游客键名格式
guestId = userinfo.GuestId
} else {
// 否则使用用户ID和用户键名格式
userId = userinfo.UserId
}
// 定义存储桶名称
var bucketName *string
// 根据类别选择存储桶
switch req.UploadBucket {
case 2:
bucketName = basic.TempfileBucketName
default:
bucketName = basic.StorageBucketName
}
resourceModel := gmodel.NewFsResourceModel(l.svcCtx.MysqlConn)
err := resourceModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) (err error) {
resourceModelTS := gmodel.NewFsResourceModel(l.svcCtx.MysqlConn)
resourceInfo, err := resourceModelTS.FindOneById(ctx, req.ResourceId)
if err != nil {
return err
}
var version string = "0.0.1"
var fsResource = &gmodel.FsResource{}
fsResource.UserId = &userId
fsResource.GuestId = &guestId
fsResource.ResourceId = req.ResourceId
fsResource.ResourceType = &req.ResourceType
fsResource.ResourceUrl = &req.ResourceUrl
fsResource.Metadata = &req.Metadata
fsResource.ApiType = &req.ApiType
fsResource.BucketName = bucketName
fsResource.Version = &version
if resourceInfo.ResourceId == "" {
_, err = resourceModelTS.Create(ctx, fsResource)
} else {
_, err = resourceModelTS.Update(ctx, fsResource)
}
return err
})
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,UploadCallback failed")
}
return resp.SetStatus(basic.CodeOK)
}

View File

@ -0,0 +1,154 @@
package logic
import (
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"fusenapi/utils/file"
"fusenapi/utils/hash"
"time"
"context"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/zeromicro/go-zero/core/logx"
)
type UploadFileBaseLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadFileBaseLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadFileBaseLogic {
return &UploadFileBaseLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UploadFileBaseLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UploadFileBaseLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UploadFileBaseLogic) UploadFileBase(req *types.UploadFileBaseReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
// 定义用户ID和S3键名格式
var userId int64
var guestId int64
// 检查用户是否是游客
if userinfo.IsGuest() {
// 如果是使用游客ID和游客键名格式
guestId = userinfo.GuestId
} else {
// 否则使用用户ID和用户键名格式
userId = userinfo.UserId
}
if guestId == 0 {
guestId = req.GuestId
}
if userId == 0 {
userId = req.UserId
}
// 定义存储桶名称
var bucketName *string
var apiType int64 = req.ApiType
// 根据类别选择存储桶
switch req.UploadBucket {
case 2:
bucketName = basic.TempfileBucketName
default:
bucketName = basic.StorageBucketName
}
// 设置AWS会话的区域
l.svcCtx.AwsSession.Config.Region = aws.String("us-west-1")
// 创建新的S3服务实例
svc := s3.New(l.svcCtx.AwsSession)
// 定义S3请求和当前时间
var s3req *request.Request
var resourceId string = hash.JsonHashKey(req.FileKey)
var uploadUrl = UploadUrl{}
resourceModel := gmodel.NewFsResourceModel(l.svcCtx.MysqlConn)
resourceInfo, err := resourceModel.FindOneById(l.ctx, resourceId)
if err == nil && resourceInfo.ResourceId != "" {
uploadUrl.Status = 1
uploadUrl.ResourceId = resourceId
uploadUrl.ResourceUrl = *resourceInfo.ResourceUrl
} else {
dist, contentType, err := file.FileBase64ToByte(req.FileData)
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,base64tobyte error")
}
// 创建S3对象存储请求
s3req, _ = svc.PutObjectRequest(
&s3.PutObjectInput{
Bucket: bucketName,
Key: &resourceId,
},
)
// 设置请求体为文件数据
s3req.SetBufferBody(dist)
// 发送请求
err = s3req.Send()
// 检查是否有错误
if err != nil {
logx.Error(err)
uploadUrl.Status = 0
} else {
var url = s3req.HTTPRequest.URL.String()
// 打印请求URL
logx.Info(url)
uploadUrl.Status = 1
uploadUrl.ResourceId = resourceId
uploadUrl.ResourceUrl = url
var version string = "0.0.1"
var nowTime = time.Now()
_, err = resourceModel.Create(l.ctx, &gmodel.FsResource{
ResourceId: resourceId,
UserId: &userId,
GuestId: &guestId,
ResourceType: &contentType,
ResourceUrl: &url,
Version: &version,
UploadedAt: &nowTime,
Metadata: &req.Metadata,
ApiType: &apiType,
BucketName: bucketName,
})
if err != nil {
logx.Error(err)
}
}
}
// 返回成功的响应和上传URL
return resp.SetStatus(basic.CodeOK, map[string]interface{}{
"upload_data": uploadUrl,
})
}

View File

@ -0,0 +1,259 @@
package logic
import (
"encoding/json"
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"fusenapi/utils/hash"
"io"
"net/http"
"time"
"context"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type UploadFilesBackendLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
r *http.Request
}
func NewUploadFilesBackendLogic(r *http.Request, svcCtx *svc.ServiceContext) *UploadFilesBackendLogic {
return &UploadFilesBackendLogic{
Logger: logx.WithContext(r.Context()),
ctx: r.Context(),
svcCtx: svcCtx,
r: r,
}
}
// 处理进入前逻辑w,r
// func (l *UploadFilesBackendLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UploadFilesBackendLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UploadFilesBackendLogic) UploadFilesBackend(req *types.UploadFilesReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
if userinfo.IsOnlooker() {
// 如果是,返回未授权的错误码
return resp.SetStatus(basic.CodeUnAuth)
}
// 定义用户ID和S3键名格式
var userId int64
var guestId int64
// 检查用户是否是游客
if userinfo.IsGuest() {
// 如果是使用游客ID和游客键名格式
guestId = userinfo.GuestId
} else {
// 否则使用用户ID和用户键名格式
userId = userinfo.UserId
}
var uploadInfoList []UploadInfo
err := json.Unmarshal([]byte(req.UploadInfo), &uploadInfoList)
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,params Unmarshal failed")
}
var fileLen = len(uploadInfoList)
if fileLen == 0 {
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,no files")
}
if req.ApiType == 1 && fileLen > 100 {
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err, files count is beyond the maximum")
}
// 定义存储桶名称
var bucketName *string
// 根据类别选择存储桶
switch req.UploadBucket {
case 2:
bucketName = basic.TempfileBucketName
default:
bucketName = basic.StorageBucketName
}
//设置内存大小
l.r.ParseMultipartForm(32 << 20)
//获取上传的文件组
files := l.r.MultipartForm.File["file"]
// 设置AWS会话的区域
l.svcCtx.AwsSession.Config.Region = aws.String("us-west-1")
// 创建新的S3服务实例
svc := s3.New(l.svcCtx.AwsSession)
// 定义S3请求和当前时间
var s3req *request.Request
resourceModel := gmodel.NewFsResourceModel(l.svcCtx.MysqlConn)
result, err := mr.MapReduce(func(source chan<- interface{}) {
for i, info := range uploadInfoList {
fileType := files[i].Header.Get("Content-Type")
// 打开文件
file, err := files[i].Open()
if err != nil {
logx.Error(err)
}
defer file.Close()
// 读取数据流
ioData, err := io.ReadAll(file)
if err != nil {
logx.Error(err)
}
// 一系列业务逻辑....验证类型,文件大小
var hashKey string = hash.JsonHashKey(info.FileKeys)
source <- UploadData{
FileKey: info.FileKeys,
FileType: fileType,
Metadata: info.Metadata,
FileData: ioData,
ApiType: req.ApiType,
Bucket: bucketName,
HashKey: hashKey,
}
}
}, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) {
uploadDataInfo := item.(UploadData)
var uploadUrl = UploadUrl{}
uploadUrl.Key = uploadDataInfo.FileKey
uploadUrl.ApiType = uploadDataInfo.ApiType
uploadUrl.ResourceType = uploadDataInfo.FileType
var resourceId string = uploadDataInfo.HashKey
// 查询数据库
resourceInfo, err := resourceModel.FindOneById(l.ctx, resourceId)
if err == nil && resourceInfo.ResourceId != "" {
uploadUrl.Status = 1
uploadUrl.ResourceId = resourceId
uploadUrl.ResourceUrl = *resourceInfo.ResourceUrl
} else {
// 创建S3对象存储请求
s3req, _ = svc.PutObjectRequest(
&s3.PutObjectInput{
Bucket: uploadDataInfo.Bucket,
Key: &uploadDataInfo.HashKey,
},
)
// 设置请求体为文件数据
s3req.SetBufferBody(uploadDataInfo.FileData)
// 发送请求
err = s3req.Send()
// 检查是否有错误
if err != nil {
logx.Error(err)
uploadUrl.Status = 0
} else {
contentType := http.DetectContentType(uploadDataInfo.FileData)
var url = s3req.HTTPRequest.URL.String()
// 打印请求URL
logx.Info(url)
uploadUrl.Status = 1
uploadUrl.ResourceId = resourceId
uploadUrl.ResourceUrl = url
var version string = "0.0.1"
var nowTime = time.Now()
_, err = resourceModel.Create(l.ctx, &gmodel.FsResource{
ResourceId: resourceId,
UserId: &userId,
GuestId: &guestId,
ResourceType: &contentType,
ResourceUrl: &url,
Version: &version,
UploadedAt: &nowTime,
Metadata: &uploadDataInfo.Metadata,
ApiType: &uploadDataInfo.ApiType,
BucketName: bucketName,
})
if err != nil {
logx.Error(err)
}
}
}
// Notice 这个必须加!
writer.Write(uploadUrl)
}, func(pipe <-chan interface{}, writer mr.Writer[interface{}], cancel func(error)) {
var uploadUrlList = make(map[string][]*UploadUrl)
var uploadUrlListFail []*UploadUrl
var uploadUrlListSuccess []*UploadUrl
for p := range pipe {
var uploadUrl = p.(UploadUrl)
if uploadUrl.Status == 1 {
uploadUrlListSuccess = append(uploadUrlListSuccess, &uploadUrl)
} else {
uploadUrlListFail = append(uploadUrlListFail, &uploadUrl)
}
}
// Notice 这个必须加!
uploadUrlList["success"] = uploadUrlListSuccess
uploadUrlList["fail"] = uploadUrlListFail
writer.Write(uploadUrlList)
})
if err != nil {
logx.Error(err)
}
// 返回成功的响应和上传URL
return resp.SetStatus(basic.CodeOK, map[string]interface{}{
"upload_data": result,
})
}
type UploadInfo struct {
FileSize int64 `json:"file_size"` // 上传文件大小
FileKeys string `json:"file_keys"` // 上传文件唯一标识
FileData *string `json:"file_data"` // 上传文件Base64
Metadata string `json:"meta_data"` // 上传文件额外信息
}
type UploadData struct {
ApiType int64 `json:"api_type"`
FileSize int64 `json:"file_size"`
FileType string `json:"file_type"`
FileKey string `json:"file_key"`
Metadata string `json:"metadata"`
Bucket *string `json:"bucket"`
HashKey string `json:"hash_key"`
FileData []byte `fsfile:"data"`
}
type UploadUrl struct {
Key string `json:"key"`
Status int64 `json:"status"`
ApiType int64 `json:"api_type"`
ResourceId string `json:"resource_id"`
ResourceType string `json:"resource_type"`
ResourceUrl string `json:"resource_url"`
}

View File

@ -0,0 +1,149 @@
package logic
import (
"encoding/json"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"fusenapi/utils/hash"
"time"
"context"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type UploadFilesFrontendLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadFilesFrontendLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadFilesFrontendLogic {
return &UploadFilesFrontendLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UploadFilesFrontendLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UploadFilesFrontendLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UploadFilesFrontendLogic) UploadFilesFrontend(req *types.UploadFilesReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
var uploadInfoList []UploadInfo
err := json.Unmarshal([]byte(req.UploadInfo), &uploadInfoList)
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,params Unmarshal failed")
}
var fileLen = len(uploadInfoList)
if fileLen == 0 {
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err,no files")
}
if req.ApiType == 1 && fileLen > 100 {
return resp.SetStatus(basic.CodeFileUploadErr, "file upload err, files count is beyond the maximum")
}
// 定义存储桶名称
var bucketName *string
// 根据类别选择存储桶
switch req.UploadBucket {
case 2:
bucketName = basic.TempfileBucketName
default:
bucketName = basic.StorageBucketName
}
// 设置AWS会话的区域
l.svcCtx.AwsSession.Config.Region = aws.String("us-west-1")
// 创建新的S3服务实例
svc := s3.New(l.svcCtx.AwsSession)
result, err := mr.MapReduce(func(source chan<- interface{}) {
for _, info := range uploadInfoList {
if info.FileSize <= 1024*1024*500 {
// 一系列业务逻辑....验证类型,文件大小
var hashKey string = hash.JsonHashKey(info.FileKeys)
source <- UploadData{
FileKey: info.FileKeys,
FileSize: info.FileSize,
Bucket: bucketName,
HashKey: hashKey,
}
}
}
}, func(item interface{}, writer mr.Writer[interface{}], cancel func(error)) {
uploadDataInfo := item.(UploadData)
var uploadUrl = UploadUrl{}
uploadUrl.Key = uploadDataInfo.FileKey
uploadUrl.ApiType = uploadDataInfo.ApiType
uploadUrl.ResourceType = uploadDataInfo.FileType
s3req, _ := svc.PutObjectRequest(
&s3.PutObjectInput{
Bucket: uploadDataInfo.Bucket,
Key: &uploadDataInfo.HashKey,
ContentLength: aws.Int64(uploadDataInfo.FileSize),
},
)
url, err := s3req.Presign(time.Minute * 5)
if err != nil {
logx.Error(err)
uploadUrl.Status = 0
} else {
// 打印请求URL
logx.Info(url)
uploadUrl.Status = 1
uploadUrl.ResourceUrl = url
uploadUrl.ResourceId = uploadDataInfo.HashKey
}
// Notice 这个必须加!
writer.Write(uploadUrl)
}, func(pipe <-chan interface{}, writer mr.Writer[interface{}], cancel func(error)) {
var uploadUrlList = make(map[string][]*UploadUrl)
var uploadUrlListFail []*UploadUrl
var uploadUrlListSuccess []*UploadUrl
for p := range pipe {
var uploadUrl = p.(UploadUrl)
if uploadUrl.Status == 1 {
uploadUrlListSuccess = append(uploadUrlListSuccess, &uploadUrl)
} else {
uploadUrlListFail = append(uploadUrlListFail, &uploadUrl)
}
}
// Notice 这个必须加!
uploadUrlList["success"] = uploadUrlListSuccess
uploadUrlList["fail"] = uploadUrlListFail
writer.Write(uploadUrlList)
})
if err != nil {
logx.Error(err)
}
// 返回成功的响应和上传URL
return resp.SetStatus(basic.CodeOK, map[string]interface{}{
"upload_urls": result,
})
}

View File

@ -0,0 +1,144 @@
package logic
import (
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"time"
"context"
"fusenapi/server/upload/internal/svc"
"fusenapi/server/upload/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type UploadLogoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadLogoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadLogoLogic {
return &UploadLogoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *UploadLogoLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *UploadLogoLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *UploadLogoLogic) UploadLogo(req *types.UploadLogoReq, userinfo *auth.UserInfo) (resp *basic.Response) {
// 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data)
// userinfo 传入值时, 一定不为null
if userinfo.IsOnlooker() {
// 如果是,返回未授权的错误码
return resp.SetStatus(basic.CodeUnAuth)
}
var userId int64
var guestId int64
// 检查用户是否是游客
if userinfo.IsGuest() {
// 如果是使用游客ID和游客键名格式
guestId = userinfo.GuestId
} else {
// 否则使用用户ID和用户键名格式
userId = userinfo.UserId
}
var logoWidth int64
var logoHeight int64
// 查看sku是否存在
if req.SkuId > 0 {
// 查询出产品模板信息
productTemplateV2Model := gmodel.NewFsProductTemplateV2Model(l.svcCtx.MysqlConn)
productTemplateV2Info, err := productTemplateV2Model.FindOne(l.ctx, req.SkuId)
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadLogoErr, "logo upload err,no product template")
}
logoWidth = *productTemplateV2Info.LogoWidth
logoHeight = *productTemplateV2Info.LogoHeight
}
// 设置默认宽高
if logoWidth == 0 || logoHeight == 0 {
logoWidth = 300
logoHeight = 200
}
var resultStr string
var err error
// apiUrl := l.svcCtx.Config.BLMService.ImageProcess.Url
// var onlyScale = true
// data := url.Values{}
// data.Set("imgurl", req.ResourceUrl)
// data.Set("layerwidth", strconv.Itoa(int(logoWidth)))
// data.Set("layerheight", strconv.Itoa(int(logoHeight)))
// data.Set("is_remove_bg", strconv.Itoa(int(req.IsRemoveBg)))
// data.Set("proportion", strconv.Itoa(int(req.Proportion)))
// data.Set("only_scale", fmt.Sprintf("%v", onlyScale))
// u, err := url.ParseRequestURI(apiUrl)
// if err != nil {
// logx.Error(err)
// return resp.SetStatus(basic.CodeFileUploadLogoErr, "service fail")
// }
// u.RawQuery = data.Encode() // URL encode
// fmt.Println(u.String())
// result, err := http.Get(u.String())
// if err != nil {
// logx.Error(err)
// return resp.SetStatus(basic.CodeFileUploadLogoErr, "service fail")
// }
// defer result.Body.Close()
// b, err := io.ReadAll(result.Body)
// if err != nil {
// logx.Error(err)
// return resp.SetStatus(basic.CodeFileUploadLogoErr, "service fail")
// }
// resultStr = string(b)
// // 上传图片
// var reqs types.UploadFileBaseReq
// reqs.ApiType = 2
// reqs.UploadBucket = 2
// reqs.Metadata = ""
// reqs.FileData = ""
// reqs.FileKey = ""
// // 创建一个业务逻辑层实例
// resUpload := NewUploadFileBaseLogic(l.ctx, l.svcCtx).UploadFileBase(&reqs, userinfo)
var module = "logo"
var nowTime = time.Now().Unix()
// 新增记录
userMaterialModel := gmodel.NewFsUserMaterialModel(l.svcCtx.MysqlConn)
_, err = userMaterialModel.CreateOrUpdate(l.ctx, &gmodel.FsUserMaterial{
Module: &module,
UserId: &userId,
GuestId: &guestId,
ResourceId: &req.ResourceId,
ResourceUrl: &req.ResourceUrl,
Metadata: &resultStr,
CreateAt: &nowTime,
})
if err != nil {
logx.Error(err)
return resp.SetStatus(basic.CodeFileUploadLogoErr, "service fail")
}
return resp.SetStatus(basic.CodeOK)
}

View File

@ -5,6 +5,45 @@ import (
"fusenapi/utils/basic"
)
type UploadFileBaseReq struct {
ApiType int64 `form:"api_type,options=[1,2],default=1"` // 调用类型1=对外2=对内
FileKey string `form:"file_key"` // 上传唯一标识信息
FileData string `form:"file_data"` // 上传文件额外信息
Metadata string `form:"meta_data,optional"` // 上传文件额外信息
UserId int64 `form:"user_id,optional"` // 上传文件额外信息
GuestId int64 `form:"guest_id,optional"` // 上传文件额外信息
UploadBucket int64 `form:"upload_bucket,options=[1,2],default=1"` // 上传桶名:1=缓存,2=持久
}
type UploadLogoReq struct {
ResourceId string `form:"resource_id"` // 资源ID
ResourceUrl string `form:"resource_url"` // 资源URL
IsRemoveBg int64 `form:"is_remove_bg"` // 是否要去掉背景
Proportion int64 `form:"proportion,default=60"` // 贴图在模型面板上的比例
SkuId int64 `form:"sku_id,default=0"` // 模板ID
}
type UploadInfo struct {
FileSize int64 `form:"file_size,optional"` // 上传唯一标识信息
FileKeys string `form:"file_keys,optional"` // 上传唯一标识信息
Metadata string `form:"meta_data,optional"` // 上传文件额外信息
}
type UploadFilesReq struct {
ApiType int64 `form:"api_type,options=[1,2],default=1"` // 调用类型1=对外2=对内
UploadBucket int64 `form:"upload_bucket,options=[1,2],default=1"` // 上传桶名:1=缓存,2=持久
UploadInfo string `form:"upload_info"` // 上传信息 json
}
type UploadCallbackReq struct {
UploadBucket int64 `form:"upload_bucket,options=[1,2],default=1"` // 上传桶名:1=缓存,2=持久
ResourceId string `form:"resource_id"` // 资源ID
ResourceType string `form:"resource_type"` // 资源类型
ResourceUrl string `form:"resource_url"` // 资源URL
Metadata string `form:"metadata,optional"` // 元数据,json格式,存储图像分率
ApiType int64 `form:"api_type,options=[1,2],default=1"` // 调用类型1=对外2=对内
}
type RequestUpFile struct {
UpFile string `form:"upfile"`
IsCut string `form:"is_cut"` // 是否裁剪

6
server/webset/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-webset-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-webset-srv"]

6
server/websocket/Dockerfile Executable file
View File

@ -0,0 +1,6 @@
FROM alpine
WORKDIR /www/fusenapi/
COPY ./bin/api-websocket-srv /www/fusenapi/
COPY ./etc /www/fusenapi/etc
CMD ["/www/fusenapi/api-websocket-srv"]

View File

@ -2,6 +2,7 @@ Name: websocket
Host: 0.0.0.0
Port: 9914
ReplicaId: 75
Timeout: 15000 #服务超时时间
SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest
Auth:
AccessSecret: fusen2023

View File

@ -1,31 +0,0 @@
package handler
import (
"fusenapi/server/websocket/internal/logic"
"fusenapi/server/websocket/internal/svc"
"fusenapi/server/websocket/internal/types"
"fusenapi/utils/basic"
"net/http"
"reflect"
)
func RenderNotifyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RenderNotifyReq
_, err := basic.RequestParse(w, r, svcCtx.SharedState, &req)
if err != nil {
return
}
// 创建一个业务逻辑层实例
l := logic.NewRenderNotifyLogic(r.Context(), svcCtx)
rl := reflect.ValueOf(l)
basic.BeforeLogic(w, r, rl)
resp := l.RenderNotify(&req)
if !basic.AfterLogic(w, r, rl, resp) {
basic.NormalAfterLogic(w, r, resp)
}
}
}

View File

@ -17,16 +17,6 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/api/websocket/data_transfer",
Handler: DataTransferHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/websocket/render_notify",
Handler: RenderNotifyHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/websocket/third_party_login_notify",
Handler: ThirdPartyLoginNotifyHandler(serverCtx),
},
},
)
}

View File

@ -3,12 +3,11 @@ package logic
import (
"bytes"
"encoding/json"
"fmt"
"fusenapi/constants"
"fusenapi/initalize"
"fusenapi/server/websocket/internal/types"
"fusenapi/utils/auth"
"fusenapi/utils/id_generator"
"fusenapi/utils/websocket_data"
"github.com/gorilla/websocket"
"net/http"
"sync"
@ -66,12 +65,13 @@ var (
type wsConnectItem struct {
conn *websocket.Conn //websocket的连接
rabbitMq *initalize.RabbitMqHandle
closeChan chan struct{} //ws连接关闭chan
isClose bool //是否已经关闭
uniqueId uint64 //ws连接唯一标识
inChan chan []byte //接受消息缓冲通道
outChan chan []byte //发送回客户端的消息
mutex sync.Mutex //互斥锁
closeChan chan struct{} //ws连接关闭chan
isClose bool //是否已经关闭
uniqueId uint64 //ws连接唯一标识
inChan chan []byte //接受消息缓冲通道
outChan chan []byte //发送回客户端的消息
mutex sync.Mutex //互斥锁
userId int64
renderProperty renderProperty //扩展云渲染属性
}
@ -84,10 +84,14 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp
}
defer conn.Close()
//鉴权不成功10秒后断开
/*isAuth, _ := l.checkAuth(svcCtx, r)
var (
userInfo *auth.UserInfo
isAuth bool
)
isAuth, userInfo = l.checkAuth(svcCtx, r)
if !isAuth {
time.Sleep(time.Second) //兼容下火狐
rsp := types.DataTransferData{
rsp := websocket_data.DataTransferData{
T: constants.WEBSOCKET_UNAUTH,
D: nil,
}
@ -97,7 +101,7 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp
//发送关闭信息
_ = conn.WriteMessage(websocket.CloseMessage, nil)
return
}*/
}
//生成连接唯一标识
uniqueId := websocketIdGenerator.Get()
ws := wsConnectItem{
@ -108,10 +112,15 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp
inChan: make(chan []byte, 1000),
outChan: make(chan []byte, 1000),
renderProperty: renderProperty{
renderImageTask: make(map[string]struct{}),
renderImageTask: make(map[string]string),
renderImageTaskCtlChan: make(chan renderImageControlChanItem, 100),
},
}
if userInfo.UserId > 0 {
ws.userId = userInfo.UserId
} else {
ws.userId = userInfo.GuestId
}
//保存连接
mapConnPool.Store(uniqueId, ws)
defer ws.close()
@ -146,10 +155,13 @@ func (l *DataTransferLogic) checkAuth(svcCtx *svc.ServiceContext, r *http.Reques
if err != nil {
return false, nil
}
} else {
return false, nil
//不是登录用户也不是游客
if !userInfo.IsUser() && !userInfo.IsGuest() {
return false, nil
}
return true, userInfo
}
return true, userInfo
return false, nil
}
// 心跳
@ -247,14 +259,9 @@ func (w *wsConnectItem) sendToOutChan(data []byte) {
}
}
// 获取需要渲染图片的map key
func (w *wsConnectItem) getRenderImageMapKey(productId, templateTagId, logoId int64, algorithmVersion string) string {
return fmt.Sprintf("%d-%d-%d-%s", productId, templateTagId, logoId, algorithmVersion)
}
// 格式化返回数据
func (w *wsConnectItem) respondDataFormat(msgType string, data interface{}) []byte {
d := types.DataTransferData{
d := websocket_data.DataTransferData{
T: msgType,
D: data,
}
@ -264,7 +271,7 @@ func (w *wsConnectItem) respondDataFormat(msgType string, data interface{}) []by
// 处理接受到的数据
func (w *wsConnectItem) dealwithReciveData(data []byte) {
var parseInfo types.DataTransferData
var parseInfo websocket_data.DataTransferData
if err := json.Unmarshal(data, &parseInfo); err != nil {
logx.Error("invalid format of websocket message")
w.outChan <- w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket message:"+string(data))
@ -275,7 +282,7 @@ func (w *wsConnectItem) dealwithReciveData(data []byte) {
switch parseInfo.T {
//图片渲染
case constants.WEBSOCKET_RENDER_IMAGE:
w.SendToCloudRender(d)
w.renderImage(d)
default:
}

View File

@ -0,0 +1,53 @@
package logic
import (
"context"
"encoding/json"
"fusenapi/constants"
"fusenapi/utils/websocket_data"
"github.com/zeromicro/go-zero/core/logx"
)
// 消费渲染结果数据
type MqConsumerRenderResult struct {
}
func (m *MqConsumerRenderResult) Run(ctx context.Context, data []byte) error {
logx.Info("接收到MqConsumerRenderResult数据:", string(data))
var parseInfo websocket_data.RenderImageNotify
if err := json.Unmarshal(data, &parseInfo); err != nil {
logx.Error("MqConsumerRenderResult data format err:", err)
return nil //不返回错误则就删掉该消息
}
//遍历websocket链接把数据传进去
mapConnPool.Range(func(key, value any) bool {
//断言连接
ws, ok := value.(wsConnectItem)
if !ok {
return true
}
//关闭标识
if ws.isClose {
return true
}
//查询有无该渲染任务
renderId, ok := ws.renderProperty.renderImageTask[parseInfo.TaskId]
if !ok {
return true
}
b := ws.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, websocket_data.RenderImageRspMsg{
RenderId: renderId,
Image: parseInfo.Image,
})
//删除对应的需要渲染的图片map
ws.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{
Option: 0, //0删除 1添加
TaskId: parseInfo.TaskId,
RenderId: renderId,
}
//发送数据到out chan
ws.sendToOutChan(b)
return true
})
return nil
}

View File

@ -1,78 +0,0 @@
package logic
import (
"fusenapi/constants"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
"time"
"context"
"fusenapi/server/websocket/internal/svc"
"fusenapi/server/websocket/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ThirdPartyLoginNotifyLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewThirdPartyLoginNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ThirdPartyLoginNotifyLogic {
return &ThirdPartyLoginNotifyLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 处理进入前逻辑w,r
// func (l *ThirdPartyLoginNotifyLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) {
// }
// 处理逻辑后 w,r 如:重定向, resp 必须重新处理
// func (l *ThirdPartyLoginNotifyLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) {
// // httpx.OkJsonCtx(r.Context(), w, resp)
// }
func (l *ThirdPartyLoginNotifyLogic) ThirdPartyLoginNotify(req *types.ThirdPartyLoginNotifyReq, userinfo *auth.UserInfo) (resp *basic.Response) {
if time.Now().Unix()-120 > req.Time /*|| req.Time > time.Now().Unix() */ {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param: time is invalid")
}
if req.Info.WebsocketId <= 0 {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:websocket_id is required")
}
if req.Info.Token == "" {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:token is required")
}
//验证签名 sha256
/*notifyByte, _ := json.Marshal(req.Info)
h := sha256.New()
h.Write([]byte(fmt.Sprintf(constants.THIRD_PARTY_LOGIN_NOTIFY_SIGN_KEY, string(notifyByte), req.Time)))
signHex := h.Sum(nil)
sign := hex.EncodeToString(signHex)
//fmt.Println(sign)
if req.Sign != sign {
return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid sign")
}*/
//查询对应websocket连接
val, ok := mapConnPool.Load(req.Info.WebsocketId)
if !ok {
return resp.SetStatusWithMessage(basic.CodeOK, "success:websocket connection is not exists")
}
ws, ok := val.(wsConnectItem)
if !ok {
return resp.SetStatusWithMessage(basic.CodeServiceErr, "type of websocket connect object is err")
}
b := ws.respondDataFormat(constants.WEBSOCKET_THIRD_PARTY_LOGIN_NOTIFY, types.ThirdPartyLoginRspMsg{
Token: req.Info.Token,
})
select {
case <-ws.closeChan:
return resp.SetStatusWithMessage(basic.CodeOK, "websocket connect object is closed")
case ws.outChan <- b:
return resp.SetStatusWithMessage(basic.CodeOK, "success")
}
}

View File

@ -3,20 +3,53 @@ package logic
import (
"encoding/json"
"fusenapi/constants"
"fusenapi/server/websocket/internal/types"
"fusenapi/utils/hash"
"fusenapi/utils/websocket_data"
"github.com/zeromicro/go-zero/core/logx"
)
// 云渲染属性
type renderProperty struct {
renderImageTask map[string]struct{} //需要渲染的图片任务
renderImageTask map[string]string //需要渲染的图片任务 key是taskId val 是renderId
renderImageTaskCtlChan chan renderImageControlChanItem //渲染任务新增移除的控制通道
}
// 渲染任务新增移除的控制通道的数据
type renderImageControlChanItem struct {
Option int // 0删除 1添加
Key string //map的key
Option int // 0删除 1添加
TaskId string //map的key
RenderId string // map的val
}
// 渲染发送到组装数据组装数据
func (w *wsConnectItem) renderImage(data []byte) {
var renderImageData websocket_data.RenderImageReqMsg
if err := json.Unmarshal(data, &renderImageData); err != nil {
w.outChan <- w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:"+string(data))
logx.Error("invalid format of websocket render image message", err)
return
}
logx.Info("收到请求云渲染图片数据:", renderImageData)
renderImageData.RenderData.UserId = w.userId
//把需要渲染的图片任务加进去
taskId := hash.JsonHashKey(renderImageData.RenderData)
w.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{
Option: 1, //0删除 1添加
TaskId: taskId,
RenderId: renderImageData.RenderId,
}
tmpData := websocket_data.AssembleRenderData{
TaskId: taskId,
RenderId: renderImageData.RenderId,
RenderData: renderImageData.RenderData,
}
d, _ := json.Marshal(tmpData)
//发送给对应的流水线组装数据
if err := w.rabbitMq.SendMsg(constants.RABBIT_MQ_ASSEMBLE_RENDER_DATA, d); err != nil {
logx.Error("发送渲染任务数据到MQ失败:", string(data), "err:", err)
return
}
logx.Info("发送渲染数据到rabbitmq成功:", string(data))
}
// 操作连接中渲染任务的增加/删除
@ -28,43 +61,10 @@ func (w *wsConnectItem) operationRenderTask() {
case data := <-w.renderProperty.renderImageTaskCtlChan:
switch data.Option {
case 0: //删除任务
delete(w.renderProperty.renderImageTask, data.Key)
delete(w.renderProperty.renderImageTask, data.TaskId)
case 1: //新增任务
w.renderProperty.renderImageTask[data.Key] = struct{}{}
default:
w.renderProperty.renderImageTask[data.TaskId] = data.RenderId
}
}
}
}
// 渲染请求数据处理发送云渲染服务处理
func (w *wsConnectItem) SendToCloudRender(data []byte) {
var renderImageData types.RenderImageReqMsg
if err := json.Unmarshal(data, &renderImageData); err != nil {
w.outChan <- w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:"+string(data))
logx.Error("invalid format of websocket render image message", err)
return
}
logx.Info("收到请求云渲染图片数据:", renderImageData)
//把需要渲染的图片任务加进去
for _, productId := range renderImageData.ProductIds {
select {
case <-w.closeChan: //连接关闭了
return
default:
//加入渲染任务
key := w.getRenderImageMapKey(productId, renderImageData.TemplateTagId, renderImageData.LogoId, renderImageData.AlgorithmVersion)
w.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{
Option: 1, //0删除 1添加
Key: key,
}
//发送给对应的流水线组装数据
if err := w.rabbitMq.SendMsg(constants.RABBIT_MQ_ASSEMBLE_RENDER_DATA, data); err != nil {
logx.Error("发送渲染任务数据到MQ失败:", string(data))
continue
}
logx.Info("发送渲染数据到rabbitmq成功:", string(data))
}
}
}

View File

@ -5,55 +5,6 @@ import (
"fusenapi/utils/basic"
)
type DataTransferData struct {
T string `json:"t"` //消息类型
D interface{} `json:"d"` //传递的消息
}
type RenderImageReqMsg struct {
ProductIds []int64 `json:"product_ids"` //产品 id
TemplateTagId int64 `json:"template_tag_id"` //模板标签id
LogoId int64 `json:"logo_id"` //logoid
AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本
}
type RenderImageRspMsg struct {
ProductId int64 `json:"product_id"` //产品 id
TemplateTagId int64 `json:"template_tag_id"` //模板标签id
AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本
LogoId int64 `json:"logo_id"` //logoid
Image string `json:"image"` //渲染后的图片
}
type ThirdPartyLoginRspMsg struct {
Token string `json:"token"`
}
type RenderNotifyReq struct {
Sign string `json:"sign"`
Time int64 `json:"time"`
Info NotifyInfo `json:"info"`
}
type NotifyInfo struct {
ProductId int64 `json:"product_id"` //产品id
TemplateTagId int64 `json:"template_tag_id"` //模板标签id
AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本
LogoId int64 `json:"logo_id"` //logoid
Image string `json:"image"`
}
type ThirdPartyLoginNotifyReq struct {
Sign string `json:"sign"`
Time int64 `json:"time"`
Info ThirdPartyLoginNotify `json:"info"`
}
type ThirdPartyLoginNotify struct {
WebsocketId uint64 `json:"websocket_id"`
Token string `json:"token"`
}
type Request struct {
}

View File

@ -1,10 +1,12 @@
package main
import (
"context"
"flag"
"fmt"
"fusenapi/constants"
"fusenapi/server/websocket/internal/logic"
"net/http"
"time"
"fusenapi/utils/auth"
@ -23,14 +25,18 @@ func main() {
var c config.Config
conf.MustLoad(*configFile, &c)
c.Timeout = int64(time.Second * 15)
server := rest.MustNewServer(c.RestConf, rest.WithCustomCors(auth.FsCors, func(w http.ResponseWriter) {
}))
defer server.Stop()
ctx := svc.NewServiceContext(c)
//消费渲染结果队列
ctx1 := context.Background()
ctx2, cancel := context.WithCancel(ctx1)
ctx2 = context.WithValue(ctx2, "allmodels", ctx.AllModels)
defer cancel()
go ctx.RabbitMq.Consume(ctx2, constants.RABBIT_MQ_RENDER_RESULT_DATA, &logic.MqConsumerRenderResult{})
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@ -56,8 +56,29 @@ service home-user-auth {
@handler UserOrderCancelHandler
get /api/user/order-cancel (UserOrderCancelReq) returns (response);
// 用户logo列表
@handler UserLogoListHandler
get /api/user/logo-list (UserLogoListReq) returns (response);
// 再来一单
@handler UserAgainOrderHandler
get /api/user/one-more-order (UserAgainOrderReq) returns (response);
}
type (
UserAgainOrderReq {
Sn string `form:"sn"` // 订单编号
}
UserAgainOrderRes struct{}
)
type (
UserLogoListReq {
}
UserLogoListRes {
}
)
type (
UserOrderDeleteReq {
ID int64 `form:"id"` //订单id

Some files were not shown because too many files have changed in this diff Show More