├── .gitignore ├── LICENSE ├── README.md ├── app ├── __init__.py ├── api │ ├── __init__.py │ ├── authentication.py │ ├── comments.py │ ├── decorators.py │ ├── errors.py │ ├── posts.py │ └── users.py ├── auth │ ├── __init__.py │ ├── forms.py │ └── views.py ├── decorators.py ├── email.py ├── exceptions.py ├── main │ ├── __init__.py │ ├── common.py │ ├── errors.py │ ├── forms.py │ ├── hs_api_log_api.py │ ├── hs_api_log_forms.py │ ├── hs_bilibili_bv_api.py │ ├── hs_bilibili_bv_forms.py │ ├── hs_model_api.py │ ├── hs_model_forms.py │ ├── hs_role_api.py │ ├── hs_role_forms.py │ ├── hs_user_api.py │ ├── hs_user_forms.py │ ├── test_layui.py │ └── views.py ├── model_api_log.py ├── model_bilibili.py ├── models.py ├── static │ ├── bootstrap_local │ │ ├── css │ │ │ ├── bootstrap-table.min.css │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ └── js │ │ │ ├── bootstrap-table.min.js │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ ├── css │ │ ├── login.css │ │ └── styles.css │ ├── image │ │ ├── 1629711723519.jpg │ │ ├── 1629711797490.jpg │ │ ├── favicon.ico │ │ └── favicon.png │ ├── js │ │ ├── TrackballControls.js │ │ ├── jquery.min.js │ │ ├── manager.js │ │ ├── moment-with-locales.min.js │ │ ├── popper.min.js │ │ ├── popper.min.js.map │ │ ├── project_config_tail.js │ │ ├── scheme.js │ │ ├── stats.min.js │ │ ├── task.js │ │ ├── three.min.js │ │ ├── viewer.js │ │ └── web_server_base.js │ └── layui │ │ ├── css │ │ ├── layui.css │ │ ├── layui.mobile.css │ │ └── modules │ │ │ ├── code.css │ │ │ ├── laydate │ │ │ └── default │ │ │ │ └── laydate.css │ │ │ └── layer │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ │ ├── images │ │ └── face │ │ │ ├── 0.gif │ │ │ ├── 1.gif │ │ │ ├── 10.gif │ │ │ ├── 11.gif │ │ │ ├── 12.gif │ │ │ ├── 13.gif │ │ │ ├── 14.gif │ │ │ ├── 15.gif │ │ │ ├── 16.gif │ │ │ ├── 17.gif │ │ │ ├── 18.gif │ │ │ ├── 19.gif │ │ │ ├── 2.gif │ │ │ ├── 20.gif │ │ │ ├── 21.gif │ │ │ ├── 22.gif │ │ │ ├── 23.gif │ │ │ ├── 24.gif │ │ │ ├── 25.gif │ │ │ ├── 26.gif │ │ │ ├── 27.gif │ │ │ ├── 28.gif │ │ │ ├── 29.gif │ │ │ ├── 3.gif │ │ │ ├── 30.gif │ │ │ ├── 31.gif │ │ │ ├── 32.gif │ │ │ ├── 33.gif │ │ │ ├── 34.gif │ │ │ ├── 35.gif │ │ │ ├── 36.gif │ │ │ ├── 37.gif │ │ │ ├── 38.gif │ │ │ ├── 39.gif │ │ │ ├── 4.gif │ │ │ ├── 40.gif │ │ │ ├── 41.gif │ │ │ ├── 42.gif │ │ │ ├── 43.gif │ │ │ ├── 44.gif │ │ │ ├── 45.gif │ │ │ ├── 46.gif │ │ │ ├── 47.gif │ │ │ ├── 48.gif │ │ │ ├── 49.gif │ │ │ ├── 5.gif │ │ │ ├── 50.gif │ │ │ ├── 51.gif │ │ │ ├── 52.gif │ │ │ ├── 53.gif │ │ │ ├── 54.gif │ │ │ ├── 55.gif │ │ │ ├── 56.gif │ │ │ ├── 57.gif │ │ │ ├── 58.gif │ │ │ ├── 59.gif │ │ │ ├── 6.gif │ │ │ ├── 60.gif │ │ │ ├── 61.gif │ │ │ ├── 62.gif │ │ │ ├── 63.gif │ │ │ ├── 64.gif │ │ │ ├── 65.gif │ │ │ ├── 66.gif │ │ │ ├── 67.gif │ │ │ ├── 68.gif │ │ │ ├── 69.gif │ │ │ ├── 7.gif │ │ │ ├── 70.gif │ │ │ ├── 71.gif │ │ │ ├── 8.gif │ │ │ └── 9.gif │ │ ├── lay │ │ └── modules │ │ │ ├── carousel.js │ │ │ ├── code.js │ │ │ ├── colorpicker.js │ │ │ ├── element.js │ │ │ ├── flow.js │ │ │ ├── form.js │ │ │ ├── jquery.js │ │ │ ├── laydate.js │ │ │ ├── layedit.js │ │ │ ├── layer.js │ │ │ ├── laypage.js │ │ │ ├── laytpl.js │ │ │ ├── mobile.js │ │ │ ├── rate.js │ │ │ ├── slider.js │ │ │ ├── table.js │ │ │ ├── transfer.js │ │ │ ├── tree.js │ │ │ ├── upload.js │ │ │ └── util.js │ │ ├── layui.all.js │ │ └── layui.js ├── templates │ ├── 400.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── _macros.html │ ├── auth │ │ ├── change_email.html │ │ ├── change_password.html │ │ ├── email │ │ │ ├── change_email.html │ │ │ ├── change_email.txt │ │ │ ├── confirm.html │ │ │ ├── confirm.txt │ │ │ ├── reset_password.html │ │ │ └── reset_password.txt │ │ ├── login.html │ │ ├── register.html │ │ ├── reset_password.html │ │ └── unconfirmed.html │ ├── base-user.html │ ├── base.html │ ├── hs_api_log_form.html │ ├── hs_api_log_tree.html │ ├── hs_bilibili_bv_form.html │ ├── hs_bilibili_bv_tree.html │ ├── hs_model_form.html │ ├── hs_model_tree.html │ ├── hs_role_form.html │ ├── hs_role_tree.html │ ├── hs_user_form.html │ ├── hs_user_reset_password_form.html │ ├── hs_user_tree.html │ ├── index.html │ ├── login.html │ ├── reset_password.html │ ├── test.html │ └── test_layui.html └── tests │ ├── __init__.py │ ├── test_api.py │ ├── test_basics.py │ ├── test_client.py │ ├── test_selenium.py │ └── test_user_model.py ├── base.py ├── config.py ├── gunicorn.conf.py ├── hello.py ├── manage.py ├── requirements.txt ├── restart_server.sh ├── tests ├── __init__.py ├── test_api.py ├── test_basics.py ├── test_client.py ├── test_selenium.py └── test_user_model.py └── tools ├── AESEncrypt.py ├── __init__.py ├── compress_uuid.py ├── date_tools.py ├── os_tools.py ├── other_tools.py ├── readme.md ├── scale_conversion.py └── short_uuid.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # SQLite databases 39 | *.sqlite 40 | 41 | # Virtual environment 42 | venv 43 | 44 | .idea 45 | 46 | migrations 47 | temp 48 | 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Roger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My WebServer 2 | 3 | [![](https://img.shields.io/badge/version-python3.x-green?style=flat-square)](https://www.python.org/downloads/) 4 | [![GitHub last commit](https://img.shields.io/github/stars/RRRoger/MyWebserver-flask.svg?style=flat-square)](https://github.com/RRRoger/MyWebserver-flask) 5 | [![GitHub issues](https://img.shields.io/github/issues/RRRoger/MyWebserver-flask.svg?style=flat-square)](https://github.com/RRRoger/MyWebserver-flask/issues) 6 | [![GitHub last commit](https://img.shields.io/github/last-commit/RRRoger/MyWebserver-flask.svg?style=flat-square)](https://github.com/RRRoger/MyWebserver-flask/commits/master) 7 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](https://github.com/RRRoger/MyWebserver-flask/blob/master/LICENSE) 8 | 9 | > Flask网站模板 10 | > 11 | > python + flask + LayUI + mysql 12 | 13 | ![](app/static/image/1629711797490.jpg) 14 | 15 | ![](app/static/image/1629711723519.jpg) 16 | 17 | ## Technology stack 18 | 19 | - Python3.* 20 | - [Flask](https://dormousehole.readthedocs.io/en/latest/) 21 | - Mysql 22 | - [Layui](https://www.layui.com/) 23 | 24 | ## Features 25 | 26 | - 增删改查操作 27 | - 用户管理 28 | - 角色管理 29 | - 权限管理 30 | 31 | - 接口日志 32 | - 搜索、分页 33 | - 异常捕获 34 | 35 | ## Init 36 | 37 | - 添加环境变量 38 | 39 | ```bash 40 | export FLASK_APP=hello.py 41 | ``` 42 | 43 | - 创建数据库 44 | 45 | ```bash 46 | # 进入数据库 47 | mysql -uroot -p 48 | 49 | # 创建爱你数据库 50 | create database test_db charset=utf8; 51 | ``` 52 | 53 | - 初始化数据库 54 | 55 | ```bash 56 | flask db init 57 | 58 | # 第一次先删除`migrations`目录 59 | rm -rf migrations 60 | 61 | flask db migrate 62 | 63 | # 修改数据库结构 64 | flask db upgrade 65 | ``` 66 | 67 | - 初始化用户数据 68 | 69 | ```bash 70 | # 初始化用户数据 71 | flask init-tables 72 | ``` 73 | 74 | 75 | 76 | ## Run 77 | 78 | ### 1. 普通启动方式 79 | 80 | ```bash 81 | python hello.py 82 | ``` 83 | 84 | ### 2. with gunicorn 85 | 86 | - 安装`gunicorn` & `gevent` 87 | 88 | ```bash 89 | pip install gunicorn 90 | pip install gevent 91 | ``` 92 | 93 | - 使用`gunicorn`启动服务 94 | 95 | ```bash 96 | cd MyWebserver-flask 97 | 98 | gunicorn -c gunicorn.conf.py hello:app --preload -b 0.0.0.0:5000 99 | 100 | # 后台启动 101 | gunicorn -c gunicorn.conf.py hello:app --preload -b 0.0.0.0:5000 --daemon 102 | ``` 103 | 104 | ## 如何关闭进程? 105 | 106 | - `ps -ef|grep gunicorn` 107 | 108 | ```bash 109 | roger 12434 1 0 15:15 ? 00:00:02 ...... -b 0.0.0.0:5000 --daemon 110 | roger 12442 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 111 | roger 12443 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 112 | roger 12444 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 113 | roger 12445 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 114 | roger 12446 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 115 | roger 12447 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 116 | roger 12448 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 117 | roger 12449 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 118 | roger 12450 12434 0 15:15 ? 00:00:01 ...... -b 0.0.0.0:5000 --daemon 119 | ``` 120 | 121 | - 删除父进程 122 | 123 | ```bash 124 | kill -9 12434 125 | ``` 126 | 127 | ## 查看日志 128 | 129 | ```bash 130 | # 访问日志 131 | tail -f ~/log/gunicorn_access.log 132 | 133 | # 接口日志 134 | tail -f ~/log/gunicorn_info.log 135 | ``` 136 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_bootstrap import Bootstrap 3 | from flask_mail import Mail 4 | from flask_moment import Moment 5 | from flask_sqlalchemy import SQLAlchemy 6 | from flask_login import LoginManager 7 | from flask_pagedown import PageDown 8 | from config import config 9 | 10 | bootstrap = Bootstrap() 11 | mail = Mail() 12 | moment = Moment() 13 | db = SQLAlchemy(use_native_unicode="utf8") 14 | pagedown = PageDown() 15 | 16 | login_manager = LoginManager() 17 | login_manager.login_view = 'auth.login' 18 | 19 | 20 | def create_app(config_name): 21 | app = Flask(__name__) 22 | app.config.from_object(config[config_name]) 23 | config[config_name].init_app(app) 24 | app.debug = True 25 | bootstrap.init_app(app) 26 | mail.init_app(app) 27 | moment.init_app(app) 28 | db.init_app(app) 29 | login_manager.init_app(app) 30 | pagedown.init_app(app) 31 | # if app.config['SSL_REDIRECT']: 32 | # from flask_sslify import SSLify 33 | # sslify = SSLify(app) 34 | 35 | from .main import main as main_blueprint 36 | app.register_blueprint(main_blueprint, url_prefix='/') 37 | 38 | from .auth import auth as auth_blueprint 39 | app.register_blueprint(auth_blueprint, url_prefix='/auth') 40 | 41 | from .api import api as api_blueprint 42 | app.register_blueprint(api_blueprint, url_prefix='/api/v1') 43 | 44 | return app 45 | -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | api = Blueprint('api', __name__) 3 | 4 | from . import authentication 5 | from . import posts 6 | from . import users 7 | from . import comments 8 | from . import errors 9 | 10 | -------------------------------------------------------------------------------- /app/api/authentication.py: -------------------------------------------------------------------------------- 1 | from flask import g, jsonify 2 | from flask_httpauth import HTTPBasicAuth 3 | from ..models import User 4 | from . import api 5 | from .errors import unauthorized, forbidden 6 | 7 | auth = HTTPBasicAuth() 8 | 9 | 10 | @auth.verify_password 11 | def verify_password(email_or_token, password): 12 | if email_or_token == '': 13 | return False 14 | if password == '': 15 | g.current_user = User.verify_auth_token(email_or_token) 16 | g.token_used = True 17 | return g.current_user is not None 18 | user = User.query.filter_by(email=email_or_token.lower()).first() 19 | if not user: 20 | return False 21 | g.current_user = user 22 | g.token_used = False 23 | return user.verify_password(password) 24 | 25 | 26 | @auth.error_handler 27 | def auth_error(): 28 | return unauthorized('Invalid credentials') 29 | 30 | 31 | @api.before_request 32 | @auth.login_required 33 | def before_request(): 34 | if not g.current_user.is_anonymous and \ 35 | not g.current_user.confirmed: 36 | return forbidden('Unconfirmed account') 37 | 38 | 39 | @api.route('/tokens/', methods=['POST']) 40 | def get_token(): 41 | if g.current_user.is_anonymous or g.token_used: 42 | return unauthorized('Invalid credentials') 43 | return jsonify({'token': g.current_user.generate_auth_token( 44 | expiration=3600), 'expiration': 3600}) 45 | -------------------------------------------------------------------------------- /app/api/comments.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, request, g, url_for, current_app 2 | from .. import db 3 | from ..models import Post, Permission, Comment 4 | from . import api 5 | from .decorators import permission_required 6 | 7 | 8 | @api.route('/comments/') 9 | def get_comments(): 10 | page = request.args.get('page', 1, type=int) 11 | pagination = Comment.query.order_by(Comment.timestamp.desc()).paginate( 12 | page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], 13 | error_out=False) 14 | comments = pagination.items 15 | prev = None 16 | if pagination.has_prev: 17 | prev = url_for('api.get_comments', page=page-1) 18 | next = None 19 | if pagination.has_next: 20 | next = url_for('api.get_comments', page=page+1) 21 | return jsonify({ 22 | 'comments': [comment.to_json() for comment in comments], 23 | 'prev': prev, 24 | 'next': next, 25 | 'count': pagination.total 26 | }) 27 | 28 | 29 | @api.route('/comments/') 30 | def get_comment(id): 31 | comment = Comment.query.get_or_404(id) 32 | return jsonify(comment.to_json()) 33 | 34 | 35 | @api.route('/posts//comments/') 36 | def get_post_comments(id): 37 | post = Post.query.get_or_404(id) 38 | page = request.args.get('page', 1, type=int) 39 | pagination = post.comments.order_by(Comment.timestamp.asc()).paginate( 40 | page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], 41 | error_out=False) 42 | comments = pagination.items 43 | prev = None 44 | if pagination.has_prev: 45 | prev = url_for('api.get_post_comments', id=id, page=page-1) 46 | next = None 47 | if pagination.has_next: 48 | next = url_for('api.get_post_comments', id=id, page=page+1) 49 | return jsonify({ 50 | 'comments': [comment.to_json() for comment in comments], 51 | 'prev': prev, 52 | 'next': next, 53 | 'count': pagination.total 54 | }) 55 | 56 | 57 | @api.route('/posts//comments/', methods=['POST']) 58 | @permission_required(Permission.COMMENT) 59 | def new_post_comment(id): 60 | post = Post.query.get_or_404(id) 61 | comment = Comment.from_json(request.json) 62 | comment.author = g.current_user 63 | comment.post = post 64 | db.session.add(comment) 65 | db.session.commit() 66 | return jsonify(comment.to_json()), 201, \ 67 | {'Location': url_for('api.get_comment', id=comment.id)} 68 | -------------------------------------------------------------------------------- /app/api/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import g 3 | from .errors import forbidden 4 | 5 | 6 | def permission_required(permission): 7 | def decorator(f): 8 | @wraps(f) 9 | def decorated_function(*args, **kwargs): 10 | if not g.current_user.can(permission): 11 | return forbidden('Insufficient permissions') 12 | return f(*args, **kwargs) 13 | return decorated_function 14 | return decorator 15 | -------------------------------------------------------------------------------- /app/api/errors.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from app.exceptions import ValidationError 3 | from . import api 4 | 5 | 6 | def bad_request(message): 7 | response = jsonify({'error': 'bad request', 'message': message}) 8 | response.status_code = 400 9 | return response 10 | 11 | 12 | def unauthorized(message): 13 | response = jsonify({'error': 'unauthorized', 'message': message}) 14 | response.status_code = 401 15 | return response 16 | 17 | 18 | def forbidden(message): 19 | response = jsonify({'error': 'forbidden', 'message': message}) 20 | response.status_code = 403 21 | return response 22 | 23 | 24 | @api.errorhandler(ValidationError) 25 | def validation_error(e): 26 | return bad_request(e.args[0]) 27 | -------------------------------------------------------------------------------- /app/api/posts.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, request, g, url_for, current_app 2 | from .. import db 3 | from ..models import Post, Permission 4 | from . import api 5 | from .decorators import permission_required 6 | from .errors import forbidden 7 | from app.main.common import try_except_log, response 8 | 9 | 10 | @api.route('/posts/') 11 | def get_posts(): 12 | page = request.args.get('page', 1, type=int) 13 | pagination = Post.query.paginate( 14 | page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], 15 | error_out=False) 16 | posts = pagination.items 17 | prev = None 18 | if pagination.has_prev: 19 | prev = url_for('api.get_posts', page=page-1) 20 | next = None 21 | if pagination.has_next: 22 | next = url_for('api.get_posts', page=page+1) 23 | return jsonify({ 24 | 'posts': [post.to_json() for post in posts], 25 | 'prev': prev, 26 | 'next': next, 27 | 'count': pagination.total 28 | }) 29 | 30 | 31 | @api.route('/posts/') 32 | def get_post(id): 33 | post = Post.query.get_or_404(id) 34 | return jsonify(post.to_json()) 35 | 36 | 37 | @api.route('/posts/', methods=['POST']) 38 | @permission_required(Permission.WRITE) 39 | def new_post(): 40 | post = Post.from_json(request.json) 41 | post.author = g.current_user 42 | db.session.add(post) 43 | db.session.commit() 44 | return jsonify(post.to_json()), 201, \ 45 | {'Location': url_for('api.get_post', id=post.id)} 46 | 47 | 48 | @api.route('/posts/', methods=['PUT']) 49 | @permission_required(Permission.WRITE) 50 | def edit_post(id): 51 | post = Post.query.get_or_404(id) 52 | if g.current_user != post.author and \ 53 | not g.current_user.can(Permission.ADMIN): 54 | return forbidden('Insufficient permissions') 55 | post.body = request.json.get('body', post.body) 56 | db.session.add(post) 57 | db.session.commit() 58 | return jsonify(post.to_json()) 59 | 60 | ############################################# ERP ###################################### 61 | 62 | import requests 63 | import json 64 | 65 | eepromModel ={ 66 | "MAC":"", 67 | "SN":"", 68 | "AngleOffset":"", 69 | "AccurityAngleOffsetPeak":0, 70 | "AccurityAngleOffsetPhase":0, 71 | "DataFormat": 0, 72 | "MoterType": 1, 73 | "ProductDate":"", 74 | "CalibrationFilePath":"calibrationFile", 75 | "HardwareInfo":{}, 76 | "SAES":""} 77 | base_url = 'http://172.31.0.63:8069' 78 | 79 | 80 | 81 | def print_error_info(response_json): 82 | print(response_json['code'] + response_json['message']) 83 | 84 | def Save2File(file, data): 85 | with open(file, 'w') as f: 86 | f.write(data) 87 | 88 | def jsonSave(file, data): 89 | with open(file, 'w') as f: 90 | json.dump(data, f) 91 | 92 | 93 | ################ mac/sn/production date ######################## 94 | def GetMac_SN_ProductDate(base_url, lidarID, eepromModel): 95 | data = { 96 | "lidarid": lidarID, 97 | } 98 | req_url='/erp/get/lidar/info' 99 | response_info = requests.post(base_url + req_url, data=data) 100 | response_json = json.loads(response_info.content.decode()) 101 | if response_json['code'] == 0: 102 | eepromModel['MAC'] = response_json['result']['mac_address'] 103 | eepromModel['SN'] = response_json['result']['sn'] 104 | eepromModel['ProductDate'] = response_json['result']['production_date'] 105 | 106 | return 0 107 | else: 108 | print_error_info(response_json) 109 | return -1 110 | 111 | ################# get angle file file ######################### 112 | def GetCalibrationFile(base_url, lidarID, eepromModel): 113 | data = { 114 | 'lidar_name': lidarID 115 | } 116 | req_url = '/erp/lidar/workstation/get_angle_file' 117 | response_info = requests.get(base_url + req_url, params=data ) 118 | response_json = json.loads( response_info.content.decode()) 119 | if response_json['code'] == 0: 120 | req_url = response_json['result']['url'] 121 | angle_respose = requests.get(req_url) 122 | Save2File(eepromModel['CalibrationFilePath'],str(angle_respose.content.decode())) 123 | return 0 124 | else: 125 | print_error_info(response_json) 126 | return -1 127 | 128 | ################# angle offset ######################### 129 | def GetAngleOffset(base_url, lidarID, eepromModel): 130 | data = { 131 | "lidarid": lidarID, 132 | "assembly_item_codes": "adjustment_angle_offset;adjustment_A;adjustment_C" 133 | } 134 | req_url = "/erp/get/lidar/assembly/info" 135 | response_info = requests.post(base_url + req_url, data=data) 136 | response_json = json.loads( response_info.content.decode()) 137 | if response_json['code'] == 0: 138 | eepromModel['AngleOffset'] = response_json['result']['adjustment_angle_offset']['content'] 139 | eepromModel['AccurityAngleOffsetPeak'] = response_json['result']['adjustment_A']['content'] 140 | eepromModel['AccurityAngleOffsetPhase'] = response_json['result']['adjustment_C']['content'] 141 | return 0 142 | else: 143 | print_error_info(response_json) 144 | return -1 145 | @api.route('/get_e2prom/', methods=['POST']) 146 | def get_e2prom(): 147 | GetMac_SN_ProductDate(base_url=base_url, lidarID='ARES-007',eepromModel=eepromModel) 148 | GetCalibrationFile(base_url=base_url, lidarID='WR36-500-66-44', eepromModel=eepromModel) 149 | GetAngleOffset(base_url=base_url, lidarID="ARES-003", eepromModel=eepromModel) 150 | return eepromModel 151 | ############################################# HSM ###################################### 152 | 153 | 154 | @api.route('/posts/test', methods=['POST', 'GET']) 155 | @permission_required(Permission.WRITE) 156 | @try_except_log(add_log=True) 157 | def post_test(): 158 | return response({}) 159 | 160 | -------------------------------------------------------------------------------- /app/api/users.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, request, current_app, url_for 2 | from . import api 3 | from ..models import User, Post 4 | 5 | 6 | @api.route('/users/') 7 | def get_user(id): 8 | user = User.query.get_or_404(id) 9 | return jsonify(user.to_json()) 10 | 11 | 12 | @api.route('/users//posts/') 13 | def get_user_posts(id): 14 | user = User.query.get_or_404(id) 15 | page = request.args.get('page', 1, type=int) 16 | pagination = user.posts.order_by(Post.timestamp.desc()).paginate( 17 | page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], 18 | error_out=False) 19 | posts = pagination.items 20 | prev = None 21 | if pagination.has_prev: 22 | prev = url_for('api.get_user_posts', id=id, page=page-1) 23 | next = None 24 | if pagination.has_next: 25 | next = url_for('api.get_user_posts', id=id, page=page+1) 26 | return jsonify({ 27 | 'posts': [post.to_json() for post in posts], 28 | 'prev': prev, 29 | 'next': next, 30 | 'count': pagination.total 31 | }) 32 | 33 | 34 | @api.route('/users//timeline/') 35 | def get_user_followed_posts(id): 36 | user = User.query.get_or_404(id) 37 | page = request.args.get('page', 1, type=int) 38 | pagination = user.followed_posts.order_by(Post.timestamp.desc()).paginate( 39 | page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], 40 | error_out=False) 41 | posts = pagination.items 42 | prev = None 43 | if pagination.has_prev: 44 | prev = url_for('api.get_user_followed_posts', id=id, page=page-1) 45 | next = None 46 | if pagination.has_next: 47 | next = url_for('api.get_user_followed_posts', id=id, page=page+1) 48 | return jsonify({ 49 | 'posts': [post.to_json() for post in posts], 50 | 'prev': prev, 51 | 'next': next, 52 | 'count': pagination.total 53 | }) 54 | -------------------------------------------------------------------------------- /app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | auth = Blueprint('auth', __name__) 4 | 5 | from . import views 6 | -------------------------------------------------------------------------------- /app/auth/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, PasswordField, BooleanField, SubmitField 3 | from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo 4 | from wtforms import ValidationError 5 | from ..models import User 6 | 7 | 8 | class LoginForm(FlaskForm): 9 | email = StringField('Email', validators=[DataRequired(), Length(1, 64), 10 | Email()]) 11 | password = PasswordField('Password', validators=[DataRequired()]) 12 | remember_me = BooleanField('Keep me logged in') 13 | submit = SubmitField('Log In') 14 | 15 | 16 | class RegistrationForm(FlaskForm): 17 | email = StringField('Email', validators=[DataRequired(), Length(1, 64), 18 | Email()]) 19 | username = StringField('Username', validators=[ 20 | DataRequired(), Length(1, 64), 21 | Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, 22 | 'Usernames must have only letters, numbers, dots or ' 23 | 'underscores')]) 24 | password = PasswordField('Password', validators=[ 25 | DataRequired(), EqualTo('password2', message='Passwords must match.')]) 26 | password2 = PasswordField('Confirm password', validators=[DataRequired()]) 27 | submit = SubmitField('Register') 28 | 29 | def validate_email(self, field): 30 | if User.query.filter_by(email=field.data.lower()).first(): 31 | raise ValidationError('Email already registered.') 32 | 33 | def validate_username(self, field): 34 | if User.query.filter_by(username=field.data).first(): 35 | raise ValidationError('Username already in use.') 36 | 37 | 38 | class ChangePasswordForm(FlaskForm): 39 | old_password = PasswordField('Old password', validators=[DataRequired()]) 40 | password = PasswordField('New password', validators=[ 41 | DataRequired(), EqualTo('password2', message='Passwords must match.')]) 42 | password2 = PasswordField('Confirm new password', 43 | validators=[DataRequired()]) 44 | submit = SubmitField('Update Password') 45 | 46 | 47 | class PasswordResetRequestForm(FlaskForm): 48 | email = StringField('Email', validators=[DataRequired(), Length(1, 64), 49 | Email()]) 50 | submit = SubmitField('Reset Password') 51 | 52 | 53 | class PasswordResetForm(FlaskForm): 54 | password = PasswordField('New Password', validators=[ 55 | DataRequired(), EqualTo('password2', message='Passwords must match')]) 56 | password2 = PasswordField('Confirm password', validators=[DataRequired()]) 57 | submit = SubmitField('Reset Password') 58 | 59 | 60 | class ChangeEmailForm(FlaskForm): 61 | email = StringField('New Email', validators=[DataRequired(), Length(1, 64), Email()]) 62 | password = PasswordField('Password', validators=[DataRequired()]) 63 | submit = SubmitField('Update Email Address') 64 | 65 | def validate_email(self, field): 66 | if User.query.filter_by(email=field.data.lower()).first(): 67 | raise ValidationError('Email already registered.') 68 | -------------------------------------------------------------------------------- /app/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from flask import abort 3 | from flask_login import current_user 4 | from .models import Permission 5 | 6 | 7 | def permission_required(permission): 8 | def decorator(f): 9 | @wraps(f) 10 | def decorated_function(*args, **kwargs): 11 | if not current_user.can(permission): 12 | abort(403) 13 | return f(*args, **kwargs) 14 | return decorated_function 15 | return decorator 16 | 17 | 18 | def admin_required(f): 19 | return permission_required(Permission.ADMIN)(f) 20 | 21 | # def pm_required(f): 22 | 23 | # return permission_required(Permission.Publishing_SoftWare)(f) and \ 24 | # permission_required(Permission.Permission.PreviewSoftware)(f) 25 | 26 | # def fl_required(f): 27 | # return permission_required(Permission.Specify_Upgrade_Version)(f) and \ 28 | # permission_required(Permission.Specify_Preupgrade_Version)(f) and \ 29 | # permission_required(Permission.Add_Remove_Burn_Upgrade_Rsa_Pub_List)(f) and \ 30 | # permission_required(Permission.Add_Remove_E2prom_Rsa_Pub_List)(f) 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/email.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from flask import current_app, render_template 3 | from flask_mail import Message 4 | from . import mail 5 | 6 | 7 | def send_async_email(app, msg): 8 | with app.app_context(): 9 | mail.send(msg) 10 | 11 | 12 | def send_email(to, subject, template, **kwargs): 13 | if 0: 14 | app = current_app._get_current_object() 15 | msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject, 16 | sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to]) 17 | msg.body = render_template(template + '.txt', **kwargs) 18 | msg.html = render_template(template + '.html', **kwargs) 19 | thr = Thread(target=send_async_email, args=[app, msg]) 20 | thr.start() 21 | return thr 22 | -------------------------------------------------------------------------------- /app/exceptions.py: -------------------------------------------------------------------------------- 1 | class ValidationError(ValueError): 2 | pass 3 | 4 | 5 | class UserError(Exception): 6 | pass 7 | 8 | 9 | class Warning(Exception): 10 | pass 11 | 12 | -------------------------------------------------------------------------------- /app/main/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | main = Blueprint('main', __name__) 3 | 4 | from . import views, errors 5 | from ..models import Permission 6 | from . import common 7 | from . import test_layui 8 | from . import hs_user_api 9 | from . import hs_user_forms 10 | from . import hs_role_forms 11 | from . import hs_role_api 12 | from . import hs_model_api 13 | from . import hs_model_forms 14 | from . import hs_api_log_api 15 | from . import hs_api_log_forms 16 | 17 | from . import hs_bilibili_bv_api 18 | from . import hs_bilibili_bv_forms 19 | 20 | 21 | @main.app_context_processor 22 | def inject_permissions(): 23 | return dict(Permission=Permission) 24 | -------------------------------------------------------------------------------- /app/main/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import flash, request, url_for 3 | from flask_login import current_user 4 | from functools import wraps 5 | from sqlalchemy import exc 6 | from flask import jsonify 7 | from app.exceptions import UserError 8 | from app.model_api_log import HSApiLog 9 | from flask import current_app 10 | from app import db 11 | 12 | 13 | def response(data): 14 | """ 15 | 返回接口使用的报文格式 16 | :param data: 17 | :return: 18 | """ 19 | result = { 20 | 'code': 0, 21 | 'data': data, 22 | } 23 | return jsonify(result) 24 | 25 | 26 | def try_except_log(add_log=False, null_body=False): 27 | """ 28 | :param add_log: 是否创建日志 29 | :param null_body: 不记录报文数据 30 | :return: 31 | """ 32 | def outer_wrapper(func): 33 | 34 | @wraps(func) 35 | def _wrapper(*args, **kw): 36 | 37 | # is_success: 接口成功/失败; current_user_id:当前用户id, result函数返回值 38 | is_success, result = True, jsonify({}) 39 | 40 | try: 41 | result = func(*args, **kw) 42 | db.session.commit() 43 | except exc.IntegrityError as e: 44 | db.session.rollback() 45 | is_success = False 46 | result = jsonify({'code': -10001, 'msg': "唯一性验证报错, 请不要重复创建!"}) 47 | except exc.OperationalError as e: 48 | db.session.rollback() 49 | is_success = False 50 | result = jsonify({'code': -10002, 'msg': "数据库操作错误: %s" % str(e)}) 51 | except UserError as e: 52 | db.session.rollback() 53 | is_success = False 54 | result = jsonify({'code': -10003, 'msg': "错误: %s" % str(e)}) 55 | except Exception as e: 56 | db.session.rollback() 57 | is_success = False 58 | result = jsonify({'code': -10000, 'msg': "系统错误: %s" % str(e)}) 59 | finally: 60 | current_user_id = None if current_user.is_anonymous else current_user.id 61 | # 添加用户操作日志, 只有用户id存在才创建 62 | if add_log and current_user_id: 63 | 64 | response_body = '' 65 | if hasattr(result, 'data'): 66 | response_body = result.data 67 | 68 | # 特殊场景不记录报文 69 | form_body = str(request.form) 70 | file_body = str(request.files) 71 | data_body = str(request.data) 72 | 73 | if null_body: 74 | form_body = '[has been cleared]' 75 | file_body = '[has been cleared]' 76 | data_body = '[has been cleared]' 77 | response_body = '[has been cleared]' 78 | 79 | log = HSApiLog( 80 | is_success=is_success, url=request.path, remote_addr=request.remote_addr, 81 | form_body=form_body, file_body=file_body, data_body=data_body, 82 | response_body=response_body, create_uid=current_user_id, 83 | ) 84 | db.session.add(log) 85 | db.session.commit() 86 | return result 87 | 88 | return _wrapper 89 | 90 | return outer_wrapper 91 | 92 | 93 | def delete_me(db, clazz, context=None): 94 | """ 95 | 删除model, 通用函数 96 | :param db: database 97 | :param clazz: 对象clazz 98 | :param context: 上下文 99 | :return: 100 | """ 101 | 102 | allow_delete = current_app.config.get('ALLOW_DELETE') 103 | if not allow_delete: 104 | raise UserError("不允许删除任何记录!") 105 | 106 | data = request.form 107 | record_id = data['record_id'] 108 | records = clazz.query.filter_by(id=record_id).first_or_404() 109 | db.session.delete(records) 110 | db.session.commit() 111 | return True 112 | -------------------------------------------------------------------------------- /app/main/errors.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request, jsonify 2 | from . import main 3 | 4 | 5 | @main.app_errorhandler(403) 6 | def forbidden(e): 7 | if request.accept_mimetypes.accept_json and \ 8 | not request.accept_mimetypes.accept_html: 9 | response = jsonify({'error': 'forbidden'}) 10 | response.status_code = 403 11 | return response 12 | return render_template('403.html'), 403 13 | 14 | 15 | @main.app_errorhandler(404) 16 | def page_not_found(e): 17 | if request.accept_mimetypes.accept_json and \ 18 | not request.accept_mimetypes.accept_html: 19 | response = jsonify({'error': 'not found'}) 20 | response.status_code = 404 21 | return response 22 | return render_template('404.html'), 404 23 | 24 | 25 | @main.app_errorhandler(500) 26 | def internal_server_error(e): 27 | if request.accept_mimetypes.accept_json and \ 28 | not request.accept_mimetypes.accept_html: 29 | response = jsonify({'error': 'internal server error'}) 30 | response.status_code = 500 31 | return response 32 | return render_template('500.html'), 500 33 | -------------------------------------------------------------------------------- /app/main/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, TextAreaField, BooleanField, SelectField,\ 3 | SubmitField 4 | from wtforms.validators import DataRequired, Length, Email, Regexp 5 | from wtforms import ValidationError 6 | from flask_pagedown.fields import PageDownField 7 | from ..models import Role, User 8 | 9 | 10 | class NameForm(FlaskForm): 11 | name = StringField('What is your name?', validators=[DataRequired()]) 12 | submit = SubmitField('Submit') 13 | 14 | 15 | class EditProfileForm(FlaskForm): 16 | name = StringField('Real name', validators=[Length(0, 64)]) 17 | location = StringField('Location', validators=[Length(0, 64)]) 18 | about_me = TextAreaField('About me') 19 | submit = SubmitField('Submit') 20 | 21 | 22 | class EditProfileAdminForm(FlaskForm): 23 | email = StringField('Email', validators=[DataRequired(), Length(1, 64), 24 | Email()]) 25 | username = StringField('Username', validators=[ 26 | DataRequired(), Length(1, 64), 27 | Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, 28 | 'Usernames must have only letters, numbers, dots or ' 29 | 'underscores')]) 30 | confirmed = BooleanField('Confirmed') 31 | role = SelectField('Role', coerce=int) 32 | name = StringField('Real name', validators=[Length(0, 64)]) 33 | location = StringField('Location', validators=[Length(0, 64)]) 34 | about_me = TextAreaField('About me') 35 | submit = SubmitField('Submit') 36 | 37 | def __init__(self, user, *args, **kwargs): 38 | super(EditProfileAdminForm, self).__init__(*args, **kwargs) 39 | self.role.choices = [(role.id, role.name) 40 | for role in Role.query.order_by(Role.name).all()] 41 | self.user = user 42 | 43 | def validate_email(self, field): 44 | if field.data != self.user.email and \ 45 | User.query.filter_by(email=field.data).first(): 46 | raise ValidationError('Email already registered.') 47 | 48 | def validate_username(self, field): 49 | if field.data != self.user.username and \ 50 | User.query.filter_by(username=field.data).first(): 51 | raise ValidationError('Username already in use.') 52 | 53 | 54 | class PostForm(FlaskForm): 55 | body = PageDownField("What's on your mind?", validators=[DataRequired()]) 56 | submit = SubmitField('Submit') 57 | 58 | 59 | class CommentForm(FlaskForm): 60 | body = StringField('Enter your comment', validators=[DataRequired()]) 61 | submit = SubmitField('Submit') 62 | -------------------------------------------------------------------------------- /app/main/hs_api_log_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import jsonify, request, current_app, url_for 4 | from . import main 5 | from ..models import User, Post 6 | from flask import render_template, session 7 | from flask_login import login_user, logout_user, login_required 8 | from .hs_api_log_forms import HsApiLogSearch, HsApiLogForm 9 | from ..model_api_log import HSApiLog 10 | from flask import Flask 11 | from .. import db 12 | from sqlalchemy import desc 13 | from .common import try_except_log, response, delete_me 14 | import logging 15 | from app.exceptions import UserError 16 | _logger = Flask(__name__).logger 17 | from flask_paginate import Pagination, get_page_parameter 18 | 19 | 20 | # View 接口 21 | @main.route('/api_log/tree', methods=['GET', 'POST']) 22 | @login_required 23 | def hs_api_log_tree(): # 这个函数里不再处理提交按钮,使用Ajax局部刷新 24 | form = HsApiLogSearch() 25 | ctx = { 26 | 'ALLOW_DELETE': current_app.config.get('ALLOW_DELETE') 27 | } 28 | return render_template('hs_api_log_tree.html', name=session.get('name'), form=form, ctx=ctx) 29 | 30 | 31 | @main.route('/api_log/form/', methods=['GET', 'POST']) 32 | @main.route('/api_log/form/', methods=['GET', 'POST']) 33 | @login_required 34 | def hs_api_log_form(record_id): 35 | this_obj = HSApiLog 36 | 37 | form = HsApiLogForm() 38 | record = this_obj.query.filter_by(id=record_id).first() 39 | return render_template('hs_api_log_form.html', name=session.get('name'), form=form, record=record) 40 | 41 | 42 | # Api 接口 43 | @main.route('/api_log-create-update', methods=['GET', 'POST']) 44 | @login_required 45 | @try_except_log(add_log=True) 46 | def api_log_create_update(): 47 | this_obj = HSApiLog 48 | 49 | # 哪一个模型, 要删除的对象名 50 | data = request.form 51 | _logger.info(data) 52 | record_id = data['record_id'] 53 | 54 | if record_id: 55 | record = this_obj.query.filter_by(id=record_id).first() 56 | else: 57 | record = this_obj() 58 | 59 | record.name = data['name'] 60 | 61 | db.session.add(record) 62 | db.session.commit() 63 | return response({ 64 | 'record_id': record.id, 65 | 'url': record.url, 66 | 'create_date': record.create_date, 67 | 'update_time': record.update_time, 68 | 'create_uid': record.create_uid, 69 | 'is_success': record.is_success, 70 | }) 71 | 72 | 73 | @main.route('/api_log/search', methods=['POST']) 74 | @login_required 75 | @try_except_log() 76 | def find_hs_api_log(): 77 | 78 | this_obj = HSApiLog 79 | query = this_obj.query.order_by(desc(this_obj.id)) 80 | 81 | form_data = request.form 82 | 83 | current_page = int(form_data.get('current_page') or 0) 84 | page_size = int(form_data.get('page_size') or 10) 85 | 86 | # 按照某个字段搜索 87 | def find_url(): 88 | if not form_data.get('content'): 89 | return query.order_by('id').paginate(current_page, page_size, error_out=False) 90 | return query.order_by('id').filter(this_obj.url.like('%'+form_data.get('content')+'%')).paginate(current_page, page_size, error_out=False) 91 | 92 | def find_remote_addr(): 93 | if not form_data.get('content'): 94 | return query.order_by('id').paginate(current_page, page_size, error_out=False) 95 | return query.order_by('id').filter(this_obj.remote_addr.like('%'+form_data.get('content')+'%')).paginate(current_page, page_size, error_out=False) 96 | 97 | # 按照create_uid字段搜索 98 | def find_create_uid(): 99 | if not form_data.get('content'): 100 | return query.paginate(current_page, page_size, error_out=False) 101 | insts = User.query.filter(User.username.like('%' + request.form.get('content') + '%')).all() 102 | inst_ids = [inst.id for inst in insts] or [-1] 103 | return query.filter(this_obj.create_uid.in_(inst_ids)).paginate(current_page, page_size, error_out=False) 104 | 105 | # 按照create_uid字段搜索 106 | def find_is_success(): 107 | if not form_data.get('content'): 108 | return query.paginate(current_page, page_size, error_out=False) 109 | 110 | return query.filter(this_obj.is_success.is_(form_data['content'] == '1')).paginate(current_page, page_size, error_out=False) 111 | 112 | methods = { 113 | 'url': find_url, # 对应上面的某一个函数 114 | 'create_uid': find_create_uid, # 对应上面的某一个函数 115 | 'is_success': find_is_success, # 对应上面的某一个函数 116 | 'remote_addr': find_remote_addr, # 对应上面的某一个函数 117 | } 118 | 119 | paginate = methods[form_data.get('method')]() 120 | data = [] 121 | for record in paginate.items: 122 | item = { 123 | 'id': record.id, 124 | 'url': record.url, 125 | 'remote_addr': record.remote_addr, 126 | 'is_success': record.is_success, 127 | 'create_date': record.create_date, 128 | 'update_time': record.update_time, 129 | 'create_user_name': record.create_user_name, 130 | } 131 | data.append(item) 132 | 133 | result = { 134 | 'total': paginate.total, 135 | 'data': data 136 | } 137 | 138 | return response(result) 139 | 140 | 141 | @main.route('/api_log-delete', methods=['GET', 'POST']) 142 | @login_required 143 | @try_except_log(add_log=True) 144 | def api_log_delete_new(): 145 | # 哪一个模型, 要删除的对象名 146 | raise UserError("不允许删除用户") 147 | 148 | -------------------------------------------------------------------------------- /app/main/hs_api_log_forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, SubmitField, SelectField, PasswordField, IntegerField, TextAreaField, BooleanField 3 | from wtforms.validators import DataRequired, EqualTo, Length 4 | 5 | 6 | class HsApiLogSearch(FlaskForm): 7 | methods = [ 8 | ('url', 'URL'), 9 | ('create_uid', 'Create User'), 10 | ('is_success', 'Is Success'), 11 | ('remote_addr', 'Remote Address'), 12 | ] 13 | method = SelectField(choices=methods, validators=[DataRequired(message=u'名称不能为空')], coerce=str) 14 | content = StringField() 15 | submit = SubmitField('搜索') 16 | 17 | 18 | class HsApiLogForm(FlaskForm): 19 | record_id = IntegerField(validators=[]) 20 | url = StringField() 21 | remote_addr = StringField() 22 | 23 | is_success = BooleanField() 24 | 25 | form_body = TextAreaField() 26 | data_body = TextAreaField() 27 | file_body = TextAreaField() 28 | response_body = TextAreaField() 29 | 30 | create_date = StringField() 31 | create_user_name = StringField() 32 | -------------------------------------------------------------------------------- /app/main/hs_bilibili_bv_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import jsonify, request, current_app, url_for 4 | from . import main 5 | from ..models import User, Post 6 | from flask import render_template, session 7 | from flask_login import login_user, logout_user, login_required 8 | from .hs_bilibili_bv_forms import HsBilibiliBvSearch, HsBilibiliBvForm 9 | from app.model_bilibili import BilibiliBv 10 | from flask import Flask 11 | from .. import db 12 | from sqlalchemy import desc 13 | from .common import try_except_log, response, delete_me 14 | import logging 15 | from flask_paginate import Pagination, get_page_parameter 16 | from app.exceptions import UserError 17 | _logger = Flask(__name__).logger 18 | 19 | 20 | # View 接口 21 | @main.route('/bilibili_bv/tree', methods=['GET', 'POST']) 22 | @login_required 23 | def hs_bilibili_bv_tree(): # 这个函数里不再处理提交按钮,使用Ajax局部刷新 24 | form = HsBilibiliBvSearch() 25 | ctx = { 26 | 'ALLOW_DELETE': current_app.config.get('ALLOW_DELETE') 27 | } 28 | return render_template('hs_bilibili_bv_tree.html', name=session.get('name'), form=form, ctx=ctx) 29 | 30 | 31 | @main.route('/bilibili_bv/form/', methods=['GET', 'POST']) 32 | @main.route('/bilibili_bv/form/', methods=['GET', 'POST']) 33 | @login_required 34 | def hs_bilibili_bv_form(record_id): 35 | this_obj = BilibiliBv 36 | 37 | form = HsBilibiliBvForm() 38 | record = this_obj.query.filter_by(id=record_id).first() 39 | return render_template('hs_bilibili_bv_form.html', name=session.get('name'), form=form, record=record) 40 | 41 | 42 | # Api 接口 43 | @main.route('/bilibili_bv-create-update', methods=['GET', 'POST']) 44 | @login_required 45 | @try_except_log(add_log=True) 46 | def bilibili_bv_create_update(): 47 | this_obj = BilibiliBv 48 | 49 | # 哪一个模型, 要删除的对象名 50 | data = request.form 51 | _logger.info(data) 52 | record_id = data['record_id'] 53 | 54 | if record_id: 55 | record = this_obj.query.filter_by(id=record_id).first() 56 | else: 57 | record = this_obj() 58 | 59 | record.bv_name = data['bv_name'] 60 | 61 | db.session.add(record) 62 | db.session.commit() 63 | return response({ 64 | 'record_id': record.id, 65 | 'name': record.bv_name 66 | }) 67 | 68 | 69 | @main.route('/bilibili_bv/search', methods=['POST']) 70 | @login_required 71 | @try_except_log() 72 | def find_hs_bilibili_bv(): 73 | 74 | this_obj = BilibiliBv 75 | query = this_obj.query.order_by(desc(this_obj.id)) 76 | 77 | form_data = request.form 78 | 79 | current_page = int(form_data.get('current_page') or 0) 80 | page_size = int(form_data.get('page_size') or 10) 81 | 82 | # 按照某个字段搜索 83 | def find_bv_name(): 84 | if not form_data.get('content'): 85 | return query.paginate(current_page, page_size, error_out=False) 86 | return query.filter(this_obj.bv_name.like('%'+form_data.get('content')+'%')).paginate(current_page, page_size, error_out=False) 87 | 88 | methods = { 89 | 'bv_name': find_bv_name, # 对应上面的某一个函数 90 | } 91 | 92 | paginate = methods[request.form.get('method')]() 93 | data = [] 94 | for record in paginate.items: 95 | item = { 96 | 'id': record.id, 97 | 'name': record.bv_name, 98 | 'create_date': record.create_date, 99 | 'update_time': record.update_time, 100 | } 101 | data.append(item) 102 | 103 | result = { 104 | 'total': paginate.total, 105 | 'data': data 106 | } 107 | 108 | return response(result) 109 | 110 | 111 | @main.route('/bilibili_bv-delete', methods=['GET', 'POST']) 112 | @login_required 113 | @try_except_log(add_log=True) 114 | def bilibili_bv_delete_new(): 115 | # 哪一个模型, 要删除的对象名 116 | raise UserError("不允许删除用户") 117 | 118 | -------------------------------------------------------------------------------- /app/main/hs_bilibili_bv_forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_wtf import FlaskForm 4 | from wtforms import StringField, SubmitField, SelectField, PasswordField, IntegerField 5 | from wtforms.validators import DataRequired, EqualTo, Length 6 | 7 | 8 | class HsBilibiliBvSearch(FlaskForm): 9 | methods = [('bv_name', 'bv_name')] 10 | method = SelectField(choices=methods, validators=[DataRequired(message=u'名称不能为空')], coerce=str) 11 | content = StringField() 12 | submit = SubmitField('搜索') 13 | 14 | 15 | class HsBilibiliBvForm(FlaskForm): 16 | record_id = IntegerField(validators=[]) 17 | bv_name = StringField(validators=[DataRequired(message=u'名称不能为空')]) 18 | submit = SubmitField('创建') 19 | new_again = SubmitField('继续创建') 20 | -------------------------------------------------------------------------------- /app/main/hs_model_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import jsonify, request, current_app, url_for 4 | from app.main import main 5 | from ..models import User, Post 6 | from flask import render_template, session 7 | from flask_login import login_user, logout_user, login_required 8 | from .hs_model_forms import HsModelSearch, HsModelForm 9 | from ..models import HSModel 10 | from flask import Flask 11 | from .. import db 12 | from sqlalchemy import desc 13 | from .common import try_except_log, response, delete_me 14 | from flask_paginate import Pagination, get_page_parameter 15 | 16 | import logging 17 | _logger = Flask(__name__).logger 18 | 19 | 20 | # View 接口 21 | @main.route('/model/tree', methods=['GET', 'POST']) 22 | @login_required 23 | def hs_model_tree(): # 这个函数里不再处理提交按钮,使用Ajax局部刷新 24 | this_obj = HSModel 25 | form = HsModelSearch() 26 | ctx = { 27 | 'ALLOW_DELETE': current_app.config.get('ALLOW_DELETE') 28 | } 29 | return render_template('hs_model_tree.html', name=session.get('name'), form=form, ctx=ctx) 30 | 31 | 32 | @main.route('/model/form/', methods=['GET', 'POST']) 33 | @main.route('/model/form/', methods=['GET', 'POST']) 34 | @login_required 35 | def hs_model_form(record_id): 36 | this_obj = HSModel 37 | form = HsModelForm() 38 | record = this_obj.query.filter_by(id=record_id).first() 39 | return render_template('hs_model_form.html', name=session.get('name'), form=form, record=record) 40 | 41 | 42 | # Api 接口 43 | @main.route('/model-create-update', methods=['GET', 'POST']) 44 | @login_required 45 | @try_except_log(add_log=True) 46 | def model_create_update(): 47 | 48 | this_obj = HSModel 49 | # print("request.data", request.data) 50 | 51 | # 哪一个模型, 要删除的对象名 52 | data = request.form 53 | _logger.info(data) 54 | record_id = data['record_id'] 55 | name = data['name'] 56 | 57 | if record_id: 58 | record = this_obj.query.filter_by(id=record_id).first() 59 | record.name = name 60 | else: 61 | record = this_obj(name=name) 62 | 63 | db.session.add(record) 64 | db.session.commit() 65 | return response({ 66 | 'record_id': record.id, 67 | 'name': record.name 68 | }) 69 | 70 | 71 | @main.route('/model/search', methods=['POST']) 72 | @login_required 73 | @try_except_log() 74 | def find_hs_model(): 75 | this_obj = HSModel 76 | query = this_obj.query.order_by(desc(this_obj.id)) 77 | 78 | form_data = request.form 79 | 80 | current_page = int(form_data.get('current_page') or 0) 81 | page_size = int(form_data.get('page_size') or 10) 82 | 83 | # 按照某个字段搜索 84 | def find_name(): 85 | if not form_data.get('content'): 86 | return query.paginate(current_page, page_size, error_out=False) 87 | return query.filter(this_obj.name.like('%'+form_data.get('content')+'%')).paginate(current_page, page_size, error_out=False) 88 | 89 | methods = { 90 | 'name': find_name, # 对应上面的某一个函数 91 | } 92 | 93 | paginate = methods[form_data.get('method')]() 94 | data = [] 95 | for record in paginate.items: 96 | item = { 97 | 'id': record.id, 98 | 'name': record.name, 99 | 'create_date': record.create_date, 100 | 'update_time': record.update_time, 101 | } 102 | data.append(item) 103 | 104 | result = { 105 | 'total': paginate.total, 106 | 'data': data 107 | } 108 | 109 | return response(result) 110 | 111 | 112 | @main.route('/model-delete', methods=['GET', 'POST']) 113 | @login_required 114 | @try_except_log(add_log=True) 115 | def model_delete_new(): 116 | # 哪一个模型, 要删除的对象名 117 | delete_me(db, HSModel) 118 | return response({}) 119 | -------------------------------------------------------------------------------- /app/main/hs_model_forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, SubmitField, SelectField, PasswordField, IntegerField 3 | from wtforms.validators import DataRequired, EqualTo, Length 4 | 5 | 6 | class HsModelSearch(FlaskForm): 7 | methods = [('name', '名称')] 8 | method = SelectField(choices=methods, validators=[DataRequired(message=u'名称不能为空')], coerce=str) 9 | content = StringField() 10 | submit = SubmitField('搜索') 11 | 12 | 13 | class HsModelForm(FlaskForm): 14 | record_id = IntegerField(validators=[]) 15 | name = StringField(validators=[DataRequired(message=u'名称不能为空')]) 16 | submit = SubmitField('创建') 17 | new_again = SubmitField('继续创建') 18 | -------------------------------------------------------------------------------- /app/main/hs_role_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import jsonify, request, current_app, url_for 4 | from . import main 5 | from ..models import User, Post 6 | from flask import render_template, session 7 | from flask_login import login_user, logout_user, login_required 8 | from .hs_role_forms import HsRoleSearch, HsRoleForm 9 | from ..models import Role 10 | from flask import Flask 11 | from .. import db 12 | from sqlalchemy import desc 13 | from .common import try_except_log, response, delete_me 14 | import logging 15 | from flask_paginate import Pagination, get_page_parameter 16 | from app.exceptions import UserError 17 | _logger = Flask(__name__).logger 18 | 19 | 20 | # View 接口 21 | @main.route('/role/tree', methods=['GET', 'POST']) 22 | @login_required 23 | def hs_role_tree(): # 这个函数里不再处理提交按钮,使用Ajax局部刷新 24 | form = HsRoleSearch() 25 | ctx = { 26 | 'ALLOW_DELETE': current_app.config.get('ALLOW_DELETE') 27 | } 28 | return render_template('hs_role_tree.html', name=session.get('name'), form=form, ctx=ctx) 29 | 30 | 31 | @main.route('/role/form/', methods=['GET', 'POST']) 32 | @main.route('/role/form/', methods=['GET', 'POST']) 33 | @login_required 34 | def hs_role_form(record_id): 35 | this_obj = Role 36 | 37 | form = HsRoleForm() 38 | record = this_obj.query.filter_by(id=record_id).first() 39 | return render_template('hs_role_form.html', name=session.get('name'), form=form, record=record) 40 | 41 | 42 | # Api 接口 43 | @main.route('/role-create-update', methods=['GET', 'POST']) 44 | @login_required 45 | @try_except_log(add_log=True) 46 | def role_create_update(): 47 | this_obj = Role 48 | 49 | # 哪一个模型, 要删除的对象名 50 | data = request.form 51 | _logger.info(data) 52 | record_id = data['record_id'] 53 | 54 | if record_id: 55 | record = this_obj.query.filter_by(id=record_id).first() 56 | else: 57 | record = this_obj() 58 | 59 | record.name = data['name'] 60 | 61 | db.session.add(record) 62 | db.session.commit() 63 | return response({ 64 | 'record_id': record.id, 65 | 'name': record.name 66 | }) 67 | 68 | 69 | @main.route('/role/search', methods=['POST']) 70 | @login_required 71 | @try_except_log() 72 | def find_hs_role(): 73 | 74 | this_obj = Role 75 | query = this_obj.query.order_by(desc(this_obj.id)) 76 | 77 | form_data = request.form 78 | 79 | current_page = int(form_data.get('current_page') or 0) 80 | page_size = int(form_data.get('page_size') or 10) 81 | 82 | # 按照某个字段搜索 83 | def find_name(): 84 | if not form_data.get('content'): 85 | return query.paginate(current_page, page_size, error_out=False) 86 | return query.filter(this_obj.name.like('%'+form_data.get('content')+'%')).paginate(current_page, page_size, error_out=False) 87 | 88 | methods = { 89 | 'name': find_name, # 对应上面的某一个函数 90 | } 91 | 92 | paginate = methods[request.form.get('method')]() 93 | data = [] 94 | for record in paginate.items: 95 | item = { 96 | 'id': record.id, 97 | 'name': record.name, 98 | 'create_date': record.create_date, 99 | 'update_time': record.update_time, 100 | } 101 | data.append(item) 102 | 103 | result = { 104 | 'total': paginate.total, 105 | 'data': data 106 | } 107 | 108 | return response(result) 109 | 110 | 111 | @main.route('/role-delete', methods=['GET', 'POST']) 112 | @login_required 113 | @try_except_log(add_log=True) 114 | def role_delete_new(): 115 | # 哪一个模型, 要删除的对象名 116 | raise UserError("不允许删除用户") 117 | 118 | -------------------------------------------------------------------------------- /app/main/hs_role_forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, SubmitField, SelectField, PasswordField, IntegerField 3 | from wtforms.validators import DataRequired, EqualTo, Length 4 | 5 | 6 | class HsRoleSearch(FlaskForm): 7 | methods = [('name', 'Name')] 8 | method = SelectField(choices=methods, validators=[DataRequired(message=u'名称不能为空')], coerce=str) 9 | content = StringField() 10 | submit = SubmitField('搜索') 11 | 12 | 13 | class HsRoleForm(FlaskForm): 14 | record_id = IntegerField(validators=[]) 15 | name = StringField(validators=[DataRequired(message=u'名称不能为空')]) 16 | submit = SubmitField('创建/更新') 17 | new_again = SubmitField('继续创建') 18 | -------------------------------------------------------------------------------- /app/main/hs_user_forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, SubmitField, SelectField, PasswordField, IntegerField, DateField 3 | from wtforms.validators import DataRequired, EqualTo, Length 4 | 5 | 6 | class HsUserSearch(FlaskForm): 7 | methods = [('username', 'User Name'), ('email', 'Email'), ('role', 'Role')] 8 | method = SelectField(choices=methods, validators=[DataRequired(message=u'名称不能为空')], coerce=str) 9 | content = StringField() 10 | submit = SubmitField('搜索') 11 | 12 | 13 | class HsUserForm(FlaskForm): 14 | record_id = IntegerField(validators=[]) 15 | 16 | username = StringField("User Name") 17 | email = StringField("Email") 18 | 19 | date = DateField("Date") 20 | submit = SubmitField('创建/更新') 21 | new_again = SubmitField('继续创建') 22 | 23 | 24 | class HsUserResetPasswordForm(FlaskForm): 25 | record_id = IntegerField(validators=[]) 26 | 27 | password = PasswordField('New password', validators=[ 28 | DataRequired(), EqualTo('password2', message='Passwords must match.')]) 29 | password2 = PasswordField('Confirm new password', 30 | validators=[DataRequired()]) 31 | submit = SubmitField('Update Password') 32 | -------------------------------------------------------------------------------- /app/main/test_layui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main 4 | from flask import render_template, session 5 | from flask_login import login_user, logout_user, login_required 6 | from .hs_model_forms import HsModelSearch, HsModelForm 7 | from flask import Flask 8 | 9 | _logger = Flask(__name__).logger 10 | 11 | 12 | # View 接口 13 | @main.route('/test_lay_ui', methods=['GET', 'POST']) 14 | @login_required 15 | def test_lay_ui(): # 这个函数里不再处理提交按钮,使用Ajax局部刷新 16 | return render_template('test_layui.html', name=session.get('name')) 17 | -------------------------------------------------------------------------------- /app/model_api_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | from datetime import datetime 5 | from . import db 6 | from .models import User 7 | 8 | 9 | class HSApiLog(db.Model): 10 | __tablename__ = 'hs_api_log' 11 | _description = "接口日志" 12 | 13 | id = db.Column(db.Integer, primary_key=True) 14 | url = db.Column(db.String(255), index=True) 15 | remote_addr = db.Column(db.String(255), index=True) 16 | 17 | is_success = db.Column(db.Boolean, default=True) 18 | 19 | form_body = db.Column(db.Text) 20 | data_body = db.Column(db.Text) 21 | file_body = db.Column(db.Text) 22 | 23 | response_body = db.Column(db.Text) 24 | 25 | create_date = db.Column(db.DateTime, default=datetime.now, index=True) 26 | update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) 27 | create_uid = db.Column(db.Integer, db.ForeignKey('users.id')) 28 | 29 | @property 30 | def create_user_name(self): 31 | """ 32 | 获取外键model_id对应的name 33 | :return: 34 | """ 35 | if self.create_uid: 36 | record = User.query.filter_by(id=self.create_uid).first() 37 | return record.username 38 | else: 39 | return '' 40 | -------------------------------------------------------------------------------- /app/model_bilibili.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | from datetime import datetime 5 | from . import db 6 | from .models import User 7 | 8 | 9 | class BilibiliBv(db.Model): 10 | __tablename__ = 'hs_bilibili_bv' 11 | 12 | id = db.Column(db.Integer, primary_key=True) 13 | bv_name = db.Column(db.String(255), index=True) 14 | 15 | create_date = db.Column(db.DateTime, default=datetime.now, index=True) 16 | update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) 17 | create_uid = db.Column(db.Integer, db.ForeignKey('users.id')) 18 | 19 | @property 20 | def create_user_name(self): 21 | """ 22 | 获取外键model_id对应的name 23 | :return: 24 | """ 25 | if self.create_uid: 26 | record = User.query.filter_by(id=self.create_uid).first() 27 | return record.username 28 | else: 29 | return '' 30 | 31 | 32 | class BiliBvReply(db.Model): 33 | __tablename__ = 'hs_bilibili_bv_reply' 34 | 35 | id = db.Column(db.Integer, primary_key=True) 36 | bv_id = db.Column(db.Integer, db.ForeignKey('hs_bilibili_bv.id')) 37 | 38 | uname = db.Column(db.String(255), index=True) 39 | message = db.Column(db.Text) 40 | 41 | create_date = db.Column(db.DateTime, default=datetime.now, index=True) 42 | update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) 43 | create_uid = db.Column(db.Integer, db.ForeignKey('users.id')) 44 | 45 | @property 46 | def create_user_name(self): 47 | """ 48 | 获取外键model_id对应的name 49 | :return: 50 | """ 51 | if self.create_uid: 52 | record = User.query.filter_by(id=self.create_uid).first() 53 | return record.username 54 | else: 55 | return '' 56 | 57 | 58 | class BiliLuckyDog(db.Model): 59 | __tablename__ = 'hs_bilibili_lucky_dog' 60 | 61 | id = db.Column(db.Integer, primary_key=True) 62 | bv_id = db.Column(db.Integer, db.ForeignKey('hs_bilibili_bv.id')) 63 | reply_id = db.Column(db.Integer, db.ForeignKey('hs_bilibili_bv_reply.id')) 64 | 65 | create_date = db.Column(db.DateTime, default=datetime.now, index=True) 66 | update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) 67 | create_uid = db.Column(db.Integer, db.ForeignKey('users.id')) 68 | 69 | @property 70 | def create_user_name(self): 71 | """ 72 | 获取外键model_id对应的name 73 | :return: 74 | """ 75 | if self.create_uid: 76 | record = User.query.filter_by(id=self.create_uid).first() 77 | return record.username 78 | else: 79 | return '' 80 | -------------------------------------------------------------------------------- /app/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .profile-thumbnail { 2 | position: absolute; 3 | } 4 | .profile-header { 5 | min-height: 260px; 6 | margin-left: 280px; 7 | } 8 | div.post-tabs { 9 | margin-top: 16px; 10 | } 11 | ul.posts { 12 | list-style-type: none; 13 | padding: 0px; 14 | margin: 16px 0px 0px 0px; 15 | border-top: 1px solid #e0e0e0; 16 | } 17 | div.post-tabs ul.posts { 18 | margin: 0px; 19 | border-top: none; 20 | } 21 | ul.posts li.post { 22 | padding: 8px; 23 | border-bottom: 1px solid #e0e0e0; 24 | } 25 | ul.posts li.post:hover { 26 | background-color: #f0f0f0; 27 | } 28 | div.post-date { 29 | float: right; 30 | } 31 | div.post-author { 32 | font-weight: bold; 33 | } 34 | div.post-thumbnail { 35 | position: absolute; 36 | } 37 | div.post-content { 38 | margin-left: 48px; 39 | min-height: 48px; 40 | } 41 | div.post-footer { 42 | text-align: right; 43 | } 44 | ul.comments { 45 | list-style-type: none; 46 | padding: 0px; 47 | margin: 16px 0px 0px 0px; 48 | } 49 | ul.comments li.comment { 50 | margin-left: 32px; 51 | padding: 8px; 52 | border-bottom: 1px solid #e0e0e0; 53 | } 54 | ul.comments li.comment:nth-child(1) { 55 | border-top: 1px solid #e0e0e0; 56 | } 57 | ul.comments li.comment:hover { 58 | background-color: #f0f0f0; 59 | } 60 | div.comment-date { 61 | float: right; 62 | } 63 | div.comment-author { 64 | font-weight: bold; 65 | } 66 | div.comment-thumbnail { 67 | position: absolute; 68 | } 69 | div.comment-content { 70 | margin-left: 48px; 71 | min-height: 48px; 72 | } 73 | div.comment-form { 74 | margin: 16px 0px 16px 32px; 75 | } 76 | div.pagination { 77 | width: 100%; 78 | text-align: right; 79 | padding: 0px; 80 | margin: 0px; 81 | } 82 | div.flask-pagedown-preview { 83 | margin: 10px 0px 10px 0px; 84 | border: 1px solid #e0e0e0; 85 | padding: 4px; 86 | } 87 | div.flask-pagedown-preview h1 { 88 | font-size: 140%; 89 | } 90 | div.flask-pagedown-preview h2 { 91 | font-size: 130%; 92 | } 93 | div.flask-pagedown-preview h3 { 94 | font-size: 120%; 95 | } 96 | .post-body h1 { 97 | font-size: 140%; 98 | } 99 | .post-body h2 { 100 | font-size: 130%; 101 | } 102 | .post-body h3 { 103 | font-size: 120%; 104 | } 105 | .table.followers tr { 106 | border-bottom: 1px solid #e0e0e0; 107 | } 108 | -------------------------------------------------------------------------------- /app/static/image/1629711723519.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/image/1629711723519.jpg -------------------------------------------------------------------------------- /app/static/image/1629711797490.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/image/1629711797490.jpg -------------------------------------------------------------------------------- /app/static/image/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/image/favicon.ico -------------------------------------------------------------------------------- /app/static/image/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/image/favicon.png -------------------------------------------------------------------------------- /app/static/js/manager.js: -------------------------------------------------------------------------------- 1 | function PCTable() { 2 | 3 | this.current_page = 0; 4 | this.page_size = 30; 5 | this.datas = null; 6 | 7 | this.getTableInfo = function (datas) { 8 | this.datas = datas; 9 | } 10 | 11 | this.plotTable = function () { 12 | 13 | // clear origin 14 | document.getElementById("pctable-body").remove(); 15 | var table_body = document.createElement("tbody"); 16 | table_body.id = "pctable-body"; 17 | document.getElementById("pctable").appendChild(table_body); 18 | 19 | for (var ri = 0; ri < this.datas.length; ri++) { 20 | var row = document.createElement("tr"); 21 | var pc_info = this.datas[ri]; 22 | 23 | var id_cell = document.createElement("td"); 24 | var text_id = document.createElement("a"); 25 | text_id.appendChild(document.createTextNode(pc_info.id)); 26 | text_id.href = "http://" + window.location.host + "/viewer/" + pc_info.id; 27 | id_cell.appendChild(text_id ); 28 | 29 | var seq_cell = document.createElement("td"); 30 | seq_cell.appendChild(document.createTextNode(pc_info.sequence)); 31 | 32 | var pcap_name_cell = document.createElement("td"); 33 | pcap_name_cell.appendChild(document.createTextNode(pc_info.pcap_name)); 34 | 35 | var annotaion_cell = document.createElement("td"); 36 | annotaion_cell.appendChild(document.createTextNode(pc_info.boxes_annotations.length)); 37 | 38 | row.appendChild(id_cell); 39 | row.appendChild(pcap_name_cell); 40 | row.appendChild(seq_cell); 41 | row.appendChild(annotaion_cell); 42 | document.getElementById("pctable-body").appendChild(row); 43 | } 44 | } 45 | } 46 | 47 | function initPCAPSelect(datas, pcap_id) { 48 | 49 | var item_o = document.createElement("option"); 50 | item_o.appendChild(document.createTextNode("无")); 51 | document.getElementById("pcap-list-menu").appendChild(item_o); 52 | 53 | var select_pcap = null; 54 | for (let i = 0; i < datas.length; i++) { 55 | var pcap_info = datas[i]; 56 | 57 | var item_o = document.createElement("option"); 58 | item_o.appendChild(document.createTextNode(pcap_info.pcap_name)); 59 | 60 | document.getElementById("pcap-list-menu").appendChild(item_o); 61 | 62 | if (pcap_info.id === pcap_id) { 63 | select_pcap = pcap_info; 64 | } 65 | } 66 | 67 | if (select_pcap) { 68 | var row = document.createElement("tr"); 69 | 70 | var pcap_name_cell = document.createElement("td"); 71 | pcap_name_cell.appendChild(document.createTextNode(select_pcap.pcap_name)); 72 | 73 | var lidar_type_cell = document.createElement("td"); 74 | lidar_type_cell.appendChild(document.createTextNode(select_pcap.lidar_type)); 75 | 76 | row.appendChild(pcap_name_cell); 77 | row.appendChild(lidar_type_cell); 78 | 79 | document.getElementById("pcaptable-body").appendChild(row); 80 | 81 | $('#pcap-list-menu').val(select_pcap.pcap_name); 82 | } 83 | } 84 | 85 | $('#pcap-list-menu').on('change',function(e){ 86 | 87 | var select_pcap_name = $(this).val(); 88 | var pcap_info = null; 89 | for (let i = 0; i < pcaps.length; i++) { 90 | if (pcaps[i].pcap_name === select_pcap_name) { 91 | pcap_info = pcaps[i]; 92 | } 93 | } 94 | 95 | var pcap_id = pcap_info?pcap_info.id:0; 96 | var url = "http://" + window.location.host + "/manager/" + pcap_id; 97 | window.location.href = url; 98 | }); 99 | -------------------------------------------------------------------------------- /app/static/js/project_config_tail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 增删改查的逻辑 3 | * 4 | * @version v1.0.1 5 | * @author Roger Chen 6 | */ 7 | 8 | 9 | // 添加model 10 | $("#model_add").click(function(event){ 11 | let new_name = $("#new_model_name").val(); 12 | 13 | if(!new_name){ 14 | alert("请输入名称!"); 15 | } 16 | 17 | $.ajax({ 18 | url:"/model/add", 19 | type:"post", 20 | dataType:"json", 21 | data: { 22 | name: new_name 23 | }, 24 | success:function(data){ 25 | //成功后执行的动作 26 | console.log(data); 27 | if(data.code==0){ 28 | //true 29 | alert("成功"); 30 | }else{ 31 | alert(data.msg); 32 | } 33 | }, 34 | }) 35 | }) -------------------------------------------------------------------------------- /app/static/js/scheme.js: -------------------------------------------------------------------------------- 1 | function initPCAPList(pcaps) { 2 | 3 | for (var ip in pcaps) { 4 | var pcap = pcaps[ip]; 5 | 6 | var row = document.createElement("tr"); 7 | 8 | var pcap_id_cell = document.createElement("td"); 9 | pcap_id_cell.appendChild(document.createTextNode(pcap.id)); 10 | 11 | var pcap_name_cell = document.createElement("td"); 12 | pcap_name_cell.appendChild(document.createTextNode(pcap.pcap_name)); 13 | 14 | var lidar_type_cell = document.createElement("td"); 15 | lidar_type_cell.appendChild(document.createTextNode(pcap.lidar_type)); 16 | 17 | var use_check_cell = document.createElement("td"); 18 | var use_check_box = document.createElement("input"); 19 | use_check_box.id = "pcap_use_cb_" + pcap.id; 20 | use_check_box.type = "checkbox"; 21 | use_check_cell.appendChild(use_check_box); 22 | 23 | var filter_range_cell = document.createElement("td"); 24 | var filter_range_input_box = document.createElement("input"); 25 | filter_range_input_box.id = "range_input_" + pcap.id; 26 | filter_range_input_box.value = "[]"; 27 | filter_range_cell.appendChild(filter_range_input_box); 28 | 29 | row.appendChild(pcap_id_cell); 30 | row.appendChild(pcap_name_cell); 31 | row.appendChild(lidar_type_cell); 32 | row.appendChild(use_check_cell); 33 | row.appendChild(filter_range_cell); 34 | 35 | document.getElementById("pcaptable-body").appendChild(row); 36 | } 37 | } 38 | 39 | $("#generate-btn").click(function(){ 40 | 41 | var scheme = { 42 | dataset: {}, 43 | api: "http://" + window.location.host + "/apply" 44 | }; 45 | for (var ip in pcaps) { 46 | var pcap = pcaps[ip]; 47 | 48 | var use_cb_id = "pcap_use_cb_" + pcap.id; 49 | var is_use = $("#" + use_cb_id).prop('checked'); 50 | 51 | if (!is_use) { 52 | continue; 53 | } 54 | 55 | var filter_range_input_id = "range_input_" + pcap.id; 56 | var filter_range_groups = JSON.parse($("#" + filter_range_input_id).val()); 57 | 58 | var pcap_scheme = { 59 | pcap_name: pcap.name, 60 | filter_range_groups: filter_range_groups, 61 | max_sequence: pcap.max_sequence 62 | } 63 | 64 | scheme.dataset[pcap.id] = pcap_scheme; 65 | } 66 | 67 | document.getElementById("api-cmd-show-panel").innerHTML = JSON.stringify(scheme); 68 | }); -------------------------------------------------------------------------------- /app/static/js/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function h(a){c.appendChild(a.dom);return a}function k(a){for(var d=0;de+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}}; 4 | Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f, 5 | v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /app/static/js/web_server_base.js: -------------------------------------------------------------------------------- 1 | /* 2 | 一些通用的函数写在这里, 这个已经放在了base.html里面, 无须再次引用 3 | *@author Roger 4 | */ 5 | 6 | 7 | /* 8 | 这个函数先验证一下接口返回是否是成功的; 9 | 如果code不是0; 则表示接口失败; 10 | 通过报错信息可以查看具体错误信息 11 | 12 | *@param {Object} result 参数说明 13 | *@param {Boolean} showTip 是否提示"成功" 14 | *@return {Boolean} 接口成功失败标志 15 | */ 16 | function validate_api_result(result, showTip){ 17 | console.log(result); 18 | if(result.code !== 0){ 19 | let msg = result.msg; 20 | if(!msg){location.href='/';return;}; 21 | layer.confirm(msg, { 22 |     btn: ['确认'], //,'否'], 23 |     time: 200000, //20s后自动关闭 24 |     },function(index){ 25 | console.log("这是点击确定按钮走的回调"); 26 |     layer.close(index); 27 |   },function(){ 28 | console.log("这是点击取消按钮走的回调"); 29 |   }); 30 | return false 31 | }else if(result.code === 0){ 32 | // 提示一下 33 | if(showTip){layui.layer.msg('成功',{time: 1000});}; 34 | }; 35 | return true 36 | } 37 | 38 | 39 | 40 | 41 | /** 42 | * 将form序列化Json对象 43 | * {key1:"value1",key2:"value2"} 44 | * @example 45 | * 48 | * ref https://blog.csdn.net/elonspace/article/details/51831066 49 | */ 50 | $.prototype.serializeObject = function() { 51 | var a, o, h, i, e; 52 | a = this.serializeArray(); 53 | o = {}; 54 | h = o.hasOwnProperty; 55 | for (i = 0; i < a.length; i++) { 56 | e = a[i]; 57 | if (!h.call(o, e.name)) { 58 | o[e.name] = e.value; 59 | } 60 | } 61 | return o; 62 | }; 63 | 64 | 65 | /* 66 | 删除动作 67 | *@param {Object} obj layui table 某个行的对象, 需要删除 68 | *@param {string} record_id 需要删除的id 69 | *@param {string} url 调用的url 70 | */ 71 | function delete_record(obj, record_id, url){ 72 | 73 | layer.confirm("确认要删除吗?", { 74 | btn: ['确认' ,'否'], 75 | time: 200000, //20s后自动关闭 76 | },function(index){ 77 | // 删除动作 78 | $.ajax({ 79 | url: url, 80 | type: "post", 81 | data: {record_id:record_id}, 82 | success: result => { 83 | if(!validate_api_result(result)){return}; // 此处先预处理一下返回值, 需要报错的要报错 84 | obj.del(); 85 | layui.layer.msg('删除成功',{time: 1000}); 86 | }, 87 | fail: err => { 88 | layui.layer.msg('删除失败',{time: 1000}); 89 | } 90 | }); 91 |     layer.close(index); 92 |   },function(){ 93 | console.log("这是点击取消按钮走的回调"); 94 | }); 95 | 96 | }; 97 | 98 | 99 | // layui table 显示 行号 100 | function table_index(d){return (d.LAY_INDEX)} 101 | -------------------------------------------------------------------------------- /app/static/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} -------------------------------------------------------------------------------- /app/static/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /app/static/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /app/static/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /app/static/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /app/static/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /app/static/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/font/iconfont.eot -------------------------------------------------------------------------------- /app/static/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /app/static/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/font/iconfont.woff -------------------------------------------------------------------------------- /app/static/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /app/static/layui/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/0.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/1.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/10.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/11.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/12.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/13.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/14.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/15.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/16.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/17.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/18.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/19.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/2.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/20.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/21.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/22.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/23.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/24.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/25.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/26.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/27.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/28.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/29.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/3.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/30.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/31.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/32.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/33.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/34.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/35.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/36.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/37.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/38.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/39.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/4.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/40.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/41.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/42.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/43.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/44.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/45.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/46.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/47.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/48.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/49.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/5.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/50.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/51.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/52.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/53.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/54.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/55.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/56.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/57.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/58.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/59.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/6.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/60.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/61.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/62.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/63.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/64.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/65.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/66.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/67.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/68.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/69.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/7.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/70.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/71.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/8.gif -------------------------------------------------------------------------------- /app/static/layui/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/static/layui/images/face/9.gif -------------------------------------------------------------------------------- /app/static/layui/lay/modules/carousel.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(clearInterval(e.timer),e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
    ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
"].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a/g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
  1. '+o.replace(/[\r\t\n]+/g,"
  2. ")+"
"),c.find(">.layui-code-h3")[0]||c.prepend('

'+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/flow.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),!i&&f.width()&&(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)}); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/laypage.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
"].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/laytpl.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/rate.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
    ",u=1;u<=i.length;u++){var r='
  • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
  • ":n+=r}n+="
"+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)}); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/transfer.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(["laytpl","form"],function(e){"use strict";var a=layui.$,t=layui.laytpl,n=layui.form,i="transfer",l={config:{},index:layui[i]?layui[i].index+1e4:0,set:function(e){var t=this;return t.config=a.extend({},t.config,e),t},on:function(e,a){return layui.onevent.call(this,i,e,a)}},r=function(){var e=this,a=e.config,t=a.id||e.index;return r.that[t]=e,r.config[t]=a,{config:a,reload:function(a){e.reload.call(e,a)},getData:function(){return e.getData.call(e)}}},c="layui-hide",o="layui-btn-disabled",d="layui-none",s="layui-transfer-box",u="layui-transfer-header",h="layui-transfer-search",f="layui-transfer-active",y="layui-transfer-data",p=function(e){return e=e||{},['
','
','","
","{{# if(d.data.showSearch){ }}",'","{{# } }}",'
    ',"
    "].join("")},v=['
    ',p({index:0,checkAllName:"layTransferLeftCheckAll"}),'
    ','",'","
    ",p({index:1,checkAllName:"layTransferRightCheckAll"}),"
    "].join(""),x=function(e){var t=this;t.index=++l.index,t.config=a.extend({},t.config,l.config,e),t.render()};x.prototype.config={title:["列表一","列表二"],width:200,height:360,data:[],value:[],showSearch:!1,id:"",text:{none:"无数据",searchNone:"无匹配数据"}},x.prototype.reload=function(e){var t=this;layui.each(e,function(e,a){a.constructor===Array&&delete t.config[e]}),t.config=a.extend(!0,{},t.config,e),t.render()},x.prototype.render=function(){var e=this,n=e.config,i=e.elem=a(t(v).render({data:n,index:e.index})),l=n.elem=a(n.elem);l[0]&&(n.data=n.data||[],n.value=n.value||[],e.key=n.id||e.index,l.html(e.elem),e.layBox=e.elem.find("."+s),e.layHeader=e.elem.find("."+u),e.laySearch=e.elem.find("."+h),e.layData=i.find("."+y),e.layBtn=i.find("."+f+" .layui-btn"),e.layBox.css({width:n.width,height:n.height}),e.layData.css({height:function(){return n.height-e.layHeader.outerHeight()-e.laySearch.outerHeight()-2}()}),e.renderData(),e.events())},x.prototype.renderData=function(){var e=this,a=(e.config,[{checkName:"layTransferLeftCheck",views:[]},{checkName:"layTransferRightCheck",views:[]}]);e.parseData(function(e){var t=e.selected?1:0,n=["
  • ",'',"
  • "].join("");a[t].views.push(n),delete e.selected}),e.layData.eq(0).html(a[0].views.join("")),e.layData.eq(1).html(a[1].views.join("")),e.renderCheckBtn()},x.prototype.renderForm=function(e){n.render(e,"LAY-transfer-"+this.index)},x.prototype.renderCheckBtn=function(e){var t=this,n=t.config;e=e||{},t.layBox.each(function(i){var l=a(this),r=l.find("."+y),d=l.find("."+u).find('input[type="checkbox"]'),s=r.find('input[type="checkbox"]'),h=0,f=!1;if(s.each(function(){var e=a(this).data("hide");(this.checked||this.disabled||e)&&h++,this.checked&&!e&&(f=!0)}),d.prop("checked",f&&h===s.length),t.layBtn.eq(i)[f?"removeClass":"addClass"](o),!e.stopNone){var p=r.children("li:not(."+c+")").length;t.noneView(r,p?"":n.text.none)}}),t.renderForm("checkbox")},x.prototype.noneView=function(e,t){var n=a('

    '+(t||"")+"

    ");e.find("."+d)[0]&&e.find("."+d).remove(),t.replace(/\s/g,"")&&e.append(n)},x.prototype.setValue=function(){var e=this,t=e.config,n=[];return e.layBox.eq(1).find("."+y+' input[type="checkbox"]').each(function(){var e=a(this).data("hide");e||n.push(this.value)}),t.value=n,e},x.prototype.parseData=function(e){var t=this,n=t.config,i=[];return layui.each(n.data,function(t,l){l=("function"==typeof n.parseData?n.parseData(l):l)||l,i.push(l=a.extend({},l)),layui.each(n.value,function(e,a){a==l.value&&(l.selected=!0)}),e&&e(l)}),n.data=i,t},x.prototype.getData=function(e){var a=this,t=a.config,n=[];return a.setValue(),layui.each(e||t.value,function(e,a){layui.each(t.data,function(e,t){delete t.selected,a==t.value&&n.push(t)})}),n},x.prototype.events=function(){var e=this,t=e.config;e.elem.on("click",'input[lay-filter="layTransferCheckbox"]+',function(){var t=a(this).prev(),n=t[0].checked,i=t.parents("."+s).eq(0).find("."+y);t[0].disabled||("all"===t.attr("lay-type")&&i.find('input[type="checkbox"]').each(function(){this.disabled||(this.checked=n)}),e.renderCheckBtn({stopNone:!0}))}),e.layBtn.on("click",function(){var n=a(this),i=n.data("index"),l=e.layBox.eq(i),r=[];if(!n.hasClass(o)){e.layBox.eq(i).each(function(t){var n=a(this),i=n.find("."+y);i.children("li").each(function(){var t=a(this),n=t.find('input[type="checkbox"]'),i=n.data("hide");n[0].checked&&!i&&(n[0].checked=!1,l.siblings("."+s).find("."+y).append(t.clone()),t.remove(),r.push(n[0].value)),e.setValue()})}),e.renderCheckBtn();var c=l.siblings("."+s).find("."+h+" input");""===c.val()||c.trigger("keyup"),t.onchange&&t.onchange(e.getData(r),i)}}),e.laySearch.find("input").on("keyup",function(){var n=this.value,i=a(this).parents("."+h).eq(0).siblings("."+y),l=i.children("li");l.each(function(){var e=a(this),t=e.find('input[type="checkbox"]'),i=t[0].title.indexOf(n)!==-1;e[i?"removeClass":"addClass"](c),t.data("hide",!i)}),e.renderCheckBtn();var r=l.length===i.children("li."+c).length;e.noneView(i,r?t.text.searchNone:"")})},r.that={},r.config={},l.reload=function(e,a){var t=r.that[e];return t.reload(a),r.call(t)},l.getData=function(e){var a=r.that[e];return a.getData()},l.render=function(e){var a=new x(e);return r.call(a)},e(i,l)}); -------------------------------------------------------------------------------- /app/static/layui/lay/modules/util.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,n,a="layui-fixbar",o="layui-fixbar-top",r=t(document),l=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],u=t(['
      ',e.bar1?'
    • '+c[0]+"
    • ":"",e.bar2?'
    • '+c[1]+"
    • ":"",'
    • '+c[2]+"
    • ","
    "].join("")),g=u.find("."+o),s=function(){var t=r.scrollTop();t>=e.showHeight?i||(g.show(),i=1):i&&(g.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&u.css(e.css),l.append(u),s(),u.find("li").on("click",function(){var i=t(this),n=i.attr("lay-type");"top"===n&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,n)}),r.on("scroll",function(){clearTimeout(n),n=setTimeout(function(){s()},100)}))},countdown:function(e,t,i){var n=this,a="function"==typeof t,o=new Date(e).getTime(),r=new Date(!t||a?(new Date).getTime():t).getTime(),l=o-r,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var u=setTimeout(function(){n.countdown(e,r+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,u),l<=0&&clearTimeout(u),u},timeAgo:function(e,t){var i=this,n=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>26784e5?(a=new Date(e),n[0][0]=i.digit(a.getFullYear(),4),n[0][1]=i.digit(a.getMonth()+1),n[0][2]=i.digit(a.getDate()),t||(n[1][0]=i.digit(a.getHours()),n[1][1]=i.digit(a.getMinutes()),n[1][2]=i.digit(a.getSeconds())),n[0].join("-")+" "+n[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=18e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var n=e.length;n/g,">").replace(/'/g,"'").replace(/"/g,""")},event:function(e,n,a){var o=t("body");return a=a||"click",n=i.event[e]=t.extend(!0,i.event[e],n)||{},i.event.UTIL_EVENT_CALLBACK=i.event.UTIL_EVENT_CALLBACK||{},o.off(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),i.event.UTIL_EVENT_CALLBACK[e]=function(){var i=t(this),a=i.attr(e);"function"==typeof n[a]&&n[a].call(this,i)},o.on(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),n}};!function(e,t,i){"$:nomunge";function n(){a=t[l](function(){o.each(function(){var t=e(this),i=t.width(),n=t.height(),a=e.data(this,u);(i!==a.w||n!==a.h)&&t.trigger(c,[a.w=i,a.h=n])}),n()},r[g])}var a,o=e([]),r=e.resize=e.extend(e.resize,{}),l="setTimeout",c="resize",u=c+"-special-event",g="delay",s="throttleWindow";r[g]=250,r[s]=!0,e.event.special[c]={setup:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.add(t),e.data(this,u,{w:t.width(),h:t.height()}),1===o.length&&n()},teardown:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.not(t),t.removeData(u),o.length||clearTimeout(a)},add:function(t){function n(t,n,o){var r=e(this),l=e.data(this,u)||{};l.w=n!==i?n:r.width(),l.h=o!==i?o:r.height(),a.apply(this,arguments)}if(!r[s]&&this[l])return!1;var a;return e.isFunction(t)?(a=t,n):(a=t.handler,void(t.handler=n))}}}(t,window),e("util",i)}); -------------------------------------------------------------------------------- /app/templates/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 400 | Bad request. 7 | 8 | 9 | 75 | 76 | 77 | 78 |
    79 | 80 | 81 |

    400

    82 |

    Bad request.

    83 | 84 |

    We’re sorry but something appears to be wrong with the request you made, please try again.

    85 | 86 | HOME PAGE 87 |
    88 | 89 | 90 | -------------------------------------------------------------------------------- /app/templates/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 403 | Access Denied. 7 | 8 | 9 | 75 | 76 | 77 | 78 |
    79 | 80 | 81 |

    403

    82 |

    Access Denied.

    83 | 84 |

    Access to the page or resource you were trying to reach is forbidden, try logging in.

    85 | 86 | HOME PAGE 87 |
    88 | 89 | 90 | -------------------------------------------------------------------------------- /app/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 | Page not found. 7 | 8 | 9 | 75 | 76 | 77 | 78 |
    79 | 80 | 81 |

    404

    82 |

    Page not found.

    83 | 84 |

    We’re sorry but it appears that we can’t find the page you were looking for. Usually this occurs because of a page that previously existed was removed or you’ve mistyped the address.

    85 | 86 | HOME PAGE 87 |
    88 | 89 | 90 | -------------------------------------------------------------------------------- /app/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 500 | Internal server error. 7 | 8 | 9 | 75 | 76 | 77 | 78 |
    79 | 80 | 81 |

    500

    82 |

    Internal Server Error.

    83 | 84 |

    Something went wrong. Our technical team have been notified of the issue and we are looking into it. Please try again shortly.

    85 | 86 | HOME PAGE 87 |
    88 | 89 | 90 | -------------------------------------------------------------------------------- /app/templates/_macros.html: -------------------------------------------------------------------------------- 1 | {% macro pagination_widget(pagination, endpoint, fragment='') %} 2 |
      3 | 4 | 5 | « 6 | 7 | 8 | {% for p in pagination.iter_pages() %} 9 | {% if p %} 10 | {% if p == pagination.page %} 11 |
    • 12 | {{ p }} 13 |
    • 14 | {% else %} 15 |
    • 16 | {{ p }} 17 |
    • 18 | {% endif %} 19 | {% else %} 20 |
    • 21 | {% endif %} 22 | {% endfor %} 23 | 24 | 25 | » 26 | 27 | 28 |
    29 | {% endmacro %} 30 | -------------------------------------------------------------------------------- /app/templates/auth/change_email.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | {% block title %}Change Email Address{% endblock %} 4 | 5 | 6 | {% block body %} 7 |
    8 | Change Email Address 9 |
    10 |
    11 | 12 |
    13 | {{ form.csrf_token }} 14 |
    15 | 16 |
    17 | {{ form.email(class="layui-input", style="width:40%") }} 18 |
    19 |
    20 |
    21 |
    22 | 25 |
    26 |
    27 |
    28 | 29 |
    30 |
    31 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/auth/change_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}修改密码{% endblock %} 3 | 4 | {% block body %} 5 |
    6 | 修改密码 7 |
    8 |
    9 |
    10 | {{ form.csrf_token }} 11 |
    12 | 13 |
    14 | {{ form.old_password(class="layui-input", style="width:40%", placeholder="请输入原密码") }} 15 |
    16 |
    17 |
    18 | 19 |
    20 | {{ form.password(class="layui-input", style="width:40%", placeholder="请输入新密码") }} 21 |
    22 |
    23 |
    24 | 25 |
    26 | {{ form.password2(class="layui-input", style="width:40%", placeholder="再次输入新密码") }} 27 |
    28 |
    29 |
    30 |
    31 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/auth/email/change_email.html: -------------------------------------------------------------------------------- 1 |

    Dear {{ user.username }},

    2 |

    To confirm your new email address click here.

    3 |

    Alternatively, you can paste the following link in your browser's address bar:

    4 |

    {{ url_for('auth.change_email', token=token, _external=True) }}

    5 |

    Sincerely,

    6 |

    The Flasky Team

    7 |

    Note: replies to this email address are not monitored.

    8 | -------------------------------------------------------------------------------- /app/templates/auth/email/change_email.txt: -------------------------------------------------------------------------------- 1 | Dear {{ user.username }}, 2 | 3 | To confirm your new email address click on the following link: 4 | 5 | {{ url_for('auth.change_email', token=token, _external=True) }} 6 | 7 | Sincerely, 8 | 9 | The Flasky Team 10 | 11 | Note: replies to this email address are not monitored. 12 | -------------------------------------------------------------------------------- /app/templates/auth/email/confirm.html: -------------------------------------------------------------------------------- 1 |

    Dear {{ user.username }},

    2 |

    Welcome to Flasky!

    3 |

    To confirm your account please click here.

    4 |

    Alternatively, you can paste the following link in your browser's address bar:

    5 |

    {{ url_for('auth.confirm', token=token, _external=True) }}

    6 |

    Sincerely,

    7 |

    The Flasky Team

    8 |

    Note: replies to this email address are not monitored.

    9 | -------------------------------------------------------------------------------- /app/templates/auth/email/confirm.txt: -------------------------------------------------------------------------------- 1 | Dear {{ user.username }}, 2 | 3 | Welcome to Flasky! 4 | 5 | To confirm your account please click on the following link: 6 | 7 | {{ url_for('auth.confirm', token=token, _external=True) }} 8 | 9 | Sincerely, 10 | 11 | The Flasky Team 12 | 13 | Note: replies to this email address are not monitored. 14 | -------------------------------------------------------------------------------- /app/templates/auth/email/reset_password.html: -------------------------------------------------------------------------------- 1 |

    Dear {{ user.username }},

    2 |

    To reset your password click here.

    3 |

    Alternatively, you can paste the following link in your browser's address bar:

    4 |

    {{ url_for('auth.password_reset', token=token, _external=True) }}

    5 |

    If you have not requested a password reset simply ignore this message.

    6 |

    Sincerely,

    7 |

    The Flasky Team

    8 |

    Note: replies to this email address are not monitored.

    9 | -------------------------------------------------------------------------------- /app/templates/auth/email/reset_password.txt: -------------------------------------------------------------------------------- 1 | Dear {{ user.username }}, 2 | 3 | To reset your password click on the following link: 4 | 5 | {{ url_for('auth.password_reset', token=token, _external=True) }} 6 | 7 | If you have not requested a password reset simply ignore this message. 8 | 9 | Sincerely, 10 | 11 | The Flasky Team 12 | 13 | Note: replies to this email address are not monitored. 14 | -------------------------------------------------------------------------------- /app/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base-user.html" %} 2 | {% block title %}Login Cyber Security Server. {% endblock %} 3 | 4 | {% block body %} 5 | 23 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/auth/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}Flasky - Register{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 |
    11 | {{ wtf.quick_form(form) }} 12 |
    13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /app/templates/auth/reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}Flasky - Password Reset{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 |
    11 | {{ wtf.quick_form(form) }} 12 |
    13 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/auth/unconfirmed.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Flasky - Confirm your account{% endblock %} 4 | 5 | {% block page_content %} 6 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /app/templates/base-user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 29 |
    30 |
    31 | 32 | 34 | 35 | 36 | 54 |
    55 |
    56 | {% for message in get_flashed_messages() %} 57 |
    58 | 59 | {{ message }} 60 |
    61 | {% endfor %} 62 | 63 | 64 | {% block body %} 65 | {% endblock %} 66 | 67 | 68 | 76 | {% block script %}{% endblock %} 77 | 78 | -------------------------------------------------------------------------------- /app/templates/hs_api_log_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Api Log{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    Api Log

    7 | 9 | 查看列表 10 | 11 |
    12 | {% endblock %} 13 | 14 | 15 | {% block body %} 16 |
    17 | 18 |
    19 | 20 |
    21 | {{ form.record_id(class="layui-input layui-disabled", style="width:40%", readonly=1, disable=1, value=record.id) 22 | }} 23 |
    24 |
    25 | 26 |
    27 | 28 |
    29 | {{ form.url(class="layui-input layui-disabled", style="width:40%;", value=record.url) }} 30 |
    31 |
    remote_addr 32 | 33 |
    34 | 35 |
    36 | {{ form.remote_addr(class="layui-input layui-disabled", style="width:40%;", value=record.remote_addr) }} 37 |
    38 |
    39 | 40 |
    41 | 42 |
    43 | 44 |
    45 |
    46 | 47 |
    48 | 49 |
    50 | 51 |
    52 |
    53 | 54 |
    55 | 56 |
    57 | 58 |
    59 |
    60 | 61 |
    62 | 63 |
    64 | 65 |
    66 |
    67 | 68 |
    69 | 70 |
    71 | 72 |
    73 |
    74 | 75 |
    76 | 77 |
    78 | {{ form.create_date(class="layui-input layui-disabled", style="width:40%;", value=record.create_date) }} 79 |
    80 |
    81 | 82 |
    83 | 84 |
    85 | {{ form.create_user_name(class="layui-input layui-disabled", style="width:40%;", value=record.create_user_name) }} 86 |
    87 |
    88 | 89 | 90 |
    91 | {% endblock %} 92 | 93 | {% block script %} 94 | 95 | 145 | 146 | 147 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/hs_bilibili_bv_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Bilibili Bv{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    Bilibili Bv

    7 | 9 | 查看列表 10 | 11 |
    12 | {% endblock %} 13 | 14 | 15 | {% block body %} 16 |
    17 | 18 |
    19 | 20 |
    21 | {{ form.record_id(class="layui-input layui-disabled", style="width:40%", readonly=1, disable=1, value=record.id) 22 | }} 23 |
    24 |
    25 | 26 |
    27 | 28 |
    29 | {{ form.bv_name(class="layui-input", style="width:40%;", value=record.bv_name) }} 30 |
    31 |
    32 | 33 |
    34 |
    35 | {{ form.submit(class="layui-btn", style="width:100px;font-size:medium", id="upsert") }} 36 | {{ form.new_again(class="layui-btn", style="width:100px;font-size:medium", id="new_again") }} 37 |
    38 |
    39 | 40 | 41 |
    42 | {% endblock %} 43 | 44 | {% block script %} 45 | 46 | 96 | 97 | 98 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/hs_model_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Model{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    Model

    7 | 8 | 查看列表 9 | 10 |
    11 | {% endblock %} 12 | 13 | 14 | {% block body %} 15 |
    16 | 17 |
    18 | 19 |
    20 | {{ form.record_id(class="layui-input layui-disabled", style="width:40%", readonly=1, disable=1, value=record.id) }} 21 |
    22 |
    23 | 24 |
    25 | 26 |
    27 | {{ form.name(class="layui-input", style="width:40%;", value=record.name, placeholder="请输入名称") }} 28 |
    29 |
    30 | 31 |
    32 |
    33 | {{ form.submit(class="layui-btn", style="width:100px;font-size:medium", id="upsert") }} 34 | 35 |
    36 |
    37 | 38 | 39 |
    40 | {% endblock %} 41 | 42 | {% block script %} 43 | 44 | 98 | 99 | 100 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/hs_role_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}Role{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    Role

    7 | 9 | 查看列表 10 | 11 |
    12 | {% endblock %} 13 | 14 | 15 | {% block body %} 16 |
    17 | 18 |
    19 | 20 |
    21 | {{ form.record_id(class="layui-input layui-disabled", style="width:40%", readonly=1, disable=1, value=record.id) 22 | }} 23 |
    24 |
    25 | 26 |
    27 | 28 |
    29 | {{ form.name(class="layui-input", style="width:40%;", value=record.name) }} 30 |
    31 |
    32 | 33 |
    34 |
    35 | {{ form.submit(class="layui-btn", style="width:100px;font-size:medium", id="upsert") }} 36 | {{ form.new_again(class="layui-btn", style="width:100px;font-size:medium", id="new_again") }} 37 |
    38 |
    39 | 40 | 41 |
    42 | {% endblock %} 43 | 44 | {% block script %} 45 | 46 | 96 | 97 | 98 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/hs_user_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}User{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    User

    7 | 9 | 查看列表 10 | 11 |
    12 | {% endblock %} 13 | 14 | 15 | {% block body %} 16 |
    17 | 18 |
    19 | 20 |
    21 | {{ form.record_id(class="layui-input layui-disabled", style="width:40%", readonly=1, disable=1, value=record.id) 22 | }} 23 |
    24 |
    25 | 26 |
    27 | 28 |
    29 | {{ form.username(class="layui-input", style="width:40%;", value=record.username) }} 30 |
    31 |
    32 | 33 |
    34 | 35 |
    36 | {{ form.email(class="layui-input", style="width:40%;", value=record.email) }} 37 |
    38 |
    39 | 40 |
    41 | 42 |
    43 | 50 |
    51 |
    52 | 53 |
    54 |
    55 | {{ form.submit(class="layui-btn", style="width:100px;font-size:medium", id="upsert") }} 56 | {{ form.new_again(class="layui-btn", style="width:100px;font-size:medium", id="new_again") }} 57 |
    58 |
    59 | 60 | 61 |
    62 | {% endblock %} 63 | 64 | {% block script %} 65 | 66 | 116 | 117 | 118 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/hs_user_reset_password_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}User{% endblock %} 3 | 4 | {% block card %} 5 |
    6 |

    User

    7 |
    8 | {% endblock %} 9 | 10 | 11 | {% block body %} 12 |
    13 | Reset Password 14 |
    15 |
    16 |
    17 | {{ form.csrf_token }} 18 |
    19 | 20 |
    21 | {{ form.password(class="layui-input", style="width:40%", placeholder="Input New Password") }} 22 |
    23 |
    24 |
    25 | 26 |
    27 | {{ form.password2(class="layui-input", style="width:40%", placeholder="Input New Password Again") }} 28 |
    29 |
    30 |
    31 |
    32 | 35 |
    36 |
    37 |
    38 |
    39 |
    40 | {% endblock %} 41 | 42 | {% block script %} 43 | 44 | 99 | 100 | 101 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} My Server {% endblock %} 5 | {% block navbar_collapse %} 6 | 7 | 8 | {% endblock %} 9 | {% block page_content %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block navbar_collapse %} 5 | 8 | {% endblock %} 9 | 10 | {% block page_content %} 11 |
    12 | {{ wtf.quick_form(form) }} 13 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/templates/reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}Flasky{% endblock %} 5 | 6 | {% block page_content %} 7 | 15 | {{ wtf.quick_form(form) }} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /app/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 选项卡 6 | 18 | 19 | 40 | 41 | 42 | 43 | 44 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/templates/test_layui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title %}{% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 33 | {% block head %}{% endblock %} 34 | 35 | 36 | 37 | 38 |
    39 |
    40 | 41 |
    42 | 43 |
    44 |
    45 |
    46 | 47 |
    48 | 49 |
    50 |
    辅助文字
    51 |
    52 |
    53 | 54 |
    55 | 63 |
    64 |
    65 |
    66 | 67 |
    68 | 69 | 70 | 71 |
    72 |
    73 |
    74 | 75 |
    76 | 77 |
    78 |
    79 |
    80 | 81 |
    82 | 83 | 84 |
    85 |
    86 |
    87 | 88 |
    89 | 90 |
    91 |
    92 |
    93 |
    94 | 95 | 96 |
    97 |
    98 |
    99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/app/tests/__init__.py -------------------------------------------------------------------------------- /app/tests/test_basics.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from flask import current_app 3 | from app import create_app, db 4 | 5 | 6 | class BasicsTestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.app = create_app('testing') 9 | self.app_context = self.app.app_context() 10 | self.app_context.push() 11 | db.create_all() 12 | 13 | def tearDown(self): 14 | db.session.remove() 15 | db.drop_all() 16 | self.app_context.pop() 17 | 18 | def test_app_exists(self): 19 | self.assertFalse(current_app is None) 20 | 21 | def test_app_is_testing(self): 22 | self.assertTrue(current_app.config['TESTING']) 23 | -------------------------------------------------------------------------------- /app/tests/test_client.py: -------------------------------------------------------------------------------- 1 | import re 2 | import unittest 3 | from app import create_app, db 4 | from app.models import User, Role 5 | 6 | class FlaskClientTestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.app = create_app('testing') 9 | self.app_context = self.app.app_context() 10 | self.app_context.push() 11 | db.create_all() 12 | Role.insert_roles() 13 | self.client = self.app.test_client(use_cookies=True) 14 | 15 | def tearDown(self): 16 | db.session.remove() 17 | db.drop_all() 18 | self.app_context.pop() 19 | 20 | def test_home_page(self): 21 | response = self.client.get('/') 22 | self.assertEqual(response.status_code, 200) 23 | self.assertTrue('Stranger' in response.get_data(as_text=True)) 24 | 25 | def test_register_and_login(self): 26 | # register a new account 27 | response = self.client.post('/auth/register', data={ 28 | 'email': 'john@example.com', 29 | 'username': 'john', 30 | 'password': 'cat', 31 | 'password2': 'cat' 32 | }) 33 | self.assertEqual(response.status_code, 302) 34 | 35 | # login with the new account 36 | response = self.client.post('/auth/login', data={ 37 | 'email': 'john@example.com', 38 | 'password': 'cat' 39 | }, follow_redirects=True) 40 | self.assertEqual(response.status_code, 200) 41 | self.assertTrue(re.search('Hello,\s+john!', 42 | response.get_data(as_text=True))) 43 | self.assertTrue( 44 | 'You have not confirmed your account yet' in response.get_data( 45 | as_text=True)) 46 | 47 | # send a confirmation token 48 | user = User.query.filter_by(email='john@example.com').first() 49 | token = user.generate_confirmation_token() 50 | response = self.client.get('/auth/confirm/{}'.format(token), 51 | follow_redirects=True) 52 | user.confirm(token) 53 | self.assertEqual(response.status_code, 200) 54 | self.assertTrue( 55 | 'You have confirmed your account' in response.get_data( 56 | as_text=True)) 57 | 58 | # log out 59 | response = self.client.get('/auth/logout', follow_redirects=True) 60 | self.assertEqual(response.status_code, 200) 61 | self.assertTrue('You have been logged out' in response.get_data( 62 | as_text=True)) 63 | -------------------------------------------------------------------------------- /app/tests/test_selenium.py: -------------------------------------------------------------------------------- 1 | import re 2 | import threading 3 | import time 4 | import unittest 5 | from selenium import webdriver 6 | from app import create_app, db, fake 7 | from app.models import Role, User, Post 8 | 9 | 10 | class SeleniumTestCase(unittest.TestCase): 11 | client = None 12 | 13 | @classmethod 14 | def setUpClass(cls): 15 | # start Chrome 16 | options = webdriver.ChromeOptions() 17 | options.add_argument('headless') 18 | try: 19 | cls.client = webdriver.Chrome(chrome_options=options) 20 | except: 21 | pass 22 | 23 | # skip these tests if the browser could not be started 24 | if cls.client: 25 | # create the application 26 | cls.app = create_app('testing') 27 | cls.app_context = cls.app.app_context() 28 | cls.app_context.push() 29 | 30 | # suppress logging to keep unittest output clean 31 | import logging 32 | logger = logging.getLogger('werkzeug') 33 | logger.setLevel("ERROR") 34 | 35 | # create the database and populate with some fake data 36 | db.create_all() 37 | Role.insert_roles() 38 | fake.users(10) 39 | fake.posts(10) 40 | 41 | # add an administrator user 42 | admin_role = Role.query.filter_by(name='Administrator').first() 43 | admin = User(email='john@example.com', 44 | username='john', password='cat', 45 | role=admin_role, confirmed=True) 46 | db.session.add(admin) 47 | db.session.commit() 48 | 49 | # start the Flask server in a thread 50 | cls.server_thread = threading.Thread(target=cls.app.run, 51 | kwargs={'debug': False}) 52 | cls.server_thread.start() 53 | 54 | # give the server a second to ensure it is up 55 | time.sleep(1) 56 | 57 | @classmethod 58 | def tearDownClass(cls): 59 | if cls.client: 60 | # stop the flask server and the browser 61 | cls.client.get('http://localhost:5000/shutdown') 62 | cls.client.quit() 63 | cls.server_thread.join() 64 | 65 | # destroy database 66 | db.drop_all() 67 | db.session.remove() 68 | 69 | # remove application context 70 | cls.app_context.pop() 71 | 72 | def setUp(self): 73 | if not self.client: 74 | self.skipTest('Web browser not available') 75 | 76 | def tearDown(self): 77 | pass 78 | 79 | def test_admin_home_page(self): 80 | # navigate to home page 81 | self.client.get('http://localhost:5000/') 82 | self.assertTrue(re.search('Hello,\s+Stranger!', 83 | self.client.page_source)) 84 | 85 | # navigate to login page 86 | self.client.find_element_by_link_text('Log In').click() 87 | self.assertIn('

    Login

    ', self.client.page_source) 88 | 89 | # login 90 | self.client.find_element_by_name('email').\ 91 | send_keys('john@example.com') 92 | self.client.find_element_by_name('password').send_keys('cat') 93 | self.client.find_element_by_name('submit').click() 94 | self.assertTrue(re.search('Hello,\s+john!', self.client.page_source)) 95 | 96 | # navigate to the user's profile page 97 | self.client.find_element_by_link_text('Profile').click() 98 | self.assertIn('

    john

    ', self.client.page_source) 99 | -------------------------------------------------------------------------------- /base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date 4 | from flask.json import JSONEncoder 5 | 6 | 7 | class CustomJSONEncoder(JSONEncoder): 8 | """ 9 | https://www.codenong.com/43663552/ 10 | 使用Flask的jsonify时,将datetime.date保持为’yyyy-mm-dd’格式 11 | """ 12 | def default(self, obj): 13 | try: 14 | if isinstance(obj, date): 15 | return obj.isoformat() #.replace('T', ' ') 16 | iterable = iter(obj) 17 | except TypeError: 18 | pass 19 | else: 20 | return list(iterable) 21 | return JSONEncoder.default(self, obj) 22 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import timedelta 3 | basedir = os.path.abspath(os.path.dirname(__file__)) 4 | from tools.os_tools import os_mkdir 5 | 6 | 7 | class Config: 8 | 9 | SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string' 10 | MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.googlemail.com') 11 | MAIL_PORT = int(os.environ.get('MAIL_PORT', '587')) 12 | MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1'] 13 | MAIL_USERNAME = os.environ.get('MAIL_USERNAME') 14 | MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') 15 | FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]' 16 | FLASKY_MAIL_SENDER = 'Flasky Admin ' 17 | 18 | FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN') 19 | 20 | SSL_REDIRECT = False 21 | SQLALCHEMY_TRACK_MODIFICATIONS = False 22 | SQLALCHEMY_RECORD_QUERIES = True 23 | FLASKY_POSTS_PER_PAGE = 20 24 | FLASKY_FOLLOWERS_PER_PAGE = 50 25 | FLASKY_COMMENTS_PER_PAGE = 30 26 | FLASKY_SLOW_DB_QUERY_TIME = 0.5 27 | SEND_FILE_MAX_AGE_DEFAULT = timedelta(seconds=10) 28 | PERMANENT_SESSION_LIFETIME = timedelta(minutes=10000) # session 过期时间 29 | 30 | BOOTSTRAP_SERVE_LOCAL = True 31 | 32 | # 自定义配置项 start .............. 33 | # ALLOW_DELETE = 1, 0 34 | ALLOW_DELETE = 0 # 允许系统删除记录 1 可以删除, 0 不可以删除 35 | 36 | TEMP_DIR_PATH = os_mkdir(basedir, 'temp') # 临时目录地址 37 | 38 | # 日期显示格式 39 | DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d" 40 | DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S" 41 | DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( 42 | DEFAULT_SERVER_DATE_FORMAT, 43 | DEFAULT_SERVER_TIME_FORMAT) 44 | 45 | # 自定义配置项 end .............. 46 | 47 | @staticmethod 48 | def init_app(app): 49 | pass 50 | 51 | 52 | class DevelopmentConfig(Config): 53 | DEBUG = False 54 | # SQLALCHEMY_DATABASE_URI = "mysql://root:123456@127.0.0.1:3306/test_mysql_db" 55 | SQLALCHEMY_DATABASE_URI = "mysql://root:hesai1588@127.0.0.1:3306/test_db?charset=utf8mb4" 56 | 57 | 58 | class TestingConfig(Config): 59 | TESTING = True 60 | SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite://' 61 | WTF_CSRF_ENABLED = False 62 | 63 | 64 | class ProductionConfig(Config): 65 | SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite') 66 | 67 | @classmethod 68 | def init_app(cls, app): 69 | Config.init_app(app) 70 | 71 | # email errors to the administrators 72 | import logging 73 | from logging.handlers import SMTPHandler 74 | credentials = None 75 | secure = None 76 | if getattr(cls, 'MAIL_USERNAME', None) is not None: 77 | credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD) 78 | if getattr(cls, 'MAIL_USE_TLS', None): 79 | secure = () 80 | mail_handler = SMTPHandler( 81 | mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT), 82 | fromaddr=cls.FLASKY_MAIL_SENDER, 83 | toaddrs=[cls.FLASKY_ADMIN], 84 | subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' Application Error', 85 | credentials=credentials, 86 | secure=secure) 87 | mail_handler.setLevel(logging.ERROR) 88 | app.logger.addHandler(mail_handler) 89 | 90 | 91 | class HerokuConfig(ProductionConfig): 92 | SSL_REDIRECT = True if os.environ.get('DYNO') else False 93 | 94 | @classmethod 95 | def init_app(cls, app): 96 | ProductionConfig.init_app(app) 97 | 98 | # handle reverse proxy server headers 99 | from werkzeug.contrib.fixers import ProxyFix 100 | app.wsgi_app = ProxyFix(app.wsgi_app) 101 | 102 | # log to stderr 103 | import logging 104 | from logging import StreamHandler 105 | file_handler = StreamHandler() 106 | file_handler.setLevel(logging.INFO) 107 | app.logger.addHandler(file_handler) 108 | 109 | 110 | class DockerConfig(ProductionConfig): 111 | @classmethod 112 | def init_app(cls, app): 113 | ProductionConfig.init_app(app) 114 | 115 | # log to stderr 116 | import logging 117 | from logging import StreamHandler 118 | file_handler = StreamHandler() 119 | file_handler.setLevel(logging.INFO) 120 | app.logger.addHandler(file_handler) 121 | 122 | 123 | class UnixConfig(ProductionConfig): 124 | @classmethod 125 | def init_app(cls, app): 126 | ProductionConfig.init_app(app) 127 | 128 | # log to syslog 129 | import logging 130 | from logging.handlers import SysLogHandler 131 | syslog_handler = SysLogHandler() 132 | syslog_handler.setLevel(logging.INFO) 133 | app.logger.addHandler(syslog_handler) 134 | 135 | 136 | config = { 137 | 'development': DevelopmentConfig, 138 | 'testing': TestingConfig, 139 | 'production': ProductionConfig, 140 | 'heroku': HerokuConfig, 141 | 'docker': DockerConfig, 142 | 'unix': UnixConfig, 143 | 144 | 'default': DevelopmentConfig 145 | } 146 | -------------------------------------------------------------------------------- /gunicorn.conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import multiprocessing 4 | 5 | # FIX import requets error: 6 | # see https://github.com/gevent/gevent/issues/1016 7 | import gevent.monkey 8 | gevent.monkey.patch_all() 9 | from requests.packages.urllib3.util.ssl_ import create_urllib3_context 10 | create_urllib3_context() 11 | 12 | workers = multiprocessing.cpu_count() * 2 + 1 #进程数 13 | worker_class = "gevent" # 采用gevent库,支持异步处理请求,提高吞吐量 14 | bind = "0.0.0.0:5000" 15 | # timeout = 30 #超时 16 | 17 | loglevel = 'info' # 日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置 18 | access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s"' # 设置gunicorn访问日志格式,错误日志无法设置 19 | 20 | home_path = os.path.expanduser('~') 21 | accesslog = os.path.join(home_path, 'log', "gunicorn_access.log") # http 访问日志 22 | errorlog = os.path.join(home_path, 'log', "gunicorn_info.log") # 系统日志, 包含主动打印的INFO 23 | 24 | 25 | 26 | 27 | """ 28 | 其每个选项的含义如下: 29 | h remote address 30 | l '-' 31 | u currently '-', may be user name in future releases 32 | t date of the request 33 | r status line (e.g. ``GET / HTTP/1.1``) 34 | s status 35 | b response length or '-' 36 | f referer 37 | a user agent 38 | T request time in seconds 39 | D request time in microseconds 40 | L request time in decimal seconds 41 | p process ID 42 | """ 43 | -------------------------------------------------------------------------------- /hello.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | import os 4 | from flask import current_app 5 | from flask import Flask, render_template, session, redirect, url_for, flash, request 6 | from flask_bootstrap import Bootstrap 7 | from flask_moment import Moment 8 | from flask_wtf import FlaskForm 9 | from flask_wtf.file import FileField, FileAllowed, FileRequired 10 | # from flask_uploads import UploadSet, IMAGES 11 | from wtforms import StringField, SubmitField, BooleanField 12 | from wtforms.validators import DataRequired 13 | from flask_sqlalchemy import SQLAlchemy 14 | from flask_migrate import Migrate 15 | from werkzeug.utils import secure_filename 16 | basedir = os.path.abspath(os.path.dirname(__file__)) 17 | from app import create_app, db 18 | from app.models import * 19 | from app.model_api_log import HSApiLog 20 | from tests.test_api import * 21 | from flask_script import Manager, Shell 22 | import logging 23 | from logging.handlers import TimedRotatingFileHandler 24 | from tools.os_tools import * 25 | from base import CustomJSONEncoder 26 | 27 | 28 | app = create_app('default') 29 | app.json_encoder = CustomJSONEncoder 30 | migrate = Migrate(app, db) 31 | from unittest import TestCase 32 | 33 | 34 | @app.cli.command() 35 | def init_tables(): 36 | Role.insert_roles() 37 | Amdin_Role = Role.query.filter_by(name='Administrator').first() 38 | user_admin = User(email='admin@163.com', username='admin', password='123', confirmed=True, role=Amdin_Role) 39 | db.session.add_all([user_admin]) 40 | db.session.commit() 41 | 42 | 43 | def log_config(debug=False): 44 | """ 45 | :param debug: 如果不是debug模式, 则日志输出到文件 46 | :return: 47 | """ 48 | # 日志配置 49 | home_path = os.path.expanduser('~') 50 | log_path = os_mkdir(home_path, "log") 51 | log_file_path = '%s/my-flask-server.log' % log_path 52 | _format = '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s' 53 | _format2 = '%(asctime)s - %(levelname)s - %(message)s' 54 | _handler = TimedRotatingFileHandler(log_file_path, 55 | when="D", 56 | interval=1, 57 | backupCount=15, 58 | encoding="UTF-8", 59 | delay=False, 60 | utc=True) 61 | 62 | _handler.setLevel(logging.DEBUG) 63 | logging_format = logging.Formatter(_format) 64 | _handler.setFormatter(logging_format) 65 | 66 | if not debug: 67 | logging.basicConfig(filename=log_file_path, level=logging.DEBUG, format=_format2) 68 | 69 | return _handler 70 | 71 | 72 | if __name__ == "__main__": 73 | 74 | # 启动日志 75 | handler = log_config() 76 | app.logger.addHandler(handler) 77 | app.run(host='0.0.0.0', port=5000, debug=True) 78 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_script import Manager 4 | from flask_migrate import Migrate 5 | from hello import app 6 | 7 | 8 | manager = Manager(app) 9 | 10 | 11 | # 定义自己要执行的command 12 | @manager.command 13 | def test(): 14 | print(u'test run') 15 | 16 | 17 | if __name__ == '__main__': 18 | manager.run() 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==0.9.3 2 | bcrypt==3.1.7 3 | bleach==3.3.0 4 | blinker==1.4 5 | Brotli==1.0.7 6 | certifi==2020.6.20 7 | cffi==1.14.0 8 | chardet==3.0.4 9 | click==7.1.2 10 | coverage==5.1 11 | dnspython==1.16.0 12 | dominate==2.5.1 13 | email-validator==1.1.1 14 | Flask==1.1.2 15 | Flask-Bcrypt==0.7.1 16 | Flask-Bootstrap==3.3.7.1 17 | Flask-Compress==1.5.0 18 | Flask-HTTPAuth==3.2.3 19 | Flask-JWT==0.3.2 20 | Flask-Login==0.4.0 21 | Flask-Mail==0.9.1 22 | Flask-Migrate==2.0.4 23 | Flask-Moment==0.9.0 24 | Flask-MySQLdb==0.2.0 25 | Flask-PageDown==0.2.2 26 | Flask-Script==2.0.6 27 | Flask-SQLAlchemy==2.4.3 28 | Flask-SSLify==0.1.5 29 | Flask-WTF==0.14.3 30 | gunicorn==19.7.1 31 | html5lib==0.999999999 32 | idna==2.10 33 | itsdangerous==1.1.0 34 | Jinja2==2.11.3 35 | Mako==1.0.7 36 | Markdown==2.6.8 37 | MarkupSafe==1.1.1 38 | mysqlclient==1.4.6 39 | pycparser==2.20 40 | PyJWT==1.4.2 41 | python-dateutil==2.6.1 42 | python-dotenv==0.6.5 43 | python-editor==1.0.3 44 | requests==2.24.0 45 | six==1.10.0 46 | SQLAlchemy==1.3.17 47 | visitor==0.1.3 48 | webencodings==0.5.1 49 | Werkzeug==1.0.1 50 | WTForms==2.3.1 51 | 52 | # 2020-07-09 11:37:24 增加 53 | flask-paginate==0.7.0 54 | -------------------------------------------------------------------------------- /restart_server.sh: -------------------------------------------------------------------------------- 1 | Port=5000 # Or 5000 2 | PyPath="$PWD/miniconda3/envs/py36/bin" 3 | 4 | pid=`ps ax | grep gunicorn | grep $Port | awk '{split($0,a," "); print a[1]}' | head -n 1` 5 | if [ -z "$pid" ]; then 6 | echo "No gunicorn deamon on port $Port" 7 | else 8 | kill $pid 9 | echo "Killed gunicorn deamon on port $Port" 10 | fi 11 | 12 | cd ~/sap_oa_broker 13 | git pull 14 | 15 | $PyPath/python $PyPath/gunicorn -c gunicorn.conf.py hello:app --preload -b 0.0.0.0:$Port --daemon 16 | 17 | cd ~ 18 | 19 | pid=`ps ax | grep gunicorn | grep $Port | awk '{split($0,a," "); print a[1]}' | head -n 1` 20 | if [ -z "$pid" ]; then 21 | echo "[!-_-!] ...Restart Fail..." 22 | else 23 | ps -ef|grep gunicorn 24 | echo "......" 25 | echo "......" 26 | echo "[*^_^*] ...Restart Successfully..." 27 | fi 28 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RRRoger/MyWebserver-flask/345d2e608cc349722f717e4a39d23c3640e2451f/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_basics.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from flask import current_app 3 | from app import create_app, db 4 | 5 | 6 | class BasicsTestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.app = create_app('testing') 9 | self.app_context = self.app.app_context() 10 | self.app_context.push() 11 | db.create_all() 12 | 13 | def tearDown(self): 14 | db.session.remove() 15 | db.drop_all() 16 | self.app_context.pop() 17 | 18 | def test_app_exists(self): 19 | self.assertFalse(current_app is None) 20 | 21 | def test_app_is_testing(self): 22 | self.assertTrue(current_app.config['TESTING']) 23 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import re 2 | import unittest 3 | from app import create_app, db 4 | from app.models import User, Role 5 | 6 | class FlaskClientTestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.app = create_app('testing') 9 | self.app_context = self.app.app_context() 10 | self.app_context.push() 11 | db.create_all() 12 | Role.insert_roles() 13 | self.client = self.app.test_client(use_cookies=True) 14 | 15 | def tearDown(self): 16 | db.session.remove() 17 | db.drop_all() 18 | self.app_context.pop() 19 | 20 | def test_home_page(self): 21 | response = self.client.get('/') 22 | self.assertEqual(response.status_code, 200) 23 | self.assertTrue('Stranger' in response.get_data(as_text=True)) 24 | 25 | def test_register_and_login(self): 26 | # register a new account 27 | response = self.client.post('/auth/register', data={ 28 | 'email': 'john@example.com', 29 | 'username': 'john', 30 | 'password': 'cat', 31 | 'password2': 'cat' 32 | }) 33 | self.assertEqual(response.status_code, 302) 34 | 35 | # login with the new account 36 | response = self.client.post('/auth/login', data={ 37 | 'email': 'john@example.com', 38 | 'password': 'cat' 39 | }, follow_redirects=True) 40 | self.assertEqual(response.status_code, 200) 41 | self.assertTrue(re.search('Hello,\s+john!', 42 | response.get_data(as_text=True))) 43 | self.assertTrue( 44 | 'You have not confirmed your account yet' in response.get_data( 45 | as_text=True)) 46 | 47 | # send a confirmation token 48 | user = User.query.filter_by(email='john@example.com').first() 49 | token = user.generate_confirmation_token() 50 | response = self.client.get('/auth/confirm/{}'.format(token), 51 | follow_redirects=True) 52 | user.confirm(token) 53 | self.assertEqual(response.status_code, 200) 54 | self.assertTrue( 55 | 'You have confirmed your account' in response.get_data( 56 | as_text=True)) 57 | 58 | # log out 59 | response = self.client.get('/auth/logout', follow_redirects=True) 60 | self.assertEqual(response.status_code, 200) 61 | self.assertTrue('You have been logged out' in response.get_data( 62 | as_text=True)) 63 | -------------------------------------------------------------------------------- /tests/test_selenium.py: -------------------------------------------------------------------------------- 1 | import re 2 | import threading 3 | import time 4 | import unittest 5 | from selenium import webdriver 6 | from app import create_app, db, fake 7 | from app.models import Role, User, Post 8 | 9 | 10 | class SeleniumTestCase(unittest.TestCase): 11 | client = None 12 | 13 | @classmethod 14 | def setUpClass(cls): 15 | # start Chrome 16 | options = webdriver.ChromeOptions() 17 | options.add_argument('headless') 18 | try: 19 | cls.client = webdriver.Chrome(chrome_options=options) 20 | except: 21 | pass 22 | 23 | # skip these tests if the browser could not be started 24 | if cls.client: 25 | # create the application 26 | cls.app = create_app('testing') 27 | cls.app_context = cls.app.app_context() 28 | cls.app_context.push() 29 | 30 | # suppress logging to keep unittest output clean 31 | import logging 32 | logger = logging.getLogger('werkzeug') 33 | logger.setLevel("ERROR") 34 | 35 | # create the database and populate with some fake data 36 | db.create_all() 37 | Role.insert_roles() 38 | fake.users(10) 39 | fake.posts(10) 40 | 41 | # add an administrator user 42 | admin_role = Role.query.filter_by(name='Administrator').first() 43 | admin = User(email='john@example.com', 44 | username='john', password='cat', 45 | role=admin_role, confirmed=True) 46 | db.session.add(admin) 47 | db.session.commit() 48 | 49 | # start the Flask server in a thread 50 | cls.server_thread = threading.Thread(target=cls.app.run, 51 | kwargs={'debug': False}) 52 | cls.server_thread.start() 53 | 54 | # give the server a second to ensure it is up 55 | time.sleep(1) 56 | 57 | @classmethod 58 | def tearDownClass(cls): 59 | if cls.client: 60 | # stop the flask server and the browser 61 | cls.client.get('http://localhost:5000/shutdown') 62 | cls.client.quit() 63 | cls.server_thread.join() 64 | 65 | # destroy database 66 | db.drop_all() 67 | db.session.remove() 68 | 69 | # remove application context 70 | cls.app_context.pop() 71 | 72 | def setUp(self): 73 | if not self.client: 74 | self.skipTest('Web browser not available') 75 | 76 | def tearDown(self): 77 | pass 78 | 79 | def test_admin_home_page(self): 80 | # navigate to home page 81 | self.client.get('http://localhost:5000/') 82 | self.assertTrue(re.search('Hello,\s+Stranger!', 83 | self.client.page_source)) 84 | 85 | # navigate to login page 86 | self.client.find_element_by_link_text('Log In').click() 87 | self.assertIn('

    Login

    ', self.client.page_source) 88 | 89 | # login 90 | self.client.find_element_by_name('email').\ 91 | send_keys('john@example.com') 92 | self.client.find_element_by_name('password').send_keys('cat') 93 | self.client.find_element_by_name('submit').click() 94 | self.assertTrue(re.search('Hello,\s+john!', self.client.page_source)) 95 | 96 | # navigate to the user's profile page 97 | self.client.find_element_by_link_text('Profile').click() 98 | self.assertIn('

    john

    ', self.client.page_source) 99 | -------------------------------------------------------------------------------- /tools/AESEncrypt.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # 这里使用pycrypto‎库 4 | # easy_install pycrypto‎ 5 | 6 | from Crypto.Cipher import AES 7 | from binascii import b2a_hex, a2b_hex 8 | 9 | fchr = '\0' # fill character 10 | 11 | """ 12 | AES(Advanced Encryption Standard)对称加密: 13 | 14 | 对称加密就是公钥和撕咬是 15 | 16 | 1. 秘钥必须长度是16,24,32 17 | 2. 加密之前需要把文本改成16的倍数, 所以需要填充特殊字符 18 | """ 19 | 20 | 21 | class AEScrypt(): 22 | def __init__(self, key): 23 | self.key = key 24 | self.mode = AES.MODE_CBC 25 | 26 | def _fill_text(self, text): 27 | """ 28 | 补位: 29 | 如果text不足16位就用空格补足为16位, 30 | 如果大于16当时不是16的倍数,那就补足为16的倍数。 31 | """ 32 | length = 16 33 | count = len(text) 34 | if count < length: 35 | add = length - count 36 | text = text + (fchr * add) 37 | elif count > length: 38 | add = (length - (count % length)) 39 | text = text + (fchr * add) 40 | return text 41 | 42 | def encrypt(self, text): 43 | """ 44 | 加密: 45 | # 这里密钥key 长度必须为16,24,32 46 | # 16 (AES-128) 47 | # 24 (AES-192) 48 | # 32 (AES-256) 49 | # 目前AES-128 足够目前使用 50 | """ 51 | cryptor = AES.new(self.key, self.mode, b'0000000000000000') 52 | text = self._fill_text(text) 53 | self.ciphertext = cryptor.encrypt(text) 54 | # 因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题 55 | # 所以这里统一把加密后的字符串转化为16进制字符串 56 | return b2a_hex(self.ciphertext) 57 | 58 | def decrypt(self, text): 59 | """ 60 | 解密: 61 | """ 62 | cryptor = AES.new(self.key, self.mode, b'0000000000000000') 63 | plain_text = cryptor.decrypt(a2b_hex(text)) 64 | return plain_text.rstrip(fchr) # 去掉补足的空格用strip()去掉 65 | 66 | 67 | if __name__ == '__main__': 68 | obj = AEScrypt('1234567890123456') # 初始化密钥 69 | content = u"hesaimaterialasync2020" 70 | 71 | # 加密 72 | e = obj.encrypt(content) 73 | 74 | # 解密 75 | d = obj.decrypt(e) 76 | print("加密:", e) 77 | print("解密:", d) 78 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import date_tools 3 | from . import other_tools 4 | from . import compress_uuid 5 | from . import short_uuid 6 | from . import scale_conversion 7 | # from . import AESEncrypt 8 | from . import os_tools 9 | -------------------------------------------------------------------------------- /tools/compress_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import uuid 4 | 5 | SAFEHASH = [x for x in "0123456789-abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ"] 6 | 7 | 8 | def compress_uuid(): 9 | """ 10 | 根据http://www.ietf.org/rfc/rfc1738.txt,由uuid编码扩大字符域生成串 11 | 包括: [0-9a-zA-Z\-_] 共64个 12 | 长度: (32-2)/3*2 = 20 13 | 备注: 可在地球上人人都用,使用100年不重复(2^120) 14 | :return: str 15 | """ 16 | 17 | row = str(uuid.uuid4()).replace('-', '') 18 | safe_code = '' 19 | for i in xrange(10): 20 | enbin = "%012d" % int(bin(int(row[i * 3] + row[i * 3 + 1] + row[i * 3 + 2], 16))[2:], 10) 21 | safe_code += (SAFEHASH[int(enbin[0:6], 2)] + SAFEHASH[int(enbin[6:12], 2)]) 22 | return safe_code 23 | -------------------------------------------------------------------------------- /tools/date_tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | 封装处理处理时间相关方法 5 | """ 6 | 7 | import datetime 8 | import logging 9 | import calendar 10 | import time 11 | _logger = logging.getLogger(__name__) 12 | 13 | 14 | def time_cost(f): 15 | """ 16 | :param f: function 17 | :return: 计算函数耗时多久 18 | """ 19 | def _wrapper(*args, **kwargs): 20 | now = time.time() 21 | res = f(*args, **kwargs) 22 | log_txt = u'函数 %s 耗时 %.2fs!' % (f.__name__, time.time() - now) 23 | _logger.info(log_txt) 24 | print(log_txt) 25 | return res 26 | return _wrapper 27 | 28 | 29 | def date_range(begin_date, end_date): 30 | """ 31 | 获取一个时间区间的list 32 | """ 33 | dates = [] 34 | dt = datetime.datetime.strptime(begin_date, "%Y-%m-%d") 35 | date = begin_date[:] 36 | while date <= end_date: 37 | dates.append(date) 38 | dt = dt + datetime.timedelta(1) 39 | date = dt.strftime("%Y-%m-%d") 40 | return dates 41 | 42 | 43 | def get_month_begin_and_end(date_str=False): 44 | """ 45 | 获取某个月的月初和月末 46 | date_str: '2012-12-12' 默认是当天 47 | """ 48 | if date_str: 49 | today = time.strptime(date_str[:10], '%Y-%m-%d') 50 | else: 51 | today = time.localtime() 52 | # 月初肯定是1号 53 | day_begin = '%d-%02d-01' % (today.tm_year, today.tm_mon) 54 | # 得到本月的天数 第一返回为月第一日为星期几(0-6), 第二返回为此月天数 55 | wday, month_range = calendar.monthrange(today.tm_year, today.tm_mon) 56 | day_end = '%d-%02d-%02d' % (today.tm_year, today.tm_mon, month_range) 57 | return day_begin, day_end 58 | 59 | 60 | def add_months(dt, months): 61 | """ 62 | 月加减 63 | """ 64 | month = dt.month - 1 + months 65 | year = dt.year + month / 12 66 | month = month % 12 + 1 67 | day = min(dt.day, calendar.monthrange(year, month)[1]) 68 | return dt.replace(year=year, month=month, day=day) 69 | 70 | 71 | def get_week_of_month(year, month, day): 72 | """ 73 | 获取指定的某天是某个月中的第几周 74 | 周一作为一周的开始 75 | """ 76 | end = int(datetime.datetime(year, month, day).strftime("%W")) 77 | begin = int(datetime.datetime(year, month, 1).strftime("%W")) 78 | return end - begin + 1 79 | 80 | 81 | def check_year_month(year_month): 82 | """ 83 | 年月的格式判断 84 | e.g. 2018-12 85 | """ 86 | year_month = str(year_month) 87 | try: 88 | datetime.datetime.strptime(year_month + '-01', '%Y-%m-%d') 89 | return True 90 | except: 91 | return False 92 | 93 | 94 | def add_hours(date_str, hours=8): 95 | """ 96 | :param date_str: `2018-11-07 15:16:33` 97 | :param hours: int 98 | :return: string: `2018-11-07 23:16:33` 99 | """ 100 | if not date_str: 101 | return date_str 102 | dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") 103 | dt = dt + datetime.timedelta(hours=hours) 104 | return dt.strftime("%Y-%m-%d %H:%M:%S") 105 | -------------------------------------------------------------------------------- /tools/os_tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | 5 | def os_mkdir(dir_path, name): 6 | """ 7 | 创建目录, 如果存在则不创建 8 | 最终返回目录的绝对路径 9 | :param dir_path: 10 | :param name: 11 | :return: your dir path 12 | """ 13 | path = os.path.join(dir_path, name) 14 | folder = os.path.exists(path) 15 | if not folder: 16 | os.makedirs(path) 17 | return path 18 | else: 19 | return path 20 | 21 | 22 | def os_remove(path): 23 | """ 24 | 删除文件目录 25 | :param path: 26 | :return: 27 | """ 28 | os.remove(path) 29 | return True 30 | 31 | 32 | def os_save_temp_file(current_app, file_name, data, parent_dir=None): 33 | """ 34 | 创建临时文件 35 | :param current_app: 当前app 36 | :param file_name: 文件名 37 | :param data: 文件流数据 38 | :param parent_dir: 上级目录 39 | :return: 40 | """ 41 | dir_path = current_app.config['TEMP_DIR_PATH'] 42 | 43 | # 如果传上级目录, 则文件保存在此目录下 44 | if parent_dir: 45 | dir_path = os_mkdir(dir_path, parent_dir) 46 | 47 | file_path = os.path.join(dir_path, file_name) 48 | 49 | with open(file_path, 'wb') as f: 50 | f.write(data) 51 | 52 | return file_path 53 | 54 | 55 | if __name__ == "__main__": 56 | HOME_PATH = os.path.expanduser('~') 57 | print(os_mkdir(HOME_PATH, 'test')) 58 | 59 | -------------------------------------------------------------------------------- /tools/other_tools.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from random import Random 4 | 5 | 6 | def random_str(length=10): 7 | res = '' 8 | chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' 9 | char_length = len(chars) - 1 10 | random = Random() 11 | for i in range(length): 12 | res += chars[random.randint(0, char_length)] 13 | return res 14 | 15 | 16 | def int_length(num): 17 | """ 18 | 计算一个整数是几位数 19 | :param num: 20 | :return: 21 | """ 22 | cnt = 0 23 | num = abs(num) 24 | while num: 25 | num //= 10 26 | cnt += 1 27 | return cnt 28 | -------------------------------------------------------------------------------- /tools/readme.md: -------------------------------------------------------------------------------- 1 | # 这里存放常用的函数 -------------------------------------------------------------------------------- /tools/scale_conversion.py: -------------------------------------------------------------------------------- 1 | # coding=UTF-8 2 | # scale_conversion.py 3 | 4 | """ 5 | - id 压缩方案, 用于生成标签暗码 6 | - 把 id 转成 36进制 7 | - 4位可用 36 ** 4 = 1,679,616 情况 8 | - 提供 36 转 10进制 func 9 | """ 10 | 11 | 12 | def base10toN(num, n=36): 13 | """ 14 | 十进制 -> N进制 15 | """ 16 | chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 17 | res = '' 18 | current = num 19 | while current != 0: 20 | res = chars[current % n] + res 21 | current = current / n 22 | return res 23 | 24 | 25 | def baseNto10(s, n=36): 26 | """ 27 | N进制 -> 十进制 28 | """ 29 | chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 30 | depth = res = 0 31 | for char in s[::-1]: 32 | res += chars.index(char) * (n ** depth) 33 | depth += 1 34 | return res 35 | 36 | 37 | if __name__ == '__main__': 38 | 39 | J = 36 40 | number = 678123 41 | 42 | num_char = base10toN(number, J) 43 | new_num = baseNto10(num_char, J) 44 | 45 | print("origin number", number) 46 | print("number char", num_char) 47 | print("number after converse", new_num) 48 | 49 | 50 | -------------------------------------------------------------------------------- /tools/short_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from uuid import uuid4 4 | 5 | uuidChars = ( 6 | "a", "b", "c", "d", "e", "f", "g", 7 | "h", "i", "j", "k", "l", "m", "n", 8 | "o", "p", "q", "r", "s", "t", 9 | "u", "v", "w", "x", "y", "z", 10 | 11 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 12 | 13 | "A", "B", "C", "D", "E", "F", "G", 14 | "H", "I", "J", "K", "L", "M", "N", 15 | "O", "P", "Q", "R", "S", "T", 16 | "U", "V", "W", "X", "Y", "Z" 17 | ) 18 | 19 | 20 | """ 21 | 1.每四位从16进制转成10进制 22 | 2.取62的余数作为index从`uuidChars`中取字符 23 | """ 24 | def short_uuid(): 25 | uuid = str(uuid4()).replace('-', '') 26 | result = '' 27 | for i in range(0, 8): 28 | sub = uuid[i * 4: i * 4 + 4] 29 | x = int(sub, 16) 30 | result += uuidChars[x % 0x3E] 31 | return result 32 | 33 | --------------------------------------------------------------------------------