背景:
儿子最近喜欢上了用儿童手表听故事,但是手表边里的应用免费内容很少,会员一年要300多,这么一笔巨款,怎能承担的起,所以打算自己开发一个专属于儿子的听书app。
最终效果:

架构:

后端由两个服务组成,一个文件服务用于预览图片和在线听故事。一个接口服务用于 获取故事列表和某个故事的详细内容。
前端app用flutter开发,一共三个页面。故事列表页,详细故事页,和播放页面。
服务端代码:
#include "crow.h"
#include <crow/json.h>
#include <iostream>
#include <dirent.h>
#include <string>
#include <vector>
#include <initializer_list>
using namespace std;
// 获取文件后缀名
std::string getFileExtension(const std::string& filename) {
    size_t lastDot = filename.find_last_of(".");
    if (lastDot != std::string::npos) {
        return filename.substr(lastDot + 1);
    }
    return ""; // 如果没有后缀,返回空字符串或其他你认为合适的默认值
}
//遍历文件夹
std::vector<string> scanDir(const char* dir, initializer_list<string> subStrinList){
    DIR* directory;
    struct dirent* entry;
    //获取所有后缀
    string suffixList("");
	for(auto beg=subStrinList.begin(); beg!=subStrinList.end(); ++beg)
		suffixList += *beg;
    std::cout<<"后缀 "<<suffixList<<std::endl;
    std::vector<std::string> allFile;
    directory = opendir(dir);
    if(directory!=nullptr){
        while((entry=readdir(directory))!=nullptr){
            std::string filename = entry->d_name;
            std::string fileSuffix = getFileExtension(filename);
            if(filename!="."&&filename!=".."&&fileSuffix.length()>0&&suffixList.find(fileSuffix)!=std::string::npos){
                allFile.push_back(filename);
            }
        }
        closedir(directory);
    }else{
         std::cerr << "Failed to open directory." << std::endl;
    }
    return allFile;
}
std::string url_decode(const std::string &str) {
    std::string result;
    std::istringstream iss(str);
    char ch;
    int i;
    while (iss >> std::noskipws >> ch) {
        if (ch == '%') {
            if (!(iss >> std::hex >> i)) {
                break;
            }
            result += static_cast<char>(i);
        } else if (ch == '+') {
            result += ' ';
        } else {
            result += ch;
        }
    }
    return result;
}
int main(int argc, char **argv)
{
    crow::SimpleApp app;
    CROW_ROUTE(app, "/")
    ([]()
    {
        crow::response res;
        res.set_header("Content-Type", "text/plain; charset=utf-8");
        res.body = "你好啊,小朋友!";
        return res;
    });
    // 获取所有条目
    CROW_ROUTE(app, "/getAllEntry")
    ([]()
     {
        crow::response res;
        res.set_header("Content-Type", "text/plain; charset=utf-8");
        crow::json::wvalue j;
        std::vector<std::string> allEntry = scanDir("./data",{"jpg"});
        for(int i=0;i<allEntry.size();i++){
            j[i]=allEntry.at(i);
        }
        std::cout<<"allEntry "<<crow::json::dump(j)<<std::endl;
        res.body = crow::json::dump(j);
        return res; 
    });
    //获取某个条目的所有内容
    CROW_ROUTE(app, "/getDetailEntry/<string>")
    ([](string name)
     {
        std::string decodedQuery = url_decode(name);
        std::cout<<"getDetailEntry "<<decodedQuery<<std::endl;
        crow::response res;
        res.set_header("Content-Type", "text/plain; charset=utf-8");
        std::string dir=std::string("./data/")+decodedQuery;
        crow::json::wvalue j;
        std::vector<std::string> allFile = scanDir(dir.data(),{"mp3","m4a"});
        for(int i=0;i<allFile.size();i++){
            j[i]=allFile.at(i);
        }
        std::cout<<"allFile "<<crow::json::dump(j)<<std::endl;
        res.body = crow::json::dump(j);
        return res; 
    });
    app.port(18080).multithreaded().run();
}
 
内容:
当有了新的故事后,只需要准备一张故事的预览图,然后一起放到服务器上即可。



















