音乐播放器详解
目錄
1.項目設計
?2.創建項目 配置文件
2.1創建項目
?2.2配置文件
2.2.1在application.properties 中添加配置文件
2.2.2在resources 目錄下創建mapper
3.數據庫的設計與實現
?4.交互接口的設計
5.工具包
5.1設置統一響應類
?5.2Constant類
5.3了解 MD5 加密 和?BCrypt?加密
?5.4在Config中 注入?BCryptPasswordEncoder?對象
5.5添加攔截器
5.5.1LoginInterceptor 類
5.5.2AppConfig 類
6.登錄模塊
6.1創建User實體類
6.2使用 Mybatis 操作數據庫
6.2.1?在 UserServer 中添加代碼
6.2.2在 UserMapper 中添加代碼
6.2.3在 UserMapper.xml 中添加代碼
6.3創建 UserConroller 添加代碼
?7.注冊模塊
7.1使用 Mybatis 操作數據庫
7.1.1在?UserServer 中添加代碼
7.1.2在 UserMapper 中添加代碼
7.1.3在 UserMapper.xml 中添加代碼
7.2向 UserController 中添加代碼
8.退出功能
8.1向 UserController 中添加代碼
8.2登錄注冊測試
?9.上傳音樂模塊
9.1創建Music 實體類
9.2使用Mybatis 操作數據庫
9.2.1?在 MusicServer 中添加代碼
9.2.2在 MusicMapper 中添加代碼
9.2.3在 MusicMapper.xml 中添加代碼
9.3向 MusicController 中添加代碼
10.播放音樂模塊
10.1向 MusicController 中添加代碼
?11.刪除音樂模塊
11.1使用 Mybatis 操作數據庫
11.1.1?在 MusicServer 和 CollectServer 中添加代碼
11.1.2在 MusicMapper 和 CollectMapper 中添加代碼
11.1.3在 MusicMapper.xml 和 CollectMapper.xml 中添加代碼
11.2刪除單一音樂功能
11.2.1向 MusicController 中添加代碼
?11.3刪除多個音樂功能
11.3.1向 MusicConroller 中添加代碼
12.收藏音樂模塊
12.1創建 Collect 實體類
12.2使用 Mybatis 操作數據庫
12.2.1在 CollectServer 中添加代碼
12.2.2?在 CollectMapper 中添加代碼
12.2.3在 CollectMapper.xml 中添加代碼
12.3向 CollectControll 中添加代碼
?13.取消收藏音樂模塊
13.1使用 Mybatis 操作數據庫
13.1.1?在 CollectServer 中添加代碼
13.1.2?在 CollectMapper 中添加代碼
13.1.3在 CollectMapper.xml 中添加代碼
13.2向 CollectControll 中添加代碼
?14.主頁面 - 查詢模塊
14.1使用 Mybatis 操作數據庫
14.1.1?在 MusicServer 中添加代碼
14.1.2在 MusicMapper 中添加代碼
14.1.3在 MusicMapper.xml 中添加代碼
14.2向 MusicController 中添加代碼
?15.收藏頁面 - 查詢模塊
15.1?使用 Mybatis 操作數據庫
15.1.1?在 CollectServer 中添加代碼
15.1.2在 CollectMapper 中添加代碼
15.1.3在 CollectMapper.xml 中添加代碼
15.2向 CollectController 中添加代碼
1.項目設計
前端:HTML+CSS+JavaScript+JQuery
后端:Spring MVC+Spring Boot + MyBatis
?2.創建項目 配置文件
2.1創建項目
?2.2配置文件
2.2.1在application.properties 中添加配置文件
配置數據庫
spring.datasource.url=jdbc:mysql://localhost:3306/onlinemusicserver?characterEncoding=utf8&useSSL=true spring.datasource.username=root spring.datasource.password=1234 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver配置MyBatis、文件上傳大小、上傳的路徑
mybatis.mapper-locations=classpath:mapper/**Mapper.xmlspring.servlet.multipart.max-file-size = 15MB spring.servlet.multipart.max-request-size=100MBupload.path=E:/logs/2.2.2在resources 目錄下創建mapper
mapper下添加 目錄 **.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 namespace="com.example.onlinemusicserver.mapper."對應的Mapper""></mapper>3.數據庫的設計與實現
用戶表
音樂表
收藏表
?4.交互接口的設計
上傳音樂
請求 POST /music/upload HTTP/1.1{singer, MultipartFile file}響應 {status: 1/-1 (1 為成功, -1 為失敗),message: "對應信息",data: "內容" }?收藏功能
請求 POST /collect/loveMusic HTTP/1.1{musicId: 1}響應 {status: 1/-1,message: "",data: "" }取消收藏功能
請求 POST /collect/deleteLoveMusic HTTP/1.1{musicId: 1}響應 {status: 1/-1,message: "",data: "" }收集頁面 — 空查詢 模糊查詢
請求 POST /collect/findLoveMusic HTTP/1.1{musicName: "可以為空可以不為空, 為空的時候,查詢所有, 不為空的時候, 模糊查詢"}響應 {status: 1/-1,message: "",data: {{musicId: "",title: "",author: "",uploadtime: "",path: "",userId: "",}...} }主頁頁面 — 空查詢 模糊查詢
請求 POST /music/findMusic HTTP/1.1{musicName: "可以為空可以不為空, 為空的時候,查詢所有, 不為空的時候, 模糊查詢"}響應 {status: 1/-1,message: "",data: {{musicId: "",title: "",author: "",uploadtime: "",path: "",userId: "",}...} }刪除單個音樂
請求 POST /music/delete HTTP/1.1{musicId: ""}響應 {status: 1/-1,message: "",data: "" }刪除多個音樂
請求 POST /music/deleteMore HTTP/1.1{musicId: "1 2 3 4 5"(數組)}響應 {status: 1/-1,message: "",data: "" }播放音樂
請求 GET /music/play?path="..." HTTP/1.1響應 {音樂的字節信息 }登錄功能
請求 POST /user/login HTTP/1.1{username: "",password: ""}響應 {status: 1/-1,message: "",data: "" }注銷功能
請求 GET /user/logout HTTP/1.1響應 HTTP/1.1 200注冊功能
請求 POST /user/register HTTP/1.1{username: "",password: ""}響應 {status: 1/-1,message: "",data: "" }5.工具包
5.1設置統一響應類
這個類是用來讓響應返回的格式統一的
public class ResponseBodyMessage<T> {private int status;private String message;private T data;public ResponseBodyMessage(int status, String message, T data) {this.status = status;this.message = message;this.data = data;} }?5.2Constant類
這個類是用來存儲不變的常量的. 例如設置了session對象 , 是一個字符串. 不變的字符串.將來在其他地方獲取對應的session需要通過這個字符串獲取
public class Constant {public static final String USER_SESSION_KEY = "user"; }5.3了解 MD5 加密 和?BCrypt?加密
MD5是一個安全的散列算法,輸入兩個不同的明文不會得到相同的輸出值,根據輸出值,不能得到原始的明文,即其過程不可逆; 但是雖然不可逆,但是不是說就是安全的。因為自從出現彩虹表后,這樣的密碼也"不安全"。
更安全的做法是加鹽或者長密碼等做法,讓整個加密的字符串變的更長,破解時間變慢。
Bcrypt就是一款加密工具,可以比較方便地實現數據的加密工作。你也可以簡單理解為它內部自己實現了隨機加鹽處理 。我們使用MD5加密,每次加密后的密文其實都是一樣的,這樣就方便了MD5通過大數據的方式進行破解。
Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解難度更大。
MD5使用示例(加鹽)
添加依賴
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency>?實現類
public class MD5Util {private static final String salt = "1q2w3e4r5t";//可任意設置public static String md5(String src) {return DigestUtils.md5Hex(src);}/*** 第一次加密 :模擬前端自己加密,然后傳到后端* @param inputPass* @return*/public static String inputPassToFormPass(String inputPass) {String str = ""+salt.charAt(1)+salt.charAt(3) + inputPass+salt.charAt(5) + salt.charAt(6);return md5(str);}/*** 第二次加密* @param formPass 前端加密過的密碼,傳給后端進行第2次加密* @param salt 后端當中的鹽值* @return*/public static String formPassToDBPass(String formPass, String salt) {String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5)+ salt.charAt(4);return md5(str);}/*** 上面兩個函數合到一起進行調用* @param inputPass* @param saltDB* @return*/public static String inputPassToDbPass(String inputPass, String saltDB) {String formPass = inputPassToFormPass(inputPass);String dbPass = formPassToDBPass(formPass, saltDB);return dbPass;} }?BCrypt使用示例
添加依賴
<!-- security依賴包 (加密)--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency>在springboot啟動類添加:
@SpringBootApplication(exclude ={org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})創建BCryptTest測試類:
public class BCryptTest { public static void main(String[] args) {//模擬從前端獲得的密碼String password = "123456";BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String newPassword = bCryptPasswordEncoder.encode(password);System.out.println("加密的密碼為: "+newPassword);//使用matches方法進行密碼的校驗boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);//返回trueSystem.out.println("加密的密碼和正確密碼對比結果: "+same_password_result);boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);//返回falseSystem.out.println("加密的密碼和錯誤的密碼對比結果: " + other_password_result); }運行結果(每次加密結果都不一樣)
?5.4在Config中 注入?BCryptPasswordEncoder?對象
@Configuration public class AppConfig implements WebMvcConfigurer {@Beanpublic BCryptPasswordEncoder getBCryptPasswordEncoder() {return new BCryptPasswordEncoder();} }5.5添加攔截器
5.5.1LoginInterceptor 類
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession httpSession = request.getSession(false);if(httpSession != null && httpSession.getAttribute(Constant.USER_SESSION_KEY) != null) {return true;}response.sendRedirect("/login.html");return false;} }5.5.2AppConfig 類
@Configuration public class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {LoginInterceptor loginInterceptor = new LoginInterceptor();registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/**/login.html").excludePathPatterns("/**/css/**.css").excludePathPatterns("/**/images/**").excludePathPatterns("/**/fonts/**").excludePathPatterns("/**/js/**.js").excludePathPatterns("/**/scss/**").excludePathPatterns("/**/user/login").excludePathPatterns("/**/user/register").excludePathPatterns("/**/user/logout");} }6.登錄模塊
6.1創建User實體類
@Data public class User {private int userId;private String username;private String password; }6.2使用 Mybatis 操作數據庫
這里登錄 需要進行 數據庫的查詢. 查詢是否存在當前 username 的用戶.
所以要設計, 通過用戶名查找用戶信息?
6.2.1?在 UserServer 中添加代碼
public User selectByName(String username) {return userMapper.selectByName(username);}6.2.2在 UserMapper 中添加代碼
/*** 通過用戶名去查找用戶信息, 用來對比登錄信息.* @param username 用戶名* @return 對應用戶名的用戶信息*/User selectByName(String username);6.2.3在 UserMapper.xml 中添加代碼
<select id="selectByName" resultType="com.example.onlinemusicserver.model.User">select * from user where username=#{username};</select>6.3創建 UserConroller 添加代碼
?7.注冊模塊
7.1使用 Mybatis 操作數據庫
這里注冊, 需要查看當前用戶是否存在, 存在就不能注冊, 通過用戶查找, 這里已經實現.
注冊一個新用戶還需要 向數據庫中添加一個新的用戶信息
7.1.1在?UserServer 中添加代碼
public int addnewUser(User newUser) {return userMapper.addnewUser(newUser);}7.1.2在 UserMapper 中添加代碼
/*** 注冊新的用戶* @param newUser 新用戶信息* @return*/int addnewUser(User newUser);7.1.3在 UserMapper.xml 中添加代碼
<insert id="addnewUser">insert into user(username,password) values (#{username},#{password});</insert>7.2向 UserController 中添加代碼
8.退出功能
這里點擊退出之后, 直接刪除 對應 的 session 即可
8.1向 UserController 中添加代碼
直接刪除對應session 為?Constant.USER_SESSION_KEY, 然后跳轉到login.html
@RequestMapping("/logout")public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {HttpSession session = request.getSession(false);// 攔截器的攔截, 所以不可能出現session為空的情況session.removeAttribute(Constant.USER_SESSION_KEY);response.sendRedirect("login.html");}8.2登錄注冊測試
?
?9.上傳音樂模塊
9.1創建Music 實體類
@Data public class Music {private int musicId;private String title;private String author;private Timestamp uploadtime;private String path;private int userId;private String srcPath; }9.2使用Mybatis 操作數據庫
上傳音樂, 要上傳 音樂名, 音樂歌手, 音樂地址, 上傳作者Id. (音樂上傳時間, 已經默認設置了. 不需要傳也可以)
通過音樂名去查找歌曲, 這里用來對當前歌曲判斷, 是否出現歌曲和歌手都相同的情況.
9.2.1?在 MusicServer 中添加代碼
public int insert(String title, String author, String path, int userId){return musicMapper.insert(title,author,path,userId);}public List<Music> selectByTitle(String title) {return musicMapper.selectByTitle(title);}9.2.2在 MusicMapper 中添加代碼
/*** 上傳音樂* @param title 音樂名* @param author 歌手* @param path 對應的地址* @param userId 上傳的用戶Id* @return 返回影響行數*/int insert(String title, String author, String path, int userId);/*** 通過音樂名去查找歌曲.* @param title 音樂名* @return 對應音樂名的所有歌曲*/List<Music> selectByTitle(String title);9.2.3在 MusicMapper.xml 中添加代碼
<insert id="insert">insert into music(title,author,path,userId) values (#{title},#{author},#{path},#{userId}); </insert><select id="selectByTitle" resultType="com.example.onlinemusicserver.model.Music">select * from music where title = #{title}; </select>9.3向 MusicController 中添加代碼
這里首先對session判斷, 判斷是否存在session. (配置攔截器之后就不需要判斷了)
去數據庫中查詢所有title相同的歌曲. 如果歌曲名相同,歌手也相同, 那么就上傳失敗.
創建文件夾. 將文件上傳到文件夾中.(文件名是以歌手-歌名創建, 為了防止重名無法讀取)
然后對該文件, 進行判斷, 判斷是不是 MP3 文件, 注意MP3文件, 字節碼中有 字符"TAG"
在數據庫中上傳數據. 注意這里的path.
@RestController @RequestMapping("/music") public class MusicController {@Autowiredprivate MusicServer musicServer;@Value("${upload.path}")public String SAVE_PATH;/*** 上傳音樂* @param singer* @param file* @param request* @return*/@RequestMapping("/upload")public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,@RequestPart("filename") MultipartFile file,HttpServletRequest request,HttpServletResponse response) {// 檢測登錄HttpSession session = request.getSession(false);if (session == null || session.getAttribute(Constant.USER_SESSION_KEY) == null) {System.out.println("當前未登錄!");return new ResponseBodyMessage<>(-1,"請登錄后上傳",false);}// 文件的類型String fileNameAndType = file.getOriginalFilename();// 防止出現重復的相同歌曲和相同歌手.可以出現相同歌曲不同歌手String title = fileNameAndType.substring(0,fileNameAndType.lastIndexOf('.'));// 可能出現多首名稱相同的歌曲, 所以用 ListList<Music> list = musicServer.selectByTitle(title);if(list != null){for(Music music : list) {if(music.getAuthor().equals(singer)){return new ResponseBodyMessage<>(-1,"當前歌手的歌曲已經存在!",false);}}}// 創建文件String path = SAVE_PATH +singer+"-"+fileNameAndType;File dest = new File(path);if(!dest.exists()) {dest.mkdirs();}try {file.transferTo(dest);//return new ResponseBodyMessage<>(1,"上傳成功!",true);} catch (IOException e) {e.printStackTrace();return new ResponseBodyMessage<>(-1,"服務器上傳失敗!",false);}// 這里對是不是 MP3 文件進行判斷. 主要是判斷是否存在 TAG 這個字符File file1 = new File(path);byte[] res = null;try {res = Files.readAllBytes(file1.toPath());if(res == null) {return new ResponseBodyMessage<>(-1,"當前文件不存在",false);}String str = new String(res);if(!str.contains("TAG")) {file1.delete();return new ResponseBodyMessage<>(-1,"當前不是mp3文件",false);}}catch (IOException e){e.printStackTrace();return new ResponseBodyMessage<>(-1,"服務器出現問題", false);}// 在數據庫中上傳數據User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);// 這里傳遞的 path 沒有帶 `.MP3` 后期在前端進行設置String uploadPath = "/music/play?path="+singer+"-"+title;try {int ret = musicServer.insert(title,singer,uploadPath,user.getUserId());if(ret == 1) {response.sendRedirect("/index.html");return new ResponseBodyMessage<>(1,"上傳成功",true);}else {return new ResponseBodyMessage<>(-1,"數據庫上傳失敗",false);}}catch (BindingException | IOException e) {dest.delete();return new ResponseBodyMessage<>(-1,"數據庫上傳失敗",false);}} }10.播放音樂模塊
10.1向 MusicController 中添加代碼
?11.刪除音樂模塊
11.1使用 Mybatis 操作數據庫
刪除音樂, 主要是兩個刪除, 一個是刪除單個, 根據單個musicId 刪除. 另一個是刪除多個, 根據多個 musicId 刪除.
?
這里根據 musicId 刪除, 需要去數據庫里查找 是否存在當前 musicId 的歌曲. 存在刪成功, 不存在刪失敗.
?
注意, 刪除的時候, 不僅要刪除 music表里的歌曲. 也要刪除 collect 表里的歌曲
11.1.1?在 MusicServer 和 CollectServer 中添加代碼
musicServer
public Music selectById(int musicId) {return musicMapper.selectById(musicId);}public int deleteById(int musicId) {return musicMapper.deleteById(musicId);}?collectServer
public int deleteLoveMusicById(int musicId){return collectMapper.deleteLoveMusicById(musicId);}11.1.2在 MusicMapper 和 CollectMapper 中添加代碼
musicMapper
/*** 通過音樂Id去查找歌曲* @param musicId 音樂Id* @return 查找到的音樂Id*/Music selectById(int musicId);/*** 刪除對應音樂Id的歌曲* @param musicId 音樂Id* @return 返回影響行數*/int deleteById(int musicId);collectMapper
/*** 刪除收藏表中音樂Id為musicId的* @param musicId 音樂Id* @return 返回受影響行數*/int deleteLoveMusicById(int musicId);11.1.3在 MusicMapper.xml 和 CollectMapper.xml 中添加代碼
<select id="selectById" resultType="com.example.onlinemusicserver.model.Music">select * from music where musicId = #{musicId};</select><delete id="deleteById">delete from music where musicId = #{musicId};</delete> <delete id="deleteLoveMusicById">delete from collect where musicId = #{musicId};</delete>11.2刪除單一音樂功能
11.2.1向 MusicController 中添加代碼
?11.3刪除多個音樂功能
11.3.1向 MusicConroller 中添加代碼
12.收藏音樂模塊
12.1創建 Collect 實體類
@Data public class Collect {private int collectId;private int userId;private int musicId; }12.2使用 Mybatis 操作數據庫
首先要通過 musicId 和 userId去查找當前是否存在 collect 表中 在通過 musicId 和 userId 去添加歌曲12.2.1在 CollectServer 中添加代碼
public Collect findCollectMusic(int userId, int musicId) {return collectMapper.findCollectMusic(userId,musicId);}public int insertLoveMusic(int userId, int musicId) {return collectMapper.insertLoveMusic(userId, musicId);}12.2.2?在 CollectMapper 中添加代碼
/*** 查看對應用戶是否已經收藏了該音樂* @param userId 用戶Id* @param musicId 音樂Id* @return 收藏歌單*/Collect findCollectMusic(int userId, int musicId);/*** 收藏音樂* @param userId 用戶Id* @param musicId 音樂Id* @return 返回影響行數*/int insertLoveMusic(int userId, int musicId);12.2.3在 CollectMapper.xml 中添加代碼
<select id="findCollectMusic" resultType="com.example.onlinemusicserver.model.Collect">select * from collect where userId = #{userId} and musicId = #{musicId};</select><insert id="insertLoveMusic">insert into collect(userId,musicId) values(#{userId},#{musicId});</insert>12.3向 CollectControll 中添加代碼
?13.取消收藏音樂模塊
13.1使用 Mybatis 操作數據庫
13.1.1?在 CollectServer 中添加代碼
public int deleteLoveMusic(int userId,int musicId){return collectMapper.deleteLoveMusic(userId,musicId);}13.1.2?在 CollectMapper 中添加代碼
/*** 刪除用戶收藏的對應的音樂Id* @param userId 用戶Id* @param musicId 音樂Id* @return 受影響行數*/int deleteLoveMusic(int userId,int musicId);13.1.3在 CollectMapper.xml 中添加代碼
<delete id="deleteLoveMusic">delete from collect where userId = #{userId} and musicId = #{musicId}</delete>13.2向 CollectControll 中添加代碼
?14.主頁面 - 查詢模塊
14.1使用 Mybatis 操作數據庫
14.1.1?在 MusicServer 中添加代碼
public List<Music> findMusic() {return musicMapper.findMusic();}public List<Music> findMusicByName(String name) {return musicMapper.findMusicByName(name);}14.1.2在 MusicMapper 中添加代碼
/*** 查找所有的歌曲* @return 所有的歌曲*/List<Music> findMusic();/*** 支持模糊查詢的歌曲.* @param name 部分歌曲名* @return 對應所有的歌曲*/List<Music> findMusicByName(String name);14.1.3在 MusicMapper.xml 中添加代碼
<select id="findMusic" resultType="com.example.onlinemusicserver.model.Music">select * from music;</select><select id="findMusicByName" resultType="com.example.onlinemusicserver.model.Music">select * from music where title like concat('%',#{name},'%');</select>14.2向 MusicController 中添加代碼
這里判斷前端傳來的 name是否為空
?15.收藏頁面 - 查詢模塊
15.1?使用 Mybatis 操作數據庫
15.1.1?在 CollectServer 中添加代碼
public List<Music> findLoveMusicByUserId(int userId){return collectMapper.findLoveMusicByUserId(userId);}public List<Music> findLoveMusicByNameAndUserId(String name,int userId){return collectMapper.findLoveMusicByNameAndUserId(name,userId);}15.1.2在 CollectMapper 中添加代碼
/*** 查找用戶收藏的所有音樂* @param userId 用戶Id* @return 返回查詢到的所有音樂*/List<Music> findLoveMusicByUserId(int userId);/*** 查找用戶收藏音樂中名字帶有 name的音樂* @param name 部分名字* @param userId 用戶Id* @return 返回查詢到的所有音樂*/List<Music> findLoveMusicByNameAndUserId(String name,int userId);15.1.3在 CollectMapper.xml 中添加代碼
<select id="findLoveMusicByUserId" resultType="com.example.onlinemusicserver.model.Music">select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId};</select><select id="findLoveMusicByNameAndUserId" resultType="com.example.onlinemusicserver.model.Music">select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId} and m.title like concat('%',#{name},'%');</select>15.2向 CollectController 中添加代碼
這里判斷前端傳來的 name是否為空
?
總結
- 上一篇: 数据库质疑修复(SUSPECT)总结,D
- 下一篇: APP测试常见功能测试点汇总,赶紧来记笔