SpringBoot+Vue3项目实战——SpringBoot篇
项目准备与配置
数据库以及其他项目配置
server:
port: 9090
spring:
datasource:
url: jdbc:mysql://localhost:3306/lostarknote
username: root
password: guxiang
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
遇到问题:找不到"url"....
可能原因:没有扫描到配置文件(yml),在pom.xml中的build标签中添加以下内容
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
启动类相关
@SpringBootApplication:包扫描时,自动扫描启动类所在包及其子包,若需要扫描其他包,需要使用@ComponentScan进行包名指定
Lombok工具:
自动生成getter/setter/toString方法 ,需要引入lombok依赖,然后在实体类上添加@Data注解
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
Result实体类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
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);
}
}
用户模块
注册功能:
1.密码加密
public class MD5Util {
//生成MD5
public static String getMD5(String message) {
String md5 = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5"); // 创建一个md5算法对象
byte[] messageByte = message.getBytes("UTF-8");
byte[] md5Byte = md.digest(messageByte); // 获得MD5字节数组,16*8=128位
md5 = bytesToHex(md5Byte); // 转换为16进制字符串
} catch (Exception e) {
e.printStackTrace();
}
return md5;
}
// 二进制转十六进制
public static String bytesToHex(byte[] bytes) {
StringBuffer hexStr = new StringBuffer();
int num;
for (int i = 0; i < bytes.length; i++) {
num = bytes[i];
if(num < 0) {
num += 256;
}
if(num < 16){
hexStr.append("0");
}
hexStr.append(Integer.toHexString(num));
}
return hexStr.toString().toUpperCase();
}
}
2.参数校验——Spring Validation
3.全局异常处理器
@RestControllerAdvice
public class GobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e)
{
e.printStackTrace();
return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "操作失败!");
}
}
登录功能
登录认证:JWT令牌
JWT工具类
public class JWTUtil {
private static final String SECRET = "guxiang";
public static String generateToken(Map<String, Object> claims){
return JWT.create()
.withClaim("claims", claims)
.withExpiresAt(new Date(System.currentTimeMillis()* 1000 * 60 * 60 * 24))
.sign(Algorithm.HMAC256(SECRET));
}
public static Map<String, Object> parseToken(String token){
return JWT.require(Algorithm.HMAC256(SECRET))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
JWT验证:从请求头中获取token
@RestController
@RequestMapping("/dungeon")
public class DungeonController {
@GetMapping("/list")
public Result<String> list(@RequestHeader("Authorization") String token, HttpServletResponse response) {
//验证token
//若能正常解析(不报错),则验证通过
try {
Map<String, Object> claims = JWTUtil.parseToken(token);
return Result.success("副本数据获取成功!");
}catch (Exception e) {
response.setStatus(401);
return Result.error("未登录!");
}
}
}
进阶:注册拦截器进行验证。
注册一个拦截器进行token的验证,就不用单独在每个业务代码里进行token验证
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌验证
String token = request.getHeader("Authorization");
//若能正常解析(不报错),则验证通过
try {
Map<String, Object> claims = JWTUtil.parseToken(token);
//放行
return true;
}catch (Exception e) {
response.setStatus(401);
return false;
}
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录注册不用拦截
registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
}
}
@RestController
@RequestMapping("/dungeon")
public class DungeonController {
@GetMapping("/list")
public Result<String> list() {
//这里不用再验证token,已经统一在拦截器中做验证
return Result.success("副本数据获取成功!");
}
}
登录优化——Redis
获取用户信息
ThreadLocal
- 获取用户信息需要通过token中存储的username到数据库中查找,需要解析token。
- 在其他业务可能也需要username的信息,又要解析token,为了避免代码重复,在之前拦截器中解析出的token统一放到线程中(ThreadLocal)。
public class ThreadLocalUtil {
//提供ThreadLocal对象
private static final ThreadLocal THREAD_LOCAL= new ThreadLocal();
public static <T> T get() {
return (T) THREAD_LOCAL.get();
}
public static void set(Object value) {
THREAD_LOCAL.set(value);
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
拦截器中将数据存入ThreadLocal中,请求完成后清除ThreadLocal,防止内存泄露
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌验证
String token = request.getHeader("Authorization");
//若能正常解析(不报错),则验证通过
try {
Map<String, Object> claims = JWTUtil.parseToken(token);
//将数据存储到ThreadLocal中
ThreadLocalUtil.set(claims);
//放行
return true;
}catch (Exception e) {
response.setStatus(401);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocal中的数据,防止内存泄露
ThreadLocalUtil.remove();
}
}
接口中从ThreadLocal中获取数据
@GetMapping("/userInfo")
public Result<User> userInfo(@RequestHeader("Authorization") String token) {
// Map<String, Object> claims = JWTUtil.parseToken(token);
// String username = (String) claims.get("username");
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.findByUsername(username);
return Result.success(loginUser);
}
@JsonIgnore
更新用户信息
实体类中参数验证
更新用户密码
参数用一个map接收,因为接受的参数名与数据库中不一致
更新用户信息时,参数名与字段名一致,所以用user实体类接收