第一次

This commit is contained in:
ovo 2024-12-06 20:36:28 +08:00
commit 4abcc03a55
52 changed files with 3217 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View File

@ -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/

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -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

192
logs/backend.log Normal file
View File

@ -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]

Binary file not shown.

155
pom.xml Normal file
View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.guwan</groupId>
<artifactId>backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend</name>
<description>backend</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<!-- 阿里云短信SMS -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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<String> handleException(Exception e) {
log.error("系统异常", e);
return Result.error(e.getMessage());
}
@ExceptionHandler(BindException.class)
public Result<String> handleBindException(BindException e) {
log.error("参数校验异常", e);
return Result.error(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
@ExceptionHandler(IllegalArgumentException.class)
public Result<String> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("参数异常", e);
return Result.error(e.getMessage());
}
}

View File

@ -0,0 +1,85 @@
package com.guwan.backend.common;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
private Long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
public static <T> Result<T> error(Integer code, String message, T data) {
Result<T> 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 <T> Result<T> validateFailed(String message) {
return error(VALIDATE_FAILED, message);
}
public static <T> Result<T> unauthorized(String message) {
return error(UNAUTHORIZED, message);
}
public static <T> Result<T> forbidden(String message) {
return error(FORBIDDEN, message);
}
public static <T> Result<T> notFound(String message) {
return error(NOT_FOUND, message);
}
}

View File

@ -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);
}
}

View File

@ -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 {
// 空配置类只是为了控制自动配置的导入
}

View File

@ -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());
}
}
}

View File

@ -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;
// }
}

View File

@ -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<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> 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<Object> 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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<UserDTO> 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<UserDTO> 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<UserDTO> getCurrentUser() {
UserDTO user = userService.getCurrentUser();
if (user == null) {
return Result.unauthorized("用户未登录");
}
return Result.success(user);
}
@GetMapping("/{id}")
public Result<UserDTO> getUserById(@PathVariable Long id) {
UserDTO user = userService.getUserById(id);
if (user == null) {
return Result.notFound("用户不存在");
}
return Result.success(user);
}
@PostMapping("/token/refresh")
public Result<String> 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<UserDTO> registerByEmail(@RequestBody @Valid EmailRegisterRequest request) {
log.info("邮箱注册: {}", request.getEmail());
return Result.success(userService.registerByEmail(request));
}
@PostMapping("/register/phone")
public Result<UserDTO> registerByPhone(@RequestBody @Valid PhoneRegisterRequest request) {
log.info("手机号注册: {}", request.getPhone());
return Result.success(userService.registerByPhone(request));
}
@PostMapping("/email/code")
public Result<Void> 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<Void> 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<Void> 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<Void> 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<UserDTO> updateUserInfo(@RequestBody @Valid UserDTO userDTO) {
try {
return Result.success(userService.updateUserInfo(userDTO));
} catch (Exception e) {
log.error("更新用户信息失败", e);
return Result.error(e.getMessage());
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String> tags;
@TableField(value = "image_urls", typeHandler = JacksonTypeHandler.class)
private List<String> 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<User> {
}

View File

@ -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<? extends GrantedAuthority> authorities,
boolean enabled) {
super(username, password, enabled, true, true,
true, authorities);
this.userId = userId;
}
}

View File

@ -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<String> 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");
}
}

View File

@ -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<User>()
.eq(User::getUsername, username)
);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
if (user.getStatus() != 1) {
throw new UsernameNotFoundException("账号已被禁用");
}
List<SimpleGrantedAuthority> 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<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new CustomUserDetails(
user.getId(),
user.getUsername(),
user.getPassword(),
authorities,
user.getStatus() == 1
);
}
}

View File

@ -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());
}
}
}

View File

@ -0,0 +1,5 @@
package com.guwan.backend.service;
public interface FileService {
String uploadBase64Image(String base64Image, String folder);
}

View File

@ -0,0 +1,13 @@
package com.guwan.backend.service;
public interface SmsService {
/**
* 发送验证码短信
*/
void sendVerificationCode(String phone, String code);
/**
* 发送通知短信
*/
void sendNotification(String phone, String content);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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) {
// 实现通知短信发送
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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<User>()
.eq(User::getUsername, username)
);
}
private User findByEmail(String email) {
return userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getEmail, email)
);
}
private User findByPhone(String phone) {
return userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getPhone, phone)
);
}
private UserDTO convertToDTO(User user) {
if (user == null) {
return null;
}
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(user, dto);
return dto;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<String, Object> 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;
}
}
}

View File

@ -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<String, Object> 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<String>) 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<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> 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<String, Object> 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<Object> 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<Object> 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<Object> 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<Object> 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;
}
}
}

View File

@ -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");
// }
/**
* <b>description</b> :
* <p>使用AK&amp;SK初始化账号Client</p>
* @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);
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
package com.guwan.backend.vo.user;
import lombok.Data;
@Data
public class FaceLoginRequest {
private String faceImage; // Base64编码的人脸图片
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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路径示例根据实际情况修改

View File

@ -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;

View File

@ -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='短信发送日志表';

View File

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>验证码</title>
<style>
body {
font-family: 'helvetica neue', PingFangSC-Light, arial, 'hiragino sans gb', 'microsoft yahei ui', 'microsoft yahei', simsun, sans-serif;
}
.container {
text-align: center;
min-width: 320px;
max-width: 660px;
border: 1px solid #f6f6f6;
background-color: #4C9BFF;
margin: auto;
padding: 20px 0 30px;
}
.logo {
width: 92px;
height: 25px;
}
.content {
border-top: #9ccbff 5px solid;
border-bottom: #0071ff 5px solid;
background-color: #fff;
padding: 23px 0 20px;
text-align: left;
position: relative;
}
.title {
font-weight: bold;
font-size: 20px;
line-height: 36px;
margin: 0 0 16px;
}
.message {
font-size: 14px;
color: rgb(51, 51, 51);
line-height: 24px;
margin: 6px 0;
word-break: break-all;
}
.password {
font-size: 24px;
text-align: center;
border: 2px solid #4C9BFF;
border-radius: 5px;
color: #4C9BFF;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 20px;
}
.info {
font-size: 14px;
color: rgb(51, 51, 51);
line-height: 18px;
margin: 0 0 8px;
padding: 0;
}
.footer {
font-size: 14px;
position: absolute;
right: 15px;
bottom: 0;
}
.spacer {
margin: 20px 0;
border-top: 1px solid rgb(234, 237, 240);
}
.table {
width: 100%;
margin-bottom: 10px;
border-collapse: collapse;
text-align: left;
}
.td-left, .td-right {
width: 3.2%;
max-width: 30px;
}
</style>
</head>
<body>
<div class="container">
<div>
<table class="table">
<tbody>
<tr>
<td class="td-left"></td>
<td>
<div class="logo">
<p style="font-size: 20px; color: white">Linyu</p>
</div>
<p class="divider"></p>
<div class="content">
<table class="table">
<tbody>
<tr>
<td class="td-left"></td>
<td>
<h1 class="title">用户密码</h1>
<p class="message">
<span style="border-bottom: 1px dashed #ccc" t="5"
times="11:11">
<strong th:text="${username}"></strong></span>
您好,账号是
<span style="border-bottom: 1px dashed #ccc" t="5"
times="11:11">
<strong th:text="${account}"></strong></span>
的密码为:
</p>
<div class="password" th:text="${password}">
</div>
<p class="spacer"></p>
<dl>
<dt class="info">请勿将您的密码,泄露给其他人。</dt>
</dl>
<dl>
<p class="footer"><strong>Linyu</strong></p>
</dl>
</td>
<td class="td-right"></td>
</tr>
</tbody>
</table>
</div>
</td>
<td class="td-left"></td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>验证码</title>
<style>
body {
font-family: 'helvetica neue', PingFangSC-Light, arial, 'hiragino sans gb', 'microsoft yahei ui', 'microsoft yahei', simsun, sans-serif;
}
.container {
text-align: center;
min-width: 320px;
max-width: 660px;
border: 1px solid #f6f6f6;
background-color: #4C9BFF;
margin: auto;
padding: 20px 0 30px;
}
.logo {
width: 92px;
height: 25px;
}
.content {
border-top: #9ccbff 5px solid;
border-bottom: #0071ff 5px solid;
background-color: #fff;
padding: 23px 0 20px;
text-align: left;
position: relative;
}
.title {
font-weight: bold;
font-size: 20px;
line-height: 36px;
margin: 0 0 16px;
}
.message {
font-size: 14px;
color: rgb(51, 51, 51);
line-height: 24px;
margin: 6px 0;
word-break: break-all;
}
.verification-code {
display: flex;
gap: 10px;
margin-top: 20px;
}
.digit {
width: 40px;
height: 50px;
font-size: 24px;
text-align: center;
border: 2px solid #4C9BFF;
border-radius: 5px;
color: #4C9BFF;
display: inline-flex;
align-items: center;
justify-content: center;
}
.info {
font-size: 14px;
color: rgb(51, 51, 51);
line-height: 18px;
margin: 0 0 8px;
padding: 0;
}
.footer {
font-size: 14px;
position: absolute;
right: 15px;
bottom: 0;
}
.spacer {
margin: 20px 0;
border-top: 1px solid rgb(234, 237, 240);
}
.table {
width: 100%;
margin-bottom: 10px;
border-collapse: collapse;
text-align: left;
}
.td-left, .td-right {
width: 3.2%;
max-width: 30px;
}
</style>
</head>
<body>
<div class="container">
<div>
<table class="table">
<tbody>
<tr>
<td class="td-left"></td>
<td>
<div class="logo">
<p style="font-size: 20px; color: white">Guwan</p>
</div>
<p class="divider"></p>
<div class="content">
<table class="table">
<tbody>
<tr>
<td class="td-left"></td>
<td>
<h1 class="title">验证码</h1>
<p class="message">
您的验证码于 <span style="border-bottom: 1px dashed #ccc" t="5"
times="11:11">
<strong th:text="${nowDate}"></strong></span>
生成,验证码为:
</p>
<div class="verification-code">
<!-- <span class="digit">1</span>-->
<!-- <span class="digit">2</span>-->
<!-- <span class="digit">3</span>-->
<!-- <span class="digit">4</span>-->
<!-- <span class="digit">5</span>-->
<!-- <span class="digit">6</span>-->
<span th:each="digit : ${code}" th:text="${digit}" class="digit"></span>
</div>
<p class="spacer"></p>
<dl>
<dt class="info">验证码将在10分钟后失效请及时使用。</dt>
</dl>
<dl>
<p class="footer"><strong>Guwan</strong></p>
</dl>
</td>
<td class="td-right"></td>
</tr>
</tbody>
</table>
</div>
</td>
<td class="td-left"></td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@ -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() {
}
}