java-web实战1

通过项目的实现,贯穿前面所学的知识点,能够灵活应用所学知识,解决实际问题。知识点是固定的,但是实现思路是各有千秋的。

学习重点:

1.知识点的项目中的应用

2.功能实现的逻辑思路

知识点复习

1.数据库

2.JDBC

3.html

4.servlet

5.JSP+EL+JSTL

6.AJAX

7.多表操作

项目实现过程

1.分析静态页面,根据html或需求创建数据库

2.填写测试数据

3.创建项目,包结构(bean,dao.impl,servlet,service.impl,util),修改html页面为jsp

html->jsp

       (1)在HTML中添加page指令

       (2) 将html的后缀改成jsp

4.填写内容(注意填写顺序)

bean:属性,封装方法,无参构造,全参构造

表名=类名 ,列名=属性名

dao包:操作方法的接口,命名:实体类名+Dao,

​ Dbutils(属性文件,*.properties)

impl包:命名: 接口名+Impl

​ 实现接口,继承Dbutils

service.impl

​ service定义的是接口,接口中的方法和dao层接口中的方法一致

​ impl : 这层的实现类主要负责调取dao层方法

servlet:

​ //1.接受参数

​ //2.调取service层的方法,service又在调取dao层的方法

​ //3.根据结果跳转页面

核心:jsp页面负责发送请求和展示数据

代码优化

(1)通用分页

​ 定义分页工具类,简化分页代码

(2)优化servlet

​ 实现同一个模块的请求走同一个servlet,减少定义servlet的个数

注意点

1. 登录功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1.接收参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//2.调取service
UsersService usersService = new UsersServiceImpl();
Users users = usersService.login(username, password);
//3.跳转页面
if(users==null){
//以弹窗方式提示用户,登录失败
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.println("<script>location.href='login.jsp';alert('用户名或密码不正确');</script>");
}else{
//跳转到主页面
//保存用户信息
req.getSession().setAttribute("u1",users);
resp.sendRedirect("index.jsp");
}

2. 退出功能

1
2
3
4
5
6
7
8
9
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//清除session
req.getSession().invalidate();
//跳转页面
//resp.sendRedirect("/login.jsp");
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("<script>alert('退出成功');top.location.href='login.jsp';</script>");
}

注意:此处的页面跳转没有采用resp.sendRedirect("login.jsp")的重定向方法,而是使用给页面插入js语句的方式

因为:image-20210430162129594

页面由三部分组成,退出按钮在head部分,只能对head部分进行跳转!!!

对于top.location.href='login.jsp',没加top的话就和重定向方法一样,加了top就是回到整个框架的最外层

3. 获取学生列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@WebServlet(urlPatterns = "/Educational/student/getStudentList")
public class GetStudentServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取参数
//1.1 模糊查条件
String stuname = req.getParameter("stuname");
System.out.println(stuname);
String stuno = req.getParameter("stuno");
String sex = req.getParameter("sex");
//1.2 分页数据 limit 开始位置,显示条数
//页码值(当前页码值)
String pageIndex = req.getParameter("pageIndex");
//如果页面没有传入pageIndex的值,则认为默认查询第一页
int index=pageIndex==null?1:Integer.parseInt(pageIndex);
//2.调取service方法
StudentService service=new StudentServiceImpl();
int usex=(sex==null||sex.length()==0?-1:Integer.parseInt(sex));
List<Student> students =
service.getStudents(stuname,stuno,usex,index,5);
//获取总页数=总条数%每页显示的条数>0?总条数/每页显示条数+1:总条数/每页显示条数
int total = service.total(stuname, stuno,usex);//总条数
int totalPages=total%5>0?total/5+1:total/5; //总页数

//3.跳转页面
//如果后台想给前台传数据,是一定要在后台存值的
req.setAttribute("stulist",students);
//存储模糊查条件
req.setAttribute("stuname",stuname);
req.setAttribute("stuno",stuno);
req.setAttribute("sex",sex);
//存储分页数据
req.setAttribute("index",index);
req.setAttribute("size",5);
req.setAttribute("total",total);
req.setAttribute("totalPages",totalPages);
req.getRequestDispatcher("list.jsp").forward(req,resp);
}
}

为什么这里的request地址用路径列表来表示

答:image-20210430200926828

相对路径/Educational/student对url进行了自动补全,此时再要用转发或者重定向跳转到list.jsp时就只需要直接写本地路径下的页面!!

4. 模糊查询+分页(Dao层)

在简单的数据库查询基础上,用StringBuffer来拼接sql语句,如果有查询信息就拼接到sql语句中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public List<Student> getStudents(String name,String stuno,int sex,int pageIndex,int pageSize) {
List list= new ArrayList<Student>();
List params=new ArrayList();
try {
StringBuffer sqlbuf=new StringBuffer(" select * from student where 1=1 and state!=4 ");
if(name!=null&&name.length()>0){
sqlbuf.append(" and stuname like ? ");
params.add("%"+name+"%");
}
if(stuno!=null&&stuno.length()>0){
sqlbuf.append(" and stuno=? ");
params.add(stuno);
}
if(sex!=-1){
sqlbuf.append(" and sex=? ");
params.add(sex);
}
//分页
sqlbuf.append(" limit ?,?"); //1 5 limit 0,5
// limit (pageIndex-1)*pageSize,pageSize;
params.add((pageIndex-1)*pageSize);
params.add(pageSize);

resultSet = query(sqlbuf.toString(), params);
while(resultSet.next()){
Student student = new Student();
student.setStuId(resultSet.getInt("stuid"));
student.setStuNo(resultSet.getString("stuno"));
student.setStuName(resultSet.getString("stuname"));
student.setSex(resultSet.getInt("sex"));
student.setPhone(resultSet.getString("phone"));
student.setProfession(resultSet.getString("profession"));
student.setRegDate(resultSet.getDate("regdate"));
//补全所有的列
list.add(student);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
closeAll();
}
return list;
}

5. 新增学生

image-20210503151202615

5.1 年级信息

Grade类:gradeId和gradeName

Grade类的Dao层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class GradeDaoImpl extends DBUtils implements GradeDao {
@Override
public List<Grade> getList() {
List gs=new ArrayList();
try {
String sql="select * from grade";
resultSet = query(sql, null);
while(resultSet.next()){
Grade grade = new Grade();
grade.setGradeId(resultSet.getInt("gradeid"));
grade.setGradeName(resultSet.getString("gradename"));
gs.add(grade);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
closeAll();
}
return gs;
}
}

web层

1
2
3
4
5
6
7
8
9
10
11
@WebServlet(urlPatterns = "/Educational/student/getGradeList")
public class GradeListServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//查询年级列表的方法
GradeService service=new GradeServiceImpl();
List<Grade> list = service.getList();
req.setAttribute("glist",list);
req.getRequestDispatcher("add.jsp").forward(req,resp);
}
}

如何将gradeName放到前端的select标签里面展示

1
2
3
4
5
<select name="gid">
<c:forEach items="${glist}" var="g">
<option value="${g.gradeId}">${g.gradeName}</option>
</c:forEach>
</select>

通过java标准标签库的方式对列表进行循环,将所有年级对象的名称和对应的id填入,方便学生表和年级表的联查

5.2 学生对象

直接将前端提交的信息存储到数据库中,通过executeUpdate()方法的返回值(受影响行数)判断是否新增成功,新增成功则跳转至学生显示页面,没成功则弹框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@WebServlet(urlPatterns = "/Educational/student/addStu")
public class AddStuServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String stuNo = req.getParameter("stuNo");
String stuname = req.getParameter("stuName");
String gid = req.getParameter("gid");
String sex = req.getParameter("sex");
String email = req.getParameter("email");
String phone = req.getParameter("phone");
String registered = req.getParameter("registered");
String address = req.getParameter("address");
String politics = req.getParameter("politics");
String idnumber = req.getParameter("idNumber");
String profession = req.getParameter("profession");
String introdction = req.getParameter("introdction");
//调取service
StudentService service=new StudentServiceImpl();
//将参数封装到学生对象中
Student student = new Student();
student.setStuNo(stuNo);
student.setStuName(stuname);
student.setGid(Integer.parseInt(gid));
student.setSex(Integer.parseInt(sex));
student.setEmail(email);
student.setPhone(phone);
student.setRegistered(registered);
student.setAddress(address);
student.setPolitics(politics);
student.setIdNumber(idnumber);
student.setProfession(profession);
student.setIntrodction(introdction);
int i = service.insertStu(student);
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
if(i>0){
writer.println("<script>alert('新增成功');location.href='/Educational/student/getStudentList'</script>");
}else{
writer.println("<script>alert('新增失败');location.href='/Educational/student/getGradeList'</script>");
}

}
}

6. 主键查询对学生信息进行修改

通过“修改”项的a标签给后台传输主键id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<c:forEach items="${stulist}" var="stu" >
<tr id="product1">
<!--显示数据库信息-->
<td align="center">${stu.stuNo}</td>
<td align="center">${stu.stuName}</td>
<td align="center">${stu.sex==1?'男':'女'}</td>
<td align="center">${stu.phone}</td>
<td align="center">${stu.profession}</td>
<td align="center">${stu.regDate}</td>
<td align="center">
<!--在get请求中传输数据id-->
<a href="/Educational/student/findbyid?sid=${stu.stuId}">修改</a>
<a href="/Educational/student/deletebyid?sid=${stu.stuId}">删除</a>
</td>
</tr>
</c:forEach>

通过主键id获取学生信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebServlet("/Educational/student/findbyid")
public class FindByIdServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String sid = req.getParameter("sid");
//根据主键查询学生信息
StudentService service=new StudentServiceImpl();
Student student = service.findById(Integer.parseInt(sid));

//查询年级列表
GradeService gradeService=new GradeServiceImpl();
List<Grade> list = gradeService.getList();

req.setAttribute("glist",list);
req.setAttribute("stu",student);
req.getRequestDispatcher("edit.jsp").forward(req,resp);

}
}

点击修改后需要先显示学生的原本信息:(这里仅以性别和年级为例)

1
<tr>    <td>班级<span style="color:red">*</span>:</td>    <td>        <select name="gid">            <c:forEach items="${glist}" var="g">                <option value="${g.gradeId}"  ${g.gradeId==stu.gid?'selected':''}>${g.gradeName}</option>            </c:forEach>        </select>    </td></tr><tr>    <td>性别<span style="color:red">*</span>:</td>    <td>        <input type="radio" name="sex" ${stu.sex==1?'checked':''} value="1" />男        <input type="radio" name="sex" ${stu.sex==0?'checked':''} value="0"/>女    </td></tr>

7. 在6的基础上进行修改

这里就和5新增学生差不多了,通过在sql语句中用加入id条件定位学生信息,对其进行update操作

8. 删除学生

和6中修改请求的提交内容相同

1
<a href="/Educational/student/deletebyid?sid=${stu.stuId}">删除</a>
  • 原理还是通过stuid删除数据库中的学生信息,将删除学生的state属性值设为4,通过受影响行数判断是否删除成功

    1
    public int delStu(String sid) {        int update = 0;        try {            String sql="update student  set state=? where stuid=?";            List params=new ArrayList();            //通过枚举的方式记录学生状态值,下节介绍            params.add(StudentEnum.DELETE.type);            params.add(sid);            update = update(sql, params);        } catch (Exception e) {            e.printStackTrace();        } finally {            closeAll();        }        return update;    }
  • 删除后重新跳转回显示界面,此时应对之前的搜寻学生的拼接sql语句(getStudents()total())进行更新

    1
    StringBuffer sqlbuf=new StringBuffer(" select count(*) from student where 1=1  and state!=4  ");

    保证只显示未删除的数据(此时删除数据仍存在于数据库)

9. 枚举值保存学生状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1表示在读  2 休学   3 退学  4  删除
public enum StudentEnum {
READING(1,"在读"),
SUSPENSION(2,"休学"),
DROPOUT(3,"退学"),
DELETE(4,"删除");

public final Integer type;
public final String value;
StudentEnum(Integer type,String value){
this.type=type;
this.value=value;
}
}

10. 使用过滤器处理乱码

详见filter章节,对所有的请求和响应统一设置编码

11. 多个请求走同一个servlet

对于学生操作的servlet类很多,如何可以将这些方法走同一个servlet呢

对每一个request请求的内容做些修改

1
2
3
<a href="/Educational/student/findbyid?sid=${stu.stuId}">修改</a>
<!--修改-->
<a href="/Educational/student/studentServlet?method=update&sid=${stu.stuId}">修改</a>

此时就可以将AddStuServlet、DeleteByIdServlet、GetStudentServlet、FindByIdServlet 、UpdateStuServlet可以合并为一个StudnetServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getParameter("method");
if("insert".equals(method)){
insert(req,resp);
}else if("update".equals(method)){
update(req, resp);
}else if("findbyid".equals(method)){
findbyid(req, resp);
}else if("delete".equals(method)){
deletestu(req, resp);
}else{
findlist(req, resp);
}
}

12. 分页工具类

和学生的枚举类类似,在代码直接出现state的数字或者说对每页size的定义都是很难让人理解的,通过一个额外的类对其进行整理是能极大的帮助数据的调整和别人的理解!!!

1
public class PageUtil {    //1.页码值 2.显示条数 3.查询结果列表 4,总条数  5.总页数    private Integer pageIndex;    private Integer pageSize=5;    private List dataList;    private Integer total;    private Integer totalPages;        public Integer getPageIndex() {        return pageIndex;    }    public void setPageIndex(Integer pageIndex) {        this.pageIndex = pageIndex;    }    public Integer getPageSize() {        return pageSize;    }    public void setPageSize(Integer pageSize) {        this.pageSize = pageSize;    }    public List getDataList() {        return dataList;    }    public void setDataList(List dataList) {        this.dataList = dataList;    }    public Integer getTotal() {        return total;    }    public void setTotal(Integer total) {        this.total = total;    }    public Integer getTotalPages() {        return total%pageSize>0? total/pageSize+1:total/pageSize;    }    public void setTotalPages(Integer totalPages) {        this.totalPages = totalPages;    }}

此时就可以对前端传过来的数据进行封装,后端存储的数据也可以通过对象传到前端(显示的总数total会发生变化)

项目进阶-多表

1. 数据库的多表合并

表:grade,menu,middle(角色和菜单的中间表,关联两者),role,student,users

用户表:通过外键关系(roleid)和角色表关联

image-20210517094427779

角色表:状态1表示可用,0表示禁用

image-20210517094326004

菜单表:upmenuid表示菜单等级,0表示最上级菜单,1表示教务中心的2级菜单,2表示权限管理的2级菜单

image-20210517094542433

中间表:roleid为1表示管理员权限,对应菜单表的所有信息,roleid为2表示班主任,对应教务中心和学生管理两个信息

image-20210517094911523

2. 实体类创建

注意在实体类中体现多表关系:

  • 用户表:一个用户对应一个角色

    1
    class Users{    private Integer userId;    ...    private Role role;}
  • 角色表:一个角色可以有多个用户,一个角色也可以管理多个表单

    1
    class Role{    private Integer roleId;    ...    private List<Users> usersList;    private List<Menu> menuList;}
  • 菜单表:一个菜单可以由多个角色的管理者访问,一个菜单可能包含多个二级菜单

    1
    class Menu{    private Integer menuId;    ...    private List<Role> roleL;    private List<Menu> secondMenuList; }

3. 用户列表展示

和上面的学生列表的展示方式差不多,通过在url中添加属性method,只需要一个servlet进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
WebServlet(urlPatterns = {"/power/user/users"})
public class UsersServlet extends HttpServlet {
private UsersService usersService=new UsersServiceImpl();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getParameter("method");
if("select".equals(method)){
select(req,resp);
}
}

//查询数据(分页)
protected void select(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.接收参数(分页参数+模糊查参数)
String index = req.getParameter("index");
int pageIndex=(index==null||index.length()==0)?1:Integer.parseInt(index);
//2.调取service方法(1.查询数据列表的方法 2.查询总条数的方法)
PageUtil pageUtil = new PageUtil();
List<Users> usersList = usersService.getUsersList(pageIndex, pageUtil.getPageSize());
int total = usersService.total();
pageUtil.setTotal(total);
pageUtil.setPageIndex(pageIndex);
pageUtil.setDataList(usersList);
//3.存值跳页面
req.setAttribute("pi",pageUtil);
req.getRequestDispatcher("list.jsp").forward(req,resp);
}
}

Dao的逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@Override
public Users login(String username, String password) {
Users users= null;
try {
String sql="select * from users where loginname=? and password=?";
ArrayList arrayList = new ArrayList();
arrayList.add(username);
arrayList.add(password);
resultSet = query(sql, arrayList);
if(resultSet==null){
return null;
}
while(resultSet.next()){
users=new Users();
users.setLoginName(username);
users.setRealName(resultSet.getString("realname"));
users.setUserId(resultSet.getInt("userid"));
users.setRoleId(resultSet.getInt("roleid"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
closeAll();
}


return users;
}

@Override
public List<Users> getUsersList(int pageIndex, int pageSize) {
List<Users> usersList=new ArrayList<Users>();
try {
String sql="select userid,loginname,realname,rolename from users u,role r where u.roleid=r.roleid limit ?,?";
List params=new ArrayList();
params.add((pageIndex-1)*pageSize);
params.add(pageSize);

resultSet = query(sql, params);
while(resultSet.next()){
//1.取出各表的数据
Users users = new Users();
users.setUserId(resultSet.getInt("userid"));
users.setLoginName(resultSet.getString("loginname"));
users.setRealName(resultSet.getString("realname"));

Role role = new Role();
role.setRoleName(resultSet.getString("rolename"));
//2.建立关系
users.setRole(role);
usersList.add(users);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
closeAll();
}
return usersList;
}

@Override
public int total() {
int total=0;
try {
String sql="select count(1) from users u,role r where u.roleid=r.roleid ";


resultSet = query(sql, null);
while(resultSet.next()){
total=resultSet.getInt(1);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
closeAll();
}
return total;
}

4. 新增人员思路

image-20210520101959894

5. 角色的新增,删除,修改

  • 新增:先将菜单列表的内容添加到html页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    @WebServlet(urlPatterns = {"/power/role/roles"})
    public class RoleServlet extends HttpServlet {
    private RoleService roleService=new RoleServiceImpl();
    private MenuService menuService=new MenuServiceImpl();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getParameter("method");
    if("select".equals(method)){
    select(req,resp);
    }else if("selectmenus".equals(method)){
    selectmenus(req,resp);
    }else if("insert".equals(method)){
    insert(req,resp);
    }
    }
    //新增角色
    protected void insert(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String rolename = req.getParameter("rolename");
    String state = req.getParameter("state");
    String[] menuids = req.getParameterValues("menuid");
    int i = roleService.insertRole(rolename, state, menuids);
    if(i>0){
    resp.sendRedirect("/power/role/roles?method=select");
    }else{
    resp.sendRedirect("/power/role/roles?method=selectmenus");
    }

    }
    //查询菜单列表
    protected void selectmenus(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.接收参数
    //2.调取service方法
    List<Menu> menuList = menuService.getMenuList();
    req.setAttribute("menulist",menuList);
    req.getRequestDispatcher("add.jsp").forward(req,resp);

    }

    //查询数据(分页)
    protected void select(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.接收参数(分页参数+模糊查参数)
    String index = req.getParameter("index");
    int pageIndex=(index==null||index.length()==0)?1:Integer.parseInt(index);
    //2.调取service方法(1.查询数据列表的方法 2.查询总条数的方法)
    PageUtil pageUtil = new PageUtil();
    List<Role> roleList = roleService.getRoleList(pageIndex, pageUtil.getPageSize());
    int total = roleService.total();
    pageUtil.setTotal(total);
    pageUtil.setPageIndex(pageIndex);
    pageUtil.setDataList(roleList);

    //3.存值跳页面
    req.setAttribute("pi",pageUtil);
    req.getRequestDispatcher("list.jsp").forward(req,resp);
    }
    }
  • 修改

    image-20210520102720259

  • 删除

    注意点:先删除中间表,去除主外键关系后再删除角色表

6. 不同角色的菜单进行动态更新

Dao层的用户类中,通过三表联查建立关系后的查询结果就是和用户权限菜单对应的结果,此时将菜单传到前端就可以了

3. 快递站项目

整体框架的建立/整个项目的数据流向:

  • MVC包:迎宾/前台

    • ResponseType(枚举类型):TEXT/VIEW,记录方法的处理对象是文本还是图像
    • ResponseBody、ResponseView(注释):通过注释名区分ResponseType,通过注释的value对应请求的url

    • DispatcherServlet类(HttpServlet实现类):负责前端请求的接收和通信,在init方法中对初始化参数(application.properties文件)转换为输入流,通过反射获取其中的操作类(Controller)的带注释的操作方法

    • HandlerMapping类(映射池):通过参数文件(application.properties接收操作类的类名),通过操作类的类名+反射获取到:方法的注释value(对应的请求url字符串)、方法(Method m)、ResponseType。用内部类MappingMVC接收(方法所在类Object,方法对象,响应类型),用Map<String,MVCMapping> data将请求URL和MappingMVC对应起来!!!!

    此时,通过HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);我就可以在servlet中获取到执行响应请求的方法。

  • controller包:包厢部/住房部主管

    • AdminController和ExpressController:两个类分别保存了对应管理员请求和快递请求的处理方法;

    此时的controller为了获取到前端请求的数据,需要访问数据库,这是一个曲折个过程。。。

    首先访问service包

  • service包:服务员

    • AdminService和ExpressService:包含了对数据库操作的意愿,但是它还碰不到数据库

    此时的service中的方法只是在调用dao包的抽象方法,具体的数据库操作还是得有dao层进行

  • Dao包:厨子

    • impl包:实现类
    • 抽象类
  • 工具(util包)和过滤器(filter包):

    • DateFormatUtil:时间格式转换
    • DruidUtil:德鲁伊连接池
    • JSONUtil:字符串转JSON
    • RandomUtil:随机数生成
    • UserUtil:通过session获取用户信息

3.1 编写MVC框架

核心:注解和反射

  • MVC框架的意义:

    以注册,登录,用户信息获取等几个功能为例,每个功能的实现可能都需要一个servlet类进行处理。框架的存在就是在一个类中对所有功能的方法进行整合!!!

  • 大概框架:

    在xml配置文件中对*.do的请求配置进入Servlet,在Servlet的init方法中:加载配置文件、建立映射地址的池(通过映射器来调用方法)

image-20210523093140435

  • 后台src中的文件:

    application.properties:配置用于处理请求的类,每个类可能包含0-n个用于处理请求的方法

3.1.1 具体代码

  • xml文件的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.kaikeba.mvc.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contentConfigLocation</param-name>
    <param-value>application.properties</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
    </servlet-mapping>
  • Servlet需要处理的信息:(初始化init后就是要对输入流is中的参数进行拆分获取【遍历类,类的反射对象,类中所有方法,按照方法注解的请求地址封装方法。。。】,处理完后才进行service编写)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public class DispatcherServlet extends javax.servlet.http.HttpServlet {
    //初始化,加载配置文件(通过在xml中给配置文件起的名字获取)
    @Override
    public void init(ServletConfig config) throws ServletException {
    String path = config.getInitParameter("contentConfigLocation");
    //通过类加载器将配置文件转换为字节输入流
    InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
    //这里的HandlerMapping就是自己构建的一个映射器
    HandlerMapping.load(is);
    }
    //doGet和doPost方法都是走service方法,可以直接在这里编写
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1. ********第一件事:获取用户请求的uri /xx.do
    String uri = req.getRequestURI();
    HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
    if( mapping == null){
    resp.sendError(404,"MVC:映射地址不存在:"+uri);
    return;
    }
    Object obj = mapping.getObj();
    Method method = mapping.getMethod();
    Object result = null;
    try {
    //参考反射中方法的执行,第一个参数就是方法所在的对象!!
    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;
    }
    }

    }

    实际的操作方法中,返回值应该都为String类型,因为result的处理流程中(上面代码最后几行),无论前端展示还是重定向都是要转为String类型

  • 注解的建立

    • 给用于向前端返回文字的方法建立注解

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      /**
      * 注解的作用:
      * 被此注解添加的方法, 会被用于处理请求。
      * 方法返回的内容,会以文字形式返回到客户端
      */
      public @interface ResponseBody {
      String value();
      }
    • 用于跳转页面的方法建立注解

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      /**
      * 注解的作用:
      * 被此注解添加的方法, 会被用于处理请求。
      * 方法返回的内容,会直接重定向
      */
      public @interface ResponseView {
      String value();
      }
  • 注解的使用1:建立枚举类型描述注解(?注解的用处不太明白,需要关注下)

    1
    2
    3
    public enum ResponseType {
    TEXT,VIEW;
    }
  • Servlet和注解建立后,就是创建映射器,加载相应的方法

    可以看出,映射器的本质就是一个Map集合,是对请求(String)和方法(MVCMapping)的一个映射,当然MVCMapping中不止封装了方法,还有该方法所在的类反射得到的对象,还有该方法的作用类型

    问:Map中的String值 ((ResponseBody) annotation).value() 是什么意思

    答:对于value方法没加默认值(default)的注解,在声明注解时必须传入参数,这时候在每个方法的注解中传入网址了,也就是Map中的String。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    /**
    * 映射器(包含了大量的网址与方法的对应关系)
    */
    public class HandlerMapping {
    private static Map<String,MVCMapping> data = new HashMap<>();
    public static MVCMapping get(String uri){
    return data.get(uri);
    }


    public static void load(InputStream is ){
    Properties ppt = new Properties();
    try {
    ppt.load(is);
    } catch (IOException e) {
    e.printStackTrace();
    }
    //获取配置文件中描述的一个个的类
    Collection<Object> values = ppt.values();
    for (Object cla:values) {
    String className = (String) cla;
    try {
    //加载配置文件中描述的每一个类
    Class c = Class.forName(className);
    //创建这个类的对象
    Object obj = c.getConstructor().newInstance();
    //获取这个类的所有方法
    Method[] methods = c.getMethods();
    for (Method m:methods) {
    Annotation[] as = m.getAnnotations();
    if(as != null){
    for(Annotation annotation:as){
    if(annotation instanceof ResponseBody){
    //说明此方法,用于返回字符串给客户端
    MVCMapping mapping = new MVCMapping(obj,m,ResponseType.TEXT);
    Object o = data.put(((ResponseBody) annotation).value(),mapping);
    if(o != null){
    //存在了重复的请求地址
    throw new RuntimeException("请求地址重复:"+((ResponseBody) annotation).value());
    }
    }else if(annotation instanceof ResponseView){
    //说明此方法,用于返回界面给客户端
    MVCMapping mapping = new MVCMapping(obj,m,ResponseType.VIEW);
    Object o = data.put(((ResponseView) annotation).value(),mapping);
    if(o != 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;
    //添加构造方法和set/get方法
    }
    }

    此时通过类名.get()就可以获取到请求对应的方法!!!

3.2 前端准备工作

3.2.1 二维码生成

下载资源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
步骤:
1. 引入Jquery.js文件
2. 引入jquery.qrcode.js文件
3. 引入支持中文的编码js文件 (utf.js)
4. 在网页中编写一个div 用于显示二维码
<div id="div1"></div>
5. 准备二维码的规格对象(JSON)
var config = {
width:数字,//值是number类型, 表示的单位是px 必须传递
height:数字,//值是number类型, 表示的单位是px 必须传递
text:"内容",//text就表示二维码中存储的数据 必须传递
correctLevel:数字,//取值为0|1|2|3 表示二维码的纠错级别0:L/1:M/2:Q/3:H ,默认0 可选参数
background:"#rrggbb",//默认白色, 表示二维码的后景颜色 可选参数
foreground:"#rrggbb",//默认黑色, 表示二维码的前景颜色 可选参数
render:"绘制模式"//取值:table/canvas , 默认table 可选参数
};
6. 通过选择器, 查找到上述的div ,得到Jquery对象, 通过jquery对象的qrcode函数生成二维码
$("#div1").qrcode(config);

注意:前端内容的编写一定要小心,没事别带空格!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<title>二维码生成</title>
<script src="js/jquery2.1.4.js"></script>
<script type="text/javascript" src="js/jquery.qrcode.js"></script>
<script src="js/utf.js"></script>
</head>
<body>
<div>123</div>
<div id="qrcode"></div>
<script>
var config={
width:200,
height:200,
text:"23456"
}
$("#qrcode").qrcode(config);
</script>
</body>
</html>

3.2.2 弹出层-layer

步骤:

   1.  下载layer , 并将解压后的layer文件夹 移动到项目中
   2.  引入jquery.js
   3.  引入layer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<script src="js/jquery2.1.4.js"></script>
<script src="layer/layer.js"></script>
<script>
function msgTest(){
//提示的文字
layer.msg("提示的文字");
}
function loadTest(){
//返回弹出层的id
var windowId=layer.load(0); //0-16
//可以通过窗口的id关闭窗口,loading窗口在弹出时, 不允许进行操作.
setTimeout(function(){
layer.close(windowId);
},2000)
}
function msg_load(){
//信息和图标同时显示
layer.msg("提示的文字",{icon:16,shade:0.01});
}
function alertTest(){
//区别于js自带的alert(每个浏览器的弹框都不一样)
layer.alert("123",{icon:10});
}
function tipsTest(){
layer.tips("这里有秘密","#secret",{tipsMore:true,tips:1});
}
</script>
<button onclick="msgTest()">msg函数</button>
<button onclick="loadTest()">load函数</button>
<button onclick="msg_load()">msg函数+load效果</button>
<button onclick="alertTest()">alert函数</button>
<button onclick="tipsTest()">tips函数</button>
<p>
锄禾日当午,疑是地上<span id="secret"></span>
</p>

3.3 准备工具类

  • 库文件:D:\idea-project\day65_javaWebTest\web\WEB-INF\lib

  • 时间格式工具

    1
    2
    3
    4
    5
    6
    public class DateFormatUtil {
    private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String format(Date date){
    return format.format(date);
    }
    }
  • 数据库操作工具(需先导入德鲁伊库+mysql8.0)

    使用的方法:

    • 直接获得与状态通道,自己对其进行处理(设置sql和占位符参数)
    • 直接调用增删改查方法,传入sql语句和参数列表(查询返回值是结果集,更新返回结果是被修改行数)
    • 最后通过closeAll()关闭所有
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    public class DBUtils {
    //1.定义变量
    private Connection connection;
    private PreparedStatement pps;
    protected ResultSet resultSet;
    private int count;//存储收影响的行数

    private static String userName;
    private static String userPass;
    private static String url;
    private static String dirverName;
    //德鲁伊
    private static DruidDataSource dataSource=new DruidDataSource();
    //2.加载驱动
    static {
    //德鲁伊
    ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
    dirverName = bundle.getString("driverClassName");
    url = bundle.getString("url");
    userName = bundle.getString("username");
    userPass = bundle.getString("password");

    dataSource.setUsername(userName);
    dataSource.setPassword(userPass);
    dataSource.setUrl(url);
    dataSource.setDriverClassName(dirverName);
    // dataSource.setInitialSize(20);

    }


    //3.获得链接
    protected Connection getConnection(){
    try {
    connection=dataSource.getConnection();
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    return connection;
    }
    //4.得到预状态通道
    protected PreparedStatement getPps(String sql){
    try {
    pps= getConnection().prepareStatement(sql);
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    return pps;
    }
    //5.绑定参数 List保存的是给占位符所赋的值
    protected void param(List list){
    if(list!=null&&list.size()>0){
    for (int i=0;i<list.size();i++) {
    try {
    pps.setObject(i+1,list.get(i));
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    }
    }
    }

    //6.执行操作(增删改+查询)
    protected int update(String sql,List list){
    getPps(sql);
    param(list);
    try {
    count= pps.executeUpdate();
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    return count;
    }

    //7.查询
    protected ResultSet query(String sql,List list){
    getPps(sql);
    param(list);
    try {
    resultSet= pps.executeQuery();
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    return resultSet;
    }

    //8.关闭资源
    protected void closeAll(){
    try {
    if (connection != null) {
    connection.close();
    }
    if (pps != null) {
    pps.close();
    }
    if (resultSet != null) {
    resultSet.close();
    }
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    }
    }

3.4 后台对接管理员数据库

数据库:id+账号名+密码+登录IP+登录时间

  • 先填写登录IP和登录时间,返回id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class AdminDaoMysql implements BaseAdminDao {
private static final String SQL_UPDATE_LOGIN_TIME = "UPDATE EADMIN SET LOGINTIME=?,LOGINIP=? WHERE USERNAME=?";
private static final String SQL_LOGIN = "SELECT ID FROM EADMIN WHERE USERNAME=? AND PASSWORD=?";
public void updateLoginTime(String username, Date date, String ip) {
//1. 获取连接
Connection conn = DruidUtil.getConnection();
PreparedStatement state = null;
//2. 预编译SQL语句
try {
state = conn.prepareStatement(SQL_UPDATE_LOGIN_TIME);
//3. 填充参数
state.setDate(1,new java.sql.Date(date.getTime()));
state.setString(2,ip);
state.setString(3,username);
//4. 执行
state.executeUpdate();

} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//5. 释放资源
DruidUtil.close(conn,state,null);
}
}

/**
* 管理员根据账号密码登陆
*
* @param username 账号
* @param password 密码
* @return 登陆的结果, true表示登陆成功
*/
@Override
public boolean login(String username, String password) {
//1. 获取连接
Connection conn = DruidUtil.getConnection();
PreparedStatement state = null;
ResultSet rs = null;
//2. 预编译SQL语句
try {
state = conn.prepareStatement(SQL_LOGIN);
//3. 填充参数
state.setString(1,username);
state.setString(2,password);
//4. 执行并获取结果
rs = state.executeQuery();
//5. 根据查询结果,返回
return rs.next();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
//6. 释放资源
DruidUtil.close(conn,state,rs);
}
return false;
}
}

3.5 管理员登录前后端交互

3.5.1 Ajax设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(function(){
$(".login-button").click(function(){
var username = $("#username").val();
var password = $("#password").val();
//1. 先使用layer,弹出load(提示加载中...)
var windowId = layer.load();
//2. ajax与服务器交互
$.post("login.do",{username:username,password:password},function(data){
//3. 关闭load窗口
layer.close(windowId);
//4. 将服务器回复的结果进行显示
layer.msg(data.result);
if(data.status == 0){
//跳转到主页
location.assign("index.html");
}
//
},"JSON");

});
});

3.6 api文档

3.6.1 快递 部分

1. 用于获取控制台所需的快递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
请求地址:express/console.do
参数列表:无
返回的格式示例:
{
status:0,
reuslt:"获取成功",
data:[
{//全部的快递
size:1000,//快递总数
day:100//今日新增
},{//待取件快递
size:500,//待取件数
day:100//今日新增
}
]
}

2. 快件列表(分页)

1
2
3
4
5
6
7
8
9
10
11
请求地址:express/findAll.do
参数列表:
1. limit:
值:0,表示开启分页(默认)
值:1,表示查询所有
2. offset:
值:数字,表示SQL语句起始索引
3. pageNumber:
值:数字,表示获取的快递数量

返回的格式示例:

3. 根据用户的手机号,查询快递信息

1
2
3
4
5
请求地址:express/findByUserPhone.do
参数列表:
1. phoneNumber:手机号码

返回的格式示例:

4.快件录入

1
2
3
4
5
6
7
8
9
请求地址:express/insert.do
参数列表:
1. number:快递单号
2. company:快递公司
3. username:收件人姓名
4. userPhone:收件人手机号码
录入成功返回的格式示例:

录入失败返回的格式示例

5. 修改快递信息

1
2
3
4
5
6
7
8
9
10
请求地址:express/update.do
参数列表:
1. id:要修改的快递id
2. number:新的快递单号
3. company:新的快递公司
4. username:新的收货人姓名
5. userPhone:新的收件人手机号码,(手机号码更新,重新生成取件码,并发送短信)
6. status:新的快递的状态

返回的格式示例:

6. 根据id删除快递信息

1
2
3
4
5
请求地址:express/delete.do
参数列表:
1. id: 要删除的快递的id

返回的格式示例:

3.6.2 快递员

1. 控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
请求地址:courier/console.do
参数列表:无
返回的格式示例:
{
status:0,
reuslt:"获取成功",
data:[
{//全部的用户
size:1000,//快递员总数
day:100//今日注册
}
]
}

2. 快递员列表

1
2
3
请求地址:courier/list.do
参数列表:无/分页(limit,offset,pagenumber)
返回的格式示例:list<Courier>,total

3. 增加

1
2
3
请求地址:courier/insert.do
参数列表:Courier c
返回格式示例:boolean

4. 根据手机号查找

1
2
3
请求地址:courier/find.do
参数列表:phoneNumber
返回值:Courier

5. 更新

1
2
3
请求地址:courier/update.do
参数列表:id,newCourier
返回值:boolean

6. 删除

1
2
3
请求地址:courier/delete.do
参数列表:id
返回值:boolean