chore: 并入 iotlcm-docs 历史到 docs/(原提交 947e22e)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:01 +08:00
231 changed files with 11084 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
node_modules
.vitepress/cache
.vitepress/dist
.vitepress/.temp
dist
*.zip
*.tar
*.war
*.log
.DS_Store
.playwright-mcp/
.kilo/
.git/
.gitignore
Dockerfile
README.md
npm-debug.log*
+64
View File
@@ -0,0 +1,64 @@
name: mirror-to-gitee
# 把本仓同步到 Gitee 企业仓 snowgoat/<同名仓库>。
# 触发:main 分支推送、v* tag 推送、手动;另有每日兜底重试。
on:
push:
branches:
- main
tags:
- 'v*'
schedule:
# Gitea Actions 的 cron 用 UTC:18:30 UTC = 次日 02:30 北京时间
- cron: '30 18 * * *'
workflow_dispatch:
concurrency:
group: mirror-${{ github.ref }}
cancel-in-progress: true
jobs:
mirror:
runs-on: host
steps:
- name: 同步到 Gitee
env:
SRC_TOKEN: ${{ secrets.SRC_GIT_TOKEN }}
DST_TOKEN: ${{ secrets.GITEE_TOKEN }}
DST_OWNER: ${{ secrets.GITEE_OWNER }}
DST_USER: ${{ secrets.GITEE_USER }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
set -u
if [ -z "${REPO_NAME:-}" ]; then
echo "未取到仓库名,退出"; exit 1
fi
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
echo "源:git.5iot.com/iotlcm-public/$REPO_NAME"
echo "目标:gitee.com/$DST_OWNER/$REPO_NAME"
git clone --mirror "https://jenkins:${SRC_TOKEN}@git.5iot.com/iotlcm-public/${REPO_NAME}.git" "$WORK/repo.git"
cd "$WORK/repo.git"
echo "本地 ref:$(git for-each-ref --format='%(refname:short)' | tr '\n' ' ')"
# Gitee 在推送密集时会返回 429 Pending queue overflowed / 408 Authorization server timeout,
# 属服务端限流,退避重试即可。
attempt=0
while [ "$attempt" -lt 5 ]; do
attempt=$((attempt + 1))
echo "----- 推送尝试 $attempt/5"
if git push --mirror "https://${DST_USER}:${DST_TOKEN}@gitee.com/${DST_OWNER}/${REPO_NAME}.git" 2>&1; then
echo "✅ 已同步到 Gitee"
exit 0
fi
wait_s=$((attempt * 30))
echo "推送失败,${wait_s}s 后重试"
sleep "$wait_s"
done
echo "❌ 连续 5 次推送失败,请检查 Gitee 状态或令牌"
exit 1
+80
View File
@@ -0,0 +1,80 @@
name: trigger-jenkins-oss
# 打 tag 时触发 Jenkins 构建开源镜像并推送到镜像仓库。
# 仅在 tag 推送时触发;main 分支的日常提交不触发。
on:
push:
tags:
- 'v*'
workflow_dispatch:
# 同一 ref 连续触发时取消旧任务,只跑最新的
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
trigger:
runs-on: host
steps:
- name: 触发 Jenkins 镜像构建
env:
JU: ${{ secrets.JENKINS_USER }}
JT: ${{ secrets.JENKINS_API_TOKEN }}
JURL: ${{ secrets.JENKINS_URL }}
JOB_PATH: ${{ secrets.JENKINS_JOB_PATH }}
REPO_NAME: ${{ github.event.repository.name }}
TAG_NAME: ${{ github.ref_name }}
run: |
set -eu
# 只有 tag 推送才构建;手动触发时按 tag 取,没有就退出
case "${GITHUB_REF:-}" in
refs/tags/v*) ;;
*)
if [ "${GITHUB_EVENT_NAME:-}" != "workflow_dispatch" ]; then
echo "非 tag 推送,跳过:${GITHUB_REF:-unknown}"
exit 0
fi
;;
esac
if [ -z "${TAG_NAME:-}" ]; then
echo "未取到 tag 名,退出"
exit 1
fi
# job 在 Jenkins 文件夹下的路径(组织密钥 JENKINS_JOB_PATH),
# 例如 job/iotlcm-opensrc/job/iotlcm-oss-build。
# 用密钥而非硬编码,Jenkins 里移动 job 时不用改 11 个仓。
JP="${JOB_PATH:-job/iotlcm-oss-build}"
echo "仓库:$REPO_NAME 版本:$TAG_NAME job:$JP"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
# 取 CSRF crumb
curl -s -c "$WORK/cj" -u "$JU:$JT" "$JURL/crumbIssuer/api/json" -o "$WORK/crumb.json"
CR=$(python3 -c "import json;print(json.load(open('$WORK/crumb.json'))['crumb'])")
# 触发参数化任务:SERVICES=仓库名,VERSION=tag
code=$(curl -s -o "$WORK/out" -w '%{http_code}' -X POST -b "$WORK/cj" -u "$JU:$JT" \
-H "Jenkins-Crumb: $CR" \
--data-urlencode "SERVICES=$REPO_NAME" \
--data-urlencode "VERSION=$TAG_NAME" \
--data-urlencode "FORCE=true" \
"$JURL/$JP/buildWithParameters")
echo "Jenkins 触发 HTTP $code"
if [ "$code" != "201" ] && [ "$code" != "200" ] && [ "$code" != "302" ] && [ "$code" != "303" ]; then
echo "触发失败:"
cat "$WORK/out"
exit 1
fi
LINK="$JURL/$JP/"
echo "=========================================="
echo "Jenkins 构建:$LINK"
echo "=========================================="
echo "::notice title=Jenkins 镜像构建::[$LINK]($LINK)"
+15
View File
@@ -0,0 +1,15 @@
node_modules
.vitepress/cache
.vitepress/dist
.vitepress/.temp
dist
*.zip
*.tar
*.war
*.log
.DS_Store
.playwright-mcp/
.kilo/
.playwright-cli/
.apitest-output/
+1
View File
@@ -0,0 +1 @@
registry=https://registry.npmmirror.com/
+164
View File
@@ -0,0 +1,164 @@
import { defineConfig } from 'vitepress'
const webManualItems = [
{ text: '快速开始', link: '/manual/web/' },
{ text: '首页与通用操作', link: '/manual/web/home' },
{ text: '卡信息', link: '/manual/web/card-info' },
{ text: '智能诊断', link: '/manual/web/intelligent-diagnosis' },
{ text: '业务受理与达量断网', link: '/manual/web/business-operations' },
{ text: '套餐配置与定价', link: '/manual/web/packages-and-pricing' },
{ text: '预警管理', link: '/manual/web/warnings' },
{ text: '入库、库存与出库', link: '/manual/web/inventory' },
{ text: '通道与接入管理', link: '/manual/web/channels' },
{ text: '企业、用户与角色', link: '/manual/web/enterprise-management' },
{ text: '工单管理', link: '/manual/web/work-orders' },
{ text: 'API 凭证与执行日志', link: '/manual/web/api-and-logs' },
{ text: '常见问题', link: '/manual/web/faq' },
]
const miniProgramManualItems = [
{ text: '快速开始', link: '/manual/mini-program/' },
{ text: '首页公开查卡', link: '/manual/mini-program/home' },
{ text: '公开卡片详情', link: '/manual/mini-program/devices' },
{ text: '卡片排查', link: '/manual/mini-program/diagnosis' },
{ text: '登录后办理概览', link: '/manual/mini-program/business' },
{ text: '客户管理与卡片划拨', link: '/manual/mini-program/customers-and-allocation' },
{ text: '工单服务', link: '/manual/mini-program/work-orders' },
{ text: '批次执行日志', link: '/manual/mini-program/execution-logs' },
{ text: '消息与个人中心', link: '/manual/mini-program/messages-and-profile' },
{ text: '常见问题', link: '/manual/mini-program/faq' },
]
function manualSidebar(_active: 'web' | 'mini' | 'both' = 'both') {
return [
{
text: 'Web端',
collapsed: false,
items: webManualItems,
},
{
text: '小程序端',
collapsed: false,
items: miniProgramManualItems,
},
]
}
export default defineConfig({
srcDir: './docs',
base: '/docs/',
title: 'iotLCM',
description: 'iotLCM documentation center',
lang: 'zh-CN',
cleanUrls: true,
lastUpdated: false,
head: [
['link', { rel: 'icon', type: 'image/png', href: '/docs/favicon.png' }]
],
vite: {
plugins: [
{
name: 'serve-root-favicon-in-dev',
configureServer(server) {
server.middlewares.use('/favicon.ico', (_request, response) => {
response.writeHead(302, { Location: '/docs/favicon.png' })
response.end()
})
}
}
]
},
themeConfig: {
logo: undefined,
nav: [
{ text: '文档首页', link: '/' },
{ text: '使用手册', link: '/manual/' },
{ text: 'OpenAPI', link: '/openapi/' },
{ text: '共享 Token 接口', link: '/openapi/operator/shared-token/' },
{ text: '部署手册', link: '/deploy/' }
],
sidebar: {
'/deploy/': [
{
text: '部署手册',
collapsed: false,
items: [
{ text: '部署概览', link: '/deploy/' },
{ text: '环境准备', link: '/deploy/prerequisites' },
{ text: '配置说明', link: '/deploy/configuration' },
{ text: '启动与验证', link: '/deploy/install' },
]
},
{
text: '其他部署方式',
collapsed: false,
items: [
{ text: '方式对比与选型', link: '/deploy/alternatives' },
{ text: '二进制部署', link: '/deploy/binary' },
{ text: '自建镜像', link: '/deploy/build-image' },
{ text: '分离部署', link: '/deploy/split' },
{ text: '离线部署', link: '/deploy/offline' },
]
},
{
text: '运维',
collapsed: false,
items: [
{ text: '可选服务', link: '/deploy/optional' },
{ text: '升级与备份', link: '/deploy/operations' },
{ text: '故障排查', link: '/deploy/troubleshooting' },
]
},
],
'/manual/': manualSidebar(),
'/manual/web/': manualSidebar('web'),
'/manual/mini-program/': manualSidebar('mini'),
'/openapi/operator/': [
{
text: '共享 Token 接口',
collapsed: false,
items: [
{ text: '概览', link: '/openapi/operator/' },
{ text: '共享 Token 接口', link: '/openapi/operator/shared-token/' },
{ text: '白名单转发', link: '/openapi/operator/mobile-v5/' },
]
},
],
'/openapi/': [
{
text: 'V3 接口',
collapsed: false,
items: [
{ text: '概览与鉴权', link: '/openapi/v3/' },
{ text: '获取调用凭证', link: '/openapi/v3/token' },
{ text: '查询类', link: '/openapi/v3/query' },
{ text: '业务类', link: '/openapi/v3/common' },
]
},
{
text: '旧版兼容接口',
collapsed: false,
items: [
{ text: '概览与鉴权', link: '/openapi/legacy/' },
{ text: '查询类', link: '/openapi/legacy/query' },
]
},
]
},
outline: {
level: [2, 3],
label: '本页目录'
},
docFooter: {
prev: '上一页',
next: '下一页'
},
search: {
provider: 'local'
},
footer: {
message: 'iotLCM 文档中心',
copyright: ''
}
}
})
+198
View File
@@ -0,0 +1,198 @@
/* 截图点击放大(medium-zoom)—— 全站生效,范围由 .vitepress/theme/index.ts 的 ZOOM_SELECTOR 定义 */
/* 叠加层必须盖住导航和侧边栏:默认主题 --vp-z-index-nav 为 30、
--vp-z-index-sidebar 为 60,所以取 100。 */
.medium-zoom-overlay {
z-index: 100;
}
.medium-zoom-image--opened {
z-index: 101;
}
/* JS 尚未挂载 medium-zoom 时也提前给出「可点击」的鼠标提示 */
.vp-doc img:not(a img):not(.no-zoom) {
cursor: zoom-in;
}
/* 单张图片保持原始尺寸,并在正文区域水平居中。 */
.vp-doc img {
display: block;
margin: 24px auto;
}
/* 单行只有一张图片时缩放到内容区域的 40%。 */
.vp-doc p > img:only-child,
.vp-doc li > img:only-child {
width: 40%;
max-width: 40%;
height: auto;
}
/* Web 端手册截图恢复为内容区域全宽展示。 */
.vp-doc img[src*="/images/manual/"]:not([src*="mini-program"]) {
width: 100%;
max-width: 100%;
height: auto;
}
/* 同一行包含两张图片时,整行缩放到内容区域的 40%。 */
.vp-doc p:has(> img:nth-child(2):last-child) {
display: flex;
width: 80%;
max-width: 80%;
height: auto;
line-height: 0;
margin: 32px auto;
align-items: center;
justify-content: center;
gap: 8px;
}
.vp-doc p:has(> img:nth-child(2):last-child) > img {
width: calc(50% - 4px);
max-width: calc(50% - 4px);
height: auto;
margin: 0;
}
/* Markdown 内嵌的双图网格统一缩放到内容区域的 80%。 */
.vp-doc div[style*="repeat(2"] {
width: 80% !important;
max-width: 80% !important;
margin: 32px auto !important;
}
.vp-doc div[style*="repeat(2"] img {
width: 100% !important;
max-width: 100% !important;
height: auto;
margin: 0 !important;
}
.vp-doc div[style*="repeat(3"] img {
margin: 0 !important;
}
.vp-doc div[style*="repeat(2"] figcaption,
.vp-doc div[style*="repeat(3"] figcaption {
margin-top: 6px;
color: var(--vp-c-brand-1);
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.vp-doc[class*="_docs_openapi_"] table {
display: table;
width: 100%;
max-width: 100%;
table-layout: auto;
}
.openapi-page .custom-block.warning,
.openapi-page .custom-block.danger {
background-color: var(--vp-code-block-bg);
}
.openapi-page .custom-block.warning code,
.openapi-page .custom-block.danger code {
background-color: var(--vp-code-bg);
}
@media (max-width: 767px) {
.vp-doc[class*="_docs_openapi_"] table {
display: block;
overflow-x: auto;
}
}
.manual-entry-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
margin: 28px 0 36px;
}
.manual-entry-card {
display: flex;
min-height: 190px;
padding: 24px;
flex-direction: column;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-soft);
color: inherit;
text-decoration: none;
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
.manual-entry-card:hover {
border-color: var(--vp-c-brand-1);
box-shadow: 0 8px 24px rgb(60 90 180 / 12%);
transform: translateY(-2px);
}
.manual-entry-title {
color: var(--vp-c-brand-1);
font-size: 22px;
font-weight: 700;
}
.manual-entry-desc {
margin-top: 12px;
color: var(--vp-c-text-2);
font-size: 14px;
line-height: 1.7;
}
.manual-entry-link {
margin-top: auto;
padding-top: 18px;
color: var(--vp-c-brand-1);
font-size: 14px;
font-weight: 600;
}
.mini-program-entry {
display: flex;
width: min(100%, 520px);
margin: 28px auto 12px;
flex-direction: column;
align-items: center;
text-align: center;
}
.vp-doc .mini-program-entry + h2 {
margin-top: 12px;
}
.mini-program-entry__qr {
margin: 0;
}
.mini-program-entry__qr img {
width: 196px;
max-width: 58vw;
margin: 0 auto;
}
.mini-program-entry__qr figcaption {
margin-top: 12px;
color: var(--vp-c-brand-1);
font-size: 15px;
font-weight: 600;
}
.mini-program-entry__hint {
margin: 14px 0 0;
color: var(--vp-c-text-2);
font-size: 14px;
line-height: 1.7;
}
@media (max-width: 640px) {
.manual-entry-grid {
grid-template-columns: 1fr;
}
}
+78
View File
@@ -0,0 +1,78 @@
import DefaultTheme from 'vitepress/theme'
import mediumZoom from 'medium-zoom'
import { onContentUpdated } from 'vitepress'
import { onMounted } from 'vue'
import type { Theme } from 'vitepress'
import './custom.css'
/**
* 参与「点击放大」的图片范围。
*
* `.vp-doc` 是 VitePress 默认主题给正文容器加的类名,使用手册 9 页与接口文档
* 全部页面共用这一套布局,所以在这里定义一次即对全站生效。
*
* 排除项:
* a img 图片本身是链接(如带外链的截图),点击应跳转而不是放大
* .no-zoom 需要单独关闭放大的图片,示例:
* `<img src="/images/manual/xx.png" class="no-zoom" alt="xx">`
*/
const ZOOM_SELECTOR = '.vp-doc img:not(a img):not(.no-zoom)'
/**
* 全站只保留一个 medium-zoom 实例,但【必须惰性创建】。
*
* 两个坑叠在一起,才得到这个写法:
*
* 1. 不能在模块顶层创建实例。medium-zoom 1.1.0 的工厂函数第一件事就是读
* `window.Promise`(dist/medium-zoom.min.js 里即 `var Promise = window.Promise || ...`),
* 而主题模块在 `vitepress build` 时会被 Node 端 SSR 打包并执行 —— 没有 window,
* 直接 `ReferenceError: window is not defined` 构建失败。
* 所以实例只能在浏览器生命周期回调里创建。
*
* 2. 也不能每次路由切换都 `mediumZoom(...)` 新建。每个实例都会在 document 上注册
* click / keyup / scroll、在 window 上注册 resize 监听器,且没有卸载入口
* (见 dist/medium-zoom.esm.js:576-581)。翻 N 个页面就会留下 N 组永不释放的
* 全局监听器。正确做法是复用实例,路由切换时只做 attach —— attach 内部会跳过
* 已挂载的图片(dist/medium-zoom.esm.js:199-204),因此重复调用是幂等的。
*/
let zoom: ReturnType<typeof mediumZoom> | null = null
const attachZoom = () => {
if (typeof window === 'undefined') return
if (!zoom) {
zoom = mediumZoom({
background: 'var(--vp-c-bg)',
margin: 24,
})
}
zoom.attach(ZOOM_SELECTOR)
}
const closeZoomOnClick = (event: MouseEvent) => {
const target = event.target as Element | null
if (!zoom || !target) return
if (target.closest('.medium-zoom-image--opened, .medium-zoom-overlay')) {
void zoom.close()
}
}
const theme: Theme = {
extends: DefaultTheme,
setup() {
// onContentUpdated 由 VitePress 在「新页面内容挂载完成后」触发,覆盖首次进入
// 与 SPA 客户端路由切换。它挂在路由组件的 onVnodeMounted 上
// (见 vitepress/dist/client/app/components/Content.js:13-20),
// 比 watch(route.path) + nextTick 更可靠 —— 后者可能在异步页面组件挂载前就执行,
// 导致某些页面图片没挂上监听、点击放大静默失效。
onContentUpdated(attachZoom)
// 兜底:主题 setup 时内容已就绪(不经过 onVnodeMounted 的极端情况)也能挂上
onMounted(attachZoom)
if (typeof document !== 'undefined') {
document.addEventListener('click', closeZoomOnClick, true)
}
},
}
export default theme
+17
View File
@@ -0,0 +1,17 @@
FROM node:22-alpine AS builder
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 npm run docs:build
FROM nginx:1.27-alpine
COPY --from=builder /app/.vitepress/dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+89
View File
@@ -0,0 +1,89 @@
# iotlcm-docs
iotLCM 物联网卡全生命周期管理平台的**使用手册与 OpenAPI 合同**。
- **使用手册**:Web 管理端(企业客户视角)与小程序端的操作说明,含截图
- **OpenAPI 合同**:V3 接口、旧版兼容接口、运营商能力代理(白名单转发 / 共享 Token)的鉴权方式、请求参数与响应结构
## 技术栈
VitePress 1.6 + Vue 3,产物为纯静态站点。
## 目录结构
```text
iotlcm-docs/
├── docs/ # 文档源(VitePress srcDir)
│ ├── index.md # 文档首页
│ ├── manual/ # 使用手册
│ │ ├── web/ # Web 管理端
│ │ └── mini-program/ # 小程序端
│ ├── openapi/ # OpenAPI 合同
│ │ ├── v3/ # V3 接口
│ │ ├── legacy/ # 旧版兼容接口
│ │ └── operator/ # 运营商能力代理
│ └── public/ # 静态资源(favicon、截图)
├── .vitepress/ # VitePress 配置与主题定制
├── docker/ # nginx 配置
├── Dockerfile
└── docker-compose.yml
```
## 本地开发
```bash
npm ci
npm run dev # http://127.0.0.1:5173/docs/
```
> 入口地址带 `/docs/`:配置里 `base: '/docs/'`,直接访问根路径会 404。
构建与预览:
```bash
npm run build # 产物在 .vitepress/dist
npm run preview
```
站点部署在子路径 `/docs/`(见 `.vitepress/config.mts` 的 `base`)。本地开发时根路径会自动重定向 favicon。
## 部署
**完整部署手册见文档站:[部署手册](https://git.5iot.com/iotlcm-public/iotlcm-docs) → `docs/deploy/`**,包含推荐环境、编译方法、nginx / 对象存储 / 离线环境部署方式与常见问题。
最短路径(容器,推荐):
```bash
docker build -t iotlcm-docs:$(git rev-parse --short HEAD) .
docker run -d --name iotlcm-docs -p 8019:80 iotlcm-docs:latest
# 访问 http://127.0.0.1:8019/docs/
```
多阶段构建:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物。
> ⚠️ **路径前缀**:`base: '/docs/'` 使资源引用为绝对路径 `/docs/assets/*`,而 dist 是平铺的。`docker/nginx.conf` 已用 `alias` 剥掉前缀,**不要**改成从根路径直接服务产物,否则白屏。
`docker-compose.yml` 里的镜像名需替换成你自己的镜像仓库地址;对外署时请用不可变标签(commit sha)而不是 `latest`。
## 文档里的占位符
文档中的服务地址、凭据等均为占位示例:
| 占位 | 含义 |
| --- | --- |
| `iotlcm.example.com` | Web 管理端地址 |
| `api.iotlcm.example.com` | OpenAPI 服务地址 |
| `YOUR_APP_ID` / `YOUR_APP_SECRET` | 平台分配的应用凭据 |
| `YOUR_ACCESS_TOKEN` / `YOUR_TRANSID` | 调用方凭据与流水号 |
| `141****5152` | 示例手机号 |
替换成你自己的环境即可。
## 相关项目
- [iotlcm-openapi](https://git.5iot.com/iotlcm-public/iotlcm-openapi) —— OpenAPI 服务实现
- [iotlcm-admin-svr](https://git.5iot.com/iotlcm-public/iotlcm-admin-svr) —— Web 管理端后端
## License
[Apache License 2.0](./LICENSE)
+12
View File
@@ -0,0 +1,12 @@
services:
docs:
image: iotlcm-docs:latest
container_name: iotlcm-docs
restart: unless-stopped
networks:
- iotlcm-network
networks:
iotlcm-network:
external: true
name: iotlcm-network
+25
View File
@@ -0,0 +1,25 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# VitePress 配置 base: '/docs/',构建产物里的资源引用是绝对路径 /docs/assets/xxx,
# 而 dist 目录是平铺的(index.html、assets/ 直接在根)。
# 因此必须在 /docs/ 路径下提供服务,用 alias 把前缀剥掉再落到产物目录,
# 否则页面能返回 200 但 JS/CSS 全部 404 → 白屏。
location /docs/ {
alias /usr/share/nginx/html/;
try_files $uri $uri.html $uri/ $uri/index.html =404;
}
# 根路径跳到文档入口,避免访问 / 时白屏
location = / {
return 302 /docs/;
}
location / {
try_files $uri $uri.html $uri/ =404;
}
}
+64
View File
@@ -0,0 +1,64 @@
---
title: 其他部署方式
description: 二进制部署、自建镜像、分离部署与离线部署
outline: deep
---
# 其他部署方式
[启动与验证](install) 描述的是 **Docker Compose 单机部署**,也是推荐方式。本组文档覆盖其他场景。
## 方式对比
| 方式 | 适用场景 | 复杂度 | 参考 |
| --- | --- | --- | --- |
| **单机 Compose**(推荐) | 试用、中小规模生产 | 低 | [启动与验证](install) |
| **二进制部署** | 不能装 Docker,或需纳入 systemd 体系 | 中 | [二进制部署](binary) |
| **自建镜像** | 需改代码、加依赖、适配私有基础镜像 | 中 | [自建镜像](build-image) |
| **分离部署** | 数据库走云托管,或中间件已有独立集群 | 中 | [分离部署](split) |
| **离线部署** | 服务器无法访问镜像仓库 | 中 | [离线部署](offline) |
## 服务依赖关系
选择部署方式前需了解各服务依赖哪些组件:
| 服务 | 依赖 |
| --- | --- |
| admin-svr | MySQL、Redis、RabbitMQ |
| openapi | MySQL、Redis、RabbitMQ |
| worker-svr | MySQL、Redis、RabbitMQ |
| app-server | MySQL、Redis |
| upstream-chan | MySQL、Redis、Consul |
| upstream-proxy | MySQL、Redis、Consul |
| admin-ui | admin-svr(反向代理) |
| nginx | admin-ui、openapi、app-web、docs |
| sms | 无 |
| docs | 无 |
| onelink-mock | 无 |
## 通用前提
无论哪种方式,这三项不变:
1. **配置文件必须能被服务读到**——容器部署时挂载到 `/app/config`,二进制部署时放在工作目录的 `config/`
2. **`JWT_SIGNING_KEY` 在 admin-svr、openapi、app-server 三处必须一致**
3. **数据库需先完成初始化**——`schema/` 脚本按顺序执行一次
## 服务间通信方式
配置文件里通过**服务名**互访,例如:
```yaml
database:
default:
link: "mysql:u_iotlcm:...@tcp(mysql:3306)/db_iotlcm"
upstream:
proxy:
address: "upstream-proxy:8510"
```
这些名称在单机 Compose 中由 Docker 网络解析。**切换到二进制部署或分离部署时必须改为实际地址**(`127.0.0.1` 或内网 IP),这是最容易遗漏的一步。
::: danger 最常见的部署失败原因
只改了数据库地址,忘了改 `upstream-proxy:8510`、`upstream-chan:8110` 这类服务名。症状是服务能启动、数据库也通,但批次执行、智能诊断全部超时。
:::
+321
View File
@@ -0,0 +1,321 @@
---
title: 二进制部署
description: 直接编译 Go 程序运行,不依赖 Docker
outline: deep
---
# 二进制部署
不使用容器,把各服务编译为可执行文件,用 systemd 托管。
## 适用场景
- 服务器不允许安装 Docker
- 需要把服务纳入既有的 systemd / 进程管理体系统一运维
- 需要针对特定 CPU 架构或内核参数做优化
## 构建环境
| 项 | 要求 |
| --- | --- |
| Go | 按各服务 `go.mod` 要求,最低 1.23(见下表) |
| GCC | admin-svr 需要(`CGO_ENABLED=1`,内存库用 SQLite) |
| 系统 | 建议与目标服务器同发行版,避免 libc 差异 |
```bash
# Debian / Ubuntu
apt-get install -y build-essential
# CentOS / Rocky
yum groupinstall -y "Development Tools"
```
::: tip 其余服务可静态编译
除 admin-svr 外,其余服务 `CGO_ENABLED=0`,产物为静态二进制,可直接拷到任意同架构 Linux 运行。
:::
## 各服务构建参数
| 服务 | 仓库 | Go 版本 | CGO | 构建目录 | 产物名 | 监听端口 |
| --- | --- | --- | --- | --- | --- | --- |
| admin-svr | `iotlcm-admin-svr` | 1.23 | **需开启** | `server/` | `iotlcmadmin_svc` | 8008 |
| openapi | `iotlcm-openapi` | 1.23 | 关闭 | 根目录 | `iotlcm-openapi` | 8000 |
| worker-svr | `iotlcm-worker-svr` | 1.25 | 关闭 | 根目录 | `iotlcm-worker-svr` | 无(队列消费) |
| upstream-chan | `iotlcm-upstream-chan` | 1.25 | 关闭 | 根目录 | `iotlcmupstreamchan_svc` | 8110 / 8310 |
| upstream-proxy | `iotlcm-upstream-proxy` | 1.25 | 关闭 | 根目录 | `iotlcmupstreamproxy_svc` | 8510 |
| app-server | `iotlcm-app` | 1.23 | 关闭 | `server/` | `iotlcm-app-server` | 8010 |
| sms | `iotlcm-sms` | 1.25 | 关闭 | 根目录 | `iotlcm-sms` | 11190 / 11200 |
| bot | `iotlcm-bot` | 1.23 | 关闭 | `cmd/iotlcm-bot` | `iotlcm-bot` | 无 |
::: warning 仓库里的 Dockerfile 不能直接照搬
部分仓库的 Dockerfile 基础镜像指向私有仓库(见[自建镜像](build-image)),二进制构建不受影响,但 `FROM` 行需要替换。
:::
## 构建
### admin-svr(需要 CGO)
```bash
git clone https://git.5iot.com/iotlcm-public/iotlcm-admin-svr.git
cd iotlcm-admin-svr/server
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
go build -trimpath \
-ldflags "-s -w \
-X main.BuildVersion=$(git describe --tags --always) \
-X main.CommitID=$(git rev-parse --short HEAD) \
-X main.BuildTime=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
-o iotlcmadmin_svc .
```
### 其余服务(静态编译)
```bash
git clone https://git.5iot.com/iotlcm-public/iotlcm-openapi.git
cd iotlcm-openapi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath \
-ldflags "-s -w \
-X main.BuildVersion=$(git describe --tags --always) \
-X main.CommitID=$(git rev-parse --short HEAD) \
-X main.BuildTime=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
-o iotlcm-openapi .
```
替换仓库名与产物名即可用于其他服务,`bot` 需要指定子目录:
```bash
cd iotlcm-bot
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -o iotlcm-bot ./cmd/iotlcm-bot
```
### 交叉编译说明
| 服务 | 能否交叉编译 |
| --- | --- |
| admin-svr(CGO) | ❌ 需在目标架构上编译,或用对应架构的交叉工具链 |
| 其余(静态) | ✅ 可直接 `GOOS=linux GOARCH=arm64` |
查看服务器架构:
```bash
uname -m # x86_64 → amd64;aarch64 → arm64
```
## 部署布局
```text
/opt/iotlcm/
├── admin-svr/
│ ├── iotlcmadmin_svc
│ ├── config/config.yaml ← 从 config/admin-svr 复制
│ ├── logs/
│ ├── upload/
│ └── cache/
├── openapi/
│ ├── iotlcm-openapi
│ ├── config/config.yaml
│ └── logs/
├── worker-svr/
│ ├── iotlcm-worker-svr
│ ├── config/config.yaml
│ └── logs/
├── upstream-chan/
├── upstream-proxy/
├── app-server/
│ ├── iotlcm-app-server
│ ├── config/config.yaml
│ └── resource/
│ ├── log/server/
│ └── upload/
├── sms/
└── bot/
```
**配置文件放 `config/config.yaml`**(相对工作目录),这是 GoFrame 的默认搜索路径。启动时工作目录必须在服务目录内。
### 目录准备
```bash
sudo mkdir -p /opt/iotlcm/{admin-svr,openapi,worker-svr,upstream-chan,upstream-proxy,app-server,sms,bot}
sudo useradd -r -s /sbin/nologin iotlcm
sudo chown -R iotlcm:iotlcm /opt/iotlcm
```
## 修改配置中的服务地址
容器部署时配置里写的是服务名(`mysql:3306`、`upstream-proxy:8510`),二进制部署必须改为实际地址:
```yaml
# admin-svr / openapi / worker-svr / app-server
database:
default:
link: "mysql:u_iotlcm:<口令>@tcp(127.0.0.1:3306)/db_iotlcm?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
redis:
default:
address: "127.0.0.1:6379"
rabbitmq:
dsn: "amqp://<用户>:<口令>@127.0.0.1:5672/"
# upstream-chan / upstream-proxy
registry:
address: "127.0.0.1:8500"
# 上游服务地址
upstream:
proxy:
address: "127.0.0.1:8510"
gateway:
address: "127.0.0.1:8110"
```
::: danger logger.path 也要改
配置里的 `logger.path` 在容器版本中是 `/app/logs`,二进制部署要改为实际路径,例如 `/opt/iotlcm/admin-svr/logs`,否则会因目录不存在启动失败。
:::
## systemd 托管
以为 `admin-svr` 为例,保存为 `/etc/systemd/system/iotlcm-admin-svr.service`:
```ini
[Unit]
Description=IotLCM Admin Service
After=network-online.target mysql.service redis.service
Wants=network-online.target
[Service]
Type=simple
User=iotlcm
Group=iotlcm
WorkingDirectory=/opt/iotlcm/admin-svr
ExecStart=/opt/iotlcm/admin-svr/iotlcmadmin_svc
Restart=always
RestartSec=5s
LimitNOFILE=65535
# 环境变量(JWT 密钥等敏感值建议放独立文件)
EnvironmentFile=-/etc/iotlcm/admin-svr.env
# 日志交给 journald,也可用配置里的文件日志
StandardOutput=journal
StandardError=journal
# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/opt/iotlcm/admin-svr
[Install]
WantedBy=multi-user.target
```
`/etc/iotlcm/admin-svr.env`:
```bash
JWT_SIGNING_KEY=<与其它服务一致的密钥>
```
启用:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now iotlcm-admin-svr
sudo systemctl status iotlcm-admin-svr
```
### 各服务的差异点
| 服务 | 特殊处理 |
| --- | --- |
| admin-svr | 需 `ReadWritePaths` 包含 upload 与 logs 目录 |
| worker-svr | 无监听端口,`After` 需包含 rabbitmq |
| upstream-chan / proxy | `After` 需包含 consul |
| app-server | 需 `resource/log/server` 与 `resource/upload` 可写 |
| bot | `ExecStart` 需带参数:`/opt/iotlcm/bot/iotlcm-bot watch --config /opt/iotlcm/bot/config/config.yaml` |
### 启动顺序
```text
MySQL / Redis / RabbitMQ / Consul
↓
admin-svr、openapi、worker-svr、upstream-chan、upstream-proxy、app-server、sms
↓
admin-ui、app-web、docs(静态文件,由 nginx 托管)
```
用 systemd 依赖表达:
```ini
[Unit]
After=iotlcm-admin-svr.service
Requires=iotlcm-admin-svr.service
```
## 静态前端
`admin-ui`、`app-web`、`docs` 是静态文件,二进制部署时不需要跑容器,把构建产物交给 nginx:
```bash
# admin-ui:需 Node 构建
cd iotlcm-admin-ui/vue
npm ci && npm run build
sudo cp -r apps/web-antd/dist /var/www/iotlcm/admin-ui
```
nginx 配置参考部署仓的 `config/admin-ui/default.conf` 与 `config/nginx/iotlcm.conf`,把 `proxy_pass http://admin-svr:8008` 改为 `http://127.0.0.1:8008`。
## 验证
```bash
# 进程状态
systemctl status iotlcm-admin-svr
journalctl -u iotlcm-admin-svr -n 50 --no-pager
# 端口监听
ss -lntp | grep -E '8008|8000|8510|8110'
# 接口
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8008/api.json
```
## 与容器部署的差异
| 项 | 容器部署 | 二进制部署 |
| --- | --- | --- |
| 配置路径 | `/app/config`(挂载) | `<服务目录>/config` |
| 日志路径 | `/app/logs` | 需在配置里改成实际路径 |
| 服务互访 | 服务名(Docker DNS) | `127.0.0.1` 或内网 IP |
| 进程管理 | Docker restart policy | systemd |
| 升级 | 换镜像 + `up -d` | 停服务 → 换二进制 → 起服务 |
| 时区 | 镜像已内置 | 依赖宿主机 tzdata |
## 升级流程
```bash
# 1. 备份配置与程序
sudo tar czf /backup/iotlcm-admin-svr-$(date +%F).tar.gz -C /opt/iotlcm admin-svr
# 2. 停服务
sudo systemctl stop iotlcm-admin-svr
# 3. 替换二进制
sudo cp iotlcmadmin_svc /opt/iotlcm/admin-svr/
sudo chown iotlcm:iotlcm /opt/iotlcm/admin-svr/iotlcmadmin_svc
# 4. 起服务并验证
sudo systemctl start iotlcm-admin-svr
sudo systemctl status iotlcm-admin-svr
```
## 回滚
保留上一版二进制,回滚只需换回:
```bash
sudo systemctl stop iotlcm-admin-svr
sudo cp /opt/iotlcm/admin-svr/iotlcmadmin_svc.bak /opt/iotlcm/admin-svr/iotlcmadmin_svc
sudo systemctl start iotlcm-admin-svr
```
+292
View File
@@ -0,0 +1,292 @@
---
title: 自建镜像
description: 从源码构建各服务的 Docker 镜像
outline: deep
---
# 自建镜像
平台镜像由官方仓库分发。如有定制需求(改代码、加依赖、适配私有基础镜像),可从源码自行构建。
## 构建环境
| 项 | 要求 |
| --- | --- |
| Docker | 20.10 以上 |
| 网络 | 需访问基础镜像仓库与 Go/npm 依赖源 |
| 磁盘 | 单服务构建约 1~2 GB(含构建缓存) |
## 各服务构建文件
| 服务 | 仓库 | Dockerfile | 构建上下文 |
| --- | --- | --- | --- |
| admin-svr | `iotlcm-admin-svr` | `server/build/Dockerfile` | `server/` |
| openapi | `iotlcm-openapi` | `build/Dockerfile` | 仓库根目录 |
| worker-svr | `iotlcm-worker-svr` | `build/Dockerfile` | 仓库根目录 |
| upstream-chan | `iotlcm-upstream-chan` | `build/Dockerfile` | 仓库根目录 |
| upstream-proxy | `iotlcm-upstream-proxy` | `build/Dockerfile` | 仓库根目录 |
| app-server | `iotlcm-app` | `server/Dockerfile` | `server/` |
| app-web | `iotlcm-app` | `app/Dockerfile` | `app/` |
| admin-ui | `iotlcm-admin-ui` | `vue/build/Dockerfile` | `vue/` |
| sms | `iotlcm-sms` | `build/Dockerfile` | 仓库根目录 |
| bot | `iotlcm-bot` | `Dockerfile` | 仓库根目录 |
| docs | `iotlcm-docs` | `Dockerfile` | 仓库根目录 |
| onelink-mock | `iotlcm-onelink-mock` | `Dockerfile` | 仓库根目录 |
## ⚠️ 先处理私有基础镜像
**当前部分仓库的 Dockerfile 仍指向私有镜像仓库**,外部环境无法拉取,直接构建会失败:
| 仓库 | 文件 | 问题 |
| --- | --- | --- |
| `iotlcm-openapi` | `manifest/docker/Dockerfile` | `FROM` 指向私有仓库 |
| `iotlcm-app` | `app/Dockerfile` | 2 处 `FROM` 指向私有仓库 |
| `iotlcm-app` | `server/Dockerfile` | 2 处 `FROM` 指向私有仓库 |
| `iotlcm-admin-ui` | `vue/build/Dockerfile` | 2 处 `FROM` 指向私有仓库 |
| `iotlcm-admin-ui` | `vue/build/DockerfileNoBuild` | `FROM` 指向私有仓库 |
构建前把这些 `FROM` 改为公共镜像:
```dockerfile
# 修改前(私有仓库,外部不可达)
FROM crpi-xxxxxxxx.cn-beijing.personal.cr.aliyuncs.com/xxx/golangalpine:1.25.7-alpine3.23 AS builder
# 修改后(公共镜像)
FROM golang:1.25-alpine AS builder
```
```dockerfile
FROM crpi-xxxxxxxx.cn-beijing.personal.cr.aliyuncs.com/xxx/nginx:1.30.0-alpine
# 改为
FROM nginx:1.30-alpine
```
::: tip 其余仓库的 Dockerfile 已可用
`admin-svr`、`worker-svr`、`upstream-chan`、`upstream-proxy`、`sms`、`bot`、`docs`、`onelink-mock` 的 Dockerfile 基础镜像均为公共镜像,可直接构建。
:::
## 构建命令
### 后端服务
```bash
COMMIT=$(git rev-parse --short HEAD)
VERSION=$(git describe --tags --always)
BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
docker build \
--build-arg BuildVersion=$VERSION \
--build-arg CommitID=$COMMIT \
--build-arg BuildTime=$BUILD_TIME \
-t your-registry/iotlcm/iotlcm:iotlcm-openapi-$VERSION \
.
```
`admin-svr` 与 `app-server` 需指定上下文目录:
```bash
cd iotlcm-admin-svr
docker build \
--build-arg BuildVersion=$VERSION \
--build-arg CommitID=$COMMIT \
--build-arg BuildTime=$BUILD_TIME \
-t your-registry/iotlcm/iotlcm:iotlcm-admin-svr-$VERSION \
-f server/build/Dockerfile server/
```
### 前端服务
```bash
cd iotlcm-admin-ui/vue
docker build -t your-registry/iotlcm/iotlcm:iotlcm-admin-ui-$VERSION -f build/Dockerfile .
```
```bash
cd iotlcm-app/app
docker build -t your-registry/iotlcm/iotlcm:iotlcm-app-web-$VERSION .
```
### 构建参数说明
| 参数 | 用途 |
| --- | --- |
| `BuildVersion` | 写入二进制,可通过版本接口查询 |
| `CommitID` | 提交号,便于追溯 |
| `BuildTime` | 构建时间 |
`openapi`、`worker-svr` 等仓库支持用 `GOPROXY` 构建参数替换代理:
```bash
docker build --build-arg GOPROXY=https://goproxy.cn,direct -t ... .
```
## 镜像命名规则
官方仓库把**所有镜像放在同一个镜像仓库** `iotlcm/iotlcm` 下,靠 tag 区分服务与版本:
```text
<Registry>/iotlcm/iotlcm:<服务名>-<版本>
```
| 服务 | tag | 完整示例 |
| --- | --- | --- |
| admin-svr | `iotlcm-admin-svr-<TAG>` | `.../iotlcm/iotlcm:iotlcm-admin-svr-v1.0.4` |
| admin-ui | `iotlcm-admin-ui-<TAG>` | `.../iotlcm/iotlcm:iotlcm-admin-ui-v1.0.4` |
| openapi | `iotlcm-openapi-<TAG>` | `.../iotlcm/iotlcm:iotlcm-openapi-v1.0.4` |
| app-server | `iotlcm-app-server-<TAG>` | `.../iotlcm/iotlcm:iotlcm-app-server-v1.0.2` |
| app-web | `iotlcm-app-web-<TAG>` | `.../iotlcm/iotlcm:iotlcm-app-web-v1.0.2` |
| upstream-chan | `iotlcm-upstream-chan-<TAG>` | `.../iotlcm/iotlcm:iotlcm-upstream-chan-v1.0.4` |
| upstream-proxy | `iotlcm-upstream-proxy-<TAG>` | `.../iotlcm/iotlcm:iotlcm-upstream-proxy-v1.0.4` |
| worker-svr | `iotlcm-worker-svr-<TAG>` | `.../iotlcm/iotlcm:iotlcm-worker-svr-v1.0.4` |
| sms | `iotlcm-sms-<TAG>` | `.../iotlcm/iotlcm:iotlcm-sms-v1.0.4` |
| bot | `iotlcm-bot-<TAG>` | `.../iotlcm/iotlcm:iotlcm-bot-v1.0.4` |
| docs | `iotlcm-docs-<TAG>` | `.../iotlcm/iotlcm:iotlcm-docs-v1.0.4` |
| onelink-mock | `iotlcm-onelink-mock-<TAG>` | `.../iotlcm/iotlcm:iotlcm-onelink-mock-v1.0.4` |
基础组件(`mysql`、`redis`、`rabbitmq`、`consul`、`nginx`)不需要构建,从上游同步到同一仓库即可,tag 为 `<组件>-<版本>`,见下节。
::: warning tag 必须带上服务名
同一个仓库里 tag 不能重复。只用版本号(如 `v1.0.4`)会让 12 个服务互相覆盖,因此 tag 固定为 `<服务名>-<版本>`。
:::
## 同步基础组件
平台编排不再从 Docker Hub 直连基础组件(公网不稳定,且 `8.0` 这类 tag 会随补丁升级而漂)。部署前先把五个镜像同步到官方仓库:
```bash
REPO=crpi-qflrqrxoohlkkvgc.cn-chengdu.personal.cr.aliyuncs.com
docker login --username=sdqmx $REPO
# 按 docker-compose.yml 中固定的版本拉取并推送
sync_image() {
local src=$1 dst=$2
docker pull "$src"
docker tag "$src" "$REPO/iotlcm/iotlcm:$dst"
docker push "$REPO/iotlcm/iotlcm:$dst"
}
# 参数:<上游镜像> <目标 tag(组件名-版本)>
sync_image mysql:8.0 mysql-8.0
sync_image redis:7.2 redis-7.2
sync_image rabbitmq:4.3.5-management rabbitmq-4.3.5-management
sync_image hashicorp/consul:1.22 consul-1.22
sync_image nginx:1.30 nginx-1.30
```
::: warning 同步时把版本号固定到补丁位
`mysql:8.0` 这类浮动 tag 会随上游发新补丁而变化。建议同步时锁定到具体补丁版(如 `mysql:8.0.43`),tag 写成 `mysql-8.0.43`,并把 `docker-compose.yml` 里对应的 `image` 同步改掉,保证多台机器拉到的镜像完全一致。
:::
同步完成后校验:
```bash
for tag in mysql-8.0 redis-7.2 rabbitmq-4.3.5-management consul-1.22 nginx-1.30; do
docker manifest inspect "$REPO/iotlcm/iotlcm:$tag" >/dev/null 2>&1 \
&& echo "✅ $tag" || echo "❌ $tag"
done
```
## 推送到自己的仓库
自建镜像不需要推送回官方仓库。推到自有仓库后,把 `docker-compose.yml` 里的镜像前缀改指向它:
```bash
REG=harbor.internal.example.com
TAG=1.0.3
docker tag iotlcm-openapi:local $REG/iotlcm/iotlcm:iotlcm-openapi-$TAG
docker login $REG
docker push $REG/iotlcm/iotlcm:iotlcm-openapi-$TAG
```
```yaml
# docker-compose.yml —— 逐个替换 image 前缀
image: 'harbor.internal.example.com/iotlcm/iotlcm:iotlcm-openapi-v1.0.4'
```
::: tip 只覆盖部分服务
只想替换其中一两个服务时,把对应镜像推到自有仓库,其余保留官方地址。此时把 `docker-compose.yml` 里那几个 `image` 单独写成完整地址,其余保持官方仓库前缀即可。
:::
## 批量构建脚本
```bash
#!/bin/bash
set -euo pipefail
REPO=${REPO:?请设置 REPO}
TAG=${TAG:-$(date +%Y%m%d)}
WORKDIR=${WORKDIR:-./repos}
# 服务:仓库:上下文:产物镜像名
SERVICES=(
"admin-svr:iotlcm-admin-svr:server/:iotlcm-admin-svr"
"openapi:iotlcm-openapi:.:iotlcm-openapi"
"worker-svr:iotlcm-worker-svr:.:iotlcm-worker-svr"
"upstream-chan:iotlcm-upstream-chan:.:iotlcm-upstream-chan"
"upstream-proxy:iotlcm-upstream-proxy:.:iotlcm-upstream-proxy"
"app-server:iotlcm-app:server/:iotlcm-app-server"
"sms:iotlcm-sms:.:iotlcm-sms"
"bot:iotlcm-bot:.:iotlcm-bot"
)
mkdir -p "$WORKDIR"
for item in "${SERVICES[@]}"; do
IFS=':' read -r name repo ctx image <<< "$item"
echo "=== 构建 $name ==="
if [ ! -d "$WORKDIR/$repo" ]; then
git clone "https://git.5iot.com/iotlcm-public/$repo.git" "$WORKDIR/$repo"
fi
VERSION=$(git -C "$WORKDIR/$repo" describe --tags --always 2>/dev/null || echo unknown)
COMMIT=$(git -C "$WORKDIR/$repo" rev-parse --short HEAD 2>/dev/null || echo unknown)
find "$WORKDIR/$repo" -name Dockerfile -exec \
sed -i 's#FROM crpi-[^ ]*/\([a-z]*\):\([0-9.]*-alpine[0-9.]*\)#FROM \1:\2#' {} \;
docker build \
--build-arg BuildVersion="$VERSION" \
--build-arg CommitID="$COMMIT" \
--build-arg BuildTime="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
-t "$REPO/iotlcm/$image:$TAG" \
-f "$WORKDIR/$repo/${ctx}Dockerfile" \
"$WORKDIR/$repo/$ctx"
done
echo "全部完成,镜像标签:$REPO/iotlcm/*:$TAG"
```
::: warning 脚本里的 sed 只能应急
上面的 `sed` 是粗略替换,把私有仓库地址映射为公共镜像名。仅适用于地址格式规整的情况,用前先 `git diff` 确认改对了。正式做法应把 Dockerfile 的修正提交回仓库。
:::
## 验证构建结果
```bash
# 镜像大小
docker images | grep iotlcm
# 二进制能否启动
docker run --rm your-registry/iotlcm/iotlcm:iotlcm-openapi-$TAG --help 2>&1 | head
# 检查架构
docker inspect your-registry/iotlcm/iotlcm:iotlcm-openapi-$TAG \
--format '{{.Architecture}}/{{.Os}}'
```
构建后按[启动与验证](install)的清单做一次完整部署验证,不要只看镜像是否构建成功。
## 常见问题
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| `pull access denied` / `unauthorized` | Dockerfile 里是私有仓库地址 | 按上文替换 `FROM` |
| `go mod download` 超时 | 网络无法访问 Go 模块代理 | 加 `--build-arg GOPROXY=https://goproxy.cn,direct` |
| `npm ci` 失败 | 前端仓库 lock 文件与 package.json 不一致 | 改用 `npm install`,并注意提交 lock 变更 |
| 镜像体积过大 | 未使用多阶段构建,或缓存未清理 | 确认用的是仓库自带的 Dockerfile(均为多阶段) |
| 容器启动后立即退出 | 镜像内缺少配置文件 | 容器认为 `config/config.yaml` 必须存在,需挂载或用占位配置 |
| `exec format error` | 构建架构与运行架构不一致 | 加 `--platform linux/amd64` |
+189
View File
@@ -0,0 +1,189 @@
---
title: 配置说明
description: 部署配置的结构、默认值与修改方式
outline: deep
---
# 配置说明
平台配置分两层,**都随仓库分发、都带可用默认值**,无需复制或初始化即可启动:
| 层 | 位置 | 管什么 |
| --- | --- | --- |
| 编排层 | `docker-compose.yml` | 镜像地址、端口、数据库/Redis/MQ 口令、容器编排 |
| 应用层 | `config/<服务>/config.yaml` | 业务参数、连接串、功能开关 |
::: warning 配置里是字面值,不是环境变量
配置文件与编排文件里直接写的是具体值,**不使用 `${VAR}` 占位**。
原因:GoFrame 不会展开配置文件中的 `${VAR}`(环境变量只用于覆盖配置键,不参与文件内文本替换),用占位符会导致连接串变成字面量 `${X}`,服务直接连不上库。
代价是改口令要改多处,见下方「改口令的正确姿势」。
:::
## 编排层:`docker-compose.yml`
### 镜像地址
```yaml
image: 'crpi-.../iotlcm/iotlcm:mysql-8.0'
image: 'crpi-.../iotlcm/iotlcm:iotlcm-admin-svr-v1.0.4'
```
所有镜像(基础组件 + 业务服务)都在**同一个镜像仓库** `<Registry>/iotlcm/iotlcm` 下,靠 tag 区分:
| tag 形式 | 示例 |
| --- | --- |
| 基础组件:`<组件>-<版本>` | `mysql-8.0`、`redis-7.2`、`nginx-1.30` |
| 业务服务:`<服务>-<版本>` | `iotlcm-admin-svr-v1.0.4`、`iotlcm-app-server-v1.0.2` |
改用自有仓库时,全局替换 Registry 前缀即可。
### 默认口令
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `MYSQL_ROOT_PASSWORD` | 见文件 | MySQL root |
| `MYSQL_DATABASE` | `db_iotlcm` | 库名 |
| `RABBITMQ_DEFAULT_PASS` | 见文件 | RabbitMQ |
| redis `--requirepass` | 见文件 | Redis |
### 端口
| 端口 | 服务 | 对外 |
| --- | --- | --- |
| 8008 | admin-svr | 经反向代理 |
| 8009 | admin-ui | **浏览器直接访问这个** |
| 8000 | openapi | 经反向代理 |
| 8010 / 8018 | app-server / app-web | 经反向代理 |
| 8019 | docs | 经反向代理 |
| 8080 | onelink-mock | 仅联调 |
| 3306 / 6379 / 5672 / 8500 | 基础组件 | 仅内网 |
| 8110 / 8310 / 8510 | chan / proxy | 仅内网 |
| 11190 / 11200 | sms | 仅内网 |
改端口直接改 `docker-compose.yml` 中对应 `ports` 的冒号左侧。
## 应用层:`config/`
```
config/
├── admin-svr/config.yaml
├── admin-ui/default.conf
├── app-server/config.yaml
├── bot/config.yaml
├── nginx/iotlcm.conf
├── openapi/config.yaml
├── sms/config.yaml
├── upstream-chan/config.yaml
├── upstream-proxy/config.yaml
└── worker-svr/config.yaml
```
### 各服务要点
#### admin-svr(管理端后端)
| 项 | 默认情况 |
| --- | --- |
| `database.default.link` | 指向 `mysql:3306`,口令与应用账号一致 |
| `database.legacyCard.link` | 空,不使用旧平台库 |
| `jwt.signingKey` | 已填固定密钥,需与 openapi、app-server 一致 |
| `warning.sms.enabled` | `true`,未配短信服务商时建议改 `false` |
| `warning.wechat.enabled` | `false` |
| `register.email.*` | 空,不发邮件 |
| `tsdb.tdengine.dsn` | 空,不启用时序库 |
| `alipay.*` | 空,不启用支付 |
| `upstream.appDiagnosisToken` | 已填,需与 upstream-proxy 一致 |
#### openapi(对外 API)
`jwt.signingKey` 与 admin-svr 一致;`database.default.link` 同库。建议另建只读账号用于生产。
#### worker-svr(异步任务)
| 项 | 说明 |
| --- | --- |
| `batch.inbound.upstreamConcurrency` | 默认 300,机器弱或上游配额小时调低 |
| `batch.inbound.upstreamRateLimit` | 运营商限流,**必须按上游实际配额设置**,调大易被封禁 |
| `tsdb.tdengine.dsn` | 空,不启用时序库 |
#### upstream-chan(运营商对接)
| 项 | 说明 |
| --- | --- |
| `registry.address` | `consul:8500` |
| `dependencies.sim_yd_mock_v5.url` | 指向挡板 `http://onelink-mock:8080/v5/`,接真实运营商时删除本段 |
运营商通道本身(AppKey、AppSecret、接口地址)在平台「通道管理」中录入,存数据库,不在本文件。
#### upstream-proxy(通道缓存转发)
| 项 | 说明 |
| --- | --- |
| `appDiagnosis.internalToken` | 与 admin-svr 的 `upstream.appDiagnosisToken` 一致 |
| `gateway.batchTimeout` | 默认 20m,**worker 侧超时必须大于此值** |
#### app-server(小程序后端)
`jwt.signingKey` 同上;`payment.wechat.enabled` 默认 `false`;`appCardRecognition.enabled` 默认 `false`(拍照识别需自备视觉模型 API)。
#### sms(短信服务)
| 项 | 默认 |
| --- | --- |
| `serviceType` | `1`(阿里云) |
| `accessKeyId` / `accessKeySecret` | 空,需自行填写 |
| `signName` | `示例签名` |
不填则无法发送验证码,其余功能正常。
#### bot(飞书通知)
`feishu.appId` / `appSecret` / `chatId` 均为空。不启动该容器即可。
#### admin-ui / nginx
`admin-ui/default.conf` 把 `/api/` 反代到 `admin-svr:8008`;`nginx/iotlcm.conf` 按域名分流,需把 `your-domain.example.com` 换成实际域名。
## 改口令的正确姿势
改数据库口令时,以下**三处必须同时改**,漏一处就起不来:
| 位置 | 内容 |
| --- | --- |
| `docker-compose.yml` | `MYSQL_ROOT_PASSWORD`、redis `--requirepass`、`RABBITMQ_DEFAULT_PASS` |
| `config/*/config.yaml` | 各服务的 `database.default.link`、`redis.default.pass`、`rabbitmq.dsn` |
| `schema/04-users.sh`、`schema/05-reset-passwords.sh` | 脚本内的 `APP_PASSWORD` / `ADMIN_PASSWORD` / `ROOT_PASSWORD` |
::: tip 一次性全局替换
```bash
OLD='原口令'
NEW='新口令'
# 编排 + 配置
grep -rl "$OLD" docker-compose.yml config/ | xargs sed -i "s/$OLD/$NEW/g"
# 初始化脚本
grep -rl "$OLD" schema/ | xargs sed -i "s/$OLD/$NEW/g"
```
:::
::: danger 口令不能含 @ : / 等字符
连接串格式为 `mysql:用户:口令@tcp(主机:端口)/库`,口令里的 `@`、`:`、`/` 会被解析成分隔符,导致连接失败。用字母数字组合。
:::
## 配置自检
```bash
# 编排语法
docker compose config --quiet
# 看某个服务最终的配置展开
docker compose config | sed -n '/admin-svr:/,/^ [a-z]/p'
```
配置语法错误会导致服务启动后立刻退出,看日志定位:
```bash
docker compose logs --tail=50 admin-svr
```
+97
View File
@@ -0,0 +1,97 @@
---
title: 部署概览
description: IotLCM 平台的部署方式、构成与前置条件
outline: deep
---
# 部署概览
IotLCM 平台通过 Docker Compose 在单机上部署。部署编排与数据库初始化脚本在独立仓库:
```bash
git clone https://git.5iot.com/iotlcm-public/iotlcm-deploy.git
cd iotlcm-deploy
```
```text
iotlcm-deploy/
├── docker-compose.yml # 服务编排(含默认口令与端口)
├── config/ # 各服务配置,10 份,已带可用默认值
└── schema/ # 数据库初始化脚本,13 个,首次启动自动执行
```
## 部署内容
| 类别 | 服务 |
| --- | --- |
| 基础组件 | MySQL、Redis、RabbitMQ、Consul |
| 管理端 | admin-svr(后端)、admin-ui(前端) |
| 对外接口 | openapi |
| 小程序 / H5 | app-server(后端)、app-web(前端) |
| 运营商对接 | upstream-chan(协议适配)、upstream-proxy(缓存转发) |
| 异步任务 | worker-svr(批次执行、全量同步) |
| 配套 | sms(短信)、docs(本文档站) |
| 可选 | onelink-mock(运营商接口挡板)、bot(飞书通知)、nginx(反向代理) |
## 部署流程
```bash
git clone <部署仓地址> iotlcm-deploy
cd iotlcm-deploy
docker compose up -d
```
三步,不需要复制或修改任何文件——配置与编排都随仓库分发且带可用默认值。首次启动自动建库并导入 schema,约 1~2 分钟。
启动后访问 `http://<宿主机IP>:8009`,账号 `admin`,口令见 `docker-compose.yml`。
::: warning 默认口令只用于试用
正式部署前必须修改数据库、Redis、RabbitMQ、admin 账号口令与 JWT 密钥,详见[配置说明](configuration)。
:::
## 文档导航
| 文档 | 内容 |
| --- | --- |
| [环境准备](prerequisites) | 硬件、系统、Docker 版本、镜像仓库、端口规划 |
| [配置说明](configuration) | `docker-compose.yml` 与 `config/` 各项含义 |
| [启动与验证](install) | 启动流程、验证清单、首次登录 |
| [其他部署方式](alternatives) | 二进制、自建镜像、分离、离线 |
| [可选服务](optional) | 接口挡板、飞书通知、反向代理 |
| [升级与备份](operations) | 版本固定、升级回滚、数据备份 |
| [故障排查](troubleshooting) | 常见问题与处理 |
## 关键约束
::: warning JWT 密钥必须三处一致
`admin-svr`、`openapi`、`app-server` 共用同一个 `JWT_SIGNING_KEY`。不一致的表现是能登录管理端,但调用外部 API 或小程序端提示未授权。
更换该值会使已签发的全部登录态与接口令牌立即失效,需在维护窗口操作。
:::
::: tip 镜像标签不要用 latest
生产环境必须把 `TAG` 固定到具体版本号。`latest` 会随仓库推送变化,同一份编排在不同时间拉到的镜像不同,出问题无法定位,也无法明确回滚。
:::
::: danger 修改 schema 脚本不会自动生效
数据库初始化只在 `data/mysql` 为空时执行。改了 `schema/` 下的脚本必须清空数据卷才会重跑,而清空会丢失全部数据。生产环境禁止这样操作。
:::
## 服务端口
默认端口规划:
| 端口 | 服务 | 是否对外 |
| --- | --- | --- |
| 8000 | openapi | 经反向代理 |
| 8008 | admin-svr | 经反向代理 |
| 8009 | admin-ui | 经反向代理 |
| 8010 | app-server | 经反向代理 |
| 8018 | app-web | 经反向代理 |
| 8019 | docs | 经反向代理 |
| 3306 / 6379 / 5672 / 8500 | MySQL / Redis / RabbitMQ / Consul | 仅内网 |
| 8110 / 8310 / 8510 | upstream-chan / upstream-proxy | 仅内网 |
| 11190 / 11200 | sms | 仅内网 |
| 8080 | onelink-mock | 仅联调 |
端口冲突时修改 `docker-compose.yml` 中对应变量,不要直接改 `docker-compose.yml`。
+187
View File
@@ -0,0 +1,187 @@
---
title: 启动与验证
description: 首次启动流程、状态检查与验证清单
outline: deep
---
# 启动与验证
## 启动
```bash
git clone <部署仓地址> iotlcm-deploy
cd iotlcm-deploy
# 首次部署建议前台启动,便于观察初始化过程
docker compose up
# 确认无误后转后台
docker compose up -d
```
不需要复制或修改任何文件,配置已带可用默认值。
### 首次启动发生什么
1. **MySQL 建库并导入 schema**(约 1~2 分钟)
`schema/` 下 13 个脚本按文件名顺序执行:建表 → 结构补齐 → 创建应用账号 → 重置种子账号口令 → 导入通道模板/字典/菜单等种子数据
2. MySQL 通过健康检查后,`admin-svr`、`openapi`、`worker-svr` 依次启动
3. 各服务向 Consul 注册
观察初始化进度:
```bash
docker compose logs -f mysql
```
看到 `[initdb] 应用账号 u_iotlcm 已创建` 与 `[initdb] 种子账号口令已统一重置` 表示初始化完成。
::: warning 初始化失败需清空数据卷
MySQL 只在 `data/mysql` 为空时执行 `schema/`。若中途失败,改完脚本必须清空卷才会重跑:
```bash
docker compose down
sudo rm -rf ./data/mysql
docker compose up -d
```
这会删除全部数据,仅限首次部署阶段使用。
:::
## 检查状态
```bash
docker compose ps
```
期望结果:
| 服务 | 状态 |
| --- | --- |
| iotlcm-mysql | Up (healthy) |
| iotlcm-redis | Up (healthy) |
| iotlcm-rabbitmq | Up (healthy) |
| iotlcm-consul | Up |
| iotlcm-admin-svr | Up |
| iotlcm-admin-ui | Up |
| iotlcm-openapi | Up |
| iotlcm-upstream-chan | Up |
| iotlcm-upstream-proxy | Up |
| iotlcm-worker-svr | Up |
| iotlcm-app-server | Up |
| iotlcm-app-web | Up |
| iotlcm-sms | Up (healthy) |
| iotlcm-docs | Up |
出现 `Restarting` 或 `Exited` 需查日志:
```bash
docker compose logs --tail=100 <服务名>
```
## 验证清单
按顺序逐项确认,任一步失败先解决再继续。
### 1. 数据库表数量
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e \
"SELECT COUNT(*) AS tables FROM information_schema.tables WHERE table_schema=\"db_iotlcm\";"'
```
预期 `tables` = **89**。
### 2. 种子数据
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e \
"SELECT (SELECT COUNT(*) FROM sys_menu) AS menus, (SELECT COUNT(*) FROM sys_user) AS users;"'
```
预期菜单约 **447** 条,账号 **18** 个。
### 3. 管理端后端
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8008/api.json
```
预期 `200`。
### 4. 管理端前端
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8009/
```
预期 `200`。
### 5. 对外 API
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/api.json
```
预期 `200`。
### 6. 短信服务
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:11190/healthz
```
预期 `200`。
### 7. 浏览器登录
访问 `http://<宿主机IP>:8009`:
| 项 | 值 |
| --- | --- |
| 账号 | `admin` |
| 口令 | `docker-compose.yml` 中 admin 的默认口令(首次登录后立即修改) |
能进入首页工作台、左侧菜单完整显示即部署成功。
## 首次登录后必做
1. **修改 admin 口令**:右上角个人中心 → 安全设置
2. **处理其余种子账号**:`user02`、`2admin` 等 17 个账号默认与 admin 同一口令,不使用请停用或删除
3. **确认字典与菜单加载**:进入「卡信息」页,确认筛选下拉选项不为空
## 后端自检
```bash
# admin-svr 是否连上数据库
docker compose logs admin-svr | grep -iE 'error|panic' | tail -20
# worker-svr 是否连上 RabbitMQ
docker compose logs worker-svr | tail -30
# upstream-chan 是否连上 Redis 与 Consul
docker compose logs upstream-chan | tail -30
```
正常日志不应出现 `connection refused`、`access denied`、`panic`。
## 停止与重启
```bash
docker compose stop # 停止,保留数据
docker compose restart # 重启全部
docker compose restart admin-svr # 重启单个
docker compose down # 停止并删除容器(./data 不受影响)
```
::: danger 不要执行 down -v
`-v` 会删除命名数据卷。本编排数据都在 `./data` 宿主机目录,但 `-v` 仍可能影响同项目的其他卷。停止服务用 `down` 即可。
:::
## 下一步
- 需要运营商挡板联调 → [可选服务](optional)
- 需要域名访问 → [可选服务](optional) 的反向代理章节
- 遇到报错 → [故障排查](troubleshooting)
+170
View File
@@ -0,0 +1,170 @@
---
title: 离线部署
description: 服务器无法访问镜像仓库时的部署方式
outline: deep
---
# 离线部署
服务器无法访问外部镜像仓库时使用。核心思路:**在有网机器上导出镜像 → 传到目标机 → 导入**。
## 适用场景
- 生产服务器在内网,不能访问外网
- 镜像仓库在内网不可达,或需要走审批才能拉取
- 需要把某一批镜像固化下来,保证多台机器部署一致
## 一、确定镜像清单
```bash
docker compose config --images | sort -u
```
输出示例(含可选 profile 时用 `--profile` 指定):
```text
registry.example.com/iotlcm/iotlcm:iotlcm-admin-svr-v1.0.4
registry.example.com/iotlcm/iotlcm:iotlcm-admin-ui-v1.0.4
registry.example.com/iotlcm/iotlcm:iotlcm-app-server-v1.0.2
...
registry.example.com/iotlcm/iotlcm:mysql-8.0
registry.example.com/iotlcm/iotlcm:redis-7.2
```
把清单保存下来,作为这次部署的镜像基线:
```bash
docker compose config --images | sort -u > images.txt
```
## 二、在有网机器导出
```bash
# 拉取全部镜像
docker compose pull
# 导出为单个压缩包
docker save $(cat images.txt) | gzip > iotlcm-images-$(date +%Y%m%d).tar.gz
```
单包体积参考:全部 14 个服务约 **1.5~2 GB**(压缩后)。
若目标机磁盘紧张,可按服务分包:
```bash
for img in $(cat images.txt); do
name=$(echo "$img" | tr '/:' '__')
docker save "$img" | gzip > "img-${name}.tar.gz"
done
```
## 三、传输
```bash
scp iotlcm-images-*.tar.gz user@target-host:/opt/iotlcm/
```
无网络直连时用移动介质拷入,注意校验完整性:
```bash
# 源端生成校验值
sha256sum iotlcm-images-*.tar.gz > images.sha256
# 目标端校验
sha256sum -c images.sha256
```
## 四、在目标机导入
```bash
gunzip -c iotlcm-images-*.tar.gz | docker load
```
分包的逐个导入:
```bash
for f in img-*.tar.gz; do gunzip -c "$f" | docker load; done
```
验证:
```bash
docker images | grep iotlcm
```
## 五、部署
导入后镜像已在本机,compose 不会再尝试拉取:
```bash
# 把 docker-compose.yml 里的镜像地址与 TAG 改成与导入的镜像一致
cp -r config.example config
vi config/*/config.yaml
docker compose up -d
```
::: warning REGISTRY 与 TAG 必须与导入的镜像名一致
`docker load` 只恢复镜像名,不会改标签。若 `docker-compose.yml` 里写 `TAG=latest` 而导入的是 `1.0.3`,compose 会认为镜像不存在并尝试拉取,离线环境直接失败。
用这条命令核对:
```bash
docker compose config --images | sort -u > expected.txt
docker images --format '{{.Repository}}:{{.Tag}}' | sort -u > actual.txt
comm -23 expected.txt actual.txt # 输出为空才说明镜像齐全
```
:::
## 六、长期方案:内网镜像仓库
如果需要经常部署或多机部署,建议搭内网镜像仓库,而不是每次传 tar 包。
### 同步镜像
```bash
# 在能访问外网的机器
for img in $(cat images.txt); do
docker pull "$img"
newimg="${img/registry.example.com/harbor.internal.example.com}"
docker tag "$img" "$newimg"
docker push "$newimg"
done
```
然后目标机的 `docker-compose.yml` 改为:
```bash
REGISTRY=harbor.internal.example.com
```
### 差异更新
版本升级时只需推送变化的镜像:
```bash
docker compose pull
# 对比 images.txt 找出新增/变更的标签,只同步这些
```
## 七、常见问题
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| `docker compose up` 卡在 `Pulling` | 镜像标签不匹配,compose 尝试联网拉取 | 按上文核对 `expected.txt` 与 `actual.txt` |
| `no space left on device` | 镜像包 + 解压后镜像双份占盘 | 导入后删除 tar 包,或换挂载点 |
| `load` 后 `docker images` 为空 | 压缩包损坏 | 用 `sha256sum -c` 校验 |
| 容器启动报 `exec format error` | 架构不匹配(x86 镜像跑在 ARM) | 在目标架构上重新导出 |
| 导出包过大 | 包含多个无用层 | 用 `docker save` 只导出必要标签 |
## 八、离线环境的其他准备
镜像只是第一步,以下也需提前准备:
| 资源 | 说明 |
| --- | --- |
| 部署仓代码 | `git clone` 或直接打包 `iotlcm-deploy/` 目录带过去 |
| 配置模板 | 已包含在部署仓 `config/` |
| 数据库脚本 | 已包含在部署仓 `schema/` |
| Docker 与 Compose | 目标机需预先安装,离线安装包需单独准备 |
| 时区数据 | 若使用 `Asia/Shanghai`,确认容器内 tzdata 完整(官方镜像已内置) |
+184
View File
@@ -0,0 +1,184 @@
---
title: 升级与备份
description: 版本固定、升级回滚、数据备份与日志清理
outline: deep
---
# 升级与备份
## 版本固定
生产环境**必须固定镜像标签**,不要用 `latest`:
- `latest` 会随仓库推送变化,同一份编排在不同时间拉到的镜像不同
- 出问题无法确定运行版本
- 回滚时没有明确目标
`docker-compose.yml` 中指定:
```bash
TAG=1.0.3
```
记录当前部署版本:
```bash
echo "$(date '+%Y-%m-%d %H:%M') 镜像=$(grep -m1 'iotlcm-admin-svr:' docker-compose.yml)" >> deploy-history.log
```
## 升级
```bash
# 1. 备份(不要跳过,见下节)
./backup.sh
# 2. 拉新镜像
vi docker-compose.yml # 修改镜像 TAG
docker compose pull
# 3. 更新
docker compose up -d
# 4. 验证
docker compose ps
docker compose logs --tail=50 admin-svr
```
滚动更新单个服务减少中断:
```bash
docker compose up -d --no-deps admin-svr
```
## 回滚
```bash
vi docker-compose.yml # TAG 改回旧版本
docker compose pull
docker compose up -d
```
::: warning 数据库变更不能随代码回滚
若新版本含数据库结构变更,回滚代码后数据库仍是新结构。升级前确认变更可逆,或保留升级前的完整备份。
:::
## 数据库重建
`schema/` 脚本只在 `data/mysql` 为空时执行:
| 情况 | 是否需要重建 |
| --- | --- |
| 升级应用镜像 | 否 |
| 修改 `config/` | 否,重启对应服务即可 |
| 修改 `schema/` 脚本 | 是(会丢数据) |
| 首次初始化失败 | 是 |
```bash
docker compose down
sudo rm -rf ./data/mysql
docker compose up -d
```
::: danger 生产环境禁止清空 data/mysql
生产数据必须通过数据库工具单独备份与迁移,不能依赖 schema 重建。
:::
## 备份
### 备份脚本
```bash
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"
STAMP=$(date '+%Y%m%d-%H%M%S')
DEST="./backups/${STAMP}"
mkdir -p "$DEST"
echo "[1/3] 导出数据库..."
docker compose exec -T mysql sh -c \
'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --triggers --events db_iotlcm' \
| gzip > "${DEST}/db_iotlcm.sql.gz"
echo "[2/3] 备份配置..."
tar czf "${DEST}/config.tar.gz" config/ docker-compose.yml
echo "[3/3] 备份上传文件..."
tar czf "${DEST}/upload.tar.gz" data/upload/ 2>/dev/null || echo " 上传目录为空,跳过"
echo "完成:${DEST}"
ls -lh "$DEST"
```
```bash
chmod +x backup.sh && ./backup.sh
```
### 只备份数据库
```bash
docker compose exec -T mysql sh -c \
'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction db_iotlcm' \
| gzip > "db_iotlcm-$(date +%Y%m%d).sql.gz"
```
### 恢复
```bash
gunzip -c db_iotlcm-20260101.sql.gz | \
docker compose exec -T mysql sh -c \
'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm'
```
### 备份内容与优先级
| 内容 | 位置 | 重要性 |
| --- | --- | --- |
| 数据库 | MySQL 容器内 `db_iotlcm` | **最高**,业务数据全部在此 |
| 配置 | `config/` + `docker-compose.yml` | 高,丢失需重新配置 |
| 上传文件 | `data/upload/` | 中,附件与图片 |
| Redis 数据 | `data/redis/` | 低,缓存可重建 |
| RabbitMQ 队列 | `data/rabbitmq/` | 低,未消费消息会丢失 |
| 日志 | `data/logs/` | 低,排障用 |
### 备份策略
| 场景 | 频率 | 保留 |
| --- | --- | --- |
| 生产 | 每日一次 + 升级前 | 30 天 |
| 试用 | 有数据变更时 | 7 天 |
备份文件应转存到异机或对象存储,不要与数据库同盘。
## 日志清理
日志写在 `data/logs/` 下按服务分目录,应用侧已按小时轮转(`{Y-m-d-H}.log`)。清理旧日志:
```bash
# 删除 7 天前的日志
find ./data/logs -name '*.log' -mtime +7 -delete
```
建议配置 cron 定期执行:
```cron
0 3 * * * root find /path/to/iotlcm-deploy/data/logs -name '*.log' -mtime +7 -delete
```
## 监控
基础组件自带端点:
| 服务 | 端点 |
| --- | --- |
| RabbitMQ | `http://<host>:15672` 管理界面 |
| Consul | `http://<host>:8500/ui` |
| MySQL | `docker compose exec mysql mysqladmin status` |
建议告警指标:
- MySQL 连接数与磁盘使用率
- Redis 内存使用率
- RabbitMQ 队列积压(`iotlcm.batch.execute`、`iotlcm.app.business`)
- worker-svr 全量同步任务失败数
+171
View File
@@ -0,0 +1,171 @@
---
title: 可选服务
description: 运营商接口挡板、飞书通知与反向代理
outline: deep
---
# 可选服务
以下服务默认不启动,按需通过 profile 开启。
## 运营商接口挡板
### 用途
模拟中国移动 OneLink 接口。**没有真实运营商环境时,靠它跑通完整链路**:
```text
admin-svr → worker-svr → upstream-chan → onelink-mock
```
不开挡板时,`upstream-chan` 会尝试连接真实运营商接口。没有凭据则批次执行、状态同步、流量查询全部失败。
### 启动
```bash
docker compose --profile with-mock up -d
```
验证:
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api.json
```
预期 `200`。接口文档在 `http://127.0.0.1:8080/swagger`。
### 配置
挡板自身无需配置,数据存在容器内 SQLite(`./data/mock/onelink_mock.db`)。
`upstream-chan` 配置中已预置指向:
```yaml
dependencies:
sim_yd_mock_v5:
url: "http://onelink-mock:8080/v5/"
```
数据库种子脚本 `schema/08-seed-mock-channel.sql` 已插入 Mock 通道(`channel_yd_mock`)并指向该地址。
`app-server` 若使用挡板,把 `upstream.onelinkMock.enabled` 改为 `true`,`baseUrl` 填 `http://onelink-mock:8080/v5`。
### 造测试卡
挡板支持生成模拟卡片,用于验证查询、开停复机、续费流程。接口见挡板 Swagger 页面。
### 切换到真实运营商
1. 删除或注释 `upstream-chan` 配置中的 `sim_yd_mock_v5` 段
2. 在平台「通道管理」录入真实运营商通道(AppKey、AppSecret、接口地址)
3. 停用 Mock 通道,避免卡片误挂
```bash
docker compose --profile with-mock stop onelink-mock
```
## 飞书通知
### 用途
把全量同步完成、失败告警等运维事件推送到飞书群。
### 准备
在飞书开放平台创建自建应用,获取 `appId`、`appSecret` 与目标群的 `chatId`,填入 `docker-compose.yml`:
```bash
FEISHU_APP_ID=cli_xxxxxxxx
FEISHU_APP_SECRET=xxxxxxxx
FEISHU_CHAT_ID=oc_xxxxxxxx
```
### 启动
```bash
docker compose --profile with-bot up -d
```
bot 读取 `worker-svr` 生成的全量同步归档(`./data/logs/worker-svr/full-sync`),该目录有数据后才会产生通知。
## 反向代理
### 用途
把分散在各端口的服务统一到域名下按路径分流。宿主机已有 nginx 或面板时不需要。
### 域名与分流
编辑 `config/nginx/iotlcm.conf`:
| 域名 | 分流到 |
| --- | --- |
| `api.your-domain.example.com` | openapi(`/openapi/v3/`、`/route/open/`) |
| `admin.your-domain.example.com` | 管理端前端 + `/docs/` 文档站 |
| `app.your-domain.example.com` | 小程序前端 + `/app/api/v1/` 后端 |
### 启动
```bash
docker compose --profile with-nginx up -d
```
默认监听 80,改 `docker-compose.yml` 的 `NGINX_HTTP_PORT` 可调整。
### 使用宿主机 nginx
不启动容器时,把 `config/nginx/iotlcm.conf` 中的 `upstream` 与 `location` 段落复制到宿主机 nginx 配置。
::: warning 宿主机 nginx 无法解析容器名
需要把 `upstream` 里的服务名换成宿主机地址:
```nginx
# 容器内写法
server admin-svr:8008;
# 宿主机写法
server 127.0.0.1:8008;
```
:::
### 启用 HTTPS
自带配置未包含证书,启用需三步:
1. 准备证书,放入 `data/certs/`
2. 在 server 块加入 SSL 配置:
```nginx
listen 443 ssl;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
```
3. 给 nginx 服务增加证书目录挂载,并加 80 → 443 跳转:
```yaml
volumes:
- ./data/certs:/etc/nginx/certs:ro
```
```nginx
server {
listen 80;
server_name admin.your-domain.example.com;
return 301 https://$host$request_uri;
}
```
## 组合启动
多个 profile 可同时启用:
```bash
docker compose --profile with-mock --profile with-nginx up -d
```
查看指定 profile 下的服务清单:
```bash
docker compose --profile with-mock config --services
```
+134
View File
@@ -0,0 +1,134 @@
---
title: 环境准备
description: 部署 IotLCM 所需的硬件、系统、Docker 与网络条件
outline: deep
---
# 环境准备
## 硬件要求
按数据规模选择:
| 场景 | CPU | 内存 | 磁盘 | 说明 |
| --- | --- | --- | --- | --- |
| 试用 / 演示 | 2 核 | 4 GB | 40 GB | 少量卡片,建议停用定时同步任务 |
| 生产(中小规模) | 4 核 | 8 GB | 200 GB | 万级卡片,开启全量同步 |
| 生产(大规模) | 8 核+ | 16 GB+ | 500 GB+ | 十万级卡片,建议数据库独立部署 |
磁盘估算:MySQL 数据约 1~2 KB/张卡;日志随同步任务量增长,建议单独挂载并配置轮转。
生产 8 GB 场景的内存分配参考:
| 服务 | 占用 |
| --- | --- |
| MySQL | 1~2 GB |
| Redis | 200~500 MB |
| RabbitMQ | 300~500 MB |
| worker-svr | 500 MB~1 GB(与并发同步卡片数相关) |
| 其余业务服务 | 各 100~200 MB |
## 系统要求
| 项 | 要求 |
| --- | --- |
| 操作系统 | Linux x86_64(CentOS 7+ / Ubuntu 20.04+ / Debian 11+) |
| 内核 | 3.10 以上 |
| 文件系统 | ext4 / xfs |
| 时区 | 建议 `Asia/Shanghai`,与 `docker-compose.yml` 的 `TZ` 保持一致 |
## Docker 要求
| 组件 | 最低版本 | 说明 |
| --- | --- | --- |
| Docker Engine | 20.10 | 需支持 `healthcheck` 与 `profiles` |
| Docker Compose | v2.0(插件版) | 使用 `docker compose` 而非 `docker-compose` |
验证:
```bash
docker version
docker compose version
```
`docker compose version` 需 ≥ v2.0。若只有 `docker-compose`(v1),请升级到 Compose V2 插件。
## 镜像仓库
平台镜像发布在阿里云容器镜像服务,**仓库类型为公开,无需登录即可拉取**。
| 项 | 值 |
| --- | --- |
| 公网地址 | `crpi-qflrqrxoohlkkvgc.cn-chengdu.personal.cr.aliyuncs.com` |
| VPC 地址 | `crpi-qflrqrxoohlkkvgc-vpc.cn-chengdu.personal.cr.aliyuncs.com` |
| 命名空间 | `iotlcm` |
| 地域 | 西南1(成都) |
**所有镜像都放在同一个镜像仓库** `iotlcm/iotlcm` 下,靠 tag 区分服务与版本:
```text
crpi-.../iotlcm/iotlcm:mysql-8.0
crpi-.../iotlcm/iotlcm:redis-7.2
crpi-.../iotlcm/iotlcm:iotlcm-admin-svr-v1.0.4
crpi-.../iotlcm/iotlcm:iotlcm-openapi-v1.0.4
crpi-.../iotlcm/iotlcm:iotlcm-app-server-v1.0.2
```
tag 格式统一为 **`<服务名>-<版本>`**。
地址结构是 `<Registry>/<命名空间>/<镜像仓库>:<tag>`,即:
```text
crpi-...cn-chengdu.personal.cr.aliyuncs.com / iotlcm / iotlcm : iotlcm-admin-svr-v1.0.4
↑ 命名空间 ↑ 镜像仓库 ↑ tag
```
`docker-compose.yml` 里的镜像地址已预置为上述公网地址,一般无需修改。
::: tip 基础组件也在同一仓库
MySQL、Redis、RabbitMQ、Consul、nginx 同样在这个仓库里(`mysql-8.0`、`redis-7.2` 等 tag),不从 Docker Hub 直连(公网不稳定且 tag 会漂)。版本已在编排文件中固定。
:::
::: tip 阿里云 VPC 地址
若部署在阿里云同地域 ECS 上,可把编排文件里的 Registry 前缀改为 VPC 地址,走内网流量、不消耗公网带宽:
```yaml
image: 'crpi-qflrqrxoohlkkvgc-vpc.cn-chengdu.personal.cr.aliyuncs.com/iotlcm/iotlcm:mysql-8.0'
```
:::
## 网络与防火墙
仅需对外开放反向代理端口(默认 80 / 443),其余服务端口应限制为内网访问。
```bash
# 示例:firewalld 仅放行 80/443
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
```
::: tip 默认只监听本机
`admin-svr`、`admin-ui`、`openapi`、`app-server`、`app-web`、`docs` 默认绑定 `127.0.0.1`,只能本机访问。对外需通过反向代理,见[可选服务](optional)。
:::
## 外部依赖(可选)
以下能力需要外部账号或服务,**不影响平台主流程**:
| 服务 | 用途 | 不配置的影响 |
| --- | --- | --- |
| 短信服务商 | 发送验证码 | 无法发送短信 |
| 微信支付 | 在线支付 | 支付功能不可用 |
| 飞书 | 运维通知 | 不启动 bot 即可 |
::: tip 时序库已内置
流量历史使用的 TDengine **已包含在编排里**,由 `tdengine-init` 服务在首次启动时自动建库建表,无需额外申请或配置:
| 库 | 分片 | 保留 | 超级表 |
| --- | --- | --- | --- |
| `iotlcm_daily` | 7 天 | 3 年 | `t_sim_card_usage_daily` |
| `iotlcm_monthly` | 30 天 | 3 年 | `t_sim_card_usage_monthly` |
编排使用的版本是 `3.3.6.13`(公开镜像可得),生产为 `3.4.0.9`。如需与生产完全一致,可自行构建镜像并替换 `docker-compose.yml` 中的 tag。
:::
+181
View File
@@ -0,0 +1,181 @@
---
title: 分离部署
description: 中间件独立部署,业务服务单独一台或多台
outline: deep
---
# 分离部署
把 MySQL、Redis、RabbitMQ、Consul 放到独立主机或使用云托管,业务服务单独部署。
## 适用场景
- 数据库已有云托管实例(RDS、云 Redis、云 MQ)
- 中间件已有独立集群,不希望在业务机上再跑一套
- 业务机配置较低,不适合同时承载数据库
## 部署结构
```text
┌─────────────────────┐ ┌──────────────────────┐
│ 中间件主机 / 云服务 │ │ 业务主机 │
│ │ │ │
│ MySQL :3306 │◄───────┤ admin-svr :8008 │
│ Redis :6379 │ │ admin-ui :80 │
│ RabbitMQ :5672 │ │ openapi :8000 │
│ Consul :8500 │ │ app-server :8010 │
│ │ │ worker-svr │
└─────────────────────┘ │ upstream-chan/-proxy│
└──────────────────────┘
```
## 一、准备中间件
在中间件侧完成(云托管通常已就绪):
| 组件 | 需准备 |
| --- | --- |
| MySQL | 建库 `db_iotlcm`、执行 `schema/` 脚本、建应用账号 `u_iotlcm` |
| Redis | 开启认证,记录地址与口令 |
| RabbitMQ | 建用户与 vhost,记录地址与口令 |
| Consul | 单机模式即可,记录地址 |
### 初始化数据库
在能连到目标 MySQL 的机器上执行:
```bash
# 按文件名顺序执行全部脚本
for f in schema/0*.sql; do
echo "执行 $f"
mysql -h <MySQL地址> -uroot -p<口令> < "$f"
done
```
::: warning `.sh` 脚本需单独执行
`schema/04-users.sh` 与 `schema/05-reset-passwords.sh` 依赖环境变量,不能直接重定向执行:
```bash
MYSQL_ROOT_PASSWORD='<口令>' MYSQL_APP_PASSWORD='<应用口令>' \
MYSQL_HOST=<地址> bash schema/04-users.sh
```
:::
## 二、准备业务机 Compose
复制 `docker-compose.yml`,删除四个中间件服务(`mysql`、`redis`、`rabbitmq`、`consul`),并移除各处 `depends_on` 对它们的引用。
```yaml
# docker-compose.app.yml
services:
admin-svr:
image: '${REGISTRY}/iotlcm/iotlcm:iotlcm-admin-svr-${TAG}'
# ... 保留 ports / volumes / environment
# 删除 depends_on 中对 mysql/redis/rabbitmq 的依赖
```
::: tip 网络要求
业务机必须能访问中间件主机的对应端口。若有防火墙,放行:
- 业务机 → 中间件:3306、6379、5672、8500
- 外部 → 业务机:仅反向代理端口(80 / 443)
:::
## 三、修改配置指向外部地址
这是本节最关键的一步。所有配置里以**服务名**书写的地址都要改成实际地址。
### admin-svr
```yaml
database:
default:
link: "mysql:u_iotlcm:<口令>@tcp(<MySQL地址>:3306)/db_iotlcm?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
redis:
default:
address: "<Redis地址>:6379"
pass: "<Redis口令>"
rabbitmq:
dsn: "amqp://<用户>:<口令>@<RabbitMQ地址>:5672/"
```
### openapi
同上三处:`database.default.link`、`redis.default.address`、`rabbitmq.dsn`。
### worker-svr
同上三处。注意 `tsdb.tdengine.dsn` 若使用 TDengine 也要改为实际地址。
### app-server
改 `database.default.link` 与 `redis.default`。
### upstream-chan / upstream-proxy
```yaml
registry:
type: "consul"
address: "<Consul地址>:8500"
```
同时改 `database.default.link` 与 `redis.default`。
### 服务互访地址
若业务服务分散在多台机器,`admin-svr`、`worker-svr`、`app-server` 配置里的上游地址也要改:
```yaml
upstream:
proxy:
address: "<upstream-proxy所在主机>:8510"
gateway:
address: "<upstream-chan所在主机>:8110"
```
`upstream-proxy` 的 `gateway.address` 同理。
::: danger 最容易出的错
只改数据库地址,忘了改 `upstream-proxy:8510`、`upstream-chan:8110` 这类服务名。症状是服务能启动、数据库也通,但批次执行、智能诊断全部超时。
:::
## 四、启动
```bash
# 按需修改 docker-compose.app.yml 的镜像地址、端口与口令
cp -r config.example config
vi config/*/config.yaml # 按上节改为外部地址
docker compose -f docker-compose.app.yml up -d
```
此时 `docker-compose.yml` 中的 `MYSQL_*`、`REDIS_PASSWORD`、`RABBITMQ_PASSWORD` 不再用于启动中间件容器,但仍作为配置中的变量来源,**取值要与中间件实际口令一致**。
## 五、验证
```bash
docker compose -f docker-compose.app.yml ps
# 数据库连通性
docker compose -f docker-compose.app.yml exec admin-svr sh -c 'nc -zv <MySQL地址> 3306'
docker compose -f docker-compose.app.yml exec admin-svr sh -c 'nc -zv <Redis地址> 6379'
# Redis 认证
docker compose -f docker-compose.app.yml exec admin-svr sh -c 'redis-cli -h <地址> -a "<口令>" ping'
# 服务日志
docker compose -f docker-compose.app.yml logs --tail=50 admin-svr
```
浏览器登录管理端,确认能查卡、能发起业务办理。
## 六、注意事项
| 项 | 说明 |
| --- | --- |
| 网络延迟 | 业务机到中间件的延迟建议 < 5ms。跨地域部署会造成批次执行明显变慢 |
| 连接池 | 外部数据库延迟较高时,适当调大 `database.default.maxIdle` 与 `maxOpen` |
| 备份 | 分离后数据库备份由中间件侧负责,本仓的 `backup.sh` 需调整连接参数 |
| 时钟 | 各机器需开启 NTP 同步,否则全量同步的时间窗口判断会出错 |
+253
View File
@@ -0,0 +1,253 @@
---
title: 故障排查
description: 部署与运行中的常见问题及处理
outline: deep
---
# 故障排查
## 通用排查
```bash
docker compose ps # 容器状态
docker compose logs --tail=100 <服务> # 最近日志
docker compose logs -f <服务> # 跟随日志
docker compose exec <服务> sh # 进容器
```
## 启动类
### MySQL 一直不 healthy
**现象**:`iotlcm-mysql` 反复重启或一直 `starting`,其余服务不启动。
```bash
docker compose logs mysql | tail -50
```
| 日志关键词 | 原因 | 处理 |
| --- | --- | --- |
| `Access denied` | root 口令与旧数据卷不一致 | 改 `docker-compose.yml` 与卷内一致,或清空 `data/mysql` 重建 |
| `Can't create/write to file` | 目录权限 | `sudo chown -R 999:999 data/mysql` |
| `No space left on device` | 磁盘满 | 清理磁盘 |
| `Can't open shared library` | 架构不匹配 | 确认镜像与宿主机架构一致 |
### schema 初始化失败
```bash
docker compose logs mysql | grep -iE 'error' | head -30
```
| 原因 | 处理 |
| --- | --- |
| `MYSQL_APP_PASSWORD` 未设置,`04-users.sh` 退出 | 补上后清空 `data/mysql` 重建 |
| `MYSQL_ADMIN_PASSWORD` 未设置,`05-reset-passwords.sh` 退出 | 同上 |
| SQL 脚本语法问题 | 单独执行该脚本定位 |
::: warning 改脚本后必须清空数据卷
MySQL 只在 `data/mysql` 为空时执行 `schema/`,文件改了不会自动重跑:
```bash
docker compose down && sudo rm -rf ./data/mysql && docker compose up -d
```
:::
### 业务服务反复重启
```bash
docker compose logs --tail=80 admin-svr
```
| 报错 | 原因 | 处理 |
| --- | --- | --- |
| `possible config files "config" not found` | `config/` 不存在或挂载失败 | `cp -r config.example config`,确认 `config/admin-svr/config.yaml` 存在 |
| `dial tcp mysql:3306: connection refused` | MySQL 未就绪 | 等 MySQL healthy |
| `Access denied for user 'u_iotlcm'` | 应用账号口令与库内不一致 | 确认 `docker-compose.yml` 的 `MYSQL_APP_PASSWORD` 与初始化时一致 |
| `panic: unsupported provider xxx` | 通道模板与镜像版本不匹配 | 确认镜像版本与 schema 版本配套 |
## 功能类
### 登录后提示未授权 / 接口 401
**原因**:`JWT_SIGNING_KEY` 在服务间不一致。
确认 `admin-svr`、`openapi`、`app-server` 三份配置的 `jwt.signingKey` 都引用 `${JWT_SIGNING_KEY}` 且 `docker-compose.yml` 有值,然后重启:
```bash
docker compose restart admin-svr openapi app-server
```
### 登录成功但页面空白 / 菜单不显示
1. 浏览器 F12 看 Network 有无 4xx/5xx
2. 检查菜单数据:
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT COUNT(*) FROM sys_menu WHERE status=0;"'
```
菜单为 0 说明种子数据未导入,需重建库。
3. 检查角色菜单关联:
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT COUNT(*) FROM sys_role_menu;"'
```
### 卡信息页通道列空白 / 筛选无选项
**原因**:Mock 通道未插入。
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "SELECT channel_id, channel_name FROM t_sim_channels;"'
```
为空则手工执行种子脚本:
```bash
docker compose exec -T mysql sh -c \
'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm' < schema/08-seed-mock-channel.sql
```
### 批次执行一直失败
按顺序排查:
1. **挡板是否启动**(无真实运营商时)
```bash
docker compose ps | grep onelink-mock
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api.json
```
未启动则 `docker compose --profile with-mock up -d`。
2. **worker-svr 是否连上 RabbitMQ**
```bash
docker compose logs worker-svr | grep -i 'rabbit\|amqp' | tail -20
```
3. **upstream-chan 是否能连到上游**
```bash
docker compose logs upstream-chan --tail=50
```
4. **是否被运营商限流**
检查 worker-svr 日志中的 `rate limit`、`429`、`12021`。核对 `batch.inbound.upstreamRateLimit` 的 QPS 是否超过上游配额。
### 历史流量查询无数据
**原因**:TDengine 未启动、未完成建表,或配置里的地址不对。
```bash
docker compose logs worker-svr | grep -i 'tdengine\|taos' | tail -20
docker compose logs tdengine-init | tail -20 # 建库建表是否成功
docker compose exec tdengine taos -s "show databases;"
```
常见原因与处理:
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| `tdengine-init` 报 MISS | 建表失败 | 看该容器日志里的 `DB error`;改 `tdengine-init/init.sql` 后重跑 `docker compose up -d tdengine-init` |
| worker 日志报库表不存在 | 未执行建表 | 同上,确保 init 容器退出码为 0 |
| 连接超时 | 地址/端口不对 | 配置里应为 `ws(tdengine:6041)`,容器内用服务名而非 IP |
建表脚本全部带 `IF NOT EXISTS`,可重复执行。
### 短信发不出去
```bash
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:11190/healthz
docker compose logs sms --tail=50
```
| 报错 | 原因 |
| --- | --- |
| `InvalidAccessKeyId` | `docker-compose.yml` 中 AK 未填或错误 |
| `isv.SMS_SIGNATURE_ILLEGAL` | `signName` 与服务商审核签名不一致 |
| `isv.SMS_TEMPLATE_ILLEGAL` | 模板 ID 未审核通过 |
| `缺少 config.templateId` | 场景模板库未配置,调用方需自带 templateId |
### 图片上传失败 / 不显示
```bash
ls -la data/upload/
sudo chown -R 1000:1000 data/upload
docker compose restart admin-svr admin-ui
```
## 性能类
### CPU 被 worker-svr 打满
**原因**:定时任务开启全量同步,大量卡片并发查询上游。
1. 降低并发与限流:
```yaml
batch:
inbound:
upstreamConcurrency: 50 # 从 300 降低
upstreamBatchSize: 1
```
2. 演示 / 试用环境直接停用定时任务:
```bash
docker compose exec mysql sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" db_iotlcm -e "UPDATE sys_job SET status=1;"'
```
种子数据中定时任务默认已全部停用(`status=1`)。
### 磁盘持续增长
```bash
du -sh data/* | sort -rh
```
| 目录 | 处理 |
| --- | --- |
| `data/logs` | 清理旧日志,见[升级与备份](operations) |
| `data/upload` | 检查异常大的上传文件 |
| `data/mysql` | 数据库增长正常,关注 binlog |
| `data/redis` | AOF 文件,正常 |
## 网络类
### 容器间无法互访
```bash
docker network inspect iotlcm-network
docker compose exec admin-svr sh -c 'nc -zv mysql 3306'
```
配置中应使用 `mysql:3306`、`redis:6379` 这样的服务名,不要写 `127.0.0.1`。
### 宿主机访问不了 127.0.0.1 绑定的服务
`admin-svr`、`admin-ui`、`openapi`、`app-server`、`app-web`、`docs` 默认只绑 `127.0.0.1`。对外访问需:
1. 用 `--profile with-nginx` 启动反向代理,或
2. 宿主机 nginx 转发到 `127.0.0.1:<端口>`
::: danger 不要直接把端口改成 0.0.0.0
这会把管理端、数据库等内部服务暴露到公网。
:::
## 求助时提供的信息
```bash
docker compose version
docker version --format '{{.Server.Version}}'
docker compose ps
docker compose logs --tail=200 <服务名>
df -h && free -h
```
+28
View File
@@ -0,0 +1,28 @@
---
layout: home
hero:
name: iotLCM
text: 文档中心
tagline: iotLCM 平台文档与接口规范。
actions:
- theme: brand
text: 使用手册
link: /manual/
- theme: alt
text: OpenAPI 接口文档
link: /openapi/
- theme: alt
text: 共享 Token 接口文档
link: /openapi/operator/
features:
- title: 使用手册
details: 按使用端提供 Web 管理平台与小程序端手册,覆盖查卡、诊断、业务管理和账号服务。
- title: OpenAPI 接口文档
details: 客户开放接口的鉴权方式、请求参数、响应结构、调用示例与兼容接口说明。
- title: 共享 Token 接口文档
details: 共享 Token 获取与白名单转发说明。
- title: 部署手册
details: 文档站的推荐环境、Node/Docker 编译方法、nginx 与对象存储部署方式,以及路径前缀配置要点。
---
+23
View File
@@ -0,0 +1,23 @@
---
layout: home
hero:
name: iotLCM 使用手册
text: 操作指南
tagline: 按使用端查看卡片查询、业务管理与移动服务说明。
actions:
- theme: brand
text: Web端手册
link: /manual/web/
- theme: alt
text: 小程序端手册
link: /manual/mini-program/
features:
- title: 版本选型指引
details: 使用浏览器登录企业管理后台,请阅读 Web 端手册;使用 IOTLCM 物联卡用户服务小程序,请阅读小程序端手册。不同端的功能入口、权限和页面结构有所区别。
- title: Web端
details: 面向企业管理员和业务人员,覆盖首页、卡信息、智能诊断、业务受理、企业管理、工单、API 凭证与执行日志。
- title: 小程序端
details: 对应 iotlcm-app v1.0.1,覆盖首页公开查卡、扫码与拍照识别、卡片详情、卡片排查和账号密码登录;办理、绑定、微信登录及注册等功能暂未开放。
---
+61
View File
@@ -0,0 +1,61 @@
# 登录后办理概览
登录后的页面会根据账号工作区和权限动态显示。看不到某个入口时,通常是账号未配置对应权限,或该业务仍受版本开关限制。
## 登录与身份选择
1. 在首页或需要账号数据的页面进入登录,选择“密码登录”或“验证码登录”。
![账号密码登录入口](/images/manual/mini-program/business-account-login-entry.png)
2. 密码登录支持手机号或用户名;验证码登录需要手机号、图形验证和短信验证码。
![账号登录中的密码登录和验证码登录选项](/images/manual/mini-program/business-login-methods.jpg)
3. 同一登录凭据匹配多个身份时,按页面提示选择本次进入的账号。
注册、微信登录和“忘记密码”页面当前关闭。无法登录时联系账号管理员或客服。
## 设备与卡片操作
登录后点击底部“设备”进入设备列表,或从“办理”进入“我的卡片”。设备页支持关键词搜索、状态筛选、刷新和加载更多;卡片详情显示账号权限范围内的字段及会话、流量信息。首页最多预览三张卡片,并优先展示即将到期的卡;点击“管理”进入全部设备。
<div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; width: 100%; max-width: 440px; margin: 24px auto;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/business-device-tab.png" alt="从底部设备入口查看我的设备" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">底部“设备”入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/business-my-cards-entry.jpg" alt="从办理页面进入我的卡片" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">“办理”中的“我的卡片”入口</figcaption>
</figure>
</div>
续费、变更加油包、停机、复机等卡片更多操作当前暂未开放。重启、数据服务操作和卡片划拨可按页面位置、账号权限及卡片状态开放。页面只显示当前账号可执行的操作,提交前仍会再次校验。
## 办理入口
“办理”页包含卡片排查、卡片与客户、工单和执行记录等分组。当前版本按权限开放以下能力:
| 能力 | 入口与说明 |
| --- | --- |
| 卡片排查 | 输入 ICCID、MSISDN 或 IMSI,查看状态、会话和流量信息 |
| 卡片划拨 | 选择启用的直属下级客户和卡片;单次最多100张,提交后到批次日志查看结果 |
| 客户管理 | 查看授权范围内的企业客户及客户详情;终端账号不显示此入口 |
| 工单服务 | 按列表、提交、详情和处理权限使用相应操作 |
| 批次执行日志 | 按列表、概览和明细权限查看批次进度与结果 |
续费、变更与加油包当前暂未开放;订单、预警和余额充值等页面仍受版本开关限制,进入时可能提示暂未开放。
## 专题说明
- [客户管理与卡片划拨](./customers-and-allocation)
- [工单服务](./work-orders)
- [批次执行日志](./execution-logs)
- [消息与个人中心](./messages-and-profile)
## 操作提示
- 划拨提交后不要重复创建任务;结果未确认时先核查批次日志和卡片归属。
- 工单附件上传失败时检查网络后重试;不要在反馈中提交无关敏感信息。
- 所有状态和排查结果仅供业务处理参考,最终以运营商和平台实际结果为准。
@@ -0,0 +1,60 @@
# 客户管理与卡片划拨
本页介绍企业、代理及平台工作区中的客户查询和卡片划拨。入口按账号身份和权限显示;终端账号不提供客户管理入口。
## 客户管理
1. 登录后从“我的”或“办理”进入“客户管理”。
<div class="manual-image-grid" style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; width: 100%; max-width: 440px; margin: 24px auto;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/customer-management-from-profile.png" alt="从我的页面进入客户管理" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">“我的”中的客户管理入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/customer-management-from-business.png" alt="从办理页面进入客户管理" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">“办理”中的客户管理入口</figcaption>
</figure>
</div>
2. 输入企业名称或手机号搜索客户,也可刷新列表或加载更多。
![客户管理中的搜索框和刷新入口](/images/manual/mini-program/customer-management-search.png)
3. 点击客户查看详情,包括客户资料、客户类型和实名信息。
![客户详情中的客户类型、实名资料状态和持卡统计](/images/manual/mini-program/customer-details-v102.png)
4. 客户详情中的卡片数量和状态统计仅统计该客户直接持有的卡,不包含下级客户。
列表及详情分别需要对应权限。若搜索无结果,检查名称或手机号;若没有入口,联系账号管理员确认账号类型和授权范围。
## 单卡划拨
从卡片详情有权限时显示的“划拨这张卡”入口开始。确认目标客户后检查卡片信息并提交。单卡入口会锁定当前卡,不能改选其他卡。划拨成功后卡片归属转给目标客户。
## 批量划拨
1. 从“办理”进入“卡片划拨”。
![从办理页面进入卡片划拨](/images/manual/mini-program/card-allocation-entry-v102.png)
2. 选择目标客户:仅支持已启用的直属下级客户,可按客户名称或编码搜索。
![批量卡片划拨中的目标客户选择入口](/images/manual/mini-program/allocation-target-customer-v102.jpg)
3. 搜索并勾选要划拨的卡片。可以选择当前结果;更换搜索条件后,先前选择仍保留。打开已选列表可移除单张或清空选择。
![批量划拨中选择目标客户、勾选卡片和查看已选入口](/images/manual/mini-program/allocation-select-cards-v102.jpg)
4. 每次最多选择100张。停机、销户或套餐过期的卡不可划拨。
5. 核对目标客户和卡数后提交。后台会整批校验;存在失败卡时整批不执行。
6. 提交后进入批次日志查看进度和结果。任务处理中时使用原页面的刷新操作查看状态。
![卡片划拨提交后的批次执行日志、进度结果和刷新入口](/images/manual/mini-program/allocation-batch-logs-v102.png)
若网络中断导致结果未知,不要再次新建划拨任务。先通过原批次号查看日志并核对卡片归属;只有确认原任务未受理且没有待执行任务后,才按页面提示核查并重试原任务。仍无法确认时联系管理员处理。
## 权限与边界
划拨入口要求企业、代理或平台工作区及卡片划拨权限。目标客户和卡片仍受账号数据范围约束。执行记录列表、概览和明细分别受批次日志权限控制;划拨页面可能提供与本次任务关联的结果查看入口。
+39
View File
@@ -0,0 +1,39 @@
# 卡片详情
从首页查询结果点击“详情”后进入卡片详情页。首页会以公开来源打开详情,因此未登录或公开来源都会使用受限展示模式。
## 公开详情展示内容
公开模式包含两组信息:
- 基础信息:卡号、卡状态、在线状态;
- 流量信息:已用流量。
![卡片详情中的基础信息和流量信息](/images/manual/mini-program/card-details-traffic.png)
卡号可能已经脱敏,页面显示什么就以什么为准。点击或长按卡号行可以复制当前显示值。
公开模式不会展示完整 IMSI、IMEI、备注、总流量、剩余流量、会话信息、APN、IPv4、IPv6 和服务状态,也不会提供重启、开通数据、停用数据等操作按钮。
## 登录后详情
登录并通过权限校验后,设备列表和卡片详情可以展示更完整的账号范围数据,例如 MSISDN、ICCID、IMSI、设备 IMEI、套餐流量和同步时间。字段是否出现取决于接口返回值和账号权限。
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/devices-list-account-data-v2.jpg" alt="登录后的设备列表和账号范围数据" style="display: block; width: 100%; height: auto;">
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/devices-search-account-data-v2.jpg" alt="全部设备中的账号范围数据" style="display: block; width: 100%; height: auto;">
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/card-details-account-data-v2.jpg" alt="登录后的卡片详情账号范围数据" style="display: block; width: 100%; height: auto;">
</figure>
</div>
## 查不到详情时
- 确认查询号码没有空格或遗漏字符;
- 从首页重新查询后再点击“详情”;
- 若页面提示当前账号没有权限,请使用公开查询,或联系账号管理员确认数据范围。
@@ -0,0 +1,61 @@
# 卡片排查
卡片排查用于根据卡片状态、在线状态、流量和设备信息整理处理方向。
## 进入排查
可以从以下位置进入:
1. 首页点击“卡片排查”,手动输入卡号;
2. 首页查询成功后点击“卡片排查”,系统会带入当前查询值;
![首页查询结果中的卡片排查入口](/images/manual/mini-program/diagnosis-entry-from-query-v2.png)
3. 直接打开卡片排查页,在输入框填写 `ICCID`、`MSISDN` 或 `IMSI`。
<div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/diagnosis-entry-from-home.png" alt="从首页常用入口进入卡片排查" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">首页卡片排查入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/diagnosis-manual-input.jpg" alt="卡片排查页手动输入卡号" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">输入卡号开始排查</figcaption>
</figure>
</div>
点击“开始排查”提交。排查过程中不要连续重复点击。
## 结果摘要
普通用户或未登录用户通常可以看到:
- 卡片状态;
- 在线状态;
- 已用流量或流量状态;
管理员账号可能看到更多卡片、数据服务、停机冻结和限制策略信息。页面会按实际接口返回值隐藏没有数据的字段。
## 流量使用分析
当前页面提供“近1个月、近3个月、近6个月”三个周期。**`v1.0.1` 默认只对顶层管理员显示流量趋势模块;其他账号即使登录,也不保证看到该模块。**
有数据时会显示周期总用量、日均或月均用量、单期峰值和趋势图;没有数据时显示“暂无历史流量数据”。趋势只用于辅助排查,不等同于实时计费结果。
## 排查结果的处理方向
| 结果 | 建议核对 |
| --- | --- |
| 在线 | 继续核对设备业务表现和应用侧请求 |
| 离线 | 检查设备供电、开机状态、信号、天线和 APN |
| 未查询到会话 | 稍后重试,并确认设备是否产生数据会话;不等同于卡已销户 |
| 流量不足或用量异常 | 核对套餐流量、设备上报和业务使用情况 |
| 机卡分离 | 核对卡片与设备的使用关系 |
| 区域受限 | 核对业务限制区域、订购状态和触发状态 |
| 停机或冻结 | 根据页面提示联系业务管理员确认 |
## 当前版本限制
排查页的“重启卡”“开通数据服务”“停用数据服务”按钮只有在登录且账号拥有对应权限时才会出现。业务办理路由、套餐、预警、订单和工单在 `v1.0.1` 中暂未开放。
排查接口或流量接口提示请求过于频繁时,请等待一段时间后再试,不要连续提交。
@@ -0,0 +1,34 @@
# 批次执行日志
批量划拨等任务提交后,可通过“我的”或“办理”中的“批次执行日志”查看执行记录。入口、批次概览和明细分别受权限控制。
## 查询批次
1. 按批次编号搜索,或展开筛选条件。
2. 可按渠道、操作模式、录入方式和执行状态组合筛选;点击刷新重新查询,清除筛选可恢复完整列表。
![批次执行日志中的批次编号搜索、筛选条件和刷新入口](/images/manual/mini-program/execution-log-filters-v102.png)
3. 点击记录查看批次概览和执行明细;需要时刷新详情,或切换为仅查看失败项。
4. 列表支持加载更多。划拨任务可能从划拨页面直接打开关联批次详情。
<div class="manual-image-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/execution-log-load-more.png" alt="批次执行日志列表中的加载更多入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">加载更多执行记录</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/execution-log-allocation-entry.png" alt="卡片划拨批次的查看详情入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">卡片划拨详情入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/execution-log-allocation-details.png" alt="划拨任务概览和逐卡执行明细" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">划拨任务概览与卡片明细</figcaption>
</figure>
</div>
记录会显示批次号、提交渠道、操作类型、录入方式和执行状态。明细权限不足时仍可能看到列表或概览,但无法查看逐卡结果;具体字段按接口权限和返回数据展示。
## 状态未确认
任务处于待处理或处理中时,刷新原批次查看进度。若提交后网络中断且页面提示结果未确认,不要新建相同任务;保留原批次号,先查看日志并核对卡片归属。只有确认任务未受理且不存在待执行任务后,才能按页面提示重试原任务。
+45
View File
@@ -0,0 +1,45 @@
# 常见问题
## v1.0.1 有哪些可用功能?
无需登录即可在首页手动输入、扫码或拍照识别卡号,并查看公开详情和卡片排查结果。已有账号可以使用账号密码登录,查看权限范围内的设备数据。
## 为什么底部没有“办理”Tab?
`v1.0.1` 关闭了办理 Tab。套餐续费、加油包、套餐变更、停复机、预警、订单、工单和客户管理等路由也暂未开放,直接访问会提示当前版本暂未开放此功能。
## 为什么设备页看不到卡片?
设备页读取登录账号的卡片范围。未登录时请回到首页使用公开查卡;登录后仍为空时,请确认账号已分配卡片或具备相应权限。
## 支持哪些查卡方式?
首页支持输入 `ICCID`、`MSISDN`、`IMSI` 或 `IMEI`,也支持扫码读取内容和从相机或相册拍照识别 ICCID。
## 拍照识别失败怎么办?
请拍摄单张、平整、完整、无反光的卡片,并确保 ICCID 清晰。识别超时或频繁请求时等待后重试;也可以改用扫码或手动输入。
## 公开详情为什么字段少?
公开模式只展示卡号、卡状态、在线状态和已用流量。完整 IMSI、IMEI、会话、APN、IP、服务状态等字段属于受限数据。
## 为什么不能绑定卡片或执行重启?
卡片绑定和“更多操作”在 `v1.0.1` 中统一关闭。即使登录,当前版本也不会提供绑定、套餐办理、停复机、数据服务变更和预警配置入口。
## 为什么看不到流量趋势?
流量趋势模块当前只对顶层管理员开放,且接口必须返回有效历史数据。其他账号或没有历史数据时不会显示趋势图。
## 为什么排查显示离线或未查询到会话?
先检查设备供电、开机状态、信号、天线和 APN,再稍后重新排查。“未查询到会话”只表示当前没有查到数据会话,不等同于卡片已销户。
## 账号密码登录有什么限制?
当前版本只开放已有账号的账号密码登录;微信登录和注册账号未开放。忘记密码页面会提示短信服务接入后开放,请联系管理员或客服处理账号问题。
## 排查结果是否是最终结论?
不是。排查结果仅用于辅助判断,最终以运营商和业务系统状态为准。请勿在查询、排查和反馈内容中提交身份证号、银行卡号或其他无关敏感信息。
+67
View File
@@ -0,0 +1,67 @@
# 首页查卡
首页是当前版本的主要使用入口。查询不要求登录,支持手动输入、微信小程序扫码和图片识别三种录入方式。
## 登录入口
首页和登录页都保留“未登录也可查卡”入口。需要查看账号名下设备时,再通过账号密码登录;微信登录和注册账号暂未开放。
## 手动查询
1. 在“卡片查询”输入框输入完整的 `ICCID`、`MSISDN`、`IMSI` 或 `IMEI`。
2. 点击“查询”,等待查询完成。
![点击查询并等待查询完成](/images/manual/mini-program/query-complete.png)
3. 查询成功后,结果区域显示卡号、使用流量和在线状态。
4. 点击“详情”查看公开卡片详情,或点击“卡片排查”查看状态摘要。
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/query-actions-v2.jpg" alt="查询结果中的详情和卡片排查入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">详情与卡片排查入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/card-details-v2.jpg" alt="卡片详情页展示的公开字段" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">卡片详情</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/card-diagnosis-v2.jpg" alt="卡片排查页展示的状态摘要" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">卡片排查</figcaption>
</figure>
</div>
输入内容请去除前后空格并核对末位字符。查询失败时先检查输入值,再重新提交。
## 扫码查询
1. 点击“扫码”。
![首页扫码入口](/images/manual/mini-program/scan-entry-highlight.png)
2. 将卡片条码或二维码放入扫描区域。
3. 扫描成功后,识别出的内容会自动填入输入框并立即发起查询。
光线不足、反光或距离不合适时可能扫描失败;此时请调整卡片位置,或改用手动输入。
## 拍照识别 ICCID
1. 点击“拍照”。
![首页拍照入口](/images/manual/mini-program/photo-entry.png)
2. 从相机或相册选择一张卡片图片。
3. 确保图片中只有一张卡片,并且 ICCID 完整、清晰、无反光。
4. 识别成功后,系统会将 ICCID 填入输入框并继续查询。
常见提示及处理方式:
| 提示 | 处理方式 |
| --- | --- |
| 图片中存在多个可能的 ICCID | 重新拍摄单张卡片 |
| 未能识别清晰的 ICCID | 保持卡片平整、对焦清楚后重拍 |
| 图片识别超时或请求过于频繁 | 等待后重试,或使用扫码、手动输入 |
## 首页结果
首页结果中的“剩余流量”按“剩余 / 套餐总量”展示;在线状态可能为在线、离线、未知或未查询到会话。结果区域的查询和排查说明均提示:内容仅供参考,以业务系统结果为准。
+99
View File
@@ -0,0 +1,99 @@
---
title: 小程序端使用手册
---
# 小程序端使用手册
本文介绍 iotLCM 平台当前已开放的页面及操作功能,具体可用内容以账号实际权限为准。当前版本以**公开查卡和状态排查**为主,业务办理入口暂未开放。
<div class="mini-program-entry">
<figure class="mini-program-entry__qr">
<img src="/images/manual/mini-program/mini-program-code.jpg" alt="扫码进入 iotLCM 小程序">
<figcaption>扫码进入 iotLCM 小程序</figcaption>
</figure>
<p class="mini-program-entry__hint">使用微信扫一扫即可打开小程序,体验公开查卡和卡片状态排查功能。</p>
</div>
## 版本范围
| 页面或功能 | 当前状态 | 说明 |
| --- | --- | --- |
| 首页 | 已开放 | 手动输入、扫码、拍照识别并查询卡片 |
| 卡片详情 | 已开放 | 公开查询只展示卡号、卡状态、在线状态和已用流量 |
| 卡片排查 | 已开放 | 输入卡号查看状态摘要;流量趋势仅对管理员账号显示 |
| 账号密码登录 | 已开放 | 企业、代理商和终端用户使用已有账号登录 |
| 设备列表 | 登录后使用 | 显示当前账号权限范围内的卡片;未登录不要用此页进行公开查卡 |
| 办理、订单、工单、预警、开停数据等 | 暂未开放 | 入口或路由会提示“当前版本暂未开放此功能” |
| 微信登录、注册、绑定卡片、卡片更多操作 | 暂未开放 | 本版本不提供这些入口 |
## 推荐流程
1. 打开小程序首页,在“卡片查询”中输入 ICCID、MSISDN、IMSI 或 IMEI。
![小程序首页卡片查询输入框](/images/manual/mini-program/home-card-query.png)
2. 也可以点击“扫码”读取卡片条码,或点击“拍照”识别卡片上的 ICCID。
![首页扫码和拍照入口](/images/manual/mini-program-scan-photo-entry.png)
3. 点击“查询”,查看卡号、卡状态、使用流量和在线状态。
![点击查询查看卡号、卡状态、使用流量和在线状态](/images/manual/mini-program/query-result.png)
4. 需要查看公开字段时点击“详情”;需要判断异常原因时点击“卡片排查”。
<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/query-actions-v2.jpg" alt="首页查询结果中的详情和卡片排查入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">首页入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/card-details-v2.jpg" alt="卡片详情页展示的公开字段" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">卡片详情</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/card-diagnosis-v2.jpg" alt="卡片排查页展示的状态摘要" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">卡片排查</figcaption>
</figure>
</div>
5. 如果需要停复机、开通数据服务、套餐办理、预警或工单,请等待后续版本开放。
## 账号密码登录
1. 在需要账号数据的页面进入登录页,或在首页点击登录入口。
<div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/login-entry-home.jpg" alt="从首页底部的我的入口进入登录" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">首页“我的”入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/login-page-entry.jpg" alt="登录页中的账号密码登录入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">账号密码登录</figcaption>
</figure>
</div>
2. 输入已有账号和密码,完成页面要求的安全验证后点击“登录”。
![账号密码登录表单](/images/manual/mini-program/account-password-login.png)
3. 如果同一账号匹配多个登录身份,按页面提示选择本次要进入的账号。
4. 登录成功后返回原页面;设备页会按账号权限加载卡片数据。
![登录后设备页按账号权限加载卡片数据](/images/manual/mini-program/devices-page.png)
当前版本不开放注册和微信登录。“忘记密码”页面会提示短信服务接入后开放,无法登录时请联系账号管理员或客服。
## 使用边界
- 公开查询和排查结果仅供辅助判断,最终以运营商和业务系统状态为准。
- 未登录查询不会展示完整 IMSI、IMEI、APN、IP、套餐内部信息等受限字段。
- 请勿在查询、排查或反馈内容中提交身份证号、银行卡号等无关敏感信息;密码只应填写在登录表单中。
## 阅读指引
- [首页查卡、扫码与拍照](./home)
- [卡片详情](./devices)
- [卡片排查](./diagnosis)
- [常见问题](./faq)
@@ -0,0 +1,24 @@
# 消息与个人中心
登录后从底部“我的”进入“消息中心”及账号功能。可见内容按当前账号及接口返回数据决定。
## 消息中心
1. 在“我的”点击“消息中心”,查看流量预警、卡片消息、订单通知和系统通知模块及未读数。平台公告等消息可在实际归属的模块中查看,通常归入“系统通知”。
![从我的页面进入消息中心](/images/manual/mini-program/message-center-entry.png)
2. 进入模块后,可按消息标题或内容搜索,并选择最新优先、最近30天或时间正序;点击刷新重新加载。
3. 点击消息查看详情。进入模块后可将当前模块未读消息全部标记为已读。
4. 工单相关通知可跳转工单详情,但仍需工单详情权限;无效链接或无权限时返回消息页并联系管理员。
“消息提醒”设置入口会转到消息中心。当前版本的流量预警通知通过站内消息查看,不代表会发送微信模板消息或推送。
## 我的
- **业务工作台**:有对应权限时显示工单、客户管理和批次执行日志入口。只有工单提交权限时进入提交页面。
- **个人资料**:终端用户可编辑个人及客户资料并保存;企业、代理及平台账号只能查看资料,资料变更由管理平台维护。账号安全区域可查看手机号、邮箱、密码入口和微信绑定状态。
- **实名认证**:查看实名状态、登记名称、联系电话和客户类型;企业类账号还可查看客户编码及联系地址。该页面用于查看信息。
- **微信绑定**:可在微信小程序中绑定、更换或解除当前微信。解除绑定会移除当前微信与账号的关联;操作前先确认可通过密码或短信验证码登录。微信登录入口在本版本仍未开放。
- **退出登录**:在“我的”页或个人资料账号安全区域点击退出并确认。
帮助与反馈页面当前为静态说明入口,不提供在线提交反馈或工单操作;问题请通过工单服务或页面列出的客服联系渠道处理。
@@ -0,0 +1,56 @@
# 工单服务
登录后可从“办理”或“我的”进入工单服务。卡片详情中的反馈入口可自动带入关联业务并锁定工单类型。列表、提交、详情及处理操作分别受权限控制;只有提交权限的账号仍可直接新建工单。
## 提交工单
1. 选择工单类型:卡片、模组、设备、订单、余额或提现问题。从卡片详情进入时,类型由关联业务决定;订单页面当前受版本开关限制。
<div class="manual-image-grid" style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; width: 100%; max-width: 440px; margin: 24px auto;">
<figure style="margin: 0;">
<img src="/images/manual/mini-program/work-orders-business-entry-v102.png" alt="从办理页面进入工单服务" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">“办理”中的工单服务入口</figcaption>
</figure>
<figure style="margin: 0;">
<img src="/images/manual/mini-program/work-orders-submit-entry-v102.png" alt="工单列表右上角的提交工单入口" style="display: block; width: 100%; height: auto;">
<figcaption style="text-align: center;">工单列表中的提交入口</figcaption>
</figure>
</div>
2. 填写标题、创建人手机号和问题描述。创建人手机号必填,须为有效的11位手机号。
3. 标题最多100字,描述最多2000字。最多添加6个附件,每个附件不超过10 MB。
![提交工单的标题、问题描述和附件填写页面](/images/manual/mini-program/work-order-submit.jpg)
4. 核对内容并提交。有工单列表权限时,可在列表查看状态;仅有提交权限时,工单由服务部门继续处理。
附件上传或提交失败时检查网络、文件大小和附件数量后重试。不要反复提交已经显示成功的工单。
## 查看工单
列表可按“全部相关”“我提交的”“我收到的”查看(是否出现取决于角色),按全部、已分派、处理中、待确认、已完结或已取消筛选;搜索支持工单编号、标题和内容。使用“加载更多”浏览较早记录。
![工单列表中的查看范围、搜索和状态筛选](/images/manual/mini-program/work-order-list-filters.png)
点击有详情权限的工单查看描述、附件、状态流转和沟通记录。沟通详情会自动刷新,也可手动刷新或加载历史记录。附件可在支持的格式和平台中预览或下载;旧附件若未保存原始文件名,显示名称可能来自文件地址。
![工单详情中的基本信息、问题描述、工单沟通和处理进度](/images/manual/mini-program/work-order-details-v102.png)
## 工单跟进
页面按当前工单状态和角色显示可执行操作:
| 操作 | 适用情况 |
| --- | --- |
| 回复 | 创建人或当前处理方在可沟通状态补充文字或附件 |
| 标记解决 | 当前处理方完成处理后通知创建人确认 |
| 转单 | 有转单权限的处理方将工单转交给同租户的顶级部门及其负责人;转单后原处理方可能失去办理权限 |
| 关单 | 当前处理方在已标记解决、等待创建人确认时,可按权限关闭工单 |
| 反馈未解决 | 创建人在待确认时填写未解决原因,工单重新进入处理中 |
| 确认解决 | 创建人确认问题已解决,工单完结 |
| 取消 | 创建人在允许的处理中状态取消工单;历史记录仍可查看 |
| 评价 | 创建人对已完结工单提交一次1至5星评价 |
**工单进入待确认后,如部署并运行了相应的队列或补偿处理服务,72小时未反馈的工单可能自动完结。**
查看权限不代表可以办理其他人的工单。没有按钮或提示无权限时,联系账号管理员确认工单角色权限。
+53
View File
@@ -0,0 +1,53 @@
# API 凭证与执行日志
## 我的 API 凭证
**入口:** 左侧菜单 → **我的 API 凭证**。
![我的 API 凭证页面](/images/manual/api-credentials-entry.png)
页面显示本企业的企业 ID、企业名称、`appKey`(API 账号标识)和 `appSecret`(API 密钥),并提供复制按钮。接口鉴权和请求格式请配合 [OpenAPI 接口文档](/openapi/) 使用。
::: warning appKey ≠ appId
- **`appKey`** 和 **`appSecret`** 仅用于调用 `/openapi/v3/query/token` 换取 token
- **`appId`**(客户组织编码,如 `CUS-202601-0001`)由 token 接口返回,调用其他接口时在请求体中传入
- 不要把 `appKey` 当作 `appId` 使用,两者是不同的概念
:::
### 安全要求
- `appSecret` 等同接口密码,只能提供给获准的开发和运维人员。
- 不要把凭证写入前端代码、公开仓库、工单正文、聊天截图或日志。
- 服务端应通过环境变量或密钥管理服务保存凭证。
- 不同环境不要共用同一份配置文件。
- 怀疑泄露时立即停止相关调用并联系平台支持更换凭证。
复制后粘贴到配置前,检查是否带入多余空格或换行。接口返回鉴权失败时,先核对企业对应的 appId 与 appSecret 是否配套,再检查签名、时间戳和请求地址。
## 执行日志(暂未开放)
**入口:** 左侧菜单 → **执行日志**。
批量续费、套餐变更、加油包、重启、停复机、划拨、达量断网等任务提交后,均应通过执行日志确认最终结果。
### 查询批次
可按关键字、批次编号、批次类型、异步处理状态和创建时间查询。列表显示:
- 批次编号和批次类型。
- 来源方式,如 Excel 或批量输入。
- 运行结果和异步状态。
- 总数量、开始时间和结束时间。
点击批次编号可查看逐卡明细,包括行号、ICCID、MSISDN、IMSI、明细状态和异常原因。点击“查看”可查看批次概要、失败原因、源文件、结果文件和处理时间。
### 处理失败任务
1. 用提交时记录的批次号查询。
2. 确认批次已结束,避免把“处理中”误判为失败。
3. 点击批次号查看失败卡及异常原因。
4. 下载结果文件,修正卡号、状态、套餐或模板数据。
5. 只重提失败且可修正的卡,不要整批重复提交。
6. 无法处理的上游错误通过工单反馈,并附批次号和失败原因。
常见失败原因包括卡号不属于当前企业、卡状态不允许办理、套餐不匹配、余额不足、模板字段错误、上游超时或重复业务仍在处理中。
@@ -0,0 +1,99 @@
# 业务受理与达量断网
## 业务受理入口
左侧菜单 → **通讯管理 → 业务受理**。
![业务受理入口](/images/manual/business-acceptance-entry.png)
页面按“套餐及数据”“生命周期”“预警中心”展示业务入口。每个入口都有独立按钮权限,未授权的入口不会显示。
可能出现的业务包括:
| 分类 | 业务 | 用途 |
| --- | --- | --- |
| 套餐及数据 | 续费套餐 | 延长当前套餐或购买可续费套餐 |
| 套餐及数据 | 变更套餐 | 将卡切换到其他可购套餐 |
| 套餐及数据 | 加油包订购 | 为单卡或流量池购买叠加包 |
| 套餐及数据 | 开停数据 | 开启或关闭数据通信服务 |
| 套餐及数据 | 达量断网 | 设置达到指定流量后断网 |
| 生命周期 | 重启 | 向运营商提交卡重启 |
| 生命周期 | 自主停机/自主复机 | 暂停或恢复卡服务 |
| 生命周期 | 转销户 | 将卡转为销户状态 |
| 生命周期 | 划拨 | 将卡划拨给目标下级企业 |
| 生命周期 | 退订 | 退订符合条件的套餐或业务 |
| 预警中心 | 流量预警配置 | 配置单卡流量阈值预警 |
| 预警中心 | 流量池预警配置 | 配置流量池阈值预警 |
## 通用办理方式(部分开放)
不同业务会提供以下一种或多种数据来源:
- **Excel:** 先下载页面提供的最新模板,按模板填写后上传 `.xls` 或 `.xlsx` 文件。不要修改表头或合并单元格。
- **批量输入:** 通常每行一个 ICCID;部分业务支持每行填写 `ICCID MSISDN IMSI`。
- **连续号段:** 填写 ICCID、MSISDN 或 IMSI 的起始值和结束值,适合连续卡段。
标准操作流程:
1. 选择业务入口和数据来源。
2. 上传文件或输入卡号,提交预处理。
3. 记录系统返回的批次号。
4. 核对有效数量、失败数量、目标套餐、购买时长、生效方式、金额和余额等信息。
5. 确认无误后提交正式任务。
6. 前往“执行日志”查询批次最终状态和失败原因。
::: warning 提交前确认
批量任务可能影响通信、套餐、费用或卡生命周期。提交前应抽查卡号、目标企业、套餐编码、阈值、生效方式和付款方式。不要重复提交仍在处理中的批次。
:::
## 续费、变更与加油包(暂未开放)
### 续费套餐
支持 Excel 或批量输入。预处理后选择可续费套餐、续费年限、续费日期和付款方式。余额支付时必须确认余额充足;线下支付时按页面要求完成线下确认。
### 变更套餐
支持 Excel、批量输入和连续号段。预处理后选择新套餐、购买时长、赠送时长和“立即生效/次月生效”。变更前重点确认生效方式,避免影响当前套餐剩余资源。
### 加油包订购
可选择“单卡加油包”或“流量池加油包”。单卡 Excel 需填写 ICCID 和加油包套餐编码;流量池 Excel 需填写流量池编码和套餐编码。预处理后核对数量、单价、总金额、付款账户和余额。
## 生命周期和数据服务(部分开放)
重启、自主停机、自主复机、开停数据、转销户和退订通常支持 Excel 或批量输入。生命周期任务提交后不会立即在列表全部完成,应通过批次号在执行日志中跟踪。
- 重启适合设备长时间不在线且卡状态正常的场景。
- 自主停机和关闭数据会中断通信,恢复前请确认业务窗口。
- 转销户通常不可逆或需要人工处理,提交前必须复核卡号。
- 划拨前确认目标企业,任务完成后卡的数据归属和可见范围会变化。
## 达量断网批量配置(部分开放)
在“业务受理”点击“达量断网”,可通过 Excel、批量输入或连续号段批量配置:
1. 选择开启或关闭。
2. 开启时填写阈值,单位为 MB,阈值必须大于 0,最多保留 3 位小数。
3. 填写卡号或上传模板后提交。
4. 记录批次号并在执行日志中检查结果。
关闭配置表示不再按该阈值触发断网,不等同于立即恢复所有因其他原因停机的卡。
## 达量断网页面(部分开放)
**入口:** 左侧菜单 → **通讯管理 → 达量断网**。
![达量断网入口](/images/manual/data-usage-cutoff-entry.png)
该页面用于查看和维护已有配置:
- 可按 MSISDN、ICCID、IMSI、IMEI 或配置状态查询。
- 列表显示通道、所属公司、当前套餐、配置状态和阈值。
- 有编辑权限时,可修改开启/关闭状态及阈值。
- 有删除权限时,可删除该卡的达量断网配置。
- 有导出权限时,可按当前条件导出配置。
批量新增或修改建议从“业务受理 → 达量断网”完成;单卡核对和调整可在本页面完成。
![业务受理中的达量断网批量输入](/images/manual/business-operations-batch-threshold.png)
+84
View File
@@ -0,0 +1,84 @@
# 卡信息
## 页面入口
左侧菜单 → **通讯管理 → 卡信息**。
该页面是日常查卡的主要入口,可查询卡的身份标识、运营商状态、套餐、流量、归属企业、通道和到期情况。
![日常查卡页面入口](/images/manual/card-info-main-entry.png)
## 查询卡片
常用查询条件如下:
| 条件 | 用法 |
| --- | --- |
| 卡号 | 输入 MSISDN、ICCID 或 IMSI 任一标识 |
| 卡状态 | 按运营商卡状态筛选 |
| IMEI | 按终端设备号查询 |
| 所属通道 | 按运营商或接入通道筛选,支持搜索 |
| 时间类型、时间范围 | 按所选业务时间区间筛选 |
| 查询条件、起始号段、结束号段 | 选择号段类型,并按起止号段范围筛选卡片 |
| 备注信息 | 按备注内容筛选 |
| 维度、维度状态、维度值 | 按指定业务维度进行组合筛选 |
| 排序值、排序类型 | 指定排序字段及升降序 |
查询不到卡时,先确认使用的是完整卡号,再清空状态、时间和通道等附加条件重试。
## 列表信息
| 字段 | 说明 |
| --- | --- |
| ICCID | SIM 卡芯片的唯一识别码,业务办理和故障定位最常用 |
| MSISDN | 运营商分配的移动号码,也常称卡号 |
| IMSI | SIM 卡在移动网络中的用户识别码 |
| 卡状态 | 运营商侧当前生命周期状态 |
| 类型 | “单卡”表示独立计量,“池卡”表示参与流量池共享 |
| 通道 | 卡接入的运营商或上游通道 |
| 所属公司 | 当前卡归属的企业 |
| 套餐 | 当前主套餐名称 |
| 总流量、已用流量、剩余流量 | 当前套餐流量,单位为 MB |
| 到期时间 | 当前套餐预计到期日期 |
| 出库时间 | 卡片分配出库日期 |
| 备注信息 | 企业维护的业务备注 |
列表中的流量不是实时会话流量。需要最新结果时,执行“同步用量”后再刷新。
## 查看卡详情
点击目标卡右侧“查看”。详情上方展示:MSISDN、ICCID、IMSI、IMEI、卡状态、网络类型、流量同步时间、套餐总流量、到期时间、出库时间、当前套餐、已用流量、剩余流量、所属企业、所属通道、达量断网配置状态、限额、备注。
![在卡信息列表点击“查看”打开卡详情](/images/manual/card-info-view-entry.png)
详情下方包含以下页签:
- **会话信息:** APN、IPv4、IPv6、接入方式、在线状态、会话建立时间、实时 IMEI、开关机状态等。点击“刷新”重新查询;“未查询到会话”通常表示设备当前未建立数据会话,并不等同于卡已销户。
- **归属信息:** 当前套餐编号、套餐名称、所属企业和所属通道(所属通道不予展示)。
- **主套餐:** 订单编号、套餐名称、套餐类型、订购状态、生效时间和到期时间。
- **叠加套餐:** 加油包名称、总流量、支付方式和交易时间。
- **变更历史:** 变更前后套餐、订购状态和办理时间。
![卡详情页签](/images/manual/card-detail-tabs.png)
## 常用操作
页面上的操作按钮以账号权限为准。
- **备注:** 编辑本企业对卡片的业务备注,便于按项目、设备或使用场景检索。
- **同步用量:** 向上游请求最新用量。同步成功后查看“流量同步时间”是否更新。
- **同步状态:** 向上游请求最新运营商卡状态。
- **导出:** 按当前筛选条件导出卡信息。
- **诊断:** 在卡详情点击“诊断”,系统会携带当前 ICCID 跳转到智能诊断并自动查询。
“批量操作”当前仍显示为待接入功能。
![卡信息列表更多操作菜单](/images/manual/card-info-more-actions.png)
## 查看数据异常时
1. 先核对卡号和所属企业。
2. 查看卡状态、套餐到期时间及流量同步时间。
3. 分别执行“同步状态”和“同步用量”。
4. 打开详情刷新会话信息。
5. 进入[智能诊断](./intelligent-diagnosis)做完整排查。
6. 仍有异常时创建工单,并提供 ICCID、异常时间和已完成的操作。
+41
View File
@@ -0,0 +1,41 @@
# 通道与接入管理
本章仅供有接入维护权限的平台管理员使用。企业日常查卡、诊断及业务受理通常不需要修改通道。入口为左侧菜单中的“通道模板”和“通道管理”。
## 通道模板
![通道模板查询入口](/images/manual/channel-template-query.png)
1. 按模板标识、名称、模型或状态查询已有模板,优先使用已确认可用的模板。
2. 新增时填写模板标识、名称、模型、状态及模板配置。
3. 模型包括 `shared_token` 和 `direct`;必须与实际上游接入方式匹配。
4. 模板配置按对应接入实现要求填写有效 JSON,保存后重新查看配置。
模板影响引用它的通道。变更前核对关联通道和兼容性,不在公开手册、截图或工单里展示真实密钥。
## 新增与维护通道
![通道管理中的新增入口](/images/manual/web/channel-add-entry.png)
1. 在“通道管理”新增,填写通道名称、别名、编号、运营商及模板。
2. 按模板要求配置应用标识、密钥、地址等接入信息,不将凭证填入名称或备注。
3. 分别核对通道状态、是否正常、是否轮询、共享 Token 及对接信息;这些配置含义不同,不能互相代替。
4. 保存后打开详情核对,并使用通道测试验证上游返回。
5. 需要时使用“刷新卡数量”,再核对库存、已出库及已销户数量。
未开启共享 Token 的通道修改编号时,系统会提示迁移关联数据并刷新通道实例。确认前核对旧编号、新编号和关联业务,不把它当作仅修改显示名称。已开启共享 Token 的通道不能在页面修改编号。
## 通道测试
1. 在目标通道点击“测试”,选择“单码直测”“单码补全”或“完整三码”。
![通道管理列表中的测试入口](/images/manual/channel-test-entry.png)
2. 单码模式选择 ICCID、IMSI 或 MSISDN 并输入对应值;“单码直测”直接使用所填号码调用查询接口,“单码补全”会先查询完整卡信息再测试。“完整三码”需填写同一张卡的 ICCID、IMSI 和 MSISDN。部分电信接口只能按 MSISDN 查询,测试前应选用适合该通道的模式。
![通道测试中的单码直测、单码补全和完整三码模式](/images/manual/web/channel-test-modes.jpg)
3. 选择全部测试项或单项测试。测试项包括基础信息、实时会话、卡状态、停机原因、冻结、机卡绑定、设备、区域限制、IMEI、流量及 APN 等。
4. 执行后逐项查看成功、失败或超时结果。需要重试时先核对失败原因,只重试相关项目。
某一查询成功不代表所有上游能力均已支持。测试失败时核对模板、凭证、请求地址、上游授权和卡片所属通道,避免高频重复请求。
@@ -0,0 +1,102 @@
# 企业、用户与角色
## 企业信息
**入口:** 左侧菜单 → **企业管理 → 企业信息**。
![企业信息页面入口](/images/manual/enterprise-information-entry.png)
该页面用于维护当前企业资料,分为单位信息和实名认证信息。
单位信息通常包括联系电话、邮箱、统一社会信用代码、组织所在地和详细地址。实名认证信息包括单位全称、行业、法人姓名、法人身份证号、单位类型、成立时间、开户行名称和开户行账号。
单位信息如需修改请联系管理员。
## 角色管理
**入口:** 左侧菜单 → **企业管理 → 角色管理**。
![角色管理页面入口](/images/manual/role-management-entry.png)
角色用于把菜单和按钮权限批量授予用户。列表可按角色名称、权限字符和状态查询。
### 创建角色
1. 点击“新增”。
![创建角色时点击新增](/images/manual/role-create-add-button.png)
2. 填写角色名称和角色权限字符。
3. 设置角色状态。
4. 在“菜单权限”中勾选该角色需要使用的页面和操作。
5. 需要传递给直属下级企业时,按业务要求设置“下发给下一级”。
![设置下发给下一级](/images/manual/role-management-distribute-to-child.png)
6. 保存后回到“用户管理”,将角色分配给用户。
![在用户管理中分配角色](/images/manual/assign-role-to-user.png)
本级机构绑定的默认角色不能修改或删除。新增自定义角色时遵循最小权限原则,不要为普通查询人员授予企业删除、用户删除、角色修改或批量业务提交权限。
## 企业客户
**入口:** 左侧菜单 → **企业管理 → 企业客户**。
![企业客户页面入口](/images/manual/enterprise-customer-entry.jpg)
该页面面向获准管理下级企业的客户。可按企业名称和状态查询,列表显示企业名称、登录账号、负责人、联系电话、邮箱、组织所在地、详细地址、创建时间和状态。
根据按钮权限,可执行:
- **新增:** 创建直属企业客户之前,需在角色管理界面创建该企业客户同名角色,之后返回新增页面设置登录账号、初始密码、联系方式和企业地址等信息。
![角色管理中创建企业客户同名角色](/images/manual/enterprise-customer-role-match.png)
![新增企业客户页面](/images/manual/enterprise-customer-create-form.jpg)
- **修改:** 调整企业名称、登录账号、负责人。
![修改企业客户信息](/images/manual/enterprise-customer-edit.png)
- **重置密码:** 为该企业主账号设置新密码,密码长度为 5 至 20 位。
![企业客户重置密码](/images/manual/enterprise-customer-reset-password.png)
- **企业管理:** 分两步维护单位信息和实名认证信息。
![维护企业单位信息](/images/manual/enterprise-customer-manage.png)
- **删除:** 删除前确认该企业没有仍在使用的卡、用户和业务数据。删除属于高风险操作。
![企业客户删除确认](/images/manual/enterprise-customer-delete-confirm.png)
新增企业后,应及时通知对方修改初始密码,并按需要创建角色和用户。
## 用户管理
**入口:** 左侧菜单 → **企业管理 → 用户管理**。
![用户管理入口](/images/manual/user-management-entry.png)
可按用户名称、昵称、联系电话、状态和创建时间查询。列表显示用户名称、昵称、角色、联系电话、邮箱、状态和创建时间。
### 新增用户
1. 点击“新增”。
2. 填写用户名称、初始密码、昵称、联系电话、邮箱、性别和备注。
3. 设置账号状态。
4. 至少选择一个角色后保存。
初始密码长度为 5 至 20 位。建议使用更长且不重复的密码,并要求用户首次登录后修改。
![新增用户页面](/images/manual/user-create-form.png)
### 维护用户
- “修改”可调整资料、状态和角色。
- “重置密码”可设置新密码。
- “删除”会使用户失去登录和数据访问能力,操作前确认账号不再使用。
![维护用户信息](/images/manual/user-management-maintain-user.png)
## 建议的角色划分
| 角色 | 建议权限 |
| --- | --- |
| 查询人员 | 卡信息查看、智能诊断,只读查看执行日志 |
| 业务操作员 | 查询权限,以及已批准的业务受理按钮 |
| 工单人员 | 我的工单、沟通、工单记录 |
| 接口开发 | 我的 API 凭证,只读卡信息;凭证按需单独授权 |
| 企业管理员 | 用户、角色和企业资料管理;高风险权限严格控制 |
+57
View File
@@ -0,0 +1,57 @@
# 常见问题
## 登录后看不到某个菜单
平台按角色显示菜单。先联系本企业管理员检查用户是否绑定正确角色、角色是否正常以及菜单权限是否勾选。若整个企业都没有该功能,需要联系平台支持开通。
## 同事有按钮,我没有
页面权限和按钮权限分别控制。即使都能进入同一页面,新增、编辑、导出、同步、删除或业务提交按钮也可能不同。由企业管理员对比双方角色权限。
## 卡信息查询不到卡
1. 清空其他筛选条件。
2. 使用完整 MSISDN、ICCID 或 IMSI 查询。
3. 检查是否有前后空格或字符缺失。
4. 确认该卡已划拨到当前企业,且账号有相应数据范围。
5. 仍无结果时创建工单并提供卡号。
## 卡状态或流量没有更新
在“卡信息”中分别执行“同步状态”和“同步用量”,完成后刷新列表,并查看同步时间是否变化。运营商接口可能有延迟;短时间内不要反复同步。
## 智能诊断显示未查询到会话
表示当前没有查到数据会话,不一定是卡异常。检查设备供电、信号、APN 和天线,确认数据服务已开启后重启设备,再稍后诊断。
## 诊断提示运营商不支持
当前卡所属通道未提供该项实时查询能力。结合卡信息、同步时间、设备侧日志和其他诊断模块判断;需要运营商侧核实时提交工单。
## 批量业务提交后一直没有结果
记录批次号,进入“执行日志”查看异步状态。仍在处理中时请等待;已失败时查看批次明细和失败原因。不要在结果未明确前重复提交同一批卡。
## Excel 上传失败
- 使用页面当次下载的最新模板。
- 文件格式必须为 `.xls` 或 `.xlsx`,且不超过页面限制。
- 不修改表头、不合并单元格、不增加公式。
- 卡号列按文本格式保存,避免科学计数法或末位被修改。
- 根据失败提示修正后,仅重提失败数据。
## 达量断网开启后没有立即断网
确认阈值单位为 MB、配置任务已成功、卡用量已同步且确实达到阈值。运营商侧执行可能有延迟;同时检查卡是否受其他套餐或通道策略影响。
## 如何提交高质量工单
提供完整 ICCID、MSISDN、故障开始时间、影响数量、设备位置、复现步骤、诊断结果、执行日志批次号和必要截图。描述中避免使用“卡有问题”这类无法定位的表述,也不要发送密码或 API 凭证。
## 使用安全建议
- 为每位操作人员创建独立账号,不共享管理员账号。
- 查询人员、业务操作员和管理员使用不同角色。
- 定期停用离职或不再使用的账号。
- 高风险批量任务实行复核,保留批次号和结果文件。
- 导出的卡信息、企业资料和 API 凭证按敏感数据管理。
+52
View File
@@ -0,0 +1,52 @@
# 首页与通用操作
## 首页(暂未开放)
**入口:** 登录后默认进入“首页”;如已授权,也可点击左侧“首页”。
首页用于快速掌握企业卡业务概况,通常包括:
- SIM 卡总数、本月新增、本月数据用量、本月短信用量和 SIM 卡月活跃度。
- 生命周期分布、流量池、物联卡位置分布等图表。
- 欠费停机、主动停机、机卡分离停机、强制停机等业务预警。
- 流量用尽关停、限额管控关停、封顶关停和套餐余量提醒等流量预警。
- 卡信息、智能诊断等快捷入口。
首页数据用于概览和发现异常。处理单张卡时,应进入“卡信息”查看最新详情;预警统计与明细可能存在同步时间差。
## 列表页通用操作
### 查询与重置
1. 在查询区填写一个或多个条件。
2. 点击“查询”;部分页面修改条件后会自动查询。
3. 条件过多导致无结果时,点击“重置”后只保留卡号、批次号等关键条件重试。
卡号输入框通常支持 MSISDN、ICCID、IMSI。输入前后不要带空格;批量输入时一般每行一张卡。
![卡号查询](/images/manual/card-info-query-single.jpg)
![按号段批量查询](/images/manual/card-info-query-batch.jpg)
### 表格工具
列表右上角可能提供刷新、列设置、密度或全屏等工具。横向字段较多时可左右滚动,固定在右侧的“操作”列用于查看或办理业务。
![表格工具](/images/manual/table-tools.png)
### 导出(暂未开放)
“导出”通常按当前查询条件导出,而不是只导出当前页。建议先缩小查询范围再导出,并妥善保管包含卡号、设备号和企业信息的文件。
### 状态与数据时间
卡状态、流量、会话和执行结果来自不同上游接口,更新时间可能不同。判断数据是否最新时,优先查看“同步时间”“开始时间”“结束时间”等时间字段,不要只看页面打开时间。
## 权限与数据范围
- 菜单权限决定页面是否显示。
- 按钮权限决定是否能新增、编辑、导出、同步或删除。
- 数据权限决定能看到本企业、下级企业还是仅本人数据。
- 同一企业内不同用户看到的菜单和数据可能不同。
企业管理员可通过“企业管理 → 用户管理”和“角色管理”调整本企业用户权限。涉及平台未向企业开放的功能时,需要联系平台支持。
+90
View File
@@ -0,0 +1,90 @@
# Web 端使用手册
本手册面向 iotLCM Web 管理平台的企业客户,说明登录平台后各页面的入口、信息含义和常用操作。
::: info 权限说明
平台按企业、角色和用户分配权限。左侧菜单、页面按钮可能因企业实际授权不同而不同;看不到某项功能时,先联系本企业管理员确认角色权限。按钮以页面实际显示为准。
:::
## 登录平台
![iotLCM 管理平台登录页面](/images/manual/login-page.png)
1. 访问 [iotLCM 管理平台](https://admin.example.com/)。
2. 输入平台分配的账号和密码登录。
3. 首次登录后建议立即修改初始密码,并确认绑定的手机号、邮箱是否正确。
4. 登录后通过左侧菜单进入业务页面;顶部标签可在已打开页面间切换。
若提示账号停用、密码错误或无可用菜单,请联系本企业管理员;企业管理员无法处理时,可通过工单联系平台支持。
## 功能地图(部分开放)
| 菜单 | 页面 | 主要用途 |
| --- | --- | --- |
| 顶级菜单 | 首页 | 查看卡量、用量、生命周期和预警概览 |
| 通讯管理 | 业务受理 | 批量办理套餐、数据服务、生命周期和预警业务 |
| 通讯管理 | 卡信息 | 查询卡、查看套餐和会话、同步状态与用量 |
| 通讯管理 | 达量断网 | 查询、编辑和导出单卡达量断网配置 |
| 通讯管理 | 智能诊断 | 按 MSISDN、ICCID 或 IMSI 一键排查卡和网络状态 |
| 企业管理 | 企业信息 | 维护本企业基础资料和实名认证资料 |
| 企业管理 | 用户管理 | 管理本企业用户及其角色 |
| 企业管理 | 角色管理 | 配置角色菜单权限和状态 |
| 企业管理 | 企业客户 | 管理直属企业客户、登录账号和企业资料 |
| 工单管理 | 我的工单 | 创建工单、沟通、跟踪处理和评价 |
| 工单管理 | 工单记录 | 查询工单的状态变更与操作轨迹 |
| 顶级菜单 | 我的 API 凭证 | 查看并复制本企业接口凭证 |
| 顶级菜单 | 执行日志 | 跟踪批量任务结果及逐卡失败原因 |
“工单问题沟通”是从“我的工单”进入的详情页面,不在左侧单独显示。
## 推荐使用流程
### 日常查卡
1. 进入“通讯管理 → 卡信息”。
2. 用 MSISDN、ICCID、IMSI 或 IMEI 查询目标卡。
3. 打开“查看”,核对卡状态、套餐、用量、到期时间和同步时间。
4. 数据较旧时使用“同步用量”或“同步状态”,再刷新查看。
![卡信息同步操作示意图](/images/manual/card-info-sync-actions.png)
### 排查不能联网
1. 从卡详情点击“诊断”,或进入“通讯管理 → 智能诊断”。
![从卡详情进入智能诊断](/images/manual/card-detail-diagnosis-entry-new.png)
![从通讯管理进入智能诊断](/images/manual/intelligent-diagnosis-entry-new.jpg)
2. 输入卡号并执行“一键诊断”。
![输入卡号并执行一键诊断](/images/manual/intelligent-diagnosis-submit.jpg)
3. 依次查看 SIM 卡状态、通信服务、实时流量、策略限制和网络连接。
![智能诊断结果页面](/images/manual/intelligent-diagnosis-result.png)
4. 仍无法定位时创建工单,并附上卡号、故障时间、诊断结果和终端现象。
![进入我的工单并创建工单](/images/manual/work-order-create-entry-new.jpg)
![填写创建工单信息](/images/manual/work-order-create-form-new.jpg)
### 办理批量业务(暂未开放)
1. 进入“通讯管理 → 业务受理”,选择已授权的业务。
2. 下载最新 Excel 模板,或使用批量输入、连续号段方式提交卡号。
3. 对需要预处理的业务先核对有效数量、失败项、套餐、价格和生效方式。
4. 确认提交后记录批次号。
5. 进入“执行日志”按批次号查看最终结果。
## 阅读指引
- [首页与通用操作](./home):了解查询、重置、导出和数据刷新规则。
- [卡信息](./card-info):了解卡列表字段、卡详情和会话信息。
- [智能诊断](./intelligent-diagnosis):了解入口、诊断步骤和结果判断。
- [业务受理与达量断网](./business-operations):了解批量业务及达量断网配置。
- [企业、用户与角色](./enterprise-management):了解组织和账号权限管理。
- [工单管理](./work-orders):了解问题提交、沟通、关闭和评价。
- [API 凭证与执行日志](./api-and-logs):了解凭证保管和批次追踪。
- [常见问题](./faq):快速排查菜单、数据、任务和诊断问题。
@@ -0,0 +1,71 @@
# 智能诊断
## 入口
有两种进入方式:
1. 左侧菜单 → **通讯管理 → 智能诊断**。
![从左侧菜单进入智能诊断](/images/manual/intelligent-diagnosis-menu.png)
2. 左侧菜单 → **通讯管理 → 卡信息** → 找到目标卡 → “查看” → “诊断”。第二种方式会自动带入 ICCID 并开始查询。
![从卡详情进入智能诊断](/images/manual/card-detail-diagnosis-entry-attachment.png)
## 开始诊断
1. 输入 MSISDN、ICCID 或 IMSI 任一完整标识。
2. 点击“一键诊断”。
3. 等待页面提示“诊断完成”。不同上游能力返回速度可能不同,请勿连续重复点击。
4. 展开各诊断模块查看状态、详细字段和提示。
诊断完成后,页面顶部显示 MSISDN、ICCID、IMSI 和实时 IMEI;下方同时查询近 7 天、近 30 天和近 6 个月的流量趋势。
![智能诊断输入卡号并执行一键诊断](/images/manual/intelligent-diagnosis-submit-button.png)
## 诊断结果
![智能诊断结果总览](/images/manual/intelligent-diagnosis-result-attachment.png)
### SIM 卡状态
关注卡状态、停机原因、是否冻结、冻结原因和冻结操作时间。若卡处于停机、冻结、待激活或销户状态,应先处理卡生命周期问题,再排查终端网络。
### 通信服务
关注 APN、IPv4、IPv6、通信服务状态、在线状态、会话状态、会话建立时间、接入方式和实时 IMEI。
- 通信服务关闭时,需先办理开通或复机。
- 没有会话时,检查设备是否开机、天线和信号是否正常、APN 是否正确。
- 实时 IMEI 与预期设备不一致时,确认是否换机、换卡或发生机卡分离。
### 实时流量
展示已用流量、剩余流量和同步时间。历史趋势用于判断设备是否持续上报、是否出现突增或长时间无流量。
### 策略限制
展示区域限制订购、触发状态、限制区域和人联网访问限制等。卡和设备均正常但仍无法联网时,应重点检查此模块。
### 网络连接
展示开关机状态、设备状态和机卡绑定状态。出现关机、设备异常或机卡分离时,优先在终端侧核查供电、信号、插卡和绑定关系。
## 推荐排查顺序
1. **卡是否可用:** 卡状态、冻结和停机原因。
2. **服务是否开启:** 数据通信服务和套餐是否有效。
3. **流量是否充足:** 剩余流量、达量限制和到期时间。
4. **策略是否限制:** 区域、人联网及其他策略。
5. **设备是否在线:** 开关机、设备状态、会话和实时 IMEI。
6. **趋势是否异常:** 对比近 7 天、30 天、6 个月流量。
## 结果提示说明
- **查询成功但未返回数值:** 上游已响应,但本次没有可展示的数据;结合同步时间和其他模块判断。
- **未查询到会话:** 当前没有在线数据会话,可稍后重试并检查终端。
- **运营商暂不支持/不支持查询:** 当前通道没有对应实时查询能力,不代表卡一定异常。
- **查询暂时未完成:** 可能是上游忙、缓存或接口超时,稍后重试。
- **系统无记录:** 先检查卡号是否完整以及卡是否归属当前企业。
需要提交工单时,建议附上诊断时间、完整 ICCID、异常模块、终端所在地、设备指示灯或日志,以及问题首次出现时间。
+65
View File
@@ -0,0 +1,65 @@
# 入库、库存与出库
本章供获授权的库存及业务管理员使用。在左侧菜单查找“入库管理”“库存卡信息”“批次管理”和“销户卡信息”。这些操作可能改变卡片归属、套餐和订单,请先确认目标客户与卡片范围。
## 入库
1. 进入“入库管理”,先查询既有批次,确认卡片未重复录入。
![入库管理入口与批次编号查询](/images/manual/inventory-inbound-batch-query.png)
2. 选择 Excel、批量输入或连续号段入口;Excel 使用页面下载的当前模板,输入方式按页面要求填写卡标识。
![入库管理中的Excel文件、连号和批量输入入口](/images/manual/web/inventory-inbound-methods.png)
3. 填写通道、上游套餐、卡类型、协议到期等表单要求的信息;按需选择是否校验上游基础信息。
4. 核对通道及数量后提交,保存返回的批次号。
5. 在执行日志查看处理结果,在库存卡信息中抽查成功卡片;失败卡按原因修正后再处理。
入库只是将卡纳入库存管理,不等于已销售给客户,也不表示已开通通信或订购套餐。
## 查询库存
按 ICCID、MSISDN、IMSI、通道、上游套餐、卡类型、协议到期区间及创建时间查询。优先检查入库批次号、协议到期时间和通道是否正确,再决定出库范围。
号段查询与输入须选择正确的号码类型,卡号应保留完整字符。不要在 Excel 中把长卡号转换成科学计数法或丢掉前导零。
![库存卡信息入口与查询条件](/images/manual/inventory-card-query-filters.png)
## 出库
库存页面提供批量输入、号段及按批次出库等入口,具体以权限为准。按批次出库按“选择批次 → 确认卡 → 填写信息”完成。
![库存卡信息中的出库管理入口及出库方式](/images/manual/inventory-outbound-entry.png)
1. 选择要出库的库存批次或卡片,核对数量及每张卡的状态。
2. 选择客户/机构与业务套餐,填写套餐开始时间、购买时长和赠送时长,检查预计结束日期。
3. 核对订单金额、备注和付款安排。正常余额方式会校验客户余额账户存在、状态正常且余额充足。
4. 若线下合同、销售及走账确已完成,才按实际业务勾选“线下合同/销售/走账已完成”。不要用该选项绕过未完成的付款处理。
5. 确认后提交,记录批次号和相关订单信息。
6. 在执行日志核对结果,成功后到“卡信息”确认客户归属、套餐和到期时间,并在订单页面核对交易记录。
余额不足或账户异常时先联系财务及管理员处理,不要多次提交相同卡片。提交超时也应先查原批次。
## 批次及销户记录
![批次管理入口及批次查询页面](/images/manual/inventory-batch-management.png)
- “批次管理”用于查询批次信息,并通过页面入口查看批次卡片;实际执行状态仍需对照执行日志。
![销户卡信息查询与导出页面](/images/manual/web/cancelled-card-info.png)
- “销户卡信息”用于查询已进入销户记录的卡片,有权限时可导出。
![业务受理中的转销户入口](/images/manual/web/business-cancellation-entry.png)
- 申请转销户从[业务受理](./business-operations)进入,不能把查询销户记录视为恢复服务入口。
## 常见问题
| 现象 | 核对方向 |
| --- | --- |
| 入库失败或重复 | 原批次结果、卡号格式、通道及库存已有记录 |
| 库存中找不到卡 | 是否已出库、查询条件及账号数据权限 |
| 出库不能提交 | 必填项、客户/套餐选择、金额及余额账户状态 |
| 出库后归属或套餐不符 | 对照原批次与订单,不要另建任务覆盖问题,联系管理员核实 |
@@ -0,0 +1,34 @@
# 套餐配置与定价
本章面向获授权的产品、定价及企业管理人员。请在左侧菜单查找同名页面;实际菜单分组以部署配置为准。普通查卡账号不需要维护这些配置。
## 套餐配置顺序
套餐分类和上游套餐用于组织产品及上游资源;流量套餐定义对客户提供的产品;定价用于确定不同客户层级的价格。维护产品后,还要配置允许续费、变更和加油包的关系,业务受理页才可能出现相应选项。
1. 在“套餐分类”查询或新增分类,填写表单必填项并保存。
![在套餐分类页面点击新增并填写分类编号、分类名称等必填项](/images/manual/package-category-create-form.png)
2. 在“上游套餐”查询对应上游产品,核对编码、名称及关联信息;需要新增或修改时由掌握上游资料的管理员操作。
3. 在“流量套餐信息”新增产品,选择单卡套餐、单卡加油包、池卡套餐或池卡加油包。
![在流量套餐信息页面点击新增,选择单卡套餐、单卡加油包、池卡套餐或池卡加油包](/images/manual/flow-package-add-product.png)
4. 填写套餐名称、编码、计费方式、分类、流量(MB)、启用状态及价格相关字段。
![新增单卡套餐表单:必填项为套餐名称、套餐编码、计费方式、套餐分类、套餐流量,价格信息包含目录售价、成本价与利润系数](/images/manual/flow-package-product-form.png)
5. 设置允许的续费时长、折扣、可续费套餐、可变更套餐和可订购加油包;需要续费本套餐时检查对应开关。
6. 保存后重新查询并查看详情,确认产品类型和关联编码正确,再供业务人员使用。
可配置自动续费套餐和年限时,应同时核对自动续费开关。保存配置仅代表规则已保存,实际自动处理还依赖部署的服务与任务运行情况。
## 常见问题
| 问题 | 核对方向 |
| --- | --- |
| 新建套餐后业务页不可选 | 启用状态、套餐类型、当前套餐的允许办理关系及客户价格 |
| 调价后显示价格没有变化 | 当前选中的机构层级、价格来源及是否存在客户独立覆盖 |
| 流量池用量与单卡合计暂不一致 | 数据同步时间、统计范围及卡片归属,先刷新后再比较 |
| 无维护按钮 | 产品、定价、查询和导出可能分别授权,请联系管理员 |
+55
View File
@@ -0,0 +1,55 @@
# 预警管理
**适用人员:** 获授权的企业管理员和业务人员。左侧菜单查找预警中心-卡配置-新增预警或预警中心-流量池配置-新增预警;批量设置也可从“通讯管理 → 业务受理 → 预警中心”进入。
预警用于在达到阈值时提醒;[达量断网](./business-operations)用于限制通信,两者需要分别核对。保存配置成功不代表已经发送通知或执行断网。
## 新增或编辑配置
1. 选择单卡或流量池配置页面,按对象编码及状态查询,避免重复维护。
![卡预警配置页面按 ICCID 和状态查询](/images/manual/card-warning-config-search.png)
2. 点击“新增”,或对已有记录点击“修改”。
![新增单卡预警配置](/images/manual/card-warning-add-form.png)
3. 填写对应卡号或流量池编号。单卡使用流量阈值(MB),流量池使用百分比;不要混用单位。
4. 填写第一次提醒阈值,按需设置第二、第三次提醒,按表单与服务端提示校验各档数值。
5. 核对提醒方式。站内提醒为基础方式;选择邮箱或短信时,填写相应接收地址或手机号。
6. 设置启用状态和备注,保存后重新查询并查看详情。
接收人填写错误、短信或邮件服务未配置,都可能导致外部通知无法送达。不要将配置存在等同于通知已送达;必要时请管理员核查通知记录与服务状态。
## 启停、删除与批量配置
- 列表可以按权限启用、停用、查看、修改或删除规则;删除前保留必要业务记录。
![单卡预警规则列表中的查看、修改、停用和删除操作](/images/manual/card-warning-list-actions.png)
- 停用规则表示不再按该规则继续触发,不会撤销已执行的通信操作。
- 批量配置从业务受理选择单卡或流量池预警。单卡提供 Excel、输入及号段方式;流量池不支持连续号段。Excel 使用当前模板填写。
- 提交后记录批次号,前往[执行日志](./api-and-logs)核对结果,再抽查配置页面。
## 未收到提醒时
依次检查对象编码、单位、阈值、启用状态、用量同步时间和接收人;仍有异常时提供对象编码、配置时间、预期触发档位和批次号创建工单。提交工单时不要附带消息服务密钥。
## 告警与通知记录
预警是否真的发出了提醒,要通过发送记录核对,不能只看配置是否存在。
| 页面 | 主要字段 | 用途 |
| --- | --- | --- |
| 消息日志 | 消息类型(系统通知、活动通知、订单通知、账户通知)、接收用户、消息标题、消息内容、跳转链接、发送时间、是否撤回 | 核对站内消息是否写入、发给了哪些用户、是否被撤回 |
| 通知日志 | 通知标题、通知内容、发送用户类型(代理商/终端用户)、发送时间 | 核对面向客户的通知记录 |
- 两个页面都支持按标题和发送时间查询,有权限时可导出当前查询结果。
- 当前版本没有独立的“告警记录”页面。判断一次预警是否触发,要结合预警配置、消息日志和通知日志一起看:配置决定阈值和接收人,日志决定有没有发出去。
- 日志中出现记录,说明消息已写入并进入发送流程;短信、邮件是否送达仍取决于部署的消息服务和登记的联系方式,这两个页面不能替代短信服务商的发送回执。
## 套餐到期提醒
套餐到期提醒与上面的流量阈值配置不同:本地服务按有效单卡套餐的到期时间自动扫描,默认在到期前 30 天、15 天、7 天分档判断。阈值由服务端配置决定,当前 Web 的“卡预警配置”表单只提供流量阈值(MB),不提供到期天数设置。
收到提醒后,先在[卡信息](./card-info)核对到期日期、套餐和归属企业,再决定是否办理续费。站内消息会按符合条件的账号写入消息中心;短信、邮件是否发送取决于已登记的联系方式和部署的消息服务。未收到时,请管理员检查通知或消息日志及后台任务,不要只根据首页的固定预警面板判断。
+61
View File
@@ -0,0 +1,61 @@
# 工单管理
## 我的工单
**入口:** 左侧菜单 → **工单管理 → 我的工单**。
![我的工单页面入口](/images/manual/work-orders-entry.png)
可按工单编号、工单类型和当前处理状态查询。列表显示工单编号、类型、标题、关联业务号、创建人、当前状态、首次响应时间和创建时间。
常见状态包括已分派、处理中、已解决和已完结;取消后的工单也会保留记录。
## 创建工单
1. 点击“创建工单”。
2. 选择工单类型,如卡问题、套餐问题、订单问题、系统问题或其他问题。
3. 填写关联业务 ID。卡问题建议填写完整 ICCID,并在问题描述中补充 MSISDN。
4. 填写清晰的工单标题和问题描述。
5. 按需上传截图、日志或结果文件。
6. 核对创建人信息后提交。
问题描述建议包含:发生时间、影响范围、卡号、设备所在地、预期结果、实际现象、已执行的同步或诊断操作。不要在附件中上传 API 密钥、账号密码等敏感信息。
![创建工单表单](/images/manual/work-order-create-form-attachment.png)
## 沟通与处理
点击工单右侧“沟通”;工单已结束时按钮显示为“查看”。工单问题沟通页会显示历史消息、附件、工单信息和处理轨迹。
![工单沟通入口](/images/manual/work-order-communication-entry.png)
- 在输入区填写回复内容,或按权限上传附件后发送。
- 平台回复后可继续补充信息,尽量在同一工单中保持上下文完整。
- 有转单权限时,可把工单转交其他处理方。
- 问题确认解决后可执行“解决”;不再需要处理时可“取消”。
- 工单完成后可“关闭”,并按 1 至 5 分提交满意度和评价内容。
![工单沟通与处理操作](/images/manual/work-orders-communication.png)
![已关闭工单的服务评价入口](/images/manual/work-orders-closed-evaluation.png)
按钮是否出现取决于工单当前状态和用户权限。已完结或已取消的工单通常只能查看。
## 工单记录
**入口:** 左侧菜单 → **工单管理 → 工单记录**。
![工单记录页面入口](/images/manual/work-orders-records-entry.png)
该页面用于审计工单流转,可按工单编号和操作类型查询。主要字段包括操作类型、操作机构、操作用户、目标部门、目标用户、变更前状态和变更后状态。
常见操作类型为创建、分派、处理、完成、关单和取消。需要核对“谁在何时改变了工单状态”时,以此页面记录为准;如已授权,可查看详情或导出当前查询结果。
## 何时新建工单
- 同一张卡完成状态同步和智能诊断后仍无法联网。
- 批量任务在执行日志中失败,错误原因无法自行处理。
- 套餐、费用或归属与实际业务不符。
- 页面持续报错,且刷新或重新登录后仍可复现。
同一问题不要重复创建多个工单。补充信息应在原工单的沟通页继续发送。
+23
View File
@@ -0,0 +1,23 @@
---
layout: home
hero:
name: iotLCM OpenAPI
text: 接口文档
tagline: 对接说明:鉴权方式、请求参数、响应结构与示例。
actions:
- theme: brand
text: V3 接口(推荐)
link: /openapi/v3/
- theme: alt
text: 旧版兼容接口
link: /openapi/legacy/
features:
- title: 版本选型指引
details: 新接入推荐使用 V3。V3 接口位于 /openapi/v3/query,通过 X-Access-Token 鉴权,调用时需在请求体中传入 token 接口返回的 appId(⚠️ 注意区分:appKey 用于获取 token,appId 由 token 接口返回并在后续调用中传入),数据权限按账号所属客户隔离;旧版兼容接口位于 /route/open,通过请求 body verify 入参鉴权,仅作为存量客户兼容保留。
- title: V3 接口
details: 鉴权、获取客户调用凭证、查询 SIM 卡信息、查询 SIM 流量限制、查询 SIM IMEI(单卡 / 批量)、查询 SIM 流量用量。
- title: 旧版兼容接口
details: 查询 SIM 详情、查询 SIM 账期用量、查询 SIM 历史月用量、查询 SIM 状态、查询 SIM 在线会话、查询卡列表信息。
---
+133
View File
@@ -0,0 +1,133 @@
---
title: 概览与鉴权
description: iotLCM 旧版兼容接口概览与鉴权说明
outline: deep
---
# 概览与鉴权
本文档收录历史 `/route/open/*` 路径下仍在维护的兼容接口。鉴权通过请求体 `verify` 中的 `appId`、`password` 和 `timeStamp` 完成;`sign` 为历史兼容字段,当前服务不校验。
这些接口由 iotLCM OpenAPI 服务实现并提供,与 V3 接口同域(`api.example.com`),仅请求路径与鉴权方式不同。
## 服务地址
```text
https://api.example.com
```
完整请求 URL:
```text
https://api.example.com/route/open/{接口名}
```
## 统一鉴权
以下各接口的“请求示例”均为完整 JSON 请求体,调用时请设置 `Content-Type: application/json`;`verify.sign` 可传空字符串 `""`。
所有接口通过请求体 `verify` 字段鉴权:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| appId | string | 用于唯一标识请求 |
| password | string | 密码 |
| timeStamp | string | 时间戳,`"" + System.currentTimeMillis()` |
| sign | string | 当前服务不校验,可传空字符串 `""`。 |
### 历史签名规则(仅供参考)
当前服务不要求计算签名,也无需提供 `accessKey`。以下为历史客户端示例中的签名算法,仅供维护已有签名逻辑时参考。
```text
appId={appId}&password={password}&timeStamp={timeStamp}&key={accessKey}
```
对拼接后的字符串计算 MD5,并将结果转为大写,即为 `sign`。`sign` 不参与签名计算;`accessKey` 仅用于签名,不放入请求体的 `verify` 字段。
## 统一返回结构
```json
{
"status": 0,
"message": "",
"result": {}
}
```
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| status | number | 业务状态码,成功为 `0` |
| message | string | 错误或提示信息 |
| result | any | 业务数据 |
## 公共字典
卡状态编码(适用于 `cardInfo`、`simStatus`、`queryCardInfoList` 响应中的 `statusId` / `statusCode` / `status`):
| 编码 | 说明 |
| --- | --- |
| 1 | 库存 |
| 2 | 可测试 |
| 3 | 待激活 |
| 4 | 已激活 |
| 5 | 已停机 |
| 6 | 预销户 |
| 7 | 已销户 |
| 8 | 未知 |
## 请求案例
以下为历史 Java 示例,保留原有 `verify` 块与 `sign` 的生成方式供参考。调用当前服务时,可直接设置 `verify.sign` 为 `""`,省略签名计算。
```java
// 服务器请求地址
protected static String server_Ip = "https://api.example.com/route/open";
// appId
public static String appId = null;
// 密钥密码
public static String password = null;
/**
* Sign 加密类
*/
public static String getSign(Map<String, Object> map, String key) {
List<String> list = new ArrayList<>(map.keySet());
Collections.sort(list);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < list.size(); i++) {
String k = list.get(i);
String v = (String) map.get(k);
sb.append(k).append("=").append(v).append("&");
}
String signstr = sb.append("key=").append(key).toString();
String sign = MD5Util.MD5Encode(signstr).toUpperCase();
return sign;
}
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
Map<String, Object> parmap = new HashMap<>();
Map<String, Object> verify = new HashMap<>();
String appId = "test";
String password = "uhbhkh223123";
String accessKey = "1234567896132";
String timeStamp = "" + System.currentTimeMillis();
verify.put("password", password);
verify.put("appId", appId);
verify.put("timeStamp", timeStamp);
verify.put("sign", getSign(verify, accessKey));
parmap.put("type", "iccid");
parmap.put("cardno", "89860400000000002135");
map.put("verify", verify);
map.put("Param", parmap);
server_Ip += "/cardInfo";
String result = HttpUtil.post(server_Ip, JSON.toJSONString(map));
System.out.println(result);
}
```
+389
View File
@@ -0,0 +1,389 @@
---
title: 查询类
description: iotLCM 旧版兼容查询类接口
outline: deep
---
# 查询类
## 查询 SIM 详情
```http
POST /route/open/cardInfo
```
简要描述:查询物联卡基础信息。
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| type | string | 查询类型:`msisdn` / `iccid` / `imsi` / `vid` |
| cardno | string | 对应的查询值 |
请求示例:
```json
{
"Param": {
"type": "iccid",
"cardno": "89860400000000005474"
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| vid | string | 本地卡记录 ID |
| msisdn | string | MSISDN |
| iccid | string | ICCID |
| imsi | string | IMSI |
| imei | string | IMEI |
| deptName | string | 当前归属组织名称 |
| statusId | number | 状态编码,参考公共字典 |
| packageId | string | 当前套餐编码 |
| packageName | string | 当前套餐名称 |
| used | number | 当前周期已用流量,单位 MB |
| remaining | number | 当前周期剩余流量,单位 MB |
| type | string | 卡类型名称 |
| packetArr | array | 资费计划,未配置时返回 `null` |
| remark | string | 备注 |
| grouping | string | 分组,未配置时为空字符串 |
| deliverDate | string | 发货日期,未配置时为空字符串 |
| activateDate | string | 激活日期 |
| expireTime | string | 到期时间 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": {
"vid": "3933",
"msisdn": "1441543605474",
"iccid": "89860400000000005474",
"imsi": "460087436015474",
"imei": "",
"deptName": "示例企业",
"statusId": 4,
"packageId": "yd-100m-3y",
"packageName": "每月100m3年",
"used": 0.875,
"remaining": 99.125,
"type": "",
"packetArr": null,
"remark": "测试卡",
"grouping": "",
"deliverDate": "",
"activateDate": "2026-06-01",
"expireTime": "2029-01-31 23:59:59"
}
}
```
## 查询 SIM 账期用量
```http
POST /route/open/simDataUsage
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | string | ICCID |
请求示例:
```json
{
"Param": {
"iccid": "89860400000000005474"
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| useUnit | string | 单位,当前为 `MB` |
| useAmount | number | 当前周期已用流量 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": {
"useUnit": "MB",
"useAmount": 0.875
}
}
```
## 查询 SIM 历史月用量
```http
POST /route/open/simDataUsageMonthly
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | string | ICCID |
| dateTime | string | 查询月份,格式 `yyyyMM`(示例:`202509`) |
请求示例:
```json
{
"Param": {
"iccid": "89860400000000005474",
"dateTime": "202509"
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| useUnit | string | 单位,当前为 `MB` |
| useAmount | number | 指定月份用量 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": {
"useUnit": "MB",
"useAmount": 0
}
}
```
## 查询 SIM 状态
```http
POST /route/open/simStatus
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | string | ICCID |
请求示例:
```json
{
"Param": {
"iccid": "89860400000000005474"
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| statusMessage | string | 状态描述 |
| statusCode | number | 状态编码,参考公共字典 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": {
"statusMessage": "已激活",
"statusCode": 4
}
}
```
## 查询 SIM 在线会话
```http
POST /route/open/simSession
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | string | ICCID |
请求示例:
```json
{
"Param": {
"iccid": "89860400000000005474"
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| typeName | string | 运营商系统类型,未上报时为空字符串 |
| imei | string | IMEI,未上报时为空字符串 |
| statusMessage | string | 状态描述,未上报时为空字符串 |
| statusCode | number | 状态码,未上报时为 `0` |
| data | object | 会话详情 |
`data` 字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| apnId | string | APN ID |
| rat | string | 接入方式 |
| ipv6 | string | IPv6 地址 |
| ip | string | IPv4 地址 |
| ipv6Prefix | string | IPv6 前缀 |
| status | string | 在线状态 |
| createDate | string | 会话创建时间 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": {
"typeName": "",
"imei": "",
"statusMessage": "",
"statusCode": 0,
"data": {
"apnId": "",
"rat": "",
"ipv6": "",
"ip": "",
"ipv6Prefix": "",
"status": "",
"createDate": ""
}
}
}
```
## 查询卡列表信息
```http
POST /route/open/queryCardInfoList
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | `array<string>` | ICCID 列表,最多支持 100 个 |
请求示例:
```json
{
"Param": {
"iccid": [
"89860400000000005473",
"89860400000000005474"
]
},
"verify": {
"timeStamp": "1784097284347",
"password": "example-password",
"appId": "example-app-id",
"sign": ""
}
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| iccid | string | ICCID |
| activateDate | string | 激活日期 |
| dueExpireTime | string | 到期时间 |
| imei | string | IMEI |
| imsi | string | IMSI |
| msisdn | string | MSISDN |
| type | string | 卡类型 |
| remarks | string | 备注 |
| status | number | 状态编码,参考公共字典 |
响应示例:
```json
{
"status": 0,
"message": "",
"result": [
{
"iccid": "89860400000000005473",
"activateDate": "2023-05-11",
"dueExpireTime": "",
"imei": "",
"imsi": "460087436015473",
"msisdn": "1441543605473",
"type": "",
"remarks": "测试卡",
"status": 4
},
{
"iccid": "89860400000000005474",
"activateDate": "2026-06-01",
"dueExpireTime": "2029-01-31 23:59:59",
"imei": "",
"imsi": "460087436015474",
"msisdn": "1441543605474",
"type": "",
"remarks": "测试卡",
"status": 4
}
]
}
```
+14
View File
@@ -0,0 +1,14 @@
---
title: 共享 Token 接口概览
description: 共享 Token 接口与白名单转发说明
outline: deep
---
# 共享 Token 接口概览
本目录包含共享 Token 获取接口和白名单转发说明。
## 接口文档
- [共享 Token 接口](./shared-token/)
- [白名单转发](./mobile-v5/)
@@ -0,0 +1,138 @@
---
title: 白名单转发
description: 通过 api.example.com 访问中国移动物联网能力开放平台 V5 接口
outline: deep
---
# 白名单转发
本目录说明如何通过 `api.example.com` 访问中国移动物联网能力开放平台 V5 接口。平台仅提供安全代理转发,不改变中国移动 V5 接口的请求方法、请求参数、响应结构和业务语义。
## 服务地址
客户侧统一使用:
```text
https://api.example.com
```
V5 接口的请求路径保持与中国移动官方接口一致,将官方域名替换为平台域名即可:
```text
https://api.example.com/v5/{官方接口路径}
```
例如,官方接口路径为 `/v5/ec/query/sim-basic-info` 时,客户侧请求路径为:
```http
POST https://api.example.com/v5/ec/query/sim-basic-info
```
平台会将该请求转发至:
```text
https://api.iot.10086.cn/v5/ec/query/sim-basic-info
```
> [!IMPORTANT] 使用 POST 时务必携带 JSON 请求体
> 中国移动 V5 接口对 `POST` 只接受 JSON 格式。通过代理请求时必须设置
> `Content-Type: application/json`,并在请求体中传入参数;
> 未带该请求头或使用表单编码(`application/x-www-form-urlencoded`)会返回
> `{"status":"11012","message":"POST请求仅支持JSON格式","result":[]}`。
>
> 若使用 `GET`,则以 Query 参数传参,无需设置 `Content-Type`。
## 调用前准备
调用 V5 业务接口前,需要先从平台取得以下信息:
| 参数 | 说明 |
| --- | --- |
| `appid` | 第三方应用唯一标识 |
| `password` | 应用密钥 |
| `transid` | 按官方规则生成的事务编码 |
| `token` | 通过共享 Token 接口获取的运营商令牌 |
其中 `transid` 由客户生成,通常遵循 `appid + YYYYMMDDHHMISS + 8位数字序列`;中国移动通常不校验此事务编号。具体规则及字段约束以中国移动当前发布的接口文档为准。
## 业务接口示例
以下接口字段摘自中国移动 V5 接口清单,用于说明代理路径的写法。完整接口、字段、错误码和版本变更请以官方文档为准。
### 单卡基本信息查询
```http
POST https://api.example.com/v5/ec/query/sim-basic-info
```
请求参数:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `transid` | string | 事务编码 |
| `token` | string | V5 Token |
| `msisdn` | string | 物联卡号码 |
| `iccid` | string | ICCID;与 `msisdn`、`imsi` 按官方规则选择 |
| `imsi` | string | IMSI |
请求示例:
```json
{
"transid": "113712264334542026051515200001000",
"token": "example-access-token",
"iccid": "89860800000000000009"
}
```
响应主要字段:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `status` | string | `0` 表示成功,非 `0` 表示失败 |
| `message` | string | 错误或提示信息 |
| `result` | array | 查询结果 |
| `result[].msisdn` | string | 物联卡号码 |
| `result[].iccid` | string | ICCID |
| `result[].imsi` | string | IMSI |
| `result[].activeDate` | string | 首次激活日期 |
| `result[].openDate` | string | 开卡时间 |
响应示例:
```json
{
"status": "0",
"message": "正确",
"result": [
{
"msisdn": "1442206200009",
"imsi": "460240406200009",
"iccid": "89860800000000000009",
"activeDate": "2025-03-26 15:49:50",
"openDate": "2025-03-13 16:52:10",
"remark": ""
}
]
}
```
## 请求与响应约定
- V5 接口支持的请求方法、参数位置(Query 或 Form)、参数格式和字段大小写均以中国移动官方文档为准。
- `GET` 与 `POST` 均可通过本代理访问。使用 `POST` 时**必须**设置 `Content-Type: application/json` 并携带 JSON 请求体,否则返回 `11012`;使用表单编码亦不被接受。
- 代理会以平台固定出口地址访问中国移动,因此客户端侧的来源 IP 无需加入中国移动白名单;请勿在请求中自行添加 `X-Forwarded-For`、`X-Real-IP` 等来源标识请求头,中国移动会依据其判定 IP 鉴权,携带非白名单地址将返回 `12003`。
- 平台不将 V5 响应包装成 iotLCM V3 的 `code/message/data` 结构,客户应直接按照中国移动 V5 响应结构解析。
- 运营商 Token 具有有效期,收到 Token 失效类错误后应重新获取,不建议长期缓存。
## 官方资料
接口完整清单、参数定义、返回示例、错误码和 SDK 示例请参阅:
[中国移动物联网能力开放平台 V5 接口文档下载页面](https://api.iot.10086.cn/api/index.html#/resourceCenter/docDownload)
平台代理入口仅替换访问域名,不替代中国移动官方接口规范。
@@ -0,0 +1,87 @@
---
title: 共享 Token 接口
description: 共享 Token 接口说明
outline: deep
---
# 共享 Token 接口
本接口用于获取平台已开放共享权限的移动运营商访问 Token。客户无需直接维护运营商应用密钥,平台根据客户已授权的通道配置完成校验并向运营商申请 Token。
## 服务地址
```text
https://api.example.com
```
HTTP(`http://api.example.com`)仅为存量兼容保留,不做自动跳转,新接入请使用 HTTPS。
## 接口信息
```http
POST /openapi/v3/query/operator-token
```
请求头:`Content-Type: application/json`。
请求体:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 平台分配的客户应用标识 |
| `chanKey` | string | 已开通共享 Token 权限的通道标识 |
请求示例:
```json
{
"appId": "example-app-id",
"chanKey": "channel_010"
}
```
响应中的 `data.accessToken` 用于调用移动运营商 V5 接口。Token 的有效期以 `data.tokenExpire` 为准,请勿将 Token 写入代码仓库或日志。
响应示例:
失败时,接口返回的 `data` 为 `{}`,不是 `null`;例如 `code=50` 表示平台侧获取运营商 Token 失败。
```json
{
"code": 0,
"message": "",
"data": {
"chanAlias": "中国移动物联网",
"chanKey": "channel_010",
"accessToken": "example-access-token",
"tokenExpire": "2026-09-09 16:00:00"
},
"error": null
}
```
## 字段说明
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 平台业务状态码,`0` 表示成功;`50` 表示平台侧获取运营商 Token 失败 |
| `message` | string | 错误或提示信息 |
| `data` | object | 成功时返回 Token 信息;失败时返回空对象 `{}`,不是 `null` |
| `data.chanAlias` | string | 通道名称 |
| `data.chanKey` | string | 通道标识 |
| `data.accessToken` | string | 移动运营商接口访问 Token |
| `data.tokenExpire` | string | Token 过期时间 |
| `error` | any | 错误详情;无错误时通常为 `null` |
## 调用示例
```bash
curl --request POST \
--url https://api.example.com/openapi/v3/query/operator-token \
--header 'Content-Type: application/json' \
--data '{
"appId": "example-app-id",
"chanKey": "channel_010"
}'
```
共享 Token 仅适用于已开通共享权限的通道。若通道未授权、客户应用标识与通道不匹配,或运营商 Token 获取失败,接口将返回失败信息。
@@ -0,0 +1,10 @@
---
title: 共享 Token 接口
description: 共享 Token 接口文档兼容入口
sidebar: false
search: false
---
# 共享 Token 接口
文档已归档到运营商接口模块,请访问[共享 Token 接口](./operator/shared-token/)。
+352
View File
@@ -0,0 +1,352 @@
---
title: 业务类
description: iotLCM V3 业务类接口
outline: deep
---
# 业务类
## 服务地址
```text
https://api.example.com
```
HTTP(`http://api.example.com`)仅为存量兼容保留,不做自动跳转,新接入请使用 HTTPS。
## 鉴权
先调用 `/openapi/v3/query/token` 获取访问令牌,再在请求头中传入 `X-Access-Token`。请求体中的 `appId` 必须与凭证所属客户一致。
## 标准入口与兼容入口
新接入统一使用本文的 `/openapi/v3/change/*` 路径。原 `/openapi/v3/common/*` 前缀已停止提供,调用返回 `404`,请改用 `/change` 下的对应接口。
批量续费使用 `/change/batch-sim-subscribe`;历史 `/change/renew` 接收 `cards[]`,允许每张卡携带套餐信息,作为兼容入口保留。两者共用续费实现。
批量变更达量断网使用 `/change/batch-sim-traffic-cutoff`。原 `/change/traffic-cutoff-config` 不再对外提供,请改用标准入口——两者请求参数完全一致,迁移无需改动报文体。`/query/traffic-cutoff-config` 仅查询配置。
## 统一返回结构
```json
{
"code": 0,
"message": "",
"data": {},
"error": null
}
```
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 业务状态码,成功为 `0` |
| `message` | string | 错误或提示信息 |
| `data` | object | 业务数据 |
| `error` | any | 错误详情 |
## 单卡套餐续费
```http
POST /openapi/v3/change/sim-subscribe
```
单卡续费按串行方式执行,接口等待本卡处理完成后直接返回结果。
请求参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `iccid` | string | ICCID |
| `msisdn` | string | MSISDN/卡号 |
| `imsi` | string | IMSI |
| `packageId` | string | 目标套餐编码 |
| `validity` | integer | 续费时长,单位为年,最小值为 `1` |
| `remark` | string | 续费备注 |
> `iccid`、`msisdn`、`imsi` 至少传一个。
请求示例:
```json
{
"appId": "CUS-202608-0001",
"iccid": "89860400000000005152",
"packageId": "001",
"validity": 1,
"remark": "客户单卡续费"
}
```
响应字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `orderId` | string | 订单 ID |
| `orderSn` | string | 订单号 |
| `iccid` | string | 续费卡 ICCID |
| `planName` | string | 续费套餐名称 |
| `effectiveType` | string | 生效类型 |
| `effectiveDate` | string | 套餐生效日期 |
| `expireDate` | string | 套餐到期日期 |
| `amount` | number | 续费金额 |
| `status` | string | 续费状态,成功为 `success`,失败为 `failed` |
| `errorCode` | string | 失败时返回的错误编码 |
| `errorMessage` | string | 失败时返回的错误说明 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"orderId": "54",
"orderSn": "REN20260910153546001000001",
"iccid": "89860400000000005152",
"planName": "中移-1G",
"effectiveType": "immediate",
"effectiveDate": "2026-09-10 15:35:46",
"expireDate": "2027-09-10 15:35:46",
"amount": 0,
"status": "success"
},
"error": null
}
```
失败时仍按统一返回结构返回 `data`,并通过 `status=failed` 和 `errorMessage` 说明失败原因。
```json
{
"code": 0,
"message": "",
"data": {
"orderId": "0",
"orderSn": "",
"iccid": "89860400000000005152",
"planName": "",
"effectiveType": "",
"effectiveDate": "",
"expireDate": "",
"amount": 0,
"status": "failed",
"errorCode": "RENEW_FAILED",
"errorMessage": "卡片不存在或无权限"
},
"error": null
}
```
## 单卡限额变更
```http
POST /openapi/v3/change/sim-traffic-cutoff
```
单卡配置按串行方式执行,接口等待本卡处理完成后直接返回配置结果。
请求参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `iccid` | string | ICCID |
| `msisdn` | string | MSISDN/卡号 |
| `imsi` | string | IMSI |
| `trafficCutoffStatus` | integer | `0` 删除,`1` 开启,`2` 关闭 |
| `trafficCutoffThreshold` | number | 达量阈值,单位为 MB;开启时必须大于 `0` |
| `remark` | string | 配置备注 |
> `iccid`、`msisdn`、`imsi` 三码至少传一个。trafficCutoffStatus=0 表示取消达量断网配置。
请求示例:
```json
{
"appId": "CUS-202608-0001",
"iccid": "89860400000000005152",
"trafficCutoffStatus": 1,
"trafficCutoffThreshold": 1024,
"remark": "客户开启达量断网"
}
```
响应字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `success` | boolean | 是否配置成功 |
| `iccid` | string | 平台卡 ICCID |
| `msisdn` | string | MSISDN |
| `imsi` | string | IMSI |
| `trafficCutoffStatus` | integer | `0` 删除,`1` 已开启,`2` 已关闭 |
| `trafficCutoffThreshold` | number | 达量阈值,单位为 MB |
| `trafficCutoffThresholdUnit` | string | 阈值单位,固定为 `MB` |
| `message` | string | 处理结果说明 |
| `errorCode` | string | 失败时的错误编码 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"success": true,
"iccid": "89860400000000005152",
"msisdn": "14100005152",
"imsi": "460045462635152",
"trafficCutoffStatus": 1,
"trafficCutoffThreshold": 1024,
"trafficCutoffThresholdUnit": "MB",
"message": "traffic cutoff configuration succeeded"
},
"error": null
}
```
失败时仍返回同一响应结构,`success=false`,`message` 为失败原因。
```json
{
"code": 0,
"message": "",
"data": {
"success": false,
"iccid": "89860400000000005152",
"trafficCutoffStatus": 1,
"trafficCutoffThreshold": 1024,
"trafficCutoffThresholdUnit": "MB",
"message": "卡片不存在或无权限",
"errorCode": "TRAFFIC_CUTOFF_FAILED"
},
"error": null
}
```
## 物联网卡批量套餐续费
```http
POST /openapi/v3/change/batch-sim-subscribe
```
批量续费只创建异步批次,不等待所有卡执行完成。请保存响应中的 `batchNo`,再通过批次查询接口跟踪处理结果。
批量接口提交成功后只返回批次信息;卡片执行成功或失败请通过批次查询接口获取。
请求参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `cards` | array | 续费卡列表 |
| `cards[].iccid` | string | ICCID |
| `cards[].msisdn` | string | MSISDN/卡号 |
| `cards[].imsi` | string | IMSI |
| `packageId` | string | 目标套餐编码 |
| `validity` | integer | 续费时长,单位为年 |
| `remark` | string | 续费备注 |
> 每个 `cards[]` 明细中的`iccid`、`msisdn`、`imsi` 三码至少传一个。
请求示例:
```json
{
"appId": "CUS-202608-0001",
"cards": [
{
"iccid": "89860400000000005152"
}
],
"packageId": "001",
"validity": 1,
"remark": "客户批量续费"
}
```
响应字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `batchNo` | string | 系统生成的续费批次号 |
| `batchType` | integer | 批次类型,续费为 `101` |
| `total` | integer | 提交的卡数量 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"batchNo": "REN20260910153546001000002",
"batchType": 101,
"total": 1
},
"error": null
}
```
## 物联网卡批量限额变更
```http
POST /openapi/v3/change/batch-sim-traffic-cutoff
```
批量配置只创建异步批次,不等待所有卡执行完成。请保存响应中的 `batchNo`,再通过批次查询接口跟踪处理结果。
批量接口提交成功后只返回批次信息;卡片执行成功或失败请通过批次查询接口获取。
请求参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `cards` | array | 配置卡列表,单次最多 1000 张 |
| `cards[].iccid` | string | ICCID |
| `cards[].msisdn` | string | MSISDN/卡号 |
| `cards[].imsi` | string | IMSI |
| `trafficCutoffStatus` | integer | `0` 删除,`1` 开启,`2` 关闭 |
| `trafficCutoffThreshold` | number | 达量阈值,单位为 MB;开启时必须大于 `0` |
| `remark` | string | 配置批次备注 |
> 每个 `cards[]` 明细中的`iccid`、`msisdn`、`imsi` 三码至少传一个。`trafficCutoffStatus=0`时表示取消达量断网配置。
请求示例:
```json
{
"appId": "CUS-202608-0001",
"cards": [
{
"iccid": "89860400000000005152"
}
],
"trafficCutoffStatus": 1,
"trafficCutoffThreshold": 1024,
"remark": "客户批量开启达量断网"
}
```
响应字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `batchNo` | string | 系统生成的达量断网配置批次号 |
| `batchType` | integer | 批次类型,达量断网配置为 `41` |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"batchNo": "TCC20260910153546001000001",
"batchType": 41
},
"error": null
}
```
+81
View File
@@ -0,0 +1,81 @@
---
title: 概览与鉴权
description: iotLCM V3 接口概览与鉴权说明
outline: deep
---
# 概览与鉴权
## 服务地址
```text
https://api.example.com
```
HTTP(`http://api.example.com`)仅为存量兼容保留,不做自动跳转,新接入请使用 HTTPS。
## 统一返回结构
V3 返回格式:
```json
{
"code": 0,
"message": "",
"data": {},
"error": null
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| code | number | 业务状态码,成功为 `0` |
| message | string | 错误或提示信息 |
| data | object | 业务数据 |
| error | any | 错误信息字段 |
## 公共字典
卡状态编码
| 编码 | 说明 |
| :-: | :-: |
| 0 | 未知 |
| 1 | 待激活 |
| 2 | 已激活 |
| 4 | 停机 |
| 6 | 可测试 |
| 7 | 库存 |
| 8 | 预销户 |
| 9 | 已销户 |
## V3 鉴权
V3 路由前缀:
```text
/openapi/v3/query
/openapi/v3/change
```
文档中的“请求示例”仅展示请求 body;调用 V3 POST 接口时,请设置 `Content-Type: application/json` 并将示例 JSON 作为请求体发送。请求路径和鉴权请求头见各接口说明。
除获取 token 接口外,其他 V3 接口需要请求头:
```http
X-Access-Token: {accessToken}
```
也支持传入带 `Bearer ` 前缀的值,服务端会去掉前缀。
::: info 重要:请求体中的 appId 必须正确
先调用 `/openapi/v3/query/token` 获取 token,接口响应中会返回 `data.appId`(客户组织编码,格式如 `CUS-202601-0001`)。**后续调用所有 V3 接口时,请求体都必须传入这个 `appId` 值**,且要与 token 所属的客户组织匹配,否则返回 `code: 61 "登录已过期"`。
⚠️ **不要把 `appKey`(API 账号标识)当成 `appId` 使用。** `appKey` 只用于获取 token。
:::
权限控制:调用接口需具备对应接口资源的访问权限,权限与 API 账号所属权限组关联。未登记资源、资源停用、账号未授权均拒绝访问,返回 `10014`;权限数据查询异常返回 `10016`(系统侧故障,不是授权问题)。
原 `/openapi/v3/common/*` 前缀已停止提供,调用返回 `404`,请使用 `/openapi/v3/change/*` 下的对应接口。
+783
View File
@@ -0,0 +1,783 @@
---
title: 查询类
description: iotLCM V3 查询类接口
outline: deep
---
# 查询类
## 单卡基本信息查询
```http
POST /openapi/v3/query/sim-info
```
请求头:
```http
X-Access-Token: {accessToken}
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| appId | string | 客户组织编码 |
| iccid | string | ICCID,`iccid`、`msisdn`、`imsi` 至少传一个 |
| msisdn | string | MSISDN |
| imsi | string | IMSI |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"iccid": "89860400000000005474"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.iccid` | string | ICCID |
| `data.msisdn` | string | MSISDN |
| `data.imsi` | string | IMSI |
| `data.imei` | string | IMEI |
| `data.username` | string | 当前归属客户/组织名称 |
| `data.location` | string | 归属地 |
| `data.planCategory` | string | 套餐分类 |
| `data.currentPlanName` | string | 当前套餐名称 |
| `data.plangroup` | string | 套餐分组,未配置时为空字符串 |
| `data.planid` | string | 当前套餐编码 |
| `data.appid` | string | 归属客户组织编码 |
| `data.allowPlanChange` | number | 是否允许改套餐,未启用时返回 `0` |
| `data.lastSyncTime` | string | 状态最后同步时间 |
| `data.accessType` | string | 接入方式 |
| `data.cardStatus` | string | 卡状态 |
| `data.cardType` | any | 卡类型 |
| `data.networkType` | any | 网络类型 |
| `data.activationDate` | string | 出库/激活相关日期 |
| `data.activeDate` | string | 当前套餐生效日期 |
| `data.validityPeriod` | number | 有效期,按月份计算 |
| `data.expiryDate` | string | 当前套餐到期日期 |
| `data.testPeriod` | number | 测试期,未启用时返回 `0` |
| `data.testPeriodExpiryDate` | any | 测试期到期时间,未启用时返回 `null` |
| `data.silentPeriod` | number | 沉默期,未启用时返回 `0` |
| `data.silentPeriodExpiryDate` | any | 沉默期到期时间,未启用时返回 `null` |
| `data.dataService` | number | 数据服务状态,`1` 启用,`0` 禁用 |
| `data.planData` | number | 套餐总流量,单位 MB |
| `data.usedData` | number | 已用流量,单位 MB |
| `data.remainingData` | string | 剩余流量,单位 MB |
| `data.smsService` | number | 短信服务状态 |
| `data.planSMS` | number | 套餐短信总量 |
| `data.usedSMS` | number | 已用短信数 |
| `data.remainingSMS` | string | 剩余短信数 |
| `data.voiceService` | number | 语音服务状态 |
| `data.planMinutes` | number | 套餐语音分钟数 |
| `data.usedMinutes` | number | 已用语音分钟数 |
| `data.remainingMinutes` | string | 剩余语音分钟数 |
| `data.internationalRoamingService` | number | 国际漫游服务状态 |
| `data.planInternationalData` | number | 国际漫游套餐流量 |
| `data.usedInternationalData` | number | 已用国际漫游流量 |
| `data.remainingInternationalData` | string | 剩余国际漫游流量 |
| `data.description` | any | 备注描述,未填写时返回 `null` |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"iccid": "89860400000000005474",
"imei": "",
"username": "示例企业",
"location": "370100000000",
"planCategory": "移动小流量",
"currentPlanName": "每月100m3年",
"plangroup": "",
"planid": "yd-100m-3y",
"appid": "CUS-202601-0001",
"allowPlanChange": 0,
"lastSyncTime": "2026-06-24 00:03:19",
"accessType": "",
"cardStatus": "已激活",
"cardType": "",
"networkType": "",
"activationDate": "2026-06-01",
"activeDate": "2026-06-01",
"validityPeriod": 32,
"expiryDate": "2029-01-31",
"testPeriod": 0,
"testPeriodExpiryDate": null,
"silentPeriod": 0,
"silentPeriodExpiryDate": null,
"dataService": 1,
"planData": 100,
"usedData": 0.875,
"remainingData": "99.1",
"smsService": 1,
"planSMS": 0,
"usedSMS": 0,
"remainingSMS": "0",
"voiceService": 1,
"planMinutes": 0,
"usedMinutes": 0,
"remainingMinutes": "0",
"internationalRoamingService": 0,
"planInternationalData": 0,
"usedInternationalData": 0,
"remainingInternationalData": "0.0",
"description": null
},
"error": null
}
```
## 单卡状态信息查询
```http
POST /openapi/v3/query/sim-status
```
请求头:
```http
X-Access-Token: {accessToken}
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | token 返回的客户组织编码 |
| `iccid` | string | ICCID;与 `msisdn`、`imsi` 至少传一个 |
| `msisdn` | string | MSISDN |
| `imsi` | string | IMSI |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"iccid": "89860400000000005474"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 业务状态码,成功为 `0` |
| `message` | string | 错误或提示信息 |
| `data.iccid` | string | ICCID,查询命中时返回 |
| `data.lifeCycle` | number | 卡生命周期状态编码 |
| `data.lifeCycleName` | string | 卡生命周期状态文字 |
| `error` | any | 错误信息字段 |
响应示例(查询命中):
```json
{
"code": 0,
"message": "",
"data": {
"iccid": "89860400000000005474",
"lifeCycle": 2,
"lifeCycleName": "已激活"
},
"error": null
}
```
响应示例(未命中):
```json
{
"code": 50,
"message": "查询sim卡信息失败,请确认ICCID,MSISDN,IMSI是否正确",
"data": {},
"error": null
}
```
`lifeCycleName` 为状态文字;`lifeCycle` 是此接口的状态编码,不要与其他接口或字典中的状态编码混用。
## 单卡流量限额查询
```http
POST /openapi/v3/query/sim-limit
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `iccid` | string | ICCID |
| `msisdn` | string | MSISDN |
| `imsi` | string | IMSI |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"iccid": "89860400000000005474"
}
```
响应字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.trafficCutoffStatus` | integer | 达量断网状态:`0` 未配置、`1` 已开启、`2` 已关闭 |
| `data.trafficCutoffName` | string | 状态名称,返回 `未配置`、`已开启` 或 `已关闭` |
| `data.trafficCutoffThreshold` | number | 达量断网阈值,单位 MB |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"trafficCutoffStatus": 1,
"trafficCutoffName": "已开启",
"trafficCutoffThreshold": 1024
},
"error": null
}
```
## 单卡实时终端 IMEI 查询
```http
POST /openapi/v3/query/sim-imei
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| appId | string | 客户组织编码 |
| iccid | string | ICCID |
| msisdn | string | MSISDN |
| imsi | string | IMSI |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"iccid": "89860400000000005474"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.imei` | string | IMEI |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"imei": ""
},
"error": null
}
```
## 单卡本月流量使用量查询
```http
POST /openapi/v3/query/sim-usage
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| appId | string | 客户组织编码 |
| iccid | string | ICCID |
| msisdn | string | MSISDN |
| imsi | string | IMSI |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"iccid": "89860400000000005474"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.useUnit` | string | 流量单位,固定为 `MB` |
| `data.useAmount` | number | 当前周期已用流量 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"useUnit": "MB",
"useAmount": 0.875
},
"error": null
}
```
## 物联网卡批量状态信息查询
```http
POST /openapi/v3/query/sim-status-batch
```
单次最多查询 1000 张卡。每张卡至少提供 `iccid`、`msisdn`、`imsi` 中的一个;同一条记录提供多个标识时,必须对应同一张卡。
请求头:
```http
X-Access-Token: {accessToken}
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | token 返回的客户组织编码,示例为 `CUS-202601-0001` |
| `cards` | array | 卡片标识列表;示例包含 3 张卡,单次最多 1000 项 |
| `cards[].iccid` | string | ICCID;示例中每项均使用 ICCID,实际调用时与 `msisdn`、`imsi` 至少传一个 |
| `cards[].msisdn` | string | MSISDN,可代替 ICCID 标识卡片 |
| `cards[].imsi` | string | IMSI,可代替 ICCID 标识卡片 |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"cards": [
{
"iccid": "89860400000000005473"
},
{
"iccid": "89860400000000005474"
},
{
"iccid": "8986110000000004371"
}
]
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 业务状态码,成功为 `0` |
| `message` | string | 错误或提示信息 |
| `data.total` | integer | 本次请求的卡片总数;示例为 `3` |
| `data.matchedCount` | integer | 匹配成功的卡片数量;示例为 `3` |
| `data.notFoundCount` | integer | 未匹配成功的卡片数量;示例为 `0` |
| `data.items` | array | 每张卡的查询结果;示例依次对应请求中的 3 张卡 |
| `data.items[].matched` | boolean | 是否匹配成功;示例中均为 `true` |
| `data.items[].iccid` | string | 匹配成功时返回的 ICCID |
| `data.items[].lifeCycle` | number | 匹配成功时返回的卡生命周期状态编码;示例中 `2` 为已激活 |
| `data.items[].lifeCycleName` | string | 匹配成功时返回的卡生命周期状态文字 |
| `data.items[].errorCode` | string | 匹配失败时返回的错误码;示例均匹配成功,因此未返回 |
| `data.items[].errorMessage` | string | 匹配失败时返回的错误说明;示例均匹配成功,因此未返回 |
| `error` | any | 错误信息字段 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"total": 3,
"matchedCount": 3,
"notFoundCount": 0,
"items": [
{
"matched": true,
"iccid": "89860400000000005473",
"lifeCycle": 2,
"lifeCycleName": "已激活"
},
{
"matched": true,
"iccid": "89860400000000005474",
"lifeCycle": 2,
"lifeCycleName": "已激活"
},
{
"matched": true,
"iccid": "8986110000000004371",
"lifeCycle": 2,
"lifeCycleName": "已激活"
}
]
},
"error": null
}
```
## 物联网卡批量流量信息查询
```http
POST /openapi/v3/query/sim-usage-batch
```
单次最多查询 1000 张卡。请求体需传入 `appId` 和 `cards` 数组;数组中的每张卡至少提供 `iccid`、`msisdn`、`imsi` 中的一个。同一条记录提供多个标识时,必须对应同一张卡。
请求头:
```http
X-Access-Token: {accessToken}
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | token 返回的客户组织编码 |
| `cards` | array | 卡片标识列表,单次最多 1000 项 |
| `cards[].iccid` | string | ICCID;每项与 `msisdn`、`imsi` 至少传一个 |
| `cards[].msisdn` | string | MSISDN,可代替 ICCID 标识卡片 |
| `cards[].imsi` | string | IMSI,可代替 ICCID 标识卡片 |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"cards": [
{
"iccid": "89860400000000005474"
},
{
"iccid": "89860400000000005473"
},
{
"iccid": "8986110000000004371"
}
]
}
```
响应结构(流量单位为 MB):
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 业务状态码,成功为 `0` |
| `message` | string | 错误或提示信息 |
| `data.total` | integer | 本次请求的卡片总数 |
| `data.matchedCount` | integer | 匹配成功的卡片数量 |
| `data.notFoundCount` | integer | 未匹配成功的卡片数量 |
| `data.items` | array | 每张卡的查询结果 |
| `data.items[].matched` | boolean | 是否匹配成功 |
| `data.items[].iccid` | string | 匹配成功时返回的 ICCID |
| `data.items[].useUnit` | string | 流量单位,示例为 `MB` |
| `data.items[].useAmount` | number | 流量使用量 |
| `data.items[].errorCode` | string | 匹配失败时返回的错误码;匹配成功时不返回 |
| `data.items[].errorMessage` | string | 匹配失败时返回的错误说明;匹配成功时不返回 |
| `error` | any | 错误信息字段 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"total": 3,
"matchedCount": 3,
"notFoundCount": 0,
"items": [
{
"matched": true,
"iccid": "89860400000000005474",
"useUnit": "MB",
"useAmount": 0.875
},
{
"matched": true,
"iccid": "89860400000000005473",
"useUnit": "MB",
"useAmount": 12.5
},
{
"matched": true,
"iccid": "8986110000000004371",
"useUnit": "MB",
"useAmount": 36.25
}
]
},
"error": null
}
```
## 物联网卡批量 IMEI 查询
```http
POST /openapi/v3/query/sim-imei-batch
```
单次最多查询 1000 张卡。每张卡至少提供 `iccid`、`msisdn`、`imsi` 中的一个;同一条记录提供多个标识时,必须对应同一张卡。
请求头:
```http
X-Access-Token: {accessToken}
```
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | token 返回的客户组织编码 |
| `cards` | array | 卡片标识列表,单次最多 1000 项 |
| `cards[].iccid` | string | ICCID;每项与 `msisdn`、`imsi` 至少传一个 |
| `cards[].msisdn` | string | MSISDN,可代替 ICCID 标识卡片 |
| `cards[].imsi` | string | IMSI,可代替 ICCID 标识卡片 |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"cards": [
{
"iccid": "89860400000000005474"
},
{
"iccid": "89860400000000005473"
},
{
"msisdn": "1441543605474"
}
]
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `code` | number | 业务状态码,成功为 `0` |
| `message` | string | 错误或提示信息 |
| `data.total` | integer | 本次请求的卡片总数 |
| `data.matchedCount` | integer | 匹配成功的卡片数量 |
| `data.notFoundCount` | integer | 未匹配成功的卡片数量 |
| `data.items` | array | 每张卡的查询结果,顺序与请求中的 `cards` 一致 |
| `data.items[].matched` | boolean | 是否匹配成功 |
| `data.items[].iccid` | string | 匹配成功时返回的 ICCID |
| `data.items[].imei` | string | IMEI;尚未采集到该卡 IMEI 时为空字符串 |
| `data.items[].errorCode` | string | 匹配失败时返回的错误码;匹配成功时不返回 |
| `data.items[].errorMessage` | string | 匹配失败时返回的错误说明;匹配成功时不返回 |
| `error` | any | 错误信息字段 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"total": 3,
"matchedCount": 2,
"notFoundCount": 1,
"items": [
{
"matched": true,
"iccid": "89860400000000005474",
"imei": ""
},
{
"matched": true,
"iccid": "89860400000000005473",
"imei": "865466070817587"
},
{
"matched": false,
"imei": "",
"errorCode": "CARD_NOT_FOUND",
"errorMessage": "卡片不存在或无权限"
}
]
},
"error": null
}
```
## 物联网卡批量限额信息查询
```http
POST /openapi/v3/query/traffic-cutoff-config
```
根据卡片标识批量查询当前达量断网配置。`cards[]` 中的 `iccid`、`msisdn`、`imsi` 三码至少传一个。
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `cards` | array | 卡片标识列表 |
| `cards[].iccid` | string | ICCID |
| `cards[].msisdn` | string | MSISDN |
| `cards[].imsi` | string | IMSI |
| `includeUsage` | boolean | 是否返回套餐和流量使用信息 |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"cards": [
{
"iccid": "89860400000000005474"
},
{
"iccid": "89860400000000005475"
},
{
"iccid": "89860400000000005476"
}
],
"includeUsage": true
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.total` | integer | 请求中的卡片数量 |
| `data.matchedCount` | integer | 匹配成功的卡片数量 |
| `data.notFoundCount` | integer | 未匹配成功的卡片数量 |
| `data.items` | array | 每张卡的查询结果 |
| `data.items[].matched` | boolean | 是否匹配成功 |
| `data.items[].iccid` | string | 平台返回的 ICCID |
| `data.items[].trafficCutoffStatus` | integer | 达量断网状态:`0` 未配置、`1` 已开启、`2` 已关闭 |
| `data.items[].trafficCutoffStatusName` | string | 达量断网状态名称 |
| `data.items[].trafficCutoffThreshold` | number | 达量断网阈值,单位 MB |
| `data.items[].trafficCutoffThresholdUnit` | string | 阈值单位,固定为 `MB` |
| `data.items[].packageTotalFlowMb` | number | 套餐总流量;`includeUsage=true` 时返回 |
| `data.items[].usedFlowMb` | number | 已用流量;`includeUsage=true` 时返回 |
| `data.items[].remainingFlowMb` | number | 剩余流量;`includeUsage=true` 时返回 |
| `data.items[].errorCode` | string | 匹配失败时返回 |
| `data.items[].errorMessage` | string | 匹配失败时返回 |
响应示例:
以下状态、阈值和流量数值用于展示批量响应结构,实际结果以接口查询为准。
```json
{
"code": 0,
"message": "",
"data": {
"total": 3,
"matchedCount": 3,
"notFoundCount": 0,
"items": [
{
"matched": true,
"iccid": "89860400000000005474",
"trafficCutoffStatus": 0,
"trafficCutoffStatusName": "未配置",
"trafficCutoffThreshold": 0,
"trafficCutoffThresholdUnit": "MB",
"packageTotalFlowMb": 0,
"usedFlowMb": 18,
"remainingFlowMb": 0
},
{
"matched": true,
"iccid": "89860400000000005475",
"trafficCutoffStatus": 1,
"trafficCutoffStatusName": "已开启",
"trafficCutoffThreshold": 100,
"trafficCutoffThresholdUnit": "MB",
"packageTotalFlowMb": 200,
"usedFlowMb": 80,
"remainingFlowMb": 120
},
{
"matched": true,
"iccid": "89860400000000005476",
"trafficCutoffStatus": 2,
"trafficCutoffStatusName": "已关闭",
"trafficCutoffThreshold": 50,
"trafficCutoffThresholdUnit": "MB",
"packageTotalFlowMb": 100,
"usedFlowMb": 30,
"remainingFlowMb": 70
}
]
},
"error": null
}
```
## 批次状态查询
```http
POST /openapi/v3/query/batch-status
```
根据指定批次号查询异步处理状态,支持续费、达量断网等异步批次。
请求参数:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `appId` | string | 客户组织编码 |
| `batchNo` | string | 异步 API 返回的批次号 |
请求示例:
```json
{
"appId": "CUS-202601-0001",
"batchNo": "REN20260910153546001000001"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `data.batchNo` | string | 请求中的批次号 |
| `data.batchAsyncStatus` | integer | 异步状态:`0` 已创建、`1` 待处理、`2` 处理中、`3` 成功、`4` 失败、`5` 部分成功 |
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"batchNo": "REN20260910153546001000001",
"batchAsyncStatus": 3
},
"error": null
}
```
+57
View File
@@ -0,0 +1,57 @@
---
title: 获取调用凭证
description: 使用 appKey、appSecret 换取 V3 接口调用凭证
outline: deep
---
# 获取调用凭证
```http
POST /openapi/v3/query/token
```
说明:使用 `appKey`、`appSecret` 换取接口调用凭证。
请求体:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| appKey | string | API 账号标识,在管理后台申请获取 |
| appSecret | string | API 密钥,在管理后台申请获取 |
| refresh | string | 是否刷新 token:`1` 强制生成新 token,`0` 或留空时尽量复用已有 token |
请求示例:
```json
{
"appKey": "example-app-key",
"appSecret": "example-app-secret"
}
```
响应结构:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| data.appId | string | 客户组织编码,**后续所有接口请求体中都要传入此值** |
| data.accessToken | string | 接口调用凭证,后续放入 `X-Access-Token` |
::: info 注意:appId ≠ appKey
- **`appKey`**(如 `SDYZ`)只用于获取 token,调用 `/openapi/v3/query/token` 后就不再需要
- **`appId`**(如 `CUS-202601-0001`)由 token 接口返回,后续调用 sim-info 等接口时,请求体中的 `appId` 字段必须填这个值
- 如果传错会导致 `code: 61 "登录已过期"` 错误
:::
响应示例:
```json
{
"code": 0,
"message": "",
"data": {
"appId": "CUS-202601-0001",
"accessToken": "example-access-token"
},
"error": null
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Some files were not shown because too many files have changed in this diff Show More