2022-04-22 00:56:57
从今天开始就是一些比较繁杂的业务了,比如友链、番剧、仓库。对于这三个模块,每个模块都有大量的业务需要处理。
目前暂且将灵感和TODO模块给删除了,还在考量其必要性。
然后今天完成的主要工作就是,友链的各项后端接口设计:友链的增删改查、评论的增加与分页查询。前端就是友链的展示样式设计、评论的添加与回复实现(大把时间在设计和改这个上)。下面先放两张成果图:
浏览了大量的他人的博客,一直很好奇他们的评论头像是怎么产生的,于是不断分析与查询,得知有个名为gravatar的网站提供了这个服务,不过部署在国外的,访问需要一些措施。所以这个网站不可行,那自然而然就会想到国内镜像,果然有不少,比如https://gravatar.loli.net/avatar/、https://cravatar.cn/,我这次采用的是后者。
头像生成的步骤是:
1. 获取邮箱地址
2. 根据邮箱,将邮箱字符串使用CP1252编码成字节数组,之后对该字节数组进行md5加密成16进制形式。
3. 将编码后的hash值假如到访问地址的指定位置即可返回相应的头像。
然后对于cravatar站的头像生成有个好处就是其依据的头像获取逻辑是:cravatar > gravatar > qq, 也就是对于已有gravatar的用户也不用单独配置cravtar,就算没有,如果你的邮箱是qq邮箱,那么还会生成对应qq号的头像,所以一般情况都是会有一个最关联的头像产生。对于一二步骤于springboot中非常简单,我单独封装成了一个工具类:
package top.dreamcenter.dreamcenter.utils;
import org.springframework.util.DigestUtils;
import java.io.UnsupportedEncodingException;
public class HashConstructor {
public static String byEmail(String email){
try {
return DigestUtils.md5DigestAsHex(email.getBytes("CP1252"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
}
对于第三步,肯定还是要防范目标站点出现故障的,所以也采用了配置方式来设定,并且将链接规则设定为:[&hash&]部分将被替换成实际hash码,实际运用例如:
// https://s.gravatar.com/avatar/[&hash&]?s=80
// https://cravatar.cn/avatar/[&hash&] china
// https://gravatar.loli.net/avatar/[&hash&]?d=identicon loli
String rawUrl = "https://cravatar.cn/avatar/[&hash&]?d=mp";
String result = rawUrl.replace("[&hash&]",HashConstructor.byEmail("1981669259@qq.com"));
System.out.println(result);
当然rawUrl是从配置读取出来的,这里只做适当转换方便理解。
处理起来相当的繁琐,前后数据表改了大约有三次。第一次是评论一开始数据库关系模式设计的时候忘记考虑了时间字段,一次是最开始对评论回复的理解不深入,只设计了parent和当前id来解释层次关系,然而实际操作中发现,在楼中回复楼时,目标可能就不是parent和id了,所以增加了一个target字段来辅助。也就是一个评论或者回复需要parent确实所属大评论层次,一个id表示当前子评论,一个target表示当前评论回复的对象,方便后续@target:msg来进行展示具体回复的对象。实际存储传递中target存储的是点中的评论的id。parent表示当前评论树的根所在。所以整个mybatis设计如下:
<select id="selectListReview" resultMap="listReview">
SELECT * FROM friend_review WHERE parent=0 ORDER BY time LIMIT #{begin},#{size}
</select>
<select id="selectListChild" resultMap="listChild">
SELECT fr1.id, fr1.nickname, fr1.email, fr1.time, fr1.url,fr1.tip,CONCAT(fr2.nickname,':',fr1.msg) msg
FROM friend_review fr1
LEFT JOIN friend_review fr2
ON fr1.target=fr2.id
WHERE fr1.parent=#{id}
</select>
<resultMap id="listReview" type="top.dreamcenter.dreamcenter.vo.FriendReviewVO">
<id property="id" column="id"/>
<result property="nickname" column="nickname"/>
<result property="email" column="email"/>
<result property="time" column="time"/>
<result property="url" column="url"/>
<result property="msg" column="msg"/>
<result property="tip" column="tip"/>
<collection property="child" column="id" select="top.dreamcenter.dreamcenter.mapper.FriendReviewDao.selectListChild"/>
</resultMap>
<resultMap id="listChild" type="top.dreamcenter.dreamcenter.entity.FriendReview">
<id property="id" column="id"/>
<result property="nickname" column="nickname"/>
<result property="email" column="email"/>
<result property="time" column="time"/>
<result property="url" column="url"/>
<result property="msg" column="msg"/>
<result property="tip" column="tip"/>
</resultMap>
只需要调用selectListReview即可,其它的是分步查询及resultMap的设定,这里有自联表操作,原本没打算用分步方式,但是处理起来的逻辑实在是过于复杂,所以最终用分步方式解决了问题。
友链的设计还是非常简单的,这里用来flex布局,width 45%,所以左右会有空位,左边就插入了一个h1标签,限于空间,自动的就转为竖着展示了。
评论的那个表单框我单独抽离成了一个component组件:review,因为后面可能在每一个父级评论下展现,组件中使用了slot槽用于扩展,并且使用props来进行参数的传递,因为如果是创建一个父级的评论,那么无需取消回复功能,而作为内部回复用的话,需要取消回复,这样就可以继续发布父级评论而不被卡死在回复中了。
对于review组件,所需要传递的就只有parent和target两个属性,其它都是可以组件内产生的,review在哪里显示?我设置的每一个父级评论后都有一个v-if="parent===item.id"状态的review,只要点击了某个父级评论树,那么v-if=true,就会展示了,而对于创建父级的review,我们设定其parent=0,所以只要重置parent为0,则就只会显示在上方了。
以上就是今天的收获与总结。明天预计要完成友链页的完整功能,大概可以?定的明天清单如下:评论回复发邮件、评论后刷新策略、评论分页、管理友链。尽量完成!