Compare commits
61 Commits
main
...
feat-cjy-r
| Author | SHA1 | Date | |
|---|---|---|---|
| 7564af8220 | |||
| 4ce735d4de | |||
| c6f4385991 | |||
| c0ea800511 | |||
| 6c7e27ce78 | |||
| 7ba69a3afd | |||
| e7f90a304b | |||
| d20a124e14 | |||
| cccee7f86e | |||
| adf3b5ee89 | |||
| ffdc047d15 | |||
| 89bf59d878 | |||
| 6845ea1736 | |||
| 351709d08e | |||
| 11c8d63789 | |||
| c8397bcfe9 | |||
| 91f7b54581 | |||
| 4d8e91822f | |||
| e9fa67ae00 | |||
| 4d82fb6f96 | |||
| 445eb6a751 | |||
| ee9bbde2a7 | |||
| 3180c3c476 | |||
| 3c11449f6d | |||
| 4f9a38693d | |||
| d097e9a20e | |||
| a2d46c4463 | |||
| 222b294101 | |||
| 9170c77e32 | |||
|
|
76a08f9ad8 | ||
|
|
8688bd6abd | ||
| e9cd6876c2 | |||
| a4bff4284a | |||
| d811235da5 | |||
| bdf3fa6144 | |||
| dfb2e5e037 | |||
| 6b52775913 | |||
|
|
8d36aeb751 | ||
| 0226b0af12 | |||
| ae76287088 | |||
| 13fa87ec2b | |||
| fbf24995b8 | |||
| 145935ba04 | |||
| a3e617a87f | |||
| 8fa9f89db9 | |||
| e6ca737fb1 | |||
| 49caaa73c6 | |||
| 79f37993c1 | |||
| b079b597c3 | |||
|
|
c5f7903c6c | ||
|
|
065db45cdc | ||
|
|
3d67be4e09 | ||
|
|
e75e6b7ce9 | ||
| 789af8f0db | |||
| 09f598a1f4 | |||
| a6e5d38a43 | |||
| 26c59ee54c | |||
| 17215e758b | |||
|
|
daad39d53c | ||
|
|
b52190f021 | ||
|
|
2a38ed44b0 |
File diff suppressed because it is too large
Load Diff
13805
api/cast/cast.pb.go
13805
api/cast/cast.pb.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-triple. DO NOT EDIT.
|
// Code generated by protoc-gen-go-triple. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-triple v1.0.8
|
// - protoc-gen-go-triple v1.0.8
|
||||||
// - protoc v6.32.0--rc2
|
// - protoc v3.21.1
|
||||||
// source: pb/fiee/cast.proto
|
// source: pb/fiee/cast.proto
|
||||||
|
|
||||||
package cast
|
package cast
|
||||||
@ -52,6 +52,9 @@ type CastClient interface {
|
|||||||
UpdateWorkPlatformInfo(ctx context.Context, in *UpdateWorkPlatformInfoReq, opts ...grpc_go.CallOption) (*UpdateWorkPlatformInfoResp, common.ErrorWithAttachment)
|
UpdateWorkPlatformInfo(ctx context.Context, in *UpdateWorkPlatformInfoReq, opts ...grpc_go.CallOption) (*UpdateWorkPlatformInfoResp, common.ErrorWithAttachment)
|
||||||
UpdateWorkPublishLog(ctx context.Context, in *UpdateWorkPublishLogReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
|
UpdateWorkPublishLog(ctx context.Context, in *UpdateWorkPublishLogReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
|
||||||
RefreshWorkList(ctx context.Context, in *RefreshWorkListReq, opts ...grpc_go.CallOption) (*RefreshWorkListResp, common.ErrorWithAttachment)
|
RefreshWorkList(ctx context.Context, in *RefreshWorkListReq, opts ...grpc_go.CallOption) (*RefreshWorkListResp, common.ErrorWithAttachment)
|
||||||
|
WorkResource(ctx context.Context, in *WorkResourceReq, opts ...grpc_go.CallOption) (*WorkResourceResp, common.ErrorWithAttachment)
|
||||||
|
UpdateWorkResource(ctx context.Context, in *UpdateWorkResourceReq, opts ...grpc_go.CallOption) (*UpdateWorkResourceResp, common.ErrorWithAttachment)
|
||||||
|
UpdateMediaAccStatus(ctx context.Context, in *UpdateMediaAccStatusReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
|
||||||
OAuthAccount(ctx context.Context, in *OAuthAccountReq, opts ...grpc_go.CallOption) (*OAuthAccountResp, common.ErrorWithAttachment)
|
OAuthAccount(ctx context.Context, in *OAuthAccountReq, opts ...grpc_go.CallOption) (*OAuthAccountResp, common.ErrorWithAttachment)
|
||||||
OAuthAccountV2(ctx context.Context, in *OAuthAccountV2Req, opts ...grpc_go.CallOption) (*OAuthAccountV2Resp, common.ErrorWithAttachment)
|
OAuthAccountV2(ctx context.Context, in *OAuthAccountV2Req, opts ...grpc_go.CallOption) (*OAuthAccountV2Resp, common.ErrorWithAttachment)
|
||||||
OAuthCodeToToken(ctx context.Context, in *OAuthCodeToTokenReq, opts ...grpc_go.CallOption) (*OAuthCodeToTokenResp, common.ErrorWithAttachment)
|
OAuthCodeToToken(ctx context.Context, in *OAuthCodeToTokenReq, opts ...grpc_go.CallOption) (*OAuthCodeToTokenResp, common.ErrorWithAttachment)
|
||||||
@ -160,6 +163,9 @@ type CastClientImpl struct {
|
|||||||
UpdateWorkPlatformInfo func(ctx context.Context, in *UpdateWorkPlatformInfoReq) (*UpdateWorkPlatformInfoResp, error)
|
UpdateWorkPlatformInfo func(ctx context.Context, in *UpdateWorkPlatformInfoReq) (*UpdateWorkPlatformInfoResp, error)
|
||||||
UpdateWorkPublishLog func(ctx context.Context, in *UpdateWorkPublishLogReq) (*emptypb.Empty, error)
|
UpdateWorkPublishLog func(ctx context.Context, in *UpdateWorkPublishLogReq) (*emptypb.Empty, error)
|
||||||
RefreshWorkList func(ctx context.Context, in *RefreshWorkListReq) (*RefreshWorkListResp, error)
|
RefreshWorkList func(ctx context.Context, in *RefreshWorkListReq) (*RefreshWorkListResp, error)
|
||||||
|
WorkResource func(ctx context.Context, in *WorkResourceReq) (*WorkResourceResp, error)
|
||||||
|
UpdateWorkResource func(ctx context.Context, in *UpdateWorkResourceReq) (*UpdateWorkResourceResp, error)
|
||||||
|
UpdateMediaAccStatus func(ctx context.Context, in *UpdateMediaAccStatusReq) (*emptypb.Empty, error)
|
||||||
OAuthAccount func(ctx context.Context, in *OAuthAccountReq) (*OAuthAccountResp, error)
|
OAuthAccount func(ctx context.Context, in *OAuthAccountReq) (*OAuthAccountResp, error)
|
||||||
OAuthAccountV2 func(ctx context.Context, in *OAuthAccountV2Req) (*OAuthAccountV2Resp, error)
|
OAuthAccountV2 func(ctx context.Context, in *OAuthAccountV2Req) (*OAuthAccountV2Resp, error)
|
||||||
OAuthCodeToToken func(ctx context.Context, in *OAuthCodeToTokenReq) (*OAuthCodeToTokenResp, error)
|
OAuthCodeToToken func(ctx context.Context, in *OAuthCodeToTokenReq) (*OAuthCodeToTokenResp, error)
|
||||||
@ -375,6 +381,24 @@ func (c *castClient) RefreshWorkList(ctx context.Context, in *RefreshWorkListReq
|
|||||||
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/RefreshWorkList", in, out)
|
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/RefreshWorkList", in, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *castClient) WorkResource(ctx context.Context, in *WorkResourceReq, opts ...grpc_go.CallOption) (*WorkResourceResp, common.ErrorWithAttachment) {
|
||||||
|
out := new(WorkResourceResp)
|
||||||
|
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
|
||||||
|
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/WorkResource", in, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *castClient) UpdateWorkResource(ctx context.Context, in *UpdateWorkResourceReq, opts ...grpc_go.CallOption) (*UpdateWorkResourceResp, common.ErrorWithAttachment) {
|
||||||
|
out := new(UpdateWorkResourceResp)
|
||||||
|
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
|
||||||
|
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/UpdateWorkResource", in, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *castClient) UpdateMediaAccStatus(ctx context.Context, in *UpdateMediaAccStatusReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment) {
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
|
||||||
|
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/UpdateMediaAccStatus", in, out)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *castClient) OAuthAccount(ctx context.Context, in *OAuthAccountReq, opts ...grpc_go.CallOption) (*OAuthAccountResp, common.ErrorWithAttachment) {
|
func (c *castClient) OAuthAccount(ctx context.Context, in *OAuthAccountReq, opts ...grpc_go.CallOption) (*OAuthAccountResp, common.ErrorWithAttachment) {
|
||||||
out := new(OAuthAccountResp)
|
out := new(OAuthAccountResp)
|
||||||
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
|
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
|
||||||
@ -780,6 +804,9 @@ type CastServer interface {
|
|||||||
UpdateWorkPlatformInfo(context.Context, *UpdateWorkPlatformInfoReq) (*UpdateWorkPlatformInfoResp, error)
|
UpdateWorkPlatformInfo(context.Context, *UpdateWorkPlatformInfoReq) (*UpdateWorkPlatformInfoResp, error)
|
||||||
UpdateWorkPublishLog(context.Context, *UpdateWorkPublishLogReq) (*emptypb.Empty, error)
|
UpdateWorkPublishLog(context.Context, *UpdateWorkPublishLogReq) (*emptypb.Empty, error)
|
||||||
RefreshWorkList(context.Context, *RefreshWorkListReq) (*RefreshWorkListResp, error)
|
RefreshWorkList(context.Context, *RefreshWorkListReq) (*RefreshWorkListResp, error)
|
||||||
|
WorkResource(context.Context, *WorkResourceReq) (*WorkResourceResp, error)
|
||||||
|
UpdateWorkResource(context.Context, *UpdateWorkResourceReq) (*UpdateWorkResourceResp, error)
|
||||||
|
UpdateMediaAccStatus(context.Context, *UpdateMediaAccStatusReq) (*emptypb.Empty, error)
|
||||||
OAuthAccount(context.Context, *OAuthAccountReq) (*OAuthAccountResp, error)
|
OAuthAccount(context.Context, *OAuthAccountReq) (*OAuthAccountResp, error)
|
||||||
OAuthAccountV2(context.Context, *OAuthAccountV2Req) (*OAuthAccountV2Resp, error)
|
OAuthAccountV2(context.Context, *OAuthAccountV2Req) (*OAuthAccountV2Resp, error)
|
||||||
OAuthCodeToToken(context.Context, *OAuthCodeToTokenReq) (*OAuthCodeToTokenResp, error)
|
OAuthCodeToToken(context.Context, *OAuthCodeToTokenReq) (*OAuthCodeToTokenResp, error)
|
||||||
@ -935,6 +962,15 @@ func (UnimplementedCastServer) UpdateWorkPublishLog(context.Context, *UpdateWork
|
|||||||
func (UnimplementedCastServer) RefreshWorkList(context.Context, *RefreshWorkListReq) (*RefreshWorkListResp, error) {
|
func (UnimplementedCastServer) RefreshWorkList(context.Context, *RefreshWorkListReq) (*RefreshWorkListResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RefreshWorkList not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RefreshWorkList not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedCastServer) WorkResource(context.Context, *WorkResourceReq) (*WorkResourceResp, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method WorkResource not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCastServer) UpdateWorkResource(context.Context, *UpdateWorkResourceReq) (*UpdateWorkResourceResp, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkResource not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCastServer) UpdateMediaAccStatus(context.Context, *UpdateMediaAccStatusReq) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UpdateMediaAccStatus not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedCastServer) OAuthAccount(context.Context, *OAuthAccountReq) (*OAuthAccountResp, error) {
|
func (UnimplementedCastServer) OAuthAccount(context.Context, *OAuthAccountReq) (*OAuthAccountResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method OAuthAccount not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method OAuthAccount not implemented")
|
||||||
}
|
}
|
||||||
@ -1819,6 +1855,93 @@ func _Cast_RefreshWorkList_Handler(srv interface{}, ctx context.Context, dec fun
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Cast_WorkResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(WorkResourceReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
base := srv.(dubbo3.Dubbo3GrpcService)
|
||||||
|
args := []interface{}{}
|
||||||
|
args = append(args, in)
|
||||||
|
md, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
invAttachment := make(map[string]interface{}, len(md))
|
||||||
|
for k, v := range md {
|
||||||
|
invAttachment[k] = v
|
||||||
|
}
|
||||||
|
invo := invocation.NewRPCInvocation("WorkResource", args, invAttachment)
|
||||||
|
if interceptor == nil {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
info := &grpc_go.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Cast_UpdateWorkResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UpdateWorkResourceReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
base := srv.(dubbo3.Dubbo3GrpcService)
|
||||||
|
args := []interface{}{}
|
||||||
|
args = append(args, in)
|
||||||
|
md, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
invAttachment := make(map[string]interface{}, len(md))
|
||||||
|
for k, v := range md {
|
||||||
|
invAttachment[k] = v
|
||||||
|
}
|
||||||
|
invo := invocation.NewRPCInvocation("UpdateWorkResource", args, invAttachment)
|
||||||
|
if interceptor == nil {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
info := &grpc_go.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Cast_UpdateMediaAccStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UpdateMediaAccStatusReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
base := srv.(dubbo3.Dubbo3GrpcService)
|
||||||
|
args := []interface{}{}
|
||||||
|
args = append(args, in)
|
||||||
|
md, _ := metadata.FromIncomingContext(ctx)
|
||||||
|
invAttachment := make(map[string]interface{}, len(md))
|
||||||
|
for k, v := range md {
|
||||||
|
invAttachment[k] = v
|
||||||
|
}
|
||||||
|
invo := invocation.NewRPCInvocation("UpdateMediaAccStatus", args, invAttachment)
|
||||||
|
if interceptor == nil {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
info := &grpc_go.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
|
||||||
|
return result, result.Error()
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Cast_OAuthAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
|
func _Cast_OAuthAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(OAuthAccountReq)
|
in := new(OAuthAccountReq)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -3745,6 +3868,18 @@ var Cast_ServiceDesc = grpc_go.ServiceDesc{
|
|||||||
MethodName: "RefreshWorkList",
|
MethodName: "RefreshWorkList",
|
||||||
Handler: _Cast_RefreshWorkList_Handler,
|
Handler: _Cast_RefreshWorkList_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "WorkResource",
|
||||||
|
Handler: _Cast_WorkResource_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UpdateWorkResource",
|
||||||
|
Handler: _Cast_UpdateWorkResource_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UpdateMediaAccStatus",
|
||||||
|
Handler: _Cast_UpdateMediaAccStatus_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "OAuthAccount",
|
MethodName: "OAuthAccount",
|
||||||
Handler: _Cast_OAuthAccount_Handler,
|
Handler: _Cast_OAuthAccount_Handler,
|
||||||
|
|||||||
BIN
data/竞品报告导入模板.xlsx
Normal file
BIN
data/竞品报告导入模板.xlsx
Normal file
Binary file not shown.
5
go.mod
5
go.mod
@ -114,8 +114,10 @@ require (
|
|||||||
github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000
|
github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000
|
||||||
github.com/gin-contrib/pprof v1.4.0
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mholt/archiver v3.1.1+incompatible
|
github.com/mholt/archiver v3.1.1+incompatible
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||||
|
github.com/phpdave11/gofpdf v1.4.3
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||||
github.com/samber/lo v1.52.0
|
github.com/samber/lo v1.52.0
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
@ -165,7 +167,6 @@ require (
|
|||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
||||||
github.com/golang/mock v1.5.0 // indirect
|
github.com/golang/mock v1.5.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
@ -180,7 +181,7 @@ require (
|
|||||||
github.com/nxadm/tail v1.4.11 // indirect
|
github.com/nxadm/tail v1.4.11 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
github.com/onsi/gomega v1.18.1 // indirect
|
github.com/onsi/gomega v1.18.1 // indirect
|
||||||
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
|
github.com/phpdave11/gofpdi v1.0.15 // indirect
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||||
github.com/polarismesh/polaris-go v1.1.0 // indirect
|
github.com/polarismesh/polaris-go v1.1.0 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@ -168,6 +168,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
@ -583,6 +584,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
@ -747,8 +749,11 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO
|
|||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 h1:zyWXQ6vu27ETMpYsEMAsisQ+GqJ4e1TPvSNfdOPF0no=
|
github.com/phpdave11/gofpdf v1.4.3 h1:M/zHvS8FO3zh9tUd2RCOPEjyuVcs281FCyF22Qlz/IA=
|
||||||
|
github.com/phpdave11/gofpdf v1.4.3/go.mod h1:MAwzoUIgD3J55u0rxIG2eu37c+XWhBtXSpPAhnQXf/o=
|
||||||
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||||
|
github.com/phpdave11/gofpdi v1.0.15 h1:iJazY1BQ07I9s7N5EWjBO1YbhmKfHGxNligUv/Rw4Lc=
|
||||||
|
github.com/phpdave11/gofpdi v1.0.15/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
||||||
@ -827,6 +832,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
|||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
@ -1081,6 +1087,7 @@ golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8H
|
|||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||||
|
|||||||
109
pkg/common/qwen/qwen_vl.go
Normal file
109
pkg/common/qwen/qwen_vl.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package qwen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
modelQwen "fonchain-fiee/pkg/model/qwen"
|
||||||
|
"fonchain-fiee/pkg/utils"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VL 调用通义千问视觉多模态API,支持多个视频、多张图片和文本
|
||||||
|
func VL(videoURLs []string, imageURLs []string, text string, model string) (resp *modelQwen.VLResponse, err error) {
|
||||||
|
// 设置默认模型
|
||||||
|
if model == "" {
|
||||||
|
model = "qwen3-vl-plus"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建内容列表
|
||||||
|
content := make([]modelQwen.VLContent, 0)
|
||||||
|
|
||||||
|
// 添加视频内容,支持自定义fps
|
||||||
|
for _, videoURL := range videoURLs {
|
||||||
|
fps := 2 // 默认fps为2
|
||||||
|
content = append(content, modelQwen.VLContent{
|
||||||
|
Type: "video_url",
|
||||||
|
VideoURL: &modelQwen.VideoURL{
|
||||||
|
URL: videoURL,
|
||||||
|
},
|
||||||
|
FPS: fps,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加图片内容
|
||||||
|
for _, imageURL := range imageURLs {
|
||||||
|
content = append(content, modelQwen.VLContent{
|
||||||
|
Type: "image_url",
|
||||||
|
ImageURL: &modelQwen.ImageURL{
|
||||||
|
URL: imageURL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文本内容
|
||||||
|
if text != "" {
|
||||||
|
content = append(content, modelQwen.VLContent{
|
||||||
|
Type: "text",
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求
|
||||||
|
req := modelQwen.VLRequest{
|
||||||
|
Model: model,
|
||||||
|
Messages: []modelQwen.VLMessage{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: content,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化请求
|
||||||
|
jsonData, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("VL Marshal failed", zap.Error(err))
|
||||||
|
return nil, errors.New("序列化请求失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求,使用PostBytesHeader获取状态码和响应体
|
||||||
|
statusCode, body, err := utils.PostBytesHeader(modelQwen.DashscopeVLURL, map[string]interface{}{
|
||||||
|
"Authorization": "Bearer " + modelQwen.DashscopeAPIKey,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
// "X-DashScope-OssResourceResolve": "enable", // 启用OSS资源解析
|
||||||
|
}, jsonData)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("VL Post failed", zap.Error(err))
|
||||||
|
return nil, errors.New("请求视觉AI失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查状态码,如果不是200,尝试解析错误响应
|
||||||
|
if statusCode != 200 {
|
||||||
|
// 尝试解析错误响应
|
||||||
|
var errorResp struct {
|
||||||
|
Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.Error.Message != "" {
|
||||||
|
zap.L().Error("VL API error", zap.Int("status", statusCode), zap.String("message", errorResp.Error.Message))
|
||||||
|
return nil, fmt.Errorf("%s", errorResp.Error.Message)
|
||||||
|
}
|
||||||
|
// 如果无法解析错误响应,返回通用错误
|
||||||
|
zap.L().Error("VL API error", zap.Int("status", statusCode), zap.String("body", string(body)))
|
||||||
|
return nil, fmt.Errorf("接口返回错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
var result modelQwen.VLResponse
|
||||||
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
|
zap.L().Error("VL Unmarshal failed", zap.Error(err), zap.String("body", string(body)))
|
||||||
|
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
@ -32,6 +32,8 @@ func InitTasks() error {
|
|||||||
|
|
||||||
err = cm.AddTask("artistAutoConfirmAnalysis", "0 */1 * * * *", ArtistAutoConfirmAnalysisTask)
|
err = cm.AddTask("artistAutoConfirmAnalysis", "0 */1 * * * *", ArtistAutoConfirmAnalysisTask)
|
||||||
err = cm.AddTask("refreshWorkAnalysisApprovalStatus", "0 */1 * * * *", RefreshWorkAnalysisApprovalStatusTask)
|
err = cm.AddTask("refreshWorkAnalysisApprovalStatus", "0 */1 * * * *", RefreshWorkAnalysisApprovalStatusTask)
|
||||||
|
err = cm.AddTask("artistAutoConfirmReport", "0 */1 * * * *", ArtistAutoConfirmReportTask)
|
||||||
|
err = cm.AddTask("refreshCompetitiveReportApprovalStatus", "0 */1 * * * *", RefreshCompetitiveReportApprovalStatusTask)
|
||||||
err = cm.AddTask("refreshArtistOrder", "0 */30 * * * *", RefreshArtistOrderTask)
|
err = cm.AddTask("refreshArtistOrder", "0 */30 * * * *", RefreshArtistOrderTask)
|
||||||
|
|
||||||
// 每天 00:30 和 12:30 执行 Ayrshare 指标采集任务
|
// 每天 00:30 和 12:30 执行 Ayrshare 指标采集任务
|
||||||
@ -500,6 +502,43 @@ func ArtistAutoConfirmAnalysisTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ArtistAutoConfirmReportTask() {
|
||||||
|
now := float64(time.Now().Unix())
|
||||||
|
opt := redis.ZRangeBy{
|
||||||
|
Min: fmt.Sprintf("%d", 0),
|
||||||
|
Max: fmt.Sprintf("%f", now),
|
||||||
|
}
|
||||||
|
reportUuids, err := cache.RedisClient.ZRangeByScore(modelCast.AutoConfirmReportQueueKey, opt).Result()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("获取到期竞品报告任务失败", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(reportUuids) == 0 {
|
||||||
|
zap.L().Debug("没有到期的竞品报告任务")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zap.L().Info("发现到期竞品报告任务", zap.Int("count", len(reportUuids)))
|
||||||
|
for _, reportUuid := range reportUuids {
|
||||||
|
serverCast.ProcessReportTask(context.Background(), reportUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RefreshCompetitiveReportApprovalStatusTask() {
|
||||||
|
resp, err := service.CastProvider.ListCompetitiveReport(context.Background(), &cast.ListCompetitiveReportReq{
|
||||||
|
Page: 1,
|
||||||
|
StatusList: []uint32{2}, // 状态为2表示待审批
|
||||||
|
PageSize: 999999,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取竞品报告列表失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.Data == nil || len(resp.Data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverCast.RefreshCompetitiveReportApproval(nil, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
// AyrshareMetricsCollectorTask Ayrshare 指标采集定时任务(每天 00:30 和 12:30 执行)
|
// AyrshareMetricsCollectorTask Ayrshare 指标采集定时任务(每天 00:30 和 12:30 执行)
|
||||||
func AyrshareMetricsCollectorTask() {
|
func AyrshareMetricsCollectorTask() {
|
||||||
serverCast.ExecuteAyrshareMetricsCollector()
|
serverCast.ExecuteAyrshareMetricsCollector()
|
||||||
|
|||||||
@ -13,7 +13,6 @@ type UserWorkAnalysisConfirmReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetBundleBalanceListResp struct {
|
type GetBundleBalanceListResp struct {
|
||||||
|
|
||||||
Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total"`
|
Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total"`
|
||||||
Data []*BundleBalanceItem `protobuf:"bytes,2,rep,name=data,proto3" json:"data"`
|
Data []*BundleBalanceItem `protobuf:"bytes,2,rep,name=data,proto3" json:"data"`
|
||||||
}
|
}
|
||||||
@ -33,6 +32,8 @@ type BundleBalanceItem struct {
|
|||||||
ImageConsumptionNumber int32 `protobuf:"varint,12,opt,name=imageConsumptionNumber,proto3" json:"imageConsumptionNumber"`
|
ImageConsumptionNumber int32 `protobuf:"varint,12,opt,name=imageConsumptionNumber,proto3" json:"imageConsumptionNumber"`
|
||||||
DataAnalysisNumber int32 `protobuf:"varint,13,opt,name=dataAnalysisNumber,proto3" json:"dataAnalysisNumber"`
|
DataAnalysisNumber int32 `protobuf:"varint,13,opt,name=dataAnalysisNumber,proto3" json:"dataAnalysisNumber"`
|
||||||
DataAnalysisConsumptionNumber int32 `protobuf:"varint,14,opt,name=dataAnalysisConsumptionNumber,proto3" json:"dataAnalysisConsumptionNumber"`
|
DataAnalysisConsumptionNumber int32 `protobuf:"varint,14,opt,name=dataAnalysisConsumptionNumber,proto3" json:"dataAnalysisConsumptionNumber"`
|
||||||
ExpansionPacksNumber int32 `protobuf:"varint,15,opt,name=expansionPacksNumber,proto3" json:"expansionPacksNumber"`
|
CompetitiveNumber int32 `protobuf:"varint,15,opt,name=competitiveNumber,proto3" json:"competitiveNumber"`
|
||||||
Bought int32 `protobuf:"varint,16,opt,name=bought,proto3" json:"bought"`
|
CompetitiveConsumptionNumber int32 `protobuf:"varint,16,opt,name=competitiveConsumptionNumber,proto3" json:"competitiveConsumptionNumber"`
|
||||||
|
ExpansionPacksNumber int32 `protobuf:"varint,17,opt,name=expansionPacksNumber,proto3" json:"expansionPacksNumber"`
|
||||||
|
Bought int32 `protobuf:"varint,18,opt,name=bought,proto3" json:"bought"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,11 @@ type SyncAsProfileReq struct {
|
|||||||
|
|
||||||
// 定义枚举值
|
// 定义枚举值
|
||||||
const (
|
const (
|
||||||
BalanceTypeAccountValue BalanceTypeEnum = 1
|
BalanceTypeAccountValue BalanceTypeEnum = 1 //账号
|
||||||
BalanceTypeImageValue BalanceTypeEnum = 2
|
BalanceTypeImageValue BalanceTypeEnum = 2 //图文
|
||||||
BalanceTypeVideoValue BalanceTypeEnum = 3
|
BalanceTypeVideoValue BalanceTypeEnum = 3 //视频
|
||||||
BalanceTypeDataValue BalanceTypeEnum = 4
|
BalanceTypeDataValue BalanceTypeEnum = 4 //数据分析
|
||||||
|
BalanceTypeCompetitiveValue BalanceTypeEnum = 5 //竞品数
|
||||||
)
|
)
|
||||||
|
|
||||||
var PlatformNameKv = map[uint32]string{
|
var PlatformNameKv = map[uint32]string{
|
||||||
|
|||||||
@ -21,6 +21,9 @@ const (
|
|||||||
AutoConfirmAnalysisQueueKey = "auto_confirm:analysis:queue"
|
AutoConfirmAnalysisQueueKey = "auto_confirm:analysis:queue"
|
||||||
AutoConfirmAnalysisLockKey = "auto_confirm:analysis:lock:%s"
|
AutoConfirmAnalysisLockKey = "auto_confirm:analysis:lock:%s"
|
||||||
|
|
||||||
|
AutoConfirmReportQueueKey = "auto_confirm:report:queue"
|
||||||
|
AutoConfirmReportLockKey = "auto_confirm:report:lock:%s"
|
||||||
|
|
||||||
// AyrshareMetricsCollectorLockKey Ayrshare 指标采集任务锁
|
// AyrshareMetricsCollectorLockKey Ayrshare 指标采集任务锁
|
||||||
AyrshareMetricsCollectorLockKey = "ayrshare:metrics:collector:lock"
|
AyrshareMetricsCollectorLockKey = "ayrshare:metrics:collector:lock"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const (
|
|||||||
DashscopeAPIKey string = "sk-5ae9df5d3bcf4755ad5d12012058a2e7"
|
DashscopeAPIKey string = "sk-5ae9df5d3bcf4755ad5d12012058a2e7"
|
||||||
DashscopeText2ImageURL string = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis"
|
DashscopeText2ImageURL string = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis"
|
||||||
DashscopeEditImageURL string = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis"
|
DashscopeEditImageURL string = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis"
|
||||||
|
DashscopeVLURL string = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QwenImageRequest 通义千问文生图请求
|
// QwenImageRequest 通义千问文生图请求
|
||||||
|
|||||||
47
pkg/model/qwen/qwen_vl.go
Normal file
47
pkg/model/qwen/qwen_vl.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package qwen
|
||||||
|
|
||||||
|
// VLContent 视觉多模态内容结构,支持文本、图片和视频
|
||||||
|
type VLContent struct {
|
||||||
|
Type string `json:"type"` // text, image_url, video_url
|
||||||
|
Text string `json:"text,omitempty"` // type=text 时使用
|
||||||
|
ImageURL *ImageURL `json:"image_url,omitempty"` // type=image_url 时使用
|
||||||
|
VideoURL *VideoURL `json:"video_url,omitempty"` // type=video_url 时使用
|
||||||
|
FPS int `json:"fps,omitempty"` // type=video_url 时可选,视频帧率
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoURL 视频URL结构
|
||||||
|
type VideoURL struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VLRequest 视觉多模态请求结构
|
||||||
|
type VLRequest struct {
|
||||||
|
Model string `json:"model"` // 模型名称,如 qwen3-vl-plus
|
||||||
|
Messages []VLMessage `json:"messages"` // 消息列表
|
||||||
|
Seed int64 `json:"seed,omitempty"` // 随机种子
|
||||||
|
EnableSearch bool `json:"enable_search,omitempty"` // 是否启用搜索
|
||||||
|
}
|
||||||
|
|
||||||
|
// VLMessage 视觉多模态消息结构
|
||||||
|
type VLMessage struct {
|
||||||
|
Role string `json:"role"` // user, assistant, system
|
||||||
|
Content []VLContent `json:"content"` // 内容列表,可包含文本、图片、视频
|
||||||
|
}
|
||||||
|
|
||||||
|
// VLResponse 视觉多模态响应结构
|
||||||
|
type VLResponse struct {
|
||||||
|
Choices []VLChoice `json:"choices"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VLChoice 视觉多模态选择结果
|
||||||
|
type VLChoice struct {
|
||||||
|
Message struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
ReasoningContent string `json:"reasoning_content"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
} `json:"message"`
|
||||||
|
FinishReason string `json:"finish_reason"`
|
||||||
|
Index int `json:"index,omitempty"`
|
||||||
|
}
|
||||||
@ -33,18 +33,41 @@ func AnalysisRouter(r *gin.RouterGroup) {
|
|||||||
analysis.POST("update-approval-id", serviceCast.UpdateWorkAnalysisApprovalID) // 更新作品分析审批ID
|
analysis.POST("update-approval-id", serviceCast.UpdateWorkAnalysisApprovalID) // 更新作品分析审批ID
|
||||||
|
|
||||||
analysis.POST("trigger-ayrshare-metrics", serviceCast.TriggerAyrshareMetricsCollector) // 手动触发 Ayrshare 指标采集任务
|
analysis.POST("trigger-ayrshare-metrics", serviceCast.TriggerAyrshareMetricsCollector) // 手动触发 Ayrshare 指标采集任务
|
||||||
|
}
|
||||||
|
|
||||||
|
competitiveReport := r.Group("report")
|
||||||
|
competitiveReport.Use(middleware.CheckWebLogin(service.AccountProvider))
|
||||||
|
{
|
||||||
|
competitiveReport.POST("create", serviceCast.CreateCompetitiveReport) // 创建竞品报告
|
||||||
|
competitiveReport.POST("import-batch", serviceCast.ImportCompetitiveReportBatch) // 批量导入竞品报告
|
||||||
|
competitiveReport.POST("update-status", serviceCast.UpdateCompetitiveReportStatus) // 更新竞品报告状态
|
||||||
|
competitiveReport.POST("detail", serviceCast.GetCompetitiveReport) // 获取竞品报告详情
|
||||||
|
competitiveReport.POST("list", serviceCast.ListCompetitiveReport) // 获取竞品报告列表
|
||||||
|
competitiveReport.POST("single-list", serviceCast.ListCompetitiveReportByArtistUuid) // 根据艺人UUID获取竞品报告列表
|
||||||
|
competitiveReport.POST("delete", serviceCast.DeleteCompetitiveReport) // 删除竞品报告
|
||||||
|
competitiveReport.POST("update-approval-id", serviceCast.UpdateCompetitiveReportApprovalID) // 更新竞品报告审批ID
|
||||||
|
competitiveReport.POST("count-by-work-uuids", serviceCast.CountCompetitiveReportByWorkUuids) // 根据作品UUID统计竞品报告数量
|
||||||
|
competitiveReport.POST("export-list", serviceCast.ListCompetitiveReportExport) // 竞品报告列表导出
|
||||||
|
competitiveReport.POST("export-single-list", serviceCast.ListCompetitiveReportSingleExport) // 竞品报告单个列表导出
|
||||||
}
|
}
|
||||||
|
|
||||||
// 员工任务相关路由(需要App登录验证
|
// 员工任务相关路由(需要App登录验证
|
||||||
analysisAppRoute := r.Group("app/analysis")
|
analysisAppRoute := r.Group("app/analysis")
|
||||||
analysisAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
analysisAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
||||||
{
|
{
|
||||||
analysisAppRoute.POST("list", serviceCast.ListWorkAnalysis) // 作品列表
|
analysisAppRoute.POST("list", serviceCast.ListWorkAnalysisForApp) // 作品列表
|
||||||
analysisAppRoute.POST("detail", serviceCast.GetWorkAnalysis) // 作品分析详情
|
analysisAppRoute.POST("detail", serviceCast.GetWorkAnalysis) // 作品分析详情
|
||||||
analysisAppRoute.POST("update-status", serviceCast.UpdateWorkAnalysisStatus) // 用户确认
|
analysisAppRoute.POST("update-status", serviceCast.UpdateWorkAnalysisStatus) // 用户确认
|
||||||
analysisAppRoute.POST("check-balance", serviceCast.CheckBundleBalance) // 检查套餐余量
|
analysisAppRoute.POST("check-balance", serviceCast.CheckBundleBalance) // 检查套餐余量
|
||||||
analysisAppRoute.POST("tobe-confirmed-list", serviceCast.TobeConfirmedList) // 待确认数据列表
|
analysisAppRoute.POST("tobe-confirmed-list", serviceCast.TobeConfirmedList) // 待确认数据列表
|
||||||
analysisAppRoute.POST("work-analysis-confirm", bundle.WorkAnalysisConfirm)
|
analysisAppRoute.POST("work-analysis-confirm", bundle.WorkAnalysisConfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
competitiveReportAppRoute := r.Group("app/report")
|
||||||
|
competitiveReportAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
||||||
|
{
|
||||||
|
competitiveReportAppRoute.POST("detail", serviceCast.GetCompetitiveReportForApp) // 获取竞品报告详情(App端)
|
||||||
|
competitiveReportAppRoute.POST("list", serviceCast.ListReportByArtistUuidForApp) // 根据艺人UUID获取竞品报告列表(App端)
|
||||||
|
competitiveReportAppRoute.POST("update-status", serviceCast.UpdateCompetitiveReportStatus) // 更新竞品报告状态(App端)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,7 @@ func MediaRouter(r *gin.RouterGroup) {
|
|||||||
work.POST("remind", serviceCast.Remind)
|
work.POST("remind", serviceCast.Remind)
|
||||||
work.POST("publish-info", serviceCast.PublishInfo)
|
work.POST("publish-info", serviceCast.PublishInfo)
|
||||||
work.POST("import-batch", serviceCast.ImportWorkBatch)
|
work.POST("import-batch", serviceCast.ImportWorkBatch)
|
||||||
|
work.POST("list-published", serviceCast.WorkListPublished)
|
||||||
}
|
}
|
||||||
|
|
||||||
script := auth.Group("script")
|
script := auth.Group("script")
|
||||||
@ -85,11 +86,13 @@ func MediaRouter(r *gin.RouterGroup) {
|
|||||||
{
|
{
|
||||||
aiNoAuth.POST("image-generate", serviceAI.AIImageGenerate)
|
aiNoAuth.POST("image-generate", serviceAI.AIImageGenerate)
|
||||||
aiNoAuth.POST("text-generate", serviceAI.AIChat)
|
aiNoAuth.POST("text-generate", serviceAI.AIChat)
|
||||||
|
aiNoAuth.POST("video-vl", serviceAI.AIVideoVL)
|
||||||
}
|
}
|
||||||
aiAuth := auth.Group("ai")
|
aiAuth := auth.Group("ai")
|
||||||
{
|
{
|
||||||
aiAuth.POST("one-text", serviceAI.OneText)
|
aiAuth.POST("one-text", serviceAI.OneText)
|
||||||
aiAuth.POST("more-text", serviceAI.MoreText)
|
aiAuth.POST("more-text", serviceAI.MoreText)
|
||||||
|
aiAuth.POST("generate-report", serviceAI.AICompetitorReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
social := noAuth.Group("social")
|
social := noAuth.Group("social")
|
||||||
|
|||||||
@ -16,28 +16,22 @@ func TaskBenchRouter(r *gin.RouterGroup) {
|
|||||||
taskBenchRoute.Use(middleware.CheckWebLogin(service.AccountProvider))
|
taskBenchRoute.Use(middleware.CheckWebLogin(service.AccountProvider))
|
||||||
// 任务台管理
|
// 任务台管理
|
||||||
{
|
{
|
||||||
// 查询待指派任务记录
|
|
||||||
taskBenchRoute.POST("pending-task-list", taskbench.GetPendingTaskList)
|
|
||||||
|
|
||||||
// 待指派任务布局
|
// 待指派任务布局
|
||||||
taskBenchRoute.POST("pending-task-layout", taskbench.GetPendingTaskLayout)
|
taskBenchRoute.POST("pending-task-layout", taskbench.GetPendingTaskLayout)
|
||||||
taskBenchRoute.POST("set-pending-task-layout", taskbench.SetPendingTaskLayout)
|
taskBenchRoute.POST("set-pending-task-layout", taskbench.SetPendingTaskLayout)
|
||||||
|
|
||||||
// 指派某位员工完成某个艺人的任务
|
// 指派
|
||||||
taskBenchRoute.POST("assign-task", taskbench.AssignTask)
|
taskBenchRoute.POST("assign-task", taskbench.AssignTask)
|
||||||
|
|
||||||
// 批量指派任务
|
// 批量指派
|
||||||
taskBenchRoute.POST("batch-assign-task", taskbench.BatchAssignTask)
|
taskBenchRoute.POST("batch-assign-task", taskbench.BatchAssignTask)
|
||||||
|
|
||||||
// 中止指派任务(根据任务指派记录UUID)
|
// 中止指派
|
||||||
taskBenchRoute.POST("terminate-task-by-uuid", taskbench.TerminateTaskByUUID)
|
taskBenchRoute.POST("terminate-task-by-uuid", taskbench.TerminateTaskByUUID)
|
||||||
|
|
||||||
// 批量中止指派任务(根据多个任务指派记录UUID)
|
// 批量中止指派
|
||||||
taskBenchRoute.POST("batch-terminate-task", taskbench.BatchTerminateTask)
|
taskBenchRoute.POST("batch-terminate-task", taskbench.BatchTerminateTask)
|
||||||
|
|
||||||
// 修改待发数量
|
|
||||||
taskBenchRoute.POST("update-pending-count", taskbench.UpdatePendingCount)
|
|
||||||
|
|
||||||
// 查询最近被指派记录
|
// 查询最近被指派记录
|
||||||
taskBenchRoute.POST("recent-assign-records", taskbench.GetRecentAssignRecords)
|
taskBenchRoute.POST("recent-assign-records", taskbench.GetRecentAssignRecords)
|
||||||
|
|
||||||
@ -62,12 +56,6 @@ func TaskBenchRouter(r *gin.RouterGroup) {
|
|||||||
// 员工手动点击完成任务
|
// 员工手动点击完成任务
|
||||||
taskBenchRoute.POST("complete-manually", taskbench.CompleteTaskManually)
|
taskBenchRoute.POST("complete-manually", taskbench.CompleteTaskManually)
|
||||||
|
|
||||||
// 查询艺人套餐剩余数量
|
|
||||||
taskBenchRoute.POST("artist-bundle-balance", taskbench.GetArtistBundleBalance)
|
|
||||||
|
|
||||||
// 批量查询艺人待上传数量
|
|
||||||
taskBenchRoute.POST("batch-get-pending-upload", taskbench.GetPendingUploadBreakdown)
|
|
||||||
|
|
||||||
// 查询艺人待上传列表
|
// 查询艺人待上传列表
|
||||||
taskBenchRoute.POST("pending-upload-list", taskbench.GetArtistUploadStatsList)
|
taskBenchRoute.POST("pending-upload-list", taskbench.GetArtistUploadStatsList)
|
||||||
|
|
||||||
@ -81,12 +69,4 @@ func TaskBenchRouter(r *gin.RouterGroup) {
|
|||||||
taskBenchRoute.POST("pending-data-list", taskbench.GetPendingAssign)
|
taskBenchRoute.POST("pending-data-list", taskbench.GetPendingAssign)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 员工任务相关路由(需要App登录验证)
|
|
||||||
taskBenchAppRoute := r.Group("task-bench")
|
|
||||||
taskBenchAppRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
|
|
||||||
|
|
||||||
{
|
|
||||||
// 员工实际完成任务状态更新
|
|
||||||
taskBenchAppRoute.POST("update-progress", taskbench.UpdateTaskProgress)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
277
pkg/service/ai/video_vl.go
Normal file
277
pkg/service/ai/video_vl.go
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"fonchain-fiee/pkg/common/qwen"
|
||||||
|
"fonchain-fiee/pkg/service"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VideoVLRequest 视频/图片理解请求参数
|
||||||
|
type VideoVLRequest struct {
|
||||||
|
Videos []string `json:"videos"` // 视频URL列表
|
||||||
|
Images []string `json:"images"` // 图片URL列表
|
||||||
|
Text string `json:"text"` // 可选的文本提示
|
||||||
|
Model string `json:"model"` // 可选的模型名称,默认使用 qwen3-vl-plus
|
||||||
|
}
|
||||||
|
|
||||||
|
// AIVideoVL AI理解视频/图片接口
|
||||||
|
func AIVideoVL(ctx *gin.Context) {
|
||||||
|
var req VideoVLRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(ctx, errors.New("参数错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否至少提供了视频或图片
|
||||||
|
if len(req.Videos) == 0 && len(req.Images) == 0 {
|
||||||
|
service.Error(ctx, errors.New("至少需要提供一个视频或图片"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Videos) > 1 {
|
||||||
|
service.Error(ctx, errors.New("当前只能选一个视频"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt := "请你详细描述视频和图片中的内容分别是什么"
|
||||||
|
|
||||||
|
// 调用VL函数进行AI理解
|
||||||
|
result, err := qwen.VL(req.Videos, req.Images, Prompt, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
// 检查是否是文件下载超时错误(内容过大)
|
||||||
|
errMsg := err.Error()
|
||||||
|
if contains(errMsg, "Download multimodal file timed out") || contains(errMsg, "timed out") {
|
||||||
|
service.Error(ctx, errors.New("内容过大,请重新选择"))
|
||||||
|
} else {
|
||||||
|
service.Error(ctx, errors.New("ai分析帖子内容失败"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回AI返回的数据
|
||||||
|
service.Success(ctx, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains 检查字符串是否包含子字符串(不区分大小写)
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompetitorReportRequest 竞品报告请求参数
|
||||||
|
type CompetitorReportRequest struct {
|
||||||
|
Videos []string `json:"videos"` // 视频URL列表
|
||||||
|
Images []string `json:"images"` // 图片URL列表
|
||||||
|
TextPrompt string `json:"textPrompt"` // 竞品报告要求文本
|
||||||
|
ImagePrompt string `json:"imagePrompt"` // 图片URL
|
||||||
|
Model string `json:"model"` // 可选的模型名称,默认使用 qwen3-vl-plus
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompetitorReportResponse 竞品报告响应数据
|
||||||
|
type CompetitorReportResponse struct {
|
||||||
|
ImageURL string `json:"image_url,omitempty"` // 生成的图片URL(1024*1024),非必须返回
|
||||||
|
Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// AICompetitorReport 生成竞品报告接口
|
||||||
|
func AICompetitorReport(ctx *gin.Context) {
|
||||||
|
var req CompetitorReportRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(ctx, errors.New("参数错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TextPrompt == "" && req.ImagePrompt == "" {
|
||||||
|
service.Error(ctx, errors.New("文本和图片提示词不能同时为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否至少提供了视频或图片
|
||||||
|
if len(req.Videos) == 0 && len(req.Images) == 0 {
|
||||||
|
service.Error(ctx, errors.New("至少需要提供一个视频或图片"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Videos) > 1 {
|
||||||
|
service.Error(ctx, errors.New("当前只能选一个视频"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:调用AI理解视频/图片内容
|
||||||
|
vlPrompt := "请你详细描述这些视频或者这些图片中的内容分别是什么,请详细描述,不要遗漏任何细节"
|
||||||
|
vlResult, err := qwen.VL(req.Videos, req.Images, vlPrompt, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
// 检查是否是文件下载超时错误(内容过大)
|
||||||
|
errMsg := err.Error()
|
||||||
|
if contains(errMsg, "Download multimodal file timed out") || contains(errMsg, "timed out") {
|
||||||
|
service.Error(ctx, errors.New("内容过大,请重新选择"))
|
||||||
|
} else {
|
||||||
|
service.Error(ctx, fmt.Errorf("AI理解视频图片失败: %v", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取理解后的内容
|
||||||
|
if len(vlResult.Choices) == 0 {
|
||||||
|
service.Error(ctx, errors.New("AI理解返回结果为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vlContent := vlResult.Choices[0].Message.Content
|
||||||
|
|
||||||
|
// 定义协程结果结构
|
||||||
|
type textResult struct {
|
||||||
|
text string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type imageResult struct {
|
||||||
|
imageURL string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 TextPrompt 和 ImagePrompt 是否为空决定启动哪些协程
|
||||||
|
needText := req.TextPrompt != ""
|
||||||
|
needImage := req.ImagePrompt != ""
|
||||||
|
|
||||||
|
var textChan chan textResult
|
||||||
|
var imageChan chan imageResult
|
||||||
|
|
||||||
|
// 如果需要生成文本,启动文本生成协程
|
||||||
|
if needText {
|
||||||
|
textChan = make(chan textResult, 1)
|
||||||
|
go func() {
|
||||||
|
// 构建文本生成提示词:理解内容 + 用户要求
|
||||||
|
textPrompt := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告:注意不要输出markdown格式来进行排版,请直接输出纯文本。只需要回复竞品报告的内容,其他无关的内容不要输出,输出的内容第一行不要标题,直接输出竞品报告的正文即可\n我的要求是:\n%s", vlContent, req.TextPrompt)
|
||||||
|
|
||||||
|
chatReq, err := buildChatRequest(textPrompt, nil)
|
||||||
|
if err != nil {
|
||||||
|
textChan <- textResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatResp, err := qwen.Chat(*chatReq)
|
||||||
|
if err != nil {
|
||||||
|
textChan <- textResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chatResp.Choices) == 0 {
|
||||||
|
textChan <- textResult{err: errors.New("文本生成返回结果为空")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
textChan <- textResult{text: chatResp.Choices[0].Message.Content}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要生成图片,启动图片生成协程
|
||||||
|
if needImage {
|
||||||
|
imageChan = make(chan imageResult, 1)
|
||||||
|
go func() {
|
||||||
|
// 先请求聊天获取图片提示词
|
||||||
|
imagePromptText := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告图片的提示词:\n%s", vlContent, req.ImagePrompt)
|
||||||
|
|
||||||
|
chatReq, err := buildChatRequest(imagePromptText, nil)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatResp, err := qwen.Chat(*chatReq)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chatResp.Choices) == 0 {
|
||||||
|
imageChan <- imageResult{err: errors.New("图片提示词生成返回结果为空")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePrompt := chatResp.Choices[0].Message.Content
|
||||||
|
|
||||||
|
// 生成图片(1024*1024),基于理解后的内容使用文生图
|
||||||
|
size := "1024*1024"
|
||||||
|
resultTask, err := qwen.GenerateTextImage(imagePrompt, size)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultTask.Code != "" {
|
||||||
|
imageChan <- imageResult{err: errors.New("文生图失败: " + resultTask.Message)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待图片生成完成
|
||||||
|
result, err := qwen.ImgTaskResult(resultTask.Output.TaskID)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || len(result.Output.Results) == 0 {
|
||||||
|
imageChan <- imageResult{err: errors.New("图片生成失败")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一张图片的URL
|
||||||
|
imageChan <- imageResult{imageURL: result.Output.Results[0].URL}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有启动的协程完成
|
||||||
|
var textRes textResult
|
||||||
|
var imageRes imageResult
|
||||||
|
|
||||||
|
// 根据实际启动的协程数量等待结果
|
||||||
|
if needText && needImage {
|
||||||
|
// 两个协程都启动了,使用循环等待两个都完成
|
||||||
|
completed := 0
|
||||||
|
for completed < 2 {
|
||||||
|
select {
|
||||||
|
case textRes = <-textChan:
|
||||||
|
completed++
|
||||||
|
case imageRes = <-imageChan:
|
||||||
|
completed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if needText {
|
||||||
|
// 只启动文本生成协程
|
||||||
|
textRes = <-textChan
|
||||||
|
} else if needImage {
|
||||||
|
// 只启动图片生成协程
|
||||||
|
imageRes = <-imageChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文本结果(如果生成了文本)
|
||||||
|
if needText {
|
||||||
|
if textRes.err != nil {
|
||||||
|
service.Error(ctx, fmt.Errorf("生成竞品报告文本失败: %v", textRes.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片结果(如果生成了图片)
|
||||||
|
if needImage {
|
||||||
|
if imageRes.err != nil {
|
||||||
|
service.Error(ctx, fmt.Errorf("生成竞品报告图片失败: %v", imageRes.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果(只返回实际生成的内容)
|
||||||
|
result := CompetitorReportResponse{}
|
||||||
|
if needText {
|
||||||
|
result.Text = textRes.text
|
||||||
|
}
|
||||||
|
if needImage {
|
||||||
|
result.ImageURL = imageRes.imageURL
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Success(ctx, result)
|
||||||
|
}
|
||||||
@ -287,7 +287,7 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
|||||||
fmt.Println("res:", res)
|
fmt.Println("res:", res)
|
||||||
fmt.Println("err:", err)
|
fmt.Println("err:", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
|
service.Error(c, errors.New("驳回失败"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
service.Success(c, res)
|
service.Success(c, res)
|
||||||
@ -302,6 +302,11 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||||
|
service.Error(c, errors.New("套餐已过期"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
analysisInfoRes, err := service.CastProvider.GetWorkAnalysis(c, &cast.GetWorkAnalysisDetailReq{
|
analysisInfoRes, err := service.CastProvider.GetWorkAnalysis(c, &cast.GetWorkAnalysisDetailReq{
|
||||||
Uuid: req.Uuid,
|
Uuid: req.Uuid,
|
||||||
})
|
})
|
||||||
@ -313,6 +318,11 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
|||||||
service.Error(c, errors.New("数据分析不是待确认状态"))
|
service.Error(c, errors.New("数据分析不是待确认状态"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
artistID, _ := strconv.ParseUint(analysisInfoRes.ArtistID, 10, 64)
|
||||||
|
if artistID != uint64(userInfo.ID) {
|
||||||
|
service.Error(c, errors.New("非本人数据分析,无法操作"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var addBalanceReq bundle.AddBundleBalanceReq
|
var addBalanceReq bundle.AddBundleBalanceReq
|
||||||
addBalanceReq.UserId = int32(userInfo.ID)
|
addBalanceReq.UserId = int32(userInfo.ID)
|
||||||
@ -338,7 +348,7 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
|
|||||||
fmt.Println("res:", res)
|
fmt.Println("res:", res)
|
||||||
fmt.Println("err:", err)
|
fmt.Println("err:", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
|
service.Error(c, errors.New("确认失败"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 如果是艺人手动确认,确认操作后,自动标记为待阅读状态
|
// 如果是艺人手动确认,确认操作后,自动标记为待阅读状态
|
||||||
@ -457,7 +467,8 @@ func writeToExcel(filename string, items []*bundle.BundleBalanceExportItem) erro
|
|||||||
"当前可用套餐视频数", "当前可用增值视频数", "当前已用套餐视频数", "当前已用增值视频数", "当前作废套餐视频数", "当前作废增值视频数", "当月新增可用套餐视频数", "当月新增可用增值视频数", "当月使用套餐视频数", "当月使用增值视频数", "当月作废套餐视频数", "当月作废增值视频数",
|
"当前可用套餐视频数", "当前可用增值视频数", "当前已用套餐视频数", "当前已用增值视频数", "当前作废套餐视频数", "当前作废增值视频数", "当月新增可用套餐视频数", "当月新增可用增值视频数", "当月使用套餐视频数", "当月使用增值视频数", "当月作废套餐视频数", "当月作废增值视频数",
|
||||||
"当前可用套餐图文数", "当前可用增值图文数", "当前已用套餐图文数", "当前已用增值图文数", "当前作废套餐图文数", "当前作废增值图文数", "当月新增可用套餐图文数", "当月新增可用增值图文数", "当月使用套餐图文数", "当月使用增值图文数", "当月作废套餐图文数", "当月作废增值图文数",
|
"当前可用套餐图文数", "当前可用增值图文数", "当前已用套餐图文数", "当前已用增值图文数", "当前作废套餐图文数", "当前作废增值图文数", "当月新增可用套餐图文数", "当月新增可用增值图文数", "当月使用套餐图文数", "当月使用增值图文数", "当月作废套餐图文数", "当月作废增值图文数",
|
||||||
"当前可用套餐数据分析数", "当前可用增值数据分析数", "当前已用套餐数据分析数", "当前已用增值数据分析数", "当前作废套餐数据分析数", "当前作废增值数据分析数", "当月新增可用套餐数据分析数", "当月新增可用增值数据分析数", "当月使用套餐数据分析数", "当月使用增值数据分析数", "当月作废套餐数据分析数", "当月作废增值数据分析数",
|
"当前可用套餐数据分析数", "当前可用增值数据分析数", "当前已用套餐数据分析数", "当前已用增值数据分析数", "当前作废套餐数据分析数", "当前作废增值数据分析数", "当月新增可用套餐数据分析数", "当月新增可用增值数据分析数", "当月使用套餐数据分析数", "当月使用增值数据分析数", "当月作废套餐数据分析数", "当月作废增值数据分析数",
|
||||||
"当月手动扩展账号新增数", "当月手动扩展视频新增数", "当月手动扩展图文新增数", "当月手动扩展数据分析新增数", "当月新增手动扩展时长(天)", "当月手动扩展账号使用数", "当月手动扩展视频使用数", "当月手动扩展图文使用数", "当月手动扩展数据分析使用数",
|
"当前可用套餐竞品数", "当前可用增值竞品数", "当前已用套餐竞品数", "当前已用增值竞品数", "当前作废套餐竞品数", "当前作废增值竞品数", "当月新增可用套餐竞品数", "当月新增可用增值竞品数", "当月使用套餐竞品数", "当月使用增值竞品数", "当月作废套餐竞品数", "当月作废增值竞品数",
|
||||||
|
"当月手动扩展账号新增数", "当月手动扩展视频新增数", "当月手动扩展图文新增数", "当月手动扩展数据分析新增数", "当月新增手动扩展时长(天)", "当月手动扩展账号使用数", "当月手动扩展视频使用数", "当月手动扩展图文使用数", "当月手动扩展数据分析使用数", "当月手动扩展竞品数", "当月手动扩展竞品使用数",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写表头
|
// 写表头
|
||||||
@ -555,21 +566,37 @@ func writeToExcel(filename string, items []*bundle.BundleBalanceExportItem) erro
|
|||||||
_ = write(55, int(it.MonthlyInvalidBundleDataAnalysisNumber))
|
_ = write(55, int(it.MonthlyInvalidBundleDataAnalysisNumber))
|
||||||
_ = write(56, int(it.MonthlyInvalidIncreaseDataAnalysisNumber))
|
_ = write(56, int(it.MonthlyInvalidIncreaseDataAnalysisNumber))
|
||||||
|
|
||||||
|
// 竞品数
|
||||||
|
_ = write(57, int(it.MonthlyBundleCompetitiveNumber))
|
||||||
|
_ = write(58, int(it.MonthlyIncreaseCompetitiveNumber))
|
||||||
|
_ = write(59, int(it.BundleCompetitiveConsumptionNumber))
|
||||||
|
_ = write(60, int(it.IncreaseCompetitiveConsumptionNumber))
|
||||||
|
_ = write(61, int(it.InvalidBundleCompetitiveNumber))
|
||||||
|
_ = write(62, int(it.InvalidIncreaseCompetitiveNumber))
|
||||||
|
_ = write(63, int(it.MonthlyNewBundleCompetitiveNumber))
|
||||||
|
_ = write(64, int(it.MonthlyNewIncreaseCompetitiveNumber))
|
||||||
|
_ = write(65, int(it.MonthlyBundleCompetitiveConsumptionNumber))
|
||||||
|
_ = write(66, int(it.MonthlyIncreaseCompetitiveConsumptionNumber))
|
||||||
|
_ = write(67, int(it.MonthlyInvalidBundleCompetitiveNumber))
|
||||||
|
_ = write(68, int(it.MonthlyInvalidIncreaseCompetitiveNumber))
|
||||||
|
|
||||||
// 手动扩展类
|
// 手动扩展类
|
||||||
_ = write(57, int(it.MonthlyNewManualAccountNumber))
|
_ = write(69, int(it.MonthlyNewManualAccountNumber))
|
||||||
_ = write(58, int(it.MonthlyNewManualVideoNumber))
|
_ = write(70, int(it.MonthlyNewManualVideoNumber))
|
||||||
_ = write(59, int(it.MonthlyNewManualImageNumber))
|
_ = write(71, int(it.MonthlyNewManualImageNumber))
|
||||||
_ = write(60, int(it.MonthlyNewManualDataAnalysisNumber))
|
_ = write(72, int(it.MonthlyNewManualDataAnalysisNumber))
|
||||||
_ = write(61, int(it.MonthlyNewDurationNumber))
|
_ = write(73, int(it.MonthlyNewDurationNumber))
|
||||||
_ = write(62, int(it.MonthlyManualAccountConsumptionNumber))
|
_ = write(74, int(it.MonthlyManualAccountConsumptionNumber))
|
||||||
_ = write(63, int(it.MonthlyManualVideoConsumptionNumber))
|
_ = write(75, int(it.MonthlyManualVideoConsumptionNumber))
|
||||||
_ = write(64, int(it.MonthlyManualImageConsumptionNumber))
|
_ = write(76, int(it.MonthlyManualImageConsumptionNumber))
|
||||||
_ = write(65, int(it.MonthlyManualDataAnalysisConsumptionNumber))
|
_ = write(77, int(it.MonthlyManualDataAnalysisConsumptionNumber))
|
||||||
|
_ = write(78, int(it.MonthlyNewManualCompetitiveNumber))
|
||||||
|
_ = write(79, int(it.MonthlyManualCompetitiveConsumptionNumber))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选:设置列宽,使表格更美观
|
// 可选:设置列宽,使表格更美观
|
||||||
_ = f.SetColWidth(sheet, "A", "AZ", 15)
|
_ = f.SetColWidth(sheet, "A", "BZ", 15)
|
||||||
|
|
||||||
// 保存文件
|
// 保存文件
|
||||||
if err := f.SaveAs(filename); err != nil {
|
if err := f.SaveAs(filename); err != nil {
|
||||||
@ -609,6 +636,8 @@ func GetAccountBundleBalance(c *gin.Context) {
|
|||||||
ImageConsumptionNumber: item.BundleImageConsumptionNumber + item.IncreaseImageConsumptionNumber + item.ManualImageNumber,
|
ImageConsumptionNumber: item.BundleImageConsumptionNumber + item.IncreaseImageConsumptionNumber + item.ManualImageNumber,
|
||||||
DataAnalysisNumber: item.BundleDataAnalysisNumber + item.IncreaseDataAnalysisNumber + item.ManualDataAnalysisNumber,
|
DataAnalysisNumber: item.BundleDataAnalysisNumber + item.IncreaseDataAnalysisNumber + item.ManualDataAnalysisNumber,
|
||||||
DataAnalysisConsumptionNumber: item.BundleDataAnalysisConsumptionNumber + item.IncreaseDataAnalysisConsumptionNumber + item.ManualDataAnalysisNumber,
|
DataAnalysisConsumptionNumber: item.BundleDataAnalysisConsumptionNumber + item.IncreaseDataAnalysisConsumptionNumber + item.ManualDataAnalysisNumber,
|
||||||
|
CompetitiveNumber: item.BundleCompetitiveNumber + item.IncreaseCompetitiveNumber + item.ManualCompetitiveNumber,
|
||||||
|
CompetitiveConsumptionNumber: item.BundleCompetitiveConsumptionNumber + item.IncreaseCompetitiveConsumptionNumber + item.ManualCompetitiveConsumptionNumber,
|
||||||
Bought: item.Bought,
|
Bought: item.Bought,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package cast
|
package cast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -13,6 +13,7 @@ import (
|
|||||||
modelCast "fonchain-fiee/pkg/model/cast"
|
modelCast "fonchain-fiee/pkg/model/cast"
|
||||||
"fonchain-fiee/pkg/model/login"
|
"fonchain-fiee/pkg/model/login"
|
||||||
"fonchain-fiee/pkg/service"
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/service/bundle/common"
|
||||||
"fonchain-fiee/pkg/utils"
|
"fonchain-fiee/pkg/utils"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@ -143,6 +144,41 @@ func ListWorkAnalysis(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListWorkAnalysis 获取作品分析列表
|
||||||
|
func ListWorkAnalysisForApp(ctx *gin.Context) {
|
||||||
|
var req *cast.ListWorkAnalysisReq
|
||||||
|
var err error
|
||||||
|
if err = ctx.ShouldBind(&req); err != nil {
|
||||||
|
service.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newCtx := NewCtxWithUserInfo(ctx)
|
||||||
|
loginInfo := login.GetUserInfoFromC(ctx)
|
||||||
|
// 查询用户套餐有没有过期
|
||||||
|
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
|
||||||
|
UserId: int32(loginInfo.ID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("ListWorkAnalysisForApp GetBundleBalanceByUserId", zap.Any("err", err))
|
||||||
|
service.Error(ctx, errors.New(common.GetUserBalanceFailed))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 套餐未过期的话,传入 subNum ,只获取该套餐的有效期内数据
|
||||||
|
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
|
||||||
|
zap.L().Info("ListWorkAnalysisForApp BundleNotExpired", zap.Any("loginInfo", loginInfo))
|
||||||
|
req.SubNum = loginInfo.SubNum
|
||||||
|
}
|
||||||
|
resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("ListWorkAnalysisForApp ListWorkAnalysis", zap.Any("err", err))
|
||||||
|
service.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// RefreshWorkAnalysisApproval(ctx, resp.Data)
|
||||||
|
service.Success(ctx, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshWorkAnalysisApproval 刷新作品分析审批状态
|
// RefreshWorkAnalysisApproval 刷新作品分析审批状态
|
||||||
func RefreshWorkAnalysisApproval(ctx *gin.Context, data []*cast.WorkAnalysisInfo) {
|
func RefreshWorkAnalysisApproval(ctx *gin.Context, data []*cast.WorkAnalysisInfo) {
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
@ -538,6 +574,12 @@ func CheckBundleBalance(ctx *gin.Context) {
|
|||||||
service.Error(ctx, err)
|
service.Error(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case modelCast.BalanceTypeCompetitiveValue:
|
||||||
|
if resp.CompetitiveExtendNumber-resp.CompetitiveExtendConsumptionNumber <= 0 {
|
||||||
|
err = errors.New(e.ErrorBalanceInsufficient)
|
||||||
|
service.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service.Success(ctx, resp)
|
service.Success(ctx, resp)
|
||||||
@ -603,6 +645,48 @@ func autoConfirmAnalysis(ctx context.Context, analysisUuid string) (err error) {
|
|||||||
isFailed = true
|
isFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if balanceInfoRes.BundleStatus == common.BundleExpired {
|
||||||
|
confirmRemark = "套餐已过期"
|
||||||
|
// 直接提交
|
||||||
|
_, err = service.CastProvider.UpdateWorkAnalysisStatus(context.Background(), &cast.UpdateWorkAnalysisStatusReq{
|
||||||
|
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||||
|
Uuid: analysisUuid,
|
||||||
|
ConfirmRemark: confirmRemark,
|
||||||
|
ConfirmStatus: 3,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("autoConfirmAnalysis UpdateWorkAnalysisStatus", zap.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 判断数据分析的提交时间是否在现在的套餐期间范围之内
|
||||||
|
submitTime, err := time.Parse("2006-01-02 15:04:05", infoResp.SubmitTime)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("autoConfirmAnalysis ParseSubmitTime", zap.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if submitTime.Before(time.Unix(balanceInfoRes.PayTime, 0)) {
|
||||||
|
// todo 暂时先这样
|
||||||
|
// confirmRemark = "该报告提交时间不在该套餐期间范围之内"
|
||||||
|
// 直接提交
|
||||||
|
confirmRemark = "系统自动确认"
|
||||||
|
usedType = 0
|
||||||
|
_, err = service.CastProvider.UpdateWorkAnalysisStatus(context.Background(), &cast.UpdateWorkAnalysisStatusReq{
|
||||||
|
WorkAction: cast.WorkActionENUM_CONFIRM,
|
||||||
|
Uuid: analysisUuid,
|
||||||
|
ConfirmRemark: confirmRemark,
|
||||||
|
CostType: usedType,
|
||||||
|
ConfirmStatus: 1,
|
||||||
|
ConfirmType: 2,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("autoConfirmAnalysis UpdateWorkAnalysisStatus", zap.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var addBalanceReq bundle.AddBundleBalanceReq
|
var addBalanceReq bundle.AddBundleBalanceReq
|
||||||
addBalanceReq.UserId = int32(userID)
|
addBalanceReq.UserId = int32(userID)
|
||||||
// 检查数据分析余量
|
// 检查数据分析余量
|
||||||
|
|||||||
1131
pkg/service/cast/report.go
Normal file
1131
pkg/service/cast/report.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -419,6 +419,12 @@ func CheckUserBundleBalance(userID int32, balanceType modelCast.BalanceTypeEnum)
|
|||||||
// err = errors.New(e.ErrorBalanceInsufficient)
|
// err = errors.New(e.ErrorBalanceInsufficient)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case modelCast.BalanceTypeCompetitiveValue:
|
||||||
|
if resp.CompetitiveExtendNumber-resp.CompetitiveExtendConsumptionNumber <= 0 {
|
||||||
|
err = errors.New("该艺人竞品数可用次数为0")
|
||||||
|
// err = errors.New(e.ErrorBalanceInsufficient)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1799,3 +1805,24 @@ func checkAndReuploadImage(imageUrl string, mediaType string) (string, error) {
|
|||||||
}
|
}
|
||||||
return compressUrl, nil
|
return compressUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkListPublished 获取已发布的作品列表
|
||||||
|
func WorkListPublished(ctx *gin.Context) {
|
||||||
|
var (
|
||||||
|
req *cast.WorkListPublishedReq
|
||||||
|
resp *cast.WorkListPublishedResp
|
||||||
|
)
|
||||||
|
var err error
|
||||||
|
if err = ctx.ShouldBind(&req); err != nil {
|
||||||
|
service.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newCtx := NewCtxWithUserInfo(ctx)
|
||||||
|
resp, err = service.CastProvider.WorkListPublished(newCtx, req)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(ctx, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@ -22,23 +22,6 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPendingTaskList 查询待指派任务记录
|
|
||||||
func GetPendingTaskList(c *gin.Context) {
|
|
||||||
var req bundle.TaskQueryRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := service.BundleProvider.GetPendingTaskList(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Success(c, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPendingTaskLayout(c *gin.Context) {
|
func GetPendingTaskLayout(c *gin.Context) {
|
||||||
res, err := service.BundleProvider.GetPendingTaskLayout(context.Background(), &bundle.GetPendingTaskLayoutReq{})
|
res, err := service.BundleProvider.GetPendingTaskLayout(context.Background(), &bundle.GetPendingTaskLayoutReq{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,26 +127,6 @@ func BatchTerminateTask(c *gin.Context) {
|
|||||||
service.Success(c, res)
|
service.Success(c, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePendingCount 修改待发数量
|
|
||||||
func UpdatePendingCount(c *gin.Context) {
|
|
||||||
var req bundle.UpdatePendingCountRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userInfo := login.GetUserInfoFromC(c)
|
|
||||||
req.OperatorNum = userInfo.TelNum
|
|
||||||
req.Operator = userInfo.Name
|
|
||||||
|
|
||||||
res, err := service.BundleProvider.UpdatePendingCount(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Success(c, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRecentAssignRecords 查询最近被指派记录
|
// GetRecentAssignRecords 查询最近被指派记录
|
||||||
func GetRecentAssignRecords(c *gin.Context) {
|
func GetRecentAssignRecords(c *gin.Context) {
|
||||||
var req bundle.RecentAssignRecordsRequest
|
var req bundle.RecentAssignRecordsRequest
|
||||||
@ -238,23 +201,6 @@ func CompleteTaskManually(c *gin.Context) {
|
|||||||
service.Success(c, res)
|
service.Success(c, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTaskProgress 员工实际完成任务状态更新
|
|
||||||
func UpdateTaskProgress(c *gin.Context) {
|
|
||||||
var req bundle.UpdateTaskProgressRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := service.BundleProvider.UpdateTaskProgress(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Success(c, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTaskAssignRecordsList 多条件查询操作记录表
|
// GetTaskAssignRecordsList 多条件查询操作记录表
|
||||||
func GetTaskAssignRecordsList(c *gin.Context) {
|
func GetTaskAssignRecordsList(c *gin.Context) {
|
||||||
var req bundle.TaskAssignRecordsQueryRequest
|
var req bundle.TaskAssignRecordsQueryRequest
|
||||||
@ -532,23 +478,6 @@ func UpdateWorkVideoWithUUID(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArtistBundleBalance 查询艺人套餐剩余数量
|
|
||||||
func GetArtistBundleBalance(c *gin.Context) {
|
|
||||||
var req bundle.ArtistBundleBalanceRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := service.BundleProvider.GetArtistBundleBalance(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Success(c, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArtistUploadStatsList 查询艺人待上传列表
|
// GetArtistUploadStatsList 查询艺人待上传列表
|
||||||
func GetArtistUploadStatsList(c *gin.Context) {
|
func GetArtistUploadStatsList(c *gin.Context) {
|
||||||
var req bundle.TaskQueryRequest
|
var req bundle.TaskQueryRequest
|
||||||
@ -566,22 +495,6 @@ func GetArtistUploadStatsList(c *gin.Context) {
|
|||||||
service.Success(c, res)
|
service.Success(c, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPendingUploadBreakdown(c *gin.Context) {
|
|
||||||
var req bundle.PendingUploadBreakdownRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := service.BundleProvider.GetPendingUploadBreakdown(context.Background(), &req)
|
|
||||||
if err != nil {
|
|
||||||
service.Error(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service.Success(c, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArtistUploadStatsListDownload 导出艺人待上传列表为Excel
|
// GetArtistUploadStatsListDownload 导出艺人待上传列表为Excel
|
||||||
func GetArtistUploadStatsListDownload(c *gin.Context) {
|
func GetArtistUploadStatsListDownload(c *gin.Context) {
|
||||||
var req bundle.TaskQueryRequest
|
var req bundle.TaskQueryRequest
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -50,3 +51,50 @@ func SaveUrlFileDisk(url string, path string, filename string) (fullPath string,
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRemoteFileSize 通过HTTP HEAD请求获取远程文件大小(不下载文件)
|
||||||
|
func GetRemoteFileSize(url string) (size int64, err error) {
|
||||||
|
// 创建HEAD请求
|
||||||
|
req, err := http.NewRequest("HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("GetRemoteFileSize create request err", zap.String("url", url), zap.Error(err))
|
||||||
|
err = errors.New(e.GetMsg(e.ERROR_DOWNLOAD_FILE))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("GetRemoteFileSize request err", zap.String("url", url), zap.Error(err))
|
||||||
|
err = errors.New(e.GetMsg(e.ERROR_DOWNLOAD_FILE))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 检查HTTP状态码
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
zap.L().Error("GetRemoteFileSize status code err", zap.String("url", url), zap.Int("status", resp.StatusCode))
|
||||||
|
err = errors.New(e.GetMsg(e.ERROR_DOWNLOAD_FILE))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取Content-Length头部
|
||||||
|
contentLength := resp.Header.Get("Content-Length")
|
||||||
|
if contentLength == "" {
|
||||||
|
zap.L().Error("GetRemoteFileSize Content-Length header not found", zap.String("url", url))
|
||||||
|
err = errors.New("无法获取文件大小")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析文件大小
|
||||||
|
size, err = strconv.ParseInt(contentLength, 10, 64)
|
||||||
|
size = size / 1024 / 1024
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("GetRemoteFileSize parse size err", zap.String("url", url), zap.String("contentLength", contentLength), zap.Error(err))
|
||||||
|
err = errors.New("解析文件大小失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
174
pkg/utils/pdf.go
Normal file
174
pkg/utils/pdf.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/phpdave11/gofpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cleanTextForPDF 清理文本,移除PDF不支持的字符(如emoji)
|
||||||
|
// gofpdf库不支持某些特殊字符
|
||||||
|
func cleanTextForPDF(text string) string {
|
||||||
|
var result []rune
|
||||||
|
for _, r := range text {
|
||||||
|
// 保留基本多文种平面(BMP)内的字符(码点 <= 0xFFFF)
|
||||||
|
// 这样可以保留中文、英文、数字等常用字符,但过滤掉emoji等特殊字符
|
||||||
|
if r <= 0xFFFF && (unicode.IsPrint(r) || unicode.IsSpace(r)) {
|
||||||
|
result = append(result, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadChineseFont 加载中文字体
|
||||||
|
func loadChineseFont(pdf *gofpdf.Fpdf, fontPath string) error {
|
||||||
|
var fontData []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// 如果提供了本地字体路径,优先使用本地字体
|
||||||
|
if fontPath == "" {
|
||||||
|
return errors.New("字体文件路径不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
fontData, err = os.ReadFile(fontPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取字体文件失败: %v", err)
|
||||||
|
}
|
||||||
|
// 使用本地字体文件
|
||||||
|
pdf.AddUTF8FontFromBytes("Chinese", "", fontData)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePDF 生成PDF文件
|
||||||
|
func GeneratePDF(text, imageURL, outputPath, fontPath string) error {
|
||||||
|
if text == "" {
|
||||||
|
return errors.New("文本不能为空")
|
||||||
|
}
|
||||||
|
// 创建PDF实例,P=纵向,mm=毫米单位,A4=页面大小
|
||||||
|
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||||
|
|
||||||
|
// 加载中文字体
|
||||||
|
err := loadChineseFont(pdf, fontPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加载中文字体失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新页面
|
||||||
|
pdf.AddPage()
|
||||||
|
|
||||||
|
// 设置字体,使用中文字体,12号字体
|
||||||
|
pdf.SetFont("Chinese", "", 12)
|
||||||
|
|
||||||
|
// 设置页面边距(左、上、右)
|
||||||
|
pdf.SetMargins(20, 10, 20)
|
||||||
|
|
||||||
|
// 设置当前位置(x, y),从左上角开始
|
||||||
|
pdf.SetXY(20, 10)
|
||||||
|
|
||||||
|
// 清理文本,移除PDF不支持的字符(如emoji)
|
||||||
|
cleanedText := cleanTextForPDF(text)
|
||||||
|
|
||||||
|
// 添加文本内容
|
||||||
|
// 使用MultiCell方法处理多行文本,支持自动换行
|
||||||
|
// 参数:宽度、行高、文本内容、边框、对齐方式、是否填充
|
||||||
|
// A4页面宽度210mm,减去左右边距40mm,可用宽度170mm
|
||||||
|
textWidth := 170.0
|
||||||
|
lineHeight := 7.0
|
||||||
|
pdf.MultiCell(textWidth, lineHeight, cleanedText, "", "L", false)
|
||||||
|
|
||||||
|
// 如果提供了图片URL,则添加图片
|
||||||
|
if imageURL != "" {
|
||||||
|
// 添加一些间距
|
||||||
|
pdf.Ln(5)
|
||||||
|
|
||||||
|
// 解析URL获取文件扩展名
|
||||||
|
u, err := url.Parse(imageURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("图片链接解析错误: %v", err)
|
||||||
|
}
|
||||||
|
fileExt := filepath.Ext(u.Path)
|
||||||
|
// 如果没有扩展名,默认使用.jpg
|
||||||
|
if fileExt == "" {
|
||||||
|
fileExt = ".jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载图片
|
||||||
|
resp, err := http.Get(imageURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("下载图片失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取图片数据
|
||||||
|
imageData, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取图片数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将图片数据保存到临时文件(gofpdf需要文件路径)
|
||||||
|
tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建临时文件失败: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name()) // 使用完后删除临时文件
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
// 写入图片数据到临时文件
|
||||||
|
_, err = tmpFile.Write(imageData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("写入临时文件失败: %v", err)
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
// A4纵向页面宽度210mm,减去左右边距40mm,可用宽度170mm
|
||||||
|
// 图片宽度设为可用宽度的70%
|
||||||
|
imageWidth := textWidth * 0.7
|
||||||
|
// 计算居中位置:页面宽度210mm,图片居中
|
||||||
|
imageX := (210.0 - imageWidth) / 2
|
||||||
|
currentY := pdf.GetY()
|
||||||
|
|
||||||
|
// 注册图片并获取原始尺寸,用于计算缩放后的高度
|
||||||
|
imgInfo := pdf.RegisterImageOptions(tmpFile.Name(), gofpdf.ImageOptions{})
|
||||||
|
if imgInfo == nil {
|
||||||
|
return fmt.Errorf("注册图片失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算缩放后的图片高度(按比例缩放)
|
||||||
|
// 原始宽度:原始高度 = 缩放后宽度:缩放后高度
|
||||||
|
originalWidth, originalHeight := imgInfo.Extent()
|
||||||
|
imageHeight := (imageWidth / originalWidth) * originalHeight
|
||||||
|
|
||||||
|
// A4页面高度297mm,底部边距10mm,计算可用的最大Y坐标
|
||||||
|
pageHeight := 297.0
|
||||||
|
bottomMargin := 10.0
|
||||||
|
maxY := pageHeight - bottomMargin
|
||||||
|
|
||||||
|
// 检查当前页面剩余空间是否足够放下图片
|
||||||
|
// 如果图片底部会超出页面可用区域,则添加新页面
|
||||||
|
if currentY+imageHeight > maxY {
|
||||||
|
pdf.AddPage()
|
||||||
|
// 新页面从顶部边距开始
|
||||||
|
currentY = 10.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加图片
|
||||||
|
// ImageOptions参数:图片路径、x坐标、y坐标、宽度、高度、是否流式布局、选项、链接
|
||||||
|
// 高度设为0表示按比例自动计算
|
||||||
|
pdf.ImageOptions(tmpFile.Name(), imageX, currentY, imageWidth, 0, false, gofpdf.ImageOptions{}, 0, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成并保存PDF文件
|
||||||
|
err = pdf.OutputFileAndClose(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("生成PDF失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user