最近在做一个网页的小说阅读器,因为之前没有接手过正式的项目所以走了很多弯路,现在项目接近尾声,所以做个随笔总结一下:
- git工具的使用
- 接口文档的使用
- 前后端交互的心得
- 目录树的构建
- 优化心得
目录树构建
参考链接
实现思路
前端使用elementUi框架实现,后端生成树结构的目录节点生成嵌套结构
树节点pojo
package com.dms.guyiyao.pojo;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Data
public class TreeNode implements Comparable{
//节点id
private String id;
//节点层级
private Integer grade;
//章节名
private String chapter;
//父节点id
private String parentId;
//子节点
private List<TreeNode>cataLog;
/*节点结构化状态*/
private boolean flage;
/*
* 排序
* */
private int sequence;
@Override
public int compareTo(Object o) {
TreeNode treeNode=(TreeNode)o;
if (this.getSequence()>treeNode.getSequence())return 1;
else if (this.getSequence()<treeNode.getSequence()) return -1;
return this.chapter.compareTo(treeNode.chapter);
}
}
网上找到的参考案例是递归算法实现父子结构,如果目录树的节点太深(多)可能会导致栈溢出的问题
所以优化了一下,改为了非递归的算法:(具体实现除了这个函数以外还有其他两个函数,具体代码去看看上面的第二个链接)
//使用非递归算法构建父子关系,flage判断改节点是否找到父节点,如果为true表明已找到直接跳过
Stack stack=new Stack<>();
public TreeNode buildChildTree(TreeNode pNode){
System.out.println("顶级"+pNode.getId());
List<TreeNode>childTree=new ArrayList<TreeNode>();
stack.push(pNode);
while (stack.size()!=0){
TreeNode parNode= (TreeNode) stack.pop();
for (int i = 0; i < nodeList.size(); i++) {
if(!nodeList.get(i).isFlage()&&nodeList.get(i).getParentId().equals(parNode.getId())){
nodeList.get(i).setFlage(true);//子节点已被收纳,不在被其他节点收纳
childTree.add(nodeList.get(i));
stack.push(nodeList.get(i));//子节点成为新的父节点
}
}
// 根据sequence排序
Collections.sort(childTree);//通过sequence字段排序
parNode.setCataLog(childTree);
childTree=new ArrayList<TreeNode>();//清空数组不然会导致栈溢出
}
// 返回顶级节点
return pNode;
}
递归算法->非递归算法的转换
递归:
按照递归算法的做法只有在子节点都不满足递归条件后才会有返回,顶级节点才能返回:可以理解为所有子节点都有返回值父节点才能返回。这个过程会在JVM的栈区消耗大量空间
非递归:
利用Stack,保存父节点的工作进度既:顶级父节点找到他的所有子节点,建立父和子(子节点放入数组),子与子的关系(子与子在数组的排序)后将找到的子作为新的父节点放入栈。然后进行下一次栈循环。
优化心得
- 异步优化
- IO优化
刚刚开始这个项目的时候,经验不足,导致后期重构了大量的代码。
异步优化
义务需求里面有一个批处理的需求,批量导入excel中的数据到数据库,刚刚开始的时候没有考虑到远程数据库IO特别慢的问题,程序中存在大量的数据库IO,一万两千条数据大概花了2个时候才上传成功,后面上了多线程的异步请求。把IO操作单独独立出来(这里的io是insert操作。他的返回值不会影响他后面的操作,所以程序没有必要阻塞在这里等它)
package com.dms.guyiyao.service.Impl;
import com.dms.guyiyao.mapper.TsBookDifferentMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncService {
@Autowired
TsBookDifferentMapper bookDifferentMapper;
@SneakyThrows
@Async("asyncPoolTaskExecutor")
public void DiffAdd_IO(String contentFrom_uuid,String contentTo_uuid, String contentFrom, String contentTo, String chapterIdFrom, String chapterIdTo,String datetime){
long t1=System.currentTimeMillis();
bookDifferentMapper.addDiffContent(contentFrom_uuid,contentFrom,chapterIdFrom,datetime);
bookDifferentMapper.addDiffContent(contentTo_uuid,contentTo,chapterIdTo,datetime);
bookDifferentMapper.addConnection(contentFrom_uuid,contentTo_uuid,datetime);
long t2=System.currentTimeMillis();
System.out.println("IO线程花费时间:"+(t2-t1));
}
}
参考链接:
@Asnc配置
IO优化
优化思路和上面使用异步请求一样。因为像这种接口调用,特别是远程数据库的IO特别拖慢程序的速度,在系统的主体代码完成之后。肯定需要抽离出来进行优化。
这里的思路是减少接口调用次数,一个接口完成多个接口的功能
<select id="getAllDiffInfo" resultType="com.dms.guyiyao.pojo.ContentDiff">
SELECT t1.id as contentIdFrom,t1.content as contentFrom,t2.id as diffId,t2.jsonFrom,t2.jsonTo,t11.content as contentTo,t11.id as contentIdTo,a.bookname as bookName,b.id as chapterId, b.chapter_name as chapterName
FROM ts_book_content_diff as t1,ts_book_different as t2 ,ts_book_content_diff as t11, ts_book as a,ts_book_chapter as b
WHERE t1.id=t2.ts_book_content_id_from
and t1.ts_book_chapter_id=#{chapterId} and t1.id=t2.ts_book_content_id_from and t11.id=t2.ts_book_content_id_to and a.id=b.ts_book_id AND b.id=t11.ts_book_chapter_id
</select>
在编码初期为了快速出效果。所以针对不同的表写了大量的数据库访问接口。产生了大量的IO。后期压缩成一条sql语句。程序效率极大提升。
前后端交互的心得
docway接口文档管理
swagger笔记_狂神
前后端分离是合理的。但是前端和后端交给不同的人来做真的太鸡肋了。特别是如果都没有什么经验的话。前端和后端的交互。就像是程序的接口调用一样。可能项目的结构性和可维护性变强了。但是这个消耗真的太大了。如果前后端一个人贯通的话。对项目开发周期,代码的健壮性,后期扩展性都是极大的提升。
经验
- 系统设计不应该是后端一个人的事情,设计数据库时也应该和前端进行交流。因为最后所以的数据都是交给前端在使用。如果前端的实现需要的数据和系统设计的数据库不太一样。开发到一半可能就会发现突然崩盘。因为一个功能模块的实现往往需要其他几个功能模块的支持。(比如这个项目的异文就出现过这种问题)
- 接口的规定,应该是并发进行的。如果前端的进度快,前端应该使用docway这种接口文档把接口写好。如果后端的进度更快,则可以直接用swgger这种带测试功能的框架把接口接上。前端拿到后可以直接测试使用。
- 后端编码的前期写的代码粒度应该尽可能的小。提高代码的通用性。方便后期有相同需求时直接调用,或者cv后进行修改。(比如这个项目的图片上传和操作excel表就多次使用了某些代码)