Skip to content

Commit 9f15fba

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

File tree

9 files changed

+164
-31
lines changed

9 files changed

+164
-31
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const WebServerImageDefaultPort = 9000;
2+
3+
export const YunTiTagDocHref = 'https://doc.weixin.qq.com/doc/w3_AQ8AWgYkAOEEeD1cr34R7S66r8ONY?scode=AJEAIQdfAAoiSWrYcZAOMAswb5AFM';

src/modules/scf/index.ts

Lines changed: 45 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, getQcsResourceId, 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 {
@@ -262,6 +265,29 @@ export default class Scf {
262265
},
263266
});
264267

268+
// 如果触发器类型为 API 网关触发器 且当前账号是自研账号,则需要为 API 网关触发器添加函数标签
269+
if (trigger.Type === ApiServiceType.apigw) {
270+
try {
271+
const cam = new Cam(this.credentials, this.region);
272+
const userInfo = await cam.GetUserAppId();
273+
const isYunTi = await checkYunTi(userInfo?.OwnerUin);
274+
if (isYunTi) {
275+
await this.tagClient.deployResourceTags({
276+
tags: funcInfo.Tags.map(({ Key, Value }) => ({ TagKey: Key, TagValue: Value })),
277+
resourceId: getQcsResourceId(
278+
ApiServiceType.apigw,
279+
this.region,
280+
userInfo.OwnerUin,
281+
`service/${triggerOutput?.serviceId}`,
282+
),
283+
serviceType: ApiServiceType.apigw,
284+
resourcePrefix: 'service',
285+
});
286+
}
287+
} catch (error) {
288+
console.log('adding tags for trigger resource error:', error?.message)
289+
}
290+
}
265291
deployList[i] = {
266292
NeedCreate: trigger?.NeedCreate,
267293
...triggerOutput,
@@ -278,6 +304,23 @@ export default class Scf {
278304
const functionName = inputs.name;
279305
const { ignoreTriggers = false } = inputs;
280306

307+
// 自研账号,需要检查函数标签是否配置云梯标签(运营部门、运营产品、负责人); 非自研账号,不需要检查
308+
const cam = new Cam(this.credentials, this.region);
309+
const userInfo = await cam.GetUserAppId();
310+
const isYunTi = await checkYunTi(userInfo?.OwnerUin);
311+
if (isYunTi) {
312+
if (
313+
!isAddedYunTiTags(
314+
Object.entries(inputs.tags || {}).map(([TagKey, TagValue]) => ({ TagKey, TagValue })),
315+
)
316+
) {
317+
throw new ApiError({
318+
type: 'API_SCF_DeployFunction',
319+
message: `部署失败:自研用户请按照运营部门、运营产品、负责人正确配置函数标签,标签配置指南请参考:${YunTiTagDocHref}`,
320+
});
321+
}
322+
}
323+
281324
// 在部署前,检查函数初始状态,如果初始为 CreateFailed,尝试先删除,再重新创建
282325
let funcInfo = await this.scf.getInitialStatus({ namespace, functionName });
283326

src/utils/api.ts

Lines changed: 38 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,39 @@ 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+
throw error;
131+
}
132+
return isYunTi;
133+
};

src/utils/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ 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';
67

78
// TODO: 将一些库换成 lodash
89

@@ -273,3 +274,45 @@ export const getQcsResourceId = (service: string, region: string, uin: string, s
273274
// 云资源六段式
274275
return `qcs::${service}:${region}:uin/${uin}:${suffix}`;
275276
};
277+
278+
/**
279+
* hmacSha1 加密HmacSHA1
280+
* @param text 加密文本
281+
* @param key 加密密钥
282+
* @returns
283+
*/
284+
export const hmacSha1 = (text: string, key: string) => {
285+
return crypto.createHmac('sha1', key).update(text).digest('hex');
286+
};
287+
288+
/**
289+
* getYunTiApiUrl 查询云梯API地址
290+
* @returns 云梯API地址
291+
*/
292+
export const getYunTiApiUrl = (): string => {
293+
const apiKey = process.env.SLS_YUNTI_API_KEY || '';
294+
const apiSecret = process.env.SLS_YUNTI_API_SECRET || '';
295+
const apiUrl = process.env.SLS_YUNTI_API_URL;
296+
const timeStamp = Math.floor(Date.now() / 1000);
297+
const apiSign = hmacSha1(`${timeStamp}${apiKey}`, apiSecret);
298+
const url = `${apiUrl}?api_key=${apiKey}&api_ts=${timeStamp}&api_sign=${apiSign}`;
299+
return url;
300+
};
301+
302+
/**
303+
* 是否配置云梯标签
304+
* @param tags 标签列表
305+
* @returns true: 已配置云梯标签; false: 未配置云梯标签
306+
*/
307+
export const isAddedYunTiTags = (tags: { TagKey: string; TagValue?: string }[]): boolean => {
308+
let result = false;
309+
if (
310+
tags?.length > 0 &&
311+
['运营部门', '运营产品', '负责人'].every((key) =>
312+
tags.some((item) => item.TagKey === key && !!item.TagValue),
313+
)
314+
) {
315+
result = true;
316+
}
317+
return result;
318+
};

0 commit comments

Comments
 (0)