# ExpressEWebDemo **Repository Path**: YuHao9527/ExpressEWebDemo ## Basic Information - **Project Name**: ExpressEWebDemo - **Description**: 快递e栈demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2020-09-06 - **Last Updated**: 2021-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 快递e栈(javaWeb后台管理) 随着互联网和通信技术的高速发展,使电子商务迅速普及,网购成为人们购物的重要手段之一。据统计2019年双十一,11月11日~16日,受电商平台集中促销影响,业务量达全年高峰,6天内共处理邮(快)件23.09亿件。 疫情期间配送快递成了一件难事。疫情防控已经趋于常态化,很多小区出台了新的治安管理条例,开始禁止快递人员进入。这给民众收发快件造成了极大的不便,因进不去小区,快递人员只能在小区门口周边摆摊设点,也影响了小区周边环境。 除了快递,外卖也一样面临相同的问题。除了小区,写字楼、校园也面临着相同的问题。正是基于对疫情期间配送最后一公里深刻的认识,我们探索出一条解决之道,那就是社区快递e栈(类似快递柜)。 ## 一、项目需求 1. 管理员模块 - 管理员登录 - 管理退出 2. 快递管理模块 - 快递列表显示 - 快递新增 - 快递修改 - 快递删除 3. 用户管理模块 - 用户列表显示 - 用户新增 - 用户修改 - 用户删除 4. 快递员管理模块 - 快递员查询 - 快递员新增 - 快递员修改 - 快递员删除 5. 控制台显示模块 - 显示快件区域区分图 - 显示用户人数 - 显示快递员人数 - 显示总快件数 - 显示等待取件数量 ## 二、项目环境搭建 ### 1. 开发环境 - Java version:jdk11.0.6 - Web服务器:tomcat 8.5.34 - 数据库:MySQL 5.7.31 - 开发工具:IDEA - 相关jar包: ​ ### 2. MVC框架 #### 概述 首先我们搭建项目的MVC框架,封装一个Servlet,首先获取客户端请求的uri,然后进入mapping(映射器)中寻找到它对应的Method(方法),如果存在,就通过反射机制创建控制器的实例对象及获取它的Method方法并执行,然后将结果响应给客户端。 流程: `Servlet --> 映射器 --> 调用方法 --> 将结果返回` #### 具体实现 - 创建注解(Annotation) ``` 对于不同的响应结果不外乎文本和需跳转uri地址,我们可以创建两个注解类型: 1. 文本响应(ResponseBody): 被此注解添加的方法,会被用于处理请求,方法返回的内容会以文字(/JSON)的形式返回给客户端 2. 视图响应(ResponseView): 被此注解添加的方法,会被用于处理请求,方法返回的内容为uri地址,响应给客户端跳转到对应视图 注:创建一个枚举用于标识注解的类型:ResponseType ``` - 枚举(ResponseType) ```java package com.java.mvc; /** * 注解类型 **/ public enum ResponseType { TEXT, VIEW } ``` - 文本响应(ResponseBody) ```java package com.java.mvc; import java.lang.annotation.*; @Target(ElementType.METHOD)//用于注解方法 @Documented//包含在javadoc文档 @Retention(RetentionPolicy.RUNTIME)//标识注解在运行时可以通过注解访问 /** * 被此注解添加的方法,会被用于处理请求 * 方法返回的内容,会以文字(/JSON)形式返回到客户端 **/ public @interface ResponseBody { String value(); } ``` - 视图响应(ResponseView) ```java package com.java.mvc; import java.lang.annotation.*; @Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) /** * 被此注解添加的方法,会被用于处理请求 * 方法返回的内容为uri地址,会直接重定向跳转到对应视图界面 **/ public @interface ResponseView { String value(); } ``` - 创建映射器 ``` 映射对象(MVCMapping):JAVABean类,用于存储控制器对象及其被注解的方法 映射器:加载本地配置文件,通过反射机制获取控制器对象及方法并存入映射对象容器 ``` ```java package com.java.mvc; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * @ClassName HandlerMapping * @Description 映射器 * @Version 1.0 */ public class HandlerMapping { /** * 创建集合,用于存储请求uri地址与映射对象一一对应关系 */ private static Map data = new HashMap<>(); /** * @Description 获取映射对象 * @Param [uri] [请求uri地址] * @return com.java.mvc.HandlerMapping.MVCMapping */ public static MVCMapping get(String uri) { return data.get(uri); } /** * @Description 映射器(包含大量的uri地址与方法的对应关系) * @Param [is] 输入流获取本地配置文件 **/ public static void load(InputStream is) { //1. 加载本地配置文件 Properties ppt = new Properties(); try { ppt.load(is); } catch (IOException e) { e.printStackTrace(); } //2. 获取配置文件中描述的每一个的类 Collection values = ppt.values(); for (Object cla : values) { String className = String.valueOf(cla); try { //3. 加载配置文件中描述的每一个类 Class c = Class.forName(className); //4. 创建这个类的对象 Object obj = c.getConstructor().newInstance(); //5. 获取这个类的所有方法 Method[] methods = c.getMethods(); for (Method m : methods) { //6. 获取这个方法的所有注解 Annotation[] as = m.getAnnotations(); if (as != null) { for (Annotation annotation : as) { //7.判断该方法的注解类型 if (annotation instanceof ResponseBody) { //说明此方法,用于返回字符串给客户端 //创建映射对象并将其与对应请求uri地址存入Map集合 MVCMapping mapping = new MVCMapping(obj, m, ResponseType.TEXT); MVCMapping put = data.put(((ResponseBody) annotation).value(), mapping); //存储失败,抛出异常请求地址重复 if (put != null) { throw new RuntimeException("请求地址重复:" + ((ResponseBody) annotation).value()); } }else if (annotation instanceof ResponseView) { //说明此方法,用于返回视图给客户端 MVCMapping mapping = new MVCMapping(obj, m, ResponseType.VIEW); MVCMapping put = data.put(((ResponseView) annotation).value(), mapping); if (put != null) { throw new RuntimeException("请求地址重复:" + ((ResponseView) annotation).value()); } } } } } } catch (Exception e) { e.printStackTrace(); } } } /** * 用于存储控制器对象及其被注解的方法 **/ public static class MVCMapping { private Object obj; private Method method; private ResponseType type; public MVCMapping() { } public MVCMapping(Object obj, Method method, ResponseType type) { this.obj = obj; this.method = method; this.type = type; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public ResponseType getType() { return type; } public void setType(ResponseType type) { this.type = type; } } } ``` - 创建Servlet ``` 1. 首先我们需要创建一个本地配置文件,用于存储所有控制器的uri地址,这里我用.properties存储 2. 在Servler的初始化方法中加载该配置文件 3. 获取客户端的请求uri,然后在映射器中查找其对应的映射对象,然后执行对应方法,这里需要判断该方法的注解类型来对其返回的结果做相应处理 ``` - 本地配置文件(application.properties) ```properties # 这里用于配置每一个用于请求的控制器,每一个控制器中可能包含0-n个用于处理请求的方法 # 管理员登录控制器 a=com.java.controller.AdminController # 快递管理控制器 b=com.java.controller.ExpressController ``` - DispatcherServlet ```java package com.java.mvc; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebInitParam; 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.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @ClassName DispatcherServlet * @Description Servlet容器 * @Date 2020/9/7 17:39 * @Version 1.0 */ @WebServlet( value = "*.do",//对所有.do的请求做响应 initParams = { //配置初始化参数,存入本地配置文件uri @WebInitParam( name = "contentConfigLocation", value = "application.properties" ) }, //服务器启动时加载该Servlet loadOnStartup = 0 ) public class DispatcherServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { //1. 获取本地文件uri地址 String path = config.getInitParameter("contentConfigLocation"); //2. 通过类加载器获取本地配置文件输入流 InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path); //3,调用映射器的静态方法load(),通过反射机制获取所有控制器被注解的方法并存入Map集合 HandlerMapping.load(is); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 获取用户请求的uri /xx.do String uri = req.getRequestURI(); //2. 通过映射器查找是否存在对应映射对象 HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri); if (mapping == null) { resp.sendError(404, "自定义MVC:映射地址不存在"+uri); return; } //3,从映射对象中获取控制器对象、方法、注解类型 Object obj = mapping.getObj(); Method method = mapping.getMethod(); Object result = null; try { //4. 执行方法并用Object接收返回结果 result = method.invoke(obj, req, resp); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } //根据注解类型判断该方法返回结果 switch (mapping.getType()) { case TEXT: //文本响应,将文本结果返回给客户端 resp.getWriter().write((String) result); break; case VIEW: //视图响应,重定向至对应视图界面 resp.sendRedirect((String) result); break; } } } ``` ### 3. 工具类 #### JDBC数据库工具类 ``` 本项目使用的数据库是MySQL,使用Druid数据库连接池连接数据库,首先我们需要创建一个配置文件,然后创建一个工具类用于获取一个连接操作数据库。 配置文件里存放连接池的初始化设置,这里我们先为本项目创建一个数据库expressWeb sql语句: CREATE DATABASE IF NOT EXISTS expressWeb DEFAULT charset=utf8mb4; ``` ##### 配置文件(druid.properties) ```properties #数据库连接地址 url=jdbc:mysql://localhost:3306/expressWeb?useUnicode=true&characterEncoding=utf-8 #账户 username=root #密码 password= #驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别相应的driverClassName driverClassName=com.mysql.jdbc.Driver #初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次 getConnection时 initialSize=5 #最大连接池数量 maxActive=10 #最小连接池数量 minIdle=5 #获取连接时最大等待时间,单位毫秒。 maxWait=3000 ``` ##### DruidUtil ```java package com.java.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * @ClassName HandlerMapping * @Description Druid数据库连接池工具类 */ public class DruidUtil { private static DataSource ds; static{ try { //加载本地配置文件,通过连接池工厂类创建一个连接池 Properties ppt = new Properties(); ppt.load(DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties")); ds = DruidDataSourceFactory.createDataSource(ppt); } catch (Exception e) { e.printStackTrace(); } } /** * 从连接池中取出一个连接给用户 * @return 连接对象 */ public static Connection getConnection(){ try { return ds.getConnection(); } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } /** * 释放资源(连接、sql执行环境、结果集等) * @return */ public static void close(Connection conn, Statement state, ResultSet res){ try { if (res != null) { res.close(); } } catch (Exception throwables) { throwables.printStackTrace(); } try { state.close(); } catch (Exception throwables) { throwables.printStackTrace(); } try { conn.close(); } catch (Exception throwables) { throwables.printStackTrace(); } } } ``` #### 日期格式化工具类 ```java package com.java.util; import java.text.SimpleDateFormat; import java.util.Date; /** * @ClassName DateFormatUtil * @Description 日期格式化工具类 */ public class DateFormatUtil { private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 格式化日期 * @return 格式化后的日期字符串 */ public static String formatDate(Date date) { return format.format(date); } } ``` #### JSON格式转换工具类 ```java package com.java.util; import com.google.gson.Gson; /** * @ClassName JSONUtil * @Description JSON格式转换工具类(Gson工具) */ public class JSONUtil { private static Gson g = new Gson(); /** * 将文本对象转为JSON格式 * @return JSON */ public static String toJSON(Object obj) { return g.toJson(obj); } } ``` #### JavaBean类 ```java package com.java.bean; /** * @ClassName Message * @Description 消息类,用于存储服务器返回给客户端的响应 */ public class Message { /** * @Description 状态码,0表示成功,-1表示失败 */ private int status; /** * @Description 消息内容 */ private String content; /** * @Description 消息所携带数据 */ private Object data; public Message() { } public Message(int status) { this.status = status; } public Message(int status, String content, Object data) { this.status = status; this.content = content; this.data = data; } public Message(int status, String content) { this.status = status; this.content = content; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } } ``` #### 过滤器 ``` 字符集过滤器,防止乱码 ``` ### 2. Layer前端弹出层组件 ``` 为了方便与用户交互,这里我们使用了Layer web弹出层组件,它为我们免费提供很多弹出层模式,能够让我们的页面拥有丰富友好的操作体验。 Layer官方地址:https://layer.layui.com/ 导入: 首先我们从官网下载最新版本的Layer组件,然后将其将其解压,接着将layer文件夹复制到你JavaWeb项目的web文件夹下即可。 使用方式: ``` ## 三、功能实现 ### 1. 管理员登录 #### 子模块 ##### 管理员登录 - 简单的登录判断,登录成功后跳转页面并记录登录时间和登录ip地址 ##### 管理员退出 - 退出登录,退出至登录页面 #### 编写流程 ##### 编写数据库表格(eadmin) ```mysql CREATE TABLE eadmin ( id INT UNSIGNED auto_increment, username VARCHAR ( 18 ) UNIQUE, PASSWORD VARCHAR ( 18 ), loginTime TIMESTAMP, loginIp VARCHAR ( 32 ), createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ( `id` ) ); ``` ##### 编写DAO ##### 编写Service ##### 编写Controller #### api文档 1. 登录 ``` 请求地址:admin/login.do 参数列表:username,password 返回的格式示例: { status:0, content:"登录成功" } ``` 2. 退出 ``` 请求地址:admin/quit.do 参数列表:username 返回格式示例: { status:0, content:"退出成功" } ``` ### 2. 快递管理 #### 子模块 ##### 快递列表显示 - 分页查询的列表 ##### 新增快递 - 用户输入内容,后台接收参数,向数据库存储 ##### 删除快递 - 用户输入快递单号查询到快递信息 - 浏览快递信息的最后,可以点击删除按钮 ,删除快递 ##### 修改快递 - 用户输入快递单号查询到快递信息 - 浏览(可修改)快递信息的最后,可以点击确认按钮 ,确认修改快递 #### 编写的流程 ##### 创建数据库表格(express) ```mysql CREATE TABLE express ( id INT UNSIGNED auto_increment, number VARCHAR ( 64 ) UNIQUE, username VARCHAR ( 32 ), userPhone VARCHAR ( 32 ), company VARCHAR ( 32 ), code VARCHAR ( 32 ) UNIQUE, inTime TIMESTAMP, outTime TIMESTAMP, status INT DEFAULT 0, sysPhone VARCHAR ( 32 ), PRIMARY KEY ( `id` ) ); ``` ##### 编写DAO ##### 编写Service ##### 编写Controller ##### 前后端的交互 前端发起ajax→DispatcherServlet→Controller→Service→DAO→数据库 **标准流程** - 前端发起ajax ``` $("按钮选择器").click(function(){ //1. 先使用layer,弹出load(提示加载中...) var windowId = layer.load(); //2. ajax与服务器交互 $.post("服务器地址",参数JSON,function(data){ //3. 关闭load窗口 layer.close(windowId); //4. 将服务器回复的结果进行显示 layer.msg(data.result); },"JSON"); }); ``` - 编写Controller,用于处理ajax的请求 - 在Controller中调用service处理 - 处理完毕, 根据service返回的结果,给ajax返回 #### api文档 ##### 快递 部分 ###### 1. 用于获取控制台所需的快递数据 ``` 请求地址:express/console.do 参数列表:无 返回的格式示例: { status:0, reuslt:"获取成功", data:[ {//全部的快递 size:1000,//快递总数 day:100//今日新增 },{//待取件快递 size:500,//待取件数 day:100//今日新增 } ] } ``` ###### 2. 快件列表(分页) ``` 请求地址:express/findAll.do 参数列表: 1. limit: 值:0,表示开启分页(默认) 值:1,表示查询所有 2. offset: 值:数字,表示SQL语句起始索引 3. pageNumber: 值:数字,表示获取的快递数量 返回的格式示例: ``` ###### 3.根据单号查询快递信息 ``` 请求地址:express/findByNumber.do 参数列表: 1. number:快递单号 返回的格式示例: ``` ###### 4. 根据取件码查询快递信息 ``` 请求地址:express/findByCode.do 参数列表: 1. code:取件码 返回的格式示例: ``` ###### 5. 根据用户的手机号,查询快递信息 ``` 请求地址:express/findByUserPhone.do 参数列表: 1. phoneNumber:手机号码 2. status: 值:0表示查询待取件的快递(默认) 值:1表示查询已取件的快递 值:2表示查询用户的所有快递 返回的格式示例: ``` ###### 6.根据录入人的手机号,查询快递信息(快递员/柜子的历史记录) ``` 请求地址:express/findBySysPhone.do 参数列表: 1. sysPhone:手机号码 返回的格式示例: ``` ###### 7.进行快递数量的排序查询(用户表) ``` 请求地址:express/lazyboard.do 参数列表: 1. type: 值:0,表示查询总排名 值:1,表示查询年排名 值:2,表示查询月排名 返回的格式示例: ``` ###### 8.快件录入 ``` 请求地址:express/insert.do 参数列表: 1. number:快递单号 2. company:快递公司 3. username:收件人姓名 4. userPhone:收件人手机号码 录入成功返回的格式示例: 录入失败返回的格式示例: ``` ###### 9. 修改快递信息 ``` 请求地址:express/update.do 参数列表: 1. id:要修改的快递id 2. number:新的快递单号 3. company:新的快递公司 4. username:新的收货人姓名 5. userPhone:新的收件人手机号码,(手机号码更新,重新生成取件码,并发送短信) 6. status:新的快递的状态 返回的格式示例: ``` ###### 10. 根据id删除快递信息 ``` 请求地址:express/delete.do 参数列表: 1. id: 要删除的快递的id 返回的格式示例: ``` ###### 11.确认取件 ``` 请求地址:express/updateStatus.do 参数列表: number:要更改状态为已取件的快递单号 返回的格式示例: ``` ### 3. 用户的管理 #### 子模块 ##### 用户列表显示 - 分页查询的列表 ##### 新增用户 - 用户输入内容,后台接收参数,向数据库存储 ##### 删除用户 - 输入用户手机号码查询到用户信息 - 浏览用户信息的最后,可以点击删除按钮 ,删除用户 ##### 修改快递 - 用户输入快递员手机号码查询到用户信息 - 浏览(可修改)用户信息的最后,可以点击确认按钮 ,确认修改用户 #### 编写的流程 ##### 创建数据库表格(courier) ```mysql CREATE TABLE user ( id INT UNSIGNED auto_increment, nickName VARCHAR ( 32 ), phone VARCHAR ( 32 ) UNIQUE, idCard VARCHAR ( 32 ) UNIQUE, password VARCHAR ( 32 ), status INT UNSIGNED DEFAULT 0, signUpTime TIMESTAMP, loginTime TIMESTAMP, PRIMARY KEY ( `id` ) ) ``` ### 4. 快递员管理 #### 子模块 ##### 快递员列表显示 - 分页查询的列表 ##### 新增快递员 - 用户输入内容,后台接收参数,向数据库存储 ##### 删除快递员 - 用户输入快递员手机号码查询到快递员信息 - 浏览快递员信息的最后,可以点击删除按钮 ,删除快递员 ##### 修改快递 - 用户输入快递员手机号码查询到快递员信息 - 浏览(可修改)快递员信息的最后,可以点击确认按钮 ,确认修改快递员 #### 编写的流程 ##### 创建数据库表格(courier) ```mysql CREATE TABLE courier ( id INT UNSIGNED auto_increment, name VARCHAR ( 32 ), phone VARCHAR ( 32 ) UNIQUE, idCard VARCHAR ( 32 ) UNIQUE, password VARCHAR ( 32 ), numberDispatch INT UNSIGNED DEFAULT 0, status INT UNSIGNED DEFAULT 0, signUpTime TIMESTAMP, loginTime TIMESTAMP, PRIMARY KEY ( `id` ) ) ``` ### 5. 控制台显示 # 快递e栈(微信端)