智慧在线判题OJ系统项目总体,包含功能开发思路,内部中间件,已经部分知识点

发布于:2025-05-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

回顾一下xml文件怎么写

哪个地方使用了哪个技术 

MyBatis-Plus-oj的表结构设计,

管理员登录功能 

Swagger

Apifox​编辑

BCrypt

日志框架引入(slf4j+logback)

nacos

Swagger无法被所有微服务获取到修改的原因

身份认证三种方式:

JWT(Json Web Json,一种基于JSON的安全跨平台信息传输格式,JWT(定义了一种紧凑且自包含的方式)用于各方间安全传输信息,此信息可以用于验证和相互信任。

缺点:

改良

Redis

RedisTemplate

Entity

DTO

VO

RouterLink组件

RouterView组件

VUE规则

Axios(代替ajax)

B端功能(定义接口请求方式Get(查询),Post(新增),Put(修改),Delete(删除)

PageHelper

Vue声明周期函数

C端功能流程

阿里云密钥(阿里云短信登录)

核心是使用redis来模拟

Jmeter

XXL-job

竞赛报名功能

TransmittableThreadLocal

我的竞赛功能

 题目详情缓存  (引入ES)

ES是什么

ES写入提高效率

ES的全文检索

代码沙箱(判题功能)

因此引入MQ

用户拉黑功能

引入注解

引入切面类

我的消息功能

消息发送实现:

竞赛排名功能

临时记录

Nginx

正向代理

反向代理

负载均衡

动静分离

题目的顺序列表(用户端的前一题和后一题,怎么做)

2025年5月13日 面试,某某某某达

ES怎么做添加


回顾一下xml文件怎么写

 namespace=xxx映射命名空间,xml对应的java(Mapper)接口

<mapper namespace="com.bite.system.mapper.exam.ExamMapper">

resultType指,映射到ExamVO这个类中

使用#{}接收传递过来的参数

<select id="selectExamList" resultType="com.bite.system.domain.exam.vo.ExamVO">

  select
    te.exam_id,
    te.title,
    te.start_time,
    te.end_time,
    te.create_time,
    ts.nick_name as create_name,
    te.status
    from
    tb_exam te
    left join
    tb_sys_user ts
    on
    te.create_by = ts.user_id
    <where>
//假如有一个有时候存在,有时候不存在则使用这个if,在where里面使用
<if  test="title!=null and title!=''">
AND title like CONCAT('%',#{title},%)      进行一个模糊查询
</>
        <if test="title !=null and title !=''">
            AND title LIKE CONCAT('%',#{title},'%')
        </if>
        <if test="startTime !=null and startTime !='' ">
            AND te.start_time >= #{startTime}
        </if>
        <if test="endTime !=null and endTime !='' ">
            AND te.end_time &lt;= CONCAT(#{endTime},'23:59:59.999')
        </if>
    </where>
    ORDER BY
     te.start_time DESC
</select>

接口文档,接口的说明文档

作用:简化前端开发,易于错误处理,代码可维护性,文档化

//请求方法和请求参数 先是

Authorization(奥丝ruai贼神)

接口概述,接口地址,请求方法,请求参数,相应数据,请求和相应示例

HTTP协议:1xx信息.表示临时相应并且需要请求者继续执行操作

2XX 成功。操作被成功接受并处理

3xx重定向,表示客户端必须执行一些其他操作才能完成其请求

4xx客户端错误,请求包含语法错误或者无法完成请求

5xx服务器错误,这些错误可能是服务器本身的错误,而不是请求出错

this(这个/自身)

+this,调用自身的属性和方法

什么时候使用this,当传入的参数名字和你的属性名字一样时候,就使用this。

this(),调用自己的构造方法,必须要放到首行

MyBatis-Plus-oj的表结构设计,

 

之间无脑快速安装版本简单易操作

数据库连接池:借助mybatis-plus操作数据库虽然给我们很大便捷,但是这样方式操作会导致一些问题

频繁的创建来哪和销毁连接:包括TCP层的握手和Mysql协议的握手,会消耗大量时间

连接数不受控制:在业务流量高峰期,大量应用服务器可能同时请求数据库连接,而数据库能够承载的连接数目有限,这可能导致数据库性能降低

连接管理困难:开发者需自行管理数据库连接的创建,使用和关闭,这增加了开发的复杂性和出错的可能性。

数据库连接池用来解决这些问题:

提供统一管理:数据库连接池对数据库连接创建等操作的统一管理

提高系统性能:由于创建和销毁数据库连接需要消耗时间和系统资源,如果每次进行数据库操作都不断销毁,创建会影响性能使用连接池可以复用已经创建好了连接,大大提高系统的性能

提高资源利用率:连接池通过复用已有连接,避免了频繁创建和销毁连接带来的资源浪费,提高了系统资源利用率

提高系统稳定性:数据库连接池可以有效控制系统中并发访问数据库的连接数量,避免因并发连接数过多导致数据库崩溃,同时连接池还会定时检查连接的有效性,自动替换掉无效连接,保证了系统的稳定性

常见的:C3P0,Druid,HikariCP

为什么使用HikariCP(controation pool)

高性能:HikariCP是一个高性能的数据库连接池,他提供了快速,低延迟的连接获取和释放机制,在SpringBoot项目中,这可以显著提高应用程序的响应速度和吞吐量,特别是在高并发场景

资源优化:HikariCP对资源的使用进行精细的管理和优化,他采用一种内存效率极高的数据结构来存储和管理连接,减少了内存占用和垃圾回收的压力,还减少了不必要的线程和锁竞争

配置灵活:HikariCP提供了丰富的配置选项,允许开发者根据项目的具体需求进行微调

与SpringBoot集成良好:SpringBoot对HikariCp提供了良好的支持,可以轻松过集成

线程假如满了,超时就抛弃,假如空闲时间,没有使用那个临时线程,那么就会销毁这个线程

管理员登录功能 

表结构设计:满足需求,避免冗余设计,考虑今后发展

1.导入依赖,mapper去继承这个类,

 2.可以理解为接受的对象,就是和数据库表对应的,然后我们填入那个TableId的名字

3.对应的Service,找到你想要的方法,去实现,假如不是那个查找全部列表就lambda

比如这种,使用lambda表达式,传入的参数填入lambda,需要xx使用xx来查

Swagger

swagger是否是一个公共的呢,没啥难度,引入就直接操作啦

Apifox

BCrypt

哈希加密算法,被广泛应用于存储密码和进行身份验证,并且BCrypt算法每次计算都会先生成一个随机盐值,与用户密码一起参与计算最终得到一个加密的字符串,由于生成盐值随机的,所以每次使用相同的密码得到结果也不相同,这样有效防止攻击者破解密码

日志框架引入(slf4j+logback)

1.重要性

故障的排查和问题定位

系统监控

数据采集

日志审计

2.注意事项

注意日志级别

注意日志内容,日志格式和可读性

注意日志的滚动和归档

为什么选择slf4j+logback

易于切换,配置灵活

logback性能更好,集成更方便,功能更强大

SpringBoot默认的日志框架

每次修改一个配置都需要重新打包上线,团队的协作比较困难,环境隔离不足,开发,测试,生产

nacos

配置MYSQL数据库

  1. 数据持久性:使用MYSQL作为外置数据库可以确保数据被持久化存储,这对于确保服务的稳定性和数据的可靠性非常重要
  2. 高可用性:NACOS支持集群部署,使用MYSQL作为共享的数据存储可用确保集群中各个节点的数据一致性,此外MYSQL自身也支持高可用和故障转移,使用主从复制或者集群解决方案,从而进一步提高系统可用性。
  3. 性能优化:使用nacos内置的数据库,虽然能简化部署,但是性能收到限制,外置的MYSQL可用根据需要进行优化和扩展,满足更高的性能需求
  4. 易于管理和维护:我们系统本身用MYSQL,nacos外置数据库和我们系统采用同样的数据库库,可以保证技术使用的统一,简化了数据库的管理和维护工作,降低运维成本。

这里我们项目分为三类

1000 SUCCESS      操作成功

2000 ERROR 服务器繁忙请稍后重试(前端根据错误码显示,服务器繁忙,请稍后重试)

3000 操作失败,但是服务器不存在异常

3001 未授权

3002 参数校验失败

3003资源不存在

3004资源已经存在

3101用户已经存在

3102用户不存在

3103用户或者密码错误

3104 你已被列入黑名单,请联系管理员

Swagger无法被所有微服务获取到修改的原因

我们想把Swagger进行一些修改,可是发现为什么改不了呢

(org.springframework.boot.autoconfigure.AutoConfiguration.imports,明明这个已经写了,还是在那个目录里面)

这个是因为你的META-INF.spring,是一步创建的,正常来说这个应该是先创建INF,再去创建spring,由IDEA来合并,可不是你自己就去合并了,这就是导致不能被访问的原因。

身份认证三种方式:

  1. 基于Session的身份认证:这个是最常见的身份认证方式,当用户首次登录的时候,服务器会将用户信息存入session并生产一个唯一的SessionID,然后返回给客户端,此后的请求客户端会要携带这个Session ID,服务器通过Session ID有效性来判断用户身份
  2. 基于OAuth身份认证:OAuth认证机制是一种安全,开发且简易的授权标准,他允许用户授权第三方应用访问其账户资源,而无需向这些应用提供用户名和密码,如微信,QQ登录其他网站
  3. 基于Token的身份认证,这种方式,服务器在用户登录过后,会返回一个Token给客户端,客户端每次请求资源,都在请求头上携带Token,服务器通过验证Token的有效性来判断用户的身份,这种方式常见于前后端分离的架构中,如JWT进行身份验证。

JWT(Json Web Json,一种基于JSON的安全跨平台信息传输格式,JWT(定义了一种紧凑且自包含的方式)用于各方间安全传输信息,此信息可以用于验证和相互信任。

由三部分组成:头部,载荷,签名

头部(header):包含令牌类型和使用方法

载荷(payload):包含用户信息和其他元数据(使用base编码)

签名(signatiure):用于检验令牌的完整性和真实性

签名算法{

base64(header+base64编码(payload)

}

客户端使用用户名跟密码请求登录

服务端收到请求,去验证用户名和密码

验证成功后,服务端签发一个Token,再把这个Token发送客户端(token上述的jwt串)

客户端收到Token,会把他存储起来

每次客户向服务端请求资源时候,带着Token

服务端收到请求,验证客户端里面带着的Token,如果验证成功,向客户端返回请求的数据。

缺点:

JWT的payload没有加密,假如token泄漏,那么用户的信息就完全暴露了,所以完全不能存敏感数据。(用户手机号,银行卡号啥的)

jwt,无状态,假如想要修改其中的内容,就要重新签发一个新jwt

无法延长jwt的过期时间。

改良(JWT+Redis)

1.payload不存敏感信息,解决:仅仅存储用户的唯一标识信息。

第三方存储敏感信息(根据唯一标识信息,从第三方机制查处相应敏感信息

存储的查询性能要高,并且不需要长期存储

2.用户修改了个人信息之后,jwt不变。

满足第一点后,相当于就是两个分开了,我只是改了第三方敏感信

3.控制jwt的过期时间

不能再使用JWT提供的过期时间的参数。

通过第三方记录jwt的过期时间,并且支持过期时间的修改,最好还有第三方工具提供存储功能    

- Redis

当账号和密码匹配成功之后,生成JWT

redis(表明用户身份字段,加上前缀就是)

logintoken+雪花id/或者(假如我们不用雪花id,没这个条件,只可以自增ID,B,C表全是自增的,肯定会重复,引入hutool工具包的uuid,),存储的对象是用的里面fastjson序列化器,

把一个对象存入redis(设置过期时间哈要)

假如720分钟之后,用户拿着过期的token,我怎么查找出来呢?,我直接通过redis值判断

token存的是自增主键和uuid

y用户携带我们给他返回的token,向我们服务器再次发送请求的时候,我们调用parse方法解析(传入token和secret盐值),解析后得到这两个值,我去拿到redis中,去进行一个查询,如果查询到我们之前存的数据key是下面的,然后value有东西就可以

key  //LoginToken+userId (因为UserId本身就是唯一的,通过雪花算法) 

value:用户身份(管理员/用户) nickName昵称,headImage头像

Redis

jwt+Redis实现身份认证机制。

为什么要封装Service

package com.bite.common.redis.config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
   // @ConditionalOnSingleCandidate     初始化这个Bean对象
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory
                                                                   connectionFactory) {
        //new对象
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //对属性进行设置,和redis简历连接
        template.setConnectionFactory(connectionFactory);
        //new的一个自定义序列化器
        JsonRedisSerializer serializer = new JsonRedisSerializer(Object.class);
// 使⽤StringRedisSerializer来序列化和反序列化redis的key值      key和value的序列化器是不同的
        template.setKeySerializer(new StringRedisSerializer());
        //我们一般对于key,还是哈希的key,都是采用String 类型,所以说,用String类型序列化器完全搞定
        //但是我们的value可能是存放具体的对象,对象的需要序列化,把序列化结果存起来,使用fastjson
        template.setValueSerializer(serializer);
// Hash的key也采⽤StringRedisSerializer的序列化⽅式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        //完成设置后,对于后续的初始化操作
        template.afterPropertiesSet();

        return template;
    }
}

直接用redisTemple,他的内部有很多bean对象,通过bean去调用方法进行操作,使用一个redisService(进行一个封装),使代码和具体第三方实现解耦

抽象于解耦合:封装第三方组建可以提供一个更加高级的抽象层,使得你的代码于具体的第三方实现解耦合,如果未来更换第三方组件,或者调整其配置,你只需要对封装的Service层,而不用修改整个应用中的大量代码

统一接口:多个三方提供相似功能,api与用法各自不同,通过封装提供统一接口,使开发者不用关注底层工具的具体差异

扩展性:更容易为第三方工具添加额外的功能和逻辑

错误处理和异常处理:可以统一封装第三方的特定异常或者错误

代码可读性和可维护性:使用封装的service可以使代码更加清晰,容易理解

RedisTemplate

Boolean haskey(String key)判断key是否存在

unit是时间单位

boolean expire(final String key ,final long timeout,finalTimeUnit unit ){} 设置有效时间 

getExpire(final String key,final TimeUnit unit){}

deleteObject(String key)删除单个key

//缓存基本的对象,Integer,String 实体类等。

setCacheObject(final String key,final T value 缓存基本的对象,Integer,String,实体类等)

getCacheObject(final String key,Class<T>clazz)获得缓存基本对象

getListSize(final String key)获得List中存储数据数量


Entity

与数据库表中的字段--对应的实体类

他与数据库表一一对应,用于持久化数据

DTO

接受前端的传递的数据

DTO(Data Transfer Object,数据传输对象),通常是轻量级的,只包含需要传输的数据(只传递需要的数据,比如登录人,登录密码(对应的数据库的ID,我们不去传递)

VO

VO(View Object 视图对象),用于在展示层显示数据,通常是将表示数据的实体对象中的一部分属性进行选择性的组合形成一个新的对象,目的是为了满足展示层数据要求的特定数据结构。      (比如新增或者删除数据,我们不会把其中的更新时间,更新人放出来)

区分的目的:

提高代码可读性和可维护性:每个对象都有特定的职责,使得代码结构更加清晰,每个对象统一命名,项目变得更加一致和可预测

解耦:Entity,DTO,VO的划分降低了各个部分的耦合度,修改某一层逻辑或数据时减少对其他层的影响

优化性能:不同的对象,比如DTO和VO可以根据自身功能和当前需求,进行裁剪和拼装,合理利用网络传输资源,提高性能

但是假如我们划分过细的话

增加复杂性:过多的概念划分可能导致开发者需要花费更多时间去理解每个对象的作用和用途,增加了学习和理解的难度,导致开发的效率降低,甚至引入不必要的错误。

过度设计/影响性能:过度设计意味着简单功能时引入了过多抽象和复杂性,增加开发成本,和对象转化与数据处理直接有性能开销(比如DTO需要转化成实体)

维护成本上升:对象划分的增多,对象数量增加,导致代码库的膨胀

耦合度增加:对象划分为了降低耦合度,过细划分导致耦合上升,因为创建过多的中间层或者转化层来处理对象之间不同的交互,导致系统各个部分之间的依赖关系

RouterLink组件

生成可点击的连接,用于页面之间的跳转,RouterLink通过其to属性指定链接的目标地址,当用户点击这些链接时候,路由会自动切换对应的页面

RouterView组件

用于根据当前路由状态动态渲染匹配的组件,在单页应用中,URL发生变化时候,RouterView会根据当前路由状态自动渲染对应组件,这意味着无论导航到哪里,RouterView都会显示与当前路由相匹配的组件内容

VUE规则

assets目录结构:用于存储项目中需要导入的静态资源:如图片

views:views目录通常存放安歇路由直接相关的页面级别的组件

components:components目录通常用于存放那些可复用的,小巧的,功能单一的组件,组件通常不会被直接渲染到路由对应页面上

router:存放Vue Router的配置文件

项目工程名:全部小写方式

目录和文件夹:全部小写,短横线分隔

js文件:全部小写,优先选择单个单词命名

1.前端接受用户输入信息(账号,密码)

2.携带用户输入的信息,向后端发起登录请求

3.后端接收到前端的请求,执行登录相关的业务逻辑

4.后端执行完相关的逻辑之后,将结果返回前端

5.前端接受到后端返回的结果之后,如果登录成功,则跳转到后台管理页面,并且前端需要将后端返回到token存储起来,假如登录失败,前端将后端返回错误提示,展示给用户。

v-model是Vue中用于输入元素和Vue实例数据直接创建双向数据绑定的指令,他使得数据和DOM之间能够相互同步,当输入框的内容发生变化时,Vue实例中的数据也会自动更新,反之,当Vue实例中的数据发生变化时,输入框内容也会相应更新。

Axios(代替ajax)

1.前端携带token向后端发起请求

2.收到后端响应后,前端应该根据后端返回的结果进行一个判断,假如成功,从响应结果中获取用户昵称并且展示,如果失败,将失败的原因展示给用户

响应式数据:当数据变化时候,可以自动更新和通知与之相关的视图和组件

前端发送请求时候,需要携带token.,退出登录时候,怎么让token认证失败呢?我可以删除token,或者加加减减,反正只要不相同就行(把redis用户睡觉删除掉就行,让token认证失败)(失败就保持登录状态,停留当前页面,假如成功,就去执行后续流程)

前端清除存储的token,返回登录页面

1.点击退出登录,弹出确认框

2.携带者token(请求头中)向后端发起请求

3.前端接受到后端的响应结果之后,如果成功,清除掉cookie存储token,并且跳转回登录页面,如果失败,将失败原因提示给用户,停留给当前页面

1.非登录状态,访问除了登录页以外的页面,自动跳转回登录页

2.已经登录过,并且token没有过期,此时访问登录页面应该自动跳转到后台管理页面

B端功能(定义接口请求方式Get(查询),Post(新增),Put(修改),Delete(删除)

登录模块

题库管理-难度,题目搜索,支持分页等 -考虑表结构等设计(

题目添加

1.我们登录后需要点击题目管理

2.点击添加题目之后,弹出抽屉

3.添加一些选项,比如难度,标题,内容,默认代码块,main函数,输入相关题目信息,点击发布

4.向后端发起添加题目的请求(将用户携带的题目信息作为参数发送添加题目的请求)

5.后端收到请求之后,对请求参数进行处理

6.对数据存储起来,往mysql数据库里面存储等

7.后端需要将题目添加的结果返回给前端

8.前端收到后端的响应(如果成功,提示用户添加成功,并且在列表中,将新增的题目展示在第一个,如果失败,把失败原因提示给用户)

竞赛管理 (携带token向后端发送请求,发送后请求之后,等待后端处理,前端等待处理,直到前端接受到数据,根据响应结果,如果成功,将题目列表数据展示在页面中,如果失败,向用户提示错误信息,页面保持原样即可

题目编辑功能:

1.找到要编辑的题目,找到之后,点击编辑按钮,

2.携带着题目id向后端发起题目详情的请求

3.后端接受到题目详情的请求之后,根据题目查询出题目详情(从数据库中查询出来),并且将查询结果返回给前端展示(如果查询失败,编辑题目流程结束)

4.根据展示出来的题目详情和对于题目的修改需要,对题目的具体修改,点击发布操作

5.前端携带着题目id,和修改后的内容,向后端发起编辑题目的请求。

6.后端收到编辑请求之后,根据题目id查到对应的题目,在根据修改后的题目内容对题目进行修改。并且将修改后的结果返回给前端.

7.前端接收到后端返回的响应结果之后,成功->提示用户编辑成功,并且再去查看信息时候应该是修改之后的,失败则返回失败原因.

题目删除:

1.找到要删除的题目,并且点击删除按钮

2.前端携带者题目id向后端发起删除题目的请求

3.后端接收到请求之后,根据题目id删除题目,并将删除的结果返回给前端(成功或者失败)

4.前端接受后端响应之后,如果成功,提示用户删除成功,将删除的题目从题目列表删除 假如失败,提示用户失败原因

竞赛列表功能:

1.用户切换到竞赛管理

2.前端携带着相关参数发起获取竞赛列表的请求

3.后端收到请求之后,根据请求参数,从数据库中查询符合条件的竞赛.并将查询结果返回给前端

4.前端接收到后端返回的响应之后,如果成功-把查询到的列表数据展示到页面当中

如果失败,提示用户失败的原因

题目新增:

一.不包含题目的新增,我可以先不去包含题目,先去考虑别的,那么此时我们不予许他发布就行,就像是这个csdn一样(不允许发布)

1.前端带着用户输入的竞赛基本信息,向后端发起添加竞赛请求,

2.后端接收到请求之后,对相关参数进行处理,将竞赛信息存入数据库,并且将添加结果返回前端(成功/失败)

3.前端接收到后端的响应之后,如果成功,提示用户添加成功,并且竞赛列表中能看到新增的竞赛,如果失败,提示用户失败的原因。

二.包含题目新增 =新增不包含题目都竞赛+为这个竞赛提供题目(假如我们不保存竞赛,就可以添加题目(假如用户添加了100+题目,然后一个刷新(此时全毁了就,所以不能让他直接点击添加按钮

1.先走上面那三步(不包含题目的新增),

2.点击添加题目按钮,获取题目列表的请求。(请求之前实现的题目列表接口即可)

3.从题目列表中,选择题目,点击提交按钮,携带着竞赛id和题目id发起为竞赛添加题目的请求

4.后端收到请求后,处理相关参数

竞赛编辑功能:竞赛详情功能+

竞赛详情功能:

1.携带着竞赛id向后端发起获取竞赛详情的请求的

2.后端接收到请求之后,根据竞赛id从数据库中查询竞赛详情信息,并将查询结果返回给前端

3.前端接收到后端响应之后,如果成功,页面展示竞赛详情信息,如果失败,提示用户失败原因。

竞赛编辑功能:竞赛详情功能+竞赛基本信息进行编辑(可以不去编辑)+竞赛题目信息进行编辑(又可能不去编辑)

竞赛基本信息编辑:(针对输入框或者选择框进行编辑)

1.输入或者选择更新后端的数据,点击保存,前端携带着更新后的数据向后端发起编辑竞赛的基本信息的请求,

2.后端接收到请求之后,根据竞赛id找到要编辑的竞赛,根据请求参数对竞赛数据进行更新,并且将更新结果要同步到数据库中,并且将编辑结果返回前端。

3.前端接收到后端响应之后,

如果成功,展示竞赛编辑后到信息,并且返回题目列表是看到的题目信息也是更新后得。如果失败,则提示用户失败原因,比拿给所展示的信息还是之前题目的信息。

竞赛中题目信息的编辑功能:竞赛中题目添加功能+竞赛中题目删除功能

竞赛中题目删除功能:

1.找到竞赛中要删除的题目,点击删除按钮,前端向后端发起删除竞赛中题目的请求,携带(参数-看后端要什么你传什么,删除的话应该是id)

2.后端接收到请求之后,查出要删除的竞赛,并且判断是否能够进行操作,如果不能操作,返回前端不能操作的原因,如果能操作,开始进行竞赛中的题目删除操作。根据题目id找到要被删除的题目进行删除操作。删除数据库中对应的竞赛中的题目数据。

3.前端接收响应,如果成功,竞赛题目列表中对应的题目将消失

                               如果失败,提示失败原因。

竞赛删除=删除竞赛的基本信息(tb_exam)+删除竞赛对应的题目信息(tb_exam_question)

1.找到要删除的竞赛,点击删除按钮,前端向后端id发起删除竞赛请求,

2.后端接收到请求之后,根据竞赛id(先去判断竞赛是否存在。竞赛是否开赛),删除竞赛基本信心(tb_exam)和竞赛题目信息(tb_exam_question)并且将删除结果返回给前端

3.前端接收到响应之后,如果成功竞赛列表中不在返回该竞赛,如果失败返回失败原因。

发布竞赛:(前提,竞赛是存在的,竞赛中存在题目)(对于竞赛发布的话,后端怎么做?)

1.添加竞赛页面(基本信息保存好,在满足发布竞赛前提之后,点击发布竞赛,前端向后端发起请求(携带竞赛id))

2.后端接收到请求之后,根据竞赛id判断前提是否成立,如果不成立将不成立的原因发布给前端,如果成立,竞赛的状态字段从0改为1,并且同步到数据库,再将修改结果返回给前端

3.前端接收到后端响应之后,如果失败,展示失败原因,如果成功,挑战会列表页,当前的状态变为已发布,C端竞赛要能找到已发布的竞赛

竞赛撤销发布:(竞赛撤销前提,1.竞赛存在,2.竞赛还没有开始)

1.找到要撤销发布的竞赛,点击撤销发布按钮,前端携带着竞赛id向后端发起请求。

2.后端接收到请求之后,根据竞赛id撤销对应的竞赛是否成立,如果不成立,返回前端不成立的原因。如果成立,将竞赛状态字段再从0变成1,再将结果返回给前端,前端接收到响应之后,如果失败,显示失败原因,如果成功,B端竞赛列表中当前竞赛状态,变为未发布,当前竞赛C端竞赛列表中消失。

)

C端用户管理(

1.前端携带相应的参数向后端发起获取用户列表的请求。

2.后端接收前端的请求之后,将参数转换为查询条件,从数据中查出符合查询条件的数据,并且将查询结果返回给前端(成功/失败)

修改用户状态(拉黑/解禁)

1.前端携带参数(userId,status)向后端发起修改用户状态的请求。

2.后端接收到请求之后,从数据库中查询出要修改状态的用户,然后将其状态进行修改,并且将更新后的状态同步的到数据库中,再向前端返回更新结果(成功/失败)

3.前端接收到后端的响应之后,如果失败,用户保持原状态不变,将失败原因给用户

如果成功,用户状态变为修改之后的状态,如果是将用户拉黑,要限制用户的操作

如果解禁,那么就去放开之前限制用户操作的功能的使用权限。

)

定时任务管理

PageHelper

PageHelper会自动解析你执行的SQL语句,并根据你提供的页码和每页显示的记录数,自动添加分页相关的SQL片段,从而返回正确的分页结果,无需在SQL语句中手动编写复杂的分页逻辑

配置简单:在SpringBoot项目中国添加依赖,简单配置即可使用

易于集成:无论你是注解方式还是XML映射方式,都可以方便的使用PageHelper

性能优化:使用物理分页,比起内存分页,可以减少内存消耗提高查询性能

Vue声明周期函数

每个VUE组件实例创建时候,都需要经历一系列初始化步骤,如设置好数据侦听,编译模版,挂载实例到DOM,以及数据改变时,更新DOM等。(让开发者有特定机会执行自己的代码),在这个过程中他也会一直运行:被称为生命周期的函数

在此过程中他会运行被称为生命周期钩子的函数

创建阶段:

setup()组件初始化阶段的核心函数,用于设置组件的初始状态,计算属性等

挂载阶段:

onBeforeMount:在组件挂载之前调用,这个钩子可以做一些准备工作

onMounted:在组件挂载到DOM后调用,在这个钩子里,你可以访问和操作DOM元素,如初始化第三方库,添加事件监听器等

更新阶段

onBeforeUpdate:Vue更新DOM之前调用,可以在这个钩子里执行一些结算或者逻辑判断

onUpdated:在组件DOM更新之后调用,可以在钩子里执行基于更新DOM的操作,如重新绑定事件监听器

卸载阶段
onBeforeUnmount:在组件即将卸载之前调用,用来清除定时器,取消异步请求,移除事件监听器等。

C端功能流程

C端登录/注册功能,分情况讨论,新用户/老用户,

老用户:

1.正常输入手机号,点击获取验证码,前端向后端发起验证请求,

2.后端接收到请求之后,随机生成一个验证码,并且将验证码发送到用户的手机上,

3.用户收到验证码之后,在验证码的输入框中输入收到的验证码,点击登录/注册按钮,发起登录/注册请求,搜索一下库里面,根据手机号看是新还是老用户,假如是老用户,执行登录逻辑,并且将登录是否成功返回前端

4.后端接收到请求之后,根据用户的手机号判断用户是新用户还是老用户,如果是老用户的话,直接执行登录逻辑,并将登录是否成功返回给前端。

5.前端接收到响应,如果成功,跳转到系统首页(系统右上角会获取他的昵称头像啥的)

如果失败,停留当前登录页

验证码是否能放到数据库里面呢?假如很多人登录会很多次都注册里面,导致服务器压力很大,验证码一般有有效时间

C端题目列表功能(

竞赛列表:引入redis-(假如redis+数据库都没有数据,假如redis没有,数据库有,那么把数据库同步到redis里面

数据结构:list          key           value

//新用户,执行注册逻辑,
//先完成验证码的比对,通过手机号拿到key,然后比对验证码,如果比对成功,往系统中新增一个用户(向数据库的用户表插入一条数据即可)

设计一个开关:

如果是投入生产使用的话,我们把开关打开,打开后的逻辑,就是生成随机验证码,并且将验证码发送给用户手机上,假如测试,把开关关闭,生成一个固定的验证码123456,不把验证码发送出去

阿里云密钥(阿里云短信登录)

AccessKey和Access Secret是我们访问阿里云API的密钥,具有该账户的权限,需要我们妥善保管。 

核心是使用redis来模拟

我们会先对用户,设置一个验证码的接口,我们在redis中统计这个设置验证码的次数,然后对你的要求进行比较,看多还是少(第一个多少次20次,第二天计数清0,(code,time)c:t:手机号来存储(次数),你登录之后,我就使用(phone,code)p:c:存code,第一次就设置有效时间,需要去动态计算)

C端功能和B端功能使用的群体也不同,C端是给具体的用户,

sudo chmod -R 777   /文件名/oj-fe-c/src      

需要两个页面:一个登录注册页,一个是首页

router配置文件中,增加新页面的路由配置信息

view目录下,创建两个页面级.vue (Login.vue \Home.vue)

获取竞赛列表不同:

展示不同(前端处理)

每个竞赛展示的数据不同,C端的更少一些,(只需要调整查询sql)

搜索条件不同+默认加上是否发布的搜索条件(只需要调整查询sql)

C端支持游客使用,B端必须先完成登录(网关配置跳过身份认证)

Jmeter

Apache Jmeter是Apache组织开发的基于Java的压力测试工具,对于软件做压力测试,

可以测试静态文件,Java小程序,CGI脚本,Java对象

100万个数据

线程数1000,examList(服务器iP,服务器ip),对数据库接口的地址,和参数

测试结果说明:

Summary:表示该时间点新增啦多少个请求

in:该时间点内发生的请求数

如:summary +1000 in 00:00:30表示过去30秒内新增了100个请求。     

5.9/s每秒处理5.9个请求,就是qps(当前性能特别差啦)Error:错误请求占用总请求的概率,因为每次压测的结果不同,受限于场外原因,所有就要考虑一下改啥的(从数据库里面拿)

加了redis之后,到了300qps,提升差不多60倍

什么时候将C端竞赛数据存储到redis中,特点都是已经发布的竞赛,在发布竞赛的时候,将C端端竞赛数据存储到redis里,取消发布还需要将C端端竞赛数据从redis当中移除,

选择怎样的数据结构存储C端的页面。Redis的list可以维持他的插入顺序,还可以进行分页。

选择list:使用两个list 一个使用未完赛,一个使用历史比赛,选择两个list结构,分别存储未完赛的竞赛列表和历史竞赛列表。

会重复存储数据,所以不能这么存储(假如存储基本信息的话)

key是什么,value是什么

key :   竞赛:历史:列表 e:h:l:    value:竞赛:未完赛:列表 e:t:l (t:表示还有时间)

value: 竞赛基本信息(对象存储->json形式)            ->根据value的数据能够查到下面的信息是最好的(改变方式,存储examId,我只需要根据这个查找下面的examId )

如何改变

将竞赛的基本信息只存储一份-数据结构

String  key:       value

          key尽可能知道我们存的是什么 e:d:examId(详情描述detail,区分开不同竞赛)

value(存储竞赛的基本信息)

什么时候将C端竞赛数据存储到redis中,

都是已经发布的竞赛,在发布竞赛的时候,应该将C端端竞赛数据存储到redis里,取消发布时候,还需要将C端端竞赛数据从redis中移除

如果当前竞赛已经结束,就不能再发布了

未完赛列表的竞赛如果结束了,怎么从未完赛列表移入到历史竞赛列表。

1.竞赛要先结束,当前时间假如小于结束时间。写一个移入方法,要去长期的反复的去执行竞赛移动的方法,(定时任务->按照一定频率去反复的操作这个方法,如何定义呢?多少频率:给每个竞赛去搞一个定时任务,(那能否一个定时任务解决呢?)每天凌晨1:00去执行一次历史任务,把未完赛的移入到完赛的(但是细致一想还会有一点点问题:今天结束的竞赛:下午4:00就结束了,但是细想一下,今天的任务,也不能叫历史任务)或者每12h执行一次,但是貌似你的竞赛中午去清也不是很好(12-1h    12-1)两个这样貌似还可以)

因此引入xxl-job

XXL-job

是一个分布式任务调度平台,核心设计目标是开发迅速,学习简单,轻量级

appName长度有限制,执行器在xxl-job里面的唯一标识,要和后台对应一点,名称没要求,就是自己知道是干嘛的就行

竞赛报名功能

比赛:已经报名,已经开赛,哪些用户报名需要记录下来,报名功能最相关的就是答题页面,竞赛排名功能:比赛已经结束,比赛统计时候只统计对象,是报名参加这个竞赛的用户,

我的竞赛功能:当前用户已经报名的竞赛,哪些用户报名我们需要记录,

竞赛列表功能:竞赛可不可以报名的按钮标签啥的,需要从是否报名来判断。

用户的竞赛报名信息,需要存储到数据库中,为了查询到性能,还需要放到redis,这个是我们竞赛列表

redis存储结构
存储信息             数据结构           key         value
未完赛竞赛列表         List           e:t:l       examId
历史竞赛列表           List           e:h:l        examId
竞赛详情信息           List           e:d:examId   JSON存储竞赛详情

但是我的竞赛列表的缓存,应该不止一个,每个用户都有一个,所以应该

key:    u:e:l:用户id            (user exam list   用户的竞赛列表,)

用户竞赛列表     List        u:e:l:用户id    examId
竞赛详情信息     String      e:d:examID     JSON结构存储竞赛详情

先登录,登录之后在竞赛列表找到要报名的竞赛,点击报名竞赛按钮,前端携带竞赛id和token想后端发送请求

后端接收请求,需要先判断是否符合报名条件 如果满足条件,将用户的报名信息存储到数据库和redis,

报名条件:1.用户处于登录状态,2.不能报名不存在的比赛,3.不能重复报名,4.已经开赛的竞赛不能再进行报名

前端接收到后端响应之后,根据返回结果提示用户报名成功或者失败

TransmittableThreadLocal

竞赛报名后端开发:因为我在开始的时候,我就以及把token解析后,已经获取来他的用户ID,此时就给他存起来,那么后面我就不用来回的去解析啥的了,我们要存一个地方,首选redis,但是我的userID属于哪个用户(他是作为一个value),ThreadLocal线程本地变量,为每个线程变量,拥有一个独立的副本,对于其他线程不受影响(useId 用userId存储时候,我既要存他,还要去区分不同的用户),相当于每个线程里面存储的都是一个用户id,就会解决掉了,把解析的id放到threadLocal,但是原生的ThreadLocal,处理异步任务,异步任务在主线程中,又可能开启一些子线程,这样会变成异步的,所以解决的话,使用阿里给的一个TransmittableThreadLocal.导入对应的依赖

我的竞赛功能

已开赛(标签)  竞赛开始时间<当前时间    前端+后端(返回当前用户是否报名这个竞赛)

                        当前用户未报名此竞赛

已报名(标签):竞赛开始时间>档期啊时间,用户之前报名了这个竞赛

竞赛练习和查看排名:应该是在历史的竞赛里面才能看到

开始答题:竞赛开始时间<当前时间,竞赛结束时间>当前时间,用户已经报名参加了这个竞赛

报名参赛:竞赛开始时间>当前时间(未开赛)用户之前未报名这个竞赛

 题目详情缓存  (引入ES)

string 类型      key:q:d:questionId   value:JSON题目详情      

搜索方式十分难受啊 比如合并两个有序数组,我搜索这道题目,有多种组合,这样就很麻烦,我查找后,进行一个过滤,使用java,原生方式,通过for循环从整个列表都过滤一遍,然后返回

2.redis把对应的关键字存起来,但是关键字的组合过多,而且关键字又可能会重复,你假如添加一个竞赛,关键字对应的里面有什么呢?

value:questionId.(而且拿中文当key有点抽象)

一般使用ElasticSearch,这个用来去解决模糊查询,(开源分布式搜索引擎,独特能力,近乎实时的数据搜索能力,全文检索,模糊查询,数据分析)

基本使用:

正排索引:

一种索引机制,通常按照文档的ID或者其他唯一标识符进行排序,正排索引中,索引的键是文档标识符,索引的值是文档的详细信息,当你知道一个文档ID时候,可以通过正排索引迅速找到该文档的全部信息,

主键   数据

1      白蛇

2      白蛇喜欢

3      白蛇吃白狐

4      白蛇猴利谢

5      白蛇大舌头

倒排索引:按照文档的(关键词)来组织,倒排索引中,索引的键是文档集合中出现的每个独特词汇和关键词,索引的值包含该关键词的所有文档标识符,以及可选的额外信息。(索引结构匹配模糊搜索)

索引关键字    对应电影序号

白蛇                 12345

喜欢                   2

吃                      3

大舌头              5

基本概念

         MYSQL        ElasticSearch
数据库: DataBase      index(索引)
表   : Table         Type(类型)
数据行: row          Document(文档)
数据列:column        Field(字段)
模式:Schema         Mapping(映射)

index:具有相同结构的文档集合,如同数据库一样,一个集群可以创建多个索引,如客户信息索引等,可以给每个索引创建数据结构,索引命名要求全部使用小写(建立索引,搜索,更新,删除操作)都需要用到索引名称(6.0之后下降到相当于数据库表的级别)

type:在索引内的逻辑细分,但是6.0后版本废弃。

Document:文档,相当于每个数据.可以包含多个字段,每个字段包含多个字段,可以是文本,数字,日期等类型。

Field:文档中的一个元素或者属性,每个字段都有一个数据类型,如字符串,整数,日期等

Mapping:ES中很重要的内容,类似传统关系型数据中的table的schema(定义数据库的数据如何组织的,包括表的结构,字段的数据类型,键的设置(如外键,主键等),用于定义一个索引等数据结构,在es中,我们手动创建mapping,也采用默认创建方式,默认配置下,ES可以根据插入的数据自动的创建mapping

比如这个命令,他的要求不是十分严格,所以employee就相当于是数据库(自动帮你创建了,因为你写的很清楚,在xx目录啥的,doc就是document)

修改

1 POST /employee/_update/3
2 { 3 "doc" : {
4 "last_name" : "mark"
5 }
6 }

题目列表:

先查es:如果能查到数据->前端,如果查不到,再查mysql,数据同步给es,并且将查到的数据返回前端,如果数据库查不到,返回给前端空。

IK分词器,分为两块,一个是ik_smart 和ik_slow

smart尽量保持长词,比如我想吃肉夹馍

我,想吃,肉夹馍 花合斗

ES是什么

搜索,存储引擎,非关系型文档数据库

特点天生分布式,高性能,高可用,易扩展,易维护 

ES写入提高效率

减少负分片,对Mapping属性进行临时去除,同时可以批量写入

ES的全文检索

扫描文本中每个单词,对单词建立索引,保存该单词在文本的位置,以及出现的次数,假如是你的模糊匹配,必须要准确出现那个词才行,但是这个就是他进行一个分词之后,你想要什么都可以,比如说es快速入门,es,快速入门,这两个作为两个关键词搜索,比如的,呢的过滤

撤销发布:为啥一般都是发布先变成撤销发布才可以修改(编辑啥的),因为我们搜索啥的涉及es,但是假如每个都去处理es和redis,是不是不是非常好处理,但假如我们撤销发布,他会自动把缓存清除了,然后再去编辑啥的,就又省事了

代码沙箱(判题功能)

判题功能:后端收到前端的请求,获取参数,我需要知道用户的语言选择是啥,根据语言进行不同处理,

根据questionId从es中查询出对应的main函数和测试用例(入参),将代码判断完整。查询的时候,还需要查询题目的时间,空间限制,难易程度

javac:将java代码进行编译(成功/失败)

成功:继续执行后续逻辑   (执行java)

失败:终止逻辑,失败原因返回前端.

java:

成功:继续执行后续逻辑

失败:终止逻辑,失败原因返回前端。

题目答案的比对:上一步代码实际输出的结果,和测试用例中期望结果进行比对。

如果比对一致:题目作答正确,如果不一致,题目作答错误。返回错误原因给前端。

参数从main函数里面获取

public static void main(String[]var0){

String var1=var0[0];   //一般来说命令行操作,Java命令行后面跟一些参数,可以从这块获取

sout(isValid(var1));

}

这个传递的参数是啥呢?,难道什么都行吗,我们是需要插入测试用例的。

使用一个json类型的数组。输入输出 

input:参数(从这块拿你的参数)

output:输入结果(从这里检查,预期输出结果)

时间限制和空间限制的比对:比如代码执行的所使用的实际的实际和空间和期望的时间和空间进行对比,如果<=期望值,说吗符合要求,判定题目作答正确,并且结果返回前端,否则则不符合要求,判定题目作答错误,并且将错误原因返回前端

对于用户答题结果,无论失败还是成功,我们都应该存储到mysql数据库中,以供后续使用,答题结果在计算的时候,需要注意对于分值的处理(分值的计算和题目难度相关)

对了

用户的代码可能有点抽象

资源滥用,攻击:比如死循环啥的,cpu,网络,把资源都占完了,那么系统就很难保证运行的稳定性和性能

-分配有限资源,限制cpu,内存资源等资源使用

-运行数据限制,不予许用户长时间占用资源

安全漏洞;代码中如果存在病毒会导致系统瘫痪

-限制用户代码执行权限,限制文件等读写,限制网络等访问。

数据异常:用户提交的代码有很高权限,可以访问我们系统里面的文件,如果敏感信息文件让别人获取就完了,或者往文件中写非法的数据

-和系统运行环境隔离开,不同用户代码执行环境也隔离开

相互干扰:多个用户同时提交代码,同个环境下可能互相影响

-如果发现恶意攻击的用户,随时将用户拉黑处理

使用docker,创建隔离的容器去实现代码,包括一些权限啥的,而且宿主机隔离的很好

通过java操作docker,引入什么服务呢?

判题的逻辑很耗时,所以我们之前专门划分的judge服务,专门去处理判题,能提高系统的性能,可以集中资源去处理这个服务

因为friend里面有什么提交啥的好多功能,假如分开,就好操作了,而且还要维护什么es,啥的,因此,尽可能使judge性能更高,集中精力做一件事。

friend操作号所有数据,然后服务间调用judge服务就好

容器池(开始我们是一个创建容器,然后启动容器,再去删除容器,就像是多线程变成线程池)

来个请求,都是创建一下(如果请求大爆发,我们很有可能资源被吃满,比如竞赛,搞一个容器池)

创建一个方法,把用户提交的代码放到一个目录,多个容器,不能用同一个目录,怎么生成挂在目录,公共的加容器名

如何存储容器,存储到哪里呢? 存到集合里面放到阻塞队列BlockingQueue<String>containerQueue 初始化就ArrayBlockingQueue<>(限制数量)

为什么引入消息队列:面对报名一个竞赛的时候,报名点击数目陡增

因此引入MQ

生产者把用户的信息啥的传递给MQ MQ给消费者,让他去拿,

用户拉黑功能

B端把C端用户的所有基本信息都列出来,可以进行拉黑操作,拉黑用户的目的是限制C端用户的行为,比如C端用户有违法操作,恶意攻击用户进行拉黑

status=0 拉黑

status=1 正常

限制他报名竞赛就好

checkUserStatus这个注解

这里是用了一个自定义注解结合切面类(通过ThreadLocal获取id,然后我看你的status状态码),就类似于那个校验登录似的,假如你非法操作,我限制你报名竞赛(在你报名点击竞赛之前),用户被拉黑,我就不予许你被

因为我们要加入注解,给报名方法加上一个Before()前置通知类型的方法,然后我们会在目标方法之前执行。

引入注解

package com.bite.friend.aspect;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})  // 可标注在类和方法上
@Retention(RetentionPolicy.RUNTIME)              // 运行时保留 //因此可以通过反射机制来读取。
@Documented                                      // 包含在Javadoc中
public @interface CheckUserStatus {
    // 这是一个标记注解,没有定义属性
}

引入切面类

  • 当调用带有@CheckUserStatus注解的方法时
  • AOP拦截器会先执行UserStatusCheckAspect中的before方法
  • before方法中去判断是否被拉黑
package com.bite.friend.aspect;

import com.bite.common.core.constants.Constants;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.utils.ThreadLocalUtil;
import com.bite.common.security.exception.ServiceException;
import com.bite.friend.domain.user.vo.UserVO;
import com.bite.friend.manager.UserCacheManager;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Aspect
@Component
public class UserStatusCheckAspect {
    @Autowired
    private UserCacheManager userCacheManager;
//应该是操作某些功能之前,进行一个判断,前置刚好合适。在报名之前去检查,注解加到哪里,就是哪个操作之前检查
    @Before(value = " @annotation(com.bite.friend.aspect.CheckUserStatus)")
    public void before(JoinPoint joinPoint) {
        //获取当前用户信息 status
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        UserVO user = userCacheManager.getUserById(userId);
        if (user.getStatus() == 0) {
            throw new ServiceException(ResultCode.FAILED_USER_BANNED);
        }
        if (Objects.equals(user.getStatus(), Constants.FALSE)) {
            throw new ServiceException(ResultCode.FAILED_USER_BANNED);
        }
    }
}

我的消息功能

站内信:网站内部的一种通信方式

1.用户和用户之间的通信(点对点)

2.管理员/系统和某个用户之间的通信(点对点)

3.管理员/系统和某个用户群(满足某一条件的用户群体)之间的通信.(点对面)

竞赛结果通知消息-点对点(因为不同用户,你当前是多少多少名)

数据库表设计:

包含消息发送人,接收人,内容

主键id, 消息标题,消息接收人,发送人   ,消息内容 (长远来看,假如给用户和用户,用户和用户群加上,以免因为结构不充足,造成大规模调整,第一种支持,但是用户群就会冗余了,发送给一个用户群,我发的消息唯一,那么我要重复好多次)

主键     消息标题     内容   接收人   发送人

1.          福利信息     内容     1           0  

1           福利信息     内容     1           0     这种

分表-

消息发送

消息如何产生:竞赛结果通知消息(凌晨的xxljob 通知)

每天凌晨,会对当天(前一天,一个意思)结束的竞赛进行用户排名的统计,消息在统计的过程中,随即产生了。(为啥凌晨,肯定是要当天所有的竞赛,都统计一次,又可能早上一个,中午一个,下午一个,有可能一些人,写了还没提交,大部分竞赛的结束时间都是晚上xx点,比如10点,11点)

xxl-job凌晨1点执行,统计前一天结束的所有竞赛

消息如何发送:

最终效果是用户能在消息页面中找到消息,换句话说,只要存在数据库里面就好,设计redis缓存

存储信息。        redis数据结构     key                      value

用户消息列表      List                   u:m:l:用户ID         textId

消息详情信息      String               m:d:textId            JSON结构存储消息详情

消息发送实现:

通过定时任务生成消息->将消息存储到数据库和缓存当中

竞赛排名功能

只有历史竞赛才能查看排名,未完赛你看不了,把得分排名啥的存储起来,开发和设计我的竞赛时候,这么存储

缓存结构:

数据结构list

key:竞赛排名列表 e:r:l:+examId

value:直接存详情名次, json :examRank      userId(通过userId查 

昵称会改,但是我的id要保证,不能说你id改完,找不到了,除非你缓存也要改

 nickname score)

为什么不是存id呢(假如我们之间是存详情信息,那我我们会存很多份,因为首先他的竞赛就有可能你的题库和竞赛列表有重复的数据,假如有详情信息, 但是竞赛排名会有这个情况吗,但是竞赛的排名数据是基本不会重复)

临时记录

我在做项目的时候,想到一个问题,我们微服务,为什么要把一个类,粘贴到另一个类

就比如我的job模块有User类,我的friend服务也需要这个类,为啥我不能导入job这个依赖,然后我去使用User类呢,这么粘贴不麻烦吗?

假如你引入啦,不就是变成单体架构啦吗,那么这里我还有感悟,你的引入依赖,引入的不应该是启动类的微服务,换句话你应该引入的全部都是工具类。

Nginx

Nginx是一个高性能的HTTP和反向代理Web服务器,功能有反向代理,负载均衡和动静分离

正向代理

客户端通过代理服务器来访问互联网上的其他服务器,这种情况,客户端配置了代理设置(代理是在客户端配置的)所有对外的请求都会先发送到代理服务器上,再由代理服务器转发到真正的目标服务器,

(为什么不直接发目标服务器)

好处:

隐私保护:客户端的真实IP地址不会直接暴露给外部服务器

缓存:代理服务器具有缓存功能,对于频繁请求的数据,可以直接从缓存中返回结果,提高响应速度

内容过滤:组织或者企业可以使用正向代理对员工的上网行为进行监控,或者过滤不合适的网站内容。

反向代理

客户端直接访问的服务器实际是代理服务器(代理服务器在服务器端配置)这个代理服务器接收客户端请求后,会把请求转发给真正的后端服务器,客户端本身是不知道这个代理服务器的存在的 (好处)

负载均衡:通过反向代理,可以多个后端服务器直接分配请求,分散单个服务器压力

安全性:隐藏真正的后端服务器地址,增加安全防护

缓存与速度:类似于正向代理,反向代理也可以实现缓存功能,减少负载加快响应速度

负载均衡

为了避免单点故障或者现有的请求使服务器压力太大无法承受,所以我们搭建一个服务器集群,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载均衡的分发到不同服务器,也就是负载均衡

假如ABCD ,四个服务器,性能不均,怎么做到均衡呢?

Nginx提供了算法

1.轮询法

每个请求按照时间顺序逐一分配(要求所有服务器性能大差不差)

2.权重模式(加权轮询,weight)

轮询,weight和访问比率成正比,用于后端服务器性能不均情况,这种方式比较灵活,当后端服务器性能存在差异的时候,通过配置权重,可以让服务器性能充分发挥,有效利用资源,weight和访问比率成正比,权重越高,被访问的概率越大(能力越大,干的越多)

ip_hash

请求通过哈希算法,自动定位到该服务器。每个请求按照ip的hash结果分配,这样每个访客固定访问一个后端服务器

最少连接

nginx统计每个后端当前活动连接数,分配活动连接数最少的服务器上,根据服务器的实际负载情况动态分配请求,将新请求分配到当前连接数最少的服务器上,避免某些服务器负载过高而其他服务器负载过低的情况。

动静分离

动:动态资源,动态资源指通过请求服务器获取到的资源

静:静态资源,不需要请求服务器就可以得到的资源如CSS,html,jpg,js)

这些静态资源既然无需服务器处理,则可以由nginx直接来处理无需后端服务器介入,这样可以减轻后端服务器的负载,使其专注于处理动态内容,从而提高整体性能,并且可以根据静态资源的特点做缓存操作

题目的顺序列表(用户端的前一题和后一题,怎么做)

redis的list数据类型,(保证顺序,只要差人的顺序不变就好,我们只要存入那个题目的id存进去就好) list  key:q:l   :  value:id

查询一下题目列表,@Component

@redisService

把顺序列表查出来

redis应该目前没数据,我们都是先查redis->没数据->mysql,

mp的语句

List<Question>questionList=questionMapper.selectList(new          LambdaQueryWrapper<Question>           //要返回的类型 ().select(Question::getQuestionId).orderByDesc(Question::getCreateTime)

然后流的使用,集合.steam().map(Question::getQuestionId).collect(Collectors.toList())

尾插,即右插入。

}


2025年5月13日 面试,某某某某达

抽象问题给我打懵逼了

前端通过ref获取到即时的响应式变量

怎么获取前端传过来的数据

el-input 绑定 这个ref,假如你输入框输入内容,userAccount自动更新

@PostMapping("/login")接收前端传递过来的数据

@RequestBody:接收前端传递过来的Body。 我们通常使用一个对象,来包装这个东西

@ApiResponse:这是Swagger注解,用于定义API的可能响应及其描述。

我们采用一个范型R,来包装

int code;比如成功的统一码(比如返回0失败,1成功,3....)

String msg; //通常作为code的辅助说明,一个code对应一个msg.

private T data; //请求某个接口返回的数据 list SysUser范型。

使用一个范型

export function loginService(userAccount,password){
    return  service({
        url: "/sysUser/login",
        method: "post",
        data:{
            userAccount,
            password
          }                      //接口地址
       })
}

在您参与的定时任务中,是如何处理竞赛信息的发布和用户排名的计算的,有没有用到一些特定的框架或技术

使用了xxl-job技术,用户竞赛信息的发布,首先要对时间进行一个处理,比如查看一下,当前时间和竞赛的开始结束时间比较,

竞赛排名信息的发布,因为我们每道题的竞赛都有分数,我们用sql都存了。

通过竞赛来找分(通过SQL计算时间范围内,所有竞赛id的组合)

ES怎么做添加

ES的结构:


网站公告

今日签到

点亮在社区的每一天
去签到