并不简单的基础开发工作(二):信息展示列表
本文以被各大学校学生做滥了的“学生信息管理系统”为例,讲述信息展示列表开发中的一些问题。
需求
本文只讨论一个页面:学生信息管理系统中的学生列表页面,大体上样式如下(PC浏览器)
身份证号码: 姓名: 学号:
序号 | 身份证号码 | 姓名 | 性别 | 民族 | 户籍省份 | 市 | 区县 | 出生日期 | 学院 | 班级 | 学号 | 联系电话 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1234...78 | 一花 | 女 | 汉族 | 北京市 | 西城区 | 19XX年X月X日 | 计算机与软件工程学院 | 软件1901 | 201901010101 | 13012345678 | |
2 | 1234...79 | 二乃 | 女 | 汉族 | 上海市 | 黄浦区 | 19XX年X月X日 | 计算机与软件工程学院 | 软件1901 | 201901010102 | 13012345679 | |
3 | 1234...80 | 三玖 | 女 | 汉族 | 广东省 | 广州市 | 越秀区 | 19XX年X月X日 | 计算机与软件工程学院 | 软件1901 | 201901010103 | 13012345680 |
4 | 1234...81 | 四叶 | 女 | 汉族 | 浙江省 | 杭州市 | 西湖区 | 19XX年X月X日 | 计算机与软件工程学院 | 软件1901 | 201901010104 | 13012345681 |
5 | 1234...82 | 五月 | 女 | 汉族 | 江苏省 | 南京市 | 鼓楼区 | 19XX年X月X日 | 计算机与软件工程学院 | 软件1901 | 201901010105 | 13012345682 |
每页显示条,共49条 /5页
原理
列表页面的原理比较简单,不考虑分页的话,SELECT idcard, name, gender, ... FROM students WHERE 一些条件
,然后将查出来的结果全部展示到页面即可。但是加上分页之后逻辑会复杂很多,因此建议事先将分页功能封装成公共函数或组件(本文不再讨论分页的实现方式)。
对于有固定取值的数据(性别、省市县、学院等),建议准备专门的字典表或配置文件,展示时从字典中取值。建议将读取字典数据也维护成公共函数或组件。
设计方面的问题
屏幕分辨率
开发人员经常使用大显示器,分辨率比较高,而用户的显示器五花八门,有大有小,甚至在当今这个满街液晶显示屏的时代仍然使用大脑袋瓜子的1024x768 CRT显示器。因此,设计页面时要考虑多种显示器宽度和高度,例如采用响应式布局,或者去除不必要的东西,或者加水平滚动条,总之要避免让窄屏用户看到像本博客上面那样“东西装不下”的页面效果。
如果你的显示器分辨率比较高,建议把一些常用的宽度和高度测量好,贴在你的显示器上,并且在调试的时候测试不同尺寸的浏览器窗口。
不清楚实际数据规模
一个班级大约有二三十人,而一个年级会有几十至几百人,一个学校可能超过上万人。如果不考虑实际数据规模,设计和实现出来的东西有可能会使用户困扰,例如没有分页(然后把全校上万人全部展示出来)、缺少导入功能(上万人信息需要一个一个地录入)和批量操作功能(上万工作单需要一个一个地提交)。
即使故意要求给用户增加障碍,例如审核工作单时必须一个一个地操作,我们其实也可以设法在不违反原则的前提下给用户提供一些便利,例如审核完成后自动跳转到下一工作单,并提醒还有多少工作单未处理,而用户不想处理的单子也可以轻松跳过。
没有搜索/搜索条件不足
数据很多的时候,应当给用户提供搜索功能,便于很快从大量数据中找到需要的内容。另外设计查询条件时要了解用户希望如何定位数据,避免漏掉常用条件或提供很多多余条件,例如去营业厅查电话费,以身份证或手机号中的前几位或后几位作为查询条件比较靠谱,而用姓名和性别作为条件就不靠谱。
对于上面的表单来说,查询条件里便缺少学院、班级和学号,并且身份证号和学号最好是模糊查询,因为除了学生本人以外很少有人能把这些号码记得一个数字都不差,而且就算拿着本人身份证或学生证,敲数字也挺麻烦的。
排序
展示数据最好排个顺序(默认排序顺序应当取决于用户经常做什么事情),而且要让用户能选择给哪一列排序,例如拿学生名单核对数据时会希望按学号排序,找人的时候会希望按姓名拼音排序,统计生源地时会希望按出生地排序……数据库里面什么顺序就展示什么顺序的话,没准会让用户头大。
个人隐私
身份证号、手机号等数据属于个人隐私,没有必要的话不要随意展示给用户。如果确实需要展示,建议考虑脱敏处理(即使本人录入的也是,谁敢说不会盗号呢),例如130****5678。
展示逻辑删除的数据
对于删除的数据,无论是物理删除还是逻辑删除,只要删除就没必要再展示给用户了(除非另外设计“回收站”功能)。例如上面表单中有一个“状态”,如果“删除”的操作只是把这个“状态”由有效变成无效,那么建议直接去掉本列,而且使“无效”的数据不再呈现。
实现方面的问题
跨站脚本攻击
举个例子,在数据库里维护一个名字叫张三<script>alert('xss');</script>
的学生,若加载页面时弹出个窗口,说明页面有跨站脚本攻击的风险。
跨站脚本攻击对策有多种,建议用比较彻底的一种:将页面上的动态输出设置为默认转义。换句话讲,用<%= name %>
这类模板输出内容时,让模板引擎默认自动将其中的HTML代码转义,这样在页面上展示的就是张三<script>alert('xss');</script>
而非张三和一个弹框。(不过前提是模板引擎支持。如果不支持,那么建议做一个统一的转义标记,并要求输出用那个统一标记)
性能
查询要注意性能问题。例如
数据库索引
如果该建索引的地方没建索引,或者SQL写得很糟糕,根本没走索引,那么查询速度自然会变慢。
分页
以Oracle为例
SELECT * FROM (
SELECT s.*, s.ROWNUM rn FROM (
SELECT * FROM students where ...
) s WHERE rownum<=10000000+10
) WHERE rn>=10000000;
的效率要比
SELECT * FROM students WHERE ROWID IN (
SELECT id FROM (
SELECT s.ROWID id, s.ROWNUM n FROM (
SELECT * FROM students WHERE ...
) s WHERE rownum<=10000000+10
) WHERE n>=10000000
);
低一些,因此建议按后者查询。
反复查数据库
出生地、民族等字典数据可能也存放在数据库中。如果每次获取取值都查一遍数据库(特别是循环里面查数据库)会非常影响性能。由于字典数据变化并不频繁,建议将此类数据做成缓存,一次性从数据库查好并缓存之后就直接从缓存里面取值。
缓存会有缓存时效以及线程安全等问题,这里不再展开讨论。
模糊查询
模糊查询,特别是双“%”(LIKE '%字符%'
)会影响性能,如果不能去掉模糊查询功能,那么要控制好条件,避免大幅度的表扫描。
越权访问
假如查看的链接是“/students/view.do?id=10001”,那么将id=10001改成不属于你的10002,你能否看到数据呢?如果能,说明存在漏洞,应当在查数据时附上身份验证(例如在SQL加上是创建者本人的查询条件),确保用户只能看到自己的数据。
拒绝服务
假如查询查出总共10000条数据(当然是把分页之后的数据合到一起),你能否通过修改请求的方式让每页展示10000条数据?如果能展示,而且性能开销还不小,那么别人也可以用类似的方法使系统变慢甚至瘫痪。
你的查询耗时长吗?如果耗时比较长,而且SQL层面的优化已经做得很到位,无法再进一步提高效率,那么建议增加按钮变灰(点一下查询按钮之后就无法再点击,直到查询完成)、动画反馈(Loading...)和限制操作频率(例如禁止频繁操作,或要求先输入验证码再查询)等措施,以免用户频繁提交影响系统整体性能。
SQL注入
老生常谈的问题,不再详细讨论。假如你在某个查询条件框输入' or '1'='1
却能查出很多数据,那么你要赶紧补漏洞了。
未防止误操作
删除是一个“有危险性”的操作,如果删除之后重建比较麻烦,那么当用户点击“删除”按钮时,系统最好不要直接执行删除,而是弹出一个提示框,让用户确认无误之后再删,以免用户误操作。提交工作单(提交成功之后就不能再修改内容了)之类的“重大”操作也是如此。