目录
一、应用数据库
二、登录记忆
三、新建应用
四、获取应用列表
五、重命名应用
本项目的交流QQ群:701889554
物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html
物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html
本项目资源文件https://download.csdn.net/download/ypp240124016/89301894
sqlite数据库查看软件 https://download.csdn.net/download/ypp240124016/89302020
一、应用数据库
在账户数据库中,除了账户表,还有个应用表,它的函数列表和示例内容如下两图所示,主要也是包含了建表、插入和更新等操作。
//建立应用表
bool AccountSqlite::createAppListTable(void)
{
QString str_query = QString::asprintf("CREATE TABLE If Not Exists app_id_list ("
"id INTEGER NOT NULL,"
"app_id bigint NOT NULL UNIQUE,"
"app_name char(30) DEFAULT NULL,"
"creator char(30) DEFAULT NULL,"
"create_time timestamp DEFAULT (datetime(\'now\',\'localtime\')),"
"PRIMARY KEY (id)"
")"
);
if(runSqlQuery(str_query)==false)
{
qDebug("createAppLisTable error!");
return false;
}
return true;
}
//添加应用ID
bool AccountSqlite::addAppIDToList(u32 app_id, QString creator)
{
QString str_query = QString::asprintf("INSERT INTO app_id_list (app_id, app_name, creator) VALUES ( %u, \"%s\" , \"%s\" )", app_id, "新应用",creator.toUtf8().data());
if( runSqlQuery(str_query))
{
qDebug("addAppIDToList ok!");
AccountNodeStruct tag_account;
tag_account.account.clear();
selectAccountByName(creator, tag_account);
if(tag_account.account.isEmpty()==false)
{
int nSize=tag_account.appList.size();
for(int i=0; i<nSize; i++)
{
if(tag_account.appList.at(i)==app_id)
{
return true;
}
}
tag_account.appList.append(app_id);
updateAccountAppList(creator, tag_account.appList);//更新应用列表
}
return true;
}
qDebug("addAppIDToList failed!");
return false;
}
//更新应用名称
bool AccountSqlite::updateAppName(u32 app_id, QString app_name)
{
QString str_query = QString::asprintf("UPDATE app_id_list SET app_name=\"%s\" WHERE app_id=%u",
app_name.toUtf8().data(), app_id);
// qDebug()<<str_query;
if( runSqlQuery(str_query))
{
qDebug("updateAppName ok!");
return true;
}
return false;
}
//获取应用名称
QString AccountSqlite::selectAppName(u32 app_id)
{
QString app_name="";
QString str_query = QString::asprintf("select app_name from app_id_list where app_id=%u", app_id);
if( runSqlQuery(str_query))
{
while(m_sqlQuery.next())
{
app_name=m_sqlQuery.value(0).toString();
break;
}
m_sqlQuery.finish();
}
return app_name;
}
//查询应用节点信息
bool AccountSqlite::selectAppInfo(u32 app_id, AppNodeStruct &app_node)
{
QString str_query = QString::asprintf("select app_id, app_name, creator, create_time from app_id_list where app_id=%u", app_id);
// qDebug()<<str_query;
if( runSqlQuery(str_query))
{
while(m_sqlQuery.next())
{
int ptr=0;
app_node.appID=m_sqlQuery.value(ptr++).toUInt();
app_node.appName=m_sqlQuery.value(ptr++).toString();
app_node.creator=m_sqlQuery.value(ptr++).toString();
app_node.createTime=m_sqlQuery.value(ptr++).toString();
m_sqlQuery.finish();
return true;
}
m_sqlQuery.finish();
}
return false;
}
//获取最大的应用ID
u32 AccountSqlite::selectMaxAppID(void)
{
u32 max_app=0;
QString str_query = QString::asprintf("SELECT app_id FROM app_id_list ORDER BY app_id DESC limit 10");
if(runSqlQuery(str_query)==false)
{
qDebug("selectMaxAppID error_01!");
return max_app;
}
while(m_sqlQuery.next())
{
max_app=m_sqlQuery.value(0).toUInt();
break;
}
m_sqlQuery.finish();
// qDebug()<<"selectAppList="<<app_list;
return max_app;
}
//获取该账户下的应用数量
u32 AccountSqlite::getAppCountFromAccount(QString account)
{
u32 app_cnts=0;
QString str_query = QString::asprintf("select app_id from app_id_list where creator=\"%s\"", account.toUtf8().data());
// qDebug()<<str_query;
if( runSqlQuery(str_query))
{
while(m_sqlQuery.next())
{
app_cnts++;
}
}
return app_cnts;
}
以上是应用相关的数据库函数,这里面比较特殊的是selectMaxAppID,用来查询最大的应用ID,这样用户在新建应用时候就知道该分配什么ID给他了,这里面用DESC依据app_id字段降序查询,这样第一个就是最大的应用ID了;还有一个getAppCountFromAccount用来获取某个账户下已经存在的应用数量,目的是为了判断改用户能否再新建应用,我这对这个有数量限制,目前是最多8个应用,这个可以自己更改,或者根据账户的权限等级自己去分配。
二、登录记忆
在实际使用APP过程中,为了方便用户,一般在一定时间内(比如一星期)可以免登录,直接跳转到主界面进行使用了,那么,我们这边也使用这种模式。首次打开时,跳转到验证码登录页面,引导用户登录,正常操作登录后,用户端会保存当前账户和登录时间,密码不保存;在下次打开APP的时候,内部程序会先读取保存数据,如果有账户并且时间未过期,那么就以这个账户直接获取该账户下的应用列表,完成登录过程,这个过程用户无感,只需等待几秒即可,增强体验感。下面看下具体实现。
首先需要一个密码保护配置文件,这个密码根据每个手机的MAC地址决定,这样配置文件就不会被复制利用了,具体的密码生成可以使用自己的方法,这里只是个参考。
接下来就是读取和保存配置的内容了,writeConfig比较简单,就是保存当前登录账户和时间;读取readConfig后,需要检查账户是否符合要求,另外时间上我这里定的是3天以内,如果这两个条件满足要求,就直接免登录 ,同时再保存一次更新时间,如果这里不更新保存也行的,意味着3天后需要强制重新登录,相对安全些,就看自己要如何权衡便捷与安全了。
另外,drv_com.readConfg和drv_com.writeConfg内部会根据密码自动加解密,如果不需要加密,那就密码传入NULL即可。
void AccountMan::readConfig(void)
{
QString path=m_rootPath+"/account.txt";
QJsonObject root_obj=drv_com.readConfg(path, m_keyBuff);
if(root_obj.contains("account"))
{
QString account_str=root_obj.value("account").toString();
qDebug()<<"account_str="<<account_str;
m_accountWork.account=account_str;
}
if(root_obj.contains("login_time"))
{
qint64 login_time=root_obj.value("login_time").toDouble();
qDebug()<<"login_time="<<login_time;
m_accountWork.login_time=login_time;
}
qint64 det_time=QDateTime::currentDateTime().toTime_t()-m_accountWork.login_time;
qDebug()<<"det_time="<<det_time;
if(checkAccount(m_accountWork.account)==0 && det_time<86400*3)
{
emit siqSetLoginState(1);//免登录,发给前端
writeConfig();//更新登录时间
emit sigUpdateLoginAccount(m_accountWork.account, 1);//发给控制中心
}
else
{
emit siqSetLoginState(0);//引导登录
}
}
void AccountMan::writeConfig(void)
{
QString path=m_rootPath+"/account.txt";
QJsonObject root_obj;
QString account_str=m_accountWork.account;
root_obj.insert("account", account_str);
root_obj.insert("login_time", (qint64)QDateTime::currentDateTime().toTime_t());
drv_com.writeConfig(path, root_obj, m_keyBuff);
}
三、新建应用
账户注册的时候后台系统会自动创建一个应用,如果不够用,当前系统允许再新建7个应用,新建的过程也比较常规,就是请求——创建——回复。在这里,应用管理需要单独区分了,跟以后的分组管理和设备管理统一规划到中心管理去,就是下图新建的文件类;相应的,需要添加订阅的话题和组合新的发布话题。应用相关的订阅话题是yyy125/as/pub/center/AC:5A:FC:C7:15:3D/28454/app。
在CenterMan类中,请求代码如下,核心参数是new_name,由前端输入,这样服务器后台会直接更新数据库内的应用名称。
void CenterMan::requestNewApp(QString new_name)
{
QJsonObject root_obj;
QJsonDocument json_doc;
root_obj.insert("cmd_type", "new_app");
root_obj.insert("account", m_loginAccount);
root_obj.insert("new_name", new_name);
root_obj.insert("rand_num", m_randNum);
root_obj.insert("mac", m_macStr);
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic("app");
emit sigMqttPushMessage(topic, msg_ba);
}
以下是后台服务器新建应用部分的代码,基本流程是检查账户合法性、检查应用数量、获取最大应用ID和添加应用。回复的时候同样会返回应用ID和名称。
if(cmd_type=="new_app")//新建应用
{
qDebug()<<"account= "<<account<<" req new app!";
AccountSqlite::AccountNodeStruct tag_account;
AccountSqlite::AppNodeStruct tag_app;
u32 new_app_id=0;
tag_app.appID=0;
tag_account.parentAccount="";
m_accountSqlite->selectAccountByName(account, tag_account);
qDebug("@@tag_account.auth=0x%08X", tag_account.auth);
if(tag_account.account.isEmpty()==true)
{
qDebug()<<"account"<<account<<" is not found!";
ackNewAppState(account, mac_str, rand_num, 0, "",1, account+" 未找到账号!");
return;
}
if(tag_account.parentAccount.isEmpty()==false)//检测是否为根账号
{
qDebug()<<"account="<<account<<" is not a root count!";
ackNewAppState(account, mac_str, rand_num, 0, "",1, account+" 该账号不是主账号!");
return;
}
int app_cnts=m_accountSqlite->getAppCountFromAccount(account);//当前的应用数量
if(app_cnts>=8)//限制每个账号创建应用的数量
{
ackNewAppState(account, mac_str, rand_num, 0, "",1, account+" 应用数量已达上限!");
return;
}
u32 max_app=m_accountSqlite->selectMaxAppID();
if(max_app>APP_ID_MIN)
new_app_id=max_app+1;
else
new_app_id=APP_ID_MIN+1;
qDebug()<<"new_app_id="<<new_app_id;
bool ok=m_accountSqlite->addAppIDToList(new_app_id, account);
if(ok)
{
QString new_name="新应用";
if(root_obj.contains("new_name"))//命令
{
QJsonValue value = root_obj.value("new_name");
new_name=value.toString();
}
m_accountSqlite->updateAppName(new_app_id, new_name); //更新数据库内应用名称
ackNewAppState(account, mac_str, rand_num, new_app_id, new_name, 0, "创建成功!");
qDebug()<<"111 addAppIDToList ok, new_app_id="<<new_app_id<<", account="<<account;
}
else
{
ackNewAppState(account, mac_str, rand_num, 0, "",1, "创建失败!");
}
}
void CenterThread::ackNewAppState(QString account, QString mac_str, int rand_num, u32 app_id, QString app_name, int result, QString ack_str)
{
QJsonObject root_obj;
QJsonDocument json_doc;
root_obj.insert("cmd_type", "new_app");
root_obj.insert("app_id", (qint64)app_id);
root_obj.insert("app_name", app_name);
root_obj.insert("create_time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
root_obj.insert("result", result);
root_obj.insert("ack_str", ack_str);
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic(account, mac_str, rand_num, "app");
emit sigMqttPushMessage(topic, msg_ba);
}
以下是用户端对返回结果的解析和处理方式。
if(cmd_type=="new_app")//返回新应用
{
u32 app_id=0;
if(root_obj.contains("app_id"))
{
QJsonValue value = root_obj.value("app_id");
if(value.isDouble())
{
app_id=value.toDouble();
}
}
if(app_id>0)
{
QString app_name="新应用";
if(root_obj.contains("app_name"))
{
app_name=root_obj.value("app_name").toString();
}
AppWorkStruct tag_work_app;
tag_work_app.appID=app_id;
tag_work_app.appName=app_name;
m_appWorkList.append(tag_work_app);
}
}
四、获取应用列表
登录成功后的第一件事就是获取当前账户下的应用列表,这样才能进行后续工作,请求代码如下:
void CenterMan::requestAppList(void)
{
QJsonObject root_obj;
QJsonDocument json_doc;
root_obj.insert("account", m_loginAccount);
root_obj.insert("rand_num", m_randNum);
root_obj.insert("mac", m_macStr);
root_obj.insert("cmd_type", "app_list");
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic("app");
emit sigMqttPushMessage(topic, msg_ba);
}
服务端代码如下,回复的内容相对繁琐,要一个个组成json数组发送回应。
else if(cmd_type=="app_list")//获取应用列表
{
AccountSqlite::AccountNodeStruct tag_account;
tag_account.account.clear();
tag_account.appList.clear();
m_accountSqlite->selectAccountByName(account, tag_account);
if(tag_account.account.isEmpty()==false)
{
ackReqAppListState(account, mac_str, rand_num, tag_account.appList, 0, "应用获取成功!");
}
}
void CenterThread::ackReqAppListState(QString account, QString mac_str, int rand_num, QList<u32> app_list, int result, QString ack_str)
{
QJsonArray app_array;
QJsonObject root_obj;
QJsonDocument json_doc;
for(int i=0; i<app_list.size(); i++)
{
u32 app_id=app_list.at(i);
if(app_id>0)
{
AccountSqlite::AppNodeStruct tag_app_node;
m_accountSqlite->selectAppInfo(app_id, tag_app_node);
QJsonObject app_obj;
app_obj.insert("app_id", (qint64)app_id);
app_obj.insert("app_name", tag_app_node.appName);
app_obj.insert("create_time", tag_app_node.createTime);
app_array.append(app_obj);
}
}
root_obj.insert("cmd_type", "app_list");
root_obj.insert("account", account);
root_obj.insert("app_list", app_array);
root_obj.insert("result", result);
root_obj.insert("ack_str", ack_str);
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic(account, mac_str, rand_num, "app");
emit sigMqttPushMessage(topic, msg_ba);
}
用户端解析如下,有一个细节处理需要处理,就是上次操作的应用ID会保存在配置文件内,需要将此app_id跟当前应用列表逐一对比,如果有匹配则继续将当前app_id作为激活状态,如果没有,那就默认使用列表的第一个app_id,用户切换后会自动保存激活的app_id。
else if(cmd_type=="app_list")//返回应用列表
{
if(root_obj.contains("app_list"))
{
QJsonValue value=root_obj.value("app_list");
if(value.isArray())
{
m_appWorkList.clear();//清除APP列表
bool curr_app_flag=false;
QJsonArray app_array=value.toArray();
qDebug()<<app_array;
int nSize=app_array.size();
for(int i=0; i<nSize; i++)
{
QJsonValue value=app_array.at(i);
if(value.isObject())
{
AppWorkStruct tag_app_work;
tag_app_work.appID=0;
QJsonObject app_obj=value.toObject();
if(app_obj.contains("app_name"))
{
QJsonValue value=app_obj.value("app_name");
tag_app_work.appName=value.toString();
}
if(app_obj.contains("app_id"))
{
QJsonValue value=app_obj.value("app_id");
if(value.isDouble())
{
u32 app_id=value.toDouble();
if(app_id>0)
{
tag_app_work.appID=app_id;
if(m_currAppWork.appID==app_id)
{
m_currAppWork.appName=tag_app_work.appName;
curr_app_flag=true;
}
}
}
}
if(app_obj.contains("create_time"))
{
QJsonValue value=app_obj.value("create_time");
tag_app_work.createTime=value.toString();
}
if(tag_app_work.appID>0)
{
m_appWorkList.append(tag_app_work);
}
}
}
if(curr_app_flag==false && m_appWorkList.size()>0)//没有默认应用
{
m_currAppWork=m_appWorkList.first();
}
emit siqUpdateCurrAppName(m_currAppWork.appID, m_currAppWork.appName);
writeConfig();
}
}
}
五、重命名应用
用户有时候需要更改应用名称,具体代码如下,只需要传入应用ID和新名称即可。
void CenterMan::requestRenameApp(qint64 app_id, QString new_name)
{
if(app_id==0)
return;
QJsonObject root_obj;
QJsonDocument json_doc;
root_obj.insert("account", m_loginAccount);
root_obj.insert("cmd_type", "rename");
root_obj.insert("rand_num", m_randNum);
root_obj.insert("mac", m_macStr);
root_obj.insert("app_id", (qint64)app_id);
root_obj.insert("app_name", new_name);
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic("app");
emit sigMqttPushMessage(topic, msg_ba);
}
服务器端操作也相对简单,数据库进行更新操作即可。
else if(cmd_type=="rename")//重命名应用
{
u32 app_id=0;
if(root_obj.contains("app_id"))
{
QJsonValue value=root_obj.value("app_id");
if(value.isDouble())
{
app_id=value.toDouble();
}
}
QString app_name="新名称";
if(root_obj.contains("app_name"))
{
QJsonValue value=root_obj.value("app_name");
if(value.isString())
{
app_name=value.toString();
}
}
if(app_id>0)
{
m_accountSqlite->updateAppName(app_id, app_name);
ackRenameAppState(account, mac_str, rand_num, app_id, app_name, 0, "更新成功!");
}
else
{
ackRenameAppState(account, mac_str, rand_num, app_id, app_name, 1, "更新失败!");
}
}
void CenterThread::ackRenameAppState(QString account, QString mac_str, int rand_num, u32 app_id, QString app_name, int result, QString ack_str)
{
QJsonObject root_obj;
QJsonDocument json_doc;
root_obj.insert("cmd_type", "rename");
root_obj.insert("app_id", (qint64)app_id);
root_obj.insert("app_name", app_name);
root_obj.insert("result", result);
root_obj.insert("ack_str", ack_str);
json_doc.setObject(root_obj);
QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
QString topic=makePubTopic(account, mac_str, rand_num, "app");
emit sigMqttPushMessage(topic, msg_ba);
}
用户端解析后将新名称更新到前端,完成闭环。
else if(cmd_type=="rename")//返回重命名
{
u32 app_id=0;
if(root_obj.contains("app_id"))
{
QJsonValue value = root_obj.value("app_id");
if(value.isDouble())
{
app_id=value.toDouble();
}
}
QString app_name="";
if(root_obj.contains("app_name"))
{
QJsonValue value = root_obj.value("app_name");
if(value.isString())
{
app_name=value.toString();
}
}
if(app_id>0)
{
int i=0;
for(auto iter : m_appWorkList)
{
if(iter.appID==app_id)
{
m_appWorkList[i].appName=app_name;
}
i++;
}
emit siqUpdateCurrAppName(app_id, app_name);
}
}
目前账户和应用都没设计删除功能,主要是为了避免引起不必要的麻烦,比如我们的app_id是增长型的,每创建一个应用都会先查找最大的app_id,然后再+1,如果删除了,怕会出现混乱,所以就不提供此功能了。