Chap05-NodejsServer

Chap05-NodejsServer

Nodejs基础

非专业,仅因使用在这里补充一些简单的基础。

1. JavaScript 基础语法

变量声明

javascript

// var (函数作用域,不推荐)
var oldVariable = "value";

// let (块级作用域,可重新赋值)
let counter = 0;
counter = 1;

// const (块级作用域,不可重新赋值)
const PI = 3.14159;
const user = { name: "John" };
user.name = "Jane"; // 允许,对象内部可修改

数据类型

javascript

// 原始类型
const str = "Hello";
const num = 42;
const bool = true;
const nullValue = null;
const undefinedValue = undefined;
const sym = Symbol("unique");

// 引用类型
const obj = { key: "value" };
const arr = [1, 2, 3];
const func = function() { return "result"; };

函数

javascript

// 函数声明
function add(a, b) {
    return a + b;
}

// 函数表达式
const multiply = function(a, b) {
    return a * b;
};

// 箭头函数 (ES6+)
const divide = (a, b) => a / b;
const square = x => x * x;

// 默认参数
function greet(name = "Guest") {
    return `Hello, ${name}!`;
}

2. 对象和数组操作

对象

javascript

// 对象创建和访问
const person = {
    name: "Alice",
    age: 30,
    hobbies: ["reading", "coding"],
    
    // 方法
    introduce() {
        return `I'm ${this.name}, ${this.age} years old`;
    }
};

// 动态访问
console.log(person.name);        // "Alice"
console.log(person["age"]);      // 30

// 对象解构
const { name, age } = person;
const { name: personName, ...rest } = person;

// 对象扩展
const updatedPerson = { ...person, city: "New York" };

数组

javascript

// 数组操作
const numbers = [1, 2, 3, 4, 5];

// 常用方法
numbers.push(6);           // 末尾添加
numbers.pop();             // 末尾删除
numbers.map(x => x * 2);   // 映射新数组
numbers.filter(x => x > 2);// 过滤
numbers.reduce((sum, x) => sum + x, 0); // 累加

// 数组解构
const [first, second, ...others] = numbers;

// 扩展运算符
const newArray = [...numbers, 6, 7];

3. 控制流

条件语句

javascript

// if-else
if (age >= 18) {
    console.log("Adult");
} else if (age >= 13) {
    console.log("Teenager");
} else {
    console.log("Child");
}

// 三元运算符
const status = age >= 18 ? "Adult" : "Minor";

// switch
switch (day) {
    case "Monday":
        console.log("Start of week");
        break;
    case "Friday":
        console.log("Weekend coming");
        break;
    default:
        console.log("Regular day");
}

循环

javascript

// for 循环
for (let i = 0; i < 5; i++) {
    console.log(i);
}

// for...of (数组)
for (const item of array) {
    console.log(item);
}

// for...in (对象属性)
for (const key in object) {
    console.log(key, object[key]);
}

// while
let i = 0;
while (i < 5) {
    console.log(i);
    i++;
}

// 数组方法循环
array.forEach(item => console.log(item));

4. 类与面向对象

javascript

// 类定义
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 方法
    introduce() {
        return `Hello, I'm ${this.name}`;
    }
    
    // 静态方法
    static createAnonymous() {
        return new Person("Anonymous", 0);
    }
}

// 继承
class Student extends Person {
    constructor(name, age, grade) {
        super(name, age);  // 调用父类构造函数
        this.grade = grade;
    }
    
    study() {
        return `${this.name} is studying`;
    }
}

// 使用
const student = new Student("Bob", 20, "A");
console.log(student.introduce());

5. 异步编程核心

Promise

javascript

// 创建 Promise
const fetchData = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            resolve("Data received");
        } else {
            reject("Error: Failed to fetch data");
        }
    }, 1000);
});

// 使用 Promise
fetchData
    .then(data => {
        console.log("Success:", data);
        return processData(data);
    })
    .then(processedData => {
        console.log("Processed:", processedData);
    })
    .catch(error => {
        console.error("Error:", error);
    })
    .finally(() => {
        console.log("Operation completed");
    });

// Promise 工具方法
Promise.all([promise1, promise2])      // 所有成功
Promise.race([promise1, promise2])     // 第一个完成
Promise.any([promise1, promise2])      // 第一个成功

Async/Await

javascript

// async 函数总是返回 Promise
async function getUserData(userId) {
    try {
        const user = await fetchUser(userId);
        const posts = await fetchUserPosts(userId);
        
        return {
            user,
            posts
        };
    } catch (error) {
        console.error("Failed to get user data:", error);
        throw error; // 重新抛出错误
    }
}

// 使用 async 函数
getUserData(123)
    .then(data => console.log(data))
    .catch(error => console.error(error));

// 立即执行 async 函数
(async () => {
    try {
        const data = await getUserData(123);
        console.log(data);
    } catch (error) {
        console.error(error);
    }
})();

6. 错误处理

javascript

// try-catch
try {
    // 可能抛出错误的代码
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("Operation failed:", error.message);
} finally {
    console.log("Cleanup");
}

// 自定义错误
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

// 抛出错误
function validateInput(input) {
    if (!input) {
        throw new ValidationError("Input cannot be empty");
    }
}

// Promise 错误处理
someAsyncFunction()
    .catch(error => {
        console.error("Async error:", error);
        return defaultValue;
    });

7. 模块系统

CommonJS 模块 (Node.js 默认)

javascript

// math.js - 导出模块
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 导出方式 1: module.exports
module.exports = {
    add,
    multiply
};

// 导出方式 2: exports (不推荐直接赋值)
exports.add = add;
exports.multiply = multiply;

// app.js - 导入模块
const math = require('./math.js');
// 或者解构导入
const { add, multiply } = require('./math.js');

console.log(math.add(2, 3)); // 5

ES6 模块 (需要 package.json 中 "type": "module")

javascript

// math.mjs - 导出
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export default function calculator() { /* ... */ }

// app.mjs - 导入
import calculator, { add, multiply } from './math.mjs';
import * as math from './math.mjs';

8. 事件处理

javascript

const EventEmitter = require('events');

// 创建事件发射器
class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// 监听事件
myEmitter.on('event', (data) => {
    console.log('Event received:', data);
});

// 一次性监听
myEmitter.once('firstEvent', () => {
    console.log('This will only run once');
});

// 发射事件
myEmitter.emit('event', { message: 'Hello' });
myEmitter.emit('firstEvent');

// 错误事件处理
myEmitter.on('error', (error) => {
    console.error('Emitter error:', error);
});

9. 函数高级特性

闭包

javascript

function createCounter() {
    let count = 0; // 私有变量
    
    return {
        increment: () => ++count,
        decrement: () => --count,
        getCount: () => count
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.getCount());  // 1

高阶函数

javascript

// 接受函数作为参数
function mapArray(arr, transform) {
    const result = [];
    for (let item of arr) {
        result.push(transform(item));
    }
    return result;
}

const doubled = mapArray([1, 2, 3], x => x * 2);

// 返回函数
function createMultiplier(multiplier) {
    return function(x) {
        return x * multiplier;
    };
}

const double = createMultiplier(2);
console.log(double(5)); // 10

10. 实用语法特性

模板字符串

javascript

const name = "Alice";
const age = 30;

// 多行字符串和插值
const message = `
    Hello, ${name}!
    You are ${age} years old.
    Next year you'll be ${age + 1}.
`;

console.log(message);

可选链和空值合并

javascript

const user = {
    profile: {
        name: "John",
        address: {
            city: "New York"
        }
    }
};

// 可选链 (?.)
const city = user?.profile?.address?.city; // "New York"
const country = user?.profile?.address?.country; // undefined

// 空值合并 (??)
const displayName = user?.name ?? "Anonymous";
const count = 0;
const result = count ?? 42; // 0 (只有 null/undefined 才用默认值)

解构赋值

javascript

// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
// a=1, b=2, rest=[3,4,5]

// 对象解构
const { name, age, ...otherProps } = person;
const { name: personName, age: personAge } = person;

// 函数参数解构
function printUser({ name, age = 18 }) {
    console.log(`${name} is ${age} years old`);
}

11. 内存管理注意事项

javascript

// 避免内存泄漏
class DataProcessor {
    constructor() {
        this.data = new Array(1000000).fill('data');
        // 移除不必要的全局引用
    }
    
    // 清理方法
    cleanup() {
        this.data = null;
    }
}

// 使用弱引用 (WeakMap/WeakSet)
const weakMap = new WeakMap();
const key = {};
weakMap.set(key, 'value');
// 当 key 被垃圾回收时,值也会自动清理

Nodejs实现邮箱验证服务

这一节我们使用nodejs实现邮箱发送服务。之所以不用cpp实现,是因为相对来说要复杂的多。因此,我们选用了易用的nodejs.

首先选择一个目录,使用npm init初始化项目。

然后清除旧的镜像源

npm cache clean --force

重新设置淘宝镜像源

npm config set registry https://registry.npmmirror.com

紧接着下载grpc-js

npm install @grpc/grpc-js

安装proto-loader解析proto文件

npm install @grpc/proto-loader

再来安装重要的流行的

npm install node nodemailer

整体项目如下

image-20251016163217827

首先我们把在GateWayServer创建的proto文件移动到这里。因为既然要通信,那么通信的载体或者说格式应该是双方互通的。在这里我们不需要命令工具解析这个proto文件,因为我们使用js库动态解析。

  1. proto.js

    用于动态解析proto文件。

    const path = require('path')
    const grpc = require('@grpc/grpc-js')
    const protoLoader = require('@grpc/proto-loader')
    
    const PROTO_PATH = path.join(__dirname, 'message.proto')
    const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true })
    const protoDescriptor = grpc.loadPackageDefinition(packageDefinition)
    
    const message_proto = protoDescriptor.message
    
    module.exports = message_proto
    
  2. config.json

    实现参数方便配置,我们创建json文件存入配置方便修改。

    {
        "email":{
            "user" : "v125250@163.com",
            "pass" : "YJn8bUJKDLfEGA9F" 
        },
        "mysql":{
            "host":"127.0.0.1",
            "port":33046,
            "password":38324
        },
        "redis":{
            "host":"127.0.0.1",
            "port":6380,
            "password" :38324
        }
    }
    
  3. config.js

    有了json配置,我们创建config.js来读取配置

    const fs = require('fs');
    
    let config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
    let email_user = config.email.user;
    let email_pass = config.email.pass;
    let mysql_host = config.mysql.host;
    let mysql_port = config.mysql.port;
    let redis_host = config.redis.host;
    let redis_port = config.redis.port;
    let redis_passwd = config.redis.passwd;
    let code_prefix = "code_";
    
    
    module.exports = {email_pass, email_user, mysql_host, mysql_port,redis_host, redis_port, redis_passwd, code_prefix}
    
  4. email.js

    封装发邮件的模块+发邮件的方法

    const nodemailer = require('nodemailer');
    const config_module = require("./config")
    
    /**
     * 创建发送邮件的代理
     */
    let transport = nodemailer.createTransport({
        host: 'smtp.163.com',
        port: 465,
        secure: true,
        auth: {
            user: config_module.email_user, // 发送方邮箱地址
            pass: config_module.email_pass // 邮箱授权码或者密码
        }
    });
    
    
    /**
     * 发送邮件的函数
     * @param {*} mailOptions_ 发送邮件的参数
     * @returns 
     */
    
    
    /**
    因为transport.SendMail相当于一个异步函数,调用该函数后发送的结果是通过回调函数通知的,所以我们没办法同步使用,需要用Promise封装这个调用,抛出Promise给外部,那么外部就可以通过await或者then catch的方式处理了。
    
    这里的Promise类似cpp的future包装了一下,future.wait可以同步的等待接受数据。
    这里的promise同理。
    */
    
    function SendMail(mailOptions_){
        return new Promise(function(resolve, reject){
            transport.sendMail(mailOptions_, function(error, info){
                if (error) {
                    console.log(error);
                    reject(error);
                } else {
                    console.log('邮件已成功发送:' + info.response);
                    resolve(info.response)
                }
            });
        })
    
    }
    
    module.exports.SendMail = SendMail
    
  5. server.js

    这个文件是主main入口。

    显示引用了需要的模块,然后为grpc服务器添加GetSecurityCode服务。这个GetSecurityCode的方法名称需要和proto定义的一致。

    同时为了接收方接受的界面美观,我们在html_str使用了样式美化。

    在要传送的结构mailOptions里面传给html即可。或者如果要使用纯文本,传给text也可。

    
    const grpc = require('@grpc/grpc-js')
    const message_proto = require('./proto')
    const const_module = require('./const')
    const {v4:uuidv4} = require('uuid')
    const emailModule = require('./email')
    
    async function GetSecurityCode(call, callback) {
        console.log("Email is:", call.request.email)
        try{
            uniqueId = uuidv4().toUpperCase().substring(0,4);
            console.log("UniqueId is ", uniqueId)
                    let html_str = `
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <meta charset="utf-8">
                        <style>
                            .container { max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif; }
                            .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; }
                            .content { padding: 30px; background: #f8f9fa; }
                            .code-box { background: white; padding: 25px; border-radius: 10px; text-align: center; margin: 20px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
                            .verification-code { font-size: 42px; font-weight: bold; color: #e74c3c; letter-spacing: 8px; margin: 15px 0; }
                            .warning { color: #e74c3c; font-weight: bold; background: #ffeaa7; padding: 10px; border-radius: 5px; margin: 15px 0; }
                            .footer { text-align: center; color: #666; font-size: 12px; padding: 20px; }
                        </style>
                    </head>
                    <body>
                        <div class="container">
                            <div class="header">
                                <h1>QuickChat 账户注册验证</h1>
                            </div>
                            <div class="content">
                                <p>亲爱的用户,您好!</p>
                                <p>您正在注册QuickChat账户,请使用以下验证码完成验证:</p>
                                
                                <div class="code-box">
                                    <p>验证码</p>
                                    <div class="verification-code">${uniqueId}</div>
                                    <p style="color: #666;">(有效期3分钟)</p>
                                </div>
                                
                                <div class="warning">
                                    ⚠️ 安全提示:请勿向任何人泄露此验证码!
                                </div>
                                
                                <p>如果这不是您的操作,请忽略此邮件。</p>
                            </div>
                            <div class="footer">
                                <p>系统自动发送,请勿回复</p>
                            </div>
                        </div>
                    </body>
                    </html>
                            `;
            //发送邮件
            let mailOptions = {
                from: 'v125250@163.com',
                to: call.request.email,
                subject: '验证码',
                html: html_str,
            };
    
            let send_res = await emailModule.SendMail(mailOptions);
            console.log("Send Result Is ", send_res)
    
            callback(null, { email:  call.request.email,
                error:const_module.Errors.Success
            }); 
    
    
        }catch(error){
            console.log("Catch Error Is ", error)
    
            callback(null, { email:  call.request.email,
                error:const_module.Errors.Exception
            }); 
        }
    
    }
    
    function main() {
        var server = new grpc.Server()
        server.addService(message_proto.VarifyService.service, { GetSecurityCode: GetSecurityCode })
        server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
            server.start()
            console.log('grpc server started')        
        })
    }
    
    main()
    

这下,真正的邮箱发送服务完成。我们可以打开qt前端进行测试。

image-20251016164432149

发送获取服务后,我们的GateWayServer接受到了请求:

image-20251016164517283

同时nodejs后端服务也受到了grpc请求:

image-20251016164548751

打开邮箱验证:

image-20251016164617918

至此,验证码获取服务完成。

posted @ 2025-12-24 23:16  大胖熊哈  阅读(1)  评论(0)    收藏  举报