大家好,我是鱼皮,今天分享我的老弟第一次写后端代码时出现的囧事,希望大家引以为戒。
我的老弟小阿巴,目前大一,自学编程有一段时间了,之前主要以学前端为主,比较好上手。
但这货最近不知道咋回事,一直嚷嚷着要写写后端代码。
我说:你前端才刚学没多久呢,急什么?
小阿巴说:没人比我更懂前端!
好家伙,没想到几日不见,这家伙现在这么骄傲了!那必须得杀杀他的锐气,让他领略一下后端的恐怖。
于是我说:成,正好我最近在开发一个新功能【删除消息】,功能很简单,允许用户删除自己已经阅读过的消息。前端后端都交给你来做,时间也不急,给你两周,拿去练练手,熟悉下项目吧~
没想到小阿巴这狗说:两周?瞧不起谁呢,就这么一个小功能,我 3 天给你搞定!
我大惊:现在的年轻人都这么强了么?行,我等你!
没想到,不到 3 天,小阿巴真的提交了代码,让我们一起来看看他的实现思路和代码吧。
功能实现包括前端和后端两部分,分别来思考一下。
要实现用户已读消息删除功能,前端的工作比较简单,添加一个删除按钮,并且给按钮添加一个点击事件,点击后调用后端 消息删除 接口即可。
前端界面
小阿巴写的前端代码:
<!-- 伪代码 -->
<button onClick={doDelete(消息)}>删除</button>
<script>
// 删除消息
function doDelete(msg) {
// 消息 id 存在且为已读
if(msg.id && msg.isRead) {
// 调用后端接口
service.deleteMsgById(msg.id);
}
}
</script>
看着好像没啥问题吧,写的还挺工整的,代码规范好评!
再看看后端怎么样。
后端要做的事情就是接受前端发过来的请求,操作数据库,将数据库中指定 id 的消息删除,再返回是否删除成功给前端。
存放消息的数据库
很多编程语言都可以拿来写后端,比如 JAVA、Go 语言等。但由于小阿巴是第一次做后端,我心疼他,所以让它使用 NodeJS(JavaScript 语法)来写。
看看小阿巴写的后端代码:
// 删除消息接口
// @params msgId 消息 id
function deleteMsgById(msgId) {
// 调用数据库删除函数,得到结果
const res = db.deleteById(msgId);
return res;
}
总共就这么几行代码,简洁清晰,也难怪小阿巴花了 3 天的时间就写出来了。
不知道大家觉得这段代码怎么样,像不像自己第一次写的代码呢?
请大家思考一下,他写的代码有没有什么问题?
其实,小阿巴这段代码问题非常大!一旦上线了,后果不堪设想!
主要有三个问题,我把小阿巴叫了过来,要根据问题的严重性从大到小给他盘一盘。
我对小阿巴说:再仔细看看你的代码,是不是少了校验逻辑?
小阿巴疑惑:我不是在前端判断消息 id 是否存在、消息是否已读了么?
我:那如果用户不在浏览器里点删除按钮,而是直接请求接口,随便传消息 id 呢?
小阿巴陷入了沉思。
这是第一次写后台的同学经常犯的错误,尤其是前后端都一个人写的时候,以为在前端判断参数是否合法就够了。但其实,恶意用户可不管这么多,他们可以直接用各种工具在浏览器外向你的后端发送请求,随便传一些消息 id,甚至直接遍历可能的 id。如果后端不做校验,一上线,正常用户的消息可能就被删光了!绝对的 最高级事故!
小阿巴:没想到这么严重,那我在后台补上对消息状态的校验,好像也不太行吧?毕竟用户可以任意传递请求参数。
我:挺聪明嘛,的确如此,所以我们还要补上对当前登录用户的校验。
完善的代码大致是这样的:
// 删除消息接口
// @params msgId 消息 id
function deleteMsgById(msgId) {
// 校验参数合法性
if (!mgsId) {
return false;
}
// 从数据库查消息最新状态
const msg = db.getMsgById(msgId);
// 从 session 或中间件获取当前用户信息
const user = getCurrentUser();
// 消息未读或不是该用户的消息
if (!msg.isRead || !user || msg.userId !== user.id) {
return false;
}
// 调用数据库删除函数,得到结果
return db.deleteById(msgId);
}
小阿巴:我记住啦,后端接口是业务核心,一定要加强校验!
我:不错,来看看其他的问题吧。
我:在你的代码中,直接调用了 delete 函数直接删除数据,你知道这会有什么问题么?
小阿巴:有啥问题?
我:直接删除数据,会导致管理员在需要排查问题时缺少线索。比如用户发送过违规消息,一段之间后把消息自己删掉了,那管理员也不能查出他的违规记录了。
小阿巴:还真是,那咋办?这数据不能删?
我:一般会采用 软删除,给数据表添加一个额外的字段来表示删除状态,比如 isDelete,状态为 0 表示未删除,为 1 表示已删除。正常情况下,只给用户展示 isDelete = 0 的数据,删除时,将该字段的值从 0 更新为 1 即可。
所以上述代码的最后那部分,可以略作修改:
// 原代码,真实删除
db.deleteById(msgId)
// 新代码,软删除(更新)
db.updateById(msgId, {isDelete: 1})
这样后端代码就基本完善了。
当然,也不是所有的数据表都需要软删除,要根据业务场景来决定。
最后还有一个产品体验上的小问题,建议在用户点击删除时,出一个二次确认的弹框,否则用户不小心点错了,想找却又找不回消息,那就会感到愤怒了!
确认删除
前端开发做的越多,就会越注重这些小细节,提升用户体验非常重要!
小阿巴:学到了,学到了!我好菜啊 555。
我:没事,这是很正常的,知错能改,就还是好阿巴。
很多正在阅读文章的朋友们,是否也犯过这些小错误呢?请养成良好的编程习惯,多多检查自己的代码吧!