web项目
1.关于web项目目的:
将web阶段所有学过的知识点复习总结.
2.关于web项目功能:
功能:
1、用户注册
2、用户登录
3、添加商品(上传)
4、商品查看– 列表查询
5、商品详情页面
6、将商品添加购物车
7、查看购物车
8、修改购物车
9、生成订单
10、订单查看(取消)
11、在线支付
12、销售榜单查看
会对项目进行重构.
会使用注解+动态代理实现细粒度权限控制.
添加关于ajax操作
对于订单操作时,使用ajax
在线支付功能
3.系统分析
1.通过UML用例图来确定当前用户以及所具有的功能
游客(未登录): 注册、登陆、商品查看
商城注册用户 : 商品查看、添加商品到购物车、购物车管理、生成订单、订单管理、在线支付
管理员 : 添加商品、商品管理、查看订单 、榜单查看(导出)
2.系统设计
1.技术选型
JSTL + JSP + Servlet + JavaBean + BeanUtils + FileUpload + JavaMail + DBUtils(JDBC) + C3P0 + MySQL + MyEclipse10+ Tomcat7.0 + JDK6 + Windows
MVC 模式
JavaEE 三层结构
DAO 模式
2.数据库设计
E-R图 实体关系图.
对于我们当前项目有这些实体 用户 商品 购物车 订单
通过E-R图可以分析出我们数据库中表与表之间的关系,以及每一个表中的属性。
create table users (
id int primary key auto_increment,
username varchar(40),
password varchar(100),
nickname varchar(40),
email varchar(100),
role varchar(100) ,
state int ,
activecode varchar(100),
updatetime timestamp );
商品表
create table products(
id varchar(100) primary key ,
name varchar(40),
price double,
category varchar(40),
pnum int ,
imgurl varchar(100),
description varchar(255));
订单表
create table orders(
id varchar(100) primary key,
money double,
receiverinfo varchar(255),
paystate int,
ordertime timestamp,
user_id int ,
foreign key(user_id) references users(id)
);
用户与订单之间存在 一对多关系 : 在多方添加一方主键作为外键
订单和商品之间存在 多对多关系 : 创建第三张关系表,引入两张表主键作为外键 (联合主键)
订单项
create table orderitem(
order_id varchar(100),
product_id varchar(100),
buynum int ,
primary key(order_id,product_id),
foreign key(order_id) references orders(id),
foreign key(product_id) references products(id)
);
设置数据库环境
数据库 :create database estoresystem;
——————————————————————–
4.环境搭建
1.导入jar包
导入mysql驱动 mysql driver / mysql-connector-java-5.0.8-bin.jar
导入c3p0 c3p0/c3p0-0.9.1.2.jar 将c3p0-config.xml 复制src下 将DataSourceUtils复制 cn.itcast.estore.utils —– 配置c3p0-config.xml数据库连接参数
导入dbutils apache commons\dbutils\commons-dbutils-1.4.jar
导入beanutils commons-beanutils-1.8.3.jar commons-logging-1.1.1.jar
导入fileupload commons-fileupload-1.2.1.jar commons-io-1.4.jar
导入javamail mail.jar
导入jstl jstl.jar standard.jar
2.创建包结构
cn.itcast.estore.web.servlet
cn.itcast.estore.web.filter
cn.itcast.estore.web.listener
cn.itcast.estore.service
cn.itcast.estore.dao
cn.itcast.estore.domain
cn.itcast.estore.utils
3.创建domain
UML中类图画法
4.工程发布
将estore项目配置虚拟主机,以顶级域名方式进行发布
在浏览器上直接输入www.estore.com就可以访问到我们的工程.
1.在tomcat的conf目录下的server.xml文件中配置
1.修改tomcat的端口 80.
2.配置虚拟主机
<Engine name="Catalina" defaultHost="www.estore.com">
<Host name="www.estore.com" appBase="D:\java1110\workspace\estore" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Context path="" docBase="D:\java1110\workspace\estore\WebRoot"/>
</Host>
3.在C:\Windows\System32\drivers\etc\hosts文件中配置
127.0.0.1 www.estore.com
注意:在启动tomcat时,不需要将工程estore工程部署到tomcat.
=========================================================================================
功能实现:
1.注册操作
1.一次性验证码
2.表单的js校验(非空校验)
服务器端的校验
3.全局编码过滤器
4.密码md5加密
5.发送激活邮件
6.通用错误页面配置
2.实现注册功能
1.修改静态页面
直接创建一个jsp,将page.html页面内容复制到home.jsp页面.
在index.jsp中添加一个请求转发操作
<jsp:forward page="/home.jsp"/>
2.在home.jsp页面上添加一个注册的连接。
<a href="${pageContext.request.contextPath}/regist.jsp">注册</a>
3.创建一个regist.jsp页面
页面上有
1.username
2.password
3.email
4.nickname
还需要一个repassword 确认密码
还需要一个验证码
4.在页面上产生验证码
1.将new_words.txt复制到WEB-INF目录下.
2.在regist.jsp页面上
<img src="${pageContext.request.contextPath}/checkImg" οnclick="change();" id="cimg">
注意编码问题.
5.完成注册的流程
regist.jsp—->RegistServlet—–UserService UserDao
关于用户的role与state
对于注册的用户它的role我们默认设置为"user",state默认设置为0 代表没有激活,
对于激活码,我们在注册时,要生成。通过uuid生成.
注册成功后跳转到 regist_success.jsp页面,在页面上显示3秒后跳转到首页.
问题:
1.怎样让3秒数字变化.
通过js代码来完成
var interval;
window.onload = function() {
interval = window.setInterval("fun()", 1000); //设置1秒调用一次fun函数
};
function fun() {
var time = document.getElementById("s").innerHTML;
//判断如果等于0了,不在进行调用fun函数,
if (time == 0) {
window.clearInterval(interval);
return;
}
document.getElementById("s").innerHTML = (time – 1);
}
2.关于乱码处理
使用全局的编码过滤器
EncodingFilter完成post与get请求的编码处理.
6.关于注册操作的校验问题
1.表单校验
就是通过js代码完成.
1.在<form οnsubmit="return checkForm();">
2.校验,只校验非空.
function checkNull(fieldName){
var value = document.getElementById(fieldName).value;
var reg = /^\s*$/; //代表0个或多个空字符。
if(reg.test(value)){
document.getElementById(fieldName+"_message").innerHTML="<font color='red'>"+fieldName+"不能为空</font>";
return false;
}else{
return true;
}
}
2.服务器端校验
在javaBean中做一个校验方法
public Map<String, String> validation() {
Map<String, String> map = new HashMap<String, String>();
if (username == null || username.trim().length() == 0) {
map.put("regist.username.error", "用户名不能为空");
}
if (password == null || password.trim().length() == 0) {
map.put("regist.password.error", "密码不能为空");
}
return map;
}
在通过BeanUtils将请求参数封装到javaBean后,调用校验方法.
如果判断Map集合中有数据,说明存储了错误信息,就跳转到regist.jsp页面。
在页面上展示错误信息,使用jstl标签。
7.关于发送激活邮件
邮箱 duhongabcdef@163.com
密码 abc123
关于激活邮件发送我们在UserService中的regist方法中完成。
关于MailUtils工具使用:
1.props.setProperty("mail.host", "自己邮箱的smtp");
2.return new PasswordAuthentication("邮箱帐户", "密码");
3.message.setFrom(new InternetAddress("发送者邮箱"));
注意:关于发送的信息问题:
String emailMsg="注册成功,
请<a href='http://www.estore.com/activeUser?activeCode="+user.getActivecode()+"'>激活</a>,
激活码为:"+user.getActivecode();
8.关于激活用户操作
http://www.estore.com/activeUser?activeCode=d104ed31-0a5f-4eb4-8377-085c9be5f6e4
1.创建一个ActiveUserServlet
1.得到激活码,
2.调用service中激活操作
注意:激活是有时间限制的。
3.在service中完成操作时有两件事要做:
1.根据激活码查找用户
2.判断用户激活码没有过期,进行激活操作.
9.md5加密
mysql中:UPDATE users SET PASSWORD=MD5(PASSWORD);
在UserDao的addUser方法中,对user.getPassword()使用Md5Utils工具进行加密。
10.验证码
在所有操作前,通过requst获取请求中的验证三,与session中存储的验证码进行对比。
从session中获取完成后,马上删除。
在CheckImgServlet中有一句话:
request.getSession().setAttribute("checkcode_session", word);
String checkCode = request.getParameter("checkcode");
String _checkCode = (String) request.getSession().getAttribute(
"checkcode_session");
request.getSession().removeAttribute("checkcode_session");//从session中删除。
if (!checkCode.equals(_checkCode)) {
request.setAttribute("regist.message", "验证码不正确");
request.getRequestDispatcher("/regist.jsp").forward(request,
response);
return;
}
================================================================================================================
2.登录操作
1.登录操作中具有的功能
1.请求信息的校验
1.客户端校验
2.服务器端校验
2.请住用户名操作
3.自动登录操作
4.注销功能
登录注意事项:
1.注意用户是否激活
2.注意密码已经进行了md5加密。
用户登录成功后,将用户存储到session中.
2.登录代码实现:
1.登录基本流程
1.在home.jsp页面上有登录窗口。
2.创建一个LoginServlet
完成登录操作 ,在LoginServlet中
1.得到用户名与密码
2.调用service完成登录操作.
3.在service中判断用户是否可以登录,以及用户是否激活
4.在home.jsp页面上,完成错误信息展示以及用户登录的提示。
————————————————————
2.记住用户名操作
原理:当用户登录成功后,会判断用户是否勾选了记住用户名,如果勾选了,将用户名存储到cookie中。
下一次在访问登录页面,直接从cookie中获取用户名,显示在用户名文本框上。
问题:cookie中不能存中文?
存:Cookie cookie = new Cookie("remember", URLEncoder.encode(user.getUsername(), "utf-8"));
取:
window.οnlοad=function(){//页面加载成功后跳用这个函数。
var username=document.getElementById("username");
window.de
username.value=window.decodeURIComponent("${cookie.remember.value}","utf-8");
};
关于删除cookie:
1.setMaxAge(0/-1) 0代表立即删除 -1代表关闭浏览器后才删除。
2.删除cookie时,必须与原cookie的path值一致.
———————————————————————–
3.自动登录
原理:用户登录成功,判断是否勾选了自动登录,如果勾选了,将用户名与密码存储到cookie中。
需要一个Filter,当用户访问工程时,在Filter中从cookie取出用户名与密码,进行登录操作。
注意事项:
1.存密码时,注意加密。
2.如果用户已经登录,不需要在登录。
3.如果访问的资源路径不需要自动登录,那么不进行自动登录。
4.第一个用户自动登录,又使用了第二个用户登录,它没有勾选自动登录,
这时,需要将自动登录的cookie删除。
————————————————————————-
4.注销
在home.jsp页面有注解的连接。
<a href="${pageContext.request.contextPath}/logout">注销</a>
创建一个LogOutServlet完成注解功能
session.invalidate();
问题:销毁session的方式:
1.关闭服务器
2.invalidate()
3.自动超时
在tomcat/conf/web.xml文件中配置超时时间
<session-config>
<session-timeout>30</session-timeout>
</session-config>
4.setMaxInactiveInterval(int interval)
手动设置session超时时间.
注意:如果我们有自动登录操作,那么当我们完成注销操作后,会跳转到首页,
这时,如果在cookie中存储了用户名与密码,就会进行自动登录,那么
注销的效果就看不到了,所以要看到效果,可以将自动登录的cookie删除。
问题:
1.一个用户在两个浏览器登录
要想解决,需要将数据存储到数据库中。
可以使用session共享服务器来解决.
2.两个用户在同一个浏览器登录
会出现共享session问题,简单说,第一个用户购买的物品,存储在session中。
第二个用户登录后,直接就可以看到第一个用户购买的商品。
解决方案:在每一个用户登录时,先销毁session.
============================================================================================================
商品操作:
1.添加商品(上传操作)
1.在home.jsp页面上有一个连接
<a href="${pageContext.request.contextPath}/addProduct.jsp">添加商品</a>
2.创建addProduct.jsp页面
问题:页面上有什么组件?
查看products表中的数据.
文件上传时浏览器端注意:
1.method=post
2.encType="multipart/form-data"
3.<input type="file" name="f">
3.根据表中的数据创建Product类
private String id; // 商品编号
private String name; // 名称
private double price; // 价格
private String category; // 分类
private int pnum; // 数量
private String imgurl; // 图片路径
private String description; // 描述
4.编写AddProductServlet
完成添加商品操作—-其实是文件上传.
1.DiskFileItemFactory
2.ServletFileUpload
3.FileItem
在这个servlet中要完成两件事情:
1.文件上传
问题:
1.上传文件中文名乱码
upload.setHeaderEncoding("utf-8");
2.上传文件名称获取?
item.getName(); 得到的有可能包含路径。
3.上传文件名称重复
uuid获取随机名称
4.上传文件随机目录。
通过文件名的hashCode进行计算,随机得到目录.
5.关于上传文件保存位置
对于我们这个项目,上传的商品图片,是允许浏览器直接访问的,
所以我们保存到WebRoot下的upload目录下.
2.向products表中添加数据。
1.得到所有数据封装到Product对象中.
BeanUtils.populate(product,Map);
这个Map怎样得到?
手动创建一个Map<String,String[]> 将数据手动封装.
问题:关于id怎样封装?
uuid获取.
问题:关于imgurl怎样封装?
map.put("imgurl", new String[]{"/upload"+uuidDir+"/"+uuidname})
2.调用service,dao完成添加操作.
3.添加成功后,跳转到首面.
—————————————————————
2.查看商品
1.查看全部
index.jsp—->findAllProduct——->home.jsp
1.创建一个FindAllProductServlet
在这个servlet中查询出所有商品List<Product>,将其存储到request域,在请求转发到home.jsp页面
2.在home.jsp页面展示
<div class="art-content-layout overview-table">
<div class="art-content-layout-row">
<c:forEach items="${ps}" var="p" varStatus="vs">
<div class="art-layout-cell">
<div class="overview-table-inner">
<h4>${p.name }</h4>
<img src="${pageContext.request.contextPath}${p.imgurl}" width="55px" height="55px"
alt="an image" class="image" />
<p>价格: ¥${p.price }</p>
<p>速速抢购</p>
</div>
</div>
<c:if test="${vs.count%5==0}">
</div> <!– 判断当前已经有5个商品了,这 一行结束,在重新开启一行 –>
<div class="art-content-layout-row">
</c:if>
</c:forEach>
<!– end cell –>
</div>
<!– end row –>
</div>
<!– end table –>
————————————————–
2.查看商品详细信息
它有两个入口,一个是点击速速抢购.还有点击图片也可以查看商品详细信息.
1.<a href="${pageContext.request.contextPath}/findProductById?id=${p.id}">速速抢购</a>
2.在图片上添加一个onclick
function findProductById(id){
location.href="http://www.estore.com/findProductById?id="+id;
};
查看商品详细信息:
1.创建FindProductByIdServlet
1.得到商品id
2.根据id调用service,dao完成查询商品操作.
2.创建productInfo.jsp页面,展示商品信息
关于展示商品时,
<img>展示商品图片,可以通过它的width与height属性来控制图片的大小。
在开发中还有另外一种处理方式:使用商品图片的缩略图。
我们可以自己编程,去获取一个上传图片的缩略图。
在展示时,直接得到它的缩略图来展示 .
在文件上传完成后,添加这两名话就会产生图片的缩略图
// 生成缩略图
PicUtils putils = new PicUtils(dest.getCanonicalPath());// 获取上传文件的绝对磁盘路径。
putils.resize(200, 200);// 就会产生一个200*200的缩略图.
使用缩略图
1.在Product类中添加一个方法
public String getImgurl_s() {
int index = imgurl.lastIndexOf(".");
return imgurl.substring(0, index) + "_s" + imgurl.substring(index);
}
2.在productInfo.jsp页面上
<img src="${pageContext.request.contextPath}${p.imgurl_s}">
==========================================================================================================
购物车
我们使用的session来存储购物车,在数据库中没有关于购物车中商品信息。
1.添加商品到购物车
在productInfo.jsp页面有连接。
问题:怎样将商品添加到购物车,购物车我们使用什么数据结构来存储商品信息?
购物车我们使用Map<Product,Integer>
在productInfo.jsp页面上连接,它要传递商品的id
function addProductToCart(id){
location.href="${pageContext.request.contextPath}/addProductToCart?id="+id;
}
创建一个AddProductToCartServlet
1.根据id得到商品
2.得到购物车
3.将商品添加到购物车
注意:我们使用的购物车其实是一个HashMap<Product,Integer>,对于HashMap,它的维护主键唯一性,是使用
key值的hashCode与equals方法,也就是说,对于我们的购物车,它是使用Product的hashCode与equals方法
来保证,我们同一个商品的数量的变化.
简单说,对于我们就需要重写Product类的equals方法与hashCode方法。
————————————————————————————–
2.查看购物车商品
有两个入口
1.添加商品到购物车成功后,有提示,查看购物车
2.在首面有查看购物车
查看购物车,我们直接就是一个jsp页面上将购物车中商品展示出来就可以。
因为购物车就存储在session中.
创建一个showCart.jsp,用于展示购物车中所有商品.
购物车中商品总价怎样获取:每个商品的单价*商品数量
<c:set var="totalMoney" value="${totalMoney+c.key.price*c.value}"/>
—————————————————————————————–
3.改变购物车中商品数量
1.加操作
点击加按钮,要访问一个servlet,在servlet中,获取购物车中商品,对其数量进行操作.
function changeCount(id,count){
location.href="${pageContext.request.contextPath}/changeCount?id="+id+"&count="+count;
}
在服务器端:
Product p=new Product();
p.setId(id);
cart.put(p, count);
问题:怎样控制边界?
如果数量减到0,相当于将商品从购物车中删除。
如果数量加到比商品库存还大,就让它等于最大值.
在js代码中控制
//控制边界
if (count <= 0) {
//删除
var flag = window.confirm("要删除商品吗?");
if (flag) {
count = 0;
} else {
count = 1;
}
} else if (count >= pnum) {
alert("最大购物数量"+pnum);
count = pnum;
}
注意:如果购物数量为0,这时会以服务器端将商品从购物车中删除。
关于文本框中数量修改:
对于文本框,可以添加一个onblur事件
注意:在js中数据是无类型的,操作时,有的进修传参数,想要传递的是一个数值类型,
但是,js将其做为字符串处理了,就需要使用parseInt() parseFloat()来转换成数值类型。
数字文本框:
在<input type="text">这个文本框中只能输入数字,不能输入其它的字符。
原理:给文本框添加onkeydown事件,当触发事件时,获取按下的键的键码值,如果它的键码是数字0-9之间的就可以执行,
如果不是,阻止事件的默认行为 .
1.问题:怎样获取按下的键码值
2.问题:怎样阻止事件的默认行为
function a(e){
var keyCode;
if(e&&e.preventDefault){
//判断是firefox浏览器
keyCode=e.which;
}else{
//ie浏览器
keyCode=window.event.keyCode;
}
//alert(keyCode);
//0-9之间的键码值是48-57
if(!(keyCode>=48&&keyCode<=57||keyCode==8)){
//阻止事件的默认行为
if(e&&e.preventDefault){
// e对象存在,preventDefault方法存在 —- 火狐浏览器
e.preventDefault();
}else{
// 不支持e对象,或者没有preventDefault方法 —- IE
window.event.returnValue = false;
}
}
};
—————————————————————————
从购物车中删除商品
1.连接提交时,将商品id携带到服务器
<a href="${pageContext.request.contextPath}/removeProductFromCart?id=${c.key.id}">删除</a>
2.创建RemoveProductFromCartServlet 将要删除的商品从购物车中删除
// 得到要删除的商品的id
String id = request.getParameter("id");
// 得到购物车,从购物车中将商品删除,
Map<Product, Integer> cart = (Map<Product, Integer>) request
.getSession().getAttribute("cart");
Product p = new Product();
p.setId(id);
cart.remove(p);
//如果购物车中无商品,将购物车删除。
if (cart.size() == 0) {
request.getSession().removeAttribute("cart");
}
关于删除商品时的提示处理:
方式1:
<a href="javascript:void(0)" οnclick="removeProduct('${c.key.id}')">删除</a>
function removeProduct(id) {
var flag = window.confirm("要删除商品码?");
if(flag){
//要删除
location.href="${pageContext.request.contextPath}/removeProductFromCart?id="+id;
}
}
方式2:通过阻止事件的默认行为来控制
<a href="${pageContext.request.contextPath}/removeProductFromCart?id=${c.key.id}"
οnclick="deleteProduct(event)">删除</a>
function deleteProduct(e) {
var flag = window.confirm("要删除商品码?");
if (!flag) {
//不删除,阻止连接的默认行为 执行。
//阻止事件的默认行为
if (e && e.preventDefault) {
// e对象存在,preventDefault方法存在 —- 火狐浏览器
e.preventDefault();
} else {
// 不支持e对象,或者没有preventDefault方法 —- IE
window.event.returnValue = false;
}
}
};
============================================================================================================
订单操作
1.生成订单
1.在showCart.jsp页面上,有一个连接,进行结算中心,
应该跳转到一个order.jsp页面.在这个页面上输入订单的相关信息。
输入完成后,提交信息,生成订单。
2.表单提交,访问一个AddOrderServlet,完成生成订单操作.
1.将数据封装到Order对象中.
Order order=new Order();
//它封装了订单的 送货地址,总价.
BeanUtils.populate(order, request.getParameterMap());
String id=UUID.randomUUID().toString();
order.setId(id);//封装订单的id
order.setPaystate(0);//默认值为0,代表未支付。如果为1,代表支付.
//封装user_id
//从session中获取当前用户.
User user=(User) request.getSession().getAttribute("user");
int user_id=user.getId();
order.setUser_id(user_id);
问题:我们生成订单,会对几张表操作?
1.insert into orders
2.insert into orderItem
3.update products set pnum=pnum-?;
对于订单操作,必须添加事务处理.
3.对DataSourceUtils进行修改
// 获取绑定到ThreadLocal中的Connection。
public static Connection getConnectionByTransaction() throws SQLException {
Connection con = tl.get();
if (con == null) {
con = dataSource.getConnection();
tl.set(con);
}
return con;
}
// 开启事务
public static void startTransaction(Connection con) throws SQLException {
if (con != null)
con.setAutoCommit(false);
}
// 事务回滚
public static void rollback(Connection con) throws SQLException {
if (con != null)
con.rollback();
}
public static void closeConnection(Connection con) throws SQLException {
if (con != null) {
con.commit();// 事务提交
con.close();
tl.remove();
}
}
注意:在dao中在使用QueryRunner时,不要使用有参数构造,要使用无参数构造,
使用QueryRunner的batch,update方法时,要带Connection参数,而Connection对象的获取是
通过getConnectionByTransaction来获取的。
——————————————————————————
查看订单:
1.查看订单时,如果当前用户是admin角色,可以查看所有人订单,如果用户是user,只能查看自己订单。
2.查看订单实现:
1.select * from orders;—–>List<Order>
2.查询订单中商品的信息。
3.代码实现:
1.点击查看订单时,访问一个ShowOrderServlet,查询出所有订单信息
select * from orders;—–>List<Order>
将List集合存储到request域,跳转到showOrder.jsp页面在,展示所有信息.
response.getWriter().write("订单生成成功,<a href='"+request.getContextPath()+"/showOrder'>查看订单</a>");
<a href="${pageContext.request.contextPath}/showOrder"></a>
2.在showOrder.jsp页面展示订单:
问题:展示订单时,想要显示当前订单是哪个用户的,怎样得到用户名?
我们需要将orders与users表关联查询.
String sql = "select users.username,users.nickname,orders.* from orders,users where users.id=orders.user_id ";
查询出的数据要封装到Order对象中,但是不能封装username,nickname,所以我们在Order类中添加了两个属性
private String username;
private String nickname;
————————————-
3.查看订单中商品详细信息 ajax完成
1.查找某一个订单中所有商品信息的sql语句
SELECT
数据
FROM
orderitem,products
WHERE
orderitem.product_id=products.id
AND
orderitem.order_id="订单id";
2.使用ajax完成操作
1.得到XMLHttpRequest对象
2.onreadstatechange 注册回调函数
3.open
4.send
5.在回调函数中操作.
我们查询出订单中商品信息,以json格式返回.
———————————————————————————————
删除订单
1.在showOrder.jsp页面有删除订单的连接。点击连接时,将订单的id传递到服务器端,完成根据id删除订单操作.
问题:如果订单是已经支付的怎样处理?如果是没有支付怎样处理?
我们人为规定,如果是已支付订单,不能删除。如果是未支付订单可以删除。
<c:if test="${order.paystate==0}">
<a href="#">删除</a>
</c:if>
<c:if test="${order.paystate!=0}">
删除
</c:if>
问题:对于未支付订单,删除时,怎样操作?
需要做三件事情:
1.delete from orders where id=?;
2.delte from orderitem where order_id=?
3.update products set pnum=pnum+? where id=?;
在操作时,需要先删除orderitem表中数据,在删除orders中数据。
我们最后要修改products表中的数据,而它需要的buynum,与product_id都是在orderitem表中存在的。
分析完成上面操作,我们在代码实现时,步骤:
1.select * from orderitem where order_id=?;—–>List<OrderItem>
2.delete from orderitem where order_id=?;
3.delete from order where id=?
4.updat products set pnum=pnum+buynum where id=product_id;
我们操作时,需要对多表进行操作,也需要事务控制.
2.代码实现:
1.在showOrder.jsp页面添加连接路径.
<a href="${pageContext.request.contextPath}/delOrder?orderid=${order.id}">删除</a>
2.创建DelOrderServlet
1.得到要删除的订单id.
2.调用service完成删除订单操作.
3.在OrderService中创建一个方法 delOrderById(String orderid);
在这个方法中.
1.开启事务
2.根据orderid查询出所有的orderitem中数据.得到一个List<Orderitem>
3.根据orderid在orderitem中删除数据
4.根据orderid在orders事删除数据
5.根据List<OrderItem>在products表中修改数据.
6.如果有异常,事务回滚,没问题,事务提交,资源释放。
——————————————————————————–
订单支付:
在线支付—新的知识点
1.在showOrder.jsp页面,如果当订单状态是为支付,我们将其设置成一个连接。
<a href='${pageContext.request.contextPath}/pay.jsp?orderid=${order.id}&money=${order.money}'>未支付</a>
2.创建一个pay.jsp页面,它就是我们的支付页面。
在页面上得到订单号,金额信息,并且可以选择支付的银行.
3.完成在线支付
1.什么是在线支付?在线支付实现方式?
通过网络直接完成订单的支付。
有两种方式:
1.直接与银行做对接
优点:不会有延迟。
缺点:开发,维护费用比较高.银行接口变动,需要更改。
这种方式不适合中小商户。
2.使用第三方支付
优点:方便,不用处理是怎样支付
缺点:有延迟,会收取一定费用。
这种方式比较适合中小商户。
2.我们使用的是第三方支付
现在使用的比较多第三方支付 支付宝 财富通 快钱
我们使用 易宝支付(http://www.yeepay.com/)
在线支付条件:
1.可以上网。
2.需要开通网银.
开发在线支付条件:
1.可以上网.
2.需要一个独立ip.
3.需要在易宝支付申请一个商家账号。 10001126856
在线支付流程:
查看图
——————————————–
在线支付代码实现:
1.pay.jsp,页面上有订单号,金额以及选择的银行。
2.OnLinePayServlet,这个servlet就完成数据的收集,以及向第三方支付发送数据过程.
问题:
1.要发送给第三方支付的信息有哪些?
2.发送给第三方支付的路径是什么?
https://www.yeepay.com/app-merchant-proxy/node
以上这两个问题,我们需要查询易宝支付开发手册。
重要属性:p8_Url 是易宝支付反馈信息时的路径。
hmac=数据+密钥+算法.
密钥:L69cl522AV6q613Ii4W6u8K6XuW8vM1N6bFgyv769220IuYe9u37N4y7rI4Pl
3.创建一个CallBackServlet,用于接收第三方支付返回的信息.
http://www.estore.com/callBack?p1_MerId=10001126856&r0_Cmd=Buy&r1_Code=1&r2_TrxId=315223279200392I&r3_Amt=0.01&r4_Cur=RMB&r5_Pid=&r6_Order=asdflkasdiej&r7_Uid=&r8_MP=&r9_BType=1&ru_Trxtime=20141222105450&ro_BankOrderId=2636414683141222&rb_BankId=BOC-NET&rp_PayDate=20141222105443&rq_CardNo=&rq_SourceFee=0.0&rq_TargetFee=0.0&hmac=1852414bf1f5f63587a59e40cd8c35f2
关于第三方返回信息重点:
r9_BType 交易结果返回类型
为“1”: 浏览器重定向; 如果关闭浏览器,就可能接收不到信息。
为“2”: 服务器点对点通讯.—要求必须返回一个success,否则会一直发送。
———————————————————————————————–
在线支付完成后,修改订单的状态。
update order set paystate=1 where id=r6_order;
=================================================================================================
下载销售榜单
就是一个文件下载操作.
问题:怎样获取到下载文件中的数据?
要在已经支付的订单中查找销售的商品名称以及数量。
select
products.name,sum(buynum) totalSaleNum
from
products,orderitem,orders
where
orderitem.product_id=products.id
and
orders.id=orderitem.order_id
and
orders.paystate=1
group by
pname
order by
totalSaleNum
下载操作:
1.下载榜单连接
<a href="${pageContext.request.contextPath}/download">
2.创建一个DownloadServlet
1.得到数据
2.根据数据,通过response.getWriter()流写回到浏览器端.
3. 榜单文件是什么格式?
导出Excel 使用 POI类库
csv 格式文件 , 逗号分隔文件
1) 信息当中有,在两端加 双引号
2) 信息当中有" 在之前加双引号 转义
文件下载
设置Content-Type、Content-Disposition 头信息
文件流输出 (输出文件内容)
Excel 默认读取字符集gbk
==============================================================================================
权限
1.url级别权限控制(粗粒度权限控制)
原理:得到当前的访问的资源路径,得到当前用户角色,来判断当前用户是否有权限访问该资源.
1.得到资源路径.
String uri=request.getRequestURI();
String contextPath=request.getContextpath();
String path=uri.substring(contextPath.length());
2.当到当前用户角色,通过角色,判断当前用户是否有权限访问path资源。
1.得到当前用户
request.getSession().getAttribute("user");
2.做配置文件,在配置文件中声明每个角色具有的权限。
amdin.properties
user.properties.
实现:
1.添加商品—–>admin
2.下载榜单—–>admin
3.添加商品到购物车 购物车操作。—–user
3.关于订单操作 user admin
代码实现:
1.创建两个配置文件
user.properties 配置关于user角色具有的权限
admin.properties 配置关于admin角色具有的权限.
将配置文件放置在WEB-INF下.
2.创建一个PrivilegeFilter进行权限控制
1.在其init方法中将配置文件中内容读取出来装入到admins,users集合中。
2.得到请求资源路径,判断是否需要权限.
3.创建一个自定义异常,如果权限不足,抛出这个异常。
在web.xml文件中配置全局异常处理.
<error-page>
<exception-type>cn.itcast.estore.exception.PrivilegeException</exception-type>
<location>/error/privilege.jsp</location>
</error-page>
=========================================================================================================================
重构
1.一个请求一个servlet,现在要做一个模块一个servlet,也就是说,多个请求会访问同一个servlet.
UserServlet 注册 登录 注销 激活
CartServlet 关于购物车操作
ProductServlet 关于商品操作 注意:我们重构时没有将添加商品处理.
OrderServlet 关于订单操作
2.对servlet中的操作在进行一次重构
原因:
在UserServlet中
if ("regist".equals(method)) {
regist(request, response);
} else if ("login".equals(method)) {
login(request, response);
}
在ProductServlet中
if ("findProductById".equals(method)) {
findProductById(request, response);
} else {
// 默认就是查询所有.
findAllProduct(request, response);
}
上面的代码,操作是一致的,我们可以在一次进行抽取。生成一个BaseServlet.
它的代码
String methodName = request.getParameter("method");
Method method = this.getClass().getDeclaredMethod(methodName,
HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, request, response);
举例分析:
http://www.estore.com/user?method=login
1.知道要访问的是UserServlet。
2.因为UserServlet extends BaseServlet,这时就会访问
BaseServlet中的service方法。
3.在BaseServlet中
request.getParameter("method");—>login
4.得到指定的servlet中指定方法
Method method = this.getClass().getDeclaredMethod(methodName,HttpServletRequest.class, HttpServletResponse.class);
这句话就相当于得到了UserServlet中的login方法。
5.method.invoke(this,request,response);
这句话就相录于
UserServlet.login(request,response);
—————————–
以上重载操作后,
在访问servlet中的方法时,只需要 /url-pattern值?method=方法名。
2.细粒度(annotation+动态代理)
原理:在service中的方法上添加一个注解,注解的值代表的是访问这个功能所需要的权限名称。
我们的service的获取,是通过一个工厂获取的,而在工厂中返回的是service的动态代理对象。
在动态代理中去控制是否有权限访问当前操作。
代码实现:
1.创建一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface PrivilegeInfo {
String value();
}
2.抽取service
抽取出接口.例如:
public interface ProductService {
// 添加商品
@PrivilegeInfo("添加商品")
public void addProduct(Product p) throws Exception;
// 查询所有商品
public List<Product> findAll() throws Exception;
// 根据id查询商品
public Product findById(String id) throws Exception;
// 下载榜单数据
@PrivilegeInfo("下载榜单")
public List<Product> downloadSell(User user) throws PrivilegeException,
Exception
}
3.创建ServiceFactory
在serviceFactory中通过动态代理生成一个service代理对象,返回这个代理对象.
在servlet中使用的都是通过ServiceFactory获取的service对象,也就动态代理对象。
ProductService service = ProductServiceFactory.getInstance();
4.在动态代理的InvocationHandler的invoke方法中处理。
1.判断方法上是否有注解,也就知道,是否需要权限控制.
boolean flag = method
.isAnnotationPresent(PrivilegeInfo.class);
2.得到注解中的属性值,也就得到了访问该方法的权限名称
String pname = method.getAnnotation(
PrivilegeInfo.class).value();
3.得到当前用户(对于需要权限控制的方法,在其方法的第一个参数,都设置为User)
User user = (User) args[0];
4.得到user的role,在权限配置文件中查找这个角色所具有的权限名称
List<String> pnames = Arrays.asList(ResourceBundle
.getBundle("privilege").getString(role)
.split(","));
5.判断这个角色是否可以执行该方法.
if (!pnames.contains(pname)) {
throw new PrivilegeException();
}
问题:抛出异常不跳转到指定的页面?
原因:我们操作是在invoke方法中执行的。而invoke方法抛出的是Throwable,我们自己抛出的PrivilegeException,
会被包装。
在外面捕获不到。
再比如,我们对页面的权限控制可以使用url级别,而对具体的行为执行,可能通过细粒度权限控制。