内容管理页面增强:上传/删除/API文档集成到原有admin页面
- 将"初始化数据库"和"同步到数据库"按钮替换为"上传内容"和"API接口" - 隐藏"导入"、"导出"、"同步飞书"按钮 - 每个章节条目增加"删除"按钮 - 添加上传面板和API接口文档面板(可展开/收起) - 保持原有侧边栏和页面风格不变 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
454
soul-admin/dist/assets/index-CbOmKBRd.js
vendored
Normal file
454
soul-admin/dist/assets/index-CbOmKBRd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
soul-admin/dist/assets/index-DBQ1UORI.css
vendored
Normal file
1
soul-admin/dist/assets/index-DBQ1UORI.css
vendored
Normal file
File diff suppressed because one or more lines are too long
244
soul-admin/dist/index.html
vendored
Normal file
244
soul-admin/dist/index.html
vendored
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>管理后台 - Soul创业派对</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-CbOmKBRd.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-DBQ1UORI.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var CSS=document.createElement('style');
|
||||||
|
CSS.textContent=`
|
||||||
|
.si-del{padding:2px 8px;font-size:11px;border-radius:4px;cursor:pointer;background:transparent;
|
||||||
|
border:1px solid #7f1d1d;color:#ef4444;margin-left:6px;transition:all .15s}
|
||||||
|
.si-del:hover{background:#7f1d1d;color:#fff}
|
||||||
|
.si-panel{background:#111827;border:1px solid #1e293b;border-radius:10px;padding:20px;margin:16px 0}
|
||||||
|
.si-panel h3{font-size:15px;margin:0 0 14px;color:#e0e6ed}
|
||||||
|
.si-panel label{display:block;font-size:12px;color:#94a3b8;margin:10px 0 4px}
|
||||||
|
.si-panel input,.si-panel select,.si-panel textarea{width:100%;padding:8px 10px;box-sizing:border-box;
|
||||||
|
background:#0a0e17;border:1px solid #1e293b;border-radius:6px;color:#e0e6ed;font-size:13px;outline:none}
|
||||||
|
.si-panel input:focus,.si-panel textarea:focus{border-color:#2dd4a8}
|
||||||
|
.si-panel textarea{min-height:160px;font-family:monospace;resize:vertical}
|
||||||
|
.si-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||||
|
.si-submit{width:100%;padding:10px;margin-top:14px;background:#2dd4a8;color:#0a0e17;
|
||||||
|
border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer}
|
||||||
|
.si-submit:hover{background:#22b896}
|
||||||
|
.si-api{font-family:monospace;font-size:12px;line-height:1.7;color:#94a3b8}
|
||||||
|
.si-api pre{background:#0a0e17;border:1px solid #1e293b;border-radius:6px;padding:12px;
|
||||||
|
overflow-x:auto;margin:6px 0 14px;font-size:12px;color:#2dd4a8;white-space:pre-wrap}
|
||||||
|
.si-api h4{color:#e0e6ed;font-size:13px;margin:16px 0 4px;font-family:sans-serif}
|
||||||
|
.si-toast{position:fixed;top:16px;right:16px;padding:10px 18px;border-radius:6px;
|
||||||
|
font-size:13px;z-index:99999;animation:siFade .25s}
|
||||||
|
.si-toast.ok{background:#065f46;color:#6ee7b7}
|
||||||
|
.si-toast.err{background:#7f1d1d;color:#fca5a5}
|
||||||
|
@keyframes siFade{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(CSS);
|
||||||
|
|
||||||
|
var API='https://souldev.quwanzhi.com';
|
||||||
|
var token=localStorage.getItem('admin_token')||'';
|
||||||
|
|
||||||
|
function toast(m,ok){var t=document.createElement('div');t.className='si-toast '+(ok!==false?'ok':'err');
|
||||||
|
t.textContent=m;document.body.appendChild(t);setTimeout(function(){t.remove()},3000)}
|
||||||
|
function apicall(method,path,body){
|
||||||
|
var opts={method:method,headers:{'Content-Type':'application/json'}};
|
||||||
|
if(token)opts.headers['Authorization']='Bearer '+token;
|
||||||
|
if(body)opts.body=JSON.stringify(body);
|
||||||
|
return fetch(API+path,opts).then(function(r){return r.json()}).catch(function(e){return{success:false,error:e.message}})
|
||||||
|
}
|
||||||
|
function auth(){
|
||||||
|
if(token)return apicall('GET','/api/admin').then(function(r){if(r.success)return true;return doLogin()});
|
||||||
|
return doLogin()
|
||||||
|
}
|
||||||
|
function doLogin(){
|
||||||
|
return apicall('POST','/api/admin',{username:'admin',password:'admin123'}).then(function(r){
|
||||||
|
if(r.success&&r.token){token=r.token;localStorage.setItem('admin_token',token);return true}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findBtn(text){
|
||||||
|
var all=document.querySelectorAll('button');
|
||||||
|
for(var i=0;i<all.length;i++){if(all[i].textContent.trim()===text)return all[i]}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var done=false;
|
||||||
|
|
||||||
|
function run(){
|
||||||
|
if(done)return;
|
||||||
|
if(!location.pathname.includes('content'))return;
|
||||||
|
var initBtn=findBtn('初始化数据库');
|
||||||
|
if(!initBtn)return;
|
||||||
|
done=true;
|
||||||
|
|
||||||
|
// === 1. 改造顶部按钮 ===
|
||||||
|
var syncBtn=findBtn('同步到数据库');
|
||||||
|
var importBtn=findBtn('导入');
|
||||||
|
var exportBtn=findBtn('导出');
|
||||||
|
var feishuBtn=findBtn('同步飞书');
|
||||||
|
|
||||||
|
// 把前两个按钮改成"上传内容"和"API接口"
|
||||||
|
initBtn.textContent='上传内容';
|
||||||
|
initBtn.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('upload')};
|
||||||
|
// 去掉原来的事件
|
||||||
|
var newInit=initBtn.cloneNode(true);
|
||||||
|
newInit.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('upload')};
|
||||||
|
initBtn.parentNode.replaceChild(newInit,initBtn);
|
||||||
|
|
||||||
|
if(syncBtn){
|
||||||
|
var newSync=syncBtn.cloneNode(true);
|
||||||
|
newSync.textContent='API 接口';
|
||||||
|
newSync.onclick=function(e){e.preventDefault();e.stopPropagation();togglePanel('api')};
|
||||||
|
syncBtn.parentNode.replaceChild(newSync,syncBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏其余按钮
|
||||||
|
if(importBtn)importBtn.style.display='none';
|
||||||
|
if(exportBtn)exportBtn.style.display='none';
|
||||||
|
if(feishuBtn)feishuBtn.style.display='none';
|
||||||
|
|
||||||
|
// === 2. 创建面板(插入到 tabs 之前) ===
|
||||||
|
var tabBar=document.querySelector('[role="tablist"]');
|
||||||
|
if(!tabBar){
|
||||||
|
var tabs=findBtn('章节管理');
|
||||||
|
if(tabs)tabBar=tabs.parentElement;
|
||||||
|
}
|
||||||
|
var insertTarget=tabBar||newInit.parentElement;
|
||||||
|
|
||||||
|
// 上传面板
|
||||||
|
var upP=document.createElement('div');
|
||||||
|
upP.className='si-panel';upP.id='si-upload';upP.style.display='none';
|
||||||
|
upP.innerHTML='<h3>上传新章节</h3>'
|
||||||
|
+'<div class="si-row"><div><label>章节ID (留空自动)</label><input id="si-uid" placeholder="如 1.6"></div>'
|
||||||
|
+'<div><label>定价 (0=免费)</label><input type="number" id="si-uprice" value="1" step="0.1" min="0"></div></div>'
|
||||||
|
+'<label>标题 *</label><input id="si-utitle" placeholder="章节标题">'
|
||||||
|
+'<div class="si-row"><div><label>所属篇</label><select id="si-upart">'
|
||||||
|
+'<option value="part-1">第一篇|真实的人</option><option value="part-2">第二篇|真实的行业</option>'
|
||||||
|
+'<option value="part-3">第三篇|真实的错误</option><option value="part-4">第四篇|真实的赚钱</option>'
|
||||||
|
+'<option value="part-5">第五篇|真实的社会</option><option value="appendix">附录</option>'
|
||||||
|
+'<option value="intro">序言</option><option value="outro">尾声</option></select></div>'
|
||||||
|
+'<div><label>所属章</label><select id="si-uchap">'
|
||||||
|
+'<option value="chapter-1">第1章</option><option value="chapter-2">第2章</option>'
|
||||||
|
+'<option value="chapter-3">第3章</option><option value="chapter-4">第4章</option>'
|
||||||
|
+'<option value="chapter-5">第5章</option><option value="chapter-6">第6章</option>'
|
||||||
|
+'<option value="chapter-7">第7章</option><option value="chapter-8">第8章</option>'
|
||||||
|
+'<option value="chapter-9">第9章</option><option value="chapter-10">第10章</option>'
|
||||||
|
+'<option value="chapter-11">第11章</option><option value="appendix">附录</option>'
|
||||||
|
+'<option value="preface">序言</option><option value="epilogue">尾声</option></select></div></div>'
|
||||||
|
+'<label>内容 (Markdown) *</label><textarea id="si-ucontent" placeholder="正文内容... 图片占位用 {{image_1}}"></textarea>'
|
||||||
|
+'<label>图片URL (每行一个)</label><textarea id="si-uimgs" style="min-height:60px" placeholder="https://example.com/1.png"></textarea>'
|
||||||
|
+'<button class="si-submit" id="si-submit-btn">上传章节</button>';
|
||||||
|
insertTarget.parentElement.insertBefore(upP,insertTarget);
|
||||||
|
|
||||||
|
document.getElementById('si-submit-btn').onclick=function(){siUpload()};
|
||||||
|
|
||||||
|
// API文档面板
|
||||||
|
var apiP=document.createElement('div');
|
||||||
|
apiP.className='si-panel';apiP.id='si-apidoc';apiP.style.display='none';
|
||||||
|
apiP.innerHTML='<div class="si-api">'
|
||||||
|
+'<h3 style="font-family:sans-serif">内容管理 API 接口文档</h3>'
|
||||||
|
+'<p>基础域名: <code>https://soulapi.quwanzhi.com</code> (正式) / <code>https://souldev.quwanzhi.com</code> (开发)</p>'
|
||||||
|
+'<h4>1. 获取所有章节 (无需认证)</h4><pre>GET /api/book/all-chapters\n\ncurl https://soulapi.quwanzhi.com/api/book/all-chapters</pre>'
|
||||||
|
+'<h4>2. 获取单章内容</h4><pre>GET /api/book/chapter/:id\n\ncurl https://soulapi.quwanzhi.com/api/book/chapter/1.1</pre>'
|
||||||
|
+'<h4>3. 管理员登录 (获取Token)</h4><pre>POST /api/admin\nBody: {"username":"admin","password":"admin123"}\n\ncurl -X POST https://souldev.quwanzhi.com/api/admin \\\n -H "Content-Type: application/json" \\\n -d \'{"username":"admin","password":"admin123"}\'</pre>'
|
||||||
|
+'<h4>4. 创建/更新章节 (需Token)</h4><pre>POST /api/db/book\nAuthorization: Bearer {token}\nBody: {\n "id": "1.6",\n "title": "标题",\n "content": "Markdown正文",\n "price": 1.0,\n "partId": "part-1",\n "chapterId": "chapter-1"\n}\n\ncurl -X POST https://souldev.quwanzhi.com/api/db/book \\\n -H "Authorization: Bearer TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"id":"1.6","title":"新章节","content":"正文","price":1.0,"partId":"part-1","chapterId":"chapter-1"}\'</pre>'
|
||||||
|
+'<h4>5. 删除章节 (需Token)</h4><pre>DELETE /api/admin/content/:id\n\ncurl -X DELETE https://souldev.quwanzhi.com/api/admin/content/1.6 \\\n -H "Authorization: Bearer TOKEN"</pre>'
|
||||||
|
+'<h4>6. 命令行上传 (数据库直写)</h4><pre>python3 content_upload.py --title "标题" --price 1.0 --content "正文" \\\n --part part-1 --chapter chapter-1\n\npython3 content_upload.py --list-structure # 查看篇章结构\npython3 content_upload.py --list-chapters # 列出所有章节</pre>'
|
||||||
|
+'<h4>7. 数据库直连</h4><pre>Host: 56b4c23f6853c.gz.cdb.myqcloud.com:14413\nUser: cdb_outerroot\nDB: soul_miniprogram\n表: chapters</pre>'
|
||||||
|
+'</div>';
|
||||||
|
insertTarget.parentElement.insertBefore(apiP,insertTarget);
|
||||||
|
|
||||||
|
// === 3. 给每个章节添加删除按钮 ===
|
||||||
|
addDelBtns();
|
||||||
|
new MutationObserver(function(){addDelBtns()}).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||||||
|
}
|
||||||
|
|
||||||
|
var activePanel='';
|
||||||
|
function togglePanel(name){
|
||||||
|
var up=document.getElementById('si-upload');
|
||||||
|
var ap=document.getElementById('si-apidoc');
|
||||||
|
if(!up||!ap)return;
|
||||||
|
if(activePanel===name){up.style.display='none';ap.style.display='none';activePanel='';return}
|
||||||
|
up.style.display=name==='upload'?'block':'none';
|
||||||
|
ap.style.display=name==='api'?'block':'none';
|
||||||
|
activePanel=name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDelBtns(){
|
||||||
|
var all=document.querySelectorAll('button');
|
||||||
|
for(var i=0;i<all.length;i++){
|
||||||
|
var b=all[i];
|
||||||
|
if(b.textContent.trim()==='编辑'&&!b.dataset.sid){
|
||||||
|
b.dataset.sid='1';
|
||||||
|
var del=document.createElement('button');
|
||||||
|
del.className='si-del';
|
||||||
|
del.textContent='删除';
|
||||||
|
(function(editBtn){
|
||||||
|
del.onclick=function(e){
|
||||||
|
e.stopPropagation();e.preventDefault();
|
||||||
|
var row=editBtn.closest('[class]');
|
||||||
|
var txt=row?row.textContent:'';
|
||||||
|
var m=txt.match(/([\d]+\.[\d]+|appendix-[\w]+|preface|epilogue)/);
|
||||||
|
var sid=m?m[0]:'';
|
||||||
|
var name=txt.substring(0,40).replace(/读取|编辑|删除|免费|¥[\d.]+/g,'').trim();
|
||||||
|
if(!confirm('确定删除「'+name+'」'+(sid?' (ID:'+sid+')':'')+' ?'))return;
|
||||||
|
auth().then(function(ok){
|
||||||
|
if(!ok){toast('认证失败',false);return}
|
||||||
|
apicall('DELETE','/api/admin/content/'+(sid||name)).then(function(r){
|
||||||
|
if(r.success!==false){toast('已删除');setTimeout(function(){location.reload()},800)}
|
||||||
|
else{
|
||||||
|
apicall('DELETE','/api/db/book?action=delete&id='+(sid||name)).then(function(r2){
|
||||||
|
if(r2.success!==false){toast('已删除');setTimeout(function(){location.reload()},800)}
|
||||||
|
else toast('删除失败: '+(r2.error||r.error||''),false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})(b);
|
||||||
|
b.parentElement.appendChild(del);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function siUpload(){
|
||||||
|
var title=document.getElementById('si-utitle').value.trim();
|
||||||
|
var content=document.getElementById('si-ucontent').value.trim();
|
||||||
|
if(!title){toast('请填写标题',false);return}
|
||||||
|
if(!content){toast('请填写内容',false);return}
|
||||||
|
var imgs=document.getElementById('si-uimgs').value.trim().split('\n').filter(Boolean);
|
||||||
|
imgs.forEach(function(u,i){content=content.replace('{{image_'+(i+1)+'}}','+')')});
|
||||||
|
var price=parseFloat(document.getElementById('si-uprice').value)||0;
|
||||||
|
var data={
|
||||||
|
id:document.getElementById('si-uid').value.trim()||undefined,
|
||||||
|
title:title,content:content,price:price,isFree:price===0,
|
||||||
|
partId:document.getElementById('si-upart').value,
|
||||||
|
chapterId:document.getElementById('si-uchap').value
|
||||||
|
};
|
||||||
|
toast('上传中...');
|
||||||
|
auth().then(function(ok){
|
||||||
|
if(!ok){toast('认证失败',false);return}
|
||||||
|
apicall('POST','/api/db/book',data).then(function(r){
|
||||||
|
if(r.success!==false){
|
||||||
|
toast('上传成功!');
|
||||||
|
document.getElementById('si-utitle').value='';
|
||||||
|
document.getElementById('si-ucontent').value='';
|
||||||
|
document.getElementById('si-uimgs').value='';
|
||||||
|
document.getElementById('si-uid').value='';
|
||||||
|
setTimeout(function(){location.reload()},1000)
|
||||||
|
}else toast('失败: '+(r.error||''),false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(run,500);
|
||||||
|
new MutationObserver(run).observe(document.getElementById('root'),{childList:true,subtree:true});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
部署到GitHub与宝塔.sh
Executable file
52
部署到GitHub与宝塔.sh
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 1) 以本地为准推送到 GitHub yongpxu-soul
|
||||||
|
# 2) 打包 → SCP 上传 → SSH 解压并 pnpm install + build
|
||||||
|
# 3) 使用宝塔 API 重启 Node 项目(不用 pm2 命令)
|
||||||
|
# 在「一场soul的创业实验」目录下执行
|
||||||
|
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "===== 1. 推送到 GitHub(以本地为准)====="
|
||||||
|
git push origin yongpxu-soul --force-with-lease
|
||||||
|
|
||||||
|
echo "===== 2. 打包 ====="
|
||||||
|
tar --exclude='node_modules' --exclude='.next' --exclude='.git' -czf /tmp/soul_update.tar.gz .
|
||||||
|
|
||||||
|
echo "===== 3. 上传到宝塔服务器 ====="
|
||||||
|
sshpass -p 'Zhiqun1984' scp /tmp/soul_update.tar.gz root@42.194.232.22:/tmp/
|
||||||
|
|
||||||
|
echo "===== 4. SSH:解压、安装、构建(不执行 pm2)====="
|
||||||
|
sshpass -p 'Zhiqun1984' ssh root@42.194.232.22 "
|
||||||
|
cd /www/wwwroot/soul
|
||||||
|
rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next
|
||||||
|
tar -xzf /tmp/soul_update.tar.gz
|
||||||
|
rm /tmp/soul_update.tar.gz
|
||||||
|
export PATH=/www/server/nodejs/v22.14.0/bin:\$PATH
|
||||||
|
pnpm install
|
||||||
|
pnpm run build
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "===== 5. 宝塔 API 重启 Node 项目 soul ====="
|
||||||
|
BT_HOST="42.194.232.22"
|
||||||
|
BT_PORT="9988"
|
||||||
|
BT_KEY="hsAWqFSi0GOCrunhmYdkxy92tBXfqYjd"
|
||||||
|
REQUEST_TIME=$(date +%s)
|
||||||
|
# request_token = md5( request_time + md5(api_key) ),兼容 macOS/Linux
|
||||||
|
md5hex() { printf '%s' "$1" | openssl md5 2>/dev/null | awk '{print $NF}' || true; }
|
||||||
|
MD5_KEY=$(md5hex "$BT_KEY")
|
||||||
|
SIGN_STR="${REQUEST_TIME}${MD5_KEY}"
|
||||||
|
REQUEST_TOKEN=$(md5hex "$SIGN_STR")
|
||||||
|
|
||||||
|
RESP=$(curl -s -k -X POST "https://${BT_HOST}:${BT_PORT}/project/nodejs/restart_project" \
|
||||||
|
-d "request_time=${REQUEST_TIME}" \
|
||||||
|
-d "request_token=${REQUEST_TOKEN}" \
|
||||||
|
-d "project_name=soul" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if echo "$RESP" | grep -q '"status":true\|"status": true'; then
|
||||||
|
echo "宝塔 API 重启成功: $RESP"
|
||||||
|
else
|
||||||
|
echo "宝塔 API 返回(若失败请到面板手动重启): $RESP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "===== 部署完成 ====="
|
||||||
52
部署到Kr宝塔.sh
Executable file
52
部署到Kr宝塔.sh
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 部署到 Kr宝塔 (43.139.27.93):打包 → SCP(端口22022) → SSH 解压构建 → 宝塔 API 重启
|
||||||
|
# 不用 pm2 命令,用宝塔 API 操作。在「一场soul的创业实验」目录下执行。
|
||||||
|
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
SSH_PORT="22022"
|
||||||
|
BT_HOST="43.139.27.93"
|
||||||
|
BT_PORT="9988"
|
||||||
|
BT_KEY="qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
|
||||||
|
PROJECT_PATH="/www/wwwroot/soul"
|
||||||
|
PROJECT_NAME="soul"
|
||||||
|
|
||||||
|
echo "===== 1. 打包 ====="
|
||||||
|
tar --exclude='node_modules' --exclude='.next' --exclude='.git' -czf /tmp/soul_update.tar.gz .
|
||||||
|
|
||||||
|
echo "===== 2. 上传到 Kr宝塔 (${BT_HOST}:${SSH_PORT}) ====="
|
||||||
|
sshpass -p 'Zhiqun1984' scp -P "$SSH_PORT" /tmp/soul_update.tar.gz root@${BT_HOST}:/tmp/
|
||||||
|
|
||||||
|
echo "===== 3. SSH:解压、安装、构建(不执行 pm2)====="
|
||||||
|
sshpass -p 'Zhiqun1984' ssh -p "$SSH_PORT" root@${BT_HOST} "
|
||||||
|
mkdir -p ${PROJECT_PATH}
|
||||||
|
cd ${PROJECT_PATH}
|
||||||
|
rm -rf app components lib public styles *.json *.js *.ts *.mjs *.md .next
|
||||||
|
tar -xzf /tmp/soul_update.tar.gz
|
||||||
|
rm /tmp/soul_update.tar.gz
|
||||||
|
export PATH=/www/server/nodejs/v22.14.0/bin:\$PATH
|
||||||
|
[ -x \"\$(command -v pnpm)\" ] || npm i -g pnpm
|
||||||
|
pnpm install
|
||||||
|
pnpm run build
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "===== 4. 宝塔 API 重启 Node 项目 ${PROJECT_NAME} ====="
|
||||||
|
REQUEST_TIME=$(date +%s)
|
||||||
|
md5hex() { printf '%s' "$1" | openssl md5 2>/dev/null | awk '{print $NF}' || true; }
|
||||||
|
MD5_KEY=$(md5hex "$BT_KEY")
|
||||||
|
SIGN_STR="${REQUEST_TIME}${MD5_KEY}"
|
||||||
|
REQUEST_TOKEN=$(md5hex "$SIGN_STR")
|
||||||
|
|
||||||
|
RESP=$(curl -s -k -X POST "https://${BT_HOST}:${BT_PORT}/project/nodejs/restart_project" \
|
||||||
|
-d "request_time=${REQUEST_TIME}" \
|
||||||
|
-d "request_token=${REQUEST_TOKEN}" \
|
||||||
|
-d "project_name=${PROJECT_NAME}" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if echo "$RESP" | grep -q '"status":true\|"status": true'; then
|
||||||
|
echo "宝塔 API 重启成功: $RESP"
|
||||||
|
else
|
||||||
|
echo "宝塔 API 返回(若失败请到面板 网站→Node项目→${PROJECT_NAME}→重启): $RESP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "===== 部署到 Kr宝塔 完成 ====="
|
||||||
Reference in New Issue
Block a user