diff --git a/pom.xml b/pom.xml index 83906db..ad27c0b 100644 --- a/pom.xml +++ b/pom.xml @@ -63,8 +63,14 @@ test - org.springframework.boot - spring-boot-starter-security + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.10.1 + + + cn.dev33 + sa-token-spring-boot3-starter + 1.39.0 diff --git a/src/main/java/com/kane/animo/AnimoApplication.java b/src/main/java/com/kane/animo/AnimoApplication.java index 3b8b9da..65261be 100644 --- a/src/main/java/com/kane/animo/AnimoApplication.java +++ b/src/main/java/com/kane/animo/AnimoApplication.java @@ -1,5 +1,7 @@ package com.kane.animo; +import org.mybatis.spring.annotation.MapperScan; +import org.mybatis.spring.annotation.MapperScans; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -8,6 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * @author Spring Boot Framework */ @SpringBootApplication +@MapperScan("com.kane.animo.*.mapper") public class AnimoApplication { public static void main(String[] args) { diff --git a/src/main/java/com/kane/animo/auth/controller/AuthController.java b/src/main/java/com/kane/animo/auth/controller/AuthController.java new file mode 100644 index 0000000..0d6d2f8 --- /dev/null +++ b/src/main/java/com/kane/animo/auth/controller/AuthController.java @@ -0,0 +1,43 @@ +package com.kane.animo.auth.controller; + +import com.kane.animo.auth.domain.form.LoginForm; +import com.kane.animo.auth.service.AuthService; +import com.kane.animo.model.R; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 认证 + * @author Kane + * @since 2025/11/7 14:25 + */ +@RestController +@RequestMapping("/auth") +public class AuthController { + + @Resource + private AuthService service; + + /** + * 登录 + * @param from 登录参数 + * @return 登录结果 + */ + @PostMapping("/login") + public R login(@RequestBody LoginForm from) { + return service.login(from); + } + + /** + * 注册 + * @param from 注册参数 + * @return 注册结果 + */ + @PostMapping("/register") + public R register(@RequestBody LoginForm from) { + return service.register(from); + } +} diff --git a/src/main/java/com/kane/animo/auth/domain/User.java b/src/main/java/com/kane/animo/auth/domain/User.java new file mode 100644 index 0000000..e87c75d --- /dev/null +++ b/src/main/java/com/kane/animo/auth/domain/User.java @@ -0,0 +1,55 @@ +package com.kane.animo.auth.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.InputStream; +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户 + * @author Kane + * @since 2025/11/07 03:10 + */ +@Data +@TableName("user") +public class User implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId + private Long id; + + /** + * 用户名 + */ + private String user; + + /** + * 密码 + */ + private String password; + + /** + * 头像 + */ + private InputStream avatar; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/kane/animo/auth/domain/form/LoginForm.java b/src/main/java/com/kane/animo/auth/domain/form/LoginForm.java new file mode 100644 index 0000000..7d27ed4 --- /dev/null +++ b/src/main/java/com/kane/animo/auth/domain/form/LoginForm.java @@ -0,0 +1,14 @@ +package com.kane.animo.auth.domain.form; + +/** + * 登录参数 + * @author Kane + * @since 2025/11/7 14:28 + * @param user 用户名 + * @param password 密码 + */ +public record LoginForm( + String user, + String password +) { +} diff --git a/src/main/java/com/kane/animo/auth/mapper/UserMapper.java b/src/main/java/com/kane/animo/auth/mapper/UserMapper.java new file mode 100644 index 0000000..865c26b --- /dev/null +++ b/src/main/java/com/kane/animo/auth/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.kane.animo.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.kane.animo.auth.domain.User; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户持久层 + * @author Kane + * @since 2025/11/7 15:11 + */ +@Mapper +public interface UserMapper extends BaseMapper { +} diff --git a/src/main/java/com/kane/animo/auth/service/AuthService.java b/src/main/java/com/kane/animo/auth/service/AuthService.java new file mode 100644 index 0000000..1391bc6 --- /dev/null +++ b/src/main/java/com/kane/animo/auth/service/AuthService.java @@ -0,0 +1,25 @@ +package com.kane.animo.auth.service; + +import com.kane.animo.auth.domain.form.LoginForm; +import com.kane.animo.model.R; + +/** + * 认证服务 + * @author Kane + * @since 2025/11/7 14:49 + */ +public interface AuthService { + /** + * 登录 + * @param from 登录参数 + * @return 登录结果 + */ + R login(LoginForm from); + + /** + * 注册 + * @param from 注册参数 + * @return 注册结果 + */ + R register(LoginForm from); +} diff --git a/src/main/java/com/kane/animo/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/kane/animo/auth/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..7f1d9b2 --- /dev/null +++ b/src/main/java/com/kane/animo/auth/service/impl/AuthServiceImpl.java @@ -0,0 +1,67 @@ +package com.kane.animo.auth.service.impl; + +import cn.dev33.satoken.secure.BCrypt; +import cn.dev33.satoken.stp.StpUtil; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.kane.animo.auth.domain.User; +import com.kane.animo.auth.domain.form.LoginForm; +import com.kane.animo.auth.mapper.UserMapper; +import com.kane.animo.auth.service.AuthService; +import com.kane.animo.exception.ServiceException; +import com.kane.animo.model.R; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +/** + * 认证服务 + * @author Kane + * @since 2025/11/7 14:49 + */ +@Service +public class AuthServiceImpl implements AuthService { + + @Resource + private UserMapper userMapper; + + /** + * 登录 + * + * @param from 登录参数 + * @return 登录结果 + */ + @Override + public R login(LoginForm from) { + User one = new LambdaQueryChainWrapper<>(userMapper) + .eq(User::getUser, from.user()) + .one(); + if (one == null){ + throw new ServiceException("用户不存在"); + } + if (!BCrypt.checkpw(from.password(), one.getPassword())){ + throw new ServiceException("密码错误"); + } + StpUtil.login(one.getId()); + return R.success(); + } + + /** + * 注册 + * + * @param from 注册参数 + * @return 注册结果 + */ + @Override + public R register(LoginForm from) { + User one = new LambdaQueryChainWrapper<>(userMapper) + .eq(User::getUser, from.user()) + .one(); + if (one != null){ + throw new ServiceException("用户已存在"); + } + User user = new User(); + user.setUser(from.user()); + user.setPassword(BCrypt.hashpw(from.password(), BCrypt.gensalt())); + userMapper.insert(user); + return login(from); + } +} diff --git a/src/main/java/com/kane/animo/config/SaTokenConfigurer.java b/src/main/java/com/kane/animo/config/SaTokenConfigurer.java new file mode 100644 index 0000000..7bd315a --- /dev/null +++ b/src/main/java/com/kane/animo/config/SaTokenConfigurer.java @@ -0,0 +1,23 @@ +package com.kane.animo.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 拦截器配置 + * @author Kane + * @since 2025/11/7 16:13 + */ +@Configuration +public class SaTokenConfigurer implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor(handler -> StpUtil.checkLogin())) + .addPathPatterns("/**") + .excludePathPatterns("/auth/login"); +// .excludePathPatterns("/auth/register"); + } +} diff --git a/src/main/java/com/kane/animo/exception/ServiceException.java b/src/main/java/com/kane/animo/exception/ServiceException.java new file mode 100644 index 0000000..d5373dd --- /dev/null +++ b/src/main/java/com/kane/animo/exception/ServiceException.java @@ -0,0 +1,12 @@ +package com.kane.animo.exception; + +/** + * 服务异常 + * @author Kane + * @since 2025/11/7 15:16 + */ +public class ServiceException extends RuntimeException { + public ServiceException(String message) { + super(message); + } +} diff --git a/src/main/java/com/kane/animo/filter/ExceptionFilter.java b/src/main/java/com/kane/animo/filter/ExceptionFilter.java new file mode 100644 index 0000000..83c0b86 --- /dev/null +++ b/src/main/java/com/kane/animo/filter/ExceptionFilter.java @@ -0,0 +1,25 @@ +package com.kane.animo.filter; + +import cn.dev33.satoken.exception.NotLoginException; +import com.kane.animo.exception.ServiceException; +import com.kane.animo.model.R; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 异常过滤器 + * @author Kane + * @since 2025/11/7 15:16 + */ +@RestControllerAdvice +public class ExceptionFilter { + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e) { + return R.error(e.getMessage()); + } + + @ExceptionHandler(NotLoginException.class) + public R handleException(NotLoginException e) { + return R.error("认证失败 - 未登录"); + } +} diff --git a/src/main/java/com/kane/animo/model/R.java b/src/main/java/com/kane/animo/model/R.java new file mode 100644 index 0000000..ea40df6 --- /dev/null +++ b/src/main/java/com/kane/animo/model/R.java @@ -0,0 +1,63 @@ +package com.kane.animo.model; + +import lombok.Data; + +/** + * 统一返回结果 + * @author Kane + * @since 2025/11/7 14:50 + */ +@Data +public class R { + private int code; + private String message; + private T data; + private long timestamp; + public static R success() { + R r = new R<>(); + r.setCode(200); + r.setMessage("success"); + r.setData(null); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + public static R success(T data) { + R r = new R<>(); + r.setCode(200); + r.setMessage("success"); + r.setData(data); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + public static R success(String message, T data) { + R r = new R<>(); + r.setCode(200); + r.setMessage(message); + r.setData(data); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + public static R error(String message) { + R r = new R<>(); + r.setCode(500); + r.setMessage(message); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + public static R build(int code, String message) { + R r = new R<>(); + r.setCode(code); + r.setMessage(message); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + public static R build(int code, String message, T data) { + R r = new R<>(); + r.setCode(code); + r.setMessage(message); + r.setData(data); + r.setTimestamp(System.currentTimeMillis()); + return r; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b4d1286..9eadcb5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,3 +4,12 @@ server: spring: application: name: Animo + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/animo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false + username: animo + password: WS6PCwksRpEYNpNt + +mybatis-plus: + mapper-locations: classpath*:/mapper/**/*.xml