SSM 重構註冊登陸介面

胖若倆人發表於2018-08-26

正文之前

之前有做過一個練手的商品管理的小專案,然後用 SSM 重構了,接下來又做了一個模擬註冊登入的介面,然後前兩天用 SSM 重構了這個專案的後臺程式碼,修改了一點前端頁面,後臺功能不變

接下來還有在此基礎上做新的功能:保持登陸狀態,檢視個人資訊等,然後把註冊登陸介面整合至商品管理的專案,一步一步擴大

正文

1. 專案截圖

下面的文件中有給出截圖

2. 功能實現

  1. 註冊、登陸

常規的註冊登陸操作

  1. 驗證碼

輸出驗證碼,單擊驗證碼圖片可重新整理

  1. 驗證使用者

註冊是驗證使用者名稱、電話號碼和郵箱是否已被註冊,登陸時驗證使用者是否已註冊以及密碼是否正確,兩個過程中都有檢測驗證碼的準確性

3. 具體說明

我還是放上專案的 README 文件吧:

Registration-login-interface2

Version 0.1

使用 SSM 框架來對原先的 Registration-login-interface 進行重構,頁面做細微改動,後臺使用框架,來達到同樣的效果:

SSM 重構註冊登陸介面

SSM 重構註冊登陸介面

0.1 版本是使用框架進行重構,接下來的 0.2 版本將會是新增一些功能:使用者保持登陸狀態,新增一張 SQL 表來存放使用者資訊,並在頁面中進行個人資訊新增和修改

1. 檔案結構

關於建包和建立哪些類,不多說,直接上一整個專案的圖:

SSM 重構註冊登陸介面

2. 配置檔案(config 包)

將 Spring 和 MyBatis 整合的方式,在之前的 new-p-mmybatis-spring 官方文件 中都能找到答案,這裡直接給出配置:

資料庫資訊(db.properties):

jdbc.driver = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/user jdbc.username = xxx(你自己的使用者名稱) jdbc.password = xxx(你自己的密碼)

spring-mvc.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 元件掃描 -->
    <context:component-scan base-package="controller"/>
    <context:component-scan base-package="service"/>

    <!-- 註解驅動 -->
    <mvc:annotation-driven/>

    <!-- 配置檢視解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 這兩行程式碼是為了不讓靜態資源被 servlet 攔截 -->
    <mvc:resources mapping="/image/**" location="image"/>
    <mvc:resources mapping="../../css/**" location="css"/>
</beans>
複製程式碼

spring-mybatis.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 將資料庫資訊配置在外部檔案中,使用佔位符來代替具體資訊的配置 -->
    <context:property-placeholder location="classpath:config/db.properties"/>

    <!-- 配置資料來源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- sqlSessionFactory 的配置,這是基於 MyBatis 的應用的核心 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 使用上面定義的資料來源來進行配置 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 查詢下面指定的類路徑中的對映器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 定義 Mapper 配置器的位置 -->
        <property name="basePackage" value="mapper"/>
    </bean>

    <!-- 之後要用到的兩個 Bean -->
    <bean id="exceptionService" class="service.impl.ExceptionServiceImpl"/>
    <bean id="verifyCode" class="util.VerifyCode"/>
</beans>
複製程式碼

3. web.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">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/config/spring/spring-mybatis.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:config/spring/spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>
</web-app>
複製程式碼

第 2 步中配置的兩個檔案作為上下文配置資訊,最後的 url 對映是 xxx.action 的形式

4. 實體類(User.java)

為了節省篇幅,這裡不給出實體類程式碼,和 Registration-login-interface 中的實體類是一樣的

5. 自定義異常

自定義的異常是呼叫父類方法來實現的,因使用者註冊或登陸時輸入有誤而丟擲

public class UserException extends Exception { //自定義異常 public UserException(String message) { super(message); } }

6. 對映器

MyBatis-Spring 提供的 MapperFactoryBean 能夠進行動態代理,能夠將資料對映器介面注入到 Service 層中的 Bean 裡,注意,一定要是介面,不能是實現類,所以我們這裡寫了一個 UserMapper:

public interface UserMapper {
    void addUser(User user);
    User findUserByName(String username);
    User findUserByPhone(String phone);
    User findUserByEmail(String email);
}
複製程式碼

對映器介面對應著一個同名的 XML 對映器檔案檔案:UserMapper.xml

這個對映器中寫的是 SQL 語句,這裡面有四句,新增,按照名稱、電話號碼和郵箱進行查詢,對映檔案的名稱空間(namespace)對應著對映器介面的名稱,SQL 語句的 id 對應著介面中的方法,不能有誤

<mapper namespace="mapper.UserMapper">
    <insert id="addUser" parameterType="domain.User">
      INSERT INTO user(username,password,phone,email)
      VALUES (#{username}, #{password}, #{phone}, #{email})
    </insert>

    <select id="findUserByName" parameterType="String" resultType="domain.User">
        SELECT * FROM user WHERE username = #{username}
    </select>

    <select id="findUserByPhone" parameterType="String" resultType="domain.User">
        SELECT * FROM user WHERE phone = #{phone}
    </select>

    <select id="findUserByEmail" parameterType="String" resultType="domain.User">
        SELECT * FROM user WHERE email = #{email}
    </select>
</mapper>
複製程式碼

6. 驗證碼

SSM 版本的驗證碼沒有變化,還是 Registration-login-interface 中的驗證碼,不做更改

7. Service 層

Service 層有兩個介面,一個是關於註冊和登陸的:

public interface UserService {
    public void addUser(User user) throws UserException;
    public void login(User user) throws UserException;
}
複製程式碼

另一個是檢測註冊和登陸過程中的錯誤情況:

@Service
public interface ExceptionService {

    //_user 是從資料庫中查詢出的記錄,user 是使用者輸入
    public void loginException(User user, User db_user) throws UserException;

    public void addUserException1(User user) throws UserException;

    public void addUserException2(User user) throws UserException;
}
複製程式碼

先寫 ExceptionService 的實現,在註冊和登陸過程中要使用:

    //先建立 Bean,接下來會用到
    private final UserMapper userMapper;

    @Autowired
    public ExceptionServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    
    //先判斷輸入格式是否有誤
    public void addUserException1(User user) throws UserException{
        if (user.getUsername() == null || user.getUsername().trim().isEmpty()){
            throw new UserException("使用者名稱不能為空");
        }else if (user.getUsername().length() < 5 || user.getUsername().length() > 15){
            throw new UserException("使用者名稱必須為5-15個字元");
        }

        if (user.getPassword() == null || user.getPassword().trim().isEmpty()){
            throw new UserException("密碼不能為空");
        }else if (user.getPassword().length() < 5 || user.getPassword().length() > 15){
            throw new UserException("密碼必須為5-15個字元");
        }

        if (user.getPhone() == null || user.getPhone().trim().isEmpty()){
            throw new UserException("電話號碼不能為空");
        }

        if (user.getEmail() == null || user.getEmail().trim().isEmpty()){
            throw new UserException("郵箱不能為空");
        }
    }

    //再判斷輸入的資訊是否已被註冊
    public void addUserException2(User user) throws UserException{
        //這三者都必須是唯一的
        if (userMapper.findUserByName(user.getUsername()) != null){
            throw new UserException("該使用者名稱已被註冊");
        } else if (userMapper.findUserByPhone(user.getPhone()) != null){
            throw new UserException("該電話號碼已被註冊");
        } else if (userMapper.findUserByEmail(user.getEmail()) != null){
            throw new UserException("該郵箱已被註冊");
        }
    }

    //登入檢測
    public void loginException(User user, User db_user) throws UserException {

        if(db_user == null){
            throw new UserException("該使用者不存在");
        }
        if(!user.getPassword().equals(db_user.getPassword())){
            throw new UserException("密碼錯誤");
        }
    }

    //驗證碼檢測
    @Override
    public void verifyCodeException(String inputVerifyCode, String code) throws UserException {
        if (inputVerifyCode == null || inputVerifyCode.trim().isEmpty()){
            throw new UserException("驗證碼不能為空");
        } else if (inputVerifyCode.length() != 4){
            throw new UserException("驗證碼長度應為 4 位");
        } else if (!inputVerifyCode.equals(code)){
            throw new UserException("驗證碼錯誤");
        }
    }
複製程式碼

然後是 UserService 的實現:

    //先是用構造器注入來建立 UserMapper 和 ExceptionService 兩個 Bean
    private final UserMapper userMapper;

    private final ExceptionService exceptionService;

    @Autowired
    public UserServiceImpl(UserMapper userMapper, ExceptionService exceptionService) {
        this.userMapper = userMapper;
        this.exceptionService = exceptionService;
    }
    
    public void addUser(User user) throws UserException {
        //先判斷使用者的輸入是否有錯
        exceptionService.addUserException1(user);
        //再判斷使用者的資訊是否已被註冊
        exceptionService.addUserException2(user);
        userMapper.addUser(user);
    }
    
    //根據使用者輸入名字去資料庫查詢有沒有這個使用者,如果沒有,就會丟擲異常
    public void login(User user) throws UserException {
        User db_user = userMapper.findUserByName(user.getUsername());
        exceptionService.loginException(user, db_user);
    }
複製程式碼

可能有人會覺得為什麼登陸的方法沒有返回值,其實如果登入成功,也就是沒有丟擲異常,在 Controller 中就可以接著執行後面的方法,如果使用者名稱或密碼錯誤,是會丟擲異常,中斷程式的

8. Controller

到了關鍵的一步,Controller 負責處理 DispatcherServlet 分發的請求:

首先是使用構造器注入來建立三個 Bean:

    private final UserService userService;

    private final VerifyCode verifyCode;

    private final ExceptionService exceptionService;

    @Autowired
    public UserController(UserService userService, VerifyCode verifyCode, ExceptionService exceptionService) {
        this.userService = userService;
        this.verifyCode = verifyCode;
        this.exceptionService = exceptionService;
    }
複製程式碼

userService 就是用於註冊和登陸的,verifyCode 就是用於得到驗證碼,exceptionService 是用來檢測註冊和登陸過程中是否出現錯誤

在註冊和登陸之前,都需要得到帶有表單的頁面:

    //在註冊之前需要先得到註冊的介面
    @RequestMapping("/preAdd")
    public ModelAndView preAdd(){
        return new ModelAndView("addUser");
    }
    
    //同樣的,需要先得到介面
    @RequestMapping("preLogin")
    public ModelAndView preLogin(){
        return new ModelAndView("login");
    }
複製程式碼

然後是註冊的過程,先呼叫 addUser() 方法,如果使用者註冊的時候出現了問題,比如說使用者名稱、電話號碼或者郵箱已被註冊,就直接丟擲異常,就沒有執行驗證碼驗證的方法了,如果沒問題,就接著檢測驗證碼輸入,將表單輸入與驗證碼文字進行比較

    @RequestMapping("/addUser")
    public ModelAndView addUser(User user, HttpServletRequest request){
        ModelAndView modelAndView;
        //如果下面的 try 語句塊沒有丟擲異常,則返回 addUserSuccessful.jsp
        modelAndView = new ModelAndView("addUserSuccessful");
        try{
            //先呼叫新增使用者的方法,看看有沒有因為不符規定的輸入而導致異常丟擲
            userService.addUser(user);
            //然後再看有沒有因為驗證碼錯誤而導致異常丟擲
            exceptionService.verifyCodeException(request.getParameter("verifyCode"), verifyCode.getText());
        } catch (UserException e){
            //如果捕獲異常,就帶著異常資訊返回註冊介面
            modelAndView = new ModelAndView("addUser");
            modelAndView.addObject("message", e.getMessage());
        }
        return modelAndView;
    }
複製程式碼

登陸的過程,也是先先檢查使用者輸入資訊是否有誤,再檢查驗證碼資訊

    //登陸的邏輯和上面是一樣的
    @RequestMapping("/login")
    public ModelAndView login(User user, HttpServletRequest request) {
        ModelAndView modelAndView;
        modelAndView = new ModelAndView("loginSuccessful");
        try {
            userService.login(user);
            exceptionService.verifyCodeException(request.getParameter("verifyCode"), verifyCode.getText());
        } catch (UserException e){
            modelAndView = new ModelAndView("login");
            modelAndView.addObject("message", e.getMessage());
        }
        return modelAndView;
    }
複製程式碼

最後是關於輸出驗證碼圖片的操作:

    //得到驗證碼,然後用於 jsp 檔案的 <img> 標籤的 src 屬性中
    @RequestMapping("/getVerifyCode")
    public void setVerifyCode(HttpServletResponse response)
            throws IOException{
        //設定響應格式
        response.setContentType("image/jpg");
        //得到圖片
        BufferedImage image = verifyCode.getImage();
        //輸出
        verifyCode.output(image, response.getOutputStream());
    }
複製程式碼

相關文章