Compare commits
No commits in common. 'main' and 'master' have entirely different histories.
@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
__pycache__/
|
||||||
|
*.log
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.12.4-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip config set global.index-url http://mirrors.aliyun.com/pypi/simple && pip config set install.trusted-host mirrors.aliyun.com
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . ./
|
||||||
|
CMD ["gunicorn", "-w", "4", "--bind", "0.0.0.0:3002", "myapp:app" ]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 50b2c113694b3c40ed3aea797ce341532d5bebd7
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
from flask import request
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
class LogService:
|
||||||
|
@staticmethod
|
||||||
|
def configure_logging():
|
||||||
|
logger.remove() # 移除默认的日志配置
|
||||||
|
logger.add(
|
||||||
|
"logs/app.log", # 日志文件名
|
||||||
|
rotation="1 week", # 每周轮换
|
||||||
|
retention="1 month", # 保留一个月
|
||||||
|
level="INFO", # 记录 INFO 及以上级别的日志
|
||||||
|
format="{time} - {level} - {message}", # 日志格式
|
||||||
|
backtrace=True, # 打印完整的异常堆栈
|
||||||
|
diagnose=True # 打印详细的异常信息
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log():
|
||||||
|
logger.info(f"Request Path: {request.path}")
|
||||||
|
logger.info(f"Request Method: {request.method}")
|
||||||
|
logger.info(f"Request Headers: {request.headers}")
|
||||||
|
logger.info(f"Request args: {request.args}")
|
||||||
|
logger.info(f"Request Body: {request.get_data(as_text=True)}")
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from flask_jwt_extended import create_access_token
|
||||||
|
|
||||||
|
from app import LogService
|
||||||
|
from app.models import Passenger
|
||||||
|
from presenter import PassengerPresenter
|
||||||
|
from utils import StateCode, create_response
|
||||||
|
|
||||||
|
login_bp = Blueprint('login', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@login_bp.route('/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
data = request.json
|
||||||
|
account = data.get('account')
|
||||||
|
password = data.get('password')
|
||||||
|
if not account or not password:
|
||||||
|
return jsonify(create_response(StateCode.PARAMS_ERROR)), 400
|
||||||
|
user = Passenger.verifyPassenger(account, password)
|
||||||
|
if user:
|
||||||
|
access_token = create_access_token(identity=user.id)
|
||||||
|
user_presenter = PassengerPresenter(user, {"token": access_token}).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=user_presenter)), 200
|
||||||
|
return jsonify(create_response(StateCode.PASSWORD_INCORRECT)), 400
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
from app import db
|
||||||
|
from .order_lib import Order
|
||||||
|
from .passenger_lib import Passenger
|
||||||
|
from .station_lib import Station
|
||||||
|
from .train_lib import Train
|
||||||
|
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
import pdb
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
from app.models.ticket_lib import Ticket
|
||||||
|
from app.models.train_station_lib import TrainStation
|
||||||
|
|
||||||
|
|
||||||
|
class Order(db.Model):
|
||||||
|
__tablename__ = 'order'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
order_no = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
|
price = db.Column(db.Numeric(8, 2))
|
||||||
|
payment_time = db.Column(db.DateTime)
|
||||||
|
state = db.Column(db.Integer, default=0)
|
||||||
|
passenger_id = db.Column(db.Integer, db.ForeignKey('passenger.id'), nullable=False, index=True)
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
|
||||||
|
passenger = db.relationship('Passenger', backref=db.backref('orders'))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Order {self.id}>'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setOrderState(cls, order_no, passenger_id, **kwargs):
|
||||||
|
order = cls.query.filter_by(order_no=order_no, passenger_id=passenger_id).first()
|
||||||
|
if order:
|
||||||
|
# 更新订单的属性
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(order, key, value)
|
||||||
|
if key == 'state' and value == 1:
|
||||||
|
for ticket in order.tickets:
|
||||||
|
Ticket.updateState(ticket) # Update state to 1
|
||||||
|
# 提交更改到数据库
|
||||||
|
db.session.commit()
|
||||||
|
return order
|
||||||
|
else:
|
||||||
|
# 处理订单未找到的情况
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_unique_id(length=4):
|
||||||
|
# 获取当前的秒级时间戳
|
||||||
|
timestamp_seconds = int(time.time())
|
||||||
|
|
||||||
|
# 生成 4 位随机数
|
||||||
|
random_number = random.randint(0, 10 ** length - 1)
|
||||||
|
|
||||||
|
# 确保随机数是 4 位数字
|
||||||
|
random_number_str = str(random_number).zfill(length)
|
||||||
|
|
||||||
|
# 生成唯一标识符
|
||||||
|
unique_id = f"{timestamp_seconds}{random_number_str}"
|
||||||
|
return unique_id
|
||||||
|
|
||||||
|
|
||||||
|
def generateOrder(params, passenger_id):
|
||||||
|
# Create a new Order object
|
||||||
|
order = Order(
|
||||||
|
order_no=generate_unique_id(5),
|
||||||
|
passenger_id=passenger_id
|
||||||
|
)
|
||||||
|
|
||||||
|
for e in params['tickets']:
|
||||||
|
# 创建Ticket对象
|
||||||
|
ticket = Ticket.generateTicket(e, passenger_id)
|
||||||
|
# 计算起始站和终点站
|
||||||
|
from_station = TrainStation.query.filter_by(train_no=ticket.train_no, station_name=ticket.from_station).one()
|
||||||
|
to_station = TrainStation.query.filter_by(train_no=ticket.train_no, station_name=ticket.to_station).one()
|
||||||
|
# 计算区间价格
|
||||||
|
train_stations = TrainStation.query.filter(
|
||||||
|
TrainStation.train_no == ticket.train_no,
|
||||||
|
TrainStation.index.between(from_station.index, to_station.index)
|
||||||
|
).all()
|
||||||
|
ticket.price = sum(e.price for e in train_stations)
|
||||||
|
# 计算座位号
|
||||||
|
seat_count = db.session.query(func.count(Ticket.id)).filter_by(
|
||||||
|
train_no=ticket.train_no,
|
||||||
|
date=ticket.date
|
||||||
|
).scalar()
|
||||||
|
ticket.seat_no = seat_count + 1
|
||||||
|
|
||||||
|
# 转换时间 计算出发时间
|
||||||
|
interval_days = (
|
||||||
|
datetime.strptime(ticket.date, "%Y/%m/%d") - datetime.combine(from_station.departure_time.date(),
|
||||||
|
datetime.min.time())).days
|
||||||
|
ticket.departure_time = from_station.departure_time + timedelta(days=interval_days)
|
||||||
|
ticket.arrival_time = to_station.arrival_time + timedelta(days=interval_days)
|
||||||
|
|
||||||
|
order.tickets.append(ticket)
|
||||||
|
|
||||||
|
order.price = sum(ticket.price for ticket in order.tickets)
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
|
|
||||||
|
def isAvailable(account):
|
||||||
|
Passenger.query.filter_by(account=account).first()
|
||||||
|
|
||||||
|
|
||||||
|
class Passenger(db.Model):
|
||||||
|
__tablename__ = 'passenger'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(120))
|
||||||
|
account = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
||||||
|
password_digest = db.Column(db.String(2000), nullable=False)
|
||||||
|
id_card_no = db.Column(db.String(120))
|
||||||
|
mobile_no = db.Column(db.String(120))
|
||||||
|
bank_card_no = db.Column(db.String(120))
|
||||||
|
state = db.Column(db.Integer, default=0)
|
||||||
|
member_type = db.Column(db.Integer)
|
||||||
|
last_login_time = db.Column(db.DateTime)
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.password_digest, password) # 验证密码
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verifyPassenger(cls, account, password):
|
||||||
|
passenger = cls.query.filter_by(account=account).first()
|
||||||
|
if passenger and passenger.check_password(password):
|
||||||
|
return passenger
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, data):
|
||||||
|
passenger = cls(
|
||||||
|
account=data.get('account'),
|
||||||
|
password_digest=generate_password_hash(data.get('password')),
|
||||||
|
name=data.get('name'),
|
||||||
|
id_card_no=data.get('idCardNo'),
|
||||||
|
mobile_no=data.get('mobileNo'),
|
||||||
|
bank_card_no=data.get('bankCard'),
|
||||||
|
)
|
||||||
|
db.session.add(passenger)
|
||||||
|
db.session.commit()
|
||||||
|
return passenger
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def destroy(cls, passenger_id):
|
||||||
|
passenger = cls.query.get(passenger_id)
|
||||||
|
if passenger:
|
||||||
|
db.session.delete(passenger)
|
||||||
|
db.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import pdb
|
||||||
|
from datetime import datetime
|
||||||
|
from pypinyin import pinyin, Style
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
|
||||||
|
|
||||||
|
class Station(db.Model):
|
||||||
|
__tablename__: str = 'station'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
||||||
|
pinyin = db.Column(db.String(120))
|
||||||
|
province = db.Column(db.String(120))
|
||||||
|
city = db.Column(db.String(120))
|
||||||
|
district = db.Column(db.String(120))
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Station {self.name}>'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, data):
|
||||||
|
station = cls(
|
||||||
|
name=data.get('name'),
|
||||||
|
pinyin=''.join([item[0] for item in pinyin(data.get('name'), style=Style.NORMAL)]).upper(),
|
||||||
|
province=data.get('province'),
|
||||||
|
city=data.get('city'),
|
||||||
|
created_at=datetime.now(),
|
||||||
|
updated_at=datetime.now(),
|
||||||
|
)
|
||||||
|
db.session.add(station)
|
||||||
|
db.session.commit()
|
||||||
|
return station
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def destroy(cls, station_id):
|
||||||
|
station = cls.query.get(station_id)
|
||||||
|
if station:
|
||||||
|
db.session.delete(station)
|
||||||
|
db.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
|
||||||
|
|
||||||
|
class Ticket(db.Model):
|
||||||
|
__tablename__: str = 'ticket'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
seat_no = db.Column(db.String(120))
|
||||||
|
seat_class = db.Column(db.String(120))
|
||||||
|
price = db.Column(db.Numeric(8, 2))
|
||||||
|
state = db.Column(db.Integer, default=0)
|
||||||
|
train_no = db.Column(db.String(120), db.ForeignKey('train.train_no'), nullable=False, index=True)
|
||||||
|
passenger_id = db.Column(db.Integer, db.ForeignKey('passenger.id'), nullable=False, index=True)
|
||||||
|
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False, index=True)
|
||||||
|
from_station = db.Column(db.String(120))
|
||||||
|
to_station = db.Column(db.String(120))
|
||||||
|
date = db.Column(db.String(120))
|
||||||
|
departure_time = db.Column(db.DateTime)
|
||||||
|
arrival_time = db.Column(db.DateTime)
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
order = db.relationship('Order', backref=db.backref('tickets'))
|
||||||
|
passenger = db.relationship('Passenger', backref=db.backref('tickets'))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Ticket {self.id}>'
|
||||||
|
|
||||||
|
def updateState(self):
|
||||||
|
self.state = 1
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generateTicket(cls, item, passenger_id):
|
||||||
|
# Create a new Ticket object
|
||||||
|
ticket = Ticket(
|
||||||
|
seat_class=item["seatClass"],
|
||||||
|
train_no=item["trainNo"],
|
||||||
|
from_station=item["from"],
|
||||||
|
to_station=item["to"],
|
||||||
|
date=item["date"],
|
||||||
|
passenger_id=passenger_id
|
||||||
|
)
|
||||||
|
return ticket
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
from app.models.train_station_lib import TrainStation
|
||||||
|
|
||||||
|
class Train(db.Model):
|
||||||
|
__tablename__: str = 'train'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
train_no = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
||||||
|
departure_station = db.Column(db.String(120))
|
||||||
|
arrival_station = db.Column(db.String(120))
|
||||||
|
departure_time = db.Column(db.DateTime)
|
||||||
|
expiration_time = db.Column(db.DateTime)
|
||||||
|
effective_time = db.Column(db.DateTime)
|
||||||
|
arrival_time = db.Column(db.DateTime)
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Train {self.id}>'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, new_train):
|
||||||
|
db.session.add(new_train)
|
||||||
|
db.session.commit()
|
||||||
|
return new_train
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def queryTrains(cls, from_station, to_station, date):
|
||||||
|
# Query for train stations where the station name matches `from_station`
|
||||||
|
from_train = TrainStation.query.filter_by(station_name=from_station).all()
|
||||||
|
|
||||||
|
# Query for train stations where the station name matches `to_station`
|
||||||
|
to_train = TrainStation.query.filter_by(station_name=to_station).all()
|
||||||
|
|
||||||
|
# Extract train_no from both query results
|
||||||
|
from_train_nos = {ts.train_no for ts in from_train}
|
||||||
|
to_train_nos = {ts.train_no for ts in to_train}
|
||||||
|
|
||||||
|
# Find the common train_no between the two stations
|
||||||
|
common_train_nos = from_train_nos & to_train_nos
|
||||||
|
|
||||||
|
# Filter train numbers where the index of the from station is less than the index of the to station
|
||||||
|
valid_train_nos = [
|
||||||
|
train_no for train_no in common_train_nos
|
||||||
|
if next(ts.index for ts in from_train if ts.train_no == train_no) <
|
||||||
|
next(ts.index for ts in to_train if ts.train_no == train_no)
|
||||||
|
]
|
||||||
|
# Query trains by the filtered train numbers and the given date (assuming date filtering)
|
||||||
|
trains = Train.query.filter(
|
||||||
|
Train.effective_time >= date,
|
||||||
|
Train.train_no.in_(valid_train_nos)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Assuming you have a presenter or serializer for the trains
|
||||||
|
return trains
|
||||||
|
|
||||||
|
|
||||||
|
def buildTrain(params):
|
||||||
|
# Create a new Train object
|
||||||
|
train = Train(
|
||||||
|
train_no=params['trainNo'],
|
||||||
|
effective_time=params['effective_time'],
|
||||||
|
expiration_time=params['expiration_time']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract indexes for determining first and last station
|
||||||
|
indexes = [e["index"] for e in params['stations']]
|
||||||
|
|
||||||
|
for e in params['stations']:
|
||||||
|
# Create and associate TrainStation objects
|
||||||
|
train_station = TrainStation(
|
||||||
|
station_name=e["name"],
|
||||||
|
price=e["price"],
|
||||||
|
departure_time=e["depTime"],
|
||||||
|
arrival_time=e["arrTime"],
|
||||||
|
index=e["index"]
|
||||||
|
)
|
||||||
|
train.train_stations.append(train_station)
|
||||||
|
# Determine the departure time and station for the first station
|
||||||
|
if e["index"] == 0:
|
||||||
|
train.departure_time = e["depTime"]
|
||||||
|
train.departure_station = e["name"]
|
||||||
|
|
||||||
|
# Determine the arrival time and station for the last station
|
||||||
|
if e["index"] == max(indexes):
|
||||||
|
train.arrival_time = e["arrTime"]
|
||||||
|
train.arrival_station = e["name"]
|
||||||
|
|
||||||
|
return train
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
from app.models import db
|
||||||
|
|
||||||
|
|
||||||
|
class TrainStation(db.Model):
|
||||||
|
__tablename__: str = 'train_station'
|
||||||
|
|
||||||
|
train_no = db.Column(db.String(120), db.ForeignKey('train.train_no'), primary_key=True, index=True)
|
||||||
|
station_name = db.Column(db.String(120), db.ForeignKey('station.name'), primary_key=True)
|
||||||
|
|
||||||
|
price = db.Column(db.Numeric(8, 2))
|
||||||
|
arrival_time = db.Column(db.DateTime)
|
||||||
|
departure_time = db.Column(db.DateTime)
|
||||||
|
index = db.Column(db.Integer, default=0)
|
||||||
|
created_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
updated_at = db.Column(db.DateTime, default=func.now())
|
||||||
|
|
||||||
|
station = db.relationship('Station', backref=db.backref('train_stations'))
|
||||||
|
train = db.relationship('Train', backref=db.backref('train_stations'))
|
||||||
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from flask_jwt_extended import get_jwt_identity, jwt_required
|
||||||
|
from app import db, LogService
|
||||||
|
from app.models.order_lib import generateOrder, Order
|
||||||
|
from presenter.order import OrderPresenter
|
||||||
|
from utils import create_response, StateCode
|
||||||
|
from utils.server import verifyOrderPayment
|
||||||
|
|
||||||
|
order_bp = Blueprint('order', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@order_bp.route('/orders', methods=['POST'])
|
||||||
|
@jwt_required()
|
||||||
|
def createOrder():
|
||||||
|
current_user = get_jwt_identity()
|
||||||
|
data = request.json
|
||||||
|
new_order = generateOrder(data, current_user)
|
||||||
|
db.session.add(new_order)
|
||||||
|
db.session.commit()
|
||||||
|
order_presenter = OrderPresenter(new_order).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=order_presenter)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@order_bp.route('/orders/<string:order_no>/query_payment', methods=['POST'])
|
||||||
|
@jwt_required()
|
||||||
|
def queryPayment(order_no):
|
||||||
|
current_user = get_jwt_identity()
|
||||||
|
response = verifyOrderPayment(order_no)
|
||||||
|
if response.status_code == 200 and response.json().get("data")["state"] == "successful":
|
||||||
|
order = Order.setOrderState(order_no, current_user,
|
||||||
|
state=1, payment_time=response.json().get("data")["pay_time"])
|
||||||
|
order_presenter = OrderPresenter(order).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=order_presenter)), 200
|
||||||
|
else:
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.ORDER_PAY_ERROR)), 400
|
||||||
|
|
||||||
|
|
||||||
|
@order_bp.route('/orders', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def queryOrder():
|
||||||
|
state = request.args.get("state")
|
||||||
|
current_user = get_jwt_identity()
|
||||||
|
orders = Order.query.filter(Order.passenger_id == current_user, Order.state == state).all()
|
||||||
|
order_presenter = [OrderPresenter(order).as_dict() for order in orders]
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=order_presenter)), 200
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||||
|
|
||||||
|
from app import redis_client, LogService
|
||||||
|
from app.models.passenger_lib import Passenger, isAvailable
|
||||||
|
from presenter import PassengerPresenter
|
||||||
|
from utils import StateCode, create_response, checkMobile, checkIdCard, checkBankCard
|
||||||
|
|
||||||
|
register_bp = Blueprint('register', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@register_bp.route('/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
data = request.json
|
||||||
|
account = data.get('account')
|
||||||
|
password = data.get('password')
|
||||||
|
mobile_no = data.get('mobileNo')
|
||||||
|
mobile_code = data.get('mobileCode')
|
||||||
|
id_card_no = data.get('idCardNo')
|
||||||
|
bank_card_no = data.get('bankCard')
|
||||||
|
|
||||||
|
if not account or not password:
|
||||||
|
return jsonify(create_response(StateCode.PARAMS_ERROR)), 400
|
||||||
|
|
||||||
|
if not checkMobile(mobile_no):
|
||||||
|
return jsonify(create_response(StateCode.MOBILE_ERROR)), 400
|
||||||
|
|
||||||
|
if not verifyCode(mobile_no, mobile_code):
|
||||||
|
return jsonify(create_response(StateCode.MOBILE_CODE_ERROR)), 400
|
||||||
|
|
||||||
|
if not checkIdCard(id_card_no):
|
||||||
|
return jsonify(create_response(StateCode.ID_CARD_ERROR)), 400
|
||||||
|
|
||||||
|
if isAvailable(account):
|
||||||
|
return jsonify(create_response(StateCode.USER_ALREADY_EXISTS)), 400
|
||||||
|
|
||||||
|
if not checkBankCard(bank_card_no):
|
||||||
|
return jsonify(create_response(StateCode.BANK_CARD_ERROR)), 400
|
||||||
|
|
||||||
|
new_passenger = Passenger.create(data=data)
|
||||||
|
access_token = create_access_token(identity=new_passenger.id)
|
||||||
|
user_presenter = PassengerPresenter(new_passenger, {"token": access_token}).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=user_presenter)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@register_bp.route('/auth', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def auth():
|
||||||
|
current_user = get_jwt_identity()
|
||||||
|
user = Passenger.query.get(current_user)
|
||||||
|
if not user:
|
||||||
|
return jsonify(create_response(StateCode.USER_NOT_FOUND)), 400
|
||||||
|
user_presenter = PassengerPresenter(user, {}).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
|
||||||
|
return jsonify(create_response(code=StateCode.SUCCESS, data=user_presenter)), 200
|
||||||
|
|
||||||
|
|
||||||
|
def verifyCode(mobile_no, code):
|
||||||
|
stored_code = redis_client.get(mobile_no)
|
||||||
|
return stored_code == code
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import iso8601
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from flask_jwt_extended import get_jwt_identity, jwt_required
|
||||||
|
|
||||||
|
from app import LogService
|
||||||
|
from app.models import Station
|
||||||
|
from app.models.ticket_lib import Ticket
|
||||||
|
from app.models.train_lib import Train
|
||||||
|
from presenter import StationPresenter
|
||||||
|
from presenter.ticket import TicketPresenter
|
||||||
|
from presenter.train import TrainPresenter
|
||||||
|
from utils import create_response, StateCode
|
||||||
|
|
||||||
|
query_bp = Blueprint('query', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@query_bp.route('/trains/query_train', methods=['GET'])
|
||||||
|
def queryTrains(): # 查询车次方法
|
||||||
|
from_station = request.args.get('from')
|
||||||
|
to_station = request.args.get('to')
|
||||||
|
date = iso8601.parse_date(request.args.get('date'))
|
||||||
|
if not from_station or not to_station or not date:
|
||||||
|
return jsonify(create_response(StateCode.PARAMS_ERROR)), 400
|
||||||
|
|
||||||
|
trains = Train.queryTrains(from_station, to_station, date)
|
||||||
|
trains_presenters = [TrainPresenter(train).as_dict() for train in trains]
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=trains_presenters)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@query_bp.route('/tickets', methods=['GET'])
|
||||||
|
@jwt_required()
|
||||||
|
def getTickets(): # 查询车票方法
|
||||||
|
current_user = get_jwt_identity() # 解码JWT得到用户ID
|
||||||
|
tickets = Ticket.query.filter_by(passenger_id=current_user).all()
|
||||||
|
tickets_presenters = [TicketPresenter(ticket).as_dict() for ticket in tickets]
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=tickets_presenters)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@station_bp.route('/stations', methods=['GET'])
|
||||||
|
def queryStations(): # 查询站点方法
|
||||||
|
stations = Station.query.all()
|
||||||
|
stations_presenters = [StationPresenter(station).as_dict() for station in stations]
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=stations_presenters)), 200
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
from flask import request, jsonify, Blueprint
|
||||||
|
from app import LogService
|
||||||
|
from app.models import Station
|
||||||
|
from presenter import StationPresenter
|
||||||
|
from utils import create_response, StateCode
|
||||||
|
|
||||||
|
station_bp = Blueprint('stations', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@station_bp.route('/stations', methods=['POST'])
|
||||||
|
def createStation(): # 创建站点方法
|
||||||
|
data = request.json
|
||||||
|
new_station = Station.create(data=data)
|
||||||
|
station_presenter = StationPresenter(new_station).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=station_presenter)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@station_bp.route('/stations/quantity_create', methods=['POST'])
|
||||||
|
def quantityCreate(): # 批量创建站点方法
|
||||||
|
stations = request.json.get("stations")
|
||||||
|
for name in stations:
|
||||||
|
station_hash = {"name": name}
|
||||||
|
Station.create(station_hash)
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS)), 200
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
from flask import request, jsonify, Blueprint
|
||||||
|
from app import db, LogService
|
||||||
|
from app.models import Train
|
||||||
|
from app.models.train_lib import buildTrain
|
||||||
|
from presenter.train import TrainPresenter
|
||||||
|
from utils import create_response, StateCode
|
||||||
|
|
||||||
|
trains_bp = Blueprint('trains', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@trains_bp.route('/trains', methods=['GET'])
|
||||||
|
def queryAllTrains():
|
||||||
|
trains = Train.query.all()
|
||||||
|
trains_presenters = [TrainPresenter(train).as_dict() for train in trains]
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=trains_presenters)), 200
|
||||||
|
|
||||||
|
|
||||||
|
@trains_bp.route('/trains', methods=['POST'])
|
||||||
|
def createTrain():
|
||||||
|
data = request.json
|
||||||
|
new_train = buildTrain(data)
|
||||||
|
db.session.add(new_train)
|
||||||
|
db.session.commit()
|
||||||
|
train_presenter = TrainPresenter(new_train).as_dict()
|
||||||
|
LogService.log()
|
||||||
|
return jsonify(create_response(StateCode.SUCCESS, data=train_presenter)), 200
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
python:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:postgres@db:5432/mini12306_python
|
||||||
|
- REDIS_URL=redis
|
||||||
|
- SERVER_LIB_URL=http://py12306.learnerhub.net
|
||||||
|
ports:
|
||||||
|
- "3002:3002"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- /var/log/mini12306_python:/app/logs
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: 12306_redis
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: 12306_postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: mini12306_python
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
@ -0,0 +1 @@
|
|||||||
|
init folder
|
||||||
@ -0,0 +1 @@
|
|||||||
|
Single-database configuration for Flask.
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic,flask_migrate
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[logger_flask_migrate]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = flask_migrate
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import logging
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine():
|
||||||
|
try:
|
||||||
|
# this works with Flask-SQLAlchemy<3 and Alchemical
|
||||||
|
return current_app.extensions['migrate'].db.get_engine()
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
# this works with Flask-SQLAlchemy>=3
|
||||||
|
return current_app.extensions['migrate'].db.engine
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine_url():
|
||||||
|
try:
|
||||||
|
return get_engine().url.render_as_string(hide_password=False).replace(
|
||||||
|
'%', '%%')
|
||||||
|
except AttributeError:
|
||||||
|
return str(get_engine().url).replace('%', '%%')
|
||||||
|
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
config.set_main_option('sqlalchemy.url', get_engine_url())
|
||||||
|
target_db = current_app.extensions['migrate'].db
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def get_metadata():
|
||||||
|
if hasattr(target_db, 'metadatas'):
|
||||||
|
return target_db.metadatas[None]
|
||||||
|
return target_db.metadata
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url, target_metadata=get_metadata(), literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
conf_args = current_app.extensions['migrate'].configure_args
|
||||||
|
if conf_args.get("process_revision_directives") is None:
|
||||||
|
conf_args["process_revision_directives"] = process_revision_directives
|
||||||
|
|
||||||
|
connectable = get_engine()
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=get_metadata(),
|
||||||
|
**conf_args
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
"""init databases
|
||||||
|
|
||||||
|
Revision ID: 1b3bc6809b30
|
||||||
|
Revises:
|
||||||
|
Create Date: 2024-08-15 15:09:26.124279
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '1b3bc6809b30'
|
||||||
|
down_revision = None
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('log',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('log_user_agent', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_method', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_quest_path', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_ip', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_params', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_controller', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_controller_action', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('log_controller_id', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('account_id', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('event_name', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('event_content', sa.Text(), nullable=True),
|
||||||
|
sa.Column('operator', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('passenger',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('account', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('password_digest', sa.String(length=2000), nullable=False),
|
||||||
|
sa.Column('id_card_no', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('mobile_no', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('bank_card_no', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('state', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('member_type', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('last_login_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('passenger', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_passenger_account'), ['account'], unique=True)
|
||||||
|
|
||||||
|
op.create_table('station',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('pinyin', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('province', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('city', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('district', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('station', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_station_name'), ['name'], unique=True)
|
||||||
|
|
||||||
|
op.create_table('train',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('departure_station', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('arrival_station', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('expiration_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('effective_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('train', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_train_train_no'), ['train_no'], unique=True)
|
||||||
|
|
||||||
|
op.create_table('order',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('order_no', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||||
|
sa.Column('payment_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('state', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('passenger_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['passenger_id'], ['passenger.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('order_no')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('order', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_order_passenger_id'), ['passenger_id'], unique=False)
|
||||||
|
|
||||||
|
op.create_table('train_station',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('station_name', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||||
|
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('index', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['station_name'], ['station.name'], ),
|
||||||
|
sa.ForeignKeyConstraint(['train_no'], ['train.train_no'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id', 'train_no', 'station_name')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_train_station_train_no'), ['train_no'], unique=False)
|
||||||
|
|
||||||
|
op.create_table('ticket',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('seat_no', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('seat_class', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=True),
|
||||||
|
sa.Column('state', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('train_no', sa.String(length=120), nullable=False),
|
||||||
|
sa.Column('passenger_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('order_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('from_station', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('to_station', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('date', sa.String(length=120), nullable=True),
|
||||||
|
sa.Column('departure_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('arrival_time', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['order_id'], ['order.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['passenger_id'], ['passenger.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['train_no'], ['train.train_no'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('ticket', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_ticket_order_id'), ['order_id'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_ticket_passenger_id'), ['passenger_id'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_ticket_train_no'), ['train_no'], unique=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('ticket', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_ticket_train_no'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_ticket_passenger_id'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_ticket_order_id'))
|
||||||
|
|
||||||
|
op.drop_table('ticket')
|
||||||
|
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_train_station_train_no'))
|
||||||
|
|
||||||
|
op.drop_table('train_station')
|
||||||
|
with op.batch_alter_table('order', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_order_passenger_id'))
|
||||||
|
|
||||||
|
op.drop_table('order')
|
||||||
|
with op.batch_alter_table('train', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_train_train_no'))
|
||||||
|
|
||||||
|
op.drop_table('train')
|
||||||
|
with op.batch_alter_table('station', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_station_name'))
|
||||||
|
|
||||||
|
op.drop_table('station')
|
||||||
|
with op.batch_alter_table('passenger', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_passenger_account'))
|
||||||
|
|
||||||
|
op.drop_table('passenger')
|
||||||
|
op.drop_table('log')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
"""remove TrainStation id
|
||||||
|
|
||||||
|
Revision ID: 7866a6e8a75b
|
||||||
|
Revises: 1b3bc6809b30
|
||||||
|
Create Date: 2024-08-15 15:29:40.410360
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '7866a6e8a75b'
|
||||||
|
down_revision = '1b3bc6809b30'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('id')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('train_station', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('id', sa.INTEGER(), autoincrement=False, nullable=False))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
# myapp.py
|
||||||
|
from app import create_app, LogService
|
||||||
|
from utils import response
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from .passenger import PassengerPresenter
|
||||||
|
from .station import StationPresenter
|
||||||
|
from .mobile_code import MobileCodePresenter
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
class MobileCodePresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"code": self.data
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
from presenter.ticket import TicketPresenter
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.data.id,
|
||||||
|
"orderNo": self.data.order_no,
|
||||||
|
"price": self.data.price,
|
||||||
|
"state": self.data.state,
|
||||||
|
"paymentTime": self.data.payment_time,
|
||||||
|
"tickets": [TicketPresenter(ticket).as_dict() for ticket in self.data.tickets]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
class PassengerPresenter:
|
||||||
|
def __init__(self, data, aid_data=None):
|
||||||
|
self.data = data
|
||||||
|
self.aid_data = aid_data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.data.id,
|
||||||
|
"account": self.data.account,
|
||||||
|
"name": self.data.name,
|
||||||
|
"idCardNo": self.data.id_card_no,
|
||||||
|
"mobileNo": self.data.mobile_no,
|
||||||
|
"bankCardNo": self.data.bank_card_no,
|
||||||
|
"state": self.data.state,
|
||||||
|
"memberType": self.data.member_type
|
||||||
|
} | self.aid_data
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
class StationPresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.data.id,
|
||||||
|
"name": self.data.name,
|
||||||
|
"pinyin": self.data.pinyin,
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
class TicketPresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.data.id,
|
||||||
|
"seatNo": self.data.seat_no,
|
||||||
|
"seatClass": self.data.seat_class,
|
||||||
|
"price": self.data.price,
|
||||||
|
"state": self.data.state,
|
||||||
|
"trainNo": self.data.train_no,
|
||||||
|
"from": self.data.from_station,
|
||||||
|
"orderNo": self.data.order.order_no,
|
||||||
|
"to": self.data.to_station,
|
||||||
|
"date": self.data.date,
|
||||||
|
"fromTime": self.data.departure_time,
|
||||||
|
"toTime": self.data.arrival_time,
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
from presenter.train_station import TrainStationPresenter
|
||||||
|
|
||||||
|
|
||||||
|
class TrainPresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.data.id,
|
||||||
|
"trainNo": self.data.train_no,
|
||||||
|
"arrTime": self.data.arrival_time,
|
||||||
|
"depTime": self.data.departure_time,
|
||||||
|
"arr": self.data.arrival_station,
|
||||||
|
"dep": self.data.departure_station,
|
||||||
|
"stations": [TrainStationPresenter(station).as_dict() for station in self.data.train_stations]
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
class TrainStationPresenter:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"index": self.data.index,
|
||||||
|
"trainNo": self.data.train_no,
|
||||||
|
"name": self.data.station_name,
|
||||||
|
"arrTime": self.data.arrival_time,
|
||||||
|
"depTime": self.data.departure_time,
|
||||||
|
"price": self.data.price,
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
Flask~=3.0.3
|
||||||
|
Flask-SQLAlchemy
|
||||||
|
psycopg2-binary
|
||||||
|
PyJWT
|
||||||
|
Flask-Migrate
|
||||||
|
itsdangerous
|
||||||
|
redis~=5.0.8
|
||||||
|
Flask-JWT-Extended
|
||||||
|
pypinyin~=0.52.0
|
||||||
|
Werkzeug~=3.0.3
|
||||||
|
SQLAlchemy~=2.0.32
|
||||||
|
alembic~=1.13.2
|
||||||
|
python-dateutil
|
||||||
|
requests
|
||||||
|
gunicorn==22.0.0
|
||||||
|
Levenshtein==0.25.1
|
||||||
|
munkres==1.1.4
|
||||||
|
argparse==1.4.0
|
||||||
|
flask-cors==4.0.1
|
||||||
|
loguru
|
||||||
|
iso8601
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
from .response import create_response, StateCode
|
||||||
|
from .server import checkMobile, checkIdCard, checkBankCard
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
hosts = os.getenv('SERVER_LIB_URL', "http://127.0.0.1:5000")
|
||||||
|
|
||||||
|
|
||||||
|
def checkIdCard(id_card):
|
||||||
|
url = hosts + "/id_card/verify"
|
||||||
|
params = {
|
||||||
|
'idCardNo': id_card
|
||||||
|
}
|
||||||
|
# response = requests.get(url, params=params) 发送get请求
|
||||||
|
|
||||||
|
response = requests.post(url, json=params)
|
||||||
|
return response.status_code == 200 and response.json().get("data")["result"]
|
||||||
|
|
||||||
|
|
||||||
|
def checkBankCard(bank_card):
|
||||||
|
url = hosts + "/bank/card_verify"
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'bankCard': bank_card
|
||||||
|
}
|
||||||
|
response = requests.post(url, json=params)
|
||||||
|
return response.status_code == 200 and response.json().get("data")["result"]
|
||||||
|
|
||||||
|
|
||||||
|
def checkMobile(mobile_no):
|
||||||
|
url = hosts + "/mobile/check"
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'mobileNo': mobile_no
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, json=params)
|
||||||
|
return response.status_code == 200 and response.json().get("data")["result"]
|
||||||
|
|
||||||
|
|
||||||
|
def verifyOrderPayment(order_no):
|
||||||
|
url = hosts + "/bank/query"
|
||||||
|
|
||||||
|
# 模拟向支付中心发送验证请求进行验证
|
||||||
|
params = {"OrderNo": order_no}
|
||||||
|
response = requests.post(url, json=params)
|
||||||
|
return response
|
||||||
Loading…
Reference in new issue