2024年7月

在iOS开发中,保障应用的网络安全是一个非常重要的环节。以下是一些常见的网络安全措施及对应的示例代码:

Swift版

1. 使用HTTPS

确保所有的网络请求使用HTTPS协议,以加密数据传输,防止中间人攻击。

示例代码:

在Info.plist中配置App Transport Security (ATS):

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>

2. SSL Pinning

通过SSL Pinning可以确保应用程序只信任指定的服务器证书,防止被劫持到伪造的服务器。

示例代码:

import Foundation

class URLSessionPinningDelegate: NSObject, URLSessionDelegate {
  func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if let serverTrust = challenge.protectionSpace.serverTrust,
           SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
           let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {

            let localCertificateData = try? Data(contentsOf: Bundle.main.url(forResource: "your_cert", withExtension: "cer")!)
            let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data

            if localCertificateData == serverCertificateData {
                let credential = URLCredential(trust: serverTrust)
                completionHandler(.useCredential, credential)
                return
            }
        }
        completionHandler(.cancelAuthenticationChallenge, nil)
  }
}

// Usage
let url = URL(string: "https://yoursecurewebsite.com")!
let session = URLSession(configuration: .default, delegate: URLSessionPinningDelegate(), delegateQueue: nil)
let task = session.dataTask(with: url) { data, response, error in
    // Handle response
}
task.resume()

3. 防止SQL注入

在处理用户输入时,使用参数化查询来防止SQL注入攻击。

示例代码:

import SQLite3

func queryDatabase(userInput: String) {
    var db: OpaquePointer?
    // Open database (assuming dbPath is the path to your database)
    sqlite3_open(dbPath, &db)

    var queryStatement: OpaquePointer?
    let query = "SELECT * FROM users WHERE username = ?"
    
    if sqlite3_prepare_v2(db, query, -1, &queryStatement, nil) == SQLITE_OK {
        sqlite3_bind_text(queryStatement, 1, userInput, -1, nil)
        
        while sqlite3_step(queryStatement) == SQLITE_ROW {
            // Process results
        }
    }
    
    sqlite3_finalize(queryStatement)
    sqlite3_close(db)
}

4. Data Encryption

在存储敏感数据时,使用iOS的加密库来加密数据,比如使用
Keychain

示例代码:

import Security

func saveToKeychain(key: String, data: Data) -> OSStatus {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data
    ]

    SecItemDelete(query as CFDictionary) // Delete any existing item
    return SecItemAdd(query as CFDictionary, nil) // Add new item
}

func loadFromKeychain(key: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecReturnData as String: kCFBooleanTrue!,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]

    var dataTypeRef: AnyObject?
    let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

    if status == noErr {
        return dataTypeRef as? Data
    } else {
        return nil
    }
}

5. 输入验证与清理

对用户输入进行验证和清理,防止XSS(跨站脚本攻击)和其他注入攻击。

示例代码:

func sanitize(userInput: String) -> String {
    // Remove any script tags or other potentially dangerous content
    return userInput.replacingOccurrences(of: "<script>", with: "", options: .caseInsensitive)
                    .replacingOccurrences(of: "</script>", with: "", options: .caseInsensitive)
}

// Usage
let userInput = "<script>alert('xss')</script>"
let sanitizedInput = sanitize(userInput: userInput)
print(sanitizedInput) // Outputs: alert('xss')

OC版

1. 使用HTTPS

确保所有的网络请求都使用HTTPS协议,以加密数据传输,防止中间人攻击。

示例代码:


Info.plist
中配置App Transport Security (ATS):

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>

2. SSL Pinning

通过SSL Pinning可以确保应用程序只信任指定的服务器证书,防止被劫持到伪造的服务器。

示例代码:

#import <Foundation/Foundation.h>

@interface URLSessionPinningDelegate : NSObject <NSURLSessionDelegate>

@end

@implementation URLSessionPinningDelegate

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
        SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
        
        NSString *certPath = [[NSBundle mainBundle] pathForResource:@"your_cert" ofType:@"cer"];
        NSData *localCertData = [NSData dataWithContentsOfFile:certPath];
        NSData *serverCertData = (__bridge NSData *)(SecCertificateCopyData(serverCertificate));
        
        if ([localCertData isEqualToData:serverCertData]) {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            return;
        }
    }
    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}

@end

// Usage
NSURL *url = [NSURL URLWithString:@"https://yoursecurewebsite.com"];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
URLSessionPinningDelegate *pinningDelegate = [[URLSessionPinningDelegate alloc] init];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:pinningDelegate delegateQueue:nil];

NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error == nil) {
        // Handle response
    }
}];
[task resume];

3. 防止SQL注入

在处理用户输入时,使用参数化查询来防止SQL注入攻击。

示例代码:

#import <sqlite3.h>

- (void)queryDatabase:(NSString *)userInput {
    sqlite3 *db;
    // Open database (assuming dbPath is the path to your database)
    if (sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK) {
        sqlite3_stmt *statement;
        const char *query = "SELECT * FROM users WHERE username = ?";
        
        if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK) {
            sqlite3_bind_text(statement, 1, [userInput UTF8String], -1, SQLITE_TRANSIENT);
            
            while (sqlite3_step(statement) == SQLITE_ROW) {
                // Process results
            }
        }
        
        sqlite3_finalize(statement);
        sqlite3_close(db);
    }
}

4. Data Encryption

在存储敏感数据时,使用iOS的加密库来加密数据,比如使用
Keychain

示例代码:

#import <Security/Security.h>

- (OSStatus)saveToKeychainWithKey:(NSString *)key data:(NSData *)data {
    NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                            (__bridge id)kSecAttrAccount: key,
                            (__bridge id)kSecValueData: data};
    
    SecItemDelete((__bridge CFDictionaryRef)query); // Delete any existing item
    return SecItemAdd((__bridge CFDictionaryRef)query, NULL); // Add new item
}

- (NSData *)loadFromKeychainWithKey:(NSString *)key {
    NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                            (__bridge id)kSecAttrAccount: key,
                            (__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
                            (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne};
    
    CFTypeRef dataTypeRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
    
    if (status == noErr) {
        return (__bridge_transfer NSData *)dataTypeRef;
    } else {
        return nil;
    }
}

5. 输入验证与清理

对用户输入进行验证和清理,防止XSS(跨站脚本攻击)和其他注入攻击。

示例代码:

- (NSString *)sanitize:(NSString *)userInput {
    // Remove any script tags or other potentially dangerous content
    NSString *sanitizedInput = [userInput stringByReplacingOccurrencesOfString:@"<script>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, userInput.length)];
    sanitizedInput = [sanitizedInput stringByReplacingOccurrencesOfString:@"</script>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, sanitizedInput.length)];
    return sanitizedInput;
}

// Usage
NSString *userInput = @"<script>alert('xss')</script>";
NSString *sanitizedInput = [self sanitize:userInput];
NSLog(@"%@", sanitizedInput); // Outputs: alert('xss')

通过这些措施,你可以显著提升iOS应用的网络安全性。根据项目需求,灵活运用这些技术以确保用户数据的安全。

前言

作者这一次也是差一点一次过,因为没有经验的原因,或者说题目对问题描述不太对,如果说是求黑客反连的ip的话我或许就知道要执行一下留下来的那个 .elf 可疑文件。

简介

账号:root 密码:linuxruqin
ssh root@IP
1.web目录存在木马,请找到木马的密码提交
2.服务器疑似存在不死马,请找到不死马的密码提交
3.不死马是通过哪个文件生成的,请提交文件名
4.黑客留下了木马文件,请找出黑客的服务器ip提交
5.黑客留下了木马文件,请找出黑客服务器开启的监端口提交

应急开始

准备工作

  • 题目说明了web目录下存在木马,为了节省时间,连上服务器后直接cd
    /var/www/html
    (不是这个的话再另外找目录,一般是这个目录。)
  • 进入目录后直接导出来,准备webshell查杀工具
    我这里用D盾和河马扫一遍
    在这里插入图片描述
    在这里插入图片描述

这里使用河马发现他每次误报都好多,看来不更新后已经快跟不上了。

同时找到了几个webshell文件分别是:

  • 1.php
  • .shell.php
  • index.php
  • 注意:
    'shell(1).elf'
    这个是反连黑客服务器的程序文件。
    这个文件是elf可执行文件,名字已经很明显了,但是我经验比较少且比较犟,就一直看log日志去了,没有想到这个是反连的程序文件,但是我主要还是题目问题描述有问题吧,要是改成反连过去的黑客ip我肯定优先执行该文件。


步骤 1

1.web目录存在木马,请找到木马的密码提交

  • webshell查杀工具扫描出来后,直接看最明显的一句话木马就是1.php
    在这里插入图片描述
  • flag为:
    flag{1}

步骤 2

2.服务器疑似存在不死马,请找到不死马的密码提交

  • 不死马(杀不死的马)
    其实就是通过一个脚本不停的去检测另外一个木马是否存在,不存在的话就创建出来,然后该检测脚本一般来说会定期执行去检测另外的;不死webshell木马是否存在。
    题目使用除了1.php后,可以发现是index.php不停的去检测和创建不死马,创建.shell.php这个不死webshell木马,创建.符号开头也很明确了,就是为了创建隐藏webshell连接木马。
  • 不死马连接密码
    在这里插入图片描述
    5d41402abc4b2a76b9719d911017c592
    == md5(
    hello
    )
    在这里插入图片描述
  • flag为:
    flag{hello}

步骤 3

3.不死马是通过哪个文件生成的,请提交文件名

  • 在步骤2中,我们已经分析出来index.php是创建不死马的,所以flag就直接出来了。
  • flag为:
    flag{index.php}

步骤 4

4.黑客留下了木马文件,请找出黑客的服务器ip提交

  • 这里确实是一个坑,题目要是改成:请找出
    反连
    黑客的服务器ip,我必定先去运行一下
    /var/www/html
    目录下的
    'shell(1).elf'
    文件,因为很明显了。
    直接执行的话执行不了,因为没有x执行权限,加权限即可:
    root@ip-10-0-10-1:/var/www/html# chmod +x shell\(1\).elf
    root@ip-10-0-10-1:/var/www/html# ./shell\(1\).elf &
    
  • netstat -alntup
    #查看一下连接情况(看连接程序文件名字为.shell(1).elf的即可)
    在这里插入图片描述
  • flag为:
    flag{10.11.55.21}

步骤5

5.黑客留下了木马文件,请找出黑客服务器开启的监端口提交

  • 步骤4了解后,那么端口号也知道了
  • flag为:
    flag{3333}

总结


成果:
flag{1}
flag{hello}
flag{index.php}
flag{10.11.55.21}
flag{3333}


其实将样本备份出来后,分析完成后,应该要删除掉服务器上面所有webshell文件和后门,并且进行一轮入侵排查。
做完这题的感受就是,在做应急之前的准备工作很重要,虽然这次依旧是10金币的时间做完,但是已经相比之前快了很多,能够逐渐熟悉整个应急流程了。其实很学到了不死马这个词语,说实话作者真的是菜鸟一个,好多术语名词都没有真正去了解和熟悉认识,通过做题来弥补也挺好。
(注:本题目在第一章中属于中等难度,相比另外两个题目难度大的,所以我是按照难度来做的先后顺序,所以我才觉得熟练了)

拯救SQL Server数据库事务日志文件损坏的终极大招

在数据库的日常管理中,我们不可避免的会遇到服务器突然断电(没有进行电源冗余),服务器故障或者 SQL Server 服务突然停掉,

头大的是ldf事务日志文件也损毁了,SQL Server服务器起来之后,发现数据库处于"Recovery Pending" 状态。

更麻烦的是该数据库没有任何备份或者备份已经比较久远;

当然这些都不是最难的,最难的是连资深DBA使出
ATTACH_REBUILD_LOG

DBCC CEHECKDB

REPAIR_ALLOW_DATA_LOSS
选项等招数时候,

即使已经做好了最坏打算,做了丢失部分数据的准备,数据库还是无法上线。

本文将分享终极处理方法,帮助您成功恢复数据库。


测试环境: SQL Server 2022,Windows 2016

注意:奇技淫巧有风险,做任何操作之前注意先做备份!

模拟环境

首先,在数据库 testdb 中创建 testObject 表,并不停插入所有对象数据。

在窗口一我们运行插入数据脚本,使用多次 CROSS JOIN,以获得足够多的数据,插入数据脚本实际是一个模拟的大事务。

--窗口1

CREATE DATABASEtestdbGO
USEtestdbGO
SELECT * INTO testObject FROMsys.all_objects--前面脚本执行完成再执行下面的插入语句 INSERT INTOdbo.testObjectSELECT o.* FROMsys.all_objects oCROSS JOINsys.all_objects o1CROSS JOINsys.all_objects o2CROSS JOINsys.all_objects o3CROSS JOINsys.all_objects o4

返回信息如下

--Msg 109, Level 20, State 0, Line 0--A transport-level error has occurred when receiving results from the server. (provider: Shared Memory Provider, error: 0 - 管道已结束。)

在窗口二我们在关闭测试实例时,窗口一的插入事务仍然在运行。

这将使得数据库处于不一致状态,在数据库启动时,执行数据库恢复。

--窗口2--执行完下面语句之后,移走ldf文件,模拟ldf文件损坏


SHUTDOWN WITH NOWAIT

数据库停服后,将testdb数据库 的ldf事务日志文件改名或者移到其他路径,重新启动SQL Server 服务,可以看到,testdb 数据库处于“恢复挂起”状态。

因为在停服时候,还有未提交的插入事务保存在ldf事务日志文件,需要在数据库启动时候把事务日志捞出来做crash recovery。

数据库启动之前,已经把ldf事务日志文件移动到别的地方

此时,我们已经有一个孤立的,不一致的数据库文件。

现在我们必须先离线数据库,把mdf文件
复制
到别的地方作为备份,然后删除数据库,为后续的附加ldf事务日志文件做准备

--窗口3

USEmasterGO     
ALTER DATABASE [testdb] SET OFFLINE;

把mdf文件
复制
到别的地方作为备份

--窗口4

USEmasterGO     
DROP  DATABASE [testdb] ;

传统方法

使用
ATTACH_REBUILD_LOG
来重建ldf事务日志文件

--窗口5

USEmasterGO    
CREATE DATABASE [testdb] ON(FILENAME='E:\DataBase\testdb.mdf')FORATTACH_REBUILD_LOGGO  

报错信息如下

--文件激活失败。物理文件名称'E:\DataBase\testdb_log.ldf'可能不正确。--无法重新生成日志,原因是数据库关闭时存在打开的事务/用户,该数据库没有检查点或者该数据库是只读的。如果事务日志文件被手动删除或者由于硬件或环境问题而丢失,则可能出现此错误。--Msg 1813, Level 16, State 2, Line 8--无法打开新数据库 'testdb'。CREATE DATABASE 中止。


到此为止,我们很可能只有去找备份文件还原了(如果有的话),否则可能就是一场灾难了。


新方法
接下来将介绍终极恢复数据库的方法,以帮助您度过劫难。

使用 CREATE DATABASE 语句中非官方文档记载(undocument)的命令,这个命令就是
ATTACH_FORCE_REBUILD_LOG

这个命令会强制重建ldf事务日志文件,即使数据库检测到ldf事务日志文件和mdf数据文件之间有不一致的情况。

--窗口6

USEmasterGO    
CREATE DATABASE [testdb] ON(FILENAME='E:\DataBase\testdb.mdf')FORATTACH_FORCE_REBUILD_LOGGO  

返回信息如下

--文件激活失败。物理文件名称'E:\DataBase\testdb_log.ldf'可能不正确。--新的日志文件 'E:\DataBase\testdb_log.ldf' 已创建。

数据库虽然恢复正常,但数据表依然无法访问

--窗口7

USE [testdb]
GO

SELECT TOP 10 *  FROM [dbo].[testObject]

SELECT COUNT(*)  FROM [dbo].[testObject]

报错信息如下

--Msg 824, Level 24, State 2, Line 18--SQL Server 检测到基于逻辑一致性的 I/O 错误: pageid 不正确(应为 1:69856,但实际为 0:0)。在文件“E:\DataBase\testdb.mdf”中的偏移 0x000000221c0000 处,在数据库 ID 9 中的页面 (1:69856) 的 读取 期间发生。SQL Server 错误日志或操作系统错误日志中的其他消息可能会提供更多详细信息。这是一个威胁数据库完整性的严重错误条件,必须立即更正。请执行完整的数据库一致性检查(DBCC CHECKDB)。此错误可以由许多因素导致;有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2252374。

使用最小数据丢失的方式,修复数据库

头两个命令将数据库分别置于紧急模式和单用户模式,这是我们执行
DBCC CHECKDB

REPAIR_ALLOW_DATA_LOSS
选项的前提。

最后一句命令是将数据库恢复多用户模式。

--窗口8
--使用最小数据丢失的方式,修复数据库

USE [master]
GO 
ALTER DATABASE [testdb] SETEMERGENCYGO  
ALTER DATABASE [testdb] SET SINGLE_USER WITHNO_WAITGO  
DBCC CHECKDB([testdb],REPAIR_ALLOW_DATA_LOSS) WITHALL_ERRORMSGS--dbcc checkdb执行完毕之后执行下面语句,让数据库可以重新访问
ALTER DATABASE [testdb] SET MULTI_USER WITH NO_WAIT

DBCC CHECKDB返回信息如下,很多信息这里做了省略

可以看到有5924 个一致性错误,修复了 5924 个一致性错误,也就是全部修复了

--testdb的 DBCC 结果。

--Msg 8909, Level 16, State 1, Line 19--表错误: 对象 ID 0,索引 ID -1,分区 ID 0,分配单元 ID 0 (类型为 Unknown),页 ID (1:69830) 在其页头中包含错误的页 ID。页头中的 PageId 为 (0:0)。--该错误已修复。--Msg 8909, Level 16, State 1, Line 19--表错误: 对象 ID 0,索引 ID -1,分区 ID 0,分配单元 ID 0 (类型为 Unknown),页 ID (1:69831) 在其页头中包含错误的页 ID。页头中的 PageId 为 (0:0)。--该错误已修复。--Msg 8909, Level 16, State 1, Line 19--data)释放。--修复: 页 (1:70420) 已从对象 ID 1541580530,索引 ID 0,分区 ID 72057594045857792,分配单元 ID 72057594052673536 (类型为 In-row data)释放。--修复: 页 (1:70421) 已从对象 ID 1541580530,索引 ID 0,分区 ID 72057594045857792,分配单元 ID 72057594052673536 (类型为 In-row data)释放
。。。

--对象 ID 1541580530,索引 ID 0,分区 ID 72057594045857792,分配单元 ID 72057594052673536 (类型为 In-row data): 无法处理页 (1:69866)。有关详细信息,请参阅其他错误消息。--该错误已修复。--Msg 8928, Level 16, State 1, Line 19--对象 ID 1541580530,索引 ID 0,分区 ID 72057594045857792,分配单元 ID 72057594052673536 (类型为 In-row data): 无法处理页 (1:69867)。有关详细信息,请参阅其他错误消息。--该错误已修复。

。。。

--sys.filetable_updates_2105058535的 DBCC 结果。--对象“sys.filetable_updates_2105058535”在 0 页中找到 0 行。--CHECKDB 在数据库 'testdb' 中发现 0 个分配错误和 5924 个一致性错误。--CHECKDB 在数据库 'testdb' 中修复了 0 个分配错误和 5924 个一致性错误。--DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。

数据库处于单用户模式

设置回多用户模式之后,尝试查询数据

--窗口9

--从数据行数来看,具体你是不知道丢失多少数据的,只能说能挽救多少是多少吧



USE [testdb]
GO


SELECT TOP 10 *  FROM [dbo].[testObject]

SELECT COUNT(*) AS'rowcount' FROM [dbo].[testObject]

数据是查询出来了,但是具体丢失多少数据,我们无法掌握

至少数据库最后一次checkpoint点之后的所有数据将会丢失。


总结

在传统的方法里面,还有一个方法就是 新建一个同名的空数据库作为傀儡数据库,然后替换傀儡数据库的数据文件

再对傀儡数据库执行
DBCC CEHECKDB

REPAIR_ALLOW_DATA_LOSS
选项,但是实际上也不能保证100%有效

这个方法网上已经有相关文章,这里就不展开叙述了。


前几天帮一个网友恢复数据库,由于这个网友的数据库没有任何备份,并且遇到ldf事务日志损坏的问题,

起初使用ATTACH_REBUILD_LOG来重建日志文件不成功。在外网刚好搜索到ATTACH_FORCE_REBUILD_LOG这个命令,

最后总算帮这个网友尽最大努力挽回了数据。


最后提醒一下,附加没有ldf事务日志文件的数据库,并重建日志文件,有以下方法,其中有些方法已经废弃

DBCC REBUILD_LOG:已经废弃
sp_attach_single_file_db:已经废弃
ATTACH_REBUILD_LOG:推荐使用
ATTACH_FORCE_REBUILD_LOG:慎用

参考文章

https://www.mssqltips.com/sqlservertip/3579/how-to-attach-a-sql-server-database-without-a-transaction-log-and-with-open-transactions/
https://blog.sqlauthority.com/2016/11/04/sql-server-unable-attach-database-file-activation-failure-log-cannot-rebuilt/
https://vladdba.com/2022/08/31/recovering-master-database-with-corrupted-transaction-log-and-no-backups/

本文版权归作者所有,未经作者同意不得转载。

2024年7月15日微软宣布,其开发执行团队将在下个月的开发者大会上聚焦于使用 .NET Aspire 的云原生开发,以及结合人工智能的“现代 SQL”在 Microsoft Fabric 中的应用。微软的 Visual Studio LIVE! 2024 大会不仅是一个会议,而是创新、学习和社区庆祝的盛会。大会将于 8 月 5 日至 9 日在微软总部雷德蒙德举行,公司高层执行人员将发表两场主题演讲。Scott Hunter 将介绍“
介绍 .NET Aspire – 为 .NET 构建的云原生开发
”,而另一场主题演讲将由 Asad Khan 和 Bob Ward 主持,题为“
使用 AI 和 Microsoft Fabric 构建现代 SQL 应用程序
”。

微软正在推广 .NET Aspire,这是一个针对云原生的堆栈,用于构建可观察的、生产就绪的、分布式的、云原生的 .NET 应用程序。此外,微软还将在大会上展示其对“现代 SQL”的看法,包括 SQL 产品和服务的最新进展,以及如何利用人工智能和 Microsoft Fabric 提高应用程序的效率。

.NET Aspire

Microsoft Fabric
是一个端到端分析/数据平台,包括数据移动、处理、引入、转换、实时事件路由和报表生成。Khan 和 Ward 最近对他们的演讲进行了预览,表示将展示如何使用数据安全且大规模地构建生成式 AI 应用程序。他们还将介绍如何使用 Microsoft Copilot 体验来帮助构建应用程序、管理数据库和开发 SQL 查询。

image

微软的 Harrer 还描述了参加大会的其他好处,包括网络机会、动手实验室、超过
70 个会议环节
,以及从行业专家那里学习的机会,包括微软的 Mads Kristensen、James Montemagno、Daniel Roth 等。

前言

上一篇文章写了如何使用RabbitMQ做个简单的发送邮件项目,然后评论也是比较多,也是准备去学习一下如何确保RabbitMQ的消息可靠性,但是由于时间原因,先来说说设计模式中的简单工厂模式吧!
在了解简单工厂模式之前,我们要知道C#是一款面向对象的高级程序语言。它有3大特性,封装、继承、多态。

简述

工厂模式(Factory Pattern)是一种常用的设计模式,属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂模式的核心是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。这样客户端可以无需指定具体产品的类,只需通过工厂类即可得到所需的产品对象。

工厂模式主要分为三种类型:简单工厂模式(Simple Factory Pattern)、工厂方法模式(Factory Method Pattern)和抽象工厂模式(Abstract Factory Pattern)。
本文主要讲解简单工厂模式(Simple Factory Pattern)。

案例带入

下面使用C#控制台程序去写一个简易的计算器,实现加减乘除。如果我没学过设计模式,我会这么写:

static void Main(string[] args)  
{  
    Console.WriteLine("请输入数字A:");  
    string A = Console.ReadLine();  
    Console.WriteLine("请选择运算符号:(+、-、*、/):");  
    string op = Console.ReadLine();  
    Console.WriteLine("请输入数字B:");  
    string B = Console.ReadLine();  
    string result = "";  
    switch (op)  
    {        
	    case "+":  
            result = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(B));  
            break;  
        case "-":  
            result = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(B));  
            break;  
        case "*":  
            result = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(B));  
            break;  
        case "/":  
            result = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(B));  
            break;  
        default:  
            Console.WriteLine("输入的运算符号有误!");  
            break;  
    }    
    Console.WriteLine("结果:" + result);  
}

上述代码乍一看没问题,实则隐藏了很多陷阱,比如:

  1. 变量命名不规范
  2. 除数为0怎么办
  3. 输入的不是数字怎么办
  4. ......

优化

我们用面向对象的思想进行优化,主要体现在:可维护、可复用、可扩展、灵活性几个方面。通过封装、继承、多态来降低程序的耦合度。

封装

我们可以将运算逻辑封装成一个方法去实现,让主方法减轻负担。封装后:
Operation

public class Operation
{
    public static double GetResult(double num1, double num2, string op)
    {
        double result = 0d;
        switch (op)
        {
            case "+":
                result = num1 + num2;
                break;
            case "-":
                result = num1 - num2;
                break;
            case "*":
                result = num1 * num2;
                break;
            case "/":
                result = num1 / num2;
                break;
        }
        return result;
    }
}

Main
方法

static void Main(string[] args)
{
	try
	{
		Console.WriteLine("请输入数字A:");
		string strNumA = Console.ReadLine();
		Console.WriteLine("请选择运算符号:(+、-、*、/):");
		string op = Console.ReadLine();
		Console.WriteLine("请输入数字B:");
		string strNumB = Console.ReadLine();
		string result = "";
		result = Convert.ToString(Operation.GetResult(Convert.ToDouble(strNumA), Convert.ToDouble(strNumB), op));
		Console.WriteLine("结果:" + result);
	}
	catch (Exception e)
	{
		Console.WriteLine("发生异常:" + e.Message);
	}
}

松耦合

当我们完成封装后开始思考一个问题,如果后面有新的需求,需要增加一个开根运行,应该如何去修改?如果是我,我会在switch里面加一个分支,但是这样耦合度太高。我明明只需要去开根,但是却要让加减乘除参与进来,所以我们应该将加减乘除运算分离出来。
优化耦合度:

public class Operation
{
    private double _num1;
    private double _num2;
    public double Num1 { get => _num1; set => _num1 = value; }
    public double Num2 { get => _num2; set => _num2 = value; }
    public virtual double GetResult()
    {
        return 0;
    }
}

//加法类
public class AddOperation : Operation
{
    public override double GetResult()
    {
        return Num1 + Num2;
    }
}
//减法类
public class SubtractOperation : Operation
{
    public override double GetResult()
    {
        return Num1 - Num2;
    }
}
//乘法类
public class MultiplyOperation : Operation
{
    public override double GetResult()
    {
        return Num1 * Num2;
    }
}
//除法类
public class DivideOperation : Operation
{
    public override double GetResult()
    {
        if (Num2 == 0)
            throw new DivideByZeroException("除数不能为0");
        return Num1 / Num2;
    }
}  

我创建了
Operation
基类,并定义了2个成员变量
_num1

_num2
,同时定义了一个
GetResult
虚方法。同时分别创建了加减乘除子类去重写
GetResult
方法来降级耦合度。

回归正题(简单工厂模式)

我们需要通过简单工厂模式,来让程序知道该实例化谁。需要来创建一个工厂类:

public class OperationFactory
{
    public static Operation CreateOperation(string operation)
    {
        switch (operation)
        {
            case "+":
                return new AddOperation();
            case "-":
                return new SubtractOperation();
            case "*":
                return new MultiplyOperation();
            case "/":
                return new DivideOperation();
            default:
                return null;
        }
    }
}

创建了工厂类有什么好处呢,好处就是,只需要输入运算符号,工厂就能自己实例化出合适的对象,通过多态,返回父类的方法实现了计算器的计算结果。
Main
方法
通过简单工厂模式,让我们在计算加减乘除的时候只需要去增加对应的子类就行了,下面的代码进行加法运行时,通过传入+号让工厂去帮我们实例化子类。

static void Main(string[] args)  
{  
    try  
    {  
        // 简单工厂模式  
        var oper = OperationFactory.CreateOperation("+");  
        oper.Num1 = 10;  
        oper.Num2 = 5;  
        Console.WriteLine(oper.GetResult());  
    }    
    catch (Exception e)  
    {        
    Console.WriteLine("发生异常:" + e.Message);  
    }
}

类图

讲完简单工厂模式后,简简单单复现一下类图:
image

小小知识点

  • 接口
    :强调“做什么”,即接口定义了对象应该做什么,而不关心它是如何做的。
  • 虚方法
    :强调“如何做”,即基类提供了一种实现方式,但允许派生类根据需要进行修改。

参考资料

  • 熟读并反复背诵大话设计模式-程杰出版