站点参数文件入库,不是站点数据入库
站点参数入库,注意不是站点数据入库
站点参数的某些东西是站点数据要用的比如站点参数数据中的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);
}