基于 FC 的中国云服务厂商公告推送

基于 FC 的中国云服务厂商公告推送

越来越多的业务选择上云,但是有时候云服务厂商的服务调整会影响到我们自身的业务。目前我了解服务调整的渠道只有官方公告,所以专门写了个程序在进行监控,目前只考虑了阿里云、腾讯云、华为云。程序流程是借助 FC 来获取最新的公告,然后通过 Server酱 推送到微信。

本来二月中旬程序已经写好了,在实际的应用中发现很多公告都没有进行推送,排查到的是大厂他们的公告是有一个审核时间。之前程序没有存储功能,通过列表的时间或者 ID 来判断是不是最新公告就行不通了。

FC 具有一定的免费额度,按照 128MB 内存、10s 耗时、每分钟执行一次,一个月大概执行 43200 次,消耗的资源占免费额度的 18%。

OSS

创建存储桶。

这里的地域节点后面需要用到。

FC

创建一个函数。

函数创建完成后点击配置服务。

到配置服务页面后下滑到最底部,需要拥有 AliyunOSSFullAccess 权限。

回到我们刚刚创建的函数,代码压缩包在文章底部,下载后进行上传保存。

回到函数概览页,配置初始化入口和环境变量。

1
2
3
4
oss_bucket=     # OSS 桶名
oss_endpoint= # OSS 地域节点
providers= # 厂商列表,多个厂商用英文逗号隔开,具体值参考下表。
sckey= # Server 酱密钥
厂商
阿里云 aliyun
腾讯云 tencent
华为云 huawei

创建一个定时触发器,时间间隔为1分钟。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
<?php

# composer require aliyuncs/oss-sdk-php jaeger/querylist tightenco/collect "5.*"
require __DIR__ . '/vendor/autoload.php';

use OSS\OssClient;
use QL\QueryList;
use Tightenco\Collect\Support\Collection;

ini_set('date.timezone', 'Asia/Shanghai');
libxml_use_internal_errors(true);

// 获取存储桶名称
$bucket = getenv('oss_bucket');
// 配置文件名
$config_name = 'config.json';
// 配置全局存储
$config = [];
// 推送全局存储
$push = [];
// OSS 全局存储
$oss = null;

function initializer($context) {
global $config, $oss;

if(is_null($oss)) {
// 实例化 OSS
// $oss = new OssClient(
// 'yourAccessKeyId',
// 'yourAccessKeySecret',
// 'https://' . getenv('oss_endpoint')
// );
$oss = new OssClient(
$context['credentials']['accessKeyId'],
$context['credentials']['accessKeySecret'],
'https://' . getenv('oss_endpoint'),
false,
$context['credentials']['securityToken']
);
}

$config = get_config();
}

function handler($event, $context) {
global $config, $push;

// 复制一份配置数据,最后判断好判断是否需要更新 OSS 上面的配置文件。
$_config = $config;

// 清空推送
$push = [];

// 获取需要监控的厂商
$providers = explode(',', getenv('providers') ?? 'aliyun,tencent,huawei');

array_map(function($name) use(&$push) {
if(function_exists('get_' . $name)) {
call_user_func('get_' . $name);
}
}, $providers);

// 进行推送
foreach($push as $item) {
send(...$item);
}

if($config != $_config) {
save_config($config);
}

return 1;
}

/**
* 获取公告列表
*
* @param string $provider 厂商
* @return array
*/
function get_list($provider) {
global $config;

// 获取厂商公告列表数据
$data = call_user_func('get_' . $provider . '_list');

// 判断配置数据里面有没有该厂商,没有的话保存进去然后返回。
if(!isset($config[$provider])) {
$config[$provider] = $data;

return [];
}

// 对当前读取的公告数据和配置数据里面的进行比对拿到不存在的。
$list = collect(normalize_list($data))
->diffKeys(normalize_list($config[$provider]))
->all();

return $list ? array_reverse($list) : [];
}

/**
* 获取阿里云
*
* @return array
*/
function get_aliyun() {
$provider = 'aliyun';

$list = get_list($provider);

if(!$list) {
return [];
}

return save_push($provider, '阿里云', '#se-knowledge', $list);
}

/**
* 获取阿里云公告列表
*
* @return array
*/
function get_aliyun_list() {
return QueryList::get('https://help.aliyun.com/noticelist.html')
->rules([
'name' => ['a', 'text'],
'href' => ['a', 'href'],
])
->range('.help-notice-box ul > li')
->query()
->queryData(function($item) {
$item['href'] = 'https://help.aliyun.com' . $item['href'];
return $item;
});
}

/**
* 获取腾讯云
*
* @return array
*/
function get_tencent() {
$provider = 'tencent';

$list = get_list($provider);

if(!$list) {
return [];
}

return save_push($provider, '腾讯云', '.msg-d-bd', $list);
}

/**
* 获取腾讯云公告列表
*
* @return array
*/
function get_tencent_list() {
// api 接口地址
$api_url = 'https://cloud.tencent.com/announce/ajax?action=getAnnounceList';

$data = json_decode(file_get_contents($api_url), true)['data']['rows'];

return array_map(function($item) {
return [
'name' => $item['title'],
'href' => 'https://cloud.tencent.com/announce/detail/' . $item['announceId'],
];
}, $data);
}

/**
* 获取华为云
*
* @return array
*/
function get_huawei() {
$provider = 'huawei';

$list = get_list($provider);

if(!$list) {
return [];
}

return save_push($provider, '华为云', '.notice-content', $list);
}

/**
* 获取华为云公告列表
*
* @return array
*/
function get_huawei_list() {
return QueryList::get('https://www.huaweicloud.com/notice.html')
->rules([
'name' => ['.content > a', 'bi_name'],
'href' => ['.content > a', 'href'],
])
->range('.notice-content .notice-list:first > .content-list')
->query()
->queryData();
}

/**
* 获取详情页内容
*
* @param string $url 链接
* @param string $find 选择器
* @return string
*/
function get_context(string $url, string $find) {
$context = QueryList::get($url)
->find($find);

return normalize_context($context);
}

/**
* 保存推送
*
* @param string $provider 厂商
* @param string $provider_name 厂商名
* @param string $find 选择器
* @param array $data 数据
* @return void
*/
function save_push(string $provider, string $provider_name, string $find, array $data) {
global $config, $push;

$provider_data = $config[$provider];

foreach($data as $item) {
$push[] = [$provider_name, $item['name'], get_context($item['href'], $find . ' > *')];

array_unshift($provider_data, $item);
}

$config[$provider] = array_slice($provider_data, 0, 30);

return;
}

/**
* 推送
* wiki: http://sc.ftqq.com/?c=code
*
* @param string $provider 厂商
* @param string $name 名称
* @param string $context 内容
* @return string|bool
*/
function send($provider, $name, $context) {
$api_url = 'https://sc.ftqq.com/' . getenv('sckey') . '.send';

return file_get_contents($api_url, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => sprintf('text=%s-%s&desp=%s', $provider, $name, $context),
],
]));
}

/**
* 格式化列表
* 将列表的 name 设置为 key
*
* @param array $data 数据
* @return array
*/
function normalize_list(array $data) {
return collect($data)
->keyBy('name')
->all();
}

/**
* 格式化内容
*
* @param \QL\QueryList $ql ql
* @return string
*/
function normalize_context($ql) {
return $ql->htmls()
->map(function($item) {
return urlencode(str_replace('<br>', urldecode('%0D%0A%0D%0A'), $item));
})
// 换行符号
->implode('%0D%0A%0D%0A');
}

/**
* 获取配置
*
* @return array
*/
function get_config() {
global $oss, $bucket, $config_name;

$exist = $oss->doesObjectExist($bucket, $config_name);

if(!$exist) {
return [];
}

$config = $oss->getObject($bucket, $config_name);

return json_decode($config, true);
}

/**
* 保存配置
*
* @param array $config 配置
* @return void
*/
function save_config(array $config) {
global $oss, $bucket, $config_name;

return $oss->putObject($bucket, $config_name, json_encode($config, JSON_UNESCAPED_UNICODE));
}

?>

结果


源码下载

往上