第四周
回顧
上週的主要內容是使用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
最終效果如下圖