样式:Tab栏美化并将接口接齐
This commit is contained in:
parent
d7ec1d530a
commit
21c75461b4
|
|
@ -21,6 +21,7 @@ from lover.routers import friend as friend_router
|
||||||
from lover.routers import msg as msg_router
|
from lover.routers import msg as msg_router
|
||||||
from lover.routers import huanxin as huanxin_router
|
from lover.routers import huanxin as huanxin_router
|
||||||
from lover.routers import user as user_router
|
from lover.routers import user as user_router
|
||||||
|
from lover.routers import music_library as music_library_router
|
||||||
from lover.task_queue import start_sing_workers
|
from lover.task_queue import start_sing_workers
|
||||||
from lover.config import settings
|
from lover.config import settings
|
||||||
|
|
||||||
|
|
@ -82,6 +83,7 @@ app.include_router(friend_router.router)
|
||||||
app.include_router(msg_router.router)
|
app.include_router(msg_router.router)
|
||||||
app.include_router(huanxin_router.router)
|
app.include_router(huanxin_router.router)
|
||||||
app.include_router(user_router.router)
|
app.include_router(user_router.router)
|
||||||
|
app.include_router(music_library_router.router)
|
||||||
|
|
||||||
|
|
||||||
@app.exception_handler(HTTPException)
|
@app.exception_handler(HTTPException)
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,35 @@ class OutfitItem(Base):
|
||||||
updatetime = Column(BigInteger)
|
updatetime = Column(BigInteger)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicLibrary(Base):
|
||||||
|
__tablename__ = "nf_music_library"
|
||||||
|
|
||||||
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(BigInteger, nullable=False, index=True)
|
||||||
|
title = Column(String(255), nullable=False)
|
||||||
|
artist = Column(String(255))
|
||||||
|
music_url = Column(String(500), nullable=False)
|
||||||
|
cover_url = Column(String(500))
|
||||||
|
duration = Column(Integer)
|
||||||
|
upload_type = Column(String(10), nullable=False, default="link") # file, link
|
||||||
|
is_public = Column(Integer, nullable=False, default=1)
|
||||||
|
play_count = Column(Integer, nullable=False, default=0)
|
||||||
|
like_count = Column(Integer, nullable=False, default=0)
|
||||||
|
status = Column(String(20), nullable=False, default="approved") # pending, approved, rejected
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||||
|
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
deleted_at = Column(DateTime)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicLike(Base):
|
||||||
|
__tablename__ = "nf_music_likes"
|
||||||
|
|
||||||
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(BigInteger, nullable=False, index=True)
|
||||||
|
music_id = Column(BigInteger, nullable=False, index=True)
|
||||||
|
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
class OutfitLook(Base):
|
class OutfitLook(Base):
|
||||||
__tablename__ = "nf_outfit_looks"
|
__tablename__ = "nf_outfit_looks"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,7 @@ class ApiResponse(BaseModel, Generic[T]):
|
||||||
|
|
||||||
def success_response(data: Optional[T] = None, msg: str = "ok") -> ApiResponse[T]:
|
def success_response(data: Optional[T] = None, msg: str = "ok") -> ApiResponse[T]:
|
||||||
return ApiResponse[T](code=1, msg=msg, data=data)
|
return ApiResponse[T](code=1, msg=msg, data=data)
|
||||||
|
|
||||||
|
|
||||||
|
def error_response(msg: str = "error", code: int = 0, data: Optional[T] = None) -> ApiResponse[T]:
|
||||||
|
return ApiResponse[T](code=code, msg=msg, data=data)
|
||||||
|
|
|
||||||
398
lover/routers/music_library.py
Normal file
398
lover/routers/music_library.py
Normal file
|
|
@ -0,0 +1,398 @@
|
||||||
|
"""
|
||||||
|
音乐库路由
|
||||||
|
"""
|
||||||
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from lover.db import get_db
|
||||||
|
from lover.deps import get_current_user
|
||||||
|
from lover.models import User, MusicLibrary, MusicLike
|
||||||
|
from lover.response import success_response, error_response, ApiResponse
|
||||||
|
from lover.config import settings
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/music", tags=["音乐库"])
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Pydantic 模型 ==========
|
||||||
|
class MusicOut(BaseModel):
|
||||||
|
id: int
|
||||||
|
user_id: int
|
||||||
|
username: str = ""
|
||||||
|
user_avatar: str = ""
|
||||||
|
title: str
|
||||||
|
artist: Optional[str] = None
|
||||||
|
music_url: str
|
||||||
|
cover_url: Optional[str] = None
|
||||||
|
duration: Optional[int] = None
|
||||||
|
upload_type: str
|
||||||
|
play_count: int = 0
|
||||||
|
like_count: int = 0
|
||||||
|
is_liked: bool = False
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class MusicListResponse(BaseModel):
|
||||||
|
total: int
|
||||||
|
list: List[MusicOut]
|
||||||
|
|
||||||
|
|
||||||
|
class MusicAddLinkRequest(BaseModel):
|
||||||
|
title: str = Field(..., min_length=1, max_length=255, description="歌曲标题")
|
||||||
|
artist: Optional[str] = Field(None, max_length=255, description="艺术家")
|
||||||
|
music_url: str = Field(..., min_length=1, max_length=500, description="音乐链接")
|
||||||
|
cover_url: Optional[str] = Field(None, max_length=500, description="封面图链接")
|
||||||
|
duration: Optional[int] = Field(None, description="时长(秒)")
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 辅助函数 ==========
|
||||||
|
def _cdnize(url: str) -> str:
|
||||||
|
"""将相对路径转换为 CDN 完整路径"""
|
||||||
|
if not url:
|
||||||
|
return ""
|
||||||
|
if url.startswith("http://") or url.startswith("https://"):
|
||||||
|
return url
|
||||||
|
cdn_domain = getattr(settings, "CDN_DOMAIN", "")
|
||||||
|
if cdn_domain:
|
||||||
|
return f"{cdn_domain.rstrip('/')}/{url.lstrip('/')}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def _save_upload_file(file: UploadFile, folder: str = "music") -> str:
|
||||||
|
"""保存上传的文件"""
|
||||||
|
# 生成唯一文件名
|
||||||
|
ext = os.path.splitext(file.filename)[1]
|
||||||
|
filename = f"{uuid.uuid4().hex}{ext}"
|
||||||
|
|
||||||
|
# 创建保存路径
|
||||||
|
upload_dir = os.path.join("public", folder)
|
||||||
|
os.makedirs(upload_dir, exist_ok=True)
|
||||||
|
|
||||||
|
file_path = os.path.join(upload_dir, filename)
|
||||||
|
|
||||||
|
# 保存文件
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(file.file.read())
|
||||||
|
|
||||||
|
# 返回相对路径
|
||||||
|
return f"{folder}/{filename}"
|
||||||
|
|
||||||
|
|
||||||
|
# ========== API 路由 ==========
|
||||||
|
@router.get("/library", response_model=ApiResponse[MusicListResponse])
|
||||||
|
def get_music_library(
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = 20,
|
||||||
|
keyword: Optional[str] = None,
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取音乐库列表(所有公开的音乐)
|
||||||
|
"""
|
||||||
|
query = db.query(MusicLibrary).filter(
|
||||||
|
MusicLibrary.deleted_at.is_(None),
|
||||||
|
MusicLibrary.is_public == 1,
|
||||||
|
MusicLibrary.status == "approved"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 关键词搜索
|
||||||
|
if keyword:
|
||||||
|
query = query.filter(
|
||||||
|
(MusicLibrary.title.like(f"%{keyword}%")) |
|
||||||
|
(MusicLibrary.artist.like(f"%{keyword}%"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# 总数
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
# 分页
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
music_list = query.order_by(MusicLibrary.created_at.desc()).offset(offset).limit(page_size).all()
|
||||||
|
|
||||||
|
# 获取用户点赞信息
|
||||||
|
liked_music_ids = set()
|
||||||
|
if user:
|
||||||
|
likes = db.query(MusicLike.music_id).filter(MusicLike.user_id == user.id).all()
|
||||||
|
liked_music_ids = {like.music_id for like in likes}
|
||||||
|
|
||||||
|
# 获取上传用户信息
|
||||||
|
user_ids = [m.user_id for m in music_list]
|
||||||
|
users_map = {}
|
||||||
|
if user_ids:
|
||||||
|
users = db.query(User).filter(User.id.in_(user_ids)).all()
|
||||||
|
users_map = {u.id: u for u in users}
|
||||||
|
|
||||||
|
# 构建返回数据
|
||||||
|
result = []
|
||||||
|
for music in music_list:
|
||||||
|
uploader = users_map.get(music.user_id)
|
||||||
|
result.append(MusicOut(
|
||||||
|
id=music.id,
|
||||||
|
user_id=music.user_id,
|
||||||
|
username=uploader.username if uploader else "未知用户",
|
||||||
|
user_avatar=_cdnize(uploader.avatar) if uploader and uploader.avatar else "",
|
||||||
|
title=music.title,
|
||||||
|
artist=music.artist,
|
||||||
|
music_url=_cdnize(music.music_url),
|
||||||
|
cover_url=_cdnize(music.cover_url) if music.cover_url else "",
|
||||||
|
duration=music.duration,
|
||||||
|
upload_type=music.upload_type,
|
||||||
|
play_count=music.play_count,
|
||||||
|
like_count=music.like_count,
|
||||||
|
is_liked=music.id in liked_music_ids,
|
||||||
|
created_at=music.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
return success_response(MusicListResponse(total=total, list=result))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/add-link", response_model=ApiResponse[MusicOut])
|
||||||
|
def add_music_link(
|
||||||
|
data: MusicAddLinkRequest,
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
添加音乐链接到音乐库
|
||||||
|
"""
|
||||||
|
# 创建音乐记录
|
||||||
|
music = MusicLibrary(
|
||||||
|
user_id=user.id,
|
||||||
|
title=data.title,
|
||||||
|
artist=data.artist,
|
||||||
|
music_url=data.music_url,
|
||||||
|
cover_url=data.cover_url,
|
||||||
|
duration=data.duration,
|
||||||
|
upload_type="link",
|
||||||
|
is_public=1,
|
||||||
|
status="approved"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(music)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(music)
|
||||||
|
|
||||||
|
return success_response(MusicOut(
|
||||||
|
id=music.id,
|
||||||
|
user_id=music.user_id,
|
||||||
|
username=user.username,
|
||||||
|
user_avatar=_cdnize(user.avatar) if user.avatar else "",
|
||||||
|
title=music.title,
|
||||||
|
artist=music.artist,
|
||||||
|
music_url=_cdnize(music.music_url),
|
||||||
|
cover_url=_cdnize(music.cover_url) if music.cover_url else "",
|
||||||
|
duration=music.duration,
|
||||||
|
upload_type=music.upload_type,
|
||||||
|
play_count=music.play_count,
|
||||||
|
like_count=music.like_count,
|
||||||
|
is_liked=False,
|
||||||
|
created_at=music.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload", response_model=ApiResponse[MusicOut])
|
||||||
|
async def upload_music_file(
|
||||||
|
title: str = Form(...),
|
||||||
|
artist: Optional[str] = Form(None),
|
||||||
|
duration: Optional[int] = Form(None),
|
||||||
|
music_file: UploadFile = File(...),
|
||||||
|
cover_file: Optional[UploadFile] = File(None),
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
上传音乐文件到音乐库
|
||||||
|
"""
|
||||||
|
# 检查文件类型
|
||||||
|
allowed_audio = [".mp3", ".wav", ".m4a", ".flac", ".ogg"]
|
||||||
|
allowed_image = [".jpg", ".jpeg", ".png", ".gif", ".webp"]
|
||||||
|
|
||||||
|
music_ext = os.path.splitext(music_file.filename)[1].lower()
|
||||||
|
if music_ext not in allowed_audio:
|
||||||
|
return error_response("不支持的音频格式")
|
||||||
|
|
||||||
|
# 保存音乐文件
|
||||||
|
music_path = _save_upload_file(music_file, "music")
|
||||||
|
|
||||||
|
# 保存封面文件
|
||||||
|
cover_path = None
|
||||||
|
if cover_file:
|
||||||
|
cover_ext = os.path.splitext(cover_file.filename)[1].lower()
|
||||||
|
if cover_ext in allowed_image:
|
||||||
|
cover_path = _save_upload_file(cover_file, "music/covers")
|
||||||
|
|
||||||
|
# 创建音乐记录
|
||||||
|
music = MusicLibrary(
|
||||||
|
user_id=user.id,
|
||||||
|
title=title,
|
||||||
|
artist=artist,
|
||||||
|
music_url=music_path,
|
||||||
|
cover_url=cover_path,
|
||||||
|
duration=duration,
|
||||||
|
upload_type="file",
|
||||||
|
is_public=1,
|
||||||
|
status="approved"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(music)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(music)
|
||||||
|
|
||||||
|
return success_response(MusicOut(
|
||||||
|
id=music.id,
|
||||||
|
user_id=music.user_id,
|
||||||
|
username=user.username,
|
||||||
|
user_avatar=_cdnize(user.avatar) if user.avatar else "",
|
||||||
|
title=music.title,
|
||||||
|
artist=music.artist,
|
||||||
|
music_url=_cdnize(music.music_url),
|
||||||
|
cover_url=_cdnize(music.cover_url) if music.cover_url else "",
|
||||||
|
duration=music.duration,
|
||||||
|
upload_type=music.upload_type,
|
||||||
|
play_count=music.play_count,
|
||||||
|
like_count=music.like_count,
|
||||||
|
is_liked=False,
|
||||||
|
created_at=music.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{music_id}/like", response_model=ApiResponse[dict])
|
||||||
|
def like_music(
|
||||||
|
music_id: int,
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
点赞音乐
|
||||||
|
"""
|
||||||
|
# 检查音乐是否存在
|
||||||
|
music = db.query(MusicLibrary).filter(
|
||||||
|
MusicLibrary.id == music_id,
|
||||||
|
MusicLibrary.deleted_at.is_(None)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not music:
|
||||||
|
return error_response("音乐不存在")
|
||||||
|
|
||||||
|
# 检查是否已点赞
|
||||||
|
existing_like = db.query(MusicLike).filter(
|
||||||
|
MusicLike.user_id == user.id,
|
||||||
|
MusicLike.music_id == music_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_like:
|
||||||
|
# 取消点赞
|
||||||
|
db.delete(existing_like)
|
||||||
|
music.like_count = max(0, music.like_count - 1)
|
||||||
|
db.commit()
|
||||||
|
return success_response({"is_liked": False, "like_count": music.like_count})
|
||||||
|
else:
|
||||||
|
# 添加点赞
|
||||||
|
like = MusicLike(user_id=user.id, music_id=music_id)
|
||||||
|
db.add(like)
|
||||||
|
music.like_count += 1
|
||||||
|
db.commit()
|
||||||
|
return success_response({"is_liked": True, "like_count": music.like_count})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{music_id}/play", response_model=ApiResponse[dict])
|
||||||
|
def record_play(
|
||||||
|
music_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
记录播放次数
|
||||||
|
"""
|
||||||
|
music = db.query(MusicLibrary).filter(
|
||||||
|
MusicLibrary.id == music_id,
|
||||||
|
MusicLibrary.deleted_at.is_(None)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not music:
|
||||||
|
return error_response("音乐不存在")
|
||||||
|
|
||||||
|
music.play_count += 1
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return success_response({"play_count": music.play_count})
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{music_id}", response_model=ApiResponse[dict])
|
||||||
|
def delete_music(
|
||||||
|
music_id: int,
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
删除音乐(仅限上传者)
|
||||||
|
"""
|
||||||
|
music = db.query(MusicLibrary).filter(
|
||||||
|
MusicLibrary.id == music_id,
|
||||||
|
MusicLibrary.deleted_at.is_(None)
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not music:
|
||||||
|
return error_response("音乐不存在")
|
||||||
|
|
||||||
|
if music.user_id != user.id:
|
||||||
|
return error_response("无权删除此音乐")
|
||||||
|
|
||||||
|
music.deleted_at = datetime.utcnow()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return success_response({"message": "删除成功"})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/my", response_model=ApiResponse[MusicListResponse])
|
||||||
|
def get_my_music(
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = 20,
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取我上传的音乐
|
||||||
|
"""
|
||||||
|
query = db.query(MusicLibrary).filter(
|
||||||
|
MusicLibrary.user_id == user.id,
|
||||||
|
MusicLibrary.deleted_at.is_(None)
|
||||||
|
)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
music_list = query.order_by(MusicLibrary.created_at.desc()).offset(offset).limit(page_size).all()
|
||||||
|
|
||||||
|
# 获取点赞信息
|
||||||
|
liked_music_ids = set()
|
||||||
|
likes = db.query(MusicLike.music_id).filter(MusicLike.user_id == user.id).all()
|
||||||
|
liked_music_ids = {like.music_id for like in likes}
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for music in music_list:
|
||||||
|
result.append(MusicOut(
|
||||||
|
id=music.id,
|
||||||
|
user_id=music.user_id,
|
||||||
|
username=user.username,
|
||||||
|
user_avatar=_cdnize(user.avatar) if user.avatar else "",
|
||||||
|
title=music.title,
|
||||||
|
artist=music.artist,
|
||||||
|
music_url=_cdnize(music.music_url),
|
||||||
|
cover_url=_cdnize(music.cover_url) if music.cover_url else "",
|
||||||
|
duration=music.duration,
|
||||||
|
upload_type=music.upload_type,
|
||||||
|
play_count=music.play_count,
|
||||||
|
like_count=music.like_count,
|
||||||
|
is_liked=music.id in liked_music_ids,
|
||||||
|
created_at=music.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
return success_response(MusicListResponse(total=total, list=result))
|
||||||
161
start_php_advanced.bat
Normal file
161
start_php_advanced.bat
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
title PHP 开发服务器
|
||||||
|
|
||||||
|
:MENU
|
||||||
|
cls
|
||||||
|
echo ========================================
|
||||||
|
echo PHP 开发服务器启动脚本 (高级版)
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo 请选择启动模式:
|
||||||
|
echo.
|
||||||
|
echo [1] 快速启动 (端口 8080)
|
||||||
|
echo [2] 自定义端口
|
||||||
|
echo [3] 查看 PHP 信息
|
||||||
|
echo [4] 退出
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
set /p choice=请输入选项 (1-4):
|
||||||
|
|
||||||
|
if "%choice%"=="1" goto QUICK_START
|
||||||
|
if "%choice%"=="2" goto CUSTOM_PORT
|
||||||
|
if "%choice%"=="3" goto PHP_INFO
|
||||||
|
if "%choice%"=="4" goto END
|
||||||
|
echo [错误] 无效选项,请重新选择
|
||||||
|
timeout /t 2 >nul
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:QUICK_START
|
||||||
|
set PORT=8080
|
||||||
|
goto START_SERVER
|
||||||
|
|
||||||
|
:CUSTOM_PORT
|
||||||
|
echo.
|
||||||
|
set /p PORT=请输入端口号 (例如: 8080):
|
||||||
|
if "%PORT%"=="" (
|
||||||
|
echo [错误] 端口号不能为空
|
||||||
|
timeout /t 2 >nul
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
goto START_SERVER
|
||||||
|
|
||||||
|
:START_SERVER
|
||||||
|
cls
|
||||||
|
echo ========================================
|
||||||
|
echo 正在启动 PHP 开发服务器...
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 设置 PHP 路径
|
||||||
|
set PHP_PATH=D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe
|
||||||
|
|
||||||
|
REM 检查 PHP 是否存在
|
||||||
|
if not exist "%PHP_PATH%" (
|
||||||
|
echo [错误] PHP 未找到: %PHP_PATH%
|
||||||
|
echo.
|
||||||
|
echo 请修改脚本中的 PHP_PATH 变量
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
|
||||||
|
REM 显示 PHP 版本
|
||||||
|
echo [信息] PHP 版本:
|
||||||
|
"%PHP_PATH%" -v | findstr /C:"PHP"
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 设置项目根目录
|
||||||
|
set PROJECT_ROOT=%~dp0xunifriend_RaeeC\public
|
||||||
|
|
||||||
|
REM 检查项目目录是否存在
|
||||||
|
if not exist "%PROJECT_ROOT%" (
|
||||||
|
echo [错误] 项目目录未找到: %PROJECT_ROOT%
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
|
||||||
|
REM 获取本机 IP 地址
|
||||||
|
for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /C:"IPv4"') do (
|
||||||
|
set LOCAL_IP=%%a
|
||||||
|
goto :IP_FOUND
|
||||||
|
)
|
||||||
|
:IP_FOUND
|
||||||
|
set LOCAL_IP=%LOCAL_IP: =%
|
||||||
|
|
||||||
|
REM 设置服务器参数
|
||||||
|
set HOST=0.0.0.0
|
||||||
|
|
||||||
|
echo [信息] 项目目录: %PROJECT_ROOT%
|
||||||
|
echo [信息] 服务器端口: %PORT%
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo 访问地址:
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo [本地访问]
|
||||||
|
echo http://127.0.0.1:%PORT%
|
||||||
|
echo http://localhost:%PORT%
|
||||||
|
echo.
|
||||||
|
echo [局域网访问]
|
||||||
|
echo http://%LOCAL_IP%:%PORT%
|
||||||
|
echo.
|
||||||
|
echo [管理后台]
|
||||||
|
echo http://127.0.0.1:%PORT%/admin
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo [提示] 按 Ctrl+C 停止服务器
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 询问是否打开浏览器
|
||||||
|
set /p OPEN_BROWSER=是否自动打开浏览器? (Y/N):
|
||||||
|
if /i "%OPEN_BROWSER%"=="Y" (
|
||||||
|
echo [信息] 正在打开浏览器...
|
||||||
|
start http://127.0.0.1:%PORT%
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [信息] 服务器启动中...
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM 启动 PHP 内置服务器
|
||||||
|
cd /d "%PROJECT_ROOT%"
|
||||||
|
"%PHP_PATH%" -S %HOST%:%PORT% -t .
|
||||||
|
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:PHP_INFO
|
||||||
|
cls
|
||||||
|
echo ========================================
|
||||||
|
echo PHP 信息
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set PHP_PATH=D:\2_part\php-8.0.0-Win32-vs16-x64\php.exe
|
||||||
|
|
||||||
|
if not exist "%PHP_PATH%" (
|
||||||
|
echo [错误] PHP 未找到: %PHP_PATH%
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [PHP 版本]
|
||||||
|
"%PHP_PATH%" -v
|
||||||
|
echo.
|
||||||
|
echo [PHP 配置文件]
|
||||||
|
"%PHP_PATH%" --ini
|
||||||
|
echo.
|
||||||
|
echo [已加载的扩展]
|
||||||
|
"%PHP_PATH%" -m
|
||||||
|
echo.
|
||||||
|
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:END
|
||||||
|
echo.
|
||||||
|
echo 感谢使用!
|
||||||
|
timeout /t 1 >nul
|
||||||
|
exit
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""测试视频生成次数重置功能"""
|
|
||||||
from datetime import datetime, date
|
|
||||||
from lover.db import SessionLocal
|
|
||||||
from lover.models import User
|
|
||||||
|
|
||||||
def test_reset_logic():
|
|
||||||
db = SessionLocal()
|
|
||||||
try:
|
|
||||||
user = db.query(User).filter(User.id == 70).with_for_update().first()
|
|
||||||
if not user:
|
|
||||||
print("用户不存在")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"重置前:")
|
|
||||||
print(f" video_gen_remaining: {user.video_gen_remaining}")
|
|
||||||
print(f" video_gen_reset_date: {user.video_gen_reset_date}")
|
|
||||||
|
|
||||||
# 模拟重置逻辑
|
|
||||||
current_timestamp = int(datetime.utcnow().timestamp())
|
|
||||||
is_vip = user.vip_endtime and user.vip_endtime > current_timestamp
|
|
||||||
|
|
||||||
if is_vip:
|
|
||||||
last_reset = user.video_gen_reset_date
|
|
||||||
today = datetime.utcnow().date()
|
|
||||||
|
|
||||||
print(f"\n检查:")
|
|
||||||
print(f" 是否 VIP: {is_vip}")
|
|
||||||
print(f" 上次重置日期: {last_reset}")
|
|
||||||
print(f" 今天日期: {today}")
|
|
||||||
print(f" 需要重置: {not last_reset or last_reset < today}")
|
|
||||||
|
|
||||||
if not last_reset or last_reset < today:
|
|
||||||
user.video_gen_remaining = 2
|
|
||||||
user.video_gen_reset_date = today
|
|
||||||
db.add(user)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
print(f"\n重置后:")
|
|
||||||
print(f" video_gen_remaining: {user.video_gen_remaining}")
|
|
||||||
print(f" video_gen_reset_date: {user.video_gen_reset_date}")
|
|
||||||
else:
|
|
||||||
print("\n今天已经重置过了,不需要再次重置")
|
|
||||||
else:
|
|
||||||
print("用户不是 VIP,不重置")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_reset_logic()
|
|
||||||
33
test_vip.py
33
test_vip.py
|
|
@ -1,33 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""测试 VIP 功能"""
|
|
||||||
from datetime import datetime
|
|
||||||
from lover.db import SessionLocal
|
|
||||||
from lover.models import User
|
|
||||||
|
|
||||||
def test_vip_status():
|
|
||||||
db = SessionLocal()
|
|
||||||
try:
|
|
||||||
# 测试用户 70 和 84
|
|
||||||
for user_id in [70, 84]:
|
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
|
||||||
if not user:
|
|
||||||
print(f"用户 {user_id} 不存在")
|
|
||||||
continue
|
|
||||||
|
|
||||||
current_timestamp = int(datetime.utcnow().timestamp())
|
|
||||||
is_vip = user.vip_endtime and user.vip_endtime > current_timestamp
|
|
||||||
|
|
||||||
print(f"\n用户 {user_id} ({user.nickname}):")
|
|
||||||
print(f" VIP 到期时间戳: {user.vip_endtime}")
|
|
||||||
if user.vip_endtime:
|
|
||||||
vip_end_date = datetime.fromtimestamp(user.vip_endtime)
|
|
||||||
print(f" VIP 到期日期: {vip_end_date}")
|
|
||||||
print(f" 当前时间戳: {current_timestamp}")
|
|
||||||
print(f" 是否 VIP: {is_vip}")
|
|
||||||
print(f" 视频生成次数: {user.video_gen_remaining}")
|
|
||||||
print(f" 上次重置日期: {user.video_gen_reset_date}")
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_vip_status()
|
|
||||||
File diff suppressed because it is too large
Load Diff
2
开发/2026年2月3日/2026年2月3日.md
Normal file
2
开发/2026年2月3日/2026年2月3日.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
1. 将所有tab栏的功能接上并且将界面美化
|
||||||
|
2. 增加音乐库功能
|
||||||
34
开发/2026年2月3日/音乐库.sql
Normal file
34
开发/2026年2月3日/音乐库.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
-- 音乐库表
|
||||||
|
CREATE TABLE IF NOT EXISTS `nf_music_library` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`user_id` bigint(20) NOT NULL COMMENT '上传用户ID',
|
||||||
|
`title` varchar(255) NOT NULL COMMENT '歌曲标题',
|
||||||
|
`artist` varchar(255) DEFAULT NULL COMMENT '艺术家',
|
||||||
|
`music_url` varchar(500) NOT NULL COMMENT '音乐文件URL或链接',
|
||||||
|
`cover_url` varchar(500) DEFAULT NULL COMMENT '封面图URL',
|
||||||
|
`duration` int(11) DEFAULT NULL COMMENT '时长(秒)',
|
||||||
|
`upload_type` enum('file','link') NOT NULL DEFAULT 'link' COMMENT '上传类型:file=文件上传,link=链接',
|
||||||
|
`is_public` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否公开:1=公开,0=私有',
|
||||||
|
`play_count` int(11) NOT NULL DEFAULT 0 COMMENT '播放次数',
|
||||||
|
`like_count` int(11) NOT NULL DEFAULT 0 COMMENT '点赞次数',
|
||||||
|
`status` enum('pending','approved','rejected') NOT NULL DEFAULT 'approved' COMMENT '审核状态',
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_is_public` (`is_public`),
|
||||||
|
KEY `idx_created_at` (`created_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='音乐库表';
|
||||||
|
|
||||||
|
-- 用户音乐点赞表
|
||||||
|
CREATE TABLE IF NOT EXISTS `nf_music_likes` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
|
||||||
|
`music_id` bigint(20) NOT NULL COMMENT '音乐ID',
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_music` (`user_id`, `music_id`),
|
||||||
|
KEY `idx_music_id` (`music_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户音乐点赞表';
|
||||||
Loading…
Reference in New Issue
Block a user