更新 ts-proto 版本

This commit is contained in:
whyour
2025-01-29 23:29:45 +08:00
parent 924dfd6a6f
commit 6df651aa63
11 changed files with 2061 additions and 275 deletions
+128 -39
View File
@@ -1,45 +1,134 @@
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const { join } = require('path');
const PROTO_PATH = `${process.env.QL_DIR}/back/protos/api.proto`;
const options = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
};
const packageDefinition = protoLoader.loadSync(PROTO_PATH, options);
const apiProto = grpc.loadPackageDefinition(packageDefinition).com.ql.api;
const client = new apiProto.Api(
`0.0.0.0:5500`,
grpc.credentials.createInsecure(),
{ 'grpc.enable_http_proxy': 0 },
);
const promisify = (fn) => {
return (...args) => {
return new Promise((resolve, reject) => {
fn.call(client, ...args, (err, response) => {
if (err) return reject(err);
resolve(response);
});
});
class GrpcClient {
static #config = {
protoPath: join(process.env.QL_DIR, 'back/protos/api.proto'),
serverAddress: '0.0.0.0:5500',
protoOptions: {
keepCase: true,
longs: String,
enums: String,
defaults: true,
},
grpcOptions: {
'grpc.enable_http_proxy': 0,
'grpc.keepalive_time_ms': 120000,
'grpc.keepalive_timeout_ms': 20000,
'grpc.max_receive_message_length': 100 * 1024 * 1024,
},
defaultTimeout: 30000,
};
};
const api = {
getEnvs: promisify(client.GetEnvs),
createEnv: promisify(client.CreateEnv),
updateEnv: promisify(client.UpdateEnv),
deleteEnvs: promisify(client.DeleteEnvs),
moveEnv: promisify(client.MoveEnv),
disableEnvs: promisify(client.DisableEnvs),
enableEnvs: promisify(client.EnableEnvs),
updateEnvNames: promisify(client.UpdateEnvNames),
getEnvById: promisify(client.GetEnvById),
systemNotify: promisify(client.SystemNotify),
};
static #methods = [
'getEnvs',
'createEnv',
'updateEnv',
'deleteEnvs',
'moveEnv',
'disableEnvs',
'enableEnvs',
'updateEnvNames',
'getEnvById',
'systemNotify',
'getCronDetail',
'createCron',
'updateCron',
'deleteCrons'
];
module.exports = api;
#client;
#api = {};
constructor() {
this.#initializeClient();
this.#bindMethods();
}
#initializeClient() {
try {
const { protoPath, protoOptions, serverAddress, grpcOptions } =
GrpcClient.#config;
const packageDefinition = protoLoader.loadSync(protoPath, protoOptions);
const apiProto = grpc.loadPackageDefinition(packageDefinition).com.ql.api;
this.#client = new apiProto.Api(
serverAddress,
grpc.credentials.createInsecure(),
grpcOptions,
);
this.#checkConnection();
} catch (error) {
console.error('Failed to initialize gRPC client:', error);
process.exit(1);
}
}
#checkConnection() {
this.#client.waitForReady(Date.now() + 5000, (error) => {
if (error) {
console.error('gRPC client connection failed:', error);
process.exit(1);
}
});
}
#promisifyMethod(methodName) {
const capitalizedMethod =
methodName.charAt(0).toUpperCase() + methodName.slice(1);
const grpcMethod = this.#client[capitalizedMethod].bind(this.#client);
return async (params = {}) => {
return new Promise((resolve, reject) => {
const metadata = new grpc.Metadata();
const deadline = new Date(
Date.now() + GrpcClient.#config.defaultTimeout,
);
grpcMethod(params, metadata, { deadline }, (error, response) => {
if (error) {
return reject(error);
}
resolve(response);
});
});
};
}
#bindMethods() {
GrpcClient.#methods.forEach((method) => {
this.#api[method] = this.#promisifyMethod(method);
});
}
getApi() {
return {
...this.#api,
close: this.close.bind(this),
};
}
close() {
if (this.#client) {
this.#client.close();
this.#client = null;
}
}
}
const grpcClient = new GrpcClient();
process.on('SIGTERM', () => {
grpcClient.close();
process.exit(0);
});
process.on('SIGINT', () => {
grpcClient.close();
process.exit(0);
});
module.exports = grpcClient.getApi();
+200 -30
View File
@@ -2,7 +2,7 @@ import subprocess
import json
import tempfile
import os
from typing import Dict, List
from typing import Dict, List, TypedDict, Optional
from functools import wraps
@@ -11,16 +11,170 @@ def error_handler(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except json.JSONDecodeError as e:
raise Exception(f"parse json error: {str(e)}")
except subprocess.SubprocessError as e:
raise Exception(f"node process error: {str(e)}")
except TypeError as e:
if "missing" in str(e):
func_name = func.__name__
annotations = func.__annotations__
param_type = next(
(t for name, t in annotations.items() if name != "return"), None
)
if param_type and hasattr(param_type, "__annotations__"):
required_fields = {
k: v
for k, v in param_type.__annotations__.items()
if not getattr(param_type, "__total__", True)
or k in getattr(param_type, "__required_keys__", set())
}
fields_str = ", ".join(
f'"{k}": {v.__name__}' for k, v in required_fields.items()
)
raise Exception(
f"unknown error: {func_name}() requires a dictionary with parameters: {{{fields_str}}}"
) from None
raise Exception(f"unknown error: {str(e)}") from None
except Exception as e:
raise Exception(f"unknown error: {str(e)}")
error_msg = str(e)
if "Error:" in error_msg:
error_msg = error_msg.split("Error:")[-1].split("\n")[0].strip()
raise Exception(f"unknown error: {error_msg}") from None
return wrapper
class EnvItem(TypedDict, total=False):
id: Optional[int]
name: Optional[str]
value: Optional[str]
remarks: Optional[str]
status: Optional[int]
position: Optional[int]
class GetEnvsParams(TypedDict, total=False):
searchValue: str
class CreateEnvParams(TypedDict):
envs: List[EnvItem]
class UpdateEnvParams(TypedDict):
env: EnvItem
class DeleteEnvsParams(TypedDict):
ids: List[int]
class MoveEnvParams(TypedDict):
id: int
fromIndex: int
toIndex: int
class DisableEnvsParams(TypedDict):
ids: List[int]
class EnableEnvsParams(TypedDict):
ids: List[int]
class UpdateEnvNamesParams(TypedDict):
ids: List[int]
name: str
class GetEnvByIdParams(TypedDict):
id: int
class SystemNotifyParams(TypedDict):
title: str
content: str
class EnvsResponse(TypedDict):
code: int
data: List[EnvItem]
message: Optional[str]
class EnvResponse(TypedDict):
code: int
data: EnvItem
message: Optional[str]
class Response(TypedDict):
code: int
message: Optional[str]
class ExtraScheduleItem(TypedDict, total=False):
schedule: Optional[str]
class CronItem(TypedDict, total=False):
id: Optional[int]
command: Optional[str]
schedule: Optional[str]
name: Optional[str]
labels: List[str]
sub_id: Optional[int]
extra_schedules: List[ExtraScheduleItem]
task_before: Optional[str]
task_after: Optional[str]
status: Optional[int]
log_path: Optional[str]
pid: Optional[int]
last_running_time: Optional[int]
last_execution_time: Optional[int]
class CreateCronParams(TypedDict):
command: str
schedule: str
name: Optional[str]
labels: List[str]
sub_id: Optional[int]
extra_schedules: List[ExtraScheduleItem]
task_before: Optional[str]
task_after: Optional[str]
class UpdateCronParams(TypedDict):
id: int
command: str
schedule: str
name: Optional[str]
labels: List[str]
sub_id: Optional[int]
extra_schedules: List[ExtraScheduleItem]
task_before: Optional[str]
task_after: Optional[str]
class DeleteCronsParams(TypedDict):
ids: List[int]
class CronDetailParams(TypedDict):
log_path: str
class CronsResponse(TypedDict):
code: int
data: List[CronItem]
message: Optional[str]
class CronResponse(TypedDict):
code: int
data: CronItem
message: Optional[str]
class Client:
def __init__(self):
self.temp_dir = tempfile.mkdtemp(prefix="node_client_")
@@ -46,7 +200,8 @@ class Client:
}} catch (error) {{
console.error(JSON.stringify({{
error: error.message,
stack: error.stack
stack: error.stack,
name: error.name
}}));
process.exit(1);
}}
@@ -56,58 +211,73 @@ class Client:
with open(self.temp_script, "w", encoding="utf-8") as f:
f.write(node_code)
try:
result = subprocess.run(
["node", self.temp_script],
capture_output=True,
text=True,
timeout=30,
result = subprocess.run(
["node", self.temp_script],
capture_output=True,
text=True,
timeout=30,
)
if result.returncode != 0:
error_data = json.loads(result.stderr)
raise Exception(
f"{error_data.get('name', 'Error')}: {error_data.get('stack')}"
)
if result.returncode != 0:
error_data = json.loads(result.stderr)
raise Exception(f"{error_data.get('stack')}")
return json.loads(result.stdout)
except subprocess.TimeoutExpired:
raise Exception("node process timeout")
return json.loads(result.stdout)
@error_handler
def getEnvs(self, params: Dict = None) -> Dict:
def getEnvs(self, params: GetEnvsParams = None) -> EnvsResponse:
return self._execute_node("getEnvs", params)
@error_handler
def createEnv(self, data: Dict) -> Dict:
def createEnv(self, data: CreateEnvParams) -> EnvsResponse:
return self._execute_node("createEnv", data)
@error_handler
def updateEnv(self, data: Dict) -> Dict:
def updateEnv(self, data: UpdateEnvParams) -> EnvResponse:
return self._execute_node("updateEnv", data)
@error_handler
def deleteEnvs(self, data: Dict) -> Dict:
def deleteEnvs(self, data: DeleteEnvsParams) -> Response:
return self._execute_node("deleteEnvs", data)
@error_handler
def moveEnv(self, data: Dict) -> Dict:
def moveEnv(self, data: MoveEnvParams) -> EnvResponse:
return self._execute_node("moveEnv", data)
@error_handler
def disableEnvs(self, data: Dict) -> Dict:
def disableEnvs(self, data: DisableEnvsParams) -> Response:
return self._execute_node("disableEnvs", data)
@error_handler
def enableEnvs(self, data: Dict) -> Dict:
def enableEnvs(self, data: EnableEnvsParams) -> Response:
return self._execute_node("enableEnvs", data)
@error_handler
def updateEnvNames(self, data: Dict) -> Dict:
def updateEnvNames(self, data: UpdateEnvNamesParams) -> Response:
return self._execute_node("updateEnvNames", data)
@error_handler
def getEnvById(self, data: Dict) -> Dict:
def getEnvById(self, data: GetEnvByIdParams) -> EnvResponse:
return self._execute_node("getEnvById", data)
@error_handler
def systemNotify(self, data: Dict) -> Dict:
def systemNotify(self, data: SystemNotifyParams) -> Response:
return self._execute_node("systemNotify", data)
@error_handler
def getCronDetail(self, data: CronDetailParams) -> CronResponse:
return self._execute_node("getCronDetail", data)
@error_handler
def createCron(self, data: CreateCronParams) -> CronResponse:
return self._execute_node("createCron", data)
@error_handler
def updateCron(self, data: UpdateCronParams) -> CronResponse:
return self._execute_node("updateCron", data)
@error_handler
def deleteCrons(self, data: DeleteCronsParams) -> Response:
return self._execute_node("deleteCrons", data)