标签: Web

  • Web 课程设计

    本文参考1 WebSocket之仿QQWeb即时聊天系统(上)

    本文参考2 WebSocket之仿QQWeb即时聊天系统(下)

    本文参考3 Node.js 教程_菜鸟

    本文参考4 socket.io 概述

    摇骰子摇到了1,所以选择了仿QQWeb即时聊天系统

    功能要求

    1. 实现Web的点对点即时的文本消息聊天功能。
    2. 实现Web的表情的发送、接收和显示功能。
    3. 实现Web的图片的发送、接收和显示功能。
    4. 实现本地消息的存储,在离线的时候也能加载和查看历史消息;
    5. 要求使用WebSocket

    功能分析

    就是能发 文本、表情、图片。点对点。

    还使用了WebSocket

    WebSocket

    WebSocket

    WebSocket 是独立的、创建在 TCP 上的协议。

    Websocket 通过HTTP/1.1 协议的101状态码进行握手。

    为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。

    WebSocket的出现,使得浏览器具备了实时双向通信的能力。

    1. WebSocket可以在浏览器里使用
    2. 支持双向通信
    3. 使用很简单

    WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

    现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

    HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

    浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

    当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

    WebSocket是一个很棒的概念,我单独拿出来写。

    WebSocket

    创建 对象

    以下 API 用于创建 WebSocket 对象。

    var Socket = new WebSocket(url, [protocol] );
    

    以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。


    属性

    以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

    属性描述
    Socket.readyState只读属性 readyState 表示连接状态,可以是以下值:0 – 表示连接尚未建立。1 – 表示连接已建立,可以进行通信。2 – 表示连接正在进行关闭。3 – 表示连接已经关闭或者连接不能打开。
    Socket.bufferedAmount只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

    WebSocket 事件

    以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

    事件事件处理程序描述
    openSocket.onopen连接建立时触发
    messageSocket.onmessage客户端接收服务端数据时触发
    errorSocket.onerror通信发生错误时触发
    closeSocket.onclose连接关闭时触发

    WebSocket 方法

    以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

    方法描述
    Socket.send()使用连接发送数据
    Socket.close()关闭连接

    为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

    一些准备工作

    根据大佬RongLin02的博客做一些准备工作。

    首先是对于WebSocket的了解。以上已经介绍了。

    然后是安装Node.js,你也可以进入 官网 下载。学习页面。

    NPM的安装,在上一步极有可能已经附带完成。

    socket.io

    是一个WebSocket库,用起来很方便。

    库所以只需要引入而不需要下载。

    在项目根目录 右键–在集成终端中打开

    执行以下代码:

    npm install jquery
    npm install socketio
    npm install express
    

    会生成一个node_modules文件夹用来存放安装好的模块,一些配置信息会生成在package-lock.jsonpackage.json文件中。

    使用方法

    中文文档

    在中文文档里面发现第一个文档 socket.io 快速入门教程——聊天应用 就和我们的任务很像。

    按照其流程一步一步来。

    常用方法

    socket.emit('A',value);
    

    这个是发送方法,A是名字,叫什么名字都行,value就是要发送的值,可以是服务器给客户端发送,也可以是客户端给服务器发送,类型也可以是一个对象。

    socket.on('A',function(data){
            console.log(data);
        });
    

    有发送就有接受,服务器或者客户端会监听,如果有终端通过 名字 A 发送,就会获取到发送的数据,并且把value存到data中,所以说上边的那个data的值就是emitvalue

    emiton一对一。

    运行方法

    ​ 根目录下 右键-终端运行 输入 node index.js

    或 编译器左边项目目录右键打开于终端。在编译器下方的窗口输入指令。

    REPL 命令

    • ctrl + c – 退出当前终端。
    • ctrl + c 按下两次 – 退出 Node REPL。
    • ctrl + d – 退出 Node REPL.
    • 向上/向下 键 – 查看输入的历史命令
    • tab 键 – 列出当前命令

    学习过程

    1. 服务器端的编写

    socket.io 快速入门教程——聊天应用

    Node.js Express 框架

    创建服务器

    使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定端口3000 。 函数通过 request, response 参数来接收和响应数据。

    在项目根目录 新建一个 index.js 文件来创建应用。

    var app = require('express')();
    var http = require('http').Server(app);
    
    app.get('/', function(req, res){
        console.log("主页 POST 请求");
        res.send('<h1>Hello world</h1>');
    });
    
    
    var server = app.listen(3000, function () {
    
        var host = server.address().address
        var port = server.address().port
    
        console.log("访问地址为 http://%s:%s", host, port)
    
    })
    

    这段代码作用如下:

    1. Express 初始化 app 作为 HTTP 服务器的回调函数 (见第 2 行)。
    2. 定义了一个路由/来处理首页访问。在终端输出"主页 POST 请求",给客户端返回'<h1>Hello world</h1>'
    3. 使 http 服务器监听端口 3000。也就是说这个代码只对端口3000有效。

    路由/其实是域名后面的东西。如果访问 localhost:3000/,因为 域名后面只有“/”,所以匹配了这个路由。

    运行node index.js

    2. Node.js

    模块

    引入模块

    Node.js 中,引入一个模块非常简单。

    创建 hello.js 文件,代码如下:

    //hello.js
    exports.world = function() {
      console.log('Hello World');
    }
    //world模块就暴露了,调用见下
    

    创建一个 main.js 文件并引入 hello 模块,代码如下:

    //main.js
    var hello = require('./hello');//引入
    hello.world();//调用
    

    以上实例中,代码 require('./hello') 引入了当前目录下的 hello.js 文件(./ 为当前目录,node.js 默认后缀为 js)。

    Node.js 提供了 exportsrequire 两个对象。

    exports 是模块公开的接口。

    require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

    有时候我们只是想把一个对象封装到模块中,格式如下:

    module.exports = function() {
      // ...
    }
    

    例如:

    //hello.js 
    function Hello() { 
        var name; 
        this.setName = function(thyName) { 
            name = thyName; 
        }; 
        this.sayHello = function() { 
            console.log('Hello ' + name); 
        }; 
    }; 
    module.exports = Hello;        //暴露接口
    

    这样就可以直接获得这个对象了:

    //main.js 
    var Hello = require('./hello'); 
    hello = new Hello(); 
    hello.setName('BYVoid'); 
    hello.sayHello(); 
    

    Node.js 中自带了一个叫做 http 的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

    这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。


    模块的加载

    在路径 Y 下执行 require(X) 语句执行顺序:

    1. 如果 X 是内置模块
       a. 返回内置模块
       b. 停止执行
    2. 如果 X 以 '/' 开头
       a. 设置 Y 为文件根路径
    3. 如果 X 以 './' 或 '/' or '../' 开头
       a. LOAD_AS_FILE(Y + X)
       b. LOAD_AS_DIRECTORY(Y + X)
    4. LOAD_NODE_MODULES(X, dirname(Y))
    5. 抛出异常 "not found"
    
    LOAD_AS_FILE(X)                    //作为文件加载
    1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。
    2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。
    3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。
    4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。
    
    LOAD_INDEX(X)                    //加载索引
    1. 如果 X/index.js 是一个文件,  将 X/index.js 作为 JavaScript 文本载入并停止执行。
    2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。
    3. 如果 X/index.node 是一个文件,  将 X/index.node 作为二进制插件载入并停止执行。
    
    LOAD_AS_DIRECTORY(X)             //作为目录加载
    1. 如果 X/package.json 是一个文件,
       a. 解析 X/package.json, 并查找 "main" 字段。
       b. let M = X + (json main 字段)
       c. LOAD_AS_FILE(M)
       d. LOAD_INDEX(M)
    2. LOAD_INDEX(X)
    
    LOAD_NODE_MODULES(X, START)        //加载节点模块
    1. let DIRS=NODE_MODULES_PATHS(START)
    2. for each DIR in DIRS:
       a. LOAD_AS_FILE(DIR/X)
       b. LOAD_AS_DIRECTORY(DIR/X)
    
    NODE_MODULES_PATHS(START)        //节点模块路径
    1. let PARTS = path split(START)
    2. let I = count of PARTS - 1
    3. let DIRS = []
    4. while I >= 0,
       a. if PARTS[I] = "node_modules" CONTINUE
       b. DIR = path join(PARTS[0 .. I] + "node_modules")
       c. DIRS = DIRS + DIR
       d. let I = I - 1
    5. return DIRS
    


    回调函数

    回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。


    函数

    一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

    举例来说,你可以这样做:

    function say(word) {
      console.log(word);
    }
    
    function execute(someFunction, value) {
      someFunction(value);
    }
    
    execute(say, "Hello");
    

    匿名函数

    我们可以把一个函数作为变量传递。但是我们不一定要绕这个”先定义,再传递”的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:

    function execute(someFunction, value) {
      someFunction(value);
    }
    execute(function(word){ console.log(word) }, "Hello");
    

    我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

    用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。

    全局对象

    __filename

    __filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

    __dirname

    __dirname 表示当前执行脚本所在的目录。


    文件系统

    Node 导入文件系统模块(fs)语法如下所示:

    var fs = require("fs")
    

    Node.js 文件系统

    Node.js Web 模块

    使用 Node 创建 Web 服务器
    Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块,代码如下:

    var http = require('http');
    

    Node.js Express 框架

    Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

    使用 Express 可以快速地搭建一个完整功能的网站。

    Express 框架核心特性:

    • 可以设置中间件来响应 HTTP 请求。
    • 定义了路由表用于执行不同的 HTTP 请求动作。
    • 可以通过向模板传递参数来动态渲染 HTML 页面。
    请求和响应

    Express 应用使用回调函数的参数: requestresponse 对象来处理请求和响应的数据。

    app.get('/', function (req, res) {
       // --
    })
    

    requestresponse 对象的具体介绍:

    Request 对象 – request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。

    常见属性有:

    1. req.app:当callback为外部文件时,用req.app访问express的实例
    2. req.baseUrl:获取路由当前安装的URL路径
    3. req.body / req.cookies:获得「请求主体」/ Cookies
    4. req.fresh / req.stale:判断请求是否还「新鲜」
    5. req.hostname / req.ip:获取主机名和IP地址
    6. req.originalUrl:获取原始请求URL
    7. req.params:获取路由的parameters
    8. req.path:获取请求路径
    9. req.protocol:获取协议类型
    10. req.query:获取URL的查询参数串
    11. req.route:获取当前匹配的路由
    12. req.subdomains:获取子域名
    13. req.accepts():检查可接受的请求的文档类型
    14. req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
    15. req.get():获取指定的HTTP请求头
    16. req.is():判断请求头Content-Type的MIME类型

    Response 对象 – response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。

    常见属性有:

    1. res.app:同req.app一样
    2. res.append():追加指定HTTP头
    3. res.set()在res.append()后将重置之前设置的头
    4. res.cookie(name,value [,option]):设置Cookie
    5. opition: domain / expires / httpOnly / maxAge / path / secure / signed
    6. res.clearCookie():清除Cookie
    7. res.download():传送指定路径的文件
    8. res.get():返回指定的HTTP头
    9. res.json():传送JSON响应
    10. res.jsonp():传送JSONP响应
    11. res.location():只设置响应的Location HTTP头,不设置状态码或者close response
    12. res.redirect():设置响应的Location HTTP头,并且设置状态码302
    13. res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
    14. res.send():传送HTTP响应
    15. res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
    16. res.set():设置HTTP头,传入object可以一次设置多个头
    17. res.status():设置HTTP状态码
    18. res.type():设置Content-Type的MIME类型

    app.use
    app.use(path,callback)

    app.use(path,callback)中的callback既可以是router对象又可以是函数。

    app.get(path,callback)中的callback只能是函数。

    当一个路由有好多个子路由时用app.use(path,router)

    例子:

    http://localhost:3000/home/one

    http://localhost:3000/home/two

    http://localhost:3000/home/three

    路由/home后面有三个子路由紧紧跟随,分别是/one,/two,/three,如果使用app.get(),则要不断的重复,很麻烦,也不利用区分。

    我们可以创建一个router.js专门用来一个路由匹配多个子路由。

    var express = require('express')
    var router = express.Router()
    router.get("/",(req,res)=>{
        res.send("/")
    })
    router.get("/one",(req,res)=>{
        res.send("one")
    })
    router.get("/two",(req,res)=>{
        res.send("second")
    })
    router.get("/three",(req,res)=>{
        res.send("three")
    })
    module.exports = router;
    //module.exports把router暴露出去了。
    

    app.js中导入router.js

    var express = require('express')
    var router = require("./router")
    var app = express()
    
    app.use('/home',router) //router路由对象中的路由都会匹配到"/home"路由后面
    app.get('/about', function (req, res) {
      console.log(req.query)
      res.send('你好,我是 Express!')
    })
    app.listen(3000, function () {
      console.log('app is running at port 3000.')
    })
    

    app.use(express.static(‘public’));

    为了提供对静态资源文件(图片,css,js文件)的服务,请使用Express内置的中间函数express.static

    传递一个包含静态资源的目录给express.static中间件用于立即开始提供文件。 比如用以下代码来提供public目录下的图片、css文件和js文件:

    app.use(express.static('public'));
    

    如果前台想请求后台public目录下images/08.jpg静态的图片资源
    通过: http://localhost:3000/images/08.jpg

    通过多次使用express.static中间件来添加多个静态资源目录:

    app.use(express.static('public'));
    app.use(express.static('file'));
    

    Express将会按照你设置静态资源目录的顺序来查看静态资源文件。

    为了给静态资源文件创建一个虚拟的文件前缀(文件系统中不存在),可以使用express.static函数指定一个虚拟的静态目录,如下:

    app.use('/static', express.static('public'));
    

    现在你可以使用‘/static’作为前缀来加载public文件夹下的文件了。

    比如: http:// localhost:3000/static/image/kitten.jpg


    get/post

    get

    “读取“一个资源。比如Get到一个html文件。反复读取不应该对访问的数据有副作用。

    post

    例如,在页面里<form> 标签会定义一个表单。点击其中的submit元素会发出一个POST请求让服务器做一件事。这件事往往是有副作用的,不幂等的。

    post是一种提交。

    4. 客户端的编写

    socket.io 快速入门教程——聊天应用

    在上面的这个代码里面,我们对访问 localhost:3000 采用 res.send() 方法,让其返回一个网页。这样显然对于前端并不友好。

    替代的方法是新建一个 index.html 文件作为服务器响应。

    现在我们用 sendFile 来重构之前的回调:

    app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
    });
    

    __dirname指的是当前文件所在文件夹的绝对路径。

    index.html 内容如下:

    <!doctype html>
    <html>
      <head>
        <title>Socket.IO chat</title>  <!--网页名称-->
        <style>
          * { margin: 0; padding: 0; box-sizing: border-box; }
          body { font: 13px Helvetica, Arial; }
          form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
          form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
          form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
          #messages { list-style-type: none; margin: 0; padding: 0; }
          #messages li { padding: 5px 10px; }
          #messages li:nth-child(odd) { background: #eee; }
        </style>
      </head>
      <body>
        <ul id="messages"></ul>
        <form action="">
          <input id="m" autocomplete="off" /><button>Send</button>
        </form>
      </body>
    </html>
    

    1. margin 属性为“外边距”,指的是围绕在元素边框的空白区域;
    2. Padding 属性为“内边距”,定义元素边框与元素内容之间的空间。
    3. box-sizing 是设置盒子模型用的,他其实是设置容器最终的尺寸的计算方法,盒子的计算方法如下: width(宽度) + padding(内边距) + border(边框) = 元素实际宽度
      height(高度) + padding(内边距) + border(边框) = 元素实际高度 上面的计算方法是按照box-sizing:content-box来说的,根据这种设置办法是,先规定了显示区域的大小,在加上边框和内边距就是元素最终显示的大小 还有另外一种模式,就是box-sizing:border-box,他的使用方式就是,先规定了最终显示的区域大小,实际内容展示区域是根据最终区域大小减去边框和内边距,自适应比较强,有时候比较方便页面布局一些。
    4. font 规定文本的字体、字体尺寸、字体颜色。

    日后再补。

    5. 集成 Socket.IO

    Socket.IO 由两部分组成:

    • 一个服务端用于集成 (或挂载) 到 Node.JS HTTP 服务器: socket.io
    • 一个加载到浏览器中的客户端: socket.io-client

    开发环境下, socket.io 会自动提供客户端。

    Socket.IO 的核心理念就是允许发送、接收任意事件和任意数据。任意能被编码为 JSON 的对象都可以用于传输。

    6. jQuery

    $就是jQuery的别称。

    jQuery就是jQuery库提供的一个函数.(好像也不仅仅只是函数, 因为还有 $.ajax(options) 这样的使用,等同 jQuery.ajax(options))

    这个函数的作用是根据 () 里的参数进行查找和选择html文档中的元素, 函数作用之一就是GetElementByID的代替,但()内不仅可以是ID,还可以是各类选择器。

    比如:

    $(document)就是 选取整个文档对象。

    7. HTML

    ![img](Web 课程设计.assets/02A7DD95-22B4-4FB9-B994-DDB5393F7F03.jpg)

    • <!DOCTYPE html> 声明为 HTML5 文档
    • <html> 元素是 HTML 页面的根元素
    • <head> 元素包含了文档的元(meta)数据,如 <meta charset=“utf-8”> 定义网页编码格式为 utf-8
    • <title> 元素描述了文档的标题
    • <body> 元素包含了可见的页面内容
    • <h1> 元素定义一个大标题
    • <p> 元素定义一个段落

    学习过程中的代码

    前期的学习到这里结束。

    接下来放一段到此为止的代码。

    服务端:

    const { data } = require('jquery');
    const app = require('express')();
    const http = require('http').Server(app);
    const io = require('socket.io')(http);
    
    app.get('/', function(req, res){
        res.sendFile(__dirname + '/index.html');
        console.log("主页 get 请求");
    });
    
    
    io.on('connection', function(socket){
        console.log("一位用户连接");
        socket.on('chat message', function(msg){
            console.log('message: ' + msg);
            io.emit('chat message', msg);
        });
        socket.on('disconnect', function(){
            console.log('用户关闭连接');
        });
    });
    
    http.listen(3000, function(){
        console.log('http://127.0.0.1:3000');
    });
    

    客户端

    <!doctype html>
    <html>
    <head>
        <title>Socket.IO chat</title>
        <style>
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body { font: 13px Helvetica, Arial; }
            form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
            form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
            form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
            #messages { list-style-type: none; margin: 0; padding: 0; }
            #messages li { padding: 5px 10px; }
            #messages li:nth-child(odd) { background: #eee; }
        </style>
    </head>
    <body>
    <ul id="messages"></ul>
    <form action="">
        <input id="m" autocomplete="off" /><button>Send</button>
    </form>
    
    
    </body>
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-1.11.1.js" rel="external nofollow" ></script>
    <script>
        $(function () {
            var socket = io();
            $('form').submit(function(){
                socket.emit('chat message', $('#m').val());
                $('#m').val('');
                return false;
            });
            socket.on('chat message', function(msg){
                $('#messages').append($('<li>').text(msg));
            });
        });
    </script>
    </html>
    

    文件结构

    接下来正式开始。我将在上面代码的基础上修改。


    开始

    本地数据库

    建立数据库

    数据库建表

    index.js文件添加依赖及定义,现在定义部分如下

    const { data } = require('jquery');
    const express=require('express');
    const app = require('express')();
    const http = require('http').Server(app);
    const io = require('socket.io')(http);
    const mysql = require('mysql');
    const url = require('url');
    const fs = require('fs');
    const { Console } = require('console');
    const { query } = require('express');
    const PORT = 3000;
    

    后端代码

    const { data } = require('jquery');
    const express=require('express');
    const app = require('express')();
    const http = require('http').Server(app);
    const io = require('socket.io')(http);
    const mysql = require('mysql');
    const url = require('url');
    const fs = require('fs');
    const { Console } = require('console');
    const { query } = require('express');
    const Front=__dirname +"/Front/";
    const PORT = 3000;
    
    const _cilents=[];          //在线的名称
    
    //数据库连接
    const connection = mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: 'mhqdcqxs',
        database: 'ChatProject'
    });
    
    //从数据库中查询
    function select_user(data,callback){
        //连接数据库开始查询
        let sql = 'SELECT * FROM user where ac = \''+data.username+'\';';
        connection.query(sql,function (err, result) {
            if(err){
                console.log('[SELECT ERROR] - ',err.message);
                callback(null)
                //如果查不到就返回null
            }
            //用回调函数表示执行完了。回调函数是哪个?取决于传参。
            //查到了就返回结果
            callback(result);
        });
    }
    
    //插入数据库
    function insert_user(data){
        let sql ='INSERT INTO user VALUES (\''+data.username+'\',\''+data.password+'\');';
        connection.query(sql,(err,result)=>{
            if(err){
                console.log('[INSERT ERROR] - ',err.message);
                return;
            };
        });
    }
    
    app.get('/', function(req, res){
        res.sendFile(Front + 'index.html');
        console.log("主页 get 请求");
    });
    
    io.on('connection', function(socket){
        socket.on('login',function(data){
    
            //寻找是否在线
            var t_cilent = _cilents.find(item => item == data.username);
            if(t_cilent){
                socket.emit('loginFail','该用户已经登录');
                return ;
            }
    
            //从数据库中寻找
            select_user(data,result=>{
                if(result.length){
                    if(result[0].pw != data.password){
                        socket.emit('loginFail','密码错误!');
                        return ;
                    }
                }
                else{
                    insert_user(data);
                }
    
                //记录已经登录的用户
                _cilents.push(data.username);
    
                socket.emit('loginSuccess',_cilents);
    
                //全局广播,来了新用户
                io.emit('updateUser', _cilents);
                socket.username = data.username;
            })
        });
    
        // 用户断开连接的功能
        socket.on('disconnect', () => {
            // 把当前用户的信息删除
            // 找到断开连接的用户的下标
            const idx =_cilents.findIndex(item => item == socket.username)
            // 删除函数,第一个参数是下标,第二个是距离,也就是删几个元素
            _cilents.splice(idx, 1);
            // 告诉所有人,用户发生更新
            io.emit('updateUser', _cilents)
        });
    
        socket.on('sendMessage',data=>{
            var toSocket = null
            //找到接收者在io.sockets.sockets中的位置。
            for (const key in io.sockets.sockets) {
                if (io.sockets.sockets[key].username == data.toName) {
                    toSocket = key
                    break
                }
            }
            if (toSocket) {
                // 发送给指定用户
                socket.to(toSocket).emit('receiveMessage', {
                    msg:data.msg,
                    fromName:data.name,
                    time : data.time,
                    name:data.toName
                })
            }
    
            //将数据存入文件中
            let url = './history/'+data.name+'To'+data.toName+'.txt';
            fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});
            if(data.toName != data.name){
                let t_url = './history/'+data.toName+'To'+data.name+'.txt';
                fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});
            }
        });
    
        socket.on('sendImage',data=>{
            var toSocket = null
            //console.log(io.sockets.sockets)
            for (const key in io.sockets.sockets) {
                if (io.sockets.sockets[key].username == data.toName) {
                    toSocket = key
                    break
                }
            }
            //console.log(data);
            if (toSocket) {
                // 发送给指定用户
                socket.to(toSocket).emit('receiveImage', {
                    image:data.image,
                    fromName:data.name,
                    time : data.time,
                    name:data.toName
                })
            }
    
            //将数据存入文件中
            let url = './history/'+data.name+'To'+data.toName+'.txt';
            fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});
            if(data.toName != data.name){
                let t_url = './history/'+data.toName+'To'+data.name+'.txt';
                fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}});
            }
        })
    
    });
    
    http.listen(PORT, function(){
        console.log('http://127.0.0.1:'+PORT+'/');
    });
    

    后面没有了,开发时间太紧没有继续记录,但以上作为一个前置步骤已经比较完整。

  • Web 复习提纲

    Web

    概念

    Web:在计算机网页开发设计中指网页

    发展历程(3个阶段)

    1. 静态文档阶段:

      格式:htm、html、shtml。

    2. 动态网页阶段:

      动态网页指网页具有动态和个性化的交互,而不是动态效果。

      格式:jsp、php、asp。

    3. Web2.0阶段:

      以用户为中心,促进信息交换和协同合作。

      例:博客,微博,百科全书,社交网络,对等计算(P2P),即时信息,基于地理信息服务。

    Web牵涉的技术

    1. 客户端技术
      • HTML
      • CSS:层叠样式表
      • Flash:交互式矢量动画制作技术
      • 客户端脚本技术
    2. 服务器端技术
      • CGI:通用网关接口。
      • ASP
      • PHP:一种开发动态网页技术的名称。
      • ASP.NET:建立动态Web应用程序的技术。
      • JSP:基础是Java。

    HTML

    Web应用开发中的一种客户端技术。网页制作标注性语言,不算程序设计语言。

    定义超文本标记语言

    1. 客户端技术的基础
    2. 用于显示网页信息。
    3. 不需要编译,由浏览器解释执行。

    不区分大小写。

    文档结构

    文档标记若是成对出现,均是以 <?> 开头,以 </?> 结束。注意斜杠方向。

    主要标记

    html 标记

    是HTML文件的开头。

    以 <html> 开头, </html> 结束。

    成对出现。

    head 标记

    头标记,放置文件信息。

    成对出现。

    title 标记

    网页标题标记,内含网页标题。

    定义在 head 标记内

    显示在浏览器顶部。

    成对出现。

    body 标记

    页面主体标记。页面内容定义在 <body> 标记中。

    成对出现。

    常用标记

    • 换行:<br>
    • 段落:<p>
    • 标题:<h?> “?”是一个数字,如 <h1> 表示一级标题。成
    • 居中:<center>
    • 文字列表标记
      • 无序列表 <ul> unordered list 成
      • 有序列表 <ol> ordered list 成

    表格标记

    • 表格标记: <table>

      标记内可以定义属性。

    • 标题标记: <caption>

      标记内可以定义属性。

    • 表头标记: <th>

      标记内可以定义属性。

    • 表格行标记: <tr>

      标记内可以定义属性。

    • 单元格标记: <td>

      标记内可以定义属性。

    表单标记

    表单是类似登录界面用户名文本框和密码文本框的元素。

    • <form> 标记,表单标记,成
    • <input> 标记,表单输入标记 ,
    • <select> 标记,下拉菜单标记,成
    • <textarea> 标记,多行文本标记,一般存在于 <form> 标记中,创造一个可以输入多行文本的输入框。

    form 是一整块表单,可能包含多个输入框,而 input 是单个输入框。

    超链接标记和图片标记

    • 超链接标记:<a> ,成
    • 图像标记:<img> ,成

    CSS

    样式表(层叠样式表)。

    1. 对网页的样式实现了更精确的控制。
    2. 美化页面
    3. 优化网页速度(本身提供很多滤镜效果,避免使用大量图片,压缩文件体积,提高下载速度)

    与HTML的关系:HTML是内容,CSS是形式。

    CSS规则

    CSS的三部分:选择符(选择器)、属性、属性值。

    选择器

    标记选择器

    如a选择器

    <style>
        a{
            <!--属性值-->
        }
    </style>
    

    类别选择器
    ID选择器

    属性

    字体属性,文本属性,背景属性,布局属性,边界属性,列表项目属性,表格属性等。

    属性值

    属性的有效值。

    页面中如何加入css

    1. 行内样式

      通过行内定义样式,控制页面样式。

    2. 内嵌式样式表

      利用 <style></style> 标记将CSS样式包含在页面中。

    3. 链接式样式表

      将 CSS 定义在一个单独的文件中,在 HTML 中通过 <link> 引用。

    Javascript

    脚本语言

    特点

    1. 解释性
    2. 基于对象
    3. 事件驱动
    4. 安全性
    5. 跨平台

    区分大小写。

    行尾分号可有可无。

    弱类型。

    大括号标记代码块。

    注释 // /**/

    Ajax技术

    异步的 JavaScript 与 XML 。

    是 JavaScript 、 XML 、 CSS 、 DOM 等多种已有技术的组合。

    核心: XMLHttpRequest ,一个具有应用程序接口的 JavaScript 对象。

    Ajax 引擎:

    • 用户通过 Ajax 引擎与服务器端通信,返回结果提交给 Ajax 引擎,再由 Ajax 引擎来决定将这些数据插入页面的指定位置。

    优点:

    1. 减轻服务器负担
    2. 把一部分服务器负担的工作转移到客户端
    3. 无刷新更新页面
    4. 可以调用 XML 等外部数据
    5. 基于标准化的并广泛支持的技术不需要下载插件或者小程序

    JSP

    动态网页技术标准。

    技术特征

    1. 跨平台
    2. 业务代码分离
    3. 组件重用
    4. 继承 Java Servlet 功能
    5. 预编译

    基本构成

    1. 指令标签
    2. HTML 语句
    3. 注释
    4. 嵌入 Java 代码
    5. JSP 动作标签

    指令标签

    指令标签不会产生任何内容输出到网页中,主要是定义JSP页面的相关信息。

    <%@ %>

    page 指令

    <%@ page   %>
    

    1. language 属性,语言,目前只能Java
    2. extends 属性,继承的Java类
    3. import 属性,导入的类包
    4. pageEncoding 属性,定义编码格式
    5. contentType 属性,设置mime类型和字符编码

    include 指令

    在 JSP 中包含另一个文件的内容。静态包含,代码不会执行。

    taglib指令

    加载用户自定义标签。

    request 对象

    代表客户端请求信息。

    作用域:一次请求。

    作用

    1. 获取请求参数
    2. 获取 form 表单信息
    3. 获取请求客户端信息
    4. 在作用域中管理属性
    5. cookie管理

    response 对象

    代表的是对客户端的响应。

    将 JSP 容器处理过的对象传回到客户端。

    作用域: JSP 页面内有效。

    作用

    1. 重定向网页
    2. 处理 HTTP 文件头
    3. 设置输出缓冲

    session 对象

    与用户请求相关的对象。服务器自动生成。

    用 Map 类来保存数据,因此保存数据格式为 “key/value” 。其中 value 可以是复杂的对象类型,不仅仅是字符串类型。

    作用域:在一次会话范围内有效。

    作用

    1. 创建及获取 session 信息
    2. 从会话中移除指定的绑定对象
    3. 销毁 session
    4. 会话超时的管理
    5. session 对象的应用

    application 对象

    将信息保存在服务器中,直到服务器关闭。生命周期长,类似于 “全局变量” 。

    作用域:在整个上下文中生效。

    作用

    1. 访问应用程序初始化参数
    2. 管理应用程序环境属性

    Servlet

    是 使用 Java Servlet 接口 运行在 Web 应用服务器上的 Java 程序。

    可以处理 Web 浏览器或其他 HTTP 客户端程序发送的请求。

    Servlet 对象需要布置到 Servlet 容器中。

    Servlet 生命周期

    客户端请求 -> 客户端第一次请求服务

    加载 Servlet -> 容器加载 Servlet 类,放入 Servlet 实例池

    初始化 Servlet 对象 -> Servlet 对象的 init() 方法初始化

    处理请求 -> 容器通过 Servlet 的 service() 方法处理客户端请求,service() 方法中,实例对不同的 HTTP 请求作出不同处理并响应

    处理完成,回收对象 -> Web 容器关闭时,容器调用 Servlet 对象的 destroy() 方法对资源进行释放。此后, Servlet 对象将被垃圾回收器回收

    Servlet 技术特点

    1. 方便、实用的 API 方法
    2. 高效的处理方式
    3. 跨平台
    4. 更加灵活、扩展
    5. 安全性高

    Servlet 配置

    1. 声明 Servlet 对象
    2. 映射 Servlet

    数据库系统

    组成部分

    1. 数据库
    2. 数据库管理系统和应用系统
    3. 数据库管理员

    关键组成部分:数据库管理系统,包括数据库定义、数据查询、数据维护等。

    JDBC

    用于执行 SQL 语句的 API 类包。

    一组 Java 语言编写的类和接口。

    操作

    1. 同数据库建立连接
    2. 向数据库发送 SQL 语句
    3. 处理数据库返回的结果

    优点

    1. JDBC 与 ODBC 相似,便于开发人员理解
    2. JDBC 使软件开发人员可以完全专注于业务逻辑的开发。
    3. JDBC 支持多种关系型数据库,大大增加了软件的可移植性。
    4. JDBC API 是面向对象的,软件开发人员可以对其二次封装,提高代码重用性。

    缺点

    1. 通过 JDBC 访问数据库时速度将受到一定影响。
    2. 虽然 JDBC API 是面向对象的,但通过 JDBC 访问数据库依然是面向关系的。
    3. JDBC 提供了对不同厂家的产品的支持,将对数据源带来影响。

    数据库操作的五大步骤

    1. 加载 JDBC 驱动程序。
    2. 创建 Connection 对象的实例。
    3. 执行 SQL 语句。
    4. 获得查询结果
    5. 关闭连接

    Spring MVC 框架

    Spring MVC 是一款基于 MVC 架构模式的轻量级 Web 框架。

    M:model

    V:view

    C:controller

    目的:

    1. 将 Web 开发模块化
    2. 解耦整体架构
    3. 简化 Web 开发流程

    MVC 设计模式

    三个核心

    1. 模型

      模型代表核心功能。

    2. 视图

      指用户看见并与之交互的界面。即 Java Web 应用程序的外观。

    3. 控制器

      负责交互和将用户输入的数据导入模型。

    三者关系

    ​ 视图:显示数据 发送请求 接收数据更新

    4、选择视图显示给用户↑↓1、发送请求

    ​ 控制器:接收请求 将请求映射到模型

    ​ 3、更新后回复控制器↑↓2、更新模型

    ​ 模型:处理业务流程 通知视图更新

    MVC模式特点

    1. 框架机制

      Servlet 实现

    2. 拦截机制

      • 方法级别的拦截
      • 线程安全
    3. 性能

      零配置

    4. 配置

      项目管理和安全高

    5. 设计思想

      Servlet 扩展

    6. 集成

      继承 Ajax

    体系结构

    1. HTTP 请求
    2. 寻找处理器
    3. 调用处理器
    4. 调用模型处理业务
    5. 得到处理结果
    6. 处理视图映射
    7. 将模型数据传给 View 显示
    8. HTTP 响应

    Spring 的 DAO 模式(书P~259~)

    DAO :数据访问对象,Data Access Object

    作用:将持久性相关的问题与一般业务规则和工作隔离开来。

    SSM

    分别为 Spring 、SpringMVC 、MyBatis 三大框架。

    作用

    使用框架可以减少代码量。框架内有封装好的方法可供使用,枯燥的操作交给框架完成。