wld 4 months ago
parent 3bc3f8ed5e
commit 4036ed0fff

@ -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,77 @@
# app/__init__.py
import os
from datetime import timedelta
import redis
from flask import Flask, request
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from loguru import logger
from config import Config
from app.log_service import LogService
db = SQLAlchemy()
# redis_host_url = os.getenv('REDIS_URL', 'localhost')
# redis_client = redis.StrictRedis(host=redis_host_url, port=6379, db=0, decode_responses=True)
# 使用完整 URL 创建 Redis 客户端
redis_client = redis.from_url(Config.REDIS_URL, decode_responses=True)
# 做一些准备工作,创建 Flask 应用, 配置 JWT 令牌,初始化数据库, 日志系统注册蓝图Blueprints--是各个模块独立独立管理
def create_app():
# 创建 Flask 应用,并指定静态文件夹为 'mini12306'
app = Flask(__name__, static_folder='mini12306')
# 允许跨域请求CORS
CORS(app)
# 推送应用上下文,确保在应用初始化时可以使用 app 相关对象
app.app_context().push()
# 从配置对象加载应用配置
app.config.from_object(Config)
# 将 redis_client 挂载到 app 上,便于全局访问
app.redis_client = redis_client
# 设置 JWT 令牌的有效期为 30 天
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(days=30)
# 初始化数据库
db.init_app(app)
# 绑定数据库迁移工具
Migrate(app, db)
# 初始化 JWT 认证管理
JWTManager(app)
# 配置日志服务
LogService.configure_logging()
# 导入路由模块
from app.login_manager import login_bp # 登录管理
from app.passenger_manager import register_bp # 乘客管理(注册)
from app.query_manager import query_bp # 查询管理
from app.station_manager import station_bp # 车站管理
from app.mobile_server import mobile_bp # 移动端服务
from app.train_manager import trains_bp # 列车管理
from app.bank_server import bank_bp # 银行服务
from app.id_card_server import id_card_bp # 身份证验证服务
from app.order_manager import order_bp # 订单管理
# 注册蓝图Blueprints将各个功能模块绑定到 Flask 应用
app.register_blueprint(login_bp)
app.register_blueprint(register_bp)
app.register_blueprint(query_bp)
app.register_blueprint(station_bp)
app.register_blueprint(mobile_bp)
app.register_blueprint(trains_bp)
app.register_blueprint(bank_bp)
app.register_blueprint(id_card_bp)
app.register_blueprint(order_bp)
# 返回 Flask 应用实例
return app

@ -0,0 +1,38 @@
import re
import datetime
from flask import Blueprint, jsonify, request
from app import LogService
from utils import create_response, StateCode
bank_bp = Blueprint('bank_server', __name__)
#校验银行卡号
@bank_bp.route('/bank/card_verify', methods=['POST'])
def bankCardVerify():
pattern = r'^\d{13,19}$'
# 确保银行卡号是一个完整的 13 到 19 位的数字字符串
number = request.json.get('bankCard')
state = {
"result": bool(re.match(pattern, number))
}
LogService.log()
return jsonify(create_response(StateCode.SUCCESS, data=state)), 200
@bank_bp.route('/bank/pay', methods=['POST'])
def pay():
# 模拟支付成功,返回
state = {"state": "successful", "pay_time": datetime.datetime.now()}
LogService.log()
return jsonify(create_response(StateCode.SUCCESS, data=state)), 200
@bank_bp.route('/bank/query', methods=['POST'])
def query():
# 模拟验证成功,返回
state = {"state": "successful", "pay_time": datetime.datetime.now()}
LogService.log()
return jsonify(create_response(StateCode.SUCCESS, data=state)), 200

@ -0,0 +1,25 @@
import re
from flask import Blueprint, jsonify, request
from app import LogService
from utils import create_response, StateCode
id_card_bp = Blueprint('id_card_server', __name__)
#校验身份证号码
@id_card_bp.route('/id_card/verify', methods=['POST'])
def idCardVerify():
print("idCardVerify() 被调用了")
pattern = r'^\d{18}$'
id_number = request.json.get('idCardNo')
print(f"Received ID number: {id_number}") # 先打印看看
state = {
"result": bool(re.match(pattern, id_number))
}
LogService.log()
return jsonify(create_response(StateCode.SUCCESS, data=state)), 200

@ -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,26 @@
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=str(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,56 @@
import random
import re
from flask import current_app, jsonify, request, Blueprint
import utils
from app import redis_client, LogService
from presenter import MobileCodePresenter
from utils import create_response, StateCode
key = current_app.config['SECRET_KEY']
mobile_bp = Blueprint('mobile_server', __name__)
@mobile_bp.route('/mobile/get_verify_code', methods=['POST'])
# 发送一次性密码(验证码)
def getVerifyCode():
mobile_no = request.json.get('mobileNo')
if not utils.checkMobile(mobile_no):
return jsonify(create_response(StateCode.MOBILE_ERROR)), 400
sent_code = getVerificationCode(mobile_no)
mobile_present = MobileCodePresenter(sent_code).as_dict()
LogService.log()
return jsonify(create_response(StateCode.SUCCESS, data=mobile_present)), 200
#检验手机号的格式
@mobile_bp.route('/mobile/check', methods=['POST'])
def checkMobile():
#正则表达式用户输入的必须是中国大陆的手机号码格式例如11位第一位必须是1等
pattern = r'^1[3-9]\d{9}$'
#获取到客户输入的手机号
number = request.json.get('mobileNo')
print(number)
state = {
#进行匹配将匹配的结果保存在state字典中
"result": bool(re.match(pattern, number))
}
LogService.log() #记录日志
#返回一个json格式的数据
return jsonify(create_response(StateCode.SUCCESS, data=state)), 200
def getVerificationCode(mobile_no):
verification_code = generateRandomNumber() # 生成验证码
print(verification_code)
if redis_client.set(mobile_no, verification_code, 300):
return verification_code
else:
print(f"{redis_client.client}")
# 生成6位随机数字
def generateRandomNumber(length=6):
# 确保生成的数字至少有6位即使前几位是0
number = random.randint(10 ** (length - 1), 10 ** length - 1)
return number

@ -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,125 @@
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
try:
# 尝试从数据库中查询特定的订单
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)
# 如果更新了订单状态为1则更新该订单下所有车票的状态
if key == 'state' and value == 1:
for ticket in order.tickets:
Ticket.updateState(ticket)
# 提交更改到数据库
db.session.commit()
return order
else:
# 如果没有找到订单返回None
return None
except Exception as e:
# 如果发生异常,回滚数据库更改并重新抛出异常
db.session.rollback()
raise e
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 > from_station.index,
TrainStation.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,76 @@
from sqlalchemy import func
from app.models import db
from werkzeug.security import generate_password_hash, check_password_hash
def isAvailable(account):
#从数据库中查找第一个匹配 account 的 Passenger 记录。
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):
print(1)
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
#根据乘客id删除数据即删除操作
@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,131 @@
from datetime import datetime
import pytz
from sqlalchemy import func
from app.models import db
from app.models.train_station_lib import TrainStation
#Train实体
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
"""
根据出发站到达站和日期查询符合条件的列车信息
该方法首先根据出发站和到达站名称分别查询经过这两个站点的列车
然后找出同时经过这两个站点的列车编号train_no
再通过判断站点顺序出发站在到达站之前筛选出有效的列车
最后根据有效时间和列车编号查询最终结果
参数:
- from_station: 出发站名称字符串
- to_station: 到达站名称字符串
- date: 查询日期datetime 对象
返回:
- 符合条件的列车列表
"""
# # 根据列车编号和有效时间筛选符合条件的列车
# trains = Train.query.filter(
# #Train.effective_time >= date, # 只查询在指定日期之后有效的列车
# Train.train_no.in_(valid_train_nos) # 使用有效列车编号进行过滤
# ).all()
@classmethod
# 业务层(服务层)
def queryTrains(cls, from_station, to_station, date):
# 查询所有经过出发站的列车信息
from_train = TrainStation.query.filter_by(station_name=from_station).all()
# 查询所有经过到达站的列车信息
to_train = TrainStation.query.filter_by(station_name=to_station).all()
# 提取出发站对应的所有列车编号train_no
from_train_nos = {ts.train_no for ts in from_train}
# 提取到达站对应的所有列车编号train_no
to_train_nos = {ts.train_no for ts in to_train}
# 找出两个站点共有的列车编号(即:哪些列车同时经过出发站和到达站)
common_train_nos = from_train_nos & to_train_nos
# 过滤出“出发站出现在到达站之前”的列车编号
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)
]
# 获取当前系统时间
now = datetime.now(pytz.timezone('Asia/Shanghai'))
#判断date是否大于当前系统时间
if date > now:
# 根据列车编号和有效时间筛选符合条件的列车
trains = Train.query.filter(
Train.effective_time < date,
Train.expiration_time > date,
Train.train_no.in_(valid_train_nos) # 使用有效列车编号进行过滤
).all()
else:
# 根据列车编号和有效时间筛选符合条件的列车
trains = db.session.query(Train, TrainStation).join(
TrainStation,
Train.train_no == TrainStation.train_no and TrainStation.station_name == from_station
).filter(
Train.effective_time < date,
Train.expiration_time > date,
# 不大于当前的系统时间还需判断当前的时间是否大于列车的出发时间
func.time(TrainStation.departure_time) > func.time(date),
Train.train_no.in_(valid_train_nos) # 使用有效列车编号进行过滤
).all()
# 返回查询到的列车列表
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,59 @@
from email.utils import parsedate_to_datetime
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:
data = response.json().get("data")
pay_time_str = data["pay_time"] # 如Thu, 05 Jun 2025 20:56:56 GMT
# 解析为 datetime 对象
pay_time = parsedate_to_datetime(pay_time_str)
# 更新订单状态和支付时间
order = Order.setOrderState(order_no, current_user, state=1, payment_time=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,73 @@
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
print(data)
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):
#获取到随机产生的验证码,再判断用户输入的验证码是否正确
#这里的用到了redis,Redis是一种开源的、基于内存的键值存储通常用于缓存、分布式存储 和 高效数据处理,
#所以通过get方法获取到对应的值即验证码
stored_code = redis_client.get(mobile_no)
return stored_code == code

@ -0,0 +1,53 @@
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 app.station_manager import station_bp
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')
#获取日期并用iso8601解析
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,16 @@
import os
class Config:
# SECRET_KEY = os.environ.get('SECRET_KEY') or 'your_secret_key'
# SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = '8665e31a29ab7e12cb0f92c1dbobj1e3a6a15230fb17'
# SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or
# 'postgresql://username:password@localhost:5432/your dbname'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'mysql+pymysql://root:1625344721Qq%40@localhost/mini12306'
SQLALCHEMY_TRACK_MODIFICATIONS = False
REDIS_URL = "redis://:123456@localhost:6379/0"

@ -0,0 +1,87 @@
# 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:
version: '3.8'
services:
python:
build:
context: ./
dockerfile: ./Dockerfile
restart: always
environment:
- DATABASE_URL=mysql+pymysql://root:123456@db:3306/mini12306_python # 修改数据库连接字符串
- REDIS_URL=redis
- SERVER_LIB_URL=http://py12306.learnerhub.net
ports:
- "3002:3002"
depends_on:
- db # 服务名保持不变,但已指向新的 MySQL 服务
- redis
volumes:
- /var/log/mini12306_python:/app/logs
redis:
image: redis:alpine
container_name: 12306_redis
volumes:
- redis_data:/data
db: # 修改后的 MySQL 服务
image: mysql:8.0 # 使用官方 MySQL 镜像
container_name: 12306_mysql # 修改容器名称
restart: always
environment:
MYSQL_ROOT_PASSWORD: 123456 # MySQL 根密码(建议修改)
MYSQL_DATABASE: mini12306_python # 自动创建的数据库
MYSQL_USER: app_user # 可选:创建专用用户
MYSQL_PASSWORD: user123 # 可选用户密码
volumes:
- mysql_data:/var/lib/mysql # MySQL 数据存储路径
# 建议添加的健康检查
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 5
volumes:
mysql_data: # 修改卷名称
redis_data:

@ -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,50 @@
"""Initial migration
Revision ID: 67902ab783bc
Revises: b1465436c340
Create Date: 2025-03-03 21:19:33.592231
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '67902ab783bc'
down_revision = 'b1465436c340'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('tb_emp', schema=None) as batch_op:
batch_op.drop_index('username')
op.drop_table('tb_emp')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tb_emp',
sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主键'),
sa.Column('username', mysql.VARCHAR(length=20), nullable=False, comment='用户名'),
sa.Column('password', mysql.VARCHAR(length=32), server_default=sa.text("'123456'"), nullable=True, comment='密码'),
sa.Column('emp_name', mysql.VARCHAR(length=10), nullable=False, comment='员工姓名'),
sa.Column('gender', mysql.TINYINT(unsigned=True), autoincrement=False, nullable=False, comment='性别1 男生 2 女生)'),
sa.Column('image', mysql.VARCHAR(length=300), nullable=True, comment='图片的访问路径'),
sa.Column('job', mysql.TINYINT(unsigned=True), autoincrement=False, nullable=True, comment='职位1 班主任 2 讲师 3 学工主管 4 教研主管)'),
sa.Column('entrydate', mysql.DATETIME(), nullable=True, comment='入职日期'),
sa.Column('dept', mysql.VARCHAR(length=20), nullable=True, comment='归属部门'),
sa.Column('create_time', mysql.DATETIME(), nullable=False, comment='创建时间'),
sa.Column('update_time', mysql.DATETIME(), nullable=False, comment='更新时间'),
sa.PrimaryKeyConstraint('id'),
mysql_collate='utf8mb4_0900_ai_ci',
mysql_default_charset='utf8mb4',
mysql_engine='InnoDB'
)
with op.batch_alter_table('tb_emp', schema=None) as batch_op:
batch_op.create_index('username', ['username'], unique=True)
# ### 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,48 @@
"""empty message
Revision ID: b1465436c340
Revises: 7866a6e8a75b
Create Date: 2025-02-21 16:47:25.383937
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'b1465436c340'
down_revision = '7866a6e8a75b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('log')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('log',
sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
sa.Column('log_user_agent', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_method', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_quest_path', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_ip', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_params', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_controller', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_controller_action', mysql.VARCHAR(length=120), nullable=True),
sa.Column('log_controller_id', mysql.VARCHAR(length=120), nullable=True),
sa.Column('account_id', mysql.VARCHAR(length=120), nullable=True),
sa.Column('event_name', mysql.VARCHAR(length=120), nullable=True),
sa.Column('event_content', mysql.TEXT(), nullable=True),
sa.Column('operator', mysql.VARCHAR(length=120), nullable=True),
sa.Column('created_at', mysql.DATETIME(), nullable=True),
sa.Column('updated_at', mysql.DATETIME(), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_collate='utf8mb4_0900_ai_ci',
mysql_default_charset='utf8mb4',
mysql_engine='InnoDB'
)
# ### end Alembic commands ###

@ -0,0 +1,12 @@
# myapp.py
from app import create_app, LogService
from utils import response
app = create_app()
#项目入口
if __name__ == "__main__":
#服务端运行在哪个ip地址上的哪个端口是否开启调试这里是本机上的5000端口
app.run(host="127.0.0.1", port=5000, 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,2 @@
from .response import create_response, StateCode
from .server import checkMobile, checkIdCard, checkBankCard

@ -0,0 +1,53 @@
from enum import Enum
from typing import Any, Dict, List, Union
#状态码,响应前端发送的某次请求,观察此次请求的结果,成功与否
class StateCode(Enum):
SUCCESS = 0
NOT_AUTHORIZED = 1002
PARAMS_ERROR = 1003
USER_NOT_FOUND = 2001
USER_ALREADY_EXISTS = 2002
PASSWORD_INCORRECT = 2003
MOBILE_CODE_ERROR = 2004
ID_CARD_ERROR = 2005
BANK_CARD_ERROR = 2006
MOBILE_ERROR = 2007
ORDER_PAY_ERROR = 3001
#状态信息,响应给前端作出相应提示
STATE_MESSAGES = {
StateCode.SUCCESS: "OK",
StateCode.PARAMS_ERROR: "缺少参数",
StateCode.USER_NOT_FOUND: "用户不存在,操作错误",
StateCode.NOT_AUTHORIZED: "未登录",
StateCode.USER_ALREADY_EXISTS: "用户已存在",
StateCode.PASSWORD_INCORRECT: "用户名或密码错误",
StateCode.MOBILE_CODE_ERROR: "验证码错误",
StateCode.ID_CARD_ERROR: "身份证号验证失败",
StateCode.MOBILE_ERROR: "手机号格式有误",
StateCode.BANK_CARD_ERROR: "银行卡验证错误",
StateCode.ORDER_PAY_ERROR: "支付失败"
}
#返回一个json格式的数据主要是为了和前端统一数据格式包含三个部分上面的状态码响应信息返回的数据
def create_response(
code: StateCode,
message: str = None,
data: Union[Dict[str, Any], List[Dict[str, Any]], Any] = None
) -> Dict[str, Any]:
"""
创建统一的返回格式
:param code: ErrorCode 错误码
:param message: 错误信息可选如果不传递则使用默认的错误信息
:param data: 包含单个或多个 Presenter 转换后的数据单个使用 dict多个使用 list
:return: 返回统一格式的字典
"""
return {
"code": code.value,
"msg": message or STATE_MESSAGES.get(code, "Unknown error"),
"data": data
}

@ -0,0 +1,56 @@
import os
import requests
hosts = os.getenv('SERVER_LIB_URL', "http://127.0.0.1:5000")
#检验身份证号码
def checkIdCard(id_card):
# 获取到url地址
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地址
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地址
url = hosts + "/mobile/check"
#请求参数
params = {
'mobileNo': mobile_no
}
#获取到mobile_server类中返回的数据
response = requests.post(url, json=params)
#判断返回数据的状态码和result(布尔值,手机号是否匹配成功)只有状态码是200和手机号匹配成功才返回true
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…
Cancel
Save