- 所有文件不要直接修改
.css,而是修改.scss,然后执行yarn run build生成新的 CSS。js同理,请修改js,而不是min.js,然后同样是通过yarn run build编译min.js - 在 WSL 中执行 Maven 时,优先复用 Windows 本地仓库:
-Dmaven.repo.local=/mnt/c/.m2/repository(对应 Windows 的C:\.m2\repository),可避免依赖下载时的 SSL 握手失败;Java 版本固定使用 25。 - 如果你的更改涉及到操作数据库,请注意在引用Repository类的时候不要加上包名org.b3log.latke.repository,如果没有请直接import,另外除了查询以外,增、删、改 所有写入的操作都需要用Transaction进行完整的事务commit,否则会无法正常写入。
- 涉及到前端的更改,请不要只修改classic(PC端)皮肤,mobile(移动端)皮肤也要同步修改
- 涉及到新增 Repository(新增表/集合)时,除了 SQL 建表外,必须同步更新
src/main/resources/repository.json,并且 Repository 的super("...")/模型常量中不要写symphony_前缀(框架会根据jdbc.tablePrefix自动加前缀)。 - 除非用户提到代码编译有问题或者主动要求你进行编译,否则不要使用mvn编译java,这个过程会浪费用户的时间,除非你觉得非常有必要,并且询问用户且被同意
- 每次遇到新的项目架构关键信息,对后续项目AI使用有帮助的,请主动维护AGENTS.md,注意每次维护不要过度细节,只要让AI正确理解就可以了,防止prompt token过长
- 我和你沟通都是在开发环境(http://localhost:8080),生产环境是摸鱼派(https://fishpi.cn),如果你能调用Chrome,可以调试辅助你诊断代码,注意,如果你编译了js和css,强制刷新开发环境就会生效,如果你改了Java或者FTL,那就需要告知用户通过IDEA重新编译或者重启服务端才能生效;当前本地运行环境经常直接读取
target/classes下的资源,如果源码已改但浏览器仍是旧效果,优先同步对应资源到target/classes或提醒我重新编译 - 只要本轮改动涉及 Java / FTL,联调前就要明确提醒我手动通过 IDEA 重新编译或重启服务端;不要假设我已经做了,确认前不要把浏览器里的旧行为当成代码未生效
- 在 WSL 环境中不要依赖 git 状态/差异判断工作区是否干净(可能存在异常噪声);默认按任务目标直接改指定文件,git 清理由用户在 Windows 环境统一处理
AGENTS.md保持精简,只保留会影响决策的长期约定;新增内容优先补充“关键链路”,避免堆砌背景信息。
- 前端改动只改源码:
.scss与非min.js;执行yarn run build生成产物;PC/移动端经典皮肤当前目录分别为skins/classic/pc与skins/classic/mobile,改动需同步评估两端。 - 鱼排扩展独立脚本需包含标准
UserScript头(至少@name、@match、@grant、@run-at);聊天室联调脚本建议同时匹配https://fishpi.cn/cr*与http://localhost:8080/cr*。 - WSL 执行 Maven 优先使用:
-Dmaven.repo.local=/mnt/c/.m2/repository;Java 固定 25。 - 未经用户明确要求,不主动执行
mvn编译。 - 涉及数据库写操作(增/删/改)必须使用
Transaction并完整commit。 Repository相关类直接import,不要在代码里写org.b3log.latke.repository全限定名。- 新增 Repository(新表/集合)时:同步更新
src/main/resources/repository.json;super("...")/模型常量不要写symphony_前缀。 - 安全基线(已审计):防扫描验证码链路核心在
BeforeRequestHandler+AnonymousViewCheckMidware#handle+CaptchaProcessor#validateCaptcha;LoginCheckMidware#handle负责登录态校验(含 apiKey),两套逻辑都不可被绕过。 - 访问控制基线(已审计):当前未登录可访问并不只首页/文章/登录/注册,还包含验证码页、
/about、/download、/privacy、/agreement及部分公开 API;匿名可访问范围还受symphony.properties的anonymous.viewSkips影响。 - 新增页面/路由默认要求登录(优先挂
loginCheck::handle);若业务必须匿名访问,必须显式接入anonymousViewCheckMidware::handle并评审风险,不能裸放。 - 新增或改造登录/入口逻辑必须做回归:未登录拦截、登录后放行、黑名单 IP 跳转
/test、/validateCaptcha解封、开发模式关闭风控后行为一致。 loginCheck::handle默认兼容Sessions+apiKey(query 参数与 JSON body),并写入context.attr(User.USER);后续处理逻辑优先从该 attr 取当前用户。apiCheck::handle同样支持apiKey并写入context.attr(User.USER),但未登录返回 JSON{"result":"Unauthorized"};适合纯 API 场景。permissionMidware::check只做权限判断,不负责写入context.attr(User.USER);若处理方法还需要当前用户信息,必须自行取用户(或叠加loginCheck)。permissionMidware::check在permission.rule.url.*缺失时会直接放行(含匿名);凡仅挂permissionMidware的路由必须同步补齐对应权限规则,避免后台能力裸露。- 处理方法取当前用户推荐顺序:
context.attr(User.USER)->Sessions.getUser()->ApiProcessor.getUserByKey(...);不要只依赖Sessions.getUser()。 - 聊天室
/cr的两套样式分别走chat-room.js与chat-room-2.js,但红包消息都通过/chat-room/getMessage返回原始 JSON(msgType=redPacket),领取统一走/chat-room/red-packet/open。 - 聊天室红包详情/打开后的弹层头部走前端通用
Util.alert()(src/main/resources/js/common.js),标题与关闭按钮布局异常优先排查这个公共弹层,而不是红包业务模板。 - 评论与聊天室 emoji reaction 共用
reaction表;历史接口直接补reactionSummary/currentUserReaction,聊天室实时选中态需结合chatReaction增量事件里的actorUserId/actorReaction在前端按当前用户合并。 - 聊天室红包风险约束:
heartbeat可能抢到负积分,rockPaperScissors猜错会扣积分,dice当前服务端不支持领取;自动化脚本默认只建议开启安全类型。 - 接口设计安全约束:前后端新增/改造接口时,必须同时评估常见漏洞(越权、未鉴权访问、CSRF、XSS、注入、SSRF、批量请求滥用、敏感信息泄露)。
- 字符串输入必须做限制与校验:长度上限、空白处理、字符白名单/黑名单、格式校验(如用户名/URL/JSON)、必要的转义或编码;禁止直接信任前端传参。
- 涉及业务规则(可用字符、最大长度、是否允许 HTML/Markdown、过滤策略)不明确时,先与用户确认规则再实现,避免误伤或放漏。
- 后端:
src/main/java/org/b3log/symphony(入口Server.java,核心分层processor/service/repository/model/util)。 - 资源:
src/main/resources(配置、静态资源、skins模板)。 - 前端编译输出:
src/main/webapp/css、src/main/webapp/js。 - 构建:后端 Maven(
pom.xml),前端 Gulp(gulpfile.js、package.json)。 - 本地 Latke 框架源码:
C:\Users\陈辉\IdeaProjects\rhy-latke(非官方版本,分析框架行为优先对照这里,不要假设与官方仓库一致)。
- Latke 原生仅提供轻量皮肤元信息能力(如
Latkes#getSkinName读取/skins/<dir>/skin.properties),不负责 Rhythm 的运行时选肤;Rhythm 当前皮肤链路在BeforeRequestHandler#resolveSkinDir+Sessions+processor/SkinRenderer+util/Templates。 - Rhythm 当前“皮肤”本质是模板目录名:默认值来自
symphony.properties的skinDirName=classic/pc、mobileSkinDirName=classic/mobile;运行时先按 UA 分流 pc/mobile 默认皮肤,再按用户字段userSkin/userMobileSkin覆盖,设置页通过/settings/skin保存,SkinQueryService负责扫描/skins/**/skin.properties识别可选主题。 - Rhythm 侧
SkinQueryService现已按 UTF-8 读取skin.properties,皮肤名称/描述可直接写中文;但src/main/resources/lib/latke-core.jar内的Latkes#getSkinName仍是旧Properties.load,若后续有代码依赖该方法读取皮肤元信息,需要同步改 Latke 并替换 jar。 - 模板加载已支持“同设备 fallback”:若自定义皮肤缺少某个 FTL,
Templates会回退到默认皮肤的同路径模板(如foo/pc/header.ftl -> classic/pc/header.ftl,foo/mobile/common/comment.ftl -> classic/mobile/common/comment.ftl);因此新增皮肤可以按需覆盖,但至少要提供对应目录与skin.properties。 - Freemarker +
org.json兼容约束:顶层放进 dataModel 的List/JSONArray可直接#list,但挂在JSONObject字段里的嵌套JSONArray/Iterable在 FTL 中可能被包装成非序列;这类数据优先展开成固定字段,或改为顶层列表再渲染。 - 皮肤设置页预览图来自各皮肤目录下
skin.properties的previewUrl;新增皮肤若希望在设置页展示预览图,需要同步配置该字段。 - 前端全局主题变量入口:
src/main/resources/scss/_variables.scss;$theme-primary会影响module/首页卡片/聊天室等主区背景,深色会导致整站大面积染色;顶栏建议在base.scss/mobile-base.scss的.nav单独设色。 - 移动端文章页(
skins/classic/mobile/article.ftl)存在多个#replyUseName(含隐藏占位.fn-none);m-article.js处理回复目标时需优先选中非.fn-none节点,避免“回复对象已记录但指示未显示”。 - 评论区交互不是单点模板:首屏评论列表由
ArticleProcessor+CommentQueryService#getArticleComments组装,展开“原评论/回复”走CommentProcessor#getOriginalComment/getReplies,实时新评论卡片由processor/channel/ArticleChannel渲染skins/**/common/comment.ftl;改评论动作区时需同步评估 PC/移动模板、article.js/m-article.js以及实时插入链路。 - 新版表情面板的入口与工具条统一由
src/main/resources/js/emoji-groups.js渲染;文章页/聊天室 FTL 里旧的#uploadEmoji尾栏大多已注释,恢复“本地上传”优先改这里,不要分别恢复多套模板。 - 表情集分享链路使用新表
emoji_share保存“分组快照 + 永久分享码”;分享内容是静态快照,后续源分组变更不会实时同步,导入会在目标用户下新建自定义分组并同步补入其“全部”分组。 - 首页右栏专栏列表(classic/pc)需使用
module-list long-column-module-list(见skins/classic/pc/index.ftl的“最新专栏/热门专栏/最近阅读”);否则会命中.module-list .title默认margin-left: 30px产生左侧空白。 - 勋章管理页:
/admin/medal- 后端:
src/main/java/org/b3log/symphony/processor/MedalProcessor.java(showAdminMedal、register) - 前端:
src/main/resources/js/medal.js
- 后端:
- 模板:
src/main/resources/skins/classic/pc/admin/medal.ftl、src/main/resources/skins/classic/mobile/admin/medal.ftl - 管理 API:
/api/medal/admin/list、/api/medal/admin/search、/api/medal/admin/grant、/api/medal/admin/revoke、/api/medal/admin/owners - 会员状态 API:
GET /api/membership/{userId}(MembershipProcessor#getUserMembershipStatus) - VIP 管理页:
/admin/vip(MembershipProcessor#showAdminVipManagePage,classic/pc 与 classic/mobile 同路径模板admin/vip.ftl)。 - VIP 管理 API(仅
adminRole):/api/admin/vip/list、/api/admin/vip/add、/api/admin/vip/update、/api/admin/vip/refund、/api/admin/vip/extend。 - VIP 管理服务关键方法位于
MembershipMgmtService:免费新增(不扣积分)、手工维护、按剩余天数退款并失效。 - VIP 按天退款成功后会复用“系统转账通知”(
Notification.DATA_TYPE_C_POINT_TRANSFER)给用户发送站内通知。 - 积分变更统一走
PointtransferMgmtService:独立流程用transfer(...)(方法内自带事务);若外层已有事务,必须用transferInCurrentTransaction(...)避免事务冲突。 - 发积分/扣积分约定:发积分用
fromId=Pointtransfer.ID_C_SYS、toId=userId;扣积分用fromId=userId、toId=Pointtransfer.ID_C_SYS(常用类型Pointtransfer.TRANSFER_TYPE_C_ABUSE_DEDUCT)。 - 需要给用户发“系统转账通知”时:在转账成功拿到
transferId后,调用NotificationMgmtService#addPointTransferNotification,并设置Notification.NOTIFICATION_USER_ID与Notification.NOTIFICATION_DATA_ID=transferId。 - 通知金手指:
POST /user/edit/notification(UserProcessor#sendSystemNotification),使用gold.finger.notification校验;请求体使用userName+notification+goldFingerKey,新正文优先写notification.content(最大 4096),老库未补content列时仅兼容旧 64 字纯文本dataId链路。 - 定制系统通知渲染:
Notification.DATA_TYPE_C_CUSTOM_SYS在NotificationQueryService#getSysAnnounceNotifications处理;dataId=1为固定“水贴提醒”模板,content支持 Markdown 链接[文本](http/https://...)与换行,原始 HTML 会被转义后再清洗。 - VIP 管理页配置项不再手填 JSON:前端依据等级
benefits模板自动生成可视化表单,再序列化为configJson提交。 - VIP 管理页样式需注意
home.css的.form--admin label { flex: 1; }会影响布局;配置项行在vip-admin.scss中需显式改为整行(label/builder 100%)并对 checkbox 使用类型选择器,避免控件被放大。 - 有效期字段:勋章
expireTime(毫秒,0=永久);会员expiresAt(可回填勋章到期)。 - 首页最新文章链路:
IndexProcessor#loadIndexData通过ArticleQueryService#getIndexRecentArticles(fetchSize, page)组装 classic/pc 与 classic/mobile 首页“最新文章”;第一页置顶插入与数量行为在该方法维护。 /recent与/api/articles/recent*共用ArticleQueryService#getRecentArticles;该链路已明确排除长篇(articleType=6),长篇列表请走/recent/long页面或GET /api/articles/recent/long。- 首页两列对齐约束:
getIndexRecentArticles的第一页会插入全部置顶且不截断;第二页起需按第一页“置顶占位数”补偿fetchSize与分页偏移,保证两列等高且不丢中间文章。 - 首页右侧排行补偿(无前端延迟):由
IndexProcessor#loadIndexData按两列最新文章的最大行数计算rankCompensateRows,先换算“右栏总补偿行数”再分摊到checkinVisibleCount/onlineVisibleCount,Freemarker 直接按该数量渲染,不再依赖 JS 运行时增删行。 - 路由总入口:
Router#requestMapping+ 各 Processorregister();新增路由先决定使用loginCheck/apiCheck/permission/anonymousViewCheck哪条链路。 BeforeRequestHandler通过Dispatcher.startRequestHandler在路由中间件前执行;该阶段query/form/cookie已由 latke-core 解析,可直接读取请求参数与 Cookie。BeforeRequestHandler获取当前用户优先用UserQueryService#getCurrentUser(request)(底层Sessions.currentUser(request));Sessions.getUser()仅读取 ThreadLocal,未Sessions.setUser(...)前通常为null。LoginCheckMidware#handle:未登录统一 401(特殊 URI/gen返回空 SVG);支持Sessions与apiKey两种登录态来源。- 新接口若需“页面登录态 + apiKey 调用”双兼容,路由层优先挂
loginCheck::handle,处理方法再读取context.attr(User.USER);避免只挂permission导致拿不到当前用户对象。 - 文章页实时链路走
ArticleChannel:新增评论仍是type=comment,评论 reaction 增量走type=commentReaction,帖子本体 reaction 增量走type=articleReaction;联调这类 Java 推送改动时,前端强刷还不够,必须让用户重启或重新编译服务端。 AnonymousViewCheckMidware#handle:匿名访问触发验证码(2 小时首次访问 + 每 5 次访问),并结合anonymous.viewSkips、文章匿名开关、匿名访问次数 Cookie 限制。Server启动逻辑:DEVELOPMENT模式会关闭Firewall与AnonymousViewCheck(验证码盾),联调时不要误判“线上无校验”。- 历史遗留:部分接口未在路由层挂登录中间件而在方法内鉴权(如
MedalProcessor#requireAdmin/requireLogin、UserProcessor的 goldFingerKey 系列);新增接口不要复用该模式,优先路由层显式鉴权。