整理了路由,添加了前缀/api/rank

This commit is contained in:
xbh 2025-10-18 00:04:53 +08:00
parent 93928b8a66
commit 2c0f891a89
3 changed files with 472 additions and 541 deletions

73
app.py
View File

@ -1,4 +1,4 @@
from flask import Flask from flask import Flask, jsonify
from flask_cors import CORS from flask_cors import CORS
import logging import logging
import os import os
@ -20,74 +20,13 @@ logging.basicConfig(
] ]
) )
# 导入路由 # 导入并注册蓝图
from routers.rank_api_routes import api from routers.rank_api_routes import rank_bp
app.register_blueprint(rank_bp)
# 注册路由
@app.route('/')
def index():
"""API首页"""
from flask import jsonify
return jsonify({
"name": "抖音播放量数据API服务",
"version": "2.0",
"description": "主程序服务 - 整合小程序API功能",
"endpoints": {
"/api/videos": "获取视频列表 (支持分页和排序)",
"/api/top": "获取热门视频榜单",
"/api/search": "搜索视频",
"/api/detail": "获取视频详情",
"/api/stats": "获取统计信息",
"/api/health": "健康检查"
},
"features": [
"分页支持",
"多种排序方式",
"搜索功能",
"详情查看",
"统计分析",
"小程序优化"
]
})
# 注册小程序API路由
@app.route('/api/videos')
def get_videos():
return api.get_videos()
@app.route('/api/top')
def get_top():
return api.get_top()
@app.route('/api/search')
def search():
return api.search()
@app.route('/api/detail')
def get_detail():
return api.get_detail()
@app.route('/api/stats')
def get_stats():
return api.get_stats()
@app.route('/api/health')
def health_check():
return api.health_check()
if __name__ == '__main__': if __name__ == '__main__':
print("启动主程序服务...") print("启动主程序服务...")
print("服务地址: http://localhost:5000") print("服务地址: http://localhost:5001")
print("API接口列表:")
print(" - GET / 显示API信息")
print(" - GET /api/videos?page=1&limit=20&sort=playcount 获取视频列表(总播放量排序)")
print(" - GET /api/videos?page=1&limit=20&sort=growth 获取视频列表(增长排序,默认昨天到今天的差值)")
print(" - GET /api/videos?page=1&limit=20&sort=growth&start_date=2025-10-16&end_date=2025-10-17 获取视频列表(自定义日期范围增长排序)")
print(" - GET /api/top?limit=10 获取热门榜单")
print(" - GET /api/search?q=关键词&page=1&limit=10 搜索视频")
print(" - GET /api/detail?id=视频ID 获取视频详情")
print(" - GET /api/stats 获取统计信息")
print(" - GET /api/health 健康检查")
print("专为小程序优化:分页、搜索、详情、统计、增长排序、自定义日期范围")
app.run(host='0.0.0.0', port=5000, debug=True) app.run(host='0.0.0.0', port=5001, debug=True)

View File

@ -2,8 +2,8 @@ import os
import importlib import importlib
# 数据库配置 # 数据库配置
# MONGO_URI = "mongodb://localhost:27017" MONGO_URI = "mongodb://localhost:27017"
MONGO_URI = "mongodb://mongouser:Jdei2243afN@172.16.0.6:27017,172.16.0.4:27017/test?replicaSet=cmgo-r6qkaern_0&authSource=admin" # MONGO_URI = "mongodb://mongouser:Jdei2243afN@172.16.0.6:27017,172.16.0.4:27017/test?replicaSet=cmgo-r6qkaern_0&authSource=admin"
MONGO_DB_NAME = "kemeng_media" MONGO_DB_NAME = "kemeng_media"
# 应用配置 # 应用配置
@ -15,6 +15,6 @@ LOG_LEVEL = 'INFO'
LOG_DIR = 'logs' LOG_DIR = 'logs'
# 定时器配置 # 定时器配置
SCHEDULER_TIME = "20:23" # 定时器执行时间,格式为 HH:MM (24小时制) SCHEDULER_TIME = "00:01" # 定时器执行时间,格式为 HH:MM (24小时制)
print(f"Successfully loaded configuration for environment: {APP_ENV}") print(f"Successfully loaded configuration for environment: {APP_ENV}")

View File

@ -5,34 +5,19 @@
优化的数据格式和接口设计专为小程序使用 优化的数据格式和接口设计专为小程序使用
""" """
from pymongo import MongoClient from flask import Blueprint, request, jsonify
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
import re import re
from database import db
class MiniprogramAPI: # 创建蓝图
def __init__(self): rank_bp = Blueprint('rank', __name__, url_prefix='/api/rank')
self.client = None
self.db = None
self.collection = None
self.connect_mongodb()
def connect_mongodb(self): # 获取数据库集合
"""连接MongoDB数据库""" collection = db['Rankings_list']
try:
self.client = MongoClient('mongodb://localhost:27017/')
# 测试连接
self.client.admin.command('ping')
# 使用数据库与集合
self.db = self.client['Rankings']
self.collection = self.db['Rankings_list']
logging.info("MongoDB连接成功")
return True
except Exception as e:
logging.error(f"MongoDB连接失败: {e}")
return False
def format_playcount(self, playcount_str): def format_playcount(playcount_str):
"""格式化播放量字符串为数字""" """格式化播放量字符串为数字"""
if not playcount_str: if not playcount_str:
return 0 return 0
@ -56,7 +41,7 @@ class MiniprogramAPI:
except: except:
return 0 return 0
def format_cover_url(self, cover_data): def format_cover_url(cover_data):
"""格式化封面图片URL""" """格式化封面图片URL"""
if not cover_data: if not cover_data:
return "" return ""
@ -68,7 +53,7 @@ class MiniprogramAPI:
else: else:
return "" return ""
def format_time(self, time_obj): def format_time(time_obj):
"""格式化时间""" """格式化时间"""
if not time_obj: if not time_obj:
return "" return ""
@ -78,11 +63,11 @@ class MiniprogramAPI:
else: else:
return str(time_obj) return str(time_obj)
def format_mix_item(self, doc): def format_mix_item(doc):
"""格式化合集数据项 - 完全按照数据库原始字段返回""" """格式化合集数据项 - 完全按照数据库原始字段返回"""
return { return {
"_id": str(doc.get("_id", "")), "_id": str(doc.get("_id", "")),
"batch_time": self.format_time(doc.get("batch_time")), "batch_time": format_time(doc.get("batch_time")),
"mix_name": doc.get("mix_name", ""), "mix_name": doc.get("mix_name", ""),
"video_url": doc.get("video_url", ""), "video_url": doc.get("video_url", ""),
"playcount": doc.get("playcount", ""), "playcount": doc.get("playcount", ""),
@ -93,7 +78,7 @@ class MiniprogramAPI:
"cover_backup_urls": doc.get("cover_backup_urls", []) "cover_backup_urls": doc.get("cover_backup_urls", [])
} }
def get_mix_list(self, page=1, limit=20, sort_by="playcount"): def get_mix_list(page=1, limit=20, sort_by="playcount"):
"""获取合集列表(分页)""" """获取合集列表(分页)"""
try: try:
# 计算跳过的数量 # 计算跳过的数量
@ -102,7 +87,7 @@ class MiniprogramAPI:
# 设置排序字段 # 设置排序字段
if sort_by == "growth": if sort_by == "growth":
# 按增长排序需要特殊处理 # 按增长排序需要特殊处理
return self.get_growth_mixes(page, limit) return get_growth_mixes(page, limit)
else: else:
sort_field = "play_vv" if sort_by == "playcount" else "batch_time" sort_field = "play_vv" if sort_by == "playcount" else "batch_time"
sort_order = -1 # 降序 sort_order = -1 # 降序
@ -132,7 +117,7 @@ class MiniprogramAPI:
{"$limit": limit} {"$limit": limit}
] ]
docs = list(self.collection.aggregate(pipeline)) docs = list(collection.aggregate(pipeline))
# 获取总数 # 获取总数
total_pipeline = [ total_pipeline = [
@ -141,13 +126,13 @@ class MiniprogramAPI:
{"$group": {"_id": "$mix_name"}}, {"$group": {"_id": "$mix_name"}},
{"$count": "total"} {"$count": "total"}
] ]
total_result = list(self.collection.aggregate(total_pipeline)) total_result = list(collection.aggregate(total_pipeline))
total = total_result[0]["total"] if total_result else 0 total = total_result[0]["total"] if total_result else 0
# 格式化数据 # 格式化数据
mix_list = [] mix_list = []
for doc in docs: for doc in docs:
item = self.format_mix_item(doc) item = format_mix_item(doc)
mix_list.append(item) mix_list.append(item)
return { return {
@ -169,7 +154,7 @@ class MiniprogramAPI:
logging.error(f"获取合集列表失败: {e}") logging.error(f"获取合集列表失败: {e}")
return {"success": False, "message": f"获取数据失败: {str(e)}"} return {"success": False, "message": f"获取数据失败: {str(e)}"}
def get_growth_mixes(self, page=1, limit=20, start_date=None, end_date=None): def get_growth_mixes(page=1, limit=20, start_date=None, end_date=None):
"""获取按播放量增长排序的合集列表""" """获取按播放量增长排序的合集列表"""
try: try:
# 计算跳过的数量 # 计算跳过的数量
@ -187,7 +172,7 @@ class MiniprogramAPI:
end_date = datetime.strptime(end_date, "%Y-%m-%d").date() end_date = datetime.strptime(end_date, "%Y-%m-%d").date()
# 查询结束日期的数据 # 查询结束日期的数据
end_cursor = self.collection.find({ end_cursor = collection.find({
"batch_time": { "batch_time": {
"$gte": datetime(end_date.year, end_date.month, end_date.day), "$gte": datetime(end_date.year, end_date.month, end_date.day),
"$lt": datetime(end_date.year, end_date.month, end_date.day) + timedelta(days=1) "$lt": datetime(end_date.year, end_date.month, end_date.day) + timedelta(days=1)
@ -196,7 +181,7 @@ class MiniprogramAPI:
end_data = list(end_cursor) end_data = list(end_cursor)
# 查询开始日期的数据 # 查询开始日期的数据
start_cursor = self.collection.find({ start_cursor = collection.find({
"batch_time": { "batch_time": {
"$gte": datetime(start_date.year, start_date.month, start_date.day), "$gte": datetime(start_date.year, start_date.month, start_date.day),
"$lt": datetime(start_date.year, start_date.month, start_date.day) + timedelta(days=1) "$lt": datetime(start_date.year, start_date.month, start_date.day) + timedelta(days=1)
@ -217,14 +202,14 @@ class MiniprogramAPI:
# 只保留增长为正的数据 # 只保留增长为正的数据
if growth > 0: if growth > 0:
item = self.format_mix_item(end_item) item = format_mix_item(end_item)
item["growth"] = growth item["growth"] = growth
item["start_date"] = start_date.strftime("%Y-%m-%d") item["start_date"] = start_date.strftime("%Y-%m-%d")
item["end_date"] = end_date.strftime("%Y-%m-%d") item["end_date"] = end_date.strftime("%Y-%m-%d")
growth_data.append(item) growth_data.append(item)
else: else:
# 如果开始日期没有数据,但结束日期有,也认为是新增长 # 如果开始日期没有数据,但结束日期有,也认为是新增长
item = self.format_mix_item(end_item) item = format_mix_item(end_item)
item["growth"] = end_item.get("play_vv", 0) item["growth"] = end_item.get("play_vv", 0)
item["start_date"] = start_date.strftime("%Y-%m-%d") item["start_date"] = start_date.strftime("%Y-%m-%d")
item["end_date"] = end_date.strftime("%Y-%m-%d") item["end_date"] = end_date.strftime("%Y-%m-%d")
@ -263,13 +248,13 @@ class MiniprogramAPI:
except Exception as e: except Exception as e:
logging.error(f"获取增长合集列表失败: {e}") logging.error(f"获取增长合集列表失败: {e}")
# 如果增长计算失败,返回按播放量排序的数据作为备选 # 如果增长计算失败,返回按播放量排序的数据作为备选
return self.get_mix_list(page, limit, "playcount") return get_mix_list(page, limit, "playcount")
def get_top_mixes(self, limit=10): def get_top_mixes(limit=10):
"""获取热门合集TOP榜单""" """获取热门合集TOP榜单"""
try: try:
# 按播放量排序获取热门合集 # 按播放量排序获取热门合集
cursor = self.collection.find().sort("play_vv", -1).limit(limit) cursor = collection.find().sort("play_vv", -1).limit(limit)
docs = list(cursor) docs = list(cursor)
if not docs: if not docs:
@ -278,21 +263,21 @@ class MiniprogramAPI:
# 格式化数据 # 格式化数据
top_list = [] top_list = []
for doc in docs: for doc in docs:
item = self.format_mix_item(doc) item = format_mix_item(doc)
top_list.append(item) top_list.append(item)
return { return {
"success": True, "success": True,
"data": top_list, "data": top_list,
"total": len(top_list), "total": len(top_list),
"update_time": self.format_time(docs[0].get("batch_time")) if docs else "" "update_time": format_time(docs[0].get("batch_time")) if docs else ""
} }
except Exception as e: except Exception as e:
logging.error(f"获取热门合集失败: {e}") logging.error(f"获取热门合集失败: {e}")
return {"success": False, "message": f"获取数据失败: {str(e)}"} return {"success": False, "message": f"获取数据失败: {str(e)}"}
def search_mixes(self, keyword, page=1, limit=10): def search_mixes(keyword, page=1, limit=10):
"""搜索合集""" """搜索合集"""
try: try:
if not keyword: if not keyword:
@ -307,16 +292,16 @@ class MiniprogramAPI:
} }
# 查询数据 # 查询数据
cursor = self.collection.find(search_condition).sort("play_vv", -1).skip(skip).limit(limit) cursor = collection.find(search_condition).sort("play_vv", -1).skip(skip).limit(limit)
docs = list(cursor) docs = list(cursor)
# 获取搜索结果总数 # 获取搜索结果总数
total = self.collection.count_documents(search_condition) total = collection.count_documents(search_condition)
# 格式化数据 # 格式化数据
search_results = [] search_results = []
for doc in docs: for doc in docs:
item = self.format_mix_item(doc) item = format_mix_item(doc)
search_results.append(item) search_results.append(item)
return { return {
@ -338,17 +323,17 @@ class MiniprogramAPI:
logging.error(f"搜索合集失败: {e}") logging.error(f"搜索合集失败: {e}")
return {"success": False, "message": f"搜索失败: {str(e)}"} return {"success": False, "message": f"搜索失败: {str(e)}"}
def get_mix_detail(self, mix_id): def get_mix_detail(mix_id):
"""获取合集详情""" """获取合集详情"""
try: try:
from bson import ObjectId from bson import ObjectId
# 尝试通过ObjectId查找 # 尝试通过ObjectId查找
try: try:
doc = self.collection.find_one({"_id": ObjectId(mix_id)}) doc = collection.find_one({"_id": ObjectId(mix_id)})
except: except:
# 如果ObjectId无效尝试其他字段 # 如果ObjectId无效尝试其他字段
doc = self.collection.find_one({ doc = collection.find_one({
"$or": [ "$or": [
{"mix_name": mix_id}, {"mix_name": mix_id},
{"request_id": mix_id} {"request_id": mix_id}
@ -359,7 +344,7 @@ class MiniprogramAPI:
return {"success": False, "message": "未找到合集信息"} return {"success": False, "message": "未找到合集信息"}
# 格式化详细信息 - 只返回数据库原始字段 # 格式化详细信息 - 只返回数据库原始字段
detail = self.format_mix_item(doc) detail = format_mix_item(doc)
return { return {
"success": True, "success": True,
@ -371,11 +356,11 @@ class MiniprogramAPI:
logging.error(f"获取合集详情失败: {e}") logging.error(f"获取合集详情失败: {e}")
return {"success": False, "message": f"获取详情失败: {str(e)}"} return {"success": False, "message": f"获取详情失败: {str(e)}"}
def get_statistics(self): def get_statistics():
"""获取统计信息""" """获取统计信息"""
try: try:
# 基本统计 # 基本统计
total_mixes = self.collection.count_documents({}) total_mixes = collection.count_documents({})
if total_mixes == 0: if total_mixes == 0:
return {"success": False, "message": "暂无数据"} return {"success": False, "message": "暂无数据"}
@ -393,16 +378,16 @@ class MiniprogramAPI:
} }
] ]
stats_result = list(self.collection.aggregate(pipeline)) stats_result = list(collection.aggregate(pipeline))
stats = stats_result[0] if stats_result else {} stats = stats_result[0] if stats_result else {}
# 获取最新更新时间 # 获取最新更新时间
latest_doc = self.collection.find().sort("batch_time", -1).limit(1) latest_doc = collection.find().sort("batch_time", -1).limit(1)
latest_time = "" latest_time = ""
if latest_doc: if latest_doc:
latest_list = list(latest_doc) latest_list = list(latest_doc)
if latest_list: if latest_list:
latest_time = self.format_time(latest_list[0].get("batch_time")) latest_time = format_time(latest_list[0].get("batch_time"))
# 热门分类统计(按播放量区间) # 热门分类统计(按播放量区间)
categories = [ categories = [
@ -414,11 +399,11 @@ class MiniprogramAPI:
for category in categories: for category in categories:
if "max" in category: if "max" in category:
count = self.collection.count_documents({ count = collection.count_documents({
"play_vv": {"$gte": category["min"], "$lte": category["max"]} "play_vv": {"$gte": category["min"], "$lte": category["max"]}
}) })
else: else:
count = self.collection.count_documents({ count = collection.count_documents({
"play_vv": {"$gte": category["min"]} "play_vv": {"$gte": category["min"]}
}) })
category["count"] = count category["count"] = count
@ -441,10 +426,10 @@ class MiniprogramAPI:
logging.error(f"获取统计信息失败: {e}") logging.error(f"获取统计信息失败: {e}")
return {"success": False, "message": f"获取统计失败: {str(e)}"} return {"success": False, "message": f"获取统计失败: {str(e)}"}
def get_videos(self): # 路由定义
@rank_bp.route('/videos')
def get_videos():
"""获取合集列表 - 兼容app.py调用""" """获取合集列表 - 兼容app.py调用"""
from flask import request
page = int(request.args.get('page', 1)) page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20)) limit = int(request.args.get('limit', 20))
sort_by = request.args.get('sort', 'playcount') sort_by = request.args.get('sort', 'playcount')
@ -452,48 +437,58 @@ class MiniprogramAPI:
if sort_by == 'growth': if sort_by == 'growth':
start_date = request.args.get('start_date') start_date = request.args.get('start_date')
end_date = request.args.get('end_date') end_date = request.args.get('end_date')
return self.get_growth_mixes(page, limit, start_date, end_date) result = get_growth_mixes(page, limit, start_date, end_date)
else: else:
return self.get_mix_list(page, limit, sort_by) result = get_mix_list(page, limit, sort_by)
def get_top(self): return jsonify(result)
@rank_bp.route('/top')
def get_top():
"""获取热门榜单 - 兼容app.py调用""" """获取热门榜单 - 兼容app.py调用"""
from flask import request
limit = int(request.args.get('limit', 10)) limit = int(request.args.get('limit', 10))
return self.get_top_mixes(limit) result = get_top_mixes(limit)
return jsonify(result)
def search(self): @rank_bp.route('/search')
def search():
"""搜索合集 - 兼容app.py调用""" """搜索合集 - 兼容app.py调用"""
from flask import request
keyword = request.args.get('q', '') keyword = request.args.get('q', '')
page = int(request.args.get('page', 1)) page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10)) limit = int(request.args.get('limit', 10))
return self.search_mixes(keyword, page, limit) result = search_mixes(keyword, page, limit)
return jsonify(result)
def get_detail(self): @rank_bp.route('/detail')
def get_detail():
"""获取合集详情 - 兼容app.py调用""" """获取合集详情 - 兼容app.py调用"""
from flask import request
mix_id = request.args.get('id', '') mix_id = request.args.get('id', '')
return self.get_mix_detail(mix_id) result = get_mix_detail(mix_id)
return jsonify(result)
def get_stats(self): @rank_bp.route('/stats')
def get_stats():
"""获取统计信息 - 兼容app.py调用""" """获取统计信息 - 兼容app.py调用"""
return self.get_statistics() result = get_statistics()
return jsonify(result)
def health_check(self): @rank_bp.route('/health')
def health_check():
"""健康检查 - 兼容app.py调用""" """健康检查 - 兼容app.py调用"""
try: try:
from database import client
# 检查数据库连接 # 检查数据库连接
if not self.client: if not client:
return {"success": False, "message": "数据库未连接"} return jsonify({"success": False, "message": "数据库未连接"})
# 测试数据库连接 # 测试数据库连接
self.client.admin.command('ping') client.admin.command('ping')
# 获取数据统计 # 获取数据统计
total_count = self.collection.count_documents({}) total_count = collection.count_documents({})
return { return jsonify({
"success": True, "success": True,
"message": "服务正常", "message": "服务正常",
"data": { "data": {
@ -501,10 +496,7 @@ class MiniprogramAPI:
"total_records": total_count, "total_records": total_count,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
} }
} })
except Exception as e: except Exception as e:
logging.error(f"健康检查失败: {e}") logging.error(f"健康检查失败: {e}")
return {"success": False, "message": f"服务异常: {str(e)}"} return jsonify({"success": False, "message": f"服务异常: {str(e)}"})
# 创建API实例
api = MiniprogramAPI()