鱼答答

鱼答答

项目经历

项目架构

Spring Boot + Redis + ChatGLM + RxJava + SSE + Caffeine

项目简介

智能化应用答题平台,用户可以基于AI自动生成题目并制作应用,经管理员审核后,可在线答题并基于多种评分算法或AI得到回答总结,管理员还可通过后台管理系统整理网站内容,并进行统计分析

我负责的部分

  • 库表设计:根据业务设计用户/应用/题目/评分结果/用户答题表。其中题目采用JSON存储复杂的嵌套题目和选项,便于维护扩展,并通过题目表格添加appId索引提升检索性能。
  • 评分模块:基于策略模式实现了多种用户回答评分算法(如统计得分、AI评分等),全局执行器会扫描策略类上的自定义注解并选取策略,相比于if-else提高了系统的可扩展性
  • 基于ChatGLM AI封装了通用AI模块,并通过配置类自动读取key配置初始化AI客户端Bean,便于全局使用
  • AI生产题目:通过系统预设、少样本学习、任务拆建等手段优化Prompt,让AI返回JSON格式的题目,便于后端处理
  • 由于AI生成题目较慢,选用ChatGLM的流式API并通过SSE实时推送单道题目给前端,提高用户体验
  • 基于RxJava的操作符链式调用处理AI异步处理数据,先通过map获取并处理字符串、filter过滤空值、flatMap映射串为单个字符,再通过括号匹配算法精确拼出单道题目,使得逻辑简单清楚
  • 为防止用户多次提交重复答案,基于雪花算法为每次答题分配唯一id,并通过数据库实现幂等设计,避免了重复的脏操作
  • 由于相同答案的AI评分应该也是相同的,使用Caffeine本地缓存答案Hash对应的AI评分结果,并通过Redisson分布式锁解决缓存击穿问题
  • 为应对用户答题数的激增,基于Sharding JDBC配置取模分片算法,根据应用id对用户答题记录分表,提高了单表查询性能
  • 由于服务器资源有限,为会员创建核心线程数更大的隔离线程池,保证普通用户不会影响到会员使用AI的体验

架构设计

语雀(画图)

核心业务流程图

时序图

架构设计图

MBTI 实现方案介绍

核心组成部分:题目、用户答案、评分规则

1.题目结构(用json表示结构,前端结果看似是数组,但实际上是因为后端封装的是类)

2.用户答案结构(传递一个数组,如["A","B"])【数组顺序也是题目顺序,不用传递题目,节省传输体积】

3.评分规则(权威著作)

需求分析

1.按模块梳理

​ 用户模块

​ 应用模块

​ 题目模块

​ 评分模块

​ 回答模块

​ 统计分析模块

2.核心业务逻辑

​ 流程图

​ 文字描述

3.进行需求优先级分析

库表设计

用户表

应用表

题目表

​ 题目内容使用json格式

评分结果表

​ resultId可能为空,是因为AI分析策略不会从结果表中选取结果,没有resultId

​ 评分结果基本不会更改,是一次的提交数据,因此在表中设计冗余字段,便于查询,不用列表,节约开发成本

​ 还有可能通过异步的方式、或者题目答案没提交(只答一半的时候,先临时保存题目回答记录)

用户答题记录表

后端初始化

引用万用后端模块功能

准备依赖

执行初始化SQL脚本

模板改造【ctrl+shift+r】(模块名、包名【Shift+F6】、移除不必要的模块、更改数据库)

后端基础开发 - 增删改查

先写最简单的增删改查的功能,不包含任何业务逻辑

1.数据访问层代码生成

使用MybatisX插件根据数据库生成实体类和mapper接口

改造Enity类,将id改为雪花算法(防止他人爬虫),添加逻辑删除的注解

2.业务逻辑代码生成

使用万用模板的代码生成器工具(CodeGenerator)生成代码(Controller、Service接口和实现类、数据模型包装类和枚举类)

3.数据模型开发

编写数据模型包装类(请求类和视图类)、JSON结构对应的类、枚举类

包装类需要根据前端传递的请求参数或需要的响应结果自行修改

问题

使用json格式,但是前端传递json格式就是字符串

​ 将前端转递的字符串接受为一个集合(如List),实体类可以用String接受,然后在VO类里面进行转换,将json和对象互相转换

今天做的最多的就是在controller层中将DTO转化为json数组,其次就是在service层中校验数据

查询Query请求里面的是String对象,因为数据库中存储的是text,二进制存储,没有对象这一说,这里的VO对象也像是工具类,里面有objToVo的方法

后端核心业务流程开发

核心业务流程开发

1.用户注册 - 用户登录

2.用户创建应用 - 创建题目(包括题目选项得分)- 创建评分规则(评分方式和评分结果)

3.管理员管理应用,审核发布(或下架)应用

4.用户查看和检索应用列表,进入应用详情页,在线答题并提交回答

5.经过评分模块计算后,用户查看本次评分结果

需要额外开发的能力:

应用模块

​ 审核发布和下架应用(仅管理员可用)

回答模块

​ 提交回答(创建)。需要额外调整,提交回答后就可以调用评分模块并更新回答表

评分模块

​ 根据回答计算评分结果(多种评分策略)

​ 自定义规则 -- 测评类

​ 自定义规则 -- 打分类

最后通过swagger接口文档来测试业务逻辑

评分模块实现

针对不同的应用类型和评分策略编写不同的实现方式

自定义测评类思路

通过appId得到app相关的问题和结果集合(通过属性),通过用户的答案来判断收集答案属性(遍历题目列表以及题目选项),遍历每种评分结果(计算哪个结果分数最高),构造答案返回值

自定义得分类思路

通过appId得到app相关的问题和结果集合(通过得分范围),统计用户总得分,遍历得分结果,找到第一个用户分数大于得分范围的结果,作为最终结果,构造答案返回值

全局执行器

两种实现方式

1.编程式(硬编码)

2.声明式(在每个策略类中通过接口声明对应的生效条件

接口

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component public @interface ScoringStrategyConfig {

text
复制代码
int appType(); int scoringStrategy();

}

给策略类补充注解

@ScoringStrategyConfig(appType = 0, scoringStrategy = 0)

该注解使用了@component注解,会被当做bean来注入

平台智能化

智谱AI有3种接入方式(SDK、http、第三方框架)

封装AI模块

AI生成题目

主要的点就是调Prompt参数

问题

​ 现在AI生成的题目依旧不稳定,但是测验的时候,其实是不同的

优化

​ 如果要生成大量题目,gpt无法实现,只能分批生成,将历史生成的题目关联到上下文中,防止重复

AI智能评分

主要难点:调AI的Prompt参数

性能优化

当前问题:AI生成过慢,影响用户体验

解决方案:使用流,AI生成多少就让前端看到多少

RxJava响应式编程

响应式编程

1.数据流:数据以流的形式存在(理解为河流),它可以被过滤、合并(stream流)

2.异步处理:响应式编程是异步的,操作不会阻塞主线程

3.变化传播:数据有变化就会传递【举例:可能啊可能,鱼皮说的是订阅股票的人会收到通知,我想的是现在手机上的外卖进度】

RxJava

RxJava是一个基于事件驱动的、利用可观测序列来实现异步编程的类库,是响应式编程在Java语言上的实现

事件驱动:出现操作行为。每当一个事件发生的时候,这个事件就会被推送给那些对它感兴趣的观察者

可观测序列:指一系列按照时间顺序发出的数据项,可以被观测和处理

RxJava应用场景

核心应用场景:UI场景

UI场景天然涉及响应和事件两点。eg:当我们在app上点击某个按钮(事件)的时候,就会有对应的弹窗(响应)

好处:异步操作(1.安卓的主线程是需要试试响应用户的请求,如果它去执行查询图片,app可能就会卡顿,影响用户体验

​ 2.耗时的操作需要到子线程中操作,然后最终显示结果才需用利用主线程)

理解:可以让耗时的操作去子线程中执行,RxJava里面有写好的方法

RxJava核心知识点

基于观察者模式,观察者是Observer,被观察者是Observable和Flowable

Observable不具备反压处理能力,当生产速度超过消费速度的时候,会出现问题,导致内存溢出

Flowable可针对上述情况,有多种背压策略处理这种情况

常用操作符

1.变化类,map

2.聚合类,toList、toMap

3.过滤类,skip

4.连接,concat

5.排序

事件

1.onNext,被观察者每发送一次,触发一次

2.onError,错误触发

3.onComplete,最后一次调用onNext触发

依赖引入:因为智谱依旧引入了,不需要再引入

前后端实时通讯方案

1.轮询

前端定时调用后端接口获取数据,主动轮询是一种伪轮询,有可能会造成消息滞后

2.SSE

后端实时发送数据给前端

3.WebSocket

双向沟通

SSE技术

基本概念

服务器发送事件,一种用于服务器到客户端的单向、实时数据传输技术,基于HTTP协议

重要的特点:

单向通信、纯文本格式、保持连接、自动重连

SSE数据格式

注意

一定要实现Get请求

AI评分优化

需求分析

问题:AI评分响应时间较长;AI调用需要费用,如果用户对同样的题目做出同样的选择,理论会得到一样的解答

解决方案

缓存【响应慢、数据可重复】

方案设计

本地缓存和分布式缓存

分布式缓存劣势:成本高、增加系统复杂度

缓存设计

1.缓存key设计:

需求:如果用户对同样的题目做出同样的选择,理论会得到一样的解答

将应用id和用户的答案列表作为key【答案列表可能很长,可以使用哈希算法压缩key】

注意

如果是分布式缓存,可以在key开头拼接业务前缀,单独为每个业务创建缓存(类似苍穹外卖里面缓存菜品、套餐,黑马点评里面短信验证码、用户token值)

2.缓存value设计

缓存AI回答的结果,为了可读性存json结构,为了压缩空间可以使用二进制结构

3.设置缓存过期时间

业务流程

  1. 在AI答题之前,获取appId、用户选项,拼接key
  2. 去Redis里面寻找
  3. 没有调用ai接口
  4. ai接口返回json数组,缓存在Redis

注意

​ 应用题目变更的时候自行清理Redis缓存数据

​ 该缓存主要针对AI答案

分库分表设计

分表:单张表数据量过大,达到三、四千万的数据

如何分表:按照日期分配,打算在哪里加索引就用哪个字段分表,核心在于业务的查询,减少跨分库发表的查询

Sharding_JDBC原理

改写SQL(简化代码、提供人的信息即可)

注意

当user_answer更新的时候,注意先不要更新appId,因为更改appId的时候是不会自动更改库的

系统优化

幂等设计

场景问题:用户多次点击按钮,发送同一请求

​ 电商平台里面当用户付款的时候出现付款不成功的问题,造成多次扣款

特点:一个操作执行多次和一个操作执行一次的结果是一致的

主流的幂等性设计

1.数据库唯一索引

2.数据库乐观锁

给表中的某一列增加一个字段

3.天然幂等操作

例如数据库中的查询、更新操作

4.分布式锁

解决并发操作

方案设计

业务流程

给用户答题记录表的哪一个字段添加唯一索引呢

【此时尚未插入数据,id还未生成】造一个字段作为唯一索引

1.前端进入答题页面的时候,请求后端发送一个全局唯一id

2.用户提交回答的时候将这个全局唯一id带上

3.插入数据库的时候也将这个保存到数据库中【使用数据库实习幂等性】

线程池隔离

将核心业务进行隔离,防止其它业务影响核心线程池

性能优化:针对不同任务类型配置不同的线程池(CPU密集型)

RxJava使用的线程是全局共享,使用的是无界队列,并且没有做限制,来一个任务创建一个线程池,容易造成OOM

方案设计

VIP专用线程池定义

创建一个Configuration配置类,创建线程池作为bean,将vip线程注入题目控制类里面,将SSE推送技术中使用到的RxJava使用到的线程池替换,判断完是否是会员的时候进行替换

统计分析

需求分析

​ 1.根据数据统计,去进行功能优化,将热门应用排在前面,并添加缓存提升访问速度

​ 2.分析应用内用户的答案结果分布,根据结果发布,可以优化对群体大的用户进行定制化开发或广告投放

技术选型

​ 1.Excel(适合小规模的数据分析场景)

​ 2.关系型数据库(数据规模不大、简答的数据分析)

​ 3.第三方数据分析平台(要钱)

​ 4.大数据分析(建设成本高)

后端开发

基于答题记录统计热门应用

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
面向对象的王者很迟缓
下载 APP