Node.js问答系统流程

589 阅读3分钟

1.建立文件夹

2.引入js模块

3.在html中引入需要用到的bootstrap.css、bootstrap.js、jQuery.js、jQuery-cookie.js

4.注册用户页面

5.登录用户页面

6.提问页面

7.我来回答

8.上传头像

  • 先建立一个问答系统文件夹,在这个文件夹下再建立一个public文件夹来存放前端代码,初始化npm,建立一个index.js文件, 将固定的模块引入和post请求的固定代码写好。 7.PNG
  • 在public文件夹建立一个index.html文件,将页面布局调整。
<nav class="navbar navbar-inverse navbar-orange" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse"
                    data-target="#example-navbar-collapse">
                    <span class="sr-only">切换导航</span>
                    <!-- 字体图标  -->
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">问答系统</a>
            </div>
            <div class="collapse navbar-collapse" id="example-navbar-collapse">
                <ul class="nav navbar-nav">
                    <li ><a href="#">
                        <span class="glyphicon glyphicon-search"></span>
                        提问</a></li>
                    <li id="regist"><a href="login.html">
                        <span class="glyphicon glyphicon-plus"></span>
                        登录</a></li>
                    <li id="dropdown" class="dropdown hide">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            共和国小青年 <b class="caret"></b>
                        </a>
                        <ul class="dropdown-menu">
                            <li><a href="#">上传头像</a></li>
                            <li><a onclick='logout()'>退出登录</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

8.PNG 在script里判断是否已登录,若已登录则如下图显示出账号名,隐藏登录按钮,若无账号登录则如上图显示登录按钮,隐藏账号名。

     var cookieName = $.cookie('loginName')
    if(cookieName){
        // 如果有
        $('#regist').addClass('hide')
        $('#dropdown').removeClass('hide').addClass('show')
    }
    else{
        $('#regist').addClass('show').removeClass('hide')
        $('#dropdown').removeClass('show').addClass('hide')
 
    }

9.PNG 给退出登录按钮设置一个点击事件,退回首页

function logout(){
            $.removeCookie('loginName')
            window.location.href= '/'
        }
  • 设置注册页面,页面基本导航复制首页,做出修改即可。在页面内写入input账号密码以及重置密码输入框
<div class="container">
        <div class="">
            <label for="">账号:</label>
            <input type="text" id="user" class="form-control">
        </div>
        <br>
        <div class="">
            <label for="">密码:</label>
            <input type="text" id="password" class="form-control">
        </div>
        <br>
        <div class="">
            <label for="">重复密码:</label>
            <input type="text" id="again" class="form-control">
        </div>
        <br><br>

        <button onclick="clickRegist()" class="form-control btn-success">注册</button>
    </div>

给注册按钮设置一个点击事件,点击注册之后获取到输入的账号密码以及重置的密码,账号密码不能少于六位字符,且密码和重置密码要一样,var一个xhr请求,使用post请求发送给后端,同时接收到后端返回的数据,后端返回的是“注册成功”,则跳转到登录页面

function clickRegist(){
            var user = $("#user").val()
            var psw = $('#password').val()
            var again = $("#again").val()
            if(user.length < 6){
                alert('账号不能少于6位')
                return
            }
            if(psw.length <  6){
                alert('密码不能少于6位')
                return
            }
            if(psw != again){
                alert('两次密码不一致')
                return
            }

            var xhr = new XMLHttpRequest()
            xhr.open('post' , '/regist')
            xhr.setRequestHeader("Content-Type" , 'application/x-www-form-urlencoded')
            xhr.send(`account=${user}&password=${psw}`)

            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    if(xhr.responseText == '注册成功'){
                        location.href = 'login.html'
                    }
                    else{
                        alert(xhr.responseText)
                    }
                }
            }
        }
  • 在后端判断是否存在一个.txt文件,不存在就写入一个allUser.txt文件,输入内容为数组allUserArray内容,若存在就读取allUser.txt内的内容,赋值给数组allUserArray

  • JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串,而JSON.parse()可以将JSON字符串转为一个对象。两者区别

var allUserArray = []
fs.access('allUser.txt', function(noExists){
    if(noExists){
        fs.writeFileSync('allUser.txt' , JSON.stringify(allUserArray))
    }
    else{
        fs.readFile("allUser.txt", 'utf-8' , function(err , data){
            allUserArray = JSON.parse(data)
        })
    }
})
  • 设置后端注册代码,根据前端设的post请求,/regist接口接收前端发送的内容,将前端发送的内容与allUserArray内的数据进行循环对比,账号重复即是注册过,不重复就注册成功,同时将传过来的账号密码推入allUserArray数组第一个,再将allUserArray写入allUser.txt文本内,返回给前端一个“注册成功”的消息。
web.post('/regist',function(req ,res){
    console.log(req.body);
    for(var index = 0 ; index < allUserArray.length ; index ++){
        var user = allUserArray[index]
        if(user.account == req.body.account){
            res.send('该账号已经注册')
            return
        }
    }
    // 刚注册的人用的是默认头像
    req.body.header = './header/defualt.png'
    allUserArray.push(req.body)
    fs.writeFile('allUser.txt' , JSON.stringify(allUserArray) , function(err){
        res.send('注册成功')
    })
    
})
  • 设置登录页面,页面基本导航复制首页,账号密码输入框根据注册页面做出修改即可。

10.PNG 给登录按钮设置一个点击事件,点击登录之后获取到输入的账号密码,账号密码不能少于六位字符,var一个xhr请求,使用post请求发送给后端,同时接收到后端返回的数据,后端返回的是“登录成功”,则跳转到首页。代码根据注册代码调整。

  • 设置后端登录代码,根据前端设的post请求,/login接口接收前端发送的内容,将前端发送的内容与allUSerArray内的数据进行循环对比,若账号已存在就判断密码是否相同,密码也相同,将此账号作为cookieName返回给前端,同时返回“登录成功”;若账号相同密码不相同返回前端“登录失败,密码错误”。若账号不相同则返回前端“登录失败,该账号尚未注册”
web.post('/login',function(req ,res){
    console.log(req.body);
    for(var index = 0 ; index < allUserArray.length ; index ++){
        var user = allUserArray[index]
        if(user.account == req.body.account){
            if(user.password == req.body.password){

                res.cookie("loginName" , req.body.account)
                res.send("登录成功")
                
            }
            else{
                res.send("登录失败,密码错误")
            }
            return
        }
        
    }
    res.send("登录失败,该账号尚未注册")
})
  • 在后端新建一个所有问题的数组allQuestionArray,判断是否存在question.txt,不存在就新建一个文本,并把allQuestionArray写入question.txt内,若存在就读取question.txt内的信息,将新的数据赋值给allQuestionArray
var allQuestionArray = []
fs.access('question.txt',function(noExists){
    if(noExists){
        fs.writeFileSync('question.txt' , JSON.stringify(allQuestionArray))
    }
    else{
        fs.readFile('question.txt' , 'utf-8' , function(err , data){
            allQuestionArray = JSON.parse(data)
        })
    }
})
  • 在首页给提问按钮设置一个点击事件,若已登录跳转到提问页面,若未登录则跳转到登录界面。
 function toQuestion() {
            if (cookieName) {
                location.href = 'question.html'
            }
            else {
                window.location.href = 'login.html'
            }
        }
  • 提问页面基础导航复制前面登录界面,输入框修改为textarea框,其余简单修改即可。给提交问题按钮添加一个点击事件,获取到输入内容,内容不能为空,使用个体请求将内容以及用户名发送给后端,同时获取到后端返回的数据“提问成功”,页面跳转回首页。
function clickToQuestion(){
            var question = $("textarea").val()
           
            if(question.length == 0){
                alert('问题不能为空')
                return
            }
        
            var xhr = new XMLHttpRequest()
            xhr.open('post' , '/question')
            xhr.setRequestHeader("Content-Type" , 'application/x-www-form-urlencoded')
            xhr.send(`question=${question}&user=${$.cookie("loginName")}`)

            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    if(xhr.responseText == '提问成功'){
                        location.href = './index.html'
                    }
                    else{
                        alert(xhr.responseText)
                    }
                }
            }
        }
  • 在后端设置提问请求,从所有已经注册的人当中找到和现在提问人一样的人,将所有的信息汇总在一个变量question里面,getTime()函数是获取当前时刻的时间,在index.js写入相关代码
    function getTime(){
    var time = new Date()
    time = time.toLocaleString()
    // 把/转化成-
    time = time.replace(/\//g, '-')
    time = time.replace('上午' ,'')
    time = time.replace('下午' ,'')
    return time
web.post('/question',function(req ,res){
    console.log(req.body);
    var register = allUserArray.filter(function( el ){
        return el.account == req.body.user
    })
    var question = {
        // 提问人
        asker:req.body.user ,
        // 提问内容
        content:req.body.question , 
        // 提问时间
        time:getTime() ,
        // 提问人头像
        header: register[0].header ,
        // 回答列表
        answerList:[]
    }

    allQuestionArray.unshift(question)
    fs.writeFile('question.txt' , JSON.stringify(allQuestionArray) , function(err){
        res.send('提问成功')
    })
    
})

将最新的问题放入到数组allQuestionArray当中的第一个位置,同时将数组内容写入question.txt文本内,给前端返回一个“提问成功”。

  • 在首页上面显示出提的问题,需要在首页的script内写入一个函数,使用get请求,接口位getQuestion,给后端传递空的信息,同时接受后端返回的所有问题的数组,将后端返回的数组值赋值给新定义的一个变量questionList,通过循环questionList将每一个问题写入html内
function getAllQuestion() {
            var xhr = new XMLHttpRequest()
            xhr.open('get', '/getQuestion')
            xhr.send()

            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {

                    var questionList = JSON.parse(xhr.responseText)
                    // console.log(questionList);
                    var html = ''
                    for (var index = 0; index < questionList.length; index++) {
                        var question = questionList[index]
                        html += `
                        <div class="panel panel-danger getMargin">
                            <div class="panel-heading">
                                <h3 class="panel-title">
                                    ${question.asker} 
                                    <img class="header" src='${question.header}'>
                                    ${question.time}
                                </h3>
                            </div>
                            <div class="panel-body questionPanel">
                                <div class="questionContent">  ${question.content} </div>
                                <button onclick="clickToAnswer(${index})" class="btn btn-success answerBtn">我来回答</button>
                                <div class='answerPanel'></div>
                            </div>
                        </div>
                        
                        `
                     }
                    $('#list').html(html)
                                      
                }
            }
        }
        getAllQuestion()

11.PNG

在后端接收一下前端提问的请求,将含有所有问题的数组返回给前端。

web.get('/getQuestion',function(req ,res){
    res.send(allQuestionArray)
})
  • 我来回答:在bootstrap库里找出模态框模板放在html里,在首页给“我来回答”按钮添加一个点击事件,判断是否是已登录状态,已登录就弹出模态框,同时保留问题的索引;未登录状态就跳转到登录界面。
 function clickToAnswer(index) {

            if (cookieName) {
                // 弹出模态框
                // alert()
                $('#myModal').modal('show')
                // 保留问题的索引
                $.cookie('index', index)
            }
            else {
                location.href = 'login.html'
            }
        }

给弹出模态框的回答按钮添加一个点击事件,先获取到留言框里的内容,判断留言框是否为空,通过post请求给后端发送回答的内容、回答人、回答的是哪一个问题(此问题的索引),若模态框的内容不为空,隐藏模态框,清空数据内容。

function sendAnswer() {
            var content = $('textarea').val()
            if (content.length == 0) {
                alert('内容不能为空')
                return
            }
            var xhr = new XMLHttpRequest()
            xhr.open('post', '/sendAnswer')
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
   xhr.send(`content=${content}&index=${$.cookie('index')}&answerMan=${cookieName}`)

            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    // console.log(xhr.responseText);
                    // 隐藏模态框
                    $('#myModal').modal('hide')
                    // 清空输入内容
                    $('textarea').val("")
                    // 获取最新内容
                    location.href = '/'
                }
            }
        }
  • 在后端接收到回答请求,从所有已经注册的人当中找到和现在回答人一样的人,将所有的信息汇总在一个变量answer里面,getTime()函数是获取当前时刻的时间,在index.js写入相关代码

将最新的答案放入到数组allQuestionArray当中指定问题所有回答中的第一个位置,同时将数组内容写入question.txt文本内,给前端返回一个“回答成功”。

web.post('/sendAnswer',function(req ,res){
    console.log(req.body);
    var findUser = allUserArray.filter(function(el){
        return el.account == req.body.answerMan
    })
    var answer = {
        // 回答人
        answerMan:req.body.answerMan, 
        // 回答内容
        content:req.body.content , 
        // 时间
        time:getTime(),
        // 头像
        header:findUser[0].header
    }
    // 将最新的答案 放入到指定问题当中所有的回答的第一个位置
    allQuestionArray[req.body.index].answerList.unshift(answer)

    fs.writeFile('question.txt' , JSON.stringify(allQuestionArray),function(){
        res.send("回答成功")
    })
   
})

在前端页面内显示出回答的内容,需要在前面设置的getAllQuestion()函数内添加上回答的内容,在问题的for循环内加上回答的for循环,将answerHtml追加到页面中。

 var answerHtml = ''
 for (var a = 0; a < question.answerList.length; a++) {
     var answer = question.answerList[a]
     console.log(answer);
     answerHtml += `
         <div class="panel panel-success dropMargin " style="text-align:right">
             <div class="panel-heading">
                 <h3 class="panel-title">
                     ${answer.answerMan} 
                     <img class="header" src='${answer.header}'>
                     ${answer.time}
                 </h3>
             </div>
             <div class="panel-body questionPanel">
                 <div class="questionContent">  ${answer.content} </div>
             </div>
         </div>     
 `
 }
 // 把问题和答案拼接在一起
   html += answerHtml
    $('#list').html(html)
                }
            }
        }
    getAllQuestion()
  • 上传头像:前端给上传头像按钮添加一个点击事件
function getHeader(){
            var xhr = new XMLHttpRequest()
            xhr.open('get' , '/getHeader')
            xhr.send()
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4){
                    $('#header').attr('src' , xhr.responseText)
                }
            }
        }

后端对头像信息进行一些操作,分割图片、获取图片格式、拼接最新的图片地址

var headerName = ''
var diskStorage = multer.diskStorage({
    destination:function(req ,file , callback){
        callback(null , './public')
    },
    filename(req , file ,callback){
        // 分割图片
        var splitArray = file.originalname.split('.')
        // 获取图片格式
        var type = splitArray[splitArray.length - 1]
        // 拼接最新的图片地址
        headerName = './header/' +  req.cookies.loginName + '.' + type

        console.log(headerName);
        // 从所有用户当中找账号跟当前上传头像的账号一样的用户的索引
        var index = allUserArray.findIndex(function(el){
            return el.account == req.cookies.loginName
        })
        // 设置用户头像为最新头像
        allUserArray[index].header = headerName
        fs.writeFile("allUser.txt" , JSON.stringify(allUserArray), function(err){

        })
        callback(null , headerName)
    }
})

将所有跟上传头像人相同的提问人和回答人的头像更改为刚刚上传的头像。

var headerConfig = multer({storage:diskStorage})
web.post('/upload' , headerConfig.single("photo"),function(req ,res){
    
    allQuestionArray.forEach(function(el){
        // 提问人和上传头像的人是同一个人
        if(el.asker == req.cookies.loginName){
            el.header = headerName
        }
        if(el.answerList){
            el.answerList.forEach(ans => {
                // 回答人和上传头像的人是一样
                if(ans.answerMan == req.cookies.loginName){
                    ans.header= headerName
                }
            });
        }
    })

    fs.writeFile('question.txt' , JSON.stringify(allQuestionArray) ,function(){
        res.send("<script>location.href='/'</script>")
    })
   
})

结果图:

12.PNG