概要
目前市场上的诸多背单词软件更注重于单词的纯记忆和拼写,但很少有软件能够有效地将已学习的单词联系起来。有时我们会遇到某个单词似曾相识,实际上却从未接触过。此外,我们经常会想要运用某个单词的同义词替换,却在关键时刻想不起来。因此,本文致力于设计一种基于语义相似度模型的单词联想记忆学习系统,以促进用户的词汇积累和语言学习。大致思路是使用语义相似度模型为每一个单词设置一组相似词和同义词保存在数据库中,用户可以自定义这些相似词和同义词。
技术实现:Vue2,element-ui;Spring Cloud。具体包括spring cloud gateway,springboot,redis,MySQL等等。
系统功能
自定义词书
该功能的主要目的是让用户可以自己选择想学习的书籍,用户可以通过上传一个txt文件来生成词书,并设置书名和封面,该文件的标准格式是每行包含一个英文单词,对于无法识别的单词,系统给出相应的提示。
学习单词
在用户点击“开始学习”按钮时,系统会根据用户选择的词书自动加载一组新单词,每组默认包含10个单词,用户可以在个人信息页面自行调整每组单词的数量,以满足个人需求。同时,系统为每个单词初始化了5个相似词和同义词,以提供丰富的词汇学习资源。用户还可以自由地为每个单词删除或添加相似单词和同义单词,但这些更改仅针对当前所选词书生效,不会影响其他词书的内容。
复习单词需求
当用户点击“开始复习”按钮时,系统将自动加载一组已学习过的单词,为了方便用户,系统默认加载10个单词。然而,用户可以根据自己的学习进度和需求,在个人信息页面调整每组单词的数量。对于每个单词,系统会记录复习的轮数,并在完成5轮复习后将其标记为已完成,不再出现在复习列表中。
学习记录
在主页,用户可以通过图表清晰地查看自己最近一周的学习情况。这些图表包含学习时长、学习的单词数量等关键指标。系统采用柱形图和曲线图相结合的方式,横轴代表最近一周的日期,柱形图的纵轴代表学习单词的数量。
个人信息
用户把鼠标悬浮在右上角的头像后,会弹出一个面包屑按钮,点击“个人信息”选项卡后,会跳转到个人中心页面,用户可以基本信息页修改手机号以及每轮单词数量。同时,在安全信息页中可以修改登录密码。
后台管理系统
针对本系统的词汇和推荐书籍进行统一的管理。单词管理:包括对词汇的相似词和同义词,词汇的示例句式,词汇的中文释义,简明释义,专业释义,固定搭配等等进行管理。词书管理:对推荐书籍进行管理等。具体包括词书的内容,词书名称,词书封面等等。
系统架构
系统架构图如下,分为普通用户和管理员两个角色。只有管理员可以登录后台管理系统,设置推荐书籍和全局修改单词信息。
`
单词表
单词信息表包含本系统的单词基本信息,表结构如下所示。
字段 | 类型 | 注释 |
---|---|---|
id | int | 主键id |
content | varchar | 英文单词 |
translation | varchar | 翻译 |
synonym_ids | varchar | 同义词id集合 |
similar_ids | varchar | 相似词id集合 |
词书表
词书表是用来存储本系统中系统推荐书籍及用户自定义书籍信息,它的结构如下所示。
字段 | 类型 | 注释 |
---|---|---|
id | int | 主键id |
name | varchar | 词书名称 |
word_ids | varchar | 单词id集合 |
user_id | int | 创建者id |
url | varchar | 词书封面地址 |
create_time | datetime | 创建时间 |
count | int | 单词个数 |
用户书籍关系表
用户书籍关系表是用来存储本系统中使用者自定义的书籍的信息表,表结构如下所示。
字段 | 类型 | 注释 |
---|---|---|
id | int | 主键id |
user_id | int | 用户id |
book_id | int | 书籍id |
user_id | int | 创建者id |
learn_word_count | int | 已学习的单词 |
review_word_count | int | 已复习的单词 |
state | tinyint | 是否使用0:未使用,1:使用 |
系统实现
系统主页如下图所示
用户进入主页后,选择头像的汉堡按钮中的“词书库”按钮,进入到词书库页面,可以查看到所有自定义的所有书籍和推荐书籍。
在词书库页面点击书籍封面可以进入该书籍的详情页。
当用户进入到主页面时,点击“学习”按钮后跳转到学习页面。页面会展示英语词汇和四个相似的释义答案选项,在选择答案后,正确的选项会变成绿色,同时会展示该单词的相似单词和同义单词,点击下一个会刷新当前页面数据,展示下一个单词。
用户进入主页后,选择头像的汉堡按钮中的“我的”按钮进入个人信息页面,展示用户的头像,用户名等基本信息,并允许用户修改,同时支持修改密码的功能
后台管理分为两大核心功能:单词管理和词书管理。单词管理页面会以分页的方式展示出系统内的所有单词,点击编辑即可跳转到单词详情页面,单词详情页面包含了单词的相似词,同义词,释义等等。在编辑完后需要点击“保存更新”按钮才会生效。
词书管理可以对本系统的推荐书籍进行管理。包括对推荐书籍的新增和删除,以及对词书内单词进行新增和删除。如下图,点击新增书籍按钮,会弹出一个弹窗和主系统的自定义词书效果一致。点击词书会进入词书详情页,在该页面可以对推荐书籍的书名,封面进行修改,同时会以分页的形式展示出该词书中的单词,可以在操作栏对词书中每个单词进行相应的操作。
代码实现
文件上传接口
//controller层
@PostMapping("/createBook")
public Response createBook(@RequestParam MultipartFile file,
@RequestParam String bookName,
@RequestParam(required = false) String url,
@RequestParam(required = false) Integer userId) throws IOException {
ReqBook reqBook = new ReqBook();
reqBook.setName(bookName).setUrl(url).setUserId(userId);
return Response.ok(bookService.createBook(file, reqBook));
}
//service层
@Transactional(rollbackFor = Exception.class)
public List<String> createBook(MultipartFile file, ReqBook reqBook) throws IOException {
if (file == null) {
return new ArrayList<>();
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream()));
String word;
List<String> words = new ArrayList<>();
while ((word = bufferedReader.readLine()) != null) {
word = word.trim();
words.add(word);
}
//识别不了的单词
List<String> res = wordService.checkBatch(words);
//识别成功的单词
List<String> recognition = words.stream()
.filter(i -> !res.contains(i)).collect(Collectors.toList());
List<Word> recognitionWords = wordService.lambdaQuery().in(Word::getContent, recognition).list();
//封装书籍实体
Book book = new Book();
BeanUtils.copyProperties(reqBook, book);
//把识别出来的单词存入词书
List<Integer> wordIds = recognitionWords.stream().map(Word::getId).collect(Collectors.toList());
String wordListIds = JSON.toJSONString(wordIds);
book.setCount(wordIds.size()).setWordIds(wordListIds);
if (book.getUserId().equals(0)) {
book.setType(0);
}
this.save(book);
//保存书籍与用户的关系
UserBookRelation relation = new UserBookRelation();
relation.setBookId(book.getId()).setUserId(reqBook.getUserId()).setState(0)
.setLearnWordCount(0).setReviewWordCount(0);
userBookRelationService.save(relation);
//初始化记忆天数为0
Memory memory = new Memory();
memory.setBookId(book.getId()).setUserId(reqBook.getUserId()).setDay(0).setWordId(wordListIds).setLength(wordIds.size());
memoryService.save(memory);
return res;
}
学习单词接口
//controller层
@GetMapping("/getMemoryWord")
public Response getMemoryWord(@RequestParam Integer bookId, @RequestParam Integer day,
@RequestParam Integer count, @RequestParam Integer userId,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate time) {
return Response.ok(memoryService.getMemoryWord(bookId, day, count, userId, time));
}
//service层
@Override
@Transactional(rollbackFor = Exception.class)
public RespWord getMemoryWord(Integer bookId, Integer day, Integer count, Integer userId, LocalDate date) {
//确定要获取复习了第几天的单词
Memory memory = chooseMemory(userId, bookId, day);
if (memory == null) {
throw new ServiceException("暂无学习/复习单词!");
}
RespWord res = new RespWord();
res.setDay(memory.getDay()).setCreateTime(memory.getCreateTime());
List<RespLearn> learns;
List<Integer> wordIds = JSON.parseObject(memory.getWordId(), List.class);
//count不为0表示是提交学习情况
if (count != 0) {
//提交本次学习记录
wordIds = saveLearningProgress(wordIds, bookId, day, count, userId, date);
}
//默认10个单词为一组
Integer group = LoginUtils.getCurrentUser().getCount();
wordIds = wordIds.subList(0, Math.min(group, wordIds.size()));
if (CollectionUtils.isNotEmpty(wordIds)) {
List<Word> words = wordService.listByIds(wordIds);
wordService.fillWords(words, bookId);
learns = RespLearn.build(words);
res.setWords(learns);
}
return res;
}
//选择记忆
private Memory chooseMemory(Integer userId, Integer bookId, Integer day) {
Memory res = null;
LocalDate now = LocalDate.now();
//表示第一次请求,走轮询路线,获取最近学习的单词
if (day == -1) {
for (int i = 1; i <= 4; i++) {
List<Memory> memories = this.lambdaQuery().eq(Memory::getBookId, bookId)
.eq(Memory::getUserId, userId).eq(Memory::getDay, i).list();
for (Memory memory : memories) {
if (memory != null && memory.getLength() != 0 && !memory.getCreateTime().plusDays(memory.getDay()).isAfter(now)) {
return memory;
}
}
}
} else {
List<Memory> memories = this.lambdaQuery().eq(Memory::getUserId, userId)
.eq(Memory::getBookId, bookId).eq(Memory::getDay, day).list();
for (Memory memory : memories) {
if (memory != null && memory.getLength() != 0 && !memory.getCreateTime().plusDays(memory.getDay()).isAfter(now)) {
return memory;
}
}
}
return res;
}
private List<Integer> saveLearningProgress(List<Integer> wordIds, Integer bookId, Integer day,
Integer count, Integer userId, LocalDate time) {
//修改本次学习情况
List<Integer> oldUpdate = wordIds.subList(count, wordIds.size());
this.lambdaUpdate().eq(Memory::getBookId, bookId)
.eq(Memory::getDay, day)
.eq(Memory::getUserId, userId)
.eq(Memory::getCreateTime, time)
.set(Memory::getWordId, JSON.toJSONString(oldUpdate))
.set(Memory::getLength, oldUpdate.size())
.update();
//修改学习或复习单词的个数
UserBookRelation relation = userBookRelationService.lambdaQuery().eq(UserBookRelation::getUserId, userId)
.eq(UserBookRelation::getBookId, bookId).one();
if (day == 0) {
userBookRelationService.lambdaUpdate()
.eq(UserBookRelation::getId, relation.getId())
.set(UserBookRelation::getLearnWordCount, relation.getLearnWordCount() + count)
.update();
}
//把本次学习的单词保存到day+1天后的记录中,但一个单词最多复习4轮
if (day == 4) {
return oldUpdate;
}
wordIds = wordIds.subList(0, count);
Memory one = this.lambdaQuery()
.eq(Memory::getUserId, userId)
.eq(Memory::getBookId, bookId)
.eq(Memory::getCreateTime, LocalDate.now())
.eq(Memory::getDay, day + 1).one();
if (one == null) {
Memory memory = new Memory();
memory.setBookId(bookId).setWordId(JSON.toJSONString(wordIds)).setUserId(userId)
.setLength(wordIds.size()).setDay(day + 1);
this.save(memory);
} else {
List<Integer> all = JSON.parseObject(one.getWordId(), List.class);
all.addAll(wordIds);
String allWords = JSON.toJSONString(all);
this.lambdaUpdate().eq(Memory::getId, one.getId()).set(Memory::getWordId, allWords)
.set(Memory::getLength, all.size())
.update();
}
return oldUpdate;
}
小结
需要源码的小伙伴请私聊我