轻量级Web框架应用_第四周

回顾

上周的主要内容是使用swagger配置生成我们的接口文档, 并通过http://localhost:8080/swagger-ui.html页面访问测试接口.

基础知识准备

SSM

Spring+SpringMVC+MyBatis 框架集
由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架
(复制百度百科)

MVC

模型(model)-视图(view)-控制器(controller)

  • Model层
    存放实体类; 如果不理解,可以想象成数据库中的一张表
    例:

    学生ID 学生姓名
    01 Robin

    在以上学生表中, 有两个字段(学生ID,学生姓名)
    那么对应我们Model层

    	public class Student {
        private int 学生ID;
        private String 学生姓名;
    
        public int get学生ID() {
            return 学生ID;
        }
    
        public void set学生ID(int 学生ID) {
            this.学生ID = 学生ID;
        }
    
        public String get学生姓名() {
            return 学生姓名;
        }
    
        public void set学生姓名(String 学生姓名) {
            this.学生姓名 = 学生姓名;
        }
    
        public Student() {
        }
    
        public Student(int 学生ID, String 学生姓名) {
            this.学生ID = 学生ID;
            this.学生姓名 = 学生姓名;
        }
    }
    
  • View层
    在Spring时期, 多使用jsp来展示前端页面

  • Controller层
    对用户操作进行响应; 从前端拿到数据并进行业务操作

  • Mapper层(也叫Dao层)
    对数据库进行数据持久化操作, 通常有对应的xml文件
    例: 我们对上面的学生表进行查询
    StudentMapper.java

    Student getStudent(int 学生ID)
    

    StudentMapper.xml

    <?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>
      <select id="getStudent" parameterType="int" resultType="Student">
      select * from 学生表
      where 学生ID=#{学生ID}
      </select>
    </mapper>
    
  • Service层
    将Mapper中的方法再次封装, Controller层调用的方法就是从这儿来的
    例: 在Service层中实现上文中的getStudent方法
    StudentService

    public interface StudentService{
    	Student getStudent(int 学生ID);
    }
    

    接口的具体实现在impl
    StudentServiceImpl

    public class implements StudentService{
    
    	public Student getStudent(int 学生ID){
    		Student student = StudentMapper.getStudent(学生ID)
    		return student;
    	}	
    }
    

创建数据库

首先分析需求:
我们有三个基本实体: 用户, 角色, 权限

  • 一个用户可以拥有多个角色
  • 一个角色可以被多个用户拥有
  • 一个角色可以拥有多个权限
  • 一个权限可以被多个角色拥有

那么在我们的ER图中可以这么表示
在这里插入图片描述
具体的数据库文件已上传班群

MVC框架搭建

建立新分支

开发前别忘了新建一个分支!!!

分包分层

首先在我们的项目下建立model, mapper, mapperxml, service文件夹, 注意分包分层, 如图所示:
在这里插入图片描述

mapper, mapperxml, model层的基本框架老师已经帮我们写好了, 源码已上传班群, 将其复制到项目中


mapper

打开mapper文件夹
首先在UserInfoMapper中, 添加一个通过用户ID查询用户信息的方法(如下图)
在这里插入图片描述
其次在UserRoleMapper中, 添加一个通过用户ID查询用户角色的方法(如下图)
在这里插入图片描述

mapperxml

首先打开resources/mapperxml文件夹
修改UserInfoMapper.xml文件, 新增一条select语句

  <!--根据用户ID查询用户信息-->
  <select id="findFirstByUserName" resultMap="BaseResultMap">
    SELECT * from user_info where user_name = #{userName} limit 1
  </select>

如图:
在这里插入图片描述
其次修改UserRoleMapper.xml, 同样新增一条select语句

  <!--根据用户ID查询用户角色-->
  <select id="findUserRolesByUserID" resultType="string">
    select role_info.role_name
    from user_role join user_info ui on user_role.user_id = ui.user_id
    join role_info ri on user_role.role_id = ri.role_id
    where ui.user_id = #{userID,jdbcType=INTEGER}
  </select>

如图:
在这里插入图片描述

service

首先在service文件夹下创建一个UserService, 注意是Interface
在这里插入图片描述
编写我们在mapper中定义的两个方法: 获取用户信息, 获取用户角色

public interface UserService {

    UserInfo getUserInfo(String userName);

    Set<String> getUserRoles(Integer userID);

}

其次在service文件夹下创建一个impl文件夹
在impl文件夹中创建一个UserServiceImpl的Class文件
在这里插入图片描述
在UserServiceImpl来实现我们的三个方法

@Service
public class UserServiceImpl implements UserService {

    @Autowired(required=false)
    private UserInfoMapper userInfoMapper;
    @Autowired(required=false)
    private UserRoleMapper userRoleMapper;

    @Override
    public UserInfo getUserInfo(String userName) {
        UserInfo userInfo = userInfoMapper.findFirstByUserName(userName);
        return userInfo;
    }

    @Override
    public Set<String> getUserRoles(Integer userID) {
        List<String> userRoles = userRoleMapper.findUserRolesByUserID(userID);
        Set<String> roles = new HashSet<>(userRoles);
        return roles;
    }

}

下图是service文件夹的结构, 对照看看有没有错
在这里插入图片描述
授人以鱼不如授人以渔, 试试看实现通过用户ID查询用户权限~

测试Mapper

写好了与数据库交互的Mapper, 不来测试下能不能用吗?

连接数据库

首先得把咱们的数据库连上
复制application.properties中的数据库连接语句

jdbc:mysql://localhost:3306/myhotel_course?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC

在最右侧有个 Database -> + -> Data Source -> MySql (如下图)
在这里插入图片描述
将其粘贴到URL框中, 并点击OK
在这里插入图片描述
期间可能会为你自动下载数据库连接驱动, 连接成功后会显示数据库结构(如图)
在这里插入图片描述

编写测试类

我们在test/java/cn.edu.fjzzit.web.myhotel文件夹下新建一个mapper文件夹
在mapper文件夹中新建一个UserInfoMapperTests的Class测试类
注意: 测试类应该与被测试类的项目结构保持一致(如图)
在这里插入图片描述
我们如何编写自己的测试类呢? 可以参照项目自动生成的测试类
MyhotelApplicationTests

package cn.edu.fjzzit.web.myhotel;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyhotelApplicationTests {

    @Test
    public void contextLoads() {
    }

}

不难看出一个测试类中用@RunWith(SpringRunner.class)@SpringBootTest来声明一个测试类
测试类中用@Test来声明一个测试方法
由于@SpringBootTest会启动整个SpringBoot项目, 但我们这里只测试Mapper, 不需要启动整个项目. 所以改用@MybatisTest的方式
那么我们现在gradle中引入一下mybatis测试包吧
打开build.gradle文件, 在dependencies下粘贴

//mybatis测试包
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.0'

如图:
在这里插入图片描述
接下来我们试着在UserInfoMapperTests中编写自己的测试类
首先我们来测试通过用户ID查询用户信息

@RunWith(SpringRunner.class)
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserInfoMapperTests {

    @Autowired
    private  UserInfoMapper userInfoMapper;

    @Test
    public void testFindFirstByUserName(){
        UserInfo userInfo = userInfoMapper.findFirstByUserName("");
        //断言: 返回值为空
        Assert.assertNull(userInfo);
    }
}

看到这个小箭头了吗? 点击运行!
在这里插入图片描述
下方控制台可以看到我们的测试通过啦!
在这里插入图片描述
再试着测试向用户信息表插入一条记录

    @Test
    public void testInsert(){
        String salt = "";
        UserInfo userInfo = new UserInfo();
        userInfo.setUserName("admin");
        //加密
        SimpleHash simpleHash = new SimpleHash(Md5Hash.ALGORITHM_NAME,"123456",salt,1);
        userInfo.setPassword(simpleHash.toString());
        userInfo.setUserState(Byte.parseByte("0"));
        userInfo.setCreateTime(new Date());
        userInfo.setSalt(salt);
        int effectedRows = userInfoMapper.insert(userInfo);
        //断言: 影响行数为一行
        Assert.assertEquals(effectedRows,1);
    }

同样, 点击方法名旁边的三角按钮测试下 !


Shiro

一个java安全框架, 执行身份验证、授权、密码和会话管理
在这里插入图片描述
首先我们在build.gradle中引入Shiro
还不会引入依赖的快去跟老师道歉 ! ! !

//shiro
implementation 'org.apache.shiro:shiro-spring:1.4.1'

基本的Shiro配置文件老师已经帮我们配置完成了, 将ShiroConfig文件复制到Config文件夹下
接下来要靠我们自己完善(文件已上传班群)

Realm (Shiro连接数据的桥梁)

首先我们在config文件夹下创建一个MyShiroRealm的Class文件
并且让其继承AuthorizingRealm, 继承这个类会实现两个方法, 一个授权一个认证
在这里插入图片描述
将光标点击到红色这一行上, 会提示实现方法
在这里插入图片描述
全选, 并且点击OK, 会为我们自动生成授权和认证的方法
在这里插入图片描述
如图
在这里插入图片描述
编写我们的授权方法

    @Autowired
    private UserService userService;

    //授权(用户认证)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(userService.getUserRoles(userInfo.getUserId()));
        //simpleAuthorizationInfo.setStringPermissions();
        return simpleAuthorizationInfo;
    }

编写认证方法

    //认证(身份认证)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken authToken = (UsernamePasswordToken) token;
        String userName = authToken.getUsername();
        UserInfo userInfo = userService.getUserInfo(userName);
        if(userInfo == null){
            throw new UnknownAccountException();
        }else if(userInfo.getUserState() == 2){
            throw new DisabledAccountException();
        }else {
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo,
                    userInfo.getPassword(),
                    ByteSource.Util.bytes(userInfo.getSalt()),
                    getName()
            );
            return authenticationInfo;
        }
    }

session管理

别忘了后端也是需要考虑session的, 在config中编写我们自己的session吧!
在config文件夹中新建一个命名为MySessionManager的Class
并让其继承DefaultWebSessionManager类
我们来实现其中的getSessionId方法(输入getSessionId, idea会帮我们自动补全)
在这里插入图片描述
在这里插入图片描述
如果sessionID为null时, 返回 super.getSessionId(request, response);
否则呢?
我们按住Ctrl点击getSessionId
来到了源码中的getSessionId
在这里插入图片描述
看不出啥门路? 再次按住Ctrl点击getReferencedSessionId
哈! 把它id != null的地方的源码复制出来
在这里插入图片描述
最后, 我们的MySessionManager应该是长这样的
在这里插入图片描述

ShiroConfig

完成了我们的session和realm, 得在config中用上它们了!

  1. 实例化SessionManager
    返回的是我们自己的MySessionManager
  2. 实例化Realm
    返回的是我们自己的MyShiroRealm
  3. 实例化SecurityManager,
    并设置session为第一步实例化出来的session
    以及设置realm为第二步实例化出来的realm
  4. 实例化ShiroFilterFactoryBean
    并设置securityManager为第三步实例化出来的securityManager

最终效果如下图
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章