Skip to content

Commit b1dccba

Browse files
committed
feat(emoji): 添加表情分组分享和本地上传功能
- 实现表情分组分享码生成功能,支持静态快照分享 - 添加表情分组分享码导入功能,支持创建新的分组 - 集成本地表情上传功能,支持图片格式验证和大小限制 - 优化计数器位置计算逻辑,调整默认显示位置 - 扩展表情管理界面,增加分享和导入操作按钮 - 实现表情分组的批量导出和导入功能,支持跨用户分享 - 添加本地上传触发器和文件处理流程,集成前端上传组件
1 parent b32405e commit b1dccba

File tree

15 files changed

+997
-13
lines changed

15 files changed

+997
-13
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
## 关键链路(持续维护)
4545
- 前端全局主题变量入口:`src/main/resources/scss/_variables.scss``$theme-primary` 会影响 `module`/首页卡片/聊天室等主区背景,深色会导致整站大面积染色;顶栏建议在 `base.scss`/`mobile-base.scss``.nav` 单独设色。
4646
- 移动端文章页(`skins/mobile/article.ftl`)存在多个 `#replyUseName`(含隐藏占位 `.fn-none`);`m-article.js` 处理回复目标时需优先选中非 `.fn-none` 节点,避免“回复对象已记录但指示未显示”。
47+
- 新版表情面板的入口与工具条统一由 `src/main/resources/js/emoji-groups.js` 渲染;文章页/聊天室 FTL 里旧的 `#uploadEmoji` 尾栏大多已注释,恢复“本地上传”优先改这里,不要分别恢复多套模板。
48+
- 表情集分享链路使用新表 `emoji_share` 保存“分组快照 + 永久分享码”;分享内容是静态快照,后续源分组变更不会实时同步,导入会在目标用户下新建自定义分组并同步补入其“全部”分组。
4749
- 首页右栏专栏列表(classic)需使用 `module-list long-column-module-list`(见 `skins/classic/index.ftl` 的“最新专栏/热门专栏/最近阅读”);否则会命中 `.module-list .title` 默认 `margin-left: 30px` 产生左侧空白。
4850
- 勋章管理页:`/admin/medal`
4951
- 后端:`src/main/java/org/b3log/symphony/processor/MedalProcessor.java``showAdminMedal``register`

sql/20260318_emoji_share.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE IF NOT EXISTS `symphony_emoji_share` (
2+
`oId` VARCHAR(19) NOT NULL,
3+
`emojiShareCode` VARCHAR(16) NOT NULL,
4+
`emojiShareOwnerUserId` VARCHAR(19) NOT NULL,
5+
`emojiShareGroupId` VARCHAR(19) NOT NULL,
6+
`emojiShareGroupName` VARCHAR(64) NOT NULL,
7+
`emojiShareSnapshot` LONGTEXT NOT NULL,
8+
`emojiShareEmojiCount` INT NOT NULL DEFAULT 0,
9+
`emojiShareImportedCount` INT NOT NULL DEFAULT 0,
10+
`emojiShareCreatedTime` BIGINT NOT NULL,
11+
PRIMARY KEY (`oId`),
12+
UNIQUE KEY `uk_emoji_share_code` (`emojiShareCode`),
13+
KEY `idx_emoji_share_owner_created` (`emojiShareOwnerUserId`, `emojiShareCreatedTime`)
14+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Rhythm - A modern community (forum/BBS/SNS/blog) platform written in Java.
3+
* Modified version from Symphony, Thanks Symphony :)
4+
* Copyright (C) 2012-present, b3log.org
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
package org.b3log.symphony.model;
20+
21+
/**
22+
* Emoji share snapshot model constants.
23+
*/
24+
public final class EmojiShare {
25+
26+
private EmojiShare() {
27+
}
28+
29+
public static final String EMOJI_SHARE = "emoji_share";
30+
31+
public static final String SHARE_CODE = "emojiShareCode";
32+
public static final String OWNER_USER_ID = "emojiShareOwnerUserId";
33+
public static final String GROUP_ID = "emojiShareGroupId";
34+
public static final String GROUP_NAME = "emojiShareGroupName";
35+
public static final String SNAPSHOT = "emojiShareSnapshot";
36+
public static final String EMOJI_COUNT = "emojiShareEmojiCount";
37+
public static final String IMPORTED_COUNT = "emojiShareImportedCount";
38+
public static final String CREATED_TIME = "emojiShareCreatedTime";
39+
}

src/main/java/org/b3log/symphony/processor/EmojiProcessor.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.b3log.latke.service.ServiceException;
3131
import org.b3log.symphony.model.EmojiGroup;
3232
import org.b3log.symphony.model.EmojiGroupItem;
33+
import org.b3log.latke.model.User;
3334
import org.b3log.symphony.processor.middleware.LoginCheckMidware;
3435
import org.b3log.symphony.service.CloudService;
3536
import org.b3log.symphony.service.EmojiMgmtService;
@@ -107,6 +108,8 @@ public static void register() {
107108
Dispatcher.post("/api/emoji/group/add-emoji", emojiProcessor::addEmojiToGroup, loginCheck::handle);
108109
Dispatcher.post("/api/emoji/group/add-url-emoji", emojiProcessor::addUrlEmojiToGroup, loginCheck::handle);
109110
Dispatcher.post("/api/emoji/group/remove-emoji", emojiProcessor::removeEmojiFromGroup, loginCheck::handle);
111+
Dispatcher.post("/api/emoji/group/share/create", emojiProcessor::createGroupShare, loginCheck::handle);
112+
Dispatcher.post("/api/emoji/group/share/import", emojiProcessor::importGroupShare, loginCheck::handle);
110113
Dispatcher.post("/api/emoji/emoji/update", emojiProcessor::updateEmojiItem, loginCheck::handle);
111114
Dispatcher.post("/api/emoji/emoji/migrate", emojiProcessor::migrateOldEmoji, loginCheck::handle);
112115
}
@@ -567,6 +570,58 @@ public void removeEmojiFromGroup(final RequestContext context) {
567570
}
568571
}
569572

573+
public void createGroupShare(final RequestContext context) {
574+
try {
575+
final JSONObject currentUser = getCurrentUser(context);
576+
final JSONObject requestJSONObject = context.requestJSON();
577+
final String groupId = requestJSONObject.optString("groupId");
578+
if (StringUtils.isBlank(groupId)) {
579+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg("缺少分组参数");
580+
return;
581+
}
582+
583+
final JSONObject resultData = emojiMgmtService.createShareSnapshot(
584+
currentUser.optString(Keys.OBJECT_ID), groupId
585+
);
586+
final JSONObject result = new JSONObject();
587+
result.put("code", StatusCodes.SUCC);
588+
result.put("data", resultData);
589+
context.renderJSON(result);
590+
} catch (final ServiceException e) {
591+
LOGGER.log(Level.WARN, "Create emoji group share failed", e);
592+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg(e.getMessage());
593+
} catch (final Exception e) {
594+
LOGGER.log(Level.ERROR, "Create emoji group share failed", e);
595+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg("生成分享码失败");
596+
}
597+
}
598+
599+
public void importGroupShare(final RequestContext context) {
600+
try {
601+
final JSONObject currentUser = getCurrentUser(context);
602+
final JSONObject requestJSONObject = context.requestJSON();
603+
final String shareCode = requestJSONObject.optString("shareCode");
604+
if (StringUtils.isBlank(shareCode)) {
605+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg("请输入分享码");
606+
return;
607+
}
608+
609+
final JSONObject resultData = emojiMgmtService.importShareSnapshot(
610+
currentUser.optString(Keys.OBJECT_ID), shareCode
611+
);
612+
final JSONObject result = new JSONObject();
613+
result.put("code", StatusCodes.SUCC);
614+
result.put("data", resultData);
615+
context.renderJSON(result);
616+
} catch (final ServiceException e) {
617+
LOGGER.log(Level.WARN, "Import emoji group share failed", e);
618+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg(e.getMessage());
619+
} catch (final Exception e) {
620+
LOGGER.log(Level.ERROR, "Import emoji group share failed", e);
621+
context.renderJSON(new JSONObject()).renderCode(StatusCodes.ERR).renderMsg("导入分享码失败");
622+
}
623+
}
624+
570625
// 用户修改表情名字
571626
/**
572627
* Updates an emoji item (name and sort).
@@ -717,5 +772,31 @@ private boolean isSafeName(final String name) {
717772
return SAFE_NAME_PATTERN.matcher(name).matches();
718773
}
719774

775+
private JSONObject getCurrentUser(final RequestContext context) {
776+
Object userObj = context.attr(User.USER);
777+
if (userObj instanceof JSONObject) {
778+
return (JSONObject) userObj;
779+
}
780+
781+
JSONObject currentUser = Sessions.getUser();
782+
if (currentUser != null) {
783+
return currentUser;
784+
}
785+
786+
try {
787+
final JSONObject requestJSONObject = context.requestJSON();
788+
final String apiKey = requestJSONObject.optString("apiKey");
789+
if (StringUtils.isNotBlank(apiKey)) {
790+
currentUser = ApiProcessor.getUserByKey(apiKey);
791+
if (currentUser != null) {
792+
return currentUser;
793+
}
794+
}
795+
} catch (final Exception ignored) {
796+
}
797+
798+
return new JSONObject();
799+
}
800+
720801

721802
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Rhythm - A modern community (forum/BBS/SNS/blog) platform written in Java.
3+
* Modified version from Symphony, Thanks Symphony :)
4+
* Copyright (C) 2012-present, b3log.org
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
package org.b3log.symphony.repository;
20+
21+
import org.b3log.latke.repository.AbstractRepository;
22+
import org.b3log.latke.repository.FilterOperator;
23+
import org.b3log.latke.repository.PropertyFilter;
24+
import org.b3log.latke.repository.Query;
25+
import org.b3log.latke.repository.RepositoryException;
26+
import org.b3log.latke.repository.annotation.Repository;
27+
import org.b3log.symphony.model.EmojiShare;
28+
import org.json.JSONObject;
29+
30+
/**
31+
* Emoji share repository.
32+
*/
33+
@Repository
34+
public class EmojiShareRepository extends AbstractRepository {
35+
36+
public EmojiShareRepository() {
37+
super(EmojiShare.EMOJI_SHARE);
38+
}
39+
40+
public JSONObject getByShareCode(final String shareCode) throws RepositoryException {
41+
final Query query = new Query().setFilter(
42+
new PropertyFilter(EmojiShare.SHARE_CODE, FilterOperator.EQUAL, shareCode)
43+
);
44+
return getFirst(query);
45+
}
46+
}

0 commit comments

Comments
 (0)