MemorySearch 开发者文档 MemorySearch 开发者文档
首页
    • 概述
    • 系统设计
    • 维护升级
    • 高效多元搜索
    • 互动创作平台
    • 流量统计分析
    • 个人中心管理
    • 资源全面管理
    • 概览
    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
    • 初始模板定制
    • 全局逻辑梳理
  • 简介
  • 常见问题与解答
首页
    • 概述
    • 系统设计
    • 维护升级
    • 高效多元搜索
    • 互动创作平台
    • 流量统计分析
    • 个人中心管理
    • 资源全面管理
    • 概览
    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
    • 初始模板定制
    • 全局逻辑梳理
  • 简介
  • 常见问题与解答
  • 内容概览

    • 概览
  • 前端

    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
  • 后端

    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
      • 需求场景
      • 解决方案
        • 定义统一接口
        • 聚合搜索结果
        • 数据源分离
        • 搜索请求转发
        • 提供客户端搜索服务
        • 🥗 适配器模式
      • 需求场景
        • 接口不一致
        • 问题分析
      • 解决方案
        • 设计目标接口
        • 确定适配者
        • 实现目标接口
        • 实现统一调用
        • 🍜 注册器模式
      • 需求场景
      • 解决方案
        • 搜索类型枚举
        • 数据源注入
        • 支持获取数据源
        • 全新聚合搜索结果
        • 全新客户端搜索服务
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
    • 初始模板定制
    • 全局逻辑梳理
目录

设计模式荟萃

学习目标

在这里,你将系统学习了解设计模式荟萃的相关内容

我们将以最简单直接的方式为您呈现内容!

# 🍚 门面模式

我们选择门面模式的原因主要有以下几点:

  • 简化了客户端与系统之间的交互,客户端只需要与门面交互,而不需要了解系统内部的复杂性。

  • 降低了系统的耦合度,使系统更容易维护和修改。

  • 提高了代码的可读性和可维护性,因为客户端代码更简洁清晰。

门面模式允许客户端通过一个统一的接口与系统交互,而不需要了解系统内部的复杂性,降低客户端与系统之间的耦合度,使系统更易于维护、扩展和修改。

# 需求场景

  • 通过适配器模式,我们有效地解耦了数据源的具体实现细节,只需关注统一的接口,即可实现对不同数据源的搜索请求的统一处理。
/**
 * @author 邓哈哈
 * 2023/8/31 16:35
 * Function:
 * Version 1.0
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Resource
    private PostDataSource postDataSource;

    @Resource
    private PictureDataSource pictureDataSource;

    @Resource
    private ArticleDataSource articleDataSource;


    @Override
    public List<String> searchSuggestFromEs(String suggestText) {
        postDataSource.search(searchText, pageSize, current));

        articleDataSource.search(searchText, pageSize, current));

        pictureDataSource.search(searchText, pageSize, current));

        .............................
    }
}
  • 为了满足用户对聚合搜索的需求,我们希望提供一个统一的接口,该接口能够同时调用诗词搜索、博文搜索和图片搜索功能。为了实现这一目标,我们决定采用门面模式进行封装。

# 解决方案

# 定义统一接口

  • 在SearchService中,定义聚合搜索统一接口searchAll,对外提供聚合搜索服务:
/**
* 聚合搜索服务
*
* @param searchQueryRequest 聚合搜索词条
* @param request            request
* @return 搜索结果
*/
SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request);

# 聚合搜索结果

-我们设计SearchVO类,主要职责是存储聚合搜索的结果,并将结果返回给前端

@Data
public class SearchVO implements Serializable {
    /**
     * 博文搜索列表
     */
    private List<ArticleVO> articleList;

    /**
     * 诗词搜索列表
     */
    private List<PostVO> postVOList;

    /**
     * 图片搜索列表
     */
    private List<Picture> pictureList;


    private static final long serialVersionUID = 1L;
}

# 数据源分离

  • 通过适配器模式,我们已经将不同的数据源:Post、Article和Picture独立出来,每个数据源都有自己的搜索实现。详见:适配器模式

# 搜索请求转发

  • 在 searchSuggestFromEs 方法中,根据传入的 suggestText,搜索请求被分发到不同的数据源进行搜索,这里,postDataSource.search(), articleDataSource.search(), 和 pictureDataSource.search() 方法分别处理来自不同数据源的搜索请求。
 @Override
    public SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request) {
        // 校验参数正确性
        ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);

        // 获取搜索词条
        String searchText = searchQueryRequest.getSearchText();
        long pageSize = searchQueryRequest.getPageSize();
        long current = searchQueryRequest.getPageNum();

        // 诗词搜索
        CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() ->
                postDataSource.search(searchText, pageSize, current));

        // 博文搜索
        CompletableFuture<Page<ArticleVO>> articleTask = CompletableFuture.supplyAsync(() ->
                articleDataSource.search(searchText, pageSize, current));

        // 图片搜索
        CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() ->
                pictureDataSource.search(searchText, pageSize, current));

        // 组合异步任务结果
        CompletableFuture.allOf(postTask, pictureTask, articleTask).join();

        // 整合不同数据源的搜索结果
        try {
            Page<PostVO> postVOPage = postTask.get();
            Page<Picture> picturePage = pictureTask.get();
            Page<ArticleVO> articlePage = articleTask.get();

            searchVO = new SearchVO();
            searchVO.setPostVOList(postVOPage.getRecords());
            searchVO.setPictureList(picturePage.getRecords());
            searchVO.setArticleList(articlePage.getRecords());

        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        // 返回聚合搜索结果
        return searchVO;
    }

# 提供客户端搜索服务

  • 设计 SearchController,接收客户端发来的搜索请求参数,调用SearchService的searchAll方法处理搜索请求,将返回的SearchVO对象封装为成功的响应结果并返回给客户端
@RestController
@RequestMapping("/search")
@Slf4j
public class SearchController {
    @Resource
    private SearchService searchService;

    @PostMapping("/all")
    public BaseResponse<SearchVO> searchAll(@RequestBody SearchQueryRequest searchQueryRequest,
                                            HttpServletRequest request) throws IOException, ExecutionException, InterruptedException {
        // controller层对参数的校验

        SearchVO searchVO = searchService.searchAll(searchQueryRequest, request);
        return ResultUtils.success(searchVO);
    }
}
  • 至此,我们成功运用门面模式构建了统一接口,该接口能够整合诗词搜索、博文搜索以及图片搜索等多种功能。

  • 这一设计确保了客户端能够通过单一的接口调用,便捷地获取所需的信息,极大地提升了用户体验和系统效率。

# 🥗 适配器模式

我们选择适配器模式的原因主要有以下几点:

  • 解耦:适配器模式有助于解耦不兼容的接口,使得不同的系统或组件之间可以更灵活地协同工作,减少因接口不匹配而导致的问题。

  • 提高复用性:通过适配器模式,我们可以将一些不兼容的类组合成新的功能,从而提高代码的复用性。

  • 扩展性:适配器模式使得系统更加模块化,便于后续的扩展和维护。当有新的接口或组件需要加入时,只需适配新的接口,而无需修改现有代码。

  • 灵活性:通过适配器模式,我们可以轻松地在运行时切换不同的适配器实现,以适应不同的场景和需求。

beta Vdoing主题

# 需求场景

在具体实施前,我们需要深入分析当前的业务逻辑和设计痛点,明确需要适配的范围和目标,精确应用适配器模式。

# 接口不一致

  • 我们对外分别提供诗词搜索、博文搜索和图片搜索功能,而这三者的实现的请求接口是不一致的

  • 在PostService下,我们编写了searchFromEs方法进行诗词数据访问,实现了诗词搜索功能:

/**
* 从 ES 查询
*
* @param postQueryRequest 诗词搜索请求参数
* @return 诗词列表
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
  • 在ArticleSercive下,我们编写了searchFromEs方法进行博文数据访问,实现了博文搜索功能:
/**
* 从 ES 查询
*
* @param articleQueryRequest 博文查询请求参数
* @return 博文列表
*/
Page<ArticleVO> searchFromEs(ArticleQueryRequest articleQueryRequest);
  • 在PictureService下,我们编写了searchFromEs方法进行诗词数据访问,实现了图片搜索功能:
/**
* 图片搜索
*
* @param searchText  搜索关键词
* @param pageSize    每页容量
* @param currentPage 当前页码
* @return 图片列表
*/
Page<Picture> listPictureVOByPage(String searchText, long pageSize, long currentPage);

# 问题分析

  • 显然,目前存在的方法在实现请求参数上存在不一致性,这无疑增加了维护的复杂性。

  • 为了提高代码的可维护性和可扩展性,我们需要在不修改现有方法的基础上,实现这三个接口调用的统一化。

  • 因此,考虑使用适配器模式来优化当前的实现方式,以解决这种不一致性问题。

# 解决方案

# 设计目标接口

/**
 * 适配器 目标值
 */
public interface DataSource<T> {
/**
 * 统一查询
 *
 * @param searText 搜索词条
 * @param pageSize 每页数
 * @param current  当前页
 * @return Page
 */
 Page<T> search(String searText, long pageSize, long current) throws IOException;
}

# 确定适配者

  • 为了提升系统的兼容性和可扩展性,我们将针对PostService、ArticleService和PictureService这三个服务进行适配实现。

# 实现目标接口

  • 新增PostDataSource类,该类将完全实现DataSource接口,以提供诗词搜索的适配实现。
/**
 * 诗词搜索 适配实现
 */
@Service
public class PostDataSource implements DataSource<PostVO> {
    @Resource
    private PostService postService;

    @Override
    public Page<PostVO> search(String searText, long pageSize, long current) {
        PostQueryRequest postQueryRequest = new PostQueryRequest();

        postQueryRequest.setSearchText(searText);
        postQueryRequest.setPageSize(pageSize);
        postQueryRequest.setPageNum(current);

        Page<Post> postPage = postService.searchFromEs(postQueryRequest);
        return postService.getPostVOPage(postPage, null);
    }
}
  • 新增ArticleDataSource类,该类将完全实现DataSource接口,以提供博文搜索的适配实现。
/**
 * 博文搜索 适配实现
 */
@Service
public class ArticleDataSource implements DataSource<ArticleVO> {
    @Resource
    private ArticleService articleService;

    @Override
    public Page<ArticleVO> search(String searText, long pageSize, long current) {
        ArticleQueryRequest articleQueryRequest = new ArticleQueryRequest();

        articleQueryRequest.setSearchText(searText);
        articleQueryRequest.setPageSize(pageSize);
        articleQueryRequest.setPageNum(current);

        return articleService.searchFromEs(articleQueryRequest);
    }
}
  • 新增PictureDataSource类,该类将完全实现DataSource接口,以提供图片搜索的适配实现。
/**
 * 图片搜索 适配实现
 */
@Service
public class PictureDataSource implements DataSource<Picture> {
    @Resource
    private PictureService pictureService;

    @Override
    public Page<Picture> search(String searText, long pageSize, long current) {
        return pictureService.listPictureVOByPage(searText, pageSize, current);
    }
}

# 实现统一调用

  • 通过适配器模式,我们无需关心具体的数据源实现细节,只需调用统一的接口即可。至此,我们已经能够统一处理来自不同数据源的搜索请求了:
/**
 * @author 邓哈哈
 * 2023/8/31 16:35
 * Function:
 * Version 1.0
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Resource
    private PostDataSource postDataSource;

    @Resource
    private PictureDataSource pictureDataSource;

    @Resource
    private ArticleDataSource articleDataSource;


    @Override
    public List<String> searchSuggestFromEs(String suggestText) {
        postDataSource.search(searchText, pageSize, current));

        articleDataSource.search(searchText, pageSize, current));

        pictureDataSource.search(searchText, pageSize, current));

        .............................
    }
}
  • 通过专业的适配工作,我们能够确保这些服务与其他使用相同接口的系统或组件无缝对接,构建了一个更加健壮和灵活的系统架构。

# 🍜 注册器模式

我们选择注册器模式的原因主要有以下几点:

  • 动态管理:注册器模式允许在运行时动态地添加、删除和查找组件,而无需修改现有代码或重启应用程序。

  • 灵活性:通过使用注册器模式,我们可以轻松地添加或删除组件,从而轻松地扩展应用程序的功能。

  • 集中管理:注册器模式提供了一个集中的地方来管理组件,使得查找、配置和使用组件变得更加简单。

  • 简化组件交互:注册器模式简化了组件之间的交互,使得组件之间的通信更加清晰和一致。

  • 易于测试和维护:由于组件之间的耦合度较低,这使得组件更容易测试和维护。

通过使用注册器模式,应用程序可以在运行时动态地添加或删除组件,而无需修改现有代码。此外,由于组件之间的耦合度较低,这使得组件更容易测试和维护。

# 需求场景

  • 我们已成功地运用门面模式构建了一个统一的接口, 整合了诗词搜索、博文搜索以及图片搜索,这一设计确保了客户端能够通过单一的接口调用,便捷地获取所需的信息,极大地提升了用户体验和系统效率。
@RestController
@RequestMapping("/search")
@Slf4j
public class SearchController {
    @Resource
    private SearchService searchService;

    @PostMapping("/all")
    public BaseResponse<SearchVO> searchAll(@RequestBody SearchQueryRequest searchQueryRequest,
                                            HttpServletRequest request) throws IOException, ExecutionException, InterruptedException {
        // controller层对参数的校验

        SearchVO searchVO = searchService.searchAll(searchQueryRequest, request);
        return ResultUtils.success(searchVO);
    }
}
  • 然而,随着业务需求的变化,用户调用聚合搜索接口的参数也发生了相应的调整。为了更好地满足用户需求,我们根据前端发来的搜索关键词,动态地调用相应的搜索接口,确保准确性和高效性。

# 解决方案

  • 在新需求中,我们支持客户端通过传输搜索类型字段,精确选择执行特定类型的聚合搜索。

  • 为了实现这一目标,我们选择使用注册器模式,在服务端,我们通过分析客户端传输的搜索类型,动态切换到指定类型的聚合搜索服务,从而实现精确的聚合搜索。

# 搜索类型枚举

  • 新增搜索枚举类型SearchTypeEnum,实现搜索类型枚举(构造器和 getter、setter 方法省略)
/**
 * 搜索类型枚举
 */
public enum SearchTypeEnum {

    POST("诗词", "post"),

    ARTICLE("博文", "article"),

    PICTURE("图片", "picture");

    private final String text;

    private final String value;

    /**
     * 根据 value 获取枚举
     *
     * @param value
     * @return
     */
    public static SearchTypeEnum getEnumByValue(String value) {
        if (ObjectUtils.isEmpty(value)) {
            return null;
        }
        for (SearchTypeEnum anEnum : SearchTypeEnum.values()) {
            if (anEnum.value.equals(value)) {
                return anEnum;
            }
        }
        return null;
    }
}

# 数据源注入

  • 创建注册器类DataSourceRegistry,用于管理注册的对象,支持根据给定的搜索类型来获取相应的数据源
/**
 * 数据源注册器
 */
@Component
public class DataSourceRegistry {
    // 诗词数据源
    @Resource
    private PostDataSource postDataSource;
    // 图片数据源
    @Resource
    private PictureDataSource pictureDataSource;
    // 博文数据源
    @Resource
    private ArticleDataSource articleDataSource;
    // 关联搜索类型和数据源
    private Map<String, DataSource<T>> typeDataSourceMap;

    @PostConstruct
    public void doInit() {
        System.out.println(1);
        typeDataSourceMap = new HashMap() {{
            put(SearchTypeEnum.POST.getValue(), postDataSource);
            put(SearchTypeEnum.PICTURE.getValue(), pictureDataSource);
            put(SearchTypeEnum.ARTICLE.getValue(), articleDataSource);
        }};
    }

    ..............................
}

# 支持获取数据源

  • 接收搜索类型参数,通过注册器模式,在注册器中选择合适的数据源,执行精确的搜索操作
/**
* 获取搜索接口
*
* @param type 搜索类型
* @return 对应数据源
*/
public DataSource getDataSourceByType(String type) {
    if (typeDataSourceMap == null) {
        return null;
    }

    return typeDataSourceMap.get(type);
}

# 全新聚合搜索结果

  • 我们更新SearchVO类,主要职责是存储聚合搜索的结果,并将结果返回给前端
@Data
public class SearchVO implements Serializable {
    /**
     * 博文搜索列表
     */
    private List<ArticleVO> articleList;

    /**
     * 诗词搜索列表
     */
    private List<PostVO> postVOList;

    /**
     * 图片搜索列表
     */
    private List<Picture> pictureList;

    /**
     * 聚合搜索结果列表
     */
    private List<?> dataList;

    private static final long serialVersionUID = 1L;
}

# 全新客户端搜索服务

  • 在 SearchController中,接收客户端发来的搜索请求参数,分析客户端传输的搜索类型,动态切换到指定类型的聚合搜索服务,实现精确的聚合搜索。
/**
* 聚合搜索服务
*
* @param searchQueryRequest 聚合搜索词条
* @param request            request
* @return 搜索结果
*/
@Override
public SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request) {
    // 检查type类型
    String type = searchQueryRequest.getType();
    // 校验参数正确性
    ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);
    // 获取页面类型
    SearchTypeEnum enumByValue = SearchTypeEnum.getEnumByValue(type);

    String searchText = searchQueryRequest.getSearchText();
    long pageSize = searchQueryRequest.getPageSize();
    long current = searchQueryRequest.getPageNum();

    SearchVO searchVO = null;

    // 执行查询全部数据
    if (enumByValue == null) {
        // 诗词搜索
        CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() ->
                postDataSource.search(searchText, pageSize, current));

        // 博文搜索
        CompletableFuture<Page<ArticleVO>> articleTask = CompletableFuture.supplyAsync(() ->
                articleDataSource.search(searchText, pageSize, current));

        // 图片搜索
        CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() ->
                pictureDataSource.search(searchText, pageSize, current));


        CompletableFuture.allOf(postTask, pictureTask, articleTask).join();

        try {
            Page<PostVO> postVOPage = postTask.get();
            Page<Picture> picturePage = pictureTask.get();
            Page<ArticleVO> articlePage = articleTask.get();

            searchVO = new SearchVO();
            searchVO.setPostVOList(postVOPage.getRecords());
            searchVO.setPictureList(picturePage.getRecords());
            searchVO.setArticleList(articlePage.getRecords());

        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
         // 执行指定搜索接口
    } else {
        // 分类查询
        searchVO = new SearchVO();
        // 获取数据源
        DataSource<?> dataSourceByType = dataSourceRegistry.getDataSourceByType(type);
        try {
            Page<?> page = dataSourceByType.search(searchText, pageSize, current);
            searchVO.setDataList(page.getRecords());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 返回聚合搜索结果
    return searchVO;
}
  • 通过运用注册器模式,我们成功地实现了客户端能够通过传输搜索类型字段,精确选择并执行``特定类型的聚合搜索。

  • 这一改进使我们的系统展现出更高的灵活性和可扩展性,从而更好地适应不断变化的业务需求。

Elastic Stack 全家桶
外源数据抓取

← Elastic Stack 全家桶 外源数据抓取→

Theme by Vdoing | Copyright © 2023-2024 回忆如初
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式