|
- #!/usr/bin/env python3
- # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil -*-
- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8
-
- __author__ = ['"anjingyu" <anjingyu@navinfo.com>']
-
-
- import sys
- import os
- import json
- import base64
- import uuid
- import hashlib
- import struct
- import uvicorn
- from sqlalchemy import create_engine, Column, Integer, String, BLOB
- from sqlalchemy.ext.declarative import declarative_base
- from sqlalchemy.orm import Session, sessionmaker
- from fastapi import Depends, FastAPI, HTTPException, APIRouter, Request
- from fastapi.responses import PlainTextResponse
-
- from starlette.middleware.cors import CORSMiddleware
- from starlette.status import HTTP_404_NOT_FOUND
-
- from config import ALLOWED_HOSTS, API_PREFIX, DEBUG, PROJECT_NAME, VERSION, PORT, HOST, DATABASE_URL, PICTURE_DIR
-
- engine = create_engine(DATABASE_URL, connect_args={'check_same_thread': False})
- SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
- mysql_base_uri = os.getenv("NS_MYSQL_URI")
- mysql_engine = create_engine('{}/ns_deqing'.format(mysql_base_uri))
- MySQLSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=mysql_engine)
-
- BaseSQLite = declarative_base()
- BaseMySQL = declarative_base()
-
- router = APIRouter()
-
- class MapServerInfo(BaseSQLite):
- __tablename__ = "MapServer"
-
- id = Column(Integer, primary_key=True)
- catalog = Column(String, index=True)
- version = Column(String, index=True)
- value = Column(String)
- description = Column(String)
-
- class MySQLMapServerInfo(BaseMySQL):
- __tablename__ = "MapServer"
-
- id = Column(Integer, primary_key=True)
- url = Column(String)
- thumbnail = Column(BLOB)
- majorVer = Column(Integer)
- minorVer = Column(Integer)
- catalog = Column(String)
- description = Column(String)
-
- class MySQLUserMapInfo(BaseMySQL):
- __tablename__ = "UserMap"
-
- userId = Column(Integer, primary_key=True)
- mapServerId = Column(Integer, index=True)
- enName = Column(String, index=True)
- zhName = Column(String, index=True)
-
- # Dependencies
- def get_db() -> Session:
- db = None
- try:
- db = SessionLocal()
- yield db
- finally:
- if db:
- db.close()
-
- def get_mysql_db() -> Session:
- db = None
- try:
- db = MySQLSessionLocal()
- yield db
- finally:
- if db:
- db.close()
-
- @router.get("/map-server")
- async def query_map_server(catalog: str, version: str, db: Session=Depends(get_db)) -> PlainTextResponse:
- records = db.query(MapServerInfo).filter(MapServerInfo.catalog == catalog).filter(MapServerInfo.version == version)
- if records.count() == 0:
- raise HTTPException(status_code=HTTP_404_NOT_FOUND, detail=f"No record: catalog={catalog} and version={version}")
- else:
- all_records = []
- for record in records:
- all_records.append(record.value)
- return PlainTextResponse(content=";".join(all_records))
-
- @router.post('/deploy')
- async def depoly(request: Request, db: Session=Depends(get_db), mysql_db: Session=Depends(get_mysql_db)) -> PlainTextResponse:
- info = await request.body()
- info = json.loads(info)
-
- map_record = MapServerInfo(catalog=info["catalog"],
- version=info["version"],
- value=info["value"],
- description=info["description"])
-
- keys = ['catalog', 'version', 'value', 'description']
-
- # If the record has been existent, overwrite the latest one
- record = db.query(MapServerInfo).filter(MapServerInfo.catalog == info['catalog']).filter(MapServerInfo.version == info['version']).order_by(MapServerInfo.id.desc()).first()
- if record:
- for k in keys:
- setattr(record, k, getattr(map_record, k))
- db.commit()
- db.flush()
- db.refresh(record)
- else:
- db.add(map_record)
- db.commit()
- db.refresh(map_record)
-
- # A patch for 1.0 Map Server API based on MySQL
- desc = {"ave_lane": 1, "ave_speed": "49.68 km/h", "length": "3.38 km", "signals": 3}
-
- picture_info_data = None
- # Generate UUID, ETAG(MD5) and ETAG size, and size of original binary buffer,
- # then pack then together in binary format.
- # the storage strcuture is presented as following(little-endian):
- # +-------------+------------------+-----------------------------------------+
- # | Item | Size | Remarks |
- # +=============+==================+=========================================+
- # | Buffer Size | 4 byte | |
- # +-------------+------------------+-----------------------------------------+
- # | ETAG Length | 4 byte | The length contains the NULL at the end |
- # +-------------+------------------+-----------------------------------------+
- # | ETAG(MD4) | length(ETAG) + 1 | Identifier contains the NULL at the end |
- # +-------------+------------------+-----------------------------------------+
- # | UUID | length(UUID) + 1 | UUID contains the NULL at the end |
- # +-------------+------------------+-----------------------------------------+
- #
- # Thumbnail binary buffer:
- # If there is no next block in current file,
- # the last `Next Block Offset` will be filled with -1.
- # In another words, the file must end with -1.
- # +-------------------+-------------+
- # | Next Block Offset | 4 bytes |---+
- # +-------------------+-------------+ |
- # | Buffer Size | 4 bytes | |
- # +-------------------+-------------+ |
- # | Binary Buffer | Buffer Size | |
- # +-------------------+-------------+<--+
- # | Next Block Offset | 4 bytes |
- # +-------------------+-------------+
- # | ... ... | ... ... |
- # +-------------------+-------------+
- if 'picture' in info:
- # Convert base64 string to image binary
- picture_data = base64.b64decode(info['picture'])
- picture_data_len = len(picture_data)
-
- picture_md5 = hashlib.md5(picture_data).hexdigest().encode('utf-8')
- picture_md5_len = len(picture_md5)
-
- picture_uuid = str(uuid.uuid4()).encode('utf-8')
- picture_uuid_len = len(picture_uuid)
-
- # Save image into specified directory
- with open(f"{PICTURE_DIR}/{picture_uuid}", "wb+") as f:
- # Write `Next Block Offset` and `Buffer Size`
- f.write(struct.pack("<ii", picture_data_len + 8, picture_data_len))
- f.write(picture_data)
- # The end `Next Block Offset`
- f.write(struct.pack("<i", -1))
-
- picture_uuid = picture_uuid.encode('utf-8')
-
- picture_info_data = struct.pack("<ii{}sb{}sb".format(picture_md5_len, picture_uuid_len), picture_data_len, picture_md5_len + 1, picture_md5, 0, picture_uuid, 0)
-
- ma, _, mi = info['version'].split('.')
- major_version = int(ma[1:])
- minor_version = int(mi)
-
- mysql_map_record = MySQLMapServerInfo(catalog=info["catalog"],
- majorVer=major_version,
- minorVer=minor_version,
- url=info["value"],
- thumbnail=picture_info_data,
- description=json.dumps(desc))
-
- mysql_keys = ['url', 'thumbnail', 'majorVer', 'minorVer', 'catalog', 'description']
-
- record = mysql_db.query(MySQLMapServerInfo).filter(MySQLMapServerInfo.catalog == info['catalog']).filter(MySQLMapServerInfo.majorVer == major_version).filter(MySQLMapServerInfo.minorVer == minor_version).order_by(MySQLMapServerInfo.id.desc()).first()
-
- if record:
- for k in mysql_keys:
- setattr(record, k, getattr(mysql_map_record, k))
- mysql_db.commit()
- mysql_db.flush()
- mysql_db.refresh(record)
- else:
- mysql_db.add(mysql_map_record)
- mysql_db.commit()
- mysql_db.refresh(mysql_map_record)
-
- user_map_record = MySQLUserMapInfo(userId=10000,
- mapServerId=mysql_map_record.id,
- enName=info['enName'],
- zhName=info['zhName'])
-
- mysql_db.add(user_map_record)
- mysql_db.commit()
- mysql_db.refresh(user_map_record)
-
- return PlainTextResponse(content="OK")
-
- def get_application() -> FastAPI:
- application = FastAPI(title=PROJECT_NAME, debug=DEBUG, version=VERSION)
-
- application.add_middleware(
- CORSMiddleware,
- allow_origins=ALLOWED_HOSTS or ["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
-
- application.include_router(router, prefix=API_PREFIX)
-
- return application
-
- if __name__ == '__main__':
- uvicorn.run(get_application(), host=HOST, port=PORT)
|