Add webdrive upload and preview tweaks
This commit is contained in:
232
back/main.py
232
back/main.py
@@ -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: 添加管理员端点
|
||||
|
||||
Reference in New Issue
Block a user