第四周
回顾
上周的主要内容是使用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.javaStudent 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方法
StudentServicepublic interface StudentService{ Student getStudent(int 学生ID); }
接口的具体实现在impl中
StudentServiceImplpublic 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中用上它们了!
- 实例化SessionManager
返回的是我们自己的MySessionManager - 实例化Realm
返回的是我们自己的MyShiroRealm - 实例化SecurityManager,
并设置session为第一步实例化出来的session
以及设置realm为第二步实例化出来的realm - 实例化ShiroFilterFactoryBean
并设置securityManager为第三步实例化出来的securityManager
最终效果如下图