SpringBatch+Mysql+hanlp简版智能搜索
资源条件有限,需要支持智搜的数据量也不大,上es搜索有点大材小用了,只好写个简版mysql的智搜,处理全文搜素,支持拼音搜索,中文分词,自定义分词断词,地图范围搜索,周边搜索,自定义多边形搜索。
通过设置定时SpringBatch抽取需要进行检索的表,并将检索的标题,概要,内容等存入检索表中。通过检索表进行全文检索。
代码主要包:通过网盘分享的文件:mysql智搜.zip
链接: https://pan.baidu.com/s/1MMhmyVD8o56Grp1Aa5IiKQ 提取码: 0530
1、提供的接口
@RestController
@Tag(name = "智搜全局搜索API")
@RequestMapping(path = "/smart/serach")
public class SearchIndexController {@Resourceprivate SearchIndexService searchIndexService;@PostMapping("/smartSearch")@Operation(summary = "智搜数据查询")@Log(title = "一张图智搜数据查询", businessType = BusinessType.QUERY)public Response<Page<SearchIndexDTO>> getSearchIndexDataByPage(@RequestBody SearchIndexParamDTO searchIndexParam) {return Response.with(searchIndexService.getSearchIndexDataByPage(searchIndexParam));}@PostMapping("/getSearchIndexByPage")@Operation(summary = "普通智搜内容分页查询")@Log(title = "普通智搜内容分页查询", businessType = BusinessType.QUERY)public Response<Page<SearchIndexDTO>> getSearchIndexByPage(@RequestBody SearchIndexParamDTO searchIndexParam) {return Response.with(searchIndexService.getSearchIndexByPage(searchIndexParam));}@PostMapping("/getSearchIndexById")@Operation(summary = "通过主键获取检索详细信息")@Log(title = "普通智搜内容分页查询", businessType = BusinessType.QUERY)public Response<SearchIndexDTO> getSearchIndexById(@RequestBody SearchIndexParamDTO searchIndexParam) {return Response.with(searchIndexService.getSearchIndexById(searchIndexParam));}@PostMapping("/smartSearchRiver")@Operation(summary = "智搜溯源河流数据查询")@Log(title = "一张图智搜溯源河流数据查询", businessType = BusinessType.QUERY)public Response<String> getSmartSearchRiver(@RequestBody SearchPointParamDTO searchIndexParam) {return Response.with(searchIndexService.getSmartSearchRiver(searchIndexParam));}}
2、service
public interface SearchIndexService extends IService<SearchIndex> {Page<SearchIndexDTO> getSearchIndexByPage(SearchIndexParamDTO searchIndexParamDTO);SearchIndexDTO getSearchIndexById(SearchIndexParamDTO searchIndexParamDTO);Page<SearchIndexDTO> getSearchIndexDataByPage(SearchIndexParamDTO searchIndexParamDTO);String getSmartSearchRiver(SearchPointParamDTO searchIndexParam);}
@Service
@Slf4j
@RequiredArgsConstructor
@DS("master")
public class SearchIndexServiceImpl extends ServiceImpl<SearchIndexMapper, SearchIndex> implements SearchIndexService {@Resourceprivate SearchIndexMapper searchIndexMapper;@Resourceprivate DataSourceService dataSourceService;@Resourceprivate DynamicDataSourcesService dynamicDataSourcesService;@Overridepublic Page<SearchIndexDTO> getSearchIndexByPage(SearchIndexParamDTO searchIndexParamDTO) {// 处理拼音查询if (searchIndexParamDTO.getSearchContent() != null) {boolean isPingyin = KeyWordUtils.isContainsPinyin(searchIndexParamDTO.getSearchContent());if (isPingyin) {searchIndexParamDTO.setSearchContent(KeyWordUtils.getPinyin(searchIndexParamDTO.getSearchContent(), CommonConstant.SPACE));} else {searchIndexParamDTO.setSearchContent(HanLpAdvancedUtil.extractKeywords(searchIndexParamDTO.getSearchContent()));}}// 处理自定义区域搜索// 从GeoJSON中提取多边形坐标if (ObjectUtil.isNotEmpty(searchIndexParamDTO.getGeoJson())) {List<List<Double>> polygonCoordinates = GeoJsonUtil.extractPolygonCoordinates(searchIndexParamDTO.getGeoJson());// 转换为WKT格式String polygonWKT = GeoJsonUtil.convertGeoJsonToWKT(polygonCoordinates);searchIndexParamDTO.setPolygonWKT(polygonWKT);}return searchIndexMapper.getSearchIndexByPage(searchIndexParamDTO.build(), searchIndexParamDTO);}@Overridepublic SearchIndexDTO getSearchIndexById(SearchIndexParamDTO searchIndexParamDTO) {// 处理拼音查询if (StrUtil.isEmpty(searchIndexParamDTO.getPkId())) {throw new CoreException(ErrorCodeEnum.SYS_ERROR, "查询参数缺失");}// 查找指定ID数据SearchIndex searchIndex = searchIndexMapper.selectById(searchIndexParamDTO.getPkId());// 构造来源数据查询sqlString sql = "select * from " + searchIndex.getEntityTable() + " where " + searchIndex.getEntityKeyColumn() + " = ?";// 获取表结构信息DataSourceDTO dataSourceDTO = dataSourceService.findById(searchIndex.getEntitySourceId());List<Column> columnList = dynamicDataSourcesService.getDbColumnList(dataSourceDTO, searchIndex.getEntityTable());List<Object> jdbcParamValues = new ArrayList<>();jdbcParamValues.add(searchIndex.getEntityId());List<Map<String, Object>> listInfo = dynamicDataSourcesService.executeListSql(dataSourceDTO, sql, jdbcParamValues);SearchIndexDTO searchIndexDTO = new SearchIndexDTO();BeanUtilCopy.copyProperties(searchIndex, searchIndexDTO);searchIndexDTO.setColumnList(columnList);searchIndexDTO.setDetailInfo(CollUtil.isNotEmpty(listInfo)? listInfo.get(0) : null);return searchIndexDTO;}@Overridepublic Page<SearchIndexDTO> getSearchIndexDataByPage(SearchIndexParamDTO searchIndexParamDTO) {// 处理拼音查询if (searchIndexParamDTO.getSearchContent() != null) {boolean isPingyin = KeyWordUtils.isContainsPinyin(searchIndexParamDTO.getSearchContent());if (isPingyin) {searchIndexParamDTO.setSearchContent(KeyWordUtils.getPinyin(HanLpAdvancedUtil.extractKeywords(searchIndexParamDTO.getSearchContent()), CommonConstant.SPACE));} else {searchIndexParamDTO.setSearchContent(HanLpAdvancedUtil.segmentToString(searchIndexParamDTO.getSearchContent(), Boolean.TRUE));}}// 处理自定义区域搜索// 从GeoJSON中提取多边形坐标if (ObjectUtil.isNotEmpty(searchIndexParamDTO.getGeoJson())) {List<List<Double>> polygonCoordinates = GeoJsonUtil.extractPolygonCoordinates(searchIndexParamDTO.getGeoJson());// 转换为WKT格式String polygonWKT = GeoJsonUtil.convertGeoJsonToWKT(polygonCoordinates);searchIndexParamDTO.setPolygonWKT(polygonWKT);}if (CollUtil.isEmpty(searchIndexParamDTO.getEntityTypes())) {List<String> entityTypes = Arrays.asList("3","4","5","6","7");searchIndexParamDTO.setEntityTypes(entityTypes);}return searchIndexMapper.getSearchIndexByPage(searchIndexParamDTO.build(), searchIndexParamDTO);}/*** 获取河流子集合** @param searchIndexParam* @return*/@Overridepublic String getSmartSearchRiver(SearchPointParamDTO searchIndexParam) {if (searchIndexParam.getMaxDistanceKm() == null) {searchIndexParam.setMaxDistanceKm(5D);}return GeoJsonUtil.extractLineSubset(searchIndexParam.getLongitudeStart(), searchIndexParam.getLatitudeStart(), searchIndexParam.getLongitudeEnd(), searchIndexParam.getLatitudeEnd(), searchIndexParam.getMaxDistanceKm());//return JSON.toJSONString(LineStringSubsetExtractor.extractSubsetBetweenPoints(searchIndexParam.getLongitudeStart(), searchIndexParam.getLatitudeStart(), searchIndexParam.getLongitudeEnd(), searchIndexParam.getLatitudeEnd(), searchIndexParam.getMaxDistanceKm()));}
}
3、数据库表设计
数据库表
DROP TABLE IF EXISTS `ads_search_index_table`;
CREATE TABLE `ads_search_index_table` (`pk_id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`entity_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类',`entity_type_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分类名称',`entity_key_column` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键字段',`entity_table` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '实体对应的表名',`entity_table_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体对应的表名中文',`entity_source_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体所在数据源,目前只支持当前库',`title_column` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题抽入字段,逗号分割',`sumary_column` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '摘要信息字段,逗号分割',`content_column` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容content需要抽入的字段英文逗号分割',`keywords_default` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '关键词默认值',`keywords_column` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '关键词keywords要抽入的字段英文逗号分割',`weight_default` int NULL DEFAULT NULL COMMENT '默认权重',`update_flag` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '是否更新1是0否',`lng_column` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '经度字段',`lat_column` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '纬度字段',`delete_column` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '逻辑删除字段',`region_code_column` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '行政区划编码',`file_url_column` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件地址字段',`deploy_time_column` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据发布时间字段',`source_from` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据来源',`revision` int NULL DEFAULT 0 COMMENT '乐观锁',`created_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',`created_time` datetime NULL DEFAULT NULL COMMENT '创建时间',`updated_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',`updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间',`deleted` int NULL DEFAULT 0 COMMENT '删除标志:0-未删除;1-已删除',PRIMARY KEY (`pk_id`) USING BTREE,FULLTEXT INDEX `ft_search`(`title_column`, `keywords_column`) WITH PARSER `ngram`
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '全局搜索表数据来源信息' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of ads_search_index_table
-- ----------------------------
INSERT INTO `ads_search_index_table` VALUES (8, '8', '新闻', 'pk_id', 'test_environmental_news', '新闻表', '1', 'news_title', 'news_sumary', 'news_content', '新闻', 'news_title,news_content', 80, '0', '', '', '', 'region_code', 'attachment_url', 'publish_time', '某某单位', 19, NULL, NULL, 'xxxx@qq.com', '2025-05-26 16:22:58', 0);
INSERT INTO `ads_search_index_table` VALUES (9, '9', '数据', 'PK_ID', 'ads_t_app_interface', '某接口表', '1', 'interface_name', 'interface_name', 'interface_url', '某接口', 'interface_name', 60, '0', NULL, NULL, '', NULL, NULL, 'created_time', '某信息中心', 5, NULL, NULL, 'xxxx@qq.com', '2025-05-20 17:08:28', 0);
INSERT INTO `ads_search_index_table` VALUES (10, '10', '文件', 'pk_id', 'test_environmental_news', '新闻表', '1', 'news_title', 'news_sumary', 'news_content', '文件', 'news_title,news_content', 60, '0', '', '', '', 'region_code', 'attachment_url', 'publish_time', '某某单位', 6, NULL, NULL, 'xxxx@qq.com', '2025-05-26 16:22:58', 0);
INSERT INTO `ads_search_index_table` VALUES (11, '11', '地图', 'id', 'test_dust_emission_source', '扬尘源', '1', 'project_name', 'project_name', 'project_name', '地图', 'project_name', 60, '0', 'longitude', 'latitude', '', 'region_code', '', 'create_time', '某某单位', 5, NULL, NULL, 'xxx@qq.com', '2025-05-20 17:08:28', 0);
INSERT INTO `ads_search_index_table` VALUES (12, '12', '应用', 'app_id', 'ads_t_sys_app', '应用', '1', 'name', 'name', 'description', '应用', 'name', 60, '0', '', '', '', '', '', 'created_time', '某某单位', 7, NULL, NULL, 'xxxx@qq.com', '2025-05-26 16:22:58', 0);
INSERT INTO `ads_search_index_table` VALUES (13, '13', '公告', 'pk_id', 'test_environmental_announcement', '公告表', '1', 'title', 'summary', 'content', '公告', 'title,content', 60, '0', '', '', '', 'region_code', 'attachment_url', 'publish_time', '某某单位', 5, NULL, NULL, 'xxxx@qq.com', '2025-05-26 16:22:58', 0);DROP TABLE IF EXISTS `ads_search_index`;
CREATE TABLE `ads_search_index` (`pk_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',`entity_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类',`entity_type_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类名称',`entity_key_column` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '主键字段',`entity_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '对应实体表的主键',`entity_table` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体对应的表名',`entity_table_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体对应的表中文名',`entity_source_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实体所在数据源',`title` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',`sumary` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '摘要',`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '内容',`keywords` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '关键词或标签',`pingyin_keywords` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '拼音分词',`weight` int NULL DEFAULT NULL COMMENT '权重或排序',`lng` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '经度',`lat` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '纬度',`region_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '行政区划编码',`region_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '行政区划名称',`file_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文件地址逗号分割',`deploy_time` datetime NULL DEFAULT NULL COMMENT '数据来源发布时间',`source_from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据来源',`revision` int NULL DEFAULT 0 COMMENT '乐观锁',`created_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',`created_time` datetime NULL DEFAULT NULL COMMENT '创建时间',`updated_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',`updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间',`deleted` int NULL DEFAULT 0 COMMENT '删除标志:0-未删除;1-已删除',`location_point` point NULL,PRIMARY KEY (`pk_id`) USING BTREE,INDEX `idx_search_index`(`entity_type`, `entity_table`, `deleted`) USING BTREE,FULLTEXT INDEX `ft_search`(`title`, `content`, `keywords`, `pingyin_keywords`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '全局搜索表' ROW_FORMAT = DYNAMIC;
4、SpringBatch抽取数据
依赖<dependency><groupId>org.springframework.batch</groupId><artifactId>spring-batch-core</artifactId><version>4.3.10</version></dependency>
<dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.1</version></dependency><!--地图--><dependency><groupId>org.geotools</groupId><artifactId>gt-shapefile</artifactId><version>${geotools.version}</version><exclusions><exclusion><groupId>org.geotools</groupId><artifactId>gt-main</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-main</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-geojson</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-swing</artifactId><version>${geotools.version}</version></dependency><dependency><groupId>org.geotools</groupId><artifactId>gt-epsg-hsql</artifactId><version>${geotools.version}</version> <!-- 与项目中其他GeoTools版本一致 --></dependency><!-- JTS几何库 --><dependency><groupId>org.locationtech.jts</groupId><artifactId>jts-core</artifactId><version>1.18.1</version></dependency><!-- Reactor Netty --><dependency><groupId>io.projectreactor.netty</groupId><artifactId>reactor-netty-http</artifactId><version>1.0.39</version></dependency><!-- IK 分词器--><dependency><groupId>com.janeluo</groupId><artifactId>ikanalyzer</artifactId><version>2012_u6</version></dependency><dependency><groupId>com.hankcs</groupId><artifactId>hanlp</artifactId><version>portable-1.8.4</version></dependency>
分词hanlp.properties配置
# HanLP 根路径
#root=D:/tools/hanlp/
root=/data/hanlp/# 核心词典
CoreDictionaryPath=data/dictionary/CoreNatureDictionary.txt# 自定义词典路径
CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt;data/dictionary/custom/hanyucidianDic.txt;data/dictionary/custom/siteDic.txt# 断词词典路径
CoreStopWordDictionaryPath=data/dictionary/stopwords.txt# 分词线程数据
SegmentThreadNumber=4# 是否显示词性标注信息
ShowTermNature=false# 日志级别
HanLPLogLevel=WARN
SpringBatch配置
application.yxmlSpringbatch:job:enabled: false #启动时不启动jobjdbc:initialize-schema: always#是否定时任务执行scheduler:enabled: truesql:init:schema-locations: classpath:/org/springframework/batch/core/schema-mysql.sql// 定时任务
@Component
public class ScheduledBatchTask {/** 创建索引任务*/@Resource@Qualifier("searchIndexCreateTaskJob")private Job searchIndexCreateTaskJob;@ResourceJobLauncher jobLauncher;@Value("${spring.batch.scheduler.enabled:false}")private boolean schedulerEnabled;/*** 每日03点执行批量更新*/@Scheduled(cron = "0 0 3 * * ?")public void searchIndexCreateTask() throws Exception {if (schedulerEnabled) {JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters();JobExecution run = jobLauncher.run(searchIndexCreateTaskJob, jobParameters);run.getId();}}}// 1. 主配置类调整(按表分区)
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Autowiredprivate SqlSessionFactory sqlSessionFactory;// 线程池配置(核心线程数=表数量)@Bean("batchTaskExecutor")public TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setThreadNamePrefix("table-processor-");return executor;}/*** 更新搜索索引任务** @return*/@Bean("searchIndexCreateTaskJob")public Job searchIndexCreateTaskJob(@Qualifier("deleteOldDataStep") Step deleteOldDataStep,@Qualifier("masterSearchStep") Step masterStep) {return jobBuilderFactory.get("searchIndexCreateTaskJob").start(deleteOldDataStep).next(masterStep).listener(new BatchSearchJobListener()).build();}@Beanpublic Step deleteOldDataStep(@Qualifier("deleteTasklet") Tasklet deleteTasklet) {return stepBuilderFactory.get("deleteOldDataStep").tasklet(deleteTasklet).build();}/*** 索引表更新主任务步骤** @return*/@Bean("masterSearchStep")public Step masterStep(@Qualifier("updateSearchIndexData") Step updateSearchIndexData,@Qualifier("multiIndexTablePartitioner") MultiIndexTablePartitioner multiIndexTablePartitioner) {return stepBuilderFactory.get("masterSearchStep").partitioner(updateSearchIndexData.getName(), multiIndexTablePartitioner) // 分区器按表名分区一个表一个分区.step(updateSearchIndexData).gridSize(10) // 按表分区了 并发数一般设置为核心数.taskExecutor(batchSearchTaskExecutor()).build();}// 索引线程池配置(核心线程数=表数量)@Bean("batchSearchTaskExecutor")public TaskExecutor batchSearchTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setThreadNamePrefix("search-processor-");return executor;}/*** 处理分页数据更新步骤* @return*/@Bean("updateSearchIndexData")public Step updateSearchIndexData(@Qualifier("searchTableReader") MyBatisPagingItemReader<Map<String, Object>> myBatisPagingItemReader,@Qualifier("batchSearchIndexWriter") BatchSearchIndexWriter batchSearchIndexWriter,@Qualifier("searchIndexProcessor") SearchIndexProcessor searchIndexProcessor) {return stepBuilderFactory.get("updateSearchIndexData").<Map<String, Object>, Map<String, Object>>chunk(100).reader(myBatisPagingItemReader).processor(searchIndexProcessor).writer(batchSearchIndexWriter).faultTolerant().skipPolicy(new AlwaysSkipItemSkipPolicy()).build();}/*** 分页获取需要处理的智搜数据的表数据* @return*/@Bean("searchTableReader")@StepScopepublic MyBatisPagingItemReader<Map<String, Object>> searchTableReader(@Value("#{stepExecutionContext['entityType']}") String entityType, //分类@Value("#{stepExecutionContext['entityTypeName']}") String entityTypeName, //分类@Value("#{stepExecutionContext['entityKeyColumn']}") String entityKeyColumn,// 主键字段@Value("#{stepExecutionContext['entityTable']}") String entityTable,// 表名@Value("#{stepExecutionContext['entityTableName']}") String entityTableName,// 表名@Value("#{stepExecutionContext['entitySourceId']}") String entitySourceId,// 表数据来源@Value("#{stepExecutionContext['titleColumn']}") String titleColumn, // 标题字段@Value("#{stepExecutionContext['sumaryColumn']}") String sumaryColumn, // 摘要字段@Value("#{stepExecutionContext['deployTimeColumn']}") String deployTimeColumn, // 发布时间字段@Value("#{stepExecutionContext['contentColumn']}") String contentColumn, // 内容字段@Value("#{stepExecutionContext['sourceFrom']}") String sourceFrom, // 数据来源@Value("#{stepExecutionContext['keywordsDefault']}") String keywordsDefault, // 默认关键词@Value("#{stepExecutionContext['keywordsColumn']}") String keywordsColumn, // 关键词字段@Value("#{stepExecutionContext['fileUrlColumn']}") String fileUrlColumn, // 关键词字段@Value("#{stepExecutionContext['weightDefault']}") String weightDefault, // 默认权重@Value("#{stepExecutionContext['deleteColumn']}") String deleteColumn, // 逻辑删除字段@Value("#{stepExecutionContext['lngColumn']}") String lngColumn, // 逻辑删除字段@Value("#{stepExecutionContext['latColumn']}") String latColumn, // 逻辑删除字段@Value("#{stepExecutionContext['regionCodeColumn']}") String regionCodeColumn, // 新区区划字段@Value("#{stepExecutionContext['updateFlag']}") String updateFlag // 是否需要更新) {MyBatisPagingItemReader<Map<String, Object>> reader = new MyBatisPagingItemReader<>();reader.setSqlSessionFactory(sqlSessionFactory);reader.setQueryId("com.bigdatacd.panorama.system.mapper.SearchIndexTableMapper.selectSearchIndexTableDataByPage");Map<String,Object> param = new HashMap<>();param.put(CommonConstant.ENTITY_TYPE,entityType);param.put(CommonConstant.ENTITY_TYPE_NAME,entityTypeName);param.put(CommonConstant.ENTITY_KEY_COLUMN,entityKeyColumn);param.put(CommonConstant.ENTITY_TABLE,entityTable);param.put(CommonConstant.ENTITY_TABLE_NAME,entityTableName);param.put(CommonConstant.SOURCE_FROM,sourceFrom);param.put(CommonConstant.DEPLOY_TIME_COLUMN,deployTimeColumn);param.put(CommonConstant.FILE_URL_COLUMN,fileUrlColumn);param.put(CommonConstant.SUMARY_COLUMN,sumaryColumn);param.put(CommonConstant.ENTITY_SOURCE_ID,entitySourceId);param.put(CommonConstant.TITLE_COLUMN,titleColumn);param.put(CommonConstant.CONTET_COLUMN,contentColumn);param.put(CommonConstant.KEYWORDS_DEFAULT,keywordsDefault);param.put(CommonConstant.KEYWORDS_COLUMN,keywordsColumn);param.put(CommonConstant.WEIGHT_DEFAULT,weightDefault);param.put(CommonConstant.UPDATE_FLAG,updateFlag);param.put(CommonConstant.DELETE_COLUMN,deleteColumn);param.put(CommonConstant.LNG_COLUMN,lngColumn);param.put(CommonConstant.LAT_COLUMN,latColumn);param.put(CommonConstant.REGION_CODE_COLUMN,regionCodeColumn);reader.setParameterValues(param);reader.setPageSize(1000);return reader;}}// 过程监听
@Component
public class BatchSearchJobListener implements JobExecutionListener {private long beingTime;private long endTime;@Overridepublic void beforeJob(JobExecution jobExecution) {beingTime = System.currentTimeMillis();System.out.println(jobExecution.getJobInstance().getJobName() + " beforeJob...... " + beingTime);}@Overridepublic void afterJob(JobExecution jobExecution) {endTime = System.currentTimeMillis();System.out.println(jobExecution.getJobInstance().getJobName() + "一共耗耗时:【" + (endTime - beingTime) + "】毫秒");}}// 构造智搜数据
@Component("batchSearchIndexWriter")
@StepScope
public class BatchSearchIndexWriter implements ItemWriter<Map<String, Object>> {@Autowiredprivate NamedParameterJdbcTemplate jdbcTemplate;@Overridepublic void write(List<? extends Map<String, Object>> items) {//如果需要更新数据则删除后重新插入if (CollUtil.isNotEmpty(items)) {try {StringBuilder insertSql = new StringBuilder();// 构造插入语句insertSql.append("insert into ads_search_index(").append("pk_id,entity_type,entity_type_name,entity_key_column,entity_id,entity_source_id,entity_table,entity_table_name,title,sumary,file_url,source_from,deploy_time, ").append("content,keywords,pingyin_keywords,weight,lng,lat,region_code,region_name,created_time,updated_time)").append(" values ").append("(:pkId,:entityType,:entityTypeName,:entityKeyColumn,:entityId,:entitySourceId,:entityTable,:entityTableName,:title,:sumary,:fileUrl,:sourceFrom,:deployTime, ").append(":content,:keywords,:pingyinKeywords,:weight,:lng,:lat,:regionCode,:regionName,:createdTime,:updatedTime)");jdbcTemplate.batchUpdate(insertSql.toString(), items.stream().map(item -> new MapSqlParameterSource().addValue(CommonConstant.PK_ID, UUID.randomUUID().toString().replaceAll("-", CommonConstant.NULL_STR).toUpperCase()).addValue(CommonConstant.ENTITY_TYPE, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_TYPE))? item.get(CommonConstant.ENTITY_TYPE) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_TYPE_NAME, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_TYPE_NAME))? item.get(CommonConstant.ENTITY_TYPE_NAME) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_KEY_COLUMN, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_KEY_COLUMN))? item.get(CommonConstant.ENTITY_KEY_COLUMN) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_ID, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_ID))? item.get(CommonConstant.ENTITY_ID) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_SOURCE_ID, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_SOURCE_ID))? item.get(CommonConstant.ENTITY_SOURCE_ID) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_TABLE, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_TABLE))? item.get(CommonConstant.ENTITY_TABLE) : CommonConstant.NULL_STR).addValue(CommonConstant.ENTITY_TABLE_NAME, ObjectUtil.isNotEmpty(item.get(CommonConstant.ENTITY_TABLE_NAME))? item.get(CommonConstant.ENTITY_TABLE_NAME) : CommonConstant.NULL_STR).addValue(CommonConstant.TITLE, ObjectUtil.isNotEmpty(item.get(CommonConstant.TITLE))? item.get(CommonConstant.TITLE) : CommonConstant.NULL_STR).addValue(CommonConstant.SUMARY, ObjectUtil.isNotEmpty(item.get(CommonConstant.SUMARY))? item.get(CommonConstant.SUMARY) : CommonConstant.NULL_STR).addValue(CommonConstant.FILE_URL, ObjectUtil.isNotEmpty(item.get(CommonConstant.FILE_URL))? item.get(CommonConstant.FILE_URL) : CommonConstant.NULL_STR).addValue(CommonConstant.SOURCE_FROM, ObjectUtil.isNotEmpty(item.get(CommonConstant.SOURCE_FROM))? item.get(CommonConstant.SOURCE_FROM) : CommonConstant.NULL_STR).addValue(CommonConstant.DEPLOY_TIME, ObjectUtil.isNotEmpty(item.get(CommonConstant.DEPLOY_TIME))? item.get(CommonConstant.DEPLOY_TIME) : null).addValue(CommonConstant.CONTENT, ObjectUtil.isNotEmpty(item.get(CommonConstant.CONTENT))? item.get(CommonConstant.CONTENT) : CommonConstant.NULL_STR).addValue(CommonConstant.KEYWORDS, ObjectUtil.isNotEmpty(item.get(CommonConstant.KEYWORDS))? item.get(CommonConstant.KEYWORDS): CommonConstant.NULL_STR).addValue(CommonConstant.PINGYIN_KEYWORDS, ObjectUtil.isNotEmpty(item.get(CommonConstant.PINGYIN_KEYWORDS))? item.get(CommonConstant.PINGYIN_KEYWORDS) : CommonConstant.NULL_STR).addValue(CommonConstant.WEIGHT, ObjectUtil.isNotEmpty(item.get(CommonConstant.WEIGHT_DEFAULT))? item.get(CommonConstant.WEIGHT_DEFAULT) : 100)// 判读经纬的范围.addValue(CommonConstant.LNG, ObjectUtil.isNotEmpty(item.get(CommonConstant.LNG))&& LatLonValidator.isLongitudeValid(item.get(CommonConstant.LNG).toString())? item.get(CommonConstant.LNG) : CommonConstant.ZERO).addValue(CommonConstant.LAT, ObjectUtil.isNotEmpty(item.get(CommonConstant.LAT)) && LatLonValidator.isLatitudeValid(item.get(CommonConstant.LAT).toString())? item.get(CommonConstant.LAT) : CommonConstant.ZERO).addValue(CommonConstant.REGION_CODE, ObjectUtil.isNotEmpty(item.get(CommonConstant.REGION_CODE))? item.get(CommonConstant.REGION_CODE) : CommonConstant.NULL_STR).addValue(CommonConstant.REGION_NAME, ObjectUtil.isNotEmpty(item.get(CommonConstant.REGION_NAME))? item.get(CommonConstant.REGION_NAME) : CommonConstant.NULL_STR).addValue(CommonConstant.CREATED_TIME, DateUtil.date()).addValue(CommonConstant.UPDATED_TIME, DateUtil.date())).toArray(SqlParameterSource[]::new));} catch (Exception e) {e.printStackTrace();throw new CoreException(ErrorCodeEnum.SYS_ERROR, e.getMessage());}}}
}
// 简单处理数据同步问题直接删除再添加数据量不大
@Component("deleteTasklet")
@StepScope
public class DeleteTasklet implements Tasklet {@Autowiredprivate NamedParameterJdbcTemplate jdbcTemplate;@Overridepublic RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {StringBuilder sqlBuilder = new StringBuilder();sqlBuilder.append("SELECT ");sqlBuilder.append("pk_id as pkId,");sqlBuilder.append("entity_type as entityType,");sqlBuilder.append("entity_type_name as entityTypeName,");sqlBuilder.append("entity_key_column as entityKeyColumn,");sqlBuilder.append("entity_table as entityTable,");sqlBuilder.append("entity_source_id as entitySourceId,");sqlBuilder.append("title_column as titleColumn,");sqlBuilder.append("content_column as contentColumn,");sqlBuilder.append("keywords_default as keywordsDefault,");sqlBuilder.append("keywords_column as keywordsColumn,");sqlBuilder.append("update_flag as updateFlag,");sqlBuilder.append("delete_column as deleteColumn,");sqlBuilder.append("lng_column as lngColumn,");sqlBuilder.append("lat_column as latColumn,");sqlBuilder.append("region_code_column as regionCodeColumn,");sqlBuilder.append("weight_default as weightDefault");sqlBuilder.append(" FROM ads_search_index_table where deleted =0 and update_flag ='1' ");List<Map<String, Object>> tables = jdbcTemplate.queryForList(sqlBuilder.toString(), new MapSqlParameterSource());String delSql = "update ads_search_index set deleted = '1' where deleted = '0' and entity_table = :entityTable and entity_type = :entityType";for (int i = 0; i < tables.size(); i++) {// 删除智搜索引数据jdbcTemplate.update(delSql, new MapSqlParameterSource().addValue(CommonConstant.ENTITY_TABLE, tables.get(i).get(CommonConstant.ENTITY_TABLE)).addValue(CommonConstant.ENTITY_TYPE, tables.get(i).get(CommonConstant.ENTITY_TYPE)));}return RepeatStatus.FINISHED;}
}/*** 获取需要更新搜素索引的表*/
@Component
@Slf4j
public class MultiIndexTablePartitioner implements Partitioner {private final DataSource dataSource;public MultiIndexTablePartitioner(DataSource dataSource) {this.dataSource = dataSource;}@Overridepublic Map<String, ExecutionContext> partition(int gridSize) {JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);StringBuilder sqlBuilder = new StringBuilder();sqlBuilder.append("SELECT ");sqlBuilder.append("pk_id as pkId,");sqlBuilder.append("entity_type as entityType,");sqlBuilder.append("entity_type_name as entityTypeName,");sqlBuilder.append("entity_key_column as entityKeyColumn,");sqlBuilder.append("entity_table as entityTable,");sqlBuilder.append("entity_table_name as entityTableName,");sqlBuilder.append("entity_source_id as entitySourceId,");sqlBuilder.append("title_column as titleColumn,");sqlBuilder.append("sumary_column as sumaryColumn,");sqlBuilder.append("file_url_column as fileUrlColumn,");sqlBuilder.append("deploy_time_column as deployTimeColumn,");sqlBuilder.append("source_from as sourceFrom,");sqlBuilder.append("content_column as contentColumn,");sqlBuilder.append("keywords_default as keywordsDefault,");sqlBuilder.append("keywords_column as keywordsColumn,");sqlBuilder.append("update_flag as updateFlag,");sqlBuilder.append("delete_column as deleteColumn,");sqlBuilder.append("lng_column as lngColumn,");sqlBuilder.append("lat_column as latColumn,");sqlBuilder.append("region_code_column as regionCodeColumn,");sqlBuilder.append("weight_default as weightDefault");sqlBuilder.append(" FROM ads_search_index_table where deleted =0 and update_flag ='1' ");List<Map<String,Object>> tables = jdbcTemplate.queryForList(sqlBuilder.toString());// 删除智搜索引数据Map<String, ExecutionContext> partitions = new HashMap<>();for (int i = 0; i < tables.size(); i++) {ExecutionContext ctx = new ExecutionContext();// 将需要传递的参数放到上下文中,用于动态批量更新的sql用ctx.putString(CommonConstant.PK_ID, String.valueOf(tables.get(i).get(CommonConstant.PK_ID)));ctx.putString(CommonConstant.ENTITY_TYPE, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_TYPE)));ctx.putString(CommonConstant.ENTITY_TYPE_NAME, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_TYPE_NAME)));ctx.putString(CommonConstant.ENTITY_KEY_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_KEY_COLUMN)));ctx.putString(CommonConstant.ENTITY_TABLE, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_TABLE)));ctx.putString(CommonConstant.ENTITY_TABLE_NAME, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_TABLE_NAME)));ctx.putString(CommonConstant.ENTITY_SOURCE_ID, String.valueOf(tables.get(i).get(CommonConstant.ENTITY_SOURCE_ID)));ctx.putString(CommonConstant.TITLE_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.TITLE_COLUMN)));ctx.putString(CommonConstant.SUMARY_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.SUMARY_COLUMN)));ctx.putString(CommonConstant.FILE_URL_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.FILE_URL_COLUMN)));ctx.putString(CommonConstant.DEPLOY_TIME_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.DEPLOY_TIME_COLUMN)));ctx.putString(CommonConstant.SOURCE_FROM, String.valueOf(tables.get(i).get(CommonConstant.SOURCE_FROM)));ctx.putString(CommonConstant.CONTET_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.CONTET_COLUMN)));ctx.putString(CommonConstant.KEYWORDS_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.KEYWORDS_COLUMN)));ctx.putString(CommonConstant.KEYWORDS_DEFAULT, String.valueOf(tables.get(i).get(CommonConstant.KEYWORDS_DEFAULT)));ctx.putString(CommonConstant.WEIGHT_DEFAULT, String.valueOf(tables.get(i).get(CommonConstant.WEIGHT_DEFAULT)));ctx.putString(CommonConstant.UPDATE_FLAG, String.valueOf(tables.get(i).get(CommonConstant.UPDATE_FLAG)));ctx.putString(CommonConstant.DELETE_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.DELETE_COLUMN)));ctx.putString(CommonConstant.LNG_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.LNG_COLUMN)));ctx.putString(CommonConstant.LAT_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.LAT_COLUMN)));ctx.putString(CommonConstant.REGION_CODE_COLUMN, String.valueOf(tables.get(i).get(CommonConstant.REGION_CODE_COLUMN)));partitions.put("partition" + i, ctx);}return partitions;}
}
// 处理索引数据分词
@Component("searchIndexProcessor")
@Builder
public class SearchIndexProcessor implements ItemProcessor<Map<String, Object>, Map<String, Object>> {@Autowiredprivate SysRegionService sysRegionService;@Overridepublic Map<String, Object> process(Map<String, Object> item) {// 标题内容关键字分词item.put(CommonConstant.TITLE, item.get(CommonConstant.TITLE).toString());item.put(CommonConstant.SUMARY, ObjectUtil.isNotEmpty(item.get(CommonConstant.SUMARY))?item.get(CommonConstant.SUMARY).toString():CommonConstant.NULL_STR);item.put(CommonConstant.CONTENT, HanLpAdvancedUtil.segmentToString(item.get(CommonConstant.CONTENT).toString()));item.put(CommonConstant.KEYWORDS, HanLpAdvancedUtil.extractKeywords(item.get(CommonConstant.KEYWORDS).toString()));item.put(CommonConstant.PINGYIN_KEYWORDS, KeyWordUtils.getPinyin(item.get(CommonConstant.KEYWORDS).toString(), CommonConstant.SPACE));// 处理行政区划if (ObjectUtil.isNotEmpty(item.get(CommonConstant.REGION_CODE))) {// 不满12位的补全12位if (item.get(CommonConstant.REGION_CODE).toString().length() < CommonConstant.TWELVE_INT) {item.put(CommonConstant.REGION_CODE, StrUtil.padAfter(item.get(CommonConstant.REGION_CODE).toString(), CommonConstant.TWELVE_INT, CommonConstant.ZERO));}if (ObjectUtil.isNotEmpty(sysRegionService.getRegionNameByCode(item.get(CommonConstant.REGION_CODE).toString()))) {item.put(CommonConstant.REGION_NAME, sysRegionService.getRegionNameByCode(item.get(CommonConstant.REGION_CODE).toString()));} else {item.put(CommonConstant.REGION_CODE, CommonConstant.NULL_STR);item.put(CommonConstant.REGION_NAME, CommonConstant.NULL_STR);}}return item;}
}
效果图
相关文章:

SpringBatch+Mysql+hanlp简版智能搜索
资源条件有限,需要支持智搜的数据量也不大,上es搜索有点大材小用了,只好写个简版mysql的智搜,处理全文搜素,支持拼音搜索,中文分词,自定义分词断词,地图范围搜索,周边搜索…...
常见 Web 安全问题
网站在提供便利的同时,也面临着各种安全威胁。一个小小的漏洞可能导致数据泄露、系统瘫痪,甚至带来不可估量的经济损失。本文介绍几种最常见的 Web 安全问题,包括其原理、危害以及防护策略。 一、SQL 注入(SQL Injectionÿ…...
spring切面
概念 两个特点: IOC控制反转AOP主要用来处理公共的代码 例如一个案例就是添加用户,重复的代码包含了记录日志、事务提交和事务回滚等,都是重复的,为了简单,交给AOP来做。 即将复杂的需求分解出不同方面,…...

go语言基础|slice入门
slice slice介绍 slice中文叫切片,是go官方提供的一个可变数组,是一个轻量级的数据结构,功能上和c的vector,Java的ArrayList差不多。 slice和数组是有一些区别的,是为了弥补数组的一些不足而诞生的数据结构。最大的…...

使用 HTML + JavaScript 实现可拖拽的任务看板系统
本文将介绍如何使用 HTML、CSS 和 JavaScript 创建一个交互式任务看板系统。该系统支持拖拽任务、添加新任务以及动态创建列,适用于任务管理和团队协作场景。 效果演示 页面结构 HTML 部分主要包含三个默认的任务列(待办、进行中、已完成)和一个用于添加新列的按钮。 <…...
LangChain核心之Runnable接口底层实现
导读:作为LangChain框架的核心抽象层,Runnable接口正在重新定义AI应用开发的标准模式。这一统一接口设计将模型调用、数据处理和API集成等功能封装为可复用的逻辑单元,通过简洁的管道符语法实现复杂任务的声明式编排。 对于面临AI应用架构选择…...
软件评测师 案例真题笔记
2009 软件测试质量 软件测试质量管理要素包括: •测试过程,例如技术过程、管理过程、支持过程。 •测试人员及组织。 •测试工作文档,例如测试计划、测试说明、测试用例、测试报告、问题报告。 软件测试质量控制的主要方法包括:…...
RAG架构中用到的模型学习思考
前言 RAG(Retrieval-Augmented Generation,检索增强生成)架构结合了检索和生成能力,通过引入外部知识库来提升大语言模型(LLM)的回答准确性和可靠性。以下是RAG架构中常用的模型及其总结: 一、…...

统信 UOS 服务器版离线部署 DeepSeek 攻略
日前,DeepSeek 系列模型因拥有“更低的成本、更强的性能、更好的体验”三大核心优势,在全球范围内备受瞩目。 本次,我们为大家提供了在统信 UOS 服务器版 V20(AMD64 或 ARM64 架构)上本地离线部署 DeepSeek-R1 模型的…...

美尔斯通携手北京康复辅具技术中心开展公益活动,科技赋能助力银龄健康管理
2025 年 5 月 30 日,北京美尔斯通科技发展股份有限公司携手北京市康复辅具技术中心,在朝阳区核桃园社区开展 “全国助残日公益服务” 系列活动。活动通过科普讲座、健康检测与科技体验,将听力保健与心脏健康服务送至居民家门口,助…...
《前端面试题:前端响应式介绍》
前端响应式设计完全指南:从理论到实战 掌握响应式设计是构建现代网站的核心能力,也是前端面试的必考内容 一、响应式设计:移动优先时代的必备技能 在当今多设备时代,用户通过手机、平板、笔记本、桌面显示器等多种设备访问网站。…...

Redis Stack常见拓展
Redis JSON RedisJSON 是 Redis Stack 提供的模块之一,允许你以 原生 JSON 格式 存储、检索和修改数据。相比传统 Redis Hash,它更适合结构化文档型数据,并支持嵌套结构、高效查询和部分更新。 #设置⼀个JSON数据,其中$表示JSON数据的根节点…...

Linux 驱动之设备树
Linux 驱动之设备树 参考视频地址 【北京迅为】嵌入式学习之Linux驱动(第七期_设备树_全新升级)_基于RK3568_哔哩哔哩_bilibili 本章总领 1.设备树基本知识 什么是设备树? Linux之父Linus Torvalds在2011年3月17日的ARM Linux邮件列表…...

12、企业应收账款(AR)全流程解析:从发票开具到回款完成
在商业活动中,现金流如同企业的命脉,而应收管理则是维系这条命脉正常运转的重要保障。许多企业由于对应收账款缺乏有效管理,常常面临资金周转困难的问题。实践证明,建立科学的应收管理体系能够显著提升资金回笼效率,为…...
php 各版本下载
https://windows.php.net/downloads/releases/archives/ 参考资料:php5.6.40 在 win10下安装全过程 ( 图文教程、附官方下载链接 )...

【notepad++】如何设置notepad++背景颜色?
如何设置notepad背景颜色? 设置--语言格式设置 勾选使用全局背景色 例如选择护眼色---80,97,205;...

使用 C++/OpenCV 制作跳动的爱心动画
使用 C/OpenCV 制作跳动的爱心动画 本文将引导你如何使用 C 和 OpenCV 库创建一个简单但有趣的跳动爱心动画。我们将通过绘制参数方程定义的爱心形状,并利用正弦函数来模拟心跳的缩放效果。 目录 简介先决条件核心概念 参数方程绘制爱心动画循环模拟心跳效果 代码…...
Go Modules 详解 -《Go语言实战指南》
Go Modules(简称 go mod)是 Go 官方推出的包依赖管理系统,自 Go 1.11 起引入,Go 1.16 起成为默认方式,取代了旧的 GOPATH 模式。 本章将全面讲解 Go Modules 的基本原理、使用方法以及常见问题处理。 一、Go Modules 简…...

在Oxygen编辑器中使用DeepSeek
罗马尼亚公司研制开发的Oxygen编辑器怎样与国产大模型结合,这是今年我在tcworld大会上给大家的分享,需要ppt的朋友请私信联系 - 1 - Oxygen编辑器中的人工智能助手 Oxygen编辑器是罗马尼亚的Syncro Soft公司开发的一款结构化文档编辑器。 它是用来编写…...
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 腐烂的橘子(力扣200 / 994 )(Go语言版)
🌊 BFS/DFS 实战:岛屿数量 & 腐烂的橘子(LeetCode 200 & 994) 两道图论基础题,涉及 BFS 与 DFS 的应用,主要用于掌握二维网格中遍历与标记访问的技巧: 🏝️ 200. 岛屿数量…...

一、基础环境配置
一、虚拟机 主:192.168.200.200 从:192.168.200.201 从:192.168.200.202 二、docker docker基础搭建,有不会的自行百度。 1.目录结构 /opt/software:软件包/opt/module:解压包,自定义脚本…...

论文阅读笔记——FLOW MATCHING FOR GENERATIVE MODELING
Flow Matching 论文 扩散模型:根据中心极限定理,对原始图像不断加高斯噪声,最终将原始信号破坏为近似的标准正态分布。这其中每一步都构造为条件高斯分布,形成离散的马尔科夫链。再通过逐步去噪得到原始图像。 Flow matching 采取…...

SQL Views(视图)
目录 Views Declaring Views Example: View Definition Example: Accessing a View Advantages of Views Triggers on Views Interpreting a View Insertion(视图插入操作的解释) The Trigger Views A view is a relation defined in terms of…...

「卫星百科」“绿色守卫”高分六号
高分六号(GF-6)是中国高分辨率对地观测系统(高分专项)的重要组成卫星,于2018年6月2日成功发射。高分六号卫星凭借其高时空分辨率、红边波段、宽覆盖能力,在农业、生态、灾害等领域提供了重要的数据支撑。本…...

秋招Day12 - 计算机网络 - IP
IP协议的定义和作用? IP协议用于在计算机网络中传递数据包,定义了数据包的格式和处理规则,确保数据能够从一个设备传递到另一个设备,中间可能经过多个不同的设备(路由器)。 IP协议有哪些作用?…...
Servlet 快速入门
文章目录 概念SpringBoot 测试案例执行原理传统 Servlet在 SpringBoot (嵌入式 Tomcat Spring MVC) 中请求从浏览器到业务代码的完整步骤关键点流程图 参考 概念 运行在服务器端的小程序, Servlet 就是一个接口,定义 Java 类被浏…...

【前端】CSS面试八股
网上现有资料已经很丰富了,我挑了些自己押面试题时总结过的来写。 Q:回流和重绘 A: 回流reflow:计算元素的几何,引发layout重绘repaint:更新元素可见样式,引发paint 回流的成本比重绘高得多&…...
[蓝桥杯]找到给定字符串中的不同字符
题目描述 在不考虑字符排列的条件下,对于相差只有一个字符的两个字符串,实现一个算法来识别相差的那个字符。要求如下: 当传入的字符串为 aad 和 ad 时,结果为 a。 当传入的字符串为 aaabccdd 和 abdcacade 时,结果为…...

Redis底层数据结构之字典(Dict)
Dict基本结构 Dict我们可以想象成目录,要翻看什么内容,直接通过目录能找到页数,翻过去看。如果没有目录,我们需要一页一页往后翻,这样时间复杂度就与遍历的O(n)一样了,而用了Dict我们就可以在O(1)的时间复杂…...

佰力博科技与您探讨低温介电温谱测试仪的应用领域
低温介电温谱测试应用领域有如下: 一、电子材料: 低温介电温谱测试仪广泛应用于电子材料的性能测试,如陶瓷材料、半导体材料、压电材料等。通过该设备,可以评估材料在高温或低温环境下的介电性能,为材料的优化和应用提…...