IoTDB 权限管理完全指南:从概念到实战操作
IoTDB 自带的权限管理功能特别实用,能帮我们好好管控数据和集群系统的访问权限,让数据安全和系统稳定更有保障。这篇文章就来详细聊聊 IoTDB 权限模块的那些事儿,不管是基本概念、用户角色定义,还是权限管理、鉴权逻辑,再到具体的功能用例,都会一一讲清楚。另外,在 JAVA 编程环境里,大家还能通过 JDBC API 单条或者批量执行权限管理相关语句,操作起来也挺方便。


IoTDB 权限管理完全指南:从概念到实战操作
IoTDB 自带的权限管理功能特别实用,能帮我们好好管控数据和集群系统的访问权限,让数据安全和系统稳定更有保障。这篇文章就来详细聊聊 IoTDB 权限模块的那些事儿,不管是基本概念、用户角色定义,还是权限管理、鉴权逻辑,再到具体的功能用例,都会一一讲清楚。另外,在 JAVA 编程环境里,大家还能通过 JDBC API 单条或者批量执行权限管理相关语句,操作起来也挺方便。

一、基本概念
1.1 用户
用户说白了就是数据库的合法使用者。每个用户都对应着唯一的用户名,而且得有密码来做身份验证。咱们要想用数据库,第一步就得提供数据库里已有的合法用户名和密码,这样才能成功登录。
1.2 权限
数据库能支持多种操作,但不是所有用户都能随便执行所有操作。要是一个用户能做某项操作,那就说明他有这项操作的权限。权限一般得有个路径来限定生效范围,用路径模式来管理权限,灵活度还挺高。
1.3 角色
角色其实就是一堆权限的集合,还有个唯一的角色名当标识。它通常和现实中的某个身份对应,比如交通调度员这种。同一个现实身份可能会对应多个用户,这些用户需要的权限往往是一样的,角色就是为了方便统一管理这类权限而设计的抽象概念。
1.4 默认用户与角色
IoTDB 刚安装初始化完,会有个默认用户 root,默认密码也是 root。这个用户是管理员,所有权限都直接拉满,而且这些权限没法被赋予也没法被撤销,更不能删除,整个数据库里就这一个管理员用户。
还有个点要注意,新创建的用户或者角色,一开始是没有任何权限的,得后续手动配置。
二、用户定义
只有拥有 MANAGE_USER、MANAGE_ROLE 权限的用户,或者管理员,才能创建用户和角色,而且创建的时候得满足下面这些约束:
2.1 用户名限制
用户名长度得在 4~32 个字符之间,能用上英文大小写字母、数字,还有 !@#$%^&*()_±= 这些特殊字符。另外,可不能创建和管理员用户同名的用户名哦。
2.2 密码限制
密码长度同样是 4~32 个字符,支持英文大小写字母、数字和 !@#$%^&*()_+-= 特殊字符。默认情况下,密码会用 SHA-256 加密,安全性还是有保障的。
2.3 角色名限制
角色名的要求和用户名差不多,长度 4~32 个字符,支持英文大小写字母、数字以及 !@#$%^&*()_+-= 特殊字符,同样不能和管理员用户同名。
三、权限管理
IoTDB 里的权限主要分两类:序列权限和全局权限,咱们分别来看看。
3.1 序列权限
序列权限管的是用户访问数据的范围和方式,既支持对绝对路径授权,也支持前缀匹配路径授权,而且能在 timeseries 粒度上生效。具体的权限种类和范围如下:
| 权限名称 | 描述 |
|---|---|
| READ_DATA | 允许读取授权路径下的序列数据。 |
| WRITE_DATA | 允许读取授权路径下的序列数据;允许插入、删除授权路径下的序列数据;允许在授权路径下导入、加载数据(导入时需有对应路径的 WRITE_DATA 权限,自动创建数据库与序列时,需有 MANAGE_DATABASE 与 WRITE_SCHEMA 权限)。 |
| READ_SCHEMA | 允许获取授权路径下元数据树的详细信息,包括路径下的数据库、子路径、子节点、设备、序列、模版、视图等。 |
| WRITE_SCHEMA | 允许获取授权路径下元数据树的详细信息;允许在授权路径下对序列、模版、视图等进行创建、删除、修改操作(创建或修改 view 时,会检查 view 路径的 WRITE_SCHEMA 权限、数据源的 READ_SCHEMA 权限;对 view 进行查询、插入时,会检查 view 路径的 READ_DATA 权限、WRITE_DATA 权限);允许在授权路径下设置、取消、查看 TTL;允许在授权路径下挂载或者解除挂载模板。 |
3.2 全局权限
全局权限约束的是用户对数据库功能的使用,还限制用户执行那些会改变系统状态和任务状态的命令。用户拿到全局授权后,才能对数据库进行管理操作。具体的系统权限种类如下:
| 权限名称 | 描述 |
|---|---|
| MANAGE_DATABASE | 允许用户创建、删除数据库。 |
| MANAGE_USER | 允许用户创建、删除、修改、查看用户。 |
| MANAGE_ROLE | 允许用户创建、删除、查看角色;允许用户将角色授予给其他用户,或取消其他用户的角色。 |
| USE_TRIGGER | 允许用户创建、删除、查看触发器,与触发器的数据源权限检查相独立。 |
| USE_UDF | 允许用户创建、删除、查看用户自定义函数,与自定义函数的数据源权限检查相独立。 |
| USE_CQ | 允许用户创建、开始、停止、删除、查看管道;允许用户创建、删除、查看管道插件,与管道的数据源权限检查相独立。 |
| USE_PIPE | 允许用户注册、开始、停止、卸载、查询流处理任务;允许用户注册、卸载、查询注册流处理任务插件。 |
| EXTEND_TEMPLATE | 允许自动扩展模板。 |
| MAINTAIN | 允许用户查询、取消查询;允许用户查看变量;允许用户查看集群状态。 |
| USE_MODEL | 允许用户创建、删除、查询深度学习模型。 |
关于模板权限的补充说明:
- 模板的创建、删除、修改、查询、挂载、卸载,只有管理员能操作;
- 激活模板需要拥有激活路径的 WRITE_SCHEMA 权限;
- 若开启了自动创建,向挂载了模板的不存在路径写入时,数据库会自动扩展模板并写入数据,这时候需要有 EXTEND_TEMPLATE 权限和写入序列的 WRITE_DATA 权限;
- 解除模板,需要拥有挂载模板路径的 WRITE_SCHEMA 权限;
- 查询使用了某个元数据模板的路径,需要有路径的 READ_SCHEMA 权限,不然查询结果会为空。
3.3 权限授予与取消
权限授予的三种途径
在 IoTDB 里,用户想拿到权限,有三种方式:
- 由超级管理员授予,超级管理员能全权控制其他用户的权限;
- 由有授权权限的用户授予,不过这个用户拿到权限的时候得带有 grant option 关键字;
- 由超级管理员或者有 MANAGE_ROLE 权限的用户,把某个角色授予给用户,用户从而获得角色对应的权限。
权限取消的三种途径
想取消用户的权限,也有三种办法:
- 由超级管理员直接取消;
- 由有授权权限的用户取消,同样这个用户的权限得带有 grant option 关键字;
- 由超级管理员或者有 MANAGE_ROLE 权限的用户,取消用户的某个角色,从而取消对应的权限。
授权与取消授权的关键注意点
- 授权的时候,必须指定路径。全局权限的路径得是 root.**,而序列相关权限的路径,要么是绝对路径,要么是以双通配符结尾的前缀路径;
- 给角色授权时,如果加上了 with grant option 关键字,那就意味着拥有这个角色的用户,能把授权路径上的权限转授给别人,也能取消其他用户在这个授权路径上的权限。比如用户 A 拿到了集团1.公司1.** 的读权限,而且带有 grant option 关键字,那 A 就能把集团1.公司1 以下任意节点、序列的读权限转授给其他人,也能取消其他用户在这部分路径下的读权限;
- 取消授权的时候,取消语句会和用户的所有权限路径进行匹配,只要匹配上的,都会被清理掉。比如用户 A 有集团1.公司1.工厂1 的读权限,要是执行了取消集团1.公司1.** 读权限的操作,那用户 A 这个工厂1 的读权限也会被清除。
四、鉴权
用户的权限主要由三部分构成:权限生效范围(也就是路径)、权限类型,还有 with grant option 标记。举个例子:
userTest1 :
root.t1.** - read_schema, read_data - with grant option
root.** - write_schema, write_data - with grant option
每个用户都有这么一个权限访问列表,里面记录了所有已获得的权限,用 LIST PRIVILEGES OF USER 这个语句就能查看。
鉴权规则
- 对单个路径鉴权时,数据库会拿这个路径和权限访问列表里的路径逐一匹配。比如要检查 root.t1.t2 的 read_schema 权限,先看和 root.t1.** 匹不匹配,匹配上了就检查这个权限里有没有 read_schema,没匹配上就继续往下找,直到匹配成功或者所有路径都检查完;
- 多路径鉴权的时候,情况有点不一样:多路径查询任务,数据库只会返回用户有权限的数据,没权限的数据不会出现在结果里;而多路径写入任务,必须所有目标序列都有对应的权限,才能成功写入,少一个都不行。
需检查多重权限的特殊操作
- 要是开启了自动创建序列功能,往不存在的序列里插入数据时,不仅需要这个序列的写入权限,还得有元数据修改权限;
- 执行 select into 语句时,得同时有源序列的读权限和目标序列的写权限。这里要注意,源序列可能因为权限不足,只能获取部分数据,而目标序列要是没写权限,整个任务会直接报错终止;
- View 权限和数据源的权限是分开的,往 View 里读写数据,只检查 View 本身的权限,不会再去校验源路径的权限。
五、功能语法与示例
IoTDB 还提供了组合权限,方便大家快速授权,组合权限其实就是把常用的权限打包好了,和直接写单个权限的效果是一样的,具体如下:
| 权限名称 | 权限范围 |
|---|---|
| ALL | 所有权限 |
| READ | READ_SCHEMA、READ_DATA |
| WRITE | WRITE_SCHEMA、WRITE_DATA |
下面这些语句,非管理员执行的话,得先拿到对应的权限,需要的权限会标在操作描述后面。
5.1 用户与角色相关
- 创建用户(需 MANAGE_USER 权限):CREATE USER ,示例:CREATE USER user1 ‘passwd’
- 删除用户(需 MANAGE_USER 权限):DROP USER ,示例:DROP USER user1
- 创建角色(需 MANAGE_ROLE 权限):CREATE ROLE ,示例:CREATE ROLE role1
- 删除角色(需 MANAGE_ROLE 权限):DROP ROLE ,示例:DROP ROLE role1
- 赋予用户角色(需 MANAGE_ROLE 权限):GRANT ROLE TO ,示例:GRANT ROLE admin TO user1
- 移除用户角色(需 MANAGE_ROLE 权限):REVOKE ROLE FROM ,示例:REVOKE ROLE admin FROM user1
- 列出所有用户(需 MANAGE_USER 权限):LIST USER
- 列出所有角色(需 MANAGE_ROLE 权限):LIST ROLE
- 列出指定角色下所有用户(需 MANAGE_USER 权限):LIST USER OF ROLE ,示例:LIST USER OF ROLE roleuser
- 列出指定用户下所有角色:用户能查看自己的角色,查看别人的角色需要 MANAGE_ROLE 权限,语句:LIST ROLE OF USER ,示例:LIST ROLE OF USER tempuser
- 列出用户所有权限:用户能查看自己的权限,查看别人的权限需要 MANAGE_USER 权限,语句:LIST PRIVILEGES OF USER ; 示例:LIST PRIVILEGES OF USER tempuser;
- 列出角色所有权限:用户能查看自己拥有的角色的权限,查看其他角色的权限需要 MANAGE_ROLE 权限,语句:LIST PRIVILEGES OF ROLE ; 示例:LIST PRIVILEGES OF ROLE actor;
- 修改密码:用户能修改自己的密码,修改别人的密码需要 MANAGE_USER 权限,语句:ALTER USER SET PASSWORD ; 示例:ALTER USER tempuser SET PASSWORD ‘newpwd’;
5.2 授权与取消授权
授权语法
GRANT ON TO ROLE/USER [WITH GRANT OPTION];
示例:
- GRANT READ ON root.** TO ROLE role1;
- GRANT READ_DATA, WRITE_DATA ON root.t1.** TO USER user1;
- GRANT READ_DATA, WRITE_DATA ON root.t1.,root.t2. TO USER user1;
- GRANT MANAGE_ROLE ON root.** TO USER user1 WITH GRANT OPTION;
- GRANT ALL ON root.** TO USER user1 WITH GRANT OPTION;
取消授权语法
REVOKE ON FROM ROLE/USER ;
示例:
- REVOKE READ ON root.** FROM ROLE role1;
- REVOKE READ_DATA, WRITE_DATA ON root.t1.** FROM USER user1;
- REVOKE READ_DATA, WRITE_DATA ON root.t1., root.t2. FROM USER user1;
- REVOKE MANAGE_ROLE ON root.** FROM USER user1;
- REVOKE ALL ON ROOT.** FROM USER user1;
授权与取消授权的重要规则
- 非管理员执行授权或取消授权语句时,得对指定的 拥有对应的 权限,而且这个权限还得带有 WITH GRANT OPTION;
- 授予或取消全局权限时(包括 ALL 权限,因为 ALL 展开后会包含全局权限),路径必须指定为 root.**,不然语句是非法的。比如这些是合法的:
- GRANT MANAGE_USER ON root.** TO USER user1;
- GRANT MANAGE_ROLE ON root.** TO ROLE role1 WITH GRANT OPTION;
- REVOKE ALL ON root.** FROM ROLE role1;
而这些是非法的: - GRANT READ, MANAGE_ROLE ON root.t1.** TO USER user1;
- REVOKE ALL ON root.t1.t2 FROM USER user1;
只能是全路径,或者是以双通配符结尾的匹配路径。合法的路径比如 root. 、root.t1.t2.、root.t1.t2.t3;非法的比如 root.t1. 、root.t1.**.t2、root.t1.t2.t3。
六、示例
IoTDB 的样例数据可能属于 ln、sgcc 等不同发电集团,不同集团肯定不希望别人获取自己的数据库数据,所以咱们得在集团层面对数据做权限隔离,具体操作如下:
6.1 创建用户
用 CREATE USER 语句创建用户。比如用 root 用户给 ln 和 sgcc 集团分别创建用户 ln_write_user 和 sgcc_write_user,密码都是 write_pwd,建议用反引号()把用户名包起来,SQL 语句如下: CREATE USER ln_write_user'write_pwd' CREATE USERsgcc_write_user` ‘write_pwd’
执行完之后,用 LIST USER 语句就能看到创建好的用户,结果如下:
IoTDB> CREATE USER ln_write_user ‘write_pwd’
Msg: The statement is executed successfully.
IoTDB> CREATE USER sgcc_write_user ‘write_pwd’
Msg: The statement is executed successfully.
IoTDB> LIST USER;
±--------------+
| user|
±--------------+
| ln_write_user|
| root|
|sgcc_write_user|
±--------------+
Total line number = 3
It costs 0.012s
6.2 赋予用户权限
刚创建好的用户是没有任何权限的,根本没法操作数据库。比如用 ln_write_user 执行写入数据的语句:
INSERT INTO root.ln.wf01.wt01(timestamp,status) values(1509465600000,true)
系统会直接报错,提示没有权限:
IoTDB> INSERT INTO root.ln.wf01.wt01(timestamp,status) values(1509465600000,true)
Msg: 803: No permissions for this operation, please add privilege WRITE_DATA on [root.ln.wf01.wt01.status]
这时候就需要用 root 用户给他们赋予对应的写入权限,用 GRANT ON TO USER 语句:
GRANT WRITE_DATA ON root.ln.** TO USER ln_write_user
GRANT WRITE_DATA ON root.sgcc1., root.sgcc2. TO USER sgcc_write_user
执行成功后,再用 ln_write_user 尝试写入数据,就能成功了:
IoTDB> GRANT WRITE_DATA ON root.ln.** TO USER ln_write_user
Msg: The statement is executed successfully.
IoTDB> GRANT WRITE_DATA ON root.sgcc1., root.sgcc2. TO USER sgcc_write_user
Msg: The statement is executed successfully.
IoTDB> INSERT INTO root.ln.wf01.wt01(timestamp, status) values(1509465600000, true)
Msg: The statement is executed successfully.
6.3 撤销用户权限
要是想收回用户的权限,用 REVOKE ON FROM USER 语句就行。比如用 root 用户撤销刚才给 ln_write_user 和 sgcc_write_user 的权限:
REVOKE WRITE_DATA ON root.ln.** FROM USER ln_write_user
REVOKE WRITE_DATA ON root.sgcc1., root.sgcc2. FROM USER sgcc_write_user
执行成功后,ln_write_user 就没法再往 root.ln.** 路径下写入数据了:
IoTDB> REVOKE WRITE_DATA ON root.ln.** FROM USER ln_write_user
Msg: The statement is executed successfully.
IoTDB> REVOKE WRITE_DATA ON root.sgcc1., root.sgcc2. FROM USER sgcc_write_user
Msg: The statement is executed successfully.
IoTDB> INSERT INTO root.ln.wf01.wt01(timestamp, status) values(1509465600000, true)
Msg: 803: No permissions for this operation, please add privilege WRITE_DATA on [root.ln.wf01.wt01.status]
七、其他说明
角色是权限的集合,而权限和角色都是用户的属性。一个角色可以包含多个权限,一个用户也可以拥有多个角色和自己的专属权限(也就是用户自身权限)。
在 IoTDB 里,权限之间不会有冲突,用户真正拥有的权限,是自己的专属权限和所有角色权限的并集。简单说,只要用户自己的权限或者某个角色的权限里,有允许某项操作的权限,用户就能执行这项操作。就算用户自身和角色有相同的权限,也不会有任何影响。
这里有个很关键的点要记住:如果用户自己有某项权限(比如操作 A),同时某个角色也有这项权限,那只撤销用户自身的这个权限,是没法禁止用户执行操作 A 的,还得要么从那个角色里撤销这项权限,要么把用户从那个角色里移除。反过来也一样,只撤销角色的权限,用户自身还有的话,照样能执行操作 A。
另外,对角色的修改会马上作用到所有拥有这个角色的用户身上。比如给角色加了某项权限,所有有这个角色的用户立马就有了这项权限;要是从角色里删了某项权限,这些用户也会马上失去这项权限(除非用户自己本身就有这项权限)。
八、升级说明
在 IoTDB 1.3 版本之前,权限类型比较多,而这一版本对权限类型做了精简,还对权限路径加了约束。
1.3 版本里,权限路径必须是全路径,或者是以双通配符结尾的匹配路径。系统升级的时候,会自动把不合法的权限路径和权限类型转换成合法的:路径上第一个非法节点会被替换成 **,不支持的权限类型也会映射到当前系统支持的权限上。比如:
| 权限类型 | 权限路径 | 映射之后的权限类型 | 权限路径 |
|---|---|---|---|
| CREATE_DATBASE | root.db.t1.* | MANAGE_DATABASE | root.** |
| INSERT_TIMESERIES | root.db.t2.*.t3 | WRITE_DATA | root.db.t2.** |
| CREATE_TIMESERIES | root.db.t2*c.t3 | WRITE_SCHEMA | root.db.** |
| LIST_ROLE | root.** | (忽略) |
新旧版本的权限类型对照如下(–IGNORE 表示新版本会忽略该权限):
| 权限名称 | 是否路径相关 | 新权限名称 | 是否路径相关 |
|---|---|---|---|
| CREATE_DATABASE | 是 | MANAGE_DATABASE | 否 |
| INSERT_TIMESERIES | 是 | WRITE_DATA | 是 |
| UPDATE_TIMESERIES | 是 | WRITE_DATA | 是 |
| READ_TIMESERIES | 是 | READ_DATA | 是 |
| CREATE_TIMESERIES | 是 | WRITE_SCHEMA | 是 |
| DELETE_TIMESERIES | 是 | WRITE_SCHEMA | 是 |
| CREATE_USER | 否 | MANAGE_USER | 否 |
| DELETE_USER | 否 | MANAGE_USER | 否 |
| MODIFY_PASSWORD | 否 | – IGNORE | |
| LIST_USER | 否 | – IGNORE | |
| GRANT_USER_PRIVILEGE | 否 | – IGNORE | |
| REVOKE_USER_PRIVILEGE | 否 | – IGNORE | |
| GRANT_USER_ROLE | 否 | MANAGE_ROLE | 否 |
| REVOKE_USER_ROLE | 否 | MANAGE_ROLE | 否 |
| CREATE_ROLE | 否 | MANAGE_ROLE | 否 |
| DELETE_ROLE | 否 | MANAGE_ROLE | 否 |
| LIST_ROLE | 否 | – IGNORE | |
| GRANT_ROLE_PRIVILEGE | 否 | – IGNORE | |
| REVOKE_ROLE_PRIVILEGE | 否 | – IGNORE | |
| CREATE_FUNCTION | 否 | USE_UDF | 否 |
| DROP_FUNCTION | 否 | USE_UDF | 否 |
| CREATE_TRIGGER | 是 | USE_TRIGGER | 否 |
| DROP_TRIGGER | 是 | USE_TRIGGER | 否 |
| START_TRIGGER | 是 | USE_TRIGGER | 否 |
| STOP_TRIGGER | 是 | USE_TRIGGER | 否 |
| CREATE_CONTINUOUS_QUERY | 否 | USE_CQ | 否 |
| DROP_CONTINUOUS_QUERY | 否 | USE_CQ | 否 |
| ALL | 否 | All privilegs | |
| DELETE_DATABASE | 是 | MANAGE_DATABASE | 否 |
| ALTER_TIMESERIES | 是 | WRITE_SCHEMA | 是 |
| UPDATE_TEMPLATE | 否 | – IGNORE | |
| READ_TEMPLATE | 否 | – IGNORE | |
| APPLY_TEMPLATE | 是 | WRITE_SCHEMA | 是 |
| READ_TEMPLATE_APPLICATION | 否 | – IGNORE | |
| SHOW_CONTINUOUS_QUERIES | 否 | – IGNORE | |
| CREATE_PIPEPLUGIN | 否 | USE_PIPE | 否 |
| DROP_PIPEPLUGINS | 否 | USE_PIPE | 否 |
| SHOW_PIPEPLUGINS | 否 | – IGNORE | |
| CREATE_PIPE | 否 | USE_PIPE | 否 |
| START_PIPE | 否 | USE_PIPE | 否 |
| STOP_PIPE | 否 | USE_PIPE | 否 |
| DROP_PIPE | 否 | USE_PIPE | 否 |
| SHOW_PIPES | 否 | – IGNORE | |
| CREATE_VIEW | 是 | WRITE_SCHEMA | 是 |
| ALTER_VIEW | 是 | WRITE_SCHEMA | 是 |
| RENAME_VIEW | 是 | WRITE_SCHEMA | 是 |
| DELETE_VIEW | 是 | WRITE_SCHEMA | 是 |
🌐 附:IoTDB的各大版本
📄 Apache IoTDB 是一款工业物联网时序数据库管理系统,采用端边云协同的轻量化架构,支持一体化的物联网时序数据收集、存储、管理与分析 ,具有多协议兼容、超高压缩比、高通量读写、工业级稳定、极简运维等特点。
| 版本 | IoTDB 二进制包 | IoTDB 源代码 | 发布说明 |
|---|---|---|---|
| 2.0.5 | - All-in-one - AINode - SHA512 - ASC |
- 源代码 - SHA512 - ASC |
release notes |
| 1.3.5 | - All-in-one - AINode - SHA512 - ASC |
- 源代码 - SHA512 - ASC |
release notes |
| 0.13.4 | - All-in-one - Grafana 连接器 - Grafana 插件 - SHA512 - ASC |
- 源代码 - SHA512 - ASC |
release notes |
✨ 去获取:https://archive.apache.org/dist/iotdb/
联系博主
xcLeigh 博主,全栈领域优质创作者,博客专家,目前,活跃在CSDN、微信公众号、小红书、知乎、掘金、快手、思否、微博、51CTO、B站、腾讯云开发者社区、阿里云开发者社区等平台,全网拥有几十万的粉丝,全网统一IP为 xcLeigh。希望通过我的分享,让大家能在喜悦的情况下收获到有用的知识。主要分享编程、开发工具、算法、技术学习心得等内容。很多读者评价他的文章简洁易懂,尤其对于一些复杂的技术话题,他能通过通俗的语言来解释,帮助初学者更好地理解。博客通常也会涉及一些实践经验,项目分享以及解决实际开发中遇到的问题。如果你是开发领域的初学者,或者在学习一些新的编程语言或框架,关注他的文章对你有很大帮助。
亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。
愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。
至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。
💞 关注博主 🌀 带你实现畅游前后端!
🏰 大屏可视化 🌀 带你体验酷炫大屏!
💯 神秘个人简介 🌀 带你体验不一样得介绍!
🥇 从零到一学习Python 🌀 带你玩转Python技术流!
🏆 前沿应用深度测评 🌀 前沿AI产品热门应用在线等你来发掘!
💦 注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://xcleigh.blog.csdn.net/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。

📣 亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(或者关注下方公众号,看见后第一时间回复,还有海量编程资料等你来领!),博主看见后一定及时给您答复 💌💌💌
更多推荐


所有评论(0)