前置条件
在开始接入之前,请确保已从管理员处获取以下两个凭证,并明确各自的使用范围:
| 凭证 | 格式 | 使用位置 | 用途 |
|---|---|---|---|
AppKey |
UUID | ✓ 前端 | 加载结构编辑器,绑定到授权域名 |
ApiKey |
UUID | ✗ 仅后端 | 调用结构检索 API,必须保密,不得出现在任何前端代码中 |
Error call Host 错误。如需增加域名,请联系管理员。
引入 JS 文件
SDK 由服务端统一托管,直接从 API 部署域名 引入,无需 npm 安装。
sdk.js 内部会向 APIURL 发起 XHR 请求加载编辑器资源。因此
sdk.js 的引入地址 与 CJSDraw_SDK.Cfg.APIURL 必须指向同一个域名(即 API 服务的部署域名)。如果两者域名不一致,或者
APIURL 与你自己页面的域名不同且服务端未配置 CORS,编辑器将无法正常加载。
<!-- 1. jQuery(SDK 依赖)——从 API 部署域名加载 --> <script src="//mol.mkbio.com.cn/static/jquery.min.js"></script> <!-- 2. 结构搜索 SDK——必须从 API 部署域名加载,不能从你自己的域名引入 --> <script src="//mol.mkbio.com.cn/static/sdk.js"></script> <!-- 3. 配置 APIURL——必须与上面 sdk.js 的域名完全一致 --> <script> CJSDraw_SDK.Cfg.APIURL = "//mol.mkbio.com.cn"; </script>
// 开头的协议无关 URL,SDK 会自动继承当前页面的 http / https 协议,无需手动区分。
初始化结构编辑器
在页面上准备一个容器元素,然后调用 CJSDraw_SDK.Init() 初始化编辑器。默认加载内置编辑器;如需使用 Ketcher,传入 version: "ketcher-sa"(参见编辑器选型)。
HTML 容器
<!-- 编辑器将渲染到此元素内 --> <div id="jsdrawbox"></div>
初始化调用
var appkey = "your-appkey-uuid"; // Init(place, cfg) — 两个参数,callback 放在 cfg 内 CJSDraw_SDK.Init("jsdrawbox", { appkey: appkey, skin: "w8", // 皮肤,固定值 width: "100%", // 编辑器宽度 height: "510px", // 编辑器高度 callback: function() { // 编辑器加载完成后的回调 console.log("编辑器已就绪"); }, });
封装成函数(推荐)
Demo 页面中的封装写法,方便统一传入 AppKey:
function InitJsDraw(el, callback, cfg, appkey) { cfg = $.extend({ appkey: appkey || '', callback: function() { callback(); } }, cfg); CJSDraw_SDK.Init(el, cfg); } // 调用示例 InitJsDraw( "jsdrawbox", // 容器 id function() { console.log("就绪"); }, // 回调 { skin: "w8", width: "100%", height: "510px" }, "your-appkey-uuid" );
获取编辑器中的结构
从编辑器取出用户绘制的分子结构,SDK 通过 CJSDraw_SDK.Do("getSmiles", ...) 异步获取 SMILES 字符串,结果在回调的 data.value 字段中。
CJSDraw_SDK.Do("getSmiles", {}, function(data) { var smiles = data.value; // SMILES 字符串,在 data.value 字段 console.log("当前结构 SMILES:", smiles); if (!smiles) { alert("请先绘制结构"); return; } // 继续后续操作,如检索、提交等 });
data.value 为空字符串,需先判空再提交。
回填结构到编辑器
将已知的 Molfile 字符串渲染到编辑器中,通常用于"查看详情"或"再次编辑"场景。命令为 setMolfile,参数字段为 mol(Molfile 格式,非 SMILES):
// 取得当前编辑器的 Molfile(例如保存时使用) CJSDraw_SDK.Do("getMolfile", {}, function(data) { var molfile = data.value; // Molfile 字符串,在 data.value 字段 console.log(molfile); }); // 回填 Molfile 到编辑器(再次编辑场景) CJSDraw_SDK.Do("setMolfile", { mol: molfileString }, function(data) { console.log("结构已回填"); });
setMolfile 回填。
编辑器配置项参考
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
appkey | string | 必填 | — | 编辑器授权 AppKey(UUID) |
version | string | 可选 | 默认编辑器 | 编辑器版本:不传使用默认编辑器;传 "ketcher-sa" 切换为 Ketcher 编辑器(功能更丰富,支持反应式) |
skin | string | 可选 | "w8" | 编辑器皮肤,仅默认编辑器有效,固定为 w8 |
width | string | 可选 | "100%" | 编辑器宽度,支持 CSS 值如 "800px" |
height | string | 可选 | "510px" | 编辑器高度,建议不低于 400px |
callback | function | 可选 | — | 编辑器加载完成后的回调函数 |
编辑器选型
平台支持两款结构编辑器,通过 Init() 的 version 参数切换,其余 API(getSmiles、setMolfile 等)完全一致。
| 编辑器 | version 值 | 特点 | 适用场景 |
|---|---|---|---|
| 默认编辑器(JSDraw) | 不传 / 省略 | 轻量、加载快,基础绘图功能完整 | 大多数化合物检索场景 |
| Ketcher | "ketcher-sa" |
功能全面、界面现代,支持反应式(Reaction)、SMARTS、模板库等 | 需要复杂绘图、反应式搜索的场景 |
切换为 Ketcher 编辑器
// 使用 Ketcher 编辑器:仅需传入 version: "ketcher-sa" CJSDraw_SDK.Init("jsdrawbox", { appkey: "your-appkey-uuid", version: "ketcher-sa", // ← 切换为 Ketcher width: "100%", height: "560px", // Ketcher 建议高度 ≥ 500px callback: function() { console.log("Ketcher 已就绪"); }, });
CJSDraw_SDK.Do("getSmiles", ...)、Do("getMolfile", ...)、Do("setMolfile", ...) 的调用方式完全相同,无需修改业务代码。
推荐接入架构
结构检索需要携带 ApiKey,为防止泄露,不能由前端直接调用检索接口。正确的做法是让前端从编辑器获取 SMILES 后,提交给你自己的后端接口,由后端持有 ApiKey 并转发给我们的检索服务。
获取 SMILES
转发检索请求
返回结果
前端发起搜索
用户点击搜索时,前端从编辑器取出 SMILES,连同检索类型等参数一起提交给你自己的后端接口:
function doSearch(page) { // 第一步:从编辑器获取当前绘制的结构 CJSDraw_SDK.Do("getSmiles", {}, function(data) { var smiles = data.value; if (!smiles) { alert("请先绘制结构"); return; } // 第二步:提交给你自己的后端接口 // ⚠ 这里调用的是你自己的服务端 URL,不是结构检索服务 fetch("/your-api/structure-search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ smiles: smiles, searchtype: $("input[name='stype']:checked").val(), page: page || 1, psize: 12, }), }) .then(r => r.json()) .then(result => renderResults(result, page === 1)) .catch(err => console.error("搜索失败", err)); }); }
后端代理说明
你的后端接收前端传来的 SMILES 和检索参数后,以 ApiKey 为鉴权凭证,调用我们的结构检索 REST API,再将结果返回给前端。ApiKey 始终保存在服务端环境变量中,不会暴露给浏览器。
import os, requests # ApiKey 从环境变量读取,严禁硬编码或返回给前端 API_KEY = os.environ["CHEM_API_KEY"] def structure_search(smiles, searchtype, page=1, psize=12): resp = requests.post( "https://your-chem-service.com/api/search", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "smiles": smiles, "searchtype": searchtype, "page": page, "psize": psize, }, ) return resp.json()
检索类型枚举
向后端传递 searchtype 参数以指定检索模式,由后端转发给检索服务:
渲染检索结果
你的后端将检索结果返回给前端后,前端根据数据渲染结果列表。建议后端直接在结果中携带预存的结构图 URL(imgUrl),避免前端批量实时调用 /api/mol2svg 渲染接口耗费服务器资源(详见结构渲染接口说明)。
后端建议返回的数据格式
{
"ok": true,
"nextpage": 2, // 下一页页码,≤ 0 表示无更多结果
"list": [
{
"cas": "110-89-4", // CAS 号
"smile": "C1CCNCC1", // SMILES
"imgUrl":"/api/mol2svg?smile=C1CCNCC1", // 保存时预存的结构图 URL,直接展示
"enname":"Piperidine" // 英文名
}
]
}
前端渲染示例
var nextpage = 1; function renderResults(result, isFirstPage) { if (!result.ok) { $("#results").html("<p>检索失败,请稍后重试</p>"); return; } if (!result.list || result.list.length === 0) { $("#results").html("<p>未检索到匹配化合物</p>"); return; } var html = ''; result.list.forEach(function(item) { // 使用后端预存的结构图 URL(推荐),避免批量实时渲染耗费服务器资源 var imgUrl = item.imgUrl || ('/api/mol2svg?smile=' + encodeURIComponent(item.smile)); html += '<div class="result-card">' + ' <img src="' + imgUrl + '" alt="结构式" loading="lazy">' + ' <p class="cas">' + item.cas + '</p>' + ' <p class="name">' + (item.enname || '—') + '</p>' + '</div>'; }); if (isFirstPage) { $("#results").html(html); } else { $("#results").append(html); // 加载更多时追加 } // 控制"加载更多"按钮 nextpage = parseInt(result.nextpage); $("#load-more").toggle(nextpage > 0); }
结构渲染为 SVG 图片
无需鉴权,直接拼接图片 URL 即可将 SMILES 渲染为结构式图片,可直接用于 <img> 标签:
<!-- GET /api/mol2svg?smile=SMILES --> <img src="/api/mol2svg?smile=C1CCNCC1" alt="结构式" style="max-width:200px">
// 动态拼接,注意 SMILES 需 encodeURIComponent var smiles = "C1CCNCC1"; var imgUrl = "/api/mol2svg?smile=" + encodeURIComponent(smiles); $("#mol-img").attr("src", imgUrl);
推荐做法:保存化合物时,预先将结构式静态图片地址存入记录;查询结果直接返回该地址,前端无需实时调用渲染接口。
完整页面示例
以下是一个符合安全架构的完整最小化示例:前端仅持有 AppKey,所有检索请求通过调用你自己的后端接口完成,ApiKey 不出现在任何前端代码中。
/your-api/structure-search 是你自己服务端的代理接口,由你的后端持有 ApiKey 并转发给检索服务。请勿将 ApiKey 替换进此前端代码。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>结构式检索</title> <script src="//mol.mkbio.com.cn/static/jquery.min.js"></script> <script src="//mol.mkbio.com.cn/static/sdk.js"></script> <script>CJSDraw_SDK.Cfg.APIURL = "//mol.mkbio.com.cn";</script> <style> body { font-family: sans-serif; max-width: 960px; margin: 0 auto; padding: 20px; } #results { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; } .card { border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; width: 160px; text-align: center; } .card img { max-width: 100%; height: 120px; object-fit: contain; } .card p { font-size: 12px; color: #64748b; margin: 4px 0 0; } </style> </head> <body> <!-- 编辑器容器 --> <div id="jsdrawbox"></div> <!-- 检索类型 + 搜索按钮 --> <div style="margin-top:12px;"> <label><input type="radio" name="stype" value="1" checked> 子结构</label> <label><input type="radio" name="stype" value="2"> 全结构</label> <label><input type="radio" name="stype" value="4"> 相似≥90%</label> <button id="btn-search" style="margin-left:12px;padding:6px 18px;">搜索</button> </div> <!-- 结果容器 --> <div id="results"></div> <div id="load-more" style="display:none;text-align:center;margin-top:16px;"> <button id="btn-more">加载更多</button> </div> <script> // ⚠ 前端只持有 AppKey,ApiKey 由后端保管,禁止出现在此处 var APP_KEY = "your-appkey-uuid"; var nextpage = 1; var curSmiles = ""; var curType = "1"; // 初始化编辑器(Init 只接受两个参数,callback 在 cfg 内) CJSDraw_SDK.Init("jsdrawbox", { appkey: APP_KEY, skin: "w8", width: "100%", height: "480px", callback: function() { console.log("编辑器就绪"); }, }); // 新检索:先取 SMILES,再调自己的后端接口 function newSearch() { CJSDraw_SDK.Do("getSmiles", {}, function(data) { if (!data.value) { alert("请先绘制结构"); return; } curSmiles = data.value; curType = $("input[name='stype']:checked").val(); nextpage = 1; $("#results").empty(); fetchSearch(); }); } // 调用你自己的后端代理接口 function fetchSearch() { fetch("/your-api/structure-search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ smiles: curSmiles, searchtype: curType, page: nextpage, psize: 12, }), }) .then(r => r.json()) .then(function(result) { if (!result.ok) { alert("检索失败"); return; } if (!result.list || !result.list.length) { $("#results").html("<p>未检索到匹配化合物</p>"); return; } result.list.forEach(function(item) { // 使用后端预存的结构图 URL,避免批量实时渲染 var imgUrl = item.imgUrl || ('/api/mol2svg?smile=' + encodeURIComponent(item.smile)); $("#results").append( '<div class="card">' + '<img src="' + imgUrl + '" loading="lazy" alt="结构式">' + '<p>' + item.cas + '</p>' + '<p>' + (item.enname || '—') + '</p>' + '</div>' ); }); nextpage = parseInt(result.nextpage); $("#load-more").toggle(nextpage > 0); }); } $(function() { $("#btn-search").on("click", newSearch); $("#btn-more").on("click", fetchSearch); }); </script> </body> </html>