【PostgreSQL内核学习(二十七) —— (编码转换)】

编码转换

  • 概述
  • 处理客户端与服务器之间的字符串编码转换
    • pg_do_encoding_conversion 函数
    • FindDefaultConversionProc 函数
    • FindDefaultConversion 函数
  • 处理服务器与客户端之间的字符串编码转换
    • 两者的联系和区别

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-10.1 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书以及一些相关资料。

概述

  PostgreSQL 处理客户端和服务器端字符集匹配问题的机制是复杂且灵活的,能够确保数据在不同编码之间传输时保持其原始的意义和结构,从而避免了乱码问题。这一机制主要涉及字符集的识别转换和验证过程,以确保从客户端发送到服务器的数据能够被正确解释和存储,即使客户端和服务器使用的是不同的字符编码(例如,客户端使用 GBK,而服务器使用 UTF-8)。以下是这个过程的详细描述:

  1. 编码设置和识别
     • 客户端编码设置: 客户端可以在与服务器建立连接时指定其使用的字符编码。这允许客户端告诉服务器它将以何种编码发送数据。例如,一个使用 GBK 编码的客户端可以在连接时告知服务器其使用 GBK 编码
     • 服务器编码设置: 服务器端的编码通常在数据库创建时指定,并且在数据库级别统一。服务器端编码定义了数据库存储数据时使用的默认字符编码,如 UTF-8
  2. 字符编码转换
     当客户端发送数据给服务器时,PostgreSQL 会根据客户端和服务器的编码设置自动进行字符编码转换
     • 转换机制: 如果客户端和服务器使用不同的编码,PostgreSQL 会利用内建的字符编码转换功能,将数据从客户端的编码转换为服务器的编码。例如,当客户端使用 GBK,而服务器使用 UTF-8 时,PostgreSQL 会将从客户端接收的 GBK 编码的数据转换为 UTF-8 编码,然后再进行处理和存储。
     • 验证和确保数据有效性: 在转换过程中,PostgreSQL 还会验证数据的有效性,确保转换后的数据在目标编码中是合法的。这一步骤是防止数据损坏的关键。
  3. 透明处理
     对于客户端而言,这个过程是透明的,客户端不需要进行额外的配置或编码转换操作。客户端只需在建立连接时声明其使用的编码,PostgreSQL 会自动处理编码转换和数据验证
  4. 特殊情况处理
     • SQL_ASCII: 在一些特殊情况下,如果服务器端编码设置为 SQL_ASCIIPostgreSQL 不会尝试进行字符编码转换,因为 SQL_ASCII 基本上允许任何类型的字节序列。在这种情况下,数据的有效性正确性更多地依赖于客户端确保其发送的数据能够被服务器端正确解释。
     • 不支持的编码转换: 如果 PostgreSQL 内建的字符编码转换功能不支持客户端和服务器端之间的特定编码转换,可能会导致错误或者要求客户端采取特定措施来避免编码问题。

  PostgreSQL 通过内建的编码识别和转换机制,能够灵活地处理客户端和服务器之间不同字符编码的匹配问题。这一机制确保了数据在传输过程中的准确性有效性,让客户端和服务器即使在使用不同的编码时也能无缝交互,极大地减少了乱码问题的发生。

处理客户端与服务器之间的字符串编码转换

  以下代码定义了两个函数 pg_client_to_serverpg_any_to_server,用于处理字符串的编码转换,确保数据在客户端和服务器之间传输时保持正确的编码格式。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c

/** 将客户端编码的字符串转换为服务器编码。** 请参阅本文件顶部有关字符串转换函数的注释。*/
char *
pg_client_to_server(const char *s, int len)
{// 调用pg_any_to_server函数,将字符串从客户端编码转换为服务器编码return pg_any_to_server(s, len, ClientEncoding->encoding);
}/** 将任意编码的字符串转换为服务器编码。** 请参阅本文件顶部有关字符串转换函数的注释。** 与其他字符串转换函数不同,即使encoding == DatabaseEncoding->encoding,* 也会应用验证。这是因为它用于处理来自数据库外部的数据,* 我们永远不想仅仅假设数据是有效的。*/
char *
pg_any_to_server(const char *s, int len, int encoding)
{if (len <= 0)return (char *) s;		/* 如果字符串长度小于等于0,直接返回,空字符串总是有效的 */if (encoding == DatabaseEncoding->encoding ||encoding == PG_SQL_ASCII){/** 如果不需要转换,但我们仍需验证数据。*/(void) pg_verify_mbstr(DatabaseEncoding->encoding, s, len, false);return (char *) s;}if (DatabaseEncoding->encoding == PG_SQL_ASCII){/** 如果不可能转换,但我们仍需验证数据,* 因为客户端代码可能已经使用选定的client_encoding进行了字符串转义。* 如果客户端编码对ASCII是安全的,则我们只需按该编码直接验证。* 对于一个ASCII不安全的编码,我们有一个问题:我们不敢将这样的数据传递给解析器,* 但我们没有办法转换它。我们通过拒绝包含任何非ASCII字符的数据来妥协。*/if (PG_VALID_BE_ENCODING(encoding))(void) pg_verify_mbstr(encoding, s, len, false);else{int			i;for (i = 0; i < len; i++){if (s[i] == '\0' || IS_HIGHBIT_SET(s[i]))ereport(ERROR,(errcode(ERRCODE_CHARACTER_NOT_IN_REPERTOIRE),errmsg("invalid byte value for encoding \"%s\": 0x%02x",pg_enc2name_tbl[PG_SQL_ASCII].name,(unsigned char) s[i])));}}return (char *) s;}/* 如果我们能使用缓存的转换函数,这是一个快速路径 */if (encoding == ClientEncoding->encoding)return perform_default_encoding_conversion(s, len, true);/* 通用情况...在事务外部不会工作 */return (char *) pg_do_encoding_conversion((unsigned char *) s,len,encoding,DatabaseEncoding->encoding);
}
  • pg_client_to_server 函数是一个简单的封装,它调用 pg_any_to_server 函数,将客户端编码的字符串转换为服务器编码。
  • pg_any_to_server 函数更加复杂,它首先检查是否需要进行编码转换。如果输入字符串的长度小于等于 0,或者字符串的编码已经是目标编码,它将直接返回输入字符串,因为在这些情况下不需要转换。否则,它会根据需要进行适当的编码转换,同时确保转换过程中验证字符串的有效性,以防止无效的数据进入数据库。

pg_do_encoding_conversion 函数

  pg_do_encoding_conversion 函数是一个用于执行字符串编码转换的通用函数它处理从源编码到目标编码的转换,同时确保转换过程中字符串的有效性和正确性。这个函数首先检查是否需要进行转换,如果源编码和目标编码相同,或者目标编码是 SQL_ASCII,它将直接返回源字符串。对于 SQL_ASCII 源编码的字符串,会进行验证而不是转换。如果转换是必需的,该函数会查找适当的转换函数,进行转换,并返回转换后的字符串。这个过程发生在事务状态中,确保了数据的一致性和安全性。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c

/** 将源字符串转换为另一种编码(通用情况)。** 请参阅本文件顶部有关字符串转换函数的注释。*/
unsigned char *
pg_do_encoding_conversion(unsigned char *src, int len,int src_encoding, int dest_encoding)
{unsigned char *result;Oid			proc;if (len <= 0)return src;				/* 如果字符串为空或长度小于等于0,直接返回源字符串,空字符串总是有效的 */if (src_encoding == dest_encoding)return src;				/* 如果源编码和目标编码相同,不需要转换,直接返回源字符串 */if (dest_encoding == PG_SQL_ASCII)return src;				/* 如果目标编码是SQL_ASCII,任何字符串都是有效的,直接返回源字符串 */if (src_encoding == PG_SQL_ASCII){/* 如果源编码是SQL_ASCII,不能转换,但我们必须验证结果 */(void) pg_verify_mbstr(dest_encoding, (const char *) src, len, false);return src;}if (!IsTransactionState())	/* 这种情况不应该发生 */elog(ERROR, "cannot perform encoding conversion outside a transaction");proc = FindDefaultConversionProc(src_encoding, dest_encoding);if (!OidIsValid(proc))ereport(ERROR,(errcode(ERRCODE_UNDEFINED_FUNCTION),errmsg("default conversion function for encoding \"%s\" to \"%s\" does not exist",pg_encoding_to_char(src_encoding),pg_encoding_to_char(dest_encoding))));/** 为转换结果分配空间,注意整数溢出*/if ((Size) len >= (MaxAllocSize / (Size) MAX_CONVERSION_GROWTH))ereport(ERROR,(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),errmsg("out of memory"),errdetail("String of %d bytes is too long for encoding conversion.",len)));result = palloc(len * MAX_CONVERSION_GROWTH + 1);OidFunctionCall5(proc,Int32GetDatum(src_encoding),Int32GetDatum(dest_encoding),CStringGetDatum(src),CStringGetDatum(result),Int32GetDatum(len));return result;
}

其中需要注意以下几点

  1. 事务状态检查: 转换必须在事务状态下进行,以确保数据的一致性。
  2. 转换函数查找: 通过 FindDefaultConversionProc 查找适合的编码转换函数
  3. 内存管理: 为了防止整数溢出,转换结果分配的空间基于源字符串长度乘以一个最大增长率(MAX_CONVERSION_GROWTH)进行计算,以避免分配过多内存。
  4. 转换执行: 使用找到的转换函数执行实际的编码转换。

FindDefaultConversionProc 函数

  FindDefaultConversionProc 函数的主要作用和功能是在给定的数据库系统中,根据输入的源编码和目标编码来查找一个默认的编码转换过程。它通过遍历数据库的搜索路径(即一系列命名空间),在每个命名空间中尝试查找匹配的编码转换过程。如果在某个命名空间中找到了一个有效的转换过程,函数就会返回这个过程的标识符Oid)。如果在所有的搜索路径中都没有找到有效的转换过程,则返回一个无效的标识符InvalidOid)。这个机制允许数据库动态地处理字符集转换,提高了数据库系统处理不同编码数据的灵活性和兼容性。函数源码如下所示:(路径:src\backend\catalog\namespace.c

/** FindDefaultConversionProc - 查找默认的编码转换过程*/
Oid
FindDefaultConversionProc(int32 for_encoding, int32 to_encoding)
{Oid			proc; // 定义一个Oid变量proc,用于存储找到的转换过程的标识符ListCell   *l; // 定义一个ListCell指针l,用于遍历搜索路径recomputeNamespacePath(); // 重新计算命名空间路径,确保搜索路径是最新的foreach(l, activeSearchPath) // 遍历当前激活的搜索路径{Oid			namespaceId = lfirst_oid(l); // 获取当前命名空间的IDif (namespaceId == myTempNamespace)continue;			/* 如果是临时命名空间,则跳过,不在临时命名空间中查找 */proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding); // 尝试在当前命名空间中找到默认的转换过程if (OidIsValid(proc)) // 如果找到有效的转换过程return proc; // 返回找到的转换过程的标识符}/* 如果在搜索路径中没有找到 */return InvalidOid; // 返回一个无效的Oid
}

FindDefaultConversion 函数

  FindDefaultConversion 函数的作用是在指定的命名空间中,根据给定的源编码和目标编码查找默认的转换过程,并返回该转换过程的 OID。首先通过系统缓存搜索到符合条件的转换过程列表,然后遍历列表中的每个元素,检查是否为默认转换过程,如果是则获取其 OID 并返回,否则返回无效的 OID。函数源码如下所示:(路径:D:\pg相关src\backend\catalog\pg_conversion.c

/** FindDefaultConversion* * 在给定的命名空间中通过给定的源编码和目标编码找到“默认”转换过程。* * 如果找到,则返回该过程的OID,否则返回InvalidOid。请注意,您得到的是过程的OID,而不是转换的OID!*/
Oid
FindDefaultConversion(Oid name_space, int32 for_encoding, int32 to_encoding)
{CatCList   *catlist;  // 定义一个指向系统目录缓存列表的指针HeapTuple   tuple;     // 定义一个堆元组变量Form_pg_conversion body;  // 定义一个指向pg_conversion系统表行数据的指针Oid         proc = InvalidOid;  // 初始化过程OID为InvalidOidint         i;  // 定义循环变量// 通过指定的命名空间、源编码和目标编码搜索系统缓存中的转换过程列表catlist = SearchSysCacheList3(CONDEFAULT,ObjectIdGetDatum(name_space),Int32GetDatum(for_encoding),Int32GetDatum(to_encoding));// 遍历转换过程列表中的每个元素for (i = 0; i < catlist->n_members; i++){tuple = &catlist->members[i]->tuple;  // 获取当前元素的元组body = (Form_pg_conversion) GETSTRUCT(tuple);  // 获取当前元素对应的转换过程数据// 如果当前转换过程为默认转换过程if (body->condefault){proc = body->conproc;  // 获取该转换过程的OIDbreak;  // 跳出循环}}ReleaseSysCacheList(catlist);  // 释放系统缓存列表return proc;  // 返回找到的默认转换过程的OID
}

我们用一个简单的案例来更好地描述以上场景:

  1. 假设我们有一个命名空间 “public”,并且有以下转换过程:
connamename_spacefor_encodingto_encodingcondefaultconproc
conv1220068false1001
conv2220086true1002
conv3220069true1003
  1. 现在,我们调用 FindDefaultConversion 函数,寻找默认转换过程,示例可能如下:
Oid defaultConversionOid = FindDefaultConversion(2200, 8, 6);

  在这个例子中,我们希望找到命名空间2200源编码为 8目标编码为 6 的默认转换过程的 OID

  1. 函数会执行以下步骤:
  • 从系统缓存中检索所有符合条件的转换过程。
  • 遍历转换过程列表,查找符合条件的默认转换过程。
  • 如果找到了符合条件的默认转换过程,则返回该转换过程的 OID;如果没有找到,则返回 InvalidOid。

  在这个例子中,由于我们指定的源编码为 8目标编码为 6,并且要求的是默认转换过程,所以函数会返回转换过程 “conv2” 的 OID,即 1002

处理服务器与客户端之间的字符串编码转换

  以下代码实现了在 PostgreSQL 数据库中服务器编码转换为客户端编码或任意其他编码的功能pg_server_to_client 函数将调用 pg_server_to_any 函数,并将客户端编码作为目标编码传递pg_server_to_any 函数根据提供的目标编码执行不同的操作。函数源码如下所示:(路径:src\backend\utils\mb\mbutils.c

/** 将服务器编码转换为客户端编码。** 请参阅本文件顶部关于字符串转换函数的注释。*/
char *
pg_server_to_client(const char *s, int len)
{// 调用 pg_server_to_any 函数,将服务器编码转换为客户端编码return pg_server_to_any(s, len, ClientEncoding->encoding);
}/** 将服务器编码转换为任意编码。** 请参阅本文件顶部关于字符串转换函数的注释。*/
char *
pg_server_to_any(const char *s, int len, int encoding)
{// 如果字符串长度小于等于 0,则返回原始字符串,空字符串始终有效if (len <= 0)return (char *) s; // 返回原始字符串// 如果目标编码与数据库编码相同,或者目标编码为 PG_SQL_ASCII,则返回原始字符串,假定数据有效if (encoding == DatabaseEncoding->encoding ||encoding == PG_SQL_ASCII)return (char *) s; // 返回原始字符串// 如果数据库编码为 PG_SQL_ASCII,则无法进行转换,但是需要验证结果是否有效if (DatabaseEncoding->encoding == PG_SQL_ASCII){// 调用 pg_verify_mbstr 函数验证多字节字符串是否有效,并返回原始字符串(void) pg_verify_mbstr(encoding, s, len, false);return (char *) s; // 返回原始字符串}// 如果目标编码与客户端编码相同,可以使用缓存的转换函数进行快速转换if (encoding == ClientEncoding->encoding)// 调用 perform_default_encoding_conversion 函数,使用默认转换函数进行快速转换并返回结果return perform_default_encoding_conversion(s, len, false);// 通用情况...在事务外部不起作用// 调用 pg_do_encoding_conversion 函数,将字符串从数据库编码转换为目标编码并返回结果return (char *) pg_do_encoding_conversion((unsigned char *) s,len,DatabaseEncoding->encoding,encoding);
}

  在 PostgreSQL 中,pg_server_to_anypg_any_to_server 是两个用于处理编码转换的内部函数。这些函数主要用于在服务器编码和任何其他指定编码之间转换文本数据。虽然在标准的 PostgreSQL 安装和文档中,这些函数通常不会直接暴露给最终用户,了解它们的作用对于理解 PostgreSQL 如何处理不同编码的数据仍然很重要。

两者的联系和区别

  1. 联系: pg_server_to_anypg_any_to_server 都是处理编码转换的函数,它们之间的主要联系在于它们的工作目的是相反的。一个是从服务器编码转换为其他编码,另一个则是从其他编码转换为服务器编码。这两个函数一起确保了数据库能够接受、存储和返回不同编码的文本数据,从而支持多语言和国际化应用。
  2. 区别: 主要区别在于它们转换方向的不同pg_server_to_any 主要用于输出场景,即将数据发送给客户端时使用;而 pg_any_to_server 主要用于输入场景,即接收来自客户端的数据时使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/699771.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于Linux中top的使用

关于Linux中top的使用 1 简介1 系统统计信息2 进程信息 2 常用参数 在项目开发中, 经常会有服务器负载突然飙升, 服务不可用等情况. 需要去关注项目的运行情况. 而top命令是Linux里面常用的性能分析工具, 能够实时显示系统中各个进程的资源占用状况. 1 简介 登录服务器, 输入命…

单片机04__基本定时器__毫秒微秒延时

基本定时器__毫秒微秒延时 基本定时器介绍&#xff08;STM32F40x&#xff09; STM32F40X芯片一共包含14个定时器&#xff0c;这14个定时器分为3大类&#xff1a; 通用定时器 10个 TIM9-TIM1和TIM2-TIM5 具有基本定时器功能&#xff0c; 还具有输入捕获&#xff0c;输出比较功…

char 、unsigned char、const char区别

1. const char的意思是该字符是 常量类型的。 比如const char ch a; 是告诉编译器&#xff0c;这个变量不会改变。 当你给这个变量重新赋值的时候&#xff0c;会发出warning&#xff0c;提示用户。 const char *和 char const * 以及char * const Bjarne在他的The C Programm…

Linux TCP 参数设置

文章目录 Linux TCP 参数设置参考 Linux TCP 参数设置 查询tcp相关内核参数 sysctl -a|grep ipv4|grep -i --color tcp[rootlocalhost ~]# sysctl -a|grep ipv4|grep -i --color tcp sysctl: reading key "net.ipv6.conf.all.stable_secret" sysctl: reading key &…

Django——ORM增删改查

基本对象 model.objects 创建数据 可以通过django编写的命令行方式快捷创建数据 python manage.py shell 如果对模型层有任何修改都需要重启shell&#xff0c;否则操作容易出错 在shell中我们需要先引入我们的模型&#xff0c;如from bookstore.models import Book 然后通过…

【接口加密】Java中的接口加密实践

目录 2.1 Java加密相关的基础知识 2.1.1 Java加密框架概述 2.1.2 Java加密常用算法介绍 2.2 Java中的接口加密实现 2.2.1 使用Java加密标准&#xff08;JCE&#xff09;实现接口加密 2.2.2 通过Bouncy Castle库实现接口加密 2.2.3 第三方加密工具的集成与应用 2.3 Java…

Uniapp-小程序简单的时间选择组件-年月日时分

文章目录 前言一、组件效果二、组件代码使用 总结 前言 uniapp小程序开发系列。本文实现一个简单时间选择控件。uniapp用个心仪时间控件真的麻烦。官方给得要么年月日&#xff0c;要么时分。产品只要年月日时分。这该怎么玩。旧项目没有引入ui框架&#xff0c;我也不想去引入&…

如何连接ACL认证的Redis

点击上方蓝字关注我 应用程序连接开启了ACL认证的Redis时与原先的方式有差别&#xff0c;本文介绍几种连接开启ACL认证的Redis的Redis的方法。 对于RedisACL认证相关内容&#xff0c;可以参考历史文章&#xff1a; Redis权限管理体系(一&#xff09;&#xff1a;客户端名及用户…

python 中 ftplib库的使用说明

在Python3中&#xff0c;ftplib库是用于处理FTP协议的内置模块。以下是一些使用ftplib库的基本操作示例和说明&#xff1a; 连接与登录FTP服务器 from ftplib import FTP# 创建一个FTP对象 ftp FTP()# 连接到FTP服务器 ftp.connect(ftp.example.com, port21) # 默认端口为2…

【二十七】【C++】二叉树练习

606. 根据二叉树创建字符串 给你二叉树的根节点 root &#xff0c;请你采用前序遍历的方式&#xff0c;将二叉树转化为一个由括号和整数组成的字符串&#xff0c;返回构造出的字符串。 空节点使用一对空括号对 "()" 表示&#xff0c;转化后需要省略所有不影响字符串与…

【无刷电机学习】各种电机优势比较

目录 0 参考出处 1 有刷与无刷比较 2 交流与直流比较 3 内转子与外转子比较 4 Delta型与Y型定子绕向比较 5 低压BLDC的一些优点 0 参考出处 【仅作自学记录&#xff0c;不出于任何商业目的。如有侵权&#xff0c;请联系删除&#xff0c;谢谢&#xff01;】 维基百科…

C++力扣题目139--单词拆分 198--打家劫舍 213--打家劫舍II 337打家劫舍III

139.单词拆分 力扣题目链接(opens new window) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 …

Java代理模式:实现灵活的控制访问

Java代理模式&#xff1a;实现灵活的控制访问 代理模式是常用的设计模式之一&#xff0c;它提供了一种方式来控制对某个对象的访问&#xff0c;通过引入一个代理对象来间接访问目标对象。在Java中&#xff0c;代理模式广泛应用于远程方法调用、AOP&#xff08;面向切面编程&am…

Unity中URP实现水体效果(水的深度)

文章目录 前言一、搭建预备场景1、新建一个面片&#xff0c;使其倾斜一个角度&#xff0c;来模拟水底和岸边的效果2、随便创建几个物体&#xff0c;作为与水面接触的物体3、再新建一个面片&#xff0c;作为水面 二、开始编写水体的Shader效果1、新建一个URP基础Shader2、把水体…

knife4j springboot3使用

简介 在日常开发中&#xff0c;写接口文档是我们必不可少的&#xff0c;而Knife4j就是一个接口文档工具&#xff0c;可以看作是Swagger的升级版&#xff0c;但是界面比Swagger更好看&#xff0c;功能更丰富 使用 我使用的是springboot3.2.3 knife4j 4.3.0,knife4j 4.4版本有…

自动化操作读写Excel —— xlrd 和 xlwt 模块参数说明与代码实战【第95篇—自动化操作读写Excel 】

自动化操作读写Excel —— xlrd 和 xlwt 模块参数说明与代码实战 在日常工作中&#xff0c;Excel表格是不可或缺的数据处理工具。为了提高工作效率&#xff0c;Python中的xlrd和xlwt模块为我们提供了强大的功能&#xff0c;使得自动化操作Excel变得更加简便。本文将介绍xlrd和…

「优选算法刷题」:两数之和

一、题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任…

【心得】如何做一个靠谱的程序员

你是否曾经遇到过一些不应该出现的小 BUG &#xff0c;比如路径错了一个字母&#xff0c;进而找不到文件 File no found&#xff0c; 写的小功能 直接用错了变量&#xff0c;进而出现一些莫名其妙的问题。这些问题容易解决&#xff0c;但也浪费了我们不少时间。 那么&#xff…

利用nginx内部访问特性实现静态资源授权访问

在nginx中&#xff0c;将静态资源设为internal&#xff1b;然后将前端的静态资源地址改为指向后端&#xff0c;在后端的响应头部中写上静态资源地址。 近期客户对我们项目做安全性测评&#xff0c;暴露出一些安全性问题&#xff0c;其中一个是有些静态页面&#xff08;*.html&…

线性代数:向量、张量、矩阵和标量

线性代数&#xff1a;向量、张量、矩阵和标量 背景 在线性代数中&#xff0c;向量、张量、矩阵和标量都属于基础概念&#xff0c;特别是最近AI的爆火&#xff0c;向量和张量的概念也越来越普及&#xff0c;本文将介绍下这些基本概念。 1. 标量&#xff08;Scalar&#xff0…