当前位置: 首页 > news >正文

微服务项目->在线oj系统(Java版 - 3)

相信自己,终会成功

 

目录

管理端代码

管理端管理用户接口

有关token的代码解析 

前缀裁剪原理

管理员登录代码 

题目接口详解

题目相关接口详解

竞赛接口详解

MyBatis分页插件

PageHelper实现

原理分析

Java Stream 流

1. Stream 的核心特点

1. 中间操作(Intermediate Operations)

2. 终端操作(Terminal Operations)

 Elasticsearch

优点

缺点


管理端代码

管理端管理用户接口

管理员登录之后,生成一个token令牌,之后携带令牌调用各种接口

添加用户时,需要先验证用户是否存在,存在的话就会报错

  public String createToken(Long userId,String secret,Integer identity,String nickName,String headInmage){//生成JWT token的方法Map<String,Object> claims=new HashMap<>();String userKey= UUID.fastUUID().toString();claims.put(JwtConstants.LOGIN_USER_ID,userId);claims.put(JwtConstants.LOGIN_USER_KEY,userKey);//            secret定期更换,不能写死,写在nacos上String token= JwtUtils.createToken(claims,secret);//往第三方存储敏感信息 redis//身份认证具体要存储哪些信息表明用户身份字段 identity 1 表示普通用户 2表示管理员用户//但是最好是可扩展的,所以设置为对象//使用什么样的数据结构 String  key-value//key必须保证唯一,便于维护=>统一前缀+logintoken:userId  userId通过雪花算法生成的唯一的字符串//主键若为自增,redis存储所用用户登录信息(包括管理员和C端用户)//hutool提供的工具类(uuid可以理解为全局唯一标识)String tokenkey = getTokenKey(userKey);
//            String key ="logintoken:" + sysUser.getUserId();//记录过期时间,过期时间定义多长 Redis如果能查出来则没过期,不存在则过期LoginUser loginUSer=new LoginUser();loginUSer.setIdentity(identity);loginUSer.setNickName(nickName);loginUSer.setHeadImage(headInmage);redisService.setCacheObject(tokenkey,loginUSer, CacheConstants.EXP, TimeUnit.MINUTES);return token;}

有关token的代码解析 

filter方法,获取http请求,之后调用了ServerHttpRequest.getURI().getPath()方法,其返回值是请求的路径部分。

举个例子,如果完整的请求 URL 是http://example.com/api/users/123,那么getPath()返回的就是/api/users/123

判断这个url是否在白名单中,如果匹配则会直接放行该请求

不会则会继续判断,走到getToken函数  , 从请求头中获取JWR令牌,判断令牌是否存在,并且移除前缀,

replaceFirst方法会用空字符串替换掉第一个匹配到的前缀。

经过这一步处理后,Bearer eyJhbGciOiJIUzI1NiJ9...就会变成eyJhbGciOiJIUzI1NiJ9...

前缀就会被移除了

前缀裁剪原理

  • 正则表达式匹配replaceFirst的第一个参数实际上是一个正则表达式,不过像"Bearer "这样的普通字符串也能被正确匹配。
  • 只替换首个匹配项:该方法只会替换第一次出现的前缀,这样就能避免误处理令牌中可能重复出现的相同字符序列。
  • 空格处理:前缀中的空格很关键,它能确保Bearer和真正的令牌内容分隔开

假设:

  • HttpConstants.PREFIX的值为"Bearer "
  • 客户端发送的请求头是Authorization: Bearer abc123
 

执行过程如下:

  1. 从请求头获取到的原始令牌是"Bearer abc123"
  2. 因为这个令牌以"Bearer "开头,所以会进入if语句块。
  3. 执行replaceFirst后,得到的结果是"abc123"

    之后判断token 是否为空,如果为空抛出异常

    为什么不用 throw new RuntimeException而选择返回特定响应??

    1.响应式编程模型的要求:需通过异步数据流返回结果,而非同步抛出异常。

    2.精确控制 HTTP 响应:直接设置状态码和错误信息,符合业务语义(如 401 Unauthorized)。

    3.避免破坏过滤器链:保证请求处理流程正常终止,不影响其他过滤器逻辑。

    获取token的中间部分( Payload ) ,获取令牌中信息 解析payload中信息 存储着用户的唯一表示信息

    之后获取jwt中的key,判断key是否过期

    如果走到下述代码则说明token正确,且未过期

    LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);
    

    再判断当前请求 请求的时C端功能(只有C端用户可以请求) 还是B端功能(只有管理员可以请求)

    拿出存储信息,判断是管理员还是用户,确实身份后进行延长token(前提token少于规定的时间)

    @Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//获取http请求ServerHttpRequest request = exchange.getRequest();String url = request.getURI().getPath();//请求接口地址 登录接口是否需要身份认证? 否// 跳过不需要验证的路径 接口白名单,通过白名单的接口均不需要进行身份验证if (matches(url, ignoreWhite.getWhites())) {//判断当前接口如果在白名单中则不需要身份认证return chain.filter(exchange);          //ignoreWhite.getWhites():拿到nacos上配置接口地址的白名单}//执行到这里,说明接口不在白名单上,接着进行身份认证//通过token进行身份认证,客户端会把token放在请求头中,需要把token获取出来//从http请求头中获取tokenString token = getToken(request);if (StrUtil.isEmpty(token)) {//为什么不用 throw new RuntimeExceptionreturn unauthorizedResponse(exchange, "令牌不能为空");}Claims claims;try {
    // Payload是JWT中最重要的部分,因为它包含了令牌携带的实际信息,这些信息可以在不接触数据库的情况下验证用户身份和权限
    // 在JWT(JSON Web Token)中,payload是指令牌的中间部分,包含了令牌携带的实际数据(也称为"声明"claims)。claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息 存储着用户的唯一表示信息if (claims == null) {//soringcloud gateway基于webfluxreturn unauthorizedResponse(exchange, "令牌已过期或验证不正确!");}} catch (Exception e) {return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");}
    //        String key ="logintoken:" + sysUser.getUserId();这个方法存储的key,可以这样取值
    //        String userId = JwtUtils.getUserId(claims);
    //        boolean isLogin = redisService.hasKey(getTokenKey(userId));//控制token有效时间,判断redis存储的敏感信息到期,是否存在String userKey = JwtUtils.getUserKey(claims); //获取jwt中的keyboolean isLogin = redisService.hasKey(getTokenKey(userKey));if (!isLogin) {return unauthorizedResponse(exchange, "登录状态已过期");}String userId = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整if (StrUtil.isEmpty(userId)) {return unauthorizedResponse(exchange, "令牌验证失败");}//token正确,并且没有过期//判断redis存储的关于用户身份认证信息是否正确//判断当前请求 请求的时C端功能(只有C端用户可以请求)     还是B端功能(只有管理员可以请求)//拿出存储信息//url.contains,前缀如果是Steam,访问system接口//             前缀如果是friend,访问friend接口LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) &&!UserIdentity.ADMIN.getValue().equals(user.getIdentity())) {return unauthorizedResponse(exchange, "令牌验证失败");}if (url.contains(HttpConstants.FRIEND_URL_PREFIX) &&!UserIdentity.ORDINARY.getValue().equals(user.getIdentity())) {return unauthorizedResponse(exchange, "令牌验证失败");}
    //        将当前用户的ID(userId)存储到线程本地变量(ThreadLocal)中
    //        并以常量Constants.USER_ID作为键名。
    //        如果写在这里,存在线程隔离 common和gateway是两个微服务,
    //        所以换到TokenInterceptor中了//ThreadLocalUtil.set(Constants.USER_ID,userId);//确定身份认证之后,进行时间延长return chain.filter(exchange);}
    

      获取token

    /*** 从请求头中获取请求token*/private String getToken(ServerHttpRequest request) {String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);// 如果前端设置了令牌前缀,则裁剪掉前缀if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);}return token;}

     查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串

       /*** 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串** @param url         指定url* @param patternList 需要检查的匹配规则链表* @return 是否匹配*/private boolean matches(String url, List<String> patternList) {
    //接口匹配if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {return false;}
    //接口地址如果和白名单其中一个地址匹配就返回true
    // 如果遍历完白名单所有地址都没有匹配返回falsefor (String pattern : patternList) {if (isMatch(pattern, url)) {//匹配上了return true;}}return false;}
    

    记录过期时间,过期时间定义多长 Redis如果能查出来则没过期,不存在则过期

     /*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Longtimeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key, Class<T> clazz) {ValueOperations<String, T> operation = redisTemplate.opsForValue();T t = operation.get(key);if (t instanceof String) {return t;}return JSON.parseObject(String.valueOf(t), clazz);}

    管理员登录代码 

    public boolean logout(String token) {//检查token是否为空if (StrUtil.isEmpty(token)) {throw new IllegalArgumentException("token不为null");}//再处理前缀if (token.startsWith(HttpConstants.PREFIX)) {token = token.substring(HttpConstants.PREFIX.length());}return tokenService.deleteLoginUser(token,secret);}

    退出登录代码,主要逻辑是接收一个 token,处理可能存在的前缀,然后调用 tokenService 的 deleteLoginUser 方法删除用户的登录状态

     @Overridepublic boolean logout(String token) {//检查token是否为空if (StrUtil.isEmpty(token)) {throw new IllegalArgumentException("token不为null");}//再处理前缀if (token.startsWith(HttpConstants.PREFIX)) {token = token.substring(HttpConstants.PREFIX.length());}return tokenService.deleteLoginUser(token,secret);}

    题目接口详解

    获取题目列表 

    使用 String.split() 方法按分隔符(通常是 ;)将字符串拆分为数组。

    Constants.SPLIT_SEM 是自定义常量,代表分隔符(如 ";")。

    代码中涉及一个知识点Java Stream 流(下方有介绍)

    excludeIdSet = Arrays.stream(excludeIdStr.split(Constants.SPLIT_SEM)).filter(StrUtil::isNotBlank)  // 过滤空字符串(避免 "1,,2" 的情况).map(Long::valueOf)           // 转为 Long.collect(Collectors.toSet()); // 收集为 Set

    对上述代码详解

    Arrays.stream()将数组转换为流(Stream)

    filter()过滤空字符串(处理 "1;;2" 这种情况)

    StrUtil::isNotBlank引用工具类方法判断字符串非空(来自 Hutool 或类似库)。

    map()将每个字符串元素转换为 Long 类型

    Long::valueOf引用 Long 类的静态方法进行类型转换。

    collect(Collectors.toSet()):将流中的元素收集到 Set 中,自动去重

      @Overridepublic List<QuestionVO> list(QuestionQueryDTO questionQueryDTO) {String excludeIdStr = questionQueryDTO.getExcludeIdStr();Set<Long> excludeIdSet = new HashSet<>(); // 默认空集合if (StrUtil.isNotEmpty(excludeIdStr)) {excludeIdSet = Arrays.stream(excludeIdStr.split(Constants.SPLIT_SEM)).filter(StrUtil::isNotBlank)  // 过滤空字符串(避免 "1,,2" 的情况).map(Long::valueOf)           // 转为 Long.collect(Collectors.toSet()); // 收集为 SetquestionQueryDTO.setExcludeIdSet(excludeIdSet);}//       自动分页:PageHelper 会自动解析你执⾏的 SQL 语句,并根据你提供的⻚码和每页显示的记录数,
    //       自动添加分页相关的 SQL ⽚段,从而返回正确的分页结果。无需在 SQL 语句中手动编写复杂的分页逻辑PageHelper.startPage(questionQueryDTO.getPageNum(),questionQueryDTO.getPageSize());return questionMapper.selectQuestionList(questionQueryDTO);}

    题目相关接口详解

    管理员添加题目 

      @Overridepublic boolean add(QuestionAddDTO questionAddDTO) {
    //        List<Question> questionList=questionMapper.selectList(
    //                new LambdaQueryWrapper<Question>()
    //                        .eq(Question::getTitle,QuestionAddDTO.getTitle()));List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().eq(Question::getTitle, questionAddDTO.getTitle())  // 这里修正); if(CollectionUtil.isNotEmpty(questionList)){throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);}Question question=new Question();//        questionAddDTO:源对象(Source),通常是数据传输对象(DTO),包含前端传入的数据。
    //        question:  目标对象(Target),通常是一个实体类(Entity),用于后续的数据库操作或其他业务逻辑。
    //        将前端传入的 DTO 对象转换为数据库实体对象。对象属性拷贝:避免手动逐个 set 属性,简化代码。// 将DTO属性复制到实体类BeanUtils.copyProperties(questionAddDTO, question);// 插入数据库int insert = questionMapper.insert(question);// 检查插入是否成功if(insert <= 0) {return false;}QuestionES questionES=new QuestionES();//往es(Elasticsearch)中添加BeanUtil.copyProperties(question,questionES);questionRepository.save(questionES);questionCacheManager.addCache(question.getQuestionId());return true;}

    Question::getTitle:通过方法引用指定实体类的字段(对应数据库的 title 列)

    questionAddDTO.getTitle():实际查询的值(来自 DTO 的标题) 

    List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().eq(Question::getTitle, questionAddDTO.getTitle())  // 这里修正); 

    将 questionAddDTO 对象的属性值复制到 question 对象中。

    仅复制属性名相同且类型兼容的字段。

            BeanUtils.copyProperties(questionAddDTO,question);
    

    可能结果:

    存在匹配记录:返回包含所有匹配记录的列表(可能多条,因为标题可能重复)

    无匹配记录:返回空列表 [](不会返回 null

    异常情况:若 SQL 执行出错(如字段名错误、数据库连接问题),抛出异常。

    下方有关于ES的介绍 

    // 创建ES文档对象
    QuestionES questionES = new QuestionES();
    // 将数据库实体属性复制到ES文档对象
    BeanUtil.copyProperties(question, questionES);
    // 保存到Elasticsearch
    questionRepository.save(questionES);

     删除题目数据

     @Overridepublic int delete(Long questionId) {Question Question=questionMapper.selectById(questionId);if(Question==null){throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);}//在es中删除数据questionRepository.deleteById(questionId);questionCacheManager.deleteCache(questionId);return questionMapper.deleteById(questionId);}

     这个是实现题目的接口,至于edit和detail没有涉及特殊知识点就不介绍了

    public interface IQuestionService {List<QuestionVO> list(QuestionQueryDTO questionQueryDTO);boolean add(QuestionAddDTO questionAddDTO);QuestionDetialVO detail(Long questionId);int edit(QuestionEditDTO questionEditDTO);int delete(Long questionId);
    }
    

    竞赛接口详解

    添加题目接口

    从前端拿到数据,checkExamSaveParams进行判断

    在数据库中查询内容

    检查考试标题是否重复(排除当前考试) , 验证开始时间和结束时间是否存在

    验证开始时间是否早于当前时间 , 验证开始时间是否早于结束时间

    如果没有出异常信息则正常,将前端拿到的数据复制到exam中,进而添加到数据库中

     @Overridepublic String add(ExamAddDTO examAddDTO) {checkExamSaveParams(examAddDTO,null);Exam exam = new Exam();BeanUtils.copyProperties(examAddDTO, exam);examMapper.insert(exam);
    //返回新创建考试的ID字符串形式return exam.getExamId().toString();}
    private void checkExamSaveParams(ExamAddDTO examSaveDTO,Long examId) {List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>().eq(Exam::getTitle, examSaveDTO.getTitle()).ne(examId!=null,Exam::getExamId,examId));if (CollectionUtil.isNotEmpty(examList)) {throw new ServiceException(ResultCode.EXAM_RESULT_REPEAT);}if (examSaveDTO.getStartTime() == null || examSaveDTO.getEndTime() == null) {throw new ServiceException(ResultCode.EXAM_START_EXAM_TIME_NOT_EXIST);}if (examSaveDTO.getStartTime().isBefore((LocalDateTime.now()))) {throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);//竞赛开始时间不能早于当前时间}if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);//竞赛开始时间不能早于竞赛结束时间}}

    在书写竞赛接口时需要注意

    检查考试标题是否重复(排除当前考试) , 验证开始时间和结束时间是否存在

    验证开始时间是否早于当前时间 , 验证开始时间是否早于结束时间

    添加题目时,注意竞赛是否已经发布,如果已经发布则禁止编辑,在添加题目的时候,可以限制添加题数

    发布和未发布就是需要把状态修改一下(数据库中内容也需要更新),在添加题目时,注意这个题目是否已被添加,如果以备添加,就不需要显示了

    给竞赛添加题目,详情看注释(添加题目顺序)

     @Overridepublic boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {//判断竞赛是否存在Exam exam = getExam(examQuestionAddDTO.getExamId());checkExam(exam);if(Constants.TRUE.equals(exam.getStatus())){throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);}// 2. 从DTO中获取题目ID集合Set<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();// 3. 如果题目ID集合为空,直接返回trueif (CollectionUtil.isEmpty(questionIdSet)) {return true;}// 4. 根据ID集合批量查询题目List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);// 4. 根据ID集合批量查询题目if (CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXIST);}return saveExamQuestion(exam, questionIdSet);}
    
    private boolean saveExamQuestion(Exam exam, Set<Long> questionIdSet) {// 初始化题目序号int num = 1;// 创建考试题目关联列表List<ExamQuestion> examQuestionList = new ArrayList<>();// 遍历题目ID集合for (Long questionId : questionIdSet) {ExamQuestion examQuestion = new ExamQuestion();// 设置考试IDexamQuestion.setExamId(exam.getExamId());// 设置题目IDexamQuestion.setQuestionId(questionId);// 设置题目顺序(自动递增)examQuestion.setQuestionOrder(num++);// 添加到列表examQuestionList.add(examQuestion);}//批量操作的方式return saveBatch(examQuestionList);}
    

    MyBatis分页插件

    分页是数据处理中的基础功能,良好的分页机制能显著提升系统性能和用户体验。以下是对分页管理机制的深度分析。

    基本参数设计

    参数名说明推荐默认值
    pageNum当前页码(从1开始)1
    pageSize每页记录数10-50(视业务而定)
    total总记录数-
    pages总页数自动计算
    orderBy排序字段(可选)"create_time desc"

    逻辑分页(内存分页) 

    // Java示例:使用List.subList
    List<Data> allData = getFromCacheOrDB();
    List<Data> pageData = allData.subList(start, end);
    • 优点:实现简单,适合小数据量

    • 缺点:数据量大时内存消耗高

    • 适用场景:本地缓存数据分页

     物理分页(数据库分页)

    -- MySQL
    SELECT * FROM table LIMIT 10 OFFSET 20;-- Oracle
    SELECT * FROM (SELECT t.*, ROWNUM rn FROM table t WHERE ROWNUM <= 30
    ) WHERE rn > 20;
    • 优点:性能高,节省内存

    • 缺点:SQL语法差异大

    • 适用场景:大数据量查询

    主流技术实现方案

    PageHelper实现
    // 1. 引入依赖
    <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>最新版</version>
    </dependency>// 2. 使用示例
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    PageInfo<User> pageInfo = new PageInfo<>(users);
    原理分析
    1. 通过ThreadLocal绑定分页参数

    2. 自动改写SQL添加方言特定的分页语句

    3. 执行count查询获取总数

    优化手段实现方式适用场景
    延迟关联先查ID再关联详情多表关联查询
    游标分页使用WHERE id > lastId LIMIT size无限滚动列表
    缓存总条数缓存count结果,设置短过期时间数据变化不频繁
    避免count查询仅"下一页"按钮时不做count移动端分页

    Java Stream 流

    Stream(流) 是 Java 8 引入的一个强大的数据处理 API,它允许你以声明式方式(类似 SQL 语句)对集合(Collection)、数组等数据进行高效、简洁的操作(如过滤、映射、排序、聚合等)。

    Stream 的核心作用是简化集合操作、提升代码可读性,并通过惰性求值(中间操作(如 filtermap)不会立即执行,直到遇到终端操作(如 collectforEach),避免不必要的计算)并行处理优化性能。

    1. Stream 的核心特点

    • 非数据结构:不存储数据,只是数据源的视图

    • 函数式编程:支持 lambda 表达式和方法引用

    • 惰性执行:中间操作不会立即执行

    • 可消费性:流只能被消费一次

    2. Stream 的操作类型

    Stream 的操作分为 中间操作(Intermediate) 和 终止操作(Terminal)

    操作类型

    说明

    示例方法

    中间操作

    返回新的 Stream,可链式调用

    filter()map()sorted()distinct()limit()

    终止操作

    触发计算并返回结果(非 Stream)

    collect()forEach()count()reduce()anyMatch()

    1. 中间操作(Intermediate Operations)

    操作说明示例
    filter过滤元素.filter(s -> s.length() > 3)
    map元素转换.map(String::toUpperCase)
    flatMap扁平化流.flatMap(list -> list.stream())
    distinct去重.distinct()
    sorted排序.sorted(Comparator.reverseOrder())
    peek查看元素但不修改.peek(System.out::println)
    limit限制元素数量.limit(10)
    skip跳过前N个元素.skip(5)

    2. 终端操作(Terminal Operations)

    操作说明示例
    forEach遍历每个元素.forEach(System.out::println)
    collect收集为集合.collect(Collectors.toList())
    toArray转为数组.toArray(String[]::new)
    reduce归约操作.reduce(0, Integer::sum)
    min/max查找最小/最大值.max(Comparator.naturalOrder())
    count统计元素数量.count()
    anyMatch任意元素匹配.anyMatch(s -> s.startsWith("a"))
    allMatch所有元素匹配.allMatch(s -> s.length() > 2)
    noneMatch没有元素匹配.noneMatch(Objects::isNull)
    findFirst返回第一个元素.findFirst()
    findAny返回任意元素.findAny()

     Elasticsearch

    Elastic Docs | Elastic官方网站:

    Elastic Docs | Elastic

    Elasticsearch(简称 ES)是一个开源的分布式 搜索和分析引擎,基于 Apache Lucene 构建,专为处理海量数据设计,支持近实时(NRT, Near Real-Time)搜索。

    优点

    • 高性能搜索,支持复杂查询(全文检索、模糊匹配、聚合分析)。

    • 水平扩展能力强,适合大数据场景。

    • 生态完善(ELK Stack、APM、SIEM 等)。

    缺点

    • 不支持事务(不适合金融级一致性要求场景)。

    • 资源消耗较高(尤其是内存)。

    • 学习曲线较陡(需理解分词、映射、集群管理等)

    术语说明
    Index(索引)类似数据库中的“表”,存储相关文档(如 user_indexproduct_index)。
    Document(文档)索引中的基本数据单元,使用 JSON 格式存储(如一条用户信息)。
    Type(类型)7.x 版本后已弃用,现一个索引仅支持一种类型(_doc)。
    Shard(分片)索引被拆分为多个分片,提高并发和存储能力(如一个索引拆分为 5 个分片)。
    Replica(副本)分片的备份,提高容错能力(如每个分片有 1 个副本)。
    Node(节点)一个 Elasticsearch 运行实例,多个节点组成集群(Cluster)。
    http://www.xdnf.cn/news/519229.html

    相关文章:

  4. 王树森推荐系统公开课 排序02:Multi-gate Mixture-of-Experts (MMoE)
  5. 【AI面试秘籍】| 第15期:大模型如何稳定输出合法JSON?
  6. 【Linux笔记】——线程同步条件变量与生产者消费者模型的实现
  7. GEE谷歌地球引擎批量下载逐日ERA5气象数据的方法
  8. 等于和绝对等于的区别
  9. LeetCode 394. 字符串解码详解:Java栈实现与逐行解析
  10. 第5章 监控与回归测试:日志收集 · 代码覆盖率 · 静态分析 · 质量门
  11. Python爬虫实战:通过PyExecJS库实现逆向解密
  12. 院士方复全数学命题证明采用预期理由和循环论证以及类比的错误方法
  13. web页面布局基础
  14. 【动态规划】路径问题
  15. STM32八股【9】-----volatile关键字
  16. vim - v
  17. Python数据可视化 - Pyecharts绘图示例
  18. 中级统计师-统计学基础知识-第三章 参数估计
  19. 【Linux】命令行参数和环境变量
  20. 【PyQt5实战】五大对话框控件详解:从文件选择到消息弹窗
  21. 【typenum】 11 私有模块(private.rs)
  22. 【Redis实战篇】Redis消息队列
  23. 10.9 LangChain LCEL革命:43%性能提升+声明式语法,AI开发效率飙升实战指南
  24. 深入理解递归算法:Go语言实现指南
  25. C44-练习
  26. 全基因组关联研究揭示了脑淋巴活动的机制
  27. Rstudio换皮:自定义彩虹括号与缩进线
  28. Python Requests库完全指南:从入门到精通
  29. 《C语言中的传值调用与传址调用》
  30. 多头自注意力机制—Transformer模型的并行特征捕获引擎
  31. 如何畅通需求收集渠道,获取用户反馈?
  32. c++多线程debug
  33. 【android bluetooth 协议分析 01】【HCI 层介绍 6】【WriteLeHostSupport命令介绍】