书架重构设计(精简版)
by hzwusibo 20181210
一、 业务描述
重构最重要的首要任务是梳理功能结构。书架模块主要包含以下以下几个界面:

用例图:

中除了 WIFI 传输、导入本地书籍、查看阅读历史以外的所有用例都属于书架首页这个界面内部。
二、书架架构(Android):
书架主要分为三层、 BookBean(对应数据库)、ShelfItemData(对应内存)、 ShelfModel(对应界面)

BookBean (和书架数据库一一对应, 用于网络获取、数据库存储)
ShelfItemData 列表为内存中数据(是 DB 读取,进行处理后(分组、分区、排序)的数据 list)。
ShelfModel 只用于显示(从 ShelfItemData 层生成),类似于 MVVM 中的 ViewModel。
ManagerShelf 进行对 ShelfItemData 层数据进行管理操作(置顶、分组、删除等),操作完成后更新ShelfModel界面层与 DB 。
三、客户端模块分层
模块化,将一个程序按照功能,分成相互独立的模块,以便每个模块只包含与其功能相关的内容,书架重构设计上把书架独立一个模块。

将书架模块、正文模块等不相干业务相互独立, 每个模块只包含与其功能相关的内容。 通过Arouter路由进行通讯。
框架服务层: 基础的组件,如网络、图片、通讯、工具类等
业务模块层: 书架、阅读器等
补充部分实现内容:
(实现的时候略有些区别)

阅读重构有些跟最初设计不符的地方,设计和实现分别见上面两张图,最终实现时业务层的搜索没有拆分成module,本来打算管理公共数据模型的module_common_model也没有使用,因为目前公用的数据模型还无法完全剥离,暂时还是放在module_base中,另外模块通信层目前看算是各个业务依赖的中间层,通信用到的数据模型也都放在了module_communication,下个Q阅读重构还会持续做,模块化也会继续实践和探索,下面的依赖图也会随着我们的不断实践来更新。
客户端模块分层后的工程结构:base基础的组件、 book_shelf书架、communication模块之间通讯等。

模块之间的通讯通过Arouter的服务管理实现, 以书架为例。
1、暴露书架对外提供的服务(声明接口,其他组件通过接口来调用书架的服务),接口统一在module_communication模块中声明,例如这里书架暴露了void addShelfBook(String id);接口。

2、在书架模块中实现外提供的服务的接口

3、其他模块调用,如购物车模块调用书架提供的加入服务。

四、书架对外接口:

1、从服务器刷新书架:1,限免领取特权领取成功需要刷新书架 2,开通包月后需要更新书架信息
void updateBookShelfFromServers();
2、根据用户名获取所有书架书籍
@param userName:用户名
@return 对应用户的书架书籍列表,列表元素数据模型见 {@link BaseBook}
List<SearchBook> getAllShelfDataWithUserName(String userName);
3、获取内存中所有的书籍,直接查询内存
List<SearchBook> getAllShelfData();
4、本地书加入书架
void addshelfLocalBook(LocalBook local, AddShelfLocalBookCallBack addShelfLocalBookCallBack);
5、加入书架
void addShelfBook(String id); // 新增一本书
void addShelfBookAndUpdateBook(String id, boolean is_voice_reading, int read_cont, float total_percent);// 新增并且更新信息
void addShelfBaoyue(String id);// 新增包月
void addShelfBooks(List<String> list);// 新增多本书
void syncAddBooks(List<String> list);// 同步后台新增书
void addShelfCallBackListener(AddShelfBookCallBack callBack); // 添加回调
void removeShelfCallBackListener(AddShelfBookCallBack callBack); // 移出回调
6、 打开了某本书,书架排到一个
@param bookid:书籍
void openBookItem(String bookid);
7、 更新书籍阅读进度
@param bookid 需要更新的书籍
@param read_cont 已读的章节数
@param total_percent 书籍阅读的进度百分比
void updateBookProgressById(String bookid, int read_cont, float total_percent);
8、更新是否是语音阅读
@param is_voice_reading 是否是语音阅读
void updateIsVoiceReading(String bookid, boolean is_voice_reading);
9、更新章节本地更新时间,用于去除小红
void doBookUpdateTimeLocal(String bookid);
10、查询一本书是否在当前用户书架
@param bookid 书籍id
@return true:在,false:不在
boolean isBookShelfBook(String bookid);
11、删除书架上的书
void offLineDeleteShelfs();
12、 同步上传书
void updateSyncBook(SyncModel model);
五.主界面设计
备注: 越红色越重要, BookShelf为书架主要最重要的类,ShelfListAdapter和ShelfGridAdapter为书架的adapter。 BookGroupDialog为组内书的弹出框,BookGroupDialogAdapter为相应的adapter。 以上5个类是书架最核心的类。 BookOperateGroup为分组操作, BookManageActivity为批量操作, BookOperatePopWindow为书籍长按操作弹框 。 绿色的部分为推展功能, ShelfFilterPopWindow为筛选框, BookRecommendPopWindow为书架推荐。

六、 初始化时序图

书架是属于 TabBar 的子页面,APP 打开的时候其就会进行初始化缓存数据,以下时序图描述的就是这个过程。
初始化 BookShelf 以及 DataManager
DataManager 根据 userName 获取 userName 对应的书籍记录
DataManager 对各区的书籍进行分组
DataManager 对各区的书籍进行排序
DataManager 对书籍进行分区
DataManager 对各区的书籍生成对应的 ViewModel
BookShelf 根据生成的 ViewModel 进行展示
七. 书架刷新流程时序图(ShelfManagerBookBean)

刷新流程阶段描述的是任何主动/被动因素导致界面重新请求服务器从而刷新界面数据的流程
整个流程大体分为几个部分(下面的流程跟时序图并不对应):
请求推荐数据:匿名用户或者登录用户都会针对性的返回一些推荐数据,这些数据也是需要展现在书架中的
请求 info.json 接口,此接口主要是返回书架界面主要的显示数据,例如书籍名称、书籍封面等等。此接口是全量拉取,是书架界面主要的瓶颈之一。
界面刷新
判定是否需要全量更新服务器和本地中的一方数据
请求 detail.json 接口,此接口是按需请求,也就是没有显示出来的视图不会请求对应书籍的详细信息。此接口主要包含的是书籍信息当中的用户行为相关的数据,例如书籍进度,包月数据等。由于是按需请求,所以这里可能需要有高并发。
合并 detail.json 到显示数据当中并进行界面刷新
重复4
时序图当中步骤 8. 根据用户操作时间戳比较是否已替换信息逻辑进一步可以细化为:
关健步骤
1、更新数据库
2、通知 显示界面重新从数据库读到内存, 再 initBookData (主界面、 组内, 批量删除界面),更新界面

时序图当中步骤 18. 根据书籍更新时间、书籍进度比较是否替换信息逻辑进一步可以细化为:
关健步骤
更新数据库 ,同时更新内存数据
通知显示界面刷新 (主界面、 组内, 批量删除界面)

需要注意的是客户端阅读过的书籍,相应的书架上的书籍就会显示本地的阅读进度,而非 detail.json 接口给的进度。
8 和 18 都是同步操作,但是本质上面两个操作还是有区别的:
第 8 步主要是更新书籍的基本显示数据
第 18 步主要更新的是用户阅读书籍进度以及包月信息等数据
7.1 分组操作流程图

7.2 分区操作流程图

7.3. 排序流程图

7.4. 重置Order流程图

7.6 . 加入置顶区流程图

7.7 移到第一本

八、 加入书架时序图
此图描述的是书架模块对外提供的加入书架接口,可能涉及到的业务有:

书籍详情加入书架
包月加入书架
外部提供加入书架的书籍id/包月id
书架请求获取添加书架接口(addshelf.json)
添加书架接口(addshelf.json)返回是否添加成功,并且返回相应书籍的“书架信息”,返回包含书籍与用户关联的数据。
书架将书籍的书架信息添加到缓存,并且将书籍添加到非置顶区的首位并刷新视图
书架将书籍的书架信息保存到数据库
同步书架数据 Android : 最后一步略有差别, 更新数据库,再从数据库读数据更新内存
8.1 删除流程图
图描述的是书架模块对外提供的删除书架接口,可能涉及到的业务有:
删除书架书籍
删除包月
删除、批量删除场景逻辑可以分为多种:

8.2 移出分组

九、 数据库设计
9.1. 书架表(book_shelf)


十、接口设计
10 /shelf/info.json
接口描述: 返回书架省略信息接口,用于展示/排列书架信息
10.2 /shelf/detail.json
接口描述: 批量接口,外层为数组
10.3 /shelf/saveGroup.json
接口描述: 批量接口,外层为数组
10.4 /shelf/addShelf.json
接口描述:添加到书架,支持包月以及书籍添加
10.5 /shelf/deleteShelf.json
接口描述:批量接口,外层为数组移除书架项。支持包月以及书籍的移除。