diff --git a/.env b/.env
new file mode 100644
index 0000000..67ee479
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+DEBUG=True
+SECRET_KEY="rss2Ebook"
\ No newline at end of file
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..80a980e
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: gunicorn run:app
\ No newline at end of file
diff --git a/app.py b/app.py
index 1a17bd8..effbb41 100644
--- a/app.py
+++ b/app.py
@@ -1,10 +1,11 @@
# from book.views import user, feed, wechat, myfeed
-from book import *
+import os
+
+from book import app
from book.schedule import *
-from book.views import *
if __name__ == '__main__':
- # print(app.url_map)
+ print(app.url_map)
app.run(debug=True,threaded=True,use_reloader=False,port=8000)
diff --git a/book/__init__.py b/book/__init__.py
index 6e5d28f..5d1761c 100644
--- a/book/__init__.py
+++ b/book/__init__.py
@@ -1,8 +1,10 @@
# -*-coding: utf-8-*-
import os
import logging
+from importlib import import_module
from logging.handlers import RotatingFileHandler
-from flask import Flask, request, render_template, jsonify
+
+from flask import Flask, jsonify
from flask_caching import Cache
from flask_jwt_extended.exceptions import NoAuthorizationError, InvalidHeaderError, WrongTokenError
from flask_mail import Mail
@@ -11,26 +13,29 @@
app = Flask(__name__)
app.config.from_object('config')
+
+
+
mail = Mail(app)
cache = Cache(app)
db = SQLAlchemy(app)
+jwt = JWTManager(app)
with app.app_context():
db.create_all()
-"""
-Initialize logging
-"""
-logging.basicConfig(level=logging.INFO) # Debug level (development environment)
-file_log_handler = RotatingFileHandler(
- f"{os.path.dirname(app.root_path)}/logs/books.log", maxBytes=1024 * 1024 * 100, backupCount=10
-) # 100M
-formatter = logging.Formatter(
- '%(asctime)s-%(levelname)s-%(filename)s-%(funcName)s-%(lineno)s-%(message)s'
-) # Time, log level, log file, line number, message
-file_log_handler.setFormatter(formatter)
-logging.getLogger().addHandler(file_log_handler)
-jwt = JWTManager(app)
+def register_extensions(app):
+ db.init_app(app)
+
+
+def configure_database(app):
+ @app.before_first_request
+ def initialize_database():
+ db.create_all()
+
+ @app.teardown_request
+ def shutdown_session(exception=None):
+ db.session.remove()
@app.errorhandler(NoAuthorizationError)
@@ -45,4 +50,24 @@ def error_date(error):
return render_template("404.html"), 404
-from book.dbModels import *
+from book.views import *
+modules = ['user', 'ebook', 'feed', 'wechat']
+for model_name in modules:
+ model = import_module(f"{app.name}.views.{model_name}")
+ app.register_blueprint(model.blueprint)
+
+
+
+
+"""
+Initialize logging
+"""
+logfile = f"{os.path.dirname(app.root_path)}/logs/books.log"
+logging.basicConfig(level=logging.INFO)
+handler = RotatingFileHandler(logfile, maxBytes=1024 * 1024 * 100, backupCount=10) # 最大100M
+# Time, log level, log file, line number, message
+formatter = logging.Formatter('%(asctime)s-%(levelname)s-%(filename)s-%(funcName)s-%(lineno)s-%(message)s')
+handler.setFormatter(formatter)
+logging.getLogger().addHandler(handler)
+
+
diff --git a/book/dbModels.py b/book/dbModels.py
index 958a63b..ea0686e 100644
--- a/book/dbModels.py
+++ b/book/dbModels.py
@@ -9,9 +9,9 @@ class User(db.Model):
email = db.Column(db.String(120))
role = db.Column(db.String(120))
srole = db.Column(db.Integer, default=0)
- hash_pass = db.Column(db.String(200))
+ hash_pass = db.Column(db.String(240))
kindle_email = db.Column(db.String(120))
- wx_openid = db.Column(db.String(64),unique=True)
+ wx_openid = db.Column(db.String(64), unique=True)
upload_times = db.Column(db.Integer, default=0)
download_times = db.Column(db.Integer, default=0)
feed_count = db.Column(db.Integer, default=0)
diff --git a/book/schedule.py b/book/schedule.py
index a79cf77..7eddf6b 100644
--- a/book/schedule.py
+++ b/book/schedule.py
@@ -1,7 +1,9 @@
+# encoding:utf-8
+from apscheduler.schedulers.background import BackgroundScheduler
from flask_apscheduler import APScheduler
-from flask_apscheduler.scheduler import BackgroundScheduler
-from book.utils.wxMsg import mail_body, send_failed_body, mail_download_url_body
+
from book import app
+from book.utils.wxMsg import mail_body, send_failed_body, mail_download_url_body
from book.utils.mailUtil import send_email
from book.utils import *
@@ -24,9 +26,7 @@ def delete_file_out_24_hours():
def book_send(send_status):
- from book.dbModels import Userlog
- from book.dbModels import db
-
+ from book.dbModels import Userlog, db
with app.app_context():
userlogs = Userlog.query.filter(Userlog.status == send_status).all()
if userlogs:
@@ -61,7 +61,6 @@ def book_send(send_status):
scheduler = APScheduler(BackgroundScheduler())
scheduler.init_app(app)
-
scheduler.add_job(id="delete_file", func=delete_file_out_24_hours, trigger="interval", hours=2, replace_existing=False)
scheduler.add_job(id="send_file", func=book_send, args=("0"), trigger="interval", seconds=180, replace_existing=False,
max_instances=3)
diff --git a/book/utils/__init__.py b/book/utils/__init__.py
index 487578d..a411122 100644
--- a/book/utils/__init__.py
+++ b/book/utils/__init__.py
@@ -218,7 +218,7 @@ def generate_code():
if __name__ == '__main__':
- print("aaa")
+ print(get_file_name(__file__))
# author = "[]未知12213COMchenjin5.comePUBw.COM 12344"
# author = str(author).translate(str.maketrans('', '', '[]未知COAY.COMchenjin5.comePUBw.COM'))
# print(author)
diff --git a/book/utils/mailUtil.py b/book/utils/mailUtil.py
index 9b86838..7cf61fa 100644
--- a/book/utils/mailUtil.py
+++ b/book/utils/mailUtil.py
@@ -1,7 +1,7 @@
import logging
from threading import Thread
from flask_mail import Message
-from book import mail, app
+from book import mail
def send_async_email(app, msg):
@@ -19,5 +19,6 @@ def send_email(subject, body, receiver, attach=None):
logging.error('open file failed.' + e)
msg.html = body
logging.info(f'发送邮件.{subject}-接收邮箱{receiver}')
+ from book import app
Thread(target=send_async_email, args=[app, msg]).start()
return u'发送成功'
diff --git a/book/utils/wxMsg.py b/book/utils/wxMsg.py
index 9d240f9..0a6c41f 100644
--- a/book/utils/wxMsg.py
+++ b/book/utils/wxMsg.py
@@ -1,24 +1,23 @@
# coding: utf-8
import hashlib
-import time, os
+import time
import config
+create_time = str(int(time.time()))
+
def unbind_email_msg(user_email):
- return f'''你好,你已经解绑邮箱:{user_email}\n解除绑定回复:1001'''
+ return f'''已经解绑邮箱:{user_email}\n解除绑定回复:1001'''
def bind_email_msg(user_email):
- return f'''你好,你绑定邮箱:{user_email}\n解除绑定回复:1001'''
+ return f'''绑定邮箱:{user_email}\n解除绑定回复:1001'''
no_book_content = "未找到书籍,在更新中!请换其他的书籍"
-
not_isbn_search = "不支持ISBN搜索,请输入书籍名称搜索!"
-
send_failed_msg = "根据其他用户报告,此书籍无法发送,请换一个编号继续!"
-
-no_bind_email_msg = '''你好,你还没有绑定邮箱!
+no_bind_email_msg = '''你还没有绑定邮箱!
请发送【邮箱地址】进行绑定
例如:book@book.com
查看帮助请回复 ?'''
@@ -37,7 +36,7 @@ def bind_email_msg(user_email):
reply_subscribe = f'''欢迎关注books图书馆,本书站收录图书超乎你的想象
按以下步骤将电子书自动发送到您的邮箱:
-1.在聊天栏里发送邮箱地址 「你的邮箱地址」,如:xxxx@163.com *
+1.在聊天栏里发送邮箱地址 「你的邮箱地址」,如:xxxx@163.com
2.查询书籍,在聊天栏里发送你要找的书籍,直接回复书籍名称,如:平凡的世界
@@ -76,8 +75,8 @@ def send_failed_body(bookname):
'''
-# 当文件大于20M的时候 发送下载地址到邮箱
def mail_download_url_body(filename):
+ """当文件大于20M的时候 发送下载地址到邮箱"""
download_url = config.DOWNLOAD_URL + filename
return f'''
《{filename}》
@@ -91,9 +90,6 @@ def mail_download_url_body(filename):
'''
-create_time = str(int(time.time()))
-
-
def wx_reply_xml(from_user, to_user, msg_content):
"""
desc: 微信回复消息模版
@@ -114,7 +110,7 @@ def wx_reply_xml(from_user, to_user, msg_content):
def check_signature(token, signature, timestamp, nonce):
- """校验签名"""
+ """微信校验签名"""
temp_arr = [token, timestamp, nonce]
temp_arr.sort()
temp_str = ''.join(temp_arr)
diff --git a/book/views/__init__.py b/book/views/__init__.py
index eea435e..054b496 100644
--- a/book/views/__init__.py
+++ b/book/views/__init__.py
@@ -1,6 +1,9 @@
+# -*- encoding: utf-8 -*-
+"""
+Copyright (c) 2019 - present benben
+"""
from flask import render_template
-from . import user, feed, wechat, myfeed
from book import app
@@ -14,3 +17,8 @@ def page_404():
def home():
# logging.error(app.template_folder)
return "欢迎关注公众号:sendtokindles 下载电子书"
+
+
+
+
+
diff --git a/book/views/myfeed.py b/book/views/ebook.py
similarity index 82%
rename from book/views/myfeed.py
rename to book/views/ebook.py
index c5dce1f..5c6a082 100644
--- a/book/views/myfeed.py
+++ b/book/views/ebook.py
@@ -1,11 +1,13 @@
import logging
-from flask import request
+from flask import request, Blueprint
from flask_jwt_extended import get_jwt_identity, jwt_required
-from book import app, jwt, Userlog, db
-from book.utils.ApiResponse import APIResponse
+from book.utils import get_file_name
+from book.utils.ApiResponse import APIResponse
+from book.dbModels import Userlog, db
-@app.route("/user/send/ebook", methods=["POST"])
+blueprint = Blueprint(get_file_name(__file__), __name__, url_prefix='/api/v2')
+@blueprint.route("/send/ebook", methods=["POST"])
@jwt_required()
def dl_ebook():
try:
diff --git a/book/views/feed.py b/book/views/feed.py
index 1ca1863..44a007a 100644
--- a/book/views/feed.py
+++ b/book/views/feed.py
@@ -1,22 +1,20 @@
+# -*-coding: utf-8-*-
import json
-import logging
-
import requests
+from flask import request, Blueprint
from flask_jwt_extended import jwt_required, get_jwt_identity
import config
-from book import app, request, User, db
+from book.dbModels import User, db
+from book.utils import get_file_name
from book.utils.ApiResponse import APIResponse
-headers = {
- 'Content-Type': 'application/x-www-form-urlencoded'
-}
+headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+blueprint = Blueprint(get_file_name(__file__), __name__, url_prefix='/api/v2')
-# 用户同步
-@app.route('/api/v2/sync/user/add', methods=['POST'])
+@blueprint.route('/sync/user/add', methods=['POST'])
def user_add():
-
-
+ """用户同步"""
res = sync_post(request.path)
if res['status'].lower() == "ok":
return APIResponse.success(msg=res['msg'])
@@ -24,10 +22,10 @@ def user_add():
return APIResponse.bad_request(msg=res['msg'])
-# 用户设置
-@app.route('/api/v2/sync/user/seting', methods=['POST'])
+@blueprint.route('/sync/user/seting', methods=['POST'])
@jwt_required()
def user_setting():
+ """用户设置"""
res = sync_post(request.path)
if res['status'].lower() == "ok":
return APIResponse.success(msg=res['msg'])
@@ -35,9 +33,10 @@ def user_setting():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/sync/user/upgrade', methods=['POST'])
+@blueprint.route('/sync/user/upgrade', methods=['POST'])
@jwt_required()
def user_upgrade():
+ """订阅用户升级到付费用户"""
res = sync_post(request.path)
if res['status'].lower() == "ok":
return APIResponse.success(msg=res['msg'])
@@ -45,23 +44,21 @@ def user_upgrade():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/sync/user/info', methods=['POST'])
+@blueprint.route('/sync/user/info', methods=['POST'])
@jwt_required()
def rss_user_info():
res = sync_post(request.path)
if res['status'] == "ok":
- print(res['data'])
- # print(type(res['data']))
user_info = res['data']
return APIResponse.success(data=user_info)
else:
return APIResponse.bad_request(msg=res['msg'])
-# 获取发送日志
-@app.route('/api/v2/my/deliver/logs', methods=['POST'])
+@blueprint.route('/my/deliver/logs', methods=['POST'])
@jwt_required()
def get_deliver_logs():
+ """获取发送订阅日志"""
res = sync_post(request.path)
if res['status'] == "ok":
return APIResponse.success(data=res['data'])
@@ -69,7 +66,7 @@ def get_deliver_logs():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/feed/book/deliver', methods=['POST'])
+@blueprint.route('/feed/book/deliver', methods=['POST'])
@jwt_required()
def get_my_feed_deliver():
res = sync_post(request.path)
@@ -79,9 +76,9 @@ def get_my_feed_deliver():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/my/rss', methods=['POST'])
+@blueprint.route('/my/rss', methods=['POST'])
@jwt_required()
-def rss_my():
+def my_rss():
api_path = '/api/v2/rss/myrss'
res = sync_post(api_path)
if res['status'].lower() == "ok":
@@ -90,7 +87,7 @@ def rss_my():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/my/rss/add', methods=['POST'])
+@blueprint.route('/my/rss/add', methods=['POST'])
@jwt_required()
def my_rss_add():
api_path = '/api/v2/rss/add'
@@ -110,23 +107,22 @@ def my_rss_add():
return APIResponse.bad_request(msg=res['msg'])
-# 公共订阅源
-@app.route('/api/v2/rss/pub', methods=['POST'])
+@blueprint.route('/rss/pub', methods=['POST'])
@jwt_required()
def get_pub_rss():
+ """公共订阅源"""
api_path = '/api/v2/rss/pub'
res = sync_post(api_path)
- # logging.error(res)
if res['status'] == "ok":
return APIResponse.success(data=res['data'])
else:
return APIResponse.bad_request(msg=res['msg'])
-# 删除我的订阅
-@app.route('/api/v2/my/rss/del', methods=['POST'])
+@blueprint.route('/my/rss/del', methods=['POST'])
@jwt_required()
def my_rss_del():
+ """删除我的订阅"""
api_path = '/api/v2/rss/del'
res = sync_post(api_path)
user = get_jwt_identity()
@@ -140,9 +136,10 @@ def my_rss_del():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/rss/share', methods=['POST'])
+@blueprint.route('/rss/share', methods=['POST'])
@jwt_required()
def rss_share():
+ """订阅源共享"""
res = sync_post(request.path)
if res['status'].lower() == "ok":
return APIResponse.success(msg=res['msg'])
@@ -150,7 +147,7 @@ def rss_share():
return APIResponse.bad_request(msg=res['msg'])
-@app.route('/api/v2/rss/invalid', methods=['POST'])
+@blueprint.route('/rss/invalid', methods=['POST'])
@jwt_required()
def rss_invalid():
res = sync_post(request.path)
@@ -170,7 +167,6 @@ def sync_post(path):
request_url = config.RSS2EBOOK_URL
# request_url = "http://127.0.0.1:8080"
res = requests.post(request_url + path, data=data, headers=headers, timeout=60)
- # print(res.content)
if res.status_code == 200:
json_data = json.loads(res.text)
if json_data['status'] == "ok":
diff --git a/book/views/user.py b/book/views/user.py
index e07e87b..ee8733f 100644
--- a/book/views/user.py
+++ b/book/views/user.py
@@ -1,21 +1,26 @@
# encoding:utf-8
import json
import time
-
-from operator import or_
-
import requests
from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity
+from sqlalchemy import or_
from werkzeug.security import check_password_hash, generate_password_hash
-
import config
-from book import request, cache, app, db, User
+from book import cache
+from book.dbModels import db, User
+from flask import request, Blueprint
from book.utils.ApiResponse import APIResponse
-from book.utils import check_email, generate_code, model_to_dict
+from book.utils import check_email, generate_code, model_to_dict, get_file_name
from book.utils.mailUtil import send_email
+blueprint = Blueprint(
+ get_file_name(__file__),
+ __name__,
+ url_prefix='/user'
+)
+
-@app.route('/user/login', methods=['POST'])
+@blueprint.route('/login', methods=['POST'])
def login():
data = request.get_json()
if not all(key in data for key in ['email', 'passwd']):
@@ -34,8 +39,8 @@ def login():
return APIResponse.success(data=data)
-@app.route("/user/email/code/")
-def send_email_verification_code(email):
+@blueprint.route("/email/code/")
+def email_verify_code(email):
if check_email(email):
user = User.query.filter(or_(User.email == email, User.name == email)).first()
if user:
@@ -49,7 +54,7 @@ def send_email_verification_code(email):
return APIResponse.bad_request(msg="无效的邮箱地址!")
-@app.route("/user/sign_up", methods=['POST'])
+@blueprint.route("/sign_up", methods=['POST'])
def sign_up():
"""POST /user/sign_up: user sign up handler
"""
@@ -87,7 +92,7 @@ def sign_up():
return APIResponse.bad_request(msg="注册失败!")
-@app.route('/user/forget/passwd', methods=['POST'])
+@blueprint.route('/forget/passwd', methods=['POST'])
@jwt_required()
def forget_passwd():
data = request.get_json()
@@ -106,12 +111,12 @@ def forget_passwd():
return APIResponse.bad_request(msg="用户名或邮箱地址为空!")
-@app.route('/user/logout')
+@blueprint.route('/logout')
def logout():
return APIResponse.success()
-@app.route('/user/info')
+@blueprint.route('/info')
@jwt_required()
def user_info():
t_user = get_jwt_identity()
@@ -123,7 +128,7 @@ def user_info():
return APIResponse.success(data=data)
-@app.route('/user/update/', methods=['GET', 'POST'])
+@blueprint.route('/update/', methods=['GET', 'POST'])
def user_update(id):
user = User.query.get(id)
return APIResponse.success()
diff --git a/book/views/wechat.py b/book/views/wechat.py
index eaebab3..14cd975 100644
--- a/book/views/wechat.py
+++ b/book/views/wechat.py
@@ -1,16 +1,17 @@
import json
-import logging
-
-import requests
-from flask import redirect, send_from_directory, render_template
+from flask import redirect, send_from_directory, request, Blueprint
from sqlalchemy import or_
from werkzeug.security import generate_password_hash
-from book import request, cache, app, db, upgradeUser
+from book import cache, db, upgradeUser
from book.utils import *
from book.utils.wxMsg import *
-
-@app.route('/api/wechat', methods=['GET', 'POST'])
+blueprint = Blueprint(
+ get_file_name(__file__),
+ __name__,
+ url_prefix='/api'
+)
+@blueprint.route('/wechat', methods=['GET', 'POST'])
def wechat():
if request.method == 'GET':
# 处理验证请求
@@ -131,8 +132,8 @@ def wechat():
return wx_reply_xml(from_user, to_user, reply_help_msg)
-@app.route('/download/')
-def dl(filename):
+@blueprint.route('/download/')
+def dl_file(filename):
if os.path.exists(os.path.join(config.DOWNLOAD_DIR,filename)) is False:
return redirect("/404")
return send_from_directory(config.DOWNLOAD_DIR, filename)
@@ -189,9 +190,3 @@ def create_menu(self):
print("a")
except Exception as e:
print(e)
-if __name__ == '__main__':
- with app.app_context():
- from book.dbModels import User
- content = '892100089@qq.com'
- user_info = User.query.filter(or_(User.email==content,User.name==content)).first()
- print(user_info.name)
\ No newline at end of file
diff --git a/config.py b/config.py
index 3d13e6f..288ace4 100644
--- a/config.py
+++ b/config.py
@@ -6,10 +6,8 @@
APPSECRET = "20764c0ae174a1e12c78e809a877c382"
wechat_token = "kindlebooks"
-SECRET_KEY = os.getenv('SECRET_KEY','ebook')
-
+SECRET_KEY = os.getenv('SECRET_KEY', 'ebook')
SQLALCHEMY_DATABASE_URI = "sqlite:///"+ os.path.join(basedir,'db/ebook.db')
-
SQLALCHEMY_TRACK_MODIFICATIONS = False
CSRF_ENABLED = True
diff --git a/gunicorn-cfg.py b/gunicorn-cfg.py
new file mode 100644
index 0000000..acc54cd
--- /dev/null
+++ b/gunicorn-cfg.py
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+"""
+Copyright (c) 2023 - present AppSeed.us
+"""
+
+bind = '0.0.0.0:8000'
+workers = 4
+worker_class = 'gevent'
+accesslog = '-'
+loglevel = 'debug'
+capture_output = True
+enable_stdio_inheritance = True
diff --git a/gunicorn.conf.py b/gunicorn.conf.py
deleted file mode 100644
index c3eabfe..0000000
--- a/gunicorn.conf.py
+++ /dev/null
@@ -1,3 +0,0 @@
-bind = '0.0.0.0:5000'
-workers = 4
-worker_class = 'gevent'
diff --git a/runtime.tx b/runtime.tx
new file mode 100644
index 0000000..032aea2
--- /dev/null
+++ b/runtime.tx
@@ -0,0 +1 @@
+python-3.9
\ No newline at end of file