# 一、基础准备

说明:学习本案例,需要把第二学期-第二季课程:章节7:Node.js基础 按照老师的课程视频,一步一步听下来敲下来,不要跳课学习!

# ① 搭建node服务器

新建: /app.js文件,修改 /package.json的调试命令

// package.json的调试命令修改启动文件
"scripts": {
    "start": "nodemon app.js", //启动app.js文件
    "start:node": "node index.js",
    "dev": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
},

/app.js文件代码,参考:章节7-五、Node中的数据交互-② querystring模块处理POST(post)请求 或者 复制 /index.js文件:213行-247行

let http = require('http');
let fs = require('fs');
let $url = require('url');
let querystring = require('querystring');
const server =  http.createServer((request,response)=>{
    if(request.method == 'GET'){
        let url = request.url;
        if(url.indexOf('/api/') == -1){
            fs.readFile(`./${url}`,(err,data)=>{
                if(err){
                    response.setHeader('Content-Type','text/html; charset=utf-8');
                    response.writeHead(404);
                    response.end('404页面');
                }else{
                    response.writeHead(200);
                    response.end(data);
                }
            });
        }  
    }else if(request.method == 'POST'){
        let result = [];
        request.on('data',buffer=>{
           result.push(buffer);
        });
        request.on('end',()=>{
           let data = Buffer.concat(result);
           console.log(querystring.parse(data.toString()));
        });
        console.log(123456789);
    }
});

server.listen(8888,'127.0.0.1',()=>{
    console.log('服务器已启动');
});

启动node服务,让页面先运行起来

//vscode: 终端--新建终端(ctrl +shift + `),选择 mynode文件夹
npm run start

谷歌浏览器地址栏输入:http://localhost:8888/index.html 或者 http://127.0.0.1:8888/index.html

# ② contact.html页面

引入jQuery,新建: /static/js/contact.jquery.js,删除页面底部之前写的js文件

<script src="./static/js/jquery.1.11.3.min.js"></script>
<script src="./static/js/contact.jquery.js"></script>

补充一下发送按钮的样式,鼠标放上去有个手型,/static/css/style.css

.page-message .message .submit-div span{
    color: #ffffff;
    font-size: 24px;
    border: 3px solid #ffffff;
    padding: 15px 70px;
    cursor: pointer;//补充上
}

/static/js/contact.jquery.js

$(function(){
   $('.submit-div span').on('click',function(){
       console.log('点击发送按钮')
   });
});

# 二、获取留言板输入的内容

说明:由于我们还没有学习正则表达式,所以本案例不对用户输入内容的合规做深度说明,比如:姓名里面输入数字,电话号码输入错误(关于正则表达式我们后面有专题课讲解)

//手机号正则:
/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/
//说明:
//1.这个正则表达式可以匹配大多数中国大陆的手机号码,包括 13、14、15、17、18 开头的手机号码,
//  以及 166、198、199 开头的虚拟运营商的手机号码
//2.这个正则表达式不能匹配国际号码、小灵通号码等

# ① 补全留言板表单信息

<div class="message-main">
    <div>
        <span>您的称呼:</span>
        <input type="text" name="username" placeholder="请输入您的称呼" />
    </div>
    <div>
        <span>您的电话:</span>
        <input type="text" name="tel" placeholder="请输入您的电话号码">
    </div>
    <div>
        <span>您的留言:</span>
        <textarea rows="10" cols="30" name="message" placeholder="请输入您想咨询的信息"></textarea>
    </div>
</div>

回顾面向对象的知识,完成获取表单数据及合规判断

$(function(){
   
    //回顾一下面向对象
    let message = {
        //获取您的称呼的值
        username:function(){
            return $('input[name=username]').val();
            // return document.getElementsByName('username')[0].value;
        },
        tel:function(){
            return $('input[name=tel]').val();
            // return document.getElementsByName('tel')[0].value;
        },
        message:function(){
            return $('textarea[name=message]').val();
            // return document.getElementsByName('message')[0].value;
        },
        //合法性判断
        is_tel:function(){
            let tel = this.tel();
            // return tel;
            let patter = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/;
            if(patter.test(tel)){//返回布尔值,匹配成功返回true,不成功返回false
                //成功
                return {
                    msg:'ok',
                    data:tel
                }
            }else{
                //不成功
                return {
                    msg:'error',
                    data:'电话号码错误'
                }
            }
        },
        //页面提示
        is_tel_html:function(){
            let is_tel = this.is_tel();
            if(is_tel.msg == 'ok'){
                return  is_tel;
            }else{
                //页面提示,并终止程序
                $('input[name=tel]').parent().append('<span style="color:red">'+ is_tel.data + '</span>');
                return;
            }
        }
    }
    $('.submit-div span').on('click',function(){
       // console.log(message.username());
       // console.log(message.tel());
       // console.log(message.message());

       // console.log(message.is_tel());
       console.log(message.is_tel_html());
   });
});

# ② 监听输入数据的合法性

当手机号码对了之后,移除错误提示,可以监听输入框输入值的变化

let tel_parent = $('input[name=tel]').parent();
let message = {
    //合法性判断
       is_tel:function(){
           let tel = this.tel();
           // return tel;
           if(!tel){
               return {
                   msg:'error',
                   data:'请输入您的电话号码'
               }
           }
           let patter = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/;
           // console.log(patter.test(tel));
           if(patter.test(tel)){
               //正确的电话号码
               return {
                   msg:'ok',
                   data:tel
               }
           }else{
               return {
                   msg:'error',
                   data:'电话号码不正确'
               }
           }
       },
       //页面提示
       is_tel_html:function(){
           let is_tel = this.is_tel();
           console.log(is_tel);
           // return is_tel;
           let spanhtml = '<span style="color:red">'+ is_tel.data +'</span>';
           tel_parent.find('span').eq(1).remove();
           if(is_tel.msg == 'ok'){
               // console.log(tel_parent.find('span').eq(1).length);
               /*
               if(tel_parent.find('span').eq(1).length){
                   tel_parent.find('span').eq(1).remove();
               }
               */
               // tel_parent.find('span').eq(1).remove();
               return is_tel;
           }else{
               //页面提示,并终止程序
               // console.log(tel_parent.find('span').eq(1).length);
               // tel_parent.find('span').eq(1).remove();
               tel_parent.append(spanhtml);
               return;
           }
       },
       //监听电话画面输入框
       input_is_tel_html:function(){
           /*
           let _this = this;
           $('input[name=tel]').on('input',function(){
               // console.log($(this).val());
               _this.is_tel_html();
           });
           */
           //箭头函数和匿名函数的区别我们会在后面讲解
           $('input[name=tel]').on('input',()=>{
              if($('input[name=tel]').val().length > 10 || tel_parent.find('span').eq(1).length){
                 this.is_tel_html();
              }               
           });
       }
};
//监听输入框输入内容变化
message.input_is_tel_html();
$('.submit-div span').on('click',function(){
    // console.log(message.username());
    // console.log(message.tel());
    // console.log(message.message());

    // console.log(message.is_tel());

    // console.log(message.is_tel_html());
    let tel = message.is_tel_html();
});

称呼我们就只判断一下是否输入,以及称呼的长度限制在15个字以内

# 三、提交数据

定义提交接口:./api/message
提交数据方式采用jQuery的ajax方式,搜索表单序列化

$(function(){
    //回顾一下面向对象
    let tel_parent = $('input[name=tel]').parent();
    let username_parent = $('input[name=username]').parent();
    let message = {
        //获取您的称呼的值
        username:function(){
            return document.getElementsByName('username')[0].value;
            // return $('input[name=username]').val();
        },
        tel:function(){
            return document.getElementsByName('tel')[0].value;
            // return $('input[name=tel]').val();
        },
        message:function(){
            return document.getElementsByName('message')[0].value;
            // return $('textarea[name=message]').val();
        },
        //合法性判断
        is_tel:function(){
            let tel = this.tel();
            if(!tel){
                return {
                    msg:'error',
                    data:'请输入您的电话号码'
                }
            }
            // console.log(tel);
            // return tel;
            let patter = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/;
            // console.log(patter.test(tel));
            if(patter.test(tel)){
                //正确的电话号码
                return {
                    msg:'ok',
                    data:tel
                }
            }else{
                return {
                    msg:'error',
                    data:'电话号码不正确'
                }
            }
        },
        //页面提示
        is_tel_html:function(){
            let is_tel = this.is_tel();
            // return is_tel;
            // console.log(is_tel);
            tel_parent.find('span').eq(1).remove();
            if(is_tel.msg == 'error'){
                let spanhtml = '<span style="color:red">'+ is_tel.data +'</span>';
                tel_parent.append(spanhtml);
            }
            return is_tel;
        },
        //监听电话号码的输入值
        input_is_tel_html:function(){
            /*
            let _this = this;
            $('input[name=tel]').on('input',function(){
                // console.log($(this).val());
                _this.is_tel_html();
            })
            */
            //箭头函数和匿名函数的区别我们会在后面讲解
            $('input[name=tel]').on('input',()=>{
                if($('input[name=tel]').val().length > 10 || tel_parent.find('span').eq(1).length){
                    this.is_tel_html();
                }
            });
        },
        //称呼合法性判断
        is_username:function(){
            let username = this.username();
            if(!username){
                return {
                    msg:'error',
                    data:'请输入您的称呼'
                }
            }
            if(username.length > 0 && username.length <= 15){
                return {
                    msg:'ok',
                    data:username
                }
            }else{
                return {
                    msg:'error',
                    data:'您的称呼不能超过15个字符'
                }
            }
        },
        //称呼页面提示
        is_username_html:function(){
            let is_username = this.is_username();
            // console.log(is_username);
            username_parent.find('span').eq(1).remove();
            if(is_username.msg == 'error'){
                //页面提示
                let spanhtml = '<span style="color:red">'+ is_username.data +'</span>';
                username_parent.append(spanhtml);
            }
            return is_username;
        },
        //监听称呼的输入值
        input_is_username_html:function(){
            $('input[name=username]').on('input',()=>{
                if($('input[name=username]').val().length > 15 || username_parent.find('span').eq(1).length){
                    this.is_username_html();
                }
            });
        },
    }

    //监听电话号码的输入值
    message.input_is_tel_html();
    //监听称呼的输入值
    message.input_is_username_html();

    $('.submit-div span').on('click',function(){
        // console.log(message.username());
        // console.log(message.tel());
        // console.log(message.message());

        // console.log(message.is_tel());

        // console.log(message.is_tel_html());

        let username = message.is_username_html();
        let tel = message.is_tel_html();
        console.log(username);
        console.log(tel);
        if(username.msg == 'ok' && tel.msg == 'ok'){
            // console.log('提交数据')
            _this = this;
            $.ajax({
                type: 'POST', //表单提交我们采用post , get方式数据量小也可以
                url: './api/message',//提交给服务器的接口地址,一般后端程序员给你一个接口(当然你可以自己写一个接口)
                //使用$.param()将对象形式的键值对转为 URL 地址的字符串键值对,可以更加稳定准确的传递表单内容
                data:$.param({ 
                    username : username.data,
                    tel : tel.data,
                    message : message.message(),  
                }),
                beforeSend:function(xhr){
                    $(_this).text('提交中...');
                },
                success: function (response, stutas, xhr) {
                    $(_this).text('发送')
                    console.log(response)
                },
            });
        }
    });
});

# ① 提交留言之后的页面处理

$('.submit-div span').on('click',function(){
    let username = message.is_username_html();
    let tel = message.is_tel_html();
    if(username.msg == 'ok' && tel.msg == 'ok'){
        console.log($(this).text());
        let _this = this;
        $.ajax({
            type: 'POST', //表单提交我们采用post , get方式数据量小也可以
            url: './api/message',//提交给服务器的接口地址,一般后端程序员给你一个接口(当然你可以自己写一个接口)
            data:$.param({ 
                username : username.data,
                tel:  tel.data,
                message:message.message()
            }),
            beforeSend:function(xhr){
                // console.log($(this).text());
                //$(this)指向的是最近调用它的jquery对象,$(this)就是表示ajax对象了
                // $('.submit-div span').text('提交中...');
                $(_this).text('提交中...');
            },
            success: function (response, stutas, xhr) {
                console.log(response)
                $('input,textarea').val('');//清空输入内容
                $(_this).text('您的留言已提交成功,我们会尽快和您联系!');
                setTimeout(() => {
                    $(_this).text('发送');
                }, 5000);
            },
            error:function(err){
                $(_this).text('发送');
            },
            complete:function(){
                // $(_this).text('发送');
            }
            });
    }
});

# 四、nodejs服务器端处理留言

说明:由于我们目前没有学习数据库相关的内容(常用的数据库如:MySQL,Oracle,MongoDB,SQL Server等等),因此我们目前可以将用户提交的留言以json文件的形式,存放在服务器上。
留言内容 /data/message.json 文件数据格式

{
    "data":[
        {"username":"迪丽热巴","tel":"13545375400","message":"请问贵公司还招人吗?","id":1},
        {"username":"古力娜扎","tel":"13854856512","message":"","id":2},
        {"username":"梁咏琪","tel":"17858584587","message":"我有个业务想和贵公司合作一下,请及时联系我","id":3},
        {"username":"刘德华","tel":"13945444477","message":"","id":4},
        {"username":"古天乐","tel":"13954854477","message":"","id":5}
    ],
    "total":5,
    "currentId":5
}

# ① 首次留言处理

//留言写入json文件
function addmessage(data){
    data = querystring.parse(data.toString());
    console.log(data);
    //创建一个文件夹data
    if(!fs.existsSync('./data')){//不存在则创建
        fs.mkdirSync('./data');
    }
    //判断message.json文件是否存在,存在说明之前写入过了,先读一下
    /*
    let flag = false;
    console.log(fs.existsSync('./data/message.json'));
    if(fs.existsSync('./data/message.json')) flag = true;
    */
    let flag = fs.existsSync('./data/message.json') ? true : false;
    if(flag){
        //读一下再写
        console.log('读一下再写');
        readmessage('./data/message.json',data);
    }else{
        //第一次创建数据
        let ms = data;
        ms.id = 1;
        //加入时间,所在地等等
        ms.timestamp = new Date().getTime();
        console.log(ms);
        let o = {};
        o.data = [];
        o.data.push(ms);
        o.total = 1;
        o.currentId = 1;
        console.log(o);
        //写入内容,同步异步promise,以及可写流
        fs.writeFile('./data/message.json',JSON.stringify(o),(err)=>{
            if(err) throw err;
            console.log('写入成功');
        });
    }
}

时间戳参考:时间戳和时间的转化

# ② 存储用户的留言

//读一下json文件在做处理
function readmessage(path,data) {
    //读取内容,同步异步promise,以及可写流
    fs.readFile(path, {
        encoding: 'utf-8'
    }, (err, oldmessage) => {
        if (err) throw err;
        oldmessage = JSON.parse(oldmessage);
        // console.log(oldmessage)
        // console.log(data);
        //处理数据
        data.id = oldmessage.currentId + 1;
        //加入时间,所在地等等
        data.timestamp = new Date().getTime();
        //新的留言放进数组
        oldmessage.data.push(data);
        //处理大对象
        oldmessage.total = oldmessage.data.length;
        oldmessage.currentId = data.id;
        console.log(oldmessage);
        //写入内容,同步异步promise,以及可写流
        fs.writeFile(path, JSON.stringify(oldmessage), (err) => {
            if (err) throw err;
            console.log('写入成功')
        });
    });
}

关于一般返回的接口规范,我们可以参考高德地图:https://lbs.amap.com/api/webservice/summary

# 五、案例最终代码展示

# ① contact.html页面

引入jQuery,新建: /static/js/contact.jquery.js,删除页面底部之前写的js文件

<script src="./static/js/jquery.1.11.3.min.js"></script>
<script src="./static/js/contact.jquery.js"></script>

补充一下发送按钮的样式,鼠标放上去有个手型,/static/css/style.css

.page-message .message .submit-div span{
    color: #ffffff;
    font-size: 24px;
    border: 3px solid #ffffff;
    padding: 15px 70px;
    cursor: pointer;//补充上
}

/static/js/contact.jquery.js

$(function(){
    //回顾一下面向对象
    let tel_parent = $('input[name=tel]').parent();
    let username_parent = $('input[name=username]').parent();
    let message = {
        //获取您的称呼的值
        username:function(){
            return document.getElementsByName('username')[0].value;
            // return $('input[name=username]').val();
        },
        tel:function(){
            return document.getElementsByName('tel')[0].value;
            // return $('input[name=tel]').val();
        },
        message:function(){
            return document.getElementsByName('message')[0].value;
            // return $('textarea[name=message]').val();
        },
        //合法性判断
        is_tel:function(){
            let tel = this.tel();
            if(!tel){
                return {
                    msg:'error',
                    data:'请输入您的电话号码'
                }
            }
            // console.log(tel);
            // return tel;
            let patter = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199)\d{8}$/;
            // console.log(patter.test(tel));
            if(patter.test(tel)){
                //正确的电话号码
                return {
                    msg:'ok',
                    data:tel
                }
            }else{
                return {
                    msg:'error',
                    data:'电话号码不正确'
                }
            }
        },
        //页面提示
        is_tel_html:function(){
            let is_tel = this.is_tel();
            // return is_tel;
            // console.log(is_tel);
            tel_parent.find('span').eq(1).remove();
            if(is_tel.msg == 'error'){
               //页面提示
               let spanhtml = '<span style="color:red">'+ is_tel.data +'</span>';
               tel_parent.append(spanhtml);
            }
            return is_tel;
        },
        //监听电话号码的输入值
        input_is_tel_html:function(){
            /*
            let _this = this;
            $('input[name=tel]').on('input',function(){
                // console.log($(this).val());
                _this.is_tel_html();
            })
            */
            //箭头函数和匿名函数的区别我们会在后面讲解
            $('input[name=tel]').on('input',()=>{
                if($('input[name=tel]').val().length > 10 || tel_parent.find('span').eq(1).length){
                    this.is_tel_html();
                }
                
            });
        },
        //称呼合法性判断
        is_username:function(){
            let username = this.username();
            if(!username){
               return {
                    msg:'error',
                    data:'请输入您的称呼'
                }
            }
            if(username.length > 0 && username.length <= 15){
                //正确的称呼
               return {
                    msg:'ok',
                    data:username
                }
            }else{
                return {
                    msg:'error',
                    data:'您的称呼不能超过15个字符'
                }
            }
        },
        //称呼页面提示
        is_username_html:function(){
            let is_username = this.is_username();
            username_parent.find('span').eq(1).remove();
            if(is_username.msg == 'error'){
               //页面提示
               let spanhtml = '<span style="color:red">'+ is_username.data +'</span>';
               username_parent.append(spanhtml);
            }
            return is_username;
        },
        //监听电话号码的输入值
        input_is_username_html:function(){
            $('input[name=username]').on('input',()=>{
                if($('input[name=username]').val().length > 15 || username_parent.find('span').eq(1).length){
                    this.is_username_html();
               }
                
            });
        },
    }

    //监听电话号码的输入值
    message.input_is_tel_html();
    //监听称呼的输入值
    message.input_is_username_html();

    $('.submit-div span').on('click',function(){
        let username = message.is_username_html();
        let tel = message.is_tel_html();
        console.log(username);
        console.log(tel);
        if(username.msg == 'ok' && tel.msg == 'ok'){
            // console.log($(this).text());
            let _this = this;
            $.ajax({
                type: 'POST', //表单提交我们采用post , get方式数据量小也可以
                url: './api/message',//提交给服务器的接口地址,一般后端程序员给你一个接口(当然你可以自己写一个接口)
                //使用$.param()将对象形式的键值对转为 URL 地址的字符串键值对,可以更加稳定准确的传递表单内容
                data:$.param({ 
                    username : username.data,
                    tel:  tel.data,
                    message:message.message()
                }),
                beforeSend:function(xhr){
                    // console.log($(_this).text());
                    //$(this)指向的是最近调用它的jquery对象,$(this)就是表示ajax对象了
                    // $('.submit-div span').text('提交中...');
                    $(_this).text('提交中...');
                },
                success: function (response, stutas, xhr) {
                    console.log(response);
                    $(_this).text('您的留言已提交成功,我们会尽快和您联系!');
                    setTimeout(()=>{
                        $(_this).text('发送');
                    },5000);
                },
                error:function(err){
                    $(_this).text('发送');
                },
                complete:function(){
                    $('input,textarea').val('');
                }
            });
        }
    });
});

# ② 服务器端app.js代码

新建: /app.js文件,修改 /package.json的调试命令

// package.json的调试命令修改启动文件
"scripts": {
    "start": "nodemon app.js", //启动app.js文件
    "start:node": "node index.js",
    "dev": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
},

/app.js文件代码

let http = require('http');
let fs = require('fs');
let $url = require('url');
let querystring = require('querystring');
const server = http.createServer((request, response) => {
    if (request.method == 'GET') {
        let url = request.url;
        if (url.indexOf('/api/') == -1) {
            if (url.indexOf('.json') > -1) {
                return errorhtml(response);
            }
            fs.readFile(`./${url}`, (err, data) => {
                if (err) {
                    errorhtml(response);
                } else {
                    response.writeHead(200);
                    response.end(data);
                }
            });
        }
    } else if (request.method == 'POST') {
        let result = [];
        request.on('data', buffer => {
            result.push(buffer);
        });
        request.on('end', () => {
            let data = Buffer.concat(result);
            response.writeHead(200);
            response.end('ok');
            //留言写入json文件,自定义文件夹名字,和文件名
            let paths = {
                dir:'./data',
                file: './data/message.json'
                // dir:'./Appdata',
                // file: './Appdata/data.json'
            }
            addmessage(data,paths);
        });

    }
});

server.listen(8888, '127.0.0.1', () => {
    console.log('服务器已启动');
});

const errorhtml = (response)=>{
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    response.writeHead(200);
    let msg = {
        status:404,
        info:"error",
        data:'not found'
    }
    response.end(JSON.stringify(msg));
}

//留言写入json文件
function addmessage(data,paths) {
    console.log(paths);
    data = querystring.parse(data.toString());
    if (!fs.existsSync(paths.dir)) {
        fs.mkdirSync(paths.dir);
    };
    let flag = fs.existsSync(paths.file);
    if (flag) {
        //存在先读取一下再写
        readmessage(paths.file, data)
    } else {
        //不存在,首次直接写
        let ms = data;
        ms.id = 1;
        //加入时间,所在地等等
        ms.timestamp = new Date().getTime();
        let o = {};
        o.data = [];
        o.data.push(ms);
        o.total = 1;
        o.currentId = 1;
        writemessage(paths.file, o);
    }
}

//存在先读取一下再写
function readmessage(path, data) {
    fs.readFile(path, {
        flag: 'r',
        encoding: 'utf-8',
    }, (err, oldmessage) => {
        if (err) throw err;
        oldmessage = JSON.parse(oldmessage);
        //处理留言数据
        data.id = oldmessage.currentId + 1;
        //加入时间,所在地等等
        data.timestamp = new Date().getTime();
        //大对象
        oldmessage.data.push(data);
        oldmessage.total = oldmessage.data.length;
        oldmessage.currentId = data.id;
        writemessage(path, oldmessage);
    })
}

//写入数据
function writemessage(path, data) {
    fs.writeFile(path, JSON.stringify(data), (err) => {
        if (err) throw err;
        console.log('写入成功')
    });
}
更新时间: 2024年1月12日星期五上午10点54分