SSM 重构注册登陆界面

文章来自我的博客

正文之前

之前有做过一个练手的商品管理的小项目,然后用 SSM 重构了,接下来又做了一个模拟注册登录的界面,然后前两天用 SSM 重构了这个项目的后台代码,修改了一点前端页面,后台功能不变

接下来还有在此基础上做新的功能:保持登陆状态,查看个人信息等,然后把注册登陆界面整合至商品管理的项目,一步一步扩大

正文

1. 项目截图

下面的文档中有给出截图

2. 功能实现

  1. 注册、登陆

常规的注册登陆操作

  1. 验证码

输出验证码,单击验证码图片可刷新

  1. 验证用户

注册是验证用户名、电话号码和邮箱是否已被注册,登陆时验证用户是否已注册以及密码是否正确,两个过程中都有检测验证码的准确性

3. 具体说明

我还是放上项目的 README 文档吧:

Registration-login-interface2

Version 0.1

使用 SSM 框架来对原先的 Registration-login-interface 进行重构,页面做细微改动,后台使用框架,来达到同样的效果:

0.1 版本是使用框架进行重构,接下来的 0.2 版本将会是添加一些功能:用户保持登陆状态,添加一张 SQL 表来存放用户信息,并在页面中进行个人信息添加和修改

1. 文件结构

关于建包和创建哪些类,不多说,直接上一整个项目的图:

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());
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 很有意思,人类以貌取人的条件是颜值越高机会就会越多,而影片中的著名学府同样也是以貌取人,却因长的丑陋(人类认为的)...
    糅文愈心阅读 1,253评论 0 1
  • gitignore需要忽略子目录中,拥有某后缀的文件。用**代表所有子目录例如,我要忽略以iml结尾的文件,可以如下所示
    java修炼阅读 24,656评论 2 6
  • 高中阶段能记起来的读过的书(我卑微存在的勉强证明 虽然读了这么多好的坏的乱七八糟的书,可头脑依旧没有开化): 沈复...
    爱写点文字的女孩阅读 285评论 0 0
  • “习武之人全在勤!天远,快点起来练武了!已经五更了!”师父这几句话虽未用内功传音,却也把与他一门之隔的梁天远从梦境...
    湘潭大学三翼传媒阅读 698评论 1 2