Compare commits

...

306 Commits

Author SHA1 Message Date
刘祥超
66bc60a47c 更新SQL 2022-11-28 18:17:25 +08:00
刘祥超
1c048da1f0 节点版本号修改为0.5.9 2022-11-28 18:14:17 +08:00
刘祥超
da8aa20f83 版本号修改为0.5.9 2022-11-28 15:58:40 +08:00
刘祥超
14315923d8 删除不必要的文件 2022-11-26 19:49:05 +08:00
刘祥超
3d3228fe96 修复删除节点时不能自动同步DNS的问题 2022-11-26 19:02:08 +08:00
刘祥超
173ac5a8aa 优化健康检查连接超时时间 2022-11-26 18:11:35 +08:00
刘祥超
e9c5d7e7cf 健康检查没有开启上下线的时候也发送节点状态变更通知 2022-11-26 15:54:16 +08:00
刘祥超
194127dce9 提交SQL 2022-11-26 15:39:16 +08:00
刘祥超
86cb7e9d41 缓存任务:校验缓存Key时支持域名中含有星号通配符 2022-11-26 15:03:24 +08:00
刘祥超
b2774de6a2 健康检查时只有开启了自动下线才发送上线通知 2022-11-25 15:48:57 +08:00
刘祥超
3a4722b701 用户可以使用管理员设置的公用线路 2022-11-24 17:19:51 +08:00
刘祥超
028aea4e3d 完全没有设置过SSH登录参数的节点也可以远程安装 2022-11-23 19:39:35 +08:00
刘祥超
c3dd97a7c1 增加数据库版本号 2022-11-22 14:32:04 +08:00
刘祥超
d527fcdd78 在节点详情中显示API节点地址 2022-11-21 21:09:06 +08:00
刘祥超
d6f311e057 节点可以单独设置所使用的API节点地址 2022-11-21 19:55:01 +08:00
刘祥超
991e08fa71 远程安装节点时uname读取失败时自动重试 2022-11-18 15:57:06 +08:00
刘祥超
06b44dc101 提升ssh sudo安装的稳定性 2022-11-18 15:44:53 +08:00
刘祥超
2d94b994fa DNS API支持查询多个同名记录/优化ACME申请 2022-11-17 17:33:59 +08:00
刘祥超
c036186dde 优化代码 2022-11-17 10:01:07 +08:00
刘祥超
bc2ad13037 集群被删除或者不可用时,健康检查时不提示错误 2022-11-16 14:10:03 +08:00
刘祥超
bfa04856aa 同步域名解析时自动剔除相同的节点A记录 2022-11-16 09:01:41 +08:00
刘祥超
feb1068441 边缘节点支持设置多个缓存目录 2022-11-15 20:35:59 +08:00
刘祥超
181a4d05b0 生成账单时只处理用户ID大于0的记录 2022-11-15 16:34:33 +08:00
刘祥超
c449265e05 cloudflare域名单页读取数从20修改为50,修复测试用例 2022-11-14 21:15:10 +08:00
刘祥超
e778616b5c 修复cloudflare域名只能读取第一页的问题 2022-11-14 21:04:13 +08:00
刘祥超
dad5be2670 缩短节点运行日志队列长度 2022-11-14 16:42:18 +08:00
刘祥超
414afd17b8 在写入API节点日志时尽量避免重复内容 2022-11-14 16:38:11 +08:00
刘祥超
86a806bca2 修复默认黑白名单不是全局的问题 2022-11-13 10:31:42 +08:00
刘祥超
faed7420a7 安装过程显示更详细内容 2022-11-11 21:48:00 +08:00
刘祥超
ae14ff4f9f 优化操作系统和浏览器统计相关程序 2022-11-11 17:48:30 +08:00
刘祥超
99e1658fdf 默认创建的IP名单设置为全局有效 2022-11-11 16:12:15 +08:00
刘祥超
e79264eefc 检查检查时先检查集群是否已经部署服务,如果没有部署服务,则直接跳过 2022-11-10 12:44:12 +08:00
刘祥超
dd0e26e7bc 访问日志搜索method:XXX和requestMethod:XXX方法 2022-11-09 11:58:50 +08:00
刘祥超
342c4bfbc2 NSRecords增加mxPriority字段 2022-11-06 20:34:57 +08:00
刘祥超
7d2b8fd4c8 保持导出SQL的稳定性 2022-11-06 19:26:23 +08:00
刘祥超
6426622992 优化代码 2022-11-06 19:13:47 +08:00
刘祥超
3d154411de 将指标统计导入到数据库时忽略 transaction deadlock错误 2022-11-06 16:03:33 +08:00
刘祥超
241f41e900 使用版本号来读取节点任务,提升任务同步稳定性 2022-11-06 12:03:11 +08:00
刘祥超
6c3d24d895 修复域名解析--DNS服务商--同步域名时无法解析集群额外附加的CNAME的问题 2022-11-05 19:25:35 +08:00
刘祥超
b2a0204f6b 优化代码 2022-11-05 14:39:40 +08:00
刘祥超
a9d71652b7 带宽相关数据增加百分位 2022-11-04 20:30:53 +08:00
刘祥超
1b6bfb33d6 用户看板增加带宽百分位 2022-11-04 17:39:53 +08:00
刘祥超
aec0d8d681 优化代码 2022-11-04 15:23:02 +08:00
刘祥超
e38871b52d 优化代码 2022-11-03 15:17:59 +08:00
刘祥超
c1b4551dd1 提升数据库升级速度 2022-11-02 16:58:09 +08:00
刘祥超
d479140f87 版本修改为0.5.8 2022-11-02 15:11:27 +08:00
刘祥超
881bb89ac0 准备0.5.7 2022-11-02 10:54:07 +08:00
刘祥超
0e4158f600 优化代码 2022-11-02 10:11:25 +08:00
刘祥超
57bbd77ae5 版本号修改为0.5.7 2022-10-31 19:13:29 +08:00
刘祥超
6906b3094b 增加edge-api reset命令 2022-10-30 20:07:16 +08:00
刘祥超
aec28b5087 集群中自动设置CNAME记录如果已经存在,则跳过 2022-10-28 15:27:52 +08:00
刘祥超
b59ed1f73e 测试环境下申请ACME证书时打印调试日志 2022-10-27 20:12:00 +08:00
刘祥超
2a83f61bdd 优化代码 2022-10-27 18:12:15 +08:00
刘祥超
4f21d60ca4 更新SQL 2022-10-27 11:18:49 +08:00
刘祥超
0a6111b2e5 自动检测本地数据库磁盘是否已满,如果已满,则不再写入访问日志 2022-10-27 10:27:47 +08:00
刘祥超
4b425e1698 节点SSH登录自动使用集群设置 2022-10-26 19:25:07 +08:00
刘祥超
bee7da807b 节点设置中增加“通过IP名单”选项 2022-10-26 10:41:53 +08:00
刘祥超
a906a7db06 提升流量账单精度 2022-10-26 09:38:13 +08:00
刘祥超
ea62cf0ff7 增加用户账单中流量精度 2022-10-25 19:14:31 +08:00
刘祥超
72e0c55f5d IP名单支持模糊查询 2022-10-25 10:15:32 +08:00
刘祥超
3a88f23181 修复域名解析--集群中单节点多IP时无法修改IP的Bug 2022-10-24 16:34:07 +08:00
刘祥超
967c9080fb 更新用户服务可用状态时同时返回状态 2022-10-23 20:12:28 +08:00
刘祥超
f44b9434ad 更新相关库 2022-10-23 19:57:43 +08:00
刘祥超
e21a3c5f8c 节点所属集群删除后,不再接收API请求 2022-10-23 19:56:58 +08:00
刘祥超
88dae56b6c 优化用户服务整体启用和禁用 2022-10-23 16:22:20 +08:00
刘祥超
40c3475306 提升小数数字精度 2022-10-23 11:54:00 +08:00
刘祥超
318c8dd566 优化小数数字格式化 2022-10-23 09:25:20 +08:00
刘祥超
a5f30b1573 修复默认生成的集群没有开启节点和服务DNS同步的Bug 2022-10-23 09:02:45 +08:00
刘祥超
a8ec959c70 优化小数格式化 2022-10-22 10:22:45 +08:00
刘祥超
c393b2f480 更新SQL 2022-10-22 10:22:31 +08:00
刘祥超
548f56f8f0 用户账单增加字段/优化代码 2022-10-21 15:45:22 +08:00
刘祥超
9aa71365b9 安装生成的默认集群默认的DNS设置包含节点和服务自动同步 2022-10-21 14:48:59 +08:00
刘祥超
292fb72a26 优化代码 2022-10-20 22:59:17 +08:00
刘祥超
e5315c3b8d DNSPod和Alidns记录信息增加缓存 2022-10-20 21:47:21 +08:00
刘祥超
a4dd7bb75a 优化DNSPod和Alidns相关代码 2022-10-20 18:06:27 +08:00
刘祥超
53ef0f3fb2 修复访问日志采样率可能会被放大的Bug 2022-10-20 16:12:35 +08:00
刘祥超
6af8bff802 增加操作节点区域相关接口 2022-10-20 15:11:57 +08:00
刘祥超
d849f7440a 更新SQL 2022-10-20 10:42:06 +08:00
刘祥超
425c0ec44c 增加流量包相关表 2022-10-20 10:24:36 +08:00
刘祥超
aad0b01581 修复在域名同步后,无法看到线路等问题的Bug 2022-10-19 19:56:36 +08:00
刘祥超
bc2c3dfa0b 查询DNSPod域名和记录时每页尺寸从100增加到3000 2022-10-17 19:49:36 +08:00
刘祥超
0fe76430c6 实现用户计费方式相关多个接口 2022-10-15 19:16:08 +08:00
刘祥超
979ff4c44e 优化代码 2022-10-14 16:16:42 +08:00
刘祥超
b0b6b5984f 优化代码/删除不需要的代码 2022-10-14 10:03:29 +08:00
刘祥超
5d4da6cccb 查询带宽统计时,自动调整开始和结束日期顺序 2022-10-04 08:55:03 +08:00
刘祥超
912ffa062f 更新SQL 2022-10-03 20:54:18 +08:00
刘祥超
1db4661c75 增加多个服务流量、带宽统计API 2022-10-03 19:28:03 +08:00
刘祥超
f7dd9e3f39 创建通知消息的时候限制内容长度不超过1024长度 2022-10-03 16:07:14 +08:00
刘祥超
0cc74b920e 版本修改为0.5.6 2022-10-01 08:49:32 +08:00
刘祥超
51c3807d01 删除不必要的文件 2022-10-01 07:14:28 +08:00
刘祥超
c2c42ca2b7 优化服务列表返回速度 2022-09-30 13:50:19 +08:00
刘祥超
2a6db6ebfe 边缘节点远程安装文件最小化(从16.xM减少到2.xM) 2022-09-30 10:34:32 +08:00
刘祥超
30d8edbdcf 执行uname和systemctl时增加命令完整路径 2022-09-30 09:40:38 +08:00
刘祥超
177afafe12 完善订单相关表 2022-09-29 10:22:17 +08:00
刘祥超
98765b6e2a 版本调整为v0.5.5 2022-09-28 18:56:47 +08:00
刘祥超
e4e0aab010 阶段性提交 2022-09-28 17:38:52 +08:00
刘祥超
ed87b4e2a9 用户节点版本改为0.5.4 2022-09-28 08:56:40 +08:00
刘祥超
337eb36d25 DNS版本改为0.2.8 2022-09-28 08:16:57 +08:00
刘祥超
c44e40d72d systemd服务增加BEGIN INIT INFO 2022-09-28 08:16:49 +08:00
刘祥超
2e8ba831a1 DNS版本修改为0.2.7.1 2022-09-27 08:06:16 +08:00
刘祥超
a706c2a5a5 将版本修改为0.5.4 2022-09-26 15:17:00 +08:00
刘祥超
093826222a 将版本修改为v0.5.3.1 2022-09-26 12:16:44 +08:00
刘祥超
0d7b487afc 修复开源版本无法编译的问题 2022-09-26 12:16:20 +08:00
刘祥超
8de17b6d9c 只有在数据库用户是root时才执行某些命令 2022-09-26 12:16:08 +08:00
刘祥超
49d217a883 提交EdgeDNS API相关代码 2022-09-26 11:51:45 +08:00
刘祥超
4827555899 启动过程增加多个提示 2022-09-26 11:00:58 +08:00
刘祥超
41f3825ee2 使用并发队列安装和升级数据表/启动时自动调整MySQL变量 2022-09-25 20:34:19 +08:00
刘祥超
096aa153ab 创建缓存策略时默认缓存条件自动支持206 Partial Content,并使用简单条件设置 2022-09-25 10:37:05 +08:00
刘祥超
8f8b611ac1 查询5分钟流量接口增加timeFrom和timeTo参数 2022-09-24 18:55:33 +08:00
刘祥超
08b1c038f1 服务流量接口增加5分钟查询接口 2022-09-24 18:36:27 +08:00
刘祥超
934b10a254 REST 接口接收内容为空时,默认为 {} 2022-09-24 18:35:29 +08:00
刘祥超
4947f13416 更新SQL 2022-09-24 18:34:46 +08:00
刘祥超
84198d5948 优化智能DNS相关代码 2022-09-24 14:07:19 +08:00
刘祥超
d4a04bc798 智能DNS支持应答模式配置 2022-09-23 19:01:18 +08:00
刘祥超
c94b3c26c1 优化代码 2022-09-23 15:25:51 +08:00
刘祥超
5655f89ba6 优化代码 2022-09-23 09:28:19 +08:00
刘祥超
c568ad3e9a 查看访问日志列表时,不查询requestBody和responseBody,防止内容过大 2022-09-22 16:47:36 +08:00
刘祥超
d2f532447d 同步防盗链功能 2022-09-22 16:41:02 +08:00
刘祥超
ec97feab28 增加防盗链功能 2022-09-22 16:33:46 +08:00
刘祥超
316cd36f71 删除集群的时候,同时删除对应的域名解析/集群切换二级域名先删除再添加新的域名解析 2022-09-22 14:09:28 +08:00
刘祥超
702a0f1ecf DNS访问日志创建表已经存在时不提示错误 2022-09-21 20:41:21 +08:00
刘祥超
2d2b7b7bff 优化代码 2022-09-21 15:09:39 +08:00
刘祥超
6f8c5a8e99 修复按日带宽峰值查询错误的Bug 2022-09-20 13:57:45 +08:00
刘祥超
afb35953e7 DNS集群增加时区选项 2022-09-19 17:00:39 +08:00
刘祥超
2a1f78a440 修复一处带宽计算错误 2022-09-19 14:39:22 +08:00
刘祥超
fb084a9f48 优化代码 2022-09-18 12:39:17 +08:00
刘祥超
e7f620d28f 同步域名解析时不再强制要求修复节点问题 2022-09-18 10:41:29 +08:00
刘祥超
2f64d713e8 优化代码/DNS域名增加分页 2022-09-18 10:23:04 +08:00
刘祥超
86bf316468 可以设置是否自动安装nftables 2022-09-17 21:05:18 +08:00
刘祥超
bf320271d4 修复DNSPod只能取前100个域名的问题 2022-09-17 17:16:30 +08:00
刘祥超
150a63fe98 用户端刷新预热缓存超过5分钟自动认为已完成 2022-09-17 16:50:03 +08:00
刘祥超
1d1ff11eb7 优化接口权限 2022-09-17 16:07:37 +08:00
刘祥超
55eecce416 集群增加是否远程启动选项 2022-09-17 15:11:34 +08:00
刘祥超
5f822062da 检查域名是否存在时同时检查泛域名 2022-09-17 11:38:47 +08:00
刘祥超
ee2c253e7d 节点离线时尝试自动通过API节点远程启动 2022-09-17 10:18:00 +08:00
刘祥超
5b0adb4b84 创建集群的时候初始化全局服务配置 2022-09-16 19:34:19 +08:00
刘祥超
8c72540a6e 集群设置中增加服务设置 2022-09-16 18:42:14 +08:00
刘祥超
7f811997a9 证书申请任务不区分管理员 2022-09-16 15:34:03 +08:00
刘祥超
ca94e31451 启动时自动设置binlog过期时间 2022-09-16 10:39:17 +08:00
刘祥超
320d381bd9 集群增加自动同步时钟选项 2022-09-15 15:56:50 +08:00
刘祥超
c8057457cc 更新SQL 2022-09-15 11:48:51 +08:00
刘祥超
c78c4d58ff 用户系统可以切换CDN和智能DNS 2022-09-13 19:04:31 +08:00
刘祥超
d1f8e7e757 增加DNS套餐相关数据 2022-09-13 10:49:58 +08:00
刘祥超
5386b30eba 用户节点版本修改为0.5.0 2022-09-13 10:49:17 +08:00
刘祥超
8ceb1334cd 调整GRPC参数 2022-09-12 22:03:06 +08:00
刘祥超
023e563de1 调整GRPC参数 2022-09-12 22:00:12 +08:00
刘祥超
39e6d11d71 优化代码 2022-09-11 14:41:06 +08:00
刘祥超
a5a9117ce0 增强系统配置接口的权限检查 2022-09-10 17:00:54 +08:00
刘祥超
e95b0bd9a6 实现DNS域名验证 2022-09-10 16:13:21 +08:00
刘祥超
9699a9adad 自动调整写入单次写入访问日志数量 2022-09-09 21:15:03 +08:00
刘祥超
67729abd13 修复多个日志数据库节点写入NS日志时分布不平均的问题 2022-09-09 21:06:43 +08:00
刘祥超
4c7ebce97a 修复多个日志数据库节点分布不平均的问题 2022-09-09 20:37:45 +08:00
刘祥超
44e7ce9f79 自动升级NS域名状态为verified 2022-09-09 15:25:32 +08:00
刘祥超
3468fcf8a6 DNS版本修改为0.2.7 2022-09-09 14:29:29 +08:00
刘祥超
3522c22a28 修复无法一个常量错误 2022-09-09 10:39:28 +08:00
刘祥超
333a9c6611 修改管理界面设置中的时区时同时也会应用到API节点/API节点默认时区为Asia/Shanghai 2022-09-09 10:28:44 +08:00
刘祥超
e3426a84e2 域名解析增加EdgeDNS API 2022-09-08 19:36:32 +08:00
刘祥超
a25b0e6c9d 创建集群的时候可以设置DNS记录的默认TTL 2022-09-08 11:02:26 +08:00
刘祥超
c271cadabd 计算区域监控终端低版本数量时忽略已禁用的终端 2022-09-08 10:14:02 +08:00
刘祥超
428bb7eb0f 访问日志支持中文域名搜索 2022-09-07 17:07:46 +08:00
刘祥超
6ae9f447b6 增加启动过程标记 2022-09-07 15:57:31 +08:00
刘祥超
7cc503b698 将版本修改为0.5.3 2022-09-06 09:23:28 +08:00
刘祥超
c66e28cb9d 减少带宽统计显示的空档期 2022-09-05 16:04:34 +08:00
刘祥超
e5109b24d4 版本修改为0.5.2.1 2022-09-05 16:02:55 +08:00
刘祥超
695b8482de 将版本修改为0.5.3 2022-09-05 11:03:12 +08:00
刘祥超
d0b908bcaa 自动添加firewalld使用异步 2022-09-04 06:36:22 +08:00
刘祥超
3de25d4fe1 优化代码 2022-09-03 22:23:16 +08:00
刘祥超
07194855bf 优化代码 2022-09-03 22:03:22 +08:00
刘祥超
d0f1eb13ee 优化节点活跃检测机制 2022-09-03 12:43:06 +08:00
刘祥超
a0930bfd74 远程安装节点出错时打印stderr 2022-08-30 11:40:01 +08:00
刘祥超
08cff8affc 可以通过用户API修改鉴权 2022-08-30 11:23:35 +08:00
刘祥超
02132e9262 用户系统也可以申请ACME证书 2022-08-28 20:02:13 +08:00
刘祥超
61b6a49885 增加修改全体用户功能API 2022-08-28 17:01:09 +08:00
刘祥超
896e54ebe8 提供按小时、按天查询带宽峰值的API 2022-08-28 15:56:16 +08:00
刘祥超
1b36bad60a 指标统计使用事务 2022-08-27 18:50:42 +08:00
刘祥超
fc14800d70 服务列表带宽使用新的算法 2022-08-27 18:39:00 +08:00
刘祥超
fa61f277e4 服务访问日志改成通过事务写入,以提升写入速度 2022-08-27 14:57:47 +08:00
刘祥超
9117309472 可以修改服务的CNAME 2022-08-26 19:51:21 +08:00
刘祥超
6bb2977d59 Ln节点可以指定访问IP 2022-08-25 20:37:10 +08:00
刘祥超
df9dce76cb 集群DNS设置中增加”包含Ln节点“选项 2022-08-25 19:18:30 +08:00
刘祥超
4cb9c85a1c 节点运行日志可以按照节点ID设置为已读 2022-08-25 18:26:52 +08:00
刘祥超
f4f5389ffb 请求限制API支持用户调用 2022-08-25 15:35:55 +08:00
刘祥超
5d336eb77d 优化代码 2022-08-23 21:42:05 +08:00
刘祥超
c552eb3b0e IP库增加制品管理/统计中相关区域名称可以显示别名 2022-08-23 19:40:17 +08:00
刘祥超
455952e9e4 提交SQL 2022-08-22 15:12:20 +08:00
刘祥超
7132401c7f NS节点基本的DDoS防护 2022-08-22 15:11:22 +08:00
刘祥超
a4dddfb139 优化代码 2022-08-22 11:02:16 +08:00
刘祥超
7ef32bad97 IP库改为手动初始化 2022-08-21 23:09:59 +08:00
刘祥超
732513a644 用户节点版本修改为0.4.1 2022-08-21 20:50:00 +08:00
刘祥超
756cf4a9ae 初步完成新版IP库 2022-08-21 20:38:34 +08:00
刘祥超
a15a630265 更新SQL 2022-08-20 19:57:25 +08:00
刘祥超
3fab1b8294 DNS节点版本号改为0.2.6 2022-08-20 15:27:02 +08:00
刘祥超
215635f429 版本修改为0.5.2 2022-08-17 18:58:20 +08:00
刘祥超
dbb1ae180b 版本修改为0.5.1 2022-08-15 19:38:40 +08:00
刘祥超
e8d4d01d85 改进一处日志 2022-08-15 15:17:09 +08:00
刘祥超
6593989a84 修复日志内容可能过长而无法存入数据库的问题 2022-08-15 15:05:47 +08:00
刘祥超
004e640321 修复升级数据库时主键可能冲突的问题 2022-08-15 00:02:38 +08:00
刘祥超
7ad315ae4b IP库管理阶段性提交(未完成) 2022-08-14 20:03:01 +08:00
刘祥超
ba938e5361 新版IP库管理阶段性提交(未完成) 2022-08-13 23:55:48 +08:00
刘祥超
9ddf02a0e6 更新TeaGo 2022-08-11 11:52:35 +08:00
刘祥超
ebcbd5690d 删除不必要的文件 2022-08-09 18:30:23 +08:00
刘祥超
bbca766fa4 删除不必要的文件 2022-08-09 17:35:35 +08:00
刘祥超
99c7819d3a 更新SQL 2022-08-07 19:04:16 +08:00
刘祥超
08bb3e66f8 修改版本号为0.5.0 2022-08-07 19:02:19 +08:00
刘祥超
9159820742 只有发送过离线通知的节点才会发送恢复在线通知 2022-08-07 17:28:54 +08:00
刘祥超
1a565b2ebb 优化代码/启用的日志策略排在最前面 2022-08-07 15:10:05 +08:00
刘祥超
98847c53ea 更新SQL 2022-08-06 20:31:28 +08:00
刘祥超
14bafc8f20 优化代码 2022-08-06 20:28:32 +08:00
刘祥超
58a5bd0092 优化代码 2022-08-05 21:05:34 +08:00
刘祥超
4f1ce52f6a 优化代码 2022-08-05 19:25:31 +08:00
刘祥超
14ba7f6899 优化访问日志策略测试时的失败提示 2022-08-05 19:11:21 +08:00
刘祥超
e582e37c06 优化代码 2022-08-05 14:45:56 +08:00
刘祥超
6a3fa9f0ca 删除不必要的文件 2022-08-05 14:40:42 +08:00
刘祥超
e0a9965fed 简化API 2022-08-04 19:36:25 +08:00
刘祥超
481fa8cd2d 增加查找使用某个证书的NS集群数量的API 2022-08-04 16:25:09 +08:00
刘祥超
95349dc457 允许用户标记上传文件状态 2022-08-04 16:01:07 +08:00
刘祥超
fc839f96d2 优化代码 2022-08-04 15:12:39 +08:00
刘祥超
0414cc02e8 优化代码 2022-08-04 11:41:42 +08:00
刘祥超
b8babaae39 更新SQL 2022-08-03 10:45:09 +08:00
刘祥超
285ce1b312 延长节点执行任务超时时间 2022-08-01 18:57:19 +08:00
刘祥超
c309da81ae 服务带宽API增加按月、按日查询接口 2022-08-01 15:40:57 +08:00
刘祥超
c325fde52b 更新SQL 2022-08-01 11:01:51 +08:00
刘祥超
0f69b45d25 修改用户节点版本号为0.4.0 2022-08-01 11:00:52 +08:00
刘祥超
e02084ba5d 增加用户订单相关表 2022-07-31 19:56:56 +08:00
刘祥超
642b23dbb7 优化代码 2022-07-30 16:28:28 +08:00
刘祥超
b1dc385c87 自动转换用户提交的域名为小写 2022-07-30 16:25:16 +08:00
刘祥超
89a69e3165 删除集群的时候同时删除相关节点运行日志 2022-07-28 09:47:01 +08:00
刘祥超
530954dd6c EdgeDNS:访问日志增加集群和记录类型筛选 2022-07-27 20:19:29 +08:00
刘祥超
33635f7a1b 智能DNS支持自定义端口 2022-07-27 16:56:17 +08:00
刘祥超
8ac964e805 优化远程安装程序 2022-07-27 08:35:15 +08:00
刘祥超
a1519baf0f 远程升级节点时,如果老的文件不存在,则不提示 2022-07-26 20:10:50 +08:00
刘祥超
e6e32a39bb 修改DNS节点版本为0.2.5 2022-07-26 11:15:22 +08:00
刘祥超
d828b7f8a4 修改版本号为0.4.11 2022-07-26 08:57:48 +08:00
刘祥超
07b377c2fb 用户状态发生变化时,同步服务状态 2022-07-24 17:13:05 +08:00
刘祥超
33a3795773 用户增加OTP认证设置 2022-07-24 16:14:56 +08:00
刘祥超
bddb3cae96 用户列表中显示实名审核状态 2022-07-24 11:57:42 +08:00
刘祥超
6a525c2b82 相关接口增加实名认证状态字段 2022-07-24 11:28:59 +08:00
刘祥超
3a137c1c3f 提交SQL 2022-07-24 10:08:40 +08:00
刘祥超
19867568a9 可以重置用户实名认证状态 2022-07-24 10:08:08 +08:00
刘祥超
c2600b911b 优化代码/实现基础的实名认证功能 2022-07-24 09:56:27 +08:00
刘祥超
fe511ae7e5 优化代码 2022-07-22 15:05:30 +08:00
刘祥超
876c631c85 优化代码 2022-07-22 14:35:17 +08:00
刘祥超
a4cc138ef3 API节点自动生成实例代号,用来外界查询多个API节点实例是否为同一个 2022-07-21 19:21:11 +08:00
刘祥超
3f9a7a8a49 升级gosock 2022-07-21 16:30:19 +08:00
刘祥超
09d8ef00c2 API节点状态中增加主程序位置信息 2022-07-21 15:23:08 +08:00
刘祥超
b4f77ddc63 修改版本为v0.4.10 2022-07-20 18:14:27 +08:00
刘祥超
6a9045ba44 提交SQL 2022-07-18 09:13:16 +08:00
刘祥超
bc7ee19962 优化代码 2022-07-17 17:08:28 +08:00
刘祥超
2484cd4ae6 可以通过ID查找服务 2022-07-17 11:43:23 +08:00
刘祥超
57b8a96c6d 增加用户身份认证相关接口 2022-07-17 11:00:14 +08:00
刘祥超
6c8ab7cb94 集群DNS设置增加允许通过CNAME访问网站服务选项 2022-07-14 09:49:09 +08:00
刘祥超
b2c2bd247b 更新SQL 2022-07-12 14:22:20 +08:00
刘祥超
29d9e6db81 域名解析支持DNS.COM(商业版) 2022-07-11 11:51:49 +08:00
刘祥超
24fcb48c75 删除节点的时候同时也删除相关的运行日志 2022-07-07 20:31:12 +08:00
刘祥超
4911198fe7 安全设置中增加禁止搜索引擎、禁止爬虫、允许访问的域名等选项 2022-07-07 19:58:50 +08:00
刘祥超
c2af796992 服务看板增加峰值带宽数据 2022-07-07 17:11:49 +08:00
刘祥超
51c8572e53 用户界面设置中增加流量、带宽相关设置 2022-07-07 12:40:05 +08:00
刘祥超
c15cc6d75c 增加按用户统计带宽/修改其他相关代码 2022-07-07 09:18:08 +08:00
刘祥超
c0a99a4ba3 升级Go版本为1.18 2022-07-06 16:13:52 +08:00
刘祥超
62e26bed5a 增加服务带宽统计 2022-07-05 20:08:58 +08:00
刘祥超
9e68a2915c 增加UAM(5秒盾)集群设置 2022-07-03 22:10:46 +08:00
刘祥超
5b809fda1f 反向代理设置中增加移除回源主机名端口功能 2022-06-30 12:12:41 +08:00
刘祥超
88b782940a 提交SQL 2022-06-29 22:17:32 +08:00
刘祥超
85c596644d 实现源站端口跟随功能 2022-06-29 21:55:57 +08:00
刘祥超
ffa2d884bd 移除386、mips等不常用节点安装文件 2022-06-29 17:11:13 +08:00
刘祥超
813ed18610 优化编译脚本 2022-06-29 16:57:46 +08:00
刘祥超
564b11eae7 优化编译脚本 2022-06-29 14:52:11 +08:00
刘祥超
d2af0307b9 将DNS版本改为0.2.4 2022-06-28 20:31:59 +08:00
刘祥超
4fa6b03238 对于小内存(不大于2G),缩短服务统计导入数据库的时间 2022-06-25 20:57:03 +08:00
刘祥超
4bda78aa8c 限制节点自动升级时的速度和并发数 2022-06-25 20:36:31 +08:00
刘祥超
6002cc96d9 增加修改服务所在分组API 2022-06-25 19:21:46 +08:00
刘祥超
56c09f5be7 指标数据写入改成队列执行 2022-06-22 21:51:44 +08:00
刘祥超
e1fb8e4c74 删除某个IP时更新版本 2022-06-22 18:59:27 +08:00
刘祥超
14f055ce7c fix typo 2022-06-22 14:00:56 +08:00
刘祥超
7b7f2b0a00 修改版本号为v0.4.9 2022-06-20 16:00:27 +08:00
刘祥超
75662586fa 修改相关节点版本 2022-06-20 09:34:40 +08:00
刘祥超
c024331fa0 增加统计服务某日、某月流量API 2022-06-18 14:26:43 +08:00
刘祥超
b01ea79c5c 升级IP名单权限判断逻辑 2022-06-15 19:22:33 +08:00
刘祥超
d59f80b3ec 优化缓存任务Key状态增加执行中状态 2022-06-15 15:18:18 +08:00
刘祥超
b87e48c1f9 城市API增加省份信息 2022-06-14 17:37:42 +08:00
刘祥超
0e7a7f168f 访问日志查询增加requestPath:/hello、proto:HTTP/1.1、scheme:http等语法 2022-06-12 20:30:28 +08:00
刘祥超
2bbc09d3af 优化syslog提示、优化其他代码 2022-06-08 19:55:06 +08:00
刘祥超
97b50fab28 删除EdgePlus 2022-06-08 15:14:30 +08:00
刘祥超
83c867cb65 API节点启动失败后记录相关问题和处理建议 2022-06-08 15:13:24 +08:00
刘祥超
3c4b7ca57b 节点状态中增加时间戳字段 2022-06-06 19:39:08 +08:00
刘祥超
a2f98d2f25 可以设置用户每天执行缓存任务的额度 2022-06-05 21:15:28 +08:00
刘祥超
71677a8638 增加刷新、预热缓存任务管理 2022-06-05 17:13:56 +08:00
刘祥超
95a2187f95 优化API/修改用户集群不影响套餐服务 2022-05-25 11:44:18 +08:00
刘祥超
86bec813bf fix typo 2022-05-22 11:48:31 +08:00
刘祥超
0d7bd6f04e 增加LICENSE和README 2022-05-22 11:36:54 +08:00
刘祥超
d7117209b2 删除不过期IP时不立即删除,以等待节点完成同步 2022-05-21 22:06:04 +08:00
刘祥超
7a340ac68b 新创建WAF时增加默认选项 2022-05-21 18:58:03 +08:00
刘祥超
353b1b4ad1 WAF策略中增加验证码相关定制设置 2022-05-20 22:07:23 +08:00
刘祥超
fdac8beb40 健康检查增加是否记录访问日志选项 2022-05-19 17:14:32 +08:00
刘祥超
f098723a41 实现基础的DDoS防护 2022-05-18 21:02:53 +08:00
刘祥超
6ded627903 增加通过IP删除IP名单中IP参数 2022-05-10 15:11:48 +08:00
刘祥超
ed2d5ee5cc fix typo 2022-05-08 19:33:17 +08:00
刘祥超
c677368482 集群节点列表可以使用“未分组”筛选 2022-05-08 19:33:10 +08:00
刘祥超
51ccd614a7 忽略部分MySQL 1213错误 2022-05-08 00:24:22 +08:00
刘祥超
9be7f61b8c 阿里云DNS增加区域ID 2022-05-07 21:00:07 +08:00
刘祥超
14ad97c937 DNS服务商增加厂家筛选 2022-05-07 20:41:53 +08:00
刘祥超
94609d8ef4 EdgeUser版本升级为0.3.4 2022-05-05 18:56:35 +08:00
刘祥超
e34e38bcb2 Update sql.go 2022-05-04 20:35:38 +08:00
刘祥超
b10d9fe842 路由规则可以单独设置UAM(仅企业版可用) 2022-05-04 20:32:34 +08:00
刘祥超
992e725378 节点增加DNS解析库类型设置 2022-05-04 16:40:34 +08:00
刘祥超
346de7ca7a 版本修改为0.4.8 2022-04-25 11:11:43 +08:00
579 changed files with 22756 additions and 19104 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*_plus.go
*-plus.sh
*-plus.sh
*_plus_test.go

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, LiuXiangChao
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
README.md Normal file
View File

@@ -0,0 +1 @@
GoEdge API节点源码

View File

@@ -1,76 +1,76 @@
#!/usr/bin/env bash
function build() {
ROOT=$(dirname $0)
ROOT=$(dirname "$0")
NAME="edge-api"
DIST=$ROOT/"../dist/${NAME}"
OS=${1}
ARCH=${2}
TAG=${3}
NODE_ARCHITECTS=("amd64" "386" "arm64" "mips64" "mips64le")
NODE_ARCHITECTS=("amd64" "arm64")
if [ -z $OS ]; then
if [ -z "$OS" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z $ARCH ]; then
if [ -z "$ARCH" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z $TAG ]; then
if [ -z "$TAG" ]; then
TAG="community"
fi
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
# build edge-node
NodeVersion=$(lookup-version $ROOT"/../../EdgeNode/internal/const/const.go")
NodeVersion=$(lookup-version "$ROOT""/../../EdgeNode/internal/const/const.go")
echo "building edge-node v${NodeVersion} ..."
EDGE_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeNode/build/build.sh"
if [ ! -f $EDGE_NODE_BUILD_SCRIPT ]; then
if [ ! -f "$EDGE_NODE_BUILD_SCRIPT" ]; then
echo "unable to find edge-node build script 'EdgeNode/build/build.sh'"
exit
fi
cd $ROOT"/../../EdgeNode/build"
cd "$ROOT""/../../EdgeNode/build" || exit
echo "=============================="
for arch in "${NODE_ARCHITECTS[@]}"; do
if [ ! -f $ROOT"/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" ]; then
./build.sh linux $arch $TAG
if [ ! -f "$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" ]; then
./build.sh linux "$arch" $TAG
else
echo "use built node linux/$arch/v${NodeVersion}"
fi
done
echo "=============================="
cd -
cd - || exit
rm -f $ROOT/deploy/*.zip
rm -f "$ROOT"/deploy/*.zip
for arch in "${NODE_ARCHITECTS[@]}"; do
cp $ROOT"/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" $ROOT/deploy/edge-node-linux-${arch}-v${NodeVersion}.zip
cp "$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" "$ROOT"/deploy/edge-node-linux-"${arch}"-v"${NodeVersion}".zip
done
# build edge-dns
if [ "$TAG" = "plus" ]; then
DNS_ROOT=$ROOT"/../../EdgeDNS"
if [ -d $DNS_ROOT ]; then
DNSNodeVersion=$(lookup-version $ROOT"/../../EdgeDNS/internal/const/const.go")
if [ -d "$DNS_ROOT" ]; then
DNSNodeVersion=$(lookup-version "$ROOT""/../../EdgeDNS/internal/const/const.go")
echo "building edge-dns ${DNSNodeVersion} ..."
EDGE_DNS_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeDNS/build/build.sh"
if [ ! -f $EDGE_DNS_NODE_BUILD_SCRIPT ]; then
if [ ! -f "$EDGE_DNS_NODE_BUILD_SCRIPT" ]; then
echo "unable to find edge-dns build script 'EdgeDNS/build/build.sh'"
exit
fi
cd $ROOT"/../../EdgeDNS/build"
cd "$ROOT""/../../EdgeDNS/build" || exit
echo "=============================="
architects=("amd64")
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
./build.sh linux $arch $TAG
./build.sh linux "$arch" $TAG
done
echo "=============================="
cd -
cd - || exit
for arch in "${architects[@]}"; do
cp $ROOT"/../../EdgeDNS/dist/edge-dns-linux-${arch}-v${DNSNodeVersion}.zip" $ROOT/deploy/edge-dns-linux-${arch}-v${DNSNodeVersion}.zip
cp "$ROOT""/../../EdgeDNS/dist/edge-dns-linux-${arch}-v${DNSNodeVersion}.zip" "$ROOT"/deploy/edge-dns-linux-"${arch}"-v"${DNSNodeVersion}".zip
done
fi
fi
@@ -78,48 +78,48 @@ function build() {
# build sql
if [ $TAG = "plus" ]; then
echo "building sql ..."
${ROOT}/sql.sh
"${ROOT}"/sql.sh
fi
# copy files
echo "copying ..."
if [ ! -d $DIST ]; then
mkdir $DIST
mkdir $DIST/bin
mkdir $DIST/configs
mkdir $DIST/logs
if [ ! -d "$DIST" ]; then
mkdir "$DIST"
mkdir "$DIST"/bin
mkdir "$DIST"/configs
mkdir "$DIST"/logs
mkdir "$DIST"/data
fi
cp $ROOT/configs/api.template.yaml $DIST/configs/
cp $ROOT/configs/db.template.yaml $DIST/configs/
cp -R $ROOT/deploy $DIST/
rm -f $dist/deploy/.gitignore
cp -R $ROOT/installers $DIST/
cp -R $ROOT/resources $DIST/
rm -f $DIST/resources/ipdata/ip2region/global_region.csv
rm -f $DIST/resources/ipdata/ip2region/ip.merge.txt
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs/
cp "$ROOT"/configs/db.template.yaml "$DIST"/configs/
cp -R "$ROOT"/deploy "$DIST/"
rm -f "$DIST"/deploy/.gitignore
cp -R "$ROOT"/installers "$DIST"/
# building edge installer
echo "building node installer ..."
architects=("amd64" "386" "arm64")
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
# TODO support arm, mips ...
env GOOS=linux GOARCH=${arch} go build -tags $TAG --ldflags="-s -w" -o $ROOT/installers/edge-installer-helper-linux-${arch} $ROOT/../cmd/installer-helper/main.go
env GOOS=linux GOARCH="${arch}" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$ROOT"/installers/edge-installer-helper-linux-"${arch}" "$ROOT"/../cmd/installer-helper/main.go
done
# building edge dns installer
echo "building dns node installer ..."
architects=("amd64" "386" "arm64")
for arch in "${architects[@]}"; do
# TODO support arm, mips ...
env GOOS=linux GOARCH=${arch} go build -tags $TAG --ldflags="-s -w" -o $ROOT/installers/edge-installer-dns-helper-linux-${arch} $ROOT/../cmd/installer-dns-helper/main.go
done
if [ $TAG = "plus" ]; then
echo "building dns node installer ..."
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
# TODO support arm, mips ...
env GOOS=linux GOARCH="${arch}" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$ROOT"/installers/edge-installer-dns-helper-linux-"${arch}" "$ROOT"/../cmd/installer-dns-helper/main.go
done
fi
# building api node
env GOOS=$OS GOARCH=$ARCH go build -tags $TAG --ldflags="-s -w" -o $DIST/bin/edge-api $ROOT/../cmd/edge-api/main.go
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$DIST"/bin/edge-api "$ROOT"/../cmd/edge-api/main.go
# delete hidden files
find $DIST -name ".DS_Store" -delete
find $DIST -name ".gitignore" -delete
find "$DIST" -name ".DS_Store" -delete
find "$DIST" -name ".gitignore" -delete
echo "zip files"
cd "${DIST}/../" || exit
@@ -135,15 +135,15 @@ function build() {
function lookup-version() {
FILE=$1
VERSION_DATA=$(cat $FILE)
VERSION_DATA=$(cat "$FILE")
re="Version[ ]+=[ ]+\"([0-9.]+)\""
if [[ $VERSION_DATA =~ $re ]]; then
VERSION=${BASH_REMATCH[1]}
echo $VERSION
echo "$VERSION"
else
echo "could not match version"
exit
fi
}
build $1 $2 $3
build "$1" "$2" "$3"

View File

@@ -9,3 +9,7 @@ dbs:
prefix: "edge"
models:
package: internal/web/models
fields:
bool: [ "uamIsOn", "followPort", "requestHostExcludingPort", "autoRemoteStart", "autoInstallNftables" ]

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,10 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/apps"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/nodes"
"github.com/TeaOSLab/EdgeAPI/internal/setup"
@@ -21,14 +23,15 @@ func main() {
if !Tea.IsTesting() {
Tea.Env = "prod"
}
app := apps.NewAppCmd()
var app = apps.NewAppCmd()
app.Version(teaconst.Version)
app.Product(teaconst.ProductName)
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon]")
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon|issues]")
app.On("setup", func() {
setupCmd := setup.NewSetupFromCmd()
var setupCmd = setup.NewSetupFromCmd()
err := setupCmd.Run()
result := maps.Map{}
var result = maps.Map{}
if err != nil {
result["isOk"] = false
result["error"] = err.Error()
@@ -70,6 +73,14 @@ func main() {
}
fmt.Println("done")
})
app.On("reset", func() {
err := configs.ResetAPIConfig()
if err != nil {
fmt.Println("[ERROR]reset failed: " + err.Error())
return
}
fmt.Println("done")
})
app.On("goman", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "goman"})
@@ -122,6 +133,57 @@ func main() {
fmt.Println("prepared statements count: " + types.String(count))
}
})
app.On("issues", func() {
var flagSet = flag.NewFlagSet("issues", flag.ExitOnError)
var formatJSON = false
flagSet.BoolVar(&formatJSON, "json", false, "")
_ = flagSet.Parse(os.Args[2:])
data, err := os.ReadFile(Tea.LogFile("issues.log"))
if err != nil {
if formatJSON {
fmt.Print("[]")
} else {
fmt.Println("no issues yet")
}
} else {
var issueMaps = []maps.Map{}
err = json.Unmarshal(data, &issueMaps)
if err != nil {
if formatJSON {
fmt.Print("[]")
} else {
fmt.Println("no issues yet")
}
} else {
if formatJSON {
fmt.Print(string(data))
} else {
if len(issueMaps) == 0 {
fmt.Println("no issues yet")
} else {
for i, issue := range issueMaps {
fmt.Println("issue " + types.String(i+1) + ": " + issue.GetString("message"))
}
}
}
}
}
})
app.On("instance", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "instance"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
replyJSON, err := json.MarshalIndent(reply.Params, "", " ")
if err != nil {
fmt.Println("[ERROR]marshal result failed: " + err.Error())
} else {
fmt.Println(string(replyJSON))
}
}
})
app.Run(func() {
nodes.NewAPINode().Start()

View File

@@ -2,7 +2,7 @@ package main
import (
"flag"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/installers/helpers"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
@@ -51,7 +51,7 @@ func main() {
return
}
unzip := utils.NewUnzip(zipPath, targetPath)
unzip := helpers.NewUnzip(zipPath, targetPath)
err := unzip.Run()
if err != nil {
stderr("ERROR: " + err.Error())

View File

@@ -1,8 +1,9 @@
package main
// 注意这里的依赖文件应该最小化,从而使编译后的文件最小化
import (
"flag"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/installers/helpers"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
@@ -51,7 +52,7 @@ func main() {
return
}
unzip := utils.NewUnzip(zipPath, targetPath)
unzip := helpers.NewUnzip(zipPath, targetPath)
err := unzip.Run()
if err != nil {
stderr("ERROR: " + err.Error())

View File

@@ -1,193 +0,0 @@
package main
import (
"bytes"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"io/ioutil"
"os"
"regexp"
"strings"
)
func main() {
// 导入数据
if lists.ContainsString(os.Args, "import") {
dbs.NotifyReady()
data, err := ioutil.ReadFile(Tea.Root + "/resources/ipdata/ip2region/global_region.csv")
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if len(data) == 0 {
logs.Println("[ERROR]file content should not be empty")
return
}
lines := bytes.Split(data, []byte{'\n'})
for _, line := range lines {
line = bytes.TrimSpace(line)
if len(line) == 0 {
continue
}
s := string(line)
reg := regexp.MustCompile(`(?U)(\d+),(\d+),(.+),(\d+),`)
if !reg.MatchString(s) {
continue
}
result := reg.FindStringSubmatch(s)
dataId := result[1]
parentDataId := result[2]
name := result[3]
level := result[4]
switch level {
case "1": // 国家|地区
countryId, err := regions.SharedRegionCountryDAO.FindCountryIdWithDataId(nil, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if countryId == 0 {
logs.Println("creating country or region ", name)
_, err = regions.SharedRegionCountryDAO.CreateCountry(nil, name, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
}
case "2": // 省份|地区
provinceId, err := regions.SharedRegionProvinceDAO.FindProvinceIdWithDataId(nil, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if provinceId == 0 {
logs.Println("creating province", name)
countryId, err := regions.SharedRegionCountryDAO.FindCountryIdWithDataId(nil, parentDataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if countryId == 0 {
logs.Println("[ERROR]can not find country from data id '" + parentDataId + "'")
return
}
_, err = regions.SharedRegionProvinceDAO.CreateProvince(nil, countryId, name, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
}
case "3": // 城市
cityId, err := regions.SharedRegionCityDAO.FindCityWithDataId(nil, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if cityId == 0 {
logs.Println("creating city", name)
provinceId, err := regions.SharedRegionProvinceDAO.FindProvinceIdWithDataId(nil, parentDataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
_, err = regions.SharedRegionCityDAO.CreateCity(nil, provinceId, name, dataId)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
}
}
}
logs.Println("done")
}
// 检查数据
if lists.ContainsString(os.Args, "check") {
dbs.NotifyReady()
data, err := ioutil.ReadFile(Tea.Root + "/resources/ipdata/ip2region/ip.merge.txt")
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if len(data) == 0 {
logs.Println("[ERROR]file should not be empty")
return
}
lines := bytes.Split(data, []byte("\n"))
for index, line := range lines {
s := string(bytes.TrimSpace(line))
if len(s) == 0 {
continue
}
pieces := strings.Split(s, "|")
countryName := pieces[2]
provinceName := pieces[4]
providerName := pieces[6]
// 记录provider
if len(providerName) > 0 && providerName != "0" {
providerId, err := regions.SharedRegionProviderDAO.FindProviderIdWithNameCacheable(nil, providerName)
if err != nil {
logs.Println("[ERROR]find provider id failed: " + err.Error())
return
}
if providerId == 0 {
logs.Println("creating new provider '"+providerName+"' ... ", index, "line")
_, err = regions.SharedRegionProviderDAO.CreateProvider(nil, providerName)
if err != nil {
logs.Println("create new provider failed: " + providerName)
return
}
logs.Println("created new provider '" + providerName + "'")
return
}
}
if lists.ContainsString([]string{"0", "欧洲", "北美地区", "法国南部领地", "非洲地区", "亚太地区"}, countryName) {
continue
}
// 检查国家
countryId, err := regions.SharedRegionCountryDAO.FindCountryIdWithNameCacheable(nil, countryName)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if countryId == 0 {
logs.Println("[ERROR]can not find country '"+countryName+"', index: ", index, "data: "+s)
return
}
// 检查省份
if countryName == "中国" {
if lists.ContainsString([]string{"0"}, provinceName) {
continue
}
provinceId, err := regions.SharedRegionProvinceDAO.FindProvinceIdWithNameCacheable(nil, countryId, provinceName)
if err != nil {
logs.Println("[ERROR]" + err.Error())
return
}
if provinceId == 0 {
logs.Println("[ERROR]can not find province '"+provinceName+"', index: ", index, "data: "+s)
return
}
}
}
logs.Println("done")
}
}

View File

@@ -7,7 +7,6 @@ import (
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"go/format"
"io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -19,7 +18,7 @@ func main() {
fmt.Println("[ERROR]" + err.Error())
return
}
results, err := setup.NewSQLDump().Dump(db)
results, err := setup.NewSQLDump().Dump(db, true)
if err != nil {
fmt.Println("[ERROR]" + err.Error())
return
@@ -67,7 +66,7 @@ func init() {
return
}
err = ioutil.WriteFile(sqlFile, dst, 0666)
err = os.WriteFile(sqlFile, dst, 0666)
if err != nil {
fmt.Println("[ERROR]write file failed: " + err.Error())
return

3
dist/.gitignore vendored
View File

@@ -1 +1,2 @@
*.zip
*.zip
edge-api

38
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/TeaOSLab/EdgeAPI
go 1.16
go 1.18
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
@@ -12,15 +12,39 @@ require (
github.com/go-acme/lego/v4 v4.5.2
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/protobuf v1.5.2
github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
github.com/json-iterator/go v1.1.12 // indirect
github.com/iwind/TeaGo v0.0.0-20220811034530-657e3f15b79e
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62
github.com/miekg/dns v1.1.43
github.com/mozillazg/go-pinyin v0.18.0
github.com/pkg/sftp v1.12.0
github.com/shirou/gopsutil/v3 v3.22.2
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
github.com/smartwalle/alipay/v3 v3.1.7
golang.org/x/crypto v0.1.0
golang.org/x/net v0.1.0
golang.org/x/sys v0.1.0
google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/smartwalle/crypto4go v1.0.2 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

40
go.sum
View File

@@ -237,13 +237,10 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhK
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220322141208-22f88d04004d h1:e8fkTKras/RXQWECApM9fKlFWujjYjEClpshkmZmtYg=
github.com/iwind/TeaGo v0.0.0-20220322141208-22f88d04004d/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c h1:ugjYZ74FJGWlfDKKraNgMyDTeS4vbXHe89JGUVQIJMo=
github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/TeaGo v0.0.0-20220811034530-657e3f15b79e h1:cw4b6ecXdXvLd45YSstD8r9ClcnVK4ljZMZCept2aOk=
github.com/iwind/TeaGo v0.0.0-20220811034530-657e3f15b79e/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62 h1:HJH6RDheAY156DnIfJSD/bEvqyXzsZuE2gzs8PuUjoo=
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -408,6 +405,10 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartwalle/alipay/v3 v3.1.7 h1:J4U5slABafKVD/b9gPCZe/3HAPB8Pa2NOYOPcugEJBo=
github.com/smartwalle/alipay/v3 v3.1.7/go.mod h1:cZUMCCnsux9YAxA0/f3PWUR+7wckWtE1BqxbVRtGij0=
github.com/smartwalle/crypto4go v1.0.2 h1:9DUEOOsPhmp00438L4oBdcL8EZG1zumecft5bWj5phI=
github.com/smartwalle/crypto4go v1.0.2/go.mod h1:LQ7vCZIb7BE5+MuMtJBuO8ORkkQ01m4DXDBWPzLbkMY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@@ -480,6 +481,7 @@ golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -491,8 +493,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -559,9 +561,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -630,14 +631,12 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -646,8 +645,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -789,8 +788,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,71 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"strconv"
"time"
)
type BaseStorage struct {
isOk bool
version int
firewallOnly bool
}
func (this *BaseStorage) SetVersion(version int) {
this.version = version
}
func (this *BaseStorage) Version() int {
return this.version
}
func (this *BaseStorage) IsOk() bool {
return this.isOk
}
func (this *BaseStorage) SetOk(isOk bool) {
this.isOk = isOk
}
func (this *BaseStorage) SetFirewallOnly(firewallOnly bool) {
this.firewallOnly = firewallOnly
}
// Marshal 对日志进行编码
func (this *BaseStorage) Marshal(accessLog *pb.HTTPAccessLog) ([]byte, error) {
return json.Marshal(accessLog)
}
// FormatVariables 格式化字符串中的变量
func (this *BaseStorage) FormatVariables(s string) string {
now := time.Now()
return configutils.ParseVariables(s, func(varName string) (value string) {
switch varName {
case "year":
return strconv.Itoa(now.Year())
case "month":
return fmt.Sprintf("%02d", now.Month())
case "week":
_, week := now.ISOWeek()
return fmt.Sprintf("%02d", week)
case "day":
return fmt.Sprintf("%02d", now.Day())
case "hour":
return fmt.Sprintf("%02d", now.Hour())
case "minute":
return fmt.Sprintf("%02d", now.Minute())
case "second":
return fmt.Sprintf("%02d", now.Second())
case "date":
return fmt.Sprintf("%d%02d%02d", now.Year(), now.Month(), now.Day())
}
return varName
})
}

View File

@@ -1,99 +0,0 @@
package accesslogs
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os/exec"
"sync"
)
// CommandStorage 通过命令行存储
type CommandStorage struct {
BaseStorage
config *serverconfigs.AccessLogCommandStorageConfig
writeLocker sync.Mutex
}
func NewCommandStorage(config *serverconfigs.AccessLogCommandStorageConfig) *CommandStorage {
return &CommandStorage{config: config}
}
func (this *CommandStorage) Config() interface{} {
return this.config
}
// Start 启动
func (this *CommandStorage) Start() error {
if len(this.config.Command) == 0 {
return errors.New("'command' should not be empty")
}
return nil
}
// 写入日志
func (this *CommandStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
cmd := exec.Command(this.config.Command, this.config.Args...)
if len(this.config.Dir) > 0 {
cmd.Dir = this.config.Dir
}
stdout := bytes.NewBuffer([]byte{})
cmd.Stdout = stdout
w, err := cmd.StdinPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = w.Write(data)
if err != nil {
logs.Error(err)
}
_, err = w.Write([]byte("\n"))
if err != nil {
logs.Error(err)
}
}
_ = w.Close()
err = cmd.Wait()
if err != nil {
logs.Error(err)
if stdout.Len() > 0 {
logs.Error(errors.New(string(stdout.Bytes())))
}
}
return nil
}
// Close 关闭
func (this *CommandStorage) Close() error {
return nil
}

View File

@@ -1,63 +0,0 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"os"
"os/exec"
"testing"
"time"
)
func TestCommandStorage_Write(t *testing.T) {
php, err := exec.LookPath("php")
if err != nil { // not found php, so we can not test
t.Log("php:", err)
return
}
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
before := time.Now()
storage := NewCommandStorage(&serverconfigs.AccessLogCommandStorageConfig{
Command: php,
Args: []string{cwd + "/tests/command_storage.php"},
})
err = storage.Start()
if err != nil {
t.Fatal(err)
}
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "GET",
RequestPath: "/hello",
},
{
RequestMethod: "GET",
RequestPath: "/world",
},
{
RequestMethod: "GET",
RequestPath: "/lu",
},
{
RequestMethod: "GET",
RequestPath: "/ping",
},
})
if err != nil {
t.Fatal(err)
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
t.Log(time.Since(before).Seconds(), "seconds")
}

View File

@@ -1,131 +0,0 @@
package accesslogs
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"io/ioutil"
"net/http"
"regexp"
"strings"
"time"
)
// ESStorage ElasticSearch存储策略
type ESStorage struct {
BaseStorage
config *serverconfigs.AccessLogESStorageConfig
}
func NewESStorage(config *serverconfigs.AccessLogESStorageConfig) *ESStorage {
return &ESStorage{config: config}
}
func (this *ESStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *ESStorage) Start() error {
if len(this.config.Endpoint) == 0 {
return errors.New("'endpoint' should not be nil")
}
if !regexp.MustCompile(`(?i)^(http|https)://`).MatchString(this.config.Endpoint) {
this.config.Endpoint = "http://" + this.config.Endpoint
}
if len(this.config.Index) == 0 {
return errors.New("'index' should not be nil")
}
if len(this.config.MappingType) == 0 {
return errors.New("'mappingType' should not be nil")
}
return nil
}
// 写入日志
func (this *ESStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
bulk := &strings.Builder{}
indexName := this.FormatVariables(this.config.Index)
typeName := this.FormatVariables(this.config.MappingType)
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
if len(accessLog.RequestId) == 0 {
continue
}
opData, err := json.Marshal(map[string]interface{}{
"index": map[string]interface{}{
"_index": indexName,
"_type": typeName,
"_id": accessLog.RequestId,
},
})
if err != nil {
remotelogs.Error("ACCESS_LOG_ES_STORAGE", "write failed: "+err.Error())
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
remotelogs.Error("ACCESS_LOG_ES_STORAGE", "marshal data failed: "+err.Error())
continue
}
bulk.Write(opData)
bulk.WriteString("\n")
bulk.Write(data)
bulk.WriteString("\n")
}
if bulk.Len() == 0 {
return nil
}
req, err := http.NewRequest(http.MethodPost, this.config.Endpoint+"/_bulk", strings.NewReader(bulk.String()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", strings.ReplaceAll(teaconst.ProductName, " ", "-")+"/"+teaconst.Version)
if len(this.config.Username) > 0 || len(this.config.Password) > 0 {
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(this.config.Username+":"+this.config.Password)))
}
client := utils.SharedHttpClient(10 * time.Second)
defer func() {
_ = req.Body.Close()
}()
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
bodyData, _ := ioutil.ReadAll(resp.Body)
return errors.New("ElasticSearch response status code: " + fmt.Sprintf("%d", resp.StatusCode) + " content: " + string(bodyData))
}
return nil
}
// Close 关闭
func (this *ESStorage) Close() error {
return nil
}

View File

@@ -1,53 +0,0 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"testing"
"time"
)
func TestESStorage_Write(t *testing.T) {
storage := NewESStorage(&serverconfigs.AccessLogESStorageConfig{
Endpoint: "http://127.0.0.1:9200",
Index: "logs",
MappingType: "accessLogs",
Username: "hello",
Password: "world",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
TimeISO8601: "2018-07-23T22:23:35+08:00",
Header: map[string]*pb.Strings{
"Content-Type": {Values: []string{"text/html"}},
},
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
TimeISO8601: "2018-07-23T22:23:35+08:00",
Header: map[string]*pb.Strings{
"Content-Type": {Values: []string{"text/css"}},
},
},
})
if err != nil {
t.Fatal(err)
}
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,130 +0,0 @@
package accesslogs
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os"
"path/filepath"
"sync"
)
// FileStorage 文件存储策略
type FileStorage struct {
BaseStorage
config *serverconfigs.AccessLogFileStorageConfig
writeLocker sync.Mutex
files map[string]*os.File // path => *File
filesLocker sync.Mutex
}
func NewFileStorage(config *serverconfigs.AccessLogFileStorageConfig) *FileStorage {
return &FileStorage{
config: config,
}
}
func (this *FileStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *FileStorage) Start() error {
if len(this.config.Path) == 0 {
return errors.New("'path' should not be empty")
}
this.files = map[string]*os.File{}
return nil
}
// Write 写入日志
func (this *FileStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
fp := this.fp()
if fp == nil {
return errors.New("file pointer should not be nil")
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = fp.Write(data)
if err != nil {
_ = this.Close()
break
}
_, _ = fp.WriteString("\n")
}
return nil
}
// Close 关闭
func (this *FileStorage) Close() error {
this.filesLocker.Lock()
defer this.filesLocker.Unlock()
var resultErr error
for _, f := range this.files {
err := f.Close()
if err != nil {
resultErr = err
}
}
return resultErr
}
func (this *FileStorage) fp() *os.File {
path := this.FormatVariables(this.config.Path)
this.filesLocker.Lock()
defer this.filesLocker.Unlock()
fp, ok := this.files[path]
if ok {
return fp
}
// 关闭其他的文件
for _, f := range this.files {
_ = f.Close()
}
// 是否创建文件目录
if this.config.AutoCreate {
dir := filepath.Dir(path)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = os.MkdirAll(dir, 0777)
if err != nil {
logs.Error(err)
return nil
}
}
}
// 打开新文件
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logs.Error(err)
return nil
}
this.files[path] = fp
return fp
}

View File

@@ -1,70 +0,0 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"testing"
"time"
)
func TestFileStorage_Write(t *testing.T) {
storage := NewFileStorage(&serverconfigs.AccessLogFileStorageConfig{
Path: Tea.Root + "/logs/access-${date}.log",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestPath: "/hello",
},
{
RequestPath: "/world",
},
})
if err != nil {
t.Fatal(err)
}
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestPath: "/1",
},
{
RequestPath: "/2",
},
})
if err != nil {
t.Fatal(err)
}
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
})
if err != nil {
t.Fatal(err)
}
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,33 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
// StorageInterface 日志存储接口
type StorageInterface interface {
// Version 获取版本
Version() int
// SetVersion 设置版本
SetVersion(version int)
// SetFirewallOnly 设置是否只处理防火墙相关的访问日志
SetFirewallOnly(firewallOnly bool)
IsOk() bool
SetOk(ok bool)
// Config 获取配置
Config() interface{}
// Start 开启
Start() error
// Write 写入日志
Write(accessLogs []*pb.HTTPAccessLog) error
// Close 关闭
Close() error
}

View File

@@ -1,185 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"sync"
"time"
)
var SharedStorageManager = NewStorageManager()
type StorageManager struct {
storageMap map[int64]StorageInterface // policyId => Storage
locker sync.Mutex
}
func NewStorageManager() *StorageManager {
return &StorageManager{
storageMap: map[int64]StorageInterface{},
}
}
func (this *StorageManager) Start() {
var ticker = time.NewTicker(1 * time.Minute)
if Tea.IsTesting() {
ticker = time.NewTicker(5 * time.Second)
}
// 启动时执行一次
var err = this.Loop()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "update error: "+err.Error())
}
// 循环执行
for range ticker.C {
err := this.Loop()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "update error: "+err.Error())
}
}
}
// Loop 更新
func (this *StorageManager) Loop() error {
policies, err := models.SharedHTTPAccessLogPolicyDAO.FindAllEnabledAndOnPolicies(nil)
if err != nil {
return err
}
var policyIds = []int64{}
for _, policy := range policies {
if policy.IsOn {
policyIds = append(policyIds, int64(policy.Id))
}
}
this.locker.Lock()
defer this.locker.Unlock()
// 关闭不用的
for policyId, storage := range this.storageMap {
if !lists.ContainsInt64(policyIds, policyId) {
err := storage.Close()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "close '"+types.String(policyId)+"' failed: "+err.Error())
}
delete(this.storageMap, policyId)
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "remove '"+types.String(policyId)+"'")
}
}
for _, policy := range policies {
var policyId = int64(policy.Id)
storage, ok := this.storageMap[policyId]
if ok {
// 检查配置是否有变更
if types.Int(policy.Version) != storage.Version() {
err = storage.Close()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "close policy '"+types.String(policyId)+"' failed: "+err.Error())
// 继续往下执行
}
if len(policy.Options) > 0 {
err = json.Unmarshal(policy.Options, storage.Config())
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "unmarshal policy '"+types.String(policyId)+"' config failed: "+err.Error())
storage.SetOk(false)
continue
}
}
storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
err := storage.Start()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetOk(true)
remotelogs.Println("ACCESS_LOG_STORAGE_MANAGER", "restart policy '"+types.String(policyId)+"'")
}
} else {
storage, err := this.createStorage(policy.Type, policy.Options)
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "create policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
this.storageMap[policyId] = storage
err = storage.Start()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetOk(true)
remotelogs.Println("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"'")
}
}
return nil
}
func (this *StorageManager) createStorage(storageType string, optionsJSON []byte) (StorageInterface, error) {
switch storageType {
case serverconfigs.AccessLogStorageTypeFile:
var config = &serverconfigs.AccessLogFileStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewFileStorage(config), nil
case serverconfigs.AccessLogStorageTypeES:
var config = &serverconfigs.AccessLogESStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewESStorage(config), nil
case serverconfigs.AccessLogStorageTypeTCP:
var config = &serverconfigs.AccessLogTCPStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewTCPStorage(config), nil
case serverconfigs.AccessLogStorageTypeSyslog:
var config = &serverconfigs.AccessLogSyslogStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewSyslogStorage(config), nil
case serverconfigs.AccessLogStorageTypeCommand:
var config = &serverconfigs.AccessLogCommandStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewCommandStorage(config), nil
}
return nil, errors.New("invalid policy type '" + storageType + "'")
}

View File

@@ -1,17 +0,0 @@
package accesslogs
import (
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestStorageManager_Loop(t *testing.T) {
dbs.NotifyReady()
var storage = NewStorageManager()
err := storage.Loop()
if err != nil {
t.Fatal(err)
}
t.Log(storage.storageMap)
}

View File

@@ -1,15 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// 写入日志
func (this *StorageManager) Write(policyId int64, accessLogs []*pb.HTTPAccessLog) error {
return nil
}

View File

@@ -1,140 +0,0 @@
package accesslogs
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os/exec"
"runtime"
"strconv"
)
type SyslogStorageProtocol = string
const (
SyslogStorageProtocolTCP SyslogStorageProtocol = "tcp"
SyslogStorageProtocolUDP SyslogStorageProtocol = "udp"
SyslogStorageProtocolNone SyslogStorageProtocol = "none"
SyslogStorageProtocolSocket SyslogStorageProtocol = "socket"
)
type SyslogStoragePriority = int
// SyslogStorage syslog存储策略
type SyslogStorage struct {
BaseStorage
config *serverconfigs.AccessLogSyslogStorageConfig
exe string
}
func NewSyslogStorage(config *serverconfigs.AccessLogSyslogStorageConfig) *SyslogStorage {
return &SyslogStorage{config: config}
}
func (this *SyslogStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *SyslogStorage) Start() error {
if runtime.GOOS != "linux" {
return errors.New("'syslog' storage only works on linux")
}
exe, err := exec.LookPath("logger")
if err != nil {
return err
}
this.exe = exe
return nil
}
// 写入日志
func (this *SyslogStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
args := []string{}
if len(this.config.Tag) > 0 {
args = append(args, "-t", this.config.Tag)
}
if this.config.Priority >= 0 {
args = append(args, "-p", strconv.Itoa(this.config.Priority))
}
switch this.config.Protocol {
case SyslogStorageProtocolTCP:
args = append(args, "-T")
if len(this.config.ServerAddr) > 0 {
args = append(args, "-n", this.config.ServerAddr)
}
if this.config.ServerPort > 0 {
args = append(args, "-P", strconv.Itoa(this.config.ServerPort))
}
case SyslogStorageProtocolUDP:
args = append(args, "-d")
if len(this.config.ServerAddr) > 0 {
args = append(args, "-n", this.config.ServerAddr)
}
if this.config.ServerPort > 0 {
args = append(args, "-P", strconv.Itoa(this.config.ServerPort))
}
case SyslogStorageProtocolSocket:
args = append(args, "-u")
args = append(args, this.config.Socket)
case SyslogStorageProtocolNone:
// do nothing
}
args = append(args, "-S", "10240")
cmd := exec.Command(this.exe, args...)
w, err := cmd.StdinPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = w.Write(data)
if err != nil {
logs.Error(err)
}
_, err = w.Write([]byte("\n"))
if err != nil {
logs.Error(err)
}
}
_ = w.Close()
err = cmd.Wait()
if err != nil {
return err
}
return nil
}
// Close 关闭
func (this *SyslogStorage) Close() error {
return nil
}

View File

@@ -1,114 +0,0 @@
package accesslogs
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"net"
"sync"
)
// TCPStorage TCP存储策略
type TCPStorage struct {
BaseStorage
config *serverconfigs.AccessLogTCPStorageConfig
writeLocker sync.Mutex
connLocker sync.Mutex
conn net.Conn
}
func NewTCPStorage(config *serverconfigs.AccessLogTCPStorageConfig) *TCPStorage {
return &TCPStorage{config: config}
}
func (this *TCPStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *TCPStorage) Start() error {
if len(this.config.Network) == 0 {
return errors.New("'network' should not be empty")
}
if len(this.config.Addr) == 0 {
return errors.New("'addr' should not be empty")
}
return nil
}
// 写入日志
func (this *TCPStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
err := this.connect()
if err != nil {
return err
}
conn := this.conn
if conn == nil {
return errors.New("connection should not be nil")
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = conn.Write(data)
if err != nil {
_ = this.Close()
break
}
_, err = conn.Write([]byte("\n"))
if err != nil {
_ = this.Close()
break
}
}
return nil
}
// Close 关闭
func (this *TCPStorage) Close() error {
this.connLocker.Lock()
defer this.connLocker.Unlock()
if this.conn != nil {
err := this.conn.Close()
this.conn = nil
return err
}
return nil
}
func (this *TCPStorage) connect() error {
this.connLocker.Lock()
defer this.connLocker.Unlock()
if this.conn != nil {
return nil
}
conn, err := net.Dial(this.config.Network, this.config.Addr)
if err != nil {
return err
}
this.conn = conn
return nil
}

View File

@@ -1,72 +0,0 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"net"
"testing"
"time"
)
func TestTCPStorage_Write(t *testing.T) {
go func() {
server, err := net.Listen("tcp", "127.0.0.1:9981")
if err != nil {
t.Error(err)
return
}
for {
conn, err := server.Accept()
if err != nil {
break
}
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if n > 0 {
t.Log(string(buf[:n]))
}
if err != nil {
break
}
}
break
}
_ = server.Close()
}()
storage := NewTCPStorage(&serverconfigs.AccessLogTCPStorageConfig{
Network: "tcp",
Addr: "127.0.0.1:9981",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
})
if err != nil {
t.Fatal(err)
}
}
time.Sleep(2 * time.Second)
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,24 +0,0 @@
<?php
// test command storage
// open access log file
$fp = fopen("/tmp/goedge-command-storage.log", "a+");
// read access logs from stdin
$stdin = fopen("php://stdin", "r");
while(true) {
if (feof($stdin)) {
break;
}
$line = fgets($stdin);
// write to access log file
fwrite($fp, $line);
}
// close file pointers
fclose($fp);
fclose($stdin);
?>

View File

@@ -10,7 +10,7 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
acmelog "github.com/go-acme/lego/v4/log"
"io/ioutil"
"io"
"log"
"testing"
@@ -50,7 +50,7 @@ func (this *MyProvider) CleanUp(domain, token, keyAuth string) error {
// 参考 https://go-acme.github.io/lego/usage/library/
func TestGenerate(t *testing.T) {
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
// 生成私钥
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -94,7 +94,7 @@ func TestGenerate(t *testing.T) {
}
func TestGenerate_EAB(t *testing.T) {
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
// 生成私钥
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

View File

@@ -5,12 +5,18 @@ import (
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/iwind/TeaGo/lists"
"os"
"strings"
"sync"
)
type DNSProvider struct {
raw dnsclients.ProviderInterface
dnsDomain string
locker sync.Mutex
deletedRecordNames []string
}
func NewDNSProvider(raw dnsclients.ProviderInterface, dnsDomain string) *DNSProvider {
@@ -21,39 +27,47 @@ func NewDNSProvider(raw dnsclients.ProviderInterface, dnsDomain string) *DNSProv
}
func (this *DNSProvider) Present(domain, token, keyAuth string) error {
_ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true")
fqdn, value := dns01.GetRecord(domain, keyAuth)
// 设置记录
index := strings.Index(fqdn, "."+this.dnsDomain)
var index = strings.Index(fqdn, "."+this.dnsDomain)
if index < 0 {
return errors.New("invalid fqdn value")
}
recordName := fqdn[:index]
record, err := this.raw.QueryRecord(this.dnsDomain, recordName, dnstypes.RecordTypeTXT)
if err != nil {
return errors.New("query DNS record failed: " + err.Error())
var recordName = fqdn[:index]
// 先删除老的
this.locker.Lock()
var wasDeleted = lists.ContainsString(this.deletedRecordNames, recordName)
this.locker.Unlock()
if !wasDeleted {
records, err := this.raw.QueryRecords(this.dnsDomain, recordName, dnstypes.RecordTypeTXT)
if err != nil {
return errors.New("query DNS record failed: " + err.Error())
}
for _, record := range records {
err = this.raw.DeleteRecord(this.dnsDomain, record)
if err != nil {
return err
}
}
this.locker.Lock()
this.deletedRecordNames = append(this.deletedRecordNames, recordName)
this.locker.Unlock()
}
if record == nil {
err = this.raw.AddRecord(this.dnsDomain, &dnstypes.Record{
Id: "",
Name: recordName,
Type: dnstypes.RecordTypeTXT,
Value: value,
Route: this.raw.DefaultRoute(),
})
if err != nil {
return errors.New("create DNS record failed: " + err.Error())
}
} else {
err = this.raw.UpdateRecord(this.dnsDomain, record, &dnstypes.Record{
Name: recordName,
Type: dnstypes.RecordTypeTXT,
Value: value,
Route: this.raw.DefaultRoute(),
})
if err != nil {
return errors.New("update DNS record failed: " + err.Error())
}
// 添加新的
err := this.raw.AddRecord(this.dnsDomain, &dnstypes.Record{
Id: "",
Name: recordName,
Type: dnstypes.RecordTypeTXT,
Value: value,
Route: this.raw.DefaultRoute(),
})
if err != nil {
return errors.New("create DNS record failed: " + err.Error())
}
return nil

View File

@@ -9,30 +9,11 @@ type Provider struct {
Code string `json:"code"`
Description string `json:"description"`
APIURL string `json:"apiURL"`
TestAPIURL string `json:"testAPIURL"`
RequireEAB bool `json:"requireEAB"`
EABDescription string `json:"eabDescription"`
}
func FindAllProviders() []*Provider {
return []*Provider{
{
Name: "Let's Encrypt",
Code: DefaultProviderCode,
Description: "非盈利组织Let's Encrypt提供的免费证书。",
APIURL: "https://acme-v02.api.letsencrypt.org/directory",
RequireEAB: false,
},
{
Name: "ZeroSSL",
Code: "zerossl",
Description: "相关文档 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>。",
APIURL: "https://acme.zerossl.com/v2/DV90",
RequireEAB: true,
EABDescription: "在官网<a href=\"https://app.zerossl.com/developer\" target=\"_blank\">[Developer]</a>页面底部点击\"Generate\"按钮生成。",
},
}
}
func FindProviderWithCode(code string) *Provider {
for _, provider := range FindAllProviders() {
if provider.Code == code {

View File

@@ -0,0 +1,24 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package acme
func FindAllProviders() []*Provider {
return []*Provider{
{
Name: "Let's Encrypt",
Code: DefaultProviderCode,
Description: "非盈利组织Let's Encrypt提供的免费证书。",
APIURL: "https://acme-v02.api.letsencrypt.org/directory",
RequireEAB: false,
},
{
Name: "ZeroSSL",
Code: "zerossl",
Description: "相关文档 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>。",
APIURL: "https://acme.zerossl.com/v2/DV90",
RequireEAB: true,
EABDescription: "在官网<a href=\"https://app.zerossl.com/developer\" target=\"_blank\">[Developer]</a>页面底部点击\"Generate\"按钮生成。",
},
}
}

View File

@@ -8,7 +8,8 @@ import (
"github.com/go-acme/lego/v4/lego"
acmelog "github.com/go-acme/lego/v4/log"
"github.com/go-acme/lego/v4/registration"
"io/ioutil"
"github.com/iwind/TeaGo/Tea"
"io"
"log"
)
@@ -40,6 +41,7 @@ func (this *Request) Run() (certData []byte, keyData []byte, err error) {
}
if this.task.Provider.RequireEAB && this.task.Account == nil {
err = errors.New("account should not be nil when provider require EAB")
return
}
switch this.task.AuthType {
@@ -55,7 +57,9 @@ func (this *Request) Run() (certData []byte, keyData []byte, err error) {
func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
if !this.debug {
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
if !Tea.IsTesting() {
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
}
}
if this.task.User == nil {
@@ -75,7 +79,7 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
return
}
config := lego.NewConfig(this.task.User)
var config = lego.NewConfig(this.task.User)
config.Certificate.KeyType = certcrypto.RSA2048
config.CADirURL = this.task.Provider.APIURL
config.UserAgent = teaconst.ProductName + "/" + teaconst.Version
@@ -86,7 +90,7 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
}
// 注册用户
resource := this.task.User.GetRegistration()
var resource = this.task.User.GetRegistration()
if resource != nil {
resource, err = client.Registration.QueryRegistration()
if err != nil {
@@ -124,7 +128,7 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
}
// 申请证书
request := certificate.ObtainRequest{
var request = certificate.ObtainRequest{
Domains: this.task.Domains,
Bundle: true,
}
@@ -138,7 +142,9 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
if !this.debug {
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
if !Tea.IsTesting() {
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
}
}
if this.task.User == nil {
@@ -146,7 +152,7 @@ func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
return
}
config := lego.NewConfig(this.task.User)
var config = lego.NewConfig(this.task.User)
config.Certificate.KeyType = certcrypto.RSA2048
config.CADirURL = this.task.Provider.APIURL
config.UserAgent = teaconst.ProductName + "/" + teaconst.Version
@@ -157,7 +163,7 @@ func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
}
// 注册用户
resource := this.task.User.GetRegistration()
var resource = this.task.User.GetRegistration()
if resource != nil {
resource, err = client.Registration.QueryRegistration()
if err != nil {
@@ -195,7 +201,7 @@ func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
}
// 申请证书
request := certificate.ObtainRequest{
var request = certificate.ObtainRequest{
Domains: this.task.Domains,
Bundle: true,
}

View File

@@ -4,7 +4,6 @@ import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/Tea"
"gopkg.in/yaml.v3"
"io/ioutil"
"os"
"path/filepath"
)
@@ -42,7 +41,7 @@ func SharedAPIConfig() (*APIConfig, error) {
var data []byte
var err error
for _, path := range paths {
data, err = ioutil.ReadFile(path)
data, err = os.ReadFile(path)
if err == nil {
if path == localFile {
isFromLocal = true
@@ -63,7 +62,7 @@ func SharedAPIConfig() (*APIConfig, error) {
if !isFromLocal {
// 恢复文件
_ = ioutil.WriteFile(localFile, data, 0666)
_ = os.WriteFile(localFile, data, 0666)
}
// 恢复数据库文件
@@ -80,9 +79,9 @@ func SharedAPIConfig() (*APIConfig, error) {
for _, path := range paths {
_, err := os.Stat(path)
if err == nil {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err == nil {
_ = ioutil.WriteFile(dbConfigFile, data, 0666)
_ = os.WriteFile(dbConfigFile, data, 0666)
break
}
}
@@ -122,14 +121,58 @@ func (this *APIConfig) WriteFile(path string) error {
for _, backupDir := range backupDirs {
stat, err := os.Stat(backupDir)
if err == nil && stat.IsDir() {
_ = ioutil.WriteFile(backupDir+"/"+filename, data, 0666)
_ = os.WriteFile(backupDir+"/"+filename, data, 0666)
} else if err != nil && os.IsNotExist(err) {
err = os.Mkdir(backupDir, 0777)
if err == nil {
_ = ioutil.WriteFile(backupDir+"/"+filename, data, 0666)
_ = os.WriteFile(backupDir+"/"+filename, data, 0666)
}
}
}
return ioutil.WriteFile(path, data, 0666)
return os.WriteFile(path, data, 0666)
}
// ResetAPIConfig 重置配置
func ResetAPIConfig() error {
for _, filename := range []string{"api.yaml", "db.yaml"} {
// 重置 configs/api.yaml
{
var configFile = Tea.ConfigFile(filename)
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
// 重置 ~/.edge-api/api.yaml
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
// 重置 /etc/edge-api/api.yaml
{
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
}
return nil
}

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.4.7"
Version = "0.5.9"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -18,13 +18,8 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.4.7"
UserNodeVersion = "0.3.3"
AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.3"
DNSNodeVersion = "0.2.2"
ReportNodeVersion = "0.1.0"
NodeVersion = "0.5.9"
// SQLVersion SQL版本号
SQLVersion = "8"
SQLVersion = "4"
)

View File

@@ -2,9 +2,18 @@
package teaconst
var (
IsPlus = false
MaxNodes int32 = 0
NodeId int64 = 0
Debug = false
import (
"crypto/sha1"
"fmt"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"time"
)
var (
IsPlus = false
MaxNodes int32 = 0
NodeId int64 = 0
Debug = false
InstanceCode = fmt.Sprintf("%x", sha1.Sum([]byte("INSTANCE"+types.String(time.Now().UnixNano())+"@"+types.String(rands.Int64()))))
)

View File

@@ -0,0 +1,33 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
OrderMethodStateEnabled = 1 // 已启用
OrderMethodStateDisabled = 0 // 已禁用
)
type OrderMethodDAO dbs.DAO
func NewOrderMethodDAO() *OrderMethodDAO {
return dbs.NewDAO(&OrderMethodDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeOrderMethods",
Model: new(OrderMethod),
PkName: "id",
},
}).(*OrderMethodDAO)
}
var SharedOrderMethodDAO *OrderMethodDAO
func init() {
dbs.OnReady(func() {
SharedOrderMethodDAO = NewOrderMethodDAO()
})
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,40 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// OrderMethod 订单支付方式
type OrderMethod struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
IsOn bool `field:"isOn"` // 是否启用
Description string `field:"description"` // 描述
ParentCode string `field:"parentCode"` // 内置的父级代号
Code string `field:"code"` // 代号
Url string `field:"url"` // URL
Secret string `field:"secret"` // 密钥
Params dbs.JSON `field:"params"` // 参数
ClientType string `field:"clientType"` // 客户端类型
QrcodeTitle string `field:"qrcodeTitle"` // 二维码标题
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type OrderMethodOperator struct {
Id any // ID
Name any // 名称
IsOn any // 是否启用
Description any // 描述
ParentCode any // 内置的父级代号
Code any // 代号
Url any // URL
Secret any // 密钥
Params any // 参数
ClientType any // 客户端类型
QrcodeTitle any // 二维码标题
Order any // 排序
State any // 状态
}
func NewOrderMethodOperator() *OrderMethodOperator {
return &OrderMethodOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -1,80 +0,0 @@
package accounts
import (
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountDailyStatDAO dbs.DAO
func NewUserAccountDailyStatDAO() *UserAccountDailyStatDAO {
return dbs.NewDAO(&UserAccountDailyStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountDailyStats",
Model: new(UserAccountDailyStat),
PkName: "id",
},
}).(*UserAccountDailyStatDAO)
}
var SharedUserAccountDailyStatDAO *UserAccountDailyStatDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDailyStatDAO = NewUserAccountDailyStatDAO()
})
}
// UpdateDailyStat 更新当天统计数据
func (this *UserAccountDailyStatDAO) UpdateDailyStat(tx *dbs.Tx) error {
var day = timeutil.Format("Ymd")
var month = timeutil.Format("Ym")
income, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountIncomeEventTypes)
if err != nil {
return err
}
expense, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountExpenseEventTypes)
if err != nil {
return err
}
if expense < 0 {
expense = -expense
}
return this.Query(tx).
InsertOrUpdateQuickly(maps.Map{
"day": day,
"month": month,
"income": income,
"expense": expense,
}, maps.Map{
"income": income,
"expense": expense,
})
}
// FindDailyStats 查看按天统计
func (this *UserAccountDailyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Between("day", dayFrom, dayTo).
Slice(&result).
FindAll()
return
}
// FindMonthlyStats 查看某月统计
func (this *UserAccountDailyStatDAO) FindMonthlyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Result("SUM(income) AS income", "SUM(expense) AS expense", "month").
Between("day", dayFrom, dayTo).
Group("month").
Slice(&result).
FindAll()
return
}

View File

@@ -1,253 +0,0 @@
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"time"
)
func init() {
dbs.OnReadyDone(func() {
goman.New(func() {
// 自动支付账单任务
var ticker = time.NewTicker(12 * time.Hour)
for range ticker.C {
if SharedUserAccountDAO.Instance != nil {
err := SharedUserAccountDAO.Instance.RunTx(func(tx *dbs.Tx) error {
return SharedUserAccountDAO.PayBills(tx)
})
if err != nil {
remotelogs.Error("USER_ACCOUNT_DAO", "pay bills task failed: "+err.Error())
}
}
}
})
})
}
type UserAccountDAO dbs.DAO
func NewUserAccountDAO() *UserAccountDAO {
return dbs.NewDAO(&UserAccountDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccounts",
Model: new(UserAccount),
PkName: "id",
},
}).(*UserAccountDAO)
}
var SharedUserAccountDAO *UserAccountDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDAO = NewUserAccountDAO()
})
}
// FindUserAccountWithUserId 根据用户ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithUserId(tx *dbs.Tx, userId int64) (*UserAccount, error) {
if userId <= 0 {
return nil, errors.New("invalid userId '" + types.String(userId) + "'")
}
// 用户是否存在
user, err := models.SharedUserDAO.FindEnabledUser(tx, userId, nil)
if err != nil {
return nil, err
}
if user == nil {
return nil, errors.New("invalid userId '" + types.String(userId) + "'")
}
account, err := this.Query(tx).
Attr("userId", userId).
Find()
if err != nil {
return nil, err
}
if account != nil {
return account.(*UserAccount), nil
}
var op = NewUserAccountOperator()
op.UserId = userId
_, err = this.SaveInt64(tx, op)
if err != nil {
return nil, err
}
return this.FindUserAccountWithUserId(tx, userId)
}
// FindUserAccountWithAccountId 根据ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithAccountId(tx *dbs.Tx, accountId int64) (*UserAccount, error) {
one, err := this.Query(tx).
Pk(accountId).
Find()
if one != nil {
return one.(*UserAccount), nil
}
return nil, err
}
// UpdateUserAccount 操作用户账户
func (this *UserAccountDAO) UpdateUserAccount(tx *dbs.Tx, accountId int64, delta float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
account, err := this.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
var userId = int64(account.UserId)
var deltaFloat64 = float64(delta)
if deltaFloat64 < 0 && account.Total < -deltaFloat64 {
return errors.New("not enough account quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("total", dbs.SQL("total+:delta")).
Param("delta", delta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, accountId, delta, 0, eventType, description, params)
if err != nil {
return err
}
return nil
}
// UpdateUserAccountFrozen 操作用户账户冻结余额
func (this *UserAccountDAO) UpdateUserAccountFrozen(tx *dbs.Tx, userId int64, delta float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
account, err := this.FindUserAccountWithUserId(tx, userId)
if err != nil {
return err
}
var deltaFloat64 = float64(delta)
if deltaFloat64 < 0 && account.TotalFrozen < -deltaFloat64 {
return errors.New("not enough account frozen quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("totalFrozen", dbs.SQL("total+:delta")).
Param("delta", delta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, int64(account.Id), 0, delta, eventType, description, params)
if err != nil {
return err
}
return nil
}
// CountAllAccounts 计算所有账户数量
func (this *UserAccountDAO) CountAllAccounts(tx *dbs.Tx, keyword string) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
return query.Count()
}
// ListAccounts 列出单页账户
func (this *UserAccountDAO) ListAccounts(tx *dbs.Tx, keyword string, offset int64, size int64) (result []*UserAccount, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// PayBills 尝试自动支付账单
func (this *UserAccountDAO) PayBills(tx *dbs.Tx) error {
bills, err := models.SharedUserBillDAO.FindUnpaidBills(tx, 10000)
if err != nil {
return err
}
// 先支付久远的
lists.Reverse(bills)
for _, bill := range bills {
if bill.Amount <= 0 {
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
continue
}
account, err := SharedUserAccountDAO.FindUserAccountWithUserId(tx, int64(bill.UserId))
if err != nil {
return err
}
if account == nil || account.Total < bill.Amount {
continue
}
// 扣款
err = SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), -float32(bill.Amount), userconfigs.AccountEventTypePayBill, "支付账单"+bill.Code, maps.Map{"billId": bill.Id})
if err != nil {
return err
}
// 改为已支付
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
}
return nil
}
// CheckUserAccount 检查用户账户
func (this *UserAccountDAO) CheckUserAccount(tx *dbs.Tx, userId int64, accountId int64) error {
exists, err := this.Query(tx).
Pk(accountId).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !exists {
return models.ErrNotFound
}
return nil
}

View File

@@ -1,129 +0,0 @@
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountLogDAO dbs.DAO
func NewUserAccountLogDAO() *UserAccountLogDAO {
return dbs.NewDAO(&UserAccountLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountLogs",
Model: new(UserAccountLog),
PkName: "id",
},
}).(*UserAccountLogDAO)
}
var SharedUserAccountLogDAO *UserAccountLogDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountLogDAO = NewUserAccountLogDAO()
})
}
// CreateAccountLog 生成用户账户日志
func (this *UserAccountLogDAO) CreateAccountLog(tx *dbs.Tx, userId int64, accountId int64, delta float32, deltaFrozen float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
var op = NewUserAccountLogOperator()
op.UserId = userId
op.AccountId = accountId
op.Delta = delta
op.DeltaFrozen = deltaFrozen
account, err := SharedUserAccountDAO.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
op.Total = account.Total
op.TotalFrozen = account.TotalFrozen
op.EventType = eventType
op.Description = description
if params == nil {
params = maps.Map{}
}
op.Params = params.AsJSON()
op.Day = timeutil.Format("Ymd")
err = this.Save(tx, op)
if err != nil {
return err
}
return SharedUserAccountDailyStatDAO.UpdateDailyStat(tx)
}
// CountAccountLogs 计算日志数量
func (this *UserAccountLogDAO) CountAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string) (int64, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
return query.Count()
}
// ListAccountLogs 列出单页日志
func (this *UserAccountLogDAO) ListAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string, offset int64, size int64) (result []*UserAccountLog, err error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// SumDailyEventTypes 统计某天数据总和
func (this *UserAccountLogDAO) SumDailyEventTypes(tx *dbs.Tx, day string, eventTypes []userconfigs.AccountEventType) (float32, error) {
if len(eventTypes) == 0 {
return 0, nil
}
result, err := this.Query(tx).
Attr("day", day).
Attr("eventType", eventTypes).
Sum("delta", 0)
if err != nil {
return 0, err
}
return types.Float32(result), nil
}

View File

@@ -0,0 +1,33 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
UserOrderStateEnabled = 1 // 已启用
UserOrderStateDisabled = 0 // 已禁用
)
type UserOrderDAO dbs.DAO
func NewUserOrderDAO() *UserOrderDAO {
return dbs.NewDAO(&UserOrderDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserOrders",
Model: new(UserOrder),
PkName: "id",
},
}).(*UserOrderDAO)
}
var SharedUserOrderDAO *UserOrderDAO
func init() {
dbs.OnReady(func() {
SharedUserOrderDAO = NewUserOrderDAO()
})
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,28 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type UserOrderLogDAO dbs.DAO
func NewUserOrderLogDAO() *UserOrderLogDAO {
return dbs.NewDAO(&UserOrderLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserOrderLogs",
Model: new(UserOrderLog),
PkName: "id",
},
}).(*UserOrderLogDAO)
}
var SharedUserOrderLogDAO *UserOrderLogDAO
func init() {
dbs.OnReady(func() {
SharedUserOrderLogDAO = NewUserOrderLogDAO()
})
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,28 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserOrderLog 订单日志
type UserOrderLog struct {
Id uint64 `field:"id"` // ID
AdminId uint64 `field:"adminId"` // 管理员ID
UserId uint64 `field:"userId"` // 用户ID
OrderId uint64 `field:"orderId"` // 订单ID
Status string `field:"status"` // 状态
Snapshot dbs.JSON `field:"snapshot"` // 状态快照
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type UserOrderLogOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
OrderId interface{} // 订单ID
Status interface{} // 状态
Snapshot interface{} // 状态快照
CreatedAt interface{} // 创建时间
}
func NewUserOrderLogOperator() *UserOrderLogOperator {
return &UserOrderLogOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,40 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserOrder 用户订单
type UserOrder struct {
Id uint64 `field:"id"` // 用户订单
UserId uint64 `field:"userId"` // 用户ID
Code string `field:"code"` // 订单号
Type string `field:"type"` // 订单类型
MethodId uint32 `field:"methodId"` // 支付方式
Status string `field:"status"` // 订单状态
Amount float64 `field:"amount"` // 金额
Params dbs.JSON `field:"params"` // 附加参数
ExpiredAt uint64 `field:"expiredAt"` // 过期时间
CreatedAt uint64 `field:"createdAt"` // 创建时间
CancelledAt uint64 `field:"cancelledAt"` // 取消时间
FinishedAt uint64 `field:"finishedAt"` // 结束时间
State uint8 `field:"state"` // 状态
}
type UserOrderOperator struct {
Id interface{} // 用户订单
UserId interface{} // 用户ID
Code interface{} // 订单号
Type interface{} // 订单类型
MethodId interface{} // 支付方式
Status interface{} // 订单状态
Amount interface{} // 金额
Params interface{} // 附加参数
ExpiredAt interface{} // 过期时间
CreatedAt interface{} // 创建时间
CancelledAt interface{} // 取消时间
FinishedAt interface{} // 结束时间
State interface{} // 状态
}
func NewUserOrderOperator() *UserOrderOperator {
return &UserOrderOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -29,7 +29,7 @@ func init() {
// 创建认证信息
func (this *ACMEAuthenticationDAO) CreateAuth(tx *dbs.Tx, taskId int64, domain string, token string, key string) error {
op := NewACMEAuthenticationOperator()
var op = NewACMEAuthenticationOperator()
op.TaskId = taskId
op.Domain = domain
op.Token = token

View File

@@ -1,6 +1,7 @@
package acme
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
@@ -72,8 +73,9 @@ func (this *ACMEProviderAccountDAO) FindACMEProviderAccountName(tx *dbs.Tx, id i
}
// CreateAccount 创建账号
func (this *ACMEProviderAccountDAO) CreateAccount(tx *dbs.Tx, name string, providerCode string, eabKid string, eabKey string) (int64, error) {
func (this *ACMEProviderAccountDAO) CreateAccount(tx *dbs.Tx, userId int64, name string, providerCode string, eabKid string, eabKey string) (int64, error) {
var op = NewACMEProviderAccountOperator()
op.UserId = userId
op.Name = name
op.ProviderCode = providerCode
op.EabKid = eabKid
@@ -98,15 +100,18 @@ func (this *ACMEProviderAccountDAO) UpdateAccount(tx *dbs.Tx, accountId int64, n
}
// CountAllEnabledAccounts 计算账号数量
func (this *ACMEProviderAccountDAO) CountAllEnabledAccounts(tx *dbs.Tx) (int64, error) {
func (this *ACMEProviderAccountDAO) CountAllEnabledAccounts(tx *dbs.Tx, userId int64) (int64, error) {
return this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Count()
}
// ListEnabledAccounts 查找单页账号
func (this *ACMEProviderAccountDAO) ListEnabledAccounts(tx *dbs.Tx, offset int64, size int64) (result []*ACMEProviderAccount, err error) {
func (this *ACMEProviderAccountDAO) ListEnabledAccounts(tx *dbs.Tx, userId int64, offset int64, size int64) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Offset(offset).
Limit(size).
DescPk().
@@ -116,12 +121,34 @@ func (this *ACMEProviderAccountDAO) ListEnabledAccounts(tx *dbs.Tx, offset int64
}
// FindAllEnabledAccountsWithProviderCode 根据服务商代号查找账号
func (this *ACMEProviderAccountDAO) FindAllEnabledAccountsWithProviderCode(tx *dbs.Tx, providerCode string) (result []*ACMEProviderAccount, err error) {
func (this *ACMEProviderAccountDAO) FindAllEnabledAccountsWithProviderCode(tx *dbs.Tx, userId int64, providerCode string) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("providerCode", providerCode).
Attr("userId", userId).
DescPk().
Slice(&result).
FindAll()
return
}
// CheckUserAccount 检查是否为用户的服务商账号
func (this *ACMEProviderAccountDAO) CheckUserAccount(tx *dbs.Tx, userId int64, accountId int64) error {
if userId <= 0 || accountId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(accountId).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}

View File

@@ -3,24 +3,26 @@ package acme
// ACMEProviderAccount ACME提供商
type ACMEProviderAccount struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
ProviderCode string `field:"providerCode"` // 代号
Error string `field:"error"` // 最后一条错误信息
EabKid string `field:"eabKid"` // KID
EabKey string `field:"eabKey"` // Key
Error string `field:"error"` // 最后一条错误信息
State uint8 `field:"state"` // 状态
}
type ACMEProviderAccountOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启
Name interface{} // 名称
ProviderCode interface{} // 代号
Error interface{} // 最后一条错误信息
EabKid interface{} // KID
EabKey interface{} // Key
State interface{} // 状态
Id any // ID
UserId any // 用户ID
IsOn any // 是否启用
Name any // 名称
ProviderCode any // 代号
EabKid any // KID
EabKey any // Key
Error any // 最后一条错误信息
State any // 状态
}
func NewACMEProviderAccountOperator() *ACMEProviderAccountOperator {

View File

@@ -106,8 +106,9 @@ func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) err
}
// CountAllEnabledACMETasks 计算所有任务数量
func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string) (int64, error) {
query := dbutils.NewQuery(tx, this, adminId, userId)
func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string) (int64, error) {
var query = this.Query(tx)
query.Attr("userId", userId) // 这个条件必须加上
if isAvailable || isExpired || expiringDays > 0 {
query.Gt("certId", 0)
@@ -137,8 +138,9 @@ func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, use
}
// ListEnabledACMETasks 列出单页任务
func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, offset int64, size int64) (result []*ACMETask, err error) {
query := dbutils.NewQuery(tx, this, adminId, userId)
func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, offset int64, size int64) (result []*ACMETask, err error) {
var query = this.Query(tx)
query.Attr("userId", userId) // 这个条件必须加上
if isAvailable || isExpired || expiringDays > 0 {
query.Gt("certId", 0)
@@ -169,7 +171,7 @@ func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId
// CreateACMETask 创建任务
func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acmeutils.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) (int64, error) {
op := NewACMETaskOperator()
var op = NewACMETaskOperator()
op.AdminId = adminId
op.UserId = userId
op.AuthType = authType
@@ -204,7 +206,7 @@ func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId
return errors.New("invalid acmeTaskId")
}
op := NewACMETaskOperator()
var op = NewACMETaskOperator()
op.Id = acmeTaskId
op.AcmeUserId = acmeUserId
op.DnsProviderId = dnsProviderId
@@ -227,8 +229,13 @@ func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId
}
// CheckACMETask 检查权限
func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, adminId int64, userId int64, acmeTaskId int64) (bool, error) {
return dbutils.NewQuery(tx, this, adminId, userId).
func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, userId int64, acmeTaskId int64) (bool, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
return query.
State(ACMETaskStateEnabled).
Pk(acmeTaskId).
Exist()
@@ -240,7 +247,7 @@ func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int
return errors.New("invalid taskId")
}
op := NewACMETaskOperator()
var op = NewACMETaskOperator()
op.Id = taskId
op.CertId = certId
err := this.Save(tx, op)
@@ -319,7 +326,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
return
}
remoteUser := acmeutils.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
var remoteUser = acmeutils.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
resourceJSON, err := json.Marshal(resource)
if err != nil {
return err
@@ -349,7 +356,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
errMsg = "找不到DNS服务商账号"
return
}
providerInterface := dnsclients.FindProvider(dnsProvider.Type)
providerInterface := dnsclients.FindProvider(dnsProvider.Type, int64(dnsProvider.Id))
if providerInterface == nil {
errMsg = "暂不支持此类型的DNS服务商 '" + dnsProvider.Type + "'"
return
@@ -382,7 +389,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
acmeTask.Provider = acmeProvider
acmeTask.Account = acmeAccount
acmeRequest := acmeutils.NewRequest(acmeTask)
var acmeRequest = acmeutils.NewRequest(acmeTask)
acmeRequest.OnAuth(func(domain, token, keyAuth string) {
err := SharedACMEAuthenticationDAO.CreateAuth(tx, taskId, domain, token, keyAuth)
if err != nil {
@@ -398,7 +405,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
if err != nil {
remotelogs.Error("ACME", "encode auth data failed: '"+task.AuthURL+"'")
} else {
client := utils.SharedHttpClient(5 * time.Second)
var client = utils.SharedHttpClient(10 * time.Second)
req, err := http.NewRequest(http.MethodPost, task.AuthURL, bytes.NewReader(authJSON))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
@@ -423,7 +430,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
}
// 分析证书
sslConfig := &sslconfigs.SSLCertConfig{
var sslConfig = &sslconfigs.SSLCertConfig{
CertData: certData,
KeyData: keyData,
}

View File

@@ -27,9 +27,9 @@ func init() {
})
}
// 生成日志
// CreateACMETaskLog 生成日志
func (this *ACMETaskLogDAO) CreateACMETaskLog(tx *dbs.Tx, taskId int64, isOk bool, errMsg string) error {
op := NewACMETaskLogOperator()
var op = NewACMETaskLogOperator()
op.TaskId = taskId
op.Error = errMsg
op.IsOk = isOk
@@ -37,7 +37,7 @@ func (this *ACMETaskLogDAO) CreateACMETaskLog(tx *dbs.Tx, taskId int64, isOk boo
return err
}
// 取得任务的最后一条执行日志
// FindLatestACMETasKLog 取得任务的最后一条执行日志
func (this *ACMETaskLogDAO) FindLatestACMETasKLog(tx *dbs.Tx, taskId int64) (*ACMETaskLog, error) {
one, err := this.Query(tx).
Attr("taskId", taskId).

View File

@@ -83,7 +83,7 @@ func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64,
}
privateKeyText := base64.StdEncoding.EncodeToString(privateKeyData)
op := NewACMEUserOperator()
var op = NewACMEUserOperator()
op.AdminId = adminId
op.UserId = userId
op.ProviderCode = providerCode
@@ -104,7 +104,7 @@ func (this *ACMEUserDAO) UpdateACMEUser(tx *dbs.Tx, acmeUserId int64, descriptio
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
}
op := NewACMEUserOperator()
var op = NewACMEUserOperator()
op.Id = acmeUserId
op.Description = description
err := this.Save(tx, op)
@@ -116,7 +116,7 @@ func (this *ACMEUserDAO) UpdateACMEUserRegistration(tx *dbs.Tx, acmeUserId int64
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
}
op := NewACMEUserOperator()
var op = NewACMEUserOperator()
op.Id = acmeUserId
op.Registration = registrationJSON
err := this.Save(tx, op)

View File

@@ -63,6 +63,19 @@ func (this *AdminDAO) FindEnabledAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
return result.(*Admin), err
}
// FindBasicAdmin 查找管理员基本信息
func (this *AdminDAO) FindBasicAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
result, err := this.Query(tx).
Result("id", "username", "fullname").
Pk(id).
Attr("state", AdminStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*Admin), err
}
// ExistEnabledAdmin 检查管理员是否存在
func (this *AdminDAO) ExistEnabledAdmin(tx *dbs.Tx, adminId int64) (bool, error) {
return this.Query(tx).
@@ -115,7 +128,7 @@ func (this *AdminDAO) UpdateAdminPassword(tx *dbs.Tx, adminId int64, password st
if adminId <= 0 {
return errors.New("invalid adminId")
}
op := NewAdminOperator()
var op = NewAdminOperator()
op.Id = adminId
op.Password = stringutil.Md5(password)
err := this.Save(tx, op)
@@ -124,7 +137,7 @@ func (this *AdminDAO) UpdateAdminPassword(tx *dbs.Tx, adminId int64, password st
// CreateAdmin 创建管理员
func (this *AdminDAO) CreateAdmin(tx *dbs.Tx, username string, canLogin bool, password string, fullname string, isSuper bool, modulesJSON []byte) (int64, error) {
op := NewAdminOperator()
var op = NewAdminOperator()
op.IsOn = true
op.State = AdminStateEnabled
op.Username = username
@@ -149,7 +162,7 @@ func (this *AdminDAO) UpdateAdminInfo(tx *dbs.Tx, adminId int64, fullname string
if adminId <= 0 {
return errors.New("invalid adminId")
}
op := NewAdminOperator()
var op = NewAdminOperator()
op.Id = adminId
op.Fullname = fullname
err := this.Save(tx, op)
@@ -161,7 +174,7 @@ func (this *AdminDAO) UpdateAdmin(tx *dbs.Tx, adminId int64, username string, ca
if adminId <= 0 {
return errors.New("invalid adminId")
}
op := NewAdminOperator()
var op = NewAdminOperator()
op.Id = adminId
op.Fullname = fullname
op.Username = username
@@ -198,7 +211,7 @@ func (this *AdminDAO) UpdateAdminLogin(tx *dbs.Tx, adminId int64, username strin
if adminId <= 0 {
return errors.New("invalid adminId")
}
op := NewAdminOperator()
var op = NewAdminOperator()
op.Id = adminId
op.Username = username
if len(password) > 0 {
@@ -213,7 +226,7 @@ func (this *AdminDAO) UpdateAdminModules(tx *dbs.Tx, adminId int64, allowModules
if adminId <= 0 {
return errors.New("invalid adminId")
}
op := NewAdminOperator()
var op = NewAdminOperator()
op.Id = adminId
op.Modules = allowModulesJSON
err := this.Save(tx, op)

View File

@@ -14,23 +14,23 @@ type Admin struct {
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 状态
Modules dbs.JSON `field:"modules"` // 允许的模块
CanLogin uint8 `field:"canLogin"` // 是否可以登录
CanLogin bool `field:"canLogin"` // 是否可以登录
Theme string `field:"theme"` // 模板设置
}
type AdminOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
Username interface{} // 用户名
Password interface{} // 密码
Fullname interface{} // 全名
IsSuper interface{} // 是否为超级管理员
CreatedAt interface{} // 创建时间
UpdatedAt interface{} // 修改时间
State interface{} // 状态
Modules interface{} // 允许的模块
CanLogin interface{} // 是否可以登录
Theme interface{} // 模板设置
Id any // ID
IsOn any // 是否启用
Username any // 用户名
Password any // 密码
Fullname any // 全名
IsSuper any // 是否为超级管理员
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 状态
Modules any // 允许的模块
CanLogin any // 是否可以登录
Theme any // 模板设置
}
func NewAdminOperator() *AdminOperator {

View File

@@ -56,7 +56,7 @@ func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, adminId int64, us
token = rands.String(128) // TODO 增强安全性,将来使用 base64_encode(encrypt(salt+random)) 算法来代替
expiresAt = time.Now().Unix() + 7200
op := NewAPIAccessTokenOperator()
var op = NewAPIAccessTokenOperator()
if accessToken != nil {
op.Id = accessToken.(*APIAccessToken).Id

View File

@@ -58,15 +58,22 @@ func (this *APINodeDAO) EnableAPINode(tx *dbs.Tx, id int64) error {
}
// DisableAPINode 禁用条目
func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, id int64) error {
func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, nodeId int64) error {
_, err := this.Query(tx).
Pk(id).
Pk(nodeId).
Set("state", APINodeStateDisabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
err = this.NotifyUpdate(tx, nodeId)
if err != nil {
return err
}
// 删除运行日志
return SharedNodeLogDAO.DeleteNodeLogs(tx, nodeconfigs.NodeRoleAPI, nodeId)
}
// FindEnabledAPINode 查找启用中的条目
@@ -127,7 +134,7 @@ func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description strin
return
}
op := NewAPINodeOperator()
var op = NewAPINodeOperator()
op.IsOn = isOn
op.UniqueId = uniqueId
op.Secret = secret

View File

@@ -77,7 +77,7 @@ func (this *ApiTokenDAO) FindEnabledTokenWithNodeCacheable(tx *dbs.Tx, nodeId st
State(ApiTokenStateEnabled).
Find()
if one != nil {
token := one.(*ApiToken)
token = one.(*ApiToken)
SharedCacheLocker.Lock()
apiTokenCacheMap[nodeId] = token
SharedCacheLocker.Unlock()
@@ -112,7 +112,7 @@ func (this *ApiTokenDAO) FindEnabledTokenWithRole(tx *dbs.Tx, role string) (*Api
// CreateAPIToken 保存API Token
func (this *ApiTokenDAO) CreateAPIToken(tx *dbs.Tx, nodeId string, secret string, role nodeconfigs.NodeRole) error {
op := NewApiTokenOperator()
var op = NewApiTokenOperator()
op.NodeId = nodeId
op.Secret = secret
op.Role = role

View File

@@ -1,13 +1,9 @@
package authority
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type AuthorityKeyDAO dbs.DAO
@@ -33,63 +29,3 @@ func init() {
_, _ = SharedAuthorityKeyDAO.IsPlus(nil)
})
}
// UpdateKey 设置Key
func (this *AuthorityKeyDAO) UpdateKey(tx *dbs.Tx, value string, dayFrom string, dayTo string, hostname string, macAddresses []string, company string) error {
one, err := this.Query(tx).
AscPk().
Find()
if err != nil {
return err
}
op := NewAuthorityKeyOperator()
if one != nil {
op.Id = one.(*AuthorityKey).Id
}
op.Value = value
op.DayFrom = dayFrom
op.DayTo = dayTo
op.Hostname = hostname
if len(macAddresses) == 0 {
macAddresses = []string{}
}
macAddressesJSON, err := json.Marshal(macAddresses)
if err != nil {
return err
}
op.MacAddresses = macAddressesJSON
op.Company = company
op.UpdatedAt = time.Now().Unix()
return this.Save(tx, op)
}
// ReadKey 读取Key
func (this *AuthorityKeyDAO) ReadKey(tx *dbs.Tx) (key *AuthorityKey, err error) {
one, err := this.Query(tx).
AscPk().
Find()
if err != nil {
return nil, err
}
if one == nil {
return nil, nil
}
key = one.(*AuthorityKey)
// 顺便更新相关变量
if key.DayTo >= timeutil.Format("Y-m-d") {
teaconst.IsPlus = true
}
return
}
// ResetKey 重置Key
func (this *AuthorityKeyDAO) ResetKey(tx *dbs.Tx) error {
_, err := this.Query(tx).
Delete()
return err
}

View File

@@ -1,23 +0,0 @@
package authority
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestAuthorityKeyDAO_UpdateValue(t *testing.T) {
err := NewAuthorityKeyDAO().UpdateKey(nil, "12345678", "", "", "", []string{}, "")
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestAuthorityKeyDAO_ReadValue(t *testing.T) {
value, err := NewAuthorityKeyDAO().ReadKey(nil)
if err != nil {
t.Fatal(err)
}
t.Log(value)
}

View File

@@ -1,6 +1,7 @@
package authority
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
@@ -119,7 +120,7 @@ func (this *AuthorityNodeDAO) CreateAuthorityNode(tx *dbs.Tx, name string, descr
return
}
op := NewAuthorityNodeOperator()
var op = NewAuthorityNodeOperator()
op.IsOn = isOn
op.UniqueId = uniqueId
op.Secret = secret
@@ -140,7 +141,7 @@ func (this *AuthorityNodeDAO) UpdateAuthorityNode(tx *dbs.Tx, nodeId int64, name
return errors.New("invalid nodeId")
}
op := NewAuthorityNodeOperator()
var op = NewAuthorityNodeOperator()
op.Id = nodeId
op.Name = name
op.Description = description
@@ -188,13 +189,19 @@ func (this *AuthorityNodeDAO) GenUniqueId(tx *dbs.Tx) (string, error) {
}
// UpdateNodeStatus 更改节点状态
func (this *AuthorityNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, statusJSON []byte) error {
if statusJSON == nil {
func (this *AuthorityNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, nodeStatus *nodeconfigs.NodeStatus) error {
if nodeStatus == nil {
return nil
}
_, err := this.Query(tx).
nodeStatusJSON, err := json.Marshal(nodeStatus)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(nodeId).
Set("status", string(statusJSON)).
Set("status", nodeStatusJSON).
Update()
return err
}

View File

@@ -1,11 +1,15 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils/ttlcache"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strconv"
"github.com/iwind/TeaGo/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
const (
@@ -13,7 +17,20 @@ const (
ClientBrowserStateDisabled = 0 // 已禁用
)
var clientBrowserNameAndIdCacheMap = map[string]int64{}
func init() {
dbs.OnReadyDone(func() {
// 清理数据任务
var ticker = time.NewTicker(time.Duration(rands.Int(24, 48)) * time.Hour)
goman.New(func() {
for range ticker.C {
err := SharedClientBrowserDAO.Clean(nil, 7) // 只保留N天
if err != nil {
remotelogs.Error("SharedClientBrowserDAO", "clean expired data failed: "+err.Error())
}
}
})
})
}
type ClientBrowserDAO dbs.DAO
@@ -74,65 +91,64 @@ func (this *ClientBrowserDAO) FindClientBrowserName(tx *dbs.Tx, id uint32) (stri
FindStringCol("")
}
// FindBrowserIdWithNameCacheable 根据浏览器名称查找浏览器ID
func (this *ClientBrowserDAO) FindBrowserIdWithNameCacheable(tx *dbs.Tx, browserName string) (int64, error) {
SharedCacheLocker.RLock()
browserId, ok := clientBrowserNameAndIdCacheMap[browserName]
if ok {
SharedCacheLocker.RUnlock()
return browserId, nil
}
SharedCacheLocker.RUnlock()
browserId, err := this.Query(tx).
Where("JSON_CONTAINS(codes, :browserName)").
Param("browserName", strconv.Quote(browserName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if browserId > 0 {
// 只有找到的时候才放入缓存,以便于我们可以在不存在的时候创建一条新的记录
SharedCacheLocker.Lock()
clientBrowserNameAndIdCacheMap[browserName] = browserId
SharedCacheLocker.Unlock()
}
return browserId, nil
}
// CreateBrowser 创建浏览器
func (this *ClientBrowserDAO) CreateBrowser(tx *dbs.Tx, browserName string) (int64, error) {
var maxlength = 50
// CreateBrowserIfNotExists 创建浏览器信息
func (this *ClientBrowserDAO) CreateBrowserIfNotExists(tx *dbs.Tx, browserName string) error {
const maxlength = 50
if len(browserName) > maxlength {
browserName = browserName[:50]
}
SharedCacheLocker.Lock()
defer SharedCacheLocker.Unlock()
// 检查缓存
var cacheKey = "clientBrowser:" + browserName
var cacheItem = ttlcache.SharedCache.Read(cacheKey)
if cacheItem != nil {
return nil
}
// 检查是否已经创建
// 检查是否已经存在
// 不需要加状态条件
browserId, err := this.Query(tx).
Attr("name", browserName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
return err
}
if browserId > 0 {
return browserId, nil
// 加入缓存,但缓存时间不要过长,因为有别的操作在更新数据
ttlcache.SharedCache.Write(cacheKey, browserId, time.Now().Unix()+3600)
return this.Query(tx).
Pk(browserId).
Set("createdDay", timeutil.Format("Ymd")).
UpdateQuickly()
}
op := NewClientBrowserOperator()
// 如果不存在,则创建之
var op = NewClientBrowserOperator()
op.Name = browserName
codes := []string{browserName}
codesJSON, err := json.Marshal(codes)
if err != nil {
return 0, err
}
op.Codes = codesJSON
op.CreatedDay = timeutil.Format("Ymd")
op.State = ClientBrowserStateEnabled
return this.SaveInt64(tx, op)
browserId, err = this.SaveInt64(tx, op)
if err != nil && CheckSQLErrCode(err, 1062 /** duplicate entry **/) {
return nil
}
// 加入缓存,但缓存时间不要过长,因为有别的操作在更新数据
if browserId > 0 {
ttlcache.SharedCache.Write(cacheKey, browserId, time.Now().Unix()+3600)
}
return err
}
// Clean 清理
func (this *ClientBrowserDAO) Clean(tx *dbs.Tx, days int) error {
if days <= 0 {
days = 30
}
return this.Query(tx).
Lt("createdDay", timeutil.Format("Ymd", time.Now().AddDate(0, 0, -days))).
DeleteQuickly()
}

View File

@@ -1,5 +1,33 @@
package models
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
"testing"
)
func TestClientBrowserDAO_CreateBrowser(t *testing.T) {
var dao = models.NewClientBrowserDAO()
err := dao.CreateBrowserIfNotExists(nil, "Hello")
if err != nil {
t.Fatal(err)
}
err = dao.CreateBrowserIfNotExists(nil, "Hello")
if err != nil {
t.Fatal(err)
}
err = dao.CreateBrowserIfNotExists(nil, "Hello")
if err != nil {
t.Fatal(err)
}
}
func TestClientBrowserDAO_Clean(t *testing.T) {
var dao = models.NewClientBrowserDAO()
err := dao.Clean(nil, 30)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -4,17 +4,19 @@ import "github.com/iwind/TeaGo/dbs"
// ClientBrowser 终端浏览器信息
type ClientBrowser struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 浏览器名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 浏览器名称
Codes dbs.JSON `field:"codes"` // 代号
CreatedDay string `field:"createdDay"` // 创建日期YYYYMMDD
State uint8 `field:"state"` // 状态
}
type ClientBrowserOperator struct {
Id interface{} // ID
Name interface{} // 浏览器名称
Codes interface{} // 代号
State interface{} // 状态
Id any // ID
Name any // 浏览器名称
Codes any // 代号
CreatedDay any // 创建日期YYYYMMDD
State any // 状态
}
func NewClientBrowserOperator() *ClientBrowserOperator {

View File

@@ -1,11 +1,15 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils/ttlcache"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strconv"
"github.com/iwind/TeaGo/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
const (
@@ -13,7 +17,20 @@ const (
ClientSystemStateDisabled = 0 // 已禁用
)
var clientSystemNameAndIdCacheMap = map[string]int64{} // system name => id
func init() {
dbs.OnReadyDone(func() {
// 清理数据任务
var ticker = time.NewTicker(time.Duration(rands.Int(24, 48)) * time.Hour)
goman.New(func() {
for range ticker.C {
err := SharedClientSystemDAO.Clean(nil, 7) // 只保留N天
if err != nil {
remotelogs.Error("SharedClientSystemDAO", "clean expired data failed: "+err.Error())
}
}
})
})
}
type ClientSystemDAO dbs.DAO
@@ -74,67 +91,63 @@ func (this *ClientSystemDAO) FindClientSystemName(tx *dbs.Tx, id uint32) (string
FindStringCol("")
}
// FindSystemIdWithNameCacheable 根据操作系统名称查找系统ID
func (this *ClientSystemDAO) FindSystemIdWithNameCacheable(tx *dbs.Tx, systemName string) (int64, error) {
SharedCacheLocker.RLock()
systemId, ok := clientSystemNameAndIdCacheMap[systemName]
if ok {
SharedCacheLocker.RUnlock()
return systemId, nil
}
SharedCacheLocker.RUnlock()
systemId, err := this.Query(tx).
Where("JSON_CONTAINS(codes, :systemName)").
Param("systemName", strconv.Quote(systemName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if systemId > 0 {
// 只有找到的时候才放入缓存,以便于我们可以在不存在的时候创建一条新的记录
SharedCacheLocker.Lock()
clientSystemNameAndIdCacheMap[systemName] = systemId
SharedCacheLocker.Unlock()
}
return systemId, nil
}
// CreateSystem 创建浏览器
func (this *ClientSystemDAO) CreateSystem(tx *dbs.Tx, systemName string) (int64, error) {
var maxlength = 50
// CreateSystemIfNotExists 创建系统信息
func (this *ClientSystemDAO) CreateSystemIfNotExists(tx *dbs.Tx, systemName string) error {
const maxlength = 50
if len(systemName) > maxlength {
systemName = systemName[:50]
}
SharedCacheLocker.Lock()
defer SharedCacheLocker.Unlock()
// 检查缓存
var cacheKey = "clientSystem:" + systemName
var cacheItem = ttlcache.SharedCache.Read(cacheKey)
if cacheItem != nil {
return nil
}
// 检查是否已经创建
// 检查是否已经存在
// 不需要加状态条件
systemId, err := this.Query(tx).
Attr("name", systemName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
return err
}
if systemId > 0 {
return systemId, nil
// 加入缓存,但缓存时间不要过长,因为有别的操作在更新数据
ttlcache.SharedCache.Write(cacheKey, systemId, time.Now().Unix()+3600)
return this.Query(tx).
Pk(systemId).
Set("createdDay", timeutil.Format("Ymd")).
UpdateQuickly()
}
op := NewClientSystemOperator()
var op = NewClientSystemOperator()
op.Name = systemName
codes := []string{systemName}
codesJSON, err := json.Marshal(codes)
if err != nil {
return 0, err
}
op.Codes = codesJSON
op.CreatedDay = timeutil.Format("Ymd")
op.State = ClientSystemStateEnabled
return this.SaveInt64(tx, op)
systemId, err = this.SaveInt64(tx, op)
if err != nil && CheckSQLErrCode(err, 1062 /** duplicate entry **/) {
return nil
}
// 加入缓存,但缓存时间不要过长,因为有别的操作在更新数据
if systemId > 0 {
ttlcache.SharedCache.Write(cacheKey, systemId, time.Now().Unix()+3600)
}
return err
}
// Clean 清理
func (this *ClientSystemDAO) Clean(tx *dbs.Tx, days int) error {
if days <= 0 {
days = 30
}
return this.Query(tx).
Lt("createdDay", timeutil.Format("Ymd", time.Now().AddDate(0, 0, -days))).
DeleteQuickly()
}

View File

@@ -1,5 +1,31 @@
package models
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
"testing"
)
func TestClientSystemDAO_CreateSystemIfNotExists(t *testing.T) {
var dao = models.NewClientSystemDAO()
{
err := dao.CreateSystemIfNotExists(nil, "Mac OS X")
if err != nil {
t.Fatal(err)
}
}
{
err := dao.CreateSystemIfNotExists(nil, "Mac OS X 2")
if err != nil {
t.Fatal(err)
}
}
}
func TestClientSystemDAO_Clean(t *testing.T) {
var dao = models.NewClientSystemDAO()
err := dao.Clean(nil, 30)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -4,17 +4,19 @@ import "github.com/iwind/TeaGo/dbs"
// ClientSystem 终端操作系统信息
type ClientSystem struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 系统名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` //
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 系统名称
Codes dbs.JSON `field:"codes"` // 代号
CreatedDay string `field:"createdDay"` // 创建日期YYYYMMDD
State uint8 `field:"state"` // 状态
}
type ClientSystemOperator struct {
Id interface{} // ID
Name interface{} // 系统名称
Codes interface{} // 代号
State interface{} //
Id any // ID
Name any // 系统名称
Codes any // 代号
CreatedDay any // 创建日期YYYYMMDD
State any // 状态
}
func NewClientSystemOperator() *ClientSystemOperator {

View File

@@ -4,6 +4,7 @@ import (
"encoding/base64"
"github.com/TeaOSLab/EdgeAPI/internal/encrypt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -49,12 +50,17 @@ func (this *DBNodeDAO) EnableDBNode(tx *dbs.Tx, id int64) error {
}
// DisableDBNode 禁用条目
func (this *DBNodeDAO) DisableDBNode(tx *dbs.Tx, id int64) error {
func (this *DBNodeDAO) DisableDBNode(tx *dbs.Tx, nodeId int64) error {
_, err := this.Query(tx).
Pk(id).
Pk(nodeId).
Set("state", DBNodeStateDisabled).
Update()
return err
if err != nil {
return err
}
// 删除运行日志
return SharedNodeLogDAO.DeleteNodeLogs(tx, nodeconfigs.NodeRoleDatabase, nodeId)
}
// FindEnabledDBNode 查找启用中的条目
@@ -103,7 +109,7 @@ func (this *DBNodeDAO) ListEnabledNodes(tx *dbs.Tx, offset int64, size int64) (r
// CreateDBNode 创建节点
func (this *DBNodeDAO) CreateDBNode(tx *dbs.Tx, isOn bool, name string, description string, host string, port int32, database string, username string, password string, charset string) (int64, error) {
op := NewDBNodeOperator()
var op = NewDBNodeOperator()
op.State = NodeStateEnabled
op.IsOn = isOn
op.Name = name
@@ -126,7 +132,7 @@ func (this *DBNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, isOn bool, name stri
if nodeId <= 0 {
return errors.New("invalid nodeId")
}
op := NewDBNodeOperator()
var op = NewDBNodeOperator()
op.Id = nodeId
op.IsOn = isOn
op.Name = name

View File

@@ -2,18 +2,15 @@ package models
import (
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"hash/crc32"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
@@ -31,20 +28,11 @@ type httpAccessLogDefinition struct {
// HTTP服务访问
var httpAccessLogDAOMapping = map[int64]*HTTPAccessLogDAOWrapper{} // dbNodeId => DAO
// DNS服务访问
var nsAccessLogDAOMapping = map[int64]*NSAccessLogDAOWrapper{} // dbNodeId => DAO
var nsAccessLogTableMapping = map[string]bool{} // tableName_crc(dsn) => true
// HTTPAccessLogDAOWrapper HTTP访问日志DAO
type HTTPAccessLogDAOWrapper struct {
DAO *HTTPAccessLogDAO
NodeId int64
}
// NSAccessLogDAOWrapper NS访问日志DAO
type NSAccessLogDAOWrapper struct {
DAO *NSAccessLogDAO
NodeId int64
DAO *HTTPAccessLogDAO
NodeId int64
IsLocal bool
}
func init() {
@@ -78,102 +66,28 @@ func AllAccessLogDBs() []*dbs.DB {
// 获取获取DAO
func randomHTTPAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
accessLogLocker.RLock()
defer accessLogLocker.RUnlock()
if len(httpAccessLogDAOMapping) == 0 {
dao = nil
} else {
for _, d := range httpAccessLogDAOMapping {
dao = d
break
}
}
accessLogLocker.RUnlock()
return
}
func randomNSAccessLogDAO() (dao *NSAccessLogDAOWrapper) {
accessLogLocker.RLock()
if len(nsAccessLogDAOMapping) == 0 {
dao = nil
} else {
for _, d := range nsAccessLogDAOMapping {
dao = d
break
}
}
accessLogLocker.RUnlock()
return
}
func findNSAccessLogTableName(db *dbs.DB, day string) (tableName string, ok bool, err error) {
if !regexp.MustCompile(`^\d{8}$`).MatchString(day) {
err = errors.New("invalid day '" + day + "', should be YYYYMMDD")
return
}
config, err := db.Config()
if err != nil {
return "", false, err
var daoList = []*HTTPAccessLogDAOWrapper{}
for _, d := range httpAccessLogDAOMapping {
daoList = append(daoList, d)
}
tableName = "edgeNSAccessLogs_" + day
cacheKey := tableName + "_" + fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(config.Dsn)))
accessLogLocker.RLock()
_, ok = nsAccessLogTableMapping[cacheKey]
accessLogLocker.RUnlock()
if ok {
return tableName, true, nil
var l = len(daoList)
if l == 0 {
return
}
tableNames, err := db.TableNames()
if err != nil {
return tableName, false, err
if l == 1 {
return daoList[0]
}
return tableName, utils.ContainsStringInsensitive(tableNames, tableName), nil
}
func findNSAccessLogTable(db *dbs.DB, day string, force bool) (string, error) {
config, err := db.Config()
if err != nil {
return "", err
}
tableName := "edgeNSAccessLogs_" + day
cacheKey := tableName + "_" + fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(config.Dsn)))
if !force {
accessLogLocker.RLock()
_, ok := nsAccessLogTableMapping[cacheKey]
accessLogLocker.RUnlock()
if ok {
return tableName, nil
}
}
tableNames, err := db.TableNames()
if err != nil {
return tableName, err
}
if utils.ContainsStringInsensitive(tableNames, tableName) {
accessLogLocker.Lock()
nsAccessLogTableMapping[cacheKey] = true
accessLogLocker.Unlock()
return tableName, nil
}
// 创建表格
_, err = db.Exec("CREATE TABLE `" + tableName + "` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `nodeId` int(11) unsigned DEFAULT '0' COMMENT '节点ID',\n `domainId` int(11) unsigned DEFAULT '0' COMMENT '域名ID',\n `recordId` int(11) unsigned DEFAULT '0' COMMENT '记录ID',\n `content` json DEFAULT NULL COMMENT '访问数据',\n `requestId` varchar(128) DEFAULT NULL COMMENT '请求ID',\n `createdAt` bigint(11) unsigned DEFAULT '0' COMMENT '创建时间',\n `remoteAddr` varchar(128) DEFAULT NULL COMMENT 'IP',\n PRIMARY KEY (`id`),\n KEY `nodeId` (`nodeId`),\n KEY `domainId` (`domainId`),\n KEY `recordId` (`recordId`),\n KEY `requestId` (`requestId`),\n KEY `remoteAddr` (`remoteAddr`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='域名服务访问日志';")
if err != nil {
return tableName, err
}
accessLogLocker.Lock()
nsAccessLogTableMapping[cacheKey] = true
accessLogLocker.Unlock()
return tableName, nil
return daoList[rands.Int(0, l-1)]
}
// DBNodeInitializer 初始化数据库连接
@@ -209,14 +123,14 @@ func (this *DBNodeInitializer) loop() error {
return err
}
nodeIds := []int64{}
var nodeIds = []int64{}
for _, node := range dbNodes {
nodeIds = append(nodeIds, int64(node.Id))
}
// 关掉老的
accessLogLocker.Lock()
closingDbs := []*dbs.DB{}
var closingDbs = []*dbs.DB{}
for nodeId, db := range accessLogDBMapping {
if !lists.ContainsInt64(nodeIds, nodeId) {
closingDbs = append(closingDbs, db)
@@ -233,12 +147,12 @@ func (this *DBNodeInitializer) loop() error {
// 启动新的
for _, node := range dbNodes {
nodeId := int64(node.Id)
var nodeId = int64(node.Id)
accessLogLocker.Lock()
db, ok := accessLogDBMapping[nodeId]
accessLogLocker.Unlock()
dsn := node.Username + ":" + node.Password + "@tcp(" + node.Host + ":" + fmt.Sprintf("%d", node.Port) + ")/" + node.Database + "?charset=utf8mb4&timeout=10s"
var dsn = node.Username + ":" + node.Password + "@tcp(" + node.Host + ":" + fmt.Sprintf("%d", node.Port) + ")/" + node.Database + "?charset=utf8mb4&timeout=10s"
if ok {
// 检查配置是否有变化
@@ -283,7 +197,7 @@ func (this *DBNodeInitializer) loop() error {
continue
}
daoObject := dbs.DAOObject{
var daoObject = dbs.DAOObject{
Instance: db,
DB: node.Name + "(id:" + strconv.Itoa(int(node.Id)) + ")",
Table: tableDef.Name,
@@ -298,59 +212,19 @@ func (this *DBNodeInitializer) loop() error {
accessLogLocker.Lock()
accessLogDBMapping[nodeId] = db
dao := &HTTPAccessLogDAO{
var dao = &HTTPAccessLogDAO{
DAOObject: daoObject,
}
httpAccessLogDAOMapping[nodeId] = &HTTPAccessLogDAOWrapper{
DAO: dao,
NodeId: nodeId,
DAO: dao,
NodeId: nodeId,
IsLocal: dbutils.IsLocalAddr(node.Host),
}
accessLogLocker.Unlock()
}
// nsAccessLog
{
tableName, err := findNSAccessLogTable(db, timeutil.Format("Ymd"), false)
if err != nil {
if !strings.Contains(err.Error(), "1050") { // 非表格已存在错误
remotelogs.Error("DB_NODE", "create first table in database node failed: "+err.Error())
// 创建节点日志
createLogErr := SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleDatabase, nodeId, 0, 0, "error", "ACCESS_LOG", "can not create access log table: "+err.Error(), time.Now().Unix(), "", nil)
if createLogErr != nil {
remotelogs.Error("NODE_LOG", createLogErr.Error())
}
continue
} else {
err = nil
}
}
daoObject := dbs.DAOObject{
Instance: db,
DB: node.Name + "(id:" + strconv.Itoa(int(node.Id)) + ")",
Table: tableName,
PkName: "id",
Model: new(NSAccessLog),
}
err = daoObject.Init()
if err != nil {
remotelogs.Error("DB_NODE", "initialize dao failed: "+err.Error())
continue
}
accessLogLocker.Lock()
accessLogDBMapping[nodeId] = db
dao := &NSAccessLogDAO{
DAOObject: daoObject,
}
nsAccessLogDAOMapping[nodeId] = &NSAccessLogDAOWrapper{
DAO: dao,
NodeId: nodeId,
}
accessLogLocker.Unlock()
}
// 扩展
initAccessLogDAO(db, node)
}
}

View File

@@ -0,0 +1,11 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package models
import "github.com/iwind/TeaGo/dbs"
var nsAccessLogDAOMapping = map[int64]any{} // dbNodeId => DAO
func initAccessLogDAO(db *dbs.DB, node *DBNode) {
}

View File

@@ -91,7 +91,7 @@ func (this *DNSDomainDAO) FindDNSDomainName(tx *dbs.Tx, id int64) (string, error
// CreateDomain 创建域名
func (this *DNSDomainDAO) CreateDomain(tx *dbs.Tx, adminId int64, userId int64, providerId int64, name string) (int64, error) {
op := NewDNSDomainOperator()
var op = NewDNSDomainOperator()
op.ProviderId = providerId
op.AdminId = adminId
op.UserId = userId
@@ -111,7 +111,7 @@ func (this *DNSDomainDAO) UpdateDomain(tx *dbs.Tx, domainId int64, name string,
if domainId <= 0 {
return errors.New("invalid domainId")
}
op := NewDNSDomainOperator()
var op = NewDNSDomainOperator()
op.Id = domainId
op.Name = name
op.IsOn = isOn
@@ -133,11 +133,28 @@ func (this *DNSDomainDAO) FindAllEnabledDomainsWithProviderId(tx *dbs.Tx, provid
return
}
// ListDomains 列出单页域名
func (this *DNSDomainDAO) ListDomains(tx *dbs.Tx, providerId int64, isDeleted bool, isUp bool, offset int64, size int64) (result []*DNSDomain, err error) {
_, err = this.Query(tx).
State(DNSDomainStateEnabled).
Attr("providerId", providerId).
Attr("isDeleted", isDeleted).
Attr("isUp", isUp).
AscPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// CountAllEnabledDomainsWithProviderId 计算某个服务商下的域名数量
func (this *DNSDomainDAO) CountAllEnabledDomainsWithProviderId(tx *dbs.Tx, providerId int64) (int64, error) {
func (this *DNSDomainDAO) CountAllEnabledDomainsWithProviderId(tx *dbs.Tx, providerId int64, isDeleted bool, isUp bool) (int64, error) {
return this.Query(tx).
State(DNSDomainStateEnabled).
Attr("providerId", providerId).
Attr("isDeleted", isDeleted).
Attr("isUp", isUp).
Count()
}
@@ -146,7 +163,7 @@ func (this *DNSDomainDAO) UpdateDomainData(tx *dbs.Tx, domainId int64, data stri
if domainId <= 0 {
return errors.New("invalid domainId")
}
op := NewDNSDomainOperator()
var op = NewDNSDomainOperator()
op.Id = domainId
op.Data = data
err := this.Save(tx, op)
@@ -158,7 +175,7 @@ func (this *DNSDomainDAO) UpdateDomainRecords(tx *dbs.Tx, domainId int64, record
if domainId <= 0 {
return errors.New("invalid domainId")
}
op := NewDNSDomainOperator()
var op = NewDNSDomainOperator()
op.Id = domainId
op.Records = recordsJSON
op.DataUpdatedAt = time.Now().Unix()
@@ -171,7 +188,7 @@ func (this *DNSDomainDAO) UpdateDomainRoutes(tx *dbs.Tx, domainId int64, routesJ
if domainId <= 0 {
return errors.New("invalid domainId")
}
op := NewDNSDomainOperator()
var op = NewDNSDomainOperator()
op.Id = domainId
op.Routes = routesJSON
op.DataUpdatedAt = time.Now().Unix()

View File

@@ -68,7 +68,7 @@ func (this *DNSProviderDAO) FindEnabledDNSProvider(tx *dbs.Tx, id int64) (*DNSPr
// CreateDNSProvider 创建服务商
func (this *DNSProviderDAO) CreateDNSProvider(tx *dbs.Tx, adminId int64, userId int64, providerType string, name string, apiParamsJSON []byte) (int64, error) {
op := NewDNSProviderOperator()
var op = NewDNSProviderOperator()
op.AdminId = adminId
op.UserId = userId
op.Type = providerType
@@ -90,7 +90,7 @@ func (this *DNSProviderDAO) UpdateDNSProvider(tx *dbs.Tx, dnsProviderId int64, n
return errors.New("invalid dnsProviderId")
}
op := NewDNSProviderOperator()
var op = NewDNSProviderOperator()
op.Id = dnsProviderId
op.Name = name
@@ -107,7 +107,7 @@ func (this *DNSProviderDAO) UpdateDNSProvider(tx *dbs.Tx, dnsProviderId int64, n
}
// CountAllEnabledDNSProviders 计算服务商数量
func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string) (int64, error) {
func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, providerType string) (int64, error) {
var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
@@ -119,12 +119,16 @@ func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int6
query.Param("domain", domain)
}
if len(providerType) > 0 {
query.Attr("type", providerType)
}
return query.State(DNSProviderStateEnabled).
Count()
}
// ListEnabledDNSProviders 列出单页服务商
func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, offset int64, size int64) (result []*DNSProvider, err error) {
func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, providerType string, offset int64, size int64) (result []*DNSProvider, err error) {
var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
@@ -134,6 +138,9 @@ func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, u
query.Where("id IN (SELECT providerId FROM " + SharedDNSDomainDAO.Table + " WHERE state=1 AND name=:domain)")
query.Param("domain", domain)
}
if len(providerType) > 0 {
query.Attr("type", providerType)
}
_, err = query.
State(DNSProviderStateEnabled).
Offset(offset).

View File

@@ -56,11 +56,13 @@ func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int6
"isDone": false,
"isOk": false,
"error": "",
"version": time.Now().UnixNano(),
}, maps.Map{
"updatedAt": time.Now().Unix(),
"isDone": false,
"isOk": false,
"error": "",
"version": time.Now().UnixNano(),
})
return err
}
@@ -76,8 +78,8 @@ func (this *DNSTaskDAO) CreateClusterRemoveTask(tx *dbs.Tx, clusterId int64, dom
}
// CreateNodeTask 生成节点任务
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, "", taskType)
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, clusterId int64, nodeId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, clusterId, 0, nodeId, 0, "", taskType)
}
// CreateServerTask 生成服务任务
@@ -94,6 +96,7 @@ func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DN
func (this *DNSTaskDAO) FindAllDoingTasks(tx *dbs.Tx) (result []*DNSTask, err error) {
_, err = this.Query(tx).
Attr("isDone", 0).
Asc("version").
AscPk().
Slice(&result).
FindAll()
@@ -109,6 +112,7 @@ func (this *DNSTaskDAO) FindAllDoingOrErrorTasks(tx *dbs.Tx, nodeClusterId int64
_, err = query.
Where("(isDone=0 OR (isDone=1 AND isOk=0))").
Asc("updatedAt").
Asc("version").
AscPk().
Slice(&result).
FindAll()
@@ -143,7 +147,7 @@ func (this *DNSTaskDAO) UpdateDNSTaskError(tx *dbs.Tx, taskId int64, err string)
if taskId <= 0 {
return errors.New("invalid taskId")
}
op := NewDNSTaskOperator()
var op = NewDNSTaskOperator()
op.Id = taskId
op.IsDone = true
op.Error = err
@@ -156,10 +160,20 @@ func (this *DNSTaskDAO) UpdateDNSTaskDone(tx *dbs.Tx, taskId int64) error {
if taskId <= 0 {
return errors.New("invalid taskId")
}
op := NewDNSTaskOperator()
var op = NewDNSTaskOperator()
op.Id = taskId
op.IsDone = true
op.IsOk = true
op.Error = ""
return this.Save(tx, op)
}
// DeleteDNSTasksWithClusterId 删除集群相关任务
func (this *DNSTaskDAO) DeleteDNSTasksWithClusterId(tx *dbs.Tx, clusterId int64) error {
if clusterId <= 0 {
return nil
}
return this.Query(tx).
Attr("clusterId", clusterId).
DeleteQuickly()
}

View File

@@ -9,7 +9,7 @@ import (
func TestDNSTaskDAO_CreateDNSTask(t *testing.T) {
dbs.NotifyReady()
err := SharedDNSTaskDAO.CreateDNSTask(nil, 1, 2, 3, 0, "taskType")
err := SharedDNSTaskDAO.CreateDNSTask(nil, 1, 2, 3, 0, "cdn", "taskType")
if err != nil {
t.Fatal(err)
}

View File

@@ -13,20 +13,22 @@ type DNSTask struct {
IsDone bool `field:"isDone"` // 是否已完成
IsOk bool `field:"isOk"` // 是否成功
Error string `field:"error"` // 错误信息
Version uint64 `field:"version"` // 版本
}
type DNSTaskOperator struct {
Id interface{} // ID
ClusterId interface{} // 集群ID
ServerId interface{} // 服务ID
NodeId interface{} // 节点ID
DomainId interface{} // 域名ID
RecordName interface{} // 记录名
Type interface{} // 任务类型
UpdatedAt interface{} // 更新时间
IsDone interface{} // 是否已完成
IsOk interface{} // 是否成功
Error interface{} // 错误信息
Id any // ID
ClusterId any // 集群ID
ServerId any // 服务ID
NodeId any // 节点ID
DomainId any // 域名ID
RecordName any // 记录名
Type any // 任务类型
UpdatedAt any // 更新时间
IsDone any // 是否已完成
IsOk any // 是否成功
Error any // 错误信息
Version any // 版本
}
func NewDNSTaskOperator() *DNSTaskOperator {

View File

@@ -15,9 +15,12 @@ import (
// CheckClusterDNS 检查集群的DNS问题
// 藏这么深是避免package循环引用的问题
func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster) (issues []*pb.DNSIssue, err error) {
clusterId := int64(cluster.Id)
domainId := int64(cluster.DnsDomainId)
func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster, checkNodeIssues bool) (issues []*pb.DNSIssue, err error) {
var clusterId = int64(cluster.Id)
var domainId = int64(cluster.DnsDomainId)
// 集群DNS设置
var clusterDNSConfig, _ = cluster.DecodeDNSConfig()
// 检查域名
domain, err := dns.SharedDNSDomainDAO.FindEnabledDNSDomain(tx, domainId, nil)
@@ -64,7 +67,7 @@ func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster) (issues []*pb.DNSI
})
return
}
var dnsProvider = dnsclients.FindProvider(provider.Type)
var dnsProvider = dnsclients.FindProvider(provider.Type, int64(provider.Id))
if dnsProvider == nil {
issues = append(issues, &pb.DNSIssue{
Target: cluster.Name,
@@ -101,47 +104,27 @@ func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster) (issues []*pb.DNSI
// TODO 检查域名是否已解析
// 检查节点
nodes, err := models.SharedNodeDAO.FindAllEnabledNodesDNSWithClusterId(tx, clusterId, true)
if err != nil {
return nil, err
}
// TODO 检查节点数量不能为0
for _, node := range nodes {
nodeId := int64(node.Id)
routeCodes, err := node.DNSRouteCodesForDomainId(domainId)
if checkNodeIssues {
nodes, err := models.SharedNodeDAO.FindAllEnabledNodesDNSWithClusterId(tx, clusterId, true, clusterDNSConfig != nil && clusterDNSConfig.IncludingLnNodes)
if err != nil {
return nil, err
}
if len(routeCodes) == 0 && !hasDefaultRoute {
issues = append(issues, &pb.DNSIssue{
Target: node.Name,
TargetId: nodeId,
Type: "node",
Description: "没有选择节点所属线路",
Params: map[string]string{
"clusterName": cluster.Name,
"clusterId": numberutils.FormatInt64(clusterId),
},
MustFix: true,
})
continue
}
// 检查线路是否在已有线路中
for _, routeCode := range routeCodes {
routeOk, err := domain.ContainsRouteCode(routeCode)
// TODO 检查节点数量不能为0
for _, node := range nodes {
nodeId := int64(node.Id)
routeCodes, err := node.DNSRouteCodesForDomainId(domainId)
if err != nil {
return nil, err
}
if !routeOk {
if len(routeCodes) == 0 && !hasDefaultRoute {
issues = append(issues, &pb.DNSIssue{
Target: node.Name,
TargetId: nodeId,
Type: "node",
Description: "线路已经失效,请重新选择",
Description: "没有选择节点所属线路",
Params: map[string]string{
"clusterName": cluster.Name,
"clusterId": numberutils.FormatInt64(clusterId),
@@ -150,29 +133,51 @@ func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster) (issues []*pb.DNSI
})
continue
}
}
// 检查IP地址
ipAddr, _, err := models.SharedNodeIPAddressDAO.FindFirstNodeAccessIPAddress(tx, nodeId, true, nodeconfigs.NodeRoleNode)
if err != nil {
return nil, err
}
if len(ipAddr) == 0 {
issues = append(issues, &pb.DNSIssue{
Target: node.Name,
TargetId: nodeId,
Type: "node",
Description: "没有设置IP地址",
Params: map[string]string{
"clusterName": cluster.Name,
"clusterId": numberutils.FormatInt64(clusterId),
},
MustFix: true,
})
continue
}
// 检查线路是否在已有线路中
for _, routeCode := range routeCodes {
routeOk, err := domain.ContainsRouteCode(routeCode)
if err != nil {
return nil, err
}
if !routeOk {
issues = append(issues, &pb.DNSIssue{
Target: node.Name,
TargetId: nodeId,
Type: "node",
Description: "线路已经失效,请重新选择",
Params: map[string]string{
"clusterName": cluster.Name,
"clusterId": numberutils.FormatInt64(clusterId),
},
MustFix: true,
})
continue
}
}
// TODO 检查是否有解析记录
// 检查IP地址
ipAddr, _, err := models.SharedNodeIPAddressDAO.FindFirstNodeAccessIPAddress(tx, nodeId, true, nodeconfigs.NodeRoleNode)
if err != nil {
return nil, err
}
if len(ipAddr) == 0 {
issues = append(issues, &pb.DNSIssue{
Target: node.Name,
TargetId: nodeId,
Type: "node",
Description: "没有设置IP地址",
Params: map[string]string{
"clusterName": cluster.Name,
"clusterId": numberutils.FormatInt64(clusterId),
},
MustFix: true,
})
continue
}
// TODO 检查是否有解析记录
}
}
return
@@ -195,7 +200,7 @@ func FindDefaultDomainRoute(tx *dbs.Tx, domain *dns.DNSDomain) (string, error) {
if err != nil {
return "", errors.New("decode provider params failed: " + err.Error())
}
var dnsProvider = dnsclients.FindProvider(provider.Type)
var dnsProvider = dnsclients.FindProvider(provider.Type, int64(provider.Id))
if dnsProvider == nil {
return "", errors.New("not supported provider type '" + provider.Type + "'")
}

View File

@@ -21,7 +21,7 @@ func TestNodeClusterDAO_CheckClusterDNS(t *testing.T) {
t.Log("cluster not found, skip the test")
return
}
issues, err := CheckClusterDNS(tx, cluster)
issues, err := CheckClusterDNS(tx, cluster, true)
if err != nil {
t.Fatal(err)
}

View File

@@ -29,9 +29,9 @@ func init() {
})
}
// 创建文件Chunk
// CreateFileChunk 创建文件Chunk
func (this *FileChunkDAO) CreateFileChunk(tx *dbs.Tx, fileId int64, data []byte) (int64, error) {
op := NewFileChunkOperator()
var op = NewFileChunkOperator()
op.FileId = fileId
op.Data = data
err := this.Save(tx, op)
@@ -41,7 +41,7 @@ func (this *FileChunkDAO) CreateFileChunk(tx *dbs.Tx, fileId int64, data []byte)
return types.Int64(op.Id), nil
}
// 列出所有的文件Chunk
// FindAllFileChunks 列出所有的文件Chunk
func (this *FileChunkDAO) FindAllFileChunks(tx *dbs.Tx, fileId int64) (result []*FileChunk, err error) {
_, err = this.Query(tx).
Attr("fileId", fileId).
@@ -51,7 +51,7 @@ func (this *FileChunkDAO) FindAllFileChunks(tx *dbs.Tx, fileId int64) (result []
return
}
// 读取文件的所有片段ID
// FindAllFileChunkIds 读取文件的所有片段ID
func (this *FileChunkDAO) FindAllFileChunkIds(tx *dbs.Tx, fileId int64) ([]int64, error) {
ones, err := this.Query(tx).
Attr("fileId", fileId).
@@ -68,7 +68,7 @@ func (this *FileChunkDAO) FindAllFileChunkIds(tx *dbs.Tx, fileId int64) ([]int64
return result, nil
}
// 删除以前的文件
// DeleteFileChunks 删除以前的文件
func (this *FileChunkDAO) DeleteFileChunks(tx *dbs.Tx, fileId int64) error {
if fileId <= 0 {
return errors.New("invalid fileId")
@@ -79,7 +79,7 @@ func (this *FileChunkDAO) DeleteFileChunks(tx *dbs.Tx, fileId int64) error {
return err
}
// 根据ID查找片段
// FindFileChunk 根据ID查找片段
func (this *FileChunkDAO) FindFileChunk(tx *dbs.Tx, chunkId int64) (*FileChunk, error) {
one, err := this.Query(tx).
Pk(chunkId).

View File

@@ -1,6 +1,7 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -33,7 +34,7 @@ func init() {
})
}
// 启用条目
// EnableFile 启用条目
func (this *FileDAO) EnableFile(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -42,7 +43,7 @@ func (this *FileDAO) EnableFile(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableFile 禁用条目
func (this *FileDAO) DisableFile(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -51,7 +52,7 @@ func (this *FileDAO) DisableFile(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
// FindEnabledFile 查找启用中的条目
func (this *FileDAO) FindEnabledFile(tx *dbs.Tx, id int64) (*File, error) {
result, err := this.Query(tx).
Pk(id).
@@ -63,9 +64,9 @@ func (this *FileDAO) FindEnabledFile(tx *dbs.Tx, id int64) (*File, error) {
return result.(*File), err
}
// 创建文件
func (this *FileDAO) CreateFile(tx *dbs.Tx, adminId int64, userId int64, businessType, description string, filename string, size int64, isPublic bool) (int64, error) {
op := NewFileOperator()
// CreateFile 创建文件
func (this *FileDAO) CreateFile(tx *dbs.Tx, adminId int64, userId int64, businessType string, description string, filename string, size int64, mimeType string, isPublic bool) (int64, error) {
var op = NewFileOperator()
op.AdminId = adminId
op.UserId = userId
op.Type = businessType
@@ -74,6 +75,8 @@ func (this *FileDAO) CreateFile(tx *dbs.Tx, adminId int64, userId int64, busines
op.Size = size
op.Filename = filename
op.IsPublic = isPublic
op.Code = utils.Sha1RandomString()
op.MimeType = mimeType
err := this.Save(tx, op)
if err != nil {
return 0, err
@@ -82,7 +85,22 @@ func (this *FileDAO) CreateFile(tx *dbs.Tx, adminId int64, userId int64, busines
return types.Int64(op.Id), nil
}
// 将文件置为已完成
// CheckUserFile 检查用户ID
func (this *FileDAO) CheckUserFile(tx *dbs.Tx, userId int64, fileId int64) error {
b, err := this.Query(tx).
Pk(fileId).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return ErrNotFound
}
return nil
}
// UpdateFileIsFinished 将文件置为已完成
func (this *FileDAO) UpdateFileIsFinished(tx *dbs.Tx, fileId int64) error {
_, err := this.Query(tx).
Pk(fileId).

View File

@@ -1,13 +1,15 @@
package models
// 文件管理
// File 文件管理
type File struct {
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
Code string `field:"code"` // 代号
UserId uint32 `field:"userId"` // 用户ID
Description string `field:"description"` // 文件描述
Filename string `field:"filename"` // 文件名
Size uint32 `field:"size"` // 文件尺寸
MimeType string `field:"mimeType"` // Mime类型
CreatedAt uint64 `field:"createdAt"` // 创建时间
Order uint32 `field:"order"` // 排序
Type string `field:"type"` // 类型
@@ -19,10 +21,12 @@ type File struct {
type FileOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
Code interface{} // 代号
UserId interface{} // 用户ID
Description interface{} // 文件描述
Filename interface{} // 文件名
Size interface{} // 文件尺寸
MimeType interface{} // Mime类型
CreatedAt interface{} // 创建时间
Order interface{} // 排序
Type interface{} // 类型

View File

@@ -0,0 +1,207 @@
package models
import (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils/ttlcache"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"strconv"
"strings"
"time"
)
const (
FormalClientBrowserStateEnabled = 1 // 已启用
FormalClientBrowserStateDisabled = 0 // 已禁用
)
type FormalClientBrowserDAO dbs.DAO
func NewFormalClientBrowserDAO() *FormalClientBrowserDAO {
return dbs.NewDAO(&FormalClientBrowserDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeFormalClientBrowsers",
Model: new(FormalClientBrowser),
PkName: "id",
},
}).(*FormalClientBrowserDAO)
}
var SharedFormalClientBrowserDAO *FormalClientBrowserDAO
func init() {
dbs.OnReady(func() {
SharedFormalClientBrowserDAO = NewFormalClientBrowserDAO()
})
}
// EnableFormalClientBrowser 启用条目
func (this *FormalClientBrowserDAO) EnableFormalClientBrowser(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", FormalClientBrowserStateEnabled).
Update()
return err
}
// DisableFormalClientBrowser 禁用条目
func (this *FormalClientBrowserDAO) DisableFormalClientBrowser(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", FormalClientBrowserStateDisabled).
Update()
return err
}
// FindEnabledFormalClientBrowser 查找启用中的条目
func (this *FormalClientBrowserDAO) FindEnabledFormalClientBrowser(tx *dbs.Tx, id int64) (*FormalClientBrowser, error) {
result, err := this.Query(tx).
Pk(id).
State(FormalClientBrowserStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*FormalClientBrowser), err
}
// FindFormalClientBrowserName 根据主键查找名称
func (this *FormalClientBrowserDAO) FindFormalClientBrowserName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// FindBrowserIdWithNameCacheable 根据浏览器名称查找系统ID
func (this *FormalClientBrowserDAO) FindBrowserIdWithNameCacheable(tx *dbs.Tx, browserName string) (int64, error) {
var cacheKey = "formalClientBrowser:" + browserName
var cacheItem = ttlcache.SharedCache.Read(cacheKey)
if cacheItem != nil {
return types.Int64(cacheItem.Value), nil
}
// 先使用 name 查找,因为有索引,所以会快一些
browserId, err := this.Query(tx).
Attr("name", browserName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if browserId == 0 {
browserId, err = this.Query(tx).
Where("JSON_CONTAINS(codes, :browserName)").
Param("browserName", strconv.Quote(browserName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
}
// 即使找不到也要放入到缓存中
ttlcache.SharedCache.Write(cacheKey, browserId, time.Now().Unix()+3600)
return browserId, nil
}
// CountBrowsers 计算浏览器数量
func (this *FormalClientBrowserDAO) CountBrowsers(tx *dbs.Tx, keyword string) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Like("LOWER(codes)", dbutils.QuoteLikeKeyword(strings.ToLower(keyword)))
}
return query.Count()
}
// ListBrowsers 列出单页浏览器信息
func (this *FormalClientBrowserDAO) ListBrowsers(tx *dbs.Tx, keyword string, offset int64, size int64) (result []*FormalClientBrowser, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Like("LOWER(codes)", dbutils.QuoteLikeKeyword(strings.ToLower(keyword)))
}
_, err = query.
Offset(offset).
Limit(size).
DescPk().
Slice(&result).
FindAll()
return
}
// FindBrowserWithDataId 根据dataId查找浏览器信息
func (this *FormalClientBrowserDAO) FindBrowserWithDataId(tx *dbs.Tx, dataId string) (*FormalClientBrowser, error) {
one, err := this.Query(tx).
Attr("dataId", dataId).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*FormalClientBrowser), nil
}
// CreateBrowser 创建浏览器信息
func (this *FormalClientBrowserDAO) CreateBrowser(tx *dbs.Tx, name string, codes []string, dataId string) (int64, error) {
if len(dataId) == 0 {
return 0, errors.New("invalid dataId")
}
// 检查 dataId 是否已经存在
exists, err := this.Query(tx).
Attr("dataId", dataId).
Exist()
if err != nil {
return 0, err
}
if exists {
return 0, errors.New("dataId '" + dataId + "' already exists")
}
var op = NewFormalClientBrowserOperator()
op.Name = name
if len(codes) == 0 {
op.Codes = "[]"
} else {
codesJSON, err := json.Marshal(codes)
if err != nil {
return 0, err
}
op.Codes = codesJSON
}
op.DataId = dataId
op.State = FormalClientBrowserStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateBrowser 修改浏览器信息
func (this *FormalClientBrowserDAO) UpdateBrowser(tx *dbs.Tx, browserId int64, name string, codes []string, dataId string) error {
if browserId <= 0 {
return errors.New("invalid browserId '" + types.String(browserId) + "'")
}
if len(dataId) == 0 {
return errors.New("invalid dataId")
}
var op = NewFormalClientBrowserOperator()
op.Id = browserId
op.Name = name
if len(codes) == 0 {
op.Codes = "[]"
} else {
codesJSON, err := json.Marshal(codes)
if err != nil {
return err
}
op.Codes = codesJSON
}
op.DataId = dataId
return this.Save(tx, op)
}

View File

@@ -1,4 +1,4 @@
package accounts
package models_test
import (
_ "github.com/go-sql-driver/mysql"

View File

@@ -0,0 +1,24 @@
package models
import "github.com/iwind/TeaGo/dbs"
// FormalClientBrowser 终端浏览器信息
type FormalClientBrowser struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 浏览器名称
Codes dbs.JSON `field:"codes"` // 代号
DataId string `field:"dataId"` // 数据ID
State uint8 `field:"state"` // 状态
}
type FormalClientBrowserOperator struct {
Id any // ID
Name any // 浏览器名称
Codes any // 代号
DataId any // 数据ID
State any // 状态
}
func NewFormalClientBrowserOperator() *FormalClientBrowserOperator {
return &FormalClientBrowserOperator{}
}

View File

@@ -0,0 +1,21 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
)
// DecodeCodes 解析代号
func (this *FormalClientBrowser) DecodeCodes() []string {
if IsNull(this.Codes) {
return nil
}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
remotelogs.Error("FormalClientBrowser.DecodeCodes", err.Error())
}
return result
}

View File

@@ -0,0 +1,207 @@
package models
import (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils/ttlcache"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"strconv"
"strings"
"time"
)
const (
FormalClientSystemStateEnabled = 1 // 已启用
FormalClientSystemStateDisabled = 0 // 已禁用
)
type FormalClientSystemDAO dbs.DAO
func NewFormalClientSystemDAO() *FormalClientSystemDAO {
return dbs.NewDAO(&FormalClientSystemDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeFormalClientSystems",
Model: new(FormalClientSystem),
PkName: "id",
},
}).(*FormalClientSystemDAO)
}
var SharedFormalClientSystemDAO *FormalClientSystemDAO
func init() {
dbs.OnReady(func() {
SharedFormalClientSystemDAO = NewFormalClientSystemDAO()
})
}
// EnableFormalClientSystem 启用条目
func (this *FormalClientSystemDAO) EnableFormalClientSystem(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", FormalClientSystemStateEnabled).
Update()
return err
}
// DisableFormalClientSystem 禁用条目
func (this *FormalClientSystemDAO) DisableFormalClientSystem(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", FormalClientSystemStateDisabled).
Update()
return err
}
// FindEnabledFormalClientSystem 查找启用中的条目
func (this *FormalClientSystemDAO) FindEnabledFormalClientSystem(tx *dbs.Tx, id int64) (*FormalClientSystem, error) {
result, err := this.Query(tx).
Pk(id).
State(FormalClientSystemStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*FormalClientSystem), err
}
// FindFormalClientSystemName 根据主键查找名称
func (this *FormalClientSystemDAO) FindFormalClientSystemName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// FindSystemIdWithNameCacheable 根据操作系统名称查找系统ID
func (this *FormalClientSystemDAO) FindSystemIdWithNameCacheable(tx *dbs.Tx, systemName string) (int64, error) {
var cacheKey = "formalClientSystem:" + systemName
var cacheItem = ttlcache.SharedCache.Read(cacheKey)
if cacheItem != nil {
return types.Int64(cacheItem.Value), nil
}
// 先使用 name 查找,因为有索引,所以会快一些
systemId, err := this.Query(tx).
Attr("name", systemName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if systemId == 0 {
systemId, err = this.Query(tx).
Where("JSON_CONTAINS(codes, :systemName)").
Param("systemName", strconv.Quote(systemName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
}
// 即使找不到也要放入到缓存中
ttlcache.SharedCache.Write(cacheKey, systemId, time.Now().Unix()+3600)
return systemId, nil
}
// CreateSystem 创建操作系统信息
func (this *FormalClientSystemDAO) CreateSystem(tx *dbs.Tx, name string, codes []string, dataId string) (int64, error) {
if len(dataId) == 0 {
return 0, errors.New("invalid dataId")
}
// 检查 dataId 是否已经存在
exists, err := this.Query(tx).
Attr("dataId", dataId).
Exist()
if err != nil {
return 0, err
}
if exists {
return 0, errors.New("dataId '" + dataId + "' already exists")
}
var op = NewFormalClientSystemOperator()
op.Name = name
if len(codes) == 0 {
op.Codes = "[]"
} else {
codesJSON, err := json.Marshal(codes)
if err != nil {
return 0, err
}
op.Codes = codesJSON
}
op.DataId = dataId
op.State = FormalClientSystemStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateSystem 修改操作系统信息
func (this *FormalClientSystemDAO) UpdateSystem(tx *dbs.Tx, systemId int64, name string, codes []string, dataId string) error {
if systemId <= 0 {
return errors.New("invalid systemId '" + types.String(systemId) + "'")
}
if len(dataId) == 0 {
return errors.New("invalid dataId")
}
var op = NewFormalClientSystemOperator()
op.Id = systemId
op.Name = name
if len(codes) == 0 {
op.Codes = "[]"
} else {
codesJSON, err := json.Marshal(codes)
if err != nil {
return err
}
op.Codes = codesJSON
}
op.DataId = dataId
return this.Save(tx, op)
}
// CountSystems 计算操作系统数量
func (this *FormalClientSystemDAO) CountSystems(tx *dbs.Tx, keyword string) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Like("LOWER(codes)", dbutils.QuoteLikeKeyword(strings.ToLower(keyword)))
}
return query.Count()
}
// ListSystems 列出单页操作系统信息
func (this *FormalClientSystemDAO) ListSystems(tx *dbs.Tx, keyword string, offset int64, size int64) (result []*FormalClientSystem, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Like("LOWER(codes)", dbutils.QuoteLikeKeyword(strings.ToLower(keyword)))
}
_, err = query.
Offset(offset).
Limit(size).
DescPk().
Slice(&result).
FindAll()
return
}
// FindSystemWithDataId 根据dataId查找操作系统信息
func (this *FormalClientSystemDAO) FindSystemWithDataId(tx *dbs.Tx, dataId string) (*FormalClientSystem, error) {
one, err := this.Query(tx).
Attr("dataId", dataId).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*FormalClientSystem), nil
}

View File

@@ -1,4 +1,4 @@
package accounts
package models_test
import (
_ "github.com/go-sql-driver/mysql"

View File

@@ -0,0 +1,24 @@
package models
import "github.com/iwind/TeaGo/dbs"
// FormalClientSystem 终端操作系统信息
type FormalClientSystem struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 系统名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 数据ID
}
type FormalClientSystemOperator struct {
Id any // ID
Name any // 系统名称
Codes any // 代号
State any // 状态
DataId any // 数据ID
}
func NewFormalClientSystemOperator() *FormalClientSystemOperator {
return &FormalClientSystemOperator{}
}

View File

@@ -0,0 +1,21 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
)
// DecodeCodes 解析代号
func (this *FormalClientSystem) DecodeCodes() []string {
if IsNull(this.Codes) {
return nil
}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
remotelogs.Error("FormalClientSystem.DecodeCodes", err.Error())
}
return result
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"golang.org/x/net/idna"
"net"
"net/http"
"net/url"
@@ -39,9 +40,10 @@ var SharedHTTPAccessLogDAO *HTTPAccessLogDAO
var (
oldAccessLogQueue = make(chan *pb.HTTPAccessLog)
accessLogQueue = make(chan *pb.HTTPAccessLog, 10_000)
accessLogQueueMaxLength = 100_000
accessLogQueuePercent = 100 // 0-100
accessLogCountPerSecond = 10_000 // 0 表示不限制
accessLogQueueMaxLength = 100_000 // 队列最大长度
accessLogQueuePercent = 100 // 0-100
accessLogCountPerSecond = 10_000 // 每秒钟写入条数,0 表示不限制
accessLogPerTx = 100 // 单事务写入条数
accessLogConfigJSON = []byte{}
accessLogQueueChanged = make(chan zero.Zero, 1)
@@ -49,6 +51,10 @@ var (
accessLogRowsPerTable int64 = 500_000 // 自动分表的单表最大值
)
func AccessLogQueuePercent() int {
return accessLogQueuePercent
}
type accessLogTableQuery struct {
daoWrapper *HTTPAccessLogDAOWrapper
name string
@@ -84,16 +90,33 @@ func init() {
// 导出队列内容
goman.New(func() {
var ticker = time.NewTicker(1 * time.Second)
var accessLogPerLoop = accessLogPerTx
for range ticker.C {
var tx *dbs.Tx
err := SharedHTTPAccessLogDAO.DumpAccessLogsFromQueue(tx, accessLogCountPerSecond)
if err != nil {
remotelogs.Error("HTTP_ACCESS_LOG_QUEUE", "dump access logs failed: "+err.Error())
var countTxs = accessLogCountPerSecond / accessLogPerLoop
if countTxs <= 0 {
countTxs = 1
}
for i := 0; i < countTxs; i++ {
var before = time.Now()
hasMore, err := SharedHTTPAccessLogDAO.DumpAccessLogsFromQueue(accessLogPerLoop)
// 如果用时过长,则调整每次写入次数
var costMs = time.Since(before).Milliseconds()
if costMs > 150 {
accessLogPerLoop = accessLogPerTx / 4
} else if costMs > 100 {
accessLogPerLoop = accessLogPerTx / 2
} // 这里不需要恢复成默认值,因为可能是写入数量比较小
if err != nil {
remotelogs.Error("HTTP_ACCESS_LOG_QUEUE", "dump access logs failed: "+err.Error())
} else if !hasMore {
break
}
}
}
})
})
}
func NewHTTPAccessLogDAO() *HTTPAccessLogDAO {
@@ -112,13 +135,13 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLogs(tx *dbs.Tx, accessLogs []*pb.
// 写入队列
var queue = accessLogQueue // 这样写非常重要,防止在写入过程中队列有切换
for _, accessLog := range accessLogs {
if accessLog.FirewallPolicyId == 0 { // 如果是WAF记录则采取采样率
if accessLog.FirewallPolicyId == 0 { // 如果是WAF记录则采取采样率
// 采样率
if accessLogQueuePercent <= 0 {
return nil
}
if accessLogQueuePercent < 100 && rands.Int(1, 100) > accessLogQueuePercent {
return nil
continue
}
}
@@ -133,23 +156,49 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLogs(tx *dbs.Tx, accessLogs []*pb.
}
// DumpAccessLogsFromQueue 从队列导入访问日志
func (this *HTTPAccessLogDAO) DumpAccessLogsFromQueue(tx *dbs.Tx, size int) error {
func (this *HTTPAccessLogDAO) DumpAccessLogsFromQueue(size int) (hasMore bool, err error) {
if size <= 0 {
size = 100
}
if len(oldAccessLogQueue) == 0 && len(accessLogQueue) == 0 {
return false, nil
}
var dao = randomHTTPAccessLogDAO()
if dao == nil {
dao = &HTTPAccessLogDAOWrapper{
DAO: SharedHTTPAccessLogDAO,
NodeId: 0,
}
// 检查本地数据库空间
if dbutils.IsLocalDatabase && !dbutils.HasFreeSpace {
return false, errors.New("dump accesslog failed: there is no enough space left for database (" + dbutils.LocalDatabaseDataDir + ")")
}
} else if dao.IsLocal {
// 检查本地数据库空间
// 我们假定本地只能安装一个数据库访问日志中的数据库和当前API连接的数据库一致
if !dbutils.HasFreeSpace {
return true, errors.New("dump accesslog failed: there is no enough space left for database (" + dbutils.LocalDatabaseDataDir + ")")
}
}
if size <= 0 {
size = 1_000_000
// 开始事务
tx, err := dao.DAO.Instance.Begin()
if err != nil {
return false, err
}
defer func() {
_ = tx.Commit()
}()
// 复制变量,防止中途改变
var oldQueue = oldAccessLogQueue
var newQueue = accessLogQueue
hasMore = true
Loop:
for i := 0; i < size; i++ {
// old
@@ -157,7 +206,7 @@ Loop:
case accessLog := <-oldQueue:
err := this.CreateHTTPAccessLog(tx, dao.DAO, accessLog)
if err != nil {
return err
return false, err
}
continue Loop
default:
@@ -169,20 +218,28 @@ Loop:
case accessLog := <-newQueue:
err := this.CreateHTTPAccessLog(tx, dao.DAO, accessLog)
if err != nil {
return err
return false, err
}
continue Loop
default:
hasMore = false
break Loop
}
}
return nil
return hasMore, nil
}
// CreateHTTPAccessLog 写入单条访问日志
func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLogDAO, accessLog *pb.HTTPAccessLog) error {
var day = timeutil.FormatTime("Ymd", accessLog.Timestamp)
var day = ""
// 注意:如果你修改了 TimeISO8601 的逻辑,这里也需要同步修改
if len(accessLog.TimeISO8601) > 10 {
day = strings.ReplaceAll(accessLog.TimeISO8601[:10], "-", "")
} else {
timeutil.FormatTime("Ymd", accessLog.Timestamp)
}
tableDef, err := SharedHTTPAccessLogManager.FindLastTable(dao.Instance, day, true)
if err != nil {
return err
@@ -395,9 +452,15 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
var locker = sync.Mutex{}
// 这里正则表达式中的括号不能轻易变更,因为后面有引用
// TODO 支持多个查询条件的组合,比如 status:200 proto:HTTP/1.1
var statusPrefixReg = regexp.MustCompile(`status:\s*(\d{3})\b`)
var statusRangeReg = regexp.MustCompile(`status:\s*(\d{3})-(\d{3})\b`)
var urlReg = regexp.MustCompile(`^(http|https)://`)
var requestPathReg = regexp.MustCompile(`requestPath:(\S+)`)
var protoReg = regexp.MustCompile(`proto:(\S+)`)
var schemeReg = regexp.MustCompile(`scheme:(\S+)`)
var methodReg = regexp.MustCompile(`(?:method|requestMethod):(\S+)`)
var count = len(tableQueries)
var wg = &sync.WaitGroup{}
@@ -408,6 +471,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
var dao = tableQuery.daoWrapper.DAO
var query = dao.Query(tx)
query.Result("id", "serverId", "nodeId", "status", "createdAt", "content", "requestId", "firewallPolicyId", "firewallRuleGroupId", "firewallRuleSetId", "firewallRuleId", "remoteAddr", "domain")
// 条件
if nodeId > 0 {
@@ -481,6 +545,14 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
query.Where("domain LIKE :host2").
Param("host2", domain)
} else {
// 中文域名
if !regexp.MustCompile(`^[a-zA-Z0-9-.]+$`).MatchString(domain) {
unicodeDomain, err := idna.ToASCII(domain)
if err == nil && len(unicodeDomain) > 0 {
domain = unicodeDomain
}
}
query.Attr("domain", domain)
query.UseIndex("domain")
}
@@ -509,13 +581,25 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
isSpecialKeyword = true
var matches = statusRangeReg.FindStringSubmatch(keyword)
query.Between("status", types.Int(matches[1]), types.Int(matches[2]))
// TODO 处理剩余的关键词
} else if statusPrefixReg.MatchString(keyword) { // status:200
isSpecialKeyword = true
var matches = statusPrefixReg.FindStringSubmatch(keyword)
query.Attr("status", matches[1])
// TODO 处理剩余的关键词
} else if requestPathReg.MatchString(keyword) {
isSpecialKeyword = true
var matches = requestPathReg.FindStringSubmatch(keyword)
query.Where("JSON_EXTRACT(content, '$.requestPath')=:keyword").
Param("keyword", matches[1])
} else if protoReg.MatchString(keyword) {
isSpecialKeyword = true
var matches = protoReg.FindStringSubmatch(keyword)
query.Where("JSON_EXTRACT(content, '$.proto')=:keyword").
Param("keyword", strings.ToUpper(matches[1]))
} else if schemeReg.MatchString(keyword) {
isSpecialKeyword = true
var matches = schemeReg.FindStringSubmatch(keyword)
query.Where("JSON_EXTRACT(content, '$.scheme')=:keyword").
Param("keyword", strings.ToLower(matches[1]))
} else if urlReg.MatchString(keyword) { // https://xxx/yyy
u, err := url.Parse(keyword)
if err == nil {
@@ -524,6 +608,11 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
query.Where("JSON_EXTRACT(content, '$.requestURI') LIKE :keyword").
Param("keyword", dbutils.QuoteLikePrefix("\""+u.RequestURI()))
}
} else if methodReg.MatchString(keyword) { // method|requestMethod:xxx
isSpecialKeyword = true
var matches = methodReg.FindStringSubmatch(keyword)
query.Where("JSON_EXTRACT(content, '$.requestMethod')=:keyword").
Param("keyword", strings.ToUpper(matches[1]))
}
if !isSpecialKeyword {
if regexp.MustCompile(`^ip:.+`).MatchString(keyword) {
@@ -737,7 +826,7 @@ func (this *HTTPAccessLogDAO) SetupQueue() {
return
}
if bytes.Compare(accessLogConfigJSON, configJSON) == 0 {
if bytes.Equal(accessLogConfigJSON, configJSON) {
return
}
accessLogConfigJSON = configJSON
@@ -751,6 +840,9 @@ func (this *HTTPAccessLogDAO) SetupQueue() {
accessLogQueuePercent = config.Percent
accessLogCountPerSecond = config.CountPerSecond
if accessLogCountPerSecond <= 0 {
accessLogCountPerSecond = 10_000
}
if config.MaxLength <= 0 {
config.MaxLength = 100_000
}

View File

@@ -21,13 +21,13 @@ func TestCreateHTTPAccessLog(t *testing.T) {
t.Fatal(err)
}
accessLog := &pb.HTTPAccessLog{
var accessLog = &pb.HTTPAccessLog{
ServerId: 1,
NodeId: 4,
Status: 200,
Timestamp: time.Now().Unix(),
}
dao := randomHTTPAccessLogDAO()
var dao = randomHTTPAccessLogDAO()
t.Log("dao:", dao)
// 先初始化
@@ -37,12 +37,59 @@ func TestCreateHTTPAccessLog(t *testing.T) {
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
for i := 0; i < 1000; i++ {
err = SharedHTTPAccessLogDAO.CreateHTTPAccessLog(tx, dao.DAO, accessLog)
if err != nil {
t.Fatal(err)
}
}
t.Log("ok")
}
func TestCreateHTTPAccessLog_Tx(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
err := NewDBNodeInitializer().loop()
if err != nil {
t.Fatal(err)
}
var accessLog = &pb.HTTPAccessLog{
ServerId: 1,
NodeId: 4,
Status: 200,
Timestamp: time.Now().Unix(),
}
var dao = randomHTTPAccessLogDAO()
t.Log("dao:", dao)
// 先初始化
_ = SharedHTTPAccessLogDAO.CreateHTTPAccessLog(tx, dao.DAO, accessLog)
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
tx, err = dao.DAO.Instance.Begin()
if err != nil {
t.Fatal(err)
}
for i := 0; i < 200; i++ {
err = SharedHTTPAccessLogDAO.CreateHTTPAccessLog(tx, dao.DAO, accessLog)
if err != nil {
t.Fatal(err)
}
}
err = tx.Commit()
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -422,7 +422,7 @@ func (this *HTTPAccessLogManager) checkTableFields(db *dbs.DB, tableName string)
}
for _, field := range fields {
var fieldName = field.GetString("Field")
if strings.ToLower(fieldName) == strings.ToLower("remoteAddr") {
if strings.EqualFold(fieldName, "remoteAddr") {
hasRemoteAddrField = true
}
if strings.ToLower(fieldName) == "domain" {

View File

@@ -87,6 +87,7 @@ func (this *HTTPAccessLogPolicyDAO) CountAllEnabledPolicies(tx *dbs.Tx) (int64,
func (this *HTTPAccessLogPolicyDAO) ListEnabledPolicies(tx *dbs.Tx, offset int64, size int64) (result []*HTTPAccessLogPolicy, err error) {
_, err = this.Query(tx).
State(HTTPAccessLogPolicyStateEnabled).
Desc("isOn").
DescPk().
Offset(offset).
Limit(size).

View File

@@ -68,8 +68,9 @@ func (this *HTTPAuthPolicyDAO) FindEnabledHTTPAuthPolicy(tx *dbs.Tx, id int64) (
}
// CreateHTTPAuthPolicy 创建策略
func (this *HTTPAuthPolicyDAO) CreateHTTPAuthPolicy(tx *dbs.Tx, name string, methodType string, paramsJSON []byte) (int64, error) {
op := NewHTTPAuthPolicyOperator()
func (this *HTTPAuthPolicyDAO) CreateHTTPAuthPolicy(tx *dbs.Tx, userId int64, name string, methodType string, paramsJSON []byte) (int64, error) {
var op = NewHTTPAuthPolicyOperator()
op.UserId = userId
op.Name = name
op.Type = methodType
op.Params = paramsJSON
@@ -83,7 +84,7 @@ func (this *HTTPAuthPolicyDAO) UpdateHTTPAuthPolicy(tx *dbs.Tx, policyId int64,
if policyId <= 0 {
return errors.New("invalid policyId")
}
op := NewHTTPAuthPolicyOperator()
var op = NewHTTPAuthPolicyOperator()
op.Id = policyId
op.Name = name
op.Params = paramsJSON
@@ -137,6 +138,20 @@ func (this *HTTPAuthPolicyDAO) ComposePolicyConfig(tx *dbs.Tx, policyId int64, c
return config, nil
}
// CheckUserPolicy 检查用户权限
func (this *HTTPAuthPolicyDAO) CheckUserPolicy(tx *dbs.Tx, userId int64, policyId int64) error {
if userId <= 0 || policyId <= 0 {
return ErrNotFound
}
webId, err := SharedHTTPWebDAO.FindEnabledWebIdWithHTTPAuthPolicyId(tx, policyId)
if err != nil {
return err
}
return SharedHTTPWebDAO.CheckUserWeb(tx, userId, webId)
}
// NotifyUpdate 通知更改
func (this *HTTPAuthPolicyDAO) NotifyUpdate(tx *dbs.Tx, policyId int64) error {
webId, err := SharedHTTPWebDAO.FindEnabledWebIdWithHTTPAuthPolicyId(tx, policyId)

View File

@@ -113,7 +113,7 @@ func (this *HTTPBrotliPolicyDAO) ComposeBrotliConfig(tx *dbs.Tx, policyId int64)
// CreatePolicy 创建策略
func (this *HTTPBrotliPolicyDAO) CreatePolicy(tx *dbs.Tx, level int, minLengthJSON []byte, maxLengthJSON []byte, condsJSON []byte) (int64, error) {
op := NewHTTPBrotliPolicyOperator()
var op = NewHTTPBrotliPolicyOperator()
op.State = HTTPBrotliPolicyStateEnabled
op.IsOn = true
op.Level = level
@@ -138,7 +138,7 @@ func (this *HTTPBrotliPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, level
if policyId <= 0 {
return errors.New("invalid policyId")
}
op := NewHTTPBrotliPolicyOperator()
var op = NewHTTPBrotliPolicyOperator()
op.Id = policyId
op.Level = level
if len(minLengthJSON) > 0 {

Some files were not shown because too many files have changed in this diff Show More