Build a One‑Click XiaoHongShu Publishing System with Backend APIs and JS SDK
This article describes how to replace manual copy‑paste publishing on XiaoHongShu with a unified content‑management backend, share‑link generation, signature authentication, and a single‑click JavaScript SDK call that lets operators publish notes instantly across multiple accounts, dramatically improving efficiency and reducing errors.
运营小姐姐的烦恼
Our company’s operations team has to post content to platforms such as Douyin, Kuaishou, and XiaoHongShu. During promotion periods they must handle dozens of posts across brand, partnership, and test accounts, juggling many images and copy drafts, which leads to frequent mistakes and overtime.
技术出手,只为运营小姐姐少加点班
To eliminate repetitive copy‑paste work, we decided to build a "content unified management + multi‑platform sharing" system. After configuring the content in a backend, a single "generate share link" click creates a URL that opens the target platform’s publishing page with all fields pre‑filled, turning hours of manual work into a few minutes.
如何实现一键发布小红书笔记
The manual XiaoHongShu publishing flow involves opening the app, selecting images, entering title and body, choosing topics, and clicking Publish. Our system centralises the title, body, and media in a backend, generates a share link, and lets the operator open the link to preview and publish with one click.
实现一键发布前的后台准备
We built a simple backend with two pages: a list page showing all drafts and a publish page for creating new drafts. The list displays cover image, title, content snippet, image count, and actions such as "copy share link", "preview", "edit", and "delete". The publish page lets the operator fill title, Markdown‑styled body, and select images or video.
后台文案管理功能设计
The core database table x_post stores each note’s id, type (normal or video), title, content, cover URL, image URLs (JSON array), video URL, deletion flag, and timestamps. This structure supports both image‑rich posts and video posts.
CREATE TABLE `x_post` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`type` varchar(20) NOT NULL COMMENT '笔记类型 normal | video',
`title` varchar(255) DEFAULT NULL COMMENT '标题',
`content` text COMMENT '正文内容',
`cover_url` varchar(500) DEFAULT NULL COMMENT '封面图',
`image_urls` text COMMENT '图片列表,JSON数组格式',
`video_url` varchar(500) DEFAULT NULL COMMENT '视频地址',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '是否删除 0=未删除 1=已删除',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='发布笔记表';接入小红书分享平台 API
We use XiaoHongShu’s Open Platform JS SDK, which provides three main capabilities: one‑click share, fast publish (auto‑filling title, content, images, and topics), and activity‑linked publishing. The SDK handles the heavy lifting; we only need to supply content and a signed verification payload.
接入流程
Register an application on the Open Platform to obtain appKey and appSecret.
Store the credentials server‑side and generate a signature when needed.
Pass nonce, timestamp, and signature to the front end.
The front end calls the JS SDK to invoke sharing.
The backend can verify the request via OpenAPI.
鉴权签名流程
Obtain access_token using appKey and appSecret.
Generate signature from appKey, nonce, timestamp, and access_token.
Return appKey, nonce, timestamp, and signature to the front end.
发布分享
After authentication, we call xhs.share() with two objects: shareInfo (type, title, content, images, video, cover) and verifyConfig (appKey, nonce, timestamp, signature). The SDK launches the XiaoHongShu app and pre‑fills the publishing page.
xhs.share({
shareInfo: {
type: 'normal',
title: '...',
content: '...',
images: ['...'],
// video & cover for video type
},
verifyConfig: {
appKey: '...',
nonce: '...',
timestamp: '...',
signature: '...'
},
fail: (e) => { console.error('分享失败', e); }
});接口一:文案预览接口
GET /api/post/preview/{id} returns the XPost record for front‑end preview. The controller simply fetches the record from the repository.
@GetMapping("/preview/{id}")
public ResponseEntity<XPost> preview(@PathVariable Long id) {
return ResponseEntity.ok(postService.previewPost(id));
}接口二:一键发布接口
GET /api/post/publish/{id} validates the draft, generates the signature, and assembles shareInfo and data (verification fields) for the front end.
@GetMapping("/publish/{id}")
public ResponseEntity<Map<String, Object>> publish(@PathVariable Long id) {
Map<String, Object> result = postService.buildPublishResult(id);
return ResponseEntity.ok(result);
}
public Map<String, Object> buildPublishResult(Long id) {
XPost post = previewPost(id);
Map<String, Object> data = generateSignature(id);
Map<String, Object> shareInfo = new HashMap<>();
shareInfo.put("type", post.getType());
shareInfo.put("title", post.getTitle());
shareInfo.put("content", post.getContent());
shareInfo.put("images", ImageUtils.parseImages(post.getImageUrls()));
shareInfo.put("cover", Optional.ofNullable(post.getCoverUrl())
.orElseGet(() -> ImageUtils.getFirstImage(post.getImageUrls())));
Map<String, Object> result = new HashMap<>();
result.put("data", data);
result.put("shareInfo", shareInfo);
return result;
}
public Map<String, Object> generateSignature(Long postId) {
String appKey = xhsProperties.getAppKey();
String appSecret = xhsProperties.getAppSecret();
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0,12);
String timestamp = String.valueOf(System.currentTimeMillis());
String signature = SignatureUtil.buildSignature(appKey, nonce, timestamp, appSecret);
Map<String, Object> map = new HashMap<>();
map.put("appKey", appKey);
map.put("nonce", nonce);
map.put("timestamp", Long.parseLong(timestamp));
map.put("signature", signature);
return map;
}签名生成工具类
public class SignatureUtil {
public static String buildSignature(String appKey, String nonce, String timestamp, String appSecret) throws Exception {
Map<String, String> params = new HashMap<>();
params.put("appKey", appKey);
params.put("nonce", nonce);
params.put("timestamp", timestamp);
return generateSignature(appSecret, params);
}
public static String generateSignature(String secretKey, Map<String, String> params) {
Map<String, String> sorted = new TreeMap<>(params);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : sorted.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(e.getKey()).append("=").append(e.getValue());
}
sb.append(secretKey);
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
}
}图片处理工具类
public class ImageUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static List<String> parseImages(String imageUrls) {
if (imageUrls == null || imageUrls.isBlank()) return Collections.emptyList();
try {
if (imageUrls.trim().startsWith("[")) {
return mapper.readValue(imageUrls, new TypeReference<List<String>>() {});
} else {
return Arrays.stream(imageUrls.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
}
} catch (Exception e) {
return Collections.emptyList();
}
}
public static String getFirstImage(String imageUrls) {
List<String> list = parseImages(imageUrls);
return list.isEmpty() ? null : list.get(0);
}
}前端跳转页实现(跳转小红书 App 分享)
The preview page loads the draft via /api/post/preview/{id}, renders a Swiper carousel of images, shows the content, and provides a "Publish to XiaoHongShu" button. When clicked, the page checks the user‑agent; if the user is in WeChat on Android, a tip is shown to open the link in a browser. Otherwise it calls the publish API, receives shareInfo and verifyConfig, and invokes xhs.share().
const u = navigator.userAgent.toLowerCase();
const isAndroid = u.includes('android') || u.includes('adr');
const isWeixin = u.includes('micromessenger');
if (isAndroid && isWeixin) {
document.getElementById('tips').style.display = 'block';
} else {
goShare();
}
async function goShare() {
const res = await fetch(`http://example.com/api/post/publish/${id}`);
const { data: verifyConfig, shareInfo } = await res.json();
xhs.share({
shareInfo,
verifyConfig,
fail: e => { console.error('分享失败', e); alert('分享失败: ' + JSON.stringify(e)); }
});
}最终效果
Operators open the generated share link, preview the draft, click the one‑click button, and are taken directly to the XiaoHongShu app with title, body, images, topics, and (if applicable) video already filled. No manual copy‑paste is required, drastically reducing error rates and allowing operators to finish work on time.
进阶设计:多设备自动发布与后台数据统计
Beyond manual clicks, the system can control dozens of devices to open the link and trigger publishing automatically, and the backend records which accounts succeeded, draft status, exposure metrics, and failure reasons for further analysis.
用后台赋能一对多教学,打造自己的 “小红书孵化器”
Independent creators can use the same backend to prepare teaching material, generate share links, turn them into QR codes, and let students publish with a single tap, turning the tool into a content‑distribution hub for training programs.
更多平台的扩展实践:从小红书到全平台统一管理
After XiaoHongShu, we integrated Douyin, Kuaishou, Bilibili, and Weibo using similar share‑link and SDK patterns, building a "content publishing middle‑platform" that centralises assets, generates platform‑specific links, and aggregates publishing status and performance data.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
