多层结构应用系统示例

目标功能

场景

用户登录; 用户注册; 用户上传照片.

架构描述

展示层

  • 视图负责用户交互, 向用户展示模型的信息, 接收用户输入.
  • 用户登录: index.html
  • 用户注册: newUser.jsp
  • 用户上传照片: userDetail.jsp
  • 模型代表与用户交互过程中的数据. 用户输入的数据和处理后向用户展示的数据.
  • 用户: User
  • 控制器负责接收用户输入, 进行数据格式转换后更新(/创建)模型; 把模型交给服务层处理; 把服务层处理完成后的模型交给向用户展示处理结果的视图.
  • 用户登录: LoginServlet.java
  • 用户注册: RegisterServlet.java
  • 更新用户详情: UpdateUserDetailServlet.java
  • 上传照片: UploadPicServlet.java

服务层

  • 业务服务组织由多种领域对象参与的业务流程, 是服务层的入口.
  • 用户服务: UserService
  • 检查展示层传来数据的合法性
  • 检查/设置对象的活动状态/进度状态
  • 设置传递回展示层的对象必要的字段 - 一些展示层不需要的字段可能被屏蔽
  • 组织业务流程, 调用领域服务 - 领域对象的一些仅用于展示层的字段可能被丢弃
  • 领域服务负责完成基本的业务活动.
  • 身份认证工具: AuthenticationTool
  • 用户管理: UserManagement
  • 日志管理: LogTool
  • 领域对象代表出现在业务活动/流程中的对象.
  • 用户: User
  • 用户操作日志: UserOperationLog

持久层

  • DAO负责数据库增删改查, 在对象(的实例)和数据库的表(的行)之间转换.
  • User的增删改查: UserDao.java
  • 日志记录: UserOperationLogDao.java
  • 数据库负责数据的持久保存.
  • 用户信息表: APPUSER
  • 用户操作日志表: LOGIN_LOG

技术要点

在各层之间传递user对象, 并在各层的方法中改变user对象的状态.

  • 网页提交到servlet, 把user对象传递到服务层处理后, 转到新的网页.
  • 服务层在业务处理过程中调用数据访问层读写数据库.

查询日志表的SQL语句示例

SELECT * FROM ROOT.LOGIN_LOG where cast(time as varchar(128)) > '2020-03-01'

特别注意事项:

在项目的build目录中创建文件夹pic, 以存储上传照片.

用户登录场景

展示层

视图

用户登录网页index.html内容如下

<!DOCTYPE html>
<html>
    <head>
        <title>Portal</title>
        <meta charset="GB18030">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <form action="login" method="post">
            用户名<input type="text" name="user_name" required>
            口令<input type="password" name="password" value="" />
            <input type="submit" value="提交">
        </form>
        <p>点击提交按钮,如果输入框是空的,浏览器会提示错误信息。</p>
    </body>
</html>

点击"提交"按钮, 把form中的数据提交到相对当前目录的login. 此url对应的控制器是LoginServlet[^urlPatterns = {"/login"}].

控制器

LoginServlet.java内容如下

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.User;

@WebServlet(name = "LoginServlet", urlPatterns = {"/login"})
public class LoginServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userName = request.getParameter("user_name");
        String password = request.getParameter("password");
        User user = new User(userName, password);
        service.UserService.login(user);
        request.getSession().setAttribute("user", user);
        request.setAttribute("user", user);
        if (user.getIsLogin() == true) {
            request.getRequestDispatcher("/WEB-INF/view/userDetail.jsp").forward(request, response);
        } else {
            request.getRequestDispatcher("/WEB-INF/view/newUser.jsp").forward(request, response);
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
}

其关键处理过程在方法processRequest()中. 处理过程如下

  • 读取网页传来的参数, user_name和password. 封装在对象user中传递给服务层
  • 服务层service.UserService.login(user);处理完user对象后,
  • 把user放置在会话session中保存
  • 并且把user设置为request的属性
  • 根据user是否登录成功, 转到交给不同视图
  • 成功转到用户详情页面"/WEB-INF/view/userDetail.jsp"
  • user是为注册用户或者口令不正确,转到新用户注册页面/WEB-INF/view/newUser.jsp

模型

User.java内容如下

package model;

public class User {

    private String name;
    private String password;
    private boolean isLogin;
    private String pic;

    public User() {
        this("", "");
    }

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean getIsLogin() {
        return isLogin;
    }

    public void setIsLogin(boolean isLogin) {
        this.isLogin = isLogin;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }
}

服务层

业务服务 - 服务聚合

组合业务活动的流程的UserService.java内容如下

package service;

import model.User;

public class UserService {

    public static void login(User user) {
        if (UserManagement.isBadUserFormat(user)) {
            return;
        }
        boolean isAuthenticationPassed = AuthenticationTool.checkUser(user.getName(), user.getPassword());
        if (isAuthenticationPassed) {
            UserManagement.recoverUser(user);
            LogTool.appendLog(user.getName(), "login", "OK");
        } else {
            LogTool.appendLog(user.getName(), "login", "Failed");
        }
        user.setIsLogin(isAuthenticationPassed);
    }

    public static void register(User user) {
        if (UserManagement.isBadUserFormat(user)) {
            return;
        }
        boolean isAdded = UserManagement.addUser(user);
        if (isAdded == true) {
            LogTool.appendLog(user.getName(), "registor", "OK");
            user.setIsLogin(true);
        } else {
            user.setIsLogin(false);
            LogTool.appendLog(user.getName(), "registor", "Failed");
        }
    }

    public static void updateUserDetail(User user) {
        if (UserManagement.isBadUserFormat(user)) {
            return;
        }
        if (user.getIsLogin() == false) {
            LogTool.appendLog(user.getName(), "update", "Failed", "not login");
            return;
        }
        boolean isUpdated = UserManagement.updateUser(user);
        LogTool.appendLog(user.getName(), "update", String.valueOf(isUpdated));
    }
}

在用户登录场景只调用了service.UserService.login(user); 处理过程如下

  • 检查user状态是否正常.
  • UserManagement.isBadUserFormat(user)
  • 如果user==null, user.username==null或者为空串""或者全部是空白, 则返回false. 以保证user.username是正常的文字.
  • AuthenticationTool.checkUser()检查用户名和口令是否正确.
  • 如果正确, 则读取user的各个字段, 填充到user对象中UserManagement.recoverUser(user)
  • 记录操作日志LogTool.appendLog(user.getName(), "login", "OK");
  • 否则, 记录操作日志LogTool.appendLog(user.getName(), "login", "Failed");
  • 设置user的登录状态为执行AuthenticationTool.checkUser()的结果

领域服务

实现基本业务活动的服务/工具

用户认证服务AuthenticationTool.java内容如下

package service;

import dao.UserDao;
import model.User;

public class AuthenticationTool {

    static boolean checkUser(String userID, String password) {
        User user = UserDao.selectUser(userID);
        if (user == null) {
            return false;
        } else {
            return user.getPassword().equals(password);
        }
    }
}

Authentication.check功能是

  • 调用持久层的UserDao.selectUser(userID)查找指定用户的信息
  • 如果没有找到, 则认证失败
  • 如果找到, 那么如果口令一致则成功, 口令不一致则失败

用户管理服务UserManagement.java内容如下

package service;

import dao.UserDao;
import model.User;

public class UserManagement {

    static boolean removeUser(User user) {
        return UserDao.deleteUser(user);
    }

    static boolean update(User user) {
        return UserDao.updateUser(user) != null;
    }

    static User findUser(String username) {
        User user = UserDao.selectUser(username);
        return user;
    }

    static void recoverUser(User user) {
        User user1 = UserDao.selectUser(user.getName());
        if(user1 == null) return;
        user.setPassword(user1.getPassword());
        user.setPic(user.getPic());
    }

    static boolean addUser(User user) {
        boolean isUserFinded = findUser(user.getName()) != null;
        if (isUserFinded) {
            LogTool.appendLog(user.getName(), "register", "Failed", "username is existed");
            return false;
        }
        Boolean isAdded = UserDao.insertUser(user);
        return isAdded;
    }

    static boolean isBadUserFormat(User user) {
        if (user == null) {
            return true;
        }
        if (user.getName() == null) {
            return true;
        }
        if (user.getName().trim().length() == 0) {
            return true;
        }
        return false;
    }
}

UserManagement.recoverUser(user)调用UserDao.selectUser()方法. 功能是

  • 调用持久层的UserDao.selectUser(user)查找指定用户的信息, 形成user对象.
  • 如果没有找到, 则直接返回. 不对user的属性填充
  • 填充user的pic和password字段的数据.

持久层

DAO

负责访问数据库的读写User对象的UserDao.java,内容如下

package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import model.User;

public class UserDao {

    public static User selectUser(String usernname) {
        String sql = "SELECT * from appuser where username = ?";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, usernname);
            ResultSet rs = p.executeQuery();
            if (rs.next()) {
                User user = new User(rs.getString("username"), rs.getString("password"));
                user.setPic(rs.getString("pic"));
                return user;
            } else {
                return null;
            }
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    }

    public static User updateUser(User user) {
        String sql = "UPDATE appuser SET pic = ?, password = ? WHERE username = ?";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, user.getPic());
            p.setString(2, user.getPassword());
            p.setString(3, user.getName());
            p.execute();
            connectDb.commit();
        } catch (Exception e) {
            System.out.println(e);
            user = null;
        }
        return user;
    }

    public static boolean insertUser(User user) {
        String sql = "INSERT INTO appuser (username, password) VALUES (?, ?)";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, user.getName());
            p.setString(2, user.getPassword());
            p.execute();
            connectDb.commit();
        } catch (Exception e) {
            System.out.println(e);
            return false;
        }
        return true;
    }

    public static boolean deleteUser(User user) {
        String sql = "DELETE FROM appuser where username = ?";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, user.getName());
            p.execute();
            connectDb.commit();
            return true;
        } catch (Exception e) {
            System.out.println(e);
            return false;
        }
    }
}

在此次调用中, 仅有UserDao.selectUser(userID)

selectUser(String usernname)方法的执行过程如下

  • 定义查询SQL语句
  • 调用DerbyConnection.connectDb()获得到数据库的连接
  • 创建访问数据库的sql语句对象, 并设置sql语句中参数.
  • 执行查询得到结果集
  • 如果得到的结果集中有数据行
  • 根据数据行的内容创建一个User对象, 作为返回值
  • 否则返回空

负责建立数据库连接的DerbyConnection.java内容如下

package dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DerbyConnection {

    private static Connection connection;

    public static Connection connectDb() {
        if (connection != null) {
            return connection;
        }
        try {
            Class.forName("org.apache.derby.jdbc.ClientDriver");
            connection = DriverManager.getConnection(
                    "jdbc:derby://localhost:1527/OOPDB",
                    "root",
                    "123456");
            return connection;
        } catch (Exception e) {
            connection = null;
            return null;
        }       
    }
    public static void close(){
        try {
            connection.close();
        } catch (SQLException ex) {
        }
        connection = null;
    }
}

其核心功能为

  • 加载jdbc驱动程序Class.forName("org.apache.derby.jdbc.ClientDriver");
  • 创建到数据库的连接DriverManager.getConnection( "jdbc:derby://localhost:1527/OOPDB", "root", "123456");
  • 其中jdbc:derby://localhost:1527/OOPDB是数据库的url,
  • 用jdbc访问derby数据库
  • 数据库在本机的1527端口运行
  • 数据库名称为OOPDB
  • root是用户名
  • 123456是口令

另一个DAO对象是记录用户登录日志的UserOperationLogDao.java内容如下

package dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import model.UserOperationLog;

public class UserOperationLogDao {
    public static List<UserOperationLog> selectLog(String usernname) {
        List<UserOperationLog> logs = new ArrayList<>();
        String sql = "SELECT * from login_log where username = ?";
        try {
            PreparedStatement p = DerbyConnection.connectDb().prepareStatement(sql);
            p.setString(1, usernname);
            ResultSet rs = p.executeQuery();
            while (rs.next()) {
                UserOperationLog log = new UserOperationLog(rs.getString("logID"), rs.getString("username"), rs.getTimestamp("time").toLocalDateTime(), rs.getString("action"));
                logs.add(log);
            }
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
        return logs;
    }


    public static UserOperationLog insertLog(UserOperationLog log) {
        String sql = "INSERT INTO login_log (logID, username, time, action) VALUES (?, ?, ?, ?)";
        try {
            PreparedStatement p = DerbyConnection.connectDb().prepareStatement(sql);
            p.setString(1, log.getLogID());
            p.setString(2, log.getUsername());
            p.setTimestamp(3, Timestamp.valueOf(log.getTime()));
            p.setString(4, log.getAction());
            p.execute();
        } catch (Exception e) {
            System.out.println(e);
            log = null;
        }
        return log;
    } 
}

方法insertLog(LoginLog log)中用使用SQL的插入语句. 并且表 login_log的字段time是timestamp类型. 对应的日志对象LoginLog中的time是LocalDateTime类型. 程序中进行了数据格式的转换.

UserOperationLog.java内容如下

package model;

import java.time.LocalDateTime;
import java.util.UUID;

public class UserOperationLog {
    private String logID;
    private String username;
    private LocalDateTime time;
    private String action;

    public UserOperationLog() {
        this("", "");
    }

    public UserOperationLog(String username, String action) {
        this.username = username;
        this.action = action;
        this.logID = UUID.randomUUID().toString();
        this.time = LocalDateTime.now();
    }

    public UserOperationLog(String logID, String username, LocalDateTime time, String action) {
        this.logID = logID;
        this.username = username;
        this.time = time;
        this.action = action;
    }

    public String getLogID() {
        return logID;
    }

    public void setLogID(String logID) {
        this.logID = logID;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }
}

其中logID对应数据库中的主键采用UUID.randomUUID().toString();方式生成, 只是演示UUID的生成方法, 对于日志并没有实质的用途, 除了在分布式生成日志行时, 能够"保证"主键不重复.

数据库

用户信息表: APPUSER

  • username varchar[50] PK
  • password varchar[50]
  • pic varchar[200]

用户操作日志表: LOGIN_LOG

  • logid varchar[50] PK
  • username varchar[50]
  • action varchar[200]
  • time timestamp

用户注册场景

展示层

视图

用户名口令检查不通过的情况, 转到/WEB-INF/view/newUser.jsp内容如下

<%@page import="model.User"%>
<%@page contentType="text/html" pageEncoding="GB18030"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=GB18030">
        <title>JSP Page</title>
    </head>
    <body>
        <%  //java代码写在这里
            User user = (User) request.getAttribute("user");
        %>
        <h1>
            Hello <%=user.getName()%>
        </h1>
        <h1>
            口令检查结果为 <%=user.getIsLogin()%>
        </h1>
        <h1>换一个用户名, 注册新用户</h1>
        <form action="register" method="post">
            用户名<input type="text" name="user_name" required>
            口令<input type="password" name="password" value="" />
            <input type="submit" value="提交">
        </form>
        <p>点击提交按钮,如果输入框是空的,浏览器会提示错误信息。</p>    
    </body>
</html>

接收了从LoginServlet传递来的request中的属性user,

  • User user = (User) request.getAttribute(“user”);
  • 在页面中显示用户名<%=user.getName()%>和口令检查结果<%=user.getIsLogin()%>

输入新的用户名和口令后, 点击"提交"按钮后, 到同目录下的register对应的RegisterServlet[^urlPatterns = {"/register"}]执行.

控制器

控制器RegisterServlet.java内容如下

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.User;

@WebServlet(name = "RegisterServlet", urlPatterns = {"/register"})
public class RegisterServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userName = request.getParameter("user_name");
        String password = request.getParameter("password");
        User user = new User(userName, password);
        service.UserService.register(user);
        request.getSession().setAttribute("user", user);
        request.setAttribute("user", user);
        if (user.getIsLogin() == true) {
            request.getRequestDispatcher("/WEB-INF/view/userDetail.jsp").forward(request, response);
        } else {
            request.getRequestDispatcher("/WEB-INF/view/newUser.jsp").forward(request, response);
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
}

其关键处理过程在方法processRequest()中. 处理过程如下

  • 读取网页传来的参数, user_name和password. 封装在对象user中传递给服务层
  • 服务层service.UserService.register(user);处理完user对象后,
  • 把user放置在会话session中保存
  • 并且把user设置为request的属性
  • 根据user是否处于登录成功状态, 转到交给不同视图
  • 成功转到用户详情页面"/WEB-INF/view/userDetail.jsp"
  • user是为注册用户或者口令不正确,转回到新用户注册页面/WEB-INF/view/newUser.jsp

模型

User.java

服务层

业务服务 - 服务聚合

service.UserService.register(user);对应的方法内容如下

public static void register(User user) {
        if (UserManagement.isBadUserFormat(user)) {
            return;
        }
        boolean isAdded = UserManagement.addUser(user);
        if (isAdded == true) {
            LogTool.appendLog(user.getName(), "registor", "OK");
            user.setIsLogin(true);
        } else {
            user.setIsLogin(false);
            LogTool.appendLog(user.getName(), "registor", "Failed");
        }
    }

其处理过程如下

  • 检查user格式是否正常
  • 如果user==null, user.username==null或者为空串""或者全部是空白, 则返回false. 以保证user.username是正常的文字.
  • UserManagement.addUser()增加新用户.
  • 如果增加新用户成功
  • 记录操作日志LogTool.appendLog(user.getName(), "registor", "OK");
  • 调用用户登录方法, 更新用户状态
  • 否则, 设置user为未登录状态
  • 记录操作日志LogTool.appendLog(user.getName(), "registor", "Failed");

领域服务

UserManagement.java文件中的addUser()方法内容如下

static boolean addUser(User user) {
        boolean isUserFinded = findUser(user.getName()) != null;
        if (isUserFinded) {
            LogTool.appendLog(user.getName(), "register", "Failed", "username is existed");
            return false;
        }
        Boolean isAdded = UserDao.insertUser(user);
        return isAdded;
    }

其处理过程如下

  • 根据用户名查找用户是否已经存在
  • 如果找到, 则记录日志后直接返回false
  • 调用UserDao.insertUser()增加用户, 并返回增加结果是否成功.

持久层

DAO

UserDao的insertUser()方法如下

public static boolean insertUser(User user) {
        String sql = "INSERT INTO appuser (username, password) VALUES (?, ?)";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, user.getName());
            p.setString(2, user.getPassword());
            p.execute();
            connectDb.commit();
        } catch (Exception e) {
            System.out.println(e);
            return false;
        }
        return true;
    }

执行过程如下

  • 定义SQL插入语句
  • 建立到数据库的连接
  • 创建执行SQL语句的对象, 并设置需要的参数name和password
  • 执行并提交

数据库

同上

用户上传照片场景

<%-- 
    Document   : addUserDetail
    Created on : 2020-2-29, 10:45:51
    Author     : subo1
--%>

<%@page import="model.User"%>
<%@page contentType="text/html" pageEncoding="GB18030"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=GB18030">
        <title>JSP Page</title>
        <script   language=javascript>
            function   process(action) {
                document.userDetailForm.action = action;
                document.userDetailForm.submit();
            }
        </script> 
    </head>
    <body>
        <%  //java代码写在这里
            User user = (User) request.getAttribute("user");
        %>
        <h1>维护用户信息, 上传照片</h1>
        <form name="userDetailForm" method="POST" enctype="multipart/form-data">
            <table border="1">
                <thead>
                    <tr>
                        <th>用户名</th>
                        <th>口令</th>
                        <th>照片</th>
                        <th>照片上传</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><%=user.getName()%></td>
                        <td><%=user.getPassword()%></td>
                        <td><img src="<%=user.getPic()%>" alt="upload a jpg"></td>
                        <td>
                            <input type="file" name="<%=user.getName()%>.jpg"/>
                            <input type="button" value="上传"  onclick="process('uploadpic')"/>
                        </td>
                    </tr>
                    <tr>
                        <td> </td>
                        <td> </td>
                        <td> </td>
                        <td><input type="button"value="提交更改" οnclick="process('updateuser')" /></td>
                    </tr>
                </tbody>
            </table>
        </form>
    </body>
</html>

其中的JavaScript代码

<script   language=javascript>
            function   process(action) {
                document.userDetailForm.action = action;
                document.userDetailForm.submit();
            }
        </script>

用于尽心form提交. 提交action的目标url是

  • uploadpic, 当点击按钮"上传"时
  • <input type="button" value="上传" onclick="process('uploadpic')"/>
  • updateuser, 当点击按钮"提交更改"时
  • <input type="button"value="提交更改" onclick="process('updateuser')" />

在表格中显示图片<img src="<%=user.getPic()%>" alt="upload a jpg">约定,

  • 图片保存在网站运行上下文目录中的文件夹pic
  • 在用NetBeans管理运行JavaWeb项目时, 每次重新部署会删除pic目录. 只能手工重建.
  • user的pic字段用于存储图片的相对路径和文件名, 约定为jpg
  • <input type="file" name="<%=user.getName()%>.jpg"/>
  • 图片文件名为每个用户名.jpg, 目录为pic. 例如pic/00.jpg

控制器

控制器UploadPicServlet.java内容如下

package controller;

import java.io.IOException;
import java.util.Collection;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import model.User;

@WebServlet(name = "UploadPicServlet", urlPatterns = {"/uploadpic"})
@MultipartConfig(maxFileSize = 1000_000_000, maxRequestSize = 2000_000_000)
public class UploadPicServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("user");
        Collection<Part> parts = request.getParts();
        for (Part part : parts) {
            if (part.getSubmittedFileName() != null) {
                String realPath = this.getServletContext().getRealPath("/pic/");
                part.write(realPath + part.getName());
                user.setPic("pic/" + part.getName());
            }
        }
        request.setAttribute("user", user);
        request.getRequestDispatcher("/WEB-INF/view/userDetail.jsp").forward(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }

}

其关键方法processRequest()的执行过程如下:

  • 从session中读取user对象
  • 对于上传的文件, 保存在当前运行上下文目录中的/pic/目录中, 文件名用网页端指定的名称.
  • 为request设置user属性, 传递(又回)到视图网页/WEB-INF/view/userDetail.jsp

控制器UpdateUserDetailServlet内容如下

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.User;

@WebServlet(name = "UpdateUserDetailServlet", urlPatterns = {"/updateuser"})
public class UpdateUserDetailServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("user");
        service.UserService.updateUserDetail(user);
        request.getSession().setAttribute("user", user);
        request.setAttribute("user", user);
        if (user.getIsLogin() == true) {
            request.getRequestDispatcher("/WEB-INF/view/userDetail.jsp").forward(request, response);
        } else {
            request.getRequestDispatcher("/WEB-INF/view/newUser.jsp").forward(request, response);
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }
}

其关键方法processRequest()的执行过程如下:

  • 从session中读取user对象
  • 更新用户的信息, 调用service.UserService.updateUserDetail(user)
  • 为request设置user属性,
  • 如果用户处于登录状态, 传递到视图网页/WEB-INF/view/userDetail.jsp
  • 否则转到注册新用户视图/WEB-INF/view/newUser.jsp

模型

同上

服务层

业务服务 - 服务聚合

service.UserService.updateUserDetail(user);对应的方法内容如下

public static void updateUserDetail(User user) {
        if (UserManagement.isBadUserFormat(user)) {
            return;
        }
        if (user.getIsLogin() == false) {
            LogTool.appendLog(user.getName(), "update", "Failed", "not login");
            return;
        }
        boolean isUpdated = UserManagement.updateUser(user);
        LogTool.appendLog(user.getName(), "update", String.valueOf(isUpdated));
    }

其处理过程如下

  • 检查user格式是否正常
  • 如果user==null, user.username==null或者为空串""或者全部是空白, 则返回false. 以保证user.username是正常的文字.
  • 检查用户是否登录, 如果没有登录
  • 记录操作日志LogTool.appendLog(user.getName(), "update", "Failed", "not login");
  • UserManagement.updateUser()更新用户.
  • 记录操作日志LogTool.appendLog(user.getName(), "update", String.valueOf(isUpdated));

领域服务

UserManagement.java文件中的updateUser()方法内容如下

static boolean updateUser(User user) {
        return UserDao.updateUser(user) != null;
    }

持久层

DAO

UserDao的updateUser()方法如下

public static User updateUser(User user) {
        String sql = "UPDATE appuser SET pic = ?, password = ? WHERE username = ?";
        try {
            Connection connectDb = DerbyConnection.connectDb();
            PreparedStatement p = connectDb.prepareStatement(sql);
            p.setString(1, user.getPic());
            p.setString(2, user.getPassword());
            p.setString(3, user.getName());
            p.execute();
            connectDb.commit();
        } catch (Exception e) {
            System.out.println(e);
            user = null;
        }
        return user;
    }

执行过程如下

  • 定义SQL更新语句
  • 建立到数据库的连接
  • 创建执行SQL语句的对象, 并设置需要的参数pic, password和name
  • 执行并提交

数据库

同上

运行结果

登录页面

java 对象多层级怎么判断是否存在 java多层结构_User


输入数据库中存在的用户名口令, 显示用户详情, 等待照片上传.

java 对象多层级怎么判断是否存在 java多层结构_sql_02


点击"选择文件"按钮, 选择jpg图片, 然后点击"上传"按钮.

java 对象多层级怎么判断是否存在 java多层结构_sql_03


上传照片后, 点击提交更改. 数据库中appuser表的记录username=00的行的PIC被添加了图片文件名pic/00.jpg.

java 对象多层级怎么判断是否存在 java多层结构_sql_04


查看日志表

java 对象多层级怎么判断是否存在 java多层结构_java 对象多层级怎么判断是否存在_05