IM项目:进阶版即时通讯项目---文件存储和消息转发

news2024/9/25 1:18:18

文章目录

  • 文件传输服务
    • 基本功能
    • 模块划分
    • 流程图
    • 实现逻辑
    • 代码实现
  • 消息转发
    • 功能设计
    • 模块划分
    • 获取转发目标和消息处理
    • 代码实现

文件传输服务

基本功能

  1. 文件的上传
  2. 文件的下载

模块划分

  1. 基于gflags进行参数和配置文件的解析
  2. 基于spdlog进行日志输出
  3. 基于etcd进行服务注册
  4. 基于brpc进行RPC服务器远程调用
  5. 基于文件流操作进行读写的封装

流程图

在这里插入图片描述

实现逻辑

  1. 单个文件上传
  • 获取文件的元数据
  • 分配文件的ID
  • 以文件ID为文件名打开文件,写入数据
  • 组织响应返回
  1. 单个文件下载
  • 从请求中获取文件ID
  • 打开文件,获取大小,读取数据
  • 组织响应返回
  1. 多个文件上传

这个就是循环一下

  1. 多个文件下载

这个就是循环一下

代码实现

/**
 * @file file_server.hpp
 * @brief 文件传输服务,和语音传输服务基本相同
 * @author zhaobohan (zhaobohan_free@163.com)
 */
#include <brpc/server.h>
#include <butil/logging.h>

#include "../../common/etcd.hpp"     // 服务注册模块封装
#include "../../common/logger.hpp"   // 日志模块封装
#include "../../common/utils.hpp"
// #include "base.pb.h"
// #include "file.pb.h"

#include "../build/base.pb.h"
#include "../build/file.pb.h"

namespace im 
{

// 对于文件服务的封装
class FileServiceImpl : public im::FileService 
{
public:
    FileServiceImpl(const std::string &storage_path)
        : _storage_path(storage_path)
    {
        umask(0);
        mkdir(storage_path.c_str(), 0775);
        if (_storage_path.back() != '/') 
            _storage_path.push_back('/');
    }

    ~FileServiceImpl()
    {}

    void GetSingleFile(google::protobuf::RpcController* controller,
                const ::im::GetSingleFileReq* request,
                ::im::GetSingleFileRsp* response,
                ::google::protobuf::Closure* done) 
    {
        brpc::ClosureGuard rpc_guard(done);
        response->set_request_id(request->request_id());
        // 1. 取出请求中的文件ID(起始就是文件名)
        std::string fid = request->file_id();
        std::string filename = _storage_path + fid;
        // 2. 将文件ID作为文件名,读取文件数据
        std::string body;
        bool ret = readFile(filename, body);
        if (ret == false) 
        {
            response->set_success(false);
            response->set_errmsg("读取文件数据失败!");
            LOG_ERROR("{} 读取文件数据失败!", request->request_id());
            return;
        }

        // 3. 组织响应
        response->set_success(true);
        response->mutable_file_data()->set_file_id(fid);
        response->mutable_file_data()->set_file_content(body);
    }

    void GetMultiFile(google::protobuf::RpcController* controller,
                const ::im::GetMultiFileReq* request,
                ::im::GetMultiFileRsp* response,
                ::google::protobuf::Closure* done) 
    {
        brpc::ClosureGuard rpc_guard(done);
        response->set_request_id(request->request_id());
        // 循环取出请求中的文件ID,读取文件数据进行填充
        for (int i = 0; i < request->file_id_list_size(); i++) 
        {
            std::string fid = request->file_id_list(i);
            std::string filename = _storage_path + fid;
            std::string body;
            bool ret = readFile(filename, body);
            if (ret == false) 
            {
                response->set_success(false);
                response->set_errmsg("读取文件数据失败!");
                LOG_ERROR("{} 读取文件数据失败!", request->request_id());
                return;
            }

            FileDownloadData data;
            data.set_file_id(fid);
            data.set_file_content(body);
            response->mutable_file_data()->insert({fid, data});
        }
        response->set_success(true);
    }

    void PutSingleFile(google::protobuf::RpcController* controller,
                const ::im::PutSingleFileReq* request,
                ::im::PutSingleFileRsp* response,
                ::google::protobuf::Closure* done) 
    {
        brpc::ClosureGuard rpc_guard(done);
        response->set_request_id(request->request_id());
        // 1. 为文件生成一个唯一uudi作为文件名 以及 文件ID
        std::string fid = uuid();
        std::string filename = _storage_path + fid;
        // 2. 取出请求中的文件数据,进行文件数据写入
        bool ret = writeFile(filename, request->file_data().file_content());
        if (ret == false) 
        {
            response->set_success(false);
            response->set_errmsg("读取文件数据失败!");
            LOG_ERROR("{} 写入文件数据失败!", request->request_id());
            return;
        }
        // 3. 组织响应
        response->set_success(true);
        response->mutable_file_info()->set_file_id(fid);
        response->mutable_file_info()->set_file_size(request->file_data().file_size());
        response->mutable_file_info()->set_file_name(request->file_data().file_name());
    }

    void PutMultiFile(google::protobuf::RpcController* controller,
                const ::im::PutMultiFileReq* request,
                ::im::PutMultiFileRsp* response,
                ::google::protobuf::Closure* done) 
    {
        brpc::ClosureGuard rpc_guard(done);
        response->set_request_id(request->request_id());
        for (int i = 0; i < request->file_data_size(); i++) 
        {
            std::string fid = uuid();
            std::string filename = _storage_path + fid;
            bool ret = writeFile(filename, request->file_data(i).file_content());
            if (ret == false) 
            {
                response->set_success(false);
                response->set_errmsg("读取文件数据失败!");
                LOG_ERROR("{} 写入文件数据失败!", request->request_id());
                return;
            }
            im::FileMessageInfo *info  = response->add_file_info();
            info->set_file_id(fid);
            info->set_file_size(request->file_data(i).file_size());
            info->set_file_name(request->file_data(i).file_name());
        }
        response->set_success(true);
    }

private:
    std::string _storage_path;
};

class FileServer 
{
public:
    using ptr = std::shared_ptr<FileServer>;

    FileServer(const Registry::ptr &reg_client,
        const std::shared_ptr<brpc::Server> &server):
        _reg_client(reg_client),
        _rpc_server(server)
    {}

    ~FileServer()
    {}

    // 搭建RPC服务器,并启动服务器
    void start() 
    {
        _rpc_server->RunUntilAskedToQuit();
    }

private:
    Registry::ptr _reg_client;
    std::shared_ptr<brpc::Server> _rpc_server;
};

class FileServerBuilder 
{
public:
    // 用于构造服务注册客户端对象
    void make_reg_object(const std::string &reg_host,
        const std::string &service_name,
        const std::string &access_host) 
    {
        _reg_client = std::make_shared<Registry>(reg_host);
        _reg_client->registry(service_name, access_host);
    }

    // 构造RPC服务器对象
    void make_rpc_server(uint16_t port, int32_t timeout, 
        uint8_t num_threads, const std::string &path = "./data/") 
    {
        _rpc_server = std::make_shared<brpc::Server>();
        FileServiceImpl *file_service = new FileServiceImpl(path);
        int ret = _rpc_server->AddService(file_service, 
            brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
        if (ret == -1) 
        {
            LOG_ERROR("添加Rpc服务失败!");
            abort();
        }
        brpc::ServerOptions options;
        options.idle_timeout_sec = timeout;
        options.num_threads = num_threads;
        ret = _rpc_server->Start(port, &options);
        if (ret == -1) 
        {
            LOG_ERROR("服务启动失败!");
            abort();
        }
    }

    FileServer::ptr build() 
    {
        if (!_reg_client) 
        {
            LOG_ERROR("还未初始化服务注册模块!");
            abort();
        }

        if (!_rpc_server) 
        {
            LOG_ERROR("还未初始化RPC服务器模块!");
            abort();
        }

        FileServer::ptr server = std::make_shared<FileServer>(_reg_client, _rpc_server);
        return server;
    }

private:
    Registry::ptr _reg_client;
    std::shared_ptr<brpc::Server> _rpc_server;
};
}

消息转发

功能设计

消息转发子服务,主要是涉及到对于一条消息内容,组织消息的ID,以及其他元数据,然后传递给网关服务器,应该给谁进行发送

转发的目标一般是以聊天会话为基础进行传输的,通过会话就可以找到对应的聊天成员,作为一个转发的目标,并且还要把对应的数据存放在消息队列当中,消息队列收到消息后就会进行对应的回调处理,其实回调就是把数据存放在MySQL,elasticsearch,或者是文件存储系统中,这些内容我在后面的内容也都会涉及到,这里考虑到篇幅原因就不多多进行分析了

模块划分

  1. 基于gflags进行参数和配置文件解析
  2. 基于spdlog进行日志输出
  3. 基于etcd框架进行服务注册
  4. 基于ODB进行数据库对象操作
  5. 基于brpc进行RPC服务器搭建和远程调用
  6. 基于MQ将消息发布到消息队列并且进行对应的存储

下面来聊一下在这当中比较重要的接口

获取转发目标和消息处理

基本流程大概为

  1. 从请求中取出消息内容,会话ID,用户ID
  2. 根据用户ID,从用户管理子服务获取用户信息
  3. 根据消息内容进行填充消息结构,比如分配消息ID,填充发送者信息,填充消息产生时间
  4. 把消息传送给消息队列,进行持久化存储
  5. 从数据库获取目标会话的所有成员ID
  6. 组织响应,也就是完整的消息以及所有要被发送的用户ID,然后发送给网关,网关进行发送

代码实现

class TransmiteServiceImpl : public im::MsgTransmitService 
{
public:
    TransmiteServiceImpl(const std::string &user_service_name,
        const ServiceManager::ptr &channels,
        const std::shared_ptr<odb::core::database> &mysql_client,
        const std::string &exchange_name,
        const std::string &routing_key,
        const MQClient::ptr &mq_client)
        : _user_service_name(user_service_name)
        , _mm_channels(channels)
        , _mysql_session_member_table(std::make_shared<ChatSessionMemeberTable>(mysql_client))
        , _exchange_name(exchange_name)
        , _routing_key(routing_key)
        , _mq_client(mq_client)
    {}

    ~TransmiteServiceImpl()
    {}

    // 获取转发对象
    void GetTransmitTarget(google::protobuf::RpcController* controller,
                    const ::im::NewMessageReq* request,
                    ::im::GetTransmitTargetRsp* response,
                    ::google::protobuf::Closure* done) override 
    {
        brpc::ClosureGuard rpc_guard(done);
        auto err_response = [this, response](const std::string &rid, 
            const std::string &errmsg) -> void {
            response->set_request_id(rid);
            response->set_success(false);
            response->set_errmsg(errmsg);
            return;
        };

        // 从请求中获取关键信息:用户ID,所属会话ID,消息内容
        std::string rid = request->request_id();
        std::string uid = request->user_id();
        std::string chat_ssid = request->chat_session_id();
        const MessageContent &content = request->message();

        // 进行消息组织:发送者-用户子服务获取信息,所属会话,消息内容,产生时间,消息ID
        auto channel = _mm_channels->choose(_user_service_name);
        if (!channel) 
        {
            LOG_ERROR("{}-{} 没有可供访问的用户子服务节点!", rid, _user_service_name);
            return err_response(rid, "没有可供访问的用户子服务节点!");
        }

        // 去查找用户的信息
        UserService_Stub stub(channel.get());
        GetUserInfoReq req;
        GetUserInfoRsp rsp;
        req.set_request_id(rid);
        req.set_user_id(uid);
        brpc::Controller cntl;
        stub.GetUserInfo(&cntl, &req, &rsp, nullptr);
        if (cntl.Failed() == true || rsp.success() == false) 
        {
            LOG_ERROR("{} - 用户子服务调用失败:{}!", request->request_id(), cntl.ErrorText());
            return err_response(request->request_id(), "用户子服务调用失败!");
        }

        // 构造消息结构体
        MessageInfo message;
        message.set_message_id(uuid());
        message.set_chat_session_id(chat_ssid);
        message.set_timestamp(time(nullptr));
        message.mutable_sender()->CopyFrom(rsp.user_info());
        message.mutable_message()->CopyFrom(content);

        // 获取消息转发客户端用户列表
        auto target_list = _mysql_session_member_table->members(chat_ssid);

        // 将封装完毕的消息,发布到消息队列,待消息存储子服务进行消息持久化
        bool ret = _mq_client->publish(_exchange_name, message.SerializeAsString(), _routing_key);
        if (ret == false) 
        {
            LOG_ERROR("{} - 持久化消息发布失败:{}!", request->request_id(), cntl.ErrorText());
            return err_response(request->request_id(), "持久化消息发布失败:!");
        }

        // 组织响应,告诉网关要给谁传消息
        response->set_request_id(rid);
        response->set_success(true);
        response->mutable_message()->CopyFrom(message);
        for (const auto &id : target_list) 
        {
            response->add_target_id_list(id);
        }
    }

private:
    // 用户子服务调用相关信息
    std::string _user_service_name;
    ServiceManager::ptr _mm_channels;

    // 聊天会话成员表的操作句柄
    ChatSessionMemeberTable::ptr _mysql_session_member_table;

    // 消息队列客户端句柄
    std::string _exchange_name;
    std::string _routing_key;
    MQClient::ptr _mq_client;
};

class TransmiteServer 
{
public:
    using ptr = std::shared_ptr<TransmiteServer>;
    TransmiteServer(
        const std::shared_ptr<odb::core::database> &mysql_client,
        const Discovery::ptr discovery_client,
        const Registry::ptr &registry_client,
        const std::shared_ptr<brpc::Server> &server):
        _service_discoverer(discovery_client),
        _registry_client(registry_client),
        _mysql_client(mysql_client),
        _rpc_server(server)
    {}

    ~TransmiteServer()
    {}

    // 搭建RPC服务器,并启动服务器
    void start() 
    {
        _rpc_server->RunUntilAskedToQuit();
    }

private:
    // 服务发现客户端
    Discovery::ptr _service_discoverer;

    // 服务注册客户端
    Registry::ptr _registry_client;
    
    // mysql数据库客户端
    std::shared_ptr<odb::core::database> _mysql_client;
    std::shared_ptr<brpc::Server> _rpc_server;
};

class TransmiteServerBuilder 
{
public:
    // 构造mysql客户端对象
    void make_mysql_object(
        const std::string &user,
        const std::string &pswd,
        const std::string &host,
        const std::string &db,
        const std::string &cset,
        int port,
        int conn_pool_count) 
    {
        _mysql_client = ODBFactory::create(user, pswd, host, db, cset, port, conn_pool_count);
    }

    // 用于构造服务发现客户端&信道管理对象
    void make_discovery_object(const std::string &reg_host,
        const std::string &base_service_name,
        const std::string &user_service_name) 
    {
        _user_service_name = user_service_name;
        _mm_channels = std::make_shared<ServiceManager>();
        _mm_channels->declared(user_service_name);
        LOG_DEBUG("设置用户子服务为需添加管理的子服务:{}", user_service_name);
        auto put_cb = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
        auto del_cb = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
        _service_discoverer = std::make_shared<Discovery>(reg_host, base_service_name, put_cb, del_cb);
    }

    // 用于构造服务注册客户端对象
    void make_registry_object(const std::string &reg_host,
        const std::string &service_name,
        const std::string &access_host) 
    {
        _registry_client = std::make_shared<Registry>(reg_host);
        _registry_client->registry(service_name, access_host);
    }

    // 用于构造rabbitmq客户端对象
    void make_mq_object(const std::string &user, 
        const std::string &passwd,
        const std::string &host,
        const std::string &exchange_name,
        const std::string &queue_name,
        const std::string &binding_key) 
    {
        _routing_key = binding_key;
        _exchange_name = exchange_name;
        _mq_client = std::make_shared<MQClient>(user, passwd, host);
        _mq_client->declareComponents(exchange_name, queue_name, binding_key);
    }

    // 构造RPC服务器对象
    void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads) 
    {
        if (!_mq_client) 
        {
            LOG_ERROR("还未初始化消息队列客户端模块!");
            abort();
        }

        if (!_mm_channels) 
        {
            LOG_ERROR("还未初始化信道管理模块!");
            abort();
        }

        if (!_mysql_client) 
        {
            LOG_ERROR("还未初始化Mysql数据库模块!");
            abort();
        }

        _rpc_server = std::make_shared<brpc::Server>();

        TransmiteServiceImpl *transmite_service = new TransmiteServiceImpl(
            _user_service_name, _mm_channels, _mysql_client, _exchange_name, _routing_key, _mq_client);

        int ret = _rpc_server->AddService(transmite_service, 
            brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
        if (ret == -1) 
        {
            LOG_ERROR("添加Rpc服务失败!");
            abort();
        }

        brpc::ServerOptions options;
        options.idle_timeout_sec = timeout;
        options.num_threads = num_threads;
        ret = _rpc_server->Start(port, &options);
        if (ret == -1) 
        {
            LOG_ERROR("服务启动失败!");
            abort();
        }

    }


    TransmiteServer::ptr build() 
    {
        if (!_service_discoverer) 
        {
            LOG_ERROR("还未初始化服务发现模块!");
            abort();
        }

        if (!_registry_client) 
        {
            LOG_ERROR("还未初始化服务注册模块!");
            abort();
        }

        if (!_rpc_server) 
        {
            LOG_ERROR("还未初始化RPC服务器模块!");
            abort();
        }

        TransmiteServer::ptr server = std::make_shared<TransmiteServer>(
            _mysql_client, _service_discoverer, _registry_client, _rpc_server);
        return server;
    }

private:
    std::string _user_service_name;
    ServiceManager::ptr _mm_channels;
    Discovery::ptr _service_discoverer;
    
    std::string _routing_key;
    std::string _exchange_name;
    MQClient::ptr _mq_client;

    // 服务注册客户端
    Registry::ptr _registry_client;
    // mysql数据库客户端
    std::shared_ptr<odb::core::database> _mysql_client;
    std::shared_ptr<brpc::Server> _rpc_server;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于超长字符串/文本对应的数据从excel导入到PL/SQL中的尝试

问题&#xff1a; 1.字符串太长 2.str绑定之的结尾null缺失 将csv文件导入到PL/SQL表中存在的一些问题 1.本来我是需要将exceL上的几十条数据导入到PL/SQL数据库的一张表中&#xff0c;结果我花了许多时间 去导入。 想想一般情况下也就几十条数据&#xff0c;直接复制粘贴就…

并发编程之----线程池ThreadPoolExecutor,Excutors的使用及其工作原理

当前&#xff1a;并发编程之----线程池ThreadPoolExecutor,Excutors的使用及其工作原理 Redis高级----主从、哨兵、分片、脑裂原理-CSDN博客 计算机网络--面试知识总结一 计算机网络-----面试知识总结二 计算机网络--面试总结三&#xff08;Http与Https&#xff09; 计算机…

代码随想录 | 回溯算法总结

在代码随想录算法 | 回溯算法先导知识 | 题目分类&#xff0c;理论基础-CSDN博客中我们详细的介绍了回溯算法的理论知识&#xff0c;不同于教科书般的讲解&#xff0c;这里介绍的回溯法的效率&#xff0c;解决的问题以及模板都是在刷题的过程中非常实用&#xff01; 回溯是递归…

黑神话悟空|风灵月影 35项修改器下载

《黑神话&#xff1a;悟空》是由游戏科学公司制作的一款动作角色扮演游戏&#xff0c;于2024年8月20日正式发售。游戏改编自中国著名的神魔小说《西游记》&#xff0c;玩家在游戏中将扮演一位“天命人”&#xff0c;踏上一条充满危险与惊奇的西游之路。下面为带来这款游戏的修改…

AI人像换脸!Reactor插件本地部署方法(含报错解决及整合包)

​ Reactor插件是什么&#xff1f;有什么用&#xff1f; Reactor 是一个用于 Stable Diffusion 的换脸插件&#xff0c; 主要功能是实现图片中的精确换脸。它可以自动检测并替换图片中的多个面部&#xff0c;适用于多种场景&#xff0c;比如生成逼真的图像或者进行复杂的图片处…

InternVL多模态模型训练教程,流程图识别检测LLM-v1版本。检测流程图,输出基础图形bounding box

文章目录 项目介绍求一个star环境准备模型下载多模态大语言模型 (InternVL 2.0) 构造训练数据集单张图片&#xff1a;Grounding / Detection Data 开始训练 项目介绍 本篇文章主要是讲如何训练InternVL2模型&#xff0c;详细信息可以看我的Github repo&#xff0c;欢迎star&am…

ffplay源码分析(二)结构体VideoState

在多媒体的世界里&#xff0c;播放器是离用户最近的一环&#xff0c;它将数字编码的音频和视频数据转化为生动的视听体验。ffplay 播放器作为一款强大而备受关注的工具&#xff0c;其背后隐藏着一系列精妙的结构体&#xff0c;它们协同工作&#xff0c;共同完成了从数据读取、解…

Unity3D 遍历预制体

Unity3D 遍历预制体进行批量化处理。 遍历预制体 有时候&#xff0c;我们需要对一些预制体资源进行批量化处理&#xff0c;如果每一个预制体都手动处理&#xff0c;就会耗费很多时间精力&#xff0c;也容易出错。 我们可以写一个脚本遍历预制体&#xff0c;对预制体进行修改…

单HTML文件集成vue3+ElementPlus的使用

1、新建一个HTML文件 2、HTML文件引用vue3.js 3、引用elementplus.js和elementplus.css 4、Vue初始化ElementPlus 5、页面中可以使用ElementPlus啦 HTML文件例子如下&#xff1a; <html><head><meta charset"UTF-8"><script src"./js/vue…

NSTimer 引发的循环引用(内存泄漏)| NSTimer强引用

在iOS中使用NSTimer(定时器)不当会引发内存泄漏. NSTimer 有多种创建方式,具体可以看这位朋友的文章:https://blog.51cto.com/u_16099225/6716123 我这里主要讲使用NSTimer 会引发的内存泄漏情况以及解决方法: 内存泄漏出现的场景: VC A push 到VC B, VC B里启动了一个 NST…

Java基础之方法与数组

方法 在Java中&#xff0c; 方法的定义包括方法的修饰符、返回类型、方法名、参数列表和方法体。方法既能够模块化的组织代码(当代码规模比较复杂的时候)。也做到代码被重复使用&#xff08;一份代码可以在多个位置使用&#xff09;。Java中的方法类似与C语言中的函数&#xf…

Java SpringBoot实战教程:如何一步步构建保险业务管理与数据分析系统

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

LlamaIndex 实现 RAG(三)- 向量数据

RAG 中使用向量存储知识和文档数据&#xff0c;召回时通过语意进行搜索。文档转为向量是个非常消耗时的操作&#xff0c;不同 Embedding Model 参数不同&#xff0c;结果维度也不同&#xff0c;消耗的算力也不同。所以通常的做法都会在索引阶段&#xff08;Embedding&#xff0…

deeplab3-plus(中文翻译)

** Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation 文章目录 Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation1 Introduction2 Related Work3 Methods3.1 Encoder-Decoder with Atrous Convolution…

鸿蒙南向开发:测试框架xdevice核心组件

简介 xdevice是OpenHarmony中为测试框架的核心组件&#xff0c;提供用例执行所依赖的相关服务。 xdevice主要包括以下几个主要模块&#xff1a; command&#xff0c;用户与测试平台命令行交互模块&#xff0c;提供用户输入命令解析&#xff0c;命令处理。config&#xff0c;…

electron仿微信,高度还原,入门项目

效果展示 Electron仿写微信-效果展示 目前完成了一些基础的功能&#xff0c;还在持续开发中&#xff0c;后期会整理开源。 有些样式没有追求百分百还原&#xff0c;只是通过该项目&#xff0c;让大家了解一下Electron创建桌面应用&#xff0c;各种窗口的创建及销毁、事件传递机…

NLP从零开始------13.文本中阶序列处理之语言模型(1)

语言模型( language model) 用于计算一个文字序列的概率&#xff0c; 评估该序列作为一段文本出现在通用或者特定场景中的可能性。每个人的语言能力蕴涵了一个语言模型&#xff0c;当我们说出或写下一段话的时候&#xff0c;已经在不自觉地应用语言模型来帮助我们决定这段话中的…

viscode 自定义片段,快速生成自己的开发模板

设置 ---> 代码片段 2.选择新建全局代码片段文件 3.根据示例自定义配置代码片段 4.示例:vue prefix:内容--> 代表用于触发代码片段的内容 $1&#xff0c; $2 用于制表位,如 $1 代表生成后第一个输入的位置,$2代表第二个,不用自己移动鼠标 {// Place your snippets f…

Sac格式

本文章只作为自己学习时的用法&#xff0c;不通用&#xff0c;大家可不用参考。 sac格式 0.01000000 -1.569280 1.520640 -12345.00 -12345.009.459999 19.45000 -41.43000 10.46400 -12345.00-12345.00 -12345.00 -12…

SQL注入漏洞的基础知识

目录 SQL注入漏洞的定义和原理 SQL注入的类型和攻击方法 SQL注入的防御措施 示例代码 深入研究 SQL注入漏洞的常见攻击场景有哪些&#xff1f; 如何有效防范SQL注入攻击&#xff1f; SQL注入与跨站脚本攻击&#xff08;XSS&#xff09;之间有什么区别&#xff1f; 主要…