This commit is contained in:
commit
dfccd8cafa
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="mail-service" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="mail-service" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="corretto-1.8" project-jdk-type="JavaSDK" />
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,16 @@
|
|||
2025-08-16 11:15:26.668 [main] INFO com.example.mailservice.MailServiceApplication - Starting MailServiceApplication using Java 1.8.0_452 on kusoul with PID 23664 (F:\work\company-backend\target\classes started by Administrator in F:\work\company-backend)
|
||||
2025-08-16 11:15:26.669 [main] DEBUG com.example.mailservice.MailServiceApplication - Running with Spring Boot v2.7.5, Spring v5.3.23
|
||||
2025-08-16 11:15:26.669 [main] INFO com.example.mailservice.MailServiceApplication - No active profile set, falling back to 1 default profile: "default"
|
||||
2025-08-16 11:15:26.678 [background-preinit] INFO org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 6.2.5.Final
|
||||
2025-08-16 11:15:27.743 [main] INFO o.s.d.r.config.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
2025-08-16 11:15:27.745 [main] INFO o.s.d.r.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
|
||||
2025-08-16 11:15:27.770 [main] INFO o.s.d.r.config.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
|
||||
2025-08-16 11:15:28.374 [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8180 (http)
|
||||
2025-08-16 11:15:28.387 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8180"]
|
||||
2025-08-16 11:15:28.387 [main] INFO org.apache.catalina.core.StandardService - Starting service [Tomcat]
|
||||
2025-08-16 11:15:28.388 [main] INFO org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.68]
|
||||
2025-08-16 11:15:28.580 [main] INFO o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
|
||||
2025-08-16 11:15:28.580 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1852 ms
|
||||
2025-08-16 11:15:29.703 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8180"]
|
||||
2025-08-16 11:15:29.731 [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8180 (http) with context path ''
|
||||
2025-08-16 11:15:29.740 [main] INFO com.example.mailservice.MailServiceApplication - Started MailServiceApplication in 3.738 seconds (JVM running for 7.169)
|
|
@ -0,0 +1,119 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Spring" name="Spring">
|
||||
<configuration />
|
||||
</facet>
|
||||
<facet type="web" name="Web">
|
||||
<configuration>
|
||||
<webroots />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.11" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.11" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.17.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.17.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.36" level="project" />
|
||||
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.30" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.13.4.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.13.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.13.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.68" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.68" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-mail:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.sun.mail:jakarta.mail:1.6.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.sun.activation:jakarta.activation:1.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-redis:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-keyvalue:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-oxm:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.36" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.lettuce:lettuce-core:6.1.10.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-transport-native-unix-common:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.84.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.projectreactor:reactor-core:3.4.24" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.24" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.postgresql:postgresql:42.3.7" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.checkerframework:checker-qual:3.5.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:4.0.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.mybatis:mybatis:3.5.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:2.0.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-validation:2.7.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.68" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.2.5.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.7.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.7.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.7.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.7.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.4.8" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:2.4.8" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:9.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: jakarta.activation:jakarta.activation-api:1.2.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.22.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest:2.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter:5.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-params:5.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.8.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:4.5.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.12.18" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.12.18" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:3.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-junit-jupiter:4.5.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.3.23" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.3.23" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.3.23" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.9.0" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,100 @@
|
|||
<?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>2.7.5</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>mail-service</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>mail-service</name>
|
||||
<description>Spring Boot Mail Service with QQ Mail and Redis Limit</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starter Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Starter Mail -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Starter Data Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok (for reducing boilerplate code) -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- PostgreSQL -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- MyBatis -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--<!– HikariCP 连接池 –>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.zaxxer</groupId>-->
|
||||
<!--<artifactId>HikariCP</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Spring Boot Starter Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</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,19 @@
|
|||
package com.example.mailservice;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 邮件服务应用主类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
// 扫描所有Mapper接口所在的包
|
||||
@MapperScan("com.example.mailservice.mapper")
|
||||
public class MailServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MailServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.example.mailservice.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 日志配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class LogConfig {
|
||||
|
||||
/**
|
||||
* 邮件服务专用日志
|
||||
*/
|
||||
@Bean
|
||||
public Logger mailServiceLogger() {
|
||||
return LoggerFactory.getLogger("MailServiceLogger");
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制服务专用日志
|
||||
*/
|
||||
@Bean
|
||||
public Logger limitServiceLogger() {
|
||||
return LoggerFactory.getLogger("LimitServiceLogger");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.example.mailservice.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@Configuration
|
||||
public class MailConfig {
|
||||
|
||||
@Value("${spring.mail.host}")
|
||||
private String host;
|
||||
|
||||
@Value("${spring.mail.port}")
|
||||
private int port;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${spring.mail.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${spring.mail.protocol:smtp}")
|
||||
private String protocol;
|
||||
|
||||
@Value("${spring.mail.properties.mail.smtp.auth:true}")
|
||||
private boolean auth;
|
||||
|
||||
@Value("${spring.mail.properties.mail.smtp.starttls.enable:true}")
|
||||
private boolean starttlsEnable;
|
||||
|
||||
@Value("${spring.mail.properties.mail.smtp.starttls.required:true}")
|
||||
private boolean starttlsRequired;
|
||||
|
||||
@Value("${spring.mail.properties.mail.debug:false}")
|
||||
private boolean debug;
|
||||
|
||||
/**
|
||||
* 注册JavaMailSender bean
|
||||
*/
|
||||
@Bean
|
||||
public JavaMailSender mailSender() {
|
||||
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
|
||||
mailSender.setHost(host);
|
||||
mailSender.setPort(port);
|
||||
mailSender.setUsername(username);
|
||||
mailSender.setPassword(password);
|
||||
mailSender.setProtocol(protocol);
|
||||
|
||||
// 配置邮件发送属性
|
||||
Properties props = mailSender.getJavaMailProperties();
|
||||
props.put("mail.smtp.auth", auth);
|
||||
props.put("mail.smtp.starttls.enable", starttlsEnable);
|
||||
props.put("mail.smtp.starttls.required", starttlsRequired);
|
||||
props.put("mail.debug", debug);
|
||||
// 设置超时时间,避免长时间阻塞
|
||||
props.put("mail.smtp.connectiontimeout", 5000);
|
||||
props.put("mail.smtp.timeout", 5000);
|
||||
props.put("mail.smtp.writetimeout", 5000);
|
||||
|
||||
return mailSender;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.example.mailservice.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
/**
|
||||
* 配置RedisTemplate
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 使用StringRedisSerializer来序列化和反序列化redis的key值
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
// 使用GenericJackson2JsonRedisSerializer来序列化和反序列化redis的value值
|
||||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.example.mailservice.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
// 1. 配置允许跨域的路径:/** 表示所有接口(可改为特定路径,如 /api/**)
|
||||
registry.addMapping("/**")
|
||||
// 2. 允许的来源:* 表示所有前端域名
|
||||
.allowedOrigins("*")
|
||||
// 3. 允许的请求方法:所有 HTTP 方法(GET/POST/PUT/DELETE 等)
|
||||
.allowedMethods("*")
|
||||
// 4. 允许的请求头:* 表示所有请求头(包括自定义头)
|
||||
.allowedHeaders("*")
|
||||
// 5. 允许携带凭证(可选,默认 false;若前端需传 Cookie/Token,需设为 true,但此时 allowedOrigins 不能为 *,下文有说明)
|
||||
.allowCredentials(false)
|
||||
// 6. 预检请求(OPTIONS)的缓存时间:3600 秒(1 小时,避免频繁发送预检请求)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.example.mailservice.controller;
|
||||
|
||||
import com.example.mailservice.dto.ApiResponse;
|
||||
import com.example.mailservice.dto.MailRequest;
|
||||
import com.example.mailservice.service.MailLimitService;
|
||||
import com.example.mailservice.service.MailService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 邮件发送控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/mail")
|
||||
public class MailController {
|
||||
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
@Autowired
|
||||
private MailLimitService mailLimitService;
|
||||
|
||||
/**
|
||||
* 发送邮件接口
|
||||
*/
|
||||
@PostMapping("/send")
|
||||
public ApiResponse sendMail(@Validated @RequestBody MailRequest mailRequest, HttpServletRequest request) {
|
||||
// 获取客户端IP地址
|
||||
String ipAddress = getClientIpAddress(request);
|
||||
|
||||
// 检查是否达到发送限制
|
||||
if (!mailLimitService.canSendMail(ipAddress)) {
|
||||
return ApiResponse.fail(403, "您的IP今日发送邮件已达上限,请明天再试");
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
boolean sendSuccess = mailService.sendSimpleMail(
|
||||
mailRequest.getName(),
|
||||
mailRequest.getEmail(),
|
||||
mailRequest.getSubject(),
|
||||
mailRequest.getMessage()
|
||||
);
|
||||
|
||||
if (sendSuccess) {
|
||||
// 增加发送计数
|
||||
long count = mailLimitService.incrementSendCount(ipAddress);
|
||||
int remaining = mailLimitService.getRemainingCount(ipAddress);
|
||||
return ApiResponse.success("邮件发送成功,今日剩余发送次数: " + remaining);
|
||||
} else {
|
||||
return ApiResponse.fail(500, "邮件发送失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*/
|
||||
private String getClientIpAddress(HttpServletRequest request) {
|
||||
String xForwardedForHeader = request.getHeader("X-Forwarded-For");
|
||||
if (xForwardedForHeader != null) {
|
||||
// X-Forwarded-For: client, proxy1, proxy2
|
||||
return xForwardedForHeader.split(",")[0].trim();
|
||||
}
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.example.mailservice.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* API响应DTO
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse {
|
||||
|
||||
/**
|
||||
* 响应状态码:200表示成功,其他表示失败
|
||||
*/
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static ApiResponse success(Object data) {
|
||||
return new ApiResponse(200, "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
public static ApiResponse fail(int code, String message) {
|
||||
return new ApiResponse(code, message, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.example.mailservice.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 邮件请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class MailRequest {
|
||||
|
||||
/**
|
||||
* 发件人邮箱
|
||||
*/
|
||||
@NotBlank(message = "发件人邮箱不能为空")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
|
||||
/**
|
||||
* 发件人
|
||||
*/
|
||||
@NotBlank(message = "发件人不能为空")
|
||||
private String name;
|
||||
|
||||
|
||||
/**
|
||||
* 邮件主题
|
||||
*/
|
||||
@NotBlank(message = "邮件主题不能为空")
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 邮件内容
|
||||
*/
|
||||
@NotBlank(message = "邮件内容不能为空")
|
||||
private String message;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.example.mailservice.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 邮件发送记录实体类
|
||||
*/
|
||||
@Data
|
||||
public class MailSenderRecord {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 发送者邮箱
|
||||
*/
|
||||
private String senderEmail;
|
||||
|
||||
/**
|
||||
* 发送者IP地址
|
||||
*/
|
||||
private String senderIp;
|
||||
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
private LocalDateTime sendTime;
|
||||
|
||||
/**
|
||||
* 接收者邮箱
|
||||
*/
|
||||
private String recipientEmail;
|
||||
|
||||
/**
|
||||
* 邮件主题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 发送状态:1-成功,0-失败
|
||||
*/
|
||||
private Integer sendStatus;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.example.mailservice.exception;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @auther kuku
|
||||
* @description 自定义API异常
|
||||
* @date 2024/1/17
|
||||
* @github https://github.com/macrozheng
|
||||
*/
|
||||
public class EmailErrException extends RuntimeException {
|
||||
private IErrorCode errorCode;
|
||||
|
||||
public EmailErrException(IErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public EmailErrException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EmailErrException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public EmailErrException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public interface IErrorCode {
|
||||
long getCode();
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.example.mailservice.exception;
|
||||
|
||||
import com.example.mailservice.dto.ApiResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理参数验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
|
||||
for (FieldError fieldError : bindingResult.getFieldErrors()) {
|
||||
errorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(";");
|
||||
}
|
||||
|
||||
logger.warn("参数验证失败: {}", errorMsg.toString());
|
||||
return ApiResponse.fail(400, errorMsg.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他所有异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ApiResponse handleException(Exception e) {
|
||||
logger.error("系统异常: {}", e.getMessage(), e);
|
||||
return ApiResponse.fail(500, "系统异常,请联系管理员");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.example.mailservice.mapper;
|
||||
|
||||
import com.example.mailservice.entity.MailSenderRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 邮件发送记录Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface MailSenderRecordMapper {
|
||||
|
||||
/**
|
||||
* 插入邮件发送记录
|
||||
* @param record 邮件发送记录
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insert(MailSenderRecord record);
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.example.mailservice.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 邮件发送限制服务,用于控制每个IP的每日发送次数
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MailLimitService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private Logger limitServiceLogger;
|
||||
|
||||
/**
|
||||
* Redis中存储IP发送计数的键前缀
|
||||
*/
|
||||
@Value("${mail.redis-key-prefix}")
|
||||
private String redisKeyPrefix;
|
||||
|
||||
/**
|
||||
* Redis中IP计数的过期时间(单位:秒)
|
||||
*/
|
||||
@Value("${mail.redis-expire-seconds}")
|
||||
private long redisExpireSeconds;
|
||||
|
||||
/**
|
||||
* 每日每IP最大发送邮件数量限制
|
||||
*/
|
||||
@Value("${mail.daily-limit}")
|
||||
private int dailyLimit;
|
||||
|
||||
/**
|
||||
* 检查IP是否可以发送邮件
|
||||
*
|
||||
* @param ipAddress 客户端IP地址
|
||||
* @return 可以发送返回true,否则返回false
|
||||
*/
|
||||
public boolean canSendMail(String ipAddress) {
|
||||
String key = redisKeyPrefix + ipAddress;
|
||||
|
||||
// 获取当前IP的发送次数
|
||||
Object countObj = redisTemplate.opsForValue().get(key);
|
||||
Integer count = countObj != null ? Integer.parseInt(countObj.toString()) : 0;
|
||||
|
||||
// 检查是否超过限制
|
||||
if (count >= dailyLimit) {
|
||||
limitServiceLogger.warn("IP: {} 已达到每日发送上限: {}封", ipAddress, dailyLimit);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加IP的发送计数
|
||||
*
|
||||
* @param ipAddress 客户端IP地址
|
||||
* @return 增加后的计数
|
||||
*/
|
||||
public long incrementSendCount(String ipAddress) {
|
||||
String key = redisKeyPrefix + ipAddress;
|
||||
|
||||
// 自增计数,如果是新key则设置过期时间
|
||||
Long count = redisTemplate.opsForValue().increment(key);
|
||||
if (count != null && count == 1) {
|
||||
redisTemplate.expire(key, redisExpireSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
limitServiceLogger.info("IP: {} 发送计数增加到: {}次", ipAddress, count);
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取IP的剩余发送次数
|
||||
*
|
||||
* @param ipAddress 客户端IP地址
|
||||
* @return 剩余发送次数
|
||||
*/
|
||||
public int getRemainingCount(String ipAddress) {
|
||||
String key = redisKeyPrefix + ipAddress;
|
||||
|
||||
Object countObj = redisTemplate.opsForValue().get(key);
|
||||
Integer count = countObj != null ? Integer.parseInt(countObj.toString()) : 0;
|
||||
|
||||
return dailyLimit - count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.example.mailservice.service;
|
||||
|
||||
import com.example.mailservice.entity.MailSenderRecord;
|
||||
import com.example.mailservice.mapper.MailSenderRecordMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 邮件发送记录服务类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MailSenderRecordService {
|
||||
|
||||
private final MailSenderRecordMapper mailSenderRecordMapper;
|
||||
|
||||
/**
|
||||
* 保存邮件发送记录
|
||||
* @param senderEmail 发送者邮箱
|
||||
* @param senderIp 发送者IP
|
||||
* @param recipientEmail 接收者邮箱
|
||||
* @param subject 邮件主题
|
||||
* @param sendStatus 发送状态:1-成功,0-失败
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveMailRecord(String senderEmail, String senderIp, String recipientEmail, String subject, Integer sendStatus) {
|
||||
try {
|
||||
MailSenderRecord record = new MailSenderRecord();
|
||||
record.setSenderEmail(senderEmail);
|
||||
record.setSenderIp(senderIp);
|
||||
record.setRecipientEmail(recipientEmail);
|
||||
record.setSubject(subject);
|
||||
record.setSendStatus(sendStatus);
|
||||
record.setSendTime(LocalDateTime.now());
|
||||
record.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
int rows = mailSenderRecordMapper.insert(record);
|
||||
if (rows > 0) {
|
||||
log.debug("邮件发送记录保存成功,ID: {}", record.getId());
|
||||
} else {
|
||||
log.warn("邮件发送记录保存失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存邮件发送记录异常", e);
|
||||
// 记录保存失败不影响主流程,仅记录日志
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.example.mailservice.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 邮件发送服务
|
||||
*/
|
||||
@Service
|
||||
public class MailService {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
@Autowired
|
||||
private Logger mailServiceLogger;
|
||||
|
||||
/**
|
||||
* 发送者邮箱地址指定是用来发送邮件的服务器地址
|
||||
*/
|
||||
@Value("${spring.mail.username}")
|
||||
private String from;
|
||||
|
||||
|
||||
/**
|
||||
* 接收者邮箱地址 值得是 官网固定用来接收的地址
|
||||
*/
|
||||
@Value("${spring.mail.receive-mail}")
|
||||
private String receiveMail;
|
||||
|
||||
|
||||
/**
|
||||
* 发送简单文本邮件
|
||||
*
|
||||
* @param to 收件人邮箱
|
||||
* @param subject 邮件主题
|
||||
* @param content 邮件内容
|
||||
* @return 发送成功返回true,否则返回false
|
||||
*/
|
||||
public boolean sendSimpleMail(String userName, String to, String subject, String content) {
|
||||
try {
|
||||
// 创建简单邮件消息
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
// 设置发送者
|
||||
message.setFrom(from);
|
||||
// 设置收件人
|
||||
message.setTo(receiveMail);
|
||||
// 设置邮件主题
|
||||
message.setSubject(subject);
|
||||
String contents = String.format("客户姓名: %s 客户邮箱: %s \r\n %s", userName, to, content);
|
||||
// 设置邮件内容
|
||||
message.setText(contents);
|
||||
|
||||
// 发送邮件
|
||||
mailSender.send(message);
|
||||
|
||||
mailServiceLogger.info("邮件发送成功,收件人: {}, 主题: {}", to, subject);
|
||||
return true;
|
||||
} catch (MailException e) {
|
||||
mailServiceLogger.error("邮件发送失败,收件人: {}, 主题: {}, 错误信息: {}",
|
||||
to, subject, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package com.example.mailservice.service.impl;
|
||||
|
||||
import com.example.mailservice.dto.MailRequest;
|
||||
import com.example.mailservice.exception.EmailErrException;
|
||||
import com.example.mailservice.service.MailLimitService;
|
||||
import com.example.mailservice.service.MailSenderRecordService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSendException;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 邮件服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MailServiceImpl {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
private final MailLimitService mailLimitService;
|
||||
private final MailSenderRecordService mailSenderRecordService;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String fromEmail;
|
||||
|
||||
@Value("${mail.daily-limit}")
|
||||
private int dailyMaxCount = 10;
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
* @param request 邮件请求参数
|
||||
* @param clientIp 客户端IP地址
|
||||
*/
|
||||
public void sendMail(MailRequest request, String clientIp) throws MailException {
|
||||
// 检查发送限制
|
||||
boolean canSend = mailLimitService.canSendMail(clientIp);
|
||||
if (!canSend) {
|
||||
// log.warn("IP: {} 达到每日发送上限: {}", clientIp, dailyMaxCount);
|
||||
throw new EmailErrException( "前IP今日邮件发送已达上限(10封),请明天再试"
|
||||
// String.format("当前IP今日邮件发送已达上限(%d封),请明天再试", dailyMaxCount)
|
||||
);
|
||||
}
|
||||
|
||||
// 构建邮件消息
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(fromEmail);
|
||||
message.setTo(request.getEmail());
|
||||
message.setSubject(request.getSubject());
|
||||
message.setText(request.getMessage());
|
||||
|
||||
boolean sendSuccess = false;
|
||||
|
||||
try {
|
||||
// 发送邮件
|
||||
mailSender.send(message);
|
||||
sendSuccess = true;
|
||||
log.info("邮件发送成功,从 {} 到 {},主题: {}", fromEmail, request.getEmail(), request.getSubject());
|
||||
} catch (MailException e) {
|
||||
log.error("邮件发送失败,从 {} 到 {},错误: {}", fromEmail, request.getEmail(), e.getMessage(), e);
|
||||
throw new MailSendException("邮件发送失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// 保存发送记录,无论成功失败都记录
|
||||
mailSenderRecordService.saveMailRecord(
|
||||
fromEmail,
|
||||
clientIp,
|
||||
request.getEmail(),
|
||||
request.getSubject(),
|
||||
sendSuccess ? 1 : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
# 服务器端口配置
|
||||
server:
|
||||
port: 8180
|
||||
|
||||
spring:
|
||||
# 数据源配置
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/maildb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
|
||||
username: postgres
|
||||
password: 123456
|
||||
# Hikari连接池配置
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
idle-timeout: 300000
|
||||
connection-timeout: 20000
|
||||
max-lifetime: 1800000
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
# Redis配置
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
timeout: 2000
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
# 邮件配置
|
||||
mail:
|
||||
host: smtp.qq.com
|
||||
port: 587
|
||||
username: 2187434671@qq.com
|
||||
password: rvujtnksccaudjjf
|
||||
default-encoding: UTF-8
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
debug: false
|
||||
receive-mail: 2187434671@qq.com
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.example.mailservice.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
name: logs/mail-service.log
|
||||
level:
|
||||
root: INFO
|
||||
com.example.mailservice: DEBUG
|
||||
org.springframework.web: INFO
|
||||
org.springframework.mail: INFO
|
||||
org.mybatis: INFO
|
||||
com.zaxxer.hikari: INFO
|
||||
org.postgresql: INFO
|
||||
redis.clients: INFO
|
||||
|
||||
# 自定义邮件发送配置
|
||||
mail:
|
||||
# 每日每IP最大发送邮件数量限制
|
||||
daily-limit: 10
|
||||
# Redis中存储IP发送计数的键前缀
|
||||
redis-key-prefix: "mail:send:count:"
|
||||
# Redis中IP计数的过期时间(单位:秒),设置为24小时
|
||||
redis-expire-seconds: 86400
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<!-- 日志上下文名称 -->
|
||||
<contextName>mail-service</contextName>
|
||||
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
|
||||
|
||||
<!-- 日志文件路径 -->
|
||||
<property name="LOG_PATH" value="logs" />
|
||||
<property name="LOG_FILE_NAME" value="mail-service" />
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 滚动文件输出 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 日志文件路径 -->
|
||||
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
|
||||
|
||||
<!-- 滚动策略 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 归档文件命名格式 -->
|
||||
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 保留天数 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!-- 总大小限制 -->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
|
||||
<!-- 输出格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 错误日志输出 -->
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 只处理ERROR级别日志 -->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
|
||||
<!-- 错误日志文件路径 -->
|
||||
<file>${LOG_PATH}/${LOG_FILE_NAME}-error.log</file>
|
||||
|
||||
<!-- 滚动策略 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-error-%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
<!-- 输出格式 -->
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 邮件发送相关日志 -->
|
||||
<logger name="com.example.mailservice.service.MailService" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
</logger>
|
||||
|
||||
<!-- 数据库操作相关日志 -->
|
||||
<logger name="com.example.mailservice.mapper" level="INFO" additivity="false">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
</logger>
|
||||
|
||||
<!-- 根日志配置 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.example.mailservice.mapper.MailSenderRecordMapper">
|
||||
|
||||
<!-- 插入邮件发送记录 -->
|
||||
<insert id="insert" parameterType="com.example.mailservice.entity.MailSenderRecord" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO mail_sender_record (
|
||||
sender_email,
|
||||
sender_ip,
|
||||
send_time,
|
||||
recipient_email,
|
||||
subject,
|
||||
send_status,
|
||||
created_at
|
||||
) VALUES (
|
||||
#{senderEmail,jdbcType=VARCHAR},
|
||||
#{senderIp,jdbcType=VARCHAR},
|
||||
#{sendTime,jdbcType=TIMESTAMP},
|
||||
#{recipientEmail,jdbcType=VARCHAR},
|
||||
#{subject,jdbcType=VARCHAR},
|
||||
#{sendStatus,jdbcType=INTEGER},
|
||||
#{createdAt,jdbcType=TIMESTAMP}
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
||||
|
Loading…
Reference in New Issue