目录
该项目主要实现的功能如下:
- 用户登录
- 上传音乐
- 删除指定音乐
- 批量删除选中的音乐
- 查询你想要的音乐(支持模糊查询)
- 添加音乐至喜欢的列表
- 移除喜欢的音乐
此项目本地访问链接:
http://192.168.126.129:8081/login.html
所实现的主要页面如下:
首先应该先创建一个spring boot项目,然后在MySQL中创建数据库。
在项目下创建所需包如下所示:
一、数据库设计
1.1 创建一个数据库
-- 数据库
drop database if exists `musicserver`;
create database if not exists `musicserver` character set utf8;
-- 使用数据库
use `musicserver`;
1.2 创建user表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(255) NOT NULL
);
1.3 创建music表
DROP TABLE IF EXISTS `music`;
CREATE TABLE `music` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`title` varchar(50) NOT NULL,
`singer` varchar(30) NOT NULL,
`time` varchar(13) NOT NULL,
`url` varchar(1000) NOT NULL,
`userid` int(11) NOT NULL
);
1.4 创建lovemusic表
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`music_id` int(11) NOT NULL
);
二、配置数据库和xml
接下来就是在项目配置文件中对数据库进行相应的配置。
打开application.properties配置如下信息:
#配置数据库
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/musicserver?characterEncoding=utf8&serverTimezone=UTC
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#在服务器上进行数据库配置
server.port=8081
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/musicserver?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
#音乐上传后的本地路径
#music.local.path=F:/Git_demo/java-learning-2/onlinemusic/music1
#音乐上传后服务器路径
music.local.path=/root/music
# 配置springboot日志调试模式是否开启
debug=true
# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG
logging.level.org.springframework.boot.autoconfigure: error
三、登录模块设计
3.1创建一个User
类
//在package com.example.musicserver.model包中创建User类
package com.example.onlinemusic.model;
import lombok.Data;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-04
* Time: 8:33
*/
@Data
public class User {
private int id;
private String username;
private String password;
}
3.2 创建对应的Mapper
分别在对应的mapper
包下创建UserMapper
接口:
package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.User;
import org.apache.ibatis.annotations.Mapper;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-04
* Time: 8:35
*/
@Mapper
public interface UserMapper {
User login(User loginUser);
}
3.3 创建UserMapper.xml
在resource
目录下,新建mybatis
文件夹,并在此文件夹下新建UserMapper.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.onlinemusic.mapper.UserMapper">
<select id="login" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username} and password=#{password}
</select>
<select id="selectByName" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username};
</select>
</mapper>
3.4 登录的请求和响应设计
请求:
{
post,
/user/login
data:{
username,password}
}
响应:
{
"status": 0,
"message": "登录成功",
"data": {
"id": xxxxx,
"username": xxxxxx,
"password": xxxxxxxx
}
}
响应体设计字段解释:
{
状态码,设置为0代表成功,-1代表失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息
}
3.5 设计统一的响应体类工具类
在tool
包下创建ResponseBodyMessage
类:
package com.example.onlinemusic.tools;
import lombok.Data;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-07
* Time: 19:19
*/
@Data
public class ResponseBodyMessage <T>{
private int status;//状态码 >=0代表登录成功 <0代表登录失败
private String message;//返回的信息【出错的原因? 没错的原因?】
private T data;//返回给前端的数据
public ResponseBodyMessage(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
3.6 创建UserController类
在controller
包下创建:
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserMapper userMapper;
@Resource
BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/login1")
public ResponseBodyMessage<User> login1(@RequestParam String username,@RequestParam String password,
HttpServletRequest request){
User userlogin = new User();
userlogin.setUsername(username);
userlogin.setPassword(password);
User user = userMapper.login(userlogin);
if(user != null){
System.out.println("登录成功!");
// request.getSession().setAttribute("USERINFO_SESSION_KEY",user);
//修改session的代码为 :
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);
return new ResponseBodyMessage<>(0,"登录成功",userlogin);
}else{
System.out.println("登录失败!");
return new ResponseBodyMessage<>(-1,"登录失败",userlogin);
}
}
}
这里对注解进行说明:
@RestController
:@ResponseBody + @Controller
合在一起的作用。@Controller
注解,表明了这个类是一个控制器类,@ResponseBody
表示方法的返回值直接以指定的格式写入Http response body
中。@RequestMapping
: 使用@RequestMapping
来映射请求,也就是通过它来指定控制器可以处理哪些URL请求。@RequestParam
:将请求参数绑定到你控制器的方法参数上,如果这个参数是非必传的可以写为:RequestParam(required = false)
,默认是true。
上述代码中设置了session
对象,此时的key
值是一个字符串,将来在其他地方获取对应的session
需要通过这个字符串获取,但是存在一定的写错的情况。所以,此时建议把他定义为一个常量。在tools
包中新建一个Constant
类:
package com.example.onlinemusic.tools;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-07
* Time: 19:32
*/
public class Constant {
public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
登录成功验证
这里在进行验证之前要先在user表中插入用户信息:
INSERT INTO user(username,password)
VALUES("zhang","123");
然后使用postman
验证登录功能:
这里可以看到返回的相应数据,但是对于密码是显式显示的,为了保证用户密码安全,对密码进行相应的加密处理。
BCrypt加密的原理
MD5加密
MD5
是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆;但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
彩虹表:彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
不安全的原因:
- 暴力攻击速度很快
- 字典表很大
- 碰撞
更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的。
这里我们介绍加盐的做法:盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中。
添加相应MD5
的依赖:
<!-- 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>
在tools
包,新建一个MD5Util
类:
package com.example.onlinemusic.tools;
import org.apache.commons.codec.digest.DigestUtils;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-07
* Time: 19:48
*/
public class MD5Util {
//定义一个固定的盐值
private static final String salt = "1b2i3t4e";
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);
}
/**
* 第2次MD5加密
* @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;
}
public static void main(String[] args) {
System.out.println("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456"));
System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"),
"1b2i3t4e"));
System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "1b2i3t4e"));
}
}
不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。
BCrypt加密设计
Bcrypt
就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5
通过大数据的方式进行破解。
Bcrypt
生成的密文是60位的。而MD5
的是32位的。Bcrypt
破解难度更大。
在pom.xml
中添加依赖:
<!-- security依赖包 BCrypt加密(加密)-->
<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})
它是只用到了此框架下的一个类,所以没有必要将整个框架都加载进来。
启动类如下所示:
package com.example.onlinemusic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
public class OnlinemusicApplication {
public static void main(String[] args) {
SpringApplication.run(OnlinemusicApplication.class, args);
}
}
在tool
包下创建BCryptTest
测试类:
package com.example.onlinemusic.tools;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Created With IntelliJ IDEA
* Description:
* Users: yyyyy
* Date: 2022-08-07
* Time: 20:01
*/
public class BCryptTest {
public static