代码先锋网 代码片段及技术文章聚合

基于USBDeview的自制USB设备监管系统实现(3)——USB S/N Checker

在本系列文章的第一章已经说明了USB S/N Checker程序的作用,本章将详细说明这个程序的编制思路,并附完整的源代码。

由于历史原因,USB S/N Checker在实现时被命名为usbdevicelogger(以下简称UDL)。

UDL用C/C++语言写成,其main函数如下:

int main(int argc, char* argv[])
{
   loadConfig(__configFilename, &config);

   /* 获取USB设备插入时间和主机名 */
   time_t currTime = time(NULL);
   struct tm *plugTime = localtime(&currTime);
   DWORD hostNameLength = sizeof(computerName) / sizeof(TCHAR);   // windows platform only
   GetComputerName((LPTSTR)computerName, &hostNameLength);        // windows platform only

   int serialNumberCount = loadSerialNumber(config.serialNumberFilename);
   char *currSerialNumber = argv[idx_serialNumber];
   bool found = checkSerialNumber(currSerialNumber, serialNumberCount);
   if (!found)
   {  // the serial number of the plugging device is illegal.
#ifdef __STOP_DEVICE__
      // stop the illegal USB device.
      sprintf(sysCmd, "usbdeview /stop_by_serial %s", currSerialNumber);
      system(sysCmd);
#endif // __STOP_DEVICE__
      // call webservice of NTMS to record the illegal action.
      notifyToNtms(plugTime, computerName, argc, argv);
   }
   saveDeviceInfo(plugTime, computerName, argc, argv, found);

   return found ? SUCCESS : ERROR_SN_ILLEGAL;
}

main函数揭示了UDL的大体运行流程:

  1. 首先获取当前时间,由于UDL是在插入U盘时被usbdeview**,因此可以认为UDL启动运行的时间就是U盘插入的时间;
  2. 随后调用Win32 API中的GetComputerName函数获取当前主机名,这是需要被记入日志的重要元素;
  3. 从磁盘文件(默认为legal_sn.txt)中加载合法的U盘***清单,这个***清单文件是文本文件,每个***占用一行;
  4. 从命令行参数中获取当前插入的USB设备的***,调用自定义函数checkSerialNumber检查本次插入的USB设备的***是否在合法***清单中;
  5. 如果在合法***清单中未找到当前***,则使用/stop_by_serial命令调用usbdeview程序禁用这个USB设备,并通知USB Management System(本文命名为NTMS);
  6. 将本次USB设备插入事件记录到本地日志中;
  7. 结束。

main函数的第一条语句是调用loadConfig来装载UDL配置文件udl.ini,这个文件样式如下:

deviceLog=C:\Windows\usbdevice.log
agentName=ntmsAgent.exe
serialNumber=.\legal_sn.txt
debugLog=.\debug.log
baseUrl="http://ntms.company.com/ntms/illegalusb"

这个文件指定了UDL的运行时属性,每个参数占用一行,采用“key=value”的样式。注意:key和value之间用“=”字符分隔,'='字符前后不能有空格。key不区分大小写。value中的URL、文件路径最好用半角双引号包围起来。配置文件属性说明如下:

  • deviceLog属性指定了日志文件的文件名(完整绝对路径);
  • agentName属性指定了NTMS服务代理程序,UDL通过这个代理程序向NTMS发送消息;
  • serialNumber属性指定了UDL需要依据的合法***文件,可以是相对路径或绝对路径;
  • debugLog属性指定了UDL调试信息记录在哪个文件里;
  • baseUrl属性指定了URL可以通过浏览器向NTMS传递USB插入信息的URL(UDL可以通过条件编译被编译成绕过NTMS Agent直接调用浏览器向NTMS发送数据的模式)。

下面对UDL的部分函数做个说明。

  • checkSerialNumber函数
bool checkSerialNumber(char currSerialNumber[], int rangeCount)
{
   bool found = false;
   for(int index = 0; index < rangeCount; index ++)
   {
      if (strlen(currSerialNumber) == 0)     // for the device that has not serial number.
      {
         found = true;
         break;
      }
      int result = strcmp(currSerialNumber, legalSerianNumber[index]);
      if (result == 0)
      {
         found = true;
         break;
      }
   }
   return found;
}

注意里面有一段

      if (strlen(currSerialNumber) == 0)     // for the device that has not serial number.
      {
         found = true;
         break;
      }

这是判断当前插入的USB设备的***是否为空,若为空,则表示这个设备不是U盘,一般是USB key、USB键鼠之类的,这样的设备可以认为是合法设备,不用管它。

  • notifyToNtms函数
void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[])
{  // Call NTMS agent to transfer the detail of USB device to NTMS.
#ifdef __USE_WEBSERVICE__
   char *dateTimeStr = dateTime2String(plugTime);
   sprintf(sysCmd, "%s \"%s\" \"%s\"", config.ntmsAgent, dateTimeStr, hostName);
   for(int argIndex = 1; argIndex < argc; argIndex ++)
   {
      strcat(sysCmd, " \"");
      strcat(sysCmd, argv[argIndex]);
      strcat(sysCmd, "\"");
   }
   free(dateTimeStr);
#else //  __USE_WEBSERVICE__
   composeUrl(plugTime, hostName, argc, argv, ntmsUrl);
   sprintf(sysCmd, "start %s", ntmsUrl);
#endif //  __USE_WEBSERVICE__
#ifdef __DEBUG_LOG__
   logDebug(plugTime, sysCmd);
#endif // __DEBUG_LOG__
#ifdef __INVOKE_NTMSAGENT__
   system(sysCmd);
#endif // __INVOKE_NTMSAGENT__

   return;
}

这个函数其实没啥复杂的,之所以拿出来说明是因为这里用了条件编译,可以使得UDL采用两种不同的通知方式。

当代码文件定义了#define __USE_WEBSERVICE__时,notifyToNtms函数会合成一个调用ntmsAgent代理程序的命令行,存入sysCmd字符数组中;若未定义__USE_WEBSERVICE__宏,则notifyToNtms函数会调用composeUrl函数合成一个调用系统默认浏览器访问URL的命令行,存入sysCmd字符数组中。完成命令行构造之后,notifyToNtms函数调用system(sysCmd)来执行通知NTMS的动作。

  • composeUrl
void composeUrl(struct tm *plugTime, char hostName[], int argc, char *argv[], char url[])
{
   char paramBuffer[64];
   char *dateTimeStr = dateTime2String(plugTime);
   char *tempUrl = (char *)malloc(__MAX_URL_LENGTH__);
   sprintf(tempUrl, "%s?\"%s=%s&%s=%s", config.baseUrl, urlParamNames[0], dateTimeStr, urlParamNames[1], hostName);
   for(int paramIndex = 1; paramIndex < argc; paramIndex ++)
   {
      sprintf(paramBuffer, "&%s=%s", urlParamNames[paramIndex + 1], argv[paramIndex]);
      strcat(tempUrl, paramBuffer);
   }
   strcat(tempUrl, "\"");
#ifdef __DEBUG_LOG__
   logDebug(plugTime, tempUrl);
#endif // __DEBUG_LOG__
   replaceSpace(tempUrl, url);
   free(tempUrl);
   free(dateTimeStr);
#ifdef __DEBUG_LOG__
   logDebug(plugTime, url);
#endif // __DEBUG_LOG__
}

composeUrl函数用于合成形如“http://ntms.company.com/ntms/illegalusb?hostname=computername&datetime=2019-04-02%2014:23:30&serialNumber=aaddhhuua2132&.....”这样的URL,通过浏览器来发送给NTMS。这个函数会调用replaceSpace将合成的URL中的空格字符转换为“%20”字符串。replaceSpace的代码如下所示:

int replaceSpace(char src[], char dest[])    // replace the space characters to "%20"
{
   char alterString[] = "%20";
   int alterLength = strlen(alterString);
   int length = strlen(src);
   int srcIndex, destIndex = 0;
   int replaceCount = 0;
   for(srcIndex = 0; srcIndex < length; srcIndex ++)
   {
      if (src[srcIndex] != ' ')
      {
         dest[destIndex] = src[srcIndex];
         destIndex ++;
      }
      else
      {
         dest[destIndex] = '\0';
         strcat(dest, alterString);
         destIndex += alterLength;
         replaceCount ++;
      }
   }
   dest[destIndex] = '\0';

   return replaceCount;
}

最后,列出UDL的完整源代码如下:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#ifdef __cplusplus
#include <string>

using namespace std;
#endif // __cplusplus

/**
   关于条件编译宏定义的说明:
   __cplusplus__              编译器内置宏。当使用C++编译器以C++方式编译此文件时,该宏被编译器定义。
   __STOP_DEVICE__            若定义了此宏,则插入非法USB设备时,usbdevicelogger会停用(断开)该设备,在usbdeview
                              中将看不到此设备。当插入的设备为USB存储设备时,其对应的盘符会消失。UDL正式部署前,
                              应该启用该宏定义,并重新编译UDL后部署。
   __SAVE_XML__               若定义了此宏,则插入USB设备时,usbdevicelogger会调用usbdeview将当前接入到计算机中的
                              所有USB设备详情保存到usbdevice.xml文件中。如果不需要调用usbdeview保存USB设备记录,则
                              无需启用该宏定义。
   __USE_WEBSERVICE__         若定义了此宏,则插入非法USB设备时,usbdevicelogger会调用NTMS agent将非法设备信息通过
                              WS接口传递给NTMS;否则,将调用系统默认浏览器,以URL方式传递给NTMS。
   __INVOKE_NTMSAGENT__       若定义了此宏,则会将非法插入设备的信息传递给NTMS,传递方法由__USE_WEBSERVICE__宏定义
                              决定。UDL正式部署前,应启用该宏定义,并重新编译UDL后部署。
   __HIDE_WINDOW__            若定义了此宏,则usbdevicelogger的窗口不会闪现。
   __DEBUG_LOG__              若定义了此宏,则usbdevicelogger运行时会生成调试日志,保存在debug.log文件中。当发现UDL
                              运行异常时,可启用该宏定义,并重新编译UDL后部署。
*/
// #define __STOP_DEVICE__
// #define __SAVE_XML__
#define __USE_WEBSERVICE__
// #define __INVOKE_NTMSAGENT__
#define __USE_CONFIG_FILE__
// #define __HIDE_WINDOW__
// #define __DEBUG_LOG__

/** 以下是常数宏定义 */
#define __LEGAL_SN_COUNT__ 1024
#define __SN_LENGTH__ 64
#define __CMDLINE_LENGTH__ 16384
#define __MAX_URL_LENGTH__ 16384

const int SUCCESS = 0;
const int ERROR_SN_ILLEGAL = 1;

const char __configFilename[] = "udl.ini";
const char __deviceLogKey[] = "DEVICELOG";
const char __agentNameKey[] = "AGENTNAME";
const char __debugLogKey[] = "DEBUGLOG";
const char __baseUrlKey[] = "BASEURL";
const char __serialNumberKey[] = "SERIALNUMBER";

const char splitChar = '|';
const char urlParamNames[][32] =
{
   "plugTime",
   "hostName",
   "deviceDescr",
   "serialNumber",
   "deviceType",
   "serviceName",
   "deviceClass",
   "deviceMfg",
   "driverFile",
   "driverVersion",
   "firmwareVersion",
   "productName",
   "vendorName",
   "legalFlag"
};

typedef struct __tagConfig
{
   char logFilename[MAX_PATH + 1];
   char ntmsAgent[MAX_PATH + 1];
   char serialNumberFilename[MAX_PATH + 1];
   char debugLogFilename[MAX_PATH + 1];
   char baseUrl[__MAX_URL_LENGTH__ + 1];
} Config;

Config config =
{
   "C:\\Windows\\usbdevice.log",
   ".\\ntmsagent",
   ".\\legal_sn.txt",
   ".\\debug.log",
   "http://ntms.company.com/ntms/illegalusb"
};

char legalSerianNumber[__LEGAL_SN_COUNT__][__SN_LENGTH__ + 1];  // 可以存放__LEGAL_COUNT__个合法***,每个***256个字节。
char computerName[MAX_COMPUTERNAME_LENGTH + 1];
char sysCmd[__CMDLINE_LENGTH__ + 1];
char ntmsUrl[__MAX_URL_LENGTH__ + 1];

typedef enum __tagDeviceParamsIndex   // 用于指示命令行参数中设备参数信息所在的位置索引。
{
   idx_deviceDescr = 1,
   idx_serialNumber = 2,
   idx_deviceType = 3,
   idx_serviceName = 4,
   idx_deviceClass = 5,
   idx_deviceMfg = 6,
   idx_driverFile = 7,
   idx_driverVersion = 8,
   idx_firmwareVersion = 9,
   idx_productName = 10,
   idx_vendorName = 11
} DeviceParamsIndex;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

WINBASEAPI HWND WINAPI GetConsoleWindow();

int loadConfig(const char configFile[], Config *config);
void saveDeviceInfo(struct tm *plugTime, char hostName[], int argc, char* argv[], bool legal);    // 保存当前插入的USB设备的详细信息到文件中。
int loadSerialNumber(const char fileName[]);    // 读入合法的USB设备***,返回值为读入的***个数。
bool checkSerialNumber(char currSerialNumber[], int rangeCount);     // 检查插入的USB设备***是否合法。合法则返回true,非法则返回false。
void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[]);   // 将非法USB插入记录提交给NTMS。
void composeUrl(struct tm *plugTime, char hostName[], int argc, char *argv[], char url[]);
char *dateTime2String(struct tm *dateTime);

int replaceSpace(char src[], char dest[]);
void strupr(char str[]);

void logDebug(struct tm *plugTime, char logStr[]);

#ifdef __cplusplus
}
#endif // __cplusplus

int main(int argc, char* argv[])
{
#ifdef __HIDE_WINDOW__
   HWND consoleWindowHandle = GetConsoleWindow();
   ShowWindow(consoleWindowHandle, SW_HIDE);
#endif // __HIDE_WINDOW__

   loadConfig(__configFilename, &config);

   /* 获取USB设备插入时间和主机名 */
   time_t currTime = time(NULL);
   struct tm *plugTime = localtime(&currTime);
   DWORD hostNameLength = sizeof(computerName) / sizeof(TCHAR);   // windows platform only
   GetComputerName((LPTSTR)computerName, &hostNameLength);        // windows platform only

   int serialNumberCount = loadSerialNumber(config.serialNumberFilename);
   char *currSerialNumber = argv[idx_serialNumber];
   bool found = checkSerialNumber(currSerialNumber, serialNumberCount);
   if (!found)
   {  // the serial number of the plugging device is illegal.
#ifdef __STOP_DEVICE__
      // stop the illegal USB device.
      sprintf(sysCmd, "usbdeview /stop_by_serial %s", currSerialNumber);
      system(sysCmd);
#endif // __STOP_DEVICE__
      // call webservice of NTMS to record the illegal action.
      notifyToNtms(plugTime, computerName, argc, argv);
   }
   saveDeviceInfo(plugTime, computerName, argc, argv, found);

   return found ? SUCCESS : ERROR_SN_ILLEGAL;
}

int loadConfig(const char configFile[], Config *config)
{
   int loadCode = 0;
#ifdef __USE_CONFIG_FILE__
   const char splitter = '=';

   char *valuePtr;
   char key[32];
   char buffer[__MAX_URL_LENGTH__ + 1];
   FILE *cfgFp = fopen(configFile, "r");
   if (cfgFp != NULL)
   {
      loadCode = 0;
      do {
         fscanf(cfgFp, "%s", buffer);
         valuePtr = strchr(buffer, splitter);
         if (valuePtr == NULL)
         {
            loadCode = -2;
            break;
         }
         *valuePtr = '\0';
         valuePtr ++;
         // strncpy(key, buffer, strlen(buffer) - strlen(valuePtr) + 1);
         strcpy(key, buffer);
         strupr(key);
         if (!strcmp(key, __deviceLogKey))
         {
            strcpy(config->logFilename, valuePtr);
         }
         else if (!strcmp(key, __agentNameKey))
         {
            strcpy(config->ntmsAgent, valuePtr);
         }
         else if (!strcmp(key, __serialNumberKey))
         {
            strcpy(config->serialNumberFilename, valuePtr);
         }
         else if (!strcmp(key, __baseUrlKey))
         {
            strcpy(config->baseUrl, valuePtr);
         }
         else if (!strcmp(key, __debugLogKey))
         {
            strcpy(config->debugLogFilename, valuePtr);
         }
         else
         {
            loadCode = -2;
            break;
         }
      } while(!feof(cfgFp));
      fclose(cfgFp);
   }
   else
   {
      loadCode = -1;
   }
#endif // __USE_CONFIG_FILE__

   return loadCode;
}

void saveDeviceInfo(struct tm *plugTime, char hostName[], int argc, char* argv[], bool legal)
{
   FILE *ofp = fopen(config.logFilename, "a+");

   // record the date-time the USB device was plugged..
   fprintf(ofp, "%04d-%02d-%02d %02d:%02d:%02d",
           plugTime->tm_year + 1900, plugTime->tm_mon + 1, plugTime->tm_mday,
           plugTime->tm_hour, plugTime->tm_min, plugTime->tm_sec);
   // record the computer name.
   fprintf(ofp, "%c%s", splitChar, hostName);
   // record the detail of the plugged USB device.
   for(int argIdx = 1; argIdx < argc; argIdx ++)
   {
      fprintf(ofp, "%c%s", splitChar, argv[argIdx]);
   }
   fprintf(ofp, "%c%c", splitChar, legal ? 'Y' : 'N');
   fprintf(ofp, "\n");

   fclose(ofp);
#ifdef __SAVE_XML__
   system("usbdeview /sxml usbdevice.xml");     // optional //
#endif // __SAVE_XML__
}

int loadSerialNumber(const char fileName[])
{
   int count = 0;
   FILE *ifp = fopen(fileName, "r");
   if (ifp != NULL)
   {
      do {
         fscanf(ifp, "%s", legalSerianNumber[count]);
         count ++;
      } while(!feof(ifp));
      fclose(ifp);
   }

   return count;
}

bool checkSerialNumber(char currSerialNumber[], int rangeCount)
{
   bool found = false;
   for(int index = 0; index < rangeCount; index ++)
   {
      if (strlen(currSerialNumber) == 0)     // for the device that has not serial number.
      {
         found = true;
         break;
      }
      int result = strcmp(currSerialNumber, legalSerianNumber[index]);
      if (result == 0)
      {
         found = true;
         break;
      }
   }
   return found;
}

void notifyToNtms(struct tm *plugTime, char hostName[], int argc, char *argv[])
{  // Call NTMS agent to transfer the detail of USB device to NTMS.
#ifdef __USE_WEBSERVICE__
   char *dateTimeStr = dateTime2String(plugTime);
   sprintf(sysCmd, "%s \"%s\" \"%s\"", config.ntmsAgent, dateTimeStr, hostName);
   for(int argIndex = 1; argIndex < argc; argIndex ++)
   {
      strcat(sysCmd, " \"");
      strcat(sysCmd, argv[argIndex]);
      strcat(sysCmd, "\"");
   }
   free(dateTimeStr);
#else //  __USE_WEBSERVICE__
   composeUrl(plugTime, hostName, argc, argv, ntmsUrl);
   sprintf(sysCmd, "start %s", ntmsUrl);
#endif //  __USE_WEBSERVICE__
#ifdef __DEBUG_LOG__
   logDebug(plugTime, sysCmd);
#endif // __DEBUG_LOG__
#ifdef __INVOKE_NTMSAGENT__
   system(sysCmd);
#endif // __INVOKE_NTMSAGENT__

   return;
}

void composeUrl(struct tm *plugTime, char hostName[], int argc, char *argv[], char url[])
{
   char paramBuffer[64];
   char *dateTimeStr = dateTime2String(plugTime);
   char *tempUrl = (char *)malloc(__MAX_URL_LENGTH__);
   sprintf(tempUrl, "%s?\"%s=%s&%s=%s", config.baseUrl, urlParamNames[0], dateTimeStr, urlParamNames[1], hostName);
   for(int paramIndex = 1; paramIndex < argc; paramIndex ++)
   {
      sprintf(paramBuffer, "&%s=%s", urlParamNames[paramIndex + 1], argv[paramIndex]);
      strcat(tempUrl, paramBuffer);
   }
   strcat(tempUrl, "\"");
#ifdef __DEBUG_LOG__
   logDebug(plugTime, tempUrl);
#endif // __DEBUG_LOG__
   replaceSpace(tempUrl, url);
   free(tempUrl);
   free(dateTimeStr);
#ifdef __DEBUG_LOG__
   logDebug(plugTime, url);
#endif // __DEBUG_LOG__
}

void logDebug(struct tm *plugTime, char logStr[])
{
   FILE *logFp = fopen(config.debugLogFilename, "a");
   char *dtStr = dateTime2String(plugTime);
   fprintf(logFp, "%s%c%s\n", dtStr, splitChar, logStr);
   free(dtStr);
   fclose(logFp);
   // system("pause");
}

char *dateTime2String(struct tm *dateTime)
{
   char *dateTimeString = (char *)malloc(64);
   if (dateTime != NULL)
   {  // if convert success, the memory space of dateTimeString will not be freed.
      sprintf(dateTimeString, "%04d-%02d-%02d %02d:%02d:%02d",
              dateTime->tm_year + 1900, dateTime->tm_mon + 1, dateTime->tm_mday,
              dateTime->tm_hour, dateTime->tm_min, dateTime->tm_sec);
   }
   else
   {
      free(dateTimeString);
      dateTimeString = NULL;
   }
   return dateTimeString;
}

int replaceSpace(char src[], char dest[])    // replace the space characters to "%20"
{
   char alterString[] = "%20";
   int alterLength = strlen(alterString);
   int length = strlen(src);
   int srcIndex, destIndex = 0;
   int replaceCount = 0;
   for(srcIndex = 0; srcIndex < length; srcIndex ++)
   {
      if (src[srcIndex] != ' ')
      {
         dest[destIndex] = src[srcIndex];
         destIndex ++;
      }
      else
      {
         dest[destIndex] = '\0';
         strcat(dest, alterString);
         destIndex += alterLength;
         replaceCount ++;
      }
   }
   dest[destIndex] = '\0';

   return replaceCount;
}

void strupr(char str[])
{
   int length = strlen(str);
   for(int index = 0; index < length; index ++)
   {
      if ((str[index] >= 'a') && (str[index] <= 'z'))
      {
         str[index] -= 0x20;
      }
   }
}

以上代码用Code::Blocks + MinGW 5.10编译通过,并已经过简单测试。

版权声明:本文为kingfox原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/kingfox/article/details/89813482

智能推荐

Spell checker

Description You, as a member of a development team for a new spell checking program, are to write a module that will check the correctness of given words using a known dictionary of all correct words ...

empty Checker

empty Checker...

Spell checker

You, as a member of a development team for a new spell checking program, are to write a module that will check the correctness of given words using a known dictionary of all correct words in all their...

ValidationError: Your model ir_version is higher than the checker‘s 完美解决

1. 情景介绍         我用pytorch训练的模型,将训练的模型转化为.onnx文件,然而,在模型验证的时候报错,错误栈如下:          ValidationError: Your model ir_version is higher than the checker's   &n...

猜你喜欢

【MATLAB编程实例练习】-(30)Knight‘s Tour Checker(马走日)

题目: 来源于Mathwork上的Cody,Problem 96 - Knight’s Tour Checker, Given a matrix a, determine whether or not a legal knight’s tour is present. The knight’s tour always follows the pattern 1,...

USACO1.5 Checker Challenge(checker)

2019独角兽企业重金招聘Python工程师标准>>>         本题是经典的n皇后问题,使用回溯法求解。第一次用一个for循环检测当前位置是否和前面的皇后冲突,结果在最后一个测试例输入13时,超时0.436s,后改用二维数组直接判断,输入13时仅用时0.562秒,再一次显示学会利用空间换时间的...

【验证小白】只有SV+modelsim学验证(3)——加checker到环境中

前言 之前把环境搭起来,写好了gen、drv、mon后就感觉自己理解的还是有点问题,歇了好长一段时间。最近听大神讲了好多细节,感觉理解加深了很多,于是乎继续开动把checker加入环境顺便构建一个合理的结束仿真机制(之前的结束仿真太简单粗暴了)。 相关博文: https://blog.csdn.net/moon9999/article/details/81436828 https://blog.c...

pt-duplicate-key-checker检查数据库的重复索引

   pt-duplicate-key-checker这款工具也是percona-toolkit中一款非常适用的工具,它可以帮助你检测表中重复的索引或者主键。我们知道索引会更查询带来好处,但是过量的索引反而可能会使数据库的性能降低,这款工具可以帮助我们找到重复的索引并且还会给你删除重复索引的建议语句,非常好用。     &nbs...

剑指offer | 数组中出现次数超过一半的数字

题目: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 思路: ①作一个判断条件。 ②作俩个for循环,用每一个值依次和后面的值作比较。 ③当这俩个值相等时,计数加一。 ④当计数值大于数组长度的一半时,输出这个值就好了。 这里要注意in...