稻草问答-实现搜索功能
稻草问答-实现搜索功能
1 实现问题搜索功能
1.1 问题搜索功能
在稻草问答的首页上搜索框,就是问题搜索功能其大致原理如下:
- 将全部的Question数据导入到ES服务器中;
- 查询时候根据用户输入到KEY请求ES查询数据;
- 将查询数据利用VUE显示到界面上;
- 为了能够搜索到新问题,在创建新问题时候,将新问题同时存储到ES服务器。
由于需要进行远程调用,则需要将straw-search注册到Eureka,首先导入eureka-client包和straw-commons包:
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>straw-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置启动类,添加@EnableEurekaClient:
@SpringBootApplication
@EnableEurekaClient
public class StrawSearchApplication {
public static void main(String[] args) {
SpringApplication.run(StrawSearchApplication.class, args);
}
}
配置application.properties:
service.port=8099
spring.application.name=search-service
eureka.instance.prefer-ip-address=false
eureka.instance.hostname=localhost
eureka.instance.ip-address=127.0.0.1
eureka.instance.instance-id=${spring.application.name}:${eureka.instance.hostname}:${server.port}
注册测试...
1.2 将数据库中的Question复制到Elasticsearch
根据上述学习的结果,我们要想完成搜索功能,首先需要在ES中创建问题索引,然后在才能进行问题的搜索。创建问题索引的步骤:
-
在straw-faq服务模块中添加Rest API方法,分页返回Question数据;
-
straw-search项目中创建值对象QuestionVo封装需要保存到ES中的数据,在这个文件中标注Spring-Data注解;
-
在straw-search项目中创建数据导入方法,将数据导入到Elasticsearch中。为了快速将Question数据导入到ES中,我们分页从straw-faq获取Question数据,然后分批的存储到ES中。
首先,straw-faq的业务层IQuestionService声明业务层接口方法;
/**
* 返回一页问题数据
* @param pageNum 页号:从1开始
* @param pageSize 页面行数
* @return 一页数据
*/
PageInfo<Question> getQuestions(Integer pageNum, Integer pageSize);
实现业务层接口方法:
@Override
public PageInfo<Question> getQuestions(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum,pageSize);
List<Question> list=questionMapper.selectList(null);
return new PageInfo<>(list);
}
编写测试案例进行测试:
@Test
void getQuestions(){
PageInfo<Question> pageInfo=questionService.getQuestions(1,10);
pageInfo.getList().forEach(question -> log.debug("{}",question));
log.debug("{}",pageInfo);
}
编写控制器方法:
/*
* Rest API用于将Question数据导到straw-search中
* 分页导出数据,这个方法用于返回数据页数
* @Param pageSuze 页面大小
*/
@GetMapping("/page/count")
public Integer pageCount(Integer pageSize){
Integer rows=questionService.count();
return rows%pageSize==0 ? rows/pageSize:rows/pageSize+1;
}
/*
* Rest API用于将Question数据导到straw-search中
* 分页导出数据,这个方法用于返回一页数据
* @param pageNum页号,从1开始的页号
* @Param pageSuze 页面大小
* @return 返回一页数据
*/
@GetMapping("/page")
public List<Question> page(Integer pageNum, Integer pageSize){
PageInfo<Question> pageInfo=
questionService.getQuestions(pageNum,pageSize);
return pageInfo.getList();
}
在straw-search服务模块添加值对象QuestionVo:
package cn.tedu.straw.search.vo;
import cn.tedu.straw.commons.model.Tag;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/**
* <p>
*用于封装存储到Elasticsearch中的问题数据
* </p>
*
* @author tedu.cn
* @since 2021-07-29
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Document(indexName = "questions")
public class QuestionVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 已经发布待解决
* 老师已经回复,正在解决中
* 已经接受答案,已经解决问题
*/
public static final Integer POSTED=0;
public static final Integer SOLVING=1;
public static final Integer SOLVED=2;
@Id
private Integer id;
/**
* 问题的标题
*/
@Field(type= FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
private String title;
/**
* 提问内容
*/
@Field(type= FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
private String content;
/**
* 提问者用户名
*/
@Field(type=FieldType.Keyword)
private String userNickName;
/**
* 提问者id
*/
@Field(type=FieldType.Integer)
private Integer userId;
/**
* 创建时间
* 时间类型必须指定格式format,否则不能保存
*/
@Field(type=FieldType.Date,format= DateFormat.basic_date_time)
private LocalDateTime createtime;
/**
* 修改时间
*/
@Field(type=FieldType.Date,format= DateFormat.basic_date_time)
private LocalDateTime modifytime;
/**
* 状态,0-》未回答,1-》待解决,2-》已解决
*/
@Field(type = FieldType.Integer)
private Integer status;
/**
* 浏览量
*/
@Field(type = FieldType.Integer)
private Integer pageViews;
/**
* 该问题是否公开,所有学生都可见,0-》否,1-》是
*/
@Field(type = FieldType.Integer)
private Integer publicStatus;
/**
* 删除状态, 0-》否,1-》是
*/
@Field(type = FieldType.Integer)
private Integer deleteStatus;
/**
* 标签名列表
*/
@Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
private String tagNames;
/**
* 当前问题的标签列表
* 不是数据库存储的数据,@TableField(exist=false)加以说明
* 不是数据库存储的数据,标注@Transient注解以后,将不保存到ES数据库中
*/
@Transient
private List<Tag> tags;
}
编写QuestionRepository接口:
@Repository
public interface QuestionRepository extends
ElasticsearchRepository<QuestionVo, Integer> {
}
添加业务层IQuestionService,定义同步方法
public interface IQuestionService {
/**
* 同步方法,将straw-faq服务模块中的信息读取出来
* 然后保存到当前straw-search中的Elasticsearch的数据库中
*/
void sync();
}
由于需要使用Ribbon调用straw-faq中的问题数据,所以需要声明RestTemplate对象:
@SpringBootApplication
@EnableEurekaClient
public class StrawSearchApplication {
public static void main(String[] args) {
SpringApplication.run(StrawSearchApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
实现业务层,实现同步方法,采用分页方法批量复制:
@Service
@Slf4j
public class QuestionServiceImpl implements IQuestionService {
@Resource
private QuestionRepository questionRepository;
@Resource
private RestTemplate restTemplate;
@Override
public void sync() {
//先获取总页数
String url="http://faq-service/v1/questions/page/count?pageSize={1}";
int pageSize=10;
Integer pages=restTemplate.getForObject(url, Integer.class, pageSize);
for (int i=1; i<=pages; i++){
//每次读取一页数据
url="http://faq-service/v1/questions/page?pageNum={1}&pageSize={2}";
QuestionVo[] questions=restTemplate.getForObject(
url, QuestionVo[].class, i, pageSize
);
//将读取到的一页数据存储到Elasticsearch中
questionRepository.saveAll(Arrays.asList(questions));
log.debug("保存 page {}", i);
}
}
}
测试,执行测试案例以后就已将数据复制到ES中了:
@SpringBootTest
@Slf4j
public class QuestionServiceTests {
@Resource
IQuestionService iQuestionService;
@Test
void sync(){
iQuestionService.sync();
}
}
1.3 编写搜索业务层方法
使用关键字进行搜索:
Elasticseatch搜索:
- must:必须的,就是并且关系
- should:应该的,就是或者关系.
- match:内容匹配
- term:完全相等
- bool:布尔,套在must、shoul外面
使用Rest客户端测试Query语句:
###条件搜索,查询用户12或者公开的 同时标题或者内容中包含Java的问题
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {
"bool": {
"must": [{
"bool": {
"should": [ {
"match": {"title": "java"
}},
{"match": {"content": "java"}}]
}
},{
"bool": {
"should": [{"term": {"publicStatus": 1}},{"term": {"userId": 12}}]
}
}]
}
}
}
编写搜索问题数据层方法,将上述查询query的值部分复制到@Query注解中,然后将参数替换为占位符?0,?1, ?2,参数占位符个数要与方法参数个数对应,执行查询时候Spring会自动将占位符替换为参数,搜索结果往往非常多,所以采用Spring进行分页查询:
package cn.tedu.straw.search.mapper;
import cn.tedu.straw.search.vo.QuestionVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface QuestionRepository extends
ElasticsearchRepository<QuestionVo, Integer> {
@Query(" {\n" +
"\"bool\": {\n" +
"\"must\": [\n" +
" {\n" +
" \"bool\": {\n" +
" \"should\": [\n" +
" {\n" +
" \"match\": {\n" +
" \"title\": \"?0\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"match\": {\n" +
" \"content\": \"?1\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
" },{\n" +
" \"bool\": {\n" +
" \"should\": [{\"term\": {\"publicStatus\": 1}},{\"term\": {\"userId\": ?2}}]\n" +
" }\n" +
"\n" +
"}]\n" +
"}\n" +
"}")
public Page<QuestionVo> queryAllByParams(String title, String content, Integer userId, Pageable pageable);
}
测试:
package cn.tedu.straw.search;
import cn.tedu.straw.search.mapper.QuestionRepository;
import cn.tedu.straw.search.vo.QuestionVo;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import javax.annotation.Resource;
@SpringBootTest
@Slf4j
public class QuestionRepositoryTests {
@Resource
private QuestionRepository questionRepository;
@Test
void queryAllByParams(){
Pageable pageable= PageRequest.of(0,3);
Page<QuestionVo> page=questionRepository.queryAllByParams(
"编程", "编程", 11, pageable
);
page.getContent().forEach(questionVo -> log.debug("{}",questionVo));
}
}
编写将Spring翻页转换为PageHelper翻页。PageHelper更适合与VUE配合显示界面,而Spring提供的翻页组件与VUE配合显示不是很方便,顾设计一个方法将Spring翻页组件转换为PageHelper翻页组件,这样就可以做到统一处理界面了。 如下是转换方法,这个方法可以作为工具类使用,由于只是转换子对应的数据规则,没有必要逐句掌握代码:
package cn.tedu.straw.search.service;
import com.github.pagehelper.PageInfo;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.List;
public class Pages {
/**
* 将Spring-Data提供的翻页数据,转换为Pagehelper翻页数据对象
* @param page Spring-Data提供的翻页数据
* @return PageInfo
*/
public static <T> PageInfo<T> pageInfo(Page<T> page){
//当前页号从1开始, Spring-Data从0开始,所以要加1
int pageNum = page.getNumber()+1;
//当前页面大小
int pageSize = page.getSize();
//总页数 pages
int pages = page.getTotalPages();
//当前页面中数据
List<T> list = new ArrayList<>(page.toList());
//当前页面实际数据大小,有可能能小于页面大小
int size = page.getNumberOfElements();
//当前页的第一行在数据库中的行号, 这里从0开始
int startRow = page.getNumber()*pageSize;
//当前页的最后一行在数据库中的行号, 这里从0开始
int endRow = page.getNumber()*pageSize+size-1;
//当前查询中的总行数
long total = page.getTotalElements();
PageInfo<T> pageInfo = new PageInfo<>(list);
pageInfo.setPageNum(pageNum);
pageInfo.setPageSize(pageSize);
pageInfo.setPages(pages);
pageInfo.setStartRow(startRow);
pageInfo.setEndRow(endRow);
pageInfo.setSize(size);
pageInfo.setTotal(total);
pageInfo.calcByNavigatePages(PageInfo.DEFAULT_NAVIGATE_PAGES);
return pageInfo;
}
}
在straw-search中添加依赖的组件,Pagehelper:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
添加业务层lQuestionSerice中声明查询方法:
PageInfo<QuestionVo> search(String key, String username, Integer pageNum,
Integer pageSize);
实现查询方法:
private User getUser(String username){
String url="http://sys-service/v1/auth/user?username={1}";
User user=restTemplate.getForObject(url, User.class, username);
return user;
}
@Override
public PageInfo<QuestionVo> search(String key, String username, Integer pageNum,
Integer pageSize) {
if (pageNum==null){
pageNum=1;
}
if(pageSize==null){
pageSize=8;
}
int page=pageNum-1;
int size=pageSize;
Pageable pageable= PageRequest.of(page,size, Sort.Direction.DESC,"createtime");
User user=getUser(username);
Page<QuestionVo> questions=questionRepository.queryAllByParams(key, key, user.getId(), pageable);
return Pages.pageInfo(questions);
}
测试:
@Test
void search(){
String username="st2";
String key="编程";
PageInfo<QuestionVo> pageInfo=iQuestionService.search(key, username, 1, 8);
pageInfo.getList().forEach(questionVo -> log.debug("{}",questionVo));
log.debug("{}",pageInfo);
}
1.4 实现首页搜索功能
搜索时候需要搜索当前用户的问题信息,而当前用户信息需要利用Session共享获得,所以首先配置Session共享,导入包:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置session共享application.properties:
spring.session.store-type=redis
spring.redis.host=localhost
spring.redis.port=6379
在StrawSearchApplication中启用Redis保存Session:
@SpringBootApplication
@EnableEurekaClient
@EnableRedisHttpSession
public class StrawSearchApplication {
public static void main(String[] args) {
SpringApplication.run(StrawSearchApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
配置SpringSecurity:
package cn.tedu.straw.search.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().permitAll();
}
}
迁移ExceptionControllerAdvice,统一处理控制器异常。编写控制器:
@RestController
@RequestMapping("/v1/questions")
@Slf4j
public class QuestionController {
@Resource
private IQuestionService questionService;
/**
* 请求路径:http://localhost:9000/search/v1/questions
*/
@PostMapping
public R<PageInfo<QuestionVo>> search(String key, Integer pageNum,
@AuthenticationPrincipal UserDetails userDetails){
if (key==null){
key="";
}
if (pageNum==null){
pageNum=1;
}
int pageSize=8;
PageInfo<QuestionVo> pageInfo=questionService.search(
key, userDetails.getUsername(), pageNum, pageSize);
return R.ok(pageInfo);
}
}
在straw-gateway中配置zuul路由规则:
zuul.routes.search.path=/search/**
zuul.routes.search.service-id=search-service
zuul.routes.search.sensitive-headers=Authorization
在straw-gateway项目中创建搜索界面,重构首页模板index.html,使用Thymeleaf定义搜索框片段:
<div class="form-inline my-2 my-lg-0" id="searchApp" th:fragment="searchApp" >
<input class="form-control form-control-sm mr-sm-2 rounded-pill" type="search" placeholder="Search" aria-label="Search" v-model="key" >
<button class="btn btn-sm btn-outline-secondary my-2 my-sm-0 rounded-pill" type="button" v-on:click="search" ><i class="fa fa-search" aria-hidden="true"></i></button>
</div>
编写vue视图模型search_app.js:
let searchApp=new Vue({
el:"#searchApp",
data:{
key:""
},
methods:{
search:function () {
//利用URL地址栏 传递搜索关键字,地址栏必须编码
location.href = "/search.html?key=" + encodeURI(this.key);
}
},
created:function () {
let key=location.search;
//检查地址栏上是否?key=
if(key && key.startsWith("?key=")){
//截取key=后面的信息
key =decodeURI(key.substring("?key=".length));
this.key=key;
}
}
});
测试......
复制index_teacher.html为搜索模板search.html,在HomeCotoller中添加转发方法,显示搜索页面:
@GetMapping("/search.html")
public String search(){
return "search";
}
重构search.html,使用thymeleaf复用index.html中的searchApp段落:
<div class="form-inline my-2 my-lg-0" id="searchApp" th:replace="index::searchApp">
<input class="form-control form-control-sm mr-sm-2 rounded-pill" v-model="keyword" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-sm btn-outline-secondary my-2 my-sm-0 rounded-pill" type="button"><i class="fa fa-search" aria-hidden="true"></i></button>
</div>
复制index_teacher.js为search.js重构:
let questionsApp = new Vue({
el:'#questionsApp',
data:{
questions:[],
pageInfo:{},
},
methods:{
loadQuestions:function(pageNum){
if(! pageNum){
pageNum =1;
}
let key=location.search;
if(! key){
return;
}
if (! key.startsWith("?key=")){
return;
}
key=decodeURI(key.substring("?key=".length));
$.ajax({
url: '/search/v1/questions',
method: "POST",
data:{
key:key,
pageNum:pageNum
},
success:function(r){
console.log("成功加载数据");
console.log(r);
if (r.code === OK){
questionsApp.questions=r.data.list;
questionsApp.pageInfo=r.data;
//为question对象添加持续时间属性
questionsApp.updateDuration();
questionsApp.updateTagImage();
}
}
});
},
updateTagImage:function (){
let questions=this.questions;
for(let i=0; i<questions.length; i++){
let tags=questions[i].tags;
if(tags){
let tagImage='/img/tags/'+tags[0].id+'.jpg';
console.log(tagImage);
questions[i].tagImage=tagImage;
}
}
},
updateDuration:function () {
let questions = this.questions;
for(let i=0; i<questions.length; i++){
//创建问题时候的时间毫秒数
addDuration(questions[i]);
}
}
},
created:function(){
console.log("执行了方法");
this.loadQuestions(1);
}
});
测试......
重构所有包含搜索框的页面..
上述案例中没有显示每个问题的配图,其原因是我们的配图是后期利用tag进行关联的,就和显示问题时候一样, 将显示问题与tag进行关联,显示时候就能显示对应的配图了,具体做法是在业务层中进行将问题的tags属性添加上。
在straw-faq项目TagController中添加Rest接口,返回全部tag信息:
/**
* 请求URL: /v1/tags/list
*/
@GetMapping("/list")
public List<Tag> list(){
return tagService.getTags();
}
重构straw-search中QuestionServicelmp的搜索方法,利用Ribbon获取标签列表,然后为搜索结果添加tags属性:
@Override
public PageInfo<QuestionVo> search(String key, String username, Integer pageNum,
Integer pageSize) {
if (pageNum==null){
pageNum=1;
}
if(pageSize==null){
pageSize=8;
}
int page=pageNum-1;
int size=pageSize;
Pageable pageable= PageRequest.of(page,size, Sort.Direction.DESC,"createtime");
User user=getUser(username);
Page<QuestionVo> questions=questionRepository.queryAllByParams(key, key, user.getId(), pageable);
Map<String,Tag> name2TagMap=getName2TagMap();
for (QuestionVo question : questions.getContent()){
List<Tag> tags=tagNameToTag(question.getTagNames());
question.setTags(tags);
}
return Pages.pageInfo(questions);
}
private final Map<String, Tag> name2TagMap =new ConcurrentHashMap<>();
private Map<String, Tag> getName2TagMap(){
if(name2TagMap.isEmpty()){
string url = "http://faq-service/v1/tags/list";
Tag[] tags = restTemplate.getForobject(url, Tag[].class);
for(Tag tag:tags){
name2TagMap.put(tag.getName(),tag);
}
}
return name2TagMap;
}
private List<Tag> tagNameToTag(String tagNames) {
Map<String, Tag> tagMap=getTagMap();
String[] names=tagNames.split(",\\s?");
List<Tag> tags=new ArrayList<>();
for (String name: names){
Tag tag=tagMap.get(name);
tags.add(tag);
}
return tags;
}
1.5 显示配图和标签列表
搜索功能实现了,但是不能显示配图,原因QuestionVO的tags属性是空的, 填上值就行了。首先利用Ribbon回去标签列表。
在straw-faq的TagCotoller控制器上添加获取tag列表功能:
/**
* 请求URL: /v1/tags/list
*/
@GetMapping("/list")
public List<Tag> list(){
return tagService.getTags();
}
然后在straw-search的业务层QuestionServicelmpl中添加本地缓存:
//缓存标签数据
private final ConcurrentHashMap<String, Tag> tagMap=new ConcurrentHashMap<>();
@Scheduled(initialDelay = 1000*60*60, fixedRate = 1000*6*60)
private void clearTagMap(){
tagMap.clear();
}
private Map<String, Tag> getTagMap() {
if(tagMap.isEmpty()) {
synchronized (tagMap) {
if (tagMap.isEmpty()) {
String url = "http://faq-service/v1/tags/list";
Tag[] tags = restTemplate.getForObject(url, Tag[].class);
for (Tag tag : tags) {
tagMap.put(tag.getName(), tag);
}
}
}
}
return tagMap;
}
在straw-search的启动类上开启定时机会功能@EnableScheduling:
@SpringBootApplication
@EnableEurekaClient
@EnableRedisHttpSession
@EnableScheduling
public class StrawSearchApplication {
public static void main(String[] args) {
SpringApplication.run(StrawSearchApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
重构straw-search的业务层QuestionServicelmpl搜索方法,将tags填充值:
@Override
public PageInfo<QuestionVo> search(String key, String username, Integer pageNum,
Integer pageSize) {
if (pageNum==null){
pageNum=1;
}
if(pageSize==null){
pageSize=8;
}
int page=pageNum-1;
int size=pageSize;
Pageable pageable= PageRequest.of(page,size, Sort.Direction.DESC,"createtime");
User user=getUser(username);
Page<QuestionVo> questions=questionRepository.queryAllByParams(key, key, user.getId(), pageable);
for (QuestionVo question : questions.getContent()){
List<Tag> tags=tagNameToTag(question.getTagNames());
question.setTags(tags);
}
return Pages.pageInfo(questions);
}
private List<Tag> tagNameToTag(String tagNames) {
Map<String, Tag> tagMap=getTagMap();
String[] names=tagNames.split(",\\s?");
List<Tag> tags=new ArrayList<>();
for (String name: names){
Tag tag=tagMap.get(name);
tags.add(tag);
}
return tags;
}
重新启动,进行测试。
一个字体图标无法显示的错误,有时候由于Maven在部署复制字体文件时候会出现问题,造成字体无法显示的现象,解决方案是在Maven的pom文件中添加plugin,告诉Maven在编译时候放过字体文件。
重构straw-gateway的pom文件:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>