这个网上购物商城系统的教程适用于 基础入门的同学,其中前端采用了一些layui的框架的知识,数据库用的也是原生的JDBC做连接。往后学习,大家会碰到很多的框架,例如JDBC的配置类不需要自己写,用mybatis就可以做连接和增删改查,例如servlet也会被spring boot的注解所代替,但是归根结底,它们都是要基于这类知识的。我会把我 拓展了的地方写在文章一开始的地方。
1、在前端往后端传值的地方用了layui table 数据传值,其中 总价实时改变和订单价格实时改变的地方 用了layui的模板。
2、servlet做了数据的组装,使得传回的值是可以被layui table所接收的。
3、商城功能:登陆功能+购物车功能+商品展示功能
JDBC.util, 用来做数据库连接。
private static final String URL = "jdbc:mysql://localhost:3306/zmall?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PWD = "123456";
private JDBCUtil(){}
public static Connection getConn() throws SQLException, ClassNotFoundException{ //建立数据库的连接
return DriverManager.getConnection(URL,USERNAME,PWD);
}
public static void close( Connection conn) { //假如连接还存在,则关闭该连接
if(conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
例如我们先写一个登陆功能,实现的顺序是,登录页输入信息---servlet接收信息---调用service的判断是否存在方法---service调用的dao的查询方法。
登录功能:
login.jsp
<body>
<div class="loginbox">
<div class="loginbox-header">
<p>账号登录</p>
</div>
<form method="post" action="loginServlet" onsubmit ="return loginCheck()">
账号: <input type="text" name="username" id="username" placeholder="账号"/><br/>
密码: <input type="password" name="password" id="password" placeholder="请输入密码"/><br/>
<input type="submit" value="登陆" class="button" />
</form>
</div>
</body>
loginServlet.java
@WebServlet("/loginServlet")
public class loginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public loginServlet() {
super();
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
UserImpl userService = new UserImpl();
boolean result = userService.loginUser(user);
/***
* jsp的常见使用对象:out、request、response、session、application
* out: PrintWriter out = response.getWriter();
* session: request.getsession()
* appliacation : request.getapplication()
*/
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
if(result) {
System.out.println("登录成功");
request.getRequestDispatcher("index.jsp").forward(request,response); //地址栏不会发生变化
}else {
System.out.println("登录失败");
request.getRequestDispatcher("login.jsp").forward(request,response);
}
}
UserImpl.java
public boolean loginUser(User user) {
LoginDaoImpl loginDao = new LoginDaoImpl();
boolean exist = loginDao.isExist(user.getUsername(),user.getPassword());
if (exist) {
return true;
} else {
System.out.println("该用户不存在");
return false;
}
}
LoginDaoImpl.java
public boolean isExist(String username,String password) {
// 在DAO层放数据逻辑。servlet放业务逻辑。
User loginStatus = queryUserByName(username);
String pwd = loginStatus.getPassword();
if(loginStatus.getUsername() == null) {
return false;
}
else if (!password.equals(pwd)) {
return false;
}
else{
return true;
}
//
// //复习一下三目运算
// return queryUserByName(username)==null?false:true;
}
public User queryUserByName(String username) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
User user = new User();
try {
connection = JDBCUtil.getConn();
String sql = "SELECT * FROM user where user_name=? "; //数据库语句
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
rs = preparedStatement.executeQuery();
if(rs.next()) {
int userNo = rs.getInt("user_id");
String userName = rs.getString("user_name");
int age = rs.getInt("user_age");
String password = rs.getString( "user_pwd");
String gender = rs.getString("user_gender");
user.setAge(age); //封装
user.setUserId(userNo);
user.setUsername(userName);
user.setPassword(password);
user.setGender(gender);
System.out.println("(登录用户):"+userName);
}
return user;
} catch (SQLException e) {
e.printStackTrace();
return null;
}catch (Exception e) {
e.printStackTrace();
return null;
}finally {
try {
if(rs!=null)rs.close(); //这边需要加上三个抛出异常的东西
if(preparedStatement!=null) preparedStatement.close();
if(connection!=null)connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
以上的用户登录功能模块就完成了,用户查询的时候, 所查询的是某一条的数据,此时的返回集rs是一个结果集,rs.next()可以判断是否为返回了为空的结果,如果是null 就返回false ,如果是有结果的,就返回结果。当我们在后面做插入操作的时候,这个时候记得rs的定义要为int,因为此时rs表示的是insert的插入影响了多少行。
商品展示功能:
index.jsp
<a href = "login.jsp">登录</a>
<a href = "cart.jsp">购物车</a>
<table class="layui-hide" id="goodsTable" lay-filter="goodsFilter"></table>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="add">添加</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="batchdel">批量删除</a>
<a class="layui-btn layui-btn-xs" onclick="tips()" id="zz">添加tips</a>
<a class="layui-btn layui-btn-xs" lay-event="refresh" id="refresh">刷新</a>
<a class="layui-btn layui-btn-xs" lay-event="fuck">编辑</a>
</script>
<script type="text/html" id="cellbarDemo">
<a class="layui-btn layui-btn-xs" lay-event="addGoods">添加</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="checkGoods">查看详情</a>
</script>
table的展示是通过js渲染的。传值也是在js完成的。
index.js
layui.use(['layer', 'form','table','jquery'], function(){ //用之前一定要先定义
var layer = layui.layer
,form = layui.form,table = layui.table,$=layui.jquery;
var tableIns = table.render({ //获取table的实例,后面用来做表格的刷新
elem: '#goodsTable'
,url:'/Zmall/goodsListServlet' //数据接口 //表格左上角的功能可以自定义 //表格右上方的三个按钮(导出、打印、筛选)不能编辑,只能选择是否显示
,limit:20 //每一行的最多可以显示的数量
,page:true //是否开启分页
,cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
,cols: [
[
{type:'checkbox',width:120}
,{field:'goods_id', width:200, title: 'ID', sort: true}
,{field:'goods_name', width:375, title: '商品名称'}
,{field:'type', width:335, title: '商品类型'} //注意这个edit:true,表示可以被编辑
,{field:'goods_price', width:180, title: '商品价格'}
,{width:150,title:'操作',toolbar:'#cellbarDemo'} //用toolbar引入操作按钮
]
]
});
//监听单元行的事件
table.on('tool(goodsFilter)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr的DOM对象
// alert(data.goods_id);
if(layEvent === 'addGoods'){
layer.open({
type:2,
title:'添加数量',
area:["600px","600px"],
content:"/Zmall/addGoods.jsp",
success:function(dom){
$iframeDom=$(dom[0]).find("iframe").eq(0).contents();
$iframeDom.find("#goodsId").val(data.goods_id);
$iframeDom.find("#goodsName").val(data.goods_name);
$iframeDom.find("#goodsPrice").val(data.goods_price);
$iframeDom.find("#goodsType").val(data.type);
}
});
}
});
});
goodsListServlet.用于查询 到 数据库的商品信息并转为layui-table的数据格式,什么是layui-table支持的数据格式呢。
{code: ,msg: , count: ,data:[] ,}.因此在后台传来了map的数据以后,做一个数据组装,并且使用Gson.toJson(map)将map转为json数据就行。
request.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=UTF-8");
GoodsImpl goodsService = new GoodsImpl();
List<Map<String, Object>> goodsList = goodsService.getGoodsList();
Map<String, Object> map = new HashMap<String, Object>();
/**
* 进行数据组装,将所有的list的数据变layui-table的所需要的 格式
*/
if(goodsList != null) {
map.put("code", 0);
map.put("msg", "");
map.put("count",goodsList.size());
map.put("data", goodsList);
}
Gson gson = new Gson(); //将map数据类型转化为Json数据类型
// System.out.println(gson.toJson(map));
PrintWriter out = response.getWriter();
out.write(gson.toJson(map));
out.flush();
out.close();
这边要注意out这个内置对象可以用来向客户端发送数据,发送完之后要记得清空缓冲并关闭流。
在这里,service层没有做业务逻辑的判断了,就单单是调用dao层进行查询。然后要注意到dao层的写法会和上面有些不一样,对,上面也提到了,主要是结果集的类型是 int。
try {
connection = JDBCUtil.getConn();
String sql = "SELECT * FROM goods ";
preparedStatement = connection.prepareStatement(sql);
rs = preparedStatement.executeQuery();
ResultSetMetaData resultSetMetaData = (ResultSetMetaData) preparedStatement.getMetaData();
int column = resultSetMetaData.getColumnCount();
while(rs.next()) {
data = new HashMap<String, Object>();
for (int i =1;i<=column;i++) {
data.put(resultSetMetaData.getColumnLabel(i), rs.getObject(resultSetMetaData.getColumnLabel(i)));
}
goodsList.add(data);
}
return goodsList;
} catch (SQLException e) {
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(rs!=null)rs.close(); //这边需要加上三个抛出异常的东西
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(preparedStatement !=null) preparedStatement.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(connection !=null) connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这边后面为什么要写三个try catch 呢,因为Result 和 PrepareStatement 是关闭的过程也可能产生异常,那为什么要关闭他们呢,因为虽然说我这个项目没有用到数据库连接池,GC会保证在项目结束的时候清空掉这些资源,但是有的项目(而且是大部分项目)是会用到连接池的,这时候断开连接不是 清空资源,而是将Result 和 PrepareStatement归还给 数据库,那东西积少成多,就会占用数据库的资源,最后导致数据库无法加载。
写完了这两个以后,我们前端的页面是这样的。
右边定义的添加按钮,添加了一个点击事件,使用iframe传值的方法将这个父页面的值传到子页面上,然后再通过表格输入数量,点击提交信息,将订单结果存储到数据库当中。
这是Create.jsp.
表单的编写方法大家应该都会,我们重点来说,首页的商品信息,是怎么传送到这个页面里头去的,
使用layevet监听添加的按钮时间,因为这个添加是写在表格一行的末尾的,所有我们在index.js那部分是这样写的:
index.js(增加订单部分)
table.on('tool(goodsFilter)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr的DOM对象
// alert(data.goods_id);
if(layEvent === 'addGoods'){
layer.open({
type:2,
title:'添加数量',
area:["600px","600px"],
content:"/Zmall/addGoods.jsp",
success:function(dom){
$iframeDom=$(dom[0]).find("iframe").eq(0).contents();
$iframeDom.find("#goodsId").val(data.goods_id);
$iframeDom.find("#goodsName").val(data.goods_name);
$iframeDom.find("#goodsPrice").val(data.goods_price);
$iframeDom.find("#goodsType").val(data.type);
}
});
}
});
addGoods是按钮监听事件,点击这个按钮之后,layer.open弹出jsp窗口,并同时定义iframe,
$iframeDom=$(dom[0]).find("iframe").eq(0).contents();
这段代码的意思是 第0层元素(有可能出现页面嵌套)的页面作为iframe,然后如果有多个iframe(iframe和div的名字一样,可能出现重名),我们取第一个索引为eq(0)的iframe,也就是第一个iframe,再获取它的内容,此时contents就是这个页面的内容,也就是我们的addGoods.jsp页面。
data获取的是当前这一行的数据值,他是一个对象(应该是),再通过data.id这样的写法给addGoods.jsp页面里面的元素赋值。
然后增加订单又是一个船新的类似的功能,唯一不一样的是,我们在dao层的insert的时候,用的是excuteUpdate,代码如下
AddGoodsDaoImpl.java
String sql = "insert into cartItem value (?,?,?,?,?) ";
int id = cartItem.getId();
String goodsId = cartItem.getGoodsId();
String goodsName = cartItem.getGoodsName();
String goodsPrice = cartItem.getGoodsPrice();
String amount = cartItem.getAmount();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,0);
preparedStatement.setString(2, goodsId);
preparedStatement.setString(3, goodsName);
preparedStatement.setString(4, goodsPrice);
preparedStatement.setString(5, amount);
int count = preparedStatement.executeUpdate();
if (count>0) {
System.out.println("操作成功");
return true;
}else {
System.out.println("操作失败");
return false;
}
这个功能实现以后,就代表订单数据表有数据了。现在我们再写另一个页面(原理和展示商品页面一样),将购物车的页面展示出来就行。
细心的人,已经发现到了,底下做了一个实时统计总价的功能,这个我们是怎么做到的呢。
var tableIns = table.render({ //获取table的实例,后面用来做表格的刷新
elem: '#cartTable'
,url:'/Zmall/cartItemListServlet' //数据接口 //表格左上角的功能可以自定义 //表格右上方的三个按钮(导出、打印、筛选)不能编辑,只能选择是否显示
,limit:20 //每一行的最多可以显示的数量
,page:true //是否开启分页
,cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
,cols: [
[
{type:'checkbox',width:120}
,{field:'id', width:200, title: '订单编号', sort: true}
,{field:'goods_name', width:375, title: '商品名称', id:"goods_name"}
,{field:'goods_price', width:200, title: '单价'} //注意这个edit:true,表示可以被编辑
,{field:'amount', width:180, title: '数量', edit:"true"}
,{field:'orderPrice',width:135 , title:'订单价格' ,templet: function(d){
return parseInt(d.goods_price)*parseInt(d.amount)}}
,{width:150 , title:'操作',toolbar:'#cellbarDemo'} //用toolbar引入操作按钮
]
]
,done: function(res, curr, count){
//如果是异步请求数据方式,res即为你接口返回的信息。
//如果是直接赋值的方式,res即为:{data: [], count: 99} data为当前页数据、count为数据总长度
var total_price = 0;
for( i=0;i<count;i++){
total_price = parseInt(res.data[i].goods_price)*parseInt(res.data[i].amount)+total_price;
}
layer.msg(total_price);
$("#total_price").val(total_price);
//得到当前页码
// console.log(curr);
//得到数据总量
// layer.msg(count);
}
});
在我们渲染layui-table的时候,有一个done的col 属性,它可以帮助我们拿到传过来的data(就是那个组装好的json)的东西,res.data[i].amount 就是里头 某一个单元格的值,我们用一个for 循环,就可以遍历所有值,取到任何一个单元格里头的东西,那总价当然是通过单价乘以数量再加起来算到的值咯。这里有一个先前提到的难点,就是文章一开头提到的,怎么实时改变layui某一列单元格的值,以我们这个系统为例,我这里的订单价格是实时监听得到的,不是从数据库传来的。这里就用到了layui-table的自定义模板。
,cols: [
[
{type:'checkbox',width:120}
,{field:'id', width:200, title: '订单编号', sort: true}
,{field:'goods_name', width:375, title: '商品名称', id:"goods_name"}
,{field:'goods_price', width:200, title: '单价'} //注意这个edit:true,表示可以被编辑
,{field:'amount', width:180, title: '数量', edit:"true"}
,{field:'orderPrice',width:135 , title:'订单价格' ,templet: function(d){
return parseInt(d.goods_price)*parseInt(d.amount)}}
,{width:150 , title:'操作',toolbar:'#cellbarDemo'} //用toolbar引入操作按钮
]
]
看以看到最后一个field里头的值是通过函数计算得到的。这里一定要注意col里头的两个[ [ 要分开写,你连在一起写会被认为是“[[”符号而无法识别。
以上都做完了,那如果想购买的数量变了,应该怎么做呢(包括想删除订单是一样的道理)
我把订单表格的数量那一列设为可编辑的,具体的做法就是在field后面添加属性为edit;true,然后在js中监听这一行的数据是否有做改变,如果做了改变,就出发updateServlet,往数据库表格更新一条数据,具体做法如下:
cart.js(更新购物车部分)
table.on('edit(cartFilter)', function(obj){
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr的DOM对象
var inputAmount = obj.value;
$.ajax({
type: "GET",
url: "/Zmall/updateCartItemServlet",
data: {
"inputAmount": obj.value,
"id":data.id
},
dataType: "text",
success: function(data){
if (data.val() == "200"){
layer.msg("修改成功");
}else{
layer.msg("修改失败");
}
}
});
setTimeout('window.location.reload()',1000);
});
UpdateCartItemDaoImpl.java(我这个起名真的是有毒)
String sql = "UPDATE cartitem SET amount=? WHERE id=? ";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,inputAmount);
preparedStatement.setInt(2, id);
int count = preparedStatement.executeUpdate();
if (count>0) {
System.out.println("操作成功");
return true;
}else {
System.out.println("操作失败");
return false;
}
然后这里的也是excuteUpdate,对,除了查询的时候用的是excuteQuery,其他的插入,插叙,删除都是excuteUpdate。
修改完成之后,传回一个状态值,这个状态200表示修改成功,然后刷新页面,此时总价和订单总价都会被改变。
以上就是这一个网上商城购物系统。总结一下:
基本的功能都有实现,不足的地方是:订单删除功能还没做,页面应该带有登录状态,这样在提交最终订单的时候,才会带有用户id和用户名,再往前推一步,一个人只能显示自己的订单才可以。另外是同事给我提的建议,
1)lib目录存放jar包,但是你的mysql驱动包并未在这个下面。
2)WebContent目录下的WEB-INF没有classes文件夹,应当创建文件夹,并设置编译后的字节码文件全在classes文件夹下。
3)WebContent下的scripts和WEB-INF下的static/的相关目录里面的类型重复
4)页面一般除了欢迎界面在WebContent(一般都会取名webapp)下,其他页面都会直接放在WEB-INF中的一个目录下,比如views目录
以上。