feat: AI虚拟用户新闻互动系统 v1.3.0 初始提交

- 虚拟用户管理(昵称/头像/性别/简介/邮箱同步到目标平台)
- AI互动调度(点赞/收藏/评论/转发)
- 日志时间改为北京时间
- 评论达上限后继续执行点赞收藏转发
- 一键登出全部功能
- 浅色主题UI
This commit is contained in:
stefanfeng
2026-03-31 10:20:57 +08:00
commit 0cfc9bf9c8
53 changed files with 8457 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
"""AES加密工具 - 用于密码和API Key加密存储"""
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from app.core.config import settings
def _get_key() -> bytes:
"""获取32字节AES密钥"""
key = settings.AES_KEY.encode("utf-8")
return hashlib.sha256(key).digest()
def encrypt(plaintext: str) -> str:
"""AES-CBC加密"""
if not plaintext:
return ""
key = _get_key()
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(plaintext.encode("utf-8"), AES.block_size))
iv = base64.b64encode(cipher.iv).decode("utf-8")
ct = base64.b64encode(ct_bytes).decode("utf-8")
return f"{iv}:{ct}"
def decrypt(ciphertext: str) -> str:
"""AES-CBC解密"""
if not ciphertext or ":" not in ciphertext:
return ""
try:
iv_str, ct_str = ciphertext.split(":", 1)
key = _get_key()
iv = base64.b64decode(iv_str)
ct = base64.b64decode(ct_str)
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ct), AES.block_size)
return pt.decode("utf-8")
except Exception:
return ""
def mask_password(password: str) -> str:
"""密码脱敏显示"""
if not password:
return ""
if len(password) <= 2:
return "*" * len(password)
return password[0] + "*" * (len(password) - 2) + password[-1]