帮助我们用户(客户端)去更轻松的实现功能,不需要关心门面背后的细节,当调用你接口的客户端觉得麻烦的时候,是不是就可以抽象成一个门面了?
后端
将controller层的业务代码抽象到manager/SearchFacade中
前端
IndexPage代码改造为加载单类数据
<template>
<div class="index-page">
<a-input-search
v-model:value="searchText"
placeholder="input search text"
enter-button
@search="onSearch"
/>
<MyDivider/>
<a-tabs v-model:activeKey="activeKey" @change="onTabChange">
<a-tab-pane key="post" tab="文章" > <PostList :post-list="postList"/></a-tab-pane>
<a-tab-pane key="picture" tab="图片" force-render><PictureList :picture-list="pictureList"/></a-tab-pane>
<a-tab-pane key="user" tab="用户">
<UserList :user-list="userList"/></a-tab-pane>
</a-tabs>
</div>
</template>
<script setup lang="ts">
import {ref, watchEffect} from 'vue';
import PostList from '@/components/PostList.vue';
import PictureList from '@/components/PictureList.vue';
import UserList from '@/components/UserList.vue';
import MyDivider from '@/components/MyDivider.vue';
import { useLink, useRoute, useRouter } from 'vue-router';
import myAxios from '@/plugins/myAxios';
import { message } from 'ant-design-vue';
//创建路由实例
const router = useRouter();
//获取路由所有信息
const route = useRoute();
const activeKey = route.params.category;
const initSearchParams = {
type: activeKey,
text:"",
pageSize: 10,
pageNum: 1,
};
const searchText = ref(route.query.text || "");
//把initSearchParams变为响应式对象
const searchParams = ref(initSearchParams);
const postList = ref([]);
const userList = ref([]);
const pictureList = ref([]);
//旧 :加载数据
const loadDataOld = (params: any) => {
const pictureQuery = {
...params,
searchText: params.text,
};
myAxios.post("/picture/search/page/vo",pictureQuery).then((res: any) => {
pictureList.value = res.records;
});
const postQuery = {
...params,
searchText: params.text,
};
myAxios.post("/post/list/page/vo", postQuery).then((res: any) => {
postList.value = res.records;
});
const userQuery = {
...params,
userName: params.text,
}
myAxios.post("/user/list/page/vo", userQuery).then((res: any) => {
userList.value = res.records;
});
};
//新 :加载聚合数据
const loadAllData = (params: any) => {
const query = {
...params,
searchText: params.text,
};
myAxios.post("/search/all",query).then((res: any) => {
userList.value = res.userList;
pictureList.value = res.pictureList;
postList.value = res.postList;
});
};
//更新 :加载单类数据
const loadData = (params: any) => {
const {type} = params;
if(!type){
message.error("类别为空");
return;
}
const query = {
...params,
searchText: params.text,
};
myAxios.post("/search/all",query).then((res: any) => {
if(type === "post"){
postList.value = res.postList;
}else if(type === "user"){
userList.value = res.userList;
}else if(type === "picture"){
pictureList.value = res.pictureList;
}
});
};
//监视路由变化
watchEffect(() =>{
searchParams.value = {
...initSearchParams,
text: route.query.text,
type: route.params.category,
} as any;
loadData(searchParams.value);
});
//用户每次搜索
loadData(initSearchParams);
const onSearch = (value: string) =>{
//把搜索框里面的内容压到路由中
router.push({
query:{
...searchParams.value,
text: value,
},
});
loadData(searchParams.value);
};
//每次导航栏切换
const onTabChange = (key: string) => {
router.push({
path: `/${key}`,
query: searchParams.value,
});
};
</script>
package com.yupi.springbootinit.dataSource;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.poi.ss.formula.functions.T;
/**
* 数据源接口
* (新接入的数据源必须实现)
*/
public interface DataSource<T> {
Page<T> doSearch(String searchText, long pageNum, long pageSize);
}
package com.yupi.springbootinit.dataSource;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.model.entity.Picture;
import com.yupi.springbootinit.service.PictureService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 帖子服务实现
*
*
*
*/
@Service
@Slf4j
public class PictureDataSource implements DataSource<Picture> {
@Override
public Page<Picture> doSearch(String searchText, long pageNum, long pageSize) {
long current = (pageNum - 1) * pageSize;
String url = String.format("https://cn.bing.com/images/search?q=%s&first=%s",searchText,current);
Document doc = null;
try {
doc = Jsoup.connect(url).get();
} catch (IOException e) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "数据抓取失败");
}
Elements elements = doc.select(".iuscp.isv"); //数组,每个元素是每一张图片
List<Picture> pictures = new ArrayList<>();
for (Element element : elements) {
//取图片地址murl
String m = element.select(".iusc").attr("m");
Map<String, Object> map = JSONUtil.toBean(m, Map.class);
String murl = (String) map.get("murl");
//取标题
String title = element.select(".inflnk").attr("aria-label");
Picture picture = new Picture();
picture.setTitle(title);
picture.setUrl(murl);
pictures.add(picture);
if (pictures.size() >= pageSize)
break;
}
Page<Picture> picturePage = new Page<>(pageNum,pageSize);
picturePage.setRecords(pictures);
return picturePage;
}
}
package com.yupi.springbootinit.dataSource;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.CommonConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.exception.ThrowUtils;
import com.yupi.springbootinit.mapper.PostFavourMapper;
import com.yupi.springbootinit.mapper.PostMapper;
import com.yupi.springbootinit.mapper.PostThumbMapper;
import com.yupi.springbootinit.model.dto.post.PostEsDTO;
import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.entity.PostFavour;
import com.yupi.springbootinit.model.entity.PostThumb;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.vo.PostVO;
import com.yupi.springbootinit.model.vo.UserVO;
import com.yupi.springbootinit.service.PostService;
import com.yupi.springbootinit.service.UserService;
import com.yupi.springbootinit.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
/**
* 帖子服务实现
*/
@Service
@Slf4j
public class PostDataSource extends ServiceImpl<PostMapper, Post> implements DataSource<PostVO> {
@Resource
private PostService postService;
@Override
public Page<PostVO> doSearch(String searchText, long pageNum, long pageSize) {
PostQueryRequest postQueryRequest = new PostQueryRequest();
postQueryRequest.setSearchText(searchText);
postQueryRequest.setCurrent(pageNum);
postQueryRequest.setPageSize(pageSize);
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
Page<PostVO> postVOPage = postService.listPostVOByPage(postQueryRequest, request);
return postVOPage;
}
}
package com.yupi.springbootinit.dataSource;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.CommonConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.UserMapper;
import com.yupi.springbootinit.model.dto.user.UserQueryRequest;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.enums.UserRoleEnum;
import com.yupi.springbootinit.model.vo.LoginUserVO;
import com.yupi.springbootinit.model.vo.UserVO;
import com.yupi.springbootinit.service.UserService;
import com.yupi.springbootinit.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import javax.annotation.RegEx;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static com.yupi.springbootinit.constant.UserConstant.USER_LOGIN_STATE;
/**
* 用户服务实现
*
*
*
*/
@Service
@Slf4j
public class UserDataSource extends ServiceImpl<UserMapper, User> implements DataSource {
@Resource
private UserService userService;
@Override
public Page doSearch(String searchText, long pageNum, long pageSize) {
UserQueryRequest userQueryRequest = new UserQueryRequest();
userQueryRequest.setUserName(searchText);
Page<User> userPage = this.page(new Page<>(pageNum, pageSize),
userService.getQueryWrapper(userQueryRequest));
Page<UserVO> userVOPage = new Page<>(pageNum, pageSize, userPage.getTotal());
List<UserVO> userVO = userService.getUserVO(userPage.getRecords());
userVOPage.setRecords(userVO);
return userVOPage;
}
}
@Component
@Slf4j
public class SearchFacade {
@Resource
private PictureDataSource pictureDataSource;
@Resource
private UserDataSource userDataSource;
@Resource
private PostDataSource postDataSource;
public SearchVO searchAll(@RequestBody SearchRequest searchRequest, HttpServletRequest request) {
String type = searchRequest.getType();
ThrowUtils.throwIf(StringUtils.isBlank(type), ErrorCode.PARAMS_ERROR);
SearchTypeEnum searchTypeEnum = SearchTypeEnum.getEnumByValue(type);
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).join();
try {
Page<UserVO> userVOPage = userTask.get();
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
SearchVO searchVO = new SearchVO();
searchVO.setUserList(userVOPage.getRecords());
searchVO.setPictureList(picturePage.getRecords());
searchVO.setPostList(postVOPage.getRecords());
return searchVO;
}catch (Exception e){
log.error("查询异常",e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"查询异常");
}
}else{
Map<String, DataSource> typeDataSourceMap = new HashMap(){{
put(SearchTypeEnum.POST.getValue(),postDataSource);
put(SearchTypeEnum.USER.getValue(),userDataSource);
put(SearchTypeEnum.PICTURE.getValue(),pictureDataSource);
}};
SearchVO searchVO = new SearchVO();
DataSource dataSource = typeDataSourceMap.get(type);
Page page = dataSource.doSearch(searchText, current, pageSize);
searchVO.setDataList(page.getRecords());
return searchVO;
}
}
}
(本质也是单例)
提前用一个map或者其他类型存储好后面需要调用的对象。
作用:代码量大幅度减少,可维护可扩展
@Component
public class DataSourceRegistry {
@Resource
private PictureDataSource pictureDataSource;
@Resource
private UserDataSource userDataSource;
@Resource
private PostDataSource postDataSource;
private Map<String, DataSource> typeDataSourceMap;
/**
* @PostConstruct标识的方法会在Spring容器完成依赖注入后立即执行。
* 没有这个初始化方法的话,依赖注入以后还没有执行typeDataSourceMap的赋值操作,就调用了getDataSourceByType方法,肯定是返回空的
*/
@PostConstruct
public void init() {
typeDataSourceMap = new HashMap() {
{
put(SearchTypeEnum.POST.getValue(), postDataSource);
put(SearchTypeEnum.USER.getValue(), userDataSource);
put(SearchTypeEnum.PICTURE.getValue(), pictureDataSource);
}
};
}
public DataSource getDataSourceByType(String type) {
if(typeDataSourceMap == null){
return null;
}
return typeDataSourceMap.get(type);
}
}
@Component
@Slf4j
public class SearchFacade {
@Resource
private PictureDataSource pictureDataSource;
@Resource
private UserDataSource userDataSource;
@Resource
private PostDataSource postDataSource;
@Resource
private DataSourceRegistry dataSourceRegistry;
public SearchVO searchAll(@RequestBody SearchRequest searchRequest, HttpServletRequest request) {
String type = searchRequest.getType();
ThrowUtils.throwIf(StringUtils.isBlank(type), ErrorCode.PARAMS_ERROR);
SearchTypeEnum searchTypeEnum = SearchTypeEnum.getEnumByValue(type);
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).join();
try {
Page<UserVO> userVOPage = userTask.get();
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
SearchVO searchVO = new SearchVO();
searchVO.setUserList(userVOPage.getRecords());
searchVO.setPictureList(picturePage.getRecords());
searchVO.setPostList(postVOPage.getRecords());
return searchVO;
}catch (Exception 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;
}
}
}