一、系统需求分析
1.系统介绍
2.系统功能性需求
①用户浏览应用,即登录首页,在首页中主页列出最新出版的4本书,和几本主编推荐的书。
②在首页中提供购物车的链接、分类浏览的链接、结账的链接、查看订单的链接和其它相关的链接;首页中还应提供搜索的功能,方便用户者找到想要的商品。
③用户者通过搜索得到的商品和通过分类浏览得到的商品都应该提供一个放入购物车的链接,和一些商品的简单描述。当访问者点击此链接时,向购物车增加一本书。如果选择的书已在购物车中,购物车中的数量增1,而不是增加一个新商品。如果用户还想进一步了解商品的详细信息,用户可以点击商品的名字或者商品的图片进入商品的详细信息页面,此页面同样应该提供放入购物车的页面。在商品的详细信息页面中,商品的图片应该控制大小,并提供放大图片的链接,用户点击链接即可查看大图。
④用户点击放入购物车的按钮后,进入管理购物车的页面,其页面显示当前购物车的内容。这个页面(即管理购物车的页面)会列出购物车中的商品及每件商品的数量和价格。在列表的底部是购物车内商品的总价。每件商品的数量以用户可修改的方式列出。列表底部有一个“更新”按钮,可以刷新页面并用新的数量更新用户的购物车。如果在购物车的数量中输入0,则将该商品从购物车中删除。在每一件商品的右边还有一个移除的链接,如果点击这个链接,则可以把这件商品从购物车中移除。
⑤如果用户进入到管理购物车页面,而购物车中没有任何商品,就要显示一条消息,指示购物车中没有商品,并提供返回的链接。
⑥在管理购物车页面中,用户可以选择清除整个购物车,删除购物车中所有的商品。当用户选择此链接时,就会删除购物车中的所有商品,并为用户显三分类列表。
⑦用户从菜单中选择“结账”按钮,或选择在管理购物车中的“结账”按钮,得到一个收集个人信息和信用卡号信息的表单。表单确保输入了所有的值,对邮政编码和信用卡号做适当的验证。在表单的底部,会再次显示当前订单的内容和总价。
⑧如果用户选择结账但购物车中没有商品,就不会显示表单,并用一条消息指示没有要结账的商品。
⑨在结账表单中输入所有值后,用户点击“结账”按钮,订单提交给系统。如果允许信用卡方式结账,就为用户显示一个确认屏幕并将当前购物车清空。如果不允许信用卡购买,就为用户显示一条消息,请求再次尝试或联系银行。在拒绝的情况下,不会清空购物车。
⑩ 客户需要跟踪被拒绝的订单,所以即使被拒绝,也应当记录下来,并标记为被拒绝。
11在应用的所有页面中,要有一小块单独的区域显示购物车中当前商品的数○
量和价格。
12系统应当确保图书价格的改变不会影响已经记录订单的订单总价,也不应○
影响用户已经增加到购物车中的图书的价格。
13另外用户使用的语言可能不只是中文,所以还要提供英语语言的界面,方○
便用户顺利完成购物活动。
3.软硬件环境需求
因为任务只是实现购物车模块,因此对软硬件的要求都可以不用太高。 硬件环境:普通的个人计算机作为服务器
操作系统:Windows XP或以上版本
数据库系统:MySql 5.0或以上版本
Web服务器:Apache Tomcat 6.018或以上版本
Java运行环境:JDK 1.5
客户端:浏览器(可用不同浏览器进行测试)
二、系统设计
1.系统概述
此项目要完成的是购物车模块的设计和实现,由于购物车不是孤立存在的,是个某个具体的系统的一个重要的模块,所以在设计时把此模块的设计说成是系统的设计。
这个系统要完成的功能只要是商品的管理、购物车的管理和其它辅助功能的管理。
2.系统总体结构;3.子系统的结构与功能;4.系统业务流程图;5.技术方案选择;目前,比较流行的B/S设计有基于JSP、ASP.;因为该模块尚未与其他系统进行太多的交互,所以使用;序,所以这里暂时不考虑J2EE;而选择了JSP技术后,再经过考虑,选择了MVC设;在持久持,选用Hibernate,是考虑到了其高;在视同层,因为考虑到整个应用的风格应保持一致,而
2.系统总体结构
3.子系统的结构与功能
4.系统业务流程图
5.技术方案选择
目前,比较流行的B/S设计有基于JSP、ASP.NET、PHP、CGI及J2EE等模式。相比较而言PHP的功能相对简单,不适合做大程序,此模块虽然不大,但考虑到此模块的拓展性,不选择PHP;而CGI效率相对较低,所以也不考虑。
因为该模块尚未与其他系统进行太多的交互,所以使用J2EE的模式并不能够体现出J2EE本身的优势。而JSP又是J2EE的核心技术之一,可以随时升级为J2EE程
序,所以这里暂时不考虑J2EE。而JSP与ASP.NET中进行选择时,考虑到JSP具有Java语言的各种优势,并且有很多免费的JSP服务器,所以最终选择JSP。
而选择了JSP技术后,再经过考虑,选择了MVC设计模式作为整个应用的开发模式;而Struts提供了MVC的完整实现,可以提高开发效率,所以选择了Struts作为MVC的实现。
在持久持,选用Hibernate,是考虑到了其高速的开发效率,及代码重用性高、易于维护等各种优势。
在视同层,因为考虑到整个应用的风格应保持一致,而无需大量复制粘贴代码,选用了Tile作为应用的布局。
综上所述,该模块的实现技术为:Struts+Hibernate+Tiles。
6.开发环境配置
为了提高开发的效率,本模块使用NetBeans 6.5 集成环境进行开发。其中Apache Tomcat可以在安装NetBeans时一同安装。在安装NetBeans前,先安装JDK,然后配置JDK的环境变量。当安装好JDK和NetBeans后就可以进行应用的开发。
三、数据库设计
1.概念设计(E-R图)
根据前面的需求分析,得出需要保存在数据库中的对象有图书(book),图书分类 (category),客户订单(customerorder),订单商品(orderitem)及订单状态(orderstatus)。它 们 的关系如下(省略了各实体的属性):
2.逻辑关系设计
将上面的E-R图转换成逻辑关系表,如下: book() category() customerorder() orderitem()
orderstatus()
3.物理设计
category表
customerorder表
orderitem表
status表
4.数据库实现(在具体数据库中实现)
DROP DATABASE IF EXISTS `bookstore`; CREATE database bookstore; USE bookstore;
SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for book -- ---------------------------- DROP TABLE IF EXISTS `book`; CREATE TABLE `book` (
`id` int(11) NOT NULL AUTO_INCREMENT, `ISBN` varchar(20) NOT NULL, `author` varchar(20) NOT NULL, `name` varchar(100) NOT NULL, `category` int(11) NOT NULL, `unitprice` float NOT NULL, `publish` varchar(50) NOT NULL, `publishdate` datetime NOT NULL, `picture` varchar(50) NOT NULL, `description` varchar(2000) NOT NULL,
PRIMARY KEY (`id`), KEY `category` (`category`),
CONSTRAINT `book_ibfk_1` FOREIGN KEY (`category`) REFERENCES `category` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=189 DEFAULT CHARSET=utf8;
-- ---------------------------- -- Table structure for category -- ----------------------------
DROP TABLE IF EXISTS `category`; CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL DEFAULT '''''', `description` varchar(200) NOT NULL DEFAULT '''''', PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for customerorder -- ----------------------------
DROP TABLE IF EXISTS `customerorder`; CREATE TABLE `customerorder` (
`id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(10) NOT NULL, `phonenum` varchar(20) NOT NULL, `email` varchar(50) NOT NULL, `address` varchar(100) NOT NULL, `zipcode` varchar(10) NOT NULL, `orderdate` datetime NOT NULL, `status` int(11) NOT NULL,
`cardnumber` varchar(20) NOT NULL, PRIMARY KEY (`id`), KEY `status` (`status`),
CONSTRAINT`customerorder;)ENGINE=InnoDBAUTO_INCRE;------------------------;--Tablestructurefororder;------------------------;DROPTABLEIFEXISTS`orderi;CREATETABLE`orderitem`(;`id`in
CONSTRAINT `customerorder_ibfk_1` FOREIGN KEY (`status`) REFERENCES `orderstatus` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for orderitem
-- ----------------------------
DROP TABLE IF EXISTS `orderitem`;
CREATE TABLE `orderitem` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bookid` int(11) NOT NULL DEFAULT '0',
`orderid` int(11) DEFAULT '0',
`qty` int(11) NOT NULL DEFAULT '0',
`unitprice` float NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `bookid` (`bookid`),
KEY `orderid` (`orderid`),
CONSTRAINT `orderitem_ibfk_1` FOREIGN KEY (`bookid`) REFERENCES `book` (`id`),
CONSTRAINT `orderitem_ibfk_2` FOREIGN KEY (`orderid`) REFERENCES `customerorder` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for orderstatus
-- ----------------------------
DROP TABLE IF EXISTS `orderstatus`;
CREATE TABLE `orderstatus` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '订单状态名字(接受或拒绝)', PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
四、界面设计
整体布局首页界面设计
根据这个基本布局,可以设计出其它的页面,每个页面不同的地方是主体部分,其余的都一样。
其中首页的效果图如下:
五、系统代码编写
1.系统文件目录结构
(1)应用目录
build目录存放应用编译的文件
dist目录存放应用打包后的war文件
src目录存放源程序
test目录存放测试代码程序
web目录存放JSP
(2)Java类的包结构
缺省:存放Hibernate配置文件和实体类的映射
文件
com.bookstore.entity:存放实体类的对象,如
Book、Category等。
com.bookstore.filter::存放应用程序的过滤
器,像字符集的过滤器。
com.bookstore.persist:存放数据库中持久对象
中所涉及的对象,包括数据的存取,修改。
com.bookstore.struts:存放应用的国际化文件,其中包括三个资源文件(默认、中文、英文)。
com.bookstore.action:存放Action类。
com.bookstore.form:存放Form类。
com.bookstore.util:存放应用的工具类。
2.关键实体对象的实现
实体对象的代码和Hibernate配置文件和
Hibernate映射文件主要由NetBeans生成,生成的代
码目录如下,下面只给出实体对象的主要代码和增加
的方法,详细的代码见附录。
Hibernate.cfg.xml的主要代码如下:
<session-factory>
<property
name="hibernate.dialect">org.hiberna
te.dialect.MySQLDialect</property>
<property
name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property
name="hibernate.connection.url">jdbc:mysql://localhost:3306/bookstore</property>
<property
name="hibernate.connection.username">root</property>
<property
name="hibernate.connection.password">root</property>
<property name="show_sql">false</property>
<property
name="hibernate.connection.pool_size">100</property>
<mapping resource="Orderstatus.hbm.xml"/>
<mapping resource="Book.hbm.xml"/>
<mapping resource="Category.hbm.xml"/>
<mapping resource="Customerorder.hbm.xml"/>
<mapping resource="Orderitem.hbm.xml"/>
</session-factory>
Book中覆盖了hasCode()和toString()方法。
@Override
public int hashCode() {
return getId();
}
@Override
public boolean equals(Object o) {
Book b = (Book) o;
if (getId() == b.getId()) {
return true;
}
return false;
}
Category中新增了统计图书数量的方法。
public int getBookCount() {
return books.size();
}
Customerorder中新增了统计商品的总金;publicfloatgetValue(){;Iterator<Orderitem>;while(it.hasNext()){;Orderitemitem=it.next();;rs+=item.getValue();;returnrs;;Orderitem中增加的方法如下:;publicfloatCustomerorder中新增了统计商品的总金额的方法。
public float getValue() {
Iterator<Orderitem> it = orderitems.iterator(); float rs = 0.0f;
while (it.hasNext()) {
Orderitem item = it.next();
rs += item.getValue();
}
return rs;
}
Orderitem中增加的方法如下:
public float getValue() {
return qty * unitprice;
}
public int getBookId() {
return book.getId();
}
public int getOrderId() {
return customerorder.getId();
}
@Override
public int hashCode() {
return getBook().getId();
}
@Override
public boolean equals(Object o) {
Orderitem i = (Orderitem) o;
return book.equals(i.getBook());
}
ShoppingCart.表示的是购物车对象,其生命周期从客户第一次进入网站一直到离开或结束订单。购物车维护商品的一个java.util.Set。Set接口的实现可以确保商品集合的唯一性,这样Set中就不会有相同的对象。ShoppingCart还需要提供方法让应用管理购物车的内容,如增加商品、替换商品和删除商品。
购物车对象是整个购物车模块的核心,其详细的代码如下:
package com.bookstore.entity;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ShoppingCart {
private Set<Orderitem> items = null;//订单商品
public ShoppingCart() {
items = new HashSet<Orderitem>();
}
/**
* 增加订单商品。
* @param toAdd
*/
public void addOrIncrementItem(Orderitem toAdd) {
Orderitem existing =
getItemByBookID(toAdd.getBook().getId());
if (existing != null) {
removeItem(existing);
int qty = existing.getQty();
toAdd.setQty(toAdd.getQty() + qty);
}
addItem(toAdd);
}
/**
* 替换订单商品
* @param toReplace
*/
public void replaceItem(Orderitem toReplace) {
Orderitem existing =
getItemByBookID(toReplace.getBook().getId());
if (existing != null) {
removeItem(existing);
}
addItem(toReplace);
}
/**
* 增加订单商品,使用此方法时需先验证购物车中是否已经存在此种
商品
* @param item
*/
private void addItem(Orderitem item) {
items.add(item);
}
public void removeItem(Orderitem item) {
items.remove(item);
}
/**
* 清空购物车
*/
public void clear() {
items.clear();
}
public Set<Orderitem> getItems() {
return items;
}
/**
* 购物车中订单商品的数量
* @return
*/
public int getItemCount() {
return items.size();
}
/**
* 根据Book的ID,在购物车中查找订单商品
* @param bookId
* @return
*/
public Orderitem getItemByBookID(int bookId) {
Iterator<Orderitem> it = getItems().iterator(); while (it.hasNext()) {
Orderitem item = it.next();
if (item.getBookId() == bookId) {
return item;
}
}
return null;
}
/**
*
* @return 购物车中商品的总金额
*/
public float getValue() {
Set<Orderitem> item = getItems();
float result = 0.0f;
Iterator<Orderitem> it = item.iterator();
while (it.hasNext()) {
Orderitem i = it.next();
result += i.getValue();
}
return result;
}
}
3.持久性管理实现
持久性管理主要是完成各实体的持久化
操作,如 查询、删除、更新等。因为本模块采用了Hibernate技术,对各实体的管理的操作简化了很多。这一部分的代码放在右图的类包中,其中EntityManager对象是具体的对各实体的持久化操作,EntityManagerFactory是一个工厂类,提供具体的实体持久化操作的对象。HibernateSessionFactory对象是一个管理Hiernate的Session的类,提供的方法主要有产生Session,关闭Session。HiernateFactory的代码是由MyEclipse插件生成。
EntityManager提供的方法如图所示。其中getObjects(String hsql, int pageSize, int pageNo)是分页查询实现的方法。
@SuppressWarnings("unchecked")
protected EntityManager(Class c) {
this.c = c;
}
@SuppressWarnings("unchecked")
public List findAll() {
Session s = null;
try {
s = HibernateSessionFactory.getSession(); Criteria crit = s.createCriteria(c); return crit.list();
} finally {
HibernateSessionFactory.closeSession(); }
}
/**
* 使用HQL语句进行查询
* @param hsql 查询语句
* @return 符合条件的对象集合
*/
@SuppressWarnings("unchecked")
public List getObjects(String hsql) {
Session s = null;
try {
s = HibernateSessionFactory.getSession(); return s.createQuery(hsql).list(); } finally {
HibernateSessionFactory.closeSession(); }
}
/**
* 根据ID值得到持久化的对象
* @param id ID值
* @return 指定ID的对象
*/;publicObjectfindById(int;Sessions=null;;try{;s=HibernateSessionFactor;returns.get(c,newInteger;}finally{;HibernateSessionFactory.;/**;*通过HQL语句进行分页查询;*@paramhsql要执行的HQL语句;*@param
*/
public Object findById(int id) {
Session s = null;
try {
s = HibernateSessionFactory.getSession();
return s.get(c, new Integer(id));
} finally {
HibernateSessionFactory.closeSession();
}
}
/**
* 通过HQL语句进行分页查询
* @param hsql 要执行的HQL语句
* @param pageSize 每页的大小
* @param pageNo 第几页
* @return 符合条件的结果
*/
public Pager getObjects(String hsql, int pageSize, int pageNo) {
Session s = null;
try {
s = HibernateSessionFactory.getSession();
int startIndex = pageSize * (pageNo - 1);
int rowCount =
s.createQuery(hsql).list().size();
List result =
s.createQuery(hsql).setFirstResult(startIndex).setMaxResults(pageSize).list();
return new Pager(pageSize, pageNo, rowCount,
result);
} finally {
HibernateSessionFactory.closeSession();
}
}
public Object saveOrUpdate(Object ob) {
Session s = null;
try {
s = HibernateSessionFactory.getSession();
Transaction tx = (Transaction)
s.beginTransaction();
s.saveOrUpdate(ob);
tx.commit();
18
return ob;
} finally {
HibernateSessionFactory.closeSession();
}
}
EntityManagerFactory类的实现比较简单,它的方法如下,主要是提供各
EntityManager对象。其中的一个方法的代码如下:
public static EntityManager getBookManagerInstance() {
return new EntityManager(Book.class);
}
4.控制器对象实现
控制器主要是控制整个操作的流程,它所在类包如图,其中BaseAction是抽象类,其它的Action类都继承自BaseAction。在BaseAction主要是做一些初始化工作,如保
方法,
public ActionForward execute(ActionMapping mapping, 持每个会话中都有一个ShoppingCart。BaseAction类实现了Action的execute
ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
19
intialize(request);
ActionForward fwd = doAction(mapping, form, request, response);
saveToken(request);
return fwd;
}
BaseAction中的doAction是抽象方法,继承BaseAction的类必须实现这个方法。saveToken方法是保存令牌,如果需要防止重复提交,可以先进行判断Token是否有效,然后再进行业务操作。
5.各模块的具体实现
按照系统的功能,可以把系统的实现按各模块来实现。由于采用了Struts模式,所以各模块的实现还需要在struts-config.xml文件里配置,配置的结果如下图所示。示图中采用了Tiles,所有还需要tiles-defs..xml文件,这两个文件的配置具体的代码见附录。
20
(1)商品展示模块
这个模块主要是实现从数据库中提取出最新出版的书即可。对应的文件是DefaultAction.java。关键代码如下:
EntityManager bookdao =
EntityManagerFactory.getBookManagerInstance();
List<Book> books = null;
books = bookdao.getObjects("from Book order by publishdate desc", 4, 1).getResultList();
request.setAttribute("books", books);
21
(2)商品搜索模块
商品的搜索最重要的是从表单的数据中构造出语句来,然后把查询结果分页显示出来。搜索表单的文件是com/bookstore/struts./form/SearchForm.java,它的属性如下:
private String property; // 要查询的属性
private String key; // 要查询的值
private String priceCtx; // 价格复选标记
private String start ; // 最低价格
private String end ; // 最高价格
private String sortRadio;//排序方式
com/bookstore/struts./form/GetHQL.java文件是构造查询HQL语句的类的实现,它有一个静态的方法,实现的功能是根据SearchForm中的数据返回一条HQL语句。实现的思路是逐步判断表单中的每一个值,然后根据此值来判断如何构造HQL语句。具体的代码见附录。
得到了HQL语句后,就要从数据库中查询数据,因为查询的结果可能比较多,所有要进行分页查询。关键代码如下:
EntityManager bookdao =
EntityManagerFactory.getBookManagerInstance();
Pager pager2 = null;
if (pageNo != null) {
pager2 =
bookdao.getObjects(GetHQL.getHQL(searchForm), 5, new
Integer(pageNo).intValue());
} else {
pager2 =
bookdao.getObjects(GetHQL.getHQL(searchForm), 5, 1);
}
Pager是存储分页信息的类,它有如下属性:
protected int pageSize = BookstoreConstants.pageSize;// 每页大小
protected int pageNo = BookstoreConstants.pageNo; // 当前页码
protected int[] pageSizeList = {5, 10, 15, 20, 30, 40, 50};
protected int rowCount = 0; // 记录总数
protected int pageCount = 1; // 总页数
protected int startIndex = 1; // 起始行数
protected int endIndex = 1; // 结束行数
22
protectedintfirstPageNo=;protectedintprePageNo=1;;protectedintnextPageNo=1;protectedintlastPageNo=1;protectedListresultList;;Pager的这些属性主要在构造函数中实现,它的构;publicPager(intpageSize,;resul
protected int firstPageNo = 1; // 第一页页码
protected int prePageNo = 1; // 上一页页码
protected int nextPageNo = 1; // 下一页页码
protected int lastPageNo = 1; // 最后页页码
protected List resultList; // 结果集存放List
Pager的这些属性主要在构造函数中实现,它的构造函数如下:
public Pager(int pageSize, int pageNo, int rowCount, List
resultList) {
this.pageSize = pageSize;
this.pageNo = pageNo;
this.rowCount = rowCount;
this.resultList = resultList;
this.pageCount = (rowCount % pageSize == 0) ?
(rowCount / pageSize)
: (rowCount / pageSize) + 1;
this.startIndex = pageSize * (pageNo - 1);
this.endIndex = this.startIndex +
resultList.size();
this.lastPageNo = this.pageCount;
if (this.pageNo > 1) {
this.prePageNo = this.pageNo - 1;
}
if (this.pageNo == this.lastPageNo) {
this.nextPageNo = this.lastPageNo;
} else {
this.nextPageNo = this.pageNo + 1;
}
}
(3)购物车管理模块
购物车的管理主要是实现添加商品、移除商品、修改数量、清空购物车。
实现这些功能的类是ManagerItemAction和ManageCartAction,其中
ManagerItemAction主要是实现添加商品、移除商品和清空购物车。做完这三
个动作中的每一个动作后,把业务的流程交给另一个类来处理,即
ManageCartAction。在这个Action中还有另外一个功能——修改数量。因为把
添加、移除和清空动作后的流程都指向ManagerAction,而这些操作并不需要
进行数量的修改,所以这个Action要加一个是否需要修改数量的判断,以免做
不必要的数量修改操作。关键代码如下:
public ActionForward doAction(ActionMapping mapping,
ActionForm form,
HttpServletRequest request, HttpServletResponse
response)
throws Exception {
ManageCartForm cartForm = (ManageCartForm) form;
String update = cartForm.getAction();
ShoppingCart cart = getOrCreateCart(request);
//update不为空,即表示此请求取是从showCart.jsp发出,否
则是其他页面发出。
//如果是从showCart.jsp发出,即意味着点击了更新按钮,所有
要进行购物车的更新
if (update != null && update.equals("update")) {
updateCart(request, cartForm);
}
initializeForm(cartForm, cart);
return mapping.findForward("success");
}
initializeForm(cartForm, cart);做的工作是初始化表单,以正确显示出当前
购物车中订单商品的数量。
updateCart(request, cartForm);方法为修改数量的具体实现。实现的思路是
从表单中获取新的商品的数量,然后逐一设置对应订单商品的数量。难点是
如今区分开不同订单商品的不同数量。在这里使用了Map集合,其中Key为图
书的ID,Value为订单商品的数量。由于图书的ID的惟一的,所以可以用Map
正确区分出各订单商品的数量。另外如果把订单商品的数量设为0或者为负数,
就把这个订单商品从购物车中移除。如果订单商品的数量中出现了不合法的数
据,如小数点或者字母,则要不修改订单商品的数量。实现的代码如下:
private void updateCart(HttpServletRequest request,
ManageCartForm form) {
ShoppingCart cart = getOrCreateCart(request);
Map values = form.getValues();
Set<Orderitem> items = cart.getItems();
List<Orderitem> itemArr = new ArrayList(items);
for (int i = 0; i < itemArr.size(); i++) {
Orderitem item = itemArr.get(i);
String newQty = (String)
values.get(String.valueOf(item.getBookId()));
if (MyTools.isInteger(newQty)) {
Integer qty = new Integer(newQty);
if (qty.intValue() > 0) {
item.setQty(qty);
cart.replaceItem(item);
} else {
cart.removeItem(item);
}
}
}//End of for
}
(4)结账(收银台)模块
结账要完成的是收集客户的资料,然后把客户的订单保持到数据库中。收
集客户资料需要一个表单,表单的验证如果手动编写代码比较复杂,所以选择
了动态验证表单,这只需要在struts-config.xml文件中配置如下表单。
<form-bean name="checkOutForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="userName"
type="java.lang.String"/>
<form-property name="phoneNum"
type="java.lang.String"/>
<form-property name="email"
type="java.lang.String"/>
<form-property name="address"
type="java.lang.String"/>
<form-property name="zipCode"
type="java.lang.String"/>
<form-property name="cardNumber"
type="java.lang.String"/>
</form-bean>
这个表单的验证在validation.xml文件中实现,内容如下:
<form name="checkOutForm">
<field property="userName" depends="required"> <arg0 key="user.name"/>
</field>
<field property="phoneNum"
depends="required,mask">
<arg0 name="required" key="user.phoneNum"/>
<msg name="mask"
key="checkout.error.phoneNum"/>
<var>
<var-name>mask</var-name>
<var-value>^1[0-9]{10}$</var-value>
</var>
</field>
<field property="email"
depends="required,email">
<arg0 name="required" key="user.email"/>
<arg0 name="email" key="user.email"/>
</field>
<field property="address" depends="required"> <arg0 key="user.address"/>
</field>
<field property="zipCode"
depends="required,mask">
<arg0 name="required" key="user.zipCode"/>
<msg name="mask"
key="checkout.error.zipcode"/>
<var>
<var-name>mask</var-name>
<var-value>^[0-9]{6}$</var-value>
</var>
</field>
<field property="cardNumber"
depends="required,creditCard">
<arg0 name="required"
key="user.cartNumber"/>
<msg name="creditCard"
key="checkout.error.cardNumber"/>
</field>
</form>
表单的验证首先在客户端进行,如果客户端没有禁止JavaScript,就可以在
客户端的浏览器中进行验证,然后再由服务器验证。如果客户端禁止了
JavaScript,就只能由服务器进行验证。这种双重验证比单纯使用JavaScript或
者服务器的验证是有好处的:如果只用服务器端进行验证,会加大服务器的负
担;如果只用客户端进行验证,万一用户禁用了JavaScript,就不能进行验证。
开启客户端的验证比较简单,只要进行如下设置即可
<html:javascript formName="checkOutForm"/>
<html:form action="completeOrder.do" method="post"
onsubmit="return
validateCheckOutForm(this);">
得到验证的表单信息提交到服务器后,就要根据表单中的信用卡号是否有效进行订单状态的设置。如果信用卡号有效并且有足够的余额,就把订单状态设置为接受支付,否则为拒绝支付。这一部分只是进行了模拟操作,因为在现实中支付功能通常由第三方来完成。
(5)订单查询
此功能由SalesReportAction完成,具体要做的事不多,只是把订单按时间的降序排序进行查询。代码如下:
public ActionForward doAction(ActionMapping mapping,
ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
EntityManager manager =
EntityManagerFactory.getOrderManagerInstance();
List<Customerorder> orders =
manager.getObjects("from Customerorder order by orderdate desc");
request.setAttribute("orders", orders);
return mapping.findForward("success");
}
(6)语言转换
语言转换的实现首先要创建不同语言的资源文件,在这个系统中,一共设置了三个资源文件:默认、中文和英文。转换的操作由ChangeLocaleAction完成,这个Action是Struts提供的例子中的文件,要使用这个Action,需要提供两个链接:
<html:link
action="/ChangeLocale?language=en&country=US">English</html:link>
<html:link
action="/ChangeLocale?language=zh&country=CN">简体中文
</html:link>
(7)另:字符集过滤器的配置;字符集过滤器使用了Tomcat提供的例子中的代码;SetCharacterEncodingFilt;<filter>;<filter-name>SetCh;<filter-class>com.;<init-param>;<param-name>encodi;<pa
(7) 另:字符集过滤器的配置。
字符集过滤器使用了Tomcat提供的例子中的代码,具体的做法是把
SetCharacterEncodingFilter.java文件放到某个包中,这里为com.bookstore.filter,然后在web.xml中配置这个过滤器,配置如下:
<filter>
<filter-name>SetCharacterEncodingFilter</filter-name>
<filter-class>com.bookstore.filter.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>ignore</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SetCharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用了字符集过滤器后就不需要在持久化操作前进行字符集的转换。
六、测试
1.功能测试
(1)链接测试
链接测试可分为三个方面。首先,测试所有链接是否按指示的那样确实链接到了该链接的页面;其次,测试所有链接的页面是否存在;最后,保证Web应用系统上没有孤立的页面(只有知道正确的URL地址才能访问的页面)。
本模块的所涉及的页面不多,可以采用手工的方法进行测试,并且可以在测试的过程中改正出现的问题。
(2)表单测试
在本模块中需要进行表单测试的有搜索表单和订单信息表单。在搜索表单中需重点测试的是价格的值的测试,因为在进行数据库查询的时候,价格的数据类型为浮点,需要把字符型的转换为浮点,其间有可能出现异常。
订单信息表单中需要验证的输入格式比较多,有邮政编码、有手机号码、有信用卡号等,在测试时需要尽量假设出用户可能出现的各种错误,测试服务器是否可能给出正确的错误提示信息。
表单的测试也用手工测试。
(3)数据库测试
在使用了数据库的Web应用系统中,一般情况下,可能发生两种错误,分别是数据一致性错误和输出错误。数据一致性错误主要是由于用户提交的表单信息不正确而造成的,而输出错误主要是由于网络速度或者程序设计问题等引起的。所以在测试时,针对这两种情况分别进行测试。
(4)应用程序特定的功能需求
本模块最重要的功能是购物车的实现,所有在进行这方面的测试时,要着重测试整个购物的流程是否正确,是否顺畅,操作是否方便。
2.性能测试
(1)链接速度测试
(2)负载测试
(3)压力测试
以上三方面的测试需要有专门的测试软件进行,暂时先不考虑。
3.接口测试
(1)服务器接口测试
第一个需要测试的接口是浏览器与服务器的接口。测试可以这样进行:测试人员提交事务,然后查看服务器记录,并验证在浏览器上看到的正好是服务器上发生的。测试人员还可以查询数据库,确认事务数据已正确保存。
(2)错误测试
尝试在处理过程中中断事务,看看会发生什么情况?订单是否完成?尝试中断用户到服务器的网络链接。常识中断Web服务器到信用卡验证服务器的链接。在这些情况下,系统能否正确处理这些错误。如果用户自己中断事务处理,在订单已保存而用户没有返回网站确认的时候,需要由客户代表致电用户进行订单确认。
4.可用性测试
(1)导航测试
导航测试主要是测试Web应用系统的页面结构、导航、菜单、链接的风格是否一致。确保用户凭直觉就知道Web应用系统里面是否还有内容,内容在什么地方。
当Web应用系统的层次一旦决定,就可以着手测试用户导航功能,让最终用户参与这种测试,效果更好。
(2)图形测试
本模块在这方面的测试主要有:一,要确保图形有明确的用途,图片或动画不要胡乱地堆在一起。图书的图片的尺寸要控制在一定范围内,尽量小一点,并且提供到一个大图的链接;二,验证所有页面字体的风格是否一致;三、背景颜色应该与字体颜色和前景颜色想搭配;四,图片的大小和质量也要考虑,可以采用JPG格式存储图书图片。
(3)内容测试
内容测试用来检验Web应用系统提供的信息的正确性、准确性和相关性。例如图书的信息是否正确、准确,不能发生张冠李戴的错误。特别是图书的价格和图书的图片不能弄错。
(4)整体界面测试
整体界面是指整个Web应用系统的页面结构设计,给用户的一个整体感。例如,当用户浏览Web应用系统时是否感到舒适,是否凭直觉就知道要找的信息在什么地方,整个Web应用系统的设计风格是否一致。
对所有的可用性测试,都需要外部人员的参与,最好是最终用户的参与,因为Web应用系统最终要面对的就是最终用户。
5.兼容性测试
(1)平台测试
平台测试,是指在Web系统发布之前,需要在各种操作系统下对Web系统进行兼容性测试。Web应用系统的最终用户使用的操作系统可能不是统一的,同一个应用可能在某些系统性下能正常运行,但在另外的操作系统下可能会失败。
(2)浏览器测试
浏览器是Web客户端最核心的构件,来自不同厂商的流览器对Java、JavaScript、CSS、HTML有不同层次的支持。而用户使用的浏览器也很可能是不同的,所以需要进行浏览器的测试。这方面的测试可以使用市场中占有大比例的浏览器进行测试,如IE的各种版本、Firefox、Mozilla、Opera、Netscape Navigetor等。
七、总结
这个项目基本上实现了系统的需求,但仍存在着其它的问题。
界面方面。整个系统的整体风格大致统一,但还有很大的改进空间。整个界面不能给人一种很舒适的感觉。文字的字体、大小、颜色与背景并不十分协调,并且过于呆板,未能以丰富的形式展示给用户。
流程方面。
分页方面。订单查询结果未进行分页。分页的结果中未实现随意跳转页面的链接,只能进行首页、上一页、下一页、尾页的跳转。分页还不能灵活,例如每页显示的结果数不能由用户决定,只能由系统决定,比较好的做法是,每页显示的结果数由用户自己决定。
性能方面
测试方面。测试的项目还不完全,像安全测试等没有进行。测试的工具采用的基本上是手工,这样比较容易出错,不能测试出系统潜在的错误。系统的性能方面的测试基本上没有得到很好的测试,因为缺少测试工具。测试中最终用户的参与人数不多,反馈信息有些不足。
安全方面。用户订单的信息包含了敏感数据,像手机号码、信用卡号等,在传输的过程中未采用安全传输技术,如HTTPS协议。购物车未经登录的用户也可以使用,更好的做法是在使用购物车前首先验证用户是否为合法的用户。
功能方面。(1)语言转换应该由下拉框完成,这样不但可以节省空间,而且可以使页面更美观。如果是两种语言的选择,采用链接的方法还可以接受;如果是几种语言的选择的话,这种做法就真的不可取了。(2)点击放入购物车后的页面
应该停留在当前页面,并且把购物车的数据更新。而目前的做法是点击放入购物车后,页面跳转到管理购物车的页面,而用户可能并不希望在此时见到这个页面,而是在最后结账的时候看到这个页面。