【Flutter】聊天(更新)

文章目录

        • 仿微信聊天平台
          • 项目地址
          • 最终效果
          • 主要目的
          • 涉及的技术
          • 目前进度
          • nodeJS接口(本地服务,无需证书)
          • 通常情况下的操作流程(文字描述)
          • 通常情况下的操作流程(流程图形式)
        • 数据库建表
          • nodeJS接口
          • 客户端
          • 运行项目
        • 跟进更新内容

仿微信聊天平台
项目地址

gitee项目地址

最终效果

b站演示视频
【Flutter】聊天(更新)_第1张图片

主要目的

使用websocket实现单聊和群聊;使用CameraX仿微信拍视频和拍照片、Android原生技术实现文件查看器和音乐播放器

涉及的技术

nodejs、flutter、MySql、Android

目前进度
  • 使用websocket单聊
nodeJS接口(本地服务,无需证书)
  • 注册用户(http:/localhost/wechat/register)
  • 登录账号(http:/localhost//wechat/login)
  • 查询好友列表(http:/localhost//wechat/users)
  • websocket相关(ws://localhost:4000)
通常情况下的操作流程(文字描述)

若用户没有账号的情况下,用户注册账户。注册成功之后就登录账号。进入主页,查询所有用户(注:为了简化过程,这里不实现好友添加,好友删除,即注册了账户,大家就是可以互相聊天的好朋友)。用户点击某一个用户,进入聊天室,用户可以发送消息(暂时为文本、表情),也可以接受用户发来的消息;接受到的消息记录在本地,若用户不在线(即未连接上websocket),朋友发送的消息就存储在服务器。只要用户一上线,就发送给他所有存储在服务器的消息。最后用户可以登出账号。

通常情况下的操作流程(流程图形式)

【Flutter】聊天(更新)_第2张图片
https://www.processon.com/view/link/6482d583629200782585e337

数据库建表
  • 数据库用宝塔面板创一个
  • 离线消息表
    数据库存储Emoji表情符(https://zhuanlan.zhihu.com/p/427428515)
    注意这句话 CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
CREATE TABLE `my_wechat_message_cache` (
  `messageId` varchar(45) CHARACTER SET utf8mb3 NOT NULL COMMENT '消息Id',
  `senderId` varchar(45) CHARACTER SET utf8mb3 DEFAULT NULL COMMENT '发送者Id',
  `receiverId` varchar(45) CHARACTER SET utf8mb3 DEFAULT NULL COMMENT '接受者Id',
  `messageContent` varchar(45) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '消息内容',
  `sendDate` varchar(45) CHARACTER SET utf8mb3 DEFAULT NULL COMMENT '发送时间',
  `senderAvatar` varchar(200) CHARACTER SET utf8mb3 DEFAULT NULL COMMENT '发送头像',
  PRIMARY KEY (`messageId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

  • 用户表
CREATE TABLE `my_wechat_user` (
  `userId` varchar(20) NOT NULL COMMENT '用户Id',
  `userName` varchar(45) DEFAULT NULL COMMENT '用户昵称',
  `tel` varchar(20) DEFAULT NULL COMMENT '电话号码',
  `password` varchar(45) DEFAULT NULL COMMENT '密码',
  `avatar` varchar(200) DEFAULT 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg' COMMENT '头像',
  PRIMARY KEY (`userId`),
  UNIQUE KEY `tel_UNIQUE` (`tel`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='仿微信用户表';

【Flutter】聊天(更新)_第3张图片

nodeJS接口
  • 项目地址
    【Flutter】聊天(更新)_第4张图片
  • 注册账号、登录账号、查询用户
var util = require('./shared_util.js')
const express = require('express')
const app = express()
app.use(express.json())
const port = 3000
var mysql = require('mysql')
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'bilibili',
    password: '123',
    database: 'bilibili'
})

app.get('/', (req, res) => {
    res.send('服务3000')
})
//注册
app.post('/wechat/register', (req, res) => {
    let userId = (new Date()).valueOf()
    let tel = req.body.tel
    let userName = req.body.userName
    let password = req.body.password
    if (util.strIsNotEmpty(tel) && util.strIsNotEmpty(userName) && util.strIsNotEmpty(password)) {
        ///查询是否存在用户
        connection.query(
            `SELECT * FROM my_wechat_user where tel = \"${tel}\"`,
            function (err, rows, fields) {
                if (err) throw err
                if (rows.length != 0) {
                    res.send({
                        "code": 400,
                        "message": "用户存在"
                    })
                } else {
                    ///新增用户
                    connection.query(
                        `INSERT INTO my_wechat_user (\`userId\`, \`userName\`, \`tel\`, \`password\`) VALUES (\"${userId}\", \"${userName}\", \"${tel}\", \"${password}\")`,
                        function (err, rows, fields) {
                            if (err) throw err
                            res.send({
                                "code": 0,
                                "message": "注册成功"
                            })
                        }
                    )
                }
            }
        )
    } else {
        res.send({
            "code": 500,
            "message": "输入不能为空"
        })
    }
})
//登录
app.post('/wechat/login', (req, res) => {
    let tel = req.body.tel
    let password = req.body.password
    if (util.strIsNotEmpty(tel) && util.strIsNotEmpty(password)) {
        ///查询是否存在用户
        connection.query(
            `SELECT * FROM my_wechat_user where tel = \"${tel}\" && password = \"${password}\"`,
            function (err, rows, fields) {
                if (err) throw err
                if (rows.length != 0) {
                    res.send({
                        "code": 0,
                        "message": `欢迎${rows[0].userName}`,
                        "data": {
                            "userId": rows[0].userId, //用户id
                            "userName": rows[0].userName, //用户昵称
                            "avatar": rows[0].avatar, //头像
                        }
                    })
                } else {
                    res.send({
                        "code": 400,
                        "message": "用户不存在或者密码错误"
                    })
                }
            }
        )
    } else {
        res.send({
            "code": 500,
            "message": "输入不能为空"
        })
    }
})
//查询所有用户列表
app.get('/wechat/users', (req, res) => {
    connection.query(`SELECT userId, userName, avatar FROM my_wechat_user`, function (err, rows, fields) {
        if (err) throw err
        res.send({
            "code": 0,
            "message": "获取用户列表成功",
            "data": rows
        })
    })
})

app.listen(port, () => {
    connection.connect()
    console.log('服务3000启动...')
})


  • server_websocket.js
///连接数据
var mysql = require('mysql')
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'bilibili',
    password: '123',
    database: 'bilibili',
    charset: "utf8mb4", //表情符四个字节
    collate: "utf8mb4_unicode_ci",
})
///websocket服务器
const ws = require('nodejs-websocket')
///记录当前的在线的用户
var connectionMap = new Map()
const server = ws.createServer(conn => {
    ///获取连接进来的用身份户信息
    let token = JSON.parse(conn.headers.token)
    console.log("websocket connect:" + token.userId + "连进来了...")
    ///加到map
    connectionMap.set(token.userId, conn)
    ///查询待发的消息并发送
    let receiverId = token.userId
    connection.query(`SELECT * FROM my_wechat_message_cache where receiverId = \"${receiverId}\"`,
        function (err, rows, fields) {
            if (err) throw err
            rows.forEach(receivedData => {
                sendCacheMsg(receivedData, receiverId)
            })
        }
    )
    ///用户发送消息至服务
    conn.on('text', data => {
        /*
            接受消息格式
            {
                "users": ["user001", "user002"], ///发送给谁的列表
                "msg": "你好", ///发送内容
                "sender": "user002", ///发送人
                "date": "时间戳", ///时间
                "avatar": "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" ///网络图片头像
            }
        */
        try {
            let receivedData = JSON.parse(data)
            // console.log(receivedData)
            sendUserMsg(receivedData)
        } catch (e) {
            console.log(data)
            console.log(e)
        }

    })
    ///用户断开连接
    conn.on('close', data => {
        connectionMap.delete(token.userId)
        console.log("websocket close:" + token.userId + "断开连接")
    })
    ///出错
    conn.on('error', data => {
        console.log("websocket error:" + token.userId + "出错了")
    })
})
server.listen(4000, () => {
    console.log("启动服务器")
})

///广播所有连接的对象
var broadcast = (msg) => {
    server.connections.forEach(item => {
        item.send(msg)
    })
}
///发送消息
var sendUserMsg = (receivedData) => {
    ///发送人Id
    let senderId = receivedData.sender;
    ///待发送人名单
    let users = receivedData.users
    ///发送给名单里面的人
    users.forEach(user => {
        let conn = connectionMap.get(user)
        ///判断是否在线,否则消息为待发送
        if (conn != null) {
            /*
                发送消息格式
                {
                    "msg": "你好", //发送内容
                    "sender": "user001" //发送者
                    "receiver": "user001" //接受者
                    "date": "123" //发送时间
                    "avatar": "..." //头像
                }
            */
            let sendData =
                `
                {
                    \"msg\": \"${receivedData.msg}\",
                    \"sender\": \"${receivedData.sender}\", 
                    \"receiver\": \"${user}\", 
                    \"date\": ${receivedData.date}, 
                    \"avatar\": \"${receivedData.avatar}\"
                }
            `
            conn.send(sendData)
        } else {
            ///如果不在线,则消息进入待发队列
            let messageContent = receivedData.msg
            // console.log(messageContent)
            let sendDate = receivedData.date
            let senderAvatar = receivedData.avatar
            users.forEach(receiverId => {
                let messageId = (new Date()).valueOf() + senderId + receiverId
                // console.log("接受者")
                // console.log(receiverId)
                connection.query(
                    `INSERT INTO my_wechat_message_cache (\`messageId\`, \`senderId\`, \`receiverId\`, \`messageContent\`, \`sendDate\`, \`senderAvatar\`) VALUES (\"${messageId}\", \"${senderId}\", \"${receiverId}\", \"${messageContent}\", \"${sendDate}\", \"${senderAvatar}\")`,
                    function (err, rows, fields) {
                        if (err) throw err

                    }
                )
            })
        }

    })
}
///发送待发消息后删除待发记录
var sendCacheMsg = (readyMsg) => {
    server.connections.forEach(item => {
        ///用户身份验证消息
        let token = JSON.parse(item.headers.token)
        ///接受者Id
        let receiverId = token.userId;
        if (readyMsg.receiverId == receiverId) {
            let sendData =
                `
                    {
                        \"msg\": \"${readyMsg.messageContent}\",
                        \"sender\": \"${readyMsg.senderId}\", 
                        \"receiver\": \"${readyMsg.receiverId}\", 
                        \"date\": ${readyMsg.sendDate}, 
                        \"avatar\": \"${readyMsg.senderAvatar}\"
                    }
                `
            item.send(sendData)
            ///删除记录
            let messageId = readyMsg.messageId
            connection.query(`DELETE FROM my_wechat_message_cache WHERE (\`messageId\` = \"${messageId}\")`, function (err, rows, field) {
                if (err) throw err
            })
        }
    })
}
客户端

客户端采用Flutter来实现
【Flutter】聊天(更新)_第5张图片

运行项目
  • 网络连接
    因为是本地,通过ipconfig获取ip地址。
  • 运行服务(mysql和其他一些依赖,需要node下来)
    打开两个终端,分别运行两个js
    在这里插入图片描述
    在这里插入图片描述
  • 本地调试(postman)
{
    "users": ["1686038201239"],
    "msg": "你好",
    "sender": "1686038558394",
    "date": "1686038558394",
    "avatar": "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
}

【Flutter】聊天(更新)_第6张图片
【Flutter】聊天(更新)_第7张图片

【Flutter】聊天(更新)_第8张图片

  • 运行Flutter项目(ip地址需要更改为本地的ip地址,localhost不行,必须是明确的ip地址111.111.11.111)
    【Flutter】聊天(更新)_第9张图片
跟进更新内容
  • 未读消息红点更新
    对应Flutter设计模式中的观察者模式Stream & ChangeNotifier
  • 运行
    【Flutter】聊天(更新)_第10张图片

你可能感兴趣的:(flutter,android)