100 行代码实现用户登录注册与 RESTful 接口 - 手把手教程附 Python 源码
在开发大多数应用时,用户系统都是必不可少的部分,而我们总是需要开发围绕用户的登录,注册,获取,更新等接口。在这篇文章将带你用一百多行代码简洁地实现一套这样的用户鉴权与 RESTful 接口,并使用 Session 来处理用户的登录登出
我们将使用 UtilMeta 框架 完成接口开发,这是一个开源的 Python 后端元框架,同时支持接入与适配 Django, Flask, FastAPI 等主流 Python 框架,并且能简洁高效地开发 RESTful 接口
0. 安装框架
使用如下命令即可安装 UtilMeta 框架
pip install utilmeta
UtilMeta 框架需要 Python 版本 >= 3.8
1. 创建项目
我们使用如下命令来创建一个新项目
meta setup demo-user
我们的项目将会使用 Django 作为底层框架,所以在提示选择 backend 的时候我们可以输入 django
项目创建好后,我们需要先对服务的数据库连接进行配置,打开 server.py,在 service 的声明下面插入以下代码
service = UtilMeta(
__name__,
name='demo-user',
backend=django,
)
# new +++++
from utilmeta.core.server.backends.django import DjangoSettings
from utilmeta.core.orm import DatabaseConnections, Database
service.use(DjangoSettings(
secret_key='YOUR_SECRET_KEY',
))
service.use(DatabaseConnections({
'default': Database(
name='db',
engine='sqlite3',
)
}))
在插入的代码中,我们声明了 Django 的配置信息与数据库连接的配置
由于 Django 使用 app (应用) 的方式来管理数据模型,接下来我们使用如下的命令来创建一个名为 user 的 app
cd demo-user
meta add user
可以看到在我们的项目文件夹中新创建出了一个 user 文件夹,其中包括
/user
/migrations
api.py
models.py
schema.py
其中 migrations 文件夹是 Django 用来处理数据库迁移文件的,models.py 是我们编写数据模型的地方
应用创建完成后,我们将 server.py 的 Django 设置中插入一行代码来注入新创建的 user app
service.use(DjangoSettings(
secret_key='YOUR_SECRET_KEY',
apps=['user'] # new
))
至此我们完成了项目的配置和初始化
2. 编写用户模型
用户的登录注册 API 当然是围绕 “用户” 进行的了,在开发 API 之前,我们需要先编写好用户的数据模型,我们打开 user/models.py,编写
from django.db import models
from utilmeta.core.orm.backends.django.models import AbstractSession, PasswordField
class User(models.Model):
username = models.CharField(max_length=20, unique=True)
password = PasswordField(max_length=100)
signup_time = models.DateTimeField(auto_now_add=True)
class Session(AbstractSession):
user = models.ForeignKey(
User, related_name='sessions',
null=True, default=None, on_delete=models.CASCADE
)
我们首先编写了一个用户模型 User, 其中包含了以下字段
username:用户名字段,需要是不能重复的(unique=True)password:密码字段,使用的 PasswordField 会自动对输入的明文密码进行哈希加密signup_time:注册时间字段
可以看到除了 User 模型外,我们还编写了一个用户记录用户会话和登录状态的 Session 模型,继承自 UtilMeta 提供的模型基类 AbstractSession,我们将通过这个模型实现用户的登录与鉴权
初始化数据库
当我们编写好数据模型后即可使用 Django 提供的迁移命令方便地创建对应的数据表了,由于我们使用的是 SQLite,所以无需提前安装数据库软件,只需要运行以下两行命令即可完成数据库的创建
meta makemigrations
meta migrate
当看到以下输出时即表示你已完成了数据库的创建
Running migrations:
Applying contenttypes.0001_initial... OK
Applying user.0001_initial... OK
数据库迁移命令根据 server.py 中的数据库配置,在项目文件夹中创建了一个名为 db 的 SQLite 数据库,其中已经完成了 User 和 Session 模型的建表
3. 配置 Session 与用户鉴权
编写完用户鉴权相关的模型,我们就可以开始开发鉴权相关的逻辑了,我们在 user 文件夹中新建一个 auth.py 文件,编写 Session 与用户鉴权的配置
from utilmeta.core import auth
from utilmeta.core.auth.session.db import DBSessionSchema, DBSession
from .models import Session, User
USER_ID = '_user_id'
class SessionSchema(DBSessionSchema):
def get_session_data(self):
data = super().get_session_data()
data.update(user_id=self.get(USER_ID))
return data
session_config = DBSession(
session_model=Session,
engine=SessionSchema,
cookie=DBSession.Cookie(
name='sessionid',
age=7 * 24 * 3600,
http_only=True
)
)
user_config = auth.User(
user_model=User,
authentication=session_config,
key=USER_ID,
login_fields=User.username,
password_field=User.password,
)
在这段代码中,SessionSchema 是处理和存储 Session 数据的核心引擎,session_config 是声明 Session 配置的组件,定义了我们刚编写的 Session 模型以及引擎,并且配置了相应的 Cookie 策略
为了简化案例,我们选择了基于数据库的 Session 实现(
DBSession),实际开发中,我们常常使用 Redis 等缓存作为 Session 的存储实现,或者使用 缓存+数据库 的方式,这些实现方式 UtilMeta 都支持,你可以在 Session 鉴权文档 中找到更多的使用方式
另外在代码中我们也声明了 user_config 用户鉴权配置,其中的参数包括
user_model:指定鉴权的用户模型,就是我上一节中编写好的 User 模型authentication:指定鉴权策略,我们传入刚刚定义的session_config,表示着用户鉴权使用 Session 进行key:在 Session 数据中保存当前用户 ID 的名称,默认是'_user_id'login_fields:能用于登录的字段,如用户标识名,邮箱等,需要是唯一的password_field:用户的密码字段,声明这些可以让 UtilMeta 自动帮你处理登录校验逻辑
4. 编写用户 API
注册接口
我们首先来编写用户的注册接口,注册接口应该接收用户名,密码字段,校验用户名没有被占用后完成注册,并返回新注册的用户数据
我们打开 user/api.py 编写注册接口
from datetime import datetime
from utilmeta.core import api, orm
from utilmeta.utils import exceptions
from .models import User
from . import auth
class SignupSchema(orm.Schema[User]):
username: str
password: str
class UserSchema(orm.Schema[User]):
id: int
username: str
signup_time: datetime
@auth.session_config.plugin
class UserAPI(api.API):
@api.post
def signup(self, data: SignupSchema = request.Body) -> UserSchema:
if User.objects.filter(username=data.username).exists():
raise exceptions.BadRequest('Username exists')
data.save()
auth.user_config.login_user(
request=self.request,
user=data.get_instance()
)
return UserSchema.init(data.pk)
我们使用 @api 装饰器定义提供接口服务的 API 函数,其中有 get / post / put / patch / delete 等 HTTP 方法,我们注册接口使用的是 post 方法,你可以使用装饰器的第一个参数指定接口的路径,如果没有的话(比如例子中),就会自动使用函数的名称(signup)作为路径
我们首先声明了注册接口所接受的数据结构 SignupSchema 作为请求体(request.Body)的类型声明,这样 UtilMeta 就会对注册接口的请求体进行解析并转化为一个 SignupSchema 实例,不符合要求的请求会被框架自动拒绝并返回 400 响应
注册接口中的逻辑为
- 检测请求中的
username是否已被注册 - 调用
data.save()方法保存数据 - 为当前请求使用
login_user方法登录新注册的用户 - 使用
UserSchema.init(data.pk)将新用户的数据初始化为UserSchema实例后返回
UtilMeta 实现了一套高效的声明式 ORM 查询体系,我们在声明 Schema 类时便使用
orm.Schema[User]绑定了模型,这样我们就可以通过它的方法来实现数据的增删改查了,你可以在 数据查询与 ORM 文档 中查看它的更多用法
另外我们发现在 UserAPI 类被施加了 @auth.session_config.plugin 这一装饰器插件,这是 Session 配置应用到 API 上的方式,这个插件能在每次请求结束后对请求所更新的 Session 数据进行保存,并返回对应的 Set-Cookie
登录登出接口
接下来我们编写用户的登录与登出接口
from datetime import datetime
from utilmeta.core import api, orm, request
from utilmeta.utils import exceptions
from .models import User
from . import auth
import utype
class LoginSchema(utype.Schema):
username: str
password: str
@auth.session_config.plugin
class UserAPI(api.API):
@api.post
def signup(self): ...
# new ++++
@api.post
def login(self, data: LoginSchema = request.Body) -> UserSchema:
user = auth.user_config.login(
request=self.request,
ident=data.username,
password=data.password
)
if not user:
raise exceptions.PermissionDenied('Username of password wrong')
return UserSchema.init(user)
@api.post
def logout(self, session: auth.SessionSchema = auth.session_config):
session.flush()
在登录接口中,我们直接调用了鉴权配置中的 login() 方法来完成登录,由于我们已经配置好了登录字段与密码字段,UtilMeta 可以自动帮我们完成密码校验与登录,如果成功登录,便返回相应的用户实例
所以当返回为空时,我们便抛出错误返回登录失败,而成功登录后,我们调用 UserSchema.init 方法将登录的用户数据返回给客户端
而对于登出接口,我们只需将当前请求 Session 的数据清空即可,我们使用之前声明的 session_config 作为 API 函数参数的默认值从而接收当前请求的 Session 对象,然后在函数中调用 session.flush() 清空 Session 数据
用户信息的获取与更新
当我们了解了 Schema Query 的用法后,编写用户信息的获取与更新接口就非常简单了,如下
from datetime import datetime
from utilmeta.core import api, orm, request
from utilmeta.utils import exceptions
from .models import User
from . import auth
import utype
class UserUpdateSchema(orm.Schema[User]):
id: int = orm.Field(no_input=True)
username: str = orm.Field(required=False)
password: str = orm.Field(required=False)
@auth.session_config.plugin
class UserAPI(api.API):
@api.post
def signup(self): ...
@api.post
def login(self): ...
@api.post
def logout(self): ...
# new ++++
def get(self, user: User = auth.user_config) -> UserSchema:
return UserSchema.init(user)
def put(self, data: UserUpdateSchema = request.Body,
user: User = auth.user_config) -> UserSchema:
data.id = user.pk
data.save()
return UserSchema.init(data.pk)
当我们声明了用户鉴权配置后,在任何一个需要用户登录才能访问的接口,我们都可以在接口参数中声明 user: User = auth.user_config 从而拿到当前请求用户的实例,如果请求没有登录,则 UtilMeta 会自动处理并返回 401 Unauthorized
在 get 接口中,我们直接将当前的请求用户的数据用 UserSchema 初始化并返回给客户端
在 put 接口中,我们将当前用户的 ID 赋值给接收到 UserUpdateSchema 实例的 id 字段,然后保存并返回更新后的用户数据
由于我们不能允许请求用户任意指定要更新的用户 ID,所以对于请求数据的 id 字段我们使用了 no_input=True 的选项,这其实也是一种常见的权限策略,即一个用户只能更新自己的信息
当你的函数直接使用 get / put / patch / post / delete 等 HTTP 动词进行命名时,它们就会自动绑定对应的方法,路径与 API 类的路径保持一致,这些方法称为这个 API 类的核心方法
至此我们的 API 就全部开发完成了
整合 API
为了使我们开发的 UserAPI 能够提供访问,我们需要把它 挂载 到服务的根 API 上,我们回到 server.py,修改 RootAPI 的声明
from utilmeta.core.server.backends.django import DjangoSettings
service.use(DjangoSettings(
secret_key='YOUR_SECRET_KEY',
apps=['user']
))
# new +++
service.setup()
from user.api import UserAPI
class RootAPI(api.API):
user: UserAPI
service.mount(RootAPI, route='/api')
我们将开发好的 UserAPI 挂载到了 RootAPI 的 user 属性,意味着 UserAPI 的路径被挂载到了 /api/user,其中定义的接口路径也相应延申,如
GET /api/user:获取用户信息PUT /api/user:更新用户信息POST /api/user/login:用户登录POST /api/user/logout:用户登出POST /api/user/signup:用户注册
这样的 API 树级挂载对于组织接口架构和定义树状的接口路由非常方便
对于使用 Django 的 API 服务,请在导入任何模型或 API 前加入
service.setup()完成服务的初始化,这样 django 才能正确识别所有的数据模型
5. 运行 API
在项目文件夹中使用如下命令即可将 API 服务运行起来
meta run
或者你也可以使用
python server.py
当你看到如下输出时表示服务已成功启动
UtilMeta v2.4.1 starting service [demo-user]
...
Starting development server at http://127.0.0.1:8000/
你可以通过调整
server.py中的 UtilMeta 服务声明里的host和port参数来改变 API 服务监听的地址
6. 调试 API
启动好 API 服务后我们就可以调试我们的接口了,我们可以使用 UtilMeta 自带的客户端测试工具方便地调试接口,我们在项目目录中新建一个 test.py 文件,写入调试 API 的代码
from server import service
if __name__ == '__main__':
with service.get_client(live=True) as client:
r1 = client.post('user/signup', data={
'username': 'user1',
'password': '123123'
})
r1.print()
r2 = client.get('user')
r2.print()
其中编写了用户注册接口和获取当前用户接口的调试代码,当我们启动服务并运行 test.py 时,我们可以看到的输出类似
Response [200 OK] "POST /api/user/signup"
application/json (76)
{'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T12:29:33.684594'}
Response [200 OK] "GET /api/user"
application/json (76)
{'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T12:29:33.684594'}
这说明我们的注册接口和获取用户的接口开发成功,首先注册接口返回了正确的结果,然后注册接口登录了新注册的用户,所以之后访问用户获取接口也得到了同样的结果
在
with代码块中,客户端会记忆响应中Set-Cookie所存储的 cookies 并发送到接下来的请求中,所以我们可以看到与真实的浏览器类似的会话效果
UtilMeta 服务实例的 get_client 方法用于获取一个服务的客户端实例,你可以直接调用这个实例的 get, post 等方法发起 HTTP 请求,将会得到一个 utilmeta.core.response.Response 响应,这与我们在 API 服务中生成的响应类型一致,其中常用的属性有
status:响应的状态码data:解析后的响应数据,如果是 JSON 响应体,则会得到一个dict或list类型的数据headers:响应头request:响应对应的请求对象,有请求的方法,路径等参数信息
get_client方法中的live参数如果没有开启,则是直接调用对应的接口函数进行调试,无需启动服务
所以你也可以使用这个客户端编写单元测试,比如
from server import service
def test_signup():
with service.get_client(live=True) as client:
r1 = client.post('user/signup', data={
'username': 'user1',
'password': '123123'
})
assert r1.status == 200
assert isinstance(r1.data, dict)
assert r1.data.get('username') == 'user1'
我们还可以测试登录,登出与更新接口,比如在登出后 cookies 应该被清空,之后获取当前用户也应该返回空,最后完整的调试代码与对应的输出如下
from server import service
if __name__ == '__main__':
with service.get_client(live=True) as client:
r1 = client.post('user/signup', data={
'username': 'user1',
'password': '123123'
})
r1.print()
# Response [200 OK] "POST /api/user/signup"
# application/json (75)
# {'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T13:29:03.336134'}
r2 = client.get('user')
r2.print()
# Response [200 OK] "GET /api/user"
# application/json (75)
# {'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T13:29:03.336134'}
r3 = client.post('user/logout')
r3.print()
# Response [200 OK] "POST /api/user/logout"
# text/html (0)
r4 = client.get('user')
r4.print()
# Response [401 Unauthorized] "GET /api/user"
# text/html (0)
r5 = client.post('user/login', data={
'username': 'user1',
'password': '123123'
})
# Response [200 OK] "POST /api/user/login"
# application/json (75)
# {'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T13:29:03.336134'}
r5.print()
r6 = client.get('user')
r6.print()
# Response [200 OK] "GET /api/user"
# application/json (75)
# {'username': 'user1', 'id': 1, 'signup_time': '2024-01-29T13:29:03.336134'}
r7 = client.put('user', data={
'username': 'user-updated',
'password': '123456'
})
r7.print()
# Response [200 OK] "PUT /api/user"
# application/json (82)
# {'username': 'user-updated', 'id': 1, 'signup_time': '2024-01-29T13:44:30.095711'}
源码与资料
我同时也是 UtilMeta 框架的作者,如果你有什么问题也欢迎联系我,我的全网 ID 和微信号都是 voidZXL
100 行代码实现用户登录注册与 RESTful 接口 - 手把手教程附 Python 源码的更多相关文章
- JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- javaweb学习总结(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- JavaWeb学习 (二十一)————基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- 基于Servlet+JSP+JavaBean开发模式的用户登录注册
http://www.cnblogs.com/xdp-gacl/p/3902537.html 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBea ...
- javaweb(二十二)——基于Servlet+JSP+JavaBean开发模式的用户登录注册
一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp ...
- vue新手入门之使用vue框架搭建用户登录注册案例,手动搭建webpack+Vue项目(附源码,图文详解,亲测有效)
前言 本篇随笔主要写了手动搭建一个webpack+Vue项目,掌握相关loader的安装与使用,包括css-loader.style-loader.vue-loader.url-loader.sass ...
- android安卓Sqlite数据库实现用户登录注册
看了很多别人写的安卓SQlite数据的操作代码,一点也不通俗易懂,我觉得我写的不错,而且安卓项目也用上了,所以在博客园里保存分享一下!建立一个类 并继承SQLiteOpenHelper public ...
- 纯JSP实现用户登录注册,记事本
没有美化,没有格式,没有样式 1.JSP登陆注册 将用户注册的信息保存在application对象中,用于登录时的验证. 首页如下: 如果未登录,在 session 中找不到 currentUser ...
- Java Spring+Mysql+Mybatis 实现用户登录注册功能
前言: 最近在学习Java的编程,前辈让我写一个包含数据库和前端的用户登录功能,通过看博客等我先是写了一个最基础的servlet+jsp,再到后来开始用maven进行编程,最终的完成版是一个 Spri ...
- 100行代码实现现代版Router
原文:http://www.html-js.com/article/JavaScript-version-100-lines-of-code-to-achieve-a-modern-version ...
随机推荐
- [STM32H7] 实战技能分享,如何让工程代码各种优化等级通吃,含MDK AC5,AC6,IAR和GCC
引出问题: 一个好的工程项目代码,特别是开源类的,如果能做到各种优化等级通吃,是一种非常好的工程案例,这样别人借鉴的时候,可以方便的适配到自己工程里.但实际项目中,针对一款产品代码,我们一般不会 ...
- CSS - 工具类 tool.css
/* flex */ .flex{ display: flex; } .f1{ flex:1 } .flex-center{ align-items: center; ...
- k8s~istio的安装与核心组件
安装istio 在线安装:https://istio.io/latest/docs/setup/getting-started/#download 或者直接在这里下载:https://github.c ...
- [转帖]深入JVM - Code Cache内存池
深入JVM - Code Cache内存池 1. 本文内容 本文简要介绍JVM的 Code Cache(本地代码缓存池). 2. Code Cache 简要介绍 简单来说,JVM会将字节码编译为本地机 ...
- [转帖]Oracle中INITRANS和MAXTRANS参数
每个块都有一个块首部.这个块首部中有一个事务表.事务表中会建立一些条目来描述哪些事务将块上的哪些行/元素锁定.这个事务表的初始大小由对象的INITRANS 设置指定.对于表,这个值默认为2(索引的IN ...
- [转帖]Jmeter 压测中配置https证书
本文章 主要介绍证书的获取.处理.配置到jmeter中. 1. 获取证书 首先:谷歌浏览器 打开网站,点击 地址栏的锁(表示https),选择 "证书"---"隐私.搜索 ...
- [转帖]调试springboot数据库系统应用时常用debug日志配置, 解决问题缩小范围时常用
https://www.yihaomen.com/article/1853.html 摘要: 用 spring boot 开发应用时,在遇到麻烦问题时,经常会打开debug日志,下面记录一个通用的思路 ...
- [转帖]总结:记一次K8S容器OOM案例
一.背景 最近遇到个现象,hubble-api-open组件过段时间会内容占满,从而被K8S强制重启. 让我困惑的是,已经设置了-XX:MaxRAMPercentage=75.0,我觉得留有了一定的空 ...
- [转帖] Linux文本命令技巧(上)
Linux文本命令技巧(上) 原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 前一篇我介绍了awk,这是一个全能的文本处理神器,因为它本身就是一门编程语言了 ...
- Arthas 超简单离线使用教程
简介 Arthas 是阿里巴巴开源的一套监控java应用的工具 官网的文档地址: https://arthas.aliyun.com/doc/ 官方的简介说明: Arthas 是Alibaba开源的J ...