目录
Django中的会话
会话架构
构建查询
第一眼
从Base64解码
编码为文本
提取JSON
JSON验证
JSON转换
字符串清理
最终查询
使用物化视图进行快速查询
概括
Django中的会话会话是任何基于HTTP的Web框架的重要组成部分。它们允许Web服务器跟踪重复HTTP客户端的身份,而无需它们对每个请求重新进行身份验证。有几种不同的方法可以跟踪会话。有些不需要服务器保留会话数据(如JSON Web Tokens),而有些则需要。
Django是一种流行的基于Python的Web框架,附带一个默认会话后端,用于存储持久会话数据。有多种存储和缓存选项;您可以选择简单地将会话存储在SQL数据库中并每次查找它们,将它们存储在像Redis或Memcached这样的缓存中,或者使用两者,并在数据库存储之前设置缓存引擎。如果您使用最终将会话存储在 SQL 中的选项之一,则该django_session表将包含您用户的会话。
会话架构在细读应用程序的数据时,您可能会遇到一个问题,需要您将用户的会话数据链接到他们的实际用户条目(auth_user表)。这最近发生在我身上,当我查看会话表的模式定义时,我很惊讶它没有存储为列user_id。关于为什么会这样,有一些重要的设计决策,但这对像我这样的SQL用户来说很不方便。
session_key是提供客户的关键。通常,发出请求的客户端会将session_key包含在cookie中。当Web服务器收到请求时,它会查找session_key,如果存在,则查询以查看密钥是否已知。如果是,它将查看相关联session_data并检索有关用户及其会话的元数据。
这就是您能够访问Django请求中的request.user内容的方式。user_id从解码的session_data中获取,根据存储的user_id填充内置的User对象,然后该User对象可在整个项目的视图中使用。
一些快速的谷歌搜索向我展示了默认情况下会话数据存储为JSON。我已经意识到Postgres出色的JSON能力,所以我怀疑这是我们可以在Postgres范围内使用的东西。对于像我这样在Postgres上花费大量时间的人来说,这是个好消息。
构建查询 第一眼正如您在第一张图片中看到的,session_data它似乎不是JSON。存储为JSON的元数据隐藏在base64 编码后面。幸运的是,我们可以在Postgres中轻松解码base64。
从Base64解码这几乎没有可读性。我们需要将二进制数据转换为文本。
编码为文本Postgres中的“encode”函数允许您“将二进制数据编码为文本表示”。
现在,我们终于可以看到人类可读的东西了。以下是文本格式的完整解码结果之一:
11fcbb0d460fd406e83b60ae082991818a1321a4:{"_auth_user_hash":
"39308b9542b9305fc038d28a51088905e14246a1","_auth_user_backend":"x.alternate_auth.Backend",
"_auth_user_id":"52135"}
我们这里有一个以冒号和各种散列开头的JSON blob。我们只对JSON blob感兴趣。仅提取经过哈希和冒号的文本的一种快速方法是找到第一个冒号的位置并提取其后的所有字符。
为了实现这一点,我们可以同时使用RIGHT函数(返回string末尾的n个字符)和POSITION函数(返回字符串中字符的位置)。POSITION将只返回您正在搜索的第一个string实例的位置。
该RIGHT函数接受一个负索引。负索引从右侧提取字符,string排除了负索引指示的字符。
为了进一步构建此查询,我们将使用CTE将其分为2个部分。当您构建并选择了一个重要的列并且需要多次使用它时,CTE会很有帮助。如果我们只继续一个SELECT,我们将不得不多次键入该encode(decode(session_data, 'base64'), 'escape')部分。这很麻烦,如果您决定更改解析编码数据的方式,则必须在2个地方更改函数调用。
这是我们更新的查询,它提取了JSON部分。
完整结果示例:
{"_auth_user_hash":"396db3c0f4ba3d35b350a",
"_auth_user_backend":"x.alternate_auth.Backend","_auth_user_id":"52646"}
现在该列可以解析为JSON,我们可以继续。但是,如果您在Postgres中尝试将文本转换为JSON而文本不是有效的JSON,则Postgres将抛出错误并停止您的查询。在我的数据库中,某些会话无法解析为JSON。这是一种快速确保文本看起来像可解析的JSON的方法。
where
substring(decoded, position(':' in decoded) + 1, 1) = '{'
and right(decoded, 1) = '}'`
任何string不以花括号开头和结尾的都将被过滤掉。
这并不能保证它能够解析,但对于我的数百万个会话的数据库,它完成了这项工作。您可以编写自定义函数来验证JSON可解析性,但速度会较慢。
JSON转换使用WHERE排除无效会话元数据的子句,是时候将我们的string类型转换为Postgres的JSON类型并从JSON中提取_auth_user_id密钥。根据您的Django配置,此键可能不同。将对象转换为JSON类型后,您可以使用object->'key'语法通过键查询JSON值。
我们越来越近了!当从JSON转换为text时,Postgres在它周围添加双引号。最终,我们希望该user_id字段是int,但Postgres不会将包含双引号的string解析为int。甚至JavaScript也不允许!
TRIM与BOTH函数将从string的两端剥离指定的字符,留给我们用干净的string,其可以很容易被铸造成一个整数。
最终查询这是修剪多余的双引号并转换为int.
现在,如示例结果所示,我们已链接session_key到Django auth_userid。
以下是可复制形式的完整查询:
with step1 as (
select
session_key,
encode(decode(session_data, 'base64'), 'escape') :: text as decoded
from
django_session
)
select
session_key,
trim(
both '"'
from
(
right(
decoded,
0 - position(':' in decoded)
) :: json -> '_auth_user_id'
) :: text
) :: int as user_id
from
step1
where
substring(decoded, position(':' in decoded) + 1, 1) = '{'
and right(decoded, 1) = '}'
如果您的数据库有很多用户,您会注意到此查询非常慢。创建物化视图将允许您从持久视图重复查询结果,而无需重新运行SQL。
当您创建物化视图时(以及随时刷新它),视图的源代码将运行,并将用结果中的行填充它。当您需要最新数据时,请务必刷新视图!
create materialized view mv_django_session_user as
with step1 as (
…
刷新:
refresh materialized view mv_django_session_user;
Postgres中的编码和字符串操作比Python、Ruby或PHP等Web应用程序使用的常用语言要繁琐一些,但是完全在Postgres中构建一个视图以快速提取所需的确切数据是非常令人满意的并允许您直接加入其他表。
下次你需要提取由web框架或其他第三方编码的数据时,请检查Postgres的答案!
https://www.codeproject.com/Articles/5298141/Decoding-Django-Sessions-in-PostgreSQL