SMBMS 超市账单管理系统

本项目是个人学习版本,基于狂神说Java的SMBMS项目,参考视频【狂神说Java】JavaWeb入门到实战

开发工具

  • IntelliJ IDEA 2019.1.4

  • MySQL 8.0

  • Tomcat 8.0

核心业务

  1. 登录注销
  2. 密码修改
  3. 用户管理
  4. 订单管理
  5. 供应商管理

准备工作
  1. 数据库表设计
  2. 搭建项目、配置Tomcat
  3. 编写实体类
  4. 编写BaseDAO
  5. 编写过滤器
  6. 导入资源

一、数据库表设计

【狂神说】SMBMS 超市账单管理系统_sed

1、用户表

【狂神说】SMBMS 超市账单管理系统_JavaWeb_02

2、角色表

【狂神说】SMBMS 超市账单管理系统_JavaWeb_03

3、地址表

【狂神说】SMBMS 超市账单管理系统_sed_04

4、账单表

【狂神说】SMBMS 超市账单管理系统_sql_05

5、供应商表

【狂神说】SMBMS 超市账单管理系统_sql_06


二、搭建项目

1、创建webapp项目(Maven)

注意:勾选的是maven-archetype-webapp,而不是cocoon-22-archetype-webapp

【狂神说】SMBMS 超市账单管理系统_JavaWeb_07

【狂神说】SMBMS 超市账单管理系统_sed_08

【狂神说】SMBMS 超市账单管理系统_sql_09

【狂神说】SMBMS 超市账单管理系统_sql_10

2、完善项目结构

更新XML文件:使用以下代码替换原有XML代码

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
</web-app>

导入依赖

  • JSP:javax.servlet.jsp-api

  • Servlet:javax.servlet-api

  • 数据库连接:mysql-connector-java

  • jstl

  • standard

<!-- JSP -->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
<!-- Servlet -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<!-- JSTL -->
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<!-- Standard -->
<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
<!-- MySQL连接 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>

完善项目的包结构

  1. java
    • dao:数据持久化层,用于操作数据库
    • filter:过滤器
    • pojo:实体类
    • service:业务层,调用dao层处理业务
    • servlet:调用service层处理业务
    • utils:工具类
  2. resources:存放资源文件
  3. webapp:项目资源

【狂神说】SMBMS 超市账单管理系统_ide_11

3、配置Tomcat

创建Tomcat

【狂神说】SMBMS 超市账单管理系统_JavaWeb_12

配置Tomcat

【狂神说】SMBMS 超市账单管理系统_sql_13

【狂神说】SMBMS 超市账单管理系统_ide_14

*4、在IDEA中操作数据库

也可以在SQLYog、Navicat等图形化管理工具操作数据库

新建MySQL的数据源

【狂神说】SMBMS 超市账单管理系统_JavaWeb_15

填写用户名、密码、URL并测试连接

【狂神说】SMBMS 超市账单管理系统_sed_16

注意:MySQL 8.0以上需要配置时区才可连接(在MySQL Server目录下的my.ini文件中配置)

!否则报异常Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property manually.

【狂神说】SMBMS 超市账单管理系统_sql_17


三、编写实体类

ORM:对象关系映射

实体类中的属性与数据库表中的字段一一对应

注意:由于Address表存放的是地址,没有实际含义,因此不编写对应实体类

1、用户类

private Integer id; // 用户ID
private String userCode;    // 用户编码
private String userName;    // 用户名
private String userPassword;    // 用户密码
private Integer gender; // 性别
private Date birthday;  // 出生日期
private String phone;   // 电话
private String address; // 地址
private Integer userRole;   // 用户角色ID
private Integer createdBy;  // 创建者ID
private Date creationDate;// 创建时间
private Integer modifyBy;   // 修改者ID
private Date modifyDate;    // 修改时间

private Integer age;    // 年龄,通过当前时间-出生年份得出
private String userRoleBane;    // 用户角色名称

2、角色类

属性

private Integer id; // 角色ID
private String roleCode;    // 角色编码
private String roleName;    // 角色名
private Integer createdBy;  // 创建者ID
private Date creationDate;// 创建时间
private Integer modifyBy;   // 修改者ID
private Date modifyDate;    // 修改时间

3、账单类

属性

private Integer id; // 账单ID
private String billCode;    // 账单编码
private String productName; // 商品名
private String productDesc; // 商品描述
private String productUnit; // 商品单价
private BigDecimal productCount; // 商品数量
private BigDecimal totalPrice; // 总金额
private Integer isPayment; // 是否支付
private Integer createdBy;  // 创建者ID
private Date creationDate;// 创建时间
private Integer modifyBy;   // 修改者ID
private Date modifyDate;    // 修改时间
private Integer providerId; // 供应商ID

private String providerName;    //供应商名称

4、供应商类

属性

private Integer id; // 供应商ID
private String proCode; // 供应商编码
private String proName; // 供应商名称
private String proDesc; // 供应商描述
private String proContact; // 联系人名称
private String proPhone;   // 供应商电话
private String proAddress; // 供应商地址
private String proFax; // 供应商传真
private Integer createdBy;  // 创建者ID
private Date creationDate;// 创建时间
private Integer modifyBy;   // 修改者ID
private Date modifyDate;    // 修改时间

四、编写BaseDAO

创建数据库配置文件:db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=GMT%2B8
username=root
password=密码

编写公共DAO类,便于其他DAO直接调用。

  • 加载驱动

  • 获取服务器连接

  • 获取SQL执行对象

  • 执行SQL语句

  • 释放连接

public class BaseDAO {

    private static String driver;
    private static String url;
    private static String username;
    private static String password;

    static {
        try {
            InputStream in = BaseDAO.class.getClassLoader().getResourceAsStream("db.properties");
            Properties properties = new Properties();
            properties.load(in);
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            // 加载驱动(只需一次)
            Class.forName(driver);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接
     *
     * @return 数据库连接
     * @throws SQLException SQL异常
     */
    private static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    /**
     * 获取SQL执行对象
     *
     * @param sql 待执行SQL语句
     * @return PreparedStatement对象
     * @throws SQLException SQL异常
     */
    public static PreparedStatement getPreparedStatement(String sql) throws SQLException {
        Connection c = getConnection();
        if (c != null) {
            return getConnection().prepareStatement(sql);
        }
        throw new RuntimeException("Connection为空!");
    }

    /**
     * 执行SQL语句——查询
     *
     * @param ps   SQL执行对象
     * @param args 参数
     * @return 查询结果集
     * @throws SQLException SQL异常
     */
    public static ResultSet executeQuery(PreparedStatement ps, Object... args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
        return ps.executeQuery();
    }

    /**
     * 执行SQL语句——更新
     *
     * @param ps   SQL执行对象
     * @param args 参数
     * @return 受影响行数
     * @throws SQLException SQL异常
     */
    public static int executeUpdate(PreparedStatement ps, Object... args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
        return ps.executeUpdate();
    }

    /**
     * @param isReleaseConnection 是否释放连接Connection
     * @param s                   Statement对象
     * @param rs                  ResultSet对象
     */
    public static void release(boolean isReleaseConnection, Statement s, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (s != null) {
            try {
                s.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (isReleaseConnection) {
            try {
                releaseConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 释放数据库连接
     *
     * @throws SQLException
     */
    private static void releaseConnection() throws SQLException {
        Connection c = getConnection();
        if (c != null) {
            c.close();
        }
    }
}

五、过滤器

字符集编码
处理页面乱码问题

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");

    chain.doFilter(request, response);
}

注册Filter

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>indi.jaywee.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

六、导入资源

【狂神说】SMBMS 超市账单管理系统_SMBMS_18


开发

MVC架构

  • Module:业务模型
  • View:用户界面
  • Controller:控制器

页面发送请求给Controller(控制器),Controller调用Service(业务层) 处理逻辑,Service(Impl实现类)向DAO(持久层)发送请求,DAO与数据库交互后,将结果返回给Service,Service再将处理逻辑发送给Controller,最后Controller再调用视图展现数据到页面上。

【狂神说】SMBMS 超市账单管理系统_sed_19

项目架构

【狂神说】SMBMS 超市账单管理系统_ide_20

开发方式

自顶向下分析,自底向上实现


一、登录注销、权限拦截

(一)登录

【狂神说】SMBMS 超市账单管理系统_SMBMS_21
【狂神说】SMBMS 超市账单管理系统_sql_22

分析前端页面可能向后台传递了哪些参数,需要后台传递哪些参数

  • 向后台传递:用户编码(userCode)、密码(userPassword)
  • 需后台传递:用于提醒登录失败的错误信息(error)

1、DAO层

UserDAO

/**
     * 获取登录用户
     *
     * @param userCode     用户编码
     * @param userPassword 用户密码
     * @return 用户
     * @throws SQLException SQL异常
     */
public User getLoginUser(String userCode, String userPassword) throws SQLException;

UserDAOImpl

userCode和 userPassword同时输入正确才匹配到数据库中用户记录

@Override
public User getLoginUser(String userCode, String userPassword) throws SQLException {

    User user = null;

    String sql = "SELECT * FROM smbms_user WHERE user_code = ? and user_password = ?"; // 编写SQL语句

    PreparedStatement ps = BaseDAO.getPreparedStatement(sql); // 获得PreparedStatement对象
    ResultSet rs = BaseDAO.executeQuery(ps, userCode, userPassword);// 执行SQL

    if (rs.next()) { // 获得结果集
        int id = rs.getInt("id");
        String userName = rs.getString("user_name");// 要用数据库表的字段名
        int gender = rs.getInt("gender");
        Date birthday = rs.getDate("birthday");
        String phone = rs.getString("phone");
        String address = rs.getString("address");
        int userRole = rs.getInt("user_role");
        int createdBy = rs.getInt("created_by");
        Date creationDate = rs.getDate("creation_date");
        int modifyBy = rs.getInt("modify_by");
        Date modifyDate = rs.getDate("modify_date");
        user = new User(id, userCode, userName, userPassword, gender, birthday,
                        phone, address, userRole, createdBy, creationDate, modifyBy, modifyDate);

        BaseDAO.release(false, ps, rs); // 连接不用关,后续业务可能需要用到
    }
    return user;
}

2、Service层

UserService

/**
     * 用户登录
     *
     * @param userCode     用户编码
     * @param userPassword 用户密码
     * @return 登录用户
     */
public User Login(String userCode, String userPassword);

UserServiceImpl

Service层通常直接调用 DAO层方法,处理异常即可

private UserDAO userDAO;

public UserServiceImpl() {
    userDAO = new UserDAOImpl();
}

@Override
public User Login(String userCode, String userPassword) {

    User user = null;

    try {
        user = userDAO.getLoginUser(userCode, userPassword);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        BaseDAO.release(true, null, null); // 每一层只需关闭自己的资源
    }
    return user;
}

3、Servlet层

LoginServlet

用户登录成功:将该用户存储在 Session中,重定向到后台页面

用户登录失败:设置提醒,通过请求转发到登录页

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("进入LoginServlet");

    String userCode = req.getParameter("userCode");
    String userPassword = req.getParameter("userPassword");

    UserService userService = new UserServiceImpl();
    User user = userService.Login(userCode, userPassword);

    if (user != null) {
        // 将用户保存到Session
        req.getSession().setAttribute(Constants.USER_SESSION, user);
        // 跳转到后台主页
        resp.sendRedirect("jsp/frame.jsp");
    } else {
        req.setAttribute("error", "用户名或密码错误");// 通过请求转发携带参数
        // 请求转发
        req.getRequestDispatcher("login.jsp").forward(req,resp);
    }

}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp);
}

注册Servlet

<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>indi.jaywee.servlet.User.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login.do</url-pattern>
</servlet-mapping>

(二)注销

【狂神说】SMBMS 超市账单管理系统_sql_23

分析前端页面可能向后台传递了哪些参数,需要后台传递哪些参数

  • 由于登录状态是通过Session实现,只需移除Session即可。不需传递参数

LogoutServlet

重定向到登录页,移除当前用户的 Session

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("进入LogoutServlet");

    Object userSession = req.getSession().getAttribute(Constants.USER_SESSION);
    if (userSession != null) {
        resp.sendRedirect(req.getContextPath() + "/login.jsp");
        req.getSession().removeAttribute(Constants.USER_SESSION);// 移除用户Session
    }
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp);
}

注册Servlet

<servlet>
    <servlet-name>LogoutServlet</servlet-name>
    <servlet-class>indi.jaywee.servlet.User.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LogoutServlet</servlet-name>
    <url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>

(三)权限拦截

【狂神说】SMBMS 超市账单管理系统_ide_24

LoginFilter:拦截未登录用户进入后台

判断 Session 是否存在:若用户已登入,Session不为 null则放行;若用户注销或 Session过期,Session为 null则拦截重定向到错误页面。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse resp = (HttpServletResponse) response;

    if (req.getSession().getAttribute(Constants.USER_SESSION) == null) {
        System.out.println("未登录,拦截!");
        resp.sendRedirect(req.getContextPath() + "/error.jsp");
    }

    chain.doFilter(req, resp);
}

注册Filter

<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>indi.jaywee.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>LoginFilter</filter-name>
    <url-pattern>/jsp/*</url-pattern>
</filter-mapping>

二、密码修改

【狂神说】SMBMS 超市账单管理系统_sql_25

分析前端页面可能向后台传递了哪些参数,需要后台传递哪些参数

  • 向后台传递:旧密码(oldPassword)、新密码(newPassword)
  • 需后台传递:密码输入错误时的提醒信息(message)

1、DAO层

UserDAO

/**
     * 修改密码
     *
     * @param id          用户ID
     * @param newPassword 新密码
     * @return 受影响行数
     * @throws SQLException SQL异常
     */
public int modifyPassword(int id, String newPassword) throws SQLException;

UserDAOImpl

@Override
public int modifyPassword(int id, String newPassword) throws SQLException {
    String sql = "UPDATE `smbms_user` SET `user_password` = ? WHERE `id` = ?";
    PreparedStatement ps = BaseDAO.getPreparedStatement(sql);
    int i = BaseDAO.executeUpdate(ps, newPassword, id);

    BaseDAO.release(false, ps, null); // 释放连接

    return i;
}

2、Service层

UserService

/**
     * 修改密码
     *
     * @param id          用户ID
     * @param newPassword 新密码
     * @return 是否修改成功
     */
public boolean modifyPassword(int id, String newPassword);

UserServiceImpl

@Override
public boolean modifyPassword(int id, String newPassword) {
    boolean flag = false;
    try {
        if (userDAO.modifyPassword(id, newPassword) > 0) {
            flag = true;
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        BaseDAO.release(true, null, null);
    }
    return flag;
}

3、Servlet层

UserServlet:实现服务器端验证

本项目中,多个页面用到 UserServlet,只是调用的方法不同;

考虑到复用性,抽取方法。判断页面发送的是哪个请求,再调用相应方法

可以用 if 语句或 switch-case语句,如果是 switch-case

注意:一个页面可能发送 1个以上的请求,所以要用多重 if,而不是if-else;case后面不能马上 break

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("进入了UserServlet");

    String method = req.getParameter("method");
    if (method != null) {
        switch (method) { // 判断请求
            case "verifyPwd":
                this.verifyPwd(req, resp);
            case "modifyPwd":
                this.modifyPwd(req, resp);
            case "queryUserList":
                this.queryUserList(req, resp);
                break;
        }
    }
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp);
}

3.1、旧密码验证

UserServlet

服务端实现:对比用户输入的旧密码和 Session中的密码

/**
     * 验证旧密码
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
private void verifyPwd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    User user = null;
    String password = null;
    String oldpassword = req.getParameter("oldpassword");

    Object o = req.getSession().getAttribute(Constants.USER_SESSION);

    if (o != null) {
        user = (User) o;
        password = user.getUserPassword();
    }
    if (StringUtils.isNullOrEmpty(oldpassword) && !oldpassword.equals(password)) { // 旧密码不为空或空值,并且输入正确(注意取反)
        req.setAttribute(Constants.MESSAGE, "旧密码输入错误");
        req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);

    }
}

优化——Ajax验证

private void verifyPwd(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    // Ajax实现
    User user = null;
    String password = null;

    String oldpassword = req.getParameter("oldpassword");
    Object o = req.getSession().getAttribute(Constants.USER_SESSION);

    HashMap<String, String> resultMap = new HashMap<>();

    if (o == null) { // o为空
        resultMap.put("result", "sessionerror");
    } else if (StringUtils.isNullOrEmpty(oldpassword)) { // 密码为空
        resultMap.put("result", "error");
    } else {
        user = (User) o;
        password = user.getUserPassword();
        if (oldpassword.equals(password)) { // 密码输入正确
            resultMap.put("result", "true");
        } else { // 密码输入错误
            resultMap.put("result", "false");
        }
    }

    resp.setContentType("application/json");
    PrintWriter writer = resp.getWriter();
    writer.write(JSONArray.toJSONString(resultMap)); // 将Map对象转化为JSON字符串
    writer.flush();
    writer.close();
}

pwdmodify.js

$.ajax({
    type: "GET",
    url: "/smbms/jsp/user.do",
    //url:path+"/jsp/user.do",
    data: {method: "verifyPwd", oldpassword: oldpassword.val()},	// Ajax传递的参数
    // 等价于:path+/jsp/user.do?method=pwdmodify&oldpassword=oldpassword.val();
    dataType: "json",	//主流开发都是用JSON实现前后端交互
    success: function (data) {
        if (data.result == "true") {//旧密码正确
            validateTip(oldpassword.next(), {"color": "green"}, imgYes, true);
        } else if (data.result == "false") {//旧密码输入不正确
            validateTip(oldpassword.next(), {"color": "red"}, imgNo + " 原密码输入不正确", false);
        } else if (data.result == "sessionerror") {//当前用户session过期,请重新登录
            validateTip(oldpassword.next(), {"color": "red"}, imgNo + " 当前用户session过期,请重新登录", false);
        } else if (data.result == "error") {//旧密码输入为空
            validateTip(oldpassword.next(), {"color": "red"}, imgNo + " 请输入旧密码", false);
        }
    },
    error: function (data) {
        //请求出错
        validateTip(oldpassword.next(), {"color": "red"}, imgNo + " 请求错误", false);
    }
});

3.2、设置新密码

UserServlet

判断新密码不为空或者空值,才调用 Service层修改密码。密码修改成功则移除当前用户 Session

/**
     * 修改密码
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
private void modifyPwd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    User user;
    int id = 0;
    String newpassword = req.getParameter("newpassword");
    Object o = req.getSession().getAttribute(Constants.USER_SESSION);

    if (o != null) {
        user = (User) o;
        id = user.getId();
    }
    verifyPwd(req, resp);
    if (!StringUtils.isNullOrEmpty(newpassword)) { // 新密码 不为空或空值(注意取反)

        UserService us = new UserServiceImpl();

        if (us.modifyPassword(id, newpassword)) { // 密码修改成功
            req.setAttribute(Constants.MESSAGE, "密码修改成功,请使用新密码登录");
            req.getSession().removeAttribute(Constants.USER_SESSION);

        } else { // 密码修改失败
            req.setAttribute(Constants.MESSAGE, "密码修改失败");
        }
    } else {
        req.setAttribute(Constants.MESSAGE, "新密码输入有误");
    }
    req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);
}

注册Servlet

<servlet>
    <servlet-name>UserServlet</servlet-name>
    <servlet-class>indi.jaywee.servlet.User.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UserServlet</servlet-name>
    <url-pattern>/jsp/user.do</url-pattern>
</servlet-mapping>

三、用户管理

【狂神说】SMBMS 超市账单管理系统_SMBMS_26

先分析前端页面可能向后台传递了哪些参数,需要后台传递哪些参数。

  1. 查询功能

    • 向后台传递:用户名(userName)、用户角色ID(role)
    • 需后台传递:用户角色下拉框的用户角色列表(roleList)
  2. 用户列表展示

    • 需后台传递:用户列表(userList)
  3. 分页支持

    • 在后台设置:页面容量(pageSIze)

    • 向后台传递:当前页码(currentPageNo)

    • 需后台传递:页码(pageNo)、总条目数(totalCount)、总页数(totalPageCount)

(一)用户总数

1、DAO层

UserDAO

前端页面的搜索功能,可能会传入用户名和角色,因此需要用到这 2个参数

/**
     * 获取用户总数
     *
     * @param userName 用户名
     * @param userRole 用户角色ID
     * @return 用户总数
     * @throws SQLException SQL异常
     */
public int getUserCount(String userName, int userRole) throws SQLException;

UserDAOImpl

动态SQL使用 StringBuffer或 StringBuilder代替 String(提高拼接效率,在这里效果可能没有那么明显),注意拼接 SQL语句的空格问题

ArrayList: 由于不确定前端页面是否有输入userName和 userRole,即这 2个属性可能为空值,此时如果调用 SQL并传递空值,相当于往占位符填入2个 null,显然不可行。因此采用 ArrayList来动态存储参数列表。

如果 userName和 userRole不为空,则往ArrayList中添加参数,并且 userName采用模糊搜索

@Override
public int getUserCount(String userName, int userRole) throws SQLException {

    int count = 0;
    ArrayList<Object> params = new ArrayList<>(); // 使用ArrayList,动态增加参数(注意转换成数组)

    StringBuffer sql = new StringBuffer("SELECT COUNT(1) AS `count`\n" +
            "FROM `smbms_user` AS `u`,\n" +
            "     `smbms_role` AS `r`\n" +
            "WHERE `u`.`user_role` = `r`.`id`");

    if (!StringUtils.isNullOrEmpty(userName)) {
        sql.append(" AND `user_name` LIKE ?");
        params.add("%" + userName + "%"); // 模糊搜索
    }
    if (userRole > 0) {
        sql.append(" AND `user_role` = ?");
        params.add(userRole);
    }

    PreparedStatement ps = BaseDAO.getPreparedStatement(sql.toString());
    ResultSet rs = BaseDAO.executeQuery(ps, params.toArray());

    System.out.println(sql.toString());

    if (rs.next()) {
        count = rs.getInt("count");
    }
    BaseDAO.release(false, ps, rs);

    return count;
}

2、Service层

UserService

/**
     * 获取用户总数
     *
     * @param userName 用户名
     * @param userRole 用户角色ID
     * @return 用户总数
     * @throws SQLException SQL异常
     */
public int getUserCount(String userName, int userRole);

UserServiceImpl

@Override
public int getUserCount(String userName, int userRole) {
    int count = 0;

    try {
        count = userDAO.getUserCount(userName, userRole);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        BaseDAO.release(true, null, null);
    }

    return count;
}

(二)用户列表

1、DAO层

UserDAO

/**
     * 获取用户列表
     *
     * @param userName      用户名
     * @param userRole      用户角色ID
     * @param currentPageNo 当前页码
     * @param pageSize      页面容量
     * @return 用户列表
     * @throws SQLException SQL异常
     */
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize) throws SQLException;

UserDAOImpl

类似 getUserCount(),在动态SQL以及参数列表的基础上,加入了分页功能(通过MySQL的 limit实现)。

@Override
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize) throws SQLException {

    List<User> userList = new ArrayList<>(); // 存放用户列表

    StringBuffer sql = new StringBuffer("select u.*, r.role_name\n" +
                                        "from `smbms_user` as u,\n" +
                                        "     `smbms_role` as r\n"
                                        +
                                        "where u.user_role = r.id");

    List<Object> params = new ArrayList<>(); // 存放SQL参数

    if (!StringUtils.isNullOrEmpty(userName)) {
        sql.append(" and u.user_name like ?");
        params.add("%" + userName + "%");
    }
    if (userRole > 0) {
        sql.append(" and u.user_role = ?");
        params.add(userRole);
    }
    // 分页
    sql.append(" order by u.creation_date desc limit ?,?");
    int startIndex = (currentPageNo - 1) * pageSize; // 每页的起始索引
    params.add(startIndex);
    params.add(pageSize);

    PreparedStatement ps = BaseDAO.getPreparedStatement(sql.toString());

    ResultSet rs = BaseDAO.executeQuery(ps, params.toArray());

    while (rs.next()) {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setUserCode(rs.getString("user_code"));
        user.setUserName(rs.getString("user_name"));
        user.setGender(rs.getInt("gender"));
        user.setBirthday(rs.getDate("birthday"));
        user.setAge();
        user.setPhone(rs.getString("phone"));
        user.setUserRoleName(rs.getString("role_name"));

        userList.add(user);
    }
    BaseDAO.release(false, ps, rs);

    return userList;
}

2、Service层

UserService

/**
     * 获取用户列表
     *
     * @param userName      用户名
     * @param userRole      用户角色ID
     * @param currentPageNo 当前页码
     * @param pageSize      页面容量
     * @return 用户列表
     */
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize);

UserServiceImpl

@Override
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize) {

    List<User> userList = null;

    try {
        userList = userDAO.getUserList(userName, userRole, currentPageNo, pageSize);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        BaseDAO.release(true, null, null);
    }
    return userList;
}

(三)角色列表

1、DAO层

RoleDAO

/**
     * 获取角色列表
     *
     * @return 角色列表
     * @throws SQLException SQL异常
     */
List<Role> getRoleList() throws SQLException;

RoleDAOImpl

@Override
public List<Role> getRoleList() throws SQLException {
    List<Role> roleList = new ArrayList<>();

    String sql = "select * from smbms_role";
    PreparedStatement ps = BaseDAO.getPreparedStatement(sql);

    ResultSet rs = BaseDAO.executeQuery(ps);

    while (rs.next()) {
        Role role = new Role();
        role.setId(rs.getInt("id"));
        role.setRoleCode(rs.getString("role_code"));
        role.setRoleName(rs.getString("role_name"));

        roleList.add(role);
    }

    BaseDAO.release(false, ps, rs);

    return roleList;
}

2、Service层

RoleService

/**
     * 获取角色列表
     *
     * @return 角色列表
     * @throws SQLException SQL异常
     */
List<Role> getRoleList() throws SQLException;

RoleServiceImpl

@Override
public List<Role> getRoleList() throws SQLException {
    List<Role> roleList = new ArrayList<>();

    String sql = "select * from smbms_role";
    PreparedStatement ps = BaseDAO.getPreparedStatement(sql);

    ResultSet rs = BaseDAO.executeQuery(ps);

    while (rs.next()) {
        Role role = new Role();
        role.setId(rs.getInt("id"));
        role.setRoleCode(rs.getString("role_code"));
        role.setRoleName(rs.getString("role_name"));

        roleList.add(role);
    }

    BaseDAO.release(false, ps, rs);

    return roleList;
}

(四)Servlet

userRole如果前端页面没有选择角色,即没有向后台传递 userRole,则默认为 0(因为Service中当userRole >0才会拼接字符串);如果有选择角色,则将从页面接收到的 userRole转化为 int,赋给要用于查询的 userRole。

pageIndex类似userRole,只是初次打开用户管理页的时候默认值为1,在pageIndex有变化时,才

设置分页支持所需属性时,要先设置 pageSize,再设置 TotalPageCount(因为 TotalPageCount是基于 pageSize计算的)

private void queryUserList(HttpServletRequest req, HttpS。再ervletResponse resp) throws ServletException, IOException {
    UserService userService = new UserServiceImpl();
    RoleService roleService = new RoleServiceImpl();

    List<User> userList = null; // 用于展示用户
    List<Role> roleList = null; // 用于select下拉框

    // 搜索功能:用户名、角色ID
    String queryUserName = req.getParameter("queryname");   // 用户名
    String tempUserRole = req.getParameter("queryUserRole");// 前端接收到的角色ID,可能为空
    int queryUserRole = 0; // 实际使用的角色ID

    if (!StringUtils.isNullOrEmpty(tempUserRole)) {
        queryUserRole = Integer.parseInt(tempUserRole);
    }

    // 分页:页面容量、页码、总条目数、总页数
    int pageSize = 5; // 页面容量
    String pageIndex = req.getParameter("pageIndex");   // 前端接收的页码
    int currentPageNo = 1; // 实际使用的页码,初次加载是第一页

    if (!StringUtils.isNullOrEmpty(pageIndex)) {
        currentPageNo = Integer.parseInt(pageIndex);
    }

    int totalCount = userService.getUserCount(queryUserName, queryUserRole); // 总条目数
    // 调用PageSupport实现上一页、下一页
    PageSupport pageSupport = new PageSupport();
    pageSupport.setCurrentPageNo(currentPageNo); // 设置当前页码
    pageSupport.setPageSize(pageSize);  // 设置页面容量
    pageSupport.setTotalCount(totalCount);  // 设置总条目数(基于pageSize的计算,所以要在setPageSize之后)

    int totalPageCount = pageSupport.getTotalPageCount(); // 总页数

    // 限制首尾页
    if (currentPageNo < 1) {
        currentPageNo = 1;
    } else if (currentPageNo > totalPageCount) {
        currentPageNo = totalPageCount;
    }

    // 得到用户列表、角色列表
    userList = userService.getUserList(queryUserName, queryUserRole, currentPageNo, pageSize);
    roleList = roleService.getRoleList();

    // 传递页面所需参数
    req.setAttribute("userList", userList);
    req.setAttribute("roleList", roleList);

    req.setAttribute("totalPageCount", totalPageCount);
    req.setAttribute("totalCount", totalCount);
    req.setAttribute("currentPageNo", currentPageNo);

    req.getRequestDispatcher("userlist.jsp").forward(req, resp);
}

待完成

  • 用户:增删改
  • 订单、供应商管理