访问控制元素

准备

权限引擎

anycmd是个权限引擎。使用者初始化这个引擎的状态,然后往这个引擎中输入一个运动的标识它会回答是否允许这个运动发生:允许、不允许、我异常了(权限引擎异常了)。如何标识一个运动?有这么几个要素:1 谁发起的这'个'运动,2 这'种'运动的‘种’标识。3 这个运动的实参列表。

受控的运动

所谓运动,其实就是一个过程,其实就是一个计算。其实就是一个输入和输出。其实就是对象的方法啦,没什么高深的。他使用“运动”这个概念,而不使用更具体的概念是想表明,它是设计用来控制任何运动的。 比如3D打印机的打印运动,不停的运动,系统不停的采样,不停的改变权限引擎的状态,然后终于发现用户99%是在试图打印一把枪。枪有它的核心模型,无论你怎么伪装,打印出来的枪都是使用同样的物理定律去发射子弹的,它的那个核心模型是一样的,anycmd就是要采样识别出他们的伪装从而在这个运动完成之前终止它。

无边界的系统

在运动完成之前总是有办法终止这个运动。计算机指挥3D打印机打印的运动过程只是它的父过程的子过程,还有打印后从3D打印机中取结果的过程啊,没有权限就让你取不出。


落实

anycmd明白rbac处在acl和安全策略语言中间的意义和重要性,anycmd明白人们花费集体智慧人力指定出标准的意义和重要性。anycmd致力于让高高在上的国家标准、国际标准发挥价值。anycmd定义了可以任意两两组合的9中AC元素:Account、Catalog、Role、Group、Function、Menu、AppSystem、ResourceType、Privilege(暂不支持,该取值的存在是为了概念完整性。组成授权路由链表。如同面向对象机制中类的“继承”)。

不止9种元素

anycmd中有9中ac元素,为什么这么多种元素?这不是我抽象出来的,而是观察现在的世界观察出来的。人们抽象出这些元素是为了帮助简化复杂的问题,这9种ac元素是站在一个更低的抽象层次来观察的,而在高一级的抽象层次看ac只有三种元素:主体、资源、运动。目录是对资源的单元划分,目录节点是边界,每一个边界绑定的角色应该只在当前用户进入这个边界后激活离开这个边界后收回,从而不能将从一个边界内得到的角色带出这个边界去操作其他边界内的资源。

9种元素

1. Account

2. Catalog

目录是对对象集的单元划分。这种划分单元的方式与普通的分类有一个明显的不同是——目录具有层级关系。目录是一种添加了偏移量的分类方式,目录也是分类法,当然一个对象至多属于一种目录的一个节点,不可能属于多个节点,属于多个节点的那种不叫分类更不叫目录。

目录是对集合中所有元素进行分层排布

目录是对资源的单元划分。目录属于一种分类法,它与普通的分类法的唯一差别是目录具有偏移量。给定一个0到100的实数集合Set{0…100}。普通的分类法所做的分类类似这样:

 {
     [0…10},
     [10…20},
     [20…50},
     [50…100]
 }

而目录是这样:

 {
     {
         [0…10},
         [10…20}
     },
     [20…30},
     {
         [30…35},
         [35…40},
         [40…50},
         {
             [50…60},
             [60…70},
             [70…75},
             [75…80},
         }
     },
     [80…100],
 }

目录不是资源的固有属性,但应该作为资源的扩展字段直接和资源存储在一起。

Anycmd中的目录

Anycmd对目录的使用几乎要遍布所有资源,目录是资源的附加属性,而资源的固有属性只是没有偏移的分类。Anycmd希望在需要的时候能够被允许侵入应用系统的数据库设计以保存资源管理员添加的组织属性(这个组织属性可能不止一种,但不管资源管理员在管理资源集的时候添加了多少组织属性Anycmd都只会使用你对应的数据库表的一个字段存储)。之所以要侵入应用数据库表是为了根据目录对资源集的筛选能够在数据库进程完成而不必加载进应用系统的内存再筛选。框架使用那一个字段比如varchar(100),框架把它的长度再做单元划分,如果一个单元定长为10就可以存储资源管理员添加的10种目录。根据目录查询的时候应用系统发出的sql中会截取这个字段的正确的单元。

40层目录级权限

整个Anycmd权限引擎的建筑可以分为前100层和后100层两个逻辑阶段。其中前100层主要关注Function级权限,后100层主要关注数据级权限。而40层目录级权限处在前后100层中间,其中前20层在功能级权限中,后20层在数据级权限中。40层的目录级权限随同前100层的功能级权限一同建设,放在同一个工期。

使用原则

Anycmd是个权限引擎,她支持实体级的控制。但管理资源的时候尽量不推荐使用实体级的控制,首先评估实体属性(在实体属性上可以对资源集进行分类划分),如果在实体上的那些固有属性上难以制定出满足安全需求的策略的话再考虑是不是需要目录,尽量使用附加的组织属性去对资源划分单元,按照单元进行控制。尽量不使用实体级的控制(因为实体太多了,但如果目标实体是主体的话,比如User实体是主体,这个可以实体级控制,由各个主体自主控制自己而不用管理员工作)。实体级的控制的开启也需要侵入应用系统的数据库,侵入目标资源表建立“一个”安全字段,这个字段中存储的是这条记录的安全策略,安全策略是跟实体记录一同加载进应用系统的内存的,之所以要侵入你的数据库也是因为不应该多一次专门查询ACL策略的查询。

扩展:什么是标签?

标签不分层级。一个对象可以有多个标签,一个标签可以关联多个对象,对象和标签是多对多关系。标签也不是资源的固有属性,但它没有目录那么高的地位,通常不应将标签作为资源的扩展字段而和资源存储在一起。

举例:淘宝的商品“类目”是个什么东西?这可能是淘宝自创的概念,如果你进一步了解会发现淘宝的类目还分“前台类目”和“后台类目”,淘宝的前台类目就是“标签”,后台类目就是“类别”。前台类目多对多,后台类目多对一。教师的目录是个什么东西?政府作为一个大的行政单位,它有很多很多资源,为了对这些资源进行良好的管理政府划分了很多“行政单位”。同样,教育部也对教育单位做了单元划分,区县、学校、电教馆、教科所等这些就是教育目录。一个老师必定是属于且只属于一个目录的,系统可以直接使用区县、学校、电教馆等这些自然的目录来组织师生记录资源。在工作的开展上,一个老师有可能效力不只一个学校,但从资源记录的角度一个老师至多属于一个目录。

3 Role

4. Group

一个组是一个地图,整个森林中的树木是一个集合,树木类型的一个组则是一张记录树木位置的坐标图,图上的树木是整个森林的一个子集。 用户绘制一张记录树木的位置的坐标图,然后可以把这张图交给某个工人,从而工人按图索骥去伐木。 不在图上的树木可能是不允许砍伐的。Group跟手工仓库的区别是一个Group里的资源的类型都是一样的,而手工仓库里的资源可以不是同一类型的 (手工仓库中的一条资源记录需要ObjectType+ObjectID两个字段来标识而组只需一个字段)。 手工仓库也是“组”,手工仓库是复杂组。 只要用劲理解了“目录”则部门、岗位这两个概念就弄明白了,机构的本质就是目录,而岗位是具有目录属性的工作组。

机构:毫无疑问它是目录节点;

岗位:岗位是绑定到目录的工作组;

工作组:工作组是跨越目录的资源集,这个资源组中“具有主体”这种类型的资源元素,具有目录属性的工作组的意思是该组中的资源被约束为只能来自本目录和它的下级目录。

组:资源集,这个资源集中不一定只有一种类型的资源。

简单组:单一类型的资源集;

职位:不是目录。职位属于分类或字典,职位不是绑定到目录的,职位没有目录属性。不同目录的人员可能拥有相同的职位。

5. Function

operation 是一种function,operation 集是function 集的子集。operation 是功能层(展示层)的function, 一个operation 表示一个功能,一个function 不一定是一个功能。 如果一个operation 的访问是受控的,也就是说不是每个主体的请求都会被满足的则就是permission。而privilege是subject + permission。Operation = action + resourceType;Permission = managedOperation;功能级Privilege = subject * permission;实体级privilege = subject * entity;(这些公式并不严禁,并没有事先定义加减乘除是什么意思,只是用来传递意思,需要借助大量的冗余来降低信息损失)实体属性级/单元格级以此类推(注意合理分配存储和计算)。

不妨相信人类的知识树是良好的,不妨相信人类的知识树是按照构造定律构建的。那么“功能”、“能力”就是“功”、“能”、“力”。按照本意理解那棵知识树节点中的词汇出错的机会是非常小的。 从新表述一下Operation和Function的区别 与上面的表述在意义上没有差别,是一致的,是对同一个事物不不同描述。 Operation是一种Function,Operation集是Function集的子集。 Operation表示的是主体发起的调用,Operation是有标识的实体,Operation的标识在调用发起时由系统分配。在Operation方法体的直接子节点中可以通过UserSession.Current到当前线程中索引发起当前调用的主体(方法体的直接子节点是指方法体中直接书写的那些语句而不是跳到另一个方法中去)。 而Function体中不能通过UserSession.Current索引当前主体,若Function体中做功时需要当前主体信息的话,当前主体唯有通过Function的入参传入。 “方法、函数、功能”是在做功。函数体中那个运行时的栈就是在做功。function没有标识(注意这是个小写的function,表示function实例。当然大写的Function是有标识的,这个标识是元数据标识),function是值对象;operation会在主体调用时由系统分配一个唯一的标识,operation是实体。 国家的rbac标准上使用的是Operation这个词,而anycmd中使用的是Function这个词。由于Function集比Operation集大,Operation集是Function集的子集,这就是说anycmd的能力集是比rbac大的多的。

控制权限就是控制运动 时间 = 运动 = 变化 = 功 = 能。将人类所有的资料中的这些词汇相互替换后阅读一定都是通顺的。 事物有变化才需要控制,当事物睡在空间介质中不动时是不需要控制权限的,只有当有人读它、写它,只有它有变化时,只有做功时才需要控制。 在anycmd关键字下,之前咱们论述到时间就是变化。时间 = 变化 = 运动 = 功能。 事物睡在空间介质中时它是不变化的,从而它的时间是静止的。时间是相对的(运动是相对的、变化是相对的),睡在空间介质中的事物的时间是静止的,但是别的醒着的事物的时间是前进着的,为了统一管理整个系统中的时间箭头我们的系统中会有个唯一的计时设备(cpu)。 睡在空间介质中的事物可以在某些系统时刻到达时由外部醒着的事物向它们发起请求,然后由基础设施将它的副本转移到活跃的空间中去催醒它使它参与完成某件事情,办完事情后基础设施再次将它催眠。 我们的系统中的任何事物都在那棵系统树上对应有个位置,系统的运行时就是在这棵树上进行变化。但是由于这棵树非常深枝叶非常多非常复杂导致我们认识这棵树时难以人知到整体,往往陷入枝条片叶中去了。anycmd努力一下试试能不能把这棵树理出来帮助后来者理解它从而更好的做事情。

6. Menu

Menu表示空间坐标,是个标识符和定位符融为一体的对象。如果我们的系统是棵树,Menu实际上就是用来指示树上的具体位置。用户通过Menu来告诉系统他/她想要到这棵树上的哪个节点中去。Menu用来指示静态的资源树上的节点,资源树上的节点是空间、是场所。 而Function是动态的,是动态的运动树(栈)上的节点。 将Menu重命名为SpaceId可能更合适?需要是标识符不能是定位符,SpaceId对象上具有个url(定位符)。标识符和定位符的差别就是要翻译。我们无法保证系统树的结构不会随着时间改变所以必须是标识符。让我们使用ASP.NET Mvc中学到的Mvc知识来举个例子:在前端控制器层中,对应有View的Action是Space,不对应View但是返回某个资源的Action本质也是Space。比如JsonResult GetById(guid productId)本质也是Space。这个Action表达的意思是将当前主体传输到给定标识的这条product空间中去。 Function是什么呢?Function是运动,是功、能、力,体现在计算机程序里通常是运行时的栈。不妨搬出咱们从先人那里继承的思想来诠释一下Function和Space。将Function这个词替换为太极图上那个纠缠,叫“道”(Tao)吧。 subject进入space论tao。“进入”本身也是tao。用“一”(Yi)替换Space。Tao生Yi,Yi生二,二生三,三生万物。为啥不是〇?能够站在宇宙之外观察的话正是O,可是我们无法站到宇宙之外。在咱们的数据库表设计中,Menu(Yi)表中的记录正是来自于Function(Tao)表中的记录的。正是Tao生Yi,Yi是空间,Tao是时间。请暂时不要对引入Tao和Yi这两个概念反感,以后不会去频繁的搬出这些概念。保持Menu和Function的命名不变。Menu记录所标识的树上的空间位置应可以是整棵树的任何一个节点。

7. AppSystem

AppSystem是应用系统,在Anycmd中就是权限应用系统。比如OA系统、CRM系统等,如果这些系统接入Anycmd中心系统来统一管理安全策略的话每一个系统就会在Anycmd中心系统的AppSystem数据库表中对应一条记录。这条记录登记的是应用系统的基本信息:Id、Code、Name、Description等。 值得说明的是Id字段和Code字段。Id和Code的功用都是唯一地标识和识别一个应用系统的,为什么要提供两种标识呢?这是因为Id往往是数字或Guid等人类难以从中读出信息的值,Id值往往是由计算机程序填入的,而Code可以留给人填入。人类自己填入的值当然更加具有可读性。整个anycmd系统内部不涉及与人类交互的地方都应该使用Id来识别对象而不应使用Code,但可以在与人类交互地方比如与程序员交互的入口可以使用Code来索引应用系统集(AppSystemSet)中的对象,一旦索引得到appSystem对象后Code字段就无用了因为系统内部是使用Id而非Code的。

8. ResourceType

ResourceType是资源类型。资源是什么呢?资源是需要进行访问控制的系统资源,例如文件、打印机、终端、数据库记录等。而资源类型的本质是对资源的分类。这里需要首次引入分类法。 人类发明的分类法其原理是比较抽象和复杂的。Anycmd用到了分类法所以需要试图表述清楚它。

分类就是按照事物的性质、特点、用途等作为区分的标准,将符合同一标准的事物聚类,不同的则分开的一种认识事物的方法。 分类法是指将类或组按照相互间的关系,组成系统化的结构,并体现为许多类目按照一定的原则和关系组织起来的体系表,作为分类工作的依据和工具。分类法有交叉分类法,树状分类法等等。 摘自百度百科

我不理解百度百科上所说的交差分类法是什么?我认为如果我们按照人类的性别对人类类型对象集分类的话应该会是“男人”、“女人”、“未知”、“未说明”这么几类,人类的分类法绝不会把一个人既归类到“男人”又归类到“女人”。那么百度百科上所说的“交差分类法”是个什么东西呢?我觉得这可能是“泛型”导致的。当我们研究一个对象集合的时候我们可以从多种角度观察和分类它,每一个研究角度可以看作分类法的一个型。 我后来思考了一下,百度百科上所说的交差分类法可能是指的这样:比如对于一个班级的学生这个学生对象集。按照性别分会是男生、女生;按照座位的行分会是第一排、第二排……最后一排;按照成绩分会是优等生、普通生、差等生(这里需要定义“成绩”和“比较大小”的算法,需要将成绩的整个值域划分为三类优等、普通、差等);但不管从哪个角度观察和分类,目标对象集都是这个班级的学生,一个学生可以同时是男生,且是差等生且坐在第二排。上面从性别、座位的排号、成绩三个角度对一个班级的学生进行了分类,也可以创造出一些新的分类“性别 x 排号”、“性别 x 成绩”、“性别 x 成绩 x 排号”等分类角度,这些新的分类角度可能就是百度百科上所说的交差分类。

用非形式化的文字表述和理解概念是很困难的,下面我们建立一个Category模型,如下:

public class Category {
    public Guid Id { get;set; }
    public string Code { get; set; }
    public string Name { get; set; }
}

public class CategoryType {
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
    public IReadOnlyCollection<Category> Items { get; set; }
}

上面两个模型一个是分类,一个是分类类型。伪代码用来说明模型的目录不考虑设计模式。 那么对人类对象集按照性别分类就是

var humanCategoryBySex = new Category {
    Id = new Guid(“EC47866B-7E69-4092-A378-A0AB00BD43B0”),
    Code = “humanSex”,
    Name = “按照性别对人类对象集进行分类”,
    Items = new List<Category> {
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “01”,
            Name = “男”
        },
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “02”,
            Name = “女”
        },
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “03”,
            Name = “未知”
        },
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “09”,
            Name = “未说明”
        }
    }
}

对人类按照国籍进行分类就是:

var humanCategoryByNationality = new Category {
    Id = new Guid(“EC47866B-7E69-4092-A378-A0AB00BD43B0”),
    Code = “humanNationality”,
    Name = “按照国籍对人类对象集进行分类”,
    Items = new List<Category> {
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “cn”,
            Name = “中国”
        },
        new Category {
            Id = “08CAC9DE-C332-4C3C-A3F3-18F763F8D78D”,
            Code = “us”,
            Name = “美国”
        }
    }
}

一个对象至多关联一种分类方式的一个分类节点,不可能关联多个节点,关联多个节点的那种不叫分类。 停下!如果有人既拥有中国国籍又拥有美国国籍怎么办?这是个问题。对于分类法来说解决这个问题的方式是添加新的分类项:cn&us。将既有中国国籍又有美国国籍的人归类到新的编码为“cn&us”(随便什么编码,唯一即可)类别下。这就是分类法,它不分层级,它覆盖整个被分类的对象集,不存在归类不到某个分类项的子集(您可能见到过“男人”、“女人”、“其它”,为什么有其它?因为要覆盖整个人类集)。 系统字典——忘掉分类法。分类模型跟字典模型是完全一样的。通常,您的系统中有个“系统字典”模型,这个系统字典模型跟分类法模型是完全一样的,也就是说在实现分类模型的时候可以借助系统字典模型而无需添加新的模型。听到字典一词我们可能会跟离散扯上关系,跟分类一样字典项是离散的,但字典所建立起来的字典项取值可以涵盖整个对象集。比如金额看似不是字典类型的,但如果需要你也可以建立<小于1万,大于等于1万小于100万,大于100万>这样的字典项,这样的字典项覆盖了整个对象集。 如果您从书上看到过层次化的分类模型的话,请忘掉它,在Anycmd中层次化的分类法称目录。分类法是没有层次的。有层次的是分类法的子类:目录。

9. Privilege

Privilege的定义是这9种AC元素的任意两两组合,两元中的一元是“主体”,一元是“客体”,主体负责感知客体。区分主客体就是为这两两组合定义了方向。一共是(9 * 8)/(2*1) = 36 + 9 = 45种结果。

Function级Privilege

在功能级权限的这45种组合中只有一种组合最关键:(Account,Function),主体是Account,客体是Function。其余44种组合的目的最终都是为了得出这个主体为Account客体为Function的组合。 每一个组合实例称作一个Privilege,Privilege也是9种AC元素的一员,有一种组合比较特殊,它是(Privilege,Privilege),这种组合目的是组成Privilege的继承链条,类似面向对象中的继承。另外上面的36+9中的9种是(Account,Account)、(Catalog,Catalog)、(Role,Role)、(Group,Group)、(Function,Function)、(Menu,Menu)、(AppSystem,AppSystem)、(ResourceType,ResourceType)、(Privilege,Privilege)。 功能级的权限是常驻内存的。

数据级Privilege

进入数据权限的时候又多了一个元素,第10种元素是Entity或者叫资源记录,数据级权限是10种AC元素的组合。数据级权限和资源记录存储在相同的物理位置,随同对资源记录的访问一起加载进内存,用完后随时丢弃不会常驻内存。

(主体、客体、运动)三元组: 访问控制中应该是个三元组(主体、客体、运动),但是咱们的Privilege模型是个二元组。是这样的: 运动(Operation)是绑定在ResourceType上的操作列表。如果知道了Operation就知道了ResourceType。所以在功能级的权限中只需要(Subject, Operation)这两元就可以了。 而权限到了数据级的时候,由于Privilege是和资源记录一同存储在相同的物理位置的。等于Privilege和Object又合为一体了,所以还是两元组。 权限三元组可能是(主体、动词、客体)。而Operation不仅仅是动词,Operation = Verb + ResourceType。

运行时景象

按照rbac标准上的说法是能够完成任何功能级的权限控制的,那么多ac元素最终都是为了得到(account,function)组合,系统运行到一个受控的区域时什么都不管只认(account,function)组合,只问当前用户是否可以执行当前function。要鉴权只需要知道当前的account和当前正在试图访问的function分别是什么,其中account可以从UserSession中直接读取,而如何识别function相对曲折,对于asp.net mvc来说可以基于这样的约定:ControllerName=ResourceTypeCode,ActionName=FunctionCode,知道了ResourceType.Code和Function.Code就可以直接到FunctionSet中索引到function了。 上级是可以拒绝请求的,这种情况是通常的正常情况。上级做的是宏观管理,上级不需要去详细了解自己管辖的资源的具体每一个单元。比如一个消息到来了,这个消息请求解雇某个子公司的某个目录节点下的某个员工(当然脑子正常的总部不会下达这样的请求),那么这个请求不一定是会被执行的。消息来到我们这个节点,这条消息上必定带有这个员工的目录属性值,我们的系统首先索引这个目录的根目录节点的配置信息去判断这个根目录下的员工是否可以解雇,然而根节点往往说“我不知道,请询问下级节点”,于是权限引擎继续往下级询问,知道明确的得知是“允许还是不允许”不可能询问到叶子节点还不知道是允许还是不允许,因为系统全局会配置“末级隐式允许意为什么允许还是不允许?”。目录是棵树,遇到目录要爬树,爬上去再下来,上级节点的“显式允许和显式”都会重写任何子节点,只有上级节点不知道允许还是不允许的时候会继续往下级询问,一直到达具体的资源记录甚至具体的单元格值域的具体取值。 实体级的权限是什么?实体级的权限要和实体记录存储在一起,要始终尽量遵循一个原则——每一级的安全策略都要和自己本级实体存储在一起。那么实体级的安全控制其实没有任何特别的,实体资源记录上会附加一个“安全字段”,这个安全资源里存储的是这个实体的安全策略,这个实体的安全策略只有在访问到这个实体的时候才会需要这也是为什么安全策略要和实体一起存储在相同的物理位置。如果只是部分实体记录需要实体级的安全控制而其它部分不需要实体级的安全控制这个时候考虑根据从是否需要实体级安全控制的角度建立分类附加到实体资源记录上去,甚至还可以考虑将分类提升为目录从而将需要实体级安全控制的和不需要实体级安全控制的资源组织到分别组织到不同的安全控制目录节点去,目录是什么?目录就类似域,比如我访问腾讯的服务器是不会为百度的服务器带来任何压力的,因为它们在路由器上就已经分开了。 无论哪一级的安全策略,都是面向相同的主体,本级的资源,相同的动作列表来书写的。不同级的安全策略只有执行顺序不同没有结构上的任何不同。

不可控制人类

有一个问题Anycmd一直很纠结,就是:面向过程 => 面向对象 => 面向物理。物理化到一定程度 骇客帝国就出现了。网络空间和人类生活的空间早晚被物理化打通,一旦打通则网络空间的主体将会有机会控制我们的空间。不要说人类造不出胜过人类的智慧体,这只是早晚的事。如果我们的网络中出现了邪恶的主体怎么办? 比如这个邪恶的主体故意把一些不该运动到妻子主体系统中的信息运动到妻子主体系统中去,他/她故意让妻子看到丈夫的出轨记录。这给我们人类空间中的丈夫和妻子带来了痛苦,安全系统要防止这种事情发生! 如果一旦这种事情还是已经发生了,丈夫的出轨记录已经运动到妻子中去了,该怎么办呢?我想到了一个办法,控制人类:通过人类工程消除妻子和丈夫脑中的这部分数据,让这对夫妻恢复到出轨之前的状态(甚至一旦考虑到修复的成本大于从新建造的时候做出杀死这对夫妻然后重建他们的决定)。但是,但是控制人类是邪恶的!anycmd的目的是防止邪恶的主体控制人类,但是为了防止修复已经造成的伤害我们自己又不得不去干邪恶的事情去控制人类。这如何是好?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值