就业班项目实战就业班项目实战--从从0开发开发Blog 布尔教育 http://www.itbool.com 燕十八 念白白著 严禁传播 违者必究 第第1章章 项目搭建与核心功能项目搭建与核心功能 1.01 课程特点课程特点 实用性强 blog可用来记录自己的学习笔记,立即投入使用. 难度适中 面向过程,使用所学的PHP基础+MySQL即可完成 系统全面 包含"需求分析","代码规范","数据库建模","编码技巧","调试技巧","安全专题" 1.02 课程目标课程目标 学会项目需求分析 会做ER建模,(根据需求建表建表) 掌握PHP代码规范,利于团队协作 掌握网站常见功能的开发技术(文件上传,验证码,缩略图...) 学完后能独立做项目或带领团队做项目的水平 1.03 如何做需求分析如何做需求分析 客户的特点客户的特点 客户不懂技术 客户一般只能抽象提出自己想要的目标. 最典型的比如: "做个公司网站,有公司介绍和产品介绍就行","和某个网站一样就行”. 听到客户类似的描述,千万不要以为用户的需求简单. 因为描述的越不精确,客户后面的变动越大. 常见问题常见问题: 你没做出一个功能前,客户表达不出这样的需求, 而当你做出这功能后,客户又认为不合乎他的想像,需要改动. 这种情况如果多次发生,会最终把项目拖入泥潭,引发双方矛盾. 所以,要记住,需求越精确,开发越迅速,扯皮的事情越少. 而做需求分析,并不是在项目开始两天就能确定的事情,甚至会贯穿项目始终. 需求分析的原则需求分析的原则: 抽象到具体, 由文字到表格, 由表格到图片, 逐步细化而来. 包括下面要讲的功能结构,原型建模,都属于功能分析的一部分 一般情况,做需求分析的步骤需求分析的步骤: a:) 文字采访文字采访 由客户讲解,我方人员做笔录 这一阶段,客户能讲出的功能并不多(除非客户方有备而来,并有专门的人员负责调研) 往往只会说出核心功能,如”公司新闻发布”,”客户留言”等, 这一阶段要有文字记录+签字确认 b:) 引导需求引导需求 比如客户说要”公司新闻”,那么公司新闻是否允许评论? 此阶段的注意点: 1: 尽量问题让客户用是否来回答,而不要开放式的问. 如”是否允许评论”,”是否需要验证码?” 2: 常用的功能,如留言板,一定要向客户确认,不要有侥幸心理,觉得客户没说,我也不说. 但实际上,网站一上线,客户看到没有常用功能,还是会要求加上,那是改动,代价就高了. 3: 不常用的功能,不要提问客户,因为客户往往是盲目的,你提到的功能,当然想尽可能多的实现. 1.04 Blog功能结构图功能结构图 整理思路,方便和客户沟通 绘图软件: excel/Edraw等 此处用Edraw 这个表不是必须要画,但是最好画,图片比文字更具有说明效果 在这张图中我们看到几张表? 文章表和栏目表的联系 1.05 Blog页面原型图页面原型图 绘图软件绘图软件: Balsamiq Mockups / axure 等原型软件 此处用 Balsamiq Mockups 当我们谈完需求,很多客户会让我们的设计人员先设计一个效果图出来 设计人员费了老大力气,用ps等绘制了一副精细的效果图,越精细越能挑出毛病.事倍功半,客户自己都不清楚自己想要什么 此时不妨用简易的绘图软件画一个简易的圆形图出来 现在我们只要确认网页上: 有哪些,放在哪 1.06 Blog数据库建模数据库建模 建模软件建模软件: powerdesigner,MySQL workbench[前两者会直接产生建表语句]或Edraw 如果圆形图确认通过,可以兵分两路,设计人员去做前台的页面设计 后台的php人员可以去做数据库的建设,分析表结 1.07 代码规范代码规范 写代码之前要规范代码 代码规范的意义代码规范的意义: 1)便于排查 [缩进规范] 2)减少沟通成本,便于团队合作 [命名规范] 不这样写也是可以的,不会报错 类大驼峰,函数小驼峰 3)便于文档自动化生成 [注释规范] 有很多第三方的工具,可以自动化的生成文档,通过读取我们的注释 http://www.yiiframework.com/ e框架的注释就写的非常好 它的手册是自动生成的 4)有利于求职 文件说明文件说明 /** * 第一行2个*号 * 其余行也以*号开头 * @author @link @since @copyright等 */ 下面是例子 index.php 页面级别的注释 /** * index.php blog首页 * * @author nianbaibai //作者邮箱 * @link http://www.zixue.it //作者的关系连接 * @since 0.1 2015年8月8日 //版本号或是日期 * @copyright GPL //版权,开源软件 GPL协议 */ 下面这个网站提供的软件可以将注释转成文档,自然我们写注释需要以他作为一个标准和规范 http://www.phpdoc.org/ 具体注释有几种,参考下面的连接,并不要求我们一一都写 http://www.phpdoc.org/docs/latest/index.html\ 函数说明函数说明 函数的命名规范 /** * 取出最新N条新闻 * * @param int $n 取出新闻的条数 //参数 * @return arr 新闻的数组 //返回 */ function getNews($n) { return array(1,2,3,'N'); } 命名规范命名规范 类类: 大驼峰规则大驼峰规则 即每个单词首字母大写 class CatModel { } 函数函数: 小驼峰规则小驼峰规则 即第1个单词小写,后面的单词首字母大写,如: function getName() { } 注注 :对于类文件,文件名一般和类名相同 关于缩进: 用4个空格来缩进,不要tab 我们按tab,也是4个空格,是因为我们的编辑器sublime已经帮我们配置好了,按一下tab, 顶4个空格 1.08 组织项目文件组织项目文件 项目的文件/目录清晰有条理,有助于提高开发效率并减少错误. 我们按如下格式组织项目 在blog下新建如下目录 /css # 放置css文件 /images # 图片 /lib # 底层库文件 /log # 系统日志 /upload # 上传文件 /view # 模板目录 /front /admin index.php # 用户直接访问的php文件 art.php 简单的博客框架搭建好之后 为自己的博客配置一个域名 将css文件和html模版文件放入指定的位置 blog/index.php 1.09 栏目添加流程图栏目添加流程图 1.10 添加栏目添加栏目 栏目的增删改查,先从增开始 如何增加一个栏目 html表单页面 -> 提交到php -> php接收post数据 -> insert 添加到数据库 -> 是否添加成功 ok or fail blog/catadd.php //判断表单是否有post数据 if(empty($_POST)) { include('./view/admin/catadd.html'); } else { // 如有POST,则判断catname是否为空 $cat['catname'] = trim($_POST['catname']); if(empty($cat['catname'])) { exit('栏目名称不能为空'); } $conn = mysql_connect('localhost','root',''); mysql_query('use blog' , $conn); mysql_query('set names utf8' , $conn); //首先查询catname是否有重名 同学们自己加上 //$sql = select... $sql = "insert into cat (catname) values ('$cat['catname']')"; if(!mysql_query($sql , $conn)) { echo mysql_error(); } else { echo '添加成功'; } } 1.11 栏目列表栏目列表 查询所有栏目 查哪张表 -> 查出来的内容显示到模版上 foreach -> select * from ... blog/view/admin/catlist.html 删除 blog/catlist.php 首先连接数据库 $conn = mysql_connect('localhost','root',''); mysql_query('use blog' , $conn); mysql_query('set names utf8' , $conn); $sql = 'select * from cat'; $rs = mysql_query($sql , $conn); if(!$rs) { echo false; } else { $cat = array(); //将查询出来的内容放进一个数组里 while($row = mysql_fetch_assoc($rs)) { $cat[] = $row; } //print_r($cat); } include('./view/admin/catlist.html'); 1.12 删除栏目删除栏目 catlist.html ";?>删除 ";?>| 编辑 catdel.php 流程: if(cat_id不为数字) if(栏目是否存在) if(栏目下是否有文章) 参考代码: $cat_id = $_GET['cat_id']; $conn = mysql_connect('localhost','root',''); mysql_query('use blog' , $conn); mysql_query('set names utf8' , $conn); //如果cat_id 不为数字 if(!is_numeric($cat_id)) { echo '栏目错误'; exit; } //如果栏目下有文章,不能删除 $sql = 'select count(*) from art where cat_id='.$cat_id; $rs = mysql_query($sql , $conn); $row = mysql_fetch_row($rs); if($row[0] != 0) { echo '栏目下有文章,不能删除'; exit; } //查询该栏目是否存在 $sql = 'select count(*) from cat where cat_id='.$cat_id; $rs = mysql_query($sql , $conn); $row = mysql_fetch_row($rs); if( $row[0] == 0) { echo '栏目不存在'; exit; } $sql = 'delete from cat where cat_id='.$cat_id; $rs = mysql_query($sql , $conn); if(!$rs) { echo mysql_error(); } else { echo '删除成功'; } 1.13 编辑栏目编辑栏目 catedit.html 这个模版应该跟 catadd.html 是一样的 只不过 栏目框里 应该显示原来的栏目名 流程: if POST 为空 { 检测cat_id是否为数字 模板中展示栏目的旧信息 } else { 查询新栏目名是否为空字符串 修改栏目为新的栏目名 } 参考代码: $conn = mysql_connect('localhost','root',''); mysql_query('use blog' , $conn); mysql_query('set names utf8' , $conn); $cat_id = $_GET['cat_id']; if(empty($_POST)) { $sql = 'select catname from cat where cat_id='.$cat_id; $rs = mysql_query($sql , $conn); $row = mysql_fetch_row($rs); include('./view/admin/catedit.html'); } else { $catname = trim($_POST['catname']); //判断栏目名是否为空 if($catname == '') { echo '栏目名不能为空'; exit; } $sql = "update cat set catname='".$catname."' where cat_id=".$cat_id; if(!mysql_query($sql , $conn)) { echo $sql; echo mysql_error(); } else { echo '栏目修改成功'; } } 1.14 封装封装MySQL操作函数操作函数 代码不够简洁,有值得改进的地方. 不论增删改查哪一项,都需要连接数据库,选库,设置字符集. 如何能重复利用,不写总是重复的代码 --> 封装起来,反复利用-->函数 在lib库文件里,新建一个mysql.php --> 用来方式mysql系列函数 blog/lib/mysql.php $v) { $sql .= $k . "='" . $v . "',"; } $sql = rtrim($sql , ','); $sql .= ' where '.$where; return mQuery($sql); } } /*$data = array('username'=>'lili','age'=>23,'hobby'=>'pingpang','content'=>'hello'); echo mExec($data,'cat','update','catid=1');*/ /** * 返回最近的一次insert产生的主键值 * @return int */ function getLastId() { return mysql_insert_id(mConn()); } ?> 将 mysql.php 用于我们的 catadd.php blog/catadd.php require('./lib/mysql.php'); //判断表单是否有post数据 if(empty($_POST)) { include('./view/admin/catadd.html'); } else { // 如有POST,则判断catname是否为空 $cat['catname']= trim($_POST['catname']); if(empty($cat['catname'])) { exit('栏目名称不能为空'); } if(!mExec('cat',$cat)) { echo mysql_error(); } else { echo '添加成功'; } } 1.15 引入初始化文件引入初始化文件 底层函数库不会轻易动,但不同的服务器,它的数据库用户名,密码肯定是不同的, 且可能有多台服务器,多个库 改底层的mysql.php显然是不妥的. 这些易于变动的参数,我们应该以一个变量,配置文件的形式存储起来 这种直接写死在库文件的写法,称之为 --> 硬编码 (不推荐) 我们应在做一个配置文件,就叫 config.php blog/lib/config.php return array( 'host'=>'localhost', 'user'=>'root', 'password'=>'', 'db'=>'blog', 'charset'=>'utf8' ); 将config.php 引入 mysql.php blog/lib/mysql.php function mConn() { //静态变量使得 mConn在同一个页面 数据库值只连接一次 static $conn = null; if($conn === null) { $cfg = include('./config.php'); $conn = mysql_connect($cfg['host'],$cfg['user'],$cfg['password']); mysql_query('use '.$cfg['db'] , $conn); mysql_query('set names '.$cfg['charset'] , $conn); } return $conn; } 思考 catadd.php include(./lib/mysql.php) 而mysql.php 里面引入的是 ./config.php 我们运行 catadd.php 发现,找不到config.php 因为当前目录没有config.php 所以我们要将 mysql.php中 改为 include(./lib/config.php) 但是当我们网站很大,目录很多,层次很深,这样来回引入肯定出错 不要用相对路径,用绝对路径 --> 初始化文件 初始化文件: 初始化当前的环境信息,计算当前网站的据对路径在哪 blog/lib/init.php 魔术常量魔术常量 PHP 向它运行的任何脚本提供了大量的预定义常量。 不过很多常量都是由不同的扩展库定义的 只有在加载了这些扩展库时才会出现 或者动态加载后,或者在编译时已经包括进去了 有八个魔术常量它们的值随着它们在代码中的位置改变而改变。 例如 LINE 的值就依赖于它在脚本中所处的行来决定 这些特殊的常量不区分大小写 它的值,具体取决于就写在哪个文件里,它不会受包含影响 blog/test.php include('./lib/init.php'); blog/lib/init.php echo __DIR__,'
'; echo __FILE__,'
'; echo __LINE__; 初始化文件 blog/lib/init.php //拿到当前目录,往上跳一级是根目录,用dirname往上跳一级 //定义一个常量根目录 header('Content-type:text/html;charset=utf8'); define('ROOT',dirname(__DIR__)); require(ROOT.'/lib/mysql.php'); require(ROOT.'/lib/func.php'); 将其他文件的路径修改成绝对路径 1.16 封装提示函数封装提示函数 当栏目发布成功或失败的时候,我们直接echo,风格过于简洁 用模版显示出来,更我们网站的风格更相似一些 info.html 我们根据if判断,看到底是显示成功还是失败 blog/lib/func.php 我们将提示函数放在func.php里面,因为它不是mysql系列函数,性质不同 我们再写一个函数库,跟mysql无关的函数放在这个里面,当然我们网站如果足够大,肯定会有多个这样的函数库 /** * @param string $msg 成功返回的信息 * */ function succ($msg='成功') { $res = 'success'; include(ROOT.'/view/admin/info.html'); exit; } /** * @param string $msg 失败返回的报错信息 */ function error($msg='失败') { $res = 'fail'; include(ROOT.'/view/admin/info.html'); exit; } blog/view/admin/info.html
'?> 1.17 调试技巧调试技巧 在php运行中,sql有时会出差,出错好办,只要我们能看到错误; 我们封装了大量函数,sql可以自动执行,我们眼睛不能直观看到执行的sql 写一个日志功能,记录我们的sql语句 正常sql只记录sql语句,出错的sql不仅记录sql还要记录出错的信息 所有的sql都是经过 mQuery($sql) 执行的 file_put_contents — 将一个字符串写入文件 mysql.php /** * 执行sql语句 * * @param string $sql * @return mixed 返回布尔型值/数组 */ function mQuery($sql) { $rs = mysql_query($sql , mConn()); if($rs === false) { mLog($sql."\n".mysql_error()); return $rs; } mLog($sql); return $rs; } /** * *记录执行的sql 以及出错信息 * @param $log 记录的信息 */ function mLog($log) { $path = ROOT.'/log/'.date('Ymd',time()).'.txt'; //$path = '../log/'.date('Ymd',time()).'.txt'; $head = '--------------------------------------'."\n".date('Y/m/d H:i:s',time()) ."\n"; file_put_contents($path,$head.$log."\n"."\n",FILE_APPEND); } 第第2章章 功能完善功能完善 2.01 文章发布文章发布 将栏目从数据库中取出 artadd.html artadd.php include('./lib/init.php'); //从数据库中取出栏目 $sql = 'select * from cat'; $cat = mGetAll($sql); if(empty($_POST)) { include(ROOT.'/view/admin/artadd.html'); } else { //检测标题 $art['title'] = trim($_POST['title']); if(empty($art['title'])) { error('标题不能为空'); } //检测栏目 $art['cat_id'] = $_POST['cat_id']; if(!is_numeric($art['cat_id'])) { error('栏目不为数字'); } //检测内容 $art['content'] = trim($_POST['content']); if(empty($art['content'])) { error('内容不能为空'); } //文章发布时间 $art['pubtime'] = time(); //发布文章 if(!mExec('art',$art)) { error('文章发布失败'); } else{ succ('文章发布成功'); } } 2.02 文章列表及删除文章列表及删除 artlist.html '; echo __FILE__,'
'; echo __LINE__; 初始化文件 blog/lib/init.php //拿到当前目录,往上跳一级是根目录,用dirname往上跳一级 //定义一个常量根目录 header('Content-type:text/html;charset=utf8'); define('ROOT',dirname(__DIR__)); require(ROOT.'/lib/mysql.php'); require(ROOT.'/lib/func.php'); 将其他文件的路径修改成绝对路径 1.16 封装提示函数封装提示函数 当栏目发布成功或失败的时候,我们直接echo,风格过于简洁 用模版显示出来,更我们网站的风格更相似一些 info.html 我们根据if判断,看到底是显示成功还是失败 blog/lib/func.php 我们将提示函数放在func.php里面,因为它不是mysql系列函数,性质不同 我们再写一个函数库,跟mysql无关的函数放在这个里面,当然我们网站如果足够大,肯定会有多个这样的函数库 /** * @param string $msg 成功返回的信息 * */ function succ($msg='成功') { $res = 'success'; include(ROOT.'/view/admin/info.html'); exit; } /** * @param string $msg 失败返回的报错信息 */ function error($msg='失败') { $res = 'fail'; include(ROOT.'/view/admin/info.html'); exit; } blog/view/admin/info.html
'. $msg . '
'?> ' . $msg . '序号 | 日期 | 标题 | 分类 | 回复 | 状态 |
12 | 编辑 | 删除 |
art.php include('./lib/init.php'); $art_id = $_GET['art_id']; $sql = 'select title,content,pubtime,catname from art left join cat on art.cat_id=cat.cat_id where art_id='.$art_id; $art = mGetRow($sql); //print_r($art); //如果地址栏输入一个没有的文章号 专跳到首页 if(empty($art)) { header('Location:index.php'); exit; } $sql = 'select * from cat'; $cat = mGetAll($sql); //如果post非空 则有评论 if(!empty($_POST)) { $comm = array(); $comm['art_id'] = $art_id; $comm['nick'] = $_POST['nick']; $comm['content'] = $_POST['content']; $comm['email'] = $_POST['email']; $comm['pubtime'] = time(); //插入的评论返回结果 如果返回false 则发布评论失败 $rs = mExec('comment',$comm); //跳转到上一页 $ref = $_SERVER['HTTP_REFERER']; header("Location: $ref"); } //取出所有评论 $sql = 'select * from comment where art_id='.$art_id; $comment = mGetAll($sql); include(ROOT.'/view/front/art.html'); 了解 gravatar头像 http://www.wopus.org/wordpress-deepin/tech/1640.html 头像使用的是一个 在线开放头像系统做的 它是根据我们的 email来动态生成头像的 可以在这个网站根据我们的email注册 去别的网站发布评论 只要这个网站也是使用的这个头像系统 它就会动态加载你在 gravatar 设置的头像 这个作为了解 2.09 首页评论数优化及标签优化首页评论数优化及标签优化 文章发布和文章编辑之 -> tag标签 artadd.php if(!mExec('art',$art)) { //echo mysql_error(); error('文章发布失败'); } else{ //判断如果没有tag则文章发布成功 $tag = trim($_POST['tag']); if(empty($tag)) { succ('文章发布成功'); } else { //获取文章id $art_id = getLastId(); //将str tag拆成 索引数组 $tag = explode(',',$tag); $sql = "insert into tag (art_id,tag) values "; foreach ($tag as $v) { $sql .= "(".$art_id . ",'".$v."'),"; } $sql = rtrim($sql,','); //插入tag表 if(!mQuery($sql)) { //tag插入失败,删除之前插入的文章 $sql = 'delete from art where art_id='.$art_id; mQuery($sql); error('标签插入失败'); } else { succ('文章发布成功'); } } } 编辑文章处,会显示所有的标签 文章首页会显示所有标签 在art表加一个arttag字段 冗余字段 这个字段放最原始的 字符串标签 sql alter table art add arttag vatchar(100) not null default ''; 文章编辑的tag只提示如何做 artedit.php include('./lib/init.php'); $art_id = $_GET['art_id']; if(empty($_POST)) { $sql = 'select * from art where art_id='.$art_id; $art = mGetRow($sql); $sql2 = 'select * from cat'; $cat = mGetAll($sql2); include(ROOT.'/view/admin/artedit.html'); } else { // 检测art_id是否为数字 if( !is_numeric($art_id) ) { error('参数有误'); } //检测标题 $art['title'] = trim($_POST['title']); if(empty($art['title'])) { error('标题不能为空'); } //检测栏目 $art['cat_id'] = $_POST['cat_id']; if(!is_numeric($art['cat_id'])) { error('栏目不为数字'); } //检测内容 $art['content'] = trim($_POST['content']); if(empty($art['content'])) { error('内容不能为空'); } //查询是否有这篇文章 $sql = 'select count(*) from art where art_id=' . $art_id; // 没这篇文章 if(!mGetOne($sql)) { error(mysql_error()); } $art['lastup'] = time(); $art['arttag'] = trim($_POST['tag']); //发布文章 if(!mExec('art',$art,'update','art_id='.$art_id)) { //echo mysql_error(); error('文章修改失败'); } else{ //判断如果没有tag,无则文章修改成功 $tag = trim($_POST['tag']); if(empty($tag)) { succ('文章修改成功'); } else { //直接删除原标签 重新添加新标签 $sql = 'delete from tag where art_id='.$art_id; mQuery($sql); //添加新标签 $tag = explode(',',$tag); $sql = "insert into tag (art_id,tag) values "; foreach ($tag as $v) { $sql .= "(".$art_id . ",'".$v."'),"; } $sql = rtrim($sql,','); if(mQuery($sql)) { succ('文章修改成功'); } } } } 文章首页 显示有多少个评论 如何方便的查出每篇博文的评论数? 理论上,通过连接查询或子查询,可以查出文章的同时,查出评论数. 但效率不够高. 我们给每篇文章添加一个字段:评论数 每当有人评论时,此字段+1,删除1条评论,此字段-1 art表的comm字段 art.php //如果post非空 则有评论 if(!empty($_POST)) { $comm = array(); $comm['art_id'] = $art_id; $comm['nick'] = $_POST['nick']; $comm['content'] = $_POST['content']; $comm['email'] = $_POST['email']; $comm['pubtime'] = time(); //插入的评论返回结果 如果返回false 则发布评论失败 $rs = mExec('comment',$comm); //每增加一条评论,art表的 comm字段+1 $sql = 'update art set comm=comm+1 where art_id='.$art_id; mQuery($sql); } catlist 的文章数 cat表的num字段 表示当前栏目下有几篇文章 artadd.php //给cat的文章数 num+1 $sql = 'update cat set num=num+1 where cat_id=' . $art['cat_id']; mQuery($sql); 从数据库设计的角度,理论的角度讲: 一个字段如果能被其它字段推测出来,这个字段就不应该要,且认为这个字段是多余的. 如果一个数据库的设计全从理论出发,这个数据库在使用上是非常难用的 实际的工作中,我们往往通过加上一个冗余字段,来极大的简化我们的查询 在首页显示评论数 和 标签 index.html
2.10 获取用户获取用户IP 在刚才用户做评论的时候 comment表有一个IP 看看来访者的IP,了解一下哪个地方访问的多,或者是否有国外的IP 1.如何获取来访者的IP呢? 超全局变量 $_SERVER['REMOTE_ADDR'] 2.数据库中IP字段,用什么类型存储? int而不是str IP是由4个字节组成的,而int型存储大小为 4 个字节 而用字符串,假如IP是 123.123.123.123 需要15个字节 所以: 获取IP之后,需要转成int 3.如果输出也是int,一串数字,人眼不便于查看 需要 int -> IP 注意: 在有的web服务器下,用的不是 REMOTE_ADDR 比如在 iis服务器下 用的是 client_ip 代表客户的IP 还有 有的人经过代理上网,他的IP就变成了 HTTP_X_FORWARDED_FOR getenv — 获取一个环境变量的值 如果服务器禁止了超全局变量,用getenv还是可以获取的 使用 phpinfo() 你可以看到所有环境变量的列表 ip2long — 将一个IPV4的字符串互联网协议转换成数字格式 lib/mysql.php 封装一个 获取来访者IP的函数 function getIp() { static $realip = NULL; if ($realip !== NULL) { return $realip; } if (getenv('HTTP_X_FORWARDED_FOR')) { $realip = getenv('HTTP_X_FORWARDED_FOR'); } elseif (getenv('HTTP_CLIENT_IP')) { $realip = getenv('HTTP_CLIENT_IP'); } else { $realip = getenv('REMOTE_ADDR'); } return $realip; } 192.168.1.106/blog/art.php?art_id=15 当用我们的实际地址访问,发现无法插入IP 打印sql发现IP变成了负数 php是有符号的,而数据库的IP是unsigned 无符号 4.ip 要是非负数 unsigned类型 $a = ip2long('192.123.123.123'); echo sprintf('%u',$a); art.php if(!empty($_POST)) { $comm = array(); $comm['art_id'] = $art_id; $comm['nick'] = $_POST['nick']; $comm['content'] = $_POST['content']; $comm['email'] = $_POST['email']; $comm['pubtime'] = time(); //获取来访者IP $comm['ip'] = sprintf( '%u' , ip2long( getIp() ) ); //插入的评论返回结果 如果返回false 则发布评论失败 $rs = mExec('comment',$comm); //每增加一条评论,art表的 comm字段+1 if($rs) { $sql = 'update art set comm=comm+1 where art_id=' . $art_id; mQuery($sql); } } 2.11 分页类分页类 如何生成页码 limit 12345 23456 34567 当前页应该是居中的 假设当前页是 curr 一共显示5个页码 curr-2 curr-1 curr curr+1 curr+2 页码最大可以大到: 总文章数[$num]/每页显示数[$cnt] ceil — 进一法取整 func.php /** * 计算分页代码/假设显示5个页码数 * @param int $num 总文章数 * @param int $cnt 每页显示文章数 * @param int $curr 当前显示页码数 * @return arr $pages 返回一个页码数=>地址栏值的关联数组 */ function cPager($num,$cnt,$curr) { //计算最大页码数 $max $max = ceil($num/$cnt); //计算最左面的页码数 $left = max($curr - 2,1); //计算最右侧页码数 $right = $left+4; $right = min($max,$right); /* 1 [2] 3 4 5 6 7 8 9 1 2 3 4 [5] 6 7 8 9 1 2 3 4 5 6 7 [8] 9 */ //当页码使劲靠右侧,当前页为8 显示的页码为 6 7 [8] 9 , 不足5个页码 //再次 确认左侧页码数 $left = $right - 4; $left = max($left,1); //将获取的5个页码数 放进数组里 for($i=$left;$i<=$right;$i++) { $_GET['page'] = $i; $pages[$i] = http_build_query($_GET); } return $pages; } index.php include('./lib/init.php'); //计算分页代码 $sql = 'select count(*) from art'; $num = mGetOne($sql); //获取总文章数 $cnt = 2;//每页显示2篇文章 $curr = isset($_GET['page']) ? $_GET['page'] : 1 ;//当前页码数 从地址栏的page值获取 $pagers = cPager($num,$cnt,$curr); if( isset($_GET['cat_id']) ) { $where = 'and art.cat_id='.$_GET['cat_id']; } else { $where = ''; } // 取出多条,注意,用哪些字段,取哪些字段,不要用*, //用where 1 拼接后面的条件,有条件继续往后 and 即可 //加上limit 筛选分页应显示的文章数 $sql = 'select art_id,arttag,art.cat_id,user_id,nick,pubtime,title,comm,content,cat.catname from art left join cat on art.cat_id=cat.cat_id where 1 '.$where.' order by art_id desc limit '.($curr-1)*$cnt.','.$cnt; $art = mGetAll($sql); $sql = 'select * from cat'; $cat = mGetAll($sql); include(ROOT.'/view/front/index.html'); 首页点击栏目 下面的分页功能的实现 index.php?cat_id=2&page=3 更改cPager函数,不能搞丢地址栏原有的参数 http_build_query — 生成 URL-encode 之后的请求字符串 将地址栏的参数 拼接成cat_id=2&page=3&area=beijing... 计算当前栏目下的文章数 where 1 and art.cat_id=$_GET['cat_id'] index.php include('./lib/init.php'); if( isset($_GET['cat_id']) ) { $where = 'and art.cat_id='.$_GET['cat_id']; } else { $where = ''; } //计算分页代码 此处在首页一直计算的是全部的文章 如果计算某个栏目下文章 则应该改变sql //$sql = 'select count(*) from art'; //如果进入某个栏目下 筛选文章应该筛选当前栏目下的文章 改变sql //如果地址栏有 GET['cat_id'] 则拼接sql语句 $sql = 'select count(*) from art where 1 '.$where; $num = mGetOne($sql); //获取总文章数 $cnt = 2;//每页显示2篇文章 $curr = isset($_GET['page']) ? $_GET['page'] : 1 ;//当前页码数 从地址栏的page值获取 $pagers = cPager($num,$cnt,$curr); // 取出多条,注意,用哪些字段,取哪些字段,不要用*, //用where 1 拼接后面的条件,有条件继续往后 and 即可 //加上limit 筛选分页应显示的文章数 $sql = 'select art_id,arttag,art.cat_id,user_id,nick,pubtime,title,comm,content,cat.catname from art left join cat on art.cat_id=cat.cat_id where 1 $art = mGetAll($sql); $sql = 'select * from cat'; $cat = mGetAll($sql); include(ROOT.'/view/front/index.html'); 如果处于当前页码数 则不显示连接 直接显示页码数 index.html 2.12 评论列表评论列表 commlist.html
commlist.php require('./lib/init.php'); $sql = 'select * from comment order by comment_id desc'; $comm = mGetAll($sql); include(ROOT.'/view/admin/commlist.html'); 2.13 评论删除评论删除(学员自行完成学员自行完成) commdel.php require('./lib/init.php'); $comment_id = $_GET['comment_id']; //获取当前评论的 art_id $sql = 'select art_id from comment where comment_id=' . $comment_id; $art_id = mGetOne($sql); //删除评论表这条评论 $sql = 'delete from comment where comment_id=' . $comment_id; $rs = mQuery($sql); //如果获取art_id 成功 更改art表的comm 评论数 if($art_id) { $sql = 'update art set comm=comm-1 where art_id=' . $art_id; mQuery($sql); } //跳转到上一页 commlist.php $ref = $_SERVER['HTTP_REFERER']; header("Location: $ref"); 第第3章章 实战功能实战功能 3.01 文件上传讲解文件上传讲解 给我们的博文加上一张图片 文件上传的过程文件上传的过程 PHP的上传非常简单,只需要写好表单并提交,则文件会"自动"传送到服务器上. 上传文件的相关信息存储在$_FILES超级全局数组里中. 因此,我们只需要从$_FILES中读出文件在哪儿,叫什么, 再把该文件移动到我们想要保存的位置,上传就完成了. 表单的写法表单的写法 要注意要注意3点点 form的method要为post类型 form一定要加enctype=multipart/form-data input为file类型 下面是一个典型的上传表单下面是一个典型的上传表单 1.html $_FILES数组讲解数组讲解 临时文件,我们去找是找不到的,因为在我们php代码运行到最后一行,结束掉,这个文件就不存在了.所以我们需要在结束前,将这个文件 转移到别的地方去. 如何移动这个文件? move_uploaded_file — 将上传的文件移动到新位置 1.php if(move_uploaded_file($_FILES['pic']['tmp_name'],'./'.$_FILES['pic']['name'])) { echo 'ok'; } else { echo 'fail'; } 3.02 生成随机文件名并按日期存储生成随机文件名并按日期存储 网站比较大,每天会上传多个图片,所以图片按照 年/月/日 来存放图片 img3.cache.netease.com/cnews/2015/8/19/20150819154739836b4.jpg 比较小的网站,按照 年/月日 存放图片 图片名称是一个随机字符串组成的,防止上传图片重名 is_dir — 判断给定文件名是否是一个目录 mkdir — 新建目录 mkdir($path,0777,true) true代表级联创建目录 upload/2015/08 依次创建目录 rand — 产生一个随机整数 strrchr — 查找指定字符在字符串中的最后一次出现 1.按日期生成目录按日期生成目录 生成 upload/2015/08/lkjiga.png $path = './upload/' . date('Y/m'); if(!is_dir($path)) { mkdir($path , 0777 , true); } 2.生成随机文件名生成随机文件名,获取文件后缀获取文件后缀 strrchr — 查找指定字符在字符串中的最后一次出现 $rand = rand(10000,99999); //获取文件后缀 $ext = strrchr( $_FILES['pic']['name'] , '.'); $des = $path.'/'.$rand.$ext; echo move_uploaded_file($_FILES['pic']['tmp_name'], $des) ? 'ok' : 'fail'; 面试题:5种以上方法获取文件后缀 http://www.zixue.it/thread-134-1-1.html 3.03 文件上传应用于项目文件上传应用于项目 substr — 返回字符串的子串 str_shuffle — 随机打乱一个字符串 封装函数 func.php /** * 按日期创建存储目录 */ function createDir() { $path = '/upload/'.date('Y/m/d'); $abs = ROOT . $path; if( is_dir($abs) || mkdir($abs , 0777 , true) ) { return $path; } else { return false; } } /** * 生成随机字符串 * @param int $length 产生几位的随机字符 */ function randStr($length=6) { $str = str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz23456789'); $str = substr($str, 0 , $length); return $str; } /** * 获取文件后缀 * @param str $name 文件名 */ function getExt($name) { return strrchr($name , '.'); } artadd.html
序号 | 留言者 | 内容 | IP | 状态 | |
删除 |
逗你玩
略带恶意: 表单输入 如下内容 恶意: 可以读取cookie,就能送去远方 偷cookie的代码 防范防范: 1)不需要展示HTML标签的表单内容,入库时直接转成实体显示 htmlspecialchars $_POST['content] = htmlspecialchars($_POST['content']); 可以用正则检测输入框必须为email等合法数据 2)需要展示HTML标签的部分, 仅允许展示有限的标签,如p,a,img等 如strip_tags 来过滤html标签 3)严格检查标签属性,及链接地址 5.05 网站上线与发布步骤网站上线与发布步骤 作业作业 1. 写通用上传函数完成多文件上传 2. 如何限制上传文件的类型? 比如只能传jpg,png 3. 如何限制文件上传的大小? 4. 大文件上传参数配置[要求能上传30M的文件] 5. 验证码如何生成干扰线验证码如何生成干扰线? 提示:imageline()+随机数,画随机位置,随机颜色的线段 6. 如何用其他字体生成验证码如何用其他字体生成验证码,或用中文验证码或用中文验证码,再让文字倾斜再让文字倾斜? 提示: imagettftext()+字体文件. 并注意,字母可以随便选取,因为26 个字母大家都认识. 但中文不能随便选,因为汉字的生僻字太多. 一般是把常用的1000个汉字放在一个数组里,然后随机选几个. 7. 如何判断验证码输入的是正确的如何判断验证码输入的是正确的? 提示: session 8. 缩略图两端自动补白 不管一张图片是"瘦高",还是"宽扁", 我都等比例缩略,然后放到一个正方形的图中去, 那么,正方形的上下两边,或左右两边,要留出一个空白. 提示: 先计算缩放比例,然后计算左右/上下,各留多少空白,并封装成函数 9. 水印如何放在右上角? 右下角, 左下角,正中间? 试写一个函数用于快速加水印,并允许指定水印的位置 (提示:要计算小图小对于大图的位置) 10. 一个域名下最多可设置多少个cookie? 11. 单个cookie的值,最大可以多少字节? 提示:这两个问题,因具体的浏览器而略有不同, 搜索"cookie 长度" , "cookie 数量",并亲自测 试. 得到一份自己说出来有底气的答案. 12. 动手实验cookie与session能存储的数据类型
Leave a Comment
评论失败