Написано: 27.10.2018

UG7_Total.cpp, использование STL в модуле проверки результатов звонкового экспорта.

Модуль UG7_Total.cpp является частью функционала программы Gt2crm3 (которая предназначена для формирования текстового файла звонков, необходимого для загрузки данных в систему CRM3 (звонковое хранилище)).

Модуль выполняет проверку сформированных данных, то есть:

а) производит считывание данных из сформированного файла, 
б) суммирует данные по оператору, направлению доходности и направлению трафика (и по коду платежа, если нужно), 
в) сохраняет итоги в файле и/или в БД (в зависимости от конфигурации).  

Данная проверка позволяет оперативно реагировать на проблемы, если со стороны потребителей данных (системы crm3) возникнут претензии о том, что данные искажены или недостоверны. В этом случае есть возможность быстро выполнить сверку данных (без обращения к звонковому файлу, содержащего миллионы строк данных) и в приемлемые сроки обнаружить ошибку, в какой бы системе и по какой бы причине она не возникла.

Алгоритмически задача проверки выполняется с помощью контейнера map STL:

    typedef map<string, TGood> goods; 

Здесь string представляет собой ключ (который содержит в качестве составных частей код оператора, направление доходности и трафика), а TGood представляет собой класс, содержащий итоги (количество звонков, общая длительность, сумма в валюте счета и в долларовом эквиваленте).

class TGood {
private:
    int FCalls;
    double FDuration;
    double FRub;
    double FUsd;
public:
    __fastcall TGood();
    __fastcall TGood(int calls, double duration, double rub, double usd);
    __fastcall TGood(const TGood &X);
    TGood & __fastcall operator =(const TGood &X);
    TGood & __fastcall operator +=(const TGood &X);
    __property int Calls = { read = FCalls };
    __property double Duration = { read = FDuration };
    __property double Rub = { read = FRub };
    __property double Usd = { read = FUsd };
};

Работа с контейнером goods ведется с помощью следующего класса (показаны основные методы для работы с контейнером):

class TTotals {
private:
    goods FGoods;
    // остальное удалено
public:
    __fastcall TTotals();
    // остальное удалено
    void __fastcall Add(
        const char *Scala, const char *InvDir, const char *Dir,
        const char *CallDate, const char *PayCode,
        int calls, double duration, double rub, double usd
    );
    void __fastcall Print(FILE *f);
};

В методе TTotals.Add() происходит заполнение контейнера:

void __fastcall TTotals::Add(const char *Scala, const char *InvDir,
    const char *Dir, const char *CallDate, const char *PayCode,
    int calls, double duration, double rub, double usd)
{
    TGood G(calls, duration, rub, usd);
    char buf[32];
    char bfA[8];
    char SDir[2], SInvDir[2];
    char code;
    string key;

    if(InvDir[0] == '0') {
        SInvDir[0] = 'T';
    } else {
        SInvDir[0] = 'F';
    }
    SInvDir[1] = '\0';

    if(Dir[0] == '0') {
        SDir[0] = 'I';
    } else {
        SDir[0] = 'O';
    }
    SDir[1] = '\0';

    if(FTstPayCodes) {
        sprintf(buf, "%s;%s;%s;%s;%s", Scala, SInvDir, SDir, CallDate, PayCode);
        key = buf;
    } else {
        code = (CallDate[0] - '0') * 10 + (CallDate[1] - '0');
        code |= ((InvDir[0] - '0') << 5) & 0x20;
        code |= ((Dir[0] - '0') << 6) & 0x40;
        sprintf(bfA, "%c%s", code, Scala);
        key = bfA;
    }

    goods::iterator p = FGoods.find(key);

    if (p == FGoods.end()) {
        // Нет узла, добавляем
        FGoods[key] = G;
    } else {
        // Есть узел, модифицируем
        FGoods[key] += G;
    }
}

Метод TTotals::Print() содержит код использования контейнера:

void __fastcall TTotals::Print(FILE *f)
{
    char SMin[32], SAmt[32], SUsd[32];

    string value;
    goods::iterator p;
    int calls;
    double duration;
    double rub;
    double usd;
    char buf[128];
    char code, InvDir, Dir;
    int D;
    TGood G;

    for (p = FGoods.begin(); p != FGoods.end(); ++p) {
        value = p->first;
        G = p->second;
        sprintf(buf, "%s", value);
        GetFloatStr(SMin, G.Duration/60);
        GetFloatStr(SAmt, G.Rub);
        GetFloatStr(SUsd, G.Usd);
        calls = G.Calls;
        duration = G.Duration;
        rub = G.Rub;
        if(FTstPayCodes) {
            fprintf(f, "%s;%d;%s;%s;%s\n",
                buf, calls, SMin, SAmt, SUsd );
        } else {
            code = buf[0];
            D = code & 0x1F;
            InvDir = (code & 0x20) >> 5;
            if(InvDir == 0) { InvDir = 'T'; } else { InvDir = 'F'; }
            Dir = (code & 0x40) >> 6;
            if(Dir == 0) { Dir = 'I'; } else { Dir = 'O'; }
            fprintf(f, "%s;%c;%c;%02d;%d;%s;%s;%s\n",
                &buf[1], InvDir, Dir, D, calls, SMin, SAmt, SUsd );
        }
    }
}

Процесс проверки результирующего файла выполняется в классе TG7_Tst:

class TG7_Tst : public TG7_Core
{
private:
    TTotals FTotals;
    void __fastcall TstBody();
	bool __fastcall TstFile(AnsiString Fn);
	void __fastcall TstOne(char *s);
	// остальное удалено
public:
    __fastcall TG7_Tst();
    __fastcall ~TG7_Tst();
	// остальное удалено
};

Ниже приводится код функции TstOne(), в которой производится разбор одной строки файла-результата:

/* Тестирование одной строчки из файла-результата.
*/
void __fastcall TG7_Tst::TstOne(char *s)
{
    int i = 0;
    char Scala[6], CallDate[24], PayCode[64], RevCost[2], Dir[2], RealDur[24];
    char Amt[24], AmtUsd[24];
    double FRealDur, FAmt, FAmtUsd;

    ParseStr(s, Scala, 2, ',', '\'', i);
    if(Scala[0] != '\0') {
        ParseStr(s, CallDate, 1, ',', '\'', i);
        CallDate[10] = '\0';
        ParseStr(s, PayCode, 13, ',', '\'', i);
        ParseStr(s, RevCost, 15, ',', '\'', i);
        ParseStr(s, Dir, 16, ',', '\'', i);
        ParseStr(s, RealDur, 5, ',', '\'', i);
        ParseStr(s, Amt, 9, ',', '\'', i);
        ParseStr(s, AmtUsd, 8, ',', '\'', i);
        FRealDur = atof(RealDur);
        FAmt = atof(Amt);
        FAmtUsd = atof(AmtUsd);
        FTotals.Add(
            Scala, RevCost, Dir, &CallDate[8], PayCode,
            1, FRealDur, FAmt, FAmtUsd
        );
    }
}

Здесь производится распарсивание строки на нужные токены (с помощью функции ParseStr(), здесь не показана), формирование переменных и вызов метода FTotals.Add для заполнения контейнера с итогами разбора.

Ниже приводится код функции для тестирования файла:

#define BUF_SIZE    1024*1024

/* Тестирование файла
*/
bool __fastcall TG7_Tst::TstFile(AnsiString Fn)
{
    char *buf;
    char sbuf[256];
    int FIn;
    int BufCnt, BufPtr = 0, SPtr = 0;

    if((buf = (char *) calloc(1, BUF_SIZE)) == NULL) {
        CErrLog = "Ошибка выделения буфера";
        CStat = errERR;
        goto L1;
    }

    if((FIn = FileOpen(Fn.c_str(), fmOpenRead | fmShareDenyWrite)) == -1) {
        CErrLog = Format("Ошибка открытия файла %s", ARRAYOFCONST((Fn)));
        CStat = errERR;
        goto L2;
    }

    CMsgLog = Format("Начало тестирования файла %s", ARRAYOFCONST((Fn)));

    while( (BufCnt = FileRead(FIn, buf, BUF_SIZE)) > 0) {
        for( BufPtr = 0; BufPtr < BufCnt; BufPtr++) {
            if(buf[BufPtr] != '\n') {
                sbuf[SPtr++] = buf[BufPtr];
            } else {
                sbuf[SPtr] = '\0';
                if(FTstDoIt) {
                    TstOne(sbuf);
                }
                SPtr = 0;
            }
        }
        memset(buf, '\0', BUF_SIZE);
    }

    FileClose(FIn);

L2:
    // освобождаем буфер
    try {
        free(buf);
    } catch (Exception &exc) {
        CErrLog = Format("Ошибка освобождения буфера: %s",
            ARRAYOFCONST((
            exc.Message
        )));
    }

L1:
    CMsgLog = Format("Окончание тестирования файла %s", ARRAYOFCONST((Fn)));

    return (CStat == OK);
}

В функции происходит выделение памяти под буфер, далее открывается файл для разбора, из файла происходит блочное считывание, после чего осуществляется вызов метода TstOne для каждой строки, находящейся в буфере.

В заключение приведен код TG7_Tst::TstBody(), в котором и осуществляется процесс проверки файла-результата:

/* Код процесса.
*/
void __fastcall TG7_Tst::TstBody()
{
    AnsiString S, File_to, DayStr;
    AnsiString File_det;
    int MIdx = 0, RCidx, fHndl_out, Day, j, i;
    char ToDurStr[32], ToMinStr[32], ToAmtStr[32], ToUsdStr[32];
    char FrDurStr[32], FrMinStr[32], FrAmtStr[32], FrUsdStr[32];
    bool found, created = false;
    FILE *FOut, *FDet;

    DayStr = CCallDate.FormatString("dd");

    if(FTstDet) {
        File_det = Format("%s%s.det", ARRAYOFCONST((
            CTotDir, CCallDate.FormatString("yyyymmdd") )));
        if((FDet = fopen(File_det.c_str(), "a")) == NULL) {
            CErrLog = Format("Ошибка открытия файла %s",
                ARRAYOFCONST((File_det
            )));
            return ;
        }
    }

    FTotals.TstPayCodes = FTstPayCodes;
    FTotals.TstInsViaProc = FTstInsViaProc;
    FTotals.Day = FDay;
    FTotals.ExpID = CExpID;
    FTotals.CommitRows = CCommitRows;

    TstDay();       // тестирование данных за день (может быть несколько файлов)

    if(FTstMakeCsv) {
        File_to = Format("%s", ARRAYOFCONST(( FTarget_tot )));
        if( FileExists(File_to) == false ) {
            CMsgLog = Format("Файл %s не существует. Создаю",
                ARRAYOFCONST((File_to
            )));
            created = true;
        }
        if((FOut = fopen(File_to.c_str(), "a")) == NULL) {
            CErrLog = Format("Ошибка открытия файла %s", ARRAYOFCONST((File_to)));
        } else {
            if(created) {
                FTotals.PrintHead(FOut);
            }
            FTotals.Print(FOut);
            fclose(FOut);
        }
    }

    if(FTstSaveRez) {
        CMsgLog = "Сохранение результатов тестирования в БД";
        if(FTotals.InsIntoRez(COra, CCallDate.FormatString("dd").ToIntDef(1))) {
            CMsgLog = "Результаты тестирования сохранены в БД";
        } else {
            CErrLog = "Ошибка при сохранении результатов тестирования";
        }
    }
}

Как видно, TstBody() делегирует процесс тестирования функции TstDay(), которая осуществляет вызовы функций TstFile() (на этой странице не показано), где происходит заполнение контейнера с результатами тестирования.

После чего происходит формирования файла с результатами тестирования (через вызов FTotals.Print) либо сохранение результатов в БД (на этой странице не показано).