最近尝试做了一个简易的搜索平台,下面一起回顾一下搭建过程。
技术栈:
springboot,MySQL5.1.73,mybatis
搭建思路分析
MySQL建表
对于问题进行分类,这里有faq_hotel,faq_bussiness几张表,结构都是相同的
CREATE TABLE `faq_others` (
26 `id` int(11) NOT NULL AUTO_INCREMENT,
27 `question` varchar(100) DEFAULT NULL,
28 `answer` varchar(6000) DEFAULT NULL,
29 `sign` varchar(10) DEFAULT NULL,
30 PRIMARY KEY (`id`)
31 ) ENGINE=MyISAM AUTO_INCREMENT=190 DEFAULT CHARSET=utf8;
具体实现
开启springboot,mybatis和druid
具体可查看我的另外一篇文章。这里不再重复。
实体类
根据数据库的表结构,我们创建出FAQ的实体类
import lombok.Data;
import lombok.ToString;
/*
用lombok插件实现了getter,setter,tostring,提高代码简洁性
*/
@Data
@ToString
public class FAQ {
private Integer id;
private String answer;
private String question;
private String sign;
}
我们再创建一个FAQ Collection的类来保存搜索记录,以便后台管理人员数据管理,也可以方便用户快捷搜索。
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class FAQCollection {
private Integer id;
private Integer searchcount;
private String question;
}
前端页面
既然是FAQ,用户可以自由输入问题进行查询,创建hello.html文件,这里我们给一个简单的搜索框即可,通过表单以POST的方式传给后台。
<div class="search-box">
<form action="/list" method="post">
<input name="question" class="form-control input-lg" type="text" placeholder="请输入问题"
autofocus="autofocus"
th:value="${inputtext}"/>
<button class="btn btn-lg" type="submit">
<i class="fa fa-search"></i>
</button>
</form>
</div>
简单的搜索框之外,我们还可以在首页显示热搜,即数据库里搜索靠前的关键词方便用户搜索。
<div class="row text-center">
<div class="col-12">
<div>热门关键字:
<span id="keyword">
<span th:each="kw:${keywords}">
<a th:href="'/list?question='+${kw}" th:text="${kw}"></a>
</span>
</span>
</div>
</div>
</div>
这里的keywords即是从后台获取的排名靠前的关键词,
Controller层
接下来就是实现步骤的核心过程了,希望你能深吸一口气。点击查询时,访问路径为/list,我们来看看在controller里list实现了什么。
@Controller
public class HelloController {
@Autowired
FAQService faqService;//稍后再说它,别急。
@RequestMapping("hello")
public String home(Model model) {
model.addAttribute("question",true);
List<String> keywords=getKeywords();
model.addAttribute("keywords",keywords);
return "hello";
}
@RequestMapping("/list")
public String SearchAll(Model model,
@RequestParam ("question") String question,
HttpServletRequest request)
{
model.addAttribute("question",true);
if(question==null){
return "hello";
}
model.addAttribute("inputtext",question);
List<String> keywords=getKeywords();
model.addAttribute("keywords",keywords);
List<FAQ> results=faqService.getResults(question);
model.addAttribute("results",results);
return "hello";
}
hello是平台的访问路径,第一个跳入我们眼帘的是model.addAttribute("question",true);
这个是方便后面的页面渲染的,你先记住它,稍后会有妙用。接下来就是他了List<String> keywords=getKeywords();
,他实现了获取热搜关键词的功能,现在我们需要跟数据库打交道了哦,老思路,调用service,再到Mapper跟数据库交流。为方便调用就把getkeywords()
的方法写在同一个controller下
public List<String> getKeywords(){
return faqService.getKeywords();
}
Service层:
public interface FAQService {
public List<String> getKeywords();
public List<FAQ> getResults(String question);
public FAQ getAnswer(String sign,String question_id);
public List<String> findAllTables();
}
Service实现类:
@Service
public class FAQServiceImpl implements FAQService {
@Autowired
FAQMapper faqMapper;
@Override
public List<String> getKeywords(){
List<FAQCollection> collections=faqMapper.getKeywords();
List<String> keywords=new ArrayList<>();
for(int i=0;i<collections.size();i++){
String question=collections.get(i).getQuestion();
keywords.add(question);
}
return keywords;
}
@Override
public List<FAQ> getResults(String question){
List<FAQ> list = new ArrayList<>();
for (String name : findAllTables()) {
List<FAQ> temp = faqMapper.searchAll(name, question);
// System.out.println(temp);
for (FAQ faq : temp) {
String key=faq.getQuestion().toLowerCase();
faq.setQuestion(key.replace(question.toLowerCase(), Contants.PRE+question+Contants.SUFFIX));
faq.setSign(name);
}//faq赋值后没有进行任何操作,这一步意义何在?关键词标红
list.addAll(temp);
}
return list;
}
@Override
public FAQ getAnswer(String sign,String question_id){
return faqMapper.findAnswerById(sign, question_id);
}
@Override
public List<String> findAllTables() {
return faqMapper.findAllTables();
}
}
Mapper层:
@Repository
@Mapper
public interface FAQMapper {
@Select("show tables like '%faq%'")
List<String> findAllTables();
@Insert("insert into ratetest(rate) values (#{rate})")
void insertRate(@Param("rate") String rate);
@Select("select *from ratetest order by id DESC limit 1")
@Results({
@Result(property = "rate",column = "rate"),
@Result(property = "time",column = "time")
})
List<Rate> getNewestRate();
@Select("select *from search_question_collection order by search_count DESC limit 5")
List<FAQCollection> getKeywords();
@Select("select id,question from ${tableName} where question like '%${question}%'")
List<FAQ> searchAll(@Param("tableName") String tableName, @Param("question") String question);
@Select("SELECT question,answer FROM ${name} WHERE id=${id}")
FAQ findAnswerById(@Param("name") String name, @Param("id") String id);
}
在拿到热搜词后,是时候在前端把他展现出来了。
<div class="row text-center">
<div class="col-12">
<div>热门关键字:
<span id="keyword">
<span th:each="kw:${keywords}">
<a th:href="'/list?question='+${kw}" th:text="${kw}"></a>
</span>
</span>
</div>
</div>
</div>
解决获取热搜词后,最关键的还是返回问题的答案。List<FAQ> results=faqService.getResults(question);
,来看看service做了什么操作。
@Override
public List<FAQ> getResults(String question){
List<FAQ> list = new ArrayList<>();
for (String name : findAllTables()) {
List<FAQ> temp = faqMapper.searchAll(name, question);
// System.out.println(temp);
for (FAQ faq : temp) {
String key=faq.getQuestion().toLowerCase();
faq.setQuestion(key.replace(question.toLowerCase(), Contants.PRE+question+Contants.SUFFIX));
faq.setSign(name);
}//faq赋值后没有进行任何操作,这一步意义何在?关键词标红
list.addAll(temp);
}
return list;
}
findAllTables()是什么?他是找到数据库中含有faq的表,这些表含有了问题的答案。在service实现类里实现该方法。
@Override
public List<String> findAllTables() {
return faqMapper.findAllTables();
}
Mapper层:
@Select("show tables like '%faq%'")
List<String> findAllTables();
在找到问题所在的几个表后,就可以带着用户输入的问题到一个个表里搜索相关数据了,也就是List<FAQ> temp = faqMapper.searchAll(name, question);
@Select("select id,question from ${tableName} where question like '%${question}%'")
List<FAQ> searchAll(@Param("tableName") String tableName, @Param("question") String question);
找到的是问题的id和问题名称,细心的你应该发现了这时答案还没有拿到哦。定位到一个具体的问题答案时,需要问题id,表名,这时将拿到的结果赋值问题类型也就是表名,最后将搜索结果添加到list里。
for (FAQ faq : temp) {
String key=faq.getQuestion().toLowerCase();
faq.setQuestion(key.replace(question.toLowerCase(), Contants.PRE+question+Contants.SUFFIX));//关键词标红
faq.setSign(name);
}//关键词标红和记录表名
list.addAll(temp);
至此,已经找到了相关的问题答案列表,将他们展现到前端。展现到前端时,像百度搜索结果一样都会标红问题关键词,来看看是怎么实现的。只需要一个Contants接口即可:
public interface Contants {
String PRE = "<b class='text-danger'>";
String SUFFIX = "</b>";
SimpleDateFormat SIMPLE_DATE_FORMAT=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
<ol id="js-result" th:if="${question}">
<li th:each="person:${results}">
<div class="search-result">
<a class="h2" th:href="'/list/'+${person.sign}+'/'+${person.id}"
th:utext="${person.question}"></a>
</div>
</li>
</ol>
还记得之前的model.addAttribute("question",true);
吗,因为结果页面和搜索页面全部综合在了一个HTML文件上,question是作为显示什么页面的一个关键标志,当question为true时渲染出的是用户搜索得出的答案列表,当为false时展现的是单一问题的具体答案。用户在拿到答案列表后,下一个行为便是进入单一问题答案了。也就是
<a class="h2" th:href="'/list/'+${person.sign}+'/'+${person.id}"
th:utext="${person.question}"></a>
点击链接后再到Controller层进行处理用户请求:
@RequestMapping("/list/{sign}/{id}")
public String getAnswer(Model model, @PathVariable String sign,@PathVariable String id){
model.addAttribute("question",false);
FAQ answer=faqService.getAnswer(sign,id);
model.addAttribute("aer",answer);
return "hello";
}
还是一样的思路,service到mapper
Service实现类
@Override
public FAQ getAnswer(String sign,String question_id){
return faqMapper.findAnswerById(sign, question_id);
}
Mapper:
@Select("SELECT question,answer FROM ${name} WHERE id=${id}")
FAQ findAnswerById(@Param("name") String name, @Param("id") String id);
拿到的答案放入model里,最后返回HTML页面,接下来就是惊动人心的时刻了,经过漫长的追寻之旅,答案终于展现了在我们眼前:
<div th:if="${question}==false">
<div class="text-primary">
<p class="h3" th:text="${aer.question}"></p>
</div>
<div class="h6 animated fadeInRight text-left" style="line-height:30px">
<p th:utext=" ${aer.answer}">
</p>
</div>
</div>
流程走完了,还得给网页加一个异常处理,谁知道用户会输入什么网址呢?Springboot2.0集成了比较好的异常处理类ErrorController,只要简单的调用即可。
@Controller
public class GrobalException implements ErrorController {
private static final String ERROR_PATH = "/error";
@RequestMapping(value = ERROR_PATH)
public String handleError() {
return "404";
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}
404页面:
<div class="middle-box text-center animated fadeInDown">
<h1>404</h1>
<p class="h2 font-bold">
页面未找到!
<a href="/hello">返回首页>></a>
</p>
</div>
咦,是不是感觉少了些什么,刚刚说的搜索行为记录呢,别急,在下一版本中,我们将记录用户的搜索行为和访问IP,顺利的话还会添加一个用户问题反馈功能,对答案的有用性进行评价。敬请期待!