diff --git a/UI/ui.jsx b/UI/ui.jsx deleted file mode 100644 index f902b068..00000000 --- a/UI/ui.jsx +++ /dev/null @@ -1,228 +0,0 @@ -import React from 'react'; -import { motion } from 'framer-motion'; -import { Clock, Newspaper, Megaphone, Gavel, Shield, Eye, Send, Factory, Leaf, Ship, Scale, Heart, Bus, Calendar, Brain, Wifi, Droplets } from 'lucide-react'; - -const PublicAccountPlatform = () => { - // 公众号数据 - const accounts = [ - { - name: '湖南日报', - desc: '《湖南日报》是中共湖南省委机关报,是湖南省最具权威性、发行量最大的综合性大报。', - createTime: '2024-03-22', - updateTime: '2025-05-13', - icon: - }, - { - name: '长沙晚报', - desc: '在这里,了解长沙!在这里,爱上长沙!', - createTime: '2024-03-22', - updateTime: '2025-05-12', - icon: - }, - { - name: '长沙市场监管', - desc: '长沙市市场监督管理局官方公众号平台', - createTime: '2024-03-26', - updateTime: '2025-05-11', - icon: - }, - { - name: '微开福', - desc: '展示区域形象,发布政务信息,提供便民服务。', - createTime: '2025-05-09', - updateTime: '2025-05-12', - icon: - }, - { - name: '清廉长沙', - desc: '长沙市纪委市监委官微,廉政宣传教育,接受"四风"举报。', - createTime: '2024-03-22', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙警事', - desc: '长沙市公安局警务信息发布平台', - createTime: '2024-03-22', - updateTime: '2025-05-11', - icon: - }, - { - name: '文明长沙', - desc: '传播正能量', - createTime: '2024-04-29', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙观察', - desc: '长沙电视台新闻频道新媒体官方平台,关注热点新闻及综合资讯。', - createTime: '2024-03-22', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙发布', - desc: '传递政务信息,提供服务资讯,关注百姓关切——长沙发布随时为您守候。', - createTime: '2024-04-29', - updateTime: '2025-05-12', - icon: - }, - { - name: '新华社', - desc: '新华通讯社官方账号。硬新闻、暖故事、好朋友。', - createTime: '2024-03-22', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙生态环境', - desc: '宣传环保政策法规、发布长沙环保工作动态、普及环境科学知识、聚焦环保热点问题,倡导绿色、低碳的发展和生活方式!', - createTime: '2024-04-29', - updateTime: '2025-05-11', - icon: - }, - { - name: '雨花市场监管', - desc: '长沙市雨花区市场监督管理局官方公众号平台', - createTime: '2024-03-26', - updateTime: '2025-05-11', - icon: - }, - { - name: '开福市场监管', - desc: '宣传市场监管知识、传达各级文件会议精神、解读国家相关政策', - createTime: '2024-03-26', - updateTime: '2025-05-11', - icon: - }, - { - name: '宁乡市场监督', - desc: '宁乡市市场监督管理局信息发布平台', - createTime: '2024-03-26', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙县市场监管', - desc: '了解长沙县市场监督管理局工作动态,并提供相关业务咨询服务。', - createTime: '2024-03-26', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙城管', - desc: '"长沙城管"秉承"城管为民"服务理念,向公众提供城市管理动态、政策法规、投诉建议等服务,助力品质长沙建设,提高市民参与度与获得感。', - createTime: '2024-04-29', - updateTime: '2025-05-11', - icon: - }, - { - name: '长沙慈善', - desc: '弘扬慈善文化,传递慈善正能量!', - createTime: '2025-03-14', - updateTime: '2025-05-12', - icon: - }, - { - name: '长沙交通运输', - desc: '服务百姓出行,传播交通声音!', - createTime: '2024-04-29', - updateTime: '2025-05-12', - icon: - }, - { - name: '长沙会展', - desc: '聚焦会展行业,传播会展资讯,链接会展服务,发展会展经济。', - createTime: '2024-04-29', - updateTime: '2025-05-11', - icon: - }, - { - name: '思想长沙', - desc: '长沙市社会科学界联合会(长沙社会科学院)官方账号', - createTime: '2024-04-29', - updateTime: '2025-05-11', - icon: - }, - { - name: '网信长沙', - desc: '解读网信政策法规,展示长沙网信动态,交流网信工作经验,推进网络文明建设,走好网上群众路线。', - createTime: '2024-04-29', - updateTime: '2025-05-12', - icon: - }, - { - name: '长沙水运', - desc: '长沙市水运事务中心(长沙市船舶检验中心)官方账号。', - createTime: '2024-04-29', - updateTime: '2025-05-12', - icon: - }, - { - name: '天心市场监管', - desc: '长沙市天心区市场监督管理局官方公众号平台', - createTime: '2024-10-31', - updateTime: '2025-05-12', - icon: - } - ]; - - return ( -
- {/* 标题区 */} -
-
-

长沙政务公众号信息平台

-
-
-
- - {/* 内容区 */} -
-
- {accounts?.map((account, index) => ( - -
-
-
- {account?.icon} -
-

{account?.name}

-
-

{account?.desc}

-
-
- - 创建 {account?.createTime} -
-
- - 更新 {account?.updateTime} -
-
-
-
- ))} -
-
- - {/* 页脚区 */} -
-
-

created by coze space

-

页面内容均由 AI 生成,仅供参考

-
-
-
- ); -}; - -export default PublicAccountPlatform; \ No newline at end of file diff --git a/apis/article.py b/apis/article.py new file mode 100644 index 00000000..45866ca5 --- /dev/null +++ b/apis/article.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Query +from core.auth import get_current_user +from core.db import DB +from .ver import API_VERSION + +router = APIRouter(prefix=f"{API_VERSION}/articles", tags=["文章管理"]) +@router.get("", summary="获取文章列表") +async def get_articles( + offset: int = Query(0, ge=0), + limit: int = Query(5, ge=1, le=100), + current_user: dict = Depends(get_current_user) +): + session = DB.get_session() + try: + articles = session.execute( + "SELECT * FROM articles LIMIT :limit OFFSET :offset", + {"limit": limit, "offset": offset} + ).fetchall() + return {"code": 0, "data": articles} + finally: + session.close() + +@router.get("/{article_id}", summary="获取文章详情") +async def get_article_detail( + article_id: int, + current_user: dict = Depends(get_current_user) +): + session = DB.get_session() + try: + article = session.execute( + "SELECT * FROM articles WHERE id = :id", + {"id": article_id} + ).fetchone() + if not article: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="文章不存在" + ) + return {"code": 0, "data": article} + finally: + session.close() \ No newline at end of file diff --git a/apis/auth.py b/apis/auth.py new file mode 100644 index 00000000..ab08f2ff --- /dev/null +++ b/apis/auth.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from datetime import timedelta +from core.auth import ( + authenticate_user, + create_access_token, + get_current_user, + ACCESS_TOKEN_EXPIRE_MINUTES +) +from .ver import API_VERSION +router = APIRouter(prefix=f"{API_VERSION}/auth", tags=["认证"]) + +@router.post("/login", summary="用户登录") +async def login(form_data: OAuth2PasswordRequestForm = Depends()): + user = authenticate_user(form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="用户名或密码错误", + ) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user.username}, expires_delta=access_token_expires + ) + return {"access_token": access_token, "token_type": "bearer"} + +@router.post("/logout", summary="用户注销") +async def logout(current_user: dict = Depends(get_current_user)): + return {"code": 0, "message": "注销成功"} + +@router.post("/refresh", summary="刷新Token") +async def refresh_token(current_user: dict = Depends(get_current_user)): + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": current_user["username"]}, expires_delta=access_token_expires + ) + return {"access_token": access_token, "token_type": "bearer"} \ No newline at end of file diff --git a/apis/user.py b/apis/user.py new file mode 100644 index 00000000..4c46f407 --- /dev/null +++ b/apis/user.py @@ -0,0 +1,67 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from datetime import datetime +from core.auth import get_current_user +from core.db import DB +from core.models import User as DBUser +from core.auth import pwd_context +from .ver import API_VERSION + +router = APIRouter(prefix=f"{API_VERSION}/user", tags=["用户管理"]) + +@router.get("", summary="获取用户信息") +async def get_user_info(current_user: dict = Depends(get_current_user)): + session = DB.get_session() + try: + user = session.query(DBUser).filter( + DBUser.username == current_user["username"] + ).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="用户不存在" + ) + return { + "code": 0, + "data": { + "username": user.username, + "role": user.role, + "is_active": user.is_active, + "created_at": user.created_at + } + } + finally: + session.close() + +@router.put("", summary="修改用户资料") +async def update_user_info( + update_data: dict, + current_user: dict = Depends(get_current_user) +): + session = DB.get_session() + try: + user = session.query(DBUser).filter( + DBUser.username == current_user["username"] + ).first() + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="用户不存在" + ) + + if "password" in update_data: + user.password_hash = pwd_context.hash(update_data["password"]) + if "is_active" in update_data: + user.is_active = bool(update_data["is_active"]) + + user.updated_at = datetime.now() + session.commit() + + return {"code": 0, "message": "更新成功"} + except Exception as e: + session.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(e) + ) + finally: + session.close() \ No newline at end of file diff --git a/apis/ver.py b/apis/ver.py new file mode 100644 index 00000000..7c29182f --- /dev/null +++ b/apis/ver.py @@ -0,0 +1 @@ +API_VERSION="/api/v1/wx" \ No newline at end of file diff --git a/apis/wechat.py b/apis/wechat.py new file mode 100644 index 00000000..ad41bf11 --- /dev/null +++ b/apis/wechat.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Query +from core.auth import get_current_user +from core.db import DB +from .ver import API_VERSION +router = APIRouter(prefix=f"{API_VERSION}/mps", tags=["公众号管理"]) + +@router.get("", summary="获取公众号列表") +async def get_mps(current_user: dict = Depends(get_current_user)): + session = DB.get_session() + try: + mps = DB.get_all_mps() + return {"code": 0, "data": mps} + finally: + session.close() + +@router.get("/{mp_id}", summary="获取公众号详情") +async def get_mp_detail( + mp_id: str, + current_user: dict = Depends(get_current_user) +): + session = DB.get_session() + try: + mp = session.execute( + "SELECT * FROM mps WHERE mp_id = :id", + {"id": mp_id} + ).fetchone() + if not mp: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="公众号不存在" + ) + return {"code": 0, "data": mp} + finally: + session.close() \ No newline at end of file diff --git a/app.log b/app.log index d3ef0024..8f6de565 100644 --- a/app.log +++ b/app.log @@ -542,3 +542,413 @@ 2025-05-14 07:33:56 - __main__ - INFO - ʼִк 2025-05-14 09:15:44 - __main__ - INFO - ʼִк 2025-05-14 11:16:17 - __main__ - INFO - ʼִк +2025-05-14 16:53:06 - __main__ - INFO - ʼִк +2025-05-14 16:53:35 - __main__ - INFO - ʼִк +2025-05-14 16:59:52 - __main__ - INFO - ʼִк +2025-05-14 17:14:48 - __main__ - INFO - ձ ½,23 +2025-05-14 17:15:43 - __main__ - INFO - ɳ ½,23 +2025-05-14 17:16:06 - __main__ - INFO - ձ ½,0 +2025-05-14 17:16:55 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:17:48 - __main__ - INFO - ɳг ½,1 +2025-05-14 17:18:35 - __main__ - INFO - ΢ ½,0 +2025-05-14 17:19:36 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:20:10 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:20:30 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:20:56 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 17:21:19 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:21:59 - __main__ - INFO - » ½,1 +2025-05-14 17:22:11 - __main__ - INFO - ɳ̬ ½,1 +2025-05-14 17:22:46 - __main__ - INFO - 껨г ½,0 +2025-05-14 17:23:46 - __main__ - INFO - г ½,1 +2025-05-14 17:24:11 - __main__ - INFO - гල ½,0 +2025-05-14 17:24:25 - __main__ - INFO - ɳг ½,0 +2025-05-14 17:24:46 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 17:25:47 - __main__ - INFO - ɳ ½,0 +2025-05-14 17:26:28 - __main__ - INFO - ɳͨ ½,1 +2025-05-14 17:27:04 - __main__ - INFO - ɳչ ½,0 +2025-05-14 17:27:55 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 17:28:40 - __main__ - INFO - ųɳ ½,1 +2025-05-14 17:29:00 - __main__ - INFO - ɳˮ ½,1 +2025-05-14 17:29:24 - __main__ - INFO - г ½,0 +2025-05-14 17:29:44 - __main__ - INFO - йںŸ,7 +2025-05-14 21:12:59 - __main__ - INFO - ձ ½,4 +2025-05-14 21:13:23 - __main__ - INFO - ɳ ½,0 +2025-05-14 21:13:59 - __main__ - INFO - ɳг ½,0 +2025-05-14 21:14:30 - __main__ - INFO - ΢ ½,1 +2025-05-14 21:15:13 - __main__ - INFO - ɳ ½,0 +2025-05-14 21:15:26 - __main__ - INFO - ɳ ½,2 +2025-05-14 21:16:06 - __main__ - INFO - ɳ ½,1 +2025-05-14 21:16:52 - __main__ - INFO - ɳ۲ ½,3 +2025-05-14 21:17:15 - __main__ - INFO - ɳ ½,1 +2025-05-14 21:18:15 - __main__ - INFO - » ½,5 +2025-05-14 21:18:28 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 21:19:11 - __main__ - INFO - 껨г ½,0 +2025-05-14 21:19:41 - __main__ - INFO - г ½,0 +2025-05-14 21:20:28 - __main__ - INFO - гල ½,1 +2025-05-14 21:20:37 - __main__ - INFO - ɳг ½,0 +2025-05-14 21:21:19 - __main__ - INFO - ɳǹ ½,1 +2025-05-14 21:21:56 - __main__ - INFO - ɳ ½,0 +2025-05-14 21:22:44 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 21:23:06 - __main__ - INFO - ɳչ ½,1 +2025-05-14 21:23:55 - __main__ - INFO - ˼볤ɳ ½,1 +2025-05-14 21:24:32 - __main__ - INFO - ųɳ ½,0 +2025-05-14 21:25:19 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 21:26:10 - __main__ - INFO - г ½,0 +2025-05-14 21:26:16 - __main__ - INFO - йںŸ,21 +2025-05-14 22:37:16 - __main__ - INFO - ձ ½,1 +2025-05-14 22:37:39 - __main__ - INFO - ɳ ½,0 +2025-05-14 22:38:21 - __main__ - INFO - ձ ½,0 +2025-05-14 22:39:18 - __main__ - INFO - ɳ ½,0 +2025-05-14 22:39:28 - __main__ - INFO - ɳг ½,0 +2025-05-14 22:39:55 - __main__ - INFO - ΢ ½,0 +2025-05-14 22:40:00 - __main__ - INFO - ɳ ½,0 +2025-05-14 22:40:33 - __main__ - INFO - ɳ ½,0 +2025-05-14 22:41:17 - __main__ - INFO - ɳ ½,1 +2025-05-14 22:41:58 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 22:42:38 - __main__ - INFO - ɳ ½,1 +2025-05-14 22:43:34 - __main__ - INFO - » ½,3 +2025-05-14 22:44:18 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 22:45:19 - __main__ - INFO - 껨г ½,0 +2025-05-14 22:45:24 - __main__ - INFO - г ½,0 +2025-05-14 22:45:30 - __main__ - INFO - гල ½,0 +2025-05-14 22:46:04 - __main__ - INFO - ɳг ½,0 +2025-05-14 22:46:50 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 22:47:49 - __main__ - INFO - ɳ ½,0 +2025-05-14 22:48:08 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 22:48:27 - __main__ - INFO - ɳչ ½,0 +2025-05-14 22:49:29 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 22:50:07 - __main__ - INFO - ųɳ ½,0 +2025-05-14 22:50:48 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 22:51:24 - __main__ - INFO - г ½,0 +2025-05-14 22:51:56 - __main__ - INFO - йںŸ,5 +2025-05-14 23:19:15 - __main__ - INFO - ձ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - » ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:19:15 - __main__ - INFO - г ½,0 +2025-05-14 23:19:15 - __main__ - INFO - гල ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:19:15 - __main__ - INFO - г ½,0 +2025-05-14 23:19:15 - __main__ - INFO - йںŸ,0 +2025-05-14 23:20:43 - __main__ - INFO - ձ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - » ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:20:43 - __main__ - INFO - г ½,0 +2025-05-14 23:20:43 - __main__ - INFO - гල ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:20:43 - __main__ - INFO - г ½,0 +2025-05-14 23:20:43 - __main__ - INFO - йںŸ,0 +2025-05-14 23:21:05 - __main__ - INFO - ձ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - » ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:21:05 - __main__ - INFO - г ½,0 +2025-05-14 23:21:05 - __main__ - INFO - гල ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:21:05 - __main__ - INFO - г ½,0 +2025-05-14 23:21:05 - __main__ - INFO - йںŸ,0 +2025-05-14 23:22:26 - __main__ - INFO - ձ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - » ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:22:26 - __main__ - INFO - г ½,0 +2025-05-14 23:22:26 - __main__ - INFO - гල ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:22:26 - __main__ - INFO - г ½,0 +2025-05-14 23:22:26 - __main__ - INFO - йںŸ,0 +2025-05-14 23:22:48 - __main__ - INFO - ձ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - » ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:22:48 - __main__ - INFO - г ½,0 +2025-05-14 23:22:48 - __main__ - INFO - гල ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:22:48 - __main__ - INFO - г ½,0 +2025-05-14 23:22:48 - __main__ - INFO - йںŸ,0 +2025-05-14 23:23:40 - __main__ - INFO - ձ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ΢ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ۲ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - » ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ̬ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - 껨г ½,0 +2025-05-14 23:23:40 - __main__ - INFO - г ½,0 +2025-05-14 23:23:40 - __main__ - INFO - гල ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳг ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳǹ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳͨ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳչ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ˼볤ɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ųɳ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - ɳˮ ½,0 +2025-05-14 23:23:40 - __main__ - INFO - г ½,0 +2025-05-14 23:23:40 - __main__ - INFO - йںŸ,0 +2025-05-15 09:37:58 - libs.log - INFO - ձ ½,0 +2025-05-15 09:40:18 - libs.log - INFO - ձ ½,0 +2025-05-15 09:40:54 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:41:13 - libs.log - INFO - ɳг ½,0 +2025-05-15 09:41:16 - libs.log - INFO - ΢ ½,0 +2025-05-15 09:42:10 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:43:03 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:43:40 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:44:11 - libs.log - INFO - ձ ½,0 +2025-05-15 09:47:00 - libs.log - INFO - ձ ½,0 +2025-05-15 09:47:11 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:47:19 - libs.log - INFO - ɳг ½,0 +2025-05-15 09:48:10 - libs.log - INFO - ΢ ½,0 +2025-05-15 09:48:22 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:48:35 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:49:11 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:49:56 - libs.log - INFO - ɳ۲ ½,0 +2025-05-15 09:49:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:50:18 - libs.log - INFO - » ½,0 +2025-05-15 09:50:47 - libs.log - INFO - ɳ̬ ½,0 +2025-05-15 09:51:36 - libs.log - INFO - 껨г ½,0 +2025-05-15 09:52:35 - libs.log - INFO - г ½,0 +2025-05-15 09:53:08 - libs.log - INFO - гල ½,0 +2025-05-15 09:53:37 - libs.log - INFO - ɳг ½,0 +2025-05-15 09:54:35 - libs.log - INFO - ɳǹ ½,0 +2025-05-15 09:54:59 - libs.log - INFO - ɳ ½,0 +2025-05-15 09:55:35 - libs.log - INFO - ɳͨ ½,0 +2025-05-15 09:56:05 - libs.log - INFO - ɳչ ½,0 +2025-05-15 09:56:24 - libs.log - INFO - ˼볤ɳ ½,0 +2025-05-15 09:56:32 - libs.log - INFO - ųɳ ½,0 +2025-05-15 09:56:50 - libs.log - INFO - ɳˮ ½,0 +2025-05-15 09:57:13 - libs.log - INFO - г ½,0 +2025-05-15 09:57:34 - libs.log - INFO - йںŸ,0 +2025-05-15 10:01:22 - libs.log - INFO - ձ ½,0 +2025-05-15 10:01:30 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:02:07 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:02:27 - libs.log - INFO - ΢ ½,0 +2025-05-15 10:03:18 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:03:35 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:04:23 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:04:47 - libs.log - INFO - ձ ½,0 +2025-05-15 10:05:17 - libs.log - INFO - ձ ½,0 +2025-05-15 10:05:36 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ձ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ΢ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ۲ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - » ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ̬ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - 껨г ½,0 +2025-05-15 10:06:01 - libs.log - INFO - г ½,0 +2025-05-15 10:06:01 - libs.log - INFO - гල ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳǹ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳͨ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳչ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ˼볤ɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ųɳ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - ɳˮ ½,0 +2025-05-15 10:06:01 - libs.log - INFO - г ½,0 +2025-05-15 10:06:01 - libs.log - INFO - йںŸ,0 +2025-05-15 10:06:58 - libs.log - INFO - ձ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ΢ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ۲ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - » ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ̬ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - 껨г ½,0 +2025-05-15 10:06:58 - libs.log - INFO - г ½,0 +2025-05-15 10:06:58 - libs.log - INFO - гල ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳǹ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳͨ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳչ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ˼볤ɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ųɳ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - ɳˮ ½,0 +2025-05-15 10:06:58 - libs.log - INFO - г ½,0 +2025-05-15 10:06:58 - libs.log - INFO - йںŸ,0 +2025-05-15 10:09:32 - libs.log - INFO - ձ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ΢ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ۲ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - » ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ̬ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - 껨г ½,0 +2025-05-15 10:09:32 - libs.log - INFO - г ½,0 +2025-05-15 10:09:32 - libs.log - INFO - гල ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳǹ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳͨ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳչ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ˼볤ɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ųɳ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - ɳˮ ½,0 +2025-05-15 10:09:32 - libs.log - INFO - г ½,0 +2025-05-15 10:09:32 - libs.log - INFO - йںŸ,0 +2025-05-15 10:10:08 - libs.log - INFO - ձ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ΢ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ۲ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - » ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ̬ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - 껨г ½,0 +2025-05-15 10:10:08 - libs.log - INFO - г ½,0 +2025-05-15 10:10:08 - libs.log - INFO - гල ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳг ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳǹ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳͨ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳչ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ˼볤ɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ųɳ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - ɳˮ ½,0 +2025-05-15 10:10:08 - libs.log - INFO - г ½,0 +2025-05-15 10:10:08 - libs.log - INFO - йںŸ,0 +2025-05-15 12:00:30 - core.log - INFO - ձ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳг ½,0 +2025-05-15 12:00:30 - core.log - INFO - ΢ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ۲ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - » ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ̬ ½,0 +2025-05-15 12:00:30 - core.log - INFO - 껨г ½,0 +2025-05-15 12:00:30 - core.log - INFO - г ½,0 +2025-05-15 12:00:30 - core.log - INFO - гල ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳг ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳǹ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳͨ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳչ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ˼볤ɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ųɳ ½,0 +2025-05-15 12:00:30 - core.log - INFO - ɳˮ ½,0 +2025-05-15 12:00:30 - core.log - INFO - г ½,0 +2025-05-15 12:00:30 - core.log - INFO - йںŸ,0 +2025-05-15 15:52:05 - core.log - INFO - ձ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳг ½,0 +2025-05-15 15:52:05 - core.log - INFO - ΢ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ۲ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - » ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ̬ ½,0 +2025-05-15 15:52:05 - core.log - INFO - 껨г ½,0 +2025-05-15 15:52:05 - core.log - INFO - г ½,0 +2025-05-15 15:52:05 - core.log - INFO - гල ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳг ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳǹ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳͨ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳչ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ˼볤ɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ųɳ ½,0 +2025-05-15 15:52:05 - core.log - INFO - ɳˮ ½,0 +2025-05-15 15:52:05 - core.log - INFO - г ½,0 +2025-05-15 15:52:05 - core.log - INFO - йںŸ,0 diff --git a/core/auth.py b/core/auth.py new file mode 100644 index 00000000..a6911bc7 --- /dev/null +++ b/core/auth.py @@ -0,0 +1,99 @@ +from datetime import datetime, timedelta +import jwt +from functools import wraps +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from typing import Optional +from passlib.context import CryptContext +from core.models import User as DBUser +from core.db import DB + +# 配置 +SECRET_KEY = "your-secret-key-here" # 生产环境应使用更安全的密钥 +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +# 密码哈希上下文 +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +def get_user(username: str) -> Optional[DBUser]: + """从数据库获取用户""" + session = DB.get_session() + try: + return session.query(DBUser).filter(DBUser.username == username).first() + finally: + session.close() + +def authenticate_user(username: str, password: str) -> Optional[DBUser]: + """验证用户凭据""" + user = get_user(username) + if not user or not pwd_context.verify(password, user.password_hash): + return None + return user + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + """创建JWT Token""" + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + +async def get_current_user(token: str = Depends(oauth2_scheme)): + """获取当前用户""" + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except jwt.PyJWTError: + raise credentials_exception + + user = get_user(username) + if user is None: + raise credentials_exception + + return { + "username": user.username, + "role": user.role, + "permissions": user.permissions, + "original_user": user + } + +def requires_role(role: str): + """检查用户角色的装饰器""" + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + current_user = kwargs.get('current_user') + if not current_user or current_user.get('role') != role: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Insufficient permissions" + ) + return await func(*args, **kwargs) + return wrapper + return decorator + +def requires_permission(permission: str): + """检查用户权限的装饰器""" + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + current_user = kwargs.get('current_user') + if not current_user or permission not in current_user.get('permissions', []): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Insufficient permissions" + ) + return await func(*args, **kwargs) + return wrapper + return decorator \ No newline at end of file diff --git a/core/config.py b/core/config.py new file mode 100644 index 00000000..3decb2b4 --- /dev/null +++ b/core/config.py @@ -0,0 +1,29 @@ +import yaml +import sys +config = {} +def get_config(): + global config + try: + with open('config.yaml', 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + return config + except Exception as e: + print(f"Error loading configuration file{e}") + sys.exit(1) + return [] +def get(key,default:any=None): + global config + if key in config: + return config[key] + else: + print("Key {} not found in configuration".format(key)) + if default is not None: + return default + return None + +config = get_config() +DEBUG=get("debug",False) +APP_NAME=get("app_name","we-mp-rss") + + +VERSION='1.0.0' \ No newline at end of file diff --git a/libs/db.py b/core/db.py similarity index 55% rename from libs/db.py rename to core/db.py index fd816429..2c5628e5 100644 --- a/libs/db.py +++ b/core/db.py @@ -1,33 +1,8 @@ from sqlalchemy import create_engine, Column, Integer, String, DateTime -from sqlalchemy.ext.declarative import declarative_base + from sqlalchemy.orm import sessionmaker from typing import Optional, List - -Base = declarative_base() - -class Article(Base): - __tablename__ = 'articles' - id = Column(String(255), primary_key=True) - mp_id = Column(String(255)) - title = Column(String(500)) - pic_url = Column(String(500)) - publish_time = Column(Integer) - created_at = Column(DateTime) - updated_at = Column(DateTime) # Changed from Integer to DateTime for consistency - is_export = Column(Integer) - -class Feeds(Base): - __tablename__ = 'feeds' - id = Column(String(255), primary_key=True) - mp_name = Column(String(255)) - mp_cover = Column(String(255)) - mp_intro = Column(String(255)) - status = Column(Integer) - sync_time = Column(DateTime) - update_time = Column(DateTime) - created_at = Column(DateTime) - updated_at = Column(DateTime) - faker_id = Column(String(255)) +from .models import Feed,Article class Db: def __init__(self): @@ -60,7 +35,7 @@ class Db: def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def add_article(self, article_data: dict) -> None: + def add_article(self, article_data: dict) -> bool: try: art = Article(**article_data) self.session.add(art) @@ -68,33 +43,43 @@ class Db: except Exception as e: self.session.rollback() print(f"Failed to add article: {e}") - return e - + return False + return True def get_articles(self,id:str=None,limit:int=30,offset:int=0) -> List[Article]: try: - # data=self.session.query(Feeds).filter_by(id=id).limit(10) + # data=self.session.query(Feed).filter_by(id=id).limit(10) data=self.session.query(Article).limit(limit).offset(offset) return data except Exception as e: - print(f"Failed to fetch feeds: {e}") + print(f"Failed to fetch Feed: {e}") return e - def get_all_mps(self) -> List[Feeds]: - """Get all Feeds records""" + def get_all_mps(self) -> List[Feed]: + """Get all Feed records""" try: - return self.session.query(Feeds).all() + return self.session.query(Feed).all() except Exception as e: - print(f"Failed to fetch feeds: {e}") + print(f"Failed to fetch Feed: {e}") return e - def get_mps(self,mp_id:str)->Optional[Feeds]: + def get_mps(self,mp_id:str)->Optional[Feed]: try: - data=self.session.query(Feeds).filter_by(id=mp_id).first() + data=self.session.query(Feed).filter_by(id=mp_id).first() # return type(data) return data except Exception as e: - print(f"Failed to fetch feeds: {e}") + print(f"Failed to fetch Feed: {e}") return e def get_faker_id(self,mp_id:str): data= self.get_mps(mp_id) return data.faker_id + + def get_session(self): + """获取数据库会话""" + if not self.session: + raise Exception("Database not initialized") + return self.session + +DB=Db() +from .config import config as cfg +DB.init(cfg.get("db")) \ No newline at end of file diff --git a/core/log.py b/core/log.py new file mode 100644 index 00000000..50bcb3ae --- /dev/null +++ b/core/log.py @@ -0,0 +1,22 @@ +from logging.handlers import RotatingFileHandler +import logging +global logger +# 创建logger对象 +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) # 设置最低日志级别 +# 创建文件处理器,每天一个文件,保留7天备份 +handler = RotatingFileHandler('app.log', maxBytes=1024*1024, backupCount=7) +handler.setLevel(logging.DEBUG) +# 创建控制台处理器 +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.INFO) +# 创建格式器并添加到处理器 +formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +handler.setFormatter(formatter) +console_handler.setFormatter(formatter) +# 将处理器添加到logger +logger.addHandler(handler) +logger.addHandler(console_handler) \ No newline at end of file diff --git a/core/models/__init__.py b/core/models/__init__.py new file mode 100644 index 00000000..40ed3ea5 --- /dev/null +++ b/core/models/__init__.py @@ -0,0 +1,3 @@ +from .article import Article +from .feed import Feed +from .user import User \ No newline at end of file diff --git a/core/models/article.py b/core/models/article.py new file mode 100644 index 00000000..0390de0a --- /dev/null +++ b/core/models/article.py @@ -0,0 +1,13 @@ +from .base import Base,Column,String,Integer,DateTime +class Article(Base): + __tablename__ = 'articles' + id = Column(String(255), primary_key=True) + mp_id = Column(String(255)) + title = Column(String(500)) + pic_url = Column(String(500)) + publish_time = Column(Integer) + created_at = Column(DateTime) + updated_at = Column(DateTime) # Changed from Integer to DateTime for consistency + is_export = Column(Integer) + + diff --git a/core/models/base.py b/core/models/base.py new file mode 100644 index 00000000..6ac51f66 --- /dev/null +++ b/core/models/base.py @@ -0,0 +1,3 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import create_engine, Column, Integer, String, DateTime,Date,ForeignKey,Boolean,Text,Enum,Table +Base = declarative_base() diff --git a/core/models/feed.py b/core/models/feed.py new file mode 100644 index 00000000..90b56ee9 --- /dev/null +++ b/core/models/feed.py @@ -0,0 +1,13 @@ +from .base import Base,Column,String,Integer,DateTime +class Feed(Base): + __tablename__ = 'feeds' + id = Column(String(255), primary_key=True) + mp_name =Column(String(255)) + mp_cover = Column(String(255)) + mp_intro = Column(String(255)) + status = Column(Integer) + sync_time = Column(DateTime) + update_time = Column(DateTime) + created_at = Column(DateTime) + updated_at = Column(DateTime) + faker_id = Column(String(255)) \ No newline at end of file diff --git a/core/models/user.py b/core/models/user.py new file mode 100644 index 00000000..65761cfe --- /dev/null +++ b/core/models/user.py @@ -0,0 +1,27 @@ +from .base import Base, Column, String, Integer, DateTime, Boolean +from sqlalchemy import JSON + +class User(Base): + __tablename__ = 'users' + id = Column(String(255), primary_key=True) + username = Column(String(50), unique=True, nullable=False) + password_hash = Column(String(255), nullable=False) + is_active = Column(Boolean, default=True) + role = Column(String(20), default='user') # admin/editor/user + permissions = Column(JSON, default=list) # 权限列表 + + # 原有字段保持不变 + mp_name = Column(String(255)) + mp_cover = Column(String(255)) + mp_intro = Column(String(255)) + status = Column(Integer) + sync_time = Column(DateTime) + update_time = Column(DateTime) + created_at = Column(DateTime) + updated_at = Column(DateTime) + faker_id = Column(String(255)) + + def verify_password(self, password: str) -> bool: + """验证密码""" + from core.auth import pwd_context + return pwd_context.verify(password, self.password_hash) \ No newline at end of file diff --git a/core/notice.py b/core/notice.py new file mode 100644 index 00000000..bb0de805 --- /dev/null +++ b/core/notice.py @@ -0,0 +1,41 @@ +import requests +import json +def send_dingtalk_markdown(webhook_url, title, text, is_at_all=False, at_mobiles=[]): + """ + 发送Markdown格式消息 + + 参数: + - webhook_url: 机器人Webhook地址 + - title: 消息标题 + - text: Markdown格式内容 + - is_at_all: 是否@所有人 + - at_mobiles: 要@的手机号列表 + """ + headers = {'Content-Type': 'application/json'} + data = { + "msgtype": "markdown", + "markdown": { + "title": title, + "text": text + }, + "at": { + "atMobiles": at_mobiles, + "isAtAll": is_at_all + } + } + + response = requests.post( + url=webhook_url, + headers=headers, + data=json.dumps(data) + ) + print(response.text) +# 使用示例 +# markdown_text = """### 项目状态报告 +# - **项目名称**: XX系统升级 +# - **当前状态**: ✅正常运行 +# - **异常情况**: 无 +# - [查看详情](http://example.com) +# """ +# webhook="https://oapi.dingtalk.com/robot/send?access_token=xxx" +# send_dingtalk_markdown(webhook, "项目状态通知", markdown_text) diff --git a/libs/rss.py b/core/rss.py similarity index 100% rename from libs/rss.py rename to core/rss.py diff --git a/libs/wx.py b/core/wx.py similarity index 84% rename from libs/wx.py rename to core/wx.py index 5db7153b..1adfcff8 100644 --- a/libs/wx.py +++ b/core/wx.py @@ -1,10 +1,10 @@ import requests -import yaml import json import re import datetime -import libs.db as db from datetime import datetime, timezone +import core.config as cfg +import core.db as db def dateformat(timestamp:int): # UTC时间对象 @@ -16,28 +16,26 @@ def dateformat(timestamp:int): t=(local_dt.strftime("%Y-%m-%d %H:%M:%S")) return t -with open("config.yaml", "r") as f: - config = yaml.safe_load(f) def get_Articles(faker_id:str): headers = { - "Cookie": config["cookie"], - "User-Agent": config["user_agent"] + "Cookie": cfg.get("cookie"), + "User-Agent": cfg.get("user_agent") } params = { "sub": "list", "sub_action": "list_ex", "begin": 0, - "count": config["count"], + "count": cfg.get("count"), "fakeid": faker_id, - "token": config["token"], + "token": cfg.get("token"), "lang": "zh_CN", "f": "json", "ajax": 1 } url = "https://mp.weixin.qq.com/cgi-bin/appmsgpublish" headers = { - "Cookie": config["cookie"], - "User-Agent": config["user_agent"] + "Cookie": cfg.get("cookie"), + "User-Agent": cfg.get("user_agent") } data={} try: @@ -64,7 +62,7 @@ def get_list(faker_id:str=None,mp_id:str=None,is_add:bool=False): try: data=data['publish_page']['publish_list'] wx_db=db.Db() - wx_db.init(config['db']) + wx_db.init(cfg.get('db')) for i in data: art=i['publish_info'] art=json.loads(art) @@ -84,8 +82,8 @@ def get_list(faker_id:str=None,mp_id:str=None,is_add:bool=False): } articles.append(article) if is_add: - wx_db.add_article(article) - print('添加成功') + isOk=wx_db.add_article(article) + print(f'添加成功{isOk}') except Exception as e: print(e,"出错了") diff --git a/job.py b/job.py index 535c4b6c..90f90f89 100644 --- a/job.py +++ b/job.py @@ -1,48 +1,51 @@ import schedule, time -import libs.wx as wx -import libs.db as db -import logging -from logging.handlers import RotatingFileHandler -import yaml -# 创建logger对象 -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) # 设置最低日志级别 +import core.wx as wx +import core.db as db +import core.config as cfg +import core.notice as notice +import random +import core.log as log -# 创建文件处理器,每天一个文件,保留7天备份 -handler = RotatingFileHandler('app.log', maxBytes=1024*1024, backupCount=7) -handler.setLevel(logging.DEBUG) - -# 创建控制台处理器 -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.INFO) - -# 创建格式器并添加到处理器 -formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' -) -handler.setFormatter(formatter) -console_handler.setFormatter(formatter) - -# 将处理器添加到logger -logger.addHandler(handler) -logger.addHandler(console_handler) - -with open('config.yaml', 'r') as f: - config = yaml.safe_load(f) +logger=log.logger +# 初始化数据库 wx_db=db.Db() -wx_db.init(config["db"]) +wx_db.init(cfg.get("db")) + + +# 获取公众号列表 mps=wx_db.get_all_mps() -print(mps) def do_job(): - print("执行任务") + print("开始更新") + all_count=0 + text=f" **时间**:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}\n" for item in mps: + + # 成功更新数量计数 + mps_count=0 faker_id=item.faker_id mp_id=item.id - print(item,faker_id,mp_id) - data=wx.get_list(faker_id,mp_id,1) - logger.info("开始执行函数") + print(f'正在更新公众号:{item.mp_name}({item.id})') + if not cfg.DEBUG: + # 获取对应公众号列表 + data=wx.get_list(faker_id,mp_id,0) + for art in data: + # 添加到数据库 + if wx_db.add_article(art): + mps_count=mps_count+1 + else: + print("调试模式,不进行更新") + + logger.info(f"{item.mp_name} 更新结束,共更新{mps_count}") + text+=f"* **{item.mp_name}**({mps_count})\n" + all_count=all_count+mps_count + + # 随机休眠 防止被封锁 + if not cfg.DEBUG: + time.sleep(random.randint(2,60)) + logger.info(f"所有公众号更新完成,共更新{all_count}条数据") + text+=f"\n所有公众号更新完成,共更新{all_count}条数据" + send_notice(text,cfg.get('app_name',default='we-mp-rss')) def start(): schedule.every(10).seconds.do(do_job) # 每10秒执行 # schedule.every.day.at("08:00").do(job) # 每天8点执行 @@ -50,6 +53,13 @@ def start(): schedule.run_pending() time.sleep(1) +def send_notice(text:str="",title:str=""): + markdown_text = f"""### {title} 通知 + {text} + """ + + webhook=cfg.get('notice')['dingding'] + notice.send_dingtalk_markdown(webhook, title, markdown_text) + if __name__ == '__main__': - # start() do_job() \ No newline at end of file diff --git a/libs/__pycache__/db.cpython-313.pyc b/libs/__pycache__/db.cpython-313.pyc deleted file mode 100644 index 6ffbcd32..00000000 Binary files a/libs/__pycache__/db.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/db.cpython-38.pyc b/libs/__pycache__/db.cpython-38.pyc deleted file mode 100644 index 382cb40e..00000000 Binary files a/libs/__pycache__/db.cpython-38.pyc and /dev/null differ diff --git a/libs/__pycache__/job.cpython-313.pyc b/libs/__pycache__/job.cpython-313.pyc deleted file mode 100644 index c618d5f4..00000000 Binary files a/libs/__pycache__/job.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/rss.cpython-313.pyc b/libs/__pycache__/rss.cpython-313.pyc deleted file mode 100644 index 1baf24e0..00000000 Binary files a/libs/__pycache__/rss.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/wx.cpython-313.pyc b/libs/__pycache__/wx.cpython-313.pyc deleted file mode 100644 index 54d9f713..00000000 Binary files a/libs/__pycache__/wx.cpython-313.pyc and /dev/null differ diff --git a/libs/__pycache__/wx.cpython-38.pyc b/libs/__pycache__/wx.cpython-38.pyc deleted file mode 100644 index f84e6c3c..00000000 Binary files a/libs/__pycache__/wx.cpython-38.pyc and /dev/null differ diff --git a/main.py b/main.py new file mode 100644 index 00000000..4fc34288 --- /dev/null +++ b/main.py @@ -0,0 +1,4 @@ +import uvicorn +if __name__ == '__main__': + uvicorn.run("web:app", host="0.0.0.0", port=8001, reload=True,reload_excludes=['static','web_ui']) + pass \ No newline at end of file diff --git a/migrations/add_user_auth_columns.py b/migrations/add_user_auth_columns.py new file mode 100644 index 00000000..965b8694 --- /dev/null +++ b/migrations/add_user_auth_columns.py @@ -0,0 +1,109 @@ +from core.db import DB +from core.models import User +from datetime import datetime +from sqlalchemy import text + +def upgrade(): + session = DB.get_session() + try: + # 检查表是否存在 + table_exists = session.execute( + text("SHOW TABLES LIKE 'users'") + ).fetchone() + + if not table_exists: + # 创建新表 + session.execute(text(""" + CREATE TABLE users ( + id VARCHAR(255) PRIMARY KEY, + username VARCHAR(50) UNIQUE, + password_hash VARCHAR(255), + is_active BOOLEAN DEFAULT TRUE, + role VARCHAR(20) DEFAULT 'user', + permissions JSON, + mp_name VARCHAR(255), + mp_cover VARCHAR(255), + mp_intro VARCHAR(255), + status INTEGER, + sync_time DATETIME, + update_time DATETIME, + created_at DATETIME, + updated_at DATETIME, + faker_id VARCHAR(255) + ) + """)) + else: + # 检查并添加缺失的列 + columns = session.execute(text(""" + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'users' + """)).fetchall() + existing_columns = {c[0] for c in columns} + + if 'username' not in existing_columns: + session.execute(text(""" + ALTER TABLE users + ADD COLUMN username VARCHAR(50) UNIQUE + """)) + + if 'password_hash' not in existing_columns: + session.execute(text(""" + ALTER TABLE users + ADD COLUMN password_hash VARCHAR(255) + """)) + + if 'is_active' not in existing_columns: + session.execute(text(""" + ALTER TABLE users + ADD COLUMN is_active BOOLEAN DEFAULT TRUE + """)) + + if 'role' not in existing_columns: + session.execute(text(""" + ALTER TABLE users + ADD COLUMN role VARCHAR(20) DEFAULT 'user' + """)) + + if 'permissions' not in existing_columns: + session.execute(text(""" + ALTER TABLE users + ADD COLUMN permissions JSON + """)) + + # 创建默认管理员账户 + from passlib.context import CryptContext + pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + from uuid import uuid4 + admin = User( + id=str(uuid4()), + username="admin", + password_hash=pwd_context.hash("admin123"), + is_active=True, + role="admin", + permissions=["*"], + created_at=datetime.now(), + updated_at=datetime.now() + ) + session.add(admin) + session.commit() + finally: + session.close() + +def downgrade(): + session = DB.get_session() + try: + session.execute(""" + ALTER TABLE users + DROP COLUMN username, + DROP COLUMN password_hash, + DROP COLUMN is_active, + DROP COLUMN role, + DROP COLUMN permissions + """) + session.commit() + finally: + session.close() + +if __name__ == "__main__": + upgrade() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2f5756fa..f3e0d81a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,6 @@ schedule==1.2.2 SQLAlchemy==2.0.40 typing_extensions==4.13.2 urllib3==2.2.3 +pyjwt[crypto]==2.8.0 +passlib==1.7.4 +python-multipart==0.0.7 \ No newline at end of file diff --git a/web.py b/web.py index 4cf27124..e02b5a65 100644 --- a/web.py +++ b/web.py @@ -1,62 +1,21 @@ -from fastapi import FastAPI,Response # 导⼊FastAPI,⽤于定义API +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from apis.auth import router as auth_router +from apis.user import router as user_router +from apis.article import router as article_router +from apis.wechat import router as wechat_router -import libs.wx as wx -import libs.db as db -import job as job -import yaml -from fastapi.responses import PlainTextResponse -app = FastAPI() # 创建FastAPI实例 - -# http请求方式类型:get、post、put、update、delete -# 浏览器默认访问的是get类型,如果使用其他形式访问 +app = FastAPI( + title="WeRSS API", + description="微信公众号RSS生成服务", + version="1.0.0" +) -# 不带参数的访问形式: -# 访问地址:http://127.0.0.1:8000 -@app.get("/") -async def test_one(): - return {"message": "MPRSS"} - -@app.get("/wx/job") -async def start_job(): - wx_db.init(config["db"]) - mps=wx_db.get_all_mps() - wx_db.close() - - return {"code":"ok","data":mps } - -@app.get("/wx/rss/{mp_id}") -async def wx_list(mp_id: str,update:int=0): - wx_db.init(config["db"]) - faker_id=wx_db.get_faker_id(mp_id) - data=wx.get_list(faker_id,mp_id,update) - wx_db.close() - return {"mp_id": mp_id,"data":data} - - -wx_db=db.Db() -with open("config.yaml", "r") as f: - config = yaml.safe_load(f) -#获取所有需要同步的公众号 -@app.get("/wx/mps") -async def wx_mps(): - wx_db.init(config["db"]) - data=wx_db.get_all_mps() - for i in data: - print(i.mp_name) - wx_db.close() - return {"code": "ok","data":data} -import libs.rss as rss -@app.get("/wx/feeds") -async def wx_mps(offset:int=0,limit:int=5): - wx_db.init(config["db"]) - data=wx_db.get_articles(limit=limit,offset=offset) - rss_data=[] - for item in data: - print(item.id) - rss_data.append({"title":item.title,"id":item.id,"link":"https://mp.weixin.qq.com/s/"+item.id,"updated":item.created_at.isoformat()}) - wx_db.close() - _rss_data=rss.generate_rss(rss_data,"rss.xml") - print(rss_data) - # return Response(content=_rss_data, media_type="application/xml") - return Response(content=_rss_data, media_type="text/plan") +# 集成路由 +app.include_router(auth_router) +app.include_router(user_router) +app.include_router(article_router) +app.include_router(wechat_router) +# 静态文件服务 +app.mount("/", StaticFiles(directory="static", html=True), name="static") \ No newline at end of file diff --git a/web_ui/.gitignore b/web_ui/.gitignore new file mode 100644 index 00000000..76438e01 --- /dev/null +++ b/web_ui/.gitignore @@ -0,0 +1,2 @@ +*.log +node_modules/ \ No newline at end of file diff --git a/web_ui/README.md b/web_ui/README.md new file mode 100644 index 00000000..bcaa9b14 --- /dev/null +++ b/web_ui/README.md @@ -0,0 +1,71 @@ +# 微信公众号订阅助手 - WebUI + +基于Vue3 + Vite + ArcoDesign构建的前端界面 + +## 功能列表 +- 用户登录/登出 +- 文章列表展示 +- 添加公众号订阅 +- 修改用户信息 +- 修改密码 + +## 手动安装依赖 + +由于系统限制,请按以下步骤手动安装依赖: + +1. 打开PowerShell作为管理员 +2. 运行以下命令更改执行策略: +```powershell +Set-ExecutionPolicy RemoteSigned -Scope CurrentUser +``` +3. 安装项目依赖: +```powershell +npm install +``` +4. 安装ArcoDesign Vue: +```powershell +npm install --save @arco-design/web-vue @arco-design/web-vue-icon @arco-design/color +``` +5. 安装开发依赖: +```powershell +npm install --save-dev less less-loader +``` + +## 环境变量配置 + +创建`.env`文件: +```ini +VITE_API_BASE_URL=http://your-api-server.com +``` + +## 运行项目 + +开发模式: +```bash +npm run dev +``` + +生产构建: +```bash +npm run build +``` + +## 项目结构 + +``` +src/ +├── api/ # API接口 +├── assets/ # 静态资源 +├── components/ # 公共组件 +├── router/ # 路由配置 +├── views/ # 页面组件 +├── App.vue # 根组件 +└── main.ts # 入口文件 +``` + +## 技术栈 +- Vue 3 +- Vite +- ArcoDesign Vue +- TypeScript +- Axios \ No newline at end of file diff --git a/web_ui/index.html b/web_ui/index.html new file mode 100644 index 00000000..4a203c28 --- /dev/null +++ b/web_ui/index.html @@ -0,0 +1,13 @@ + + + + + + + 微信公众号订阅助手 + + +
+ + + \ No newline at end of file diff --git a/web_ui/public/vite.svg b/web_ui/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/web_ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/src/App.vue b/web_ui/src/App.vue new file mode 100644 index 00000000..a62e0913 --- /dev/null +++ b/web_ui/src/App.vue @@ -0,0 +1,213 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/api/article.ts b/web_ui/src/api/article.ts new file mode 100644 index 00000000..3e6a9b89 --- /dev/null +++ b/web_ui/src/api/article.ts @@ -0,0 +1,36 @@ +import http from './http' + +export interface Article { + id: number + title: string + content: string + account_name: string + publish_time: string + status: string + link: string +} + +export interface ArticleListParams { + page?: number + pageSize?: number + search?: string + status?: string + account_id?: number +} + +export interface ArticleListResult { + total: number + data: Article[] +} + +export const getArticles = (params: ArticleListParams) => { + return http.get('/mps', { params }) +} + +export const getArticleDetail = (id: number) => { + return http.get
(`/feeds/${id}`) +} + +export const deleteArticle = (id: number) => { + return http.delete(`/feeds/${id}`) +} \ No newline at end of file diff --git a/web_ui/src/api/auth.ts b/web_ui/src/api/auth.ts new file mode 100644 index 00000000..654a8d8c --- /dev/null +++ b/web_ui/src/api/auth.ts @@ -0,0 +1,33 @@ +import http from './http' + +export interface LoginParams { + username: string + password: string +} + +export interface LoginResult { + token: string + userInfo: { + username: string + avatar: string + } +} + +export const login = (data: LoginParams) => { + const formData = new URLSearchParams() + formData.append('username', data.username) + formData.append('password', data.password) + return http.post('/auth/login', formData, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }) +} + +export const logout = () => { + return http.post('/auth/logout') +} + +export const getCurrentUser = () => { + return http.get('/auth/user') +} \ No newline at end of file diff --git a/web_ui/src/api/http.ts b/web_ui/src/api/http.ts new file mode 100644 index 00000000..d6985647 --- /dev/null +++ b/web_ui/src/api/http.ts @@ -0,0 +1,40 @@ +import axios from 'axios' +import { Message } from '@arco-design/web-vue' + +const service = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 10000 +}) + +// 请求拦截器 +service.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + (response) => { + const res = response.data + console.log(res.code) + if (res.code !=0) { + Message.error(res.message || 'Error') + return Promise.reject(new Error(res.message || 'Error')) + } + return res.data + }, + (error) => { + Message.error(error.message || '请求失败') + return Promise.reject(error) + } +) + +export default service \ No newline at end of file diff --git a/web_ui/src/api/subscription.ts b/web_ui/src/api/subscription.ts new file mode 100644 index 00000000..4078ade2 --- /dev/null +++ b/web_ui/src/api/subscription.ts @@ -0,0 +1,35 @@ +import http from './http' + +export interface Subscription { + id: number + name: string + accountId: string + rssUrl: string + category: string + description: string + status: string +} + +export interface AddSubscriptionParams { + name: string + accountId: string + rssUrl: string + category: string + description?: string +} + +export const getSubscriptions = () => { + return http.get('/wx/mps') +} + +export const addSubscription = (data: AddSubscriptionParams) => { + return http.post('/wx/mps', data) +} + +export const deleteSubscription = (id: number) => { + return http.delete(`/wx/mps/${id}`) +} + +export const updateSubscription = (id: number, data: Partial) => { + return http.put(`/wx/mps/${id}`, data) +} \ No newline at end of file diff --git a/web_ui/src/api/user.ts b/web_ui/src/api/user.ts new file mode 100644 index 00000000..c5876f64 --- /dev/null +++ b/web_ui/src/api/user.ts @@ -0,0 +1,33 @@ +import http from './http' + +export interface UserInfo { + username: string + nickname: string + email: string + avatar: string +} + +export interface ChangePasswordParams { + old_password: string + new_password: string +} + +export const getUserInfo = () => { + return http.get('/user/info') +} + +export const updateUserInfo = (data: Partial) => { + return http.put('/user/info', data) +} + +export const changePassword = (data: ChangePasswordParams) => { + return http.put('/user/password', data) +} + +export const uploadAvatar = (file: FormData) => { + return http.post<{ url: string }>('/user/avatar', file, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} \ No newline at end of file diff --git a/web_ui/src/assets/logo.svg b/web_ui/src/assets/logo.svg new file mode 100644 index 00000000..429190cc --- /dev/null +++ b/web_ui/src/assets/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/web_ui/src/assets/vue.svg b/web_ui/src/assets/vue.svg new file mode 100644 index 00000000..770e9d33 --- /dev/null +++ b/web_ui/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/src/components/AddSubscriptionModal.vue b/web_ui/src/components/AddSubscriptionModal.vue new file mode 100644 index 00000000..f532f91c --- /dev/null +++ b/web_ui/src/components/AddSubscriptionModal.vue @@ -0,0 +1,142 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/components/HelloWorld.vue b/web_ui/src/components/HelloWorld.vue new file mode 100644 index 00000000..b58e52b9 --- /dev/null +++ b/web_ui/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/web_ui/src/main.ts b/web_ui/src/main.ts new file mode 100644 index 00000000..63743981 --- /dev/null +++ b/web_ui/src/main.ts @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' + +// 导入 ArcoDesign +import ArcoVue from '@arco-design/web-vue' +// 导入 ArcoDesign 图标 +// import ArcoVueIcon from '@arco-design/web-vue-icon' +// 导入 ArcoDesign 样式 +import '@arco-design/web-vue/dist/arco.css' +// 导入自定义样式 +import './style.css' + +const app = createApp(App) + +// 注册 ArcoDesign +app.use(ArcoVue) +// 注册图标组件 +// app.use(ArcoVueIcon) +// 注册路由 +app.use(router) + +app.mount('#app') \ No newline at end of file diff --git a/web_ui/src/router/index.ts b/web_ui/src/router/index.ts new file mode 100644 index 00000000..21183dbd --- /dev/null +++ b/web_ui/src/router/index.ts @@ -0,0 +1,56 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Login from '../views/Login.vue' +import ArticleList from '../views/ArticleList.vue' +import ChangePassword from '../views/ChangePassword.vue' +import EditUser from '../views/EditUser.vue' +import AddSubscription from '../views/AddSubscription.vue' + +const routes = [ + { + path: '/login', + name: 'Login', + component: Login + }, + { + path: '/', + name: 'ArticleList', + component: ArticleList, + meta: { requiresAuth: true } + }, + { + path: '/change-password', + name: 'ChangePassword', + component: ChangePassword, + meta: { requiresAuth: true } + }, + { + path: '/edit-user', + name: 'EditUser', + component: EditUser, + meta: { requiresAuth: true } + }, + { + path: '/add-subscription', + name: 'AddSubscription', + component: AddSubscription, + meta: { requiresAuth: true } + } +] + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes +}) + +// 路由守卫 +router.beforeEach((to, from, next) => { + const isAuthenticated = localStorage.getItem('token') + + if (to.meta.requiresAuth && !isAuthenticated) { + next('/login') + } else { + next() + } +}) + +export default router \ No newline at end of file diff --git a/web_ui/src/style.css b/web_ui/src/style.css new file mode 100644 index 00000000..9a7cf293 --- /dev/null +++ b/web_ui/src/style.css @@ -0,0 +1,38 @@ +/* 全局样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + width: 100%; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + width: 100%; + height: 100%; +} + +/* 链接样式 */ +a { + color: #165DFF; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* 自定义ArcoDesign主题变量 */ +:root { + --primary-color: #165DFF; + --success-color: #00B42A; + --warning-color: #FF7D00; + --danger-color: #F53F3F; +} \ No newline at end of file diff --git a/web_ui/src/views/AddSubscription.vue b/web_ui/src/views/AddSubscription.vue new file mode 100644 index 00000000..4285c9db --- /dev/null +++ b/web_ui/src/views/AddSubscription.vue @@ -0,0 +1,148 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/views/ArticleList.vue b/web_ui/src/views/ArticleList.vue new file mode 100644 index 00000000..52f4a66a --- /dev/null +++ b/web_ui/src/views/ArticleList.vue @@ -0,0 +1,195 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/views/ChangePassword.vue b/web_ui/src/views/ChangePassword.vue new file mode 100644 index 00000000..1eee9276 --- /dev/null +++ b/web_ui/src/views/ChangePassword.vue @@ -0,0 +1,142 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/views/EditUser.vue b/web_ui/src/views/EditUser.vue new file mode 100644 index 00000000..6f9e89f9 --- /dev/null +++ b/web_ui/src/views/EditUser.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/views/Login.vue b/web_ui/src/views/Login.vue new file mode 100644 index 00000000..cec1fc26 --- /dev/null +++ b/web_ui/src/views/Login.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/web_ui/src/vite-env.d.ts b/web_ui/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/web_ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web_ui/vite.config.ts b/web_ui/vite.config.ts new file mode 100644 index 00000000..3ebc51fc --- /dev/null +++ b/web_ui/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + optimizeDeps: { + include: ['@arco-design/web-vue', '@arco-design/web-vue-icon'] + }, + css: { + preprocessorOptions: { + less: { + javascriptEnabled: true, + } + } + }, + server: { + port: 3000, + open: true, + proxy: { + '/api': { + target: 'http://127.0.0.1:8001', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '/wx') + } + } + } +}) \ No newline at end of file