完善的轻量说说笔记系统及memos的开源替代品-你的私人朋友圈
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDtQmW5qZEgniaJ1GJUeTZQ5Dwv1uzw9B7KsERKRdgYk1fwtnoajjDZnw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="T1HaBIgPGZEXlfc" /></p><p>前言:近期碰到一个喜欢的一个项目,看到可以类似memos的轻松发布自己的内容...本着喜欢就改造的原则,这个版本的说说笔记就诞生了,使用它,你的数据将由你自由掌控,且可完全免费一键部署,重要的是碎片化的信息记录加自动化就会变的非常高自由度,感兴趣的话不妨来试试</p>
<h2>介绍</h2>
<p>这是基于Ech0基本框架的二次开发、魔改及完善,类似朋友圈样式风格,支持后台配置修改如背景图、个性签名等,支持api获取内容、更新操作等,支持对b站视频、网易云音乐、youtube等的解析添加、支持一键复制,一键生成内容图片、支持httppost发送内容到平台,支持对接webhook、telegram、企业微信、飞书的一键推送,支持内容热力图组件等个性化组件,它完全属于个人的自定化使用,会加入定制化的一些功能,由于代码已重构,不同步于原版</p>
<p>预览:https://note.noisework.cn</p>
<p>源码:https://github.com/rcy1314/echo-noise</p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDuBBTE9HfGsvfhgCsibaibTcoMyaAMKCXznf5V5ZdUYVXuCkruldHzgWw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片" /></p>
<p>原版介绍</p>
<p>Ech0 是一款专为轻量级分享而设计的开源自托管平台,支持快速发布与分享你的想法、文字与链接。简单直观的操作界面,轻松管理你的内容,让分享变得更加自由,确保数据完全掌控,随时随地与世界连接。</p>
<p>原版地址:https://github.com/lin-snow/Ech0</p>
<hr />
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDiapdqHbeib9dzw295tz2AYIhibAfr5ltFL8hza2iaggQiawVDEnlfRmNTPA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="268shot99" /></p>
<h2>整体改版</h2>
<h3>编辑器部分:</h3>
<ol>
<li>自适应高度和拖拽调整功能</li>
<li>扩展的工具栏功能</li>
<li>完整的响应式支持</li>
<li>平滑的过渡动画效果</li>
<li>优化的间距和字体设置</li>
<li>md格式图片即时预览</li>
<li>添加定制化的组件</li>
</ol>
<h2>主页部分:</h2>
<ol>
<li>调整页面内容自适应高度和宽度</li>
<li>添加随机背景图的展示并带有模糊效果</li>
<li>增加md 格式下对网易云音乐、哔哩哔哩视频、youtube、qq 音乐的解析渲染</li>
<li>调整信息条目的背景ui 及显示尺寸的优化</li>
<li>调整ui及加载响应页面的整体显示效果</li>
<li>添加朋友圈样式主图banner,并和背景图使用相同</li>
<li>所有链接都可通过新标签页打开</li>
<li>长内容的折叠展开处理</li>
<li>完善的二次编辑及预览保存</li>
<li>一键复制及生成内容图片的功能化组件</li>
<li>增加标签系统路由及组件</li>
</ol>
<h2>代码部分</h2>
<ol>
<li>调整jwk验证为session方式,同时调整token的验证机制</li>
<li>调整优化数据库的迁移及连接处理</li>
<li>增加不同的路由及调整控制器</li>
<li>增加额外的外挂插件文件</li>
<li>增加定期清理缓存</li>
</ol>
<h2>特征</h2>
<ul>
<li>
<p>一键部署无服务器平台-fly.io、zeabur、railway、vercel</p>
</li>
<li>
<p>外部扩展-支持快捷指令及popclip一键发布内容到站点</p>
</li>
<li>
<p>支持推送渠道(webhook、tg、企业微信、飞书)</p>
</li>
<li>
<p>标签系统和图片api 路由</p>
</li>
<li>
<p>支持链接远程数据库PostgreSQL、MySQL的连接支持,默认SQLite</p>
</li>
<li>
<p>个性化前端组件如发布日历-热力图组件,默认不显示,点击日历图标后显示<br />
<img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDSsrCzFY1I5X1P7H8s0HK0AKnOQicZliaCCs7fkto8iayE8sS0Bwlm8zQA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="1743765992985_副本" /></p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDOp6DIY8T0ONicUeaiauWRhgnqA2wpbJXdaS4p1CKpKpbIYQNYI9e3BIA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="01.45.31" /></p>
</li>
<li>
<p>内容二次编辑及一键复制一键生成内容图片</p>
</li>
<li>
<p>数据库文件的一键备份、上传<br />
<img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDsvhLicS05TVmDfias5QEv4XpkqCHsnp4sial9olLxNtV0hA3mEYZCicv8w/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="ehS1BxwbUKyD2Vm" /></p>
</li>
</ul>
<hr />
<h2>安装部署</h2>
<blockquote>
<p>💡 部署完成后访问 ip:1314 即可使用</p>
</blockquote>
<h2>docker部署</h2>
<p>一键部署</p>
<pre><code>docker run -d \
--name Ech0-Noise \
--platform linux/amd64 \
-p 1314:1314 \
-v /opt/data/noise.db:/app/data/noise.db \
noise233/echo-noise
</code></pre>
<p><code>/opt/data/noise.db</code>是你本地的原有数据库文件,如果没有,可以去掉这个挂载命令,它也会自动创建</p>
<p>说明:如果你是经常使用附件图片发布内容的则可以这样:-v /opt/data:/app/data \</p>
<p>默认用户名:admin</p>
<p>默认用户密码:admin</p>
<h3>docker-componse构建部署</h3>
<p>在该目录下执行以下命令启动服务(不修改环境变量时默认使用本地数据库.db 文件):</p>
<pre><code>docker-compose up -d
</code></pre>
<h2>无服务器平台+postgres免费数据库部署</h2>
<p>数据库使用 Neon PostgreSQL 云数据库服务,其它也支持</p>
<p>请先前往官网https://console.neon.tech部署好你的基础数据库</p>
<p>以下部署文件已放入根目录下的noise文件夹内</p>
<p>部署成功示例:</p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDkRaMicetydPCeFnaibibu5bccKKKxhe7AdfYUPxPa9ibPnYWcPBSR4QeQQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="SDOAt8BsdIiCzXF" /></p>
<h3>Fly.io部署</h3>
<p>fly.toml</p>
<pre><code>app = 'ech0-noise' # 修改为你的自定义容器名
primary_region = 'hkg'
auto_rollback = true
image = 'noise233/echo-noise'
dockerfile = 'Dockerfile'
CGO_ENABLED = '1'
DB_HOST = 'example.aws.neon.tech' # 修改为数据库的HOST地址
DB_NAME = 'noise' # 修改为数据库的名称
DB_PASSWORD = 'example' # 修改为数据库的密码
DB_PORT = '5432'
DB_SSL_MODE = 'require'
DB_TYPE = 'postgres'
DB_USER = 'noise_owner' # 修改为数据库的用户名
TZ = 'Asia/Shanghai'
internal_port = 1314
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
[]
protocol = 'tcp'
internal_port = 1314
[]
port = 1314
[]
memory = '512mb'
cpu_kind = 'shared'
cpus = 1
</code></pre>
<p>部署命令 在准备好 fly.toml 文件后,你可以使用以下命令来部署你的应用到 Fly.io:</p>
<h4>初始化 Fly.io 应用(如果尚未初始化)</h4>
<p><code>fly launch</code></p>
<h4>部署应用</h4>
<p><code>fly deploy</code></p>
<p>确保你已经安装并配置好了 Fly.io 的 CLI 工具,并且已经登录到你的 Fly.io 账号。如果你还没有安装 Fly.io CLI,可以通过以下命令安装:</p>
<pre><code>curl -L https://fly.io/install.sh | sh
</code></pre>
<p>安装完成后,使用 <code>fly auth login</code> 登录到你的 Fly.io 账号。</p>
<h3>zeabur部署</h3>
<p>zeabur.toml</p>
<pre><code>app = "ech0-noise"
dockerfile = "Dockerfile"
image = "noise233/echo-noise"
DB_TYPE = "postgres"
DB_HOST = 'example.aws.neon.tech' # 修改为数据库的HOST地址
DB_PORT = "5432"
DB_USER = 'noise_owner' # 修改为数据库的用户名
DB_PASSWORD = 'example' # 修改为数据库的密码
DB_NAME = 'noise' # 修改为数据库的名称
DB_SSL_MODE = "require"
CGO_ENABLED = "1"
TZ = "Asia/Shanghai"
internal_port = 1314
force_https = true
[]
protocol = "tcp"
internal_port = 1314
[]
port = 1314
[]
memory = "512mb"
cpu_kind = "shared"
cpus = 1
</code></pre>
<h4>部署命令:</h4>
<pre><code>zeabur deploy
</code></pre>
<h3>Railway部署</h3>
<p>railway.toml</p>
<pre><code>app = "ech0-noise"
dockerfile = "Dockerfile"
image = "noise233/echo-noise"
DB_TYPE = "postgres"
DB_HOST = 'example.aws.neon.tech' # 修改为数据库的HOST地址
DB_PORT = "5432"
DB_USER = 'noise_owner' # 修改为数据库的用户名
DB_PASSWORD = 'example' # 修改为数据库的密码
DB_NAME = 'noise' # 修改为数据库的名称
DB_SSL_MODE = "require"
CGO_ENABLED = "1"
TZ = "Asia/Shanghai"
internal_port = 1314
protocol = "tcp"
port = 1314
memory = "512mb"
cpu_kind = "shared"
cpus = 1
</code></pre>
<h4>部署命令:</h4>
<pre><code>railway up
</code></pre>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDVf7iaSGmoDnARujsfQ10Is3ib8OPibBL0dm3ibZliaibHgwYnq77Qa4yb0dA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="预览" /></p>
<p>注意⚠️</p>
<p>如果你是直接在平台拉取项目部署而不是通过命令部署,你需要拷贝fork本项目并将fly.toml、railway.toml、zeabur.toml文件放入根目录下才能一键部署</p>
<h2>数据库连接部分</h2>
<p>本地数据库直接docker部署即可</p>
<p>远程数据库服务则可以通过环境变量连接</p>
<p>连接远程 PostgreSQL:</p>
<pre><code>docker run -d \
--name Ech0-Noise \
--platform linux/amd64 \
-p 1314:1314 \
-e DB_TYPE=postgres \
-e DB_HOST=your.postgres.host \
-e DB_PORT=5432 \
-e DB_USER=your_username \
-e DB_PASSWORD=your_password \
-e DB_NAME=noise \
-v /opt/data/images:/app/data/images \
noise233/echo-noise
</code></pre>
<p>连接远程 MySQL:</p>
<pre><code>docker run -d \
--name Ech0-Noise \
--platform linux/amd64 \
-p 1314:1314 \
-e DB_TYPE=mysql \
-e DB_HOST=your.mysql.host \
-e DB_PORT=3306 \
-e DB_USER=your_username \
-e DB_PASSWORD=your_password \
-e DB_NAME=noise \
-v /opt/data/images:/app/data/images \
noise233/echo-noise
</code></pre>
<p>注意事项:</p>
<ol>
<li>确保远程数据库允许外部连接</li>
<li>检查防火墙设置</li>
<li>使用正确的数据库连接信息</li>
<li>建议使用加密连接</li>
<li>注意数据库的字符集设置</li>
</ol>
<p>对于 Neon PostgreSQL (地址https://console.neon.tech )这样的云数据库服务,需要使用特定的连接参数。以下是连接命令:</p>
<pre><code>docker run -d \
--name Ech0-Noise \
--platform linux/amd64 \
-p 1314:1314 \
-e DB_TYPE=postgres \
-e DB_HOST=your.host \
-e DB_PORT=5432 \
-e DB_USER=user_owner \
-e DB_PASSWORD=password \
-e DB_NAME=yourname \
-e DB_SSL_MODE=require \
-v /opt/data/images:/app/data/images \
noise233/echo-noise
</code></pre>
<p>注意事项:</p>
<ol>
<li>添加了 <code>DB_SSL_MODE=require</code> 环境变量,因为 Neon 要求 SSL 连接</li>
<li>使用了连接 URL 中提供的主机名、用户名、密码和数据库名</li>
<li>保持图片目录的挂载</li>
</ol>
<h2>数据的备份恢复</h2>
<p>对于所有数据库类型(SQLite/PostgreSQL/MySQL),点击后台数据库下载按钮后,都会先备份数据库文件</p>
<ul>
<li>
<p>然后会将包含数据库备份和图片打包成 zip 文件</p>
</li>
<li>
<p>zip 文件中会包含:</p>
<ul>
<li>数据库备份文件(.db/.sql)</li>
<li>images 目录下的所有图片</li>
</ul>
</li>
</ul>
<pre><code>备份过程:
本地 -> 执行备份命令 -> 生成备份文件 -> 打包下载
恢复过程:
上传备份文件 -> 解压缩 -> 执行恢复命令 -> 导入到云数据库
</code></pre>
<p>恢复要求:</p>
<ul>
<li>SQLite本地数据库备份和上传时默认使用的文件名是一致为noise.db</li>
<li>非本地数据库PostgreSQL/MySQL请命名为database.sql并放入database.zip来恢复</li>
<li>如果备份时zip中有图片文件夹则同时会恢复 images 目录下的所有图片</li>
</ul>
<p>⚠️ :因PostgreSQL/MySQL云服务会有SSL连接、兼容版本号、数据表格式等要求,后台一键备份恢复不一定能满足你需要连接的远程数据库,请尽量前往服务商处下载备份</p>
<h2>API指南🧭</h2>
<p>先到后台获取api token,然后可以参考下面的命令运行或使用其它服务(记得将https://your.localhost.com 更改为你自己的服务地址)</p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDEWEicX55Uk3qdE4ocSLjwdoGYErEqsyIPHlibK8yN8eCx8Npz8Q32lQA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="1743847126537" /></p>
<pre><code># 发送纯文本信息
curl -X POST 'https://your.localhost.com/api/token/messages' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer c721249bd66e1133fba430ea9e3c32f1' \
-d '{
"content": "测试信息",
"type": "text"
}'
</code></pre>
<pre><code># 方式1:使用 Markdown 语法发送文本
curl -X POST 'https://your.localhost.com/api/token/messages' \
-H 'Content-Type: application/json' \
-H 'Authorization: c721249bd66e1133fba430ea9e3c32f1' \
-d '{
"content": "# 标题\n这是一段文字\n",
"type": "text"
}'
# 方式2:使用 type: image 发送图片消息
curl -X POST 'https://your.localhost.com/api/token/messages' \
-H 'Content-Type: application/json' \
-H 'Authorization: c721249bd66e1133fba430ea9e3c32f1' \
-d '{
"content": "图片描述文字",
"type": "image",
"image": "https://example.com/image.jpg"
}'
</code></pre>
<p>如果你想使用session 认证方式</p>
<pre><code>curl -v -X POST 'https://your.localhost.com/api/messages' \
-H 'Content-Type: application/json' \
--cookie "your_session_cookie" \
-d '{
"content": "测试信息",
"type": "text"
}'
</code></pre>
<p>对于图文混合消息,可以这样发送:</p>
<pre><code>curl -X POST 'https://your.localhost.com/api/token/messages' \
-H 'Content-Type: application/json' \
-H 'Authorization: c721249bd66e1133fba430ea9e3c32f1' \
-d '{
"content": "# 这是标题\n\n这是一段文字说明\n\n\n\n继续写文字内容",
"type": "text"
}'
</code></pre>
<pre><code>或者使用 multipart 类型:
curl -X POST 'https://your.localhost.com/api/token/messages' \
-H 'Content-Type: application/json' \
-H 'Authorization: c721249bd66e1133fba430ea9e3c32f1' \
-d '{
"content": "# 这是标题\n\n这是一段文字说明",
"type": "multipart",
"image": "https://example.com/image.jpg"
}
</code></pre>
<h1>API 文档(待增加)</h1>
<h2>公共接口</h2>
<h3>1. 获取前端配置</h3>
<ul>
<li><strong>路径</strong>: <code>/api/frontend/config</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取前端配置信息</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/frontend/config
</code></pre>
<h3>2. 用户登录</h3>
<ul>
<li><strong>路径</strong>: <code>/api/login</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 用户登录接口</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"username": "admin",
"password": "password"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
</code></pre>
<h3>3. 用户注册</h3>
<ul>
<li><strong>路径</strong>: <code>/api/register</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 用户注册接口</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"username": "newuser",
"password": "password",
"email": "user@example.com"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/register \
-H "Content-Type: application/json" \
-d '{"username":"newuser","password":"password","email":"user@example.com"}'
</code></pre>
<h3>4. 获取系统状态</h3>
<ul>
<li><strong>路径</strong>: <code>/api/status</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取系统运行状态</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/status
</code></pre>
<h3>5. 消息相关公共接口</h3>
<h4>5.1 获取所有消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取所有公开消息</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/messages
</code></pre>
<h4>5.2 获取单条消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages/:id</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取指定ID的消息</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/messages/1
</code></pre>
<h4>5.3 分页获取消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages/page</code></li>
<li><strong>方法</strong>: POST或GET</li>
<li><strong>描述</strong>: 分页获取消息列表</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"page": 1,
"pageSize": 10
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/messages/page \
-H "Content-Type: application/json" \
-d '{"page":1,"pageSize":10}'
</code></pre>
<h4>5.4 获取消息日历数据</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages/calendar</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取消息发布热力图数据</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/messages/calendar
</code></pre>
<h4>5.5 搜索消息</h4>
<ul>
<li>
<p><strong>路径</strong>: <code>/api/messages/search</code></p>
</li>
<li>
<p><strong>方法</strong>: GET</p>
</li>
<li>
<p><strong>参数</strong>:</p>
<ul>
<li>keyword: 搜索关键词</li>
<li>page: 页码</li>
<li>pageSize: 每页数量</li>
</ul>
</li>
<li>
<p><strong>示例请求</strong>:</p>
</li>
</ul>
<pre><code>curl "http://localhost:8080/api/messages/search?keyword=测试&page=1&pageSize=10"
</code></pre>
<h3>6. RSS 相关接口</h3>
<h4>6.1 获取 RSS 订阅</h4>
<ul>
<li><strong>路径</strong>: <code>/rss</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取 RSS 订阅内容</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:1314/rss
</code></pre>
<h2>需要认证的接口</h2>
<h3>1. 消息操作接口</h3>
<h4>1.1 发布消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 发布新消息</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"content": "消息内容",
"private": false,
"imageURL": ""
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/messages \
-H "Content-Type: application/json" \
-H "Cookie: session=xxx" \
-d '{"content":"测试消息","private":false}'
</code></pre>
<h4>1.2 更新消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages/:id</code></li>
<li><strong>方法</strong>: PUT</li>
<li><strong>描述</strong>: 更新指定消息</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"content": "更新后的内容"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X PUT http://localhost:8080/api/messages/1 \
-H "Content-Type: application/json" \
-H "Cookie: session=xxx" \
-d '{"content":"更新后的内容"}'
</code></pre>
<h4>1.3 删除消息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/messages/:id</code></li>
<li><strong>方法</strong>: DELETE</li>
<li><strong>描述</strong>: 删除指定消息</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X DELETE http://localhost:8080/api/messages/1 \
-H "Cookie: session=xxx"
</code></pre>
<h3>2. 用户相关接口</h3>
<h4>2.1 获取用户信息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取当前登录用户信息</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/user \
-H "Cookie: session=xxx"
</code></pre>
<h4>2.2 修改密码</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user/change_password</code></li>
<li><strong>方法</strong>: PUT</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"oldPassword": "旧密码",
"newPassword": "新密码"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X PUT http://localhost:8080/api/user/change_password \
-H "Content-Type: application/json" \
-H "Cookie: session=xxx" \
-d '{"oldPassword":"old","newPassword":"new"}'
</code></pre>
<h4>2.3 更新用户信息</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user/update</code></li>
<li><strong>方法</strong>: PUT</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X PUT http://localhost:8080/api/user/update \
-H "Content-Type: application/json" \
-H "Cookie: session=xxx" \
-d '{"username":"newname"}'
</code></pre>
<h4>2.4 退出登录</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user/logout</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/user/logout \
-H "Cookie: session=xxx"
</code></pre>
<h3>3. Token 相关接口</h3>
<h4>3.1 获取用户 Token</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user/token</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/user/token \
-H "Cookie: session=xxx"
</code></pre>
<h4>3.2 重新生成 Token</h4>
<ul>
<li><strong>路径</strong>: <code>/api/user/token/regenerate</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/user/token/regenerate \
-H "Cookie: session=xxx"
</code></pre>
<h3>4. 系统设置接口</h3>
<h4>4.1 更新系统设置</h4>
<ul>
<li><strong>路径</strong>: <code>/api/settings</code></li>
<li><strong>方法</strong>: PUT</li>
<li><strong>请求体</strong>:</li>
</ul>
<pre><code>{
"allowRegistration":true,
"frontendSettings":{
"siteTitle":"网站标题",
"subtitleText":"副标题",
"avatarURL":"头像URL",
"username":"显示用户名",
"description":"描述",
"backgrounds":["背景图URL"],
"cardFooterTitle":"页脚标题",
"cardFooterLink":"页脚链接",
"pageFooterHTML":"页脚HTML",
"rssTitle":"RSS标题",
"rssDescription":"RSS描述",
"rssAuthorName":"RSS作者",
"rssFaviconURL":"RSS图标URL",
"walineServerURL":"评论系统URL"
}
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X PUT http://localhost:8080/api/settings \
-H "Content-Type: application/json" \
-H "Cookie: session=xxx" \
-d '{"allowRegistration":true,"frontendSettings":{"siteTitle":"我的网站"}}'
</code></pre>
<h3>5. 备份相关接口</h3>
<h4>5.1 下载备份</h4>
<ul>
<li><strong>路径</strong>: <code>/api/backup/download</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl http://localhost:8080/api/backup/download \
-H "Cookie: session=xxx" \
--output backup.sql
</code></pre>
<h4>5.2 恢复备份</h4>
<ul>
<li><strong>路径</strong>: <code>/api/backup/restore</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 从备份文件恢复数据</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/backup/restore \
-H "Cookie: session=xxx" \
-F "file=@backup.sql"
</code></pre>
<h3>6. 图片上传接口</h3>
<h4>6.1 上传图片</h4>
<ul>
<li><strong>路径</strong>: <code>/api/images/upload</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 上传图片文件</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/images/upload \
-H "Cookie: session=xxx" \
-F "file=@image.jpg"
</code></pre>
<h3>7.推送配置路由使用说明</h3>
<h4>获取推送配置</h4>
<ul>
<li><strong>路径</strong>: <code>/api/notify/config</code></li>
<li><strong>方法</strong>: GET</li>
<li><strong>描述</strong>: 获取当前推送渠道配置</li>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X GET http://localhost:8080/api/notify/config \
-H "Cookie: session=xxx"
</code></pre>
<h4>保存推送配置</h4>
<ul>
<li><strong>路径</strong>: <code>/api/notify/config</code></li>
<li><strong>方法</strong>: PUT</li>
<li><strong>描述</strong>: 更新推送渠道配置</li>
<li><strong>请求体示例</strong>:</li>
</ul>
<pre><code>{
"webhookEnabled":true,
"webhookURL":"https://webhook.example.com",
"telegramEnabled":true,
"telegramToken":"bot123:ABC",
"telegramChatID":"-100123456",
"weworkEnabled":false,
"weworkKey":"",
"feishuEnabled":true,
"feishuWebhook":"https://open.feishu.cn/xxx",
"feishuSecret":"signature_key"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X PUT http://localhost:8080/api/notify/config \
-H "Cookie: session=xxx" \
-H "Content-Type: application/json" \
-d '{
"webhookEnabled": true,
"webhookURL": "https://webhook.example.com"
}'
</code></pre>
<h4>测试推送</h4>
<ul>
<li><strong>路径</strong>: <code>/api/notify/test</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 测试指定推送渠道</li>
<li><strong>请求体示例</strong>:</li>
</ul>
<pre><code>{
"type": "telegram"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/notify/test \
-H "Cookie: session=xxx" \
-H "Content-Type: application/json" \
-d '{"type": "telegram"}'
</code></pre>
<h4>发送推送</h4>
<ul>
<li><strong>路径</strong>: <code>/api/notify/send</code></li>
<li><strong>方法</strong>: POST</li>
<li><strong>描述</strong>: 手动触发推送(需已配置推送渠道)</li>
<li><strong>请求体示例</strong>:</li>
</ul>
<pre><code>{
"content": "测试消息内容",
"images": ["https://example.com/image.jpg"],
"format": "markdown"
}
</code></pre>
<ul>
<li><strong>示例请求</strong>:</li>
</ul>
<pre><code>curl -X POST http://localhost:8080/api/notify/send \
-H "Cookie: session=xxx" \
-H "Content-Type: application/json" \
-d '{"content": "紧急通知!"}'
</code></pre>
<p>注意事项:</p>
<ol>
<li>所有需要认证的接口都需要在请求头中携带有效的 session cookie</li>
<li>部分接口可能需要管理员权限</li>
<li>所有请求示例中的域名和端口号需要根据实际部署情况调整</li>
<li>文件上传接口需要使用 multipart/form-data 格式</li>
<li>Token 认证接口可以使用 Token 替代 session 进行认证</li>
</ol>
<h2>发布说明</h2>
<p>目前会构建两个版本,</p>
<p>稳定版:latest镜像</p>
<p>实验版:last镜像</p>
<p>如果你需要构建自己的镜像发布-示例:</p>
<pre><code>docker buildx build --platform linux/amd64,linux/arm64 -t noise233/echo-noise:latest --push --no-cache .
</code></pre>
<h1>Memos数据库迁移示例</h1>
<p>其中,你需要设置设置源数据库和目标数据库的路径,源数据库为memos_prod.db(memos数据)目标数据库为database.db(本站数据库),你还需要修改构建插入的数据中的用户名为你自己的用户名,分别迁移了原文本内容、发布时间,可以在noise/memos迁移文件夹中找到该脚本</p>
<p>,运行python3 main.py即可,</p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFDmpTRofopb5tXxDC732PfY0EMiaMBrDako6t8ZnaCaicoFf2HZQYjypLQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="1744202949838" /></p>
<p>迁移结束后将你的数据库文件和原图片文件夹(有的话)打包为zip格式,进入站点后台选择恢复数据上传即可。</p>
<h2>Popclip发送扩展</h2>
<p>选中后自动识别安装,发送时会自动添加一个popclip开头的标签,token可在后台找到</p>
<pre><code>// #popclip extension for Send to Shuo
// name: 说说笔记
// icon: square filled 说
// language: javascript
// module: true
// entitlements:
// options: [{
// identifier: "siteUrl",
// label: "服务端地址",
// type: "string",
// defaultValue: "https://note.noisework.cn",
// description: "请确保地址正确,不要带末尾斜杠"
// }, {
// identifier: "token",
// label: "API Token",
// type: "string",
// description: "从设置页面获取最新Token"
// }]
async function sendToShuo(input, options) {
try {
// 参数预处理
const siteUrl = (options.siteUrl || "").replace(/\/+$/g, "");
const token = (options.token || "").trim();
const content = (input.text || "").trim();
// 验证参数
if (!/^https:\/\/[\w.-]+(:\d+)?$/.test(siteUrl)) {
throw new Error("地址格式错误,示例: https://note.noisework.cn");
}
if (!token) throw new Error("Token不能为空");
if (!content) throw new Error("选中文本不能为空");
// 发送请求
await sendRequestWithXMLHttpRequest(siteUrl, token, content);
PopClip.showText("✓ 发送成功");
} catch (error) {
handleRequestError(error);
}
}
// 使用 XMLHttpRequest 实现网络请求
function sendRequestWithXMLHttpRequest(siteUrl, token, content) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const url = `${siteUrl}/api/token/messages`;
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.timeout = 10000; // 设置超时时间(10秒)
// 设置回调函数
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
let errorMsg = `请求失败 (${xhr.status})`;
try {
const data = JSON.parse(xhr.responseText);
errorMsg = data.message || errorMsg;
} catch {}
reject(new Error(errorMsg));
}
}
};
// 处理网络错误
xhr.onerror = () => reject(new Error("网络错误"));
// 处理超时错误
xhr.ontimeout = () => reject(new Error("请求超时"));
try {
// 发送请求
const payload = JSON.stringify({
content: `#Popclip\n${content}`,
type: "text"
});
xhr.send(payload);
} catch (error) {
reject(new Error("请求发送失败: " + error.message));
}
});
}
// 错误处理
function handleRequestError(error) {
console.error("请求错误:", error);
const errorMap = {
"Failed to fetch": "无法连接到服务器",
"aborted": "请求超时",
"网络错误": "网络错误",
"401": "认证失败,请检查Token",
"404": "API地址不存在"
};
const message = Object.entries(errorMap).find(() =>
error.message.includes(key)
)?. || `请求错误: ${error.message.split('\n').slice(0, 50)}`;
PopClip.showText(`❌ ${message}`);
}
exports.actions = [{
title: "发送至说说笔记",
code: sendToShuo,
icon: "square filled 说"
}];
</code></pre>
<h2>Web组件示例</h2>
<p>如果你想将内容作为说说嵌入或结合到你的网站、博客可以参考</p>
<p>说明:host为站点地址,limit为每页内容数量,domId为容器名,下面的代码展示了使用js来请求数据内容到前端并渲染处理的基本框架,其余需要你自己再丰富css样式和你自己的页面</p>
<p>html前端:</p>
<pre><code>
加载中...
var note = {
host: 'https://note.noisework.cn', //请修改为你自己的站点地址
limit: '10',
domId: '#note'
}
</code></pre>
<p>note.js</p>
<pre><code>// Note says content loading script
document.addEventListener('DOMContentLoaded', function() {
// get parameters from global configuration
const config = window.note || {
host: 'https://note.noisework.cn',
limit: '10',
domId: '#note'
};
// 修改容器选择器
const container = document.querySelector('#note .note-container');
let currentPage = 1;
let isLoading = false;
let hasMore = true;
// create load more button
const loadMoreBtn = document.createElement('button');
loadMoreBtn.id = 'load-more-note';
loadMoreBtn.className = 'load-more';
loadMoreBtn.textContent = '加载更多';
loadMoreBtn.style.display = 'none';
loadMoreBtn.addEventListener('click', loadMoreContent);
// create already loaded all prompt
const loadedAll = document.createElement('div');
loadedAll.id = 'loaded-all-note';
loadedAll.className = 'loaded-all';
loadedAll.textContent = '已加载全部';
loadedAll.style.display = 'none';
container.appendChild(loadMoreBtn);
container.appendChild(loadedAll);
// initial load
loadInitialContent();
asyncfunctionloadInitialContent() {
try {
console.log(`请求URL: ${config.host}/api/messages/page?page=${currentPage}&pageSize=${config.limit}`);
const response = awaitfetch(`${config.host}/api/messages/page?page=${currentPage}&pageSize=${config.limit}`);
if (!response.ok) {
thrownewError(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('API响应数据:', result);
// 修改为检查result.data.items
if (result && result.code === 1 && result.data && result.data.items && Array.isArray(result.data.items)) {
const sortedData = result.data.items.sort((a, b) =>
newDate(b.created_at) - newDate(a.created_at)
);
renderMessages(sortedData);
if (result.data.items.length >= config.limit) {
loadMoreBtn.style.display = 'block';
} else {
loadedAll.style.display = 'block';
hasMore = false;
}
} else {
container.querySelector('.loading-wrapper').textContent = '暂无内容';
hasMore = false;
}
} catch (error) {
console.error('加载内容失败:', error);
container.querySelector('.loading-wrapper').textContent = '加载失败,请刷新重试';
}
}
asyncfunctionloadMoreContent() {
if (isLoading || !hasMore) return;
isLoading = true;
loadMoreBtn.textContent = '加载中...';
currentPage++;
try {
const response = awaitfetch(`${config.host}/api/messages/page?page=${currentPage}&pageSize=${config.limit}`);
if (!response.ok) {
thrownewError(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// 同样修改为检查result.data.items
if (result && result.code === 1 && result.data && result.data.items && Array.isArray(result.data.items)) {
const sortedData = result.data.items.sort((a, b) =>
newDate(b.created_at) - newDate(a.created_at)
);
renderMessages(sortedData);
if (result.data.items.length < config.limit) {
loadMoreBtn.style.display = 'none';
loadedAll.style.display = 'block';
hasMore = false;
}
} else {
loadMoreBtn.style.display = 'none';
loadedAll.style.display = 'block';
hasMore = false;
}
} catch (error) {
console.error('加载更多内容失败:', error);
currentPage--;
} finally {
isLoading = false;
loadMoreBtn.textContent = '加载更多';
}
}
functionrenderMessages(messages) {
const loadingWrapper = container.querySelector('.loading-wrapper');
if (loadingWrapper) {
loadingWrapper.style.display = 'none';
}
messages.forEach(message => {
const messageElement = createMessageElement(message);
container.insertBefore(messageElement, loadMoreBtn);
});
}
functioncreateMessageElement(message) {
const messageDiv = document.createElement('div');
messageDiv.className = 'rssmergecard';
const contentDiv = document.createElement('div');
contentDiv.className = 'rssmergecard-content';
const title = document.createElement('h3');
title.className = 'rssmergecard-title';
title.textContent = message.username || '匿名用户';
const description = document.createElement('div');
description.className = 'rssmergecard-description';
// 解析Markdown内容和特殊链接
let processedContent = message.content || '无内容';
processedContent = parseMarkdown(processedContent);
processedContent = parseSpecialLinks(processedContent);
description.innerHTML = processedContent;
// 如果有图片则添加图片
if (message.image_url) {
const img = document.createElement('img');
img.src = message.image_url.startsWith('http') ? message.image_url : config.host + message.image_url;
img.style.maxWidth = '100%';
img.style.borderRadius = '6px';
img.style.margin = '10px 0';
description.appendChild(img);
}
const metaDiv = document.createElement('div');
metaDiv.className = 'rssmergecard-meta';
const timeSpan = document.createElement('span');
timeSpan.className = 'rssmergecard-time';
timeSpan.textContent = formatDate(message.created_at);
metaDiv.appendChild(timeSpan);
contentDiv.appendChild(title);
contentDiv.appendChild(description);
contentDiv.appendChild(metaDiv);
messageDiv.appendChild(contentDiv);
return messageDiv;
}
functionparseMarkdown(content) {
// 处理标题
content = content.replace(/^#\s(.+)$/gm, '<h1>$1</h1>');
content = content.replace(/^##\s(.+)$/gm, '<h2>$1</h2>');
content = content.replace(/^###\s(.+)$/gm, '<h3>$1</h3>');
// 处理图片 !(url)
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:6px;margin:10px 0;">');
// 处理链接 (url)
content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
// 处理粗体 **text**
content = content.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
// 处理斜体 *text*
content = content.replace(/\*([^*]+)\*/g, '<em>$1</em>');
// 处理代码块 `code`
content = content.replace(/`([^`]+)`/g, '<code>$1</code>');
return content;
}
functionparseSpecialLinks(content) {
// 定义各种平台的正则表达式
constBILIBILI_REG = /https:\/\/www\.bilibili\.com\/video\/((av[\d]{1,10})|(BV[\w]{10}))\/?/g;
constBILIBILI_A_TAG_REG = /<a\shref="https:\/\/www\.bilibili\.com\/video\/((av[\d]{1,10})|(BV[\w]{10}))\/?">.*<\/a>/g;
constQQMUSIC_REG = /<a\shref="https\:\/\/y\.qq\.com\/.*(\/+)(\.html)?".*?>.*?<\/a>/g;
constQQVIDEO_REG = /<a\shref="https:\/\/v\.qq\.com\/.*\/(+)\.html".*?>.*?<\/a>/g;
constSPOTIFY_REG = /<a\shref="https:\/\/open\.spotify\.com\/(track|album)\/([\s\S]+)".*?>.*?<\/a>/g;
constYOUKU_REG = /<a\shref="https:\/\/v\.youku\.com\/.*\/id_(+)\.html".*?>.*<\/a>/g;
constYOUTUBE_REG = /<a\shref="https:\/\/(www\.youtube\.com\/watch\?v=|youtu\.be\/)({11})".*?>.*<\/a>/g;
constNETEASE_MUSIC_REG = /<a\shref="https?:\/\/music\.163\.com\/.*?id=(\d+)<\/a>/g;
// 解析各种链接
return content
.replace(BILIBILI_REG, "<div class='video-wrapper'><iframe src='https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=$1&as_wide=1&high_quality=1&danmaku=0' scrolling='no' border='0' frameborder='no' framespacing='0' allowfullscreen='true' style='position:absolute;height:100%;width:100%;'></iframe></div>")
.replace(YOUTUBE_REG, "<div class='video-wrapper'><iframe src='https://www.youtube.com/embed/$2' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen></iframe></div>")
.replace(NETEASE_MUSIC_REG, "<div class='music-wrapper'><meting-js auto='https://music.163.com/#/song?id=$1'></meting-js></div>")
.replace(QQMUSIC_REG, "<meting-js auto='https://y.qq.com/n/yqq/song$1.html'></meting-js>")
.replace(QQVIDEO_REG, "<div class='video-wrapper'><iframe src='//v.qq.com/iframe/player.html?vid=$1' allowFullScreen='true' frameborder='no'></iframe></div>")
.replace(SPOTIFY_REG, "<div class='spotify-wrapper'><iframe style='border-radius:12px' src='https://open.spotify.com/embed/$1/$2?utm_source=generator&theme=0' width='100%' frameBorder='0' allowfullscreen='' allow='autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture' loading='lazy'></iframe></div>")
.replace(YOUKU_REG, "<div class='video-wrapper'><iframe src='https://player.youku.com/embed/$1' frameborder=0 'allowfullscreen'></iframe></div>");
}
functionformatDate(dateString) {
if (!dateString) return'未知时间';
returnnewDate(dateString).toLocaleString();
}
});
</code></pre>
<p>示例note.css</p>
<pre><code>/* 基础卡片样式 */
.rssmergecard {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
padding: 20px;
transition: all 0.3s ease;
}
.rssmergecard:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
/* 标题样式 */
.rssmergecard-title {
color: #333;
font-size: 18px;
margin: 0 0 10px 0;
}
/* 内容样式 - 支持Markdown渲染 */
.rssmergecard-description {
color: #555;
line-height: 1.6;
font-size: 15px;
}
.rssmergecard-description p {
margin: 10px 0;
}
.rssmergecard-description a {
color: #3498db;
text-decoration: none;
}
.rssmergecard-description a:hover {
text-decoration: underline;
}
.rssmergecard-description img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
/* 元信息样式 */
.rssmergecard-meta {
margin-top: 15px;
font-size: 13px;
color: #999;
}
/* 加载更多按钮样式 */
.load-more {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin: 20px auto;
display: block;
}
.load-more:hover {
background: #2980b9;
}
.loaded-all {
text-align: center;
color: #999;
font-size: 14px;
margin: 20px 0;
}
/* 特殊链接卡片样式 */
.media-card {
background: #f8f9fa;
border-left: 4px solid #3498db;
padding: 15px;
margin: 15px 0;
border-radius: 0 4px 4px 0;
}
.media-card-title {
font-weight: bold;
margin-bottom: 5px;
}
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
height: 0;
margin: 15px 0;
}
.video-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
}
.music-wrapper, .spotify-wrapper {
margin: 15px 0;
border-radius: 8px;
overflow: hidden;
min-height: 86px; /* 确保有足够高度显示播放器 */
}
.music-wrapper meting-js {
width: 100%;
height: 86px;
}
</code></pre>
<p>ios快捷指令</p>
<p>使用快捷指令发布内容到站内,获取:https://www.icloud.com/shortcuts/8ba1240ab39d4bf2b4a02b69a5cc12bf</p>
<p><img src="https://www.3bbs.cn/index-diy/img.php?url=https://mmbiz.qpic.cn/sz_mmbiz_png/JVqvpxA6UlLiaWTORzgYkKpyYCvOrdJFD4vo9b3a0sr1nZ98RibDsCq4FxMgbjRyCypibric5arqz3UlGwpvzqvyEQ/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="idpz8Ea9DQMfyex" /></p>
<h2>问题🙋</h2>
<p>数据库可以直接迁移吗</p>
<p>1、直接上传至部署时挂载的路径中,重新启用,或者在容器文件夹/app/data/noise.db直接替换即可</p>
<p>2、使用后台数据库管理备份功能,支持一键下载、上传</p>
<p>数据库文件下载为zip格式,上传也必须为zip,本地数据库恢复包中必须有noise.db文件</p>
<h2>关于魔改指南🌈</h2>
<p>👉如何自定义化前端数据后添加到数据库?</p>
<p>需要在setting.go、migrate.go、models.go、controllers.go同时写入前端参数的后端定义,并修改前端参数信息为后端可读取的参数,其中controllers.go为控制器</p>
<ul>
<li>database.go 用于数据库连接管理</li>
<li>migrate.go 用于数据库迁移和数据初始化</li>
</ul>
<p>👉前端基本在web目录下,目前模版文件为components目录文件,pages下index.vue为父级模版</p>
<p>👉建议:不要和我一样在同一个文件里修改添加,造成一个文件上千行代码...请尽量使用父子层级来添加代码</p>
<p>最后,希望你能喜欢它!</p>
第一感觉就是程序很不错,界面很好看,安装很复杂。 猫大仙 发表于 2025-4-17 20:32
第一感觉就是程序很不错,界面很好看,安装很复杂。
哈哈,是有点复杂 好复杂。
页:
[1]