mailu branch failed

This commit is contained in:
shenjianZ 2025-07-28 12:18:54 +08:00
commit 2999e562c7
19 changed files with 1178 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/backend/node_modules
/frontend/node_modules
/backend/package-lock.json
/frontend/package-lock.json

7
GEMINI.md Normal file
View File

@ -0,0 +1,7 @@
1.在终端执行命令时使用适合Windows 的命令
2.始终使用中文回答
3.对于项目代码的中打印语句代码println语句、console语句等等和注释需要保留不要随意改动他们
4.对于前端的npm等命令你输出到屏幕即可给出命令即可我会在其他终端自己执行

95
README.md Normal file
View File

@ -0,0 +1,95 @@
# Email Unlimit Project (临时邮件项目)
## 最终架构 (单一 Docker Compose + 宿主机 Nginx)
本项目采用生产环境推荐的架构:所有服务(包括您的应用和完整的 Mailu 邮件套件)都由一个 `docker-compose.full.yml` 文件统一管理,而入口的反向代理则由 **宿主机上的 Nginx** 负责。
- **宿主机 Nginx:** 作为项目的唯一公共入口 (`main.shenjianl.cn`)。它负责:
1. 处理所有外部流量和 SSL 加密(推荐)。
2. 将对域名根路径 (`/`) 的访问反向代理到 **前端 Docker 容器** (`localhost:5181`)。
3. 将对 `/api` 路径的访问反向代理到 **后端 Docker 容器** (`localhost:5182`)。
4. 将对 `/mailu` 路径的访问反向代理到 **Mailu Admin UI** (`localhost:80`)。
- **Docker 容器 (由 `docker-compose.full.yml` 管理):**
- **您的应用:** `frontend`, `backend`, `mysql`
- **Mailu 套件:** `front` (Mailu Nginx), `admin`, `smtp`, `imap`, `redis` 等全套服务。
---
## 如何运行
### 步骤 1: 准备工作
1. **安装 Docker**: 确保您的系统中已安装 Docker 和 Docker Compose。
2. **配置 Mailu**: 打开 `mailu.env` 文件,**务必修改** `SECRET_KEY``ADMIN_PASSWORD` 为安全的值。域名和数据库配置已预填。
3. **配置 DNS**: 根据 `info.md` 中的 DNS 记录示例,在您的域名提供商处完成 SPF、DKIM 和 DMARC 的配置。
### 步骤 2: 配置宿主机 Nginx
将下面的配置块添加到您宿主机的 Nginx 中。这份配置统一处理了对您的应用和 Mailu 管理后台的访问。
```nginx
# /etc/nginx/conf.d/main.shenjianl.cn.conf
server {
listen 80;
server_name main.shenjianl.cn;
# --- 推荐配置 SSL ---
# listen 443 ssl http2;
# server_name main.shenjianl.cn;
# ssl_certificate /path/to/your/fullchain.pem;
# ssl_certificate_key /path/to/your/privkey.pem;
# --------------------
# 反向代理到前端 Vue.js 容器
location / {
proxy_pass http://localhost:5181;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 反向代理到后端 API 容器
location /api {
proxy_pass http://localhost:5182;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 反向代理到 Mailu Admin UI 和 Webmail
# 注意Mailu 自己的 Nginx (front) 在 80 端口上运行
location /mailu {
proxy_pass http://localhost:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
**配置完成后,请重载或重启您的 Nginx 服务。**
### 步骤 3: 启动 Docker 服务
使用我们最终生成的 `docker-compose.full.yml` 文件来启动所有服务。
```bash
# 在项目根目录执行
docker-compose -f docker-compose.full.yml up -d --build
```
---
## 访问<E8AEBF><E997AE><EFBFBD>
- **主应用入口**: `http://main.shenjianl.cn`
- **Mailu 管理后台**: `http://main.shenjianl.cn/mailu`
- **Mailu Webmail**: `http://main.shenjianl.cn/mailu/webmail`
docker-compose up -d --build

12
backend/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5182
CMD [ "node", "app.js" ]

32
backend/app.js Normal file
View File

@ -0,0 +1,32 @@
const express = require('express');
const cors = require('cors');
const db = require('./db');
const app = express();
const port = 5182;
app.use(cors());
app.use(express.json());
// API to get messages for a recipient
app.get('/api/messages', async (req, res) => {
const { recipient } = req.query;
if (!recipient) {
return res.status(400).send('Recipient is required');
}
try {
const [rows] = await db.execute(
'SELECT id, sender, subject, body, received_at FROM emails WHERE recipient = ? ORDER BY received_at DESC',
[recipient]
);
res.json(rows);
} catch (error) {
console.error('Failed to fetch emails:', error);
res.status(500).send('Failed to fetch emails');
}
});
app.listen(port, () => {
console.log(`Backend server listening at http://localhost:${port}`);
});

13
backend/db.js Normal file
View File

@ -0,0 +1,13 @@
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = pool.promise();

18
backend/init.sql Normal file
View File

@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS emails (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
recipient VARCHAR(255) NOT NULL,
sender VARCHAR(255) NOT NULL,
subject VARCHAR(512),
body TEXT,
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
raw MEDIUMBLOB
);
CREATE TABLE IF NOT EXISTS email_attachments (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email_id BIGINT NOT NULL,
filename VARCHAR(255),
content_type VARCHAR(128),
content LONGBLOB,
FOREIGN KEY (email_id) REFERENCES emails(id) ON DELETE CASCADE
);

16
backend/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "backend",
"version": "1.0.0",
"description": "Backend for email service",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^5.1.0",
"mysql2": "^3.14.2",
"mailparser": "^3.7.4",
"cors": "^2.8.5",
"get-stream": "^9.0.1"
}
}

54
backend/saveEmail.js Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env node
const { simpleParser } = require('mailparser');
const db = require('./db');
// Using require for get-stream v5 as it's CommonJS
const getStream = require('get-stream');
async function saveEmail(rawEmail) {
try {
const parsed = await simpleParser(rawEmail);
// Ensure 'to' and 'from' objects exist before accessing 'text'
const recipient = parsed.to ? parsed.to.text : 'undisclosed-recipients';
const sender = parsed.from ? parsed.from.text : 'unknown-sender';
const subject = parsed.subject;
const body = parsed.text || (parsed.html || '');
const [result] = await db.execute(
'INSERT INTO emails (recipient, sender, subject, body, raw) VALUES (?, ?, ?, ?, ?)',
[recipient, sender, subject, body, rawEmail]
);
console.log(`Email saved with ID: ${result.insertId}`);
if (parsed.attachments && parsed.attachments.length > 0) {
for (const attachment of parsed.attachments) {
await db.execute(
'INSERT INTO email_attachments (email_id, filename, content_type, content) VALUES (?, ?, ?, ?)',
[result.insertId, attachment.filename, attachment.contentType, attachment.content]
);
console.log(`Attachment ${attachment.filename} saved.`);
}
}
} catch (error) {
console.error('Failed to save email:', error);
// Exit with an error code to signal failure to the calling process (e.g., Mailu)
process.exit(1);
}
}
(async () => {
try {
const rawEmail = await getStream(process.stdin);
if (rawEmail && rawEmail.length > 0) {
await saveEmail(rawEmail);
} else {
console.log('Received empty input, no email to save.');
}
} catch (error) {
console.error('Error reading from stdin:', error);
process.exit(1);
}
})();

118
docker-compose.full.yml Normal file
View File

@ -0,0 +1,118 @@
# docker-compose.full.yml
# 最终合并版本:包含您的应用 (frontend, backend) 和完整的 Mailu 服务套件。
version: '3.3'
services:
# -----------------------------------------
# 您的应用服务
# -----------------------------------------
backend:
build: ./backend
container_name: mail_backend
restart: always
environment:
DB_HOST: "43.143.145.172"
DB_USER: "root"
DB_PASSWORD: "kyff145972"
DB_NAME: "maildb"
ports:
- "5182:5182"
frontend:
build: ./frontend
container_name: mail_frontend
restart: always
ports:
- "5181:8080"
depends_on:
- backend
# -----------------------------------------
# Mailu 官方服务套件
# -----------------------------------------
redis:
image: redis:alpine
restart: always
volumes:
- "mailu_redis:/data"
front:
image: ghcr.io/mailu/nginx:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_certs:/certs"
- "mailu_overrides_nginx:/overrides"
resolver:
image: ghcr.io/mailu/unbound:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
dns:
- 8.8.8.8
- 1.1.1.1
# --- 用于调试的临时修改 ---
entrypoint: /bin/sh
command: -c "sleep 3600"
admin:
image: ghcr.io/mailu/admin:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_data:/data"
- "mailu_dkim:/dkim"
depends_on:
- redis
imap:
image: ghcr.io/mailu/dovecot:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_mail:/mail"
- "mailu_overrides_dovecot:/overrides"
depends_on:
- front
smtp:
image: ghcr.io/mailu/postfix:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_overrides_postfix:/overrides"
depends_on:
- front
- resolver
antispam:
image: ghcr.io/mailu/rspamd:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_filter:/var/lib/rspamd"
- "mailu_overrides_rspamd:/etc/rspamd/override.d"
depends_on:
- front
volumes:
mailu_data:
mailu_certs:
mailu_dkim:
mailu_filter:
mailu_mail:
mailu_redis:
mailu_overrides_nginx:
mailu_overrides_dovecot:
mailu_overrides_postfix:
mailu_overrides_rspamd:

115
docker-compose.yml Normal file
View File

@ -0,0 +1,115 @@
# docker-compose.full.yml
# 最终合并版本:包含您的应用 (frontend, backend) 和完整的 Mailu 服务套件。
version: '3.3'
services:
# -----------------------------------------
# 您的应用服务
# -----------------------------------------
backend:
build: ./backend
container_name: mail_backend
restart: always
environment:
DB_HOST: "43.143.145.172"
DB_USER: "root"
DB_PASSWORD: "kyff145972"
DB_NAME: "maildb"
ports:
- "5182:5182"
frontend:
build: ./frontend
container_name: mail_frontend
restart: always
ports:
- "5181:8080"
depends_on:
- backend
# -----------------------------------------
# Mailu 官方服务套件
# -----------------------------------------
redis:
image: redis:alpine
restart: always
volumes:
- "mailu_redis:/data"
front:
image: ghcr.io/mailu/nginx:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_certs:/certs"
- "mailu_overrides_nginx:/overrides"
resolver:
image: ghcr.io/mailu/unbound:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
dns:
- 8.8.8.8
- 1.1.1.1
admin:
image: ghcr.io/mailu/admin:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_data:/data"
- "mailu_dkim:/dkim"
depends_on:
- redis
imap:
image: ghcr.io/mailu/dovecot:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_mail:/mail"
- "mailu_overrides_dovecot:/overrides"
depends_on:
- front
smtp:
image: ghcr.io/mailu/postfix:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_overrides_postfix:/overrides"
depends_on:
- front
- resolver
antispam:
image: ghcr.io/mailu/rspamd:2.0
restart: always
env_file: mailu.env
hostname: mail.shenjianl.cn
volumes:
- "mailu_filter:/var/lib/rspamd"
- "mailu_overrides_rspamd:/etc/rspamd/override.d"
depends_on:
- front
volumes:
mailu_data:
mailu_certs:
mailu_dkim:
mailu_filter:
mailu_mail:
mailu_redis:
mailu_overrides_nginx:
mailu_overrides_dovecot:
mailu_overrides_postfix:
mailu_overrides_rspamd:

19
frontend/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy all other files
COPY . .
# Expose port
EXPOSE 8080
# Run the app
CMD ["npm", "run", "serve"]

16
frontend/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"vue": "^3.5.18",
"axios": "^1.11.0"
},
"devDependencies": {
"@vue/cli-service": "~5.0.8"
}
}

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>临时邮件</title>
</head>
<body>
<noscript>
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

257
frontend/src/App.vue Normal file
View File

@ -0,0 +1,257 @@
<template>
<div id="app-container">
<h1>临时邮箱</h1>
<div class="email-generator">
<input v-model="randomEmail" type="text" readonly @click="copyToClipboard" title="点击复制"/>
<button @click="generateRandomEmail">生成新地址</button>
</div>
<div class="inbox">
<h2>收件箱 ({{ currentEmail }})</h2>
<button @click="fetchMessages" :disabled="loading">
{{ loading ? '刷新中...' : '刷新邮件' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<ul v-if="messages.length > 0" class="message-list">
<li v-for="msg in messages" :key="msg.id" @click="selectMessage(msg)" :class="{ selected: selectedMessage && selectedMessage.id === msg.id }">
<div class="sender"><strong>发件人:</strong> {{ msg.sender }}</div>
<div class="subject"><strong>主题:</strong> {{ msg.subject }}</div>
<div class="time">{{ new Date(msg.received_at).toLocaleString() }}</div>
</li>
</ul>
<div v-else class="no-messages">
<p>{{ loading ? '正在加载...' : '收件箱是空的。' }}</p>
</div>
</div>
<div v-if="selectedMessage" class="message-view">
<h3>{{ selectedMessage.subject }}</h3>
<p><strong>发件人:</strong> {{ selectedMessage.sender }}</p>
<p><strong>收件人:</strong> {{ currentEmail }}</p>
<p><strong>时间:</strong> {{ new Date(selectedMessage.received_at).toLocaleString() }}</p>
<hr>
<div v-html="selectedMessage.body" class="email-body"></div>
</div>
</div>
</template>
<script>
import axios from 'axios';
const API_URL = '/api';
export default {
name: 'App',
data() {
return {
randomEmail: '',
currentEmail: '',
messages: [],
selectedMessage: null,
loading: false,
error: null,
domain: 'shenjianl.cn', // info.md
refreshInterval: null
};
},
methods: {
generateRandomString(length) {
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
},
generateRandomEmail() {
const name = this.generateRandomString(8);
this.randomEmail = `${name}@${this.domain}`;
this.currentEmail = this.randomEmail;
this.messages = [];
this.selectedMessage = null;
this.fetchMessages();
},
async fetchMessages() {
if (!this.currentEmail) {
return;
}
this.loading = true;
this.error = null;
try {
const response = await axios.get(`${API_URL}/messages`, {
params: { recipient: this.currentEmail }
});
this.messages = response.data;
//
if (this.selectedMessage) {
const updatedSelected = this.messages.find(m => m.id === this.selectedMessage.id);
if (updatedSelected) {
this.selectedMessage = updatedSelected;
} else {
this.selectedMessage = null; //
}
}
} catch (err) {
this.error = '无法加载邮件。请检查后端服务是否运行。';
console.error(err);
} finally {
this.loading = false;
}
},
selectMessage(message) {
this.selectedMessage = message;
},
copyToClipboard() {
navigator.clipboard.writeText(this.randomEmail).then(() => {
alert('邮箱地址已复制到剪贴板');
}, (err) => {
console.error('Could not copy text: ', err);
});
},
startAutoRefresh() {
this.refreshInterval = setInterval(this.fetchMessages, 15000); // 15
},
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
},
created() {
this.generateRandomEmail();
this.startAutoRefresh();
},
beforeUnmount() {
this.stopAutoRefresh();
}
};
</script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f4f7f9;
color: #333;
margin: 0;
padding: 20px;
}
#app-container {
max-width: 900px;
margin: 0 auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
h1, h2, h3 {
color: #2c3e50;
}
.email-generator {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.email-generator input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
background: #f9f9f9;
cursor: pointer;
}
button {
padding: 10px 15px;
border: none;
background-color: #42b983;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #36a374;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.inbox {
margin-bottom: 20px;
}
.message-list {
list-style: none;
padding: 0;
border: 1px solid #eee;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
}
.message-list li {
padding: 15px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s;
}
.message-list li:last-child {
border-bottom: none;
}
.message-list li:hover {
background-color: #f9f9f9;
}
.message-list li.selected {
background-color: #e8f5e9;
}
.sender, .subject, .time {
margin-bottom: 5px;
}
.time {
font-size: 0.8em;
color: #777;
}
.no-messages {
text-align: center;
padding: 20px;
color: #777;
}
.message-view {
border: 1px solid #eee;
padding: 20px;
border-radius: 4px;
background: #fdfdfd;
}
.email-body {
margin-top: 15px;
white-space: pre-wrap;
word-wrap: break-word;
background-color: #fff;
padding: 1em;
border-radius: 4px;
border: 1px solid #ddd;
overflow: auto;
}
.error {
color: #e74c3c;
margin: 10px 0;
}
</style>

4
frontend/src/main.js Normal file
View File

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

6
frontend/vue.config.js Normal file
View File

@ -0,0 +1,6 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
// devServer 代理配置已移除,所有代理均由外部 Nginx 处理。
})

349
info.md Normal file
View File

@ -0,0 +1,349 @@
前端:**Vue**
后端:**Node.js + Express**
数据库:**MySQL**
邮件服务:**Mailu**
我帮你重新整理并生成**完整且清晰**的架构图、流程图、技术栈表 + 项目结构:
---
## 🧩 **一、架构图新版Vue + Express**
```
┌────────────────────────────┐
│ 用户 │
│ ┌──────────────────────┐ │
│ │ mail.shenjianl.cn 前端网站 │ ←─────┐
│ │ (Vue) │ │
│ └──────────────────────┘ │
└────────────┬─────────────┘
│ HTTP (REST API)
┌────────────────────────────┐
│ Node.js 后端 (Express) │
│ - 生成随机邮箱地址 │
│ - 查询收件箱 │
│ - 邮件接收解析写数据库 │
└────────────┬─────────────┘
│ MySQL
┌────────────────────────────┐
│ MySQL │
│ - emails 表 │
│ - email_attachments 表 │
└────────────┬─────────────┘
┌────────────────────────────┐
│ 邮件服务器Mailu
│ - SMTP 接收外部邮件 │
│ - Catch-all 所有收件人 │
│ - 调用 Node.js 脚本入库 │
└────────────┬─────────────┘
┌────────────────────────────┐
│ DNS │
│ - MX: mail.shenjianl.cn │
│ - A: mail.shenjianl.cn → IP │
└────────────────────────────┘
```
---
## 📝 **二、流程图:接收 & 查看流程**
```
【外部发件人】
发邮件到 abc123@shenjianl.cn
DNS MX 查找 mail.shenjianl.cn
Mailu 收到邮件 (SMTP)
调用 Node.js 脚本 (saveEmail.js)
simpleParser 解析邮件内容
写入 MySQL:
recipient, sender, subject, body, received_at
用户访问网页:
mail.shenjianl.cn
Vue 前端请求后端 API:
GET /api/messages?recipient=abc123@shenjianl.cn
后端查询 MySQL 返回邮件列表
前端展示收件箱
```
---
## ⚙ **三、技术栈清单(新版)**
| 层级 | 功能 | 技术选型 |
| -------- | -------------- | ----------------------------------- |
| 域名 & DNS | MX/A 解析 | 阿里云 DNS / Cloudflare |
| 邮件服务器 | 接收所有外部邮件 | Mailu基于 Postfix/Dovecot |
| 邮件解析 | 提取收件人/正文/主题/附件 | Node.js (mailparser) |
| 后端 API | 生成邮箱 / 查询收件箱 | Node.js + Express |
| 数据库 | 存储邮件信息 | MySQL |
| 前端 | 用户界面 / 展示收件箱 | Vue.js |
| 部署 & 管理 | 容器化 / 部署 | Docker + docker-compose |
| 安全 | HTTPS / 防滥用 | Let's Encrypt + Nginx 反代 + IP限流/黑名单 |
---
## 🛠 **四、项目结构建议(新版)**
```
📦 project-root
├── frontend/ # Vue 前端
│ └── (组件/页面/接口)
├── backend/ # Node.js 后端
│ ├── app.js # Express 主程序
│ ├── saveEmail.js # 接收邮件脚本:解析并写入数据库
│ └── db.js # 数据库连接
├── docker-compose.yml # Mailu + Node.js + MySQL
├── mailu.env # Mailu 配置
├── README.md
```
---
✅ **总结:**
* 前端Vue 只负责界面和展示
* 后端Express 提供 API + 接收邮件写 MySQL
* 邮件服务器Mailu catch-all 接收所有地址
* DNS配置 MX / A 指向服务器
---
好的!
下面我给你**直接写好**适配新版架构的:
`docker-compose.yml`包含Vue 前端 + Node.js Express 后端 + MySQL + Mailu 邮件服务器)
✅ MySQL 建表 SQL
---
## ✅ **1⃣ docker-compose.yml**
> ⚠️ 注:
* Vue 前端用 `npm run serve``npm run build` 后放 Nginx 也行,这里为了简单直接跑开发服务
* Mailu 是官方 Docker 邮件套件,需要 `mailu.env` 配置
* 建议生产用 HTTPS + Nginx下面只是开发演示架构
```yaml
version: '3.8'
services:
mysql:
image: mysql:8
container_name: mail_mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: myrootpassword
MYSQL_DATABASE: maildb
MYSQL_USER: mailuser
MYSQL_PASSWORD: mailpassword
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
container_name: mail_backend
restart: always
environment:
DB_HOST: mysql
DB_USER: mailuser
DB_PASSWORD: mailpassword
DB_NAME: maildb
ports:
- "3000:3000"
depends_on:
- mysql
frontend:
build: ./frontend
container_name: mail_frontend
restart: always
ports:
- "8080:8080"
depends_on:
- backend
mailu:
image: mailu/mailu:master
container_name: mail_server
restart: always
env_file: mailu.env
ports:
- "25:25" # SMTP
- "465:465" # SMTPS
- "143:143" # IMAP
- "993:993" # IMAPS
- "8081:80" # Mailu admin web UI可选
volumes:
- mailu_data:/data
depends_on:
- mysql
volumes:
mysql_data:
mailu_data:
```
---
## 📦 **2⃣ 建表 SQL**
```sql
CREATE TABLE emails (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
recipient VARCHAR(255) NOT NULL,
sender VARCHAR(255) NOT NULL,
subject VARCHAR(512),
body TEXT,
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
raw MEDIUMBLOB
);
CREATE TABLE email_attachments (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email_id BIGINT NOT NULL,
filename VARCHAR(255),
content_type VARCHAR(128),
content LONGBLOB,
FOREIGN KEY (email_id) REFERENCES emails(id) ON DELETE CASCADE
);
```
---
## 📦 **3⃣ mailu.env**(示例配置)
```env
DOMAIN=shenjianl.cn
HOSTNAMES=mail.shenjianl.cn
SECRET_KEY=change-this-to-a-very-random-secret
ADMIN=admin
ADMIN_PASSWORD=adminpassword
DB_FLAVOR=mysql
DB_HOST=mysql
DB_USER=mailuser
DB_PASSWORD=mailpassword
DB_NAME=maildb
```
---
## ✅ **4⃣ 项目结构(推荐)**
```
📦 project-root
├── frontend/ # Vue 前端
│ └── (页面/组件)
├── backend/ # Node.js Express 后端
│ ├── app.js # 提供 API
│ ├── saveEmail.js # 接收邮件脚本
│ └── db.js # 数据库连接
├── docker-compose.yml
├── mailu.env
└── README.md
```
---
✨ **这样就完成了:**
* Vue 前端对接后端 API
* 后端Express接收请求、存储/查询邮件
* Mailu 负责接收 SMTP 邮件并调用脚本写数据库
* MySQL 存储邮件数据
---
以下是针对你域名 `shenjianl.cn` 的**简单版 SPF、DKIM、DMARC DNS TXT 记录示范**你可以直接复制去你买域名的DNS管理后台添加。
---
## 1. SPF 记录
* **类型**TXT
* **主机名**`@` (代表根域名 shenjianl.cn
* **值(内容)**
```
v=spf1 mx ip4:你的服务器公网IP -all
```
> 把 `你的服务器公网IP` 换成你实际服务器的外网IP地址。
---
## 2. DKIM 记录(示例)
假设你的邮件服务器给你生成了一个 selector叫做 `default`,那么:
* **类型**TXT
* **主机名**`default._domainkey`
* **值(内容)**
```
v=DKIM1; k=rsa; p=这里填你的公钥内容
```
> 注意:`p=` 后面是一个很长的字符串,由邮件服务器生成的公钥。
> 你可以让 Mailu 生成 DKIM 密钥,然后复制粘贴到这里。
---
## 3. DMARC 记录
* **类型**TXT
* **主机名**`_dmarc`
* **值(内容)**
```
v=DMARC1; p=quarantine; rua=mailto:admin@shenjianl.cn; pct=100
```
解释:
* `p=quarantine`:不合规邮件放到垃圾箱
* `rua=mailto:admin@shenjianl.cn`:每隔一段时间会收到报告(你可以换成自己的邮箱)
* `pct=100`:所有邮件都应用该规则
---
# 总结
| 类型 | 主机名 | 内容示例 |
| --- | ------------------- | ---------------------------------------------------------------- |
| TXT | @ | `v=spf1 mx ip4:你的服务器公网IP -all` |
| TXT | default.\_domainkey | `v=DKIM1; k=rsa; p=你的公钥` |
| TXT | \_dmarc | `v=DMARC1; p=quarantine; rua=mailto:admin@shenjianl.cn; pct=100` |
---
如果你还没有 DKIM 公钥或者不确定怎么生成,可以告诉我,我帮你写生成步骤。

26
mailu.env Normal file
View File

@ -0,0 +1,26 @@
SECRET_KEY=d4f7a1b3c2e987f05a6d3b4c8e1f2097
DOMAIN=shenjianl.cn
HOSTNAMES=mail.shenjianl.cn
POSTMASTER=admin
DB_FLAVOR=mysql
DB_HOST=43.143.145.172
DB_USER=root
DB_PASSWORD=kyff145972
DB_NAME=maildb
WEB_ADMIN=/mailu
WEB_WEBMAIL=/mailu/webmail
ENABLE_UNPRIVILEGED_USER=false
PASSWORD_SCHEME=BLF-CRYPT
ENABLE_ADMIN=true
ENABLE_WEBMAIL=true
ENABLE_WEBDAV=false
ENABLE_ANTIVIRUS=false
ENABLE_ANTISPAM=true
DEFAULT_QUOTA=2G
ADMIN_PASSWORD=admin
TLS_FLAVOR=notls
MESSAGE_SIZE_LIMIT=20000000