Springboot项目搭建(1)-用户登录与注册
1.引入lombok依赖
若<dependency>中数据为红,则说明Maven本地仓库里未引用依赖
可在右侧“m”标识中,下载源代码和文档后刷新。
2.统一响应数据Result
在entity文档下创建,名为Result的java类
文件地址:org/example/entity/Result.java
package org.example.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//创建统一响应数据的类
@NoArgsConstructor //创建无参数构造器
@AllArgsConstructor //创建有参数构造器
@Data //配置访问属性的方法(get/set)
public class Result<T>{
private Integer code; //该属性用于存放响应码
private String message; //提示信息
private T data; //响应数据,T表示任意类型
// 创建一个方法,用于返回操作成功的反应结果,
// 如:查询个人信息,返回操作成功提示及个人信息数据
public static <E> Result <E> success(E data){
return new Result<>(0,"操作成功",data);
}
// 创建方法,返回操作成功的响应结果,
// 如:添加数据,删除数据
public static Result success(){
return new Result(0,"操作成功",null);
}
//创建方法,返回操作失败的响应结果
public static Result error(String message){
return new Result(1,message,null);
}
}
创建一个方法:
public static <E> Result <E> success(E data){
return new Result<>(0,"操作成功",data);
- public 表示方法的访问权限
- static 静态只被创建一次,配置该方法存放内存中的方法区
- <E> 方法的返回类型,E表示任意类型
- result<E>返回类型,是当前类的类型
- success方法名称
- (E data)表示参数列表,调用方法时可以通过参数列表向方法传递数据
- return 方法返回数据关键字
- new Result<>() 表示创建对象,创建一个Result对象,
- 创建对象时传入三个数据,分别是状态码,提示信息以及携带的数据
3. 创建文档
4. 数据格式化User
在org/example/entity/User.java路径中,
定义了一个用于存储和传输用户信息的Java实体类。
package org.example.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
private String email;
private String nickname;
private String userPic;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
5. 数据库交互UserMapper
声明当前文件为映射文件,与数据库进行数据交互的文件
这个 UserMapper
接口提供了两个基本的数据库操作:添加用户和根据用户名查询用户。
通过MyBatis框架,这些方法可以被Spring框架自动调用,从而实现数据的持久化操作。
这种方式使得数据库操作更加简洁和面向对象。
package org.example.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.entity.User;
@Mapper
public interface UserMapper {
//添加用户信息
@Insert("insert into user(username,password,create_time,update_time)" +
"values(#{username},#{password},now(),now())")
void insertUser(String username, String password);
//根据用户名查询用户信息
@Select("select * from user where username=#{username}")
User selectUserByUsername(String username);
}
6. 加密和密码验证
6.1 数据加密 Md5Util
在org/example/utils/Md5Util.java中,定义了一个工具类 Md5Util
- 生成MD5哈希值:将输入的字符串转换为MD5哈希值。
- 验证密码:检查输入的密码是否与给定的MD5哈希值相同。
- 将字节数组转换为十六进制字符串:用于表示MD5哈希值的结果。
以下代码来源于网络,有志者也可自行书写 。
package org.example.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Util {
/**
* 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
*/
protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
protected static MessageDigest messagedigest = null;
static {
try {
messagedigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsaex) {
System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
nsaex.printStackTrace();
}
}
/**
* 生成字符串的md5校验值
*
* @param s
* @return
*/
public static String getMD5String(String s) {
return getMD5String(s.getBytes());
}
/**
* 判断字符串的md5校验码是否与一个已知的md5码相匹配
*
* @param password 要校验的字符串
* @param md5PwdStr 已知的md5校验码
* @return
*/
public static boolean checkPassword(String password, String md5PwdStr) {
String s = getMD5String(password);
return s.equals(md5PwdStr);
}
public static String getMD5String(byte[] bytes) {
messagedigest.update(bytes);
return bufferToHex(messagedigest.digest());
}
private static String bufferToHex(byte bytes[]) {
return bufferToHex(bytes, 0, bytes.length);
}
private static String bufferToHex(byte bytes[], int m, int n) {
StringBuffer stringbuffer = new StringBuffer(2 * n);
int k = m + n;
for (int l = m; l < k; l++) {
appendHexPair(bytes[l], stringbuffer);
}
return stringbuffer.toString();
}
private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
// 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
stringbuffer.append(c0);
stringbuffer.append(c1);
}
}
6.2 密文查看与验证 Md5Test
首先在pom.xml中:导入Spring提供的测试工具
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
而后创建新的测试文件:src/test/java/Md5Test.java
可查看“123456(为例)”转码后的结果。
import org.example.utils.Md5Util;
import org.junit.jupiter.api.Test;
import java.sql.SQLOutput;
public class Md5Test {
@Test
public void test(){
String md5Password = Md5Util.getMD5String("123456");
System.out.println(md5Password);
}
}
7. 定义用户基操规UserService
7.1 创建接口
文件地址:org/example/service/UserService.java
package org.example.service;
import org.example.entity.User;
public interface UserService {
//根据用户名查询用户信息
User findUserByUsername(String username);
//注册
void register(String username, String password);
}
7.2 实现接口<lmpl>
文件地址:org/example/service/impl/UserServiceImpl.java
补充UserService.java的具体操作过程
package org.example.service.impl;
import org.example.entity.User;
import org.example.mapper.UserMapper;
import org.example.service.UserService;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//声明当前类为业务类,在业务类下可以调用事务相关配置
@Service
public class UserServiceImpl implements UserService {
//调用UserMapper操作数据库
@Autowired
@SuppressWarnings("all") //压制警告
private UserMapper userMapper;
@Override
public User findUserByUsername(String username) {
return userMapper.selectUserByUsername(username);
}
@Override
public void register(String username, String password) {
//加密
String md5String = Md5Util.getMD5String(password);
userMapper.insertUser(username, md5String);
}
}
小结:UserServiceImpl
是 UserService
接口的具体实现,它提供了接口中声明的方法的实际业务逻辑,并通过依赖注入使用 UserMapper
与数据库进行交互。这种设计模式使得代码结构清晰,易于维护和扩展。
8. 注册UserController
首先在pom.xml中添加:用于请求参数验证(校验)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在org/example/controller/UserController.java中建立一个UserController
作为表现层,
它的Spring Boot控制器类,负责处理与用户相关的HTTP请求和响应。
package org.example.controller;
import org.example.entity.Result;
import org.example.entity.User;
import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //声明该类是一个控制器类,用于浏览器与该类数据交互
@RequestMapping("/user") //声明该类的访问地址
@Validated //声明该类中的方法支持参数校验
public class UserController {
@Autowired
@SuppressWarnings("all")
private UserService userService;
//注册
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password) {
//查询注册的用户名是否存在
User user = userService.findUserByUsername(username);
//判断查询的用户是否存在,如果不存在则返回空
if (user == null) {
//该用户名可以使用
userService.register(username, password);
//返回操作成功
return Result.success();
}else{
//该用户名不允许使用
return Result.error("用户名已被占用");
}
}
@Pattern(regexp = "^\\S{5,16}$")String username:
- @Pattern:表示该参数使用校验规则,regexp表示使用正则表达式的校验规则
- ^:表示正则表达式的开始
- $:表示正则表达式的结束
- \\:转译字符,转译后为\
- \S:表示任意字符
- {5,16}:字符的位数在5~16间
实验结果:
9. 全局异常类GlobalExceptionHandle
创建org/example/exception/GlobalExceptionHandle.java文件,统一处理异常报告。
package org.example.exception;
import org.example.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandle {
//统一返回异常信息
@ExceptionHandler(value = Exception.class)
public Result handleException(Exception e) {
return Result.error(e.getMessage());
}
}
10. 登录UserController
在org/example/controller/UserController.java中的注册代码下方,续写登录代码。
循环逻辑:如果上面的if体未执行,则表示该用户存在,由于查询的是用户所有信息,包含数据库中的密码,下面的判断为判断前端传入的密码,与数据库的密码是否相等
package org.example.controller;
import jakarta.validation.constraints.Pattern;
import org.example.entity.Result;
import org.example.entity.User;
import org.example.service.UserService;
import org.example.utils.JwtUtil;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
@SuppressWarnings("all")
private UserService userService;
//注册
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password) {...}
//登录
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password){
//根据用户名查询用户信息
User loginUser = userService.findUserByUsername(username);
//如果没有用户信息则返回登录失败提示
if(loginUser == null){
return Result.error("用户名或密码错误");
}
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())){
return Result.success("登录成功");
}
return Result.error("登录失败,用户名或密码错误");
}
}
实验结果:
11. JWT令牌
11.1 认识JWT令牌
全称:JSON Web Token,由Header、Payload、Signature三部分组成:
Header(头):记录令牌类型,参数算法等;
Payload(荷载):携带用户信息等一些自定义的默认信息;
Signature(签名):防止信息被篡改,而设定的算法逻辑密文。
11.2 引入令牌 JwtUtil
添加至org/example/utils/JwtUtil.java
package org.example.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
private static final String KEY = "usermessage";
//接收业务数据,生成token并返回
public static String genToken(Map<String, Object> claims) {
return JWT.create()
.withClaim("claims", claims)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
.sign(Algorithm.HMAC256(KEY));
}
//接收token,验证token,并返回业务数据
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
引入后,在pom.xml中添加:jwt依赖(令牌技术)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
11.3 测试令牌 JwtTest
在test/java下,建立src/test/java/JwtTest.java测试文件:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTest {
@Test
public void testJwt() {
//创建容器(集合),用于存放令牌(用户)数据
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","Hela");
//生成Jwt
String token = JWT.create() //创建令牌
.withClaim("userwagaga",claims) //载荷数据
.withExpiresAt(new Date(System.currentTimeMillis()*1000*60*60*12)) //过期时间(生命周期)
.sign(Algorithm.HMAC256("usermessage")); //指定算法,配置加密密钥
System.out.println(token);
}
@Test
public void testParse(){
String token ="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJ1c2Vyd2FnYWdhIjp7Im5hbWUiOiJIZWxhIiwiaWQiOjF9LCJleHAiOjEwMzU0MTc4NDQ2MDY1OTN9." +
"tbhdeg5kp1u8Hp7NBxT8ALSaAW9-iBLasj_AdIBAYpQ";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("usermessage")).build();
DecodedJWT decodedJWT= jwtVerifier.verify(token);
Map<String, Claim> claimMap = decodedJWT.getClaims();
System.out.println(claimMap.get("userwagaga"));
}
}
11.4 将令牌补充至登录代码中
import java.util.HashMap;
import java.util.Map;
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password){
//根据用户名查询用户信息
User loginUser = userService.findUserByUsername(username);
//如果没有用户信息则返回登录失败提示
if(loginUser == null){
return Result.error("用户名或密码错误");
}
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())){
Map<String,Object> claims = new HashMap<>();// 存放用户令牌数据的容器(集合)
claims.put("id",loginUser.getId());
claims.put("username",loginUser.getUsername());
claims.put("password",loginUser.getPassword());
String token = JwtUtil.genToken(claims);
return Result.success(token);
}
return Result.error("登录失败,用户名或密码错误");
}
此阶段UserController完整代码:
package org.example.controller;
import jakarta.validation.constraints.Pattern;
import org.example.entity.Result;
import org.example.entity.User;
import org.example.service.UserService;
import org.example.utils.JwtUtil;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController //声明该类是一个控制器类,用于浏览器与该类数据交互
@RequestMapping("/user") //声明该类的访问地址
@Validated //声明该类中的方法支持参数校验
public class UserController {
@Autowired
@SuppressWarnings("all")
private UserService userService;
//注册
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password) {
//查询注册的用户名是否存在
User user = userService.findUserByUsername(username);
//判断查询的用户是否存在,如果不存在则返回空
if (user == null) {
//该用户名可以使用
userService.register(username, password);
//返回操作成功
return Result.success();
}else{
//该用户名不允许使用
return Result.error("用户名已被占用");
}
}
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password){
//根据用户名查询用户信息
User loginUser = userService.findUserByUsername(username);
//如果没有用户信息则返回登录失败提示
if(loginUser == null){
return Result.error("用户名或密码错误");
}
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())){
Map<String,Object> claims = new HashMap<>();// 存放用户令牌数据的容器(集合)
claims.put("id",loginUser.getId());
claims.put("username",loginUser.getUsername());
claims.put("password",loginUser.getPassword());
String token = JwtUtil.genToken(claims);
return Result.success(token);
}
return Result.error("登录失败,用户名或密码错误");
}
}