第十三篇学习总结
一、主页展示
主页展示分两个步骤,将所有的商品信息从数据库中取出来,然后分页。
1、获取所有商品信息
获取所有商品信息的思路是dao使用SQL语句从数据库中查询到所有商品信息的集合,service层再交给servlet。不过在此之前首先要定义一个JavaBean用于存储和运输数据,这个JavaBean中应该包含一个商品应该包含的所有信息:id,name,price,num,img
public List<Goods> getGoods(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
//由于要进行分页,并且提供名称匹配搜索,所以限制了搜索条件,不过由于
//name的默认值是"",前后各加一个%表示匹配所有
ps = cn.prepareStatement("select * from goods where name like ? limit ?,?");
goods.getName();
ps.setString(1,"%"+goods.getName()+"%");
ps.setInt(2,goods.getStart());
ps.setInt(3,goods.getPageSize());
rs = ps.executeQuery();
//调用DBUtils的selectMore方法进行映射
List<Goods> list = DBUtils.selectMore(Goods.class,rs);
//如果查询到了就返回这个list,否则返回null
return list;
}catch (Exception e){
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return null;
}
//DBUtils的selectMore方法需要两个参数,第一个是类的字节码,第二个是查询后的ResultSet
public static <E> List<E> selectMore(Class<E> clazz, ResultSet rs){
List<E> list = new ArrayList<>();
try {
ResultSetMetaData metaData = rs.getMetaData(); //获取结果集的表头字段信息
int cnum = metaData.getColumnCount(); //获取结果集中字段的数量
while(rs.next()){//遍历每一行数据
E obj = clazz.newInstance(); //反射创建一个对象
for (int i = 1; i <=cnum ; i++) { //遍历所有的字段
String cname = metaData.getColumnLabel(i); //拿到字段标签
String ctn = metaData.getColumnTypeName(i);//拿到类型名
Object value = rs.getObject(cname); //获取当前行对应的当前字段的值
if(value==null){ //如果值为null
if(ctn.equals("INTEGER")){ //如果类型是Integer,把值设置为0
value = 0;
}
}
Field field = clazz.getDeclaredField(cname);//反射获取类中的成员变量
field.setAccessible(true); //取消语法检查
field.set(obj,value);//把我们取出来的这个字段的值赋值给javabean对象的成员变量
}
list.add(obj);//将这个封装着数据的对象放到list集合中
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
2、分页
实现分页首先要定义一个BaseBean类,里面有三个成员变量:currentPage当前页,初始为1,pageSize每页展示的数量,可以自定义,start每页开始的下标,可以通过计算得出。然后最重要的就是字符串拼接,实现分页首先要知道总共有多少页。
package com.qianfeng.paging;
import java.util.Map;
//这是页面底部的分页栏
public class Paging {
public static String getPage(BaseBean baseBean, int count, String url, Map<String,String[]> map) {
int pageSize = baseBean.getPageSize(); //获取每页的数量
int pageCount = count%pageSize==0?count/pageSize:count/pageSize+1;//计算总页数
if(pageCount==0){ //如果总页数是0,就没有分页
return "";
}
//获取当前页码
int currentPage = baseBean.getCurrentPage();
//如果当前页大于总页数, 将当前页设置为最后一页
if(currentPage>pageCount){
currentPage=pageCount;
}
//如果当前页小于等于0,将当前页设置为1
if(currentPage<=0){
currentPage=1;
}
//计算分页的起始位置
int start = (currentPage-1)*pageSize; // 计算开始位置
//将计算好的起始位置存到 Javabean,便于之后查询数据
baseBean.setStart(start);
//用户如果带有参数, name我们拼接的url上页也应该带着参数, 这样点击的时候, 请求后台时,才能把参数带过去, 获取的结果,也是条件筛选的结果
StringBuilder sb = new StringBuilder(url);
sb.append("?"); //将原来的url和?拼接到一起, 形成一个头
//遍历request中的参数, 将key和value拼接成请求 key=value&
for (Map.Entry<String, String[]> entry : map.entrySet()) {
String key = entry.getKey();
//因为下面已经拼接了当前页面,那么,currentPage就不需要在这里拼接了
if(!"currentPage".equals(key)){
String[] values = entry.getValue();
//请求时,可能有一个参数名对应多个值的情况
for (String value : values) {
sb.append(key.concat("=").concat(value).concat("&"));
}
}
}
url = sb.toString(); //将拼接好的StringBuilder转成字符串,已备使用
//拼接按钮
sb = new StringBuilder();
//如果当前页是第一页, 首页和上一页都不能点击
if(currentPage==1){
sb.append("<a >首页</a>");
sb.append("<a >上一页</a>");
}else{
sb.append("<a href='"+url+"currentPage=1'>首页</a>");
sb.append("<a href='"+url+"currentPage="+(currentPage-1)+"'>上一页</a>");
}
//遍历拼接页码
if(pageCount<=7){
for (int i = 1; i <= pageCount; i++) {
if(i==currentPage){
sb.append("<a >"+i+"</a>");
}else{
sb.append("<a href='"+url+"currentPage="+i+"'>"+i+"</a>");
}
}
}else{
//在多页面情况下会有省略好,我们使用保持按钮的总数是9个, 当前页的前后两个显示显示出来,当前页的前后第三个用省略号代替
//但是,但用户点击到第一页时, 后面之后两个按钮和一个省略号,总数不足9个, 当前页前面不够了, 之后再后面多显示几个按钮
int n = 2;
if(currentPage==1||currentPage==pageCount){
n = 5;
}else if(currentPage==2||currentPage==pageCount-1){
n = 4;
}else if(currentPage==3||currentPage==pageCount-2){
n = 3;
}
for (int i = 1; i <= pageCount; i++) {
if(i==currentPage){
sb.append("<a >"+i+"</a>");
}else if(i>=currentPage-n&&i<=currentPage+n){
sb.append("<a href='"+url+"currentPage="+i+"'>"+i+"</a>");
}else if(i==currentPage-(n+1)||i==currentPage+(n+1)){
sb.append("<a >...</a>");
}
}
}
//如果当前页时最后一页,那么下一页和尾页都不能点击
if(currentPage==pageCount){
sb.append("<a >下一页</a>");
sb.append("<a >尾页</a>");
}else{
sb.append("<a href='"+url+"?currentPage="+(currentPage+1)+"'>下一页</a>");
sb.append("<a href='"+url+"?currentPage="+pageCount+"'>尾页</a>");
}
//拼接跳转页面按钮和js代码
sb.append("<input id='pageNum' type='number'/><button onclick='toPage()'>跳转</button>");
sb.append("<script>function toPage(){" +
"location.href='"+url+"?currentPage='+document.getElementById('pageNum').value"+
"}</script>");
sb.append("<span>共 "+pageCount+" 页</span>");
return sb.toString();
}
}
import com.qianfeng.shop.bean.Goods;
import com.qianfeng.shop.service.ShopSerivice;
import com.qianfeng.paging.Paging;
import com.qianfeng.utils.WebUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet("/shop/toMainPage")
public class ToMainPage extends HttpServlet {
private ShopSerivice shopSerivice = new ShopSerivice();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接受参数和当前页码
//继承BaseBean, 当前Javabean对象就可以使用分页参数了
Goods goods = WebUtils.populate(Goods.class,req);
//要想展示分页,我们要知道总共有多少页,想要知道总共有多少页,就必须知道总共有多少数据,然后计算总页数
//获取总数据量
int count = shopSerivice.getGoodsCount(goods);
//计算分页 需要当前页,每一页多少个, 数据的总量, 请求的url, 参数
String paging = Paging.getPage(goods,count,"/shop/toMainPage",req.getParameterMap());
//将计算出的分页的html代码放到请求域中
req.setAttribute("paging",paging);
//获取当前页的实际数据
List<Goods> list = shopSerivice.getGoods(goods);
//将数据放到请求域中
req.setAttribute("list",list);
//将请求参数放到请求域中,便于回显
req.setAttribute("goods",goods);
req.getRequestDispatcher("/WEB-INF/pages/main.jsp").forward(req,resp);
}
}
获取到当前页面的商品信息后就把这些信息放在request域中,jsp页面使用JSTL和EL表达式在页面展示。
二、后台
后台就是给管理员添加、修改、删除、查询数据的。
1、登录过滤器
如果用户在未登录的情况下请求非登录页面,应该将用户的请求重定向到登录页面,同时有一些页面即使不登录也应该能请求,比如一些静态资源。能实现这种功能的只有过滤器。
import com.qianfeng.shop.bean.UserBack;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//因为要使用session,所以将ServletRequest强转成HttpServletRequest
HttpServletRequest request = (HttpServletRequest) servletRequest;
//有一些静态资源和serlvet请求是不需要拦截的, 放行
//获取请求路径 /项目名/请求地址
String str = request.getRequestURI();
//将一些页面排除
if(str.startsWith("/shopback/resource/")||str.startsWith("/shopback/user/login")){
filterChain.doFilter(request,servletResponse);
return;
}
//获取登陆标志
Object userBack = request.getSession().getAttribute("user");
//判断使用已经登陆
if(userBack==null){
request.getRequestDispatcher("/resource/pages/login.jsp").forward(request,servletResponse);
}else{
filterChain.doFilter(request,servletResponse);
}
}
@Override
public void destroy() {
}
}
2、登录
我们在数据库中存储的用户密码加密后的数据,并不会直接存储用户的明文密码,所以用户登录的时候要进行密码加密:
/**
* 根据用户名密码查询用户信息
* @param userBack
* @return
*/
public UserBack getUserByUserNameAndPassword(UserBack userBack){
//使用自定义工具对密码进行md5加密
String password = DigestString.digest(userBack.getPassword());
userBack.setPassword(password);
UserBack back = userDao.getUserByUserNameAndPassword(userBack);
return back;
}
//使用md5算法进行加密
public class DigestString {
public static String digest(String str){
try {
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] bs = digest.digest(str.getBytes());
//然后再使用Base64算法将字节数组转换为字符串
Base64.Encoder encoder = Base64.getEncoder();
str = encoder.encodeToString(bs);
return str;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
}
}
3、商品分类的增删改查
商家通过后台管理系统不仅可以对商品信息进行增删改查,还可以对商品类型进行增删改查,这些操作除了查询,全都是使用Ajax异步提交的,操作成功后再重新请求当前页面。
function editById(id){
$("#content").hide(1000); //列表消失
$("#edit").show(1000); //修改框出现
$.ajax({
url:"/shopback/shop/getTypeById",
type:"post",
data:{id:id},
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message);
}else if(data.code==1){
var typeBean = data.data;
//服务器获取分类信息成功, 之后将这些信息放到页面中
$("#editName").val(typeBean.name);
$("#editUserName").text(typeBean.userName);
$("#editTime").text(typeBean.time);
$("#editId").val(typeBean.id);
}
}
})
}
4、商品管理
4.1 商品管理分页
和用户看到的主页面分页一样,管理员在后台对商品进行管理也需要分页。在商品管理页面,不仅要展示商品的基本信息:图片、商品名、数量、价格等,还需要提供商品管理的操作:增加、删除、修改、查询。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>商品管理页面</title>
<style>
img{
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<a href="/shopback/shop/toTypesPage">跳转分类管理页面</a>
<div id="content">
<div>
<!-- 名称搜索表单 -->
<form action="/shopback/shop/toGoodsPage">
<input name="name" value="${goods.name}"> <button>搜索</button>
</form>
</div>
<div>
<!-- 添加新商品按钮,点击之后显示添加新商品框 -->
<button onclick="openAdd()">添加新商品</button>
</div>
<div>
<!-- 创建一个表格,用于展示商品信息 -->
<table>
<tr>
<th>编号</th>
<th>名称</th>
<th>类型</th>
<td>价格</td>
<th>数量</th>
<th>状态</th>
<th>操作人</th>
<th>操作时间</th>
<th>操作</th>
</tr>
<!-- 这个list是放在request域中的商品信息 -->
<c:forEach items="${list}" var="item">
<tr>
<th>${item.id}</th>
<th>${item.name}</th>
<th>${item.typeName}</th>
<td>${item.price}</td>
<th>${item.num}</th>
<th>
<!-- 由于我们在数据库中存储的商品状态是用数值1和0来表示的,这里需要进行判断 -->
<c:if test="${item.status==0}">
<span style="color: red">下架状态</span>
</c:if>
<c:if test="${item.status==1}">
<span style="color: green">上架状态</span>
</c:if>
</th>
<th>${item.userName}</th>
<th>${item.createTime}</th>
<th>
<!-- 修改删除与上架、下架按钮,点击事件都有对应的函数,参数是商品的id -->
<a href="javascrit:void(0)" onclick="openEdit(${item.id})">修改</a>
<a href="javascrit:void(0)" onclick="deleteById(${item.id})">删除</a>
<c:if test="${item.status==0}">
<a href="javascrit:void(0)" onclick="editStatus(${item.id},1)">上架</a>
</c:if>
<c:if test="${item.status==1}">
<a href="javascrit:void(0)" onclick="editStatus(${item.id},0)">下架</a>
</c:if>
</th>
</tr>
</c:forEach>
</table>
</div>
<div>
${paging}
</div>
</div>
<!-- 添加商品的div -->
<div id="add" style="display: none">
封面图 : <img id="img" src="https://cn.bing.com/th?id=OIP.LR1EWcFdBaQdJoU7ZUDIDwHaGV&pid=Api&rs=1" onclick="openFile()"> <br>
<input type="file" style="display: none" id="addFile" onchange="submitImg()">
<input id="addImg" type="hidden">
商品名称 : <input id="addName"> <br>
商品类型 : <select id="addTypeId">
<c:forEach items="${types}" var="item">
<option value="${item.id}">${item.name}</option>
</c:forEach>
</select> <br>
商品价格 : <input type="number" id="addPrice"> <br>
商品数量 : <input type="number" id="addNum"> <br>
<!-- 用于富文本框的div -->
商品详情 : <div id="addContent"></div>
<button onclick="addSubmit()">提交</button> <button onclick="cancel()">取消</button>
</div>
</body>
<!-- 导入wangEditor与jQuery的js -->
<script src="/shopback/resource/js/wangEditor.min.js"></script>
<script src="/shopback/resource/js/jquery.js"></script>
<script>
//预加载, 内部的代码会在页面加载完毕之后才执行
var e;
$(function(){
//创建模板
var E = window.wangEditor;
e = new E("#addContent"); //创建富文本对象
//自定义图片上传服务器,因为wangEditor的默认上传的是网络图片
e.customConfig.uploadImgServer = '/shopback/file/upload';
e.create();
})
function addSubmit() {
var img = $("#addImg").val();
var name = $("#addName").val();
var typeId = $("#addTypeId").val();
var price = $("#addPrice").val();
var num = $("#addNum").val();
var content = e.txt.html();
var obj = {img:img,name:name,typeId:typeId,price:price,num:num,content:content};
$.ajax({
url:"/shopback/shop/addGoods",
type:"post",
data:obj,
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message)
}else if (data.code==1){
location.reload();
}
}
})
}
function submitImg(){
var file = $("#addFile")[0].files[0];
var formData = new FormData();
formData.append("file",file);
$.ajax({
url:"/shopback/file/upload",
type:"post",
data:formData,
contentType:false,
processData:false,
dataType:"json",
success: function (data) {
if(data.errno!=0){
alert("对不起,图片上传失败");
}else if(data.errno==0){
$("#addImg").val(data.data[0]);
$("#img").attr("src",data.data[0]);
}
}
})
}
function openFile(){
$("#addFile").click();
}
//打开添加框
function openAdd(){
$("#content").hide();
$("#add").show();
}
//添加和修改框全部隐藏, 主列表出现
function cancel(){
$("#content").show();
$("#add").hide();
$("#edit").hide();
}
//根据id删除商品信息
function deleteById(id){
var flg = confirm("您确定要删除吗?");
if(!flg){
return;
}
$.ajax({
url:"/shopback/shop/deleteGoodsById",
type:"post",
data:{id:id},
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message)
}else if (data.code==1){
location.reload();
}
}
})
}
function editStatus(id,status) {
var flg ;
if(status==0){
flg = confirm("是否确定要下架?");
}else{
flg = confirm("是否确定要上架?");
}
if(!flg){
return;
}
$.ajax({
url:"/shopback/shop/editGoodsStatus",
type:"post",
data:{id:id,status:status},
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message)
}else if (data.code==1){
location.reload();
}
}
})
}
</script>
</html>
编写前台页面之后,还要编写对应的servlet、service、dao,因为每一个操作都需要同数据库进行交互
网友评论