React+TS前台项目实战(二十一)-- Search业务组件封装实现全局搜索
文章目录
- 前言
- 一、Search组件封装
- 1. 效果展示
- 2. 功能分析
- 3. 代码+详细注释
- 4. 使用方式
- 二、搜索结果展示组件封装
- 1. 功能分析
- 2. 代码+详细注释
- 三、引用到文件,自行取用
- 总结
前言
今天,我们来封装一个业务灵巧的组件,它集成了全局搜索和展示搜索结果的功能。通过配置文件,我们可以为不同的模块定制展示和跳转逻辑,集中管理不同模块,当要加一个模块时,只需要通过配置即可,从而减少重复的代码,并方便地进行维护和扩展。同时,我们将使用React Query来实现搜索功能,并模拟请求成功、请求失败和中断请求的处理方式。
一、Search组件封装
1. 效果展示
(1)输入内容,当停止输入后,请求接口数据
注:如请求数据时添加加载状态,请求结束后取消加载状态

(2)点击清除按钮,清除输入框数据,并中止当前请求,重置react-query请求参数

(3)请求失败,展示失败界面

(4)是否显示搜索按钮

(5)移动端效果

2. 功能分析
(1)搜索功能灵活性: 使用防抖搜索,useMemo,以及react-query自带监听输入状态,只在输入框停止输入后,才会触发接口请求,避免在用户仍在输入时进行不必要的API调用
(2)请求库选择: 使用Tanstack React Query中的useQuery钩子来管理加载状态并获取搜索结果
(3)导航到搜索结果: 点击搜索结果项或在搜索结果显示后按下回车键时,会跳转到对应的页面
(4)清除搜索: 点击清空按钮,会清空输入框的内容,并取消接口请求,重置请求参数,隐藏搜索结果列表
(5)搜索结果展示: 一旦获取到搜索结果,该组件使用SearchResults组件渲染搜索结果。它还显示搜索结果的加载状态
(6)搜索按钮: 如果hasButton属性为true,还将渲染一个搜索按钮,当点击时触发搜索
(7)使用国际化语言,可全局切换使用;使用联合类型声明使用,不同模块,添加配置即可
(8)使用useCallback,useMemo,useEffect, memo,lodash.debounce等对组件进行性能优化
(9)提供一些回调事件,供外部调用
3. 代码+详细注释
引入之前文章封装的 输入框组件,可自行查看,以及下面封装的结果展示组件
// @/components/Search/index.tsx
import { FC, useCallback, useMemo, memo, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import debounce from "lodash.debounce";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { SearchContainer, SearchButton } from "./styled";
import Input from "@/components/Input";
import { querySearchInfo } from "@/api/search";
import { useIsMobile } from "@/hooks";
import { SearchResults } from "./searchResults";
import { getURLBySearchResult } from "./utils";// 组件的属性类型
type Props = {defaultValue?: string;hasButton?: boolean;onClear?: () => void;
};
// 搜索框组件
const Search: FC<Props> = memo(({ defaultValue, hasButton, onClear: handleClear }) => {const queryClient = useQueryClient();const navigate = useNavigate();const { t } = useTranslation();const isMobile = useIsMobile();const [keyword, _setKeyword] = useState(defaultValue || "");const searchValue = keyword.trim();// 获取搜索结果数据const fetchData = async (searchValue: string) => {const { data } = await querySearchInfo({p: searchValue,});return {data,total: data.length,};};// 使用useQuery实现搜索const {refetch: refetchSearch,data: _searchResults,isFetching,} = useQuery(["moduleSearch", searchValue], () => fetchData(searchValue), {enabled: false,});// 从查询结果中获取搜索结果数据const searchResultData = _searchResults?.data;// 使用useMemo函数创建一个防抖函数debouncedSearch,用于实现搜索请求功能const debouncedSearch = useMemo(() => {return debounce(refetchSearch, 1500, { trailing: true }); // 在搜索值变化后1.5秒后触发refetchSearch函数}, [refetchSearch]); // 当refetchSearch函数发生变化时,重新创建防抖函数debouncedSearch// 监听搜索值变化,当有搜索值时,调用debouncedSearch函数进行搜索useEffect(() => {if (!searchValue) return;debouncedSearch();}, [searchValue]);// 重置搜索const resetSearch = useCallback(() => {debouncedSearch.cancel(); // 取消搜索轮询queryClient.resetQueries(["moduleSearch", searchValue]); // 重置查询缓存}, [debouncedSearch, queryClient, searchValue]);// 清空搜索const onClear = useCallback(() => {resetSearch(); // 调用重置方法handleClear?.(); // 调用清空回调方法}, [resetSearch, handleClear]);// 设置搜索内容,如果值为空,则调用清空方法const setKeyword = (value: string) => {if (value === "") onClear();_setKeyword(value);};// 搜索按钮点击事件const handleSearch = () => {// 如果没有搜索内容,或者搜索无数据则直接返回if (!searchValue || !searchResultData) return;// 根据搜索结果数据的第一个元素获取搜索结果对应的URLconst url = getURLBySearchResult(searchResultData[0]);// 跳转到对应的URL,如果获取不到URL,则跳转到失败的搜索页面navigate(url ?? `/search/fail?q=${searchValue}`);};return (<SearchContainer>{/* 搜索框 */}<Input loading={isFetching} value={keyword} hasPrefix placeholder={t("navbar.search_placeholder")} autoFocus={!isMobile} onChange={(event) => setKeyword(event.target.value)} onEnter={handleSearch} onClear={onClear} />{/* 搜索按钮,hasButton为true时显示 */}{hasButton && <SearchButton onClick={handleSearch}>{t("search.search")}</SearchButton>}{/* 搜索结果列表组件展示 */}{(isFetching || searchResultData && <SearchResults keyword={keyword} results={searchResultData ?? []} loading={isFetching} />}</SearchContainer>);
});export default Search;
------------------------------------------------------------------------------
// @/components/Search/styled.tsx
import styled from "styled-components";
import variables from "@/styles/variables.module.scss";
export const SearchContainer = styled.div`position: relative;margin: 0 auto;width: 100%;padding-right: 0;display: flex;align-items: center;justify-content: center;background: white;border: 0 solid white;border-radius: 4px;
`;
export const SearchButton = styled.div`flex-shrink: 0;width: 72px;height: calc(100% - 4px);margin: 2px 2px 2px 8px;border-radius: 0 4px 4px 0;background-color: #121212;text-align: center;line-height: 34px;color: #fff;letter-spacing: 0.2px;font-size: 14px;cursor: pointer;@media (max-width: ${variables.mobileBreakPoint}) {display: none;}
`;
4. 使用方式
// 引入组件
import Search from '@/components/Search'
// 使用
{/* 带搜索按钮 */}
<Search hasButton />
{/* 不带搜索按钮 */}
<Search />
二、搜索结果展示组件封装
注:这个组件在上面Search组件中引用,单独列出来讲讲。运用关注点分离的策略,将页面分割成多个片段,易维护,容易定位代码位置。
1. 功能分析
(1)组件接受搜索内容,是否显示loading加载,以及搜索列表这三个参数
(2)根据搜索结果列表,按模块类型分类数据,这里举例2种类型(如Transaction 和 Block)
(3)对搜索的模块类型列表,添加点击事件,当点击某个模块时,展示该模块的数据
(4)不同模块类型的列表,展示不同效果(例如类型是 Transaction,显示交易信息,包括交易名称和所在区块的编号;类型是 Block,则显示区块信息,包括区块编号)
(5)通过useEffect监听数据变化,发生变化时,重置激活的模块类型分类,默认不选中任何模块类型
(6)封装不同模块匹配对应的地址,名字的方法,统一管理
(7)采用联合等进行类型声明的定义
2. 代码+详细注释
// @/components/Search/SearchResults/index.tsx
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { FC, useEffect, useState } from "react";
import { SearchResultsContainer, CategoryFilterList, SearchResultList, SearchResultListItem } from "./styled";
import { useIsMobile } from "@/hooks";
import Loading from "@/components/Loading";
import { SearchResultType, SearchResult } from "@/models/Search";
// 引入不同模块匹配对应的地址,名字方法
import { getURLBySearchResult, getNameBySearchResult } from "../utils";// 组件的类型定义
type Props = {keyword?: string; // 搜索内容loading?: boolean; // 是否显示 loading 状态results: SearchResult[]; // 搜索结果列表
};// 列表数据每一项Item的渲染
const SearchResultItem: FC<{ keyword?: string; item: SearchResult }> = ({ item, keyword = "" }) => {const { t } = useTranslation(); // 使用国际化const to = getURLBySearchResult(item); // 根据搜索结果项获取对应的 URLconst displayName = getNameBySearchResult(item); // 根据搜索结果项获取显示名称// 如果搜索结果项类型是 Transaction,则显示交易信息if (item.type === SearchResultType.Transaction) {return (<SearchResultListItem to={to}><div className={classNames("content")}>{/* 显示交易名称 */}<div className={classNames("secondary-text")} title={displayName}>{displayName}</div>{/* 显示交易所在区块的编号 */}<div className={classNames("sub-title", "monospace")}>{t("search.block")} # {item.attributes.blockNumber}</div></div></SearchResultListItem>);}// 否则,类型是Block, 显示区块信息return (<SearchResultListItem to={to}><div className={classNames("content")} title={displayName}>{displayName}</div></SearchResultListItem>);
};// 搜索结果列表
export const SearchResults: FC<Props> = ({ keyword = "", results, loading }) => {const isMobile = useIsMobile(); // 判断是否是移动端const { t } = useTranslation(); // 使用国际化// 设置激活的模块类型分类const [activatedCategory, setActivatedCategory] = useState<SearchResultType | undefined>(undefined);// 当搜索结果列表发生变化时,重置激活的分类useEffect(() => {setActivatedCategory(undefined);}, [results]);// 根据搜索结果列表,按模块类型分类数据const categories = results.reduce((acc, result) => {if (!acc[result.type]) {acc[result.type] = [];}acc[result.type].push(result);return acc;}, {} as Record<SearchResultType, SearchResult[]>);// 按模块类型分类的列表const SearchResultBlock = (() => {return (<SearchResultList>{Object.entries(categories).filter(([type]) => (activatedCategory === undefined ? true : activatedCategory === type)).map(([type, items]) => (<div key={type} className={classNames("search-result-item")}><div className={classNames("title")}>{t(`search.${type}`)}</div><div className={classNames("list")}>{items.map((item) => (<SearchResultItem keyword={keyword} key={item.id} item={item} />))}</div></div>))}</SearchResultList>);})();// 如果搜索结果列表为空,则显示空数据提示;否则显示搜索结果列表return (<SearchResultsContainer>{!loading && Object.keys(categories).length > 0 && (<CategoryFilterList>{(Object.keys(categories) as SearchResultType[]).map((category) => (<div key={category} className={classNames("categoryTagItem", { active: activatedCategory === category })} onClick={() => setActivatedCategory((pre) => (pre === category ? undefined : category))}>{t(`search.${category}`)} {`(${categories[category].length})`}</div>))}</CategoryFilterList>)}{loading ? <Loading size={isMobile ? "small" : undefined} /> : results.length === 0 ? <div className={classNames("empty")}>{t("search.no_search_result")}</div> : SearchResultBlock}</SearchResultsContainer>);
};------------------------------------------------------------------------------
// @/components/Search/SearchResults/styled.tsx
import styled from "styled-components";
import Link from "@/components/Link";
export const SearchResultsContainer = styled.div`display: flex;flex-direction: column;gap: 12px;width: 100%;max-height: 292px;overflow-y: auto;background: #fff;color: #000;border-radius: 4px;box-shadow: 0 4px 4px 0 #1010100d;position: absolute;z-index: 2;top: calc(100% + 8px);left: 0;.empty {padding: 28px 0;text-align: center;font-size: 16px;color: #333;}
`;
export const CategoryFilterList = styled.div`display: flex;flex-wrap: wrap;padding: 12px 12px 0;gap: 4px;.categoryTagItem {border: 1px solid #e5e5e5;border-radius: 24px;padding: 4px 12px;cursor: pointer;transition: all 0.3s;&.active {border-color: var(--cd-primary-color);color: var(--cd-primary-color);}}
`;
export const SearchResultList = styled.div`.search-result-item {.title {color: #666;font-size: 0.65rem;letter-spacing: 0.5px;font-weight: 700;padding: 12px 12px 6px;background-color: #f5f5f5;text-align: left;}.list {padding: 6px 8px;}}
`;
export const SearchResultListItem = styled(Link)`display: block;width: 100%;padding: 4px 0;cursor: pointer;border-bottom: solid 1px #e5e5e5;.content {display: flex;align-items: center;justify-content: space-between;width: 100%;padding: 4px;border-radius: 4px;text-align: left;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;color: var(--cd-primary-color);}.secondary-text {flex: 1;width: 0;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}.sub-title {font-size: 14px;color: #666;overflow: hidden;margin: 0 4px;}&:last-child {border-bottom: none;}&:hover,&:focus-within {.content {background: #f5f5f5;}}
`;
三、引用到文件,自行取用
(1)获取不同模块地址,展示名称的方法
// @/components/Search/utils
import { SearchResultType, SearchResult } from "@/models/Search";
// 根据搜索结果项类型,返回对应的 URL 链接
export const getURLBySearchResult = (item: SearchResult) => {const { type, attributes } = item;switch (type) {case SearchResultType.Block:// 如果搜索结果项类型是 Block,则返回对应的区块详情页面链接return `/block/${attributes.blockHash}`;case SearchResultType.Transaction:// 如果搜索结果项类型是 Transaction,则返回对应的交易详情页面链接return `/transaction/${attributes.transactionHash}`;default:// 如果搜索结果项类型不是 Block 或者 Transaction,则返回空字符串return "";}
};
// 根据搜索结果项类型,返回不同显示名称
export const getNameBySearchResult = (item: SearchResult) => {const { type, attributes } = item;switch (type) {case SearchResultType.Block:return attributes?.number?.toString(); // 返回高度case SearchResultType.Transaction:return attributes?.transactionHash?.toString(); // 返回交易哈希default:return ""; // 返回空字符串}
};
(2)用到的类型声明
// @/models/Search/index.ts
import { Response } from '@/request/types'
import { Block } from '@/models/Block'
import { Transaction } from '@/models/Transaction'
export enum SearchResultType {Block = 'block',Transaction = 'ckb_transaction',
}
export type SearchResult =| Response.Wrapper<Block, SearchResultType.Block>| Response.Wrapper<Transaction, SearchResultType.Transaction>
-------------------------------------------------------------------------------------------------------
// @/models/Block/index.ts
export interface Block {blockHash: stringnumber: numbertransactionsCount: numberproposalsCount: numberunclesCount: numberuncleBlockHashes: string[]reward: stringrewardStatus: 'pending' | 'issued'totalTransactionFee: stringreceivedTxFee: stringreceivedTxFeeStatus: 'pending' | 'calculated'totalCellCapacity: stringminerHash: stringminerMessage: stringtimestamp: numberdifficulty: stringepoch: numberlength: stringstartNumber: numberversion: numbernonce: stringtransactionsRoot: stringblockIndexInEpoch: stringminerReward: stringliveCellChanges: stringsize: numberlargestBlockInEpoch: numberlargestBlock: numbercycles: number | nullmaxCyclesInEpoch: number | nullmaxCycles: number | null
}
-------------------------------------------------------------------------------------------------------
// @/models/Transaction/index.ts
export interface Transaction {isBtcTimeLock: booleanisRgbTransaction: booleanrgbTxid: string | nulltransactionHash: string// FIXME: this type declaration should be fixed by adding a transformation between internal state and response of APIblockNumber: number | stringblockTimestamp: number | stringtransactionFee: stringincome: stringisCellbase: booleantargetBlockNumber: numberversion: numberdisplayInputs: anydisplayOutputs: anyliveCellChanges: stringcapacityInvolved: stringrgbTransferStep: string | nulltxStatus: stringdetailedMessage: stringbytes: numberlargestTxInEpoch: numberlargestTx: numbercycles: number | nullmaxCyclesInEpoch: number | nullmaxCycles: number | nullcreateTimestamp?: number
}
总结
下一篇讲【全局常用Echarts组件封装】。关注本栏目,将实时更新。
相关文章:
React+TS前台项目实战(二十一)-- Search业务组件封装实现全局搜索
文章目录 前言一、Search组件封装1. 效果展示2. 功能分析3. 代码详细注释4. 使用方式 二、搜索结果展示组件封装1. 功能分析2. 代码详细注释 三、引用到文件,自行取用总结 前言 今天,我们来封装一个业务灵巧的组件,它集成了全局搜索和展示搜…...
SEO与AI的结合:如何用ChatGPT生成符合搜索引擎优化的内容
在当今数字时代,搜索引擎优化(SEO)已成为每个网站和内容创作者都必须掌握的一项技能。SEO的主要目标是通过优化内容,使其在搜索引擎结果页面(SERP)中排名更高,从而吸引更多的流量。然而…...
【信息系统项目管理师知识点速记】组织通用管理:知识管理
23.3 知识管理 23.3.1 知识管理基础 知识管理是通过利用各种知识和技术手段,帮助组织和个人生产、分享、应用和创新知识,以形成知识优势并在个人、组织、业务目标、经济绩效和社会效益方面产生价值的过程。它能为组织带来知识增值,创造新的价值,提升决策效能和水平,是提…...
CM-UNet: Hybrid CNN-Mamba UNet for Remote Sensing Image Semantic Segmentation
论文:CM-UNet: Hybrid :CNN-Mamba UNet for Remote Sensing Image Semantic Segmentation 代码:https://github.com/XiaoBuL/CM-UNet Abstrcat: 由于大规模图像尺寸和对象变化,当前基于 CNN 和 Transformer 的遥感图像语义分割方…...
DP:子序列问题
文章目录 什么是子序列子序列的特点举例说明常见问题 关于子序列问题的几个例题1.最长递增子序列2.摆动序列3.最长递增子序列的个数4.最长数对链5.最长定差子序列 总结 什么是子序列 在计算机科学和数学中,子序列(Subsequence)是指从一个序列…...
Spring Data与多数据源配置
Spring Data与多数据源配置 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们来探讨如何在Spring Data中配置和使用多个数据源。 在现代应用程序中&…...
【前端vue3】TypeScrip-类型推论和类型别名
类型推论 TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。 例如: 变量xiaoc被推断类型为string 如重新给xiaoc赋值数字会报错 let xiaoc "xiaoc"xiaoc 1111111111111如没有给变量指定类型和赋值…...
javaEE——Servlet
1.web开发概述 所谓web开发,指的是从网页中向后端程序发送请求,与后端程序进行交互 2.java后端开发环境搭建 web后端(javaEE)程序需要运行在服务器中的,这样前端才可以访问得到 3.服务器是什么? ①服务器就是一款软件,可以向其发送请求&#…...
Kotlin扩展函数(also apply run let)和with函数
also apply run let with的使用例子 private fun testOperator() {/*** also*/val person Person("ZhangSan", 18)person.also {// 通常仅仅打印使用, 也可以通过it修改it.name "ZhangSan1"println("also inner name: " it.name)}println(&qu…...
C语言笔记27 •单链表介绍•
1.链表的概念及结构 链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的。 2. 顺序表带来的问题 (1)中间/头部的插⼊删除,时间复杂度为O(N) (2)增容需要申请新空间,拷⻉数据ÿ…...
C++编程(五)单例模式 友元
文章目录 一、单例模式(一)概念(二)实现方式1. 饿汉式2. 懒汉式 二、友元(一)概念(二)友元函数1.概念2.语法格式3. 使用示例访问静态成员变量访问非静态成员变量 (三&…...
012-GeoGebra基础篇-构造圆的切线
前边文章对于基础内容已经悉数覆盖了,这一篇我就不放具体的细节,若有需要可以复刻一下 目录 一、成品展示二、算式内容三、正确性检查五、文章最后 一、成品展示 二、算式内容 A(0,0) B(3,0) c: Circle(A,B) C(5,4) sSegment(A,C) DMidpoint(s) d: Circ…...
数据结构速成--查找
由于是速成专题,因此内容不会十分全面,只会涵盖考试重点,各学校课程要求不同 ,大家可以按照考纲复习,不全面的内容,可以看一下小编主页数据结构初阶的内容,找到对应专题详细学习一下。 目录 …...
SpringMVC的基本使用
SpringMVC简介 SpringMVC是Spring提供的一套建立在Servlet基础上,基于MVC模式的web解决方案 SpringMVC核心组件 DispatcherServlet:前置控制器,来自客户端的所有请求都经由DispatcherServlet进行处理和分发Handler:处理器&…...
【PYG】Cora数据集分类任务计算损失,cross_entropy为什么不能直接替换成mse_loss
cross_entropy计算误差方式,输入向量z为[1,2,3],预测y为[1],选择数为2,计算出一大坨e的式子为3.405,再用-23.405计算得到1.405MSE计算误差方式,输入z为[1,2,3],预测向量应该是[1,0,0]࿰…...
MyBatis-plus这么好用,不允许还有人不会
你好呀,我是 javapub. 做 Java 的同学都会用到的三件套,Spring、SpringMV、MyBatis。但是由于使用起来配置较多,依赖冲突频发。所有,各路大佬又在这上边做了包装,像我们常用的 SpringBoot、MyBatisPlus。 基于当前要…...
Linux驱动开发实战宝典:设备模型、模块编程、I2C/SPI/USB外设精讲
摘要: 本文将带你走进 Linux 驱动开发的世界,从设备驱动模型、内核模块开发基础开始,逐步深入 I2C、SPI、USB 等常用外设的驱动编写,结合实际案例,助你掌握 Linux 驱动开发技能。 关键词: Linux 驱动,设备驱动模型,内核模块,I2C,SPI,USB 一、Linux 设备驱动模型 Li…...
安全技术和防火墙
1、安全技术 1.1入侵检测系统 特点是不阻断网络访问,主要提供报警和事后监督。不主动介入,默默的看着你(类似于监控) 1.2入侵防御系统 透明模式工作, 数据包,网络监控,服务攻击,…...
Webpack: 开发 PWA、Node、Electron 应用
概述 毋庸置疑,对前端开发者而言,当下正是一个日升月恒的美好时代!在久远的过去,Web 页面的开发技术链条非常原始而粗糙,那时候的 JavaScript 更多用来点缀 Web 页面交互而不是用来构建一个完整的应用。直到 2009年5月…...
python处理txt文件, 如果第一列和第二列的值在连续的行中重复,则只保留一行
处理txt文件, 如果第一列和第二列的值在连续的行中重复,则只保留一个实例,使用Python的内置函数来读取文件,并逐行检查和处理数据。 一个txt文件,里面的数据是893.554382324,-119.955825806,0.0299997832626,-0.133618548512,28.1155740884,112.876833236,46.7922,19.62582…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考
目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...
