From 142150c9070cfd9af7ec1fea2ee4c098dddfbdf1 Mon Sep 17 00:00:00 2001 From: 18796357645 <674126018@qq.com> Date: Thu, 12 Jun 2025 13:27:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=90=AD=E5=BB=BA=E5=90=8E=E5=8F=B0=E6=A1=86?= =?UTF-8?q?=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 51 ++++---- README.md | 4 +- pom.xml | 119 ++++++++++++++++++ src/main/java/io/Application.java | 12 ++ src/main/java/io/common/annotation/Login.java | 12 ++ .../java/io/common/annotation/LoginUser.java | 19 +++ .../java/io/common/exception/ErrorCode.java | 18 +++ .../io/common/exception/ExceptionUtils.java | 43 +++++++ .../io/common/exception/RenException.java | 46 +++++++ .../common/exception/RenExceptionHandler.java | 41 ++++++ .../interceptor/AuthorizationInterceptor.java | 56 +++++++++ ...oginUserHandlerMethodArgumentResolver.java | 44 +++++++ src/main/java/io/common/utils/DateUtils.java | 54 ++++++++ src/main/java/io/common/utils/Result.java | 75 +++++++++++ .../java/io/common/validator/AssertUtils.java | 92 ++++++++++++++ .../java/io/config/MybatisPlusConfig.java | 21 ++++ src/main/java/io/config/ResourceConfig.java | 18 +++ src/main/java/io/config/WebMvcConfig.java | 77 ++++++++++++ .../java/io/controller/UserController.java | 85 +++++++++++++ src/main/java/io/dao/BaseDao.java | 10 ++ src/main/java/io/dao/TokenDao.java | 18 +++ src/main/java/io/dao/UserDao.java | 13 ++ src/main/java/io/entity/TokenEntity.java | 36 ++++++ src/main/java/io/entity/UserEntity.java | 25 ++++ src/main/java/io/service/TokenService.java | 29 +++++ src/main/java/io/service/UserService.java | 20 +++ .../io/service/impl/TokenServiceImpl.java | 81 ++++++++++++ .../java/io/service/impl/UserServiceImpl.java | 65 ++++++++++ src/main/resources/application-dev.yml | 46 +++++++ src/main/resources/application.yaml | 45 +++++++ 30 files changed, 1252 insertions(+), 23 deletions(-) create mode 100644 pom.xml create mode 100644 src/main/java/io/Application.java create mode 100644 src/main/java/io/common/annotation/Login.java create mode 100644 src/main/java/io/common/annotation/LoginUser.java create mode 100644 src/main/java/io/common/exception/ErrorCode.java create mode 100644 src/main/java/io/common/exception/ExceptionUtils.java create mode 100644 src/main/java/io/common/exception/RenException.java create mode 100644 src/main/java/io/common/exception/RenExceptionHandler.java create mode 100644 src/main/java/io/common/interceptor/AuthorizationInterceptor.java create mode 100644 src/main/java/io/common/resolver/LoginUserHandlerMethodArgumentResolver.java create mode 100644 src/main/java/io/common/utils/DateUtils.java create mode 100644 src/main/java/io/common/utils/Result.java create mode 100644 src/main/java/io/common/validator/AssertUtils.java create mode 100644 src/main/java/io/config/MybatisPlusConfig.java create mode 100644 src/main/java/io/config/ResourceConfig.java create mode 100644 src/main/java/io/config/WebMvcConfig.java create mode 100644 src/main/java/io/controller/UserController.java create mode 100644 src/main/java/io/dao/BaseDao.java create mode 100644 src/main/java/io/dao/TokenDao.java create mode 100644 src/main/java/io/dao/UserDao.java create mode 100644 src/main/java/io/entity/TokenEntity.java create mode 100644 src/main/java/io/entity/UserEntity.java create mode 100644 src/main/java/io/service/TokenService.java create mode 100644 src/main/java/io/service/UserService.java create mode 100644 src/main/java/io/service/impl/TokenServiceImpl.java create mode 100644 src/main/java/io/service/impl/UserServiceImpl.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application.yaml 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