feat: 开源项目门户站首版(iotLCM / 秀杰 DTU / XiuAdmin 三项目,注册表驱动)

This commit is contained in:
技小云
2026-11-04 11:08:47 +08:00
committed by MiFaZhan
commit fe63dacae2
27 changed files with 3786 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules
.vitepress/cache
.vitepress/dist
.vitepress/.temp
dist
*.log
+1
View File
@@ -0,0 +1 @@
registry=https://registry.npmmirror.com/
+74
View File
@@ -0,0 +1,74 @@
import { defineConfig } from 'vitepress'
import { projects } from '../data/projects'
/**
* 门户站构建配置
*
* SITE_BASE 由构建参数注入:
* - /www/ 过渡形态:挂在已有网关的 /www/ 路径下(没有独立域名时用)
* - / 独立域名形态:站点独占域名根路径
* 两者内容完全同源,只有资源前缀与路由前缀不同,所以用构建参数而不是改代码。
*/
const SITE_BASE = process.env.SITE_BASE ?? '/www/'
/** 代码仓组织(雪羊科技企业仓) */
const ORG_URL = process.env.ORG_URL ?? 'https://gitee.com/snowgoat'
/** 各项目的文档站地址(按项目 id 覆盖) */
const DOCS_URL: Record<string, string> = {
iotlcm: 'http://open.5iot.com:5051/docs/',
}
export default defineConfig({
srcDir: './pages',
base: SITE_BASE,
title: '雪羊科技开源',
description: '雪羊科技开源项目门户:物联网卡管理、DTU 设备管理、中后台开发框架',
lang: 'zh-CN',
cleanUrls: true,
lastUpdated: false,
head: [
['link', { rel: 'icon', type: 'image/svg+xml', href: `${SITE_BASE}favicon.svg` }],
],
themeConfig: {
nav: [
{ text: '首页', link: '/' },
{ text: '项目', link: '/projects/' },
{ text: '关于', link: '/about' },
],
sidebar: {
'/projects/': [
{
text: '项目',
collapsed: false,
items: projects.map((p) => ({ text: p.name, link: p.page })),
},
],
'/guide/': [
{
text: '门户',
collapsed: false,
items: [{ text: '门户部署', link: '/guide/portal-deploy' }],
},
],
},
socialLinks: [
{ icon: 'github', link: ORG_URL },
],
footer: {
message: '雪羊科技开源 · Apache-2.0',
copyright: 'Copyright © 雪羊科技',
},
search: {
provider: 'local',
},
outline: {
level: [2, 3],
label: '本页目录',
},
docFooter: {
prev: '上一页',
next: '下一页',
},
},
})
+111
View File
@@ -0,0 +1,111 @@
<script setup lang="ts">
import type { Project } from '../../../data/projects'
defineProps<{ project: Project }>()
</script>
<template>
<a :href="project.page" class="project-card">
<div class="project-card-head">
<h3>{{ project.name }}</h3>
<span class="project-status" :data-status="project.status">{{ project.status }}</span>
</div>
<p class="project-tagline">{{ project.tagline }}</p>
<p class="project-summary">{{ project.summary }}</p>
<ul class="project-tech">
<li v-for="t in project.tech" :key="t">{{ t }}</li>
</ul>
<div class="project-foot">
<span class="project-license">{{ project.license }}</span>
<span class="project-link">查看详情 →</span>
</div>
</a>
</template>
<style scoped>
.project-card {
display: block;
padding: 20px 22px;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
transition: border-color 0.2s, transform 0.2s;
}
.project-card:hover {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
}
.project-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.project-card h3 {
margin: 0;
font-size: 20px;
border: none;
padding: 0;
}
.project-status {
flex: none;
font-size: 12px;
padding: 2px 10px;
border-radius: 999px;
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.project-status[data-status='已发布'] {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.project-tagline {
margin: 8px 0 0;
font-weight: 600;
color: var(--vp-c-text-1);
}
.project-summary {
margin: 8px 0 0;
font-size: 14px;
line-height: 1.7;
color: var(--vp-c-text-2);
}
.project-tech {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 14px 0 0;
padding: 0;
list-style: none;
}
.project-tech li {
font-size: 12px;
padding: 2px 8px;
border-radius: 6px;
background: var(--vp-c-default-soft);
color: var(--vp-c-text-2);
}
.project-foot {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
font-size: 13px;
color: var(--vp-c-text-3);
}
.project-link {
color: var(--vp-c-brand-1);
}
</style>
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { projects } from '../../../data/projects'
import ProjectCard from './ProjectCard.vue'
</script>
<template>
<div class="project-list">
<ProjectCard v-for="p in projects" :key="p.id" :project="p" />
</div>
</template>
<style scoped>
.project-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 16px;
margin: 24px 0 0;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import ProjectList from './components/ProjectList.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('ProjectList', ProjectList)
},
} satisfies Theme
+46
View File
@@ -0,0 +1,46 @@
# 开源项目门户(VitePress)构建镜像
#
# 构建参数:
# SITE_BASE —— 站点资源前缀,同时决定容器内 nginx 的形态:
# /www/ 过渡形态:挂在已有网关的 /www/ 路径下(没有独立域名时用)
# / 独立域名形态:站点独占域名根路径
#
# 前缀与 nginx 形态必须配套:VitePress 产物里的资源引用是绝对路径
# (<base>assets/xxx),base=/www/ 时只能由带 alias 的 location 提供服务;
# base=/ 时只能由根路径 location 提供服务。混用会出现「页面 200 但 JS/CSS 全 404」。
#
# 用法(仓库根目录):
# docker build -t snowgoat-open-portal:dev --build-arg SITE_BASE=/www/ .
FROM node:22-alpine AS builder
ARG SITE_BASE=/www/
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk add --no-cache git
WORKDIR /app
COPY package*.json .npmrc ./
RUN npm ci
COPY . .
RUN SITE_BASE=${SITE_BASE} npm run build
FROM nginx:1.27-alpine
ARG SITE_BASE=/www/
COPY --from=builder /app/.vitepress/dist /usr/share/nginx/html
COPY docker/nginx.root.conf /etc/nginx/conf.d/default.conf
COPY docker/nginx.prefix.conf /tmp/nginx.prefix.conf
RUN set -eu; \
base="${SITE_BASE%/}"; \
if [ "$base" = "" ]; then \
echo "门户形态:独立域名(根路径)"; \
else \
sed -e "s|__SITE_BASE__|${SITE_BASE}|g" -e "s|__SITE_BASE_NO_SLASH__|${base}|g" \
/tmp/nginx.prefix.conf > /etc/nginx/conf.d/default.conf; \
echo "门户形态:子路径 ${SITE_BASE}"; \
fi
EXPOSE 80
+71
View File
@@ -0,0 +1,71 @@
# 雪羊科技开源项目门户
开源项目门户站源码。当前收录 **iotLCM**(物联网卡全生命周期管理平台)、**秀杰 DTU 管理平台**、**XiuAdmin**(中后台开发框架)。
- 站点框架:VitePress 1.6(与 iotLCM 的文档站同构)
- 产物:纯静态文件
- 许可:Apache-2.0
## 目录
```text
.
├── data/projects.ts # 项目注册表 —— 门户唯一项目清单来源
├── pages/
│ ├── index.md # 门户首页
│ ├── projects/index.md # 全部项目
│ ├── projects/<id>/index.md # 各项目详情页
│ ├── guide/portal-deploy.md # 门户自身的构建与部署
│ └── about.md # 关于、许可、贡献说明
├── .vitepress/theme/components/ # 项目卡片与列表组件
├── scripts/check-registry.mjs # 注册表与详情页一致性校验
├── deploy/docker-compose.yml # 容器部署编排
└── Dockerfile # 多阶段构建镜像
```
## 新增一个项目
两步,不需要改动站点其它部分:
1. 在 `data/projects.ts` 里加一条记录(名称、定位、说明、代码仓、许可证、技术栈、状态、详情页路径、子页面)
2. 写 `pages/projects/<id>/index.md` 详情页
首页卡片、项目列表页、侧边栏都会自动带上新项目。
构建前会自动校验注册表与详情页目录是否一一对应,不一致直接报错:
```bash
npm run check
```
## 本地开发
```bash
npm ci
npm run dev # http://127.0.0.1:5173
```
## 构建
```bash
npm run build # 产物在 .vitepress/dist/
```
## 部署
```bash
docker compose -f deploy/docker-compose.yml up -d --build
```
默认发布在宿主机 `127.0.0.1:8020`。站点资源前缀由 `SITE_BASE` 决定:
| 形态 | 参数 | 访问方式 |
| --- | --- | --- |
| 子路径(过渡,无独立域名) | `/www/`(默认) | 由已有网关转发 `/www/` 到本容器 |
| 独立域名 | `/` | 域名根路径 |
```bash
SITE_BASE=/ docker compose -f deploy/docker-compose.yml up -d --build
```
详见 [门户部署](pages/guide/portal-deploy.md)。
+88
View File
@@ -0,0 +1,88 @@
/**
* 开源项目注册表 —— 门户的唯一项目清单来源。
*
* 新增一个项目只需两步:
* 1) 在这里加一条记录
* 2) 写 pages/projects/<id>/index.md 详情页,并在 sections 里登记它的子页面
* 首页卡片、项目列表、侧边栏、导航都会自动带上,不需要改任何其它文件。
* 校验:npm run check(构建前自动执行,详情页与注册表不一致会直接报错)
*/
export type ProjectStatus = '已发布' | '公测中' | '开发中'
export interface ProjectSection {
text: string
link: string
}
export interface Project {
/** 目录名与 URL 片段,一经发布不再改动(改名会断链) */
id: string
name: string
/** 一句话定位,展示在卡片标题下 */
tagline: string
/** 两三句说明,展示在卡片正文 */
summary: string
/** 开源代码仓 */
repo: string
/** 许可证标识 */
license: string
/** 技术栈标签 */
tech: string[]
status: ProjectStatus
/** 门户内的详情页路径 */
page: string
/** 详情页下的子页面(快速开始、部署、教程等) */
sections: ProjectSection[]
}
export const projects: Project[] = [
{
id: 'iotlcm',
name: 'iotLCM',
tagline: '物联网卡全生命周期管理平台',
summary:
'卡片从运营商渠道入库 → 划拨给代理商 → 激活 / 续费 / 停机 → 流量与状态同步 → 销户,一个平台覆盖 SIM 卡管理全程。内置中国移动 OneLink、中国电信 5G CMP、中国联通 10646 协议适配,并支持以共享 Token 模式接入多家三方渠道商。',
repo: 'https://gitee.com/snowgoat/iotlcm',
license: 'Apache-2.0',
tech: ['GoFrame v2', 'Vue 3', 'MySQL', 'RabbitMQ', 'TDengine', 'Docker Compose'],
status: '已发布',
page: '/projects/iotlcm/',
sections: [
{ text: '快速开始', link: '/projects/iotlcm/guide/quickstart' },
{ text: '部署与运维', link: '/projects/iotlcm/guide/deploy' },
{ text: '教程视频', link: '/projects/iotlcm/guide/tutorial' },
],
},
{
id: 'xiu-dtu',
name: '秀杰 DTU 管理平台',
tagline: 'DTU 设备全生命周期管理平台',
summary:
'面向 DTU(数据传输单元)设备,覆盖设备生产、出厂、代理商划拨、终端客户绑定、配置下发、固件 OTA 升级、指令下发与运行监控的完整闭环,HTTP / TCP 双通道接入。',
repo: 'https://gitee.com/snowgoat/xiu-dtu',
license: 'Apache-2.0',
tech: ['GoFrame v2', 'Vue 3 + Vben Admin', 'MySQL', 'Redis', 'asynq', 'VitePress'],
status: '已发布',
page: '/projects/xiu-dtu/',
sections: [],
},
{
id: 'xiu-admin',
name: 'XiuAdmin',
tagline: 'GoFrame + Vue3 的全栖中后台框架',
summary:
'高生产率、可扩展、应用化 / 模块化 / 插件化的中后台开发框架。租户、用户、部门、菜单、角色、代码生成、定时任务、消息队列、通知公告等 22 项内置功能开箱可用。',
repo: 'https://gitee.com/snowgoat/xiu-admin',
license: 'Apache-2.0',
tech: ['GoFrame v2', 'Vue 3 + Vben Admin', 'Ant Design Vue', 'asynq'],
status: '已发布',
page: '/projects/xiu-admin/',
sections: [],
},
]
/** 按 id 取项目,找不到返回 undefined */
export function projectById(id: string): Project | undefined {
return projects.find((p) => p.id === id)
}
+35
View File
@@ -0,0 +1,35 @@
# 门户部署
## 启动
```bash
docker compose -f deploy/docker-compose.yml up -d --build
```
默认发布在宿主机 `127.0.0.1:8020`(用 `PORTAL_PORT` 改端口)。
## 两种形态
构建参数 `SITE_BASE` 决定站点资源前缀,同时决定容器内 nginx 的形态:
| 形态 | 参数 | 访问方式 |
| --- | --- | --- |
| 子路径(过渡,无独立域名) | `/www/`(默认) | 由已有网关转发 `/www/` 到本容器 |
| 独立域名 | `/` | 域名根路径,如 `https://<门户域名>/` |
```bash
SITE_BASE=/ docker compose -f deploy/docker-compose.yml up -d --build
```
内容同源,切换只改构建参数与入口配置,页面不用改。
> VitePress 产物里的资源引用是绝对路径(`<base>assets/xxx`),base 与 nginx 的 location 形态必须配套,
> 混用会出现「页面 200 但 JS/CSS 全 404」的白屏。镜像构建脚本按 `SITE_BASE` 自动选对应配置。
## 校验
门户项目清单与详情页的一致性在构建前自动校验:
```bash
npm run check
```
+27
View File
@@ -0,0 +1,27 @@
# 开源项目门户部署编排
#
# docker compose -f deploy/docker-compose.yml up -d --build
#
# 形态切换(站点资源前缀):
# SITE_BASE=/www/ 子路径形态,挂在已有网关的 /www/ 路径下(默认)
# SITE_BASE=/ 独立域名形态,站点独占域名根路径
#
# SITE_BASE=/ docker compose -f deploy/docker-compose.yml up -d --build
services:
portal:
build:
context: ..
dockerfile: Dockerfile
args:
SITE_BASE: '${SITE_BASE:-/www/}'
image: snowgoat-open-portal:local
container_name: snowgoat-open-portal
restart: unless-stopped
ports:
- "127.0.0.1:${PORTAL_PORT:-8020}:80"
healthcheck:
test: ["CMD", "wget", "--quiet", "--output-document=/dev/null", "http://127.0.0.1${SITE_BASE:-/www/}"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
+24
View File
@@ -0,0 +1,24 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 过渡形态:站点挂在统一网关的子路径下(没有独立域名时用)。
#
# VitePress 产物里的资源引用是绝对路径(<base>assets/xxx),
# 而 dist 目录是平铺的(index.html、assets/ 直接在根)。
# 因此必须在子路径下用 alias 把前缀剥掉再落到产物目录,
# 否则页面能返回 200 但 JS/CSS 全部 404 → 白屏。
# 网关侧对应 location ^~ <prefix> { proxy_pass http://gw_website; }(不剥前缀)。
location __SITE_BASE__ {
alias /usr/share/nginx/html/;
try_files $uri $uri.html $uri/ $uri/index.html =404;
}
# 访问不带尾斜杠的前缀时补斜杠,否则相对资源路径会解析错
location = __SITE_BASE_NO_SLASH__ {
return 301 __SITE_BASE__;
}
}
+12
View File
@@ -0,0 +1,12 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 独立域名形态:站点独占域名根路径,产物平铺在根目录。
location / {
try_files $uri $uri.html $uri/ =404;
}
}
+2534
View File
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
{
"name": "snowgoat-open-portal",
"private": true,
"type": "module",
"scripts": {
"dev": "vitepress dev . --host 127.0.0.1",
"build": "vitepress build .",
"preview": "vitepress preview . --host 127.0.0.1",
"check": "node scripts/check-registry.mjs",
"prebuild": "npm run check",
"predev": "npm run check"
},
"devDependencies": {
"vitepress": "^1.6.4"
}
}
+25
View File
@@ -0,0 +1,25 @@
---
title: 关于
description: 雪羊科技开源项目门户说明
outline: deep
---
# 关于
雪羊科技开源项目门户汇集公司对外发布的开源项目。当前收录物联网卡管理平台(iotLCM)、DTU 设备管理平台(秀杰 DTU)与中后台开发框架(XiuAdmin)。
## 许可证
全部项目以 **Apache-2.0** 发布。可自由使用、修改、商用与再分发;分发时保留版权声明与许可证原文,并对修改部分注明变更。各项目依赖的上游组件保留其原始许可。
## 参与贡献
1. 从目标项目的默认分支切出特性分支,命名 `feat/xxx`、`fix/xxx`
2. 提交前确保各自的构建与测试通过(Go 服务跑 `go build ./...` 与 `go test ./...`,前端跑 `pnpm build`)
3. 提交合入请求并说明变更动机
## 门户本身
门户是一个独立的 VitePress 工程,与平台代码不在同一个仓库。构建产物是纯静态站点,可用任意静态服务器或容器托管。
镜像构建与部署说明见[门户部署](/guide/portal-deploy)。
+52
View File
@@ -0,0 +1,52 @@
---
title: 门户部署
description: 开源项目门户的构建与部署方式
outline: deep
---
# 门户部署
门户是一个独立的 VitePress 工程,产物是纯静态文件。
## 本地构建
```bash
npm ci
npm run build
```
产物在 `.vitepress/dist/`。
## 容器部署
```bash
docker compose -f deploy/docker-compose.yml up -d --build
```
默认发布在宿主机 `127.0.0.1:8020`,容器内监听 80。
## 两种路径形态
构建参数 `SITE_BASE` 决定站点资源前缀:
| 形态 | 参数 | 访问方式 |
| --- | --- | --- |
| 子路径(过渡形态,无独立域名) | `/www/`(默认) | 挂在已有网关的 `/www/` 路径下 |
| 独立域名 | `/` | 站点独占域名根路径 |
```bash
SITE_BASE=/ docker compose -f deploy/docker-compose.yml up -d --build
```
两者内容完全同源,切换只改构建参数与入口配置,页面不用改。
> VitePress 产物里的资源引用是绝对路径(`<base>assets/xxx`),base 与 nginx 的 location 形态必须配套,混用会出现「页面 200 但 JS/CSS 全 404」的白屏。镜像构建脚本按 `SITE_BASE` 自动选对应的 nginx 配置。
## 新增项目
项目清单由 `data/projects.ts` 单一注册表驱动,新增项目不需要改动站点其它部分:
1. 在注册表里加一条记录
2. 写 `pages/projects/<id>/index.md` 详情页
构建前会自动校验注册表与详情页目录是否一一对应。
+39
View File
@@ -0,0 +1,39 @@
---
layout: home
hero:
name: 雪羊科技开源
text: 物联网与中后台基础软件
tagline: 开源项目门户 · Apache-2.0
actions:
- theme: brand
text: 浏览全部项目
link: /projects/
- theme: alt
text: iotLCM 快速开始
link: /projects/iotlcm/guide/quickstart
features:
- title: 全生命周期管理
details: iotLCM 覆盖物联网 SIM 卡的入库、划拨、激活、续费、停机、流量与状态同步、销户全流程。
- title: 设备接入闭环
details: 秀杰 DTU 管理平台覆盖 DTU 设备生产、出厂、划拨、绑定、配置下发、固件 OTA 与指令下发。
- title: 中后台开发底座
details: XiuAdmin 是基于 GoFrame + Vue3 的全栖中后台框架,租户、权限、代码生成、消息队列等内置可用。
- title: 一条命令部署
details: 各项目均提供 Docker Compose 编排与预构建镜像,clone 之后即可跑起来。
- title: 统一 Apache-2.0
details: 全部项目以 Apache-2.0 发布,可自由使用、修改、商用与再分发。
- title: 持续新增
details: 门户项目清单由单一注册表驱动,新增项目不需要改动站点其它部分。
---
## 开源项目
<ProjectList />
## 使用说明
- 每个项目在「项目详情」页给出定位、能力边界与代码仓地址
- 部署与运维细节见各项目的指南页
- 源码仓统一托管在 Gitee 企业仓 `snowgoat` 命名空间下
+20
View File
@@ -0,0 +1,20 @@
---
title: 全部项目
description: 雪羊科技开源项目清单
outline: deep
---
# 全部项目
<ProjectList />
## 关于这份清单
项目清单由站点源码里的单一注册表维护(`data/projects.ts`),首页卡片、侧边栏与本页都从它生成。
新增一个项目需要两步:
1. 在注册表里加一条记录(名称、定位、说明、代码仓、许可证、技术栈、状态)
2. 写 `pages/projects/<id>/index.md` 详情页,若有子页面(快速开始、部署、教程)在记录的 `sections` 里登记
站点构建前会自动校验「注册表条目」与「详情页目录」是否一一对应,不一致直接报错,避免清单与页面脱节。
+111
View File
@@ -0,0 +1,111 @@
---
title: 部署与运维
description: 生产部署前的必改项、可选组件、离线部署与常见问题
outline: deep
---
# 部署与运维
## 一键启动
```bash
git clone https://gitee.com/snowgoat/iotlcm.git
cd iotlcm/deploy
docker compose up -d
```
部署编排与数据库初始化脚本都在仓库的 `deploy/` 目录:
```text
deploy/
├── docker-compose.yml # 服务编排(含默认口令与端口)
├── compose.memlimit.yml # 容器内存上限(按机器规格叠加使用)
├── config/ # 各服务配置 + gateway / nginx 两种入口配置
├── schema/init.sql # MySQL 初始化(95 表 + 种子数据)
├── tdengine-init/ # 时序库建库建表(首次启动自动执行)
└── data/ # 运行数据,首次启动自动创建
```
## 可选组件
```bash
docker compose --profile with-mock up -d # 运营商接口挡板
docker compose --profile with-bot up -d # 飞书通知(运维自用)
docker compose --profile with-nginx up -d # 自带反向代理(多域名入口)
docker compose --profile with-gateway up -d # 单端口入口(默认对外 5051)
```
多个 profile 可组合。
### 单端口入口(with-gateway)
只暴露一个端口时用它。容器内监听 80,宿主机端口由 `GATEWAY_PORT` 控制(默认 5051):
```bash
docker compose --profile with-gateway up -d
# 或换端口
GATEWAY_PORT=8088 docker compose --profile with-gateway up -d
```
| 路径 | 去向 |
| --- | --- |
| `/` | 管理端(admin-ui,内部已代理 `/api/` 到 admin-svr) |
| `/docs/` | 文档站 |
| `/openapi/` | 对外 API |
| `/route/open/` | 运营商能力代理(白名单 / 共享 Token) |
配置见 `config/gateway/nginx.conf`。小程序 / H5 端的静态资源是根路径部署的,不能挂在同一域名的子路径下,需要单独域名或端口(见 `with-nginx` 的 app 段)。
`with-bot` 是把全量同步等运维事件推送到飞书群的**内部运维工具**,不是平台功能。它需要你自己的飞书自建应用凭证(`config/bot/config.yaml` 的 `appId` / `appSecret` / `chatId`),没填就不启动。
**默认不启动,也不影响平台功能**;不需要飞书通知就完全不用管它。
## 线上暴露到公网
80 / 443 入站在多数办公宽带上被运营商封禁,可评估三种形态:
| 形态 | 适用 | 代价 |
| --- | --- | --- |
| 高位端口 NAT | 内部验收、演示 | 地址带端口,不适合对外正式发布 |
| 独立子域 + 反代 | 有公网 IP 的服务器 | 需要域名解析与证书 |
| 云主机统一入口 | 正式对外发布 | 需要域名(以及国内服务器的备案) |
## 上线前必改
| 项 | 说明 |
| --- | --- |
| 数据库口令 | MySQL 的 root 与应用账号口令 |
| Redis 口令 | 缓存层口令 |
| RabbitMQ 口令 | 队列层口令 |
| admin 口令 | 种子账号初始口令统一重置过,必须改 |
| JWT 密钥 | 管理端与用户端签发令牌用 |
| 运营商通道凭据 | 通道配置的 appKey / appSecret / 账号,按运营商要求申请 |
| 对象存储 | 图片与附件的存放位置 |
## 离线部署
目标机器不能出网时:
1. 在有网机器上拉全部镜像并导出:
```bash
docker compose pull
docker save -o iotlcm-images.tar $(docker compose config --images)
```
2. 拷贝 `iotlcm-images.tar` 与仓库的 `deploy/` 目录到目标机器;
3. `docker load -i iotlcm-images.tar` 后再 `docker compose up -d`。
## 常见问题
| 现象 | 原因与处置 |
| --- | --- |
| MySQL 初始化中途失败 | schema 只在数据卷为空时执行,改完脚本必须清空卷才会重跑:`docker compose down && sudo rm -rf ./data/mysql && docker compose up -d` |
| 访问 8019 端口白屏 | 文档站必须带 `/docs/` 路径,裸端口会白屏 |
| 偶发 502 | 容器重建后 upstream IP 变化,nginx 未重新解析;配置已带 `resolver` + `resolve`,若仍出现,重启网关容器即可 |
| 通道调用报 IP 鉴权失败 | 运营商侧需要报备出口 IP,未报备则拒绝调用 |
| 端口被占用 | 编排里业务端口默认绑 `127.0.0.1`,改端口映射即可避开冲突 |
## 监控
Consul 提供服务注册与健康探针,Prometheus 指标入口在编排内已开。日志按服务落在 `deploy/data/logs/` 下,配合 logrotate 生效。
+81
View File
@@ -0,0 +1,81 @@
---
title: 快速开始
description: 三步把 iotLCM 开源版跑起来
outline: deep
---
# 快速开始
## 前置条件
| 项 | 要求 |
| --- | --- |
| 操作系统 | Linux(x86_64 / arm64) |
| Docker | 20.10 以上,含 Compose V2(`docker compose version` 可用) |
| 内存 | 建议 4 GB 以上 |
| 磁盘 | 建议 40 GB 以上(含镜像与数据库) |
| 端口 | 3306 / 6379 / 5672 / 6041 / 6030 / 8500 默认占用,起步前先确认空闲 |
## 第一步:取代码
```bash
git clone https://gitee.com/snowgoat/iotlcm.git
cd iotlcm
```
## 第二步:起栈
```bash
cd deploy
docker compose up -d
```
不需要复制或修改任何文件,编排里已带可用的默认配置。首次启动会自动:
1. MySQL 建库并导入 schema(13 个脚本按序执行:建表 → 结构补齐 → 创建应用账号 → 重置种子账号口令 → 导入通道模板、字典、菜单等种子数据),约 30 秒 ~ 1 分钟
2. TDengine 自动建库建表(流量历史库)
3. 其余服务依次启动并向 Consul 注册
观察进度:
```bash
docker compose logs -f mysql
```
看到 `[initdb] 应用账号 u_iotlcm 已创建` 与 `[initdb] 种子账号口令已统一重置` 表示初始化完成。
## 第三步:登录
部署完成后访问 `http://127.0.0.1:8009`,账号 `admin`,初始口令见仓库的 `deploy/README.md`。
> **正式使用前必须修改默认口令**:数据库、Redis、RabbitMQ、admin 账号口令与 JWT 密钥。清单见[部署与运维](/projects/iotlcm/guide/deploy)的「上线前必改」。
## 端口对照
| 服务 | 宿主机端口 | 说明 |
| --- | --- | --- |
| 管理端 | 8009 | Web 管理平台 |
| 对外接口 | 8000 | OpenAPI V3 与兼容接口 |
| 用户端 | 8018 | 小程序 / H5 |
| 文档站 | 8019 | 使用手册与接口文档,必须带 `/docs/` 路径 |
| 运营商对接 | 8110 / 8310 | 通道协议适配 |
| 通道缓存 | 8510 | 缓存转发 |
| MySQL | 3306 | 库名 `db_iotlcm` |
| Redis | 6379 | 缓存与运行状态 |
| RabbitMQ | 5672 / 15672 | 队列与管理界面 |
| TDengine | 6041 / 6030 | 时序库 |
| Consul | 8500 | 服务注册 |
编排默认把业务端口绑定在 `127.0.0.1`,需要局域网或公网访问时,改用反向代理编排或并入宿主机已有的 nginx。
## 停止与清理
```bash
docker compose down # 停止,数据保留
docker compose down -v # 停止并删除数据卷(不可恢复)
```
## 下一步
- [部署与运维](/projects/iotlcm/guide/deploy)
- [教程视频](/projects/iotlcm/guide/tutorial)
+65
View File
@@ -0,0 +1,65 @@
---
title: 教程视频
description: 部署与使用的录屏教程
outline: deep
---
# 教程视频
开源版的上手路径只有两条:**把平台跑起来**(部署)与**把平台用起来**(使用)。两条路径各一支录屏视频,文字文档只作配套说明,不替代视频。
## 部署教程
| 项 | 内容 |
| --- | --- |
| 标题 | iotLCM 开源版部署教程:从零到可用 |
| 目标观众 | 第一次接触本项目、具备 Linux 与 Docker 基础的开发者与实施工程师 |
| 时长 | 12 ~ 15 分钟 |
| 发布位置 | 官网本页 + 代码仓 README 顶部 + B 站 |
| 关联文档 | [快速开始](/projects/iotlcm/guide/quickstart)、[部署与运维](/projects/iotlcm/guide/deploy) |
| 关联仓库路径 | `deploy/`、`docs/` |
视频覆盖:
1. 环境准备:Docker 与 Compose 版本检查、端口占用确认、机器规格建议
2. 取代码:`git clone` 公开仓库,确认目录结构
3. 启动:`docker compose up -d`,观察 MySQL 初始化日志直到种子数据导入完成
4. 验证:登录管理端、打开文档站与 OpenAPI 文档、跑一次状态查询
5. 收尾:改默认口令、停栈与清理数据卷
## 使用教程
| 项 | 内容 |
| --- | --- |
| 标题 | iotLCM 开源版使用教程:核心功能实操 |
| 目标观众 | 已完成部署、准备给业务方上手的管理员与运营人员 |
| 时长 | 15 ~ 20 分钟 |
| 发布位置 | 官网本页 + 代码仓 README 顶部 + B 站 |
| 关联文档 | [功能概览](/projects/iotlcm/) |
| 关联仓库路径 | `admin-svr/`、`admin-ui/`、`openapi/`、`worker-svr/` |
视频覆盖:
1. 卡信息:列表检索、按三码查卡、卡详情与用量查看
2. 库存与划拨:入库后的代理商维度划拨
3. 资费与业务受理:套餐配置、开通/续费/停复机、达量断网
4. 通道与批次:通道配置查看、批量操作与异步进度
5. 对外接口:申请 API 凭证并完成一次真实调用
6. 账号与工单:企业用户与角色、告警工单处理
## 视频文件放置约定
成片放在代码仓的 `website/public/videos/` 下,文件名用小写连字符加版本号(如 `deploy-tutorial-v1.mp4`),页面里以相对路径引用:
```html
<video controls src="/videos/deploy-tutorial-v1.mp4"></video>
```
若改投 B 站等外部托管,页面保留外链入口即可,视频文件不进仓库(避免仓库体积膨胀)。
## 录制备忘
- 录屏环境用与观众一致的环境:干净的 Ubuntu + Docker Compose V2,不要在已有业务实例上录
- 涉及口令的片段一律打码,示例口令用编排里的默认值
- 步骤与[快速开始](/projects/iotlcm/guide/quickstart)严格一致,视频里出现的每条命令都必须能原样复制执行
- 每个视频产出配套字幕或要点说明(Markdown),随视频一起发布
+58
View File
@@ -0,0 +1,58 @@
---
title: iotLCM
description: 物联网卡全生命周期管理平台
outline: deep
---
# iotLCM
物联网卡全生命周期管理平台。卡片从运营商渠道入库 → 划拨给代理商 → 激活 / 续费 / 停机 → 流量与状态同步 → 销户,一个平台覆盖 SIM 卡管理全程。
对接**中国移动 OneLink**、**中国电信 5G CMP**、**中国联通 10646** 等运营商接口,支持以共享 Token 模式接入三方渠道商,并提供对外的 OpenAPI 服务。
| 项 | 值 |
| --- | --- |
| 代码仓 | <https://gitee.com/snowgoat/iotlcm> |
| 许可证 | Apache-2.0 |
| 状态 | 已发布 |
| 技术栈 | GoFrame v2、Vue 3 + Vben Admin、MySQL、Redis、RabbitMQ、TDengine、Consul、Docker Compose |
| 文档站 | <http://open.5iot.com:5051/docs/> |
## 能力概览
| 域 | 能力 |
| --- | --- |
| 卡资产 | 卡信息、批量导入、入库 / 库存 / 出库、批量开停复机、续费、变更套餐、达量断网 |
| 通信管理 | 通道与接入模板、运营商能力对接、共享 Token 接入三方渠道、通道缓存转发 |
| 资费 | 套餐与流量池、定价、生效与到期策略 |
| 代理商 | 代理商与分佣、划拨与回收 |
| 数据同步 | 流量 / 状态 / IMEI 全量同步与增量对齐、历史账期时序数据 |
| OpenAPI | V3 接口、旧版兼容接口、运营商能力代理(白名单转发 / 共享 Token) |
| 运营 | 工单、预警、执行日志、审计、智能诊断 |
| 端 | Web 管理端、小程序 / H5 用户端 |
## 服务构成
单仓多服务:全部后端服务、前端应用、文档站与部署编排都在同一个仓库里。
| 目录 | 说明 | 镜像 |
| --- | --- | --- |
| `admin-svr/` | 管理端后端(GoFrame) | `iotlcm-admin-svr` |
| `admin-ui/` | 管理端前端(Vue 3 + Vben Admin) | `iotlcm-admin-ui` |
| `openapi/` | 对外 API 网关 | `iotlcm-openapi` |
| `worker-svr/` | 异步执行(RabbitMQ 消费者、全量同步、批量任务) | `iotlcm-worker-svr` |
| `upstream-chan/` | 运营商 / 三方渠道对接层 | `iotlcm-upstream-chan` |
| `upstream-proxy/` | 通道缓存转发层 | `iotlcm-upstream-proxy` |
| `app/server/` | 用户端服务(GoFrame) | `iotlcm-app-server` |
| `app/app/` | 用户端前端(uni-app,基于 unibest) | `iotlcm-app-web` |
| `sms/` | 短信服务(HTTP / gRPC) | `iotlcm-sms` |
| `bot/` | 飞书通知机器人(运维自用,可选) | `iotlcm-bot` |
| `docs/` | 文档站(VitePress)与 OpenAPI 合同 | `iotlcm-docs` |
| `onelink-mock/` | OneLink 接口挡板(联调 / 演示,可选) | `iotlcm-onelink-mock` |
| `deploy/` | Docker Compose 编排与数据库初始化 | — |
## 下一步
- [快速开始](/projects/iotlcm/guide/quickstart) — 三步把整套跑起来
- [部署与运维](/projects/iotlcm/guide/deploy) — 生产部署前的必改项、可选组件、离线部署与排错
- [教程视频](/projects/iotlcm/guide/tutorial) — 部署与使用的录屏教程
+66
View File
@@ -0,0 +1,66 @@
---
title: XiuAdmin
description: 基于 GoFrame2 + Vue3 的全栖中后台框架
outline: deep
---
# XiuAdmin
基于全新 GoFrame2 + Vue3 + VueVbenAdmin 开发的全栖框架,适合大型中后台完整应用开发。
| 项 | 值 |
| --- | --- |
| 代码仓 | <https://gitee.com/snowgoat/xiu-admin> |
| 许可证 | Apache-2.0 |
| 状态 | 已发布 |
| 技术栈 | GoFrame v2、Vue 3、Vue Vben Admin、Ant Design Vue、asynq |
## 框架特征
- 高生产率:极强的可扩展性,应用化、模块化、插件化机制敏捷开发,几分钟即可搭建一个应用开发框架
- 快速生成代码:无需编写代码,只需创建表进行简单配置就能生成完善的 CURD、树表等常用开发代码,所需表单控件也是勾选即可直接生成
- 认证机制:采用 JWT 的用户状态认证
- 路由模式:得益于 GoFrame 提供了规范化的路由注册方式,无需注解自动生成 api 文档
- 模块化设计,面向接口开发
## 内置功能
1. 租户管理:系统内租户的管理,如租户套餐、过期时间、用户数量、企业信息等
2. 租户套餐管理:租户所能使用的套餐管理,如套餐内所包含的菜单等
3. 客户端管理:系统内对接的所有客户端管理,如 pc 端、小程序端等
4. 用户管理:用户是系统操作者,完成系统用户配置
5. 部门管理:配置系统组织机构(公司、部门、岗位),树结构展现支持数据权限
6. 岗位管理:配置系统用户所属担任职务
7. 菜单管理:配置系统菜单,操作权限、按钮权限标识等
8. 角色管理:角色菜单权限分配、设置角色按机构或按上下级关系进行数据范围权限划分
9. 字典管理:对系统中经常使用的一些特定数据进行维护,支持枚举字典和自定义方法字典
10. 配置管理:对系统动态配置常用参数
11. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询
12. 登录日志:系统登录日志记录查询,包含登录异常
13. 服务日志:服务端运行所产生的警告、异常、崩溃日志的详细数据和堆栈信息
14. 在线用户:当前系统中活跃用户状态监控
15. 定时任务:在线(添加、修改、删除)任务调度,包含执行结果日志
16. 代码生成:支持自动化生成前后端代码。CURD 关联表、树表、消息队列、定时任务一键生成等
17. 服务监控:监视当前系统 CPU、内存、磁盘、网络、堆栈等相关信息
18. 缓存监控:对系统的缓存信息查询、命令统计等
19. 附件管理:文件图片上传,大文件分片上传、断点续传,支持本地
20. 消息队列:Asynq 消息队列
21. 通知公告:采用 WebSocket 实时推送在线用户最新通知、公告、私信消息
22. 常用工具:集成常用的工具包和命令行工具,可以快速开发自定义命令行,多种启动入口
## 目录结构
```text
xiu-admin/
├── server/ # GoFrame 后端服务
├── vue/ # 前端 pnpm monorepo
├── docs/ # 文档与演示图例
├── build/ # 构建脚本
└── deploy/ # 部署配置
```
## 参与贡献
1. Fork 代码仓并创建特性分支
2. 提交前确保 `go build ./...` 与 `pnpm build` 通过
3. 提交 Pull Request 并说明变更动机
+140
View File
@@ -0,0 +1,140 @@
---
title: 秀杰 DTU 管理平台
description: DTU 设备全生命周期管理平台
outline: deep
---
# 秀杰 DTU 管理平台
面向 DTU(数据传输单元)设备全生命周期的开源管理平台,覆盖设备生产、出厂、代理商划拨、终端客户绑定、配置下发、固件 OTA 升级、指令下发与运行监控的完整闭环。
| 项 | 值 |
| --- | --- |
| 代码仓 | <https://gitee.com/snowgoat/xiu-dtu> |
| 许可证 | Apache-2.0 |
| 状态 | 已发布 |
| 技术栈 | GoFrame v2、Vue 3 + Vite + TypeScript、Vben Admin 5.x、Ant Design Vue、MySQL 8、Redis、TDengine(可选)、asynq、VitePress |
## 功能概览
| 模块 | 说明 |
| --- | --- |
| 生产中心 | 产品、硬件型号、工厂、生产批次、设备生产与导入导出 |
| 研发中心 | 固件管理、固件版本、OTA 升级任务 |
| 配置中心 | 产品通用模型、配置分组、配置版本与下发状态 |
| 代理中心 | 代理商设备列表、下级代理、设备划拨与划拨历史、我的订单 |
| 系统管理 | 用户、角色、菜单、部门、字典、参数、租户、监控 |
| 设备接入 | HTTP / TCP 双通道接入,指令下发、事件上报、UART 隧道 |
## 目录结构
```text
.
├── server/ # GoFrame 后端服务
│ ├── api/ # 接口定义
│ ├── internal/ # 业务逻辑
│ ├── config/ # 配置文件
│ └── storage/ # 数据库初始化脚本
├── vue/ # 前端 pnpm monorepo
│ └── apps/web-antd/
├── docs_ui/ # VitePress 文档站
├── docker/ # Docker 部署配置
├── deploy/ # systemd / nginx 部署样例
└── tools/ # 设备联调与压测脚本
```
## 快速开始
环境要求:Go 1.25+、Node.js 24+ 与 pnpm 10+、MySQL 8、Redis 7。
### 1. 初始化数据库
```sh
mysql -u root -p -e "CREATE DATABASE xiujie_dtu DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
mysql -u root -p xiujie_dtu < server/storage/data/xiu_admin.sql
mysql -u root -p xiujie_dtu < server/storage/data/xiu_dtu_init.sql
mysql -u root -p xiujie_dtu < server/storage/data/xiu_dtu_menu.sql
mysql -u root -p xiujie_dtu < server/storage/data/xiu_dtu_roles.sql
```
### 2. 配置后端
```sh
cp server/config/config_default.yaml server/config/config.yaml
```
按实际环境修改 `config.yaml` 中的数据库、Redis 连接信息。JWT 签名密钥通过环境变量注入,长度不少于 32 位,未提供时服务会拒绝签发与校验令牌。
### 3. 启动后端
```sh
cd server
go run main.go
```
后端默认监听:控制台接口 `:5689`、设备 HTTP 接口 `:5692`、设备 TCP 接入 `:5690`。
### 4. 启动前端
```sh
cd vue
pnpm install
pnpm dev
```
前端开发地址 `http://localhost:5688`。默认管理员账号 `admin`,首次登录后请立即修改。
### 5. 启动文档站(可选)
```sh
cd docs_ui
npm install
npm run docs:dev
```
文档站地址 `http://localhost:5687`。
## 设备联调
`tools/` 下提供设备侧联调与压测脚本:
```sh
# HTTP 通道联调
BASE_URL="http://127.0.0.1:5692" \
DEVICE_KEY="202606270101010001" \
PRODUCT_KEY="DTU_TEST" \
PROJECT_KEY="PROJECT_KEY_TEST" \
./tools/test-device-http-v4.sh
# TCP 通道联调(含心跳与指令监听)
TCP_HOST="127.0.0.1" \
DEVICE_KEY="202606270101010001" \
PRODUCT_KEY="DTU_TEST" \
PROJECT_KEY="PROJECT_KEY_TEST" \
python3 tools/test-device-tcp-v4.py --heartbeat --listen-command
# 2 万设备压测
BASE_URL="http://127.0.0.1:5689" TCP_HOST="127.0.0.1" \
FACTORY_CODE="TEST_FACTORY_ROLE" PH_CODE="HW_V1" \
PRODUCT_KEY="DTU_TEST" PROJECT_KEY="PROJECT_KEY_TEST" \
python3 tools/stress-2w-devices.py --count 20000 --device-key-template 'SIM{index:012d}'
```
协议细节见代码仓文档站的「通信协议」章节。
## Docker 部署
```sh
cp docker/dtu_iot/.env.example docker/dtu_iot/.env
# 编辑 .env,填入实际域名、数据库与 Redis 口令
cd docker/dtu_base && ./deploy.sh # 初始化 MySQL / Redis / TDengine
cd ../dtu_iot && ./deploy.sh # 部署业务服务
```
镜像构建与推送:
```sh
export DOCKER_USERNAME='your-registry-user'
export DOCKER_PASSWORD='your-registry-token'
make docker-build docker-push
```
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="12" fill="#0f766e"/>
<path d="M20 18h24a4 4 0 0 1 4 4v20a4 4 0 0 1-4 4H20a4 4 0 0 1-4-4V22a4 4 0 0 1 4-4z" fill="none" stroke="#fff" stroke-width="3"/>
<path d="M24 28h16M24 36h10" stroke="#fff" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env node
/**
* 校验「项目注册表」与「详情页目录」是否一一对应。
*
* 门户的项目清单由 data/projects.ts 单一驱动。如果注册表加了项目却忘了写详情页,
* 或者写了详情页却没登记进注册表,首页卡片与侧边栏就会和实际内容脱节。
* 这个脚本在 dev / build 之前自动跑,不一致直接退出非零。
*/
import { readdirSync, readFileSync, existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
const dataDir = join(root, 'data')
const pagesDir = join(root, 'pages', 'projects')
// 注册表是 TS 源文件,这里只需要 id 列表,正则抽取即可,不引入编译步骤
const registrySrc = readdirSync(dataDir)
.filter((f) => f.endsWith('.ts'))
.map((f) => readFileSync(join(dataDir, f), 'utf-8'))
.join('\n')
const registered = [...registrySrc.matchAll(/\bid:\s*'([^']+)'/g)].map((m) => m[1])
if (registered.length === 0) {
console.error('[check] 注册表里没解析到任何项目,检查 data/projects.ts 的格式')
process.exit(1)
}
const onDisk = existsSync(pagesDir)
? readdirSync(pagesDir, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name)
: []
const missingPages = registered.filter((id) => !onDisk.includes(id))
const missingEntries = onDisk.filter((id) => !registered.includes(id))
if (missingPages.length || missingEntries.length) {
if (missingPages.length) {
console.error(`[check] 注册表里有项目但缺详情页:${missingPages.join(', ')}`)
}
if (missingEntries.length) {
console.error(`[check] 有详情页但没登记进注册表:${missingEntries.join(', ')}`)
}
process.exit(1)
}
console.log(`[check] OK:注册表 ${registered.length} 个项目与详情页一一对应(${registered.join(', ')})`)