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