413 lines
12 KiB
Go
413 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"license-manager/internal/config"
|
|
"license-manager/internal/models"
|
|
"license-manager/internal/repository"
|
|
pkgcontext "license-manager/pkg/context"
|
|
"license-manager/pkg/i18n"
|
|
"license-manager/pkg/utils"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CuOrderService interface {
|
|
CreateOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error)
|
|
CreatePendingOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error)
|
|
GetOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error)
|
|
GetUserOrders(ctx context.Context, cuUserID string, offset, limit int) ([]*models.CuOrder, int64, error)
|
|
CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error)
|
|
UpdateOrderStatus(ctx context.Context, orderID string, status string) error
|
|
}
|
|
|
|
type PriceCalculationResult struct {
|
|
UnitPrice float64 `json:"unit_price"`
|
|
LicenseCount int `json:"license_count"`
|
|
DiscountRate float64 `json:"discount_rate"`
|
|
TotalAmount float64 `json:"total_amount"`
|
|
DiscountDesc string `json:"discount_description"`
|
|
}
|
|
|
|
// 套餐配置(临时写死,后续可改为配置化)
|
|
var packageConfigs = map[string]PackageConfig{
|
|
"trial": {
|
|
Name: "试用版",
|
|
Price: 0,
|
|
MaxDevices: 1, // 试用版限制1个许可
|
|
},
|
|
"basic": {
|
|
Name: "基础版",
|
|
Price: 300,
|
|
MaxDevices: 1000, // 基础版最多1000个许可
|
|
},
|
|
"professional": {
|
|
Name: "专业版",
|
|
Price: 2000,
|
|
MaxDevices: 1000, // 专业版最多1000个许可
|
|
},
|
|
}
|
|
|
|
type PackageConfig struct {
|
|
Name string
|
|
Price float64
|
|
MaxDevices int
|
|
}
|
|
|
|
// 折扣规则配置
|
|
var discountRules = []DiscountRule{
|
|
{MinQuantity: 50, MaxQuantity: 99, Rate: 0.9, Description: "50-99许可9折优惠"},
|
|
{MinQuantity: 100, MaxQuantity: 499, Rate: 0.8, Description: "100-499许可8折优惠"},
|
|
{MinQuantity: 500, MaxQuantity: 0, Rate: 0.7, Description: "500+许可7折优惠"},
|
|
}
|
|
|
|
type DiscountRule struct {
|
|
MinQuantity int
|
|
MaxQuantity int // 0表示无上限
|
|
Rate float64
|
|
Description string
|
|
}
|
|
|
|
type cuOrderService struct {
|
|
repo repository.CuOrderRepository
|
|
cuUserRepo repository.CuUserRepository
|
|
authCodeRepo repository.AuthorizationCodeRepository
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewCuOrderService(repo repository.CuOrderRepository, cuUserRepo repository.CuUserRepository, authCodeRepo repository.AuthorizationCodeRepository, db *gorm.DB) CuOrderService {
|
|
return &cuOrderService{
|
|
repo: repo,
|
|
cuUserRepo: cuUserRepo,
|
|
authCodeRepo: authCodeRepo,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (s *cuOrderService) CreatePendingOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error) {
|
|
// 计算价格
|
|
priceResult, err := s.CalculatePrice(ctx, req.PackageID, req.LicenseCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 生成订单号
|
|
orderNo := s.generateOrderNo()
|
|
|
|
// 获取套餐配置
|
|
packageConfig, exists := packageConfigs[req.PackageID]
|
|
if !exists {
|
|
return nil, i18n.NewI18nError("600001", "套餐不存在")
|
|
}
|
|
|
|
// 创建订单(pending状态)
|
|
order := &models.CuOrder{
|
|
OrderNo: orderNo,
|
|
CustomerID: customerID,
|
|
CuUserID: cuUserID,
|
|
PackageID: req.PackageID,
|
|
PackageName: packageConfig.Name,
|
|
LicenseCount: req.LicenseCount,
|
|
UnitPrice: priceResult.UnitPrice,
|
|
DiscountRate: priceResult.DiscountRate,
|
|
TotalAmount: priceResult.TotalAmount,
|
|
Status: "pending", // 待支付状态
|
|
}
|
|
|
|
// 对于试用版,设置过期时间
|
|
if req.PackageID == "trial" {
|
|
expiredAt := s.getTrialExpiryTime()
|
|
order.ExpiredAt = &expiredAt
|
|
}
|
|
|
|
// 保存订单
|
|
if err := s.repo.Create(order); err != nil {
|
|
return nil, i18n.NewI18nError("601001", "订单创建失败: "+err.Error())
|
|
}
|
|
|
|
return order, nil
|
|
}
|
|
|
|
func (s *cuOrderService) UpdateOrderStatus(ctx context.Context, orderID string, status string) error {
|
|
// 这里需要实现更新订单状态的逻辑
|
|
// 暂时先返回nil,后面再实现
|
|
return nil
|
|
}
|
|
|
|
// getTrialExpiryTime 获取试用版过期时间(当月25日23:59:59)
|
|
func (s *cuOrderService) getTrialExpiryTime() time.Time {
|
|
now := time.Now()
|
|
return time.Date(now.Year(), now.Month(), 25, 23, 59, 59, 0, now.Location())
|
|
}
|
|
|
|
func (s *cuOrderService) CreateOrder(ctx context.Context, cuUserID, customerID string, req *models.CuOrderCreateRequest) (*models.CuOrder, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if req == nil || req.PackageID == "" || req.LicenseCount <= 0 {
|
|
return nil, i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 检查套餐是否存在
|
|
pkg, exists := packageConfigs[req.PackageID]
|
|
if !exists {
|
|
return nil, i18n.NewI18nError("600001", lang) // 套餐不存在
|
|
}
|
|
|
|
// 验证许可数量
|
|
if req.LicenseCount > pkg.MaxDevices {
|
|
return nil, i18n.NewI18nError("601005", lang) // 许可数量超出限制
|
|
}
|
|
|
|
// 试用版特殊检查
|
|
if req.PackageID == "trial" {
|
|
// 试用版不支持批量购买
|
|
if req.LicenseCount != 1 {
|
|
return nil, i18n.NewI18nError("600004", lang) // 试用版不支持批量购买
|
|
}
|
|
|
|
// 检查购买时间限制
|
|
now := time.Now()
|
|
if now.Day() > 25 {
|
|
return nil, i18n.NewI18nError("600003", lang) // 试用版购买时间限制
|
|
}
|
|
|
|
// 每个用户每月只能有一个试用版
|
|
currentMonth := now.Format("2006-01")
|
|
exists, err := s.repo.CheckTrialOrderExists(cuUserID, currentMonth)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return nil, i18n.NewI18nError("600005", lang) // 当月已购买试用版
|
|
}
|
|
}
|
|
|
|
// 计算价格
|
|
priceResult, err := s.CalculatePrice(ctx, req.PackageID, req.LicenseCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 生成订单号
|
|
orderNo := s.generateOrderNo()
|
|
|
|
// 创建订单
|
|
order := &models.CuOrder{
|
|
OrderNo: orderNo,
|
|
CustomerID: customerID,
|
|
CuUserID: cuUserID,
|
|
PackageID: req.PackageID,
|
|
PackageName: pkg.Name,
|
|
LicenseCount: req.LicenseCount,
|
|
UnitPrice: priceResult.UnitPrice,
|
|
DiscountRate: priceResult.DiscountRate,
|
|
TotalAmount: priceResult.TotalAmount,
|
|
Status: "paid", // 直接设置为已支付状态
|
|
}
|
|
|
|
// 开启事务
|
|
tx := s.db.Begin()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
// 创建订单
|
|
if err := tx.Create(order).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 支付成功,创建授权码
|
|
// 实际项目中这里应该先调用支付接口,支付成功后再执行后续逻辑
|
|
now := time.Now()
|
|
order.UpdatedAt = now
|
|
|
|
// 计算授权时间
|
|
var startDate, endDate time.Time
|
|
var expiredAt *time.Time
|
|
if req.PackageID == "trial" {
|
|
// 试用版:从今天到当月25日
|
|
trialExpiry := time.Date(now.Year(), now.Month(), 25, 23, 59, 59, 0, now.Location())
|
|
if now.Day() > 25 {
|
|
// 如果今天已经过了25日,则无法购买
|
|
tx.Rollback()
|
|
return nil, i18n.NewI18nError("600003", lang) // 试用版购买时间限制
|
|
}
|
|
startDate = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
endDate = trialExpiry
|
|
expiredAt = &trialExpiry
|
|
} else {
|
|
// 永久版:从今天开始,结束时间设为很远的未来
|
|
startDate = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
endDate = startDate.AddDate(1000, 0, 0) // 1000年后
|
|
expiredAt = nil
|
|
}
|
|
|
|
// 生成自包含授权码
|
|
var featureConfigMap, usageLimitsMap map[string]interface{}
|
|
|
|
// 根据套餐类型设置配置信息
|
|
if req.PackageID == "basic" {
|
|
usageLimitsMap = map[string]interface{}{
|
|
"type": "standard",
|
|
}
|
|
}
|
|
|
|
licenseConfig := &utils.LicenseConfig{
|
|
EndDate: endDate,
|
|
FeatureConfig: featureConfigMap,
|
|
UsageLimits: usageLimitsMap,
|
|
CustomParameters: map[string]interface{}{
|
|
"package_id": req.PackageID,
|
|
"package_name": pkg.Name,
|
|
"license_count": req.LicenseCount,
|
|
},
|
|
}
|
|
|
|
// 从配置获取RSA私钥路径
|
|
privateKeyPath := config.GetConfig().License.RSA.PrivateKeyPath
|
|
authCode, err := utils.EncodeLicenseData(licenseConfig, privateKeyPath)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 构建订单描述
|
|
description := fmt.Sprintf("%s - %d个许可", pkg.Name, req.LicenseCount)
|
|
|
|
// 构建授权码实体(使用生成的授权码)
|
|
authCodeEntity := &models.AuthorizationCode{
|
|
ID: uuid.New().String(), // 生成新的UUID作为主键
|
|
Code: authCode,
|
|
CustomerID: customerID,
|
|
CreatedBy: cuUserID,
|
|
Description: &description,
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
DeploymentType: "cloud",
|
|
EncryptionType: &[]string{"standard"}[0],
|
|
MaxActivations: req.LicenseCount,
|
|
IsLocked: false,
|
|
// FeatureConfig 和 UsageLimits 已在授权码字符串中,无需重复存储
|
|
}
|
|
|
|
// 创建授权码
|
|
if err := s.authCodeRepo.CreateAuthorizationCode(ctx, authCodeEntity); err != nil {
|
|
tx.Rollback()
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 更新订单的授权码信息
|
|
order.AuthorizationCode = &authCode
|
|
order.ExpiredAt = expiredAt
|
|
|
|
// 更新订单
|
|
if err := tx.Save(order).Error; err != nil {
|
|
tx.Rollback()
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 提交事务
|
|
if err := tx.Commit().Error; err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return order, nil
|
|
}
|
|
|
|
func (s *cuOrderService) GetOrder(ctx context.Context, orderID, cuUserID string) (*models.CuOrder, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
order, err := s.repo.GetByID(orderID)
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, i18n.NewI18nError("601001", lang) // 订单不存在
|
|
}
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 检查权限:只能查看自己的订单
|
|
if order.CuUserID != cuUserID {
|
|
return nil, i18n.NewI18nError("100005", lang) // 权限不足
|
|
}
|
|
|
|
return order, nil
|
|
}
|
|
|
|
func (s *cuOrderService) GetUserOrders(ctx context.Context, cuUserID string, offset, limit int) ([]*models.CuOrder, int64, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if cuUserID == "" {
|
|
return nil, 0, i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
orders, total, err := s.repo.GetByCuUserID(cuUserID, offset, limit)
|
|
if err != nil {
|
|
return nil, 0, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return orders, total, nil
|
|
}
|
|
|
|
func (s *cuOrderService) CalculatePrice(ctx context.Context, packageID string, licenseCount int) (*PriceCalculationResult, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 检查套餐是否存在
|
|
pkg, exists := packageConfigs[packageID]
|
|
if !exists {
|
|
return nil, i18n.NewI18nError("600001", lang) // 套餐不存在
|
|
}
|
|
|
|
// 检查套餐是否已下架(虽然当前是写死配置,但保留这个检查)
|
|
// TODO: 如果将来添加status字段,可以在这里检查
|
|
// if pkg.Status != "active" {
|
|
// return nil, i18n.NewI18nError("600002", lang) // 套餐已下架
|
|
// }
|
|
|
|
// 验证许可数量
|
|
if licenseCount > pkg.MaxDevices {
|
|
return nil, i18n.NewI18nError("601005", lang) // 许可数量超出限制
|
|
}
|
|
|
|
// 计算折扣
|
|
discountRate := 1.0
|
|
discountDesc := "不享受折扣"
|
|
for _, rule := range discountRules {
|
|
if licenseCount >= rule.MinQuantity && (rule.MaxQuantity == 0 || licenseCount <= rule.MaxQuantity) {
|
|
discountRate = rule.Rate
|
|
discountDesc = rule.Description
|
|
break
|
|
}
|
|
}
|
|
|
|
// 计算总价
|
|
totalAmount := pkg.Price * float64(licenseCount) * discountRate
|
|
|
|
return &PriceCalculationResult{
|
|
UnitPrice: pkg.Price,
|
|
LicenseCount: licenseCount,
|
|
DiscountRate: discountRate,
|
|
TotalAmount: totalAmount,
|
|
DiscountDesc: discountDesc,
|
|
}, nil
|
|
}
|
|
|
|
// 生成订单号
|
|
func (s *cuOrderService) generateOrderNo() string {
|
|
now := time.Now()
|
|
dateStr := now.Format("20060102")
|
|
|
|
// 使用纳秒时间戳的最后9位作为序列号,确保全局唯一性
|
|
// 纳秒时间戳在同一纳秒内不会重复,重启应用也不会影响
|
|
nano := now.UnixNano() % 1000000000 // 取最后9位
|
|
|
|
return fmt.Sprintf("ORD%s%09d", dateStr, nano)
|
|
}
|