From 8e561a2eb75b28fbdd5dda27c4409417a66582fd Mon Sep 17 00:00:00 2001 From: LeonspaceX Date: Fri, 30 Jan 2026 23:55:36 +0800 Subject: [PATCH] Refactor backend helpers and fix UI warnings --- back/main.py | 104 +++++++++++++++------------ front/src/components/CreatePost.tsx | 11 ++- front/src/components/ImageViewer.tsx | 14 +++- front/src/components/PostCard.tsx | 11 ++- 4 files changed, 89 insertions(+), 51 deletions(-) diff --git a/back/main.py b/back/main.py index 829c9b8..62b58e9 100644 --- a/back/main.py +++ b/back/main.py @@ -25,6 +25,9 @@ NEED_AUDIT = True FILE_SIZE_LIMIT_MB = 10.0 FILE_FORMATS = ["png", "jpg", "jpeg", "gif", "webp"] +def now_time(): + return datetime.now() + # --- 定义数据库结构 --- class SiteSettings(db.Model): __tablename__ = 'site_settings' @@ -51,8 +54,8 @@ class Submission(db.Model): content = db.Column(db.Text, nullable=False) identity_token = db.Column(db.String(36), nullable=True) status = db.Column(db.String(20), default='Pending') - created_at = db.Column(db.DateTime, default=lambda: datetime.now()) - updated_at = db.Column(db.DateTime, default=lambda: datetime.now()) + created_at = db.Column(db.DateTime, default=now_time) + updated_at = db.Column(db.DateTime, default=now_time, onupdate=now_time) upvotes = db.Column(db.Integer, default=0) downvotes = db.Column(db.Integer, default=0) @@ -65,7 +68,7 @@ class Comment(db.Model): nickname = db.Column(db.String(50), default='匿名用户') content = db.Column(db.Text, nullable=False) identity_token = db.Column(db.String(36), nullable=True) - created_at = db.Column(db.DateTime, default=lambda: datetime.now()) + created_at = db.Column(db.DateTime, default=now_time) parent_comment_id = db.Column(db.Integer, db.ForeignKey('comments.id'), nullable=True) class Report(db.Model): @@ -80,6 +83,9 @@ class Report(db.Model): class Hashtag(db.Model): __tablename__ = 'hashtags' + __table_args__ = ( + db.Index('ix_hashtags_type_target', 'type', 'target_id'), + ) id = db.Column(db.Integer, primary_key=True, autoincrement=True) type = db.Column(db.Integer, nullable=False) # 0: Submission, 1: Comment target_id = db.Column(db.Integer, nullable=False) @@ -103,8 +109,8 @@ class SiteNotice(db.Model): type = db.Column(db.String(10), default='md', nullable=False) content = db.Column(db.Text, default='', nullable=False) version = db.Column(db.Integer, default=0, nullable=False) - created_at = db.Column(db.DateTime, default=lambda: datetime.now()) - updated_at = db.Column(db.DateTime, default=lambda: datetime.now(), onupdate=datetime.now) + created_at = db.Column(db.DateTime, default=now_time) + updated_at = db.Column(db.DateTime, default=now_time, onupdate=now_time) # 初始化数据库函数 def init_db(): @@ -158,6 +164,38 @@ def load_config(): print(f"Loaded {len(DENY_WORDS_CACHE)} deny words.") except Exception as e: print(f"Warning: Failed to load deny words: {e}") + +def is_identity_valid(token): + if not token: + return True + return Identity.query.filter_by(token=token).first() is not None + +def normalize_identity(token): + if token and not is_identity_valid(token): + return False, None + return True, token if token else None + +def find_deny_word(content): + try: + words = DENY_WORDS_CACHE + except Exception: + words = [] + for word in words: + if word in content: + return word + return None + +def save_hashtags(tag_type, target_id, hashtopic): + if not hashtopic: + return + for tag in hashtopic: + new_tag = Hashtag( + type=tag_type, + target_id=target_id, + name=tag + ) + db.session.add(new_tag) + db.session.commit() # --- 用户普通api端点 --- @app.route('/api/settings', methods=['GET']) @@ -277,19 +315,16 @@ def submit_post(): identity_token = data.get('identity') # 违禁词检测 - for word in DENY_WORDS_CACHE: - if word in content: - return jsonify({"code": 2005, "data": "提交内容包含违禁词"}) + if find_deny_word(content): + return jsonify({"code": 2005, "data": "提交内容包含违禁词"}) # Identity 验证 - if identity_token: - if not Identity.query.filter_by(token=identity_token).first(): - return jsonify({"code": 2004, "data": "无效的 Identity Token"}) - else: - identity_token = None + ok, identity_token = normalize_identity(identity_token) + if not ok: + return jsonify({"code": 2004, "data": "无效的 Identity Token"}) # 保存 - now = datetime.now() + now = now_time() new_post = Submission( content=content, identity_token=identity_token, @@ -301,15 +336,7 @@ def submit_post(): db.session.commit() # 保存 Hashtags - if hashtopic: - for tag in hashtopic: - new_tag = Hashtag( - type=0, # 0 for Submission - target_id=new_post.id, - name=tag - ) - db.session.add(new_tag) - db.session.commit() + save_hashtags(0, new_post.id, hashtopic) code = 1002 if new_post.status == 'Pending' else 1001 return jsonify({"code": code, "data": {"id": new_post.id}}) @@ -343,16 +370,13 @@ def submit_comment(): return jsonify({"code": 2002, "data": "投稿不存在"}) # 违禁词检测 - for word in DENY_WORDS_CACHE: - if word in content: - return jsonify({"code": 2005, "data": "提交内容包含违禁词"}) + if find_deny_word(content): + return jsonify({"code": 2005, "data": "提交内容包含违禁词"}) # Identity 验证 - if identity_token: - if not Identity.query.filter_by(token=identity_token).first(): - return jsonify({"code": 2004, "data": "无效的 Identity Token"}) - else: - identity_token = None + ok, identity_token = normalize_identity(identity_token) + if not ok: + return jsonify({"code": 2004, "data": "无效的 Identity Token"}) new_comment = Comment( submission_id=submission_id, @@ -365,15 +389,7 @@ def submit_comment(): db.session.commit() # 保存 Hashtags - if hashtopic: - for tag in hashtopic: - new_tag = Hashtag( - type=1, # 1 for Comment - target_id=new_comment.id, - name=tag - ) - db.session.add(new_tag) - db.session.commit() + save_hashtags(1, new_comment.id, hashtopic) return jsonify({"code": 1001, "data": {"id": new_comment.id}}) except Exception as e: @@ -399,11 +415,9 @@ def submit_report(): return jsonify({"code": 2002, "data": "投稿不存在"}) identity_token = data.get('identity') - if identity_token: - if not Identity.query.filter_by(token=identity_token).first(): - return jsonify({"code": 2004, "data": "无效的Identity Token"}) - else: - identity_token = None + ok, identity_token = normalize_identity(identity_token) + if not ok: + return jsonify({"code": 2004, "data": "无效的Identity Token"}) report = Report( submission_id=submission_id, diff --git a/front/src/components/CreatePost.tsx b/front/src/components/CreatePost.tsx index d3a138e..a11dcd3 100644 --- a/front/src/components/CreatePost.tsx +++ b/front/src/components/CreatePost.tsx @@ -12,6 +12,7 @@ import { Button, Text, makeStyles, + mergeClasses, shorthands, tokens, Input, @@ -672,7 +673,10 @@ const CreatePost: React.FC = () => { {suggestions.map((item, idx) => (
{ e.preventDefault(); handleSuggestApply(item); @@ -744,7 +748,10 @@ const CreatePost: React.FC = () => { {tagSuggestions.map((item, idx) => (
{ e.preventDefault(); applyTagSuggest(item); diff --git a/front/src/components/ImageViewer.tsx b/front/src/components/ImageViewer.tsx index 4d50800..f3a4c3c 100644 --- a/front/src/components/ImageViewer.tsx +++ b/front/src/components/ImageViewer.tsx @@ -83,6 +83,7 @@ const ImageViewer: React.FC = ({ src, alt, onClose }) => { const [filename, setFilename] = React.useState('image'); const startRef = React.useRef<{ x: number; y: number }>({ x: 0, y: 0 }); const offsetRef = React.useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + const imgRef = React.useRef(null); React.useEffect(() => { setScale(1); @@ -100,7 +101,7 @@ const ImageViewer: React.FC = ({ src, alt, onClose }) => { .catch(() => setFilename(fallback)); }, [src]); - const handleWheel: React.WheelEventHandler = (e) => { + const handleWheel = (e: WheelEvent) => { e.preventDefault(); const next = clamp(scale + (e.deltaY < 0 ? 0.15 : -0.15), 0.5, 5); setScale(next); @@ -129,6 +130,15 @@ const ImageViewer: React.FC = ({ src, alt, onClose }) => { setOffset({ x: 0, y: 0 }); }; + React.useEffect(() => { + const el = imgRef.current; + if (!el) return; + el.addEventListener('wheel', handleWheel, { passive: false }); + return () => { + el.removeEventListener('wheel', handleWheel as EventListener); + }; + }, [scale]); + const handleDownload = async () => { try { const res = await fetch(src, { mode: 'cors' }); @@ -161,10 +171,10 @@ const ImageViewer: React.FC = ({ src, alt, onClose }) => {
e.stopPropagation()}> {alt} appearance="transparent" - className={`${styles.actionButton} ${voteChoice === 'up' ? styles.actionButtonActive : ''}`} + className={mergeClasses( + styles.actionButton, + voteChoice === 'up' ? styles.actionButtonActive : undefined + )} style={voteChoice === 'up' ? { color: tokens.colorBrandForegroundLink } : undefined} onClick={async () => { if (hasVoted) { @@ -339,7 +343,10 @@ const PostCard = ({