【lesson8】云备份服务端完整版代码

util.hpp

#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <cstdint>
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
#include <memory>
#include "bundle.h"



namespace cloud
{
    namespace fs = std::experimental::filesystem;
    class fileUtil
    {
    public:
        fileUtil(std::string filename)
        :_filename(filename)
        {}
        
        bool Remove()
        {
            if(exists() == false)
            {
                return true;
            }

            remove(_filename.c_str());
            return true;
        }

        size_t fileSize()
        {
            struct stat st;
            int ret = stat(_filename.c_str(), &st);
            if(ret == -1)
            {
                std::cout << strerror(errno) << std::endl;
                return 0;
            }
            return st.st_size;
        }

        time_t lastModifyTime()
        {
            struct stat st;
            int ret = stat(_filename.c_str(), &st);
            if(ret == -1)
            {
                std::cout << strerror(errno) << std::endl;
                return -1;
            }
            return st.st_mtime;
        }

        time_t lastAccessTime()
        {
            struct stat st;
            int ret = stat(_filename.c_str(), &st);
            if(ret == -1)
            {
                std::cout << strerror(errno) << std::endl;
                return -1;
            }
            return st.st_atime;
        }

        std::string fileName()
        {
            size_t pos = _filename.find_last_of("/");
            if(pos == std::string::npos)
            {
                return _filename;
            }
            return _filename.substr(pos + 1);
        }

        bool setContent(const std::string &body)
        {
            std::ofstream ofs;

            ofs.open(_filename, std::ios::binary);
            if(ofs.is_open() == false)
            {
                std::cout << "setContent open failed\n";
                return false;
            }

            ofs.write(&body[0], body.size());
            if(ofs.good() == false)
            {
                std::cout << "setContent write failed\n";
                ofs.close();
                return false;
            }

            ofs.close();
            return true;
        }

        bool getPosLen(std::string *body, size_t pos, size_t len)
        {
            std::ifstream ifs;
            if(pos + len > fileSize())
            {
                std::cout << "getPosLen failed\n";
                return false;
            }

            ifs.open(_filename, std::ios::binary);
            if(ifs.is_open() == false)
            {
                std::cout << "getPosLen open failed\n";
                return false;
            }
            body->resize(len - pos);
            ifs.read(&(*body)[0], len);
            if(ifs.good() == false)
            {
                std::cout << "getPosLen read failed\n";
                ifs.close();
                return false;
            }

            ifs.close();
            return true;
        }

        bool getContent(std::string *body)
        {
            size_t n = fileSize();

            return getPosLen(body, 0, n);
        }
        

        bool exists()
        {
            return fs::exists(_filename);
        }
        bool createDirectory()
        {
            if(exists())
                return true;
            
            return fs::create_directories(_filename);
        }
        bool getDirectory(std::vector<std::string> *arry)
        {
            for(const fs::directory_entry& entry : fs::directory_iterator{_filename})
            {
                if(fs::is_directory(entry))
                    continue;
                arry->push_back(fs::path(entry).relative_path().string());
            }

            return true;
        }

        bool compress(const std::string &packname)
        {
           
            std::string body;
            getContent(&body);
            std::string buffer = bundle::pack(bundle::LZIP, body);

            std::ofstream ofs;
            ofs.open(packname, std::ios::binary);
            if(ofs.is_open() == false)
            {
                std::cout << "compress open failed\n";
                return false;
            }
            ofs.write(&buffer[0], buffer.size());
            if(ofs.good() == false)
            {
                std::cout << "compress write failed\n";
                ofs.close();
                return false;
            }
            ofs.close();

            return true;
        }

        bool uncompress(const std::string &filename)
        {
            std::string body;
            getContent(&body);
            std::string buffer = bundle::unpack(body);

            std::ofstream ofs;
            ofs.open(filename, std::ios::binary);
            if(ofs.is_open() == false)
            {
                std::cout << "uncompress open failed\n";
                return false;
            }
            ofs.write(&buffer[0], buffer.size());
            if(ofs.good() == false)
            {
                std::cout << "uncompress write failed\n";
                ofs.close();
                return false;
            }
            ofs.close();

            return true;
        }

    private:
        std::string _filename;
    };

    class JsonUtil
    {
    public:
        static bool Serialize(const Json::Value &root, std::string *str)
        {
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

            std::stringstream ss;
            int ret = sw->write(root, &ss);
            if(ret != 0)
            {
                std::cout << "Serialize failed" << std::endl;
                return false;
            }
            
            *str = ss.str();
            return true;
        }

        static bool UnSerialize(const std::string &str, Json::Value *root)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            std::string errs;
            bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &errs);
            if(ret == false)
            {
                std::cout << "UnSerialize failed " << errs << std::endl;
                return false;
            }

            return true;
        }

    };
}

config.hpp

#pragma once
#include <mutex>
#include "util.hpp"
namespace cloud
{
#define CONFIG_FILE "./cloud.conf"
    class Config
    {
    private:
        Config()
        {
            ReadConfigFile();
        }
        bool ReadConfigFile()
        {
            fileUtil fu(CONFIG_FILE);
            std::string body; 
            bool ret = fu.getContent(&body);
            if(ret == false)
            {
                std::cout << "ReadConfigFile getContent faile" << std::endl;
            }

            Json::Value root;
            ret = cloud::JsonUtil::UnSerialize(body, &root);
            if(ret == false)
            {
                std::cout << "ReadConfigFile UnSerialize faile" << std::endl;
            }

            _hot_time = root["hot_time"].asInt();
            _server_port = root["server_port"].asInt();
            _server_ip = root["server_ip"].asString();
            _download_prefix = root["download_prefix"].asString();
            _packfile_suffix = root["packfile_suffix"].asString();
            _pack_dir = root["pack_dir"].asString();
            _back_dir = root["back_dir"].asString();
            _backup_file = root["backup_file"].asString();

        }
        
    public:
        static Config* getIstance()
        {
            if(_instance == nullptr)
            {
                _mtx.lock();
                if(_instance == nullptr)
                {
                    _instance = new Config();
                }
                _mtx.unlock();
            }
            
            return _instance;
        }
        int getHotTime()
        {
            return _hot_time;
        }
        int getServerPort()
        {
            return _server_port;
        }
        std::string getServerIp()
        {
            return _server_ip;
        }
        std::string getDownloadPrefix()
        {
            return _download_prefix;
        }
        std::string getPackfileSuffix()
        {
            return _packfile_suffix;
        }
        std::string getPackDir()
        {
            return _pack_dir;
        }
        std::string getBackDir()
        {
            return _back_dir;
        }
        std::string getBackupFile()
        {
            return _backup_file;
        }

    private:
        static Config* _instance;
        static std::mutex _mtx;
    private:
        int _hot_time;
        int _server_port;
        std::string _server_ip;
        std::string _download_prefix;
        std::string _packfile_suffix;
        std::string _pack_dir;
        std::string _back_dir;
        std::string _backup_file;
    };
    Config* Config::_instance = nullptr;
    std::mutex Config::_mtx;
} // namespace cloud

hot.hpp

#pragma once
#include <cstdio>
#include <unistd.h>
#include "data.hpp"
extern cloud::dataManager *_data;

namespace cloud
{
    class HotManager
    {
    private:
        //非热点文件返回否, 热点文件返回真
        bool hotJuge(const std::string& filename)
        {
            fileUtil fu(filename);
            time_t last_atime = fu.lastAccessTime();
            time_t cur_time = time(nullptr);
            if(cur_time - last_atime > _hot_time)
            {
                return false;
            }

            return true;
        }
    public:
        HotManager()
        {
            Config* f = Config::getIstance();
            _back_dir = f->getBackDir();
            _pack_dir = f->getPackDir();
            _packfile_suffix = f->getPackfileSuffix();
            _hot_time = f->getHotTime();

            fileUtil fu1(_back_dir);
            fileUtil fu2(_pack_dir);
            fu1.createDirectory();
            fu2.createDirectory();
            
        }
        bool runMoudle()
        {
            while(true)
            {
                //std::cout << -1 << std::endl;
                fileUtil fu(_back_dir);

                std::vector<std::string> arry;
                fu.getDirectory(&arry);
                for(auto& e : arry)
                {
                    if(hotJuge(e))
                    {
                        continue;
                    }

                    BackupInfo info;
                    bool ret = _data->getBifoByRealPath(e, &info);
                    if(ret == false)
                    {
                        std::cout << "runMoudle faile" << std::endl;
                        info.NewBackupInfo(e);
                    }

                    fileUtil fu(e);
                    fu.compress(info.pack_path);
                    fu.Remove();
                    info.pack_flag = true;
                    _data->update(info);
                }
                usleep(1000);
            }
      
            return true;
        }
    private:
        std::string _back_dir;
        std::string _pack_dir;
        std::string _packfile_suffix;
        int _hot_time;
    };
}

data.hpp

#pragma once
#include <unordered_map>
#include <pthread.h>
#include "util.hpp"
#include "config.hpp"

namespace cloud
{
    struct BackupInfo
    {
        bool pack_flag;
        size_t file_size;
        time_t modify_time;
        time_t access_time;
        std::string real_path;
        std::string pack_path;
        std::string url;

        bool NewBackupInfo(const std::string& filepath)
        {
            //std::cout << filepath << std::endl;
            fileUtil fu(filepath);
            if(fu.exists() == false)
            {
                std::cout << "NewBackupInfo fail" << std::endl;
                return false;
            }
            pack_flag = false;
            //std::cout << fu.fileSize() << std::endl;
            file_size = fu.fileSize();
            modify_time = fu.lastModifyTime();
            access_time = fu.lastAccessTime();
            real_path = filepath;

            Config* f = Config::getIstance();
            std::string packdir = f->getPackDir();
            std::string packfile_suffix = f->getPackfileSuffix();
            pack_path = packdir + fu.fileName() + packfile_suffix;

            std::string download_prefix = f->getDownloadPrefix();
            url = download_prefix + fu.fileName();
            return true;
        }
    };

    class dataManager
    {
    public:
        dataManager()
        {
            _backup_file = Config::getIstance()->getBackupFile();
            pthread_rwlock_init(&_rwlock, nullptr);
            initLoad();
        }
        bool initLoad()//初始化程序运行时从文件读取数据
        {
            fileUtil fu(_backup_file);
            if(fu.exists() == false)
            {
                return true;
            }

            std::string body;
            bool ret = fu.getContent(&body);
            if(ret == false)
            {
                std::cout << "InitLoad getContent failed" << std::endl;
                return false;
            }
            

            Json::Value root;
            ret = JsonUtil::UnSerialize(body, &root);
            if(ret == false)
            {
                std::cout << "InitLoad getContent failed" << std::endl;
                return false;
            }

            for(int i = 0; i < root.size(); i++)
            {
                BackupInfo info;
                info.pack_flag = root[i]["pack_flag"].asBool();
                info.file_size = root[i]["file_size"].asInt64();
                info.modify_time = root[i]["modify_time"].asInt64();
                info.access_time = root[i]["access_time"].asInt64();
                info.real_path = root[i]["real_path"].asString();
                info.pack_path = root[i]["pack_path"].asString();
                info.url = root[i]["url"].asString();

                //_table[info.url] = info;
                insert(info);
            } 

            return true;
        }
        bool storage() //每次有信息改变则需要持久化存储一次
        {
            Json::Value root;
            for(auto& e : _table)
            {
                Json::Value tmp;
                tmp["pack_flag"] = e.second.pack_flag;
                tmp["file_size"] = (Json::Int64)e.second.file_size;
                tmp["modify_time"] = (Json::Int64)e.second.modify_time;
                tmp["access_time"] = (Json::Int64)e.second.access_time;
                tmp["real_path"] = e.second.real_path;
                tmp["pack_path"] = e.second.pack_path;
                tmp["url"] = e.second.url;

                root.append(tmp);
            }

            std::string body;
            bool ret = JsonUtil::Serialize(root, &body);
            if(ret == false)
            {
                std::cout << "Storage Serialize faile" << std::endl;
                return false;
            }

            fileUtil fu(_backup_file);
            ret = fu.setContent(body);
            if(ret == false)
            {
                std::cout << "Storage setContent faile" << std::endl;
                return false;
            }

            return true;
        }
        bool insert(const BackupInfo& Info)
        {
            pthread_rwlock_wrlock(&_rwlock);
            _table[Info.url] = Info;
            pthread_rwlock_unlock(&_rwlock);
            storage();

            return true;
        }
        bool update(const BackupInfo& Info)
        {
            pthread_rwlock_wrlock(&_rwlock);
            _table[Info.url] = Info;
            pthread_rwlock_unlock(&_rwlock);
            storage();

            return true;
        }
        bool getBifoByUrl(const std::string& url, BackupInfo* Info)
        {
            //问题这个应该是读者模式锁还是写者模式锁呢?
            pthread_rwlock_wrlock(&_rwlock);
            auto ret = _table.find(url);
            if(ret == _table.end())
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }

            *Info = ret->second;
            pthread_rwlock_unlock(&_rwlock);
            return true;

        }
        bool getBifoByRealPath(const std::string& realPath, BackupInfo* Info)
        {
            pthread_rwlock_wrlock(&_rwlock);
            for(auto& e : _table)
            {
                if(e.second.real_path == realPath)
                {
                    *Info = e.second;
                    pthread_rwlock_unlock(&_rwlock);
                    return true;
                }
            }

            pthread_rwlock_unlock(&_rwlock);
            return false;
        }
        bool getAll(std::vector<BackupInfo> *arry)
        {
            pthread_rwlock_wrlock(&_rwlock);
            for(auto& e : _table)
            {
                arry->push_back(e.second);
            }
            pthread_rwlock_unlock(&_rwlock);

            return true;
        }
        ~dataManager()
        {
            pthread_rwlock_destroy(&_rwlock);
        }
    private:
        std::string _backup_file;
        pthread_rwlock_t _rwlock;
        std::unordered_map<std::string, BackupInfo> _table;
    };
}

server.hpp

#pragma once
#include "data.hpp"
#include "httplib.h"

extern cloud::dataManager *_data;
namespace cloud
{
    class serevr
    {
    private:
        static void upLoad(const httplib::Request& rq, const httplib::Response& rp)
        {
            bool ret = rq.has_file("file");
            if(ret == false)
            {
                return ;
            }

            const auto& file = rq.get_file_value("file");
            std::string real_path = _back_dir + fileUtil(file.filename).fileName();
            fileUtil fu(real_path);
            fu.setContent(file.content);

            BackupInfo info;
            info.NewBackupInfo(real_path);
            _data->insert(info);
            return;

        }
        static std::string timeToString(time_t t)
        {
            return std::ctime(&t);
        }
        static void listShow(const httplib::Request& rq, httplib::Response& rp)
        {
            std::vector<BackupInfo> arry;
            _data->getAll(&arry);
            std::stringstream ss;
            ss << "<html><head><title>Download</title></head>";
            ss << " <body><h1>Download</h1><table>";
            for(auto& e : arry)
            {
                ss << "<tr>";

                std::string filename = fileUtil(e.real_path).fileName();
                ss << "<td><a href='" << e.url << "'>" << filename << "</a></td>";

                ss << "<td align='right'>";
                ss << timeToString(e.modify_time);
                ss << "</td>";

                ss << "<td align='right'>";
                ss << e.file_size / 1024 << "K";
                ss << "</td>";

                ss << "</tr>";
            }
            ss << "</table></body></html>";
            rp.body = ss.str();
            rp.set_header("Content-Type", "text/html");
            rp.status = 200;

        }
        static std::string getETagInfo(const BackupInfo& info)
        {
            std::string etag;
            etag += fileUtil(info.real_path).fileName();
            etag += "-";
            etag += std::to_string(info.file_size);
            etag += "-";
            etag += std::to_string(info.modify_time);

            return etag;
        }
        static void downLoad(const httplib::Request& rq, httplib::Response& rp)
        {
            std::string url = rq.path;
            //std::cout << url << std::endl;
            BackupInfo info;
            _data->getBifoByUrl(url, &info);
            //std::cout << info.real_path << std::endl;

            if(info.pack_flag == true)
            {
                //解压文件
                fileUtil fu(info.pack_path);
                fu.uncompress(info.real_path);
                
                //删除压缩文件, 并修改BackupInfo信息
                fu.Remove();
                info.pack_flag = false;
                _data->insert(info);
            }

            // if(rq.has_header("If-Range"))
            //     std::cout << "hello" << std::endl;
            // else
            //     std::cout << "no" << std::endl;
            // for(auto& e : rp.headers)
            // {
            //     std::cout << e.second << std::endl;
            // }

            fileUtil fu(info.real_path);
            fu.getContent(&rp.body);
            rp.set_header("Accept-Ranges", "bytes");
            rp.set_header("ETag", getETagInfo(info));
            rp.set_header("Content-Type", "application/octet-stream");
            //rp.status = 200;

            if(rq.has_header("If-Range") && rq.get_header_value("If-Range") == getETagInfo(info))
            {
                rp.status = 206;
                //std::cout << rp.status << std::endl;
            }
            else
            {
                rp.status = 200;
            }
        }
    public:
        serevr()
        {
            Config* cnf = Config::getIstance();
            _server_port = cnf->getServerPort();
            _server_ip = cnf->getServerIp();
            _download_prefix = cnf->getDownloadPrefix();
            _back_dir = cnf->getBackDir();
        }
        bool RunModule()
        {
            _server.Post("/upload",upLoad);
            _server.Get("/listshow", listShow);
            _server.Get("/", listShow);
            std::string url = _download_prefix + "(.*)";
            _server.Get(url,downLoad);
            _server.listen("0.0.0.0", _server_port);
        
            return true;
        }
    private:
        int _server_port;
        std::string _server_ip;
        std::string _download_prefix;
        static std::string _back_dir;
        httplib::Server _server;
    };
    std::string serevr::_back_dir;
}

server.cc

#include "util.hpp"
#include "config.hpp"
#include "data.hpp"
#include "hot.hpp"
#include "server.hpp"
#include <thread>

cloud::dataManager *_data;

void server()
{
    cloud::serevr s;
    s.RunModule();
}
void hot()
{
    cloud::HotManager h;
    h.runMoudle();
}
int main(int argc, char *argv[])
{
    // if(argc != 2)
    // {
    //     std::cout << argv[0] << " filepath newfilename" << std::endl;
    //     exit(1);
    // }

    // std::string name = argv[1];
    // cloud::fileUtil f(name);
    // std::cout << f.fileSize() << std::endl;
    // std::cout << f.lastModifyTime() << std::endl;
    // std::cout << f.lastAccessTime() << std::endl;
    // std::cout << f.fileName() << std::endl;
    // std::string buffer;
    // f.getContent(&buffer);

    // name = argv[2];
    // cloud::fileUtil f2(name);
    // f2.setContent(buffer);

    // std::string name = argv[1];
    // cloud::fileUtil f(name);
    // f.compress(name += ".lz");
    // f.uncompress(name += ".backup");

    // cloud::fileUtil f(name);
    // f.createDirectory();
    // std::vector<std::string> arry;
    // f.getDirectory(&arry);
    // for(auto& e : arry)
    // {
    //     std::cout << e << std::endl;
    // }

    // const char* name = "xiaolion";
    // int age = 18;
    // int score[] = {100, 98, 89};

    // Json::Value root;
    // root["name"] = name;
    // root["age"] = age;
    // root["socre"].append(score[0]);
    // root["socre"].append(score[1]);
    // root["socre"].append(score[2]);

    // std::string str;
    // cloud::JsonUtil::Serialize(root, &str);
    // std::cout << str << std::endl;

    // Json::Value val;
    // cloud::JsonUtil::UnSerialize(str, &val);
    // std::cout << val["name"].asString() << std::endl;
    // std::cout << val["age"].asInt() << std::endl;
    // std::cout << val["socre"][0].asInt() << std::endl;
    // std::cout << val["socre"][1].asInt() << std::endl;
    // std::cout << val["socre"][2].asInt() << std::endl;

    // cloud::Config* f = cloud::Config::getIstance();
    // std::cout << f->getHotTime() << std::endl;
    // std::cout << f->getServerPort() << std::endl;
    // std::cout << f->getServerIp() << std::endl;
    // std::cout << f->getDownloadPrefix() << std::endl;
    // std::cout << f->getPackfileSuffix() << std::endl;
    // std::cout << f->getPackDir() << std::endl;
    // std::cout << f->getBackDir() << std::endl;
    // std::cout << f->getBackupFile() << std::endl;

    // cloud::BackupInfo f;
    // f.NewBackupInfo(argv[1]);
    // cloud::fileUtil f1(argv[1]);
    // std::cout << f1.fileSize() << std::endl;
    // std::cout << argv[1] << std::endl;
    // std::cout << f.pack_flag << std::endl;
    // std::cout << f.file_size << std::endl;
    // std::cout << f.modify_time << std::endl;
    // std::cout << f.access_time << std::endl;
    // std::cout << f.real_path << std::endl;
    // std::cout << f.pack_path << std::endl;
    // std::cout << f.url << std::endl;
    // cloud::dataManager d;
    // d.insert(f);
    // f.pack_flag = true;
    // d.update(f);

    // cloud::BackupInfo tmp;
    // d.getBifoByRealPath(argv[1], &tmp);
    // std::cout << tmp.pack_flag << std::endl;
    // std::cout << tmp.file_size << std::endl;
    // std::cout << tmp.modify_time << std::endl;
    // std::cout << tmp.access_time << std::endl;
    // std::cout << tmp.real_path << std::endl;
    // std::cout << tmp.pack_path << std::endl;
    // std::cout << tmp.url << std::endl;

    // cloud::BackupInfo tmp2;
    // d.getBifoByUrl(f.url, &tmp2);
    // std::cout << tmp2.pack_flag << std::endl;
    // std::cout << tmp2.file_size << std::endl;
    // std::cout << tmp2.modify_time << std::endl;
    // std::cout << tmp2.access_time << std::endl;
    // std::cout << tmp2.real_path << std::endl;
    // std::cout << tmp2.pack_path << std::endl;
    // std::cout << tmp2.url << std::endl;

    // std::vector<cloud::BackupInfo> arry;
    // d.getAll(&arry);
    // for(auto& e : arry)
    // {
    //     std::cout << e.pack_flag << std::endl;
    //     std::cout << e.file_size << std::endl;
    //     std::cout << e.modify_time << std::endl;
    //     std::cout << e.access_time << std::endl;
    //     std::cout << e.real_path << std::endl;
    //     std::cout << e.pack_path << std::endl;
    //     std::cout << e.url << std::endl;
    // }

    // cloud::BackupInfo f;
    // f.NewBackupInfo(argv[1]);
    // cloud::dataManager d;
    // d.insert(f);

    // cloud::dataManager d;
    // std::vector<cloud::BackupInfo> arry;
    // d.getAll(&arry);
    // for(auto& e : arry)
    // {
    //     std::cout << e.pack_flag << std::endl;
    //     std::cout << e.file_size << std::endl;
    //     std::cout << e.modify_time << std::endl;
    //     std::cout << e.access_time << std::endl;
    //     std::cout << e.real_path << std::endl;
    //     std::cout << e.pack_path << std::endl;
    //     std::cout << e.url << std::endl;
    // }

    //_data = new cloud::dataManager();
    // cloud::HotManager ht;
    // ht.runMoudle();
    // cloud::serevr srv;
    // srv.RunModule();

    // httplib::Server _server;
    // _server.Post("/upload", upLoad);
    // _server.Get("/listshow", listShow);
    // _server.Get("/", listShow);
    // _server.Get("/", downLoad);
    // _server.listen("", 8080);

    _data = new cloud::dataManager();
    std::thread thread_hot(hot);
    std::thread thread_server(server);

    thread_hot.join();
    thread_server.join();
    return 0;
}

Makefile

.PHONY:util
cloudServer:cloudServer.cc
	g++ -o $@ $^ -L./lib -lpthread -lstdc++fs -ljsoncpp -lbundle

cloud.conf

{
    "hot_time" : 30,
    "server_port" : 8080,
    "server_ip" : "-.-.-.-",
    "download_prefix" : "/download/",
    "packfile_suffix" : ".lz",
    "pack_dir" : "./packdir/",
    "back_dir" : "./backdir/",
    "backup_file" : "./cloud.dat"
}

最近更新

  1. TCP协议是安全的吗?

    2024-06-12 08:26:06       14 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-12 08:26:06       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-12 08:26:06       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-12 08:26:06       18 阅读

热门阅读

  1. 设计模式-解释器模式

    2024-06-12 08:26:06       6 阅读
  2. [数据结构]——双链表

    2024-06-12 08:26:06       6 阅读
  3. python实现无人机航拍图片像素坐标转世界坐标

    2024-06-12 08:26:06       6 阅读
  4. 14、架构-可靠通讯之零信任网络

    2024-06-12 08:26:06       5 阅读
  5. 如何在小程序中实现页面之间的返回

    2024-06-12 08:26:06       6 阅读