Add webdrive upload and preview tweaks

This commit is contained in:
LeonspaceX
2026-02-01 18:53:11 +08:00
parent 5c79a9aa00
commit 8457ad64e0
7 changed files with 929 additions and 42 deletions

View File

@@ -120,6 +120,7 @@ class ImgFile(db.Model):
path = db.Column(db.String(255), nullable=False)
name = db.Column(db.String(255), nullable=True)
identity_token = db.Column(db.String(36), nullable=True)
created_at = db.Column(db.DateTime, default=now_time)
class SiteNotice(db.Model):
__tablename__ = 'site_notice'
@@ -138,6 +139,15 @@ def init_db():
os.makedirs(IMG_DIR)
with app.app_context():
db.create_all()
try:
columns = [row[1] for row in db.session.execute("PRAGMA table_info(img_files)").fetchall()]
if "created_at" not in columns:
db.session.execute("ALTER TABLE img_files ADD COLUMN created_at DATETIME")
db.session.commit()
db.session.execute("UPDATE img_files SET created_at = ? WHERE created_at IS NULL", (now_time(),))
db.session.commit()
except Exception:
db.session.rollback()
try:
existing = SiteNotice.query.first()
if not existing:
@@ -231,6 +241,57 @@ def mime_allowed(mime, rules):
return True
return False
def normalize_img_path(path):
if not path:
return None
path = str(path).strip()
if path.startswith('/api/files/'):
path = path[len('/api/files/'):]
return os.path.basename(path)
def require_identity(token):
if not token:
return False, None, 2009, "端点需要提供Identity才能操作"
if not is_identity_valid(token):
return False, None, 2004, "无效的Identity Token"
return True, token, None, None
def get_identity_from_args(source):
if not source:
return None
return source.get("identity_token") or source.get("identity")
def validate_upload_file(file):
if not file or file.filename == '':
return False, 2000, "参数错误", None
file.seek(0, os.SEEK_END)
file_length = file.tell()
file.seek(0)
if FILE_SIZE_LIMIT_MB is not None:
limit_bytes = float(FILE_SIZE_LIMIT_MB) * 1024 * 1024
if file_length > limit_bytes:
return False, 2006, "上传的图片超出限制大小", None
kind = filetype.guess(file.read(5120))
file.seek(0)
detected_mime = kind.mime if kind else None
if not detected_mime or not mime_allowed(detected_mime, FILE_FORMATS):
return False, 2007, "上传的文件类型不支持", None
try:
file.seek(0)
img = Image.open(file)
img.verify()
file.seek(0)
except (UnidentifiedImageError, OSError):
file.seek(0)
return False, 2008, "上传的文件损坏", None
except Exception:
file.seek(0)
return False, 2008, "上传的文件损坏", None
return True, None, None, kind
# --- 用户普通api端点 ---
@app.route('/api/settings', methods=['GET'])
def get_settings():
@@ -509,45 +570,22 @@ def upload_pic():
if 'file' not in request.files:
return jsonify({"code": 2000, "data": "参数错误"})
file = request.files['file']
if file.filename == '':
return jsonify({"code": 2000, "data": "参数错误"})
ok, err_code, err_msg, kind = validate_upload_file(file)
if not ok:
return jsonify({"code": err_code, "data": err_msg})
file.seek(0, os.SEEK_END)
file_length = file.tell()
file.seek(0)
if FILE_SIZE_LIMIT_MB is not None:
limit_bytes = float(FILE_SIZE_LIMIT_MB) * 1024 * 1024
if file_length > limit_bytes:
return jsonify({"code": 2006, "data": f"上传的图片超出{FILE_SIZE_LIMIT_MB}MB限制大小"})
ext = os.path.splitext(file.filename)[1].lstrip('.').lower()
kind = filetype.guess(file.read(5120))
file.seek(0)
detected_mime = kind.mime if kind else None
if not detected_mime or not mime_allowed(detected_mime, FILE_FORMATS):
return jsonify({"code": 2007, "data": "上传的文件类型不支持"})
try:
file.seek(0)
img = Image.open(file)
img.verify()
file.seek(0)
except (UnidentifiedImageError, OSError):
file.seek(0)
return jsonify({"code": 2008, "data": "上传的文件损坏"})
except Exception:
file.seek(0)
return jsonify({"code": 2008, "data": "上传的文件损坏"})
if not ext and kind:
ext = None
if kind and kind.extension:
ext = kind.extension
if not ext and file.filename:
ext = os.path.splitext(file.filename)[1].lstrip('.') or None
filename = f"{uuid.uuid4().hex}.{ext}" if ext else uuid.uuid4().hex
filepath = os.path.join(IMG_DIR, filename)
file.save(filepath)
identity_token = request.form.get('identity_token') or None
name = file.filename or None
db.session.add(ImgFile(path=filename, name=name, identity_token=identity_token))
db.session.add(ImgFile(path=filename, name=name, identity_token=identity_token, created_at=now_time()))
db.session.commit()
return jsonify({"code": 1000, "data": f"/api/files/{filename}"})
@@ -666,7 +704,7 @@ def get_posts_info():
"time": s.created_at.isoformat() if s.created_at else None,
"modified": 0 if (not s.updated_at or not s.created_at or s.updated_at == s.created_at) else 1,
"comment_count": len(s.comments),
"total_pages": pagination.total,
"total_pages": pagination.pages,
})
return jsonify({
@@ -730,7 +768,7 @@ def get_posts_by_tag():
"time": s.created_at.isoformat() if s.created_at else None,
"modified": 0 if (not s.updated_at or not s.created_at or s.updated_at == s.created_at) else 1,
"comment_count": len(s.comments),
"total_pages": pagination.total,
"total_pages": pagination.pages,
})
return jsonify({"code": 1000, "data": data})
@@ -843,7 +881,133 @@ def return_418():
abort(418)
# --- 用户的管理api端点 ---
# TODO: 用户管理端点
@app.route('/api/my/pics_pages', methods=['GET'])
def my_pics_pages():
try:
identity = get_identity_from_args(request.args)
ok, identity, code, msg = require_identity(identity)
if not ok:
return jsonify({"code": code, "data": msg})
per_page = request.args.get("num_per_pages", 5, type=int)
if per_page < 1:
per_page = 5
if per_page > 20:
per_page = 20
total = ImgFile.query.filter_by(identity_token=identity).count()
total_pages = (total + per_page - 1) // per_page if total > 0 else 0
return jsonify({"code": 1000, "data": {"total_pages": total_pages}})
except Exception as e:
return jsonify({"code": 2003, "data": str(e)})
@app.route('/api/my/all_pics', methods=['GET'])
def my_all_pics():
try:
identity = get_identity_from_args(request.args)
ok, identity, code, msg = require_identity(identity)
if not ok:
return jsonify({"code": code, "data": msg})
page = request.args.get("page", 1, type=int)
if page < 1:
page = 1
per_page = request.args.get("num_per_page", 5, type=int)
if per_page < 1:
per_page = 5
if per_page > 20:
per_page = 20
pagination = ImgFile.query.filter_by(identity_token=identity)\
.order_by(ImgFile.created_at.desc(), ImgFile.id.desc())\
.paginate(page=page, per_page=per_page, error_out=False)
data = [{
"name": i.name or "",
"path": f"/api/files/{i.path}",
"created_at": i.created_at.isoformat() if i.created_at else None
} for i in pagination.items]
return jsonify({"code": 1000, "data": {"list": data}})
except Exception as e:
return jsonify({"code": 2003, "data": str(e)})
@app.route('/api/my/modify_pic', methods=['POST'])
def my_modify_pic():
try:
identity = get_identity_from_args(request.form)
ok, identity, code, msg = require_identity(identity)
if not ok:
return jsonify({"code": code, "data": msg})
path = normalize_img_path(request.form.get("path"))
if not path:
return jsonify({"code": 2000, "data": "参数错误"})
if 'file' not in request.files:
return jsonify({"code": 2000, "data": "参数错误"})
file = request.files['file']
record = ImgFile.query.filter_by(path=path, identity_token=identity).first()
if not record:
return jsonify({"code": 2002, "data": "数据不存在"})
ok, err_code, err_msg, _ = validate_upload_file(file)
if not ok:
return jsonify({"code": err_code, "data": err_msg})
filepath = os.path.join(IMG_DIR, path)
file.save(filepath)
return jsonify({"code": 1000, "data": ""})
except Exception as e:
return jsonify({"code": 2003, "data": str(e)})
@app.route('/api/my/change_pic_name', methods=['POST'])
def my_change_pic_name():
try:
data = request.get_json()
identity = get_identity_from_args(data)
ok, identity, code, msg = require_identity(identity)
if not ok:
return jsonify({"code": code, "data": msg})
path = normalize_img_path(data.get("path") if data else None)
name = data.get("name") if data else None
if not path:
return jsonify({"code": 2000, "data": "参数错误"})
record = ImgFile.query.filter_by(path=path, identity_token=identity).first()
if not record:
return jsonify({"code": 2002, "data": "数据不存在"})
record.name = name
db.session.commit()
return jsonify({"code": 1000, "data": ""})
except Exception as e:
return jsonify({"code": 2003, "data": str(e)})
@app.route('/api/my/del_pic', methods=['POST'])
def my_del_pic():
try:
data = request.get_json()
identity = get_identity_from_args(data)
ok, identity, code, msg = require_identity(identity)
if not ok:
return jsonify({"code": code, "data": msg})
path = normalize_img_path(data.get("path") if data else None)
if not path:
return jsonify({"code": 2000, "data": "参数错误"})
record = ImgFile.query.filter_by(path=path, identity_token=identity).first()
if not record:
return jsonify({"code": 2002, "data": "数据不存在"})
filepath = os.path.join(IMG_DIR, path)
if os.path.exists(filepath):
os.remove(filepath)
db.session.delete(record)
db.session.commit()
return jsonify({"code": 1000, "data": ""})
except Exception as e:
return jsonify({"code": 2003, "data": str(e)})
# --- 管理员api端点 ---
# TODO: 添加管理员端点