├── Test.py ├── src ├── io │ ├── __init__.py │ ├── __pycache__ │ │ ├── Chat.cpython-310.pyc │ │ └── __init__.cpython-310.pyc │ └── Chat.py ├── service │ └── __init__.py ├── siwadoc │ ├── __init__.py │ ├── __pycache__ │ │ ├── CateSiwa.cpython-310.pyc │ │ ├── LinkSiwa.cpython-310.pyc │ │ ├── ResSiwa.cpython-310.pyc │ │ ├── TagSiwa.cpython-310.pyc │ │ ├── UserSiwa.cpython-310.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── ArticleSiwa.cpython-310.pyc │ │ ├── CommentSiwa.cpython-310.pyc │ │ ├── ProjectSiwa.cpython-310.pyc │ │ └── SwiperSiwa.cpython-310.pyc │ ├── ResSiwa.py │ ├── ProjectSiwa.py │ ├── TagSiwa.py │ ├── SwiperSiwa.py │ ├── CateSiwa.py │ ├── LinkSiwa.py │ ├── ArticleSiwa.py │ ├── CommentSiwa.py │ └── UserSiwa.py ├── utils │ ├── __init__.py │ ├── __pycache__ │ │ ├── jwt.cpython-310.pyc │ │ ├── file.cpython-310.pyc │ │ ├── model.cpython-310.pyc │ │ ├── __init__.cpython-310.pyc │ │ └── response.cpython-310.pyc │ ├── response.py │ ├── file.py │ ├── model.py │ └── jwt.py ├── model │ ├── system │ │ ├── __init__.py │ │ ├── SystemModel.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-310.pyc │ │ │ ├── LayoutModel.cpython-310.pyc │ │ │ └── ProjectModel.cpython-310.pyc │ │ ├── LayoutModel.py │ │ └── ProjectModel.py │ ├── __pycache__ │ │ ├── TagModel.cpython-310.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── CateModel.cpython-310.pyc │ │ ├── ChatModel.cpython-310.pyc │ │ ├── LinkModel.cpython-310.pyc │ │ ├── TypeModel.cpython-310.pyc │ │ ├── UserModel.cpython-310.pyc │ │ ├── ArticleModel.cpython-310.pyc │ │ ├── CommentModel.cpython-310.pyc │ │ └── SwiperModel.cpython-310.pyc │ ├── TagModel.py │ ├── TypeModel.py │ ├── ChatModel.py │ ├── SwiperModel.py │ ├── __init__.py │ ├── CateModel.py │ ├── LinkModel.py │ ├── UserModel.py │ ├── CommentModel.py │ └── ArticleModel.py ├── __pycache__ │ └── __init__.cpython-310.pyc ├── config │ ├── __pycache__ │ │ ├── env.cpython-310.pyc │ │ └── __init__.cpython-310.pyc │ ├── env.py │ └── __init__.py ├── upload │ └── image │ │ └── 2024 │ │ └── 1 │ │ └── 17056268327752459.jpeg ├── router │ ├── __pycache__ │ │ ├── ResRouter.cpython-310.pyc │ │ ├── TagRouter.cpython-310.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── CateRouter.cpython-310.pyc │ │ ├── ChatRouter.cpython-310.pyc │ │ ├── LinkRouter.cpython-310.pyc │ │ ├── SwiperRouter.cpython-310.pyc │ │ ├── UserRouter.cpython-310.pyc │ │ ├── ArticleRouter.cpython-310.pyc │ │ ├── CommentRouter.cpython-310.pyc │ │ ├── LinkTypeRouter.cpython-310.pyc │ │ └── ProjectRouter.cpython-310.pyc │ ├── ChatRouter.py │ ├── __init__.py │ ├── LinkTypeRouter.py │ ├── TagRouter.py │ ├── SwiperRouter.py │ ├── LinkRouter.py │ ├── ResRouter.py │ ├── CateRouter.py │ ├── CommentRouter.py │ ├── UserRouter.py │ ├── ProjectRouter.py │ └── ArticleRouter.py └── __init__.py ├── .gitignore ├── .idea ├── vcs.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml └── misc.xml ├── Dockerfile ├── app.py ├── requirements.txt ├── README.md ├── thirive.sql └── LICENSE /Test.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/io/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/siwadoc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/model/system/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /__pycache__ 3 | /venv 4 | Test.py -------------------------------------------------------------------------------- /src/model/system/SystemModel.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 阿里云oss、百度统计 -------------------------------------------------------------------------------- /src/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/io/__pycache__/Chat.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/io/__pycache__/Chat.cpython-310.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/jwt.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/utils/__pycache__/jwt.cpython-310.pyc -------------------------------------------------------------------------------- /src/config/__pycache__/env.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/config/__pycache__/env.cpython-310.pyc -------------------------------------------------------------------------------- /src/io/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/io/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/file.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/utils/__pycache__/file.cpython-310.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/model.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/utils/__pycache__/model.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/TagModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/TagModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/upload/image/2024/1/17056268327752459.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/upload/image/2024/1/17056268327752459.jpeg -------------------------------------------------------------------------------- /src/utils/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/utils/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/response.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/utils/__pycache__/response.cpython-310.pyc -------------------------------------------------------------------------------- /src/config/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/config/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/CateModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/CateModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/ChatModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/ChatModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/LinkModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/LinkModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/TypeModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/TypeModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/UserModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/UserModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/ResRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/ResRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/TagRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/TagRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/CateSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/CateSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/LinkSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/LinkSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/ResSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/ResSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/TagSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/TagSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/UserSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/UserSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/ArticleModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/ArticleModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/CommentModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/CommentModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/SwiperModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/__pycache__/SwiperModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/CateRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/CateRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/ChatRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/ChatRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/LinkRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/LinkRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/SwiperRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/SwiperRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/UserRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/UserRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/ArticleSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/ArticleSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/CommentSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/CommentSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/ProjectSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/ProjectSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/siwadoc/__pycache__/SwiperSiwa.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/siwadoc/__pycache__/SwiperSiwa.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/system/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/system/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/ArticleRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/ArticleRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/CommentRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/CommentRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/LinkTypeRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/LinkTypeRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/router/__pycache__/ProjectRouter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/router/__pycache__/ProjectRouter.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/system/__pycache__/LayoutModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/system/__pycache__/LayoutModel.cpython-310.pyc -------------------------------------------------------------------------------- /src/model/system/__pycache__/ProjectModel.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuYuYang01/Thrive-Server/HEAD/src/model/system/__pycache__/ProjectModel.cpython-310.pyc -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/model/TagModel.py: -------------------------------------------------------------------------------- 1 | from src.model import db 2 | from src.utils.model import BaseModel 3 | 4 | 5 | class TagModel(BaseModel): 6 | __tablename__ = 'tag' 7 | 8 | id = db.Column(db.Integer, primary_key=True) 9 | name = db.Column(db.String(100)) # 名称 10 | -------------------------------------------------------------------------------- /src/model/TypeModel.py: -------------------------------------------------------------------------------- 1 | from src.model import db 2 | from src.utils.model import BaseModel 3 | 4 | 5 | class TypeModel(BaseModel): 6 | __tablename__ = 'type' 7 | 8 | id = db.Column(db.Integer, primary_key=True) 9 | name = db.Column(db.String(100)) # 类型名称 10 | -------------------------------------------------------------------------------- /src/model/ChatModel.py: -------------------------------------------------------------------------------- 1 | from src.model import db 2 | from src.utils.model import BaseModel 3 | 4 | 5 | class ChatModel(BaseModel): 6 | __tablename__ = 'chat' 7 | 8 | id = db.Column(db.Integer, primary_key=True) 9 | room = db.Column(db.Integer) # 房间号 10 | data = db.Column(db.JSON) # 聊天内容 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 设置基础镜像 2 | FROM python:3.10 3 | 4 | # 设置工作目录 5 | WORKDIR /thrive 6 | 7 | # 将当前目录中所有文件复制到指定目录中 8 | COPY . /thrive 9 | 10 | # 构建镜像时做的事情:下载相关依赖 11 | RUN pip3 install -r requirements.txt -i https://mirrors.bfsu.edu.cn/pypi/web/simple/ 12 | 13 | # 暴露容器端口号 14 | EXPOSE 5000 15 | 16 | # 在容器创建成功后做的事情,相当于执行:python3 app.py 17 | CMD ["python3", "app.py"] -------------------------------------------------------------------------------- /src/utils/response.py: -------------------------------------------------------------------------------- 1 | def Result(code, message, data=None): 2 | """简化数据响应的代码""" 3 | 4 | # 如果data没有数据,就去除data属性 5 | if not data: 6 | data = [] 7 | if not len(data): return {"code": code, "message": message} 8 | 9 | # return {"code": code, "message": message, **data} 10 | return {"code": code, "message": message, "data": data} 11 | -------------------------------------------------------------------------------- /src/utils/file.py: -------------------------------------------------------------------------------- 1 | from os import path, mkdir 2 | 3 | # 生成文件唯一名称 4 | def randomName(name): 5 | """当前时间戳 + 随机数 = 唯一值""" 6 | from time import time 7 | from random import randint 8 | 9 | # 文件后缀 10 | s = path.splitext(name)[1] 11 | 12 | # 获取当前时间戳 13 | t = int(time()) 14 | 15 | # 生成一段随机数 16 | n = randint(0, 10000000) 17 | 18 | return f"{t}{n}{s}" 19 | -------------------------------------------------------------------------------- /src/model/system/LayoutModel.py: -------------------------------------------------------------------------------- 1 | class LayoutModel(object): 2 | isArticleLayout = "classics" 3 | rightSidebar = ['author', 'randomArticle', 'hotArticle', 'newComments'] 4 | swiperImage = "https://bu.dusays.com/2023/11/10/654e2cf6055b0.jpg" 5 | swiperText = ['System.out.print("有些梦虽然遥不可及,但并不是不可能实现!");', 'print(" 互联网从不缺乏天才, 而努力才是最终的入场券!")', 'console.log("再渺小的星光,也有属于他的光芒!")'] 6 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from src import CreateApp, socketio 2 | 3 | app = CreateApp("dev") 4 | 5 | 6 | @app.route("/") 7 | def Home(): 8 | return "Hello Flask!" 9 | 10 | 11 | if (__name__ == "__main__"): 12 | debug = app.config["DEBUG"] 13 | port = app.config["PORT"] 14 | 15 | app.run(debug=debug, port=port, host="0.0.0.0") 16 | socketio.run(app) 17 | 18 | print("API在线文档:http://127.0.0.1:5000/docs") 19 | -------------------------------------------------------------------------------- /src/siwadoc/ResSiwa.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional, Any 3 | 4 | 5 | class ResBody(BaseModel): 6 | file: Any = Field(description="请上传文件") 7 | target: Optional[str] = Field(default="default", description="指定文件上传的目录") 8 | 9 | 10 | class FileBody(BaseModel): 11 | files: list = Field(default=["/image/2024/1/111.jpg", "/image/2024/1/222.jpg"], description="指定需要删除的文件路径") 12 | -------------------------------------------------------------------------------- /src/model/SwiperModel.py: -------------------------------------------------------------------------------- 1 | from src.model import db 2 | from src.utils.model import BaseModel 3 | 4 | 5 | class SwiperModel(BaseModel): 6 | __tablename__ = 'swiper' 7 | 8 | id = db.Column(db.Integer, primary_key=True) 9 | title = db.Column(db.String(100)) # 标题 10 | description = db.Column(db.String(255)) # 描述 11 | image = db.Column(db.String(255)) # 图片 12 | url = db.Column(db.String(500)) # 跳转地址 13 | -------------------------------------------------------------------------------- /src/model/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | 3 | from src.config.env import switch 4 | 5 | db = None 6 | 7 | 8 | # 创建SQLAlchemy实例 9 | def CreateSQLAlchemy(app, type): 10 | global db 11 | 12 | # 选择开发 / 线上环境 13 | env = switch[type] 14 | 15 | # 将配置信息加载到 Flask 应用程序中 16 | app.config.from_object(env) 17 | 18 | # 保存起来,方便其他地方使用 19 | db = SQLAlchemy(app) 20 | 21 | return db 22 | -------------------------------------------------------------------------------- /src/config/env.py: -------------------------------------------------------------------------------- 1 | from src.config import BaseConfig 2 | 3 | 4 | # 开发环境 5 | class DevelopConfig(BaseConfig): 6 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:liuyuyang@127.0.0.1:3306/ThriveX' 7 | 8 | 9 | # 生产环境 10 | class ProduceConfig(BaseConfig): 11 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:liuyuyang@127.0.0.1:3306/ThriveX' 12 | 13 | 14 | # 选择环境 15 | switch = { 16 | "dev": DevelopConfig, 17 | "pro": ProduceConfig 18 | } 19 | -------------------------------------------------------------------------------- /src/model/CateModel.py: -------------------------------------------------------------------------------- 1 | from src.model import db 2 | from src.utils.model import BaseModel 3 | 4 | 5 | class CateModel(BaseModel): 6 | __tablename__ = 'cate' 7 | 8 | id = db.Column(db.Integer, primary_key=True) 9 | name = db.Column(db.String(255), nullable=False, unique=True) 10 | icon = db.Column(db.String(100)) 11 | url = db.Column(db.String(255)) 12 | mark = db.Column(db.String(100), unique=True) 13 | level = db.Column(db.Integer, default=0) 14 | -------------------------------------------------------------------------------- /src/siwadoc/ProjectSiwa.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class ProjectBody(BaseModel): 5 | title: str = Field(default="Thrive", description="网站标题") 6 | subhead: str = Field(default="花有重开日, 人无再少年", description="网站副标题") 7 | logo: str = Field(default="https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640", description="网站LOGO") 8 | description: str = Field(default="记录前端、Python、Java点点滴滴", description="网站描述") 9 | keyword: str = Field(default="Thrive,前端,Python,Java", description="网站SEO关键词") 10 | -------------------------------------------------------------------------------- /src/siwadoc/TagSiwa.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConstrainedList, Field 2 | from typing import Optional 3 | 4 | 5 | class TagQuery(BaseModel): 6 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 7 | size: Optional[int] = Field(default=5, description="每页显示几个") 8 | 9 | 10 | class TagBody(BaseModel): 11 | id: Optional[int] 12 | name: str = Field(default="大前端", description="标签名称") 13 | 14 | 15 | class TagBodyId(BaseModel): 16 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 17 | -------------------------------------------------------------------------------- /src/io/Chat.py: -------------------------------------------------------------------------------- 1 | from flask_socketio import emit, join_room 2 | 3 | from src import socketio 4 | from src.model import db 5 | from src.model.ChatModel import ChatModel 6 | 7 | # 当前房间号 8 | thisRoom = 10001 9 | 10 | 11 | # 加入房间 12 | @socketio.on('joinRoom') 13 | def joinRoom(room): 14 | global thisRoom 15 | 16 | thisRoom = room 17 | 18 | join_room(room) 19 | 20 | 21 | # 在房间中发送消息 22 | @socketio.on('roomMsg') 23 | def roomMsg(data): 24 | # 将聊天内容保存到数据库 25 | chat = ChatModel(room=thisRoom, data=data) 26 | db.session.add(chat) 27 | db.session.commit() 28 | 29 | emit('roomMsg', data, room=thisRoom) 30 | -------------------------------------------------------------------------------- /src/model/LinkModel.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from src.model import db 4 | from src.utils.model import BaseModel 5 | 6 | 7 | class LinkModel(BaseModel): 8 | __tablename__ = 'link' 9 | 10 | id = db.Column(db.Integer, primary_key=True) 11 | title = db.Column(db.String(100)) # 标题 12 | description = db.Column(db.String(255)) # 描述 13 | email = db.Column(db.String(100)) # 邮箱 14 | image = db.Column(db.String(255)) # 图片 15 | url = db.Column(db.String(500)) # 跳转地址 16 | type = db.Column(db.String(100)) # 跳转地址 17 | createtime = db.Column("create_time", db.DateTime, default=datetime.utcnow) # 创建时间 18 | -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | # 项目配置 2 | class Config(object): 3 | # 是否开启调试模式 4 | DEBUG = True 5 | # 项目端口号 6 | PORT = 5000 7 | # 请求路径前缀 8 | URL_PREFIX = "/api" 9 | # 上传的图片位置 10 | UPLOAD_PATH = "/upload" 11 | 12 | 13 | # SQLAlchemy配置 14 | class SQLAlchemyConfig(object): 15 | # 是否追踪数据变化 16 | SQLALCHEMY_TRACK_MODIFICATIONS = False 17 | # 是否打印底层执行的SQL 18 | SQLALCHEMY_ECHO = False 19 | 20 | 21 | # Jwt配置 22 | class JwtConfig(object): 23 | # Token过期时间 (单位:天) 24 | EXPIRE = 10000 25 | # 自定义秘钥 26 | SECRET_KEY = "LiuYuYang1024" 27 | # 加密方式 28 | ALGORITHM = "HS256" 29 | 30 | 31 | # 配置基类 32 | class BaseConfig(Config, SQLAlchemyConfig, JwtConfig): 33 | pass 34 | -------------------------------------------------------------------------------- /src/model/UserModel.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from src.model import db 4 | from src.utils.model import BaseModel 5 | 6 | 7 | class UserModel(BaseModel): 8 | # 创建用户表 9 | __tablename__ = 'user' 10 | 11 | id = db.Column(db.Integer, primary_key=True) 12 | username = db.Column(db.String(20), unique=True, nullable=False) 13 | password = db.Column(db.String(20), nullable=True) 14 | name = db.Column(db.String(50), nullable=True) 15 | email = db.Column(db.String(100)) 16 | avatar = db.Column(db.String(255)) 17 | info = db.Column(db.String(255)) 18 | role = db.Column(db.String(50), default="user") 19 | createtime = db.Column("create_time", db.DateTime, default=datetime.utcnow) # 创建时间 -------------------------------------------------------------------------------- /src/model/CommentModel.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from src.model import db 4 | from src.utils.model import BaseModel 5 | 6 | 7 | class CommentModel(BaseModel): 8 | __tablename__ = 'comment' 9 | 10 | id = db.Column(db.Integer, primary_key=True) 11 | name = db.Column(db.String(50)) # 用户名称 12 | avatar = db.Column(db.String(255)) # 用户头像 13 | content = db.Column(db.String(500)) # 评论内容 14 | email = db.Column(db.String(100)) # 邮箱 15 | url = db.Column(db.String(500)) # 地址 16 | aid = db.Column(db.Integer) # 该评论所在的文章id 17 | rid = db.Column(db.Integer) # 所有回复这条评论的id 18 | audit = db.Column(db.Integer, default=0) # 评论是否审核成功 19 | createtime = db.Column("create_time", db.DateTime, default=datetime.utcnow) # 创建时间 20 | -------------------------------------------------------------------------------- /src/model/ArticleModel.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from src.model import db 4 | from src.utils.model import BaseModel 5 | 6 | 7 | class ArticleModel(BaseModel): 8 | __tablename__ = 'article' 9 | 10 | id = db.Column(db.Integer, primary_key=True) 11 | title = db.Column(db.String(100)) # 文章标题 12 | description = db.Column(db.String(200)) # 文章摘要 13 | content = db.Column(db.Text) # 文章内容 14 | cover = db.Column(db.String(300)) # 文章封面 15 | view = db.Column(db.Integer, default=0) # 文章浏览量 16 | # comment = db.Column(db.Integer, default=0) # 评论数量 17 | cids = db.Column(db.String(255)) # 文章分类 18 | tag = db.Column(db.String(100)) # 文章标签 19 | createtime = db.Column("create_time", db.DateTime, default=datetime.utcnow) # 创建时间 20 | -------------------------------------------------------------------------------- /src/utils/model.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, date 2 | from decimal import Decimal 3 | 4 | from sqlalchemy.orm import class_mapper 5 | 6 | from src.model import db 7 | 8 | 9 | def format_data(data): 10 | # 格式化数据,处理json无法序列化的数据 11 | if isinstance(data, datetime): 12 | return data.strftime('%Y-%m-%d %H:%M:%S') 13 | elif isinstance(data, date): 14 | return data.strftime("%Y-%m-%d") 15 | elif isinstance(data, Decimal): 16 | return float(data) 17 | else: 18 | return data 19 | 20 | 21 | class BaseModel(db.Model): 22 | # 生成抽象模型,不会创建模型对应的表,减少重复代码 23 | __abstract__ = True 24 | 25 | def to(self): 26 | return {p.key: format_data(getattr(self, p.key)) for p in class_mapper(self.__class__).iterate_properties} 27 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_socketio import SocketIO 3 | from flask_siwadoc import SiwaDoc 4 | 5 | app = Flask(__name__, static_url_path="/") 6 | socketio = SocketIO(app, cors_allowed_origins='*') 7 | siwa = SiwaDoc(app, title="Flask Siwadoc", description="一个自动生成openapi文档的库", version="2.0") 8 | 9 | def CreateApp(env): 10 | from src.model import CreateSQLAlchemy 11 | db = CreateSQLAlchemy(app, env) 12 | 13 | # 在应用上下文中运行应用 14 | with app.app_context(): 15 | # 删除所有继承自db.Model的表 16 | # db.drop_all() 17 | # 创建所有继承自db.Model的表 18 | db.create_all() 19 | 20 | # 配置网站资源存放位置 21 | app.static_folder = app.config["UPLOAD_PATH"][1:] 22 | 23 | # 加载路由 24 | from src import router 25 | 26 | # 即时通讯 27 | from src.io import Chat 28 | 29 | return app 30 | -------------------------------------------------------------------------------- /src/siwadoc/SwiperSiwa.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel, ConstrainedList, Field 4 | from typing import Optional 5 | 6 | 7 | class SwiperQuery(BaseModel): 8 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 9 | size: Optional[int] = Field(default=5, description="每页显示几个") 10 | 11 | 12 | class SwiperBody(BaseModel): 13 | id: Optional[int] 14 | title: str = Field(default="大前端新趋势", description="轮播图标题") 15 | description: str = Field(default="轮播图摘要", description="轮播图摘要") 16 | image: str = Field(default="http://127.0.0.1:5000/1.jpg", description="轮播图") 17 | url: str = Field(default="http://127.0.0.1:5000", description="轮播图跳转地址") 18 | 19 | 20 | class SwiperBodyId(BaseModel): 21 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 22 | -------------------------------------------------------------------------------- /src/siwadoc/CateSiwa.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, ConstrainedList, Field 2 | from typing import Optional, Any 3 | 4 | 5 | class CateQuery(BaseModel): 6 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 7 | size: Optional[int] = Field(default=5, description="每页显示几个") 8 | 9 | 10 | class CateBody(BaseModel): 11 | id: Optional[int] 12 | name: str = Field(default="大前端", description="分类名称") 13 | icon: str = Field(default="🎉", description="分类图标") 14 | url: str = Field(default="http://127.0.0.1:5000", description="分类跳转链接") 15 | mark: str = Field(default="dqd", description="分类标识,通常为名称的英文首字母缩写") 16 | level: int = Field(default=0, description="分类级别 一级:0 | 二级:一级分类的ID") 17 | # children: ConstrainedList[Any] = Field(default=[], description="该分类下的所有子分类") 18 | 19 | 20 | class CateBodyId(BaseModel): 21 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 22 | -------------------------------------------------------------------------------- /src/siwadoc/LinkSiwa.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel, ConstrainedList, Field 4 | from typing import Optional 5 | 6 | 7 | class LinkQuery(BaseModel): 8 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 9 | size: Optional[int] = Field(default=5, description="每页显示几个") 10 | 11 | 12 | class LinkBody(BaseModel): 13 | id: Optional[int] 14 | title: str = Field(default="YuYang", description="网站标题") 15 | description: str = Field(default="逐渐强大的全栈开发工程师", description="网站介绍") 16 | email: str = Field(default="3311118881@qq.com", description="网站邮箱") 17 | image: str = Field(default="http://127.0.0.1:5000/1.jpg", description="网站图标") 18 | url: str = Field(default="http://127.0.0.1:5000", description="网站跳转地址") 19 | type: int = Field(default=1, description="网站类型的ID") 20 | createtime: datetime = Field(default=datetime.now(), description="网站加入时间") 21 | 22 | 23 | class LinkBodyId(BaseModel): 24 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bidict==0.23.1 2 | blinker==1.7.0 3 | certifi==2022.12.7 4 | cffi==1.16.0 5 | charset-normalizer==2.0.12 6 | click==8.1.7 7 | colorama==0.4.6 8 | cryptography==43.0.0 9 | distlib==0.3.6 10 | filelock==3.4.1 11 | Flask==3.0.1 12 | Flask-Cors==3.0.10 13 | Flask-HTTPAuth==4.8.0 14 | flask-siwadoc==0.2.2 15 | Flask-SocketIO==5.3.6 16 | Flask-SQLAlchemy==3.0.3 17 | greenlet==3.0.3 18 | h11==0.14.0 19 | idna==3.4 20 | importlib-resources==5.4.0 21 | importlib_metadata==8.2.0 22 | itsdangerous==2.1.2 23 | Jinja2==3.1.3 24 | MarkupSafe==2.1.5 25 | openai==0.8.0 26 | pbr==5.11.1 27 | platformdirs==2.4.0 28 | psutil==5.9.5 29 | pycparser==2.22 30 | pydantic==1.9.0 31 | PyJWT==2.7.0 32 | PyMySQL==1.1.1 33 | python-engineio==4.9.0 34 | python-socketio==5.11.1 35 | qiniu==7.10.0 36 | requests==2.27.1 37 | simple-websocket==1.0.0 38 | six==1.16.0 39 | SQLAlchemy==1.4.48 40 | stevedore==3.5.2 41 | tqdm==4.64.1 42 | typing_extensions==4.9.0 43 | tzdata==2023.3 44 | tzlocal==5.0.1 45 | urllib3==1.26.14 46 | virtualenv==20.17.1 47 | virtualenv-clone==0.5.7 48 | virtualenvwrapper-win==1.2.7 49 | Werkzeug==3.0.1 50 | wsproto==1.2.0 51 | zipp==3.19.2 52 | -------------------------------------------------------------------------------- /src/siwadoc/ArticleSiwa.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel, ConstrainedList, Field 4 | from typing import Optional 5 | 6 | 7 | class ArticleQuery(BaseModel): 8 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 9 | size: Optional[int] = Field(default=5, description="每页显示几个") 10 | 11 | 12 | class ArticleBody(BaseModel): 13 | id: Optional[int] 14 | title: str = Field(default="大前端新趋势", description="文章标题") 15 | description: str = Field(default="文章摘要", description="文章摘要") 16 | content: str = Field(default="文章内容", description="文章内容") 17 | cover: str = Field(default="http://127.0.0.1:5000/1.jpg", description="文章封面") 18 | view: int = Field(default=10, description="文章浏览量") 19 | comment: int = Field(default=10, description="文章评论数量") 20 | cids: ConstrainedList[int] = Field(default=[2, 4], description="文章的分类id") 21 | tag: str = Field(default="大前端,Python,java", description="文章标签") 22 | createtime: datetime = Field(default=datetime.now(), description="文章创建时间") 23 | 24 | 25 | class ArticleBodyId(BaseModel): 26 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 27 | -------------------------------------------------------------------------------- /src/siwadoc/CommentSiwa.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel, ConstrainedList, Field 4 | from typing import Optional 5 | 6 | 7 | class CommentQuery(BaseModel): 8 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 9 | size: Optional[int] = Field(default=5, description="每页显示几个") 10 | 11 | 12 | class CommentBody(BaseModel): 13 | id: Optional[int] 14 | name: str = Field(default="神秘人", description="用户名称") 15 | avatar: str = Field(default="http://127.0.0.1:5000/1.jpg", description="用户头像") 16 | content: str = Field(default="评论内容", description="用户评论的内容") 17 | email: str = Field(default="3311118881@qq.com", description="用户邮箱") 18 | url: str = Field(default="http://127.0.0.1:5000", description="用户网站") 19 | aid: int = Field(default=1, description="该评论所属的文章ID") 20 | rid: int = Field(default=1, description="记录所有回复该评论的ID") 21 | audit: int = Field(default=1, description="该评论是否审核 审核成功:1 | 待审核:0") 22 | createtime: datetime = Field(default=datetime.now(), description="发布评论的时间") 23 | 24 | 25 | class CommentBodyId(BaseModel): 26 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 27 | -------------------------------------------------------------------------------- /src/router/ChatRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model.ChatModel import ChatModel 4 | from src.utils.jwt import TokenRequired 5 | from src.utils.response import Result 6 | 7 | chat = Blueprint("chat", __name__) 8 | 9 | 10 | # 获取指定房间内的聊天记录 11 | @chat.route("/chat/") 12 | def list(room): 13 | page = request.args.get("page", 1, type=int) 14 | size = request.args.get("size", 5, type=int) 15 | 16 | paginate = ChatModel.query.filter_by(room=room).paginate(page=page, per_page=size, error_out=False) 17 | 18 | data = { 19 | "result": [k.to() for k in paginate], 20 | "page": paginate.page, 21 | "size": paginate.per_page, 22 | "pages": paginate.pages, 23 | "total": paginate.total, 24 | "prev": paginate.has_prev, 25 | "next": paginate.has_next 26 | } 27 | 28 | return Result(200, "获取房间聊天记录成功", data) 29 | 30 | 31 | # 获取指定房间内的所有聊天记录 32 | @chat.route("/chat/list/") 33 | def get(room): 34 | data = ChatModel.query.filter_by(room=room).all() 35 | 36 | if not data: 37 | return Result(400, "获取失败:没有此房间") 38 | 39 | return Result(200, "获取房间聊天记录成功", [k.to() for k in data]) 40 | -------------------------------------------------------------------------------- /src/utils/jwt.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | from src.utils.response import Result 3 | 4 | from functools import wraps 5 | import jwt 6 | 7 | from src import app 8 | 9 | # 加载配置信息 10 | expire = app.config["EXPIRE"] 11 | secretkey = app.config["SECRET_KEY"] 12 | algorithm = app.config["ALGORITHM"] 13 | 14 | 15 | # 定义装饰器函数,用于验证 token 的有效性 16 | class ExpiredSignatureError: 17 | pass 18 | 19 | 20 | def TokenRequired(f): 21 | @wraps(f) 22 | def decorated(*args, **kwargs): 23 | token = None 24 | 25 | # 从请求头或查询参数中获取 token 26 | if 'Authorization' in request.headers: 27 | parts = request.headers['Authorization'].split() 28 | if len(parts) == 2 and parts[0] == 'Bearer': 29 | token = parts[1] 30 | elif 'token' in request.args: 31 | token = request.args.get('token') 32 | 33 | # 如果找到 token,验证其有效性 34 | if token: 35 | try: 36 | payload = jwt.decode(token, secretkey, algorithm) 37 | # 验证成功,可以继续处理请求 38 | return f(*args, **kwargs) 39 | except jwt.ExpiredSignatureError: 40 | return Result(401, "过期的Token") 41 | except jwt.InvalidTokenError: 42 | return Result(401, "无效的Token") 43 | 44 | # 没有找到 token 45 | return Result(401, "不存在的Token") 46 | 47 | return decorated 48 | -------------------------------------------------------------------------------- /src/siwadoc/UserSiwa.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel, ConstrainedList, Field 4 | from typing import Optional 5 | 6 | 7 | class UserQuery(BaseModel): 8 | page: Optional[int] = Field(default=1, description="第几页", ge=1) 9 | size: Optional[int] = Field(default=5, description="每页显示几个") 10 | 11 | 12 | class LoginBody(BaseModel): 13 | username: str = Field(default="liuyuyang", description="用户名") 14 | password: str = Field(default="123123", description="密码") 15 | 16 | 17 | class UserBody(BaseModel): 18 | id: Optional[int] 19 | username: str = Field(default="liuyuyang", description="用户名") 20 | password: str = Field(default="123123", description="密码") 21 | name: str = Field(default="YuYang", description="用户名称") 22 | email: str = Field(default="3311118881@qq.com", description="用户邮箱") 23 | avatar: str = Field(default="https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640", description="用户头像") 24 | info: str = Field(default="再渺小的星光,也有属于他的光芒!", description="用户介绍") 25 | role: str = Field(default="admin", description="用户组") 26 | createtime: datetime = Field(default=datetime.now(), description="用户加入时间") 27 | 28 | class UserAdminPass(BaseModel): 29 | username: str = Field(default="liuyuyang", description="用户名") 30 | oldPassword: str = Field(default="123456", description="旧密码") 31 | newPassword: str = Field(default="123123", description="新密码") 32 | 33 | class UserBodyId(BaseModel): 34 | ids: ConstrainedList[int] = Field(default=[1, 2, 3], description="ID列表") 35 | -------------------------------------------------------------------------------- /src/router/__init__.py: -------------------------------------------------------------------------------- 1 | from src.utils.response import Result 2 | 3 | from .ProjectRouter import project 4 | from .UserRouter import user 5 | from .ArticleRouter import article 6 | from .TagRouter import tag 7 | from .SwiperRouter import swiper 8 | from .LinkRouter import link 9 | from .CommentRouter import comment 10 | from .CateRouter import cate 11 | from .ResRouter import res 12 | from .LinkTypeRouter import link_type 13 | from .ChatRouter import chat 14 | 15 | from src import app 16 | 17 | from flask_cors import CORS 18 | 19 | # 注册CORS, "/*" 允许访问所有api 20 | CORS(app, resources=r'/*') 21 | 22 | urlPrefix = app.config["URL_PREFIX"] 23 | 24 | app.register_blueprint(project, url_prefix=urlPrefix) 25 | app.register_blueprint(user, url_prefix=urlPrefix) 26 | app.register_blueprint(res, url_prefix=urlPrefix) 27 | app.register_blueprint(cate, url_prefix=urlPrefix) 28 | app.register_blueprint(article, url_prefix=urlPrefix) 29 | app.register_blueprint(comment, url_prefix=urlPrefix) 30 | app.register_blueprint(link, url_prefix=urlPrefix) 31 | app.register_blueprint(swiper, url_prefix=urlPrefix) 32 | app.register_blueprint(tag, url_prefix=urlPrefix) 33 | app.register_blueprint(link_type, url_prefix=urlPrefix) 34 | app.register_blueprint(chat, url_prefix=urlPrefix) 35 | 36 | 37 | # 捕获全局HTTP请求异常 38 | @app.errorhandler(Exception) 39 | def GlobalError(e): 40 | # 获取异常类型 41 | print(f"程序异常:{e}") 42 | 43 | if str(e).find("MySQLdb.IntegrityError") != -1: return Result(500, f"数据库异常:可能是数据重复") 44 | 45 | # 返回适当的错误响应 46 | return Result(500, f"程序异常:{e}") 47 | -------------------------------------------------------------------------------- /src/model/system/ProjectModel.py: -------------------------------------------------------------------------------- 1 | class ProjectModel(object): 2 | url = "https://liuyuyang.net/" # 网站链接 3 | favicon = "https://liuyuyang.net/favicon.ico" # 网站图标 4 | title = "Thrive" # 网站标题 5 | subhead = "花有重开日, 人无再少年" # 网站副标题 6 | light_logo = "https://thrive.oss-cn-beijing.aliyuncs.com/image/logo/light_logo.png" # 白天主题logo 7 | dark_logo = "https://thrive.oss-cn-beijing.aliyuncs.com/image/logo/dark_logo.png" # 暗黑主题logo 8 | description = "记录前端、Python、Java点点滴滴" # 网站描述 9 | keyword = ['Thrive', '前端', 'Python', 'Java'] # 网站SEO关键词 10 | footer = "一直对网站开发领域很感兴趣,从小就希望有一个属于自己的网站,在17年时候成功进入站长圈,并通过各种自学,以及各种折腾,才有了你现在看到的这个网站" # 底部描述 11 | font = "https://thrive.oss-cn-beijing.aliyuncs.com/static/font/LXGWWenKai.ttf" # 字体链接 12 | social = [{"name":"GitHub","url":"https://github.com/LiuYuYang01"},{"name":"Gitee","url":"https://gitee.com/liu_yu_yang666"},{"name":"Juejin","url":"https://juejin.cn/user/3083456627092078/posts"},{"name":"CSDN","url":"https://blog.csdn.net/haodian666?type=blog"},{"name":"QQ","url":"http://wpa.qq.com/msgrd?v=3&uin=3311118881&site=qq&menu=yes"}] # 社交账号 13 | covers = ['https://bu.dusays.com/2023/11/10/654e2da1d80f8.jpg', 'https://bu.dusays.com/2023/11/10/654e2d719d31c.jpg', 'https://bu.dusays.com/2023/11/10/654e2cf92cd45.jpg', 'https://bu.dusays.com/2023/11/10/654e2cf6055b0.jpg', 'https://bu.dusays.com/2023/11/10/654e2db0889fe.jpg', 'https://bu.dusays.com/2023/11/10/654e2d50015a9.jpg', 'https://bu.dusays.com/2023/11/05/65473848ed863.jpg', 'https://bu.dusays.com/2023/11/10/654e2c870e280.jpg', 'https://bu.dusays.com/2023/11/10/654e2c717eb73.jpg', 'https://bu.dusays.com/2023/11/10/654e2c5d75d5b.jpg', 'https://bu.dusays.com/2023/11/10/654e2da27801e.jpg', 'https://bu.dusays.com/2023/11/10/654e2d2a67517.jpg', 'https://bu.dusays.com/2023/11/10/654e2cf47f17a.jpg', 'https://bu.dusays.com/2023/11/05/65473848ed863.jpg'] # 随机文章封面 14 | -------------------------------------------------------------------------------- /src/router/LinkTypeRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model import db 4 | from src.model.TypeModel import TypeModel 5 | from src.utils.jwt import TokenRequired 6 | from src.utils.response import Result 7 | 8 | link_type = Blueprint("link_type", __name__) 9 | 10 | 11 | # 新增网站类型 12 | @link_type.route("/link_type", methods=["POST"]) 13 | @TokenRequired 14 | def add(): 15 | link_type = request.json 16 | 17 | data = TypeModel(**link_type) 18 | 19 | db.session.add(data) 20 | db.session.commit() 21 | 22 | return Result(200, "新增成功") 23 | 24 | 25 | # 删除网站类型 26 | @link_type.route("/link_type/", methods=["DELETE"]) 27 | @TokenRequired 28 | def drop(id): 29 | data = TypeModel.query.filter_by(id=id).first() 30 | 31 | if not data: 32 | return Result(400, "删除失败:没有此网站类型") 33 | 34 | db.session.delete(data) 35 | db.session.commit() 36 | 37 | return Result(200, "删除网站类型成功") 38 | 39 | 40 | # 批量删除 41 | @link_type.route("/link_type", methods=["DELETE"]) 42 | @TokenRequired 43 | def dropBatch(): 44 | ids = request.json["ids"] 45 | 46 | for id in ids: 47 | data = TypeModel.query.filter_by(id=id).first() 48 | 49 | if not data: 50 | return Result(400, f"批量删除失败:没有ID:{id}的网站类型") 51 | 52 | db.session.delete(data) 53 | 54 | db.session.commit() 55 | 56 | return Result(200, "批量删除网站类型成功") 57 | 58 | 59 | # 编辑网站类型 60 | @link_type.route("/link_type", methods=["PATCH"]) 61 | @TokenRequired 62 | def edit(): 63 | link_type = request.json 64 | 65 | data = TypeModel.query.filter_by(id=link_type["id"]).update(link_type) 66 | 67 | if not data: 68 | return Result(400, "编辑失败:没有此网站类型") 69 | 70 | db.session.commit() 71 | 72 | return Result(200, "编辑成功") 73 | 74 | 75 | # 获取网站类型详情 76 | @link_type.route("/link_type/") 77 | def get(id): 78 | data = TypeModel.query.filter_by(id=id).first() 79 | 80 | if not data: 81 | return Result(400, "获取失败:没有此网站类型") 82 | 83 | data = data.to() 84 | data["link_type"] = TypeModel.query.filter_by(id=data["link_type"]).first().to()["name"] 85 | 86 | return Result(200, "获取网站类型详情成功", data) 87 | 88 | 89 | # 获取网站类型列表 90 | @link_type.route("/link_type") 91 | def list(): 92 | data = TypeModel.query.all() 93 | 94 | return Result(200, "获取网站类型列表成功", [k.to() for k in data]) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **特别强调:** 此项目不允许任何商业行为,如果使用该项目进行二次开发,不允许收取任何费用,弘扬开源精神,从你我做起! 2 | 3 | # 最新版已发布 4 | 前端:[LiuYuYang01/ThriveX-Blog (github.com)](https://github.com/LiuYuYang01/ThriveX-Blog) 5 | 6 | 控制端:[LiuYuYang01/ThriveX-Admin (github.com)](https://github.com/LiuYuYang01/ThriveX-Admin) 7 | 8 | 后端:[LiuYuYang01/ThriveX-Server (github.com)](https://github.com/LiuYuYang01/ThriveX-Server) 9 | 10 | 11 | # 🎉 Thrive 现代化博客管理系统 12 | 13 | 🔥 **首先最重要的事情放第一** 14 | 15 | 1. 开源不易,麻烦占用 `10` 秒钟的时间帮忙点个免费的 `Star`,再此万分感谢! 16 | 2. 如果大家觉得这个项目还不错,碰巧你们公司缺人。不介意的话可以内推我😁,正好我也准备找工作了,我的邮箱:liuyuyang1024@yeah.net 17 | 18 | 19 | 20 | **下面开始进入主题↓↓↓** 21 | 22 | 23 | 24 | 🌈 **项目介绍:** Thrive 是一个简而不简单的现代化博客管理系统,专注于分享技术文章和知识,为技术爱好者和从业者提供一个分享、交流和学习的平台。用户可以在平台上发表自己的技术文章,或浏览其他用户分享的文章,并与他们进行讨论和互动。 25 | 26 | 27 | 28 | 🗂️ **项目预览:** [http://liuyuyang.net/](http://liuyuyang.net/) 29 | 30 | 31 | 32 | 🛠️ **技术架构:** 33 | 34 | 前端: Vue3、TypeScript、Pinia、Element-plus、Scss、Echarts、Vite 35 | 36 | 后端:Python Flask、SQLAlchemy、MySQL 37 | 38 | 后期会采用 `Spring Boot` 重构项目后端 39 | 40 | 41 | 42 | 🪧 **优缺点:** 43 | 44 | 优点:相比其他博客系统来说,这款系统采用前后端分离开发模式,如果你觉得默认的前端不好看,你完全可以不懂后端,只要你会调接口拿数据,你就可以通过现有的后端 `API` 自己写一个前端 45 | 46 | 缺点:前后端分离开发模式必然都存在一个痛点,那就是 `SEO` 问题,针对这个问题在未来我会采用 `Nuxt3` 服务端渲染这门框架对前端进行重构。 47 | 48 | 49 | 50 | ❤️ **项目初衷:** 51 | 52 | 一直对网站开发领域很感兴趣,从小就希望有一个属于自己的网站,因此踏上了 `Web` 全栈开发的旅途,立志有朝一日也能开发一款属于自己的网站。如今历时1年有余,一个人从0到1独立完成前端、控制端、后端、数据库。也算是完成了从小的一个心愿吧 53 | 54 | 55 | 56 | 🔥 **项目演示:** 57 | ![image-20240408093347003](https://bu.dusays.com/2024/04/09/66148fc49640a.png) 58 | ![image-20240408093347003](https://bu.dusays.com/2024/09/17/66e96ca781d49.png) 59 | 60 | 61 | 🏹 **未来计划:** 62 | 63 | ![image-20240408103633402](https://bu.dusays.com/2024/04/09/66148ecb66a39.png) 64 | 65 | 66 | 67 | 🌈 **项目运行:** 68 | 69 | 前端 or 控制端 70 | 71 | 环境:Nodejs16、18 72 | 73 | ``` 74 | npm i 75 | npm run dev 76 | ``` 77 | 78 | 79 | 80 | 后端 81 | 82 | Python3.9、10 83 | 84 | ```python 85 | # 创建虚拟环境 86 | virtualenv venv 87 | 88 | # 进入虚拟环境 89 | venv/Scripts/activate 90 | 91 | # 在虚拟环境中安装对应的依赖 92 | pip3 install -r requirements.txt 93 | 94 | # 在虚拟环境中运行项目,python app.py的相对路径 95 | python C:\Thrive\Thrive_API-2.0\app.py 96 | ``` 97 | 98 | 99 | 🏷️ **开源地址:** 100 | 101 | 前端:[LiuYuYang01/Thrive-Blog (github.com)](https://github.com/LiuYuYang01/Thrive-Blog) 102 | 103 | 控制端:[LiuYuYang01/Thrive-Admin (github.com)](https://github.com/LiuYuYang01/Thrive-Admin) 104 | 105 | 后端:[LiuYuYang01/Thrive-Server (github.com)](https://github.com/LiuYuYang01/Thrive-Server) 106 | 107 | 108 | 109 | 这个项目从前端到后端都是我从0到1敲出来的,所以刚开始一定会有很多隐藏的 `BUG`,希望大家能够及时在 `GitHub` 反馈,这样我也好加以改正,不断改善,成为最佳!当然如果大家能够提交 `PR` 那再好不过了 110 | -------------------------------------------------------------------------------- /src/router/TagRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model import db 4 | from src.model.TagModel import TagModel 5 | from src import siwa 6 | from src.siwadoc.TagSiwa import TagQuery, TagBody, TagBodyId 7 | from src.utils.jwt import TokenRequired 8 | from src.utils.response import Result 9 | 10 | tag = Blueprint("tag", __name__) 11 | 12 | 13 | # 新增标签 14 | @tag.route("/tag", methods=["POST"]) 15 | @siwa.doc(tags=["标签管理"], summary="新增标签", description="新增标签记得把id去掉,否则可能会导致重复id异常", 16 | body=TagBody) 17 | @TokenRequired 18 | def add(): 19 | tag = request.json 20 | 21 | data = TagModel(**tag) 22 | 23 | db.session.add(data) 24 | db.session.commit() 25 | 26 | return Result(200, "新增成功") 27 | 28 | 29 | # 删除标签 30 | @tag.route("/tag/", methods=["DELETE"]) 31 | @siwa.doc(tags=["标签管理"], summary="删除标签", description="通过ID删除指定标签") 32 | @TokenRequired 33 | def drop(id): 34 | data = TagModel.query.filter_by(id=id).first() 35 | 36 | if not data: 37 | return Result(400, "删除失败:没有此标签") 38 | 39 | db.session.delete(data) 40 | db.session.commit() 41 | 42 | return Result(200, "删除标签成功") 43 | 44 | 45 | # 批量删除 46 | @tag.route("/tag", methods=["DELETE"]) 47 | @siwa.doc(tags=["标签管理"], summary="批量删除标签", description="[1,2,3] 删除ID为1、2、3的数据", body=TagBodyId) 48 | @TokenRequired 49 | def dropBatch(): 50 | ids = request.json["ids"] 51 | 52 | for id in ids: 53 | data = TagModel.query.filter_by(id=id).first() 54 | 55 | if not data: 56 | return Result(400, f"批量删除失败:没有ID:{id}的标签") 57 | 58 | db.session.delete(data) 59 | 60 | db.session.commit() 61 | 62 | return Result(200, "批量删除标签成功") 63 | 64 | 65 | # 编辑标签 66 | @tag.route("/tag", methods=["PATCH"]) 67 | @siwa.doc(tags=["标签管理"], summary="编辑标签", body=TagBody) 68 | @TokenRequired 69 | def edit(): 70 | tag = request.json 71 | 72 | data = TagModel.query.filter_by(id=tag["id"]).update(tag) 73 | 74 | if not data: 75 | return Result(400, "编辑失败:没有此标签") 76 | 77 | db.session.commit() 78 | 79 | return Result(200, "编辑成功") 80 | 81 | 82 | # 获取标签详情 83 | @tag.route("/tag/") 84 | @siwa.doc(tags=["标签管理"], summary="获取标签详情", resp=TagBody) 85 | def get(id): 86 | data = TagModel.query.filter_by(id=id).first() 87 | 88 | if not data: 89 | return Result(400, "获取失败:没有此标签") 90 | 91 | return Result(200, "获取标签详情成功", data.to()) 92 | 93 | 94 | # 获取标签列表 95 | @tag.route("/tag") 96 | @siwa.doc(tags=["标签管理"], summary="获取标签列表", description="不传参数表示从第1页开始 每页查询5条数据", 97 | query=TagQuery) 98 | def list(): 99 | page = request.args.get("page", 1, type=int) 100 | size = request.args.get("size", 5, type=int) 101 | 102 | # 最新发布的标签在最前面排序 103 | paginate = TagModel.query.paginate(page=page, per_page=size, error_out=False) 104 | 105 | data = { 106 | "result": [k.to() for k in paginate], 107 | "page": paginate.page, 108 | "size": paginate.per_page, 109 | "pages": paginate.pages, 110 | "total": paginate.total, 111 | "prev": paginate.has_prev, 112 | "next": paginate.has_next 113 | } 114 | 115 | return Result(200, "获取标签列表成功", data) 116 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 55 | -------------------------------------------------------------------------------- /src/router/SwiperRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model import db 4 | from src.model.SwiperModel import SwiperModel 5 | from src import siwa 6 | from src.siwadoc.SwiperSiwa import SwiperQuery, SwiperBody, SwiperBodyId 7 | from src.utils.jwt import TokenRequired 8 | from src.utils.response import Result 9 | 10 | swiper = Blueprint("swiper", __name__) 11 | 12 | 13 | # 新增轮播图 14 | @swiper.route("/swiper", methods=["POST"]) 15 | @siwa.doc(tags=["轮播图管理"], summary="新增轮播图", description="新增轮播图记得把id去掉,否则可能会导致重复id异常", 16 | body=SwiperBody) 17 | @TokenRequired 18 | def add(): 19 | swiper = request.json 20 | 21 | data = SwiperModel(**swiper) 22 | 23 | db.session.add(data) 24 | db.session.commit() 25 | 26 | return Result(200, "新增成功") 27 | 28 | 29 | # 删除轮播图 30 | @swiper.route("/swiper/", methods=["DELETE"]) 31 | @siwa.doc(tags=["轮播图管理"], summary="删除轮播图", description="通过ID删除指定轮播图") 32 | @TokenRequired 33 | def drop(id): 34 | data = SwiperModel.query.filter_by(id=id).first() 35 | 36 | if not data: 37 | return Result(400, "删除失败:没有此轮播图") 38 | 39 | db.session.delete(data) 40 | db.session.commit() 41 | 42 | return Result(200, "删除轮播图成功") 43 | 44 | 45 | # 批量删除 46 | @swiper.route("/swiper", methods=["DELETE"]) 47 | @siwa.doc(tags=["轮播图管理"], summary="批量删除轮播图", description="[1,2,3] 删除ID为1、2、3的数据", body=SwiperBodyId) 48 | @TokenRequired 49 | def dropBatch(): 50 | ids = request.json["ids"] 51 | 52 | for id in ids: 53 | data = SwiperModel.query.filter_by(id=id).first() 54 | 55 | if not data: 56 | return Result(400, f"批量删除失败:没有ID:{id}的轮播图") 57 | 58 | db.session.delete(data) 59 | 60 | db.session.commit() 61 | 62 | return Result(200, "批量删除轮播图成功") 63 | 64 | 65 | # 编辑轮播图 66 | @swiper.route("/swiper", methods=["PATCH"]) 67 | @siwa.doc(tags=["轮播图管理"], summary="编辑轮播图", body=SwiperBody) 68 | @TokenRequired 69 | def edit(): 70 | swiper = request.json 71 | 72 | data = SwiperModel.query.filter_by(id=swiper["id"]).update(swiper) 73 | 74 | if not data: 75 | return Result(400, "编辑失败:没有此轮播图") 76 | 77 | db.session.commit() 78 | 79 | return Result(200, "编辑成功") 80 | 81 | 82 | # 获取轮播图详情 83 | @swiper.route("/swiper/") 84 | @siwa.doc(tags=["轮播图管理"], summary="获取轮播图详情", resp=SwiperBody) 85 | def get(id): 86 | data = SwiperModel.query.filter_by(id=id).first() 87 | 88 | if not data: 89 | return Result(400, "获取失败:没有此轮播图") 90 | 91 | return Result(200, "获取轮播图详情成功", data.to()) 92 | 93 | 94 | # 获取轮播图列表 95 | @swiper.route("/swiper") 96 | @siwa.doc(tags=["轮播图管理"], summary="获取轮播图列表", description="不传参数表示从第1页开始 每页查询5条数据", 97 | query=SwiperQuery) 98 | def list(): 99 | page = request.args.get("page", 1, type=int) 100 | size = request.args.get("size", 5, type=int) 101 | 102 | # 最新发布的轮播图在最前面排序 103 | paginate = SwiperModel.query.paginate(page=page, per_page=size, 104 | error_out=False) 105 | 106 | data = { 107 | "result": [k.to() for k in paginate], 108 | "page": paginate.page, 109 | "size": paginate.per_page, 110 | "pages": paginate.pages, 111 | "total": paginate.total, 112 | "prev": paginate.has_prev, 113 | "next": paginate.has_next 114 | } 115 | 116 | return Result(200, "获取轮播图列表成功", data) 117 | -------------------------------------------------------------------------------- /src/router/LinkRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model import db 4 | from src.model.LinkModel import LinkModel 5 | from src import siwa 6 | from src.model.TypeModel import TypeModel 7 | from src.siwadoc.LinkSiwa import LinkQuery, LinkBody, LinkBodyId 8 | from src.utils.jwt import TokenRequired 9 | from src.utils.response import Result 10 | 11 | link = Blueprint("link", __name__) 12 | 13 | 14 | # 新增网站 15 | @link.route("/link", methods=["POST"]) 16 | @siwa.doc(tags=["网站管理"], summary="新增网站", description="新增网站记得把id去掉,否则可能会导致重复id异常", 17 | body=LinkBody) 18 | @TokenRequired 19 | def add(): 20 | link = request.json 21 | 22 | data = LinkModel(**link) 23 | 24 | db.session.add(data) 25 | db.session.commit() 26 | 27 | return Result(200, "新增成功") 28 | 29 | 30 | # 删除网站 31 | @link.route("/link/", methods=["DELETE"]) 32 | @siwa.doc(tags=["网站管理"], summary="删除网站", description="通过ID删除指定网站") 33 | @TokenRequired 34 | def drop(id): 35 | data = LinkModel.query.filter_by(id=id).first() 36 | 37 | if not data: 38 | return Result(400, "删除失败:没有此网站") 39 | 40 | db.session.delete(data) 41 | db.session.commit() 42 | 43 | return Result(200, "删除网站成功") 44 | 45 | 46 | # 批量删除 47 | @link.route("/link", methods=["DELETE"]) 48 | @siwa.doc(tags=["网站管理"], summary="批量删除网站", description="[1,2,3] 删除ID为1、2、3的数据", body=LinkBodyId) 49 | @TokenRequired 50 | def dropBatch(): 51 | ids = request.json["ids"] 52 | 53 | for id in ids: 54 | data = LinkModel.query.filter_by(id=id).first() 55 | 56 | if not data: 57 | return Result(400, f"批量删除失败:没有ID:{id}的网站") 58 | 59 | db.session.delete(data) 60 | 61 | db.session.commit() 62 | 63 | return Result(200, "批量删除网站成功") 64 | 65 | 66 | # 编辑网站 67 | @link.route("/link", methods=["PATCH"]) 68 | @siwa.doc(tags=["网站管理"], summary="编辑网站", body=LinkBody) 69 | @TokenRequired 70 | def edit(): 71 | link = request.json 72 | 73 | data = LinkModel.query.filter_by(id=link["id"]).update(link) 74 | 75 | if not data: 76 | return Result(400, "编辑失败:没有此网站") 77 | 78 | db.session.commit() 79 | 80 | return Result(200, "编辑成功") 81 | 82 | 83 | # 获取网站详情 84 | @link.route("/link/") 85 | @siwa.doc(tags=["网站管理"], summary="获取网站详情", resp=LinkBody) 86 | def get(id): 87 | data = LinkModel.query.filter_by(id=id).first() 88 | 89 | if not data: 90 | return Result(400, "获取失败:没有此网站") 91 | 92 | data = data.to() 93 | data["type"] = TypeModel.query.filter_by(id=data["type"]).first().to()["name"] 94 | 95 | return Result(200, "获取网站详情成功", data) 96 | 97 | 98 | # 获取网站列表 99 | @link.route("/link") 100 | @siwa.doc(tags=["网站管理"], summary="获取网站列表", description="不传参数表示从第1页开始 每页查询5条数据", 101 | query=LinkQuery) 102 | def list(): 103 | page = request.args.get("page", 1, type=int) 104 | size = request.args.get("size", 5, type=int) 105 | 106 | # 最新发布的网站在最前面排序 107 | paginate = LinkModel.query.order_by(LinkModel.createtime.desc()).paginate(page=page, per_page=size, error_out=False) 108 | 109 | result = [k.to() for k in paginate] 110 | 111 | for k in result: 112 | k["type"] = TypeModel.query.filter_by(id=k["type"]).first().to()["name"] 113 | 114 | data = { 115 | "result": result, 116 | "page": paginate.page, 117 | "size": paginate.per_page, 118 | "pages": paginate.pages, 119 | "total": paginate.total, 120 | "prev": paginate.has_prev, 121 | "next": paginate.has_next 122 | } 123 | 124 | return Result(200, "获取网站列表成功", data) 125 | -------------------------------------------------------------------------------- /src/router/ResRouter.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Blueprint, request 4 | 5 | from src import app 6 | from src.utils.file import randomName 7 | from src.utils.jwt import TokenRequired 8 | from src.utils.response import Result 9 | 10 | from src import siwa 11 | 12 | # 创建蓝图 13 | res = Blueprint("res", __name__) 14 | 15 | from src.siwadoc.ResSiwa import ResBody, FileBody 16 | 17 | 18 | # 文件上传 19 | @res.route("/file", methods=["POST"]) 20 | @siwa.doc(tags=["文件管理"], summary="批量上传文件", 21 | description="默认上传到default目录,可以通过target指定文件上传的位置", 22 | files={'file': {"required": True, "single": False}}, 23 | form=ResBody) 24 | @TokenRequired 25 | def upload(): 26 | from datetime import datetime 27 | from werkzeug.utils import secure_filename 28 | 29 | # 获取上传的文件列表 30 | files = request.files.getlist('file') 31 | 32 | # 目标存放文件:默认为image 33 | tagger = request.form.get("target", "default", type=str) 34 | 35 | # 获取年、月份 36 | date = datetime.now() 37 | year, month = str(date.year), str(date.month) 38 | 39 | # 项目根目录 40 | path = app.root_path 41 | # 获取项目资源存放位置 42 | upload = app.config["UPLOAD_PATH"] 43 | uploadPath = path + os.path.join(upload, tagger) 44 | 45 | # 根据年、月份来命名,创建文件目录 46 | # 判断该目录是否存在, 如果不存在则自动创建 47 | os.makedirs(uploadPath, exist_ok=True) 48 | os.makedirs(os.path.join(uploadPath, year), exist_ok=True) 49 | os.makedirs(os.path.join(uploadPath, year, month), exist_ok=True) 50 | dirPath = f"{tagger}/{year}/{month}/" 51 | 52 | # 保存所有文件的 URL 53 | urls = [] 54 | 55 | for file in files: 56 | # 生成唯一文件名 57 | fileName = secure_filename(randomName(file.filename)) 58 | 59 | # 将文件上传到指定的目录 60 | file.save(os.path.join(uploadPath, year, month, fileName)) 61 | 62 | # 拼接文件路径 63 | url = f"{request.host_url}{dirPath}{fileName}" 64 | urls.append(url) 65 | 66 | return Result(200, "文件上传成功", urls) 67 | 68 | 69 | # 删除文件 70 | @res.route("/file", methods=["DELETE"]) 71 | @siwa.doc(tags=["文件管理"], summary="删除文件", body=FileBody, 72 | description="根据文件的路径来删除") 73 | @TokenRequired 74 | def delete(): 75 | files = request.json["files"] 76 | 77 | try: 78 | for file in files: 79 | os.remove(app.root_path + app.config["UPLOAD_PATH"] + file) 80 | except FileNotFoundError as e: 81 | return Result(500, str(e)) 82 | 83 | return Result(200, "删除文件成功") 84 | 85 | 86 | # 获取文件列表 87 | @res.route("/file", methods=["GET"]) 88 | @siwa.doc(tags=["文件管理"], summary="获取文件列表") 89 | @TokenRequired 90 | def list(): 91 | upload = app.config["UPLOAD_PATH"][1:] 92 | dirs = get_directory_structure(os.path.join(app.root_path, upload)) 93 | return Result(200, "获取文件列表成功", dirs) 94 | 95 | 96 | def get_directory_structure(path): 97 | structure = [] 98 | if os.path.isdir(path): 99 | items = os.listdir(path) 100 | 101 | for item in items: 102 | item_path = os.path.join(path, item) 103 | # 如果是文件 104 | if os.path.isdir(item_path): 105 | # 如果没有子目录 106 | if hasDirFile("dir", item_path): 107 | children = get_directory_structure(item_path) 108 | directory = { 109 | "children": children, 110 | "list": [], 111 | "name": item 112 | } 113 | else: 114 | list = get_directory_structure(item_path) 115 | directory = { 116 | "children": [], 117 | "list": list, 118 | "name": item 119 | } 120 | 121 | structure.append(directory) 122 | else: 123 | structure.append(item) 124 | return structure 125 | 126 | 127 | # 判断指定目录中有没有目录或文件 128 | def hasDirFile(mark, directory): 129 | if mark == "dir": 130 | # 判断当前目录中是否有子目录 131 | for item in os.listdir(directory): 132 | if os.path.isdir(os.path.join(directory, item)): 133 | return True 134 | return False 135 | elif mark == "file": 136 | # 判断当前目录中是否有文件 137 | for item in os.listdir(directory): 138 | if os.path.isfile(os.path.join(directory, item)): 139 | return True 140 | return False 141 | -------------------------------------------------------------------------------- /src/router/CateRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | from src.model import db 4 | from src.model.CateModel import CateModel 5 | from src import siwa 6 | from src.siwadoc.CateSiwa import CateQuery, CateBody, CateBodyId 7 | from src.utils.jwt import TokenRequired 8 | from src.utils.response import Result 9 | 10 | cate = Blueprint("cate", __name__) 11 | 12 | 13 | # 新增分类 14 | @cate.route("/cate", methods=["POST"]) 15 | @siwa.doc(tags=["分类管理"], summary="新增分类", 16 | description="level的值为0表示新增一级分类,值为其他分类的id表示这个分类为二级。比如分类A的id为3,如果将分类B的level设置为分类A的ID(3),那么分类B就是分类A的子分类", 17 | body=CateBody) 18 | @TokenRequired 19 | def add(): 20 | cate = request.json 21 | 22 | data = CateModel(**cate) 23 | 24 | db.session.add(data) 25 | db.session.commit() 26 | 27 | return Result(200, "新增成功") 28 | 29 | 30 | # 删除分类 31 | @cate.route("/cate/", methods=["DELETE"]) 32 | @siwa.doc(tags=["分类管理"], summary="删除分类", description="通过ID删除指定分类") 33 | @TokenRequired 34 | def drop(id): 35 | data = CateModel.query.filter_by(id=id).first() 36 | 37 | if not data: 38 | return Result(400, "删除失败:没有此分类") 39 | 40 | # 判断需要删除的分类有没有子分类 41 | size = CateModel.query.filter_by(level=id).count() 42 | if size != 0: return Result(400, "请先删除该分类中的所有子分类") 43 | 44 | db.session.delete(data) 45 | db.session.commit() 46 | 47 | return Result(200, "删除分类成功") 48 | 49 | 50 | # 批量删除 51 | @cate.route("/cate", methods=["DELETE"]) 52 | @siwa.doc(tags=["分类管理"], summary="批量删除分类", description="[1,2,3] 删除ID为1、2、3的数据", body=CateBodyId) 53 | @TokenRequired 54 | def dropBatch(): 55 | ids = request.json["ids"] 56 | 57 | for id in ids: 58 | data = CateModel.query.filter_by(id=id).first() 59 | 60 | if not data: 61 | return Result(400, f"批量删除失败:没有ID:{id}的分类") 62 | 63 | db.session.delete(data) 64 | 65 | db.session.commit() 66 | 67 | return Result(200, "批量删除分类成功") 68 | 69 | 70 | # 编辑分类 71 | @cate.route("/cate", methods=["PATCH"]) 72 | @siwa.doc(tags=["分类管理"], summary="编辑分类", body=CateBody) 73 | @TokenRequired 74 | def edit(): 75 | cate = request.json 76 | 77 | data = CateModel.query.filter_by(id=cate["id"]) 78 | 79 | if not data: 80 | return Result(400, "编辑失败:没有此分类") 81 | 82 | data.update({ 83 | "name": cate["name"], 84 | "icon": cate["icon"], 85 | "url": cate["url"], 86 | "mark": cate["mark"], 87 | "level": cate["level"] 88 | }) 89 | 90 | db.session.commit() 91 | 92 | return Result(200, "编辑成功") 93 | 94 | 95 | # 获取分类详情 96 | @cate.route("/cate/") 97 | @siwa.doc(tags=["分类管理"], summary="获取分类详情", resp=CateBody) 98 | def get(id): 99 | data = CateModel.query.filter_by(id=id).first() 100 | 101 | if not data: 102 | return Result(400, "获取失败:没有此分类") 103 | 104 | data = data.to() 105 | data['children'] = [] 106 | 107 | list = [k.to() for k in CateModel.query.all()] 108 | 109 | # 查询该分类下的所有子分类 110 | for cate in list: 111 | if cate['level'] == id: 112 | data['children'].append(cate) 113 | 114 | # 如果为空, 就不让他显示children 115 | if len(data['children']) == 0: 116 | del data['children'] 117 | 118 | return Result(200, "获取分类详情成功", data) 119 | 120 | 121 | # 获取分类列表 122 | @cate.route("/cate") 123 | @siwa.doc(tags=["分类管理"], summary="获取分类列表", description="不传参数表示从第1页开始 每页查询5条数据", 124 | query=CateQuery) 125 | def list(): 126 | page = request.args.get("page", 1, type=int) 127 | size = request.args.get("size", 5, type=int) 128 | 129 | # 最新发布的分类在最前面排序 130 | paginate = CateModel.query.filter_by(level=0).paginate(page=page, per_page=size, error_out=False) 131 | list = CateModel.query.all() 132 | 133 | def tree(pid, data): 134 | children = [] 135 | 136 | for cate in data: 137 | if cate['level'] == pid: 138 | cate['children'] = tree(cate['id'], data) 139 | 140 | # 如果为空, 就不让他显示children 141 | if len(cate['children']) == 0: 142 | del cate['children'] 143 | 144 | children.append(cate) 145 | 146 | return children 147 | 148 | data = { 149 | "result": tree(0, [k.to() for k in list]), 150 | "page": paginate.page, 151 | "size": paginate.per_page, 152 | "pages": paginate.pages, 153 | "total": paginate.total, 154 | "prev": paginate.has_prev, 155 | "next": paginate.has_next 156 | } 157 | 158 | return Result(200, "获取分类列表成功", data) 159 | -------------------------------------------------------------------------------- /src/router/CommentRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | from sqlalchemy import desc 3 | 4 | from src.model import db 5 | from src.model.ArticleModel import ArticleModel 6 | from src.model.CommentModel import CommentModel 7 | from src import siwa 8 | from src.siwadoc.CommentSiwa import CommentQuery, CommentBody, CommentBodyId 9 | from src.utils.jwt import TokenRequired 10 | from src.utils.response import Result 11 | 12 | comment = Blueprint("comment", __name__) 13 | 14 | 15 | # 新增评论 16 | @comment.route("/comment", methods=["POST"]) 17 | @siwa.doc(tags=["评论管理"], summary="新增评论", description="新增评论记得把id去掉,否则可能会导致重复id异常", 18 | body=CommentBody) 19 | def add(): 20 | comment = request.json 21 | 22 | data = CommentModel(**comment) 23 | 24 | db.session.add(data) 25 | db.session.commit() 26 | 27 | return Result(200, "新增成功") 28 | 29 | 30 | # 删除评论 31 | @comment.route("/comment/", methods=["DELETE"]) 32 | @siwa.doc(tags=["评论管理"], summary="删除评论", description="通过ID删除指定评论") 33 | @TokenRequired 34 | def drop(id): 35 | data = CommentModel.query.filter_by(id=id).first() 36 | 37 | if not data: 38 | return Result(400, "删除失败:没有此评论") 39 | 40 | db.session.delete(data) 41 | db.session.commit() 42 | 43 | return Result(200, "删除评论成功") 44 | 45 | 46 | # 批量删除 47 | @comment.route("/comment", methods=["DELETE"]) 48 | @siwa.doc(tags=["评论管理"], summary="批量删除评论", description="[1,2,3] 删除ID为1、2、3的数据", body=CommentBodyId) 49 | @TokenRequired 50 | def dropBatch(): 51 | ids = request.json["ids"] 52 | 53 | for id in ids: 54 | data = CommentModel.query.filter_by(id=id).first() 55 | 56 | if not data: 57 | return Result(400, f"批量删除失败:没有ID:{id}的评论") 58 | 59 | db.session.delete(data) 60 | 61 | db.session.commit() 62 | 63 | return Result(200, "批量删除评论成功") 64 | 65 | 66 | # 编辑评论 67 | @comment.route("/comment", methods=["PATCH"]) 68 | @siwa.doc(tags=["评论管理"], summary="编辑评论", body=CommentBody) 69 | @TokenRequired 70 | def edit(): 71 | comment = request.json 72 | 73 | data = CommentModel.query.filter_by(id=comment["id"]).update(comment) 74 | 75 | if not data: 76 | return Result(400, "编辑失败:没有此评论") 77 | 78 | db.session.commit() 79 | 80 | return Result(200, "编辑成功") 81 | 82 | 83 | # 审核评论 84 | @comment.route("/comment/audit/", methods=["PATCH"]) 85 | @TokenRequired 86 | def audit(id): 87 | data = CommentModel.query.filter_by(id=id).update({'audit': 1}) 88 | 89 | if not data: 90 | return Result(400, "审核失败:没有此评论") 91 | 92 | db.session.commit() 93 | 94 | return Result(200, "审核通过") 95 | 96 | 97 | # 获取评论详情 98 | @comment.route("/comment/") 99 | @siwa.doc(tags=["评论管理"], summary="获取评论详情", resp=CommentBody) 100 | def get(id): 101 | data = CommentModel.query.filter_by(id=id).first() 102 | 103 | if not data: 104 | return Result(400, "获取失败:没有此评论") 105 | 106 | return Result(200, "获取评论详情成功", data.to()) 107 | 108 | 109 | # 获取评论列表 110 | @comment.route("/comment") 111 | @siwa.doc(tags=["评论管理"], summary="获取评论列表", description="不传参数表示从第1页开始 每页查询5条数据", 112 | query=CommentQuery) 113 | def list(): 114 | page = request.args.get("page", 1, type=int) 115 | size = request.args.get("size", 5, type=int) 116 | 117 | # 最新发布的评论在最前面排序 118 | paginate = CommentModel.query.order_by(desc(CommentModel.createtime)).paginate(page=page, per_page=size, 119 | error_out=False) 120 | 121 | result = [] 122 | 123 | # 给每个评论绑定对应的文章标题 124 | for data in paginate: 125 | comment = data.to() 126 | 127 | art = ArticleModel.query.filter_by(id=comment["aid"]).first() 128 | if art is not None: 129 | comment["article"] = art.title 130 | else: 131 | comment["article"] = "子级" 132 | result.append(comment) 133 | 134 | data = { 135 | "result": result, 136 | "page": paginate.page, 137 | "size": paginate.per_page, 138 | "pages": paginate.pages, 139 | "total": paginate.total, 140 | "prev": paginate.has_prev, 141 | "next": paginate.has_next 142 | } 143 | 144 | return Result(200, "获取评论列表成功", data) 145 | 146 | 147 | # 获取指定文章中的所有评论 148 | @comment.route("/comment/article/") 149 | @siwa.doc(tags=["评论管理"], summary="获取指定文章中的评论", description="传入指定文章的ID") 150 | def articleComment(aid): 151 | # 最新发布的评论在最前面排序 152 | list = CommentModel.query.filter_by(aid=aid, audit=1).order_by(desc(CommentModel.createtime)).all() 153 | 154 | data = build_hierarchy([k.to() for k in list], 0) 155 | 156 | return Result(200, "获取指定文章评论成功", data) 157 | 158 | 159 | def build_hierarchy(data, rid): 160 | result = [] 161 | 162 | for item in data: 163 | if item['audit'] == 1: 164 | if item['rid'] == rid: 165 | children = build_hierarchy(data, item['id']) 166 | if children: 167 | item['children'] = children 168 | result.append(item) 169 | 170 | return result 171 | -------------------------------------------------------------------------------- /src/router/UserRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | 3 | import jwt 4 | from src import app 5 | from src.model import db 6 | from src.model.UserModel import UserModel 7 | from src import siwa 8 | from src.siwadoc.UserSiwa import UserQuery, LoginBody, UserBody, UserBodyId, UserAdminPass 9 | from src.utils.jwt import TokenRequired 10 | from src.utils.response import Result 11 | from datetime import datetime, timedelta 12 | from hashlib import md5 13 | 14 | user = Blueprint("user", __name__) 15 | 16 | 17 | # 用户登录 18 | @user.route("/login", methods=["POST"]) 19 | @siwa.doc(tags=["用户管理"], summary="用户登录", body=LoginBody) 20 | def login(): 21 | user = request.json 22 | 23 | data = UserModel.query.filter_by(username=user["username"]).first().to() 24 | if not data: return Result(400, "登录失败:没有此用户") 25 | 26 | # 给密码加密后与数据库中的做对比 27 | user["password"] = md5(user["password"].encode()).hexdigest() 28 | 29 | if user["password"] != data["password"]: return Result(400, "登录失败:用户密码错误") 30 | 31 | # 加载配置信息 32 | expire = app.config["EXPIRE"] 33 | secretkey = app.config["SECRET_KEY"] 34 | algorithm = app.config["ALGORITHM"] 35 | 36 | payload = { 37 | "exp": datetime.utcnow() + timedelta(seconds=expire) 38 | } 39 | 40 | # 生成token 41 | token = jwt.encode(payload, secretkey, algorithm) 42 | 43 | # 不返回密码字段 44 | del data["password"] 45 | 46 | return Result(200, "登录成功", {"token": token, "user": data}) 47 | 48 | 49 | # 新增用户 50 | @user.route("/user", methods=["POST"]) 51 | @siwa.doc(tags=["用户管理"], summary="新增用户", description="新增用户记得把id去掉,否则可能会导致重复id异常", 52 | body=UserBody) 53 | @TokenRequired 54 | def add(): 55 | user = request.json 56 | 57 | # 密码加密处理 58 | user["password"] = md5(user["password"].encode()).hexdigest() 59 | 60 | data = UserModel(**user) 61 | 62 | db.session.add(data) 63 | db.session.commit() 64 | 65 | return Result(200, "新增成功") 66 | 67 | 68 | # 删除用户 69 | @user.route("/user/", methods=["DELETE"]) 70 | @siwa.doc(tags=["用户管理"], summary="删除用户", description="通过ID删除指定用户") 71 | @TokenRequired 72 | def drop(id): 73 | data = UserModel.query.filter_by(id=id).first() 74 | 75 | if not data: 76 | return Result(400, "删除失败:没有此用户") 77 | 78 | db.session.delete(data) 79 | db.session.commit() 80 | 81 | return Result(200, "删除用户成功") 82 | 83 | 84 | # 批量删除 85 | @user.route("/user", methods=["DELETE"]) 86 | @siwa.doc(tags=["用户管理"], summary="批量删除用户", description="[5,2,3] 删除ID为1、2、3的数据", body=UserBodyId) 87 | @TokenRequired 88 | def dropBatch(): 89 | ids = request.json["ids"] 90 | 91 | for id in ids: 92 | data = UserModel.query.filter_by(id=id).first() 93 | 94 | if not data: 95 | return Result(400, f"批量删除失败:没有ID:{id}的用户") 96 | 97 | db.session.delete(data) 98 | 99 | db.session.commit() 100 | 101 | return Result(200, "批量删除用户成功") 102 | 103 | 104 | # 编辑用户 105 | @user.route("/user", methods=["PATCH"]) 106 | @siwa.doc(tags=["用户管理"], summary="编辑用户", body=UserBody) 107 | @TokenRequired 108 | def edit(): 109 | user = request.json 110 | 111 | data = UserModel.query.filter_by(id=user["id"]).update(user) 112 | 113 | if not data: 114 | return Result(400, "编辑失败:没有此用户") 115 | 116 | db.session.commit() 117 | 118 | return Result(200, "编辑成功") 119 | 120 | 121 | # 获取用户详情 122 | @user.route("/user/") 123 | @siwa.doc(tags=["用户管理"], summary="获取用户详情", resp=UserBody) 124 | def get(id): 125 | data = UserModel.query.filter_by(id=id).first().to() 126 | 127 | if not data: 128 | return Result(400, "获取失败:没有此用户") 129 | 130 | # 不返回密码字段 131 | del data["password"] 132 | 133 | return Result(200, "获取用户详情成功", data) 134 | 135 | 136 | # 获取用户列表 137 | @user.route("/user") 138 | @siwa.doc(tags=["用户管理"], summary="获取用户列表", description="不传参数表示从第1页开始 每页查询5条数据", 139 | query=UserQuery) 140 | def list(): 141 | page = request.args.get("page", 1, type=int) 142 | size = request.args.get("size", 5, type=int) 143 | 144 | # 最新发布的用户在最前面排序 145 | paginate = UserModel.query.order_by(UserModel.createtime.desc()).paginate(page=page, per_page=size, 146 | error_out=False) 147 | 148 | result = [k.to() for k in paginate] 149 | 150 | # 删除所有的密码字段 151 | for data in result: 152 | del data["password"] 153 | 154 | data = { 155 | "result": result, 156 | "page": paginate.page, 157 | "size": paginate.per_page, 158 | "pages": paginate.pages, 159 | "total": paginate.total, 160 | "prev": paginate.has_prev, 161 | "next": paginate.has_next 162 | } 163 | 164 | return Result(200, "获取用户列表成功", data) 165 | 166 | 167 | # 修改管理员密码 168 | @user.route("/user/admin", methods=["PATCH"]) 169 | @siwa.doc(tags=["用户管理"], summary="修改管理员密码", body=UserAdminPass) 170 | @TokenRequired 171 | def editAdminPass(): 172 | user = request.json 173 | 174 | # 查找管理员账号是否存在 175 | data = UserModel.query.filter_by(username=user["username"]).first() 176 | 177 | if not data: 178 | return Result(400, "编辑失败:没有此用户") 179 | 180 | # 密码加密处理 181 | user["oldPassword"] = md5(user["oldPassword"].encode()).hexdigest() 182 | 183 | # 判断旧密码是否正确 184 | if user["oldPassword"] == data.password: 185 | data.password = md5(user["newPassword"].encode()).hexdigest() 186 | db.session.commit() 187 | 188 | return Result(200, "编辑成功") 189 | else: 190 | return Result(400, "编辑失败:旧密码不正确,请重新输入") 191 | -------------------------------------------------------------------------------- /src/router/ProjectRouter.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import platform 3 | from datetime import datetime, date 4 | 5 | from flask import Blueprint, request 6 | 7 | from src.model.system.ProjectModel import ProjectModel 8 | from src.model.system.LayoutModel import LayoutModel 9 | from src.utils.jwt import TokenRequired 10 | from src.utils.response import Result 11 | 12 | from src import siwa 13 | 14 | # 创建蓝图 15 | project = Blueprint("project", __name__) 16 | 17 | from src.siwadoc.ProjectSiwa import ProjectBody 18 | 19 | 20 | # 获取系统配置 21 | @project.route("/project/system", methods=["GET"]) 22 | @siwa.doc(tags=["全局配置"], summary="获取系统配置", description="获取系统配置:CPU、磁盘、IP、系统等等") 23 | @TokenRequired 24 | def getSystem(): 25 | # 获取CPU信息 26 | cpu = psutil.cpu_percent(interval=1, percpu=True) 27 | cpu = round(sum(cpu) / len(cpu), 2) 28 | 29 | # 获取磁盘信息 30 | disk = psutil.disk_usage('/') 31 | diskTotal = round(disk.total / 1_073_741_824) # 总容量 32 | diskUsed = round(disk.used / 1_073_741_824) # 已使用容量 33 | diskFree = round(disk.free / 1073741824) # 可用容量 34 | diskPercent = disk.percent # 已用容量百分比 35 | 36 | # 获取内存信息 37 | memory = psutil.virtual_memory() 38 | memoryTotal = round(memory.total / 1_073_741_824, 2) # 获取内存总量 39 | memoryAvailable = round(memory.available / 1_073_741_824, 2) # 获取可用内存 40 | memoryPercent = memory.percent # 获取内存已使用百分比 41 | memoryUsed = round(memory.used / 1_073_741_824, 2) # 获取已使用内存 42 | 43 | # 系统开机时间 44 | boot_time = psutil.boot_time() 45 | boot_time_datetime = datetime.fromtimestamp(boot_time) 46 | 47 | # 获取系统不间断运行天数 48 | date1 = date(boot_time_datetime.year, boot_time_datetime.month, boot_time_datetime.day) # 系统开机时间 49 | date2 = date(datetime.now().year, datetime.now().month, datetime.now().day) 50 | # 计算日期差 51 | delta = date2 - date1 52 | # 获取天数差 53 | days = delta.days 54 | 55 | # 操作系统信息 56 | name = platform.system() # 名称 57 | version = platform.release() # 版本 58 | 59 | # 获取系统IP地址 60 | ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) 61 | 62 | data = { 63 | "cpu": cpu, 64 | "disk": {"diskTotal": diskTotal, "diskUsed": diskUsed, "diskFree": diskFree, "diskPercent": diskPercent}, 65 | "memory": {"memoryTotal": memoryTotal, "memoryAvailable": memoryAvailable, "memoryPercent": memoryPercent, 66 | "memoryUsed": memoryUsed}, 67 | "boot_time_datetime": boot_time_datetime, 68 | "name": name, 69 | "run": days, 70 | "ip": ip 71 | } 72 | 73 | return Result(200, "获取系统配置成功", data) 74 | 75 | 76 | # 获取网站配置 77 | @project.route('/project/web', methods=['GET']) 78 | @siwa.doc(tags=["全局配置"], summary="获取网站配置", description="获取网站配置:标题、描述、LOGO、关键词等等") 79 | def getSite(): 80 | p = ProjectModel() 81 | 82 | p.keyword = (",").join(p.keyword) 83 | 84 | data = { 85 | 'url': p.url, 86 | 'favicon': p.favicon, 87 | 'title': p.title, 88 | 'subhead': p.subhead, 89 | 'light_logo': p.light_logo, 90 | 'dark_logo': p.dark_logo, 91 | 'description': p.description, 92 | 'keyword': p.keyword, 93 | 'footer': p.footer, 94 | 'font': p.font, 95 | 'social': p.social, 96 | 'covers': p.covers 97 | } 98 | 99 | return Result(200, "获取网站配置成功", data) 100 | 101 | 102 | # 修改网站配置 103 | @project.route('/project/web', methods=['PATCH']) 104 | @siwa.doc(tags=["全局配置"], summary="修改网站配置", description="修改网站配置:标题、描述、LOGO、关键词等等", 105 | body=ProjectBody) 106 | @TokenRequired 107 | def editSite(): 108 | web = request.json 109 | 110 | web["keyword"] = web["keyword"].split(",") 111 | 112 | data = f"""class ProjectModel(object): 113 | url = "{web['url']}" # 网站链接 114 | favicon = "{web['favicon']}" # 网站图标 115 | title = "{web['title']}" # 网站标题 116 | subhead = "{web['subhead']}" # 网站副标题 117 | light_logo = "{web['light_logo']}" # 白天主题logo 118 | dark_logo = "{web['dark_logo']}" # 暗黑主题logo 119 | description = "{web['description']}" # 网站描述 120 | keyword = {web['keyword']} # 网站SEO关键词 121 | footer = "{web['footer']}" # 底部描述 122 | font = "{web['font']}" # 字体链接 123 | social = {web['social']} # 社交账号 124 | covers = {web['covers']} # 随机文章封面 125 | """ 126 | 127 | with open("src/model/system/ProjectModel.py", "w", encoding="utf8") as f: 128 | f.write(data) 129 | 130 | return Result(200, "修改网站配置成功") 131 | 132 | 133 | # 获取布局配置 134 | @project.route('/project/layout', methods=['GET']) 135 | @siwa.doc(tags=["全局配置"], summary="获取布局配置", description="获取布局配置:主题、文章列表、侧边栏、打字机等等") 136 | def getLayout(): 137 | l = LayoutModel() 138 | 139 | data = { 140 | 'isArticleLayout': l.isArticleLayout, 141 | 'rightSidebar': l.rightSidebar, 142 | 'swiperImage': l.swiperImage, 143 | 'swiperText': l.swiperText, 144 | } 145 | 146 | return Result(200, "获取布局配置成功", data) 147 | 148 | 149 | # 修改布局配置 150 | @project.route('/project/layout', methods=['PATCH']) 151 | @siwa.doc(tags=["全局配置"], summary="修改布局配置", description="修改布局配置:主题、文章列表、侧边栏、打字机等等", 152 | body=ProjectBody) 153 | @TokenRequired 154 | def editLayout(): 155 | layout = request.json 156 | 157 | data = f"""class LayoutModel(object): 158 | isArticleLayout = "{layout['isArticleLayout']}" 159 | rightSidebar = {layout['rightSidebar']} 160 | swiperImage = "{layout['swiperImage']}" 161 | swiperText = {layout['swiperText']} 162 | """ 163 | 164 | with open("src/model/system/LayoutModel.py", "w", encoding="utf8") as f: 165 | f.write(data) 166 | 167 | return Result(200, "修改布局配置成功") 168 | -------------------------------------------------------------------------------- /src/router/ArticleRouter.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | from sqlalchemy import text 3 | 4 | from src.model import db 5 | from src.model.ArticleModel import ArticleModel 6 | from src.model.CateModel import CateModel 7 | from src import siwa 8 | from src.model.CommentModel import CommentModel 9 | from src.siwadoc.ArticleSiwa import ArticleQuery, ArticleBody, ArticleBodyId 10 | from src.utils.jwt import TokenRequired 11 | from src.utils.response import Result 12 | 13 | article = Blueprint("article", __name__) 14 | 15 | 16 | # 新增文章 17 | @article.route("/article", methods=["POST"]) 18 | @siwa.doc(tags=["文章管理"], summary="新增文章", description="新增文章记得把id去掉,否则可能会导致重复id异常", 19 | body=ArticleBody) 20 | @TokenRequired 21 | def add(): 22 | article = request.json 23 | 24 | # 将分类数组转换为字符串 25 | article["cids"] = ",".join([str(k) for k in article["cids"]]) 26 | 27 | data = ArticleModel(**article) 28 | 29 | db.session.add(data) 30 | db.session.commit() 31 | 32 | return Result(200, "新增成功") 33 | 34 | 35 | # 删除文章 36 | @article.route("/article/", methods=["DELETE"]) 37 | @siwa.doc(tags=["文章管理"], summary="删除文章", description="通过ID删除指定文章") 38 | @TokenRequired 39 | def drop(id): 40 | data = ArticleModel.query.filter_by(id=id).first() 41 | 42 | if not data: 43 | return Result(400, "删除失败:没有此文章") 44 | 45 | db.session.delete(data) 46 | db.session.commit() 47 | 48 | return Result(200, "删除文章成功") 49 | 50 | 51 | # 批量删除 52 | @article.route("/article", methods=["DELETE"]) 53 | @siwa.doc(tags=["文章管理"], summary="批量删除文章", description="[1,2,3] 删除ID为1、2、3的数据", body=ArticleBodyId) 54 | @TokenRequired 55 | def dropBatch(): 56 | ids = request.json["ids"] 57 | 58 | for id in ids: 59 | data = ArticleModel.query.filter_by(id=id).first() 60 | 61 | if not data: 62 | return Result(400, f"批量删除失败:没有ID:{id}的文章") 63 | 64 | db.session.delete(data) 65 | 66 | db.session.commit() 67 | 68 | return Result(200, "批量删除文章成功") 69 | 70 | 71 | # 编辑文章 72 | @article.route("/article", methods=["PATCH"]) 73 | @siwa.doc(tags=["文章管理"], summary="编辑文章", body=ArticleBody) 74 | @TokenRequired 75 | def edit(): 76 | article = request.json 77 | 78 | # 将分类数组转换为字符串 79 | article["cids"] = ",".join([str(k) for k in article["cids"]]) 80 | 81 | data = ArticleModel.query.filter_by(id=article["id"]) 82 | 83 | if not data: 84 | return Result(400, "编辑失败:没有此文章") 85 | 86 | data.update({ 87 | "cids": article["cids"], 88 | "content": article["content"], 89 | "cover": article["cover"], 90 | "description": article["description"], 91 | "tag": article["tag"], 92 | "title": article["title"], 93 | "view": article["view"] 94 | }) 95 | 96 | db.session.commit() 97 | 98 | return Result(200, "编辑成功") 99 | 100 | 101 | # 获取文章详情 102 | @article.route("/article/") 103 | @siwa.doc(tags=["文章管理"], summary="获取文章详情", resp=ArticleBody) 104 | def get(id): 105 | data = ArticleModel.query.filter_by(id=id).first() 106 | 107 | # 获取评论数量 108 | comment = CommentModel.query.filter_by(aid=id).all() 109 | 110 | if not data: 111 | return Result(400, "获取失败:没有此文章") 112 | 113 | article = data.to() 114 | article["cate"] = [] 115 | # 将cid字符串转换为列表,并将字符串列表转换为数值列表 116 | article["cids"] = [int(k) for k in article["cids"].split(",")] 117 | 118 | # 循环每一项的分类id,找出文章所对应的那一个 119 | for cid in article["cids"]: 120 | cate = CateModel.query.filter_by(id=cid).first().to() 121 | article["cate"].append(cate) 122 | 123 | # 设置评论数量 124 | article["comment"] = len(comment) 125 | 126 | # 查询上一个文章 127 | prev = ArticleModel.query.filter(ArticleModel.createtime < article["createtime"]).order_by( 128 | ArticleModel.createtime.desc()).first() 129 | # 查询下一个文章 130 | next = ArticleModel.query.filter(ArticleModel.createtime > article["createtime"]).order_by( 131 | ArticleModel.createtime.asc()).first() 132 | 133 | if prev is None: 134 | result = {**article, "prev": prev, "next": next.to()} 135 | elif next is None: 136 | result = {**article, "prev": prev.to(), "next": next} 137 | else: 138 | result = {**article, "prev": prev.to(), "next": next.to()} 139 | 140 | return Result(200, "获取文章详情成功", result) 141 | 142 | 143 | # 获取文章列表 144 | @article.route("/article") 145 | @siwa.doc(tags=["文章管理"], summary="获取文章列表", description="不传参数表示从第1页开始 每页查询5条数据", 146 | query=ArticleQuery) 147 | def list(): 148 | page = request.args.get("page", 1, type=int) 149 | size = request.args.get("size", 5, type=int) 150 | 151 | # 最新发布的文章在最前面排序 152 | paginate = ArticleModel.query.order_by(ArticleModel.createtime.desc()).paginate(page=page, per_page=size, 153 | error_out=False) 154 | 155 | result = [] 156 | 157 | # 关联分类表 158 | for article in paginate: 159 | article = article.to() 160 | 161 | # 获取评论数量 162 | comment = CommentModel.query.filter_by(aid=article["id"]).all() 163 | article["comment"] = len(comment) 164 | 165 | article["cate"] = [] 166 | # 将cid字符串转换为列表,并将字符串列表转换为数值列表 167 | article["cids"] = [int(k) for k in article["cids"].split(",")] 168 | 169 | # 循环每一项的分类id,找出文章所对应的那一个 170 | for id in article["cids"]: 171 | cate = CateModel.query.filter_by(id=id).first().to() 172 | article["cate"].append(cate) 173 | 174 | result.append(article) 175 | 176 | data = { 177 | "result": result, 178 | "page": paginate.page, 179 | "size": paginate.per_page, 180 | "pages": paginate.pages, 181 | "total": paginate.total, 182 | "prev": paginate.has_prev, 183 | "next": paginate.has_next 184 | } 185 | 186 | return Result(200, "获取文章列表成功", data) 187 | 188 | 189 | # 随机五篇文章 190 | @article.route("/article/random") 191 | @siwa.doc(tags=["文章管理"], summary="获取随机五篇文章") 192 | def randomArticle(): 193 | query = text( 194 | "select * from article order by rand() limit 5") 195 | sql = db.session.execute(query) 196 | 197 | result = [] 198 | for row in sql: 199 | result.append({"id": row.id, "title": row.title, "description": row.description, "content": row.content, 200 | "cover": row.cover, 201 | "view": row.view, "cids": row.cids, "tag": row.tag, 202 | "createtime": row.create_time}) 203 | 204 | return Result(200, "获取随机文章成功", result) 205 | 206 | 207 | # 递增文章浏览量 208 | @article.route("/article/view/", methods=["PATCH"]) 209 | @siwa.doc(tags=["文章管理"], summary="递增文章浏览量") 210 | def editView(id): 211 | query = text("update article set view = view + 1 where id = :id") 212 | sql = db.session.execute(query, {"id": id}) 213 | 214 | db.session.commit() 215 | 216 | if sql.rowcount == 0: 217 | return Result(400, "递增失败") 218 | 219 | return Result(200, "递增成功") 220 | 221 | 222 | # 获取指定分类中的所有文章 223 | @article.route("/article/") 224 | @siwa.doc(tags=["文章管理"], summary="获取指定分类中的所有文章", description="根据分类的标识查询", 225 | query=ArticleQuery) 226 | def articleCate(mark): 227 | page = request.args.get("page", 1, type=int) 228 | size = request.args.get("size", 5, type=int) 229 | 230 | # 自定义sql查询 231 | query = text( 232 | "select a.* from article a, cate c where find_in_set(c.id,a.cids) and c.mark = :mark limit :size offset :offset") 233 | sql = db.session.execute(query, {'mark': mark, 'size': size, 'offset': (page - 1) * size}) 234 | 235 | result = [] 236 | for row in sql: 237 | cate = [] 238 | 239 | # 循环每一项的分类id,找出文章所对应的那一个 240 | for id in [int(k) for k in row.cids.split(",")]: 241 | cate.append(CateModel.query.filter_by(id=id).first().to()) 242 | 243 | result.append({"id": row.id, "title": row.title, "description": row.description, "content": row.content, 244 | "cover": row.cover, 245 | "view": row.view, "cids": row.cids, "cate": cate, "tag": row.tag, 246 | "createtime": row.create_time}) 247 | 248 | data = { 249 | "result": result, 250 | "page": page, 251 | "size": size, 252 | "pages": 0, 253 | "total": len(result), 254 | "prev": False, 255 | "next": False 256 | } 257 | 258 | return Result(200, "获取文章列表成功", data) 259 | -------------------------------------------------------------------------------- /thirive.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.35, for Win64 (x86_64) 2 | -- 3 | -- Host: 82.157.186.125 Database: thrive 4 | -- ------------------------------------------------------ 5 | -- Server version 8.3.0 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!50503 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `article` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `article`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!50503 SET character_set_client = utf8mb4 */; 25 | CREATE TABLE `article` ( 26 | `id` int NOT NULL AUTO_INCREMENT COMMENT '文章ID', 27 | `title` varchar(100) NOT NULL COMMENT '文章标题', 28 | `description` varchar(200) DEFAULT NULL COMMENT '文章介绍', 29 | `content` text NOT NULL COMMENT '文章主要内容', 30 | `cover` varchar(300) DEFAULT NULL COMMENT '文章封面', 31 | `view` int DEFAULT NULL COMMENT '文章浏览量', 32 | `cids` varchar(255) DEFAULT NULL COMMENT '该文章所绑定的分类ID', 33 | `tag` varchar(100) DEFAULT NULL COMMENT '文章标签', 34 | `create_time` datetime DEFAULT NULL COMMENT '文章创建时间', 35 | PRIMARY KEY (`id`) USING BTREE 36 | ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 37 | /*!40101 SET character_set_client = @saved_cs_client */; 38 | 39 | -- 40 | -- Dumping data for table `article` 41 | -- 42 | 43 | LOCK TABLES `article` WRITE; 44 | /*!40000 ALTER TABLE `article` DISABLE KEYS */; 45 | INSERT INTO `article` VALUES (1,'Vue.js 3.0新特性介绍','本文介绍了Vue.js 3.0的新特性,包括Composition API、Teleport、Suspense等','# Vue.js 3.0新特性介绍\r\n\r\nVue.js是一款流行的JavaScript框架,用于构建用户界面。它在3.0版本中引入了一些令人兴奋的新特性,使得开发者能够更加高效地构建现代化的Web应用程序。本文将介绍Vue.js 3.0的一些重要特性。\r\n\r\n## Composition API\r\n\r\nVue.js 3.0引入了Composition API,这是一个全新的API风格,旨在提供更灵活、更可组合的代码复用方式。相比于以往的Options API,Composition API允许开发者通过函数的形式组织代码逻辑,使得代码更易于理解和维护。开发者可以根据需要将相关的逻辑组合在一起,而不需要按照生命周期钩子将它们分散在不同的选项中。\r\n\r\n下面是一个使用Composition API的示例:\r\n\r\n```javascript\r\nimport { ref, reactive, computed } from \'vue\';\r\n\r\nexport default {\r\n setup() {\r\n const count = ref(0);\r\n const state = reactive({\r\n message: \'Hello, Vue.js 3.0!\',\r\n });\r\n\r\n const doubleCount = computed(() => count.value * 2);\r\n\r\n function increment() {\r\n count.value++;\r\n }\r\n\r\n return {\r\n count,\r\n state,\r\n doubleCount,\r\n increment,\r\n };\r\n },\r\n};\r\n```\r\n\r\n## 更好的性能\r\n\r\nVue.js 3.0在性能方面进行了一系列的优化,使得应用程序的渲染速度更快、内存占用更少。其中最显著的改进是使用了Proxy对象来替代Object.defineProperty,这使得Vue.js能够更高效地追踪数据的变化。此外,Vue.js 3.0还引入了静态树提升(Static Tree Hoisting)和基于模板的优化(Template-based Optimization)等技术,进一步提升了应用程序的性能。\r\n\r\n## 更好的TypeScript支持\r\n\r\nVue.js 3.0对TypeScript的支持也得到了改进。新版本中的TypeScript声明文件提供了更准确的类型推断和类型检查,使得开发者能够更轻松地编写类型安全的Vue.js应用程序。此外,Vue.js 3.0还引入了一些新的TypeScript API,如`defineComponent`和`defineProps`,以提供更好的类型支持。\r\n\r\n下面是一个使用TypeScript的示例:\r\n\r\n```typescript\r\nimport { defineComponent, defineProps } from \'vue\';\r\n\r\ninterface Props {\r\n name: string;\r\n}\r\n\r\nexport default defineComponent({\r\n props: defineProps(),\r\n setup(props) {\r\n return {\r\n greet: `Hello, ${props.name}!`,\r\n };\r\n },\r\n});\r\n```\r\n\r\n## 更小的包体积\r\n\r\nVue.js 3.0还对包体积进行了优化。由于使用了Tree-shaking技术,新版本中的Vue.js只包含了实际使用到的代码,减少了不必要的代码体积。此外,Vue.js 3.0还支持按需加载,使得开发者可以只引入需要的功能模块,进一步减小了包体积。\r\n\r\n下面是一个按需加载的示例:\r\n\r\n```javascript\r\nimport { createApp } from \'vue\';\r\nimport { Button, Input } from \'ant-design-vue\';\r\n\r\nconst app = createApp();\r\n\r\napp.use(Button);\r\napp.use(Input);\r\n\r\napp.mount(\'#app\');\r\n```\r\n\r\n## 更好的开发者工具\r\n\r\nVue.js 3.0提供了一系列新的开发者工具,使得开发者能够更轻松地调试和优化应用程序。新版本中的Devtools提供了更多的功能,如组件树的可视化、性能分析、事件追踪等。此外,Vue.js 3.0还引入了新的警告系统,使得开发者能够更容易地发现和修复潜在的问题。\r\n\r\n总结\r\n\r\nVue.js 3.0引入了许多令人兴奋的新特性,包括Composition API、更好的性能、更好的TypeScript支持、更小的包体积和更好的开发者工具。这些特性使得开发者能够更加高效地构建现代化的Web应用程序。如果你还没有尝试Vue.js 3.0,现在是时候开始了!','https://bu.dusays.com/2024/04/25/662a127c8df63.png',44,'2','Nodejs','2023-10-25 04:00:00'),(2,'Java 15新特性详解','本文介绍了Java 15的新特性,包括ZGC、Records、Text Blocks等','# Java 15新特性详解\nJava 15是Java语言的最新版本,它带来了一些非常实用的新特性。其中最重要的特性是ZGC,它是一种高效的垃圾回收器,可以大大提高Java应用程序的性能和可靠性。此外,Records和Text Blocks也是非常有用的新特性,它们可以帮助我们更好地处理数据和字符串。','https://bu.dusays.com/2024/04/25/662a127cacf86.jpg',17,'4,5,7,9','Java','2023-10-23 10:00:00'),(3,'Python爬虫实战教程','本文介绍了Python爬虫的基本原理和实战技巧','Python是一种非常流行的编程语言,它在数据处理和网络爬虫方面有着广泛的应用。本文将介绍Python爬虫的基本原理和实战技巧,包括如何使用Python的requests和beautifulsoup库来爬取网页数据,如何处理JSON和XML格式的数据,以及如何使用selenium库模拟用户行为。','https://bu.dusays.com/2024/04/25/662a127c9ac4b.jpeg',1,'4','Python','2023-10-22 08:00:00'),(4,'前端性能优化的10个技巧','本文介绍了前端性能优化的10个实用技巧,包括减少HTTP请求、使用CDN、优化图片等','前端性能优化是Web开发中非常重要的一环,它可以提高网站的加载速度和用户体验。本文将介绍10个实用的前端性能优化技巧,包括减少HTTP请求、使用CDN、优化图片、使用缓存、压缩代码等。这些技巧可以帮助开发者更好地优化自己的网站,提高用户满意度。','',0,'4','性能优化','2023-10-21 14:00:00'),(5,'使用Docker部署Java应用程序','本文介绍了如何使用Docker来部署Java应用程序','Docker是一种非常流行的容器化技术,它可以帮助开发者更好地管理和部署应用程序。本文将介绍如何使用Docker来部署Java应用程序,包括如何编写Dockerfile、如何构建和运行Docker镜像、如何使用Docker Compose来管理多个容器等。这些技巧可以帮助开发者更好地管理自己的Java应用程序,提高开发效率。','',0,'4','Java','2023-10-20 16:00:00'),(6,'前端开发入门指南','本文介绍了前端开发的基本概念和技术栈,适合初学者入门。','前端开发是指利用HTML、CSS和JavaScript等技术,开发网站的用户界面部分。它负责将网站的设计和用户交互变成可视化的界面,为用户提供优秀的用户体验。','',0,'4','入门','2023-01-01 10:00:00'),(7,'Vue.js实战教程','本文通过实例演示了如何使用Vue.js构建现代化的Web应用程序。','Vue.js是一个用于构建用户界面的渐进式JavaScript框架。它易于学习、灵活且高效,被广泛应用于前端开发领域。本文从基础概念到高级特性,详细介绍了Vue.js的使用方法。','https://bu.dusays.com/2024/04/25/662a127c8df63.png',0,'4,5,7,9','Vue.js','2023-02-01 14:00:00'),(8,'Java入门指南','本文介绍了Java编程语言的基本概念和语法,适合初学者入门。','Java是一种通用的高级编程语言,具有跨平台、面向对象和安全性等特点。它被广泛应用于企业级应用开发、移动应用开发和大数据处理等领域。本文从安装到编写简单程序,全面讲解了Java的入门知识。','https://bu.dusays.com/2024/04/25/662a127cacf86.jpg',0,'4,5,7,9','入门','2023-03-01 12:00:00'),(9,'Python数据分析实战','本文介绍了使用Python进行数据分析的基本方法和常用工具,适合数据分析初学者。','Python是一种简单易学且功能强大的编程语言,被广泛应用于数据科学和机器学习领域。本文通过实例演示了如何使用Python进行数据清洗、数据可视化和机器学习建模等任务。','https://bu.dusays.com/2024/04/25/662a127c9ac4b.jpeg',0,'4','数据分析','2023-04-01 16:00:00'),(10,'深入理解MySQL数据库','本文深入介绍了MySQL数据库的架构、优化和高级特性,适合有一定数据库基础的开发人员。','MySQL是一种常用的关系型数据库管理系统,被广泛应用于Web应用程序和企业级系统。本文从数据库设计到性能优化,详细讲解了MySQL的各个方面,帮助读者更好地理解和使用MySQL。','',7,'4','MySQL','2023-05-01 18:00:00'); 46 | /*!40000 ALTER TABLE `article` ENABLE KEYS */; 47 | UNLOCK TABLES; 48 | 49 | -- 50 | -- Table structure for table `cate` 51 | -- 52 | 53 | DROP TABLE IF EXISTS `cate`; 54 | /*!40101 SET @saved_cs_client = @@character_set_client */; 55 | /*!50503 SET character_set_client = utf8mb4 */; 56 | CREATE TABLE `cate` ( 57 | `id` int NOT NULL AUTO_INCREMENT, 58 | `name` varchar(255) NOT NULL COMMENT '分类名称', 59 | `icon` varchar(100) DEFAULT NULL COMMENT '分类图标', 60 | `url` varchar(255) DEFAULT NULL COMMENT '分类链接', 61 | `mark` varchar(100) NOT NULL COMMENT '分类标识', 62 | `level` int DEFAULT NULL COMMENT '分类级别', 63 | PRIMARY KEY (`id`) USING BTREE, 64 | UNIQUE KEY `name` (`name`) USING BTREE, 65 | UNIQUE KEY `cate_pk` (`mark`) 66 | ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 67 | /*!40101 SET character_set_client = @saved_cs_client */; 68 | 69 | -- 70 | -- Dumping data for table `cate` 71 | -- 72 | 73 | LOCK TABLES `cate` WRITE; 74 | /*!40000 ALTER TABLE `cate` DISABLE KEYS */; 75 | INSERT INTO `cate` VALUES (1,'开发笔记','🎉','/','kfbj',0),(2,'生活随笔','✍️','/','shsb',0),(4,'大前端','🎉','http://127.0.0.1:5000','dqd',0),(5,'前端','?','/','qd',4),(7,'Java','?','/','java',4),(9,'Python','?','/','python',4); 76 | /*!40000 ALTER TABLE `cate` ENABLE KEYS */; 77 | UNLOCK TABLES; 78 | 79 | -- 80 | -- Table structure for table `chat` 81 | -- 82 | 83 | DROP TABLE IF EXISTS `chat`; 84 | /*!40101 SET @saved_cs_client = @@character_set_client */; 85 | /*!50503 SET character_set_client = utf8mb4 */; 86 | CREATE TABLE `chat` ( 87 | `id` int NOT NULL AUTO_INCREMENT, 88 | `room` int NOT NULL, 89 | `data` json NOT NULL, 90 | PRIMARY KEY (`id`), 91 | UNIQUE KEY `chat_pk_2` (`id`) 92 | ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 93 | /*!40101 SET character_set_client = @saved_cs_client */; 94 | 95 | -- 96 | -- Dumping data for table `chat` 97 | -- 98 | 99 | LOCK TABLES `chat` WRITE; 100 | /*!40000 ALTER TABLE `chat` DISABLE KEYS */; 101 | INSERT INTO `chat` VALUES (18,10001,'{\"date\": \"2023-05-25\", \"name\": \"宇阳\", \"avatar\": \"https://api.dicebear.com/7.x/fun-emoji/svg?seed=Lilly\", \"content\": \"Hello! 有什么要对我说的吗?\"}'),(19,10003,'{\"date\": \"2023-05-25\", \"name\": \"神秘人\", \"avatar\": \"https://api.dicebear.com/7.x/fun-emoji/svg?seed=Lilly\", \"content\": \"大家可以在这里提交你的需求以及宝贵的建议\"}'),(20,10004,'{\"date\": \"2023-05-25\", \"name\": \"神秘人\", \"avatar\": \"https://api.dicebear.com/7.x/fun-emoji/svg?seed=Lilly\", \"content\": \"抢沙发\"}'),(21,10002,'{\"date\": \"2023-05-25\", \"name\": \"神秘人\", \"avatar\": \"https://api.dicebear.com/7.x/fun-emoji/svg?seed=Lilly\", \"content\": \"占楼\"}'); 102 | /*!40000 ALTER TABLE `chat` ENABLE KEYS */; 103 | UNLOCK TABLES; 104 | 105 | -- 106 | -- Table structure for table `comment` 107 | -- 108 | 109 | DROP TABLE IF EXISTS `comment`; 110 | /*!40101 SET @saved_cs_client = @@character_set_client */; 111 | /*!50503 SET character_set_client = utf8mb4 */; 112 | CREATE TABLE `comment` ( 113 | `id` int NOT NULL AUTO_INCREMENT, 114 | `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 115 | `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 116 | `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 117 | `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 118 | `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 119 | `aid` int NOT NULL, 120 | `rid` int NOT NULL, 121 | `create_time` datetime DEFAULT NULL, 122 | `audit` int DEFAULT '0' COMMENT '是否审核', 123 | PRIMARY KEY (`id`) USING BTREE 124 | ) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 125 | /*!40101 SET character_set_client = @saved_cs_client */; 126 | 127 | -- 128 | -- Dumping data for table `comment` 129 | -- 130 | 131 | LOCK TABLES `comment` WRITE; 132 | /*!40000 ALTER TABLE `comment` DISABLE KEYS */; 133 | INSERT INTO `comment` VALUES (36,'神秘人','https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640','测试评论','3311118881@qq.com','http://127.0.0.1:5173/',1,0,'2023-11-24 08:13:59',1),(37,'神秘人2号','https://q1.qlogo.cn/g?b=qq&nk=2518848232&s=640','测试回复评论','2518848232@qq.com','http://127.0.0.1:5173/',0,36,'2023-11-24 08:14:22',1),(38,'神秘人2号','https://q1.qlogo.cn/g?b=qq&nk=2518848232&s=640','第二条评论','2518848232@qq.com','http://127.0.0.1:5173/',1,0,'2023-11-24 08:14:32',1),(50,'神秘人3号','https://q1.qlogo.cn/g?b=qq&nk=754443568&s=640','三级','754443568@qq.com','http://127.0.0.1:5173/',0,37,'2023-11-24 09:27:37',0),(55,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,0,'2024-03-26 02:31:18',1),(56,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,55,'2024-03-26 02:31:18',1),(57,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,56,'2024-03-26 02:31:18',1),(58,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,55,'2024-03-26 02:31:18',1),(59,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,56,'2024-03-26 02:31:18',1),(60,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,56,'2024-03-26 02:31:18',1),(61,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,56,'2024-03-26 02:31:18',1),(62,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,0,'2024-03-26 02:31:18',1),(63,'刘宇阳','https://liuyuyang.net/avatar.jpg','123','liuyuyang1024@163.com','',1,0,'2024-03-26 02:31:18',1),(64,'神秘人','','写的不错','liuyuyang1024@163.com','',1,0,'2024-03-26 09:47:03',1),(65,'神秘人','https://q1.qlogo.cn/g?b=qq&nk=liuyuyang1024&s=640','123','liuyuyang1024@163.com','',1,0,'2024-03-26 09:51:58',0); 134 | /*!40000 ALTER TABLE `comment` ENABLE KEYS */; 135 | UNLOCK TABLES; 136 | 137 | -- 138 | -- Table structure for table `link` 139 | -- 140 | 141 | DROP TABLE IF EXISTS `link`; 142 | /*!40101 SET @saved_cs_client = @@character_set_client */; 143 | /*!50503 SET character_set_client = utf8mb4 */; 144 | CREATE TABLE `link` ( 145 | `id` int NOT NULL AUTO_INCREMENT, 146 | `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 147 | `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 148 | `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 149 | `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 150 | `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 151 | `type` int NOT NULL, 152 | `create_time` datetime DEFAULT NULL, 153 | PRIMARY KEY (`id`) USING BTREE 154 | ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 155 | /*!40101 SET character_set_client = @saved_cs_client */; 156 | 157 | -- 158 | -- Dumping data for table `link` 159 | -- 160 | 161 | LOCK TABLES `link` WRITE; 162 | /*!40000 ALTER TABLE `link` DISABLE KEYS */; 163 | INSERT INTO `link` VALUES (6,'Thrive','记录一个架构师的崛起','3311118881@qq.com','https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640','/',2,'2023-10-02 18:54:44'),(7,'张洪Heo','分享设计与科技生活','3311118881@qq.com','https://bu.dusays.com/2022/12/28/63ac2812183aa.png','https://blog.zhheo.com/',1,'2023-10-02 18:54:44'),(8,'友人C','友人C的个人空间','3311118881@qq.com','https://s1.ax1x.com/2023/06/02/p9zTn0O.png','http://space.eyescode.top',2,'2023-10-02 18:54:44'),(9,'秦枫鸢梦','花有重开日,人无再少年','3311118881@qq.com','https://blog.zwying.com/avatar.jpg','https://blog.zwying.com',1,'2023-10-02 18:54:44'),(10,'生活倒影','这是一个 Vue2 Vue3 与 SpringBoot 结合的产物','3311118881@qq.com','https://s1.ax1x.com/2022/11/10/z9E7X4.jpg','https://poetize.cn/',1,'2023-10-02 18:54:44'),(11,'心月云','须知少时凌云志,曾许人间第一流','3311118881@qq.com','https://wch666.com/head.png','https://wch666.com',1,'2023-10-02 18:54:44'),(12,'一克猫','一只微不足道的猫','3311118881@qq.com','https://cravatar.cn/avatar/7adbfaef92d9d082be5dec39f3fe3d02?s=200','https://www.1gcat.com',2,'2023-10-02 18:54:44'),(13,'频率','风卷过的起点','3311118881@qq.com','https://cravatar.cn/avatar/cc763511474fe24ffcc80257fb7cb970?s=256','https://pinlyu.com/',1,'2023-10-02 18:54:44'),(14,'青灯暮雨','再渺小的星光,也有属于它的光芒','3311118881@qq.com','https://www.blatr.cn/images/adminAvatar.jpg','https://www.blatr.cn',1,'2023-10-02 18:54:44'),(15,'相左','心有山海,静而不争','3311118881@qq.com','https://qiniu.ztyang.com/img/wechatavatar.jpg','https://www.ztyang.com',1,'2023-10-02 18:54:44'),(16,'Echo’s blog','韶华不为少年留 恨悠悠 几时休','3311118881@qq.com','https://yy.liveout.cn/photo/photo2.jpg','https://www.liveout.cn/index/',1,'2023-10-02 18:54:44'),(17,'奇异纬度','不曾与你分享的时间,我在进步','3311118881@qq.com','https://blog.isww.cn/logo.head.jpg','https://blog.isww.cn/',1,'2023-10-02 18:54:44'),(18,'正物博客','一场凡梦,一份追求','3311118881@qq.com','https://www.zwbo.com/tx.png','https://www.zwbo.com/',2,'2023-10-02 18:54:44'),(19,'HONG的小站','或许是个二次元','3311118881@qq.com','https://blog.zwying.com/usr/uploads/sina/63adb58e798d4.jpg','https://hongweblog.com/',1,'2023-10-02 18:54:44'),(26,'11','22','3311118881@qq.com','44','66',2,'2024-02-07 13:21:55'); 164 | /*!40000 ALTER TABLE `link` ENABLE KEYS */; 165 | UNLOCK TABLES; 166 | 167 | -- 168 | -- Table structure for table `swiper` 169 | -- 170 | 171 | DROP TABLE IF EXISTS `swiper`; 172 | /*!40101 SET @saved_cs_client = @@character_set_client */; 173 | /*!50503 SET character_set_client = utf8mb4 */; 174 | CREATE TABLE `swiper` ( 175 | `id` int NOT NULL AUTO_INCREMENT, 176 | `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 177 | `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 178 | `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 179 | `url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 180 | PRIMARY KEY (`id`) USING BTREE 181 | ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 182 | /*!40101 SET character_set_client = @saved_cs_client */; 183 | 184 | -- 185 | -- Dumping data for table `swiper` 186 | -- 187 | 188 | LOCK TABLES `swiper` WRITE; 189 | /*!40000 ALTER TABLE `swiper` DISABLE KEYS */; 190 | INSERT INTO `swiper` VALUES (1,'半山腰的风景很美,然而我还是更想到山顶看看','The scenery halfway up the mountain is beautiful, but I still prefer to see the mountaintop','https://bu.dusays.com/2023/11/10/654e2cf6055b0.jpg','/'),(14,'ces','ces','https://bu.dusays.com/2023/11/05/65473822495c8.jpg','/'); 191 | /*!40000 ALTER TABLE `swiper` ENABLE KEYS */; 192 | UNLOCK TABLES; 193 | 194 | -- 195 | -- Table structure for table `tag` 196 | -- 197 | 198 | DROP TABLE IF EXISTS `tag`; 199 | /*!40101 SET @saved_cs_client = @@character_set_client */; 200 | /*!50503 SET character_set_client = utf8mb4 */; 201 | CREATE TABLE `tag` ( 202 | `id` int NOT NULL AUTO_INCREMENT, 203 | `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 204 | PRIMARY KEY (`id`) USING BTREE 205 | ) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 206 | /*!40101 SET character_set_client = @saved_cs_client */; 207 | 208 | -- 209 | -- Dumping data for table `tag` 210 | -- 211 | 212 | LOCK TABLES `tag` WRITE; 213 | /*!40000 ALTER TABLE `tag` DISABLE KEYS */; 214 | INSERT INTO `tag` VALUES (3,'Nodejs'),(4,'Python'),(5,'Java'),(6,'Element ui'),(7,'Vue3'),(8,'React'),(10,'Django'),(11,'Flask'),(12,'JavaScript'),(13,'HTML'),(14,'CSS'),(15,'Ajax'),(16,'axios'),(17,'全栈开发'),(54,'大前端'),(63,'123'),(65,'88'),(66,'生活随笔'),(67,'Mybatis'); 215 | /*!40000 ALTER TABLE `tag` ENABLE KEYS */; 216 | UNLOCK TABLES; 217 | 218 | -- 219 | -- Table structure for table `type` 220 | -- 221 | 222 | DROP TABLE IF EXISTS `type`; 223 | /*!40101 SET @saved_cs_client = @@character_set_client */; 224 | /*!50503 SET character_set_client = utf8mb4 */; 225 | CREATE TABLE `type` ( 226 | `id` int NOT NULL AUTO_INCREMENT, 227 | `name` varchar(100) NOT NULL COMMENT '类型名称', 228 | PRIMARY KEY (`id`), 229 | UNIQUE KEY `type_pk_2` (`id`) 230 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='网站类型'; 231 | /*!40101 SET character_set_client = @saved_cs_client */; 232 | 233 | -- 234 | -- Dumping data for table `type` 235 | -- 236 | 237 | LOCK TABLES `type` WRITE; 238 | /*!40000 ALTER TABLE `type` DISABLE KEYS */; 239 | INSERT INTO `type` VALUES (1,'生活类'),(2,'技术类'); 240 | /*!40000 ALTER TABLE `type` ENABLE KEYS */; 241 | UNLOCK TABLES; 242 | 243 | -- 244 | -- Table structure for table `user` 245 | -- 246 | 247 | DROP TABLE IF EXISTS `user`; 248 | /*!40101 SET @saved_cs_client = @@character_set_client */; 249 | /*!50503 SET character_set_client = utf8mb4 */; 250 | CREATE TABLE `user` ( 251 | `id` int NOT NULL AUTO_INCREMENT, 252 | `username` varchar(50) DEFAULT NULL COMMENT '用户名', 253 | `password` varchar(50) DEFAULT NULL COMMENT '密码', 254 | `name` varchar(50) NOT NULL COMMENT '用户名称', 255 | `email` varchar(100) NOT NULL COMMENT '用户邮箱', 256 | `avatar` varchar(255) NOT NULL COMMENT '用户头像', 257 | `info` varchar(255) NOT NULL COMMENT '用户介绍', 258 | `role` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, 259 | `create_time` datetime DEFAULT NULL COMMENT '用户创建时间', 260 | PRIMARY KEY (`id`) USING BTREE, 261 | UNIQUE KEY `user_pk` (`username`) 262 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC; 263 | /*!40101 SET character_set_client = @saved_cs_client */; 264 | 265 | -- 266 | -- Dumping data for table `user` 267 | -- 268 | 269 | LOCK TABLES `user` WRITE; 270 | /*!40000 ALTER TABLE `user` DISABLE KEYS */; 271 | INSERT INTO `user` VALUES (1,'liuyuyang','4297f44b13955235245b2497399d7a93','宇阳','3311118881@qq.com','https://blog.liuyuyang.net/avatar.jpg','再渺小的星光,也有属于他的光芒!','admin','2024-01-22 18:32:30'); 272 | /*!40000 ALTER TABLE `user` ENABLE KEYS */; 273 | UNLOCK TABLES; 274 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 275 | 276 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 277 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 278 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 279 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 280 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 281 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 282 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 283 | 284 | -- Dump completed on 2024-06-01 21:02:26 285 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------