权限控制
普通用户只能访问client文件夹下面的jsp文件,对于没有权限操作的admin文件夹就会提示错误,而超级用户同时可以访问两者,一直很好奇这个权限限制怎么实现的。

原来在存在一个AdminPrivilegeFilter类继承自过滤器Fliter,获取Sessionrole属性对此进行的管制:

// 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查询,用到了ArraytoArray()方法,该方法返回一个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());}


增加商品主要逻辑集中在文件上传上面,代码很繁杂,步骤很多,主要用了DiskFileItemFactoryServletFileUploadIOUtilsFileItemFileFileUploadUtils这几个没见过的新类实现的,前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设置好了相关ContentTypeHeader、文件名、文件编码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】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图相关推荐

  1. FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程

    文章目录 1. 前言 2. 源码分析 2.1 sofia 模块的加载 2.2 呼入的处理流程 1. 前言 SIP(Session Initiation Protocol) 是应用层的信令控制协议,有许 ...

  2. Java多线程——重入锁ReentrantLock源码阅读

    上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...

  3. UEditor之基于Java图片上传前后端源码研究

    那么开始吧! 这是我的项目目录 1.从访问路径http://localhost:8081/Test/_examples/simpleDemo.html,我们主要是要看看,富文本框被加载出来之前,会调用 ...

  4. java web开发传智书城项目中文乱码

    干啥啥不行,乱码第一名! 引入JS文件乱码 Filter造成的乱码问题 数据库的乱码 引入JS文件造成的乱码问题 Filter造成的乱码问题 信息输出到数据库造成的乱码问题 起初遇到这些乱码问题的时候 ...

  5. antd request 通过jsessionid传参数_Umi-request源码阅读

    最近参照antd-pro脚手架进行开发,因此接触到了umi-request. umijs/umi-request​github.com umi-request对fetch进行了封装,简化了api的使用 ...

  6. freeswitch源码阅读 之 sofia模块

    sofia模块在freeswitch中的位置非常重要, 所有的sip通话都和它有关, 那么我们就看一下该模块的执行流程. 一. 实现的功能: 1. sip注册; 2. 呼叫; 3. Presence; ...

  7. PHP yii 框架源码阅读(二) - 整体执行流程分析

    转载链接:http://tech.ddvip.com/2013-11/1384432766205970.html 一  程序入口 <?php// change the following pat ...

  8. vue data 值如何渲染_vue源码阅读复盘-watcher模块

    回顾目标 数据驱动视图: 理解watcher.dep.observer这三个对象之间的关系 这和VUE对象又有什么关系? 这和视图又有什么关系? 叙述过程 先彻底理解了一下VUE的简介,并写出了一份建 ...

  9. 十三、传智书城项目设计

    项目源代码及sql脚本 一.项目概述 近年来,随着Internet的迅速崛起,互联网已成为收集信息的最佳渠道并逐步进入传统的流通领域.于是电子商务开始流行起来,越来越多的商家在网上建起在线商店,向消费 ...

最新文章

  1. C#二进制格式与文件相互转换
  2. 【数据库】Kingbase金仓数据库工程维护简明手册
  3. 从浏览器输入URL到最终看到页面, 这其中经历了哪些过程 ?
  4. RDA8955中碰到的问题
  5. python easygui进度条_Python _easygui详细版
  6. 基于Spring Boot和Spring Cloud实现微服务架构学习
  7. liberOJ#6006. 「网络流 24 题」试题库 网络流, 输出方案
  8. 如何使用vue使同一个弹窗同时能实现添加和编辑
  9. 在Apache中隐藏Php文件后缀
  10. 将linux文件拷贝到windows,Windows与Linux系统拷贝文件之pscp的使用分享
  11. php7 aop,php之aop实践
  12. 将图像数据jpg,png等存储为npy/npz格式
  13. python卡方检验 scipy_Fisher 精确检验 与卡方检验
  14. codevs1260 快餐问题
  15. 十年磨一剑:大众凭借电池的革命性突破超越特斯拉
  16. Mac(3) Parallels Desktop 安装 Windows10专业版
  17. ETL示例解决方案 —— Sakila下载和基本配置 (笔记一)
  18. ROS2进行人脸识别face_recognition
  19. 为什么年轻人都喜欢互联网行业
  20. 【历史上的今天】5 月 6 日:第一台实际运行程序的计算机;Adobe 转型云端;首个非拉丁文网址出现

热门文章

  1. 万里汇WorldFirst个人和企业帐户注册教程(送$25+1%提现费)
  2. 数字图像处理与Python实现-图像降噪-指数型高通滤波
  3. 77道Spring面试题以及参考答案(2021年最新版),java开发项目经理面试题
  4. QWT--滚轮放大缩小和拖拽视窗
  5. 计算机硬件英语词汇,计算机硬件英语词汇
  6. 剪不断,理还乱——UML的四种关系
  7. vue移动端用什么数据可视化插件_前端必看的数据可视化入门指南
  8. 慧据价值 链接未来丨第八届数据技术嘉年华大会全议程精彩呈现
  9. ppt中如何合并流程图_PPT中流程图如何分支?
  10. Springboot快递管理系统1k61h计算机毕业设计-课程设计-期末作业-毕设程序代做