Compare commits

...

127 Commits

Author SHA1 Message Date
刘祥超
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
307 changed files with 8542 additions and 13579 deletions

3
.gitignore vendored
View File

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

View File

@@ -88,15 +88,13 @@ function build() {
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
# building edge installer
echo "building node installer ..."
@@ -107,12 +105,14 @@ function build() {
done
# building edge dns installer
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
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 -trimpath -tags $TAG --ldflags="-s -w" -o "$DIST"/bin/edge-api "$ROOT"/../cmd/edge-api/main.go

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

@@ -14,7 +14,6 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"io/ioutil"
"log"
"os"
)
@@ -130,7 +129,7 @@ func main() {
flagSet.BoolVar(&formatJSON, "json", false, "")
_ = flagSet.Parse(os.Args[2:])
data, err := ioutil.ReadFile(Tea.LogFile("issues.log"))
data, err := os.ReadFile(Tea.LogFile("issues.log"))
if err != nil {
if formatJSON {
fmt.Print("[]")

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"
@@ -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

4
go.mod
View File

@@ -12,7 +12,7 @@ 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-20220408111647-f36b9bba3570
github.com/iwind/TeaGo v0.0.0-20220811034530-657e3f15b79e
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62
github.com/mozillazg/go-pinyin v0.18.0
github.com/pkg/sftp v1.12.0
@@ -21,7 +21,7 @@ require (
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
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 (

4
go.sum
View File

@@ -239,6 +239,8 @@ github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7Q
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220408111647-f36b9bba3570 h1:zqz2FiMMkSHXWO1EsTRJDPTwX9xQ4uuyD5GAE4JGlhM=
github.com/iwind/TeaGo v0.0.0-20220408111647-f36b9bba3570/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
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-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62 h1:HJH6RDheAY156DnIfJSD/bEvqyXzsZuE2gzs8PuUjoo=
@@ -789,6 +791,8 @@ 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 {
var 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,146 +0,0 @@
package accesslogs
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"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")
var cmd = exec.Command(this.exe, args...)
var stderrBuffer = &bytes.Buffer{}
cmd.Stderr = stderrBuffer
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 {
remotelogs.Error("ACCESS_LOG_POLICY_SYSLOG", "marshal accesslog failed: "+err.Error())
continue
}
_, err = w.Write(data)
if err != nil {
logs.Error(err)
}
_, err = w.Write([]byte("\n"))
if err != nil {
remotelogs.Error("ACCESS_LOG_POLICY_SYSLOG", "write accesslog failed: "+err.Error())
}
}
_ = w.Close()
err = cmd.Wait()
if err != nil {
return errors.New("send syslog failed: " + err.Error() + ", stderr: " + stderrBuffer.String())
}
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

@@ -24,11 +24,11 @@ func (this *DNSProvider) Present(domain, token, keyAuth string) error {
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]
var 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())

View File

@@ -8,7 +8,7 @@ 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"
"io"
"log"
)
@@ -40,6 +40,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 +56,7 @@ 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)
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
}
if this.task.User == nil {
@@ -75,7 +76,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 +87,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 +125,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 +139,7 @@ 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)
acmelog.Logger = log.New(io.Discard, "", log.LstdFlags)
}
if this.task.User == nil {
@@ -146,7 +147,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 +158,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 +196,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,14 @@ 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)
}

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.4.10"
Version = "0.5.3"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -18,11 +18,11 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.4.10"
UserNodeVersion = "0.3.6"
NodeVersion = "0.5.3"
UserNodeVersion = "0.5.0"
DNSNodeVersion = "0.2.7"
AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.4"
DNSNodeVersion = "0.2.4"
ReportNodeVersion = "0.1.1"
// SQLVersion SQL版本号

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

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

View File

@@ -0,0 +1,36 @@
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"` // 参数
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type OrderMethodOperator struct {
Id interface{} // ID
Name interface{} // 名称
IsOn interface{} // 是否启用
Description interface{} // 描述
ParentCode interface{} // 内置的父级代号
Code interface{} // 代号
Url interface{} // URL
Secret interface{} // 密钥
Params interface{} // 参数
Order interface{} // 排序
State interface{} // 状态
}
func NewOrderMethodOperator() *OrderMethodOperator {
return &OrderMethodOperator{}
}

View File

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

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

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

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

@@ -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)
@@ -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()
@@ -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
@@ -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

@@ -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).

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

@@ -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
}
var 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

@@ -2,18 +2,14 @@ package models
import (
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"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,22 +27,12 @@ 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
}
func init() {
initializer := NewDBNodeInitializer()
dbs.OnReadyDone(func() {
@@ -78,102 +64,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 +121,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 +145,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 {
// 检查配置是否有变化
@@ -308,49 +220,8 @@ func (this *DBNodeInitializer) loop() error {
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

@@ -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()
}

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
}
@@ -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()
@@ -163,3 +167,13 @@ func (this *DNSTaskDAO) UpdateDNSTaskDone(tx *dbs.Tx, taskId int64) error {
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)
@@ -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

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

@@ -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)
@@ -84,16 +86,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 {
@@ -133,7 +152,11 @@ 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
}
var dao = randomHTTPAccessLogDAO()
if dao == nil {
dao = &HTTPAccessLogDAOWrapper{
@@ -142,14 +165,25 @@ func (this *HTTPAccessLogDAO) DumpAccessLogsFromQueue(tx *dbs.Tx, size int) erro
}
}
if size <= 0 {
size = 1_000_000
if len(oldAccessLogQueue) == 0 && len(accessLogQueue) == 0 {
return false, nil
}
// 开始事务
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 +191,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 +203,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
@@ -413,6 +455,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 {
@@ -486,6 +529,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")
}
@@ -754,7 +805,7 @@ func (this *HTTPAccessLogDAO) SetupQueue() {
return
}
if bytes.Compare(accessLogConfigJSON, configJSON) == 0 {
if bytes.Equal(accessLogConfigJSON, configJSON) {
return
}
accessLogConfigJSON = configJSON
@@ -768,6 +819,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) {
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
@@ -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

@@ -125,25 +125,13 @@ func (this *HTTPCachePolicyDAO) CreateCachePolicy(tx *dbs.Tx, isOn bool, name st
MinSize: &shared.SizeCapacity{Count: 0, Unit: shared.SizeCapacityUnitKB},
SkipResponseSetCookie: true,
AllowChunkedEncoding: true,
Conds: &shared.HTTPRequestCondsConfig{
IsOn: true,
Connector: "or",
Groups: []*shared.HTTPRequestCondGroup{
{
IsOn: true,
Connector: "or",
Conds: []*shared.HTTPRequestCond{
{
Type: "url-extension",
IsRequest: true,
Param: "${requestPathExtension}",
Operator: shared.RequestCondOperatorIn,
Value: `[".html", ".js", ".css", ".gif", ".png", ".bmp", ".jpeg", ".jpg", ".webp", ".ico", ".pdf", ".ttf", ".eot", ".tiff", ".svg", ".svgz", ".eps", ".woff", ".otf", ".woff2", ".tif", ".csv", ".xls", ".xlsx", ".doc", ".docx", ".ppt", ".pptx", ".wav", ".mp3", ".mp4", ".ogg", ".mid", ".midi"]`,
},
},
Description: "初始化规则",
},
},
AllowPartialContent: true,
SimpleCond: &shared.HTTPRequestCond{
Type: "url-extension",
IsRequest: true,
Param: "${requestPathExtension}",
Operator: shared.RequestCondOperatorIn,
Value: `[".html", ".js", ".css", ".gif", ".png", ".bmp", ".jpeg", ".jpg", ".webp", ".ico", ".pdf", ".ttf", ".eot", ".tiff", ".svg", ".svgz", ".eps", ".woff", ".otf", ".woff2", ".tif", ".csv", ".xls", ".xlsx", ".doc", ".docx", ".ppt", ".pptx", ".wav", ".mp3", ".mp4", ".ogg", ".mid", ".midi"]`,
},
}
refsJSON, err := json.Marshal([]*serverconfigs.HTTPCacheRef{cacheRef})

View File

@@ -12,23 +12,23 @@ type HTTPCacheTask struct {
Day string `field:"day"` // 创建日期YYYYMMDD
IsDone bool `field:"isDone"` // 是否已完成
IsOk bool `field:"isOk"` // 是否完全成功
IsReady uint8 `field:"isReady"` // 是否已准备好
IsReady bool `field:"isReady"` // 是否已准备好
Description string `field:"description"` // 描述
}
type HTTPCacheTaskOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Type interface{} // 任务类型purge|fetch
KeyType interface{} // Key类型
State interface{} // 状态
CreatedAt interface{} // 创建时间
DoneAt interface{} // 完成时间
Day interface{} // 创建日期YYYYMMDD
IsDone interface{} // 是否已完成
IsOk interface{} // 是否完全成功
IsReady interface{} // 是否已准备好
Description interface{} // 描述
Id any // ID
UserId any // 用户ID
Type any // 任务类型purge|fetch
KeyType any // Key类型
State any // 状态
CreatedAt any // 创建时间
DoneAt any // 完成时间
Day any // 创建日期YYYYMMDD
IsDone any // 是否已完成
IsOk any // 是否完全成功
IsReady any // 是否已准备好
Description any // 描述
}
func NewHTTPCacheTaskOperator() *HTTPCacheTaskOperator {

View File

@@ -381,7 +381,7 @@ func (this *HTTPWebDAO) ComposeWebConfig(tx *dbs.Tx, webId int64, cacheMap *util
// 认证
if IsNotNull(web.Auth) {
authConfig := &serverconfigs.HTTPAuthConfig{}
var authConfig = &serverconfigs.HTTPAuthConfig{}
err = json.Unmarshal(web.Auth, authConfig)
if err != nil {
return nil, err
@@ -395,6 +395,7 @@ func (this *HTTPWebDAO) ComposeWebConfig(tx *dbs.Tx, webId int64, cacheMap *util
if policyConfig != nil {
ref.AuthPolicy = policyConfig
newRefs = append(newRefs, ref)
authConfig.PolicyRefs = newRefs
}
}
config.Auth = authConfig
@@ -457,6 +458,16 @@ func (this *HTTPWebDAO) ComposeWebConfig(tx *dbs.Tx, webId int64, cacheMap *util
config.UAM = uamConfig
}
// Referers
if IsNotNull(web.Referers) {
var referersConfig = &serverconfigs.ReferersConfig{}
err = json.Unmarshal(web.Referers, referersConfig)
if err != nil {
return nil, err
}
config.Referers = referersConfig
}
if cacheMap != nil {
cacheMap.Put(cacheKey, config)
}
@@ -1042,6 +1053,10 @@ func (this *HTTPWebDAO) FindWebServerGroupId(tx *dbs.Tx, webId int64) (groupId i
// CheckUserWeb 检查用户权限
func (this *HTTPWebDAO) CheckUserWeb(tx *dbs.Tx, userId int64, webId int64) error {
if userId <= 0 || webId <= 0 {
return ErrNotFound
}
serverId, err := this.FindWebServerId(tx, webId)
if err != nil {
return err
@@ -1208,6 +1223,35 @@ func (this *HTTPWebDAO) FindWebUAM(tx *dbs.Tx, webId int64) ([]byte, error) {
FindJSONCol()
}
// UpdateWebReferers 修改防盗链设置
func (this *HTTPWebDAO) UpdateWebReferers(tx *dbs.Tx, webId int64, referersConfig *serverconfigs.ReferersConfig) error {
if referersConfig == nil {
return nil
}
configJSON, err := json.Marshal(referersConfig)
if err != nil {
return err
}
err = this.Query(tx).
Pk(webId).
Set("referers", configJSON).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, webId)
}
// FindWebReferers 查找服务的防盗链配置
func (this *HTTPWebDAO) FindWebReferers(tx *dbs.Tx, webId int64) ([]byte, error) {
return this.Query(tx).
Pk(webId).
Result("referers").
FindJSONCol()
}
// NotifyUpdate 通知更新
func (this *HTTPWebDAO) NotifyUpdate(tx *dbs.Tx, webId int64) error {
// server

View File

@@ -38,43 +38,45 @@ type HTTPWeb struct {
RequestLimit dbs.JSON `field:"requestLimit"` // 请求限制
RequestScripts dbs.JSON `field:"requestScripts"` // 请求脚本
Uam dbs.JSON `field:"uam"` // UAM设置
Referers dbs.JSON `field:"referers"` // 防盗链设置
}
type HTTPWebOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
TemplateId interface{} // 模版ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
State interface{} // 状态
CreatedAt interface{} // 创建时间
Root interface{} // 根目录
Charset interface{} // 字符集
Shutdown interface{} // 临时关闭页面配置
Pages interface{} // 特殊页面
RedirectToHttps interface{} // 跳转到HTTPS设置
Indexes interface{} // 首页文件列表
MaxRequestBodySize interface{} // 最大允许的请求内容尺寸
RequestHeader interface{} // 请求Header配置
ResponseHeader interface{} // 响应Header配置
AccessLog interface{} // 访问日志配置
Stat interface{} // 统计配置
Gzip interface{} // Gzip配置v0.3.2弃用)
Compression interface{} // 压缩配置
Cache interface{} // 缓存配置
Firewall interface{} // 防火墙设置
Locations interface{} // 路由规则配置
Websocket interface{} // Websocket设置
RewriteRules interface{} // 重写规则配置
HostRedirects interface{} // 域名跳转
Fastcgi interface{} // Fastcgi配置
Auth interface{} // 认证策略配置
Webp interface{} // WebP配置
RemoteAddr interface{} // 客户端IP配置
MergeSlashes interface{} // 是否合并路径中的斜杠
RequestLimit interface{} // 请求限制
RequestScripts interface{} // 请求脚本
Uam interface{} // UAM设置
Id any // ID
IsOn any // 是否启用
TemplateId any // 模版ID
AdminId any // 管理员ID
UserId any // 用户ID
State any // 状态
CreatedAt any // 创建时间
Root any // 根目录
Charset any // 字符集
Shutdown any // 临时关闭页面配置
Pages any // 特殊页面
RedirectToHttps any // 跳转到HTTPS设置
Indexes any // 首页文件列表
MaxRequestBodySize any // 最大允许的请求内容尺寸
RequestHeader any // 请求Header配置
ResponseHeader any // 响应Header配置
AccessLog any // 访问日志配置
Stat any // 统计配置
Gzip any // Gzip配置v0.3.2弃用)
Compression any // 压缩配置
Cache any // 缓存配置
Firewall any // 防火墙设置
Locations any // 路由规则配置
Websocket any // Websocket设置
RewriteRules any // 重写规则配置
HostRedirects any // 域名跳转
Fastcgi any // Fastcgi配置
Auth any // 认证策略配置
Webp any // WebP配置
RemoteAddr any // 客户端IP配置
MergeSlashes any // 是否合并路径中的斜杠
RequestLimit any // 请求限制
RequestScripts any // 请求脚本
Uam any // UAM设置
Referers any // 防盗链设置
}
func NewHTTPWebOperator() *HTTPWebOperator {

View File

@@ -0,0 +1,140 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
stringutil "github.com/iwind/TeaGo/utils/string"
)
const (
IPLibraryArtifactStateEnabled = 1 // 已启用
IPLibraryArtifactStateDisabled = 0 // 已禁用
)
type IPLibraryArtifactDAO dbs.DAO
func NewIPLibraryArtifactDAO() *IPLibraryArtifactDAO {
return dbs.NewDAO(&IPLibraryArtifactDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeIPLibraryArtifacts",
Model: new(IPLibraryArtifact),
PkName: "id",
},
}).(*IPLibraryArtifactDAO)
}
var SharedIPLibraryArtifactDAO *IPLibraryArtifactDAO
func init() {
dbs.OnReady(func() {
SharedIPLibraryArtifactDAO = NewIPLibraryArtifactDAO()
})
}
// EnableIPLibraryArtifact 启用条目
func (this *IPLibraryArtifactDAO) EnableIPLibraryArtifact(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryArtifactStateEnabled).
Update()
return err
}
// DisableIPLibraryArtifact 禁用条目
func (this *IPLibraryArtifactDAO) DisableIPLibraryArtifact(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryArtifactStateDisabled).
Update()
return err
}
// FindEnabledIPLibraryArtifact 查找启用中的条目
func (this *IPLibraryArtifactDAO) FindEnabledIPLibraryArtifact(tx *dbs.Tx, id int64) (*IPLibraryArtifact, error) {
result, err := this.Query(tx).
Pk(id).
State(IPLibraryArtifactStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*IPLibraryArtifact), err
}
// CreateArtifact 创建制品
func (this *IPLibraryArtifactDAO) CreateArtifact(tx *dbs.Tx, name string, fileId int64, libraryFileId int64, meta *iplibrary.Meta) (int64, error) {
var op = NewIPLibraryArtifactOperator()
op.Name = name
op.FileId = fileId
op.LibraryFileId = libraryFileId
metaJSON, err := json.Marshal(meta)
if err != nil {
return 0, err
}
op.Meta = metaJSON
op.State = IPLibraryArtifactStateEnabled
var code = stringutil.Md5(utils.Sha1RandomString())[:8]
meta.Code = code
op.Code = code // 要比较短,方便识别
return this.SaveInt64(tx, op)
}
// FindAllArtifacts 查找制品列表
func (this *IPLibraryArtifactDAO) FindAllArtifacts(tx *dbs.Tx) (result []*IPLibraryArtifact, err error) {
_, err = this.Query(tx).
State(IPLibraryArtifactStateEnabled).
DescPk().
Slice(&result).
FindAll()
return
}
// FindPublicArtifact 查找当前使用的制品
func (this *IPLibraryArtifactDAO) FindPublicArtifact(tx *dbs.Tx) (*IPLibraryArtifact, error) {
one, err := this.Query(tx).
State(IPLibraryArtifactStateEnabled).
Attr("isPublic", true).
Result("id", "fileId", "code").
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*IPLibraryArtifact), nil
}
// UpdateArtifactPublic 使用某个制品
func (this *IPLibraryArtifactDAO) UpdateArtifactPublic(tx *dbs.Tx, artifactId int64, isPublic bool) error {
// 取消使用
if !isPublic {
return this.Query(tx).
Pk(artifactId).
Set("isPublic", false).
UpdateQuickly()
}
// 使用
// 先取消别的
err := this.Query(tx).
Neq("id", artifactId).
State(IPLibraryArtifactStateEnabled).
Attr("isPublic", true).
Set("isPublic", false).
UpdateQuickly()
if err != nil {
return err
}
return this.Query(tx).
Pk(artifactId).
Set("isPublic", true).
UpdateQuickly()
}

View File

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

View File

@@ -0,0 +1,32 @@
package models
import "github.com/iwind/TeaGo/dbs"
// IPLibraryArtifact IP库制品
type IPLibraryArtifact struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
FileId uint64 `field:"fileId"` // 文件ID
LibraryFileId uint32 `field:"libraryFileId"` // IP库文件ID
CreatedAt uint64 `field:"createdAt"` // 创建时间
Meta dbs.JSON `field:"meta"` // 元数据
IsPublic bool `field:"isPublic"` // 是否为公用
Code string `field:"code"` // 代号
State uint8 `field:"state"` // 状态
}
type IPLibraryArtifactOperator struct {
Id any // ID
Name any // 名称
FileId any // 文件ID
LibraryFileId any // IP库文件ID
CreatedAt any // 创建时间
Meta any // 元数据
IsPublic any // 是否为公用
Code any // 代号
State any // 状态
}
func NewIPLibraryArtifactOperator() *IPLibraryArtifactOperator {
return &IPLibraryArtifactOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -33,7 +33,7 @@ func init() {
})
}
// 启用条目
// EnableIPLibrary 启用条目
func (this *IPLibraryDAO) EnableIPLibrary(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -42,7 +42,7 @@ func (this *IPLibraryDAO) EnableIPLibrary(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableIPLibrary 禁用条目
func (this *IPLibraryDAO) DisableIPLibrary(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -51,7 +51,7 @@ func (this *IPLibraryDAO) DisableIPLibrary(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
// FindEnabledIPLibrary 查找启用中的条目
func (this *IPLibraryDAO) FindEnabledIPLibrary(tx *dbs.Tx, id int64) (*IPLibrary, error) {
result, err := this.Query(tx).
Pk(id).
@@ -63,7 +63,7 @@ func (this *IPLibraryDAO) FindEnabledIPLibrary(tx *dbs.Tx, id int64) (*IPLibrary
return result.(*IPLibrary), err
}
// 查找某个类型的IP库列表
// FindAllEnabledIPLibrariesWithType 查找某个类型的IP库列表
func (this *IPLibraryDAO) FindAllEnabledIPLibrariesWithType(tx *dbs.Tx, libraryType string) (result []*IPLibrary, err error) {
_, err = this.Query(tx).
State(IPLibraryStateEnabled).
@@ -74,7 +74,7 @@ func (this *IPLibraryDAO) FindAllEnabledIPLibrariesWithType(tx *dbs.Tx, libraryT
return
}
// 查找某个类型的最新的IP库
// FindLatestIPLibraryWithType 查找某个类型的最新的IP库
func (this *IPLibraryDAO) FindLatestIPLibraryWithType(tx *dbs.Tx, libraryType string) (*IPLibrary, error) {
one, err := this.Query(tx).
State(IPLibraryStateEnabled).
@@ -90,7 +90,7 @@ func (this *IPLibraryDAO) FindLatestIPLibraryWithType(tx *dbs.Tx, libraryType st
return one.(*IPLibrary), nil
}
// 创建新的IP库
// CreateIPLibrary 创建新的IP库
func (this *IPLibraryDAO) CreateIPLibrary(tx *dbs.Tx, libraryType string, fileId int64) (int64, error) {
var op = NewIPLibraryOperator()
op.Type = libraryType

View File

@@ -0,0 +1,593 @@
package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"io"
"os"
"time"
)
const (
IPLibraryFileStateEnabled = 1 // 已启用
IPLibraryFileStateDisabled = 0 // 已禁用
)
type IPLibraryFileDAO dbs.DAO
func NewIPLibraryFileDAO() *IPLibraryFileDAO {
return dbs.NewDAO(&IPLibraryFileDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeIPLibraryFiles",
Model: new(IPLibraryFile),
PkName: "id",
},
}).(*IPLibraryFileDAO)
}
var SharedIPLibraryFileDAO *IPLibraryFileDAO
func init() {
dbs.OnReady(func() {
SharedIPLibraryFileDAO = NewIPLibraryFileDAO()
})
}
// EnableIPLibraryFile 启用条目
func (this *IPLibraryFileDAO) EnableIPLibraryFile(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryFileStateEnabled).
Update()
return err
}
// DisableIPLibraryFile 禁用条目
func (this *IPLibraryFileDAO) DisableIPLibraryFile(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryFileStateDisabled).
Update()
return err
}
// FindEnabledIPLibraryFile 查找启用中的条目
func (this *IPLibraryFileDAO) FindEnabledIPLibraryFile(tx *dbs.Tx, id int64) (*IPLibraryFile, error) {
result, err := this.Query(tx).
Pk(id).
State(IPLibraryFileStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*IPLibraryFile), err
}
// CreateLibraryFile 创建文件
func (this *IPLibraryFileDAO) CreateLibraryFile(tx *dbs.Tx, name string, template string, emptyValues []string, fileId int64, countries []string, provinces [][2]string, cities [][3]string, towns [][4]string, providers []string) (int64, error) {
var op = NewIPLibraryFileOperator()
op.Name = name
op.Template = template
if emptyValues == nil {
emptyValues = []string{}
}
emptyValuesJSON, err := json.Marshal(emptyValues)
if err != nil {
return 0, err
}
op.EmptyValues = emptyValuesJSON
op.FileId = fileId
if countries == nil {
countries = []string{}
}
countriesJSON, err := json.Marshal(countries)
if err != nil {
return 0, err
}
op.Countries = countriesJSON
if provinces == nil {
provinces = [][2]string{}
}
provincesJSON, err := json.Marshal(provinces)
if err != nil {
return 0, err
}
op.Provinces = provincesJSON
if cities == nil {
cities = [][3]string{}
}
citiesJSON, err := json.Marshal(cities)
if err != nil {
return 0, err
}
op.Cities = citiesJSON
if towns == nil {
towns = [][4]string{}
}
townsJSON, err := json.Marshal(towns)
if err != nil {
return 0, err
}
op.Towns = townsJSON
if providers == nil {
providers = []string{}
}
providersJSON, err := json.Marshal(providers)
if err != nil {
return 0, err
}
op.Providers = providersJSON
op.IsFinished = false
op.State = IPLibraryFileStateEnabled
return this.SaveInt64(tx, op)
}
// FindAllFinishedLibraryFiles 查找所有已完成的文件
func (this *IPLibraryFileDAO) FindAllFinishedLibraryFiles(tx *dbs.Tx) (result []*IPLibraryFile, err error) {
_, err = this.Query(tx).
State(IPLibraryFileStateEnabled).
Result("id", "fileId", "createdAt", "generatedFileId", "generatedAt", "name"). // 这里不需要其他信息
Attr("isFinished", true).
DescPk().
Slice(&result).
FindAll()
return
}
// FindAllUnfinishedLibraryFiles 查找所有未完成的文件
func (this *IPLibraryFileDAO) FindAllUnfinishedLibraryFiles(tx *dbs.Tx) (result []*IPLibraryFile, err error) {
_, err = this.Query(tx).
State(IPLibraryFileStateEnabled).
Result("id", "fileId", "createdAt"). // 这里不需要其他信息
Attr("isFinished", false).
DescPk().
Slice(&result).
FindAll()
return
}
// UpdateLibraryFileIsFinished 设置文件为已完成
func (this *IPLibraryFileDAO) UpdateLibraryFileIsFinished(tx *dbs.Tx, fileId int64) error {
return this.Query(tx).
Pk(fileId).
Set("isFinished", true).
UpdateQuickly()
}
// FindLibraryFileCountries 获取IP库中的国家/地区
func (this *IPLibraryFileDAO) FindLibraryFileCountries(tx *dbs.Tx, fileId int64) ([]string, error) {
countriesJSON, err := this.Query(tx).
Result("countries").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(countriesJSON) {
return nil, nil
}
var result = []string{}
err = json.Unmarshal(countriesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileProvinces 获取IP库中的省份
func (this *IPLibraryFileDAO) FindLibraryFileProvinces(tx *dbs.Tx, fileId int64) ([][2]string, error) {
provincesJSON, err := this.Query(tx).
Result("provinces").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(provincesJSON) {
return nil, nil
}
var result = [][2]string{}
err = json.Unmarshal(provincesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileCities 获取IP库中的城市
func (this *IPLibraryFileDAO) FindLibraryFileCities(tx *dbs.Tx, fileId int64) ([][3]string, error) {
citiesJSON, err := this.Query(tx).
Result("cities").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(citiesJSON) {
return nil, nil
}
var result = [][3]string{}
err = json.Unmarshal(citiesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileTowns 获取IP库中的区县
func (this *IPLibraryFileDAO) FindLibraryFileTowns(tx *dbs.Tx, fileId int64) ([][4]string, error) {
townsJSON, err := this.Query(tx).
Result("towns").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(townsJSON) {
return nil, nil
}
var result = [][4]string{}
err = json.Unmarshal(townsJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileProviders 获取IP库中的ISP运营商
func (this *IPLibraryFileDAO) FindLibraryFileProviders(tx *dbs.Tx, fileId int64) ([]string, error) {
providersJSON, err := this.Query(tx).
Result("providers").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(providersJSON) {
return nil, nil
}
var result = []string{}
err = json.Unmarshal(providersJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
func (this *IPLibraryFileDAO) GenerateIPLibrary(tx *dbs.Tx, libraryFileId int64) error {
one, err := this.Query(tx).Pk(libraryFileId).Find()
if err != nil {
return err
}
if one == nil {
return errors.New("the library file not found")
}
var libraryFile = one.(*IPLibraryFile)
template, err := iplibrary.NewTemplate(libraryFile.Template)
if err != nil {
return errors.New("create template from '" + libraryFile.Template + "' failed: " + err.Error())
}
var fileId = int64(libraryFile.FileId)
if fileId == 0 {
return errors.New("the library file has not been uploaded yet")
}
var dir = Tea.Root + "/data"
stat, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir(dir, 0777)
if err != nil {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
} else {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
} else if !stat.IsDir() {
_ = os.Remove(dir)
err = os.Mkdir(dir, 0777)
if err != nil {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
}
// TODO 删除以往生成的文件,但要考虑到文件正在被别的任务所使用
// 国家
dbCountries, err := regions.SharedRegionCountryDAO.FindAllCountries(tx)
if err != nil {
return err
}
var countries = []*iplibrary.Country{}
for _, country := range dbCountries {
countries = append(countries, &iplibrary.Country{
Id: country.Id,
Name: country.DisplayName(),
Codes: country.AllCodes(),
})
}
// 省份
dbProvinces, err := regions.SharedRegionProvinceDAO.FindAllEnabledProvinces(tx)
if err != nil {
return err
}
var provinces = []*iplibrary.Province{}
for _, province := range dbProvinces {
provinces = append(provinces, &iplibrary.Province{
Id: province.Id,
Name: province.DisplayName(),
Codes: province.AllCodes(),
})
}
// 城市
dbCities, err := regions.SharedRegionCityDAO.FindAllEnabledCities(tx)
if err != nil {
return err
}
var cities = []*iplibrary.City{}
for _, city := range dbCities {
cities = append(cities, &iplibrary.City{
Id: city.Id,
Name: city.DisplayName(),
Codes: city.AllCodes(),
})
}
// 区县
dbTowns, err := regions.SharedRegionTownDAO.FindAllRegionTowns(tx)
if err != nil {
return err
}
var towns = []*iplibrary.Town{}
for _, town := range dbTowns {
towns = append(towns, &iplibrary.Town{
Id: town.Id,
Name: town.DisplayName(),
Codes: town.AllCodes(),
})
}
// ISP运营商
dbProviders, err := regions.SharedRegionProviderDAO.FindAllEnabledProviders(tx)
if err != nil {
return err
}
var providers = []*iplibrary.Provider{}
for _, provider := range dbProviders {
providers = append(providers, &iplibrary.Provider{
Id: provider.Id,
Name: provider.DisplayName(),
Codes: provider.AllCodes(),
})
}
var libraryCode = utils.Sha1RandomString() // 每次都生成新的code
var filePath = dir + "/" + this.composeFilename(libraryFileId, libraryCode)
var meta = &iplibrary.Meta{
Author: "", // 将来用户可以自行填写
CreatedAt: time.Now().Unix(),
Countries: countries,
Provinces: provinces,
Cities: cities,
Towns: towns,
Providers: providers,
}
writer, err := iplibrary.NewFileWriter(filePath, meta)
if err != nil {
return err
}
defer func() {
_ = writer.Close()
_ = os.Remove(filePath)
}()
err = writer.WriteMeta()
if err != nil {
return errors.New("write meta failed: " + err.Error())
}
chunkIds, err := SharedFileChunkDAO.FindAllFileChunkIds(tx, fileId)
if err != nil {
return err
}
// countries etc ...
var countryMap = map[string]int64{} // countryName => countryId
for _, country := range dbCountries {
for _, code := range country.AllCodes() {
countryMap[code] = int64(country.Id)
}
}
var provinceMap = map[string]int64{} // countryId_provinceName => provinceId
for _, province := range dbProvinces {
for _, code := range province.AllCodes() {
provinceMap[types.String(province.CountryId)+"_"+code] = int64(province.Id)
}
}
var cityMap = map[string]int64{} // provinceId_cityName => cityId
for _, city := range dbCities {
for _, code := range city.AllCodes() {
cityMap[types.String(city.ProvinceId)+"_"+code] = int64(city.Id)
}
}
var townMap = map[string]int64{} // cityId_townName => townId
for _, town := range dbTowns {
for _, code := range town.AllCodes() {
townMap[types.String(town.CityId)+"_"+code] = int64(town.Id)
}
}
var providerMap = map[string]int64{} // providerName => providerId
for _, provider := range dbProviders {
for _, code := range provider.AllCodes() {
providerMap[code] = int64(provider.Id)
}
}
dataParser, err := iplibrary.NewParser(&iplibrary.ParserConfig{
Template: template,
EmptyValues: libraryFile.DecodeEmptyValues(),
Iterator: func(values map[string]string) error {
var ipFrom = values["ipFrom"]
var ipTo = values["ipTo"]
var countryName = values["country"]
var provinceName = values["province"]
var cityName = values["city"]
var townName = values["town"]
var providerName = values["provider"]
var countryId = countryMap[countryName]
var provinceId int64
var cityId int64 = 0
var townId int64 = 0
var providerId = providerMap[providerName]
if countryId > 0 {
provinceId = provinceMap[types.String(countryId)+"_"+provinceName]
if provinceId > 0 {
cityId = cityMap[types.String(provinceId)+"_"+cityName]
if cityId > 0 {
townId = townMap[types.String(cityId)+"_"+townName]
}
}
}
err = writer.Write(ipFrom, ipTo, countryId, provinceId, cityId, townId, providerId)
if err != nil {
return errors.New("write failed: " + err.Error())
}
return nil
},
})
if err != nil {
return err
}
for _, chunkId := range chunkIds {
chunk, err := SharedFileChunkDAO.FindFileChunk(tx, chunkId)
if err != nil {
return err
}
if chunk == nil {
return errors.New("invalid chunk file, please upload again")
}
dataParser.Write(chunk.Data)
err = dataParser.Parse()
if err != nil {
return err
}
}
err = writer.Close()
if err != nil {
return err
}
// 将生成的内容写入到文件
stat, err = os.Stat(filePath)
if err != nil {
return errors.New("stat generated file failed: " + err.Error())
}
generatedFileId, err := SharedFileDAO.CreateFile(tx, 0, 0, "ipLibraryFile", "", libraryCode+".db", stat.Size(), "", false)
if err != nil {
return err
}
fp, err := os.Open(filePath)
if err != nil {
return errors.New("open generated file failed: " + err.Error())
}
var buf = make([]byte, 256*1024)
for {
n, err := fp.Read(buf)
if n > 0 {
_, err = SharedFileChunkDAO.CreateFileChunk(tx, generatedFileId, buf[:n])
if err != nil {
return err
}
}
if err != nil {
if err != io.EOF {
return err
}
break
}
}
err = SharedFileDAO.UpdateFileIsFinished(tx, generatedFileId)
if err != nil {
return err
}
// 设置code
err = this.Query(tx).
Pk(libraryFileId).
Set("code", libraryCode).
Set("isFinished", true).
Set("generatedFileId", generatedFileId).
Set("generatedAt", time.Now().Unix()).
UpdateQuickly()
if err != nil {
return err
}
// 添加制品
_, err = SharedIPLibraryArtifactDAO.CreateArtifact(tx, libraryFile.Name, generatedFileId, libraryFileId, meta)
if err != nil {
return err
}
return nil
}
// 组合IP库文件名
func (this *IPLibraryFileDAO) composeFilename(libraryFileId int64, code string) string {
return "ip-library-" + types.String(libraryFileId) + "-" + code + ".db"
}

View File

@@ -0,0 +1,19 @@
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestIPLibraryFileDAO_GenerateIPLibrary(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
err := models.SharedIPLibraryFileDAO.GenerateIPLibrary(tx, 4)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,46 @@
package models
import "github.com/iwind/TeaGo/dbs"
// IPLibraryFile IP库上传的文件
type IPLibraryFile struct {
Id uint64 `field:"id"` // ID
Name string `field:"name"` // IP库名称
FileId uint64 `field:"fileId"` // 原始文件ID
Template string `field:"template"` // 模板
EmptyValues dbs.JSON `field:"emptyValues"` // 空值列表
GeneratedFileId uint64 `field:"generatedFileId"` // 生成的文件ID
GeneratedAt uint64 `field:"generatedAt"` // 生成时间
IsFinished bool `field:"isFinished"` // 是否已经完成
Countries dbs.JSON `field:"countries"` // 国家/地区
Provinces dbs.JSON `field:"provinces"` // 省份
Cities dbs.JSON `field:"cities"` // 城市
Towns dbs.JSON `field:"towns"` // 区县
Providers dbs.JSON `field:"providers"` // ISP服务商
Code string `field:"code"` // 文件代号
CreatedAt uint64 `field:"createdAt"` // 上传时间
State uint8 `field:"state"` // 状态
}
type IPLibraryFileOperator struct {
Id any // ID
Name any // IP库名称
FileId any // 原始文件ID
Template any // 模板
EmptyValues any // 空值列表
GeneratedFileId any // 生成的文件ID
GeneratedAt any // 生成时间
IsFinished any // 是否已经完成
Countries any // 国家/地区
Provinces any // 省份
Cities any // 城市
Towns any // 区县
Providers any // ISP服务商
Code any // 文件代号
CreatedAt any // 上传时间
State any // 状态
}
func NewIPLibraryFileOperator() *IPLibraryFileOperator {
return &IPLibraryFileOperator{}
}

View File

@@ -0,0 +1,69 @@
package models
import "encoding/json"
func (this *IPLibraryFile) DecodeCountries() []string {
var countries = []string{}
if IsNotNull(this.Countries) {
err := json.Unmarshal(this.Countries, &countries)
if err != nil {
// ignore error
}
}
return countries
}
func (this *IPLibraryFile) DecodeProvinces() [][2]string {
var provinces = [][2]string{}
if IsNotNull(this.Provinces) {
err := json.Unmarshal(this.Provinces, &provinces)
if err != nil {
// ignore error
}
}
return provinces
}
func (this *IPLibraryFile) DecodeCities() [][3]string {
var cities = [][3]string{}
if IsNotNull(this.Cities) {
err := json.Unmarshal(this.Cities, &cities)
if err != nil {
// ignore error
}
}
return cities
}
func (this *IPLibraryFile) DecodeTowns() [][4]string {
var towns = [][4]string{}
if IsNotNull(this.Towns) {
err := json.Unmarshal(this.Towns, &towns)
if err != nil {
// ignore error
}
}
return towns
}
func (this *IPLibraryFile) DecodeProviders() []string {
var providers = []string{}
if IsNotNull(this.Providers) {
err := json.Unmarshal(this.Providers, &providers)
if err != nil {
// ignore error
}
}
return providers
}
func (this *IPLibraryFile) DecodeEmptyValues() []string {
var result = []string{}
if IsNotNull(this.EmptyValues) {
err := json.Unmarshal(this.EmptyValues, &result)
if err != nil {
// ignore error
}
}
return result
}

View File

@@ -1,22 +1,26 @@
package models
// IP库
// IPLibrary IP库
type IPLibrary struct {
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
FileId uint32 `field:"fileId"` // 文件ID
Type string `field:"type"` // 类型
Name string `field:"name"` // 名称
IsPublic bool `field:"isPublic"` // 是否公用
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type IPLibraryOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
FileId interface{} // 文件ID
Type interface{} // 类型
State interface{} // 状态
CreatedAt interface{} // 创建时间
Id any // ID
AdminId any // 管理员ID
FileId any // 文件ID
Type any // 类型
Name any // 名称
IsPublic any // 是否公用
State any // 状态
CreatedAt any // 创建时间
}
func NewIPLibraryOperator() *IPLibraryOperator {

View File

@@ -3,6 +3,7 @@ package models
import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -38,7 +39,7 @@ func init() {
func (this *LogDAO) CreateLog(tx *dbs.Tx, adminType string, adminId int64, level string, description string, action string, ip string) error {
var op = NewLogOperator()
op.Level = level
op.Description = description
op.Description = utils.LimitString(description, 1000)
op.Action = action
op.Ip = ip
op.Type = adminType

View File

@@ -1,290 +0,0 @@
package nameservers
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/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSDomainStateEnabled = 1 // 已启用
NSDomainStateDisabled = 0 // 已禁用
)
type NSDomainDAO dbs.DAO
func NewNSDomainDAO() *NSDomainDAO {
return dbs.NewDAO(&NSDomainDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSDomains",
Model: new(NSDomain),
PkName: "id",
},
}).(*NSDomainDAO)
}
var SharedNSDomainDAO *NSDomainDAO
func init() {
dbs.OnReady(func() {
SharedNSDomainDAO = NewNSDomainDAO()
})
}
// EnableNSDomain 启用条目
func (this *NSDomainDAO) EnableNSDomain(tx *dbs.Tx, domainId int64) error {
_, err := this.Query(tx).
Pk(domainId).
Set("state", NSDomainStateEnabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// DisableNSDomain 禁用条目
func (this *NSDomainDAO) DisableNSDomain(tx *dbs.Tx, domainId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(domainId).
Set("state", NSDomainStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// FindEnabledNSDomain 查找启用中的条目
func (this *NSDomainDAO) FindEnabledNSDomain(tx *dbs.Tx, id int64) (*NSDomain, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSDomainStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSDomain), err
}
// FindNSDomainName 根据主键查找名称
func (this *NSDomainDAO) FindNSDomainName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateDomain 创建域名
func (this *NSDomainDAO) CreateDomain(tx *dbs.Tx, clusterId int64, userId int64, name string) (int64, error) {
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
var op = NewNSDomainOperator()
op.ClusterId = clusterId
op.UserId = userId
op.Name = name
op.Version = version
op.IsOn = true
op.State = NSDomainStateEnabled
domainId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, domainId)
if err != nil {
return domainId, err
}
return domainId, nil
}
// UpdateDomain 修改域名
func (this *NSDomainDAO) UpdateDomain(tx *dbs.Tx, domainId int64, clusterId int64, userId int64, isOn bool) error {
if domainId <= 0 {
return errors.New("invalid domainId")
}
oldClusterId, err := this.Query(tx).
Pk(domainId).
Result("clusterId").
FindInt64Col(0)
if err != nil {
return err
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
var op = NewNSDomainOperator()
op.Id = domainId
op.ClusterId = clusterId
op.UserId = userId
op.IsOn = isOn
op.Version = version
err = this.Save(tx, op)
if err != nil {
return err
}
// 通知更新
if oldClusterId > 0 && oldClusterId != clusterId {
err = models.SharedNSClusterDAO.NotifyUpdate(tx, oldClusterId)
if err != nil {
return err
}
}
return this.NotifyUpdate(tx, domainId)
}
// CountAllEnabledDomains 计算域名数量
func (this *NSDomainDAO) CountAllEnabledDomains(tx *dbs.Tx, clusterId int64, userId int64, keyword string) (int64, error) {
query := this.Query(tx)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
query.Where("clusterId IN (SELECT id FROM " + models.SharedNSClusterDAO.Table + " WHERE state=1)")
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Where("(userId=0 OR userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1))")
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.
State(NSDomainStateEnabled).
Count()
}
// ListEnabledDomains 列出单页域名
func (this *NSDomainDAO) ListEnabledDomains(tx *dbs.Tx, clusterId int64, userId int64, keyword string, offset int64, size int64) (result []*NSDomain, err error) {
query := this.Query(tx)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
query.Where("clusterId IN (SELECT id FROM " + models.SharedNSClusterDAO.Table + " WHERE state=1)")
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Where("(userId=0 OR userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1))")
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
State(NSDomainStateEnabled).
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSDomainDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_DOMAIN_VERSION", 1)
}
// ListDomainsAfterVersion 列出某个版本后的域名
func (this *NSDomainDAO) ListDomainsAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSDomain, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// FindDomainIdWithName 根据名称查找域名
func (this *NSDomainDAO) FindDomainIdWithName(tx *dbs.Tx, clusterId int64, name string) (int64, error) {
return this.Query(tx).
Attr("clusterId", clusterId).
Attr("name", name).
State(NSDomainStateEnabled).
ResultPk().
FindInt64Col(0)
}
// FindEnabledDomainTSIG 获取TSIG配置
func (this *NSDomainDAO) FindEnabledDomainTSIG(tx *dbs.Tx, domainId int64) ([]byte, error) {
tsig, err := this.Query(tx).
Pk(domainId).
Result("tsig").
FindStringCol("")
if err != nil {
return nil, err
}
return []byte(tsig), nil
}
// UpdateDomainTSIG 修改TSIG配置
func (this *NSDomainDAO) UpdateDomainTSIG(tx *dbs.Tx, domainId int64, tsigJSON []byte) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(domainId).
Set("tsig", tsigJSON).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// FindEnabledDomainClusterId 获取域名的集群ID
func (this *NSDomainDAO) FindEnabledDomainClusterId(tx *dbs.Tx, domainId int64) (int64, error) {
return this.Query(tx).
Pk(domainId).
State(NSDomainStateEnabled).
Result("clusterId").
FindInt64Col(0)
}
// NotifyUpdate 通知更改
func (this *NSDomainDAO) NotifyUpdate(tx *dbs.Tx, domainId int64) error {
clusterId, err := this.Query(tx).
Result("clusterId").
Pk(domainId).
FindInt64Col(0)
if err != nil {
return err
}
if clusterId > 0 {
return models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, models.NSNodeTaskTypeDomainChanged)
}
return nil
}

View File

@@ -0,0 +1,24 @@
package nameservers
// NSDomainGroup 域名分组
type NSDomainGroup struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
Name string `field:"name"` // 分组名称
IsOn bool `field:"isOn"` // 是否启用
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type NSDomainGroupOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Name interface{} // 分组名称
IsOn interface{} // 是否启用
Order interface{} // 排序
State interface{} // 状态
}
func NewNSDomainGroupOperator() *NSDomainGroupOperator {
return &NSDomainGroupOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -4,27 +4,35 @@ import "github.com/iwind/TeaGo/dbs"
// NSDomain DNS域名
type NSDomain struct {
Id uint32 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
Version uint64 `field:"version"` // 版本
State uint8 `field:"state"` // 状态
Tsig dbs.JSON `field:"tsig"` // TSIG配置
Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 域名
GroupIds dbs.JSON `field:"groupIds"` // 分组ID
Tsig dbs.JSON `field:"tsig"` // TSIG配置
VerifyTXT string `field:"verifyTXT"` // 验证用的TXT
VerifyExpiresAt uint64 `field:"verifyExpiresAt"` // 验证TXT过期时间
CreatedAt uint64 `field:"createdAt"` // 创建时间
Version uint64 `field:"version"` // 版本号
Status string `field:"status"` // 状态none|verified
State uint8 `field:"state"` // 状态
}
type NSDomainOperator struct {
Id interface{} // ID
ClusterId interface{} // 集群ID
UserId interface{} // 用户ID
IsOn interface{} // 是否启用
Name interface{} // 域名
CreatedAt interface{} // 创建时间
Version interface{} // 版本
State interface{} // 状态
Tsig interface{} // TSIG配置
Id any // ID
ClusterId any // 集群ID
UserId any // 用户ID
IsOn any // 是否启用
Name any // 域名
GroupIds any // 分组ID
Tsig any // TSIG配置
VerifyTXT any // 验证用的TXT
VerifyExpiresAt any // 验证TXT过期时间
CreatedAt any // 创建时间
Version any // 版本号
Status any // 状态none|verified
State any // 状态
}
func NewNSDomainOperator() *NSDomainOperator {

View File

@@ -1 +1,20 @@
package nameservers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
)
func (this *NSDomain) DecodeGroupIds() []int64 {
if models.IsNull(this.GroupIds) {
return nil
}
var result = []int64{}
err := json.Unmarshal(this.GroupIds, &result)
if err != nil {
remotelogs.Error("NSDomain", "DecodeGroupIds:"+err.Error())
}
return result
}

View File

@@ -1,209 +0,0 @@
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSKeyStateEnabled = 1 // 已启用
NSKeyStateDisabled = 0 // 已禁用
)
type NSKeyDAO dbs.DAO
func NewNSKeyDAO() *NSKeyDAO {
return dbs.NewDAO(&NSKeyDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSKeys",
Model: new(NSKey),
PkName: "id",
},
}).(*NSKeyDAO)
}
var SharedNSKeyDAO *NSKeyDAO
func init() {
dbs.OnReady(func() {
SharedNSKeyDAO = NewNSKeyDAO()
})
}
// EnableNSKey 启用条目
func (this *NSKeyDAO) EnableNSKey(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSKeyStateEnabled).
Update()
return err
}
// DisableNSKey 禁用条目
func (this *NSKeyDAO) DisableNSKey(tx *dbs.Tx, keyId int64) error {
_, err := this.Query(tx).
Pk(keyId).
Set("state", NSKeyStateDisabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, keyId)
}
// FindEnabledNSKey 查找启用中的条目
func (this *NSKeyDAO) FindEnabledNSKey(tx *dbs.Tx, id int64) (*NSKey, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSKeyStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSKey), err
}
// FindNSKeyName 根据主键查找名称
func (this *NSKeyDAO) FindNSKeyName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateKey 创建Key
func (this *NSKeyDAO) CreateKey(tx *dbs.Tx, domainId int64, zoneId int64, name string, algo dnsconfigs.KeyAlgorithmType, secret string, secretType string) (int64, error) {
var op = NewNSKeyOperator()
op.DomainId = domainId
op.ZoneId = zoneId
op.Name = name
op.Algo = algo
op.Secret = secret
op.SecretType = secretType
op.State = NSKeyStateEnabled
keyId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, keyId)
if err != nil {
return keyId, err
}
return keyId, nil
}
// UpdateKey 修改Key
func (this *NSKeyDAO) UpdateKey(tx *dbs.Tx, keyId int64, name string, algo dnsconfigs.KeyAlgorithmType, secret string, secretType string, isOn bool) error {
if keyId <= 0 {
return errors.New("invalid keyId")
}
var op = NewNSKeyOperator()
op.Id = keyId
op.Name = name
op.Algo = algo
op.Secret = secret
op.SecretType = secretType
op.IsOn = isOn
err := this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx, keyId)
}
// CountEnabledKeys 计算Key的数量
func (this *NSKeyDAO) CountEnabledKeys(tx *dbs.Tx, domainId int64, zoneId int64) (int64, error) {
var query = this.Query(tx).
State(NSKeyStateEnabled)
if domainId > 0 {
query.Attr("domainId", domainId)
}
if zoneId > 0 {
query.Attr("zoneId", zoneId)
}
return query.Count()
}
// ListEnabledKeys 列出单页Key
func (this *NSKeyDAO) ListEnabledKeys(tx *dbs.Tx, domainId int64, zoneId int64, offset int64, size int64) (result []*NSKey, err error) {
var query = this.Query(tx).
State(NSKeyStateEnabled)
if domainId > 0 {
query.Attr("domainId", domainId)
}
if zoneId > 0 {
query.Attr("zoneId", zoneId)
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSKeyDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_KEY_VERSION", 1)
}
// ListKeysAfterVersion 列出某个版本后的密钥
func (this *NSKeyDAO) ListKeysAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSKey, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// NotifyUpdate 通知更新
func (this *NSKeyDAO) NotifyUpdate(tx *dbs.Tx, keyId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(keyId).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
// 通知集群
domainId, err := this.Query(tx).
Pk(keyId).
Result("domainId").
FindInt64Col(0)
if err != nil {
return err
}
if domainId > 0 {
clusterId, err := SharedNSDomainDAO.FindEnabledDomainClusterId(tx, domainId)
if err != nil {
return err
}
if clusterId > 0 {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, models.NSNodeTaskTypeKeyChanged)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,30 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSPlan NS套餐
type NSPlan struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 套餐名称
IsOn bool `field:"isOn"` // 是否启用
MonthlyPrice float64 `field:"monthlyPrice"` // 月价格
YearlyPrice float64 `field:"yearlyPrice"` // 年价格
Order uint32 `field:"order"` // 排序
Config dbs.JSON `field:"config"` // 配置
State uint8 `field:"state"` // 状态
}
type NSPlanOperator struct {
Id any // ID
Name any // 套餐名称
IsOn any // 是否启用
MonthlyPrice any // 月价格
YearlyPrice any // 年价格
Order any // 排序
Config any // 配置
State any // 状态
}
func NewNSPlanOperator() *NSPlanOperator {
return &NSPlanOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -1,67 +0,0 @@
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
)
type NSQuestionOptionDAO dbs.DAO
func NewNSQuestionOptionDAO() *NSQuestionOptionDAO {
return dbs.NewDAO(&NSQuestionOptionDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSQuestionOptions",
Model: new(NSQuestionOption),
PkName: "id",
},
}).(*NSQuestionOptionDAO)
}
var SharedNSQuestionOptionDAO *NSQuestionOptionDAO
func init() {
dbs.OnReady(func() {
SharedNSQuestionOptionDAO = NewNSQuestionOptionDAO()
})
}
// FindNSQuestionOptionName 根据主键查找名称
func (this *NSQuestionOptionDAO) FindNSQuestionOptionName(tx *dbs.Tx, id uint64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateOption 创建选项
func (this *NSQuestionOptionDAO) CreateOption(tx *dbs.Tx, name string, values maps.Map) (int64, error) {
if values == nil {
values = maps.Map{}
}
var op = NewNSQuestionOptionOperator()
op.Name = name
op.Values = values.AsJSON()
return this.SaveInt64(tx, op)
}
// FindOption 读取选项
func (this *NSQuestionOptionDAO) FindOption(tx *dbs.Tx, optionId int64) (*NSQuestionOption, error) {
one, err := this.Query(tx).
Pk(optionId).
Find()
if one == nil {
return nil, err
}
return one.(*NSQuestionOption), nil
}
// DeleteOption 删除选项
func (this *NSQuestionOptionDAO) DeleteOption(tx *dbs.Tx, optionId int64) error {
_, err := this.Query(tx).
Pk(optionId).
Delete()
return err
}

View File

@@ -1,289 +0,0 @@
package nameservers
import (
"encoding/json"
"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/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSRecordStateEnabled = 1 // 已启用
NSRecordStateDisabled = 0 // 已禁用
)
type NSRecordDAO dbs.DAO
func NewNSRecordDAO() *NSRecordDAO {
return dbs.NewDAO(&NSRecordDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRecords",
Model: new(NSRecord),
PkName: "id",
},
}).(*NSRecordDAO)
}
var SharedNSRecordDAO *NSRecordDAO
func init() {
dbs.OnReady(func() {
SharedNSRecordDAO = NewNSRecordDAO()
})
}
// EnableNSRecord 启用条目
func (this *NSRecordDAO) EnableNSRecord(tx *dbs.Tx, recordId int64) error {
_, err := this.Query(tx).
Pk(recordId).
Set("state", NSRecordStateEnabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// DisableNSRecord 禁用条目
func (this *NSRecordDAO) DisableNSRecord(tx *dbs.Tx, recordId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(recordId).
Set("state", NSRecordStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// FindEnabledNSRecord 查找启用中的条目
func (this *NSRecordDAO) FindEnabledNSRecord(tx *dbs.Tx, id int64) (*NSRecord, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSRecordStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRecord), err
}
// FindNSRecordName 根据主键查找名称
func (this *NSRecordDAO) FindNSRecordName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateRecord 创建记录
func (this *NSRecordDAO) CreateRecord(tx *dbs.Tx, domainId int64, description string, name string, dnsType dnsconfigs.RecordType, value string, ttl int32, routeIds []string) (int64, error) {
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
var op = NewNSRecordOperator()
op.DomainId = domainId
op.Description = description
op.Name = name
op.Type = dnsType
op.Value = value
op.Ttl = ttl
if len(routeIds) == 0 {
op.RouteIds = `["default"]`
} else {
routeIds, err := json.Marshal(routeIds)
if err != nil {
return 0, err
}
op.RouteIds = routeIds
}
op.IsOn = true
op.State = NSRecordStateEnabled
op.Version = version
recordId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, recordId)
if err != nil {
return 0, err
}
return recordId, nil
}
// UpdateRecord 修改记录
func (this *NSRecordDAO) UpdateRecord(tx *dbs.Tx, recordId int64, description string, name string, dnsType dnsconfigs.RecordType, value string, ttl int32, routeIds []string, isOn bool) error {
if recordId <= 0 {
return errors.New("invalid recordId")
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
var op = NewNSRecordOperator()
op.Id = recordId
op.Description = description
op.Name = name
op.Type = dnsType
op.Value = value
op.Ttl = ttl
op.IsOn = isOn
if len(routeIds) == 0 {
op.RouteIds = `["default"]`
} else {
routeIds, err := json.Marshal(routeIds)
if err != nil {
return err
}
op.RouteIds = routeIds
}
op.Version = version
err = this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// CountAllEnabledDomainRecords 计算域名中记录数量
func (this *NSRecordDAO) CountAllEnabledDomainRecords(tx *dbs.Tx, domainId int64, dnsType dnsconfigs.RecordType, keyword string, routeCode string) (int64, error) {
query := this.Query(tx).
Attr("domainId", domainId).
State(NSRecordStateEnabled)
if len(dnsType) > 0 {
query.Attr("type", dnsType)
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode)
if err != nil {
return 0, err
}
query.JSONContains("routeIds", string(routeCodeJSON))
}
return query.Count()
}
// CountAllEnabledRecords 计算所有记录数量
func (this *NSRecordDAO) CountAllEnabledRecords(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
Where("domainId IN (SELECT id FROM " + SharedNSDomainDAO.Table + " WHERE state=1)").
State(NSRecordStateEnabled).
Count()
}
// ListEnabledRecords 列出单页记录
func (this *NSRecordDAO) ListEnabledRecords(tx *dbs.Tx, domainId int64, dnsType dnsconfigs.RecordType, keyword string, routeCode string, offset int64, size int64) (result []*NSRecord, err error) {
query := this.Query(tx).
Attr("domainId", domainId).
State(NSRecordStateEnabled)
if len(dnsType) > 0 {
query.Attr("type", dnsType)
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode)
if err != nil {
return nil, err
}
query.JSONContains("routeIds", string(routeCodeJSON))
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSRecordDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_RECORD_VERSION", 1)
}
// ListRecordsAfterVersion 列出某个版本后的记录
func (this *NSRecordDAO) ListRecordsAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSRecord, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// FindEnabledRecordWithName 查询单条记录
func (this *NSRecordDAO) FindEnabledRecordWithName(tx *dbs.Tx, domainId int64, recordName string, recordType dnsconfigs.RecordType) (*NSRecord, error) {
record, err := this.Query(tx).
State(NSRecordStateEnabled).
Attr("domainId", domainId).
Attr("name", recordName).
Attr("type", recordType).
Find()
if record == nil {
return nil, err
}
return record.(*NSRecord), nil
}
// NotifyUpdate 通知更新
func (this *NSRecordDAO) NotifyUpdate(tx *dbs.Tx, recordId int64) error {
domainId, err := this.Query(tx).
Pk(recordId).
Result("domainId").
FindInt64Col(0)
if err != nil {
return err
}
if domainId == 0 {
return nil
}
clusterId, err := SharedNSDomainDAO.FindEnabledDomainClusterId(tx, domainId)
if err != nil {
return err
}
if clusterId > 0 {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, models.NSNodeTaskTypeRecordChanged)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,29 +0,0 @@
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestNSRecord_DecodeRouteIds(t *testing.T) {
{
record := &NSRecord{}
t.Log(record.DecodeRouteIds())
}
{
record := &NSRecord{RouteIds: []byte("[]")}
t.Log(record.DecodeRouteIds())
}
{
record := &NSRecord{RouteIds: []byte("[1, 2, 3]")}
t.Log(record.DecodeRouteIds())
}
{
record := &NSRecord{RouteIds: []byte(`["id:1", "id:2", "isp:liantong"]`)}
t.Log(record.DecodeRouteIds())
}
}

View File

@@ -1,169 +0,0 @@
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "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/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type NSRecordHourlyStatDAO dbs.DAO
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 := SharedNSRecordHourlyStatDAO.Clean(nil, 30) // 只保留N天
if err != nil {
remotelogs.Error("NodeClusterTrafficDailyStatDAO", "clean expired data failed: "+err.Error())
}
}
})
})
}
func NewNSRecordHourlyStatDAO() *NSRecordHourlyStatDAO {
return dbs.NewDAO(&NSRecordHourlyStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRecordHourlyStats",
Model: new(NSRecordHourlyStat),
PkName: "id",
},
}).(*NSRecordHourlyStatDAO)
}
var SharedNSRecordHourlyStatDAO *NSRecordHourlyStatDAO
func init() {
dbs.OnReady(func() {
SharedNSRecordHourlyStatDAO = NewNSRecordHourlyStatDAO()
})
}
// IncreaseHourlyStat 增加统计数据
func (this *NSRecordHourlyStatDAO) IncreaseHourlyStat(tx *dbs.Tx, clusterId int64, nodeId int64, hour string, domainId int64, recordId int64, countRequests int64, bytes int64) error {
if len(hour) != 10 {
return errors.New("invalid hour '" + hour + "'")
}
return this.Query(tx).
Param("countRequests", countRequests).
Param("bytes", bytes).
InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId,
"nodeId": nodeId,
"domainId": domainId,
"recordId": recordId,
"day": hour[:8],
"hour": hour,
"countRequests": countRequests,
"bytes": bytes,
}, maps.Map{
"countRequests": dbs.SQL("countRequests+:countRequests"),
"bytes": dbs.SQL("bytes+:bytes"),
})
}
// FindHourlyStats 按小时统计
func (this *NSRecordHourlyStatDAO) FindHourlyStats(tx *dbs.Tx, hourFrom string, hourTo string) (result []*NSRecordHourlyStat, err error) {
ones, err := this.Query(tx).
Result("hour", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("hour").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // hour => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Hour] = one.(*NSRecordHourlyStat)
}
hours, err := utils.RangeHours(hourFrom, hourTo)
if err != nil {
return nil, err
}
for _, hour := range hours {
stat, ok := m[hour]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Hour: hour,
})
}
}
return
}
// FindDailyStats 按天统计
func (this *NSRecordHourlyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*NSRecordHourlyStat, err error) {
ones, err := this.Query(tx).
Result("day", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("day", dayFrom, dayTo).
Group("day").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // day => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Day] = one.(*NSRecordHourlyStat)
}
days, err := utils.RangeDays(dayFrom, dayTo)
if err != nil {
return nil, err
}
for _, day := range days {
stat, ok := m[day]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Day: day,
})
}
}
return
}
// ListTopNodes 节点排行
func (this *NSRecordHourlyStatDAO) ListTopNodes(tx *dbs.Tx, hourFrom string, hourTo string, size int64) (result []*NSRecordHourlyStat, err error) {
_, err = this.Query(tx).
Result("MIN(clusterId) AS clusterId", "nodeId", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("nodeId").
Limit(size).
Slice(&result).
FindAll()
return
}
// ListTopDomains 域名排行
func (this *NSRecordHourlyStatDAO) ListTopDomains(tx *dbs.Tx, hourFrom string, hourTo string, size int64) (result []*NSRecordHourlyStat, err error) {
_, err = this.Query(tx).
Result("domainId", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("domainId").
Limit(size).
Slice(&result).
FindAll()
return
}
// Clean 清理历史数据
func (this *NSRecordHourlyStatDAO) Clean(tx *dbs.Tx, days int) error {
var hour = timeutil.Format("Ymd00", time.Now().AddDate(0, 0, -days))
_, err := this.Query(tx).
Lt("hour", hour).
Delete()
return err
}

View File

@@ -5,11 +5,11 @@ type NSRecordHourlyStat struct {
Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
NodeId uint32 `field:"nodeId"` // 节点ID
DomainId uint32 `field:"domainId"` // 域名ID
DomainId uint64 `field:"domainId"` // 域名ID
RecordId uint64 `field:"recordId"` // 记录ID
Day string `field:"day"` // YYYYMMDD
Hour string `field:"hour"` // YYYYMMDDHH
CountRequests uint32 `field:"countRequests"` // 请求数
CountRequests uint64 `field:"countRequests"` // 请求数
Bytes uint64 `field:"bytes"` // 流量
}

View File

@@ -1,268 +0,0 @@
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"regexp"
"strings"
)
const (
NSRouteStateEnabled = 1 // 已启用
NSRouteStateDisabled = 0 // 已禁用
)
type NSRouteDAO dbs.DAO
func NewNSRouteDAO() *NSRouteDAO {
return dbs.NewDAO(&NSRouteDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRoutes",
Model: new(NSRoute),
PkName: "id",
},
}).(*NSRouteDAO)
}
var SharedNSRouteDAO *NSRouteDAO
func init() {
dbs.OnReady(func() {
SharedNSRouteDAO = NewNSRouteDAO()
})
}
// EnableNSRoute 启用条目
func (this *NSRouteDAO) EnableNSRoute(tx *dbs.Tx, routeId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(routeId).
Set("state", NSRouteStateEnabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// DisableNSRoute 禁用条目
func (this *NSRouteDAO) DisableNSRoute(tx *dbs.Tx, routeId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(routeId).
Set("state", NSRouteStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// FindEnabledNSRoute 查找启用中的条目
func (this *NSRouteDAO) FindEnabledNSRoute(tx *dbs.Tx, id int64) (*NSRoute, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSRouteStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRoute), err
}
// FindEnabledRouteWithCode 根据代号获取线路信息
func (this *NSRouteDAO) FindEnabledRouteWithCode(tx *dbs.Tx, code string) (*NSRoute, error) {
if regexp.MustCompile(`^id:\d+$`).MatchString(code) {
var routeId = types.Int64(code[strings.Index(code, ":")+1:])
route, err := this.FindEnabledNSRoute(tx, routeId)
if route == nil || err != nil {
return nil, err
}
route.Code = "id:" + types.String(routeId)
return route, nil
}
route := dnsconfigs.FindDefaultRoute(code)
if route == nil {
return nil, nil
}
return &NSRoute{
Id: 0,
IsOn: true,
Name: route.Name,
Code: route.Code,
State: NSRouteStateEnabled,
}, nil
}
// FindNSRouteName 根据主键查找名称
func (this *NSRouteDAO) FindNSRouteName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateRoute 创建线路
func (this *NSRouteDAO) CreateRoute(tx *dbs.Tx, clusterId int64, domainId int64, userId int64, name string, rangesJSON []byte) (int64, error) {
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
var op = NewNSRouteOperator()
op.ClusterId = clusterId
op.DomainId = domainId
op.UserId = userId
op.Name = name
if len(rangesJSON) > 0 {
op.Ranges = rangesJSON
} else {
op.Ranges = "[]"
}
op.IsOn = true
op.State = NSRouteStateEnabled
op.Version = version
routeId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx)
if err != nil {
return 0, err
}
return routeId, nil
}
// UpdateRoute 修改线路
func (this *NSRouteDAO) UpdateRoute(tx *dbs.Tx, routeId int64, name string, rangesJSON []byte) error {
if routeId <= 0 {
return errors.New("invalid routeId")
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
var op = NewNSRouteOperator()
op.Id = routeId
op.Name = name
if len(rangesJSON) > 0 {
op.Ranges = rangesJSON
} else {
op.Ranges = "[]"
}
op.Version = version
err = this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// UpdateRouteOrders 修改线路排序
func (this *NSRouteDAO) UpdateRouteOrders(tx *dbs.Tx, routeIds []int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
order := len(routeIds)
for _, routeId := range routeIds {
_, err = this.Query(tx).
Pk(routeId).
Set("order", order).
Set("version", version).
Update()
if err != nil {
return err
}
order--
}
return this.NotifyUpdate(tx)
}
// FindAllEnabledRoutes 列出所有线路
func (this *NSRouteDAO) FindAllEnabledRoutes(tx *dbs.Tx, clusterId int64, domainId int64, userId int64) (result []*NSRoute, err error) {
query := this.Query(tx).
State(NSRouteStateEnabled).
Slice(&result).
Desc("order").
AscPk()
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
// 不查询所有集群的线路
query.Attr("clusterId", 0)
}
if domainId > 0 {
query.Attr("domainId", domainId)
}
if userId > 0 {
query.Attr("userId", userId)
}
_, err = query.FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSRouteDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_ROUTE_VERSION", 1)
}
// ListRoutesAfterVersion 列出某个版本后的域名
func (this *NSRouteDAO) ListRoutesAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSRoute, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// NotifyUpdate 通知更新
func (this *NSRouteDAO) NotifyUpdate(tx *dbs.Tx) error {
// 线路变更时所有集群都要更新
clusterIds, err := models.SharedNSClusterDAO.FindAllEnabledClusterIds(tx)
if err != nil {
return err
}
for _, clusterId := range clusterIds {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, models.NSNodeTaskTypeRouteChanged)
if err != nil {
return err
}
}
return nil
}

View File

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

View File

@@ -0,0 +1,28 @@
package nameservers
// NSUserPlan 用户套餐
type NSUserPlan struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
PlanId uint32 `field:"planId"` // 套餐ID
DayFrom string `field:"dayFrom"` // YYYYMMDD
DayTo string `field:"dayTo"` // YYYYMMDD
PeriodUnit string `field:"periodUnit"` // monthly|yearly
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
}
type NSUserPlanOperator struct {
Id any // ID
UserId any // 用户ID
PlanId any // 套餐ID
DayFrom any // YYYYMMDD
DayTo any // YYYYMMDD
PeriodUnit any // monthly|yearly
CreatedAt any // 创建时间
State any // 状态
}
func NewNSUserPlanOperator() *NSUserPlanOperator {
return &NSUserPlanOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -1,63 +0,0 @@
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSZoneStateEnabled = 1 // 已启用
NSZoneStateDisabled = 0 // 已禁用
)
type NSZoneDAO dbs.DAO
func NewNSZoneDAO() *NSZoneDAO {
return dbs.NewDAO(&NSZoneDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSZones",
Model: new(NSZone),
PkName: "id",
},
}).(*NSZoneDAO)
}
var SharedNSZoneDAO *NSZoneDAO
func init() {
dbs.OnReady(func() {
SharedNSZoneDAO = NewNSZoneDAO()
})
}
// EnableNSZone 启用条目
func (this *NSZoneDAO) EnableNSZone(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSZoneStateEnabled).
Update()
return err
}
// DisableNSZone 禁用条目
func (this *NSZoneDAO) DisableNSZone(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSZoneStateDisabled).
Update()
return err
}
// FindEnabledNSZone 查找启用中的条目
func (this *NSZoneDAO) FindEnabledNSZone(tx *dbs.Tx, id uint64) (*NSZone, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSZoneStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSZone), err
}

View File

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

View File

@@ -55,12 +55,16 @@ func (this *NodeClusterDAO) EnableNodeCluster(tx *dbs.Tx, id int64) error {
}
// DisableNodeCluster 禁用条目
func (this *NodeClusterDAO) DisableNodeCluster(tx *dbs.Tx, id int64) error {
func (this *NodeClusterDAO) DisableNodeCluster(tx *dbs.Tx, clusterId int64) error {
_, err := this.Query(tx).
Pk(id).
Pk(clusterId).
Set("state", NodeClusterStateDisabled).
Update()
return err
if err != nil {
return err
}
return SharedNodeLogDAO.DeleteNodeLogsWithCluster(tx, nodeconfigs.NodeRoleNode, clusterId)
}
// FindEnabledNodeCluster 查找集群
@@ -121,13 +125,13 @@ func (this *NodeClusterDAO) FindAllEnableClusterIds(tx *dbs.Tx) (result []int64,
}
// CreateCluster 创建集群
func (this *NodeClusterDAO) CreateCluster(tx *dbs.Tx, adminId int64, name string, grantId int64, installDir string, dnsDomainId int64, dnsName string, cachePolicyId int64, httpFirewallPolicyId int64, systemServices map[string]maps.Map) (clusterId int64, err error) {
func (this *NodeClusterDAO) CreateCluster(tx *dbs.Tx, adminId int64, name string, grantId int64, installDir string, dnsDomainId int64, dnsName string, dnsTTL int32, cachePolicyId int64, httpFirewallPolicyId int64, systemServices map[string]maps.Map, globalServerConfig *serverconfigs.GlobalServerConfig, autoInstallNftables bool) (clusterId int64, err error) {
uniqueId, err := this.GenUniqueId(tx)
if err != nil {
return 0, err
}
secret := rands.String(32)
var secret = rands.String(32)
err = SharedApiTokenDAO.CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleCluster)
if err != nil {
return 0, err
@@ -143,11 +147,12 @@ func (this *NodeClusterDAO) CreateCluster(tx *dbs.Tx, adminId int64, name string
op.DnsDomainId = dnsDomainId
op.DnsName = dnsName
var dnsConfig = &dnsconfigs.ClusterDNSConfig{
NodesAutoSync: true,
ServersAutoSync: true,
CNameRecords: []string{},
CNameAsDomain: true,
TTL: 0,
NodesAutoSync: true,
ServersAutoSync: true,
CNAMERecords: []string{},
CNAMEAsDomain: true,
TTL: dnsTTL,
IncludingLnNodes: true,
}
dnsJSON, err := json.Marshal(dnsConfig)
if err != nil {
@@ -168,10 +173,21 @@ func (this *NodeClusterDAO) CreateCluster(tx *dbs.Tx, adminId int64, name string
}
op.SystemServices = systemServicesJSON
// 全局服务配置
if globalServerConfig == nil {
globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
}
globalServerConfigJSON, err := json.Marshal(globalServerConfig)
if err != nil {
return 0, err
}
op.GlobalServerConfig = globalServerConfigJSON
op.UseAllAPINodes = 1
op.ApiNodes = "[]"
op.UniqueId = uniqueId
op.Secret = secret
op.AutoInstallNftables = autoInstallNftables
op.State = NodeClusterStateEnabled
err = this.Save(tx, op)
if err != nil {
@@ -182,7 +198,7 @@ func (this *NodeClusterDAO) CreateCluster(tx *dbs.Tx, adminId int64, name string
}
// UpdateCluster 修改集群
func (this *NodeClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, grantId int64, installDir string, timezone string, nodeMaxThreads int32, autoOpenPorts bool) error {
func (this *NodeClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, grantId int64, installDir string, timezone string, nodeMaxThreads int32, autoOpenPorts bool, clockConfig *nodeconfigs.ClockConfig, autoRemoteStart bool, autoInstallTables bool) error {
if clusterId <= 0 {
return errors.New("invalid clusterId")
}
@@ -199,6 +215,17 @@ func (this *NodeClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name stri
op.NodeMaxThreads = nodeMaxThreads
op.AutoOpenPorts = autoOpenPorts
if clockConfig != nil {
clockJSON, err := json.Marshal(clockConfig)
if err != nil {
return err
}
op.Clock = clockJSON
}
op.AutoRemoteStart = autoRemoteStart
op.AutoInstallNftables = autoInstallTables
err := this.Save(tx, op)
if err != nil {
return err
@@ -439,7 +466,7 @@ func (this *NodeClusterDAO) FindClusterDNSInfo(tx *dbs.Tx, clusterId int64, cach
one, err := this.Query(tx).
Pk(clusterId).
Result("id", "name", "dnsName", "dnsDomainId", "dns", "isOn").
Result("id", "name", "dnsName", "dnsDomainId", "dns", "isOn", "state").
Find()
if err != nil {
return nil, err
@@ -464,7 +491,7 @@ func (this *NodeClusterDAO) ExistClusterDNSName(tx *dbs.Tx, dnsName string, excl
}
// UpdateClusterDNS 修改集群DNS相关信息
func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsName string, dnsDomainId int64, nodesAutoSync bool, serversAutoSync bool, cnameRecords []string, ttl int32, cnameAsDomain bool) error {
func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsName string, dnsDomainId int64, nodesAutoSync bool, serversAutoSync bool, cnameRecords []string, ttl int32, cnameAsDomain bool, includingLnNodes bool) error {
if clusterId <= 0 {
return errors.New("invalid clusterId")
}
@@ -483,10 +510,16 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
var oldCluster = oldOne.(*NodeCluster)
var oldDNSDomainId = int64(oldCluster.DnsDomainId)
var shouldRemoveOld = false
if (oldDNSDomainId > 0 && oldDNSDomainId != dnsDomainId) || (oldCluster.DnsName != dnsName) {
err = dns.SharedDNSTaskDAO.CreateClusterRemoveTask(tx, clusterId, oldDNSDomainId, oldCluster.DnsName)
if err != nil {
return err
if oldDNSDomainId == dnsDomainId {
// 如果只是换子域名需要在新的域名添加之前先删除老的子域名防止无法添加CNAME
err = dns.SharedDNSTaskDAO.CreateClusterRemoveTask(tx, clusterId, oldDNSDomainId, oldCluster.DnsName)
if err != nil {
return err
}
} else {
shouldRemoveOld = true
}
}
@@ -500,11 +533,12 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
}
var dnsConfig = &dnsconfigs.ClusterDNSConfig{
NodesAutoSync: nodesAutoSync,
ServersAutoSync: serversAutoSync,
CNameRecords: cnameRecords,
TTL: ttl,
CNameAsDomain: cnameAsDomain,
NodesAutoSync: nodesAutoSync,
ServersAutoSync: serversAutoSync,
CNAMERecords: cnameRecords,
TTL: ttl,
CNAMEAsDomain: cnameAsDomain,
IncludingLnNodes: includingLnNodes,
}
dnsJSON, err := json.Marshal(dnsConfig)
if err != nil {
@@ -520,7 +554,20 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
if err != nil {
return err
}
return this.NotifyDNSUpdate(tx, clusterId)
err = this.NotifyDNSUpdate(tx, clusterId)
if err != nil {
return err
}
// 删除老的记录
if shouldRemoveOld {
err = dns.SharedDNSTaskDAO.CreateClusterRemoveTask(tx, clusterId, oldDNSDomainId, oldCluster.DnsName)
if err != nil {
return err
}
}
return nil
}
// FindClusterAdminId 查找集群所属管理员
@@ -920,7 +967,7 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
cluster, err := this.Query(tx).
Pk(clusterId).
State(NodeClusterStateEnabled).
Result("id", "timeZone", "nodeMaxThreads", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "uam", "isOn", "ddosProtection").
Result("id", "timeZone", "nodeMaxThreads", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "uam", "isOn", "ddosProtection", "clock", "globalServerConfig", "autoInstallNftables").
Find()
if err != nil || cluster == nil {
return nil, err
@@ -1062,7 +1109,7 @@ func (this *NodeClusterDAO) FindClusterDDoSProtection(tx *dbs.Tx, clusterId int6
return one.(*NodeCluster).DecodeDDoSProtection(), nil
}
// UpdateClusterDDoSProtection 设置集群的DDOS设置
// UpdateClusterDDoSProtection 设置集群的DDoS设置
func (this *NodeClusterDAO) UpdateClusterDDoSProtection(tx *dbs.Tx, clusterId int64, ddosProtection *ddosconfigs.ProtectionConfig) error {
if clusterId <= 0 {
return ErrNotFound
@@ -1088,6 +1135,49 @@ func (this *NodeClusterDAO) UpdateClusterDDoSProtection(tx *dbs.Tx, clusterId in
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeDDosProtectionChanged)
}
// FindClusterGlobalServerConfig 查询全局服务配置
func (this *NodeClusterDAO) FindClusterGlobalServerConfig(tx *dbs.Tx, clusterId int64) (*serverconfigs.GlobalServerConfig, error) {
configJSON, err := this.Query(tx).
Pk(clusterId).
Result("globalServerConfig").
FindJSONCol()
if err != nil {
return nil, err
}
var config = serverconfigs.DefaultGlobalServerConfig()
if IsNull(configJSON) {
return config, nil
}
err = json.Unmarshal(configJSON, config)
if err != nil {
return nil, err
}
return config, nil
}
// UpdateClusterGlobalServerConfig 修改全局服务配置
func (this *NodeClusterDAO) UpdateClusterGlobalServerConfig(tx *dbs.Tx, clusterId int64, config *serverconfigs.GlobalServerConfig) error {
if config == nil {
config = serverconfigs.DefaultGlobalServerConfig()
}
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
err = this.Query(tx).
Pk(clusterId).
Set("globalServerConfig", configJSON).
UpdateQuickly()
if err != nil {
return err
}
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeGlobalServerConfigChanged)
}
// NotifyUpdate 通知更新
func (this *NodeClusterDAO) NotifyUpdate(tx *dbs.Tx, clusterId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeConfigChanged)

View File

@@ -2,4 +2,16 @@ package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestNodeClusterDAO_DisableNodeCluster(t *testing.T) {
dbs.NotifyReady()
err := SharedNodeClusterDAO.DisableNodeCluster(nil, 46)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -30,45 +30,53 @@ type NodeCluster struct {
SystemServices dbs.JSON `field:"systemServices"` // 系统服务设置
TimeZone string `field:"timeZone"` // 时区
NodeMaxThreads uint32 `field:"nodeMaxThreads"` // 节点最大线程数
DdosProtection dbs.JSON `field:"ddosProtection"` // DDOS端口
DdosProtection dbs.JSON `field:"ddosProtection"` // DDoS防护设置
AutoOpenPorts uint8 `field:"autoOpenPorts"` // 是否自动尝试开放端口
IsPinned bool `field:"isPinned"` // 是否置顶
Webp dbs.JSON `field:"webp"` // WebP设置
Uam dbs.JSON `field:"uam"` // UAM设置
Clock dbs.JSON `field:"clock"` // 时钟配置
GlobalServerConfig dbs.JSON `field:"globalServerConfig"` // 全局服务配置
AutoRemoteStart bool `field:"autoRemoteStart"` // 自动远程启动
AutoInstallNftables bool `field:"autoInstallNftables"` // 自动安装nftables
}
type NodeClusterOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
IsOn interface{} // 是否启用
Name interface{} // 名称
UseAllAPINodes interface{} // 是否使用所有API节点
ApiNodes interface{} // 使用的API节点
InstallDir interface{} // 安装目录
Order interface{} // 排序
CreatedAt interface{} // 创建时间
GrantId interface{} // 默认认证方式
State interface{} // 状态
AutoRegister interface{} // 是否开启自动注册
UniqueId interface{} // 唯一ID
Secret interface{} // 密钥
HealthCheck interface{} // 健康检查
DnsName interface{} // DNS名称
DnsDomainId interface{} // 域名ID
Dns interface{} // DNS配置
Toa interface{} // TOA配置
CachePolicyId interface{} // 缓存策略ID
HttpFirewallPolicyId interface{} // WAF策略ID
AccessLog interface{} // 访问日志设置
SystemServices interface{} // 系统服务设置
TimeZone interface{} // 时区
NodeMaxThreads interface{} // 节点最大线程数
DdosProtection interface{} // DDOS端口
AutoOpenPorts interface{} // 是否自动尝试开放端口
IsPinned interface{} // 是否置顶
Webp interface{} // WebP设置
Uam interface{} // UAM设置
Id any // ID
AdminId any // 管理员ID
UserId any // 用户ID
IsOn any // 是否启用
Name any // 名称
UseAllAPINodes any // 是否使用所有API节点
ApiNodes any // 使用的API节点
InstallDir any // 安装目录
Order any // 排序
CreatedAt any // 创建时间
GrantId any // 默认认证方式
State any // 状态
AutoRegister any // 是否开启自动注册
UniqueId any // 唯一ID
Secret any // 密钥
HealthCheck any // 健康检查
DnsName any // DNS名称
DnsDomainId any // 域名ID
Dns any // DNS配置
Toa any // TOA配置
CachePolicyId any // 缓存策略ID
HttpFirewallPolicyId any // WAF策略ID
AccessLog any // 访问日志设置
SystemServices any // 系统服务设置
TimeZone any // 时区
NodeMaxThreads any // 节点最大线程数
DdosProtection any // DDoS防护设置
AutoOpenPorts any // 是否自动尝试开放端口
IsPinned any // 是否置顶
Webp any // WebP设置
Uam any // UAM设置
Clock any // 时钟配置
GlobalServerConfig any // 全局服务配置
AutoRemoteStart any // 自动远程启动
AutoInstallNftables any // 自动安装nftables
}
func NewNodeClusterOperator() *NodeClusterOperator {

View File

@@ -2,7 +2,10 @@ package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
)
@@ -11,13 +14,15 @@ func (this *NodeCluster) DecodeDNSConfig() (*dnsconfigs.ClusterDNSConfig, error)
if len(this.Dns) == 0 {
// 一定要返回一个默认的值防止产生nil
return &dnsconfigs.ClusterDNSConfig{
NodesAutoSync: false,
ServersAutoSync: false,
CNameAsDomain: true,
NodesAutoSync: false,
ServersAutoSync: false,
CNAMEAsDomain: true,
IncludingLnNodes: true,
}, nil
}
var dnsConfig = &dnsconfigs.ClusterDNSConfig{
CNameAsDomain: true,
CNAMEAsDomain: true,
IncludingLnNodes: true,
}
err := json.Unmarshal(this.Dns, &dnsConfig)
if err != nil {
@@ -48,3 +53,27 @@ func (this *NodeCluster) HasDDoSProtection() bool {
}
return false
}
// DecodeClock 解析时钟配置
func (this *NodeCluster) DecodeClock() *nodeconfigs.ClockConfig {
var clock = nodeconfigs.DefaultClockConfig()
if IsNotNull(this.Clock) {
err := json.Unmarshal(this.Clock, clock)
if err != nil {
remotelogs.Error("NodeCluster.DecodeClock()", err.Error())
}
}
return clock
}
// DecodeGlobalServerConfig 解析全局服务配置
func (this *NodeCluster) DecodeGlobalServerConfig() *serverconfigs.GlobalServerConfig {
var config = serverconfigs.DefaultGlobalServerConfig()
if IsNotNull(this.GlobalServerConfig) {
err := json.Unmarshal(this.GlobalServerConfig, config)
if err != nil {
remotelogs.Error("NodeCluster.DecodeGlobalServerConfig()", err.Error())
}
}
return config
}

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