輕量級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

最終效果如下圖
在這裏插入圖片描述

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