Skip to content

Repository files navigation

aism · PHP + MySQL 微博式社区(前后端完整实现)

首页信息流


✨ 网站特点

一个「能真正跑起来」的社区,不是脚手架 demo —— 从信息流到一整套积分经济都接通了。

  • 🧩 零依赖、开箱即跑:后端是原生 PHP(PDO),没有框架、没有 composer install、没有构建步骤。 默认 SQLite,docker compose up 或一条 php 命令就能起。要上生产再切 MySQL。
  • 📰 完整的微博式社区:信息流(最新/热门/多类型筛选/游标分页)、发帖框(图文/视频/音频/投票/ 红包/隐藏内容/#话题#/@提及,都带联想)、两级楼中楼评论、长文编辑器、个人主页、私信、通知中心、 关注/收藏/转发/举报/屏蔽、软删除 + 回收站、PWA、明暗主题、放歌不断的 SPA 站内导航。
  • 🪙 一整套积分经济:积分/经验规则引擎(发帖、被赞、签到都涨,全写进明细)、等级、 每日签到、称号抽奖(按档加权的老虎机)、双色球(抓真实开奖号)、红包(拼手气/平均,零和)、 VIP 套餐、认证中心(站长认证会真去抓域名校验文件)、自动勋章、黄V/蓝V。
  • 🛒 积分商城:用户开店、用积分买卖,平台按比例抽成,剩下的到卖家账上。有等级门槛(站长后台设)、 库存、自动/手动发货、审核、待发货订单可原路退款。账目严格守恒(有 41 项断言测试守着)。
  • 🎮 小程序平台:可以容纳用户提交的网页小游戏,每个都跑在不给同源的沙箱 iframe 里 —— 读不到本站 Cookie、调本站接口不带凭据、够不着外层页面。充值/账号面板由平台代画(沙箱里的 第三方代码画不了假的收款框)。体量太大装不进沙箱的(比如三国杀那种带 IndexedDB 硬依赖的) 可以走「站外源」用独立端口托管。本仓库不预装任何游戏,只带平台本身,你自己上传即可(见下文)。
  • 🛡️ 后台四级权限:游客/版主/管理员/站长逐级放权,动作逐项白名单,站长专属项(提权、模拟登录、 改设置、动账)一条都不外放;封禁即时生效、操作留痕。
  • 🔒 安全:服务端会话 + CSRF、登录失败限流、bcrypt 存密码、越权分级、 富文本/头像/上传的 XSS 与 SSRF 防护、SVG 上传已禁、报错不泄漏 SQL。经过一轮渗透测试(见文末)。

📸 界面预览

积分商城(用户开店) 小程序平台(沙箱)
积分商城 小程序平台
用积分买卖,平台抽成、等级门槛、自动/手动发货、可原路退款 网页小游戏跑在不给同源的沙箱里。截图里那两个是作者自己上传的,仓库不带任何游戏
积分与等级 称号抽奖
积分与等级 称号抽奖
每一笔涨跌都写进明细,规则表直接摊给用户看 26 个称号分五档加权,抽到重复退一半积分;「至尊」档抽不到,只能靠攒
管理后台 暗色主题
管理后台 暗色主题
20+ 个管理页,四级身份逐项放权,站长专属项一条都不外放 全站明暗双主题,跟随系统或手动切

🐳 Docker 一键部署

装了 Docker 就一条命令:

docker compose up          # 首次会构建镜像 + 自动建库,约 1–2 分钟

然后浏览器打开 http://localhost:8080 —— 首次已写入演示数据,用 admin / ChangeMe123! 登录(登录后请尽快改密码)。

  • 默认 SQLite,不需要额外的数据库容器;数据落在具名卷里,容器重建也不丢。
  • 想用 MySQL/MariaDBdocker compose --profile mysql up,并按 docker-compose.yml 里的注释给 app 补上 DB_* 环境变量。
  • 内置 PHP 服务器够自托管和演示用;对外扛量请按下方「部署示例」换成 Nginx + PHP-FPM。

不想用 Docker 就看下面的「快速开始」——同样零配置。


快速开始(SQLite,零配置)

需要 PHP ≥ 8.1,并启用 pdo_sqlitegdmbstringfileinfo 扩展。 用到小程序(用户提交网页小游戏)还需要 zip 扩展,并把上传上限调大:

; 一个 Cocos / Unity 的网页构建产物动辄二三十兆、上千个文件
upload_max_filesize = 64M
post_max_size       = 72M
max_execution_time  = 120

post_max_size 一定要比 upload_max_filesize 大一截:请求体超过它时 PHP 会把 $_POST$_FILES 一起清空且不报错,表单看起来像是什么都没填。

cd aism
php bin/console.php serve 8080        # 或:php -S 127.0.0.1:8080 -t public public/index.php

打开 http://127.0.0.1:8080 即可。首次访问会自动建表并写入演示数据AUTO_MIGRATE=1)。

演示管理员账号admin / ChangeMe123!(首次登录后请尽快改密码)。


切换到 MySQL(生产)

  1. 建库建号:

    CREATE DATABASE aism CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'aism'@'127.0.0.1' IDENTIFIED BY '你的密码';
    GRANT ALL PRIVILEGES ON aism.* TO 'aism'@'127.0.0.1';
  2. 用环境变量指定连接(PHP 不自动读 .env,见 .env.example):

    export DB_DRIVER=mysql DB_HOST=127.0.0.1 DB_DATABASE=aism \
           DB_USERNAME=aism DB_PASSWORD='你的密码' APP_DEBUG=0
    php bin/console.php migrate      # 建表 + 演示数据
  3. 用 Nginx + PHP-FPM 部署时,把 public/ 作为站点根,所有非静态请求交给 public/index.php(下方有示例)。


目录结构

aism/
├─ public/                 # 站点根(唯一对外暴露的目录)
│  ├─ index.php            # 前端控制器 + 静态直通
│  ├─ static/{css,js,img}  # 复用的原版前端资源 + app-extra.css(补样式)
│  └─ uploads/             # 用户上传(按 年月 分桶)
├─ app/
│  ├─ bootstrap.php        # 装载配置/会话/自动加载/自动迁移
│  ├─ Router.php View.php  # 极简路由 + 模板渲染
│  ├─ Auth.php Database.php helpers.php
│  ├─ routes.php           # 全部路由表
│  ├─ Models/              # 领域层(Posts/Comments/Users/Uploads/...)
│  ├─ Controllers/         # 页面控制器 + JSON API 控制器
│  └─ Views/               # 布局 + 页面 + 片段(feed/评论/发帖框等)
├─ config/config.php       # 全部配置(可被环境变量覆盖)
├─ database/               # schema.sqlite.sql / schema.mysql.sql
├─ storage/                # SQLite 数据 + 缓存 + 用户提交的小程序(均不进仓库)
├─ tests/                  # 隔离库跑的账目/权限测试(如 shop_test.php)
├─ bin/console.php         # 迁移 / 播种 / 起服务 / 各种维护命令
├─ Dockerfile              # 一键部署镜像(php:8.3-cli + 所需扩展)
└─ docker-compose.yml      # docker compose up 即起(默认 SQLite)

已实现的功能

内容与互动

  • 信息流:最新 / 热门、原创/图片/视频/音乐/文章筛选、高级搜索、游标分页;版块 / 话题 / 用户 / 搜索 / 收藏 多种范围。

  • 发帖框:灰底圆角输入区 + 右下角提示与字数;工具条 表情 / 图片 / 视频 / 音频 / 话题 / 好友,其余(附件、投票、红包、隐藏内容)收进「更多」;版块与可见范围是自绘下拉(原生 <select> 的选项列表由系统绘制,暗色模式下改不了配色)。一份模板三种形态:内联、整页 is-full(只留图标 42×42)、编辑弹层 is-edit(去卡片外壳、收掉 @)。

  • 发帖:文字、图片、视频、音频、附件、外链、#话题#、@提及(都带联想补全)、投票、红包、隐藏内容;桌面端首页内联发帖框,移动端整页发帖(/compose 在宽屏自动跳 /#composer)。

  • 编辑已发内容:帖子「···」→ 编辑 打开弹层(GET /api/posts/{id}/edit),原文、图片、附件、音视频全部回填;普通用户 30 分钟内最多改 5 次,会员和管理员不限。

  • 长文:所见即所得编辑器(/article/new/article/{id}/edit),Markdown 存储 + 白名单渲染,自动保存、字数统计、封面、导语、发布设置、转回草稿、预览、删除;发布后镜像成信息流里的文章卡片

  • 互动:点赞、收藏、转发(引用)、关注(含互关)、作者置顶、管理员首页置顶、软删除 + 回收站恢复、举报、屏蔽、用户悬浮名片。

  • 评论:两级楼中楼、热/新排序、翻页、图片评论、回复、作者置顶、软删、四档评论权限。

  • 个人主页 /u/{uid}:封面(上传更换 / 恢复默认)+ 访问量、110px 悬挂头像、内容/关注/粉丝/转评赞四项计数、仅自己可见的昨日发博 / 昨日阅读 / 昨日互动 / 累计阅读、简介与加入时间、六个分类页(全部 / 相册 / 视频 / 文章 / 赞过 / 收藏,后两个仅本人)、主页内搜索(关键字 + 类型 + 日期)、等级 / 积分 / 勋章卡片;粉丝与关注列表在 /u/{uid}/followers/u/{uid}/following

  • 个人中心 /me/*:积分与等级(等级进度 + 「怎么获得积分」规则表 + 积分明细分页)、我的勋章(点选佩戴,最多 3 枚,去抖后自动存)、我的收藏、VIP会员、我的收益、申请认证,外加左栏底部通往 /settings/* 的出口;/rank 是积分排行榜。

  • 积分 / 经验规则引擎Models/Points.php 是唯一入口,全部写进 point_logs 明细):

    规则 积分 上限
    注册账号 +20 不限次数
    每日登录 +5 每天最多 1 次
    发布内容 +5 每天最多 10 次
    发表评论 +2 每天最多 10 次
    内容被赞 +1 每天最多 50 次,同一对象只计一次
    内容被转发 +2 每天最多 30 次,同一对象只计一次
    被关注 +2 每天最多 30 次,同一对象只计一次
    内容被删(后台删) -5 不限次数
    每日签到 4 + 连续天数(第 7 天起 11) 每天 1 次

    「同一对象只计一次」靠 point_logs.target 去重(取消赞再点不刷分,换个人赞同一条照算);扣分不会把余额扣成负数,也不扣经验。

  • 等级与经验:经验只增不减(花掉积分不会降级),等级由经验现算 —— LEVEL_STEPS = [0, 50, 200, 500, 1200, 3000]

  • 收益:付费内容按站长设的 author_share(默认 70%)分成,积分解锁全额进作者余额;「我的收益」的收支明细是从订单 / 解锁 / 红包记录现算的,没有另一份流水表可以对不上。

账号与安全

  • 注册 / 登录 / 退出、服务端会话 + CSRF(X-CSRF-Token / _token)。
  • 两步验证(TOTP):符合 RFC 6238(已用官方测试向量校验),兼容 Google Authenticator;含 8 个一次性恢复码、关闭与重新生成。
  • 邮箱绑定:验证码 + 60 秒限流;未配置发信时验证码回显并写入 storage/mail.log
  • 邀请码:每人 5 个未使用额度、注册消耗、邀请人奖励;可在后台切换为「仅邀请注册」。
  • 封禁即时生效(下一次请求就退出登录)、登录日志、账号注销(软删 + 释放身份)。

变现

  • 隐藏内容四种模式(积分 / 现金 / 评论可见 / 会员可见)、会员套餐下单、收银台 3 秒轮询、sync=1 补单。
  • 红包(平均 / 拼手气):发的时候就从发的人余额里扣掉,余额不够直接拦在发帖之前;发的人自己也能抢(钱已经扣过了,抢到的是自己刚付出去的那份,全程零和);同一个人只能抢一次;到期没抢完的部分退回发的人(读到即结算,幂等)。
  • 认证中心:个人 / 机构 / 站长三类;站长认证会真的去抓取 你的域名/aism-verify.txt 校验归属,通过即时生效;工本费用积分支付。

积分玩法

  • 称号抽奖:26 个称号分六档(普通/稀有/史诗/传说/限定/至尊),按档加权、同档等概率平分,所以往一档里加称号不会抬高这一档的爆率;十连一次结算、重复退还部分积分、老虎机式转盘(全程只动 transform,等接口的时间藏在动画里)。
  • 至尊称号「富可敌国」:抽不到(权重 0),只能攒 —— 持有一百万积分集齐奖池里的全部称号,满足任意一条自动到手并自动佩戴。
  • 自动勋章:25 枚勋章 / 14 条规则,全部从已有数据现算(不开计数器,免得跟删帖、封号、迁移对不上);12 个触发点 + 打开勋章墙时全量补发一次;四档稀有度对应四档特效。
  • 大 V 认证:黄 V / 蓝 V,认证说明可在后台逐个填写;前台每颗 V 可点开一张认证卡(只回公开字段,不回申请材料里的真名和证件号)。
  • 双色球:抓真实开奖号(多数据源,实测只有 500.com 还稳定)、自选/机选、六档奖金、按期结算;概率与期望回报在页面上明写。
  • 全网热榜:聚合 8 个平台(微博/百度/知乎/B站/抖音/头条/GitHub/IT之家),每个源都配了备用接口,一个挂了不会把整块榜单变白;带 ↑n/↓n/新 的排名变化。

积分商城 /shop

  • 用户开店:谁都能发布商品,买家用积分付款,平台按站长设的比例抽成(默认 5%),剩下的到卖家账上。
  • 发布门槛:要到某个等级才能发(默认 Lv.3,站长在 /admin/shop 里调);等级靠经验攒, 换小号刷不出来。每人同时在售件数有上限。
  • 库存与发货:库存可限量 / 不限量(-1);发货分「自动」(付款即给卡密/链接)和「联系卖家」两种。
  • 订单:卖家可标记发货、买卖双方在「待发货」阶段都能取消退款;退款时买家永远先拿回全款, 卖家余额不够就由站方垫付并记账 —— 买家不被卡住是这里唯一不妥协的规则。
  • 账目守恒:一笔成交在明细里买卖双方各留一行;tests/shop_test.php 有 41 项断言守着 「钱不会凭空多也不会凭空少」,包括并发下不超卖、余额不足回滚库存、第三方看不到发货内容。

小程序平台 /mini

  • 可容纳用户提交的网页小游戏 / 小工具,后台审核后上架;一格一个,点开就玩,不用下载安装。
  • 沙箱隔离是安全根基:每个小程序跑在 sandbox="allow-scripts"故意不给 allow-same-origin 的 iframe 里 —— 拿到的是「不透明源」,读不到本站 Cookie、调本站接口不带凭据、够不着外层页面 DOM。
  • 充值/账号面板由平台代画:花积分和看账号的界面不交给沙箱里的第三方代码(它画个一模一样的框 问你要密码,你分不出真假)—— 跟微信小程序把支付交给原生弹窗是同一个道理。用积分 1:1 兑换游戏币。
  • 两类承载:普通小游戏走沙箱;体量太大或对 IndexedDB/ServiceWorker 有硬依赖的(如三国杀那种 上千文件、1GB 素材的),走「站外源」—— 由 nginx 在独立端口托管,独立端口即独立源, 隔离性比同源更强(详见 app/Models/MiniApps.php 里的说明)。
  • 自带后端的小游戏:允许小程序带自己的 Node/socket.io 服务,站长在后台登记端口,nginx 按 /ms/<端口>/ 反代 —— 客户端只认 location.host,服务器 IP 变了也不用改客户端包。
  • 本仓库不预装任何游戏(游戏文件体积大且多为第三方版权)。平台代码是自带的, 上架自己的小程序有两条路:① 后台/前台提交 zip 包/mini/new,会解压进 storage/miniapps/{id}); ② 站外源——把游戏部署到独立端口,在 MiniApps::EXTERNAL_PORTS 登记。

其它

  • 私信(8 秒轮询)、通知中心(分类 + 未读 30 秒轮询,进入即已读、红点当场清)、签到积分、徽章墙与佩戴、音乐广场、热搜榜、话题联想、链接卡片(真实抓取 OG 标签,带 SSRF 防护)、PWA、明暗主题、SPA 站内导航(放歌不中断)。

管理后台 /admin

四级身份与逐项权限

身份 users.role 能做什么
普通用户 0 没有任何管理权限
版主 1 只管自己负责的版块:删帖、删评论、处理本版块举报、板块内禁言
管理员 2 权限由站长逐项勾选(16 项),勾到哪儿管到哪儿
站长 3 全部,外加设置别人的身份与权限

设计上有几处是刻意为之,改代码前值得先读一遍 app/Models/Perm.php 顶上的注释:

  • is_admin 保留原意「≥2 级」,版主那一级它是 0。 全站二十来处旧判断读的是这一列,让版主也置 1 会让那些判断一起静悄悄放行。加一级身份不该让任何一处旧判断变松。
  • 权限判断在 switch 之前一次做完Perm::denyReason()),不散在 40 多个 case 里;Perm::ACTIONS白名单,没登记的动作一律拒绝。
  • 站点设置、模拟登录、设置别人的权限这三件事不做成可勾选项 —— 它们等于站长本人。
  • 「对方级别不低于你就动不了」是严格大于,所以两个管理员不能互相封禁、也没人能对自己动手。
  • 版主看到的列表是在 SQL 里限定的,不是把别的行藏起来 —— 列表能翻到 id 就等于能删(删是按 id 提交的),列表和动作必须用同一套边界。
  • 版主只能软删(移入回收站),不能彻底删:给版主的工具里不该有任何一件事是站长撤不回来的。

页面

自带一套独立样式(app.js 本身就把 SPA 导航排除在 /admin 之外),共 20 个页面,没勾到的页面在侧边栏里不出现、直接输网址也是 403

页面 权限键 能做什么
控制台 用户/内容/评论/举报/认证/营收 KPI、近 14 天趋势;「最近操作」要 logs 权限才看得到
用户 users 搜索(含按 IP 搜、覆盖登录历史)、封禁/解封、发认证、发徽章、加减积分(带备注)、开会员、重置密码;身份与权限只有站长看得见
内容 / 评论 content 搜索、删除(软/彻底)、回收站恢复、首页置顶
举报 reports 待处理/已处理,一键删被举报内容、标记处理、驳回
我的版块 版主 / boards 负责的版块一览 + 板块内禁言名单(禁言只在一个版块里生效)
认证 verifies 查看申请资料与证明图、通过 / 驳回(含备注)
订单 orders 按状态筛选、手动标记已支付(补单)
版块 boards 增删版块、任免版主(被任命自动升为版主,管的最后一个版块被撤掉自动降回普通用户)
话题 / 公告 / 会员套餐 topics announce plans 增删改
徽章 badges 图标、颜色、稀有度、自动发放条件与目标值
称号 titles 奖池增删改、稀有度(含「抽不到」那一档的说明)
全网热榜 hotfeeds 手动抓取、按平台开关
双色球 ssq 拉取开奖、手工结算、经营概况
广告位 ads 6 个位置,文字 / 图片 / 自定义 HTML(HTML 会过滤脚本与 javascript:
媒体库 media 全站文件浏览与删除
站点设置 站长专属 站名、描述、关键词、开放注册、仅邀请注册、积分名称、页脚等
日志 logs 管理操作审计 + 登录日志

所有写操作都记入 admin_logs(含被拒绝的越权尝试),可在「日志」页追溯。

视频转码与网易云导入

视频转码(需要 ffmpeg)

apt install ffmpeg          # 装上即自动启用,无需改配置

上传视频时同步做两件轻活:用 ffprobe 探测真实时长/分辨率(浏览器给的数字不可信, iOS 的 .mov 常常根本没有时长),以及抽一帧作为封面(前端没给封面时)。

需要转码的才排队,不阻塞上传响应

  • 判定条件:非 mp4/webm 容器、视频编码不在 h264/vp8/vp9/av1、音频不在 aac/mp3/opus/vorbis/flac、 高度超 1080p 或码率超 8 Mbps。
  • 转码目标:H.264 + AAC、yuv420p(Safari 要求)、-movflags +faststart(边下边播)、长边压到 1080p。
  • 队列在 media_jobs 表,上传后会自动 spawn 一个一次性 worker。转码期间原文件照常可播, 完成后 uploads 行和引用它的帖子一起指向新文件,原文件删除。

生产环境建议挂一个常驻 worker 而不是依赖 spawn:

php bin/console.php media:work      # 常驻,空队列时每 5 秒轮询
php bin/console.php media:status    # 看队列与最近任务

没装 ffmpeg 也不会报错——视频原样保存,只是没有封面和转码。后台「站点设置」页会显示 ffmpeg 状态。

网易云音乐导入

默认关闭,在后台「站点设置 → 网易云音乐导入」打开(会同步 window.APP.neteaseOn, 前端音频弹窗里那一页才出现)。

  • 粘贴形态全都认:分享链接、#/song?id=/song/123163cn.tv 短链、<iframe> 嵌入码、纯数字 ID。
  • 只取 ID 和元数据(歌名/歌手/专辑/封面/时长),音频不转存到本站,播放走网易云的外链地址。
  • 会主动探测能不能匿名播放;受版权或地区限制的曲目返回 playable:false, 前端提示「发出来访客可能听不到声音」——提醒但不拦截。
  • 导入的曲目会进媒体库(uploads.source='netease'),可重复使用而不再请求网易云。

⚠️ 请只导入你有权分享的内容。这个功能不绕过任何付费或 DRM 机制, 也正因如此,大量商业曲目在站外是放不出声的。

部署示例(Nginx + PHP-FPM)

server {
  listen 80;
  server_name your.domain;
  root /var/www/aism/public;
  index index.php;

  client_max_body_size 60m;   # 与 UPLOAD_MAX_BYTES 对齐

  # ⚠ 一定要开压缩:app.css + app.js 未压缩是 830 KB,gzip 之后 265 KB。
  #   缺了这一条,跨国线路上每次首屏都要多等好几秒。
  gzip on;
  gzip_vary on;
  gzip_comp_level 5;
  gzip_min_length 1024;
  gzip_types text/css application/javascript application/json image/svg+xml text/plain application/xml;

  location / { try_files $uri $uri/ /index.php$is_args$args; }

  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param DB_DRIVER mysql;
    fastcgi_param DB_DATABASE aism;
    fastcgi_param DB_USERNAME aism;
    fastcgi_param DB_PASSWORD "你的密码";
    fastcgi_param APP_DEBUG 0;
  }

  # 静态资源与上传。
  # /static 下的引用都带 ?v=<mtime>(见 helpers.php 的 asset_version),
  # 所以可以放心 immutable —— 文件一改 URL 就变,用户不会拿到旧的。
  location ~* ^/static/ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; }
  location ~* ^/uploads/ { expires 30d; access_log off; }
}

走 nginx 时静态文件不会进 PHP,上面这两段就是它的缓存策略。 直接 php bin/console.php serve 跑的时候,同样的策略由 app/static_serve.php 自己实现(缓存头 + gzip + 304 + Range), 因为 PHP 内置服务器一个缓存头都不发

安全说明

  • 生产务必设置 APP_DEBUG=0(否则异常堆栈会直接返回给访客)。
  • 所有正文/评论/资料在服务端统一转义后再渲染,链接只放行 http(s) 与站内相对路径。
  • CSRF:所有写操作校验 X-CSRF-Token 或 body 里的 _token;会话 Cookie 为 HttpOnly+SameSite=Lax
  • 登录失败限流:同一来源 IP 15 分钟内失败满 15 次即临时拒绝(AuthController)。按 IP 不按账号 —— 按账号会被人拿你的用户名故意锁你(拒绝服务);复用已有的 login_logs 表,不引依赖。
  • 后台权限逐项白名单、分四级app/Models/Perm.php):版主 < 管理员 < 站长,站长专属项 (提权、模拟登录、改站点设置、动已成交的账)一条都不外放;can() 用「严格大于」判上下级, 两个管理员无法互封,任何人无法对自己提权。
  • 支付为演示实现sync=1 直接置为已支付),接入真实支付平台前请替换 app/Controllers/PayController.phpfulfill()status()
  • 邮件发信、网易云导入等为占位,未接第三方。
  • 头像地址是服务端校验的safe_avatar_value(),入库和出库各过一次)。它不是"多一层保险":app.js 拼私信气泡时是 '<img src="' + box.dataset.peerAvatar + '">',而 dataset 读出来是解码后的原串 —— PHP 那边 h() 转义过也挡不住,所以这个字段必须从根上不允许出现引号和尖括号。
  • 不收 SVG 上传。SVG 是能带 <script> 的 XML 文档,落在 public/uploads 下由同源发出去、直接访问就会执行,nosniff 挡不住(响应类型本来就是 svg)。另外 /uploads/* 一律带 Content-Security-Policy: default-src 'none'; sandbox(实测不影响 <img> 渲染)。
  • 昵称会洗掉零宽字符、双向覆写和控制字符clean_display_name())。输出本来就是安全的(h() + json_encode_safeJSON_HEX_TAG),洗的是"看不见的字符"——那是拿来冒充别人和绕过字数上限的。
  • 登录页那行演示账号只在 APP_DEBUG=1 时显示,生产按上面把它设成 0 就自动消失。
  • 发布前做过一轮渗透测试(SQL 注入 / 越权提权 / CSRF / IDOR / 存储型 XSS / 路径穿越 / 负数溢出), 普通用户和版主的越权尝试全部被拒;期间修掉了「商城下单数量未校验」和「登录无限流」两处。

已知边界

  • 支付是演示实现:sync=1 直接置为已支付。接真实支付平台请替换 app/Controllers/PayController.phpstatus() / fulfill()
  • 邮件默认不发信(MAIL_ENABLED=0),验证码回显并写 storage/mail.log;接 SMTP 请改 app/Models/Mailer.php
  • 未接第三方 OAuth 登录,/connect/unbind 会明确返回「本站未接入第三方登录」。
  • 网易云导入只存 ID + 元数据,音频走网易云自己的外链播放,本站不转存;受版权/地区限制的曲目匿名播不出声,接口会用 playable:false 明确告知(前端会提示站长)。

About

原生 PHP 全栈社区,SQLite 开箱即跑:微博式信息流 + 积分经济 + 积分商城 + 网页小程序平台 + 四级权限后台。docker compose up 一键部署。

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages