From 4abcc03a55d9a354ee73e5f5adddd20c9e0ac173 Mon Sep 17 00:00:00 2001 From: ovo Date: Fri, 6 Dec 2024 20:36:28 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 2 + .gitignore | 33 + .mvn/wrapper/maven-wrapper.properties | 19 + logs/backend.log | 192 ++++++ logs/backend.log.2024-12-04.0.gz | Bin 0 -> 1352 bytes pom.xml | 155 +++++ .../com/guwan/backend/BackendApplication.java | 15 + .../common/GlobalExceptionHandler.java | 29 + .../java/com/guwan/backend/common/Result.java | 85 +++ .../com/guwan/backend/config/CorsConfig.java | 25 + .../backend/config/DataSourceConfig.java | 12 + .../backend/config/DatabaseInitConfig.java | 34 ++ .../backend/config/MybatisPlusConfig.java | 14 + .../com/guwan/backend/config/RedisConfig.java | 64 ++ .../guwan/backend/config/SecurityConfig.java | 57 ++ .../backend/controller/DemoController.java | 52 ++ .../backend/controller/UserController.java | 153 +++++ .../guwan/backend/dto/user/RegisterDTO.java | 31 + .../com/guwan/backend/dto/user/UserDTO.java | 21 + .../guwan/backend/entity/CommunityPost.java | 47 ++ .../java/com/guwan/backend/entity/SmsLog.java | 30 + .../java/com/guwan/backend/entity/User.java | 40 ++ .../com/guwan/backend/mapper/UserMapper.java | 9 + .../backend/security/CustomUserDetails.java | 21 + .../security/JwtAuthenticationFilter.java | 111 ++++ .../security/UserDetailsServiceImpl.java | 66 ++ .../guwan/backend/service/EmailService.java | 42 ++ .../guwan/backend/service/FileService.java | 5 + .../com/guwan/backend/service/SmsService.java | 13 + .../guwan/backend/service/UserService.java | 24 + .../backend/service/VerificationService.java | 8 + .../service/impl/AliyunSmsServiceImpl.java | 55 ++ .../service/impl/DummyFileServiceImpl.java | 18 + .../service/impl/DummySmsServiceImpl.java | 22 + .../backend/service/impl/FileServiceImpl.java | 46 ++ .../backend/service/impl/UserServiceImpl.java | 201 +++++++ .../service/impl/VerificationServiceImpl.java | 122 ++++ .../java/com/guwan/backend/util/JwtUtil.java | 62 ++ .../com/guwan/backend/util/RedisUtil.java | 120 ++++ .../com/guwan/backend/util/RedisUtils.java | 562 ++++++++++++++++++ .../java/com/guwan/backend/util/SmsUtils.java | 69 +++ .../backend/vo/user/EmailRegisterRequest.java | 20 + .../backend/vo/user/FaceLoginRequest.java | 8 + .../guwan/backend/vo/user/LoginRequest.java | 13 + .../backend/vo/user/PhoneRegisterRequest.java | 20 + .../backend/vo/user/RegisterRequest.java | 31 + src/main/resources/application.yml | 84 +++ src/main/resources/db/data.sql | 4 + src/main/resources/db/schema.sql | 33 + .../templates/email_password_template.html | 147 +++++ .../resources/templates/email_template.html | 158 +++++ .../backend/BackendApplicationTests.java | 13 + 52 files changed, 3217 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 logs/backend.log create mode 100644 logs/backend.log.2024-12-04.0.gz create mode 100644 pom.xml create mode 100644 src/main/java/com/guwan/backend/BackendApplication.java create mode 100644 src/main/java/com/guwan/backend/common/GlobalExceptionHandler.java create mode 100644 src/main/java/com/guwan/backend/common/Result.java create mode 100644 src/main/java/com/guwan/backend/config/CorsConfig.java create mode 100644 src/main/java/com/guwan/backend/config/DataSourceConfig.java create mode 100644 src/main/java/com/guwan/backend/config/DatabaseInitConfig.java create mode 100644 src/main/java/com/guwan/backend/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/guwan/backend/config/RedisConfig.java create mode 100644 src/main/java/com/guwan/backend/config/SecurityConfig.java create mode 100644 src/main/java/com/guwan/backend/controller/DemoController.java create mode 100644 src/main/java/com/guwan/backend/controller/UserController.java create mode 100644 src/main/java/com/guwan/backend/dto/user/RegisterDTO.java create mode 100644 src/main/java/com/guwan/backend/dto/user/UserDTO.java create mode 100644 src/main/java/com/guwan/backend/entity/CommunityPost.java create mode 100644 src/main/java/com/guwan/backend/entity/SmsLog.java create mode 100644 src/main/java/com/guwan/backend/entity/User.java create mode 100644 src/main/java/com/guwan/backend/mapper/UserMapper.java create mode 100644 src/main/java/com/guwan/backend/security/CustomUserDetails.java create mode 100644 src/main/java/com/guwan/backend/security/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/guwan/backend/security/UserDetailsServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/EmailService.java create mode 100644 src/main/java/com/guwan/backend/service/FileService.java create mode 100644 src/main/java/com/guwan/backend/service/SmsService.java create mode 100644 src/main/java/com/guwan/backend/service/UserService.java create mode 100644 src/main/java/com/guwan/backend/service/VerificationService.java create mode 100644 src/main/java/com/guwan/backend/service/impl/AliyunSmsServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/DummyFileServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/DummySmsServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/FileServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/VerificationServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/util/JwtUtil.java create mode 100644 src/main/java/com/guwan/backend/util/RedisUtil.java create mode 100644 src/main/java/com/guwan/backend/util/RedisUtils.java create mode 100644 src/main/java/com/guwan/backend/util/SmsUtils.java create mode 100644 src/main/java/com/guwan/backend/vo/user/EmailRegisterRequest.java create mode 100644 src/main/java/com/guwan/backend/vo/user/FaceLoginRequest.java create mode 100644 src/main/java/com/guwan/backend/vo/user/LoginRequest.java create mode 100644 src/main/java/com/guwan/backend/vo/user/PhoneRegisterRequest.java create mode 100644 src/main/java/com/guwan/backend/vo/user/RegisterRequest.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/data.sql create mode 100644 src/main/resources/db/schema.sql create mode 100644 src/main/resources/templates/email_password_template.html create mode 100644 src/main/resources/templates/email_template.html create mode 100644 src/test/java/com/guwan/backend/BackendApplicationTests.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/logs/backend.log b/logs/backend.log new file mode 100644 index 0000000..16208f7 --- /dev/null +++ b/logs/backend.log @@ -0,0 +1,192 @@ +2024-12-06T13:44:09.214+08:00 INFO 31044 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 31044 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:44:09.224+08:00 DEBUG 31044 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:44:09.225+08:00 INFO 31044 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:44:10.400+08:00 INFO 31044 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:44:10.402+08:00 INFO 31044 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:44:10.441+08:00 INFO 31044 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 22 ms. Found 0 Redis repository interfaces. +2024-12-06T13:44:10.555+08:00 WARN 31044 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:44:10.674+08:00 WARN 31044 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityCommentMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityCommentMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:44:10.680+08:00 INFO 31044 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:44:10.696+08:00 ERROR 31044 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityCommentMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityCommentMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + +2024-12-06T13:47:04.314+08:00 INFO 18440 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 18440 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:47:04.316+08:00 DEBUG 18440 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:47:04.316+08:00 INFO 18440 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:47:04.894+08:00 INFO 18440 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:47:04.896+08:00 INFO 18440 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:47:04.921+08:00 INFO 18440 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13 ms. Found 0 Redis repository interfaces. +2024-12-06T13:47:05.016+08:00 WARN 18440 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:47:05.136+08:00 WARN 18440 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityPostMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityPostMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:47:05.143+08:00 INFO 18440 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:47:05.157+08:00 ERROR 18440 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityPostMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityPostMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + +2024-12-06T13:49:08.712+08:00 INFO 13736 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 13736 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:49:08.714+08:00 DEBUG 13736 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:49:08.714+08:00 INFO 13736 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:49:09.276+08:00 INFO 13736 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:49:09.278+08:00 INFO 13736 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:49:09.305+08:00 INFO 13736 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Redis repository interfaces. +2024-12-06T13:49:09.393+08:00 WARN 13736 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:49:09.499+08:00 WARN 13736 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityPostMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityPostMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:49:09.506+08:00 INFO 13736 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:49:09.521+08:00 ERROR 13736 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'communityPostMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\CommunityPostMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + +2024-12-06T13:50:22.319+08:00 INFO 31604 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 31604 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:50:22.321+08:00 DEBUG 31604 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:50:22.321+08:00 INFO 31604 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:50:22.950+08:00 INFO 31604 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:50:22.952+08:00 INFO 31604 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:50:22.980+08:00 INFO 31604 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Redis repository interfaces. +2024-12-06T13:50:23.072+08:00 WARN 31604 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:50:23.186+08:00 WARN 31604 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:50:23.193+08:00 INFO 31604 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:50:23.207+08:00 ERROR 31604 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + +2024-12-06T13:51:25.658+08:00 INFO 16572 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 16572 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:51:25.660+08:00 DEBUG 16572 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:51:25.660+08:00 INFO 16572 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:51:26.248+08:00 INFO 16572 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:51:26.250+08:00 INFO 16572 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:51:26.275+08:00 INFO 16572 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13 ms. Found 0 Redis repository interfaces. +2024-12-06T13:51:26.377+08:00 WARN 16572 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:51:26.515+08:00 WARN 16572 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:51:26.522+08:00 INFO 16572 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:51:26.537+08:00 ERROR 16572 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + +2024-12-06T13:52:10.594+08:00 INFO 26636 --- [main] com.guwan.backend.BackendApplication : Starting BackendApplication using Java 21 with PID 26636 (D:\23_projects\Cursor\zhyl\backend\target\classes started by 12455 in D:\23_projects\Cursor\zhyl\backend) +2024-12-06T13:52:10.596+08:00 DEBUG 26636 --- [main] com.guwan.backend.BackendApplication : Running with Spring Boot v3.4.0, Spring v6.2.0 +2024-12-06T13:52:10.597+08:00 INFO 26636 --- [main] com.guwan.backend.BackendApplication : No active profile set, falling back to 1 default profile: "default" +2024-12-06T13:52:11.649+08:00 INFO 26636 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode +2024-12-06T13:52:11.651+08:00 INFO 26636 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode. +2024-12-06T13:52:11.686+08:00 INFO 26636 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 19 ms. Found 0 Redis repository interfaces. +2024-12-06T13:52:11.818+08:00 WARN 26636 --- [main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.elderly.mapper]' package. Please check your configuration. +2024-12-06T13:52:12.003+08:00 WARN 26636 --- [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String +2024-12-06T13:52:12.012+08:00 INFO 26636 --- [main] .s.b.a.l.ConditionEvaluationReportLogger : + +Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. +2024-12-06T13:52:12.027+08:00 ERROR 26636 --- [main] o.s.boot.SpringApplication : Application run failed + +org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'userMapper' defined in file [D:\23_projects\Cursor\zhyl\backend\target\classes\com\guwan\backend\mapper\UserMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:858) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:742) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:765) ~[spring-beans-6.2.0.jar:6.2.0] + at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) ~[spring-context-6.2.0.jar:6.2.0] + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.0.jar:3.4.0] + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.0.jar:3.4.0] + at com.guwan.backend.BackendApplication.main(BackendApplication.java:12) ~[classes/:na] + diff --git a/logs/backend.log.2024-12-04.0.gz b/logs/backend.log.2024-12-04.0.gz new file mode 100644 index 0000000000000000000000000000000000000000..c969d60e86a1acf75f3d0e738389bad78386b12f GIT binary patch literal 1352 zcmV-O1-JSiiwFP!00000|Fu_5bJ|D{y}Qc)p(_`%*)S0pW5SIX*dpbx!nkrUuB}08 z&?G{m%#2LP*8cbH9tlir2-(EJ2S7;EuV24@-L41qMzvP228~gz*={u2!CMhD>wgBV zb`SszF8Y^HueFZrP_0(sb}m(V2ce#eS-y~|n8@%GrjdA8ymzuJQK587r@*f^Tsvu; zN@uX;j@>0Xo zm`H2Ug5_8k!DI=wdgJ&QR0{h~`Qz}QzLkVVNkX^x?&hywB4L=PDW~dFyv~g0Lu&{3 zN1`Eu*Q;yyO;H!YK0Lo(lnV_qbm|^~h?%Mc!J>N&Q<)?TgP#Ydp#~97WuCazZEbi_ zUU{(vWGxUuaPobS!iq>35sD#Z+A62b@>Hi&HOq|$*u?~A(jkF7r!XobY$Z8e=_Jf` zlqXc`sm{~rHDKz{|7e|2p#!r%mFU5NkZZxSLFSqg5}9R;X%qJlBV`}pDN2FG z+3R<1&PE=c*a5Fmdp3A|l`0!kgKQ$(pv;D{&?!Ny9mCuT=rhj{JjTDITZs9o4AJfY zUTYk$fPd%=FP;L|VouoSL>3xx$}M~)U3~7pUo#Rk@SxZ8kAsqHNbVW7WLu}V$cCOVW!tSx_ja|bJ34OaEoXxCLWfeN_coYYeezqp^ov>>U3 zqRe}(jHk!*tl+t@HFod7B@;&H<^YfWm|9RQD30DHsX<#4B#ak4|rzIKI{ zd&X!}%v$EFly+EP6wvz;V#e^=pv_EJ&+633IWDyMBqk_RE66z5MZ`^NxvQrNqb_DN zt1u0h>k|Z=qqWq+tDvDl>4WkqMF*vHFjPJ+Tqk!ijf0Z1zDfvgg$Ec(%oF72-0lC~ zD#pa}*w5AV4Hm=PD7PHH-yT-@NAKFOkscvg}&DN;Tm383#{&P+`pDx!)>nXTpMkzmCbIK9IsEX-P z0a__lA}74DP*_TtqLN5?4metC3L0Y-#$_Cy$23u7?({+#8WrSTDx#?=;6ax_Gf6vN zXWNE<{@u}P&9~2J*1A|Fe$1>-PFmj|xy54eLpHQ-LUQSl1}#;F%b9zRESvqYA61#R z5Q`#=OT#~qv`6)!bv`>5a*n7v|}@A1;LNFc_cUug50NO$PIQuR KsY%R*4*&p;$CU;E literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6e51a6e --- /dev/null +++ b/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.1 + + + com.guwan + backend + 0.0.1-SNAPSHOT + backend + backend + + + + + + + + + + + + + + + 21 + 3.5.5 + + + + org.springframework.boot + spring-boot-starter-web + + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.baomidou + mybatis-plus-boot-starter + 3.5.5 + + + mybatis-spring + org.mybatis + + + + + org.mybatis + mybatis-spring + 3.0.3 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.apache.commons + commons-pool2 + + + org.springframework.boot + spring-boot-starter-validation + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework.boot + spring-boot-starter-security + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + com.aliyun + dysmsapi20170525 + 2.0.24 + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.apache.commons + commons-lang3 + + + cn.hutool + hutool-all + 5.8.18 + + + + com.aliyun + dysmsapi20170525 + 3.0.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/guwan/backend/BackendApplication.java b/src/main/java/com/guwan/backend/BackendApplication.java new file mode 100644 index 0000000..4611339 --- /dev/null +++ b/src/main/java/com/guwan/backend/BackendApplication.java @@ -0,0 +1,15 @@ +package com.guwan.backend; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("com.guwan.backend.mapper") +public class BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + +} diff --git a/src/main/java/com/guwan/backend/common/GlobalExceptionHandler.java b/src/main/java/com/guwan/backend/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..d0ecffc --- /dev/null +++ b/src/main/java/com/guwan/backend/common/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package com.guwan.backend.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public Result handleException(Exception e) { + log.error("系统异常", e); + return Result.error(e.getMessage()); + } + + @ExceptionHandler(BindException.class) + public Result handleBindException(BindException e) { + log.error("参数校验异常", e); + return Result.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public Result handleIllegalArgumentException(IllegalArgumentException e) { + log.error("参数异常", e); + return Result.error(e.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/common/Result.java b/src/main/java/com/guwan/backend/common/Result.java new file mode 100644 index 0000000..6cd0e5c --- /dev/null +++ b/src/main/java/com/guwan/backend/common/Result.java @@ -0,0 +1,85 @@ +package com.guwan.backend.common; + +import lombok.Data; + +@Data +public class Result { + private Integer code; + private String message; + private T data; + private Long timestamp; + + public Result() { + this.timestamp = System.currentTimeMillis(); + } + + public static Result success() { + Result result = new Result<>(); + result.setCode(200); + result.setMessage("操作成功"); + return result; + } + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage("操作成功"); + result.setData(data); + return result; + } + + public static Result success(String message, T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage(message); + result.setData(data); + return result; + } + + public static Result error(String message) { + Result result = new Result<>(); + result.setCode(500); + result.setMessage(message); + return result; + } + + public static Result error(Integer code, String message) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + return result; + } + + public static Result error(Integer code, String message, T data) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + result.setData(data); + return result; + } + + // 常用状态码 + public static final int SUCCESS = 200; + public static final int ERROR = 500; + public static final int UNAUTHORIZED = 401; + public static final int FORBIDDEN = 403; + public static final int NOT_FOUND = 404; + public static final int VALIDATE_FAILED = 400; + + // 业务状态码 + public static Result validateFailed(String message) { + return error(VALIDATE_FAILED, message); + } + + public static Result unauthorized(String message) { + return error(UNAUTHORIZED, message); + } + + public static Result forbidden(String message) { + return error(FORBIDDEN, message); + } + + public static Result notFound(String message) { + return error(NOT_FOUND, message); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/CorsConfig.java b/src/main/java/com/guwan/backend/config/CorsConfig.java new file mode 100644 index 0000000..c10e43c --- /dev/null +++ b/src/main/java/com/guwan/backend/config/CorsConfig.java @@ -0,0 +1,25 @@ +package com.guwan.backend.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +public class CorsConfig { + + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedOriginPattern("*"); + config.setAllowCredentials(true); + config.addAllowedMethod("*"); + config.addAllowedHeader("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/DataSourceConfig.java b/src/main/java/com/guwan/backend/config/DataSourceConfig.java new file mode 100644 index 0000000..397d3ea --- /dev/null +++ b/src/main/java/com/guwan/backend/config/DataSourceConfig.java @@ -0,0 +1,12 @@ +package com.guwan.backend.config; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({DataSourceAutoConfiguration.class}) +public class DataSourceConfig { + // 空配置类,只是为了控制自动配置的导入 +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/DatabaseInitConfig.java b/src/main/java/com/guwan/backend/config/DatabaseInitConfig.java new file mode 100644 index 0000000..f85ab39 --- /dev/null +++ b/src/main/java/com/guwan/backend/config/DatabaseInitConfig.java @@ -0,0 +1,34 @@ +package com.guwan.backend.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +import javax.sql.DataSource; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class DatabaseInitConfig implements ApplicationRunner { + + private final DataSource dataSource; + + @Override + public void run(ApplicationArguments args) { + log.info("开始初始化数据库..."); + try { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScript(new ClassPathResource("db/schema.sql")); + populator.addScript(new ClassPathResource("db/data.sql")); + populator.setContinueOnError(true); + populator.execute(dataSource); + log.info("数据库初始化完成"); + } catch (Exception e) { + log.error("数据库初始化失败: {}", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/MybatisPlusConfig.java b/src/main/java/com/guwan/backend/config/MybatisPlusConfig.java new file mode 100644 index 0000000..74270be --- /dev/null +++ b/src/main/java/com/guwan/backend/config/MybatisPlusConfig.java @@ -0,0 +1,14 @@ +package com.guwan.backend.config; + +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/com/guwan/backend/config/RedisConfig.java b/src/main/java/com/guwan/backend/config/RedisConfig.java new file mode 100644 index 0000000..4764f72 --- /dev/null +++ b/src/main/java/com/guwan/backend/config/RedisConfig.java @@ -0,0 +1,64 @@ +package com.guwan.backend.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(mapper); + + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + template.setKeySerializer(stringRedisSerializer); + template.setHashKeySerializer(stringRedisSerializer); + template.setValueSerializer(jackson2JsonRedisSerializer); + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.afterPropertiesSet(); + + return template; + } + + @Bean + public CacheManager cacheManager(RedisConnectionFactory factory) { + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(mapper); + + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofHours(1)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) + .disableCachingNullValues(); + + return RedisCacheManager.builder(factory) + .cacheDefaults(config) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/SecurityConfig.java b/src/main/java/com/guwan/backend/config/SecurityConfig.java new file mode 100644 index 0000000..ef24003 --- /dev/null +++ b/src/main/java/com/guwan/backend/config/SecurityConfig.java @@ -0,0 +1,57 @@ +package com.guwan.backend.config; + +import com.guwan.backend.security.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtAuthenticationFilter jwtAuthFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .cors(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers( + "/demo/**", + "/api/user/register", + "/api/user/login", + "/api/user/register/email", + "/api/user/register/phone", + "/api/user/email/code", + "/api/user/phone/code" + ).permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/DemoController.java b/src/main/java/com/guwan/backend/controller/DemoController.java new file mode 100644 index 0000000..c611f9c --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/DemoController.java @@ -0,0 +1,52 @@ +package com.guwan.backend.controller; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import com.guwan.backend.common.Result; +import com.guwan.backend.service.EmailService; +import com.guwan.backend.util.RedisUtils; +import com.guwan.backend.util.SmsUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.thymeleaf.context.Context; + +@Slf4j +@RestController +@RequestMapping("/demo") +@RequiredArgsConstructor +@Validated +public class DemoController { + private final EmailService emailService; + private final RedisUtils redisUtils; + + @GetMapping("/getEmailCode") + public Result getEmailCode(String email) { + + Context context = new Context(); + context.setVariable("nowDate", DateUtil.now()); + String code = RandomUtil.randomNumbers(6); + redisUtils.set(email, code, 10); + context.setVariable("code", code.toCharArray()); + + emailService.sendHtmlMessage(email, + "养老平台邮箱验证码", "email_template.html", context); + return Result.success(); + } + + + @GetMapping("/getPhoneCode") + public Result getPhoneCode(String phone) throws Exception { + + String random = RandomUtil.randomNumbers(6); + + SmsUtils.sendMessage(phone, random); + + redisUtils.set(phone, random, 10); + return Result.success(); + } + +} diff --git a/src/main/java/com/guwan/backend/controller/UserController.java b/src/main/java/com/guwan/backend/controller/UserController.java new file mode 100644 index 0000000..0183ae3 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/UserController.java @@ -0,0 +1,153 @@ +package com.guwan.backend.controller; + +import com.guwan.backend.common.Result; +import com.guwan.backend.dto.user.RegisterDTO; +import com.guwan.backend.dto.user.UserDTO; +import com.guwan.backend.service.UserService; +import com.guwan.backend.vo.user.LoginRequest; +import com.guwan.backend.vo.user.RegisterRequest; +import com.guwan.backend.vo.user.EmailRegisterRequest; +import com.guwan.backend.vo.user.PhoneRegisterRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import jakarta.validation.constraints.Email; + +@Slf4j +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +@Validated +public class UserController { + + private final UserService userService; + + @PostMapping("/register") + public Result register(@RequestBody @Valid RegisterDTO request) { + try { + log.info("用户注册: {}", request.getUsername()); + return Result.success("注册成功", userService.register(request)); + } catch (IllegalArgumentException e) { + return Result.validateFailed(e.getMessage()); + } catch (Exception e) { + log.error("注册失败", e); + return Result.error("系统错误"); + } + } + + @PostMapping("/login") + public Result login(@RequestBody @Valid LoginRequest request) { + try { + log.info("用户登录: {}", request.getUsername()); + return Result.success("登录成功", userService.login(request)); + } catch (IllegalArgumentException e) { + return Result.unauthorized(e.getMessage()); + } catch (Exception e) { + log.error("登录失败", e); + return Result.error("系统错误"); + } + } + + @GetMapping("/current") + public Result getCurrentUser() { + UserDTO user = userService.getCurrentUser(); + if (user == null) { + return Result.unauthorized("用户未登录"); + } + return Result.success(user); + } + + @GetMapping("/{id}") + public Result getUserById(@PathVariable Long id) { + UserDTO user = userService.getUserById(id); + if (user == null) { + return Result.notFound("用户不存在"); + } + return Result.success(user); + } + + @PostMapping("/token/refresh") + public Result refreshToken(@RequestHeader(value = "Authorization", required = false) String token) { + if (token == null || !token.startsWith("Bearer ")) { + return Result.error("无效的token"); + } + try { + String newToken = userService.refreshToken(token.substring(7)); + return Result.success(newToken); + } catch (Exception e) { + log.error("刷新token失败", e); + return Result.error(e.getMessage()); + } + } + + @PostMapping("/register/email") + public Result registerByEmail(@RequestBody @Valid EmailRegisterRequest request) { + log.info("邮箱注册: {}", request.getEmail()); + return Result.success(userService.registerByEmail(request)); + } + + @PostMapping("/register/phone") + public Result registerByPhone(@RequestBody @Valid PhoneRegisterRequest request) { + log.info("手机号注册: {}", request.getPhone()); + return Result.success(userService.registerByPhone(request)); + } + + @PostMapping("/email/code") + public Result sendEmailCode( + @RequestParam @Email(message = "邮箱格式不正确") String email) { + try { + userService.sendEmailCode(email); + return Result.success(); + } catch (Exception e) { + log.error("发送邮箱验证码失败", e); + return Result.error(e.getMessage()); + } + } + + @PostMapping("/phone/code") + public Result sendPhoneCode( + @RequestParam @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String phone) { + try { + userService.sendPhoneCode(phone); + return Result.success(); + } catch (Exception e) { + log.error("发送短信验证码失败", e); + return Result.error(e.getMessage()); + } + } + + @PostMapping("/password/reset") + public Result resetPassword(@RequestParam @Email String email) { + try { + userService.resetPassword(email); + return Result.success(); + } catch (Exception e) { + log.error("重置密码失败", e); + return Result.error(e.getMessage()); + } + } + + @PostMapping("/password/change") + public Result changePassword(@RequestParam String oldPassword, @RequestParam String newPassword) { + try { + userService.changePassword(oldPassword, newPassword); + return Result.success(); + } catch (Exception e) { + log.error("修改密码失败", e); + return Result.error(e.getMessage()); + } + } + + @PutMapping("/info") + public Result updateUserInfo(@RequestBody @Valid UserDTO userDTO) { + try { + return Result.success(userService.updateUserInfo(userDTO)); + } catch (Exception e) { + log.error("更新用户信息失败", e); + return Result.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/dto/user/RegisterDTO.java b/src/main/java/com/guwan/backend/dto/user/RegisterDTO.java new file mode 100644 index 0000000..fade5eb --- /dev/null +++ b/src/main/java/com/guwan/backend/dto/user/RegisterDTO.java @@ -0,0 +1,31 @@ +package com.guwan.backend.dto.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +@Data +public class RegisterDTO { + @NotBlank(message = "用户名不能为空") + @Pattern(regexp = "^[a-zA-Z0-9_]{4,16}$", message = "用户名必须是4-16位字母、数字或下划线") + private String username; + + @NotBlank(message = "密码不能为空") + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", + message = "密码必须包含大小写字母和数字,且不少于8位") + private String password; + + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "邮箱验证码不能为空") + private String emailCode; + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "手机验证码不能为空") + private String phoneCode; + +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/dto/user/UserDTO.java b/src/main/java/com/guwan/backend/dto/user/UserDTO.java new file mode 100644 index 0000000..8cf22dc --- /dev/null +++ b/src/main/java/com/guwan/backend/dto/user/UserDTO.java @@ -0,0 +1,21 @@ +package com.guwan.backend.dto.user; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class UserDTO { + private Long id; + private String username; + private String nickname; + private String avatar; + private String email; + private String phone; + private Integer gender; + private String bio; + private LocalDateTime createdTime; + private LocalDateTime lastLoginTime; + private Integer status; + private String token; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/CommunityPost.java b/src/main/java/com/guwan/backend/entity/CommunityPost.java new file mode 100644 index 0000000..bbc25f6 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/CommunityPost.java @@ -0,0 +1,47 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@TableName(value = "community_post", autoResultMap = true) +public class CommunityPost { + @TableId(type = IdType.AUTO) + private Long id; + + private String title; + + private String content; + + @TableField("author_id") + private Long authorId; + + private String category; + + @TableField(typeHandler = JacksonTypeHandler.class) + private List tags; + + @TableField(value = "image_urls", typeHandler = JacksonTypeHandler.class) + private List imageUrls; + + private Integer likes; + + private Integer comments; + + private Integer views; + + @TableField("created_time") + private LocalDateTime createdTime; + + @TableField("updated_time") + private LocalDateTime updatedTime; + + private Integer status; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/SmsLog.java b/src/main/java/com/guwan/backend/entity/SmsLog.java new file mode 100644 index 0000000..7280c24 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/SmsLog.java @@ -0,0 +1,30 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("sms_log") +public class SmsLog { + @TableId(type = IdType.AUTO) + private Long id; + + private String phone; + + private String type; + + private String content; + + private Integer status; + + @TableField("error_msg") + private String errorMsg; + + @TableField("created_time") + private LocalDateTime createdTime; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/User.java b/src/main/java/com/guwan/backend/entity/User.java new file mode 100644 index 0000000..5a580a1 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/User.java @@ -0,0 +1,40 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("user") +public class User { + @TableId(type = IdType.AUTO) + private Long id; + + private String username; + + private String password; + + private String nickname; + + private String avatar; + + private String email; + + private String phone; + + private Integer gender; + + private String bio; + + @TableField("created_time") + private LocalDateTime createdTime; + + @TableField("last_login_time") + private LocalDateTime lastLoginTime; + + private Integer status; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/UserMapper.java b/src/main/java/com/guwan/backend/mapper/UserMapper.java new file mode 100644 index 0000000..169470c --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/UserMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.User; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/security/CustomUserDetails.java b/src/main/java/com/guwan/backend/security/CustomUserDetails.java new file mode 100644 index 0000000..a4b246b --- /dev/null +++ b/src/main/java/com/guwan/backend/security/CustomUserDetails.java @@ -0,0 +1,21 @@ +package com.guwan.backend.security; + +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.util.Collection; + +@Getter +public class CustomUserDetails extends User { + + private final Long userId; + + public CustomUserDetails(Long userId, String username, String password, + Collection authorities, + boolean enabled) { + super(username, password, enabled, true, true, + true, authorities); + this.userId = userId; + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/guwan/backend/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..3871e45 --- /dev/null +++ b/src/main/java/com/guwan/backend/security/JwtAuthenticationFilter.java @@ -0,0 +1,111 @@ +package com.guwan.backend.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.guwan.backend.common.Result; +import com.guwan.backend.util.JwtUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final UserDetailsServiceImpl userDetailsService; + private final ObjectMapper objectMapper; + + // 不需要验证的路径 + private static final List PERMIT_PATHS = Arrays.asList( + "/demo/**", + "/api/user/register", + "/api/user/login", + "/api/user/register/email", + "/api/user/register/phone", + "/api/user/email/code", + "/api/user/phone/code" + ); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + // 检查是否是允许的路径 + String path = request.getServletPath(); + if (isPermitPath(path)) { + chain.doFilter(request, response); + return; + } + + try { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + handleAuthenticationError(response, "未登录"); + return; + } + + String jwt = authHeader.substring(7); + if (!jwtUtil.validateToken(jwt)) { + handleAuthenticationError(response, "token已过期或无效"); + return; + } + + Long userId = jwtUtil.getUserIdFromToken(jwt); + if (SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserById(userId); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + chain.doFilter(request, response); + } catch (Exception e) { + log.error("认证过程发生错误", e); + handleAuthenticationError(response, "认证失败: " + e.getMessage()); + } + } + + private boolean isPermitPath(String path) { + return PERMIT_PATHS.stream().anyMatch(pattern -> + pattern.endsWith("/**") + ? path.startsWith(pattern.substring(0, pattern.length() - 3)) + : path.equals(pattern) + ); + } + + private void handleAuthenticationError(HttpServletResponse response, String message) throws IOException { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + Result result = Result.error(message); + response.getWriter().write(objectMapper.writeValueAsString(result)); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getServletPath(); + // 静态资源不过滤 + return path.startsWith("/static/") || + path.startsWith("/public/") || + path.startsWith("/error"); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/security/UserDetailsServiceImpl.java b/src/main/java/com/guwan/backend/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..f28d50f --- /dev/null +++ b/src/main/java/com/guwan/backend/security/UserDetailsServiceImpl.java @@ -0,0 +1,66 @@ +package com.guwan.backend.security; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.guwan.backend.entity.User; +import com.guwan.backend.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserMapper userMapper; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getUsername, username) + ); + + if (user == null) { + throw new UsernameNotFoundException("用户不存在"); + } + + if (user.getStatus() != 1) { + throw new UsernameNotFoundException("账号已被禁用"); + } + + List authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + + return new CustomUserDetails( + user.getId(), + user.getUsername(), + user.getPassword(), + authorities, + user.getStatus() == 1 + ); + } + + public UserDetails loadUserById(Long userId) throws UsernameNotFoundException { + User user = userMapper.selectById(userId); + if (user == null) { + throw new UsernameNotFoundException("用户不存在"); + } + + List authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + + return new CustomUserDetails( + user.getId(), + user.getUsername(), + user.getPassword(), + authorities, + user.getStatus() == 1 + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/EmailService.java b/src/main/java/com/guwan/backend/service/EmailService.java new file mode 100644 index 0000000..3605ea0 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/EmailService.java @@ -0,0 +1,42 @@ +package com.guwan.backend.service; + +import jakarta.annotation.Resource; +import jakarta.mail.internet.MimeMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + + + + +@Service +@Slf4j +public class EmailService { + @Resource + private JavaMailSender mailSender; + + @Resource + private TemplateEngine templateEngine; + + @Value("${spring.mail.username}") + String from; + + public void sendHtmlMessage(String to, String subject, String templateName, Context context) { + try { + String process = templateEngine.process(templateName, context); + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + helper.setFrom(from); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(process, true); + mailSender.send(message); + } catch (Exception e) { + log.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/guwan/backend/service/FileService.java b/src/main/java/com/guwan/backend/service/FileService.java new file mode 100644 index 0000000..b09321a --- /dev/null +++ b/src/main/java/com/guwan/backend/service/FileService.java @@ -0,0 +1,5 @@ +package com.guwan.backend.service; + +public interface FileService { + String uploadBase64Image(String base64Image, String folder); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/SmsService.java b/src/main/java/com/guwan/backend/service/SmsService.java new file mode 100644 index 0000000..cb5282c --- /dev/null +++ b/src/main/java/com/guwan/backend/service/SmsService.java @@ -0,0 +1,13 @@ +package com.guwan.backend.service; + +public interface SmsService { + /** + * 发送验证码短信 + */ + void sendVerificationCode(String phone, String code); + + /** + * 发送通知短信 + */ + void sendNotification(String phone, String content); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/UserService.java b/src/main/java/com/guwan/backend/service/UserService.java new file mode 100644 index 0000000..c7d6d52 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/UserService.java @@ -0,0 +1,24 @@ +package com.guwan.backend.service; + +import com.guwan.backend.dto.user.RegisterDTO; +import com.guwan.backend.dto.user.UserDTO; +import com.guwan.backend.vo.user.EmailRegisterRequest; +import com.guwan.backend.vo.user.LoginRequest; +import com.guwan.backend.vo.user.PhoneRegisterRequest; +import com.guwan.backend.vo.user.RegisterRequest; + +public interface UserService { + UserDTO register(RegisterDTO registerDTO); + UserDTO login(LoginRequest request); + UserDTO getCurrentUser(); + Long getCurrentUserId(); + UserDTO getUserById(Long id); + UserDTO updateUserInfo(UserDTO userDTO); + void changePassword(String oldPassword, String newPassword); + void resetPassword(String email); + public String refreshToken(String token); + UserDTO registerByEmail(EmailRegisterRequest request); + UserDTO registerByPhone(PhoneRegisterRequest request); + void sendEmailCode(String email); + void sendPhoneCode(String phone); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/VerificationService.java b/src/main/java/com/guwan/backend/service/VerificationService.java new file mode 100644 index 0000000..03b70e5 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/VerificationService.java @@ -0,0 +1,8 @@ +package com.guwan.backend.service; + +public interface VerificationService { + void sendEmailCode(String email); + void sendSmsCode(String phone); + boolean verifyEmailCode(String email, String code); + boolean verifySmsCode(String phone, String code); +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/AliyunSmsServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/AliyunSmsServiceImpl.java new file mode 100644 index 0000000..bf9fd2c --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/AliyunSmsServiceImpl.java @@ -0,0 +1,55 @@ +package com.guwan.backend.service.impl; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.teaopenapi.models.Config; +import com.guwan.backend.service.SmsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; + +@Slf4j +public class AliyunSmsServiceImpl implements SmsService { + + @Value("${aliyun.sms.access-key-id}") + private String accessKeyId; + + @Value("${aliyun.sms.access-key-secret}") + private String accessKeySecret; + + @Value("${aliyun.sms.sign-name}") + private String signName; + + @Value("${aliyun.sms.template-code}") + private String templateCode; + + private Client createClient() throws Exception { + Config config = new Config() + .setAccessKeyId(accessKeyId) + .setAccessKeySecret(accessKeySecret) + .setEndpoint("dysmsapi.aliyuncs.com"); + return new Client(config); + } + + @Override + public void sendVerificationCode(String phone, String code) { + try { + Client client = createClient(); + SendSmsRequest request = new SendSmsRequest() + .setPhoneNumbers(phone) + .setSignName(signName) + .setTemplateCode(templateCode) + .setTemplateParam("{\"code\":\"" + code + "\"}"); + + client.sendSms(request); + log.info("短信验证码发送成功:{} -> {}", phone, code); + } catch (Exception e) { + log.error("短信发送失败:" + e.getMessage(), e); + throw new RuntimeException("短信发送失败", e); + } + } + + @Override + public void sendNotification(String phone, String content) { + // 实现通知短信发送 + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/DummyFileServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/DummyFileServiceImpl.java new file mode 100644 index 0000000..26fcf40 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/DummyFileServiceImpl.java @@ -0,0 +1,18 @@ +package com.guwan.backend.service.impl; + +import com.guwan.backend.service.FileService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@Primary +public class DummyFileServiceImpl implements FileService { + + @Override + public String uploadBase64Image(String base64Image, String folder) { + log.info("模拟上传图片到文件夹: {}", folder); + return "dummy/image/path.jpg"; + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/DummySmsServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/DummySmsServiceImpl.java new file mode 100644 index 0000000..f6a4dec --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/DummySmsServiceImpl.java @@ -0,0 +1,22 @@ +package com.guwan.backend.service.impl; + +import com.guwan.backend.service.SmsService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@Primary +public class DummySmsServiceImpl implements SmsService { + + @Override + public void sendVerificationCode(String phone, String code) { + log.info("模拟发送短信验证码到 {}: {}", phone, code); + } + + @Override + public void sendNotification(String phone, String content) { + log.info("模拟发送短信通知到 {}: {}", phone, content); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/FileServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..4ca1e64 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/FileServiceImpl.java @@ -0,0 +1,46 @@ +package com.guwan.backend.service.impl; + +import com.guwan.backend.service.FileService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Base64; +import java.util.UUID; + +@Slf4j +//@Service // 注释掉这个注解 +public class FileServiceImpl implements FileService { + + @Value("${file.upload.path}") + private String uploadPath; + + @Override + public String uploadBase64Image(String base64Image, String folder) { + try { + // 解码Base64图片 + String[] parts = base64Image.split(","); + byte[] imageBytes = Base64.getDecoder().decode(parts[1]); + + // 生成文件名 + String fileName = UUID.randomUUID().toString() + ".jpg"; + String folderPath = uploadPath + "/" + folder; + String filePath = folderPath + "/" + fileName; + + // 创建目录 + new File(folderPath).mkdirs(); + + // 写入文件 + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(imageBytes); + } + + return folder + "/" + fileName; + } catch (Exception e) { + log.error("上传图片失败", e); + throw new RuntimeException("上传图片失败", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..f98dfee --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java @@ -0,0 +1,201 @@ +package com.guwan.backend.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.guwan.backend.dto.user.RegisterDTO; +import com.guwan.backend.dto.user.UserDTO; +import com.guwan.backend.entity.User; +import com.guwan.backend.mapper.UserMapper; +import com.guwan.backend.service.EmailService; +import com.guwan.backend.service.UserService; +import com.guwan.backend.service.VerificationService; +import com.guwan.backend.util.JwtUtil; +import com.guwan.backend.util.RedisUtil; +import com.guwan.backend.vo.user.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserMapper userMapper; + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + private final RedisUtil redisUtil; + private final VerificationService verificationService; + private final EmailService emailService; + + private static final String USER_CACHE_KEY = "user:"; + private static final long USER_CACHE_DURATION = 3600L; // 1小时 + + @Override + @Transactional + public UserDTO register(RegisterDTO request) { + // 检查用户名是否已存在 + if (findByUsername(request.getUsername()) != null) { + throw new IllegalArgumentException("用户名已存在"); + } + + // 检查邮箱是否已存在 + if (findByEmail(request.getEmail()) != null) { + throw new IllegalArgumentException("邮箱已被注册"); + } + + // 检查手机号是否已存在 + if (findByPhone(request.getPhone()) != null) { + throw new IllegalArgumentException("手机号已被注册"); + } + + + + User user = new User(); + + + + BeanUtils.copyProperties(request, user); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setCreatedTime(LocalDateTime.now()); + user.setStatus(1); + + userMapper.insert(user); + + return convertToDTO(user); + } + + @Override + public UserDTO login(LoginRequest request) { + User user = findByUsername(request.getUsername()); + + if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new IllegalArgumentException("用户名或密码错误"); + } + + if (user.getStatus() != 1) { + throw new IllegalArgumentException("账号已被禁用"); + } + + // 更新最后登录时间 + user.setLastLoginTime(LocalDateTime.now()); + userMapper.updateById(user); + + UserDTO userDTO = convertToDTO(user); + String token = jwtUtil.generateToken(user.getId()); + userDTO.setToken(token); + + // 缓存用户信息 + redisUtil.set(USER_CACHE_KEY + user.getId(), userDTO, USER_CACHE_DURATION, TimeUnit.SECONDS); + + return userDTO; + } + + @Override + public UserDTO getCurrentUser() { + Long userId = getCurrentUserId(); + if (userId == null) { + return null; + } + return getUserById(userId); + } + + @Override + public Long getCurrentUserId() { + // TODO: 从SecurityContext中获取当前用户ID + return null; + } + + @Override + public UserDTO getUserById(Long id) { + // 先从缓存获取 + Object cached = redisUtil.get(USER_CACHE_KEY + id); + if (cached != null) { + return (UserDTO) cached; + } + + User user = userMapper.selectById(id); + if (user == null) { + return null; + } + + UserDTO userDTO = convertToDTO(user); + redisUtil.set(USER_CACHE_KEY + id, userDTO, USER_CACHE_DURATION, TimeUnit.SECONDS); + return userDTO; + } + + @Override + public UserDTO updateUserInfo(UserDTO userDTO) { + return null; + } + + @Override + public void changePassword(String oldPassword, String newPassword) { + + } + + @Override + public void resetPassword(String email) { + + } + + @Override + public String refreshToken(String token) { + return null; + } + + @Override + public UserDTO registerByEmail(EmailRegisterRequest request) { + return null; + } + + @Override + public UserDTO registerByPhone(PhoneRegisterRequest request) { + return null; + } + + @Override + public void sendEmailCode(String email) { + + } + + @Override + public void sendPhoneCode(String phone) { + + } + + private User findByUsername(String username) { + return userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getUsername, username) + ); + } + + private User findByEmail(String email) { + return userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getEmail, email) + ); + } + + private User findByPhone(String phone) { + return userMapper.selectOne( + new LambdaQueryWrapper() + .eq(User::getPhone, phone) + ); + } + + private UserDTO convertToDTO(User user) { + if (user == null) { + return null; + } + UserDTO dto = new UserDTO(); + BeanUtils.copyProperties(user, dto); + return dto; + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/VerificationServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/VerificationServiceImpl.java new file mode 100644 index 0000000..9696408 --- /dev/null +++ b/src/main/java/com/guwan/backend/service/impl/VerificationServiceImpl.java @@ -0,0 +1,122 @@ +package com.guwan.backend.service.impl; + +import com.guwan.backend.service.SmsService; +import com.guwan.backend.service.VerificationService; +import com.guwan.backend.util.RedisUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@RequiredArgsConstructor +public class VerificationServiceImpl implements VerificationService { + + private final RedisUtil redisUtil; + private final JavaMailSender mailSender; + private final SmsService smsService; + + private static final String EMAIL_CODE_KEY = "verification:email:"; + private static final String SMS_CODE_KEY = "verification:sms:"; + private static final int CODE_EXPIRE_MINUTES = 5; + private static final int CODE_LENGTH = 6; + private static final String SMS_SEND_TIME_KEY = "sms:send:time:"; + private static final String SMS_COUNT_KEY = "sms:count:"; + private static final int MAX_DAILY_SMS_COUNT = 10; + + @Override + public void sendEmailCode(String email) { + String code = generateCode(); + // 存储验证码 + redisUtil.set(EMAIL_CODE_KEY + email, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES); + + // 发送邮件 + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(email); + message.setSubject("注册验证码"); + message.setText("您的验证码是:" + code + "," + CODE_EXPIRE_MINUTES + "分钟内有效。"); + mailSender.send(message); + + log.info("发送邮箱验证码:{} -> {}", email, code); + } + + @Override + public void sendSmsCode(String phone) { + // 检查发送频率限制 + checkSendFrequency(phone); + + String code = generateCode(); + // 存储验证码 + redisUtil.set(SMS_CODE_KEY + phone, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES); + + // 发送短信 + smsService.sendVerificationCode(phone, code); + + // 记录发送时间 + redisUtil.set(SMS_SEND_TIME_KEY + phone, System.currentTimeMillis(), 24, TimeUnit.HOURS); + + log.info("发送短信验证码:{} -> {}", phone, code); + } + + @Override + public boolean verifyEmailCode(String email, String code) { + String key = EMAIL_CODE_KEY + email; + String savedCode = (String) redisUtil.get(key); + if (savedCode != null && savedCode.equals(code)) { + redisUtil.delete(key); + return true; + } + return false; + } + + @Override + public boolean verifySmsCode(String phone, String code) { + String key = SMS_CODE_KEY + phone; + String savedCode = (String) redisUtil.get(key); + if (savedCode != null && savedCode.equals(code)) { + redisUtil.delete(key); + return true; + } + return false; + } + + private String generateCode() { + Random random = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < CODE_LENGTH; i++) { + code.append(random.nextInt(10)); + } + return code.toString(); + } + + private void checkSendFrequency(String phone) { + // 检查是否在一分钟内重复发送 + String sendTimeKey = SMS_SEND_TIME_KEY + phone; + Object lastSendTime = redisUtil.get(sendTimeKey); + if (lastSendTime != null) { + long timeDiff = System.currentTimeMillis() - (Long) lastSendTime; + if (timeDiff < 60000) { // 60秒内不能重复发送 + throw new IllegalArgumentException("发送太频繁,请稍后再试"); + } + } + + // 检查当天发送次数 + String countKey = SMS_COUNT_KEY + phone; + Integer count = (Integer) redisUtil.get(countKey); + if (count != null && count >= MAX_DAILY_SMS_COUNT) { + throw new IllegalArgumentException("今日发送次数已达上限"); + } + + // 增加发送次数 + if (count == null) { + redisUtil.set(countKey, 1, 24, TimeUnit.HOURS); + } else { + redisUtil.increment(countKey, 1); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/util/JwtUtil.java b/src/main/java/com/guwan/backend/util/JwtUtil.java new file mode 100644 index 0000000..c287cf7 --- /dev/null +++ b/src/main/java/com/guwan/backend/util/JwtUtil.java @@ -0,0 +1,62 @@ +package com.guwan.backend.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Slf4j +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(secret.getBytes()); + } + + public String generateToken(Long userId) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + expiration); + + return Jwts.builder() + .setSubject(userId.toString()) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(getSigningKey(), SignatureAlgorithm.HS512) + .compact(); + } + + public Long getUserIdFromToken(String token) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + + return Long.parseLong(claims.getSubject()); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token); + return true; + } catch (Exception e) { + log.error("JWT验证失败: {}", e.getMessage()); + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/util/RedisUtil.java b/src/main/java/com/guwan/backend/util/RedisUtil.java new file mode 100644 index 0000000..5d0adb5 --- /dev/null +++ b/src/main/java/com/guwan/backend/util/RedisUtil.java @@ -0,0 +1,120 @@ +package com.guwan.backend.util; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RedisUtil { + + private final RedisTemplate redisTemplate; + + /** + * 设置缓存 + */ + public void set(String key, Object value, long timeout, TimeUnit timeUnit) { + try { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } catch (Exception e) { + log.error("Redis set error: ", e); + } + } + + /** + * 设置缓存(默认过期时间) + */ + public void set(String key, Object value) { + set(key, value, 24, TimeUnit.HOURS); + } + + /** + * 获取缓存 + */ + public Object get(String key) { + try { + return redisTemplate.opsForValue().get(key); + } catch (Exception e) { + log.error("Redis get error: ", e); + return null; + } + } + + /** + * 删除缓存 + */ + public void delete(String key) { + try { + redisTemplate.delete(key); + } catch (Exception e) { + log.error("Redis delete error: ", e); + } + } + + /** + * 判断key是否存在 + */ + public boolean hasKey(String key) { + try { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } catch (Exception e) { + log.error("Redis hasKey error: ", e); + return false; + } + } + + /** + * 设置过期时间 + */ + public void expire(String key, long timeout, TimeUnit timeUnit) { + try { + redisTemplate.expire(key, timeout, timeUnit); + } catch (Exception e) { + log.error("Redis expire error: ", e); + } + } + + /** + * 自增操作 + */ + public Long increment(String key, long delta) { + try { + return redisTemplate.opsForValue().increment(key, delta); + } catch (Exception e) { + log.error("Redis increment error: ", e); + return null; + } + } + + /** + * Set集合操作 + */ + public void sAdd(String key, String... values) { + try { + redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + log.error("Redis sAdd error: ", e); + } + } + + public void setRemove(String key, Object... values) { + try { + redisTemplate.opsForSet().remove(key, values); + } catch (Exception e) { + log.error("Redis setRemove error: ", e); + } + } + + public boolean sHasKey(String key, Object value) { + try { + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value)); + } catch (Exception e) { + log.error("Redis sHasKey error: ", e); + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/util/RedisUtils.java b/src/main/java/com/guwan/backend/util/RedisUtils.java new file mode 100644 index 0000000..e295b56 --- /dev/null +++ b/src/main/java/com/guwan/backend/util/RedisUtils.java @@ -0,0 +1,562 @@ +package com.guwan.backend.util; + +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@Component +public class RedisUtils { + + @Resource + private RedisTemplate redisTemplate; + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); + } + } + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 递增 + * + * @param key 键 + * @param delta 要增加几(大于0) + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * 递减 + * + * @param key 键 + * @param delta 要减少几(小于0) + * @return + */ + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递减因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) + expire(key, time); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) + expire(key, time); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) + expire(key, time); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + Long remove = redisTemplate.opsForList().remove(key, count, value); + return remove; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/util/SmsUtils.java b/src/main/java/com/guwan/backend/util/SmsUtils.java new file mode 100644 index 0000000..75d5a12 --- /dev/null +++ b/src/main/java/com/guwan/backend/util/SmsUtils.java @@ -0,0 +1,69 @@ +// This file is auto-generated, don't edit it. Thanks. +package com.guwan.backend.util; + +import cn.hutool.core.util.RandomUtil; +import com.aliyun.tea.*; + +import java.util.Random; + +public class SmsUtils { + + +// public static void main(String[] args) throws Exception { +// sendMessage("17653478621"); +// } + + + /** + * description : + *

使用AK&SK初始化账号Client

+ * @return Client + * + * @throws Exception + */ + public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception { + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。 + com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + .setAccessKeyId("LTAI5tDGd4vzV2vuzpg45mFq") + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + .setAccessKeySecret("tUkbtPFvvFpa57IUfykoHKNwuIQEYY"); + // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi + config.endpoint = "dysmsapi.aliyuncs.com"; + return new com.aliyun.dysmsapi20170525.Client(config); + } + + public static void sendMessage(String phone, String random) throws Exception { + + com.aliyun.dysmsapi20170525.Client client = SmsUtils.createClient(); + com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest() + .setSignName("阿里云短信测试") + .setTemplateCode("SMS_154950909") + .setPhoneNumbers(phone) + .setTemplateParam("{\"code\":\"" + random + "\"}"); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + try { + // 复制代码运行请自行打印 API 的返回值 + client.sendSmsWithOptions(sendSmsRequest, runtime); + + + + } catch (TeaException error) { + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + System.out.println(error.getMessage()); + // 诊断地址 + System.out.println(error.getData().get("Recommend")); + com.aliyun.teautil.Common.assertAsString(error.message); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + System.out.println(error.getMessage()); + // 诊断地址 + System.out.println(error.getData().get("Recommend")); + com.aliyun.teautil.Common.assertAsString(error.message); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/vo/user/EmailRegisterRequest.java b/src/main/java/com/guwan/backend/vo/user/EmailRegisterRequest.java new file mode 100644 index 0000000..11d6798 --- /dev/null +++ b/src/main/java/com/guwan/backend/vo/user/EmailRegisterRequest.java @@ -0,0 +1,20 @@ +package com.guwan.backend.vo.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class EmailRegisterRequest { + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "验证码不能为空") + private String code; + + @NotBlank(message = "密码不能为空") + private String password; + + private String nickname; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/vo/user/FaceLoginRequest.java b/src/main/java/com/guwan/backend/vo/user/FaceLoginRequest.java new file mode 100644 index 0000000..ae67eab --- /dev/null +++ b/src/main/java/com/guwan/backend/vo/user/FaceLoginRequest.java @@ -0,0 +1,8 @@ +package com.guwan.backend.vo.user; + +import lombok.Data; + +@Data +public class FaceLoginRequest { + private String faceImage; // Base64编码的人脸图片 +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/vo/user/LoginRequest.java b/src/main/java/com/guwan/backend/vo/user/LoginRequest.java new file mode 100644 index 0000000..1a1c5e3 --- /dev/null +++ b/src/main/java/com/guwan/backend/vo/user/LoginRequest.java @@ -0,0 +1,13 @@ +package com.guwan.backend.vo.user; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequest { + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + private String password; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/vo/user/PhoneRegisterRequest.java b/src/main/java/com/guwan/backend/vo/user/PhoneRegisterRequest.java new file mode 100644 index 0000000..55aba61 --- /dev/null +++ b/src/main/java/com/guwan/backend/vo/user/PhoneRegisterRequest.java @@ -0,0 +1,20 @@ +package com.guwan.backend.vo.user; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +@Data +public class PhoneRegisterRequest { + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "验证码不能为空") + private String code; + + @NotBlank(message = "密码不能为空") + private String password; + + private String nickname; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/vo/user/RegisterRequest.java b/src/main/java/com/guwan/backend/vo/user/RegisterRequest.java new file mode 100644 index 0000000..4279ee0 --- /dev/null +++ b/src/main/java/com/guwan/backend/vo/user/RegisterRequest.java @@ -0,0 +1,31 @@ +package com.guwan.backend.vo.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +@Data +public class RegisterRequest { + @NotBlank(message = "用户名不能为空") + @Pattern(regexp = "^[a-zA-Z0-9_]{4,16}$", message = "用户名必须是4-16位字母、数字或下划线") + private String username; + + @NotBlank(message = "密码不能为空") + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", + message = "密码必须包含大小写字母和数字,且不少于8位") + private String password; + + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "邮箱验证码不能为空") + private String emailCode; + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phone; + + @NotBlank(message = "手机验证码不能为空") + private String phoneCode; + +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..915775b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,84 @@ +server: + port: 8084 + +spring: + application: + name: backend + + # 数据库配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: root + password: 123456 + + # Redis配置 + data: + redis: + host: localhost + port: 6380 + password: 123456 # 如果有密码,请设置 + database: 8 + timeout: 10000 + lettuce: + pool: + max-active: 8 + max-wait: -1 + max-idle: 8 + min-idle: 0 + + # 邮件配置 + mail: + host: smtp.163.com + protocol: smtp + default-encoding: UTF-8 + username: 17653478621@163.com + password: NMRzfpuL6kbL4euT + test-connection: true + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + + # SQL初始化配置 + sql: + init: + mode: never # 禁用SQL初始化 + + # 自动配置排除 + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration + +# MyBatis-Plus配置 +mybatis-plus: + global-config: + db-config: + id-type: auto + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +# JWT配置 +jwt: + secret: your-secret-key-must-be-at-least-32-bytes-long + expiration: 86400000 # 24小时 + +# 阿里云配置 +aliyun: + sms: + access-key-id: your-access-key-id + access-key-secret: your-access-key-secret + sign-name: your-sign-name + template-code: SMS_123456789 + +# 文件上传配置 +file: + upload: + path: D:/upload # Windows路径示例,根据实际情况修改 \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql new file mode 100644 index 0000000..e44c34b --- /dev/null +++ b/src/main/resources/db/data.sql @@ -0,0 +1,4 @@ +-- 插入测试用户 +INSERT INTO `user` (`username`, `password`, `nickname`, `status`) +VALUES ('admin', '$2a$10$X/hU.2sPXYHPWzGRQr5RYOgG9R5ym3OKH8L7E9NFd0uK3nXKbgYdC', '管理员', 1) +ON DUPLICATE KEY UPDATE `status` = 1; \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql new file mode 100644 index 0000000..7ad37eb --- /dev/null +++ b/src/main/resources/db/schema.sql @@ -0,0 +1,33 @@ +-- 用户表 +CREATE TABLE IF NOT EXISTS `user` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` varchar(50) NOT NULL COMMENT '用户名', + `password` varchar(100) NOT NULL COMMENT '密码', + `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(20) DEFAULT NULL COMMENT '手机号', + `gender` tinyint DEFAULT '0' COMMENT '性别: 0-未知, 1-男, 2-女', + `bio` varchar(255) DEFAULT NULL COMMENT '个人简介', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态: 0-禁用, 1-正常', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + UNIQUE KEY `uk_email` (`email`), + UNIQUE KEY `uk_phone` (`phone`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; + +-- 短信日志表 +CREATE TABLE IF NOT EXISTS `sms_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID', + `phone` varchar(20) NOT NULL COMMENT '手机号', + `type` varchar(20) NOT NULL COMMENT '短信类型', + `content` varchar(500) NOT NULL COMMENT '短信内容', + `status` tinyint NOT NULL COMMENT '发送状态:0-失败,1-成功', + `error_msg` varchar(255) DEFAULT NULL COMMENT '错误信息', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_phone` (`phone`), + KEY `idx_created_time` (`created_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='短信发送日志表'; \ No newline at end of file diff --git a/src/main/resources/templates/email_password_template.html b/src/main/resources/templates/email_password_template.html new file mode 100644 index 0000000..7126073 --- /dev/null +++ b/src/main/resources/templates/email_password_template.html @@ -0,0 +1,147 @@ + + + + + 验证码 + + + +
+
+ + + + + + + + +
+ +

+
+ + + + + + + + +
+

用户密码

+

+ + + 您好,账号是 + + + 的密码为: +

+
+
+

+
+
请勿将您的密码,泄露给其他人。
+
+
+ +
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/email_template.html b/src/main/resources/templates/email_template.html new file mode 100644 index 0000000..9ba68f6 --- /dev/null +++ b/src/main/resources/templates/email_template.html @@ -0,0 +1,158 @@ + + + + + 验证码 + + + +
+
+ + + + + + + + +
+ +

+
+ + + + + + + + +
+

验证码

+

+ 您的验证码于 + + 生成,验证码为: +

+
+ + + + + + + + +
+

+
+
验证码将在10分钟后失效,请及时使用。
+
+
+ +
+
+
+
+
+
+ + diff --git a/src/test/java/com/guwan/backend/BackendApplicationTests.java b/src/test/java/com/guwan/backend/BackendApplicationTests.java new file mode 100644 index 0000000..0b676c9 --- /dev/null +++ b/src/test/java/com/guwan/backend/BackendApplicationTests.java @@ -0,0 +1,13 @@ +package com.guwan.backend; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class BackendApplicationTests { + + @Test + void contextLoads() { + } + +}