ThinkPHP开发博客系统笔记之二

1. 登录验证码javascript

当用户登录的时候咱们但愿也弹出验证码,有两种方法能够实现:一是再增长一个弹出验证码的dialog,二是和注册共用一个验证码dialog。第一种方法有大量重复代码,因此咱们使用第二种方法。首先,为了让javascript区分是登录仍是注册,咱们给验证码表单增长一个自定义属性:form-click。php

login.js

<form id="verify_register" form-click="">
    <ol class="ver_error"></ol>
    <p>
        <label for="verify">验证码:</label>
        <input type="text" name="verify" class="text" id="verify">
        <span class="star">*</span>
        <a href="javascript:void(0)" class="changeimg">换一换</a>
    </p>
    <p>
        <img src='{:U("Login/verify",'','')}' class="changeimg verifyimg">
    </p>
</form>

如今login表单和register表单的验证中submitHandler只作两件事情:给verify_register的form-click属性赋值,打开verify_register窗口:css

login.js

$('#login').validate({
        submitHandler: function(form){
            $("#verify_register").attr('form-click', 'login');
            $("#verify_register").dialog('open');
        },
login.js 

$("#register").dialog({
    width: 430,
    height: 370,
    modal: true,
    resizable: false,
    autoOpen: false,
    title: "注册新用户",
    closeText: "关闭",
    buttons: [{
        text: "提交",
        click: function(e) {
            $(this).submit();
        },
    }],
    
}).validate({
    submitHandler: function() {
        $("#verify_register").attr('form-click', 'register');
        $("#verify_register").dialog('open');
    },

验证码的检测以及注册或登录成功后的跳转都放到verify_register的submitHandler中进行处理:html

login.js

submitHandler: function(form) {
    if ($("#verify_register").attr('form-click') == 'register') {
        $('#register').ajaxSubmit({
            url: ThinkPHP["MODULE"] + "/User/register",
            type: "POST",
            data: {
                verify: $('#verify').val(),
            },
            beforeSubmit: function() {
                $('#loading').dialog('open');
            },
            success: function(responseText) {
                if (responseText) {
                    $('#loading').css('background', 'url(' + ThinkPHP['IMG'] + '/reg_success.png) no-repeat 20px center').html('数据新增成功...');
                    setTimeout(function() {
                        $('#register').dialog('close');
                        $('#verify_register').dialog('close');        //关闭注册界面
                        $('#loading').dialog('close');        //关闭提示界面
                        $('#verify_register').resetForm();            //还原注册表单
                        $('#span.star').html('*').removeClass('succ');        //恢复*去掉对号
                    }, 1000);
                }
            },
        });
    } else if ($("#verify_register").attr('form-click') == 'login') {
        $(form).ajaxSubmit({
            url: ThinkPHP['MODULE']  + '/User/login',
            type: 'POST',
            beforeSubmit: function() {
                $('#loading').dialog('open');
            },
            success: function(responseText) {
                if (responseText == -9) {
                    $('#loading').dialog('option', 'width', 200).css('background', 'url(' + ThinkPHP['IMG'] + '/warning.png) no-repeat 20px center').html('帐号或密码不正确...');
                    setTimeout(function(){
                        $('#loading').dialog('close');
                        $('#loading').dialog('option', 'width', 180).css('background', 'url(' + ThinkPHP['IMG'] + '/loading.gif) no-repeat 20px center').html('数据交互中...');
                    }, 2000);
                } else {
                    $('#loading').dialog('option', 'width', 220).css('background', 'url(' + ThinkPHP['IMG'] + '/reg_success.png) no-repeat 20px center').html('登陆成功,跳转中...');
                    setTimeout(function(){
                        location.href = 'http://www.baidu.com';
                    }, 1000);
                }
            },
        });
    }
},

 

2. 自动登陆java

如要要实现自动登陆,那么必须保存用户的登录信息,这有两种方式:cookie和session。前者保存在客户端,安全性较低;后者存储在服务器端,安全性高,可是会占用服务器资源。这里咱们先采用session的方式。node

保存方式已经决定了,接下来就要考虑保存哪些内容。咱们保存用户的id,最后登陆的IP和登陆时间。为了存储这些信息,咱们须要对原来的用户表进行调整,添加两个字段:last_login和last_ip。jquery

login.js中的一个小错误致使在这里浪费了半个小时,特地作个记录,以避免之后又犯一样的错误。css3

login.js

} else if ($("#verify_register").attr('form-click') == 'login') { $(form).ajaxSubmit({    //此处应该是$('#login') url: ThinkPHP['MODULE'] + '/User/login', type: 'POST', beforeSubmit: function() { $('#loading').dialog('open'); }, success: function(responseText) { if (responseText == -9) {

继续上面的话题,更新用户信息和写入session的动做都是在UserModel的login方法中完成的:ajax

UserModel.class.php

$user = $this->field('id, password, last_login, last_ip')->where($map)->find();
if ($user['password'] == $password) {
    
    //更新登录信息
    $update = array(
        'id'             => $user['id'],
        'last_login'    => NOW_TIME,
        'last_ip'        => get_client_ip(1),    //参数1表示返回long型数字
    );
    $this->save($update);
    
    //登录信息写入SESSION
    $auth = array(
        'id'            => $user['id'],
        'last_login'    => $user['last_login'],
        'last_ip'        => $user['last_ip'],
    );
    
    session('user_auth', $auth);

    return $user['id'];
} else {
    return -9;    //用户密码错误
}

在一个须要登陆的系统中,不少操做是只有登陆用户才能够作的,因此在许多地方咱们都须要验证用户是否已经登陆,这是经过检测session是否存在实现的。咱们这一部分提取出来创建一个新的HomeController,IndexController、UserController和LoginController都继承HomeController。浏览器

HomeController.class.php

<?php
namespace Home\Controller;
use Think\Controller;

class HomeController extends Controller {
    protected function login() {
        if (session('?user_auth')) {
            return 1;
        } else {
            $this->redirect('Login/index');        //redirect方法自带U方法
        }
    }
}
IndexController.class.php

<?php
namespace Home\Controller;

class IndexController extends HomeController {
    public function index() {
        if ($this->login()) {
            echo "Login successfully";
        }
    }
}

 接下来咱们要把用户信息写入COOKIE,由于COOKIE是保存在客户端的,为了加强安全性,咱们须要对其进行加密存储。流程以下:

1. 在配置文件里设置一个密钥COOKIE_KEY

2. 在加密函数中,首先对密钥值执行sha1,将结果与用户名进行异或,再将结果用base64加密

config.php

<?php
return array(
    'TMPL_PARSE_STRING'    => array(
        '__CSS__'    =>    __ROOT__.'/Public/'.MODULE_NAME.'/css',
        '__JS__'    =>    __ROOT__.'/Public/'.MODULE_NAME.'/js',
        '__IMG__'    =>    __ROOT__.'/Public/'.MODULE_NAME.'/img',
    ),
    
    //cookie秘钥
    'COOKIE_KEY'    => 'www.juedi.com',
);
function.php

//COOKIE加解密,0加密,1解密
function encrypttion($username, $type = 0) {
    $key = sha1(C('COOKIE_KEY'));
    
    if (!$type) {
        $username = base64_encode($username ^ $key);
    } else {
        $username = base64_decode($username) ^ $key;
    }
    
    return $username;    
}

 如今来添加自动登陆功能。

首先须要在模板上添加一个checkbox:

index.tpl

<span class="username">
    <input type="text" name="username" placeholder="用户名/邮箱">
    <label class="auto" for="auto"><input type="checkbox" id="auto" name="auto">自动登陆</label>
</span>

UserController和UserModel中的login方法也须要作相应的改动:

UserModel.class.php

//用户名加密写入COOKIE
if ($auto == 'on') {
    cookie('auto', encryption($user['username']), 3600 * 24 * 30);
}

在HomeController的login方法中首先就须要判断是否自动登陆:

HomeController.class.php

class HomeController extends Controller {
    protected function login() {
        //处理自动登陆,当cookie存在且session不存在的状况下,生成session
        if (!is_null(cookie('auto')) && !session('?user_auth')) {
            
            $username = encryption(cookie('auto'), 1);
            $map['username'] = $username;
            
            $db = D('user');
            $user = $db->field('id, username, last_login, last_ip')->where($map)->find();
            
            //登录信息写入SESSION
            $auth = array(
                'id'            => $user['id'],
                'username'        => $user['username'],
                'last_login'    => $user['last_login'],
                'last_ip'        => $user['last_ip'],
            );
            
            session('user_auth', $auth);
        }
        
        //判断session是否存在
        if (session('?user_auth')) {
            return 1;
        } else {
            $this->redirect('Login/index');        //redirect方法自带U方法
        }
    }

LoginController的login方法也要作相应的改动:

LoginController.class.php

public function index() {
    if (!session('?user_auth')) {        //只有当session不存在时才能够看到登陆界面
        $this->display();
    } else {
        $this->redirect('Index/index');
    }
    
}

 

3. 绑定IP验证登陆

若是有人恶意地将cookie复制到另一台电脑上,那么他就能够实现自动登陆。为了防止cookie被盗用,咱们把IP地址也加密写入cookie,在自动登陆的时候验证是否是用户登陆时的IP地址。

UserModel.class.php

//用户名和IP加密写入COOKIE
if ($auto == 'on') {
    cookie('auto', encryption($user['username']. '|' .get_client_ip()), 3600 * 24 * 30);
}
HomeController.class.php

if (!is_null(cookie('auto')) && !session('?user_auth')) {
    
    $value = explode('|', encryption(cookie('auto'), 1));
    list($username, $ip) = $value;
    if ($ip == get_client_ip())
    {
        $map['username'] = $username;
        
        $db = D('user');
        $user = $db->field('id, username, last_ip')->where($map)->find();
        
        //自动登陆更新登录信息
        $update = array(
            'id'             => $user['id'],
            'last_login'    => NOW_TIME,
        );
        $db->save($update);
        
        //登录信息写入SESSION
        $auth = array(
            'id'            => $user['id'],
            'username'        => $user['username'],
            'last_login'    => NOW_TIME,
            'last_ip'        => $user['last_ip'],
        );
        
        session('user_auth', $auth);
    }
}

 

4. 微博主页设计

咱们把微博主页也分红header、main和footer三个部分。

在这里咱们先复习一下CSS的定位position。position主要有四个属性:static(默认属性)、absolute、relative、fixed,区别以下:

  1. static:默认值。没有定位,元素出如今正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
  2. absolute:脱离了文档流,生成绝对定位的元素,相对于 static 定位之外的第一个父元素进行定位。元素的位置经过 "left", "top", "right" 以及 "bottom" 属性进行规定。
  3. relative:不脱离文档流,生成相对定位的元素,相对于其正常位置进行定位。所以,"left:20" 会向元素的 LEFT 位置添加 20 像素。
  4. fixed:脱离了文档流,生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置经过 "left", "top", "right" 以及 "bottom" 属性进行规定。

对于脱离了文档流的元素(即便用了absolute或fixed定位的元素),它相邻元素的margin再也不相对于此元素。还需注意left、top、right、bottom属性和margin的区别。根据position属性的不一样,left等值相对的对象也不一样,而margin一直是相对于相邻元素的。

此外这一部分咱们还用到了CSS3的box-shadow属性,关于该属性的使用能够参考 http://www.w3cplus.com/content/css3-box-shadow。

在作导航的时候遇到了一个问题今天没能解决:

能够在上图看到每一个导航项目都太靠下了。

 接着昨天的问题往下说。

进过今天下午半个多小时的仔细研究,终于解决了昨天出现的问题,特地将解决过程记录在此,以备参考。

首先,从图中能够很明显地看出li元素超出了header的下边界,其实这里还有一个很容易被忽视的问题,那就是header_main有点儿靠右。

我给ul加上一个float:left:

 

#header ul {
    float: left;
}

而后经过firebug选取ul元素以下图:

我忽然发现ul竟然有16px的上下margin,在index.css开头我明明已经把ul的margin都设为0了呀?!

//起始CSS

body, h1, h2, h3, h4, h5, h6, ol, ul, p, form {
    margin: 0;
    padding: 0;
}

再仔细一看,终于发现了问题所在:注释!

对,就是开头的”//起始CSS“使上面的CSS设置没有生效,由于在CSS中注释只能用/* */。改为以下样式就行了:

/*起始CSS*/

body, h1, h2, h3, h4, h5, h6, ol, ul, p, form {
    margin: 0;
    padding: 0;
}

如今又出现了一个新问题:文字太靠上了。咱们想让连接垂直居中,这就须要用到line-height属性了。line-height有一个特性,叫作垂直居中性,把line-height值设置为height同样大小的值能够实现单行文字的垂直居中。

 

又有一个问题:当鼠标放到导航连接上时,咱们想让它的背景颜色有42px的高度。这就须要将a元素设置为display:block:

最终的CSS代码以下:

index.css

#header .nav {
    float: left;
    height: 42px;
    margin: 0 0 0 30px;
}

#header ul li {
    float: left;
    margin-left: 5px;
    height: 42px;
    line-height: 42px;

}

#header ul li a {
    display: block;
    padding: 0 10px;
    font-size: 16px;
    color: #fff;
    text-decoration: none;
}

#header ul li a:hover {
    background: #333;
}

#header ul li a.selected {
    background: #333;
}

 如今咱们来实现“消息”和“帐号”的弹出菜单功能:

index.tpl

<li class="app">消息
    <dl class="list">
        <dd><a href="#">@到个人</a></dd>
        <dd><a href="#">收到的评论</a></dd>
        <dd><a href="#">发出的评论</a></dd>
        <dd><a href="#">个人私信</a></dd>
        <dd><a href="#">系统消息</a></dd>
        <dd><a href="#" class="line">发私信>></a></dd>
    </dl>
</li>
index.css

#header .app {
    padding: 0 10px;
    position: relative;
    cursor: pointer;
}

#header dl.list {
    width: 100px;
    background: #fff;
    border: 1px solid #666;
    position: absolute;
    top: 42px;
    left: -50px;
    display: none;
}

#header dl.list a {
    color: #444;
    height: 30px;        /*覆盖掉继承的height和line-height*/
    line-height: 30px;
}

#header dl.list a.line {
    border-top: 1px solid #eee;
}
index.js

$(function() {
    $('.app').hover(function(){
        $(this).css({
            background: '#444',        //注意,千万不要用分号,属性值必定加引号!!!
            color: '#666',
        }).find('.list').show();
    }, function(){
        $(this).css({
            background: '#666',        //注意,千万不要用分号,属性值必定加引号!!!
            color: '#fff',
        }).find('.list').hide();
    });
});

如今主页的框架基本已经搭好了,咱们如今把里面常常须要用到的东西分离出来,作成模板使用。

首先咱们创建两个文件夹:Public和Base。Public里存放各个模板文件,Base中存放引用模板文件的文件,index.tpl文件只须要继承common.tpl便可。

 

5. 退出及跳转页

当用户退出登陆的时候,咱们应该删除用户的session,若是用户选择了自动登陆,那么还应该删除用户的cookie,而后跳转到登陆页面。

UserController.class.php

//
退出登陆 public function logout() { //清除session session(null); //清理自动登陆生成的cookie cookie('auto', null); $this->success('退出成功', U('Login/index')); }

如今只要把连接加到微博首页就能够了。

index.tpl

<li class="app">帐号
    <dl class="list">
        <dd><a href="#">我的设置</a></dd>
        <dd><a href="#">排行榜</a></dd>
        <dd><a href="#">申请认证</a></dd>
        <dd><a href="{:U('User/logout')}" class="line">退出</a></dd>
    </dl>
</li>    

 下面来设计错误和成功后的跳转页,首先在配置文件里定义两个变量:

config.php

//错误跳转模板
'TMPL_ACTION_ERROR'        => 'Public/jump',

//成功跳转模板
'TMPL_ACTION_SUCCESS'    => 'Public/jump',

在设计跳转页的过程当中有几个问题须要解决:1. 文字居中显示;2. 文字前加图标,文字与图标在同一水平位置;

第一个问题的解决方法是给父元素增长padding,让文字到垂直居中,而后用text-align:center使文字水平居中:

jump.tpl

.info {
    margin: 100px auto;
    padding: 200px 0 0 0;
    height: 300px;
    width: 1200px;
    background: #fafafa;
    text-align: center;
}

第二个问题是经过设置背景图片的位置解决的:

jump.tpl

.error {
    background: url(__PUBLIC__/{:MODULE_NAME}/img/jump_error.png) no-repeat left bottom;
}

.success {
    background: url(__PUBLIC__/{:MODULE_NAME}/img/jump_success.png) no-repeat left bottom;
}

 

6. 微博发布区设计

如今但是设计微博的发布区。

这一部分主要是在main区域,咱们把它分红main_left和main_right两个部分。其中main_right的css代码以下:

index.css

#main .main_right {
    float: right;
    width: 300px;
    background: #d0d0d0;
}

咱们能够看到背景颜色只应用到了文字所在的一行,若是咱们想让背景颜色充满整个区域,那就须要给区域加个height:

index.css

#main .main_right {
    float: right;
    width: 300px;
    min-height: 800px;
    background: #d0d0d0;
}

由于左侧区域是微博的发布区,因此会有不少内容,当内容超过了该区域的高度时就会出问题:

能够看到,超出的内容到了main区域的下方,导致footer的内容挤到了右边。

这个问题能够经过javascript解决,代码以下:

 

index.js

//高度保持一致
if ($('.main_left').height() > 800) {
    $('.main_right').height($('.main_left').height());
    $('#main').height($('.main_left').height());
}

这里还有一个问题,当咱们向下拉滚动条时,提交按钮会到导航栏的上方,以下图所示:

这个能够经过z-index解决:

index.js

#header {
    position: fixed;    
    width: 100%;
    height: 42px;
    top: 0px;        /*top定义了一个定位元素的上外边距边界与其包含块上边界之间的偏移*/
    background: #666;
    z-index: 9999;
}

界面设置基本作好了,接着要实现一些小功能。

第一个功能就是限制用户的输入在140个字之内,而且数字随着用户的输入变化,以下图:

这个是经过javascript实现的:

index.js

//微博输入内容计算字个数
$('.weibo_text').on('keyup', weibo_num);

function weibo_num() {
    var total = 280;
    var len = $(this).val().length;
    var temp = 0;
    if (len > 0) {
        for (var i = 0; i < len; i++) {
            if ($(this).val().charCodeAt(i) > 255) {
                temp += 2;
            } else {
                temp ++;
            }
        }
        
        var result = parseInt((total - temp) / 2);
        $('.weibo_num').html('您还能够输入<strong>' + result + '</strong>个字');
    }
}

 

7. 引入表情插件

这一节咱们在微博发布区引入一款jquery表情插件,过程比较简单,就是把css、js文件放到相应目录,从原来的index.html文件里复制一些内容到咱们的index.tpl就能够了。

不过,在这里仍是出现了一个小问题。

当咱们添加表情的时候,字数显示并无减小,咱们须要修改js文件:

index.js

//微博输入内容获得光标计算字个数
$('.weibo_text').on('focus', weibo_num);

这里又出现了一个新问题,第一次添加表情的时候字数不会减小,直到再次添加字数才开始变化:

 

8. 微博发布及表分析

首先咱们须要新建一个表用来存储用户发布的微博及其它一些信息

这里须要注意的是上面的content_over字段。因为varchar查询起来速度比较慢,因此这里咱们用char类型存储微博内容。由于char最多只能存储255个字符,因此咱们把280-255=25个字符存到content_over中。

今天终于解决了一个困扰我多日的问题。虽然最后发现是个人粗心形成的,可是还得记录一下。

错误仍是firebug发现的:

点击上图中的绿色部分能够进入jquery.js查看:

从这儿能够开出问题出在val()方法上,elem没有nodeName。nodeName是HTML元素的一个属性,elem没有这个属性就说明它不是HTML元素,或者它是undefined或null。咱们能够看一下:

index.js

function weibo_num() {
    alert(this[0]);    //查看this[0],也就是elem var total = 280;
    var len = $(this).val().length;
    var temp = 0;
    if (len > 0) {
        for (var i = 0; i < len; i++) {
            if ($(this).val().charCodeAt(i) > 255) {
                temp += 2;
            } else {
                temp ++;
            }
        }
    

结果以下图所示:

果真是undefined。为何会是这个值呢?

仔细检查weibo_num函数,我发现了问题所在:$(this)!!!

这里应该用$('.weibo_text')!改过来以后一切恢复正常。

相关文章
相关标签/搜索