👤 用户系统从0到1:登录、权限、积分一网打尽

副标题Flask-Login + 多级权限 + 积分会员系统实战
项目原型:https://madechango.com
难度等级:⭐⭐⭐☆☆
预计阅读时间:20分钟


🎯 引子:用户体验的痛点

想象一下这样的场景:你精心搭建了一个功能强大的学术平台,但用户注册后发现:

“咦?为什么我和VIP用户享受同样的服务?”
“每天使用功能没有任何奖励,感觉没有成就感…”
“权限管理混乱,不知道自己能做什么不能做什么…”

这些都是典型的用户系统设计不完善导致的问题。一个优秀的用户系统不仅仅是登录注册那么简单,它应该是:

  • 🔐 安全可靠:完善的认证机制,保护用户隐私
  • 🏆 层次分明:清晰的权限体系,差异化服务
  • 🎯 激励有效:积分奖励机制,提升用户粘性
  • 🤖 智能增长:AI辅助的用户增长策略

今天我们就基于Madechango的真实实践,从零开始构建一个完整的用户系统!

🎁 你将收获什么?

  • 认证系统:注册、登录、邮箱验证、密码重置全流程
  • 权限控制:基于角色的权限管理,装饰器实现
  • 积分系统:多样化积分获取,会员等级升级机制
  • AI增长:智能生成虚拟用户,提升平台活跃度
  • 安全防护:会话管理、登录限制、行为监控

在这里插入图片描述

🧠 技术背景:用户系统的核心组件

在深入实战之前,让我们先了解现代用户系统的技术架构。

🔧 技术栈选择

# 用户系统核心技术栈
Flask-Login          # 会话管理,简单易用
Flask-Mail           # 邮件发送,验证通知
Flask-WTF           # 表单验证,CSRF防护
itsdangerous        # 令牌生成,安全可靠
Celery              # 异步任务,邮件队列
GLM-4 API           # AI服务,智能用户生成

🏗️ 权限设计模式对比

权限模式RBACACLABACMadechango选择
复杂度中等简单复杂RBAC + 简化ACL
扩展性很好满足当前需求
性能很好中等缓存优化后优秀
维护性中等代码清晰易懂
学习成本中等适合团队技能

为什么选择RBAC(基于角色的访问控制)?

RBAC权限模型
角色 Role
用户 User
权限 Permission
资源 Resource
user角色
普通用户
vip角色
VIP用户
admin角色
管理员
基础功能权限
高级功能权限
管理功能权限

在这里插入图片描述

💰 积分系统设计理念

一个好的积分系统应该遵循"行为驱动,价值回报"的原则:

# 积分获取规则设计
POINT_RULES = {'daily_login': {'points': 10, 'desc': '每日登录奖励'},'complete_profile': {'points': 50, 'desc': '完善个人资料'},'share_content': {'points': 5, 'desc': '分享内容'},'use_ai_analysis': {'points': 2, 'desc': '使用AI分析'},'invite_friend': {'points': 100, 'desc': '邀请好友注册'},'write_review': {'points': 20, 'desc': '撰写评价'},'upload_document': {'points': 8, 'desc': '上传文档'},'complete_task': {'points': 25, 'desc': '完成写作任务'}
}# 会员等级设计
MEMBER_LEVELS = {'bronze': {'min_points': 0, 'max_ai_calls': 10, 'features': ['基础搜索']},'silver': {'min_points': 500, 'max_ai_calls': 50, 'features': ['高级搜索', '导出功能']},'gold': {'min_points': 2000, 'max_ai_calls': 200, 'features': ['AI分析', '批量操作']},'platinum': {'min_points': 5000, 'max_ai_calls': -1, 'features': ['全部功能', '优先支持']}
}

🏗️ 系统架构设计

🌐 用户系统整体架构

用户系统架构
前端层
应用层
服务层
数据层
缓存层
用户会话
权限缓存
积分缓存
用户表
角色表
积分历史
用户活动
用户服务
邮件服务
积分服务
AI服务
认证模块
权限模块
积分模块
AI用户模块
登录注册界面
用户中心
权限提示

📊 数据模型设计

UserintidPKstringusernameUKstringemailUKstringpassword_hashstringnicknamestringavatar_urlbooleanis_activebooleanis_verifiedstringrolestringmember_levelinttotal_pointsintavailable_pointsdatetimelast_login_atUserActivityintidPKintuser_idFKstringactiontextdetailsstringip_addressdatetimecreated_atPointsHistoryintidPKintuser_idFKintpointsstringreasonintbalance_afterdatetimecreated_atRoleLoginHistoryhashasbelongs_tohas

💻 核心功能实战开发

🔐 第一步:增强用户认证系统

让我们在第一篇的基础上,构建一个更完善的用户认证系统:

# app/models/user.py - 增强版用户模型
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from itsdangerous import URLSafeTimedSerializer
from datetime import datetime, timedelta
from .base import BaseModel, dbclass User(BaseModel, UserMixin):"""增强版用户模型"""__tablename__ = 'users'# 基本信息username = db.Column(db.String(80), unique=True, nullable=False, index=True)email = db.Column(db.String(120), unique=True, nullable=False, index=True)password_hash = db.Column(db.String(255), nullable=False)# 个人资料nickname = db.Column(db.String(100))avatar_url = db.Column(db.String(255))bio = db.Column(db.Text)location = db.Column(db.String(100))website = db.Column(db.String(255))# 账户状态is_active = db.Column(db.Boolean, default=True, index=True)is_verified = db.Column(db.Boolean, default=False)email_confirmed_at = db.Column(db.DateTime)last_login_at = db.Column(db.DateTime)last_login_ip = db.Column(db.String(45))# 权限和等级role = db.Column(db.String(20), default='user', index=True)  # user, vip, adminmember_level = db.Column(db.String(20), default='bronze', index=True)# 积分系统total_points = db.Column(db.Integer, default=0)available_points = db.Column(db.Integer, default=0)points_used = db.Column(db.Integer, default=0)# AI使用统计ai_calls_today = db.Column(db.Integer, default=0)ai_calls_total = db.Column(db.Integer, default=0)last_ai_call_date = db.Column(db.Date)# 关系activities = db.relationship('UserActivity', backref='user', lazy='dynamic')points_history = db.relationship('PointsHistory', backref='user', lazy='dynamic')def set_password(self, password):"""设置密码哈希"""self.password_hash = generate_password_hash(password)def check_password(self, password):"""验证密码"""return check_password_hash(self.password_hash, password)def generate_confirmation_token(self):"""生成邮箱确认令牌"""from flask import current_appserializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])return serializer.dumps({'user_id': self.id}, salt='email-confirm')def confirm_email(self, token, expiration=3600):"""确认邮箱"""from flask import current_appserializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])try:data = serializer.loads(token, salt='email-confirm', max_age=expiration)if data['user_id'] != self.id:return Falseself.is_verified = Trueself.email_confirmed_at = datetime.utcnow()self.save()return Trueexcept:return Falsedef can_use_ai(self):"""检查是否可以使用AI功能"""# 重置每日计数today = datetime.now().date()if self.last_ai_call_date != today:self.ai_calls_today = 0self.last_ai_call_date = todayself.save()# 检查权限limits = {'bronze': 10,'silver': 50, 'gold': 200,'platinum': -1  # 无限制}limit = limits.get(self.member_level, 10)return limit == -1 or self.ai_calls_today < limitdef use_ai_call(self):"""记录AI调用"""self.ai_calls_today += 1self.ai_calls_total += 1self.last_ai_call_date = datetime.now().date()self.save()def get_display_name(self):"""获取显示名称"""return self.nickname or self.usernamedef is_admin(self):"""检查是否为管理员"""return self.role == 'admin'def is_vip(self):"""检查是否为VIP用户"""return self.role in ['vip', 'admin'] or self.member_level in ['gold', 'platinum']

🏆 第二步:权限装饰器系统

权限控制是用户系统的核心,我们设计一套灵活的装饰器系统:

# app/utils/decorators.py - 权限控制装饰器
from functools import wraps
from flask import abort, flash, redirect, url_for, request, jsonify
from flask_login import current_userdef require_roles(*roles):"""角色权限装饰器"""def decorator(f):@wraps(f)def decorated_function(*args, **kwargs):if not current_user.is_authenticated:if request.is_json:return jsonify({'error': '请先登录', 'code': 401}), 401flash('请先登录', 'warning')return redirect(url_for('auth.login', next=request.url))if current_user.role not in roles:if request.is_json:return jsonify({'error': '权限不足', 'code': 403}), 403flash('权限不足', 'error')abort(403)return f(*args, **kwargs)return decorated_functionreturn decoratordef require_verified_email(f):"""邮箱验证装饰器"""@wraps(f)def decorated_function(*args, **kwargs):if not current_user.is_authenticated:return redirect(url_for('auth.login'))if not current_user.is_verified:if request.is_json:return jsonify({'error': '请先验证邮箱', 'code': 403}), 403flash('请先验证邮箱', 'warning')return redirect(url_for('auth.verify_email'))return f(*args, **kwargs)return decorated_functiondef require_member_level(min_level):"""会员等级装饰器"""level_hierarchy = {'bronze': 1, 'silver': 2, 'gold': 3, 'platinum': 4}def decorator(f):@wraps(f)def decorated_function(*args, **kwargs):if not current_user.is_authenticated:return redirect(url_for('auth.login'))user_level = level_hierarchy.get(current_user.member_level, 1)required_level = level_hierarchy.get(min_level, 1)if user_level < required_level:if request.is_json:return jsonify({'error': f'需要 {min_level} 等级才能使用此功能','current_level': current_user.member_level,'required_level': min_level}), 403flash(f'需要 {min_level} 等级才能使用此功能', 'warning')return redirect(url_for('user.upgrade'))return f(*args, **kwargs)return decorated_functionreturn decoratordef ai_usage_limit(f):"""AI使用限制装饰器"""@wraps(f)def decorated_function(*args, **kwargs):if not current_user.is_authenticated:return redirect(url_for('auth.login'))if not current_user.can_use_ai():limits = {'bronze': '10次/天', 'silver': '50次/天', 'gold': '200次/天', 'platinum': '无限制'}limit = limits.get(current_user.member_level, '10次/天')if request.is_json:return jsonify({'error': f'今日AI使用次数已达上限 ({limit})','current_level': current_user.member_level,'upgrade_url': url_for('user.upgrade')}), 403flash(f'今日AI使用次数已达上限 ({limit}),请升级会员或明日再试', 'warning')return redirect(url_for('user.upgrade'))# 记录AI调用current_user.use_ai_call()return f(*args, **kwargs)return decorated_functiondef rate_limit(max_requests=60, window=60, per='ip'):"""频率限制装饰器"""def decorator(f):@wraps(f)def decorated_function(*args, **kwargs):from app.utils.redis_client import redis_client# 生成限制键if per == 'ip':key = f"rate_limit:{request.remote_addr}:{f.__name__}"elif per == 'user' and current_user.is_authenticated:key = f"rate_limit:user:{current_user.id}:{f.__name__}"else:key = f"rate_limit:anonymous:{f.__name__}"# 检查频率限制current_requests = redis_client.redis.get(key)if current_requests and int(current_requests) >= max_requests:if request.is_json:return jsonify({'error': f'请求过于频繁,请在{window}秒后重试','retry_after': window}), 429flash('请求过于频繁,请稍后重试', 'warning')abort(429)# 记录请求pipe = redis_client.redis.pipeline()pipe.incr(key)pipe.expire(key, window)pipe.execute()return f(*args, **kwargs)return decorated_functionreturn decorator

在这里插入图片描述

🎯 第三步:积分系统实现

积分系统是提升用户粘性的关键,让我们实现一个完整的积分管理系统:

# app/services/points_service.py - 积分系统服务
from app.models.user import User
from app.models.points import PointsHistory, UserActivity
from datetime import datetime, timedelta
from app.models import dbclass PointsService:"""积分系统服务"""# 积分规则配置POINT_RULES = {'daily_login': {'points': 10, 'desc': '每日登录奖励', 'daily_limit': 1},'complete_profile': {'points': 50, 'desc': '完善个人资料', 'once_only': True},'first_ai_use': {'points': 20, 'desc': '首次使用AI分析', 'once_only': True},'share_content': {'points': 5, 'desc': '分享内容', 'daily_limit': 3},'invite_friend': {'points': 100, 'desc': '邀请好友注册', 'daily_limit': 5},'write_review': {'points': 15, 'desc': '撰写评价', 'daily_limit': 2},'upload_document': {'points': 8, 'desc': '上传文档', 'daily_limit': 10},'complete_task': {'points': 25, 'desc': '完成写作任务'},'continuous_login': {'points': 5, 'desc': '连续登录额外奖励'}}@classmethoddef award_points(cls, user, action, custom_points=None, custom_reason=None):"""奖励积分"""if action not in cls.POINT_RULES and custom_points is None:return {'success': False, 'message': '无效的积分规则'}rule = cls.POINT_RULES.get(action, {})points = custom_points or rule.get('points', 0)reason = custom_reason or rule.get('desc', f'自定义奖励: {custom_points}')# 检查一次性奖励if rule.get('once_only'):existing = PointsHistory.query.filter_by(user_id=user.id,reason=reason).first()if existing:return {'success': False, 'message': '该奖励已经获得过了'}# 检查每日限制daily_limit = rule.get('daily_limit')if daily_limit:today = datetime.now().date()today_count = PointsHistory.query.filter(PointsHistory.user_id == user.id,PointsHistory.reason == reason,db.func.date(PointsHistory.created_at) == today,PointsHistory.points > 0).count()if today_count >= daily_limit:return {'success': False, 'message': f'今日该奖励已达上限 ({daily_limit}次)'}# 添加积分user.add_points(points, reason)# 记录用户活动activity = UserActivity(user_id=user.id,action=f'earn_points_{action}',details=f'获得 {points} 积分: {reason}',ip_address=self._get_client_ip())activity.save()return {'success': True,'points': points,'reason': reason,'total_points': user.total_points,'new_level': user.member_level}@classmethoddef consume_points(cls, user, points, reason):"""消费积分"""if user.available_points < points:return {'success': False,'message': f'积分不足,当前可用积分: {user.available_points}','required': points,'available': user.available_points}# 扣除积分user.available_points -= pointsuser.points_used += points# 记录积分历史history = PointsHistory(user_id=user.id,points=-points,  # 负数表示消费reason=reason,balance_after=user.available_points)history.save()# 记录用户活动activity = UserActivity(user_id=user.id,action='consume_points',details=f'消费 {points} 积分: {reason}',ip_address=self._get_client_ip())activity.save()user.save()return {'success': True,'consumed': points,'remaining': user.available_points,'reason': reason}@classmethoddef check_continuous_login(cls, user):"""检查连续登录并给予奖励"""# 获取最近的登录记录recent_activities = UserActivity.query.filter_by(user_id=user.id,action='earn_points_daily_login').order_by(UserActivity.created_at.desc()).limit(7).all()if not recent_activities:return 0# 计算连续登录天数continuous_days = 1current_date = datetime.now().date()for activity in recent_activities[1:]:  # 跳过今天的记录activity_date = activity.created_at.date()expected_date = current_date - timedelta(days=continuous_days)if activity_date == expected_date:continuous_days += 1else:break# 连续登录奖励if continuous_days >= 3:  # 连续3天以上给额外奖励bonus_points = min(continuous_days * 2, 20)  # 最多20分cls.award_points(user, 'continuous_login', custom_points=bonus_points,custom_reason=f'连续登录{continuous_days}天奖励')return bonus_pointsreturn 0@classmethoddef get_points_ranking(cls, limit=10, timeframe='all'):"""获取积分排行榜"""query = User.query.filter(User.is_active == True)if timeframe == 'month':# 本月积分排行month_start = datetime.now().replace(day=1, hour=0, minute=0, second=0)month_points = db.session.query(PointsHistory.user_id,db.func.sum(PointsHistory.points).label('month_points')).filter(PointsHistory.created_at >= month_start,PointsHistory.points > 0).group_by(PointsHistory.user_id).subquery()query = query.join(month_points, User.id == month_points.c.user_id)\.order_by(month_points.c.month_points.desc())else:# 总积分排行query = query.order_by(User.total_points.desc())return query.limit(limit).all()@classmethoddef get_user_points_summary(cls, user):"""获取用户积分汇总"""# 近30天积分获得thirty_days_ago = datetime.now() - timedelta(days=30)recent_earned = db.session.query(db.func.sum(PointsHistory.points)).filter(PointsHistory.user_id == user.id,PointsHistory.points > 0,PointsHistory.created_at >= thirty_days_ago).scalar() or 0# 积分来源统计points_sources = db.session.query(PointsHistory.reason,db.func.sum(PointsHistory.points).label('total'),db.func.count(PointsHistory.id).label('count')).filter(PointsHistory.user_id == user.id,PointsHistory.points > 0).group_by(PointsHistory.reason).order_by(db.func.sum(PointsHistory.points).desc()).all()# 等级信息next_level_info = cls.get_next_level_info(user)return {'total_points': user.total_points,'available_points': user.available_points,'points_used': user.points_used,'recent_earned': recent_earned,'points_sources': [{'reason': source.reason,'total': source.total,'count': source.count,'average': round(source.total / source.count, 1)} for source in points_sources],'current_level': user.member_level,'next_level': next_level_info}@classmethoddef get_next_level_info(cls, user):"""获取下一级别信息"""levels = [('bronze', 0),('silver', 500),('gold', 2000),('platinum', 5000)]current_points = user.total_pointsfor i, (level, min_points) in enumerate(levels):if current_points < min_points:return {'level': level,'required_points': min_points,'remaining_points': min_points - current_points,'progress_percent': round((current_points / min_points) * 100, 1) if min_points > 0 else 0}# 已经是最高等级return {'level': 'platinum','required_points': 5000,'remaining_points': 0,'progress_percent': 100}@staticmethoddef _get_client_ip():"""获取客户端IP"""from flask import requestreturn request.headers.get('X-Forwarded-For', request.remote_addr)

🤖 第四步:AI虚拟用户生成系统

为了提升平台活跃度,我们实现一个AI驱动的虚拟用户生成系统:

# app/services/virtual_user_service.py - AI虚拟用户生成
import random
import json
from datetime import datetime, timedelta
from app.models.user import User
from app.models.points import UserActivity
from app.services.points_service import PointsServiceclass VirtualUserService:"""AI虚拟用户生成服务"""def __init__(self):# 传统用户名模板(AI生成失败时使用)self.username_templates = ['student_{random}', 'scholar_{random}', 'researcher_{random}','academic_{random}', 'learner_{random}', 'writer_{random}','reader_{random}', 'thinker_{random}']# 邮箱域名列表self.email_domains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com','163.com', 'qq.com', 'sina.com', 'edu.my', 'student.edu']# 虚拟用户昵称库self.nicknames = {'chinese': ['学术新手', '研究者', '论文达人', '知识探索者', '学习爱好者','书虫', '思考者', '求知者', '学者', '智慧追求者','文献猎手', '知识收割机', '学术小白', '研究狂人'],'english': ['Academic', 'Scholar', 'Researcher', 'Student', 'Learner','Reader', 'Thinker', 'Explorer', 'Seeker', 'Analyst','BookLover', 'KnowledgeHunter', 'StudyBuddy', 'ResearchFan']}# 虚拟用户简介模板self.bio_templates = ['热爱学术研究的{}','专注于{}领域的研究','追求学术excellence的{}','{}方向的研究生','对{}充满热情的学者','致力于{}研究的学生']# 研究领域self.research_fields = ['计算机科学', '人工智能', '数据科学', '软件工程', '网络安全','机器学习', '深度学习', '自然语言处理', '计算机视觉', '大数据','教育学', '心理学', '管理学', '经济学', '社会学']# 马来西亚城市self.locations = ['吉隆坡', '槟城', '新山', '马六甲', '怡保', '古晋', '亚庇','Kuala Lumpur', 'Penang', 'Johor Bahru', 'Malacca', 'Ipoh']def generate_ai_user_info(self, user_type='student'):"""使用AI生成用户信息"""try:# 这里可以集成GLM-4 API进行AI生成# 为了演示,我们使用智能的传统方法return self._generate_smart_user_info(user_type)except Exception as e:print(f"AI生成用户信息失败: {e}")return self._generate_fallback_user_info()def _generate_smart_user_info(self, user_type):"""智能生成用户信息"""random_num = random.randint(1000, 9999)# 生成用户名if user_type == 'researcher':template = random.choice(['researcher_{random}', 'scholar_{random}'])elif user_type == 'student':template = random.choice(['student_{random}', 'learner_{random}'])else:template = random.choice(self.username_templates)username = template.format(random=random_num)# 确保用户名唯一while User.query.filter_by(username=username).first():random_num = random.randint(1000, 9999)username = template.format(random=random_num)# 生成邮箱domain = random.choice(self.email_domains)email = f"{username}@{domain}"# 确保邮箱唯一while User.query.filter_by(email=email).first():random_num = random.randint(1000, 9999)username = template.format(random=random_num)email = f"{username}@{domain}"# 生成昵称nickname_type = random.choice(['chinese', 'english'])nickname = random.choice(self.nicknames[nickname_type])# 添加随机数字避免重复if random.random() < 0.3:nickname += str(random.randint(1, 99))# 生成个人简介field = random.choice(self.research_fields)bio_template = random.choice(self.bio_templates)bio = bio_template.format(field)# 生成位置location = random.choice(self.locations)return {'username': username,'nickname': nickname,'email': email,'bio': bio,'location': location,'research_field': field}def _generate_fallback_user_info(self):"""传统方式生成用户信息(备用方案)"""random_num = random.randint(1000, 9999)template = random.choice(self.username_templates)username = template.format(random=random_num)# 确保用户名唯一while User.query.filter_by(username=username).first():random_num = random.randint(1000, 9999)username = template.format(random=random_num)domain = random.choice(self.email_domains)email = f"{username}@{domain}"return {'username': username,'nickname': random.choice(self.nicknames['chinese'] + self.nicknames['english']),'email': email,'bio': random.choice(['热爱学术研究', '专注于知识分享', '追求学术excellence']),'location': random.choice(self.locations),'research_field': random.choice(self.research_fields)}def create_virtual_user(self, user_type='student'):"""创建虚拟用户"""user_info = self.generate_ai_user_info(user_type)# 创建用户user = User(username=user_info['username'],email=user_info['email'],nickname=user_info['nickname'],bio=user_info['bio'],location=user_info['location'],is_verified=True,  # 虚拟用户自动验证member_level=random.choices(['bronze', 'silver', 'gold'], weights=[70, 25, 5]  # 70%青铜,25%白银,5%黄金)[0])# 设置随机密码user.set_password(f"virtual_{random.randint(100000, 999999)}")user.save()# 添加初始积分initial_points = random.randint(20, 150)PointsService.award_points(user, 'complete_profile',  # 使用现有规则custom_points=initial_points,custom_reason='虚拟用户初始积分')# 模拟一些历史活动self._simulate_user_history(user)# 记录创建活动activity = UserActivity(user_id=user.id,action='virtual_user_created',details=f'AI生成虚拟用户: {user_type}, 研究领域: {user_info.get("research_field", "未知")}')activity.save()return userdef _simulate_user_history(self, user):"""模拟用户历史活动"""# 模拟过去几天的登录days_back = random.randint(1, 7)for i in range(days_back):if random.random() < 0.7:  # 70%概率有活动activity_date = datetime.now() - timedelta(days=i)# 创建历史活动记录activity = UserActivity(user_id=user.id,action='daily_login',details='虚拟用户历史登录',created_at=activity_date)activity.save()# 有概率进行其他活动if random.random() < 0.3:  # 30%概率有其他活动actions = ['share_content', 'upload_document', 'write_review']action = random.choice(actions)activity = UserActivity(user_id=user.id,action=action,details=f'虚拟用户历史{action}',created_at=activity_date + timedelta(hours=random.randint(1, 10)))activity.save()def create_batch_virtual_users(self, count=5):"""批量创建虚拟用户"""user_types = ['student', 'researcher', 'academic', 'graduate']created_users = []for i in range(count):try:user_type = random.choice(user_types)user = self.create_virtual_user(user_type)created_users.append(user)# 随机延迟,模拟真实注册间隔import timetime.sleep(random.uniform(0.5, 2.0))except Exception as e:print(f"创建虚拟用户失败: {e}")continuereturn created_usersdef simulate_user_activities(self, days=1):"""模拟用户活动"""# 获取虚拟用户(用户名包含特定模式的用户)virtual_users = User.query.filter(db.or_(User.username.like('student_%'),User.username.like('scholar_%'),User.username.like('researcher_%'),User.username.like('academic_%'))).order_by(db.func.random()).limit(20).all()activities = [('share_content', 0.2),      # 20%概率('upload_document', 0.15),   # 15%概率('write_review', 0.1),       # 10%概率('daily_login', 0.8)         # 80%概率]activity_count = 0for user in virtual_users:for action, probability in activities:if random.random() < probability:result = PointsService.award_points(user, action)if result['success']:activity_count += 1return {'simulated_users': len(virtual_users),'activities_created': activity_count,'timestamp': datetime.now().isoformat()}

🎨 前端用户界面设计

📝 用户注册页面

在这里插入图片描述

<!-- app/templates/auth/register.html - 用户注册页面 -->
{% extends "base.html" %}{% block title %}用户注册 - Academic Platform{% endblock %}{% block extra_css %}
<style>
.auth-container {max-width: 500px;margin: 2rem auto;padding: 2rem;background: white;border-radius: 10px;box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}.form-floating {margin-bottom: 1rem;
}.password-strength {height: 4px;background: #e9ecef;border-radius: 2px;overflow: hidden;margin-top: 5px;
}.password-strength-bar {height: 100%;transition: all 0.3s ease;border-radius: 2px;
}.strength-weak { background: #dc3545; width: 25%; }
.strength-fair { background: #ffc107; width: 50%; }
.strength-good { background: #198754; width: 75%; }
.strength-strong { background: #198754; width: 100%; }.feature-highlight {background: #f8f9fa;border-radius: 8px;padding: 1rem;margin: 1rem 0;
}
</style>
{% endblock %}{% block content %}
<div class="container"><div class="auth-container"><div class="text-center mb-4"><h2 class="fw-bold text-primary"><i class="fas fa-user-plus me-2"></i>加入Academic Platform</h2><p class="text-muted">开启您的学术研究之旅</p></div><!-- 注册表单 --><form id="registerForm" method="POST"><!-- 用户名 --><div class="form-floating"><input type="text" class="form-control" id="username" name="username" placeholder="用户名" required minlength="3" maxlength="20"><label for="username"><i class="fas fa-user me-1"></i>用户名</label><div class="form-text">3-20个字符,支持字母、数字、下划线</div></div><!-- 邮箱 --><div class="form-floating"><input type="email" class="form-control" id="email" name="email" placeholder="邮箱地址" required><label for="email"><i class="fas fa-envelope me-1"></i>邮箱地址</label><div class="form-text">用于账户验证和重要通知</div></div><!-- 密码 --><div class="form-floating"><input type="password" class="form-control" id="password" name="password" placeholder="密码" required minlength="6"><label for="password"><i class="fas fa-lock me-1"></i>密码</label><div class="password-strength"><div class="password-strength-bar" id="strengthBar"></div></div><div class="form-text"><span id="strengthText">至少6个字符</span></div></div><!-- 确认密码 --><div class="form-floating"><input type="password" class="form-control" id="confirmPassword" name="confirm_password" placeholder="确认密码" required><label for="confirmPassword"><i class="fas fa-lock me-1"></i>确认密码</label><div class="invalid-feedback" id="passwordMismatch">两次输入的密码不一致</div></div><!-- 同意条款 --><div class="form-check mb-3"><input class="form-check-input" type="checkbox" id="agreeTerms" required><label class="form-check-label" for="agreeTerms">我已阅读并同意 <a href="#" class="text-decoration-none">用户协议</a><a href="#" class="text-decoration-none">隐私政策</a></label></div><!-- 注册按钮 --><button type="submit" class="btn btn-primary w-100 py-2" id="submitBtn"><i class="fas fa-user-plus me-2"></i>立即注册</button></form><!-- 登录链接 --><div class="text-center mt-3"><span class="text-muted">已有账户?</span><a href="{{ url_for('auth.login') }}" class="text-decoration-none">立即登录</a></div><!-- 功能亮点 --><div class="feature-highlight mt-4"><h6 class="fw-bold mb-2"><i class="fas fa-star text-warning me-1"></i>注册即享特权</h6><ul class="list-unstyled mb-0 small"><li><i class="fas fa-check text-success me-2"></i>每日登录奖励 10 积分</li><li><i class="fas fa-check text-success me-2"></i>完善资料奖励 50 积分</li><li><i class="fas fa-check text-success me-2"></i>免费使用 AI 分析功能</li><li><i class="fas fa-check text-success me-2"></i>优先获得新功能体验</li></ul></div></div>
</div>
{% endblock %}{% block extra_js %}
<script>
$(document).ready(function() {// 密码强度检测$('#password').on('input', function() {const password = $(this).val();const strength = calculatePasswordStrength(password);updatePasswordStrength(strength);});// 确认密码检查$('#confirmPassword').on('input', function() {const password = $('#password').val();const confirmPassword = $(this).val();if (confirmPassword && password !== confirmPassword) {$(this).addClass('is-invalid');} else {$(this).removeClass('is-invalid');}});// 表单提交$('#registerForm').on('submit', function(e) {e.preventDefault();const formData = {username: $('#username').val(),email: $('#email').val(),password: $('#password').val()};// 验证密码匹配if ($('#password').val() !== $('#confirmPassword').val()) {showAlert('两次输入的密码不一致', 'error');return;}// 提交注册submitRegistration(formData);});
});function calculatePasswordStrength(password) {let strength = 0;if (password.length >= 6) strength += 1;if (password.length >= 8) strength += 1;if (/[a-z]/.test(password)) strength += 1;if (/[A-Z]/.test(password)) strength += 1;if (/[0-9]/.test(password)) strength += 1;if (/[^A-Za-z0-9]/.test(password)) strength += 1;return Math.min(strength, 4);
}function updatePasswordStrength(strength) {const strengthBar = $('#strengthBar');const strengthText = $('#strengthText');const levels = [{ class: '', text: '至少6个字符' },{ class: 'strength-weak', text: '密码强度:弱' },{ class: 'strength-fair', text: '密码强度:一般' },{ class: 'strength-good', text: '密码强度:良好' },{ class: 'strength-strong', text: '密码强度:强' }];const level = levels[strength];strengthBar.attr('class', 'password-strength-bar ' + level.class);strengthText.text(level.text);
}function submitRegistration(formData) {const submitBtn = $('#submitBtn');const originalText = submitBtn.html();// 显示加载状态submitBtn.html('<i class="fas fa-spinner fa-spin me-2"></i>注册中...').prop('disabled', true);$.ajax({url: '{{ url_for("auth.register") }}',method: 'POST',contentType: 'application/json',data: JSON.stringify(formData),success: function(response) {if (response.success) {showAlert('注册成功!请检查邮箱验证链接', 'success');setTimeout(() => {window.location.href = '{{ url_for("auth.login") }}';}, 2000);} else {showAlert(response.message || '注册失败', 'error');submitBtn.html(originalText).prop('disabled', false);}},error: function(xhr) {const response = xhr.responseJSON;if (response && response.errors) {showAlert(response.errors.join('<br>'), 'error');} else {showAlert('注册失败,请重试', 'error');}submitBtn.html(originalText).prop('disabled', false);}});
}function showAlert(message, type) {const alertClass = type === 'error' ? 'alert-danger' : 'alert-success';const alertHtml = `<div class="alert ${alertClass} alert-dismissible fade show" role="alert">${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>`;$('.auth-container').prepend(alertHtml);// 自动消失setTimeout(() => {$('.alert').fadeOut();}, 5000);
}
</script>
{% endblock %}

👤 用户中心页面

在这里插入图片描述

<!-- app/templates/user/profile.html - 用户中心页面 -->
{% extends "base.html" %}{% block title %}个人中心 - {{ current_user.get_display_name() }}{% endblock %}{% block extra_css %}
<style>
.profile-header {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;border-radius: 15px;padding: 2rem;margin-bottom: 2rem;
}.avatar-container {position: relative;display: inline-block;
}.avatar-large {width: 120px;height: 120px;border: 4px solid rgba(255,255,255,0.3);border-radius: 50%;
}.level-badge {position: absolute;bottom: -5px;right: -5px;padding: 0.25rem 0.5rem;border-radius: 15px;font-size: 0.7rem;font-weight: bold;
}.level-bronze { background: #cd7f32; }
.level-silver { background: #c0c0c0; }
.level-gold { background: #ffd700; }
.level-platinum { background: #e5e4e2; color: #333; }.stats-card {background: white;border-radius: 10px;padding: 1.5rem;box-shadow: 0 2px 4px rgba(0,0,0,0.1);text-align: center;transition: transform 0.2s;
}.stats-card:hover {transform: translateY(-2px);
}.progress-ring {width: 80px;height: 80px;margin: 0 auto 1rem;
}.progress-ring circle {fill: none;stroke-width: 4;stroke-linecap: round;
}.progress-bg {stroke: #e9ecef;
}.progress-bar {stroke: #007bff;stroke-dasharray: 0 251.2;transform: rotate(-90deg);transform-origin: 50% 50%;transition: stroke-dasharray 1s ease;
}.activity-timeline {position: relative;padding-left: 2rem;
}.activity-timeline::before {content: '';position: absolute;left: 0.5rem;top: 0;bottom: 0;width: 2px;background: #e9ecef;
}.activity-item {position: relative;margin-bottom: 1.5rem;background: white;border-radius: 8px;padding: 1rem;box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}.activity-item::before {content: '';position: absolute;left: -1.75rem;top: 1rem;width: 10px;height: 10px;background: #007bff;border-radius: 50%;border: 2px solid white;
}
</style>
{% endblock %}{% block content %}
<div class="container"><!-- 用户资料头部 --><div class="profile-header"><div class="row align-items-center"><div class="col-md-3 text-center"><div class="avatar-container"><img src="{{ current_user.avatar_url or url_for('static', filename='images/default-avatar.png') }}" alt="头像" class="avatar-large"><span class="level-badge level-{{ current_user.member_level }}">{{ current_user.member_level.upper() }}</span></div></div><div class="col-md-6"><h2 class="mb-2">{{ current_user.get_display_name() }}</h2><p class="mb-1"><i class="fas fa-user me-2"></i>@{{ current_user.username }}</p>{% if current_user.location %}<p class="mb-1"><i class="fas fa-map-marker-alt me-2"></i>{{ current_user.location }}</p>{% endif %}{% if current_user.bio %}<p class="mb-0 opacity-75">{{ current_user.bio }}</p>{% endif %}</div><div class="col-md-3 text-center"><div class="mb-2"><i class="fas fa-coins fs-1 text-warning"></i></div><h3 class="mb-0">{{ current_user.available_points }}</h3><small class="opacity-75">可用积分</small></div></div></div><div class="row"><!-- 左侧:统计信息 --><div class="col-lg-8"><!-- 积分统计 --><div class="row mb-4"><div class="col-md-3 mb-3"><div class="stats-card"><div class="progress-ring"><svg width="80" height="80"><circle class="progress-bg" cx="40" cy="40" r="36"></circle><circle class="progress-bar" cx="40" cy="40" r="36" id="totalPointsProgress"></circle></svg></div><h4 class="text-primary">{{ current_user.total_points }}</h4><small class="text-muted">总积分</small></div></div><div class="col-md-3 mb-3"><div class="stats-card"><i class="fas fa-gift fs-1 text-success mb-3"></i><h4 class="text-success">{{ current_user.available_points }}</h4><small class="text-muted">可用积分</small></div></div><div class="col-md-3 mb-3"><div class="stats-card"><i class="fas fa-shopping-cart fs-1 text-warning mb-3"></i><h4 class="text-warning">{{ current_user.points_used }}</h4><small class="text-muted">已消费</small></div></div><div class="col-md-3 mb-3"><div class="stats-card"><i class="fas fa-robot fs-1 text-info mb-3"></i><h4 class="text-info">{{ current_user.ai_calls_total }}</h4><small class="text-muted">AI调用</small></div></div></div><!-- 等级进度 --><div class="card mb-4"><div class="card-body"><h5 class="card-title"><i class="fas fa-trophy me-2"></i>会员等级</h5><div class="row align-items-center"><div class="col-md-8"><div class="d-flex justify-content-between mb-2"><span>当前等级:<strong class="text-{{ current_user.member_level }}">{{ current_user.member_level.upper() }}</strong></span><span id="nextLevelInfo">加载中...</span></div><div class="progress" style="height: 8px;"><div class="progress-bar" role="progressbar" id="levelProgress" style="width: 0%"></div></div></div><div class="col-md-4 text-end"><a href="{{ url_for('user.upgrade') }}" class="btn btn-outline-primary"><i class="fas fa-arrow-up me-1"></i>升级会员</a></div></div></div></div><!-- 最近活动 --><div class="card"><div class="card-header"><h5 class="mb-0"><i class="fas fa-history me-2"></i>最近活动</h5></div><div class="card-body"><div class="activity-timeline" id="activityTimeline"><div class="text-center py-4"><i class="fas fa-spinner fa-spin fs-1 text-muted"></i><p class="text-muted mt-2">加载活动记录...</p></div></div></div></div></div><!-- 右侧:快速操作 --><div class="col-lg-4"><!-- 今日任务 --><div class="card mb-4"><div class="card-header"><h6 class="mb-0"><i class="fas fa-tasks me-2"></i>今日任务</h6></div><div class="card-body"><div class="task-item d-flex justify-content-between align-items-center mb-2"><span><i class="fas fa-sign-in-alt text-success me-2"></i>每日登录</span><span class="badge bg-success">+10</span></div><div class="task-item d-flex justify-content-between align-items-center mb-2"><span><i class="fas fa-share text-primary me-2"></i>分享内容 (0/3)</span><span class="badge bg-primary">+5</span></div><div class="task-item d-flex justify-content-between align-items-center mb-2"><span><i class="fas fa-upload text-warning me-2"></i>上传文档 (0/10)</span><span class="badge bg-warning">+8</span></div></div></div><!-- 积分排行 --><div class="card mb-4"><div class="card-header"><h6 class="mb-0"><i class="fas fa-medal me-2"></i>积分排行榜</h6></div><div class="card-body" id="pointsRanking"><div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i><small class="text-muted d-block mt-1">加载排行榜...</small></div></div></div><!-- 快速操作 --><div class="card"><div class="card-header"><h6 class="mb-0"><i class="fas fa-bolt me-2"></i>快速操作</h6></div><div class="card-body"><div class="d-grid gap-2"><a href="{{ url_for('user.edit_profile') }}" class="btn btn-outline-primary btn-sm"><i class="fas fa-edit me-2"></i>编辑资料</a><a href="{{ url_for('user.points_history') }}" class="btn btn-outline-success btn-sm"><i class="fas fa-history me-2"></i>积分记录</a><a href="{{ url_for('user.security') }}" class="btn btn-outline-warning btn-sm"><i class="fas fa-shield-alt me-2"></i>安全设置</a><button class="btn btn-outline-danger btn-sm" id="dailyCheckin"><i class="fas fa-calendar-check me-2"></i>每日签到</button></div></div></div></div></div>
</div>
{% endblock %}{% block extra_js %}
<script>
$(document).ready(function() {// 加载用户数据loadUserStats();loadRecentActivities();loadPointsRanking();// 每日签到$('#dailyCheckin').on('click', function() {dailyCheckin();});
});function loadUserStats() {$.get('/api/user/stats').done(function(data) {// 更新等级进度if (data.next_level) {const progress = data.next_level.progress_percent || 0;$('#levelProgress').css('width', progress + '%');$('#nextLevelInfo').text(`距离 ${data.next_level.level.toUpperCase()} 还需 ${data.next_level.remaining_points} 积分`);} else {$('#nextLevelInfo').text('已达最高等级');$('#levelProgress').css('width', '100%');}// 更新积分环形进度updateCircularProgress('totalPointsProgress', data.total_points, 5000);}).fail(function() {console.error('Failed to load user stats');});
}function loadRecentActivities() {$.get('/api/user/activities').done(function(data) {const timeline = $('#activityTimeline');timeline.empty();if (data.activities && data.activities.length > 0) {data.activities.forEach(function(activity) {const activityHtml = `<div class="activity-item"><div class="d-flex justify-content-between align-items-start"><div><h6 class="mb-1">${getActivityIcon(activity.action)} ${activity.details}</h6><small class="text-muted">${formatDateTime(activity.created_at)}</small></div>${activity.points ? `<span class="badge bg-success">+${activity.points}</span>` : ''}</div></div>`;timeline.append(activityHtml);});} else {timeline.html(`<div class="text-center py-4"><i class="fas fa-inbox fs-1 text-muted"></i><p class="text-muted mt-2">暂无活动记录</p></div>`);}}).fail(function() {$('#activityTimeline').html(`<div class="text-center py-4"><i class="fas fa-exclamation-triangle fs-1 text-warning"></i><p class="text-muted mt-2">加载活动记录失败</p></div>`);});
}function loadPointsRanking() {$.get('/api/user/points-ranking').done(function(data) {const ranking = $('#pointsRanking');ranking.empty();if (data.ranking && data.ranking.length > 0) {data.ranking.forEach(function(user, index) {const medal = ['🥇', '🥈', '🥉'][index] || `${index + 1}.`;const isCurrentUser = user.username === '{{ current_user.username }}';const userHtml = `<div class="d-flex justify-content-between align-items-center mb-2 ${isCurrentUser ? 'bg-light rounded p-2' : ''}"><span><span class="me-2">${medal}</span><strong>${user.display_name}</strong>${isCurrentUser ? '<small class="text-primary">(我)</small>' : ''}</span><span class="badge bg-primary">${user.total_points}</span></div>`;ranking.append(userHtml);});} else {ranking.html('<p class="text-muted text-center">暂无排行数据</p>');}}).fail(function() {$('#pointsRanking').html('<p class="text-danger text-center">加载失败</p>');});
}function dailyCheckin() {const btn = $('#dailyCheckin');const originalText = btn.html();btn.html('<i class="fas fa-spinner fa-spin me-2"></i>签到中...').prop('disabled', true);$.post('/api/user/daily-checkin').done(function(response) {if (response.success) {showToast('签到成功!获得 ' + response.points + ' 积分', 'success');// 刷新页面数据loadUserStats();loadRecentActivities();} else {showToast(response.message || '签到失败', 'warning');}}).fail(function(xhr) {const response = xhr.responseJSON;showToast(response ? response.message : '签到失败,请重试', 'error');}).always(function() {btn.html(originalText).prop('disabled', false);});
}function updateCircularProgress(elementId, value, max) {const circle = document.getElementById(elementId);const radius = 36;const circumference = 2 * Math.PI * radius;const progress = (value / max) * 100;const strokeDasharray = (progress / 100) * circumference;circle.style.strokeDasharray = `${strokeDasharray} ${circumference}`;
}function getActivityIcon(action) {const icons = {'daily_login': '<i class="fas fa-sign-in-alt text-success"></i>','share_content': '<i class="fas fa-share text-primary"></i>','upload_document': '<i class="fas fa-upload text-info"></i>','use_ai_analysis': '<i class="fas fa-robot text-warning"></i>','complete_profile': '<i class="fas fa-user-edit text-success"></i>','level_upgrade': '<i class="fas fa-arrow-up text-gold"></i>'};return icons[action] || '<i class="fas fa-star text-muted"></i>';
}function formatDateTime(dateString) {const date = new Date(dateString);const now = new Date();const diff = now - date;if (diff < 60000) return '刚刚';if (diff < 3600000) return Math.floor(diff / 60000) + ' 分钟前';if (diff < 86400000) return Math.floor(diff / 3600000) + ' 小时前';if (diff < 604800000) return Math.floor(diff / 86400000) + ' 天前';return date.toLocaleDateString();
}function showToast(message, type) {const toastClass = {'success': 'bg-success','warning': 'bg-warning','error': 'bg-danger'}[type] || 'bg-info';const toastHtml = `<div class="toast align-items-center text-white ${toastClass} border-0" role="alert"><div class="d-flex"><div class="toast-body">${message}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div></div>`;// 添加到页面并显示$('body').append(`<div class="toast-container position-fixed top-0 end-0 p-3">${toastHtml}</div>`);$('.toast').toast('show');// 自动清理setTimeout(() => {$('.toast-container').remove();}, 5000);
}
</script>
{% endblock %}

🚀 部署与测试

🧪 单元测试

# tests/test_user_system.py - 用户系统测试
import unittest
from datetime import datetime, timedelta
from app import create_app
from app.models import db
from app.models.user import User
from app.models.points import PointsHistory, UserActivity
from app.services.points_service import PointsService
from app.services.virtual_user_service import VirtualUserServiceclass UserSystemTestCase(unittest.TestCase):def setUp(self):self.app = create_app('testing')self.app_context = self.app.app_context()self.app_context.push()db.create_all()self.client = self.app.test_client()def tearDown(self):db.session.remove()db.drop_all()self.app_context.pop()def test_user_registration_and_verification(self):"""测试用户注册和邮箱验证"""# 注册用户response = self.client.post('/auth/register', json={'username': 'testuser','email': 'test@example.com','password': 'TestPass123'})self.assertEqual(response.status_code, 200)data = response.get_json()self.assertTrue(data['success'])# 验证用户已创建user = User.query.filter_by(username='testuser').first()self.assertIsNotNone(user)self.assertEqual(user.email, 'test@example.com')self.assertFalse(user.is_verified)# 测试邮箱验证token = user.generate_confirmation_token()self.assertTrue(user.confirm_email(token))self.assertTrue(user.is_verified)def test_points_system(self):"""测试积分系统"""user = User(username='testuser', email='test@example.com')user.set_password('testpass123')user.save()# 测试积分奖励result = PointsService.award_points(user, 'daily_login')self.assertTrue(result['success'])self.assertEqual(result['points'], 10)self.assertEqual(user.total_points, 10)# 测试重复奖励限制result = PointsService.award_points(user, 'daily_login')self.assertFalse(result['success'])  # 每日限制# 测试一次性奖励result = PointsService.award_points(user, 'complete_profile')self.assertTrue(result['success'])self.assertEqual(user.total_points, 60)  # 10 + 50# 测试重复一次性奖励result = PointsService.award_points(user, 'complete_profile')self.assertFalse(result['success'])def test_member_level_upgrade(self):"""测试会员等级升级"""user = User(username='testuser', email='test@example.com')user.set_password('testpass123')user.save()# 初始等级self.assertEqual(user.member_level, 'bronze')# 升级到银牌user.add_points(600, '测试积分')self.assertEqual(user.member_level, 'silver')# 升级到金牌user.add_points(1500, '测试积分')self.assertEqual(user.member_level, 'gold')# 升级到白金user.add_points(3000, '测试积分')self.assertEqual(user.member_level, 'platinum')def test_ai_usage_limits(self):"""测试AI使用限制"""user = User(username='testuser', email='test@example.com', member_level='bronze')user.set_password('testpass123')user.save()# 测试青铜用户限制(10次)for i in range(10):self.assertTrue(user.can_use_ai())user.use_ai_call()# 第11次应该被拒绝self.assertFalse(user.can_use_ai())# 升级到银牌后应该可以继续使用user.member_level = 'silver'user.save()self.assertTrue(user.can_use_ai())def test_virtual_user_creation(self):"""测试虚拟用户创建"""service = VirtualUserService()# 创建单个虚拟用户user = service.create_virtual_user('student')self.assertIsNotNone(user)self.assertTrue(user.is_verified)self.assertGreater(user.total_points, 0)self.assertIn(user.member_level, ['bronze', 'silver', 'gold'])# 批量创建虚拟用户users = service.create_batch_virtual_users(3)self.assertEqual(len(users), 3)# 验证用户名唯一性usernames = [user.username for user in users]self.assertEqual(len(usernames), len(set(usernames)))def test_points_consumption(self):"""测试积分消费"""user = User(username='testuser', email='test@example.com')user.set_password('testpass123')user.save()# 添加积分user.add_points(100, '测试积分')# 消费积分result = PointsService.consume_points(user, 30, '测试消费')self.assertTrue(result['success'])self.assertEqual(user.available_points, 70)self.assertEqual(user.points_used, 30)# 积分不足时消费result = PointsService.consume_points(user, 100, '测试消费')self.assertFalse(result['success'])def test_continuous_login_bonus(self):"""测试连续登录奖励"""user = User(username='testuser', email='test@example.com')user.set_password('testpass123')user.save()# 模拟连续3天登录for i in range(3):activity = UserActivity(user_id=user.id,action='earn_points_daily_login',details='每日登录奖励',created_at=datetime.now() - timedelta(days=2-i))activity.save()# 检查连续登录奖励bonus = PointsService.check_continuous_login(user)self.assertGreater(bonus, 0)  # 应该有连续登录奖励if __name__ == '__main__':unittest.main()

🎉 总结与展望

✨ 我们完成了什么?

通过这篇文章,我们构建了一个完整的用户系统:

  • 🔐 完善的认证系统:注册、登录、邮箱验证、密码重置
  • 🏆 灵活的权限控制:基于角色和等级的多层权限管理
  • 🎯 激励性积分系统:多样化积分获取,自动等级升级
  • 🤖 AI驱动增长:智能虚拟用户生成,提升平台活跃度
  • 🛡️ 安全防护机制:频率限制、会话管理、行为监控
  • 🎨 现代化UI界面:响应式设计,优秀的用户体验

📊 系统特色

功能模块特色亮点技术实现
认证系统邮箱验证、密码强度检测Flask-Login + itsdangerous
权限控制装饰器模式、缓存优化RBAC + Redis缓存
积分系统智能规则、防刷机制数据库事务 + 业务逻辑
AI增长智能用户生成、行为模拟GLM-4 API + 算法优化
安全防护多层防护、实时监控Redis限流 + 行为分析

🔮 下一步计划

在下一篇文章中,我们将深入前端界面设计:

🎨 第三篇预告:《现代化前端界面:Bootstrap5 + jQuery打造响应式UI》
  • 📱 完美的响应式设计和移动端适配
  • ⚡ jQuery实现的动态交互效果
  • 🌙 深色/浅色主题切换功能
  • 🔄 AJAX异步数据加载优化
  • 🎭 组件化设计和复用策略

💡 实战建议

  1. 安全优先:用户系统是整个应用的安全基础,不能有丝毫马虎
  2. 体验至上:积分和等级设计要有足够的激励性,但不能过于复杂
  3. 性能考虑:权限检查要使用缓存,避免频繁数据库查询
  4. 扩展性:设计时要考虑未来可能的功能扩展需求
  5. 监控完善:用户行为数据是产品优化的重要依据

基于Madechango的真实项目经验,这套用户系统已经在生产环境中稳定运行,支撑了数万用户的使用。希望这篇文章能帮助你构建出色的用户系统!


📌 重要提醒:本文基于真实项目Madechango的用户系统设计,但为了教学目的,对某些敏感实现进行了简化处理。在生产环境中,还需要考虑更多的安全性、合规性和性能优化措施。

🔗 相关链接

  • 项目原型:https://madechango.com
  • Flask-Login文档:https://flask-login.readthedocs.io/
  • Bootstrap文档:https://getbootstrap.com/docs/5.3/
  • Redis文档:https://redis.io/documentation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/100400.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/100400.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/100400.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 大视界 -- Java 大数据在智能安防视频监控系统中的视频内容理解与智能预警升级

Java 大视界 -- Java 大数据在智能安防视频监控系统中的视频内容理解与智能预警升级引言&#xff1a;正文&#xff1a;一、传统安防监控的 “三重困局”&#xff1a;看不全、看不懂、反应慢1.1 人工盯屏 “力不从心”1.1.1 摄像头密度与人力的矛盾1.1.2 录像调阅 “马后炮”1.2…

OpenHarmony包管理子系统核心源码深度解读:从BundleManager到AMS,彻底打通应用安装、卸载与沙箱机制全链路

目录 架构概览 核心组件详解 包安装流程分析 包卸载流程分析 包更新流程分析 包信息存储机制 Launcher界面管控 开机默认系统应用安装机制<

简单聊聊神经网络中的反向传播

参考文章&#xff1a; 一文弄懂神经网络中的反向传播法——BackPropagation - Charlotte77 - 博客园 反向传播求偏导原理简单理解_反向传播偏导-CSDN博客 这篇文章是笔者在读完上述两篇参考文章后的整理或者说按照自己的理解进行的一些补充&#xff0c;强烈推荐先阅读上述两篇文…

JSP自驾游管理系统46u2v--(程序+源码+数据库+调试部署+开发环境)

本系统&#xff08;程序源码数据库调试部署开发环境&#xff09;带论文文档1万字以上&#xff0c;文末可获取&#xff0c;系统界面在最后面。系统程序文件列表开题报告内容一、研究背景与意义 近年来&#xff0c;自驾游因自由度高、个性化强成为国内旅游市场增长最快的领域&…

通过 SQL 快速使用 OceanBase 向量检索学习笔记

背景 AI时代离不开向量数据库&#xff0c;向量数据库简单说就是在数据库中用多维向量存储某类事物的特征&#xff0c;通过公式计算各个向量在空间坐标系中的位置关系&#xff0c;以此来判断事物之间的相似性。相关基础概念如下: ● Embedding ● 距离/相似性度量 ○ Cosine dis…

PromptAD:首次引入提示学习,实现精准工业异常检测,1张正常样本即可超越现有方法

近年来&#xff0c;工业异常检测&#xff08;Anomaly Detection&#xff09;在智能制造、质量监控等领域扮演着越来越重要的角色。传统方法通常依赖大量正常样本进行训练&#xff0c;而在实际生产中&#xff0c;异常样本稀少甚至不存在&#xff0c;能否仅凭少量正常样本就实现精…

算法 --- 字符串

字符串 字符串算法题目主要处理文本的查找、匹配、比较、变换和统计问题&#xff0c;其核心特点是输入数据为字符序列&#xff0c;解题关键在于利用其连续性、前缀性、字典序等特性&#xff0c;并常借助哈希、自动机、指针滑动、动态规划等技巧高效处理。 详细分类型与适用场景…

SpringBoot中 Gzip 压缩的两种开启方式:GeoJSON 瘦身实战

目录 前言 一、GZIP压缩知识简介 1、什么是Gzip 2、Gzip特点 3、Gzip在GIS方面的应用 二、SpringBoot中开启Gzip的方式 1、在SpringBoot中开启Gzip的知识简介 2、SpringBoot中GeoJSON的实例 三、全局开启Gzip实现 1、实现原理 2、实现效果 四、局部约定配置 1、实现…

PPTist+cpolar:开源演示文稿的远程创作方案

文章目录前言【视频教程】1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址6. 配置固定公网地址前言 PPTist作为开源在线演示文稿工具&#xff0c;提供媲美PowerPoint的核心功能&#xff0c;支持多页面编辑、图表插入、音视频嵌入和动画效果设置。特…

服务注册/服务发现-Eureka

目的&#xff1a;解决微服务在调用远程服务时URL写死的问题注册中心服务提供者&#xff08;Server&#xff09;&#xff1a;一次业务中&#xff0c;被其他微服务调用的服务&#xff0c;也就是提供接口给其他微服务。服务消费者&#xff08;Client&#xff09;:一次业务中&#…

cuda stream

基本概念 cuda stream表示GPU的一个操作队列&#xff0c;操作在队列中按照一定的顺序执行&#xff0c;也可以向流中添加一定的操作如核函数的启动、内存的复制、事件的启动和结束等 一个流中的不同操作有着严格的顺序&#xff0c;但是不同流之间没有任何限制 cuda stream中排队…

数据结构:完全二叉树

完全二叉树 定义&#xff1a; 按层序遍历&#xff08;从上到下&#xff0c;从左到右&#xff09;填充节点。 除了最后一层外&#xff0c;其余各层必须全满。 最后一层的节点必须 连续靠左。 完全二叉树不一定是满二叉树。 满二叉树 (Full Binary Tree)&#xff1a;每个节点都有…

【Java初学基础】⭐Object()顶级父类与它的重要方法equals()

object类常见方法/*** native 方法&#xff0c;用于返回当前运行时对象的 Class 对象&#xff0c;使用了 final 关键字修饰&#xff0c;故不允许子类重写。*/ public final native Class<?> getClass() /*** native 方法&#xff0c;用于返回对象的哈希码&#xff0c;主…

用深度学习(LSTM)实现时间序列预测:从数据到闭环预测全解析

用深度学习&#xff08;LSTM&#xff09;实现时间序列预测&#xff1a;从数据到闭环预测全解析 时间序列预测是工业、金融、环境等领域的核心需求——小到预测设备温度波动&#xff0c;大到预测股价走势&#xff0c;都需要从历史数据中挖掘时序规律。长短期记忆网络&#xff08…

gpu-z功能介绍,安装与使用方法

GPU-Z 功能介绍、安装与使用方法 一、核心功能 硬件信息检测 识别显卡型号、制造商、核心架构&#xff08;如NVIDIA Ada Lovelace、AMD RDNA 3&#xff09;、制造工艺&#xff08;如5nm、7nm&#xff09;。显示显存类型&#xff08;GDDR6X、HBM2e&#xff09;、容量、带宽及显…

数据搬家后如何处理旧 iPhone

每年&#xff0c;苹果都会推出新款 iPhone&#xff0c;激发了人们升级到 iPhone 17、iPhone 17 Pro、iPhone 17 Pro Max 或 iPhone Air 等新机型的热情。但在获得新 iPhone 之前&#xff0c;有一件重要的事情要做&#xff1a;将数据从旧 iPhone 转移到新设备。虽然许多用户都能…

Java关键字深度解析(上)

这是一份全面的Java关键字实战指南 目录 1.数据类型关键字:内存布局与性能优化 1.1 基础类型的内存密码 byte-内存的极简主义者 int-Java世界的万能钥匙 long - 时间与ID的守护者 1.2 引用类型的架构设计 String-不是关键字但胜于关键字 2.访问修饰符:企业级权限控制 …

C语言深度解析:指针数组与数组指针的区别与应用

目录 1 引言&#xff1a;从名字理解本质区别 2 指针数组&#xff1a;灵活管理多个指针 2.1 基本概念与声明方式 2.2 内存布局与特性 2.3 典型应用场景&#xff1a;字符串数组与多维度数据管理 2.3.1 静态分配示例&#xff1a;字符串数组 2.3.2 动态分配示例&#xff1a;…

Node.js 高级应用:负载均衡与流量限制

在当今高并发的网络应用环境中&#xff0c;如何有效地分配服务器资源并保护系统免受恶意攻击是开发者必须面对的重要问题。Node.js 作为一款广受欢迎的服务器端 JavaScript 运行时环境&#xff0c;提供了丰富的工具和模块来应对这些挑战。本文将深入探讨如何在 Node.js 中实现负…

信任链验证流程

信任链验证流程 (The Chain of Trust)整个过程就像一场严格的接力赛&#xff0c;每一棒都必须从可信的上一位手中接过接力棒&#xff08;信任&#xff09;&#xff0c;验证无误后&#xff0c;再跑自己的那段路&#xff0c;并把信任传递给下一棒现在&#xff0c;我们来详细解读图…