重构提取逻辑,新增 area 表
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
- 日志记录
|
||||||
|
- 后台展示 mac, ip:port,实际地区
|
||||||
|
|
||||||
上传文件平铺到 uploads,不分子文件夹
|
上传文件平铺到 uploads,不分子文件夹
|
||||||
|
|
||||||
错误提示增强,展示整链路信息
|
错误提示增强,展示整链路信息
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func main() {
|
|||||||
m.Admin{},
|
m.Admin{},
|
||||||
m.AdminRole{},
|
m.AdminRole{},
|
||||||
m.Announcement{},
|
m.Announcement{},
|
||||||
|
m.Area{},
|
||||||
m.Article{},
|
m.Article{},
|
||||||
m.ArticleGroup{},
|
m.ArticleGroup{},
|
||||||
m.Bill{},
|
m.Bill{},
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ services:
|
|||||||
-api test:test@:9700
|
-api test:test@:9700
|
||||||
ports:
|
ports:
|
||||||
- "9700:9700"
|
- "9700:9700"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
258
scripts/sql/fill-area.sql
Normal file
258
scripts/sql/fill-area.sql
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
insert into area
|
||||||
|
(name, level)
|
||||||
|
values
|
||||||
|
('上海',1),
|
||||||
|
('云南',1),
|
||||||
|
('内蒙古',1),
|
||||||
|
('北京',1),
|
||||||
|
('吉林',1),
|
||||||
|
('四川',1),
|
||||||
|
('天津',1),
|
||||||
|
('宁夏',1),
|
||||||
|
('安徽',1),
|
||||||
|
('山东',1),
|
||||||
|
('山西',1),
|
||||||
|
('广东',1),
|
||||||
|
('广西',1),
|
||||||
|
('新疆',1),
|
||||||
|
('江苏',1),
|
||||||
|
('江西',1),
|
||||||
|
('河北',1),
|
||||||
|
('河南',1),
|
||||||
|
('浙江',1),
|
||||||
|
('海南',1),
|
||||||
|
('湖北',1),
|
||||||
|
('湖南',1),
|
||||||
|
('甘肃',1),
|
||||||
|
('福建',1),
|
||||||
|
('贵州',1),
|
||||||
|
('辽宁',1),
|
||||||
|
('重庆',1),
|
||||||
|
('陕西',1),
|
||||||
|
('黑龙江',1)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
insert into area
|
||||||
|
(name, level, parent_id)
|
||||||
|
values
|
||||||
|
('上海', 2, (select id from area where name = '上海')),
|
||||||
|
('昆明', 2, (select id from area where name = '云南')),
|
||||||
|
('包头', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('呼伦贝尔', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('呼和浩特', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('赤峰', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('通辽', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('鄂尔多斯', 2, (select id from area where name = '内蒙古')),
|
||||||
|
('北京', 2, (select id from area where name = '北京')),
|
||||||
|
('四平', 2, (select id from area where name = '吉林')),
|
||||||
|
('延边朝鲜族自治州', 2, (select id from area where name = '吉林')),
|
||||||
|
('松原', 2, (select id from area where name = '吉林')),
|
||||||
|
('白山', 2, (select id from area where name = '吉林')),
|
||||||
|
('通化', 2, (select id from area where name = '吉林')),
|
||||||
|
('长春', 2, (select id from area where name = '吉林')),
|
||||||
|
('乐山', 2, (select id from area where name = '四川')),
|
||||||
|
('内江', 2, (select id from area where name = '四川')),
|
||||||
|
('南充', 2, (select id from area where name = '四川')),
|
||||||
|
('宜宾', 2, (select id from area where name = '四川')),
|
||||||
|
('广元', 2, (select id from area where name = '四川')),
|
||||||
|
('德阳', 2, (select id from area where name = '四川')),
|
||||||
|
('成都', 2, (select id from area where name = '四川')),
|
||||||
|
('攀枝花', 2, (select id from area where name = '四川')),
|
||||||
|
('泸州', 2, (select id from area where name = '四川')),
|
||||||
|
('绵阳', 2, (select id from area where name = '四川')),
|
||||||
|
('自贡', 2, (select id from area where name = '四川')),
|
||||||
|
('达州', 2, (select id from area where name = '四川')),
|
||||||
|
('天津', 2, (select id from area where name = '天津')),
|
||||||
|
('银川', 2, (select id from area where name = '宁夏')),
|
||||||
|
('亳州', 2, (select id from area where name = '安徽')),
|
||||||
|
('六安', 2, (select id from area where name = '安徽')),
|
||||||
|
('合肥', 2, (select id from area where name = '安徽')),
|
||||||
|
('安庆', 2, (select id from area where name = '安徽')),
|
||||||
|
('宣城', 2, (select id from area where name = '安徽')),
|
||||||
|
('宿州', 2, (select id from area where name = '安徽')),
|
||||||
|
('池州', 2, (select id from area where name = '安徽')),
|
||||||
|
('淮北', 2, (select id from area where name = '安徽')),
|
||||||
|
('淮南', 2, (select id from area where name = '安徽')),
|
||||||
|
('滁州', 2, (select id from area where name = '安徽')),
|
||||||
|
('芜湖', 2, (select id from area where name = '安徽')),
|
||||||
|
('蚌埠', 2, (select id from area where name = '安徽')),
|
||||||
|
('铜陵', 2, (select id from area where name = '安徽')),
|
||||||
|
('阜阳', 2, (select id from area where name = '安徽')),
|
||||||
|
('马鞍山', 2, (select id from area where name = '安徽')),
|
||||||
|
('黄山', 2, (select id from area where name = '安徽')),
|
||||||
|
('东营', 2, (select id from area where name = '山东')),
|
||||||
|
('临沂', 2, (select id from area where name = '山东')),
|
||||||
|
('威海', 2, (select id from area where name = '山东')),
|
||||||
|
('德州', 2, (select id from area where name = '山东')),
|
||||||
|
('日照', 2, (select id from area where name = '山东')),
|
||||||
|
('枣庄', 2, (select id from area where name = '山东')),
|
||||||
|
('泰安', 2, (select id from area where name = '山东')),
|
||||||
|
('济南', 2, (select id from area where name = '山东')),
|
||||||
|
('济宁', 2, (select id from area where name = '山东')),
|
||||||
|
('淄博', 2, (select id from area where name = '山东')),
|
||||||
|
('滨州', 2, (select id from area where name = '山东')),
|
||||||
|
('潍坊', 2, (select id from area where name = '山东')),
|
||||||
|
('烟台', 2, (select id from area where name = '山东')),
|
||||||
|
('聊城', 2, (select id from area where name = '山东')),
|
||||||
|
('菏泽', 2, (select id from area where name = '山东')),
|
||||||
|
('青岛', 2, (select id from area where name = '山东')),
|
||||||
|
('临汾', 2, (select id from area where name = '山西')),
|
||||||
|
('吕梁', 2, (select id from area where name = '山西')),
|
||||||
|
('大同', 2, (select id from area where name = '山西')),
|
||||||
|
('太原', 2, (select id from area where name = '山西')),
|
||||||
|
('忻州', 2, (select id from area where name = '山西')),
|
||||||
|
('晋城', 2, (select id from area where name = '山西')),
|
||||||
|
('朔州', 2, (select id from area where name = '山西')),
|
||||||
|
('运城', 2, (select id from area where name = '山西')),
|
||||||
|
('长治', 2, (select id from area where name = '山西')),
|
||||||
|
('阳泉', 2, (select id from area where name = '山西')),
|
||||||
|
('东莞', 2, (select id from area where name = '广东')),
|
||||||
|
('中山', 2, (select id from area where name = '广东')),
|
||||||
|
('云浮', 2, (select id from area where name = '广东')),
|
||||||
|
('佛山', 2, (select id from area where name = '广东')),
|
||||||
|
('广州', 2, (select id from area where name = '广东')),
|
||||||
|
('惠州', 2, (select id from area where name = '广东')),
|
||||||
|
('揭阳', 2, (select id from area where name = '广东')),
|
||||||
|
('梅州', 2, (select id from area where name = '广东')),
|
||||||
|
('汕头', 2, (select id from area where name = '广东')),
|
||||||
|
('汕尾', 2, (select id from area where name = '广东')),
|
||||||
|
('江门', 2, (select id from area where name = '广东')),
|
||||||
|
('河源', 2, (select id from area where name = '广东')),
|
||||||
|
('深圳', 2, (select id from area where name = '广东')),
|
||||||
|
('清远', 2, (select id from area where name = '广东')),
|
||||||
|
('湛江', 2, (select id from area where name = '广东')),
|
||||||
|
('潮州', 2, (select id from area where name = '广东')),
|
||||||
|
('珠海', 2, (select id from area where name = '广东')),
|
||||||
|
('肇庆', 2, (select id from area where name = '广东')),
|
||||||
|
('茂名', 2, (select id from area where name = '广东')),
|
||||||
|
('阳江', 2, (select id from area where name = '广东')),
|
||||||
|
('韶关', 2, (select id from area where name = '广东')),
|
||||||
|
('北海', 2, (select id from area where name = '广西')),
|
||||||
|
('南宁', 2, (select id from area where name = '广西')),
|
||||||
|
('柳州', 2, (select id from area where name = '广西')),
|
||||||
|
('桂林', 2, (select id from area where name = '广西')),
|
||||||
|
('玉林', 2, (select id from area where name = '广西')),
|
||||||
|
('贵港', 2, (select id from area where name = '广西')),
|
||||||
|
('钦州', 2, (select id from area where name = '广西')),
|
||||||
|
('乌鲁木齐', 2, (select id from area where name = '新疆')),
|
||||||
|
('南京', 2, (select id from area where name = '江苏')),
|
||||||
|
('南通', 2, (select id from area where name = '江苏')),
|
||||||
|
('宿迁', 2, (select id from area where name = '江苏')),
|
||||||
|
('常州', 2, (select id from area where name = '江苏')),
|
||||||
|
('徐州', 2, (select id from area where name = '江苏')),
|
||||||
|
('扬州', 2, (select id from area where name = '江苏')),
|
||||||
|
('无锡', 2, (select id from area where name = '江苏')),
|
||||||
|
('泰州', 2, (select id from area where name = '江苏')),
|
||||||
|
('淮安', 2, (select id from area where name = '江苏')),
|
||||||
|
('盐城', 2, (select id from area where name = '江苏')),
|
||||||
|
('苏州', 2, (select id from area where name = '江苏')),
|
||||||
|
('连云港', 2, (select id from area where name = '江苏')),
|
||||||
|
('镇江', 2, (select id from area where name = '江苏')),
|
||||||
|
('上饶', 2, (select id from area where name = '江西')),
|
||||||
|
('九江', 2, (select id from area where name = '江西')),
|
||||||
|
('南昌', 2, (select id from area where name = '江西')),
|
||||||
|
('吉安', 2, (select id from area where name = '江西')),
|
||||||
|
('宜春', 2, (select id from area where name = '江西')),
|
||||||
|
('抚州', 2, (select id from area where name = '江西')),
|
||||||
|
('新余', 2, (select id from area where name = '江西')),
|
||||||
|
('景德镇', 2, (select id from area where name = '江西')),
|
||||||
|
('萍乡', 2, (select id from area where name = '江西')),
|
||||||
|
('赣州', 2, (select id from area where name = '江西')),
|
||||||
|
('鹰潭', 2, (select id from area where name = '江西')),
|
||||||
|
('保定', 2, (select id from area where name = '河北')),
|
||||||
|
('唐山', 2, (select id from area where name = '河北')),
|
||||||
|
('廊坊', 2, (select id from area where name = '河北')),
|
||||||
|
('张家口', 2, (select id from area where name = '河北')),
|
||||||
|
('承德', 2, (select id from area where name = '河北')),
|
||||||
|
('沧州', 2, (select id from area where name = '河北')),
|
||||||
|
('石家庄', 2, (select id from area where name = '河北')),
|
||||||
|
('秦皇岛', 2, (select id from area where name = '河北')),
|
||||||
|
('衡水', 2, (select id from area where name = '河北')),
|
||||||
|
('邢台', 2, (select id from area where name = '河北')),
|
||||||
|
('邯郸', 2, (select id from area where name = '河北')),
|
||||||
|
('信阳', 2, (select id from area where name = '河南')),
|
||||||
|
('南阳', 2, (select id from area where name = '河南')),
|
||||||
|
('周口', 2, (select id from area where name = '河南')),
|
||||||
|
('商丘', 2, (select id from area where name = '河南')),
|
||||||
|
('安阳', 2, (select id from area where name = '河南')),
|
||||||
|
('开封', 2, (select id from area where name = '河南')),
|
||||||
|
('新乡', 2, (select id from area where name = '河南')),
|
||||||
|
('洛阳', 2, (select id from area where name = '河南')),
|
||||||
|
('漯河', 2, (select id from area where name = '河南')),
|
||||||
|
('焦作', 2, (select id from area where name = '河南')),
|
||||||
|
('许昌', 2, (select id from area where name = '河南')),
|
||||||
|
('郑州', 2, (select id from area where name = '河南')),
|
||||||
|
('驻马店', 2, (select id from area where name = '河南')),
|
||||||
|
('鹤壁', 2, (select id from area where name = '河南')),
|
||||||
|
('丽水', 2, (select id from area where name = '浙江')),
|
||||||
|
('台州', 2, (select id from area where name = '浙江')),
|
||||||
|
('嘉兴', 2, (select id from area where name = '浙江')),
|
||||||
|
('宁波', 2, (select id from area where name = '浙江')),
|
||||||
|
('杭州', 2, (select id from area where name = '浙江')),
|
||||||
|
('温州', 2, (select id from area where name = '浙江')),
|
||||||
|
('湖州', 2, (select id from area where name = '浙江')),
|
||||||
|
('绍兴', 2, (select id from area where name = '浙江')),
|
||||||
|
('舟山', 2, (select id from area where name = '浙江')),
|
||||||
|
('衢州', 2, (select id from area where name = '浙江')),
|
||||||
|
('金华', 2, (select id from area where name = '浙江')),
|
||||||
|
('三亚', 2, (select id from area where name = '海南')),
|
||||||
|
('文昌', 2, (select id from area where name = '海南')),
|
||||||
|
('海口', 2, (select id from area where name = '海南')),
|
||||||
|
('咸宁', 2, (select id from area where name = '湖北')),
|
||||||
|
('孝感', 2, (select id from area where name = '湖北')),
|
||||||
|
('宜昌', 2, (select id from area where name = '湖北')),
|
||||||
|
('武汉', 2, (select id from area where name = '湖北')),
|
||||||
|
('荆州', 2, (select id from area where name = '湖北')),
|
||||||
|
('荆门', 2, (select id from area where name = '湖北')),
|
||||||
|
('襄阳', 2, (select id from area where name = '湖北')),
|
||||||
|
('黄冈', 2, (select id from area where name = '湖北')),
|
||||||
|
('黄石', 2, (select id from area where name = '湖北')),
|
||||||
|
('岳阳', 2, (select id from area where name = '湖南')),
|
||||||
|
('株洲', 2, (select id from area where name = '湖南')),
|
||||||
|
('湘潭', 2, (select id from area where name = '湖南')),
|
||||||
|
('衡阳', 2, (select id from area where name = '湖南')),
|
||||||
|
('邵阳', 2, (select id from area where name = '湖南')),
|
||||||
|
('郴州', 2, (select id from area where name = '湖南')),
|
||||||
|
('长沙', 2, (select id from area where name = '湖南')),
|
||||||
|
('兰州', 2, (select id from area where name = '甘肃')),
|
||||||
|
('三明', 2, (select id from area where name = '福建')),
|
||||||
|
('南平', 2, (select id from area where name = '福建')),
|
||||||
|
('厦门', 2, (select id from area where name = '福建')),
|
||||||
|
('宁德', 2, (select id from area where name = '福建')),
|
||||||
|
('泉州', 2, (select id from area where name = '福建')),
|
||||||
|
('福州', 2, (select id from area where name = '福建')),
|
||||||
|
('莆田', 2, (select id from area where name = '福建')),
|
||||||
|
('龙岩', 2, (select id from area where name = '福建')),
|
||||||
|
('六盘水', 2, (select id from area where name = '贵州')),
|
||||||
|
('贵阳', 2, (select id from area where name = '贵州')),
|
||||||
|
('遵义', 2, (select id from area where name = '贵州')),
|
||||||
|
('铜仁', 2, (select id from area where name = '贵州')),
|
||||||
|
('黔东南苗族侗族自治州', 2, (select id from area where name = '贵州')),
|
||||||
|
('大连', 2, (select id from area where name = '辽宁')),
|
||||||
|
('抚顺', 2, (select id from area where name = '辽宁')),
|
||||||
|
('朝阳', 2, (select id from area where name = '辽宁')),
|
||||||
|
('沈阳', 2, (select id from area where name = '辽宁')),
|
||||||
|
('盘锦', 2, (select id from area where name = '辽宁')),
|
||||||
|
('营口', 2, (select id from area where name = '辽宁')),
|
||||||
|
('葫芦岛', 2, (select id from area where name = '辽宁')),
|
||||||
|
('铁岭', 2, (select id from area where name = '辽宁')),
|
||||||
|
('鞍山', 2, (select id from area where name = '辽宁')),
|
||||||
|
('重庆', 2, (select id from area where name = '重庆')),
|
||||||
|
('咸阳', 2, (select id from area where name = '陕西')),
|
||||||
|
('宝鸡', 2, (select id from area where name = '陕西')),
|
||||||
|
('渭南', 2, (select id from area where name = '陕西')),
|
||||||
|
('西安', 2, (select id from area where name = '陕西')),
|
||||||
|
('铜川', 2, (select id from area where name = '陕西')),
|
||||||
|
('七台河', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('伊春', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('佳木斯', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('双鸭山', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('哈尔滨', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('大庆', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('牡丹江', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('绥化', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('鸡西', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('鹤岗', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('黑河', 2, (select id from area where name = '黑龙江')),
|
||||||
|
('齐齐哈尔', 2, (select id from area where name = '黑龙江'))
|
||||||
6601
scripts/sql/fill-edge.sql
Normal file
6601
scripts/sql/fill-edge.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -634,6 +634,31 @@ comment on column proxy.created_at is '创建时间';
|
|||||||
comment on column proxy.updated_at is '更新时间';
|
comment on column proxy.updated_at is '更新时间';
|
||||||
comment on column proxy.deleted_at is '删除时间';
|
comment on column proxy.deleted_at is '删除时间';
|
||||||
|
|
||||||
|
-- area
|
||||||
|
drop table if exists area cascade;
|
||||||
|
create table area (
|
||||||
|
id int generated by default as identity primary key,
|
||||||
|
name text not null,
|
||||||
|
level int not null,
|
||||||
|
parent_id int,
|
||||||
|
created_at timestamptz default current_timestamp,
|
||||||
|
updated_at timestamptz default current_timestamp,
|
||||||
|
deleted_at timestamptz
|
||||||
|
);
|
||||||
|
create index idx_area_level on area (level) where deleted_at is null;
|
||||||
|
create index idx_area_parent_id on area (parent_id) where deleted_at is null;
|
||||||
|
create index idx_area_created_at on area (created_at) where deleted_at is null;
|
||||||
|
|
||||||
|
-- area表字段注释
|
||||||
|
comment on table area is '地区表';
|
||||||
|
comment on column area.id is '地区ID';
|
||||||
|
comment on column area.name is '地区名称';
|
||||||
|
comment on column area.level is '地区层级:1-省,2-市';
|
||||||
|
comment on column area.parent_id is '父级地区ID';
|
||||||
|
comment on column area.created_at is '创建时间';
|
||||||
|
comment on column area.updated_at is '更新时间';
|
||||||
|
comment on column area.deleted_at is '删除时间';
|
||||||
|
|
||||||
-- edge
|
-- edge
|
||||||
drop table if exists edge cascade;
|
drop table if exists edge cascade;
|
||||||
create table edge (
|
create table edge (
|
||||||
@@ -644,8 +669,7 @@ create table edge (
|
|||||||
ip inet not null,
|
ip inet not null,
|
||||||
port int,
|
port int,
|
||||||
isp int not null,
|
isp int not null,
|
||||||
prov text not null,
|
area_id int not null,
|
||||||
city text not null,
|
|
||||||
status int not null default 0,
|
status int not null default 0,
|
||||||
rtt int default 0,
|
rtt int default 0,
|
||||||
loss int default 0,
|
loss int default 0,
|
||||||
@@ -655,8 +679,7 @@ create table edge (
|
|||||||
);
|
);
|
||||||
create unique index udx_edge_mac on edge (mac) where deleted_at is null;
|
create unique index udx_edge_mac on edge (mac) where deleted_at is null;
|
||||||
create index idx_edge_isp on edge (isp) where deleted_at is null;
|
create index idx_edge_isp on edge (isp) where deleted_at is null;
|
||||||
create index idx_edge_prov on edge (prov) where deleted_at is null;
|
create index idx_edge_area_id on edge (area_id) where deleted_at is null;
|
||||||
create index idx_edge_city on edge (city) where deleted_at is null;
|
|
||||||
create index idx_edge_created_at on edge (created_at) where deleted_at is null;
|
create index idx_edge_created_at on edge (created_at) where deleted_at is null;
|
||||||
|
|
||||||
-- edge表字段注释
|
-- edge表字段注释
|
||||||
@@ -668,8 +691,7 @@ comment on column edge.mac is '节点 mac 地址或 GOST chain 名称';
|
|||||||
comment on column edge.ip is '节点地址或 GOST chain addr 的 IP';
|
comment on column edge.ip is '节点地址或 GOST chain addr 的 IP';
|
||||||
comment on column edge.port is 'GOST chain addr 的端口';
|
comment on column edge.port is 'GOST chain addr 的端口';
|
||||||
comment on column edge.isp is '运营商:1-电信,2-联通,3-移动';
|
comment on column edge.isp is '运营商:1-电信,2-联通,3-移动';
|
||||||
comment on column edge.prov is '省份';
|
comment on column edge.area_id is '城市地区ID';
|
||||||
comment on column edge.city is '城市';
|
|
||||||
comment on column edge.status is '节点状态:0-离线,1-正常';
|
comment on column edge.status is '节点状态:0-离线,1-正常';
|
||||||
comment on column edge.rtt is '最近平均延迟';
|
comment on column edge.rtt is '最近平均延迟';
|
||||||
comment on column edge.loss is '最近丢包率';
|
comment on column edge.loss is '最近丢包率';
|
||||||
@@ -1237,6 +1259,10 @@ alter table channel
|
|||||||
add constraint fk_channel_user_id foreign key (user_id) references "user" (id) on delete cascade;
|
add constraint fk_channel_user_id foreign key (user_id) references "user" (id) on delete cascade;
|
||||||
alter table channel
|
alter table channel
|
||||||
add constraint fk_channel_proxy_id foreign key (proxy_id) references proxy (id) on delete set null;
|
add constraint fk_channel_proxy_id foreign key (proxy_id) references proxy (id) on delete set null;
|
||||||
|
alter table area
|
||||||
|
add constraint fk_area_parent_id foreign key (parent_id) references area (id) on delete set null;
|
||||||
|
alter table edge
|
||||||
|
add constraint fk_edge_area_id foreign key (area_id) references area (id) on delete restrict;
|
||||||
alter table channel
|
alter table channel
|
||||||
add constraint fk_channel_edge_id foreign key (edge_id) references edge (id) on delete set null;
|
add constraint fk_channel_edge_id foreign key (edge_id) references edge (id) on delete set null;
|
||||||
alter table channel
|
alter table channel
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ func (c *gostClient) request(method string, path string, payload any) ([]byte, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
return nil, fmt.Errorf("%w: %s", ErrGostNotFound, string(body))
|
return nil, fmt.Errorf("%w: %s", ErrGostNotFound, string(body))
|
||||||
}
|
}
|
||||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
package globals
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGostClientCreateServiceUsesBasicAuthAndPathPrefix(t *testing.T) {
|
|
||||||
var (
|
|
||||||
gotPath string
|
|
||||||
gotAuth string
|
|
||||||
gotBody GostServiceConfig
|
|
||||||
)
|
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
gotPath = r.URL.Path
|
|
||||||
gotAuth = r.Header.Get("Authorization")
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
|
|
||||||
t.Fatalf("Decode failed: %v", err)
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client := NewGost(server.URL, 0, "/api", "user", "pass")
|
|
||||||
err := client.CreateService(&GostServiceConfig{
|
|
||||||
Name: "svc-1",
|
|
||||||
Addr: ":10000",
|
|
||||||
Handler: GostHandlerConfig{
|
|
||||||
Type: "auto",
|
|
||||||
Chain: "chain-a",
|
|
||||||
Auther: "auther-a",
|
|
||||||
},
|
|
||||||
Listener: GostListenerConfig{Type: "tcp"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateService returned error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotPath != "/api/config/services" {
|
|
||||||
t.Fatalf("unexpected path: %s", gotPath)
|
|
||||||
}
|
|
||||||
if gotAuth != "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")) {
|
|
||||||
t.Fatalf("unexpected auth header: %s", gotAuth)
|
|
||||||
}
|
|
||||||
if gotBody.Name != "svc-1" || gotBody.Handler.Type != "auto" || gotBody.Handler.Chain != "chain-a" {
|
|
||||||
t.Fatalf("unexpected request body: %+v", gotBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGostClientDeleteServiceTreats404AsIdempotent(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client := NewGost(server.URL, 0, "", "user", "pass")
|
|
||||||
if err := client.DeleteService("svc-1"); !IsGostNotFound(err) {
|
|
||||||
t.Fatalf("expected gost not found error, got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGostClientGetChainReadsTopLevelName(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "/config/chains/chain-a" {
|
|
||||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
||||||
}
|
|
||||||
_, _ = w.Write([]byte(`{"name":"chain-a"}`))
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client := NewGost(server.URL, 0, "", "user", "pass")
|
|
||||||
chain, err := client.GetChain("chain-a")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("GetChain returned error: %v", err)
|
|
||||||
}
|
|
||||||
if chain.Name != "chain-a" {
|
|
||||||
t.Fatalf("unexpected chain: %+v", chain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalizeGostPathPrefix(t *testing.T) {
|
|
||||||
if got := normalizeGostPathPrefix("api/"); got != "/api" {
|
|
||||||
t.Fatalf("unexpected prefix: %s", got)
|
|
||||||
}
|
|
||||||
if got := normalizeGostPathPrefix(""); got != "" {
|
|
||||||
t.Fatalf("unexpected empty prefix: %s", got)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(normalizeGostPathPrefix("/v1"), "/") {
|
|
||||||
t.Fatal("expected normalized prefix to start with slash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
web/handlers/area.go
Normal file
29
web/handlers/area.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"platform/web/auth"
|
||||||
|
s "platform/web/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListArea(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitOfficialClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := s.Area.ListAreas()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAreaRespItem struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
ParentID *int32 `json:"parent_id,omitempty"`
|
||||||
|
}
|
||||||
@@ -106,41 +106,28 @@ func CreateChannel(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
areaID, err := s.Area.FindIdByFilter(req.Prov, req.City)
|
||||||
var isp *m.EdgeISP
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filter := &s.EdgeFilter{AreaID: areaID}
|
||||||
if req.Isp != nil {
|
if req.Isp != nil {
|
||||||
isp = u.X(m.ToEdgeISP(*req.Isp))
|
filter.Isp = u.X(m.ToEdgeISP(*req.Isp))
|
||||||
}
|
}
|
||||||
result, err := s.Channel.CreateChannels(
|
result, err := s.Channel.CreateChannels(
|
||||||
ip, no,
|
ip,
|
||||||
|
no,
|
||||||
req.AuthType == s.ChannelAuthTypeIp,
|
req.AuthType == s.ChannelAuthTypeIp,
|
||||||
req.AuthType == s.ChannelAuthTypePass,
|
req.AuthType == s.ChannelAuthTypePass,
|
||||||
req.Count,
|
req.Count,
|
||||||
&s.EdgeFilter{
|
filter,
|
||||||
Isp: isp,
|
|
||||||
Prov: req.Prov,
|
|
||||||
City: req.City,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回结果
|
// 返回结果
|
||||||
var resp = make([]*CreateChannelRespItem, len(result))
|
return c.JSON(buildCreateChannelResp(result, req.Protocol, req.AuthType))
|
||||||
for i, channel := range result {
|
|
||||||
resp[i] = &CreateChannelRespItem{
|
|
||||||
Proto: req.Protocol,
|
|
||||||
Host: channel.Host,
|
|
||||||
IP: channel.Proxy.IP.String(),
|
|
||||||
Port: channel.Port,
|
|
||||||
}
|
|
||||||
if req.AuthType == s.ChannelAuthTypePass {
|
|
||||||
resp[i].Username = channel.Username
|
|
||||||
resp[i].Password = channel.Password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.JSON(resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateChannelReq struct {
|
type CreateChannelReq struct {
|
||||||
@@ -169,9 +156,13 @@ func CreateChannelV2(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建通道
|
// 创建通道
|
||||||
var isp *m.EdgeISP
|
areaID, err := s.Area.FindIdByFilter(req.Prov, req.City)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filter := &s.EdgeFilter{AreaID: areaID}
|
||||||
if req.Isp != nil {
|
if req.Isp != nil {
|
||||||
isp = u.X(m.ToEdgeISP(*req.Isp))
|
filter.Isp = u.X(m.ToEdgeISP(*req.Isp))
|
||||||
}
|
}
|
||||||
result, err := s.Channel.CreateChannels(
|
result, err := s.Channel.CreateChannels(
|
||||||
ip,
|
ip,
|
||||||
@@ -179,31 +170,14 @@ func CreateChannelV2(c *fiber.Ctx) error {
|
|||||||
req.AuthType == s.ChannelAuthTypeIp,
|
req.AuthType == s.ChannelAuthTypeIp,
|
||||||
req.AuthType == s.ChannelAuthTypePass,
|
req.AuthType == s.ChannelAuthTypePass,
|
||||||
req.Count,
|
req.Count,
|
||||||
&s.EdgeFilter{
|
filter,
|
||||||
Isp: isp,
|
|
||||||
Prov: req.Prov,
|
|
||||||
City: req.City,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回结果
|
// 返回结果
|
||||||
var resp = make([]*CreateChannelRespItem, len(result))
|
return c.JSON(buildCreateChannelResp(result, req.Protocol, req.AuthType))
|
||||||
for i, channel := range result {
|
|
||||||
resp[i] = &CreateChannelRespItem{
|
|
||||||
Proto: req.Protocol,
|
|
||||||
Host: channel.Host,
|
|
||||||
IP: channel.Proxy.IP.String(),
|
|
||||||
Port: channel.Port,
|
|
||||||
}
|
|
||||||
if req.AuthType == s.ChannelAuthTypePass {
|
|
||||||
resp[i].Username = channel.Username
|
|
||||||
resp[i].Password = channel.Password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.JSON(resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateChannelReqV2 struct {
|
type CreateChannelReqV2 struct {
|
||||||
@@ -216,6 +190,63 @@ type CreateChannelReqV2 struct {
|
|||||||
Isp *int `json:"isp"`
|
Isp *int `json:"isp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateChannelV3 创建新通道 v3,使用 resource_no + area_id
|
||||||
|
func CreateChannelV3(c *fiber.Ctx) error {
|
||||||
|
req := new(CreateChannelReqV3)
|
||||||
|
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||||
|
return core.NewBizErr("解析参数失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := netip.ParseAddr(c.IP())
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取客户端地址失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &s.EdgeFilter{AreaID: req.AreaID}
|
||||||
|
if req.Isp != nil {
|
||||||
|
filter.Isp = u.X(m.ToEdgeISP(*req.Isp))
|
||||||
|
}
|
||||||
|
result, err := s.Channel.CreateChannels(
|
||||||
|
ip,
|
||||||
|
req.ResourceNo,
|
||||||
|
req.AuthType == s.ChannelAuthTypeIp,
|
||||||
|
req.AuthType == s.ChannelAuthTypePass,
|
||||||
|
req.Count,
|
||||||
|
filter,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(buildCreateChannelResp(result, req.Protocol, req.AuthType))
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateChannelReqV3 struct {
|
||||||
|
ResourceNo string `json:"resource_no" validate:"required"`
|
||||||
|
AuthType s.ChannelAuthType `json:"auth_type" validate:"required"`
|
||||||
|
Protocol int `json:"protocol" validate:"required"`
|
||||||
|
Count int `json:"count" validate:"required"`
|
||||||
|
AreaID *int32 `json:"area_id"`
|
||||||
|
Isp *int `json:"isp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCreateChannelResp(result []*m.Channel, protocol int, authType s.ChannelAuthType) []*CreateChannelRespItem {
|
||||||
|
resp := make([]*CreateChannelRespItem, len(result))
|
||||||
|
for i, channel := range result {
|
||||||
|
resp[i] = &CreateChannelRespItem{
|
||||||
|
Proto: protocol,
|
||||||
|
Host: channel.Host,
|
||||||
|
IP: channel.Proxy.IP.String(),
|
||||||
|
Port: channel.Port,
|
||||||
|
}
|
||||||
|
if authType == s.ChannelAuthTypePass {
|
||||||
|
resp[i].Username = channel.Username
|
||||||
|
resp[i].Password = channel.Password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
type CreateChannelRespItem struct {
|
type CreateChannelRespItem struct {
|
||||||
Proto int `json:"-"`
|
Proto int `json:"-"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import (
|
|||||||
"platform/web/core"
|
"platform/web/core"
|
||||||
g "platform/web/globals"
|
g "platform/web/globals"
|
||||||
s "platform/web/services"
|
s "platform/web/services"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// admin 路由
|
||||||
|
// ====================
|
||||||
|
|
||||||
func PageProxyByAdmin(c *fiber.Ctx) error {
|
func PageProxyByAdmin(c *fiber.Ctx) error {
|
||||||
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyRead)
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyRead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,6 +105,24 @@ func UpdateProxyStatus(c *fiber.Ctx) error {
|
|||||||
return c.JSON(nil)
|
return c.JSON(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SyncProxyPool(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req core.IdReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Proxy.SyncPool(req.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func RemoveProxy(c *fiber.Ctx) error {
|
func RemoveProxy(c *fiber.Ctx) error {
|
||||||
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,347 +140,3 @@ func RemoveProxy(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
return c.JSON(nil)
|
return c.JSON(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
// region 报告上线
|
|
||||||
func ProxyReportOnline(c *fiber.Ctx) (err error) {
|
|
||||||
return c.JSON(map[string]any{
|
|
||||||
"error": "接口暂不可用",
|
|
||||||
})
|
|
||||||
|
|
||||||
// // 检查接口权限
|
|
||||||
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 验证请求参数
|
|
||||||
// var req = new(ProxyReportOnlineReq)
|
|
||||||
// err = g.Validator.Validate(c, req)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 创建代理
|
|
||||||
// var ip = c.Context().RemoteIP()
|
|
||||||
|
|
||||||
// var secretBytes = make([]byte, 16)
|
|
||||||
// if _, err := rand.Read(secretBytes); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// var secret = base32.StdEncoding.
|
|
||||||
// WithPadding(base32.NoPadding).
|
|
||||||
// EncodeToString(secretBytes)
|
|
||||||
// slog.Debug("生成随机密钥", "ip", ip, "secret", secret)
|
|
||||||
|
|
||||||
// var proxy = &m.Proxy{
|
|
||||||
// Mac: req.Name,
|
|
||||||
// Version: int32(req.Version),
|
|
||||||
// Type: m.ProxyTypeSelfHosted,
|
|
||||||
// IP: ip,
|
|
||||||
// Secret: &secret,
|
|
||||||
// Status: 1,
|
|
||||||
// }
|
|
||||||
// err = q.Proxy.
|
|
||||||
// Clauses(clause.OnConflict{
|
|
||||||
// UpdateAll: true,
|
|
||||||
// Columns: []clause.Column{
|
|
||||||
// {Name: q.Proxy.Mac.ColumnName().String()},
|
|
||||||
// },
|
|
||||||
// }).
|
|
||||||
// Create(proxy)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 获取边缘节点信息
|
|
||||||
// data, err := q.Edge.Where(
|
|
||||||
// q.Edge.ProxyID.Eq(proxy.ID),
|
|
||||||
// ).Find()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// edges := make([]*ProxyEdge, len(data))
|
|
||||||
// for i, edge := range data {
|
|
||||||
// edges[i] = &ProxyEdge{
|
|
||||||
// Id: edge.ID,
|
|
||||||
// Port: edge.ProxyPort,
|
|
||||||
// Prov: &edge.Prov,
|
|
||||||
// City: &edge.City,
|
|
||||||
// Isp: u.P(edge2.ISP(edge.Isp).String()),
|
|
||||||
// Status: &edge.Status,
|
|
||||||
// Loss: edge.Loss,
|
|
||||||
// Rtt: edge.Rtt,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 获取许可配置
|
|
||||||
// channels, err := q.Channel.Where(
|
|
||||||
// q.Channel.ProxyID.Eq(proxy.ID),
|
|
||||||
// q.Channel.Expiration.Gt(orm.LocalDateTime(time.Now())),
|
|
||||||
// ).Find()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var permits = make([]*ProxyPermit, len(channels))
|
|
||||||
// for i, channel := range channels {
|
|
||||||
// if channel.EdgeID == nil {
|
|
||||||
// return core.NewBizErr(fmt.Sprintf("权限解析异常,通道缺少边缘节点ID %d", channel.ID))
|
|
||||||
// }
|
|
||||||
// permits[i] = &ProxyPermit{
|
|
||||||
// Id: *channel.EdgeID,
|
|
||||||
// Expire: time.Time(channel.Expiration),
|
|
||||||
// Whitelists: u.P(strings.Split(u.Z(channel.Whitelists), ",")),
|
|
||||||
// Username: channel.Username,
|
|
||||||
// Password: channel.Password,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// slog.Debug("注册转发服务", "ip", ip, "id", proxy.ID)
|
|
||||||
// return c.JSON(&ProxyReportOnlineResp{
|
|
||||||
// Id: proxy.ID,
|
|
||||||
// Secret: secret,
|
|
||||||
// Edges: edges,
|
|
||||||
// Permits: permits,
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyReportOnlineReq struct {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Version int `json:"version" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyReportOnlineResp struct {
|
|
||||||
Id int32 `json:"id"`
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
Permits []*ProxyPermit `json:"permits"`
|
|
||||||
Edges []*ProxyEdge `json:"edges"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// region 报告下线
|
|
||||||
func ProxyReportOffline(c *fiber.Ctx) (err error) {
|
|
||||||
return c.JSON(map[string]any{
|
|
||||||
"error": "接口暂不可用",
|
|
||||||
})
|
|
||||||
|
|
||||||
// // 检查接口权限
|
|
||||||
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 验证请求参数
|
|
||||||
// var req = new(ProxyReportOfflineReq)
|
|
||||||
// err = g.Validator.Validate(c, req)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 下线转发服务
|
|
||||||
// _, err = q.Proxy.
|
|
||||||
// Where(q.Proxy.ID.Eq(req.Id)).
|
|
||||||
// UpdateSimple(q.Proxy.Status.Value(0))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 下线所有相关的边缘节点
|
|
||||||
// _, err = q.Edge.
|
|
||||||
// Where(q.Edge.ProxyID.Eq(req.Id)).
|
|
||||||
// UpdateSimple(q.Edge.Status.Value(0))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyReportOfflineReq struct {
|
|
||||||
Id int32 `json:"id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// region 报告更新
|
|
||||||
func ProxyReportUpdate(c *fiber.Ctx) (err error) {
|
|
||||||
return c.JSON(map[string]any{
|
|
||||||
"error": "接口暂不可用",
|
|
||||||
})
|
|
||||||
|
|
||||||
// // 检查接口权限
|
|
||||||
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 验证请求参数
|
|
||||||
// var req = new(ProxyReportUpdateReq)
|
|
||||||
// err = g.Validator.Validate(c, req)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 更新节点信息
|
|
||||||
// var idsActive = make([]int32, 0, len(req.Edges))
|
|
||||||
// var idsInactive = make([]int32, 0, len(req.Edges))
|
|
||||||
// var idsIspUnknown = make([]int32, 0, len(req.Edges))
|
|
||||||
// var idsIspTelecom = make([]int32, 0, len(req.Edges))
|
|
||||||
// var idsIspUnicom = make([]int32, 0, len(req.Edges))
|
|
||||||
// var idsIspMobile = make([]int32, 0, len(req.Edges))
|
|
||||||
// var otherEdges = make([]*ProxyEdge, 0, len(req.Edges))
|
|
||||||
// for _, edge := range req.Edges {
|
|
||||||
|
|
||||||
// // 检查更新ISP
|
|
||||||
// if edge.Isp != nil {
|
|
||||||
// switch edge2.ISPFromStr(*edge.Isp) {
|
|
||||||
// case edge2.IspUnknown:
|
|
||||||
// idsIspUnknown = append(idsIspUnknown, edge.Id)
|
|
||||||
// case edge2.IspChinaTelecom:
|
|
||||||
// idsIspTelecom = append(idsIspTelecom, edge.Id)
|
|
||||||
// case edge2.IspChinaUnicom:
|
|
||||||
// idsIspUnicom = append(idsIspUnicom, edge.Id)
|
|
||||||
// case edge2.IspChinaMobile:
|
|
||||||
// idsIspMobile = append(idsIspMobile, edge.Id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 检查更新状态
|
|
||||||
// if edge.Status != nil {
|
|
||||||
// if *edge.Status == 1 {
|
|
||||||
// idsActive = append(idsActive, edge.Id)
|
|
||||||
// } else {
|
|
||||||
// idsInactive = append(idsInactive, edge.Id)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 无法分类更新
|
|
||||||
// if edge.Host != nil || edge.Port != nil || edge.Prov != nil || edge.City != nil {
|
|
||||||
// otherEdges = append(otherEdges, edge)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// slog.Debug("更新边缘节点信息",
|
|
||||||
// "active", len(idsActive),
|
|
||||||
// "inactive", len(idsInactive),
|
|
||||||
// "isp_unknown", len(idsIspUnknown),
|
|
||||||
// "isp_telecom", len(idsIspTelecom),
|
|
||||||
// "isp_unicom", len(idsIspUnicom),
|
|
||||||
// "isp_mobile", len(idsIspMobile),
|
|
||||||
// "other_edges", len(otherEdges),
|
|
||||||
// )
|
|
||||||
|
|
||||||
// err = q.Q.Transaction(func(q *q.Query) error {
|
|
||||||
// // 更新边缘节点状态
|
|
||||||
// if len(idsActive) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsActive...)).
|
|
||||||
// UpdateSimple(q.Edge.Status.Value(1))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(idsInactive) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsInactive...)).
|
|
||||||
// UpdateSimple(q.Edge.Status.Value(0))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 更新边缘节点ISP
|
|
||||||
// if len(idsIspUnknown) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsIspUnknown...)).
|
|
||||||
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspUnknown)))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(idsIspTelecom) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsIspTelecom...)).
|
|
||||||
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaTelecom)))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(idsIspUnicom) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsIspUnicom...)).
|
|
||||||
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaUnicom)))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(idsIspMobile) > 0 {
|
|
||||||
// _, err = q.Edge.Debug().
|
|
||||||
// Where(q.Edge.ID.In(idsIspMobile...)).
|
|
||||||
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaMobile)))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 更新其他边缘节点信息
|
|
||||||
// for _, edge := range otherEdges {
|
|
||||||
// do := q.Edge.Debug().Where(q.Edge.ID.Eq(edge.Id))
|
|
||||||
|
|
||||||
// var assigns = make([]field.AssignExpr, 0, 5)
|
|
||||||
// if edge.Host != nil {
|
|
||||||
// assigns = append(assigns, q.Edge.Host.Value(*edge.Host))
|
|
||||||
// }
|
|
||||||
// if edge.Port != nil {
|
|
||||||
// assigns = append(assigns, q.Edge.ProxyPort.Value(*edge.Port))
|
|
||||||
// }
|
|
||||||
// if edge.Prov != nil {
|
|
||||||
// assigns = append(assigns, q.Edge.Prov.Value(*edge.Prov))
|
|
||||||
// }
|
|
||||||
// if edge.City != nil {
|
|
||||||
// assigns = append(assigns, q.Edge.City.Value(*edge.City))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 更新边缘节点
|
|
||||||
// _, err := do.UpdateSimple(assigns...)
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("更新边缘节点 %d 失败: %w", edge.Id, err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// })
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyReportUpdateReq struct {
|
|
||||||
Id int32 `json:"id" validate:"required"`
|
|
||||||
Edges []*ProxyEdge `json:"edges" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyPermit struct {
|
|
||||||
Id int32 `json:"id"`
|
|
||||||
Expire time.Time `json:"expire"`
|
|
||||||
Whitelists *[]string `json:"whitelists"`
|
|
||||||
Username *string `json:"username"`
|
|
||||||
Password *string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyEdge struct {
|
|
||||||
Id int32 `json:"id"`
|
|
||||||
Host *string `json:"host,omitempty"` // 边缘节点地址
|
|
||||||
Port *int32 `json:"port,omitempty"` // 边缘节点代理端口
|
|
||||||
Prov *string `json:"prov,omitempty"`
|
|
||||||
City *string `json:"city,omitempty"`
|
|
||||||
Isp *string `json:"isp,omitempty"`
|
|
||||||
Status *int32 `json:"status,omitempty"`
|
|
||||||
Loss *int32 `json:"loss,omitempty"` // 丢包率
|
|
||||||
Rtt *int32 `json:"latency,omitempty"` // 延迟
|
|
||||||
}
|
|
||||||
|
|||||||
20
web/models/area.go
Normal file
20
web/models/area.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "platform/web/core"
|
||||||
|
|
||||||
|
// Area 地区表
|
||||||
|
type Area struct {
|
||||||
|
core.Model
|
||||||
|
Name string `json:"name" gorm:"column:name"` // 地区名称
|
||||||
|
Level AreaLevel `json:"level" gorm:"column:level"` // 地区层级:1-省,2-市
|
||||||
|
ParentID *int32 `json:"parent_id,omitempty" gorm:"column:parent_id"` // 父级地区ID
|
||||||
|
Parent *Area `json:"parent,omitempty" gorm:"foreignKey:ParentID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AreaLevel 地区层级枚举
|
||||||
|
type AreaLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AreaLevelProvince AreaLevel = 1 // 省
|
||||||
|
AreaLevelCity AreaLevel = 2 // 市
|
||||||
|
)
|
||||||
@@ -14,11 +14,11 @@ type Edge struct {
|
|||||||
IP orm.Inet `json:"ip" gorm:"column:ip;not null"` // 节点地址或 GOST chain addr 的 IP
|
IP orm.Inet `json:"ip" gorm:"column:ip;not null"` // 节点地址或 GOST chain addr 的 IP
|
||||||
Port *uint16 `json:"port,omitempty" gorm:"column:port"` // GOST chain addr 的端口
|
Port *uint16 `json:"port,omitempty" gorm:"column:port"` // GOST chain addr 的端口
|
||||||
ISP EdgeISP `json:"isp" gorm:"column:isp"` // 运营商:0-未知,1-电信,2-联通,3-移动
|
ISP EdgeISP `json:"isp" gorm:"column:isp"` // 运营商:0-未知,1-电信,2-联通,3-移动
|
||||||
Prov string `json:"prov" gorm:"column:prov"` // 省份
|
AreaID int32 `json:"area_id" gorm:"column:area_id"` // 城市地区ID
|
||||||
City string `json:"city" gorm:"column:city"` // 城市
|
|
||||||
Status EdgeStatus `json:"status" gorm:"column:status"` // 节点状态:0-离线,1-正常
|
Status EdgeStatus `json:"status" gorm:"column:status"` // 节点状态:0-离线,1-正常
|
||||||
RTT int32 `json:"rtt" gorm:"column:rtt"` // 最近平均延迟
|
RTT int32 `json:"rtt" gorm:"column:rtt"` // 最近平均延迟
|
||||||
Loss int32 `json:"loss" gorm:"column:loss"` // 最近丢包率
|
Loss int32 `json:"loss" gorm:"column:loss"` // 最近丢包率
|
||||||
|
Area *Area `json:"area,omitempty" gorm:"foreignKey:AreaID"` // 地区
|
||||||
}
|
}
|
||||||
|
|
||||||
// EdgeType 节点类型枚举
|
// EdgeType 节点类型枚举
|
||||||
|
|||||||
443
web/queries/area.gen.go
Normal file
443
web/queries/area.gen.go
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"platform/web/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newArea(db *gorm.DB, opts ...gen.DOOption) area {
|
||||||
|
_area := area{}
|
||||||
|
|
||||||
|
_area.areaDo.UseDB(db, opts...)
|
||||||
|
_area.areaDo.UseModel(&models.Area{})
|
||||||
|
|
||||||
|
tableName := _area.areaDo.TableName()
|
||||||
|
_area.ALL = field.NewAsterisk(tableName)
|
||||||
|
_area.ID = field.NewInt32(tableName, "id")
|
||||||
|
_area.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_area.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_area.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_area.Name = field.NewString(tableName, "name")
|
||||||
|
_area.Level = field.NewInt(tableName, "level")
|
||||||
|
_area.ParentID = field.NewInt32(tableName, "parent_id")
|
||||||
|
_area.Parent = areaBelongsToParent{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("Parent", "models.Area"),
|
||||||
|
Parent: struct {
|
||||||
|
field.RelationField
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Parent.Parent", "models.Area"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_area.fillFieldMap()
|
||||||
|
|
||||||
|
return _area
|
||||||
|
}
|
||||||
|
|
||||||
|
type area struct {
|
||||||
|
areaDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int32
|
||||||
|
CreatedAt field.Time
|
||||||
|
UpdatedAt field.Time
|
||||||
|
DeletedAt field.Field
|
||||||
|
Name field.String
|
||||||
|
Level field.Int
|
||||||
|
ParentID field.Int32
|
||||||
|
Parent areaBelongsToParent
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a area) Table(newTableName string) *area {
|
||||||
|
a.areaDo.UseTable(newTableName)
|
||||||
|
return a.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a area) As(alias string) *area {
|
||||||
|
a.areaDo.DO = *(a.areaDo.As(alias).(*gen.DO))
|
||||||
|
return a.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *area) updateTableName(table string) *area {
|
||||||
|
a.ALL = field.NewAsterisk(table)
|
||||||
|
a.ID = field.NewInt32(table, "id")
|
||||||
|
a.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
a.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
a.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
a.Name = field.NewString(table, "name")
|
||||||
|
a.Level = field.NewInt(table, "level")
|
||||||
|
a.ParentID = field.NewInt32(table, "parent_id")
|
||||||
|
|
||||||
|
a.fillFieldMap()
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *area) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := a.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *area) fillFieldMap() {
|
||||||
|
a.fieldMap = make(map[string]field.Expr, 8)
|
||||||
|
a.fieldMap["id"] = a.ID
|
||||||
|
a.fieldMap["created_at"] = a.CreatedAt
|
||||||
|
a.fieldMap["updated_at"] = a.UpdatedAt
|
||||||
|
a.fieldMap["deleted_at"] = a.DeletedAt
|
||||||
|
a.fieldMap["name"] = a.Name
|
||||||
|
a.fieldMap["level"] = a.Level
|
||||||
|
a.fieldMap["parent_id"] = a.ParentID
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a area) clone(db *gorm.DB) area {
|
||||||
|
a.areaDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
a.Parent.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
a.Parent.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a area) replaceDB(db *gorm.DB) area {
|
||||||
|
a.areaDo.ReplaceDB(db)
|
||||||
|
a.Parent.db = db.Session(&gorm.Session{})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
type areaBelongsToParent struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParent) Where(conds ...field.Expr) *areaBelongsToParent {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParent) WithContext(ctx context.Context) *areaBelongsToParent {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParent) Session(session *gorm.Session) *areaBelongsToParent {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParent) Model(m *models.Area) *areaBelongsToParentTx {
|
||||||
|
return &areaBelongsToParentTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParent) Unscoped() *areaBelongsToParent {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type areaBelongsToParentTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Find() (result *models.Area, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Append(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Replace(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Delete(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaBelongsToParentTx) Unscoped() *areaBelongsToParentTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type areaDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (a areaDo) Debug() *areaDo {
|
||||||
|
return a.withDO(a.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) WithContext(ctx context.Context) *areaDo {
|
||||||
|
return a.withDO(a.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) ReadDB() *areaDo {
|
||||||
|
return a.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) WriteDB() *areaDo {
|
||||||
|
return a.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Session(config *gorm.Session) *areaDo {
|
||||||
|
return a.withDO(a.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Clauses(conds ...clause.Expression) *areaDo {
|
||||||
|
return a.withDO(a.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Returning(value interface{}, columns ...string) *areaDo {
|
||||||
|
return a.withDO(a.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Not(conds ...gen.Condition) *areaDo {
|
||||||
|
return a.withDO(a.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Or(conds ...gen.Condition) *areaDo {
|
||||||
|
return a.withDO(a.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Select(conds ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Where(conds ...gen.Condition) *areaDo {
|
||||||
|
return a.withDO(a.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Order(conds ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Distinct(cols ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Omit(cols ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Join(table schema.Tabler, on ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) LeftJoin(table schema.Tabler, on ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) RightJoin(table schema.Tabler, on ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Group(cols ...field.Expr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Having(conds ...gen.Condition) *areaDo {
|
||||||
|
return a.withDO(a.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Limit(limit int) *areaDo {
|
||||||
|
return a.withDO(a.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Offset(offset int) *areaDo {
|
||||||
|
return a.withDO(a.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *areaDo {
|
||||||
|
return a.withDO(a.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Unscoped() *areaDo {
|
||||||
|
return a.withDO(a.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Create(values ...*models.Area) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) CreateInBatches(values []*models.Area, batchSize int) error {
|
||||||
|
return a.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (a areaDo) Save(values ...*models.Area) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) First() (*models.Area, error) {
|
||||||
|
if result, err := a.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Area), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Take() (*models.Area, error) {
|
||||||
|
if result, err := a.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Area), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Last() (*models.Area, error) {
|
||||||
|
if result, err := a.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Area), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Find() ([]*models.Area, error) {
|
||||||
|
result, err := a.DO.Find()
|
||||||
|
return result.([]*models.Area), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Area, err error) {
|
||||||
|
buf := make([]*models.Area, 0, batchSize)
|
||||||
|
err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) FindInBatches(result *[]*models.Area, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return a.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Attrs(attrs ...field.AssignExpr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Assign(attrs ...field.AssignExpr) *areaDo {
|
||||||
|
return a.withDO(a.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Joins(fields ...field.RelationField) *areaDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
a = *a.withDO(a.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Preload(fields ...field.RelationField) *areaDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
a = *a.withDO(a.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) FirstOrInit() (*models.Area, error) {
|
||||||
|
if result, err := a.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Area), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) FirstOrCreate() (*models.Area, error) {
|
||||||
|
if result, err := a.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Area), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) FindByPage(offset int, limit int) (result []*models.Area, count int64, err error) {
|
||||||
|
result, err = a.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = a.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = a.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Scan(result interface{}) (err error) {
|
||||||
|
return a.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a areaDo) Delete(models ...*models.Area) (result gen.ResultInfo, err error) {
|
||||||
|
return a.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *areaDo) withDO(do gen.Dao) *areaDo {
|
||||||
|
a.DO = *do.(*gen.DO)
|
||||||
|
return a
|
||||||
|
}
|
||||||
@@ -218,6 +218,12 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
|
|||||||
}
|
}
|
||||||
Edge struct {
|
Edge struct {
|
||||||
field.RelationField
|
field.RelationField
|
||||||
|
Area struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}{
|
}{
|
||||||
RelationField: field.NewRelation("Proxy.Channels", "models.Channel"),
|
RelationField: field.NewRelation("Proxy.Channels", "models.Channel"),
|
||||||
@@ -238,8 +244,27 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
|
|||||||
},
|
},
|
||||||
Edge: struct {
|
Edge: struct {
|
||||||
field.RelationField
|
field.RelationField
|
||||||
|
Area struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
}{
|
}{
|
||||||
RelationField: field.NewRelation("Proxy.Channels.Edge", "models.Edge"),
|
RelationField: field.NewRelation("Proxy.Channels.Edge", "models.Edge"),
|
||||||
|
Area: struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Proxy.Channels.Edge.Area", "models.Area"),
|
||||||
|
Parent: struct {
|
||||||
|
field.RelationField
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Proxy.Channels.Edge.Area.Parent", "models.Area"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -617,6 +642,12 @@ type channelBelongsToProxy struct {
|
|||||||
}
|
}
|
||||||
Edge struct {
|
Edge struct {
|
||||||
field.RelationField
|
field.RelationField
|
||||||
|
Area struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,20 @@ func newEdge(db *gorm.DB, opts ...gen.DOOption) edge {
|
|||||||
_edge.IP = field.NewField(tableName, "ip")
|
_edge.IP = field.NewField(tableName, "ip")
|
||||||
_edge.Port = field.NewUint16(tableName, "port")
|
_edge.Port = field.NewUint16(tableName, "port")
|
||||||
_edge.ISP = field.NewInt(tableName, "isp")
|
_edge.ISP = field.NewInt(tableName, "isp")
|
||||||
_edge.Prov = field.NewString(tableName, "prov")
|
_edge.AreaID = field.NewInt32(tableName, "area_id")
|
||||||
_edge.City = field.NewString(tableName, "city")
|
|
||||||
_edge.Status = field.NewInt(tableName, "status")
|
_edge.Status = field.NewInt(tableName, "status")
|
||||||
_edge.RTT = field.NewInt32(tableName, "rtt")
|
_edge.RTT = field.NewInt32(tableName, "rtt")
|
||||||
_edge.Loss = field.NewInt32(tableName, "loss")
|
_edge.Loss = field.NewInt32(tableName, "loss")
|
||||||
|
_edge.Area = edgeBelongsToArea{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("Area", "models.Area"),
|
||||||
|
Parent: struct {
|
||||||
|
field.RelationField
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Area.Parent", "models.Area"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
_edge.fillFieldMap()
|
_edge.fillFieldMap()
|
||||||
|
|
||||||
@@ -62,11 +71,11 @@ type edge struct {
|
|||||||
IP field.Field
|
IP field.Field
|
||||||
Port field.Uint16
|
Port field.Uint16
|
||||||
ISP field.Int
|
ISP field.Int
|
||||||
Prov field.String
|
AreaID field.Int32
|
||||||
City field.String
|
|
||||||
Status field.Int
|
Status field.Int
|
||||||
RTT field.Int32
|
RTT field.Int32
|
||||||
Loss field.Int32
|
Loss field.Int32
|
||||||
|
Area edgeBelongsToArea
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -93,8 +102,7 @@ func (e *edge) updateTableName(table string) *edge {
|
|||||||
e.IP = field.NewField(table, "ip")
|
e.IP = field.NewField(table, "ip")
|
||||||
e.Port = field.NewUint16(table, "port")
|
e.Port = field.NewUint16(table, "port")
|
||||||
e.ISP = field.NewInt(table, "isp")
|
e.ISP = field.NewInt(table, "isp")
|
||||||
e.Prov = field.NewString(table, "prov")
|
e.AreaID = field.NewInt32(table, "area_id")
|
||||||
e.City = field.NewString(table, "city")
|
|
||||||
e.Status = field.NewInt(table, "status")
|
e.Status = field.NewInt(table, "status")
|
||||||
e.RTT = field.NewInt32(table, "rtt")
|
e.RTT = field.NewInt32(table, "rtt")
|
||||||
e.Loss = field.NewInt32(table, "loss")
|
e.Loss = field.NewInt32(table, "loss")
|
||||||
@@ -125,23 +133,111 @@ func (e *edge) fillFieldMap() {
|
|||||||
e.fieldMap["ip"] = e.IP
|
e.fieldMap["ip"] = e.IP
|
||||||
e.fieldMap["port"] = e.Port
|
e.fieldMap["port"] = e.Port
|
||||||
e.fieldMap["isp"] = e.ISP
|
e.fieldMap["isp"] = e.ISP
|
||||||
e.fieldMap["prov"] = e.Prov
|
e.fieldMap["area_id"] = e.AreaID
|
||||||
e.fieldMap["city"] = e.City
|
|
||||||
e.fieldMap["status"] = e.Status
|
e.fieldMap["status"] = e.Status
|
||||||
e.fieldMap["rtt"] = e.RTT
|
e.fieldMap["rtt"] = e.RTT
|
||||||
e.fieldMap["loss"] = e.Loss
|
e.fieldMap["loss"] = e.Loss
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e edge) clone(db *gorm.DB) edge {
|
func (e edge) clone(db *gorm.DB) edge {
|
||||||
e.edgeDo.ReplaceConnPool(db.Statement.ConnPool)
|
e.edgeDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
e.Area.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
e.Area.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e edge) replaceDB(db *gorm.DB) edge {
|
func (e edge) replaceDB(db *gorm.DB) edge {
|
||||||
e.edgeDo.ReplaceDB(db)
|
e.edgeDo.ReplaceDB(db)
|
||||||
|
e.Area.db = db.Session(&gorm.Session{})
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type edgeBelongsToArea struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToArea) Where(conds ...field.Expr) *edgeBelongsToArea {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToArea) WithContext(ctx context.Context) *edgeBelongsToArea {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToArea) Session(session *gorm.Session) *edgeBelongsToArea {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToArea) Model(m *models.Edge) *edgeBelongsToAreaTx {
|
||||||
|
return &edgeBelongsToAreaTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToArea) Unscoped() *edgeBelongsToArea {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type edgeBelongsToAreaTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Find() (result *models.Area, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Append(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Replace(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Delete(values ...*models.Area) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a edgeBelongsToAreaTx) Unscoped() *edgeBelongsToAreaTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
type edgeDo struct{ gen.DO }
|
type edgeDo struct{ gen.DO }
|
||||||
|
|
||||||
func (e edgeDo) Debug() *edgeDo {
|
func (e edgeDo) Debug() *edgeDo {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ var (
|
|||||||
Admin *admin
|
Admin *admin
|
||||||
AdminRole *adminRole
|
AdminRole *adminRole
|
||||||
Announcement *announcement
|
Announcement *announcement
|
||||||
|
Area *area
|
||||||
Article *article
|
Article *article
|
||||||
ArticleGroup *articleGroup
|
ArticleGroup *articleGroup
|
||||||
BalanceActivity *balanceActivity
|
BalanceActivity *balanceActivity
|
||||||
@@ -61,6 +62,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
Admin = &Q.Admin
|
Admin = &Q.Admin
|
||||||
AdminRole = &Q.AdminRole
|
AdminRole = &Q.AdminRole
|
||||||
Announcement = &Q.Announcement
|
Announcement = &Q.Announcement
|
||||||
|
Area = &Q.Area
|
||||||
Article = &Q.Article
|
Article = &Q.Article
|
||||||
ArticleGroup = &Q.ArticleGroup
|
ArticleGroup = &Q.ArticleGroup
|
||||||
BalanceActivity = &Q.BalanceActivity
|
BalanceActivity = &Q.BalanceActivity
|
||||||
@@ -103,6 +105,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
Admin: newAdmin(db, opts...),
|
Admin: newAdmin(db, opts...),
|
||||||
AdminRole: newAdminRole(db, opts...),
|
AdminRole: newAdminRole(db, opts...),
|
||||||
Announcement: newAnnouncement(db, opts...),
|
Announcement: newAnnouncement(db, opts...),
|
||||||
|
Area: newArea(db, opts...),
|
||||||
Article: newArticle(db, opts...),
|
Article: newArticle(db, opts...),
|
||||||
ArticleGroup: newArticleGroup(db, opts...),
|
ArticleGroup: newArticleGroup(db, opts...),
|
||||||
BalanceActivity: newBalanceActivity(db, opts...),
|
BalanceActivity: newBalanceActivity(db, opts...),
|
||||||
@@ -146,6 +149,7 @@ type Query struct {
|
|||||||
Admin admin
|
Admin admin
|
||||||
AdminRole adminRole
|
AdminRole adminRole
|
||||||
Announcement announcement
|
Announcement announcement
|
||||||
|
Area area
|
||||||
Article article
|
Article article
|
||||||
ArticleGroup articleGroup
|
ArticleGroup articleGroup
|
||||||
BalanceActivity balanceActivity
|
BalanceActivity balanceActivity
|
||||||
@@ -190,6 +194,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
Admin: q.Admin.clone(db),
|
Admin: q.Admin.clone(db),
|
||||||
AdminRole: q.AdminRole.clone(db),
|
AdminRole: q.AdminRole.clone(db),
|
||||||
Announcement: q.Announcement.clone(db),
|
Announcement: q.Announcement.clone(db),
|
||||||
|
Area: q.Area.clone(db),
|
||||||
Article: q.Article.clone(db),
|
Article: q.Article.clone(db),
|
||||||
ArticleGroup: q.ArticleGroup.clone(db),
|
ArticleGroup: q.ArticleGroup.clone(db),
|
||||||
BalanceActivity: q.BalanceActivity.clone(db),
|
BalanceActivity: q.BalanceActivity.clone(db),
|
||||||
@@ -241,6 +246,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
Admin: q.Admin.replaceDB(db),
|
Admin: q.Admin.replaceDB(db),
|
||||||
AdminRole: q.AdminRole.replaceDB(db),
|
AdminRole: q.AdminRole.replaceDB(db),
|
||||||
Announcement: q.Announcement.replaceDB(db),
|
Announcement: q.Announcement.replaceDB(db),
|
||||||
|
Area: q.Area.replaceDB(db),
|
||||||
Article: q.Article.replaceDB(db),
|
Article: q.Article.replaceDB(db),
|
||||||
ArticleGroup: q.ArticleGroup.replaceDB(db),
|
ArticleGroup: q.ArticleGroup.replaceDB(db),
|
||||||
BalanceActivity: q.BalanceActivity.replaceDB(db),
|
BalanceActivity: q.BalanceActivity.replaceDB(db),
|
||||||
@@ -282,6 +288,7 @@ type queryCtx struct {
|
|||||||
Admin *adminDo
|
Admin *adminDo
|
||||||
AdminRole *adminRoleDo
|
AdminRole *adminRoleDo
|
||||||
Announcement *announcementDo
|
Announcement *announcementDo
|
||||||
|
Area *areaDo
|
||||||
Article *articleDo
|
Article *articleDo
|
||||||
ArticleGroup *articleGroupDo
|
ArticleGroup *articleGroupDo
|
||||||
BalanceActivity *balanceActivityDo
|
BalanceActivity *balanceActivityDo
|
||||||
@@ -323,6 +330,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
Admin: q.Admin.WithContext(ctx),
|
Admin: q.Admin.WithContext(ctx),
|
||||||
AdminRole: q.AdminRole.WithContext(ctx),
|
AdminRole: q.AdminRole.WithContext(ctx),
|
||||||
Announcement: q.Announcement.WithContext(ctx),
|
Announcement: q.Announcement.WithContext(ctx),
|
||||||
|
Area: q.Area.WithContext(ctx),
|
||||||
Article: q.Article.WithContext(ctx),
|
Article: q.Article.WithContext(ctx),
|
||||||
ArticleGroup: q.ArticleGroup.WithContext(ctx),
|
ArticleGroup: q.ArticleGroup.WithContext(ctx),
|
||||||
BalanceActivity: q.BalanceActivity.WithContext(ctx),
|
BalanceActivity: q.BalanceActivity.WithContext(ctx),
|
||||||
|
|||||||
@@ -262,8 +262,27 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
|
|||||||
},
|
},
|
||||||
Edge: struct {
|
Edge: struct {
|
||||||
field.RelationField
|
field.RelationField
|
||||||
|
Area struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
}{
|
}{
|
||||||
RelationField: field.NewRelation("Channels.Edge", "models.Edge"),
|
RelationField: field.NewRelation("Channels.Edge", "models.Edge"),
|
||||||
|
Area: struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Channels.Edge.Area", "models.Area"),
|
||||||
|
Parent: struct {
|
||||||
|
field.RelationField
|
||||||
|
}{
|
||||||
|
RelationField: field.NewRelation("Channels.Edge.Area.Parent", "models.Area"),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +454,12 @@ type proxyHasManyChannels struct {
|
|||||||
}
|
}
|
||||||
Edge struct {
|
Edge struct {
|
||||||
field.RelationField
|
field.RelationField
|
||||||
|
Area struct {
|
||||||
|
field.RelationField
|
||||||
|
Parent struct {
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"platform/pkg/env"
|
"platform/pkg/env"
|
||||||
auth2 "platform/web/auth"
|
auth2 "platform/web/auth"
|
||||||
"platform/web/core"
|
"platform/web/core"
|
||||||
"platform/web/globals"
|
"platform/web/globals"
|
||||||
"platform/web/handlers"
|
"platform/web/handlers"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
q "platform/web/queries"
|
q "platform/web/queries"
|
||||||
@@ -39,7 +41,6 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
debug.Get("/test/err", func(ctx *fiber.Ctx) error {
|
debug.Get("/test/err", func(ctx *fiber.Ctx) error {
|
||||||
return core.NewBizErr("测试错误")
|
return core.NewBizErr("测试错误")
|
||||||
})
|
})
|
||||||
|
|
||||||
debug.Get("/trade/status/:trade_no", func(ctx *fiber.Ctx) error {
|
debug.Get("/trade/status/:trade_no", func(ctx *fiber.Ctx) error {
|
||||||
tradeNo := ctx.Params("trade_no")
|
tradeNo := ctx.Params("trade_no")
|
||||||
resp, err := globals.SFTPay.QueryTrade(&globals.QueryTradeReq{
|
resp, err := globals.SFTPay.QueryTrade(&globals.QueryTradeReq{
|
||||||
@@ -50,6 +51,46 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
}
|
}
|
||||||
return ctx.JSON(resp)
|
return ctx.JSON(resp)
|
||||||
})
|
})
|
||||||
|
debug.Get("/gen-edge", func(ctx *fiber.Ctx) error {
|
||||||
|
areas, err := q.Area.Where(q.Area.Level.Eq(2)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.WriteString("INSERT INTO edge (type, version, mac, ip, port, isp, area_id, status) VALUES\n")
|
||||||
|
|
||||||
|
for i, area := range areas {
|
||||||
|
// jh edges
|
||||||
|
for j := range 20 {
|
||||||
|
fmt.Fprintf(&sb, "(2, 1, 'jh-%d-%d-%d', '192.168.50.%d', %d, 0, %d, 1)", area.ID, j+1, i+44001, j+2, i+44001, area.ID)
|
||||||
|
sb.WriteString(",\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// jg edges
|
||||||
|
for j := range 10 {
|
||||||
|
var ip string
|
||||||
|
var n int
|
||||||
|
if i < 100 {
|
||||||
|
ip = "192.168.0.232"
|
||||||
|
n = 1
|
||||||
|
} else if i < 200 {
|
||||||
|
ip = "192.168.59.236"
|
||||||
|
n = 2
|
||||||
|
} else {
|
||||||
|
ip = "192.168.59.237"
|
||||||
|
n = 3
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&sb, "(2, 1, 'jg-%d-%d-%d', '%s', %d, 0, %d, 1)", area.ID, n, i*10+j+20001, ip, i*10+j+20001, area.ID)
|
||||||
|
if i < len(areas)-1 || j < 9 {
|
||||||
|
sb.WriteString(",\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(";\n")
|
||||||
|
return ctx.SendString(sb.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +130,7 @@ func clientRouter(api fiber.Router) {
|
|||||||
|
|
||||||
// 网关
|
// 网关
|
||||||
proxy := client.Group("/proxy")
|
proxy := client.Group("/proxy")
|
||||||
proxy.Post("/online", handlers.ProxyReportOnline)
|
proxy.Post("/sync-pool", handlers.SyncProxyPool)
|
||||||
proxy.Post("/offline", handlers.ProxyReportOffline)
|
|
||||||
proxy.Post("/update", handlers.ProxyReportUpdate)
|
|
||||||
|
|
||||||
// 通道管理
|
// 通道管理
|
||||||
channel := client.Group("/channel")
|
channel := client.Group("/channel")
|
||||||
@@ -139,6 +178,11 @@ func userRouter(api fiber.Router) {
|
|||||||
channel.Post("/list", handlers.ListChannel)
|
channel.Post("/list", handlers.ListChannel)
|
||||||
channel.Post("/create", handlers.CreateChannel)
|
channel.Post("/create", handlers.CreateChannel)
|
||||||
channel.Post("/create/v2", handlers.CreateChannelV2)
|
channel.Post("/create/v2", handlers.CreateChannelV2)
|
||||||
|
channel.Post("/create/v3", handlers.CreateChannelV3)
|
||||||
|
|
||||||
|
// 地区
|
||||||
|
area := api.Group("/area")
|
||||||
|
area.Post("/list", handlers.ListArea)
|
||||||
|
|
||||||
// 交易
|
// 交易
|
||||||
trade := api.Group("/trade")
|
trade := api.Group("/trade")
|
||||||
|
|||||||
112
web/services/area.go
Normal file
112
web/services/area.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"platform/pkg/u"
|
||||||
|
"platform/web/core"
|
||||||
|
m "platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Area = &areaService{}
|
||||||
|
|
||||||
|
type areaService struct{}
|
||||||
|
|
||||||
|
func (s *areaService) ListAreas() ([]*m.Area, error) {
|
||||||
|
areas, err := q.Area.
|
||||||
|
Order(q.Area.Level, q.Area.ParentID, q.Area.ID).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
return areas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *areaService) FindIdByFilter(prov *string, city *string) (*int32, error) {
|
||||||
|
prov = u.N(prov)
|
||||||
|
city = u.N(city)
|
||||||
|
if prov == nil && city == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case prov != nil && city == nil:
|
||||||
|
area, err := q.Area.
|
||||||
|
Where(
|
||||||
|
q.Area.Level.Eq(int(m.AreaLevelProvince)),
|
||||||
|
q.Area.Name.Eq(*prov),
|
||||||
|
).
|
||||||
|
Order(q.Area.ID).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrAreaNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
return u.P(area.ID), nil
|
||||||
|
case prov == nil && city != nil:
|
||||||
|
area, err := q.Area.
|
||||||
|
Where(
|
||||||
|
q.Area.Level.Eq(int(m.AreaLevelCity)),
|
||||||
|
q.Area.Name.Eq(*city),
|
||||||
|
).
|
||||||
|
Order(q.Area.ID).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrAreaNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
return u.P(area.ID), nil
|
||||||
|
default:
|
||||||
|
province, err := q.Area.
|
||||||
|
Where(
|
||||||
|
q.Area.Level.Eq(int(m.AreaLevelProvince)),
|
||||||
|
q.Area.Name.Eq(*prov),
|
||||||
|
).
|
||||||
|
Order(q.Area.ID).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrAreaNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
area, err := q.Area.
|
||||||
|
Where(
|
||||||
|
q.Area.ParentID.Eq(province.ID),
|
||||||
|
q.Area.Level.Eq(int(m.AreaLevelCity)),
|
||||||
|
q.Area.Name.Eq(*city),
|
||||||
|
).
|
||||||
|
Order(q.Area.ID).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrAreaNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
return u.P(area.ID), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *areaService) Get(id int32) (*m.Area, error) {
|
||||||
|
area, err := q.Area.
|
||||||
|
Preload(q.Area.Parent).
|
||||||
|
Where(q.Area.ID.Eq(id)).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrAreaNotExist
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.NewServErr("查询地区失败", err)
|
||||||
|
}
|
||||||
|
return area, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrAreaNotExist = core.NewBizErr("地区不存在")
|
||||||
@@ -40,12 +40,21 @@ type channelServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelServer) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
|
func (s *channelServer) CreateChannels(source netip.Addr, resourceNo string, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
|
||||||
|
var area *m.Area
|
||||||
|
if edgeFilter.AreaID != nil {
|
||||||
|
var err error
|
||||||
|
area, err = Area.Get(*edgeFilter.AreaID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := validateChannelArea(area); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
batchNo := ID.GenReadable("bat")
|
batchNo := ID.GenReadable("bat")
|
||||||
var channels []*m.Channel
|
var channels []*m.Channel
|
||||||
if edgeFilter == nil {
|
|
||||||
edgeFilter = &EdgeFilter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var whitelistText *string
|
var whitelistText *string
|
||||||
err := g.Redsync.WithLock(lockChannelCreateKey(resourceNo), func() error {
|
err := g.Redsync.WithLock(lockChannelCreateKey(resourceNo), func() error {
|
||||||
@@ -80,6 +89,7 @@ func (s *channelServer) CreateChannels(source netip.Addr, resourceNo string, aut
|
|||||||
Expire: expire,
|
Expire: expire,
|
||||||
Count: count,
|
Count: count,
|
||||||
Filter: edgeFilter,
|
Filter: edgeFilter,
|
||||||
|
Area: area,
|
||||||
AuthWhitelist: authWhitelist,
|
AuthWhitelist: authWhitelist,
|
||||||
AuthPassword: authPassword,
|
AuthPassword: authPassword,
|
||||||
Whitelists: whitelists,
|
Whitelists: whitelists,
|
||||||
@@ -160,6 +170,7 @@ type channelCreateContext struct {
|
|||||||
Expire time.Time
|
Expire time.Time
|
||||||
Count int
|
Count int
|
||||||
Filter *EdgeFilter
|
Filter *EdgeFilter
|
||||||
|
Area *m.Area
|
||||||
AuthWhitelist bool
|
AuthWhitelist bool
|
||||||
AuthPassword bool
|
AuthPassword bool
|
||||||
Whitelists []string
|
Whitelists []string
|
||||||
@@ -172,6 +183,7 @@ type channelCreateResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newBaseChannel(ctx *channelCreateContext, port uint16) *m.Channel {
|
func newBaseChannel(ctx *channelCreateContext, port uint16) *m.Channel {
|
||||||
|
prov, city := areaProvinceCity(ctx.Area)
|
||||||
return &m.Channel{
|
return &m.Channel{
|
||||||
UserID: ctx.Resource.User.ID,
|
UserID: ctx.Resource.User.ID,
|
||||||
ResourceID: ctx.Resource.ID,
|
ResourceID: ctx.Resource.ID,
|
||||||
@@ -180,8 +192,8 @@ func newBaseChannel(ctx *channelCreateContext, port uint16) *m.Channel {
|
|||||||
Host: u.Else(ctx.Proxy.Host, ctx.Proxy.IP.String()),
|
Host: u.Else(ctx.Proxy.Host, ctx.Proxy.IP.String()),
|
||||||
Port: port,
|
Port: port,
|
||||||
FilterISP: ctx.Filter.Isp,
|
FilterISP: ctx.Filter.Isp,
|
||||||
FilterProv: ctx.Filter.Prov,
|
FilterProv: prov,
|
||||||
FilterCity: ctx.Filter.City,
|
FilterCity: city,
|
||||||
ExpiredAt: ctx.Expire,
|
ExpiredAt: ctx.Expire,
|
||||||
Proxy: ctx.Proxy,
|
Proxy: ctx.Proxy,
|
||||||
}
|
}
|
||||||
@@ -202,6 +214,7 @@ func applyChannelAuth(ctx *channelCreateContext, channel *m.Channel) (username s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func persistChannelCreate(ctx *channelCreateContext, channels []*m.Channel) error {
|
func persistChannelCreate(ctx *channelCreateContext, channels []*m.Channel) error {
|
||||||
|
prov, city := areaProvinceCity(ctx.Area)
|
||||||
return q.Q.Transaction(func(tx *q.Query) error {
|
return q.Q.Transaction(func(tx *q.Query) error {
|
||||||
var (
|
var (
|
||||||
result gen.ResultInfo
|
result gen.ResultInfo
|
||||||
@@ -252,8 +265,8 @@ func persistChannelCreate(ctx *channelCreateContext, channels []*m.Channel) erro
|
|||||||
BatchNo: ctx.BatchNo,
|
BatchNo: ctx.BatchNo,
|
||||||
Count: int32(ctx.Count),
|
Count: int32(ctx.Count),
|
||||||
ISP: u.X(ctx.Filter.Isp.String()),
|
ISP: u.X(ctx.Filter.Isp.String()),
|
||||||
Prov: ctx.Filter.Prov,
|
Prov: prov,
|
||||||
City: ctx.Filter.City,
|
City: city,
|
||||||
IP: orm.Inet{Addr: ctx.Source},
|
IP: orm.Inet{Addr: ctx.Source},
|
||||||
Time: ctx.Now,
|
Time: ctx.Now,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -264,6 +277,37 @@ func persistChannelCreate(ctx *channelCreateContext, channels []*m.Channel) erro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateChannelArea(area *m.Area) error {
|
||||||
|
if area == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch area.Level {
|
||||||
|
case m.AreaLevelProvince:
|
||||||
|
return nil
|
||||||
|
case m.AreaLevelCity:
|
||||||
|
if area.ParentID == nil || area.Parent == nil {
|
||||||
|
return core.NewServErr("地区数据异常", nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return core.NewBizErr("地区层级不支持")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func areaProvinceCity(area *m.Area) (prov *string, city *string) {
|
||||||
|
if area == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
switch area.Level {
|
||||||
|
case m.AreaLevelProvince:
|
||||||
|
return u.P(area.Name), nil
|
||||||
|
case m.AreaLevelCity:
|
||||||
|
return u.P(area.Parent.Name), u.P(area.Name)
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func findExpiredChannelBatches(proxyId int32, now time.Time) (map[string]struct{}, error) {
|
func findExpiredChannelBatches(proxyId int32, now time.Time) (map[string]struct{}, error) {
|
||||||
keys, err := g.Redis.Keys(context.Background(), usedChansKey(proxyId, "*")).Result()
|
keys, err := g.Redis.Keys(context.Background(), usedChansKey(proxyId, "*")).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -778,6 +822,20 @@ redis.call("DEL", batch_key)
|
|||||||
return 1
|
return 1
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
// 节点筛选条件
|
||||||
|
type EdgeFilter struct {
|
||||||
|
Isp *m.EdgeISP `json:"isp"`
|
||||||
|
AreaID *int32 `json:"area_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *EdgeFilter) IsEmpty() bool {
|
||||||
|
if f == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.X(f.Isp.String()) == nil && f.AreaID == nil
|
||||||
|
}
|
||||||
|
|
||||||
// 错误信息
|
// 错误信息
|
||||||
var (
|
var (
|
||||||
ErrResourceNotExist = core.NewBizErr("套餐不存在")
|
ErrResourceNotExist = core.NewBizErr("套餐不存在")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func (s *channelBaiyinProvider) prepareCreate(ctx *channelCreateContext) (*chann
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, core.NewServErr("创建代理网关失败", err)
|
return nil, core.NewServErr("创建代理网关失败", err)
|
||||||
}
|
}
|
||||||
|
prov, city := areaProvinceCity(ctx.Area)
|
||||||
|
|
||||||
channels := make([]*m.Channel, len(ctx.Ports))
|
channels := make([]*m.Channel, len(ctx.Ports))
|
||||||
chanConfigs := make([]*g.PortConfigsReq, len(ctx.Ports))
|
chanConfigs := make([]*g.PortConfigsReq, len(ctx.Ports))
|
||||||
@@ -30,8 +31,8 @@ func (s *channelBaiyinProvider) prepareCreate(ctx *channelCreateContext) (*chann
|
|||||||
Port: int(portRef.Port()),
|
Port: int(portRef.Port()),
|
||||||
Status: true,
|
Status: true,
|
||||||
AutoEdgeConfig: &g.AutoEdgeConfig{
|
AutoEdgeConfig: &g.AutoEdgeConfig{
|
||||||
Province: u.Z(ctx.Filter.Prov),
|
Province: u.Z(prov),
|
||||||
City: u.Z(ctx.Filter.City),
|
City: u.Z(city),
|
||||||
Isp: ctx.Filter.Isp.String(),
|
Isp: ctx.Filter.Isp.String(),
|
||||||
Count: u.P(1),
|
Count: u.P(1),
|
||||||
},
|
},
|
||||||
@@ -52,7 +53,7 @@ func (s *channelBaiyinProvider) prepareCreate(ctx *channelCreateContext) (*chann
|
|||||||
Channels: channels,
|
Channels: channels,
|
||||||
applyRemote: func() error {
|
applyRemote: func() error {
|
||||||
slog.Debug("提交代理端口配置", "proxy", ctx.Proxy.IP.String(), "total_count", len(chanConfigs))
|
slog.Debug("提交代理端口配置", "proxy", ctx.Proxy.IP.String(), "total_count", len(chanConfigs))
|
||||||
if err := ensureEdges(ctx.Proxy, gateway, ctx.Filter, ctx.Count); err != nil {
|
if err := ensureEdges(ctx.Proxy, gateway, ctx.Area, ctx.Filter.Isp, ctx.Count); err != nil {
|
||||||
slog.Warn("ensureEdges 失败", "err", err)
|
slog.Warn("ensureEdges 失败", "err", err)
|
||||||
}
|
}
|
||||||
if len(chanConfigs) > 0 {
|
if len(chanConfigs) > 0 {
|
||||||
@@ -96,16 +97,17 @@ func (s *channelBaiyinProvider) removeRemote(_ string, batch *usedChanBatch) err
|
|||||||
// ensureEdges 检查本地节点是否足够,如果不足从云端连入
|
// ensureEdges 检查本地节点是否足够,如果不足从云端连入
|
||||||
// 本地节点通过 Assigned = false 排除已分配节点
|
// 本地节点通过 Assigned = false 排除已分配节点
|
||||||
// 云端节点通过 NoRepeat = true 排除已分配节点
|
// 云端节点通过 NoRepeat = true 排除已分配节点
|
||||||
func ensureEdges(proxy *m.Proxy, gateway g.GatewayClient, filter *EdgeFilter, count int) error {
|
func ensureEdges(proxy *m.Proxy, gateway g.GatewayClient, area *m.Area, isp *m.EdgeISP, count int) error {
|
||||||
if filter.IsEmpty() {
|
prov, city := areaProvinceCity(area)
|
||||||
|
if prov == nil && city == nil && u.X(isp.String()) == nil {
|
||||||
return nil // 没有过滤条件,直接返回空,避免无意义的查询
|
return nil // 没有过滤条件,直接返回空,避免无意义的查询
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先查本地
|
// 先查本地
|
||||||
localEdges, err := gateway.GatewayEdge(&g.GatewayEdgeReq{
|
localEdges, err := gateway.GatewayEdge(&g.GatewayEdgeReq{
|
||||||
Province: filter.Prov,
|
Province: prov,
|
||||||
City: filter.City,
|
City: city,
|
||||||
Isp: u.X(filter.Isp.String()),
|
Isp: u.X(isp.String()),
|
||||||
Limit: &count,
|
Limit: &count,
|
||||||
Assigned: u.P(false),
|
Assigned: u.P(false),
|
||||||
})
|
})
|
||||||
@@ -119,9 +121,9 @@ func ensureEdges(proxy *m.Proxy, gateway g.GatewayClient, filter *EdgeFilter, co
|
|||||||
// 再查云端
|
// 再查云端
|
||||||
remaining := count - len(localEdges)
|
remaining := count - len(localEdges)
|
||||||
cloudEdges, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
|
cloudEdges, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
|
||||||
Province: filter.Prov,
|
Province: prov,
|
||||||
City: filter.City,
|
City: city,
|
||||||
Isp: u.X(filter.Isp.String()),
|
Isp: u.X(isp.String()),
|
||||||
Limit: &remaining,
|
Limit: &remaining,
|
||||||
NoRepeat: u.P(true),
|
NoRepeat: u.P(true),
|
||||||
ActiveTime: u.P(3600),
|
ActiveTime: u.P(3600),
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import (
|
|||||||
m "platform/web/models"
|
m "platform/web/models"
|
||||||
q "platform/web/queries"
|
q "platform/web/queries"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
type channelGostProvider struct{}
|
type channelGostProvider struct{}
|
||||||
|
|
||||||
func (s *channelGostProvider) prepareCreate(ctx *channelCreateContext) (*channelCreateResult, error) {
|
func (s *channelGostProvider) prepareCreate(ctx *channelCreateContext) (*channelCreateResult, error) {
|
||||||
edges, err := s.selectEdge(ctx.Filter, ctx.Count)
|
edges, err := s.selectEdge(ctx.Filter, ctx.Area, ctx.Count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -131,26 +133,38 @@ func (s *channelGostProvider) selectProxy(count int) (*m.Proxy, error) {
|
|||||||
return selectProxyByType(m.ProxyTypeGost, count)
|
return selectProxyByType(m.ProxyTypeGost, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *channelGostProvider) selectEdge(filter *EdgeFilter, count int) ([]*m.Edge, error) {
|
func (s *channelGostProvider) selectEdge(filter *EdgeFilter, area *m.Area, count int) ([]*m.Edge, error) {
|
||||||
if filter == nil {
|
if filter == nil {
|
||||||
filter = &EdgeFilter{}
|
filter = &EdgeFilter{}
|
||||||
}
|
}
|
||||||
|
|
||||||
do := q.Edge.Where(
|
conds := []gen.Condition{
|
||||||
q.Edge.Type.Eq(int(m.EdgeTypeGostChain)),
|
q.Edge.Type.Eq(int(m.EdgeTypeGostChain)),
|
||||||
q.Edge.Status.Eq(int(m.EdgeStatusNormal)),
|
q.Edge.Status.Eq(int(m.EdgeStatusNormal)),
|
||||||
)
|
|
||||||
if prov := u.N(filter.Prov); prov != nil {
|
|
||||||
do = do.Where(q.Edge.Prov.Eq(*prov))
|
|
||||||
}
|
|
||||||
if city := u.N(filter.City); city != nil {
|
|
||||||
do = do.Where(q.Edge.City.Eq(*city))
|
|
||||||
}
|
}
|
||||||
if isp := u.X(filter.Isp.String()); isp != nil {
|
if isp := u.X(filter.Isp.String()); isp != nil {
|
||||||
do = do.Where(q.Edge.ISP.Eq(int(*filter.Isp)))
|
conds = append(conds, q.Edge.ISP.Eq(int(*filter.Isp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
edges, err := q.Edge.Where(do).Order(q.Edge.ID).Limit(count).Find()
|
query := q.Edge.Where(conds...)
|
||||||
|
if area != nil {
|
||||||
|
switch area.Level {
|
||||||
|
case m.AreaLevelProvince:
|
||||||
|
edgeArea := q.Area.As("EdgeArea")
|
||||||
|
query = query.
|
||||||
|
Join(edgeArea, edgeArea.ID.EqCol(q.Edge.AreaID)).
|
||||||
|
Where(edgeArea.ParentID.Eq(area.ID))
|
||||||
|
case m.AreaLevelCity:
|
||||||
|
query = query.Where(q.Edge.AreaID.Eq(area.ID))
|
||||||
|
default:
|
||||||
|
return nil, core.NewBizErr("地区层级不支持")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edges, err := query.
|
||||||
|
Order(q.Edge.ID).
|
||||||
|
Limit(count).
|
||||||
|
Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, core.NewBizErr("查询可用节点失败", err)
|
return nil, core.NewBizErr("查询可用节点失败", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
m "platform/web/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExpandGostEdgesRejectsEmpty(t *testing.T) {
|
|
||||||
_, err := expandGostEdges(nil, 1)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExpandGostEdgesReusesWhenInsufficient(t *testing.T) {
|
|
||||||
edges := []*m.Edge{
|
|
||||||
{Mac: "chain-a"},
|
|
||||||
{Mac: "chain-b"},
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := expandGostEdges(edges, 5)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expandGostEdges returned error: %v", err)
|
|
||||||
}
|
|
||||||
if len(result) != 5 {
|
|
||||||
t.Fatalf("unexpected edge count: %d", len(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"chain-a", "chain-b", "chain-a", "chain-b", "chain-a"}
|
|
||||||
for i, edge := range result {
|
|
||||||
if edge.Mac != expected[i] {
|
|
||||||
t.Fatalf("unexpected edge at %d: %s", i, edge.Mac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEdgeFilterIsEmpty(t *testing.T) {
|
|
||||||
if !(*EdgeFilter)(nil).IsEmpty() {
|
|
||||||
t.Fatal("nil filter should be empty")
|
|
||||||
}
|
|
||||||
if (&EdgeFilter{}).IsEmpty() != true {
|
|
||||||
t.Fatal("empty filter should be empty")
|
|
||||||
}
|
|
||||||
if (&EdgeFilter{Prov: strPtr("")}).IsEmpty() != true {
|
|
||||||
t.Fatal("filter with empty province should be empty")
|
|
||||||
}
|
|
||||||
if (&EdgeFilter{City: strPtr("")}).IsEmpty() != true {
|
|
||||||
t.Fatal("filter with empty city should be empty")
|
|
||||||
}
|
|
||||||
if (&EdgeFilter{Isp: ispPtr(m.ToEdgeISP(0))}).IsEmpty() != true {
|
|
||||||
t.Fatal("filter with zero ISP should be empty")
|
|
||||||
}
|
|
||||||
if (&EdgeFilter{Isp: ispPtr(m.ToEdgeISP(99))}).IsEmpty() != true {
|
|
||||||
t.Fatal("filter with invalid ISP should be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
prov := "江苏"
|
|
||||||
if (&EdgeFilter{Prov: &prov}).IsEmpty() {
|
|
||||||
t.Fatal("filter with province should not be empty")
|
|
||||||
}
|
|
||||||
isp := m.EdgeISPTelecom
|
|
||||||
if (&EdgeFilter{Isp: &isp}).IsEmpty() {
|
|
||||||
t.Fatal("filter with valid ISP should not be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func strPtr(v string) *string {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func ispPtr(v m.EdgeISP) *m.EdgeISP {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"platform/pkg/u"
|
|
||||||
m "platform/web/models"
|
|
||||||
q "platform/web/queries"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Edge = &edgeService{}
|
|
||||||
|
|
||||||
type edgeService struct{}
|
|
||||||
|
|
||||||
func (s *edgeService) AllEdges(count int, filter EdgeFilter) ([]*m.Edge, error) {
|
|
||||||
do := q.Edge.Where(q.Edge.Type.Eq(int(m.EdgeTypeSelfBuilt)))
|
|
||||||
if prov := u.N(filter.Prov); prov != nil {
|
|
||||||
do = do.Where(q.Edge.Prov.Eq(*prov))
|
|
||||||
}
|
|
||||||
if city := u.N(filter.City); city != nil {
|
|
||||||
do = do.Where(q.Edge.City.Eq(*city))
|
|
||||||
}
|
|
||||||
if isp := u.X(filter.Isp.String()); isp != nil {
|
|
||||||
do = do.Where(q.Edge.ISP.Eq(int(*filter.Isp)))
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
do = do.Limit(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
edges, err := q.Edge.Where(do).Find()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return edges, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type EdgeFilter struct {
|
|
||||||
Isp *m.EdgeISP `json:"isp"`
|
|
||||||
Prov *string `json:"prov"`
|
|
||||||
City *string `json:"city"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *EdgeFilter) IsEmpty() bool {
|
|
||||||
if f == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.X(f.Isp.String()) == nil && u.N(f.Prov) == nil && u.N(f.City) == nil
|
|
||||||
}
|
|
||||||
@@ -161,6 +161,17 @@ func (s *proxyService) Update(update *UpdateProxy) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *proxyService) SyncPool(id int32) error {
|
||||||
|
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(id)).Select(q.Proxy.ID, q.Proxy.IP).First()
|
||||||
|
if err != nil {
|
||||||
|
return core.NewServErr("获取代理数据失败", err)
|
||||||
|
}
|
||||||
|
if proxy == nil {
|
||||||
|
return core.NewBizErr("代理不存在")
|
||||||
|
}
|
||||||
|
return rebuildFreeChans(id, proxy.IP.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *proxyService) Remove(id int32) error {
|
func (s *proxyService) Remove(id int32) error {
|
||||||
used, err := hasUsedChans(id)
|
used, err := hasUsedChans(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user