背景:
儿子最近喜欢上了用儿童手表听故事,但是手表边里的应用免费内容很少,会员一年要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();
}
内容:
当有了新的故事后,只需要准备一张故事的预览图,然后一起放到服务器上即可。