diff --git a/app_vx.py b/app_vx.py index 832ab6fd..5eea3975 100644 --- a/app_vx.py +++ b/app_vx.py @@ -1433,6 +1433,25 @@ class Comment(db.Model): replies = db.relationship('Comment', backref=db.backref('parent', remote_side=[id])) +class Feedback(db.Model): + """用户反馈""" + __tablename__ = 'user_feedback' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) # 可匿名反馈 + feedback_type = db.Column(db.String(50), default='general') # 反馈类型: general/bug/suggestion/complaint + title = db.Column(db.String(200)) # 反馈标题 + content = db.Column(db.Text, nullable=False) # 反馈内容 + contact = db.Column(db.String(100)) # 联系方式(可选) + images = db.Column(db.JSON) # 附带图片URL列表 + status = db.Column(db.String(20), default='pending') # pending/processing/resolved/closed + admin_reply = db.Column(db.Text) # 管理员回复 + created_at = db.Column(db.DateTime, default=beijing_now) + updated_at = db.Column(db.DateTime, default=beijing_now, onupdate=beijing_now) + + user = db.relationship('User', backref='feedbacks') + + class StockBasicInfo(db.Model): __tablename__ = 'ea_stocklist' @@ -4300,7 +4319,19 @@ def api_bindphone_wechat(): 'data': None }), 400 - # 4. 检查手机号是否已被其他用户绑定 + # 4. 检查当前用户是否已绑定此手机号 + if user.phone == phone_number and user.phone_confirmed: + # 已经绑定过了,直接返回成功 + return jsonify({ + 'code': 200, + 'message': '手机号已绑定', + 'data': { + 'phone': phone_number, + 'bindcd': True + } + }) + + # 5. 检查手机号是否已被其他用户绑定(只查确认绑定的) existing_user = User.query.filter( User.phone == phone_number, User.phone_confirmed == True, @@ -4308,13 +4339,14 @@ def api_bindphone_wechat(): ).first() if existing_user: + logger.warning(f"手机号 {phone_number[:3]}****{phone_number[-4:]} 已被用户 {existing_user.id} 绑定,当前用户 {user.id} 尝试绑定失败") return jsonify({ 'code': 400, 'message': '该手机号已被其他账号绑定', 'data': None }), 400 - # 5. 更新用户手机号 + # 6. 更新用户手机号 user.phone = phone_number user.phone_confirmed = True user.phone_confirm_time = beijing_now() @@ -5752,11 +5784,15 @@ def parse_best_matches(best_matches_value): for item in data: if isinstance(item, dict): # 新结构:包含研报信息的字典 + # 将相关度转为整数百分比 (0.928 -> 93) + raw_score = item.get('best_report_match_ratio', 0) + int_score = int(round(raw_score * 100)) if raw_score and raw_score <= 1 else int(round(raw_score)) if raw_score else 0 + stock_info = { 'code': item.get('stock_code', ''), 'name': item.get('company_name', ''), 'description': item.get('original_description', ''), - 'score': item.get('best_report_match_ratio', 0), + 'score': int_score, # 研报引用信息 'report': { 'title': item.get('best_report_title', ''), @@ -6010,11 +6046,14 @@ def api_calendar_events(): if stock_data: for stock_info in stock_data: if isinstance(stock_info, list) and len(stock_info) >= 2: + # 将相关度转为整数百分比 + raw_score = stock_info[3] if len(stock_info) > 3 else 0 + int_score = int(round(raw_score * 100)) if raw_score and raw_score <= 1 else int(round(raw_score)) if raw_score else 0 parsed_stocks.append({ 'code': stock_info[0], 'name': stock_info[1], 'description': stock_info[2] if len(stock_info) > 2 else '', - 'score': stock_info[3] if len(stock_info) > 3 else 0, + 'score': int_score, 'report': None }) except Exception as e: @@ -6741,12 +6780,16 @@ def api_user_profile(): # 总评论数(发出的评论 + 收到的评论和回复) total_comments = comments_made + comments_received + replies_received + # 判断手机号绑定状态 + phone_bindcd = bool(user.phone and user.phone_confirmed) + profile_data = { 'basic_info': { 'user_id': user.id, 'username': user.username, 'email': user.email, - 'phone': user.phone, + 'phone': user.phone if phone_bindcd else None, + 'phone_bindcd': phone_bindcd, 'nickname': user.nickname, 'avatar_url': get_full_avatar_url(user.avatar_url), # 修改这里 'bio': user.bio, @@ -6809,6 +6852,116 @@ def api_user_profile(): }), 500 +@app.route('/api/user/feedback', methods=['POST']) +@token_required +def api_user_feedback(): + """用户反馈接口""" + try: + user = request.user + + # 获取请求数据(兼容多种格式) + data = request.get_json(force=True, silent=True) + if not data: + data = request.form.to_dict() + + content = data.get('content', '').strip() + if not content: + return jsonify({ + 'code': 400, + 'message': '反馈内容不能为空', + 'data': None + }), 400 + + feedback_type = data.get('type', 'general') + title = data.get('title', '').strip() + contact = data.get('contact', '').strip() + images = data.get('images', []) + + # 确保images是列表 + if isinstance(images, str): + images = [images] if images else [] + + # 创建反馈记录 + feedback = Feedback( + user_id=user.id, + feedback_type=feedback_type, + title=title if title else None, + content=content, + contact=contact if contact else user.phone or user.email, + images=images if images else None, + status='pending' + ) + + db.session.add(feedback) + db.session.commit() + + return jsonify({ + 'code': 200, + 'message': '反馈提交成功,感谢您的宝贵意见!', + 'data': { + 'feedback_id': feedback.id, + 'status': feedback.status, + 'created_at': feedback.created_at.isoformat() if feedback.created_at else None + } + }) + + except Exception as e: + db.session.rollback() + return jsonify({ + 'code': 500, + 'message': f'反馈提交失败: {str(e)}', + 'data': None + }), 500 + + +@app.route('/api/user/feedback', methods=['GET']) +@token_required +def api_user_feedback_list(): + """获取用户反馈列表""" + try: + user = request.user + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) + + feedbacks = Feedback.query.filter_by(user_id=user.id) \ + .order_by(Feedback.created_at.desc()) \ + .paginate(page=page, per_page=per_page, error_out=False) + + feedback_list = [] + for fb in feedbacks.items: + feedback_list.append({ + 'id': fb.id, + 'type': fb.feedback_type, + 'title': fb.title, + 'content': fb.content, + 'status': fb.status, + 'admin_reply': fb.admin_reply, + 'created_at': fb.created_at.isoformat() if fb.created_at else None, + 'updated_at': fb.updated_at.isoformat() if fb.updated_at else None + }) + + return jsonify({ + 'code': 200, + 'message': 'success', + 'data': { + 'feedbacks': feedback_list, + 'pagination': { + 'page': page, + 'per_page': per_page, + 'total': feedbacks.total, + 'pages': feedbacks.pages + } + } + }) + + except Exception as e: + return jsonify({ + 'code': 500, + 'message': str(e), + 'data': None + }), 500 + + # 在文件开头添加缓存变量 _agreements_cache = {} _cache_loaded = False @@ -7262,8 +7415,8 @@ if __name__ == '__main__': # SSL 配置 ssl_context = None if not args.no_ssl: - cert_file = '/etc/letsencrypt/live/api.valuefrontier.cn/fullchain.pem' - key_file = '/etc/letsencrypt/live/api.valuefrontier.cn/privkey.pem' + cert_file = '/etc/nginx/ssl/api.valuefrontier.cn/fullchain.pem' + key_file = '/etc/nginx/ssl/api.valuefrontier.cn/privkey.pem' if os.path.exists(cert_file) and os.path.exists(key_file): ssl_context = (cert_file, key_file) else: