【JAVA EE#6】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图
权限控制
普通用户只能访问client
文件夹下面的jsp文件,对于没有权限操作的admin
文件夹就会提示错误,而超级用户同时可以访问两者,一直很好奇这个权限限制怎么实现的。
原来在存在一个AdminPrivilegeFilter
类继承自过滤器Fliter
,获取Session
的role
属性对此进行的管制:
// 1 强制转换HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) resp;// 2判断是否具有权限User user = (User) request.getSession().getAttribute("user");if (user != null && "超级用户".equals(user.getRole())) {// 3.放行chain.doFilter(request, response);return;}response.sendRedirect(request.getContextPath() + "/error/privilege.jsp");
并在web.xml注册信息里面对所有访问进行了拦截:
<filter><filter-name>adminPrivilegeFilter</filter-name><filter-class>cn.itcast.itcaststore.web.filter.AdminPrivilegeFilter</filter-class>
</filter>
<filter-mapping><filter-name>adminPrivilegeFilter</filter-name><url-pattern>/admin/*</url-pattern>
</filter-mapping>
页面分析
按照代码逻辑,管理员登陆后只能进入home.jsp
页面,并不像和普通用户一样可以进行购书操作的逻辑。页面由四个jsp组成,利用<frameset>
标签划分各个<frame>
搭建框架。body { SCROLLBAR-ARROW-COLOR: SCROLLBAR-BASE-COLOR:}
语句控制滚动条颜色。
jsp代码:
<%@ page language="java" pageEncoding="UTF-8"%>
<html><head><meta http-equiv="Content-Language" content="zh-cn"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style>body{SCROLLBAR-ARROW-COLOR: #ffffff; SCROLLBAR-BASE-COLOR: #dee3f7;}</style></head>
<frameset rows="103,*,43" frameborder=0 border="0" framespacing="0"><frame src="${pageContext.request.contextPath}/admin/login/top.jsp" name="topFrame" scrolling="NO" noresize><frameset cols="159,*" frameborder="0" border="0" framespacing="0"><frame src="${pageContext.request.contextPath}/admin/login/left.jsp" name="leftFrame" noresize scrolling="YES"><frame src="${pageContext.request.contextPath}/admin/login/welcome.jsp" name="mainFrame"></frameset><frame src="${pageContext.request.contextPath}/admin/login/bottom.jsp" name="bottomFrame" scrolling="NO" noresize>
</frameset>
</html>
top.jsp
有两个功能,显示日期以及退出系统,日期显示利用document
内置对象进行输出的,js语句不用<%>内嵌,而是语句块一样写在一起。
<font color="#000000"><script language="JavaScript"> tmpDate = new Date();date = tmpDate.getDate();month = tmpDate.getMonth() + 1;year = tmpDate.getFullYear();document.write(year);document.write("年");document.write(month);document.write("月");document.write(date);document.write("日 ");myArray = new Array(6);myArray[0] = "星期日"yArray[1] = "星期一"myArray[2] = "星期二"myArray[3] = "星期三"myArray[4] = "星期四"myArray[5] = "星期五"myArray[6] = "星期六"weekday = tmpDate.getDay();if (weekday == 0 | weekday == 6) {document.write(myArray[weekday])} else {document.write(myArray[weekday])}; </script>
</font>
退出登录调用了自定义函数exitSys()
,利用window
内置对象window.top
返回该页面的顶层窗口(不过实测,点击后并无反应,Session
也没有失效)。
<script type="text/javascript">function exitSys() {var flag = window.confirm("确认退出系统吗?");if (flag) {window.top.open('', '_parent','',true);window.top.close();}//如果你使用的是firefox浏览器必须要做以下设置 //1、在地址栏输入about:config然后回车,警告确认 //2、在过滤器中输入”dom.allow_scripts_to_close_windows“,双击即可将此值设为true 即可完成了 }
</script>
商品管理
该模块有4个功能:添加书本、查询(包括所有书本+多条件查询)、编辑书本信息、删除该书本。
点击左侧商品管理,就会访问ListProductServlet
,然后重定向到/admin/products/list.jsp
,然后循环输出书本列表。页面根据jsp传递的type访问FindProductByIdServlet
,该类得到用户类型为超级用户就会转向edit.jsp
,普通用户就会转向info.jsp
,点击删除时,就会转向DeleteProductServlet
,同时调用页面自定义函数p_del()
弹出确认框,
该jsp页面核心代码:
<!-- 循环输出所有商品 --><c:forEach items="${ps}" var="p"><tr onmouseover="this.style.backgroundColor = 'white'"onmouseout="this.style.backgroundColor = '#F5FAFE';"><td style="CURSOR: hand; HEIGHT: 22px" align="center" width="200">${p.id }</td><td style="CURSOR: hand; HEIGHT: 22px" align="center" width="18%">${p.name }</td><td style="CURSOR: hand; HEIGHT: 22px" align="center" width="8%">${p.price }</td><td style="CURSOR: hand; HEIGHT: 22px" align="center" width="8%">${p.pnum }</td><td style="CURSOR: hand; HEIGHT: 22px" align="center">${p.category}</td><td align="center" style="HEIGHT: 22px" width="7%"><a href="${pageContext.request.contextPath}/findProductById?id=${p.id}&type=admin"><img src="${pageContext.request.contextPath}/admin/images/i_edit.gif" border="0" style="CURSOR: hand"> </a> </td><td align="center" style="HEIGHT: 22px" width="7%"><a href="${pageContext.request.contextPath}/deleteProduct?id=${p.id}" onclick="javascript:return p_del()"><img src="${pageContext.request.contextPath}/admin/images/i_del.gif"width="16" height="16" border="0" style="CURSOR: hand"></a></td></tr></c:forEach>
除此之外,还有多条件查询、编辑商品信息、增加商品信息的功能。核心代码:
// 多条件查询public List<Product> findProductByManyCondition(String id, String name,String category, String minprice, String maxprice)throws SQLException {List<Object> list = new ArrayList<Object>();String sql = "select * from products where 1=1 ";QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());if (id != null && id.trim().length() > 0) {sql += " and id=?";list.add(id);}if (name != null && name.trim().length() > 0) {sql += " and name=?";list.add(name);}if (category != null && category.trim().length() > 0) {sql += " and category=?";list.add(category);}if (minprice != null && maxprice != null&& minprice.trim().length() > 0 && maxprice.trim().length() > 0) {sql += " and price between ? and ?";list.add(minprice);list.add(maxprice);}Object[] params = list.toArray();return runner.query(sql, new BeanListHandler<Product>(Product.class),params);}
多条件查询主要是对SQL摆弄,判断各个参数传入的情况加入到数组集合,再转成String
数组传入QueryRunner
查询,用到了Array
的toArray()
方法,该方法返回一个Object[]
对象。常用的集合转数组的方法。
// 修改商品信息public void editProduct(Product p) throws SQLException {//1.创建集合并将商品信息添加到集合中List<Object> obj = new ArrayList<Object>();obj.add(p.getName());obj.add(p.getPrice());obj.add(p.getCategory());obj.add(p.getPnum());obj.add(p.getDescription());//2.创建sql语句,并拼接sqlString sql = "update products " +"set name=?,price=?,category=?,pnum=?,description=? ";//判断是否有图片if (p.getImgurl() != null && p.getImgurl().trim().length() > 0) {sql += " ,imgurl=?";obj.add(p.getImgurl());}sql += " where id=?";obj.add(p.getId()); System.out.println(sql); System.out.println(obj);//3.创建QueryRunner对象QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());//4.使用QueryRunner对象的update()方法更新数据runner.update(sql, obj.toArray());}
增加商品主要逻辑集中在文件上传上面,代码很繁杂,步骤很多,主要用了DiskFileItemFactory
、ServletFileUpload
、IOUtils
、FileItem
、File
、FileUploadUtils
这几个没见过的新类实现的,前5个都是org.apache.commons
下面的第三方jar包,这个过程相当复杂,FileUploadUtils
是我们自己封装的一个工具类,作用是截取真实的文件名,我也是不明其意,暂且记录着,用到的时候说不定就慢慢懂了:
// 创建javaBean,将上传数据封装.Product p = new Product();Map<String, String> map = new HashMap<String, String>();// 封装商品idmap.put("id", IdUtils.getUUID());DiskFileItemFactory dfif = new DiskFileItemFactory();// 设置临时文件存储位置dfif.setRepository(new File(this.getServletContext().getRealPath("/temp")));// 设置上传文件缓存大小为10mdfif.setSizeThreshold(1024 * 1024 * 10);// 创建上传组件ServletFileUpload upload = new ServletFileUpload(dfif);// 处理上传文件中文乱码upload.setHeaderEncoding("utf-8");try {// 解析request得到所有的FileItemList<FileItem> items = upload.parseRequest(request);// 遍历所有FileItemfor (FileItem item : items) {// 判断当前是否是上传组件if (item.isFormField()) {// 不是上传组件String fieldName = item.getFieldName(); // 获取组件名称String value = item.getString("utf-8"); // 解决乱码问题map.put(fieldName, value);} else {// 是上传组件// 得到上传文件真实名称String fileName = item.getName();fileName = FileUploadUtils.subFileName(fileName);// 得到随机名称String randomName = FileUploadUtils.generateRandonFileName(fileName);// 得到随机目录String randomDir = FileUploadUtils.generateRandomDir(randomName);// 图片存储父目录String imgurl_parent = "/productImg" + randomDir;File parentDir = new File(this.getServletContext().getRealPath(imgurl_parent));// 验证目录是否存在,如果不存在,创建出来if (!parentDir.exists()) {parentDir.mkdirs();}String imgurl = imgurl_parent + "/" + randomName;map.put("imgurl", imgurl);IOUtils.copy(item.getInputStream(), new FileOutputStream(new File(parentDir, randomName)));item.delete();}}} catch (FileUploadException e) {e.printStackTrace();}try {// 将数据封装到javaBean中BeanUtils.populate(p, map);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}ProductService service = new ProductService();try {// 调用service完成添加商品操作service.addProduct(p);response.sendRedirect(request.getContextPath()+ "/listProduct");return;} catch (AddProductException e) {e.printStackTrace();response.getWriter().write("添加商品失败");return;}
FileUploadUtils
:
public class FileUploadUtils {/*** 截取真实文件名* * @param fileName* @return*/public static String subFileName(String fileName) {// 查找最后一个 \出现位置int index = fileName.lastIndexOf("\\");if (index == -1) {return fileName;}return fileName.substring(index + 1);}// 获得随机UUID文件名public static String generateRandonFileName(String fileName) {// 获得扩展名int index = fileName.lastIndexOf(".");if (index != -1) {String ext = fileName.substring(index);return UUID.randomUUID().toString() + ext;}return UUID.randomUUID().toString();}// 获得hashcode生成二级目录public static String generateRandomDir(String uuidFileName) {int hashCode = uuidFileName.hashCode();// 一级目录int d1 = hashCode & 0xf;// 二级目录int d2 = (hashCode >> 4) & 0xf;return "/" + d1 + "/" + d2;}
}
销售榜单
该模块只有一个销售榜单的文件下载功能,主要实现在DownloadServlet
中,文件下载后是csv
后缀的excel文档,其实只是一个"商品名称"+"销售数量"文本文档,牵涉到文件下载就要用到IO流
了,打开一个PrintWriter
对象,println()
函数写入,flush()
强制输出close()
关闭,有趣的是,这个流对象是直接由Respose
给出,在拿到流对象之前,就已经在Respose
设置好了相关ContentType
、Header
、文件名、文件编码CharacterEncoding
参数。
String year = request.getParameter("year");String month = request.getParameter("month");ProductService service = new ProductService();List<Object[]> ps = service.download(year,month);//开始设置参数String fileName=year+"年"+month+"月销售榜单.csv"; response.setContentType(this.getServletContext().getMimeType(fileName));response.setHeader("Content-Disposition", "attachement;filename="+new String(fileName.getBytes("GBK"),"iso8859-1")); response.setCharacterEncoding("gbk"); PrintWriter out = response.getWriter();out.println("商品名称,销售数量");for (int i = 0; i < ps.size(); i++) {Object[] arr=ps.get(i);out.println(arr[0]+","+arr[1]); }out.flush();out.close();
在ProductDao
底层代码里:
// 销售榜单public List<Object[]> salesList(String year, String month)throws SQLException {String sql = "SELECT products.name,SUM(orderitem.buynum) totalsalnum FROM orders,products,orderItem WHERE orders.id=orderItem.order_id AND products.id=orderItem.product_id AND orders.paystate=1 and year(ordertime)=? and month(ordertime)=? GROUP BY products.name ORDER BY totalsalnum DESC";QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());return runner.query(sql, new ArrayListHandler(), year, month);}
QueryRunner
不仅可以以javaBean
的数据形式返回查询结果,还可以以数组的形式返回数据,这样每一项各自有一段名称和销售总数的数据。
订单管理
该模块下有3个功能:查询所有订单(包括多条件查询)、查看订单详情、删除订单。
就循环输出所有订单和对应用户信息,用到了匿名内部类实现了ResultSetHandler<List<Order>>
接口,重写了handle(ResultSet rs)
方法,循环遍历结果返回List<Order>
。Dao
这样的写法还用了很多,比如多条件查询。现在还不太了解,借下大佬总结,知道这么一个东西可以这样用:
【DButils学习之】利用ResultSetHandler各实现类来处理查询结果
public List<Order> findAllOrder() throws SQLException {//1.创建sqlString sql = "select orders.*,user.* from orders,user where user.id=orders.user_id order by orders.user_id";//2.创建QueryRunner对象QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());//3.返回QueryRunner对象query()方法的查询结果return runner.query(sql, new ResultSetHandler<List<Order>>() { public List<Order> handle(ResultSet rs) throws SQLException {//创建订单集合List<Order> orders = new ArrayList<Order>();//循环遍历订单和用户信息while (rs.next()) {Order order = new Order();order.setId(rs.getString("orders.id"));order.setMoney(rs.getDouble("orders.money"));order.setOrdertime(rs.getDate("orders.ordertime"));order.setPaystate(rs.getInt("orders.paystate"));order.setReceiverAddress(rs.getString("orders.receiverAddress"));order.setReceiverName(rs.getString("orders.receiverName"));order.setReceiverPhone(rs.getString("orders.receiverPhone"));orders.add(order);User user = new User();user.setId(rs.getInt("user.id"));user.setEmail(rs.getString("user.email"));user.setGender(rs.getString("user.gender"));user.setActiveCode(rs.getString("user.activecode"));user.setIntroduce(rs.getString("user.introduce"));user.setPassword(rs.getString("user.password"));user.setRegistTime(rs.getDate("user.registtime"));user.setRole(rs.getString("user.role"));user.setState(rs.getInt("user.state"));user.setTelephone(rs.getString("user.telephone"));user.setUsername(rs.getString("user.username"));order.setUser(user);}return orders;}});
查看订单详情,具体在普通用户和管理员之间实现了一个转向,底层Dao
的逻辑多了一个订单子项的Dao
层运用,写法上大同小异没什么新的知识,研究的意义不大:
//1.获取用户类型String type=request.getParameter("type"); //2.得到要查询的订单的idString id = request.getParameter("id");//3.根据id查找订单OrderService service = new OrderService();Order order = service.findOrderById(id);//4.将查询出的订单信息添加到request作用域中request.setAttribute("order", order);//5.如果用户类型不为null,则请求转发到view.jsp页面,否则转发到orderInfo.jsp页面if(type!=null){request.getRequestDispatcher("/admin/orders/view.jsp").forward(request, response);return;}request.getRequestDispatcher("/client/orderInfo.jsp").forward(request, response);}
删除订单项时,一个订单Order
对应一组订单子项OrderItem
(以List形式存放),当删除Order
时,OrderItem
就没有对应的成员变量,就会出错,所以先删其子项再删订单本身,到最后终于利用上一开篇的DataSourceUtils
定义的事务管理方法,作用是,为了安全着想,如果订单项或订单删除时任一出错的话,删除事务回滚,操作失败:
OrderService
中:
//根据id删除订单 管理员删除订单public void delOrderById(String id) { try {DataSourceUtils.startTransaction();//开启事务oidao.delOrderItems(id);odao.delOrderById(id);} catch (SQLException e) {e.printStackTrace();try {DataSourceUtils.rollback();} catch (SQLException e1) {e1.printStackTrace();}}finally{try {DataSourceUtils.releaseAndCloseConnection();} catch (SQLException e) {e.printStackTrace();}} }
DataSourceUtils
中:
/*** 开启事务* @throws SQLException*/public static void startTransaction() throws SQLException {Connection con = getConnection();if (con != null)con.setAutoCommit(false);}/*** 从ThreadLocal中释放并且关闭Connection,并结束事务* @throws SQLException*/public static void releaseAndCloseConnection() throws SQLException {Connection con = getConnection();if (con != null) {con.commit();tl.remove();con.close();}}/*** 事务回滚* @throws SQLException */public static void rollback() throws SQLException {Connection con = getConnection();if (con != null) {con.rollback();}}
公告管理
该模块有添加公告、查询所有通告、编辑信息、删除通告子功能。
调用了ListNoticeServlet
,打开服务层获取所有通告,设置到Request参数中,jsp页面获取显示,之前用烂了的套路的套路,也没啥探讨的。
NoticeService nService = new NoticeService();List<Notice> notices = nService.getAllNotices();req.setAttribute("notices", notices);req.getRequestDispatcher("/admin/notices/list.jsp").forward(req, resp);
整理下,整个项目结构如下,jsp文件那块没有完善,主要集中在后台逻辑上,一清二楚,方便理解,后续放上原文件,感兴趣的可以下载:
完完整整的一大套业务,着实庞大,若不是阅读自己写恐怕没几个月是不可能完成的,写惯了Android小项目的我总算开了大眼界,jsp和Servlet技术这么原始纯粹的技术实现大项目起来费劲的地方还是有的,正式宣布我要开始入门web开发了,到时毕业设计不懂后端什么也做不成,这还是非常有必要的。还有寒假的两个项目开发任务可算完成了(郭霖书本的天气预报+传智书城),偷懒了这么久挺惭愧的,eclipse再见,再也不想用你了难受。
接下来专门学习后端技术,学会写写API,应用在App上面,武装自己吧!
【JAVA EE#6】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图相关推荐
- FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程
文章目录 1. 前言 2. 源码分析 2.1 sofia 模块的加载 2.2 呼入的处理流程 1. 前言 SIP(Session Initiation Protocol) 是应用层的信令控制协议,有许 ...
- Java多线程——重入锁ReentrantLock源码阅读
上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...
- UEditor之基于Java图片上传前后端源码研究
那么开始吧! 这是我的项目目录 1.从访问路径http://localhost:8081/Test/_examples/simpleDemo.html,我们主要是要看看,富文本框被加载出来之前,会调用 ...
- java web开发传智书城项目中文乱码
干啥啥不行,乱码第一名! 引入JS文件乱码 Filter造成的乱码问题 数据库的乱码 引入JS文件造成的乱码问题 Filter造成的乱码问题 信息输出到数据库造成的乱码问题 起初遇到这些乱码问题的时候 ...
- antd request 通过jsessionid传参数_Umi-request源码阅读
最近参照antd-pro脚手架进行开发,因此接触到了umi-request. umijs/umi-requestgithub.com umi-request对fetch进行了封装,简化了api的使用 ...
- freeswitch源码阅读 之 sofia模块
sofia模块在freeswitch中的位置非常重要, 所有的sip通话都和它有关, 那么我们就看一下该模块的执行流程. 一. 实现的功能: 1. sip注册; 2. 呼叫; 3. Presence; ...
- PHP yii 框架源码阅读(二) - 整体执行流程分析
转载链接:http://tech.ddvip.com/2013-11/1384432766205970.html 一 程序入口 <?php// change the following pat ...
- vue data 值如何渲染_vue源码阅读复盘-watcher模块
回顾目标 数据驱动视图: 理解watcher.dep.observer这三个对象之间的关系 这和VUE对象又有什么关系? 这和视图又有什么关系? 叙述过程 先彻底理解了一下VUE的简介,并写出了一份建 ...
- 十三、传智书城项目设计
项目源代码及sql脚本 一.项目概述 近年来,随着Internet的迅速崛起,互联网已成为收集信息的最佳渠道并逐步进入传统的流通领域.于是电子商务开始流行起来,越来越多的商家在网上建起在线商店,向消费 ...
最新文章
- C#二进制格式与文件相互转换
- 【数据库】Kingbase金仓数据库工程维护简明手册
- 从浏览器输入URL到最终看到页面, 这其中经历了哪些过程 ?
- RDA8955中碰到的问题
- python easygui进度条_Python _easygui详细版
- 基于Spring Boot和Spring Cloud实现微服务架构学习
- liberOJ#6006. 「网络流 24 题」试题库 网络流, 输出方案
- 如何使用vue使同一个弹窗同时能实现添加和编辑
- 在Apache中隐藏Php文件后缀
- 将linux文件拷贝到windows,Windows与Linux系统拷贝文件之pscp的使用分享
- php7 aop,php之aop实践
- 将图像数据jpg,png等存储为npy/npz格式
- python卡方检验 scipy_Fisher 精确检验 与卡方检验
- codevs1260 快餐问题
- 十年磨一剑:大众凭借电池的革命性突破超越特斯拉
- Mac(3) Parallels Desktop 安装 Windows10专业版
- ETL示例解决方案 —— Sakila下载和基本配置 (笔记一)
- ROS2进行人脸识别face_recognition
- 为什么年轻人都喜欢互联网行业
- 【历史上的今天】5 月 6 日:第一台实际运行程序的计算机;Adobe 转型云端;首个非拉丁文网址出现
热门文章
- 万里汇WorldFirst个人和企业帐户注册教程(送$25+1%提现费)
- 数字图像处理与Python实现-图像降噪-指数型高通滤波
- 77道Spring面试题以及参考答案(2021年最新版),java开发项目经理面试题
- QWT--滚轮放大缩小和拖拽视窗
- 计算机硬件英语词汇,计算机硬件英语词汇
- 剪不断,理还乱——UML的四种关系
- vue移动端用什么数据可视化插件_前端必看的数据可视化入门指南
- 慧据价值 链接未来丨第八届数据技术嘉年华大会全议程精彩呈现
- ppt中如何合并流程图_PPT中流程图如何分支?
- Springboot快递管理系统1k61h计算机毕业设计-课程设计-期末作业-毕设程序代做