文章來自我的博客
正文之前
之前有做過一個練手的商品管理的小項目洪燥,然后用 SSM 重構(gòu)了乳乌,接下來又做了一個模擬注冊登錄的界面,然后前兩天用 SSM 重構(gòu)了這個項目的后臺代碼汉操,修改了一點前端頁面,后臺功能不變
接下來還有在此基礎上做新的功能:保持登陸狀態(tài)磷瘤,查看個人信息等,然后把注冊登陸界面整合至商品管理的項目采缚,一步一步擴大
正文
1. 項目截圖
下面的文檔中有給出截圖
2. 功能實現(xiàn)
- 注冊、登陸
常規(guī)的注冊登陸操作
- 驗證碼
輸出驗證碼糊识,單擊驗證碼圖片可刷新
- 驗證用戶
注冊是驗證用戶名、電話號碼和郵箱是否已被注冊赂苗,登陸時驗證用戶是否已注冊以及密碼是否正確贮尉,兩個過程中都有檢測驗證碼的準確性
3. 具體說明
我還是放上項目的 README 文檔吧:
Registration-login-interface2
Version 0.1
使用 SSM 框架來對原先的 Registration-login-interface 進行重構(gòu)拌滋,頁面做細微改動猜谚,后臺使用框架,來達到同樣的效果:
0.1 版本是使用框架進行重構(gòu)昌犹,接下來的 0.2 版本將會是添加一些功能:用戶保持登陸狀態(tài),添加一張 SQL 表來存放用戶信息斜姥,并在頁面中進行個人信息添加和修改
1. 文件結(jié)構(gòu)
關于建包和創(chuàng)建哪些類,不多說铸敏,直接上一整個項目的圖:
2. 配置文件(config 包)
將 Spring 和 MyBatis 整合的方式,在之前的 new-p-m 和 mybatis-spring 官方文檔 中都能找到答案杈笔,這里直接給出配置:
數(shù)據(jù)庫信息(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"/>
<!-- 注解驅(qū)動 -->
<mvc:annotation-driven/>
<!-- 配置視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 這兩行代碼是為了不讓靜態(tài)資源被 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">
<!-- 將數(shù)據(jù)庫信息配置在外部文件中蒙具,使用占位符來代替具體信息的配置 -->
<context:property-placeholder location="classpath:config/db.properties"/>
<!-- 配置數(shù)據(jù)源 -->
<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">
<!-- 使用上面定義的數(shù)據(jù)源來進行配置 -->
<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)
為了節(jié)省篇幅鞠呈,這里不給出實體類代碼,和 Registration-login-interface 中的實體類是一樣的
5. 自定義異常
自定義的異常是調(diào)用父類方法來實現(xiàn)的蚁吝,因用戶注冊或登陸時輸入有誤而拋出
public class UserException extends Exception {
//自定義異常
public UserException(String message) {
super(message);
}
}
6. 映射器
MyBatis-Spring 提供的 MapperFactoryBean 能夠進行動態(tài)代理,能夠?qū)?shù)據(jù)映射器接口注入到 Service 層中的 Bean 里怀伦,注意,一定要是接口房待,不能是實現(xiàn)類,所以我們這里寫了一個 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 是從數(shù)據(jù)庫中查找出的記錄败许,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 的實現(xiàn)淑蔚,在注冊和登陸過程中要使用:
//先創(chuàng)建 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 的實現(xiàn):
//先是用構(gòu)造器注入來創(chuàng)建 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);
}
//根據(jù)用戶輸入名字去數(shù)據(jù)庫查找有沒有這個用戶醋寝,如果沒有带迟,就會拋出異常
public void login(User user) throws UserException {
User db_user = userMapper.findUserByName(user.getUsername());
exceptionService.loginException(user, db_user);
}
可能有人會覺得為什么登陸的方法沒有返回值,其實如果登入成功仓犬,也就是沒有拋出異常,在 Controller 中就可以接著執(zhí)行后面的方法搀继,如果用戶名或密碼錯誤,是會拋出異常叽躯,中斷程序的
8. Controller
到了關鍵的一步,Controller 負責處理 DispatcherServlet 分發(fā)的請求:
首先是使用構(gòu)造器注入來創(chuàng)建三個 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 是用來檢測注冊和登陸過程中是否出現(xiàn)錯誤
在注冊和登陸之前黑滴,都需要得到帶有表單的頁面:
//在注冊之前需要先得到注冊的界面
@RequestMapping("/preAdd")
public ModelAndView preAdd(){
return new ModelAndView("addUser");
}
//同樣的,需要先得到界面
@RequestMapping("preLogin")
public ModelAndView preLogin(){
return new ModelAndView("login");
}
然后是注冊的過程馋嗜,先調(diào)用 addUser() 方法,如果用戶注冊的時候出現(xiàn)了問題葛菇,比如說用戶名、電話號碼或者郵箱已被注冊眯停,就直接拋出異常卿泽,就沒有執(zhí)行驗證碼驗證的方法了,如果沒問題齐邦,就接著檢測驗證碼輸入措拇,將表單輸入與驗證碼文本進行比較
@RequestMapping("/addUser")
public ModelAndView addUser(User user, HttpServletRequest request){
ModelAndView modelAndView;
//如果下面的 try 語句塊沒有拋出異常,則返回 addUserSuccessful.jsp
modelAndView = new ModelAndView("addUserSuccessful");
try{
//先調(diào)用添加用戶的方法丐吓,看看有沒有因為不符規(guī)定的輸入而導致異常拋出
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());
}