站点参数入库,不是站点数据入库

站点参数入库,注意不是站点数据入库

站点参数的某些东西是站点数据要用的比如站点参数数据中的obtid是站点数据表的外键。但是目前是站点参数入库,这个文章记录了,站点参数入库的debug过程以及该程序的流程。

debug参数设置

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/idc1/bin/obtcodetodb",
            "args": ["/project/idc/ini/stcode.ini","127.0.0.1,root,mysqlpwd,mysql,3306","utf8","/log/idc/obtcodetodb.log"],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description":  "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }

    ]
}

程序运行参数枚举,这是一个逐步求精的过程不是一蹴而就的,慢慢做。

站点参数文件

既然要将一个参数文件上传至数据库中,那么参数文件应该是必须的。

数据库登录信息

数据库的编码格式

日志文件

主体流程

  • 将站点参数文件存放进入数据库中。站点参数数据不存在,就插入进去,站点数据已经存在,那就更新。
  • 打开日志文件,如果日志文件打开不了,就不用继续了。
  • 程序加入心跳信息,防止突然宕掉,由于我们这个是有数据量的,所以有可能是宕掉的,突然结束的话,让守护进程对其回收,并且,调度程序主进程不被堵塞,从而可以创建另外一个进程启动该任务,保证该任务可以被杀死和重新调度。
  • 该任务是一个周期性的任务,因为,站点参数文件是有可能变化的,比如加入新的站点文件进去。
  • 将文件从磁盘中读取出来,存放进入容器容器,便于统一操作。
  • 连接数据库,开始循环处理容器中的数据存放进入数据库。
  • 里面有各种细节问题,我将会在下面剖析。

LoadSTcode() 将文件从磁盘读取进入容器中。

  • 文件指针对象打开一个文件
    我们要知道文件里存放的都是什么玩意?类似于下面的这个样子,就是参数文件。注意第一行是无效的,我们不需要将第一行存放进去的。

  • 其次思考下,我们容器是怎么来存储这些信息,使用一个结构来存放每一行的信息,读取一行处理一行,最终完成文件的解析。

省   站号  站名 纬度   经度  海拔高度
安徽,58015,砀山,34.27,116.2,44.2
安徽,58102,亳州,33.47,115.44,39.1
安徽,58118,蒙城,33.16,116.31,10.3

存储一条数据的数据结构

// 全国气象站点参数结构体。
struct st_stcode
{
  char provname[31]; // 省
  char obtid[11];    // 站号
  char cityname[31];  // 站名
  char lat[11];      // 纬度
  char lon[11];      // 经度
  char height[11];   // 海拔高度
};

接下来,不就是这样的节奏嘛。打开文件,读取一条数据,处理一条数据,将一条数据存放到数据库中。
首先要一个缓冲区,把每一行的数据读到缓冲区中,其次要对这个字符串进行处理,所以要一个字符串处理对象CCmdstr.
主体流程:

while(true)
{
	//缓冲区清空,站点参数结构体对象清空
	//读取一行,如果没有读取到,说明结束了,退出,break
	//处理缓冲区
	//从CCmdstr对象中拿到处理好的东西
	//填入一个st_stcode站点参数结构体对象
	//压入容器中
}
数据库插入语句

这是什么意思呢?下面这个是插入一条数据的SQl语句,这样插入一条数据,为什么要使用这样一条是语句呢》是因为,我们要插入的东西,每一次都是高度相似的,只有一些地方是不一样的,所以采用同一个空间,每一次对于那个空间去变环,从而sql主题不变,就不用写重复的SQL语句。直接绑定到那个空间就行了。

insert into T_ZHOBTCODE(obtid,cityname,provname,lat,lon,height,upttime) values(:1,:2,:3,:4*100,:5*100,:6*10,now())
// 把站点参数文件中加载到vstcode容器中。
bool LoadSTCode(const char *inifile)
{
  CFile File;

  // 打开站点参数文件。
  if (File.Open(inifile,"r")==false)
  {
    logfile.Write("File.Open(%s) failed.\n",inifile); return false;
  }

  char strBuffer[301];

  CCmdStr CmdStr;

  struct st_stcode stcode;

  while (true)
  {
    // 从站点参数文件中读取一行,如果已读取完,跳出循环。
    if (File.Fgets(strBuffer,300,true)==false) break;

    // 把读取到的一行拆分。
    CmdStr.SplitToCmd(strBuffer,",",true);

    if (CmdStr.CmdCount()!=6) continue;     // 扔掉无效的行。

    // 把站点参数的每个数据项保存到站点参数结构体中。
    memset(&stcode,0,sizeof(struct st_stcode));
    CmdStr.GetValue(0, stcode.provname,30); // 省
    CmdStr.GetValue(1, stcode.obtid,10);    // 站号
    CmdStr.GetValue(2, stcode.cityname,30);  // 站名
    CmdStr.GetValue(3, stcode.lat,10);      // 纬度
    CmdStr.GetValue(4, stcode.lon,10);      // 经度
    CmdStr.GetValue(5, stcode.height,10);   // 海拔高度

    // 把站点参数结构体放入站点参数容器。
    vstcode.push_back(stcode);
  }

  /*
  for (int ii=0;ii<vstcode.size();ii++)
    logfile.Write("provname=%s,obtid=%s,cityname=%s,lat=%.2f,lon=%.2f,height=%.2f\n",\
                   vstcode[ii].provname,vstcode[ii].obtid,vstcode[ii].cityname,vstcode[ii].lat,\
                   vstcode[ii].lon,vstcode[ii].height);
  */

  return true;
}

从容器读取数据,并且使用SQL语句存放进入数据库

因为多个地方使用该容器,所以容器成为全局变量。

准备插入的SQL语句

struct st_stcode stcode;

  // 准备插入表的SQL语句。
  sqlstatement stmtins(&conn);
  stmtins.prepare("insert into T_ZHOBTCODE(obtid,cityname,provname,lat,lon,height,upttime) values(:1,:2,:3,:4*100,:5*100,:6*10,now())");
  stmtins.bindin(1,stcode.obtid,10);
  stmtins.bindin(2,stcode.cityname,30);
  stmtins.bindin(3,stcode.provname,30);
  stmtins.bindin(4,stcode.lat,10);
  stmtins.bindin(5,stcode.lon,10);
  stmtins.bindin(6,stcode.height,10);

准备更新的SQL语句

为什么会插入失败,因为主键不允许重复,如果是插入一个主键已经存在的数据,那就会包1062错误,那就执行更新。

// 准备更新表的SQL语句。
  sqlstatement stmtupt(&conn);
  stmtupt.prepare("update T_ZHOBTCODE set cityname=:1,provname=:2,lat=:3*100,lon=:4*100,height=:5*10,upttime=now() where obtid=:6");
  stmtupt.bindin(1,stcode.cityname,30);
  stmtupt.bindin(2,stcode.provname,30);
  stmtupt.bindin(3,stcode.lat,10);
  stmtupt.bindin(4,stcode.lon,10);
  stmtupt.bindin(5,stcode.height,10);
  stmtupt.bindin(6,stcode.obtid,10);

循环遍历容器,将容器里的数据拿出来,赋予给那个不断变化的对象,通过那个对象将数据存放到数据库中。
注意,当执行插入语句不成功的时候,那就执行更新语句,完成更新操作,最后进行统计。

注意提交事务

贴上所有的代码

/**
 * @file obtcodetodb.cpp
 * @author wanyingxing (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-08-27
 * 
 * @copyright Copyright (c) 2022
 * 
 * 将站点的参数加入到数据库之中
 * 
 */
#include "_public.h"
#include "_mysql.h"

// 全国气象站点参数结构体。
struct st_stcode
{
  char provname[31]; // 省
  char obtid[11];    // 站号
  char cityname[31];  // 站名
  char lat[11];      // 纬度
  char lon[11];      // 经度
  char height[11];   // 海拔高度
};

vector<struct st_stcode> vstcode; // 存放全国气象站点参数的容器。

// 把站点参数文件中加载到vstcode容器中。
bool LoadSTCode(const char *inifile);

CLogFile logfile;

connection conn;

CPActive PActive;

void EXIT(int sig);

int main(int argc,char *argv[])
{
  // 帮助文档。
  if (argc!=5)
  {
    printf("\n");
    printf("Using:./obtcodetodb inifile connstr charset logfile\n");

    printf("Example:/project/tools1/bin/procctl 120 /project/idc1/bin/obtcodetodb /project/idc/ini/stcode.ini \"127.0.0.1,root,mysqlpwd,mysql,3306\" utf8 /log/idc/obtcodetodb.log\n\n");

    printf("本程序用于把全国站点参数数据保存到数据库表中,如果站点不存在则插入,站点已存在则更新。\n");
    printf("inifile 站点参数文件名(全路径)。\n");
    printf("connstr 数据库连接参数:ip,username,password,dbname,port\n");
    printf("charset 数据库的字符集。\n");
    printf("logfile 本程序运行的日志文件名。\n");
    printf("程序每120秒运行一次,由procctl调度。\n\n\n");

    return -1;
  }

  // 关闭全部的信号和输入输出。
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
  // 但请不要用 "kill -9 +进程号" 强行终止。
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

  // 打开日志文件。
  if (logfile.Open(argv[4],"a+")==false)
  {
    printf("打开日志文件失败(%s)。\n",argv[4]); return -1;
  }

  PActive.AddPInfo(10,"obtcodetodb");   // 进程的心跳,10秒足够。
  // 注意,在调试程序的时候,可以启用类似以下的代码,防止超时。
  // PActive.AddPInfo(5000,"obtcodetodb");

  // 把全国站点参数文件加载到vstcode容器中。  
  if (LoadSTCode(argv[1])==false) return -1;

  logfile.Write("加载参数文件(%s)成功,站点数(%d)。\n",argv[1],vstcode.size());

  // 连接数据库。
  if (conn.connecttodb(argv[2],argv[3])!=0)
  {
    logfile.Write("connect database(%s) failed.\n%s\n",argv[2],conn.m_cda.message); return -1;
  }

  logfile.Write("connect database(%s) ok.\n",argv[2]);

  struct st_stcode stcode;

  // 准备插入表的SQL语句。
  sqlstatement stmtins(&conn);
  stmtins.prepare("insert into T_ZHOBTCODE(obtid,cityname,provname,lat,lon,height,upttime) values(:1,:2,:3,:4*100,:5*100,:6*10,now())");
  stmtins.bindin(1,stcode.obtid,10);
  stmtins.bindin(2,stcode.cityname,30);
  stmtins.bindin(3,stcode.provname,30);
  stmtins.bindin(4,stcode.lat,10);
  stmtins.bindin(5,stcode.lon,10);
  stmtins.bindin(6,stcode.height,10);

  // 准备更新表的SQL语句。
  sqlstatement stmtupt(&conn);
  stmtupt.prepare("update T_ZHOBTCODE set cityname=:1,provname=:2,lat=:3*100,lon=:4*100,height=:5*10,upttime=now() where obtid=:6");
  stmtupt.bindin(1,stcode.cityname,30);
  stmtupt.bindin(2,stcode.provname,30);
  stmtupt.bindin(3,stcode.lat,10);
  stmtupt.bindin(4,stcode.lon,10);
  stmtupt.bindin(5,stcode.height,10);
  stmtupt.bindin(6,stcode.obtid,10);
  // 抄以上代码的时候要小心,经常有人在这里栽跟斗。

  int inscount=0,uptcount=0;
  CTimer Timer;

  // 遍历vstcode容器。
  for (int ii=0;ii<vstcode.size();ii++)
  {
    // 从容器中取出一条记录到结构体stcode中。
    memcpy(&stcode,&vstcode[ii],sizeof(struct st_stcode));

    // 执行插入的SQL语句。
    if (stmtins.execute()!=0)
    {
      if (stmtins.m_cda.rc==1062)
      {
        // 如果记录已存在,执行更新的SQL语句。
        if (stmtupt.execute()!=0) 
        {
          logfile.Write("stmtupt.execute() failed.\n%s\n%s\n",stmtupt.m_sql,stmtupt.m_cda.message); return -1;
        }
        else
          uptcount++;
      }
      else
      {
        // 抄这行代码的时候也要小心,经常有人在这里栽跟斗。
        logfile.Write("stmtins.execute() failed.\n%s\n%s\n",stmtins.m_sql,stmtins.m_cda.message); return -1;
      }
    }
    else
      inscount++;
  }
  
  // 把总记录数、插入记录数、更新记录数、消耗时长记录日志。
  logfile.Write("总记录数=%d,插入=%d,更新=%d,耗时=%.2f秒。\n",vstcode.size(),inscount,uptcount,Timer.Elapsed());

  // 提交事务。
  conn.commit();

  return 0;
}

// 把站点参数文件中加载到vstcode容器中。
bool LoadSTCode(const char *inifile)
{
  CFile File;

  // 打开站点参数文件。
  if (File.Open(inifile,"r")==false)
  {
    logfile.Write("File.Open(%s) failed.\n",inifile); return false;
  }

  char strBuffer[301];

  CCmdStr CmdStr;

  struct st_stcode stcode;

  while (true)
  {
    // 从站点参数文件中读取一行,如果已读取完,跳出循环。
    if (File.Fgets(strBuffer,300,true)==false) break;

    // 把读取到的一行拆分。
    CmdStr.SplitToCmd(strBuffer,",",true);

    if (CmdStr.CmdCount()!=6) continue;     // 扔掉无效的行。

    // 把站点参数的每个数据项保存到站点参数结构体中。
    memset(&stcode,0,sizeof(struct st_stcode));
    CmdStr.GetValue(0, stcode.provname,30); // 省
    CmdStr.GetValue(1, stcode.obtid,10);    // 站号
    CmdStr.GetValue(2, stcode.cityname,30);  // 站名
    CmdStr.GetValue(3, stcode.lat,10);      // 纬度
    CmdStr.GetValue(4, stcode.lon,10);      // 经度
    CmdStr.GetValue(5, stcode.height,10);   // 海拔高度

    // 把站点参数结构体放入站点参数容器。
    vstcode.push_back(stcode);
  }

  /*
  for (int ii=0;ii<vstcode.size();ii++)
    logfile.Write("provname=%s,obtid=%s,cityname=%s,lat=%.2f,lon=%.2f,height=%.2f\n",\
                   vstcode[ii].provname,vstcode[ii].obtid,vstcode[ii].cityname,vstcode[ii].lat,\
                   vstcode[ii].lon,vstcode[ii].height);
  */

  return true;
}

void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);

  conn.disconnect();

  exit(0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值