web
This commit is contained in:
@@ -27,13 +27,8 @@ func NewCartService(orderRepo *repository.OrderRepository, productRepo *reposito
|
||||
}
|
||||
|
||||
// GetCart 获取购物车
|
||||
// 优化: 移除用户存在性检查,因为中间件已经验证过token和用户
|
||||
func (s *CartService) GetCart(userID uint) ([]model.Cart, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, errors.New("用户不存在")
|
||||
}
|
||||
|
||||
return s.orderRepo.GetCart(userID)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,15 @@ func (s *CommentService) GetCommentStats(productID uint) (*model.CommentStats, e
|
||||
return s.commentRepo.GetStats(productID)
|
||||
}
|
||||
|
||||
// GetHighRatingComments 获取高分评论(用于首页展示)
|
||||
func (s *CommentService) GetHighRatingComments(limit int) ([]model.Comment, error) {
|
||||
if limit <= 0 || limit > 50 {
|
||||
limit = 6 // 默认6条
|
||||
}
|
||||
// 获取评分>=4的高分评论
|
||||
return s.commentRepo.GetHighRatingComments(limit, 4)
|
||||
}
|
||||
|
||||
// GetCommentByID 获取评论详情
|
||||
func (s *CommentService) GetCommentByID(id uint) (*model.Comment, error) {
|
||||
return s.commentRepo.GetByID(id)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"dianshang/internal/model"
|
||||
"dianshang/internal/repository"
|
||||
"errors"
|
||||
@@ -224,3 +225,338 @@ func (s *CouponService) GetAvailableCouponsForOrder(userID uint, orderAmount flo
|
||||
|
||||
return availableCoupons, nil
|
||||
}
|
||||
|
||||
// ==================== 管理端方法 ====================
|
||||
|
||||
// GetCouponListForAdmin 获取优惠券列表(管理端)
|
||||
func (s *CouponService) GetCouponListForAdmin(page, pageSize int, status, couponType, keyword string) ([]model.Coupon, int64, error) {
|
||||
return s.couponRepo.GetCouponListForAdmin(page, pageSize, status, couponType, keyword)
|
||||
}
|
||||
|
||||
// GetCouponDetailForAdmin 获取优惠券详情(管理端)
|
||||
func (s *CouponService) GetCouponDetailForAdmin(couponID uint) (*model.Coupon, error) {
|
||||
coupon, err := s.couponRepo.GetByID(couponID)
|
||||
if err != nil {
|
||||
return nil, errors.New("优惠券不存在")
|
||||
}
|
||||
|
||||
// 获取优惠券的使用统计(注意:领取数和使用数不在Coupon表中,而是在UserCoupon表中统计)
|
||||
// 这里仅作为查询,不修改coupon对象
|
||||
_, _, _ = s.couponRepo.GetCouponUsageStats(couponID)
|
||||
|
||||
return coupon, nil
|
||||
}
|
||||
|
||||
// CreateCoupon 创建优惠券
|
||||
func (s *CouponService) CreateCoupon(req interface{}, adminID uint) (*model.Coupon, error) {
|
||||
// 类型断言
|
||||
type CreateCouponRequest struct {
|
||||
Name string
|
||||
Type int
|
||||
Value int64
|
||||
MinAmount int64
|
||||
Description string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
TotalCount int
|
||||
Status int
|
||||
}
|
||||
|
||||
reqData, ok := req.(*CreateCouponRequest)
|
||||
if !ok {
|
||||
return nil, errors.New("无效的请求参数")
|
||||
}
|
||||
|
||||
// 验证时间
|
||||
if reqData.EndTime.Before(reqData.StartTime) {
|
||||
return nil, errors.New("结束时间不能早于开始时间")
|
||||
}
|
||||
|
||||
// 验证优惠券类型和值
|
||||
switch reqData.Type {
|
||||
case 1: // 满减券,value是金额(分)
|
||||
if reqData.Value <= 0 {
|
||||
return nil, errors.New("满减券优惠金额必须大于0")
|
||||
}
|
||||
case 2: // 折扣券,value是折扣(85表示8.5折)
|
||||
if reqData.Value <= 0 || reqData.Value > 100 {
|
||||
return nil, errors.New("折扣券折扣必须在0-100之间")
|
||||
}
|
||||
case 3: // 免邮券
|
||||
// 免邮券不需要验证value
|
||||
default:
|
||||
return nil, errors.New("不支持的优惠券类型")
|
||||
}
|
||||
|
||||
coupon := &model.Coupon{
|
||||
Name: reqData.Name,
|
||||
Type: uint8(reqData.Type),
|
||||
Value: uint(reqData.Value),
|
||||
MinAmount: uint(reqData.MinAmount),
|
||||
Description: reqData.Description,
|
||||
StartTime: reqData.StartTime,
|
||||
EndTime: reqData.EndTime,
|
||||
TotalCount: uint(reqData.TotalCount),
|
||||
UsedCount: 0,
|
||||
Status: uint8(reqData.Status),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := s.couponRepo.Create(coupon)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建优惠券失败: %v", err)
|
||||
}
|
||||
|
||||
return coupon, nil
|
||||
}
|
||||
|
||||
// UpdateCoupon 更新优惠券
|
||||
func (s *CouponService) UpdateCoupon(couponID uint, req interface{}) error {
|
||||
// 检查优惠券是否存在
|
||||
_, err := s.couponRepo.GetByID(couponID)
|
||||
if err != nil {
|
||||
return errors.New("优惠券不存在")
|
||||
}
|
||||
|
||||
// 类型断言
|
||||
type UpdateCouponRequest struct {
|
||||
Name string
|
||||
Type int
|
||||
Value int64
|
||||
MinAmount int64
|
||||
Description string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
TotalCount int
|
||||
Status int
|
||||
}
|
||||
|
||||
reqData, ok := req.(*UpdateCouponRequest)
|
||||
if !ok {
|
||||
return errors.New("无效的请求参数")
|
||||
}
|
||||
|
||||
// 验证时间
|
||||
if !reqData.EndTime.IsZero() && !reqData.StartTime.IsZero() {
|
||||
if reqData.EndTime.Before(reqData.StartTime) {
|
||||
return errors.New("结束时间不能早于开始时间")
|
||||
}
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if reqData.Name != "" {
|
||||
updates["name"] = reqData.Name
|
||||
}
|
||||
if reqData.Type > 0 {
|
||||
updates["type"] = reqData.Type
|
||||
}
|
||||
if reqData.Value > 0 {
|
||||
updates["value"] = reqData.Value
|
||||
}
|
||||
if reqData.MinAmount >= 0 {
|
||||
updates["min_amount"] = reqData.MinAmount
|
||||
}
|
||||
if reqData.Description != "" {
|
||||
updates["description"] = reqData.Description
|
||||
}
|
||||
if !reqData.StartTime.IsZero() {
|
||||
updates["start_time"] = reqData.StartTime
|
||||
}
|
||||
if !reqData.EndTime.IsZero() {
|
||||
updates["end_time"] = reqData.EndTime
|
||||
}
|
||||
if reqData.TotalCount >= 0 {
|
||||
updates["total_count"] = reqData.TotalCount
|
||||
}
|
||||
if reqData.Status >= 0 {
|
||||
updates["status"] = reqData.Status
|
||||
}
|
||||
updates["updated_at"] = time.Now()
|
||||
|
||||
return s.couponRepo.Update(couponID, updates)
|
||||
}
|
||||
|
||||
// DeleteCoupon 删除优惠券
|
||||
func (s *CouponService) DeleteCoupon(couponID uint) error {
|
||||
// 检查是否有用户已领取
|
||||
hasUsers, err := s.couponRepo.CheckCouponHasUsers(couponID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasUsers {
|
||||
return errors.New("该优惠券已被用户领取,无法删除")
|
||||
}
|
||||
|
||||
return s.couponRepo.Delete(couponID)
|
||||
}
|
||||
|
||||
// UpdateCouponStatus 更新优惠券状态
|
||||
func (s *CouponService) UpdateCouponStatus(couponID uint, status int) error {
|
||||
_, err := s.couponRepo.GetByID(couponID)
|
||||
if err != nil {
|
||||
return errors.New("优惠券不存在")
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"status": status,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
return s.couponRepo.Update(couponID, updates)
|
||||
}
|
||||
|
||||
// BatchDeleteCoupons 批量删除优惠券
|
||||
func (s *CouponService) BatchDeleteCoupons(couponIDs []uint) error {
|
||||
// 检查每个优惠券是否可以删除
|
||||
for _, id := range couponIDs {
|
||||
hasUsers, err := s.couponRepo.CheckCouponHasUsers(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasUsers {
|
||||
return fmt.Errorf("优惠券ID %d 已被用户领取,无法删除", id)
|
||||
}
|
||||
}
|
||||
|
||||
return s.couponRepo.BatchDelete(couponIDs)
|
||||
}
|
||||
|
||||
// GetCouponStatistics 获取优惠券统计
|
||||
func (s *CouponService) GetCouponStatistics(startTime, endTime time.Time) (map[string]interface{}, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取总优惠券数
|
||||
totalCoupons, err := s.couponRepo.CountTotalCoupons(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取启用的优惠券数
|
||||
activeCoupons, err := s.couponRepo.CountActiveCoupons(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取总领取数
|
||||
totalReceived, err := s.couponRepo.CountTotalReceived(ctx, startTime, endTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取总使用数
|
||||
totalUsed, err := s.couponRepo.CountTotalUsed(ctx, startTime, endTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取各类型优惠券统计
|
||||
typeStats, err := s.couponRepo.GetCouponTypeStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取热门优惠券
|
||||
topCoupons, err := s.couponRepo.GetTopCoupons(ctx, 10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算使用率
|
||||
useRate := 0.0
|
||||
if totalReceived > 0 {
|
||||
useRate = float64(totalUsed) / float64(totalReceived) * 100
|
||||
}
|
||||
|
||||
stats := map[string]interface{}{
|
||||
"total_coupons": totalCoupons,
|
||||
"active_coupons": activeCoupons,
|
||||
"total_received": totalReceived,
|
||||
"total_used": totalUsed,
|
||||
"use_rate": fmt.Sprintf("%.2f%%", useRate),
|
||||
"type_stats": typeStats,
|
||||
"top_coupons": topCoupons,
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetUserCouponListForAdmin 获取用户优惠券列表(管理端)
|
||||
func (s *CouponService) GetUserCouponListForAdmin(userID uint, page, pageSize int) ([]model.UserCoupon, int64, error) {
|
||||
return s.couponRepo.GetUserCouponListForAdmin(userID, page, pageSize)
|
||||
}
|
||||
|
||||
// DistributeCoupon 发放优惠券
|
||||
func (s *CouponService) DistributeCoupon(couponID uint, userIDs []uint, distributeAll bool, quantity int, adminID uint) (map[string]interface{}, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 验证优惠券是否存在
|
||||
coupon, err := s.couponRepo.GetByID(couponID)
|
||||
if err != nil {
|
||||
return nil, errors.New("优惠券不存在")
|
||||
}
|
||||
|
||||
// 检查优惠券状态
|
||||
if coupon.Status != 1 {
|
||||
return nil, errors.New("优惠券已禁用,无法发放")
|
||||
}
|
||||
|
||||
// 如果是全员发放,获取所有用户ID
|
||||
var targetUserIDs []uint
|
||||
distributeType := "single"
|
||||
if distributeAll {
|
||||
distributeType = "all"
|
||||
// TODO: 从用户表获取所有用户ID
|
||||
// targetUserIDs, err = s.userRepo.GetAllUserIDs()
|
||||
// 暂时返回错误,需要注入 userRepo
|
||||
return nil, errors.New("全员发放功能暂未实现")
|
||||
} else {
|
||||
targetUserIDs = userIDs
|
||||
if len(targetUserIDs) > 1 {
|
||||
distributeType = "batch"
|
||||
}
|
||||
}
|
||||
|
||||
// 记录发放结果
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
|
||||
// 给每个用户发放优惠券
|
||||
for _, userID := range targetUserIDs {
|
||||
for i := 0; i < quantity; i++ {
|
||||
userCoupon := &model.UserCoupon{
|
||||
UserID: userID,
|
||||
CouponID: couponID,
|
||||
Status: 0, // 未使用
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := s.couponRepo.CreateUserCoupon(userCoupon)
|
||||
if err != nil {
|
||||
failCount++
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 记录发放历史到数据库
|
||||
// 需要创建 coupon_distribute_history 表
|
||||
_ = ctx
|
||||
_ = adminID
|
||||
|
||||
result := map[string]interface{}{
|
||||
"total_count": len(targetUserIDs) * quantity,
|
||||
"success_count": successCount,
|
||||
"fail_count": failCount,
|
||||
"distribute_type": distributeType,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDistributeHistory 获取发放历史
|
||||
func (s *CouponService) GetDistributeHistory(page, pageSize int) ([]map[string]interface{}, int64, error) {
|
||||
return s.couponRepo.GetDistributeHistory(page, pageSize)
|
||||
}
|
||||
|
||||
144
server/internal/service/livestream.go
Normal file
144
server/internal/service/livestream.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"dianshang/internal/model"
|
||||
"dianshang/internal/repository"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type LiveStreamService interface {
|
||||
GetLiveStreamList(page, pageSize int, title, platform string, status *int) ([]model.LiveStream, int64, error)
|
||||
GetLiveStreamByID(id uint) (*model.LiveStream, error)
|
||||
GetActiveLiveStreams() ([]model.LiveStream, error)
|
||||
CreateLiveStream(stream *model.LiveStream) error
|
||||
UpdateLiveStream(id uint, stream *model.LiveStream) error
|
||||
UpdateLiveStreamStatus(id uint, status int) error
|
||||
DeleteLiveStream(id uint) error
|
||||
BatchDeleteLiveStreams(ids []uint) error
|
||||
IncrementViewCount(id uint) error
|
||||
}
|
||||
|
||||
type liveStreamService struct {
|
||||
liveStreamRepo repository.LiveStreamRepository
|
||||
}
|
||||
|
||||
func NewLiveStreamService(liveStreamRepo repository.LiveStreamRepository) LiveStreamService {
|
||||
return &liveStreamService{
|
||||
liveStreamRepo: liveStreamRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLiveStreamList 获取投流源列表
|
||||
func (s *liveStreamService) GetLiveStreamList(page, pageSize int, title, platform string, status *int) ([]model.LiveStream, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.GetList(page, pageSize, title, platform, status)
|
||||
}
|
||||
|
||||
// GetLiveStreamByID 获取投流源详情
|
||||
func (s *liveStreamService) GetLiveStreamByID(id uint) (*model.LiveStream, error) {
|
||||
if id == 0 {
|
||||
return nil, errors.New("无效的投流源ID")
|
||||
}
|
||||
return s.liveStreamRepo.GetByID(id)
|
||||
}
|
||||
|
||||
// GetActiveLiveStreams 获取所有启用的投流源
|
||||
func (s *liveStreamService) GetActiveLiveStreams() ([]model.LiveStream, error) {
|
||||
return s.liveStreamRepo.GetActiveLiveStreams()
|
||||
}
|
||||
|
||||
// CreateLiveStream 创建投流源
|
||||
func (s *liveStreamService) CreateLiveStream(stream *model.LiveStream) error {
|
||||
if stream.Title == "" {
|
||||
return errors.New("投流源标题不能为空")
|
||||
}
|
||||
if stream.Platform == "" {
|
||||
return errors.New("平台名称不能为空")
|
||||
}
|
||||
if stream.StreamURL == "" {
|
||||
return errors.New("投流URL不能为空")
|
||||
}
|
||||
|
||||
// 检查该平台是否已有投流源
|
||||
existingStreams, _, err := s.liveStreamRepo.GetList(1, 1, "", stream.Platform, nil)
|
||||
if err != nil {
|
||||
return errors.New("检查平台投流源失败")
|
||||
}
|
||||
if len(existingStreams) > 0 {
|
||||
return errors.New("该平台已存在投流源,一个平台只能设置一个投流源")
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.Create(stream)
|
||||
}
|
||||
|
||||
// UpdateLiveStream 更新投流源
|
||||
func (s *liveStreamService) UpdateLiveStream(id uint, stream *model.LiveStream) error {
|
||||
if id == 0 {
|
||||
return errors.New("无效的投流源ID")
|
||||
}
|
||||
|
||||
// 检查投流源是否存在
|
||||
existing, err := s.liveStreamRepo.GetByID(id)
|
||||
if err != nil {
|
||||
return errors.New("投流源不存在")
|
||||
}
|
||||
|
||||
// 如果修改了平台,检查新平台是否已有其他投流源
|
||||
if stream.Platform != "" && stream.Platform != existing.Platform {
|
||||
existingStreams, _, err := s.liveStreamRepo.GetList(1, 1, "", stream.Platform, nil)
|
||||
if err != nil {
|
||||
return errors.New("检查平台投流源失败")
|
||||
}
|
||||
if len(existingStreams) > 0 {
|
||||
return errors.New("该平台已存在投流源,一个平台只能设置一个投流源")
|
||||
}
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.Update(id, stream)
|
||||
}
|
||||
|
||||
// UpdateLiveStreamStatus 更新投流源状态
|
||||
func (s *liveStreamService) UpdateLiveStreamStatus(id uint, status int) error {
|
||||
if id == 0 {
|
||||
return errors.New("无效的投流源ID")
|
||||
}
|
||||
|
||||
if status != 0 && status != 1 {
|
||||
return errors.New("无效的状态值")
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.UpdateStatus(id, status)
|
||||
}
|
||||
|
||||
// DeleteLiveStream 删除投流源
|
||||
func (s *liveStreamService) DeleteLiveStream(id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("无效的投流源ID")
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.Delete(id)
|
||||
}
|
||||
|
||||
// BatchDeleteLiveStreams 批量删除投流源
|
||||
func (s *liveStreamService) BatchDeleteLiveStreams(ids []uint) error {
|
||||
if len(ids) == 0 {
|
||||
return errors.New("请选择要删除的投流源")
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.BatchDelete(ids)
|
||||
}
|
||||
|
||||
// IncrementViewCount 增加观看次数
|
||||
func (s *liveStreamService) IncrementViewCount(id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("无效的投流源ID")
|
||||
}
|
||||
|
||||
return s.liveStreamRepo.IncrementViewCount(id)
|
||||
}
|
||||
150
server/internal/service/platform.go
Normal file
150
server/internal/service/platform.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"dianshang/internal/model"
|
||||
"dianshang/internal/repository"
|
||||
"dianshang/pkg/utils"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// PlatformService 平台服务
|
||||
type PlatformService struct {
|
||||
platformRepo *repository.PlatformRepository
|
||||
productRepo *repository.ProductRepository
|
||||
}
|
||||
|
||||
// NewPlatformService 创建平台服务
|
||||
func NewPlatformService(platformRepo *repository.PlatformRepository, productRepo *repository.ProductRepository) *PlatformService {
|
||||
return &PlatformService{
|
||||
platformRepo: platformRepo,
|
||||
productRepo: productRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPlatformList 获取平台列表
|
||||
func (s *PlatformService) GetPlatformList(page, pageSize int, status *int, name string) ([]model.Platform, *utils.Pagination, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
// 如果不需要分页,获取所有平台
|
||||
if page == 0 && pageSize == 0 {
|
||||
platforms, err := s.platformRepo.GetAll()
|
||||
return platforms, nil, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// TODO: 实现带筛选的分页查询
|
||||
// 暂时先返回所有平台
|
||||
platforms, err := s.platformRepo.GetAll()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 简单筛选
|
||||
var filteredPlatforms []model.Platform
|
||||
for _, p := range platforms {
|
||||
if status != nil && p.Status != *status {
|
||||
continue
|
||||
}
|
||||
if name != "" && p.Name != name && p.Code != name {
|
||||
continue
|
||||
}
|
||||
filteredPlatforms = append(filteredPlatforms, p)
|
||||
}
|
||||
|
||||
total := len(filteredPlatforms)
|
||||
|
||||
// 分页
|
||||
start := offset
|
||||
end := offset + pageSize
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
result := filteredPlatforms[start:end]
|
||||
|
||||
pagination := utils.NewPagination(page, pageSize)
|
||||
pagination.Total = int64(total)
|
||||
|
||||
return result, pagination, nil
|
||||
}
|
||||
|
||||
// GetPlatformByID 根据ID获取平台
|
||||
func (s *PlatformService) GetPlatformByID(id uint) (*model.Platform, error) {
|
||||
return s.platformRepo.GetByID(id)
|
||||
}
|
||||
|
||||
// GetPlatformByCode 根据代码获取平台
|
||||
func (s *PlatformService) GetPlatformByCode(code string) (*model.Platform, error) {
|
||||
return s.platformRepo.GetByCode(code)
|
||||
}
|
||||
|
||||
// CreatePlatform 创建平台
|
||||
func (s *PlatformService) CreatePlatform(platform *model.Platform) error {
|
||||
// 检查平台代码是否已存在
|
||||
existing, _ := s.platformRepo.GetByCode(platform.Code)
|
||||
if existing != nil {
|
||||
return errors.New("平台代码已存在")
|
||||
}
|
||||
|
||||
return s.platformRepo.Create(platform)
|
||||
}
|
||||
|
||||
// UpdatePlatform 更新平台
|
||||
func (s *PlatformService) UpdatePlatform(id uint, updates map[string]interface{}) error {
|
||||
// 检查平台是否存在
|
||||
_, err := s.platformRepo.GetByID(id)
|
||||
if err != nil {
|
||||
return errors.New("平台不存在")
|
||||
}
|
||||
|
||||
// 如果更新代码,检查是否与其他平台冲突
|
||||
if code, ok := updates["code"].(string); ok {
|
||||
existing, _ := s.platformRepo.GetByCode(code)
|
||||
if existing != nil && existing.ID != id {
|
||||
return errors.New("平台代码已被其他平台使用")
|
||||
}
|
||||
}
|
||||
|
||||
return s.platformRepo.Update(id, updates)
|
||||
}
|
||||
|
||||
// DeletePlatform 删除平台
|
||||
func (s *PlatformService) DeletePlatform(id uint) error {
|
||||
// 检查平台是否存在
|
||||
_, err := s.platformRepo.GetByID(id)
|
||||
if err != nil {
|
||||
return errors.New("平台不存在")
|
||||
}
|
||||
|
||||
// TODO: 检查是否有分类关联到该平台
|
||||
// 可以选择级联删除或禁止删除
|
||||
|
||||
return s.platformRepo.Delete(id)
|
||||
}
|
||||
|
||||
// GetAllActivePlatforms 获取所有启用的平台
|
||||
func (s *PlatformService) GetAllActivePlatforms() ([]model.Platform, error) {
|
||||
platforms, err := s.platformRepo.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 过滤启用的平台
|
||||
var activePlatforms []model.Platform
|
||||
for _, p := range platforms {
|
||||
if p.Status == 1 {
|
||||
activePlatforms = append(activePlatforms, p)
|
||||
}
|
||||
}
|
||||
|
||||
return activePlatforms, nil
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func NewProductService(productRepo *repository.ProductRepository, userRepo *repo
|
||||
}
|
||||
|
||||
// GetProductList 获取产品列表(前端用户)
|
||||
func (s *ProductService) GetProductList(page, pageSize int, categoryID uint, keyword string, minPrice, maxPrice float64, sort, sortType string) ([]model.Product, *utils.Pagination, error) {
|
||||
func (s *ProductService) GetProductList(page, pageSize int, categoryID uint, keyword string, minPrice, maxPrice float64, inStock *bool, sort, sortType string) ([]model.Product, *utils.Pagination, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
@@ -48,6 +48,9 @@ func (s *ProductService) GetProductList(page, pageSize int, categoryID uint, key
|
||||
if maxPrice > 0 {
|
||||
conditions["max_price"] = maxPrice
|
||||
}
|
||||
if inStock != nil {
|
||||
conditions["in_stock"] = *inStock
|
||||
}
|
||||
if sort != "" {
|
||||
conditions["sort"] = sort
|
||||
}
|
||||
@@ -128,10 +131,12 @@ func (s *ProductService) GetProductDetail(id uint) (*model.Product, error) {
|
||||
// CreateProduct 创建产品
|
||||
func (s *ProductService) CreateProduct(product *model.Product) error {
|
||||
// 验证分类是否存在
|
||||
if product.CategoryID > 0 {
|
||||
_, err := s.productRepo.GetCategoryByID(product.CategoryID)
|
||||
if err != nil {
|
||||
return errors.New("分类不存在")
|
||||
if len(product.CategoryID) > 0 {
|
||||
for _, catID := range product.CategoryID {
|
||||
_, err := s.productRepo.GetCategoryByID(catID)
|
||||
if err != nil {
|
||||
return errors.New("分类ID" + strconv.Itoa(int(catID)) + "不存在")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,28 +151,6 @@ func (s *ProductService) UpdateProduct(id uint, updates map[string]interface{})
|
||||
return errors.New("产品不存在")
|
||||
}
|
||||
|
||||
// 如果更新分类,验证分类是否存在
|
||||
if categoryID, ok := updates["category_id"]; ok {
|
||||
var catID uint
|
||||
switch v := categoryID.(type) {
|
||||
case uint:
|
||||
catID = v
|
||||
case float64:
|
||||
catID = uint(v)
|
||||
case int:
|
||||
catID = uint(v)
|
||||
default:
|
||||
return errors.New("分类ID格式错误")
|
||||
}
|
||||
|
||||
if catID > 0 {
|
||||
_, err := s.productRepo.GetCategoryByID(catID)
|
||||
if err != nil {
|
||||
return errors.New("分类不存在")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 detail_images 字段 - 确保正确转换为 JSONSlice 类型
|
||||
if detailImages, ok := updates["detail_images"]; ok {
|
||||
switch v := detailImages.(type) {
|
||||
@@ -376,6 +359,11 @@ func (s *ProductService) GetCategories() ([]model.Category, error) {
|
||||
return s.productRepo.GetCategories()
|
||||
}
|
||||
|
||||
// GetCategoriesByPlatform 根据平台获取分类列表
|
||||
func (s *ProductService) GetCategoriesByPlatform(platform string) ([]model.Category, error) {
|
||||
return s.productRepo.GetCategoriesByPlatform(platform)
|
||||
}
|
||||
|
||||
// CreateCategory 创建分类
|
||||
func (s *ProductService) CreateCategory(category *model.Category) error {
|
||||
return s.productRepo.CreateCategory(category)
|
||||
@@ -492,7 +480,8 @@ func (s *ProductService) SearchProducts(keyword string, page, pageSize int, minP
|
||||
return []model.Product{}, utils.NewPagination(page, pageSize), nil
|
||||
}
|
||||
|
||||
return s.GetProductList(page, pageSize, 0, keyword, minPrice, maxPrice, sort, sortType)
|
||||
// 搜索不筛选库存,传递 nil
|
||||
return s.GetProductList(page, pageSize, 0, keyword, minPrice, maxPrice, nil, sort, sortType)
|
||||
}
|
||||
|
||||
// UpdateStock 更新库存
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -63,6 +64,102 @@ func (s *UserService) WeChatLogin(code string) (*model.User, string, error) {
|
||||
return user, token, nil
|
||||
}
|
||||
|
||||
// EmailLogin 邮箱登录
|
||||
func (s *UserService) EmailLogin(email, password, clientIP, userAgent string) (*model.User, string, error) {
|
||||
// 查找用户
|
||||
user, err := s.userRepo.GetByEmail(email)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, "", errors.New("邮箱或密码错误")
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
||||
return nil, "", errors.New("邮箱或密码错误")
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if user.Status == 0 {
|
||||
return nil, "", errors.New("用户已被禁用")
|
||||
}
|
||||
|
||||
// 生成JWT token (7天有效期)
|
||||
tokenExpiry := 7 * 24 * 3600
|
||||
token, err := jwt.GenerateToken(user.ID, "user", tokenExpiry)
|
||||
if err != nil {
|
||||
return nil, "", errors.New("生成token失败")
|
||||
}
|
||||
|
||||
// 记录登录日志
|
||||
s.logUserLogin(user.ID, "email", true, "", clientIP, userAgent)
|
||||
|
||||
return user, token, nil
|
||||
}
|
||||
|
||||
// EmailRegister 邮箱注册
|
||||
func (s *UserService) EmailRegister(email, password, nickname string) (*model.User, error) {
|
||||
// 检查邮箱是否已注册
|
||||
_, err := s.userRepo.GetByEmail(email)
|
||||
if err == nil {
|
||||
// 找到了用户,说明邮箱已注册
|
||||
return nil, errors.New("该邮箱已被注册")
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 其他数据库错误
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, errors.New("密码加密失败")
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
// 为邮箱用户生成唯一的 OpenID(使用 email: 前缀避免与微信 OpenID 冲突)
|
||||
user := &model.User{
|
||||
OpenID: "email:" + email, // 使用邮箱作为 OpenID,避免唯一索引冲突
|
||||
Email: email,
|
||||
Password: string(hashedPassword),
|
||||
Nickname: nickname,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// logUserLogin 记录用户登录日志
|
||||
func (s *UserService) logUserLogin(userID uint, loginType string, success bool, errorMsg, ip, userAgent string) {
|
||||
status := 1
|
||||
if !success {
|
||||
status = 0
|
||||
}
|
||||
|
||||
remark := loginType + " 登录"
|
||||
if errorMsg != "" {
|
||||
remark = errorMsg
|
||||
}
|
||||
|
||||
log := &model.UserLoginLog{
|
||||
UserID: userID,
|
||||
LoginIP: ip,
|
||||
UserAgent: userAgent,
|
||||
LoginTime: time.Now(),
|
||||
Status: status,
|
||||
Remark: remark,
|
||||
}
|
||||
|
||||
if err := s.db.Create(log).Error; err != nil {
|
||||
fmt.Printf("记录登录日志失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (s *UserService) CreateUser(user *model.User) error {
|
||||
// 检查用户是否已存在
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
wechatutils "github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
)
|
||||
@@ -25,6 +26,7 @@ type WeChatPayService struct {
|
||||
config *config.WeChatPayConfig
|
||||
client *core.Client
|
||||
jsapiSvc *jsapi.JsapiApiService
|
||||
nativeSvc *native.NativeApiService
|
||||
refundSvc *refunddomestic.RefundsApiService
|
||||
privateKey *rsa.PrivateKey
|
||||
orderRepo *repository.OrderRepository
|
||||
@@ -74,6 +76,9 @@ func NewWeChatPayService(cfg *config.WeChatPayConfig, orderRepo *repository.Orde
|
||||
// 创建JSAPI服务
|
||||
jsapiSvc := &jsapi.JsapiApiService{Client: client}
|
||||
|
||||
// 创建Native扫码支付服务
|
||||
nativeSvc := &native.NativeApiService{Client: client}
|
||||
|
||||
// 创建退款服务
|
||||
refundSvc := &refunddomestic.RefundsApiService{Client: client}
|
||||
|
||||
@@ -86,6 +91,7 @@ func NewWeChatPayService(cfg *config.WeChatPayConfig, orderRepo *repository.Orde
|
||||
config: cfg,
|
||||
client: client,
|
||||
jsapiSvc: jsapiSvc,
|
||||
nativeSvc: nativeSvc,
|
||||
refundSvc: refundSvc,
|
||||
privateKey: privateKey,
|
||||
orderRepo: orderRepo,
|
||||
@@ -174,6 +180,99 @@ func (s *WeChatPayService) CreateOrder(ctx context.Context, order *model.Order,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateNativeOrder 创建Native扫码支付订单(用于PC端)
|
||||
func (s *WeChatPayService) CreateNativeOrder(ctx context.Context, order *model.Order) (*WeChatPayResponse, error) {
|
||||
// 生成唯一的微信支付订单号
|
||||
wechatOutTradeNo := utils.GenerateWechatOutTradeNo()
|
||||
|
||||
logger.Info("开始创建Native扫码支付订单",
|
||||
"orderNo", order.OrderNo,
|
||||
"wechatOutTradeNo", wechatOutTradeNo,
|
||||
"totalAmount", order.TotalAmount,
|
||||
"hasClient", s.client != nil)
|
||||
|
||||
// 更新订单的微信支付订单号
|
||||
err := s.orderRepo.UpdateByOrderNo(order.OrderNo, map[string]interface{}{
|
||||
"wechat_out_trade_no": wechatOutTradeNo,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("更新订单微信支付订单号失败", "error", err, "orderNo", order.OrderNo)
|
||||
return nil, fmt.Errorf("更新订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 如果没有客户端(开发环境),使用模拟数据
|
||||
if s.client == nil {
|
||||
logger.Warn("开发环境下使用模拟Native支付数据")
|
||||
return s.createMockNativePayment(order)
|
||||
}
|
||||
|
||||
// 构建Native预支付请求
|
||||
req := native.PrepayRequest{
|
||||
Appid: core.String(s.config.AppID),
|
||||
Mchid: core.String(s.config.MchID),
|
||||
Description: core.String(fmt.Sprintf("订单号: %s", order.OrderNo)),
|
||||
OutTradeNo: core.String(wechatOutTradeNo),
|
||||
NotifyUrl: core.String(s.config.NotifyURL),
|
||||
Amount: &native.Amount{
|
||||
Total: core.Int64(int64(order.TotalAmount)),
|
||||
Currency: core.String("CNY"),
|
||||
},
|
||||
}
|
||||
|
||||
// 调用Native预支付API
|
||||
resp, result, err := s.nativeSvc.Prepay(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("call Native Prepay err:%s", err)
|
||||
logger.Error("创建Native支付订单失败", "error", err, "orderNo", order.OrderNo)
|
||||
return nil, fmt.Errorf("创建Native支付订单失败: %v", err)
|
||||
}
|
||||
|
||||
if result.Response.StatusCode != 200 {
|
||||
log.Printf("Native Prepay status=%d", result.Response.StatusCode)
|
||||
return nil, fmt.Errorf("Native预支付请求失败,状态码: %d", result.Response.StatusCode)
|
||||
}
|
||||
|
||||
log.Printf("Native Prepay success, code_url=%s", *resp.CodeUrl)
|
||||
logger.Info("微信Native支付API响应",
|
||||
"codeUrl", *resp.CodeUrl,
|
||||
"orderNo", order.OrderNo)
|
||||
|
||||
return &WeChatPayResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: map[string]interface{}{
|
||||
"qrcode_url": *resp.CodeUrl,
|
||||
"order_no": order.OrderNo,
|
||||
"amount": order.TotalAmount,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createMockNativePayment 创建模拟Native支付数据(开发环境使用)
|
||||
func (s *WeChatPayService) createMockNativePayment(order *model.Order) (*WeChatPayResponse, error) {
|
||||
// 生成模拟的二维码URL
|
||||
mockQRCodeURL := fmt.Sprintf("weixin://wxpay/bizpayurl?pr=mock_%s", utils.GenerateRandomString(10))
|
||||
|
||||
logger.Info("生成模拟Native支付参数",
|
||||
"environment", s.config.Environment,
|
||||
"qrcodeUrl", mockQRCodeURL,
|
||||
"orderNo", order.OrderNo,
|
||||
"totalAmount", order.TotalAmount)
|
||||
|
||||
return &WeChatPayResponse{
|
||||
Code: 0,
|
||||
Message: "模拟支付创建成功",
|
||||
Data: map[string]interface{}{
|
||||
"qrcode_url": mockQRCodeURL,
|
||||
"order_no": order.OrderNo,
|
||||
"amount": order.TotalAmount,
|
||||
"sandbox": true,
|
||||
"tips": "这是模拟环境的Native支付,请使用测试接口模拟支付成功",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createMockPayment 创建模拟支付数据(沙盒环境使用)
|
||||
func (s *WeChatPayService) createMockPayment(order *model.Order, openID string) (*WeChatPayResponse, error) {
|
||||
mockPrepayID := fmt.Sprintf("wx%d%s", time.Now().Unix(), generateNonceStr()[:8])
|
||||
|
||||
Reference in New Issue
Block a user