Skip to content

Commit 9058356

Browse files
committed
feat: 部署函数支持添加云梯默认标签(运营部门,运营产品,负责人)
1 parent b525006 commit 9058356

File tree

12 files changed

+193
-32
lines changed

12 files changed

+193
-32
lines changed

package-lock.json

Lines changed: 16 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tencent-component-toolkit",
3-
"version": "2.24.2",
3+
"version": "2.24.3",
44
"description": "Tencent component toolkit",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
@@ -66,12 +66,12 @@
6666
"@semantic-release/git": "^9.0.0",
6767
"@semantic-release/npm": "^7.0.4",
6868
"@semantic-release/release-notes-generator": "^9.0.1",
69+
"@types/axios": "^0.14.0",
6970
"@types/react-grid-layout": "^1.1.2",
7071
"@types/uuid": "^8.3.1",
7172
"@typescript-eslint/eslint-plugin": "^4.14.0",
7273
"@typescript-eslint/parser": "^4.14.0",
7374
"@ygkit/secure": "^0.0.3",
74-
"axios": "^0.21.0",
7575
"dotenv": "^8.2.0",
7676
"eslint": "^7.18.0",
7777
"eslint-config-prettier": "^6.10.0",
@@ -90,6 +90,7 @@
9090
"@types/jest": "^26.0.20",
9191
"@types/node": "^14.14.31",
9292
"@ygkit/request": "^0.1.8",
93+
"axios": "^0.21.0",
9394
"camelcase": "^6.2.0",
9495
"cos-nodejs-sdk-v5": "^2.9.20",
9596
"dayjs": "^1.10.4",

src/modules/apigw/index.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,23 +151,6 @@ export default class Apigw {
151151
outputs.usagePlan = usagePlan;
152152
}
153153

154-
try {
155-
const { tags } = inputs;
156-
if (tags) {
157-
await this.tagClient.deployResourceTags({
158-
tags: tags.map(({ key, value }) => ({ TagKey: key, TagValue: value })),
159-
resourceId: serviceId,
160-
serviceType: ApiServiceType.apigw,
161-
resourcePrefix: 'service',
162-
});
163-
if (tags.length > 0) {
164-
outputs.tags = tags;
165-
}
166-
}
167-
} catch (e) {
168-
console.log(`[TAG] ${e.message}`);
169-
}
170-
171154
// return this.formatApigwOutputs(outputs);
172155
return outputs;
173156
}

src/modules/cam/apis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const ACTIONS = [
88
'CreateRole',
99
'GetRole',
1010
'DeleteRole',
11+
'GetUserAppId',
1112
] as const;
1213

1314
export type ActionType = typeof ACTIONS[number];

src/modules/cam/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,19 @@ export default class Cam {
112112
async CheckSCFExcuteRole() {
113113
return this.isRoleExist('QCS_SCFExcuteRole');
114114
}
115+
116+
/** 查询用户AppId */
117+
async GetUserAppId(): Promise<{ OwnerUin: string; AppId: string; Uin: string }> {
118+
try {
119+
return this.request({
120+
Action: 'GetUserAppId',
121+
});
122+
} catch (error) {
123+
return {
124+
OwnerUin: '',
125+
AppId: '',
126+
Uin: '',
127+
};
128+
}
129+
}
115130
}

src/modules/scf/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const WebServerImageDefaultPort = 9000;
2+
export const YunTiTagDocHref = 'https://doc.weixin.qq.com/doc/w3_AQ8AWgYkAOEEeD1cr34R7S66r8ONY?scode=AJEAIQdfAAoiSWrYcZAOMAswb5AFM';

src/modules/scf/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { ApigwRemoveInputs } from './../apigw/interface';
22
import { ActionType } from './apis';
33
import { RegionType, ApiServiceType, CapiCredentials } from './../interface';
44
import { Capi } from '@tencent-sdk/capi';
5-
import { ApiTypeError } from '../../utils/error';
6-
import { deepClone, strip } from '../../utils';
5+
import { ApiError, ApiTypeError } from '../../utils/error';
6+
import { deepClone, formatInputTags, isAddedYunTiTags, strip } from '../../utils';
77
import TagsUtils from '../tag/index';
88
import ApigwUtils from '../apigw';
99
import CONFIGS from './config';
@@ -25,6 +25,9 @@ import ScfEntity from './entities/scf';
2525
import AliasEntity from './entities/alias';
2626
import VersionEntity from './entities/version';
2727
import { ConcurrencyEntity } from './entities/concurrency';
28+
import { default as Cam } from '../cam';
29+
import { YunTiTagDocHref } from './constants';
30+
import { checkYunTi } from '../../utils/api';
2831

2932
/** 云函数组件 */
3033
export default class Scf {
@@ -252,13 +255,15 @@ export default class Scf {
252255
credentials: this.credentials,
253256
region: this.region,
254257
});
258+
const tags: any = trigger?.parameters?.tags ?? trigger?.tags ?? funcInfo.Tags ?? [];
255259
const triggerOutput = await triggerInstance.create({
256260
scf: this,
257261
region: this.region,
258262
inputs: {
259263
namespace: funcInfo.Namespace,
260264
functionName: funcInfo.FunctionName,
261265
...trigger,
266+
tags: formatInputTags(tags),
262267
},
263268
});
264269

@@ -278,6 +283,19 @@ export default class Scf {
278283
const functionName = inputs.name;
279284
const { ignoreTriggers = false } = inputs;
280285

286+
// 自研账号,需要检查函数标签是否配置云梯标签(运营部门、运营产品、负责人); 非自研账号,不需要检查
287+
const cam = new Cam(this.credentials, this.region);
288+
const userInfo = await cam.GetUserAppId();
289+
const isYunTi = await checkYunTi(userInfo?.OwnerUin);
290+
if (isYunTi) {
291+
if (!isAddedYunTiTags(inputs)) {
292+
throw new ApiError({
293+
type: 'API_SCF_DeployFunction',
294+
message: `部署失败:自研用户请按照运营部门、运营产品、负责人正确配置函数标签,标签配置指南请参考:${YunTiTagDocHref}`,
295+
});
296+
}
297+
}
298+
281299
// 在部署前,检查函数初始状态,如果初始为 CreateFailed,尝试先删除,再重新创建
282300
let funcInfo = await this.scf.getInitialStatus({ namespace, functionName });
283301

src/modules/scf/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export interface TriggerType {
7878
TriggerName?: string;
7979
Qualifier?: string;
8080
compared?: boolean;
81+
tags?: object;
82+
parameters?: any;
8183
}
8284

8385
export type OriginTriggerType = {

src/modules/triggers/apigw.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export default class ApigwTrigger extends BaseTrigger<ApigwTriggerInputsParams>
147147
funcInfo?: FunctionInfo;
148148
inputs: TriggerInputs<ApigwTriggerInputsParams>;
149149
}) {
150-
const { parameters, isAutoRelease } = inputs;
150+
const { parameters, isAutoRelease, tags } = inputs;
151151
const {
152152
oldState,
153153
protocols,
@@ -192,6 +192,7 @@ export default class ApigwTrigger extends BaseTrigger<ApigwTriggerInputsParams>
192192
method: endpoints[0].method ?? 'ANY',
193193
},
194194
created: !!parameters?.created,
195+
tags,
195196
};
196197
const triggerKey = this.getKey(triggerInputs);
197198

src/modules/triggers/interface/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApigwDeployInputs, ApiEndpoint } from '../../apigw/interface';
2+
import { TagInput } from '../../interface';
23

34
export interface ApigwTriggerRemoveScfTriggerInputs {
45
serviceId: string;
@@ -129,6 +130,9 @@ export interface TriggerInputs<P extends TriggerInputsParams = TriggerInputsPara
129130

130131
// 是否自动发布服务(API 网关特有)
131132
isAutoRelease?: boolean;
133+
134+
// 标签列表
135+
tags?: TagInput[];
132136
}
133137

134138
export interface TriggerDetail {

src/utils/api.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Capi } from '@tencent-sdk/capi';
2-
import { deepClone } from '.';
2+
import axios from 'axios';
3+
import { deepClone, getYunTiApiUrl } from '.';
34
import { ApiServiceType } from '../modules/interface';
45
import { ApiError } from './error';
56

@@ -94,3 +95,38 @@ export function ApiFactory<ACTIONS_T extends readonly string[]>({
9495

9596
return APIS;
9697
}
98+
99+
/**
100+
* checkYunTi 查询账号是否是自研账号
101+
* @param uin 客户主账号
102+
* @returns true: 是自研账号; false: 不是自研账号
103+
*/
104+
export const checkYunTi = async (uin: string) => {
105+
let isYunTi = false;
106+
try {
107+
const params = JSON.stringify({
108+
id: '1',
109+
jsonrpc: '2.0',
110+
method: 'checkOwnUin',
111+
params: { ownUin: [uin] },
112+
});
113+
const apiUrl = getYunTiApiUrl();
114+
const res = await axios.post(apiUrl, params, {
115+
headers: { 'content-type': 'application/json' },
116+
});
117+
if (res?.data?.error?.message) {
118+
throw new Error(res.data.error.message);
119+
} else {
120+
isYunTi =
121+
res?.data?.result?.data &&
122+
res.data.result.data?.some(
123+
(item: { ownUin: string; appId: string }) => item?.ownUin === uin,
124+
);
125+
console.log('check yunTi ownUin:', isYunTi);
126+
}
127+
} catch (error) {
128+
isYunTi = false;
129+
console.log('checkYunTiOwnUin error:', error);
130+
}
131+
return isYunTi;
132+
};

src/utils/index.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'path';
33
import camelCase from 'camelcase';
44
import { PascalCase } from 'type-fest';
55
import { CamelCasedProps, PascalCasedProps } from '../modules/interface';
6+
import crypto from 'crypto';
7+
import { ScfDeployInputs } from '../modules/scf/interface';
68

79
// TODO: 将一些库换成 lodash
810

@@ -273,3 +275,93 @@ export const getQcsResourceId = (service: string, region: string, uin: string, s
273275
// 云资源六段式
274276
return `qcs::${service}:${region}:uin/${uin}:${suffix}`;
275277
};
278+
279+
/**
280+
* hmacSha1 加密HmacSHA1
281+
* @param text 加密文本
282+
* @param key 加密密钥
283+
* @returns
284+
*/
285+
export const hmacSha1 = (text: string, key: string) => {
286+
return crypto.createHmac('sha1', key).update(text).digest('hex');
287+
};
288+
289+
/**
290+
* getYunTiApiUrl 查询云梯API地址
291+
* @returns 云梯API地址
292+
*/
293+
export const getYunTiApiUrl = (): string => {
294+
const apiKey = process.env.SLS_YUNTI_API_KEY || '';
295+
const apiSecret = process.env.SLS_YUNTI_API_SECRET || '';
296+
const apiUrl = process.env.SLS_YUNTI_API_URL;
297+
const timeStamp = Math.floor(Date.now() / 1000);
298+
const apiSign = hmacSha1(`${timeStamp}${apiKey}`, apiSecret);
299+
const url = `${apiUrl}?api_key=${apiKey}&api_ts=${timeStamp}&api_sign=${apiSign}`;
300+
return url;
301+
};
302+
303+
/**
304+
* formatInputTags 格式化输入标签
305+
*/
306+
export const formatInputTags = (
307+
input: Array<any> | { [key: string]: string },
308+
): { key: string; value: string }[] => {
309+
let tags: { key: string; value: string }[] = [];
310+
if (Array.isArray(input)) {
311+
tags = input.map((item) => {
312+
return {
313+
key: item?.key ?? item?.Key ?? '',
314+
value: item?.value ?? item?.Value ?? '',
315+
};
316+
});
317+
} else if (typeof input === 'object' && input !== null) {
318+
tags = Object.entries(input).map(([key, value]) => {
319+
return {
320+
key: (key ?? '').toString(),
321+
value: (value ?? '').toString(),
322+
};
323+
});
324+
} else {
325+
tags = [];
326+
}
327+
return tags;
328+
};
329+
330+
/**
331+
* 是否配置云梯标签
332+
* @param inputs ScfDeployInputs
333+
* @returns true: 已配置云梯标签; false: 未配置云梯标签
334+
*/
335+
export const isAddedYunTiTags = (inputs: ScfDeployInputs): boolean => {
336+
let result = false;
337+
let isApiAddedTags = false;
338+
let isApiAddedYunTiTags = false;
339+
inputs.events?.forEach((item) => {
340+
const [[key, value]] = Object.entries(item);
341+
console.log(key, value);
342+
const tags = formatInputTags(value?.parameters?.tags ?? []);
343+
if (key === 'apigw' && tags?.length > 0) {
344+
isApiAddedTags = true;
345+
}
346+
if (
347+
key === 'apigw' &&
348+
tags?.length > 0 &&
349+
['运营部门', '运营产品', '负责人'].every((tagKey) =>
350+
tags.some((tag) => tag.key === tagKey && !!tag.value),
351+
)
352+
) {
353+
isApiAddedYunTiTags = true;
354+
}
355+
});
356+
357+
const functionTags: { TagKey: string; TagValue: string }[] = Object.entries(
358+
inputs.tags ?? {},
359+
).map(([TagKey, TagValue]) => ({ TagKey, TagValue }));
360+
const isFunAddedYunTiTags =
361+
functionTags?.length > 0 &&
362+
['运营部门', '运营产品', '负责人'].every((key) =>
363+
functionTags.some((item) => item.TagKey === key && !!item.TagValue),
364+
);
365+
result = isApiAddedYunTiTags || (!isApiAddedTags && isFunAddedYunTiTags);
366+
return result;
367+
};

0 commit comments

Comments
 (0)