from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List, Optional import json from database import get_db from models.member_level import MemberLevel from models.user import User from models.membership import Membership from models.product import Product from models.product_media import ProductMedia from models.media import Media from schemas.member_level import MemberLevelResponse, MemberLevelCreate, MemberLevelUpdate, PublicMemberLevelResponse from schemas.operation_log import OperationLogCreate from routers.auth import get_current_user, get_current_superadmin from routers.operation_log import create_operation_log from sqlalchemy import func from config.member_levels import DEFAULT_MEMBER_LEVELS router = APIRouter() # 单位转换常量 BYTES_PER_KB = 1024 BYTES_PER_MB = 1024 * 1024 BYTES_PER_GB = 1024 * 1024 * 1024 # 计算会员等级的权益条目数 def calculate_benefit_count(level): """计算一个会员等级的权益条目数量""" # 数值类型的权益字段 numeric_benefits = [ 'deployment_nodes', 'product_sku_limit', 'image_storage_limit', 'sku_image_limit', 'sku_video_limit' ] # 布尔类型的权益字段 boolean_benefits = [ 'template_access', 'geo_seo_access', 'social_automation_access', 'custom_development_access' ] count = 0 # 统计数值类型权益 for field in numeric_benefits: if level.get(field, 0) > 0: count += 1 # 统计布尔类型权益 for field in boolean_benefits: if level.get(field, False): count += 1 return count # 获取会员等级列表(公开接口,无需认证) @router.get("/public", response_model=PublicMemberLevelResponse) async def get_public_member_levels( db: Session = Depends(get_db) ): """公开的会员等级列表接口,用于首页展示""" member_levels = db.query(MemberLevel).order_by(MemberLevel.level).all() # 如果数据库中没有数据,使用默认数据 if not member_levels: member_levels = DEFAULT_MEMBER_LEVELS else: # 转换为字典格式 member_levels = [ { "level": level.level, "name": level.name, "description": level.description, "annual_fee": level.annual_fee, "discount_price": level.discount_price, "deployment_nodes": level.deployment_nodes, "email_marketing_limit": level.email_marketing_limit, "product_sku_limit": level.product_sku_limit, "sku_image_limit": level.sku_image_limit, "image_size_limit": level.image_size_limit, "sku_video_limit": level.sku_video_limit, "image_storage_limit": level.image_storage_limit, "template_access": level.template_access, "geo_seo_access": level.geo_seo_access, "social_automation_access": level.social_automation_access, "custom_development_access": level.custom_development_access } for level in member_levels ] # 计算最大权益条目数(最高等级的权益必定最多,只需计算最后一个) max_count = calculate_benefit_count(member_levels[-1]) if member_levels else 0 return PublicMemberLevelResponse( member_levels=member_levels, max_benefit_count=max_count ) # 获取会员等级列表 @router.get("/", response_model=List[MemberLevelResponse]) async def get_member_levels( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): member_levels = db.query(MemberLevel).order_by(MemberLevel.level).all() return member_levels # 通过ID获取会员等级详情 @router.get("/{level_id}", response_model=MemberLevelResponse) async def get_member_level( level_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): member_level = db.query(MemberLevel).filter(MemberLevel.id == level_id).first() if member_level is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Member level with ID '{level_id}' not found" ) return member_level # 创建会员等级(仅超级管理员可操作) @router.post("/", response_model=MemberLevelResponse) async def create_member_level( member_level: MemberLevelCreate, current_user: User = Depends(get_current_superadmin), db: Session = Depends(get_db) ): # 检查等级是否已存在 existing_level = db.query(MemberLevel).filter(MemberLevel.level == member_level.level).first() if existing_level: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Member level with level '{member_level.level}' already exists" ) # 直接使用模型数据创建会员等级 level_data = member_level.model_dump() db_level = MemberLevel(**level_data) db.add(db_level) db.commit() db.refresh(db_level) # 记录操作日志 create_operation_log( db=db, user=current_user, module="member_level", operation_type="add", operation_detail=f"新增会员等级:{db_level.name}(等级:{db_level.level})", company_code=current_user.company_code ) return db_level # 更新会员等级(仅超级管理员可操作) @router.put("/{level_id}", response_model=MemberLevelResponse) async def update_member_level( level_id: int, member_level_update: MemberLevelUpdate, current_user: User = Depends(get_current_superadmin), db: Session = Depends(get_db) ): member_level = db.query(MemberLevel).filter(MemberLevel.id == level_id).first() if member_level is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Member level with ID '{level_id}' not found" ) # 记录修改前的数据 before_data_dict = { "name": member_level.name, "description": member_level.description, "annual_fee": member_level.annual_fee, "discount_price": member_level.discount_price, "deployment_nodes": member_level.deployment_nodes, "email_marketing_limit": member_level.email_marketing_limit, "product_sku_limit": member_level.product_sku_limit, "sku_image_limit": member_level.sku_image_limit, "image_size_limit": member_level.image_size_limit, "sku_video_limit": member_level.sku_video_limit, "image_storage_limit": member_level.image_storage_limit, "template_access": member_level.template_access, "geo_seo_access": member_level.geo_seo_access, "social_automation_access": member_level.social_automation_access, "custom_development_access": member_level.custom_development_access } # 更新会员等级信息 update_data = member_level_update.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(member_level, field, value) db.commit() db.refresh(member_level) # 记录修改后的数据 after_data_dict = { "name": member_level.name, "description": member_level.description, "annual_fee": member_level.annual_fee, "discount_price": member_level.discount_price, "deployment_nodes": member_level.deployment_nodes, "email_marketing_limit": member_level.email_marketing_limit, "product_sku_limit": member_level.product_sku_limit, "sku_image_limit": member_level.sku_image_limit, "image_size_limit": member_level.image_size_limit, "sku_video_limit": member_level.sku_video_limit, "image_storage_limit": member_level.image_storage_limit, "template_access": member_level.template_access, "geo_seo_access": member_level.geo_seo_access, "social_automation_access": member_level.social_automation_access, "custom_development_access": member_level.custom_development_access } # 只记录有修改的字段,使用中文解析 modified_fields = [] field_names = { "name": "等级名称", "description": "等级描述", "annual_fee": "会员每年权益服务费", "discount_price": "当前优惠价", "deployment_nodes": "独立站部署节点数", "email_marketing_limit": "邮件营销推送数量", "product_sku_limit": "产品SKU数", "sku_image_limit": "每个SKU可加图片数量", "image_size_limit": "图片大小限制", "sku_video_limit": "每个SKU可加视频数量", "image_storage_limit": "图片空间大小", "template_access": "模板选择权限", "geo_seo_access": "GEO,SEO关键词营销", "social_automation_access": "社交平台自动化营销", "custom_development_access": "定制化需求开发" } for field, chinese_name in field_names.items(): if before_data_dict[field] != after_data_dict[field]: modified_fields.append(f"{chinese_name}:{before_data_dict[field]} → {after_data_dict[field]}") # 生成中文描述的修改内容 if modified_fields: modification_detail = ";".join(modified_fields) else: modification_detail = "无字段修改" # 记录操作日志 create_operation_log( db=db, user=current_user, module="member_level", operation_type="edit", operation_detail=f"编辑会员等级:{member_level.name}(等级:{member_level.level})", before_data=modification_detail, after_data=None, company_code=current_user.company_code ) return member_level # 删除会员等级(仅超级管理员可操作) @router.delete("/{level_id}") async def delete_member_level( level_id: int, current_user: User = Depends(get_current_superadmin), db: Session = Depends(get_db) ): member_level = db.query(MemberLevel).filter(MemberLevel.id == level_id).first() if member_level is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Member level with ID '{level_id}' not found" ) # 记录会员等级名称和等级,用于日志 level_name = member_level.name level = member_level.level db.delete(member_level) db.commit() # 记录操作日志 create_operation_log( db=db, user=current_user, module="member_level", operation_type="delete", operation_detail=f"删除会员等级:{level_name}(等级:{level})", company_code=current_user.company_code ) return {"message": f"Member level with ID '{level_id}' deleted successfully"} # 获取当前用户会员权益使用情况 @router.get("/my/benefits") async def get_my_member_benefits( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取当前用户的会员权益及使用情况""" # 获取用户会员等级 membership = db.query(Membership).filter( Membership.company_code == current_user.company_code ).order_by(Membership.end_date.desc()).first() if not membership: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="未找到会员信息" ) # 获取会员等级详情 member_level = db.query(MemberLevel).filter(MemberLevel.level == membership.level).first() if not member_level: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="未找到会员等级配置" ) # 统计数据 # 1. 产品SKU使用数量 product_count = db.query(Product).filter( Product.company_code == current_user.company_code ).count() # 2. 图片空间使用情况 (Media模型的file_size是字节) total_image_size_bytes = db.query(func.sum(Media.file_size)).filter( Media.company_code == current_user.company_code, Media.media_type == 'image' ).scalar() or 0 # 直接转换为GB (字节 -> GB) total_image_size_gb = round(total_image_size_bytes / BYTES_PER_GB, 2) # 3. 每个SKU平均图片数量 product_media_count = db.query( ProductMedia.product_id, func.count(ProductMedia.id).label('media_count') ).filter( ProductMedia.company_code == current_user.company_code, ProductMedia.media_type == 'image' ).group_by(ProductMedia.product_id).all() avg_images_per_sku = round( sum([pm.media_count for pm in product_media_count]) / len(product_media_count), 1 ) if product_media_count else 0 # 4. 视频数量统计 video_count = db.query(ProductMedia).filter( ProductMedia.company_code == current_user.company_code, ProductMedia.media_type == 'video' ).count() # 计算每个产品的平均视频数 avg_videos_per_sku = round(video_count / product_count, 1) if product_count > 0 else 0 # 5. 邮件营销使用数量(这里暂时返回0,需要邮件模块实现后更新) email_used = 0 # 计算会员到期剩余天数 from datetime import datetime days_remaining = 0 if membership.end_date: delta = membership.end_date - datetime.now().date() days_remaining = max(0, delta.days) # 计算各权益的使用率指数 (0-100) def calculate_usage_index(used, total): if not total or total == 0: return 0 return min(round((used / total) * 100, 1), 100) benefits = { "level": membership.level, "level_name": member_level.name, "start_date": membership.start_date, "end_date": membership.end_date, "days_remaining": days_remaining, "items": [ { "name": "独立站部署节点", "icon": "🌐", "used": 1, "total": member_level.deployment_nodes or 0, "unit": "个", "usage_index": calculate_usage_index(1, member_level.deployment_nodes or 0), "description": "独立网站部署节点数量" }, { "name": "产品SKU数量", "icon": "📦", "used": product_count, "total": member_level.product_sku_limit or 0, "unit": "个", "usage_index": calculate_usage_index(product_count, member_level.product_sku_limit or 0), "description": "已使用产品SKU数量" }, { "name": "邮件营销推送", "icon": "📧", "used": email_used, "total": member_level.email_marketing_limit or 0, "unit": "封", "usage_index": calculate_usage_index(email_used, member_level.email_marketing_limit or 0), "description": "邮件营销推送数量" }, { "name": "图片存储空间", "icon": "🖼️", "used": total_image_size_gb, "total": member_level.image_storage_limit or 0, "unit": "GB", "usage_index": calculate_usage_index(total_image_size_gb, member_level.image_storage_limit or 0), "description": "已使用图片存储空间" }, { "name": "每SKU图片", "icon": "📸", "used": avg_images_per_sku, "total": member_level.sku_image_limit or 0, "unit": "张", "usage_index": calculate_usage_index(avg_images_per_sku, member_level.sku_image_limit or 0), "description": "每个SKU平均图片数量" }, { "name": "每SKU视频", "icon": "🎬", "used": avg_videos_per_sku, "total": member_level.sku_video_limit or 0, "unit": "个", "usage_index": calculate_usage_index(avg_videos_per_sku, member_level.sku_video_limit or 0), "description": "每个SKU平均视频数量" }, { "name": "模板选择权限", "icon": "🎨", "used": 1 if member_level.template_access else 0, "total": 1, "unit": "", "usage_index": 100 if member_level.template_access else 0, "description": "是否可访问模板库", "is_boolean": True }, { "name": "GEO/SEO关键词营销", "icon": "🔍", "used": 1 if member_level.geo_seo_access else 0, "total": 1, "unit": "", "usage_index": 100 if member_level.geo_seo_access else 0, "description": "是否支持SEO/ GEO营销", "is_boolean": True }, { "name": "社交平台自动化营销", "icon": "📱", "used": 1 if member_level.social_automation_access else 0, "total": 1, "unit": "", "usage_index": 100 if member_level.social_automation_access else 0, "description": "是否支持社交媒体自动化", "is_boolean": True }, { "name": "定制化需求开发", "icon": "🛠️", "used": 1 if member_level.custom_development_access else 0, "total": 1, "unit": "", "usage_index": 100 if member_level.custom_development_access else 0, "description": "是否支持定制化开发", "is_boolean": True } ], "summary": { "total_benefits": 10, "enabled_benefits": sum([ 1, # 部署节点 1, # SKU 1, # 邮件 1, # 图片存储 1, # SKU图片 1, # SKU视频 1 if member_level.template_access else 0, 1 if member_level.geo_seo_access else 0, 1 if member_level.social_automation_access else 0, 1 if member_level.custom_development_access else 0 ]), "overall_usage_index": round(sum([ calculate_usage_index(1, member_level.deployment_nodes or 0), calculate_usage_index(product_count, member_level.product_sku_limit or 0), calculate_usage_index(email_used, member_level.email_marketing_limit or 0), calculate_usage_index(total_image_size_gb, member_level.image_storage_limit or 0), calculate_usage_index(avg_images_per_sku, member_level.sku_image_limit or 0), calculate_usage_index(avg_videos_per_sku, member_level.sku_video_limit or 0), 100 if member_level.template_access else 0, 100 if member_level.geo_seo_access else 0, 100 if member_level.social_automation_access else 0, 100 if member_level.custom_development_access else 0 ]) / 10, 1) } } return benefits