env-plugin 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/bin/index.js +23 -0
  3. package/dist/Container.js +55 -0
  4. package/dist/Plugin.js +25 -0
  5. package/dist/PostProxyServer.js +85 -0
  6. package/dist/client/assets/element-plus-wndYbaZY.js.gz +0 -0
  7. package/dist/client/assets/index-Bl3B2M1W.js.gz +0 -0
  8. package/dist/client/assets/index-nrcddKJg.css.gz +0 -0
  9. package/dist/client/assets/vue-BGFZjnqd.js.gz +0 -0
  10. package/dist/client/favicon.ico +0 -0
  11. package/dist/client/index.html +16 -0
  12. package/dist/constants/responseCode.js +27 -0
  13. package/dist/controllers/DevServerController.js +102 -0
  14. package/dist/controllers/EnvController.js +177 -0
  15. package/dist/controllers/PasswordController.js +106 -0
  16. package/dist/controllers/RouteRuleController.js +106 -0
  17. package/dist/dto/BaseRes.js +1 -0
  18. package/dist/dto/EnvDTO.js +4 -0
  19. package/dist/index.js +108 -0
  20. package/dist/middleware/dto.middleware.js +38 -0
  21. package/dist/middleware/globalErrorHandler.js +42 -0
  22. package/dist/middleware/responseEnhancer.js +52 -0
  23. package/dist/models/DevServerModel.js +1 -0
  24. package/dist/models/EnvModel.js +14 -0
  25. package/dist/repositories/DevServerRepo.js +101 -0
  26. package/dist/repositories/EnvRepo.js +121 -0
  27. package/dist/repositories/PasswordRepo.js +142 -0
  28. package/dist/repositories/RouteRuleRepo.js +106 -0
  29. package/dist/repositories/database.js +95 -0
  30. package/dist/routes/index.js +78 -0
  31. package/dist/service/DevServerService.js +189 -0
  32. package/dist/service/EnvService.js +214 -0
  33. package/dist/service/PasswordService.js +100 -0
  34. package/dist/service/PreProxyServer.js +344 -0
  35. package/dist/service/ProxyAutoStarterService.js +62 -0
  36. package/dist/service/RouteRuleService.js +131 -0
  37. package/dist/types/index.js +5 -0
  38. package/dist/types/shared/BaseRes.js +36 -0
  39. package/dist/types/shared/DevServerItem.js +51 -0
  40. package/dist/types/shared/EnvItem.js +57 -0
  41. package/dist/types/shared/EnvmConfig.js +17 -0
  42. package/dist/types/shared/ListRes.js +1 -0
  43. package/dist/types/shared/Password.js +48 -0
  44. package/dist/types/shared/RouteRule.js +49 -0
  45. package/dist/utils/ResolveConfig.js +79 -0
  46. package/dist/utils/errors.js +8 -0
  47. package/dist/utils/logger.js +93 -0
  48. package/package.json +78 -0
  49. package/readme.md +214 -0
@@ -0,0 +1,142 @@
1
+ import { getDatabase } from "./database.js";
2
+ import { PasswordDeleteSchema, } from "../types/index.js";
3
+ /**
4
+ * 密码仓库类
5
+ * 负责密码相关数据的持久化操作,封装了数据库交互逻辑
6
+ */
7
+ class PasswordRepo {
8
+ /**
9
+ * 获取密码数据集合
10
+ * @returns 密码数据集合对象
11
+ */
12
+ getCollection() {
13
+ return getDatabase().getCollection("passwords");
14
+ }
15
+ /**
16
+ * 构造函数
17
+ */
18
+ constructor() { }
19
+ /**
20
+ * 初始化集合索引
21
+ */
22
+ initCollection() {
23
+ const collection = this.getCollection();
24
+ collection.ensureIndex("id");
25
+ collection.ensureIndex("envId");
26
+ collection.ensureIndex("isDefault");
27
+ }
28
+ /**
29
+ * 根据环境ID获取所有密码
30
+ * @param envId - 环境ID
31
+ * @returns 该环境的所有密码数组
32
+ */
33
+ getByEnvId(envId) {
34
+ return this.getCollection().find({ envId });
35
+ }
36
+ /**
37
+ * 统计指定环境的密码数量
38
+ * @param envId - 环境ID
39
+ * @returns 密码数量
40
+ */
41
+ countByEnvId(envId) {
42
+ return this.getCollection().count({ envId });
43
+ }
44
+ /**
45
+ * 根据ID查询单个密码
46
+ * @param id - 密码ID
47
+ * @returns 匹配的密码,若未找到则返回null
48
+ */
49
+ findOneById(id) {
50
+ return this.getCollection().findOne({ id });
51
+ }
52
+ /**
53
+ * 添加新密码
54
+ * @param password - 要添加的密码完整信息对象
55
+ */
56
+ create(password) {
57
+ this.getCollection().insert(password);
58
+ }
59
+ /**
60
+ * 删除指定密码
61
+ * @param password - 包含要删除密码ID的对象
62
+ */
63
+ delete(password) {
64
+ const target = PasswordDeleteSchema.safeParse(password);
65
+ if (!target.success) {
66
+ throw new Error(`删除密码验证失败: ${JSON.stringify(target.error)}`);
67
+ }
68
+ this.getCollection().findAndRemove(target.data);
69
+ }
70
+ /**
71
+ * 删除指定环境的所有密码
72
+ * @param envId - 环境ID
73
+ */
74
+ deleteByEnvId(envId) {
75
+ this.getCollection().findAndRemove({ envId });
76
+ }
77
+ /**
78
+ * 更新密码信息
79
+ * @param passwordItem - 包含要更新的密码ID及字段的对象
80
+ */
81
+ update(passwordItem) {
82
+ this.getCollection().findAndUpdate({ id: passwordItem.id }, (pwd) => {
83
+ if (!pwd) {
84
+ throw new Error("未找到对应的密码");
85
+ }
86
+ Object.assign(pwd, passwordItem);
87
+ return pwd;
88
+ });
89
+ }
90
+ /**
91
+ * 检查指定环境是否已存在相同名称的密码
92
+ * @param envId - 环境ID
93
+ * @param name - 密码名称
94
+ * @returns 是否存在重复
95
+ */
96
+ existsByEnvIdAndName(envId, name) {
97
+ const existing = this.getCollection().findOne({
98
+ envId,
99
+ name,
100
+ });
101
+ return existing !== null;
102
+ }
103
+ /**
104
+ * 获取指定环境的默认密码
105
+ * @param envId - 环境ID
106
+ * @returns 默认密码,若未找到则返回null
107
+ */
108
+ findDefaultByEnvId(envId) {
109
+ return this.getCollection().findOne({ envId, isDefault: true });
110
+ }
111
+ /**
112
+ * 设置指定环境的默认密码(先将其他密码的isDefault设为false)
113
+ * @param envId - 环境ID
114
+ * @param excludeId - 排除的密码ID(更新时使用)
115
+ */
116
+ clearDefaultByEnvId(envId, excludeId) {
117
+ const query = { envId, isDefault: true };
118
+ if (excludeId) {
119
+ query.id = { $ne: excludeId };
120
+ }
121
+ this.getCollection().findAndUpdate(query, (pwd) => {
122
+ if (pwd) {
123
+ pwd.isDefault = false;
124
+ }
125
+ return pwd;
126
+ });
127
+ }
128
+ /**
129
+ * 检查指定环境是否已存在默认密码
130
+ * @param envId - 环境ID
131
+ * @param excludeId - 排除的密码ID(更新时使用)
132
+ * @returns 是否存在默认密码
133
+ */
134
+ hasDefaultPassword(envId, excludeId) {
135
+ const query = { envId, isDefault: true };
136
+ if (excludeId) {
137
+ query.id = { $ne: excludeId };
138
+ }
139
+ return this.getCollection().count(query) > 0;
140
+ }
141
+ }
142
+ export { PasswordRepo };
@@ -0,0 +1,106 @@
1
+ import { getDatabase } from "./database.js";
2
+ import { RouteRuleDeleteSchema,
3
+ // RouteRuleCreate,
4
+ } from "../types/index.js";
5
+ /**
6
+ * 路由规则仓库类
7
+ * 负责路由规则相关数据的持久化操作,封装了数据库交互逻辑
8
+ */
9
+ class RouteRuleRepo {
10
+ /**
11
+ * 获取路由规则数据集合
12
+ * @returns 路由规则数据集合对象
13
+ */
14
+ getCollection() {
15
+ return getDatabase().getCollection("routeRules");
16
+ }
17
+ /**
18
+ * 构造函数
19
+ */
20
+ constructor() { }
21
+ /**
22
+ * 初始化集合索引
23
+ */
24
+ initCollection() {
25
+ const collection = this.getCollection();
26
+ collection.ensureIndex("id");
27
+ collection.ensureIndex("envId");
28
+ collection.ensureIndex("pathPrefix");
29
+ }
30
+ /**
31
+ * 根据环境ID获取所有路由规则
32
+ * @param envId - 环境ID
33
+ * @returns 该环境的所有路由规则数组
34
+ */
35
+ getByEnvId(envId) {
36
+ return this.getCollection().find({ envId });
37
+ }
38
+ /**
39
+ * 统计指定环境的路由规则数量
40
+ * @param envId - 环境ID
41
+ * @returns 路由规则数量
42
+ */
43
+ countByEnvId(envId) {
44
+ return this.getCollection().count({ envId });
45
+ }
46
+ /**
47
+ * 根据ID查询单个路由规则
48
+ * @param id - 路由规则ID
49
+ * @returns 匹配的路由规则,若未找到则返回null
50
+ */
51
+ findOneById(id) {
52
+ return this.getCollection().findOne({ id });
53
+ }
54
+ /**
55
+ * 添加新路由规则
56
+ * @param routeRule - 要添加的路由规则完整信息对象
57
+ */
58
+ create(routeRule) {
59
+ this.getCollection().insert(routeRule);
60
+ }
61
+ /**
62
+ * 删除指定路由规则
63
+ * @param routeRule - 包含要删除路由规则ID的对象
64
+ */
65
+ delete(routeRule) {
66
+ const target = RouteRuleDeleteSchema.safeParse(routeRule);
67
+ if (!target.success) {
68
+ throw new Error(`删除路由规则验证失败: ${JSON.stringify(target.error)}`);
69
+ }
70
+ this.getCollection().findAndRemove(target.data);
71
+ }
72
+ /**
73
+ * 删除指定环境的所有路由规则
74
+ * @param envId - 环境ID
75
+ */
76
+ deleteByEnvId(envId) {
77
+ this.getCollection().findAndRemove({ envId });
78
+ }
79
+ /**
80
+ * 更新路由规则信息
81
+ * @param routeRuleItem - 包含要更新的路由规则ID及字段的对象
82
+ */
83
+ update(routeRuleItem) {
84
+ this.getCollection().findAndUpdate({ id: routeRuleItem.id }, (rule) => {
85
+ if (!rule) {
86
+ throw new Error("未找到对应的路由规则");
87
+ }
88
+ Object.assign(rule, routeRuleItem);
89
+ return rule;
90
+ });
91
+ }
92
+ /**
93
+ * 检查指定环境是否已存在相同路径前缀的规则
94
+ * @param envId - 环境ID
95
+ * @param pathPrefix - 路径前缀
96
+ * @returns 是否存在重复
97
+ */
98
+ existsByEnvIdAndPathPrefix(envId, pathPrefix) {
99
+ const existing = this.getCollection().findOne({
100
+ envId,
101
+ pathPrefix,
102
+ });
103
+ return existing !== null;
104
+ }
105
+ }
106
+ export { RouteRuleRepo };
@@ -0,0 +1,95 @@
1
+ import loki from "lokijs";
2
+ import { logger } from "../utils/logger.js";
3
+ // 声明数据库实例类型(避免全局变量类型模糊)
4
+ let db = null;
5
+ /**
6
+ * 数据库初始化配置(可抽离为外部配置文件)
7
+ */
8
+ const DB_CONFIG = {
9
+ filename: ".envm.data.json",
10
+ autosave: true,
11
+ autosaveInterval: 5000, // 注意:5秒,需统一
12
+ autoload: true,
13
+ };
14
+ /**
15
+ * 初始化开发环境集合
16
+ */
17
+ const initEnvmsCollection = (db) => {
18
+ if (!db.getCollection("envms")) {
19
+ db.addCollection("envms", {
20
+ indices: ["apiBaseUrl", "id"],
21
+ unique: ["apiBaseUrl"],
22
+ });
23
+ }
24
+ };
25
+ /**
26
+ * 初始化开发服务器集合
27
+ */
28
+ const initDevServerCollection = (db) => {
29
+ if (!db.getCollection("devServer")) {
30
+ db.addCollection("devServer", {
31
+ indices: ["id", "devServerUrl"],
32
+ unique: ["devServerUrl"],
33
+ });
34
+ }
35
+ };
36
+ /**
37
+ * 初始化路由规则集合
38
+ */
39
+ const initRouteRulesCollection = (db) => {
40
+ if (!db.getCollection("routeRules")) {
41
+ db.addCollection("routeRules", {
42
+ indices: ["id", "envId", "pathPrefix"],
43
+ });
44
+ }
45
+ };
46
+ /**
47
+ * 初始化密码集合
48
+ */
49
+ const initPasswordsCollection = (db) => {
50
+ if (!db.getCollection("passwords")) {
51
+ db.addCollection("passwords", {
52
+ indices: ["id", "envId", "isDefault"],
53
+ });
54
+ }
55
+ };
56
+ /**
57
+ * 手动启动数据库(核心函数)
58
+ * @returns 数据库实例(确保单例)
59
+ */
60
+ export const startDatabase = () => {
61
+ // 1. 单例保护:避免重复创建实例
62
+ if (db)
63
+ return Promise.resolve(db);
64
+ // 2. 返回Promise,支持异步等待和错误捕获
65
+ return new Promise((resolve, reject) => {
66
+ // 3. 初始化数据库
67
+ const newDb = new loki(DB_CONFIG.filename, {
68
+ ...DB_CONFIG,
69
+ // 4. 加载回调中处理集合初始化
70
+ autoloadCallback: (err) => {
71
+ if (err) {
72
+ reject(new Error(`数据库加载失败: ${err.message}`));
73
+ return;
74
+ }
75
+ // 初始化集合
76
+ initEnvmsCollection(newDb);
77
+ initDevServerCollection(newDb);
78
+ initRouteRulesCollection(newDb);
79
+ initPasswordsCollection(newDb);
80
+ // 5. 赋值单例并返回
81
+ db = newDb;
82
+ logger.info("数据库已加载/初始化");
83
+ resolve(newDb);
84
+ },
85
+ });
86
+ });
87
+ };
88
+ /**
89
+ * 获取数据库实例(需在startDatabase后调用)
90
+ */
91
+ export const getDatabase = () => {
92
+ if (!db)
93
+ throw new Error("数据库未启动,请先调用 startDatabase()");
94
+ return db;
95
+ };
@@ -0,0 +1,78 @@
1
+ // routes/index.ts
2
+ import express, { Router } from "express";
3
+ import * as libCookie from "cookie";
4
+ import { Container } from "../Container.js";
5
+ import { getConfig } from "../utils/ResolveConfig.js";
6
+ import { toDTO } from "../middleware/dto.middleware.js";
7
+ import { EnvPrimarySchema, EnvCreateSchema, EnvUpdateSchema, DevServerCreateSchema, DevServerDeleteSchema, DevServerUpdateSchema, RouteRuleCreateSchema, RouteRuleDeleteSchema, RouteRuleUpdateSchema, PasswordCreateSchema, PasswordDeleteSchema, PasswordUpdateSchema, } from "../types/index.js";
8
+ // 1. 创建各模块路由
9
+ const createEnvRoutes = (controller) => {
10
+ const router = Router();
11
+ router.get("/getlist", (...res) => controller.handleGetList(...res));
12
+ router.post("/add", toDTO(EnvCreateSchema), (...res) => controller.handleAddEnv(...res));
13
+ router.post("/delete", toDTO(EnvPrimarySchema), (...res) => controller.handleDeleteEnv(...res));
14
+ router.post("/update", toDTO(EnvUpdateSchema), (...res) => controller.handleUpdateEnv(...res));
15
+ router.post("/start", toDTO(EnvPrimarySchema), (...res) => controller.handleStartServer(...res));
16
+ router.post("/stop", toDTO(EnvPrimarySchema), (...res) => controller.handleStopServer(...res));
17
+ return router;
18
+ };
19
+ const createDevServerRoutes = (controller) => {
20
+ const router = Router();
21
+ router.get("/list", (...res) => controller.handleGetDevServerList(...res));
22
+ router.post("/add", toDTO(DevServerCreateSchema), (...res) => controller.handleCreateDevServer(...res));
23
+ router.put("/update", toDTO(DevServerUpdateSchema), (...res) => controller.handleUpdateDevServer(...res));
24
+ router.delete("/", toDTO(DevServerDeleteSchema), (...res) => controller.handleDeleteDevServer(...res));
25
+ return router;
26
+ };
27
+ const createRouteRuleRoutes = (controller) => {
28
+ const router = Router();
29
+ router.get("/list/:envId", (...res) => controller.handleGetList(...res));
30
+ router.post("/add", toDTO(RouteRuleCreateSchema), (...res) => controller.handleAdd(...res));
31
+ router.post("/update", toDTO(RouteRuleUpdateSchema), (...res) => controller.handleUpdate(...res));
32
+ router.post("/delete", toDTO(RouteRuleDeleteSchema), (...res) => controller.handleDelete(...res));
33
+ return router;
34
+ };
35
+ const createPasswordRoutes = (controller) => {
36
+ const router = Router();
37
+ router.get("/list/:envId", (...res) => controller.handleGetList(...res));
38
+ router.post("/add", toDTO(PasswordCreateSchema), (...res) => controller.handleAdd(...res));
39
+ router.post("/update", toDTO(PasswordUpdateSchema), (...res) => controller.handleUpdate(...res));
40
+ router.post("/delete", toDTO(PasswordDeleteSchema), (...res) => controller.handleDelete(...res));
41
+ return router;
42
+ };
43
+ const createCommonRoutes = () => {
44
+ const router = Router();
45
+ router.get("/are-you-ok", (req, res) => res.success({}, "I'm ok!"));
46
+ router.get("/clear-proxy-cookie", (req, res) => {
47
+ const cookies = req.headers.cookie;
48
+ if (cookies) {
49
+ const cookieArr = libCookie.parse(cookies);
50
+ Object.keys(cookieArr).forEach((cookieName) => {
51
+ if (cookieName.endsWith(getConfig().cookieSuffix)) {
52
+ res.setHeader("Set-Cookie", `${cookieName}=; max-age=0; path=/`);
53
+ }
54
+ });
55
+ }
56
+ res.success();
57
+ });
58
+ return router;
59
+ };
60
+ // 2. 整合所有路由并导出
61
+ export const createRouter = () => {
62
+ const rootRouter = Router();
63
+ // 全局中间件(原 ManageRouter 中的通用中间件)
64
+ rootRouter.use(express.json());
65
+ // 依赖注入
66
+ const container = Container.getInstance();
67
+ const envController = container.get("envController");
68
+ const devServerController = container.get("devServerController");
69
+ const routeRuleController = container.get("routeRuleController");
70
+ const passwordController = container.get("passwordController");
71
+ // 挂载模块路由
72
+ rootRouter.use("/env", createEnvRoutes(envController));
73
+ rootRouter.use("/server", createDevServerRoutes(devServerController));
74
+ rootRouter.use("/route-rule", createRouteRuleRoutes(routeRuleController));
75
+ rootRouter.use("/password", createPasswordRoutes(passwordController));
76
+ rootRouter.use("/", createCommonRoutes());
77
+ return rootRouter;
78
+ };
@@ -0,0 +1,189 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { DevServerQuerySchema, } from "../types/index.js";
3
+ import { AppError } from "../utils/errors.js";
4
+ import { devServerLogger } from "../utils/logger.js";
5
+ /**
6
+ * 开发服务器服务类
7
+ * 负责处理开发服务器(devServer)的核心业务逻辑,包括增删改查、关联环境校验等操作
8
+ * 依赖开发服务器仓库(DevServerRepo)和环境仓库(EnvRepo)实现数据持久化与关联业务处理
9
+ */
10
+ class DevServerService {
11
+ /**
12
+ * 构造函数 - 通过依赖注入初始化仓库实例
13
+ * @param devServerRepo - 开发服务器仓库实例,用于devServer数据的持久化操作
14
+ * @param envRepo - 环境仓库实例,用于关联环境的校验与操作
15
+ */
16
+ constructor(devServerRepo, envRepo) {
17
+ this.devServerRepo = devServerRepo;
18
+ this.envRepo = envRepo;
19
+ }
20
+ /**
21
+ * 添加新开发服务器
22
+ * 1. 校验输入参数合法性 2. 检查服务器URL是否已存在 3. 生成唯一ID 4. 保存新服务器
23
+ * @param devServerItem - 待添加的开发服务器信息(不含ID)
24
+ * @returns {void}
25
+ * @throws {AppError} 当输入参数不合法时抛出
26
+ * @throws {AppError} 当服务器URL已存在时抛出
27
+ */
28
+ handleAddDevServer(devServerItem) {
29
+ const validData = devServerItem;
30
+ devServerLogger.info(validData, "准备添加开发服务器");
31
+ // 检查服务器URL是否已存在(URL作为唯一标识)
32
+ const existingServer = this.devServerRepo.findOneByUrl(validData.devServerUrl);
33
+ if (existingServer) {
34
+ throw new AppError(`添加失败,开发服务器【${existingServer.devServerUrl}】已存在`);
35
+ }
36
+ // 生成唯一ID并组装完整服务器信息(默认状态为未关联)
37
+ const newDevServer = {
38
+ ...validData,
39
+ id: uuidv4(),
40
+ };
41
+ // 保存开发服务器
42
+ this.devServerRepo.addDevServer(newDevServer);
43
+ devServerLogger.info(newDevServer, "开发服务器添加成功");
44
+ }
45
+ /**
46
+ * 删除指定开发服务器
47
+ * 1. 校验输入参数 2. 检查服务器是否存在 3. 检查是否关联环境(关联则禁止删除) 4. 执行删除
48
+ * @param devServerItem - 包含待删除服务器ID的对象
49
+ * @returns {void}
50
+ * @throws {AppError} 当输入参数不合法时抛出
51
+ * @throws {AppError} 当服务器不存在时抛出
52
+ * @throws {AppError} 当服务器已关联环境时抛出(避免数据关联异常)
53
+ */
54
+ handleDeleteDevServer(devServerItem) {
55
+ // 参数校验
56
+ const { id } = devServerItem;
57
+ devServerLogger.info(devServerItem, "准备删除开发服务器");
58
+ // 检查服务器是否存在
59
+ const existingServer = this.devServerRepo.findOneById(devServerItem);
60
+ if (!existingServer) {
61
+ throw new AppError(`删除失败,开发服务器【${id}】不存在`);
62
+ }
63
+ // 检查服务器是否已关联环境(关联环境时禁止删除,避免环境依赖异常)
64
+ const linkedEnvs = this.envRepo.findEnvsByDevServerId(id);
65
+ if (linkedEnvs.length > 0) {
66
+ throw new AppError(`删除失败,开发服务器【${existingServer.name}】已关联 ${linkedEnvs.length} 个环境,请先解除关联`);
67
+ }
68
+ // 执行删除
69
+ this.devServerRepo.deleteDevServer(devServerItem);
70
+ devServerLogger.info(devServerItem, "开发服务器删除成功");
71
+ }
72
+ /**
73
+ * 更新开发服务器信息
74
+ * 1. 校验输入参数 2. 检查服务器是否存在 3. 若更新URL则校验唯一性 4. 执行更新(同步修订号)
75
+ * @param devServerItem - 包含待更新服务器ID及字段的对象
76
+ * @returns {void}
77
+ * @throws {AppError} 当输入参数不合法时抛出
78
+ * @throws {AppError} 当服务器不存在时抛出
79
+ * @throws {AppError} 当更新的URL已存在时抛出
80
+ */
81
+ handleUpdateDevServer(devServerItem) {
82
+ // 参数校验
83
+ const { id } = devServerItem;
84
+ devServerLogger.info(devServerItem, "准备更新开发服务器");
85
+ // 检查服务器是否存在
86
+ const existingServer = this.devServerRepo.findOneById({ id });
87
+ if (!existingServer) {
88
+ throw new AppError(`更新失败,开发服务器【${id}】不存在`);
89
+ }
90
+ const validData = devServerItem;
91
+ // 若更新了URL,需校验新URL的唯一性
92
+ if (validData.devServerUrl &&
93
+ validData.devServerUrl !== existingServer.devServerUrl) {
94
+ const conflictServer = this.devServerRepo.findOneByUrl(validData.devServerUrl);
95
+ if (conflictServer) {
96
+ throw new AppError(`更新失败,URL【${validData.devServerUrl}】已被其他开发服务器使用`);
97
+ }
98
+ }
99
+ // 组装更新数据(同步元数据修订号)
100
+ const updatedServer = {
101
+ ...existingServer,
102
+ ...validData,
103
+ };
104
+ // 执行更新
105
+ this.devServerRepo.update(updatedServer);
106
+ devServerLogger.info(updatedServer, "开发服务器更新成功");
107
+ }
108
+ /**
109
+ * 获取所有开发服务器列表
110
+ * @returns {DevServerModel[]} 开发服务器信息数组
111
+ */
112
+ handleGetList() {
113
+ const list = this.devServerRepo.getAll();
114
+ devServerLogger.info(`查询到${list.length}个开发服务器`);
115
+ return list;
116
+ }
117
+ /**
118
+ * 根据ID查询单个开发服务器
119
+ * @param devServer - 包含待查询服务器ID的对象
120
+ * @returns {DevServerModel | undefined} 匹配的开发服务器信息(不存在则返回undefined)
121
+ * @throws {AppError} 当输入参数不合法时抛出
122
+ */
123
+ findOneById(devServer) {
124
+ // 参数校验
125
+ const validationResult = DevServerQuerySchema.safeParse(devServer);
126
+ if (!validationResult.success) {
127
+ throw new AppError(`查询开发服务器失败:参数不合法 - ${JSON.stringify(validationResult.error.issues)}`);
128
+ }
129
+ const server = this.devServerRepo.findOneById(validationResult.data);
130
+ devServerLogger.info(`查询开发服务器【${devServer.id}】结果:${server ? "存在" : "不存在"}`);
131
+ return server;
132
+ }
133
+ /**
134
+ * 关联开发服务器到环境(更新服务器状态为"linked")
135
+ * 1. 校验参数 2. 检查服务器/环境是否存在 3. 更新服务器关联状态
136
+ * @param devServerId - 开发服务器ID
137
+ * @param envId - 关联的环境ID
138
+ * @returns {void}
139
+ * @throws {AppError} 当参数不合法或资源不存在时抛出
140
+ */
141
+ handleLinkToEnv(devServerId, envId) {
142
+ // 校验ID格式(简化校验,实际可通过Schema补充)
143
+ if (!devServerId || !envId) {
144
+ throw new AppError("关联失败:开发服务器ID和环境ID不能为空");
145
+ }
146
+ devServerLogger.info(`准备关联开发服务器【${devServerId}】到环境【${envId}】`);
147
+ // 检查开发服务器是否存在
148
+ const devServer = this.devServerRepo.findOneById({ id: devServerId });
149
+ if (!devServer) {
150
+ throw new AppError(`关联失败,开发服务器【${devServerId}】不存在`);
151
+ }
152
+ // 检查环境是否存在
153
+ const env = this.envRepo.findOneById(envId);
154
+ if (!env) {
155
+ throw new AppError(`关联失败,环境【${envId}】不存在`);
156
+ }
157
+ // 更新开发服务器状态为"已关联"
158
+ // this.handleUpdateDevServer({
159
+ // id: devServerId,
160
+ // status: "linked",
161
+ // linkedEnvId: envId, // 记录关联的环境ID(需确保Schema支持该字段)
162
+ // });
163
+ devServerLogger.info(`开发服务器【${devServerId}】已成功关联到环境【${envId}】`);
164
+ }
165
+ /**
166
+ * 解除开发服务器与环境的关联(更新服务器状态为"unlinked")
167
+ * @param devServerId - 开发服务器ID
168
+ * @returns {void}
169
+ * @throws {AppError} 当服务器不存在或参数不合法时抛出
170
+ */
171
+ handleUnlinkFromEnv(devServerId) {
172
+ if (!devServerId) {
173
+ throw new AppError("解除关联失败:开发服务器ID不能为空");
174
+ }
175
+ devServerLogger.info(`准备解除开发服务器【${devServerId}】的环境关联`);
176
+ const devServer = this.devServerRepo.findOneById({ id: devServerId });
177
+ if (!devServer) {
178
+ throw new AppError(`解除关联失败,开发服务器【${devServerId}】不存在`);
179
+ }
180
+ // 更新服务器状态为"未关联",清空关联的环境ID
181
+ // this.handleUpdateDevServer({
182
+ // id: devServerId,
183
+ // status: "unlinked",
184
+ // linkedEnvId: undefined,
185
+ // });
186
+ devServerLogger.info(`开发服务器【${devServerId}】已解除环境关联`);
187
+ }
188
+ }
189
+ export { DevServerService };