通向架构师的道路之基于数据库的权限系统的设计

news2024/11/24 12:38:21

一、权限系统

这一天将讲述一个基本的基于数据库的权限管理系统的设计,在这一天的课程的最后将讲述“左右值无限分类实现算法”如何来优化“系统菜单”的结构而告终。今天的内容和前几天的基础框架是一样的它们都属于基础知识,在这些基础知识上还可以扩展出无数的变种与进化设计。

二、先来看客户的一个需求

2.1 用户实际需求

1.       所有的用户、角色可动态配置

2.       所有的系统菜单的权限要求具体到“增,删,改、查、打印、导出”这样的小权限的设计

3.       所有的权限基于角色来进行划分和判断

4.       一个用户可能属于多个角色

5.       系统菜单也能够动态的“增、删、改、查”

2.2 系统权限菜单样例

 

三、基于数据库的系统权限表设计

3.1 ER(Entity Relationship)

围绕上述需求,我们可以在数据库内进行如下的表设计,下面直接给出ER图:

 

3.2 表关系详解

上述设计有6张表,其中:

T_User

用于存放用户信息,此处只存放基础信息

T_Role

用于存放系统角色信息

T_User_Role

用于存放系统用户与角色的匹配关系

T_Sys_Menu

这张就是用于存放系统菜单的表了,这张表的设计主要使用了如下的表设计技巧:

注意这边的MENU_ID与MENU_PID

如果这个菜单项是一级菜单,那么我们把它的MENU_PID设为0

如果这个菜单是另一个菜单的子菜单,那么我们就把它的MENU_PID设为它的父菜单的MENU_ID。

有了这样的结构,我们一个递归就能把这颗“树”显示出来了,是不是?

此处以Oracle数据库为例,不使用递归,直接把树形结构在数据库中就造型造好(当然,还有更好的方法如:有人喜欢设level或者是deep这样的字段来简化程序解析树型结构菜单稍后我们会来讲一个根本不需要用递归的树型菜单的设计来最大程度优化设计。)

显示整颗树型菜单结构的Oracle语句:

SELECT *FROM T_SYS_MENU

STARTWITH MENU_PID=0

CONNECTBYPRIOR MENU_ID=MENU_PID

orderby MENU_ID

上述语句,已经用数据查询用句就将我们的这个“树”的层次关系理出来了,如果我们手上有一个控件叫dtree.js,那么一个循环就可以把这个树显示出来了,不是吗?

来看dtree.js的应用

//公式:  d.add(menu_id, menu_pid, ‘menudescr’, ‘menu_url’);

d = new dTree('d');

                   d.add(0,-1,'菜单');

                   d.add(1,0,'报表查询','example01.html');

                   d.add(2,1,'月报','example01.html');

                   d.add(3,1,'季报','example01.html');

                   d.add(4,1,'年报','example01.html');

                   d.add(5,0,'系统管理','example01.html');

                   d.add(6,5,'用户管理','example01.html');

                   d.add(7,6,'新增用户','example01.html');

                   d.add(8,6,'删除用户','example01.html');

              d.add(9,5,'角色管理','example01.html');

                   d.add(10,9,'新增角色','example01.html');

                   d.add(11,9,'删除角色','example01.html')

                   document.write(d);

大家看上面,这个是dtree.js插件,一个专门用于生成树的js插件的使用方法,那么如果我们附以上述的sql语句在数据库中把数据选出来后,是不是只要一个循环就可以给这个dtree.js插件显示了,不是吗?

我们如果不想显示整颗树只想显示如:

只显示系统管理菜单有其下列所有的子菜单,那么我们的Oracle中的Sql应该怎么写呢?

经查“系统管理”这个菜单的MENU_ID=’105’,于是我们的Sql语句如下:

SELECT *FROM T_SYS_MENU

STARTWITH MENU_ID='105'

CONNECTBYPRIOR MENU_ID=MENU_PID

orderby MENU_ID

 对吧?很简单哈!

T_Privilege

用于存放系统每个菜单的详细子权限如“增,删,改,查”

T_Menu_Privilege

这张表就是我们的最终终结大BOSS表,它里面是一个完整的系统权限与角色关系间的对应。

比如说:

我们想要知道“user”这个角色,可以操作哪些菜单,哪些权限,那么我们的SQL语句如下:

selectdistinct m.menu_id,m.menu_descr,m.menu_url,m.menu_pid,p.privilege_id,p.privilege_type from

t_menu_privilege mp,

t_sys_menu m,

t_privilege p,

t_user_role r

where

mp.privilege_id=p.privilege_id

and mp.role_id=r.role_id

and mp.menu_id=m.menu_id

and r.role_id='user'

orderby m.menu_id

通过这个结果我们就知道了

1.       角色“user”能操作哪些菜单

2.       角色“user”对某个菜单具有什么样的权限

进而,我们可以推出:

我们想要知道Danzel这个人,可以操作哪些菜单,以及在哪些菜单上有哪些可供操作的权限,我们使用如下的SQL语句:

selectdistinct m.menu_id,m.menu_descr,m.menu_url,m.menu_pid,p.privilege_id,p.privilege_type from

t_menu_privilege mp,

t_sys_menu m,

t_privilege p,

t_user_role r

where

mp.privilege_id=p.privilege_id

and mp.role_id=r.role_id

and mp.menu_id=m.menu_id

and r.user_id='Danzel'

orderby m.menu_id;

 

通过这个结果我们就知道了

1.       Danzel这个人能操作哪些菜单

2.       Danzel对某个菜单具有什么样的权限

3.3 界面制作

关于jsp,什么dao层,service层的具体代码这个就不讲了,这个没有意义的哦,我们来讲设计。

登录后如何显示左边的树型菜单:

ü   取得用户名

ü   将该用户名作为参数input进如下的SQL语句得到该用户在登录后可以看到的系统菜单:

selectdistinct m.menu_id, m.menu_descr, m.menu_url, m.menu_pidfrom

t_menu_privilege mp,

t_sys_menu m,

t_privilege p,

t_user_role r

where

mp.privilege_id=p.privilege_id

and mp.role_id=r.role_id

and mp.menu_id=m.menu_id

and r.user_id='Danzel'

STARTWITH MENU_PID='0'

CONNECTBYPRIOR M.MENU_ID=M.MENU_PID

orderby M.MENU_ID

将该结果直接给于index.jsp页面上的dtree.js组件,一个循环,所有菜单曾树形显示。

知道用户登录后能够对哪些菜单,并且在相关界面操作时有哪些子权限如:增、删、改、查、打印、报表的设计:

ü   在登录时得到用户名等信息,然后将该用户名作为参数input进入如下的sql语句:

selectdistinct m.menu_id,m.menu_descr,m.menu_url,m.menu_pid,p.privilege_id,p.privilege_type from

t_menu_privilege mp,

t_sys_menu m,

t_privilege p,

t_user_role r

where

mp.privilege_id=p.privilege_id

and mp.role_id=r.role_id

and mp.menu_id=m.menu_id

and r.user_id='Danzel'

orderby m.menu_id;

ü  得 到上述结果后,使用:Haspmap这样的结构将该用户所属的角色分对每个菜单有哪些操作(增、删、改、查、打印、报表)进行存储,放入用户的session中,在 以后用户在每个界面进行点击动作时进行判断,或者可以写个filter来进行判断,是不是就可以作到:

知道该登录用户在登录后可以对哪些菜单进行操作,并且拥有什么操作权限啦?

相应的我们还需要制作如下的界面:

ü   用户的管理界面

ü   角色的管理界面

ü   用户与角色的分配界面

ü   系统菜单的管理界面

ü   具体权限项的管理界面

ü   系统菜单与角色间具体的权限分配界面

好了,有了这些界面,一个完整的基于数据库引擎的权限系统算是完成了。

严重注意:

在制作“系统菜单与角色间具体的权限分配界面”时,如果在界面上把某个角色对该条菜单的“查看”这个选项disable后,那么该角色将不拥有任何该菜单的所有权限,举例:

某角色对菜单A拥有如下权限:

增、删、改、打印

但是这个“查看”权限没有,也有可能是管理员误操作,但是从真实情况我们来说,这个角色连“查看”的权限都没有,连菜单都进不了,他能做什么“增、删、改。。。”等其它的操作啊?操作个头啊!是吧?

所以一旦界面上该角色对某个系统菜单没有了查看权限后,它对这个菜单的其它权限也必须从T_MENU_PRIVILEGE这个表中删除。

四、改进T_SYSTEM_MENU表的设计

前 面我们用的是Oracle特有的递归SQL将树形菜单在从数据库中选取出来时就已经是一颗树的结构了,但是像MYSQL,SQL SERVER, DB2等可能不带有这样的特SQL,那就需我们自己动手去写递归,还有就是很多工程用的是jquery的tree或者是其它相关的ajax tree,这些tree都需要用到一个字段叫level(此处指深度、层次的意思),如果按照原来的表结构,要取得这个level,恐怕是要写递归算法 了。就算有些数据库有类似的语句,那也需要你去修改你的SQL语句从未影响了性能与通用性。

我们在这边说,我们无论什么数据库,如果都用相 同的SQL就能把我们需要的东西在数据库中就排好树形结构然后一次性选取出来,那应该有多好啊。答案是有的,在原来的T_SYSTEM_MENU表中改动 也不大,只需要增加两个字段即可,即:lft与rgt(left, right),这种设计其实已经有了,我在此只不过结合实际例子把它应用到实际上,并且进一步详细描述如果来实现它,它就是被称为:

左右值无限分类实现算法也称为预排序遍历树算法,对于这种层次型数据(Hierarchical Data)一般我们有两种设计方法:

ü   毗邻目录模式(adjacencylist model)

ü   预排序遍历树算法(modifiedpreorder tree traversal algorithm)

4.1 基于lft, rgt的无限分类算法

我们来看一个图,下面我们把我们原有的菜单画成下面这样的层次关系:

 

我们把原有的系统菜单画成了一个个的椭圆,最外层的就是我们的菜单,然后在每个椭园的两个端点即left与right,按照从左->右,开始用数字来标号,上面这个图中可以看到最外层这个大椭园的lft(左)为1,它的rgt(右)为24。

那么我们可以用一条标准的SQL,而非什么数据库自带的特有的、特殊的SQL来显示出这个树形菜单,来看下面的SQL:

SELECT

       node.menu_id menuId,

       node.menu_descr menuDescr,

       node.lft,

       node.rgt,

       node.menu_url menuUrl,

       (COUNT(parent.menu_id)-1) menuLevel,

       node.menu_pid pid

FROM t_sys_menu node,

       t_sys_menu parent

WHERE

        node.lftBETWEEN parent.lftAND parent.rgt

        AND node.menu_descr!='菜单'

GROUPBY node.menu_id,node.menu_descr,node.lft,node.rgt,node.menu_url,node.menu_pid

ORDERBY node.lft

来看显示的结果

看看上面这个结果,怎么样?

ü   树形结构也有了(可以用于dtree来显示);

ü   层次level也有了(可以用于ajax的一些tree);

ü   我们用的SQL又是最标准的所有的数据库都能用到的SQL;

尝到甜头了是吧?那我们下面来看如何对这样的基于t, rgt的数据结构来作插入操作?

4.2 如何在现有节点中插入新的子节点

如果现在我们要在“报表查询”这个圆里加入一个菜单,假设我们就叫“周报”,那么再来看这个原有的图发生了什么样的改变,来看:

看到么,原有的最外层椭园的rgt+2,原有的报表查询这个园的右边界呢?是不是也加了2啊?而原有的“月报”这个圆的lft加了多少?也是+2!

那么来看“周报”这个圆的lft与rgt关系:

“周报”的lft= “报表查询”这个圆的lft+1

“周报”的rgt=“报表查询”这个圆的lft+2

于是我们就可以整理出在原有叶子中插入child的公式:

第一步:选取要被插入new child的外面这个圆的lft的值

第二步:原有的数据中所有的rgt如果>第一步中得到的lft的值,那么全部+2

第三步:原有的数据中所有的lft如果>第一步中得到的lft的值,那么全部+2

第四步:将插入的节点的lft与rgt的设计,新节点的lft =第一步中的lft+1,新节点的rgt=第一步中

的lft+2

来看一个具体的例子:

我们要在“报表查询”即menu_id=’101’ 中插入一个新的菜单,叫“周报”,下面是按照上面四步算法的相关SQL语句:

第一步

SELECT lftFROM t_sys_menuwhere menu_id='101';

这一步我们得到的值为:2

第二步:

UPDATE t_sys_menuSET rgt = rgt +2WHERE rgt >2;

第三步:

UPDATE t_sys_menuSET lft = lft +2WHERE lft >2;

第四步:

INSERTINTO t_sys_menu(menu_id, menu_descr, menu_url, lft, rgt)VALUES('113','周报','周报的url', (2+1), (2 +2));

插完后我们运行查询SQL:

SELECT

       node.menu_id menuId,

       node.menu_descr menuDescr,

       node.lft,

       node.rgt,

       node.menu_url menuUrl,

       (COUNT(parent.menu_id)-1) menuLevel,

       node.menu_pid pid

FROM t_sys_menu node,

       t_sys_menu  parent

WHERE

        node.lft BETWEEN parent.lft AND parent.rgt

        AND node.menu_descr!='菜单'

GROUPBY node.menu_id,node.menu_descr,node.lft,node.rgt,node.menu_url,node.menu_pid

ORDERBY node.lft

Look, 数据正确无误,我们来看整个t_sys_menu表里的数据:

Look,整个最外层的“圆”,右边界增加了2,从原有的24变成了26。

4.3     如何插入一个新的节点

上面讲的是在原有的节点中插入一个子节点,现在来讲,如何插入一个新的节点,比如说:

我们现在有:报表查询,系统管理两大菜单,我们还想加一个菜单:保单审核,怎么来做?

我们把4.2节中“如何在现有节点中插入新的子节点”里四步公式,稍稍改动一下

第一步:选取要被插入新的节点左边节点的rgt的值

第二步:原有的数据中所有的rgt如果>第一步中得到的rgt的值,那么全部+2

第三步:原有的数据中所有的lft如果>第一步中得到的rgt的值,那么全部+2

第四步:将插入的节点的lft与rgt的设计,新节点的lft =第一步中的rgt+1,新节点的rgt=第一步中

rgt+2

下面来看我们在“报表查询”与“系统管理”中间,插入一个菜单叫“保单审核”。

第一步

SELECT rgtFROM t_sys_menuwhere menu_id='101';

这一步我们得到的值为:11

第二步:

UPDATE t_sys_menuSET rgt = rgt +2WHERE rgt >11;

第三步:

UPDATE t_sys_menuSET lft = lft +2WHERE lft >11;

第四步:

INSERTINTO t_sys_menu(menu_id, menu_descr, menu_url, lft, rgt)VALUES('114','保单审核','', (11+1), (11 +2));

运行下面的SQL语句我们来检查一下插入的效果:

SELECT

                  node.menu_id menuId,

                  node.menu_descr menuDescr,

                  node.lft,

                  node.rgt,

                  node.menu_url menuUrl,

                  (COUNT(parent.menu_id)-1) menuLevel,

             node.menu_pid pid

              FROM t_sys_menu node,

                  t_sys_menu  parent

              WHERE

           node.lftBETWEEN parent.lftAND parent.rgt

           AND node.menu_descr!='菜单'

              GROUPBY node.menu_id,node.menu_descr,node.lft,node.rgt,node.menu_url,node.menu_pid

              ORDERBY node.lft

 

怎么样,结果对了吧,呵呵!

看看整个菜单的右边界吧,从原来的26变成了28了,是不是哦?

4.4     如何删除一个节点

来看公式

第一步:选取要被删除的菜单的lft的值,rgt的值,以及宽度(width=rgt-lft+1);

第二步:删除所有的位于第一步中得到的lft与rgt之间的节点;

第三步:将所有的右边界大于第一步中得到的rgt的所有节点的rgt的值减去第一步中得到的width

第四步:将所有的左边界大于第一步中得到的rgt的所有节点的lft的值减去第一步中得到的width

来看实际例子,我们有下面这样的数据:

我们想将menu_id=114的保单审核删除,当然,这是一个父节点,如果把它删了,其子节点115即手工审核也必须被一起删除,要不然它就成为脏数据了是不是?套用上述四步公式:

第一步:

SELECT lft, rgt, (rgt - lft +1) widthFROM t_sys_menuWHERE menu_id ='114'

 

第二步:

DELETEFROM t_sys_menuWHERE lftBETWEEN12AND15

第三步:

UPDATE t_sys_menuSET rgt = rgt -4WHERE rgt >15

第四部:

UPDATE t_sys_menuSET lft = lft -4WHERE lft >15

全部步骤完成后,我们来运行检验的SQL:

SELECT

                  node.menu_id menuId,

                  node.menu_descr menuDescr,

                  node.lft,

                  node.rgt,

                  node.menu_url menuUrl,

                  (COUNT(parent.menu_id)-1) menuLevel,

             node.menu_pid pid

              FROM t_sys_menu node,

                  t_sys_menu  parent

              WHERE

           node.lftBETWEEN parent.lftAND parent.rgt

           AND node.menu_descr!='菜单'

              GROUPBY node.menu_id,node.menu_descr,node.lft,node.rgt,node.menu_url,node.menu_pid

              ORDERBY node.lft

 

结果正确,再来看整个“菜单”的边界,从原来的28缩减成了26了,结果正确。

上述这种基于lft, rgt左右值无限分类实现算法的个菜单的好处在于:

ü   SQL语句不受特定的数据库的限制

ü   SQL语句通用

ü   直接从数据库中远取出来的结构化的数据即可满足需要pid的如:dtree.js这样的JS控件的需要也可以满足需要level的ajax tree控件的需要。

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

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

相关文章

Leetcode 剑指 Offer II 038. 每日温度

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 请根据每日 气温 列表 temperatures ,重新生成一个列…

第5集丨Vue 江湖 —— 监视属性/侦听属性

目录 一、基本使用1.1 watch配置监视1.2 vm.$watch动态监视1.3 深度监视(deep watch)1.4 简写形式 二、computed和watch的对比2.1 使用watch实现setTimeout操作2.2 用computed无法实现setTimeout 三、其他注意事项3.1 vue devtools的bug3.2 xxxyyy格式3.3 将window传入data中 V…

高并发负载均衡---LVS

目录 前言 一:负载均衡概述 二:为啥负载均衡服务器这么快呢? ​编辑 2.1 七层应用程序慢的原因 2.2 四层负载均衡器LVS快的原因 三:LVS负载均衡器的三种模式 3.1 NAT模式 3.1.1 什么是NAT模式 3.1.2 NAT模式实现LVS的缺点…

c++--二叉树应用

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

【react】react中BrowserRouter和HashRouter的区别:

文章目录 1.底层原理不一样:2.path衣现形式不一样3.刷新后对路山state参数的影响4.备注: HashRouter可以用于解决一些路径错误相关的问题 1.底层原理不一样: BrowserRouter使用的是H5的history API,不兼容IE9及以下版不。 HashRouter使用的是URL的哈希值。 2.path衣…

Element-ui中分页器的使用

<template>中写&#xff1a; js中写&#xff1a;

pytorch的CrossEntropyLoss交叉熵损失函数默认reduction是平均值

pytorch中使用nn.CrossEntropyLoss()创建出来的交叉熵损失函数计算损失默认是求平均值的&#xff0c;即多个样本输入后获取的是一个均值标量&#xff0c;而不是样本大小的向量。 net nn.Linear(4, 2) loss nn.CrossEntropyLoss() X torch.rand(10, 4) y torch.ones(10, dt…

认识 MyBatis + MyBatis如何使用 (MyBatis操作数据库)

前言 本篇介绍了什么是MyBatis&#xff0c;MyBatis的前期配置&#xff0c;MyBatis操作数据库的两种方式&#xff1a;.xml文件 / 注释&#xff0c;使用MyBatis的一些注意&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流&#xff0c;共同进步&#x…

AI 一键去水印:教你无限量使用商业图片的技巧

场景再现 刚开始注册账号(啥账号具体不表了&#xff0c;小编不喜欢的那个)&#xff0c;想弄个闪亮&#xff0c;好看&#xff0c;有个性化的 Logo。作为一名非专美工小白人员&#xff0c;网上翻了很久作图工具&#xff0c;要么就是不好用&#xff0c;好用的大部分都收费。最后没…

如何从零开始配置前端环境以及安装必备的软件插件

文章目录 前言一、VSCode&#xff08;Visual Studio Code&#xff09;1.1 VSCode介绍与安装1.2 VSCode 常用插件安装1.3 VSCode代码格式化 二、Node.js2.1 Node的介绍与安装2. yarn的安装 三、Webpcak3.1 Webpcak的介绍与安装 四、Vue CLI4.1 Vue CLI的安装与介绍 五、初始化Vu…

设计模式-迭代器模式在Java中使用示例

场景 为开发一套销售管理系统&#xff0c;在对该系统进行分析和设计时&#xff0c;发现经常需要对系统中的商品数据、客户数据等进行遍历&#xff0c; 为了复用这些遍历代码&#xff0c;开发人员设计了一个抽象的数据集合类AbstractObjectList&#xff0c;而将存储商品和客户…

MaskedTextBox基本用法

作用&#xff1a;是一个文本输入框&#xff0c;可以指定文本的输入规则。 常用属性&#xff1a; 规定输入的格式 输入字符的占位符&#xff0c;获取输入的数据后留意处理 常用事件&#xff1a; 当输入字符不符合规则时发生 后台代码示范&#xff1a; //掩码文本控件输入不满…

快速实现一个div的水平垂直居中

效果 实现 给父盒子宽高和flex&#xff0c;子盒子margin&#xff1a;auto 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

管理类联考——写作——论说文——实战篇——行文篇——通用性强,解释多种现象的经典理论——谈必要

前言 本节内容涉及“社会分工理论”“资源稀缺性”“瓶颈理论”等理论。这些理论一般用在“利大于弊式结构”中“整体有必要”的部分&#xff0c;也可用于“AB二元类”题目“谈好处”的部分。 需要注意的是&#xff0c;“有好处”一般指有它更好&#xff1b;“有必要”一般指没…

基于人工智能的中医图像分类系统设计与实现

华佗AI 《支持中医,永远传承古老文化》 本存储库包含一个针对中药的人工智能图像分类系统。该项目的目标是通过输入图像准确识别和分类各种中草药和成分。 个人授权许可证 版权所有 2023至2050特此授予任何获得华佗AI应用程序(以下简称“软件”)副本的人免费许可,可根据以…

285 · 高楼大厦

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a; 1.从左往右维护一个单调递减&#xff0c;栈的长度就是&#xff0c;可以看到最多的高楼 2.从右往后也是维护一个单调递减的栈 class Solution { public:/*** param arr:…

Python中的PDF文本提取:使用fitz和wxPython库(带进度条)

引言&#xff1a; 处理大量PDF文档的文本提取任务可能是一项繁琐的工作。本文将介绍一个使用Python编写的工具&#xff0c;可通过简单的操作一键提取大量PDF文档中的文本内容&#xff0c;极大地提高工作效率。 import wx import pathlib import fitzclass PDFExtractor(wx.Fr…

谷歌、Edge等浏览器截图长图的方法

目录 序 谷歌浏览器 步骤1&#xff1a;打开开发者工具&#xff08;右上角->更多开发工具->开发工具&#xff09;也可以直接按F12 步骤2&#xff1a;按组合键CtrlShiftP&#xff0c;打开浏览器的console功能菜单的指令搜索栏 步骤3&#xff1a;搜索框输入full,找到Cap…

LeetCode--HOT100题(19)

目录 题目描述&#xff1a;54. 螺旋矩阵&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;54. 螺旋矩阵&#xff08;中等&#xff09; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 Le…

socker套接字

1.打印错误信息 2.socketaddr_in结构体 结构体&#xff1a; &#xff08;部分库代码&#xff09; (宏中的##) 3.manual TCP: SOCK_STREAM &#xff1a; 提供有序地&#xff0c;可靠的&#xff0c;全双工的&#xff0c;基于连接的流式服务 UDP: 面向数据报