赞
踩
因为前段时间需要搭建一套题库系统,但是网上并没有很好的开源题库
机缘巧合之下,看到fastadmin整合的xunsearch插件(提供了很好的搜索引擎)
由此我决定尝试使用fastadmin来搭建一套题库系统,而且fastadmin便捷的api给了搜题系统很大的接口支持。可以很简单的写出需要的接口,还不用担心鉴权的问题(接口收费、接口鉴权等各种功能都很好写,都有对应的插件,简化接口搭建时间)
本文利用fastadmin后台框架 + xunsearch插件,快速搭建题库系统
服务器使用的是腾讯云的99一年的服务器
本文搭建基于CentOS8.0(xunsearch服务器端必须使用linux系统,官网也是强烈推荐)
服务器环境使用宝塔搭建
提示:xunsearch服务端可以和题库系统分开部署在不同服务器上(作者并未尝试),有需求可以自己探究,本文仅展示fastadmin框架与xunsearch插件同时部署在一台服务器上。
宝塔与fastadmin的搭建就不再介绍了,这两个搭建很简单,网上都有教程。
fastadmin关于xunsearch文档的文档(你也可以根据此文档完成配置)
我们要将fastadmin插件市场中xunsearch插件安装进我们的框架
离线安装:讯搜官网
在线安装(Linux系统下):
curl -O http://www.xunsearch.com/download/xunsearch-full-latest.tar.bz2
tar -xvf xunsearch-full-latest.tar.bz2
安装 1.4.15版本
cd xunsearch-full-1.4.15
sh setup.sh
接下来就是等待安装完成
安装出现的问题:
在centos8中升级了openssl,导致xunseach内libevent版本对应不上导致报错
手动替换libevent文件
下载libevent文件2.1.12版本(我替换了这个版本解决了这个问题,其他版本尚未确认)
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
将下载的文件打包
tar -zxvf libevent-2.1.12-stable.tar.gz
复制一份到xunsearch的packages文件夹内(删除旧版本的libevent)
cp libevent-2.1.12-stable.tar.bz2 packages/
替换后
这样重新安装就不会出现版本不一致导致libevent出现问题
启动服务
当安装完成Xunsearch后,我们可以通过以下命令进行启动服务
cd xunsearch-full-1.4.15
cd bin
./xs-ctl.sh start
启动完成后就可以和我们的fastadmin进行交互了(使用fastadmin默认配置即可)
接下来我们进入fastadmin添加一个项目
字段配置
必须要添加(id/title/body)这三个类型,不创建将导致配置生成失败
接下来我们生成一下配置
这时我们的题库已经搭建完成了,我们点击前台即可进入搜索页面.
但是这个时候我们的题库并没有数据,别着急请接着完成下面数据库的搭建!
我们进入宝塔管理面板,去创建一个新的数据库
数据表创建与字段设置(请与上方fastadmin里xunsearch项目字段保持一致)
这时候请不要把题库导入该数据库
这时我们来到最后一步
用fastadmin中的在线命令管理(这个也是个插件,需要在插件市场安装后使用),生成该数据表的控制器
菜单创建
我们就可以看到菜单出现(如果没有多出一个菜单请刷新缓存)
我这个是修改过菜单名称的,初始名称并不是这样,不过这个并不重要
搭建到这个步骤可以看到使用这个菜单可以往数据库中加入数据,但是这样并不会往搜索引擎中加入索引数据
以下内容使用到fastadmin的xunsearch文档中关于api的调用
我们打开宝塔,进入网站根目录,用默认的宝塔文件管理打开
目录为:/www/wwwroot/shouti/application/admin/controller/sou/Tiku.php
如果你一键生成的控制器不和我相同,那么最后文件目录也会有差异,这一步需要根据自己配置来找
打开后修改内容
默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
我们可以在该文件中看到这个提示,根据提示,我们从[application/admin/library/traits/Backend.php]中找到添加、编辑、删除的代码
/** * 添加 */ public function add() { if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); if ($this->dataLimit && $this->dataLimitFieldAutoFill) { $params[$this->dataLimitField] = $this->auth->id; } $result = false; Db::startTrans(); try { //是否采用模型验证 if ($this->modelValidate) { $name = str_replace("\\model\\", "\\validate\\", get_class($this->model)); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate; $this->model->validateFailException(true)->validate($validate); } $result = $this->model->allowField(true)->save($params); Db::commit(); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were inserted')); } } $this->error(__('Parameter %s can not be empty', '')); } return $this->view->fetch(); } /** * 编辑 */ public function edit($ids = null) { $row = $this->model->get($ids); if (!$row) { $this->error(__('No Results were found')); } $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds)) { if (!in_array($row[$this->dataLimitField], $adminIds)) { $this->error(__('You have no permission')); } } if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); $result = false; Db::startTrans(); try { //是否采用模型验证 if ($this->modelValidate) { $name = str_replace("\\model\\", "\\validate\\", get_class($this->model)); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate; $row->validateFailException(true)->validate($validate); } $result = $row->allowField(true)->save($params); Db::commit(); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were updated')); } } $this->error(__('Parameter %s can not be empty', '')); } $this->view->assign("row", $row); return $this->view->fetch(); } /** * 删除 */ public function del($ids = "") { if (!$this->request->isPost()) { $this->error(__("Invalid parameters")); } $ids = $ids ? $ids : $this->request->post("ids"); if ($ids) { $pk = $this->model->getPk(); $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds)) { $this->model->where($this->dataLimitField, 'in', $adminIds); } $list = $this->model->where($pk, 'in', $ids)->select(); $count = 0; Db::startTrans(); try { foreach ($list as $k => $v) { $count += $v->delete(); } Db::commit(); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($count) { $this->success(); } else { $this->error(__('No rows were deleted')); } } $this->error(__('Parameter %s can not be empty', 'ids')); }
我们将这段代码复制进刚刚打开的文件中
同时将这些方法根据fastadmin提供的api进行修改,达到添加xunsearch数据库索引的效果
添加修改后代码
/** * 添加 */ public function add() { $search = \addons\xunsearch\library\Xunsearch::instance("souti"); if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); if ($this->dataLimit && $this->dataLimitFieldAutoFill) { $params[$this->dataLimitField] = $this->auth->id; } $result = false; Db::startTrans(); try { //是否采用模型验证 if ($this->modelValidate) { $name = str_replace("\\model\\", "\\validate\\", get_class($this->model)); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate; $this->model->validateFailException(true)->validate($validate); } $result = $this->model->allowField(true)->save($params); Db::commit(); $lastid = Db::table('sou_tiku')->where('id>0')->max('id'); $data = [ 'id'=>$lastid, 'title'=>"[ID".$lastid."]:".$params['title'], 'body'=>$params['body'], 'ans'=> $params['ans'] ]; $search->add($data); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were inserted')); } } $this->error(__('Parameter %s can not be empty', '')); } return $this->view->fetch(); }
添加部分解析
我首先获取到最后一位id(添加进索引数据库必须拥有一个自己的id,通过id才可以修改此条题目状态),这个id我放到题目上,如果用户投诉答案出错,可以向开发者提供此题目id,开发者可以根据id快速定位该题目
然后接下来就是根据fastadmin写好的,获取每一条数据,根据fastadmin提供的api导入进索引数据库
$lastid = Db::table('sou_tiku')->where('id>0')->max('id');
$data = [
'id'=>$lastid,
'title'=>"[ID".$lastid."]:".$params['title'],
'body'=>$params['body'],
'ans'=> $params['ans']
];
$search->add($data);
编辑修改后代码
/** * 编辑 */ public function edit($ids = null) { $search = \addons\xunsearch\library\Xunsearch::instance("souti"); $row = $this->model->get($ids); if (!$row) { $this->error(__('No Results were found')); } $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds)) { if (!in_array($row[$this->dataLimitField], $adminIds)) { $this->error(__('You have no permission')); } } if ($this->request->isPost()) { $params = $this->request->post("row/a"); if ($params) { $params = $this->preExcludeFields($params); $result = false; Db::startTrans(); try { //是否采用模型验证 if ($this->modelValidate) { $name = str_replace("\\model\\", "\\validate\\", get_class($this->model)); $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate; $row->validateFailException(true)->validate($validate); } $result = $row->allowField(true)->save($params); Db::commit(); $data = [ 'id'=>$ids, 'title'=>"[ID".$ids."]:".$params['title'], 'body'=>$params['body'], 'ans'=>$params['ans'] ]; $search->update($data); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($result !== false) { $this->success(); } else { $this->error(__('No rows were updated')); } } $this->error(__('Parameter %s can not be empty', '')); } $this->view->assign("row", $row); return $this->view->fetch(); }
编辑部分解析
编辑该数据的同时,我们将此次编辑也通过api进行修改到索引数据库
$data = [
'id'=>$ids,
'title'=>"[ID".$ids."]:".$params['title'],
'body'=>$params['body'],
'ans'=>$params['ans']
];
$search->update($data);
删除修改后代码
/** * 删除 */ public function del($ids = "") { $search = \addons\xunsearch\library\Xunsearch::instance("souti"); if (!$this->request->isPost()) { $this->error(__("Invalid parameters")); } $ids = $ids ? $ids : $this->request->post("ids"); if ($ids) { $pk = $this->model->getPk(); $adminIds = $this->getDataLimitAdminIds(); if (is_array($adminIds)) { $this->model->where($this->dataLimitField, 'in', $adminIds); } $list = $this->model->where($pk, 'in', $ids)->select(); $count = 0; Db::startTrans(); try { foreach ($list as $k => $v) { $count += $v->delete(); } Db::commit(); $search->del($ids); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } if ($count) { $this->success(); } else { $this->error(__('No rows were deleted')); } } $this->error(__('Parameter %s can not be empty', 'ids')); }
删除部分解析
这里是当数据表中数据删除的时候,我们也应该删除索引数据库里面的数据
$search->del($ids);
至此从fastadmin中添加/编辑/删除的操作,全部会同步到题库系统中.
重要提示:在fastadmin控制台只能一条一条的插入题库,因为导入的代码并未修改,所以导入进去的数据是无法查询的
这里id不一致是因为添加的时候id没有+1,上面代码未修改此问题,请自行修改[添加]部分代码.
因为一个一个导入真的是太慢了.
我这边解决方案就是搭建一个接口,通过接口上传题目
我这边创建了一个新的接口,如果是小白,可以把接口搭建在demo中
同时将接口鉴权关闭
加入题目函数
public function insertans() { $search = \addons\xunsearch\library\Xunsearch::instance("souti"); $title = $this->request->request('title'); $body = $this->request->request('body'); $ans = $this->request->request('ans'); $lastid = Db::table('sou_tiku')->where('id>0')->max('id'); $data = ['title' => $title, 'body' => $body , 'ans' => $ans]; Db::startTrans(); try { Db::table('sou_tiku')->insert($data); Db::commit(); $sdata = [ 'id'=>$lastid, 'title'=>"[ID".$lastid."]:".$title, 'body'=>$body, 'ans'=> $ans ]; $search->add($sdata); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } $this->success('返回成功',$res); //Db::table('sou_tiku')->insert($data); }
搜索题目返回函数
public function searchans()
{
$que = $this->request->request('q');
$search = \addons\xunsearch\library\Xunsearch::instance("souti");
$res = $search->search($que,1,10);
$this->success('返回成功',$res);
}
如果你实在不会写,请在该目录下创建sou.php
并加入以下代码
<?php namespace app\api\controller; use app\common\controller\Api; use think\Db; /** * 示例接口 */ class Sou extends Api { //如果$noNeedLogin为空表示所有接口都需要登录才能请求 //如果$noNeedRight为空表示所有接口都需要验证权限才能请求 //如果接口已经设置无需登录,那也就无需鉴权了 // // 无需登录的接口,*表示全部 protected $noNeedLogin = ['searchans','insertans']; // 无需鉴权的接口,*表示全部 protected $noNeedRight = []; public function insertans() { $search = \addons\xunsearch\library\Xunsearch::instance("souti"); $title = $this->request->request('title'); $body = $this->request->request('body'); $ans = $this->request->request('ans'); $lastid = Db::table('sou_tiku')->where('id>0')->max('id'); $data = ['title' => $title, 'body' => $body , 'ans' => $ans]; Db::startTrans(); try { Db::table('sou_tiku')->insert($data); Db::commit(); $sdata = [ 'id'=>$lastid, 'title'=>"[ID".$lastid."]:".$title, 'body'=>$body, 'ans'=> $ans ]; $search->add($sdata); } catch (ValidateException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (PDOException $e) { Db::rollback(); $this->error($e->getMessage()); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } $this->success('返回成功',$res); //Db::table('sou_tiku')->insert($data); } public function searchans() { $que = $this->request->request('q'); $search = \addons\xunsearch\library\Xunsearch::instance("souti"); $res = $search->search($que,1,10); $this->success('返回成功',$res); } }
具体代码含义,请自行查询thinkphp
调用网址:https://你的网站/api/sou/insertans?title=&body=&ans=
中文必须进行url编码,可以使用get或者post进行提交,title为题目,body为选项,ans为答案
具体字段可以自行修改
调用网址:http://你的网站/api/sou/searchans?q="zg"
返回的是一段json代码,可以根据需求自己解析使用
搭建比较繁琐,新手建议先看fastadmin官方给的教程,bilibili上也有的
小白请先看上面教程,不然会安装到你崩溃
看完前几集其实就差不多了,可以对比此教程看视频,学会控制器修改,api接口即可搭建
在thinkphp方面作者也是个小白,教程中一些地方写的并不是很标准,目的只是搭建能够查询的题库,更安全更标准的搭建方式,请自行探索
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。