一、源码路径
https://github.com/weiganyi/ibooking
二、界面
通过浏览器访问web网站,可以看到界面如下:
三、背景
这两年来O2O的概念越来越火热,O2O因为能够把线下的资源通过线上的信息沟通渠道进行连接,影响着很多实体行业未来的发展。
这个项目就是为餐饮店提供线上外卖订餐服务的web网站,具有O2O网站的特点。网站有顾客和管理员两类角色,分别对于与餐饮店的顾客和餐饮店自身。用户在网站上注册后会留下用户的地址和联系电话,这些信息会作为送外卖时的用户信息,然后用户能够在网站上看到餐饮店的所有菜品图片,选中后到购物车下单,就会生成一个订单,餐饮店后台可以看到这些订单,然后完成后续的餐品制作和送达服务。
系统后台采用的是成熟的SSH框架来搭建服务,前端仍然是用jsp来拼接页面,所以页面还是在后台生成的。由于本人是后台开发,所以为了美化界面采用bootstrap的css框架,在js框架方面只是使用了jquery,并没有使用其他js框架。数据库方面使用的mysql,对于用户访问量大的几个表采用了redis做为缓存,提高响应速度。
四、功能实现
1、用户功能
1)菜品预定:菜品展示(主页)、购物车、订单列表、订单详情
2)注册登录:注册、登录、用户信息
2、管理员功能
1)用户管理
2)订单管理
3)图片管理
4)菜品管理
5)菜品类型管理
6)配置管理
五、总体设计思路
1、数据库设计
1)其中ib_menu_type表存储菜品类型信息,ib_menu表存储菜品信息,ib_option表存储配置信息,ib_user表存储用户信息,ib_shopping表存储用户购物车信息,ib_order表存储订单信息,ib_order_detail表存储订单详情信息,这7个表为关系型表,使用mysql数据库存储。它们的具体字段如下:
create table ib_menu_type(
menu_type_id int(4) not null primary key auto_increment,
menu_type_name char(255) not null);
create table ib_menu(
menu_id int(4) not null primary key auto_increment,
menu_name char(255) not null,
menu_price int(16) not null,
menu_pic_addr char(255) not null,
menu_type_id int(4) not null);
create table ib_option(
option_id int(4) not null primary key auto_increment,
option_name char(255) not null,
option_value char(255) not null);
create table ib_user(
user_id int(16) not null primary key auto_increment,
user_name char(255) not null,
user_passwd char(255) not null,
user_auth enum('admin', 'customer') not null,
user_tel char(255) not null,
user_addr char(255) not null);
create table ib_shopping(
shopping_id int(16) not null primary key auto_increment,
shopping_user_name char(255) not null,
shopping_menu_name char(255) not null,
shopping_menu_price int(16) not null,
shopping_amount int(16) not null,
shopping_remark char(255) not null);
create table ib_order(
order_id int(16) not null primary key auto_increment,
order_user_name char(255) not null,
order_time datetime not null,
order_admin_name char(255) not null,
order_accept int(1) not null);
create table ib_order_detail(
detail_id int(16) not null primary key auto_increment,
detail_order_id int(16) not null,
detail_menu_name char(255) not null,
detail_menu_price int(16) not null,
detail_amount int(16) not null,
detail_remark char(255) not null);
2)用户访问量大的menu、menutype和option三个表使用redis缓存,因为这几个表不会频繁更新,不会造成缓存频繁失效,同时对于不同的用户,都会访问这几个表里相同的字段,所以它们使用缓存是比较有意义的。
3)由于使用了缓存,redis和mysql之间的数据同步设计是要点。最开始,我采用的读流程为在redis启动时先从mysql读入表的所有记录,后续直接从redis查询记录,写流程为先写入redis,并将其放入一个队列,队列消费者读取队列后再写入mysql,当mysql写失败时,要把redis写入的内容回滚。但经过分析这种设计是有问题的,redis与mysql数据同步肯定会涉及一个主次问题,之前的设计相当于redis为主,mysql为次,这样当mysql写失败时再回滚redis,这时redis可能已经有新数据写入了,再回滚会造成用户后写入的数据被刷掉了。正确的写流程应该以mysql为主,先写入mysql然后设置redis对应的键失效,这样成功写入mysql后,即便设置redis键失效操作失败,由于缓存可以设置有效期,也只是会在缓存有效期内暂时影响读到的数据,而数据由于已经写入了mysql是能够正确的保存的。
4)在写操作时redis生成自增id,然后插入mysql,必须保证redis生成的自增id大于mysql表的auto_increment当前值才能成功。所以redis启动时需要先读取mysql对应表的auto_increment值并保存,以后每次新增记录时,把auto_increment值加一并作为id值,这样就能够保证总是大于mysql表的auto_increment当前值。
5)在事务可靠性方面,当需要同时修改两个表的记录时,redis和mysql需要各自使用事务。redis自身有事务机制可以使用,而mysql可以使用SSH框架的aop声明式事务机制。
6)redis的数据格式为了对应mysql表,需要有一条记录表示mysql表的索引,如set/get "ib_menu:test:id" "6",这里test是菜品名称,也是ib_menu表的索引是菜品名,这样就可以通过这条记录取得test菜品的id值,然后对应这个id值有系列记录表示mysql表的各个字段,如set/get "ib_menu:6:menu_price" "13",这里就表示id为6的记录的menu_price字段值为13。
2、Web前端设计
1)在html页面的构造上,采用jsp脚本来完成。根据后台servlet逻辑处理完后生成的java bean对象,在jsp文件内,通过java脚本或者jstl等jsp技术,获取java bean对象拼装成所需要的html页面。
2)采用bootstrap来美化界面,采用bootstrap封装的一些组件。
3)界面按钮触发ajax请求,页面刷新方式有两种,一种响应只更新页面具体元素值,另一种是响应更新除了页头外的其余部分。
3、Java后台服务设计
1)后台整体模块采用MVC经典分成架构,分为如下5层:
表现层:jsp页面。
MVC Action层:每个页面对应一个Action。
业务逻辑层:负责后台业务逻辑实现。
DAO层:负责数据库和缓存的读写逻辑实现。
数据对象层:对数据库或缓存做对象映射。
2)对struts包含组件和特性的使用情况:
action:实现主要控制器逻辑。
国际化:使用全局国际化资源文件。
标签:主要使用s:text, s:property, s:fielderror和控制标签,structs的s:form比较弱,所以使用原生的并用bootstrap来增强。
类型转换:请求参数解析和响应参数构造由类型转换来实现,可能需要自定义转换器。
输入校验:文本输入需要增加校验,使用全局校验文件。
文件上传:使用封装的文件上传框架,包括拦截器文件过滤功能。
拦截器:实现一个自定义的管理员访问权限控制拦截器。
3)对hibernate数据库的封装的使用情况:
单表映射:ib_option使用单表映射。
关联映射:ib_menu与ib_menu_type使用无连接表的单向N-1关联。
hql查询:使用hibernate特有的hql查询语法,便于与HibernateDaoSupport框架集成。
开启二级缓存:由于使用了redis做缓存,所以不使用hibernate内置的二级缓存功能。
4)对spring特性的使用情况:
ioc依赖注入:如果采用spring来注入struts action会造成xml冗余,所以不使用这种方式,
使用spring启动hibernate的sessionFactory,并用ioc注入的dao对象方式来进行数据库表访问。
aop切面注入:对多表操作使用xml方式声明式事务机制。
5)线程安全方面的考虑,因为HibernateTemplate是线程安全的,所以mysql访问不需要加锁,而redis访问需要加锁。
6)在服务部署上,使用nginx做反向代理,把请求转发到后端的tomcat服务器上进行处理。这也是常用的部署方式,因为目前系统文件比较少,所以没有把图片等静态资源放到nginx下这种动静分离的做法。
六、文件目录介绍
bin:系统脚本,如重启脚本
res\pic:图片资源文件
res\tmp:删除图片时的临时存放目录
WEB-INF\jsp:jsp文件
WEB-INF\jsp\manager:后台管理模块的jsp文件
WEB-INF\lib:依赖的jar包
WEB-INF\src\ibooking\action:action的实现
WEB-INF\src\ibooking\action\authority:拦截器实现
WEB-INF\src\ibooking\action\base:action的父类
WEB-INF\src\ibooking\action\manager:后台管理模块的action实现
WEB-INF\src\ibooking\dao:dao层的父类
WEB-INF\src\ibooking\dao\impl:dao的实现
WEB-INF\src\ibooking\po:数据对象映射类
WEB-INF\src\ibooking\service:业务逻辑层父类
WEB-INF\src\ibooking\service\impl:业务逻辑层实现
WEB-INF\src\ibooking\util:辅助类实现
WEB-INF\src\ibooking\vo:视图层对象
WEB-INF\src\ibooking\vo\manager:后台管理模块的视图层对象
七、部署方法
1、源码下载后,用eclipse编译服务端源码。
2、在服务器上安装部署nginx和tomcat,配置nginx把所有请求转发到tmcat,同时安装部署好mysql和redis。
3、在tomcat/webapps下建立项目目录ibooking,然后把编译生成的目录和文件拷贝到ibooking下。
4、把根目录下的数据库备份文件ibooking_mysql_db.sql导入mysql。
5、通过浏览器也可以访问系统的Web部分,使用顾客和管理员角色相关功能。
(完)