之前学习了使用J2EE开发一个模仿天猫商城整站的项目,期间学习到了不少知识。但是隔了一段时间再回看代码,居然有点生疏了~所以写下这篇博客,方便日后回顾,温故而知新,也可以和大家交流学习。
本篇介绍项目的后台管理开发,模块主要分为:分类管理,用户管理,订单管理,分类下的产品管理,分类属性管理,产品属性管理和产品图片管理等等。。。
浏览地址:http://how2j.cn/tmall/admin_category_list
下载地址:链接:https://pan.baidu.com/s/12EnpGy8gr-TC6KL_JRSiCg 密码:lrxu
涉及到的知识
前端:html+css+JavaScript+jQuery+Bootstrap
后端:j2ee
数据库:mysql
数据库表关系图
这里使用的是MVC开发模式,jsp作为显示的层面,servlet充当控制层,bean和dao作为模型
如果一个功能对应一个servlet,那么一个项目里面就有很多很多的servlet,web.xml也要配置很多次。这里的解决方案是使用Filter结合servlet。假设访问路径是 http://127.0.0.1:8080/tmall/admin_category_list,首先设置一个过滤器BackServletFilter,对所有请求进行拦截,判断访问的地址是否以/admin_开头,如果是,那么做如下操作
1.取出两个下划线之间的值 category
2.取出最后一个下划线之后的值 list
3. 然后根据这个值,服务端跳转到categoryServlet,并且把list这个值传递过去
package tmall.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
public class BackServletFilter implements Filter {
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String contextPath=request.getServletContext().getContextPath();
String uri = request.getRequestURI();
uri =StringUtils.remove(uri, contextPath);
if(uri.startsWith("/admin_")){
String servletPath = StringUtils.substringBetween(uri,"_", "_") + "Servlet";
String method = StringUtils.substringAfterLast(uri,"_" );
request.setAttribute("method", method);
req.getRequestDispatcher("/" + servletPath).forward(request, response);
return;
}
chain.doFilter(request, response);
}
public void init(FilterConfig arg0) throws ServletException {
}
}
紧接着,定义一个BaseBackServlet进行抽取,原理是利用反射技术。让categoryServlet 继承BaseBackServlet,重写BaseBackServlet中的service函数。在调用categoryServlet中的dopost或者doget方法前,BaseBackServlet会被调用,根据filter传递过来method的值,借助反射,调用categoryServlet中对用的方法,最后根据servlet中返回的字段,选择进行服务器端的跳转或者是客户端的跳转。
package tmall.servlet;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import tmall.dao.CategoryDAO;
import tmall.dao.OrderDAO;
import tmall.dao.OrderItemDAO;
import tmall.dao.ProductDAO;
import tmall.dao.ProductImageDAO;
import tmall.dao.PropertyDAO;
import tmall.dao.PropertyValueDAO;
import tmall.dao.ReviewDAO;
import tmall.dao.UserDAO;
import tmall.util.Page;
public abstract class BaseBackServlet extends HttpServlet {
public abstract String add(HttpServletRequest request, HttpServletResponse response, Page page) ;
public abstract String delete(HttpServletRequest request, HttpServletResponse response, Page page) ;
public abstract String edit(HttpServletRequest request, HttpServletResponse response, Page page) ;
public abstract String update(HttpServletRequest request, HttpServletResponse response, Page page) ;
public abstract String list(HttpServletRequest request, HttpServletResponse response, Page page) ;
protected CategoryDAO categoryDAO = new CategoryDAO();
protected OrderDAO orderDAO = new OrderDAO();
protected OrderItemDAO orderItemDAO = new OrderItemDAO();
protected ProductDAO productDAO = new ProductDAO();
protected ProductImageDAO productImageDAO = new ProductImageDAO();
protected PropertyDAO propertyDAO = new PropertyDAO();
protected PropertyValueDAO propertyValueDAO = new PropertyValueDAO();
protected ReviewDAO reviewDAO = new ReviewDAO();
protected UserDAO userDAO = new UserDAO();
public void service(HttpServletRequest request, HttpServletResponse response) {
try {
/*获取分页信息*/
int start= 0;
int count = 5;
try {
start = Integer.parseInt(request.getParameter("page.start"));
} catch (Exception e) {
}
try {
count = Integer.parseInt(request.getParameter("page.count"));
} catch (Exception e) {
}
Page page = new Page(start,count);
/*借助反射,调用对应的方法*/
String method = (String) request.getAttribute("method");
Method m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
javax.servlet.http.HttpServletResponse.class,Page.class);
String redirect = m.invoke(this,request, response,page).toString();
/*根据方法的返回值,进行相应的客户端跳转,服务端跳转,或者仅仅是输出字符串*/
if(redirect.startsWith("@"))
response.sendRedirect(redirect.substring(1));
else if(redirect.startsWith("%"))
response.getWriter().print(redirect.substring(1));
else
request.getRequestDispatcher(redirect).forward(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}
}
1.分类管理
分类管理是后台开发的起始,是产品管理的父级,这里实现了分类管理增删查改的基本操作,同时提供分页查询,分类属性的增删查改,分类下产品管理入口(在跳转时传递分类id查找分类下的产品)。下面是分类下分页查询代码,分页查询在其他功能开发中都会用到,在page中设置start和count作为参数传入categoryDao中,查询出条件下的分类集合,用request传到jsp页面中去
public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
List cs = categoryDAO.list(page.getStart(),page.getCount());
int total = categoryDAO.getTotal();
page.setTotal(total);
request.setAttribute("thecs", cs);
request.setAttribute("page", page);
return "admin/listCategory.jsp";
}
2.分类属性管理
每个分类都有对应属性,例如电视机,会有产地,尺寸,颜色等属性,所以我们得对它的这些属性进行管理。例如做一个增加操作,提交数据是在istProperty.jsp页面中的,除了提交属性名称,还会提交cid,在PropertyServlet中根据获取到的cid,name参数,创建新的Property对象,并插入到数据库,客户端跳转到admin_property_list,并带上参数cid。
listProperty.jsp代码片段
propertyServlet代码片段
public String add(HttpServletRequest request, HttpServletResponse response, Page page) {
int cid = Integer.parseInt(request.getParameter("cid"));
Category c = categoryDAO.get(cid);
String name= request.getParameter("name");
Property p = new Property();
p.setCategory(c);
p.setName(name);
propertyDAO.add(p);
return "@admin_property_list?cid="+cid;
}
3.产品管理
产品管理这里也是关于增删查改的基本操作,有不同的是page下必须多夹带一个参数,这个参数就是产品父级-分类的id,在产品的分页查询需要用到这个参数,查询访问的是ProductServlet的list()方法,首先获取分类 cid,基于cid,获取当前分类下的产品集合, 获取当前分类下的产品总数,并且设置给分页page对象,拼接字符串"&cid="+c.getId(),设置给page对象的Param值。 因为产品分页都是基于当前分类下的分页,所以分页的时候需要传递这个cid,封装好数据,服务端跳转到admin/listProduct.jsp页面, 在listProduct.jsp页面上使用c:forEach 遍历ps集合,并显示
public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
int cid = Integer.parseInt(request.getParameter("cid"));
Category c = categoryDAO.get(cid);
List ps = productDAO.list(cid, page.getStart(),page.getCount());
int total = productDAO.getTotal(cid);
page.setTotal(total);
page.setParam("&cid="+c.getId());
request.setAttribute("ps", ps);
request.setAttribute("c", c);
request.setAttribute("page", page);
return "admin/listProduct.jsp";
}
listProduct.jsp代码片段
${p.id}
${p.name}
${p.subTitle}
${p.orignalPrice}
${p.promotePrice}
${p.stock}
4.产品图片管理
每个产品的图片有分为单张图片和详细信息图片,单张图片是为了展示产品外观,而详细信息图片是为了展示产品详细信息。parseUpload 获取上传文件的输入流,parseUpload 方法会修改params 参数,并且把浏览器提交的type,pid信息放在其中,从params 中取出type,pid信息,并根据这个type,pid,借助productImageDAO,向数据库中插入数据,根据request.getSession().getServletContext().getRealPath( "img/productSingle"),定位到存放分类图片的目录,除了productSingle,还有productSingle_middle和productSingle_small。 因为每上传一张图片,都会有对应的正常,中等和小的三种大小图片,并且放在3个不同的目录下,文件命名以保存到数据库的分类对象的id+".jpg"的格式命名,根据步骤1获取的输入流,把浏览器提交的文件,复制到目标文件,再借助ImageUtil.resizeImage把正常大小的图片,改变大小之后,分别复制到productSingle_middle和productSingle_small目录下, 处理完毕之后,客户端条跳转到admin_productImage_list?pid=,并带上pid。
productImageServlet代码片段
public String add(HttpServletRequest request, HttpServletResponse response, Page page) {
//上传文件的输入流
InputStream is = null;
//提交上传文件时的其他参数
Map params = new HashMap<>();
//解析上传
is = parseUpload(request, params);
//根据上传的参数生成productImage对象
String type= params.get("type");
int pid = Integer.parseInt(params.get("pid"));
Product p =productDAO.get(pid);
ProductImage pi = new ProductImage();
pi.setType(type);
pi.setProduct(p);
productImageDAO.add(pi);
//生成文件
String fileName = pi.getId()+ ".jpg";
String imageFolder;
String imageFolder_small=null;
String imageFolder_middle=null;
if(ProductImageDAO.type_single.equals(pi.getType())){
imageFolder= request.getSession().getServletContext().getRealPath("img/productSingle");
imageFolder_small= request.getSession().getServletContext().getRealPath("img/productSingle_small");
imageFolder_middle= request.getSession().getServletContext().getRealPath("img/productSingle_middle");
}
else
imageFolder= request.getSession().getServletContext().getRealPath("img/productDetail");
File f = new File(imageFolder, fileName);
f.getParentFile().mkdirs();
// 复制文件
try {
if(null!=is && 0!=is.available()){
try(FileOutputStream fos = new FileOutputStream(f)){
byte b[] = new byte[1024 * 1024];
int length = 0;
while (-1 != (length = is.read(b))) {
fos.write(b, 0, length);
}
fos.flush();
//通过如下代码,把文件保存为jpg格式
BufferedImage img = ImageUtil.change2jpg(f);
ImageIO.write(img, "jpg", f);
if(ProductImageDAO.type_single.equals(pi.getType())){
File f_small = new File(imageFolder_small, fileName);
File f_middle = new File(imageFolder_middle, fileName);
ImageUtil.resizeImage(f, 56, 56, f_small);
ImageUtil.resizeImage(f, 217, 190, f_middle);
}
}
catch(Exception e){
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "@admin_productImage_list?pid="+p.getId();
}
5.产品属性管理
每个产品都有各自的属性,比如电视机的颜色是红色。所以对产品属性的管理也无非是增删查改,这里用修改举例,修改时候使用了post提交ajax异步处理。使用监听输入框上的keyup事件,获取输入框里的,还有定义的pvid,借助JQuery的ajax函数 $.post,把id和值,提交到admin_product_updatePropertyValue,admin_product_updatePropertyValue导致ProductServlet的updatePropertyValue方法被调用, BaseBackServlet根据返回值"%success",直接输出字符串"success" 到浏览器, 浏览器判断如果返回值是"success",那么就把边框设置为绿色,表示修改成功,否则设置为红色,表示修改失败。
editProperty.jsp代码片段
$("input.pvValue").keyup(function(){
var value = $(this).val();
var page = "admin_product_updatePropertyValue";
var pvid = $(this).attr("pvid");
var parentSpan = $(this).parent("span");
parentSpan.css("border","1px solid yellow");
$.post(
page,
{"value":value,"pvid":pvid},
function(result){
if("success"==result)
parentSpan.css("border","1px solid green");
else
parentSpan.css("border","1px solid red");
}
);
});
6.用户管理
这里就只是从数据库取出来,分页显示~
7.订单管理
后台订单管理只提供发货和查询的方法,下单和取消订单是由前台用户自己进行的。当订单状态是waitDelivery的时候,就会出现发货按钮,发货按钮链接跳转到admin_order_delivery,OrderServlet.delivery()方法被调用,获取对象id,修改发货时间,设置发货状态,更新数据库,跳转,完成了
orderServlet片段
public String delivery(HttpServletRequest request, HttpServletResponse response, Page page) {
int id = Integer.parseInt(request.getParameter("id"));
Order o = orderDAO.get(id);
o.setDeliveryDate(new Date());
o.setStatus(OrderDAO.waitConfirm);
orderDAO.update(o);
return "@admin_order_list";
}
总结下来,j2ee开发增删查改第一步先是获取到传递过来的数据,在根据实际业务对数据进行操作,到这里后台部分就写完了