65.9K
CodeProject 正在变化。 阅读更多。
Home

问题跟踪器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (18投票s)

2014年7月14日

CPOL

16分钟阅读

viewsIcon

43290

downloadIcon

1039

今天发现了什么 bug 吗?那么这个工具可能会为您节省一些时间

注意:必须在启动应用程序之前运行 MySQL 脚本!

引言

最近我发现了一个很棒的基于 Web 的无痛 Bug 跟踪工具。但在此之前,我发现它缺少一些功能,例如:

  • 发送通知电子邮件的能力(如果找到解决方案)
  • 附件(任何类型)的能力,例如屏幕截图等
  • 记录 Bug 之间交互的能力(依赖关系)
  • 开发为独立的 Windows 二进制应用程序

对于我的某些任务,ToDoListBugReporter 项目是一个非常好的替代方案;但我需要一个更具可伸缩性、分布式解决方案……

使用软件

您是否运行了 MySQL 脚本来创建数据库?那么是时候建立与数据库的第一个连接了。您需要输入服务器名称、数据库名称、用户名和密码才能访问数据库,如下图所示:(请确保您拥有在“问题跟踪器”数据库表中应用 INSERTUPDATEDELETE 语句的适当权限)

每次启动“问题跟踪器”应用程序时,它都会使用 GetUserNameEx 函数(如果第一个函数失败,则使用 GetUserName 函数)检查当前登录用户,并在用户不存在时将其添加到数据库。我假设使用此软件的计算机处于使用 Active Directory 身份验证的私有网络中,而大多数软件公司都是如此。

接下来,您应该通过“问题跟踪器”系统菜单中的设置...选项来选择您的设置。

现在您已准备好使用“问题跟踪器”应用程序了。

  • 要添加新 bug,请单击“新建”,填写信息,然后单击“插入”;要中止操作,请按“取消”按钮。
  • 要修改 bug 的数据,请从顶部的列表中选择它,单击“更改”,更新信息,然后单击“更新”;要中止操作,请按“取消”按钮。
  • 要将 bug 的信息保存到 HTML 文档中,请从顶部的列表中选择它,单击“另存为...”,然后选择文件名,然后按“确定”。
  • 要打印 bug 的信息,请从顶部的列表中选择它,单击“打印...”,然后选择您的打印机,然后按“确定”。
  • 要按特定字段搜索数据库,请取消选择顶部的所有问题,填写搜索条件,然后按“搜索”。

实现

“问题跟踪器”应用程序的设计考虑了三个抽象层(因此可以轻松扩展此软件)。

  • MFC/应用程序层 - 提供典型 Windows 应用程序的基本功能;此层在 CIssueTrackerAppCIssueTrackerDlgCUserConfigurationDlgCFileAttachementsDlgCConnectionSettingsDlgCSmtpConfigurationDlg 类中实现;
  • 对象/数据操作层 - 提供插入/删除/搜索用户、问题及其关联文件和注释的特定功能;它在 CIssueUserItemCIssueUserListCIssueFileItemCIssueFileListCIssueNoteItemCIssueNoteListCIssueDataItemCIssueDataListcode 类中实现;
  • ODBC 数据库访问层 - 提供通过 ODBC API 访问数据库的包装函数;它在 CDatabaseConnectorCDatabaseRecordset 类中实现。

关于 MFC/应用程序层的类没什么好说的。

  • CIssueTrackerDlg 类是应用程序的主界面;
  • CUserConfigurationDlg 类实现用户配置对话框;
  • CFileAttachementsDlg 类允许用户对附加到 bug 的文件执行基本操作;
  • CConnectionSettingsDlg 类允许用户设置数据库连接;
  • CSmtpConfigurationDlg 类允许用户设置 SMTP 服务器连接。

CIssueUserItem 类是 IssueUser 表的包装器,它具有以下成员函数:

  • UINT GetID() - 返回用户的 ID;
  • SetID( UINT uintID ) - 设置用户的 ID;
  • CString GetFullName() - 返回用户的全名;
  • void SetFullName( CString lpszFullName ) - 设置用户的全名;
  • CString GetEmailAddr() - 返回用户的电子邮件地址;
  • void SetEmailAddr( CString lpszEmailAddr ) - 设置用户的电子邮件地址;
  • GetNotification() - 如果用户希望通过电子邮件收到通知,则返回 TRUE,否则返回 FALSE
  • void SetNotification( UINT uintNotification ) - 为用户启用/禁用电子邮件通知;
  • void CopyDataFrom( CIssueUserItem * pIssueUser ) - 复制同类型另一个对象的数据。

CIssueUserList 类是 CIssueUserItem 对象的数组,它具有以下成员函数:

  • int GetSize() - 返回当前可用用户的计数;
  • CIssueUserItem * GetItemAt( int nItemIndex ) - 返回数组中 nItemIndex 位置的用户;
  • void SetItemAt( int nItemIndex, CIssueUserItem * pIssueUser ) - 将新用户设置到数组的 nItemIndex 位置;
  • void RemoveAllItems() - 删除数组中存储的所有用户;
  • BOOL LoadAllItems() - 从数据库加载所有用户;
  • BOOL InsertItem( CIssueUserItem * pIssueUser ) - 将一个新用户插入数组,也插入数据库;
  • BOOL UpdateItem( CIssueUserItem * pIssueUser ) - 更新数组中用户的数​​据,也更新数据库;
  • BOOL DeleteItem( CIssueUserItem * pIssueUser ) - 从数组中删除用户,也从数据库中删除;
  • CIssueUserItem * SearchItem( UINT uintID, BOOL bUseDatabase ) - 根据用户的 ID 在数组/数据库中搜索用户;
  • CIssueUserItem * SearchItem( CString lpszFullName, BOOL bUseDatabase ) - 根据用户的全名在数组/数据库中搜索用户;
  • CIssueUserItem * SearchAddr( CString lpszEmailAddr, BOOL bUseDatabase ) - 根据用户的电子邮件地址在数组/数据库中搜索用户;
  • UINT GetUserLoggedID() - 返回当前登录用户的 ID;
  • void SetUserLoggedID( UINT uintID ) - 设置当前登录用户的 ID;
  • CString GetUserLoggedName() - 返回当前登录用户的全名;
  • void SetUserLoggedName( CString lpszName ) - 设置当前登录用户的全名;
  • CString GetUserLoggedEmailAddr() - 返回当前登录用户的电子邮件地址;
  • void SetUserLoggedEmailAddr( CString lpszEmailAddr ) - 设置当前登录用户的电子邮件地址;
  • BOOL ValidateCurrentUserLogged() - 如果当前登录用户不在数据库中,则将其添加到数据库。

CIssueFileItem 类是 IssueFile 表的包装器,它具有以下成员函数:

  • UINT GetID() - 返回文件的 ID;
  • void SetID( UINT uintID ) - 设置文件的 ID;
  • UINT GetIssueID() - 返回与文件关联的问题的 ID;
  • void SetIssueID( UINT uintIssueID ) - 设置与文件关联的问题的 ID;
  • CIssueUserItem * GetAuthor() - 返回文件的作者;
  • void SetAuthor( CIssueUserItem * itemAuthor ) - 设置文件的作者;
  • CString GetFileName() - 返回文件名;
  • void SetFileName( CString lpszFileName ) - 设置文件名;
  • UINT GetFileSize() - 返回文件大小(以字节为单位);
  • void SetFileSize( UINT uintFileSize ) - 设置文件大小(以字节为单位);
  • CTime GetModified() - 返回文件的修改日期/时间;
  • void SetModified( CTime timeModified ) - 设置文件的修改日期/时间;
  • CLongBinary * GetContent() - 获取文件内容;
  • void SetContent( CLongBinary * byteContent ) - 设置文件内容;
  • void CopyDataFrom( CIssueFileItem* pIssueFile ) - 复制同类型另一个对象的数据。

CIssueFileList 类是 CIssueFileItem 对象的数组,它具有以下成员函数:

  • int GetSize() - 返回当前可用的文件数量(针对某个问题);
  • CIssueFileItem * GetItemAt( int nItemIndex ) - 返回数组中 nItemIndex 位置的文件;
  • void SetItemAt( int nItemIndex, CIssueFileItem * pIssueFile ) - 将新文件设置到数组的 nItemIndex 位置;
  • void RemoveAllItems() - 删除数组中存储的所有文件;
  • BOOL LoadAllItems( UINT uintIssueID, CIssueUserList * listIssueUser ) - 从数据库加载与某个问题相关的所有文件;
  • BOOL InsertItem( CIssueFileItem * pIssueFile ) - 将一个新文件插入数组,也插入数据库;
  • BOOL UpdateItem( CIssueFileItem * pIssueFile ) - 更新数组中文件的数​​据,也更新数据库;
  • BOOL DeleteItem( CIssueFileItem * pIssueFile ) - 从数组中删除文件,也从数据库中删除;
  • CIssueFileItem * SearchItem( UINT uintID, CIssueUserList * listIssueUser, BOOL bUseDatabase ) - 根据文件的 ID 在数组/数据库中搜索文件。

CIssueNoteItem 类是 IssueNote 表的包装器,它具有以下成员函数:

  • UINT GetID() - 返回注释的 ID;
  • void SetID( UINT uintID ) - 设置注释的 ID;
  • UINT GetIssueID() - 返回与注释相关的问题的 ID;
  • void SetIssueID( UINT uintIssueID ) - 设置与注释相关的问题的 ID;
  • CIssueUserItem * GetAuthor() - 返回注释的作者;
  • void SetAuthor( CIssueUserItem * itemAuthor ) - 设置注释的作者;
  • UINT GetFileSize() - 返回注释的大小(以字节为单位);
  • void SetFileSize( UINT uintFileSize ) - 设置注释的大小(以字节为单位);
  • CTime GetModified() - 返回注释的修改日期/时间;
  • void SetModified( CTime timeModified ) - 设置注释的修改日期/时间;
  • CLongBinary * GetContent() - 获取注释的内容;
  • void SetContent( CLongBinary * byteContent ) - 设置注释的内容;
  • void CopyDataFrom( CIssueNoteItem* pIssueNote ) - 复制同类型另一个对象的数据。

CIssueNoteList 类是 CIssueNoteItem 对象的数组,它具有以下成员函数:

  • int GetSize() - 返回当前可用的注释数量(针对某个问题);
  • CIssueNoteItem * GetItemAt( int nItemIndex ) - 返回数组中 nItemIndex 位置的注释;
  • void SetItemAt( int nItemIndex, CIssueNoteItem * pIssueNote ) - 将新注释设置到数组的 nItemIndex 位置;
  • void RemoveAllItems() - 删除数组中存储的所有注释;
  • BOOL LoadAllItems( UINT uintIssueID, CIssueUserList * listIssueUser ) - 从数据库加载与某个问题相关的所有注释;
  • BOOL InsertItem( CIssueNoteItem * pIssueNote ) - 将一个新注释插入数组,也插入数据库;
  • BOOL UpdateItem( CIssueNoteItem * pIssueNote ) - 更新数组中注释的数​​据,也更新数据库;
  • BOOL DeleteItem( CIssueNoteItem * pIssueNote ) - 从数组中删除注释,也从数据库中删除;
  • CIssueNoteItem * SearchItem( UINT uintID, CIssueUserList * listIssueUser, BOOL bUseDatabase ) - 根据注释的 ID 在数组/数据库中搜索注释。

CIssueDataItem 类是 IssueData 表的包装器,它具有以下成员函数:

  • UINT GetID() - 返回问题的 ID;
  • void SetID( UINT uintID ) - 设置问题的 ID;
  • CString GetTitle() - 返回问题的标题;
  • void SetTitle( CString lpszTitle ) - 设置问题的标题;
  • CString GetProduct() - 返回生成问题的产品;
  • void SetProduct( CString lpszProduct ) - 设置生成问题的产品;
  • CString GetComponent() - 返回生成问题的组件;
  • void SetComponent( CString lpszComponent ) - 设置生成问题的组件;
  • CString GetVersion() - 返回生成问题的软件版本;
  • void SetVersion( CString lpszVersion ) - 设置生成问题的软件版本;
  • CString GetPlatform() - 返回生成问题的软件平台;
  • void SetPlatform( CString lpszPlatform ) - 设置生成问题的软件平台;
  • UINT GetStatus() - 设置问题的状态;
  • void SetStatus( UINT uintStatus ) - 返回问题的状态;
  • UINT GetImportance() - 返回问题的优先级;
  • void SetImportance( UINT uintImportance ) - 设置问题的优先级;
  • CString GetMilestone() - 返回问题的截止日期;
  • void SetMilestone( CString lpszMilestone ) - 设置问题的截止日期;
  • CIssueUserItem * GetReporter() - 返回报告此问题的用户;
  • SetReporter( CIssueUserItem * itemReporter ) - 设置报告此问题的用户;
  • CIssueUserItem * GetResolver() - 返回应修复此问题的软件开发人员;
  • void SetResolver( CIssueUserItem * itemResolver ) - 设置应修复此问题的软件开发人员;
  • CIssueUserItem * GetConfirmer() - 返回将确认此问题修复的 QA 联系人;
  • void SetConfirmer( CIssueUserItem * itemConfirmer ) - 设置将确认此问题修复的 QA 联系人;
  • CString GetDependencies() - 返回此问题所依赖的其他问题的 ID;
  • void SetDependencies( CString lpszDependencies ) - 设置此问题所依赖的其他问题的 ID;
  • CString GetBlocksIssues() - 返回此问题所阻止的其他问题的 ID;
  • void SetBlocksIssues( CString lpszBlocksIssues ) - 设置此问题所阻止的其他问题的 ID;
  • CString GetDistribution() - 返回通知电子邮件的 CC 地址列表;
  • void SetDistribution( CString lpszDistribution ) - 设置通知电子邮件的 CC 地址列表;
  • CTime GetModified() - 返回上次修改此问题的时间;
  • void SetModified( CTime timeModified ) - 设置上次修改此问题的时间;
  • void CopyDataFrom( CIssueDataItem* pIssueData ) - 复制同类型另一个对象的数据。

CIssueDataList 类是 CIssueDataItem 对象的数组,它具有以下成员函数:

  • int GetSize() - 返回当前可用的问题数量;
  • CIssueDataItem * GetItemAt( int nItemIndex ) - 返回数组中 nItemIndex 位置的问题;
  • void SetItemAt( int nItemIndex, CIssueDataItem * pIssueData ) - 将新问题设置到数组的 nItemIndex 位置;
  • void RemoveAllItems() - 删除数组中存储的所有问题;
  • BOOL LoadAllItems( BOOL bRefreshUsers, BOOL bLoadAllFiles, BOOL bLoadAllNotes, BOOL bFindRelations ) - 从数据库加载所有问题;
  • BOOL InsertItem( CIssueDataItem * pIssueData ) - 将一个新问题插入数组,也插入数据库;
  • BOOL UpdateItem( CIssueDataItem * pIssueData ) - 更新数组中问题的数​​据,也更新数据库;
  • BOOL DeleteItem( CIssueDataItem * pIssueData ) - 从数组中删除问题,也从数据库中删除;
  • CIssueNoteItem * SearchItem( UINT uintID, BOOL bUseDatabase ) - 根据问题的 ID 在数组/数据库中搜索问题;
  • BOOL SearchEngine( CIssueDataItem * pIssueData, BOOL bRefreshUsers, BOOL bLoadAllFiles, BOOL bLoadAllNotes, BOOL bFindRelations ) - 搜索所有与 pIssueData 中指定的字段匹配的问题;
  • BOOL FindRelation( UINT uintID, CString &lpszBlocks ) - 搜索此问题所阻止的所有问题的 ID。

CDatabaseConnector 类是我们通过 ODBC 驱动程序访问数据库的基本资源,它具有以下成员函数:

  • void AllocConnectorHandle() - 为数据库连接分配新的句柄;
  • void FreeConnectorHandle() - 释放之前为数据库连接分配的句柄;
  • BOOL DriverConnect( LPCTSTR lpszInputConnection ) - 创建到数据库的新 ODBC 连接;
  • BOOL OpenConnection() - 打开到数据库的新 ODBC 连接;
  • void CloseConnection() - 关闭当前到数据库的 ODBC 连接;
  • BOOL ExecuteStatement( LPCTSTR lpszStatement ) - 执行 SQL 语句(例如 INSERTUPDATEDELETE);
  • void SetLoginTimeout( const long nLoginTimeout ) - 设置建立数据库连接的超时时间;
  • long GetLoginTimeout() - 返回建立数据库连接的超时时间;
  • BOOL IsDatabaseOpened() - 如果已建立数据库连接,则返回 TRUE,否则返回 FALSE
  • long GetRowsAffected() - 返回上次执行的 SQL 查询所影响的行数;
  • DATABASE_TYPE GetDatabaseType() - 返回当前连接的数据库类型(例如 SQL SERVER、ORACLE、MYSQL 等);
  • void SetDatabaseType( DATABASE_TYPE uintDatabaseType ) - 设置新连接的数据库类型。

CDatabaseRecordset 类利用 CDatabaseConnector 提供的连接,它具有以下成员函数:

  • void AllocRecordsetHandle() - 为 SQL 语句分配新的句柄;
  • void FreeRecordsetHandle() - 释放之前为 SQL 语句分配的句柄;
  • BOOL OpenRecordset( LPCTSTR lpszStatement ) - 执行 SQL 查询并获取数据行集;
  • void CloseRecordset() - 关闭由上一个 SQL 查询创建的数据行集;
  • void GetFieldValue( WORD nFieldIndex, LONG nFieldType, DWORD nFieldSize, void* pDataBuffer ) - 从 nFieldIndex 列获取指定类型 nFieldType 的值,并将其存储到大小为 nFieldSizepDataBuffer 中;
  • void GetColumnAttr( WORD nColumnIndex, LPTSTR lpszColumnName, WORD nBufferLength, WORD &nColumnType, ULONG &nColumnSize, WORD &nColumnScale, BOOL &bNullable ) - 获取当前数据行集中某一列的属性;
  • void SeekFirstRecord() - 将光标移动到当前数据行集的第一条记录;
  • void SeekLastRecord() - 将光标移动到当前数据行集的最后一条记录;
  • void SeekPrevRecord() - 将光标移动到当前数据行集的前一条记录;
  • void SeekNextRecord() - 将光标移动到当前数据行集的下一条记录;
  • long GetNumResultRows() - 返回上次执行的 SQL 查询所影响的行数;
  • long GetNumResultCols() - 返回当前数据行集中的列数;
  • void SetQueryTimeout( const long nQueryTimeout ) - 设置 SQL 查询的超时时间;
  • long GetQueryTimeout() - 返回为 SQL 查询选择的超时时间;
  • BOOL IsRecordsetOpened() - 如果数据行集可用,则返回 TRUE,否则返回 FALSE
  • BOOL IsRecordsetBOF() - 如果光标已到达数据行集的开头,则返回 TRUE,否则返回 FALSE
  • BOOL IsRecordsetEOF() - 如果光标已到达数据行集的末尾,则返回 TRUE,否则返回 FALSE

关注点

众所周知,Windows 提供了对注册表数据库的便捷访问。因此,“问题跟踪器”应用程序很好地利用了它来存储数据库访问设置。但我无法将访问密码以纯文本格式存储。所以我查看了 Microsoft Cryptography Library,并决定为此目的实现两个辅助函数:GetRegistryPasswordSetRegistryPassword

/// Returns a decrypted password read from Windows Registry, using Crypto API calls
BOOL GetRegistryPassword( LPCTSTR lpszCryptoKey, LPCTSTR lpszSection, LPCTSTR lpszEntry, LPTSTR lpszValue, LPCTSTR lpszDefault )
{
	if ( !lpszSection || !lpszEntry || !lpszValue )
		return FALSE;

	if ( !USE_CRYPTO_METHODS )
	{
		return ( _tcscpy( lpszValue, AfxGetApp()->GetProfileString( lpszSection, lpszEntry, lpszDefault ) ) != NULL);
	}

	if ( !lpszCryptoKey )
		lpszCryptoKey = AfxGetAppName();

	LPBYTE lpcbPassword = (LPBYTE) lpszCryptoKey;
	const DWORD dwPasswordLen = (DWORD) ( sizeof(TCHAR) * _tcslen( lpszCryptoKey ) );

	BYTE lpcbDataValue[PASSWORD_MAXLENGTH];
	DWORD dwHowManyBytes = 0;
	LPBYTE lpcbTempBuffer = NULL;

	BOOL bDecryptionDone = FALSE;
	HCRYPTPROV hCryptoProvider = NULL;
	HCRYPTHASH hCryptoHash = NULL;
	HCRYPTKEY hCryptoKey = NULL;

	if ( AfxGetApp()->GetProfileBinary( lpszSection, lpszEntry, (LPBYTE *)&lpcbTempBuffer, (UINT *)&dwHowManyBytes ) )
	{
		if ( dwHowManyBytes != 0)
		{
			ZeroMemory( lpcbDataValue, sizeof( lpcbDataValue ) );
			CopyMemory( lpcbDataValue, lpcbTempBuffer, dwHowManyBytes );

			if ( CryptAcquireContext( &hCryptoProvider, NULL, NULL, PROV_RSA_FULL, 0 ) )
			{
				if ( CryptCreateHash( hCryptoProvider, CALG_MD5, NULL, 0, &hCryptoHash ) )
				{
					if ( CryptHashData( hCryptoHash, lpcbPassword, dwPasswordLen, 0 ) )
					{
						if ( CryptDeriveKey( hCryptoProvider, CALG_RC4, hCryptoHash, CRYPT_EXPORTABLE, &hCryptoKey ) )
						{
							if ( CryptDecrypt( hCryptoKey, NULL, TRUE, 0, lpcbDataValue, &dwHowManyBytes ) )
							{
								bDecryptionDone = TRUE;
								_tcscpy( lpszValue, (LPTSTR) lpcbDataValue );
							}
							else
							{
								DisplayLastError( _T("CryptDecrypt: ") );
							}
							VERIFY( CryptDestroyKey( hCryptoKey ) );
						}
						else
						{
							DisplayLastError( _T("CryptDeriveKey: ") );
						}
					}
					else
					{
						DisplayLastError( _T("CryptHashData: ") );
					}
					VERIFY( CryptDestroyHash( hCryptoHash ) );
				}
				else
				{
					DisplayLastError( _T("CryptCreateHash: ") );
				}
				VERIFY( CryptReleaseContext( hCryptoProvider, 0 ) );
			}
			else
			{
				DisplayLastError( _T("CryptAcquireContext: ") );
			}
		}
	}
	else
	{
		if ( !dwHowManyBytes )
		{
			_tcscpy( lpszValue, lpszDefault );
			bDecryptionDone = TRUE;
		}
	}

	if ( lpcbTempBuffer != NULL )
		delete lpcbTempBuffer;

	return bDecryptionDone;
}

/// Writes an encrypted password to Windows Registry, using Crypto API calls
BOOL SetRegistryPassword( LPCTSTR lpszCryptoKey, LPCTSTR lpszSection, LPCTSTR lpszEntry, LPTSTR lpszValue )
{
	if ( !lpszSection || !lpszEntry || !lpszValue )
		return FALSE;

	if ( !USE_CRYPTO_METHODS )
	{
		return AfxGetApp()->WriteProfileString( lpszSection, lpszEntry, lpszValue );
	}

	if ( !lpszCryptoKey )
		lpszCryptoKey = AfxGetAppName();

	LPBYTE lpcbPassword = (LPBYTE) lpszCryptoKey;
	const DWORD dwPasswordLen = (DWORD)( sizeof( TCHAR ) * _tcslen( lpszCryptoKey ) );

	BYTE lpcbDataValue[PASSWORD_MAXLENGTH];
	const DWORD dwDataValueLen = PASSWORD_MAXLENGTH;
	DWORD dwHowManyBytes = dwDataValueLen;

	BOOL bEncryptionDone = FALSE;
	HCRYPTPROV hCryptoProvider = NULL;
	HCRYPTHASH hCryptoHash = NULL;
	HCRYPTKEY hCryptoKey = NULL;

	ZeroMemory( lpcbDataValue, sizeof( lpcbDataValue ) );
	CopyMemory( lpcbDataValue, lpszValue, dwDataValueLen );

	if ( CryptAcquireContext( &hCryptoProvider, NULL, NULL, PROV_RSA_FULL, 0 ) )
	{
		if ( CryptCreateHash( hCryptoProvider, CALG_MD5, NULL, 0, &hCryptoHash ) )
		{
			if ( CryptHashData( hCryptoHash, lpcbPassword, dwPasswordLen, 0 ) )
			{
				if ( CryptDeriveKey( hCryptoProvider, CALG_RC4, hCryptoHash, CRYPT_EXPORTABLE, &hCryptoKey ) )
				{
					if ( CryptEncrypt( hCryptoKey, NULL, TRUE, 0, lpcbDataValue, &dwHowManyBytes, dwDataValueLen ) )
					{
						bEncryptionDone = AfxGetApp()->WriteProfileBinary( lpszSection, lpszEntry, lpcbDataValue, (UINT)dwHowManyBytes );
					}
					else
					{
						DisplayLastError( _T("CryptEncrypt: ") );
					}
					VERIFY( CryptDestroyKey( hCryptoKey ) );
				}
				else
				{
					DisplayLastError( _T("CryptDeriveKey: ") );
				}
			}
			else
			{
				DisplayLastError( _T("CryptHashData: ") );
			}
			VERIFY( CryptDestroyHash( hCryptoHash ) );
		}
		else
		{
			DisplayLastError( _T("CryptCreateHash: ") );
		}
		VERIFY( CryptReleaseContext( hCryptoProvider, 0 ) );
	}
	else
	{
		DisplayLastError( _T("CryptAcquireContext: ") );
	}

	return bEncryptionDone;
}

其他敏感数据(仅存储在数据库中)使用 MySQL 的 AES_ENCRYPTAES_DECRYPT 函数进行加密。您可以在此处找到有关此主题的更多信息。

如果您想在 MS SQL Server 或 Oracle 数据库上运行此应用程序,则需要修改 IssueTrackerExt.cpp 文件中的 SQL 语句。

最后的寄语

“问题跟踪器”应用程序使用了许多在 The Code Project 上发布的组件。非常感谢:

为了重新编译此 Visual C++ .NET 2005 项目,您应该考虑从 Boost 官方页面下载 boost::regex 库,因为我正在使用它来读取电子邮件地址和问题的依赖项列表。

PJ Naughter 的 CPJNSMTPConnection 类还需要 OpenSSL Project。一个不错的选择是从 Shining Light Productions 页面下载 OpenSSL Project 的 Windows 二进制文件。

虽然此工具最初仅供我个人使用,但现在它是一个“社区”项目,如果您觉得它有用并希望提出增强功能或 bug 修复的建议,请在下方发帖。

历史

  • 版本 1.00(2014 年 7 月 13 日)- 初始发布。
© . All rights reserved.