// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. //go:build plus // +build plus package services import ( "context" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models/accounts" "github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/userconfigs" "github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/types" timeutil "github.com/iwind/TeaGo/utils/time" "strings" "time" ) // UserPlanService 用户购买的套餐 type UserPlanService struct { BaseService } // BuyUserPlan 添加已购套餐 func (this *UserPlanService) BuyUserPlan(ctx context.Context, req *pb.BuyUserPlanRequest) (*pb.BuyUserPlanResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } if userId > 0 { req.UserId = userId } if req.CountPeriod <= 0 { req.CountPeriod = 1 } var userPlanId int64 err = this.RunTx(func(tx *dbs.Tx) error { // 套餐 plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, req.PlanId, nil) if err != nil { return err } if plan == nil { return errors.New("can not find plan with id '" + types.String(req.PlanId) + "'") } // 周期 var dayTo = req.DayTo if plan.PriceType == serverconfigs.PlanPriceTypePeriod { var cost float64 var periodDescription string switch req.Period { case "monthly": dayTo = timeutil.Format("Y-m-d", time.Now().AddDate(0, int(req.CountPeriod), 0)) cost = plan.MonthlyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "个月" case "seasonally": dayTo = timeutil.Format("Y-m-d", time.Now().AddDate(0, int(req.CountPeriod*3), 0)) cost = plan.SeasonallyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "个季度" case "yearly": dayTo = timeutil.Format("Y-m-d", time.Now().AddDate(int(req.CountPeriod), 0, 0)) cost = plan.YearlyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "年" default: return errors.New("invalid period '" + req.Period + "'") } // 用户账户 account, err := accounts.SharedUserAccountDAO.FindUserAccountWithUserId(tx, req.UserId) if err != nil { return err } if account == nil { return errors.New("can not find account for user '" + types.String(req.UserId) + "'") } if account.Total < cost { return errors.New("not enough quota to buy") } // 扣费 err = accounts.SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), -cost, userconfigs.AccountEventTypeBuyPlan, "购买套餐:"+types.String(plan.Name)+"/"+periodDescription+"/到期时间:"+dayTo, maps.Map{"planId": plan.Id}) if err != nil { return err } } else if plan.PriceType == serverconfigs.PlanPriceTypeTraffic { // DO NOTHING } else if plan.PriceType == serverconfigs.PlanPriceTypeBandwidth { // DO NOTHING } else { return errors.New("price type '" + plan.PriceType + "' is not supported yet") } userPlanId, err = models.SharedUserPlanDAO.CreateUserPlan(tx, req.UserId, req.PlanId, req.Name, dayTo) if err != nil { return err } return nil }) if err != nil { return nil, err } return &pb.BuyUserPlanResponse{UserPlanId: userPlanId}, nil } // RenewUserPlan 续费套餐 func (this *UserPlanService) RenewUserPlan(ctx context.Context, req *pb.RenewUserPlanRequest) (*pb.RPCSuccess, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } if req.CountPeriod <= 0 { req.CountPeriod = 1 } var tx = this.NullTx() if userId > 0 { err = models.SharedUserPlanDAO.CheckUserPlan(tx, userId, req.UserPlanId) if err != nil { return nil, err } } userPlan, err := models.SharedUserPlanDAO.FindEnabledUserPlan(tx, req.UserPlanId, nil) if err != nil { return nil, err } if userPlan == nil || userPlan.State != models.UserPlanStateEnabled { return nil, errors.New("can not find user plan to renew") } var planId = int64(userPlan.PlanId) userId = int64(userPlan.UserId) // 套餐 plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, planId, nil) if err != nil { return nil, err } if plan == nil { return nil, errors.New("can not find plan with id '" + types.String(planId) + "'") } if len(userPlan.DayTo) == 0 { userPlan.DayTo = timeutil.Format("Y-m-d") } var pieces = strings.Split(userPlan.DayTo, "-") if len(pieces) != 3 { return nil, errors.New("invalid 'dayTo': " + userPlan.DayTo) } var year = types.Int(pieces[0]) var month = types.Int(pieces[1]) var day = types.Int(pieces[2]) var startTime = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local).AddDate(0, 0, 1) err = this.RunTx(func(tx *dbs.Tx) error { // 周期 var dayTo = req.DayTo if plan.PriceType == serverconfigs.PlanPriceTypePeriod { var cost float64 var periodDescription string switch req.Period { case "monthly": dayTo = timeutil.Format("Y-m-d", startTime.AddDate(0, int(req.CountPeriod), 0)) cost = plan.MonthlyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "个月" case "seasonally": dayTo = timeutil.Format("Y-m-d", startTime.AddDate(0, int(req.CountPeriod*3), 0)) cost = plan.SeasonallyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "个季度" case "yearly": dayTo = timeutil.Format("Y-m-d", startTime.AddDate(int(req.CountPeriod), 0, 0)) cost = plan.YearlyPrice * float64(req.CountPeriod) periodDescription = types.String(req.CountPeriod) + "年" default: return errors.New("invalid period '" + req.Period + "'") } // 用户账户 account, err := accounts.SharedUserAccountDAO.FindUserAccountWithUserId(tx, userId) if err != nil { return err } if account == nil { return errors.New("can not find account for user '" + types.String(userId) + "'") } if account.Total < cost { return errors.New("not enough quota to buy") } // 扣费 err = accounts.SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), -cost, userconfigs.AccountEventTypeBuyPlan, "续费套餐:"+types.String(plan.Name)+"/"+periodDescription+"/到期时间:"+dayTo, maps.Map{"planId": plan.Id}) if err != nil { return err } } else if plan.PriceType == serverconfigs.PlanPriceTypeTraffic { // DO NOTHING } else if plan.PriceType == serverconfigs.PlanPriceTypeBandwidth { // DO NOTHING } else { return errors.New("price type '" + plan.PriceType + "' is not supported yet") } err = models.SharedUserPlanDAO.UpdateUserPlanDayTo(tx, req.UserPlanId, dayTo) if err != nil { return err } return nil }) if err != nil { return nil, err } return this.Success() } // FindEnabledUserPlan 查找单个已购套餐信息 func (this *UserPlanService) FindEnabledUserPlan(ctx context.Context, req *pb.FindEnabledUserPlanRequest) (*pb.FindEnabledUserPlanResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() if userId > 0 { err = models.SharedUserPlanDAO.CheckUserPlan(tx, userId, req.UserPlanId) if err != nil { return nil, err } } userPlan, err := models.SharedUserPlanDAO.FindEnabledUserPlan(tx, req.UserPlanId, nil) if err != nil { return nil, err } if userPlan == nil { return &pb.FindEnabledUserPlanResponse{UserPlan: nil}, nil } // user var pbUser = &pb.User{Id: int64(userPlan.UserId)} user, err := models.SharedUserDAO.FindEnabledUser(tx, int64(userPlan.UserId), nil) if err != nil { return nil, err } if user != nil { pbUser = &pb.User{ Id: int64(user.Id), Username: user.Username, Fullname: user.Fullname, } } // plan var pbPlan = &pb.Plan{Id: int64(userPlan.PlanId)} plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, int64(userPlan.PlanId), nil) if err != nil { return nil, err } if plan != nil { pbPlan = &pb.Plan{ Id: int64(plan.Id), Name: plan.Name, TotalServers: types.Int32(plan.TotalServers), TotalServerNames: types.Int32(plan.TotalServerNames), TotalServerNamesPerServer: types.Int32(plan.TotalServerNamesPerServer), DailyRequests: int64(plan.DailyRequests), MonthlyRequests: int64(plan.MonthlyRequests), } } return &pb.FindEnabledUserPlanResponse{UserPlan: &pb.UserPlan{ Id: int64(userPlan.Id), UserId: int64(userPlan.UserId), PlanId: int64(userPlan.PlanId), IsOn: userPlan.IsOn, Name: userPlan.Name, DayTo: userPlan.DayTo, User: pbUser, Plan: pbPlan, }}, nil } // UpdateUserPlan 修改已购套餐 func (this *UserPlanService) UpdateUserPlan(ctx context.Context, req *pb.UpdateUserPlanRequest) (*pb.RPCSuccess, error) { _, err := this.ValidateAdmin(ctx) if err != nil { return nil, err } var tx = this.NullTx() err = models.SharedUserPlanDAO.UpdateUserPlan(tx, req.UserPlanId, req.PlanId, req.Name, req.DayTo, req.IsOn) if err != nil { return nil, err } return this.Success() } // DeleteUserPlan 删除已购套餐 func (this *UserPlanService) DeleteUserPlan(ctx context.Context, req *pb.DeleteUserPlanRequest) (*pb.RPCSuccess, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() if userId > 0 { // 检查用户套餐 err = models.SharedUserPlanDAO.CheckUserPlan(tx, userId, req.UserPlanId) if err != nil { return nil, err } } err = models.SharedUserPlanDAO.DisableUserPlan(tx, req.UserPlanId) if err != nil { return nil, err } return this.Success() } // CountAllEnabledUserPlans 计算已购套餐数 func (this *UserPlanService) CountAllEnabledUserPlans(ctx context.Context, req *pb.CountAllEnabledUserPlansRequest) (*pb.RPCCountResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } if userId > 0 { req.UserId = userId } var tx = this.NullTx() count, err := models.SharedUserPlanDAO.CountAllEnabledUserPlans(tx, req.UserId, req.IsAvailable, req.IsExpired, req.ExpiringDays) if err != nil { return nil, err } return this.SuccessCount(count) } // ListEnabledUserPlans 列出单页已购套餐 func (this *UserPlanService) ListEnabledUserPlans(ctx context.Context, req *pb.ListEnabledUserPlansRequest) (*pb.ListEnabledUserPlansResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } if userId > 0 { req.UserId = userId } var tx = this.NullTx() userPlans, err := models.SharedUserPlanDAO.ListEnabledUserPlans(tx, req.UserId, req.IsAvailable, req.IsExpired, req.ExpiringDays, req.Offset, req.Size) if err != nil { return nil, err } var pbUserPlans = []*pb.UserPlan{} var cacheMap = utils.NewCacheMap() for _, userPlan := range userPlans { // user var pbUser = &pb.User{Id: int64(userPlan.UserId)} user, err := models.SharedUserDAO.FindEnabledUser(tx, int64(userPlan.UserId), cacheMap) if err != nil { return nil, err } if user != nil { pbUser = &pb.User{ Id: int64(user.Id), Username: user.Username, Fullname: user.Fullname, } } // plan var pbPlan = &pb.Plan{Id: int64(userPlan.PlanId)} plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, int64(userPlan.PlanId), cacheMap) if err != nil { return nil, err } if plan != nil { pbPlan = &pb.Plan{ Id: int64(plan.Id), Name: plan.Name, PriceType: plan.PriceType, TrafficLimitJSON: plan.TrafficLimit, BandwidthLimitPerNodeJSON: plan.BandwidthLimitPerNode, TrafficPriceJSON: plan.TrafficPrice, BandwidthPriceJSON: plan.BandwidthPrice, MonthlyPrice: plan.MonthlyPrice, SeasonallyPrice: plan.SeasonallyPrice, YearlyPrice: plan.YearlyPrice, TotalServers: types.Int32(plan.TotalServers), TotalServerNames: types.Int32(plan.TotalServerNames), TotalServerNamesPerServer: types.Int32(plan.TotalServerNamesPerServer), DailyRequests: int64(plan.DailyRequests), MonthlyRequests: int64(plan.MonthlyRequests), DailyWebsocketConnections: int64(plan.DailyWebsocketConnections), MonthlyWebsocketConnections: int64(plan.MonthlyWebsocketConnections), } } // server var pbServer *pb.Server var pbServers []*pb.Server servers, err := models.SharedServerDAO.FindEnabledServersWithUserPlanId(tx, int64(userPlan.Id)) if err != nil { return nil, err } if len(servers) > 0 { // 兼容以往版本 var firstServer = servers[0] pbServer = &pb.Server{ Id: int64(firstServer.Id), Name: firstServer.Name, ServerNamesJSON: firstServer.ServerNames, Type: firstServer.Type, } // 列表 for _, server := range servers { pbServers = append(pbServers, &pb.Server{ Id: int64(server.Id), Name: server.Name, ServerNamesJSON: server.ServerNames, Type: server.Type, }) } } pbUserPlans = append(pbUserPlans, &pb.UserPlan{ Id: int64(userPlan.Id), UserId: int64(userPlan.UserId), PlanId: int64(userPlan.PlanId), IsOn: userPlan.IsOn, Name: userPlan.Name, DayTo: userPlan.DayTo, User: pbUser, Plan: pbPlan, Server: pbServer, Servers: pbServers, }) } return &pb.ListEnabledUserPlansResponse{UserPlans: pbUserPlans}, nil } // FindAllEnabledUserPlansForServer 查找当前网站所有可用的套餐 func (this *UserPlanService) FindAllEnabledUserPlansForServer(ctx context.Context, req *pb.FindAllEnabledUserPlansForServerRequest) (*pb.FindAllEnabledUserPlansForServerResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() if userId > 0 && req.ServerId > 0 { // 检查服务 err = models.SharedServerDAO.CheckUserServer(tx, userId, req.ServerId) if err != nil { return nil, err } req.UserId = userId } userPlans, err := models.SharedUserPlanDAO.FindAllEnabledPlansForServer(tx, req.UserId, req.ServerId) if err != nil { return nil, err } var pbUserPlans = []*pb.UserPlan{} var cacheMap = utils.NewCacheMap() for _, userPlan := range userPlans { // user var pbUser = &pb.User{Id: int64(userPlan.UserId)} user, err := models.SharedUserDAO.FindEnabledUser(tx, int64(userPlan.UserId), cacheMap) if err != nil { return nil, err } if user != nil { pbUser = &pb.User{ Id: int64(user.Id), Username: user.Username, Fullname: user.Fullname, } } // plan var pbPlan = &pb.Plan{Id: int64(userPlan.PlanId)} plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, int64(userPlan.PlanId), cacheMap) if err != nil { return nil, err } if plan != nil { pbPlan = &pb.Plan{ Id: int64(plan.Id), Name: plan.Name, TotalServers: types.Int32(plan.TotalServers), TotalServerNames: types.Int32(plan.TotalServerNames), TotalServerNamesPerServer: types.Int32(plan.TotalServerNamesPerServer), DailyRequests: int64(plan.DailyRequests), MonthlyRequests: int64(plan.MonthlyRequests), } } pbUserPlans = append(pbUserPlans, &pb.UserPlan{ Id: int64(userPlan.Id), UserId: int64(userPlan.UserId), PlanId: int64(userPlan.PlanId), Name: userPlan.Name, IsOn: userPlan.IsOn, DayTo: userPlan.DayTo, User: pbUser, Plan: pbPlan, }) } return &pb.FindAllEnabledUserPlansForServerResponse{UserPlans: pbUserPlans}, nil }