Back to releases
LatestPublished

企业官网正式上线

基于 Strapi v5 + Nuxt 4 的三语企业官网正式发布。涵盖 CMS 内容架构、Admin 后台定制、三语前端页面、认证文件网关、缓存策略与安全加固,并集成微信公众号文章导入与 TLS 证书自动化。

概览

#

企业官网是双仓库项目——Strapi v5 CMS 管内容,Nuxt 4 前端管渲染。编辑人员在多语言 Admin 后台维护产品、新闻和资源,前端在 bioer.com / en.bioer.com / jp.bioer.com 下按域名渲染中英日三种语言,集成认证、搜索、文件门控交付,以及微信公众号文章导入和 TLS 证书自动签发。

架构示意:

用户浏览器
  |
  v
Nginx 80/443
  |-- bioer.com / en.bioer.com / jp.bioer.com -> Nuxt Node 127.0.0.1:3000
  |-- cms.bioer.com        -> Strapi  127.0.0.1:1337
  |-- media.bioer.com      -> 媒体 CDN(Strapi /uploads 渲染域名)

中英日三语共用同一份 Nuxt 构建产物和一个 Node 进程,语言由请求 Host 决定。

CMS — Strapi 后端

#

内容架构

十二个 Collection Type 构成内容骨架:产品带多级分类树、新闻带分类、资源带分类文件库,about/product/article 三种 Banner,首页 home_module(轮播 / 产品展示)模块,联系表单收件箱(生命周期钩子发邮件通知),以及 components/search/seo 组件。

内容组(Content Group)描述字段与翻译键管理:编辑者指定内容所属分组,前端按组渲染布局;多语言字段空值时显示翻译占位符而非空白。

自研插件

两个本地插件扩展 Content Manager:

  • product-category-filter — 列表页按分层分类树筛选产品、文章和资源
  • product-category-sort — 分类树拖拽排序(基于 vendored sortable-entries),排序结果用于前端侧边栏

第三方插件:strapi-plugin-img-convert(上传时生成响应式 WebP/JPEG/PNG 变体)、@_sh/strapi-plugin-ckeditor(富文本)、strapi-soft-delete-plugin(软删除 + 可选定时清理)、@sensinum/strapi-table-field(产品规格表格)。

// 图片响应式配置 — 四个尺寸,webp 优先,保留 JPEG/PNG 兜底
breakpoints: {
  xlarge: { breakpoint: 1566, formats: ['webp', 'jpeg', 'png'] },
  large:  { breakpoint: 1280, formats: ['webp', 'jpeg', 'png'] },
  medium: { breakpoint: 768,  formats: ['webp', 'jpeg', 'png'] },
  small:  { breakpoint: 640,  formats: ['webp', 'jpeg', 'png'] },
},

Admin 后台定制

品牌化:自定义 Logo/Favicon,一套蓝色主色调贯穿按钮、链接、边框、底色。多语言覆盖完整:

  • Admin 界面文本经自定义 locale 文件翻译,硬编码英文在运行时替换
  • 内容模型字段标签跟随 Admin 语言
  • 翻译缺失收集器(report:i18n-nonkey)按需生成审计报告

Content Manager 导航按业务域分组,顺序在 src/admin/app.ts 集中管理。DOM 观察器保证 Vite/HMR 热更新后标题替换、按钮标签、分组标签依然正确。产品/文章编辑页有"前端查看"按钮,一键新标签打开线上页面。

内容工具链

  • 微信公众号导入scrape-wechat-articles.py 抓取 + import-wechat-articles.cjs 导入,配套 purge-soft-deleted.cjs 清理
  • 日文站迁移migrate:ja / publish:ja / validate:ja-plan 脚本链,从中文内容迁移、发布并校验日文站点
  • 首页支持 home_module 切换轮播/产品展示,编辑者不改代码即可通过 Strapi API 实时同步首页展示

Web — Nuxt 前端

#

技术栈

Nuxt 4 + @nuxt/ui 4 + Tailwind CSS 4 + daisyUI 5 + @nuxtjs/i18n 10。en.json/zh.json/ja.json 三语,按域名切换(en.bioer.com / bioer.com / jp.bioer.com),中文站含 www.com.cn 别名。自托管 Inter 字体,WOFF2 覆盖 400–900 字重。媒体资源经 media.bioer.com CDN 域名渲染。

公共页面

页面功能
首页轮播 / 产品展示模块,推荐产品与新闻摘要
产品分类侧边栏、卡片列表、详情页(概览/规格/图库/下载 Tab)、响应式画廊、Markdown 渲染
新闻按分类筛选列表、CKEditor 富文本详情、相关文章
资源可筛选资源库,登录门控的文件预览与下载;hide 模式下非公开资源不出现在列表/搜索
关于公司介绍,荣誉墙卡片、触摸滚动和键盘导航
联系联系表单,客户端校验 + 服务端提交
搜索客户端全文搜索(产品/新闻/资源),多词匹配、加权评分、类型/分类筛选、分页、URL 查询状态
认证登录、注册、隐私政策、服务条款

产品/新闻正文支持受限 Markdown:miniMarkdown 先转义再渲染安全子集,sanitizeHtml 做白名单消毒。

安全架构

认证用 httpOnly JWT Cookie(HTTPS 下加 secure)。会话刷新走 /api/auth/session,未登录返回 { user: null };登录、注册、登出、当前用户端点基于同一套 Cookie 流程。

# 安全响应头中间件(实际生效)
Content-Security-Policy: base-uri 'self'; frame-ancestors 'none'; form-action 'self'; object-src 'none'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), geolocation=(), microphone=()
Strict-Transport-Security: max-age=31536000; includeSubDomains   # HTTPS 时

requestSecurity 统一处理跨站防护:数据接收端点按 Origin/Fetch Metadata 拒绝跨站 POST,限制请求体大小,只接受 application/json。CKEditor 产出的 HTML 渲染前经属性白名单消毒:移除脚本、事件处理器、不安全 URL、任意 CSS 函数、嵌入内容和表单控件。

文件网关

产品/资源文件预览与下载经 Nitro 路由流式传输,浏览器接触不到原始 Strapi 媒体 URL。预览仅限浏览器安全类型(PDF/PNG/JPG/JPEG/WebP)。门控检查登录态、资源可见性和可选邮箱白名单。

基础设施

#

服务端 Nitro 层

端点功能
/api/strapi/**公共 Strapi 内容代理,限制已知 Collection 根路径,含限流(默认 2400 req/60s)
/api/auth/**登录、注册、登出、会话刷新、当前用户
/api/contact-message联系表单提交 + 速率限制 + SMTP 通知
/api/resources/**资源文件下载/预览网关
/api/products/[id]/files/**产品文件下载/预览网关
/sitemap.xml动态产品/新闻 URL + 静态回落
/robots.txt爬虫指令

缓存策略

page-cache-control 中间件按路径分类下发 Cache-Control,TTL 由环境变量控制:

路径响应头默认 s-maxage
动态 HTMLpublic, max-age=0, s-maxage=N300
/privacy-policy/terms-of-use同上,更长3600
/login/register/api/**no-store / private
NUXT_PUBLIC_HTML_CACHE_MAX_AGE=300          # 动态 HTML
NUXT_PUBLIC_STRAPI_API_CACHE_MAX_AGE=300    # /api/strapi/** 代理
NUXT_PUBLIC_STATIC_PAGE_CACHE_MAX_AGE=3600  # 静态页

运维

  • TLS 自动化scripts/tls-automation/bioer-tls.mjs 走 DNS-01 自动签发/续期证书,按域名分发,先等 Nginx TLS 重载再验证
  • 备份与日志backup-operations.mdlogging-operations.md 固化数据库备份与日志采集流程
  • 部署:PM2 守护 + systemd Nginx 反向代理
# 构建与启动(三语共用一个进程,按 Host 分流)
pnpm typecheck && pnpm build
PORT=3000 pm2 start .output/server/index.mjs --name bioer-web --cwd /var/www/site/web --time
pm2 save && pm2 startup

# 更新
cd /var/www/site && git pull && pnpm install && pnpm typecheck && pnpm build
pm2 restart bioer-web

改进

#
  • Strapi 代理查询参数白名单(fields/filters/locale/pagination/populate/publicationState/sort),分页上限每页 100,公共代理限流
  • 文件下载路由流式传输,不整文件读入内存
  • 搜索在客户端本地索引,不依赖外部搜索服务
  • Sitemap 从 Strapi 取动态 URL,后端不可达时回落静态条目并返回 x-sitemap-status: static-fallback
  • 服务启动前 scripts/validate-config.cjs 校验环境变量
  • 会话端点对游客返回 { user: null } 而非 401
  • 软删除默认关闭自动清理,需显式环境变量启用
  • 资源访问 hide 模式:非公开资源不出现在列表与搜索结果,减少暴露面
  • 三语按域名共享一个构建产物与 Node 进程,语言由 Host 决定,无重复部署

修复

#
  • Strapi Admin Vite 开发 host 显式白名单,防隧道服务 EPERM / host-check
  • 本地化报告按需生成,不再以仓库快照提交
  • 未到发布日期的草稿不再出现在公共 API(代理显式 publicationState: live
  • CKEditor 过宽表格换行处理,防移动端横向溢出
  • 软删除条目默认不出现在公共 API
  • Vite HMR 后 Admin 标题替换/按钮标签丢失 — DOM 观察器兜底

已知问题

#
  • Strapi Admin 构建链以 CommonJS 加载 Vite Node API,输出 CJS deprecation 提示,不影响功能
  • 内存速率限制器在 PM2 集群/多节点下失效,届时需切换 Redis 共享存储

升级说明

#

从 v0.2.0 升级:

cd /var/www/site
git pull && pnpm install && pnpm typecheck && pnpm build
pm2 restart bioer-web

新增环境变量(可选,不设置则用默认值):

NUXT_PUBLIC_HTML_CACHE_MAX_AGE=300
NUXT_PUBLIC_STRAPI_API_CACHE_MAX_AGE=300
NUXT_PUBLIC_STATIC_PAGE_CACHE_MAX_AGE=3600
NUXT_PUBLIC_SITE_DOMAIN_JA=jp.bioer.com
NUXT_PUBLIC_MEDIA_URL=https://media.bioer.com
NUXT_PUBLIC_RESOURCES_ACCESS_MODE=hide
SOFT_DELETE_AUTO_PURGE_ENABLED=false
SOFT_DELETE_AUTO_PURGE_TTL_DAYS=30

$ echo "collect what matters, write it down"