管理首页

Macaw::get('/admin', 'adminIndex@index');
//里面两个都是单引号,然后/admin斜杠在前,后面的路径,admin是命名空间,Index是类,index是方法,注意这里是反斜杠,否则会提示找不到类

思路是:先在index.php定义路径,然后按照路径去controller里面建类文件、类和方法,然后在方法里面调用views模板(即$this->display('index/index'))

自定义全局路径

获取不同项目的路径

  /*
    下面这个是为了能够自动获取项目路径而写的
    主要就是为了根据不同的环境(https和端口)拼出来url,以使得所有的资源都可以使用相对路径
  */

  function getCurUrl(){
    $url = "http://";

    if(isset($_SERVER['SERVER_HTTPS']) && $_SERVER['SERVER_HTTPS'] == 'on'){
      $url = "https://";
    }

    //判断端口
    if($_SERVER['SERVER_PORT'] != '80'){
      $url .= $_SERVER['SERVER_NAME'].":".$_SERVER['SERVER_PORT'];
    }else{
      $url .= $_SERVER['SERVER_NAME'];
    }

    return $url;
  }

获取了路径之后,在controller里面定义好几个路径的变量

        $this->assign('url', $url.'/app/views/admin/resource'); //自己模板下的资源
        $this->assign('public', $url.'/app/views/public'); //公共模板下的资源
        $this->assign('res', $url.'uploads'); //文件上传资源

之后在模板文件里面的适当位置使用变量即可。

这一部分,在admin和home层面上是不同的url路径,所以在admin和home的基类里都重写display方法,在这个时候就定义好url。

admin\Index类添加以下方法
//设置url变量,重写父类里的display方法
protected function display($template) {

  $url = getCurUrl();
  $this->assign('url', $url.'/app/views/admin/resource'); //自己模板下的资源
  $this->assign('public', $url.'/app/views/public'); //公共模板下的资源
  $this->assign('res', $url.'uploads'); //文件上传资源

  echo $this->twig->render($template.'.html', $this->data);
}

home\Index类添加以下方法:
//设置url变量,重写父类里的display方法
protected function display($template) {

  $url = getCurUrl();
  $this->assign('url', $url.'/app/views/'.TEMPNAME.'/resource'); //自己模板下的资源
  $this->assign('public', $url.'/app/views/public'); //公共模板下的资源
  $this->assign('res', $url.'uploads'); //文件上传资源

  echo $this->twig->render($template.'.html', $this->data);
}

设置重写规则例外

在.htaccess里面,之前有这样一条规则:
RewriteRule ^(.*)$ index.php?$1 [QSA,L]

这条规则把所有的访问,都变成abc.cn/index.php/xxx/xxx了
这时候如果要访问资源的话,变成这样的路径是找不到资源的,所以要设置资源文件例外

添加一条规则就可以:
RewriteCond %{REQUEST_URI} !^.*(.css|.js|.png|.gif|.jpg|.jpeg)

这个应该也是正则表达式吧

根据controller传过来的字段来显示样式

<li {% if menumark == 'category' %} class="sel" {% endif %}><a href="/admin/category">商品分类</a></li>

//总觉得这个办法有点笨

在Index类中创建构建函数

    function __construct(){
        $this->assign('menumark', 'product');
        parent::__construct(); 
    }

知识点:在子类中建立构造函数,一定要使用parent::__construct()才能使用父类的构造函数,如果父类没有构造函数就不需要这个。

模板文件循环显示数据

{% for link in links %}

<tr>
    <td>{{ link.id }}</td>
    <td><input type="text" name="ord[{{ link.id }}]" value="1" class="inputtext input40" /></td>
    <td>{{ link.name }}</td>
    <td><a href="{{ link.url }}" target="_blank">{{ link.url }}</a></td>
    <td>
        <a href="/admin/link/mod/{{ link.id }}" class="admin_edit mar3">修改</a>
        <a href="/admin/link/del/{{ link.id }}" class="admin_del" onclick="return confirm('你确定要删除链接{{ link.name }}吗?')">删除</a>
    </td>
</tr>

{% endfor %}

(坑)数据库读出来的中文乱码

这个原因是charset没有设置的原因,在baseDao.php里medoo的设置里面加上

'charset' => 'utf8"

就好了

(坑)读数据库的时候使用的命令

我用$db->select()读单条数据,结果显示不正常,出来是一个二维数组
后来跟教程对比了一下,使用$db->get()才是一个一位数组
以后这种情况很多,一定要注意啊

(坑)Medoo组件在连不上网的时候会报错

报错却是找不到Class,你说坑不坑,还以为什么地方写错了。

列表页修改顺序

如果第一行name是a,value是1,第二行name是b,value是2,第三行name是c,value是3,那么post出去,就会获得数组:
{a:1, b:2, c:3}
如果第一行name是a[1],value是1,第二行name是a[2],value是2,第三行name是a[3],value是3,那么post出去,就会获得数组:
'a' =>

array (size=4)
  1 => string '1' (length=2)
  2 => string '2' (length=2)
  3 => string '3' (length=2)

这样就可以用for loop每条每条去修改顺序了。

$num = 0;
foreach($_POST['ord'] as $id=>$ord){
    $num += $db->update('link', ['ord'=>$ord], ['id'=>$id]);
}

if($num>0){
    $this->success('/admin/link', '修改成功');
}else{
    $this->success('/admin/link', '修改失败');
}

有两个知识点:

第一: foreach($_POST['ord'] as $id=>$ord) 是把每一条值里面的key和value赋值到变量$id和$ord里了
第二:使用$num=0和$num += $db返回的1,最后判断$num是不是大于0来判断整体是不是执行了

这一点挺有意思的。

提交表单提示404

今天(10.19)自己写增加管理员,发现提交表单后移一直显示404,我看了我的路径都没有错啊,找了几乎半个小时。
最后无奈按照老师的方法,把link的路径复制一遍改,一提交居然就可以了。对比了发现,原来我用的是get方法,而提交表单需要也接受post,所以应该写Macaw::any();
就这个事情又浪费了半个小时,哎。

安全问题如何解决

现在这些方法,都是可以直接访问的,即使看不到前台,也可以通过伪造url直接访问,增删改查其实都可以,这是非常大的安全隐患,应该怎么解决呢?后面有没有相应的教程呢?

(10.21 这个问题在后面使用session判断是否登录的时候解决了,如果没有登录,是访问不到这些控制器的,因为在父类控制器admin里面就已经加入了判断。)

用户名已经存在是不是没有判断

自己添加了判断在里面。

如何制作验证码

首先定义一个路由 /admin/login/vcode,指向到Login类的vcode方法。
到Packagist.org寻找Captcha组件
然后进入项目根目录使用composer安装,composer require gregwar/captcha
安装完后,在login.php use它,然后在vcode方法里这么写:

    function vcode(){
        //生成验证码
        $builder = new CaptchaBuilder;
        $builder->build();
        //获得验证码并保存到session,以备后续校验
        $_SESSION['code'] = strtoupper($builder->getPhrase());
        //输出验证码图片
        header('Content-type: image/jpeg');
        $builder->output();
    }

别忘了还需要在入口文件Index.php里面开启session_start;
另外还需要有一段js,让用户点击的时候刷新图片,后面加一个time参数,避免由于浏览器缓存导致图片不刷新。

$(".authcode").click(function(){
    $(this).attr("src", $(this).attr("src") + '?time=' + new Date().getTime());
});

讲输入的字母变成大写

<input class="fl" type="text" maxlength="6" onkeyup="if(this.value!=this.value.toUpperCase()) this.value=this.value.toUpperCase()" name="code" />

验证用户登录后如何处理session

首先用户登录验证后,讲获取到的用户id和name信息存入session
$_SESSION = $user;

然后在某个控制器里面,讲$_SESSION assign到前端页面,在Admin的构造方法里添加。
$this->assign('session', $_SESSION);

之后就可以在模板文件里使用{{session.id}}来使用数据了。

判断是否登录

首先创建一个token,因为直接用id判断不安全:
$_SESSION['admin_token'] = md5($user['id'].$_SERVER['HTTP_HOST']);

在Helpers.php里判断这个,返回1/0

function ew_login($uType){

return true;
//return md5($_SESSION['id'].$_SERVER['HTTP_HOST']) == $_SERVER[$uType.'_token'] ? 1 : 0;

}

在admin.php的构建函数里,调用ew_login('admin'),如果是假就返回登录页,提示请先登录。

if(!ew_login('admin')){
  $this->error('/admin/login', '请先登录');
}

由于login也集成了admin类,加了这个判断之后,连login页也打不开了,所以要把login类改成集成BaseControllers(admin的父类)。
在BaseControllers的display方法里,添加:

  $url = getCurUrl();
  $this->assign('uri', $url);

同时在__construct里由于路径只是“/app/views”,所以在login.php的index()里面调用模板的路径要变成'admin/login/index'。

这里面有一个坑,我才发现__construct里面的内容,BaseControllers里面有,Admin里面也有,我之前把BaseControllers里的构造函数直接剪切到Admin里面去了,这次改的时候才发现的这个错误。

用户退出

固定的写法,记下来。

    function logout(){
        //先将session设置为空值
        $_SESSION = array();

        //如果cookie里面还有session_name,设置成空值,时间设置为之前以便过期,/表示从根目录开始删除
        if(isset($_COOKIE[session_name()])){
            setcookie(session_name(), '', time()-3600, '/');
        }

        //销毁session
        session_destroy();

        //提示
        $this->success('/admin/login', '退出成功');
    }

HTML中的固定列表处理

现在controller文件里面assign一个数组:

$this->assign('position', [
    '1'=>'首页顶部广告(980*80)', 
    '2'=>'首页底部广告(980*80)', 
    '3'=>'所有页面顶部广告(980*80)', 
    '4'=>'所有页面底部广告(980*80)', 
    '5'=>'首页焦点图广告(730*300)'
]);

再在html文件里面循环显示标签就可以了。

{% for k,v in position %}
<option value="{{k}}">{{v}}</option>
{% endfor %}

一个神奇的事情

居然call了两次模板文件,都不知道是什么原因:

2020-10-22 11-35-48.png

查到原因是路由文件里写了两次mod方法的路由。

处理图片上传

安装slince/upload组件

composer require slince/upload

组件使用教程在这里,用以下代码来处理文件上传:

if($_POST['do_submit']){

    unset($_POST['do_submit']);

    //处理图片上传
    $path = TEMPDIR.'/uploads/ad';
    $builder = new UploadHandlerBuilder();

    $handler = $builder
        ->allowExtensions(['jpg', 'gif', 'png'])
        ->allowMimeTypes(['image/*'])
        ->saveTo($path)
        ->getHandler();

    $files = $handler->handle();

    $filename = $files['logo']->getUploadedFile()->getClientOriginalName();
    $newfilename = date('Y-md').'-'.uniqid().'.'.$files['logo']->getUploadedFile()->getClientOriginalName();

    rename($path.'/'.$filename, $path.'/'.$newfilename);
    $_POST['logo'] = $newfilename;
    
    //添加数据
    $db = new BaseDao();
    if($db->insert('ad', $_POST)){
        $this->success('/admin/ad', '添加成功');
    }else{
        $this->error('/admin/ad/add', '添加失败');
    }
}

input是file的时候,提交过去会存在$_FILES里,我在$_POST里找了半天,其实是没有这个值的,需要在$_FILES里面有,而且里面包含了图片的name, type, size等信息。

删除广告的时候把图片删除

在删除了记录之后,把相对应的图片也删除掉,使用@unlink命令。

$path = TEMPDIR.'/uploads/ad';
@unlink($path.'/'.$logo);

更新广告图片

把update的代码拿过来即可,需要注意的是:

  1. 获取当前图片名称,更新成功后,删除原有图片
  2. 如果图片的后缀是大写,那么需要在->allowExtensions(['jpg', 'gif', 'png'])添加'JPG',否则会出错。

获取设置信息并存储在array里

最后编辑:2021年01月05日 ©著作权归作者所有

发表评论