博客列表 - 第 5 页

共 87 篇文章

软考复习之机械硬盘

# 机械硬盘管理 ## 硬盘结构 硬盘内部是由许许多多的圆形盘片、机械手臂、 磁头与主轴马达所组成的 实际的数据都是写在具有磁性物质的盘片上头,而读写主要是通过在机械手臂上的磁头(head)来达成。 实际运行时, 主轴马达让盘片转动,然后机械手臂可伸展让磁头在盘片上头进行读写的动作。 另外,由于单一盘片的容量有限,因此**有的硬盘内部会有两个以上的盘片** ![](disk/Disktructure.jpg) 磁盘上的数据都存放于磁道上。<mark>磁道就是磁盘上的一组同心圆</mark>,**其宽度与磁头的宽度相同**。为了避免减小干扰,**磁道与磁道之间要保持一定的间隔**(inter-track gap),<mark>沿磁盘半径方向,单位长度内磁道的数目称之为道密度</mark>(道/英寸,TPI),最外层为0道。 <mark>沿磁道方向,单位长度内存储二进制信息的个数叫位密度</mark>。为了简化电路设计,**每个磁道存储的位数都是相同的,所以其位密度也随着从外向内而增加**。磁盘的数据传输是以块为单位的,所以<mark>磁盘上的数据也以块的形式进行存放。这些块就称为扇区</mark>(sector),**每个磁道通常包括10~100个扇区。同样为了避免干扰,扇区之间也相互留有空隙**(inter–sector gap)。柱面是若干个磁盘组成的磁盘组,<mark>所有盘面上相同位置的磁道组称为一个柱面</mark>(每个柱面有n个磁道);若每个磁盘有m个磁道,则该磁盘组共有m个柱面。 通常数据的读写会由外圈开始往内写 ## 磁盘容量 **磁盘的非格式化容量为<mark>Cn=w×3.14×d×m×n</mark>,其中w为位密度,d为最内圈直径,m为记录面数,n为每面磁道数。** 磁盘格式化后能够存储有用信息的总量。**<mark>存储容量=n×t×s×b</mark>,其中:n为保存数据的总盘面 数;t为每面磁道数;s为每道的扇区数;b为每个扇区存储的字节数。** ## 磁盘读取时间 磁盘的存取时间包括寻道时间和等待时间。<mark>寻道时间(查找时间,Seek Time)为磁头移动到目标磁道所需的时间</mark>(movabe–head disk),对于固定磁头磁盘而言,无须移动磁头,只需选择目标磁道对应的磁头即可。<mark>等待时间为等待读写的扇区旋转到磁头下方所用的时间</mark>。一般选用磁道旋转一周所用时间的一半作为平均等待时间。寻道时间由磁盘机的性能决定 <mark>磁盘的数据传输速率是指磁头找到地址后,单位时间写入或读出的字节数</mark>。<mark>R=TB/T</mark>,其中:TB为一个磁道上记录的字节数,T为磁盘每转一圈所需的时间,R为数据传输速率。

软考复习之内存管理

## 内存管理 ## 存储管理的概念 存储管理主要是指对内存储器的管理,负责对内存的分配和回收、内存的保护和内存的扩充。存储管理的目的是尽量提高内存的使用效率。 ## 单一连续区管理 在单道程序系统中,内存区域的用户空间全部为一个作业或进程占用。单一连续分配方法主要用于早期单道批处理系统。单一连续分配方法主要采用静态分配方法,为降低成本和减少复杂度,通常不对内存进行保护,因而会引起冲突使系统瘫痪。 ## 分区存储管理 分区存储管理包括固定分区、可变分区,其基本思想是把内存划分成若干个连续区域,每个分区装入一个作业运行。要求作业一次性装入内存,且分区内部地址必须连续。 ### 固定分区存储管理 固定分区分配方法是把内存空间固定地划分为若干个大小不等的区域,划分的原则由系统决 定。系统使用分区表描述分区情况。分区一旦划分结束,在整个执行过程中每个分区的长度和内存的总分区个数保持不变。 ### 可变分区存储管理 可变分区分配方法是把内存空间按用户要求动态地划分成若干个分区。随着进程的执行,剩余的自由区域会变得更小,这时需要合并自由区和存储拼接技术。合并自由区是将相邻自由存储区合并为单一自由区的方法;存储拼接技术也称碎片收集,包括移动存储器的所有被占用区域到主存的某一端。可变分区克服了固定分区分配方法中的小作业占据大分区后产生碎片的浪费问题。 ### 存储分配算法 常使用的4种存储分配算法介绍如下。 **首次适应算法:** 把内存中的可用分区单独组成可用分区表或可用分区自由链,按起始地址递增的次序排列。每次按递增次序向后找,<mark>一旦找到大于或等于所要求内存长度的分区</mark>,则结束探索,从找到的分区中找出所要求的内存长度分配给用户,并把剩余的部分进行合并。 **循环适应算法:** 上述首次适应法经常利用的是低地址空间,后面经常是较大的空白区,为使内存所有线性地址空间尽可能轮流使用到,<mark>每重新分配一次,都在当前之后寻找。</mark> **最佳适应算法:** 最佳适应算法是<mark>将输入作业放入主存中与它所需大小最接近的空白区中</mark>,使剩下的未用空间最小,该法要求空白区大小按从小到大次序组成空白区可用表或自由链。在进行分配时总是从最小的一个开始查询,因而找到的一个能满足要求的空白区便是最佳的一个。 **最差适应算法:** 分配时把一个作业程序放入主存中最不适合它的空白区,即<mark>最大的空白区(空闲区)内</mark>。 ### 交换与覆盖技术 覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法。覆盖技术主要用在早期的操作系统中,而交换技术则在现代操作系统中得到了进一步发展。 覆盖技术是一种解决小内存运行大作业的方法。-个作业中若干程序段和数据段可以不同时使 用,这样它们就可以共享内存的某个区域,再根据需要分别调入该区域,这个区域就称为覆盖区。将程序执行时并不要求同时装入主存的覆盖组成一组,并称其为覆盖段,这个覆盖段分配到同一个覆盖区。交换技术可以将暂不需要的作业移到外存,让出内存空间以调入其他作业,交换到外存的作业也可以被再次调入。交换技术与覆盖技术相比不要求给出程序段之间的覆盖结构。交换主要是在作业之间进行的,而覆盖则主要是在同一个作业内进行的。 ## 页式存储管理 分页的基本思想是把程序的逻辑空间和内存的物理空间按照同样的大小划分成若干页面,以页面为单位进行分配。在页式存储管理中,系统中虚地址是一个有序对(页号,位移)。系统为每一个进程建立一个页表,其内容包括进程的逻辑页号与物理页号的对应关系、状态等。 ## 段式存储管理 段式存储管理与页式存储管理相似。分段的基本思想是把用户作业按逻辑意义上有完整意义的段来划分,以段为单位作为内、外存交换的空间尺度。一个作业是由若干个具有逻辑意义的段(如主程序、子程序、数据段等)组成的。在分段系统中,容许程序(作业)占据内存中许多分离的分区。每个分区存储一个程序分段。这样,每个作业需要几对界限地址寄存器,判定访问地址是否越界也困难了。在分段存储系统中常常利用存储保护键实现存储保护。分段系统中虚地址是一个有序对(段号,位移)。系统为每个作业建立一个段表,其内容包括段号、段长、内存起始地址和状态等。状态指出这个段是否已调入内存,即内存起始地址指出这个段,状态指出这个段的访问权限。 ## 段页式存储管理 段页式管理是段式和页式两种管理方法结合的产物,综合了段式组织与页式组织的特点,根据程序模块分段,段内再分页,内存被划分成定长的页。段页式系统中虚地址形式是(段号、页号、位移)。系统为每个进程建立一个段,为每个段建立一个页表。段页式管理采用段式分配、页式使用的方法,便于动态连接和存储的动态分配。这种存储管理能提高内存空间的利用率。段页式虚拟存储管理结合了段式和页式的优点,但增加了设置表格(段表、页表)和查表等开销,段页式虚拟存储器一般只在大型计算机系统中使用。 ## 页面调度 如果选择的页面被频繁地装入和调出,这种现象称为"抖动",应减少和避免抖动现象。常用的页面调度算法有以下几种。 **最优(OPT)算法**。<mark>选择不再使用或最远的将来才被使用的页</mark>,难以实现,常用于淘汰算法的 比较。 **随机(RAND)算法**。<mark>随机地选择被淘汰的页</mark>,开销小,但是可以选中立即就要访问的页。 先进先出(First in First out,FIFO)算法,又称轮转法(RR)。选择在内存驻留时间最长的 页,似乎合理,但可能淘汰掉频繁使用的页。另外,使用FIFO算法时,在未给予进程分配足够的页面数时,有时会出现给予进程的页面数增多,缺页次数反而增加的异常现象。FIFO算法简单,可采用队列实现。 **最近最少使用(Least Recently Used缩写为LRU)算法**。<mark>选择离当前时间最近的一段时间内使用得最少的页</mark>。这个算法的主要出发点是,如果某个页被访问了,则它可能马上就要被访问;反之,如果某个页长时间未被访问,则它在最近一段时间也不会被访问。 另外,还有最不经常使用的页面先淘汰(LFU,least frequent used)、最近没有使用的页面先淘汰(NUR)、最优淘汰算法(OPT,optimal replacement algorithm)等。

软考复习之其他知识点

# 其他 ## 数据结构 ### 强连通分量 有向非强连通图的<mark>极大</mark>强连通子图,称为强连通分量 ### 霍夫曼编码 [霍夫曼(Huffman)压缩(文件压缩机制)](https://blog.csdn.net/Charlesix59/article/details/119975011) ## 计算机组成原理 - 立即寻址:指令的地址字段指出的不是操作数的地址,而是操作数本身 - 直接寻址:指令中的形式地址部分即为有效地址 - 间接寻址:指令中的形式地址不是操作数的地址,而是 “操作数地址的地址” - 隐含寻址:指令中不直接给出操作数地址,操作数地址通常隐含在操作码或某个(约定)寄存器中 - 寄存器寻址:指令中的形式地址直接指出寄存器的编号,操作数存储于寄存器中 - 寄存器间接寻址:指令中的形式地址为寄存器的编号,寄存器的内容是操作数的有效地址 - 基址寻址:指令中的形式地址与基址寄存器内容之和为有效地址。 - 变址寻址:指令中的形式地址与变址寄存器内容之和为有效地址。 - 相对寻址:有效地址为程序计数器*PC*的值与形式地址之和。 - 堆栈寻址: - [寻址方式_百度百科 (baidu.com)](https://baike.baidu.com/item/%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F/3210621) ## 软件工程 ### McCabe算法 `V(G)=m−n+2p` 其中,V(G) 是有向图 G 中的环路数,m 是图 G 中弧的个数,n 是图 G 中的结点数,p 是图 G 中的强连通分量个数 ### SCI EMM - L1:CMMI一级,完成级。在完成级水平上,企业对项目的 目标与要做的努力很清晰。项目的目标得以实现。因此,任务是完成了。 但是由于任务的完成带有很大的偶然性,企业无法保证在实施同类项目的时候仍然能够完成任务。企业在一级上的项目实施对实施人员有很大的依赖性。 - L2:CMMI二级,管理级。在管理级水平上,企业在项目实施上能够遵守既定的计划与流程,有资源准备,权责到人,对相关的项目实施人员有相应的培训,对 整个流程有监测与控制,并与上级单位对项目与流程进行审查。企业在二级水平上体现了对项目的一系列的管理程序。这一系列的管理手段排除了企业在一级时完成 任务的随机性,保证了企业的所有项目实施都会得到成功。 - L3:CMMI三级,定义级。在定义级水平上,企业不仅仅能够对项目的实施有一整套的管理措施,并保障项目的完成;而且,企业能够根据自身的特殊情况以及 自己的标准流程,将这套管理体系与流程予以制度化。这样,企业不仅能够在同类的项目上得到成功的实施,在不同类的项目上一样能够得到成功的实施。科学的管 理成为企业的一种文化,企业的组织财富。 - L4:CMMI四级,量化管理级。在量化管理级水平上,企业的项目管理不仅仅形成了一种制度, 而且要实现数字化的管理。对管理流程要做到量化与数字化。通过量化技术来实现流程的稳定性,实现管理的精度,降低项目实施再质量上的波动。 - L5:CMMI五级,优化级。在优化级水品上, 企业的项目管理达到了最高的境界。企业仅仅能够通过信息手段与数字数手段来实现对项目的管理, 而且能够充分利用信息资料,对企业在项目实施的过程中可能出现的次品予以预防。能够主动地改善流程,运用新技术,实现流程的优化 CMMI是英文Capacity Maturity Model Integrated的简称。 中文的译意是能力成熟度集成模型。CMMI是CMM模型的最新版本。早期的能力成熟度模型是一种单一的模型其英文缩写为CMM,较多地用于软件工程。 ### 数据流图 [数据流图(DFD)_溢出的vector的博客-CSDN博客_数据流图](https://blog.csdn.net/weixin_46694417/article/details/120588235) #### 数据流图平衡 [【软件工程】数据流图 ( 数据字典 | 数据流图平衡原则 | 父图与子图平衡 | 子图内平衡 | 数据流图绘制原则 )_韩曙亮的博客](https://blog.csdn.net/shulianghan/article/details/109276722) ### 统一过程 (RUP) 统一过程有四个阶段,每个阶段又有多个任务。 ### 软件专利 这篇博客讲的还挺简单、详细的 [软考中级软件设计师---知识产权(自用)_嘟嘟的程序员铲屎官的博客](https://blog.csdn.net/weixin_42753193/article/details/124991506) ### 风险管理 **风险识别** 可以用不同的方法对风险进行分类。从宏观上来看,风险可以分为项目风险、技术风险和商业风险。<mark>项目风险识别潜在的预算、进度、个人、资源、用户和需求</mark>方面的问题。<mark>技术风包括识别潜在的设计、实现、接口、检验和维护</mark>方面的问题。而商业风险则主要来源于市场。 风险识别的重要工作就是将潜在的风险找到,文档化。 **风险估计** 风险估计使用两种方法来估计每一种风险。**一种方法是估计其发生的可能性;另一种方法是估计它可能带来的破坏性**。然后根据这样的结果对其进行排列优先级,对于那种可能性大、破坏力也大的风险就应该更加重视,拟定相应的解决方案才能够有效地防范。 **风险驾驭** 风险驾驭是指利用某种技术,如原型化、软件自动化、软件心理学、可靠性工程学,以及某些项目管理方法等设法避开或转移风险。 ### 内聚的分类 内聚的种类由紧到松(越紧越好)依次为: 1. **功能内聚**:指模块内的所有元素共同作用完成一个功能,缺一不可。 2. **顺序内聚**:指一个模块中的各个处理元素都密切相关于同一功能且必须顺序执行,前一功能元素的输出就是下一个功能元素的输入。 3. **通信内聚**:指模块内所有处理元素都在同一个数据结构上。 4. **过程内聚**:指一个模块完成多个任务,这些任务必须按指定的过程执行。 5. **瞬时内聚**:把需要同时执行的任务或动作组合在一起(如初始化模块)。 6. **逻辑内聚**:模块完成逻辑上相关的一组任务。 7. **偶然内聚**:指一个模块内的各处理元素之间没有任何联系或有松散的联系。 ### 耦合的分类 耦合的种类从高到低(越低越好)依次为: 1. **内容耦合**:一个模块直接使用另一个模块的内部数据,或通过非正常入口转入另一个模块内部时,这种耦合关系就是内容耦合。 2. **公共耦合**:指一组模块访问一个公共数据环境,如全局数据结构。 3. **外部耦合**:指一组模块访问一个公共变量,这里指基本数据类型而不是数据结构(或者说对象)。 4. **控制耦合**:指一个模块调用另一个模块时,传递的是控制变量,被调用模块通过该控制变量的值选择执行模块内某一功能。那么也就是说,被调用的模块应具有多个功能。 5. **标记耦合**:耦合模块之间以数据结构传递(比如在 java 程序中,传递的就是一个对象)。 6. **数据耦合**:耦合模块之间有调用关系,传递的是简单数据类型的值(比如在 java 程序中,传递的就是一个基本数据类型的值)。 7. **无直接耦合**:指两个模块之间没有直接的关系,它们分别从属于不同模块的控制与调用,它们之间不传递任何信息。 ### 软件体系结构风格 ~~考得不多,但不是不考~~ [13种常见软件体系结构风格定义分析、结构图、优缺点_Jayphone17的博客](https://blog.csdn.net/Jayphone17/article/details/103651076) ## 操作系统 ### 进程分配图 [ 资源分配图化简法_coding1994的博客-CSDN博客](https://blog.csdn.net/coding1994/article/details/52474731) ### 硬盘位视图 [位示图_百度百科 (baidu.com)](https://baike.baidu.com/item/%E4%BD%8D%E7%A4%BA%E5%9B%BE/2475925?fr=aladdin) 一bit代表一个硬盘块 ### Flynn算法 [Flynn分类法 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/413778318) ### 实时系统 实时操作系统是保证在一定时间限制内完成特定功能的操作系统。 #### 分类 实时操作系统有硬实时和软实时之分,**硬实时要求在规定的时间内必须完成操作**,这是在操作系统设计时保证的;**软实时则只要按照任务的优先级,尽可能快地完成操作即可**。我们通常使用的操作系统在经过一定改变之后就可以变成实时操作系统。 #### 要求 - 多任务 - 处理能被区分优先次序的进程线 - 一个中断水平的充分数量 #### 特征 - 高精度计时系统 - 多级中断机制 - 实时调度机制 ## 计网 ### 实现IPv4到IPv6的通信 #### 双栈协议 在IPv6实现之前,使一部分主机装有双协议栈:一个IPv4和一个IPv6。经过IPv4网络时将IPv6报文头转化为IPv4报文头 #### 隧道技术 在IPv6报文将要进入IPv4网络的时候将IPv6数据报封装在IPv4数据报里面

软考复习之安全性措施

# 安全性措施 ## 加密算法 ### 术语 - [明文](https://baike.baidu.com/item/%E6%98%8E%E6%96%87?fromModule=lemma_inlink),即原始的或未加密的[数据](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE?fromModule=lemma_inlink)。通过[加密算法](https://baike.baidu.com/item/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95?fromModule=lemma_inlink)对其进行加密 - 密文,明文加密后的格式,是加密算法的输出信息。密文不应为无[密钥](https://baike.baidu.com/item/%E5%AF%86%E9%92%A5?fromModule=lemma_inlink)的用户理解,用于数据的存储以及传输; - 加密,把明文转换为密文的过程; - 加密算法,加密所采用的变换方法,[加密算法](https://baike.baidu.com/item/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95?fromModule=lemma_inlink)的输入信息为明文和[密钥](https://baike.baidu.com/item/%E5%AF%86%E9%92%A5?fromModule=lemma_inlink); - 密钥,是由数字、字母或特殊符号组成的字符串,用它控制数据加密、解密的过程;加密算法是公开的,[密钥](https://baike.baidu.com/item/%E5%AF%86%E9%92%A5?fromModule=lemma_inlink)则是不公开的 - 解密,对密文实施与加密相逆的变换,从而获得明文的过程; - 解密算法,解密所采用的变换方法。 ### 简介 数据加密是对明文按照某种加密算法进行处理,形成密文。这样一来,密文即使被截获,截获方也无法或难以解码,从而防止泄露信息。 - 秘密密钥加密体制K1=K2:加密和解密采用相同的密钥,因而又称为对称密码体制。因为加密速度快,通常用来加密大批量的数据。 - 公开密钥加密体制K1≠K2:又称不对称密码体制,其加密和解密使用不同的密钥;其中一个密钥是公开的,另一个密钥则是保密的。由于加密速度较慢,所以往往用在数据量较小的通信业务中。 ### 目的 - 提供高质量的数据保护,防止数据未经授权的泄露和未被察觉的修改; - 应具有相当高的复杂性,使得破译的开销超过可能获得的利益,同时又要便于理解和掌握; - 密码体制的安全性应该不依赖于算法的保密,其安全性仅以加密密钥的保密为基础; - 实现经济,运行有效,并且适用于多种完全不同的应用。 ### 具体算法 #### DES算法 **简介**: DES全称为Data Encryption Standard,即数据加密标准,是一种使用[密钥加密](https://baike.baidu.com/item/%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86/5928903?fromModule=lemma_inlink)的块算法 **参数:** DES算法的入口参数有三个:**Key、Data、Mode**。其中Key为7个字节共56位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。 **历史沿革**: 一般DES算法的密钥长度为56位,为了加速DES算法和RSA算法的执行过程,可以用硬件电路来实现加密和解密。针对DES密钥短的问题,科学家又研制了80位的密钥,以及在DES的基础上采用三重DES和双密钥加密的方法。即用两个56位的密钥K1、K2,发送方用K1加密,K2解密,再使用K1加密。接收方则使用K1解密,K2加密,再使用K1解密,其效果相当于将密钥长度加倍。 #### RSA算法 **简介** 在公开密钥密码体制中,**加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的**。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,但却不能根据PK计算出SK **过程** 先生成一对RSA密钥,**其中之一是保密密钥,由用户保存**;另一个为公开密钥,可对外公开,甚至可在网络服务器中注册。为提高保密强度,RSA密钥至少为500位长。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与[公开密钥加密](https://baike.baidu.com/item/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86/8090774?fromModule=lemma_inlink)方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要 **算法** 略,可参考[RSA算法_百度百科](https://baike.baidu.com/item/RSA%E7%AE%97%E6%B3%95) #### 其他算法 - 国际数据加密算法(IDEA)在1990年正式公布。这种算法是在DES算法的基础上发展起来的,类似于三重DES.发展IDEA也是因为感到DES具有密钥太短等缺点,IDEA的密钥为128位,这么长的密钥在今后若干年内应该是安全的。 - 1993年4月16日,美国政府推出了cipper密码芯片,该芯片采用美国国家安全局设计的Skipjack加密算法。采用Cipper的加密体制能为信息传输提供高等级的安全和保密,该体制是以防篡改硬件器件(Cipper芯片)和密钥Escrow(第三方托管)系统为基础的。 - 1994年2月14日,美国政府宣布了Escrow加密标准,其加密算法使用Skipjack.该算法采用80位密钥和合法强制访问字段(aw Enforcement Access Fied,EAF),以便在防篡改芯片和硬件上实现。由于使用了80位的密钥,Skipjack算法具有较高的强度。 | 名称 | 对称性 | 特点 | 密钥长度(通常) | |------|-----|--------------|----------| | DES | 对称 | 不够安全 | 56 | | RSA | 不对称 | 安全性高,速度慢 | 512 | | IDEA | 对称 | 速度快,密钥管理复杂困难 | 128 | ## 身份认证技术 数字签名用来保证信息传输过程中信息的完整和提供信息发送者的身份认证和不可抵赖性,该技术利用公开密钥算法对于电子信息进行数学变换,通过这一过程,数字签名存在于文档之中,不能被复制 ### 哈希签名 Hash签名不属于强计算密集型算法,应用较广泛。Hash的主要局限是接收方必须持有用户密钥的副本以检验签名,因为双方都知道生成签名的密钥,较容易攻破,存在伪造签名的可能。 #### MD5 MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的[密码散列函数](https://baike.baidu.com/item/%E5%AF%86%E7%A0%81%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B0/14937715?fromModule=lemma_inlink),**可以产生出一个128位(16[字节](https://baike.baidu.com/item/%E5%AD%97%E8%8A%82/1096318?fromModule=lemma_inlink))的散列值(hash value)**,用于确保信息传输完整一致。 ### RSA签名 RSA既可以用来加密数据,也可以用于身份认证。 RSA算法中数字签名技术实际上是通过一个Hash函数来实现的。数字签名的特点是它代表了文件的特征,文件如果发生改变,数字签名的值也将发生变化。不同的文件将得到不同的数字签名。 ### DSS算法 对数字签名和公开密钥加密技术来说,都会面临公开密钥的分发问题,即如何把一个用户的公钥以一种安全可靠的方式发送给需要的另一方。这就要求管理这些公钥的系统必须是值得信赖的。 所以,必须有一项技术来解决公钥与合法拥有者身份的绑定问题。假设有一个人自称某一个公钥是自己的,必须有一定的措施和技术来对其进行验证。 **数字证书**是解决这一问题的有效方法。它通常是一个签名文档,标记特定对象的公开密钥。**数字证书由一个认证中心(CA)签发**,认证中心类似于现实生活中公证人的角色,它具有权威性,是一个普遍可信的第三方。当通信双方都信任同一个CA时,两者就可以得到对方的公开密钥,从而能进行秘密通信、签名和检验。 #### 数字证书的基本工作原理: 第一,发送方在发送信息前,需先与接收方联系,同时利用公钥加密信息,信息在进行传输的过程当中一直是处于[密文](https://baike.baidu.com/item/%E5%AF%86%E6%96%87/9684333?fromModule=lemma_inlink)状态,包括接收方接收后也是加密的,确保了信息传输的[单一性](https://baike.baidu.com/item/%E5%8D%95%E4%B8%80%E6%80%A7/6153534?fromModule=lemma_inlink),若信息被窃取或截取,也必须利用接收方的[私钥](https://baike.baidu.com/item/%E7%A7%81%E9%92%A5/8973452?fromModule=lemma_inlink)才可解读数据,而无法更改数据,这也有利保障信息的完整性和安全性。 [3] 第二,数字证书的数据[签名](https://baike.baidu.com/item/%E7%AD%BE%E5%90%8D/2890277?fromModule=lemma_inlink)类似于[加密](https://baike.baidu.com/item/%E5%8A%A0%E5%AF%86/752748?fromModule=lemma_inlink)过程,数据在实施加密后,只有接收方才可打开或更改数据信息,并加上自己的[签名](https://baike.baidu.com/item/%E7%AD%BE%E5%90%8D/2890277?fromModule=lemma_inlink)后再传输至发送方,而接收方的[私钥](https://baike.baidu.com/item/%E7%A7%81%E9%92%A5/8973452?fromModule=lemma_inlink)具唯一性和[私密性](https://baike.baidu.com/item/%E7%A7%81%E5%AF%86%E6%80%A7/7896067?fromModule=lemma_inlink),这也保证了签名的[真实性](https://baike.baidu.com/item/%E7%9C%9F%E5%AE%9E%E6%80%A7/6345696?fromModule=lemma_inlink)和[可靠性](https://baike.baidu.com/item/%E5%8F%AF%E9%9D%A0%E6%80%A7/512935?fromModule=lemma_inlink),进而保障信息的安全性。 简单来说,发送方用**公钥**加密信息,接收方收到后用**接收方的私钥**解密

软考复习之软件需求

# 软件需求 软件需求是 (1)用户解决问题或达到目标所需条件或权能(Capability)。 (2)系统或系统部件要满足合同、标准、规范或其它正式规定文档所需具有的条件或权能。 (3)一种反映上面(1)或(2)所述条件或权能的文档说明。 它包括功能性需求及非功能性需求,非功能性需求对设计和实现提出了限制,比如性能要求,质量标准,或者设计限制。 ## 类型 - 业务需求(Business Requirments):组织或者客户高层次的目标,从宏观上描述开发系统的必要性、意义和目标,具有以业务为想到、可度量、合理、可行的特点。BR的核心部分是业务建模,对当前企业当前业务流程进行评估,并对新开发系统的业务处理流程进行展望。 - 用户需求(User Requirements):用户要求系统必须要完成的任务,即用户要系统做什么,产生什么业务价值。 - 系统需求(System Requirments):整个系统的顶级需求,由系统分析人员对UR进行分析、提炼、整理,从而生成指导开发的、更准确地软件需求。完整的表达了软件项目的预期特征,为接下来的软件设计和测试提供了依据和基础。 - 功能需求(Functional Requirements):规定开发人员必须在产品中要实现的软件功能 ### 非功能性需求 - 产品必须遵从的规范、标准和合约 - 外部界面的具体细节 - 性能需求 - 设计或实现的约束条件及质量属性 ## 过程标准 清楚(Clear)、完整(Complete)、一致(Consistent)、可测试(Testable)。 此外还有其他的概念,如可跟踪的、可修改的等等

修复fluid的tags中的词云tag跳转异常问题

# 修复fluid的tags中的词云tag跳转异常问题 ## 前言 最近在尝试搭建自己的blog,再再三考量之下选择了**hexo**,不得不说这个blog框架还是很香的,配合github的托管能够快速搭建一个界面美观的博客。 但是在使用的过程中我发现了一个小问题,就是博客的tags并不能正常工作。 当我们从首页中的tag跳转时,可以跳转到正常的界面,如图: ![](../../essay/prose/imperial_edict/img1.png) <font color=red>但是当我从tags界面的词云中跳转时就会出现错误</font> ![](fluid-tags-bug-fix/img2.png) 错误界面如图: ![](fluid-tags-bug-fix/img3.png) <!-- more --> ## 解决方法 <font color=red>我们可以很明显的观测到问题——url地址错误,重复了一次'tags/'目录</font> 我们首先说解决方案: <font color=red>找到目录`<your hexo dirctory>\node_modules\hexo\lib\plugins\helper`中的tagcloud.js文件,将文件第71行改为:</font> ```javascript `<a href="${url_for.call(this, tag.path).substring(5)}" style="${style}"${attr}>${transform ? transform(tag.name) : tag.name}</a>` //原代码为: //`<a href="${url_for.call(this, tag.path))}" style="${style}"${attr}>${transform ? transform(tag.name) : tag.name}</a>` //加上了一个子字符串分割 ``` ***注意!这个方法只适合跟我描述的错误相同的朋友,如果问题不同请不要随意更改!*** ## 详细解释 这一部分讲述我怎么分析并解决问题的,如果不感兴趣可以直接关掉网页了 首先发现到这一点后,我企图弄清楚fluid是如何生成tags这个html文件的。但是我观察文件夹时并没有和明显的察觉到这一点。于是我找到`public`文件夹,**这个文件夹存放的是通过hexo生成的html文件。** 然后我们找到`tags`文件夹下的`index.html`   文件,随着翻阅html文件,我发现了这样的一块代码: ```html <div class="text-center tagcloud"> <a href="tags/node-js/" style="font-size: 15px; color: #bbe">node.js</a> <a href="tags/前端/" style="font-size: 15px; color: #bbe">前端</a> </div> ``` 毫无疑问,这就是生成词云的代码,但是我已经生成为html的文件我们并不能直接改,因为如果要这样解决问题,每次生成新的html之后我们都要手动修改一次,这是很没有效率的。于是我们顺藤摸瓜继续寻找问题。 于是我在webstrom使用<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>r</kbd>全局查找tagcloud,发现在fluid的layout中有一个名为`tags.ejs`文件,这显然是生成tags的文件之一。其中有这样的一块代码: ```ejs <div class="text-center tagcloud"> <%- tagcloud({ min_font: min_font, max_font: max_font, amount: 999, unit: unit, color: true, start_color, end_color }) %> </div> ``` 很明显,这是生成上面那个html的映射文件,但是这其中依然没有修改herf的方法。于是我们继续向源头寻找。 然后我们发现tagcloud包是被引用进来的,而他的源码就在我们面提到过的文件夹中。 ```javascript `<a href="${url_for.call(this, tag.path)}" style="${style}"${attr}>${transform ? transform(tag.name) : tag.name}</a>` ``` 这一行很明显就是生成每个小tag的代码。虽然我们看不太懂`${url_for.call(this, tag.path)}`这行代码的意思,但是我们知道最终他都会被解析为字符串。如果你熟悉js,就会知道js拥有将所有东西转化为字符串的能力😂因此我们直接使用substring方法来更改这一行代码就可以解决问题了

修復hexo fluid归档、分类、标签界面查看文章404错误

# 修復fluid归档、分类、标签界面查看文章404错误 ## 前言 上次遇到了[tag跳转的问题](https://charlesix59.github.io/2022/09/01/fluid-tags-bug-fix/)之后,我又遇到了这个奇怪的问题。按照按图索骥的原则,我又用那篇文章中提到过的方法进行了纠错,最终也是顺利的解决了这个bug。 错误如图: ![](../../essay/prose/imperial_edict/img1.png) <!-- more --> ## 解决方案 <font color=red>还是和以前一样,先说结论</font> <font color=orange>警告⚠:如果你的错误和我的不同,请不要随意修改</font> 首先找到fluid主题的目录中的`<your fluid dir>/layout/_partials` 打开`category-list.ejs`文件 修改第**45行**为 ```html <a href="../<%= url_for(cat.path) %>" style="text-align: center" class="list-group-item list-group-item-action"> ``` 修改第**50**行为 ```html <a href="../<%= url_for(post.path) %>" title="<%= post.title %>" ``` 然后打开`archieve-list.ejs` 修改第10行为 ```html <a href="../<%= url_for(post.path) %>" class="list-group-item list-group-item-action"> ``` 然后**复制**整个文档,**新建**一个名为`sub-archive-list.ejs`(名字可以自定)的文件,将复制的内容**粘贴**过去 并修改第10行为 ```html <a href="../../<%= url_for(post.path) %>" class="list-group-item list-group-item-action"> ``` 然后回到上一级目录,找到`tag.ejs`,修改第12行引用的文件为你新建的那个文件: ```ejs <%- partial('_partials/sub-archive-list.ejs', { params: { key: page.layout, postTotal: tag ? tag.posts.length : 0 } }) %> ``` 同时如上修改`category.ejs`中的第12行为: ```ejs <%- partial('_partials/sub-archive-list.ejs', { params: { key: page.layout, postTotal: cat ? cat.posts.length : 0 } }) %> ``` ## 其他 我不知道是不是因为我配置不完全或者安装出现问题才导致有这么多的bug,但是随着我的重装这个问题也没有得到很好的解决。 如果你也遇到了相似的问题可以尝试用这种方法解决 如果你对详细的解决思路感兴趣,你可以查看[前言](#前言)中提到的文章

认识与修改hexo的paginator(分页)系统

# 认识与修改hexo的pagintor(分页)系统 ## 前言 为啥我会出这篇博客呢?不出意外的当然是又出意外了。我的分页系统出现了一点问题。 当然,它可能从最开始就有一点问题,只不过是随着页面的增加我才渐渐注意到而已。 问题就是它会跳到当前页面+当前页面+分页,比如在archives界面点击第二页就会跳转到 `domain/archives/archives/page/2` 这个界面。这很明显是错误的 为了修正这个问题我又是对着各种源码一顿查找。 <!-- more --> ## 修改方案 老规矩,先贴出来修改方案。如果问题和我不同的就不要跟着做了。 首先找到我们需要修改的文件夹 `<your hexo directory>/node_modules/hexo/lib/plugins/helper/pagintor` 将第八行更改为 ```javascript return i => '/' + url_for.call(ctx, i === 1 ? base : base + format.replace('%d', i)); ``` 同时要保证在hexo目录中的配置文件`_config.yml`中主目录的地址路由为'' 即`index_generator`中的`path`属性的值为`''` ```yaml index_generator: path: '' per_page: 10 order_by: -date ``` ## 详解paginator 我觉得我直接贴注释就好 ```javascript 'use strict'; const { htmlTag, url_for } = require('hexo-util'); //这个函数就是用来创建链接的,它决定了路径的内容 const createLink = (options, ctx) => { const { base, format } = options; //加一个'/'是为了能够返回主目录 return i => '/' + url_for.call(ctx, i === 1 ? base : base + format.replace('%d', i)); }; //这个函数生成html界面的内容,具体说就是界面的按钮(数字加链接) const createPageTag = (options, ctx) => { const link = createLink(options, ctx); const { current, escape, transform } = options; return i => { if (i === current) { return htmlTag('span', { class: 'page-number current' }, transform ? transform(i) : i, escape); } return htmlTag('a', { class: 'page-number', href: link(i) }, transform ? transform(i) : i, escape); }; }; //为生成的所有的按钮进行排序与包装 const showAll = (tags, options, ctx) => { const { total } = options; const pageLink = createPageTag(options, ctx); for (let i = 1; i <= total; i++) { tags.push(pageLink(i)); } }; const pagenasionPartShow = (tags, options, ctx) => { const { current, total, space, end_size: endSize, mid_size: midSize } = options; const leftEnd = Math.min(endSize, current - 1); const rightEnd = Math.max(total - endSize + 1, current + 1); const leftMid = Math.max(leftEnd + 1, current - midSize); const rightMid = Math.min(rightEnd - 1, current + midSize); const spaceHtml = htmlTag('span', { class: 'space' }, space, false); const pageTag = createPageTag(options, ctx); // Display pages on the left edge for (let i = 1; i <= leftEnd; i++) { tags.push(pageTag(i)); } // Display spaces between edges and middle pages if (space && leftMid - leftEnd > 1) { tags.push(spaceHtml); } // Display left middle pages for (let i = leftMid; i < current; i++) { tags.push(pageTag(i)); } // Display the current page tags.push(pageTag(current)); // Display right middle pages for (let i = current + 1; i <= rightMid; i++) { tags.push(pageTag(i)); } // Display spaces between edges and middle pages if (space && rightEnd - rightMid > 1) { tags.push(spaceHtml); } // Display pages on the right edge for (let i = rightEnd; i <= total; i++) { tags.push(pageTag(i)); } }; //这是函数的入口,它接受option参数 function paginatorHelper(options = {}) { options = Object.assign({ base: this.page.base || '', //文章的路径 current: this.page.current || 0, //当前页 format: `${this.config.pagination_dir}/%d/`, //待格式化的page路径 total: this.page.total || 1, //总页面 end_size: 1, mid_size: 2, space: '…', next_text: 'Next', //下一个界面的信息 prev_text: 'Prev', //上一个界面的信息 prev_next: true, escape: true }, options); const { current, total, prev_text: prevText, next_text: nextText, prev_next: prevNext, escape } = options; if (!current) return ''; const link = createLink(options, this); const tags = []; // Display the link to the previous page if (prevNext && current > 1) { tags.push(htmlTag('a', { class: 'extend prev', rel: 'prev', href: link(current - 1)}, prevText, escape)); } if (options.show_all) { showAll(tags, options, this); } else { pagenasionPartShow(tags, options, this); } // Display the link to the next page if (prevNext && current < total) { tags.push(htmlTag('a', { class: 'extend next', rel: 'next', href: link(current + 1) }, nextText, escape)); } return tags.join(''); } module.exports = paginatorHelper; ``` ## 碎碎念 本来以为分页应该也是在ejs中生成的,我只需要修改ejs文件即可。 可是万万没想到hexo的分页是直接通过一个插件的函数生成html内容然后显示上的 所以这个改的还挺艰难的,差点给我愁死😭

IndexDB游标的正确打开方式

# IndexDB游标的正确打开方式 ## 引言 我最近在维护我的玩具项目`react-door`,当我试图使用IndexDB的游标遍历某个表时,我遇到了一些让人十分困惑的问题,下面我将带大家来复现一下事故的现场。 ## 复现 首先,在我的印象中,游标类似迭代器,所以刚开始我试图拿到游标传递出去然后在dao层调用: ```js // indexDB.js // 获取cursor const getCursor = () => { if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor(); return new Promise((resolve, reject) => { request.onsuccess = (e) => { const cursor = e.target["result"] resolve(cursor) } request.onerror = (e) => { reject(e) } }) } // in dao.js async function someFunc() { const cursor = await getCursor(); if (cursor) { // do something cursor.continue() } else { console.log("no more data") } } ``` 一开始我是这么设想的,但是我发现了一个令人特别疑惑的问题:为什么在MDN的文档中,使用`if...else...`来遍历这个cursor呢? ```js if (cursor) { var listItem = document.createElement("li"); listItem.innerHTML = cursor.value.albumTitle + ", " + cursor.value.year; list.appendChild(listItem); cursor.continue(); } else { console.log("Entries all displayed."); } // 来自MDN,可以看到使用 if else 进行遍历 ``` 当时我也是借鉴MDN,使用`if...else...`来遍历,但是我发现我的代码只能运行一次,这肯定不叫遍历啊。**谁家好人用`if...else`做遍历啊???** 年轻人不气盛能叫年轻人吗?这肯定是MDN写错了!根据我的经验,立刻给他改写成了这样: ```js async function someFunc() { const cursor = await getCursor(); // 从 if 语句变为 while 语句 while (cursor) { // 猜猜会输出什么? console.log(cursor.key); // do something cursor.continue() } console.log("no more data") } ``` 看起来没什问题,非常正确! **但是** ,在我实际运行这段代码的时候,并不能正常运行。我们希望它的输出是`1 2 3 4 5 ....`,而实际的输出结果却是`1 1 1 1 1 ...`。为啥会出现这种结果呢?😥本来我以为是`cursor.continue()`方法是异步的,但是结果这是一个完完全全的同步函数。怎么回事呢? 如果说`continue`方法不起作用的话,但是它确实能够帮我们将cursor向前移位,并在cursor结束时结束循环(虽然报了一个info),但是如果说它起作用的,我们的cursor每次都指向第一条数据。这是怎么回事呢??? 难道IndexDB这个方法还有bug?不可能吧?*难道真的是使用`if...else`遍历*?于是我再次查阅MDN,现在我为大家贴上MDN完整的代码段: ```js function displayData() { var transaction = db.transaction(["rushAlbumList"], "readonly"); var objectStore = transaction.objectStore("rushAlbumList"); objectStore.openCursor().onsuccess = function (event) { var cursor = event.target.result; if (cursor) { var listItem = document.createElement("li"); listItem.innerHTML = cursor.value.albumTitle + ", " + cursor.value.year; list.appendChild(listItem); cursor.continue(); } else { console.log("Entries all displayed."); } }; } ``` 经过我的观察,难道...`cursor只能在openCursor的回调中使用??` 带着这个猜想,我将我的代码改为这样: ```js if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor(); return new Promise((resolve, reject) => { request.onsuccess = (e) => { const res = [] const cursor = e.target["result"] if (cursor) { res.push(cursor.value) cursor.continue() } else { resolve(res) } } request.onerror = (e) => { reject(e) } }) ``` 嗯,果然不出所料,现在我们已经可以正确的遍历cursor了!但是又有一个问题随之而来,为什么每次我的res只有一个数据😅?经过我的一番思考,一个很恐怖的想法闪现出来,沃日,indexDB你不会....**每次游标移位都要调用onSuccess回调吧**? 于是我们再次改写代码,写成这样: ```js if (!db) { return Promise.reject("database connect instance can't be null") } const store = db.transaction(storeName, 'readonly').objectStore(storeName) const request = store.openCursor(); // 将res放到回调函数之外 const res = [] return new Promise((resolve, reject) => { request.onsuccess = (e) => { const cursor = e.target["result"] if (cursor) { res.push(cursor.value) cursor.continue() } else { resolve(res) } } request.onerror = (e) => { reject(e) } }) ``` 这时,我们终于如愿以偿的拿到了我们需要的数据(真是辛苦啊😭) ## 总结 现在我们来总结一下cursor的坑: - cursor必须配合`oepnCursor`函数的回调函数一起使用 - 每次调用`cursor.continue()`并不是简单的将cursor移位,而是请求IDB的API更新cursor并再次触发`openCursor`的回调函数 所以上面那一版代码就是使用cursor遍历的标准模板,因为这个过程是类似递归的过程,所以我们的cursor是真的使用`if...else...`遍历代码😥,没毛病哦兄弟们

使用node.js创建一个todo列表——node.js服务器搭建、json读写以及使用pm2保持服务运行

# 使用node.js创建一个todo列表——node.js服务器搭建以及json读写 ## 前言 前些日子学习了 nodejs ,顺理成章的想要找点东西练练手。恰好最近需要一个简介的todo list网页,因为之前用的一些todo list应用都被墙了,访问速度感人,于是就想自己搞一个todo。 而恰好,我要搞的todo是一个非常简单的程序,非常适合用nodejs这样的解释型脚本语言来实现,比起java这样光搭框架就要半天的专注大型项目的语言,nodejs 的优势就是非明显了。 <!-- more --> nodejs搭建服务器只需要去nodejs官网复制这样的一块代码: ```javascript const express = require('express') const app = express() app.get('/', (req, res) => { res.send('Hi!') }) app.listen(3000, () => console.log('Server ready')) ``` 所以我们可以把更多的经历放在我们的业务实现上。 ## 准备工作 ### 需求分析 在开始动工之前,要先列出需求。当时我的需求如下: - 添加需要做的事情 - 展示需要做的事情 - 完成需要做的事情 - 查看曾经完成的事情 ### 解决方案分析 对于上述需求,我打算这样实现: - 使用json文件系统实现持久化操作 - 使用nodejs来获取数据 - 使用nodejs来处理数据并储存数据 这样的话。明确了需求与实现方式,我们就可以动工了。 ## 实现 ### json读写 nodejs提供了一套文件的读写的模块:`fs`。使用fs可以很简单的实现文本文件的读写。但是为了我们在开发的时候更加方便,我又将他封装成了一个专注json读写的方法: ```javascript //in jsonHandler.js const fs = require("fs"); function test(){ console.log("Hello world!") } function readJson(name){ let jsonFile = fs.readFileSync("./"+name); let toDoList = JSON.parse(jsonFile); //解析json,并直接返回json对象 return toDoList; } function writeJson(name,data){ fs.writeFileSync("./"+name,JSON.stringify(data)); } module.exports = { readJson, writeJson } ``` ### 服务器搭建 还记得我们的服务器的格式吗?我们先定义一下我们的服务器接口: ```javascript //in index.js const jh = require("./jsonHandler") const http = require('http'); const url = require('url'); const util = require('util'); let workList = jh.readJson("list.json"); const port = 3000 const server = http.createServer((req, res) => { //设置响应头与请求编码 res.statusCode = 200 //设置字节流编码为utf-8 res.setHeader('Content-Type', 'text/plain;charset=utf8') //设置允许跨域 res.setHeader('Access-Control-Allow-Origin','*') req.setEncoding('utf8'); //write your code here let ret=""; //这是需要返回的数据 res.end(ret) }) server.listen(port, () => { }) ``` 然后根据我们的需求分析,先来确定以下API接口: - `addWork()`:用来添加一个任务并将数据保存到json - `deleteWord(res)`:用来将一个任务删除并将数据保存到json - `writeHistory(work)`:将完成的任务添加到历史记录中 - `getAllWork()`:获取目前正在运行中的任务 首先我们先开始addWork()的编写,要添加的话,首先要接受并解析客户端发送的请求,然后再储存到json中,代码如下: ```javascript //用来增加一个任务 function addWork(){ let work=""; req.on('data', chunk => { //接受客户端发送的请求流 //这里要取子串的原因是请求会带key,我这里是work=xxx,所以要将'work='去掉 work=chunk.toString().substring(5); //设置编码,不然中文会乱码 work = decodeURIComponent(work); //解析worklist let jsonObj=eval(workList) jsonObj.push({"work":work}) //将数据写回 jh.writeJson("list.json",jsonObj) }) } ``` 任务历史与添加任务几乎同理,只不过这个方法获取的数据是deleteWork提供的,我们不需要再去监控客户端的请求。代码如下: ```javascript //任务历史 function writeHistory(work) { let jsonStr = jh.readJson("history.json"); let jsonObj = eval(jsonStr); let event = {} event.work = work; event.finishTime =new Date(); jsonObj.push(event); jh.writeJson("history.json",jsonObj) } ``` 删除任务也是同理,我们要在前端给每个任务隐性的标上index,然后就可以用来删除了: ```javascript //删除任务 function deleteWork(res){ req.on('data', chunk => { console.log(`可用的数据块: ${chunk}`) //获取点击事件的位置 let index=chunk.toString().substring(3); let jsonObj=eval(workList) //添加历史 let work = jsonObj[index].work writeHistory(work) //删除 jsonObj.splice(index,1); // console.log(jsonObj) jh.writeJson("list.json",jsonObj) }) } ``` 获取任务就非常简单了,我们只需要从json中拿到数据并转发给客户端就可以了: ```javascript //获取所有任务 function getAllWork(){ workList=jh.readJson("list.json"); ret=JSON.stringify(workList); } ``` 最后呢,我们需要写一个路由解析,因为js非常小,所以这些东西都写在一个文件里面就可以: ```javascript /解析路由 let pathname = url.parse(req.url).pathname; //路由配置 if (pathname==="/getAllWork"){ getAllWork() } else if(pathname==="/addWork"){ addWork() } else if(pathname==="/deleteWork"){ deleteWork() } ``` 随下贴出前端源码: ```html <-- in index.html --> <!DOCTYPE html> <html lang="ch"> <head> <link rel="stylesheet" type="text/css" href="https://cdn.simplecss.org/simple.css"> <link rel="stylesheet" type="text/css" href="technological.css"> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body class="tech-background" style="text-align: center"> <p> wish you a substantial day</p> <p>to do list</p> <label> <input name="add-work" id="add-work" class="tech-input" style="width: 70%"> <button onclick='addWork()' class="tech-btn" style="display: inline;">增加</button> </label> <div id="container"></div> <script> 'use strict' $.ajax({ url:"http://localhost:3000/getAllWork", method:"GET", crossDomain: true, success:(res)=>{ let con=document.getElementById("container"); let workList=JSON.parse(res) console.log(workList) workList=eval(workList) for (let i=0;i<workList.length;i++){ let list=document.createElement("label"); list.innerHTML="<input name=\"work-list\" id=\"work"+i+"\" value='"+workList[i].work+"' class='tech-input' style=\"width: 70%\"> " + "<button onclick='deleteWork(this)' id='"+i+"' class='tech-btn' style=\"display: inline;\">完成</button>"; con.appendChild(list); } } }) function addWork(){ let work=document.getElementById("add-work").value; $.ajax({ url: "http://localhost:3000/addWork", method: "POST", crossDomain: true, data:{ "work":work }, success:(res)=>{ // alert("success"); location.reload(); } }) } function deleteWork(res){ let index=res.id; $.ajax({ url: "http://localhost:3000/deleteWork", method: "POST", crossDomain: true, data:{ "id":index }, success:(res)=>{ // alert("success"); location.reload(); } }) } </script> </body> </html> ``` 这个项目的源码我也会放在GitHub(因为项目实在太小我甚至都没有建一个文件夹) [项目地址](https://github.com/charlesix59/todo_list_by_nodeJS) ## 保持代码运行 我们服务器代码当然要有一个服务器去运行,而这个迷你的项目显然不值得让我们去分配他一个云服务器加一个域名,那么我们是否可以把这个服务运行在本地呢? 可是运行在本地就需要频繁的重启服务,每次开机都要启动一次,这未免也太麻烦了。于是我们就可以使用**pm2**来使我们的项目保持运行 关于pm2我在此不多赘述了,大家有兴趣的可以自行百度搜索,我只给出部署的命令: ```shell npm install pm2 -g #全局安装pm2 cd <项目目录> pm2 start index.js --watch pm2 save pm2 list ``` 如果你部署成功,那么当你运行`pm2 list`之后你就可以看到你的项目了

flex-direction为row时主动换行

# flex-direction为row时主动换行(React native主动换行) 我今天在写`react-native`时候,遇到了很不舒服的一件事情。因为RN默认是`flex`布局,所以如果我想要实现`inline`模式就只能设置父元素的`flex-direction`的value为`row`。这样如果我们如果想要换行需要怎么处理呢? 要知道,在RN中是没有`<br>`标签的,所以要想换行需要采取一些非常的手段。 那有没有什么好办法能够做到主动换行呢? 在正式开始之前我先上一个例子: ```jsx const arr = [1, 2, 3, 4, 5, 6]; export default function App() { return ( <SafeAreaView style={styles.container}> <View style={{ flexDirection: 'row' }}> {arr.map((item, index) => ( <View> <Text key={index}>{item}</Text> </View> ))} </View> </SafeAreaView> ); } ``` 很明显,上述的代码会生成一个纵向排列的`123456`,如果我们希望在**每三个断一下**,即将纵向的`123456`改成横向的`123 <br> 456`应该如何做呢? 刚开始的时候我问了ChatGPT,他给出的答案是使用伪类撑开元素: ```html <div class="flex-container"> <div class="flex-item">1</div> <div class="flex-item break-after">2</div> <!-- 想要在这个元素后换行 --> <div class="flex-item">3</div> <div class="flex-item">4</div> </div> ``` ```css .flex-container { display: flex; flex-wrap: wrap; flex-direction: row; } .flex-item { width: 100px; margin: 5px; } .break-after::after { content: ""; flex-basis: 100%; } ``` 经过我的尝试,这种方法很遗憾的不起任何作用,而且在RN中使用CSS class并不是很容易。但是他给出的代码确实给了我一些启发,经过一些摸索,我发现**如果一次渲染两个元素,其中一个是正常元素而另一个负责撑开元素**,那么就可以实现换行,代码如下: ```jsx const arr = [1, 2, 3, 4, 5, 6]; export default function App() { return ( <SafeAreaView style={styles.container}> <View style={{ flexDirection: 'row', flexWrap: 'wrap', width: 200 }}> {arr.map((item, index) => { if (item % 3 !== 0) { return <Text key={index}>{item}</Text>; } else { return ( <> {[ <Text key={index}>{item}</Text>, <View style={{ width: '100%' }}></View>, ]} </> ); } })} </View> </SafeAreaView> ); } ``` 但是很抱歉,这样依旧有缺陷,因为reactNative中`<></>`实际上会影响继承,即Text和View将会被看作一个整体,而不会被解析成两个单独的元素,**并且`return`中的`jsx fragment`必须有一个根节点**,也就说说这种方法与顶上charGPT给出的方法并没有区别,都无法将一个元素完美换行。这只会让我们需要换行的元素变为单独的一行而已。 那么,还有什么方法呢? ## 完美的换行 这时候我们或许需要更新一下思路,如果在渲染过程中无法做到完美的换行,那么我们为什么不能在数据上动一点手脚呢?经过一系列思路的转换,我突然想到我们完全可以**进行一个数组分割+元素嵌套的方式实现换行**,代码如下: ```jsx const arr = [1, 2, 3, 4, 5, 6]; export default function App() { const [arrState, setArrState] = useState([]); useEffect(() => { const result = []; let tempArr = []; arr.forEach((item) => { tempArr.push(item); if (item % 3 === 0) { result.push(tempArr); tempArr = []; } }); setArrState(result); }, []); return ( <SafeAreaView style={styles.container}> {arrState.map((subArr, index) => ( <View key={index} style={{ flexDirection: 'row', flexWrap: 'wrap', width: 200 }}> {subArr.map((item, subIndex) => { return <Text key={subIndex}>{item}</Text>; })} </View> ))} </SafeAreaView> ); } ``` 终于,我们实现了完美的换行! so,如果你也想要在flex布局中尝试主动换行,那么希望这篇文章能够解答你的疑惑。 当然,如果你有更好的方法,非常希望能在下方评论区和我分享你的好主意🤗