登录,登出
首页,修改密码,个人信息
后台管理,用户管理
JWT认证
Node.js 12.16.3
Vue 4.5.11
Python 3.8
Django 3.1.3
Django REST framework 3.12.2
SQLite 3
models.JSONField
,SQLite默认不兼容,所以需要下载sqlite3.dll
文件替换下:sqlite-dll-win32-x86-3340100.zip
这个软件包,解压后将D:\Program Files (x86)\Python38-32\DLLs\sqlite3.dll
替换。npm
淘宝镜像:npm config set registry https://registry.npm.taobao.org
Vue CLI
:npm install -g @vue/cli
teprunner-frontend
项目:vue create teprunner-frontend
项目名字请随意。
推荐一个图标下载网站:https://www.easyicon.net/。
package.json
,安装项目所需依赖:axios
用于异步请求,发送http
给后端。element-ui
为饿了么开源前端框架,简化了从头写html麻烦,高度复用,统一风格。vue-router
提供了路由跳转,在上个时代,路由是在后端来控制的,把页面渲染后返回给前端直接展示,前后端分离后,后端只负责返回数据,把控制权交给前端。devDependencies
是写代码用到的依赖,这里把eslint
和prettier
标出来了,它们是用来做代码静态检查的,配置后能给与代码规范提示,帮你写出更漂亮的代码,同样是在package.json
文件编辑:npm install
进行安装。有可能会出现下图提示:npm audit fix
就修复好了:vue.config.js
文件,添加Vue项目配置:args[0].title
给网页设置了浏览器title。proxy
指定了后端接口根路径为/api
,后端服务器访问地址为http://127.0.0.1:8000/
,这是Django启动后默认本地域名和端口。element-ui
默认页面是会出现滚动条的,在登录页会显得很丑,需要在public/index.html
加上样式:main.js
,把需要初始化加载的代码写在这里:index.html
文件中div
:html
文件,其他组件都是挂载到这个div
下面的。其中有个App.vue
:router-view
是一块区域,用来展示路由匹配到的组件,也就是说所有路由匹配到的组件都会通过App.vue
根组件来展示。路由配置在router/index.js
文件中编辑:/login
登录和/
首页,首页只有菜单,没有具体内容,显示没有意义,所以重定向到了后台管理的用户管理。第二层路由是具体的功能模块,作为子路由放在首页路由下,比如后台管理。后台管理的子模块用户管理也放到了后台管理的子路由,根据url
访问路径定义父子路由关系。meta.requireAuth
,用来设置哪些路由需要拦截,这里把首页设置为True
:index.html
一个html
文件,其他页面都是放在views
文件夹下,新建一个views/login/index.vue
文件:el-form
标签添加用户名、密码、忘记密码和登录按钮。:model
给表单绑定了数据对象,分别填充到form.username
、form.password
、form.rememberMe
::rules
定义了表单规则,比如是否必填:登录没有做用户名和密码校验,新增用户时才会做校验。
localStorage
中移除userInfo
和token
,登录信息保留7天:login
方法,发起登录请求:views/home/index.vue
,编写首页代码:<router-link>
提供了链接跳转,左上角logo跳转到首页,顶部导航栏根据后端返回的authList
权限菜单进行显示,因为后台管理只有管理员才能访问。接着编写右上角区域代码:el-dialog
做了个弹出框:/users/passwords/set
接口:authList
,并判断是否有权限,没有权限的话跳转到登录页面:components
文件夹下:el-menu
标签:slot
是个插槽,相当于挖个坑在这,用的时候填一下坑,类似于模板。然后用el-breadcrumb
做了个面包屑,点击可跳转到相应路由。接着就把左侧菜单应用到后台管理模块上,新建views/console/index.vue
:views/console/userManagement.vue
,编写用户管理代码:el-form
和el-table
标签。表格数据通过:data
绑定到了tableData
对象,调用后端接口后,从响应中拿数据填充:dialogFormVisible
默认为False
不可见,点击新增按钮后,会设置为True
。新建views/console/addUser.vue
文件编写用户弹窗的代码:userManagement.vue
和新增用户addUser.vue
这两个组件叫做父子组件,父组件如果想传值给子组件,需要通过props
来实现:watch
能监视传值的状态,及时渲染。watch
不是必须的,等到做编辑用例和用例运行结果的时候,会更加体会到它的作用。
nameValidator
和pwdValidator
是公共方法,定义在utils/const.js
文件中:utils
文件夹下还有个commonMethods.js
文件,定义了一些公共js
方法:axios.js
,它定义了异步请求实例:header
需要包括jwt
请求头:Authorization: Bearer
。还添加了一个响应拦截器:pip install --default-timeout=6000 -i https://pypi.tuna.tsinghua.edu.cn/simple django
teprunner-backend
项目:django-admin startproject teprunnerbackend
项目名字请随意。
-
,如果想用短横线,可以先去掉短横线执行命令,再手动改回来,外层这个名字对项目没有任何影响:pip install --default-timeout=6000 -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
user
应用:django-admin startapp user
teprunnerbackend/settings.py
:django-cors-headers
为Django提供了跨域访问的解决方案,需要配置ALLOWED_HOSTS
为*
,允许所有域访问,并注册INSTALLED_APPS
和MIDDLEWARE
。user
应用也需要在INSTALLED_APPS
注册后才会生效。继续改配置,把时区改为Asia/Shanghai
:user
表进行自定义改造,所以通过配置里面的AUTH_USER_MODEL
指定为刚刚创建的user
应用的User
。REST_FRAMEWORK
是Django RESTful framework的配置项,同样要进行自定义改造,所以这里通过配置DEFAULT_AUTHENTICATION_CLASSES
指定认证鉴权类为CustomJSONWebTokenAuthentication
,通过EXCEPTION_HANDLER
指定异常处理函数为custom_exception_handler
,通过DEFAULT_PAGINATION_CLASS
指定分页类为CustomPagination
。JWT_AUTH
是jwt
的配置项,定义了过期时间为30天,允许刷新,刷新间隔,响应处理,header
前缀。最后补充了django-cors-headers
的3个配置。teprunnerbackend/urls.py
:user
的url
都添加到api/users/
下面。新建user/urls.py
文件:path()
只接受可调用对象,所以类视图需要使用as_view()
进行转化,比如views.UserLogin.as_view()
。函数视图直接写上函数名就可以了,比如views.update_password
。user/models.py
文件,添加数据模型:model
建立了代码和数据库的映射,这称为orm
,对象关系映射。基础表定义了共有的created_at
和updated_at
字段。auto_now_add
表示记录新增时间,auto_now
表示记录更新时间,都是自动进行,无需手动写代码来处理。用户表继承了Django自带的AbstractUser
,REQUIRED_FIELDS
规定了哪些字段必填,username
和password
是隐式规定了必填的,不需要设置,默认email
也是必填,这里把它去掉。Django默认表名会加上 django_
前缀,使用Meta.db_table
来定义没有前缀的表名。
model
写完了,执行以下命令同步到数据库中,创建表结构:python manage.py makemigrations
python manage.py migrate
SQLiteStudio
,选择根目录的db.sqlite3
文件:Role
有个models.JSONField
字段,为菜单权限JSON
,使用Django的fixtures
给项目添加初始化数据:fixtures
名字是固定的,就像pytest的conftest.py
一样,只认这个名字。user.json
存放数据:auth
里面定义了菜单,对应首页的顶部导航栏的栏目,比如本文只添加了后台管理。access
表示角色是否有权限访问,只有管理员的这条数据,access
为true
。通过以下命令把这些数据写入数据库中:python manage.py loaddata user
Django会在 user.fixtures
目录下自动找名字为user
的.json
、.xml
或.yaml
文件进行加载。
user/serializers.py
文件写序列化的代码。Django序列化是指,把数据库的数据转化为json
返回给前端,反序列化是指把前端传过来的json
写入数据库。先写登录的序列化器:serializers.ModelSerializer
,一般需要在Meta
定义两个属性,model
指定相应的模型,fields
指定所需要的的字段,这些字段就是json
的key
。图中的roleName
不属于User
表的字段,所以通过定义为serializers.SerializerMethodField()
,再定义get_roleName()
方法来取。serializer
写好后,写视图,编辑user/views.py
:jwt
认证,所以这里继承了JSONWebTokenAPIView
,提取请求参数,check_password()
简单校验了下请求的密码和数据库的密码hash
值是否相等,后面的代码是JSONWebTokenAPIView.post
方法现成的,重写了一遍。ErrInvalidPassword
等是在user/errors.py
定义的错误响应:这样可读性会更高。响应状态码也建议这么写 status=status.HTTP_500_INTERNAL_SERVER_ERROR
,from rest_framework import status
已经定义好了所有状态码的常量。
user/utils.py
文件,编写jwt_response_payload_handler
来定义登录接口的响应结构:token
、user
、auth
三个字段。custom_exception_handler
规范了一下异常响应信息。这2个方法都是在settings.py
中的REST_FRAMEWORK
配置过的,还有一项配置是分页,新建user/pagination.py
文件:PageNumberPagination
,指定了查询参数名page
、perPage
,自定义了响应字段名currentPage
、items
、totalNum
、totalPage
,并添加了2个字段hasNext
和hasPrev
。user/authentications.py
:BaseJSONWebTokenAuthentication
。通过get_authorization_header
提取请求头中的Authorization
字段,没有就提示“缺失JWT请求头”。后面的代码是父类现成的。jwt
认证也做好了:serializers.py
和views.py
两个文件,序列化器提供数据库表字段和响应json
的序列化和反序列化,视图使用序列化器,编写业务处理代码。按照这个思路,编写用户的增删改查功能,先在serializers.py
文件中写2个序列化器UserCreateSerializer
和UserPagingSerializer
:User
模型创建了2个序列化器。图中标红了代码是把int
的id
值转化为了str
类型,方便前端处理。is_staff
表示是否为管理员,这个名字是Django定的。再写views.py
:GenericViewSet
是Django REST framework提供了超级封装的类视图,一般不需要重写,给queryset
和serializer_class
赋值就可以了。不过因为有些自定义规则,所以本项目进行了复写。permission_classes
指定了接口访问权限,IsAdminUser
表示必须管理员才能访问,也是Django定义好的,和前面的is_staff
相对应:user/permissions.py
新建了个IsTester
,用来控制某些功能只能测试使用:本文还用不到这个。
list
方法:username
和nickname
的模糊查询。create
方法:user
表,根据角色名是否包含管理员,判断是否写is_staff
字段,接着用入库后产生的user_id
写user_role
表。注意最后一行的status
,新增的话,状态码返回201
。put
方法:user_role
表数据时,需要根据老角色和新角色,比较差异后,添加新增的,删除废旧的。delete
方法:user
表和user_role
表。注意最后一行的status
,删除的话,状态码返回204
。user_detail
方法,返回单个用户信息:GenericViewSet
的这些请求方法在user/urls.py
文件中配置映射关系:<int:pk>
定义了url
中的整形参数,pk
为变量名,通过kwargs["pk"]
来取。ListAPIView
:ListAPIView
,还有CreateAPIView
、RetrieveAPIView
、ListCreateAPIView
等,按需取用。APIView
来实现:put
方法,从请求url
中获取参数值user_id
,查询user
对象后,调用预置的set_password
方法,把密码重置为qa123456
。记得调用user.save()
把数据更新到数据库。flask
框架一样,感受写纯后端接口的体验,按这个方法来写修改密码接口:@api_view(['PUT'])
是Django REST framework提供的方法装饰器。修改密码时,会对jwt
进行解码,获取到user_id
,然后检查老密码是否和数据库中的密码hash
值一致。python manage.py runserver
npm run serve
admin
,密码为qa123456
,登录成功后可以尝试走一遍功能检查下:点击左上角logo,不会出现跳转到空白页。
点击右上角信息,弹出下拉菜单,分别有修改密码、个人信息、退出登录。
点击退出,返回登录页,重新登录。
查询右上角个人信息,包括用户名、昵称、角色。
通过右上角下拉菜单修改密码,和老密码不匹配会提示修改失败,填写正确信息会修改成功,自动跳转到登录页面重新登录。输入老密码登录失败,输入新密码登录成功。
新增用户,保持默认密码,新增成功后,用qa123456
登录成功。
新增用户,选择自定义密码,新增成功后,用qa123456
登录失败,用自定义密码登录成功。
新增用户,分别创建管理员、开发、测试3个角色用户。
使用新用户登录,管理员用户能登录成功,开发和测试由于没有后台管理权限,点击登录接口后会提示“无菜单权限”。
修改用户,修改用户名、密码,修改测试角色用户为管理员角色,重新登录,能看到用户名、密码已更新为修改后的用户名、密码,并且管理员角色生效,能登进去看到后台管理功能。
输入用户名或昵称,点击搜索按钮,测试模糊查询功能正常,重置后清空搜索框,自动查询一次列表。
点击删除按钮,提示是否确认删除,确认后删除成功,检查数据库user_role
表数据也被清理干净。
切换分页,刷新列表,选择不同分页条数,正常计算显示相应的分页总数。
找到自定义密码的用户,点击重置密码,重置成功后,重新登录,使用自定义密码登录失败,使用默认密码qa123456
登录成功。
点击左侧菜单旁边的面包屑按钮,能收起和展开左侧菜单。
由于时间关系,目前还没有做角色管理功能,角色通过后端Django的 fixtures/user.json
进行数据初始化。
mock.js
来搭建,也可以使用Python的FastAPI
,不嫌麻烦用Spring Boot
或者Nginx
做一个也可以。不过为了方便起见,选择上手就能用的可以省不少心。一些网站会提供在线Mock服务,在网站上填写url
和response body
,有个缺点是我找了一圈都没有发现能设置响应状态码的,比如在调试axios.js
的响应拦截器时,就需要根据404
、500
来进行调试,看效果。寻寻觅觅,发现平时都在用的Postman,直接可以做Mock Server。首先登陆Postman,只有登陆后才能使用这个功能:+ New
:Mock Server
。依次填写请求方法、请求路径、响应状态码、响应体:联系客服