Compare commits

...
19 Commits
Author SHA1 Message Date
wonder dde912a392 📝Docs: 增加图片 2025-10-08 00:45:15 +08:00
wonder d875e62ee6 📝Docs: Final 2025-10-08 00:43:15 +08:00
wonder 4d4663e405 🐛Fix: 修复前后端数据格式不一致问题 2025-10-08 00:40:21 +08:00
wonder ab5d895ca1 🐛Fix: 修复请求路径错误 2025-10-08 00:30:40 +08:00
wonder 5a3df648a2 🔄Update: 配置了跨域 2025-10-08 00:21:48 +08:00
wonder 5d6023a628 🔧Chore: 删除冗余导入 2025-10-08 00:18:26 +08:00
wonder 0ac267316b 🔄Update: 完成邮件发送接口 2025-10-08 00:17:22 +08:00
wonder c72a374f21 🔄Update: 删除附件功能 2025-10-08 00:17:05 +08:00
wonder 594eb23979 ✨Feat: 增加发送邮件功能 2025-10-07 22:50:59 +08:00
wonder 12b21004b3 🔧Chore: 修正任务 2025-10-07 22:17:56 +08:00
wonder 84ba8ceb06 📝Docs: 完成 T03/集成 Axios 实现 AJAX 提交邮件数据 2025-10-07 22:16:44 +08:00
wonder 30ac68ad5c 📝Docs: 完成 T02/开发邮件发送页面 2025-10-07 22:16:10 +08:00
wonder 3f9b966880 🐛Fix: 修复 Element-plus 未挂载问题 2025-10-07 22:04:33 +08:00
wonder 6d323146ff ✨Feat: 增加前端页面核心内容 2025-10-07 21:59:55 +08:00
wonder e5f0cb1870 📝Docs: 完成 T01/搭建 Vue 3 前端项目 2025-10-07 21:47:40 +08:00
wonder 68ea448688 🔄Update: 添加依赖 element-plus 和 axios 2025-10-07 21:45:06 +08:00
wonder 8e71ee682c 🔄Update: 构建前端项目框架 2025-10-07 21:40:00 +08:00
wonder 58f0db1b1d 📝Docs: 撰写任务清单 2025-10-07 21:35:26 +08:00
wonder 4dc2760f8a 📝Docs: 撰写 PRD 文档 2025-10-07 21:29:43 +08:00
26 changed files with 5177 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
.idea
+141 -1
View File
@@ -1,2 +1,142 @@
# send-mail # Mini Email Sender
一个简易的邮件发送应用,用户可以通过前端界面填写邮件信息并发送邮件。
![alt text](image.png)
## 项目介绍
Mini Email Sender 是一个前后端分离的邮件发送应用,采用现代化的技术栈构建。用户可以通过直观的前端界面快速发送邮件,后端负责处理邮件发送逻辑。
## 技术栈
### 后端 (Spring Boot)
- **框架**: Spring Boot 3.x
- **语言**: Java 21
- **邮件发送**: Spring Mail + JavaMail
- **构建工具**: Maven
### 前端 (Vue.js)
- **框架**: Vue 3 + Vite
- **UI组件库**: Element Plus
- **HTTP客户端**: Axios
- **样式**: Scss + Custom CSS
## 项目结构
```
.
├── backend/ # 后端代码
│ ├── src/main/java/cn/hezhaohui/mail/
│ │ ├── controller/ # 控制器层
│ │ ├── service/ # 服务层
│ │ ├── config/ # 配置类
│ │ └── entity/ # 实体类
│ └── pom.xml # Maven配置文件
├── frontend/ # 前端代码
│ ├── src/
│ │ ├── components/ # 组件目录
│ │ │ └── EmailSender.vue # 邮件发送组件
│ │ ├── App.vue # 根组件
│ │ └── main.js # 应用入口
│ └── package.json # 前端依赖配置
└── README.md # 项目说明文档
```
## 功能特性
### 前端功能
- 邮件发送表单(收件人、标题、内容)
- 表单验证(邮箱格式、必填项)
- 响应式UI界面
- 发送状态提示
- 重置表单功能
### 后端功能
- RESTful API接口
- 邮件发送服务
- 基本的错误处理
- 日志记录
## 快速开始
### 环境要求
- Java 21
- Node.js 16+
- Maven 3.6+
### 后端启动
1. 进入后端目录:
```bash
cd backend
```
2. 编译项目:
```bash
mvn clean compile
```
3. 运行应用:
```bash
mvn spring-boot:run
```
默认端口:`8080`
### 前端启动
1. 进入前端目录:
```bash
cd frontend
```
2. 安装依赖:
```bash
npm install
```
3. 启动开发服务器:
```bash
npm run dev
```
默认端口:`3000`
## API接口
### 发送邮件
- **URL**: `/api/email/send`
- **方法**: `POST`
- **参数**:
- `to` (string, 必填): 收件人邮箱地址
- `subject` (string, 必填): 邮件标题
- `content` (string, 必填): 邮件内容
- **响应**:
```json
{
"status": 200,
"message": "success"
}
```
## 配置说明
### 后端邮件配置
需要在 `application.properties` 或 `application.yml` 中配置SMTP服务器信息:
```yaml
spring:
mail:
host: smtp.example.com
port: 587
username: your-email@example.com
password: your-password
properties:
mail:
smtp:
auth: true
starttls:
enable: true
```
+38
View File
@@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+45
View File
@@ -0,0 +1,45 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.hezhaohui</groupId>
<artifactId>backend</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version>
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,12 @@
package cn.hezhaohui.mail;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SendMailApplication {
public static void main(String[] args) {
SpringApplication.run(SendMailApplication.class, args);
}
}
@@ -0,0 +1,31 @@
package cn.hezhaohui.mail.config;
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 {
@Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.163.com");
mailSender.setPort(465);
mailSender.setUsername("hezhaohui0807@163.com");
mailSender.setPassword("AMaauzQZQMnkSfZi");
Properties properties = mailSender.getJavaMailProperties();
properties.put("mail.transport.protocol", "smtp");
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.ssl.enable", "true");
properties.put("mail.smtp.starttls.enable", "true");
properties.put("mail.debug", "true");
return mailSender;
}
}
@@ -0,0 +1,19 @@
package cn.hezhaohui.mail.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) {
registry.addMapping("/**") // 应用到所有路径
.allowedOrigins("http://localhost:5173") // 允许前端开发服务器的源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
.allowedHeaders("*") // 允许所有header
.allowCredentials(true) // 允许发送凭证信息(如cookies)
.maxAge(3600); // 预检请求的有效期,单位为秒
}
}
@@ -0,0 +1,23 @@
package cn.hezhaohui.mail.controller;
import cn.hezhaohui.mail.entity.Response;
import cn.hezhaohui.mail.service.MailService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/email")
@Slf4j
public class MailController {
@Resource
private MailService mailService;
@PostMapping("/send")
public Response sendMail(@RequestParam String to, @RequestParam String subject, @RequestParam String content) {
mailService.sendMessage(to, subject, content);
log.info("[To]: {}\n[Subject]: {}\n[Content]: {}\n", to, subject, content);
return new Response(200, "success");
}
}
@@ -0,0 +1,10 @@
package cn.hezhaohui.mail.entity;
import lombok.Data;
@Data
public class Response {
final private int status;
final private String message;
}
@@ -0,0 +1,22 @@
package cn.hezhaohui.mail.service;
import jakarta.annotation.Resource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class MailService {
@Resource
private JavaMailSender emailSender;
public void sendMessage(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("hezhaohui0807@163.com");
message.setTo(to);
message.setSubject(subject);
message.setText(text);
emailSender.send(message);
}
}
@@ -0,0 +1,17 @@
package cn.hezhaohui.mail.service;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MailServiceTest {
@Resource
private MailService mailService;
@Test
void sendMessage() {
mailService.sendMessage("2311719626@qq.com", "你好", "Test");
}
}
+8
View File
@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
}
+24
View File
@@ -0,0 +1,24 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
export default defineConfig([
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
{
languageOptions: {
globals: {
...globals.browser,
},
},
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
])
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
+4494
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "frontend",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix"
},
"dependencies": {
"axios": "^1.12.2",
"element-plus": "^2.11.4",
"vue": "^3.5.22"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@vitejs/plugin-vue": "^6.0.1",
"eslint": "^9.33.0",
"eslint-plugin-vue": "~10.4.0",
"globals": "^16.3.0",
"vite": "^7.1.7",
"vite-plugin-vue-devtools": "^8.0.2"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+9
View File
@@ -0,0 +1,9 @@
<script setup>
import EmailSender from './components/EmailSender.vue'
</script>
<template>
<EmailSender />
</template>
<style scoped></style>
+171
View File
@@ -0,0 +1,171 @@
<template>
<div class="email-sender">
<h2>Mini Email Sender</h2>
<el-form
:model="emailForm"
:rules="rules"
ref="formRef"
label-width="100px"
@submit.prevent="submitForm"
>
<!-- 收件人 -->
<el-form-item label="收件人" prop="to">
<el-input
v-model="emailForm.to"
placeholder="请输入收件人邮箱地址"
clearable
/>
</el-form-item>
<!-- 邮件标题 -->
<el-form-item label="标题" prop="subject">
<el-input
v-model="emailForm.subject"
placeholder="请输入邮件标题"
clearable
/>
</el-form-item>
<!-- 邮件内容 -->
<el-form-item label="内容" prop="content">
<el-input
v-model="emailForm.content"
type="textarea"
placeholder="请输入邮件内容"
:rows="6"
/>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item>
<el-button
type="primary"
@click="submitForm"
:loading="submitting"
:disabled="submitting"
>
{{ submitting ? '发送中...' : '发送邮件' }}
</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
<!-- 状态提示 -->
<el-alert
v-if="message"
:title="message"
:type="messageType"
show-icon
closable
style="margin-top: 20px;"
/>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElForm, ElFormItem, ElInput, ElButton, ElAlert } from 'element-plus'
import axios from 'axios'
// 表单引用
const formRef = ref()
// 表单数据
const emailForm = reactive({
to: '',
subject: '',
content: ''
})
// 提交状态
const submitting = ref(false)
// 消息提示
const message = ref('')
const messageType = ref('success')
// 表单验证规则
const rules = {
to: [
{ required: true, message: '请输入收件人邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
subject: [
{ required: true, message: '请输入邮件标题', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入邮件内容', trigger: 'blur' }
]
}
// 重置表单
const resetForm = () => {
formRef.value.resetFields()
message.value = ''
}
// 提交表单
const submitForm = async () => {
// 验证表单
if (!formRef.value) return
try {
await formRef.value.validate()
// 构建发送数据
const formData = new FormData()
formData.append('to', emailForm.to)
formData.append('subject', emailForm.subject)
formData.append('content', emailForm.content)
submitting.value = true
message.value = ''
// 发送请求
const response = await axios.post('http://localhost:8080/api/email/send', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
// 处理响应
if (response.data.status === 200) {
messageType.value = 'success'
message.value = '邮件已成功发送'
resetForm()
} else {
messageType.value = 'error'
message.value = response.data.message || '邮件发送失败'
}
} catch (error) {
console.error('发送邮件错误:', error)
messageType.value = 'error'
message.value = error.response?.data?.message || '邮件发送失败,请检查网络连接'
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.email-sender {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.email-sender h2 {
text-align: center;
margin-bottom: 30px;
}
.email-sender .el-form-item {
margin-bottom: 20px;
}
.email-sender .el-alert {
margin-top: 20px;
}
</style>
+6
View File
@@ -0,0 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(ElementPlus).mount('#app')
+18
View File
@@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB