正在显示
53 个修改的文件
包含
4705 行增加
和
0 行删除
.gitignore
0 → 100644
1 | +*.pyc | |
2 | +app.db | |
3 | +./tmp | |
4 | +./build/* | |
5 | +.idea | |
6 | +env | |
7 | +venv | |
8 | +.venv/ | |
9 | +.venv | |
10 | +*.sublime* | |
11 | +tmp | |
12 | +tmp/ | |
13 | + | |
14 | +venv/ | |
15 | +whls/ | |
16 | +tmp/ | |
17 | +depoly/ | |
18 | +logs/ | |
19 | +*.pyc | |
20 | +__pycache__/ | |
21 | + | |
22 | +instance/ | |
23 | + | |
24 | +.pytest_cache/ | |
25 | +.coverage | |
26 | +htmlcov/ | |
27 | +kservice/tmp | |
28 | +dist/ | |
29 | +build/ | |
30 | +*.egg-info/ | |
31 | +.git/ | |
\ No newline at end of file | ... | ... |
Dockerfile
0 → 100644
app/__init__.py
0 → 100644
1 | +import decimal | |
2 | + | |
3 | +from flask import Flask as _Flask | |
4 | +from flask.json import JSONEncoder as _JSONEncoder | |
5 | +from flask_cors import CORS | |
6 | +import time | |
7 | +import configure | |
8 | +from app.util import BlueprintApi | |
9 | +from app.util import find_class | |
10 | +from app.models import db,Table,InsertingLayerName,Database,DES,Task | |
11 | + | |
12 | +from flasgger import Swagger | |
13 | +# from rtree import index | |
14 | +import logging | |
15 | +from sqlalchemy.orm import Session | |
16 | +import multiprocessing | |
17 | +from app.util.component.EntryData import EntryData | |
18 | +from app.util.component.EntryDataVacuate import EntryDataVacuate | |
19 | +import json | |
20 | +import threading | |
21 | +import traceback | |
22 | +from sqlalchemy import distinct | |
23 | +import uuid | |
24 | +from osgeo.ogr import DataSource | |
25 | +import datetime | |
26 | +from sqlalchemy import or_ | |
27 | +from app.util.component.StructuredPrint import StructurePrint | |
28 | +from app.util.component.PGUtil import PGUtil | |
29 | +import os | |
30 | + | |
31 | +""" | |
32 | +因为decimal不能序列化,增加Flask对decimal类的解析 | |
33 | +""" | |
34 | +class JSONEncoder(_JSONEncoder): | |
35 | + def default(self, o): | |
36 | + if isinstance(o, decimal.Decimal): | |
37 | + return float(o) | |
38 | + super(JSONEncoder, self).default(o) | |
39 | + | |
40 | +class Flask(_Flask): | |
41 | + json_encoder = JSONEncoder | |
42 | + | |
43 | + | |
44 | +# idx =None | |
45 | +# url_json_list=None | |
46 | +# sqlite3_connect= None | |
47 | +def create_app(): | |
48 | + | |
49 | + # app基本设置 | |
50 | + app = Flask(__name__) | |
51 | + app.config['SQLALCHEMY_DATABASE_URI'] = configure.SQLALCHEMY_DATABASE_URI | |
52 | + app.config['echo'] = True | |
53 | + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True | |
54 | + app.config['JSON_AS_ASCII'] = False | |
55 | + # app.config['SQLALCHEMY_ECHO'] = True | |
56 | + | |
57 | + # 跨域设置 | |
58 | + CORS(app) | |
59 | + | |
60 | + #swagger设置 | |
61 | + swagger_config = Swagger.DEFAULT_CONFIG | |
62 | + swagger_config.update(configure.swagger_configure) | |
63 | + Swagger(app, config=swagger_config) | |
64 | + | |
65 | + # 创建数据库 | |
66 | + db.init_app(app) | |
67 | + db.create_all(app=app) | |
68 | + | |
69 | + | |
70 | + # 日志 | |
71 | + logging.basicConfig(level=logging.INFO) | |
72 | + log_file = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"logs","log.txt") | |
73 | + handler = logging.FileHandler(log_file, encoding='UTF-8') # 设置日志字符集和存储路径名字 | |
74 | + logging_format = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s') | |
75 | + handler.setFormatter(logging_format) | |
76 | + app.logger.addHandler(handler) | |
77 | + | |
78 | + | |
79 | + # 注册blueprint,查找BlueprintApi的子类 | |
80 | + for scan in configure.scan_module: | |
81 | + for api in find_class(scan, BlueprintApi): | |
82 | + app.register_blueprint(api.bp) | |
83 | + | |
84 | + | |
85 | + # 入库监测线程 | |
86 | + @app.before_first_request | |
87 | + def data_entry_process(): | |
88 | + StructurePrint.print("start listen") | |
89 | + process = threading.Thread(target=data_entry_center) | |
90 | + process.start() | |
91 | + | |
92 | + return app | |
93 | + | |
94 | + | |
95 | + | |
96 | + | |
97 | +def data_entry_center(): | |
98 | + running_dict = {} | |
99 | + sys_session: Session = PGUtil.get_db_session(configure.SQLALCHEMY_DATABASE_URI) | |
100 | + | |
101 | + while True: | |
102 | + | |
103 | + | |
104 | + try: | |
105 | + time.sleep(3) | |
106 | + | |
107 | + # 已经结束的进程 从监测中删除 | |
108 | + remove_process = [] | |
109 | + | |
110 | + # structured_print(running_dict.__len__().__str__()) | |
111 | + | |
112 | + for process,layer_names in running_dict.items(): | |
113 | + if not process.is_alive(): | |
114 | + for l in layer_names: | |
115 | + inserted = sys_session.query(InsertingLayerName).filter_by(name=l).one_or_none() | |
116 | + if inserted: | |
117 | + sys_session.delete(inserted) | |
118 | + sys_session.commit() | |
119 | + remove_process.append(process) | |
120 | + for process in remove_process: | |
121 | + running_dict.pop(process) | |
122 | + | |
123 | + # StructurePrint.print("listening...") | |
124 | + | |
125 | + # 入库进程少于阈值,开启入库进程 | |
126 | + | |
127 | + inter_size = sys_session.query(distinct(InsertingLayerName.task_guid)).count() | |
128 | + | |
129 | + if inter_size < configure.entry_data_thread: | |
130 | + # 锁表啊 | |
131 | + ready_task:Task = sys_session.query(Task).filter_by(state=0).order_by(Task.create_time).with_lockmode("update").limit(1).one_or_none() | |
132 | + if ready_task: | |
133 | + | |
134 | + try: | |
135 | + parameter = json.loads(ready_task.parameter) | |
136 | + StructurePrint.print("检测到入库任务") | |
137 | + ready_task.state=2 | |
138 | + ready_task.process="入库中" | |
139 | + sys_session.commit() | |
140 | + | |
141 | + metas: list = json.loads(parameter.get("meta").__str__()) | |
142 | + parameter["meta"] = metas | |
143 | + | |
144 | + | |
145 | + database = sys_session.query(Database).filter_by(guid=ready_task.database_guid).one_or_none() | |
146 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1,DES.decode(database.sqlalchemy_uri)) | |
147 | + | |
148 | + this_task_layer = [] | |
149 | + for meta in metas: | |
150 | + overwrite = parameter.get("overwrite", "no") | |
151 | + | |
152 | + | |
153 | + | |
154 | + for layer_name_origin, layer_name in meta.get("layer").items(): | |
155 | + origin_name = layer_name | |
156 | + no = 1 | |
157 | + | |
158 | + while (overwrite.__eq__("no") and pg_ds.GetLayerByName(layer_name)) or sys_session.query(InsertingLayerName).filter_by(name=layer_name).one_or_none() : | |
159 | + layer_name = origin_name + "_{}".format(no) | |
160 | + no += 1 | |
161 | + | |
162 | + # 添加到正在入库的列表中 | |
163 | + iln = InsertingLayerName(guid=uuid.uuid1().__str__(), | |
164 | + task_guid=ready_task.guid, | |
165 | + name=layer_name) | |
166 | + | |
167 | + sys_session.add(iln) | |
168 | + sys_session.commit() | |
169 | + this_task_layer.append(layer_name) | |
170 | + # 修改表名 | |
171 | + meta["layer"][layer_name_origin] = layer_name | |
172 | + | |
173 | + pg_ds.Destroy() | |
174 | + entry_data_process = multiprocessing.Process(target=EntryDataVacuate().entry,args=(parameter,)) | |
175 | + entry_data_process.start() | |
176 | + running_dict[entry_data_process] = this_task_layer | |
177 | + except Exception as e: | |
178 | + sys_session.query(Task).filter_by(guid=ready_task.guid).update( | |
179 | + {"state": -1, "process": "入库失败"}) | |
180 | + sys_session.commit() | |
181 | + StructurePrint.print(e.__str__(), "error") | |
182 | + else: | |
183 | + # 解表啊 | |
184 | + sys_session.commit() | |
185 | + except Exception as e: | |
186 | + sys_session.commit() | |
187 | + StructurePrint.print(e.__str__(),"error") | |
\ No newline at end of file | ... | ... |
app/models.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +import base64 | |
3 | +import os | |
4 | +from flask_sqlalchemy import SQLAlchemy,Model | |
5 | +import importlib | |
6 | +from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime, Time,Float | |
7 | +from sqlalchemy.orm import relationship | |
8 | +import base64 | |
9 | +from pyDes import * | |
10 | +import datetime | |
11 | + | |
12 | +from app.util.component.PGUtil import PGUtil | |
13 | +import glob | |
14 | + | |
15 | +class DES(): | |
16 | + ''' | |
17 | + DES密码加解密 | |
18 | + ''' | |
19 | + Des: des = des("Chinadci", ECB, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5) | |
20 | + | |
21 | + @classmethod | |
22 | + def encode(cls, data): | |
23 | + return str(base64.b64encode(cls.Des.encrypt(data)), encoding="utf8") | |
24 | + | |
25 | + | |
26 | + @classmethod | |
27 | + def decode(cls, data): | |
28 | + if data: | |
29 | + return str(cls.Des.decrypt(base64.b64decode(data.encode("utf-8"))), encoding="utf8") | |
30 | + | |
31 | + else: | |
32 | + return data | |
33 | + | |
34 | +db = SQLAlchemy() | |
35 | + | |
36 | +# 动态加载Model | |
37 | +module_dir = "modules" | |
38 | +model_pkg = "models" | |
39 | +current_dir = os.path.abspath(os.path.dirname(__file__)) | |
40 | +for pkg in glob.glob('%s/%s/*/%s' % (current_dir,module_dir,model_pkg)) : | |
41 | + module = os.path.basename(os.path.dirname(pkg)) | |
42 | + base_name = os.path.basename(pkg).split('.')[0] | |
43 | + pkg_name = ".".join(["app.modules",module,base_name]) | |
44 | + try: | |
45 | + __import__(pkg_name) | |
46 | + except: | |
47 | + pass | |
48 | + | |
49 | + | |
50 | + | |
51 | +class Table(db.Model): | |
52 | + ''' | |
53 | + 数据表元数据 | |
54 | + ''' | |
55 | + __tablename__ = 'dmdms_table' | |
56 | + guid = Column(String(256), primary_key=True) | |
57 | + # 数据源外键 | |
58 | + database_guid = Column(String(256), ForeignKey('dmdms_database.guid')) | |
59 | + | |
60 | + # 点线面123 | |
61 | + table_type = Column(Integer) | |
62 | + | |
63 | + name = Column(String(256)) | |
64 | + alias = Column(Text) | |
65 | + create_time = Column(DateTime) | |
66 | + update_time = Column(DateTime) | |
67 | + | |
68 | + feature_count = Column(Integer) | |
69 | + | |
70 | + description = Column(Text) | |
71 | + extent = Column(Text) | |
72 | + | |
73 | + #用户 | |
74 | + creator = Column(Text) | |
75 | + | |
76 | + # 目录外键 | |
77 | + catalog_guid = Column(String(256), ForeignKey('dmdms_catalog.guid')) | |
78 | + | |
79 | + relate_columns = relationship('Columns', backref='relate_table', lazy='dynamic') | |
80 | + relate_table_vacuates = relationship('TableVacuate', backref='relate_table', lazy='dynamic') | |
81 | + | |
82 | + | |
83 | +class TableVacuate(db.Model): | |
84 | + ''' | |
85 | + sub数据表元数据 | |
86 | + ''' | |
87 | + __tablename__ = 'dmdms_table_vacuate' | |
88 | + guid = Column(String(256), primary_key=True) | |
89 | + name = Column(String(256)) | |
90 | + level = Column(Integer) | |
91 | + pixel_distance = Column(Float) | |
92 | + table_guid = Column(String(256), ForeignKey('dmdms_table.guid')) | |
93 | + | |
94 | + | |
95 | + | |
96 | +class Columns(db.Model): | |
97 | + ''' | |
98 | + 数据表的列 | |
99 | + ''' | |
100 | + __tablename__ = 'dmdms_column' | |
101 | + guid = Column(String(256), primary_key=True) | |
102 | + # 表外键 | |
103 | + table_guid= Column(String(256), ForeignKey('dmdms_table.guid')) | |
104 | + name=Column(String(256)) | |
105 | + alias = Column(String(256)) | |
106 | + create_time = Column(DateTime) | |
107 | + update_time = Column(DateTime) | |
108 | + | |
109 | +class Task(db.Model): | |
110 | + ''' | |
111 | + 任务表 | |
112 | + ''' | |
113 | + __tablename__ = 'dmdms_task' | |
114 | + guid = Column(String(256), primary_key=True) | |
115 | + name = Column(Text) | |
116 | + process = Column(Text) | |
117 | + create_time = Column(DateTime) | |
118 | + update_time = Column(DateTime) | |
119 | + state = Column(Integer) | |
120 | + # 数据源外键 | |
121 | + database_guid = Column(String(256), ForeignKey('dmdms_database.guid')) | |
122 | + # 目录外键 | |
123 | + catalog_guid = Column(String(256), ForeignKey('dmdms_catalog.guid')) | |
124 | + | |
125 | + creator = Column(Text) | |
126 | + file_name = Column(Text) | |
127 | + | |
128 | + relate_processes = relationship('Process', backref='relate_task', lazy='dynamic') | |
129 | + | |
130 | + # 入库参数 | |
131 | + parameter= Column(Text) | |
132 | + | |
133 | + | |
134 | + | |
135 | +class Process(db.Model): | |
136 | + ''' | |
137 | + 过程信息表 | |
138 | + ''' | |
139 | + __tablename__ = 'dmdms_process' | |
140 | + guid = Column(String(256), primary_key=True) | |
141 | + # 任务外键 | |
142 | + task_guid = Column(String(256), ForeignKey('dmdms_task.guid')) | |
143 | + time = Column(DateTime) | |
144 | + message = Column(Text) | |
145 | + | |
146 | + | |
147 | +class Catalog(db.Model): | |
148 | + ''' | |
149 | + 目录表 | |
150 | + ''' | |
151 | + __tablename__ = 'dmdms_catalog' | |
152 | + guid = Column(String(256), primary_key=True) | |
153 | + database_guid = Column(String(256), ForeignKey('dmdms_database.guid')) | |
154 | + pguid = Column(String(256)) | |
155 | + path = Column(Text) | |
156 | + name = Column(String(256)) | |
157 | + sort = Column(Integer) | |
158 | + description = Column(Text) | |
159 | + relate_tables = relationship('Table', backref='relate_catalog', lazy='dynamic') | |
160 | + relate_tasks = relationship('Task', backref='relate_catalog', lazy='dynamic') | |
161 | + | |
162 | +class Database(db.Model): | |
163 | + ''' | |
164 | + 数据源表 | |
165 | + ''' | |
166 | + __tablename__ = 'dmdms_database' | |
167 | + guid = Column(String(256), primary_key=True) | |
168 | + name = Column(String(256)) | |
169 | + alias = Column(String(256)) | |
170 | + sqlalchemy_uri = Column(String(256)) | |
171 | + description = Column(Text) | |
172 | + # 唯一性约束,不能注册相同连接的库 | |
173 | + connectstr= Column(Text,unique=True) | |
174 | + | |
175 | + creator = Column(String(256)) | |
176 | + | |
177 | + create_time = Column(DateTime) | |
178 | + update_time = Column(DateTime) | |
179 | + | |
180 | + relate_catalogs = relationship('Catalog', backref='relate_database', lazy='dynamic') | |
181 | + relate_tables = relationship('Table', backref='relate_database', lazy='dynamic') | |
182 | + relate_tasks = relationship('Task', backref='relate_database', lazy='dynamic') | |
183 | + | |
184 | + | |
185 | + | |
186 | +class InsertingLayerName(db.Model): | |
187 | + ''' | |
188 | + 正在入库的数据 | |
189 | + ''' | |
190 | + __tablename__ = 'dmdms_iln' | |
191 | + guid = Column(String(256), primary_key=True) | |
192 | + task_guid = Column(String(256)) | |
193 | + name = Column(String(256)) | |
194 | + | ... | ... |
app/modules/__init__.py
0 → 100644
app/modules/catalog/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/1 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from flasgger import swag_from | |
8 | +from flask import Blueprint | |
9 | +from app.util import BlueprintApi | |
10 | +from . import catalog_create | |
11 | +from . import catalog_next | |
12 | +from . import catalog_tree | |
13 | +from . import catalog_delete | |
14 | +from . import catalog_edit | |
15 | +from . import catalog_real_tree | |
16 | + | |
17 | + | |
18 | +class DataManager(BlueprintApi): | |
19 | + | |
20 | + bp = Blueprint("Category", __name__, url_prefix="/API/Category") | |
21 | + | |
22 | + | |
23 | + @staticmethod | |
24 | + @bp.route('/Create', methods=['POST']) | |
25 | + @swag_from(catalog_create.Api.api_doc) | |
26 | + def catalog_create(): | |
27 | + """ | |
28 | + 创建目录 | |
29 | + """ | |
30 | + return catalog_create.Api().result | |
31 | + | |
32 | + @staticmethod | |
33 | + @bp.route('/Next', methods=['POST']) | |
34 | + @swag_from(catalog_next.Api.api_doc) | |
35 | + def catalog_next(): | |
36 | + """ | |
37 | + 下一级目录 | |
38 | + """ | |
39 | + return catalog_next.Api().result | |
40 | + | |
41 | + @staticmethod | |
42 | + @bp.route('/Tree', methods=['POST']) | |
43 | + @swag_from(catalog_tree.Api.api_doc) | |
44 | + def catalog_tree(): | |
45 | + """ | |
46 | + 目录树 | |
47 | + """ | |
48 | + return catalog_tree.Api().result | |
49 | + | |
50 | + | |
51 | + @staticmethod | |
52 | + @bp.route('/RealTree', methods=['POST']) | |
53 | + @swag_from(catalog_real_tree.Api.api_doc) | |
54 | + def catalog_real_tree(): | |
55 | + """ | |
56 | + 目录树 | |
57 | + """ | |
58 | + return catalog_real_tree.Api().result | |
59 | + | |
60 | + | |
61 | + @staticmethod | |
62 | + @bp.route('/Edit', methods=['POST']) | |
63 | + @swag_from(catalog_edit.Api.api_doc) | |
64 | + def catalog_edit(): | |
65 | + """ | |
66 | + 修改目录 | |
67 | + """ | |
68 | + return catalog_edit.Api().result | |
69 | + | |
70 | + @staticmethod | |
71 | + @bp.route('/Delete', methods=['POST']) | |
72 | + @swag_from(catalog_delete.Api.api_doc) | |
73 | + def catalog_delete(): | |
74 | + """ | |
75 | + 删除目录 | |
76 | + """ | |
77 | + return catalog_delete.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/catalog/catalog_create.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +import uuid | |
7 | +from app.models import * | |
8 | +from app.util.component.ApiTemplate import ApiTemplate | |
9 | +class Api(ApiTemplate): | |
10 | + api_name = "创建目录" | |
11 | + def para_check(self): | |
12 | + if not self.para.get("database_guid"): | |
13 | + raise Exception("缺乏database_guid参数") | |
14 | + if not self.para.get("pguid"): | |
15 | + raise Exception("缺乏pguid参数") | |
16 | + if not self.para.get("name"): | |
17 | + raise Exception("缺乏name参数") | |
18 | + | |
19 | + def process(self): | |
20 | + | |
21 | + # 返回结果 | |
22 | + res = {} | |
23 | + res["result"] = False | |
24 | + try: | |
25 | + # 业务逻辑 | |
26 | + | |
27 | + if not Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none(): | |
28 | + res["msg"]="数据库不存在!" | |
29 | + return res | |
30 | + | |
31 | + | |
32 | + # 可以创建已有数据目录的子目录 | |
33 | + # if Table.query.filter_by(catalog_guid=self.para.get("pguid")).all(): | |
34 | + # raise Exception("父目录挂载了数据,不能创建子目录") | |
35 | + | |
36 | + if Catalog.query.filter_by(name=self.para.get("name"),pguid=self.para.get("pguid"),database_guid=self.para.get("database_guid")).one_or_none(): | |
37 | + res["msg"]="目录已经存在!" | |
38 | + return res | |
39 | + | |
40 | + | |
41 | + # if Catalog.query.filter_by(pguid="0",database_guid=self.para.get("database_guid")).one_or_none() and self.para.get("pguid").__eq__("0"): | |
42 | + # raise Exception("只能有一个根目录!") | |
43 | + | |
44 | + guid = uuid.uuid1().__str__() | |
45 | + path = guid | |
46 | + | |
47 | + # 获得目录的全路径 | |
48 | + pguid = self.para.get("pguid") | |
49 | + count = 0 | |
50 | + while pguid !="0" and count<100: | |
51 | + count+=1 | |
52 | + path = pguid+":"+path | |
53 | + p_catalog = Catalog.query.filter_by(guid=pguid).one_or_none() | |
54 | + pguid = p_catalog.pguid | |
55 | + if count==100: | |
56 | + raise Exception("目录结构出现问题!") | |
57 | + path = "0" + ":" + path | |
58 | + | |
59 | + sort = Catalog.query.filter_by(pguid=self.para.get("pguid")).count() | |
60 | + | |
61 | + catalog = Catalog(guid=guid,pguid=self.para.get("pguid"),name=self.para.get("name"), | |
62 | + sort=sort,description=self.para.get("description"), | |
63 | + database_guid=self.para.get("database_guid"),path=path) | |
64 | + db.session.add(catalog) | |
65 | + db.session.commit() | |
66 | + | |
67 | + | |
68 | + res["msg"] = "目录创建成功!" | |
69 | + res["data"] = guid | |
70 | + res["result"] = True | |
71 | + except Exception as e: | |
72 | + db.session.rollback() | |
73 | + raise e | |
74 | + return res | |
75 | + | |
76 | + api_doc={ | |
77 | + | |
78 | + "tags":["目录接口"], | |
79 | + "parameters":[ | |
80 | + {"name": "name", | |
81 | + "in": "formData", | |
82 | + "type": "string", | |
83 | + "description":"目录名"}, | |
84 | + {"name": "pguid", | |
85 | + "in": "formData", | |
86 | + "type": "string","description":"父目录guid,创建根目录时为0"}, | |
87 | + {"name": "database_guid", | |
88 | + "in": "formData", | |
89 | + "type": "string","description":"数据库guid"} | |
90 | + | |
91 | + ], | |
92 | + "responses":{ | |
93 | + 200:{ | |
94 | + "schema":{ | |
95 | + "properties":{ | |
96 | + } | |
97 | + } | |
98 | + } | |
99 | + } | |
100 | + } | |
\ No newline at end of file | ... | ... |
app/modules/catalog/catalog_delete.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | + | |
8 | +from app.models import * | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +class Api(ApiTemplate): | |
11 | + api_name = "删除目录" | |
12 | + def process(self): | |
13 | + | |
14 | + | |
15 | + # 返回结果 | |
16 | + res = {} | |
17 | + try: | |
18 | + # 业务逻辑 | |
19 | + | |
20 | + | |
21 | + # 啥情况都能删 | |
22 | + # if Table.query.filter_by(catalog_guid=self.para.get("guid")).all(): | |
23 | + # raise Exception("目录挂载了数据,不可删除,可将数据移出目录后删除!") | |
24 | + # if Catalog.query.filter_by(pguid=self.para.get("guid")).all(): | |
25 | + # raise Exception("目录非子目录,不可删除,请先将子目录删除!") | |
26 | + | |
27 | + catalog_guid = self.para.get("guid") | |
28 | + | |
29 | + catalog = Catalog.query.filter_by(guid=catalog_guid).one_or_none() | |
30 | + if not catalog: | |
31 | + res["msg"]="目录不存在!" | |
32 | + return res | |
33 | + | |
34 | + else: | |
35 | + | |
36 | + # 转移目录下的数据 | |
37 | + | |
38 | + # # 删除根节点 | |
39 | + # if catalog.pguid.__eq__("0"): | |
40 | + # database_guid = catalog.database_guid | |
41 | + # Table.query.filter_by(database_guid=database_guid).update({"catalog_guid": None}) | |
42 | + # catalogs = Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all() | |
43 | + # for cata in catalogs: | |
44 | + # db.session.delete(cata) | |
45 | + # | |
46 | + # # 获取所有子目录: | |
47 | + # else: | |
48 | + | |
49 | + pguid = catalog.pguid | |
50 | + # 所有目录 | |
51 | + catalogs = Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all() | |
52 | + for cata in catalogs: | |
53 | + if pguid.__eq__("0"): | |
54 | + Table.query.filter_by(catalog_guid=cata.guid).update({"catalog_guid": None}) | |
55 | + db.session.delete(cata) | |
56 | + else: | |
57 | + Table.query.filter_by(catalog_guid=cata.guid).update({"catalog_guid": pguid}) | |
58 | + db.session.delete(cata) | |
59 | + | |
60 | + db.session.commit() | |
61 | + res["msg"] = "目录删除成功!" | |
62 | + res["result"] = True | |
63 | + except Exception as e: | |
64 | + db.session.rollback() | |
65 | + raise e | |
66 | + return res | |
67 | + | |
68 | + | |
69 | + api_doc = { | |
70 | + "tags": ["目录接口"], | |
71 | + "parameters": [ | |
72 | + {"name": "guid", | |
73 | + "in": "formData", | |
74 | + "type": "string", | |
75 | + "description": "目录guid", "required": "true"}, | |
76 | + ], | |
77 | + "responses": { | |
78 | + 200: { | |
79 | + "schema": { | |
80 | + "properties": { | |
81 | + } | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | + } | ... | ... |
app/modules/catalog/catalog_edit.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import * | |
7 | + | |
8 | +from app.util.component.ApiTemplate import ApiTemplate | |
9 | +class Api(ApiTemplate): | |
10 | + api_name = "修改目录" | |
11 | + def process(self): | |
12 | + | |
13 | + | |
14 | + # 返回结果 | |
15 | + res = {} | |
16 | + try: | |
17 | + # 业务逻辑 | |
18 | + if not Catalog.query.filter_by(guid=self.para.get("guid")).one_or_none(): | |
19 | + res["msg"]="目录不存在!" | |
20 | + res["result"]=False | |
21 | + return res | |
22 | + else: | |
23 | + if self.para.get("name"): | |
24 | + Catalog.query.filter_by(guid=self.para.get("guid")).update({"name":self.para.get("name")}) | |
25 | + if self.para.__contains__("description"): | |
26 | + Catalog.query.filter_by(guid=self.para.get("guid")).update({"description":self.para.get("description")}) | |
27 | + db.session.commit() | |
28 | + res["msg"] = "目录修改成功!" | |
29 | + res["result"] = True | |
30 | + except Exception as e: | |
31 | + db.session.rollback() | |
32 | + raise e | |
33 | + return res | |
34 | + | |
35 | + | |
36 | + api_doc = { | |
37 | + "tags": ["目录接口"], | |
38 | + "parameters": [ | |
39 | + {"name": "guid", | |
40 | + "in": "formData", | |
41 | + "type": "string", | |
42 | + "description": "目录guid", "required": "true"}, | |
43 | + {"name": "name", | |
44 | + "in": "formData", | |
45 | + "type": "string", | |
46 | + "description": "目录名", "required": "true"} | |
47 | + ], | |
48 | + "responses": { | |
49 | + 200: { | |
50 | + "schema": { | |
51 | + "properties": { | |
52 | + } | |
53 | + } | |
54 | + } | |
55 | + } | |
56 | + } | ... | ... |
app/modules/catalog/catalog_next.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from app.models import * | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +from app.util.component.ModelVisitor import ModelVisitor | |
11 | +class Api(ApiTemplate): | |
12 | + api_name = "下一级目录" | |
13 | + def process(self): | |
14 | + | |
15 | + # 返回结果 | |
16 | + res = {} | |
17 | + try: | |
18 | + # 业务逻辑 | |
19 | + | |
20 | + res["data"] = [] | |
21 | + catalogs = Catalog.query.filter_by(pguid=self.para.get("catalog_guid"),database_guid=self.para.get("database_guid")).all() | |
22 | + for cata in catalogs: | |
23 | + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()] | |
24 | + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count() | |
25 | + database_alias = cata.relate_database.alias | |
26 | + cata_json = ModelVisitor.object_to_json(cata) | |
27 | + cata_json["table_count"]=table_count | |
28 | + cata_json["database_alias"] = database_alias | |
29 | + | |
30 | + res["data"].append(cata_json) | |
31 | + res["result"] = True | |
32 | + except Exception as e: | |
33 | + raise e | |
34 | + return res | |
35 | + | |
36 | + api_doc={ | |
37 | + | |
38 | + "tags":["目录接口"], | |
39 | + "parameters":[ | |
40 | + {"name": "catalog_guid", | |
41 | + "in": "formData", | |
42 | + "type": "string", | |
43 | + "description":"目录guid","required": "true"}, | |
44 | + {"name": "database_guid", | |
45 | + "in": "formData", | |
46 | + "type": "string", | |
47 | + "description": "数据库guid", "required": "true"}, | |
48 | + | |
49 | + ], | |
50 | + "responses":{ | |
51 | + 200:{ | |
52 | + "schema":{ | |
53 | + "properties":{ | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + } | |
58 | + } | |
\ No newline at end of file | ... | ... |
app/modules/catalog/catalog_real_tree.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from app.models import * | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +class Api(ApiTemplate): | |
11 | + api_name = "目录树" | |
12 | + def process(self): | |
13 | + | |
14 | + # 返回结果 | |
15 | + res = {} | |
16 | + try: | |
17 | + # 业务逻辑 | |
18 | + database_guid = self.para.get("database_guid") | |
19 | + catalogs = Catalog.query.filter_by(database_guid=database_guid).all() | |
20 | + | |
21 | + tree_origin = [] | |
22 | + for cata in catalogs: | |
23 | + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()] | |
24 | + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count() | |
25 | + | |
26 | + # cata_json = object_to_json(cata) | |
27 | + cata_json ={} | |
28 | + cata_json["database_guid"]=cata.database_guid | |
29 | + cata_json["description"] = cata.description | |
30 | + cata_json["guid"] = cata.guid | |
31 | + cata_json["name"] = cata.name | |
32 | + cata_json["path"] = cata.path | |
33 | + cata_json["pguid"] = cata.pguid | |
34 | + cata_json["sort"] = cata.sort | |
35 | + cata_json["table_count"]=table_count | |
36 | + cata_json["children"] = [] | |
37 | + tree_origin.append(cata_json) | |
38 | + | |
39 | + for cata in tree_origin: | |
40 | + cata_pguid = cata["pguid"] | |
41 | + if not cata_pguid=="0": | |
42 | + for c in tree_origin: | |
43 | + if c["guid"].__eq__(cata_pguid): | |
44 | + c["children"].append(cata) | |
45 | + | |
46 | + res["data"] = [cata for cata in tree_origin if cata["pguid"].__eq__("0")] | |
47 | + res["result"] = True | |
48 | + except Exception as e: | |
49 | + raise e | |
50 | + return res | |
51 | + | |
52 | + api_doc={ | |
53 | + | |
54 | + "tags":["目录接口"], | |
55 | + "parameters":[ | |
56 | + {"name": "database_guid", | |
57 | + "in": "formData", | |
58 | + "type": "string", | |
59 | + "description": "数据库guid", "required": "true"}, | |
60 | + | |
61 | + ], | |
62 | + "responses":{ | |
63 | + 200:{ | |
64 | + "schema":{ | |
65 | + "properties":{ | |
66 | + } | |
67 | + } | |
68 | + } | |
69 | + } | |
70 | + } | |
\ No newline at end of file | ... | ... |
app/modules/catalog/catalog_tree.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from app.models import * | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +class Api(ApiTemplate): | |
11 | + api_name = "目录" | |
12 | + def process(self): | |
13 | + | |
14 | + # 返回结果 | |
15 | + res = {} | |
16 | + try: | |
17 | + # 业务逻辑 | |
18 | + database_guid = self.para.get("database_guid") | |
19 | + catalogs = Catalog.query.filter_by(database_guid=database_guid).all() | |
20 | + res["data"]=[] | |
21 | + for cata in catalogs: | |
22 | + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + cata.guid + "%")).all()] | |
23 | + table_count = Table.query.filter(Table.catalog_guid.in_(catalog_guids)).count() | |
24 | + | |
25 | + # cata_json = object_to_json(cata) | |
26 | + cata_json ={} | |
27 | + cata_json["database_guid"]=cata.database_guid | |
28 | + cata_json["description"] = cata.description | |
29 | + cata_json["guid"] = cata.guid | |
30 | + cata_json["name"] = cata.name | |
31 | + cata_json["path"] = cata.path | |
32 | + cata_json["pguid"] = cata.pguid | |
33 | + cata_json["sort"] = cata.sort | |
34 | + cata_json["table_count"]=table_count | |
35 | + res["data"].append(cata_json) | |
36 | + | |
37 | + res["result"] = True | |
38 | + | |
39 | + except Exception as e: | |
40 | + raise e | |
41 | + return res | |
42 | + | |
43 | + api_doc={ | |
44 | + | |
45 | + "tags":["目录接口"], | |
46 | + "parameters":[ | |
47 | + {"name": "database_guid", | |
48 | + "in": "formData", | |
49 | + "type": "string", | |
50 | + "description": "数据库guid", "required": "true"}, | |
51 | + | |
52 | + ], | |
53 | + "responses":{ | |
54 | + 200:{ | |
55 | + "schema":{ | |
56 | + "properties":{ | |
57 | + } | |
58 | + } | |
59 | + } | |
60 | + } | |
61 | + } | |
\ No newline at end of file | ... | ... |
app/modules/database/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | + | |
8 | +from flasgger import swag_from | |
9 | +from flask import Blueprint | |
10 | +from app.util import BlueprintApi | |
11 | + | |
12 | +from . import database_register | |
13 | +from . import database_test | |
14 | +from . import database_list | |
15 | +from . import database_delete | |
16 | +from . import database_edit | |
17 | +from . import database_alias_check | |
18 | +from . import database_connect_test | |
19 | +from . import database_info | |
20 | + | |
21 | +class DataManager(BlueprintApi): | |
22 | + | |
23 | + bp = Blueprint("Database", __name__, url_prefix="/API/Database") | |
24 | + | |
25 | + | |
26 | + @staticmethod | |
27 | + @bp.route('/Register', methods=['POST']) | |
28 | + @swag_from(database_register.Api.api_doc) | |
29 | + def api_database_register(): | |
30 | + """ | |
31 | + 数据源注册 | |
32 | + """ | |
33 | + return database_register.Api().result | |
34 | + | |
35 | + @staticmethod | |
36 | + @bp.route('/List', methods=['POST']) | |
37 | + @swag_from(database_list.Api.api_doc) | |
38 | + def api_database_list(): | |
39 | + """ | |
40 | + 数据源列表 | |
41 | + """ | |
42 | + return database_list.Api().result | |
43 | + | |
44 | + @staticmethod | |
45 | + @bp.route('/Delete', methods=['POST']) | |
46 | + @swag_from(database_delete.Api.api_doc) | |
47 | + def api_database_delete(): | |
48 | + """ | |
49 | + 数据源注销 | |
50 | + """ | |
51 | + return database_delete.Api().result | |
52 | + | |
53 | + @staticmethod | |
54 | + @bp.route('/Edit', methods=['POST']) | |
55 | + @swag_from(database_edit.Api.api_doc) | |
56 | + def database_edit(): | |
57 | + """ | |
58 | + 修改数据源 | |
59 | + """ | |
60 | + return database_edit.Api().result | |
61 | + | |
62 | + @staticmethod | |
63 | + @bp.route('/Test', methods=['POST']) | |
64 | + @swag_from(database_test.Api.api_doc) | |
65 | + def api_database_test(): | |
66 | + """ | |
67 | + 数据源测试 | |
68 | + """ | |
69 | + return database_test.Api().result | |
70 | + | |
71 | + @staticmethod | |
72 | + @bp.route('/CheckAlias', methods=['POST']) | |
73 | + @swag_from(database_alias_check.Api.api_doc) | |
74 | + def api_database_alias_check(): | |
75 | + """ | |
76 | + 数据源别名测试 | |
77 | + """ | |
78 | + return database_alias_check.Api().result | |
79 | + | |
80 | + @staticmethod | |
81 | + @bp.route('/CheckConnect', methods=['POST']) | |
82 | + @swag_from(database_connect_test.Api.api_doc) | |
83 | + def api_database_connect_test(): | |
84 | + """ | |
85 | + 数据源连接测试 | |
86 | + """ | |
87 | + return database_connect_test.Api().result | |
88 | + | |
89 | + | |
90 | + @staticmethod | |
91 | + @bp.route('/Info', methods=['POST']) | |
92 | + @swag_from(database_info.Api.api_doc) | |
93 | + def api_database_info(): | |
94 | + """ | |
95 | + 数据源信息 | |
96 | + """ | |
97 | + return database_info.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/database/database_alias_check.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import Database,db | |
7 | + | |
8 | + | |
9 | + | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | +class Api(ApiTemplate): | |
12 | + api_name = "测试数据库别名" | |
13 | + | |
14 | + def process(self): | |
15 | + res ={} | |
16 | + try: | |
17 | + database = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none() | |
18 | + if database: | |
19 | + res["msg"]="数据库重名!" | |
20 | + res["result"]=False | |
21 | + else: | |
22 | + res["result"] = True | |
23 | + except Exception as e: | |
24 | + raise e | |
25 | + return res | |
26 | + | |
27 | + api_doc={ | |
28 | + "tags":["数据库接口"], | |
29 | + "parameters":[ | |
30 | + {"name": "alias", | |
31 | + "in": "formData", | |
32 | + "type": "string","description":"数据库别名","required": "true"}, | |
33 | + | |
34 | + ], | |
35 | + "responses":{ | |
36 | + 200:{ | |
37 | + "schema":{ | |
38 | + "properties":{ | |
39 | + } | |
40 | + } | |
41 | + } | |
42 | + } | |
43 | + } | |
\ No newline at end of file | ... | ... |
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/6/22 | |
4 | +# email: nheweijun@sina.com | |
5 | +from contextlib import closing | |
6 | + | |
7 | +from sqlalchemy import create_engine | |
8 | + | |
9 | +from app.models import DES,Database | |
10 | + | |
11 | + | |
12 | +from app.util.component.ApiTemplate import ApiTemplate | |
13 | +class Api(ApiTemplate): | |
14 | + api_name = "测试数据库连接" | |
15 | + def process(self): | |
16 | + res = {} | |
17 | + try: | |
18 | + guid = self.para.get("guid") | |
19 | + database = Database.query.filter_by(guid=guid) | |
20 | + dbase:Database = database.one_or_none() | |
21 | + engine = create_engine(DES.decode(dbase.sqlalchemy_uri), connect_args={'connect_timeout': 1}) | |
22 | + with closing(engine.connect()): | |
23 | + pass | |
24 | + res["result"]=True | |
25 | + res["msg"] = "测试连接成功!" | |
26 | + except: | |
27 | + raise Exception("测试连接失败!") | |
28 | + return res | |
29 | + | |
30 | + api_doc={ | |
31 | + "tags":["数据库接口"], | |
32 | + "parameters":[ | |
33 | + | |
34 | + {"name": "guid", | |
35 | + "in": "formData", | |
36 | + "type": "string", "required": "true"}, | |
37 | + | |
38 | + | |
39 | + ], | |
40 | + "responses":{ | |
41 | + 200:{ | |
42 | + "schema":{ | |
43 | + "properties":{ | |
44 | + } | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
\ No newline at end of file | ... | ... |
app/modules/database/database_delete.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from app.models import Database,db | |
8 | + | |
9 | + | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | +class Api(ApiTemplate): | |
12 | + api_name = "删除数据库" | |
13 | + def process(self): | |
14 | + res ={} | |
15 | + try: | |
16 | + | |
17 | + database = db.session.query(Database).filter_by(guid=self.para.get("guid")).one_or_none() | |
18 | + if database: | |
19 | + db.session.delete(database) | |
20 | + db.session.commit() | |
21 | + res["msg"] = "数据库删除成功!" | |
22 | + res["result"] = True | |
23 | + else: | |
24 | + res["msg"] = "数据库不存在!" | |
25 | + res["result"] = False | |
26 | + except Exception as e: | |
27 | + db.session.rollback() | |
28 | + raise e | |
29 | + return res | |
30 | + | |
31 | + | |
32 | + | |
33 | + api_doc={ | |
34 | + "tags":["数据库接口"], | |
35 | + "parameters":[ | |
36 | + {"name": "guid", | |
37 | + "in": "formData", | |
38 | + "type": "string","description":"数据库guid","required": "true"}, | |
39 | + | |
40 | + ], | |
41 | + "responses":{ | |
42 | + 200:{ | |
43 | + "schema":{ | |
44 | + "properties":{ | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | + } | |
\ No newline at end of file | ... | ... |
app/modules/database/database_edit.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import Database,db | |
7 | +from contextlib import closing | |
8 | + | |
9 | +from sqlalchemy import create_engine | |
10 | + | |
11 | +from app.models import DES | |
12 | +import datetime | |
13 | +from . import database_alias_check | |
14 | + | |
15 | +from app.util.component.ApiTemplate import ApiTemplate | |
16 | + | |
17 | +from app.util.component.PGUtil import PGUtil | |
18 | +class Api(ApiTemplate): | |
19 | + api_name = "修改数据库" | |
20 | + def process(self): | |
21 | + | |
22 | + res = {} | |
23 | + res["result"] = False | |
24 | + try: | |
25 | + guid = self.para.get("guid") | |
26 | + alias = self.para.get("alias") | |
27 | + | |
28 | + passwd = self.para.get("passwd") | |
29 | + database = Database.query.filter_by(guid=guid) | |
30 | + dbase:Database = database.one_or_none() | |
31 | + | |
32 | + update_dict={} | |
33 | + | |
34 | + if not dbase: | |
35 | + res["msg"] = "数据库不存在!" | |
36 | + return res | |
37 | + if not alias and not passwd: | |
38 | + res["msg"] = "请填写参数!" | |
39 | + return res | |
40 | + | |
41 | + | |
42 | + if alias: | |
43 | + # 检测一波别名 | |
44 | + check_alias = database_alias_check.Api().result | |
45 | + if not check_alias["result"]: | |
46 | + return check_alias | |
47 | + update_dict["alias"]=alias | |
48 | + | |
49 | + if passwd: | |
50 | + try: | |
51 | + user, password, host, port, database = PGUtil.get_info_from_sqlachemy_uri(DES.decode(dbase.sqlalchemy_uri)) | |
52 | + | |
53 | + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user, passwd,host, | |
54 | + port, database) | |
55 | + | |
56 | + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host, port, database, user, | |
57 | + passwd) | |
58 | + engine = create_engine(sqlalchemy_uri, connect_args={'connect_timeout': 2}) | |
59 | + with closing(engine.connect()): | |
60 | + pass | |
61 | + | |
62 | + except : | |
63 | + res["msg"] = "密码错误!" | |
64 | + return res | |
65 | + | |
66 | + update_dict["sqlalchemy_uri"]= DES.encode(sqlalchemy_uri) | |
67 | + update_dict["connectstr"] = DES.encode(connectsrt) | |
68 | + | |
69 | + if update_dict: | |
70 | + update_dict["update_time"]=datetime.datetime.now() | |
71 | + Database.query.filter_by(guid=guid).update(update_dict) | |
72 | + db.session.commit() | |
73 | + res["result"] = True | |
74 | + res["msg"] = "数据修改成功!" | |
75 | + except Exception as e: | |
76 | + db.session.rollback() | |
77 | + raise e | |
78 | + return res | |
79 | + | |
80 | + | |
81 | + | |
82 | + api_doc={ | |
83 | + "tags":["数据库接口"], | |
84 | + "parameters":[ | |
85 | + {"name": "guid", | |
86 | + "in": "formData", | |
87 | + "type": "string"}, | |
88 | + {"name": "alias", | |
89 | + "in": "formData", | |
90 | + "type": "string","description":"数据库别名"}, | |
91 | + {"name": "passwd", | |
92 | + "in": "formData", | |
93 | + "type": "string", "description": "数据库密码"} | |
94 | + ], | |
95 | + "responses":{ | |
96 | + 200:{ | |
97 | + "schema":{ | |
98 | + "properties":{ | |
99 | + } | |
100 | + } | |
101 | + } | |
102 | + } | |
103 | + } | |
\ No newline at end of file | ... | ... |
app/modules/database/database_info.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | +from app.models import Database,DES | |
6 | +from contextlib import closing | |
7 | +from sqlalchemy import create_engine,or_ | |
8 | + | |
9 | +from app.models import Database,Catalog,Table | |
10 | + | |
11 | + | |
12 | +from app.util.component.ApiTemplate import ApiTemplate | |
13 | + | |
14 | + | |
15 | +class Api(ApiTemplate): | |
16 | + api_name = "获取数据库信息" | |
17 | + def process(self): | |
18 | + # 返回结果 | |
19 | + res = {} | |
20 | + try: | |
21 | + # 业务逻辑 | |
22 | + database_guid = self.para.get("guid") | |
23 | + table_type = self.para.get("table_type") | |
24 | + catalogs = Catalog.query.filter_by(database_guid=database_guid).all() | |
25 | + | |
26 | + tree_origin = [] | |
27 | + for cata in catalogs: | |
28 | + cata_json = {} | |
29 | + cata_json["type"] = "catalog" | |
30 | + cata_json["guid"] = cata.guid | |
31 | + cata_json["name"] = cata.name | |
32 | + cata_json["pguid"] = cata.pguid | |
33 | + cata_json["children"] = [] | |
34 | + tree_origin.append(cata_json) | |
35 | + | |
36 | + #挂接表 | |
37 | + tables =Table.query.filter_by(database_guid=database_guid,catalog_guid=cata.guid).filter(Table.table_type != 0) | |
38 | + if table_type: | |
39 | + tables = tables.filter_by(table_type=table_type) | |
40 | + tables = tables.order_by(Table.name).all() | |
41 | + | |
42 | + for table in tables: | |
43 | + table_json= {} | |
44 | + table_json["name"]=table.name | |
45 | + table_json["alias"] = table.alias | |
46 | + table_json["guid"] = table.guid | |
47 | + table_json["type"] = "table" | |
48 | + table_json["table_type"] = table.table_type | |
49 | + cata_json["children"].append(table_json) | |
50 | + | |
51 | + for cata in tree_origin: | |
52 | + cata_pguid = cata["pguid"] | |
53 | + if not cata_pguid == "0": | |
54 | + for c in tree_origin: | |
55 | + if c["guid"].__eq__(cata_pguid): | |
56 | + c["children"].append(cata) | |
57 | + res["data"] = [cata for cata in tree_origin if cata["pguid"].__eq__("0")] | |
58 | + | |
59 | + # 挂接表 | |
60 | + tables = Table.query.filter_by(database_guid=database_guid, catalog_guid=None).filter( | |
61 | + Table.table_type != 0) | |
62 | + if table_type: | |
63 | + tables = tables.filter_by(table_type=table_type) | |
64 | + tables = tables.order_by(Table.name).all() | |
65 | + | |
66 | + for table in tables: | |
67 | + table_json = {} | |
68 | + table_json["name"] = table.name | |
69 | + table_json["alias"] = table.alias | |
70 | + table_json["guid"] = table.guid | |
71 | + table_json["type"] = "table" | |
72 | + table_json["table_type"] = table.table_type | |
73 | + res["data"].append(table_json) | |
74 | + | |
75 | + res["result"] = True | |
76 | + except Exception as e: | |
77 | + raise e | |
78 | + return res | |
79 | + | |
80 | + api_doc={ | |
81 | + "tags":["数据库接口"], | |
82 | + "parameters":[ | |
83 | + {"name": "guid", | |
84 | + "in": "formData", | |
85 | + "type": "string", "description": "数据库guid"}, | |
86 | + {"name": "table_type", | |
87 | + "in": "formData", | |
88 | + "type": "int", "description": "表类型","enum":[1,2,3]} | |
89 | + ], | |
90 | + "responses":{ | |
91 | + 200:{ | |
92 | + "schema":{ | |
93 | + "properties":{ | |
94 | + } | |
95 | + } | |
96 | + } | |
97 | + } | |
98 | + } | ... | ... |
app/modules/database/database_list.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | +from app.models import Database,DES | |
6 | +from contextlib import closing | |
7 | +from sqlalchemy import create_engine | |
8 | + | |
9 | +from app.models import Database | |
10 | + | |
11 | + | |
12 | +from app.util.component.ApiTemplate import ApiTemplate | |
13 | +from app.util.component.ModelVisitor import ModelVisitor | |
14 | +class Api(ApiTemplate): | |
15 | + api_name = "获取数据库列表" | |
16 | + def process(self): | |
17 | + res = {} | |
18 | + res["data"]={} | |
19 | + try: | |
20 | + page_index = int(self.para.get("pageIndex", "0")) | |
21 | + page_size = int(self.para.get("pageSize", "10")) | |
22 | + alias = self.para.get("alias") | |
23 | + database_guid = self.para.get("guid") | |
24 | + database = Database.query.order_by(Database.create_time.desc()) | |
25 | + | |
26 | + if alias: | |
27 | + database = database.filter(Database.alias.like("%" + alias + "%")) | |
28 | + if database_guid: | |
29 | + database = database.filter_by(guid = database_guid) | |
30 | + | |
31 | + res["data"]["count"] = database.count() | |
32 | + | |
33 | + database = database.limit(page_size).offset(page_index * page_size).all() | |
34 | + res["data"]["list"] =[] | |
35 | + for datab in database: | |
36 | + # 测试连接 | |
37 | + try: | |
38 | + engine = create_engine(DES.decode(datab.sqlalchemy_uri), connect_args={'connect_timeout': 1}) | |
39 | + with closing(engine.connect()): | |
40 | + pass | |
41 | + available=1 | |
42 | + except: | |
43 | + available=-1 | |
44 | + table_count = datab.relate_tables.count() | |
45 | + datab_json = ModelVisitor.database_to_json(datab) | |
46 | + datab_json["table_count"] = table_count | |
47 | + datab_json["available"] = available | |
48 | + res["data"]["list"].append(datab_json) | |
49 | + | |
50 | + # 可用非可用排序 | |
51 | + sorted_list = [dat for dat in res["data"]["list"] if dat["available"]==1] | |
52 | + sorted_list.extend([dat for dat in res["data"]["list"] if dat["available"] == -1]) | |
53 | + res["data"]["list"] = sorted_list | |
54 | + res["result"]=True | |
55 | + except Exception as e: | |
56 | + raise e | |
57 | + return res | |
58 | + | |
59 | + | |
60 | + | |
61 | + api_doc={ | |
62 | + "tags":["数据库接口"], | |
63 | + "parameters":[ | |
64 | + {"name": "page_index", | |
65 | + "in": "formData", | |
66 | + "type": "int", | |
67 | + "default": "0"}, | |
68 | + {"name": "page_size", | |
69 | + "in": "formData", | |
70 | + "type": "int", | |
71 | + "default": "10"}, | |
72 | + {"name": "alias", | |
73 | + "in": "formData", | |
74 | + "type": "string","description":"数据库别名"}, | |
75 | + {"name": "guid", | |
76 | + "in": "formData", | |
77 | + "type": "string", "description": "数据库guid"} | |
78 | + ], | |
79 | + "responses":{ | |
80 | + 200:{ | |
81 | + "schema":{ | |
82 | + "properties":{ | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + } | |
87 | + } | ... | ... |
app/modules/database/database_register.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/9 | |
4 | +#email: nheweijun@sina.com | |
5 | +import datetime | |
6 | +from app.models import Database,db,DES,Table,Columns,TableVacuate | |
7 | +# from app.util import get_db_session,get_info_from_sqlachemy_uri | |
8 | +import uuid | |
9 | +from . import database_test | |
10 | +# from app.util import open_pg_data_source | |
11 | +from osgeo.ogr import DataSource,Layer,FeatureDefn,FieldDefn,Feature | |
12 | +from sqlalchemy.orm import Session | |
13 | +from sqlalchemy import create_engine | |
14 | +from sqlalchemy.orm import sessionmaker | |
15 | +from app.util.component.ApiTemplate import ApiTemplate | |
16 | +from app.util.component.PGUtil import PGUtil | |
17 | +from app.util.component.GeometryAdapter import GeometryAdapter | |
18 | +class Api(ApiTemplate): | |
19 | + api_name = "数据库注册" | |
20 | + def process(self): | |
21 | + res ={} | |
22 | + res["result"] = False | |
23 | + try: | |
24 | + host = self.para.get("host") | |
25 | + port = self.para.get("port") | |
26 | + user = self.para.get("user") | |
27 | + passwd = self.para.get("passwd") | |
28 | + database = self.para.get("database") | |
29 | + encryption = int(self.para.get("encryption","0")) | |
30 | + if encryption: | |
31 | + passwd = DES.decode(passwd) | |
32 | + | |
33 | + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user,passwd,host,port,database) | |
34 | + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host,port,database,user,passwd) | |
35 | + | |
36 | + #测试连接 | |
37 | + test = database_test.Api().result | |
38 | + | |
39 | + if not test["result"]: | |
40 | + return test | |
41 | + | |
42 | + #判断数据库是否存在 | |
43 | + database = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none() | |
44 | + | |
45 | + #真实的数据库 | |
46 | + real_database = db.session.query(Database).filter_by(connectstr=DES.encode(connectsrt)).all() | |
47 | + | |
48 | + if database: | |
49 | + res["msg"] = "数据库已存在,请修改别名!" | |
50 | + return res | |
51 | + | |
52 | + elif real_database: | |
53 | + res["msg"] = "数据库连接已存在,请修改数据库连接!" | |
54 | + return res | |
55 | + elif not self.check_space(sqlalchemy_uri): | |
56 | + res["msg"] = "数据不是空间数据库!" | |
57 | + return res | |
58 | + | |
59 | + else: | |
60 | + this_time = datetime.datetime.now() | |
61 | + database_guid = uuid.uuid1().__str__() | |
62 | + | |
63 | + db_tuple = PGUtil.get_info_from_sqlachemy_uri(sqlalchemy_uri) | |
64 | + | |
65 | + database = Database(guid= database_guid, | |
66 | + name = db_tuple[4], | |
67 | + alias=self.para.get("alias"), | |
68 | + sqlalchemy_uri=DES.encode(sqlalchemy_uri), | |
69 | + description=self.para.get("description"), | |
70 | + creator=self.para.get("creator"), | |
71 | + create_time=this_time, | |
72 | + update_time=this_time, | |
73 | + connectstr=DES.encode(connectsrt)) | |
74 | + db.session.add(database) | |
75 | + | |
76 | + # 将该库中的数据都注册进来 | |
77 | + self.register_table(database) | |
78 | + db.session.commit() | |
79 | + res["msg"] = "注册成功!" | |
80 | + res["result"]=True | |
81 | + res["data"] = database_guid | |
82 | + except Exception as e: | |
83 | + db.session.rollback() | |
84 | + raise e | |
85 | + return res | |
86 | + | |
87 | + | |
88 | + def register_table(self,database): | |
89 | + this_time = datetime.datetime.now() | |
90 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri)) | |
91 | + | |
92 | + # 注册空间表 | |
93 | + spatial_table_name,tables = self.register_spatial_table(pg_ds, database, this_time) | |
94 | + | |
95 | + #注册抽稀表 | |
96 | + self.regiser_vacuate_talbe(pg_ds,tables) | |
97 | + | |
98 | + #注册普通表 | |
99 | + self.register_common_table(this_time,database,spatial_table_name) | |
100 | + | |
101 | + pg_ds.Destroy() | |
102 | + | |
103 | + | |
104 | + def register_spatial_table(self,pg_ds,database,this_time): | |
105 | + spatial_table_name =[] | |
106 | + tables=[] | |
107 | + for i in range(pg_ds.GetLayerCount()): | |
108 | + layer: Layer = pg_ds.GetLayer(i) | |
109 | + l_name = layer.GetName() | |
110 | + # 只注册public的空间表,其他表空间的表名会有. | |
111 | + if layer.GetName().__contains__("."): | |
112 | + continue | |
113 | + | |
114 | + # 不注册抽稀表 | |
115 | + if layer.GetName().__contains__("_vacuate_"): | |
116 | + spatial_table_name.append(layer.GetName()) | |
117 | + continue | |
118 | + | |
119 | + # 范围统计和数量统计以100w为界限 | |
120 | + query_count_layer: Layer = pg_ds.ExecuteSQL( | |
121 | + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format(l_name)) | |
122 | + feature_count = query_count_layer.GetFeature(0).GetField("ec") | |
123 | + # 要素少于100w可以精确统计 | |
124 | + if feature_count < 1000000: | |
125 | + feature_count = layer.GetFeatureCount() | |
126 | + ext = layer.GetExtent() | |
127 | + else: | |
128 | + query_ext_layer: Layer = pg_ds.ExecuteSQL( | |
129 | + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name, | |
130 | + layer.GetGeometryColumn())) | |
131 | + ext = query_ext_layer.GetExtent() | |
132 | + if ext[0] < 360: | |
133 | + ext = [round(e, 6) for e in ext] | |
134 | + else: | |
135 | + ext = [round(e, 2) for e in ext] | |
136 | + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3]) | |
137 | + | |
138 | + table_guid = uuid.uuid1().__str__() | |
139 | + | |
140 | + table = Table(guid=table_guid, | |
141 | + database_guid=database.guid, | |
142 | + # alias=layer.GetName(), | |
143 | + name=layer.GetName(), create_time=this_time, update_time=this_time, | |
144 | + table_type=GeometryAdapter.get_table_type(layer.GetGeomType()), | |
145 | + extent=extent, | |
146 | + feature_count=feature_count | |
147 | + ) | |
148 | + | |
149 | + db.session.add(table) | |
150 | + tables.append(table) | |
151 | + | |
152 | + feature_defn: FeatureDefn = layer.GetLayerDefn() | |
153 | + | |
154 | + for i in range(feature_defn.GetFieldCount()): | |
155 | + field_defn: FieldDefn = feature_defn.GetFieldDefn(i) | |
156 | + field_name = field_defn.GetName().lower() | |
157 | + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__( | |
158 | + "") else field_defn.GetAlternativeName() | |
159 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid, | |
160 | + name=field_name, alias=field_alias, create_time=this_time, update_time=this_time) | |
161 | + db.session.add(column) | |
162 | + | |
163 | + spatial_table_name.append(layer.GetName()) | |
164 | + return spatial_table_name,tables | |
165 | + | |
166 | + | |
167 | + def register_common_table(self,this_time,database,spatial_table_name): | |
168 | + # 注册普通表 | |
169 | + db_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri)) | |
170 | + | |
171 | + # 只注册public中的表 | |
172 | + result = db_session.execute( | |
173 | + "select relname as tabname from pg_class c where relkind = 'r' and relnamespace=2200 and relname not like 'pg_%' and relname not like 'sql_%' order by relname").fetchall() | |
174 | + for re in result: | |
175 | + table_name = re[0] | |
176 | + if table_name not in spatial_table_name: | |
177 | + | |
178 | + table_guid = uuid.uuid1().__str__() | |
179 | + count = db_session.execute('select count(*) from "{}"'.format(table_name)).fetchone()[0] | |
180 | + | |
181 | + table = Table(guid=table_guid, | |
182 | + database_guid=database.guid, | |
183 | + # alias=layer.GetName(), | |
184 | + name=table_name, create_time=this_time, update_time=this_time, | |
185 | + table_type=0, | |
186 | + feature_count=count | |
187 | + ) | |
188 | + | |
189 | + db.session.add(table) | |
190 | + | |
191 | + sql = ''' | |
192 | + SELECT | |
193 | + a.attnum, | |
194 | + a.attname AS field | |
195 | + FROM | |
196 | + pg_class c, | |
197 | + pg_attribute a, | |
198 | + pg_type t | |
199 | + WHERE | |
200 | + c.relname = '{}' | |
201 | + and a.attnum > 0 | |
202 | + and a.attrelid = c.oid | |
203 | + and a.atttypid = t.oid | |
204 | + ORDER BY a.attnum | |
205 | + '''.format(table_name) | |
206 | + | |
207 | + cols = db_session.execute(sql).fetchall() | |
208 | + for col in cols: | |
209 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid, | |
210 | + name=col[1], create_time=this_time, update_time=this_time) | |
211 | + db.session.add(column) | |
212 | + db_session.close() | |
213 | + | |
214 | + def regiser_vacuate_talbe(self,pg_ds,tables): | |
215 | + | |
216 | + # 注册抽稀表 | |
217 | + for i in range(pg_ds.GetLayerCount()): | |
218 | + layer:Layer = pg_ds.GetLayer(i) | |
219 | + l_name = layer.GetName() | |
220 | + if l_name.__contains__("_vacuate_"): | |
221 | + base_layer_name=l_name.split("_vacuate_")[0] | |
222 | + level = l_name.split("_")[-2] | |
223 | + pixel_distance_str: str ="0" | |
224 | + try: | |
225 | + pixel_distance_str :str= l_name.split("_")[-1] | |
226 | + if pixel_distance_str.startswith("0"): | |
227 | + pixel_distance_str="0.{}".format(pixel_distance_str) | |
228 | + except: | |
229 | + pass | |
230 | + base_table = [table for table in tables if table.name.__eq__(base_layer_name)][0] | |
231 | + table_vacuate = TableVacuate(guid=uuid.uuid1().__str__(), | |
232 | + table_guid=base_table.guid, | |
233 | + level=level, | |
234 | + name=l_name, | |
235 | + pixel_distance=float(pixel_distance_str)) | |
236 | + db.session.add(table_vacuate) | |
237 | + | |
238 | + | |
239 | + | |
240 | + def check_space(self,sqlachemy_uri): | |
241 | + system_session = None | |
242 | + check = True | |
243 | + try: | |
244 | + test_sql = "select st_geometryfromtext('POINT(1 1)')" | |
245 | + engine = create_engine(sqlachemy_uri) | |
246 | + system_session = sessionmaker(bind=engine)() | |
247 | + system_session.execute(test_sql).fetchone() | |
248 | + | |
249 | + except: | |
250 | + check = False | |
251 | + finally: | |
252 | + if system_session: | |
253 | + system_session.close() | |
254 | + | |
255 | + return check | |
256 | + | |
257 | + api_doc={ | |
258 | + "tags":["数据库接口"], | |
259 | + "parameters":[ | |
260 | + {"name": "host", | |
261 | + "in": "formData", | |
262 | + "type": "string", "required": "true"}, | |
263 | + {"name": "port", | |
264 | + "in": "formData", | |
265 | + "type": "string", "required": "true"}, | |
266 | + {"name": "user", | |
267 | + "in": "formData", | |
268 | + "type": "string", "required": "true"}, | |
269 | + {"name": "passwd", | |
270 | + "in": "formData", | |
271 | + "type": "string", "required": "true"}, | |
272 | + {"name": "database", | |
273 | + "in": "formData", | |
274 | + "type": "string", "required": "true"}, | |
275 | + {"name": "creator", | |
276 | + "in": "formData", | |
277 | + "type": "string", "required": "true"}, | |
278 | + | |
279 | + {"name": "alias", | |
280 | + "in": "formData", | |
281 | + "type": "string","description":"数据库别名","required": "true"}, | |
282 | + | |
283 | + {"name": "encryption", | |
284 | + "in": "formData", | |
285 | + "type": "integer", "description": "密码是否加密", "enum": [0,1]}, | |
286 | + ], | |
287 | + "responses":{ | |
288 | + 200:{ | |
289 | + "schema":{ | |
290 | + "properties":{ | |
291 | + } | |
292 | + } | |
293 | + } | |
294 | + } | |
295 | +} | |
\ No newline at end of file | ... | ... |
app/modules/database/database_test.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/6/22 | |
4 | +# email: nheweijun@sina.com | |
5 | +from contextlib import closing | |
6 | + | |
7 | +from sqlalchemy import create_engine | |
8 | + | |
9 | +from app.models import DES,Database,db | |
10 | + | |
11 | +from sqlalchemy.orm import sessionmaker | |
12 | +from app.util.component.ApiTemplate import ApiTemplate | |
13 | +class Api(ApiTemplate): | |
14 | + api_name = "数据库连接测试" | |
15 | + def process(self): | |
16 | + res = {} | |
17 | + res["result"] = False | |
18 | + try: | |
19 | + | |
20 | + | |
21 | + host = self.para.get("host") | |
22 | + port = self.para.get("port") | |
23 | + user = self.para.get("user") | |
24 | + passwd = self.para.get("passwd") | |
25 | + database = self.para.get("database") | |
26 | + | |
27 | + encryption = int(self.para.get("encryption", "0")) | |
28 | + if encryption: | |
29 | + passwd = DES.decode(passwd) | |
30 | + | |
31 | + | |
32 | + sqlalchemy_uri = "postgresql://{}:{}@{}:{}/{}".format(user,passwd,host,port,database) | |
33 | + | |
34 | + # if is_encrypt == "1": | |
35 | + # sqlalchemy_uri=DES.decode(sqlalchemy_uri) | |
36 | + | |
37 | + engine = create_engine(sqlalchemy_uri, connect_args={'connect_timeout': 2}) | |
38 | + with closing(engine.connect()): | |
39 | + pass | |
40 | + | |
41 | + | |
42 | + #判断数据库是否存在 | |
43 | + datab = db.session.query(Database).filter_by(alias=self.para.get("alias")).one_or_none() | |
44 | + #真实的数据库 | |
45 | + connectsrt = "hostaddr={} port={} dbname='{}' user='{}' password='{}'".format(host, port, database, user, | |
46 | + passwd) | |
47 | + real_database = db.session.query(Database).filter_by(connectstr=DES.encode(connectsrt)).all() | |
48 | + | |
49 | + if datab: | |
50 | + res["msg"] = "数据库已存在,请修改别名!" | |
51 | + return res | |
52 | + | |
53 | + elif real_database: | |
54 | + res["msg"] = "数据库连接已存在,请修改数据库连接!" | |
55 | + return res | |
56 | + | |
57 | + elif not self.check_space(sqlalchemy_uri): | |
58 | + res["msg"] = "数据不是空间数据库!" | |
59 | + return res | |
60 | + else: | |
61 | + res["result"] = True | |
62 | + res["msg"] = "测试连接成功" | |
63 | + except: | |
64 | + raise Exception("测试连接失败!") | |
65 | + return res | |
66 | + | |
67 | + | |
68 | + def check_space(self,sqlachemy_uri): | |
69 | + system_session = None | |
70 | + check = True | |
71 | + try: | |
72 | + test_sql = "select st_geometryfromtext('POINT(1 1)')" | |
73 | + engine = create_engine(sqlachemy_uri) | |
74 | + system_session = sessionmaker(bind=engine)() | |
75 | + system_session.execute(test_sql).fetchone() | |
76 | + except: | |
77 | + check = False | |
78 | + finally: | |
79 | + if system_session: | |
80 | + system_session.close() | |
81 | + | |
82 | + return check | |
83 | + | |
84 | + api_doc={ | |
85 | + "tags":["数据库接口"], | |
86 | + "parameters":[ | |
87 | + # {"name": "sqlalchemy_uri", | |
88 | + # "in": "formData", | |
89 | + # "type": "string","description":"数据库uri","required": "true"}, | |
90 | + {"name": "host", | |
91 | + "in": "formData", | |
92 | + "type": "string", "required": "true"}, | |
93 | + {"name": "port", | |
94 | + "in": "formData", | |
95 | + "type": "string", "required": "true"}, | |
96 | + {"name": "user", | |
97 | + "in": "formData", | |
98 | + "type": "string", "required": "true"}, | |
99 | + {"name": "passwd", | |
100 | + "in": "formData", | |
101 | + "type": "string", "required": "true"}, | |
102 | + {"name": "database", | |
103 | + "in": "formData", | |
104 | + "type": "string", "required": "true"}, | |
105 | + # {"name": "alias", | |
106 | + # "in": "formData", | |
107 | + # "type": "string", "required": "true"}, | |
108 | + {"name": "encryption", | |
109 | + "in": "formData", | |
110 | + "type": "int", "description": "密码是否加密", "enum": [0, 1]}, | |
111 | + | |
112 | + ], | |
113 | + "responses":{ | |
114 | + 200:{ | |
115 | + "schema":{ | |
116 | + "properties":{ | |
117 | + } | |
118 | + } | |
119 | + } | |
120 | + } | |
121 | +} | |
\ No newline at end of file | ... | ... |
app/modules/index/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/2/23 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from flask import Blueprint | |
7 | +from app.util import BlueprintApi | |
8 | +from flasgger import swag_from | |
9 | +from . import index | |
10 | +from . import release | |
11 | +from . import get_app_name | |
12 | +class Template(BlueprintApi): | |
13 | + | |
14 | + bp = Blueprint("Index", __name__, url_prefix="/") | |
15 | + | |
16 | + @staticmethod | |
17 | + @bp.route('/', methods=['GET']) | |
18 | + @swag_from(index.Api.api_doc) | |
19 | + def api_index(): | |
20 | + """ | |
21 | + Index接口 | |
22 | + """ | |
23 | + return index.Api().result | |
24 | + | |
25 | + @staticmethod | |
26 | + @bp.route('/release', methods=['GET']) | |
27 | + @swag_from(release.Api.api_doc) | |
28 | + def release(): | |
29 | + """ | |
30 | + release接口 | |
31 | + """ | |
32 | + return release.Api().result | |
33 | + | |
34 | + @staticmethod | |
35 | + @bp.route('/GetAppName', methods=['GET']) | |
36 | + @swag_from(get_app_name.Api.api_doc) | |
37 | + def get_app_name(): | |
38 | + """ | |
39 | + GetAppName接口 | |
40 | + """ | |
41 | + return get_app_name.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/index/get_app_name.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/7/20 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +import hashlib | |
7 | +from flask import current_app as app | |
8 | +import socket | |
9 | +import configure | |
10 | + | |
11 | +from app.util.component.ApiTemplate import ApiTemplate | |
12 | +class Api(ApiTemplate): | |
13 | + | |
14 | + def process(self): | |
15 | + return configure.application_name | |
16 | + | |
17 | + api_doc={ | |
18 | + "tags":["Index接口"], | |
19 | + "description":"Index接口", | |
20 | + "parameters":[ | |
21 | + ], | |
22 | + "responses":{ | |
23 | + 200:{ | |
24 | + "schema":{ | |
25 | + "properties":{ | |
26 | + "content":{ | |
27 | + "type": "string", | |
28 | + "description": "The name of the user" | |
29 | + } | |
30 | + } | |
31 | + } | |
32 | + } | |
33 | + } | |
34 | + } | |
\ No newline at end of file | ... | ... |
app/modules/index/index.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/2/23 | |
4 | +#email: nheweijun@sina.com | |
5 | +import hashlib | |
6 | +from flask import current_app as app | |
7 | +import socket | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +class Api(ApiTemplate): | |
11 | + | |
12 | + def process(self): | |
13 | + return {"Name":socket.gethostname(),"Type":"DMapDMS"} | |
14 | + | |
15 | + api_doc={ | |
16 | + "tags":["Index接口"], | |
17 | + "description":"Index接口", | |
18 | + "parameters":[ | |
19 | + ], | |
20 | + "responses":{ | |
21 | + 200:{ | |
22 | + "schema":{ | |
23 | + "properties":{ | |
24 | + "content":{ | |
25 | + "type": "string", | |
26 | + "description": "The name of the user" | |
27 | + } | |
28 | + } | |
29 | + } | |
30 | + } | |
31 | + } | |
32 | + } | |
\ No newline at end of file | ... | ... |
app/modules/index/release.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/2/23 | |
4 | +#email: nheweijun@sina.com | |
5 | +import hashlib | |
6 | +from flask import current_app as app | |
7 | +import socket | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | +from app.models import Task,InsertingLayerName,db | |
11 | +import datetime | |
12 | +import traceback | |
13 | +from sqlalchemy import or_ | |
14 | +class Api(ApiTemplate): | |
15 | + | |
16 | + def process(self): | |
17 | + try: | |
18 | + # 遗留任务变成失败 | |
19 | + last_tasks = db.session.query(Task).filter(or_(Task.state == 0, Task.state == 2)) | |
20 | + last_tasks.update({"state": -1,"process":"入库失败","update_time":datetime.datetime.now()}) | |
21 | + last_lins = db.session.query(InsertingLayerName).all() | |
22 | + if last_lins: | |
23 | + for lin in last_lins: | |
24 | + db.session.delete(lin) | |
25 | + db.session.commit() | |
26 | + except Exception as e: | |
27 | + # print(e) | |
28 | + print(traceback.format_exc()) | |
29 | + | |
30 | + return {"state": 1} | |
31 | + | |
32 | + api_doc={ | |
33 | + "tags":["Index接口"], | |
34 | + "description":"Index接口", | |
35 | + "parameters":[ | |
36 | + ], | |
37 | + "responses":{ | |
38 | + 200:{ | |
39 | + "schema":{ | |
40 | + "properties":{ | |
41 | + "content":{ | |
42 | + "type": "string", | |
43 | + "description": "The name of the user" | |
44 | + } | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | + } | |
\ No newline at end of file | ... | ... |
app/modules/io/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/12/8 | |
4 | +#email: nheweijun@sina.com | |
5 | +from flasgger import swag_from | |
6 | +from flask import Blueprint | |
7 | +from app.util import BlueprintApi | |
8 | + | |
9 | +from flask import send_from_directory,current_app,send_file | |
10 | +import os | |
11 | +import shutil | |
12 | +from . import data_download | |
13 | +from . import get_meta | |
14 | +from . import data_entry_by_meta | |
15 | +from . import get_data_list | |
16 | +from flask import after_this_request | |
17 | +import time | |
18 | + | |
19 | +class DataManager(BlueprintApi): | |
20 | + | |
21 | + bp:Blueprint = Blueprint("DataTool", __name__, url_prefix="/API/IO") | |
22 | + | |
23 | + | |
24 | + @staticmethod | |
25 | + @bp.route('/Download/<file>', methods=['GET']) | |
26 | + def table_download_file(file): | |
27 | + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |
28 | + dirpath = os.path.join(parent,"file_tmp") | |
29 | + | |
30 | + # @staticmethod | |
31 | + # @after_this_request | |
32 | + # def delete(response): | |
33 | + # try: | |
34 | + # os.remove(os.path.join(dirpath,file)) | |
35 | + # # shutil.rmtree(dirpath) | |
36 | + # print("删除文件成功!") | |
37 | + # except Exception as e: | |
38 | + # print(e) | |
39 | + # print("删除文件失败!") | |
40 | + # return response | |
41 | + | |
42 | + return send_from_directory(dirpath, filename=file, as_attachment=True) | |
43 | + | |
44 | + @staticmethod | |
45 | + @bp.route('/DeleteFile/<file>', methods=['GET']) | |
46 | + def d_file(file): | |
47 | + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |
48 | + dirpath = os.path.join(parent, "file_tmp") | |
49 | + | |
50 | + result={} | |
51 | + try: | |
52 | + os.remove(os.path.join(dirpath, file)) | |
53 | + # shutil.rmtree(dirpath) | |
54 | + result["message"]="删除文件成功!" | |
55 | + except Exception as e: | |
56 | + print(e) | |
57 | + result["message"] ="删除文件失败!" | |
58 | + return result | |
59 | + | |
60 | + @staticmethod | |
61 | + @bp.route('/DataDownload', methods=['POST']) | |
62 | + @swag_from(data_download.Api.api_doc) | |
63 | + def table_download(): | |
64 | + """ | |
65 | + 下载数据 | |
66 | + """ | |
67 | + return data_download.Api().result | |
68 | + | |
69 | + | |
70 | + @staticmethod | |
71 | + @bp.route('/GetMeta', methods=['POST']) | |
72 | + @swag_from(get_meta.Api.api_doc) | |
73 | + def get_meta(): | |
74 | + """ | |
75 | + 数据Meta | |
76 | + """ | |
77 | + return get_meta.Api().result | |
78 | + | |
79 | + @staticmethod | |
80 | + @bp.route('/GetDataList', methods=['POST']) | |
81 | + @swag_from(get_data_list.Api.api_doc) | |
82 | + def get_data_list(): | |
83 | + """ | |
84 | + 本地数据list | |
85 | + """ | |
86 | + return get_data_list.Api().result | |
87 | + | |
88 | + @staticmethod | |
89 | + @bp.route('/DataEntryByMeta', methods=['POST']) | |
90 | + @swag_from(data_entry_by_meta.Api.api_doc) | |
91 | + def data_entry_by_meta(): | |
92 | + """ | |
93 | + 数据入库ByMeta | |
94 | + """ | |
95 | + return data_entry_by_meta.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/io/check_meta.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2021/1/27 | |
4 | +# email: nheweijun@sina.com | |
5 | + | |
6 | +from osgeo.ogr import * | |
7 | +import uuid | |
8 | + | |
9 | +import time | |
10 | +from app.models import * | |
11 | +import json | |
12 | +import re | |
13 | +from app.util.component.ApiTemplate import ApiTemplate | |
14 | +from app.util.component.PGUtil import PGUtil | |
15 | + | |
16 | + | |
17 | +class Api(ApiTemplate): | |
18 | + api_name = "检查meta" | |
19 | + def process(self): | |
20 | + | |
21 | + # 设置任务信息 | |
22 | + self.para["task_guid"] = uuid.uuid1().__str__() | |
23 | + self.para["task_time"] = time.time() | |
24 | + | |
25 | + # 返回结果 | |
26 | + res = {} | |
27 | + | |
28 | + try: | |
29 | + | |
30 | + | |
31 | + # 图层重名检查 | |
32 | + | |
33 | + meta_list: list = json.loads(self.para.get("meta").__str__()) | |
34 | + check_meta_only = int(self.para.get("check_meta_only", 0)) | |
35 | + | |
36 | + res["data"] = {} | |
37 | + | |
38 | + | |
39 | + database = Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none() | |
40 | + if not database: | |
41 | + raise Exception("数据库不存在!") | |
42 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri)) | |
43 | + | |
44 | + res["result"] = True | |
45 | + | |
46 | + for meta in meta_list: | |
47 | + layers: dict = meta.get("layer") | |
48 | + for layer_name_origin in layers.keys(): | |
49 | + layer_name = layers.get(layer_name_origin) | |
50 | + if pg_ds.GetLayerByName(layer_name) or InsertingLayerName.query.filter_by( | |
51 | + name=layer_name).one_or_none(): | |
52 | + res["data"][layer_name_origin] = 0 | |
53 | + res["result"] = False | |
54 | + # 判断特殊字符 | |
55 | + elif re.search(r"\W", layer_name): | |
56 | + res["data"][layer_name_origin] = -1 | |
57 | + res["result"] = False | |
58 | + else: | |
59 | + res["data"][layer_name_origin] = 1 | |
60 | + if pg_ds: | |
61 | + try: | |
62 | + pg_ds.Destroy() | |
63 | + except: | |
64 | + print("关闭数据库失败!") | |
65 | + | |
66 | + except Exception as e: | |
67 | + raise e | |
68 | + | |
69 | + return res | |
70 | + | |
71 | + api_doc = { | |
72 | + "tags": ["IO接口"], | |
73 | + "parameters": [ | |
74 | + {"name": "meta", | |
75 | + "in": "formData", | |
76 | + "type": "string", | |
77 | + "description": "数据meta"} | |
78 | + ], | |
79 | + "responses": { | |
80 | + 200: { | |
81 | + "schema": { | |
82 | + "properties": { | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + } | |
87 | + } | |
\ No newline at end of file | ... | ... |
app/modules/io/data_download.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/11/27 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from app.models import * | |
8 | + | |
9 | +import traceback | |
10 | + | |
11 | +from osgeo.ogr import DataSource,Layer,FeatureDefn,FieldDefn | |
12 | +from osgeo import gdal ,ogr | |
13 | +import os | |
14 | +import uuid | |
15 | +import configure | |
16 | +from app.util.component.ApiTemplate import ApiTemplate | |
17 | +from app.util.component.PGUtil import PGUtil | |
18 | +from app.util.component.ZipUtil import ZipUtil | |
19 | +class Api(ApiTemplate): | |
20 | + | |
21 | + api_name = "下载数据" | |
22 | + def process(self): | |
23 | + #获取参数 | |
24 | + | |
25 | + #返回结果 | |
26 | + res={} | |
27 | + #设置编码 | |
28 | + encoding = self.para.get("encoding") | |
29 | + if encoding: | |
30 | + gdal.SetConfigOption("SHAPE_ENCODING",encoding) | |
31 | + else: | |
32 | + gdal.SetConfigOption("SHAPE_ENCODING", "GBK") | |
33 | + | |
34 | + ds:DataSource = None | |
35 | + try: | |
36 | + table_names = self.para.get("table_name").split(",") | |
37 | + database_guid = self.para.get("database_guid") | |
38 | + database = Database.query.filter_by(guid=database_guid).one_or_none() | |
39 | + if not database: | |
40 | + raise Exception("数据库不存在!") | |
41 | + | |
42 | + ds:DataSource = PGUtil.open_pg_data_source(0,DES.decode(database.sqlalchemy_uri)) | |
43 | + | |
44 | + download_type = self.para.get("download_type") | |
45 | + | |
46 | + data = None | |
47 | + if download_type.__eq__("shp"): | |
48 | + data = self.download_shp(table_names,ds) | |
49 | + if download_type.__eq__("gdb"): | |
50 | + data = self.download_gdb(table_names, ds,database_guid) | |
51 | + | |
52 | + res["data"] = data | |
53 | + res["result"] = True | |
54 | + except Exception as e: | |
55 | + print(traceback.format_exc()) | |
56 | + res["msg"]= e.__str__() | |
57 | + res["result"]=False | |
58 | + finally: | |
59 | + if ds: | |
60 | + ds.Destroy() | |
61 | + return res | |
62 | + | |
63 | + | |
64 | + def download_shp(self,table_names,ds): | |
65 | + data = [] | |
66 | + for table_name in table_names: | |
67 | + url = self.download_one(ds, table_name) | |
68 | + data.append({"name": table_name, "download_url": url}) | |
69 | + return data | |
70 | + | |
71 | + def download_one(self,ds,table_name): | |
72 | + | |
73 | + layer: Layer = ds.GetLayerByName(table_name) | |
74 | + driver = ogr.GetDriverByName("ESRI Shapefile") | |
75 | + uuid_ = uuid.uuid1().__str__() | |
76 | + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |
77 | + dirpath = os.path.join(parent, "file_tmp", uuid_) | |
78 | + os.makedirs(dirpath) | |
79 | + data_source: DataSource = driver.CreateDataSource(dirpath + "/{}.shp".format(table_name)) | |
80 | + data_source.CopyLayer(layer, table_name) | |
81 | + data_source.Destroy() | |
82 | + ZipUtil.create_zip(os.path.join(parent, "file_tmp", table_name+"_"+uuid_) + ".zip", [dirpath]) | |
83 | + return "http://" + configure.deploy_ip_host + "/API/IO/Download/{}".format(table_name+"_"+uuid_ + ".zip") | |
84 | + | |
85 | + | |
86 | + def download_gdb(self,table_names,ds,database_guid): | |
87 | + ogr.RegisterAll() | |
88 | + data = [] | |
89 | + gdal.UseExceptions() | |
90 | + gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES") | |
91 | + | |
92 | + # 创建一个gdb datasource | |
93 | + gdb_driver = ogr.GetDriverByName('FileGDB') | |
94 | + uuid_ = uuid.uuid1().__str__() | |
95 | + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |
96 | + gdb_path = os.path.join(parent, "file_tmp", uuid_+".gdb") | |
97 | + gdb_ds: DataSource = gdb_driver.CreateDataSource(gdb_path) | |
98 | + | |
99 | + | |
100 | + for table_name in table_names: | |
101 | + | |
102 | + layer: Layer = ds.GetLayerByName(table_name) | |
103 | + table = Table.query.filter_by(name=table_name, database_guid=database_guid).one_or_none() | |
104 | + feature_defn: FeatureDefn = layer.GetLayerDefn() | |
105 | + | |
106 | + for i in range(feature_defn.GetFieldCount()): | |
107 | + field_defn:FieldDefn = feature_defn.GetFieldDefn(i) | |
108 | + field_alias = Columns.query.filter_by(table_guid=table.guid,name=field_defn.GetName()).one_or_none().alias | |
109 | + field_defn.SetAlternativeName(field_alias) | |
110 | + | |
111 | + table_alias= table.alias | |
112 | + | |
113 | + # if is_chinese(table_name): | |
114 | + # if not table_alias: | |
115 | + # table_alias = table_name | |
116 | + # table_name = "table{}".format(table_name.__hash__()) | |
117 | + | |
118 | + gdb_ds.CopyLayer(layer, table_name,["LAYER_ALIAS={}".format(table_alias)]) | |
119 | + | |
120 | + gdb_ds.Destroy() | |
121 | + ZipUtil.create_zip(gdb_path + ".zip", [gdb_path]) | |
122 | + data.append({"name": ",".join(table_names), "download_url": "http://" + configure.deploy_ip_host + "/API/IO/Download/{}".format(uuid_+".gdb" + ".zip")}) | |
123 | + | |
124 | + | |
125 | + return data | |
126 | + | |
127 | + | |
128 | + | |
129 | + api_doc={ | |
130 | + "tags":["IO接口"], | |
131 | + "description":"下载数据", | |
132 | + "parameters":[ | |
133 | + {"name": "table_name", | |
134 | + "in": "formData", | |
135 | + "type":"string","description":"支持多图层下载,以逗号相隔","required":"true"}, | |
136 | + {"name": "encoding", | |
137 | + "in": "formData", | |
138 | + "type": "string", | |
139 | + "enum":["GBK","UTF-8"]}, | |
140 | + {"name": "download_type", | |
141 | + "in": "formData", | |
142 | + "type": "string", | |
143 | + "enum": ["shp", "gdb"],"required":"true" | |
144 | + }, | |
145 | + {"name": "database_guid", | |
146 | + "in": "formData", | |
147 | + "type": "string","required":"true" | |
148 | + } | |
149 | + ], | |
150 | + "responses":{ | |
151 | + 200:{ | |
152 | + "schema":{ | |
153 | + "properties":{ | |
154 | + "content":{ | |
155 | + "type": "string", | |
156 | + "description": "The name of the user" | |
157 | + } | |
158 | + } | |
159 | + } | |
160 | + } | |
161 | + } | |
162 | +} | |
\ No newline at end of file | ... | ... |
app/modules/io/data_entry_by_meta.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/1/27 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from osgeo.ogr import * | |
7 | +import uuid | |
8 | + | |
9 | +import time | |
10 | +from app.models import * | |
11 | +import json | |
12 | +import re | |
13 | +from app.util.component.ApiTemplate import ApiTemplate | |
14 | +from app.util.component.PGUtil import PGUtil | |
15 | + | |
16 | + | |
17 | + | |
18 | +class Api(ApiTemplate): | |
19 | + | |
20 | + api_name = "通过meta入库" | |
21 | + | |
22 | + def process(self): | |
23 | + | |
24 | + #设置任务信息 | |
25 | + self.para["task_guid"] = uuid.uuid1().__str__() | |
26 | + self.para["task_time"] = time.time() | |
27 | + | |
28 | + #返回结果 | |
29 | + res={} | |
30 | + | |
31 | + try: | |
32 | + #检测目录 | |
33 | + if Catalog.query.filter_by(pguid=self.para.get("guid")).all(): | |
34 | + raise Exception("目录非子目录,不可入库!") | |
35 | + | |
36 | + # 图层重名检查 | |
37 | + meta_list:list = json.loads(self.para.get("meta").__str__()) | |
38 | + check_meta_only = int(self.para.get("check_meta_only",0)) | |
39 | + | |
40 | + res["data"] = {} | |
41 | + if check_meta_only: | |
42 | + | |
43 | + database = Database.query.filter_by(guid=self.para.get("database_guid")).one_or_none() | |
44 | + if not database: | |
45 | + raise Exception("数据库不存在!") | |
46 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri)) | |
47 | + | |
48 | + res["result"] = True | |
49 | + | |
50 | + for meta in meta_list: | |
51 | + layers:dict = meta.get("layer") | |
52 | + for layer_name_origin in layers.keys(): | |
53 | + layer_name = layers.get(layer_name_origin) | |
54 | + if pg_ds.GetLayerByName(layer_name) or InsertingLayerName.query.filter_by(name=layer_name).one_or_none(): | |
55 | + res["data"][layer_name_origin]=0 | |
56 | + res["result"] = False | |
57 | + # 判断特殊字符 | |
58 | + elif re.search(r"\W",layer_name): | |
59 | + res["data"][layer_name_origin]=-1 | |
60 | + res["result"] = False | |
61 | + else : | |
62 | + res["data"][layer_name_origin] = 1 | |
63 | + | |
64 | + if pg_ds: | |
65 | + try: | |
66 | + pg_ds.Destroy() | |
67 | + except: | |
68 | + print("关闭数据库失败!") | |
69 | + return res | |
70 | + | |
71 | + # 录入数据后台进程,录入主函数为entry | |
72 | + # 初始化task | |
73 | + task = Task(guid=self.para.get("task_guid"), | |
74 | + name=self.para.get("task_name"), | |
75 | + create_time=datetime.datetime.now(), | |
76 | + state=0, | |
77 | + creator=self.para.get("creator"), | |
78 | + file_name=meta_list[0].get("filename"), | |
79 | + database_guid=self.para.get("database_guid"), | |
80 | + catalog_guid=self.para.get("catalog_guid"), | |
81 | + process="等待入库", | |
82 | + parameter=json.dumps(self.para)) | |
83 | + db.session.add(task) | |
84 | + db.session.commit() | |
85 | + | |
86 | + res["result"] = True | |
87 | + res["msg"] = "数据录入提交成功!" | |
88 | + res["data"] = self.para["task_guid"] | |
89 | + except Exception as e: | |
90 | + raise e | |
91 | + return res | |
92 | + | |
93 | + | |
94 | + api_doc={ | |
95 | + "tags":["IO接口"], | |
96 | + "parameters":[ | |
97 | + {"name": "meta", | |
98 | + "in": "formData", | |
99 | + "type": "string", | |
100 | + "description": "数据meta"}, | |
101 | + {"name": "encoding", | |
102 | + "in": "formData", | |
103 | + "type": "string", | |
104 | + "description": "原shp文件编码,非必要,优先使用cpg文件中编码,没有则默认GBK","enum":["UTF-8","GBK"]}, | |
105 | + {"name": "overwrite", | |
106 | + "in": "formData", | |
107 | + "type": "string", | |
108 | + "description": "是否覆盖", | |
109 | + "enum":["yes","no"]}, | |
110 | + {"name": "fid", | |
111 | + "in": "formData", | |
112 | + "type": "string", | |
113 | + "description": "fid列名"}, | |
114 | + {"name": "geom_name", | |
115 | + "in": "formData", | |
116 | + "type": "string", | |
117 | + "description": "空间属性列名"}, | |
118 | + {"name": "task_name", | |
119 | + "in": "formData", | |
120 | + "type": "string", | |
121 | + "description": "任务名", | |
122 | + "required":"true"}, | |
123 | + {"name": "creator", | |
124 | + "in": "formData", | |
125 | + "type": "string", | |
126 | + "description": "创建人"}, | |
127 | + {"name": "database_guid", | |
128 | + "in": "formData", | |
129 | + "type": "string", | |
130 | + "description": "数据库guid", | |
131 | + "required": "true"}, | |
132 | + {"name": "catalog_guid", | |
133 | + "in": "formData", | |
134 | + "type": "string", | |
135 | + "description": "目录guid"}, | |
136 | + {"name": "check_meta_only", | |
137 | + "in": "formData", | |
138 | + "type": "int", | |
139 | + "description": "是否只检查meta","enum":[0,1]} | |
140 | + | |
141 | + ], | |
142 | + "responses":{ | |
143 | + 200:{ | |
144 | + "schema":{ | |
145 | + "properties":{ | |
146 | + } | |
147 | + } | |
148 | + } | |
149 | + } | |
150 | + } | |
\ No newline at end of file | ... | ... |
app/modules/io/get_data_list.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/9/4 | |
4 | +# email: nheweijun@sina.com | |
5 | + | |
6 | +import traceback | |
7 | +from osgeo.ogr import * | |
8 | +from osgeo import ogr | |
9 | + | |
10 | +from flask import request | |
11 | +import os | |
12 | +import uuid | |
13 | + | |
14 | +import json | |
15 | +from app.util.component.ApiTemplate import ApiTemplate | |
16 | +from app.util.component.ZipUtil import ZipUtil | |
17 | +from app.util.component.FileProcess import FileProcess | |
18 | +import datetime | |
19 | +import time | |
20 | +class Api(ApiTemplate): | |
21 | + api_name = "本地数据list" | |
22 | + def process(self): | |
23 | + res = {} | |
24 | + | |
25 | + try: | |
26 | + project_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) | |
27 | + base_path = os.path.join(project_path,"tmp") | |
28 | + if self.para.get("data_path"): | |
29 | + base_path = os.path.normpath(self.para.get("data_path")) | |
30 | + data_list:list = [] | |
31 | + for f in os.listdir(base_path): | |
32 | + | |
33 | + file_path = os.path.normpath(os.path.join(base_path, f)) | |
34 | + file_size = FileProcess.get_file_size(file_path) | |
35 | + | |
36 | + fctime = datetime.datetime.fromtimestamp(os.path.getctime(file_path)).strftime('%Y-%m-%d %H:%M:%S') | |
37 | + | |
38 | + file_info ={"name":f,"path":file_path,"size":file_size,"create_time":fctime} | |
39 | + | |
40 | + if file_path.endswith("shp"): | |
41 | + file_info["type"]="shp" | |
42 | + data_list.append(file_info) | |
43 | + elif file_path.endswith("gdb"): | |
44 | + file_info["type"]="gdb" | |
45 | + data_list.append(file_info) | |
46 | + elif file_path.endswith("zip"): | |
47 | + file_info["type"]="zip" | |
48 | + data_list.append(file_info) | |
49 | + elif os.path.isdir(file_path): | |
50 | + bn = os.path.basename(file_path) | |
51 | + if not len(bn.split("-"))==5: | |
52 | + file_info["type"] = "dir" | |
53 | + data_list.append(file_info) | |
54 | + | |
55 | + data_list_sorted = sorted(data_list, key=lambda x: x["name"]) | |
56 | + res["data"]=data_list_sorted | |
57 | + res["result"]=True | |
58 | + except Exception as e: | |
59 | + raise e | |
60 | + return res | |
61 | + | |
62 | + | |
63 | + | |
64 | + api_doc={ | |
65 | + "tags":["IO接口"], | |
66 | + "parameters":[ | |
67 | + {"name": "data_path", | |
68 | + "in": "formData", | |
69 | + "type": "string", | |
70 | + "description": "数据文件路径"} | |
71 | + ], | |
72 | + "responses":{ | |
73 | + 200:{ | |
74 | + "schema":{ | |
75 | + "properties":{ | |
76 | + } | |
77 | + } | |
78 | + } | |
79 | + } | |
80 | +} | |
\ No newline at end of file | ... | ... |
app/modules/io/get_meta.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/9/4 | |
4 | +# email: nheweijun@sina.com | |
5 | + | |
6 | +import traceback | |
7 | +from osgeo.ogr import * | |
8 | +from osgeo import ogr | |
9 | + | |
10 | +from flask import request | |
11 | +import os | |
12 | +import uuid | |
13 | + | |
14 | +import json | |
15 | +from app.util.component.ApiTemplate import ApiTemplate | |
16 | +from app.util.component.ZipUtil import ZipUtil | |
17 | +from app.util.component.FileProcess import FileProcess | |
18 | + | |
19 | + | |
20 | +class Api(ApiTemplate): | |
21 | + api_name = "获取meta" | |
22 | + def process(self): | |
23 | + res = {} | |
24 | + | |
25 | + try: | |
26 | + | |
27 | + spatial_files=[] | |
28 | + if self.para.get("data_path"): | |
29 | + filename = os.path.basename(self.para.get("data_path")) | |
30 | + #处理 | |
31 | + | |
32 | + if self.para.get("data_path").endswith("zip"): | |
33 | + | |
34 | + store_path = ZipUtil.unzip(self.para.get("data_path"),True) | |
35 | + spatial_files = FileProcess.get_spatial_file(store_path) | |
36 | + | |
37 | + elif self.para.get("data_path").endswith("shp"): | |
38 | + data_path=self.para.get("data_path") | |
39 | + encoding_cpg_path = data_path.split(".shp")[0]+".cpg" | |
40 | + with open(encoding_cpg_path) as fd: | |
41 | + encoding_cpg = fd.readline().strip() | |
42 | + if not os.path.exists(encoding_cpg_path): | |
43 | + encoding_cpg=None | |
44 | + spatial_files.append((data_path, encoding_cpg)) | |
45 | + elif self.para.get("data_path").endswith("gdb"): | |
46 | + data_path=self.para.get("data_path") | |
47 | + encoding_cpg=None | |
48 | + spatial_files.append((data_path, encoding_cpg)) | |
49 | + else: | |
50 | + raise Exception("文件不符合规格!") | |
51 | + | |
52 | + else: | |
53 | + # 保存文件 | |
54 | + parent = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | |
55 | + dir_path, store_file = FileProcess.save(parent) | |
56 | + store_path = ZipUtil.unzip(store_file) | |
57 | + | |
58 | + spatial_files = FileProcess.get_spatial_file(store_path) | |
59 | + | |
60 | + file = request.files['file'] | |
61 | + filename = file.filename.split('"')[0] | |
62 | + | |
63 | + res["data"] =[] | |
64 | + | |
65 | + for data_path,code in spatial_files: | |
66 | + one_data = self.get_meta(data_path) | |
67 | + one_data["encoding"]=code | |
68 | + one_data["filename"] = filename | |
69 | + res["data"].append(one_data) | |
70 | + res["result"] = True | |
71 | + | |
72 | + except Exception as e: | |
73 | + raise e | |
74 | + return json.dumps(res,ensure_ascii=False) | |
75 | + | |
76 | + | |
77 | + def get_meta(self,data_path): | |
78 | + ds: DataSource = None | |
79 | + info = {} | |
80 | + layer_name = {} | |
81 | + info["data_path"] = os.path.normpath(data_path) | |
82 | + info["layer"] =layer_name | |
83 | + try: | |
84 | + # 分为shp和gdb 2种 | |
85 | + if data_path.endswith("shp"): | |
86 | + info["type"]="shp" | |
87 | + driver: Driver = ogr.GetDriverByName("ESRI Shapefile") | |
88 | + ds: DataSource = driver.Open(data_path, 1) | |
89 | + if not ds: | |
90 | + raise Exception("打开数据失败!") | |
91 | + layer: Layer = ds.GetLayer(0) | |
92 | + layer_name[layer.GetName().lower()] = layer.GetName().lower() | |
93 | + | |
94 | + if data_path.endswith("gdb"): | |
95 | + info["type"] = "gdb" | |
96 | + driver: Driver = ogr.GetDriverByName("OpenFileGDB") | |
97 | + ds: DataSource = driver.Open(data_path, 0) | |
98 | + if not ds: | |
99 | + raise Exception("打开数据失败!") | |
100 | + for i in range(ds.GetLayerCount()): | |
101 | + layer: Layer = ds.GetLayer(i) | |
102 | + layer_name[layer.GetName().lower()] = layer.GetName().lower() | |
103 | + except Exception as e : | |
104 | + print(traceback.format_exc()) | |
105 | + info={} | |
106 | + finally: | |
107 | + if ds: | |
108 | + ds.Destroy() | |
109 | + return info | |
110 | + | |
111 | + | |
112 | + api_doc={ | |
113 | + "tags":["IO接口"], | |
114 | + "parameters":[ | |
115 | + {"name": "file", | |
116 | + "in": "formData", | |
117 | + "type":"file", | |
118 | + "description":"数据文件zip压缩包"}, | |
119 | + {"name": "data_path", | |
120 | + "in": "formData", | |
121 | + "type": "string", | |
122 | + "description": "数据文件路径"} | |
123 | + ], | |
124 | + "responses":{ | |
125 | + 200:{ | |
126 | + "schema":{ | |
127 | + "properties":{ | |
128 | + } | |
129 | + } | |
130 | + } | |
131 | + } | |
132 | +} | |
\ No newline at end of file | ... | ... |
app/modules/io/util/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/11/30 | |
4 | +#email: nheweijun@sina.com | |
5 | +from app.util import * | |
6 | +import configure | |
7 | +from osgeo import ogr | |
8 | + | |
9 | +from configure import SQLALCHEMY_DATABASE_URI | |
10 | +from sqlalchemy import create_engine | |
11 | +from sqlalchemy.orm import sessionmaker, session | |
12 | + | |
13 | + | |
14 | + | |
15 | + | |
16 | + | |
17 | + | |
18 | + | |
19 | + | |
20 | + | ... | ... |
app/modules/manager/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/1 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from flasgger import swag_from | |
8 | +from flask import Blueprint | |
9 | +from app.util import BlueprintApi | |
10 | + | |
11 | +from . import field_edit | |
12 | + | |
13 | +from . import table_list | |
14 | +from . import field_list | |
15 | +from . import table_edit | |
16 | +from . import table_info | |
17 | +from . import table_refresh | |
18 | +from . import table_delete | |
19 | +from . import table_view | |
20 | +class DataManager(BlueprintApi): | |
21 | + | |
22 | + bp = Blueprint("DataManager", __name__, url_prefix="/API/Manager") | |
23 | + | |
24 | + @staticmethod | |
25 | + @bp.route('/FieldEdit', methods=['POST']) | |
26 | + @swag_from(field_edit.Api.api_doc) | |
27 | + def field_edit(): | |
28 | + """ | |
29 | + 修改属性别名 | |
30 | + """ | |
31 | + return field_edit.Api().result | |
32 | + | |
33 | + @staticmethod | |
34 | + @bp.route('/FieldList', methods=['POST']) | |
35 | + @swag_from(field_list.Api.api_doc) | |
36 | + def field_list(): | |
37 | + """ | |
38 | + 属性列表 | |
39 | + """ | |
40 | + return field_list.Api().result | |
41 | + | |
42 | + @staticmethod | |
43 | + @bp.route('/TableList', methods=['POST']) | |
44 | + @swag_from(table_list.Api.api_doc) | |
45 | + def table_list(): | |
46 | + """ | |
47 | + 数据列表 | |
48 | + """ | |
49 | + return table_list.Api().result | |
50 | + | |
51 | + | |
52 | + @staticmethod | |
53 | + @bp.route('/TableEdit', methods=['POST']) | |
54 | + @swag_from(table_edit.Api.api_doc) | |
55 | + def table_edit(): | |
56 | + """ | |
57 | + 修改数据 | |
58 | + """ | |
59 | + return table_edit.Api().result | |
60 | + | |
61 | + | |
62 | + @staticmethod | |
63 | + @bp.route('/TableDelete', methods=['POST']) | |
64 | + @swag_from(table_delete.Api.api_doc) | |
65 | + def table_delete(): | |
66 | + """ | |
67 | + 删除数据 | |
68 | + """ | |
69 | + return table_delete.Api().result | |
70 | + | |
71 | + | |
72 | + @staticmethod | |
73 | + @bp.route('/TableInfo', methods=['POST']) | |
74 | + @swag_from(table_info.Api.api_doc) | |
75 | + def table_info(): | |
76 | + """ | |
77 | + 数据信息 | |
78 | + """ | |
79 | + return table_info.Api().result | |
80 | + | |
81 | + | |
82 | + @staticmethod | |
83 | + @bp.route('/TableRefresh', methods=['POST']) | |
84 | + @swag_from(table_refresh.Api.api_doc) | |
85 | + def table_refresh(): | |
86 | + """ | |
87 | + 刷新数据 | |
88 | + """ | |
89 | + return table_refresh.Api().result | |
90 | + | |
91 | + @staticmethod | |
92 | + @bp.route('/TableView', methods=['POST']) | |
93 | + @swag_from(table_view.Api.api_doc) | |
94 | + def table_view(): | |
95 | + """ | |
96 | + 数据浏览 | |
97 | + """ | |
98 | + return table_view.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/manager/field_edit.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | +from app.models import Columns | |
6 | +import datetime | |
7 | + | |
8 | + | |
9 | +from app.models import db | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | + | |
12 | + | |
13 | +class Api(ApiTemplate): | |
14 | + api_name = "修改属性" | |
15 | + def process(self): | |
16 | + | |
17 | + #返回结果 | |
18 | + res={} | |
19 | + | |
20 | + column_guid = self.para.get("column_guid") | |
21 | + try: | |
22 | + Columns.query.filter_by(guid = column_guid).update({"alias":self.para.get("column_alias"),"update_time":datetime.datetime.now()}) | |
23 | + db.session.commit() | |
24 | + res["result"] = True | |
25 | + res["msg"] = "属性别名修改成功!" | |
26 | + | |
27 | + except Exception as e: | |
28 | + raise e | |
29 | + | |
30 | + return res | |
31 | + | |
32 | + api_doc={ | |
33 | + "tags":["管理接口"], | |
34 | + "parameters":[ | |
35 | + {"name": "column_guid", | |
36 | + "in": "formData", | |
37 | + "type": "string", | |
38 | + "description": "属性guid","required":"true"}, | |
39 | + {"name": "column_alias", | |
40 | + "in": "formData", | |
41 | + "type": "string", | |
42 | + "description": "属性别名","required":"true"}, | |
43 | + | |
44 | + ], | |
45 | + "responses":{ | |
46 | + 200:{ | |
47 | + "schema":{ | |
48 | + "properties":{ | |
49 | + } | |
50 | + } | |
51 | + } | |
52 | + } | |
53 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/field_list.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | +import traceback | |
7 | +from app.models import Table | |
8 | +from app.util.component.ApiTemplate import ApiTemplate | |
9 | +from app.util.component.ModelVisitor import ModelVisitor | |
10 | + | |
11 | +class Api(ApiTemplate): | |
12 | + api_name = "属性列表" | |
13 | + def process(self): | |
14 | + | |
15 | + #返回结果 | |
16 | + dir_path = None | |
17 | + res={} | |
18 | + | |
19 | + try: | |
20 | + table_guid = self.para.get("guid") | |
21 | + table:Table = Table.query.filter_by(guid=table_guid).one_or_none() | |
22 | + if table: | |
23 | + res["result"]=True | |
24 | + res["data"] = ModelVisitor.objects_to_jsonarray(table.relate_columns) | |
25 | + else: | |
26 | + res["result"]=False | |
27 | + res["msg"] = "数据表不存在!" | |
28 | + except Exception as e: | |
29 | + print(traceback.format_exc()) | |
30 | + raise e | |
31 | + | |
32 | + return res | |
33 | + | |
34 | + api_doc={ | |
35 | + "tags":["管理接口"], | |
36 | + "parameters":[ | |
37 | + {"name": "guid", | |
38 | + "in": "formData", | |
39 | + "type": "string", | |
40 | + "description": "表guid"}, | |
41 | + | |
42 | + ], | |
43 | + "responses":{ | |
44 | + 200:{ | |
45 | + "schema":{ | |
46 | + "properties":{ | |
47 | + } | |
48 | + } | |
49 | + } | |
50 | + } | |
51 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_delete.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | + | |
7 | +import traceback | |
8 | +from app.models import Table,Database,DES | |
9 | + | |
10 | +from osgeo.ogr import DataSource | |
11 | + | |
12 | + | |
13 | +from app.models import db | |
14 | +from app.util.component.ApiTemplate import ApiTemplate | |
15 | + | |
16 | +from app.util.component.PGUtil import PGUtil | |
17 | +class Api(ApiTemplate): | |
18 | + api_name = "删除表" | |
19 | + def process(self): | |
20 | + | |
21 | + res = {} | |
22 | + pg_ds = None | |
23 | + try: | |
24 | + | |
25 | + table_guid = self.para.get("guid") | |
26 | + | |
27 | + table = Table.query.filter_by(guid=table_guid) | |
28 | + | |
29 | + if not table.one_or_none(): | |
30 | + res["result"]=False | |
31 | + res["msg"]= "数据不存在!" | |
32 | + return res | |
33 | + | |
34 | + | |
35 | + table = table.one_or_none() | |
36 | + | |
37 | + | |
38 | + | |
39 | + # 删除真实数据 | |
40 | + database = Database.query.filter_by(guid=table.database_guid).one_or_none() | |
41 | + if not database: | |
42 | + res["result"]=False | |
43 | + res["msg"]= "数据库不存在!" | |
44 | + return res | |
45 | + | |
46 | + | |
47 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri)) | |
48 | + | |
49 | + | |
50 | + #删除抽稀表 | |
51 | + vacuate_tables=table.relate_table_vacuates.all() | |
52 | + for vt in vacuate_tables: | |
53 | + pg_ds.DeleteLayer(vt.name) | |
54 | + | |
55 | + pg_ds.DeleteLayer(table.name) | |
56 | + | |
57 | + # 删除元数据 | |
58 | + | |
59 | + db.session.delete(table) | |
60 | + db.session.commit() | |
61 | + res["result"] = True | |
62 | + res["msg"] ="删除成功!" | |
63 | + except Exception as e: | |
64 | + raise e | |
65 | + finally: | |
66 | + if pg_ds: | |
67 | + pg_ds.Destroy() | |
68 | + return res | |
69 | + | |
70 | + api_doc={ | |
71 | + "tags":["管理接口"], | |
72 | + "parameters":[ | |
73 | + {"name": "guid", | |
74 | + "in": "formData", | |
75 | + "type": "string", | |
76 | + "description": "表guid","required":"true"}, | |
77 | + ], | |
78 | + "responses":{ | |
79 | + 200:{ | |
80 | + "schema":{ | |
81 | + "properties":{ | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_edit.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | + | |
7 | +from app.models import Table,Catalog,Database,DES | |
8 | + | |
9 | +from app.models import db,Columns | |
10 | +import json | |
11 | + | |
12 | +from sqlalchemy.orm import Session | |
13 | +import datetime | |
14 | +from app.util.component.ApiTemplate import ApiTemplate | |
15 | +from app.util.component.PGUtil import PGUtil | |
16 | + | |
17 | +class Api(ApiTemplate): | |
18 | + api_name = "修改表信息" | |
19 | + def process(self): | |
20 | + | |
21 | + res = {} | |
22 | + try: | |
23 | + | |
24 | + table_guid = self.para.get("guid") | |
25 | + table_alias = self.para.get("alias") | |
26 | + catalog_guid = self.para.get("catalog_guid") | |
27 | + description = self.para.get("description") | |
28 | + name = self.para.get("name") | |
29 | + columns_edit = self.para.get("columns_edit") | |
30 | + | |
31 | + | |
32 | + table = Table.query.filter_by(guid=table_guid) | |
33 | + if not table.one_or_none(): | |
34 | + res["result"]=False | |
35 | + res["msg"]= "数据不存在!" | |
36 | + return res | |
37 | + | |
38 | + | |
39 | + if self.para.__contains__("catalog_guid"): | |
40 | + if catalog_guid is None: | |
41 | + table.update({"catalog_guid": None}) | |
42 | + | |
43 | + else: | |
44 | + # 检测目录的有效性 | |
45 | + | |
46 | + if not Catalog.query.filter_by(guid=catalog_guid).one_or_none(): | |
47 | + raise Exception("目录不存在!") | |
48 | + | |
49 | + if not table.one_or_none().database_guid.__eq__( | |
50 | + Catalog.query.filter_by(guid=catalog_guid).one_or_none().database_guid | |
51 | + ): | |
52 | + raise Exception("该目录不属于表所在的数据库!") | |
53 | + table.update({"catalog_guid": catalog_guid}) | |
54 | + | |
55 | + | |
56 | + if self.para.__contains__("alias"): | |
57 | + table.update({"alias":table_alias}) | |
58 | + | |
59 | + if self.para.__contains__("description"): | |
60 | + table.update({"description":description}) | |
61 | + | |
62 | + if columns_edit: | |
63 | + columns_edit_list = json.loads(columns_edit) | |
64 | + for ce in columns_edit_list: | |
65 | + Columns.query.filter_by(guid=ce["guid"]).update({"alias": ce.get("alias")}) | |
66 | + | |
67 | + if name: | |
68 | + sys_session=None | |
69 | + try: | |
70 | + this_table = table.one_or_none() | |
71 | + database:Database= this_table.relate_database | |
72 | + sys_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri)) | |
73 | + rename_sql = 'alter table "{}" rename to "{}"'.format(this_table.name,name) | |
74 | + sys_session.execute(rename_sql) | |
75 | + sys_session.commit() | |
76 | + print(rename_sql) | |
77 | + # 更新所有相关业务表 | |
78 | + same_databases = Database.query.filter_by(sqlalchemy_uri=this_table.relate_database.sqlalchemy_uri).all() | |
79 | + same_databases_database_guid = [d.guid for d in same_databases] | |
80 | + re_tables = Table.query.filter(Table.name==this_table.name).filter(Table.database_guid.in_(same_databases_database_guid)) | |
81 | + re_tables.update({"name": name},synchronize_session=False) | |
82 | + except: | |
83 | + raise Exception("表名更新失败,表名已存在!") | |
84 | + finally: | |
85 | + if sys_session: | |
86 | + sys_session.close() | |
87 | + db.session.commit() | |
88 | + | |
89 | + if catalog_guid or table_alias or name or columns_edit: | |
90 | + Table.query.filter_by(guid=table_guid).update({"update_time":datetime.datetime.now()}) | |
91 | + db.session.commit() | |
92 | + res["result"] = True | |
93 | + res["msg"] = "更新成功!" | |
94 | + except Exception as e: | |
95 | + db.session.rollback() | |
96 | + raise e | |
97 | + return res | |
98 | + | |
99 | + api_doc={ | |
100 | + "tags":["管理接口"], | |
101 | + "parameters":[ | |
102 | + {"name": "guid", | |
103 | + "in": "formData", | |
104 | + "type": "string", | |
105 | + "description": "表guid","required":"true"}, | |
106 | + {"name": "alias", | |
107 | + "in": "formData", | |
108 | + "type": "string", | |
109 | + "description": "表别名"}, | |
110 | + {"name": "name", | |
111 | + "in": "formData", | |
112 | + "type": "string", | |
113 | + "description": "表名"}, | |
114 | + {"name": "description", | |
115 | + "in": "formData", | |
116 | + "type": "string", | |
117 | + "description": "表描述"}, | |
118 | + {"name": "catalog_guid", | |
119 | + "in": "formData", | |
120 | + "type": "string", | |
121 | + "description": "目录guid,如果要撤销挂载目录,传None"}, | |
122 | + {"name": "columns_edit", | |
123 | + "in": "formData", | |
124 | + "type": "string", | |
125 | + "description": '需要修改的属性的列表,如 [{"guid":"1fda","alias":"测试"}]'}, | |
126 | + | |
127 | + ], | |
128 | + "responses":{ | |
129 | + 200:{ | |
130 | + "schema":{ | |
131 | + "properties":{ | |
132 | + } | |
133 | + } | |
134 | + } | |
135 | + } | |
136 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_info.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | +from app.models import Table,Columns | |
6 | + | |
7 | +from app.util.component.ApiTemplate import ApiTemplate | |
8 | +from app.util.component.ModelVisitor import ModelVisitor | |
9 | + | |
10 | +class Api(ApiTemplate): | |
11 | + api_name = "表信息" | |
12 | + def process(self): | |
13 | + res = {} | |
14 | + try: | |
15 | + table_guid = self.para.get("guid") | |
16 | + table = Table.query.filter_by(guid=table_guid) | |
17 | + if not table.one_or_none(): | |
18 | + raise Exception("数据不存在!") | |
19 | + table = table.one_or_none() | |
20 | + columns = table.relate_columns.order_by(Columns.name).all() | |
21 | + res["data"]=ModelVisitor.table_to_json(table) | |
22 | + res["data"]["columns"] = ModelVisitor.objects_to_jsonarray(columns) | |
23 | + res["result"] = True | |
24 | + | |
25 | + except Exception as e: | |
26 | + raise e | |
27 | + return res | |
28 | + | |
29 | + api_doc={ | |
30 | + "tags":["管理接口"], | |
31 | + "parameters":[ | |
32 | + {"name": "guid", | |
33 | + "in": "formData", | |
34 | + "type": "string", | |
35 | + "description": "表guid","required":"true"}, | |
36 | + ], | |
37 | + "responses":{ | |
38 | + 200:{ | |
39 | + "schema":{ | |
40 | + "properties":{ | |
41 | + } | |
42 | + } | |
43 | + } | |
44 | + } | |
45 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_list.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | +from app.models import Table,Catalog,Database,DES,Columns,db | |
7 | + | |
8 | +from sqlalchemy import or_,and_ | |
9 | + | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | + | |
12 | +from app.util.component.ModelVisitor import ModelVisitor | |
13 | +class Api(ApiTemplate): | |
14 | + api_name = "表列表" | |
15 | + def process(self): | |
16 | + | |
17 | + res = {} | |
18 | + try: | |
19 | + | |
20 | + page_index = int(self.para.get("page_index", "0")) | |
21 | + page_size = int(self.para.get("page_size", "10")) | |
22 | + | |
23 | + alias = self.para.get("alias") | |
24 | + name = self.para.get("name") | |
25 | + | |
26 | + | |
27 | + database_guid = self.para.get("database_guid") | |
28 | + catalog_guid = self.para.get("catalog_guid") | |
29 | + table_type = self.para.get("table_type") | |
30 | + | |
31 | + start_time = self.para.get("start_time") | |
32 | + end_time = self.para.get("end_time") | |
33 | + | |
34 | + | |
35 | + | |
36 | + recursion = int(self.para.get("recursion","0")) | |
37 | + | |
38 | + sort = int(self.para.get("sort","1")) | |
39 | + if sort.__eq__(1): | |
40 | + tables = Table.query.order_by(Table.update_time.desc()) | |
41 | + else: | |
42 | + tables = Table.query.order_by(Table.name) | |
43 | + | |
44 | + if database_guid: | |
45 | + tables = tables.filter_by(database_guid=database_guid) | |
46 | + if catalog_guid: | |
47 | + | |
48 | + if recursion.__eq__(0): | |
49 | + tables = tables.filter_by(catalog_guid=catalog_guid) | |
50 | + else: | |
51 | + #所有子目录id | |
52 | + catalog_guids = [c.guid for c in Catalog.query.filter(Catalog.path.like("%" + catalog_guid + "%")).all()] | |
53 | + tables = tables.filter(Table.catalog_guid.in_(catalog_guids)) | |
54 | + | |
55 | + if table_type: | |
56 | + tables = tables.filter_by(table_type=table_type) | |
57 | + if start_time and end_time: | |
58 | + tables = tables.filter(Table.create_time.between(start_time, end_time)) | |
59 | + | |
60 | + # 并集 | |
61 | + if alias and name: | |
62 | + tables = tables.filter(or_(Table.alias.like("%" + alias + "%") , Table.name.like("%" + name + "%"))) | |
63 | + else: | |
64 | + if alias: | |
65 | + tables = tables.filter(Table.alias.like("%" + alias + "%")) | |
66 | + if name: | |
67 | + tables = tables.filter(Table.name.like("%" + name + "%")) | |
68 | + res["data"]={} | |
69 | + res["data"]["count"] = tables.count() | |
70 | + tables = tables.limit(page_size).offset(page_index * page_size).all() | |
71 | + res["data"]["list"]=[] | |
72 | + | |
73 | + for t in tables: | |
74 | + res["data"]["list"].append(ModelVisitor.table_to_json(t)) | |
75 | + | |
76 | + res["result"] = True | |
77 | + except Exception as e: | |
78 | + raise e | |
79 | + return res | |
80 | + | |
81 | + | |
82 | + | |
83 | + | |
84 | + | |
85 | + api_doc={ | |
86 | + "tags":["管理接口"], | |
87 | + "parameters":[ | |
88 | + {"name": "page_index", | |
89 | + "in": "formData", | |
90 | + "type": "int", | |
91 | + "description": "页"}, | |
92 | + {"name": "page_size", | |
93 | + "in": "formData", | |
94 | + "type": "int", | |
95 | + "description": "页大小"}, | |
96 | + {"name": "alias", | |
97 | + "in": "formData", | |
98 | + "type": "string", | |
99 | + "description": "表别名"}, | |
100 | + {"name": "name", | |
101 | + "in": "formData", | |
102 | + "type": "string", | |
103 | + "description": "表名"}, | |
104 | + {"name": "table_type", | |
105 | + "in": "formData", | |
106 | + "type": "int", | |
107 | + "description": "点线面123","enum":[1,2,3]}, | |
108 | + {"name": "database_guid", | |
109 | + "in": "formData", | |
110 | + "type": "string", | |
111 | + "description": "数据库guid"}, | |
112 | + {"name": "catalog_guid", | |
113 | + "in": "formData", | |
114 | + "type": "string", | |
115 | + "description": "目录guid"}, | |
116 | + {"name": "recursion", | |
117 | + "in": "formData", | |
118 | + "type": "int", | |
119 | + "description": "是否递归目录,0不递归,1递归","enum":[0,1]}, | |
120 | + {"name": "start_time", | |
121 | + "in": "formData", | |
122 | + "type": "string", | |
123 | + "description": "开始时间 2020-12-12 00:00:00"}, | |
124 | + {"name": "end_time", | |
125 | + "in": "formData", | |
126 | + "type": "string", | |
127 | + "description": "结束时间"}, | |
128 | + {"name": "sort", | |
129 | + "in": "formData", | |
130 | + "type": "int", | |
131 | + "description": "排序","enum":[1,2]}, | |
132 | + ], | |
133 | + "responses":{ | |
134 | + 200:{ | |
135 | + "schema":{ | |
136 | + "properties":{ | |
137 | + } | |
138 | + } | |
139 | + } | |
140 | + } | |
141 | + } | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_refresh.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | + | |
7 | +import traceback | |
8 | +from app.models import Table,Database,DES,Columns,db,TableVacuate | |
9 | + | |
10 | +from osgeo.ogr import DataSource,FeatureDefn,FieldDefn,Layer | |
11 | + | |
12 | +import datetime | |
13 | +import uuid | |
14 | + | |
15 | +from sqlalchemy.orm import Session | |
16 | + | |
17 | +from app.util.component.PGUtil import PGUtil | |
18 | +from app.util.component.ModelVisitor import ModelVisitor | |
19 | +from app.util.component.StructuredPrint import StructurePrint | |
20 | +from app.util.component.ApiTemplate import ApiTemplate | |
21 | +from app.util.component.GeometryAdapter import GeometryAdapter | |
22 | + | |
23 | +class Api(ApiTemplate): | |
24 | + api_name = "数据刷新" | |
25 | + def process(self): | |
26 | + | |
27 | + res = {} | |
28 | + pg_ds =None | |
29 | + data_session=None | |
30 | + this_time = datetime.datetime.now() | |
31 | + try: | |
32 | + | |
33 | + database_guid = self.para.get("database_guid") | |
34 | + database = Database.query.filter_by(guid=database_guid).one_or_none() | |
35 | + | |
36 | + if not database: | |
37 | + raise Exception("数据库不存在!") | |
38 | + spatial_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type!=0).all() | |
39 | + spatial_tables_names = [table.name for table in spatial_tables] | |
40 | + db_tables_names = [] | |
41 | + | |
42 | + | |
43 | + pg_ds: DataSource = PGUtil.open_pg_data_source(1, DES.decode(database.sqlalchemy_uri)) | |
44 | + # 更新空间表 | |
45 | + # 增加表 | |
46 | + self.add_spatail_table(database, pg_ds, spatial_tables_names, db_tables_names, this_time) | |
47 | + | |
48 | + # 删除/修改表 | |
49 | + self.edit_spatial_table(pg_ds, spatial_tables, db_tables_names, this_time) | |
50 | + | |
51 | + # 处理抽稀表 | |
52 | + self.deal_vacuate_table(pg_ds,database.guid) | |
53 | + | |
54 | + | |
55 | + # 空间表处理完毕 | |
56 | + db.session.commit() | |
57 | + | |
58 | + | |
59 | + # 注册普通表 | |
60 | + # 实体库连接 | |
61 | + data_session: Session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri)) | |
62 | + | |
63 | + # 空间表 | |
64 | + spatial_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type!=0).all() | |
65 | + spatial_tables_names = [table.name for table in spatial_tables] | |
66 | + | |
67 | + | |
68 | + #原有普通表 | |
69 | + common_tables = Table.query.order_by(Table.create_time.desc()).filter_by(database_guid=database_guid).filter(Table.table_type==0).all() | |
70 | + origin_common_tables_name = [table.name for table in common_tables] | |
71 | + | |
72 | + | |
73 | + # 现有普通表 | |
74 | + real_common_tables_name =[] | |
75 | + # 只注册public中的表 | |
76 | + common_result = data_session.execute( | |
77 | + "select relname as tabname from pg_class c where relkind = 'r' and relnamespace=2200 and relname not like 'pg_%' and relname not like 'sql_%' order by relname").fetchall() | |
78 | + for re in common_result: | |
79 | + table_name = re[0] | |
80 | + if table_name not in spatial_tables_names and (not table_name.__contains__("_vacuate_")): | |
81 | + real_common_tables_name.append(table_name) | |
82 | + | |
83 | + # 增加新普通表 | |
84 | + | |
85 | + self.add_common_table(data_session, database_guid, real_common_tables_name, origin_common_tables_name, | |
86 | + this_time) | |
87 | + | |
88 | + | |
89 | + #删除、修改普通表 | |
90 | + self.edit_common_table(data_session, database_guid, real_common_tables_name, origin_common_tables_name, | |
91 | + this_time) | |
92 | + | |
93 | + db.session.commit() | |
94 | + res["msg"] = "刷新数据成功!" | |
95 | + res["result"] = True | |
96 | + | |
97 | + except Exception as e: | |
98 | + raise e | |
99 | + | |
100 | + finally: | |
101 | + if pg_ds: | |
102 | + pg_ds.Destroy() | |
103 | + if data_session: | |
104 | + data_session.close() | |
105 | + | |
106 | + return res | |
107 | + | |
108 | + | |
109 | + def add_spatail_table(self,database,pg_ds,spatial_tables_names,db_tables_names,this_time): | |
110 | + # 更新空间表 | |
111 | + # 增加表 | |
112 | + | |
113 | + for i in range(pg_ds.GetLayerCount()): | |
114 | + layer: Layer = pg_ds.GetLayer(i) | |
115 | + geom_column = layer.GetGeometryColumn() | |
116 | + db_tables_names.append(layer.GetName()) | |
117 | + if not geom_column: | |
118 | + continue | |
119 | + if layer.GetName() not in spatial_tables_names: | |
120 | + l_name = layer.GetName() | |
121 | + | |
122 | + # 只注册public的空间表,其他表空间的表名会有. | |
123 | + if layer.GetName().__contains__("."): | |
124 | + continue | |
125 | + | |
126 | + if layer.GetName().__contains__("_vacuate_"): | |
127 | + continue | |
128 | + | |
129 | + # 范围统计和数量统计以100w为界限 | |
130 | + query_count_layer: Layer = pg_ds.ExecuteSQL( | |
131 | + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format( | |
132 | + l_name)) | |
133 | + feature_count = query_count_layer.GetFeature(0).GetField("ec") | |
134 | + # 要素少于100w可以精确统计 | |
135 | + if feature_count < 1000000: | |
136 | + feature_count = layer.GetFeatureCount() | |
137 | + ext = layer.GetExtent() | |
138 | + else: | |
139 | + query_ext_layer: Layer = pg_ds.ExecuteSQL( | |
140 | + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name, | |
141 | + layer.GetGeometryColumn())) | |
142 | + ext = query_ext_layer.GetExtent() | |
143 | + if ext[0] < 360: | |
144 | + ext = [round(e, 6) for e in ext] | |
145 | + else: | |
146 | + ext = [round(e, 2) for e in ext] | |
147 | + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3]) | |
148 | + | |
149 | + StructurePrint.print("空间表增加!") | |
150 | + | |
151 | + table_guid = uuid.uuid1().__str__() | |
152 | + table = Table(guid=table_guid, | |
153 | + database_guid=database.guid, | |
154 | + alias=layer.GetName(), | |
155 | + name=layer.GetName(), create_time=this_time, update_time=this_time, | |
156 | + table_type=GeometryAdapter.get_table_type(layer.GetGeomType()), | |
157 | + extent=extent, | |
158 | + feature_count=feature_count | |
159 | + ) | |
160 | + db.session.add(table) | |
161 | + | |
162 | + feature_defn: FeatureDefn = layer.GetLayerDefn() | |
163 | + | |
164 | + for i in range(feature_defn.GetFieldCount()): | |
165 | + field_defn: FieldDefn = feature_defn.GetFieldDefn(i) | |
166 | + field_name = field_defn.GetName().lower() | |
167 | + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__( | |
168 | + "") else field_defn.GetAlternativeName() | |
169 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid, | |
170 | + name=field_name, alias=field_alias, create_time=this_time, update_time=this_time) | |
171 | + db.session.add(column) | |
172 | + | |
173 | + def deal_vacuate_table(self,pg_ds,database_guid): | |
174 | + | |
175 | + | |
176 | + for i in range(pg_ds.GetLayerCount()): | |
177 | + layer: Layer = pg_ds.GetLayer(i) | |
178 | + geom_column = layer.GetGeometryColumn() | |
179 | + | |
180 | + if not geom_column: | |
181 | + continue | |
182 | + | |
183 | + | |
184 | + | |
185 | + if layer.GetName().__contains__("_vacuate_"): | |
186 | + l_name = layer.GetName() | |
187 | + | |
188 | + | |
189 | + base_layer_name = l_name.split("_vacuate_")[0] | |
190 | + level = l_name.split("_")[-2] | |
191 | + | |
192 | + pixel_distance_str: str ="0" | |
193 | + try: | |
194 | + pixel_distance_str: str = l_name.split("_")[-1] | |
195 | + if pixel_distance_str.startswith("0"): | |
196 | + pixel_distance_str = "0.{}".format(pixel_distance_str) | |
197 | + except: | |
198 | + pass | |
199 | + | |
200 | + base_table =Table.query.filter_by(name=base_layer_name,database_guid=database_guid).one_or_none() | |
201 | + if base_table: | |
202 | + if not TableVacuate.query.filter_by(table_guid=base_table.guid,name=l_name).one_or_none(): | |
203 | + table_vacuate = TableVacuate(guid=uuid.uuid1().__str__(), | |
204 | + table_guid=base_table.guid, | |
205 | + level=level, | |
206 | + name=l_name, | |
207 | + pixel_distance=float(pixel_distance_str)) | |
208 | + db.session.add(table_vacuate) | |
209 | + else: | |
210 | + kk=1 | |
211 | + | |
212 | + | |
213 | + | |
214 | + | |
215 | + def edit_spatial_table(self,pg_ds,spatial_tables,db_tables_names,this_time): | |
216 | + | |
217 | + for table in spatial_tables: | |
218 | + | |
219 | + # 删除表 | |
220 | + if table.name not in db_tables_names: | |
221 | + StructurePrint.print("空间表减少!") | |
222 | + db.session.delete(table) | |
223 | + # 修改表 | |
224 | + else: | |
225 | + layer: Layer = pg_ds.GetLayerByName(table.name) | |
226 | + l_name = layer.GetName() | |
227 | + | |
228 | + # 只注册public的空间表,其他表空间的表名会有. | |
229 | + if layer.GetName().__contains__("."): | |
230 | + continue | |
231 | + | |
232 | + if layer.GetName().__contains__("_vacuate_"): | |
233 | + continue | |
234 | + | |
235 | + columns = table.relate_columns | |
236 | + columns_names = [column.name for column in columns] | |
237 | + feature_defn: FeatureDefn = layer.GetLayerDefn() | |
238 | + db_columns_names = [] | |
239 | + | |
240 | + # 增加列 | |
241 | + for i in range(feature_defn.GetFieldCount()): | |
242 | + field_defn: FieldDefn = feature_defn.GetFieldDefn(i) | |
243 | + field_name = field_defn.GetName().lower() | |
244 | + db_columns_names.append(field_name) | |
245 | + | |
246 | + if field_name not in columns_names: | |
247 | + StructurePrint.print("{}空间表属性增加!".format(table.name)) | |
248 | + field_alias = field_name if field_defn.GetAlternativeName() is None or field_defn.GetAlternativeName().__eq__( | |
249 | + "") else field_defn.GetAlternativeName() | |
250 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table.guid, | |
251 | + name=field_name, alias=field_alias, create_time=this_time, | |
252 | + update_time=this_time) | |
253 | + db.session.add(column) | |
254 | + | |
255 | + # 删除列 | |
256 | + for column in columns: | |
257 | + if column.name not in db_columns_names: | |
258 | + StructurePrint.print("{}空间表属性减少!".format(table.name)) | |
259 | + db.session.delete(column) | |
260 | + | |
261 | + | |
262 | + | |
263 | + # 范围统计和数量统计以100w为界限 | |
264 | + query_count_layer: Layer = pg_ds.ExecuteSQL( | |
265 | + "SELECT reltuples::bigint AS ec FROM pg_class WHERE oid = 'public.{}'::regclass".format( | |
266 | + l_name)) | |
267 | + feature_count = query_count_layer.GetFeature(0).GetField("ec") | |
268 | + # 要素少于100w可以精确统计 | |
269 | + if feature_count < 1000000: | |
270 | + feature_count = layer.GetFeatureCount() | |
271 | + ext = layer.GetExtent() | |
272 | + else: | |
273 | + query_ext_layer: Layer = pg_ds.ExecuteSQL( | |
274 | + "select geometry(ST_EstimatedExtent('public', '{}','{}'))".format(l_name, | |
275 | + layer.GetGeometryColumn())) | |
276 | + ext = query_ext_layer.GetExtent() | |
277 | + if ext[0] < 360: | |
278 | + ext = [round(e, 6) for e in ext] | |
279 | + else: | |
280 | + ext = [round(e, 2) for e in ext] | |
281 | + extent = "{},{},{},{}".format(ext[0], ext[1], ext[2], ext[3]) | |
282 | + | |
283 | + # 修改要素量 | |
284 | + if not table.feature_count.__eq__(feature_count): | |
285 | + StructurePrint.print("{}空间表要素!".format(table.name)) | |
286 | + Table.query.filter_by(guid=table.guid).update({"feature_count": feature_count, | |
287 | + "extent": extent}) | |
288 | + | |
289 | + | |
290 | + def add_common_table(self,data_session,database_guid,real_common_tables_name,origin_common_tables_name,this_time): | |
291 | + for table_name in real_common_tables_name: | |
292 | + if table_name not in origin_common_tables_name: | |
293 | + StructurePrint.print("{}非空间表增加!".format(table_name)) | |
294 | + table_guid = uuid.uuid1().__str__() | |
295 | + count = data_session.execute('select count(*) from "{}"'.format(table_name)).fetchone()[0] | |
296 | + | |
297 | + table = Table(guid=table_guid, | |
298 | + database_guid=database_guid, | |
299 | + name=table_name, create_time=this_time, update_time=this_time, | |
300 | + table_type=0, | |
301 | + feature_count=count | |
302 | + ) | |
303 | + | |
304 | + db.session.add(table) | |
305 | + | |
306 | + sql = ''' | |
307 | + SELECT | |
308 | + a.attnum, | |
309 | + a.attname AS field | |
310 | + FROM | |
311 | + pg_class c, | |
312 | + pg_attribute a, | |
313 | + pg_type t | |
314 | + WHERE | |
315 | + c.relname = '{}' | |
316 | + and a.attnum > 0 | |
317 | + and a.attrelid = c.oid | |
318 | + and a.atttypid = t.oid | |
319 | + ORDER BY a.attnum | |
320 | + '''.format(table_name) | |
321 | + | |
322 | + cols = data_session.execute(sql).fetchall() | |
323 | + for col in cols: | |
324 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table_guid, | |
325 | + name=col[1], create_time=this_time, update_time=this_time) | |
326 | + db.session.add(column) | |
327 | + | |
328 | + # 删除不存在的表 | |
329 | + for n in origin_common_tables_name: | |
330 | + if n not in real_common_tables_name: | |
331 | + table = Table.query.filter_by(name=n).filter_by(database_guid=database_guid).one_or_none() | |
332 | + if table: | |
333 | + db.session.delete(table) | |
334 | + | |
335 | + def edit_common_table(self,data_session,database_guid,real_common_tables_name,origin_common_tables_name,this_time): | |
336 | + for table_name in origin_common_tables_name: | |
337 | + table = Table.query.filter_by(name=table_name).filter_by(database_guid=database_guid).one_or_none() | |
338 | + if table: | |
339 | + if table_name not in real_common_tables_name: | |
340 | + StructurePrint.print("{}非空间表减少!".format(table_name)) | |
341 | + db.session.delete(table) | |
342 | + # 修改表 | |
343 | + else: | |
344 | + columns = table.relate_columns | |
345 | + columns_names = [column.name for column in columns] | |
346 | + | |
347 | + sql = ''' | |
348 | + SELECT | |
349 | + a.attnum, | |
350 | + a.attname AS field | |
351 | + FROM | |
352 | + pg_class c, | |
353 | + pg_attribute a, | |
354 | + pg_type t | |
355 | + WHERE | |
356 | + c.relname = '{}' | |
357 | + and a.attnum > 0 | |
358 | + and a.attrelid = c.oid | |
359 | + and a.atttypid = t.oid | |
360 | + ORDER BY a.attnum | |
361 | + '''.format(table_name) | |
362 | + | |
363 | + cols = data_session.execute(sql).fetchall() | |
364 | + real_cols_name = [col[1] for col in cols] | |
365 | + | |
366 | + # 属性增加 | |
367 | + for col in real_cols_name: | |
368 | + if col not in columns_names: | |
369 | + StructurePrint.print("{}表要素属性增加!".format(table_name)) | |
370 | + column = Columns(guid=uuid.uuid1().__str__(), table_guid=table.guid, | |
371 | + name=col, create_time=this_time, update_time=this_time) | |
372 | + db.session.add(column) | |
373 | + | |
374 | + # 属性减少 | |
375 | + for column in columns: | |
376 | + if column.name not in real_cols_name: | |
377 | + StructurePrint.print("{}表要素属性减少!".format(table_name)) | |
378 | + db.session.delete(column) | |
379 | + | |
380 | + # 修改要素量 | |
381 | + sql = 'select count(*) from "{}"'.format(table_name) | |
382 | + | |
383 | + count = data_session.execute(sql).fetchone()[0] | |
384 | + if not table.feature_count.__eq__(count): | |
385 | + StructurePrint.print("{}表要素变化!".format(table_name)) | |
386 | + Table.query.filter_by(guid=table.guid).update({"feature_count": count}) | |
387 | + | |
388 | + | |
389 | + | |
390 | + | |
391 | + api_doc={ | |
392 | + "tags":["管理接口"], | |
393 | + "parameters":[ | |
394 | + {"name": "database_guid", | |
395 | + "in": "formData", | |
396 | + "type": "string", | |
397 | + "description": "数据库guid", | |
398 | + "required":"true"}, | |
399 | + {"name": "user", | |
400 | + "in": "formData", | |
401 | + "type": "string", | |
402 | + "description": "用户"} | |
403 | + | |
404 | + ], | |
405 | + "responses":{ | |
406 | + 200:{ | |
407 | + "schema":{ | |
408 | + "properties":{ | |
409 | + } | |
410 | + } | |
411 | + } | |
412 | + } | |
413 | +} | |
\ No newline at end of file | ... | ... |
app/modules/manager/table_view.py
0 → 100644
1 | +#author: 4N | |
2 | +#createtime: 2021/1/27 | |
3 | +#email: nheweijun@sina.com | |
4 | + | |
5 | + | |
6 | +import datetime | |
7 | +import traceback | |
8 | +from app.models import Table,Database,DES | |
9 | + | |
10 | +from sqlalchemy.engine import ResultProxy | |
11 | +from app.util.component.ApiTemplate import ApiTemplate | |
12 | +from app.util.component.PGUtil import PGUtil | |
13 | + | |
14 | +class Api(ApiTemplate): | |
15 | + api_name = "数据浏览" | |
16 | + def process(self): | |
17 | + | |
18 | + res = {} | |
19 | + res["data"] = {} | |
20 | + db_session=None | |
21 | + try: | |
22 | + table_guid = self.para.get("guid") | |
23 | + limit = int(self.para.get("limit",50)) | |
24 | + offset = int(self.para.get("offset", 0)) | |
25 | + table :Table= Table.query.filter_by(guid=table_guid).one_or_none() | |
26 | + | |
27 | + if not table: | |
28 | + raise Exception("数据不存在!") | |
29 | + | |
30 | + database:Database = table.relate_database | |
31 | + | |
32 | + db_session = PGUtil.get_db_session(DES.decode(database.sqlalchemy_uri)) | |
33 | + | |
34 | + geom_col = PGUtil.get_geo_column(table.name,db_session) | |
35 | + | |
36 | + query_result : ResultProxy = db_session.execute('select * from "{}" limit {} offset {}'.format(table.name,limit,offset)) | |
37 | + | |
38 | + res["data"]["count"]=PGUtil.get_table_count(table.name,db_session) | |
39 | + | |
40 | + res["data"]["list"]=[] | |
41 | + for row_proxy in query_result: | |
42 | + d = {} | |
43 | + for column, value in row_proxy.items(): | |
44 | + #跳过空间列 | |
45 | + if geom_col: | |
46 | + if column.__eq__(geom_col): | |
47 | + continue | |
48 | + #格式化时间列 | |
49 | + if isinstance(value, datetime.datetime): | |
50 | + d[column] = value.strftime('%Y-%m-%d %H:%M:%S') | |
51 | + else: | |
52 | + d[column]=value | |
53 | + res["data"]["list"].append(d) | |
54 | + res["result"]=True | |
55 | + except Exception as e: | |
56 | + raise Exception("数据库连接失败!") | |
57 | + finally: | |
58 | + if db_session: | |
59 | + db_session.close() | |
60 | + return res | |
61 | + | |
62 | + api_doc={ | |
63 | + "tags":["管理接口"], | |
64 | + "parameters":[ | |
65 | + {"name": "guid", | |
66 | + "in": "formData", | |
67 | + "type": "string", | |
68 | + "description": "表guid","required":"true"}, | |
69 | + {"name": "limit", | |
70 | + "in": "formData", | |
71 | + "type": "int" | |
72 | + }, | |
73 | + {"name": "offset", | |
74 | + "in": "formData", | |
75 | + "type": "int"}, | |
76 | + ], | |
77 | + "responses":{ | |
78 | + 200:{ | |
79 | + "schema":{ | |
80 | + "properties":{ | |
81 | + } | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | + } | |
\ No newline at end of file | ... | ... |
app/modules/monitor/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/5/18 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from flasgger import swag_from | |
8 | +from flask import Blueprint | |
9 | +from app.util import BlueprintApi | |
10 | + | |
11 | +from . import monitor_info | |
12 | +class DataManager(): | |
13 | + | |
14 | + bp = Blueprint("Monitor", __name__, url_prefix="/API/Monitor") | |
15 | + | |
16 | + @staticmethod | |
17 | + @bp.route('/Info', methods=['GET']) | |
18 | + @swag_from(monitor_info.Api.api_doc) | |
19 | + def monitor_info(): | |
20 | + """ | |
21 | + 性能监控 | |
22 | + """ | |
23 | + return monitor_info.Api().result | |
\ No newline at end of file | ... | ... |
app/modules/monitor/models/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/6/11 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime, Time | |
8 | +from app.models import db | |
9 | + | |
10 | +class TestModel(db.Model): | |
11 | + ''' | |
12 | + 数据表元数据 | |
13 | + ''' | |
14 | + __tablename__ = 'test_model' | |
15 | + guid = Column(String(256), primary_key=True) | |
\ No newline at end of file | ... | ... |
app/modules/monitor/monitor_info.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/7/9 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import * | |
7 | + | |
8 | +from app.util.component.ApiTemplate import ApiTemplate | |
9 | +from app.util.component.ModelVisitor import ModelVisitor | |
10 | +import psutil | |
11 | + | |
12 | +class Api(ApiTemplate): | |
13 | + api_name = "监控" | |
14 | + | |
15 | + def process(self): | |
16 | + | |
17 | + # 返回结果 | |
18 | + res = {} | |
19 | + res["data"] = {} | |
20 | + try: | |
21 | + # 业务逻辑 | |
22 | + cpu_count = psutil.cpu_count(False) | |
23 | + cpu_per = int(psutil.cpu_percent()) | |
24 | + res["data"]["cpu"] ={"count":cpu_count,"percent":"{}%".format(cpu_per)} | |
25 | + | |
26 | + | |
27 | + mem_total = int(psutil.virtual_memory()[0]) | |
28 | + mem_used = int(psutil.virtual_memory()[3]) | |
29 | + mem_per = int(psutil.virtual_memory()[2]) | |
30 | + | |
31 | + res["data"]["memory"] = { | |
32 | + 'total': self.format_value(mem_total), | |
33 | + 'used': self.format_value(mem_used), | |
34 | + 'percent': "{}%".format(mem_per) | |
35 | + } | |
36 | + | |
37 | + | |
38 | + | |
39 | + network_sent = int(psutil.net_io_counters()[0] / 8 ) # 每秒接受的kb | |
40 | + network_recv = int(psutil.net_io_counters()[1] / 8 ) | |
41 | + | |
42 | + res["data"]["network"] = { | |
43 | + 'sent': self.format_value(network_sent), | |
44 | + 'recv': self.format_value(network_recv) | |
45 | + } | |
46 | + | |
47 | + | |
48 | + res["result"] = True | |
49 | + except Exception as e: | |
50 | + raise e | |
51 | + return res | |
52 | + | |
53 | + api_doc = { | |
54 | + | |
55 | + "tags": ["监控接口"], | |
56 | + "parameters": [ | |
57 | + ], | |
58 | + "responses": { | |
59 | + 200: { | |
60 | + "schema": { | |
61 | + "properties": { | |
62 | + } | |
63 | + } | |
64 | + } | |
65 | + } | |
66 | + } | |
67 | + | |
68 | + def format_value(self,value): | |
69 | + if value>1024**3: | |
70 | + value = "{}GB".format(format(value/1024.0**3,'.1f')) | |
71 | + elif value>1024**2: | |
72 | + value = "{}MB".format(format(value / 1024.0 ** 2, '.1f')) | |
73 | + elif value>1024: | |
74 | + value = "{}KB".format(format(value / 1024.0, '.1f')) | |
75 | + else: | |
76 | + value = "{}B".format(format(value, '.1f')) | |
77 | + return value | |
78 | + | |
79 | +if __name__ == '__main__': | |
80 | + api = Api() | |
81 | + api.process() | ... | ... |
app/modules/task/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2021/3/1 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.util import BlueprintApi | |
7 | +from flasgger import swag_from | |
8 | +from flask import Blueprint | |
9 | +from . import task_list | |
10 | +from . import task_detail | |
11 | +from . import task_delete | |
12 | +from . import task_count | |
13 | + | |
14 | +class DataManager(BlueprintApi): | |
15 | + | |
16 | + bp = Blueprint("Task", __name__, url_prefix="/API/Task") | |
17 | + | |
18 | + | |
19 | + @staticmethod | |
20 | + @bp.route('/List', methods=['POST']) | |
21 | + @swag_from(task_list.Api.api_doc) | |
22 | + def api_task_list(): | |
23 | + """ | |
24 | + 任务列表 | |
25 | + """ | |
26 | + return task_list.Api().result | |
27 | + | |
28 | + @staticmethod | |
29 | + @bp.route('/Detail', methods=['POST']) | |
30 | + @swag_from(task_detail.Api.api_doc) | |
31 | + def api_task_detail(): | |
32 | + """ | |
33 | + 任务详情 | |
34 | + """ | |
35 | + return task_detail.Api().result | |
36 | + | |
37 | + @staticmethod | |
38 | + @bp.route('/Delete', methods=['POST']) | |
39 | + @swag_from(task_delete.Api.api_doc) | |
40 | + def task_delete(): | |
41 | + """ | |
42 | + 删除任务 | |
43 | + """ | |
44 | + return task_delete.Api().result | |
45 | + | |
46 | + @staticmethod | |
47 | + @bp.route('/Count', methods=['POST']) | |
48 | + @swag_from(task_count.Api.api_doc) | |
49 | + def task_count(): | |
50 | + """ | |
51 | + 任务统计 | |
52 | + """ | |
53 | + return task_count.Api().result | |
54 | + | |
55 | + | ... | ... |
app/modules/task/models/__init__.py
0 → 100644
app/modules/task/task_count.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/9/4 | |
4 | +# email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import db,Task | |
7 | + | |
8 | +from sqlalchemy import func | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | + | |
11 | + | |
12 | +class Api(ApiTemplate): | |
13 | + api_name = "任务统计" | |
14 | + def process(self): | |
15 | + res= {} | |
16 | + try: | |
17 | + tasks = db.session.query(func.count(Task.state), Task.state) | |
18 | + creator = self.para.get("creator") | |
19 | + | |
20 | + if creator: | |
21 | + tasks = tasks.filter_by(creator=creator) | |
22 | + | |
23 | + tasks = tasks.group_by(Task.state).all() | |
24 | + res["data"] = {} | |
25 | + for task in tasks: | |
26 | + res["data"][task[1].__str__()] = task[0] | |
27 | + | |
28 | + for status in ["-1", "0", "1", "2"]: | |
29 | + if not res["data"].get(status): | |
30 | + res["data"][status] = 0 | |
31 | + | |
32 | + res["result"] = True | |
33 | + except Exception as e: | |
34 | + raise e | |
35 | + return res | |
36 | + | |
37 | + | |
38 | + | |
39 | + api_doc = { | |
40 | + "tags": ["任务接口"], | |
41 | + "parameters": [ | |
42 | + {"name": "creator", | |
43 | + "in": "formData", | |
44 | + "type": "string"} | |
45 | + ], | |
46 | + "responses": { | |
47 | + 200: { | |
48 | + "schema": { | |
49 | + "properties": { | |
50 | + } | |
51 | + } | |
52 | + } | |
53 | + } | |
54 | + } | |
\ No newline at end of file | ... | ... |
app/modules/task/task_delete.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/9/4 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import db,Task | |
7 | + | |
8 | + | |
9 | +from app.util.component.ApiTemplate import ApiTemplate | |
10 | + | |
11 | + | |
12 | +class Api(ApiTemplate): | |
13 | + api_name = "删除任务" | |
14 | + def para_check(self): | |
15 | + pass | |
16 | + | |
17 | + def process(self): | |
18 | + res = {} | |
19 | + try: | |
20 | + task_guid = self.para.get("task_guid") | |
21 | + state = self.para.get("state") | |
22 | + creator = self.para.get("creator") | |
23 | + | |
24 | + tasks = Task.query | |
25 | + if task_guid: | |
26 | + tasks = tasks.filter_by(guid=task_guid) | |
27 | + if state: | |
28 | + state = int(state) | |
29 | + tasks = tasks.filter_by(state=state) | |
30 | + if creator: | |
31 | + tasks = tasks.filter_by(creator=creator) | |
32 | + tasks = tasks.all() | |
33 | + | |
34 | + if not tasks: | |
35 | + res["result"] = False | |
36 | + res["msg"] = "数据不存在!" | |
37 | + return res | |
38 | + else: | |
39 | + for task in tasks: | |
40 | + db.session.delete(task) | |
41 | + db.session.commit() | |
42 | + res["msg"] = "删除成功!" | |
43 | + res["result"] = True | |
44 | + except Exception as e: | |
45 | + db.session.rollback() | |
46 | + raise e | |
47 | + return res | |
48 | + | |
49 | + | |
50 | + api_doc = { | |
51 | + "tags": ["任务接口"], | |
52 | + "parameters": [ | |
53 | + {"name": "task_guid", | |
54 | + "in": "formData", | |
55 | + "type": "string"}, | |
56 | + {"name": "state", | |
57 | + "in": "formData", | |
58 | + "type": "string"}, | |
59 | + {"name": "creator", | |
60 | + "in": "formData", | |
61 | + "type": "string"}, | |
62 | + | |
63 | + ], | |
64 | + "responses": { | |
65 | + 200: { | |
66 | + "schema": { | |
67 | + "properties": { | |
68 | + } | |
69 | + } | |
70 | + } | |
71 | + } | |
72 | + } | |
\ No newline at end of file | ... | ... |
app/modules/task/task_detail.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/9/4 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from app.models import db,Task,Process | |
7 | +from app.util.component.ApiTemplate import ApiTemplate | |
8 | +from app.util.component.ModelVisitor import ModelVisitor | |
9 | +class Api(ApiTemplate): | |
10 | + api_name = "任务详情" | |
11 | + def para_check(self): | |
12 | + if not self.para.get("task_guid"): | |
13 | + raise Exception("缺乏task_guid参数。") | |
14 | + | |
15 | + def process(self): | |
16 | + | |
17 | + res = {} | |
18 | + try: | |
19 | + task_guid = self.para.get("task_guid") | |
20 | + task:Task = Task.query.filter_by(guid=task_guid).one_or_none() | |
21 | + | |
22 | + if not task : | |
23 | + raise Exception("任务不存在!") | |
24 | + else: | |
25 | + task_processes = task.relate_processes.order_by(Process.time).all() | |
26 | + res["data"] = ModelVisitor.objects_to_jsonarray(task_processes) | |
27 | + | |
28 | + res["result"] = True | |
29 | + except Exception as e: | |
30 | + raise e | |
31 | + return res | |
32 | + | |
33 | + api_doc={ | |
34 | + "tags":["任务接口"], | |
35 | + "parameters":[ | |
36 | + {"name": "task_guid", | |
37 | + "in": "formData", | |
38 | + "type": "string"}, | |
39 | + | |
40 | + ], | |
41 | + "responses":{ | |
42 | + 200:{ | |
43 | + "schema":{ | |
44 | + "properties":{ | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | + } | |
\ No newline at end of file | ... | ... |
app/modules/task/task_list.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +# author: 4N | |
3 | +# createtime: 2020/9/4 | |
4 | +# email: nheweijun@sina.com | |
5 | +import datetime | |
6 | + | |
7 | + | |
8 | + | |
9 | +from app.models import Task | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | +from app.util.component.ModelVisitor import ModelVisitor | |
12 | +class Api(ApiTemplate): | |
13 | + api_name = "任务列表" | |
14 | + def para_check(self): | |
15 | + pass | |
16 | + | |
17 | + def process(self): | |
18 | + | |
19 | + res = {} | |
20 | + res["data"] = {} | |
21 | + try: | |
22 | + parameter = self.para | |
23 | + page_index = int(parameter.get("page_index", "0")) | |
24 | + page_size = int(parameter.get("page_size", "10")) | |
25 | + name = parameter.get("name") | |
26 | + tasks = Task.query.order_by(Task.create_time.desc()) | |
27 | + start_time = parameter.get("start_time") | |
28 | + end_time = parameter.get("end_time") | |
29 | + creator = parameter.get("creator") | |
30 | + | |
31 | + state = parameter.get("state") | |
32 | + | |
33 | + if creator: | |
34 | + tasks = tasks.filter_by(creator=creator) | |
35 | + if start_time and end_time: | |
36 | + tasks = tasks.filter(Task.create_time.between(start_time, end_time)) | |
37 | + if name: | |
38 | + tasks = tasks.filter(Task.name.like("%" + name + "%")) | |
39 | + if state is not None: | |
40 | + state_list = state.split(",") | |
41 | + state_list =[int(x) for x in state_list] | |
42 | + | |
43 | + tasks = tasks.filter(Task.state.in_(state_list)) | |
44 | + | |
45 | + res["data"]["count"] = tasks.count() | |
46 | + | |
47 | + tasks = tasks.limit(page_size).offset(page_index * page_size).all() | |
48 | + | |
49 | + res["data"]["list"] = [ModelVisitor.task_to_json(task) for task in tasks] | |
50 | + res["result"] = True | |
51 | + except Exception as e: | |
52 | + raise e | |
53 | + return res | |
54 | + | |
55 | + | |
56 | + | |
57 | + api_doc={ | |
58 | + "tags":["任务接口"], | |
59 | + "parameters":[ | |
60 | + {"name": "page_index", | |
61 | + "in": "formData", | |
62 | + "type": "int", | |
63 | + "default": "0"}, | |
64 | + {"name": "page_size", | |
65 | + "in": "formData", | |
66 | + "type": "int", | |
67 | + "default": "10"}, | |
68 | + {"name": "start_time", | |
69 | + "in": "formData", | |
70 | + "type": "string"}, | |
71 | + {"name": "end_time", | |
72 | + "in": "formData", | |
73 | + "type": "string"}, | |
74 | + {"name": "name", | |
75 | + "in": "formData", | |
76 | + "type": "string"}, | |
77 | + {"name": "creator", | |
78 | + "in": "formData", | |
79 | + "type": "string"}, | |
80 | + {"name": "state", | |
81 | + "in": "formData", | |
82 | + "type": "string"} | |
83 | + ], | |
84 | + "responses":{ | |
85 | + 200:{ | |
86 | + "schema":{ | |
87 | + "properties":{ | |
88 | + } | |
89 | + } | |
90 | + } | |
91 | + } | |
92 | + } | |
\ No newline at end of file | ... | ... |
app/modules/template/__init__.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/8/27 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | +from flask import Blueprint,request | |
7 | +from app.util import BlueprintApi | |
8 | +from . import sample | |
9 | +from flasgger import swag_from | |
10 | + | |
11 | +class Template(BlueprintApi): | |
12 | + | |
13 | + bp = Blueprint("Template", __name__, url_prefix="/API/Template") | |
14 | + | |
15 | + @staticmethod | |
16 | + @bp.route('/Sample', methods=['GET']) | |
17 | + @swag_from(sample.Api.api_doc) | |
18 | + def api_sample(): | |
19 | + """ | |
20 | + 测试接口 | |
21 | + """ | |
22 | + return sample.Api().result | |
23 | + | ... | ... |
app/modules/template/sample.py
0 → 100644
1 | +# coding=utf-8 | |
2 | +#author: 4N | |
3 | +#createtime: 2020/8/27 | |
4 | +#email: nheweijun@sina.com | |
5 | + | |
6 | + | |
7 | +from flask import current_app | |
8 | +import os | |
9 | + | |
10 | +from app.util.component.ApiTemplate import ApiTemplate | |
11 | +from app.util.component.StructuredPrint import StructurePrint | |
12 | + | |
13 | +from flask import current_app | |
14 | +class Api(ApiTemplate): | |
15 | + | |
16 | + def para_check(self): | |
17 | + if self.para.get("content") is None: | |
18 | + raise Exception("缺乏content参数") | |
19 | + | |
20 | + def log(self): | |
21 | + current_app.logger.info('info log') | |
22 | + | |
23 | + def process(self): | |
24 | + | |
25 | + result=dict() | |
26 | + result.update(self.para) | |
27 | + result["root_path"] = os.path.dirname(current_app.instance_path) | |
28 | + return result | |
29 | + | |
30 | + | |
31 | + api_doc= { | |
32 | + "tags": ["测试接口"], | |
33 | + "description": "测试接口", | |
34 | + "parameters": [ | |
35 | + {"name": "content", | |
36 | + "in": "query"} | |
37 | + ], | |
38 | + "responses": { | |
39 | + 200: { | |
40 | + "schema": { | |
41 | + "properties": { | |
42 | + "content": { | |
43 | + "type": "string", | |
44 | + "description": "The name of the user" | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | + } | |
50 | + } | |
51 | + | |
52 | + | ... | ... |
app/static/mapbox-gl.css
0 → 100644
1 | +.mapboxgl-map { | |
2 | + font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif; | |
3 | + overflow: hidden; | |
4 | + position: relative; | |
5 | + -webkit-tap-highlight-color: rgba(0,0,0,0); | |
6 | +} | |
7 | + | |
8 | +.mapboxgl-canvas-container.mapboxgl-interactive, | |
9 | +.mapboxgl-ctrl-nav-compass { | |
10 | + cursor: -webkit-grab; | |
11 | + cursor: -moz-grab; | |
12 | + cursor: grab; | |
13 | +} | |
14 | +.mapboxgl-canvas-container.mapboxgl-interactive:active, | |
15 | +.mapboxgl-ctrl-nav-compass:active { | |
16 | + cursor: -webkit-grabbing; | |
17 | + cursor: -moz-grabbing; | |
18 | + cursor: grabbing; | |
19 | +} | |
20 | + | |
21 | +.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate { | |
22 | + -ms-touch-action: pan-x pan-y; | |
23 | + touch-action: pan-x pan-y; | |
24 | +} | |
25 | +.mapboxgl-canvas-container.mapboxgl-touch-drag-pan { | |
26 | + -ms-touch-action: pinch-zoom; | |
27 | +} | |
28 | +.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan { | |
29 | + -ms-touch-action: none; | |
30 | + touch-action: none; | |
31 | +} | |
32 | +.mapboxgl-ctrl-top-left, | |
33 | +.mapboxgl-ctrl-top-right, | |
34 | +.mapboxgl-ctrl-bottom-left, | |
35 | +.mapboxgl-ctrl-bottom-right { position:absolute; pointer-events:none; z-index:2; } | |
36 | +.mapboxgl-ctrl-top-left { top:0; left:0; } | |
37 | +.mapboxgl-ctrl-top-right { top:0; right:0; } | |
38 | +.mapboxgl-ctrl-bottom-left { bottom:0; left:0; } | |
39 | +.mapboxgl-ctrl-bottom-right { right:0; bottom:0; } | |
40 | + | |
41 | +.mapboxgl-ctrl { clear:both; pointer-events:auto } | |
42 | +.mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; } | |
43 | +.mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; } | |
44 | +.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; } | |
45 | +.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl { margin:0 10px 10px 0; float:right; } | |
46 | + | |
47 | +.mapboxgl-ctrl-group { | |
48 | + border-radius: 4px; | |
49 | + -moz-box-shadow: 0px 0px 2px rgba(0,0,0,0.1); | |
50 | + -webkit-box-shadow: 0px 0px 2px rgba(0,0,0,0.1); | |
51 | + box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1); | |
52 | + overflow: hidden; | |
53 | + background: #fff; | |
54 | +} | |
55 | +.mapboxgl-ctrl-group > button { | |
56 | + width: 30px; | |
57 | + height: 30px; | |
58 | + display: block; | |
59 | + padding: 0; | |
60 | + outline: none; | |
61 | + border: none; | |
62 | + border-bottom: 1px solid #ddd; | |
63 | + box-sizing: border-box; | |
64 | + background-color: rgba(0,0,0,0); | |
65 | + cursor: pointer; | |
66 | +} | |
67 | +/* https://bugzilla.mozilla.org/show_bug.cgi?id=140562 */ | |
68 | +.mapboxgl-ctrl > button::-moz-focus-inner { | |
69 | + border: 0; | |
70 | + padding: 0; | |
71 | +} | |
72 | +.mapboxgl-ctrl > button:last-child { | |
73 | + border-bottom: 0; | |
74 | +} | |
75 | +.mapboxgl-ctrl > button:hover { | |
76 | + background-color: rgba(0,0,0,0.05); | |
77 | +} | |
78 | +.mapboxgl-ctrl-icon, | |
79 | +.mapboxgl-ctrl-icon > .mapboxgl-ctrl-compass-arrow { | |
80 | + speak: none; | |
81 | + -webkit-font-smoothing: antialiased; | |
82 | + -moz-osx-font-smoothing: grayscale; | |
83 | +} | |
84 | +.mapboxgl-ctrl-icon { | |
85 | + padding: 5px; | |
86 | +} | |
87 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out { | |
88 | + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A"); | |
89 | +} | |
90 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in { | |
91 | + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A"); | |
92 | +} | |
93 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate { | |
94 | + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23333%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); | |
95 | +} | |
96 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.mapboxgl-watching { | |
97 | + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%2300f%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); | |
98 | +} | |
99 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-fullscreen { | |
100 | + background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAyMCAyMCIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjAgMjA7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkxIHIxMzcyNSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZnVsbHNjcmVlbi5zdmciPjxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTQxODUiPjxyZGY6UkRGPjxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj48ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD48ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+PGRjOnRpdGxlPjwvZGM6dGl0bGU+PC9jYzpXb3JrPjwvcmRmOlJERj48L21ldGFkYXRhPjxkZWZzCiAgICAgaWQ9ImRlZnM0MTgzIiAvPjxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTQ3MSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI2OTUiCiAgICAgaWQ9Im5hbWVkdmlldzQxODEiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjExLjMxMzcwOCIKICAgICBpbmtzY2FwZTpjeD0iMTQuNjk4MjgiCiAgICAgaW5rc2NhcGU6Y3k9IjEwLjUyNjY4OSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNjk3IgogICAgIGlua3NjYXBlOndpbmRvdy15PSIyOTgiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1wYXRocz0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LW5vZGVzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+PGlua3NjYXBlOmdyaWQKICAgICAgIHR5cGU9Inh5Z3JpZCIKICAgICAgIGlkPSJncmlkNjA3NiIgLz48L3NvZGlwb2RpOm5hbWVkdmlldz48cGF0aAogICAgIGQ9Ik0gNSA0IEMgNC41IDQgNCA0LjUgNCA1IEwgNCA2IEwgNCA5IEwgNC41IDkgTCA1Ljc3NzM0MzggNy4yOTY4NzUgQyA2Ljc3NzEzMTkgOC4wNjAyMTMxIDcuODM1NzY1IDguOTU2NTcyOCA4Ljg5MDYyNSAxMCBDIDcuODI1NzEyMSAxMS4wNjMzIDYuNzc2MTc5MSAxMS45NTE2NzUgNS43ODEyNSAxMi43MDcwMzEgTCA0LjUgMTEgTCA0IDExIEwgNCAxNSBDIDQgMTUuNSA0LjUgMTYgNSAxNiBMIDkgMTYgTCA5IDE1LjUgTCA3LjI3MzQzNzUgMTQuMjA1MDc4IEMgOC4wNDI4OTMxIDEzLjE4Nzg4NiA4LjkzOTU0NDEgMTIuMTMzNDgxIDkuOTYwOTM3NSAxMS4wNjgzNTkgQyAxMS4wNDIzNzEgMTIuMTQ2OTkgMTEuOTQyMDkzIDEzLjIxMTIgMTIuNzA3MDMxIDE0LjIxODc1IEwgMTEgMTUuNSBMIDExIDE2IEwgMTQgMTYgTCAxNSAxNiBDIDE1LjUgMTYgMTYgMTUuNSAxNiAxNSBMIDE2IDE0IEwgMTYgMTEgTCAxNS41IDExIEwgMTQuMjA1MDc4IDEyLjcyNjU2MiBDIDEzLjE3Nzk4NSAxMS45NDk2MTcgMTIuMTEyNzE4IDExLjA0MzU3NyAxMS4wMzcxMDkgMTAuMDA5NzY2IEMgMTIuMTUxODU2IDguOTgxMDYxIDEzLjIyNDM0NSA4LjA3OTg2MjQgMTQuMjI4NTE2IDcuMzA0Njg3NSBMIDE1LjUgOSBMIDE2IDkgTCAxNiA1IEMgMTYgNC41IDE1LjUgNCAxNSA0IEwgMTEgNCBMIDExIDQuNSBMIDEyLjcwMzEyNSA1Ljc3NzM0MzggQyAxMS45MzI2NDcgNi43ODY0ODM0IDExLjAyNjY5MyA3Ljg1NTQ3MTIgOS45NzA3MDMxIDguOTE5OTIxOSBDIDguOTU4NDczOSA3LjgyMDQ5NDMgOC4wNjk4NzY3IDYuNzYyNzE4OCA3LjMwNDY4NzUgNS43NzE0ODQ0IEwgOSA0LjUgTCA5IDQgTCA2IDQgTCA1IDQgeiAiCiAgICAgaWQ9InBhdGg0MTY5IiAvPjwvc3ZnPg=="); | |
101 | +} | |
102 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-shrink { | |
103 | + background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4KCjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgeD0iMHB4IgogICB5PSIwcHgiCiAgIHZpZXdCb3g9IjAgMCAyMCAyMCIKICAgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjAgMjA7IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkxIHIxMzcyNSIKICAgc29kaXBvZGk6ZG9jbmFtZT0ic2hyaW5rLnN2ZyI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhMTkiPjxyZGY6UkRGPjxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj48ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD48ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+PGRjOnRpdGxlPjwvZGM6dGl0bGU+PC9jYzpXb3JrPjwvcmRmOlJERj48L21ldGFkYXRhPjxkZWZzCiAgICAgaWQ9ImRlZnMxNyIgLz48c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBndWlkZXRvbGVyYW5jZT0iMTAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjIwMjEiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iOTA4IgogICAgIGlkPSJuYW1lZHZpZXcxNSIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMSIKICAgICBpbmtzY2FwZTpjeD0iNC45NTAxMDgyIgogICAgIGlua3NjYXBlOmN5PSIxMC44NTQ3NDciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOnNuYXAtYmJveC1lZGdlLW1pZHBvaW50cz0idHJ1ZSIKICAgICBpbmtzY2FwZTpiYm94LW5vZGVzPSJ0cnVlIgogICAgIGlua3NjYXBlOnNuYXAtYmJveC1taWRwb2ludHM9InRydWUiCiAgICAgaW5rc2NhcGU6b2JqZWN0LXBhdGhzPSJ0cnVlIgogICAgIGlua3NjYXBlOm9iamVjdC1ub2Rlcz0idHJ1ZSI+PGlua3NjYXBlOmdyaWQKICAgICAgIHR5cGU9Inh5Z3JpZCIKICAgICAgIGlkPSJncmlkNDE0NyIgLz48L3NvZGlwb2RpOm5hbWVkdmlldz48cGF0aAogICAgIHN0eWxlPSJmaWxsOiMwMDAwMDAiCiAgICAgZD0iTSA0LjI0MjE4NzUgMy40OTIxODc1IEEgMC43NTAwNzUgMC43NTAwNzUgMCAwIDAgMy43MTg3NSA0Ljc4MTI1IEwgNS45NjQ4NDM4IDcuMDI3MzQzOCBMIDQgOC41IEwgNCA5IEwgOCA5IEMgOC41MDAwMDEgOC45OTk5OTg4IDkgOC40OTk5OTkyIDkgOCBMIDkgNCBMIDguNSA0IEwgNy4wMTc1NzgxIDUuOTU1MDc4MSBMIDQuNzgxMjUgMy43MTg3NSBBIDAuNzUwMDc1IDAuNzUwMDc1IDAgMCAwIDQuMjQyMTg3NSAzLjQ5MjE4NzUgeiBNIDE1LjczNDM3NSAzLjQ5MjE4NzUgQSAwLjc1MDA3NSAwLjc1MDA3NSAwIDAgMCAxNS4yMTg3NSAzLjcxODc1IEwgMTIuOTg0Mzc1IDUuOTUzMTI1IEwgMTEuNSA0IEwgMTEgNCBMIDExIDggQyAxMSA4LjQ5OTk5OTIgMTEuNDk5OTk5IDguOTk5OTk4OCAxMiA5IEwgMTYgOSBMIDE2IDguNSBMIDE0LjAzNTE1NiA3LjAyNzM0MzggTCAxNi4yODEyNSA0Ljc4MTI1IEEgMC43NTAwNzUgMC43NTAwNzUgMCAwIDAgMTUuNzM0Mzc1IDMuNDkyMTg3NSB6IE0gNCAxMSBMIDQgMTEuNSBMIDUuOTY0ODQzOCAxMi45NzI2NTYgTCAzLjcxODc1IDE1LjIxODc1IEEgMC43NTEzMDA5NiAwLjc1MTMwMDk2IDAgMSAwIDQuNzgxMjUgMTYuMjgxMjUgTCA3LjAyNzM0MzggMTQuMDM1MTU2IEwgOC41IDE2IEwgOSAxNiBMIDkgMTIgQyA5IDExLjUwMDAwMSA4LjUwMDAwMSAxMS4wMDAwMDEgOCAxMSBMIDQgMTEgeiBNIDEyIDExIEMgMTEuNDk5OTk5IDExLjAwMDAwMSAxMSAxMS41MDAwMDEgMTEgMTIgTCAxMSAxNiBMIDExLjUgMTYgTCAxMi45NzI2NTYgMTQuMDM1MTU2IEwgMTUuMjE4NzUgMTYuMjgxMjUgQSAwLjc1MTMwMDk2IDAuNzUxMzAwOTYgMCAxIDAgMTYuMjgxMjUgMTUuMjE4NzUgTCAxNC4wMzUxNTYgMTIuOTcyNjU2IEwgMTYgMTEuNSBMIDE2IDExIEwgMTIgMTEgeiAiCiAgICAgaWQ9InBhdGg3IiAvPjwvc3ZnPg=="); | |
104 | +} | |
105 | +.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > .mapboxgl-ctrl-compass-arrow { | |
106 | + width: 20px; | |
107 | + height: 20px; | |
108 | + margin: 5px; | |
109 | + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E"); | |
110 | + background-repeat: no-repeat; | |
111 | + display: inline-block; | |
112 | +} | |
113 | + | |
114 | +a.mapboxgl-ctrl-logo { | |
115 | + width: 85px; | |
116 | + height: 21px; | |
117 | + margin: 0 0 -3px -3px; | |
118 | + display: block; | |
119 | + background-repeat: no-repeat; | |
120 | + cursor: pointer; | |
121 | + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiAgIHZpZXdCb3g9IjAgMCA4NC40OSAyMSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgODQuNDkgMjE7IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik04My4yNSwxNC4yNmMwLDAuMTItMC4wOSwwLjIxLTAuMjEsMC4yMWgtMS42MWMtMC4xMywwLTAuMjQtMC4wNi0wLjMtMC4xN2wtMS40NC0yLjM5bC0xLjQ0LDIuMzkgICAgYy0wLjA2LDAuMTEtMC4xOCwwLjE3LTAuMywwLjE3aC0xLjYxYy0wLjA0LDAtMC4wOC0wLjAxLTAuMTItMC4wM2MtMC4wOS0wLjA2LTAuMTMtMC4xOS0wLjA2LTAuMjhsMCwwbDIuNDMtMy42OEw3Ni4yLDYuODQgICAgYy0wLjAyLTAuMDMtMC4wMy0wLjA3LTAuMDMtMC4xMmMwLTAuMTIsMC4wOS0wLjIxLDAuMjEtMC4yMWgxLjYxYzAuMTMsMCwwLjI0LDAuMDYsMC4zLDAuMTdsMS40MSwyLjM2bDEuNC0yLjM1ICAgIGMwLjA2LTAuMTEsMC4xOC0wLjE3LDAuMy0wLjE3SDgzYzAuMDQsMCwwLjA4LDAuMDEsMC4xMiwwLjAzYzAuMDksMC4wNiwwLjEzLDAuMTksMC4wNiwwLjI4bDAsMGwtMi4zNywzLjYzbDIuNDMsMy42NyAgICBDODMuMjQsMTQuMTgsODMuMjUsMTQuMjIsODMuMjUsMTQuMjZ6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNjYuMjQsOS41OWMtMC4zOS0xLjg4LTEuOTYtMy4yOC0zLjg0LTMuMjhjLTEuMDMsMC0yLjAzLDAuNDItMi43MywxLjE4VjMuNTFjMC0wLjEzLTAuMS0wLjIzLTAuMjMtMC4yM2gtMS40ICAgIGMtMC4xMywwLTAuMjMsMC4xMS0wLjIzLDAuMjN2MTAuNzJjMCwwLjEzLDAuMSwwLjIzLDAuMjMsMC4yM2gxLjRjMC4xMywwLDAuMjMtMC4xMSwwLjIzLTAuMjNWMTMuNWMwLjcxLDAuNzUsMS43LDEuMTgsMi43MywxLjE4ICAgIGMxLjg4LDAsMy40NS0xLjQxLDMuODQtMy4yOUM2Ni4zNywxMC43OSw2Ni4zNywxMC4xOCw2Ni4yNCw5LjU5TDY2LjI0LDkuNTl6IE02Mi4wOCwxM2MtMS4zMiwwLTIuMzktMS4xMS0yLjQxLTIuNDh2LTAuMDYgICAgYzAuMDItMS4zOCwxLjA5LTIuNDgsMi40MS0yLjQ4czIuNDIsMS4xMiwyLjQyLDIuNTFTNjMuNDEsMTMsNjIuMDgsMTN6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNzEuNjcsNi4zMmMtMS45OC0wLjAxLTMuNzIsMS4zNS00LjE2LDMuMjljLTAuMTMsMC41OS0wLjEzLDEuMTksMCwxLjc3YzAuNDQsMS45NCwyLjE3LDMuMzIsNC4xNywzLjMgICAgYzIuMzUsMCw0LjI2LTEuODcsNC4yNi00LjE5Uzc0LjA0LDYuMzIsNzEuNjcsNi4zMnogTTcxLjY1LDEzLjAxYy0xLjMzLDAtMi40Mi0xLjEyLTIuNDItMi41MXMxLjA4LTIuNTIsMi40Mi0yLjUyICAgIGMxLjMzLDAsMi40MiwxLjEyLDIuNDIsMi41MVM3Mi45OSwxMyw3MS42NSwxMy4wMUw3MS42NSwxMy4wMXoiLz4gIDxwYXRoIGNsYXNzPSJzdDEiIHN0eWxlPSJvcGFjaXR5OjAuMzU7IGVuYWJsZS1iYWNrZ3JvdW5kOm5ldzsiIGQ9Ik02Mi4wOCw3Ljk4Yy0xLjMyLDAtMi4zOSwxLjExLTIuNDEsMi40OHYwLjA2QzU5LjY4LDExLjksNjAuNzUsMTMsNjIuMDgsMTNzMi40Mi0xLjEyLDIuNDItMi41MSAgICBTNjMuNDEsNy45OCw2Mi4wOCw3Ljk4eiBNNjIuMDgsMTEuNzZjLTAuNjMsMC0xLjE0LTAuNTYtMS4xNy0xLjI1di0wLjA0YzAuMDEtMC42OSwwLjU0LTEuMjUsMS4xNy0xLjI1ICAgIGMwLjYzLDAsMS4xNywwLjU3LDEuMTcsMS4yN0M2My4yNCwxMS4yLDYyLjczLDExLjc2LDYyLjA4LDExLjc2eiIvPiAgPHBhdGggY2xhc3M9InN0MSIgc3R5bGU9Im9wYWNpdHk6MC4zNTsgZW5hYmxlLWJhY2tncm91bmQ6bmV3OyIgZD0iTTcxLjY1LDcuOThjLTEuMzMsMC0yLjQyLDEuMTItMi40MiwyLjUxUzcwLjMyLDEzLDcxLjY1LDEzczIuNDItMS4xMiwyLjQyLTIuNTFTNzIuOTksNy45OCw3MS42NSw3Ljk4eiAgICAgTTcxLjY1LDExLjc2Yy0wLjY0LDAtMS4xNy0wLjU3LTEuMTctMS4yN2MwLTAuNywwLjUzLTEuMjYsMS4xNy0xLjI2czEuMTcsMC41NywxLjE3LDEuMjdDNzIuODIsMTEuMjEsNzIuMjksMTEuNzYsNzEuNjUsMTEuNzZ6IiAgICAvPiAgPHBhdGggY2xhc3M9InN0MCIgc3R5bGU9Im9wYWNpdHk6MC45OyBmaWxsOiAjRkZGRkZGOyBlbmFibGUtYmFja2dyb3VuZDogbmV3OyIgZD0iTTQ1Ljc0LDYuNTNoLTEuNGMtMC4xMywwLTAuMjMsMC4xMS0wLjIzLDAuMjN2MC43M2MtMC43MS0wLjc1LTEuNy0xLjE4LTIuNzMtMS4xOCAgICBjLTIuMTcsMC0zLjk0LDEuODctMy45NCw0LjE5czEuNzcsNC4xOSwzLjk0LDQuMTljMS4wNCwwLDIuMDMtMC40MywyLjczLTEuMTl2MC43M2MwLDAuMTMsMC4xLDAuMjMsMC4yMywwLjIzaDEuNCAgICBjMC4xMywwLDAuMjMtMC4xMSwwLjIzLTAuMjNWNi43NGMwLTAuMTItMC4wOS0wLjIyLTAuMjItMC4yMkM0NS43NSw2LjUzLDQ1Ljc1LDYuNTMsNDUuNzQsNi41M3ogTTQ0LjEyLDEwLjUzICAgIEM0NC4xMSwxMS45LDQzLjAzLDEzLDQxLjcxLDEzcy0yLjQyLTEuMTItMi40Mi0yLjUxczEuMDgtMi41MiwyLjQtMi41MmMxLjMzLDAsMi4zOSwxLjExLDIuNDEsMi40OEw0NC4xMiwxMC41M3oiLz4gIDxwYXRoIGNsYXNzPSJzdDEiIHN0eWxlPSJvcGFjaXR5OjAuMzU7IGVuYWJsZS1iYWNrZ3JvdW5kOm5ldzsiIGQ9Ik00MS43MSw3Ljk4Yy0xLjMzLDAtMi40MiwxLjEyLTIuNDIsMi41MVM0MC4zNywxMyw0MS43MSwxM3MyLjM5LTEuMTEsMi40MS0yLjQ4di0wLjA2ICAgIEM0NC4xLDkuMDksNDMuMDMsNy45OCw0MS43MSw3Ljk4eiBNNDAuNTUsMTAuNDljMC0wLjcsMC41Mi0xLjI3LDEuMTctMS4yN2MwLjY0LDAsMS4xNCwwLjU2LDEuMTcsMS4yNXYwLjA0ICAgIGMtMC4wMSwwLjY4LTAuNTMsMS4yNC0xLjE3LDEuMjRDNDEuMDgsMTEuNzUsNDAuNTUsMTEuMTksNDAuNTUsMTAuNDl6Ii8+ICA8cGF0aCBjbGFzcz0ic3QwIiBzdHlsZT0ib3BhY2l0eTowLjk7IGZpbGw6ICNGRkZGRkY7IGVuYWJsZS1iYWNrZ3JvdW5kOiBuZXc7IiBkPSJNNTIuNDEsNi4zMmMtMS4wMywwLTIuMDMsMC40Mi0yLjczLDEuMThWNi43NWMwLTAuMTMtMC4xLTAuMjMtMC4yMy0wLjIzaC0xLjRjLTAuMTMsMC0wLjIzLDAuMTEtMC4yMywwLjIzICAgIHYxMC43MmMwLDAuMTMsMC4xLDAuMjMsMC4yMywwLjIzaDEuNGMwLjEzLDAsMC4yMy0wLjEsMC4yMy0wLjIzVjEzLjVjMC43MSwwLjc1LDEuNywxLjE4LDIuNzQsMS4xOGMyLjE3LDAsMy45NC0xLjg3LDMuOTQtNC4xOSAgICBTNTQuNTgsNi4zMiw1Mi40MSw2LjMyeiBNNTIuMDgsMTMuMDFjLTEuMzIsMC0yLjM5LTEuMTEtMi40Mi0yLjQ4di0wLjA3YzAuMDItMS4zOCwxLjA5LTIuNDksMi40LTIuNDljMS4zMiwwLDIuNDEsMS4xMiwyLjQxLDIuNTEgICAgUzUzLjQsMTMsNTIuMDgsMTMuMDFMNTIuMDgsMTMuMDF6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNNTIuMDgsNy45OGMtMS4zMiwwLTIuMzksMS4xMS0yLjQyLDIuNDh2MC4wNmMwLjAzLDEuMzgsMS4xLDIuNDgsMi40MiwyLjQ4czIuNDEtMS4xMiwyLjQxLTIuNTEgICAgUzUzLjQsNy45OCw1Mi4wOCw3Ljk4eiBNNTIuMDgsMTEuNzZjLTAuNjMsMC0xLjE0LTAuNTYtMS4xNy0xLjI1di0wLjA0YzAuMDEtMC42OSwwLjU0LTEuMjUsMS4xNy0xLjI1YzAuNjMsMCwxLjE3LDAuNTgsMS4xNywxLjI3ICAgIFM1Mi43MiwxMS43Niw1Mi4wOCwxMS43NnoiLz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik0zNi4wOCwxNC4yNGMwLDAuMTMtMC4xLDAuMjMtMC4yMywwLjIzaC0xLjQxYy0wLjEzLDAtMC4yMy0wLjExLTAuMjMtMC4yM1Y5LjY4YzAtMC45OC0wLjc0LTEuNzEtMS42Mi0xLjcxICAgIGMtMC44LDAtMS40NiwwLjctMS41OSwxLjYybDAuMDEsNC42NmMwLDAuMTMtMC4xMSwwLjIzLTAuMjMsMC4yM2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWOS42OCAgICBjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuODUsMC0xLjU0LDAuNzktMS42LDEuOHY0LjQ4YzAsMC4xMy0wLjEsMC4yMy0wLjIzLDAuMjNoLTEuNGMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWNi43NCAgICBjMC4wMS0wLjEzLDAuMS0wLjIyLDAuMjMtMC4yMmgxLjRjMC4xMywwLDAuMjIsMC4xMSwwLjIzLDAuMjJWNy40YzAuNS0wLjY4LDEuMy0xLjA5LDIuMTYtMS4xaDAuMDNjMS4wOSwwLDIuMDksMC42LDIuNiwxLjU1ICAgIGMwLjQ1LTAuOTUsMS40LTEuNTUsMi40NC0xLjU2YzEuNjIsMCwyLjkzLDEuMjUsMi45LDIuNzhMMzYuMDgsMTQuMjR6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNODQuMzQsMTMuNTlsLTAuMDctMC4xM2wtMS45Ni0yLjk5bDEuOTQtMi45NWMwLjQ0LTAuNjcsMC4yNi0xLjU2LTAuNDEtMi4wMmMtMC4wMiwwLTAuMDMsMC0wLjA0LTAuMDEgICAgYy0wLjIzLTAuMTUtMC41LTAuMjItMC43OC0wLjIyaC0xLjYxYy0wLjU2LDAtMS4wOCwwLjI5LTEuMzcsMC43OEw3OS43Miw2LjZsLTAuMzQtMC41NkM3OS4wOSw1LjU2LDc4LjU3LDUuMjcsNzgsNS4yN2gtMS42ICAgIGMtMC42LDAtMS4xMywwLjM3LTEuMzUsMC45MmMtMi4xOS0xLjY2LTUuMjgtMS40Ny03LjI2LDAuNDVjLTAuMzUsMC4zNC0wLjY1LDAuNzItMC44OSwxLjE0Yy0wLjktMS42Mi0yLjU4LTIuNzItNC41LTIuNzIgICAgYy0wLjUsMC0xLjAxLDAuMDctMS40OCwwLjIzVjMuNTFjMC0wLjgyLTAuNjYtMS40OC0xLjQ3LTEuNDhoLTEuNGMtMC44MSwwLTEuNDcsMC42Ni0xLjQ3LDEuNDd2My43NSAgICBjLTAuOTUtMS4zNi0yLjUtMi4xOC00LjE3LTIuMTljLTAuNzQsMC0xLjQ2LDAuMTYtMi4xMiwwLjQ3Yy0wLjI0LTAuMTctMC41NC0wLjI2LTAuODQtMC4yNmgtMS40Yy0wLjQ1LDAtMC44NywwLjIxLTEuMTUsMC41NiAgICBjLTAuMDItMC4wMy0wLjA0LTAuMDUtMC4wNy0wLjA4Yy0wLjI4LTAuMy0wLjY4LTAuNDctMS4wOS0wLjQ3aC0xLjM5Yy0wLjMsMC0wLjYsMC4wOS0wLjg0LDAuMjZjLTAuNjctMC4zLTEuMzktMC40Ni0yLjEyLTAuNDYgICAgYy0xLjgzLDAtMy40MywxLTQuMzcsMi41Yy0wLjItMC40Ni0wLjQ4LTAuODktMC44My0xLjI1Yy0wLjgtMC44MS0xLjg5LTEuMjUtMy4wMi0xLjI1aC0wLjAxYy0wLjg5LDAuMDEtMS43NSwwLjMzLTIuNDYsMC44OCAgICBjLTAuNzQtMC41Ny0xLjY0LTAuODgtMi41Ny0wLjg4SDI4LjFjLTAuMjksMC0wLjU4LDAuMDMtMC44NiwwLjExYy0wLjI4LDAuMDYtMC41NiwwLjE2LTAuODIsMC4yOGMtMC4yMS0wLjEyLTAuNDUtMC4xOC0wLjctMC4xOCAgICBoLTEuNGMtMC44MiwwLTEuNDcsMC42Ni0xLjQ3LDEuNDd2Ny41YzAsMC44MiwwLjY2LDEuNDcsMS40NywxLjQ3aDEuNGMwLjgyLDAsMS40OC0wLjY2LDEuNDgtMS40OGwwLDBWOS43OSAgICBjMC4wMy0wLjM2LDAuMjMtMC41OSwwLjM2LTAuNTljMC4xOCwwLDAuMzgsMC4xOCwwLjM4LDAuNDd2NC41N2MwLDAuODIsMC42NiwxLjQ3LDEuNDcsMS40N2gxLjQxYzAuODIsMCwxLjQ3LTAuNjYsMS40Ny0xLjQ3ICAgIGwtMC4wMS00LjU3YzAuMDYtMC4zMiwwLjI1LTAuNDcsMC4zNS0wLjQ3YzAuMTgsMCwwLjM4LDAuMTgsMC4zOCwwLjQ3djQuNTdjMCwwLjgyLDAuNjYsMS40NywxLjQ3LDEuNDdoMS40MSAgICBjMC44MiwwLDEuNDctMC42NiwxLjQ3LTEuNDd2LTAuMzhjMC45NiwxLjI5LDIuNDYsMi4wNiw0LjA2LDIuMDZjMC43NCwwLDEuNDYtMC4xNiwyLjEyLTAuNDdjMC4yNCwwLjE3LDAuNTQsMC4yNiwwLjg0LDAuMjZoMS4zOSAgICBjMC4zLDAsMC42LTAuMDksMC44NC0wLjI2djIuMDFjMCwwLjgyLDAuNjYsMS40NywxLjQ3LDEuNDdoMS40YzAuODIsMCwxLjQ3LTAuNjYsMS40Ny0xLjQ3di0xLjc3YzAuNDgsMC4xNSwwLjk5LDAuMjMsMS40OSwwLjIyICAgIGMxLjcsMCwzLjIyLTAuODcsNC4xNy0yLjJ2MC41MmMwLDAuODIsMC42NiwxLjQ3LDEuNDcsMS40N2gxLjRjMC4zLDAsMC42LTAuMDksMC44NC0wLjI2YzAuNjYsMC4zMSwxLjM5LDAuNDcsMi4xMiwwLjQ3ICAgIGMxLjkyLDAsMy42LTEuMSw0LjQ5LTIuNzNjMS41NCwyLjY1LDQuOTUsMy41Myw3LjU4LDEuOThjMC4xOC0wLjExLDAuMzYtMC4yMiwwLjUzLTAuMzZjMC4yMiwwLjU1LDAuNzYsMC45MSwxLjM1LDAuOUg3OCAgICBjMC41NiwwLDEuMDgtMC4yOSwxLjM3LTAuNzhsMC4zNy0wLjYxbDAuMzcsMC42MWMwLjI5LDAuNDgsMC44MSwwLjc4LDEuMzgsMC43OGgxLjZjMC44MSwwLDEuNDYtMC42NiwxLjQ1LTEuNDYgICAgQzg0LjQ5LDE0LjAyLDg0LjQ0LDEzLjgsODQuMzQsMTMuNTlMODQuMzQsMTMuNTl6IE0zNS44NiwxNC40N2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjNWOS42OCAgICBjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuOCwwLTEuNDYsMC43LTEuNTksMS42MmwwLjAxLDQuNjZjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40MWMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjMgICAgVjkuNjhjMC0wLjk4LTAuNzQtMS43MS0xLjYyLTEuNzFjLTAuODUsMC0xLjU0LDAuNzktMS42LDEuOHY0LjQ4YzAsMC4xMy0wLjEsMC4yMy0wLjIzLDAuMjNoLTEuNGMtMC4xMywwLTAuMjMtMC4xMS0wLjIzLTAuMjMgICAgVjYuNzRjMC4wMS0wLjEzLDAuMTEtMC4yMiwwLjIzLTAuMjJoMS40YzAuMTMsMCwwLjIyLDAuMTEsMC4yMywwLjIyVjcuNGMwLjUtMC42OCwxLjMtMS4wOSwyLjE2LTEuMWgwLjAzICAgIGMxLjA5LDAsMi4wOSwwLjYsMi42LDEuNTVjMC40NS0wLjk1LDEuNC0xLjU1LDIuNDQtMS41NmMxLjYyLDAsMi45MywxLjI1LDIuOSwyLjc4bDAuMDEsNS4xNkMzNi4wOSwxNC4zNiwzNS45OCwxNC40NiwzNS44NiwxNC40NyAgICBMMzUuODYsMTQuNDd6IE00NS45NywxNC4yNGMwLDAuMTMtMC4xLDAuMjMtMC4yMywwLjIzaC0xLjRjLTAuMTMsMC0wLjIzLTAuMTEtMC4yMy0wLjIzVjEzLjVjLTAuNywwLjc2LTEuNjksMS4xOC0yLjcyLDEuMTggICAgYy0yLjE3LDAtMy45NC0xLjg3LTMuOTQtNC4xOXMxLjc3LTQuMTksMy45NC00LjE5YzEuMDMsMCwyLjAyLDAuNDMsMi43MywxLjE4VjYuNzRjMC0wLjEzLDAuMS0wLjIzLDAuMjMtMC4yM2gxLjQgICAgYzAuMTItMC4wMSwwLjIyLDAuMDgsMC4yMywwLjIxYzAsMC4wMSwwLDAuMDEsMCwwLjAydjcuNTFoLTAuMDFWMTQuMjR6IE01Mi40MSwxNC42N2MtMS4wMywwLTIuMDItMC40My0yLjczLTEuMTh2My45NyAgICBjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40Yy0wLjEzLDAtMC4yMy0wLjEtMC4yMy0wLjIzVjYuNzVjMC0wLjEzLDAuMS0wLjIyLDAuMjMtMC4yMmgxLjRjMC4xMywwLDAuMjMsMC4xMSwwLjIzLDAuMjN2MC43MyAgICBjMC43MS0wLjc2LDEuNy0xLjE4LDIuNzMtMS4xOGMyLjE3LDAsMy45NCwxLjg2LDMuOTQsNC4xOFM1NC41OCwxNC42Nyw1Mi40MSwxNC42N3ogTTY2LjI0LDExLjM5Yy0wLjM5LDEuODctMS45NiwzLjI5LTMuODQsMy4yOSAgICBjLTEuMDMsMC0yLjAyLTAuNDMtMi43My0xLjE4djAuNzNjMCwwLjEzLTAuMSwwLjIzLTAuMjMsMC4yM2gtMS40Yy0wLjEzLDAtMC4yMy0wLjExLTAuMjMtMC4yM1YzLjUxYzAtMC4xMywwLjEtMC4yMywwLjIzLTAuMjMgICAgaDEuNGMwLjEzLDAsMC4yMywwLjExLDAuMjMsMC4yM3YzLjk3YzAuNzEtMC43NSwxLjctMS4xOCwyLjczLTEuMTdjMS44OCwwLDMuNDUsMS40LDMuODQsMy4yOEM2Ni4zNywxMC4xOSw2Ni4zNywxMC44LDY2LjI0LDExLjM5ICAgIEw2Ni4yNCwxMS4zOUw2Ni4yNCwxMS4zOXogTTcxLjY3LDE0LjY4Yy0yLDAuMDEtMy43My0xLjM1LTQuMTctMy4zYy0wLjEzLTAuNTktMC4xMy0xLjE5LDAtMS43N2MwLjQ0LTEuOTQsMi4xNy0zLjMxLDQuMTctMy4zICAgIGMyLjM2LDAsNC4yNiwxLjg3LDQuMjYsNC4xOVM3NC4wMywxNC42OCw3MS42NywxNC42OEw3MS42NywxNC42OHogTTgzLjA0LDE0LjQ3aC0xLjYxYy0wLjEzLDAtMC4yNC0wLjA2LTAuMy0wLjE3bC0xLjQ0LTIuMzkgICAgbC0xLjQ0LDIuMzljLTAuMDYsMC4xMS0wLjE4LDAuMTctMC4zLDAuMTdoLTEuNjFjLTAuMDQsMC0wLjA4LTAuMDEtMC4xMi0wLjAzYy0wLjA5LTAuMDYtMC4xMy0wLjE5LTAuMDYtMC4yOGwwLDBsMi40My0zLjY4ICAgIEw3Ni4yLDYuODRjLTAuMDItMC4wMy0wLjAzLTAuMDctMC4wMy0wLjEyYzAtMC4xMiwwLjA5LTAuMjEsMC4yMS0wLjIxaDEuNjFjMC4xMywwLDAuMjQsMC4wNiwwLjMsMC4xN2wxLjQxLDIuMzZsMS40MS0yLjM2ICAgIGMwLjA2LTAuMTEsMC4xOC0wLjE3LDAuMy0wLjE3aDEuNjFjMC4wNCwwLDAuMDgsMC4wMSwwLjEyLDAuMDNjMC4wOSwwLjA2LDAuMTMsMC4xOSwwLjA2LDAuMjhsMCwwbC0yLjM4LDMuNjRsMi40MywzLjY3ICAgIGMwLjAyLDAuMDMsMC4wMywwLjA3LDAuMDMsMC4xMkM4My4yNSwxNC4zOCw4My4xNiwxNC40Nyw4My4wNCwxNC40N0w4My4wNCwxNC40N0w4My4wNCwxNC40N3oiLz4gIDxwYXRoIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIGQ9Ik0xMC41LDEuMjRjLTUuMTEsMC05LjI1LDQuMTUtOS4yNSw5LjI1czQuMTUsOS4yNSw5LjI1LDkuMjVzOS4yNS00LjE1LDkuMjUtOS4yNSAgICBDMTkuNzUsNS4zOCwxNS42MSwxLjI0LDEwLjUsMS4yNHogTTE0Ljg5LDEyLjc3Yy0xLjkzLDEuOTMtNC43OCwyLjMxLTYuNywyLjMxYy0wLjcsMC0xLjQxLTAuMDUtMi4xLTAuMTZjMCwwLTEuMDItNS42NCwyLjE0LTguODEgICAgYzAuODMtMC44MywxLjk1LTEuMjgsMy4xMy0xLjI4YzEuMjcsMCwyLjQ5LDAuNTEsMy4zOSwxLjQyQzE2LjU5LDguMDksMTYuNjQsMTEsMTQuODksMTIuNzd6Ii8+ICA8cGF0aCBjbGFzcz0ic3QxIiBzdHlsZT0ib3BhY2l0eTowLjM1OyBlbmFibGUtYmFja2dyb3VuZDpuZXc7IiBkPSJNMTAuNS0wLjAxQzQuNy0wLjAxLDAsNC43LDAsMTAuNDlzNC43LDEwLjUsMTAuNSwxMC41UzIxLDE2LjI5LDIxLDEwLjQ5QzIwLjk5LDQuNywxNi4zLTAuMDEsMTAuNS0wLjAxeiAgICAgTTEwLjUsMTkuNzRjLTUuMTEsMC05LjI1LTQuMTUtOS4yNS05LjI1czQuMTQtOS4yNiw5LjI1LTkuMjZzOS4yNSw0LjE1LDkuMjUsOS4yNUMxOS43NSwxNS42MSwxNS42MSwxOS43NCwxMC41LDE5Ljc0eiIvPiAgPHBhdGggY2xhc3M9InN0MSIgc3R5bGU9Im9wYWNpdHk6MC4zNTsgZW5hYmxlLWJhY2tncm91bmQ6bmV3OyIgZD0iTTE0Ljc0LDYuMjVDMTIuOSw0LjQxLDkuOTgsNC4zNSw4LjIzLDYuMWMtMy4xNiwzLjE3LTIuMTQsOC44MS0yLjE0LDguODFzNS42NCwxLjAyLDguODEtMi4xNCAgICBDMTYuNjQsMTEsMTYuNTksOC4wOSwxNC43NCw2LjI1eiBNMTIuNDcsMTAuMzRsLTAuOTEsMS44N2wtMC45LTEuODdMOC44LDkuNDNsMS44Ni0wLjlsMC45LTEuODdsMC45MSwxLjg3bDEuODYsMC45TDEyLjQ3LDEwLjM0eiIgICAgLz4gIDxwb2x5Z29uIGNsYXNzPSJzdDAiIHN0eWxlPSJvcGFjaXR5OjAuOTsgZmlsbDogI0ZGRkZGRjsgZW5hYmxlLWJhY2tncm91bmQ6IG5ldzsiIHBvaW50cz0iMTQuMzMsOS40MyAxMi40NywxMC4zNCAxMS41NiwxMi4yMSAxMC42NiwxMC4zNCA4LjgsOS40MyAxMC42Niw4LjUzIDExLjU2LDYuNjYgMTIuNDcsOC41MyAgICIvPjwvZz48L3N2Zz4=); | |
122 | +} | |
123 | + | |
124 | +.mapboxgl-ctrl.mapboxgl-ctrl-attrib { | |
125 | + padding: 0 5px; | |
126 | + background-color: rgba(255, 255, 255, .5); | |
127 | + margin: 0; | |
128 | +} | |
129 | +.mapboxgl-ctrl-attrib.mapboxgl-compact { | |
130 | + padding-top: 2px; | |
131 | + padding-bottom: 2px; | |
132 | + margin: 0 10px 10px 10px; | |
133 | + position: relative; | |
134 | + padding-right: 24px; | |
135 | + background-color: #fff; | |
136 | + border-radius: 3px 12px 12px 3px; | |
137 | + visibility: hidden; | |
138 | +} | |
139 | +.mapboxgl-ctrl-attrib.mapboxgl-compact:hover { | |
140 | + visibility: visible; | |
141 | +} | |
142 | +.mapboxgl-ctrl-attrib.mapboxgl-compact:after { | |
143 | + content: ''; | |
144 | + cursor: pointer; | |
145 | + position: absolute; | |
146 | + bottom: 0; | |
147 | + right: 0; | |
148 | + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%09%3Cpath%20fill%3D%27%23333333%27%20fill-rule%3D%27evenodd%27%20d%3D%27M4%2C10a6%2C6%200%201%2C0%2012%2C0a6%2C6%200%201%2C0%20-12%2C0%20M9%2C7a1%2C1%200%201%2C0%202%2C0a1%2C1%200%201%2C0%20-2%2C0%20M9%2C10a1%2C1%200%201%2C1%202%2C0l0%2C3a1%2C1%200%201%2C1%20-2%2C0%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); | |
149 | + background-color: rgba(255, 255, 255, .5); | |
150 | + width: 24px; | |
151 | + height: 24px; | |
152 | + box-sizing: border-box; | |
153 | + visibility: visible; | |
154 | + border-radius: 12px; | |
155 | +} | |
156 | +.mapboxgl-ctrl-attrib a { | |
157 | + color: rgba(0,0,0,0.75); | |
158 | + text-decoration: none; | |
159 | +} | |
160 | +.mapboxgl-ctrl-attrib a:hover { | |
161 | + color: inherit; | |
162 | + text-decoration: underline; | |
163 | +} | |
164 | +/* stylelint-disable */ | |
165 | +.mapboxgl-ctrl-attrib .mapbox-improve-map { | |
166 | + font-weight: bold; | |
167 | + margin-left: 2px; | |
168 | +} | |
169 | +/*stylelint-enable*/ | |
170 | +.mapboxgl-ctrl-scale { | |
171 | + background-color: rgba(255,255,255,0.75); | |
172 | + font-size: 10px; | |
173 | + border-width: medium 2px 2px; | |
174 | + border-style: none solid solid; | |
175 | + border-color: #333; | |
176 | + padding: 0 5px; | |
177 | + color: #333; | |
178 | +} | |
179 | + | |
180 | +.mapboxgl-popup { | |
181 | + position: absolute; | |
182 | + top: 0; | |
183 | + left: 0; | |
184 | + display: -webkit-flex; | |
185 | + display: flex; | |
186 | + will-change: transform; | |
187 | + pointer-events: none; | |
188 | +} | |
189 | +.mapboxgl-popup-anchor-top, | |
190 | +.mapboxgl-popup-anchor-top-left, | |
191 | +.mapboxgl-popup-anchor-top-right { | |
192 | + -webkit-flex-direction: column; | |
193 | + flex-direction: column; | |
194 | +} | |
195 | +.mapboxgl-popup-anchor-bottom, | |
196 | +.mapboxgl-popup-anchor-bottom-left, | |
197 | +.mapboxgl-popup-anchor-bottom-right { | |
198 | + -webkit-flex-direction: column-reverse; | |
199 | + flex-direction: column-reverse; | |
200 | +} | |
201 | +.mapboxgl-popup-anchor-left { | |
202 | + -webkit-flex-direction: row; | |
203 | + flex-direction: row; | |
204 | +} | |
205 | +.mapboxgl-popup-anchor-right { | |
206 | + -webkit-flex-direction: row-reverse; | |
207 | + flex-direction: row-reverse; | |
208 | +} | |
209 | +.mapboxgl-popup-tip { | |
210 | + width: 0; | |
211 | + height: 0; | |
212 | + border: 10px solid transparent; | |
213 | + z-index: 1; | |
214 | +} | |
215 | +.mapboxgl-popup-anchor-top .mapboxgl-popup-tip { | |
216 | + -webkit-align-self: center; | |
217 | + align-self: center; | |
218 | + border-top: none; | |
219 | + border-bottom-color: #fff; | |
220 | +} | |
221 | +.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip { | |
222 | + -webkit-align-self: flex-start; | |
223 | + align-self: flex-start; | |
224 | + border-top: none; | |
225 | + border-left: none; | |
226 | + border-bottom-color: #fff; | |
227 | +} | |
228 | +.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip { | |
229 | + -webkit-align-self: flex-end; | |
230 | + align-self: flex-end; | |
231 | + border-top: none; | |
232 | + border-right: none; | |
233 | + border-bottom-color: #fff; | |
234 | +} | |
235 | +.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip { | |
236 | + -webkit-align-self: center; | |
237 | + align-self: center; | |
238 | + border-bottom: none; | |
239 | + border-top-color: #fff; | |
240 | +} | |
241 | +.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip { | |
242 | + -webkit-align-self: flex-start; | |
243 | + align-self: flex-start; | |
244 | + border-bottom: none; | |
245 | + border-left: none; | |
246 | + border-top-color: #fff; | |
247 | +} | |
248 | +.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip { | |
249 | + -webkit-align-self: flex-end; | |
250 | + align-self: flex-end; | |
251 | + border-bottom: none; | |
252 | + border-right: none; | |
253 | + border-top-color: #fff; | |
254 | +} | |
255 | +.mapboxgl-popup-anchor-left .mapboxgl-popup-tip { | |
256 | + -webkit-align-self: center; | |
257 | + align-self: center; | |
258 | + border-left: none; | |
259 | + border-right-color: #fff; | |
260 | +} | |
261 | +.mapboxgl-popup-anchor-right .mapboxgl-popup-tip { | |
262 | + -webkit-align-self: center; | |
263 | + align-self: center; | |
264 | + border-right: none; | |
265 | + border-left-color: #fff; | |
266 | +} | |
267 | +.mapboxgl-popup-close-button { | |
268 | + position: absolute; | |
269 | + right: 0; | |
270 | + top: 0; | |
271 | + border: none; | |
272 | + border-radius: 0 3px 0 0; | |
273 | + cursor: pointer; | |
274 | + background-color: rgba(0,0,0,0); | |
275 | +} | |
276 | +.mapboxgl-popup-close-button:hover { | |
277 | + background-color: rgba(0,0,0,0.05); | |
278 | +} | |
279 | +.mapboxgl-popup-content { | |
280 | + position: relative; | |
281 | + background: #fff; | |
282 | + border-radius: 3px; | |
283 | + box-shadow: 0 1px 2px rgba(0,0,0,0.10); | |
284 | + padding: 10px 10px 15px; | |
285 | + pointer-events: auto; | |
286 | +} | |
287 | +.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content { | |
288 | + border-top-left-radius: 0; | |
289 | +} | |
290 | +.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content { | |
291 | + border-top-right-radius: 0; | |
292 | +} | |
293 | +.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content { | |
294 | + border-bottom-left-radius: 0; | |
295 | +} | |
296 | +.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content { | |
297 | + border-bottom-right-radius: 0; | |
298 | +} | |
299 | + | |
300 | +.mapboxgl-marker { | |
301 | + position: absolute; | |
302 | + top: 0; | |
303 | + left: 0; | |
304 | + will-change: transform; | |
305 | +} | |
306 | + | |
307 | +.mapboxgl-crosshair, | |
308 | +.mapboxgl-crosshair .mapboxgl-interactive, | |
309 | +.mapboxgl-crosshair .mapboxgl-interactive:active { | |
310 | + cursor: crosshair; | |
311 | +} | |
312 | +.mapboxgl-boxzoom { | |
313 | + position: absolute; | |
314 | + top: 0; | |
315 | + left: 0; | |
316 | + width: 0; | |
317 | + height: 0; | |
318 | + background: #fff; | |
319 | + border: 2px dotted #202020; | |
320 | + opacity: 0.5; | |
321 | +} | |
322 | + | |
323 | +@media print { | |
324 | +/* stylelint-disable */ | |
325 | + .mapbox-improve-map { | |
326 | + display:none; | |
327 | + } | |
328 | +/* stylelint-enable */ | |
329 | +} | |
\ No newline at end of file | ... | ... |
请
注册
或
登录
后发表评论