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