物联网实战--平台篇之(四)账户后台交互

news2024/11/18 14:30:02

目录

一、交互逻辑

二、请求验证码

三、帐号注册

四、帐号/验证码登录

五、重置密码


本项目的交流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

一、交互逻辑

        对于账户的注册、登录等流程的交互逻辑基本上是这样的:

        1、用户端APP提交相关信息;

        2、服务器接收解析信息,同时对信息的合法性进行认证;

        3、根据命令类型执行对应的操作;

        4、返回操作结果;

        5、用户端APP显示结果并进入下一步。

        两个工程都有跟账户相关的类文件,核心就是对这个流程中的不同指令进行处理,接下来根据指令详细看下处理流程。

        

二、请求验证码

        先看下图箭头所指的三个关键信息,这是手机APP的代码,刚开始的时候会获取设备的mac地址和一个随机数,然后把他们作为订阅话题的组成部分,这样服务器就可以根据上报携带的身份信息进行针对性地回应了,这个订阅话题示例:yyy125/as/pub/account/E0:23:A3:62:63:E2/1629/#

这样基本上具备唯一性了。第三个箭头是账户相关的前后端交互接口,QML文件中可以直接用theAccountMan调用AccountMan类中的后端函数。

        接下来进入用户端APP的AccountMan类中,核心是下图框框内的几个函数,分别是请求验证码、请求注册、请求账户登录、请求验证码登录和请求重置密码,可以看出,所有流程都是以APP端请求开始的。其它是对字符串进行有效性验证的函数,比如手机号、账户名和密码的格式进行校验,便于前端检测输入的合法性。

        

void AccountMan::requestVerCode(QString account, QString phone)
{
    QJsonObject root_obj;
    QJsonDocument json_doc;

    root_obj.insert("cmd_type", "req_vercode");
    root_obj.insert("account", account);
    root_obj.insert("phone", phone);
    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("code");
    emit sigMqttPushMessage(topic, msg_ba);
}


QString AccountMan::makePubTopic(QString key_str)
{
    QString topic=QString(TOPIC_HEAD)+"as/sub/account/"+key_str;
    return  topic;
}

        以上是请求验证码的代码,参数账户名和手机号由前端输入,其中账户名可以为空,保留功能,核心是要手机号,然后用json的形式组合参数,命令类型是req_vercode,随机数和mac地址是主程序中传进来的,服务器就是根据这两个信息组合返回的发布话题的,这样才能收到返回结果。在这里还有个组合发布话题的函数,现在传入的是code关键字,组合后就是yyy125/as/sub/account/code,字面上就能理解了这个话题的数据是发送给应用服务器的,内容是账户相关的,子功能为验证码,至于具体要干嘛那就交给数据包内的cmd_type字段去处理了,对于验证码功能主要就是 “请求验证码” 命令了。

        数据发送到服务器后,主程序先根据话题筛选出账户类话题,然后把该类型话题的数据转发到账户线程中去进一步处理,代码如下:


void MainInterface::slotMqttReceived(const QMQTT::Message &message)
{
    QJsonParseError json_error;
    QJsonDocument json_doc;
    QString recv_topic=message.topic();
     qDebug()<<"msg topic= "<<message.topic();
     qDebug()<<message.payload().data();
    json_doc = QJsonDocument::fromJson(message.payload(), &json_error);//转为JSON格式
    if(json_error.error != QJsonParseError::NoError)
    {
//        qDebug()<<"json error= "<<json_error.error;
        return;
    }
    QJsonObject rootObj = json_doc.object();
    
    if(recv_topic.contains("account/"))//账户类相关的话题
    {
        emit sigAccountThreadMessage(recv_topic, rootObj);
    }
    
}

        进入账户线程后代码如下,根据话题的子功能进行分类处理,对于验证码类型,我们刚才组合话题时候添加的关键字是code,那么话题内有/code的就进行具体的验证码数据解析,具体函数是parseVerCodeTopic(),在函数内,我们获取了命令类型、手机号、mac地址和随机数等数据,命令类型目前只有请求验证码,对于此命令的处理步骤是:

        1、检查手机号;

        2、获取随机验证码;

        3、缓存验证码;

        4、发送验证码;

        5、回复用户端APP

        其中,步骤3在缓存时会检查该手机号之前是否有验证码存在,如果有的话就缓存失败,同时也会回复用户端失败的信息;验证码的添加成功后会在一个列表内暂存30秒,超时后自动删除;步骤4中的发送验证码,理论上需要SMS服务器的,这个是需要企业才能办理的业务,我们当前先打印出来就好,实际的发送后面再专门出一篇。


void AccountThread::slotAccountThreadMessage(QString topic, QJsonObject root_obj)
{
    if(topic.contains("/reg"))//注册相关
    {
        parseRegTopic(root_obj);
    }
    else if(topic.contains("/login"))//登录相关
    {
        parseLoginTopic(root_obj);
    }
    else if(topic.contains("/code"))//验证码相关
    {
        parseVerCodeTopic(root_obj);
    }
    else if(topic.contains("/passwd"))//密码相关
    {
        parsePasswdTopic(root_obj);
    }
    else if(topic.contains("/child"))//子账户相关
    {
        
    }
    else if(topic.contains("/app"))//应用相关
    {
        
    }
    else if(topic.contains("/group"))//分组相关
    {
        
    }
    else if(topic.contains("/device"))//设备相关
    {
        
    }
}



//解析验证码话题
void AccountThread::parseVerCodeTopic(QJsonObject root_obj)
{
    QString cmd_type="";
    if(root_obj.contains("cmd_type"))//命令类型
    {
        QJsonValue value = root_obj.value("cmd_type");
        if(value.isString())
        {
            cmd_type=value.toString();
        }
    }
    QString account="";
    if(root_obj.contains("account"))//账户
    {
        QJsonValue value = root_obj.value("account");
        if(value.isString())
        {
            account=value.toString();
        }
    }

    int rand_num=0;
    if(root_obj.contains("rand_num"))//随机数
    {
        QJsonValue value = root_obj.value("rand_num");
        if(value.isDouble())
        {
            rand_num=(int)value.toDouble();
        }
    }
    QString mac_str="";
    if(root_obj.contains("mac"))//mac
    {
        QJsonValue value = root_obj.value("mac");
        if(value.isString())
        {
            mac_str=value.toString();
        }
    }
    QString phone="";
    if(root_obj.contains("phone"))//手机号码
    {
        QJsonValue value = root_obj.value("phone");
        if(value.isString())
        {
            phone=value.toString();
        }
    }
    
    if(cmd_type=="req_vercode")//请求验证码
    {
        if(phone.isEmpty())
        {
            qDebug()<<"phone.isEmpty()";
            return;
        }
        QString ver_code_str=takeVerCode();
        bool ok=addReqVerCodeNode(account, phone, ver_code_str);//添加进列表,进行超时检测
        if(ok)
        {
            //向SMS服务器发送验证码
//            sendSmsCheckCode(phone, ver_code_str);
            ackReqVerCodeState(account, mac_str, rand_num, phone, 0, "验证码已发送!");//返回验证码已发送状态
            qDebug()<<"req_vercode ok";
        }
        else
        {
            ackReqVerCodeState(account, mac_str, rand_num, phone, 1, "重复发送!");//
        }

    }
}

        回复函数根据不同的命令类型参数略有区别,下面是请求验证码的函数,注意点就是回复的话题,需要根据上传的mac、随机数和关键字段组合,与开头就形成闭环了。

void AccountThread::ackReqVerCodeState(QString account, QString mac_str, int rand_num, QString phone, int result, QString ack_str)
{
    QJsonObject root_obj;
    QJsonDocument json_doc;
    root_obj.insert("cmd_type", "req_vercode");
    root_obj.insert("result", result);
    root_obj.insert("phone", phone);
    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, "code");
    emit sigMqttPushMessage(topic, msg_ba);
}


//生成发布话题
QString AccountThread::makePubTopic(QString account, QString mac_str, int rand_num, QString key_str)
{
    if(account.isEmpty())
    {
        
    }
    QString topic=QString(TOPIC_HEAD) + SERVER_PUB_TOPIC+QString("/account/")+mac_str+QString::asprintf("/%d/", rand_num)+key_str;
    return topic;
}

        回到用户端这边,主程序也是根据话题类型归类处理的,目前只进行简单的信息提示处理,就是将服务器的回复信息发送到QML前端进行显示交互。

        

        总的来讲,整个流程就是这样了,其他注册、登录等功能也是类似的。

三、帐号注册

        1、用户端发送注册信息,包含账户、密码、手机、验证码等:

void AccountMan::requestReg(QString account, QString passwd, QString phone, QString ver_code)
{
    QJsonObject root_obj;
    QJsonDocument json_doc;

    root_obj.insert("cmd_type", "req_reg");
    root_obj.insert("account", account);
    root_obj.insert("pass_word", passwd);
    root_obj.insert("phone", phone);
    root_obj.insert("rand_num", m_randNum);
    root_obj.insert("mac", m_macStr);
    root_obj.insert("ver_code", ver_code);
    json_doc.setObject(root_obj);
    QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
    QString topic=makePubTopic("reg");
    emit sigMqttPushMessage(topic, msg_ba);
}

        2、服务器接收解析,在这里要对账户和手机号的重复性进行检查,注册成功后会默认新建一个app_id。


//解析注册话题
void AccountThread::parseRegTopic(QJsonObject root_obj)
{
    QString cmd_type="";
    if(root_obj.contains("cmd_type"))//命令类型
    {
        QJsonValue value = root_obj.value("cmd_type");
        if(value.isString())
        {
            cmd_type=value.toString();
        }
    }
    QString account="";
    if(root_obj.contains("account"))//账户
    {
        QJsonValue value = root_obj.value("account");
        if(value.isString())
        {
            account=value.toString();
        }
    }

    int rand_num=0;
    if(root_obj.contains("rand_num"))//随机数
    {
        QJsonValue value = root_obj.value("rand_num");
        if(value.isDouble())
        {
            rand_num=(int)value.toDouble();
        }
    }
    QString mac_str="";
    if(root_obj.contains("mac"))//mac
    {
        QJsonValue value = root_obj.value("mac");
        if(value.isString())
        {
            mac_str=value.toString();
        }
    }
    QString pass_word="";
    if(root_obj.contains("pass_word"))//密码
    {
        QJsonValue value = root_obj.value("pass_word");
        if(value.isString())
        {
            pass_word=value.toString();
        }
    }
    QString phone="";
    if(root_obj.contains("phone"))//手机号码
    {
        QJsonValue value = root_obj.value("phone");
        if(value.isString())
        {
            phone=value.toString();
        }
    }
    QString ver_code="";
    if(root_obj.contains("ver_code"))//验证码
    {
        QJsonValue value = root_obj.value("ver_code");
        if(value.isString())
        {
            ver_code=value.toString();
        }
    }
    
    if(cmd_type=="req_reg")//请求注册
    {
        bool ok;
        account.toDouble(&ok);
        if(ok)
        {
            ackReqRegState(account, mac_str, rand_num, phone, 6, "账户不能为纯数字!");
            return;
        }
        
        AccountSqlite::AccountNodeStruct tag_account;
        bool ok1, ok2;
        ok1=m_accountSqlite->searchAccountByName(account, tag_account);
        ok2=m_accountSqlite->searchAccountByPhone(phone, tag_account);
        if(ok1==false && ok2==false)//未找到重复的,可以注册
        {
            for(auto iter : m_reqVerCodeList)
            {
                if(iter.phone == phone)//根据手机号检索
                {
                    if(iter.verCode == ver_code)//验证码相等
                    {
                        bool ok=m_accountSqlite->addAccountNode(account, pass_word, 0x00, "", phone);
                        if(ok)
                        {
                            ackReqRegState(account, mac_str, rand_num, phone, 0, "注册成功!");
                            qDebug()<<"reg ok!";
                            //自动创建一个应用
                            u32 max_app=m_accountSqlite->selectMaxAppID();
                            u32 new_app_id=0;
                            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;
                           m_accountSqlite->addAppIDToList(new_app_id, account);
                        }
                        else
                        {
                            ackReqRegState(account, mac_str, rand_num, phone, 1, "数据库存储出错!");
                            qDebug()<<"reg sql error!!";
                        }
                    }
                    else
                    {
                        ackReqRegState(account, mac_str, rand_num, phone, 2, "验证码错误!");
                        qDebug()<<"reg ver_code error!";
                    }
                    return;
                }
            }
            ackReqRegState(account, mac_str, rand_num, phone, 3, "验证码超时!");
            qDebug()<<"no found code!";
        }
        else
        {
            if(ok1==true)
            {
                ackReqRegState(account, mac_str, rand_num, phone, 4, "账户名已存在!");
                qDebug()<<"have same account="<<account;
            }
            else if(ok2==true)
            {
                ackReqRegState(account, mac_str, rand_num, phone, 5, "手机号已存在!");
                qDebug()<<"have same phone="<<phone;
            }
        }
    }
}

四、帐号/验证码登录

        1、用户发送帐号登录信息,主要包括账户和密码:

void AccountMan::requestLogin(QString account, QString pass_word, int remember)
{
    if(remember){}
    QJsonObject root_obj;
    QJsonDocument json_doc;
    root_obj.insert("cmd_type", "login_pwd");
    root_obj.insert("account", account);
    root_obj.insert("pass_word", pass_word);
    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("login");
    emit sigMqttPushMessage(topic, msg_ba);
}

        如果是验证码登录,那就是手机号和验证码

void AccountMan::requestLoginByVerCode(QString phone, QString ver_code)
{
    QJsonObject root_obj;
    QJsonDocument json_doc;
    root_obj.insert("cmd_type", "login_code");
    root_obj.insert("phone", phone);
    root_obj.insert("ver_code", ver_code);
    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("login");
    emit sigMqttPushMessage(topic, msg_ba);
}

        2、服务端接收解析,登录操作要仔细校验账户名和密码,账户也可以填手机号,服务端会自己判断;验证码登录的时候,如果该手机号未注册则会自动注册,用户名默认是Y+手机号,密码随机,需要自己去重置。


//解析登录话题
void AccountThread::parseLoginTopic(QJsonObject root_obj)
{
    QString cmd_type="";
    if(root_obj.contains("cmd_type"))//命令类型
    {
        QJsonValue value = root_obj.value("cmd_type");
        if(value.isString())
        {
            cmd_type=value.toString();
        }
    }
    QString account="";
    if(root_obj.contains("account"))//账户
    {
        QJsonValue value = root_obj.value("account");
        if(value.isString())
        {
            account=value.toString();
        }
    }

    int rand_num=0;
    if(root_obj.contains("rand_num"))//随机数
    {
        QJsonValue value = root_obj.value("rand_num");
        if(value.isDouble())
        {
            rand_num=(int)value.toDouble();
        }
    }
    QString mac_str="";
    if(root_obj.contains("mac"))//mac
    {
        QJsonValue value = root_obj.value("mac");
        if(value.isString())
        {
            mac_str=value.toString();
        }
    }
    QString pass_word="";
    if(root_obj.contains("pass_word"))//密码
    {
        QJsonValue value = root_obj.value("pass_word");
        if(value.isString())
        {
            pass_word=value.toString();
        }
    }
    QString phone="";
    if(root_obj.contains("phone"))//手机号码
    {
        QJsonValue value = root_obj.value("phone");
        if(value.isString())
        {
            phone=value.toString();
        }
    }
    QString ver_code="";
    if(root_obj.contains("ver_code"))//验证码
    {
        QJsonValue value = root_obj.value("ver_code");
        if(value.isString())
        {
            ver_code=value.toString();
        }
    }
    
    AccountSqlite::AccountNodeStruct tag_account;
    if(cmd_type=="login_pwd")//密码登录
    {
        if(!account.isEmpty())
        {                        
            bool ok=account.toDouble();
            if(ok)//手机号登录
            {
                QString phone=account;
                if( m_accountSqlite->selectAccountByPhone(phone, tag_account)==false)
                {
                    ackLoginState("", phone, "", mac_str, rand_num, 0, "", 1, "手机号未注册!");
                    return;
                }
            }
            else
            {
                if( m_accountSqlite->selectAccountByName(account, tag_account)==false)
                {
                    ackLoginState(account, "", "", mac_str, rand_num, 0, "", 2, "帐号未注册!");
                    return;
                }
            }
            if((account==tag_account.account || account==tag_account.phone) && pass_word==tag_account.passWord)//再次校验用户名跟密码
            {
               ackLoginState(tag_account.account,tag_account.phone, tag_account.parentAccount, mac_str, rand_num, tag_account.auth, tag_account.createTime, 0, "登录成功!");
            }
            else
            {
                ackLoginState(tag_account.account,tag_account.phone, tag_account.parentAccount, mac_str, rand_num, tag_account.auth, tag_account.createTime, 3, "密码错误!");
            }
        }
        else
        {
            ackLoginState(account, "", "", mac_str, rand_num, 0, "", 4, "账户不存在!");
        }
    }
    else if(cmd_type=="login_code")//验证码登录
    {
        bool ok; 
        phone.toDouble(&ok);
        if(phone.size()!=11 || !ok)
        {
            qDebug()<<"login phone="<<phone<<" error!";
            ackReqRegState(phone, mac_str, rand_num, phone, 1, "手机号有误!");
            return;
        }
        bool flag=false;
        for(auto iter : m_reqVerCodeList)
        {
            if(iter.phone == phone)
            {
                if(iter.verCode == ver_code)
                {
                    flag=true;
                }
                break;
            }
        }
        if(flag==false)
        {
            ackReqRegState(phone, mac_str, rand_num, phone, 1, "验证码错误!");
            return;
        }
        
        tag_account.phone.clear();
        m_accountSqlite->searchAccountByPhone(phone, tag_account);
        if(phone==tag_account.phone)
        {
            ackReqRegState(phone, mac_str, rand_num, phone, 0, "登录成功!");
        }
        else//新手机号,直接注册
        {
            account="Y"+phone;
            pass_word.clear();
            for(int i=0; i<8; i++)
            {
                pass_word+=QString::asprintf("%d", drv_com.takeRandNumber()%10);//随机密码
            }
            qDebug()<<account<<" pass_word="<<pass_word;
            bool ok=m_accountSqlite->addAccountNode(account, pass_word, 0x00, "", phone);
            if(ok)
            {
                ackReqRegState(phone, mac_str, rand_num, phone, 0, "登录(注册)成功!");
                qDebug()<<"reg ok!";
                //自动创建一个应用
                u32 max_app=m_accountSqlite->selectMaxAppID();
                u32 new_app_id=0;
                if(max_app>APP_ID_MIN)
                    new_app_id=max_app+1;
                else  
                    new_app_id=APP_ID_MIN+1;
                
                qDebug()<<"phone new_app_id="<<new_app_id;
               m_accountSqlite->addAppIDToList(new_app_id, account);
            }
            else
            {
                ackReqRegState(phone, mac_str, rand_num, phone, 1, "数据库存储错误!");
                qDebug()<<"reg sql error!!";
            }
        }
    }
    
}

五、重置密码

        1、用户发送重置信息主要包括用户名、新密码、手机号和校验码:

void AccountMan::requestResetPasswd(QString account, QString pass_word, QString phone, QString ver_code)
{
    QJsonObject root_obj;
    QJsonDocument json_doc;
    root_obj.insert("cmd_type", "reset_pwd");
    root_obj.insert("account", account);
    root_obj.insert("pass_word", pass_word);
    root_obj.insert("phone", phone);
    root_obj.insert("rand_num", m_randNum);
    root_obj.insert("mac", m_macStr);
    root_obj.insert("ver_code", ver_code);
    json_doc.setObject(root_obj);
    QByteArray msg_ba = json_doc.toJson(QJsonDocument::Indented);
    QString topic=makePubTopic("passwd");
    emit sigMqttPushMessage(topic, msg_ba);
}

        2、服务端接收解析,首先要检查与数据库里的账户和手机是否匹配,然后校验验证码,通过后就可以更新密码了,为了确保写入成功,需要再读取出来进行对比,新密码校验成功后才算真正完成了密码重置。


void AccountThread::parsePasswdTopic(QJsonObject root_obj)
{
    QString cmd_type="";
    if(root_obj.contains("cmd_type"))//命令类型
    {
        QJsonValue value = root_obj.value("cmd_type");
        if(value.isString())
        {
            cmd_type=value.toString();
        }
    }
    QString account="";
    if(root_obj.contains("account"))//账户
    {
        QJsonValue value = root_obj.value("account");
        if(value.isString())
        {
            account=value.toString();
        }
    }
    QString mac_str="";
    if(root_obj.contains("mac"))//mac
    {
        QJsonValue value = root_obj.value("mac");
        if(value.isString())
        {
            mac_str=value.toString();
        }
    }

    int rand_num=0;
    if(root_obj.contains("rand_num"))//rand_num
    {
        QJsonValue value = root_obj.value("rand_num");
        if(value.isDouble())
        {
            rand_num=(int)value.toDouble();
        }
    }
    QString pass_word="";
    if(root_obj.contains("pass_word"))//密码
    {
        QJsonValue value = root_obj.value("pass_word");
        if(value.isString())
        {
            pass_word=value.toString();
        }
    }
    QString phone="";
    if(root_obj.contains("phone"))//手机号码
    {
        QJsonValue value = root_obj.value("phone");
        if(value.isString())
        {
            phone=value.toString();
        }
    }
    QString ver_code="";
    if(root_obj.contains("ver_code"))//验证码
    {
        QJsonValue value = root_obj.value("ver_code");
        if(value.isString())
        {
            ver_code=value.toString();
        }
    }
    
    if(cmd_type=="reset_pwd")
    {
        if(account.isEmpty() || phone.isEmpty())
        {
            qDebug()<<"account.isEmpty() || phone.isEmpty()";
            return;
        }
        AccountSqlite::AccountNodeStruct tag_account;
        m_accountSqlite->selectAccountByPhone(phone, tag_account);
        if(tag_account.account!=account)
        {
            ackResetPasswdState(account, mac_str, rand_num, 4, "账户与手机不匹配!");
            return;
        }
                
        for(auto iter : m_reqVerCodeList)
        {
            if(iter.phone == phone)
            {
                if(iter.verCode == ver_code)
                {
                    tag_account.passWord.clear();
                    m_accountSqlite->updateAccountPassWord(account, pass_word);//更新密码
                    m_accountSqlite->selectAccountByName(account, tag_account);//重新获取密码
                    if(tag_account.passWord==pass_word)//新密码校验
                    {
                        ackResetPasswdState(account, mac_str, rand_num, 0, "密码修改成功!");
                        qDebug("set new pwd ok!");
                    }
                    else
                    {
                        ackResetPasswdState(account, mac_str, rand_num, 1, "密码修改(校验)失败!");
                        qDebug("set new pwd failed!");
                    }
                }
                else
                {
                    ackResetPasswdState(account, mac_str, rand_num, 2, "验证码错误!");
                    qDebug()<<"reg ver_code error!";
                }
                return;
            }
        }
        ackResetPasswdState(account, mac_str, rand_num, 3, "验证码已过期!");
        
    }
    
}

        服务端经常有数据库操作步骤,这里再看下数据库的创建和打开,因为账户管理任务是独立的线程,所以我这里数据库定义为指针类型,然后在槽函数里new一个 AccountSqlite(),这样数据库操作才属于线程内部,这点很重要,这是QT的特性;然后就是打开数据库,参数就是文件名(包括路径)+连接名称,如果文件不存在就会自动创建,连接名称随意,这里一般不会重复;最后就是创建账户表和应用表了,之前数据库语句介绍过了,这样操作并不会重复创建库表。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1657779.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《21天学通C++》(第二十章)STL映射类(map和multimap)

为什么需要map和multimap&#xff1a; 1.查找高效&#xff1a; 映射类允许通过键快速查找对应的值&#xff0c;这对于需要频繁查找特定元素的场景非常适合。 2.自动排序&#xff1a; 会自动根据键的顺序对元素进行排序 3.多级映射&#xff1a; 映射类可以嵌套使用&#xff0c;创…

java.net.SocketInputStream.socketRead0 卡死导致 tomcat 线程池打满的问题

0 TL;DR; 问题与原因&#xff1a;某些特定条件下 java.net.SocketInputStream.socketRead0 方法会卡死&#xff0c;导致运行线程一直被占用导致泄露采用的方案&#xff1a;使用监控线程异步监控卡死事件&#xff0c;如果发生直接关闭网络连接释放链接以及对应的线程 1. 问题 …

贪心算法--将数组和减半的最小操作数

本题是力扣2208---点击跳转题目 思路&#xff1a; 要尽快的把数组和减小&#xff0c;那么每次挑出数组中最大的元素减半即可&#xff0c;由于每次都是找出最值元素&#xff0c;可以用优先队列来存储这些数组元素 每次取出最值&#xff0c;减半后再放入优先队列中&#xff0c;操…

最新:Lodash 严重安全漏洞背后你不得不知道的 JavaScript 知识

可能有信息敏感的同学已经了解到&#xff1a;Lodash 库爆出严重安全漏洞&#xff0c;波及 400万 项目。这个漏洞使得 lodash “连夜”发版以解决潜在问题&#xff0c;并强烈建议开发者升级版本。 我们在忙着“看热闹”或者“”升级版本”的同时&#xff0c;静下心来想&#xf…

如何通过代理IP实现搜索引擎优化

目录 前言 一、代理IP的基本概念 二、通过代理IP访问其他地区的搜索引擎 三、对比不同地区搜索结果 结论 前言 搜索引擎优化&#xff08;Search Engine Optimization&#xff0c;SEO&#xff09;是指通过优化网站的结构、内容和关键词等因素&#xff0c;提高网站在搜索引…

ubuntu挂载固态硬盘

ubuntu挂载固态硬盘 两种情况 包装盒拆出来的新硬盘用过的需要后处理的硬盘 新硬盘 一、确认硬盘设备 插上主机后输入 lsblk检查是否识别到你插入的硬盘 可以看到上图的nvme0n1是我挂载的硬盘&#xff08;目前已经挂载完成并映射到 ~/ssd目录&#xff09;&#xff0c;nvm…

如果你这样使用电路仿真软件,你就无敌了!

在电子设计领域&#xff0c;电路仿真软件如同一把锋利的宝剑&#xff0c;掌握它&#xff0c;你就能在复杂的电子世界中游刃有余。今天&#xff0c;就让我们一起探讨如何高效利用电路仿真软件&#xff0c;让你在电子设计领域所向披靡&#xff01; 一、熟悉软件界面与基础操作 …

点击短信链接唤起Android App实战

一.概述 在很多业务场景中,需要点击短信链接跳转到App的指定页面。在Android系统中,想要实现这个功能,可以通过DeepLink或AppLink实现。二.方案 2.1 DeepLink 2.1.1 方案效果 DeepLink是Android系统最基础、最普遍、最广泛的外部唤起App的方式,不受系统版本限制。当用户…

基于Vue3与ElementUI Plus的酷企秀场景可视化DIY设计器:前端技术引领下的数字化展示新篇章

一、引言 在当今信息化高速发展的时代&#xff0c;企业对于展示自身形象、提升用户体验以及增强品牌知名度的需求日益迫切。针对这一市场需求&#xff0c;我们推出了基于Vue3与ElementUI Plus的酷企秀场景可视化DIY设计器。该产品不仅具备电子画册、VR全景、地图秀三大核心功能…

2024年自动驾驶、车辆工程与智能交通国际会议(ICADVEIT2024)

2024年自动驾驶、车辆工程与智能交通国际会议&#xff08;ICADVEIT2024&#xff09; 会议简介 2024年自动驾驶、车辆工程和智能交通国际会议&#xff08;ICADVEIT 2024&#xff09;将在中国深圳举行。会议主要聚焦自动驾驶、车辆工程和智能交通等研究领域&#xff0c;旨在为从…

pytest教程-42-钩子函数-pytest_runtest_makereport

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_teardown钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_runtest_makereport钩子函数的使用方法。 pytest_runtest_makereport 钩子函数在 pytest 为每个测试生成报…

使用HashMap实现,对一个字符集进行哈夫曼编码

最终达到的效果: 调用一个类 class HuffmanCodin{.....} 使用类中的静态方法&#xff0c;获取哈夫曼编码&#xff1a; 事前准备——哈夫曼树的节点定义 class Node implements Comparable<Node> {int weight;//权重Node left;Node right;char ch;//关键字&#xff0c…

Zabbix5.0——安装与部署

目录 一、zabbix-server(192.168.206.134) 监控方 1. 环境准备 2.安装zabbix 2.1 准备zabbix-repo 2.2清理缓存 2.3安装zabbix主包&#xff08;服务器和代理&#xff09; 2.4安装zabbix前端包 3. 数据库安装 3.1 授权zabbix账号 3.2导入数据库&#xff08;初始化zabbix&#x…

切实有效的提高VMWARE游戏性能-各版本通杀 vm17pro

这里的游戏性能&#xff0c;当然了&#xff0c;特别指出的是3D性能&#xff0c;毕竟现在2D也很少了。 因为平时没啥事&#xff0c;所以&#xff0c;无聊就跟朋友挂挂游戏&#xff0c;没事写点代码折腾下。所以&#xff0c;免不了跟VMWARE搭上边。走了很多的弯路&#xff0c;中…

Linux-信号执行

1. 信号什么时候被处理 当进程从内核态返回到用户态的时候&#xff0c;进行信号的检测和处理 什么内核态&#xff0c;什么又是用户态呢&#xff1f; 当进程在CPU上运行时&#xff0c;内核态&#xff1a;允许进程访问操作系统的代码和数据&#xff0c;用户态&#xff1a;进程只…

Kubernetes容器技术详解

kubernetes Kubernetes&#xff08;K8s&#xff09;由Google打造&#xff0c;是一款功能强大、灵活可扩展的容器编排平台&#xff0c;引领云原生技术潮流。 Kubernetes主要解决以下4大点&#xff1a; 1.自动化运维平台 如下图所示&#xff1a; Kubernetes携手Docker&#xf…

【go项目01_学习记录08】

学习记录 1 模板文件1.1 articlesStoreHandler() 使用模板文件1.2 统一模板 1 模板文件 重构 articlesCreateHandler() 和 articlesStoreHandler() 函数&#xff0c;将 HTML 抽离并放置于独立的模板文件中。 1.1 articlesStoreHandler() 使用模板文件 . . . func articlesSt…

【动态规划】:路径问题_地下城游戏

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

怎么用AI软件设计字体

一 工具准备 软件&#xff1a;Adobe illustrator 如下网盘自取 链接&#xff1a;https://pan.baidu.com/s/1hlImpN4QlsSkOLLUxINOGA 提取码&#xff1a;love 安装的时候看不全界面&#xff0c;多按几下tab键就能看到按钮。 直接找一款喜欢的字体修改&#xff0c;字体包如下…

PyCharm安装教程(超详细图文教程)

一、下载和安装 1.进入PyCharm官方下载&#xff0c;官网下载地址&#xff1a; https://www.jetbrains.com/pycharm/download/ 专业版安装插件放网盘了&#xff0c;网盘下载即可&#xff1a;itcxy.xyz/229.html2.安装 1.下载后找到PyCharm安装包&#xff0c;然后双击双击.ex…