Merge branch 'fix-download-daiyb' into feature-userinfo-daiyb

This commit is contained in:
戴育兵 2025-11-14 20:30:40 +08:00
commit a6798a2997
15 changed files with 6391 additions and 2575 deletions

File diff suppressed because it is too large Load Diff

View File

@ -589,3 +589,72 @@ func (this *GetBundleBalanceLayoutReq) Validate() error {
func (this *GetBundleBalanceLayoutResp) Validate() error {
return nil
}
func (this *MetricsBusinessReq) Validate() error {
return nil
}
func (this *MetricsBusinessResp) Validate() error {
return nil
}
func (this *MetricsOperatingCreateReq) Validate() error {
return nil
}
func (this *MetricsOperatingCreateResp) Validate() error {
return nil
}
func (this *MetricsOperatingStatusReq) Validate() error {
return nil
}
func (this *MetricsOperatingStatusResp) Validate() error {
return nil
}
func (this *MetricsBundlePurchaseExportReq) Validate() error {
return nil
}
func (this *MetricsBundlePurchaseExportResp) Validate() error {
for _, item := range this.Data {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Data", err)
}
}
}
return nil
}
func (this *MetricsBundlePurchaseItem) Validate() error {
return nil
}
func (this *MetricsArtistAccountExportReq) Validate() error {
return nil
}
func (this *MetricsArtistAccountExportResp) Validate() error {
for _, item := range this.Data {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Data", err)
}
}
}
return nil
}
func (this *MetricsArtistAccountExportItem) Validate() error {
return nil
}
func (this *MetricsVideoSubmitExportReq) Validate() error {
return nil
}
func (this *MetricsVideoSubmitExportResp) Validate() error {
for _, item := range this.Data {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Data", err)
}
}
}
return nil
}
func (this *MetricsVideoSubmitExportItem) Validate() error {
return nil
}
func (this *MetricsBalanceDetailExportReq) Validate() error {
return nil
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-triple. DO NOT EDIT.
// versions:
// - protoc-gen-go-triple v1.0.8
// - protoc v3.12.4
// - protoc-gen-go-triple v1.0.5
// - protoc v5.26.0
// source: pb/bundle.proto
package bundle
@ -97,6 +97,12 @@ type BundleClient interface {
UpdateTaskProgress(ctx context.Context, in *UpdateTaskProgressRequest, opts ...grpc_go.CallOption) (*CommonResponse, common.ErrorWithAttachment)
GetTaskAssignRecordsList(ctx context.Context, in *TaskAssignRecordsQueryRequest, opts ...grpc_go.CallOption) (*TaskAssignRecordsQueryResponse, common.ErrorWithAttachment)
GetArtistBundleBalance(ctx context.Context, in *ArtistBundleBalanceRequest, opts ...grpc_go.CallOption) (*ArtistBundleBalanceResponse, common.ErrorWithAttachment)
MetricsBusiness(ctx context.Context, in *MetricsBusinessReq, opts ...grpc_go.CallOption) (*MetricsBusinessResp, common.ErrorWithAttachment)
MetricsOperatingCreate(ctx context.Context, in *MetricsOperatingCreateReq, opts ...grpc_go.CallOption) (*MetricsOperatingCreateResp, common.ErrorWithAttachment)
MetricsOperatingStatus(ctx context.Context, in *MetricsOperatingStatusReq, opts ...grpc_go.CallOption) (*MetricsOperatingStatusResp, common.ErrorWithAttachment)
MetricsBundlePurchaseExport(ctx context.Context, in *MetricsBundlePurchaseExportReq, opts ...grpc_go.CallOption) (*MetricsBundlePurchaseExportResp, common.ErrorWithAttachment)
MetricsArtistAccountExport(ctx context.Context, in *MetricsArtistAccountExportReq, opts ...grpc_go.CallOption) (*MetricsArtistAccountExportResp, common.ErrorWithAttachment)
MetricsVideoSubmitExport(ctx context.Context, in *MetricsVideoSubmitExportReq, opts ...grpc_go.CallOption) (*MetricsVideoSubmitExportResp, common.ErrorWithAttachment)
}
type bundleClient struct {
@ -166,6 +172,12 @@ type BundleClientImpl struct {
UpdateTaskProgress func(ctx context.Context, in *UpdateTaskProgressRequest) (*CommonResponse, error)
GetTaskAssignRecordsList func(ctx context.Context, in *TaskAssignRecordsQueryRequest) (*TaskAssignRecordsQueryResponse, error)
GetArtistBundleBalance func(ctx context.Context, in *ArtistBundleBalanceRequest) (*ArtistBundleBalanceResponse, error)
MetricsBusiness func(ctx context.Context, in *MetricsBusinessReq) (*MetricsBusinessResp, error)
MetricsOperatingCreate func(ctx context.Context, in *MetricsOperatingCreateReq) (*MetricsOperatingCreateResp, error)
MetricsOperatingStatus func(ctx context.Context, in *MetricsOperatingStatusReq) (*MetricsOperatingStatusResp, error)
MetricsBundlePurchaseExport func(ctx context.Context, in *MetricsBundlePurchaseExportReq) (*MetricsBundlePurchaseExportResp, error)
MetricsArtistAccountExport func(ctx context.Context, in *MetricsArtistAccountExportReq) (*MetricsArtistAccountExportResp, error)
MetricsVideoSubmitExport func(ctx context.Context, in *MetricsVideoSubmitExportReq) (*MetricsVideoSubmitExportResp, error)
}
func (c *BundleClientImpl) GetDubboStub(cc *triple.TripleConn) BundleClient {
@ -552,6 +564,42 @@ func (c *bundleClient) GetArtistBundleBalance(ctx context.Context, in *ArtistBun
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/GetArtistBundleBalance", in, out)
}
func (c *bundleClient) MetricsBusiness(ctx context.Context, in *MetricsBusinessReq, opts ...grpc_go.CallOption) (*MetricsBusinessResp, common.ErrorWithAttachment) {
out := new(MetricsBusinessResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsBusiness", in, out)
}
func (c *bundleClient) MetricsOperatingCreate(ctx context.Context, in *MetricsOperatingCreateReq, opts ...grpc_go.CallOption) (*MetricsOperatingCreateResp, common.ErrorWithAttachment) {
out := new(MetricsOperatingCreateResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsOperatingCreate", in, out)
}
func (c *bundleClient) MetricsOperatingStatus(ctx context.Context, in *MetricsOperatingStatusReq, opts ...grpc_go.CallOption) (*MetricsOperatingStatusResp, common.ErrorWithAttachment) {
out := new(MetricsOperatingStatusResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsOperatingStatus", in, out)
}
func (c *bundleClient) MetricsBundlePurchaseExport(ctx context.Context, in *MetricsBundlePurchaseExportReq, opts ...grpc_go.CallOption) (*MetricsBundlePurchaseExportResp, common.ErrorWithAttachment) {
out := new(MetricsBundlePurchaseExportResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsBundlePurchaseExport", in, out)
}
func (c *bundleClient) MetricsArtistAccountExport(ctx context.Context, in *MetricsArtistAccountExportReq, opts ...grpc_go.CallOption) (*MetricsArtistAccountExportResp, common.ErrorWithAttachment) {
out := new(MetricsArtistAccountExportResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsArtistAccountExport", in, out)
}
func (c *bundleClient) MetricsVideoSubmitExport(ctx context.Context, in *MetricsVideoSubmitExportReq, opts ...grpc_go.CallOption) (*MetricsVideoSubmitExportResp, common.ErrorWithAttachment) {
out := new(MetricsVideoSubmitExportResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/MetricsVideoSubmitExport", in, out)
}
// BundleServer is the server API for Bundle service.
// All implementations must embed UnimplementedBundleServer
// for forward compatibility
@ -625,6 +673,12 @@ type BundleServer interface {
UpdateTaskProgress(context.Context, *UpdateTaskProgressRequest) (*CommonResponse, error)
GetTaskAssignRecordsList(context.Context, *TaskAssignRecordsQueryRequest) (*TaskAssignRecordsQueryResponse, error)
GetArtistBundleBalance(context.Context, *ArtistBundleBalanceRequest) (*ArtistBundleBalanceResponse, error)
MetricsBusiness(context.Context, *MetricsBusinessReq) (*MetricsBusinessResp, error)
MetricsOperatingCreate(context.Context, *MetricsOperatingCreateReq) (*MetricsOperatingCreateResp, error)
MetricsOperatingStatus(context.Context, *MetricsOperatingStatusReq) (*MetricsOperatingStatusResp, error)
MetricsBundlePurchaseExport(context.Context, *MetricsBundlePurchaseExportReq) (*MetricsBundlePurchaseExportResp, error)
MetricsArtistAccountExport(context.Context, *MetricsArtistAccountExportReq) (*MetricsArtistAccountExportResp, error)
MetricsVideoSubmitExport(context.Context, *MetricsVideoSubmitExportReq) (*MetricsVideoSubmitExportResp, error)
mustEmbedUnimplementedBundleServer()
}
@ -819,6 +873,24 @@ func (UnimplementedBundleServer) GetTaskAssignRecordsList(context.Context, *Task
func (UnimplementedBundleServer) GetArtistBundleBalance(context.Context, *ArtistBundleBalanceRequest) (*ArtistBundleBalanceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetArtistBundleBalance not implemented")
}
func (UnimplementedBundleServer) MetricsBusiness(context.Context, *MetricsBusinessReq) (*MetricsBusinessResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsBusiness not implemented")
}
func (UnimplementedBundleServer) MetricsOperatingCreate(context.Context, *MetricsOperatingCreateReq) (*MetricsOperatingCreateResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsOperatingCreate not implemented")
}
func (UnimplementedBundleServer) MetricsOperatingStatus(context.Context, *MetricsOperatingStatusReq) (*MetricsOperatingStatusResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsOperatingStatus not implemented")
}
func (UnimplementedBundleServer) MetricsBundlePurchaseExport(context.Context, *MetricsBundlePurchaseExportReq) (*MetricsBundlePurchaseExportResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsBundlePurchaseExport not implemented")
}
func (UnimplementedBundleServer) MetricsArtistAccountExport(context.Context, *MetricsArtistAccountExportReq) (*MetricsArtistAccountExportResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsArtistAccountExport not implemented")
}
func (UnimplementedBundleServer) MetricsVideoSubmitExport(context.Context, *MetricsVideoSubmitExportReq) (*MetricsVideoSubmitExportResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method MetricsVideoSubmitExport not implemented")
}
func (s *UnimplementedBundleServer) XXX_SetProxyImpl(impl protocol.Invoker) {
s.proxyImpl = impl
}
@ -2645,6 +2717,180 @@ func _Bundle_GetArtistBundleBalance_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler)
}
func _Bundle_MetricsBusiness_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsBusinessReq)
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("MetricsBusiness", 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 _Bundle_MetricsOperatingCreate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsOperatingCreateReq)
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("MetricsOperatingCreate", 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 _Bundle_MetricsOperatingStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsOperatingStatusReq)
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("MetricsOperatingStatus", 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 _Bundle_MetricsBundlePurchaseExport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsBundlePurchaseExportReq)
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("MetricsBundlePurchaseExport", 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 _Bundle_MetricsArtistAccountExport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsArtistAccountExportReq)
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("MetricsArtistAccountExport", 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 _Bundle_MetricsVideoSubmitExport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(MetricsVideoSubmitExportReq)
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("MetricsVideoSubmitExport", 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)
}
// Bundle_ServiceDesc is the grpc_go.ServiceDesc for Bundle service.
// It's only intended for direct use with grpc_go.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -2900,6 +3146,30 @@ var Bundle_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "GetArtistBundleBalance",
Handler: _Bundle_GetArtistBundleBalance_Handler,
},
{
MethodName: "MetricsBusiness",
Handler: _Bundle_MetricsBusiness_Handler,
},
{
MethodName: "MetricsOperatingCreate",
Handler: _Bundle_MetricsOperatingCreate_Handler,
},
{
MethodName: "MetricsOperatingStatus",
Handler: _Bundle_MetricsOperatingStatus_Handler,
},
{
MethodName: "MetricsBundlePurchaseExport",
Handler: _Bundle_MetricsBundlePurchaseExport_Handler,
},
{
MethodName: "MetricsArtistAccountExport",
Handler: _Bundle_MetricsArtistAccountExport_Handler,
},
{
MethodName: "MetricsVideoSubmitExport",
Handler: _Bundle_MetricsVideoSubmitExport_Handler,
},
},
Streams: []grpc_go.StreamDesc{},
Metadata: "pb/bundle.proto",

4
go.mod
View File

@ -79,7 +79,7 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/statsd_exporter v0.21.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/robfig/cron/v3 v3.0.1
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/shirou/gopsutil v3.20.11+incompatible // indirect
github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
@ -104,6 +104,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/PuerkitoBio/goquery v1.8.1
github.com/disintegration/imaging v1.6.2
github.com/duke-git/lancet/v2 v2.3.8
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/fonchain/utils/voice v0.0.0-00010101000000-000000000000
github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000
@ -113,6 +114,7 @@ require (
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/samber/lo v1.52.0
github.com/shopspring/decimal v1.4.0
github.com/signintech/gopdf v0.29.2
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/viper v1.7.1

4
go.sum
View File

@ -208,6 +208,8 @@ github.com/dubbogo/net v0.0.4/go.mod h1:1CGOnM7X3he+qgGNqjeADuE5vKZQx/eMSeUkpU3u
github.com/dubbogo/triple v1.0.9/go.mod h1:1t9me4j4CTvNDcsMZy6/OGarbRyAUSY0tFXGXHCp7Iw=
github.com/dubbogo/triple v1.1.8 h1:yE+J3W1NTZCEPa1FoX+VWZH6UF1c0+A2MGfERlU2zbI=
github.com/dubbogo/triple v1.1.8/go.mod h1:9pgEahtmsY/avYJp3dzUQE8CMMVe1NtGBmUhfICKLJk=
github.com/duke-git/lancet/v2 v2.3.8 h1:dlkqn6Nj2LRWFuObNxttkMHxrFeaV6T26JR8jbEVbPg=
github.com/duke-git/lancet/v2 v2.3.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -789,6 +791,8 @@ github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v3 v3.21.6 h1:vU7jrp1Ic/2sHB7w6UNs7MIkn7ebVtTb5D9j45o9VYE=
github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/signintech/gopdf v0.29.2 h1:ksvYuHNwEBP8Mi/4q5MN1ZdW9OpMbWn3pEn3ewiWoSc=
github.com/signintech/gopdf v0.29.2/go.mod h1:d23eO35GpEliSrF22eJ4bsM3wVeQJTjXTHq5x5qGKjA=

View File

@ -1,12 +1,11 @@
package cast
import (
"fmt"
"fonchain-fiee/api/cast"
modelCast "fonchain-fiee/pkg/model/cast"
"strings"
"github.com/360EntSecGroup-Skylar/excelize"
"github.com/xuri/excelize/v2"
)
type Work struct {
@ -17,43 +16,60 @@ func (w *Work) ExportExcelWorkList(data []*cast.WorkListResp_Info) (*excelize.Fi
sheetName := "Sheet1"
f.SetSheetName("Sheet1", sheetName)
headers := []string{
"艺人", "艺人手机号", "作品标题", "作品类型", "类型", "发布平台", "提交时间", "作品状态", "发布账号", "管理人",
}
for i, h := range headers {
cell := fmt.Sprintf("%s%d", string(rune('A'+i)), 1)
f.SetCellValue(sheetName, cell, h)
// 使用 StreamWriter
sw, err := f.NewStreamWriter(sheetName)
if err != nil {
return nil, err
}
// 表头
headers := []interface{}{
"艺人", "艺人手机号", "作品标题", "作品类型", "类型", "发布平台", "提交时间", "作品状态", "发布账号", "管理人",
}
if err := sw.SetRow("A1", headers); err != nil {
return nil, err
}
// 内容
rowIndex := 2
for _, info := range data {
var platformNames string
for _, v := range info.PlatformIDs {
platformNames += modelCast.PlatformIDMM[int(v)] + "/"
// 拼接字段
platformNames := strings.Join(func() []string {
arr := make([]string, 0, len(info.PlatformIDs))
for _, v := range info.PlatformIDs {
arr = append(arr, modelCast.PlatformIDMM[int(v)])
}
return arr
}(), "/")
mediaAccountNames := strings.Join(info.MediaAccountNames, "/")
managerNames := strings.Join(info.ManagerUserNames, "/")
row := []interface{}{
info.ArtistName,
info.ArtistPhone,
info.Title,
modelCast.WorkCategoryMM[int(info.WorkCategory)],
modelCast.WorkCostTypeMM[int(info.CostType)],
platformNames,
info.SubmitTime,
modelCast.WorkStatusMM[int(info.WorkStatus)],
mediaAccountNames,
managerNames,
}
platformNames = strings.Trim(platformNames, "/")
var ManagerUserNames string
for _, v := range info.ManagerUserNames {
ManagerUserNames += v + "/"
cell, _ := excelize.CoordinatesToCellName(1, rowIndex)
if err := sw.SetRow(cell, row); err != nil {
return nil, err
}
ManagerUserNames = strings.Trim(ManagerUserNames, "/")
var mediaAccountNames string
for _, v := range info.MediaAccountNames {
mediaAccountNames += v + "/"
}
mediaAccountNames = strings.Trim(mediaAccountNames, "/")
f.SetCellValue(sheetName, fmt.Sprintf("A%d", rowIndex), info.ArtistName)
f.SetCellValue(sheetName, fmt.Sprintf("B%d", rowIndex), info.ArtistPhone)
f.SetCellValue(sheetName, fmt.Sprintf("C%d", rowIndex), info.Title)
f.SetCellValue(sheetName, fmt.Sprintf("D%d", rowIndex), modelCast.WorkCategoryMM[int(info.WorkCategory)])
f.SetCellValue(sheetName, fmt.Sprintf("E%d", rowIndex), modelCast.WorkCostTypeMM[int(info.CostType)])
f.SetCellValue(sheetName, fmt.Sprintf("F%d", rowIndex), platformNames)
f.SetCellValue(sheetName, fmt.Sprintf("G%d", rowIndex), info.SubmitTime)
f.SetCellValue(sheetName, fmt.Sprintf("H%d", rowIndex), modelCast.WorkStatusMM[int(info.WorkStatus)])
f.SetCellValue(sheetName, fmt.Sprintf("I%d", rowIndex), mediaAccountNames)
f.SetCellValue(sheetName, fmt.Sprintf("J%d", rowIndex), ManagerUserNames)
rowIndex++
}
// 结束流写入
if err := sw.Flush(); err != nil {
return nil, err
}
return f, nil
}

View File

@ -41,5 +41,5 @@ var WorkStatusMM = map[int]string{
6: "发布成功",
7: "发布失败",
8: "未知",
9: "验确认",
9: "验确认",
}

View File

@ -39,6 +39,17 @@ func BundleRouter(r *gin.RouterGroup) {
bundleBalance.POST("layout-update", bundle.SetBalanceLayout)
bundleBalance.POST("layout", bundle.GetBalanceLayout)
}
metrics := bundleClientRoute.Group("metrics")
{
metrics.POST("business", bundle.MetricsBusiness)
metrics.POST("operator-create", bundle.MetricsOpratorCreate)
metrics.POST("operator-status", bundle.MetricsOpratorStatus)
metrics.POST("export/bundle-purchase", bundle.MetricsBundlePurchaseExport)
metrics.POST("export/artist-account", bundle.MetricsArtistAccountExport)
metrics.POST("export/video-submit", bundle.MetricsVideoSubmitExport)
metrics.POST("export/balance-detail", bundle.MetricsBalanceDetailExport)
metrics.POST("export/balance-metrics", bundle.BalanceMetricsExport)
}
}
bundleClientRouteV2 := bundleRoute.Group("system/v2")
{

View File

@ -195,6 +195,7 @@ func NewRouter() *gin.Engine {
importRoute.POST("data/publish", imports.ImportPublish)
importRoute.POST("data/publish2", imports.ImportPublishV2)
importRoute.POST("data/publish3", imports.ImportPublishV3)
importRoute.POST("data/publish4", imports.ImportPublishV4)
importRoute.POST("data/confirm", imports.WorkConfirm)
}
//静态文件

View File

@ -462,6 +462,7 @@ func GetAccountBundleBalance(c *gin.Context) {
service.Error(c, err)
return
}
req.Month = time.Now().Format("2006-01")
res, err := service.BundleProvider.GetBundleBalanceList(context.Background(), &req)
if err != nil {
service.Error(c, err)

View File

@ -0,0 +1,585 @@
package bundle
import (
"context"
"fmt"
"fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast"
logicCast "fonchain-fiee/pkg/logic/cast"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service"
serviceCast "fonchain-fiee/pkg/service/cast"
"fonchain-fiee/pkg/utils"
"reflect"
"strings"
"time"
"github.com/duke-git/lancet/v2/datetime"
"github.com/shopspring/decimal"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/xuri/excelize/v2"
)
func MetricsBusiness(ctx *gin.Context) {
var req bundle.MetricsBusinessReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.MetricsBusiness(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
}
func MetricsOpratorCreate(ctx *gin.Context) {
var req bundle.MetricsOperatingCreateReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.MetricsOperatingCreate(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
}
func MetricsOpratorStatus(ctx *gin.Context) {
var req bundle.MetricsOperatingStatusReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.MetricsOperatingStatus(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
}
func MetricsBundlePurchaseExport(ctx *gin.Context) {
var req bundle.MetricsBundlePurchaseExportReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.MetricsBundlePurchaseExport(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
userInfo := login.GetUserInfoFromC(ctx)
exportFileName := "套餐购买数据" + req.Month + ".xlsx"
filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
var statistic = func(data []*bundle.MetricsBundlePurchaseItem, headers []string, f *excelize.File) {
sheet := f.GetSheetName(f.GetActiveSheetIndex())
// 设置“小计”
endRow := len(data) + 3
sumPayment := decimal.Zero
sumFinal := decimal.Zero
sumFee := decimal.Zero
for _, i := range data {
sumPayment = sumPayment.Add(decimal.NewFromFloat(float64(i.PaymentAmount)))
sumFinal = sumFinal.Add(decimal.NewFromFloat(float64(i.FinalAmount)))
sumFee = sumFee.Add(decimal.NewFromFloat(float64(i.FeeAmount)))
}
f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow), "合计支付金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow), "合计结算金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow), "合计手续费金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow+1), "$"+sumPayment.StringFixed(2))
f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow+1), "$"+sumFinal.StringFixed(2))
f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow+1), "$"+sumFee.StringFixed(2))
// 创建黑色边框样式
borderStyle, err := f.NewStyle(&excelize.Style{
Border: []excelize.Border{
{Type: "left", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
},
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
if err != nil {
fmt.Println("创建边框样式失败:", err)
return
}
// 应用样式到合计区域(包括标题行和数值行)
startCell := fmt.Sprintf("A%d", endRow)
endCell := fmt.Sprintf("C%d", endRow+1)
if err := f.SetCellStyle(sheet, startCell, endCell, borderStyle); err != nil {
fmt.Println("设置边框样式失败:", err)
}
_ = f.SetColWidth(sheet, "A", "N", 25)
}
if err := exportStructToExcel(resp.Data, []string{
"订单编号", "套餐", "用户编号", "客户姓名", "手机号", "支付时间", "套餐视频数", "增值视频数", "套餐金额", "增值金额", "支付金额", "结算金额", "手续费", "汇率(%",
}, filePath, statistic); err != nil {
service.Error(ctx, err)
return
}
var scheme string
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
} else {
scheme = "http"
}
var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
service.Success(ctx, gin.H{
"url": exportUrl,
})
}
func MetricsArtistAccountExport(ctx *gin.Context) {
var req bundle.MetricsArtistAccountExportReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.MetricsArtistAccountExport(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
userInfo := login.GetUserInfoFromC(ctx)
exportFileName := "艺人账号" + req.Month + ".xlsx"
filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
sheet := "Sheet1"
f := excelize.NewFile()
headers := []string{"序号", "艺人", "用户编号", "DM账号", "DM账号昵称", "Instagram账号", "Instagram账号昵称", "TikTok账号", "TikTok账号昵称"}
for i, h := range headers {
col, _ := excelize.ColumnNumberToName(i + 1)
cell := col + "1"
if err := f.SetCellValue(sheet, cell, h); err != nil {
break
}
}
var scheme string
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
} else {
scheme = "http"
}
for r, it := range resp.Data {
row := r + 2
// 逐列写入(注意顺序必须和 headers 一致)
write := func(colIdx int, v interface{}) error {
col, _ := excelize.ColumnNumberToName(colIdx)
cell := fmt.Sprintf("%s%d", col, row)
return f.SetCellValue(sheet, cell, v)
}
_ = write(1, r+1)
_ = write(2, it.ArtistName)
_ = write(3, it.UserNum)
_ = write(4, it.DmAccount)
_ = write(5, it.DmNickname)
_ = write(6, it.InstagramAccount)
_ = write(7, it.InstagramNickname)
_ = write(8, it.TiktokAccount)
_ = write(9, it.TiktokNickname)
}
// 可选:设置列宽,使表格更美观
_ = f.SetColWidth(sheet, "A", "AZ", 15)
// 保存文件
if err := f.SaveAs(filePath); err != nil {
service.Error(ctx, err)
return
}
var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
service.Success(ctx, gin.H{
"url": exportUrl,
})
}
func MetricsVideoSubmitExport(ctx *gin.Context) {
var req bundle.MetricsVideoSubmitExportReq
var (
resp *cast.WorkListResp
)
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
newCtx := serviceCast.NewCtxWithUserInfo(ctx)
t, err := time.Parse("2006-01", req.Month)
if err == nil {
resp, err = service.CastProvider.WorkList(newCtx, &cast.WorkListReq{
SubmitStartTime: datetime.BeginOfMonth(t).Format(time.DateTime),
SubmitEndTime: datetime.EndOfMonth(t).Format(time.DateTime),
Page: 1,
PageSize: 99999,
})
fmt.Printf("resp.Count: %v\n", resp.Count)
} else {
resp, err = service.CastProvider.WorkList(newCtx, &cast.WorkListReq{
Page: 1,
PageSize: 99999,
})
fmt.Printf("resp.Count: %v\n", resp.Count)
}
if err != nil {
service.Error(ctx, err)
return
}
var loigcCastWork = new(logicCast.Work)
excelFile, err := loigcCastWork.ExportExcelWorkList(resp.Data)
if err != nil {
service.Error(ctx, err)
return
}
userInfo := login.GetUserInfoFromC(ctx)
exportFileName := "视频上传数据" + req.Month + ".xlsx"
filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
// 保存文件
if err := excelFile.SaveAs(filePath); err != nil {
service.Error(ctx, err)
return
}
var scheme string
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
} else {
scheme = "http"
}
var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
service.Success(ctx, gin.H{
"url": exportUrl,
})
}
// func MetricsVideoSubmitExport(ctx *gin.Context) {
// var req bundle.MetricsVideoSubmitExportReq
// if err := ctx.ShouldBindJSON(&req); err != nil {
// service.Error(ctx, err)
// return
// }
// resp, err := service.BundleProvider.MetricsVideoSubmitExport(ctx, &req)
// if err != nil {
// service.Error(ctx, err)
// return
// }
// userInfo := login.GetUserInfoFromC(ctx)
// exportFileName := "视频上传数据.xlsx"
// filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
// utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
// sheet := "Sheet1"
// f := excelize.NewFile()
// headers := []string{"序号", "画家", "用户编号", "视频标题", "DM发布时间", "Instagram发布时间", "TikTok发布时间"}
// for i, h := range headers {
// col, _ := excelize.ColumnNumberToName(i + 1)
// cell := col + "1"
// if err := f.SetCellValue(sheet, cell, h); err != nil {
// break
// }
// }
// var scheme string
// if ctx.GetHeader("X-Forwarded-Proto") == "https" {
// scheme = "https"
// } else {
// scheme = "http"
// }
// for r, it := range resp.Data {
// row := r + 2
// // 逐列写入(注意顺序必须和 headers 一致)
// write := func(colIdx int, v interface{}) error {
// col, _ := excelize.ColumnNumberToName(colIdx)
// cell := fmt.Sprintf("%s%d", col, row)
// return f.SetCellValue(sheet, cell, v)
// }
// _ = write(1, r+1)
// _ = write(2, it.ArtistName)
// _ = write(3, it.UserNum)
// _ = write(4, it.VideoTitle)
// _ = write(5, tsToStr(it.DmUploadTime, "2006/01/02"))
// _ = write(6, tsToStr(it.InstagramUploadTime, "2006/01/02"))
// _ = write(7, tsToStr(it.TiktokUploadTime, "2006/01/02"))
// }
// // 可选:设置列宽,使表格更美观
// _ = f.SetColWidth(sheet, "A", "AZ", 15)
// // 保存文件
// if err := f.SaveAs(filePath); err != nil {
// service.Error(ctx, err)
// return
// }
// var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
// service.Success(ctx, gin.H{
// "url": exportUrl,
// })
// }
func MetricsBalanceDetailExport(ctx *gin.Context) {
var req bundle.BundleBalanceExportReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.BundleProvider.BundleBalanceExport(ctx, &req)
if err != nil {
service.Error(ctx, err)
return
}
type itemStruct struct {
Month string
CustomerNum string
UserName string
UserPhoneNumber string
PayTime string
BundleAmount float32
IncreaseAmount float32
TotalPayAmount float32
Currency string
Fee string
BundleVideoNumber int32
IncreaseVideoNumber int32
BundleVideoUnitPrice float32
IncreaseVideoUnitPrice float32
MonthlyNewBundleVideoNumber int32
MonthlyNewIncreaseVideoNumber int32
BundleVideoConsumptionNumber int32
IncreaseVideoConsumptionNumber int32
BundleVideoUsedPrice float32
IncreaseVideoUsedPrice float32
}
items := lo.Map(resp.Data, func(item *bundle.BundleBalanceExportItem, _ int) itemStruct {
payTime, _ := time.Parse(time.DateTime, item.PayTime)
return itemStruct{
Month: item.Month,
CustomerNum: item.CustomerNum,
UserName: item.UserName,
UserPhoneNumber: item.UserPhoneNumber,
PayTime: payTime.Format("2006/01/02"),
BundleAmount: item.BundleAmount,
IncreaseAmount: item.IncreaseAmount,
TotalPayAmount: item.TotalPayAmount,
Currency: "美元",
Fee: "$" + item.Fee,
BundleVideoNumber: item.BundleVideoNumber,
IncreaseVideoNumber: item.IncreaseVideoNumber,
BundleVideoUnitPrice: item.BundleVideoUnitPrice,
IncreaseVideoUnitPrice: item.IncreaseVideoUnitPrice,
MonthlyNewBundleVideoNumber: item.MonthlyBundleVideoNumber,
MonthlyNewIncreaseVideoNumber: item.MonthlyIncreaseVideoNumber,
BundleVideoConsumptionNumber: item.BundleVideoConsumptionNumber,
IncreaseVideoConsumptionNumber: item.IncreaseVideoConsumptionNumber,
BundleVideoUsedPrice: float32(item.BundleVideoConsumptionNumber) * item.BundleVideoUnitPrice,
IncreaseVideoUsedPrice: float32(item.IncreaseVideoConsumptionNumber) * item.IncreaseVideoUnitPrice,
}
})
var statistic = func(data []itemStruct, headers []string, f *excelize.File) {
sheet := f.GetSheetName(f.GetActiveSheetIndex())
// 设置“小计”
endRow := len(data) + 1
sumBundle := decimal.Zero
sumIncrease := decimal.Zero
sumTotal := decimal.Zero
for _, i := range items {
sumBundle = sumBundle.Add(decimal.NewFromFloat(float64(i.BundleVideoUsedPrice)))
sumIncrease = sumIncrease.Add(decimal.NewFromFloat(float64(i.IncreaseVideoUsedPrice)))
}
sumTotal = sumBundle.Add(sumIncrease)
f.SetCellValue(sheet, fmt.Sprintf("R%d", endRow+1), "小计")
f.SetCellValue(sheet, fmt.Sprintf("S%d", endRow+1), fmt.Sprintf("$%s", sumBundle.StringFixed(2)))
f.SetCellValue(sheet, fmt.Sprintf("T%d", endRow+1), fmt.Sprintf("$%s", sumIncrease.StringFixed(2)))
f.MergeCell(sheet, fmt.Sprintf("S%d", endRow+2), fmt.Sprintf("T%d", endRow+2))
f.SetCellValue(sheet, fmt.Sprintf("R%d", endRow+2), "合计")
f.SetCellValue(sheet, fmt.Sprintf("T%d", endRow+2), fmt.Sprintf("$%s", sumTotal.StringFixed(2)))
// 设置样式(加粗、边框)
boldStyle, _ := f.NewStyle(&excelize.Style{
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#FFFF00"}, // 黄色
Pattern: 1, // 实心填充
},
Font: &excelize.Font{Bold: true},
Border: []excelize.Border{
{Type: "left", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
},
})
f.SetCellStyle(sheet, fmt.Sprintf("R%d", endRow+1), fmt.Sprintf("T%d", endRow+2), boldStyle)
}
userInfo := login.GetUserInfoFromC(ctx)
exportFileName := "视频发布明细" + req.Month + ".xlsx"
filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
if err := exportStructToExcel(items, []string{
"所属月份", "用户编号", "姓名", "手机号", "购买套餐时间", "套餐金额", "增值金额", "支付金额", "币种", "手续费", "套餐视频总数",
"增值视频总数", "套餐视频单价", "增值视频单价", "当前需要上传套餐视频数", "当前需要上传增值视频数",
"当前已上传套餐视频数", "当前已上传增值视频数", "当前套餐视频已消费总金额", "当前增值视频已消费总金额",
}, filePath, yelloStyle, statistic); err != nil {
service.Error(ctx, err)
return
}
var scheme string
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
} else {
scheme = "http"
}
var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
service.Success(ctx, gin.H{
"url": exportUrl,
})
}
func yelloStyle[T any](_ []T, headers []string, f *excelize.File) {
style, err := f.NewStyle(&excelize.Style{
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#FFFF00"}, // 黄色
Pattern: 1, // 实心填充
},
Font: &excelize.Font{
Bold: true,
},
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
})
if err != nil {
fmt.Println("创建样式失败:", err)
return
}
sheet := f.GetSheetName(f.GetActiveSheetIndex())
endCell, _ := excelize.CoordinatesToCellName(len(headers), 1)
// 应用样式到第一行
f.SetCellStyle(sheet, "A1", endCell, style)
}
func BalanceMetricsExport(ctx *gin.Context) {
var req bundle.BundleBalanceExportReq
if err := ctx.ShouldBindJSON(&req); err != nil {
service.Error(ctx, err)
return
}
userInfo := login.GetUserInfoFromC(ctx)
exportFileName := "服务使用明细数据" + req.Month + ".xlsx"
filePath := fmt.Sprintf("./runtime/%d/%s", userInfo.ID, exportFileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(userInfo.ID), true)
res, err := service.BundleProvider.BundleBalanceExport(context.Background(), &req)
if err != nil {
service.Error(ctx, err)
return
}
if err := writeToExcel(filePath, res.Data); err != nil {
service.Error(ctx, err)
return
}
var scheme string
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
} else {
scheme = "http"
}
var exportUrl string = fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
service.Success(ctx, gin.H{
"url": exportUrl,
})
}
func exportStructToExcel[T any](data []T, headers []string, filename string, fns ...func(data []T, headers []string, f *excelize.File)) error {
f := excelize.NewFile()
sheet := f.GetSheetName(f.GetActiveSheetIndex())
// 写入表头
for i, h := range headers {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
f.SetCellValue(sheet, cell, h)
}
if len(data) != 0 {
// 获取结构体字段顺序
v := reflect.ValueOf(data[0])
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
// 过滤出导出字段
exportedFields := make([]int, 0)
for i := 0; i < t.NumField(); i++ {
if t.Field(i).IsExported() { // Go 1.17+ 新增的方法
exportedFields = append(exportedFields, i)
}
}
// 写入数据
for rowIdx, item := range data {
val := reflect.ValueOf(item)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for colIdx, fieldIdx := range exportedFields {
field := val.Field(fieldIdx)
var cellValue any
switch field.Kind() {
case reflect.Float32, reflect.Float64:
cellValue = fmt.Sprintf("$%.2f", field.Float()) // 保留两位小数
default:
cellValue = field.Interface()
}
cell, _ := excelize.CoordinatesToCellName(colIdx+1, rowIdx+2)
f.SetCellValue(sheet, cell, cellValue)
}
}
}
for _, fn := range fns {
fn(data, headers, f)
}
// 保存文件
return f.SaveAs(filename)
}
func tsToStr(ts int64, layout string) string {
if ts == 0 {
return ""
}
t := time.Unix(ts, 0).In(time.Local)
return t.Format(layout)
}

View File

@ -46,16 +46,17 @@ func ValidateUserStatus(userInfo login.Info) error {
// 校验用户是否已有套餐
func CheckUserOrder(userID uint64) error {
req := bundle.OrderRecordsRequest{CustomerID: strconv.FormatUint(userID, 10)}
records, err := service.BundleProvider.OrderRecordsList(context.Background(), &req)
if err != nil {
return err
}
for _, order := range records.OrderRecords {
if order.CustomerID == strconv.FormatUint(userID, 10) && order.Status == 1 {
req := bundle.OrderRecordsDetailRequest{CustomerID: strconv.FormatUint(userID, 10)}
records, _ := service.BundleProvider.OrderRecordsDetail(context.Background(), &req)
//if err != nil {
// return err
//}
// 校验用户是否已有订单
if records.OrderRecord != nil {
if records.OrderRecord.CustomerID == strconv.FormatUint(userID, 10) && records.OrderRecord.Status == 1 {
return errors.New(common.ThereAreOutstandingOrders)
}
if order.CustomerID == strconv.FormatUint(userID, 10) && order.ExpirationTime > time.Now().Format("2006-01-02 15:04:05") {
if records.OrderRecord.CustomerID == strconv.FormatUint(userID, 10) && records.OrderRecord.ExpirationTime > time.Now().Format("2006-01-02 15:04:05") {
return errors.New(common.HadOrder)
}
}

View File

@ -252,14 +252,16 @@ func AutoCreateUserAndOrder(c *gin.Context) {
reportUuid := ""
accountUuid := ""
durationUuid := ""
bundleVideoUuid := ""
if config.AppConfig.System.AppMode == "prod" {
BundleName = "全球尊享版"
BundleUuid = "ac4c99c2951c2fcdbf417928d321554d"
videoUuid = "a29a1fa2862b2cdda1377b19066c8eb7"
textAndImagesUuid = "dfba176a40ae2d23aa4ef9b30b646bc8"
reportUuid = "1727557a85c92957a3e3332d18c713aa"
accountUuid = "e1cc219e4f682b3d8cb85929e540a0de"
durationUuid = "f002449ac57a2e71b0673da938c0354e"
videoUuid = "355aae784d77280197c92ff56733459d" // 增值视频
textAndImagesUuid = "41a7753d210d22f8972dc273ff1360c4" // 套餐图文
reportUuid = "069497de55852c24a3b0f702c1250900" // 套餐数据
accountUuid = "1e04078d2a8824d18be1c281bc3167a8" // 套餐账号
durationUuid = "e3ad8f15aa022b12afe47170c9051db9" // 套餐时长
bundleVideoUuid = "fdbef018707e2a8ebc82a22e257abaff" // 套餐视频
} else {
BundleName = "测试导入全球尊享版"
BundleUuid = "5e84f86cb7f92a4ab785271e4a383aa5"
@ -304,7 +306,21 @@ func AutoCreateUserAndOrder(c *gin.Context) {
f64, err := strconv.ParseFloat(unfinishInfo.OrderPayAmount, 32)
TotalPrice = float32(f64)
addRecords = append(addRecords,
&bundle.OrderCreateAddRecord{
&bundle.OrderCreateAddRecord{ // 套餐视频
ServiceType: 1,
ValueUid: bundleVideoUuid,
CurrencyType: 2, //美元
Amount: 0, //增值服务金额
Num: 24,
Unit: "个",
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 2,
},
&bundle.OrderCreateAddRecord{ //视频增值
ServiceType: 1,
ValueUid: videoUuid,
CurrencyType: 2, //美元
@ -314,6 +330,8 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 2,
QuotaType: 1,
}, &bundle.OrderCreateAddRecord{ //图文
ServiceType: 2,
ValueUid: textAndImagesUuid,
@ -324,6 +342,9 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 10,
}, &bundle.OrderCreateAddRecord{ //数据报表
ServiceType: 3,
ValueUid: reportUuid,
@ -334,6 +355,9 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 1,
}, &bundle.OrderCreateAddRecord{ //账号数
ServiceType: 4,
ValueUid: accountUuid,
@ -344,16 +368,20 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 1,
}, &bundle.OrderCreateAddRecord{ //可用时长
ServiceType: 5,
ValueUid: durationUuid,
CurrencyType: 2, //美元
Amount: 0, //增值服务金额
Num: 10,
Num: 1,
Unit: "年",
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 1,
},
)
// 当前 未将 签名 写入合同中 todo 金额和有效时间待修改

View File

@ -15,15 +15,17 @@ type ArtistAccount struct {
Account map[apiCast.PlatformIDENUM]AccountInfo `json:"account"`
}
type ArtistMedia struct {
Id string `json:"id"`
Name string `json:"name"`
SubNum string `json:"subNum"`
Title string `json:"title"`
Img string `json:"img"`
Video string `json:"video"`
Youtube string `json:"youtube"`
Instagram string `json:"instagram"`
TikTok string `json:"tiktok"`
Id string `json:"id"`
Name string `json:"name"`
SubNum string `json:"subNum"`
Title string `json:"title"`
Img string `json:"img"`
Video string `json:"video"`
Youtube string `json:"youtube"`
Instagram string `json:"instagram"`
TikTok string `json:"tiktok"`
PinyinName string `json:"column:pinyin_name"`
PinyinFileName string `json:"column:pinyin_file_name"`
}
type FailedRecord struct {

View File

@ -0,0 +1,428 @@
package imports
import (
"context"
"fmt"
"fonchain-fiee/api/accountFiee"
apiCast "fonchain-fiee/api/cast"
"fonchain-fiee/api/files"
"fonchain-fiee/cmd/config"
"fonchain-fiee/pkg/model"
modelCast "fonchain-fiee/pkg/model/cast"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/cast"
"fonchain-fiee/pkg/service/upload"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/mholt/archiver"
"github.com/xuri/excelize/v2"
)
func ImportPublishV4(c *gin.Context) {
excelFile, err := c.FormFile("excel")
if err != nil {
c.JSON(400, gin.H{"error": "缺少 Excel 文件 excel"})
return
}
zipFile, err := c.FormFile("zip")
if err != nil {
c.JSON(400, gin.H{"error": "缺少 ZIP 文件"})
return
}
tempDir := "tmp"
os.MkdirAll(tempDir, 0755)
excelPath := filepath.Join(tempDir, "artists.xlsx")
zipPath := filepath.Join(tempDir, "archive.zip")
fmt.Println("before save excel...")
now := time.Now()
if err = c.SaveUploadedFile(excelFile, excelPath); err != nil {
c.JSON(500, gin.H{"error": "保存 Excel 失败"})
return
}
fmt.Println("save excel success", time.Since(now))
if err = c.SaveUploadedFile(zipFile, zipPath); err != nil {
c.JSON(500, gin.H{"error": "保存 ZIP 文件失败"})
return
}
fmt.Println("save zip success", time.Since(now))
// 3. 解压 ZIP
unzipPath := filepath.Join(tempDir, "unzipped")
if _, err = os.Stat(unzipPath); err == nil {
// 路径已存在,删除
if removeErr := os.RemoveAll(unzipPath); removeErr != nil {
c.JSON(500, gin.H{"error": "清理已存在解压目录失败: " + removeErr.Error()})
return
}
}
fmt.Println("开始解压...")
os.MkdirAll(unzipPath, 0755)
if err = archiver.Unarchive(zipPath, unzipPath); err != nil {
c.JSON(500, gin.H{"error": "解压 ZIP 失败: " + err.Error()})
return
}
entries, err := os.ReadDir(unzipPath)
if err != nil || len(entries) == 0 {
c.JSON(500, gin.H{"error": "读取解压目录失败或目录为空"})
return
}
// 处理解压后的文件名
if len(entries) == 1 && entries[0].IsDir() {
// 说明解压后多了一层目录,把它设为新的 unzipPath
unzipPath = filepath.Join(unzipPath, entries[0].Name())
}
defer os.RemoveAll(tempDir)
//------------------------------------------------- 4. 读取 Excel 画家名单, 匹配视频和图片
artists, err := readArtistVideoInfoV4(excelPath, unzipPath)
if err != nil {
c.JSON(500, gin.H{"error": "读取 Excel 失败"})
return
}
//-----------------------------------------------------------------获取用户信息
var failedRecords []FailedRecord
var artistResp []ArtistVideoDetail
fmt.Println("artists num: ", len(artists))
for _, artist := range artists {
var infoResp *accountFiee.UserInfoResponse
list, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{
Name: artist.Name,
SubNum: artist.SubNum,
})
if err != nil {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()),
})
log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error()))
continue
}
if len(list.UserList) == 0 {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("未找到用户信息: %s", artist.Name),
})
log.Printf(fmt.Sprintf("未找到用户信息: %s", artist.Name))
continue
}
if list != nil && len(list.UserList) > 0 {
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: list.UserList[0].Id,
Domain: "app",
})
if err != nil {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("获取用户信息失败: %s", err.Error()),
})
log.Printf(fmt.Sprintf("获取用户信息失败: %s", err.Error()))
continue
}
}
//--------------------------------------------------检查用户视频数量
if err = cast.CheckUserBundleBalance(int32(list.UserList[0].Id), modelCast.BalanceTypeVideoValue); err != nil {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("检查用户视频可消耗数量: %s", err.Error()),
})
log.Printf(fmt.Sprintf("检查用户视频可消耗数量: %s", err.Error()))
continue
}
//-----------------------------------------------------获取自媒体账号
accountList, err := service.CastProvider.MediaUserList(c, &apiCast.MediaUserListReq{
ArtistUuid: strconv.FormatUint(list.UserList[0].Id, 10),
ArtistVal: artist.Name,
Page: 1,
PageSize: 10,
})
if err != nil {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("自媒体账号数量获取失败: %s,账号数量:%d", err.Error(), len(accountList.Data)),
})
log.Printf(fmt.Sprintf("自媒体账号数量获取失败: %s,账号数量:%d", err.Error(), len(accountList.Data)))
continue
}
if accountList == nil || len(accountList.Data) == 0 {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: "自媒体账号数量为0",
})
log.Printf(fmt.Sprintf("自媒体账号,账号数量:%d", len(accountList.Data)))
continue
}
mediaAccountUuids := []string{}
mediaAccountNames := []string{}
platformIDs := []apiCast.PlatformIDENUM{}
for _, info := range accountList.Data {
if info.PlatformID == 2 && ((artist.Id == "31" && info.ArtistName == "荣小松") ||
(artist.Id == "72" && info.ArtistName == "韩风霞")) {
continue // 跳过
}
mediaAccountUuids = append(mediaAccountUuids, info.MediaAccountUuid)
mediaAccountNames = append(mediaAccountNames, info.PlatformUserName)
platformIDs = append(platformIDs, apiCast.PlatformIDENUM(info.PlatformID))
}
//---------------------------------------------------发布
newCtx := cast.NewCtxWithUserInfo(c)
resp, err := service.CastProvider.UpdateWorkVideo(newCtx, &apiCast.UpdateWorkVideoReq{
Title: artist.Title,
Content: artist.Title,
VideoUrl: artist.Video,
CoverUrl: artist.Img,
AutoPublish: apiCast.AutoPublishENUM_AutoPublish_FALSE,
MediaAccountUuids: mediaAccountUuids,
MediaAccountNames: mediaAccountNames,
PlatformIDs: platformIDs,
PublishConfig1: &apiCast.PublishConfig{
CanComment: 1,
CanJoin: 1,
CanQuote: 1,
ForbidComment: 2,
IsAI: 1,
PublicType: 1,
},
PublishConfig2: &apiCast.PublishConfig{
CanComment: 1,
CanJoin: 1,
CanQuote: 1,
ForbidComment: 2,
IsAI: 1,
PublicType: 1,
},
PublishConfig3: &apiCast.PublishConfig{
CanComment: 1,
CanJoin: 1,
CanQuote: 1,
ForbidComment: 1,
IsAI: 1,
PublicType: 1,
},
Action: "submit",
ArtistUuid: strconv.FormatUint(list.UserList[0].Id, 10),
ArtistName: infoResp.Name,
ArtistPhone: infoResp.TelNum,
ArtistPhoneAreaCode: infoResp.TelAreaCode,
Source: 2,
})
if err != nil {
failedRecords = append(failedRecords, FailedRecord{
Name: artist.Name,
Msg: fmt.Sprintf("发布"+artist.Name+"视频"+artist.Title+"失败: %s", err.Error()),
})
log.Printf(fmt.Sprintf("发布"+artist.Name+"视频"+artist.Title+"失败: %s", err.Error()))
continue
}
artistResp = append(artistResp, ArtistVideoDetail{
Id: artist.Id,
ArtistName: artist.Name,
SubNum: artist.SubNum,
Title: artist.Title,
WorkUuid: resp.WorkUuid,
Youtube: artist.Youtube,
Instagram: artist.Instagram,
TikTok: artist.TikTok,
})
}
service.Success(c, map[string]interface{}{
"failedRecords": failedRecords,
})
}
// 读取excel 数据
func readArtistVideoInfoV4(excelPath, unzipPath string) ([]ArtistMedia, error) {
log.Println(unzipPath)
f, err := excelize.OpenFile(excelPath)
if err != nil {
return nil, err
}
defer f.Close()
sheetName := f.GetSheetName(0)
rows, err := f.GetRows(sheetName)
if err != nil {
return nil, err
}
log.Println("start read excel")
var artists []ArtistMedia
for i, _ := range rows {
id, _ := f.GetCellValue(sheetName, fmt.Sprintf("A%d", i+1))
if id != "" {
id = strings.TrimSpace(id)
}
artistName, _ := f.GetCellValue(sheetName, fmt.Sprintf("B%d", i+1))
if artistName != "" {
artistName = strings.TrimSpace(artistName)
}
PinyinName, _ := f.GetCellValue(sheetName, fmt.Sprintf("C%d", i+1))
if PinyinName != "" {
PinyinName = strings.TrimSpace(PinyinName)
}
subNum, _ := f.GetCellValue(sheetName, fmt.Sprintf("D%d", i+1))
if subNum != "" {
subNum = strings.TrimSpace(subNum)
}
PinyinFileName, _ := f.GetCellValue(sheetName, fmt.Sprintf("E%d", i+1))
if PinyinFileName != "" {
PinyinFileName = strings.TrimSpace(PinyinFileName)
}
title, _ := f.GetCellValue(sheetName, fmt.Sprintf("F%d", i+1))
if title != "" {
title = strings.TrimSpace(title)
}
artists = append(artists, ArtistMedia{
Id: id,
Name: artistName,
Title: title,
PinyinName: PinyinName,
PinyinFileName: PinyinFileName,
//Youtube: youtube,
//Instagram: instagram,
//TikTok: tiktok,
SubNum: subNum,
})
}
artists, err = matchArtistMediaV4(artists, unzipPath)
return artists, nil
}
// -------------------------------------------------------上传视频图片
func matchArtistMediaV4(artists []ArtistMedia, unzipPath string) ([]ArtistMedia, error) {
var err error
var res []ArtistMedia
for _, artist := range artists {
var oldVideoPath, oldImgPath string
//查找图片
for _, ext := range []string{".jpg", ".png", ".jpeg"} {
p := fmt.Sprintf("%s/%s/%s%s", unzipPath, artist.PinyinName, artist.PinyinFileName, ext)
if _, err = os.Stat(p); err == nil {
oldImgPath = p
break
}
}
// 不存在跳过
if _, err = os.Stat(oldImgPath); os.IsNotExist(err) {
fmt.Println("图片不存在: ", artist.Id, artist.Name, oldImgPath)
continue
}
//查找视频
for _, ext := range []string{".mp4", ".mov"} {
p := fmt.Sprintf("%s/%s/%s%s", unzipPath, artist.PinyinName, artist.PinyinFileName, ext)
if _, err = os.Stat(p); err == nil {
oldVideoPath = p
break
}
}
//不存在跳过
if oldVideoPath == "" {
fmt.Println("视频不存在: ", artist.Id, artist.Name, oldVideoPath)
continue
}
baseDir := filepath.Join(unzipPath, artist.Name)
if err = os.MkdirAll(baseDir, 0755); err != nil {
log.Println("创建目录失败:", err)
return nil, err
}
log.Println("创建目录成功:", baseDir)
// -------------------------文件重命名
//重命名图片
now := time.Now().Unix()
imgPath := fmt.Sprintf("%s/%s/%s_%d.jpg", unzipPath, artist.PinyinName, artist.PinyinFileName, now)
if err = os.Rename(oldImgPath, imgPath); err != nil {
log.Println("图片:"+oldImgPath+"重命名失败:", err)
return nil, err
}
//重命名视频
videoPath := fmt.Sprintf("%s/%s/%s_%d.mp4", unzipPath, artist.PinyinName, artist.PinyinFileName, now)
if err = os.Rename(oldVideoPath, videoPath); err != nil {
log.Println("视频:"+oldVideoPath+"重命名失败:", err)
return nil, err
}
//------------------------------------------------上传视频
content, err := os.ReadFile(videoPath)
if err != nil {
return nil, err
}
if err = UploadToAnotherServiceV4(context.Background(), content, filepath.Base(videoPath)); err != nil {
log.Println("上传视频失败:", err)
return nil, err
}
var httpType string
if config.AppConfig.System.AppMode == "dev" {
url := "114.218.158.24:9020"
httpType = fmt.Sprintf("%s%s", model.HttpType, url)
} else {
url := "saas.fiee.com"
httpType = fmt.Sprintf("%s%s", model.HttpsType, url)
}
baseUrl := fmt.Sprintf("%s/api/fiee/resource/raw/", httpType)
videoUrl := baseUrl + filepath.Base(videoPath)
//------------------------------------------上传图片
imgUrl, err := upload.PutBos(filepath.ToSlash(imgPath), "image", false)
if err != nil {
log.Println("上传图片失败:", err)
return nil, err
}
//---------------------------------回填数据
tmp := artist
tmp.Id = artist.Id
tmp.Name = artist.Name
tmp.Title = artist.Title
tmp.Img = imgUrl
tmp.Video = filepath.ToSlash(videoUrl)
tmp.SubNum = artist.SubNum
res = append(res, tmp)
}
return res, nil
}
func UploadToAnotherServiceV4(ctx context.Context, fileData []byte, path string) error {
const chunkSize = 4*1024*1024 - 100
_, err := service.FilesProvider.TusCreate(ctx, &files.TusCreateReq{
Path: path,
UserSpacePath: "",
Override: true,
})
if err != nil {
return err
}
log.Println("create success ......**********")
offset := int64(0)
totalSize := int64(len(fileData))
for offset < totalSize {
end := offset + chunkSize
if end > totalSize {
end = totalSize
}
chunk := fileData[offset:end]
_, err = service.FilesProvider.TusUpload(ctx, &files.TusUploadReq{
Path: path,
UploadOffset: offset,
Content: chunk,
UserSpacePath: "",
})
if err != nil {
return fmt.Errorf("上传 offset=%d chunk 失败: %w", offset, err)
}
log.Printf("upload chunk: %d - %d success\n", offset, end)
offset = end
}
return nil
}