iOS:應用程序擴展開發之Today擴展(Today Extesnsion)

一、簡介

iOS應用程序擴展是蘋果在iOS8推出的一個新特性,可以將自定義的功能和內容擴展到應用程序之外,在之後又經過不斷地優化和更新,已經成為開發中不可或缺的功能之一。擴展也是一個Target項目,它運行在主機應用程序上,可以與主機應用程序實現資源共享,和宿主應用程序的Target項目是彼此獨立的。系統提供的擴展有很多,Toady擴展就是其中之一,也被成為應用程序插件,它的作用是將今日發生的簡單消息展示在系統的插件界面上。Toady擴展模板名稱為Today Extension。圖1是創建Today擴展,圖2是擴展显示在插件界面上(可以通過點擊Edit來添加或者移除擴展)。 

 

二、創建

按照上圖1的方式創建一個Today Extension的Target后,系統會默認幫我們生成一個TodayViewController控制器類、MainInterface.storyBoard故事板、plist序列化文件,文件結構圖如下:

上圖中紅色圈內和箭頭指向的配置就是系統通過MainInterface.storyBoard幫我們實現了一個基本的Toady插件UI布局,運行后可以直接显示在插件界面上。可是,有的時候開發者並不想使用系統的故事板來構建UI,系統支持自定義的,我們只需要修改plist配置即可。具體的配置是這樣的:

[1] 將NSExtensionMainStoryboard字段刪除;

[2] 添加NSExtensionPrincipalClass字段,修改value為控制器的類名。 

[3] 在TodayViewController中的ViewDidLoad中設置preferredContentSize屬性大小,用來調整widget界面UI的尺寸。

配置如下圖所示:

//設置尺寸
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); 

  

三、分析

TodayViewController類比較簡單,就是一個VC類,它實現了系統提供的一個擴展協議<NCWidgetProviding>,可以在協議方法中實現對擴展的更新和狀態監控。

協議如下,都是可選的,開發者根據需要進行重寫。

//協議
@protocol NCWidgetProviding <NSObject>

@optional

//當數據更新時調用的方法,系統會定期更新擴展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler;

//監聽显示模式(寬鬆型、緊奏型)和尺寸的改變,其中寬鬆和緊湊表示的是展開和摺疊狀態, iOS10開始才能使用
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize __API_AVAILABLE(ios(10.0));

//設置擴展UI邊距,注意:使用StoryBoard時,若要所見即所得,則這個方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不會再被調用,棄用了)
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets __API_DEPRECATED("This method will not be called on widgets linked against iOS versions 10.0 and later.", ios(8.0, 10.0));

@end
//擴展,都是iOS10開始才能使用
@interface NSExtensionContext (NCWidgetAdditions)

//設置widget摺疊或展開狀態
@property (nonatomic, assign) NCWidgetDisplayMode widgetLargestAvailableDisplayMode __API_AVAILABLE(ios(10.0));

//只讀,widget狀態
@property (nonatomic, assign, readonly) NCWidgetDisplayMode widgetActiveDisplayMode __API_AVAILABLE(ios(10.0));

//獲取widget不同狀態的尺寸
- (CGSize)widgetMaximumSizeForDisplayMode:(NCWidgetDisplayMode)displayMode __API_AVAILABLE(ios(10.0));

@end

 

四、交互

Today擴展是寄宿於主機應用程序上的, TodayViewController又是一個UIViewController類,系統支持Today擴展對UIViewController進行切換。也就是說,蘋果在考慮提供給開發者在對UIViewController中添加各種展示控件這種便利的同時,也相應的提供給開發者通過Today擴展的widget從主機應用程序激活並打開宿主應用程序的機會。不過這個操作必須通過設置並調起scheme來實現。步驟如下:

[1] 配置宿主應用程序的scheme;

[2] 使用擴展的openURL打開宿主應用程序。

交互如下:

//擴展通過scheme打開主宿主應用程序
[self.extensionContext openURL:[NSURL URLWithString:@"MainApp://"] completionHandler:nil];

 

五、數據

既然Today擴展能與宿主應用程序進行交互,那麼肯定就存在數據通信的問題了。擴展與宿主目錄應用程序位於不同的目錄結構中,默認情況下,擴展與宿主應用程序的數據並不共享,代碼也不能復用。例如在宿主目錄應用程序中可能有網絡請求、數據持久化存儲等結構框架,在擴展中不可以直接使用,擴展需要提供自己的網絡請求框架、數據持久化框架等。這些問題蘋果都提供了解決方法,可以通過創建靜態庫的方式實現代碼共享,通過APP Group和Scheme跳轉實現數據共享。這裏主要講一下數據共享。注意:擴展和宿主應用程序的素材文件也是互相獨立的,必須將擴展中的素材添加到擴展Target。

方式一:通過配置scheme跳轉來實現數據共享。可以將傳遞的數據配置到URL中,然後宿主應用程序通過AppDeleagte的代理方法application:openURL:options:獲取數據,不過這個數據傳遞只能是單方向的。

 //打開主應用程序
-(void)openMainApp {
    
//共享數據
    NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
    [self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil];
}
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    
    //從URL獲取共享數據,截取數據
    NSLog(@"---------url = %@---------",url);
    
    return YES;
}

方式二:給擴展的Target和宿主應用程序的Target項目都開啟APP Group,兩者配置相同的appgroupIndentifier標識,分別生成後綴名為.entitlements文件。然後對於小數據推薦使用偏好進行雙向傳遞共享數據,如圖所示。

//共享數據
//使用偏好設置
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
[defalut setObject:@"xiayuanquan" forKey:@"name"];
//從偏好設置獲取共享數據
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
NSString *name1 = [defalut objectForKey:@"name"];
NSLog(@"1------------name1=%@",name1);

方式三:配置跟方式二一樣,不過雙向傳遞共享數據使用文件目錄來實現。

//共享數據
//方式二:使用共享目錄
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
[data writeToURL:filePath atomically:YES];
//從共享目錄獲取共享數據
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSData dataWithContentsOfURL:filePath];
NSLog(@"2------------data=%@",data);

 

六、適配

從iOS10開始,蘋果提供了NCWidgetDisplayMode展示模式,通過設置該模式來支持對widget進行摺疊和展開。在這裏,preferredContentSize就用到了。這個是用來設置widget的尺寸的。蘋果對widget的尺寸有自己的標準,width為maxSize.width,height取值範圍[110, maxSize.height]。這個maxSize可以在擴展協議<NCWidgetProviding>的協議方法也即widgetActiveDisplayModeDidChange:withMaximumSize中獲取:,可以發現每一種機型maxSize不一樣。

// 6s模擬器下:
// NCWidgetDisplayModeCompact模式下:{359.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{359.000000, 528.000000}

// 8 plus模擬器下:
// NCWidgetDisplayModeCompact模式下:{304.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{304.000000, 616.000000}

摺疊狀態:widget的高為110,此時設置preferredContentSize無效; 

展開狀態:widget的高為開發者設置的preferredContentSize.height,但是如果preferredContentSize.height>maxSize.height,此時取值為maxSize.height。 

適配iOS10,默認支持展開,設置如下: 

//設置widget默認為可以展開,此時處於摺疊狀態
#ifdef __IPHONE_10_0 //適配iOS10
   self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif

 

七、範例

【去掉MainInterface.storyBoard,採用純代碼實現】

1、宿主應用程序AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    //存儲共享數據
    //方式二:使用偏好設置
    NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
    [defalut setObject:@"xiayuanquan" forKey:@"name"];
    
    //存儲共享數據
    //方式三:使用共享目錄
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
    NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
    [data writeToURL:filePath atomically:YES];
    
    return YES;
}

-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    
    //方式一:從URL獲取共享數據,例如參數
    NSLog(@"---------url = %@---------",url);
    
    return YES;
    
}

2、Widget擴展TodayViewController

//
//  TodayViewController.m
//  TodayExtension
//  Created by 夏遠全 on 2019/11/19.
//

#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>

@interface TodayViewController () <NCWidgetProviding>

@end

@implementation TodayViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self config];
    [self createUI];
    [self fecthData];
}

//配置
-(void)config {
    
    self.view.backgroundColor = [UIColor lightGrayColor]; //widget背景色為灰色
    self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); //widget尺寸大小, 寬度實際取maxSize,width,高度[110, maxSize.height]
    
    //設置widget默認為可以展開,此時處於摺疊狀態
    #ifdef __IPHONE_10_0 //適配iOS10
        self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
    #endif
    
}

//創建UI
-(void)createUI {
    
    CGFloat width = self.view.frame.size.width;
    CGFloat btnWidth = 100;
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake((width-btnWidth)/2, 0, btnWidth, 40)];
    button.backgroundColor = [UIColor greenColor];
    [button setTitle:@"OpenAPP" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [button addTarget:self action:@selector(openMainApp) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

//打開主應用程序
-(void)openMainApp {
    
    //傳遞共享數據
    //方式一:參數傳遞
    NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
    [self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil];
    
}

//獲取共享數據
-(void)fecthData {
    
    //方式二:從偏好設置獲取共享數據
    NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
    NSString *name1 = [defalut objectForKey:@"name"];
    NSLog(@"1------------name1=%@",name1);
    
    //方式三:從共享目錄獲取共享數據
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
    NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
    NSData *data = [NSData dataWithContentsOfURL:filePath];
    NSLog(@"2------------data=%@",data);
}

//當數據更新時調用的方法,系統會定期更新擴展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
    
    //獲取共享的數據,根據判斷回調對應的block
    //NCUpdateResultNewData,
    //NCUpdateResultNoData,
    //NCUpdateResultFailed
    
    completionHandler(NCUpdateResultNoData);
}


//監聽显示模式(寬鬆型、緊奏型)和尺寸的改變
//NCWidgetDisplayModeCompact :  摺疊
//NCWidgetDisplayModeExpanded : 展開
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
    
    //maxSize:
    //雖說是最大的Size,但蘋果還是把Widget的高度範圍限制在了[110 ~ maxSize]之間
    //如果設置高度小於110,那麼default = 110;
    //如果設置高度大於開發者設置的preferredContentSize.Heiget,那麼default = maxSize;
    //摺疊狀態下,蘋果將高度固定為110,這個時候設置preferredContentSize屬性無效。
    NSLog(@"width = %lf-------height = %lf",maxSize.width,maxSize.height);
    
    //可以更改狀態
    if (activeDisplayMode == NCWidgetDisplayModeExpanded) {
        self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300);
    }
    else{
        self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 100);
    }
}

//設置擴展UI邊距,注意:使用StoryBoard時,若要所見即所得,則這個方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不會再被調用)
//- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
//    return UIEdgeInsetsZero;
//}

@end

3、打印和gif

2019-11-20 16:22:31.074596+0800 TodayExtension[29668:1132736] 1------------name1=xiayuanquan
2019-11-20 16:22:31.234435+0800 TodayExtension[29668:1132736] 2------------data={length = 149, bytes = 0x62706c69 73743030 d4010203 04050607 ... 00000000 00000068 }
2019-11-20 16:22:31.234970+0800 TodayExtension[29668:1132736] maxSize.width = 359.000000-------maxSize.height = 110.000000 //摺疊
2019-11-20 16:22:38.117764+0800 TodayExtension[29668:1132736] maxSize.width = 359.000000-------maxSize.height = 528.000000 //展開

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

遲到的故障公告:錯誤的緩存數據引發新版博客後台發布后的故障

10月18日晚上 22:00 ,我們對處於灰度發布階段的新版博客後台(Angular 8.2.7 + .NET Core 3.0)進行了一次發布操作,在發布後由於清除緩存 web api 的一個 bug 造成在發布后通過新版博客後台修改的博文無法訪問(404錯誤);在發現問題后,我們回退至發布之前的版本,但是由於 appsettings.Production.json 配置文件的不一致造成回退後的版本出現 500 錯誤;在修復配置文件問題后,在 docker swarm 集群上部署時又遭遇奇怪的容器健康檢查失敗的問題,多次部署后才成功,直至 23:00 左右才恢復正常。

非常抱歉,這次故障給使用新版博客後台的園友帶來了很大的麻煩,請您諒解。

在這次發布中包含一個比較大但卻沒有引起我們足夠重視的變更,原先在博客後台代碼中進行的清除 memcached 緩存(修改博文時清除對應的緩存)的操作改為調用 web api ,在實現清除緩存 web api 時由於沒有足夠重視在沒有寫集成測試覆蓋的情況下就發布了,從而沒有及時發現其中埋藏的一個 bug ,這個 bug 是由下面的 C# 代碼引起的:

await _cacheService.RemoveAsync(CacheKeyManager.GetBlogPost(blogId.Value, postId.Value));
var post = await blogPostService.GetCachedPostById(blogId.Value, postId.Value);            
//...
if (post.DisplayOnHomePage)
{
    await ClearHomePostsList(blogId.Value);
}
//..

上面的代碼中在清除所修改博文的緩存后,又獲取該博文進一步清除與該博文相關聯的緩存,調用 GetCachedPostById 方法時又創建了緩存,但由於實現時漏寫了 DTO 映射配置代碼,造成緩存的 BlogPostDto 字段值不完整從而 PostId 的值為 0 。在我們的緩存機制中,對於不存在的博文,會 new 一個空的 PostId 為 0 的 BlogPostDto 放入緩存,所以 PostId 為 0 的緩存數據都當作不存在的博文直接響應 404 ,故障因此而引發。

針對這次故障,在修掉 bug 代碼的同時我們將採取以下改進措施:

1)對從緩存中獲取的數據進行校驗並自動修復,這樣即使出現錯誤的緩存數據,也可以減少對業務的影響。

else if (blogPost.PostId != postId)
{
    blogPost = await GetBlogPostById(blogId, postId);
    await _cacheService.UpdateAsync(cacheKey, 3600, blogPost);
}

2)加強 Code Review

3)提高集成測試的覆蓋率

4)解決生產環境配置管理的問題

5)改用 k8s 部署生產環境

最近的新版博客後台發布故障暴露了我們在團隊開發能力上的落後,我們正在努力改進與提升,希望大家能夠諒解我們暫時的 low 。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

go中的關鍵字-select

1. select的使用

  定義:在golang裡頭select的功能與epoll(nginx)/poll/select的功能類似,都是堅挺IO操作,當IO操作發生的時候,觸發相應的動作。

1.1 一些使用規範

  在Go的語言規範中,select中的case的執行順序是隨機的,當有多個case都可以運行,select會隨機公平地選出一個執行,其他的便不會執行:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     ch := make (chan int, 1)
 7 
 8     ch<-1
 9     select {
10     case <-ch:
11         fmt.Println("隨機一")
12     case <-ch:
13         fmt.Println("隨機二n")
14     }
15 }

  輸出內容為隨機一二里面的任意一個。

  case後面必須是channel操作,否則報錯;default子句總是可運行的,所以沒有default的select才會阻塞等待事件 ;沒有運行的case,那麼將會阻塞事件發生報錯(死鎖)。

1.2 select的應用場景

timeout 機制(超時判斷)
 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     timeout := make (chan bool, 1)
10     go func() {
11         time.Sleep(1*time.Second) // 休眠1s,如果超過1s還沒I操作則認為超時,通知select已經超時啦~
12         timeout <- true
13     }()
14     ch := make (chan int)
15     select {
16     case <- ch:
17     case <- timeout:
18         fmt.Println("超時啦!")
19     }
20 }

  也可以這麼寫:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     ch := make (chan int)
10     select {
11     case <-ch:
12     case <-time.After(time.Second * 1): // 利用time來實現,After代表多少時間后執行輸出東西
13         fmt.Println("超時啦!")
14     }
15 }

  判斷channel是否阻塞(或者說channel是否已經滿了)

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 func main() {
 8     ch := make (chan int, 1)  // 注意這裏給的容量是1
 9     ch <- 1
10     select {
11     case ch <- 2:
12     default:
13         fmt.Println("通道channel已經滿啦,塞不下東西了!")
14     }
15 }

  退出機制

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     i := 0
10     ch := make(chan string, 0)
11     defer func() {
12         close(ch)
13     }()
14 
15     go func() {
16         DONE: 
17         for {
18             time.Sleep(1*time.Second)
19             fmt.Println(time.Now().Unix())
20             i++
21 
22             select {
23             case m := <-ch:
24                 println(m)
25                 break DONE // 跳出 select 和 for 循環
26             default:
27             }
28         }
29     }()
30 
31     time.Sleep(time.Second * 4)
32     ch<-"stop"
33 }

2. select的實現

  select-case中的chan操作編譯成了if-else。如:

1  select {
2  case v = <-c:
3          ...foo
4  default:
5          ...bar
6  }

  會被編譯為:

1  if selectnbrecv(&v, c) {
2          ...foo
3  } else {
4          ...bar
5  }

  類似地

1  select {
2  case v, ok = <-c:
3      ... foo
4  default:
5      ... bar
6  }

  會被編譯為:

1  if c != nil && selectnbrecv2(&v, &ok, c) {
2      ... foo
3  } else {
4      ... bar
5  }

  selectnbrecv函數只是簡單地調用runtime.chanrecv函數,不過是設置了一個參數,告訴當runtime.chanrecv函數,當不能完成操作時不要阻塞,而是返回失敗。也就是說,所有的select操作其實都僅僅是被換成了if-else判斷,底層調用的不阻塞的通道操作函數。

  在Go的語言規範中,select中的case的執行順序是隨機的,那麼,如何實現隨機呢?

  select和case關鍵字使用了下面的結構體:

1 struct    Scase
2   {
3       SudoG    sg;            // must be first member (cast to Scase)
4       Hchan*    chan;        // chan
5       byte*    pc;            // return pc
6       uint16    kind;
7       uint16    so;            // vararg of selected bool
8       bool*    receivedp;    // pointer to received bool (recv2)
9   };
1  struct    Select
2      {
3      uint16    tcase;            // 總的scase[]數量
4      uint16    ncase;            // 當前填充了的scase[]數量
5      uint16*    pollorder;        // case的poll次序
6      Hchan**    lockorder;        // channel的鎖住的次序
7      Scase    scase[1];        // 每個case會在結構體里有一個Scase,順序是按出現的次序
8  };

  每個select都對應一個Select結構體。在Select數據結構中有個Scase數組,記錄下了每一個case,而Scase中包含了Hchan。然後pollorder數組將元素隨機排列,這樣就可以將Scase亂序了。

 3. select死鎖

  select不注意也會發生死鎖,分兩種情況:

  如果沒有數據需要發送,select中又存在接收通道數據的語句,那麼將發送死鎖

1 package main
2 func main() {  
3     ch := make(chan string)
4     select {
5     case <-ch:
6     }
7 }

  預防的話加default。

  空select,也會引起死鎖。

1 package main
2 
3 func main() {  
4     select {}
5 }

 4. select和switch的區別

select

select只能應用於channel的操作,既可以用於channel的數據接收,也可以用於channel的數據發送。如果select的多個分支都滿足條件,則會隨機的選取其中一個滿足條件的分支, 如規範中所述:

If multiple cases can proceed, a uniform pseudo-random choice is made to decide which single communication will execute.

`case`語句的表達式可以為一個變量或者兩個變量賦值。有default語句。

31 package main                                                                                                                                              32 import "time"
33 import "fmt"                                                                                                                                              
35 func main() {                                                                                                                                             36     c1 := make(chan string)
37     c2 := make(chan string)                                                                                                                               38     go func() {
39         time.Sleep(time.Second * 1)                                                                                                                       40         c1 <- "one"
41     }()                                                                                                                                                   42     go func() {
43         time.Sleep(time.Second * 2)                                                                                                                       44         c2 <- "two"
45     }()                                                                                                                                                   46     for i := 0; i < 2; i++ {
47         select {                                                                                                                                          48             case msg1 := <-c1:
49             fmt.Println("received", msg1)          
50 case msg2 := <-c2: 51 fmt.Println("received", msg2)
52 } 53 }

switch

  switch可以為各種類型進行分支操作, 設置可以為接口類型進行分支判斷(通過i.(type))。switch 分支是順序執行的,這和select不同。

 1 package main                  
 2 import "fmt"
 3 import "time"  
 4 
 5 func main() {                                                                                                                             
 6      i := 2
 7      fmt.Print("Write ", i, " as ")  
 8      switch i {
 9          case 1:
10          fmt.Println("one")
11          case 2:                                                                                                                                  
12          fmt.Println("two")
13          case 3:                                                                                                                      
14          fmt.Println("three")
15      }                                                                                                                                             
16      switch time.Now().Weekday() {
17          case time.Saturday, time.Sunday:
18          fmt.Println("It's the weekend")
19          default:                                                                                                                                      
20          fmt.Println("It's a weekday")
21      }                                                                                                                                                 
22      t := time.Now()
23      switch {                                                                                                                                         
24          case t.Hour() < 12:
25          fmt.Println("It's before noon")                                                                                                              
26          default:
27          fmt.Println("It's after noon")                                                                                                                  
28      }
29      whatAmI := func(i interface{}) {                                                                                                                   
30          switch t := i.(type) {
31              case bool:                                                                                                                              
32              fmt.Println("I'm a bool")
33              case int:                                                                                                                                 
34              fmt.Println("I'm an int")
35              default:                                                                                                                                 
36              fmt.Printf("Don't know type %T\n", t)
37          }
38      }
39      whatAmI(true)                                                                                                                                     
40      whatAmI(1)
41      whatAmI("hey")                                                                                                                                 
42  }

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

【Elasticsearch 7 探索之路】(三)倒排索引

上一篇,我們介紹了 ES 文檔的基本 CURE 和批量操作。我們都知道倒排索引是搜索引擎非常重要的一種數據結構,什麼是倒排索引,倒排索引的原理是什麼。

1 索引過程

在講解倒排索引前,我們先了解索引創建,下圖是 Elasticsearch 中數據索引過程的流程。

從上圖可以看到,文檔未在 ES 中進行索引,而是 由 Analyzer 組件對其執行一些操作並將其拆分為 token/term。然後將這些術語作為倒排索引存儲在磁盤中。假設我們有兩個名為 name 和 age 字段,當要將文檔索引到 ES 時,Analyzers 組件 以某些定界符(有默認定界符,例如空格,句號等)將它們分割開獲取 token,再對每個 token 應用特定的過濾器。經過分析的這些標記稱為 term。然後將這些 term 針對該字段)存儲在倒排列表中。

2 倒排索引

2.1 正排與倒排索引

一般在我們閱讀圖書,我們會根據目錄快速定位想要閱讀的章節,過了一段時間,你想要的回顧之前某一個知識點,你發現從目錄難以查找到對應的地方,這時你可能就會從索引頁從去查找對應內容索引,從而找到頁碼。

搜索引擎其實跟我們的使用圖書很相似,下面我來對圖書和搜索引擎進行一個簡單的類比,來看一下搜素引擎中正排和倒排索引。

  • 圖書
    • 正排索引-目錄頁
    • 倒排索引-索引頁
  • 搜索引擎
    • 正排索引-文檔 Id 到文檔內容和單詞的關聯
    • 倒排索引-單詞到文檔 Id 的關係

2.2 倒排索引的核心組成

舉個例子,假設我們有 3 個文檔:

Doc 1:breakthrough drug for schizophrenia

Doc 2:new schizophrenia drug 

Doc 3:new approach for treatment of schizophrenia

經過分析,文件中的術語如下

文檔 分詞結果
Doc 1 breakthrough,drug,for,schizophrenia
Doc 2 new,schizophrenia,drug
Doc 3 new,approach,for,treatment,of

倒排列表的元數據結構:

(DocID;TF;<POS>)

其中:

  • DocID:出現某單詞的文檔ID

  • TF(詞頻):單詞在該文檔中出現的次數

  • POS:單詞在文檔中的位置

則它們生成的倒排索引

單詞 逆向文檔頻率 倒排列表(DocID;TF; ))
breakthrough 1 (1;1;<1>)
drug 2 (1;1;<2>),(2;1;<3>)
for 2 (1;1;<3>),(3;1;<3>)
schizophrenia 2 (1;1;<4>),(2;1;<2>)
new 2 (2;1;<1>),(3;1;<1>)
approach 1 (3;1;<2>)
treatment 1 (3;1;<4>)
of 1 (3;1;<5>)
  • ES 倒排索引包含兩個部分

    • 單詞詞典 (Term Dictionary),索引最小單位,記錄所有文檔的單詞,記錄單詞到倒排列表的關聯關係
      • 單詞詞典一般都會非常多,通過 B+ 樹或 Hash 表方式以滿足高性能的插入與查詢
    • 倒排列表(Posting List)-由倒排索引項(Posting)組成
      • 文檔 ID
      • 詞頻 TF,該單詞在文檔中出現的次數,用於相關性評分
      • 位置(Position),單詞在文檔中分詞的位置。用於語句搜索(phrase query)
      • 偏移(Offset),記錄單詞的開始結束位置,實現高亮显示

ES 也可以指定對某些字段不做索引

  • 優點:節省存儲空間
  • 缺點:字段無法被搜索

3 總結

在之前文章說了 ES 的文檔是基於 JSON 格式,在我們創建索引的時候,對每一個文檔記錄對應索引相關的信息。在對倒排索引進行搜索時,查詢單詞是否在單詞字典,獲取單詞在倒排列表的指針,獲取有該單詞單詞的文檔 Id 列表,通過 ES 的倒排索引,我們輕易對全文進行快速搜素。

系列文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

二、netcore跨平台之 Linux部署nginx代理webapi

上一章,我們講了在linux上安裝netcore環境,以及讓netcore在linux上運行。

這一章我們開始講在linux上配置nginx,以及讓nginx反向代理我們的webapi。

什麼nginx???

Nginx(“engine x”)是一款是由俄羅斯的程序設計師Igor Sysoev所開發高性能的 Web和 反向代理 服務器,也是一個 IMAP/POP3/SMTP 代理服務器。

在高連接併發的情況下,Nginx是Apache服務器不錯的替代品,而且現在越來越多的公司都開始用nginx,學習nginx是程序員必不可少的一部分。

 

現實用xshell登錄到我們的linux系統中。

輸入如下命令

安裝編譯工具和對應的庫文件,輸入下面的命令,回車即可

yum -y install make zlib zlib-devel gcc-c++ libtool  openssl openssl-devel

如下圖示,就表示安裝好了。

 

 

接下來,我們正式安裝Nginx,我們這次選擇安裝1.6.2版本。

(1)下載nginx,輸入下面命令,回車即可。

wget http://nginx.org/download/nginx-1.6.2.tar.gz

nginx比較小,下載起來還是蠻快的,如下圖所示,表示下載完成。

 

 

 

 (2)解壓nginx壓縮包

tar zxvf nginx-1.6.2.tar.gz

(3)進入到nginx目錄中

cd nginx-1.6.2

(4)編譯和安裝,分別執行下面的命令

./configure --prefix=/usr/local/nginx
make

 

 

 (5)查看版本

/usr/local/nginx/sbin/nginx -v

看到下面這個表示成功了

(6)啟動nginx

 

/usr/local/nginx/sbin/nginx

然後在你的電腦上用瀏覽器訪問即可

注意這裏的默認監聽端口是80,所以輸入ip加上80端口即可

出現如下這種視圖就表示成功了。

 

 

 

 

接下來就是很關鍵的配置了,其實安裝還是很簡單的。

 進入到你的nginx的安裝目錄下

分別輸入如下命令,即可查看到您的配置信息

cd  /usr/local/nginx/conf
ll

 

編輯您的nginx.conf文件,我這裏用vim進行編輯

vim nginx.conf

出現如圖所示:

 我們主要看如下圖所示紅色標記的部分

 

 

listen:表示當前的代理服務器監聽的端口,默認的是監聽80端口。注意,如果配置了多個server,這個listen要配置不一樣。

server_name:表示監聽到之後需要轉到哪裡去,這時我們直接轉到本地,這時是直接到nginx文件夾內。

location:表示匹配的路徑,這時配置了/表示所有請求都被匹配到這裏

root:裏面配置了root這時表示當匹配這個請求的路徑時,將會在這個文件夾內尋找相應的文件,這裏對我們之後的靜態文件伺服很有用。

index:當沒有指定主頁時,默認會選擇這個指定的文件,它可以有多個,並按順序來加載,如果第一個不存在,則找第二個,依此類推。

然後我們把上面的location進行了修改

用鍵盤的上下左右鍵把光標移動到如下位置

 

然後輸入鍵盤的 i(鍵盤的字母i)進入到編輯模式(這裡是給不懂linux說的,懂得人請無視)

然後用鍵盤的退回 ←把這裏的都刪除

 

 然後在輸入  proxy_pass http://localhost:6666;(這裏接上一章已經配置好的netcore webapi)

 

 

 然後點擊鍵盤的ESC退出編輯狀態

然後輸入:wq退出並保存

:wq

 

 這樣就保存配置結束了,conf配置其實很複雜,網上有很多根據不同需求進行的配置

大家有興趣可以去看看,我這裏只說入門的部分,額,其實入門都算不上,能用就行,哈哈哈

最後執行下面兩個命令

這裡是檢查配置

/usr/local/nginx/sbin/nginx -t

 

 最後一個就是重啟運行nginx

/usr/local/nginx/sbin/nginx -s reload

 

 

哦,最後我們看看效果,額

這裏一定不要忘記啟動我的netcore程序了

輸入命令進入到我們的項目中

cd /root/netcore/

然後輸入啟動程序的命令

dotnet WebApiTest.dll

如下圖所示,就表示啟動成功了

 

 

 

 然後,我們用測試工具測試一下我們的接口

如圖所示,我們把以前的6666接口換成了80端口,這裏還是訪問成功了,這就表示我們的nginx代理成功了

 

 

 

 

 

到這裏就基本結束了,不過大家一定有個奇怪的地方每次啟動netcore,每次關了你的xshell后就不能訪問了,

這裏建議使用下面的命令,使之成為後台進程即可

nohup dotnet  WebApiTest.dll  &

over !!!!!!!!!!!!!!!!!!!!!!!!

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

Spring框架學習筆記(7)——Spring Boot 實現上傳和下載

最近忙着都沒時間寫博客了,做了個項目,實現了下載功能,沒用到上傳,寫這篇文章也是順便參考學習了如何實現上傳,上傳和下載做一篇筆記吧

下載

主要有下面的兩種方式:

  • 通過ResponseEntity 實現
  • 通過寫HttpServletResponse的OutputStream實現

我只測試了ResponseEntity<InputStreamResource>這種方法可行,另外一種方法請各位搜索資料。

我們在controller層中,讓某個方法返回ResponseEntity,之後,用戶打開這個url,就會直接開始下載文件

這裏,封裝了一個方法export,負責把File對象轉為ResponseEntity

public ResponseEntity<FileSystemResource> export(File file) {
    if (file == null) {
        return null;
    }
    HttpHeaders headers = new HttpHeaders();
    headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
    headers.add("Content-Disposition", "attachment; filename=" + System.currentTimeMillis() + ".xls");//以時間命名文件,防止出現文件存在的情況,根據實際情況修改,我這裡是返回一個xls文件
    headers.add("Pragma", "no-cache");
    headers.add("Expires", "0");
    headers.add("Last-Modified", new Date().toString());
    headers.add("ETag", String.valueOf(System.currentTimeMillis()));

    return ResponseEntity
            .ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.parseMediaType("application/octet-stream"))
            .body(new FileSystemResource(file));
}

Controller

@RequestMapping("download")
public ResponseEntity<FileSystemResource> downloadFile() {
    return export(new FIle());//這裏返回調用export的結果
}

上傳

1.配置

spring boot使用上傳功能,得先進行配置,spring boot配置方式有兩種,一種是資源文件properties配置,另外一種方式則是yml配置

properties配置:

## MULTIPART (MultipartProperties)
# 開啟 multipart 上傳功能
spring.servlet.multipart.enabled=true
# 文件寫入磁盤的閾值
spring.servlet.multipart.file-size-threshold=2KB
# 最大文件大小
spring.servlet.multipart.max-file-size=200MB
# 最大請求大小
spring.servlet.multipart.max-request-size=215MB

yml配置:

spring:
    servlet:
        multipart:
          enabled: true # 開啟 multipart 上傳功能
          max-file-size: 200MB # 最大文件大小
          max-request-size: 215MB # 最大文件請求大小
          file-size-threshold: 2KB # 文件寫入磁盤的閾值

2.編寫url請求

controller

@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return "上傳失敗,請選擇文件";
    }

    String fileName = file.getOriginalFilename();
    String filePath = "/Users/itinypocket/workspace/temp/";//文件上傳到服務器的路徑,根據實際情況修改
    File dest = new File(filePath + fileName);
    try {
        file.transferTo(dest);
        LOGGER.info("上傳成功");
        return "上傳成功";
    } catch (IOException e) {
        LOGGER.error(e.toString(), e);
    }
    return "上傳失敗!";
}

3.Web頁面上傳文件

注意,input標籤的name與url的請求參數名相同,上傳只能使用post請求
單個文件上傳:

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>

多個文件上傳:

input標籤加上multiple屬性,即可一次選擇多個文件

<form method="post"  action="/upload" enctype="multipart/form-data">
    <input type="file" multiple name="file"><br>
    <input type="submit" value="提交">
</form>

4.Android端上傳文件

使用okhttp上傳文件

RequestBody filebody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody body = new MultipartBody.Builder()
        .addFormDataPart("file", file.getName(), filebody)
        .build();
Request request = new Request.Builder()
        .url("http://192.168.1.106:8080/webapp/fileUploadPage")
        .post(body)
        .build();

Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.e(TAG, "請求失敗:" + e.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.e(TAG, "請求成功!");
    }
});

參考鏈接:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

遏制空污 新德里裝空污預警系統

摘錄自2018年10月2日中央社新德里報導

印度新德里空氣污染嚴重程度經常位居世界前幾名,新德里高層官員表示,2018年在進入空污嚴重的冬天前,空污預警系統即將啟用,預計在10月底或11月初安裝,可預測兩天後的空氣品質,以提前遏制空污。

空污系統安裝好後,將從印度中央污染控制局(Central Pollution Control Board,CPCB)、德里污染控制委員會(Delhi Pollution Control Committee)和空氣品質及天氣預測與研究系統機構(System of Air Quality & Weather Forecasting and Research,SAFAR)旗下的36個空氣品質監測站獲得數據,同時從美國國家航空暨太空總署(NASA)衛星獲取即時氣候資料,綜合研判後,對空氣品質進行預測。

協助這項預警系統設置的印度熱帶研究所普恩分所(Indian Institute of Tropical Meteorology-Pune)科學家谷德表示,系統安裝後的第一年,僅空污濃度指標PM2.5(細懸浮微粒)數據被輸入系統,未來幾年將把PM10、二氧化硫(SO2)、二氧化氮(NO2)濃度等數據都納入系統,使空污預測更加準確。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

老舊柴油車釀空污 德國將提因應計畫

摘錄自2018年10月2日中央社柏林報導

德國政府2日將針對車齡較老、污染較嚴重的柴油車的未來發展方向,向數以百萬計車主提出妥協方案,而汽車製造商可能因為廢氣排放造假醜聞引發危機,而面臨嚴苛責罰。

德國總理梅克爾(Angela Merkel)和主要部門首長商議至1日深夜後,她領導的保守派基民/基社聯盟(CDU/CSU)和中間偏左執政聯盟夥伴社會民主黨(SPD)公布了「潔淨空氣和確保我國都市機動性的概念」。這起柴油高峰會目的在於清除都市空汙,同時避免車主支出額外費用。

漢堡(Hamburg)和斯圖加特(Stuttgart)等主要城市的某些地區不歡迎老舊柴油車,法院也逐漸對其他地區祭出禁令。交通部長舒爾(Andreas Scheuer)在會議開始前告訴德新社(DPA),最重要的是,「我們希望避免進一步祭出駕駛禁令」。不過,汽車製造商可能面臨嚴峻懲罰,環境部長舒爾茨(Svenja Schulze)強調,是「汽車業把我們捲進麻煩中,應該為此付出代價」。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

多氯聯苯傳給下一代 半數的虎鯨將在50年後死亡

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

佛羅倫斯颶風橫掃美國,可再生能源設備受創如何?

摘錄自2018年10月2日科技新報報導

美國2018年9月遭佛羅倫斯颶風侵襲,災情慘重,許多人擔心可再生能源基礎建設是否能夠抵擋這樣的天災侵襲?美國各大電力公司正在全面盤點受損情況,目前看來,可再生能源在許多防備天災的安全技術下,大體上通過考驗。

事實上,過去幾次美國重要風災已經證明太陽能設備抵擋風災的能力,不論是德州的哈維風災或是夏威夷風災,若房屋本身結構沒有問題,太陽能板也都能挺過颶風的肆虐。

因此,極端氣候的颶風接連來襲,並未讓美國住宅太陽能安裝打退堂鼓,反倒是颶風後集中式大電網因為輸配電設備與線路損壞,使得美國人更重視分散式能源,颶風連續來襲使得加裝能源儲存系統的用戶比例大增。在加州,住宅太陽能安裝商彼得生狄恩(PetersenDean)近 6 個月的客戶選擇安裝能源儲存的比例高達一半。佛羅倫斯颶風過後,南北卡羅萊納州安裝商也接到許多用戶希望安裝能源儲存系統的要求。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?