初始化提交
This commit is contained in:
30
mengyalinkfly-backend/README.md
Normal file
30
mengyalinkfly-backend/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 萌芽短链接分发系统 - 后端
|
||||
|
||||
## 功能说明
|
||||
|
||||
- 生成4位随机字母数字组合的短链接(支持大小写)
|
||||
- 检查短链接是否已被使用
|
||||
- 创建和保存短链接
|
||||
- 重定向到目标URL
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
服务将在 `http://localhost:5000` 启动
|
||||
|
||||
## API 接口
|
||||
|
||||
- `GET /api/check/<code>` - 检查短链接是否可用
|
||||
- `GET /api/generate` - 生成随机可用短链接
|
||||
- `POST /api/create` - 创建短链接
|
||||
- `GET /<code>` - 跳转到目标链接
|
||||
- `GET /api/links` - 获取所有链接列表
|
||||
407
mengyalinkfly-backend/app.py
Normal file
407
mengyalinkfly-backend/app.py
Normal file
@@ -0,0 +1,407 @@
|
||||
from flask import Flask, request, jsonify, redirect
|
||||
from flask_cors import CORS
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
DATA_FILE = 'link_data.json'
|
||||
|
||||
def load_links():
|
||||
"""加载链接数据"""
|
||||
if not os.path.exists(DATA_FILE):
|
||||
return {}
|
||||
try:
|
||||
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return {}
|
||||
|
||||
def save_links(links):
|
||||
"""保存链接数据"""
|
||||
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(links, f, ensure_ascii=False, indent=2)
|
||||
|
||||
def clean_expired_links():
|
||||
"""清理过期的链接"""
|
||||
links = load_links()
|
||||
current_time = datetime.now()
|
||||
expired_codes = []
|
||||
|
||||
for code, data in links.items():
|
||||
expires_at = data.get('expires_at')
|
||||
if expires_at and expires_at != 'never':
|
||||
expire_time = datetime.fromisoformat(expires_at)
|
||||
if current_time > expire_time:
|
||||
expired_codes.append(code)
|
||||
|
||||
for code in expired_codes:
|
||||
del links[code]
|
||||
|
||||
if expired_codes:
|
||||
save_links(links)
|
||||
|
||||
return len(expired_codes)
|
||||
|
||||
def is_link_expired(link_data):
|
||||
"""检查链接是否过期"""
|
||||
expires_at = link_data.get('expires_at')
|
||||
if not expires_at or expires_at == 'never':
|
||||
return False
|
||||
|
||||
expire_time = datetime.fromisoformat(expires_at)
|
||||
return datetime.now() > expire_time
|
||||
|
||||
def calculate_expire_time(expire_option):
|
||||
"""计算过期时间"""
|
||||
if expire_option == 'never':
|
||||
return 'never'
|
||||
|
||||
expire_map = {
|
||||
'10min': timedelta(minutes=10),
|
||||
'1hour': timedelta(hours=1),
|
||||
'1day': timedelta(days=1),
|
||||
'3days': timedelta(days=3),
|
||||
'1month': timedelta(days=30),
|
||||
'1year': timedelta(days=365)
|
||||
}
|
||||
|
||||
delta = expire_map.get(expire_option)
|
||||
if delta:
|
||||
return (datetime.now() + delta).isoformat()
|
||||
return 'never'
|
||||
|
||||
def generate_random_code():
|
||||
"""生成随机4位字母数字组合(包含大小写)"""
|
||||
chars = string.ascii_letters + string.digits # a-zA-Z0-9
|
||||
return ''.join(random.choices(chars, k=4))
|
||||
|
||||
|
||||
|
||||
#==========================后端API接口==========================#
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""首页"""
|
||||
return jsonify({
|
||||
'message': '萌芽短链接分发系统',
|
||||
"author": "树萌芽",
|
||||
"API":
|
||||
{
|
||||
"/api/check/XXXX":"检查短链接是否已被使用",
|
||||
"/api/generate":"生成随机可用的短链接代码",
|
||||
"/api/create":"创建短链接",
|
||||
"/XXXX":"跳转到目标链接",
|
||||
"/api/links":"获取所有链接(可选功能)",
|
||||
"/api/redirect/XXXX":"API方式获取跳转链接(供前端路由使用)",
|
||||
"":"",
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/api/check/<code>', methods=['GET'])
|
||||
def check_code(code):
|
||||
"""检查短链接是否已被使用"""
|
||||
if len(code) != 4:
|
||||
return jsonify({'valid': False, 'message': '链接代码必须是4位字符'}), 400
|
||||
|
||||
links = load_links()
|
||||
exists = code in links
|
||||
|
||||
return jsonify({
|
||||
'exists': exists,
|
||||
'available': not exists
|
||||
})
|
||||
|
||||
@app.route('/api/generate', methods=['GET'])
|
||||
def generate_code():
|
||||
"""生成随机可用的短链接代码"""
|
||||
links = load_links()
|
||||
max_attempts = 100
|
||||
|
||||
for _ in range(max_attempts):
|
||||
code = generate_random_code()
|
||||
if code not in links:
|
||||
return jsonify({
|
||||
'code': code,
|
||||
'success': True
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '生成失败,请重试'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/create', methods=['POST'])
|
||||
def create_link():
|
||||
"""创建短链接"""
|
||||
data = request.json
|
||||
code = data.get('code', '').strip()
|
||||
target_url = data.get('target_url', '').strip()
|
||||
expire_option = data.get('expire_option', 'never')
|
||||
|
||||
# 验证输入
|
||||
if not code or not target_url:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '链接代码和目标地址不能为空'
|
||||
}), 400
|
||||
|
||||
if len(code) != 4:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '链接代码必须是4位字符'
|
||||
}), 400
|
||||
|
||||
# 清理过期链接
|
||||
clean_expired_links()
|
||||
|
||||
# 检查是否已存在
|
||||
links = load_links()
|
||||
if code in links:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '该链接代码已被使用,请选择其他代码'
|
||||
}), 409
|
||||
|
||||
# 计算过期时间
|
||||
expires_at = calculate_expire_time(expire_option)
|
||||
|
||||
# 保存链接
|
||||
links[code] = {
|
||||
'target_url': target_url,
|
||||
'created_at': datetime.now().isoformat(),
|
||||
'expires_at': expires_at,
|
||||
'expire_option': expire_option,
|
||||
'visit_count': 0
|
||||
}
|
||||
save_links(links)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'code': code,
|
||||
'short_url': f'short.shumengya.top/{code}',
|
||||
'expires_at': expires_at,
|
||||
'message': '短链接创建成功'
|
||||
})
|
||||
|
||||
@app.route('/<code>', methods=['GET'])
|
||||
def redirect_link(code):
|
||||
"""跳转到目标链接"""
|
||||
if len(code) != 4:
|
||||
return jsonify({
|
||||
'error': '无效的短链接',
|
||||
'message': '链接代码必须是4位字符'
|
||||
}), 404
|
||||
|
||||
# 清理过期链接
|
||||
clean_expired_links()
|
||||
|
||||
links = load_links()
|
||||
if code not in links:
|
||||
return jsonify({
|
||||
'error': '短链接不存在',
|
||||
'message': f'未找到代码为 {code} 的短链接'
|
||||
}), 404
|
||||
|
||||
link_data = links[code]
|
||||
|
||||
# 检查是否过期
|
||||
if is_link_expired(link_data):
|
||||
del links[code]
|
||||
save_links(links)
|
||||
return jsonify({
|
||||
'error': '短链接已过期',
|
||||
'message': f'代码为 {code} 的短链接已过期'
|
||||
}), 410
|
||||
|
||||
# 增加访问次数
|
||||
link_data['visit_count'] = link_data.get('visit_count', 0) + 1
|
||||
links[code] = link_data
|
||||
save_links(links)
|
||||
|
||||
target_url = link_data['target_url']
|
||||
|
||||
# 确保URL包含协议
|
||||
if not target_url.startswith(('http://', 'https://')):
|
||||
target_url = 'http://' + target_url
|
||||
|
||||
return redirect(target_url, code=302)
|
||||
|
||||
@app.route('/api/links', methods=['GET'])
|
||||
def get_all_links():
|
||||
"""获取所有链接(可选功能)"""
|
||||
# 清理过期链接
|
||||
clean_expired_links()
|
||||
|
||||
links = load_links()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'count': len(links),
|
||||
'links': links
|
||||
})
|
||||
|
||||
@app.route('/api/redirect/<code>', methods=['GET'])
|
||||
def api_redirect(code):
|
||||
"""API方式获取跳转链接(供前端路由使用)"""
|
||||
if len(code) != 4:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '链接代码必须是4位字符'
|
||||
}), 404
|
||||
|
||||
# 清理过期链接
|
||||
clean_expired_links()
|
||||
|
||||
links = load_links()
|
||||
if code not in links:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'未找到代码为 {code} 的短链接'
|
||||
}), 404
|
||||
|
||||
link_data = links[code]
|
||||
|
||||
# 检查是否过期
|
||||
if is_link_expired(link_data):
|
||||
del links[code]
|
||||
save_links(links)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'代码为 {code} 的短链接已过期'
|
||||
}), 410
|
||||
|
||||
# 增加访问次数
|
||||
link_data['visit_count'] = link_data.get('visit_count', 0) + 1
|
||||
links[code] = link_data
|
||||
save_links(links)
|
||||
|
||||
target_url = link_data['target_url']
|
||||
|
||||
# 确保URL包含协议
|
||||
if not target_url.startswith(('http://', 'https://')):
|
||||
target_url = 'http://' + target_url
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'code': code,
|
||||
'target_url': target_url
|
||||
})
|
||||
|
||||
@app.route('/api/stats/<code>', methods=['GET'])
|
||||
def get_link_stats(code):
|
||||
"""获取链接统计信息"""
|
||||
if len(code) != 4:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '链接代码必须是4位字符'
|
||||
}), 404
|
||||
|
||||
links = load_links()
|
||||
if code not in links:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'未找到代码为 {code} 的短链接'
|
||||
}), 404
|
||||
|
||||
link_data = links[code]
|
||||
|
||||
# 检查是否过期
|
||||
if is_link_expired(link_data):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'代码为 {code} 的短链接已过期',
|
||||
'expired': True
|
||||
}), 410
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'code': code,
|
||||
'target_url': link_data['target_url'],
|
||||
'visit_count': link_data.get('visit_count', 0),
|
||||
'created_at': link_data.get('created_at'),
|
||||
'expires_at': link_data.get('expires_at'),
|
||||
'expire_option': link_data.get('expire_option')
|
||||
})
|
||||
|
||||
@app.route('/api/admin/delete/<code>', methods=['DELETE'])
|
||||
def admin_delete_link(code):
|
||||
"""管理员删除指定链接"""
|
||||
if len(code) != 4:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '链接代码必须是4位字符'
|
||||
}), 400
|
||||
|
||||
links = load_links()
|
||||
if code not in links:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'未找到代码为 {code} 的短链接'
|
||||
}), 404
|
||||
|
||||
# 删除链接
|
||||
del links[code]
|
||||
save_links(links)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'成功删除短链接 {code}'
|
||||
})
|
||||
|
||||
@app.route('/api/admin/delete-batch', methods=['POST'])
|
||||
def admin_delete_batch():
|
||||
"""管理员批量删除链接"""
|
||||
data = request.json
|
||||
codes = data.get('codes', [])
|
||||
|
||||
if not codes or not isinstance(codes, list):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': '请提供要删除的链接代码列表'
|
||||
}), 400
|
||||
|
||||
links = load_links()
|
||||
deleted = []
|
||||
not_found = []
|
||||
|
||||
for code in codes:
|
||||
if len(code) == 4 and code in links:
|
||||
del links[code]
|
||||
deleted.append(code)
|
||||
else:
|
||||
not_found.append(code)
|
||||
|
||||
save_links(links)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'deleted': deleted,
|
||||
'deleted_count': len(deleted),
|
||||
'not_found': not_found,
|
||||
'message': f'成功删除 {len(deleted)} 个短链接'
|
||||
})
|
||||
|
||||
@app.route('/api/admin/clear-expired', methods=['POST'])
|
||||
def admin_clear_expired():
|
||||
"""管理员清理所有过期链接"""
|
||||
expired_count = clean_expired_links()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'cleared_count': expired_count,
|
||||
'message': f'成功清理 {expired_count} 个过期链接'
|
||||
})
|
||||
|
||||
#==========================后端API接口==========================#
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 确保数据文件存在
|
||||
if not os.path.exists(DATA_FILE):
|
||||
save_links({})
|
||||
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
8
mengyalinkfly-backend/link_data.json
Normal file
8
mengyalinkfly-backend/link_data.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"wadw": {
|
||||
"target_url": "https://pan.shumengya.top",
|
||||
"created_at": "2025-11-16T13:33:19.613949",
|
||||
"expires_at": "never",
|
||||
"expire_option": "never"
|
||||
}
|
||||
}
|
||||
2
mengyalinkfly-backend/requirements.txt
Normal file
2
mengyalinkfly-backend/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
Reference in New Issue
Block a user