第一次
This commit is contained in:
commit
4abcc03a55
|
@ -0,0 +1,2 @@
|
||||||
|
/mvnw text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
|
@ -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/
|
|
@ -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
|
|
@ -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.
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
// 空配置类,只是为了控制自动配置的导入
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
// }
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.guwan.backend.service;
|
||||||
|
|
||||||
|
public interface FileService {
|
||||||
|
String uploadBase64Image(String base64Image, String folder);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.guwan.backend.service;
|
||||||
|
|
||||||
|
public interface SmsService {
|
||||||
|
/**
|
||||||
|
* 发送验证码短信
|
||||||
|
*/
|
||||||
|
void sendVerificationCode(String phone, String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送通知短信
|
||||||
|
*/
|
||||||
|
void sendNotification(String phone, String content);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
// 实现通知短信发送
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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&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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.guwan.backend.vo.user;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FaceLoginRequest {
|
||||||
|
private String faceImage; // Base64编码的人脸图片
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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路径示例,根据实际情况修改
|
|
@ -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;
|
|
@ -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='短信发送日志表';
|
|
@ -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>
|
|
@ -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>
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue