diff --git a/.gitignore b/.gitignore
index 9154f4c..667aaef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,33 @@
-# ---> Java
-# Compiled class file
-*.class
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
-# Log file
-*.log
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
-# BlueJ files
-*.ctxt
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-replay_pid*
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+### VS Code ###
+.vscode/
diff --git a/README.md b/README.md
index 8b3b3ae..173fe7d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# java-master
-模板
\ No newline at end of file
+模板
+
+1.用户登录
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d1832e3
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,119 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.0
+
+
+ com.api
+ java-master
+ 0.0.1-SNAPSHOT
+ demo
+ 管理系统
+
+
+ UTF-8
+ UTF-8
+ 17
+ 1.2.21
+ 3.5.5
+ 3.0.3
+ 4.0
+ 11.2.0.3
+ 8.1.2.141
+ 5.8.29
+ 1.15.3
+ 4.5.0
+ 1.18.24
+ 4.0.1
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+ com.alibaba
+ druid-spring-boot-3-starter
+ ${druid.version}
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatisplus.version}
+
+
+ org.mybatis
+ mybatis-spring
+ ${mybatis.spring}
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+
+
+
+ com.github.xiaoymin
+ knife4j-openapi3-jakarta-spring-boot-starter
+ ${knife4j.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public/
+
+ true
+
+
+
+
+
+ public
+ aliyun nexus
+ https://maven.aliyun.com/repository/public/
+
+ true
+
+
+ false
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/io/Application.java b/src/main/java/io/Application.java
new file mode 100644
index 0000000..75ad326
--- /dev/null
+++ b/src/main/java/io/Application.java
@@ -0,0 +1,12 @@
+package io;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/src/main/java/io/common/annotation/Login.java b/src/main/java/io/common/annotation/Login.java
new file mode 100644
index 0000000..4c2651e
--- /dev/null
+++ b/src/main/java/io/common/annotation/Login.java
@@ -0,0 +1,12 @@
+package io.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 登录效验
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Login {
+}
diff --git a/src/main/java/io/common/annotation/LoginUser.java b/src/main/java/io/common/annotation/LoginUser.java
new file mode 100644
index 0000000..a88ea7f
--- /dev/null
+++ b/src/main/java/io/common/annotation/LoginUser.java
@@ -0,0 +1,19 @@
+
+
+package io.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录用户信息
+ *
+
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LoginUser {
+
+}
diff --git a/src/main/java/io/common/exception/ErrorCode.java b/src/main/java/io/common/exception/ErrorCode.java
new file mode 100644
index 0000000..51d6913
--- /dev/null
+++ b/src/main/java/io/common/exception/ErrorCode.java
@@ -0,0 +1,18 @@
+package io.common.exception;
+
+/**
+ * 错误编码,由5位数字组成,前2位为模块编码,后3位为业务编码
+ *
+ * 如:10001(10代表系统模块,001代表业务代码)
+ *
+ */
+public interface ErrorCode {
+ int INTERNAL_SERVER_ERROR = 500;
+ int UNAUTHORIZED = 401;
+ int NOT_NULL = 500;
+
+ int ACCOUNT_PASSWORD_ERROR = 10004;
+
+ int IDENTIFIER_NOT_NULL = 10006;
+
+}
diff --git a/src/main/java/io/common/exception/ExceptionUtils.java b/src/main/java/io/common/exception/ExceptionUtils.java
new file mode 100644
index 0000000..0d5165f
--- /dev/null
+++ b/src/main/java/io/common/exception/ExceptionUtils.java
@@ -0,0 +1,43 @@
+package io.common.exception;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Exception工具类
+ */
+public class ExceptionUtils {
+
+ /**
+ * 获取异常信息
+ * @param ex 异常
+ * @return 返回异常信息
+ */
+ public static String getErrorStackTrace(Exception ex){
+ StringWriter sw = null;
+ PrintWriter pw = null;
+ try {
+ sw = new StringWriter();
+ pw = new PrintWriter(sw, true);
+ ex.printStackTrace(pw);
+ }finally {
+ try {
+ if(pw != null) {
+ pw.close();
+ }
+ } catch (Exception e) {
+
+ }
+ try {
+ if(sw != null) {
+ sw.close();
+ }
+ } catch (IOException e) {
+
+ }
+ }
+
+ return sw.toString();
+ }
+}
diff --git a/src/main/java/io/common/exception/RenException.java b/src/main/java/io/common/exception/RenException.java
new file mode 100644
index 0000000..50a2673
--- /dev/null
+++ b/src/main/java/io/common/exception/RenException.java
@@ -0,0 +1,46 @@
+package io.common.exception;
+
+import java.util.Arrays;
+
+/**
+ * 自定义异常
+ *
+ */
+public class RenException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private int code;
+ private String msg;
+
+ public RenException(int code, String... params) {
+ this.code = code;
+ this.msg = Arrays.toString(params);
+ }
+
+ public RenException(String msg) {
+ super(msg);
+ this.code = 500;
+ this.msg = msg;
+ }
+
+ public RenException(String msg, Throwable e) {
+ super(msg, e);
+ this.code = 400;
+ this.msg = msg;
+ }
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+}
diff --git a/src/main/java/io/common/exception/RenExceptionHandler.java b/src/main/java/io/common/exception/RenExceptionHandler.java
new file mode 100644
index 0000000..b9e1ed5
--- /dev/null
+++ b/src/main/java/io/common/exception/RenExceptionHandler.java
@@ -0,0 +1,41 @@
+package io.common.exception;
+
+import io.common.utils.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 异常处理器
+ */
+@RestControllerAdvice
+public class RenExceptionHandler {
+ private static final Logger logger = LoggerFactory.getLogger(RenExceptionHandler.class);
+
+ /**
+ * 处理自定义异常
+ */
+ @ExceptionHandler(RenException.class)
+ public Result handleRenException(RenException ex){
+ Result result = new Result();
+ result.error(ex.getCode(), ex.getMsg());
+
+ return result;
+ }
+
+ @ExceptionHandler(DuplicateKeyException.class)
+ public Result handleDuplicateKeyException(DuplicateKeyException ex){
+ Result result = new Result();
+ result.error("数据库中已存在该记录!");
+ return result;
+ }
+
+ @ExceptionHandler(Exception.class)
+ public Result handleException(Exception ex){
+ logger.error(ex.getMessage(), ex);
+
+ return new Result().error(ex.getMessage());
+ }
+}
diff --git a/src/main/java/io/common/interceptor/AuthorizationInterceptor.java b/src/main/java/io/common/interceptor/AuthorizationInterceptor.java
new file mode 100644
index 0000000..86845d9
--- /dev/null
+++ b/src/main/java/io/common/interceptor/AuthorizationInterceptor.java
@@ -0,0 +1,56 @@
+package io.common.interceptor;
+
+import cn.hutool.core.util.StrUtil;
+import io.common.annotation.Login;
+import io.common.exception.RenException;
+import io.entity.TokenEntity;
+import io.service.TokenService;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+/**
+ * 权限(Token)验证
+ *
+ */
+@Component
+public class AuthorizationInterceptor implements HandlerInterceptor {
+
+ @Autowired
+ private TokenService tokenService;
+
+ public static final String USER_KEY = "userId";
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
+ Login annotation;
+ if (handler instanceof HandlerMethod) {
+ annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
+ } else {
+ return true;
+ }
+ if (annotation == null) {
+ return true;
+ }
+ //从header中获取token
+ String token = request.getHeader("Authorization");
+ //如果header中不存在token,则从参数中获取token
+ if (StrUtil.isBlank(token)) {
+ token = request.getParameter("Authorization");
+ }
+ //token为空
+ if (StrUtil.isBlank(token)) {
+ throw new RenException(401,"登录失效");
+ }
+ //查询token信息
+ TokenEntity tokenEntity = tokenService.getByToken(token);
+ if (tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()) {
+ throw new RenException(401,"登录失效");
+ }
+ //设置userId到request里,后续根据userId,获取用户信息
+ request.setAttribute(USER_KEY, tokenEntity.getUserId());
+ return true;
+ }
+}
diff --git a/src/main/java/io/common/resolver/LoginUserHandlerMethodArgumentResolver.java b/src/main/java/io/common/resolver/LoginUserHandlerMethodArgumentResolver.java
new file mode 100644
index 0000000..f269bc3
--- /dev/null
+++ b/src/main/java/io/common/resolver/LoginUserHandlerMethodArgumentResolver.java
@@ -0,0 +1,44 @@
+package io.common.resolver;
+
+import io.common.annotation.LoginUser;
+import io.common.interceptor.AuthorizationInterceptor;
+import io.entity.UserEntity;
+import io.service.UserService;
+import lombok.AllArgsConstructor;
+import org.springframework.core.MethodParameter;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * 有@LoginUser注解的方法参数,注入当前登录用户
+ *
+ */
+@Component
+@AllArgsConstructor
+public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
+ private final UserService userService;
+
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return parameter.getParameterType().isAssignableFrom(UserEntity.class) && parameter.hasParameterAnnotation(LoginUser.class);
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
+ NativeWebRequest request, WebDataBinderFactory factory){
+ //获取用户ID
+ Object object = request.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
+
+ if (object == null) {
+ return null;
+ }
+
+ //获取用户信息
+ UserEntity user = userService.getUserByUserId((Long) object);
+ return user;
+ }
+}
diff --git a/src/main/java/io/common/utils/DateUtils.java b/src/main/java/io/common/utils/DateUtils.java
new file mode 100644
index 0000000..ab41f05
--- /dev/null
+++ b/src/main/java/io/common/utils/DateUtils.java
@@ -0,0 +1,54 @@
+package io.common.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 日期处理
+ *
+ */
+public class DateUtils {
+ /** 时间格式(yyyy-MM-dd) */
+ public final static String DATE_PATTERN = "yyyy-MM-dd";
+ /** 时间格式(yyyy-MM-dd HH:mm:ss) */
+ public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 日期格式化 日期格式为:yyyy-MM-dd
+ * @param date 日期
+ * @return 返回yyyy-MM-dd格式日期
+ */
+ public static String format(Date date) {
+ return format(date, DATE_PATTERN);
+ }
+
+ /**
+ * 日期格式化 日期格式为:yyyy-MM-dd
+ * @param date 日期
+ * @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN
+ * @return 返回yyyy-MM-dd格式日期
+ */
+ public static String format(Date date, String pattern) {
+ if (date != null) {
+ SimpleDateFormat df = new SimpleDateFormat(pattern);
+ return df.format(date);
+ }
+ return null;
+ }
+
+ /**
+ * 日期解析
+ * @param date 日期
+ * @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN
+ * @return 返回Date
+ */
+ public static Date parse(String date, String pattern) {
+ try {
+ return new SimpleDateFormat(pattern).parse(date);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/io/common/utils/Result.java b/src/main/java/io/common/utils/Result.java
new file mode 100644
index 0000000..2a8c4fb
--- /dev/null
+++ b/src/main/java/io/common/utils/Result.java
@@ -0,0 +1,75 @@
+package io.common.utils;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+
+/**
+ * 响应数据
+ *
+
+ * @since 1.0.0
+ */
+@Schema(title = "响应")
+public class Result implements Serializable {
+ private static final long serialVersionUID = 1L;
+ /**
+ * 编码:0表示成功,其他值表示失败
+ */
+ @Schema(title = "编码:0表示成功,其他值表示失败")
+ private int code = 0;
+ /**
+ * 消息内容
+ */
+ @Schema(title = "消息内容")
+ private String msg = "success";
+ /**
+ * 响应数据
+ */
+ @Schema(title = "响应数据")
+ private T data;
+
+ public Result ok(T data) {
+ this.setData(data);
+ return this;
+ }
+ public boolean success(){
+ return code == 0;
+ }
+
+ public Result error(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ return this;
+ }
+
+ public Result error(String msg) {
+ this.code = 500;
+ this.msg = msg;
+ return this;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+}
diff --git a/src/main/java/io/common/validator/AssertUtils.java b/src/main/java/io/common/validator/AssertUtils.java
new file mode 100644
index 0000000..c2c5bb5
--- /dev/null
+++ b/src/main/java/io/common/validator/AssertUtils.java
@@ -0,0 +1,92 @@
+package io.common.validator;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+
+import io.common.exception.ErrorCode;
+import io.common.exception.RenException;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 校验工具类
+ *
+
+ * @since 1.0.0
+ */
+public class AssertUtils {
+
+ public static void isBlank(String str, String... params) {
+ isBlank(str, ErrorCode.NOT_NULL, params);
+ }
+
+ public static void isBlank(String str, Integer code, String... params) {
+ if(code == null){
+ throw new RenException(ErrorCode.NOT_NULL, "code");
+ }
+
+ if (StrUtil.isBlank(str)) {
+ throw new RenException(code, params);
+ }
+ }
+
+ public static void isNull(Object object, String... params) {
+ isNull(object, ErrorCode.NOT_NULL, params);
+ }
+
+ public static void isNull(Object object, Integer code, String... params) {
+ if(code == null){
+ throw new RenException(ErrorCode.NOT_NULL, "code");
+ }
+
+ if (object == null) {
+ throw new RenException(code, params);
+ }
+ }
+
+ public static void isArrayEmpty(Object[] array, String... params) {
+ isArrayEmpty(array, ErrorCode.NOT_NULL, params);
+ }
+
+ public static void isArrayEmpty(Object[] array, Integer code, String... params) {
+ if(code == null){
+ throw new RenException(ErrorCode.NOT_NULL, "code");
+ }
+
+ if(ArrayUtil.isEmpty(array)){
+ throw new RenException(code, params);
+ }
+ }
+
+ public static void isListEmpty(List> list, String... params) {
+ isListEmpty(list, ErrorCode.NOT_NULL, params);
+ }
+
+ public static void isListEmpty(List> list, Integer code, String... params) {
+ if(code == null){
+ throw new RenException(ErrorCode.NOT_NULL, "code");
+ }
+
+ if(CollUtil.isEmpty(list)){
+ throw new RenException(code, params);
+ }
+ }
+
+ public static void isMapEmpty(Map map, String... params) {
+ isMapEmpty(map, ErrorCode.NOT_NULL, params);
+ }
+
+ public static void isMapEmpty(Map map, Integer code, String... params) {
+ if(code == null){
+ throw new RenException(ErrorCode.NOT_NULL, "code");
+ }
+
+ if(MapUtil.isEmpty(map)){
+ throw new RenException(code, params);
+ }
+ }
+}
diff --git a/src/main/java/io/config/MybatisPlusConfig.java b/src/main/java/io/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..18218f3
--- /dev/null
+++ b/src/main/java/io/config/MybatisPlusConfig.java
@@ -0,0 +1,21 @@
+package io.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MybatisPlusConfig {
+
+ /**
+ * 新的分页插件配置方式
+ */
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ return interceptor;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/config/ResourceConfig.java b/src/main/java/io/config/ResourceConfig.java
new file mode 100644
index 0000000..72c4367
--- /dev/null
+++ b/src/main/java/io/config/ResourceConfig.java
@@ -0,0 +1,18 @@
+package io.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class ResourceConfig implements WebMvcConfigurer {
+ @Value("${spring.web.resources.static-locations}")
+ private String path;
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/upload/**")
+ .addResourceLocations(path);
+ }
+}
diff --git a/src/main/java/io/config/WebMvcConfig.java b/src/main/java/io/config/WebMvcConfig.java
new file mode 100644
index 0000000..f829654
--- /dev/null
+++ b/src/main/java/io/config/WebMvcConfig.java
@@ -0,0 +1,77 @@
+package io.config;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.common.interceptor.AuthorizationInterceptor;
+import io.common.resolver.LoginUserHandlerMethodArgumentResolver;
+import io.common.utils.DateUtils;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * MVC配置
+ *
+
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+ @Resource
+ private AuthorizationInterceptor authorizationInterceptor;
+ @Resource
+ private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(authorizationInterceptor).addPathPatterns("/api/**");
+ }
+
+ @Override
+ public void addArgumentResolvers(List argumentResolvers) {
+ argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
+ }
+
+ @Override
+ public void configureMessageConverters(List> converters) {
+ converters.add(new ByteArrayHttpMessageConverter());
+ converters.add(new StringHttpMessageConverter());
+ converters.add(new ResourceHttpMessageConverter());
+ converters.add(new AllEncompassingFormHttpMessageConverter());
+ converters.add(new StringHttpMessageConverter());
+ converters.add(jackson2HttpMessageConverter());
+ }
+
+ @Bean
+ public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
+ MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+ ObjectMapper mapper = new ObjectMapper();
+
+ //日期格式转换
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
+ mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
+
+ //Long类型转String类型
+ SimpleModule simpleModule = new SimpleModule();
+ simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+ simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
+ mapper.registerModule(simpleModule);
+
+ converter.setObjectMapper(mapper);
+ return converter;
+ }
+}
diff --git a/src/main/java/io/controller/UserController.java b/src/main/java/io/controller/UserController.java
new file mode 100644
index 0000000..1bcb18a
--- /dev/null
+++ b/src/main/java/io/controller/UserController.java
@@ -0,0 +1,85 @@
+package io.controller;
+
+import cn.hutool.crypto.digest.DigestUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.common.annotation.Login;
+import io.common.annotation.LoginUser;
+import io.common.utils.Result;
+import io.entity.UserEntity;
+import io.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import java.util.Map;
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+
+ @Autowired
+ private UserService userService;
+
+ // UserController.java 添加登录方法
+ @PostMapping("/login")
+ public Result login(@Valid @RequestBody UserEntity dto) {
+ //用户登录
+ Map map = userService.login(dto);
+ return new Result().ok(map);
+ }
+
+
+ // UserController.java 添加注册方法
+ @PostMapping("/register")
+ public Result register(@Valid @RequestBody UserEntity dto) {
+ // 检查用户名是否已存在
+ if (userService.existsUsername(dto.getUsername())) {
+ return new Result().error("用户名已存在");
+ }
+ // 密码加密
+ String sha256Hex = DigestUtil.sha256Hex(dto.getPassword());
+ UserEntity user = new UserEntity();
+ user.setPassword(sha256Hex);
+ user.setUsername(dto.getUsername());
+ user.setRole("USER"); // 默认启用
+ userService.save(user);
+ return new Result().ok("注册成功");
+ }
+
+
+ @GetMapping("/page")
+ public Page page(@RequestParam(defaultValue = "1") Integer current,
+ @RequestParam(defaultValue = "10") Integer size) {
+ Page page = new Page<>(current, size);
+ return userService.page(page);
+ }
+
+ @Login
+ @GetMapping
+ public Result list(@Parameter(hidden = true) @RequestAttribute("userId") Long userId) {
+ System.out.println(userId);
+ return new Result().ok(userService.list()) ; // 查询所有用户
+ }
+
+ @PostMapping
+ public boolean save(@RequestBody UserEntity user) {
+ return userService.save(user); // 新增用户
+ }
+
+ @PutMapping
+ public boolean update(@RequestBody UserEntity user) {
+ return userService.updateById(user); // 更新用户
+ }
+
+ @DeleteMapping("/{id}")
+ public boolean delete(@PathVariable Long id) {
+ return userService.removeById(id); // 删除用户
+ }
+
+ @Login
+ @GetMapping("userInfo")
+ @Operation(summary = "获取用户信息")
+ public Result userInfo(@Parameter(hidden = true) @LoginUser UserEntity user) {
+ return new Result().ok(user);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/dao/BaseDao.java b/src/main/java/io/dao/BaseDao.java
new file mode 100644
index 0000000..9f8067e
--- /dev/null
+++ b/src/main/java/io/dao/BaseDao.java
@@ -0,0 +1,10 @@
+package io.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 基础Dao
+ */
+public interface BaseDao extends BaseMapper {
+
+}
diff --git a/src/main/java/io/dao/TokenDao.java b/src/main/java/io/dao/TokenDao.java
new file mode 100644
index 0000000..4232961
--- /dev/null
+++ b/src/main/java/io/dao/TokenDao.java
@@ -0,0 +1,18 @@
+
+
+package io.dao;
+
+import io.entity.TokenEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户Token
+ *
+
+ */
+@Mapper
+public interface TokenDao extends BaseDao {
+ TokenEntity getByToken(String token);
+
+ TokenEntity getByUserId(Long userId);
+}
diff --git a/src/main/java/io/dao/UserDao.java b/src/main/java/io/dao/UserDao.java
new file mode 100644
index 0000000..bee68ac
--- /dev/null
+++ b/src/main/java/io/dao/UserDao.java
@@ -0,0 +1,13 @@
+package io.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import io.entity.UserEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户
+ */
+@Mapper
+public interface UserDao extends BaseMapper {
+
+}
diff --git a/src/main/java/io/entity/TokenEntity.java b/src/main/java/io/entity/TokenEntity.java
new file mode 100644
index 0000000..be32967
--- /dev/null
+++ b/src/main/java/io/entity/TokenEntity.java
@@ -0,0 +1,36 @@
+package io.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户Token
+ */
+@Data
+@TableName("tb_token")
+public class TokenEntity implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @TableId
+ private Long id;
+ /**
+ * 用户ID
+ */
+ private Long userId;
+ /**
+ * 用户token
+ */
+ private String token;
+ /**
+ * 过期时间
+ */
+ private Date expireDate;
+ /**
+ * 更新时间
+ */
+ private Date updateDate;
+
+}
diff --git a/src/main/java/io/entity/UserEntity.java b/src/main/java/io/entity/UserEntity.java
new file mode 100644
index 0000000..c48ff72
--- /dev/null
+++ b/src/main/java/io/entity/UserEntity.java
@@ -0,0 +1,25 @@
+package io.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import java.util.Date;
+/**
+ * 用户
+ */
+@Data
+@TableName("tb_user")
+public class UserEntity {
+
+ private Long id;
+ private String role;
+ private String username;
+ private String password;
+ private Date createDate;
+ private String nickName;
+ private String tag;
+ private String avatar; // 头像URL
+ @TableField(exist = false)
+ private String confirmPassword;
+}
diff --git a/src/main/java/io/service/TokenService.java b/src/main/java/io/service/TokenService.java
new file mode 100644
index 0000000..5a7e1b8
--- /dev/null
+++ b/src/main/java/io/service/TokenService.java
@@ -0,0 +1,29 @@
+package io.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import io.entity.TokenEntity;
+
+/**
+ * 用户Token
+ *
+ */
+public interface TokenService extends IService {
+
+ TokenEntity getByToken(String token);
+
+ /**
+ * 生成token
+ *
+ * @param userId 用户ID
+ * @return 返回token信息
+ */
+ TokenEntity createToken(Long userId);
+
+ /**
+ * 设置token过期
+ *
+ * @param userId 用户ID
+ */
+ void expireToken(Long userId);
+
+}
diff --git a/src/main/java/io/service/UserService.java b/src/main/java/io/service/UserService.java
new file mode 100644
index 0000000..f2a4bc0
--- /dev/null
+++ b/src/main/java/io/service/UserService.java
@@ -0,0 +1,20 @@
+package io.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import io.entity.UserEntity;
+
+import java.util.Map;
+
+public interface UserService extends IService {
+
+ //通过用户编号或者用户详情
+ UserEntity getUserByUserId(Long userId);
+
+ //判断用户是否存在
+ boolean existsUsername(String username);
+
+ //通过用户名获取用户信息
+ UserEntity getByUsername(String username);
+
+ Map login(UserEntity dto);
+}
\ No newline at end of file
diff --git a/src/main/java/io/service/impl/TokenServiceImpl.java b/src/main/java/io/service/impl/TokenServiceImpl.java
new file mode 100644
index 0000000..b18def1
--- /dev/null
+++ b/src/main/java/io/service/impl/TokenServiceImpl.java
@@ -0,0 +1,81 @@
+package io.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.dao.TokenDao;
+import io.entity.TokenEntity;
+import io.service.TokenService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import java.util.Date;
+import java.util.UUID;
+
+
+@Service
+public class TokenServiceImpl extends ServiceImpl implements TokenService {
+
+ @Resource
+ private TokenDao tokenDao;
+ /**
+ * 12小时后过期
+ */
+ private final static int EXPIRE = 3600 * 12;
+
+ @Override
+ public TokenEntity getByToken(String token) {
+ return baseMapper.getByToken(token);
+ }
+
+ @Override
+ public TokenEntity createToken(Long userId) {
+ //当前时间
+ Date now = new Date();
+ //过期时间
+ Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
+
+ //用户token
+ String token;
+
+ //判断是否生成过token
+ TokenEntity tokenEntity = baseMapper.getByUserId(userId);
+ if(tokenEntity == null){
+ //生成一个token
+ token = generateToken();
+
+ tokenEntity = new TokenEntity();
+ tokenEntity.setUserId(userId);
+ tokenEntity.setToken(token);
+ tokenEntity.setUpdateDate(now);
+ tokenEntity.setExpireDate(expireTime);
+ //保存token
+ baseMapper.insert(tokenEntity);
+ }else{
+ //判断token是否过期
+ if(tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){
+ //token过期,重新生成token
+ token = generateToken();
+ }else {
+ token = tokenEntity.getToken();
+ }
+ tokenEntity.setToken(token);
+ tokenEntity.setUpdateDate(now);
+ tokenEntity.setExpireDate(expireTime);
+ //更新token
+ this.updateById(tokenEntity);
+ }
+ return tokenEntity;
+ }
+
+ @Override
+ public void expireToken(Long userId){
+ Date now = new Date();
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.eq(TokenEntity::getUserId, userId);
+ updateWrapper.set(TokenEntity::getExpireDate, now);
+ updateWrapper.set(TokenEntity::getUpdateDate, now);
+ tokenDao.update(updateWrapper);
+ }
+ private String generateToken(){
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+}
diff --git a/src/main/java/io/service/impl/UserServiceImpl.java b/src/main/java/io/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..4e85107
--- /dev/null
+++ b/src/main/java/io/service/impl/UserServiceImpl.java
@@ -0,0 +1,65 @@
+package io.service.impl;
+
+import cn.hutool.crypto.digest.DigestUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.common.exception.RenException;
+import io.common.validator.AssertUtils;
+import io.dao.UserDao;
+import io.entity.TokenEntity;
+import io.entity.UserEntity;
+import io.service.TokenService;
+import io.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 用户
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl implements UserService {
+
+ @Autowired
+ private TokenService tokenService;
+
+ @Override
+ public UserEntity getUserByUserId(Long userId) {
+ return baseMapper.selectById(userId);
+ }
+
+ @Override
+ public boolean existsUsername(String username) {
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.eq("username", username);
+ return baseMapper.selectCount(wrapper) > 0;
+ }
+
+ @Override
+ public UserEntity getByUsername(String username) {
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.eq("username", username);
+ return baseMapper.selectOne(wrapper);
+ }
+
+ @Override
+ public Map login(UserEntity dto) {
+ UserEntity user = getByUsername(dto.getUsername());
+ AssertUtils.isNull(user, "用户名不存在~");
+
+ //密码错误
+ if (!user.getPassword().equals(DigestUtil.sha256Hex(dto.getPassword()))) {
+ throw new RenException("密码输入错误~");
+ }
+
+ //获取登录token
+ TokenEntity tokenEntity = tokenService.createToken(user.getId());
+
+ Map map = new HashMap<>(2);
+ map.put("token", tokenEntity.getToken());
+ map.put("expire", tokenEntity.getExpireDate().getTime() - System.currentTimeMillis());
+ return map;
+ }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..c8240b9
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,46 @@
+spring:
+ web:
+ resources:
+ static-locations: "file:${upload.path}"
+ datasource:
+ druid:
+ #MySQL
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+ username: root
+ password: 123456
+ initial-size: 10
+ max-active: 100
+ min-idle: 10
+ max-wait: 6000
+ pool-prepared-statements: true
+ max-pool-prepared-statement-per-connection-size: 20
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ stat-view-servlet:
+ enabled: true
+ url-pattern: /druid/*
+mybatis-plus:
+ mapper-locations: classpath*:/mapper/**/*.xml
+ typeAliasesPackage: io.modules.*.entity,io.modules.*.entity
+ global-config:
+ db-config:
+ id-type: AUTO
+ banner: false
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: false
+ call-setters-on-nulls: true
+ jdbc-type-for-null: 'null'
+ configuration-properties:
+ prefix:
+ blobType: BLOB
+ boolValue: TRUE
+
+upload:
+ # path: ./upload
+ path: ${user.dir}${file.separator}upload${file.separator}
+ url: http://localhost:18081/
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..581164b
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,45 @@
+# Tomcat
+server:
+ tomcat:
+ uri-encoding: UTF-8
+ threads:
+ max: 1000
+ min-spare: 30
+ port: 18080
+ servlet:
+ context-path: /
+ session:
+ cookie:
+ http-only: true
+
+spring:
+ profiles:
+ active: dev
+ messages:
+ encoding: UTF-8
+ basename: i18n/messages
+ mvc:
+ pathmatch:
+ matching-strategy: ANT_PATH_MATCHER
+ servlet:
+ multipart:
+ max-file-size: 100MB
+ max-request-size: 100MB
+ enabled: true
+#mybatis
+mybatis-plus:
+ mapper-locations: classpath*:/mapper/**/*.xml
+ typeAliasesPackage: io.modules.*.entity
+ global-config:
+ db-config:
+ id-type: AUTO
+ banner: false
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: false
+ call-setters-on-nulls: true
+ jdbc-type-for-null: 'null'
+ configuration-properties:
+ prefix:
+ blobType: BLOB
+ boolValue: TRUE