精华内容
下载资源
问答
  • 大公司内部的标准开发文档模板,包含项目研发过程文档,项目管理过程文档,机构支撑过程文档,需求文档模板、设计文档模板、编码实现文档,缺陷跟踪文档,质量保证文档,等等,又全,又好,又规范。下载地址:...

    大公司内部的标准开发文档模板,包含项目研发过程文档,项目管理过程文档,机构支撑过程文档,需求文档模板、设计文档模板、编码实现文档,缺陷跟踪文档,质量保证文档,等等,又全,又好,又规范。
    下载地址:http://zy41796745.download.csdn.net/

    展开全文
  • 目前商密认证的企业集中反馈问题比较多的是密码模块分级检测申请材料的编写。里面抽象的概念和送检产品本身如何对应,如何回答材料中的安全要求,甚至如何对产品分类等等。这些问题确实是我们面对的第一道关口,关系...

    前言

    目前商密认证的企业集中反馈问题比较多的是密码模块分级检测申请材料的编写。里面抽象的概念和送检产品本身如何对应,如何回答材料中的安全要求,甚至如何对产品分类等等。这些问题确实是我们面对的第一道关口,关系后面整体材料的方向。受限于时间和精力,这篇文档不可能面面俱到,我们只能简单分析出问题比较多的点。后续我会定期更新同类型的产品所对应的密码模块要求,以及各类产品涉及的规范要求。如有商密认证问题或需要可站内留言或联系微信号15011462285。

    材料编写说明

    总体说明

    商用密码产品认证,在提交认证申请时,首先要同步提交密码模块分级检测材料。该材料立足于GM/T 0028-2014《密码模块安全技术要求》和GM/T 0039-2015《密码模块安全检测要求》。由于这两个规范里面都是抽象的概念,对于初次编写这个材料的人来说存在一定的困难。

    本文的目的是为初次编写《密码模块分级检测申请材料》的人提供基本的入门指引。虽不能对每种类型的产品都能具体举例,但编写文档时用到基本的概念、编写思路和关键点会重点介绍。

    总体而言,《密码模块分级检测申请材料》编写者需将送检产品与GM/T 0028和GM/T 0039对应,从密码规格、接口、角色服务与鉴别、软件固件安全、运行环境、物理安全、非入侵式安全、敏感安全参数管理、自测试、生命周期保障、对其他攻击的缓解等共11个域分析产品情况,是否满足密码模块相关等级的安全要求。

    密码模块的整体评级为11个域中对应的最低评级。因此对于一个产品,应提前熟悉规范,确定产品能达到的评级(安全等级)。在编写材料时超出自身安全等级的不必关注,但产品达不到该安全等级要求的需及时调整,避免被检测降级。总体而言,目前通过商密检测的产品多数为安全一级和安全二级,极个别为三级,目前没有四级。在密评时会对商密产品的安全等级进行要求,目前来说具备硬件的产品应尽量按照安全二级来申报。

    《密码模块分级检测申请材料》中采用“安全要求+产品实现情况”形式实现对产品的安全评估。其中安全要求采用<CYxx.xx: 安全要求>(安全级别)的形式,在文档模板中已经写好,编写者不要修改;产品实现情况为产品针对安全要求的具体实现,由编写者编写。材料应回答安全要求中提出的问题,描述产品实现方式,最终得出产品符合安全要求的结论。在编写时应尽量避免出现“抄规范”或者直接“写结论”这两种情况。

    下面的章节会选取部分重要安全要求进行解读并附示例。详细内容和示例见文档:https://download.csdn.net/download/Lapedius/12563828

    密码模块规格

    <CY02.03:材料应描述密码模块类型,并解释选择这一类型的依据;材料应提供密码模块的规格,以标识所有密码模块的硬件、软件和/固件部件。>(安全级别1,2,3,4)
    密码模块类型包括硬件模块、软件模块、固件模块、混合软件模块和混合固件模块,应根据送检产品的特征定义该模块类型,并写出判断依据,以及说明送检产品的软硬件组成部分。在确定密码模块的类型前,需首先划定密码边界,边界非常非常重要,涉及产品名称、类型、材料和检测等所有重要的环节。密码边界即为报送的产品要明确哪些软件和硬件包含在内,哪些不包含。

    <CY02.07:材料应明确界定密码模块的密码边界,详细说明密码边界内的所有软硬件部件>(安全级别1,2,3,4)
    密码边界由定义明确的边线(例如,硬件、软件或固件部分的集合)组成,该边线建立了密码模块所有部件的边界,该边界至少包含密码模块内所有安全相关的算法、安全功能、过程和部件。

    <CY02.09:材料应提供密码边界内所有与安全相关的算法、安全功能、过程和部件清单,安全功能包括但不限于:分组密码、流密码、非对称密码算法和技术、消息鉴别码、杂凑函数、实体鉴别、密钥管理、随机比特生成器>(安全级别1,2,3,4)
    本部分要描述实现算法、实现过程(比如密码卡实现加解密、签名验签运算,比如pos采用xx算法实现pin管理,安全协议、交易)。一定要写出产品具体实现什么功能,怎么用算法实现的功能,不能所有产品写的都像抽象的产品。部件不能简单只写密码卡、芯片、智能密码钥匙,要有详细清单。

    密码模块接口

    <CY03.01:材料中应说明密码模块的每个物理端口和逻辑接口;材料中应通过GM/T 0028-2014附录A.2.2和附录B.2.2要求提供的框图、设计规格、源代码以及原理图,说明密码模块的信息流和物理接入点。同时还需提供其他有助于明确说明信息流、物理接入点和物理端口、逻辑接口的关系的文档;对于密码模块的每一个物理或逻辑的输入,以及物理或逻辑的输出,材料中应明确逻辑接口所对应的物理输入或输出。>
    应说明密码模块所有的物理端口与逻辑接口,并且明确物理端口的输入输出、逻辑接口的类型;对于逻辑接口,应说明密码模块与外部交互的协议所遵照标准,若无标准则应详细描述协议过程。

    <CY03.02:送检单位的设计应根据AY03.04所列的类别将模块的接口分成逻辑上不同和相互分离的类别,并且如果适用,AY03.12亦可作为依据。这些信息应符合AY03.01描述的逻辑接口和物理端口的规格。材料中应提供每类逻辑接口到密码模块的物理端口的之间的映射。逻辑接口可以在物理上分布在多个物理端口上,或两个或多个逻辑接口可以共享一个物理端口。如果两个或多个逻辑接口共享同一个物理端口,送检单位的文档中应说明这些不同类别接口的信息流是如何在逻辑上相互分离的。>
    应说明送检产品支持几种逻辑接口和物理端口,物理端口与逻辑接口之间的关系,各个接口实现的功能是什么。
    密码模块接口这个章节要讲物理端口有哪些,逻辑端口有哪些,物理端口就是usb、7816、RJ45、串口等等,逻辑接口就比如软件API、SDF等各种接口,要定义出来有哪些物理和逻辑,输入输出什么输入,参数是什么。上面两个标黄的问题,要每个问题都回答。特别是CY03.02中逻辑接口与物理接口的映射,比如PCI各种SDF接口,那怎么对应到一个PCI接口上,这么多逻辑接口都用一个物理端口做输入输出,那怎么区分,怎么隔离,怎么知道这个数据是这个逻辑接口的。

    <CY03.05:密码模块应有数据输入接口。所有输入到模块和由模块处理的数据(除通过控制输入接口输入的控制数据)应通过数据输入接口进入,包括:
    ——明文数据;
    ——密文或签名数据;
    ——加密密钥和其他密钥管理数据(明文或密文);
    ——认证数据(明文或加密的);
    ——来自外部的状态信息;
    ——其他输入数据。
    若适用,材料中应说明所有与密码模块同时使用的外部输入设备,此设备用于输入数据到数据输入接口,如智能卡、令牌、键盘、密钥加载器和/或生物识别设备。>(安全级别1,2,3,4)
    注:应说明每个数据输入接口的功能、性能、传输数据的报文格式等。如有远程配置接口、专用密钥注入接口等也须说明说明。

    角色、服务与鉴别

    <CY04.05:材料中应描述密码主管角色的功能,包括:执行密码初始化或管理功能,以及常用的安全服务,例如,模块初始化、CSP和PSP的管理以及审计功能。>(安全级别1,2,3,4)
    送检单位的文档中需描述密码主管角色的功能,密码主管角色的权限和许可的服务。密码主管的定义需要与其执行的操作关联,并赋予其相应权限,包括执行密码初始化、CSP和PSP的管理、审计功能。

    <CY04.16:材料中应说明密码模块核准的工作模式中使用核准的安全功能。>(安全级别1,2,3,4)
    要与CY02.19回答的对应,各个模式,各个功能。
    <CY04.43:应记录模块内实现的鉴别类型。应记录用于执行操作员身份鉴别的机制、操作员的身份鉴别、一个或多个角色隐式地或显示地选择、操作员担任角色的鉴别。>
    <CY04.50:材料中应说明采用何种核准的鉴别机制。>
    <CY04.54:材料中应说明密码模块鉴别机制的实现方法和原理。>
    <CY04.58:应记录密码模块运行的鉴别类型。应记录操作员隐式或显示地选择一个或一系列角色的机制,及操作员担任角色的鉴别方法;材料中应提供操作员隐式或显示地担任角色的描述。>
    这部分问题要回答遵循了0028附录E中哪个规范做的鉴别,具体如何实现,拼接数据的方式,如何跟规范对应的。不能简单说明发送签名给服务端或者验证口令。要写清楚怎么拼接,如何认证,几次鉴别。

    <CY04.53:材料中应说明每个核准的鉴别机制在1min内的多次随机尝试使用通过核准的鉴别机制的成功概率。>
    1min内可以暴力破解多少次就锁定啊或者不如登录啊,这样分析破解能成功的概率是多少,近乎为0啊,10万分之一啊,要有分析过程,不是随便说的。

    软件/固件安全

    <CY05.09:送检文档应说明通过SFMI、HSMI或HFMI服务按需执行已核准的完整性技术的方法。>(安全级别1,2,3,4)
    注:SFMI为软件或固件模块接口,定义为用于请求软件或固件模块服务的命令全集,请求服务的命令中包括输入到密码模块或者由密码模块输出的参数。
    HSMI或HFMI为混合软件或混合固件模块接口,定义为用于请求混合软件或混合固件模块服务的命令全集,请求服务的命令中包括输入到密码模块或者由密码模块输出的参数。

    <CY05.14:送检单位应提供HM1、SFM1、HFM1或HSM1服务的说明>
    这个问题回答要看0039中相应AY的要求,不能只看这一句话断章取义。

    运行环境

    <CY06.03:材料应按照GM/T0028——2014附录A.2.6中规定的要求编写。>
    先分析属于可修改\不可修改\受限的哪种运行环境
    像pci\密码机这样硬件模块基本都是不可修改的,协同签名这样软件模块安装到安卓\ios操作系统的都是可修改的,因为操作系统可以再安装其他软件.
    对于不可修改和受限的运行环境,一级和二级都回答<CY06.05到<CY06.08:内容,并且6.2 不可修改运行环境的操作系统要求这个标题要从可修改改成不可修改.

    物理安全

    若密码模块完全由软件实现,使得物理安全仅由计算平台提供,那么该模块将不受物理安全要求的限制。
    <CY07.09:送检文档应按照GM/T 0028—2014中7.7.1的要求明确说明密码模块的物理实体,包括单芯片密码模块、多芯片嵌入式模块,或多芯片独立式模块>(安全级别1,2,3,4)
    注:送检单位根据产品情况表明所达到的安全级别并划分物理实体类型:
    单芯片密码模块:单个集成电路(IC)芯片构成的模块,该芯片可以作为独立模块使用,或者可以嵌入可能没有物理保护的外壳或其它模块中。这个单芯片由片装和外部输入/输出连接器组成,其中片装使用统一的外部材料如塑料或陶瓷包装。例如:单IC芯片、单IC芯片智能卡或者包含实现密码功能的单IC芯片的其它系统。
    多芯片嵌入式密码模块:模块由两个或两个以上互相连接的IC芯片构成,并物理嵌入到其他产品或未被物理保护的外壳中。例如:适配器和扩展板。
    多芯片独立密码模块:两个或两个以上互相连接的IC芯片嵌入到完全受到物理保护的外壳中。例如:加密路由器、安全无线电话和USB令牌。
    物理部件可包括外壳(如材质组成、缝合原理等)、内部元器件、IC芯片或其它硬件部件,能够监测或执行触发机制,以保证内部敏感信息安全。

    非入侵式安全

    <CY08.04:提供的文档详细说明用于保护模块CSP免受所有非入侵式攻击的缓解技术。>
    通常指密码模块实现的,首先要写出怎么缓解的,具体方法是什么,不是笼统的介绍。要说清楚具体措施和机制。附录F的非入侵式攻击包括能量分析、计时分析、电磁泄漏,具体缓解方法详见附录F.2。

    <CY08.05:材料中应详细说明每个缓解技术的有效性。>(安全级别1,2,3, 4)
    有效性,要能够证明的,要有数据说话的,不是吧8.4再复制一遍.要给出证明文件,加缓解措施和没加缓解措施效果对比。

    敏感安全参数管理

    <CY09.01:送检单位的文档应描述模块内所有CSP的保护措施,包括防止非授权的访问、使用、泄露、修改和替换的实现机制>
    像第九章要对每个密钥都展开描述,不是一句话csp能够保障xxx就行的,要分开A密钥xxxx措施xxxx保障使用,xxx措施保障泄露,B密钥xxxx措施xxxx保障使用,xxx措施保障泄露。

    <CY09.03:送检单位提供的文档应描述生成的、输入或输出模块的SSP与被分配实体(即人、组、角色或进程)的关联关系>(安全级别1,2,3, 4)
    注:材料书写时可使用图标方式将SSP与实体对应起来。

    自测试

    <CY10.07:送检文档应记录每一项自测试对应的错误状态,并标明该错误状态对应的错误指示>(安全级别1,2,3, 4)
    注:此处应描述每一项自测试对应的错误状态及其对应的错误指示。
    例:自检失败的错误状态和错误码如下所示:
    错误状态 错误码
    固件完整性校验失败 67 10
    随机数自检失败 67 19

    <CY10.24:送检文档应包括条件自测试的相关信息>
    要写出具体原理、算法、随机数单次、周期、软固件完整性、密钥完整性等测试机制,预置数据。写的顺序最好跟执行顺序是一致的。

    生命周期保障

    <CY11.29:应提供材料详细说明在密码模块上执行的功能测试。>(安全级别1,2,3, 4)
    这是开发完代码,你们厂商自己做的运维测试也好,产品测试也好,不属于来检测中心做的检测,这是两回事。
    注:仔细读规范0028和0039,规范都写了详细说明,不要一句话糊弄,不是抄规范,要写出具体原理机制和自己的心得体会。

    展开全文
  • 一篇文带你从0到1了解建站及完成CMS系统编写

    万次阅读 多人点赞 2020-10-24 00:48:06
    学习目标 了解搭建一般网站的简便方式 了解最原始一般站点搭建 了解内容管理站点搭建 了解权限设计及完成 ...文章为从0到1了解内容管理系统搭建与编写,由于一篇文章内容篇幅过长,文章内容经过压缩,该项目中相

    学习目标

    1. 了解搭建一般网站的简便方式
    2. 了解最原始一般站点搭建
    3. 了解内容管理站点搭建
    4. 了解权限设计及完成
    5. 了解使用设计模式减少代码冗余
    6. 了解前端拖拽页面生成及生成
    7. 了解自定义数据的创建
    8. 了解动态生成的前端页如何绑定自定义数据

    开发环境

    • Windows7 *64 SP1
    • php5.6
    • apache/nginx
    • thinkphp5.1
    • mysql
    • phpstudy2018
    • sqlyog
    • layoutit

    声明

    文章为从0到1了解内容管理系统搭建与编写,由于一篇文章内容篇幅过长,文章内容经过压缩,该项目中相同逻辑的实现只以一个实例作为描述,主要以核心关键功能的开发作为主要的讲解步骤。如有想学习完整内容系统编写可在留言区留言,我会尽快完成完整版的实战教程发布。谢谢。本篇不涉及vue、nodejs的前端框架。

    知识门槛

    以下内容有过一些了解即可:

    • html
    • sql
    • php
    • tp框架

    面向人群

    • 刚学了php不懂怎么用的同学
    • 会一点点建站但是又不清楚流程的同学
    • 学习完了一些框架不懂怎么使用的同学
    • 有过一些web开发经验的同学等
    • 希望本篇文章对每一个阅读完的同学都有帮助

    注意:本篇文章部分细节由于篇幅关系并不会去深入完善,并且相同逻辑的实现只以一个实例作为描述,主要以核心功能的开发作为主要的讲解步骤。本篇不涉及vue、nodejs的前端框架。

    一、 了解一些专业术语及概念

    在了解搭建网站前,需要普及一些基本的知识概念,防止某些同学在一方面有概念性的错误,并且我个人认为在学习一方面知识前需要对这一方面的知识有一个广度的了解,这里所指的广度为这东西是用来干什么的、作用是什么、为什么要这样写;所以在正式开始介绍如何编写CMS前将会介绍这一部分内容。为了方便阅读第一点内容引入我另外一篇原创文章。

    1.1了解浏览一个网页的基本流程方式

    在学习一门技术的时候,往往是了解整体体系架构才能更好的学习,不然在学习的过程中会出现不知道为什么这样做,做出这一部分是该整体部分的哪个区域,只会跟着做,但是并不了解这是在干啥。可能一些萌新体会颇深,就照着打,老师教怎么写,我就怎么写,反正做出来了。

    本篇博文,就来用最接地气的方式对基本的web开发做一个整体的讲解,带各个萌新过一遍web开发的流程,好让各位萌新知道学习的时候学习了什么知识点,这个知识点能够干哈。

    最开始,我们就以个人浏览网站的方式给大家说一下这一个过程是如何运作的。
    在这里插入图片描述

    我们访问网站,一般先打开浏览器(不要杠),输入一个网址,随后浏览器打开一个网页。在你在请求这一个网址数据的时候,已经发生了一系列的操作。

    1.2了解IP地址

    假设你输入的是“csdn.net”,浏览器想要去访问你这个网站,首先需要的是获得你这个网站的IP地址。可能就有萌新问了**“什么是IP地址?”。IP地址就是“指互联网协议地址,或者说网际协议地址”。又有萌新说了“你这么说我怎么懂?”**,好了现在容我慢慢道来。

    IP地址就是在网络中,定位你这台电脑,或者说是设备的一个标记,这个标记是人们指定好的标准协议而产生的(协议就是你和我说好了一件事,拉钩了,以后要这样做)。就像你家的门牌号例如叫做“CSDN市,CSDN区,CSDN街道的CSDN小区第CSDN栋的第CSDN号”…这是由有关机构制定的一套规范名称,不允许随意更改;我们换个例子,例如你家是“深圳市南山区深南大道某某小区第八栋808”,你写快递的收件地址肯定是写这个,难道你写“宇宙第一星球第一市第一栋第一号”?地址是由专门组织规范且制定的一套定位规范,遵循这个规范可以使遵循该规范的设备或者人之间相互通信,这个通信指可以传达交互,能够定位、找到。综上所述,IP地址就不要纠结为什么要这样写,只要知道这个IP地址是你要用的就行。

    1.3了解DNS

    现在IP地址知道是什么了,那么怎么获得IP地址?这个时候就需要用到DNS了,啥是DNS??!!
    在这里插入图片描述

    DNS的英文全称是 Domain Name System,翻译过来就是域名系统。好了,这个时候问题又来了。

    1.4了解域名

    啥是域名?域名就是用来标识IP地址的一个标记,或者说是昵称。“为什么不直接用IP地址?”这个问题问得好,如果我们人不用名称,就用身份证号,我叫你的时候就会叫“450333333333333333…”。。。我觉得这样不是很好。。。当人们觉得使用IP地址不方便记忆后,就产生了域名地址,就像CSDN,我们就知道是CSDN就好了,难道还要去记她的IP地址吗?例如CSDN的地址是192.168.1.1,难不难受…以后可能你记网站名称就在记数字了,又不方便又崩溃。好了,回归正题,我们输入了网址后,按下Enter键后,浏览器将会去DNS请求这个域名对应的IP是什么,如果找到了,就返回一个IP地址。可能又有萌新问了,“浏览器会自动去找DNS?”,会是会,但是我们也会给它一个目标,在我们的网络连接里面,本地连接右键属性,里面有个IPV4,双击进去就可以查看自己配置的DNS了,一般别乱改,不然很难过的,有时候浏览器打不开网址,就是这个原因。
    在这里插入图片描述
    记住,网络IP冲突可能会导致上不了网,这种情况在学校的机房里很常见,只要改成自动获取IP就ok了,会自动分配闲置的IP地址。

    1.5 了解数据请求

    在这里插入图片描述

    当找到了IP地址,这个时候就会向该IP地址的设备去请求数据,请求数据的意思就是,这个设备或者说服务器就像一个大型的分发机构,就是送情报的一个部门,一共有65535个窗口,每个窗口送不同的情报;例如我们需要请求网站之类的数据,就通过第80个窗口请求,这个时候浏览器派来的小弟来到这个80号窗口,可能会排一下队,拿到数据后,回到浏览器,浏览器把拿到的数据显示给你看。

    1.6 了解“ 渲染”

    在这里插入图片描述

    其实在这个时候,浏览器显示的数据会根据一些标记,进行排版,这些标记就称是HTML,HTML是 Hyper Text Markup Language 的缩写,中文名是超级文本标记语言,其实说那么深奥还不方便理解;简单来说就是通过特定的标签,把一段文本信息标记起来,表示这段文本信息要怎么样去进行显示,或者是这个文本信息是啥东西;例如 <title>CSDN-专业IT技术社区</title>是CSDN官网首页的标题,用了title这个标签把文本信息标记,标记好后,浏览器就知道这个文本要显示在哪里,要怎么进行显示,最终浏览器把这一段信息显示在了浏览器标题头位置:
    在这里插入图片描述
    我们再看看另外的一个例子:
    在这里插入图片描述
    这一段HTML语言所标记了一个博客的文本,整个标记的情况为了清晰的看清楚,我在这里列出:<a href="//blog.csdn.net/" class="toolbar_to_feed" title="博客">博客</a>,标记语言HTML那一些标记并不会进行显示,只显示了博客这个这个文本在网页上:
    在这里插入图片描述
    那是因为浏览器是通过标记语言的内容去进行显示,标记语言的作用就是告诉浏览器这里你要怎么显示这个内容,或者说这个内容有什么功能。这里是博客的一个跳转,使用的是a标签,a标签是什么?a标签就是<a>这里是要显示的文本</a>,在a标签里面可以添加一些固定的操作,例如a标签的作用是跳转到指定的页面,那么这个页面肯定是有一个链接的,那么这个链接需要什么来指定呢?

    答案就很简单了,使用href来指定,这个href呢就需要把要跳转到的页面的地址给加上,在我们查看到的HTML代码中是href="//blog.csdn.net/",这就表示会跳转到blog.csdn.net这个地址,有人点击就会跳转到博客了。

    class="toolbar_to_feed" 是什么东西?在这里我们可以把它当做给定了一个样式,给定了一个style,要怎么样显示,你要显示的样子是什么?可能红色的底,绿色的字,俗话说,红配绿。。。这个样式的名称就叫做 toolbar_to_feed 。在这里并不会深入的讲解这个样式要让博客这个文本显示成啥样,大家只要通过例子知道这个html是用来告诉浏览器怎么样显示这个文本,或者这个文本有什么用就ok了。其实还有些动态的数据,但是在这里并不会讲解,基本的理解这样就没问题了。专业点的说法就是构件编排用户界面。

    1.7 了解前端

    在这里插入图片描述

    通过以上描述就很清楚的知道,如果我们做web开发的话,做html相关的就是给页面制作布局,怎么样好看,甚至可以做特效,让页面显示多姿多彩;一般我们称做HTML这种,是为了数据的显示的排版工作,或者说是为了包装数据工作的这类职位叫做前端;不过前端是个相对概念,在web上可以这样理解是没问题的,不过现在的前端,如果不去大厂,基本上要做的不止是包装数据的排版那么简单,可能还会做得更多。如果我们去做前端工作的话,还要掌握跟服务器交互的一些操作,打个比方,用户点击了一个按钮,这个按钮的功能是获取到你们的用户人数,这个时候你需要编写一个逻辑,去服务器获取到这个用户想要的数据。不过这点只是作为一个提醒,当真正接触前端的话会了解的。

    1.8 了解后端

    在这里插入图片描述

    有很多小问号的朋友可能会记得刚刚说的,前端可能要向服务器请求数据,那么这个数据,是不是就是传说中的后端做的?(听没听过后端某问题,反正就是后端)

    后端可以理解为一些业务逻辑的代码编写实现,就是需要后端,什么是业务逻辑?简单的举个例子,就像你淘宝买东西,你点了这个物品,下单了,我要在代码上怎么实现这个下单这个背后的操作;因为下单后你还需要交易,交易要收钱,收钱你还要把这个记录记载到你存放数据的地方,我们可以叫做数据库,存进去后,用户查看自己的下单记录,你还需要把这个记录取出来,用代码实现这个取出来这个过程给用户看到,不然没有记录那就很尴尬了,只收钱不卖货!流批!所以一般是指的是数据库(因为要存储数据,例如你网站的用户数据,肯定要用东西来存储,这个东西就是数据库)进行交互以处理相应的业务逻辑。虽然后端要考虑很多东西,但是一般来说这样举例子就比较方便理解,就不过多的谈论其它东西了。

    现在整个逻辑基本上就通了,简单的理解,后端就是实现一些数据操作,业务逻辑的实现(其实可能会运维),前端呢就是负责用户的页面数据的展示排版;嗯,大体这样理解问题不大。

    1.9 了解建站

    在这里插入图片描述

    既然理解通了,我们就来说说一个网站搭建的流程是什么吧!
    首先我们需要租一个服务器,嗯…这个萌新不理解,那我们降一个档次,那就是我们在我们自己的本地电脑进行试验,这样就问题不大了,方便快捷。

    搭建一个简单企业门户网站其实贼简单,不吹不黑,几年前的时候,做这个还是挺得钱的,接接外包,舒舒服服,现在就不行了,毕竟技术在更新,过时的技术也变得更加廉价了,但是依旧是基本。

    以下我使用一个静态网站作为例子演示一个网站的搭建;“啥是静态网站?”。静态网站就是没有后端,好吧,简单来说就是这样,由于后端需要一些其它语言,本篇博文针对于普遍人群,为了方便理解就不用后端了,直接静态网站作为演示,列出html的代码,到时候萌新们可以直接复制代码拿去自己试验,舒舒服服,美滋滋。

    1.10 了解集成环境

    在这里插入图片描述

    首先我们下载一个集成环境。“啥是集成环境?”

    集成环境打个比方,就像你做菜、需要火源、锅、锅铲,这种就是环境;我做网站也要一个环境,这个环境有人给你做好了,你直接拿过来用就好,就不需要自己搭建,有些初学者就喜欢自己搭建,然后发现一堆问题,搞着搞着发现太难就不学了,简直嘤嘤嘤!初学者我个人建议先别增加自己的难度,先学,不然没搞懂就上会一脸懵圈的。现在我们下载一个叫做phpstudy的软件,下载点这里
    去官网。然后进行傻瓜式安装。
    在这里插入图片描述
    安装完后打开服务:
    在这里插入图片描述
    Apache可能会有人问是什么,Apache是服务器软件,它就是你做菜需要的必要工具之一,开启了就对了,可能你只开启Apache只能做汤,那也没事,毕竟我现在演示的是静态网站。

    首先我们把我们的资源文件带到网站根目录下:
    在这里插入图片描述
    根目录不会找?没关系,我们打开网站,点击管理找到根目录就ok:
    在这里插入图片描述
    找到后把资源文件放到根目录下,删除以前的根目录下的内容即可。
    然后在浏览器输入:http://127.0.0.1/ 或者输入 http://localhost/ 就可以访问我们本地电脑上的网站了!

    二、给所搭建的静态网站添加后端

    在以上第一节内容中,我们已经做好了一个静态的网站,但该网站并没有一些后台功能。例如后台设置网页的所展示的内容,那为什么要后台设置网页展示的内容呢?当我们的网站成功架设后,假设该网站是双十一的推广网站,图片这些全部都是标有双十一字样,当双十一过后该网站难道就不能继续使用了吗?答案当然是不,只需要编写一管理后台,用户在后台中可自由设置图片要显示哪一张。该功能完成后,用户可根据自己的需要更改对应的图片;既然图片都可以更改了,那么文章也同样可以更改,这时网站的自由度将会更高。

    更改网站图片的显示与更改文字内容的显示都需要使用数据库,当然其它方式也可以,但我们在这里使用一种较为常规与成熟的数据库方式进行存储,并且使用一个php的开发框架thinkphp来方便我们的搭建。thinkphp的版本是5.1版本。可能有些小伙伴们问为什么要使用框架?这不是增加学习成本吗?其实使用框架并不会增加你的开发时长,并且会增加你的开发效率;框架就像搭建房子时的地基,直接使用一个地基比你自己再去做一个地基更加简单方便,而且更为标准;如果你是一个新手,自己去搭建一个地基,往往会做到一半就“塌”了,这种情况也不是不可能。

    2.1 了解thinkphp5.1 的使用

    首先我们下载thinkphp5.1,解压后目录如下:
    在这里插入图片描述
    目录参考可以根据thinkpp5.1手册
    在这里插入图片描述
    thinkphp5.1的目录结构在本文并不需要了解过多,本文将会说明需要了解的目录。
    我们复制解压出来的文件至网站根目录下,并且删除原有网站根目录下的内容:

    在这里插入图片描述由于thinkphp框架的入口在public目录下,我们打开public目录进行查看:

    在这里插入图片描述
    在public目录下找到了index.php文件。由于该框架的入口文件是index.php,需要更改网站的根目录为public。打开phpstudy,依次点击其它菜单选项->软件设置->端口常规设置:
    在这里插入图片描述
    在弹出来的根目录设置中,选择public作为根目录:
    在这里插入图片描述此时输入localhost进行访问:
    在这里插入图片描述
    出现如上示例则表示当前thinkphp部署成功。接下来就可以进行相应的代码编写了。

    2.2 完成第一节静态网站的移植部署

    在第一节中,我们实现了一个静态网站的搭建,现在将第一节编写好的静态网站index.html文件复制到如下路径中:
    在这里插入图片描述

    我的目录是 E:\devlop\phpstuy\PHPTutorial\WWW\application\index\view\index,如果没有该目录可以自己创建。我们浏览器再次输入localhost查看,发现依旧出现之前的web页提示,这是什么回事呢?因为我们需要在thinkphp的控制器中,添加一行跳转到该html文件的代码。控制器文件在 E:\devlop\phpstuy\PHPTutorial\WWW\application\index\controller 下:
    在这里插入图片描述

    该目录是存放当前模块下所有控制器的地方(当然你可以不这样),控制器在thinkphp框架中用于对用户访问进行控制,例如用户需要访问首页则需要访问首页的控制器,默认是index控制器;index控制器可以对index这个页面进行逻辑控制,可以传值、权限控制等一些列操作。换句话说则是控制用户访问指定资源的逻辑(不理解也没关系)。我们打开index.php这个控制器:

    <?php
    namespace app\index\controller;
    
    class Index
    {
        public function index()
        {
            return '<style type="text/css">*{ padding: 0; margin: 0; } .think_default_text{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">十年磨一剑 - 为API开发设计的高性能框架</span></p></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>';
        }
    
        public function hello($name = 'ThinkPHP5')
        {
            return 'hello,' . $name;
        }
    }
    

    改php控制器默认index为访问方法,index方法将会返回一条html的字符串,改字符串通过渲染将会显示成我们之前所看到的部署成功的欢迎界面。在这里需要将该代码删除。换成:

    return $this->view->fetch();
    

    整个php文件则为:

    <?php
    namespace app\index\controller;
    use think\Controller;
    
    class Index extends Controller{
        public function index()
        {
            return $this->view->fetch();
        }
    
        public function hello($name = 'ThinkPHP5')
        {
            return 'hello,' . $name;
        }
    }
    

    return $this->view->fetch(); 我们可以查看thinkphp5.1手册:
    在这里插入图片描述

    使用 fetch 方法将会自动定位到模板文件。thinkphp已经帮我们写好了一定的规则,自动定位到默认view目录下对应控制器名下的index文件。在此注意,是自动定位到view目录下与控制器同名的目录下的文件,不加参数自动定位到index.html,也就是view/控制器名/index.html,由于控制器名是index,则是view/index/index.html;view目录下的index目录则是之前复制静态网站html文件的目录。

    保存php文件,访问localhost:

    在这里插入图片描述

    这时发现整个web页错乱,这时因为所有css文件、js文件、img文件的路径都有所改变,这时需要更改到正确的资源加载目录。为了方便加载,在网站根目录public目录下新建一个home目录,复制该页面所需的资源文件到该目录下:
    在这里插入图片描述

    网站根目录资源的访问路径是“/”表示网站根目录下,由于在根目录下创建了一个home目录,则进一步可以写为“/home/”,在home目录下有一个asset,则可以写为“/home/assets/”,assets下的文件访问则可以根据目录进行具体访问,例如asset下的目录img有一个图片叫做1.png,那么访问则可以写成“/home/assets/img/1.png”。

    了解了访问的规则后,修改index.html文件,将所有 assets/ 都更换为 /home/assets/,我使用的编辑器是 vscode,快捷键 ctrl+h 即可调出一键替换:
    在这里插入图片描述

    点击如上图中的一键替换即可完成资源内容的目录修改,随后保存,再次访问:
    在这里插入图片描述完美呈现,是不是贼爽?那么接下来就实现这些图片资源的可后台更换。

    三、完成后台模块的编写

    3.1 完成管理后台模块搭建

    首先复制application目录下的index目录:

    在这里插入图片描述
    更改index-副本名为admin:
    在这里插入图片描述
    随后更改admin目录下controller目录中的index.php文件内容,原文件内容如下:

    <?php
    namespace app\index\controller;
    use think\Controller;
    
    class Index extends Controller{
        public function index()
        {
            return $this->view->fetch();
        }
    }
    

    更改为:

    <?php
    namespace app\admin\controller;
    use think\Controller;
    
    class Index extends Controller{
        public function index()
        {
            return $this->view->fetch();
        }
    }
    

    以上的内容主要更改在命名空间,从 namespace app\index\controller;更改为了 namespace app\admin\controller;。命名空间主要是为了区分不同区域或空间内的不同“东西”。例如学校中A班的小明与B班的小明,这两者有着班别的区别,命名空间也是如此,表示不同区域不同空间内的值。

    更改完成后访问 http://localhost/index.php/admin/index,这行url地址表示该网站中admin模块下的index方法,其中index.php在访问首页的时候是默认隐藏,即http://localhost/index.php等于localhost,由于当下访问其他模块在此需要写全(当然可以配置隐藏,但不是本节内容则不过多增加难度)。访问后发现该页面与访问localhost出现的内容一致,这是因为admin模块中的index方法也用了return $this->view->fetch();这一行代码输出了html文件的代码,这个html文件并不是index模块下的view/index下的index.html,而是admin模块下的view/index下的index.html,因为刚刚整个模块我们都进行了复制。这时该html不符合我们的需求,需要更换html内容,在此我使用了一模板(该模板编写是前端内容,在此并不过多赘述,实现逻辑与index.html类型,均是修改页面的资源路径即可),访问效果如下:
    在这里插入图片描述

    注:本节项目代码将会打包分享给大家。

    3.2 完成数据库的导入

    完成后台管理页的搭建后,发现该后台所有用户均可访问,这对于一个网站是不好的权限行为;必须实现可控的权限管理,使得网站内容不得随意更改。

    首先打开sqlyog,输入数据库的帐号密码,一般帐号为root密码为root或空:
    在这里插入图片描述
    连接成功后,邮件你本地数据库点击创建数据:
    在这里插入图片描述
    输入数据库名,我创建数据库名为minimalism_cms,并且选择字符集,字符集为utf8即可,点击创建:
    在这里插入图片描述在出现的新建数据库中,选择创建表:
    在这里插入图片描述
    输入表信息如以下:
    在这里插入图片描述
    以上所有所需的数据库表我将会导出sql文件,同学们使用时在数据库导入即可,导入步骤如下:
    在这里插入图片描述
    在对应数据库中右键选择导入点击执行sql脚本即可。

    导入完将会出现如下的数据库表:
    在这里插入图片描述
    以上数据库表考虑排错等操作并没有过多约束。

    3.3 完成权限内容添加功能编写

    权限管理首先需要有账户,账户属于什么角色,该角色又有什么权限,这是实现权限管理的思想。例如有个账户名为admin,admin属于超级管理员这个角色,该角色拥有所有的权限。接下来首先创建管理员用户。
    在权限管理下拉列表中选择管理员管理进入页面:
    在这里插入图片描述我们查看url连接:http://localhost/index.php/admin/auth/adminauth.html
    以上链接中,admin表示admin这个模块,auth表示控制器,adminauth表示方法名;auth控制器我们还未创建,在admin模块下的index控制器同目录创建一个名为Auth.php文件,内容如下:

    <?php
    /**
     * |-----------------------
     * | 页面跳转
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    
    class Auth extends Controller{
    
        //Auth 管理首页
        public function adminAuth(){
            return $this->view->fetch();
        }
    }
    

    通过以上控制器,可以使url连接访问到该控制器并且访问adminAuth所对应的html文件,该html对应的文件在view目录下的auth目录中。在thinkphp中,对应的view目录根据控制器名分配,Auth控制器需要一个名为auth的目录存放该控制器下的html文件;在auth目录下创建一个名为admin_auth的html文件,为什么要名为admin_auth?thinkphp会访问方法名默认控制器对应的目录中一同方法名的html文件,如方法名有大写,则表示在该名称前有一下划线,则adminAuth则为admin_auth。该html代码将会打包下载即用。

    点击添加,添加管理员进入页面:
    在这里插入图片描述该url为:http://localhost/index.php/admin/auth/adminadd.html
    在控制器中添加方法:

    <?php
    /**
     * |-----------------------
     * | 页面跳转
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    
    class Auth extends Controller{
    
        //Auth 管理首页
        public function adminAuth(){
            return $this->view->fetch();
        }
        //管理员添加页
        public function adminAdd(){
            return $this->view->fetch();
        }
    }
    	
    

    该页面拥有管理员账户、管理员密码、名称及角色组内容。暂时我们并没有角色组,首先创建一个管理员账户。查看html中的关键代码:

    <form class="form-horizontal" role="form">
                                    
       <div class="form-group">
             <label class="col-md-2 control-label">管理员账户</label>
             <div class="col-md-10">
                 <input id="user" type="text" class="form-control" placeholder="帐号">
             </div>
         </div>
    
         <div class="form-group">
             <label class="col-md-2 control-label">管理员密码</label>
             <div class="col-md-10">
                 <input id="password" type="password" class="form-control" placeholder="密码">
             </div>
         </div>
    
         <div class="form-group">
             <label class="col-md-2 control-label">名称</label>
             <div class="col-md-10">
                 <input id="realname" type="text" class="form-control" placeholder="输入名称或代号">
             </div>
         </div>
     </form>
    

    通过以上html得知,id为user是账户,id为password为密码,id为realname为真实姓名。在此使用ajax进行数据提交到php后台实现内容访问。查看ajax代码:

    <script>
                function add(){
                    var user=$('#user').val();
                    var password=$('#password').val();
                    var realname=$('#realname').val();
                    $.ajax({
                        type:'post',
                        url:'/index.php?s=/admin/Authpost/adminAdd/',
                        data:{"user":user,"password":md5(password),"realname":realname,"group":group},
                        dataType:"json", 
                        success:function(data){
                            if(data.success==1){
                                alert(data.msg);
                            }else{
                                alert(data.msg);
                            }
                        },error:function(jqXHR){
    
                        }
                    }) 
                }
            </script>
    

    从以上ajax代码中,使用jq获取了id为user、password、realname元素的值,在此并没有做检查是否合规,希望小伙伴们在使用该代码的时候注意。在获取密码时使用了md5加密,md5我是在线引入的,引入如下:

    <script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.js"></script>
    

    获取值后使用ajax传递给 /index.php?s=/admin/Authpost/adminAdd/ 这个url地址。该地址使用了兼容模式,因为担心一些同学本地环境有问题,所以特地在此使用该模式进行传值。该模式的格式为:http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值…]。则admin为模块名,Authpost表示控制器名,adminAdd表示控制器中的方法。我们在admin的控制器目录创建一个名为Authpost的控制器,并且编写adminAdd方法,代码如下:

    <?php
    /**
     * |-----------------------
     * | 对数据库操作
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    use think\Db;
    use think\facade\Request;
    
    use app\admin\model\Admin;
    use app\admin\code\ReturnCodeInfo;
    
    class Authpost extends Controller{
    
        //administartor add 
        public function adminadd(){
            $request_data = Request::post();
    
            $data['password'] = md5(trim($request_data['password']));
            $data['username']=$request_data['user'];
            $data['realname']=$request_data['realname'];
            $data['group']=$request_data['group'];
    
            $data['logintime'] = time();
            $data['create_time'] = time();
            $data['loginip'] = Request::ip();
            $data['status'] = 1;
    
            $res = Admin::create($data);
    
            if($res){
                return json((new ReturnCodeInfo())->actionSuccess());
            }else{
                return json((new ReturnCodeInfo())->actionError());
            }
        }
        }
    

    先不看以上代码,我们查看需要存储值的数据库字段有哪些:
    在这里插入图片描述
    通过表得知,数据库字段包括 username、password、logintime、loginip、realname、create_time。我们接收值需要设定这几个初始字段,Authpost 控制器adminadd方法中这部分代码为:

    $request_data = Request::post();
    
    $data['password'] = md5(trim($request_data['password']));
    $data['username']=$request_data['user'];
    $data['realname']=$request_data['realname'];
    $data['group']=$request_data['group'];
    
    $data['create_time'] = time();
    $data['loginip'] = Request::ip();
    $data['status'] = 1;
    

    以上代码使用了 Request::post();接收post值,在使用Request时必须引用use think\facade\Request;;随后将值赋给$request_data变量。随后使用 $data 变量存储即将要存储到数据库的值。在存储password密码时使用了md5加密,提高安全性。最后使用模型的create方法将数据库的值存储:

    $res = Admin::create($data);
    

    模型方法可以方便的使值进行存储。模型对应的是一个数据库,例如我数据库名为tp_admin,设置前缀为tp_后可以直接创建一个名为Admin的模型,其实也就是名为Admin的php文件,文件中类名也为Admin,该类集成model基类故此有模型特性。创建模型的方法如下,在admin下的controller同目录,注意是同目录创建一个model文件夹,在该文件夹下创建一个Admin的php文件,内容如下:

    <?php
    namespace app\admin\model;
    use think\Model;
    
    class Admin extends Model {
        
    }
    

    创建完成后,在需要使用到该模型的文件中引入,我们在Authpost头部引入 ,添加代码:

    use app\admin\model\Admin;
    

    其实以上代码是通过模型所在目录进行引入,app表示根目录,根目录下的admin模块中model目录下的Admin模型。
    在引入后还差很关键的一步,需要配置数据的连接。
    在application目录下config文件夹中找到database.php文件,打开修改hostname为127.0.0.1或者是localhost、修改database为我们创建的数据库的名称例如minimalism_cms、修改username帐号为root、修改password密码为root。配置内容如下:

    <?php
    // +----------------------------------------------------------------------
    // | ThinkPHP [ WE CAN DO IT JUST THINK ]
    // +----------------------------------------------------------------------
    // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
    // +----------------------------------------------------------------------
    // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
    // +----------------------------------------------------------------------
    // | Author: liu21st <liu21st@gmail.com>
    // +----------------------------------------------------------------------
    
    return [
        // 数据库类型
        'type'            => 'mysql',
        // 服务器地址
        'hostname'        => '127.0.0.1',
        // 数据库名
        'database'        => 'minimalism_cms',
        // 用户名
        'username'        => 'root',
        // 密码
        'password'        => 'root',
        // 端口
        'hostport'        => '',
        // 连接dsn
        'dsn'             => '',
        // 数据库连接参数
        'params'          => [],
        // 数据库编码默认采用utf8
        'charset'         => 'utf8',
        // 数据库表前缀
        'prefix'          => 'tp_',
        // 数据库调试模式
        'debug'           => true,
        // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
        'deploy'          => 0,
        // 数据库读写是否分离 主从式有效
        'rw_separate'     => false,
        // 读写分离后 主服务器数量
        'master_num'      => 1,
        // 指定从服务器序号
        'slave_no'        => '',
        // 自动读取主库数据
        'read_master'     => false,
        // 是否严格检查字段是否存在
        'fields_strict'   => true,
        // 数据集返回类型
        'resultset_type'  => 'array',
        // 自动写入时间戳字段
        'auto_timestamp'  => false,
        // 时间字段取出后的默认时间格式
        'datetime_format' => 'Y-m-d H:i:s',
        // 是否需要进行SQL性能分析
        'sql_explain'     => false,
        // Builder类
        'builder'         => '',
        // Query类
        'query'           => '\\think\\db\\Query',
        // 是否需要断线重连
        'break_reconnect' => false,
        // 断线标识字符串
        'break_match_str' => [],
    ];
    

    修改完成后保存。
    修改完成后查看 adminadd 方法中:

    if($res){
                return json((new ReturnCodeInfo())->actionSuccess());
            }else{
                return json((new ReturnCodeInfo())->actionError());
            }
    

    以上代码为返回通用的操作返回值码,减少代码冗余。使用 json返回json值,该值为new ReturnCodeInfo中的actionSuccess方法返回值,代码内容为:

    <?php
    namespace app\admin\code;
    
    class ReturnCodeInfo{
        //验证器成功代码 10001,错误 10002
        private $validate_success=10001;
        private $validate_error=10002;
        private $validate_info='验证成功';
        
        //数据库存
        private $action_success=10003;
        private $action_error=10004;
        private $action_sinfo='操作成功';
        private $action_einfo='操作失败';
    
        //验证器 Code
        public function validataSuccess(){
            return ['code'=>$this->validate_success,'msg'=>$this->validate_info];
        }
        public function validataError($msg){
            return ['code'=>$this->validate_error,'msg'=>$msg];
        }
    
        //规则 Code
        public function actionSuccess(){
            return ['code'=>$this->action_success,'msg'=>$this->action_sinfo];
        }
        public function actionError(){
            return ['code'=>$this->action_error,'msg'=>$this->action_einfo];
        }
    
    }
    

    以上类定义了操作失败或成功的返回状态,方便之后的操作调用该状态码。该php文件我写在controller同目录下的code目录中,名为ReturnCodeInfo的php文件。则在Authpost代码中使用了如下代码引入:

    use app\admin\code\ReturnCodeInfo;
    

    接着我们返回到html中,在提交按钮上绑定onclick事件,当然你使用别的方式也行,代码如下:

    <button type="button" onclick="add()" class="btn btn-success btn-bordered waves-effect w-md waves-light m-b-5">提交</button>
    

    随后在页面中填入内容:
    在这里插入图片描述
    点击提交:
    在这里插入图片描述
    操作成功。
    我们按照如上方式创建角色组的创建,点击角色组管理:
    在这里插入图片描述角色组实现逻辑与管理员实现逻辑类似,不再赘述。点击添加进入添加页:
    在这里插入图片描述填入组名后点击提交,操作成功:
    在这里插入图片描述该逻辑实现一致,均是创建模型后进行数据插入。
    随后开始添加规则,进入规则添加页:
    在这里插入图片描述点击提交进行添加。

    上述相同过程完成后开始实现权限认证的逻辑。
    接下来完全用户对角色组的绑定,进入管理员管理页,点击编辑:
    在这里插入图片描述进入编辑页后选择超级管理员:
    在这里插入图片描述
    点击提交,完成绑定:
    在这里插入图片描述
    提交方法使用ajax,所访问的接口为Authpost下的groupBindUser方法:

    //组绑定用户
        public function groupBindUser(){
            $request_data = Request::post();
    
            $data['uid']=$request_data['uid'];
            $data['group_id']=$request_data['gid'];
            
            $data['create_time']=$data['update_time']=time();
    
            //存储
            $res = AuthGroupAccess::create($data);
            
            if($res){
                return json((new ReturnCodeInfo())->actionSuccess());
            }else{
                return json((new ReturnCodeInfo())->actionError());
            }
        }
    

    该方法主要使用AuthGroupAccess模型调用create方法进行数据插入。数据库存储如下:
    在这里插入图片描述
    为用户id对应的组id。
    随后进入组页面,进行组绑定规则的操作,点击编辑进入超级管理员编辑页:
    在这里插入图片描述
    选择需要的规则,点击提交完成规则与组的绑定:
    在这里插入图片描述数据库存储如下:
    在这里插入图片描述
    以上表中,id为组id,rules则为规则的id。

    3.4 完成权限管理逻辑编写

    为了使验证层能够灵活的使用,在admin目录下创建一个AuthRuleValidate目录,新建一php文件名为AuthRuleValidateBase,内容如下:

    <?php
    
    namespace app\admin\AuthRuleValidate;
    use think\Controller;
    use think\Db;
    
    class AuthRuleValidateBase extends Controller{
        //传入uid 与当前 路由验证是否有此权限
        public function check($uid,$access){
            $res=Db::table('tp_admin')
            ->alias('a')
            ->field('rules')
            ->join('tp_auth_group_access agc','a.id = '.$uid)
            ->join('tp_auth_group ag','ag.id = agc.group_id')
            ->find(); 
    
            $rules=Db::name('auth_rule')->field('rule')->where('id','in',$res['rules'])->select();
            $rules=array_column($rules, 'rule');
            in_array($access,$rules)?:$this->error('权限不足');
        }
    
    }
    

    逻辑很简单,该方法接受当前的uid用于查询用户所属组,改组拥有的规则,再通过规则与当前规则进行匹配,如含有则表示拥有该权限。
    首先查询tp_admin管理员表所在的组:

    $res=Db::table('tp_admin')
            ->alias('a')
            ->field('rules')
            ->join('tp_auth_group_access agc','a.id = '.$uid)
            ->join('tp_auth_group ag','ag.id = agc.group_id')
            ->find(); 
    

    得到结果后,查询与改组id匹配的规则,最后判断该权限是否在当前的规则内,是的话不做任何操作,否则提示权限不足。
    随后在controller控制器目录下创建一基类php文件,名为Base。内容为:

    <?php
    namespace app\admin\controller;
    
    use think\Controller;
    use think\facade\Session;
    use app\admin\AuthRuleValidate\AuthRuleValidateBase;
    
    class Base extends Controller{
        protected $beforeActionList = [
            'ruleCheck'=>['except' => 'login']
        ];
    
        protected function ruleCheck()
        {
            session('?admin')?:$this->error('未登录或已失效','Index/login');
            $AuthRuleValidate=new AuthRuleValidateBase();
            $s=session('admin');
            /*echo request()->module().'/'.request()->controller().'/'.request()->action(); */
            
            $AuthRuleValidate->check($s['id'],strtolower(request()->controller()).'/'.strtolower(request()->action()));
        }
    }
    

    该文件引入了刚刚创建的权限判断类,在此基础上并且判断了该用户是否登录。
    查看代码:

    protected $beforeActionList = [
            'ruleCheck'=>['except' => 'login']
        ];
    

    该代码为设置前置曹祖,其中except表示除什么方法之外,在这里设置除login登录方法外,因为所有用户都必须登录后才能判断权限,登录方法则不受此影响。随后查看ruleCheck方法,该方法首先判断用户是否登录:

    session('?admin')?:$this->error('未登录或已失效','Index/login');
    

    随后新建权限判断类:

    $AuthRuleValidate=new AuthRuleValidateBase();
    

    接着使用seesion获取uid:

    $s=session('admin');
    

    最后调用权限判断方法传入当前控制器方法与uid进行权限判断:

     $AuthRuleValidate->check($s['id'],strtolower(request()->controller()).'/'.strtolower(request()->action()));
    

    完全权限判断基类后,使所有管理后台的控制器继承与该方法,例Auth控制器(该操作可以等待登录页编写后再进行):

    class Auth extends Base{
    

    3.5 完成登录功能编写

    在admin模块中,index控制器添加方法login,内容为:

    public function login(){
            return $this->view->fetch();
        }
    

    前端代码使用ajax传值,前端页显示如下:
    在这里插入图片描述
    随后填入帐号及密码通过ajax传值到admin模块下的Authpost控制器中login方法中,内容如下:

    //登录
        public function login(){
            $request_data = Request::post();
            $data['username']=$request_data['user'];
            $data['password']=md5(trim($request_data['password']));
    
            $res=db('admin')->where($data)->find();
            $res?session('admin', $res):$this->error('帐号或密码错误');
        }
    

    使用find方法对传入值进行对比,密码正确则将值传入到seesion否则将提示帐号密码错误。

    3.6 完成传入值的判断

    在基本权限实现完成后,使用验证器对传入值进行判断,毕竟外部值都是不可靠的值。
    在controller同级下创建一目录validate,创建目录后在该目录下创建一php文件名为BaseValidate作为对数据进行判断类的基类,代码内容如下:

    <?php
    namespace app\admin\validate;
    
    use think\Validate;
    use think\Controller;
    use app\admin\code\ReturnCodeInfo;
    
    class BaseValidate extends Validate{
    
        public function gocheck($validata){
            if(!$this->check($validata)){
                return (new ReturnCodeInfo())->validataError($this->getError());
            }
            return (new ReturnCodeInfo())->validataSuccess();
        }
    }
    

    该类继承验证器类,具有验证器特性。验证器的使用查看tp5.1文档
    查看gocheck方法,gocheck方法调用了验证器本身的check方法,其接收的参数$validata为需要验证的数据。check判断错误则调用 ReturnCodeInfo类中的报错数据返回,否则则返回正确。
    假设在管理员添加时需要验证数据是否合规,那么在validate目录中创建一名为AdminValidate的php文件,内容为:

    <?php
    namespace app\admin\validate;
    
    use app\admin\validate\BaseValidate;
    
    class AdminValidate extends BaseValidate{
        protected $rule = [
            'password'  =>  'require|max:50',
            'username'  =>  'require|max:30',
            'realname'  =>  'require|max:30',
            'group'  =>  'require|max:30'
        ];
    
    }
    

    在管理员添加方法中(Authpost控制器中的adminadd方法)添加:

    //验证器
            $valires=(new AdminValidate())->gocheck($data);
            if ($valires['code']==10002){
                return json($valires);
            }
    

    即可完成,但一定要注意,需要引入该验证器:

    use app\admin\validate\AdminValidate;
    

    四、完成内容管理功能的编写

    4.1 完成管理后台模块搭建

    我们首先实现查看轮播图区域元素:
    在这里插入图片描述

    发现元素包含轮播图标题、简介,以及轮播图标题1、简介1以及背景图。数据库设计如下:

    在这里插入图片描述

    我们通过sqlyog的可视化操作添加轮播图所需要资源的数据,可以通过邮件检查直接获取资源路径及内容:
    在这里插入图片描述

    首先得到轮播图第一张图片的数据:
    在这里插入图片描述
    复制内容填入sqlyog表中:
    在这里插入图片描述
    同理获取所有的内容填入至表:
    在这里插入图片描述
    所有内容填入数据库:
    在这里插入图片描述

    回到index模块下的index控制器中,在index方法中添加获取轮播图数据表中数据:

    <?php
    namespace app\index\controller;
    use think\Controller;
    use think\Db;
    
    class Index extends Controller{
        public function index()
        {
            $banner_res=Db::table('tp_home_banner')
            ->order('id', 'desc')
            ->limit(4)
            ->select();
            print_r($banner_res);
            die;
            return $this->view->fetch();
        }
    
    }
    

    在以上代码中,使用select方法查询轮播图数据表中的数据,查询方式是id的降序,这样使轮播图将会以最新添加的作为显示依据,并且每次只查询前4条;查询结构复制给变量banner_res,使用print_r对该变量进行输出,随后在输出模板前使用die终止,查看输出。
    访问localhost成功获得数据:
    在这里插入图片描述
    在index方法中添加代码,像前端传递banner_res变量,并且删除die代码:

    <?php
    namespace app\index\controller;
    use think\Controller;
    use think\Db;
    
    class Index extends Controller{
        public function index()
        {
            $banner_res=Db::table('tp_home_banner')
            ->order('id', 'desc')
            ->limit(4)
            ->select();
    
            $this->view->assign('banner',$banner_res);
            return $this->view->fetch();
        }
    
    }
    

    接下来我们将在html代码中使用tp的前端模板语法对一些html元素进行控制。我们通过元素查询得知轮播图元素id为homev1:
    在这里插入图片描述在代码中找到id为homev1的元素,查看代码,每个轮播图标签类似,只有默认选项多了个class修饰:
    在这里插入图片描述
    但是有些小伙伴觉得很麻烦,那我们换一种方式,使用tp框架前端的模板语法,类似if判断,从而输出内容:
    在这里插入图片描述

    首先使用volist标签进行循环,在标签中设置循环变量key,该key循环第一次的值为1,当为1使用eq标签判断,是1则输出第一个轮播图的html代码:

    {eq name="k" value="1"}
    

    需要输出的html代码需要使用成对的eq标签包含,结束的eq标签为 {/eq}。
    代码如下:

    <div class="carousel-inner" role="listbox">
                            <!-- Third Slide -->
                            {volist name="banner" id="vo" key="k" }
                            {eq name="k" value="1"}
                            <div class="item active">
                                <!-- Slide Background -->
                                <img src="{$vo.img}" alt="SeoPress Slider" />
                                <div class="bs-slider-overlay"></div>
    
                                <div class="container">
                                    <div class="row">
                                        <div class="col-md-8 col-md-offset-2">
                                            <div class="slide-text slide_style_center">
                                                <h1 class="text-white" data-animation="animated zoomInRight">{$vo.title}</h1>
                                                <p class="text-white m-top-10" data-animation="animated fadeInLeft">{$vo.content}</p>
                                                <a class="btn btn-primary btn-round m-top-30" data-animation="animated fadeInLeft" href="" target="_blank">Read More</a>
                                                <a class="btn btn-default btn-round m-top-30" data-animation="animated fadeInRight" href="" target="_blank">Read More</a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            {/eq}
                            <!-- End of Slide -->
                            {eq name="k" value="2"}
                            <!-- Second Slide -->
                            <div class="item">
                                <img src="{$vo.img}" alt="SeoPress Slider" />
                                <div class="bs-slider-overlay"></div>
                                <div class="container">
                                    <div class="row">
                                        <!-- Slide Text Layer -->
                                        <div class="col-md-6">
                                            <div class="slide-text slide_style_left">
                                                <h1 class="text-white" data-animation="animated fadeInRight">{$vo.title}</h1>
                                                <p class="text-white m-top-10" data-animation="animated zoomInLeft">{$vo.content}
                                                </p>
    
                                                <a class="btn btn-default btn-round m-top-30" data-animation="animated fadeInRight" href="" target="_blank">Read More</a>
                                                <a class="btn btn-primary btn-round m-top-30" data-animation="animated fadeInLeft" href="" target="_blank">Read More</a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <!-- End of Slide -->
                            {/eq}
                            {eq name="k" value="3"}
                            <!-- Third Slide -->
                            <div class="item">
                                <img src="{$vo.img}" alt="SeoPress Slider" />
                                <div class="bs-slider-overlay"></div>
                                <div class="container">
                                    <div class="row">
                                        <!-- Slide Text Layer -->
                                        <div class="col-md-6">
                                            <div class="slide-text slide_style_left">
                                                <h1 class="text-white" data-animation="animated fadeInDown">{$vo.title}</h1>
                                                <p class="text-white m-top-10" data-animation="animated fadeInLeft">{$vo.content}
                                                </p>
    
                                                <a class="btn btn-primary btn-round m-top-30" data-animation="animated fadeInLeft" href="" target="_blank">Read More</a>
                                                <a class="btn btn-default btn-round m-top-30" data-animation="animated fadeInRight" href="" target="_blank">Read More</a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            {/eq}
                            {eq name="k" value="4"}
                            <!-- Fourth Slide -->
                            <div class="item">
                                <img src="{$vo.img}" alt="SeoPress Slider" />
                                <div class="bs-slider-overlay"></div>
                                <div class="container">
                                    <div class="row">
                                        <!-- Slide Text Layer -->
                                        <div class="col-md-6">
                                            <div class="slide-text slide_style_left">
                                                <h1 class="text-white" data-animation="animated fadeInLeft">{$vo.title} <br />
                                                    Online Marketing Needs</h1>
                                                <p class="text-white m-top-10" data-animation="animated fadeInRight">{$vo.content}
                                                </p>
                                                <a class="btn btn-primary btn-round m-top-30" data-animation="animated fadeInLeft" href="" target="_blank">Read More</a>
                                                <a class="btn btn-default btn-round m-top-30" data-animation="animated fadeInRight" href="" target="_blank">Read More</a>
                                            </div>
                                        </div>
    
                                    </div>
                                </div>
                            </div>
                            {/eq}
                            {/volist}
                            <!-- End of Slide -->
                        </div><!-- End of Wrapper For Slides -->
    

    接着我们往下查看首页内容:
    在这里插入图片描述

    个人觉得该区域可以放一个“有利于”之类的宣传语,那么建一表存放标题、图片、内容信息:
    在这里插入图片描述
    在该表中填入网页中原有的数据:
    在这里插入图片描述
    在index控制器中添加查询tp_home_advantageous表数据的代码并将结果传至前端:

    <?php
    namespace app\index\controller;
    use think\Controller;
    use think\Db;
    
    class Index extends Controller{
        public function index()
        {
            $banner_res=Db::table('tp_home_banner')
            ->order('id', 'desc')
            ->limit(4)
            ->select();
    
            $advantageous_res=Db::table('tp_home_advantageous')
            ->order('id', 'desc')
            ->limit(6)
            ->select();
    
            $this->view->assign('advantageous',$advantageous_res);
            $this->view->assign('banner',$banner_res);
            return $this->view->fetch();
        }
    
    }
    

    修改前端代码,发现该区域代码的html几乎一致,前3个的class=“service-item sm-m-top-65”,后3个的class=“service-item m-top-65”:

    <div class="main-service-area text-center m-top-80">
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item sm-m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service1.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">Search Engine Optimization</h5>
                                        <p class="text-black m-top-20">With our 17+ years of experience, our SEO services will get your site ranking.</p>
                                    </div>
                                </div>
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item sm-m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service3.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">Content Marketing</h5>
                                        <p class="text-black m-top-20">From blogs and social posts to 
                                            infographics videos we create and promote quality.</p>
                                    </div>
                                </div>
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item sm-m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service2.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">Social Media Marketing</h5>
                                        <p class="text-black m-top-20">Boost brand awareness and reach your 
                                            customers on a human level.</p>
                                    </div>
                                </div>
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service4.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">Web Design & Development</h5>
                                        <p class="text-black m-top-20">Our designers and developers will create an attractive, SEO-friendly & fully functional.</p>
                                    </div>
                                </div>
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service5.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">eCommerce Solutions</h5>
                                        <p class="text-black m-top-20">With our 17+ years of experience, 
                                            our SEO services will get your site ranking.</p>
                                    </div>
                                </div>
                                <div class="col-md-4 col-sm-6">
                                    <div class="service-item m-top-65">
                                        <div class="service-icon">
                                            <img src="/home/assets/images/service6.png" alt="" />
                                        </div>
                                        <h5 class="text-info m-top-50">Inbound Marketing</h5>
                                        <p class="text-black m-top-20">With our ecommerce solutions, 
                                            you'll provide an enjoyable, seamless.</p>
                                    </div>
                                </div>
                            </div>
    

    这是把其它div删除,留下1个div,使用volist标签进行遍历输出值,并且设置循环变量key,使用tp框架的前端判断标签,判断小于4时输出class为col-sm-6:

    {lt name="k" value="4"}col-sm-6{/eq}
    

    当循环后3三位,则是k值大于3,大于3输出col-sm-6,使用gt标签:

    {gt name="k" value="3"}col-sm-6{/eq}
    

    将两个前端代码编写与div中,完整代码如下:

    <div class="main-service-area text-center m-top-80">
        {volist name="advantageous" id="vo" key="k" }
        <div class="col-md-4 col-sm-6">
            <div class='service-item {lt name="k" value="4"}col-sm-6{/eq} {gt name="k" value="3"}col-sm-6{/eq}'>
                <div class="service-icon">
                    <img src="{$vo.img}" alt="" />
                </div>
                <h5 class="text-info m-top-50">{$vo.title}</h5>
                <p class="text-black m-top-20">{$vo.content}</p>
            </div>
        </div>
        {/volist}
    </div>
    

    运行结果:
    在这里插入图片描述
    接着往下,查看页面区域:
    在这里插入图片描述

    我们将该页面编写成产品展示区域。新建一数据库表:
    在这里插入图片描述
    填入内容:
    在这里插入图片描述在index控制器index方法中添加product数据库查询代码并传至前端:

    <?php
    namespace app\index\controller;
    use think\Controller;
    use think\Db;
    
    class Index extends Controller{
        public function index()
        {
            $banner_res=Db::table('tp_home_banner')
            ->order('id', 'desc')
            ->limit(4)
            ->select();
    
            $advantageous_res=Db::table('tp_home_advantageous')
            ->order('id', 'desc')
            ->limit(6)
            ->select();
    
            $product_res=Db::table('tp_home_product')
            ->order('id', 'desc')
            ->limit(2)
            ->select();
    
            $this->view->assign('product',$product_res);
            $this->view->assign('advantageous',$advantageous_res);
            $this->view->assign('banner',$banner_res);
            return $this->view->fetch();
        }
    
    }
    

    随后在html代码中输出内容即可:

    <section id="leading" class="leading bg-primary sections2">
                    <div class="container">
                        <div class="row">
                            <div class="main-leading">
                                <div class="col-md-6">
                                    <div class="leading-content">
                                        <div class="head-title">
                                            <h2 class="text-white">{$product[0]['title']}</h2>
                                            <p class="m-top-30 text-white">{$product[0]['specs']}</p>
    
                                            <div class="separator2 hv2"><span></span><span></span><span></span></div>
                                        </div>
    
                                        <p class="m-top-40">{$product[0]['content']}</p>
                                        <a href="" class="btn btn-default btn-round m-top-20">Our Team</a>
    
                                    </div>
                                </div>
                                <div class="col-md-5">
                                    <div class="leading-img sm-m-top-50">
                                        <img src="{$product[0]['img']}" alt="" />
                                    </div>
                                </div>
                            </div>
                        </div><!-- End off row-->
                    </div><!-- End off container -->
                </section><!-- End off leading section-->
                <!--Allies Section-->
                <section id="allies" class="allies sections">
                    <div class="container">
                        <div class="row">
    
                            <div class="main-allies">
                                <div class="col-md-5">
                                    <div class="allies-img">
                                        <img src="{$product[1]['img']}" alt="" />
                                    </div>
                                </div>
    
                                <div class="col-md-7">
                                    <div class="allies-content sm-m-top-50">
                                        <div class="head-title">
                                            <h2 class="text-black">O{$product[1]['title']}</h2>
                                            <h5 class="text-black m-top-30">{$product[1]['content']}</h5>
                                            <div class="separator2"><span></span><span></span><span></span></div>
    
                                        </div>
                                        <p class="m-top-40">{$product[1]['specs']}</p>
    
                                        <a href="" class="btn btn-primary btn-round m-top-30">Portfolio</a>
    
                                    </div>
                                </div>
                            </div>
    
                        </div><!-- End off row -->
                    </div><!--End off container -->
                </section><!-- End off Allies Section-->
    

    接着往下看:
    在这里插入图片描述

    该区域可以更改成文章的展示,创建已数据库表:
    在这里插入图片描述
    添加内容:
    在这里插入图片描述

    查看html代码:

    <div class="col-md-4 col-sm-6">
                                            <div class="studies-item">
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-01.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
                                                    <div class="custom-hover"></div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Acme Corporation</a></h4>
                                                    <p class="m-top-10">Objective: Build a larger twitter community</p>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-4 col-sm-6">
                                            <div class="studies-item xs-m-top-35">
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-02.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Soylent Corp </a></h4>
                                                    <p class="m-top-10">Objective: Make tone & branding consistency</p>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-4 col-sm-6">
                                            <div class="studies-item sm-m-top-35">
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-03.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Umbrella Corporation</a></h4>
                                                    <p class="m-top-10">Objective: Eliminate the residue of black-hat methods</p>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-4 col-sm-6">
                                            <div class="studies-item m-top-35">
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-04.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Initech</a></h4>
                                                    <p class="m-top-10">Objective: Improve site load speed & functionality</p>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-4 col-sm-6">
                                            <div class="studies-item m-top-35">
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-05.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded">
                                                        <a href="">
                                                            <span class="icon icon-arrows-2 hvr-hang"></span>
                                                        </a>
                                                    </div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Vehement Capital Partners </a></h4>
                                                    <p class="m-top-10">Objective: Increase nationwide sales</p>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="col-md-4 col-sm-6">
                                            <div class="studies-item m-top-35">
    
                                                <div class="studies-feature border">
                                                    <img class="img-rounded" src="/home/assets/images/studies-img-06.jpg" alt="" />
                                                    <div class="studies-overlay img-rounded">
                                                        <a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a>
                                                    </div>
                                                </div>
                                                <div class="studies-conten m-top-30">
                                                    <h4><a href="">Massive Dynamic</a></h4>
                                                    <p class="m-top-10">Objective: Increase qualified traffic</p>
                                                </div>
                                            </div>
                                        </div>
    

    发现该html代码中前3个div的class有所变化,后3个div布局内容则无变化。我们使用eq标签使前3个div照原样输出,后3个div遍历输出:

    {volist name="article" id="vo" key="k" }
    {eq name="k" value="1"}
     <div class="col-md-4 col-sm-6">
         <div class="studies-item">
             <div class="studies-feature border">
                 <img class="img-rounded" src="{$vo.img}" alt="" />
                 <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
                 <div class="custom-hover"></div>
             </div>
             <div class="studies-conten m-top-30">
                 <h4><a href="">{$vo.title}</a></h4>
                 <p class="m-top-10">{$vo.content}</p>
             </div>
         </div>
     </div>
     {/eq}
     {eq name="k" value="2"}
     <div class="col-md-4 col-sm-6">
         <div class="studies-item xs-m-top-35">
             <div class="studies-feature border">
                 <img class="img-rounded" src="{$vo.img}" alt="" />
                 <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
             </div>
             <div class="studies-conten m-top-30">
                 <h4><a href="">{$vo.title}</a></h4>
                 <p class="m-top-10">{$vo.content}</p>
             </div>
         </div>
     </div>
     {/eq}
     {eq name="k" value="3"}
     <div class="col-md-4 col-sm-6">
         <div class="studies-item sm-m-top-35">
             <div class="studies-feature border">
                 <img class="img-rounded" src="{$vo.img}" alt="" />
                 <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
             </div>
             <div class="studies-conten m-top-30">
                 <h4><a href="">{$vo.title}</a></h4>
                 <p class="m-top-10">{$vo.content}</p>
             </div>
         </div>
     </div>
     {/eq}
     {gt name="k" value="3"}
     <div class="col-md-4 col-sm-6">
         <div class="studies-item m-top-35">
             <div class="studies-feature border">
                 <img class="img-rounded" src="{$vo.img}" alt="" />
                 <div class="studies-overlay img-rounded"><a href=""><span class="icon icon-arrows-2 hvr-hang"></span></a></div>
             </div>
             <div class="studies-conten m-top-30">
                 <h4><a href="">{$vo.title}</a></h4>
                 <p class="m-top-10">{$vo.content}</p>
             </div>
         </div>
     </div>
     {/gt}
     {/volist}
    

    在index控制器的首页方法index中添加对article表数据的查询:

    <?php
    namespace app\index\controller;
    use think\Controller;
    use think\Db;
    
    class Index extends Controller{
        public function index()
        {
            $banner_res=Db::table('tp_home_banner')
            ->order('id', 'desc')
            ->limit(4)
            ->select();
    
            $advantageous_res=Db::table('tp_home_advantageous')
            ->order('id', 'desc')
            ->limit(6)
            ->select();
    
            $product_res=Db::table('tp_home_product')
            ->order('id', 'desc')
            ->limit(2)
            ->select();
    
            $article_res=Db::table('tp_home_article')
            ->order('id', 'desc')
            ->limit(6)
            ->select();
    
            $this->view->assign('article',$article_res);
            $this->view->assign('product',$product_res);
            $this->view->assign('advantageous',$advantageous_res);
            $this->view->assign('banner',$banner_res);
            return $this->view->fetch();
        }
    
    }
    

    其它的前端内容通过数据库更改不再赘述。

    4.2 完成通过后台设置更改与添加前端内容

    创建控制器Contentmanger,添加方法bannerManger,bannerManger方法跳转到一页面用于显示banner数据,点击每条数据可进行编辑:
    在这里插入图片描述

    由于有数据的查询,在控制器中需要查询banner表数据,代码为:

    <?php
    /**
     * |-----------------------
     * | 页面跳转
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    use think\Db;
    use think\facade\Request;
    
    class Contentmanger extends Base{
    
        //官网首页内容管理
        public function bannerManger(){
    
            $list=Db::table('tp_home_banner')
            ->order('id')
            ->select();
            
            $this->view->assign('list',$list);
            return $this->view->fetch();
        }
    }
    

    此处可添加验证器检测传入值是否正确,为了节省篇幅接下来的代码中不再过多的添加其它内容。
    html代码前端的编辑修改按钮,使用了url方法传参,传参后获取该id的内容,方便进行修改:
    在这里插入图片描述
    点击编辑后将会可以随意修改banner的值:
    在这里插入图片描述
    点击choosefile可选择img文件,修改banner图片。

    创建一控制器用来管理内容修改操作的逻辑,创建一php文件名为 Contentmangerpost ,添加 bannerEdit 方法:

    <?php
    /**
     * |-----------------------
     * | 页面跳转
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    use think\Db;
    use think\facade\Request;
    use \think\File;
    
    use app\admin\model\Goods;
    use app\admin\validate\IdValidate;
    use app\admin\code\ReturnCodeInfo;
    
    class Contentmangerpost extends Base{
        //轮播图编辑
        public function bannerEdit(){
            $save_path='/public';
            $save_path_='/uploads/imgs/';
            $request_data = Request::post();
            $con['id']=$request_data['id'];
            $data['title']=$request_data['title'];
            $data['title_2']=$request_data['title_2'];
            $data['content']=$request_data['content'];
            $data['content_2']=$request_data['content_2'];
    
    
            // 获取表单上传文件 例如上传了001.jpg
            $file = request()->file('file');
            if($file){
                // 移动到框架应用根目录/uploads/ 目录下
                $info = $file->move('..'.$save_path.$save_path_);
                if($info){
                    // 成功上传后 获取上传信息
                    $data['img']=$save_path_.str_replace('\\','/',$info->getSaveName());
                }else{
                    // 上传失败获取错误信息
                    echo $file->getError();
                }  
            }else{
                $data['img']='/home/assets/images/slide-bg-01.jpg';
            }
    
    
            $res=Db::name('home_banner')
            ->where($con)
            ->update($data);
    
            if($res){
                return json((new ReturnCodeInfo())->actionSuccess());
            }else{
                return json((new ReturnCodeInfo())->actionError());
            }
        }
    }
    

    以上代码使用request()->file(‘file’);判断是否接收到file值,如接收到说明用户选择了新图片,那么使用move方法保存图片,通过getSaveName方法获取保存图片名。最终更新至数据库中。其它数据的更新方法与该步骤类似,不再赘述。接下来通过拖拽实现web并且绑定数据。

    五、完成页面拖拽生成并绑定数据功能的编写

    拖拽页面在此提供一个思想,通过bootstrap的layoutit可视化布局可以完成简单页面拖拽生成,需要完成更多复杂的界面需要对layoutit进行二次开发。本篇内容为一个demo,通过可视化layoutit生成界面且进行代码替换完成对于thinkphp模板的制作,最后通过可视化数据绑定生成php代码。

    5.1 完成拖拽界面的前端搭建

    首先为layoutit添加一个控制器并更改资源路径,此操作不再赘述。部署完成后打开界面:
    在这里插入图片描述

    可拖拽布局实现界面编辑:
    在这里插入图片描述

    拖拽成如下界面:
    在这里插入图片描述
    点击下载可查看生成的html代码:
    在这里插入图片描述

    在点击下载时,我通过js保存了生成的代码:

    	  var template=''; 
    	  $("[data-target=#downloadModal]").click(function(e) {
    	    e.preventDefault();
    	    downloadLayoutSrc();
    	  });
    	
    	  function downloadLayoutSrc() {
    	    var e = "";
    	    $("#download-layout").children().html($(".demo").html());
    	    var t = $("#download-layout").children();
    	    t.find(".preview, .configuration, .drag, .remove").remove();
    	    t.find(".lyrow").addClass("removeClean");
    	    t.find(".box-element").addClass("removeClean");
    	    t.find(".lyrow .lyrow .lyrow .lyrow .lyrow .removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".lyrow .lyrow .lyrow .lyrow .removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".lyrow .lyrow .lyrow .removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".lyrow .lyrow .removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".lyrow .removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".removeClean").each(function() {
    	      cleanHtml(this)
    	    });
    	    t.find(".removeClean").remove();
    	    $("#download-layout .column").removeClass("ui-sortable");
    	    $("#download-layout .row-fluid").removeClass("clearfix").children().removeClass("column");
    	    if ($("#download-layout .container").length > 0) {
    	      changeStructure("row-fluid", "row")
    	    }
    	    formatSrc = $.htmlClean($("#download-layout").html(), {
    	      format: true,
    	      allowedAttributes: [
    	        ["id"],
    	        ["class"],
    	        ["data-toggle"],
    	        ["data-target"],
    	        ["data-parent"],
    	        ["role"],
    	        ["data-dismiss"],
    	        ["aria-labelledby"],
    	        ["aria-hidden"],
    	        ["data-slide-to"],
    	        ["data-slide"]
    	      ]
    	    });
    	    $("#download-layout").html(formatSrc);
    	    $("#downloadModal textarea").empty();
    	    $("#downloadModal textarea").val(formatSrc)
    	    console.log(formatSrc);
    	    template=formatSrc;
    	  }
    

    此代码为layoutit的js代码,在此基础上我新建了已js全局变量保存数据,变量为template,在js代码清洗完成后把清洗后的值赋值给全局变量template。
    随后点击保存:

    <button class="btn btn-primary" onclick="bc()">保存</button>
    

    保存按钮点击后对应的js代码为:

    function bc(){
                    {literal} 
                    template='{$head|raw}'+'<body style="min-height: 816px; cursor: auto;" class="devpreview sourcepreview">'+template+'</body>';
                    {/literal} 
                    $.ajax({
                        type:'post',
                        url:'/index.php?s=/admin/Autoviewpost/test/',
                        data:{"template":template},
                        dataType:"json", 
                        success:function(data){
        
                        },error:function(jqXHR){
        
                        }
                    })  
                  }
    

    在因为生成的代码需要一定的js文件引入,在此我添加了{$head|raw}为前端的模板代码,使用了{literal} 标签对thinkphp的模板代码进行修饰,表示不解析其中内容。head变量的内容为:

    $head='<link href="/autoview/css/bootstrap-combined.min.css" rel="stylesheet">
            <link href="/autoview/css/layoutit.css" rel="stylesheet">
            <!-- Le styles -->
            <link href="/autoview/css/bootstrap-combined.min.css" rel="stylesheet">
            <link href="/autoview/css/layoutit.css" rel="stylesheet">
            
            <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
            <!--[if lt IE 9]>
                    <script src="/autoview/js/html5shiv.js"></script>
                <![endif]-->
            
                <!-- Fav and touch icons -->
                <link rel="shortcut icon" href="/autoview/img/favicon.png">
                
                <script type="text/javascript" src="/autoview/js/jquery-2.0.0.min.js"></script>
                <!--[if lt IE 9]>
                <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
                <![endif]-->
                <script type="text/javascript" src="/autoview/js/bootstrap.min.js"></script>
                <script type="text/javascript" src="/autoview/js/jquery-ui.js"></script>
                <script type="text/javascript" src="/autoview/js/jquery.ui.touch-punch.min.js"></script>
            <script type="text/javascript" src="/autoview/js/jquery.htmlClean.js"></script>
            <script type="text/javascript" src="/autoview/ckeditor/ckeditor.js"></script>
            <script type="text/javascript" src="/autoview/ckeditor/config.js"></script>
            <script type="text/javascript" src="/autoview/js/scripts.js"></script>';
    

    点击保存后,生成的html代码文本将会传到Autoviewpost控制器下的test方法中,test方法代码如下:

    public function test(){
            $request_data = Request::post();
            $template=$request_data['template'];
    
            /* $regex4="/<div class=\"media\".*?>.*?<\/div>.*?<\/div>/ism";   */
            $template=preg_replace(getMediaReStr(),getMediaHtmlStr(),$template);//media 替换
            $template=preg_replace(getCarouselReStr(),getCarouselHtmlStr(),$template);//轮播图 替换
            $template=preg_replace(getThumbnailsReStr(),getThumbnailsHtmlStr(),$template);//缩略图 替换
            $template=preg_replace(getUnitReStr(),getUnitHtmlStr(),$template);//概述 替换
    
            $template=str_replace("{eq name=&quot;key&quot; value=&quot;1&quot;}active{/eq}",'{eq name="key" value="1"}active{/eq}',$template);
            
            file_put_contents(dirname(dirname(__FILE__)).'\view\templates\t1.html',$template);
            /* print_r($request_data['template']);  */
        }
    

    该方法在接收值后对一部分进行替换。使用preg_replace对文本进行替换,在该对比中我使用了正则对数据进行匹配,该方法我编写在common公共函数的php文件中,地址为application\common.php,内容为:

    <?php
    // +----------------------------------------------------------------------
    // | ThinkPHP [ WE CAN DO IT JUST THINK ]
    // +----------------------------------------------------------------------
    // | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
    // +----------------------------------------------------------------------
    // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
    // +----------------------------------------------------------------------
    // | Author: 流年 <liu21st@gmail.com>
    // +----------------------------------------------------------------------
    
    // 应用公共文件
    function arrunset(&$arr){
        array_splice($arr,0,1);
    }
    
    //Media php code
    function getMediaHtmlStr(){
        $str='{volist name="media" id="data" }'.
        '<div class="media">'.
        '<a href="{$data.src}" class="pull-left"><img src="{$data.img}" class="media-object" alt=\'\' /></a>'.
        '<div class="media-body">'.
        '<h4 class="media-heading">'.
        '{$data.title}'.
        '</h4> {$data.content}'.
        '</div>'.
        '</div>'.
        '{:arrunset($media)}'.
        '{/volist}';
        return $str;
    }
    //Media regex str
    function getMediaReStr(){
        $re="/<div class=\"media\".*?>.*?<\/div>.*?<\/div>/ism"; 
        return $re;
    }
    
    //轮播图 php code
    function getCarouselHtmlStr(){
        $str='<div class="carousel slide" id="carousel-998124"><div class="carousel-inner"> '.
        '{volist name="banner" id="data"}'.
        '<div class=\'item {eq name="key" value="1"}active{/eq} \'>'.
        '<img alt="" src="{$data.img}" />'.
        '<div class="carousel-caption">'.
        '<h4>'.
        '{$data.title}'.
        '</h4>'.
        '<p>'.
        '{$data.content}'.
        '</p>'.
        '</div>'.
        '</div>'.
        '{/volist} '.
        '</div> '.
        '<a data-slide="prev" href="#carousel-998124" class="left carousel-control">‹</a><a data-slide="next" href="#carousel-998124" class="right carousel-control">›</a></div>';
        return $str;
    }
    //轮播图 regex str
    function getCarouselReStr(){
        $re="/<div class=\"carousel slide\".*?>.*?class=\"right carousel-control\">.*?<\/div>/ism"; 
        return $re;
    }
    
    
    //缩略图 php code
    function getThumbnailsHtmlStr(){
        $str='<ul class="thumbnails">'.
        '{volist name="article" id="data"}'.
        '<li class="span4">'.
        '<div class="thumbnail"> <img alt="300x200" src="{$data.faceimg}">'.
        '<div class="caption" contenteditable="true">'.
        '<h3>{$data.title}</h3>'.
        '<p>{$data.content}</p>'.
        '<p><a class="btn btn-primary" href="#">浏览</a> <a class="btn" href="#">分享</a></p>'.
        '</div>'.
        '</div>'.
        '</li>'.
        '{/volist}'.
        '</ul>';
        return $str;
    }
    //缩略图 regex str
    function getThumbnailsReStr(){
        $re="/<ul class=\"thumbnails\".*?>.*?<\/ul>/ism"; 
        return $re;
    }
    
    //概述 php code
    function getUnitHtmlStr(){
        $str='{volist name="ad" id="data" offset="0" length=\'1\'}'.
        '<div class="hero-unit" contenteditable="true">'.
        '<h1>{$data.title}</h1>'.
        '<p>{$data.content} </p>'.
        '<p><a class="btn btn-primary btn-large" href="#">参看更多 »</a></p>'.
        '</div>'.
        '{:arrunset($ad)}'.
        '{/volist}' ;
        return $str;
    }
    //概述 regex str
    function getUnitReStr(){
        $re="/<div class=\"hero-unit\".*?>.*?<\/div>/ism"; 
        return $re;
    }
    

    使用不同的方法返回不同组件、html代码的正则匹配,替换成所需的带有thinkphp框架语法的html代码,这些代码同样在common文件中。完成替换后由于发现某些字符需要进行替换,编写代码:

    $template=str_replace("{eq name=&quot;key&quot; value=&quot;1&quot;}active{/eq}",'{eq name="key" value="1"}active{/eq}',$template);
    

    完成清洗替换后生成html模板生成危机:

    file_put_contents(dirname(dirname(__FILE__)).'\view\templates\t1.html',$template);
    

    由于是demo,所以位置写死了。
    随后访问Autoview控制器下的createcontrol方法(页面没写):
    在这里插入图片描述
    输入你想要生成的控制器名、方法名,该方法需要绑定数据表中哪些元素,以及绑定的页面路径:
    在这里插入图片描述
    输入完成后点击提交,数据将会传到Autoview控制器中的buildcontrol方法中,方法代码如下:

    //控制器生成 方法名及数据库
        public function buildcontrol(){
            $request_data = Request::post();
            $data['controll'] = $request_data['controll'];
            $data['function']=$request_data['function'];
            $data['datas']=$request_data['datas'];
            $data['templatepath']=$request_data['templatepath'];
    
            $controlcode='<?php
    
            namespace app\admin\controller;
            use think\Controller;
            use think\Db;
            
            class '.$data['controll'].' extends AutoviewBase{
            
                public function '.$data['function'].'(){
            
                    return $this->view->fetch(dirname(dirname(__FILE__)).'.$data['templatepath'].');
                }
            }';
    
            file_put_contents(dirname(dirname(__FILE__)).'\controller\\'.$data['controll'].'.php',$controlcode);
    
            $res = Url_datas::create($data);
    
            if($res){
                return json((new ReturnCodeInfo())->actionSuccess());
            }else{
                return json((new ReturnCodeInfo())->actionError());
            }
        }
    

    该方法controlcode变量为控制器模板变量,该模板文本可以得知该控制器名称为自定义名称,继承于AutoviewBase基类,方法名也是自定义,模板位置根据指定路径进行输出渲染。最后使用 file_put_contents 进行控制器生成。最后将数据存入到Url_datas模型中,也是Url_datas表中,数据表结构数据如下:
    在这里插入图片描述

    在这里插入图片描述我们从控制器生成路径中可以得知,是admin内的控制器,我们访问生成的控制器方法查看效果:

    在这里插入图片描述

    数据页面得到显示,这些数据都是数据库中的数据。在创建控制器时,我们在指定数据表及字段时使用的格式内容为如下:

    {             
    "banner":"id,title,img,content",            
     "article":"id,title,content,faceimg",             
     "media":"id,src,img,title,content",             
     "ad":"id,title,content,img"        
      }
    

    数据指定格式为“数据表”:“字段1,字段2…”,通过在AutoviewBase的前置方法中对该json数据进行解析,AutoviewBase基类如下:

    <?php
    /**
     * |-----------------------
     * | 前端页面自定义
     * |-----------------------
     */
    namespace app\admin\controller;
    use think\Controller;
    use think\Db;
    
    class AutoviewBase extends Controller{
        protected $beforeActionList = [
            'toview'
        ];
    
        public function toview(){
            $head='<link href="/autoview/css/bootstrap-combined.min.css" rel="stylesheet">
            <link href="/autoview/css/layoutit.css" rel="stylesheet">
            <!-- Le styles -->
            <link href="/autoview/css/bootstrap-combined.min.css" rel="stylesheet">
            <link href="/autoview/css/layoutit.css" rel="stylesheet">
            
            <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
            <!--[if lt IE 9]>
                    <script src="/autoview/js/html5shiv.js"></script>
                <![endif]-->
            
                <!-- Fav and touch icons -->
                <link rel="shortcut icon" href="/autoview/img/favicon.png">
                
                <script type="text/javascript" src="/autoview/js/jquery-2.0.0.min.js"></script>
                <!--[if lt IE 9]>
                <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
                <![endif]-->
                <script type="text/javascript" src="/autoview/js/bootstrap.min.js"></script>
                <script type="text/javascript" src="/autoview/js/jquery-ui.js"></script>
                <script type="text/javascript" src="/autoview/js/jquery.ui.touch-punch.min.js"></script>
            <script type="text/javascript" src="/autoview/js/jquery.htmlClean.js"></script>
            <script type="text/javascript" src="/autoview/ckeditor/ckeditor.js"></script>
            <script type="text/javascript" src="/autoview/ckeditor/config.js"></script>
            <script type="text/javascript" src="/autoview/js/scripts.js"></script>';
            
            $con['controll']=strtolower(request()->controller());
            $con['function']=strtolower(request()->action());
    
            //by controll and action select rules
            $data_rules=Db::name('url_datas')
            ->where($con)
            ->order('id', 'desc')
            ->field('datas')
            ->find();
            
            //get datas
            $datas=[];
            $tables = json_decode($data_rules['datas'], true);
            foreach($tables as $key => $value){
                $datas[$key]=Db::name($key)
                            ->column($value);
            }
    
            //输出到前端
            foreach($datas as $key => $value){
                $this->view->assign($key,$value);
            }
    
    
            $this->view->assign('head',$head);
    
            return $this->view->fetch(dirname(dirname(__FILE__)).'\view\templates\t1.html');
        }
    
    }
    

    以上代码中定义了前置操作toview方法,在toview方法中定义了head为头部资源文件,之后使用如下代码获取当前控制器及方法名:

    $con['controll']=strtolower(request()->controller());
    $con['function']=strtolower(request()->action());
    

    把控制器及方法名当作条件至url_datas数据表中查询所需的数据要求及格式:

    //by controll and action select rules
            $data_rules=Db::name('url_datas')
            ->where($con)
            ->order('id', 'desc')
            ->field('datas')
            ->find();
    

    得到了所需数据后,对该数据进行json解析,解析后遍历该数据作为对指定表与数据的查询:

    		$datas=[];
            $tables = json_decode($data_rules['datas'], true);
            foreach($tables as $key => $value){
                $datas[$key]=Db::name($key)
                            ->column($value);
            }
    

    之后使用遍历把得到的数据结果输出到前端:

     //输出到前端
        foreach($datas as $key => $value){
            $this->view->assign($key,$value);
        }
    

    最后把head传递值前端代码,渲染输出:

     $this->view->assign('head',$head);
    return $this->view->fetch(dirname(dirname(__FILE__)).'\view\templates\t1.html');
    

    以上内容准备过于匆忙,只讲解了实现中较为重要的地方,很多优化及细节没有说明,希望下次将会编写一份完全的教程给大家!如有错误欢迎指出,想要深入学习可以关注博主,点赞博主、收藏博文,谢谢~
    原创作品@CSDN 1_bit https://blog.csdn.net/A757291228

    展开全文
  • 编写有效用例

    千次阅读 2012-07-11 15:48:15
    编写有效用例 ISBN978-7-121-16672-3 [美]Alistair Cockburn(阿利斯泰尔.科伯恩) 著 王雷,张莉译 2012年5月出版 定价:69.00元 16开 340页 宣传语:一项技术产品只有在获得了Jolt奖之后才能真正成为行业...

    Jolt 大奖精选丛书

    编写有效用例

    ISBN978-7-121-16672-3

    [美]Alistair Cockburn(阿利斯泰尔.科伯恩) 著

    王雷,张莉译

    2012年5月出版

    定价:69.00元

    16开

    340页

    宣传语:一项技术产品只有在获得了Jolt奖之后才能真正成为行业的主流,一本技术图书只有在获得了Jolt奖之后才能真正奠定经典的地位。

    内 容 简 介

        Jolt大奖素有“软件业之奥斯卡”的美称,本丛书精选自Jolt历届获奖图书,以植根于开发实践中的独到工程思想与杰出方法论为主要甄选方向。本书作者Alistair Cockburn,凭借自己在面向对象领域的丰富经验,并参考其他专家的建议,扩展了典型的用例处理方法,为软件开发人员编写用例提供了一种“基本、具体和实用的”指南。本书完整地叙述了有关用例的初、中、高级概念,并提供了大量的、正反两方面的用例编写实例,是一本概念清晰、结构完整、内容丰富的专业图书。

    本书荣获2001年Jolt世界图书大奖,适用于不同知识层次的软件工作、研究人员和用例编写人员。

    经久不息的回荡

    今时的读书人,不复有无书可读之苦,却时有品种繁多而无从择优之惑,甚而专业度颇高的技术书领域,亦日趋遭逢乱花迷眼的境地。此时,若得觅权威书评,抑或有公信力的排行榜,可按图索骥,大大增加选中好书的命中率。然而,如此良助,不可多得,纵观中外也唯见一枝独秀——素有“软件业奥斯卡”之美誉的Jolt奖!

    震撼世界者为谁

    在计算设备已经成为企业生产和日常生活之必备工具的今天,专业和大众用户对于软件的功能、性能和用户体验的要求都在不断提高。在这样的背景下,如何能够发挥出软件开发的最高效率和最大效能,已经是摆在每一个从业者面前的重大课题,而这也正是Jolt大奖横空出世的初衷及坚持数年的宗旨。

    Jolt大奖历时20余年,在图书及软件业知名度极高,广受推崇。奖如其名,为引领计算机科学与工程发展主流,Jolt坚持将每年的奖项只颁给那些给整个IT业界带来震撼结果的图书、工具、产品及理念等,因一流的眼光及超高的专业度而得以闻名遐迩,声名远播。

    除图书外,Jolt针对软件产品设有诸多奖项分类,如配置管理、协作工具、数据库引擎/数据库工具、设计工具/建模、开发环境、企业工具、库/框架、移动开发工具等。但图书历来是Jolt大奖中最受瞩目且传播最广的一个奖项分支。Jolt曾设有通用类图书、技术类图书等分类,每个分类又设有“卓越奖”(Jolt Award,一般为一个)和“生产力奖”(ProductivityAward,一般为2或3个)。获奖技术图书一经公布,即打上经典烙印,可谓一举“震撼全世界”(赞助商Jolt可乐的广告词)。

    作为计算机技术图书的厚爱者,我们总在追问——是谁在震撼世界,是谁在照亮明天?Jolt大奖恰似摆在眼前的橱窗,让我们可以近距离观看潮流在舞蹈,倾听震撼在轰鸣!

    朝花夕拾为哪般

    Jolt像是一年一度的承诺,在茫茫书海中为我们淘砺出一批批经得起岁月冲刷的杰作,头顶桂冠的佳作也因而得以一批批引进中国,为国人开阔了眼界,滋补了技术养分。然而,或因技术差距造就的生不逢时、水土不服,或因翻译、制作的不如人意,抑或是疏于宣传等诸多原因,这些经典著作在国内出版后,尽管不乏如获至宝的拥趸,却仍不为诸多人所知,从而与大量本应从中获益的读者擦肩而过。既然这生生错失的遗憾本不该发生,则更不应延续。为此,我们邀国外出版同行、国内技术专家一道,踏上朝花夕拾之路,竭力为广大读者筛选出历久弥新、震撼依旧的Jolt图书精品。

    Jolt获奖图书皆由业界专家一致评出,并得到软件从业人员的高度认可,虽然这些书今天读来,不再能看到上世纪史诗时代那般日新月异的理论突破,以及依赖于高深繁复的科学研究所取得的系统化成果,更多是在日复一日的开发实践中总结和提炼出来的工程思想和方法论。重新选材之所以有所弃取,从Jolt多年来的评奖规律中可窥端倪——

    一万小时真理见

    凡是在工程思想领域取得革命性、颠覆性突破的图书,就被归于“震撼”获奖分类。比如,从基于过程的程序设计模型过渡到面向对象的全新模型,就是软件开发思想上的一次带来巨大震撼的革命;再比如,打破传统的瀑布模型而转向持续集成的软件交付模型,这也是一场业界的重大思想转变。像这样的重大思想突破,可以说是数年甚至数十年一遇的,而荣获Jolt大奖的图书中更为常见的,则是基于最佳实践的“生产效率”获奖者。获得此类殊荣的图书,都是作者们从平凡的、重复的,甚至用一般人的眼光看来不怎么起眼的日常开发实践中,以独具的慧眼、过人的耐心和大胆的创新,闯开一条不平常道路的心血与经验总结。

    这些图书所涉及的主题,都是普通的软件开发人员每天要面对的工作——代码阅读、撰写测试用例、修复软件问题……但就是这样貌似平淡无奇的工作,是否能每一天、每一个项目都做好,着实拉开了软件开发人员素质的差距,也决定了软件企业开发出来的产品和服务的质量。我们中国有一句古话,叫做熟能生巧;某位著名企业家也说过一句家喻户晓的名言:“把简单的事千百万次地做好,就是不简单的。”这些朴素而实际的真理,同样也是本套丛书最能彰显的所谓程序员精神。它建立在脚踏实地的实践基础之上,也充满了对于自由和创新的向往。

    名作可堪比名曲

    就不因岁月流逝而褪色来说,与这些Jolt名作相媲美者,只有那些百年响彻、震撼古今的经典名曲。希望本丛书带给大家的每部著作,也如百听不厌的乐曲,掩卷良久方余音绕梁,真知存心。仔细想来,软件开发与古典音乐岂非有异曲同工之妙?既是人类心智索问精确科学的探究,亦是寻觅美学享受的追求。工程是艺术的根基,而艺术是工程的极致。衷心地希望各位读者能够认真阅读本丛书的本本珍品,并切实地用于自己的日常工作中,在充分享受大师魅力的同时,为中国的软件事业谱写更多、更震撼的乐章。

     

    电子工业出版社博文视点

    二零一二年春 

    译者序

    如何准确地描述用户需求,一直是软件开发领域研究的热点。Ivar Jacobson首先提出了“用例”的概念,用例在描述系统行为需求、软件系统或业务流程等方面被广泛使用。但是,用例应该描述什么?描述到什么程度?这些问题,却非常难以回答。《编写有效用例》一书很好地回答了这些问题,并深受读者欢迎。非常感谢电子工业出版社张春雨和符隆美编辑选择再版本书。

    本书荣获了2001年度《美国软件开发杂志》的“Joltand Productivity Award”,这个奖项奖励本年度全球软件业最杰出的技术和书籍,共分6大类,其中有一类是图书。2001年全球只有4本书获得了“Jolt andProductivity Award”,最优秀的图书荣获“Jolt Award”,其余3本书共享“Productivity Award”。本书获得的就是2001年度“Productivity Award”。

    本书通过大量来自实际项目的用例,总结出了用例编写和培训中的一些指导性原则,完整地介绍了用例的基本概念。本书一个重要的特点在于,提出了项目相关人员和利益模型,并且提供了大量的正反两方面实例。本书共分为“引言”、“用例体部分”、“经常讨论的主题”和“对忙于编写用例人的提示”等4个部分。

    “引言部分”介绍了与用例相关的重要概念,包括:用例的定义、用例中3个最基本的概念、用例的基本组成部分、不同项目组的不同用例编写风格等。

    “用例体部分”,由10个章节组成,详细地阐述了执行者、项目相关人员和目标层次等编写用例需要掌握的重要概念,并且提供了大量用例编写的模板。具体章节为“用例是规范行为的契约”、“范围”、“项目相关人员和执行者”、“三个命名的目标层次”、“前置条件、触发事件和保证”、“场景和步骤”、“扩展”、“技术和数据的变化”、“连接用例”和“用例格式”。通过该部分的阅读,读者可以完整地了解用例各个部分的编写原则。

    “经常讨论的主题”部分,由8个章节组成,详细说明了编写用例过程中可能反复出现的问题,例如用例什么时候才算完成、如何从一个用例扩展到多个用例、如何编写基于数据库的小用例、如何对业务过程进行建模、哪些是可能被遗漏的需求及用例在整个过程中的作用等问题。该部分详细解答了用例编写过程中所遇到的问题,有很高的参考价值。

           “对忙于编写用例的人的提示”部分,由3个章节组成,包含了针对用例关键概念的一组提示。这部分分为:“对每个用例的提示”、“对用例集的提示”和“处理用例的提示”。通过阅读这部分,读者可以回顾一下在编写用例过程中可能遇到的关键问题。

    本书由浅入深,每一部分都包括概念、示例、提示和练习(以及部分答案),非常适合软件开发专业人员自学。

    本书作者Alistair Cockburn是一位面向对象领域的国际著名专家,是软件项目开发方面的资深顾问。在他建立的网站http://alistair.cockburn.us/中,相信读者可以找到所需要的资料。

    再版过程中,我们对翻译的内容进行了重新校对,限于我们的翻译水平,书中不可避免有疏漏和错误的地方,望读者指正。参加校对工作的还有聂坤明、路红、王欢、王刚和李康,在此对他们表示感谢!

     

                                                       王雷

    2011年12月于北京

     

    前  言

    为了描述行为需求、软件系统或业务流程,用例正在被越来越多的人所使用。初听起来,编写用例似乎是件很容易的事——只需要写清楚如何使用系统就可以了。但是,当提笔编写时,首先碰到的问题就是:“到底应该写些什么?多写一些,还是少写一些,详细程度如何?”这些问题确实很难回答。编写用例就像写散文一样,全部困难在于既要采用一般的写作方式,又要具有完美的表达能力。很难说什么样的用例是好用例,但是我们却可以去考虑:如何去写,才能写出好用例。

    本书给出了我在用例编写和培训中总结出的一些指导性原则:应该如何思考、观察需要的内容,最终写出更好的用例和用例集。

    本书还给出了大量的例子,既有好的用例,也有不好的用例,以及在不同情况下的编写方式。最重要的是要记住,我们需要的是有用的用例,而不必是一个最好的用例。有时即使是很平常的用例也很有用,甚至远胜于编写许多需求文档。所以无须太紧张,只要写出的东西可读,就说明你已经做出了贡献。

    本书的读者

    本书主要是针对工业界专业人员自学而编写的,因此在组织方式上也有所侧重,就像一本自学指南。本书由浅入深,每一部分都包括概念、示例、提示和练习(部分答案)。

    编写用例的培训教师应给出适当的解释和示例,课程设计人员可以围绕本书设计课程资料,在必要时作为指定读物发布(由于我对许多练习都给出了答案,因此他们必须编写属于自己的考试材料)。

    本书的组织

    本书首先概要地介绍了用例的概念,之后对用例体部分进行了详细阐述,然后是常见的问题,对忙于编写用例的人的提示,以及最后的注意事项。

    “引言”部分对重要的概念进行了介绍,依次讨论了以下问题:“用例是什么样的?”、“何时编写用例?”和“什么样的用例是合法的?”。所有问题的答案都是根据具体情况(如时间、地点、人员及编写的理由等)来确定的。关于这几个问题的讨论虽从本部分开始,但实际上贯穿本书。

    “第一部分,用例体部分”,由多个章节组成,分别阐述了需要掌握的重要概念,以及应编写的模板。具体章节为“用例是规范行为的契约”、“范围”、“项目相关人员和执行者”、“三个命名的目标层次”、“前置条件、触发事件和保证”、“场景和步骤”、“扩展”、“技术和数据的变化”、“连接用例”和“用例格式”。

    “第二部分,经常讨论的主题”,列举了反复出现的特殊问题:“什么时候才算完成”、“扩展到多个用例”、“CRUD和参数化用例”、“业务过程建模”、“遗漏的需求”、“用例在整个过程中的作用”、“用例概述和极端编程”和“错误改正”。

    “第三部分,对忙于编写用例的人的提示”,包含了一组提示,针对那些已经阅读完本书,或者已经了解这些资料,想要回顾一下关键概念的人们。这部分结构如下:“对每个用例的提示”、“对用例集的提示”和“处理用例的提示”。

    本书有4个附录:附录A讨论了“UML中的用例”,附录B包括“部分习题的答案”。最后是附录C(术语表)和附录D(参考文献),即撰写本书时使用的资料列表。

    中文版书中订口处的表示英文版原书页码,便于读者与原书对照阅读,本书的索引所列页码为原英文版页码。

    本书概念的来源

    在20世纪60年代后期,Ivar Jacobson在爱立信公司电话系统工作时提出了后来成为众所周知的“用例”。在20世纪80年代后期,他将用例引入了面向对象编程领域,在这里人们认识到用例可以填补需求分析过程中一个明显的空白。我在20世纪90年代初参加了Jacobson的课程,他和他的小组没用我所使用的目标(goal)和目标失败(goal failure)这两个词,但最后我终于知道他们曾经使用过类似的概念。经过几次比较,他和我都发现我们两个模型之间没有显著的区别。我慢慢地根据最近的想法扩展了他的模型。

    在1994年我为IBM顾问组编写用例指南时,建立了执行者(actor)和目标(goal)概念模型。从而把用例中一些难以理解的事情解释清楚了,并且为如何构造和编写用例提供了指导。从1995年开始,执行者和目标模型就在http://members.aol.com/acockburn及后来的www.usecases.org中正式使用,最后出现在1997年Journal of Object Oriented Programming上我写的一篇文章中,题为“构造带目标的用例”。

    从1994到1999年,尽管在理论上还有几个松散的分支,但用例模型的概念基本保持稳定。在教授和指导过程中,我终于明白了人们为什么在简单的概念上花费了大量时间(我竟然从来没想到,我第一次尝试时也犯过很多同样的错误!)。这些想法,加上“执行者和目标”模型中一些不合理之处,产生了本书及项目相关人员(stakeholder)和利益(interest)模型,该模型是本书提出的一个新概念。

    统一建模语言(UML)对这些概念没什么影响,反之亦然。Jacobson以前的一位同事Gunnar Overgaard编写了大量UML用例的资料,并且保持了Jacobson的风格。然而UML标准开发组受画图工具的影响很大,以至于用例的文本特征在标准中消失了。Gunnar Overgaard 和Ivar Jacobson讨论了我提出的概念,并向我保证这些关于用例的大部分概念都适合放在UML的一个椭圆图中,因此既不会影响UML,也不会被UML标准所提出的概念影响。这意味着你可以使用本书的概念,并与UML1.3用例标准兼容。另一方面,如果你只读了UML标准,由于它根本没有讨论用例的内容及如何去编写,那么你也不知道用例到底是什么、如何使用,并且还可能产生一个危险的想法,即用例由图形而不是文本构成的。本书的目的是告诉你如何编写有效的用例,而标准很少谈及这些,因此我把对UML的评述单独放在附录A中。

    本书所用的实例

    本书编写的实例尽可能取自实际项目,并且有些实例看起来可能不太完美。我所要说明的是,这些实例对项目组的需求来说已经足够了,并且用例编写过程中的那些不完美之处是在误差和经济允许的范围内。

    Addison-Wesley的编辑们说服我把这些实例从原有形式中整理出来,以强调它们正确的形式,而不是它们实际的和可用的形式。希望你通过阅读这些实例发现一些有用之处,并了解项目中的实际编写过程。你可以应用我在这些实例中采用的一些原则,并找到改进它们的方法。这种事情经常发生,因为改进一个人编写的实例是一件永无止境的事情,所以我接受这个挑战和任何批评意见。

    《软件专业人员集锦》中的用例

    《软件专业人员集锦》(The Crystal Collection for Software Professionals)只是图书选集之一,强调了一直被忽略的、发挥人能动性的软件开发技术。其中一些书讨论了某种技术,一些书讨论了项目中的角色,一些书讨论了开发组协作的观点。

    “集锦”遵循下面两个原则。

    n  软件开发是创造和交流相互作用的游戏。当我们提高开发人员的个人技术和开发组协作效率时,它得到了改进。

    n  不同的项目有不同的需求。系统有不同的特征,由大小不同的开发组研制,并且开发组成员有着不同的价值观,优先考虑的事情也不同。因此不可能定义一个最好的软件开发方法。

    “集锦”中的基础读物——Software Development as a Cooperative Game,详细描述了这样一些观点:软件开发是一种合作游戏,方法学是一种合作文化,以及方法学系列化。该书分别对方法学的不同侧面、技术与活动、工作产品和标准进行了讨论。用例所需要讨论的精髓出现在本书的1.2节“你的用例不能作为我的用例”。

    本书是一本技术指南,描述了用例编写过程。尽管你可以在几乎所有项目中使用这些技术,但是必须根据每个项目的实际需要选择模板和编写标准。

    致  谢

    在此我要感谢很多人。感谢那些阅读了本书草稿,并要求对那些可能引起他们的客户、同事及学生混淆的主题予以澄清的人们。特别感谢Russell Walters的鼓励和具体的反馈意见,他是一个经验丰富、对开发组的方向和实际需求有敏锐目光的人。感谢FirePond & Fireman基金保险公司提供了生动的用例实例。Pete McBreen第一个尝试了项目相关人员和利益模型,并加入他的合理想法、实践结果和改进建议。感谢硅谷的Patterns Group仔细阅读了早期的几版草稿,以及他们对不同文稿和概念所做的富有教益的评述。感谢Fort Union Bean & Brew的Mike Jones为子系统用例想出了螺钉图标。

    应该特别提一下Susan Lilly,她严格地阅读本书,并改正能想到的任何疏忽:顺序、内容、格式,甚至用例实例。她所做的大量工作都体现在得到大为改进的最终版本上。

    还有其他一些评阅者提出了详细的意见并给予了鼓励,他们是Paul Ramney,Andy Pols,Martin Fowler,Karl Waclawek,Alan Williams,BrianHenderson-Sellers,Larry Constantine,Russell Gold。Addison-Wesley出版公司的编辑们很好地完成了他们的工作,清除了我不雅观的句型及经常出现的拼写错误。

    感谢听我讲课的人们,他们帮助我找出了书中的错误概念。

    再次感谢我的家人,Deanna,Cameron,Sean和Kieran;还要感谢Fort Union Beans & Brew的人员提供了许多咖啡和营造了一种欢乐的气氛。

    在我维护的网站members.aol.com/acockburn和www.usecases.org上可以找到更多关于用例的材料。为了避免我们将来见面时的尴尬,我名字的发音是Co-burn,o是长元音。

     

    展开全文
  • 软件开发文档模板

    千次阅读 2018-08-13 12:39:53
    中提供了文档的编写模板供开发者参考,在进行具体软件开发时,开发者可根据实际情况采编写,但必须提供双方约定的文档,文档中约定的内容必须描述清楚。 2. 总体要求 2.1 总体功能要求 网络应用环境以 ...
  • 软件文档编写向导

    千次阅读 2016-08-05 11:20:40
    综述: ...为此,我们特意收集了一些在项目开发过程中经常用到的文档模板,这些模板包括格式和简单的写作说明,相信能够帮助大家编写出更加高效、实用的技术文档。在收集过程中,我们十分注重其实用性
  • 需求规格说明书模板

    2010-08-24 17:09:00
    <br /> 1)采用软件需求规格说明模版: 采用需求规格说明书模板在你的组织中要为编写软件需求文档定义一种标准模板。该模板为记录功能需求和各种其它与需求相关的重要信息提供了统一的结构。注
  • 详细设计模板

    千次阅读 2019-06-19 16:20:12
    如果一个软件系统比较简单,层次很少,本文件可以不单独编写,和概要设计说明书中不重复部分合并编写。 方案重点是模块的执行流程和数据库系统详细设计的描述。 1.2背景 应包含以下几个方面的内容: ...
  • 公司技术规范模板

    千次阅读 2019-03-15 10:21:51
    第二章 – 代码编写规范 1. 2. 语言 本文中用到的关键字 必须=强制。不能=强制。要求=强制,如果有特殊情况,需要商讨后决定是否可以解除此次强制。应该=不强制,但如果不遵守,可能会出现意外情况。不应该=不强制,...
  • 软件需求规格说明书模板

    万次阅读 多人点赞 2017-03-13 16:51:39
    软件需求规格说明书是软件开发过程需求分析阶段需要产出的文档,是为了使用户和软件开发者对软件的规格有一个共同的理解而撰写的,软件需求规格说明有标准模板 方法/步骤 ...
  • WEB前端简历模板

    万次阅读 多人点赞 2016-12-02 15:08:34
    经过二十多年的发展,形成了集产业园区运营、金融投资和模板钢结构为一体的集团化企业。产业园区作为联东集团主  要业务板块 , 独创聚合 U 模式,以 ” 联东 U 谷 ” 为品牌 , 成功进驻北京、上海、天津、重庆、...
  • 测试报告模板

    万次阅读 多人点赞 2019-05-15 14:18:03
    查询指定排污企业信息 通过 56 重置搜索框 重置搜索框成功 通过 57 查看排污企业详情信息 进入排污企业详情页 通过 58 企业上架 未上架状态的企业上架成功 59 企业下架 已上架的企业下架成功 60 资源共享-求购信息...
  • 软件需求分析模板

    千次阅读 2016-04-07 13:37:35
     说明这份软件产品需求分析报告是为哪个软件产品编写的,开发这个软件产品意义、作用、以及最终要达到的意图。通过这份软件产品需求分析报告详尽说明了该软件产品的需求规格,包括修正和(或)发行版本号,从而对该...
  • 求职模板

    千次阅读 2014-02-20 13:31:28
     2004年11月份,应用CMM标准进行管理,领导开发了“固定资产数据导出系统”,将学得的理论应用到了实践中,并更深入的理解了CMM方面的知识,获得了实践经验。 2005年4月开始,领导项目组开发“会计人员记账系统”...
  • 软件开发文档编写规范

    万次阅读 多人点赞 2016-08-19 11:17:10
    为了控制好软件产品质量和规范,就必须用大量的文档约束软件工程的进度和状态。浩大的软件工程对于缺少工作和项目经验的人来说,必然是摸不着头脑不知从何开始。为了让大家能够快速适应标准的软件开发过程,今天我为...
  • 软件需求规格书模板

    千次阅读 2019-07-25 10:09:15
    智能RFID资产仓储管理系统PDA端需求文档 软件系统需求说明书主要...软件系统需求说明书另外一个重要的作用是提供一个图软件产品的确定验收标准,进行功能实现的识别和性能、约束条件的设定。 1.概述 1.1 编写目的 【...
  • 软件需求分析报告模板

    千次阅读 2013-02-27 17:43:18
     说明这份软件产品需求分析报告是为哪个软件产品编写的,开发这个软件产品意义、作用、以及最终要达到的意图。通过这份软件产品需求分析报告详尽说明了该软件产品的需求规格,包括修正和(或)发行版本号,从而对该...
  • 测试用例模板和例子

    万次阅读 多人点赞 2017-02-14 15:58:31
    该范例已经包含一个测试用例的模板。 项目/软件 技术出口合同网络申领系统 (企业端) 程序版本 1.0.25       功能模块名 Login  编制人  ...
  • 连锁企业发展越来越迅速,专业、系统及标准化的门店管理是连锁企业持续赢利、发展的根基,是门店竞争力的核心禀赋。一套系统化的管理工具,不仅能够规范及统一各门店的营运流程、制度、表单等,使门店的管理有章可循...
  • Latex 宏包编写,自定义宏包

    千次阅读 2019-05-28 05:56:00
    2019独角兽企业重金招聘Python工程师标准>>> ...
  • 需求分析类文档模板

    千次阅读 2009-09-20 18:20:00
    许多有经验的开发团队在开始需求调查的时候,总会将“软件客户需求权利书”和“软件客户需求义务书”提交给客户,让客户明确其权利与义务,...要求分析人员组织需求获取期间所介绍的信息,并编写软件需求规格说明。 4.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,091
精华内容 13,236
关键字:

产品企业标准编写模板