Spring整合SpringMVC与Mybatis(SSM)实现完整登录功能流程详解

Java
356
0
0
2023-02-27
目录
  • 项目演示
  • 1 创建工程完成配置
  • 2 表设计
  • 3 实体类
  • 4 mapper
  • 5 service
  • Impl 实现类
  • 异常
  • 6 controller
  • 7 工具类
  • MD5
  • 统一返回对象
  • 8 前端页面
  • 总结

项目演示

演示中只用一个用户登录,只是为了测试功能,实际使用中是根据数据库表内数据来决定的。

1 创建工程完成配置

1 ieda新建maven项目名字随便起

2 点击文件,项目 ,模块 ,点加号添加web模块 ,这里要注意文件路径

我们改一下第一项的路径 从web 改成src\main\webapp

下面Web资源目录也改一下

选择webapp 点击右下角创建工件系统自动创建工件,点确认

发现我们webapp有个小蓝点就算成功了。

这个时候先在java包下建一个test类构建一下项目,看一下target包出来没,如果先配置tomcat target得后期手动拉出来,很麻烦。

现在开始配tomcat 点击右上角添加配置

点击加号选择tomcat本地

点击配置,主目录放我们tomcat的文件夹,系统会自动生成其他的

完成后点右下角的小红灯泡配置工件,进去后不用动,把下面的应用程序上下文改为 “/” 就行。

点击确认。 为了验证配置是否可用进入webapp目录下,新建一个index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<h1> 配置完成,终于可以开始了</h1>
</body>
</html>

点击gebug启动tomcat ,根据之前的端口在浏览器看看有没有我们写的网页

如果网页显示正常我们开始添加依赖和写配置文件。

pom.xml 这里只显示需要加的,每个依赖我也标上了注释

 <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <!--        webmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.8</version>
        </dependency>
        <!--        json序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.12.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9</version>
        </dependency>
        <!--        JDBC-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.8</version>
        </dependency>
        <!--        mybatis-plus 自动引入mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.4.3.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!--        druid 连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!--        junit单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.8</version>
        </dependency>
        <!--        javax.servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
            <!--只在测试时用-->
        </dependency>
        <!--        日志组件-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--   MD5加密组件-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>
    </dependencies>

在resources下新建applicationContext.xml 来作为配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/task
            http://www.springframework.org/schema/task/spring-task.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--    组件扫描,完成对象的创建与初始化  base-package写自己的 -->
    <context:component-scan base-package="com.xatu"/>
    <!--    开启注解模式-->
    <mvc:annotation-driven>
        <!--        消息转换器-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <!--设置属性 媒体类型(list集合)-->
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!--    把静态资源排除在外-->
    <mvc:default-servlet-handler/>
    <!--MyBatis与Spring的整合配置 -->
    <!--1.配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--        实例化DruidDataSource设置与连接相关的属性-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <!--        jdbc驱动-->
        <property name="url" value="jdbc:mysql://localhost:3306/cnpc_comment?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <!--        URL连接字符串   用户名密码写自己mysql数据库的账号密码-->
        <property name="username" value="******"/>
        <!--        用户名-->
        <property name="password" value="******"/>
        <!--        密码-->
        <property name="initialSize" value="5"/>
        <!--        初始化时几个数据库连接,初始数量-->
        <property name="maxActive" value="20"/>
        <!--        最大数量-->
    </bean>
    <!--2.配置SessionFactoryBean-->
    <!--SqlSessionFactoryBean用于根据配置信息创建SqlSessionFactory,不再需要我们自己编码创建-->
    <!--   没有引入mybatis-plus之前的 <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
    <bean id="sessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--        数据源  ref关联到 上面的dataSource对象-->
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!--        加载对象过程中扫描哪个文件 保存sql文件的xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--        本身框架配置文件路径-->
        <!-- MyBaits Plus 3.4以后版本需要在MybatisSqlSessionFactoryBean.plugins属性中进行设置 -->
        <property name="plugins">
            <array>
                <bean class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
                    <property name="interceptors">
                        <list>
                            <bean class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"/>
                        </list>
                    </property>
                </bean>
            </array>
        </property>
        <!--        插件配置项 代表一系列插件,
        实例化一个MybatisPlusInterceptor插件容器
        用interceptors进行指定
                   -->
    </bean>
    <!--3.配置Mapper扫描器   value 写自己mapper的位置-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xatu.mapper"/>
    </bean>
    <!--    声明式事务配置-->
    <!--    1,定义事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--    2,开启注解模式的声明式事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

mybatis.config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--    驼峰命名转换-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!-- MyBatis Plus 3.4版本以前配置分页插件 -->
    <!--<plugins>
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor"></plugin>
    </plugins>-->
</configuration>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--向控制台书写日志的追加器-->
        <encoder>
            <pattern>%d{HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n</pattern>
            <!-- 线程名字,产生日志的时间,日志的级别,由哪个类产生的日志-->
            <charset>GBK</charset>
        </encoder>
    </appender>
    <root level="debug">
        <!-- 最低允许输出debug级别的日志,日志向控制台进行输出-->
        <appender-ref ref="console"/>
    </root>
</configuration>

mappers包下的manager.xml 他要跟自己的mapper包进行绑定 由于用的mybatis-plus所以不用自己写语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xatu.mapper.">
</map

基本配置就完成了

2 表设计

表名叫manager (实体类要绑定的注意)

3 实体类

@TableName("manager")
public class Manager {
    @TableId(type = IdType.AUTO)
    private Long managerId;
    private String username;
    private String password;
    private Integer salt;
    @Override
    public String toString() {
        return "Manager{" +
                "userId=" + managerId +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", salt=" + salt +
                '}';
    }
    public Long getUserId() {
        return managerId;
    }
    public void setUserId(Long userId) {
        this.managerId = userId;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Integer getSalt() {
        return salt;
    }
    public void setSalt(Integer salt) {
        this.salt = salt;
    }
}

4 mapper

public interface ManagerMapper extends BaseMapper<Manager> {
}

5 service

public interface ManagerService {
    public Manager checkLogin1(String username, String password);
}

Impl 实现类

先使用mapper查看是否用这个用户名的用户再调用用户表的信息来判断密码是否正确,我们这里假设没有重名用户。

@Service
//默认方法不开启事务
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class ManagerServiceImpl implements ManagerService {
    @Resource
    private ManagerMapper managerMapper;
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Manager checkLogin1(String username, String password) {
        QueryWrapper<Manager> wrapper = new QueryWrapper();
        wrapper.eq("username", username);
        Manager manager = managerMapper.selectOne(wrapper);
        if (manager == null) {
            throw new MemberException("用户不存在");
        }
        String md5 = MD5Utils.md5Digest(password, manager.getSalt());
        if (!md5.equals(manager.getPassword())) {
            throw new MemberException("您输入的密码有误");
        }
        return manager;
    }
}

异常

public class MemberException extends RuntimeException{
    public MemberException(String message){
        super(message);
    }
}

6 controller

调用service的chedkLogin1方法来检验登录 为了安全,在成功登录后我们将密码 和盐值 设为null

@RestController
@RequestMapping("/api/manager")
public class ManagerController {
    @Resource
    private ManagerService managerService;
    @PostMapping("/check_login1")
    public ResponseUtils checkLogin1(String username , String password){
        ResponseUtils resp ;
            resp = new ResponseUtils();
            try {
                Manager manager = managerService.checkLogin1(username, password);
                manager.setPassword(null);
                manager.setSalt(null);
                resp = new ResponseUtils().put("manager" , manager);
            }catch (Exception e){
                e.printStackTrace();
                resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage());
            }
        return resp;
    }
}

7 工具类

MD5

用来使用hash算法使密码变复杂

public class MD5Utils {
    public static String md5Digest(String source ,Integer salt){
        char[] ca = source.toCharArray();
        for(int i = 0 ; i < ca.length ;i++){
            ca[i] = (char) (ca[i] + salt);
        }
        String target = new String(ca);
        String md5 = DigestUtils.md5Hex(target);
        return md5;
    }
}

统一返回对象

public class ResponseUtils {
    private String code;//服务处处理编码
    private String message;//服务器返回消息
    private Map data = new LinkedHashMap<>();//服务器返回的数据
    //成功
    public ResponseUtils(){
        this.code = "0";
        this.message = "success";
    }
    //失败,传入编码和消息
    public ResponseUtils(String code , String message){
        this.code = code;
        this.message = message;
    }
    //给data进行赋值
    public ResponseUtils put(String key , Object value){
        this.data.put(key, value);
        return this;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Map getData() {
        return data;
    }
    public void setData(Map data) {
        this.data = data;
    }
}

整体

8 前端页面

我们是有vue 并且使用element-plus组件库来简化和美化我们的页面

这里我选择的是创建静态文件包,里面放着常用文工具,需要的时候直接把包放在webapp下载页面编写时进行调用。没有的话请选择替他方法,方法很多。

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <!-- 引入样式 -->
    <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css" rel="external nofollow" >
    <!-- 引入组件库 -->
    <script src="/assets/vue/vue.global.js"></script>
    <script src="/assets/element-plus/index.full.js"></script>
    <script src="/assets/axios/axios.js"></script>
    <style>
        .login-box {
            border: 1px solid #DCDFE6;
            width: 350px;
            margin: 180px auto;
            padding: 35px 35px 15px 35px;
            border-radius: 5px;
            -webkit-border-radius: 5px;
            -moz-border-radius: 5px;
            box-shadow: 0 0 25px #909399;
        }
        .login-title{
            text-align: center;
            margin: 0 auto 40px auto;
            color: #303133;
        }
    </style>
</head>
<body>
<div id="app">
    <el-form ref="loginForm" label-width="80px" :rules="rules" :model="form" class="login-box">
        <h2 class="login-title">登录</h2>
        <el-form-item label="账号" prop="username">
            <el-input type="text" placeholder="请输入账号" v-model="form.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
            <el-input type="password" placeholder="请输入密码" v-model="form.password"></el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" v-on:click="onSubmit('loginForm')" style="width:200px">登录</el-button>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" v-on:click="returnBack" style="width:200px">退出</el-button>
        </el-form-item>
    </el-form>
</div>
<script>
    const Main = {
        data() {
            return {
                form: {
                    username: ''
                    ,password: ''
                }
                ,rules:{
                    username: [
                        {required: true,message : '账号不能为空' , trigger:'blur'}
                    ],
                    password:[
                        {required: true,message : '密码不能为空' , trigger:'blur'}
                    ]
                }
            }
        }
        ,methods : {
            onSubmit(formName){
                const objApp = this;
                const form = this.$refs[formName];
                form.validate((valid) => {
                    if(valid){
                        console.info("表单校验成功,准备提交数据");
                        const form = this.form;
                        const $message = this.$message;
                        const params = new URLSearchParams();
                        params.append("username", form.username);
                        params.append("password", form.password);
                        axios.post("/api/manager/check_login1", params)
                            .then(function (response) {
                            console.info(response);
                            const json = response.data;
                            if(json.code=="0"){
                                window.location.href = "/index1.html";
                            }else{
                                $message.error({message:json.message, offset: 100});
                            }
                        });
                    }
                })
            }
            ,returnBack : function (){
                window.location.href = "/index.html";
            }
        }
    };
    //初始化Vue,绑定Main中的数据,利用ElementPlus对#app容器进行重新渲染
    const app = Vue.createApp(Main);
    app.use(ElementPlus);
    app.mount("#app");
</script>
</body>
</html>

至于另外两个页面,只是使用a标签添加链接 因为只是登录功能没有下一步所以建立两个网页来判断跳转与登录是否成功

总结

这算是完成了基础的登录功能,虽然我们并没有涉及到使用到session去保存用户信息以及去进行权限的判定,但是这个项目算是已经为后续的工作完成了基础的搭建后面只要在库中新建表或者在manager表上进行扩充,改写实体类并进行设计就可以添加更多的功能,这个前端登录页面也可以作为一个通用化的页面,只需要改变里面的的api接口就能做到重复使用。