-
从0到1 区块链的概念到实践
2017-08-18 15:50:12从0到1 区块链的概念到实践 -
一篇文带你从0到1了解建站及完成CMS系统编写
2020-10-24 00:48:06学习目标 了解搭建一般网站的简便方式 了解最原始一般站点搭建 了解内容管理站点搭建 了解权限设计及完成 ...文章为从0到1了解内容管理系统搭建与编写,由于一篇文章内容篇幅过长,文章内容经过压缩,该项目中相学习目标
- 了解搭建一般网站的简便方式
- 了解最原始一般站点搭建
- 了解内容管理站点搭建
- 了解权限设计及完成
- 了解使用设计模式减少代码冗余
- 了解前端拖拽页面生成及生成
- 了解自定义数据的创建
- 了解动态生成的前端页如何绑定自定义数据
开发环境
- 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="key" value="1"}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="key" value="1"}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 -
玩安卓从 0 到 1 之列表一键置顶
2020-12-02 17:06:561、玩安卓从 0 到 1 之总体概览 2、玩安卓从 0 到 1 之项目首页 3、玩安卓从 0 到 1 之首页框架搭建。 4、玩安卓从 0 到 1 之架构思考 5、玩安卓从 0 到 1 之适配器思考 按照惯例,放一下 Github 地址和 apk 下载...前言
系列文章
这篇文章是这个系列的第六篇文章了,下面是前五篇文章:
按照惯例,放一下 Github 地址和 apk 下载地址吧!
apk 下载地址:www.pgyer.com/llj2
Github地址:github.com/zhujiang521…
前因后果
RecyclerView 大家平时都会使用,在本项目中 RecyclerView 使用方法基本都是下面这种:
基本都是文章的列表,包括有下拉刷新和上拉加载,下拉刷新和上拉加载用的是下面这个库:
下面是这个库的依赖:
api 'com.scwang.smart:refresh-layout-kernel:2.0.1' //核心必须依赖 api 'com.scwang.smart:refresh-header-classics:2.0.1' //经典刷新头 api 'com.scwang.smart:refresh-footer-classics:2.0.1' //经典加载
这个库的使用方法在下面会有提及,不过更建议去官方 Github 上看文档。
说到这里,终于要进入正题了!今天到底要实现一个什么功能呢?
故事发生在上周末,我无聊在刷着自己写的玩安卓,在看最新的一些文章,刷了好久,看了很多条目,突然想回到顶部,就只能一直往下滑了,想了下这也太不人性化了吧!好多软件都有一键置顶的功能,也是很方便的,说干就干,那就来给玩安卓也添加一个一键置顶的功能吧!
先来看看最终的实现效果吧!
开始实现
实现
其实核心实现非常简单,只需要下面的一行代码:
mToTopRecycleView.smoothScrollToPosition(0)
这个大家都会,我就不多解释了。
我展示一键置顶按钮的时机是只有在上滑的时候才会展示,平时不展示,下滑的时候也不展示,,当上滑到顶部的时候也不展示。为什么这样设置呢?因为我个人认为只有用户向上滑了才证明用户又可能想回到顶部。
那么下面就来看下怎样设置展示时机吧!
mToTopRecycleView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (!recyclerView.canScrollVertically(-1)) { // 上滑到顶部 } else if (dy < 0) { // 上滑 } else if (dy > 0) { // 下滑 } } })
其实到这里基本已经实现功能了,只需要在布局中添加一个按钮,然后给按钮添加下点击事件就行了。但是。。。。。。
凡事就怕但是,如果只是这一个页面需要一键置顶的话那就简单了,在这个页面布局中加一个按钮就好,但是文章列表有很多个页面啊,不止一个!而且都有下拉刷新和上拉加载的功能,本着程序员的懒劲,还是少写点吧,抽一个控件出来吧,别的地方如果需要使用的话直接用这个控件就行!那么就开始吧!
先来想一下这个控件的父类该是谁,肯定是一个 ViewGroup,其实布局并不难,那就使用 FrameLayout 吧!
class ToTopRecyclerView @JvmOverloads constructor( private val mContext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(mContext, attrs, defStyleAttr)
定义好类就完成了一大半了,为啥呢?万事开头难嘛!头都开了,还怕啥!
接下来在 init 方法中将写好的布局加载进来:
init{ View.inflate(mContext, R.layout.layout_to_top, this) }
来看下布局文件吧:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.scwang.smart.refresh.layout.SmartRefreshLayout android:id="@+id/toTopSmartRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/toTopRecycleView" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.scwang.smart.refresh.layout.SmartRefreshLayout> <ImageView android:id="@+id/toTopIvClick" android:layout_width="@dimen/dp_40" android:layout_height="@dimen/dp_40" android:layout_gravity="right|bottom" android:layout_margin="@dimen/dp_20" android:background="@drawable/to_top_bg" android:padding="@dimen/dp_8" android:src="@drawable/ic_baseline_vertical_align_top_24" android:visibility="gone" /> </merge>
为啥最外面是 merge 就不说了,这要是不会赶快再去看看基础吧。
下面就是 findViewById 了,这个太简单就不贴代码了,浪费时间。
接下来需要想一下咱们想让这个控件完成什么功能,思来想去一共有以下几个功能:
- 设置 RecyclerView 的 adapter
- 设置上拉加载和下拉刷新的事件
- 设置 RecyclerView 的 LayoutManager
可能有人会说,一键置顶不需要吗?如果需要的话那咱们写这个控件的意义何在啊!
下面就根据上面的列表顺序来一个一个写吧!
首先是设置 RecyclerView 的 adapter:
fun setAdapter(adapter: RecyclerView.Adapter<BaseListAdapter.ViewHolder>) { mToTopRecycleView.adapter = adapter }
代码很简单,只是提供给外部一个设置 adapter 的入口。
然后是设置上拉加载和下拉刷新的事件:
fun onRefreshListener(onRefreshListener: () -> Unit, onLoadMoreListener: () -> Unit) { mToTopSmartRefreshLayout.apply { setOnRefreshListener { reLayout -> reLayout.finishRefresh(measureTimeMillis { onRefreshListener.invoke() }.toInt()) } setOnLoadMoreListener { reLayout -> val time = measureTimeMillis { onLoadMoreListener.invoke() }.toInt() reLayout.finishLoadMore(if (time > mLoadTime) time else mLoadTime) } } }
这个方法有必要说下了,这就是文章开头所说那个库的使用方法。
这块使用了一个高阶函数,方法参数是两个函数,通过名称就可以知道第一个是刷新的函数,第二个是加载更多的函数。
最后是设置 RecyclerView 的 LayoutManager,为了省事我决定将这个函数写的方便一些,别的地方调用的时候只需要传入布尔值或者 int ,然后通过判断设置不同的 LayoutManager。
思来想去,本项目中的 RecyclerView 只用到了两种 LayoutManager,分别是 LinearLayoutManager(竖屏) 和 StaggeredGridLayoutManager(横屏),那么就用布尔值作为参数来判断使用哪种 LayoutManager 吧,下面是方法的代码:
fun setRecyclerViewLayoutManager(isLinearLayout: Boolean) { if (isLinearLayout) { mToTopRecycleView.layoutManager = LinearLayoutManager(context) } else { val spanCount = 2 val layoutManager = StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL) mToTopRecycleView.layoutManager = layoutManager layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE } }
OK,这就差不多了。
使用
实现完成了就该使用了,实践是检验真理的唯一标准!
先给首页尝试下,添加到布局中:
<com.zj.core.view.custom.ToTopRecyclerView android:id="@+id/homeToTopRecyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/>
注意!上面写的下面就要进行调用了!
homeToTopRecyclerView.setAdapter(articleAdapter) homeToTopRecyclerView.onRefreshListener({ page = 1 getArticleList(true) }, { page++ getArticleList(true) }) homeToTopRecyclerView.setRecyclerViewLayoutManager(true)
还是那句话,如果只是一个地方调用,怎么写都是对的,但如果很多地方调用的话抽出来就很有必要了!
大家可以下载项目看下历史提交,省了很多代码。
精致的结尾
到这里本篇文章就要和大家说再见了,这篇文章虽然实现的功能很简单,但也能提升一些用户体验!欢迎大家下载体验。
能力一般、水平有限,对大家有帮助的话别忘了三连,有 Github 账号的帮忙点个 Star ,感激不尽!
就这样,下回再见!!!
-
从0到1搭建大数据平台之数据采集系统
2020-07-09 06:40:00关于从0到1搭建大数据平台,之前的一篇博文《如何从0到1搭建大数据平台》已经给大家介绍过了,接下来我们会分步讲解搭建大数据平台的具体注意事项。一、“大”数据海量的数据当你需要搭建大数据平...关于从0到1搭建大数据平台,之前的一篇博文《如何从0到1搭建大数据平台》已经给大家介绍过了,接下来我们会分步讲解搭建大数据平台的具体注意事项。
一、“大”数据
海量的数据
当你需要搭建大数据平台的时候一定是传统的关系型数据库无法满足业务的存储计算要求了,所以首先我们面临的是海量的数据。
复杂的数据
复杂数据的概念和理想数据完全相反。所有数据集都有一定的复杂性,但有一些天生更难处理。通常这些复杂数据集没有定义结构(没有行列结构),经常变化,数据质量很差。比如更新的网页日志,json数据,xml数据等。
高速的数据
高速数据通常被认为是实时的或是准实时的数据流。数据流本质上是在生成后就发给处理器的数据包,比如物联网的穿戴设备,制造业的传感器,车联网的终端芯片等等。处理实时数据流有很多挑战,包括在采集时不丢失数据、处理数据流中的重复记录、数据如何实时写入磁盘存储、以及如何进行实时分析。
二、采集工具
日志采集
我们业务平台每天都会有大量用户访问,会产生大量的访问日志数据,比如电商系统的浏览,加入购物车,下订单,付款等一系列流程我们都可以通过埋点获取到用户的访问路径以及访问时长这些数据;再比智能穿戴设备,实时都会采集我们的血压、脉搏、心率等数据实时上报到云端。通过分析这些日志信息,我们可以得到出很多业务价值。通过对这些日志信息进行日志采集、收集,然后进行数据分析,挖掘公司业务平台日志数据中的潜在价值。为公司决策和公司后台服务器平台性能评估提高可靠的数据保证。系统日志采集系统做的事情就是收集日志数据提供离线和在线的实时分析使用。目前常用的开源日志收集系统有Flume、Logstash、Filebeat。可以根据自己公司的技术栈储备或者组件的优缺点选择合适的日志采集系统,目前了解到的Flume使用的比较多。各个采集工具的对别如下:
具体组价的相关配置可以参考之前的文章《日志收集组件—Flume、Logstash、Filebeat对比》
数据库抽取
企业一般都会会使用传统的关系型数据库MySQL或Oracle等来存储业务系统数据。每时每刻产生的业务数据,以数据库一行记录的形式被直接写入到数据库中保存。
大数据分析一般是基于历史海量数据,多维度分析,我们不能直接在原始的业务数据库上直接操作,因为分析的一些复杂SQL查询会明显的影响业务数据库的效率,导致业务系统不可用。所以我们通常通过数据库采集系统直接与企业业务后台数据库服务器结合,在业务不那么繁忙的凌晨,抽取我们想要的数据到分析数据库或者到HDFS上,最后有大数据处理系统对这些数据进行清洗、组合进行数据分析。
常用数据库抽取工具:
-
阿里开源软件:DataX
DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。开源的DataX貌似只能单机部署。
-
Apache开源软件:Sqoop
Sqoop(发音:skup)是一款开源的工具,主要用于在HADOOP(Hive)与传统的数据库(mysql、postgresql...)间进行数据的传递,可以将一个关系型数据库(例如 : MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。可以集群化部署。
爬虫爬取
有很多外部数据,比如天气、IP地址等数据,我们通常会爬取相应的网站数据存储。目前常用的爬虫工具是Scrapy,它是一个爬虫框架,提供给开发人员便利的爬虫API接口。开发人员只需要关心爬虫API接口的实现,不需要关心具体框架怎么爬取数据。Scrapy框架大大降低了开发人员开发速率,开发人员可以很快的完成一个爬虫系统的开发。
三、数据存储
HDFS
2003年,Google发布论文GFS,启发Apache Nutch开发了HDFS。2004年,Google 又发布了论文《MapReduce: Simplified Data Processing on Large Clusters》,Doug Cutting等人实现计算框架MapReduce ,并与HDFS结合来更好的支持该框架。2006年项目从Butch搜索引擎中独立出来,成为了现在的Hadoop。
GFS隐藏了底层的负载均衡,切片备份等细节,使复杂性透明化,并提供统一的文件系统接口。其成本低,容错高,高吞吐,适合超大数据集应用场景。
-
HDFS原理:
横向扩展,增加“数据节点”就能增加容量。 -
增加协调部门,“命名节点”维护元数据,负责文件系统的命名空间,控
-
外部访问,将数据块印射到数据节点。还会备份元数据从命名节点,它只与命名节点通信。
-
数据在多个数据节点备份。
通常关系型数据库存储的都是结构化的数据,我们抽取后会直接放到HDFS上作为离线分析的数据源。
HBase
在实际应用中,我们有很多数据可能不需要复杂的分析,只需要我们能存储,并且提供快速查询的功能。HBase在HDFS基础上提供了Bigtable的能力; 并且基于列的模式进行存储。列存储设计的有事减少不必要的字段占用存储,同时查询的时候也可以只对查询的指定列有IO操作。HBase可以存储海量的数据,并且可以根据rowkey提供快速的查询性能,是非常好的明细数据存储方案,比如电商的订单数据就可以放入HBase提供高效的查询。
当然还有其他的存储引擎,比如ES适合文本搜索查询等。
总结
了解了上面的技术栈后,在实际数据接入中,你还会面临各种问题,比如如何考虑确保数据一致性,保障数据不能丢失,数据采集存储的效率,不能产生数据积压等,这些都需要对每个组件进行研究,适配适合你自己业务系统的参数,用最少的资源,达到最好的结果。
历史好文推荐
你点的每个在看,我都认真当成了喜欢
-
-
从0到1搭建大数据平台之计算存储系统
2020-08-03 06:45:00前面已经给大家讲了《从0到1搭建大数据平台之数据采集系统》、《从0到1搭建大数据平台之调度系统》,今天给大家讲一下大数据平台计算存储系统。大数据计算平台目前主要都是围绕着hadoop生态...前面已经给大家讲了《从0到1搭建大数据平台之数据采集系统》、《从0到1搭建大数据平台之调度系统》,今天给大家讲一下大数据平台计算存储系统。大数据计算平台目前主要都是围绕着hadoop生态发展的,运用HDFS作为数据存储,计算框架分为批处理、流处理。
一、传统的计算平台
我们都知道,没有大数据之前,我们计算平台基本是依赖数据库,大数据量的计算基本依赖Oracle数据库。Oracle很强大,支撑了很多年银行、电信业务数据的计算存储。Oracle多以集中式架构为主,最大特点就是将所有的数据都集中在一个数据库中,依靠大型高端设备来提供高处理能力和扩展性。集中式数据库的扩展性主要采用向上扩展的方式,通过增加CPU,内存,磁盘等方式提高处理能力。这种集中式数据库的架构,使得数据库成为了整个系统的瓶颈,已经越来越不适应海量数据对计算能力的巨大需求。同时传统数据库架构对高端设备的依赖,无疑将直接导致系统成本的大幅度增加,甚至可能会导致系统被主机和硬件厂商所“绑架”,不得不持续增加投入成本。
二、Hadoop的崛起
随着互联网行业的发展,特别是移动互联网的快速发展,传统数据库面临着海量数据的存储成本、有限的扩展能力等问题。新的计算框架MapReduce出现了,新的存储编码方式HDFS出现了,二者合起来,我们一般称之为Hadoop。
Hadoop很快凭借其高可靠性、高扩展性、成本低、高效计算等优势在各个领域得到了广泛应用。
三、Hive的应用
Hive最初是Facebook开源的,我们来看看Hive的特点:
-
Hive是一个构建于Hadoop顶层的数据仓库工具,可以查询和管理PB级别的分布式数据。
-
支持类SQL语音。
-
可以看作为用户编程接口,本身不存储和处理数据
-
依赖HDFS作为存储
我们看到Hive支持类SQL语法,我们可以很容易的把传统关系型数据库建立的数据仓库任务迁移到Hadoop平台上。
Hive的架构:
我们可以看到hive提供了多种连接方式:JDBC、ODBC、Thrift。
那么我们以前使用Oracle的存储过程怎么迁移到Hive中呢?用过Hive的同学可能都知道,Hive是没有想Oracle那样的游标循环呀,所以我们必须借助其他语言来配合hive一起完成数据仓库的ETL过程。比如这个项目:PyHive(https://github.com/dropbox/PyHive)
借助Python,我们可以很好的弥补Hive在复杂处理的一些缺陷,同时也能更好的开发ETL任务。
所以,通过Hive我们就可以搭建起一套大数据计算平台。
四、Spark的应用
Hive在刚开始使用过程中很好用,对大数据量的处理确实比以前传统数据库要好,但是随着业务的增长,公司越来越多的数据工程师反馈查询慢,同时业务侧也纷纷提出,我们的数据能不能早点出,不要老是等到早上8点才刷新。我们需要更强大的计算引擎,Spark使用了十分之一的计算资源,获得了比Hadoop快3倍的速度,Spark为什么这么快呢?
我们来看看Spark的特点:
-
速度快,使用DGA(有向无环图)。
-
支持内存计算。
-
低延迟、高容错。
Spark提供了存计算,可以将计算结果存放到内存中,我们都知道MR是将数据存储在磁盘,对磁盘读写,势必会增加IO操作,计算时间过长。之前我也做过一个Hive和Spark的一个执行效率的对比,当时使用的是Spark1.6,数据是千万级别的表。
还是可以看出Spark比Hive快了很多,现在Spark2.0以后,会更快了。而且,Spark同样提供的有JDBC、ODBC 、Thrift连接方式。
我们可以从Hive环境直接迁移到Spark环境,提高执行效率。
五、MPP的应用
用了Spark还是不够快,每次查询提交任务后,都得等着任务启动然后看着任务执行进度一直等着。
MPP(Massively Parallel Processing)是指多个处理器(或独立的计算机)并行处理一组协同计算。为了保证各节点的独立计算能力,MPP数据库通常采用ShareNothing架构。比较有代表性大家熟知的比如:GPDB、Vertica。
MPP具备以下特点:
-
低成本的硬件、和Hadoop一样,使用x86架构的PC就可以
-
数据存储采用不同的压缩算法,减少使用空间,提高IO性能
-
数据加载高效,并行加载、数据加载的速度取决于带宽
-
易扩展,容易对集群节点进行增减
-
列存储,很多MPP支持列存储架构,能够更高效的访问需要的数据
-
支持标准SQL,MPP比SparkSQL、HiveSQL对标准SQL支持的更好
从以上MPP的特点和上面我们介绍的Hadoop的特点,会发现MPP更适合数据自助分析、即席查询等场景、能够使数据人员快速获取数据结果。
六、搭建自己的计算平台
开源的计算引擎这么多、我们如何选择合适的计算引擎搭建平台呢?
下面分多个场景来和大家探讨下:
1、小公司、无大数据平台
真正的从无到有搭建大数据平台,开发人员较少。可以直接使用CDH搭建起来你的大数据平台,选用Hive作为数据仓库的计算引擎。为什么这样选择呢?很多小公司没有足够的资金支撑大数据平台的建设,那么就会选择相对来说的比较稳定的开源组件,Hive发展了很多年,和磁盘的交互MR计算架构中的任务很少会出错。Hive对SQL支持的很好,开发人员很容易上手,而且维护成本很低。
2、小公司、大数据平台升级
已经有过一段时间使用Hive作为计算引擎后,工程师们对大数据平台已经有一定的了解和知识积累,对Hadoop的运维能力也提升了,随着开发人员反馈Hive比较慢,领导也考虑升级平台,这时候就可以引入Spark了。上面我们也说了Spark是基于内存运算的,内存始终是没有磁盘稳定,Spark任务很多时候会因为资源设置不合理而报错。而SparkSQL和可以直接共享Hive的metestore,直接从Hive迁移到Spark上很自然,工作流很小。同时Spark还提供了Streaming功能,可以满足公司逐渐发展遇到的实时数据问题,再也不用担心以前hive没半小时执行一次任务,任务还偶尔出现执行不完的场景了。
3、大公司
很多传统行业的大公司一直依赖传统关系型数据库来处理数据,花了很多钱购置硬件和服务。现在要“降本增效”,必然会对IT部门下手。大公司有钱,就可以招聘到专业的工程师,他们有过建设大数据平台的经验,在计算选型上可以根据自己的技术栈选择合适的计算引擎。
另外,可以买一些MPP数据库的服务,比如GreenPlum商业版、Vertica。商业版的很稳定,几乎不会碰到棘手的问题,平时只管使用就行了,大大提高的运维、开发效率。对比下来会发现比以前使用传统的关系型数据库省了不少钱。
七、总结
基于多个计算引擎搭建大数据平台是目前的现状,针对不同的企业和团队选择适合自己的,同一个公司不同的业务也可以选择不同的计算引擎。不考虑商业方案,就要根据自己的技术掌握情况,选择自己精通的并且适合业务的。考虑商业方案的可以选择商业的MPP,给开发和业务人员提供更好的环境和体验。
-
-
Python 网络爬虫从0到1 (1):Requests库入门详解
2020-09-13 20:12:53Python 网络爬虫从0到1 (1):Requests库入门详解 网络爬虫中,网络请求是基础部分。没有网络请求以及响应,网络爬虫的后续数据分析也就失去了意义。Python中的网络请求,主要由Requests库来完成,本篇,我们... -
两分钟读懂《从0到1》——《从0到1》读书笔记
2015-06-02 21:12:45Some ideas can’t wait!(好主意不应等待) 一些好的创意是无法等待的,如果想到一个好的idea没有去验证,那么等于没有idea。互联网如今的商业思维就是快速试错、快速迭代、快速验证自己的想法。...从0到1从0到 -
从0到1的创业思维
2015-05-12 07:33:42从0到1,或者说从无到有,意味着企业要善于创造和创新,通过技术专利、网络效应、规模经济、品牌等形成壁垒,从而实现质的垂直性层级跨越,由此开辟一个只属于自已的蓝海市场而成为这个市场的唯一 -
从0到1开发H5游戏
2018-05-09 10:24:08我们将从0到1,帮助游戏开发者讲述游戏是怎么制作出来的。什么是H5游戏?H5游戏具有即开即玩、碎片化、上手简单、易玩等特点,市面上的游戏平台包括了:微信公众号、微信小游戏、QQ玩一玩、Facebook InstantGam... -
《从0到1》读书笔记
2019-05-01 08:39:52从0到1 彼得·蒂尔(Peter Thiel),PayPal公司创始人,硅谷创投教父,Facebook的首个外部投资者。徐小平称他“象征着美国异想天开、特立独行但又脚踏实地从无到有的创新精神”。 【关于本书】作者颠覆了关于... -
Python 网络爬虫从0到1 (5):Re(正则表达式)库入门详解
2020-09-22 00:07:05Python 网络爬虫从0到1 (5):re(正则表达式)库入门详解 在上一节Python 网络爬虫从0到1 (4):Beautiful Soup 4库入门详解中,我们已经能够从由Requests库发起请求得到的HTML响应报文主体中解析页面标签树... -
Python 网络爬虫从0到1 (3):基于Requests库的爬虫入门实战
2020-09-15 22:52:36Python 网络爬虫从0到1 (3):基于Requests库的爬虫入门实战 在学习了Requests库的基本用法后,我们就可以使用Requests库进行一些最简单的网页爬取。由于目前还没有学习Beautifulsoup4库用于分析响应,目前... -
阅读整理《从0到1》
2016-02-10 23:39:40《从0到1》的读书记录和读书笔记 -
Appium自动化框架从0到1之 框架结构组成
2020-07-09 09:21:50从0到1搭框架框架背景框架功能框架视图 框架背景 可能会利用一周的时间,我们来写一个Appium自动化框架的搭建, 从0到1,跟着小鱼一起,完善Android 的自动化框架体系。 框架模式:PO 语言:python3.7 + Appium 1.17... -
Python 网络爬虫从0到1 (0):序与目录
2020-09-02 00:43:47Python 网络爬虫从0到1 (0):序与目录 序 很多人说,如今,我们正处于一个信息爆炸的时代,被各式各样的信息包裹者。从一个普通用户的角度来看,信息爆炸,不过是在宣传广告中加入“大数据”几字凸显高大上,... -
从0到1绘制蜡烛线(实现细节)
2020-06-13 11:24:58股票?...所以就从今天开始我从0到1打造出这个复杂的行情图!费话不多说,上图!上链接: https://github.com/SlamDunk007/StockChart 一、效果图 二、绘制流程 整个绘制过程完全自定义Vi... -
Linux从0到1:安装Linux操作系统(超级详细版)
2018-06-28 10:31:09分享一下安装Linxu操作系统的流程 安装虚拟机 首先自己进行Vmware workstation的安装,打开此软件进行以下步骤。 在VMware中新建虚拟机 ...自定义虚拟机名称,和文件夹位置(建议D:\VM\Centos7-1-64... -
ML:从0到1 机器学习算法思路实现全部过程最强攻略
2018-11-15 12:15:37ML:从0到1 机器学习算法思路实现全部过程最强攻略 目录 思维导图 设计思路 思维导图 设计思路 相关文章 ML之FE:结合Kaggle比赛的某一案例细究Feature Engineering思路框架ML之FE:... -
读《从0到1》有感
2015-10-08 11:10:28蒂尔写的《从0到1》,当今的发展有两种方式,一种是水平进步从1到N广泛复制;另一种是垂直进步,从0到1探索创新。经济学家们所倡导的完全竞争,看似能够促进企业的发展,但残酷的竞争却吞噬了企业的利润,有些公司... -
从0到1 微服务架构的概念到实践
2017-05-25 16:29:08作为一种基于服务的架构,微服务架构被认为是目前适合开发高可扩展性应用的架构风格。那么,什么情况下适合使用微服务架构?...本次讲座从基本概念入手,正本清源,并进一步分享了IBM Bluemix 微服务架构的部署与实践。 -
如何从0到1搭建大数据平台
2020-07-02 06:20:00大数据时代这个词被提出已有10年了吧,越来越多的企业已经完成了大数据平台的搭建。随着移动互联网和物联网的爆发,大数据价值在越来越多的场景中被挖掘,随着大家都在使用欧冠大数据,大数据平台的... -
《Android 之美 从0到1 -- 高手之路》
2016-05-26 16:06:31Android 之美 从0到1 – 高手之路 随着Android 面试题总结,已经形成比较多的篇幅,为了方便大家阅读,本篇将作为面试题总结导读,也将成为Android 面试题的大纲,也只是Android 之美 从0到1 的一部分,陆续补充... -
从0到1教你设计业务系统
2017-11-25 20:13:56本文将以一个案例,向读者逐步揭示一套业务系统从0到1的设计过程。重点讲述架构、模型等业务系统最本质的设计精要。 一、业务系统设计概述 1、什么是业务系统 互联网公司常常将产品方向分为两类,C端和B端,C端... -
《从0到1:CTFer成长之路》 配套题目Web WP
2020-10-05 18:09:08非要给个定位的话,自己现在顶多算是0.2,距离这本书的从0到1还有很大的差距。因此为了提高自己,真正的成为1,自己买了这本书而且要努力的进行学习。 这本书的配套题目可以说给了自己学习知识的同时进行实践的机会... -
不止从0到1|从日活10万到日活1000万,该如何优化产品?
2017-12-04 00:00:00作者:WinsonL 全文共 2786 字 7 图,阅读需要 6 分钟 ———— / BEGIN / ———— ...今天要分享的是从「日活十万...当然从0到1的过程是令人兴奋的,但是从1到100也不会容易,同样也充满挑战,而且更加有成就感。 -
Hadoop生态从0到1_理论篇_[HDFS|Yarn|MapReduce|Hive]_CodingPark编程公园
2020-04-24 17:27:23文章介绍: 本文将带领你进入Hadoop的生态世界,本文为Hadoop生态从0到1_理论篇 -
自己动手从0到1写嵌入式操作系统
2017-04-06 11:44:58这不是rtos源码分析的课程,而是为初级的同学设计,从基础原理讲师,一步步不断迭代设计rtos的课程! 用不到【2000行代码,汇编代码仅18行】(不含注释)实现一个精巧的可以运行在ARM Cortex-M内核芯片上的RTOS! ... -
从0到1搭建大数据平台之调度系统
2020-07-20 06:40:00目前大数据平台经常会用来跑一些批任务,跑批处理当然就离不开定时任务。比如定时抽取业务数据库的数据,定时跑hive/spark任务,定时推送日报、月报指标数据。任务调度系统已经俨然成为了大... -
从 0 到 1 走进 Kaggle
2017-05-18 11:47:20本文结构: - kaggle 是什么 - 如何参赛 - 解决问题一般步骤 - 进一步: - 如何探索数据 - 如何构造特征 - 提交结果kaggle 是什么?Kaggle 是一个数据科学竞赛的平台,很多公司会发布一些接近真实业务的... -
从0到1打造正则表达式执行引擎(一) 正则表达式转NFA
2020-05-01 17:28:42+ ***)重复0-1次 ?重复1次以上重复0次以上匹配指定次数特殊符号(正则表达式中的 **. \d \s……**)子表达式(正则表达式 **()**)练习题代码实现建图匹配下集预告功能完善化DFA引擎正则引擎优化 今天是五一假期第一天...
-
pdf转swf全部工具
-
C++学习笔记:父类和子类的类型兼容规则
-
SubstancePainter插件开发-基础入门
-
thinkphp5.1博客后台实战视频
-
阿里P9天天加班到凌晨,就是为了编写这份IT架构运维实践手册!
-
Adaptive cavity-enhanced dual-comb spectroscopy
-
可调谐半导体激光器业内获认可,将持续升级产品——访MKS集团Newport产品技术支持工程师刘斌
-
Kotlin协程极简入门与解密
-
易语言开发通达信DLL公式接口
-
基于AD2S1210的旋转变压器解码系统设计
-
新闻
-
Anaconda换源和常用命令
-
ReentrantLock与读写锁
-
高级驱动——三、i2c子系统(从设备通信)
-
WPF上位机数据采集与监控系统零基础实战
-
ProBuilder快速原型开发技术
-
基于改进模板匹配算法的伤票识别与定位
-
DDP 比 DP 快几倍的原因
-
2021最新Kubernetes(k8s)集群实战精讲
-
光学频率合成器的自动化控制研究