前言
聚合搜索平台终于写完了。5月5日-今天。中间麦粒肿停了两三天。中午一点左右看第四期视频,一打开给我干蒙好家伙四个半小时,咬咬牙看到七点左右,好在现在实现了所有功能,完结撒花。
总结
系统架构
初始化项目
前端
- Vue+ts
- Ant Design Vue
- axios
eg:如何创建vue项目不再过多赘述,注意如果是对前端不熟悉建议不要选择代码校验**
Vue引入ant-design和axios
npm install ant-design-vue --save
import axios from 'axios';
引入组件
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import Antd from "ant-design-vue";
import "ant-design-vue/dist/reset.css";
createApp(App).use(Antd).use(router).mount("#app");
注意这里是reset.css,官方文档里的引入实例是错误的。
单独抽取axios
import axios from "axios"
const instance = axios.create({
baseURL: 'http://localhost:8101/api',
timeout: 10000,
headers: {},
});
//全局相应拦截器,取出响应的data
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
const data = response.data;
if(data.code == 0) {
return data.data;
}
console.error("request error",data);
return data.data;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export default instance;
后端
- springboot-init-master,开箱即食,香!、
- Elasticsearch
- Logstash
- JMeter压力测试
- 阿里开源的canul
数据接入
获取数据
- 实时抓取,直接调用url
- 解析html
处理数据
@Test
void testFetchPicture() throws IOException {
int current = 1;
String url = "https://cn.bing.com/images/search?q=%e5%b0%8f%e9%bb%91%e5%ad%90&qpvt=%e5%b0%8f%e9%bb%91%e5%ad%90&form=IGRE&first=" + current;
Document doc = Jsoup.connect(url).get();
Elements newsHeadlines = doc.select(".iuscp.isv");
List<Picture> pictures = new ArrayList<>();
for (Element element : newsHeadlines) {
//取图片地址(murl)
String m = element.select(".iusc").get(0).attr("m");
Map<String, Object> map = JSONUtil.toBean(m, Map.class);
String murl = String.valueOf(map.get("murl"));
// System.out.println(murl);
//取标题
String title = element.select(".inflnk").get(0).attr("aria-label");
// System.out.println(title);
Picture picture = new Picture();
picture.setTitle(title);
picture.setUrl(murl);
pictures.add(picture);
}
}
@Test
void testFetchPassage() {
// 获取数据
String json = "{\"current\":1,\"pageSize\":8,\"sortField\":\"createTime\",\"sortOrder\":\"descend\",\"category\":\"文章\",\"reviewStatus\":1}";
String result = HttpRequest.post("https://www.code-nav.cn/api/post/search/page/vo")
.body(json)
.execute().body();
System.out.println(result);
// json转对象
Map<String, Object> map = JSONUtil.toBean(result, Map.class);
JSONObject data = (JSONObject) map.get("data");
JSONArray records = (JSONArray) data.get("records");
List<Post> postList = new ArrayList<>();
System.out.println(records);
for (Object record : records) {
JSONObject tempRecord = (JSONObject) record;
Post post = new Post();
post.setTitle(tempRecord.getStr("title"));
post.setContent(tempRecord.getStr("content"));
JSONArray tags = (JSONArray) tempRecord.get("tags");
List<String> tagList = tags.toList(String.class);
post.setTags(JSONUtil.toJsonStr(tagList));
post.setUserId(1L);
postList.add(post);
}
//数据入库
boolean b = postService.saveBatch(postList);
Assertions.assertTrue(b);
System.out.println(postList);
}
// 以下是自己试着照猫画虎抓了一下同程旅游
@Test
void testFetchInform() throws IOException {
List<Picture> pictures = new ArrayList<>();
int current = 1;
String url = "https://www.ly.com/";
Document doc = Jsoup.connect(url).get();
System.out.println(doc);
Elements newsHeadlines = doc.select(".slpnel");
System.out.println(newsHeadlines);
for (Element element : newsHeadlines) {
String title = doc.select(".slpnel").get(0).attr("title");
String purl = doc.select(".slpnel").get(0).attr("pic_box");
Picture picture = new Picture();
picture.setTitle(title);
picture.setUrl(purl);
pictures.add(picture);
}
写入数据库
mybatispus批量插入
优化迭代
门面模式
我的理解,前端最少知道原则,只负责数据渲染,不做业务和逻辑上的处理
适配器模式
类似于转接口,例如轻薄本没有网线口,接一个转换口。
注册器模式
替代if-else
public SearchVO searchAll(@RequestBody SearchRequest searchRequest, HttpServletRequest request) {
/**
* 如果type为空则搜索所有数据
* 不为空
* 合法:查出对应数据
* 非法:抛出异常
*/
String type = searchRequest.getType();
SearchTypeEnum searchTypeEnum = SearchTypeEnum.getEnumByValue(type);
ThrowUtils.throwIf(StringUtils.isBlank(type), ErrorCode.PARAMS_ERROR);
String searchText = searchRequest.getSearchText();
long current = searchRequest.getCurrent();
long pageSize = searchRequest.getPageSize();
if (searchTypeEnum == null) {
CompletableFuture<Page<UserVO>> userTask = CompletableFuture.supplyAsync(() -> {
UserQueryRequest userQueryRequest = new UserQueryRequest();
userQueryRequest.setUserName(searchText);
Page<UserVO> userVOPage = userDataSource.doSearch(searchText,current,pageSize);
return userVOPage;
});
CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() -> {
PostQueryRequest postQueryRequest = new PostQueryRequest();
postQueryRequest.setSearchText(searchText);
Page<PostVO> postVOPage = postDataSource.doSearch(searchText,current,pageSize);
return postVOPage;
});
CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() -> {
Page<Picture> picturePage = pictureDataSource.doSearch(searchText, 1, 10);
return picturePage;
});
CompletableFuture.allOf(userTask, postTask, pictureTask);
Page<UserVO> userVOPage = null;
try {
userVOPage = userTask.get();
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
SearchVO searchVO = new SearchVO();
searchVO.setUserList(userVOPage.getRecords());
searchVO.setPostList(postVOPage.getRecords());
searchVO.setPictureList(picturePage.getRecords());
return searchVO;
} catch (InterruptedException e) {
log.error("线程中断", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "线程中断");
} catch (ExecutionException e) {
log.error("查询异常", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "查询异常");
}
}else {
SearchVO searchVO = new SearchVO();
DataSource<?> dataSource = dataSourceRegistry.getDataSourceByType(type);
Page<?> page = dataSource.doSearch(searchText,current,pageSize);
searchVO.setDataList(page.getRecords());
return searchVO;
}
/*------------------------此处再次优化,注册器模式---------------------------------*/
/* SearchVO searchVO = new SearchVO();
DataSource source = null;
switch (searchTypeEnum) {
case POST:
source = postDataSource;
break;
case USER:
source = userDataSource;
break;
case PICTURE:
source = postDataSource;
break;
case VIDEO:
break;
default:
}
Page page = source.doSearch(searchText,current,pageSize);
*/
/*-----------------------------此处优化为上面----------------------------*/
/* switch (searchTypeEnum) {
case POST:
PostQueryRequest postQueryRequest = new PostQueryRequest();
postQueryRequest.setSearchText(searchText);
Page<PostVO> postVOPage = postDataSource.doSearch(searchText,current,pageSize);
searchVO.setPostList(postVOPage.getRecords());
break;
case USER:
UserQueryRequest userQueryRequest = new UserQueryRequest();
userQueryRequest.setUserName(searchText);
Page<UserVO> userVOPage = userDataSource.doSearch(searchText,current,pageSize);
searchVO.setUserList(userVOPage.getRecords());
break;
case PICTURE:
Page<Picture> picturePage = pictureDataSource.doSearch(searchText, 1, 10);
searchVO.setPictureList(picturePage.getRecords());
break;
default:
}
}*/
}
ES接入
- 学习了ES基本的增删改查。
- Java客户端链接ES
- ES同步数据库,就目前而言我感觉用job更好理解一点
压力测试
到视频最后了注意力没那么集中,囫囵吞枣哈哈
心得体会
这个项目是我的第一个全栈项目,虽然前端页面只有几个组件,但那路由处理却不简单,期间我也碰到了很多问题,在前端耗费了很多时间,好在时间都是我暂停视频自己思考如何解决,然后继续,视频中也会解决。
后端学到了很多,以前写的都是能跑就行的原则,通过这次我有了迭代优化的意识,让自己的代码更优雅。尤其在将搜索帖子的PostDataSource的数据源从MySQL改为ES时,只需要改一个方法名,我深刻体会到之前做的优化多么高明!
在这之前,我喜欢看视频,但完结这个项目以后,我读文档的能力显著提升,我发现文档的内容更加精简,重点。目前正在跟着React官方文档入门。