.html',
views.bindsuccess,
name='bindsuccess'),
+
+ # 5. OAuth登录入口URL(命名为"oauthlogin")
+ # 路径:/oauth/oauthlogin(前端点击第三方登录按钮时跳转到该地址)
+ # 由views.oauthlogin视图函数处理(生成第三方平台的授权链接,引导用户跳转授权)
path(
r'oauth/oauthlogin',
views.oauthlogin,
- name='oauthlogin')]
+ name='oauthlogin')
+]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/oauth/views.py b/src/DjangoBlog-master/oauth/views.py
index 12e3a6e..5cfec60 100644
--- a/src/DjangoBlog-master/oauth/views.py
+++ b/src/DjangoBlog-master/oauth/views.py
@@ -1,155 +1,216 @@
import logging
-# Create your views here.
-from urllib.parse import urlparse
+# 定义视图函数(处理用户请求的逻辑)
+from urllib.parse import urlparse # 用于解析URL,验证跳转地址合法性
from django.conf import settings
-from django.contrib.auth import get_user_model
-from django.contrib.auth import login
-from django.core.exceptions import ObjectDoesNotExist
-from django.db import transaction
-from django.http import HttpResponseForbidden
-from django.http import HttpResponseRedirect
-from django.shortcuts import get_object_or_404
-from django.shortcuts import render
-from django.urls import reverse
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import FormView
-
-from djangoblog.blog_signals import oauth_user_login_signal
-from djangoblog.utils import get_current_site
-from djangoblog.utils import send_email, get_sha256
-from oauth.forms import RequireEmailForm
-from .models import OAuthUser
-from .oauthmanager import get_manager_by_type, OAuthAccessTokenException
+from django.contrib.auth import get_user_model # 获取自定义用户模型
+from django.contrib.auth import login # 用于用户登录
+from django.core.exceptions import ObjectDoesNotExist # 用于捕获对象不存在异常
+from django.db import transaction # 用于数据库事务,确保操作原子性
+from django.http import HttpResponseForbidden # 用于返回403禁止访问响应
+from django.http import HttpResponseRedirect # 用于重定向响应
+from django.shortcuts import get_object_or_404 # 获取对象,不存在则返回404
+from django.shortcuts import render # 用于渲染模板
+from django.urls import reverse # 用于反向解析URL
+from django.utils import timezone # 用于获取当前时间
+from django.utils.translation import gettext_lazy as _ # 用于国际化翻译
+from django.views.generic import FormView # 基于类的表单视图
+from djangoblog.blog_signals import oauth_user_login_signal # 导入OAuth登录信号
+from djangoblog.utils import get_current_site # 获取当前网站域名
+from djangoblog.utils import send_email, get_sha256 # 导入发送邮件和SHA256加密工具
+from oauth.forms import RequireEmailForm # 导入补充邮箱表单
+from .models import OAuthUser # 导入OAuth用户模型
+from .oauthmanager import get_manager_by_type, OAuthAccessTokenException # 导入OAuth管理器和异常
+
+# 创建当前模块的日志记录器
logger = logging.getLogger(__name__)
def get_redirecturl(request):
+ """
+ 处理授权后的跳转URL,确保安全性(只允许本站内跳转)
+ """
+ # 从请求参数中获取跳转地址(默认值为None)
nexturl = request.GET.get('next_url', None)
+ # 过滤非法跳转地址(如登录页)
if not nexturl or nexturl == '/login/' or nexturl == '/login':
nexturl = '/'
return nexturl
+ # 解析URL,检查是否为本站域名
p = urlparse(nexturl)
- if p.netloc:
- site = get_current_site().domain
+ if p.netloc: # 若URL包含域名
+ site = get_current_site().domain # 获取当前网站域名
+ # 去除www.前缀后比较,确保跳转地址为本站
if not p.netloc.replace('www.', '') == site.replace('www.', ''):
- logger.info('非法url:' + nexturl)
- return "/"
- return nexturl
+ logger.info('非法url:' + nexturl) # 记录非法URL日志
+ return "/" # 非法则跳转到首页
+ return nexturl # 返回合法的跳转地址
def oauthlogin(request):
+ """
+ OAuth登录入口:生成第三方平台的授权链接,引导用户跳转
+ """
+ # 获取请求中的平台类型(如'weibo'、'github')
type = request.GET.get('type', None)
- if not type:
+ if not type: # 若未指定平台类型,跳转到首页
return HttpResponseRedirect('/')
+ # 获取对应平台的OAuth管理器
manager = get_manager_by_type(type)
- if not manager:
+ if not manager: # 若管理器不存在,跳转到首页
return HttpResponseRedirect('/')
+ # 获取安全的跳转地址(授权后返回的页面)
nexturl = get_redirecturl(request)
+ # 生成第三方平台的授权URL
authorizeurl = manager.get_authorization_url(nexturl)
+ # 重定向到第三方平台的授权页面
return HttpResponseRedirect(authorizeurl)
def authorize(request):
+ """
+ OAuth授权回调处理:解析授权码、获取用户信息、绑定本地用户
+ """
+ # 获取平台类型
type = request.GET.get('type', None)
if not type:
return HttpResponseRedirect('/')
+ # 获取对应平台的OAuth管理器
manager = get_manager_by_type(type)
if not manager:
return HttpResponseRedirect('/')
+ # 获取授权码code(第三方平台返回)
code = request.GET.get('code', None)
try:
+ # 使用授权码获取access_token
rsp = manager.get_access_token_by_code(code)
except OAuthAccessTokenException as e:
+ # 捕获token获取失败异常,记录日志并跳转首页
logger.warning("OAuthAccessTokenException:" + str(e))
return HttpResponseRedirect('/')
except Exception as e:
+ # 捕获其他异常,记录日志
logger.error(e)
rsp = None
+ # 获取授权后的跳转地址
nexturl = get_redirecturl(request)
- if not rsp:
+ if not rsp: # 若获取token失败,重新生成授权链接并重定向
return HttpResponseRedirect(manager.get_authorization_url(nexturl))
+
+ # 通过token获取第三方用户信息
user = manager.get_oauth_userinfo()
- if user:
+ if user: # 若成功获取用户信息
+ # 处理空昵称(生成默认昵称:djangoblog+时间戳)
if not user.nickname or not user.nickname.strip():
user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
+
+ # 检查该第三方用户是否已存在(避免重复创建)
try:
temp = OAuthUser.objects.get(type=type, openid=user.openid)
+ # 更新已有用户的头像、元数据、昵称
temp.picture = user.picture
temp.metadata = user.metadata
temp.nickname = user.nickname
- user = temp
+ user = temp # 使用已有用户对象
except ObjectDoesNotExist:
+ # 若不存在,则使用新创建的用户对象
pass
- # facebook的token过长
+
+ # Facebook的token过长,这里清空(根据实际需求处理)
if type == 'facebook':
user.token = ''
+
+ # 若用户信息包含邮箱,直接绑定本地用户
if user.email:
- with transaction.atomic():
+ with transaction.atomic(): # 数据库事务:确保操作原子性
author = None
+ # 尝试获取已关联的本地用户
try:
author = get_user_model().objects.get(id=user.author_id)
except ObjectDoesNotExist:
pass
+
+ # 若未关联本地用户,则创建或获取本地用户
if not author:
+ # 按邮箱查询或创建本地用户
result = get_user_model().objects.get_or_create(email=user.email)
author = result[0]
+ # 若为新创建的用户,设置用户名和来源
if result[1]:
try:
+ # 检查昵称是否已存在
get_user_model().objects.get(username=user.nickname)
except ObjectDoesNotExist:
+ # 昵称不存在,直接使用
author.username = user.nickname
else:
+ # 昵称已存在,生成默认用户名
author.username = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
- author.source = 'authorize'
- author.save()
-
+ author.source = 'authorize' # 标记来源为OAuth授权
+ author.save() # 保存用户信息
+
+ # 关联本地用户到OAuthUser
user.author = author
user.save()
-
+
+ # 发送OAuth用户登录信号(供其他模块监听处理)
oauth_user_login_signal.send(
sender=authorize.__class__, id=user.id)
+ # 登录用户(创建会话)
login(request, author)
+ # 重定向到授权前的页面
return HttpResponseRedirect(nexturl)
else:
+ # 若用户信息不含邮箱,保存OAuthUser并跳转到补充邮箱页面
user.save()
- url = reverse('oauth:require_email', kwargs={
- 'oauthid': user.id
- })
-
+ url = reverse('oauth:require_email', kwargs={'oauthid': user.id})
return HttpResponseRedirect(url)
else:
+ # 若未获取到用户信息,跳转到授权前的页面
return HttpResponseRedirect(nexturl)
def emailconfirm(request, id, sign):
- if not sign:
- return HttpResponseForbidden()
- if not get_sha256(settings.SECRET_KEY +
- str(id) +
- settings.SECRET_KEY).upper() == sign.upper():
+ """
+ 邮箱验证:验证签名合法性,完成本地用户绑定
+ """
+ if not sign: # 签名为空,返回403禁止访问
return HttpResponseForbidden()
+
+ # 验证签名(使用SECRET_KEY+ID+SECRET_KEY生成SHA256签名)
+ if not get_sha256(settings.SECRET_KEY + str(id) + settings.SECRET_KEY).upper() == sign.upper():
+ return HttpResponseForbidden() # 签名不匹配,返回403
+
+ # 获取对应的OAuthUser对象(不存在则返回404)
oauthuser = get_object_or_404(OAuthUser, pk=id)
- with transaction.atomic():
+
+ with transaction.atomic(): # 数据库事务
+ # 检查是否已关联本地用户
if oauthuser.author:
author = get_user_model().objects.get(pk=oauthuser.author_id)
else:
+ # 按邮箱查询或创建本地用户
result = get_user_model().objects.get_or_create(email=oauthuser.email)
author = result[0]
+ # 若为新创建的用户,设置用户名和来源
if result[1]:
- author.source = 'emailconfirm'
- author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip(
- ) else "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
+ author.source = 'emailconfirm' # 标记来源为邮箱验证
+ # 设置用户名为OAuth用户的昵称(为空则生成默认值)
+ author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip() else "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
author.save()
+
+ # 关联本地用户到OAuthUser
oauthuser.author = author
oauthuser.save()
+
+ # 发送OAuth用户登录信号
oauth_user_login_signal.send(
- sender=emailconfirm.__class__,
- id=oauthuser.id)
+ sender=emailconfirm.__class__, id=oauthuser.id)
+ # 登录用户
login(request, author)
-
+
+ # 发送绑定成功邮件
site = 'http://' + get_current_site().domain
content = _('''
Congratulations, you have successfully bound your email address. You can use
@@ -161,59 +222,63 @@ def emailconfirm(request, id, sign):
If the link above cannot be opened, please copy this link to your browser.
%(site)s
''') % {'oauthuser_type': oauthuser.type, 'site': site}
-
send_email(emailto=[oauthuser.email, ], title=_('Congratulations on your successful binding!'), content=content)
- url = reverse('oauth:bindsuccess', kwargs={
- 'oauthid': id
- })
+
+ # 重定向到绑定成功页面
+ url = reverse('oauth:bindsuccess', kwargs={'oauthid': id})
url = url + '?type=success'
return HttpResponseRedirect(url)
class RequireEmailView(FormView):
- form_class = RequireEmailForm
- template_name = 'oauth/require_email.html'
+ """
+ 补充邮箱视图:处理用户补充邮箱的表单提交
+ """
+ form_class = RequireEmailForm # 使用的表单类
+ template_name = 'oauth/require_email.html' # 渲染的模板
def get(self, request, *args, **kwargs):
- oauthid = self.kwargs['oauthid']
+ """处理GET请求:展示补充邮箱表单"""
+ oauthid = self.kwargs['oauthid'] # 从URL中获取OAuthUser的ID
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
- if oauthuser.email:
- pass
- # return HttpResponseRedirect('/')
-
+ # 若已存在邮箱,可在此处添加逻辑(如跳转到首页)
+ # if oauthuser.email:
+ # return HttpResponseRedirect('/')
return super(RequireEmailView, self).get(request, *args, **kwargs)
def get_initial(self):
+ """设置表单初始值:填充oauthid"""
oauthid = self.kwargs['oauthid']
- return {
- 'email': '',
- 'oauthid': oauthid
- }
+ return {'email': '', 'oauthid': oauthid}
- def get_context_data(self, **kwargs):
+ def get_context_data(self,** kwargs):
+ """向模板传递额外上下文:用户头像"""
oauthid = self.kwargs['oauthid']
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
if oauthuser.picture:
- kwargs['picture'] = oauthuser.picture
+ kwargs['picture'] = oauthuser.picture # 传递头像URL
return super(RequireEmailView, self).get_context_data(**kwargs)
def form_valid(self, form):
- email = form.cleaned_data['email']
- oauthid = form.cleaned_data['oauthid']
+ """处理表单验证通过的逻辑:保存邮箱并发送验证邮件"""
+ email = form.cleaned_data['email'] # 获取清洗后的邮箱
+ oauthid = form.cleaned_data['oauthid'] # 获取OAuthUser的ID
oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
+ # 保存用户邮箱
oauthuser.email = email
oauthuser.save()
- sign = get_sha256(settings.SECRET_KEY +
- str(oauthuser.id) + settings.SECRET_KEY)
+
+ # 生成邮箱验证签名
+ sign = get_sha256(settings.SECRET_KEY + str(oauthuser.id) + settings.SECRET_KEY)
+ # 获取当前网站域名(开发环境使用127.0.0.1:8000)
site = get_current_site().domain
if settings.DEBUG:
site = '127.0.0.1:8000'
- path = reverse('oauth:email_confirm', kwargs={
- 'id': oauthid,
- 'sign': sign
- })
+ # 生成邮箱验证链接
+ path = reverse('oauth:email_confirm', kwargs={'id': oauthid, 'sign': sign})
url = "http://{site}{path}".format(site=site, path=path)
-
+
+ # 发送邮箱验证邮件
content = _("""
Please click the link below to bind your email
@@ -226,16 +291,21 @@ class RequireEmailView(FormView):
%(url)s
""") % {'url': url}
send_email(emailto=[email, ], title=_('Bind your email'), content=content)
- url = reverse('oauth:bindsuccess', kwargs={
- 'oauthid': oauthid
- })
+
+ # 重定向到绑定成功页面(提示用户查收邮件)
+ url = reverse('oauth:bindsuccess', kwargs={'oauthid': oauthid})
url = url + '?type=email'
return HttpResponseRedirect(url)
def bindsuccess(request, oauthid):
- type = request.GET.get('type', None)
- oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
+ """
+ 绑定成功页面:展示绑定结果信息
+ """
+ type = request.GET.get('type', None) # 获取类型(email:待验证;success:已验证)
+ oauthuser = get_object_or_404(OAuthUser, pk=oauthid) # 获取对应的OAuthUser
+
+ # 根据类型设置页面标题和内容
if type == 'email':
title = _('Bind your email')
content = _(
@@ -247,7 +317,9 @@ def bindsuccess(request, oauthid):
"Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s"
" to directly log in to this website without a password. You are welcome to continue to follow this site." % {
'oauthuser_type': oauthuser.type})
+
+ # 渲染绑定成功页面
return render(request, 'oauth/bindsuccess.html', {
'title': title,
'content': content
- })
+ })
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 75d9766..e69de29 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1 +0,0 @@
-print('hello world')
diff --git a/电影评价软件界面设计说明书模板.docx b/电影评价软件界面设计说明书模板.docx
new file mode 100644
index 0000000..3c9baa0
Binary files /dev/null and b/电影评价软件界面设计说明书模板.docx differ