筋斗云教程(二): 系统分析与设计

系统分析与设计

请记住,设计先于编码。筋斗云框架使用DESIGN.wiki文件作为主设计文档。需要从以下几个角度来完成应用系统的设计:

  • 概要设计: 描述需求, 定义概念/术语, 确定系统数据模型
  • 数据库设计: 定义数据库表及字段。
  • 前端应用接口: 定义前端应用接口或应用内的页面。
  • 交互接口: 定义前端访问后端的API接口。

需求分析与原型设计

需求一般通过“用例”(Use Case)来描述系统功能,定义系统有哪几类用户,以及使用本系统的主要场景。它用于指导其后的“系统建模”和“通讯协议设计”。

在示例应用“筋斗城”中,主要应用场景有:

  • 用户到餐厅后,可以打开微信扫描桌面上的二维码浏览菜单和点单。我们将制作微信H5应用“筋斗城客户端”来完成该需求。
  • 餐厅管理员(或称为商户员工)可以通过手机应用自助开店,编辑菜单,管理订单。我们将制作安卓和苹果原生应用“筋斗城商户端”来完成该需求。

一般建议使用UML用例图来更直观的描述需求。下面是使用StarUML工具画出的用例图:

用例图

可以将用例图链接到设计文档的概要设计中,清晰简练的描述需求。

基本功能清楚了,就可以开始做原型设计了,即设计UI界面。
原型设计可以在纸上或脑子里比划,也可以使用专业的原型设计软件如Axure等。

我们以“筋斗城商户端”的原型设计为例,列出几个主要页面。

初次打开应用后进入登录页,登录后进入“首页”:

登录 首页

首页下方分三栏“首页”,“订单”和“我”,点“订单”进入“订单列表”页,再点一个订单项,可查看“订单明细”:

订单列表 订单明细

首页中点“我”进入“个人信息”页:

个人信息

首页点“商品管理”进入“商品列表”,可在左上角菜单中选择添加商品:

商品列表 商品详情

“商品详情”页既可用于添加新商品,也可用于编辑已有商品。

数据模型设计

数据是应用的基础。根据需求和用例图,我们找出系统中有哪些主要对象以及之间的关系,称为数据模型设计。

数据模型将直接指导之后的“数据库设计”,以及基于对象操作的接口设计。
对重要对象应定义每个对象的中英文名字,以便在之后的开发过程中所有人使用统一的概念。
定义对象间的关系,确定对象间是一对多或多对多的关系。

一般建议通过画ER图或类图来描述,简明清晰。下面是使用StarUML工具画出的类图:

数据模型图

根据需求,这里列出了系统中的主要概念,比如用户(User), 订单(Ordr),商户(Store),商户员工(Employee)等。
对象连线上的”1”和”*”体现了对象间的相互关系,如

  • 商户可以有多个订单,订单只属于一个商户,因而关系是1对多,即“商户(1) - (*)订单”
  • 商户可以有多个员工来管理,一个员工可以管理多个商户,因而关系是多对多,即“商户() - ()员工”。

在数据模型中不必细致到每个表和字段,那些可在数据库设计中完善。

数据库设计

数据模型图完成后,可开始数据库设计。后期根据产品原型图或实现的需要可以对数据库设计进行增加和更新。

在筋斗云框架中,数据库设计直接使用以下方式在文档中描述,以商户、员工相关表设计为例:

商户:
@Store: id, name, dscr, createTm, expireTm

员工:
@Employee: id, uname, phone(s), pwd, name(s), perms, storeId, createTm

员工可管理的商户:
@Emp_Store: id, empId, storeId

筋斗云倡导基于设计文档的“一站式数据模型部署”,即在文档中依照规约格式来定义数据模型,再通过部署工具实现自动部署和升级数据库(详情可参考“后端框架”->“服务端部署与升级”章节)。
筋斗云框架对数据库设计文档有以下规约:

  • 表定义的格式是“@表名: 字段1, 字段2, …”,注意以@开头,前面无空格,后面的”:”, “,”均为英文符号,勿使用中文字符。
  • 表和字段的命名方式使用驼峰式,即大小写字母相间,表名以大写字母开头(如Store),字段名以小写字母开头(如createTm)。
  • 字段类型大多通过名称隐式说明,常见的规则将在下面描述。
  • 由于商户与员工间是多对多关系,因而需要创建一张关联表,习惯上纯关联表常采用”表1_表2”的命名格式,即使用下划线连接两张表。而普通表的名称中不建议使用下划线。关联表至少有三个字段,id,表1的id和表2的id。

字段类型常常通过名称隐式说明, 规则如下:

  • “id”为整型主键字段
  • 以”Id”结尾表示整数类型,且一般表示外键,比如@Emp_Store表中的empId即到关联到员工表Employee的主键id。
  • 以”Tm”结尾表示日期时间类型,比如createTm, expireTm.
  • 以Price,Total,Qty,Amount结尾表示小数类型(Currency),比如”price”, “unitPrice”等。
  • 以”Flag”结尾表示1字节的整数,常用于布尔类型,如”flag”, “readFlag”等。
  • 其它都当作字符类型(nvarchar,支持unicode)处理。可以指定字段长度,如 “phone(20)”表示最大长度为20. 也可采用以下描述字符:
    • s: small=20,例如”phone(s)”与”phone(20)”是一个意思。
    • m: medium=50 (缺省),例如”name”, “name(m)”, “name(5)”是一样的
    • l: long=255
    • t: text,长字符串,用于预期可能很长的字段,一般最大可达64K。例如”cmt(t)”.

字段类型还可以通过声明后缀显式说明,如:

  • 后缀”&”表示整型,如个数”cnt&”
  • 后缀”@”表示描述金额的小数(固定精度,一般精确到小数点后4位),如总价格”docTotal@”
  • 后缀”#”表示浮点数,如分数”score#”.

一个综合的例子:

@Item: id, createTm, name, abstract(l), price, price2@, score#, cmtCnt&, openFlag, dscr(t)

部署工具将根据以上规约自动更新数据库。关于数据库设计的详细约定,可参考筋斗云文档“后端框架”->“数据库设计”章节。

前端应用接口

从本节起我们进入接口设计。对于接口,我们必须描述清楚:

  1. 接口是如何调用的,即 接口协议,比如是函数调用,或是通过某种网络协议的远程调用以及其数据封装格式等
  2. 如何形式化(严谨地规范地)描述接口提供的功能,即 接口原型,比如函数原型等。

筋斗云框架使用的都是WEB应用,因而应用都是通过访问URL来调用的。
前端应用接口设计将描述系统中有哪些应用,每个应用URL地址及调用参数有哪些。

每一个应用均应定义一个唯一的应用标识(app),如”emp”, “emp-store”等。在调用交互接口时,框架会自动将应用标识作为参数传给后端。
应用标识中字符”-“之前的部分称为应用类型(app type),如果应用标识里没有”-“,则应用类型与应用标识相同。应用类型常用于登录类型与权限控制。

筋斗云框架支持 移动应用桌面应用 两种典型的WEB应用。

筋斗云的移动应用可做为Web应用在浏览器中运行, 也可以接入微信公众号或支付宝服务窗, 也可以通过cordova框架包装在应用容器中提供android/ios应用程序.

移动应用按惯例放在m2目录下。以客户端为例,其文件名一般是:

  • index.html/index.js 客户端应用的UI框架及通用逻辑,js文件中包含多个页面共享的全局函数、全局变量等。
  • index.css 客户端应用全局样式。
  • page/{xx}.html, page/{xx}.js 每个逻辑页面的UI和逻辑。页面私有的样式可以内嵌在html页面中。

此外还有文件:

  • app.js / app.css 所有移动应用共享的逻辑与样式。

移动应用的对外接口包括页面URL,允许的入口页面(entry),URL参数等。下面举例描述这些接口。

桌面应用按惯例放在web目录下。其常用文件与移动应用类似,以管理端应用为例:

  • store.html 管理端应用。按需可拆分出 store.js, store.css等文件。
  • app.js / style.css 所有桌面应用共享的逻辑与全局样式。

根据需求分析,我们知道筋斗城应用中包括以下应用:

  • 筋斗城客户端,移动应用,由用户在微信中使用,是一个微信H5应用。
  • 筋斗城商户端,移动应用,由餐厅管理员(或称为商户员工)使用,在安卓或苹果手机上以原生应用程序的方式运行。

筋斗云框架还默认带有管理端的桌面应用,我们也写到设计文档中来。初步设计如下:

客户端(app=user)

URL接口原型:

m2/index.html

这表示打开这个URL,就进入移动客户端应用。

客户端应用标识app=user,其应用类型也是”user”,在login交互接口中,对应用类型”user”将作用户登录处理(如查询用户表),登录成功后赋予其用户权限。

未来根据需求可能会添加一些参数,这里先举一个例子如下:

进入移动客户端并显示指定订单的URL接口原型:

m2/index.html#order(orderId)

这表示可以请求这样的URL,用于打开客户端订单页面, 显示32号订单.

m2/index.html?orderId=32#order

其中: m2/index.html是页面地址, “?”后为参数(使用URL编码方式), “#”后为入口点, 表示允许进入的逻辑页面.
在前端应用实现时,就应将order逻辑页加到允许的入口页中,且处理URL参数orderId.

商户端(app=emp-store)

URL接口:

m2/store.html

商户端应用标识被定义为”emp-store”,它的应用类型是”emp”,在后端将作员工登录处理(比如查询的是员工表),登录成功后赋予其员工权限。

管理端(app=emp-adm)

一般由商户员工使用,管理员工、订单等:
URL接口:

web/store.html

定义管理端应用标识是”emp-adm”,它与商户端应用emp-store是相同类型,因而登录方式和权限是相同的,即应使用员工信息登录。
可见,不同的应用可以是相同的应用类型。在实现交互接口时,不同的应用标识会使用不同的cookie名称,以避免多个应用同时使用时相互干扰。

超级管理端应用(app=admin)

一般由超级管理员使用,甚至可执行SQL语句。URL接口为:

web/adm.html

该应用的应用标识定义为”admin”,使用超级管理员帐号登录。注意:超级管理员帐号在用户配置文件conf.user.php中由P_ADMIN_CRED环境变量设定。

交互接口设计

交互接口描述的是前端应用如何从后端获取数据。
交互接口设计的依据是产品原型图,我们需要考虑的是,要显示一个原型页面,前端需要调用怎样的接口来获得数据。

筋斗云的前后端交互接口使用“业务查询协议”,一种基于HTTP协议的REST-RPC风格的调用规范。REST-RPC是介于RESTful和RPC之间的一种接口设计风格。
上一章节提到过,对于接口,我们需要了解其协议和原型。下面就先从这两方面简要介绍一下。

业务查询协议

筋斗云框架是依据DACA架构规范中的业务查询协议来实现的。
业务查询协议,简称BQP(Business Query Protocol),定义业务接口设计规范及如何形式化描述业务接口,客户端怎样请求服务端业务逻辑,以及服务端如何返回业务数据。

客户端通过HTTP协议与服务端交互,调用服务端接口。 接口请求一般使用HTTP GET或POST方法,通过URL或POST内容传递参数,参数使用urlencoded编码方式,即p1=value1&p2=value2的形式;
接口返回内容使用JSON格式。传输中,参数或属性值均使用UTF-8编码。

在定义业务接口时,应使用形式化方式描述接口。每个接口均应在接口文档中规范描述,比如接口描述:

fn(p1, p2) -> {field1, field2}

其中fn为接口名,p1, p2是两个参数,->后面部分是调用成功时的返回值,使用扩展的蚕茧表示法描述。如果没有箭头后面部分,表示没有返回值,默认返回字符串”OK”.

以下假定接口调用地址为”/api.php”,该调用可以用HTTP GET请求(通过URL传参)实现如下:

GET /api.php/fn?p1=value1&p2=value2

或用HTTP POST请求实现:

POST /api.php/fn
Content-Type: application/x-www-form-urlencoded

p2=value2&p1=value1

服务端处理成功时返回内容为一个JSON格式的数组,形如 [0, data],其中data的类型由接口描述定义,例如:

HTTP/1.1 200 OK

[0, {field1: "value1", field2: "value2"}]

服务端处理失败时返回内容格式为 [非0错误码, 错误信息],如:

HTTP/1.1 200 OK

[1, "未认证"]

业务接口包括函数调用型接口和对象调用型接口。

函数型接口名称一般为动词或动词开头,如queryOrder, getOrder等。
对象型接口的格式为{对象名}.{动作}, 如 “Order.get”, “Order.query”等。
对象型接口支持以下标准CRUD操作:add, set, query, get, del,即对象的添加、更新、查询和删除。

本节详细内容,可参考筋斗云文档”后端框架 -> 通讯协议设计”.
或可参考github上业务查询协议定义:https://github.com/skyshore2001/daca/blob/master/BQP.md

函数调用型接口

我们以登录页为例设计接口,需要输入手机号、密码,登录成功后进入首页。

登录

因而我们可设计登录接口如下:

login(uname, pwd) -> {id}

uname:: 员工手机号
pwd:: 员工登录密码

登录成功后,返回该员工的id.

这里的接口原型描述了接口的参数和返回值。

对象调用型接口

登录后,在首页和个人信息页,需要员工名称、手机号、当前商户名等信息。

首页 个人信息

可设计一个接口返回这些信息,比如

getEmployee() -> {name, phone, storeName}

name:: 员工姓名. 来自Employee.name.
phone:: 员工手机号. 来自Employee.phone.
storeName:: 当前商户名. 根据Employee.storeId关联到Store.name.

应用逻辑:
- 接口权限:AUTH_EMP (员工登录后才可用)
- 服务端根据当前session已知员工id, 因此无须传参员工id。

这里不仅描述了接口原型,也指明了诸如权限检查、对应底层字段等应用逻辑,用于指导之后的实现。

我们可以发现,这个接口其实就是典型地获取Employee对象的信息,更好的设计应该是重用通用对象调用接口,如:

Employee.get(id?) -> {id, name, phone, ..., storeName?}

storeName:: 当前商户名,关联Store.name.

应用逻辑:
- 接口权限:AUTH_EMP
- 如果未指定参数id, 则使用当前session中的员工id.
- 不可返回pwd字段。

注意:

  • 参数id是get操作的标准参数,这里加个问号表示在实现时,该参数应该可缺省,并在应用逻辑中说明该参数未给定时应如何处理。
  • 返回字段列表中的”…”表示可直接参考表”Employee”的字段列表。在返回字段说明中,我们看到name, phone这些都省略了,因为可参考表定义,没有特别逻辑时,不必重复说明。
    这样定义的接口默认会返回表Employee中的所有字段,如果有哪些字段不应返回,须在“应用逻辑”中明确说明,比如员工登录密码pwd不应返回。
  • “…”后的字段称为虚拟字段,表示不是”Employee”的物理字段,一般在实现时通过关联或计算得到。加问号表示默认不返回。

这样,我们用Employee对象的get操作替代了之前单独设计的getEmployee接口。
标准get接口支持res参数,用于选择返回字段,比如:

Employee.get() -> {id, name, phone}
Employee.get(res=*,storeName) -> {id, name, phone, storeName}
Employee.get(res=name,storeName) -> {name, storeName}

在设计接口时,我们应尽量利用通用对象接口。

通用对象操作接口

通用对象操作完成对象的增删改查(CRUD)动作。 一般一个对象对应一张数据库主表,若干子表以及若干关联表。
对象名即主表名,接口如下:

{object}.add()(POST fields...) -> id

{object}.set(id)(POST fields...)

{object}.get(id, res?) -> {fields...}

{object}.query(res?, cond?, orderby?) -> tbl(field1,field2,...)

{object}.del(id)

对于add/set方法, 须使用HTTP POST请求; POST内容是对象字段及其值,如a=1&b=2。其它操作既可以用HTTP GET请求,也可以用HTTP POST请求。
set操作中的id参数是对象的整型主键,不可修改,须通过URL参数传递。

get/query操作是比较类似的,分别返回一个对象和对象列表。

query操作非常灵活,除基本的查询外,还支持分页、分组统计、导出等。
查询操作的参数可参照SQL语句来理解:

res
String. 指定返回字段, 多个字段以逗号分隔,例如, res=”field1,field2”.
cond
String. 指定查询条件,格式可参照SQL语句的”WHERE”子句。例如:cond=”field1>100 AND field2=’hello’”, 注意使用UTF8+URL编码, 字符串值应加上单引号.
orderby
String. 指定排序条件,格式可参照SQL语句的”ORDER BY”子句,例如:orderby=”id desc”,也可以多个排序:”tm desc,status” (按时间倒排,再按状态正排)

尽管类似SQL语句,但对参数值有一些安全限制:

  • res, orderby只能是字段(或虚拟字段)列表,不能出现函数、子查询等。
  • cond可以由多个条件通过and或or组合而成,而每个条件的左边是字段名,右边是常量。不允许对字段运算,不允许子查询。

用参数cond指定查询条件, 如:

cond="type='A' and name like '%hello%'" 

以下情况都不允许:

left(type, 1)='A'  -- 条件左边只能是字段,不允许计算或函数
type=type2  -- 字段与字段比较不允许
type in (select type from table2) -- 子表不允许

query操作返回table结构,如

{
    "h": ["id", "name"],
    "d": [[1, "liang"], [2, "wang"]]
}
对象操作示例

[例: 添加商户]

添加商户, 指定一些字段:

Store.add()
    name=华莹汽车(张江店)
    addr=金科路88号
    tel=021-12345678

注:

  • Store是商户表名, 通过POST字段传递各字段内容. HTTP POST请求如下所示(实际发送时, 每个字段的值应使用UTF8+URL编码, 示例中未进行编码):

    POST /api.php?ac=Store.add
    Content-Type: application/x-www-form-urlencoded
    
    name=华莹汽车(张江店)&addr=金科路88号&tel=021-12345678
    
  • id这种主键或只读字段无须设置. 即使设置也应被忽略.

操作成功时返回id值:

8

[例: 获取商户]

取刚添加的商户(id=8):

Store.get(id=8)

操作成功时返回该行内容:

{id: 8, name: "华莹汽车(张江店)", addr: "金科路88号", tel: "021-12345678", opentime: null, dscr: null}

可以像query方法一样用POST参数res指定返回值, 如

Store.get(id=8)
    res=id,name as storeName,addr

操作成功时返回该行内容:

{id: 8, storeName: "华莹汽车(张江店)", addr: "金科路88号"}

[例: 查询商户]

查询”华莹汽车”在”浦东”的门店, 即查询名称含有”华莹汽车”且地址中含有”浦东”的商户, 只返回id, name, addr字段:

Store.query()
    res=id,name,addr
    cond=name like '%华莹%' and addr like '%浦东%'

操作成功时返回内容如下:

{
    "h": [ "id", "name", "addr" ],
    "d": [
        [ 7, "华莹汽车(金桥店)", "上海市浦东区金桥路1100号"],
        [ 8, "华莹汽车(张江店)", "金科路88号" ]
    ]
}

[例: 更新商户]

为商户设置描述信息等:

Store.set(id=8)
    opentime=8:00-18:00
    dscr=描述信息.

操作成功时无返回内容.

[例: 删除商户]

Store.del(id=8)

操作成功时无返回内容.

接口原型中的类型描述

上述例子中,很多返回了JSON格式的复合类型,主要有对象、数组等,在原型中使用了”蚕茧表示法”来描述数据结构,常用的列举如下:

{id, name}

一个简单对象,有两个字段id和name。例:{id: 100, name: "name1"}

[id…]

一个简单数组,元素为id。例:[100, 200, 400], 每项为一个id

[id, name]

一个简单数组,例:[100, "liang"],第一项为id, 第二项为name

[ [id, name] ] 或 varr(id, name)

简单二维数组,又称varr, 如 [ [100, "liang"], [101, "wang"] ].

[{id, name}] 或 objarr(id, name)

一个数组,每项为一个对象,又称objarr。例:[{id: 100, name: "name1"}, {id: 101, name: "name2"}]

tbl(id, name)

table对象。其详细格式为 {h: [header1, header2, ...], d:[row1, row2, ...]},例如

{
  h: ["id", "name"],
  d: [[100, "myname1"], [200, "myname2"]]
}
功能描述 筋斗云在线测评系统,主要包括:试题展示、用户管理、成绩查询、历史记录、试题管理、智能组卷、智能分析。  电子商务系统功能描述 试题展示:实现试题的选择、展示和提交。 用户管理:实现用户的注册、登录、修改密码和修改个人信息。 成绩查询:实现成绩查询,错题解析和成绩分析报告。 历史记录:实现对历史测评的查询和回顾,对错题智能提示。 试题管理:实现动态向数据库增加、删除、修改和查询试题功能。 智能组卷:实现根据制定条件自动选题,可以动态更改试题。 智能分析:实现对用户的答题记录,智能分析错题,显示分析报告。 本系统采用SSM框架,使用了redis技术,双服务器;’ 本系统页面设计精美,特效很多; 本测评系统实现了多角色多用户的切换,实现了试卷的创建,智能组卷,无聊刷题,考试情况的分析统计等功能,分为前台后台两部分。 通过设置tomcat,修改数据库配置文件后即可使用, 本系统包括测评系统主程序源代码,数据库文件,需求文档,数据库设计包含在需求文档中,十分详细, 囊括了功能结构图,用例图,业务流程图,ER结构图,清晰明了,足以满足您在技术上的需求或者直接进行使用,您可以在参考原项目的基础上进行修改,或者直接录入数据信息进行使用。 本系统属于原创代码,如您需要在其他位置进行发,请注明出处。谢谢使用!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值