diff --git a/admin/public/register.html b/admin/public/register.html
new file mode 100644
index 0000000..7d232f0
--- /dev/null
+++ b/admin/public/register.html
@@ -0,0 +1,762 @@
+
+
+
+
+
+ 邀请注册
+
+
+
+
+
+
+
+
+
+
+
+
+
注册说明
+
+ - 邮箱将作为您的登录账号
+ - 密码长度至少6位,建议使用字母+数字组合
+ - 邮箱验证码有效期为5分钟
+ - 邀请码为选填,填写邀请码注册成功后,邀请人将获得3天试用期奖励
+ - 请妥善保管您的账号信息
+
+
+
+
+
+
+
diff --git a/admin/src/router/component-map.js b/admin/src/router/component-map.js
index c9918bd..56fe105 100644
--- a/admin/src/router/component-map.js
+++ b/admin/src/router/component-map.js
@@ -25,8 +25,6 @@ import JobTypes from '@/views/work/job_types.vue'
// 首页模块
import HomeIndex from '@/views/home/index.vue'
-// 邀请注册模块
-import InviteRegister from '@/views/invite/invite_register.vue'
@@ -56,7 +54,8 @@ const componentMap = {
'system/version': Version,
'work/job_types': JobTypes,
'home/index': HomeIndex,
- 'invite/invite_register': InviteRegister,
+
+
}
export default componentMap
diff --git a/admin/src/views/invite/invite_register.vue b/admin/src/views/invite/invite_register.vue
deleted file mode 100644
index 1a8810d..0000000
--- a/admin/src/views/invite/invite_register.vue
+++ /dev/null
@@ -1,359 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/admin/webpack.config.js b/admin/webpack.config.js
index e3af3a3..c8f788a 100644
--- a/admin/webpack.config.js
+++ b/admin/webpack.config.js
@@ -7,7 +7,9 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const is_production = process.env.NODE_ENV === 'production' || process.argv.includes('--mode=production')
module.exports = {
- entry: './src/main.js',
+ entry: {
+ main: './src/main.js'
+ },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
@@ -50,9 +52,30 @@ module.exports = {
},
plugins: [
new VueLoaderPlugin(),
+ // 主应用 HTML
new HtmlWebpackPlugin({
template: './public/index.html',
- title: 'Admin Framework Demo'
+ title: 'Admin Framework Demo',
+ filename: 'index.html',
+ chunks: ['main']
+ }),
+ // 注册页面 HTML(独立页面,不依赖 webpack)
+ new HtmlWebpackPlugin({
+ template: './public/register.html',
+ title: '邀请注册',
+ filename: 'register.html',
+ inject: false, // 不注入 webpack 生成的脚本
+ minify: is_production ? {
+ removeComments: true,
+ collapseWhitespace: true,
+ removeRedundantAttributes: true,
+ useShortDoctype: true,
+ removeEmptyAttributes: true,
+ removeStyleLinkTypeAttributes: true,
+ keepClosingSlash: true,
+ minifyJS: true,
+ minifyCSS: true
+ } : false
}),
...(is_production ? [
new MiniCssExtractPlugin({
diff --git a/api/controller_admin/invite_register.js b/api/controller_admin/invite_register.js
index eb87855..04f825b 100644
--- a/api/controller_admin/invite_register.js
+++ b/api/controller_admin/invite_register.js
@@ -5,6 +5,7 @@
const Framework = require("../../framework/node-core-framework.js");
const dayjs = require('dayjs');
+const email_service = require('../services/email_service.js');
module.exports = {
/**
@@ -21,26 +22,25 @@ module.exports = {
* schema:
* type: object
* required:
- * - phone
+ * - email
* - password
- * - sms_code
- * - invite_code
+ * - email_code
* properties:
- * phone:
+ * email:
* type: string
- * description: 手机号
- * example: '13800138000'
+ * description: 邮箱地址
+ * example: 'user@example.com'
* password:
* type: string
* description: 密码
* example: 'password123'
- * sms_code:
+ * email_code:
* type: string
- * description: 短信验证码
+ * description: 验证码
* example: '123456'
* invite_code:
* type: string
- * description: 邀请码
+ * description: 邀请码(选填)
* example: 'INV123_ABC123'
* responses:
* 200:
@@ -49,17 +49,17 @@ module.exports = {
'POST /invite/register': async (ctx) => {
try {
const body = ctx.getBody();
- const { phone, password, sms_code, invite_code } = body;
+ const { email, password, email_code, invite_code } = body;
- // 验证参数
- if (!phone || !password || !sms_code || !invite_code) {
- return ctx.fail('手机号、密码、短信验证码和邀请码不能为空');
+ // 验证必填参数
+ if (!email || !password || !email_code) {
+ return ctx.fail('邮箱、密码和验证码不能为空');
}
- // 验证手机号格式
- const phoneRegex = /^1[3-9]\d{9}$/;
- if (!phoneRegex.test(phone)) {
- return ctx.fail('手机号格式不正确');
+ // 验证邮箱格式
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(email)) {
+ return ctx.fail('邮箱格式不正确');
}
// 验证密码长度
@@ -67,51 +67,57 @@ module.exports = {
return ctx.fail('密码长度不能少于6位');
}
- // 验证短信验证码(这里需要调用短信验证服务)
- const smsVerifyResult = await verifySmsCode(phone, sms_code);
- if (!smsVerifyResult.success) {
- return ctx.fail(smsVerifyResult.message || '短信验证码错误或已过期');
+ // 验证验证码
+ const emailVerifyResult = await verifyEmailCode(email, email_code);
+ if (!emailVerifyResult.success) {
+ return ctx.fail(emailVerifyResult.message || '验证码错误或已过期');
}
const { pla_account } = await Framework.getModels();
- // 检查手机号是否已注册
+ // 检查邮箱是否已注册
const existingUser = await pla_account.findOne({
- where: { login_name: phone }
+ where: { login_name: email }
});
if (existingUser) {
- return ctx.fail('该手机号已被注册');
+ return ctx.fail('该邮箱已被注册');
}
- // 解析邀请码,获取邀请人ID
- // 邀请码格式:INV{user_id}_{timestamp}
- const inviteMatch = invite_code.match(/^INV(\d+)_/);
- if (!inviteMatch) {
- return ctx.fail('邀请码格式不正确');
+ // 验证邀请码(如果提供了邀请码)
+ let inviter = null;
+ let inviter_id = null;
+
+ if (invite_code) {
+ // 解析邀请码,获取邀请人ID
+ // 邀请码格式:INV{user_id}_{timestamp}
+ const inviteMatch = invite_code.match(/^INV(\d+)_/);
+ if (!inviteMatch) {
+ return ctx.fail('邀请码格式不正确');
+ }
+
+ inviter_id = parseInt(inviteMatch[1]);
+
+ // 验证邀请人是否存在
+ inviter = await pla_account.findOne({
+ where: { id: inviter_id }
+ });
+
+ if (!inviter) {
+ return ctx.fail('邀请码无效,邀请人不存在');
+ }
}
- const inviter_id = parseInt(inviteMatch[1]);
-
- // 验证邀请人是否存在
- const inviter = await pla_account.findOne({
- where: { id: inviter_id }
- });
-
- if (!inviter) {
- return ctx.fail('邀请码无效,邀请人不存在');
- }
-
- // 生成设备SN码(基于手机号和时间戳)
+ // 生成设备SN码(基于邮箱和时间戳)
const sn_code = `SN${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
// 创建新用户
const newUser = await pla_account.create({
- name: phone, // 默认使用手机号作为名称
+ name: email.split('@')[0], // 默认使用邮箱用户名作为名称
sn_code: sn_code,
device_id: '', // 设备ID由客户端登录时提供
platform_type: 'boss', // 默认平台类型
- login_name: phone,
+ login_name: email,
pwd: password,
keyword: '',
is_enabled: 1,
@@ -158,7 +164,7 @@ module.exports = {
inviter_sn_code: inviter.sn_code,
invitee_id: newUser.id,
invitee_sn_code: newUser.sn_code,
- invitee_phone: phone,
+ invitee_phone: email, // 使用邮箱代替手机号
invite_code: invite_code,
register_time: new Date(),
reward_status: 1, // 已发放
@@ -167,7 +173,9 @@ module.exports = {
is_delete: 0
});
- console.log(`[邀请注册] 用户 ${phone} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`);
+ console.log(`[邀请注册] 用户 ${email} 通过邀请码 ${invite_code} 注册成功,邀请人 ${inviter.sn_code} 获得3天试用期`);
+ } else {
+ console.log(`[邀请注册] 用户 ${email} 注册成功(无邀请码)`);
}
return ctx.success({
@@ -186,10 +194,10 @@ module.exports = {
/**
* @swagger
- * /admin_api/invite/send-sms:
+ * /admin_api/invite/send-email-code:
* post:
- * summary: 发送短信验证码
- * description: 发送短信验证码到指定手机号
+ * summary: 发送验证码
+ * description: 发送验证码到指定邮箱地址
* tags: [后台-邀请注册]
* requestBody:
* required: true
@@ -198,107 +206,114 @@ module.exports = {
* schema:
* type: object
* required:
- * - phone
+ * - email
* properties:
- * phone:
+ * email:
* type: string
- * description: 手机号
- * example: '13800138000'
+ * description: 邮箱地址
+ * example: 'user@example.com'
* responses:
* 200:
* description: 发送成功
*/
- 'POST /invite/send-sms': async (ctx) => {
+ 'POST /invite/send-email-code': async (ctx) => {
try {
const body = ctx.getBody();
- const { phone } = body;
+ const { email } = body;
- if (!phone) {
- return ctx.fail('手机号不能为空');
+ if (!email) {
+ return ctx.fail('邮箱地址不能为空');
}
- // 验证手机号格式
- const phoneRegex = /^1[3-9]\d{9}$/;
- if (!phoneRegex.test(phone)) {
- return ctx.fail('手机号格式不正确');
+ // 验证邮箱格式
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(email)) {
+ return ctx.fail('邮箱格式不正确');
}
- // 发送短信验证码
- const smsResult = await sendSmsCode(phone);
- if (!smsResult.success) {
- return ctx.fail(smsResult.message || '发送短信验证码失败');
+ // 发送验证码
+ const emailResult = await sendEmailCode(email);
+ if (!emailResult.success) {
+ return ctx.fail(emailResult.message || '发送验证码失败');
}
return ctx.success({
- message: '短信验证码已发送',
- expire_time: smsResult.expire_time || 300 // 默认5分钟过期
+ message: '验证码已发送',
+ expire_time: emailResult.expire_time || 300 // 默认5分钟过期
});
} catch (error) {
- console.error('发送短信验证码失败:', error);
- return ctx.fail('发送短信验证码失败: ' + error.message);
+ console.error('发送验证码失败:', error);
+ return ctx.fail('发送验证码失败: ' + error.message);
}
}
};
/**
- * 发送短信验证码
- * @param {string} phone 手机号
+ * 发送验证码
+ * @param {string} email 邮箱地址
* @returns {Promise<{success: boolean, message?: string, expire_time?: number}>}
*/
-async function sendSmsCode(phone) {
+async function sendEmailCode(email) {
try {
- // TODO: 实现真实的短信发送逻辑
- // 这里可以使用第三方短信服务(如阿里云、腾讯云等)
-
// 生成6位随机验证码
const code = Math.floor(100000 + Math.random() * 900000).toString();
// 将验证码存储到缓存中(可以使用Redis或内存缓存)
- // 格式:sms_code:{phone} = {code, expire_time}
+ // 格式:email_code:{email} = {code, expire_time}
const expire_time = Date.now() + 5 * 60 * 1000; // 5分钟后过期
// 这里应该存储到缓存中,暂时使用全局变量(生产环境应使用Redis)
- if (!global.smsCodeCache) {
- global.smsCodeCache = {};
+ if (!global.emailCodeCache) {
+ global.emailCodeCache = {};
}
- global.smsCodeCache[phone] = {
+ global.emailCodeCache[email] = {
code: code,
expire_time: expire_time
};
- // TODO: 调用真实的短信发送接口
- console.log(`[短信验证] 发送验证码到 ${phone}: ${code} (5分钟内有效)`);
+ // 调用邮件服务发送验证码
+ const email_result = await email_service.send_verification_code(email, code);
+
+ if (!email_result.success) {
+ // 如果邮件发送失败,删除已生成的验证码
+ delete global.emailCodeCache[email];
+ return {
+ success: false,
+ message: email_result.message || '发送验证码失败'
+ };
+ }
+
+ console.log(`[邮箱验证] 验证码已发送到 ${email}: ${code} (5分钟内有效)`);
- // 模拟发送成功
return {
success: true,
expire_time: 300
};
} catch (error) {
- console.error('发送短信验证码失败:', error);
+ console.error('发送验证码失败:', error);
return {
success: false,
- message: error.message || '发送短信验证码失败'
+ message: error.message || '发送验证码失败'
};
}
}
/**
- * 验证短信验证码
- * @param {string} phone 手机号
+ * 验证验证码
+ * @param {string} email 邮箱地址
* @param {string} code 验证码
* @returns {Promise<{success: boolean, message?: string}>}
*/
-async function verifySmsCode(phone, code) {
+async function verifyEmailCode(email, code) {
try {
- if (!global.smsCodeCache) {
+ if (!global.emailCodeCache) {
return {
success: false,
message: '验证码不存在或已过期'
};
}
- const cached = global.smsCodeCache[phone];
+ const cached = global.emailCodeCache[email];
if (!cached) {
return {
success: false,
@@ -308,7 +323,7 @@ async function verifySmsCode(phone, code) {
// 检查是否过期
if (Date.now() > cached.expire_time) {
- delete global.smsCodeCache[phone];
+ delete global.emailCodeCache[email];
return {
success: false,
message: '验证码已过期,请重新获取'
@@ -324,13 +339,13 @@ async function verifySmsCode(phone, code) {
}
// 验证成功后删除缓存
- delete global.smsCodeCache[phone];
+ delete global.emailCodeCache[email];
return {
success: true
};
} catch (error) {
- console.error('验证短信验证码失败:', error);
+ console.error('验证验证码失败:', error);
return {
success: false,
message: error.message || '验证失败'
diff --git a/api/services/email_service.js b/api/services/email_service.js
new file mode 100644
index 0000000..db008e3
--- /dev/null
+++ b/api/services/email_service.js
@@ -0,0 +1,189 @@
+/**
+ * 邮件服务
+ * 使用QQ邮箱SMTP服务发送邮件
+ */
+
+const nodemailer = require('nodemailer');
+const config = require('../../config/config.js');
+
+// 创建邮件传输器
+let transporter = null;
+
+/**
+ * 初始化邮件传输器
+ */
+function init_transporter() {
+ if (transporter) {
+ return transporter;
+ }
+
+ // QQ邮箱SMTP配置
+ const email_config = config.email || {
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true, // 使用SSL
+ auth: {
+ user: process.env.QQ_EMAIL_USER || '', // QQ邮箱账号
+ pass: process.env.QQ_EMAIL_PASS || '' // QQ邮箱授权码(不是密码)
+ }
+ };
+
+ transporter = nodemailer.createTransport({
+ host: email_config.host,
+ port: email_config.port,
+ secure: email_config.secure,
+ auth: email_config.auth
+ });
+
+ return transporter;
+}
+
+/**
+ * 发送邮件
+ * @param {Object} options 邮件选项
+ * @param {string} options.to 收件人邮箱
+ * @param {string} options.subject 邮件主题
+ * @param {string} options.html 邮件HTML内容
+ * @param {string} options.text 邮件纯文本内容(可选)
+ * @returns {Promise<{success: boolean, message?: string, messageId?: string}>}
+ */
+async function send_email(options) {
+ try {
+ const transporter_instance = init_transporter();
+
+ if (!transporter_instance) {
+ return {
+ success: false,
+ message: '邮件服务未配置'
+ };
+ }
+
+ // 如果没有配置邮箱账号,返回错误
+ const email_config = config.email || {};
+ if (!email_config.auth || !email_config.auth.user || !email_config.auth.pass) {
+ console.warn('[邮件服务] QQ邮箱未配置,使用模拟发送');
+ // 开发环境可以模拟发送
+ if (config.env === 'development') {
+ console.log(`[模拟邮件] 发送到 ${options.to}`);
+ console.log(`[模拟邮件] 主题: ${options.subject}`);
+ console.log(`[模拟邮件] 内容: ${options.text || options.html}`);
+ return {
+ success: true,
+ message: '邮件已发送(模拟)',
+ messageId: 'mock-' + Date.now()
+ };
+ }
+ return {
+ success: false,
+ message: '邮件服务未配置,请联系管理员'
+ };
+ }
+
+ // 发送邮件
+ const mail_options = {
+ from: `"${email_config.fromName || '自动找工作系统'}" <${email_config.auth.user}>`,
+ to: options.to,
+ subject: options.subject,
+ html: options.html,
+ text: options.text || options.html.replace(/<[^>]*>/g, '') // 如果没有text,从html提取
+ };
+
+ const info = await transporter_instance.sendMail(mail_options);
+
+ console.log(`[邮件服务] 邮件发送成功: ${options.to}, MessageId: ${info.messageId}`);
+
+ return {
+ success: true,
+ message: '邮件发送成功',
+ messageId: info.messageId
+ };
+ } catch (error) {
+ console.error('[邮件服务] 发送邮件失败:', error);
+ return {
+ success: false,
+ message: error.message || '发送邮件失败'
+ };
+ }
+}
+
+/**
+ * 发送验证码邮件
+ * @param {string} email 收件人邮箱
+ * @param {string} code 验证码
+ * @returns {Promise<{success: boolean, message?: string, messageId?: string}>}
+ */
+async function send_verification_code(email, code) {
+ const subject = '【自动找工作系统】注册验证码';
+ const html = `
+
+
+
+
+
+
+
+
+
+
验证码
+
您好,
+
您正在注册自动找工作系统,验证码为:
+
${code}
+
验证码有效期为 5分钟,请勿泄露给他人。
+
+
如果这不是您的操作,请忽略此邮件。
+
此邮件由系统自动发送,请勿回复。
+
+
+
+
+
+ `;
+
+ return await send_email({
+ to: email,
+ subject: subject,
+ html: html
+ });
+}
+
+module.exports = {
+ send_email,
+ send_verification_code,
+ init_transporter
+};
diff --git a/config/config.js b/config/config.js
index 8f34850..43eea6c 100644
--- a/config/config.js
+++ b/config/config.js
@@ -64,7 +64,7 @@ module.exports = {
},
// 白名单URL - 不需要token验证的接口
- "allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', '/admin_api/invite/register', '/admin_api/invite/send-sms'],
+ "allowUrls": ["/admin_api/sys_user/login", "/admin_api/sys_user/authorityMenus", "/admin_api/sys_user/register", "/api/user/loginByWeixin", "/file/", "/sys_file/", "/admin_api/win_data/viewLogInfo", "/api/user/wx_auth", '/api/docs', 'api/swagger.json', 'payment/notify', 'payment/refund-notify', 'wallet/transfer_notify', 'user/sms/send', 'user/sms/verify', '/api/version/check','/api/file/upload_file_to_oss_by_auto_work','/api/version/create', '/admin_api/invite/register', '/admin_api/invite/send-email-code'],
// AI服务配置
@@ -89,5 +89,16 @@ module.exports = {
},
qq_map_key: "VIABZ-3N6HT-4BLXK-VF3FD-TM6YF-YRFQM",
+ // 邮件服务配置(QQ邮箱)
+ email: {
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true, // 使用SSL
+ fromName: '自动找工作系统',
+ auth: {
+ user: 'light603@qq.com' || '', // QQ邮箱账号,例如: 123456789@qq.com
+ pass: 'fxqnednoacqybbba' || '' // QQ邮箱授权码(不是密码,需要在QQ邮箱设置中获取)
+ }
+ }
};
diff --git a/package.json b/package.json
index 10cc4e8..5b336a7 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"mqtt": "^5.14.0",
"mysql2": "^1.7.0",
"node-schedule": "latest",
+ "nodemailer": "^6.9.7",
"node-uuid": "^1.4.8",
"redis": "^5.8.3",
"sequelize": "^5.22.5",