一.前言
本文描述笔者做的一个基于lamp(Linux+Apache+MySQL+PHP)开发的简单的web应用,该应用采取的架构风格为以数据为中心的“仓库”风格,主要模块是php与mysql交互的部分。本文是从初学者角度尝试着写的,用于学习交流,且并未列出详细代码,希望大家能对本文不足的部分加以指正。
以下主要分为需求分析、架构设计、功能实现、质量属性分析、总结这几部分。
二.需求分析
健康管理系统是指对个体或群体的健康进行全面的监测、分析、评估,同时提供健康咨询和指导以及对健康危险因素进行合理干预的过程。在本次实验过程中,实现的系统功能关注本次疫情,实现如下4个子需求:
1 .录入个人健康的数据。 健康信息需求主要取决于用户自身健康状况,是根据用户实际健康状况而提出的对预防、康复以及保健等相关健康信息的需求。将个人相关信息储存在数据库中;并且能在查询时反馈是否存在异常情况,也就是对有不良症状或者接触疑似、确诊病例的标红。
2 . 删除个人数据。管理员可删除健康打卡系统的用户信息,在删除过程中,系统应该可以反馈要删除的某条记录,在管理员确认后删除。
3. 修改个人信息。在健康管理系统中,用户应该能及时更改个人的信息,同样地,系统应该可以反馈要修改的某条记录,在管理员确认后进行修改。 保证数据的实时有效。
4. 查询所有信息。表格分页显示所有提交信息的人,对出现异常的(有不良症状或者接触疑似、确诊病例)的标红。
三.架构设计
1.设计概述
架构设计的目标是描述系统关键的功能性需求和质量约束,通过前一步的需求分析明确系统的关键功能和约束对系统架构非常重要。本系统共有四个关键功能需求,因此确定四个关键功能。就系统架构风格而言,考虑到这四个功能均是和数据库交互,所以联想到了“仓库”风格实现的可能,最终本系统采用以数据为中心的架构风格中的“仓库”风格。对于系统的整体描述,采用UML建模语言,通过RUP4+1视图描述系统(实现视图通过第三部分功能实现描述)。下图为本系统的架构风格示意图。
2.开发策略
本系统基于B/S的web开发,在服务器端,本地主机安装了Apache服务器,使用php语言处理数据,与MySQL数据库交互。在客户端,使用HTML、JavaScript、CSS编写.html的文档,使用Chrome浏览器测试。
3.场景视图
基于前文分析的需求,得到两个角色:管理员和用户。产生不同的功能需求,使用用例图描述,下图为功能性需求的描述。
用例说明书:
4.逻辑视图
逻辑视图从系统内在逻辑结构的角度描述系统的基本结构和动态行为,通常包括分析模型,设计模型以及数据模型等。设计模型说明了系统的组成元素,组织架构和关系,并描述了个组成元素的协作和约束。下图为本系统的层次模型。
5.部署视图
部署图是在描述系统硬件的物理拓扑结构及在此结构上执行的软件。部署图可以显示计算节点的拓扑结构和通信路径、节点上运行的软件组件。在UML中,部署图显示了系统的硬件和安装在硬件上的软件,以及用于连接异构计算机之间的中间件。部署图通常被认为是一个网络图或者物理架构图。本系统部署在本地主机上,但也可以修改配置参数,将数据库服务器和web服务器部署在一个局域网的不同设备上,客户端的浏览器部署在用户机上。下图是本系统的部署图。
6.进程视图
进程视图侧重于系统的运行特性,主要关注一些非功能性需求,例如,系统的性能和可用性等。进程视图强调并发性、分布性、系统集成性和容错能力,以及逻辑视图中的功能抽象如何适合进程结构等,它也定义了逻辑视图中的各个类的操作具体是在哪一个线程中被执行的。进程视图可以描述成多层抽象,每个级别分别关注不同的方面。下图使用UML画顺序图来描述进程视图:
录入个人信息:
删除个人信息:
修改个人信息:
显示所有记录:
四.功能实现
本系统采用的架构风格是以数据为中心的“仓库”风格,本部分描述作为“仓库”的数据库的数据表维护和周围的功能模块,以及描述实现策略的具体细节和依赖框架的一般方法,也可以看作实现视图的描述。
- “仓库”中维护的数据表
mysql> show create table item \G; *************************** 1. row *************************** Table: item Create Table: CREATE TABLE `item` ( `name` varchar(30) NOT NULL, `idType` varchar(20) NOT NULL, `idNum` varchar(20) NOT NULL, `phoneNum` char(11) NOT NULL, `address` varchar(50) NOT NULL, `travelHistory` varchar(90) NOT NULL, `symptom` int NOT NULL, `contact` int NOT NULL, PRIMARY KEY (`phoneNum`), UNIQUE KEY `idNum` (`idNum`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 录入个人信息
描述:点开之后,服务器返回名为hms_wirteForm.html文档,在这个文档 中,form为主要元素,使用css定义一些样式,使用JS来判断输入的合法性。提交表单后,将通过post方法提交给hms_writeFormData.php。在这个文档中,使用sql预处理方法防止sql注入,sql语句为插入元组。另外,对于是否有症状,转换为1/0存入数据表中;对于用户输入验证,在JS中采用几个正则表达式来判断。
sql预处理的PHP代码
// 预处理及绑定
$stmt = $conn->prepare("INSERT INTO `item` (`name`, `idType`, `idNum`,`phoneNum` ,`address` ,`travelHistory`,`symptom`,`contact` ) VALUES (?, ?, ?, ?, ? , ?, ?, ?)");
$stmt->bind_param("ssssssii",
$_POST['name'], $_POST['idType'], $_POST['idNum'],$_POST['phoneNum'],$_POST['address'],$_POST['travelHistory'],$symptom,$contact);
表单验证的JS代码
<script>
function validatePhoneNum(){
var patt = /^\d{11}$/;
if(patt.test(document.forms["myForm"]["phoneNum"].value)){
return true;
}else{
alert('需要输入11位数字的手机号码');
return false;
}
}
function validateIdNum(){
var patt;
switch(document.forms["myForm"]["idType"].value){
case "身份证":patt = /^\d{17}(\d|X)$/;
break;
case "护照":patt = /^[A-Z]{1}\d{8}$/;
break;
case "外籍居留证":patt = /^([A-Z]|[a-z]){3}\d{12}$/;
break;
case "港澳通行证":patt = /^[A-Z]{1}(\d){8}$/;
break;
}
if(patt.test(document.forms["myForm"]["idNum"].value)){
return true;
}else{
alert('需要输入正确的证件号码');
return false;
}
}
function validateSymptom(){
if(document.forms["myForm"]["symptom"].value==null){
return false;
}
return true;
}
function validateContact(){
if(document.forms["myForm"]["contact"].value==null){
return false;
}
return true;
}
function validateForm(){
return validateIdNum()&&validatePhoneNum()&&validateSymptom()&&validateContact();
}
</script>
- 修改个人信息
描述:点开修改信息之后,服务器返回名为modifyTitle.html的文档,在这个文档中,通过表单提交用户手机号,在这个表单下面有一个iframe框架,显示用户查询的信息,返回不存在或者以表单显示查询得到的用户信息(hms_selectOneEditable.php)。对于有风险人员(有病征或者有接触),显示会标红。除了手机号之外,用户可编辑其他的信息,编辑好之后,用户点击提交按钮,打开hmshms_updateFormData.php这个文档,它的内容主要是update语句,提交到数据库。
操作数据库的PHP的代码:
<?php
// 创建连接
$conn = new mysqli("127.0.0.1:3306", "librarian", "","myDB");
// 检测连接
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
//预处理及绑定
$stmt = $conn->prepare("update `item` set `name`=?, `idType`=?, `idNum`=?,`address`=? ,`travelHistory`=?,`symptom`=?,`contact`=? where phoneNum=" . $_POST['phoneNum']);
$stmt->bind_param("sssssii", $_POST['name'], $_POST['idType'], $_POST['idNum'],$_POST['address'],$_POST['travelHistory'],$symptom,$contact);
// 设置参数
$symptom =$_POST['symptom']=='true'?1:0;
$contact=$_POST['contact']=='true'?1:0;
$stmt->execute();
echo "更新成功";
$stmt->close();
$conn->close();
?>
- 删除个人信息
描述:点开删除个人信息之后,服务器返回名为deleteTitle.html的文档,在这个文档中,通过表单提交用户手机号,在这个表单下面有一个iframe框架,显示用户查询的信息,返回不存在或者以表格显示查询得到的用户信息(hms_selectOne.php)。对于有风险人员(有病征或者有接触),显示会标红。用户确认无误后,可以点击删除按钮,它与提交按钮属于同一个表单,该按钮通过formaction属性覆写了submit按钮,它将打开hms_deleteOne.php。它的内容主要是sql的delete语句,提交到数据库。
与数据库交互的PHP的代码:
<html>
<body>
<?php
// 创建连接
$conn = mysqli_connect("127.0.0.1", "librarian", "", "myDB");
// 检测连接
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$sql = "delete from item
where phoneNum=" . $_POST['phoneNum'];
$result = mysqli_query( $conn, $sql );
if(! $result )
{
die('无法删除该元组: ' . mysqli_error($conn));
}
echo "<h1>删除成功</h1>";
// 释放内存
mysqli_close($conn);
?>
</body>
</html>
- 显示所有信息
描述:点击显示所有信息后,打开hms_showAll.php,该文档使用session维护两个变量,第一个$_SESSION['phoneNums']记录查询得到数据库中的所有电话号码,第二个$_SESSION['pos']负责记录下当前页码。另外,它的body部分主要内容是两个超链接,即上一页和下一页,对应的是hms_previousPage.php和hms_nextPage.php,以及一个iframe,用于表格显示元组信息。hms_previousPage.php和hms_nextPage.php类似,都是通过$_SESSION['pos']得到页码对应的电话号码,再通过这个电话号码查询元组,即调用hms_selectOne.php返回一个表格。同样地,也会对风险人员标红。
与数据库交互的PHP的代码:
<?php
session_start();
// 创建连接
$conn = mysqli_connect("127.0.0.1", "librarian", "", "myDB");
// 检测连接
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$sql = "select phoneNum from item ";
$result = mysqli_query( $conn, $sql );
if(! $result )
{
die('无法读取数据: ' . mysqli_error($conn));
}
if(mysqli_num_rows($result) ==0){
die('<h1>查询结果为空</h1>');
}
$_SESSION['phoneNums']=array();
while($row = mysqli_fetch_assoc($result)) {
array_push($_SESSION['phoneNums'],$row["phoneNum"]);
}
$_SESSION['pos']=-1;
?>
NextPage.php的代码:
<?php
session_start();
?>
<html>
<body>
<?php
if(isset($_SESSION['pos'])){
$_SESSION['pos']=($_SESSION['pos']+1)%count($_SESSION['phoneNums']);
}
// 创建连接
$conn = mysqli_connect("127.0.0.1", "librarian", "", "myDB");
// 检测连接
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$sql = "select name,idType,idNum,phoneNum,address,travelHistory,symptom,contact from item
where phoneNum=" . $_SESSION['phoneNums'][$_SESSION['pos']];
$result = mysqli_query( $conn, $sql );
if(! $result )
{
die('无法读取数据: ' . mysqli_error($conn));
}
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
if($row==null){
die('<h1>查询结果为空</h1>');
}
//看是否需要标红
if($row['symptom'] == 1 || $row['contact'] == 1){
echo '<table border="1" style="border-collapse: collapse;background-color:red;opacity:0.7;filter:Alpha(opacity=70); top: 10%;
margin: auto;
width: 50%;
border: 3px solid #73AD21;
padding: 10px;
background-color: #cae8ca;
text-align:center;">';
}
else{
echo '<table border="1" style="border-collapse: collapse;opacity:0.7;filter:Alpha(opacity=70); top: 10%;
margin: auto;
width: 50%;
border: 3px solid #73AD21;
padding: 10px;
background-color: #cae8ca;
text-align:center;">';
}
//转换数据项含义
$row['symptom'] = ($row['symptom'] == 1)? '有' : '无';
$row['contact'] = ($row['contact'] == 1)? '有' : '无';
$data=array('name'=>'姓名','idType'=>'证件类型','idNum'=>'证件号码','phoneNum'=>'手机号码','address'=>'户籍地址','travelHistory'=>'旅行史','symptom'=>'存在症状?','contact'=>'与病例有接触?');
foreach($data as $x =>$x_value)
{
echo "<tr><th> {$data[$x]}</th> ".
"<td>{$row[$x]} </td> ".
"</tr>";
}
echo '</table>';
// 释放内存
mysqli_free_result($result);
mysqli_close($conn);
?>
</body>
</html>
五.质量属性分析
1.可用性:
当用户使用系统时,系统可用的概率。关注系统故障,使用场景描述如下:刺激源——故障的迹象,刺激——可能出现的有网站超载,数据库故障,web服务器故障,网络故障,客户端无法获取正确信息等等,制品——网站,环境——单机环境,响应——浏览器日志、停机,响应指标——对于网站而言,即网站正常运行时间的百分比。对于本网站的提升策略如下:代码部分对于可能出错的部分,比如连接数据库,从数据库中存取记录等等使用异常捕获的方法,即try-catch(exception)的方法;增加冗余,能有效提高可用性,以及数据库回滚操作等等。
2.可修改性:
关注修改的成本,哪些部分被修改,修改的时间等等。使用场景描述如下:刺激源——修改者(开发者),刺激——可能的修改有在浏览器改变html文档,开发者修改源代码,改变数据库的交互过程,移植运行时环境等等,制品——功能、UI、其他方面,环境——在设计时、开发时、运行时,响应——本例中,在修改时打开MySQL交互式环境,可以及时输入SQL语句查看验证修改的结果,在客户端打开chrome浏览器的开发者工具,可以查看客户端受到的html文档是否符合修改的预期,响应指标——时间、成本。提升策略关注降低修改的时间成本,用到的方法如下:限制修改范围(提高不同的功能模块的内聚性);可能的其他方法:使用配置文件以延长数据的绑定时间、限制通信路径。
3.性能:
关注系统响应事件的速度。使用场景描述如下:刺激源——使用者,刺激——使用该网站,制品——系统的功能,环境——正常/超载/其他异常,响应——系统处理事件,响应指标——处理事件的时间、速度、错误率、丢包率。提升策略关注提高资源量以及资源利用率,降低响应时间,可能的方法如下:增加可用资源,如计算、存储、网络带宽;优化代码,如减少某些业务调用文档的次数,使用更效的方法实现功能;动态优先级调度到来的事件,如初始时查询操作和改变元组的优先级不同,随时间增加而动态改变。
4.安全性:
关注在保证合法用户使用的前提下,抵抗非法用户的攻击。使用场景描述如下:刺激源——非法用户,攻击程序,刺激——攻击该系统,窃取信息,修改数据库等等,制品——系统的功能或数据,环境——联网,响应——系统遭受攻击或者抵抗住了攻击,响应指标——攻击的难度,恢复的成本。提升策略关注使用防御措施和降低恢复成本,使用的方法如下:在php模块修改数据库的地方,采用了SQL预处理绑定的方法,防止SQL注入。可能的方法如下:增加管理员登陆验证的代码;减少网络暴露;提升数据库服务器的安全性;使用其他软件检测攻击;使用移动端的短信验证码登陆等等。
5.易用性:
关注在用户使用软件的难度。使用场景描述如下:刺激源——管理员或者普通用户,刺激——简单易用地使用系统,制品——系统,环境——运行时,联网,响应——系统响应用户,响应指标——用户上手时间,出错率,满意度。提升策略关注降低使用难度,方法如下:浏览器会记录用户输入记录;界面设计简单易懂;对于非法输入,也就是误操作,能及时提供反馈。
6.可测试性
关注尽可能地暴露软件的bug,测试软件质量等等。使用场景描述如下:刺激源——测试者,刺激——测试某一模块或者某一段代码是否有问题,制品——代码、系统、部署环境,环境——设计、开发、运行时,响应——可以测试,并能获得反馈,响应指标——代码覆盖率、边界值、响应时间、吞吐量等等。提升策略关注使用测试工具,描述测试结果,使用一些可行的测试方法等等,方法和工具如下:使用chrome浏览器的开发者工具测试;使用selenium IDE进行web功能测试;在浏览器打开web开发的一些在线编辑器试验一些小段的代码;方法如使用等价类划分、对布尔变量做组合测试、对功能做探索测试等等。
六、总结
本次实验学习了一些web开发的技术,复习了架构课程的以数据为中心的体系风格并以此开始构思、设计、开发,以及复习了质量属性的相关知识。在实践过程中,遇到不少问题,有些技术细节问题通过查阅资料很快解决,有些问题表现的不太明显,花了比较长的时间去发现引起问题的原因,例如安装部署环境大概花了好几天,因为遇到了一些配置的问题,比如在安装完lamp环境后发现apache无法解析php文件,执行a2enmod php7.0发现php模块不存在,经检查,可能的原因本机安装了两个php模块产生冲突,导致php模块未加载进apache。在开发的时候,发现php模块无法操作数据库,原因是Socket文件找不到,但是在php.ini里面设置了mysqli和pdo的默认socket为/tmp/mysql.sock,这与本机的mysql的unix socket参数的值相同,后来发现是mysql的配置文件my.cnf里面没有设置socket,后来设置my.conf里面的值socket=/tmp/mysql.sock后问题解决。本系统的功能有很大的扩展空间,可移植性比较差,可以试试把它放在docker里,增强可移植性。对于真正使用时,需要考虑的问题还很多,部分被描述在质量属性分析中。
最后,贴几张运行截图: