路飞:如何为开源项目贡献代码、pycharm使用git、登录注册功能分析、手机号是否存在接口、多方式登录接口、腾讯云短信申请、补充
一、如何为开源项目贡献代码
当我们闲着的时候可以去github,gitee逛一逛,看到好的开源项目,我们可以去看一看
当我们发现这个项目有bug,或是你给他添加了一些优秀的新功能,这时候我们也想把代码合并到他的项目中去,我们应该怎么做?
这里我们以李强学长的项目为目标项目
步骤一
点击右上角的fork按钮
步骤二
选中我们自己,然后点击确认,就会把这个项目复制到我们的仓库中
等待几秒后我们就可以看到在自己的仓库中,多了该项目(相当于克隆过来了)
步骤三
这时候我们可以使用git bash去clone项目,也可以直接在pycharm中使用图形化界面进行克隆
步骤四
接着就是一些重复的操作,配置环境和模块,然后编写代码
步骤五
依旧是使用git把本地项目提交到远程仓库(我们自己仓库中克隆的那个项目)中
步骤六
这时候我们就需要提交pr,这时候源分支是我们的项目,目标分支是哪个开源项目的对应分支
提交了pr后就需要等待这个作者查看
但是这东西也比较混乱,我们可以看到一些打的项目中有很多的人去提交,对于名气大的项目来说是请求太多,处理不过来
在一些小项目中,作者可能看到这个bug,就自己把他改了,不要你的合并
二、pycharm使用git
pycharm 的功能十分的强大,只要是用命令可以实现的git操作,都可以在pycharm中使用点击完成
pycharm使用git
1. 大前提:先在settings里面把git配上
打开Pycharm, 点击File -> Settings -> Version Control --> Git ,在 Path to Git executable中选择本地的git.exe路径:
2. 克隆代码
① 先从gitee上复制一下远端地址
② 再去克隆,点击Pycharm导航栏中的 VCS -> Get from Version Control ,粘贴地址,并指定要放的路径
3 新增文件提交到版本库
单独提交test1.py到本地版本库:就只选中test1.py,点击右键,选择Git -> Commit File
输入提交的备注信息,选择Commit and Push
Compare with:
这里注意下,就是每次提交之前,点击Compare with,让本地文件跟版本库做一下比较再提。它不仅可以跟当前的比,还可以跟历史的比,甚至还可以跟某些分支比。
在我又加了第二行代码,做下比较再提交:
4. git add . (把所有工作区内容提交到暂存区 Git -> Add )
5. git commit -m '注释'(将所有变化提交到版本库 )
6. git pull (将远端的dev拉到本地的dev)
# 将本地提交到远端之前,要先从远端拉到本地
同样的作用:直接点箭头,也可以操作从远端拉到本地,它拉下来就直接自动merge进来:
7. git push到远端(把本地的dev推到远端的dev )
查看远端dev已经添加进去:
双击注释可以看到代码:
同样点击箭头也可以完成push操作:
查看远程dev仓库:
8. 线上分支合并(远端的dev合并到远端的master分支上)
>> 先切到dev分支上,再点击+Pull Request
提交后管理员审查代码(代码review),通过之后点击合并,合并之后点击接收Pull Request
合进来之后,可以看到master分支上就可以看到已经合进来了:
9. 远程仓库回滚
切换到master分支,输入命令 git log 可以看到日志, 如果是要恢复到最初的状态,复制版本号
按q退出后输入命令:
git reset --hard e36c43c4657ed915cc685c9afcdc7daa7aea4198
现在本地master 就恢复到了最初状态(远端还没有恢复)
输入命令提交到远端:
git push origin master -f # f是强制提交到远端,这个一般不要用
现在查看远端master, 已经恢复了:
10. 添加或删除远程仓库
11. 分支相关操作
点击pycharm右下角
>> 在本地建一个dev分支
>> 把本地dev分支 push到远端dev
远端就有了dev:
>> 删除本地dev
要先切到本地master,直接在本地的dev分支上不能删
再delete dev
>> 把远端dev拉到本地dev
本地就有了dev:
最后注意,协同开发 分支合并,本地推到远端之前 永远要将最新的版本拉下来
三、登录注册功能分析
接口分析
- 登陆注册功能需要支持多方式登录接口:用户名/手机号/邮箱 +密码都可以登录
- 在登陆时如果选择使用手机号登陆,需要验证手机号是否存在(校验手机号是否存在的接口)
目前我们先写这个校验手机号是否存在的功能,后面分析的功能需要借助腾讯云短信平台
- 发送手机验证码接口
- 短信登陆接口
- 注册接口
四、手机号是否存在接口
4.1编写思路及流程
因为这是登陆注册功能中的接口,所以需要写在user app中
在user app中创建urls.py文件
并且在总路由中添加user app的路由分发
urlpatterns = [
path('admin/', admin.site.urls),
path('test_logger/', views.test_logger),
path('test_exception/', views.TestExcepiton.as_view()),
path('test_response/', views.TestAPIResponse.as_view()),
# 开启media访问
# re_path('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
# 路由分发
path('api/v1/home/', include('home.urls')),
path('api/v1/user/', include('user.urls')),
]
接着我们去user app中编写对应功能的视图类
这里我们可以偷懒使用GenericViewSet
- 初始版本(面条版本)
views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from utils.common_response import APIResponse
from .models import User
# 初级版本(面条版代码)
class UserView(GenericViewSet):
'这里的父类不能使用ModelViewSet'
'因为路由和请求对应的方法都使用视图类自带的,容易被别人猜到'
'比如我们发现你的url发送请求的时候,有list、login'
'我就可以通过这种方式携带数据来跳过登陆,直接往你的数据库生成信息,安全漏洞很大'
@action(methods=['GET'],detail=False)
def send_msg(self, request, *args, **kwargs):
# 首先我们从地址栏获取到手机号
'这里的query_params之前在分析APIVIEW的Request的时候提到,他相当于request.GET,获取到url中携带的数据'
mobile = request.query_params.get('mobile')
if mobile:
'这里就是判断有没有在注册的时候填写手机号'
user = User.objects.filter(mobile=mobile).first()
if user:
'''
这里有点逻辑需要分析一下
不管我们的手机号有没有在数据库中找到
对于我们这个接口来说,他只是判断手机号是否存在
我们可以自行选择代码的逻辑
这里我们设置成无论手机号是否存在都返回状态码100
但是对应的msg要修改
然后给他添加一个exist信息,用于指明是否在数据库中存在
'''
return APIResponse(code=100, msg='手机号存在', exist=True)
else:
return APIResponse(code=100, msg='手机号不存在', exist=False)
else:
'没有填写的话就返回信息给前端'
return APIResponse(code=999, msg='手机号必须填')
这个版本的代码已经实现了我们的接口需求,但是不够美观
- 升级版本
这里的核心思路就是因为我们做了全局异常捕获,所以很多的返回信息可以直接用报错的方式来返回给前端(节省代码)
views.py
class UserView(GenericViewSet):
'这里的父类不能使用ModelViewSet'
'因为路由和请求对应的方法都使用视图类自带的,容易被别人猜到'
'比如我们发现你的url发送请求的时候,有list、login'
'我就可以通过这种方式携带数据来跳过登陆,直接往你的数据库生成信息,安全漏洞很大'
@action(methods=['GET'],detail=False)
def send_msg(self, request, *args, **kwargs):
try:
# 首先我们从地址栏获取到手机号
'这里的query_params之前在分析APIVIEW的Request的时候提到,他相当于request.GET,获取到url中携带的数据'
'同时这里必须用get取值,用字典的方式会报错,因为他的形式虽然像字典,但他的对象类型是queryDict'
mobile = request.query_params.get('mobile')
'''
获取到手机号之后我们使用get来获取数据库的信息
这时候只有手机号在数据库存在的时候,返回给前端的code才是100
否则都是用报错的方式给前端返回信息
'''
User.objects.get(mobile=mobile)
except Exception as e:
raise e
return APIResponse(code=100, msg='手机号存在')
在user app的路由中配置
urls.py
from rest_framework.routers import SimpleRouter
from . import views
router = SimpleRouter()
# 访问 http://127.0.0.1:8000/api/v1/user/userinfo/send_msg/ ---->get 请求就可以查询手机号是否存在
router.register('userinfo', views.UserView, 'userinfo')
urlpatterns = [
]
urlpatterns += router.urls
使用postman测试
经过测试我们发现号码不存在的时候报错信息被写死了,所以我们要去我们编写的全局异常捕获那里修改一下,别的地方不用改,把全局的异常的提示信息改成exc,让他直接把报错信息发出来
common_exceptions
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from utils.common_logger import logger
# 只要走到这个函数中,一定是出异常了,所以要记录日志
def exception_handler(exc, context):
# 1 记录日志 : 哪个ip地址,用户id是多少,访问哪个路径,执行哪个视图函数,出了什么错
request = context.get('request')
view = context.get('view')
ip = request.META.get('REMOTE_ADDR')
user_id = request.user.pk
path = request.get_full_path()
response = drf_exception_handler(exc, context)
if response:
'之所以drf的异常用warning记录是因为可能会出现没有认证导致的报错等等'
logger.warning('drf出了异常,异常是:%s' % str(exc))
# drf的异常已经处理了--->直接取detail 会有点小小的问题,碰到再解决
res = Response({'code': 999, 'msg': response.data.get('detail', '服务器异常,请联系系统管理员')})
else:
# djagno的异常,咱们要处理
logger.error('用户【%s】,ip地址为【%s】,访问地址为【%s】,执行视图函数为【%s】,出错是【%s】' % (user_id, ip, path, str(view), str(exc)))
res = Response({'code': 888, 'msg': str(exc)})
return res
4.2 视图函数模版
将来我们可以把视图类都用这样的方式套进去,这样不仅可以优化代码,还可以减少报错
def send_sms(self, request, *args, **kwargs):
try:
# 放心大胆写
except Exception as e:
raise e
return APIResponse()
五、多方式登录接口
这里我们设置成可以使用用户名或手机号或邮箱+密码登录
5.1 视图类
from .serializer import UserLoginSerializer
class UserView(GenericViewSet):
'这里的父类不能使用ModelViewSet'
'因为路由和请求对应的方法都使用视图类自带的,容易被别人猜到'
'比如我们发现你的url发送请求的时候,有list、login'
'我就可以通过这种方式携带数据来跳过登陆,直接往你的数据库生成信息,安全漏洞很大'
'这里的is_active用于判断用户是否被拉入黑名单'
queryset = User.objects.all().filter(is_active=True)
'因为登陆接口中我们把所有的校验都放到了一格序列化类中,所以需要导入,虽然他不起到校验的功能'
serializer_class = UserLoginSerializer
@action(methods=['GET'], detail=False)
def send_msg(self, request, *args, **kwargs):
try:
# 首先我们从地址栏获取到手机号
'这里的query_params之前在分析APIVIEW的Request的时候提到,他相当于request.GET,获取到url中携带的数据'
'同时这里必须用get取值,用字典的方式会报错,因为他的形式虽然像字典,但他的对象类型是queryDict'
mobile = request.query_params.get('mobile')
'''
获取到手机号之后我们使用get来获取数据库的信息
这时候只有手机号在数据库存在的时候,返回给前端的code才是100
否则都是用报错的方式给前端返回信息
'''
User.objects.get(mobile=mobile)
except Exception as e:
raise e
return APIResponse(code=100, msg='手机号存在')
@action(methods=['POST'], detail=False)
def send_mul(self, request, *args, **kwargs):
'''
这里我们使用把校验逻辑放在序列化类中的方式实现多方式的登陆接口
1、取出前端传入的用户名和密码
2、通过用户名和密码去数据库查询对象
3、如果能查到,签发token
4、返回数据给前端
'''
ser = self.get_serializer(data=request.data)
# ser = UserLoginSerializer()
'上面两种方式其实是一样的,这里只是为了让pycharm能自动提示'
'这里的raise_exception就是在校验失败的时候报错的作用'
'然后之前分析源码的时候我们了解到执行这个判断,就相当于使用序列化类对数据进行了校验,会执行钩子函数'
ser.is_valid(raise_exception=True)
'实例化 序列化类对象时,可以传入context 字典 context 是 视图类和序列化类沟通的桥梁'
'context是在序列化类全局钩子,放入的'
token = ser.context.get('token')
username = ser.context.get('username')
return APIResponse(token=token, username=username) # {code:100,msg:成功,token:aasdfa,username:lqz}
5.2 序列化类
序列化类的部分,因为需要用到jwt认证,所以需要安装djangorestframework-jwt模块
pip install djangorestframework-jwt
from rest_framework import serializers
from .models import User
import re
from rest_framework.exceptions import APIException
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
import rest_framework_jwt
'这个序列化类不做序列化和反序列化,只是用于登陆的校验'
class UserLoginSerializer(serializers.ModelSerializer):
username = serializers.CharField()
'''
这里需要我们重新定义字段自己的校验
否则就会因为用户表中定义的时候,规定的唯一限制导致报错
unique=True
'''
class Meta:
model = User
fields = ['username', 'password']
'这是全局钩子'
def validate(self, attrs):
'''
这里我们使用把校验逻辑放在序列化类中的方式实现多方式的登陆接口
1、取出前端传入的用户名和密码
2、通过用户名和密码去数据库查询对象
3、如果能查到,签发token
4、返回数据给前端
'''
'这里也是在之前研究源码的时候了解到的,在使用序列化类校验的时候,是想校验字段本身的条件,然后是局部钩子,再是全局钩子'
'因此这里的attrs就是经过前两者校验的数据,就是{username:xxx, password:123}'
user = self._get_user(attrs)
token = self._get_token(user)
'这里我们还需要把token和用户名放到ser的context中去'
'''
在视图函数中我们把user和token存储在content中
但是这不是随意命名的,序列化类继承了ModelSerializer
我们可以看到ModelSerializer是Serializer
Serializer的父类是BaseSerializer
BaseSerializer中有__init__方法
前面的几个类中都没有
因此创建对象的时候就是 BaseSerializer创建的
而他的源码中创建了一个content属性
self._context = kwargs.pop('context', {})
因此我们可以传数据的时候传入context参数,他的数据需要是字典类型
'''
self.context['token'] = token
self.context['username'] = user.username
return attrs
'''
这里我们看到,各个功能的代码被我们封装到了对应的函数中
但是我们会发现他们都是用一个下划线开头的
这里是公司内部一个约定俗称的规定
在类内部的隐藏属性和方法,本来是要使用两个下划线__开头的
但是公司内通常使用一个下划线来表示
他就表示你的这个函数是不想给外部用的,但是如果人家实在想用也能用
'''
def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
'接下来我们使用正则来匹配电话的格式'
if re.match(r'^[3-9][0-9]{9}$', username):
user = User.objects.filter(mobile=username).first()
elif re.match(r'^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$', username):
user = User.objects.filter(email=username).first()
else:
user = User.objects.filter(username=username).first()
if user and user.check_password(password):
return user
else:
raise APIException('用户名不存在或密码错误')
def _get_token(self, user):
'''
这里是根据源码分析的时候
我们提到JSONWebTokenAPIView就是他的序列化类
然后再他的validate方法中payload获取荷载(通过用户名)
然后通过荷载获得token
'''
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return token
使用postman测试
六、腾讯云短信申请
当我们编写登陆接口的时候,需要给用户发送验证码,来实现登陆的双向确认
而发送短信接口,需要借助于第三方短信平台,是收费的
-腾讯云短信
-阿里 大于短信
-。。。。
这里我们使用腾讯云发送短信
步骤一
进入腾讯云网页,在右上角点击登陆后,使用微信扫码登陆
步骤二
在上方的搜索框内输入短信进行搜索
步骤三
第一次进入的时候需要同意相关协议
步骤四
接着我们根据他提供的教程进行操作即可
步骤五
创建签名的时候有比较严格的限制,这里我们使用微信公众号去创建签名
步骤六
进入微信公众平台注册公众号,点击右上角的注册
微信公众平台:https://mp.weixin.qq.com/
步骤七
填写基本信息
步骤八
步骤九
根据要求等级个人信息
步骤十
填写公众号信息,这里就是填写公众号名称和简介
步骤十一
注册成功后会进入下图界面
我们可以在这里提前截图,等下在腾讯云那边会用到
步骤十二
在腾讯云的概览界面点击创建签名后填写信息
步骤十三
接着去申请模版
步骤十四
当前面两项申请成功后,我们就可以根据他提供的教程发送短信了
有API和SDK两种方式
# API SDK
-API: 咱们学习过的API接口,写起来比较麻烦,自己分析接口
-SDK:集成开发工具包,分语言,java,python,go
-使用python 对api进行封装成包
-以后我们只需要,安装包,导入包,包名.发送短信,传入参数,就可以发送了
- 只要官方提供sdk,优先用sdk
pip install tencentcloud-sdk-python
七、补充
这个人的项目可以去研究一下
# https://gitee.com/aeasringnar/django-RESTfulAPI/tree/master
# https://gitee.com/aeasringnar