一、主页展示
主页展示分两个步骤,将所有的商品信息从数据库中取出来,然后分页。
1、获取所有商品信息
获取所有商品信息的思路是dao使用SQL语句从数据库中查询到所有商品信息的集合,service层再交给servlet。不过在此之前首先要定义一个JavaBean用于存储和运输数据,这个JavaBean中应该包含一个商品应该包含的所有信息:id,name,price,num,img
/**
* 根据条件查询商品
* @param goods
* @return
*/
public List<Goods> getGoodses(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
ps = cn.prepareStatement("select g.*,t.name as typeName,ub.userName from goods g " +
"left join types t on t.id = g.typeId " +
"left join user_back ub on ub.id = g.userId "+
"where g.name like ? limit ?,?");
ps.setString(1, "%"+goods.getName()+"%");
ps.setInt(2, goods.getStart());
ps.setInt(3, goods.getPageSize());
rs = ps.executeQuery();
List<Goods> list = DBUtils.selectMore(Goods.class,rs);
return list;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return null;
}
/**
* 根据条件获取商品数量
* @param goods
* @return
*/
public int getGoodsesCount(Goods goods){
Connection cn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
cn = JDBCUtils.getConnection();
ps = cn.prepareStatement("select count(1) from goods where name like ? ");
ps.setString(1, "%"+goods.getName()+"%");
rs = ps.executeQuery();
if (rs.next()){
return rs.getInt(1);
}
return 0;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,ps,cn);
}
return 0;
}
//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.utils;
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 class='btn' 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();
}
}
package com.shop.web;
import com.shop.bean.Goods;
import com.shop.bean.Type;
import com.shop.service.ShopService;
import com.shop.service.TypesService;
import com.utils.Paging;
import com.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 ShopService shopService = new ShopService();
private TypesService typesService = new TypesService();
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接受参数和当前页码
//继承BaseBean, 当前Javabean对象就可以使用分页参数了
//如果前台传递来的参数有多个值,那么后台需要使用数组接收
Goods goods = WebUtils.populate(Goods.class,req);
//要想展示分页,我们要知道总共有多少页,想要知道总共有多少页,就必须知道总共有多少数据,然后计算总页数
//获取总数据量
int count = shopService.getGoodsCount(goods);
//计算分页 需要当前页,每一页多少个, 数据的总量, 请求的url, 参数
String paging = Paging.getPage(goods,count,"/shop/toMainPage",req.getParameterMap());
//将计算出的分页的html代码放到请求域中
req.setAttribute("paging",paging);
//获取当前页的实际数据
List<Goods> list = shopService.getGoods(goods);
//将数据放到请求域中
req.setAttribute("list",list);
//将请求参数放到请求域中,便于回显
req.setAttribute("goods",goods);
List<Type> types = typesService.getTypesAll();
req.setAttribute("types",types);
req.getRequestDispatcher("/WEB-INF/pages/main.jsp").forward(req,resp);
}
}
- 获取到当前页面的商品信息后就把这些信息放在request域中,jsp页面使用JSTL和EL表达式在页面展示。
二、后台
后台就是给管理员添加、修改、删除、查询数据的。
1、登录过滤器
如果用户在未登录的情况下请求非登录页面,应该将用户的请求重定向到登录页面,同时有一些页面即使不登录也应该能请求,比如一些静态资源。能实现这种功能的只有过滤器。
import com.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 {
public void init(FilterConfig filterConfig) throws ServletException { }
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);
}
}
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>
body { font-family: 楷体; }
div { text-align: center; }
table { margin: auto; }
td { text-align: center;}
img {
width: 100px;
height: 100px;
}
#icon , #icon1{
width: 100px;
height: 100px;
border-radius: 50px;
box-shadow: 2px 2px 2px;
margin: 20px auto;
}
</style>
<link rel="stylesheet" href="/resource/css/MyCss.css">
</head>
<body style="background: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578736051725&di=76768c9735298e1e0422b1930727c243&imgtype=0&src=http%3A%2F%2Fimg.pptjia.com%2Fimage%2F20190112%2F43847e4d1e0b3af9623f502c45148e15.jpg)">
<div class="form">
<div class="heading"><h2>商品管理页面</h2><br></div>
<div class="form-group">
<a href="/shop/toTypesPage">跳转分类管理页面</a>
</div>
<%--商品展示--%>
<div class="form-group" id="content">
<div>
<form action="/shop/toGoodsPage">
<input class="inp" name="name" value="${goods.name}">  
<button class="btn">搜索</button>
</form>
</div><br>
<div>
<button class="btn" onclick="openAdd()">添加商品</button>
</div><br>
<div>
<table class="table">
<tr>
<th>编号</th>
<th>名称</th>
<th>类型</th>
<th>价格</th>
<th>数量</th>
<th>状态</th>
<th>操作人</th>
<th>操作时间</th>
<th>操作</th>
</tr>
<c:forEach items="${list}" var="item">
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.typeName}</td>
<td>${item.price}¥</td>
<td>${item.num}</td>
<th>
<c:if test="${item.status==0}">
<span style="color: red">下架状态</span>
</c:if>
<c:if test="${item.status==1}">
<span style="color: lawngreen">上架状态</span>
</c:if>
</th>
<td>${item.userName}</td>
<td>${item.createTime}</td>
<td>
<a href="javascript:void(0)" onclick="editById(${item.id})">修改</a><br>
<a href="javascript:void(0)" onclick="deleteById(${item.id})">删除</a>
<c:if test="${item.status==0}"><br>
<a href="javascript:void(0)" onclick="editStatus(${item.id},1)">上架</a>
</c:if>
<c:if test="${item.status==1}"><br>
<a href="javascript:void(0)" onclick="editStatus(${item.id},0)">下架</a>
</c:if>
</td>
</tr>
</c:forEach>
</table>
</div><br><br>
<div class="paging">
${paging}
</div>
</div><br>
<%--添加商品--%>
<div class="form-group" id="add" style="display: none">
<div class="heading"><h2>添加商品</h2></div>
<table class="table">
<tr>
<td>封面图:</td>
<td><img id="icon" src="http://office-1256119282.file.myqcloud.com/20190730/office-cn/official-web/static/img/ic_logo@2x.png" onclick="openFile()">
<input class="inp" type="file" id="addFile" style="display: none" onchange="addSubmitIcon()"><br>
<input class="inp" type="hidden" id="addIcon"><br>
</td>
</tr>
<tr>
<td>商品名称:</td>
<td><input class="inp" id="addName"></td>
</tr>
<tr>
<td>商品类型:</td>
<td><select id="addTypeId">
<c:forEach items="${types}" var="item">
<option value="${item.id}">${item.name}</option>
</c:forEach>
</select></td>
</tr>
<tr>
<td>商品价格:</td>
<td><input class="inp" type="number" id="addPrice">¥</td>
</tr>
<tr>
<td>商品数量:</td>
<td><input class="inp" type="number" id="addNum">件</td>
</tr>
<tr>
<td colspan="2">商品详情:</td>
</tr>
<tr>
<td colspan="2"><div id="addContent"></div></td>
</tr>
<tr>
<td colspan="2">
<button class="btn" onclick="addSubmit()">添加</button>
    
<button class="btn" onclick="cancel()">取消</button>
</td>
</tr>
</table>
</div><br>
<%--修改--%>
<div class="form-group" id="edit" style="display: none">
<input type="hidden" id="editId">
<div class="heading"><h2>修改商品</h2><br></div>
<table class="table">
<tr>
<td>封面图:</td>
<td><img id="icon1" src="http://office-1256119282.file.myqcloud.com/20190730/office-cn/official-web/static/img/ic_logo@2x.png" onclick="openFile()">
<input type="file" id="editFile" style="display: none" onchange="editSubmitIcon()"><br>
<input type="hidden" id="editIcon"><br>
</td>
</tr>
<tr>
<td>商品名称:</td>
<td><input id="editName"></td>
</tr>
<tr>
<td>商品类型:</td>
<td><select id="editTypeId">
<c:forEach items="${types}" var="item">
<option value="${item.id}">${item.name}</option>
</c:forEach>
</select></td>
</tr>
<tr>
<td>商品价格:</td>
<td><input type="number" id="editPrice">¥</td>
</tr>
<tr>
<td>商品数量:</td>
<td><input type="number" id="editNum">件</td>
</tr>
<tr>
<td colspan="2">商品详情:</td>
</tr>
<tr>
<td colspan="2"><div id="editContent"></div></td>
</tr>
<tr>
<td colspan="2">
<button class="btn" onclick="editSubmit()">修改</button>
    
<button class="btn" onclick="cancel()">取消</button>
</td>
</tr>
</table>
</div>
</div>
</body>
<script src="/resource/js/wangEditor.min.js"></script>
<script src="/resource/js/jquery.js"></script>
<script>
//打开图片
function openFile(){
$("#addFile").click();
}
//打开添加框
function openAdd(){
$("#content").hide(1000);
$("#add").show(1000);
}
//打开修改框
function openEdit(){
$("#content").hide(1000);
$("#edit").show(1000);
}
//添加和修改框隐藏, 主列表出现
function cancel(){
$("#content").show(1000);
$("#add").hide(1000);
$("#edit").hide(1000);
}
//预加载,$(function(){})在页面加载完后执行
//富文本框
var e;
$(function(){
//创建模板
var E = window.wangEditor;
e1 = new E("#addContent"); //创建富文本对象
e1.customConfig.zIndex = 100;
e1.customConfig.uploadImgServer = 'http://localhost:8070/FileServer/file/upload';
e1.create();
e2 = new E("#editContent"); //创建富文本对象
e2.customConfig.zIndex = 100;
e2.customConfig.uploadImgServer = 'http://localhost:8070/FileServer/file/upload';
e2.create();
});
//添加商品
function addSubmit() {
var icon = $("#addIcon").val();
var name = $("#addName").val();
var typeId = $("#addTypeId").val();
var price = $("#addPrice").val();
var num = $("#addNum").val();
var content = e1.txt.html();
var obj = {icon:icon,name:name,typeId:typeId,price:price,num:num,content:content};
$.ajax({
url:"/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 editSubmit() {
var icon = $("#editIcon").val();
var name = $("#editName").val();
var typeId = $("#editTypeId").val();
var price = $("#editPrice").val();
var num = $("#editNum").val();
var content = e2.txt.html();
var id = $("#editId").val(); //获取当前的id
var obj = {icon:icon,name:name,typeId:typeId,price:price,num:num,content:content,id:id};
if(name.trim()==""){
alert("对不起,名称不能为空");
return;
}
if(typeId.trim()==""){
alert("对不起,商品类型不能为空");
return;
}
if(price.trim()==""){
alert("对不起,价格不能为空");
return;
}
if(num.trim()==""){
alert("对不起,数量不能为空");
return;
}
$.ajax({
url:"/shop/editGoodsById",
type:"post",
data:obj,
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message);
}else if(data.code==1){
location.reload(); //刷新页面
}
}
});
e2.txt.clear();
}
//根据id修改商品信息
function editById(id){
$("#content").hide(1000); //列表消失
$("#edit").show(1000); //修改框出现
$.ajax({
url:"/shop/getGoodsById",
type:"post",
data:{id:id},
dataType:"json",
success: function (data) {
if(data.code==-1){
alert(data.message);
}else if(data.code==1){
var goodsBean = data.data;
//服务器获取分类信息成功, 之后将这些信息放到页面中
$("#editName").val(goodsBean.name);
$("#editPrice").val(goodsBean.price);
$("#editNum").val(goodsBean.num);
$("#editIcon").val(goodsBean.icon);
$("#editContent").val(e2.txt.append(goodsBean.content));
$("#editId").val(goodsBean.id);
}
}
});
}
//根据id删除商品信息
function deleteById(id) {
//提醒用户,是否真正删除
var flg = confirm("是否确定删除");
if(!flg){
return;
}
$.ajax({
url:"/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 addSubmitIcon() {
var file = $("#addFile")[0].files[0];
var formData = new FormData();
formData.append("file",file);
$.ajax({
url:"http://localhost:8070/FileServer/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){
$("#addIcon").val(data.data[0]);
$("#icon").attr("src",data.data[0]);
}
}
});
}
//修改上传图片
function editSubmitIcon() {
var file = $("#editFile")[0].files[0];
var formData = new FormData();
formData.append("file",file);
$.ajax({
url:"http://localhost:8070/FileServer/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){
$("#editIcon").val(data.data[0]);
$("#icon1").attr("src",data.data[0]);
}
}
});
}
//商品上下架
function editStatus(id,status) {
var flg;
if (status == 0){
flg = confirm("是否要下架?");
}else {
flg = confirm("是否要上架?");
}
if (!flg){
return;
}
$.ajax({
url:"/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,因为每一个操作都需要同数据库进行交互