A4 - v3 - 比较麻烦的逻辑

A4 - v3 - 比较麻烦的逻辑

黄鹏宇 512 2022-04-04

一、主线规则:

  1. 卡片创建出来为待练习状态
  2. 该卡片需要在距离创建时间的第1天、第2天、第4天、第7天、第15天复习
  3. 如果提前背,不改变复习时间线
  4. 如果滞后背,会对应推迟复习时间线

比如,list在第0天创建,应该在[1,2,4,7,15]天后复习

如果他第1天没复习,在第2天开始第一次复习,则他的复习时间线变为了[2,3,5,8,16](距离创建时间的天数)

  1. 如果手动标记为已完成,则不会推荐复习
  2. 如果未学当日新学卡片,则会推至明日

二、现有数据库结构

三、须解决的逻辑清单

0. 卡片的状态

规则

1. 新学卡片:刚生成为 待练习,有一次练习为已练习,6次练习后为已完成,或者手动已完成。

2. 复习卡片:当天未复习为待练习,练习一次后为已练习/已完成。可手动标记已练习卡片为已完成。

实现

数据库只记录用户手动修改的已完成状态DELETED,其余状态动态判定。

怎么标记复习卡片的状态?

  • 已完成: 已完成,
  • 已练习: 练习时间 = 今天
  • 待练习: 练习时间 < 今天

1. 今日须新学的卡片

规则

  1. 要先选择词书
  2. 根据当前进度 生成 今日卡片
  3. 可以一次生成多张(根据每日目标 )
  4. 分为生成与获取
    今日卡片数量(日期=今日,或练习次数=0)
select (
(SELECT
	COUNT(*) 
FROM
	card
	RIGHT JOIN USER ON USER.openid = card.openid 
WHERE
	card.openid = 'o4nOp5Akkbm3J3aW2uNZuJp85jmg' 
	AND date = '20220407' 
	AND dict_code = USER.current_dict_code) +
	
	 (SELECT
	COUNT(*) 
FROM
	card
	RIGHT JOIN USER ON USER.openid = card.openid
	RIGHT JOIN practice_record r ON r.card_id = card.id 
WHERE
	card.openid = 'o4nOp5Akkbm3J3aW2uNZuJp85jmg' 
	AND r.seq = 0 
	AND dict_code = USER.current_dict_code 
	AND date != '20220407')) as sum;

接口

流程图

https://www.processon.com/view/link/624e0f3d5653bb0743c36976
返回所有今日的卡片,如果数量 < 目标数,则生成差值

2. 今日须复习的卡片

规则

  • 今日须复习且待练习的卡片,即须复习

    1. 卡片为当前选择的词书
    2. seq > 1
    3. 有[practice_record],且next_review_time < now()
    4. practice_time 不为今天
  • 今日须复习且已练习的卡片,即已复习

    1. 卡片为当前选择的词书
    2. practice_time 为今天
    3. seq > 1

流程图

https://www.processon.com/view/link/624e0f3d5653bb0743c36976

sql

        SELECT
practice_record.card_id,               card.dict_code,               card.CREATE_DATE                                             AS createTimeStamp,               ( SELECT DATEDIFF( practice_record.practice_time, NOW()) = 0 ) AS isTodayHasReview,               practice_record.seq                                         AS validPracticeCount,               ( SELECT count(*)                 FROM practice_card_record                 WHERE practice_card_record.card_id = practice_record.card_id                   AND openid = USER.openid )                                   realPracticeCount,               (                   SELECT
DATE_FORMAT( card.CREATE_DATE, '%Y%m%d' ))         AS date         
FROM
	practice_record                
	INNER JOIN card ON id = practice_record.card_id                
	INNER JOIN USER ON USER.openid = openid         
WHERE
	practice_record.openid = openid           
	AND card.DELETED = 0           
	AND DATEDIFF( NOW(), next_review_time ) <![ CDATA [>=]]> 0           
	AND card.dict_code = USER.current_dict_code           
	AND seq != 0         
ORDER BY
	card.CREATE_DATE   


展示

根据创建时间分组 => 不用一次返回给前端,只返回第一张+数量

接口

todo

3. 所有卡片

4. 学习 (core)

规则

  1. 学习后,会增加[ practice_card_record ]的记录
  2. 但不一定修改[ practice_record ]的记录,除非:当前时间 > next_review_time
  3. 如果修改,则next_review_time = [REVIEW_ARR][seq] + [上次学习时间的0点] ,practice_time = [当前时间的0点]
  4. 学习后要判断是否已完成今日复习/学习任务,如果已完成,则在[ finish_record ]表里去重新增。
  5. 如果学习的是seq = 0 的卡片,则修改他的date为今日,这样才能在今日推荐学习中刷到他

5. 本年、本月学习天数、连续学习天数

practice_date: 20220102

practice_time: datetime

  • 今年学习天数
SELECT
	COUNT(
	DISTINCT ( practice_date )) 
FROM
	practice_card_record 
WHERE
	LEFT ( practice_date, 4 ) = YEAR ( NOW() ) 
	AND openid = #{openid}
  • 本月学习天数

这里的RIGHT ( 100 + MONTH( NOW() ) 是为了将 1、2、3月=> 01,02,03

SELECT
	COUNT( DISTINCT ( practice_date ) ) 
FROM
	practice_card_record 
WHERE
	substring( practice_date, 5, 2 ) = RIGHT ( 100 + MONTH( NOW() ), 2 ) 
	AND openid = #{openid}
  • 连续学习天数
SELECT max(count)
                FROM (
                         SELECT openid,
                                practice_date,
                                IF(@previousid = openid AND DATEDIFF(@previouspractice_date, practice_date) = 1,
                                   @count := @Count + 1, @count := 1) as `count`,
                                @previousid := openid,
                                @previouspractice_date := practice_date
                         FROM practice_card_record,
                              (SELECT @previousid := null, @count := 1, @previouspractice_date := null) a
                         ORDER BY practice_date desc) a
                where openid = #{openid}      as 'lastDayCount'

6. 侧边栏的日历

  1. 计算起始日期
    当前日期的周日 -> 当前日期的周六
  2. 得到该日的完成情况 (0,1,2)
  3. 补全空数据

SELECT
	d.date,
	IFNULL( T.STATUS, 0 ) STATUS 
FROM
	(
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) + 1 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) + 0 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) - 1 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) - 2 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) - 3 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) - 4 DAY ) AS date UNION ALL
	SELECT
		DATE_SUB( CURDATE(), INTERVAL WEEKDAY( CURDATE()) - 5 DAY ) AS date 
	) d
	LEFT JOIN ( SELECT STATUS, CREATE_DATE FROM finish_record WHERE openid = 'o4nOp5Akkbm3J3aW2uNZuJp85jmg' ) T ON T.CREATE_DATE = d.date

image.png

7. 今日X人已完成学习计划

[ finish_record ] 表
image.png

8. 随心一张

在该用户card表里,随机挑选一张,满足当前词书的卡片

9. 去除不需要背的单词

+个ignore
这里是全局删除吗?

10. 今日复习计划、今日新学计划

  • 需复习卡片数:

    1. 卡片为当前选择的词书
    2. seq>=2
    3. 有[practice_record],且next_review_time < now()
    4. practice_time 不为今天
  • 已完成复习数:

    1. 卡片为当前选择的词书
    2. practice_time 为今天
    3. seq > 1
  • 需新学数:
    MAX(每日计划量 / 5 - 已完成学习数,0)

  • 已完成学习数:
    今日创建的卡片 且 seq = 1

12. 消息推送