物联网实战--平台篇之(三)账户后台数据库

目录

一、账户后台设计

二、账户数据库

三、数据库操作------增

四、数据库操作------改

五、数据库操作------查


本项目的交流QQ群:701889554

物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html

物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html

本项目资源文件https://download.csdn.net/download/ypp240124016/89280540

一、账户后台设计

上图是应用服务器里的帐号相关服务,包括账户数据库和软件服务模块。在这里,我们的账户模块设计是比较基础简单的,就是常规的请求手机验证码、注册、登录和重置密码等流程,修改密码是登录后的操作,放在其它文章讲。这些操作是建立在上一篇文章的工程基础之上的,之前已经完成了MQTT网络部分和基础的数据库部分,那么这一章以账户后台为切入点,主要讲解下数据库的具体使用。

二、账户数据库

工欲善其事必先利其器,在设计注册、登录等流程之前,肯定要预先设计好数据库的,这是根本,对于用户的这些动作才能有对应的操作对象,比如用户登录帐号,把帐号密码发到服务器了,后台服务肯定要对比验证,那比较的对象在哪儿------自然就是数据库里的账户信息了。

之前说过,为了方便,我们采用的是sqlite文件数据库,上图是当前数据库的信息。根据数据库的基本知识,数据库是由一堆的库表组成的,每张表有各自的作用,表之间通过一些关键字段相互联系。先看账户列表的数据库表,即account_list_tb,包含了id、账户名、密码、权限、父账号、关联手机号、帐号下的所属应用和创建时间,接下来看下这个库表是如何建立的。

cpp 复制代码
#ifndef ACCOUNTSQLITE_H
#define ACCOUNTSQLITE_H

#include <QObject>
#include "BaseSqlite.h"

class AccountSqlite : public BaseSqlite
{
    Q_OBJECT
public:
    typedef struct
    {
        QString account;  //账户名
        QString passWord;  //密码
        u32 auth;  //权限值
        QString parentAccount; //父账号
        QString phone; //关联手机号
        QList<u32>appList;  //名下的应用列表
        QString createTime; //创建时间
    }AccountNodeStruct;  //账户节点

    typedef struct
    {
        u32 appID;   //应用ID
        QString appName; //应用名称
        QString creator;  //建立者的账户名
        QString createTime; //创建时间
    }AppNodeStruct;  //应用节点
    
public:
    explicit AccountSqlite(QObject *parent = nullptr);
    
    bool createAccountListTable(void);
    bool addAccountNode(QString account, QString pass_word, u32 auth, QString parent_account, QString phone);
    bool updateAccountPassWord(QString account, QString pass_word);
    bool updateAccountPhone(QString account, QString phone);
    bool updateAccountAuth(QString account, u32 auth);
    bool updateAccountAppList(QString account, QList<u32>app_list);
    bool selectAccountByName(QString account, AccountNodeStruct &account_node);
    bool selectAccountByPhone(QString phone, AccountNodeStruct &account_node);
    bool selectChildAccountList(QString parent_account, QList<AccountNodeStruct>&child_list);
    bool searchAccountByName(QString account, AccountNodeStruct &account_node);
    bool searchAccountByPhone(QString phone, AccountNodeStruct &account_node);
    bool selectAccountAppList(QString account, QList<u32> &app_list);
    
    bool createAppListTable(void);
    bool addAppIDToList(u32 app_id, QString creator);
    bool updateAppName(u32 app_id, QString app_name);
    QString selectAppName(u32 app_id);
    bool selectAppInfo(u32 app_id, AppNodeStruct &app_node);
    bool selectLastAppInfo(AppNodeStruct &app_node);
    bool selectAppList(QList<u32>&app_list);
    u32 selectMaxAppID(void);
    u32 getAppCountFromAccount(QString account);

private:
    u8 m_keyBuff[16];
    DrvCommon drv_com;
signals:
    
};

#endif // ACCOUNTSQLITE_H

这里是账户数据库相关的头文件,首先可以看到它继承于之前创建的基础数据库驱动类BaseSqlite,接下来就是定义账户的数据结构体,这样便于后期的修改和查询;在功能上,账户相关数据库函数有建表、添加账户、更新密码、更新手机、更新权限、更新应用列表、根据账户名查询、根据手机号查询、子账户查询和应用列表查询等功能;具体每个功能是怎么实现的举个例子,相似度比较高。

cpp 复制代码
//建立账户列表  数据库表
bool AccountSqlite::createAccountListTable(void)
{
    QString str_query = "CREATE TABLE If Not Exists account_list_tb ("
            "id INTEGER NOT NULL,"
            "account char(30) NOT NULL UNIQUE,"
            "pass_word varchar(50) NOT NULL,"
            "auth bigint DEFAULT 0,"
            "parent_account char(30) DEFAULT NULL,"
            "phone char(30) DEFAULT NULL UNIQUE,"
            "app_list varchar(5000) DEFAULT 0,"
            "create_time timestamp DEFAULT (datetime(\'now\',\'localtime\')),"
            "PRIMARY KEY (id)"
          ") ";


    if(runSqlQuery(str_query)==false)
    {
        qDebug("createAccountsTable error!");
        return false;
    }
    return true;
}

上面这个是创建账户库表的功能函数,可以发现,主要就是执行数据库语言的语句,runSqlQuery就是执行语句,不同的功能都是需要它去执行的,差异化在执行语句上。建表的语句是

复制代码
CREATE TABLE If Not Exists account_list_tb

Not Exists表示不存在account_list_tb表的时候才建立新表,不会重复建表。

对创建账户数据库表内容进行分析,首先有个自增长的id号,NOT NULL表示不能为空。account行的char(30)表示最大存储30个字符,UNIQUE表示这个字段具有唯一性,所有记录中这个字段的内容不能重复,这是比较重要的,账户名重复了整个系统就乱套了,手机号也是这个道理。app_list字段存放的是这个账户创建的应用,采用base64编码存储,

解码前eyJhcHBfbGlzdCI6WzEyMzAwMl19,

解码后

{

"app_list": [

123002

]

}

另外,密码是需要加密存储的,所以在头文件里定义了一个密码缓存区,后续账户数据库里的加解密都用这个密码,可以看到密码在base64解码后是一串乱码,即使数据库被复制了也看不到密码。

三、数据库操作------增

数据库增加操作的语句是INSERT INTO.....VALUES....,根据内容组合起来,再用runSqlQuery执行即可,下面是增加一个账户的功能函数,要注意的是密码要进一步加密再存储,加密算法采用AES,密码就是之前所说的m_keyBuff;由于account字段是唯一性的,所以如果账户名重复了是会添加失败的。

cpp 复制代码
//添加一个账户信息到数据库
bool AccountSqlite::addAccountNode(QString account, QString pass_word, u32 auth, QString parent_account, QString phone)
{
    if(phone.isEmpty())
    {
        phone=account;
    }
    if(pass_word.isEmpty())
    {
        pass_word="12345678";
    }
    u8 out_passwd[50]={0};
    int out_len=drv_com.aes_encrypt_buff((u8*)pass_word.toUtf8().data(), (u16)pass_word.toUtf8().size(), out_passwd, (u16)sizeof (out_passwd), m_keyBuff);//密码加密存储
    if(out_len<16)
        return  false;
    QByteArray pwd_ba((char*)out_passwd, out_len);
    pass_word=pwd_ba.toBase64();
    QString str_query = QString::asprintf("INSERT INTO account_list_tb (account, pass_word, auth, parent_account, phone) VALUES (  \"%s\", \"%s\", %u, \"%s\", \"%s\")",\
                                          account.toUtf8().data(), pass_word.toUtf8().data(), auth, \
                                          parent_account.toUtf8().data(), phone.toUtf8().data());
    if( runSqlQuery(str_query))
    {
        qDebug("addAccountNode ok!");
        return true;
    }

    qDebug("addAccountNode failed!");
    return false;
}
四、数据库操作------改

数据库修改语句是UPDATE....SET....WHERE条件,这里以更新账户密码为例,UPDATE后面是要更新的字段,WHERE后面跟的是条件,这里就是账户名匹配了。

cpp 复制代码
//更新账户密码
bool AccountSqlite::updateAccountPassWord(QString account, QString pass_word)
{
    if(pass_word.isEmpty())
    {
        pass_word="12345678";
    }
    u8 out_passwd[50]={0};
    int out_len=drv_com.aes_encrypt_buff((u8*)pass_word.toUtf8().data(), (u16)pass_word.toUtf8().size(), out_passwd, (u16)sizeof (out_passwd), m_keyBuff);
    if(out_len<16)
        return  false;
    QByteArray pwd_ba((char*)out_passwd, out_len);
    pass_word=pwd_ba.toBase64();

    QString str_query = QString::asprintf("UPDATE account_list_tb SET pass_word=\"%s\"   WHERE account=\"%s\"",
                                          pass_word.toUtf8().data(), account.toUtf8().data());
//    qDebug()<<str_query;
    if( runSqlQuery(str_query))
    {
        qDebug("updateAccountPassWord ok!");
        return true;
    }
    return false;
}

下面这个更新应用列表也是比较重要的内容,每次创建新应用后都要更新一遍,应用列表是采用json数据格式,并转为base64编码保存的。

cpp 复制代码
//更新账户的应用列表
bool AccountSqlite::updateAccountAppList(QString account, QList<u32> app_list)
{
    QJsonDocument json_doc;
    QJsonObject root_obj;
    QJsonArray app_array;
    int nSize=app_list.size();
    for(int i=0; i<nSize; i++)
    {
        u32 app_id=app_list.at(i);
        if(app_id>0)
        {
            app_array.append((qint64)app_id);
        }
    }
    root_obj.insert("app_list", app_array);
    json_doc.setObject(root_obj);
    QByteArray json_ba=json_doc.toJson(QJsonDocument::Compact);//转为数据保存
    qDebug()<<"json_ba="<<json_ba.data();
    QString str_query = QString::asprintf("UPDATE account_list_tb SET  app_list=\"%s\"   WHERE account=\"%s\"",
                                          json_ba.toBase64().data(), account.toUtf8().data());

//    qDebug()<<str_query;
    if( runSqlQuery(str_query))
    {
        qDebug("updateAccountAppList ok!");
        return true;
    }
    return false;
}
五、数据库操作------查

数据库查找操作是使用最频繁的了,语句格式是SELECT......WHERE条件,这里以查询账户信息为例,SELECT后面跟着要查找内容的字段,这里很多人喜欢用*,不建议这样做,*代表选取所有字段,一个是效率肯定有所降低了,另一个是对所选取的字段顺序和数量不明确,在后面的解析转换中会出错,比如过段时间在第一列又增加了个字段内容,那么之前的解析代码就不能用了,顺序不对了。我们这里选择了account, pass_word, auth, parent_account, phone, app_list, create_time这些字段,不管库表增加什么字段,对我们后面解析都没影响。解析的时候也是按照这个顺序和对应的类型一个个转化的,ptr就是索引,每转化一个就自增到下一个字段,保存的时候是什么流程,解析的时候就是一个逆过程,比如这里的密码,先进行base64解码,然后再AES解密,这样才能得到正确的密码。

另外一个注意点是返回之前要执行下m_sqlQuery.finish()语句,这样才能结束查询,避免出错。

cpp 复制代码
//根据名称 获取帐号信息
bool AccountSqlite::selectAccountByName(QString account, AccountNodeStruct &account_node)
{
    QString str_query = QString::asprintf("SELECT account, pass_word, auth, parent_account, phone, app_list, create_time  FROM account_list_tb WHERE account=\"%s\"" , account.toUtf8().data());

//    qDebug()<<str_query;
    if(runSqlQuery(str_query)==false)
    {
        qDebug("selectAccountNode error_01!");
         return false;
    }
    while(m_sqlQuery.next())
    {
        int ptr=0;
        account_node.account=m_sqlQuery.value(ptr++).toString();
        QString pwd_str=m_sqlQuery.value(ptr++).toString();
        QByteArray pwd_ba=QByteArray::fromBase64(pwd_str.toUtf8());
        u8 out_passwd[50]={0};
        int out_len=drv_com.aes_decrypt_buff((u8*)pwd_ba.data(), (u16)pwd_ba.size(), out_passwd, sizeof (out_passwd), m_keyBuff);//存储密码解密
        if(out_len<=0)
        {
            m_sqlQuery.finish();
            return  false;
        }
        account_node.passWord=QString((char*)out_passwd);
        account_node.auth=m_sqlQuery.value(ptr++).toUInt();
        account_node.parentAccount=m_sqlQuery.value(ptr++).toString();
        account_node.phone=m_sqlQuery.value(ptr++).toString();

        QString json_str=m_sqlQuery.value(ptr++).toString();//应用列表JSON
        QByteArray json_ba=QByteArray::fromBase64(json_str.toUtf8());
        QJsonParseError json_error;
        QJsonDocument json_doc;
        json_doc = QJsonDocument(QJsonDocument::fromJson(json_ba, &json_error));//转为JSON格式
        if(json_error.error != QJsonParseError::NoError)
        {
            qDebug()<<"json error= "<<json_error.error;
        }
        else
        {
            QJsonObject root_obj = json_doc.object();
            if(root_obj.contains("app_list"))//应用列表
            {
                QJsonValue value=root_obj.value("app_list");
                if(value.isArray())
                {
                    QJsonArray app_array=value.toArray();
                    int nSize=app_array.size();
                    for(int i=0; i<nSize; i++)
                    {
                        QJsonValue value=app_array.at(i);
                        if(value.isDouble())
                        {
                            u32 app_id=(u32)value.toDouble();
                            if(app_id>0)
                            {
                                account_node.appList.append(app_id);//添加app_id
                            }
                        }
                    }
                }
            }
        }
        account_node.createTime=m_sqlQuery.value(ptr++).toString();
        m_sqlQuery.finish();
        return true;
    }
    qDebug("selectAccountNode error_02!");
    return false;
}

对于删除,这里暂时没用到,其实也很简单,就是DELETE....WHERE条件,后面有用到了再做举例。

相关推荐
Mr.1331 分钟前
数据库的三范式是什么?
数据库
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Python之栈1 小时前
【无标题】
数据库·python·mysql
风_流沙1 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
亽仒凣凣1 小时前
Windows安装Redis图文教程
数据库·windows·redis
亦世凡华、1 小时前
MySQL--》如何在MySQL中打造高效优化索引
数据库·经验分享·mysql·索引·性能分析
YashanDB1 小时前
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
数据库·yashandb·崖山数据库
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
中科岩创3 小时前
榆能横山煤电厂及周边建筑物爆破振动和位移自动化监测
物联网
云和数据.ChenGuang7 小时前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite